pax_global_header00006660000000000000000000000064147232161210014511gustar00rootroot0000000000000052 comment=672a8fad54880472a195d7bfeb0c9472518265c7 golang-github-onsi-ginkgo-v2-2.22.0/000077500000000000000000000000001472321612100170725ustar00rootroot00000000000000golang-github-onsi-ginkgo-v2-2.22.0/.github/000077500000000000000000000000001472321612100204325ustar00rootroot00000000000000golang-github-onsi-ginkgo-v2-2.22.0/.github/FUNDING.yml000066400000000000000000000000171472321612100222450ustar00rootroot00000000000000github: [onsi] golang-github-onsi-ginkgo-v2-2.22.0/.github/dependabot.yml000066400000000000000000000006411472321612100232630ustar00rootroot00000000000000version: 2 updates: - package-ecosystem: "bundler" directory: "/docs" schedule: interval: daily time: '01:00' open-pull-requests-limit: 5 - package-ecosystem: gomod directory: "/" schedule: interval: daily time: '01:00' open-pull-requests-limit: 5 - package-ecosystem: "github-actions" directory: "/" schedule: interval: daily time: '01:00' open-pull-requests-limit: 5 golang-github-onsi-ginkgo-v2-2.22.0/.github/workflows/000077500000000000000000000000001472321612100224675ustar00rootroot00000000000000golang-github-onsi-ginkgo-v2-2.22.0/.github/workflows/codeql-analysis.yml000066400000000000000000000051171472321612100263060ustar00rootroot00000000000000# For most projects, this workflow file will not need changing; you simply need # to commit it to your repository. # # You may wish to alter this file to override the set of languages analyzed, # or to provide custom queries or build logic. # # ******** NOTE ******** # We have attempted to detect the languages in your repository. Please check # the `language` matrix defined below to confirm you have the correct set of # supported CodeQL languages. # name: "CodeQL" on: push: branches: [ master ] pull_request: # The branches below must be a subset of the branches above branches: [ master ] schedule: - cron: '21 7 * * 0' permissions: contents: read jobs: analyze: permissions: actions: read # for github/codeql-action/init to get workflow details contents: read # for actions/checkout to fetch code security-events: write # for github/codeql-action/autobuild to send a status report name: Analyze runs-on: ubuntu-latest strategy: fail-fast: false matrix: language: [ 'go', 'ruby' ] # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python' ] # Learn more: # https://docs.github.com/en/free-pro-team@latest/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#changing-the-languages-that-are-analyzed steps: - name: Checkout repository uses: actions/checkout@v4 # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL uses: github/codeql-action/init@v3 with: languages: ${{ matrix.language }} # If you wish to specify custom queries, you can do so here or in a config file. # By default, queries listed here will override any specified in a config file. # Prefix the list here with "+" to use these queries and those in the config file. # queries: ./path/to/local/query, your-org/your-repo/queries@main # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). # If this step fails, then you should remove it and run the build manually (see below) - name: Autobuild uses: github/codeql-action/autobuild@v3 # ℹ️ Command-line programs to run using the OS shell. # 📚 https://git.io/JvXDl # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines # and modify them (or add more) to build your code if your project # uses a compiled language #- run: | # make bootstrap # make release - name: Perform CodeQL Analysis uses: github/codeql-action/analyze@v3 golang-github-onsi-ginkgo-v2-2.22.0/.github/workflows/test.yml000066400000000000000000000013721472321612100241740ustar00rootroot00000000000000name: test on: [push, pull_request] permissions: contents: read jobs: mod: runs-on: ubuntu-latest name: Check modules steps: - uses: actions/setup-go@v5 with: go-version: 'oldstable' - uses: actions/checkout@v4 - run: go mod tidy && git diff --exit-code go.mod go.sum build: runs-on: ubuntu-latest strategy: matrix: version: [ 'oldstable', 'stable' ] name: Go ${{ matrix.version }} steps: - uses: actions/setup-go@v5 with: go-version: ${{ matrix.version }} - uses: actions/checkout@v4 - run: go vet ./... - run: go run ./ginkgo --github-output -r -randomize-all -randomize-suites -race -trace -procs=2 -poll-progress-after=10s -poll-progress-interval=10s golang-github-onsi-ginkgo-v2-2.22.0/.gitignore000066400000000000000000000000731472321612100210620ustar00rootroot00000000000000.DS_Store TODO tmp/**/* *.coverprofile .vscode .idea/ *.loggolang-github-onsi-ginkgo-v2-2.22.0/CHANGELOG.md000066400000000000000000001265241472321612100207150ustar00rootroot00000000000000## 2.22.0 ### Features - Add label to serial nodes [0fcaa08] This allows serial tests to be filtered using the `label-filter` ### Maintenance Various doc fixes ## 2.21.0 ### Features - add support for GINKGO_TIME_FORMAT [a69eb39] - add GINKGO_NO_COLOR to disable colors via environment variables [bcab9c8] ### Fixes - increase threshold in timeline matcher [e548367] - Fix the document by replacing `SpecsThatWillBeRun` with `SpecsThatWillRun` [c2c4d3c] ### Maintenance - bump various dependencies [7e65a00] ## 2.20.2 Require Go 1.22+ ### Maintenance - bump go to v1.22 [a671816] ## 2.20.1 ### Fixes - make BeSpecEvent duration matcher more forgiving [d6f9640] ## 2.20.0 ### Features - Add buildvcs flag [be5ab95] ### Maintenance - Add update-deps to makefile [d303d14] - bump all dependencies [7a50221] ## 2.19.1 ### Fixes - update supported platforms for race conditions [63c8c30] - [build] Allow custom name for binaries. [ff41e27] ### Maintenance - bump gomega [76f4e0c] - Bump rexml from 3.2.6 to 3.2.8 in /docs (#1417) [b69c00d] - Bump golang.org/x/sys from 0.20.0 to 0.21.0 (#1425) [f097741] ## 2.19.0 ### Features [Label Sets](https://onsi.github.io/ginkgo/#label-sets) allow for more expressive and flexible label filtering. ## 2.18.0 ### Features - Add --slience-skips and --force-newlines [f010b65] - fail when no tests were run and --fail-on-empty was set [d80eebe] ### Fixes - Fix table entry context edge case [42013d6] ### Maintenance - Bump golang.org/x/tools from 0.20.0 to 0.21.0 (#1406) [fcf1fd7] - Bump github.com/onsi/gomega from 1.33.0 to 1.33.1 (#1399) [8bb14fd] - Bump golang.org/x/net from 0.24.0 to 0.25.0 (#1407) [04bfad7] ## 2.17.3 ### Fixes `ginkgo watch` now ignores hidden files [bde6e00] ## 2.17.2 ### Fixes - fix: close files [32259c8] - fix github output log level for skipped specs [780e7a3] ### Maintenance - Bump github.com/google/pprof [d91fe4e] - Bump github.com/go-task/slim-sprig to v3 [8cb662e] - Bump golang.org/x/net in /integration/_fixtures/version_mismatch_fixture (#1391) [3134422] - Bump github-pages from 230 to 231 in /docs (#1384) [eca81b4] - Bump golang.org/x/tools from 0.19.0 to 0.20.0 (#1383) [760def8] - Bump golang.org/x/net from 0.23.0 to 0.24.0 (#1381) [4ce33f4] - Fix test for gomega version bump [f2fcd97] - Bump github.com/onsi/gomega from 1.30.0 to 1.33.0 (#1390) [fd622d2] - Bump golang.org/x/tools from 0.17.0 to 0.19.0 (#1368) [5474a26] - Bump github-pages from 229 to 230 in /docs (#1359) [e6d1170] - Bump google.golang.org/protobuf from 1.28.0 to 1.33.0 (#1374) [7f447b2] - Bump golang.org/x/net from 0.20.0 to 0.23.0 (#1380) [f15239a] ## 2.17.1 ### Fixes - If the user sets --seed=0, make sure all parallel nodes get the same seed [af0330d] ## 2.17.0 ### Features - add `--github-output` for nicer output in github actions [e8a2056] ### Maintenance - fix typo in core_dsl.go [977bc6f] - Fix typo in docs [e297e7b] ## 2.16.0 ### Features - add SpecContext to reporting nodes ### Fixes - merge coverages instead of combining them (#1329) (#1340) [23f0cc5] - core_dsl: disable Getwd() with environment variable (#1357) [cd418b7] ### Maintenance - docs/index.md: Typo [2cebe8d] - fix docs [06de431] - chore: test with Go 1.22 (#1352) [898cba9] - Bump golang.org/x/tools from 0.16.1 to 0.17.0 (#1336) [17ae120] - Bump golang.org/x/sys from 0.15.0 to 0.16.0 (#1327) [5a179ed] - Bump github.com/go-logr/logr from 1.3.0 to 1.4.1 (#1321) [a1e6b69] - Bump github-pages and jekyll-feed in /docs (#1351) [d52951d] - Fix docs for handling failures in goroutines (#1339) [4471b2e] ## 2.15.0 ### Features - JUnit reports now interpret Label(owner:X) and set owner to X. [8f3bd70] - include cancellation reason when cancelling spec context [96e915c] ### Fixes - emit output of failed go tool cover invocation so users can try to debug things for themselves [c245d09] - fix outline when using nodot in ginkgo v2 [dca77c8] - Document areas where GinkgoT() behaves differently from testing.T [dbaf18f] - bugfix(docs): use Unsetenv instead of Clearenv (#1337) [6f67a14] ### Maintenance - Bump to go 1.20 [4fcd0b3] ## 2.14.0 ### Features You can now use `GinkgoTB()` when you need an instance of `testing.TB` to pass to a library. Prior to this release table testing only supported generating individual `It`s for each test entry. `DescribeTableSubtree` extends table testing support to entire testing subtrees - under the hood `DescrieTableSubtree` generates a new container for each entry and invokes your function to fill our the container. See the [docs](https://onsi.github.io/ginkgo/#generating-subtree-tables) to learn more. - Introduce DescribeTableSubtree [65ec56d] - add GinkgoTB() to docs [4a2c832] - Add GinkgoTB() function (#1333) [92b6744] ### Fixes - Fix typo in internal/suite.go (#1332) [beb9507] - Fix typo in docs/index.md (#1319) [4ac3a13] - allow wasm to compile with ginkgo present (#1311) [b2e5bc5] ### Maintenance - Bump golang.org/x/tools from 0.16.0 to 0.16.1 (#1316) [465a8ec] - Bump actions/setup-go from 4 to 5 (#1313) [eab0e40] - Bump github/codeql-action from 2 to 3 (#1317) [fbf9724] - Bump golang.org/x/crypto (#1318) [3ee80ee] - Bump golang.org/x/tools from 0.14.0 to 0.16.0 (#1306) [123e1d5] - Bump github.com/onsi/gomega from 1.29.0 to 1.30.0 (#1297) [558f6e0] - Bump golang.org/x/net from 0.17.0 to 0.19.0 (#1307) [84ff7f3] ## 2.13.2 ### Fixes - Fix file handler leak (#1309) [e2e81c8] - Avoid allocations with `(*regexp.Regexp).MatchString` (#1302) [3b2a2a7] ## 2.13.1 ### Fixes - # 1296 fix(precompiled test guite): exec bit check omitted on Windows (#1301) [26eea01] ### Maintenance - Bump github.com/go-logr/logr from 1.2.4 to 1.3.0 (#1291) [7161a9d] - Bump golang.org/x/sys from 0.13.0 to 0.14.0 (#1295) [7fc7b10] - Bump golang.org/x/tools from 0.12.0 to 0.14.0 (#1282) [74bbd65] - Bump github.com/onsi/gomega from 1.27.10 to 1.29.0 (#1290) [9373633] - Bump golang.org/x/net in /integration/_fixtures/version_mismatch_fixture (#1286) [6e3cf65] ## 2.13.0 ### Features Add PreviewSpect() to enable programmatic preview access to the suite report (fixes #1225) ## 2.12.1 ### Fixes - Print logr prefix if it exists (#1275) [90d4846] ### Maintenance - Bump actions/checkout from 3 to 4 (#1271) [555f543] - Bump golang.org/x/sys from 0.11.0 to 0.12.0 (#1270) [d867b7d] ## 2.12.0 ### Features - feat: allow MustPassRepeatedly decorator to be set at suite level (#1266) [05de518] ### Fixes - fix-errors-in-readme (#1244) [27c2f5d] ### Maintenance Various chores/dependency bumps. ## 2.11.0 In prior versions of Ginkgo specs the CLI filter flags (e.g. `--focus`, `--label-filter`) would _override_ any programmatic focus. This behavior has proved surprising and confusing in at least the following ways: - users cannot combine programmatic filters and CLI filters to more efficiently select subsets of tests - CLI filters can override programmatic focus on CI systems resulting in an exit code of 0 despite the presence of (incorrectly!) committed focused specs. Going forward Ginkgo will AND all programmatic and CLI filters. Moreover, the presence of any programmatic focused tests will always result in a non-zero exit code. This change is technically a change in Ginkgo's external contract and may require some users to make changes to successfully adopt. Specifically: it's possible some users were intentionally using CLI filters to override programmatic focus. If this is you please open an issue so we can explore solutions to the underlying problem you are trying to solve. ### Fixes - Programmatic focus is no longer overwrriten by CLI filters [d6bba86] ### Maintenance - Bump github.com/onsi/gomega from 1.27.7 to 1.27.8 (#1218) [4a70a38] - Bump golang.org/x/sys from 0.8.0 to 0.9.0 (#1219) [97eda4d] ## 2.10.0 ### Features - feat(ginkgo/generators): add --tags flag (#1216) [a782a77] adds a new --tags flag to ginkgo generate ### Fixes - Fix broken link of MIGRATING_TO_V2.md (#1217) [548d78e] ### Maintenance - Bump golang.org/x/tools from 0.9.1 to 0.9.3 (#1215) [2b76a5e] ## 2.9.7 ### Fixes - fix race when multiple defercleanups are called in goroutines [07fc3a0] ## 2.9.6 ### Fixes - fix: create parent directory before report files (#1212) [0ac65de] ### Maintenance - Bump github.com/onsi/gomega from 1.27.6 to 1.27.7 (#1202) [3e39231] ## 2.9.5 ### Fixes - ensure the correct deterministic sort order is produced when ordered specs are generated by a helper function [7fa0b6b] ### Maintenance - fix generators link (#1200) [9f9d8b9] - Bump golang.org/x/tools from 0.8.0 to 0.9.1 (#1196) [150e3f2] - fix spelling err in docs (#1199) [0013b1a] - Bump golang.org/x/sys from 0.7.0 to 0.8.0 (#1193) [9e9e3e5] ## 2.9.4 ### Fixes - fix hang with ginkgo -p (#1192) [15d4bdc] - this addresses a _long_ standing issue related to Ginkgo hanging when a child process spawned by the test does not exit. - fix: fail fast may cause Serial spec or cleanup Node interrupted (#1178) [8dea88b] - prior to this there was a small gap in which specs on other processes might start even if one process has tried to abort the suite. ### Maintenance - Document run order when multiple setup nodes are at the same nesting level [903be81] ## 2.9.3 ### Features - Add RenderTimeline to GinkgoT() [c0c77b6] ### Fixes - update Measure deprecation message. fixes #1176 [227c662] - add newlines to GinkgoLogr (#1170) (#1171) [0de0e7c] ### Maintenance - Bump commonmarker from 0.23.8 to 0.23.9 in /docs (#1183) [8b925ab] - Bump nokogiri from 1.14.1 to 1.14.3 in /docs (#1184) [e3795a4] - Bump golang.org/x/tools from 0.7.0 to 0.8.0 (#1182) [b453793] - Bump actions/setup-go from 3 to 4 (#1164) [73ed75b] - Bump github.com/onsi/gomega from 1.27.4 to 1.27.6 (#1173) [0a2bc64] - Bump github.com/go-logr/logr from 1.2.3 to 1.2.4 (#1174) [f41c557] - Bump golang.org/x/sys from 0.6.0 to 0.7.0 (#1179) [8e423e5] ## 2.9.2 ### Maintenance - Bump github.com/go-task/slim-sprig (#1167) [3fcc5bf] - Bump github.com/onsi/gomega from 1.27.3 to 1.27.4 (#1163) [6143ffe] ## 2.9.1 ### Fixes This release fixes a longstanding issue where `ginkgo -coverpkg=./...` would not work. This is now resolved and fixes [#1161](https://github.com/onsi/ginkgo/issues/1161) and [#995](https://github.com/onsi/ginkgo/issues/995) - Support -coverpkg=./... [26ca1b5] - document coverpkg a bit more clearly [fc44c3b] ### Maintenance - bump various dependencies - Improve Documentation and fix typo (#1158) [93de676] ## 2.9.0 ### Features - AttachProgressReporter is an experimental feature that allows users to provide arbitrary information when a ProgressReport is requested [28801fe] - GinkgoT() has been expanded to include several Ginkgo-specific methods [2bd5a3b] The intent is to enable the development of third-party libraries that integrate deeply with Ginkgo using `GinkgoT()` to access Ginkgo's functionality. ## 2.8.4 ### Features - Add OmitSuiteSetupNodes to JunitReportConfig (#1147) [979fbc2] - Add a reference to ginkgolinter in docs.index.md (#1143) [8432589] ### Fixes - rename tools hack to see if it fixes things for downstream users [a8bb39a] ### Maintenance - Bump golang.org/x/text (#1144) [41b2a8a] - Bump github.com/onsi/gomega from 1.27.0 to 1.27.1 (#1142) [7c4f583] ## 2.8.3 Released to fix security issue in golang.org/x/net dependency ### Maintenance - Bump golang.org/x/net from 0.6.0 to 0.7.0 (#1141) [fc1a02e] - remove tools.go hack from documentation [0718693] ## 2.8.2 Ginkgo now includes a `tools.go` file in the root directory of the `ginkgo` package. This should allow modules that simply `go get github.com/onsi/ginkgo/v2` to also pull in the CLI dependencies. This obviates the need for consumers of Ginkgo to have their own `tools.go` file and makes it simpler to ensure that the version of the `ginkgo` CLI being used matches the version of the library. You can simply run `go run github.com/onsi/ginkgo/v2/ginkgo` to run the version of the cli associated with your package go.mod. ### Maintenance - Bump github.com/onsi/gomega from 1.26.0 to 1.27.0 (#1139) [5767b0a] - Fix minor typos (#1138) [e1e9723] - Fix link in V2 Migration Guide (#1137) [a588f60] ## 2.8.1 ### Fixes - lock around default report output to avoid triggering the race detector when calling By from goroutines [2d5075a] - don't run ReportEntries through sprintf [febbe38] ### Maintenance - Bump golang.org/x/tools from 0.5.0 to 0.6.0 (#1135) [11a4860] - test: update matrix for Go 1.20 (#1130) [4890a62] - Bump golang.org/x/sys from 0.4.0 to 0.5.0 (#1133) [a774638] - Bump github.com/onsi/gomega from 1.25.0 to 1.26.0 (#1120) [3f233bd] - Bump github-pages from 227 to 228 in /docs (#1131) [f9b8649] - Bump activesupport from 6.0.6 to 6.0.6.1 in /docs (#1127) [6f8c042] - Update index.md with instructions on how to upgrade Ginkgo [833a75e] ## 2.8.0 ### Features - Introduce GinkgoHelper() to track and exclude helper functions from potential CodeLocations [e19f556] Modeled after `testing.T.Helper()`. Now, rather than write code like: ```go func helper(model Model) { Expect(model).WithOffset(1).To(BeValid()) Expect(model.SerialNumber).WithOffset(1).To(MatchRegexp(/[a-f0-9]*/)) } ``` you can stop tracking offsets (which makes nesting composing helpers nearly impossible) and simply write: ```go func helper(model Model) { GinkgoHelper() Expect(model).To(BeValid()) Expect(model.SerialNumber).To(MatchRegexp(/[a-f0-9]*/)) } ``` - Introduce GinkgoLabelFilter() and Label().MatchesLabelFilter() to make it possible to programmatically match filters (fixes #1119) [2f6597c] You can now write code like this: ```go BeforeSuite(func() { if Label("slow").MatchesLabelFilter(GinkgoLabelFilter()) { // do slow setup } if Label("fast").MatchesLabelFilter(GinkgoLabelFilter()) { // do fast setup } }) ``` to programmatically check whether a given set of labels will match the configured `--label-filter`. ### Maintenance - Bump webrick from 1.7.0 to 1.8.1 in /docs (#1125) [ea4966e] - cdeql: add ruby language (#1124) [9dd275b] - dependabot: add bundler package-ecosystem for docs (#1123) [14e7bdd] ## 2.7.1 ### Fixes - Bring back SuiteConfig.EmitSpecProgress to avoid compilation issue for consumers that set it manually [d2a1cb0] ### Maintenance - Bump github.com/onsi/gomega from 1.24.2 to 1.25.0 (#1118) [cafece6] - Bump golang.org/x/tools from 0.4.0 to 0.5.0 (#1111) [eda66c2] - Bump golang.org/x/sys from 0.3.0 to 0.4.0 (#1112) [ac5ccaa] - Bump github.com/onsi/gomega from 1.24.1 to 1.24.2 (#1097) [eee6480] ## 2.7.0 ### Features - Introduce ContinueOnFailure for Ordered containers [e0123ca] - Ordered containers that are also decorated with ContinueOnFailure will not stop running specs after the first spec fails. - Support for bootstrap commands to use custom data for templates (#1110) [7a2b242] - Support for labels and pending decorator in ginkgo outline output (#1113) [e6e3b98] - Color aliases for custom color support (#1101) [49fab7a] ### Fixes - correctly ensure deterministic spec order, even if specs are generated by iterating over a map [89dda20] - Fix a bug where timedout specs were not correctly treated as failures when determining whether or not to run AfterAlls in an Ordered container. - Ensure go test coverprofile outputs to the expected location (#1105) [b0bd77b] ## 2.6.1 ### Features - Override formatter colors from envvars - this is a new feature but an alternative approach involving config files might be taken in the future (#1095) [60240d1] ### Fixes - GinkgoRecover now supports ignoring panics that match a specific, hidden, interface [301f3e2] ### Maintenance - Bump github.com/onsi/gomega from 1.24.0 to 1.24.1 (#1077) [3643823] - Bump golang.org/x/tools from 0.2.0 to 0.4.0 (#1090) [f9f856e] - Bump nokogiri from 1.13.9 to 1.13.10 in /docs (#1091) [0d7087e] ## 2.6.0 ### Features - `ReportBeforeSuite` provides access to the suite report before the suite begins. - Add junit config option for omitting leafnodetype (#1088) [956e6d2] - Add support to customize junit report config to omit spec labels (#1087) [de44005] ### Fixes - Fix stack trace pruning so that it has a chance of working on windows [2165648] ## 2.5.1 ### Fixes - skipped tests only show as 'S' when running with -v [3ab38ae] - Fix typo in docs/index.md (#1082) [55fc58d] - Fix typo in docs/index.md (#1081) [8a14f1f] - Fix link notation in docs/index.md (#1080) [2669612] - Fix typo in `--progress` deprecation message (#1076) [b4b7edc] ### Maintenance - chore: Included githubactions in the dependabot config (#976) [baea341] - Bump golang.org/x/sys from 0.1.0 to 0.2.0 (#1075) [9646297] ## 2.5.0 ### Ginkgo output now includes a timeline-view of the spec This commit changes Ginkgo's default output. Spec details are now presented as a **timeline** that includes events that occur during the spec lifecycle interleaved with any GinkgoWriter content. This makes is much easier to understand the flow of a spec and where a given failure occurs. The --progress, --slow-spec-threshold, --always-emit-ginkgo-writer flags and the SuppressProgressReporting decorator have all been deprecated. Instead the existing -v and -vv flags better capture the level of verbosity to display. However, a new --show-node-events flag is added to include node `> Enter` and `< Exit` events in the spec timeline. In addition, JUnit reports now include the timeline (rendered with -vv) and custom JUnit reports can be configured and generated using `GenerateJUnitReportWithConfig(report types.Report, dst string, config JunitReportConfig)` Code should continue to work unchanged with this version of Ginkgo - however if you have tooling that was relying on the specific output format of Ginkgo you _may_ run into issues. Ginkgo's console output is not guaranteed to be stable for tooling and automation purposes. You should, instead, use Ginkgo's JSON format to build tooling on top of as it has stronger guarantees to be stable from version to version. ### Features - Provide details about which timeout expired [0f2fa27] ### Fixes - Add Support Policy to docs [c70867a] ### Maintenance - Bump github.com/onsi/gomega from 1.22.1 to 1.23.0 (#1070) [bb3b4e2] ## 2.4.0 ### Features - DeferCleanup supports functions with multiple-return values [5e33c75] - Add GinkgoLogr (#1067) [bf78c28] - Introduction of 'MustPassRepeatedly' decorator (#1051) [047c02f] ### Fixes - correcting some typos (#1064) [1403d3c] - fix flaky internal_integration interrupt specs [2105ba3] - Correct busted link in README [be6b5b9] ### Maintenance - Bump actions/checkout from 2 to 3 (#1062) [8a2f483] - Bump golang.org/x/tools from 0.1.12 to 0.2.0 (#1065) [529c4e8] - Bump github/codeql-action from 1 to 2 (#1061) [da09146] - Bump actions/setup-go from 2 to 3 (#1060) [918040d] - Bump github.com/onsi/gomega from 1.22.0 to 1.22.1 (#1053) [2098e4d] - Bump nokogiri from 1.13.8 to 1.13.9 in /docs (#1066) [1d74122] - Add GHA to dependabot config [4442772] ## 2.3.1 ## Fixes Several users were invoking `ginkgo` by installing the latest version of the cli via `go install github.com/onsi/ginkgo/v2/ginkgo@latest`. When 2.3.0 was released this resulted in an influx of issues as CI systems failed due to a change in the internal contract between the Ginkgo CLI and the Ginkgo library. Ginkgo only supports running the same version of the library as the cli (which is why both are packaged in the same repository). With this patch release, the ginkgo CLI can now identify a version mismatch and emit a helpful error message. - Ginkgo cli can identify version mismatches and emit a helpful error message [bc4ae2f] - further emphasize that a version match is required when running Ginkgo on CI and/or locally [2691dd8] ### Maintenance - bump gomega to v1.22.0 [822a937] ## 2.3.0 ### Interruptible Nodes and Timeouts Ginkgo now supports per-node and per-spec timeouts on interruptible nodes. Check out the [documentation for all the details](https://onsi.github.io/ginkgo/#spec-timeouts-and-interruptible-nodes) but the gist is you can now write specs like this: ```go It("is interruptible", func(ctx SpecContext) { // or context.Context instead of SpecContext, both are valid. // do things until `ctx.Done()` is closed, for example: req, err := http.NewRequestWithContext(ctx, "POST", "/build-widgets", nil) Expect(err).NotTo(HaveOccured()) _, err := http.DefaultClient.Do(req) Expect(err).NotTo(HaveOccured()) Eventually(client.WidgetCount).WithContext(ctx).Should(Equal(17)) }, NodeTimeout(time.Second*20), GracePeriod(5*time.Second)) ``` and have Ginkgo ensure that the node completes before the timeout elapses. If it does elapse, or if an external interrupt is received (e.g. `^C`) then Ginkgo will cancel the context and wait for the Grace Period for the node to exit before proceeding with any cleanup nodes associated with the spec. The `ctx` provided by Ginkgo can also be passed down to Gomega's `Eventually` to have all assertions within the node governed by a single deadline. ### Features - Ginkgo now records any additional failures that occur during the cleanup of a failed spec. In prior versions this information was quietly discarded, but the introduction of a more rigorous approach to timeouts and interruptions allows Ginkgo to better track subsequent failures. - `SpecContext` also provides a mechanism for third-party libraries to provide additional information when a Progress Report is generated. Gomega uses this to provide the current state of an `Eventually().WithContext()` assertion when a Progress Report is requested. - DescribeTable now exits with an error if it is not passed any Entries [a4c9865] ## Fixes - fixes crashes on newer Ruby 3 installations by upgrading github-pages gem dependency [92c88d5] - Make the outline command able to use the DSL import [1be2427] ## Maintenance - chore(docs): delete no meaning d [57c373c] - chore(docs): Fix hyperlinks [30526d5] - chore(docs): fix code blocks without language settings [cf611c4] - fix intra-doc link [b541bcb] ## 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 - Add `SuppressProgressReporting` decorator to turn off --progress announcements for a given node [dfef62a] - chore: remove duplicate word in comments [7373214] ## 2.1.5 ### Fixes - drop -mod=mod instructions; fixes #1026 [6ad7138] - Ensure `CurrentSpecReport` and `AddReportEntry` are thread-safe [817c09b] - remove stale importmap gcflags flag test [3cd8b93] - Always emit spec summary [5cf23e2] - even when only one spec has failed - Fix ReportAfterSuite usage in docs [b1864ad] - fixed typo (#997) [219cc00] - TrimRight is not designed to trim Suffix [71ebb74] - refactor: replace strings.Replace with strings.ReplaceAll (#978) [143d208] - fix syntax in examples (#975) [b69554f] ### Maintenance - Bump github.com/onsi/gomega from 1.20.0 to 1.20.1 (#1027) [e5dfce4] - Bump tzinfo from 1.2.9 to 1.2.10 in /docs (#1006) [7ae91c4] - Bump github.com/onsi/gomega from 1.19.0 to 1.20.0 (#1005) [e87a85a] - test: add new Go 1.19 to test matrix (#1014) [bbefe12] - Bump golang.org/x/tools from 0.1.11 to 0.1.12 (#1012) [9327906] - Bump golang.org/x/tools from 0.1.10 to 0.1.11 (#993) [f44af96] - Bump nokogiri from 1.13.3 to 1.13.6 in /docs (#981) [ef336aa] ## 2.1.4 ### Fixes - Numerous documentation typos - Prepend `when` when using `When` (this behavior was in 1.x but unintentionally lost during the 2.0 rewrite) [efce903] - improve error message when a parallel process fails to report back [a7bd1fe] - guard against concurrent map writes in DeprecationTracker [0976569] - Invoke reporting nodes during dry-run (fixes #956 and #935) [aae4480] - Fix ginkgo import circle [f779385] ## 2.1.3 See [https://onsi.github.io/ginkgo/MIGRATING_TO_V2](https://onsi.github.io/ginkgo/MIGRATING_TO_V2) for details on V2. ### Fixes - Calling By in a container node now emits a useful error. [ff12cee] ## 2.1.2 ### Fixes - Track location of focused specs correctly in `ginkgo unfocus` [a612ff1] - Profiling suites with focused specs no longer generates an erroneous failure message [8fbfa02] - Several documentation typos fixed. Big thanks to everyone who helped catch them and report/fix them! ## 2.1.1 See [https://onsi.github.io/ginkgo/MIGRATING_TO_V2](https://onsi.github.io/ginkgo/MIGRATING_TO_V2) for details on V2. ### Fixes - Suites that only import the new dsl packages are now correctly identified as Ginkgo suites [ec17e17] ## 2.1.0 See [https://onsi.github.io/ginkgo/MIGRATING_TO_V2](https://onsi.github.io/ginkgo/MIGRATING_TO_V2) for details on V2. 2.1.0 is a minor release with a few tweaks: - Introduce new DSL packages to enable users to pick-and-choose which portions of the DSL to dot-import. [90868e2] More details [here](https://onsi.github.io/ginkgo/#alternatives-to-dot-importing-ginkgo). - Add error check for invalid/nil parameters to DescribeTable [6f8577e] - Myriad docs typos fixed (thanks everyone!) [718542a, ecb7098, 146654c, a8f9913, 6bdffde, 03dcd7e] ## 2.0.0 See [https://onsi.github.io/ginkgo/MIGRATING_TO_V2](https://onsi.github.io/ginkgo/MIGRATING_TO_V2) ## 1.16.5 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_GINKGO_RC=true` environment variable or creating a file in your home directory called `.ack-ginkgo-rc` ## 1.16.4 ### Fixes 1.16.4 retracts 1.16.3. There are no code changes. The 1.16.3 tag was associated with the wrong commit and an attempt to change it after-the-fact has proven problematic. 1.16.4 retracts 1.16.3 in Ginkgo's go.mod and creates a new, correctly tagged, release. ## 1.16.3 ### Features - Measure is now deprecated and emits a deprecation warning. ## 1.16.2 ### Fixes - Deprecations can be suppressed by setting an `ACK_GINKGO_DEPRECATIONS=` environment variable. ## 1.16.1 ### Fixes - Suppress --stream deprecation warning on windows (#793) ## 1.16.0 ### Features - Advertise Ginkgo 2.0. Introduce deprecations. [9ef1913] - Update README.md to advertise that Ginkgo 2.0 is coming. - Backport the 2.0 DeprecationTracker and start alerting users about upcoming deprecations. - Add slim-sprig template functions to bootstrap/generate (#775) [9162b86] - Fix accidental reference to 1488 (#784) [9fb7fe4] ## 1.15.2 ### Fixes - ignore blank `-focus` and `-skip` flags (#780) [e90a4a0] ## 1.15.1 ### Fixes - reporters/junit: Use `system-out` element instead of `passed` (#769) [9eda305] ## 1.15.0 ### Features - Adds 'outline' command to print the outline of specs/containers in a file (#754) [071c369] [6803cc3] [935b538] [06744e8] [0c40583] - Add support for using template to generate tests (#752) [efb9e69] - Add a Chinese Doc #755 (#756) [5207632] - cli: allow multiple -focus and -skip flags (#736) [9a782fb] ### Fixes - Add _internal to filename of tests created with internal flag (#751) [43c12da] ## 1.14.2 ### Fixes - correct handling windows backslash in import path (#721) [97f3d51] - Add additional methods to GinkgoT() to improve compatibility with the testing.TB interface [b5fe44d] ## 1.14.1 ### Fixes - Discard exported method declaration when running ginkgo bootstrap (#558) [f4b0240] ## 1.14.0 ### Features - Defer running top-level container nodes until RunSpecs is called [d44dedf] - [Document Ginkgo lifecycle](http://onsi.github.io/ginkgo/#understanding-ginkgos-lifecycle) - Add `extensions/globals` package (#692) [3295c8f] - this can be helpful in contexts where you are test-driving your test-generation code (see [#692](https://github.com/onsi/ginkgo/pull/692)) - Print Skip reason in JUnit reporter if one was provided [820dfab] ## 1.13.0 ### Features - Add a version of table.Entry that allows dumping the entry parameters. (#689) [21eaef2] ### Fixes - Ensure integration tests pass in an environment sans GOPATH [606fba2] - Add books package (#568) [fc0e44e] - doc(readme): installation via "tools package" (#677) [83bb20e] - Solve the undefined: unix.Dup2 compile error on mips64le (#680) [0624f75] - Import package without dot (#687) [6321024] - Fix integration tests to stop require GOPATH (#686) [a912ec5] ## 1.12.3 ### Fixes - Print correct code location of failing table test (#666) [c6d7afb] ## 1.12.2 ### Fixes - Update dependencies [ea4a036] ## 1.12.1 ### Fixes - Make unfocus ("blur") much faster (#674) [8b18061] - Fix typo (#673) [7fdcbe8] - Test against 1.14 and remove 1.12 [d5c2ad6] - Test if a coverprofile content is empty before checking its latest character (#670) [14d9fa2] - 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 successful -> successful (#663) [1ea49cf] - Fix invalid link (#658) [b886136] - convert utility : Include comments from source (#657) [1077c6d] - Explain what BDD means [d79e7fb] - skip race detector test on unsupported platform (#642) [f8ab89d] - Use Dup2 from golang.org/x/sys/unix instead of syscallDup (#638) [5d53c55] - Fix missing newline in combined coverage file (#641) [6a07ea2] - check if a spec is run before returning SpecSummary (#645) [8850000] ## 1.12.0 ### Features - Add module definition (#630) [78916ab] ## 1.11.0 ### Features - Add syscall for riscv64 architecture [f66e896] - teamcity reporter: output location of test failure as well as test definition (#626) [9869142] - teamcity reporter: output newline after every service message (#625) [3cfa02d] - Add support for go module when running `generate` command (#578) [9c89e3f] ## 1.10.3 ### Fixes - Set go_import_path in travis.yml to allow internal packages in forks (#607) [3b721db] - Add integration test [d90e0dc] - Fix coverage files combining [e5dde8c] - A new CLI option: -ginkgo.reportFile (#601) [034fd25] ## 1.10.2 ### Fixes - speed up table entry generateIt() (#609) [5049dc5] - Fix. Write errors to stderr instead of stdout (#610) [7bb3091] ## 1.10.1 ### Fixes - stack backtrace: fix skipping (#600) [2a4c0bd] ## 1.10.0 ### Fixes - stack backtrace: fix alignment and skipping [66915d6] - fix typo in documentation [8f97b93] ## 1.9.0 ### Features - Option to print output into report, when tests have passed [0545415] ### Fixes - Fixed typos in comments [0ecbc58] - gofmt code [a7f8bfb] - Simplify code [7454d00] - Simplify concatenation, incrementation and function assignment [4825557] - Avoid unnecessary conversions [9d9403c] - JUnit: include more detailed information about panic [19cca4b] - Print help to stdout when the user asks for help [4cb7441] ## 1.8.0 ### New Features - allow config of the vet flag for `go test` (#562) [3cd45fa] - Support projects using go modules [d56ee76] ### Fixes and Minor Improvements - chore(godoc): fixes typos in Measurement funcs [dbaca8e] - Optimize focus to avoid allocations [f493786] - Ensure generated test file names are underscored [505cc35] ## 1.7.0 ### New Features - Add JustAfterEach (#484) [0d4f080] ### Fixes - Correctly round suite time in junit reporter [2445fc1] - Avoid using -i argument to go test for Golang 1.10+ [46bbc26] ## 1.6.0 ### New Features - add --debug flag to emit node output to files (#499) [39febac] ### Fixes - fix: for `go vet` to pass [69338ec] - docs: fix for contributing instructions [7004cb1] - consolidate and streamline contribution docs (#494) [d848015] - Make generated Junit file compatible with "Maven Surefire" (#488) [e51bee6] - all: gofmt [000d317] - Increase eventually timeout to 30s [c73579c] - Clarify asynchronous test behavior [294d8f4] - Travis badge should only show master [26d2143] ## 1.5.0 5/10/2018 ### New Features - Supports go v1.10 (#443, #446, #451) [e873237, 468e89e, e37dbfe, a37f4c0, c0b857d, bca5260, 4177ca8] - Add a When() synonym for Context() (#386) [747514b, 7484dad, 7354a07, dd826c8] - Re-add noisySkippings flag [652e15c] - Allow coverage to be displayed for focused specs (#367) [11459a8] - Handle -outputdir flag (#364) [228e3a8] - Handle -coverprofile flag (#355) [43392d5] ### Fixes - When using custom reporters register the custom reporters *before* the default reporter. This allows users to see the output of any print statements in their customer reporters. (#365) [8382b23] - 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 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] - 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] - Use fmt.Errorf instead of errors.New(fmt.Sprintf (#401) [a299f56, 44e2eaa] - Imports in generated code should follow conventions (#398) [0bec0b0, e8536d8] - Prevent data race error when Recording a benchmark value from multiple go routines (#390) [c0c4881, 7a241e9] - Replace GOPATH in Environment [4b883f0] ## 1.4.0 7/16/2017 - `ginkgo` now provides a hint if you accidentally forget to run `ginkgo bootstrap` to generate a `*_suite_test.go` file that actually invokes the Ginkgo test runner. [#345](https://github.com/onsi/ginkgo/pull/345) - thanks to improvements in `go test -c` `ginkgo` no longer needs to fix Go's compilation output to ensure compilation errors are expressed relative to the CWD. [#357] - `ginkgo watch -watchRegExp=...` allows you to specify a custom regular expression to watch. Only files matching the regular expression are watched for changes (the default is `\.go$`) [#356] - `ginkgo` now always emits compilation output. Previously, only failed compilation output was printed out. [#277] - `ginkgo -requireSuite` now fails the test run if there are `*_test.go` files but `go test` fails to detect any tests. Typically this means you forgot to run `ginkgo bootstrap` to generate a suite file. [#344] - `ginkgo -timeout=DURATION` allows you to adjust the timeout for the entire test suite (default is 24 hours) [#248] ## 1.3.0 3/28/2017 Improvements: - Significantly improved parallel test distribution. Now instead of pre-sharding test cases across workers (which can result in idle workers and poor test performance) Ginkgo uses a shared queue to keep all workers busy until all tests are complete. This improves test-time performance and consistency. - `Skip(message)` can be used to skip the current test. - Added `extensions/table` - a Ginkgo DSL for [Table Driven Tests](http://onsi.github.io/ginkgo/#table-driven-tests) - Add `GinkgoRandomSeed()` - shorthand for `config.GinkgoConfig.RandomSeed` - Support for retrying flaky tests with `--flakeAttempts` - `ginkgo ./...` now recurses as you'd expect - Added `Specify` a synonym for `It` - Support colorise on Windows - Broader support for various go compilation flags in the `ginkgo` CLI Bug Fixes: - Ginkgo tests now fail when you `panic(nil)` (#167) ## 1.2.0 5/31/2015 Improvements - `ginkgo -coverpkg` calls down to `go test -coverpkg` (#160) - `ginkgo -afterSuiteHook COMMAND` invokes the passed-in `COMMAND` after a test suite completes (#152) - Relaxed requirement for Go 1.4+. `ginkgo` now works with Go v1.3+ (#166) ## 1.2.0-beta Ginkgo now requires Go 1.4+ Improvements: - Call reporters in reverse order when announcing spec completion -- allows custom reporters to emit output before the default reporter does. - Improved focus behavior. Now, this: ```golang FDescribe("Some describe", func() { It("A", func() {}) FIt("B", func() {}) }) ``` will run `B` but *not* `A`. This tends to be a common usage pattern when in the thick of writing and debugging tests. - When `SIGINT` is received, Ginkgo will emit the contents of the `GinkgoWriter` before running the `AfterSuite`. Useful for debugging stuck tests. - When `--progress` is set, Ginkgo will write test progress (in particular, Ginkgo will say when it is about to run a BeforeEach, AfterEach, It, etc...) to the `GinkgoWriter`. This is useful for debugging stuck tests and tests that generate many logs. - Improved output when an error occurs in a setup or teardown block. - When `--dryRun` is set, Ginkgo will walk the spec tree and emit to its reporter *without* actually running anything. Best paired with `-v` to understand which specs will run in which order. - Add `By` to help document long `It`s. `By` simply writes to the `GinkgoWriter`. - Add support for precompiled tests: - `ginkgo build ` will now compile the package, producing a file named `package.test` - The compiled `package.test` file can be run directly. This runs the tests in series. - To run precompiled tests in parallel, you can run: `ginkgo -p package.test` - Support `bootstrap`ping and `generate`ing [Agouti](http://agouti.org) specs. - `ginkgo generate` and `ginkgo bootstrap` now honor the package name already defined in a given directory - The `ginkgo` CLI ignores `SIGQUIT`. Prevents its stack dump from interlacing with the underlying test suite's stack dump. - The `ginkgo` CLI now compiles tests into a temporary directory instead of the package directory. This necessitates upgrading to Go v1.4+. - `ginkgo -notify` now works on Linux Bug Fixes: - If --skipPackages is used and all packages are skipped, Ginkgo should exit 0. - Fix tempfile leak when running in parallel - Fix incorrect failure message when a panic occurs during a parallel test run - Fixed an issue where a pending test within a focused context (or a focused test within a pending context) would skip all other tests. - Be more consistent about handling SIGTERM as well as SIGINT - When interrupted while concurrently compiling test suites in the background, Ginkgo now cleans up the compiled artifacts. - Fixed a long standing bug where `ginkgo -p` would hang if a process spawned by one of the Ginkgo parallel nodes does not exit. (Hooray!) ## 1.1.0 (8/2/2014) No changes, just dropping the beta. ## 1.1.0-beta (7/22/2014) New Features: - `ginkgo watch` now monitors packages *and their dependencies* for changes. The depth of the dependency tree can be modified with the `-depth` flag. - Test suites with a programmatic focus (`FIt`, `FDescribe`, etc...) exit with non-zero status code, even when they pass. This allows CI systems to detect accidental commits of focused test suites. - `ginkgo -p` runs the testsuite in parallel with an auto-detected number of nodes. - `ginkgo -tags=TAG_LIST` passes a list of tags down to the `go build` command. - `ginkgo --failFast` aborts the test suite after the first failure. - `ginkgo generate file_1 file_2` can take multiple file arguments. - Ginkgo now summarizes any spec failures that occurred at the end of the test run. - `ginkgo --randomizeSuites` will run tests *suites* in random order using the generated/passed-in seed. Improvements: - `ginkgo -skipPackage` now takes a comma-separated list of strings. If the *relative path* to a package matches one of the entries in the comma-separated list, that package is skipped. - `ginkgo --untilItFails` no longer recompiles between attempts. - Ginkgo now panics when a runnable node (`It`, `BeforeEach`, `JustBeforeEach`, `AfterEach`, `Measure`) is nested within another runnable node. This is always a mistake. Any test suites that panic because of this change should be fixed. Bug Fixes: - `ginkgo boostrap` and `ginkgo generate` no longer fail when dealing with `hyphen-separated-packages`. - parallel specs are now better distributed across nodes - fixed a crashing bug where (for example) distributing 11 tests across 7 nodes would panic ## 1.0.0 (5/24/2014) New Features: - Add `GinkgoParallelNode()` - shorthand for `config.GinkgoConfig.ParallelNode` Improvements: - When compilation fails, the compilation output is rewritten to present a correct *relative* path. Allows ⌘-clicking in iTerm open the file in your text editor. - `--untilItFails` and `ginkgo watch` now generate new random seeds between test runs, unless a particular random seed is specified. Bug Fixes: - `-cover` now generates a correctly combined coverprofile when running with in parallel with multiple `-node`s. - Print out the contents of the `GinkgoWriter` when `BeforeSuite` or `AfterSuite` fail. - Fix all remaining race conditions in Ginkgo's test suite. ## 1.0.0-beta (4/14/2014) Breaking changes: - `thirdparty/gomocktestreporter` is gone. Use `GinkgoT()` instead - Modified the Reporter interface - `watch` is now a subcommand, not a flag. DSL changes: - `BeforeSuite` and `AfterSuite` for setting up and tearing down test suites. - `AfterSuite` is triggered on interrupt (`^C`) as well as exit. - `SynchronizedBeforeSuite` and `SynchronizedAfterSuite` for setting up and tearing down singleton resources across parallel nodes. CLI changes: - `watch` is now a subcommand, not a flag - `--nodot` flag can be passed to `ginkgo generate` and `ginkgo bootstrap` to avoid dot imports. This explicitly imports all exported identifiers in Ginkgo and Gomega. Refreshing this list can be done by running `ginkgo nodot` - Additional arguments can be passed to specs. Pass them after the `--` separator - `--skipPackage` flag takes a regexp and ignores any packages with package names passing said regexp. - `--trace` flag prints out full stack traces when errors occur, not just the line at which the error occurs. Misc: - Start using semantic versioning - Start maintaining changelog Major refactor: - Pull out Ginkgo's internal to `internal` - Rename `example` everywhere to `spec` - Much more! golang-github-onsi-ginkgo-v2-2.22.0/CONTRIBUTING.md000066400000000000000000000016451472321612100213310ustar00rootroot00000000000000# Contributing to Ginkgo Your contributions to Ginkgo are essential for its long-term maintenance and improvement. - Please **open an issue first** - describe what problem you are trying to solve and give the community a forum for input and feedback ahead of investing time in writing code! - Ensure adequate test coverage: - When adding to the Ginkgo library, add unit and/or integration tests (under the `integration` folder). - When adding to the Ginkgo CLI, note that there are very few unit tests. Please add an integration test. - Run `make` or: - Install ginkgo locally via `go install ./...` - Make sure all the tests succeed via `ginkgo -r -p` - Vet your changes via `go vet ./...` - Update the documentation. Ginkgo uses `godoc` comments and documentation in `docs/index.md`. You can run `bundle && bundle exec jekyll serve` in the `docs` directory to preview your changes. Thanks for supporting Ginkgo! golang-github-onsi-ginkgo-v2-2.22.0/LICENSE000066400000000000000000000020461472321612100201010ustar00rootroot00000000000000Copyright (c) 2013-2014 Onsi Fakhouri Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. golang-github-onsi-ginkgo-v2-2.22.0/Makefile000066400000000000000000000003701472321612100205320ustar00rootroot00000000000000# default task since it's first .PHONY: all all: vet test .PHONY: test test: go run github.com/onsi/ginkgo/v2/ginkgo -r -p -randomize-all -keep-going .PHONY: vet vet: go vet ./... .PHONY: update-deps update-deps: go get -u ./... go mod tidygolang-github-onsi-ginkgo-v2-2.22.0/README.md000066400000000000000000000165731472321612100203650ustar00rootroot00000000000000![Ginkgo](https://onsi.github.io/ginkgo/images/ginkgo.png) [![test](https://github.com/onsi/ginkgo/workflows/test/badge.svg?branch=master)](https://github.com/onsi/ginkgo/actions?query=workflow%3Atest+branch%3Amaster) | [Ginkgo Docs](https://onsi.github.io/ginkgo/) --- # Ginkgo Ginkgo is a mature testing framework for Go designed to help you write expressive specs. Ginkgo builds on top of Go's `testing` foundation and is complemented by the [Gomega](https://github.com/onsi/gomega) matcher library. Together, Ginkgo and Gomega let you express the intent behind your specs clearly: ```go import ( . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" ... ) var _ = Describe("Checking books out of the library", Label("library"), func() { var library *libraries.Library var book *books.Book var valjean *users.User BeforeEach(func() { library = libraries.NewClient() book = &books.Book{ Title: "Les Miserables", Author: "Victor Hugo", } valjean = users.NewUser("Jean Valjean") }) When("the library has the book in question", func() { BeforeEach(func(ctx SpecContext) { Expect(library.Store(ctx, book)).To(Succeed()) }) Context("and the book is available", func() { It("lends it to the reader", func(ctx SpecContext) { Expect(valjean.Checkout(ctx, library, "Les Miserables")).To(Succeed()) Expect(valjean.Books()).To(ContainElement(book)) Expect(library.UserWithBook(ctx, book)).To(Equal(valjean)) }, SpecTimeout(time.Second * 5)) }) Context("but the book has already been checked out", func() { var javert *users.User BeforeEach(func(ctx SpecContext) { javert = users.NewUser("Javert") Expect(javert.Checkout(ctx, library, "Les Miserables")).To(Succeed()) }) It("tells the user", func(ctx SpecContext) { err := valjean.Checkout(ctx, library, "Les Miserables") Expect(err).To(MatchError("Les Miserables is currently checked out")) }, SpecTimeout(time.Second * 5)) It("lets the user place a hold and get notified later", func(ctx SpecContext) { Expect(valjean.Hold(ctx, library, "Les Miserables")).To(Succeed()) Expect(valjean.Holds(ctx)).To(ContainElement(book)) By("when Javert returns the book") Expect(javert.Return(ctx, library, book)).To(Succeed()) By("it eventually informs Valjean") notification := "Les Miserables is ready for pick up" Eventually(ctx, valjean.Notifications).Should(ContainElement(notification)) Expect(valjean.Checkout(ctx, library, "Les Miserables")).To(Succeed()) Expect(valjean.Books(ctx)).To(ContainElement(book)) Expect(valjean.Holds(ctx)).To(BeEmpty()) }, SpecTimeout(time.Second * 10)) }) }) When("the library does not have the book in question", func() { It("tells the reader the book is unavailable", func(ctx SpecContext) { err := valjean.Checkout(ctx, library, "Les Miserables") Expect(err).To(MatchError("Les Miserables is not in the library catalog")) }, SpecTimeout(time.Second * 5)) }) }) ``` Jump to the [docs](https://onsi.github.io/ginkgo/) to learn more. It's easy to [bootstrap](https://onsi.github.io/ginkgo/#bootstrapping-a-suite) and start writing your [first specs](https://onsi.github.io/ginkgo/#adding-specs-to-a-suite). If you have a question, comment, bug report, feature request, etc. please open a [GitHub issue](https://github.com/onsi/ginkgo/issues/new), or visit the [Ginkgo Slack channel](https://app.slack.com/client/T029RQSE6/CQQ50BBNW). ## Capabilities Whether writing basic unit specs, complex integration specs, or even performance specs - Ginkgo gives you an expressive Domain-Specific Language (DSL) that will be familiar to users coming from frameworks such as [Quick](https://github.com/Quick/Quick), [RSpec](https://rspec.info), [Jasmine](https://jasmine.github.io), and [Busted](https://lunarmodules.github.io/busted/). This style of testing is sometimes referred to as "Behavior-Driven Development" (BDD) though Ginkgo's utility extends beyond acceptance-level testing. With Ginkgo's DSL you can use nestable [`Describe`, `Context` and `When` container nodes](https://onsi.github.io/ginkgo/#organizing-specs-with-container-nodes) to help you organize your specs. [`BeforeEach` and `AfterEach` setup nodes](https://onsi.github.io/ginkgo/#extracting-common-setup-beforeeach) for setup and cleanup. [`It` and `Specify` subject nodes](https://onsi.github.io/ginkgo/#spec-subjects-it) that hold your assertions. [`BeforeSuite` and `AfterSuite` nodes](https://onsi.github.io/ginkgo/#suite-setup-and-cleanup-beforesuite-and-aftersuite) to prep for and cleanup after a suite... and [much more!](https://onsi.github.io/ginkgo/#writing-specs). At runtime, Ginkgo can run your specs in reproducibly [random order](https://onsi.github.io/ginkgo/#spec-randomization) and has sophisticated support for [spec parallelization](https://onsi.github.io/ginkgo/#spec-parallelization). In fact, running specs in parallel is as easy as ```bash ginkgo -p ``` By following [established patterns for writing parallel specs](https://onsi.github.io/ginkgo/#patterns-for-parallel-integration-specs) you can build even large, complex integration suites that parallelize cleanly and run performantly. And you don't have to worry about your spec suite hanging or leaving a mess behind - Ginkgo provides a per-node `context.Context` and the capability to interrupt the spec after a set period of time - and then clean up. As your suites grow Ginkgo helps you keep your specs organized with [labels](https://onsi.github.io/ginkgo/#spec-labels) and lets you easily run [subsets of specs](https://onsi.github.io/ginkgo/#filtering-specs), either [programmatically](https://onsi.github.io/ginkgo/#focused-specs) or on the [command line](https://onsi.github.io/ginkgo/#combining-filters). And Ginkgo's reporting infrastructure generates machine-readable output in a [variety of formats](https://onsi.github.io/ginkgo/#generating-machine-readable-reports) _and_ allows you to build your own [custom reporting infrastructure](https://onsi.github.io/ginkgo/#generating-reports-programmatically). Ginkgo ships with `ginkgo`, a [command line tool](https://onsi.github.io/ginkgo/#ginkgo-cli-overview) with support for generating, running, filtering, and profiling Ginkgo suites. You can even have Ginkgo automatically run your specs when it detects a change with `ginkgo watch`, enabling rapid feedback loops during test-driven development. And that's just Ginkgo! [Gomega](https://onsi.github.io/gomega/) brings a rich, mature, family of [assertions and matchers](https://onsi.github.io/gomega/#provided-matchers) to your suites. With Gomega you can easily mix [synchronous and asynchronous assertions](https://onsi.github.io/ginkgo/#patterns-for-asynchronous-testing) in your specs. You can even build your own set of expressive domain-specific matchers quickly and easily by composing Gomega's [existing building blocks](https://onsi.github.io/ginkgo/#building-custom-matchers). Happy Testing! ## License Ginkgo is MIT-Licensed ## Contributing See [CONTRIBUTING.md](CONTRIBUTING.md) golang-github-onsi-ginkgo-v2-2.22.0/RELEASING.md000066400000000000000000000015321472321612100207260ustar00rootroot00000000000000A Ginkgo release is a tagged git sha and a GitHub release. To cut a release: 1. Ensure CHANGELOG.md is up to date. - Use ```bash LAST_VERSION=$(git tag --sort=version:refname | tail -n1) CHANGES=$(git log --pretty=format:'- %s [%h]' HEAD...$LAST_VERSION) echo -e "## NEXT\n\n$CHANGES\n\n### Features\n\n### Fixes\n\n### Maintenance\n\n$(cat CHANGELOG.md)" > CHANGELOG.md ``` to update the changelog - Categorize the changes into - Breaking Changes (requires a major version) - New Features (minor version) - Fixes (fix version) - Maintenance (which in general should not be mentioned in `CHANGELOG.md` as they have no user impact) 1. Update `VERSION` in `types/version.go` 1. Commit, push, and release: ``` git commit -m "vM.m.p" git push gh release create "vM.m.p" git fetch --tags origin master ```golang-github-onsi-ginkgo-v2-2.22.0/config/000077500000000000000000000000001472321612100203375ustar00rootroot00000000000000golang-github-onsi-ginkgo-v2-2.22.0/config/deprecated.go000066400000000000000000000065171472321612100227770ustar00rootroot00000000000000package config // GinkgoConfigType has been deprecated and its equivalent now lives in // the types package. You can no longer access Ginkgo configuration from the config // package. Instead use the DSL's GinkgoConfiguration() function to get copies of the // current configuration // // GinkgoConfigType is still here so custom V1 reporters do not result in a compilation error // It will be removed in a future minor release of Ginkgo type GinkgoConfigType = DeprecatedGinkgoConfigType type DeprecatedGinkgoConfigType struct { RandomSeed int64 RandomizeAllSpecs bool RegexScansFilePath bool FocusStrings []string SkipStrings []string SkipMeasurements bool FailOnPending bool FailFast bool FlakeAttempts int EmitSpecProgress bool DryRun bool DebugParallel bool ParallelNode int ParallelTotal int SyncHost string StreamHost string } // DefaultReporterConfigType has been deprecated and its equivalent now lives in // the types package. You can no longer access Ginkgo configuration from the config // package. Instead use the DSL's GinkgoConfiguration() function to get copies of the // current configuration // // DefaultReporterConfigType is still here so custom V1 reporters do not result in a compilation error // It will be removed in a future minor release of Ginkgo type DefaultReporterConfigType = DeprecatedDefaultReporterConfigType type DeprecatedDefaultReporterConfigType struct { NoColor bool SlowSpecThreshold float64 NoisyPendings bool NoisySkippings bool Succinct bool Verbose bool FullTrace bool ReportPassed bool ReportFile string } // Sadly there is no way to gracefully deprecate access to these global config variables. // Users who need access to Ginkgo's configuration should use the DSL's GinkgoConfiguration() method // These new unwieldy type names exist to give users a hint when they try to compile and the compilation fails type GinkgoConfigIsNoLongerAccessibleFromTheConfigPackageUseTheDSLsGinkgoConfigurationFunctionInstead struct{} // Sadly there is no way to gracefully deprecate access to these global config variables. // Users who need access to Ginkgo's configuration should use the DSL's GinkgoConfiguration() method // These new unwieldy type names exist to give users a hint when they try to compile and the compilation fails var GinkgoConfig = GinkgoConfigIsNoLongerAccessibleFromTheConfigPackageUseTheDSLsGinkgoConfigurationFunctionInstead{} // Sadly there is no way to gracefully deprecate access to these global config variables. // Users who need access to Ginkgo's configuration should use the DSL's GinkgoConfiguration() method // These new unwieldy type names exist to give users a hint when they try to compile and the compilation fails type DefaultReporterConfigIsNoLongerAccessibleFromTheConfigPackageUseTheDSLsGinkgoConfigurationFunctionInstead struct{} // Sadly there is no way to gracefully deprecate access to these global config variables. // Users who need access to Ginkgo's configuration should use the DSL's GinkgoConfiguration() method // These new unwieldy type names exist to give users a hint when they try to compile and the compilation fails var DefaultReporterConfig = DefaultReporterConfigIsNoLongerAccessibleFromTheConfigPackageUseTheDSLsGinkgoConfigurationFunctionInstead{} golang-github-onsi-ginkgo-v2-2.22.0/core_dsl.go000066400000000000000000001105241472321612100212160ustar00rootroot00000000000000/* Ginkgo is a testing framework for Go designed to help you write expressive tests. https://github.com/onsi/ginkgo MIT-Licensed The godoc documentation outlines Ginkgo's API. Since Ginkgo is a Domain-Specific Language it is important to build a mental model for Ginkgo - the narrative documentation at https://onsi.github.io/ginkgo/ is designed to help you do that. You should start there - even a brief skim will be helpful. At minimum you should skim through the https://onsi.github.io/ginkgo/#getting-started chapter. Ginkgo's is best paired with the Gomega matcher library: https://github.com/onsi/gomega You can run Ginkgo specs with go test - however we recommend using the ginkgo cli. It enables functionality that go test does not (especially running suites in parallel). You can learn more at https://onsi.github.io/ginkgo/#ginkgo-cli-overview or by running 'ginkgo help'. */ package ginkgo import ( "fmt" "io" "os" "path/filepath" "strings" "github.com/go-logr/logr" "github.com/onsi/ginkgo/v2/formatter" "github.com/onsi/ginkgo/v2/internal" "github.com/onsi/ginkgo/v2/internal/global" "github.com/onsi/ginkgo/v2/internal/interrupt_handler" "github.com/onsi/ginkgo/v2/internal/parallel_support" "github.com/onsi/ginkgo/v2/reporters" "github.com/onsi/ginkgo/v2/types" ) const GINKGO_VERSION = types.VERSION var flagSet types.GinkgoFlagSet var deprecationTracker = types.NewDeprecationTracker() var suiteConfig = types.NewDefaultSuiteConfig() var reporterConfig = types.NewDefaultReporterConfig() var suiteDidRun = false var outputInterceptor internal.OutputInterceptor var client parallel_support.Client func init() { var err error flagSet, err = types.BuildTestSuiteFlagSet(&suiteConfig, &reporterConfig) exitIfErr(err) writer := internal.NewWriter(os.Stdout) GinkgoWriter = writer GinkgoLogr = internal.GinkgoLogrFunc(writer) } func exitIfErr(err error) { if err != nil { if outputInterceptor != nil { outputInterceptor.Shutdown() } if client != nil { client.Close() } fmt.Fprintln(formatter.ColorableStdErr, err.Error()) os.Exit(1) } } func exitIfErrors(errors []error) { if len(errors) > 0 { if outputInterceptor != nil { outputInterceptor.Shutdown() } if client != nil { client.Close() } for _, err := range errors { fmt.Fprintln(formatter.ColorableStdErr, err.Error()) } os.Exit(1) } } // The interface implemented by GinkgoWriter type GinkgoWriterInterface interface { io.Writer Print(a ...interface{}) Printf(format string, a ...interface{}) Println(a ...interface{}) TeeTo(writer io.Writer) ClearTeeWriters() } /* SpecContext is the context object passed into nodes that are subject to a timeout or need to be notified of an interrupt. It implements the standard context.Context interface but also contains additional helpers to provide an extensibility point for Ginkgo. (As an example, Gomega's Eventually can use the methods defined on SpecContext to provide deeper integration with Ginkgo). You can do anything with SpecContext that you do with a typical context.Context including wrapping it with any of the context.With* methods. Ginkgo will cancel the SpecContext when a node is interrupted (e.g. by the user sending an interrupt signal) or when a node has exceeded its allowed run-time. Note, however, that even in cases where a node has a deadline, SpecContext will not return a deadline via .Deadline(). This is because Ginkgo does not use a WithDeadline() context to model node deadlines as Ginkgo needs control over the precise timing of the context cancellation to ensure it can provide an accurate progress report at the moment of cancellation. */ type SpecContext = internal.SpecContext /* GinkgoWriter implements a GinkgoWriterInterface and io.Writer When running in verbose mode (ginkgo -v) any writes to GinkgoWriter will be immediately printed to stdout. Otherwise, GinkgoWriter will buffer any writes produced during the current test and flush them to screen only if the current test fails. GinkgoWriter also provides convenience Print, Printf and Println methods and allows you to tee to a custom writer via GinkgoWriter.TeeTo(writer). Writes to GinkgoWriter are immediately sent to any registered TeeTo() writers. You can unregister all TeeTo() Writers with GinkgoWriter.ClearTeeWriters() You can learn more at https://onsi.github.io/ginkgo/#logging-output */ var GinkgoWriter GinkgoWriterInterface /* GinkgoLogr is a logr.Logger that writes to GinkgoWriter */ var GinkgoLogr logr.Logger // The interface by which Ginkgo receives *testing.T type GinkgoTestingT interface { Fail() } /* GinkgoConfiguration returns the configuration of the current suite. The first return value is the SuiteConfig which controls aspects of how the suite runs, the second return value is the ReporterConfig which controls aspects of how Ginkgo's default reporter emits output. Mutating the returned configurations has no effect. To reconfigure Ginkgo programmatically you need to pass in your mutated copies into RunSpecs(). You can learn more at https://onsi.github.io/ginkgo/#overriding-ginkgos-command-line-configuration-in-the-suite */ func GinkgoConfiguration() (types.SuiteConfig, types.ReporterConfig) { return suiteConfig, reporterConfig } /* GinkgoRandomSeed returns the seed used to randomize spec execution order. It is useful for seeding your own pseudorandom number generators to ensure consistent executions from run to run, where your tests contain variability (for example, when selecting random spec data). You can learn more at https://onsi.github.io/ginkgo/#spec-randomization */ func GinkgoRandomSeed() int64 { return suiteConfig.RandomSeed } /* GinkgoParallelProcess returns the parallel process number for the current ginkgo process The process number is 1-indexed. You can use GinkgoParallelProcess() to shard access to shared resources across your suites. You can learn more about patterns for sharding at https://onsi.github.io/ginkgo/#patterns-for-parallel-integration-specs For more on how specs are parallelized in Ginkgo, see http://onsi.github.io/ginkgo/#spec-parallelization */ func GinkgoParallelProcess() int { return suiteConfig.ParallelProcess } /* GinkgoHelper marks the function it's called in as a test helper. When a failure occurs inside a helper function, Ginkgo will skip the helper when analyzing the stack trace to identify where the failure occurred. This is an alternative, simpler, mechanism to passing in a skip offset when calling Fail or using Gomega. */ func GinkgoHelper() { types.MarkAsHelper(1) } /* GinkgoLabelFilter() returns the label filter configured for this suite via `--label-filter`. You can use this to manually check if a set of labels would satisfy the filter via: if (Label("cat", "dog").MatchesLabelFilter(GinkgoLabelFilter())) { //... } */ func GinkgoLabelFilter() string { suiteConfig, _ := GinkgoConfiguration() return suiteConfig.LabelFilter } /* PauseOutputInterception() pauses Ginkgo's output interception. This is only relevant when running in parallel and output to stdout/stderr is being intercepted. You generally don't need to call this function - however there are cases when Ginkgo's output interception mechanisms can interfere with external processes launched by the test process. In particular, if an external process is launched that has cmd.Stdout/cmd.Stderr set to os.Stdout/os.Stderr then Ginkgo's output interceptor will hang. To circumvent this, set cmd.Stdout/cmd.Stderr to GinkgoWriter. If, for some reason, you aren't able to do that, you can PauseOutputInterception() before starting the process then ResumeOutputInterception() after starting it. Note that PauseOutputInterception() does not cause stdout writes to print to the console - this simply stops intercepting and storing stdout writes to an internal buffer. */ func PauseOutputInterception() { if outputInterceptor == nil { return } outputInterceptor.PauseIntercepting() } // ResumeOutputInterception() - see docs for PauseOutputInterception() func ResumeOutputInterception() { if outputInterceptor == nil { return } outputInterceptor.ResumeIntercepting() } /* RunSpecs is the entry point for the Ginkgo spec runner. You must call this within a Golang testing TestX(t *testing.T) function. If you bootstrapped your suite with "ginkgo bootstrap" this is already done for you. Ginkgo is typically configured via command-line flags. This configuration can be overridden, however, and passed into RunSpecs as optional arguments: func TestMySuite(t *testing.T) { RegisterFailHandler(gomega.Fail) // fetch the current config suiteConfig, reporterConfig := GinkgoConfiguration() // adjust it suiteConfig.SkipStrings = []string{"NEVER-RUN"} reporterConfig.FullTrace = true // pass it in to RunSpecs RunSpecs(t, "My Suite", suiteConfig, reporterConfig) } Note that some configuration changes can lead to undefined behavior. For example, you should not change ParallelProcess or ParallelTotal as the Ginkgo CLI is responsible for setting these and orchestrating parallel specs across the parallel processes. See http://onsi.github.io/ginkgo/#spec-parallelization for more on how specs are parallelized in Ginkgo. You can also pass suite-level Label() decorators to RunSpecs. The passed-in labels will apply to all specs in the suite. */ func RunSpecs(t GinkgoTestingT, description string, args ...interface{}) bool { if suiteDidRun { exitIfErr(types.GinkgoErrors.RerunningSuite()) } suiteDidRun = true err := global.PushClone() if err != nil { exitIfErr(err) } defer global.PopClone() suiteLabels := extractSuiteConfiguration(args) var reporter reporters.Reporter if suiteConfig.ParallelTotal == 1 { reporter = reporters.NewDefaultReporter(reporterConfig, formatter.ColorableStdOut) outputInterceptor = internal.NoopOutputInterceptor{} client = nil } else { reporter = reporters.NoopReporter{} switch strings.ToLower(suiteConfig.OutputInterceptorMode) { case "swap": outputInterceptor = internal.NewOSGlobalReassigningOutputInterceptor() case "none": outputInterceptor = internal.NoopOutputInterceptor{} default: outputInterceptor = internal.NewOutputInterceptor() } client = parallel_support.NewClient(suiteConfig.ParallelHost) if !client.Connect() { client = nil exitIfErr(types.GinkgoErrors.UnreachableParallelHost(suiteConfig.ParallelHost)) } defer client.Close() } writer := GinkgoWriter.(*internal.Writer) if reporterConfig.Verbosity().GTE(types.VerbosityLevelVerbose) && suiteConfig.ParallelTotal == 1 { writer.SetMode(internal.WriterModeStreamAndBuffer) } else { writer.SetMode(internal.WriterModeBufferOnly) } if reporterConfig.WillGenerateReport() { registerReportAfterSuiteNodeForAutogeneratedReports(reporterConfig) } err = global.Suite.BuildTree() exitIfErr(err) suitePath, err := getwd() exitIfErr(err) suitePath, err = filepath.Abs(suitePath) exitIfErr(err) passed, hasFocusedTests := global.Suite.Run(description, suiteLabels, suitePath, global.Failer, reporter, writer, outputInterceptor, interrupt_handler.NewInterruptHandler(client), client, internal.RegisterForProgressSignal, suiteConfig) outputInterceptor.Shutdown() flagSet.ValidateDeprecations(deprecationTracker) if deprecationTracker.DidTrackDeprecations() { fmt.Fprintln(formatter.ColorableStdErr, deprecationTracker.DeprecationsReport()) } if !passed { t.Fail() } if passed && hasFocusedTests && strings.TrimSpace(os.Getenv("GINKGO_EDITOR_INTEGRATION")) == "" { fmt.Println("PASS | FOCUSED") os.Exit(types.GINKGO_FOCUS_EXIT_CODE) } return passed } func extractSuiteConfiguration(args []interface{}) Labels { suiteLabels := Labels{} configErrors := []error{} for _, arg := range args { switch arg := arg.(type) { case types.SuiteConfig: suiteConfig = arg case types.ReporterConfig: reporterConfig = arg case Labels: suiteLabels = append(suiteLabels, arg...) default: configErrors = append(configErrors, types.GinkgoErrors.UnknownTypePassedToRunSpecs(arg)) } } exitIfErrors(configErrors) configErrors = types.VetConfig(flagSet, suiteConfig, reporterConfig) if len(configErrors) > 0 { fmt.Fprintf(formatter.ColorableStdErr, formatter.F("{{red}}Ginkgo detected configuration issues:{{/}}\n")) for _, err := range configErrors { fmt.Fprintf(formatter.ColorableStdErr, err.Error()) } os.Exit(1) } return suiteLabels } func getwd() (string, error) { if !strings.EqualFold(os.Getenv("GINKGO_PRESERVE_CACHE"), "true") { // Getwd calls os.Getenv("PWD"), which breaks test caching if the cache // is shared between two different directories with the same test code. return os.Getwd() } return "", nil } /* PreviewSpecs walks the testing tree and produces a report without actually invoking the specs. See http://onsi.github.io/ginkgo/#previewing-specs for more information. */ func PreviewSpecs(description string, args ...any) Report { err := global.PushClone() if err != nil { exitIfErr(err) } defer global.PopClone() suiteLabels := extractSuiteConfiguration(args) priorDryRun, priorParallelTotal, priorParallelProcess := suiteConfig.DryRun, suiteConfig.ParallelTotal, suiteConfig.ParallelProcess suiteConfig.DryRun, suiteConfig.ParallelTotal, suiteConfig.ParallelProcess = true, 1, 1 defer func() { suiteConfig.DryRun, suiteConfig.ParallelTotal, suiteConfig.ParallelProcess = priorDryRun, priorParallelTotal, priorParallelProcess }() reporter := reporters.NoopReporter{} outputInterceptor = internal.NoopOutputInterceptor{} client = nil writer := GinkgoWriter.(*internal.Writer) err = global.Suite.BuildTree() exitIfErr(err) suitePath, err := getwd() exitIfErr(err) suitePath, err = filepath.Abs(suitePath) exitIfErr(err) global.Suite.Run(description, suiteLabels, suitePath, global.Failer, reporter, writer, outputInterceptor, interrupt_handler.NewInterruptHandler(client), client, internal.RegisterForProgressSignal, suiteConfig) return global.Suite.GetPreviewReport() } /* Skip instructs Ginkgo to skip the current spec You can call Skip in any Setup or Subject node closure. For more on how to filter specs in Ginkgo see https://onsi.github.io/ginkgo/#filtering-specs */ func Skip(message string, callerSkip ...int) { skip := 0 if len(callerSkip) > 0 { skip = callerSkip[0] } cl := types.NewCodeLocationWithStackTrace(skip + 1) global.Failer.Skip(message, cl) panic(types.GinkgoErrors.UncaughtGinkgoPanic(cl)) } /* Fail notifies Ginkgo that the current spec has failed. (Gomega will call Fail for you automatically when an assertion fails.) Under the hood, Fail panics to end execution of the current spec. Ginkgo will catch this panic and proceed with the subsequent spec. If you call Fail, or make an assertion, within a goroutine launched by your spec you must add defer GinkgoRecover() to the goroutine to catch the panic emitted by Fail. You can call Fail in any Setup or Subject node closure. You can learn more about how Ginkgo manages failures here: https://onsi.github.io/ginkgo/#mental-model-how-ginkgo-handles-failure */ func Fail(message string, callerSkip ...int) { skip := 0 if len(callerSkip) > 0 { skip = callerSkip[0] } cl := types.NewCodeLocationWithStackTrace(skip + 1) global.Failer.Fail(message, cl) panic(types.GinkgoErrors.UncaughtGinkgoPanic(cl)) } /* AbortSuite instructs Ginkgo to fail the current spec and skip all subsequent specs, thereby aborting the suite. You can call AbortSuite in any Setup or Subject node closure. You can learn more about how Ginkgo handles suite interruptions here: https://onsi.github.io/ginkgo/#interrupting-aborting-and-timing-out-suites */ func AbortSuite(message string, callerSkip ...int) { skip := 0 if len(callerSkip) > 0 { skip = callerSkip[0] } cl := types.NewCodeLocationWithStackTrace(skip + 1) global.Failer.AbortSuite(message, cl) panic(types.GinkgoErrors.UncaughtGinkgoPanic(cl)) } /* ignorablePanic is used by Gomega to signal to GinkgoRecover that Goemga is handling the error associated with this panic. It i used when Eventually/Consistently are passed a func(g Gomega) and the resulting function launches a goroutines that makes a failed assertion. That failed assertion is registered by Gomega and then panics. Ordinarily the panic is captured by Gomega. In the case of a goroutine Gomega can't capture the panic - so we piggy back on GinkgoRecover so users have a single defer GinkgoRecover() pattern to follow. To do that we need to tell Ginkgo to ignore this panic and not register it as a panic on the global Failer. */ type ignorablePanic interface{ GinkgoRecoverShouldIgnoreThisPanic() } /* GinkgoRecover should be deferred at the top of any spawned goroutine that (may) call `Fail` Since Gomega assertions call fail, you should throw a `defer GinkgoRecover()` at the top of any goroutine that calls out to Gomega Here's why: Ginkgo's `Fail` method records the failure and then panics to prevent further assertions from running. This panic must be recovered. Normally, Ginkgo recovers the panic for you, however if a panic originates on a goroutine *launched* from one of your specs there's no way for Ginkgo to rescue the panic. To do this, you must remember to `defer GinkgoRecover()` at the top of such a goroutine. You can learn more about how Ginkgo manages failures here: https://onsi.github.io/ginkgo/#mental-model-how-ginkgo-handles-failure */ func GinkgoRecover() { e := recover() if e != nil { if _, ok := e.(ignorablePanic); ok { return } global.Failer.Panic(types.NewCodeLocationWithStackTrace(1), e) } } // pushNode is used by the various test construction DSL methods to push nodes onto the suite // it handles returned errors, emits a detailed error message to help the user learn what they may have done wrong, then exits func pushNode(node internal.Node, errors []error) bool { exitIfErrors(errors) exitIfErr(global.Suite.PushNode(node)) return true } /* Describe nodes are Container nodes that allow you to organize your specs. A Describe node's closure can contain any number of Setup nodes (e.g. BeforeEach, AfterEach, JustBeforeEach), and Subject nodes (i.e. It). Context and When nodes are aliases for Describe - use whichever gives your suite a better narrative flow. It is idomatic to Describe the behavior of an object or function and, within that Describe, outline a number of Contexts and Whens. You can learn more at https://onsi.github.io/ginkgo/#organizing-specs-with-container-nodes In addition, container nodes can be decorated with a variety of decorators. You can learn more here: https://onsi.github.io/ginkgo/#decorator-reference */ func Describe(text string, args ...interface{}) bool { return pushNode(internal.NewNode(deprecationTracker, types.NodeTypeContainer, text, args...)) } /* FDescribe focuses specs within the Describe block. */ func FDescribe(text string, args ...interface{}) bool { args = append(args, internal.Focus) return pushNode(internal.NewNode(deprecationTracker, types.NodeTypeContainer, text, args...)) } /* PDescribe marks specs within the Describe block as pending. */ func PDescribe(text string, args ...interface{}) bool { args = append(args, internal.Pending) return pushNode(internal.NewNode(deprecationTracker, types.NodeTypeContainer, text, args...)) } /* XDescribe marks specs within the Describe block as pending. XDescribe is an alias for PDescribe */ var XDescribe = PDescribe /* Context is an alias for Describe - it generates the exact same kind of Container node */ var Context, FContext, PContext, XContext = Describe, FDescribe, PDescribe, XDescribe /* When is an alias for Describe - it generates the exact same kind of Container node */ func When(text string, args ...interface{}) bool { return pushNode(internal.NewNode(deprecationTracker, types.NodeTypeContainer, "when "+text, args...)) } /* When is an alias for Describe - it generates the exact same kind of Container node */ func FWhen(text string, args ...interface{}) bool { args = append(args, internal.Focus) return pushNode(internal.NewNode(deprecationTracker, types.NodeTypeContainer, "when "+text, args...)) } /* When is an alias for Describe - it generates the exact same kind of Container node */ func PWhen(text string, args ...interface{}) bool { args = append(args, internal.Pending) return pushNode(internal.NewNode(deprecationTracker, types.NodeTypeContainer, "when "+text, args...)) } var XWhen = PWhen /* It nodes are Subject nodes that contain your spec code and assertions. Each It node corresponds to an individual Ginkgo spec. You cannot nest any other Ginkgo nodes within an It node's closure. You can pass It nodes bare functions (func() {}) or functions that receive a SpecContext or context.Context: func(ctx SpecContext) {} and func (ctx context.Context) {}. If the function takes a context then the It is deemed interruptible and Ginkgo will cancel the context in the event of a timeout (configured via the SpecTimeout() or NodeTimeout() decorators) or of an interrupt signal. You can learn more at https://onsi.github.io/ginkgo/#spec-subjects-it In addition, subject nodes can be decorated with a variety of decorators. You can learn more here: https://onsi.github.io/ginkgo/#decorator-reference */ func It(text string, args ...interface{}) bool { return pushNode(internal.NewNode(deprecationTracker, types.NodeTypeIt, text, args...)) } /* FIt allows you to focus an individual It. */ func FIt(text string, args ...interface{}) bool { args = append(args, internal.Focus) return pushNode(internal.NewNode(deprecationTracker, types.NodeTypeIt, text, args...)) } /* PIt allows you to mark an individual It as pending. */ func PIt(text string, args ...interface{}) bool { args = append(args, internal.Pending) return pushNode(internal.NewNode(deprecationTracker, types.NodeTypeIt, text, args...)) } /* XIt allows you to mark an individual It as pending. XIt is an alias for PIt */ var XIt = PIt /* Specify is an alias for It - it can allow for more natural wording in some context. */ var Specify, FSpecify, PSpecify, XSpecify = It, FIt, PIt, XIt /* By allows you to better document complex Specs. Generally you should try to keep your Its short and to the point. This is not always possible, however, especially in the context of integration tests that capture complex or lengthy workflows. By allows you to document such flows. By may be called within a Setup or Subject node (It, BeforeEach, etc...) and will simply log the passed in text to the GinkgoWriter. If By is handed a function it will immediately run the function. By will also generate and attach a ReportEntry to the spec. This will ensure that By annotations appear in Ginkgo's machine-readable reports. Note that By does not generate a new Ginkgo node - rather it is simply syntactic sugar around GinkgoWriter and AddReportEntry You can learn more about By here: https://onsi.github.io/ginkgo/#documenting-complex-specs-by */ func By(text string, callback ...func()) { exitIfErr(global.Suite.By(text, callback...)) } /* BeforeSuite nodes are suite-level Setup nodes that run just once before any specs are run. When running in parallel, each parallel process will call BeforeSuite. You may only register *one* BeforeSuite handler per test suite. You typically do so in your bootstrap file at the top level. BeforeSuite can take a func() body, or an interruptible func(SpecContext)/func(context.Context) body. 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 interface{}, args ...interface{}) bool { combinedArgs := []interface{}{body} combinedArgs = append(combinedArgs, args...) return pushNode(internal.NewNode(deprecationTracker, types.NodeTypeBeforeSuite, "", combinedArgs...)) } /* AfterSuite nodes are suite-level Setup nodes run after all specs have finished - regardless of whether specs have passed or failed. AfterSuite node closures always run, even if Ginkgo receives an interrupt signal (^C), in order to ensure cleanup occurs. When running in parallel, each parallel process will call AfterSuite. You may only register *one* AfterSuite handler per test suite. You typically do so in your bootstrap file at the top level. AfterSuite can take a func() body, or an interruptible func(SpecContext)/func(context.Context) body. 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 interface{}, args ...interface{}) bool { combinedArgs := []interface{}{body} combinedArgs = append(combinedArgs, args...) return pushNode(internal.NewNode(deprecationTracker, types.NodeTypeAfterSuite, "", combinedArgs...)) } /* SynchronizedBeforeSuite nodes allow you to perform some of the suite setup just once - on parallel process #1 - and then pass information from that setup to the rest of the suite setup on all processes. This is useful for performing expensive or singleton setup once, then passing information from that setup to all parallel processes. SynchronizedBeforeSuite accomplishes this by taking *two* function arguments and passing data between them. The first function is only run on parallel process #1. The second is run on all processes, but *only* after the first function completes successfully. The functions have the following signatures: The first function (which only runs on process #1) can have any of the following the signatures: func() func(ctx context.Context) func(ctx SpecContext) func() []byte func(ctx context.Context) []byte func(ctx SpecContext) []byte The byte array returned by the first function (if present) is then passed to the second function, which can have any of the following signature: func() func(ctx context.Context) func(ctx SpecContext) func(data []byte) func(ctx context.Context, data []byte) func(ctx SpecContext, data []byte) If either function receives a context.Context/SpecContext it is considered interruptible. 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 interface{}, allProcessBody interface{}, args ...interface{}) bool { combinedArgs := []interface{}{process1Body, allProcessBody} combinedArgs = append(combinedArgs, args...) return pushNode(internal.NewNode(deprecationTracker, types.NodeTypeSynchronizedBeforeSuite, "", combinedArgs...)) } /* SynchronizedAfterSuite nodes complement the SynchronizedBeforeSuite nodes in solving the problem of splitting clean up into a piece that runs on all processes and a piece that must only run once - on process #1. SynchronizedAfterSuite accomplishes this by taking *two* function arguments. The first runs on all processes. The second runs only on parallel process #1 and *only* after all other processes have finished and exited. This ensures that process #1, and any resources it is managing, remain alive until all other processes are finished. These two functions can be bare functions (func()) or interruptible (func(context.Context)/func(SpecContext)) Note that you can also use DeferCleanup() in SynchronizedBeforeSuite to accomplish similar results. 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 interface{}, process1Body interface{}, args ...interface{}) bool { combinedArgs := []interface{}{allProcessBody, process1Body} combinedArgs = append(combinedArgs, args...) return pushNode(internal.NewNode(deprecationTracker, types.NodeTypeSynchronizedAfterSuite, "", combinedArgs...)) } /* BeforeEach nodes are Setup nodes whose closures run before It node closures. When multiple BeforeEach nodes are defined in nested Container nodes the outermost BeforeEach node closures are run first. BeforeEach can take a func() body, or an interruptible func(SpecContext)/func(context.Context) body. You cannot nest any other Ginkgo nodes within a BeforeEach node's closure. You can learn more here: https://onsi.github.io/ginkgo/#extracting-common-setup-beforeeach */ func BeforeEach(args ...interface{}) bool { return pushNode(internal.NewNode(deprecationTracker, types.NodeTypeBeforeEach, "", args...)) } /* JustBeforeEach nodes are similar to BeforeEach nodes, however they are guaranteed to run *after* all BeforeEach node closures - just before the It node closure. This can allow you to separate configuration from creation of resources for a spec. JustBeforeEach can take a func() body, or an interruptible func(SpecContext)/func(context.Context) body. You cannot nest any other Ginkgo nodes within a JustBeforeEach node's closure. You can learn more and see some examples here: https://onsi.github.io/ginkgo/#separating-creation-and-configuration-justbeforeeach */ func JustBeforeEach(args ...interface{}) bool { return pushNode(internal.NewNode(deprecationTracker, types.NodeTypeJustBeforeEach, "", args...)) } /* AfterEach nodes are Setup nodes whose closures run after It node closures. When multiple AfterEach nodes are defined in nested Container nodes the innermost AfterEach node closures are run first. Note that you can also use DeferCleanup() in other Setup or Subject nodes to accomplish similar results. AfterEach can take a func() body, or an interruptible func(SpecContext)/func(context.Context) body. You cannot nest any other Ginkgo nodes within an AfterEach node's closure. You can learn more here: https://onsi.github.io/ginkgo/#spec-cleanup-aftereach-and-defercleanup */ func AfterEach(args ...interface{}) bool { return pushNode(internal.NewNode(deprecationTracker, types.NodeTypeAfterEach, "", args...)) } /* JustAfterEach nodes are similar to AfterEach nodes, however they are guaranteed to run *before* all AfterEach node closures - just after the It node closure. This can allow you to separate diagnostics collection from teardown for a spec. JustAfterEach can take a func() body, or an interruptible func(SpecContext)/func(context.Context) body. You cannot nest any other Ginkgo nodes within a JustAfterEach node's closure. You can learn more and see some examples here: https://onsi.github.io/ginkgo/#separating-diagnostics-collection-and-teardown-justaftereach */ func JustAfterEach(args ...interface{}) bool { return pushNode(internal.NewNode(deprecationTracker, types.NodeTypeJustAfterEach, "", args...)) } /* BeforeAll nodes are Setup nodes that can occur inside Ordered containers. They run just once before any specs in the Ordered container run. Multiple BeforeAll nodes can be defined in a given Ordered container however they cannot be nested inside any other container. BeforeAll can take a func() body, or an interruptible func(SpecContext)/func(context.Context) body. You cannot nest any other Ginkgo nodes within a BeforeAll node's closure. You can learn more about Ordered Containers at: https://onsi.github.io/ginkgo/#ordered-containers And you can learn more about BeforeAll at: https://onsi.github.io/ginkgo/#setup-in-ordered-containers-beforeall-and-afterall */ func BeforeAll(args ...interface{}) bool { return pushNode(internal.NewNode(deprecationTracker, types.NodeTypeBeforeAll, "", args...)) } /* AfterAll nodes are Setup nodes that can occur inside Ordered containers. They run just once after all specs in the Ordered container have run. Multiple AfterAll nodes can be defined in a given Ordered container however they cannot be nested inside any other container. Note that you can also use DeferCleanup() in a BeforeAll node to accomplish similar behavior. AfterAll can take a func() body, or an interruptible func(SpecContext)/func(context.Context) body. You cannot nest any other Ginkgo nodes within an AfterAll node's closure. You can learn more about Ordered Containers at: https://onsi.github.io/ginkgo/#ordered-containers And you can learn more about AfterAll at: https://onsi.github.io/ginkgo/#setup-in-ordered-containers-beforeall-and-afterall */ func AfterAll(args ...interface{}) bool { return pushNode(internal.NewNode(deprecationTracker, types.NodeTypeAfterAll, "", args...)) } /* DeferCleanup can be called within any Setup or Subject node to register a cleanup callback that Ginkgo will call at the appropriate time to cleanup after the spec. DeferCleanup can be passed: 1. A function that takes no arguments and returns no values. 2. A function that returns multiple values. `DeferCleanup` will ignore all these return values except for the last one. If this last return value is a non-nil error `DeferCleanup` will fail the spec). 3. A function that takes a context.Context or SpecContext (and optionally returns multiple values). The resulting cleanup node is deemed interruptible and the passed-in context will be cancelled in the event of a timeout or interrupt. 4. A function that takes arguments (and optionally returns multiple values) followed by a list of arguments to pass to the function. 5. A function that takes SpecContext and a list of arguments (and optionally returns multiple values) followed by a list of arguments to pass to the function. For example: BeforeEach(func() { DeferCleanup(os.Setenv, "FOO", os.GetEnv("FOO")) os.Setenv("FOO", "BAR") }) will register a cleanup handler that will set the environment variable "FOO" to its current value (obtained by os.GetEnv("FOO")) after the spec runs and then sets the environment variable "FOO" to "BAR" for the current spec. Similarly: BeforeEach(func() { DeferCleanup(func(ctx SpecContext, path) { req, err := http.NewRequestWithContext(ctx, "POST", path, nil) Expect(err).NotTo(HaveOccured()) _, err := http.DefaultClient.Do(req) Expect(err).NotTo(HaveOccured()) }, "example.com/cleanup", NodeTimeout(time.Second*3)) }) will register a cleanup handler that will have three seconds to successfully complete a request to the specified path. Note that we do not specify a context in the list of arguments passed to DeferCleanup - only in the signature of the function we pass in. Ginkgo will detect the requested context and supply a SpecContext when it invokes the cleanup node. If you want to pass in your own context in addition to the Ginkgo-provided SpecContext you must specify the SpecContext as the first argument (e.g. func(ctx SpecContext, otherCtx context.Context)). When DeferCleanup is called in BeforeEach, JustBeforeEach, It, AfterEach, or JustAfterEach the registered callback will be invoked when the spec completes (i.e. it will behave like an AfterEach node) When DeferCleanup is called in BeforeAll or AfterAll the registered callback will be invoked when the ordered container completes (i.e. it will behave like an AfterAll node) When DeferCleanup is called in BeforeSuite, SynchronizedBeforeSuite, AfterSuite, or SynchronizedAfterSuite the registered callback will be invoked when the suite completes (i.e. it will behave like an AfterSuite node) Note that DeferCleanup does not represent a node but rather dynamically generates the appropriate type of cleanup node based on the context in which it is called. As such you must call DeferCleanup within a Setup or Subject node, and not within a Container node. You can learn more about DeferCleanup here: https://onsi.github.io/ginkgo/#cleaning-up-our-cleanup-code-defercleanup */ func DeferCleanup(args ...interface{}) { fail := func(message string, cl types.CodeLocation) { global.Failer.Fail(message, cl) } pushNode(internal.NewCleanupNode(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. **This is an experimental feature and the public-facing interface may change in a future minor version of Ginkgo** Progress Reports are generated: - whenever the user explicitly requests one (via `SIGINFO` or `SIGUSR1`) - on nodes decorated with PollProgressAfter - on suites run with --poll-progress-after - whenever a test times out Ginkgo uses Progress Reports to convey the current state of the test suite, including any running goroutines. By attaching a progress reporter you are able to supplement these reports with additional information. # AttachProgressReporter returns a function that can be called to detach the progress reporter You can learn more about AttachProgressReporter here: https://onsi.github.io/ginkgo/#attaching-additional-information-to-progress-reports */ func AttachProgressReporter(reporter func() string) func() { return global.Suite.AttachProgressReporter(reporter) } golang-github-onsi-ginkgo-v2-2.22.0/decorator_dsl.go000066400000000000000000000176641472321612100222630ustar00rootroot00000000000000package ginkgo import ( "github.com/onsi/ginkgo/v2/internal" ) /* Offset(uint) is a decorator that allows you to change the stack-frame offset used when computing the line number of the node in question. You can learn more here: https://onsi.github.io/ginkgo/#the-offset-decorator You can learn more about decorators here: https://onsi.github.io/ginkgo/#decorator-reference */ type Offset = internal.Offset /* FlakeAttempts(uint N) is a decorator that allows you to mark individual specs or spec containers as flaky. Ginkgo will run them up to `N` times until they pass. You can learn more here: https://onsi.github.io/ginkgo/#the-flakeattempts-decorator You can learn more about decorators here: https://onsi.github.io/ginkgo/#decorator-reference */ type FlakeAttempts = internal.FlakeAttempts /* MustPassRepeatedly(uint N) is a decorator that allows you to repeat the execution of individual specs or spec containers. Ginkgo will run them up to `N` times until they fail. You can learn more here: https://onsi.github.io/ginkgo/#the-mustpassrepeatedly-decorator You can learn more about decorators here: https://onsi.github.io/ginkgo/#decorator-reference */ type MustPassRepeatedly = internal.MustPassRepeatedly /* Focus is a decorator that allows you to mark a spec or container as focused. Identical to FIt and FDescribe. You can learn more here: https://onsi.github.io/ginkgo/#filtering-specs You can learn more about decorators here: https://onsi.github.io/ginkgo/#decorator-reference */ const Focus = internal.Focus /* Pending is a decorator that allows you to mark a spec or container as pending. Identical to PIt and PDescribe. You can learn more here: https://onsi.github.io/ginkgo/#filtering-specs You can learn more about decorators here: https://onsi.github.io/ginkgo/#decorator-reference */ const Pending = internal.Pending /* Serial is a decorator that allows you to mark a spec or container as serial. These specs will never run in parallel with other specs. Specs in ordered containers cannot be marked as serial - mark the ordered container instead. You can learn more here: https://onsi.github.io/ginkgo/#serial-specs You can learn more about decorators here: https://onsi.github.io/ginkgo/#decorator-reference */ const Serial = internal.Serial /* Ordered is a decorator that allows you to mark a container as ordered. Specs in the container will always run in the order they appear. They will never be randomized and they will never run in parallel with one another, though they may run in parallel with other specs. You can learn more here: https://onsi.github.io/ginkgo/#ordered-containers You can learn more about decorators here: https://onsi.github.io/ginkgo/#decorator-reference */ const Ordered = internal.Ordered /* ContinueOnFailure is a decorator that allows you to mark an Ordered container to continue running specs even if failures occur. Ordinarily an ordered container will stop running specs after the first failure occurs. Note that if a BeforeAll or a BeforeEach/JustBeforeEach annotated with OncePerOrdered fails then no specs will run as the precondition for the Ordered container will consider to be failed. ContinueOnFailure only applies to the outermost Ordered container. Attempting to place ContinueOnFailure in a nested container will result in an error. You can learn more here: https://onsi.github.io/ginkgo/#ordered-containers You can learn more about decorators here: https://onsi.github.io/ginkgo/#decorator-reference */ const ContinueOnFailure = internal.ContinueOnFailure /* OncePerOrdered is a decorator that allows you to mark outer BeforeEach, AfterEach, JustBeforeEach, and JustAfterEach setup nodes to run once 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 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 /* Label decorates specs with Labels. Multiple labels can be passed to Label and these can be arbitrary strings but must not include the following characters: "&|!,()/". Labels can be applied to container and subject nodes, but not setup nodes. You can provide multiple Labels to a given node and a spec's labels is the union of all labels in its node hierarchy. You can learn more here: https://onsi.github.io/ginkgo/#spec-labels You can learn more about decorators here: https://onsi.github.io/ginkgo/#decorator-reference */ func Label(labels ...string) Labels { return Labels(labels) } /* Labels are the type for spec Label decorators. Use Label(...) to construct Labels. 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 /* NodeTimeout allows you to specify a timeout for an indivdiual node. The node cannot be a container and must be interruptible (i.e. it must be passed a function that accepts a SpecContext or context.Context). If the node does not exit within the specified NodeTimeout its context will be cancelled. The node wil then have a period of time controlled by the GracePeriod decorator (or global --grace-period command-line argument) to exit. If the node does not exit within GracePeriod Ginkgo will leak the node and proceed to any clean-up nodes associated with the current spec. */ type NodeTimeout = internal.NodeTimeout /* SpecTimeout allows you to specify a timeout for an indivdiual spec. SpecTimeout can only decorate interruptible It nodes. All nodes associated with the It node will need to complete before the SpecTimeout has elapsed. Individual nodes (e.g. BeforeEach) may be decorated with different NodeTimeouts - but these can only serve to provide a more stringent deadline for the node in question; they cannot extend the deadline past the SpecTimeout. If the spec does not complete within the specified SpecTimeout the currently running node will have its context cancelled. The node wil then have a period of time controlled by that node's GracePeriod decorator (or global --grace-period command-line argument) to exit. If the node does not exit within GracePeriod Ginkgo will leak the node and proceed to any clean-up nodes associated with the current spec. */ type SpecTimeout = internal.SpecTimeout /* GracePeriod denotes the period of time Ginkgo will wait for an interruptible node to exit once an interruption (whether due to a timeout or a user-invoked signal) has occurred. If both the global --grace-period cli flag and a GracePeriod decorator are specified the value in the decorator will take precedence. Nodes that do not finish within a GracePeriod will be leaked and Ginkgo will proceed to run subsequent nodes. In the event of a timeout, such leaks will be reported to the user. */ type GracePeriod = internal.GracePeriod /* 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. */ const SuppressProgressReporting = internal.SuppressProgressReporting golang-github-onsi-ginkgo-v2-2.22.0/deprecated_dsl.go000066400000000000000000000114351472321612100223670ustar00rootroot00000000000000package ginkgo import ( "time" "github.com/onsi/ginkgo/v2/internal" "github.com/onsi/ginkgo/v2/internal/global" "github.com/onsi/ginkgo/v2/reporters" "github.com/onsi/ginkgo/v2/types" ) /* Deprecated: Done Channel for asynchronous testing The Done channel pattern is no longer supported in Ginkgo 2.0. 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 */ type Done = internal.Done /* Deprecated: Custom Ginkgo test reporters are deprecated in Ginkgo 2.0. Use Ginkgo's reporting nodes instead and 2.0 reporting infrastructure instead. You can learn more here: https://onsi.github.io/ginkgo/#reporting-infrastructure For a migration guide see: https://onsi.github.io/ginkgo/MIGRATING_TO_V2#removed-custom-reporters */ type Reporter = reporters.DeprecatedReporter /* Deprecated: Custom Reporters have been removed in Ginkgo 2.0. RunSpecsWithDefaultAndCustomReporters will simply call RunSpecs() Use Ginkgo's reporting nodes instead and 2.0 reporting infrastructure instead. You can learn more here: https://onsi.github.io/ginkgo/#reporting-infrastructure For a migration guide see: https://onsi.github.io/ginkgo/MIGRATING_TO_V2#removed-custom-reporters */ func RunSpecsWithDefaultAndCustomReporters(t GinkgoTestingT, description string, _ []Reporter) bool { deprecationTracker.TrackDeprecation(types.Deprecations.CustomReporter()) return RunSpecs(t, description) } /* Deprecated: Custom Reporters have been removed in Ginkgo 2.0. RunSpecsWithCustomReporters will simply call RunSpecs() Use Ginkgo's reporting nodes instead and 2.0 reporting infrastructure instead. You can learn more here: https://onsi.github.io/ginkgo/#reporting-infrastructure For a migration guide see: https://onsi.github.io/ginkgo/MIGRATING_TO_V2#removed-custom-reporters */ func RunSpecsWithCustomReporters(t GinkgoTestingT, description string, _ []Reporter) bool { deprecationTracker.TrackDeprecation(types.Deprecations.CustomReporter()) return RunSpecs(t, description) } /* Deprecated: GinkgoTestDescription has been replaced with SpecReport. Use CurrentSpecReport() instead. You can learn more here: https://onsi.github.io/ginkgo/#getting-a-report-for-the-current-spec The SpecReport type is documented here: https://pkg.go.dev/github.com/onsi/ginkgo/v2/types#SpecReport */ type DeprecatedGinkgoTestDescription struct { FullTestText string ComponentTexts []string TestText string FileName string LineNumber int Failed bool Duration time.Duration } type GinkgoTestDescription = DeprecatedGinkgoTestDescription /* Deprecated: CurrentGinkgoTestDescription has been replaced with CurrentSpecReport. Use CurrentSpecReport() instead. You can learn more here: https://onsi.github.io/ginkgo/#getting-a-report-for-the-current-spec The SpecReport type is documented here: https://pkg.go.dev/github.com/onsi/ginkgo/v2/types#SpecReport */ func CurrentGinkgoTestDescription() DeprecatedGinkgoTestDescription { deprecationTracker.TrackDeprecation( types.Deprecations.CurrentGinkgoTestDescription(), types.NewCodeLocation(1), ) report := global.Suite.CurrentSpecReport() if report.State == types.SpecStateInvalid { return GinkgoTestDescription{} } componentTexts := []string{} componentTexts = append(componentTexts, report.ContainerHierarchyTexts...) componentTexts = append(componentTexts, report.LeafNodeText) return DeprecatedGinkgoTestDescription{ ComponentTexts: componentTexts, FullTestText: report.FullText(), TestText: report.LeafNodeText, FileName: report.LeafNodeLocation.FileName, LineNumber: report.LeafNodeLocation.LineNumber, Failed: report.State.Is(types.SpecStateFailureStates), Duration: report.RunTime, } } /* Deprecated: GinkgoParallelNode() has been renamed to GinkgoParallelProcess() */ func GinkgoParallelNode() int { deprecationTracker.TrackDeprecation( types.Deprecations.ParallelNode(), types.NewCodeLocation(1), ) return GinkgoParallelProcess() } /* Deprecated: Benchmarker has been removed from Ginkgo 2.0 Use Gomega's gmeasure package instead. You can learn more here: https://onsi.github.io/ginkgo/#benchmarking-code */ type Benchmarker interface { Time(name string, body func(), info ...interface{}) (elapsedTime time.Duration) RecordValue(name string, value float64, info ...interface{}) RecordValueWithPrecision(name string, value float64, units string, precision int, info ...interface{}) } /* Deprecated: Measure() has been removed from Ginkgo 2.0 Use Gomega's gmeasure package instead. You can learn more here: https://onsi.github.io/ginkgo/#benchmarking-code */ func Measure(_ ...interface{}) bool { deprecationTracker.TrackDeprecation(types.Deprecations.Measure(), types.NewCodeLocation(1)) return true } golang-github-onsi-ginkgo-v2-2.22.0/docs/000077500000000000000000000000001472321612100200225ustar00rootroot00000000000000golang-github-onsi-ginkgo-v2-2.22.0/docs/.gitignore000066400000000000000000000000751472321612100220140ustar00rootroot00000000000000_site .sass-cache .jekyll-cache .jekyll-metadata vendor golang-github-onsi-ginkgo-v2-2.22.0/docs/Gemfile000066400000000000000000000021761472321612100213230ustar00rootroot00000000000000source "https://rubygems.org" # Hello! This is where you manage which Jekyll version is used to run. # When you want to use a different version, change it below, save the # file and run `bundle install`. Run Jekyll with `bundle exec`, like so: # # bundle exec jekyll serve # # This will help ensure the proper Jekyll version is running. # Happy Jekylling! # gem "jekyll", "~> 4.2.1" # This is the default theme for new Jekyll sites. You may change this to anything you like. gem "minima", "~> 2.5" # If you want to use GitHub Pages, remove the "gem "jekyll"" above and # uncomment the line below. To upgrade, run `bundle update github-pages`. gem "github-pages", "~> 231", group: :jekyll_plugins # If you have any plugins, put them here! group :jekyll_plugins do gem "jekyll-feed", "~> 0.17" end gem "webrick" # Windows and JRuby does not include zoneinfo files, so bundle the tzinfo-data gem # and associated library. platforms :mingw, :x64_mingw, :mswin, :jruby do gem "tzinfo", "~> 1.2" gem "tzinfo-data" end # Performance-booster for watching directories on Windows gem "wdm", "~> 0.1.1", :platforms => [:mingw, :x64_mingw, :mswin] golang-github-onsi-ginkgo-v2-2.22.0/docs/Gemfile.lock000066400000000000000000000165241472321612100222540ustar00rootroot00000000000000GEM remote: https://rubygems.org/ specs: activesupport (6.0.6.1) concurrent-ruby (~> 1.0, >= 1.0.2) i18n (>= 0.7, < 2) minitest (~> 5.1) tzinfo (~> 1.1) zeitwerk (~> 2.2, >= 2.2.2) addressable (2.8.6) public_suffix (>= 2.0.2, < 6.0) coffee-script (2.4.1) coffee-script-source execjs coffee-script-source (1.12.2) colorator (1.1.0) commonmarker (0.23.10) concurrent-ruby (1.2.3) dnsruby (1.72.0) simpleidn (~> 0.2.1) em-websocket (0.5.3) eventmachine (>= 0.12.9) http_parser.rb (~> 0) ethon (0.16.0) ffi (>= 1.15.0) eventmachine (1.2.7) execjs (2.9.1) faraday (2.9.0) faraday-net_http (>= 2.0, < 3.2) faraday-net_http (3.1.0) net-http ffi (1.16.3) forwardable-extended (2.6.0) gemoji (4.1.0) github-pages (231) github-pages-health-check (= 1.18.2) jekyll (= 3.9.5) jekyll-avatar (= 0.8.0) jekyll-coffeescript (= 1.2.2) jekyll-commonmark-ghpages (= 0.4.0) jekyll-default-layout (= 0.1.5) jekyll-feed (= 0.17.0) jekyll-gist (= 1.5.0) jekyll-github-metadata (= 2.16.1) jekyll-include-cache (= 0.2.1) jekyll-mentions (= 1.6.0) jekyll-optional-front-matter (= 0.3.2) jekyll-paginate (= 1.1.0) jekyll-readme-index (= 0.3.0) jekyll-redirect-from (= 0.16.0) jekyll-relative-links (= 0.6.1) jekyll-remote-theme (= 0.4.3) jekyll-sass-converter (= 1.5.2) jekyll-seo-tag (= 2.8.0) jekyll-sitemap (= 1.4.0) jekyll-swiss (= 1.0.0) jekyll-theme-architect (= 0.2.0) jekyll-theme-cayman (= 0.2.0) jekyll-theme-dinky (= 0.2.0) jekyll-theme-hacker (= 0.2.0) jekyll-theme-leap-day (= 0.2.0) jekyll-theme-merlot (= 0.2.0) jekyll-theme-midnight (= 0.2.0) jekyll-theme-minimal (= 0.2.0) jekyll-theme-modernist (= 0.2.0) jekyll-theme-primer (= 0.6.0) jekyll-theme-slate (= 0.2.0) jekyll-theme-tactile (= 0.2.0) jekyll-theme-time-machine (= 0.2.0) jekyll-titles-from-headings (= 0.5.3) jemoji (= 0.13.0) kramdown (= 2.4.0) kramdown-parser-gfm (= 1.1.0) liquid (= 4.0.4) mercenary (~> 0.3) minima (= 2.5.1) nokogiri (>= 1.13.6, < 2.0) rouge (= 3.30.0) terminal-table (~> 1.4) github-pages-health-check (1.18.2) addressable (~> 2.3) dnsruby (~> 1.60) octokit (>= 4, < 8) public_suffix (>= 3.0, < 6.0) typhoeus (~> 1.3) html-pipeline (2.14.3) activesupport (>= 2) nokogiri (>= 1.4) http_parser.rb (0.8.0) i18n (1.14.4) concurrent-ruby (~> 1.0) jekyll (3.9.5) addressable (~> 2.4) colorator (~> 1.0) em-websocket (~> 0.5) i18n (>= 0.7, < 2) jekyll-sass-converter (~> 1.0) jekyll-watch (~> 2.0) kramdown (>= 1.17, < 3) liquid (~> 4.0) mercenary (~> 0.3.3) pathutil (~> 0.9) rouge (>= 1.7, < 4) safe_yaml (~> 1.0) jekyll-avatar (0.8.0) jekyll (>= 3.0, < 5.0) jekyll-coffeescript (1.2.2) coffee-script (~> 2.2) coffee-script-source (~> 1.12) jekyll-commonmark (1.4.0) commonmarker (~> 0.22) jekyll-commonmark-ghpages (0.4.0) commonmarker (~> 0.23.7) jekyll (~> 3.9.0) jekyll-commonmark (~> 1.4.0) rouge (>= 2.0, < 5.0) jekyll-default-layout (0.1.5) jekyll (>= 3.0, < 5.0) jekyll-feed (0.17.0) jekyll (>= 3.7, < 5.0) jekyll-gist (1.5.0) octokit (~> 4.2) jekyll-github-metadata (2.16.1) jekyll (>= 3.4, < 5.0) octokit (>= 4, < 7, != 4.4.0) jekyll-include-cache (0.2.1) jekyll (>= 3.7, < 5.0) jekyll-mentions (1.6.0) html-pipeline (~> 2.3) jekyll (>= 3.7, < 5.0) jekyll-optional-front-matter (0.3.2) jekyll (>= 3.0, < 5.0) jekyll-paginate (1.1.0) jekyll-readme-index (0.3.0) jekyll (>= 3.0, < 5.0) jekyll-redirect-from (0.16.0) jekyll (>= 3.3, < 5.0) jekyll-relative-links (0.6.1) jekyll (>= 3.3, < 5.0) jekyll-remote-theme (0.4.3) addressable (~> 2.0) jekyll (>= 3.5, < 5.0) jekyll-sass-converter (>= 1.0, <= 3.0.0, != 2.0.0) rubyzip (>= 1.3.0, < 3.0) jekyll-sass-converter (1.5.2) sass (~> 3.4) jekyll-seo-tag (2.8.0) jekyll (>= 3.8, < 5.0) jekyll-sitemap (1.4.0) jekyll (>= 3.7, < 5.0) jekyll-swiss (1.0.0) jekyll-theme-architect (0.2.0) jekyll (> 3.5, < 5.0) jekyll-seo-tag (~> 2.0) jekyll-theme-cayman (0.2.0) jekyll (> 3.5, < 5.0) jekyll-seo-tag (~> 2.0) jekyll-theme-dinky (0.2.0) jekyll (> 3.5, < 5.0) jekyll-seo-tag (~> 2.0) jekyll-theme-hacker (0.2.0) jekyll (> 3.5, < 5.0) jekyll-seo-tag (~> 2.0) jekyll-theme-leap-day (0.2.0) jekyll (> 3.5, < 5.0) jekyll-seo-tag (~> 2.0) jekyll-theme-merlot (0.2.0) jekyll (> 3.5, < 5.0) jekyll-seo-tag (~> 2.0) jekyll-theme-midnight (0.2.0) jekyll (> 3.5, < 5.0) jekyll-seo-tag (~> 2.0) jekyll-theme-minimal (0.2.0) jekyll (> 3.5, < 5.0) jekyll-seo-tag (~> 2.0) jekyll-theme-modernist (0.2.0) jekyll (> 3.5, < 5.0) jekyll-seo-tag (~> 2.0) jekyll-theme-primer (0.6.0) jekyll (> 3.5, < 5.0) jekyll-github-metadata (~> 2.9) jekyll-seo-tag (~> 2.0) jekyll-theme-slate (0.2.0) jekyll (> 3.5, < 5.0) jekyll-seo-tag (~> 2.0) jekyll-theme-tactile (0.2.0) jekyll (> 3.5, < 5.0) jekyll-seo-tag (~> 2.0) jekyll-theme-time-machine (0.2.0) jekyll (> 3.5, < 5.0) jekyll-seo-tag (~> 2.0) jekyll-titles-from-headings (0.5.3) jekyll (>= 3.3, < 5.0) jekyll-watch (2.2.1) listen (~> 3.0) jemoji (0.13.0) gemoji (>= 3, < 5) html-pipeline (~> 2.2) jekyll (>= 3.0, < 5.0) kramdown (2.4.0) rexml kramdown-parser-gfm (1.1.0) kramdown (~> 2.0) liquid (4.0.4) listen (3.9.0) rb-fsevent (~> 0.10, >= 0.10.3) rb-inotify (~> 0.9, >= 0.9.10) mercenary (0.3.6) minima (2.5.1) jekyll (>= 3.5, < 5.0) jekyll-feed (~> 0.9) jekyll-seo-tag (~> 2.1) minitest (5.22.3) net-http (0.4.1) uri nokogiri (1.16.3-x86_64-linux) racc (~> 1.4) octokit (4.25.1) faraday (>= 1, < 3) sawyer (~> 0.9) pathutil (0.16.2) forwardable-extended (~> 2.6) public_suffix (5.0.5) racc (1.7.3) rb-fsevent (0.11.2) rb-inotify (0.10.1) ffi (~> 1.0) rexml (3.2.8) strscan (>= 3.0.9) rouge (3.30.0) rubyzip (2.3.2) safe_yaml (1.0.5) sass (3.7.4) sass-listen (~> 4.0.0) sass-listen (4.0.0) rb-fsevent (~> 0.9, >= 0.9.4) rb-inotify (~> 0.9, >= 0.9.7) sawyer (0.9.2) addressable (>= 2.3.5) faraday (>= 0.17.3, < 3) simpleidn (0.2.1) unf (~> 0.1.4) strscan (3.1.0) terminal-table (1.8.0) unicode-display_width (~> 1.1, >= 1.1.1) thread_safe (0.3.6) typhoeus (1.4.1) ethon (>= 0.9.0) tzinfo (1.2.10) thread_safe (~> 0.1) unf (0.1.4) unf_ext unf_ext (0.0.9.1) unicode-display_width (1.8.0) uri (0.13.0) webrick (1.8.1) zeitwerk (2.6.13) PLATFORMS x86_64-linux DEPENDENCIES github-pages (~> 231) jekyll-feed (~> 0.17) minima (~> 2.5) tzinfo (~> 1.2) tzinfo-data wdm (~> 0.1.1) webrick BUNDLED WITH 2.3.22 golang-github-onsi-ginkgo-v2-2.22.0/docs/MIGRATING_TO_V2.md000066400000000000000000001366451472321612100226550ustar00rootroot00000000000000--- layout: default title: Migrating to Ginkgo V2 --- {% raw %} # Ginkgo 2.0 Migration Guide [Ginkgo 2.0](https://github.com/onsi/ginkgo/issues/711) is a major release that adds substantial new functionality and removes/moves existing functionality. This document serves as a changelog and migration guide for users migrating from Ginkgo 1.x to 2.0. The intent is that the migration will take minimal user effort - please [open an issue](https://github.com/onsi/ginkgo/issues/new) if you run into any problems. The 2.0 work was tracked on issue [#711](https://github.com/onsi/ginkgo/issues/711) - you can refer to that issue to find the original proposal and backlog. ## Upgrading to Ginkgo 2.0 To upgrade to Ginkgo 2.0, assuming you are using `go mod`, you'll need to do the following in an existing or new project: 1. Upgrade to the v2 module: ```bash go get github.com/onsi/ginkgo/v2 ``` 2. Install the V2 CLI. Running this may require you to run a few additional `go get`s - just follow the go toolchain's instructions until you successfully get ginkgo v2 compiled: ```bash go install -mod=mod github.com/onsi/ginkgo/v2/ginkgo@latest ginkgo version //should print out "Ginkgo Version 2.0.0" ``` 3. Update all your import statements from `import github.com/onsi/ginkgo` to `import github.com/onsi/ginkgo/v2`. You can use your text editor to replace all instances of `"github.com/onsi/ginkgo` with `"github.com/onsi/ginkgo/v2` Updating to V2 will require you to make some changes to your test suites however the intent is that this work should be relatively minimal for most users. This migration guide should answer most questions - your first step is to simply run `ginkgo` and see what sorts of deprecation messages you get. Please don't hesitate to [open an issue](https://github.com/onsi/ginkgo/issues/new) if you run into any problems. With the release of Ginkgo 2.0 the 1.x version is formally deprecated and no longer supported. All future development will occur on version 2. The next sections describe the [new features in Ginkgo 2.0](#major-additions-and-improvements) and the [major changes](#major-changes) along with details on how to migrate your test code to adapt to the changes. At the end of this doc is an [FAQ](#faq) with common gotchas that will be tracked as they emerge. ## Major Additions and Improvements ### Interrupt Behavior Interrupt behavior is substantially improved, sending an interrupt signal will now: - immediately cause the current test to unwind. Ginkgo will run any `AfterEach` blocks, then immediately skip all remaining tests, then run the `AfterSuite` block. - emit information about which node Ginkgo was running when the interrupt signal was received. - emit as much information as possible about the interrupted test (e.g. `GinkgoWriter` contents, `stdout` and `stderr` context). - emit a stack trace of every running goroutine at the moment of interruption. Previously, sending a second interrupt signal would cause Ginkgo to exit immediately. With the improved interrupt behavior this is no longer necessary and Ginkgo will not exit until the test suite has unwound and completed. ### Timeout Behavior In Ginkgo V1.x, Ginkgo's timeout was managed by `go test`. This meant that timeouts exited the test suite abruptly with no opportunity for custom reporters or clean up code (e.g. `AfterEach`, `AfterSuite`) to run. This is fixed in V2. Ginkgo now manages its own timeout and when a timeout triggers the test winds down gracefully. In fact, a timeout is now functionally equivalent to a user-initiated interrupt. In addition, in V1.x when running multiple test suites Ginkgo would give each suite the full timeout allotment (so `ginkgo -r -timeout=1h` would give _each_ test suite one hour to complete). In V2 the timeout now applies to the entire test suite run so that `ginkgo -r -timeout=1h` is now guaranteed to exit after (about) one hour. **Finally, the default timeout has been reduced from `24h` down to `1h`.** Users with long-running tests may need to adjust the timeout in their CI scripts. ### Spec Decorators Specs can now be decorated with a series of new spec decorators. These decorators enable fine-grained control over certain aspects of the spec's creation and lifecycle. To support decorators, the signature for Ginkgo's container, setup, and It nodes have been changed to: ```go func Describe(text string, args ...interface{}) func It(text string, args ...interface{}) func BeforeEach(args ...interface{}) ``` Note that this change is backwards compatible with v1.X. Ginkgo supports passing in decorators _and_ arbitrarily nested slices of decorators. Ginkgo will unroll any slices and process the flattened list of decorators. This makes it easier to pass around and combine groups of decorators. In addition, decorators can be passed into the table-related DSL: `DescribeTable` and `Entry`. Here's a list of new decorators. They are documented in more detail in the [Node Decorator Reference](https://onsi.github.io/ginkgo/#node-decorators-overview) section of the documentation. #### Serial Decorator Specs can now be decorated with the `Serial` decorator. Specs decorated as `Serial` will never run in parallel with other specs. Instead, Ginkgo will run them on a single test process _after_ all the parallel tests have finished running. #### Ordered Decorator Spec containers (i.e. `Describe` and `Context` blocks) can now be decorated with the `Ordered` decorator. Specs within `Ordered` containers will always run in the order they appear and will never be randomized. In addition, when running in parallel, specs in an `Ordered` containers will always run on the same process to ensure spec order is preserved. When a spec in an `Ordered` container fails, all subsequent specs in the container are skipped. `Ordered` containers also support `BeforeAll` and `AfterAll` setup nodes. These nodes will run just once - the `BeforeAll` will run before any ordered tests in the container run; the `AfterAll` will run after all the ordered tests in the container are finished. Ordered containers are documented in more details in the [Ordered Container](https://onsi.github.io/ginkgo/#ordered-containers) section of the documentation. #### OncePerOrdered Decorator The `OncePerOrdered` decorator can be applied to setup nodes and causes them to run just once around ordered containers. More details in the [Setup around Ordered Containers: the OncePerOrdered Decorator](https://onsi.github.io/ginkgo/#setup-around-ordered-containers-the-onceperordered-decorator) section of the documentation. #### Label Decorator Specs can now be decorated with the `Label` decorator (see [Spec Labels](#spec-labels) below for details): ```go Describe("a labelled container", Label("red", "white"), Label("blue"), func() { It("a labelled test", Label("yellow"), func() { }) }) ``` the labels associated with a given spec is the union of the labels attached to that spec's `It` and any of the `It`'s containers. So `"a labelled test"` will have the labels `red`, `white`, `blue`, and `yellow`. Labels can be arbitrary strings however they cannot include any of the following characters: `"&|!,()/"`. #### Focus Decorator In addition to `FDescribe` and `FIt`, specs can now be focused using the new `Focus` decorator: ```go Describe("a focused container", Focus, func() { .... }) ``` #### Pending Decorator In addition to `PDescribe` and `PIt`, specs can now be focused using the new `Pending` decorator: ```go Describe("a focused container", Pending, func() { .... }) ``` #### Offset Decorator The `Offset(uint)` decorator allows the user to change the stack-frame offset used to compute the location of the test node. This is useful when building shared test behaviors. For example: ```go SharedBehaviorIt := func() { It("does something common and complicated", Offset(1), func() { ... }) } Describe("thing A", func() { SharedBehaviorIt() }) Describe("thing B", func() { SharedBehaviorIt() }) ``` now, if the `It` defined in `SharedBehaviorIt` the location reported by Ginkgo will point to the line where `SharedBehaviorIt` is *invoked*. #### FlakeAttempts Decorator The `FlakeAttempts(uint)` decorator allows the user to flag specific tests or groups of tests as potentially flaky. Ginkgo will run tests up to the number of times specified in `FlakeAttempts` until they pass. For example: ```go Describe("flaky tests", FlakeAttempts(3), func() { It("is flaky", func() { ... }) It("is also flaky", func() { ... }) It("is _really_ flaky", FlakeAttempts(5) func() { ... }) It("is _not_ flaky", FlakeAttempts(1), func() { ... }) }) ``` With this setup, `"is flaky"` and `"is also flaky"` will run up to 3 times. `"is _really_ flaky"` will run up to 5 times. `"is _not_ flaky"` will run only once. Note that if `ginkgo --flake-attempts=N` is set the value passed in by the CLI will override all the decorated values. Every test will now run up to `N` times. ### Spec Labels Users can now label specs using the [`Label` decorator](#label-decorator). Labels provide more fine-grained control for organizing specs and running specific subsets of labelled specs. Labels are arbitrary strings however they cannot contain the characters `"&|!,()/"`. A given spec inherits the labels of all its containers and any labels attached to the spec's `It`, for example: ```go Describe("Extracting widgets", Label("integration", "extracting widgets"), func() { It("can extract widgets from the external database", Label("network", "slow"), func() { //has labels [integration, extracting widgets, network, slow] }) It("can delete extracted widgets", Label("network"), func() { //has labels [integration, extracting widgets, network] }) It("can create new widgets locally", Label("local"), func() { //has labels [integration, extracting widgets, local] }) }) Describe("Editing widgets", Label("integration", "editing widgets"), func() { It("can edit widgets in the external database", Label("network", "slow"), func() { //has labels [integration, editing widgets, network, slow] }) It("errors if the widget does not exist", Label("network"), func() { //has labels [integration, editing widgets, network] }) }) ``` In addition an entire suite can be decorated by passing a `Label` decorator to `RunSpecs`: ```go RunSpecs(t, "My Suite", Label("top-level-label", "labels-all-specs")) ``` You can filter by label using the new `ginkgo --label-filter` flag. Label filter accepts a simple filter language that supports the following: - The `&&` and `||` logical binary operators representing AND and OR operations. - The `!` unary operator representing the NOT operation. - The `,` binary operator equivalent to `||`. - The `()` for grouping expressions. - All other characters will match as label literals. Label matches are case intensive and trailing and leading whitespace is trimmed. - Regular expressions can be provided using `/REGEXP/` notation. For example: - `ginkgo --label-filter=integration` will match any specs with the `integration` label. - `ginkgo --label-filter=!slow` will avoid any tests labelled `slow`. - `ginkgo --label-filter=(local || network) && !slow` will run any specs labelled `local` and `network` but without the `slow` label. - `ginkgo --label-filter=/widgets/ && !slow` will run any specs with a label that matches the regular expression `widgets` but does not include the `slow` label. This would match both the `extracting widgets` and `editing widgets` labels in our example above. ### DeferCleanup `DeferCleanup` allows users to move cleanup code out of `AfterEach/AfterAll/AfterSuite` and closer to the setup code that needs to be cleaned up. Based on the context in which it is called, `DeferCleanup` will effectively register a dynamic `AfterEach/AfterAll/AfterSuite` node to clean up after the test/test group/suite. The [docs](https://onsi.github.io/ginkgo/#spec-cleanup-aftereach-and-defercleanup) have more detailed examples. `DeferCleanup` allows `GinkgoT()` to more fully implement the `testing.T` interface. `Cleanup`, `TempDir`, and `Setenv` are now all supported. ### Aborting the Test Suite Users can now signal that the entire test suite should abort via `AbortSuite(message string, skip int)`. This will fail the current test and skip all subsequent tests. ### Improved: --fail-fast `ginkgo --fail-fast` now interrupts all test processes when a failure occurs and the tests are running in parallel. ### CLI Flags Ginkgo's CLI flags have been rewritten to provide clearer, better-organized documentation. In addition, Ginkgo v1 was mishandling several go cli flags. This is now resolved with clear distinctions between flags intended for compilation time and run-time. As a result, users can now generate `memprofile`s and `cpuprofile`s using the Ginkgo CLI. Ginkgo 2.0 will automatically merge profiles generated by running tests in parallel (i.e. across multiple processes) and will allow you to choose between having profiles stored in individual package directories, or collected in one place using the `-output-dir` flag. See [Changed: Profiling Support](#improved-profiling-support) for more details. ### Expanded GinkgoWriter Functionality The `GinkgoWriter` is used to write output that is only made visible if a test fails, or if the user runs in verbose mode with `ginkgo -v`. In Ginkgo 2.0 `GinkgoWriter` now has: - Three new convenience methods `GinkgoWriter.Print(a ...interface{})`, `GinkgoWriter.Println(a ...interface{})`, `GinkgoWriter.Printf(format string, a ...interface{})` These are equivalent to calling the associated `fmt.Fprint*` functions and passing in `GinkgoWriter`. - The ability to tee to additional writers. `GinkgoWriter.TeeTo(writer)` will send any future data written to `GinkgoWriter` to the passed in `writer`. You can attach multiple `io.Writer`s for `GinkgoWriter` to tee to. You can remove all attached writers with `GinkgoWriter.ClearTeeWriters()`. Note that _all_ data written to `GinkgoWriter` is immediately forwarded to attached tee writers regardless of where a test passes or fails. ### Improved: Reporting Infrastructure Ginkgo V2 provides an improved reporting infrastructure that [replaces and improves upon Ginkgo V1's support for custom reporters](#removed-custom-reporters). Here are a few distinct use-cases that the new reporting infrastructure supports: #### Generating machine-readable reports Ginkgo now natively supports generating and aggregating reports in a number of machine-readable formats - and these reports can be generated and managed by simply passing `ginkgo` command line flags. Ginkgo V2 introduces a new JSON format that faithfully captures all available information about a Ginkgo test suite. JSON reports can be generated via `ginkgo --json-report=out.json`. The resulting JSON file encodes an array of `types.Report`. Each entry in that array lists detailed information about the test suite and includes a list of `types.SpecReport` that captures detailed information about each spec. These types are documented [here](https://github.com/onsi/ginkgo/blob/ver2/types/types.go). Ginkgo also supports generating JUnit reports with `ginkgo --junit-report=out.xml` and Teamcity reports with `ginkgo --teamcity-report=out.teamcity`. In addition, Ginkgo V2's JUnit reporter has been improved and is now more conformant with the JUnit specification. Ginkgo follows the following rules when generating reports using these new `--FORMAT-report` flags: - By default, a single report file per format is generated at the passed-in file name. This single report merges all the reports generated by each individual suite. - If `-output-dir` is set: the report files are placed in the specified `-output-dir` directory. - If `-keep-separate-reports` is set, the individual reports generated by each test suite are not merged. Instead, individual report files will appear in each package directory. If `-output-dir` is _also_ set these individual files are copied into the `-output-dir` directory and namespaced with `PACKAGE_NAME_{REPORT}`. #### Generating Custom Reports when a test suite completes Ginkgo now provides a new node, `ReportAfterSuite`, with the following properties and constraints: - `ReportAfterSuite` nodes are passed a function that takes a `types.Report`: ```go var _ = ReportAfterSuite("custom reporter", func(report types.Report) { // do stuff with report }) ``` - These functions are called exactly once at the end of the test suite after any `AfterSuite` nodes have run. When running in parallel the functions are called on the primary Ginkgo process after all other processes have finished and is guaranteed to have an aggregated copy of `types.Report` that includes all `SpecReport`s from all the Ginkgo parallel processes. - If a failure occurs in `ReportAfterSuite` it is reported in reports sent to subsequent `ReportAfterSuite`s. In particular, it is reported as part of Ginkgo's default output and is in included in any reports generated by the `--FORMAT-report` flags described above. - `ReportAfterSuite` nodes **cannot** be interrupted by the user. This is to ensure the integrity of generated reports... so be careful what kind of code you put in there! - Multiple `ReportAfterSuite` nodes can be registered per test suite, but all must be defined at the top-level of the suite. `ReportAfterSuite` is useful for users who want to emit a custom-formatted report or register the results of the test run with an external service. #### Capturing report information about each spec as the test suite runs Ginkgo also provides a new node, `ReportAfterEach`, with the following properties and constraints: - `ReportAfterEach` nodes are passed a function that takes a `types.SpecReport`: ```go var _ = ReportAfterEach(func(specReport types.SpecReport) { // do stuff with specReport }) ``` - `ReportAfterEach` nodes are called after a spec completes (i.e. after any `AfterEach` nodes have run). `ReportAfterEach` nodes are **always** called - even if the test has failed, is marked pending, or is skipped. In this way, the user is guaranteed to have access to a report about every spec defined in a suite. - If a failure occurs in `ReportAfterEach`, the spec in question is marked as failed. Any subsequently defined `ReportAfterEach` block will receive an updated report that includes the failure. In general, though, assertions about your code should go in `AfterEach` nodes. - `ReportAfterEach` nodes **cannot** be interrupted by the user. This is to ensure the integrity of generated reports... so be careful what kind of code you put in there! - `ReportAfterEach` nodes can be placed in any container node in the suite's hierarchy - in this way the follow the same semantics as `AfterEach` blocks. - When running in parallel, `ReportAfterEach` nodes will run on the Ginkgo process that is running the spec being reported on. This means that multiple `ReportAfterEach` blocks can be running concurrently on independent processes. `ReportAfterEach` is useful if you need to stream or emit up-to-date information about the test suite as it runs. Ginkgo also provides `ReportBeforeEach` which is called before the test runs and receives a preliminary `types.SpecReport` - the state of this report will indicate whether the test will be skipped or is marked pending. ### New: Report Entries Ginkgo V2 supports attaching arbitrary data to individual spec reports. These are called `ReportEntries` and appear in the various report-related data structures (e.g. `Report` in `ReportAfterSuite` and `SpecReport` in `ReportAfterEach`) as well as the machine-readable reports generated by `--json-report`, `--junit-report`, etc. `ReportEntries` are also emitted to the console by Ginkgo's reporter and you can specify a visibility policy to control when this output is displayed. You attach data to a spec report via ```go AddReportEntry(name string, args ...interface{}) ``` `AddReportEntry` can be called from any runnable node (e.g. `It`, `BeforeEach`, `BeforeSuite`) - but not from the body of a container node (e.g. `Describe`, `Context`). `AddReportEntry` generates `ReportEntry` and attaches it to the current running spec. `ReportEntry` includes the passed in `name` as well as the time and source location at which `AddReportEntry` was called. Users can also attach a single object of arbitrary type to the `ReportEntry` by passing it into `AddReportEntry` - this object is wrapped and stored under `ReportEntry.Value` and is always included in the suite's JSON report. You can access the report entries attached to a spec by getting the `CurrentSpecReport()` or registering a `ReportAfterEach()` - the returned report will include the attached `ReportEntries`. You can fetch the value associated with the `ReportEntry` by calling `entry.GetRawValue()`. When called in-process this returns the object that was passed to `AddReportEntry`. When called after hydrating a report from JSON `entry.GetRawValue()` will include a parsed JSON `interface{}` - if you want to hydrate the JSON yourself into an object of known type you can `json.Unmarshal([]byte(entry.Value.AsJSON), &object)`. #### Supported Args `AddReportEntry` supports the `Offset` and `CodeLocation` decorators. These will control the source code location associated with the generated `ReportEntry`. You can also pass in a `time.Time` to override the `ReportEntry`'s timestamp. It also supports passing in a `ReportEntryVisibility` enum to control the report's visibility (see below). #### Controlling Output By default, Ginkgo's console reporter will emit any `ReportEntry` attached to a spec. It will emit the `ReportEntry` name, location, and time. If the `ReportEntry` value is non-nil it will also emit a representation of the value. If the value implements `fmt.Stringer` or `types.ColorableStringer` then `value.String()` or `value.ColorableString()` (which takes precedence) is used to generate the representation, otherwise Ginkgo uses `fmt.Sprintf("%#v", value)`. You can modify this default behavior by passing in one of the `ReportEntryVisibility` enum to `AddReportEntry`: - `ReportEntryVisibilityAlways`: the default behavior - the `ReportEntry` is always emitted. - `ReportEntryVisibilityFailureOrVerbose`: the `ReportEntry` is only emitted if the spec fails or is run with `-v` (similar to `GinkgoWriter`s behavior). - `ReportEntryVisibilityNever`: the `ReportEntry` is never emitted though it appears in any generated machine-readable reports (e.g. by setting `--json-report`). The console reporter passes the string representation of the `ReportEntry.Value` through Ginkgo's `formatter`. This allows you to generate colorful console output using the color codes documented in `github.com/onsi/ginkgo/formatter/formatter.go`. For example: ```go type StringerStruct struct { Label string Count int } // ColorableString for ReportEntry to use func (s StringerStruct) ColorableString() string { return fmt.Sprintf("{{red}}%s {{yellow}}{{bold}}%d{{/}}", s.Label, s.Count) } // non-colorable String() is used by go's string formatting support but ignored by ReportEntry func (s StringerStruct) String() string { return fmt.Sprintf("%s %d", s.Label, s.Count) } It("is reported", func() { AddReportEntry("Report", StringerStruct{Label: "Mahomes", Count: 15}) }) ``` Will emit a report that has the word "Mahomes" in red and the number 15 in bold and yellow. Lastly, it is possible to pass a pointer into `AddReportEntry`. Ginkgo will compute the string representation of the passed in pointer at the last possible moment - so any changes to the object _after_ it is reported will be captured in the final report. This is useful for building libraries on top of `AddReportEntry` - users can simply register objects when they're created and any subsequent mutations will appear in the generated report. ### New: Table-level Entry Descriptions Table `Entry`s can now opt-into table-level descriptions. Simply pass `nil` as the first argument into `Entry`. By default, Ginkgo will generate an `Entry` description from the `Entry`s parameters. You can also provide a string-returning function to `DescribeTable` which will be used to generate the description for these entries. There's also a new `EntryDescription` decorator that can be passed in to `DescribeTable` - `EntryDescription` wraps a format string that can be used to format the parameters associated with each `Entry` to generate it's description. For example: ```go var _ = Describe("Math", func() { DescribeTable("addition", func(a, b, c int) { Expect(a+b).To(Equal(c)) }, EntryDescription("%d + %d = %d") Entry(nil, 1, 2, 3), Entry(nil, -1, 2, 1), Entry("zeros", 0, 0, 0), Entry(EntryDescription("%[3]d = %[1]d + %[2]d"), 2, 3, 5) Entry(func(a, b, c int) string {fmt.Sprintf("%d = %d", a + b, c)}, 4, 3, 7) ) }) ``` Will generate entries named: `1 + 2 = 3`, `-1 + 2 = 1`, `zeros`, `5 = 2 + 3`, and `7 = 7`. ### Improved: Profiling Support Ginkgo V1 was incorrectly handling Go test's various profiling flags (e.g. -cpuprofile, -memprofile). This has been fixed in V2. In fact, V2 can capture profiles for multiple packages (e.g. ginkgo -r -cpuprofile=profile.out will work). When generating profiles for `-cpuprofile=FILE`, `-blockprofile=FILE`, `-memprofile=FILE`, `-mutexprofile=FILE`, and `-execution-trace=FILE` (Ginkgo's alias for `go test -test.trace`) the following rules apply: - If `-output-dir` is not set: each profile generates a file named `$FILE` in the directory of each package under test. - If `-output-dir` is set: each profile generates a file in the specified `-output-dir` directory. named `PACKAGE_NAME_$FILE` ### Improved: Cover Support Coverage reporting is much improved in 2.0: - `ginkgo -cover -p` now emits code coverage after the test completes, just like `ginkgo -cover` does in series. - When running across multiple packages (e.g. `ginkgo -r -cover`) ginkgo will now emit a composite coverage statistic that represents the total coverage across all test suites run. (Note that this is disabled if you set `-keep-separate-coverprofiles`). In addition, Ginkgo now follows the following rules when generating cover profiles using `-cover` and/or `-coverprofile=FILE`: - By default, a single cover profile is generated at `FILE` (or `coverprofile.out` if `-cover-profile` is not set but `-cover` is set). This includes the merged results of all the cover profiles reported by each suite. - If `-output-dir` is set: the `FILE` is placed in the specified `-output-dir` directory. - If `-keep-separate-coverprofiles` is set, the individual coverprofiles generated by each package are not merged and, instead, a file named `FILE` will appear in each package directory. If `-output-dir` is _also_ set these files are copied into the `-output-dir` directory and namespaced with `PACKAGE_NAME_{FILE}`. ### New: --repeat Ginkgo can now repeat a test suite N additional times by running `ginkgo --repeat=N`. This is similar to `go test -count=N+1` and is a variant of `ginkgo --until-it-fails` that can be run in CI environments to repeat test runs to suss out flakey tests. Ginkgo requires the tests to succeed during each repetition in order to consider the test run a success. ### New: --focus-file and --skip-file You can now tell Ginkgo to only run specs that match (or don't match) a given file filter. You can filter by filename as well as file:line. See the [Filtering Specs](https://onsi.github.io/ginkgo/#filtering-specs) documentation for more details. ### Improved: windows support for capturing stdout and stderr In V1 Ginkgo would run windows tests in parallel with the `--stream` option. This would result in hard-to-understand interleaved output. The reason behind this design choice was that it proved challenging to intercept all stdout and stderr output on Windows. V2 implements a best-effort output interception scheme for windows that entails reassigning the global `os.Stdout` and `os.Stderr` variables. While not as bullet-proof as the Unix `syscall.Dup2` based implementation, this is likely good enough for most usecases and allows Ginkgo support on Windows to come into parity with unix. ## Minor Additions and Improvements - `BeforeSuite` and `AfterSuite` no longer run if all tests in a suite are skipped. - The entire suite is skipped if `Skip()` is called in `BeforeSuite`. - Any output generated by `SynchronizedBeforeSuite`'s proc-1 function will now be immediately streamed to stdout, even when running in parallel. This is useful to debug complex long-running `SynchronizedBeforeSuite` setups. - Ginkgo's performance should be improved now when running multiple suites in a context where the go mod dependencies have not been fetched yet (e.g. on CI). Previously, Ginkgo would compile suites in parallel resulting in substantial slowdown when fetching the dependencies in parallel. In V2, Ginkgo compiles the first suite in series before compiling the remaining suites in parallel. - Ginkgo can now catch several common user gotchas and emit a helpful error. - Tables can now accept slices of []TableEntry in addition to individual entries. This allows for entries to be reused across different tables. - Error output is clearer and consistently links to relevant sections in the documentation. - `By` now emits a timestamp. It also registers a `ReportEntry` that appears in the suite report as structured data. If passed a callback, `By` will now time the callback and include the duration in the suite report. - Test randomization is now more stable as tests are now sorted deterministically on file_name:line_number first (previously they were sorted on test text which could not guarantee a stable sort). - A new "very verbose" setting is now available. Setting `-vv` implies `-v` but also causes skipped tests to be emitted. - Ginkgo's OutputInterceptor (the component that intercepts stdout/stderr when running in parallel) should now be more performant and better handle edge cases. It can be paused and resumed with PauseOutputInterception() and ResumeOutputInterception() and disabled entirely with --output-interceptor-mode=none. ## Major Changes These are major changes that will need user intervention to migrate successfully. ### Removed: Async Testing As described in the [Ginkgo 2.0 Proposal](https://docs.google.com/document/d/1h28ZknXRsTLPNNiOjdHIO-F2toCzq4xoZDXbfYaBdoQ/edit#heading=h.mzgqmkg24xoo) the Ginkgo 1.x implementation of asynchronous testing using a `Done` channel was a confusing source of test-pollution. It is removed in Ginkgo 2.0. In Ginkgo 2.0 tests of the form: ```go It("...", func(done Done) { // user test code to run asynchronously close(done) //signifies the test is done }, timeout) ``` will emit a deprecation warning and will run **synchronously**. This means the `timeout` will not be enforced and the status of the `Done` channel will be ignored - a test that hangs will hang indefinitely. #### Migration Strategy: We recommend users make targeted use of Gomega's [Asynchronous Assertions](https://onsi.github.io/gomega/#making-asynchronous-assertions) to better test asynchronous behavior. In addition, as of Ginkgo 2.3.0, users can [make individual nodes interruptible and reintroduce the notion of spec timeouts](https://onsi.github.io/ginkgo/#spec-timeouts-and-interruptible-nodes). As a first migration pass that produces **equivalent behavior** users can replace asynchronous tests with: ```go It("...", func(ctx SpecContext) { // user test code to run asynchronously }, NodeTimeout(timeout)) ``` if your code supports it, you can use the `ctx` passed in to the `It` to signal that the spec deadline has elapsed and cause the spec to exit. ### Removed: Measure As described in the [Ginkgo 2.0 Proposal](https://docs.google.com/document/d/1h28ZknXRsTLPNNiOjdHIO-F2toCzq4xoZDXbfYaBdoQ/edit#heading=h.2ezhpn4gmcgs) the Ginkgo 1.x implementation of benchmarking using `Measure` nodes was a source of tightly-coupled complexity. It is removed in Ginkgo 2.0. In Ginkgo 2.0 tests of the form: ```go Measure(..., func(b Benchmarker) { // user benchmark code }) ``` will emit a deprecation warning and **will no longer run**. #### Migration Strategy: Gomega now provides a benchmarking subpackage called `gmeasure`. Users should migrate to `gmeasure` by replacing `Measure` nodes with `It` nodes that create `gmeasure.Experiment`s and record values/durations. To generate output in Ginkgo reports add the `experiment` as a `ReportEntry` via `AddReportEntry(experiment.Name, experiment)`. ### Removed: Custom Reporters Ginkgo 2.0 removes support for Ginkgo 1.X's custom reporters - they behaved poorly when running in parallel and represented unnecessary and error-prone boiler plate for users who simply wanted to produce machine-readable reports. Instead, the reporting infrastructure has been significantly improved to enable simpler support for the most common use-cases and custom reporting needs. Please read through the [Improved: Reporting Infrastructure](#improved-reporting-infrastructure) section to learn more. For users with custom reporters, follow the migration guide below. #### Migration Strategy: In Ginkgo 2.0 both `RunSpecsWithDefaultAndCustomReporters` and `RunSpecsWithCustomReporters` have been deprecated. Users must call `RunSpecs` instead. If you were using custom reporters to generate JUnit or Teamcity reports, simply call `RunSpecs` and [invoke your tests with the new `--junit-report` and/or `--teamcity-report` flags](#generating-machine-readable-reports). Note that unlike the 1.X JUnit and Teamcity reporters, these flags generate unified reports for all test suites run (though you can adjust this with the `--keep-separate-reports` flag) and take care of aggregating reports from parallel processes for you. If you've written your own custom reporter, [add a `ReportAfterSuite` node](#generating-custom-reports-when-a-test-suite-completes) and process the `types.Report` that it provides you. If you'd like to continue using your custom reporter you can simply call `reporters.ReportViaDeprecatedReporter(reporter, report)` in `ReportAfterSuite` - though we recommend actually changing your code's logic to use the `types.Report` object directly as `reporters.ReportViaDeprecatedReporter` will be removed in a future release of Ginkgo 2.X. Unlike 1.X custom reporters which are called concurrently by independent parallel processes when running in parallel, `ReportAFterSuite` is called exactly once per suite and is guaranteed to have aggregated information from all parallel processes. Alternatively, you can use the new `--json-report` flag to produce a machine readable JSON-format report that you can post-process after the test completes. Finally, if you still need the real-time reporting capabilities that 1.X's custom reporters provided you can use [`ReportBeforeEach` and `ReportAfterEach`](#capturing-report-information-about-each-spec-as-the-test-suite-runs) to get information about each spec as it completes. ### Changed: First-class Support for Table Testing The table extension has been moved into the core Ginkgo DSL and the table functionality has been improved while maintaining backward compatibility. Users no longer need to `import "github.com/onsi/ginkgo/v2/extensions/table"`. Instead the table DSL is automatically pulled in by importing `"github.com/onsi/ginkgo/v2"`. #### Migration Strategy: Remove `"github.com/onsi/ginkgo/v2/extensions/table` imports. Code that was dot-importing both Ginkgo and the table extension should automatically work. If you were not dot-importing you will need to replace references to `table.DescribeTable` and `table.Entry` with `ginkgo.DescribeTable` and `ginkgo.Entry`. ### Changed: CurrentGinkgoTestDescription() `CurrentGinkgoTestDescription()` has been deprecated and will be removed in a future release. The method was returning a processed object that included a subset of information available about the running test. It has been replaced with `CurrentSpecReport()` which returns the full-fledge `types.SpecReport` used by Ginkgo's reporting infrastructure. To help users migrate, `types.SpecReport` now includes a number of helper methods to make it easier to extract information about the running test. #### Migration Strategy: Replace any calls to `CurrentGinkgoTestDescription()` with `CurrentSpecReport()` and use the struct fields or helper methods on the returned `types.SpecReport` to get the information you need about the current test. ### Changed: availability of Ginkgo's configuration In v1 Ginkgo's configuration could be accessed by importing the `config` package and accessing the globally available `GinkgoConfig` and `DefaultReporterConfig` objects. This is no longer supported in V2. V1 also allowed mutating the global config objects which could lead to strange behavior if done within a test. This too is no longer supported in V2. #### Migration Strategy: Instead, configuration can be accessed using the DSL's `GinkgoConfiguration()` function. This will return a `types.SuiteConfig` and `types.ReporterConfig`. Users generally don't need to access this configuration - the most commonly used fields by end users are already made available via `GinkgoRandomSeed()` and `GinkgoParallelProcess()`. It is generally recommended that users use the CLI to configure Ginkgo as some aspects of configuration must apply to the CLI as well as the suite under tests - nonetheless there are contexts where it is necessary to change Ginkgo's configuration programmatically. V2 supports this by allowing users to pass updated configuration into `RunSpecs`: ```go func TestMySuite(t *testing.T) { RegisterFailHandler(gomega.Fail) // fetch the current config suiteConfig, reporterConfig := GinkgoConfiguration() // adjust it suiteConfig.SkipStrings = []string{"NEVER-RUN"} reporterConfig.FullTrace = true // pass it in to RunSpecs RunSpecs(t, "My Suite", suiteConfig, reporterConfig) } ``` ### Renamed: GinkgoParallelNode `GinkgoParallelNode` has been renamed to `GinkgoParallelProcess` to reduce confusion around the word `node` and better capture Ginkgo's parallelization mechanism. #### Migration strategy: Change all instance of `GinkgoParallelNode()` to `GinkgoParallelProcess()` ### Changed: Command Line Flags All camel case flags (e.g. `-randomizeAllSpecs`) are replaced with kebab case flags (e.g. `-randomize-all-specs`) in Ginkgo 2.0. The camel case versions continue to work but emit a deprecation warning. #### Migration Strategy: Users should update any scripts they have that invoke the `ginkgo` cli from camel case to kebab case (:camel: :arrow_right: :oden:). ### Removed: -stream `-stream` was originally introduce in Ginkgo 1.x to force parallel test processes to emit output simultaneously in order to help debug hanging test issues. With improvements to Ginkgo's interrupt handling and parallel test reporting this behavior is no longer necessary and has been removed. ### Removed: -notify `-notify` instructed Ginkgo to emit desktop notifications on linux and MacOS. This feature was rarely used and has been removed. ### Removed: -noisyPendings and -noisySkippings Both these flags tweaked the reporter's behavior for pending and skipped tests but never worked quite right. Now the user can specify between four verbosity levels. `--succinct`, no verbosity setting, `-v`, and `-vv`. Specifically, when run with `-vv` skipped tests will emit their titles and code locations - otherwise skipped tests are silent. ### Changed: -slowSpecThreshold `-slowSpecThreshold` is now `-slow-spec-threshold` and takes a `time.Duration` (e.g. `5s` or `3m`) instead of a `float64` number of seconds. ### Renamed: -reportPassed `-reportPassed` is now `--always-emit-ginkgo-writer` which better captures the intent of the flag; namely to always emit any GinkgoWriter content, even if the spec has passed. ### Removed: -debug The `-debug` flag has been removed. It functioned primarily as a band-aid to Ginkgo V1's poor handling of stuck parallel tests. The new [interrupt behavior](#interrupt-behavior) in V2 resolves the root issues behind the `-debug` flag. ### Removed: -regexScansFilePath `-regexScansFilePath` allowed users to have the `-focus` and `-skip` regular expressions apply to filenames. It is now removed in favor of `-focus-file` and `-skip-file` which provide more granular and explicit control over focusing/skipping files and line numbers. #### Migration Strategy: Users should remove -stream from any scripts they have that invoke the `ginkgo` cli. ### Removed: ginkgo nodot The `ginkgo nodot` subcommand in V1, along with the `--nodot` flags for `ginkgo bootstrap` and `ginkgo generate` were provided to allow users to avoid a `.` import of Ginkgo and Gomega but still have access to the exported variables and types at the top-level. This was implemented by defining top-level aliases that pointed to the objects and types in the imported Ginkgo and Gomega libraries in the user's bootstrap file. In practice most users either dot-import Ginkgo and Gomega, or they don't and use the imported package name to refer to objects and types instead. V2 removes the support generating and maintaining these alias lists. `--nodot` remains for `ginkgo bootstrap` and `ginkgo generate` and it simply avoids dot-importing Ginkgo and Gomega. As a result of this change custom bootstrap and generate templates may need to be updated: 1. `ginkgo generate` templates should no longer reference `{{.IncludeImports}}`. Instead they should `import {{.GinkgoImport}}` and `import {{.GomegaImport}}`. 2. Both `ginkgo generate` and `ginkgo boostrap` templates can use `{{.GinkgoPackage}}` and `{{.GomegaPackage}}` to correctly reference any names exported by Ginkgo or Gomega. For example: ```go import ( {{.GinkgoImport}} {{.GomegaImport}} } var _ = {{.GinkgoPackage}}It("is templated", func() { {{.GomegaPackage}}Expect(foo).To({{.GomegaPackage}}Equal(bar)) }) ``` will generate the correct output if `--nodot` is specified by the user. ### Removed: ginkgo convert The `ginkgo convert` subcommand in V1 could convert an existing set of Go tests into a Ginkgo test suite, wrapping each `TestX` function in an `It`. This subcommand added complexity to the codebase and was infrequently used. It has been removed. Users who want to convert tests suites over to Ginkgo will need to do so by hand. ## Minor Changes These are minor changes that will be transparent for most users. - `"top level"` is no longer the first element in `types.SpecReport.NodeTexts`. This will only affect users who write custom reporters. - The output format of Ginkgo's Default Reporter has changed in numerous subtle ways to improve readability and the user experience. Users who were scraping Ginkgo output programmatically may need to change their scripts or use the new JSON formatted report option. - When running in series and verbose mode (i.e. `ginkgo -v`) GinkgoWriter output is emitted in real-time (existing behavior) but also emitted in the failure message for failed tests. This allows for consistent failure messages regardless of verbosity settings and also makes it possible for the resulting JSON report to include captured GinkgoWriter information. - Removed `ginkgo blur` alias. Use `ginkgo unfocus` instead. ## FAQ As users have started adopting Ginkgo v2 they've bumped into a few specific issues. This FAQ will grow as these issues are identified to help address them. ### Can I mix Ginkgo V1 and Ginkgo V2? ..._ish_. **What you _can't_ do** Under the hood Ginkgo V2 is effectively a rewrite of Ginkgo V1. While the external interfaces are largely compatible (modulo the differences pointed out in this doc) the internals are very different. Because of this **it is not possible** to import and use V1 _and_ V2 **in the same _package_**. In fact, trying to do so will result in a crash as Ginkgo V1's `init` function and Ginkgo V2's `init` function will register conflicting command line flags. That means you can't do something like: ```go /* sprockets/widget_test.go */ import ( . "github.com/onsi/ginkgo" //v1 ) var _ = It("uses V1", func() {...}) /* sprockets/doodad_test.go */ import ( . "github.com/onsi/ginkgo/v2" //v2 ) var _ = It("uses V2", func() {...}) ``` It _also_ means you can't use a _dependency_ in your test that, in turn, imports a mismatched version of Ginkgo. For example, let's say we have a test helper package: ```go /* helpers/test_helper.go */ import ( "github.com/onsi/ginkgo" //imports v1 ) func EnsureNoSprocketRust(sprocket *Sprocket) { if sprocket.IsRusty() { Fail("Sprocket rust detected") } } ``` this test helper package imports Ginkgo V1. If we try to use it in a test package that uses Ginkgo V2: ```go /* sprockets/widget_test.go */ import ( . "github.com/onsi/ginkgo/v2" //v2 "helpers" //imports v1 => boom ) var _ = It("has no rusty sprockets", func() { helpers.EnsureNoSprocketRust(sprocket) }) ``` this won't work as the two versions of Ginkgo will be imported and result in a conflict. Lastly, you can run into this issue accidentally while upgrading to 2.0 if you update some, but not all, of the import statements in your package. **What you _can_ do** While you cannot import V1 and V2 in the same package you _can_ have some packages that use V1 and other packages that use V2 associated with a given module. The different test packages are compiled separately and the V1 packages will use Ginkgo V1 whereas the V2 packages will use Ginkgo V2. Go basically treats different major versions of a dependency as completely different packages. This means that your dependencies can use a different major version of Ginkgo for _their_ test suites than your codebase (as long as you aren't importing a test-helper dependency into your test suite and running into the major version clash described above). This _also_ means that you can, in principle, upgrade different test suites in your module at different times. For example, in a fictitious `factory` module the `sprockets` package can be upgraded to Ginkgo V2 first, and the `convery_belt` package can stay at Ginkgo V1 until later. In _practice_ however, you'll run into difficulties as the `ginkgo` cli used to invoke the tests will be at a different major version than some subset of packages under test - this basically won't work because of changes in the client/server contract between the CLI and the test library across the two major versions. So you'll need to take care to use the correct version of the cli with the correct test package. In general the migration to V2 is intended to be simple enough that you should rarely need to resort to having mixed-version numbers like this. ### A symbol in V2 now clashes with a symbol in my codebase. What do I do? If Ginkgo 2.0 introduces a new exported symbol that now clashes with your codebase (because you are dot-importing Ginkgo). Check out the [Alternatives to Dot-Importing Ginkgo](https://onsi.github.io/ginkgo/#alternatives-to-dot-importing-ginkgo) section of the documentation for some options. You may be able to, instead, dot-import just a subset of the Ginkgo DSL using the new `github.com/onsi/ginkgo/v2/dsl` set of packages. Specifically when upgrading from v1 to v2 if you see a dot-import clash due to a newly introduced symbol (e.g. the new `Label` decorator) you can instead choose to dot-import the core DSL and import the `decorator` dsl separately: ```go import ( . "github.com/onsi/ginkgo/v2/dsl/core" "github.com/onsi/ginkgo/v2/dsl/decorators" ) var _ = It("gives you the core DSL", decorators.Label("and namespaced decorators"), func() { ... }) ``` ### I've upgraded to V2 and now have race conditions in my test. What do I do? Most likely you are launching a goroutine that outlives the spec it was launched in and calling `By` in it. You probably didn't intend to have the goroutine outlive its spec so you'll probably want to fix that. More details here: https://github.com/onsi/ginkgo/issues/844 If that isn't the cause of your race condition you may have come across a bug, Please [open an issue](https://github.com/onsi/ginkgo/issues/new)! {% endraw %} golang-github-onsi-ginkgo-v2-2.22.0/docs/_config.yml000066400000000000000000000002401472321612100221450ustar00rootroot00000000000000baseurl: "/ginkgo" # the subpath of your site, e.g. /blog # Build settings name: Ginkgo markdown: GFM highlighter: rouge lsi: false exclude: - "*.go"golang-github-onsi-ginkgo-v2-2.22.0/docs/_layouts/000077500000000000000000000000001472321612100216615ustar00rootroot00000000000000golang-github-onsi-ginkgo-v2-2.22.0/docs/_layouts/default.html000066400000000000000000000026121472321612100241740ustar00rootroot00000000000000
{{ content }}
golang-github-onsi-ginkgo-v2-2.22.0/docs/css/000077500000000000000000000000001472321612100206125ustar00rootroot00000000000000golang-github-onsi-ginkgo-v2-2.22.0/docs/css/layout.css000066400000000000000000000125251472321612100226460ustar00rootroot00000000000000:root { --max-width: 1024px; --header-height: 50px; --breakpoint: 640px; --ginkgo-green: #2dad6c; --ginkgo-green-darker: #0d8d5c; --ginkgo-green-faint: #fafffa; } body { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"; font-size: 14px; margin: 0; } #header { background-color: var(--ginkgo-green); color: #fff; border-bottom: solid 1px var(--ginkgo-green-darker); } #left-background, #right-background { height: 100vh; background-color: var(--ginkgo-green-faint); } #left-background { box-shadow: inset -2px 0 7px -5px var(--ginkgo-green-darker); } #right-background { box-shadow: inset 2px 0 7px -5px var(--ginkgo-green-darker); } #content { background-color: #fff; overflow-y: scroll; padding: 5px; padding-right:10px; } #sidebar { background-color: #fff; overflow-y: scroll; padding:5px; position:relative; font-size: 16px; } #mask { display:none; } img[alt="Ginkgo"] { max-width: 80%; max-height: 200px; display: block; margin-left: auto; margin-right: auto; } /* code styling */ .markdown-body div.highlight { margin-left: 20px; margin-right: 20px; border-radius: 10px; margin-bottom: 16px !important; max-width: 800px; border: solid 2px #f0f0f0; } .markdown-body .highlight pre, .markdown-body pre { background-color: transparent; } div.highlight.invalid { border: solid 2px #f0aaaa; background-color: #f0dddd; } /* sidebar items */ .sidebar-heading, .sidebar-item { display:block; text-decoration: none; color: #000; } .sidebar-heading { margin:5px 5px; padding:3px 0; } .sidebar-heading.active { font-weight:bold; color: var(--ginkgo-green); } .sidebar-item { font-size: 0.8rem; margin: 5px 0 5px 10px; } .sidebar-item:nth-child(2n+1) { color:#555; } .sidebar-item.active { font-weight:bold; color: var(--ginkgo-green); } .sidebar-section { overflow: hidden; max-height: 0; // transition: max-height 0.3s; } .sidebar-heading.active + .sidebar-section { max-height: 1000px; } /* header */ #header { display: flex; align-items: center; } .brand { font-size: 1.8rem; margin-left:10px; font-weight: bold; text-decoration: none; color: #fff; } .spacer { flex: 1; } .logo { max-height: 22px; margin: 0 5px; } a:last-of-type .logo { margin-right: 10px; } #disclosure { box-sizing: border-box; width: var(--header-height); height: var(--header-height); display: flex; flex-direction: column; align-items: center; justify-content: space-around; padding: 12px 0; cursor: pointer; } .hamburger-slice { width: calc(var(--header-height) - 20px); height: 2px; border-radius: 4px; background-color: #fff; } /* content styling */ #content h2 { border-bottom: none; } /* Desktop */ @media screen and (min-width: 640px) { #container { --sidebar-width: 200px; display: grid; grid-template-areas: "left-background header header right-background" "left-background sidebar content right-background"; grid-template-columns: 1fr var(--sidebar-width) minmax(calc(var(--breakpoint) - var(--sidebar-width)), var(--max-width)) 1fr; grid-template-rows: var(--header-height) auto; gap: 0; height:100vh; } #left-background { grid-area: left-background; } #right-background { grid-area: right-background; } #header { grid-area: header; } #sidebar { grid-area: sidebar; border-right: 5px solid #fff; } #content { grid-area: content; } #disclosure { display: none; } } /* Mobile */ @media screen and (max-width: 640px) { #container { --sidebar-width: 300px; display: grid; grid-template-areas: "header" "content"; grid-template-columns: 1fr; grid-template-rows: var(--header-height) auto; gap: 0; height:100vh; } #header { grid-area: header; } #content { grid-area: content; } #left-background { display: none; } #right-background { display: none; } #sidebar { position: fixed; width: var(--sidebar-width); top: var(--header-height); bottom: 0px; right: calc(-5px - var(--sidebar-width)); z-index:2; transition: right 0.3s; } #mask { background-color: rgba(0,0,0,0); position: fixed; width: 100vw; top: var(--header-height); bottom: 0px; left:0px; z-index:1; cursor: pointer; transition: background-color 0.3s; } #container.reveal-sidebar #sidebar { right: 0px; } #container.reveal-sidebar #mask { display:block; background-color: rgba(0,0,0,0.2); } .hamburger-slice { transition: transform 0.3s, opacity 0.1s; } #container .hamburger-slice:nth-child(1) { transform: rotate(0deg); transform-origin: top left; } #container .hamburger-slice:nth-child(2) { opacity: 100; } #container .hamburger-slice:nth-child(3) { transform: rotate(0deg); transform-origin: bottom left; } #container.reveal-sidebar .hamburger-slice:nth-child(1) { transform: rotate(35deg); } #container.reveal-sidebar .hamburger-slice:nth-child(2) { opacity: 0; } #container.reveal-sidebar .hamburger-slice:nth-child(3) { transform: rotate(-35deg); } } /* Print */ @media print { body { font-size: 10pt; margin: 0.25in; background-image: none; } #header { display: none; } #sidebar { display: none; } #left-background { display: none; } #right-background { display: none; } h2 { break-before: page; } .highlight { break-inside: avoid-page; } } golang-github-onsi-ginkgo-v2-2.22.0/docs/css/primer-minimal.css000066400000000000000000000253661472321612100242620ustar00rootroot00000000000000.markdown-body { line-height: 1.5; word-wrap: break-word; } .markdown-body > * :first-child { margin-top: 0 !important; } .markdown-body > * :last-child { margin-bottom: 0 !important; } .markdown-body a { color: #000; text-decoration: underline; } .markdown-body a:visited { color: #333; text-decoration: underline; } .markdown-body .anchorjs-link { text-decoration: none; } .markdown-body .absent { color: #cb2431; } .markdown-body .anchor { float: left; padding-right: 4px; margin-left: -20px; line-height: 1; } .markdown-body .anchor:focus { outline: none; } .markdown-body p, .markdown-body blockquote, .markdown-body ul, .markdown-body ol, .markdown-body dl, .markdown-body table, .markdown-body pre { margin-top: 0; margin-bottom: 16px; } .markdown-body hr { height: 0.25em; padding: 0; margin: 24px 0; background-color: #e1e4e8; border: 0; } .markdown-body blockquote { padding: 0 1em; color: #6a737d; border-left: 0.25em solid #dfe2e5; } .markdown-body blockquote > :first-child { margin-top: 0; } .markdown-body blockquote > :last-child { margin-bottom: 0; } .markdown-body kbd { display: inline-block; padding: 3px 5px; font-size: 11px; line-height: 10px; color: #444d56; vertical-align: middle; background-color: #fafbfc; border: solid 1px #c6cbd1; border-bottom-color: #959da5; border-radius: 3px; box-shadow: inset 0 -1px 0 #959da5; } .markdown-body h1, .markdown-body h2, .markdown-body h3, .markdown-body h4, .markdown-body h5, .markdown-body h6 { margin-top: 24px; margin-bottom: 16px; font-weight: 600; line-height: 1.25; } .markdown-body h1 .octicon-link, .markdown-body h2 .octicon-link, .markdown-body h3 .octicon-link, .markdown-body h4 .octicon-link, .markdown-body h5 .octicon-link, .markdown-body h6 .octicon-link { color: #1b1f23; vertical-align: middle; visibility: hidden; } .markdown-body h1:hover .anchor, .markdown-body h2:hover .anchor, .markdown-body h3:hover .anchor, .markdown-body h4:hover .anchor, .markdown-body h5:hover .anchor, .markdown-body h6:hover .anchor { text-decoration: none; } .markdown-body h1:hover .anchor .octicon-link, .markdown-body h2:hover .anchor .octicon-link, .markdown-body h3:hover .anchor .octicon-link, .markdown-body h4:hover .anchor .octicon-link, .markdown-body h5:hover .anchor .octicon-link, .markdown-body h6:hover .anchor .octicon-link { visibility: visible; } .markdown-body h1 tt, .markdown-body h1 code, .markdown-body h2 tt, .markdown-body h2 code, .markdown-body h3 tt, .markdown-body h3 code, .markdown-body h4 tt, .markdown-body h4 code, .markdown-body h5 tt, .markdown-body h5 code, .markdown-body h6 tt, .markdown-body h6 code { font-size: inherit; } .markdown-body h1 { padding-bottom: 0.3em; font-size: 2em; border-bottom: 1px solid #eaecef; } .markdown-body h2 { padding-bottom: 0.3em; font-size: 1.5em; border-bottom: 1px solid #eaecef; } .markdown-body h3 { font-size: 1.25em; } .markdown-body h4 { font-size: 1em; } .markdown-body h5 { font-size: 0.875em; } .markdown-body h6 { font-size: 0.85em; color: #6a737d; } .markdown-body ul, .markdown-body ol { padding-left: 2em; } .markdown-body ul.no-list, .markdown-body ol.no-list { padding: 0; list-style-type: none; } .markdown-body ul ul, .markdown-body ul ol, .markdown-body ol ol, .markdown-body ol ul { margin-top: 0; margin-bottom: 0; } .markdown-body li { word-wrap: break-all; } .markdown-body li > p { margin-top: 16px; } .markdown-body li + li { margin-top: 0.25em; } .markdown-body dl { padding: 0; } .markdown-body dl dt { padding: 0; margin-top: 16px; font-size: 1em; font-style: italic; font-weight: 600; } .markdown-body dl dd { padding: 0 16px; margin-bottom: 16px; } .markdown-body table { display: block; width: 100%; overflow: auto; } .markdown-body table th { font-weight: 600; } .markdown-body table th, .markdown-body table td { padding: 6px 13px; border: 1px solid #dfe2e5; } .markdown-body table tr { background-color: #fff; border-top: 1px solid #c6cbd1; } .markdown-body table tr:nth-child(2n) { background-color: #f6f8fa; } .markdown-body table img { background-color: transparent; } .markdown-body img { max-width: 100%; box-sizing: content-box; background-color: #fff; } .markdown-body img[align=right] { padding-left: 20px; } .markdown-body img[align=left] { padding-right: 20px; } .markdown-body .emoji { max-width: none; vertical-align: text-top; background-color: transparent; } .markdown-body span.frame { display: block; overflow: hidden; } .markdown-body span.frame > span { display: block; float: left; width: auto; padding: 7px; margin: 13px 0 0; overflow: hidden; border: 1px solid #dfe2e5; } .markdown-body span.frame span img { display: block; float: left; } .markdown-body span.frame span span { display: block; padding: 5px 0 0; clear: both; color: #24292e; } .markdown-body span.align-center { display: block; overflow: hidden; clear: both; } .markdown-body span.align-center > span { display: block; margin: 13px auto 0; overflow: hidden; text-align: center; } .markdown-body span.align-center span img { margin: 0 auto; text-align: center; } .markdown-body span.align-right { display: block; overflow: hidden; clear: both; } .markdown-body span.align-right > span { display: block; margin: 13px 0 0; overflow: hidden; text-align: right; } .markdown-body span.align-right span img { margin: 0; text-align: right; } .markdown-body span.float-left { display: block; float: left; margin-right: 13px; overflow: hidden; } .markdown-body span.float-left span { margin: 13px 0 0; } .markdown-body span.float-right { display: block; float: right; margin-left: 13px; overflow: hidden; } .markdown-body span.float-right > span { display: block; margin: 13px auto 0; overflow: hidden; text-align: right; } .markdown-body code, .markdown-body tt { padding: 0.2em 0.4em; margin: 0; font-size: 85%; background-color: rgba(27, 31, 35, 0.05); border-radius: 3px; } .markdown-body code br, .markdown-body tt br { display: none; } .markdown-body del code { text-decoration: inherit; } .markdown-body pre { word-wrap: normal; } .markdown-body pre > code { padding: 0; margin: 0; font-size: 100%; word-break: normal; white-space: pre; background: transparent; border: 0; } .markdown-body .highlight { margin-bottom: 16px; } .markdown-body .highlight pre { margin-bottom: 0; word-break: normal; } .markdown-body .highlight pre, .markdown-body pre { padding: 16px; overflow: auto; font-size: 85%; line-height: 1.45; background-color: #f6f8fa; border-radius: 3px; } .markdown-body pre code, .markdown-body pre tt { display: inline; max-width: auto; padding: 0; margin: 0; overflow: visible; line-height: inherit; word-wrap: normal; background-color: transparent; border: 0; } .markdown-body .csv-data td, .markdown-body .csv-data th { padding: 5px; overflow: hidden; font-size: 12px; line-height: 1; text-align: left; white-space: nowrap; } .markdown-body .csv-data .blob-num { padding: 10px 8px 9px; text-align: right; background: #fff; border: 0; } .markdown-body .csv-data tr { border-top: 0; } .markdown-body .csv-data th { font-weight: 600; background: #f6f8fa; border-top: 0; } .highlight table td { padding: 5px; } .highlight table pre { margin: 0; } .highlight .cm { color: #999988; font-style: italic; } .highlight .cp { color: #999999; font-weight: bold; } .highlight .c1 { color: #999988; font-style: italic; } .highlight .cs { color: #999999; font-weight: bold; font-style: italic; } .highlight .c, .highlight .cd { color: #999988; font-style: italic; } .highlight .err { color: #a61717; background-color: #e3d2d2; } .highlight .gd { color: #000000; background-color: #ffdddd; } .highlight .ge { color: #000000; font-style: italic; } .highlight .gr { color: #aa0000; } .highlight .gh { color: #999999; } .highlight .gi { color: #000000; background-color: #ddffdd; } .highlight .go { color: #888888; } .highlight .gp { color: #555555; } .highlight .gs { font-weight: bold; } .highlight .gu { color: #aaaaaa; } .highlight .gt { color: #aa0000; } .highlight .kc { color: #000000; font-weight: bold; } .highlight .kd { color: #000000; font-weight: bold; } .highlight .kn { color: #000000; font-weight: bold; } .highlight .kp { color: #000000; font-weight: bold; } .highlight .kr { color: #000000; font-weight: bold; } .highlight .kt { color: #445588; font-weight: bold; } .highlight .k, .highlight .kv { color: #000000; font-weight: bold; } .highlight .mf { color: #009999; } .highlight .mh { color: #009999; } .highlight .il { color: #009999; } .highlight .mi { color: #009999; } .highlight .mo { color: #009999; } .highlight .m, .highlight .mb, .highlight .mx { color: #009999; } .highlight .sb { color: #d14; } .highlight .sc { color: #d14; } .highlight .sd { color: #d14; } .highlight .s2 { color: #d14; } .highlight .se { color: #d14; } .highlight .sh { color: #d14; } .highlight .si { color: #d14; } .highlight .sx { color: #d14; } .highlight .sr { color: #009926; } .highlight .s1 { color: #d14; } .highlight .ss { color: #990073; } .highlight .s { color: #d14; } .highlight .na { color: #008080; } .highlight .bp { color: #999999; } .highlight .nb { color: #0086B3; } .highlight .nc { color: #445588; font-weight: bold; } .highlight .no { color: #008080; } .highlight .nd { color: #3c5d5d; font-weight: bold; } .highlight .ni { color: #800080; } .highlight .ne { color: #990000; font-weight: bold; } .highlight .nf { color: #990000; font-weight: bold; } .highlight .nl { color: #990000; font-weight: bold; } .highlight .nn { color: #555555; } .highlight .nt { color: #000080; } .highlight .vc { color: #008080; } .highlight .vg { color: #008080; } .highlight .vi { color: #008080; } .highlight .nv { color: #008080; } .highlight .ow { color: #000000; font-weight: bold; } .highlight .o { color: #000000; font-weight: bold; } .highlight .w { color: #bbbbbb; } .highlight { background-color: #f8f8f8; }golang-github-onsi-ginkgo-v2-2.22.0/docs/docs_suite_test.go000066400000000000000000000061151472321612100235540ustar00rootroot00000000000000package docs_test import ( "go/ast" "go/doc" "go/parser" "go/token" "os" "path/filepath" "regexp" "strings" "testing" . "github.com/onsi/ginkgo/v2" "github.com/onsi/ginkgo/v2/internal/test_helpers" . "github.com/onsi/gomega" ) func TestDocs(t *testing.T) { RegisterFailHandler(Fail) RunSpecs(t, "Docs Suite") } var anchors test_helpers.Anchors var _ = BeforeSuite(func() { var err error anchors, err = test_helpers.LoadAnchors(test_helpers.DOCS, "../") Ω(err).ShouldNot(HaveOccurred()) }) var _ = Describe("Validating internal links", func() { var entries = []TableEntry{ Entry("Narrative Documentation", "index.md"), Entry("V2 Migration Documentation", "MIGRATING_TO_V2.md"), Entry("Repo Readme", "README.md"), } DescribeTable("Ensuring no headings have any markdown formatting characters in them", func(name string) { headings, err := test_helpers.LoadMarkdownHeadings(test_helpers.DOCS.DocWithName(name).Path("../")) Ω(err).ShouldNot(HaveOccurred()) failed := false for _, heading := range headings { if strings.ContainsAny(heading, "`*_~#") { failed = true GinkgoWriter.Printf("%s: '%s'\n", name, heading) } } if failed { Fail("Identified invalid headings") } }, entries) DescribeTable("Ensuring all anchors resolve", func(name string) { links, err := test_helpers.LoadMarkdownLinks(test_helpers.DOCS.DocWithName(name).Path("../")) Ω(err).ShouldNot(HaveOccurred()) Ω(links).ShouldNot(BeEmpty()) failed := false for _, link := range links { if !anchors.IsResolvable(name, link) { failed = true GinkgoWriter.Printf("%s: '%s'\n", name, link) } } if failed { Fail("Identified invalid links") } }, entries) }) var _ = Describe("Validating godoc links", func() { It("validates that all links in the core dsl package are good", func() { fset := token.NewFileSet() entries, err := os.ReadDir("../") Ω(err).ShouldNot(HaveOccurred()) parsedFiles := []*ast.File{} for _, entry := range entries { name := entry.Name() if !strings.HasSuffix(name, ".go") { continue } parsed, err := parser.ParseFile(fset, filepath.Join("../", name), nil, parser.ParseComments) Ω(err).ShouldNot(HaveOccurred()) parsedFiles = append(parsedFiles, parsed) } p, err := doc.NewFromFiles(fset, parsedFiles, "github.com/onsi/ginkgo/v2") Ω(err).ShouldNot(HaveOccurred()) var b strings.Builder b.WriteString(p.Doc) b.WriteString("\n") for _, elem := range p.Consts { b.WriteString(elem.Doc) b.WriteString("\n") } for _, elem := range p.Types { b.WriteString(elem.Doc) b.WriteString("\n") } for _, elem := range p.Vars { b.WriteString(elem.Doc) b.WriteString("\n") } for _, elem := range p.Funcs { b.WriteString(elem.Doc) b.WriteString("\n") } doc := b.String() urlRegexp := regexp.MustCompile(`https*[\w:/#\-\.]*`) links := urlRegexp.FindAllString(doc, -1) failed := false for _, link := range links { if !anchors.IsResolvable("", link) { failed = true GinkgoWriter.Printf("Godoc: '%s'\n", link) } } if failed { Fail("Identified invalid links") } }) }) golang-github-onsi-ginkgo-v2-2.22.0/docs/images/000077500000000000000000000000001472321612100212675ustar00rootroot00000000000000golang-github-onsi-ginkgo-v2-2.22.0/docs/images/ginkgo.png000066400000000000000000005142371472321612100232670ustar00rootroot00000000000000PNG  IHDR>zsRGBeXIfMM*V^(1fizHHPixelmator Pro 2.22021:11:10 13:16:27="f pHYs  iTXtXML:com.adobe.xmp 1419 438 720000/10000 720000/10000 1 2 Pixelmator Pro 2.2 2021-11-10T13:16:27-07:00 2021-11-11T12:34:31-07:00 6 @IDATx %gy[UguzfzFhE##]cDq!:@02# ~o@l Khfzfzz;}=F#!.,YOS]U~Ou}?o$NV X+`V X+`V X+`V X+`V X+`V X+`V X+`V X+`V X+`V X+`V X+`V X+`V X+`V X+`V X+`V X+`V X+`V X+`V X+`V X+`V X+`V X+`V X+`V X+`V X+`V X+`V X+`V X+`V X+`V X+`V X+`V X+`V X+`V X+`V X+`V X+`V X+`V X+`V X+`V X+`V X+`V X+`V X+`V X+`V X+`V X+`V X+`V X+`V X+`V X+`V X+`V X+`V X+`V X+`V X+`V X+`V X+`V X+`V X+`V X+`V X+`V X+`V X+`V X+`V X+`V X+`V X+`V X+`V X+`V X+`V X+`V X+`V X+`V X+`V X+`V X+`V X+` _%V X+`V X+`VS s-;gG;u) q qV X+`V X+`/~~c+kKFs MQdfI1_֫@v:LsV X+`V X+`(M%eY֟]xš}3Wu{GndܝlIQy=IFN95W&_}͍/W&jW\;eg)slV X+`V X+`,~ƟϾ :緖LdP jeY&u~'[;~(?m -_|jZz'Կoͷ}ڝ$7ڦ8q- `0kiЏZO|i7'VUp6 X+`V X+`?q׾W3~ͦ6ÿߚla|h=(V:ڬN yR}=/Z,w~/H_Sn{|rd=Ɂ*olh8S?@V[y͆Ε]eƾG'c+p+`X|_!gV X+`V X]k5_xԞz8Hp_w?/cKz*CMu^N ؎6 Ȓ{rrӐpRW^I[I7ѝnzM?yޭ_=T>:6$G_ya3pxv bh1~5i?YH/g=erU}e%ߜY\W_! WvY+`V X+`VP/7e럞;.ޱcSikrǫ^7o@ۜ@u֦ 5#LmOpw(KO:U|x wT<Գ^'$jUziMQSw2&aj̶JU-V\H$9%IP :e6Ż7Iٻ\ V߶ iy k` V X+`V X+p)yb7ՃG%yYN5:oٷm ^z:zޯ.3n믺3[?u7Kyxs ޶fE;%/ޡqX!+L]S3΄Psв`1/z ?8ELk.zUԥ>KVxU&0$;V9= wE{]1PcB\0 &o _X$x HRx¼K\߀*xAǛZ˳ɣ=ux9h@|^T7RC"of,t9`~x c 40+y'[i,M>m9c&=իD?0o0{8V`4ce NҚnۘL>C]'+` Ӌ ~/^ w#Z$g/-Ag#MwNF=4xRss:w..-Z-[ťkZy?V X+`8xšߞ lQK]Ioz*%ͤ!0T'B] fvc=[?zi󨃿t@,m] lXA1˞ X^6, W;N @TpI@W%' >y;t>*sCxAhɫsN6!9Wŀ͏4#m8&sGE%hH}y"׷t{J߾f\?Ct^ߕ,^XL]gٵ(<\Kv7f|m3oӼen?kSڰ[ ZemSe7U6VK zĚYw6>>B,&ygV X+`#w5YKyyyYk+'O? ߒj#85ŞfMUpxt?I⧆/ٕq[~Y ʋ0 #3Ѿ[ /\)Yf<).nK]wenncS^yLzObۥÔl2> x*`[  8pLiOyPY@k=[uؕs-~Fc 9m?UH tحvE:={zK>_> < Tɇ+wa8-x|xWqK lԲZ7peӍv:ndEa试^LW\Qf~hL^hEm*7̋Zono/p'+`V X+` ^rWlmcK?}0MB^MzV꧝?ˣ]%f$ꖗ]qUbg_ A&pɋj%pR#uJǴ/ٽܢxỮwM^%3 R' ʓF@_^G,(ցx@VpJhl%ZZY=-y"WPZ2keVCmZ E_( ;A|_|>"ƿǑV X+`V < \ˮ$޸VZ3tA lj{^]{ۦ}v]M|m' ?6D;%-ޮH)|%ǀiM<xw/kF7s-Yk̀7'P@c[N4`. ;+Y3jE  7כz1Y/RoT$.jNK7^4+ bճZ*2|߿"4kj#5n/kEU \|dy3vFLVϏnh|'4GcdV X+`ìz|_;VKI>x+`- #gܨ[$|2|uWc_-g⦸ E@1~@*ƫ7'īr B[i_Q)G)ˌo6ް`HpM} CDR8l.ҒOfFd-< Og,_o|"V X+`xxm|5ewm$drι`-uF C:ɺ"}d6Lno+[ΡZs=ږ/˳#Bj[`0@P+9 U/Hȴ̝c/lP^1^Mbۤ|5 h='|TІ-fRQ;.%$0o]ڷUw2U6٠*xy˼9_ԘhE}5/c<iόIV93g^d0Ÿn3ျI@xW^${Y@j X+`V XHw ]byuL,}X-:DZVpYDAarpSy"7'M ԣP,ǀ0s~Gp p$D/mjţh06#5c@.J`0c@\]G[3ၢ /5*/2+H֘8d%QbIī?$ɬʰ)ex2G4Q|1L-*{ҳr.M@cGx1OV!F3g³0 zge+yx ј1\W%.#:Vzscb݇U2'?/g'+p Sd8P|'\Lk&ZF*ĺ`p2lj1%VV$ʲоUyKf8PY}eWvi,SIQLbg`ja7KrN>T ݋o(ۖu3wҼyB8b{z̵iy/I1HӁl% 6=%/\!4R00>?랼V X+`ä0;? Ez&O!Gl`$@awsGxPuKfN R"EB+hl'yD4yL8 ,/VFxy{0Js=' <`ըb:}Uo0ݙbw~9YM+{;mԛz!wN}(aygyK뚶Hlk}NeyYv]uR.ݡWեhʱ9Z#fQ-vҠVmƲQ!UqнS_˽$Oh'YqȲ"(gZ*(p4::nbiOa,c+`V X3T>ru,7@>$@\(%qK5\t<$ t%1IV4%/>-@O1yL/x6୫q<SPk]Gzt펇P2ԏ~W]L%{:v1vІA?Gq}3//n9/9L N'-|yϛnd'+P)O|&vߣsҟ"'wh;pvstpH ¡vթrh K4kF騣nS5NR_[h-|4?X,XCDݸńm_%2oF>LkVOY;YV.mxRQapIGsl⭫&],Q!/LbvzT4o1;Y+`V XNl~nΏ>;h=@-0'ږn DUt@M-kZx%'^-2` _IU /9PHSP_'8%ysƞ+cvD}P T/x;l&XL^}.l=gCGg;Vz*V[7s l-uPxئX1ҌAleD [C3y/.lT# ]1 }|@.ctxde]ڄ6tas@c yIʊ9@@=\կ^p&aYDp[צš8Z쩮= ,D]`}OhIOx Q`CQ8MdT".fto:o]#9禥nXR#1ŀX9 y]T,UW7jy1&BK Oe)>UЏ%F5{PA!/i6IG<3_Za'&6ϓ_ 㝎&=4NjS|1w`/橷<3[}jNqݓRis k&ij9 L@4Q艥Kx3\:~bOm4*P7ӎnӺN˩wV7Y}?\M|Eۭ\xsIK`-Z5u(@2p(O+VFTrJ0cp(_ǩ@޲f,,WhR[zw1t&4|Z4`,bK9,WKB^4>cwV X+`8mx_YxYt.Go^~kؚ&;?Mn`wWX eۈ w,>ޭHk4GCtWE1x=uGϚZ[d+sig:S>鿚/7kSz %™ r:j{&FP[}Hh;tP v^^~U--]%2:/B'0׋u+%٬sB츔P|K0`+8n48qx 7PUvڏ"e`e(7{5S6C|ӋAcc #$c飛Kehm'Vg hԺ!ZQke??ZM狋W]{'ŒA͜V X+`Oyӿp?3j:  w/۸s/~3֚<{-?uc#a?8^eZy(юow.v-@CfT7W> w)+1>xၺ7 l=؉W3@EziK cxҿnQ /Vok?"/ }:LZz"{Xljkȶ-92 3vvYC;1+wTm*#Nx ӎzjn^j0-"MAqLOjG0/=iY\&\EGx/-׏ A 4:0tGtVP<$>~U~Le<ފ{=_eBq&Z7@@e4m[rtP:?(&3jΧdVbmRVmN ?.m+ڜNM{ .\Ȏ|qPgòQ4hV_&ΦXQ9,mm/ekP!HX7d`+ڧĺY ގt,*T<-@RQF\cz`1Ɖ M{1oWVYuâE+֫jiVhÍh۷5 o^x/:V[+`V XV9Ko7V{?o7d{V{v͏\\\曽ޑxn ^ɟ!+pހyRPX% )0pѩ\9 أ  < `J u˘og~ޫЂ1Etxc;ZRNb$> (v2~s'7wm.N$X8 |{i}rz[߮ɻhӝQ8(& ?HM/{%IЛͼ k^bj2ŵ~tUL `0`as,2"CZ3cü.VqB@maY5Dp8&U0es3t>.NUqCyS#c*EFLA"$C.ٖ_tM\{Ig`,ExP U k>rrV XS]뗞G[l툳g+ޘ4ռ &Oݵznl87ĢkwIwSPT060Ix.;=*OOπ5>Px&`_%BLbR@fĚU{ V7+x}@X"f &}׊l/%L0کZ>@6}?2WYN@*G%cUK\<5tX@^:u /i;[m[}Ld^̑ЀVF;L__OgdK[ 1\Rf>@p0 p><'Kt {iU} 91/.@8WKɌF̅=yo {/Br}rU5t:;3bd7c2cx=ӌ^l;[>"WNg|bN>.I/l=;c-]&yd;iSmt++˯$]ZJE)@uYywI#֝5<8W R&//K C]$kq ˕}Zue,(O+O bϚ$xδ جŏVFR_w@]\di=yk^̰e*ri=)ы1&қV(2=?٫^) OdCn~Zg/$WK4zz#QA#Ed(ǍWRnͤǁH qSM qU6d!P f@z1 I}~]l5nY'0MT%l` B"D p| rO&CNjj+G8&TlLu5']+^Y '<3Bu=Лhuv(m)#۔usxfnxChc|5Zf0U0l~) .xyk٭W:j];_so?WUN$8$ >+ޠڜNNvʬ3ھvhs:*L&]@qKk-Zc^/f-B!(U\h4Y|YALx7L-J2Z<"(VsTleq2/:H"MdaS/@od<Zw^^ܨF A|mÙYgS0>;\T8ܘ#se M*4/N+jV X+`TdJ-E´֟gN2ruDN˴y;Qޡ7@ TxM1Q2t+p$2D$Ӛ IldSyv 7WS ȫ:@J!uو} t9P:`}+uL_$QӀdx2 xe}?`5.&B61?z30\{|c712?Bp}{ -ʫ/'pH3|:_z0V@WV@_ %VM~_S9Z |5mhMmjs:Yн0n{A ?oe۶7nnhRa'>켴̹{ ꖩ­X-R"fpXpV8+/:&ι_ҟ}iڄuRuIO(=yuL6-z;.e.̉1Gʜ; %*4S'+`V X+p:(pkقk݈*VqX!´֒4'!KV* ԀUӋBxyR~"eR@IDATO{`utBT(K[Aͮ#@2l!D{H1iK$ ̜(QkO6^oxقw޷ZCcR19$cc6c06(_u}dU sR\3sG@wZ+5V X+`8%n7P%z@uq1y20L";yXլ h@M (< \}JK6U>FWg ‹Y|@apLY`K.' tU-X$ j􉭼_vω8x N瀴0t@MBUPzI^y vU;LLVH9@e1a__4Ջ-tPjӀ~Hz$&sƄ,gg_sҘhP)αz 1 {1v+caZ0Y{7=>R>%A[V{R-%dUEguZXza8fu.o\EcKxЋh:<@1uGׂVH 32<줵'ԝ*pL+sV U.>ne9>V X+`O70}|Ay[ 8]U OZOhrp5iGp`!Z$l]mD3ƜWs_l@ g=`ȘCO=NY_&PD6^|6LomD/<jT% YU95[zFzU'pZ^G}l+0̟qaV0 r}(9y0e !͹N;^i]i̘̟umс9Eh6@ZF؛1(3^/?v 1^6veʸ`nhmxSStY骀>QN%ymmd˥ڈSMjKn)wMxnlܒEdZT;^&X ANscG SxG,7o+X՘1.yAB ӹܚ[>ʜ; hSpV X+`50 ŶX_/kwxg 0 pLcl ]i1;A4KRr7U_qC@D;;>6FK@ _Nv6 JMeCC`g74P#$Ä*vCic@vIU-lR6as&)1<͋=V]fulFx o06:yxْ-0O\+H2/; W2sŖ5l\ s|!6(]|F\g c/uó]؆v5Gpt1y; 0LOuwȌa q5QN9= EzhW8Q~[mk{>~ -,gO/AM^ţڠh÷WtnEjӯGǭ_Աb?ٴYR0H} HvR(`V+x8K?M h=@Y$0wKWmwI] vܣ>%E`;~qx_u郍@J}a.NRr̘T^GB7e@]h|*!jNSc҆oSmh d|b+KBWԢim=S>/oam;=Jw6ݻ_l׿tw8e[jno C!xm|9gOxt`W~ 1DVc?6F* 8f,>?Ǘ zv\/kF7NN?+ mi{CП8\7ksLkwCPҶ=bOLP@_`kN#nx B9n- U&|7s"YDP:aU:l@q( qc9/55lG1w5n͉(x( uV X+`Տ`c&cxJm"_ 8HԀyj,eyԌ|Ru=fS;vH63c*0>)GH;αg=큕ey28C1A3iBx`@28]s5&8!|)H 'ê(al \Ѧt&@bw$r.w8жϦc|60bYSO@lGi}17C_K+h\l絀=vцXNu]S^fwPoV\Bxu@GU/B\ȖiDeua3|a|a}AcVNNRߕhهY{+G鷲ӃP{kMAg#%°e2#vny9s LUk1nRg.FMȇx9rG59 H\R tB\RoT~(eLxS7[`Cվ9u㓘z0V,i6h:ڡ!#L҉UV X+`VTOw'&L .ݒC4 s& RYVpǂ<:-nUFEOک~oL@vڐx҆&ϼH6=82W Л* mTo1uNcdž~g9 KS!*K_QFIK6lAm96@:f$x:G;ʀ|臍S^u$?5.S#!׏k89N Mî֬LW?!N|Q/j{6_dr~R=~LۅygDD/<IsiH!Dt07cվ8f8ձJ CNwܑV %,SYuT!51LR|?:VRD ;9+^T_}FV)^3">K/ASE @  X+`V # 0ݑd/da1e.&q.xYﴒ@=*J ?@ e=@&0 oa>P3|U@Lh6v> &< L ֩zpH9uzN<Ϋ>m@MP_@\3PL[P#xRN(ء>gd|!4xCycicO#9'=9W[y: %^9=tr~e2i@*7tT>(}њO9k-gubNzB0@9|6<|qC26yXh1{.)&0^1)9;;$4戎#'!SВ6h.u'#}&{U4W@~]]ڹx0 (@xo3;:Hn:dYC/piŐY=OUYB`mYpNPnvÁ49_L:hyLU[<. ,^ԡD_3yC[H*a3i ԈI6Gi\>gJ<]2t\~3~懾x^=޸h 8r00l!EАBO/ ޺ᑬ#Uza sM p65岡-a>!`Nte\Fp@7DbbQS}:cTs! 0"p eS0ح nē3>zoT91s&R1:(i oҁi>NCǫΧ=~u+p(*uQm?v}rLÍYk5NVV(q\(8+{j(]4<0激rse)6BPZr8:(gr፬E  x҆ UЍzK *= Ζvx9Y+`V XTo~]ZW?i/)v&M`ch Vf,דd-`kNd"@P=MPqsc3$1 PXyV@3T3Hp/0,:g\DUC_@"D*u9ux|SmO`D Do4N4PHʻXJ0O.Vs뗾oNuM>w|YiJ,i|m>Z`(19gѯGoj}zYIe|ј\1ؘ3Ԥ>|4vG\kݚۮ1sd򔟗>3+1~s2`POeD0cp W觕Vw4W@oKT{ < %lgTۻ9}z7-+(t2O{woYZ~}j[E ĀQ"AcTq`T@"Qi5AA4{5ÍTsLu*Vz}}YksDZ|Oz[WpMu\]8#uoF \E*iDH\^ő<)캟ǻm y&¾xÖCc5QxwVӛغ*}NM7 {LZL6lX t?: t,Y@g: <,Ϝy}݁c[//}Y3X)е{zOs9 6f H݇<=݃-7 (f< À9mxww'M ޹F^Aʔr Sb0gkȧ'/lI= ٘o;8\B ykꁇ8ȔIG\@Qñ#xL_$\t4/[^W{n6e{F=H{a^WʖsC[.d,û(9KJQs!JZNGGѝNd3Y!!jͤ}h'W۵a'Ul-x#OLhvX{57`xV|̥Co;YFºn2bkD\WC2oCG?`ZG7M{Gfw|NT}o~ +Kt4?{?FyYSaCTeOV76Ov{J7 |::esks?ܱ~Q QNtsO? ߱7 7~( *N$lOE]NgOr5h^:}\qc.^o9ٛu\<b5{0(x W]`~ !0#7J0UWg`n~s/y,e N]ek( ֽ\5Ƈ^iӌ RDPvWn rѦejݾ@g: t,Ya|7ϼMj̏玶?= Vz LlKLbȶmo侘fee5sYhZ޴Gxqq)}zN] lv빔r-$ E)KgFP`|r}YH=Pd_w&@>R#v#)(_۲yɠP ))BS/b7j@ ,vlC.}:g= f{2m3y`'(P)-|$J*x9WXцc#(F՚S57K@;yhc C0׏v^PZj׎9Wϋ b"l^L?BD4C k8kJ^`֙3vzh"Ymmg߭i9}7q/ݜʙ/;kSf%nMW鉿>8Y7>؞zoߧJu:X% Ů[ʺ& @*37w>_sͯ4^8WAd7\S8*ȏo9ύJv#w \o{յgoB̩>9V~+0ꏏ^ڗl]\V/Z 'g2DHA"W׭a+ٮ|I꿞],Y@g: t,X'Ͻ|K/,|a<~'/tS{oF Ln4[ 3wcBi>O^5痛cy1&H *kjI`Z ZAM5f`м*E%+`ٟ{SB8[POcW0nEL6QΞL"&cxl|gAXrj_>) det.R|Y?Dz}C\%7-p&Bor]ٹ?셌$RCem_U[?ĥ;SJGjW m"{67NB\A@ %iڤH ʎ"TXp#dґ㞭>OaxyHhj'rҥ: t,Y@gKsíLJMހR+f)X"u(7 ]MӖbWV,c5xx^ l㹪Vyɓx-i?0⯝ʉKEK W?t }Iq߮{\ `|^2@[0eBR'[̗ͤBg,lYl"߶zRvpNk6 ~J{Ӈļv\R?ǀjvu;c9*=؃9DŽ$xR@uhxvΫ@xxۧl3t.opD#K~dzj*BxLG/65WWNfkb.B}S͜n}yJч2ModN7ݭq9:6z~{nt?g'O g=ܿևqO&.LXi^_ǘ]XȊR,3Ug8!ۓq?~stTa֍Dxlkn'߬9.|3DE ٺ梌&"?^AOXǬTQ*]zGL?7a»Xw6hT)7s"ic=3d9Rg: t,Y@gqsUX:68ܳ9wyׁ=CiY)/ y,/Rm +ZRFOKZXm֎NY_A ⁞4=W+)?O]_@{\FՂ^'Vg3EN>ϱq&1p-|I57nVi| 8 Iq݁Rlί͹z#@1FP,whb7^VRGH ZSÛ%$sΰW7Oyi|)/|m>z͝QcYŋ2x3f_ LGڙrœ^L7Sg3xK~n/?ܻ{|bcq4ʺӋc^V p9X3?ew9 \6'F 31M41rΏ*9.n \g HO«v=_sqm)x\䊍z(IׯSP@9uSFұ9,i^Y 9n|XE)٦sgk+yl],Y@g: t,Xnί ^;kw`o#]3͝G ^Zh;C@70P4m ?`_ulqI}MʴT U,^OpS:W adsZLm F1%ʴkmɣ1 yU^kOO_ZW1xAZ<;+O .P^OЧ_%ߘkvڗos x}D՗Ɏl|46Kjd,6`s۱ߎA==6Gt!G{@*YFZH]m1]&G< JT C.s^TڰiڥԣOygOwkXd=ٳ[/lSzᓷ|+Sk_3l[um _ ͝yݗ)0rW ӆ4{@Ss;GtڊX ':[Cᑹ ޻7|oPK1n;P|/lx(۵ٸppKwI aGᵹԛ$o*`o'Rk]E_pNhvYoօWJ O!DNոZ{euPr79\ŖW1UlFMlu7ly@g: t,Y}hw="\g*lpIM-^:3^tPr9=1ƣSV8>zh 6E0hs^.F?Ϲj.& j Yt5/d@^ pyBb(N~4&lzq䩣/rX1`JΣ^6ʁ'r2>mKdhC6)%n[0).IсlN1iÂ%$M?c %V1jKeչ#kXd'`}.k +: r9㸶`ٔ=͞x{yMVjxfITlWbۤ5c] юOIĴ/ɮ-ě~~᳛9x6W^kb+24sw- z㻥^;ZTYܟJĆvʗv0k~+` ``oc6I(k%l8s-$j@,@Z0@IDATE@sy T } 8 6] S/F~y7';vCX %z/]l />`!zxs}ALzHU?uX̏ kyƦ|so}*3^ڻ=ۑJ*r3AXԩN'໾ qկ :BQ%9^xTT<fKո /u։ٝM@-uE|ap[ӺQ\~oah5" =+4䒣 s[S_qo̚L4<0w~~;_z+/}Fguc E9/Dӿ3Qlɾ|F"cQ~\؈}^y /'Se¾dj1}\V?lf)+7+-e{6nWnh_-ܴsɽԾQhbb4Ͷ q:5q%)rmݛX-J(56h7OH62r= [|[iS HBKc 6kZ^UasiO d3S %}0:lQ6mFiq: t,Y@g: ܟ[nwrsW g_ "~(`"9%*h WLNcځ:.Os_ 0XmN-EV ̦ n%E7!Ш_6^ݲ)/p>qt3fWi/l)F./OKPX͟ j $K_Urt}M ֶq+om9PJoڎ%W|]_^yT{䱦'<j>.M}f)5bq1>؄`c-%V%3Y5_S)6Ol]9{A?%[?b[2X9]/c _yO7uikևl@5މ0Q;6> ҋ݁ba-]ӧ6֗:n}k%v^3c۷͍'/uvpyJBhN^\i=(l J%{~5g]ngOuI;uAqxǛfi>^A6>K /:SrEXJY]ͯd_QwR- ^͆xMMnz\Ť8|.(oF rwϽ?#ۭN겑BG| 6un^ ~xdA^/9 l6c[+LON^V/H'۲!BRZLCi}/+90lhy$҃Yrė SaVz8/V"|}voʭ[59V _q{ɻ??l_wpm+k&>w֋ϝ9`w9|39q&rE%X}FڲnϥuE6U'6_[ݵw⧌~ƂWL2_Ẅvh')ەY6kFs޸0ZZgpb?n\=̈́뒋yT.X@|d :0/E%62Dn Yr\02k tŲEosX_^ʃ6u eoz$.SG/7䃺t(u҆}v^LVu}X(cccKv+>mƣ?sNۗ)19ɲZrsN:{6lyJkon'r yjk.JlLYk `dombdwNZ![_݂:NT94Z.I=ޗ7_?<=N/>\]OEs+ 1(>a}'i= 7$KƾNʱeWk܀"l:*yիeʬ+SBRf-?sG?#ֹ"q<۷_Y#- 9̺Ξ~c?X3JDD>ޭDi.4[íɹXM 8u vtnVTI, q =JNŜj9t_ 2;ʁ).k DvPA"Y;ꎏB]{*m ]uc:~t,Y@g: t,0<97\:$ ,@[T˟6YyJڀG"C%@,,,TmHz X.p%j-])x; u۸;/ǥO #}\\5eiK_ұ1Ӎ. Vˮ%3yWcC MRl["yǎ}ƴ؍ךf߸\QvE潫 ^Q>i717e\]3shRBQ̌&&vo0J{~X2.@{ us\u3ߤnrC.[Ps 'Cg@~y)`\-k.ݥ^JTp+Sa'SKg4M&M(b3cò%eY@g: t,Y~7ɿnM\n7Lx'>@,sp@1xWg2 x;=h I@LS)S 8x!25E^)8}W.-5f}ܷy|j_hOVa 979G/gOuM6jteKv(Lv56_ A٭d]:5f:Qɧ ;>B$OlZv;Mo6/l放@{dx_)A/KI_顃qނ[7<=2y+kK /o?MjKy5ѩlsc` ElH>[ ҭNdً/CW]ɸo}ɡwWW"!6z{Zʥnӿc;V֢؍gWw[3vDQ u[[:y$[]vBZk^S]O̔^Q)+O?>e7wo%_ïJo8>S鵽 +yFsbrX^Ź:r0zrf1.˛8uTUv?h tJv"t.< 1H~O%\7+u|cs}.{n'/y]c&lFlfldž֦yݾ@g: t,Y'euW6҅g^K$8'm$8("~Ns:]Yx*X62.{WoaB!/6 HJu=ՎC> Ph+z@#9`qw U`Z\]lD|_zDvd 4Ք ʥXQצmt5;=ȰQF)"kƫ@m.؊ظO'tWLȡEYv~2&ٚy zʁ7e}C˸ɥ@~ʕbyѡј'}:Xl]LotW>g Fgwq[x|.YYΨ=No޶qp?^ce#,;M!+dPM\XϒLȚJY=ɹ"J}~aLonyϒ2n3}iZglrҎ/1?-#vt: { 47Z%w+pLm &üʭpsx3ן9wUSoXeG\@v1`;~E3z;{9uzMS(]ƕfeu䢙 $nJm: 3AaznZ򆮺jGo>ڐ;}1; ՝GnSHX@;lz僲l$O_>qkcjg|<}AN?d[,OKo|@&YiL^@g]r.S'%lG{}#YH}s v bb}# یژ\;6뱯61pmݜѓ.`n^e[bW3|uoG~qߛX=hXH`>20EkƸ;ٹ9Y>-lɗf䳾;b3ךל9>g9{kX#7b3Z㍽b}{pg?o, ]醌U.1^ ~@~67go+ %֮M.[<ťX~`q`i_OȈh[w. q]~Gs:9ݜo cX W1SVV{Rprw v)b`$CKt/}#8 m<]ٔkys9cg`a#bz]86Rg: t,Y}mFg૮ $5ǎi70L^1 ҺI7|`rf{u)Z z'#k-++ j.`,:XCA4=pD7 G_i-Ob cg'pOFN 7rmN͍v٫A2nm<DYy󚔤 yzuͿq|_=[ ,'WT{@ {Obyoed'Koȷs>6qgA 1xY 7Gl|;z#u>ٵ^{jxɋk# W4ﵺ"R|Y\]ɖ#],Y,99ŏʻN[=_n\}zwױqxm8 K%HHǿ?^70os\M~`]Mv mF`onsm红N#=2s෰lv\,]\3MPrbG'T q闛Ugzc.-d:،pY:Rg: t,Y@gO__y¹%w~O^?V?w9ބkq/84J\$e@*uAH2 显-X U<@0M`<^ixdo˹rz4l). }⑋9ٍduO0s.7NnTCI)iW2Z݈vooc'ki/[@B䫯}S఼[h=R곛y4O3gdz0u$رlMXtп6^AȣK}GYb>T[S՟Y_}x3,j+ecԬ-3ǣh: b[R^g[k=rs񠅑WN'0uzv YO\߸Llod\'g )Sw1g8Ķ޾c}K}4R,t٥|e7n@; |{D}}'A*i'Ů#aP3 6{ɍx' C\oY0O@ E.uMʇ!IzНQyoZWg7)JT$RzEk>/eqK7(E}]9\pDw1ȝ 71;5qAcW@g: t,Y'gbߙWa}usmo[ME' V B,` hܕ,>t>W&s HOc l R >E-v#XX?QV pLDuO~ʥEO%-(m}YHdo`vTըTcN?<}; Yꁉ_8raC_~rdo,?l j,i@&ݔ3pxKd է}jn=d}ҍtL]Uz$toc•.@%G{q%˞aygMd켪$H_Œ朧p)m|ca\e~ix#"/J{ s̴QW;;fS}۱~=&7_K?~ɹGaiwZ &|қ|k|-&X^~33X W5پ ܒe~:p [{݃APL63\[lnv6`.#WBGh"8qaZד ;ѫ6;8wb|U=PUȉJ<ՀqKGu|] μj>kP>9-6bcfs1;<5:w~.u,Y@g: t,]'W =A+cB][if T6|ҁ:W# ҹ^ 04J=^r \uv-03T@Y#0`rBh{ P; IJgy&l"q[[!7+C1ګjs8$-0{AHґy_N#cF?8ՋO[SCu)<%RW!()c xW'H=e G d/_eڨ'<8~V/@ܫ]Lsf Z7@6J/;9yGu,0(KG0Y;h;yyN@.d]jd[UHtoRkNyFs5;H}0"˚o|wΧkqM܎j||zW>X/>}ل` 6`hАϴ8-ewNd.3%M߃9g4DFt_,M)g;{`ǨK%1Yܾ|U:Ae0qndO'llCsx]-پ>0za)yFjo{kr7 (Y?ů}o?nv]_ֺ87,C+Y^^DEeXY)p}A M|ʊPei0&G ry;Mp\x Wȉ>NYDbu'fNݶ~^8+qa(ʫ7Nڻ=`<3wn~5Rg: t,Y@g{oW]5_MlW9rt~:t8o;\t"r}p|=O6nցʥ6@\8 7o%hU Y#@liNem<*9Z:7x ٬wX,ƒƣx:e8[VzƘtpOsk-Dld,֦3ҜPi-2;3Ft7S~&umXO`p(җ8æf+r={Iq wf}[}LnzImn%ٞo.lz/!3s6{{זy=`?ћӽc_9}yW`cs+Dr(ŕ11E+DEyWuz,!1r0uGXܫX&|E卭e]ȍp4\:63AV+6Ggv7.u,Y@g: t6˙og͝}L5 /`[@gn|xYw3{w7'&D5Pio`_{I q X=By u,ޏ@#`l=KAfޱ0kLr ''}ɢgP = ˫8ץ{gWvIϞsHځi!7``Y跾פO1Y@^@}0N_J^;M ;8cg"Wd֗RrJ/ d嘝\*z`qM/x? ̹Y_lx2޳{}ĸfskm%`:H?CT<ؠ\.YryhgߣyF☬/57y?z^H]>9un ;vɌk*js.COw図])V%?5CƦ_lIɯĿIi>/svl]-Ki]rqɷU=rG3Zn(r]]p͹bw gsdqyޝ(j8*DAԯ `זrw՟ɳ/{ >S9*Ε/E\'eg=U}1c4Vc6v6[ ۈUVwy؆K: t,Y@gӛͻ_9㖵q %wcMncs `ڿ7_&!6)#J %Y\@FN`'xɛP&Lvz<:@4h2I$kPZ X.C <=iʜ45ϴխ !@J^׀_$K䃜; с P1N BWV< S`;=-R_ڐ_lfsl/w@~ƪ؁,Y6g\8S/a`֮c B~WJgh#UNWv_*gtL[=AOsW<祛"$igSD@@ԜݭŴegmA@[;Xmy6o~sgn6]o~g֮o-h4;[;وΰ;`/Թ#wvn>KDg*{V{@L#g9Ek!ui]Wr.LۈsLv=d~֡p-G?w ZZ_kNm_/~_:9m/ 1 >N?ٮl|kʧe7wY%R;׉(O?pnv3nanmm7ǿqrt/1Xvlswts0`c $(YKF3Ȝq W\ꍶrn;e;rw;к4v6w{[ یӵRg: t,Y@g{cmkia#77 |Yl*Q@@0 ؃d|sMLcüy[^lV*_&5<AkX;`>]q 2 %{H .?rSp[~;eڗZs prvȑȥ#8U@.aK,s~-5>Qqĥg0J?eGg: t,Y=xrfp/!d.Ԓ },Ȧr {I}?<;[`` hpARޟY/@g`<0X TA:E$ydʔ9]BМV;'iG;@t1[uLԗ{)g1έ &O9.ؔ_sjF򲫺{`襾y/ߴ7wt_9`#e+QN͡z:eŴodj/ǡ\Ӧ}X@?'7>22ߚ96י-<]=j, 4OgkN>苌0 b q66< 7 Bfo `9ٵFDϜW3Lm3߱wήO^g,9m>n_lcHgs~d]]xQFǯr) y]omNha^t7'ۄX5Bb/%pO~Į[p  emy 7y WH>v 4 6D/`9c P@b;'+Y}+X"c珁6 ؂M؆WKy;; t,Y@g>&{m\ |3_sz6'p7 WV^J Y 9`'+P ŴmW8>:3u V[`(C<1JWqk:A_..oܔ_EPe+&X)xB_vQ`b >@D//BP+ B/}D^ӌ(ЂٙqAߔNH a3yx#,B"^b' |WdyޑQ|4\ [<}} 6ɧrdXH~/H,꽇~,ĝ6;l[S|Oɻ K5D@s_f{TlWBPٌ֥m/M7}Kdǻ8w8pW%Vڽͭ.Fpbp8y~}/:UbP`nWH;Anvq1t'er>.(Ȭ oWH 5Pr>osO}k ˨1elhEmF6&jڥ: t,Y@gĞ[7լV7'P *''Rn|xͥ>b>`s A\y!0ώœ( Ez8O_'*0 Fs¦*( |`?Y3 T.WFt(P8Uʻ !<N#E.W}p^B^@@D/s!ӟc1/pO]'Yv ( ]V>Adp_b^cB@n-m%lQP6:U ߒq_UG;ϛd`E;x[L P6YG֦D'R[GlYb[${+ȝ<5っTn~ƲuK5ѮnMWY3+?9oH叛~~V~,M9[f{t=}57rc޺˷O~ Tbv4FDG\d\4"t<~`/L5yo3\ź1WckK>Յ'#{3lG_ 7&>cc9=7.,ӗ_h/5ץOMeU_ɸ_E;uo&ۣ-  /_3ڻwQlmdo::GM~[~կf$1O\` g_޼u!NdߩS^ʎKV=Lux G2bɜa "L>'t1111O ٢{ܻt,Y@gx }Kxy!(wzx0t|ǞawK`0k1g#c dʀ^0H#K xe:pJ؆͎OEޙ]" ,@2$+4m9U}c d@pvKDg3JF:E'*[Hhwж@s(3?dڐM%S dW<:%;2I5!9ZѳaALr@IDATIƂ2 ;؇h<:w {Ӄps/اB)+g(ȐJ {pGyFyXG mH6E:zfm1Vݴ_>Jgkă PU3#nb_o/Pc\kVv6v]oMvY׳OݵP}Kז}9fw9üq(R/c@H,jng;6'ZIW(K/߆x}uѯ:b)'DQnp7#gژhFkyj+K^vo@\~%R+8[cжs K8gbfbGȈ9W72֏̱r'6o (XHX?󕯢kAn bLfj~~ltyLw7: t,Y@gxm{<.x7 KRO` u'pV]z4 4lA3OO, 8):9W~MI"{ `m,dedE X*K:e\>KtF,j6$/][Y=Z/_}o!c l\j΀iZ\ؘ.l*Y_2G}*lHZdk/՜kL[䀭dM^i[ACױ/k3;2Z){`M McuG UcFJgԕwUŞAjOBCwq%O,n6>d_2g}Y1qWǏ_ve˙ m/Q}֯ wM?2B7X2G/s>x$VΆlw6^R;krH< >Rg>Wso̾a'PZ :yQ?3S|dLekBث)B|FoM|o{\*\Vɔ\J 쿸~uc滲}e=}_ݝ_벝Tq?߾;8(On 'f^xjjb5 'zu]?*︹7nEw^<߭<90#ٜ'RLa\ƑV9ugίZtHW:.kux`N: t,Y@gK=ɱm`U0h'`JgZk+?+h>b#\Q];Nu10Cګ˦'X呹+ K.%Kju|Cw,0uUW&*DqǸx#;5b`z0 Wyd lז qu$`($`x.g\ (/srdl6V۸l_Kqy&1ٵ&ծ;!Ng+Y؛xתɳ7U9سBV|Xk~͇ڱR5>fg=]k/HqdA Z@zff],Sv8}{3Ywflv+2;w#´0'cڛ[R3|y /7[TO&6tȺuŀ~&j~YCkN͓qZr 9:-ɽ|~$S{َ_* ?z1=)=^.ݽ3yEk=r'9vxy6!.tӋzyk|CokL5 pѬ]JAB; A.2۽h{kawkz?{wgYVsΌ+F (yJ,E * 2r"3#c+$I 5DDFɝ {{w]g$5_(z5ch/uܞRYWC«}>uahds77HU-mu凴HZ;- Elѹӷ];zs64@76ò/r 6h@gȧ 4lau+%D6:n"Y;|`I76r&oz"?:ǿ䜈EM[n==}s]"g nBrْHγDJ?9#Q>5gQJ X1{K6ߘ>?ҌzG6NBz ^RQzӁËʇOv*&K2C#8}~d~t FGҙl`#FA2N:t`s"Q dn9NY[nꀭy[Hր1P*";1s}aC_[ F?PӶB?ʓA7ѹ|?dٮ"U7 H䁝d놁ʇ ȩzl3i^#Cbsǟl֠&ӆs=Dk@sn:mqiHC8c;=5~gf7ܗG,| ։5֋y|0q׏уcDueMSrGvH[7ѻ"]'cX++p3}ek4|Tc*Ǭ7,0z '-7ѿ羉ͮHC3Cs~7,p~2`}on ǟ^zЦ>#{jvA?pi~>ze׍U: Uq9䉸6GI$]3g3>T/ /Ũ]\@&R@* 0,*Z1Ɨd"mpwْ}׿dh! }c6߰q Ѯhd`hGJwa__ѹּj`!]_ _m7;_Yna?g>z--OqEGojn"[mؕ.Ƣ#@Jw6(K)5M`9 :O>`7[A=>2u\smDZF7fr\ uvMF ̙Rs l^eH_㙟tI_"TmU+D'i-`*Y#U:ۃ<|=V#zW _E7әF\PwO3kG*Og;K39UD;<ּ}뉿WOvWu#myXN#kG7) Φ8~oq9Y&tvtuM׮5fιlL\(Mg dG^kJ;ry؞3*F޺O?3t'Mw04rl_nz;nC~Ңm{|Ewl L?;gg?[s|?zbz00<LGh%yzp7ן<2=(?[;X/(̻k:tm}e@`_gv1 kKgo0sI&i3F\q±žl\pnNlJ/غ1vlVF׃NevO[zE^ZѱwsY:"Kd?5Gi{ .>НnͦsXKX] n[c͋ ΦGtevkVz()+]%W3޷fw צk|G ϩy]a]zGߜ;qnXz|0#g`0<3$q`$״|ȏjzc.4:?TCCÁ#Ӡe-;U?/sC|^嫨'!yM%?E>}vb&xfl= @(Lmڈs㦅#U99f'0L7"KH =,4-xFAg;S5cW[sh֞[OS8cX?" TH㜴 %DNDQtٛs޵d ڵ [3֙4֚ "so^g/k\baQ L|ڹ.z&Mw뭍܄mc?8 ];iN-4yKyn?rܤ]m`H-X;lMS|O?t\hH+"n?7~M-VAɬbp T\Wxx%gVRԿtj(o;z`׮gw46zx|~l|ovɑss3C<TόND|9B1<8϶st0;2 Z9mV} La,ctHW:ӝ l"I pqq?nh?&E:jצ( T99H*:@}HqD'k#=J.H$Ҹh₹i!Z D:V}zNR)!VtpڂJsZi '΋ls|[#D[a6rt6w_[82x/CYOOy hWvHCe -TϩdSwjL>!xyp=RїnZĖMhJ W5  <'8NHe6{6m@涜_QOA}0Hŕq+'܏4/~ûH3?O1C?ōz} 5 j\zʎz4uei+ty^cl9c|W>'!c]?tb:eLt&ekݣ]A@F$r1v@$޲/ڒkҍfrs1iQyR^8y#(xٕX WK\Va?tCv#-zwȷvƩyuCvsӿT6|ro'8jCgy<9%8xTJ^*xOcUX{m/dI1eLcӁ.t3ml\粳@:tww"?+B6ϴ3?1_pxm *X#AW{uD9@V hmN' KC OD Ds"< FojA6 8ѭڤP'N'\a蠯_߽E.{f;Ky΀(ݜ;H \I~y6Kq@<[E2&/=_+HU#( `Y^7rrԒlo.1X4\dckb[|EWvR:վ㕝"{f)DVj#r|W44`c=8)'"Út`_LmEVKal˩}" ==+}nX ?Ӱ/voSͩu+RZ?DWלŮp(ͤ%m+ u>nXnIk!K:#Cʘ holX\~4ז̓ϳ7ٛCVD_xqQ˳5]C9g]oiz9<􎎽d߹#EuJޏɃ*=CF᧎x-^%ܺR[bNgH} aݠyQj4_kfkw,}nA|ϜʦD;968Mug?Nѹff>ۚ#CCS1<24Dý^dJ|6%$$D344II1po~>t?qCy\@~fzt~]nOhB7߻yco&.H5sk:J:tc4!W o*8S}Ͽ&'J犢Ms #m F>HgԶs`|-=W.`uDU6aݚ3E?`O:=ncp_(OOAm`n돷d v]Ԙ"ڃ`oL 5͚Slm|(nT#q(m͚Tsybi'g342yr}^ l-~ŭ|c߹~3r2 V7wG/mhq~c ۞M!72fAưحAɂ:,s8st"mBNz8]]?47pf3ȑZg8/}L:L6YKS29#7&sDi1|>7<4m^\]~+њEVbKSVpo_q_=&|㚰rKRpDo1ΤlE'Ƀ?=::ScS`|n\ohlol~rx0?9O N>3?בj;ǜF[}%,26iVt v}t|͗DƑ)TJ:tNl:oڀOxTfvl9Ny흫`[Y4tb\6_SѾ9g_;8liEn :fs7g&KEA;pԫlF16%#E.Mz"P j @'U p9 ˢbݙ5CW6 qD?ikvv+Rqx8"3~(~Rf\֑~ٗ8ϛM'M WȊ,z*x Ywj=e1LdZ)>b>fHo9)Zh<36ajߜxx>IH7=2>}azvT.yw~06? ٮc9KY- g c;årW4qtgbِ2i:t+~zjp%zT  &GDmbB9"GDk)i=#1 JoGFHjxh Mc&@?@MDьr؂dtvTbSE6{LkorқnX hFgX)LgY6il &tHh Rsx!2v JIX>-_\vkb lN"s)un} u6[ݵ'=řO]џ<ۻq73r.םN"󣽭cS &)̟-"/4| 7Ϗ7Fٍ3]cMF& 毸cfbf+Ͻ}ψ[SN'VU+:t4OK~/u?'3WY'֝ywkt1Gי]HdmmiY3F3e9E>u;Dw]5́?;5n^+]8Kdͭ3)6\ sNz`[o0,= /K|#tt7vB=}v#>G6w4czد"vFu!ڃ/\w랯ɵr~lp,jJ[q%+*+yqo.hO[stg54 פ+XAc7ݒGoћs7sӹ=z;57{'q=9=6ftb}161`8[ M=ÃfM;>0}"?{{7@f}G䂾J:tl֝SL7wQߑ!A6B֘ (VXQЕ6vɸ"[If(~_zGVٟ]iej-_.vdj^2$X5"jU}tyޜ9͟4%Ƥ6l% 5mI냬*p_`:s"&*n9,UmL7FrL;`S`MZ>֏nJ[6ڦYh]_P+󁬅+_)ɉ?Rb` Z5n ?m 7f'"nnŚZ%1"{[ |gՁ䠭kE~u'ŎLA)}'ƒGD2{Us 9[qhKqXUW]XMOG 49r*UN1GIrmIἺ>p=Dy߭ۘ)LΑH]]i% m5]^a[/M:f `08]okv5FzkE^Bv{+7 >OӦݞF#_ ͝t&̡ŧ``]7-㦌Pas Zsܼ:ZRD"l0_nh1>}47o݃|~(~v nIuDg~)Luk'u?ʉ}gl,7tDlKSu"2fA?͕~C/7ol:3+l6H'ɚ-X_~ݯԧ\~?jCz:^|P(rkw-u`ΰ{F@%l=7ޱ([m ^rb@{3˩<PtߜéfN&(0H:H#RRd!⁒R4 䜯?8V%(c PzH"82cv:'P4D@- {?S, \@EWʹc΃c :}BXDjM5d(l>_ N\}c +Z4:]d!9b#X 'j[c#Q"C+hi ̍#MW@vMYPѩDQ<Cė'06D')KzGt)0szlN7'9`\>lO{}62EJC21ݞ~w ѫی} E 9qCo_bQd pG)lI?9} мwsѥ?x%67j =RNb7T9sZ W;rp9?Mk)8YzNv+kz`yuPC]N+76昏ZdUb-eg:myiDǂiw*k<ɷ콣v2LƐX@X/7jTFC__ס?5ա DNZ!QП\c]htVThWFKg/NuIvgF?O;ƚ(OJ;hW䁅ԋa{ kV~D ݉+Vq\W̕gӣzzvxMIP灋j!.6G;N'tKѮ"}`-@|L;;|mi ^@, /$UH-` htA ( _k"yK1Ȁo~o-X :@o0^tg9aW݀em;ݟϹV׉@:=Qlf18YlO=٪mOվ~yslQϒG'X(³!ё:qEp젻}3.( ړ)2P~Ʊ.6F?7,2D;>?@èP \7pI^`0Y[ȼz3ۑ)]@_]^c`dʘFA6v@d~ߑfs01K?i:O98VOi~GÖp]~s4 |[TPȸ*:eGb-`\SIFڞ᫜ρk17 \rnD1[g,<v@kzDj&zl|d^Hr OF ^N`_NwEm~dME+cљL [vÅgOl}FUQS;W6Kȡ Ԭ\fxWԗ_fz?ZuMOJ}G+O|-Q縎od{k灕SSL9oUiy_ڒorM &ˇ_M9zWRy@yrצ>gP/8jPȡ^jgMW> 4#`Iʱ@#(^E0WJzfo>EG60-S6r@5@;`UV@(}`RN=v[K- *S'd3x$g2[[2%,̩j{E}Y<4nhH}A/) ]ЉPgS u@pγ\wzd-{/@5)@6ZoTl'`KEm bE/oMrgwExV_M;WeY6GR'堭| U`e}}_l7ٓ=I2Bt֕42,D4۹lN [/爩ݛ5F:^.xȘt%ʣc+#@jgߞ 4?Dz۾pm}>AQ~ FwQ" NA~|"ńrQ r9QRUwӭy)&Oe{(}Ofu>#J>7T̾7}oϩUSmD5YښO=&_FTHk<;FZ01ؘOŒϘtXϼޘm_mK.-v΃eϻ/wfgiW:t]P/Ǻ'_/Ulev) QÑ8?UTr9](E~AK֠Ri>k7fpYd2_G3w萜͎5=YUǣL/7[+7~9->\.M=>D/ 7"֕rG6ND/[Zs]O72͇,9UIzJc?_-ozc ~zºq'H-XǣR9}f f#糿A^|| {,Aw&["ך2GT[K9wx ˃>Zr?_1+FWmK?l7|#h^QJ(S+YN yIl[Բ@}%cԷ)Sm+:t<_YJ] _XRÍwA,-CCIq! m]Rd8`{3ՠ(G)P\s\$H?QD:b?V-BzicEiYp@-9eX0~*ZYQ` HL{ tEO $f6HUʾt8؇2=l:P z6h>iL>Iӂl-nG"l0m2e ɿH͜ZSџm!8s"vsθx˽g f{2P^ֱ>#?k:Xb݊(2Z,7S#RC ^2|H|I "5ni,bhO _@K<y}h~yoiznW@j2X@R HCmk=,2%v i=2S `@/:̋m3@ TKx"30c~2.Hv"J^"-c+[A60f*+zMzz*֘m(@y~DkfRr![5tZEQT;:_mGDr/2Q:2~,H ?ߚϚ/9g Xp1"n+'nv/|RȢ-ɵy=rGĕ R1x^5t>r+["znsdܜ1N~]@=(D\rEgБEJ>k>Xp>җɵ3/=욳XW` pe\ۮO6AdK;n- `٠WGv&ۨ_ᴓދ 9r9QW'&/܊(/^Z,27fwex+RmϙwŊ y?2W<ǻʃ=sS_ԑ˽^+[>z]xro>͉ƛ KMn;M;[6(8=<Ccf?ąSaƒtW PR\C ׏T 7yU@A"+A6PLi*zWD&d` b|ǢA=7nQ[`ü(2+[d(JV#@`Mpp,;/t^leЉnP9ncM 6T_[5x ~+-PElLQc@cWxqN9@H 7>Ms(PA=SrWnͫ~C|>s͇:>A#w"cg*OC՟<޺j{Ev ?rPl >A}n !e Ճ>㫿f]nH hE {1c^Rkkݜ2fQPT7'C![O\+u[7}9ʿ ARs~]*ѭ ?m:^㕖Qѕր) "wCDdxڂSiZ7 9knfd;CO5ɢ\ؔ,wI z=x +x͕/o\sVn]S)̲n*gcӷdQn_nkcݿmmO*^\CnxͧŏW۪Z={C gUO qYr}F^V;b详ޖP<7}e.EלXNV>?p*3 ^n%Vk<7%_j5O%64b AJ -sh(Cȷ4)`SQ+iW#pSl4ܸ'6ti_k9/BO!26-ZZv@zڙ3I zק6)N78g\kHnn4s ,^kUcW K߼֍斎57G.x yZRًN~`ˋ. ~8=k9MHYsg-H9zE> c{>׮Sr/j7Dx{͕?o\sV~NdVͳc+W>}TڶimK_^]Qgn۶+<:OέrMٴJ 2gKU|U%EJFZLoxߧv@灇z 5zCO#+~wMYэݓߒk?zUuW/?L}k-/5/_G KOH,c55_-.كQ\b.Nd z܇+zxs1._k?>Fr;r_.5\DKTk'w6jW>5.rm.̑㡐RNܝk6"+n=:9.g5|.|krm\Z:=Tg{G6j(\˭2|2 4jYG~5|2WK_))1m=n)>U>Sv5ʵOvy}o_ɥg;Jx:]Yf|V_O}vukRߓ;ڕ>ݛ?К{k,~~j\m*ԵZ(%k @P~=X2`(p3! 8#usΉaAW@@G|JOI/mg_T`#m1*jHE  *CV#cA E\SQzrQe;ӟnc3v bE:i-Rw^5١} +kl:sҤlFlWL&'P6>'=C5]ߑ5_|^ι6d&f ,P~U|YW_ٜp403|O"< VDs ƾ=Wӣ-eG"祿s ҬM; -@|ڱß&͚c3VኂΘM:Y#{ӭ3%Gt)ֽrSHN\0]'jM7w/ݤyI"ƭ%k7KpyޝY'؛6^{d'rglu+:,b~s9xqϤt;xJJ +BWGZ>;ߴ,࢙\QޕC䝷Vbc}k>M _X['_Asp՗lWׅ恉 Lf`+Rߕ/HJM?NxwLn-cҕx_H]A ?)Tz A (zt3;}PRRò9iË[n !%0"#XX:0(zԶTEL ͶB` tMׂ" Tju(jAQrs8}{7$6`TS 芎4m2EE~5"qQl&C$N7-+8qȔ"&HUPM@(ռL:Еh>lP[:8nsiG ݸC3NܦY3Gt4Ms)RUATAu_Trr @f{̺Nic XӖ@zkfO~! <׼Ofjy%#m.so֊b)K/|? kڼ"]:Ծ|zhu|]ַ1"te|׫ɞRxpv?ݗ#iįn<>/SY~f`9?p|΅\[sepevUczq\y]YxAoK^=~[[Rڕko]/Oy&x?>?w<!}COwG> C(L;Pu_H^|~ HP^OsC;rDPJŶ=)7s K4kr5f[S,2BG 0EG,@eO3>*@}H=p >ƮFX}hN}W Ǹt;Q #>.-0,k Cdm4n1l;N}fO{5 5?,v[7R#b<~!\;"fy;0V䯛1`ܸfWf}*s[(-ŅuC M"Um96n`S~oHdkm2\ Z;k&-O3H5 =? κ 58Wm]fq:17:ͧqUPSuGzEs@]Q\q kaC y=lDOucٴIwۜ}iqrشA˚;3yq(ܽ\ŕu52{-\F/wf.3؍35uǫK+ؿ̓cצ^nxԖazmNKy`-yY1גcsn-[RJ}EjS[jDW3C Q|s܀i\~~O2O}@,'D'z@ "߄E"uAcdx1PM 3[; $"S mzxcEY3컚ҽT]Y08HDHMћvm@5Xj`3X<Ч=9jaLmAc2Rs""DM٩J/ז"Z sFՂl;.#C^iRM ʢSGG0y⠍q*:Av2#p3T .\3џQ07KDcy{~coaշ]V4OȪ#Kw\(9Ǐ" FsiZ?eE{Sȴ3wl':݄rxa-GGu!R\2t7 /̓kw]lڍb-i%vD(f<,|ƒm\֊5b,ƺp`TxݩST;WM\ ﯩW5km/o\6D}Quo{ x꧰kv|o?Du;\6[Z-!TsSYxV_Ibౚ𗩟X, du@% +ע7F*1P(\= 4FyX"Mi4H&%@ u8+3 /X3q߀=%L?co`^[pCGPQ5۴ ̓B'cN=)HwsS0>dQ`:xOg7 UaLlmJo|^>10L3|wm[Pi!ZSiVk̽w C ئ=H+־viXJOo|,J熚h3|"sn 1͝u禋5oyظ@/<7F7Mq*ٱ*5ߗnpT\E| ?K7a̵7CW7]e S! ڵ康֬rsuMzn<~D] GF5Y kD6Lt}Ⱥ0|!r˾ͣgბ*J[ey Օ5߉yY).]՞ }RaKQ֊| ̷]sWk\Kql d\֫5}Dg %CDݙ5]GsCrListLs75ZSU҇^7|꾯TًO{e'|q<~ -d'zwsE+n>f5(j^2wcK/_ueMVnƜ;NQk5nWqͩs%mk7٬}{mDd|"7jGg#Y19۽lKt"g,&zItKa-ɑXx|Gez=wU§S޵݈:<HAǻω(0K?++VFKHyC/O;;]ե˩?ЕhH8؊c |G@x8 P &@.@ :qE{:D;V ] Vd土G"0b-"xnd~l~ Z(J';M}^@'2 hk3&X*ݘWWV ZP."D)x>h1(#Ӄ/2^љ<}WU6HTdxpdX#Y7| t{K W5iX&Y+PO΁DצtiZ3dltÀɌ} "g7wK|S: bUސĽ7<ڀTkZ0\ ]kND] |o 45lН F/U:J?i+6%nȀmq\k;sZF ZϮ=s·*8 t }Fr~bNP*J4DN^;# P k| 1+[ȣ>6X 4jgdx_E`e`%n"5z"EE?z真l(UBk'd+h'ڵƏ'9| "Xjr-[M[v KmئkL\^hpEҟ c*ӥ;hO`'@lVcsEy{W*^l~^Kx"h+3GSSZs^yei-Ol!R?5;_ujH9Pvg}O|Uc#Yrْ]KƳNkT\5kH50"wEuo`i^\ekedK-!uf4ޒ+E7S *WMg2?Eǡ5o9ODuNp۸W6ZyԚ`h/Hrm썼i"ދSYy?r[ yzf[v]b),Gyr cޝ߰wC/p^o\:tx^K_]Y"NRyrOŀ܍x(rD|BЕ'_oIoJ/X&Sp,WF  4[!?a8#"az(~BXO(y(D؝e} Z EN $Vd#Rpe9ymj#H P lT샣`XNsA4F#+ fl|^2QڶQ\ɮ~;:;F`Pe6^UvP\l7@!xUJ2P߽"J^-691!m"&~ YCN/k 06w2v,MwX OJ?N{W=[sͼL(ٙDXEoZy26~@ғlw Y#|^c2z"Eٲ]d\ņ|*:ؼZO >?n|Us4ͯkLs7: 6Ayz GG;]h E[*).mE :ص|2dg@3ay+̇ׯѧ?kEt_Z1ў#.`.֮A| 6?iWmiV|t%+uG[WyC{1Yr4!ܖIǤ’ώ>/0ex@T(GRԫYp$@ x(~"rRI_HX<u@mir9 `Tr:PS_ )#K_̣,U$i&sUbNE{gF|Fҧ@i:n@[5Oӱ_lת=Hfyl,*`l>D={5g(d*P͘@vȣh],+W:t|~o:E9߭"Ebe(aI>gü {|q *a~fRbiV$rx(aMQ(r[sV" OP;zl :Kj}innJN4ߜ|Εo5_gDZwMF؎OkMO>0v W(m ^tC4v]p7Vܼ)"u-Ps&vuBIMucw3o:9+6]*;L",4&ok'tñk]gMdk |\p:.9m|6z%/_-R'߼f_{tqYyF/tm WӠ/O{9߷LgyVZUjYW=! oNHN$ E?pUAt`N е:TA)jRy wхhOrDl__4(c 'Pd* EM",["{A)pt>6WNgs c;~$2F6ט} l [FNL(djW~qp]V!",Dr~EU4m'I >5pT,r(9 6`2i}hkL2&g|?.d/}*R6'WTXjfcJ=˱,;3kE['ٍ4,ؐݏ(7Mʇ=9m|HAxCA`[:X_6wl߸nDi[@9E iٹ\G*XF 2c֠'KɉUW\ّ1Z\d46 >z!>nRE5[1>V0׮G:k%C !~펌5I"tk]F!׏%^|̱mNUQ%/_#.W*ҍlxCF~޲ܾ{JO^!~c􀐌N}~cYG'՟`k1KZJՕPg ,Z[Q|x ׎882Haqgsr Y/@Odi'"7 TFll $ (R=A'76ǥqѽXE:<}l9EE'J^cOR~~,6j6t F1clDhd\Q6-69o@t+FJ8/T43s9T6P`Ĵ}AГACsj.f̀ɠ)H^:GwʹJ_}Z_5צ!9Pn2~duuclc9& rO#ɡ1:g\ ng-}C[3eRG3H穴˿Z_9CPwq]ʆ/[ȯ-jXNq]?wrZo-lιis$:9nl5ȗ֤5yH$[;|[\<;CQ"?I?׉yfu"5Xl_Oxd?&9ߔ:#gNm:ܾ߯۴$U*"`O`E }P+*bET@Q%M!4nnsn߷sv^Hhp={='ךcso=֙>y1qة6q29E9^Z6uTt֗Ě ^><6M?|X<\kσ^ /R}}uj+l+"_ғ~udTK(s*|[ <62uꗦ OYufn$pzyHP-Az@L9LA o\Du,(S'wBQcd? ϗr`qڐOΏF6 Tٿ@ʓOD&Y?m+'\e9#@u{(߶Y@;]E5jk^z&}Dً3T7# F@voԯsm]@4 gdm[dKEzV x8$R=hWk^].RH6c4~'P$߳@$@S)0Dޙd;딷y6"twh4@*jlk. ǘxnjN4)Y'χtw]=z"LDzƘܥLĦl"2[ ޺& F|ăďsJt'rqc5؎.֖}ݏ3k6[GkJ&>?d,Hc>߻_w.պHfk2`1-94T:5\碟=0 @Bysl$}١TnZ-?ag@pҌIgjkm DKZ6^w3,,,,@, DFڬd}BT`,&)u+rS|ORaVQ R'W 9TErȊI{S-U'䢊 s|*1D[,@́<\. Y+Jq$}LsրbfNrpy)=9I;V;&_>c( EF:'hv6})+-6t^!hP28a}A:Μ8]`O zV 6I5.fn0:KV뚇U[:d5vH/6'rN4Ѕ,@-Y0i/-PkN Px xK ؗg}͙a)c 21Cts[ڝq{kflz3vM+߷_wm޻h6&*rY4^ Z֎'$`qmdpvp^|&ێMrGXi{Lo(ݨZszf-_qM%U: XOyc${X⑜:cocG/&>u.6(ktX@ޚϞe]Ni~#o\~S{2 {%SlC&E@@@[ "^o.a{M,o?tGxjj~O<45pF+ggD?P" /eةZ@ Z@4d,rMT)'bWA20 L#D;*PP%Dp22صfƗ@+Яns"*;pInS@RaeനSP$+A.ܢ#`׃tbmtW> T mlHF@N-p [T2l+%y+S~=`5~e?s={}4h-!O+;=NWs\];8gHa .5pOͥ9/-ݥ*1.#/np,%C}Uݟ7} Ds #M߱t(XDH wKr~*6׋>I0$ q"zA,cnORQ"MAn?ؼ?w= ,XMQvP=#$jRK$HM:{t'p9Tcj+}ADmKsI ky;|@]un k\KAFӀ .{*2I2]$C:e>!@'DE(FAJ k v:)i^>D'Bbۑ)ɮދk lʾG6c{!` ':m+X5Ym-c=I6a'Y'왱}Tw^\'DM/uG5E"[/@w:]9*m\;Mg,7o\P̳ y Uƫ<9Ruڂ5EK < ux`76wy@IDATEΝs$v: m>k쾰b g^ti,XƸs[skG,j>snK RDKVf^?VY[|X w fGo IG|2f֗k+`ߊ9?sυR>!4D@6׀"âL<7 u~P_s@Sy)i`HEj`m~2ҿtH9b!@L*0H"ǒ(g@{U^[+J1rrg,D"1r8 h`u.Q]߫1۩Yݥ@=cLMgފM޺ЗnJdȱuR@hhk޲wtRi,rMh3Jklה=)ETᜩˮ1M_ЅJ_|V2w"[WtҾ6 2-Ҕas?  e=3x.EO<ֵ$[a߀%rU :kÁsrDlYPdOFPtܟ"m"uykEo@&Oڜ`;ffpI%Fcǩc3 I/u mϵ\b}j462fEƔ vΜ9͸zӷX!k=/3HDֵ;] ɥl[FLkҋf;@vk6)ٌwf*u&c( U `]iBtrN*JOƕS_؈(_&o,-yt4ecc 6CeXy1mDՊ=6ehm`sn"cM_YsJ媦{WV阴֩m,@jr9`K"w׺Y{6@qjlhw5Eч/~D.?t?ofY;X^3EFWژrߤC7lj׽6gDKVvf/FYYYY`-X&z}MCh/ܯ{j mTf[, 쌸w?洀r@xS'@|=P`i]Kl7p<7VO ! cX@+T@4 q"0(Xbz'2F,1(,k]&yDǂbE 4RA0i+7WAOwc/u bԢE;Smu 9OnS^ނsL0P!+;+lF+R6w g9r4d氊6ּrZ@TC#smM@Q`Y.h%]ض^(g˰eKӥ[5?16Ywl+%l bƤRb8;u={Gĉ:xa#梀xT>%~2mٱ\w{(*vcс/XSi{:c]ܝc,;o#N&E}&b_&ҞXc^ٟui#ɼޑ6;〺|}{(Pҋ?ܦ+@_7yUlx _)C:E |dM$G{!W塚ޚhviHǪ |L0֭w Dww|%_#fffffbǧJ֧,3ھ 4 >J>u[0|?/g~g _ߐPVϊPا$ d JO`zANpSD ^!є``t=*J<y {@~hc Z P- *"|| "[ eN?Z x.!G- "a{Pd\g?\ڐ=qD  jK7ǮX 5#Cv*5 ˼WsK*Wජ5k~r|Dk-rB&}6 ve^Q`4=0˝YǞͷƬDz'sn]Eͽי\wY}d"2\q<੩Vjwo QCJ\isWl5[q zh&O{_I@9oOd<ȶ4m>2=f_"&=\lNXJ|qGTZ >g<.xfq:ipf } r?-m6ĖGtk\^;~47`K{)ʣ3I846E.6dץ毕V㯤~Х08Qߔ:_R._1~"3(Cvǩ MOjH 0Rg$Lϴ9#(؈7L_H~TW j|E@ֺL%K43*ETY"D'u@EV}'rtyI5/E~ Z2PLX%AQ{TYnVwhtYFQ|s㙋ܮ=+2 ~}vy)M 5rԀ6vhK{z0$+"+`͜IN#c5v9sM=P6ȁl <;>֚~yXr+_kC ta7OFk&u mo:Ѥ2M]\,׭@$\"coڷ,Y҉Ds ^d喕u2@tz*Z\93Ӟ^Dּׂq}9GEG<4Ns~ODׯ ^tQwRMgmwRQ΢|_׺ 5j;?I>[j"c2'4E]9ʁ|B{QuIܒE맺p]K1I'41w MOa3SSi. |53gM+oAVIԐV+0 YhFQ ,<':W;APHġƬ܀c9@!O h}/B"`= "3 Nr+˗=  U[@VH[A;6(c`YZ?Y%}g#Ћ2BW,su^R},yMؑEʂQ qfYss DdE2 :l.:r$E[k :Wp3Dj`yѱPeE^ Y<8zPoK6NFLO֊ wq,6(X|TZ'qL_eǒZGf<97EEy#B֦dM-x nxl6M6c}>ek>=l=ldAİ=l2d@m/_637%M:'M+ؽok6,L_X<~"rR<^1j}f7+I07ĢV)=Aj!&5,0g5#Ud#9|[+[>?p-t`B<q#.. I hB jJQpѩC&eUO؍Lw~esܕSEdM0|`(L. H`O1и9 ̜OU5m:& ^;օ=G_N? =_"u5Q3֍x`?X*ATD>"ZVYA]("Mb]]cw>z;ZrOuٌn`Gv5sb i}lsA4K(/#ʵ"Nɓ>@}8yMw>ݱf$>ّZ?\^DyE>!"=%o`z +`[h{W>cPUj9Kh]D++F"t_5ExϪf ~?~ܚL4z9tHe6tM3TTxw?zkǽG^t%YJ{t.I&XKb6I@@@BQ M=*G?{δfo_Ȑ_uI15܆<.h;\L IE˗ OfD @ Wn`ul{ Pw.Q =kr+x9) ; FE'Y{((Knd%D$}͸h[\@4љRTPAbyLA-KKwWM,9E=&30<8=ud0LO0lVt'܃l%hJV ?6$֩R.r%0Ƕ"{Etζ&Syns,=9|KG>=Lɵx-3gN6=EL7"e>z̵F6ڳq!KҎ0_{C1޽c=}FXc>W*DιT@>wr6WD5 eRvo|[J/fb.^>,,,,0_wW,` htԻMwga.}wR}cokRmhRt;kh.Z;ܟ@)AAuVӼF xl  ,0yR+**0t(= 3ѿ.+I` E mHY"E)DDX""s JBt$*1mEh4Q"=H etXƔWacTTHiS-z#hP`x +y rk`5 QA9 Xj\ [ Wd7 [ӿ8@:+q](yi6IOO|͙S|&rNx{,\_eEhI"w32!@h=!rgT[ihhhhx04~҃M xz~r5c?-5솪dn4>e5*R;P{g 9+2dm f P):RBج^'ED3.)swsA j)X5+?,k"ge?p:k7I+LnauO_@0@@ӁTVv5 L>6?jU^V}T>H I$6 Vt.hEWV*\tFl <b07C@O0`tҬe0~`+}A4΃?Fl΁Ơd8E{]H6ykّ]l4My|K/R\Mhٌ--C´տ_"|$!~+D$Dȧ#'[ce9)Ǡ;?=z]%}m>Rs^d5qbsn;ce/9k"XS)= n^k \[WƷ_c{Qփ߸Idm=3!ctpƫsmU^e\<+<6&=,^4_Y\}4;H&J@@@`X# Mʋ`o/NMHE+Tߔ72}ptFuC(_O X (Ѡ@X>:8P99V:;,=8BIXi+3EߚN`d,qAμ}Az )E+@F|"A,$ (UtkJ.9#Z՝wwƽ#RZ-#^,P >7ld~cs yWls]KO1Dd9w:$%HQPO?<[:c˲adD@O3rgui9sAUrK ܮ@= ϥ"6n#PAl0H! e#7ٛlc\Aη|>/cc01; [h|8/KMS9D/= Q׬MKQ/X~&"`+8sG=ÍYi\n'8|r,"'X[m[zY6,r=nӤ+>7q}m%Ɵ5StI>NM|C:|^.Ikuc7g`h9e`?̹5 YpX mwI忕feLMP\)mm!>}:D `9`'pt@- Ί=8yy Y1&`lVL3HU#?#p)Ƃ`Bsi˫E.5fCr-H!V+  ~d"#^Qi#8|M2+` Lg8 GJt. ` gnDÊK nk,B(` ij**3C՜IgM:k#C/9p"2n)wkb7W4-B?/e#ЯgC+4mmEdHV:f{`tclmIpeAc[;v W؆ƚ7ni@6^2gz`[u<#1UD;KuNJ3b粦b|]fH@Өۭӭ"wҦH9OZݑAD\OsmXS6F#TH4RNX{ӆzGuOZOzI'|3'|xecӱ]Hnc7-8DMoVmؑ_$.ȟ6*'Gg}D~-X"/N,2fqJq>Yn位 F YE6C/˵m"C~~]dE8䲗 ((F^d%}6n}&SYYYYYY,p]D!O&рX jS?{@bx?8r6>V,ƶ& `r"h[ Lm@#3Jre{`2!z8} D}z0 kn0 0Õ6z҃i/:l@@mY@' uet5xK#P'QRA(sʀҼU?`6"$S+'2E.g:mkp(;tATQ2eMQ9XAg$؎\?xu"3Yj׌x4vkw.+C#JA@NclcN,MK_BGlL{q9zh#J{"HɌi#ojiT z"pY襏űҙ^JW<:]vTO4\sGA g@ٳ=H$mE[#y8_$ʷqƵ1^SHzϗٜk1[;oj\wӼH;0{|/ӑ9]f6D=Su>s=E2wU'D^b9-e]CQ-E+OtfObW}k ƅ'/ߤ+D,0 <\sV^՝ x['D 06(pw("5F@HHAL@F5T+R4+@-al?O q(XZ9]O (UpnI@4`a` lpTJЋƝ5m&3.l:HՎb"]/ds|Eݚ)K"9j:^/ӼLl O5xnY ]~,wY]p z{أ(+&À&Y*8OE!霅\ډ7@}'6q#l𷓹v.dyHQt1[KRoim=4&E:Kr X6o.lȸG#y.1%SymB}ߺӏ_EQ^zFG O={eKßpe$t ey\gci4 4 4 4 4 4 nhտ"6?ק] ~$#}(WY[ O0&'}j]@cγ@3*PT@9FF`+耽RM);(ضf\*+xn%(bc[t ӭLr%n}V[/xy6ɖS;)PX` |m\AnIp=3}&vvgWOcax_fffffffOb>#?vYYr?х!9 eC}ռ"'~'ft*4Z`:9ۃ5 *X[yP0|o]y< = E`]~e_ѣG.(P> x>DQ̢U .VTSY&``x8ai+ Rə RC^ MDYwEڳ`KJ-?ُEʯ4h)@Fvv[ط@q # ?:lA49&(؄'s\"OzO2|Aai->ikrօ\y5ֺX kgcK'wYОf.飑Hck]vsN[| _ws6b]cE9}p+N??ˆEEkfr g< "ޗߖ> z/Z5=)T 86PmDZ !<6Ea@:rO.Zvki&X|ӜZ ,,,,,,,_Я/4>sf/ȿoHCY^t(5Q'J`[`eݚ5`ZQy=0 gE1xyDrŁ4ރJ`#OJ׀URx *0/ pDr<s@ґc3sS`FpI7LK]IeHm[=>BRI{"^ -VW5Ao:҆"x>\,Mg֋\sH@qGP9`Cr[g+9@9Wzw[i(VWD'0\ >E1K݉WDZsQ10 l{6X6J'E{5dgϢnòX]<+~wMDvyPҧlY9}O=3JQ!C&ޫcCkv*UM ͕6#?q<3qj}sl잟N{.H[d @b>h6a <19=\in(c| ~.RĐ φپr= cS<䛦H=u/'r.cr>{Od'y+_vV>קPߖovCY|ơԼ),p¹鉓:֔f`N>zT@L:W01U*X>H(@n*1s ;V.TQ΃"q7gN|Ԫ27ɜ9A0)(typh] [5 Dj+j:"DhUgVe0]GNW"EJa,ό y^i'_)!D0_cPMO+hnr~-K>ќws`=fi/:;~pXy[2 Y6%+mIv/MUCz`P$얬[w 366e3=z6{RD9+ ԟ h[H&J@@@@@`X Qv7n0mRΓ6f7n{wFIͷΡ-}KmY`-0;1i<7,e$X,ee(ZPnD. Ӛ 9gA'[:^+ zx+h ^QΫ")%D=X Gm" S]vf9oS,iR 2r"g>c*^ԓU!|[(ݶ\g,ɽ>"ܮNe Qt\)f=ٕ^EXW:9GNs4;9~R8l\P1oDZ[% dGD7P BAiQ 2"ZEm|36={]9^`1.%þuc~':$qy(_ (c5rMsPޚ#H 5y#6q|3~ g _-`>w3.s?4|YʈI??waDS_c.s6Y(&23%\>2;^mƤ_yM1/yJ=kSKVX7M7=`` sU9WCHPsaK*u?!%_^.VHͷΡ-_Ϳtho/< ,%n5ݑ@ (~#ۖt`hΉP 9s> S`sQtdPX6jY~R~zU$3ypD $2/iܢP3U1$\7M`+j5}AzZ\M ``YTE+Ayݘ7DDR{zr̡&4٫`|`O?}5 C3>Y9-A޺ڱP=niA_(@%n=ĭ7ak0c5 '2ݤ"r.u`t" >` {㇢c%MTE [>2?>zI-oSPȇڬqJ'"]kZʏr_q?eO: Q [;86-_`z$kc 8licF͙@gkn/yr7$C1ӏܟކH|(xhlץL\6g#HMffffflݔfp5Q}\hnN^?2u. <=Z/܂P)q,k>8XqhX??7(H\@Ec#|8^|:<6@+虅5gNxYOv>4gϤ@~>aAQ0Ppʼz Vq]EyMrKz,S;?ׯ򶦏\@.hL\* %Mv)ʣՃ$q{R?{y0v@10O3E!Zuz }x@"O~E oZ Dg[1'EO_{R,:ܚևP;tc's]j=0msi'֌EH[b')5ȣi;mc 30rm_w,6|3뙷Y#$Q/0fN׃+;0_e#y*~[53m<0/o 5M_GYk޺Gkֽ&Z]GB}6 p@ jckNG7>[/\V9mnO^T=ׇ2fc$Q|gWe :Ke}<Om{yPKQh62˙@*@cHQˀl4!r{S4y|[sSl:裋~#jF"lI,,,,,q__.oR̜vj"{S>55r/ |nuY;b {ZꇇTTM{};-m~ p׫c%ꎼ^u{[Iox_wU9092&teO@ݙS !S?k18ήL3@_ >7:t2L@@*Pm+b0HGd `P40V* pnׂDʟEVd3{"rkg` 9;8'P0 |Dts 2`\c ]Infyh HY>W1V ;Sܢ՜.pyCV҅l>sKT:㳱u3Zw& &XaSQQ{^   r!؛ {4y\EhwwnB֣t_e ڃ,3,j36rV_*m!ڑk-d7Zֆ&پ5.e\>~͌=oEH|r+LUl¿%|؊{ʚ{o|JQ)arͽ" t8Gst^zxd&; d%윳kltK&M@@@@=+G?2o=w[R[iFhc'X|To7>5O^i KRPV># -vӛk{$;G{¦sݹc>q=ucK՝:}:II;@3GB[.*_iW;E"9N]p 8ouڀ  0\1ŜdtU$Tr:PS6*W90WNl{ٌD@IDATo"jUJ  FWo΋  >#*K>/D &ckfvp LsnV`Pd?U15":5k *u0bbO>:geVO-%ʼn˳*ɵW33 vۗ郉_WX*c'Gx{X1Hؑ_"[3vi=f ֛O܋rs(]Yj*2CZVO"_vȇmxȜ1mr|AF |<2m2I'?!cƼu?ܧ|^ E2xKkR_osR:MSmd2D')@wi176d_Rh/|@793y_ЖiYyKgo:nq/[v/y꣺šV^-yXw7lw+GvsHh cg#8&#ݕW'`yIܱaMQ#`j<2Ʋ< mSl# ҞHUt'9=A4VY` nUi(b?_ A_p XSzQR5% jUp"Eq, @XSѾ9fKL @2R3^/6 vv"|AJ㋨MԦ57Fw=>b~ڊ.O';  ݶ&8 GmX^rw97oo޿:##M|o>+p8|Tlt"⭕8( K!xoK9'bceζ/C|0@+oq/oA]ygGEs{5v ʯ/2LF'Q=vJ򳏹N^ۿ?˟zǫ埫"XOEßXZ6U}a]h%]P jX`}5<؎_cyc>b&| p=\qa6DC@s/P/GI7eާ6M7*k# S:KJ~#دZЙ{j/Qy7/s@YG'$p;1e!Ew=&ѐw-7wKW @s\$Rq۔oQVa,ޫFL^~zi}S,5y0{`~ߦwDZ;CAx $6Gms ,5 >xW0 ^P>3Jf@0zAZѝ)]S*u|o;39E6K&m<#G?z<2x#׭,NEoy` xƵӉf)C c);EɲI򇕱|ҥ|Ėٓ)0ϷDx6!+Il-5ku^ԑ@tMp5Z~c~ȷGa~H=0~o>:ԦMKsX 4Xk㞫{uZNW[;M&7/W|K& dŇ3޾9`YYYYYYi}'Q_zW*|N~0*t; +(IX}= {Yϧ>ec/O}I귤 `JOQEμ~{+}(ө w7f^k{v`+u??~ۼd7!@S#8+ V@%)']э%jwW}X"4EYkUݜ୹[Nd+Y3mz^$=8+ΙܕU"DA4A0mDK;IM^JmɄZpͭ~QWp<9m p.s-A;Ekȝb, ϤYys=Ӕ= 5QnC8 ]/$M3cQ <[:7>g==$R9ZZo@nuWkClvsϳe坶!<_/A~c, 6S'"Pm7^͹m^9r=Y\#Gd.=zXWmsR.QsЉI,,,,0$8=5O\_P]4<?tj}^-|Ahtyﶠ{|_rk#쎜ߞO>OmWw͋o]'f8`ocFutw߮{L^a\ D2(eKD=@` |^J@lXVEI pF.Ñ:CtRk͢=$ AeW4@Gd'X'2] إ!{K7tlwg #H>Zދr`Gf_O<*[עxqqr8JƆl.~-Hl?Ƃ4׬8,3>^^T;@Ep]m(3G^mب"|6k*\5/k2Ac+W}Ǐlи7z}ڸi~j )4rڒSE?m9즖>?-wx[}윓Qs})VYYYY`,p<:~Y?/ ]9H}遨ҨYph NߦKͷVf-pòɰ$ 5EǦRK1E{3{Xy۰[8?ݖ#9L&+(L)3z(\F&ж5`Z%Aǀ<@L( elDN}.׼΃t@#Ǽ&#+@J,4da RN@tp קZ0ܕ5 }y;*%PX9+]zo+ۿ{S}&ĢX϶`<|Hgù'ߙHn+,<s̸E"߰yk]t N_oiOp6M>hsmS3y[F8z)d7"kvPDC[j'N,ڲo?e ^M/a_X`Nes2j:џb5|xZBŽ%{a>_Bݹlʙ8/QR {Yrߠe9w}G=yԇ Tv?0׺MWXz|d^߸p);^,&W.ݤU: @%\wlѴhA193ѠV8Xj9gA1Ec{@iJx: v=\6Ceڊ4J;;tvb /yKC;& hlsj, pkx0[EZFjM! >Zz@ݪ1FxfAX*'z0q,҂\H@ZihhhhT_Cjy'wM_VCQMm(4mJ^V7+ NOɰ|5ޞ7OL/38`>!}kXN|~s۝ܟt'_S}Gt[.^L!C< IEJ$@|]5N-}8T~@~X9`9M%^@\MvuhSb ƑÃ@H3tg0AAOn "Cu=/uɬ0`b9WGzZέ+cO{u>~jA zRXx%/&_~elLْkY ̫[ زzŽ/&4ֻvLPCPy8ޚÒ wweƷT"zP!ڊᇎȻ!>BcH.uu?d^S>% !{8|$y垮uFG/P\%Su'-xaU7'%^2eĭ4 4 4 4 4 \* D&zl̳jzK5Yg^, "MePjjO_O/7EH]Q滣a5/ /?>ܝ|u'?osy|y6ܺԾ,lI@X3p|Vvt ~1ˀ?`,*X; NuY4b>Ec}RPx0 r1`Vp.\)!E'p{ۘP!ʹ\L+Ngtκwu٫dAytnh`v"O&Uc5ҬurBo\r&AmJ[v'R=pP r]2 w"alM "u66 !vaz Qj ]=]7sVj"cJ" \3yEGKyE/=ZѳOwJ$2,xbNmhhhhsR1Leoٺ޹b|/BWw T姤%̓@v)Pka>W:S^GL[s韲|_({Gѷꦞ~u}c?"v2*z2k< uU c xZTpM&h6DP4EAjo΃i^}ʌo\\gnl"@hm#;ѩr oe\ys\lR<:E_+FQ d䕿Xt7o}ſwccWN~Kr?h(=;9;qyebIwSl0$wt,vOI?2RxO"U*"885AY)@5ay@"]aCP)HOs(:6J@h`ǎEO̱)ta參A¯#N"Mw?d6~x|l!OȪn/dgJ^tAb(B˹W͙bX|f9?3{Lfm |G454~+oGj[enɨuCh_پ5P>ߌ~-rӀPߓN7cƇ2@3oxNf+vrQ65L²ۻ'uj^E @ݔ4"+[@I͛S\)!*g;@J@ZO2s8~}0Y|;>{ZyE)ڜKC| d \Jӷ? S.5ĠsD_̽Py$r3y`X[60K* +R5a,S'X?&2O+%s+sy^{@c`5(m A`.Eoځr4@Hk@>0"?r[1ͻ?_[Op6 [k3::Jp8}C鰀񝁱=U;;=غfI;_([T2x k' 74KYQ֑UcR6r[ڐ`_S |I7a^ڒ6 h~Λ־91ii3u}lRЙwmEő!-i^BY. e6OWE9)e4 4 4 4 4 ̩Ds:` CHEW,0:?@q{Ew_ {gXDֿvS=̃ĮX``W.cSW7rcN53uj8Ƙ"(0DܫkDM:\S5bA`D Ћi衺kS95):ꮳ}Ԯw{g=k}`nnH| J }EyDKyÅw5t9Ez\.hsh >ʸ'PNlơO cUF-%(F@5%>Mv\^+PΖ<pڝs`^ϲG+Jl[y}o]ZR]̝x|:>\pr5OϟS60e|օ`Ɗng_˞ԗ*E\.1+MrN9:k!RUߜ%orWڬįK;4Si Jj %V#~w=loqpqyDkP!G[or~*Avp|{Kr>F4w&5vsɯsg_zG<ӢίTxJ>>10Ld, |Ke 9\[0QyN)x&Gxܰԣ#,XvB:hΠNIy~AIMQ@@M.ZkWRgT,in&JclQ}(?:s9ɡm{Eˆs%uD ڂ`+Xnn֎"݂9ML\A%՜ A~z-kϚb&`Ur8vg9Yrn=]Z ?/5ј?36\Y ~Syh<7|ҕWcbK,=I(*2r6V^|?p ܣ@`  G=L r`FM dln>ɯ-.M.^)Jx!$0'sN-*'qyx3i'XCn\`B(7 Eoi*軔Tˣ=h?}-;VƳܒbv@Jŕ@ҡ!!*Wy)h=( 6 W1obM=X RRI!T{iN*83ǽ5 ^ǮTl(=.5{U~lҊҼ9-q%9ɩkrw/>1wѶ6ON FEy;i gy@'ˁ\zX`_uZi^a@M T-RU RzڰNZ 7/Ea Zy`-@F5 `JMp,B_ ݖWND־Fj<0R/+? 6߾g{-AopD_.bOEf!B.ݽIlv'@qb lFA ʛ#l}a"_ zMs.1lEyڗR߾+Յ p (+roq1UTR<:@.u+JQ7u}~0 Mh7-ؐ7WZ5|͟B9 ,{58gА@%{`lWr,P炀F2F:w^*ꄼ/pz9C#I r7cXڑ90a6d67d~(E{xp.#/6ޛ u;ؔD>ټ`h;y|TP%ېNַ؀ʠXw,[S֒Ӎl*ecpo)mc؜MiO.<+Ԭ>77Ļu/kh#ocm6[[O6-$ l&w.<|4#ky xcYY|F@.{Byl-}%tW`=sl-O?{&kY X:֛9p%y05[ֲS6(bUrږX" Am|!6҆ϼuTֆC:͹\]{c:Rns^|搽TϮY֖~~X>,9J-~殎񋊿W"%Q2%+u@d[j7 xQZ6i^h~=nR#[db]#\})c ?c8_c#gJpWQCv@Z@`+8o}J䥢¼U(> %qA8*J̟T`0W>@)sIgsi(A}RQq8 Co[ڗM26 lJ|7utTǠ$m,vKw<\#l6|8r*c+kcX ]<Η(}s_o6}! +sBA,I6ɳ8a*_F h")Xis:kr\6/E-S4@SkUFͻuRu|?0tkY_^ǎ52KU ,=M~D"7^ԬanLhֽl`}$nJFFFFF`#+1Tjy|auV+sf_rĖ*>Q'[j7g,Tk{S9ºp D1^P //Լ&_^JX 1 +&)dlyP{sEp,zQ(S|ISٰ s L@|6 w 9c@ 9sZ5)Xs_ ccKSx&\|Os ÁCͭQrSckn{_+@Ť[)@=Lc]ͫ@/|qu~AAcqja̅y2gEss6 .yi~ƬIԕBh˺XqY9)5QGH]/kckX;8gk_e#t}$^ȵpg+d;>c~cZֵAu]:X@@@Z^W#lM'7J|i^9jy|xkiZ?i G:T+1I w&FeOsjdsḰn)~ sy>kHb=R3;NxײR.p9uC a6KkZ}p͇I?w]ӴlψW2xLlmQ!=y!߷Aȶb$u=O=ݷ )zr˔Zjjjjj5Gbb`͙5:Mk-9oB߯o_^#[}ho>~Vzl,w=9ؚ-Do`sLL mUC_c_ֱP Mv&m9z.68+܁`0htmRYl^ACj#B20-8`(Hp辔aw? ے#<Cs,!MrJ} Em:ucKe <"o^_ev>xoAż,oص̑=|smh=(\*>kJxb.sEn x؞YWJQ27;(Ӈ8&rS\+)6Gf=QL[ x-v2c[9_I̅ES%rjFFFFF`]#â_qjyGO~|~Wwǣ86C_w];1_>tG^t~G9T_M_pz0΀7 ?k$@euP'X'm>HڑG9o)FFjRcW~[PN? 흹&((T{PSllJO!rIW~_R*BN[=~ /5|tOLPT6<G.6 ̃ nQr'irlPtm:)KNAԹ:3?l6iXWb|{aCa4)6KΉ| U{oi*2mǥܘOcY/7~ٱ^G1ޙܷ/M\y4opK<\Oֽ0hR#P#P#P#P#4Q[f\]Wut:3<3&U͵W5!|] 䮮 !lPBmgmPV~?eG| @6WQ , \jM@ +, Tx٤i*ʹHJ5*'WPړ\e_0q8GޗkRFm}QGMsuh& *H;L]`:2Nic-f`|@&h%u%Weg6a^}z aŵbO= ^0ʺ9x5jX{bRX#6|7 1X`3Us*dkPlʱ62vTۏ$]-q-;7\l}KTwB/ah5P W)a ԥ9蕶KKu6ڝ^z8ywX&ulT^ISl>|Cu~{8mTdU]xy<>cOn^(л_y2g1 *`Z3u<H(jt%Gژ;KbRZ7Զ۸|h+WvQ&}o@5q)Wl̳]j8_>o vof]JA T{h=:;&.8k r=.շجMm_>_Ѻ =`@@@[d3qm[#p~6}[$S:D`cM ; |MgW]ܮlm\o}[j^5#~9 ʧ+̀; G+;xu0ڀ^RI omJ&RP Qؿ9.`Jۑ^BTkΧ x y(S@TД߂`ბvk^ xNð|و rܸ^RR2;~VUA,mT/90JJ}梅D jwbcb$:h^ekݘm8lZ/is5Y[TX,?\ -'֢/J!ur>YySw<0w7^,ʬFFFFF`"?R ~-5}׍пK'O4\ľ73WM 2>ԔsP.uK6J7/`T{j^Z{UcŚBhXFS׭q^kNc<ڹ.52=2P5͠roDk JY/xXbT|=5v*z.)/̦|ڝs)1My8īu/[ g5R#P#P#P#P#nx׺Y궡FgriqZ2oH @IDAT9򩦖uΠsRE Dy{\qF˰RP|R^k~+'GW3 x2ZU~cbl>2 aFI5 J9RB[A60 J*!ucB>) DO NSuK=ܴL~0Wc~-8ky[`'^g@*D&@P*kc:9|rkNU4^fpޛ8ko9vS:::3Gt؝뮙-&źFz5gT^) 6yos=6  }kRlw=2y}ku"_?~vVX>;gPR b Lf~鎢-Ƃlue-l6 ߤD LmGxiFkjLJPj=U/X'?0xvw Hg`.gt@́ ɏyWjUPۢͽN(rv1}TE=z6~g_57sܥ}h9PYs=Abpx< g7i*6uKmS.m:r{ ui@.8K ʁT^Ac0O{ۍT"+ 7'{%h={k'u((>թMhT=ۖ l|nK<}h1sE4Fu'@H/[i(c܀oSJ9KiAy!OyUs?vc.SAlkmK-ߩKixXQRU'/O k 6z.aCq`}J[aB֐-+O,6Q$6c~Ͼ:ɰW"nX٭FFFFF`]"pߺXF[c6()>g/e`R]9m)QQS,qX3i\}mb>Q]5GΏ67'_4e0u. J; 25SyJSR{*- t);׆x@~Ec:c N5/i0@&o:d9admRU-k?U顃5haHʚY١[/񬕲U>'u}bek1M^tt9 ]\_sڣDh l#1űM@3 t%@~uR}ܴiG9=>Gפ^@ f q)[f|qh0+xMݺ{'LSGu=}}m@r)O;WE*}C;(]M[;p`PQTU=j39TMJj{Jasg=%T@$ԾaN(s<<\l[Tܕ[uZT̹ax'MN>fŧ?gO:4TFgtW4Ylj>pHm; R_Jx}^Y =;}_ xG@ `6\W(yAfQic4ci4AIEw`2 S9+E}l^S.qSaѠ'M\d5w}:q0]͵/XIY~N hg;_:*3O=]?|J wfhE|gD/|[b kjj( /Օ/6Oo0١u_.1]~Bn]zkS.p@͹Pb`0;tixzEjLLm %j\ +v@FDTnNr`  I#AL LL@ݍ Ȭ( ` OYZQRlIAE`9i<6[uXlKP w5ݺy@+=e  @Y=Xz0Bʉ-rwɉWjlkjN?_i k}(ր 3_7uNX9ZzA*緵JT/bJ>Jbzq>%3p ƛ~?Ԍ7<jmxJ mrpKFFFF8܏Nj555WޯDuMdgʹqnKtkGWkc׽ŗhc컚!_lF՜4r·Q`kUk.hWJ&礎 (Y]ZHpi?k|~vBރzq桪\D1=SSEq R/eƞ0`\Z쩭s5ߠ2ŵ4#LPڼ%U=}P"H-*\xnwe^R6p7&viw5>R` 9.oL2 X{`<`땭ϹumPuhur̓ CmܥI?ԕD3|,C8ݒZ)buI_Zjjjjjw[@ْq kjjZ7OloE]o Ø+,C׺]CAk I:xaob΋Z7`jM{gsLE/eĥ\;H"l#Mi;r8mG~JQ|6s`'o*ԝӏ7Tԣ`W)F*\* y8~QugE^CyhJdz>e6趤b8TөL. lÿygO@TpU*^bܘ6͚]SJ!@/_5@ :X%O,:3WRxzhZ^Ӈg^=8p 8SPf,ba#SxneQln}4{>m5uN+Zئ:}wn]r j`g@}nl䷄Zjjj.R6{y'fs<͘B+}1 ony ](U}wN-ۜ[|Rs1]I _ycZ@]RC9р} eRl@ɋ .=*%gl^O] f1RGd0?H QyUmlWR-伨_SGj`}'Y;4O׾@98O&~ʕXG}:qvB8K5RF.̊mSf.J+qQ#r!s_nkG-|116]A`}RWXa#lM]S2!-|d=_Ӥ qBѴ<[QR#l\:zΥɽ 9pdj}c׼ooV_r j`g@}nl䷥Zjjj2_^һ kkVk=Jt$$#"آ4}"r[NMmV9Db}h@,5 Qij's"uO`6RFrm[E6h~>HJU,M|cHC9/yS/J^l/Xg >ܦ&S}v`s"g(N%+W⸀O( `5TR)+50}yّCzCԿzbk]RSTRC%o@,@ 6/}E>ZLROw޺p\O{KxPY3khmB4 >[7|XEN3Vk+p+VhŃ9/իH6-Cw-4j?3ܿ>.`R߬wn]odÆo #|y ~SuJA9]QPKBj`tWfv)Ig^E=EHx<g ^+$FOI]AIxԢơo20o"lqsZ`^RU5+?5rb SՖ s5Y=sP-x:%DlN$JIBM P# S~{C^JP|,1Y(H]E=v|׮iGf珇Tl)`#{2\bLMO+oUM+%?y%o1]/?p_@@@jj/ߚ?UjYWo_Wk'F,jZyw0,~ 7ۥ[W)3Hԏ)GƨyOLQT7mv6uǮdʀRBߚ`I=@Afz&De}VFgVfޖ\RS='u+XBJAеҋ8QJ ǻ#s2+GQ'vҁdgṈ{%|EMlPV 2]sr8mm)OԕS6=?dfg })/ؐ-i.^Z YweMڞ8GA_+L/z=ރ^{7c ƒ 4642ط},Z[pnX|c+ѫFkjjjR*,J]Z#9#OSkrpFyBK9hs/ܱ9(H&وQs@5=Ȯa HU S M;2V9}}6@b$ҁ[PZ9 ޞ5^q){) ,$&J6+k+_ ͵%tv9ϥUxHm>ԃө졄 pXx~5q.~k|PN_m5ɑ}ϱϔ-e$mm0O0;5555/kl>ǫ55[.?-G>"`WK}̂MΖ*Eq x+_:WOBWԑRZza6Qt#+*]z΀ܧ8dT{SRft#wX~^ s]\N=ש`ԙZ7rj#hC5J֣sR{7f)elWirSqR ̯^%F"խ5y(6v%.WyE!y^=ir^=z.53@֮!ɓgx>녪ؕZb*@Q$K"l-P(nPϧ3p؃b~('R|ڝ__e0_ɏ;m;fhy04" KIP jS"_LE ڽnKLNu;Uu^޽)sdYlqNViD=q>N !lZ'5o\(sE<2Wf<0&Y V9ғf"(疧/"|m2 n$@(U\wp1W$Ԕ@t՜ x7[ ()?bP @v'9knu@\.mE˱u|.t?PrV3݉+ly(x-'湤)C9({fbmP7@\㝁Kj/#?,[o\ ЗS;2.~YjΎ][N{%Vfy ?}\|jOҊo4:[ɶ#wXմӺZB[~U ;daD ~8C\W ?(dbal|n=7)M8Á@`ڜ9O71UˢVTAX?H((2 r:j yJcQlK^̹!}1.\jD>^0W6tbPH Yi'R5 }]iK#^Rݓgmm\&1WPܚPpmFW6Kۘ#qkߔͻc;ʸcfP|֛4.9-M0fd޵V0Z~%hv+l7q>lLVectvFl--F ġ#vl9{O hܩ22ȣFƕVZr}gl6hb$PEDștr= F $? T? i?Y1hSBTٓksH? ȋ^KqN1 (Pr,޶]4t{m%B^ց_J߄yOyBURWP[rJ2ku!'2w^{/^hSci2x8l4_MYGք ukPEjE}AmcbOx/^/-:{4R}ВJFZy5˃g*oNG55555555F51O4OIۢ.K.2r\@Ի9T;czOy%lwNy!T tDzz E ɳ %Wol ީg.Vb+5)]x`e9J6zYTר~8f(.t6T/k}W/}8f^KE3}O .;u?]lXPHIl.k(׼!UxR`gr}2G:Xlă֊?Pk= ŕ*'555555555E)19~.:cLe1C:[6f|[}9{_4>XDq zԔj%G0w *6;4\UL TҁnF~.s u(Po,O9E>>N`@Paդ`@éfgSJ;0O?UVo)MN`5J\͏4P^c-p*g318J5R jl@ O|ե̣{r^S-@fk T/s>͡fnڒ8mlG[.GS-ԹT @pչɞ 霮M=y&33Jm侘AX^'/,OL'[-fyoj?'Ȁܸ?w?O5sO?~!Ǒ?VGPaqqv#c!dP}q7Kz] XA=frNJ@hRx&ltto! \K;`q#rhG$=#ΣO|\R[[QAzC?ijPyD?)@ H`l-x:>2߱gG'a`.AMS W>)iѡIIK]y!poo .ɜK9`;|8J9Ic H>Rw{`w6@ u΋D~U{$@D$p'߱a\ͦ~eöGCf%TbɰLcH\Ni OLw(b_R)HA5Nu>s~%mTիSS#SD]ʺ0TWaF's 1c;M̖u'M֠b}ȏ k5Rr,0cQ'&݋ ^:uYgz4{sG V5[+"fi]ݦcZ}kG65^u55555]K3߻0ꇺ|> y-DvP!-k{ǝ&ni.pi@6,|(u*?Jn\\ Џϫ}wWLb+Dޮn\nlڦP'H *ո[I3vnI T)k@=k 7=`KjX.$ҷbSE,m@ڡ\mK?lpAa/HN1A`_D?1y a]I6-w2huYC^ 3㏟m^ xjJىjk/I-5555555>?|NjFtlRSKK&lj)X,ղqgdX3E, }>|@^:8ۓ̔' TB]k3U?;}اlXڔ 뀼s\Խ gPRJ/Q{v::my%N+.9%C,0|5_Ď"{8R *G^ -61 Yte S[p͟A3w3fu83!"fYsN1k[ȹn>cKz'rP>_)ZPE"fOjWXG%s˷>mgZˮ52Zr<_-9+XiċVX?` ϏD,\S|FFFFFFF  rƵ|r鱳v6#9~~h3x|u8Gbf`1rF]|sRPK;;Pp0EfQ2s6ACAhT;;2lЗ(>[(8]@ǀN` >G] $2 H tz)*5u7))`0B247mLG;Oυ&DO"[@qٰ3;sά _?K{x֊V ?v%},u8 ɚ'sΖ9\J,f!SVO\='0 '2ܢlE2`l:kC 13^kU FG̤z,R(Oe|m. sk S1W pZ avIs4JA@ Jo`L:r- u% p9 ^v_i{Jڰֽ"EU]$>Y w%Ɨs]ld]M\^_̀R( :o e\ckA Sy7䵦nέmt=taJ[ HM fe|&ZxlS_z0[nk`淚GLB}lw<%EEz dx.@E3;X8y{ɢ/ ]N9*,n}Iu,Tց$r:h|RɰŇZڋ@7aS{zkڨ[=!>+_ SK_3&q.`!sk@~qT֞kEWޢN}6KYs@׸Ӵ7u SߐZDZ)\..9'_>fc{+k*]ǣ{[ap:xr\5Rd@#WOtխF J⿒|©e-RafS{CD˰xc>sח}us|x -@`2}O(AE/U&Wc!G1ʽN5wf=.hy;,i|d/˕ٗ\]|ArFEIw!™wO!G JsOy?vT8Pr;ڰмJ"@`` nJ}0-iJԼߑz(r\ + ( lRRc}. Oev>RG7&((ݵQW V~)ɺϚl@~圖1 JPkK]!vK|򳲔Wb{8>;hӼ8)@z8c= Fώ?=T/8_>Ujjjjjjj'9_㧮HVXܱ pO J04$jeUke2kY:N֯uUjjjjjjj'N_͑O;$ٲ# 76ċ3}9Ԧec6L\/r] t'-'DxXs0ҳ|?MԵ5QdΦ>3w+i.?|oEn\p],yLOxof@l=T%Wjɋbn;G?9b7'/+̈́G7`p A˴E~g\?~1\ۖЕj@m9yX7Fy^ʺީifT*ɉ}6)6^_Y le킵7?A]0x.շ97fc+)-rq)GAs4i'_mχqK%u1Ci/4OP$5֥dt})[ i"jՙZj.7bWpjjjjjj_+qtF~?u6}jZUqdMk O<7?[ ب! τQ{ \SRgOQ;{~6h7;L͝o`j"gX'ֹg*[|Ojvm/g|w^vwRLZ@ՀM0V~iU]*;b{,7F'gi+~%pyÆ@[ Xlrw[|~wf]J`3iǾun۱'kYN{w[=ͦm'k&VQDN4<~i<;4n_?I`ş^LW.><[Am?_mmߴxΦ:^#P#P#P#P#P#P#pT&O-wvd1- QSC܏q<\}_?'ƞ=]hr`-0 nȓoDC LH6;l;[*䷿߽G;/?1f:AB5 Ndx)`L+ +.l9]Ru1%˕v4My2v'S8*UD] l @x3uo>(֥(cԸr<(\4Nb^Z N2?m%r|XMj"OE-U'P,J^oڙsWӿ\_lho}U}S\yf3%̓@kY b oĀǏ%-άר_ #F|&罴%gQS0w"ݍk-ojԙQ7פ7<%h^Wݞz02 -U m异YEEͷ.`mPXu39%]{t1,;"BS5O<}-xїr$iKoWg7ޕE IiZjjjjjjjB8|im~1JhZ5)S9Ogӷ??$mr7F暛_}:5Z-knͼz/=GԿqʜq\o)y^=?|P~C/v3# HnGɯ&݇7*,_W}rfح|1yc8pn$jQ]}M/E};ƾG= Sԯ`r)J '*T'%/j=HĂ;u,mGrmw@dWPUmñͫ|\)Sg9sPGJ+%5z,ͮ3` Xz@.*-8J~.MrOqP;?'SF^˭忦DIdDf?%F["ܬOzFFFFF{'90Cxޖ#\Z>? ڒ֜hA-fCz;>{Ҹ.7(E;ήB3lA9p5[] ;ʹ5g ݞtn|yi>UqXg1x=}Ip 6f V~aX* -+nx ,2RN>i)sH~0B8w8JRA] Xj [~bV: Jg1,5;x{ٻ}.vh؂Dh"%&5{%XbĊEh"X^޽=޻SΜys朷yt(h%VkmmlHlUc,whǗ{_tbۚ4-MF ) 5*WqΟܷmH^ 1wE4`;-`4ӖR UAzf _"O y0SwBapY^|. o_`wf{]|)peLs G&u/, 5)f_fXZM%&d24KX,HKZ~3G8f #v|ܖje-6t70t%G4*NIٛ(N.[`Cs!VDZ!BP B)Gӱ`خOC3Q]`U(YΏUvf=zOgXiTGSi3W?5ĬsgƧfyʿU\>|ET>V 0k 9cnY [e"[ց{J4Sηxt|琖[i9i r mPMyZBjI 35T {̈́N7_9 wpߚ]vZ)4gztZxn790#-mVpm)'&𜘼}HToYϙ7/{+Zjo$`<ՠ#P BPVsoPwv x`!Yİf} t}~l}ˣk7d J./ed+qƂd[dRF Ab6zC2W_ɝUUe]7kvZZVYfm\ ln,Yx06Vha,Tu.3qƼmaǥ-5aFSKg@X?ؒe}e!gYMP"C{'kOr__u9;_"1uLg0:yH/@ځC k;3f 0Kn{âM WߘNZNqTE+" WFY>UFX5BP ~4xv,]y;)XSSS'ɟre( 10OL1s@3^u=/# ĽR9ai6ЭXj3dJ/霼 uޚpɱ!8 T4c B sI/t V{RT/Bhn7]΍ )M6u!Z f4S2f(,@9lYn tsvyf6d=~oHg1xr+czYc1,ڙae@Z-R8-;G{ZSFieWTd-8r.P୬KYP BP ȿ/# fƜcyeI&ڭD/ MCcop<Wz̢cʿlYV<|dbIi6a)53KsxgԏlLkJ hlʫӂ P]{WJJ8w~TIX<|Z2R/jAL,30k¹ଵ~cɏÕ]5;N͓M |-`֌a!t)0 Zk.'C8LY9%lc/Dy4<5x$;9^afv3ƒNmǏp5+X:8vMB-5 7Ca> zpךܚ &G65Jߵ,d"y1;Bfa >b!~1_ͷ(W9^U]ƪ+re=UօXL JIX&BP o|RĂ']5 a ȣ~C>4#!Oɑ%rblQLA~@zfc6`Pg&/K +kO Z+U nlaT*S,`K8E0){Bܴ2f ],aY,ϼB^mb>k oX.-;[)I:m'"3<)ox{2 D3keY؇]kgt3Wq:fDYL]xLN>uzlb-2l K=ly.yYo6[Bf5"BF!`:$~Ad4Wmv>ٞy 2Lo]s&vN{ v5r*4fn)o<0^ZYw}N|-Voh0#* X_@> gbυR$E$D( @( K߼a_p핌cD-g 5U5j RRNsSHMr>{TE7-z;xpcg en &ijMvAu=k6/–X+$K<FOF"f a'Ly~i ȭA6@Bi6ǀduF%1,[+n6gx}N-vkr)5ݸ_}\7Le3=έpVf1j-?׬fَ6 M>B<(kÓ|qV*B^BV2S@( @4.n9Ry>GpOToW>$9M6{|#=HU,r+ Y!KHEdLLN E^1=!˷'Ԍ0[X L0֌[|9L0$E% \Z.[L7< fћY&.V0kF0R'Hsfs7Ba7s^uߚif >ֱs\m ?-_cyBnl#,wamcylm}[) Wo +Pn|k70c7^?vE>U}Wpm_x6F˙'O)>8L'BP j|b\R70)Dj=h_V03q;2nư(LYO*g?ǣB{OZ~¬VP?a 㷲^'+Iv&C , hՄǃJ#P BPb 7+~LRMy߆o2_\3%Vsvśp ^85ٱ(nu1Xրu & n(8Tf}lfcKlfWfֱ+ήcWftv53_73VS;k|׺;A׫K45z],wÃOV܃9*>rEVwpBP BP ; |W.ݬdkx7?=@;{h Ybf%S%,Phn۩D*BE;Q s &,n(xTV)d;Ӝ Rfšջ[KBX:fZ!9L_pHKZpkvuf,a3v@:k k%N ( 4 l0k5YCl P׵]}cK2~3G,<_Kfcu{W ›ێY۸5rzgwwױJUfM6Ui&ꊚ>_A?p-BP O0>u +Z qlTto./ٛ9oΞ[D,|>ϴHep:\Zu*ERbj46%;!w;eHgn$0IR{An L zrZ`jL[F`782n j0,A73c!-{%d]ߌdk3Yf:~ڒwH!,Bqt\+k9.:6PYCȜdǮZBagvX8c̸m?XvlV~c]xº7t *޶W+EX?W΅X8?9bx( @( dW_Sr'8|5]YJ>esJIdҟa|b};"|P,Nř*f)/Y%JOM!fWxFuMW˳ֲ }d.&]sFwf9|7sBYAu|`x fn-a&u^3qkPZ(s>5>]f!w-3a3] e3լ^,ma H[f [cY,3/tG2T6˺yX3_Ǭcz'Km,P}[ 2> /*Q,})|ѪKa @( 9T̨'c|B({cū˾j|8.nK*ٙ؏w2+-# \?JL|y`%(`@:\5jSL_U8ֿYӶa@ck.Vqm"ӗa *&W@k=1 MȵFXamqiyC.j7sf ؿ2ZKBVL D &ݐάa3}7 knә,kc3z- 5C ur}/4^ee"\!Pؚir+_sWм̀8DYxKWXB@\f {Fzf>zU7sZc㴞1ڵ+W4+9O1 Z[߿Wbe}"+TЏG1_}œfx @( sT4mb3x'bN9 ~2~e!23OyBf:G a3k6_o%OGTurTMiƬzlZߩX.¾fZ,_KT{t.heoى-ٵ0yn(g_a,a!Ev:KJf|b.3TD˘i`l}Bc7s~ quٖX8.pNX<9%-5߂79/cY= z'ܶ! _8WT9%X̣҂ddS{ϲhTEtn BP BP |\KOVfwyOey l1s?8̖r޼Iwe1d+`k` /;Z( @( eS`+legƕȎ;EE-ۓ{ζesxPWgT(L;WiYç):ۓ>!8b[f5Ww GfB!b  T'xNw=O4km@4 in \su9f.KQ8Joc{:76˹(FN{3py [3]K0lqZ̦H[bs=!q3X,tb4kkǷ ?~/A ms/ nnh5ʘN|`v﹒7T㙑%SU֙*FKp BP BP (g5^4[qO ~˕W;w8d#a@4SزMW@U`*5f{2,NK2]-hjs7#`/ܠnî_G6:f z۬@fKBXn)v:7q3ؚ@Y#^z뇍 wN߻Mͬnf-p1fof ܋90z%2Sþ5=mowҮܳ8{q0 .1WaCP BVM5Rb~K㾞na\{ џ^o5@1BrHL</{ƣCm9 9h-P3#G}{99gϡ>QK8vfcx\KQrR!6Rf yγkMQ_ f}6Z>ۘGhAY\ьYk.5МN/aB3جd3 x_8u2uFZ#hk7[xh@XG?ḙnx+NhV1sƘ@]r,k>Mޘ9*Ϧ3{,>fU%>RebP BP ~{ÿVƴ碟kj3/p^U/RUYA mL^4MWWK7>geG,{cOPU i^3w8cBcK3lNB[>- Mӻza-}f) 5K\k:ܵ\%$Zu| /-wAA볐Z+)c{avp?yIl & 5v7[Y-xV3201p>-gv4d>rC{w%G}0S.8,:Va^?VT؏ZYWػj%3BP 2M^y/ ڊxa=(dيx4YThUg׹*۷xdpda n:ipmr򯝞=هқ}"Z#=֬V]u;9_ٳf G_Yofa7s8a`&%]hfwڞUؒ lH |}c=gv>%Ma9T˥·L{E5ng=af6n[,_"uX\0ldnR*d^Aم{S+.iFg|Lҙ=:ki:WM^0M{T@y< +$b ʵV{f NoM Cv`&(9bPEd8/!x@8^O-^Ojfwo9kk_ Y2$aN40u{[F'{lۥ|fӱb~m 8 ; h3S@^{̆DP BP 2f8' >_wҮN_?d;9H> Dgƌx> s'[m#0v?]yo~] Z섽nFg *X7KcpC[5lיexFughP\7Z~I P =( >>u!XˍC0<f2*5zmbm^lsl2ƔpYM[ӵX_u[o>GXi=b} 0:XݭSI{{\WWmDv;̼pc'fåL{`{?曺h@( @( T["T8deH-@}}2NX {a f_f9aU;Yx?5s[d.8m)A]@cBt: L: bf)[JŒfk{Cɼi]RI .%-[lAxpZC0~7㗙ZZttq_w-ϸ.vQ*{Ui6| n6vt6\ʔ1, BP BP`v )&yOwh>?~"1 G˰#hS_4Qzk6rm)zki2Fl<dM_;=.u7#h<:16 B?a~Yk%Q Z-BP B*{+z5"W(и֛MK1CV3vD Z$mq|͟_al66³)`Ћۑ޻sA Z.}$&R_W}'-phNhUKK=<  VFx8 kF#ٸi cL/Tf@/_KC;c㺐Pw2%$k(+`z v4~ܵg'Z hk;wekw?<57غ^-\یac2s`29L7߳r3.eอKSNתMմuUmv1~:Kr̟lh1s@IDATn^J-BP B\)JY'r|Nb)FcصXր "yv=v>v0- @sO.I^uq[kdS;M$ M_cOsB[ŅX(ρ/%]1o'<5l]ͬr4AH+kySxb7qN@krZ/$`7KcBYp=lg`}N u KK}жc`^3'YK;B[&|4&3gkffEonqlcH39[b-,Q_ؒ<#L\f'0-k}*6쵘/1BP BP ȣ!/1߷>t>FMq|1v#V(R` .Ֆޕ֘53_.N'~ja.rṌnA?? 4G6}Qcݑz/^f xf 8mUsYK17 %:YK=sc22-a =?ˁBkANָ+ed(˵z vngM=o3<-[mĐ-25l` en%3fn73yv#Cӯ JU|8Wc<P鮞~_BշeDL,Zy h% 30ךi| /VɹytJCY⏥_"+T{݅Z͝5i{3|ޟ>RK f&]Jo%S>~Q0ڽ[d-1̬u#87pQP(L Q*d5Zfڬ,91`zԹCk}QnĖ0KU 觏 nd'p6sW:/'SpM:.ͦYۭ :l B[er{#OypRwTEjl/Uy;]}1Ƴj_Sv1Vj߃ JN,rwVq{W `޵8pH( ]!Ȩv 1zB,M?|ZjneVGЃpA؉bъ@`]7Zknq|{Vҧc7cC> HSr;Ƴa:F.5lfxBSk/!{WغL`ܬl12u9[32_@!X8kIrl-F8٤?_`6à :0?f7Sja Z ,V7a(N0qUe|쥿xpNKS#,s'W\uϛO>Nn[Ky9, [3~vSl6yɵ0 zd S^Vl_u>[ĺ`skAp 8^D3h27I;0y#g61DzB)wk/c,Vu7k`^On ;o9^KVUϗ+D[?f,q_$~] [/yq>;v7[ZUc*+bيVY\fg*,hgU{fCʽh ׫FXWB]:P~؆:8 L}wP o jl^9s c^]57,|.{ZoPҴεz,h@=Jx櫒&[뒍ڻ!fJ-#1"‹*t8Le9 Ak'fV(d{ȵ®lVo+nh m&`\kc7[J9a9XhlI tBj׵TE>=t&6[ǘQ41αeuOjiPm=ZFP=/]6,nͼ?}_1s1z8ӷB؊y$h@( @( \OYKRS`rkNQbO VJ|{[O'a_ޏewr vMYϴyvv]3|ZYnlg=/ YAl)Fl7u_4+Igt̸׺foc])hsmy:ANcqyz [yA ! 7Ykb7~;ihU TePْcV[Bna&M﫪AշUE T~=yyh%!2uM<}xNͣS@"u h Qɟ<+C#x eL^ ^;g i[wo(@Ӗ^*80,fFtB\KATR [9–qr56MSl9S2wAB^vC' w ۺidΥYo悛E,kZb?],7N?#ƭ%εW?_?ˎ/MX>LCn`ίFfٳVJU4b"uG@( @( SxUL|jK>s?Fp4= ^ٵ~#~8=>.Ğ-b/̚{'v8 1!&愊9mC>x]-u~̟]jfrn,mO&=2\.e qBeGXA8fλI g=8a2]y+` K&N61ƍin@&l > ~$6k:x-T ~}ofWjD7Qo+gnkf IIXBY `(mdWu3pzn Q7yPrEzO?%%ܴ/0_(@yU5PWY_(C1k7`K\~(C݇p5]fY5SDGc[ripqBx%Iq(\ G\!]iK+BgKiC Mvm 8q=ؾc-5 |^?ؒ=7:fK&0' 洟e6g\v.kno WD\E@( @( sW`CϝbΟ!r@Fx~]Q­P`wOkdrt [fvrk&04Y^`B1Z= \p+jن6lDA`v}%-e^! [fy`9]?ݜO:Y L&|1qsvH_wncVpcatq܎1v;@6;{RVMP,VOcVe(OxU @( +~fXsT|RK7ZշۈUE WUH]Ōg_}gCۇ;XfwVܵC 8/Yf gu3X3POx?3. W)ko2ԖЇ1`\J72/<(Yg1%}q3ZYgq[CHn&cMj9!N!йKVL!'Vy ,b~Nh#P BP (f dxs3l\{/̀ eU`}+bݐ.$'_@_'Y%(,03W;̼[uSj 3 ;Ycw3 y3˃_ksL1a)@_󩧸/ za=>|.X/&%Lhe/6aݬge쇐I-`e'/bUljR݇qū)cOacpSS>0BP ʥXV[K]i7ݣŏBP gӵ\BZK 'ScI'U(lFq53Wk\-K9Kh8 q܂k oy h`="8|!#D=Xq7sx3є~j0η(kYsb+חQ<涔kwӍq@IoV2ТUTm>\1ugxbkҌ~Yf1\769_2s-'a 8$WK=nF7|+"cxB_=y v&3@zuKf+ o 븿~ZgY`lֳ1_=~%:1MfL[Bcׁt+Y aڮ%G_U}\mNP c ^DS@( @(0gnbys]W!t,~Ay$;9}2wQN@Rgc=Z(P1$*<9 % 줮?ͶZB l8ݼ7t [d=BL@a/%'6#yl)'6ʱe%,&zc3wM\oNY!b7#ZlFױ%ܯg>v:,gkoG7fl~'o/ nz3TU3ZiغZ 8^_݋"LBP B9+6F?B-$hRr 30>F˙L,~`*WiEc8S=qjAIS/Ғ)^,1amb7s#;kZrk^Λ l^vp@:wq#ܷu(t`~n6?v9ll'MsI:v4q9JFy6`q/f ncuK5lԮ#k)5 0`>ĉK@( @(0't{ǜFV.h= / @!|ÈPU,,x줕;^ @}&wV7fZFB(-6 e(qTHk )LV4~ w8ǘ@[!;4܄5`$3' L!V|k4r?TS[H1ǁ沿5-Cq&,sSneltXumZs'`k` C/3ɫ3QCP BP`N |Qid=#hP@H|QR.ʨFz.F?L K&nM(dfX6n;5lnY Bd e[&cҲb8 Hsߍ:cy w0edC]{sfzUF 5g>,}GV&o;aVmtVfD *1_r×h@( @( Sf^W h.ӎˮ* W{A*a|< Vb) z'_p˙L^~5sW, =\v>ЖKI[7 8g[o+sW^Á<$c[`\̿q"gʹȉi2zNhkޚ9t8̟;7::#6#Y,p~`m M#S IŦu-1ZV_Ml6rKz,;& &mf}NqӽMdc:hh 2)UeYU>/P`F |^F@( @(P"-%N2w/^U7LCOHAXhU7 !<+SѾ6mZRZ36;x ;˟atrßs3G|@p3@@Mp3s o޲s>3[Yf%1ZXb7~=9]ykGc{ Q_ބ#+PQڵE%& BP BP` |KA{|)7Z^2C?jpt \}Ȝt{ڇoq/M٢]-鮴-Ϧh~9,]9mv*EP BP 8ǮUþMs_Z!+A!fHqso^AT/l×Ȧrg$ͭ' m&ti0/D5HGfEqYf))I<~M⭜X9D5[X6(]aY%Wߟ<]e X]p2G?¾yM#?}Y e.$>U,ڲ@( @($9x'H|JN`,Y{пT& {A$ `\y,MҶ'Sc<ڏxゥ()^uCP`e5`qBP BWĿF5OZj7؋RWc\D'yv7v Z(ЗhTB?,ET0QhiNlGrnJxsa4S^wXI^{IGs|3nس?ï^;2F+G0'oʘ)C Y+J,<5yM|3QxYO |]_禭"sMy(KTdjorYT`X\gK#ogfB7qrFm۰bX\<y ̱DZʼn,Wz|F׿?f۟v{Kֆ'SuI[Gk2@5r&v oL%['67mkO7m yWHxءYu0`wzdT7ord%hxr,V)%\/`ߢ1%9S_s&M{oԤ3 tSg}ƽm3]_Uf.<%X0>Lr{;g5*:[Ͻ 7UIhcuBR7ٗ?-\4.Zܷ.FS@ҜnY!_{_-޽ ˽(=zBcXỶc*ފ)E?$ ހ<0o½3\r\͂{x'psY ugG1Wvaת͔-15%S>ſXIrsS r^xWlL_ԭ;8k2iln,-'׽|buB] :7aաEm`=Xq&!K E5>P6'ztJ@Mdu|+ sg3yV\v6k{&"@O<?y{LD)qL'݌n!a>wϕ=wg볘_JG"K.E *YĮvaYlT3\ Xf@kUYpמj?XgQ[Cf&l6ZVk__Y\q,TzoaV|`Xݛq\p-ȬhQoޅÞ/-[ 1mro(BP BP (A7ݩv6?VKܷs^8 T[=[=+WV~ lfJ94,Zv˦bcgb`B*M%_adK,ZdaNƲx,A @( QjZ-Fp->ØNcށ]U+܂v v֏-*eXo k \y_ %uGc;1!+h@( H m09DZjDfe(\2~ /nhQbbjڏʬ_5,j\:g? DRGF>XVJ|)E̕bVμu=ɥg'<ŝ$uu|kmnڛ?!xBP BP BP W B4sZG,̅sꤾ9i{qc stZ`r4nq8~)X ,/$:@U+p>BP BP BP g <"gLz n):OC%MnlG:qe(&' MmW^Tth@( L3)z@( @( @( Pr,R52Y3˽,L4ƓɆ N1"P 8`1A( @( @( @(P}LQf$Nyosv[H7`sPώB2PX}]p|=M(Pr}BP BP BP BP Ȁ]..1ߟJNL1j7s:< G@( _,޿F#BP BP BP (+"X YKcGN&Sd7xZInm! Bb*j\@( @( @( ?dڻvT IۑY\桘qIr/:p>*@@( @( @( Rps3Wٔ ?*A1kF14niHzwߛaQTЀAM@,%!e4TFRX )5)+&嘥H+$hF=AqHXT060 ̼y[/0SL!{u=}3|C&գH<Q_Tf$@$%9$@ @`ff;_OEL|ǫ/bX܃xAڰ*5Jх"-6q!q6rn(уUMI@,.Y# @&pm;n{0oѦs @ 0x _]l<[/~G+"s|-OMFLm(f`\ilD @( @,FL\2-2^:/o*!IfUGxMVvQ֩ŭ;ΐl6Q=qM @H E>A{GnT671>~(fV,izۦ#L{^w$@D%:R%@ @`hreqШFN}xӓu=>XH<Ԉؿ3O%>]P,) @ vn3_?nT7߃}g"D/^pkeCw"nh+sqUq'=O(@T*m @F@c֭Fx'/X>9<ѭkADvsVEikuDggX\IOo/GqnLW@x^/ @ @|YX_x𜖞䂞^_~R3O#]q-;JH"ez ` @ @50ut,[0: p}c#^0=sb`i@L?IDATֺ͝4ی^QΙUŵÃl @`q,^  @ P =x=9bb(𽈲gXu;!U4JD3ٷF@YzM @/oq>4"](89r"b3|Sc~-qUq%qUx=ϥZ}P,^4  @ `\{a\0>dtSxܝ}0"(v]_|'_y~i֭i68ssٹW\Mfo+ݻq+ (N @ @.]9f~"E+ւ"N|DžÒ'o]%э"&Ӂ5<u{= P^2g[ @ ͱmS}O.7"E}ZD_/⹷޾2ݭO}$UF-&WWݤaP,ƳbO @I2%;OkG#QQKzDae*3@/r?u{-헤Z;ɔm2-'5{ @@Fi" @,H ~sAGJGD\gOֱOl=G'F0Hm+jrN;"GU\ũOi2qۦA(լ @X@պB'p cQ4girT]u(ELt|sn)5 詀bqOyMN @ \G#Df_ƍ@ehY:Zj @Ž57 @.Wq ?ܑ.pwڈhkJЛ?Hc5ib H_풣STѳ8ZK3/7 X@x' @%*E_vWk2s[}湩$.%(W"!iŏrROX@n= }j[4_*yWbqn3V}O۰ %P,.7  @9b'Dl(\|i}"rպwZjQ"qF==$7 0[ @]:f;:j |1^>VIS'R=gNbO$@|;2&@ @?4^qm1ґŀ>ǹQs)ws]VK"i"X<v @ @+UK#.l&)""Ji B^YTO)48чH{϶y.ĞI@9yeM @@q򯧿*$> L:oXߧ,38EԊz}=iIYv:ӝA(* @@TҟD~z #pyd6⣅hDY_Yߌ}`LU&nTr @@l @Cka_bI|%zK"rkco|)+-(WXJfֻ[n,O@Kv¥K @@6F-V#pI|80!,qQi^Ketz5m<{  (Gx @Dd |Hb1Fqb&rlwNצIiktFkjiqsm͇6zS蟀bqD @~ "Nߋ[o(.]<7_"Ucl#V(wDhZReLaګ PrgY @ Pn"#T?pt-NT<Ev3Hթ+hfeb(={'@ @ȗ,qP#f" \)6/etH[4g螑_O6賀bq-G @ <yij"Ίl4R81⨈/D以C-d?ZOܻ5tzJ+>޾ PHBVI @ @q~xGP QYLcӊtDz0j(䫊gt+H;N0 _z[ @&@lψXqS1qtnġcSIZMHE3Q0ԜJڭdSUc}S>[ @# p\1픈#2l1Rya#5Ox{1i!8roXoQx/Qܽ= @ @@/䯎ȅ"" ߍ/G(B?<4W:`tdK @ 0{O-r3_*o[L|3Ԉ}"  @ @ ^7D|*bC ˆu#  0m(nQ @F w؈0We(vDCÈ T@x'@ @@#ϏC"jewEҹ0h @P,sbG @(@QޑprFZ8鎸9w^9|_<7 @H(iI @R`Edu."r_UՈAv{;73 @H (y @Zy.4ȹŮ>r/o$z*8 @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @(]pHIENDB`golang-github-onsi-ginkgo-v2-2.22.0/docs/images/github.png000066400000000000000000000044321472321612100232620ustar00rootroot00000000000000PNG  IHDR@@iqtEXtSoftwareAdobe ImageReadyqe<$iTXtXML:com.adobe.xmp *NeCIDATxohUulY۪YmֵZz9!)ai0j %" "ϋ^+P ͡3ˢ5sV}s9wϋ<߿755"*$j}6P n2`\gG\^$SE, [% P^mஈ./s aff8>dQФlSgCnw@= PfIXGl ^/SI( l^ca 0| P` aQ>h+ 0x+QdhP!OG7@p,@{-iާ9G3~FQ)%mgKvC|?DZ~ ~OV1?7;yemb1 -1A]=`4lqߺV3t"@cg2N[cY rVm4" zBinu:'nUŶ*?ﵔF.t`B̫UI۟R$>a]Nd+y]ǃtz|rVv!G,[ |c*䱿}Gx? Y>uvN`ƭGK$;qkfAFn <8ٶ0!1xdipo;%!?±86WrDf^q8C81qڳ({0hï#.  #EN5!. 3*-\. oJiOfs}JvfN%YG5LGo]J: xMn#:ا TOZ&]/si}Ў!lhԡkW2 ~mU^vvOmz0}XYB_ -Bs_IENDB`golang-github-onsi-ginkgo-v2-2.22.0/docs/images/go.png000066400000000000000000000124311472321612100224030ustar00rootroot00000000000000PNG  IHDRd!ZdrsRGBeXIfMM*V^(1fi|,,Pixelmator Pro 2.1.5Ϡd2021:10:14 11:28:55[ pHYs.#.#x?viTXtXML:com.adobe.xmp 207 100 3000000/10000 3000000/10000 1 2 Pixelmator Pro 2.1.5 2021-10-14T11:28:55-06:00 2021-10-14T11:31:18-06:00 ʸ,IDATx E{bKEiJ)[SQ-RCOVB[(AP{Ҋ% IŖDd|߼;3ウ7yܹs|+1p 8ǀc1p 8ǀc1p 8ǀc1p 8ǀc1@;Kz 455Fu*` X kn1G'eu*S{` >C߫%ςT 8BmM ,`!p\iR(Y%_.Za|ě$T:E8#2C?<f jNuYY ?2LVq i{$ev@qZ Ht1 y?jo" whߥ<4Nq(ʬWa8J?Z4; *c' f$DNI´ku[m]^&a_d_]HZԤ8΄2w5m6eM THى+-#w^-?i…KZh+sg-hf%)Yǀ^.j– @u h rw?<*)?G2GWi@ך&Z;L5+ )-hQ}Țǡ᫂tXNz %]^E[+<[gn]*!G+R<, l[ M7sx5?„8&E;,/o۲Gtڡj[M><]̱q qn`a Qah%F]@\q@~ ̦8-1c op%}s3pXE;wC~{:ֿ֋ s5|ƥz|#Au_kwuF?Ъgz{`<>_rE8܂/ϸPP:^ <߇Г/=gu [Sw Fi1%huΉ ȢՒs[2š'; ){:F-y lػRa4$Oj4jFmKuc7vۃw U'o{X 6 s/y=4i>r_dw{`ɫp5UbS܆]܊U(kMaՒNUژ\0;G0SǙh.p3ޒj,u 8ZӒ#zIWǫ>owKoKLFoL'>l|| 8A9hI5\-;Yz%GΠNZE%C(O/ AĮy\7y0CzjԆk~Gc9\?L%G-4}uu6'jy4wᠠָ:}ɫ+yq]3zS y=&;Ϛʫ>A񃆜̢5T2>ҵbBoqަlb֭:\ S 'u4-c F3YPU;u5LrY܌9ɗQk*t0m2-oseG)G%*zM,qZM<=jōNǸrq܌97ЛfҰtɆ\Ͽ?@S&e֪jj[C nrFͰ_MfFMwQŨ7YL}̗l33,3g[U`g߹/FxhȰ( ;5]h8\饠=zA0OQ+2f'$Lw*Pt8rg]3JYxl@q0Mxwj] }Mtu/,P37CG ~dQtJ'I Ɵ{?_qKʅ害:.o z]:yb?CnTJ:i? ~kf:yJiǰQ͉!DI'CU .w4ВMADCU3mY3+&BuRϓv)4 dJ=?Dg^)22, .mJBD#n"$Ph"bxR~֑c.z1MgCIiaK #rF*+vw%"e OJjvC}MBYv`z(bF[ʃ‘xu F~0{CA5+TniŭaCZa[2 RZ/v'L82w(НGDD-cmv tkՁ45 {s*8」|0dS`b{ۢo^m '![Pөdl`ѫqsϖӓvKubէw6燙 =]>6N+_Q| t?\V5ď+`.-_BY' ZB.ZMC݅4-u|@>%!fbey? b芥Nl,_&?|Ԕw`w$HRǘ.@ 7s.\/U_x~Ks;?@A$$G`>ЂE͜ml9BmUYcpAa^Ŷ?KAgH 0=ˈʺ!{VJd:b;qm-韉^4OZ뇟 nr_C NnEFS:N?㸤)45;TXeSi]S-DZU uTR̺/ź40z^O{5$nfr> GQɪlO @e(.՝GV&]{):H K4x8hpҺ GzKJy8<h"(r%S!C_b|t*#+Nkof^Xy bii)雈PqR/fi ZȣӺX䑜Z7SPs-5b;[@^d^Yr-r}M&pL32B&_ f'+Y9*=H nYk$vC,Ȣ©@^,VOlE!Ӕ1>dj̎?@d Nh\$+ P!`>HJbh8-+<w( >=|榪#K%ic p$t&ET0&?`Cя=;l>Fq< PNCј7]AWaE/g9 !>tΠ"M/~.>1胈3iash8FF+ZtgڲvJ8 ėǀc1p 8ǀc1p 8ǀc1p 8ǀc1p 8ǀc\  sIENDB`golang-github-onsi-ginkgo-v2-2.22.0/docs/images/sponsor.png000066400000000000000000000052551472321612100235070ustar00rootroot00000000000000PNG  IHDR@@iqsRGBeXIfMM*V^(1fi|HHPixelmator Pro 2.1.5@@2021:10:14 11:22:28W pHYs  iTXtXML:com.adobe.xmp 2 720000/10000 1 720000/10000 64 64 Pixelmator Pro 2.1.5 2021-10-14T11:22:28-06:00 2021-10-14T11:31:52-06:00 s{IDATx[UycEeie&qLd'JQPDA*0,"aZD 3B-et~Q/v=kͷOf|.gk3dHG:tӁqjyk.=@A}q ցZ[fW`2Fa` Z* _gэK Da 讬/[AQxRBA !@Y"fTx)[ Rs:QV:@ކtpn{἖{[>ng(~(*^7v$G^wpKeWiǂU1I`&͡Mm!̄}f_h)pZAM3܈46vZ@5i bv ZEi@#E`M*ZA*O? ,"A,"}ʉiCv1.J; >oxdb2&5fs4,: By default, Go does not allow you to invoke bare functions at the top-level of a file. Ginkgo gets around this by having its node DSL functions return a value that is intended to be discarded. This allows us to write `var _ = Describe(...)` at the top-level which satisfies Go's top-level policies. Let's add a few specs, now, to describe our book model's ability to categorize books: ```go var _ = Describe("Books", func() { var foxInSocks, lesMis *books.Book BeforeEach(func() { lesMis = &books.Book{ Title: "Les Miserables", Author: "Victor Hugo", Pages: 2783, } foxInSocks = &books.Book{ Title: "Fox In Socks", Author: "Dr. Seuss", Pages: 24, } }) Describe("Categorizing books", func() { Context("with more than 300 pages", func() { It("should be a novel", func() { Expect(lesMis.Category()).To(Equal(books.CategoryNovel)) }) }) Context("with fewer than 300 pages", func() { It("should be a short story", func() { Expect(foxInSocks.Category()).To(Equal(books.CategoryShortStory)) }) }) }) }) ``` There's a lot going on here so let's break it down. Ginkgo makes extensive use of closures to allow us to build a descriptive spec hierarchy. This hierarchy is constructed using three kinds of **nodes**: We use **container nodes** like `Describe` and `Context` to organize the different aspects of code that we are testing hierarchically. In this case we are describing our book model's ability to categorize books across two different contexts - the behavior for large books `"With more than 300 pages"` and small books `"With fewer than 300 pages"`. We use **setup nodes** like `BeforeEach` to set up the state of our specs. In this case, we are instantiating two new book models: `lesMis` and `foxInSocks`. Finally, we use **subject nodes** like `It` to write a spec that makes assertions about the subject under test. In this case, we are ensuring that `book.Category()` returns the correct category `enum` based on the length of the book. We make these assertions using Gomega's `Equal` matcher and `Expect` syntax. You can learn much more about [Gomega here](https://onsi.github.io/gomega/#making-assertions) - the examples used throughout these docs should be self-explanatory. The container, setup, and subject nodes form a **spec tree**. Ginkgo uses the tree to construct a flattened list of specs where each spec can have multiple setup nodes but will only have one subject node. Because there are two subject nodes, Ginkgo will identify two specs to run. For each spec, Ginkgo will run the closures attached to any associated setup nodes and then run the closure attached to the subject node. In order to share state between the setup node and subject node we define closure variables like `lesMis` and `foxInSocks`. This is a common pattern and the main way that tests are organized in Ginkgo. Assuming a `book.Book` model with this behavior we can run the tests: ```bash ginkgo Running Suite: Books Suite - path/to/books ========================================================== Random Seed: 1634748172 Will run 2 of 2 specs •• Ran 2 of 2 Specs in 0.000 seconds SUCCESS! -- 2 Passed | 0 Failed | 0 Pending | 0 Skipped PASS Ginkgo ran 1 suite in Xs Test Suite Passed ``` Success! We've written and run our first Ginkgo suite. From here we can grow our test suite as we iterate on our code. The next sections will delve into the many mechanisms Ginkgo provides for writing and running specs. ## Writing Specs Ginkgo makes it easy to write expressive specs that describe the behavior of your code in an organized manner. We've seen that Ginkgo suites are hierarchical collections of specs comprised of container nodes, setup nodes, and subject nodes organized into a spec tree. In this section we dig into the various kinds of nodes available in Ginkgo and their properties. ### Spec Subjects: It Every Ginkgo spec has exactly one subject node. You can add a single spec to a suite by adding a new subject node using `It(, )`. Here's a spec to validate that we can extract the author's last name from a `Book` model: ```go var _ = Describe("Books", func() { It("can extract the author's last name", func() { book = &books.Book{ Title: "Les Miserables", Author: "Victor Hugo", Pages: 2783, } Expect(book.AuthorLastName()).To(Equal("Hugo")) }) }) ``` As you can see, the description documents the intent of the spec while the closure includes assertions about our code's behavior. We can add multiple specs to a `Describe` container: ```go var _ = Describe("Books", func() { It("can extract the author's last name", func() { book = &books.Book{ Title: "Les Miserables", Author: "Victor Hugo", Pages: 2783, } Expect(book.AuthorLastName()).To(Equal("Hugo")) }) It("can fetch a summary of the book from the library service", func(ctx SpecContext) { book = &books.Book{ Title: "Les Miserables", Author: "Victor Hugo", Pages: 2783, } summary, err := library.FetchSummary(ctx, book) Expect(err).NotTo(HaveOccurred()) Expect(summary).To(ContainSubstring("Jean Valjean")) }, SpecTimeout(time.Second)) }) ``` Our new spec connects with a library service to fetch a summary of the book and asserts that the request succeeds with a meaningful response. This example previews a few advanced concepts that you'll learn about later in these docs: Ginkgo supports [decorators](#mental-model-spec-decorators) like [SpecTimeout](#the-spectimeout-and-nodetimeout-decorators) to annotate and modify the behavior of specs; and Ginkgo allows you to test potentially long-running code by writing [interruptible](#spec-timeouts-and-interruptible-nodes) specs that accept a `SpecContext` or `context.Context`. Now, if more than a second elapses, _or_ an interrupt signal is received, Ginkgo will signal `library.FetchSummary` to clean up by cancelling `ctx`. Ginkgo provides an alias for `It` called `Specify`. `Specify` is functionally identical to `It` but can help your specs read more naturally. ### Extracting Common Setup: BeforeEach You can remove duplication and share common setup across specs using `BeforeEach()` setup nodes. Let's add specs to our `Book` suite that cover extracting the author's first name and a few natural edge cases: ```go var _ = Describe("Books", func() { var book *books.Book BeforeEach(func() { book = &books.Book{ Title: "Les Miserables", Author: "Victor Hugo", Pages: 2783, } Expect(book.IsValid()).To(BeTrue()) }) It("can extract the author's last name", func() { Expect(book.AuthorLastName()).To(Equal("Hugo")) }) It("interprets a single author name as a last name", func() { book.Author = "Hugo" Expect(book.AuthorLastName()).To(Equal("Hugo")) }) It("can extract the author's first name", func() { Expect(book.AuthorFirstName()).To(Equal("Victor")) }) It("returns no first name when there is a single author name", func() { book.Author = "Hugo" Expect(book.AuthorFirstName()).To(BeZero()) //BeZero asserts the value is the zero-value for its type. In this case: "" }) }) ``` We now have four subject nodes so Ginkgo will run four specs. The common setup for each spec is captured in the `BeforeEach` node. When running each spec Ginkgo will first run the `BeforeEach` closure and then the subject closure. Information is shared between closures via closure variables. It is idiomatic for these closure variables to be declared within the container node closure and initialized in the setup node closure. Doing so ensures that each spec has a pristine, correctly initialized, copy of the shared variable. In this example, the `single author name` specs _mutate_ the shared `book` closure variable. These mutations do not pollute the other specs because the `BeforeEach` closure reinitializes `book`. This detail is really important. Ginkgo requires, by default, that specs be fully **independent**. This allows Ginkgo to shuffle the order of specs and run specs in parallel. We'll cover this in more detail later on but for now embrace this takeaway: **"Declare in container nodes, initialize in setup nodes"**. One last point - Ginkgo allows assertions to be made in both setup nodes and subject nodes. In fact, it's a common pattern to make assertions in setup nodes to validate that the spec setup is correct _before_ making behavioral assertions in subject nodes. In our (admittedly contrived) example here, we are asserting that the `book` we've instantiated is valid with `Expect(book.IsValid()).To(BeTrue())`. ### Organizing Specs With Container Nodes Ginkgo allows you to hierarchically organize the specs in your suite using container nodes. Ginkgo provides three synonymous nouns for creating container nodes: `Describe`, `Context`, and `When`. These three are functionally identical and are provided to help the spec narrative flow. You usually `Describe` different capabilities of your code and explore the behavior of each capability across different `Context`s. Our `book` suite is getting longer and would benefit from some hierarchical organization. Let's organize what we have so far using container nodes: ```go var _ = Describe("Books", func() { var book *books.Book BeforeEach(func() { book = &books.Book{ Title: "Les Miserables", Author: "Victor Hugo", Pages: 2783, } Expect(book.IsValid()).To(BeTrue()) }) Describe("Extracting the author's first and last name", func() { Context("When the author has both names", func() { It("can extract the author's last name", func() { Expect(book.AuthorLastName()).To(Equal("Hugo")) }) It("can extract the author's first name", func() { Expect(book.AuthorFirstName()).To(Equal("Victor")) }) }) Context("When the author only has one name", func() { BeforeEach(func() { book.Author = "Hugo" }) It("interprets the single author name as a last name", func() { Expect(book.AuthorLastName()).To(Equal("Hugo")) }) It("returns empty for the first name", func() { Expect(book.AuthorFirstName()).To(BeZero()) }) }) }) }) ``` Using container nodes helps clarify the intent behind our suite. The author name specs are now clearly grouped together and we're exploring the behavior of our code in different contexts. Most importantly, we're able to scope additional setup nodes to those contexts to refine our spec setup. When Ginkgo runs a spec it runs through all the `BeforeEach` closures that appear in that spec's hierarchy from the outer-most to the inner-most. If multiple `BeforeEach` nodes appear at the same nesting level they will be run in the order in which they appear in the test file. For the `both names` specs, Ginkgo will run the outermost `BeforeEach` closure before the subject node closure. For the `one name` specs, Ginkgo will run the outermost `BeforeEach` closure and then the innermost `BeforeEach` closure which sets `book.Author = "Hugo"`. Organizing our specs in this way can also help us reason about our spec coverage. What additional contexts are we missing? What edge cases should we worry about? Let's add a few: ```go var _ = Describe("Books", func() { var book *books.Book BeforeEach(func() { book = &books.Book{ Title: "Les Miserables", Author: "Victor Hugo", Pages: 2783, } Expect(book.IsValid()).To(BeTrue()) }) Describe("Extracting the author's first and last name", func() { Context("When the author has both names", func() { It("can extract the author's last name", func() { Expect(book.AuthorLastName()).To(Equal("Hugo")) }) It("can extract the author's first name", func() { Expect(book.AuthorFirstName()).To(Equal("Victor")) }) }) Context("When the author only has one name", func() { BeforeEach(func() { book.Author = "Hugo" }) It("interprets the single author name as a last name", func() { Expect(book.AuthorLastName()).To(Equal("Hugo")) }) It("returns empty for the first name", func() { Expect(book.AuthorFirstName()).To(BeZero()) }) }) Context("When the author has a middle name", func() { BeforeEach(func() { book.Author = "Victor Marie Hugo" }) It("can extract the author's last name", func() { Expect(book.AuthorLastName()).To(Equal("Hugo")) }) It("can extract the author's first name", func() { Expect(book.AuthorFirstName()).To(Equal("Victor")) }) }) Context("When the author has no name", func() { It("should not be a valid book and returns empty for first and last name", func() { book.Author = "" Expect(book.IsValid()).To(BeFalse()) Expect(book.AuthorLastName()).To(BeZero()) Expect(book.AuthorFirstName()).To(BeZero()) }) }) }) }) ``` That should cover most edge cases. As you can see we have flexibility in how we structure our specs. Some developers prefer single assertions in `It` nodes where possible. Others prefer consolidating multiple assertions into a single `It` as we do in the `no name` context. Both approaches are supported and perfectly reasonable. Let's keep going and add spec out some additional behavior. Let's test how our `book` model handles JSON encoding/decoding. Since we're describing new behavior we'll add a new `Describe` container node: ```go var _ = Describe("Books", func() { var book *books.Book BeforeEach(func() { book = &books.Book{ Title: "Les Miserables", Author: "Victor Hugo", Pages: 2783, } Expect(book.IsValid()).To(BeTrue()) }) Describe("Extracting the author's first and last name", func() { ... }) Describe("JSON encoding and decoding", func() { It("survives the round trip", func() { encoded, err := book.AsJSON() Expect(err).NotTo(HaveOccurred()) decoded, err := books.NewBookFromJSON(encoded) Expect(err).NotTo(HaveOccurred()) Expect(decoded).To(Equal(book)) }) Describe("some JSON decoding edge cases", func() { var err error When("the JSON fails to parse", func() { BeforeEach(func() { book, err = NewBookFromJSON(`{ "title":"Les Miserables", "author":"Victor Hugo", "pages":2783oops }`) }) It("returns a nil book", func() { Expect(book).To(BeNil()) }) It("errors", func() { Expect(err).To(MatchError(books.ErrInvalidJSON)) }) }) When("the JSON is incomplete", func() { BeforeEach(func() { book, err = NewBookFromJSON(`{ "title":"Les Miserables", "author":"Victor Hugo" }`) }) It("returns a nil book", func() { Expect(book).To(BeNil()) }) It("errors", func() { Expect(err).To(MatchError(books.ErrIncompleteJSON)) }) }) }) }) }) ``` In this way we can continue to grow our suite while clearly delineating the structure of our specs using a spec tree hierarchy. Note that we use the `When` container variant in this example as it reads cleanly. Remember that `Describe`, `Context`, and `When` are functionally equivalent aliases. ### Mental Model: How Ginkgo Traverses the Spec Hierarchy We've delved into the three basic Ginkgo node types: container nodes, setup nodes, and subject nodes. Before we move on let's build a mental model for how Ginkgo traverses and runs specs in a little more detail. When Ginkgo runs a suite it does so in _two phases_. The **Tree Construction Phase** followed by the **Run Phase**. During the Tree Construction Phase Ginkgo enters all container nodes by invoking their closures to construct the spec tree. During this phase Ginkgo is capturing and saving off the various setup and subject node closures it encounters in the tree _without running them_. Only container node closures run during this phase and Ginkgo does not expect to encounter any assertions as no specs are running yet. Let's paint a picture of what that looks like in practice. Consider the following set of book specs: ```go var _ = Describe("Books", func() { var book *books.Book BeforeEach(func() { //Closure A book = &books.Book{ Title: "Les Miserables", Author: "Victor Hugo", Pages: 2783, } Expect(book.IsValid()).To(BeTrue()) }) Describe("Extracting names", func() { When("author has both names", func() { It("extracts the last name", func() { //Closure B Expect(book.AuthorLastName()).To(Equal("Hugo")) }) It("extracts the first name", func() { //Closure C Expect(book.AuthorFirstName()).To(Equal("Victor")) }) }) When("author has one name", func() { BeforeEach(func() { //Closure D book.Author = "Hugo" }) It("extracts the last name", func() { //Closure E Expect(book.AuthorLastName()).To(Equal("Hugo")) }) It("returns empty first name", func() { //Closure F Expect(book.AuthorFirstName()).To(BeZero()) }) }) }) }) ``` We could represent the spec tree that Ginkgo generates as follows: ``` Describe: "Books" |_BeforeEach: |_Describe: "Extracting names" |_When: "author has both names" |_It: "extracts the last name", |_It: "extracts the first name", |_When: "author has one name" |_BeforeEach: |_It: "extracts the last name", |_It: "returns empty first name", ``` Note that Ginkgo is saving off just the setup and subject node closures. Once the spec tree is constructed Ginkgo walks the tree to generate a flattened list of specs. For our example, the resulting spec list would look a bit like: ``` { Texts: ["Books", "Extracting names", "author has both names", "extracts the last name"], Closures: , }, { Texts: ["Books", "Extracting names", "author has both names", "extracts the first name"], Closures: , }, { Texts: ["Books", "Extracting names", "author has one name", "extracts the last name"], Closures: , , }, { Texts: ["Books", "Extracting names", "author has one name", "returns empty first name"], Closures: , , } ``` As you can see each generated spec has exactly one subject node, and the appropriate set of setup nodes. During the Run Phase Ginkgo runs through each spec in the spec list sequentially. When running a spec Ginkgo invokes the setup and subject nodes closures in the correct order and tracks any failed assertions. Note that container node closures are _never_ invoked during the run phase. Given this mental model, here are a few common gotchas to avoid: #### Nodes only belong in Container Nodes Since the spec tree is constructed by traversing container nodes all Ginkgo nodes **must** only appear at the top-level of the suite _or_ nested within a container node. They cannot appear within a subject node or setup node. The following is invalid: ```go /* === INVALID === */ var _ = It("has a color", func() { Context("when blue", func() { // NO! Nodes can only be nested in containers It("is blue", func() { // NO! Nodes can only be nested in containers }) }) }) ``` Ginkgo will emit a warning if it detects this. #### No Assertions in Container Nodes Because container nodes are invoked to construct the spec tree, but never when running specs, assertions _must_ be in subject nodes or setup nodes. Never in container nodes. The following is invalid: ```go /* === INVALID === */ var _ = Describe("book", func() { var book *Book Expect(book.Title()).To(BeFalse()) // NO! Place in a setup node instead. It("tests something", func() {...}) }) ``` Ginkgo will emit a warning if it detects this. #### Avoid Spec Pollution: Don't Initialize Variables in Container Nodes We've covered this already but it bears repeating: **"Declare in container nodes, initialize in setup nodes"**. Since container nodes are only invoked once during the tree construction phase you should declare closure variables in container nodes but always initialize them in setup nodes. The following is invalid can potentially infuriating to debug: ```go /* === INVALID === */ var _ = Describe("book", func() { book := &books.Book{ // No! Title: "Les Miserables", Author: "Victor Hugo", Pages: 2783, } It("is invalid with no author", func() { book.Author = "" // bam! we've changed the closure variable and it will never be reset. Expect(book.IsValid()).To(BeFalse()) }) It("is valid with an author", func() { Expect(book.IsValid()).To(BeTrue()) // this will fail if it runs after the previous test }) }) ``` you should do this instead: ```go var _ = Describe("book", func() { var book *books.Book // declare in container nodes BeforeEach(func() { book = &books.Book { //initialize in setup nodes Title: "Les Miserables", Author: "Victor Hugo", Pages: 2783, } }) It("is invalid with no author", func() { book.Author = "" Expect(book.IsValid()).To(BeFalse()) }) It("is valid with an author", func() { Expect(book.IsValid()).To(BeTrue()) }) }) ``` Ginkgo currently has no mechanism in place to detect this failure mode, you'll need to stick to "declare in container nodes, initialize in setup nodes" to avoid spec pollution. ### Separating Creation and Configuration: JustBeforeEach Let's get back to our growing Book suite and explore a few more Ginkgo nodes. So far we've met the `BeforeEach` setup node, let's introduce its closely related cousin: `JustBeforeEach`. `JustBeforeEach` is intended to solve a very specific problem but should be used with care as it can add complexity to a test suite. Consider the following section of our JSON decoding book tests: ```go Describe("some JSON decoding edge cases", func() { var book *books.Book var err error When("the JSON fails to parse", func() { BeforeEach(func() { book, err = NewBookFromJSON(`{ "title":"Les Miserables", "author":"Victor Hugo", "pages":2783oops }`) }) It("returns a nil book", func() { Expect(book).To(BeNil()) }) It("errors", func() { Expect(err).To(MatchError(books.ErrInvalidJSON)) }) }) When("the JSON is incomplete", func() { BeforeEach(func() { book, err = NewBookFromJSON(`{ "title":"Les Miserables", "author":"Victor Hugo" }`) }) It("returns a nil book", func() { Expect(book).To(BeNil()) }) It("errors", func() { Expect(err).To(MatchError(books.ErrIncompleteJSON)) }) }) }) ``` In each case we're creating a new `book` from an invalid snippet of JSON, ensuring the `book` is `nil` and checking that the correct error was returned. There's some degree of deduplication that could be attained here. We could try to pull out a shared `BeforeEach` like so: ```go /* === INVALID === */ Describe("some JSON decoding edge cases", func() { var book *books.Book var err error BeforeEach(func() { book, err = NewBookFromJSON(???) Expect(book).To(BeNil()) }) When("the JSON fails to parse", func() { It("errors", func() { Expect(err).To(MatchError(books.ErrInvalidJSON)) }) }) When("the JSON is incomplete", func() { It("errors", func() { Expect(err).To(MatchError(books.ErrIncompleteJSON)) }) }) }) ``` but there's no way using `BeforeEach` and `It` nodes to configure the json we use to create the book differently for each `When` container _before_ we invoke `NewBookFromJSON`. That's where `JustBeforeEach` comes in. As the name suggests, `JustBeforeEach` nodes run _just before_ the subject node but _after_ any other `BeforeEach` nodes. We can leverage this behavior to write: ```go Describe("some JSON decoding edge cases", func() { var book *books.Book var err error var json string JustBeforeEach(func() { book, err = NewBookFromJSON(json) Expect(book).To(BeNil()) }) When("the JSON fails to parse", func() { BeforeEach(func() { json = `{ "title":"Les Miserables", "author":"Victor Hugo", "pages":2783oops }` }) It("errors", func() { Expect(err).To(MatchError(books.ErrInvalidJSON)) }) }) When("the JSON is incomplete", func() { BeforeEach(func() { json = `{ "title":"Les Miserables", "author":"Victor Hugo" }` }) It("errors", func() { Expect(err).To(MatchError(books.ErrIncompleteJSON)) }) }) }) ``` When Ginkgo runs these specs it will _first_ run the `BeforeEach` setup closures, thereby population the `json` variable, and _then_ run the `JustBeforeEach` setup closure, thereby decoding the correct JSON string. Abstractly, `JustBeforeEach` allows you to decouple **creation** from **configuration**. Creation occurs in the `JustBeforeEach` using configuration specified and modified by a chain of `BeforeEach`s. As with `BeforeEach` you can have multiple `JustBeforeEach` nodes at different levels of container nesting. Ginkgo will first run all the `BeforeEach` closures from the outside in, then all the `JustBeforeEach` closures from the outside in. While powerful and flexible overuse of `JustBeforeEach` (and nest `JustBeforeEach`es in particular!) can lead to confusing suites to be sure to use `JustBeforeEach` judiciously! ### Spec Cleanup: AfterEach and DeferCleanup The setup nodes we've seen so far all run _before_ the spec's subject closure. Ginkgo also provides setup nodes that run _after_ the spec's subject: `AfterEach` and `JustAfterEach`. These are used to clean up after specs and can be particularly helpful in complex integration suites where some external system must be restored to its original state after each spec. Here's a simple (if contrived!) example to get us started. Let's suspend disbelief and imagine that our `book` model tracks the weight of books... and that the units used to display the weight can be specified with an environment variable. Let's spec this out: ```go Describe("Reporting book weight", func() { var book *books.Book BeforeEach(func() { book = &books.Book{ Title: "Les Miserables", Author: "Victor Hugo", Pages: 2783, Weight: 500, } }) Context("with no WEIGHT_UNITS environment set", func() { BeforeEach(func() { err := os.Unsetenv("WEIGHT_UNITS") Expect(err).NotTo(HaveOccurred()) }) It("reports the weight in grams", func() { Expect(book.HumanReadableWeight()).To(Equal("500g")) }) }) Context("when WEIGHT_UNITS is set to oz", func() { BeforeEach(func() { err := os.Setenv("WEIGHT_UNITS", "oz") Expect(err).NotTo(HaveOccurred()) }) It("reports the weight in ounces", func() { Expect(book.HumanReadableWeight()).To(Equal("17.6oz")) }) }) Context("when WEIGHT_UNITS is invalid", func() { BeforeEach(func() { err := os.Setenv("WEIGHT_UNITS", "smoots") Expect(err).NotTo(HaveOccurred()) }) It("errors", func() { weight, err := book.HumanReadableWeight() Expect(weight).To(BeZero()) Expect(err).To(HaveOccurred()) }) }) }) ``` These specs are... _OK_. But we've got a subtle issue: we're not cleaning up when we override the value of `WEIGHT_UNITS`. This is an example of spec pollution and can lead to subtle failures in unrelated specs. Let's fix this up using an `AfterEach`: ```go Describe("Reporting book weight", func() { var book *books.Book BeforeEach(func() { book = &books.Book{ Title: "Les Miserables", Author: "Victor Hugo", Pages: 2783, Weight: 500, } }) AfterEach(func() { err := os.Unsetenv("WEIGHT_UNITS") Expect(err).NotTo(HaveOccurred()) }) Context("with no WEIGHT_UNITS environment set", func() { BeforeEach(func() { err := os.Unsetenv("WEIGHT_UNITS") Expect(err).NotTo(HaveOccurred()) }) It("reports the weight in grams", func() { Expect(book.HumanReadableWeight()).To(Equal("500g")) }) }) Context("when WEIGHT_UNITS is set to oz", func() { BeforeEach(func() { err := os.Setenv("WEIGHT_UNITS", "oz") Expect(err).NotTo(HaveOccurred()) }) It("reports the weight in ounces", func() { Expect(book.HumanReadableWeight()).To(Equal("17.6oz")) }) }) Context("when WEIGHT_UNITS is invalid", func() { BeforeEach(func() { err := os.Setenv("WEIGHT_UNITS", "smoots") Expect(err).NotTo(HaveOccurred()) }) It("errors", func() { weight, err := book.HumanReadableWeight() Expect(weight).To(BeZero()) Expect(err).To(HaveOccurred()) }) }) }) ``` Now we're guaranteed to clear out `WEIGHT_UNITS` after each spec as Ginkgo will run the `AfterEach` node's closure after the subject node for each spec... ...but we've still got a subtle issue. By clearing it out in our `AfterEach` we're assuming that `WEIGHT_UNITS` is not set when the specs run. But perhaps it is? What we really want to do is restore `WEIGHT_UNITS` to its original value. We can solve this by recording the original value first: ```go Describe("Reporting book weight", func() { var book *books.Book var originalWeightUnits string BeforeEach(func() { book = &books.Book{ Title: "Les Miserables", Author: "Victor Hugo", Pages: 2783, Weight: 500, } originalWeightUnits = os.Getenv("WEIGHT_UNITS") }) AfterEach(func() { err := os.Setenv("WEIGHT_UNITS", originalWeightUnits) Expect(err).NotTo(HaveOccurred()) }) ... }) ``` That's better. The specs will now clean up after themselves correctly. One quick note before we move on, you may have caught that `book.HumanReadableWeight()` returns _two_ values - a `weight` string and an `error`. This is common pattern and Gomega has first class support for it. The assertion: ```go Expect(book.HumanReadableWeight()).To(Equal("17.6oz")) ``` is actually making two assertions under the hood. That the first value returned by `book.HumanReadableWeight` is equal to `"17.6oz"` and that any subsequent values (i.e. the returned `error`) are `nil`. This elegantly inlines error handling and can make your specs more readable. #### Cleaning up our Cleanup code: DeferCleanup Setup and cleanup patterns like the one above are common in Ginkgo suites. While powerful, however, `AfterEach` nodes have a tendency to separate cleanup code from setup code. We had to create an `originalWeightUnits` closure variable to keep track of the original environment variable in the `BeforeEach` and pass it to the `AfterEach` - this feels noisy and potential error-prone. Ginkgo provides the `DeferCleanup()` function to help solve for this usecase and bring spec setup closer to spec cleanup. Here's what our example looks like with `DeferCleanup()`: ```go Describe("Reporting book weight", func() { var book *books.Book BeforeEach(func() { ... originalWeightUnits := os.Getenv("WEIGHT_UNITS") DeferCleanup(func() { err := os.Setenv("WEIGHT_UNITS", originalWeightUnits) Expect(err).NotTo(HaveOccurred()) }) }) ... }) ``` As you can see, `DeferCleanup()` can be called inside any setup or subject nodes. This allows us to bring our intended cleanup closer to our setup code and avoid extracting a separate closure variable. At first glance this code might seem confusing - as we discussed [above](#nodes-only-belong-in-container-nodes) Ginkgo does not allow you to define nodes within setup or subject nodes. `DeferCleanup` is not a Ginkgo node, however, but rather a convenience function that knows how to track cleanup code and run it at the right time in the spec's lifecycle. > Under the hood `DeferCleanup` is generating a dynamic `AfterEach` node and adding it to the running spec. This detail isn't important - you can simply assume that code in `DeferCleanup` has the identical runtime semantics to code in an `AfterEach`. `DeferCleanup` has a few more tricks up its sleeve. As shown above `DeferCleanup` can be passed a function that takes no arguments and returns no value. You can also pass a function that returns values. `DeferCleanup` ignores all these return value except for the last. If the last return value is a non-nil error - a common go pattern - `DeferCleanup` will fail the spec. This allows us to rewrite our example as: ```go Describe("Reporting book weight", func() { var book *books.Book BeforeEach(func() { ... originalWeightUnits := os.Getenv("WEIGHT_UNITS") DeferCleanup(func() error { return os.Setenv("WEIGHT_UNITS", originalWeightUnits) }) }) ... }) ``` You can also pass in a function that accepts arguments, then pass those arguments in directly to `DeferCleanup`. These arguments will be captured and passed to the function when cleanup is invoked. This allows us to rewrite our example once more as: ```go Describe("Reporting book weight", func() { var book *books.Book BeforeEach(func() { ... DeferCleanup(os.Setenv, "WEIGHT_UNITS", os.Getenv("WEIGHT_UNITS")) }) ... }) ``` here `DeferCleanup` is capturing the original value of `WEIGHT_UNITS` as returned by `os.Getenv("WEIGHT_UNITS")` then passing both it into `os.Setenv` when cleanup is triggered after each spec and asserting that the error returned by `os.Setenv` is `nil`. We've reduced our cleanup code to a single line! #### Separating Diagnostics Collection and Teardown: JustAfterEach We haven't discussed it but Ginkgo also provides a `JustAfterEach` setup node. `JustAfterEach` closures runs _just after_ the subject node and before any `AfterEach` closures. This can be useful if you need to collect diagnostic information about your spec _before_ invoking the clean up code in `AfterEach`. Here's a quick example: ```go Describe("Saving books to a database", func() { AfterEach(func() { dbClient.Clear() //clear out the database between tests }) JustAfterEach(func() { if CurrentSpecReport().Failed() { AddReportEntry("db-dump", dbClient.Dump()) } }) It("saves the book", func() { err := dbClient.Save(book) Expect(err).NotTo(HaveOccurred()) }) }) ``` We're, admittedly, jumping ahead a bit here by introducing a few new concepts that we'll dig into more later. The `JustAfterEach` closure in this container will always run after the subject closure but before the `AfterEach` closure. When it runs it will check if the current spec has failed (`CurrentSpecReport().Failed()`) and, if a failure was detected, it will download a dump of the database `dbClient.Dump()` and attach it to the spec's report `AddReportEntry()`. It's important that this runs before the `dbClient.Clear()` invocation in `AfterEach` - so we use a `JustAfterEach`. Of course, we could have inlined this diagnostic behavior into our `AfterEach`. As with `JustBeforeEach`, `JustAfterEach` can be nested in multiple containers. Doing so can have powerful results but might lead to confusing test suites -- so use nested `JustAfterEach`es judiciously. ### Suite Setup and Cleanup: BeforeSuite and AfterSuite The setup nodes we've explored so far have all applied at the spec level. They run Before**Each** or After**Each** spec in their associated container node. It is common, however, to need to perform setup and cleanup at the level of the Ginkgo suite. This is setup that should be performed just once - before any specs run, and cleanup that should be performed just once, when all the specs have finished. Such code is particularly common in integration tests that need to prepare environments or spin up external resources. Ginkgo supports suite-level setup and cleanup through two specialized **suite setup** nodes: `BeforeSuite` and `AfterSuite`. These suite setup nodes **must** be called at the top-level of the suite and cannot be nested in containers. Also there can be at most one `BeforeSuite` node and one `AfterSuite` node per suite. It is idiomatic to place the suite setup nodes in the Ginkgo bootstrap suite file. Let's continue to build out our book tests. Books can be stored and retrieved from an external database and we'd like to test this behavior. To do that, we'll need to spin up a database and set up a client to access it. We could do that in the `BeforeEach` spec - but doing so would be prohibitively expensive and slow. Instead, it would be more efficient to spin up the database just once when the suite starts. Here's how we'd do it in our `books_suite_test.go` file: ```go package books_test import ( . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" "path/to/db" "testing" ) var dbRunner *db.Runner var dbClient *db.Client func TestBooks(t *testing.T) { RegisterFailHandler(Fail) RunSpecs(t, "Books Suite") } var _ = BeforeSuite(func() { dbRunner = db.NewRunner() Expect(dbRunner.Start()).To(Succeed()) dbClient = db.NewClient() Expect(dbClient.Connect(dbRunner.Address())).To(Succeed()) }) var _ = AfterSuite(func() { Expect(dbRunner.Stop()).To(Succeed()) }) var _ = AfterEach(func() { Expect(dbClient.Clear()).To(Succeed()) }) ``` Ginkgo will run our `BeforeSuite` closure at the beginning of the [run phase](#mental-model-how-ginkgo-traverses-the-spec-hierarchy) - i.e. after the spec tree has been constructed but before any specs have run. This closure will instantiate a new `*db.Runner` - this is hypothetical code that knows how to spin up an instance of a database - and ask the runner to `Start()` a database. It will then instantiate a `*db.Client` and connect it to the database. Since `dbRunner` and `dbClient` are closure variables defined at the top-level all specs in our suite will have access to them and can trust that they have been correctly initialized. Our specs will be manipulating the database in all sorts of ways. However, since we're only spinning the database up once we run the risk of spec pollution if one spec does something that puts the database in a state that will influence an independent spec. To avoid that, it's a common pattern to introduce a top-level `AfterEach` to clear out our database. This `AfterEach` closure will run after each spec and clear out the database ensuring a pristine state for the spec. This is often much faster than instantiating a new copy of the database! Finally, the `AfterSuite` closure will run after all the tests to tear down the running database via `dbRunner.Stop()`. We can, alternatively, use `DeferCleanup` to achieve the same effect: ```go var _ = BeforeSuite(func() { dbRunner = db.NewRunner() Expect(dbRunner.Start()).To(Succeed()) DeferCleanup(dbRunner.Stop) dbClient = db.NewClient() Expect(dbClient.Connect(dbRunner.Address())).To(Succeed()) }) ``` `DeferCleanup` is context-aware and knows that it's being called in a `BeforeSuite`. The registered cleanup code will only run after all the specs have completed, just like `AfterSuite`. One quick note before we move on. We've introduced Gomega's [`Succeed()`](https://onsi.github.io/gomega/#handling-errors) matcher here. `Succeed()` simply asserts that a passed-in error is `nil`. The following two assertions are equivalent: ```go err := dbRunner.Start() Expect(err).NotTo(HaveOccurred()) /* is equivalent to */ Expect(dbRunner.Start()).To(Succeed()) ``` The `Succeed()` form is more succinct and reads clearly. > We won't get into it here but make sure to keep reading to understand how Ginkgo manages [suite parallelism](#spec-parallelization) and provides [SynchronizedBeforeSuite and SynchronizedAfterSuite](#parallel-suite-setup-and-cleanup-synchronizedbeforesuite-and-synchronizedaftersuite) suite setup nodes. ### Mental Model: How Ginkgo Handles Failure So far we've focused on how Ginkgo specs are constructed using nested nodes and how node closures are called in order when specs run. ...but Ginkgo is a testing framework. And tests fail! Let's delve into how Ginkgo handles failure. You typically use a matcher library, like [Gomega](https://github.com/onsi/gomega) to make assertions in your spec. When a Gomega assertion fails, Gomega generates a failure message and passes it to Ginkgo to signal that the spec has failed. It does this via Ginkgo's global `Fail` function. Of course, you're allowed to call this function directly yourself: ```go It("can read books", func() { if book.Title == "Les Miserables" && user.Age <= 3 { Fail("User is too young for this book") } user.Read(book) }) ``` whether in a setup or subject node, whenever `Fail` is called Ginkgo will mark the spec as failed and record and display the message passed to `Fail`. But there's more. The `Fail` function **panics** when it is called. This allows Ginkgo to stop the current closure in its tracks - no subsequent assertions or code in the closure will run. Ginkgo is quite opinionated about this behavior - if an assertion has failed then the current spec is not in an expected state and subsequent assertions will likely fail. This fast-fail approach is especially useful when running slow complex integration tests. It cannot be disabled. When a failure occurs in a `BeforeEach`, `JustBeforeEach`, or `It` closure Ginkgo halts execution of the current spec and cleans up by invoking any registered `AfterEach` or `JustAfterEach` closures (and any registered `DeferCleanup` closures if applicable). This is important to ensure the spec state is cleaned up. Ginkgo orchestrates this behavior by rescuing the panic thrown by `Fail` and unwinding the spec. However, if your spec launches a **goroutine** that calls `Fail` (or, equivalently, invokes a failing Gomega assertion), there's no way for Ginkgo to rescue the panic that `Fail` throws. This will cause the suite to panic and no subsequent specs will run. To get around this you must rescue the panic using `defer GinkgoRecover()`. Here's an example: However, if you block in a test case, Ginkgo will not be able to catch the failure and the case will time out instead. ```go /* === INVALID === */ It("panics in a goroutine", func() { var c chan interface{} go func() { defer GinkgoRecover() Fail("boom") close(c) }() <-c // Do not block! }) ``` [Asynchronous assertions](https://onsi.github.io/gomega/#making-asynchronous-assertions) can be used to wait for the condition while allowing Ginkgo to abort the case when an async failure occurs. ```go It("panics in a goroutine", func() { c := make(chan struct{}) go func() { defer GinkgoRecover() Fail("boom") close(c) }() Eventually(c).Should(BeClosed()) }) ``` You must remember to follow this pattern when making assertions in goroutines - however, if uncaught, Ginkgo's panic will include a helpful error to remind you to add `defer GinkgoRecover()` to your goroutine. When a failure occurs Ginkgo marks the current spec as failed and moves on to the next spec. If, however, you'd like to stop the entire suite when the first failure occurs you can run `ginkgo --fail-fast`. One last thing before we move on. When a failure occurs, Ginkgo records and presents the location of the failure to help you pinpoint where to look to debug your specs. This is typically the line where the call to `Fail` was performed (or, if you're using Gomega, the line where the Gomega assertion failed). Sometimes, however, you need to control the reported location. For example, consider the case where you are using a helper function: ```go /* === INVALID === */ func EnsureUserCanRead(book Book, user User) { if book.Title == "Les Miserables" && user.Age <= 3 { Fail("user is too young for this book") //A } } It("can read books", func() { EnsureUserCanRead(book, user) //B user.Read(book) }) ``` Now, if the `EnsureUserCanRead` helper fails the location presented to the user will point to `//A`. Ideally, however we'd prefer that Ginkgo report `//B`. There are a few ways to solve for this. The first is to pass `Fail` an `offset` like so: ```go func EnsureUserCanRead(book Book, user User) { if book.Title == "Les Miserables" && user.Age <= 3 { Fail("user is too young for this book", 1) } } ``` This will tell Ginkgo to skip a stack frame when calculating the offset. In this particular case Ginkgo will report the location that called `EnsureUserCanRead`: i.e. `//B`. This works... however managing offset can quickly get unwieldy. For example, say we wanted to compose helpers: ```go func EnsureUserCanCheckout(book Book, user User) { EnsureUserCanRead(book, user) EnsureUserHasAccessTo(book, user) } ``` in _this_ case, we'd need the offset that `EnsureUserCanRead` passes to `Fail` to be `2` instead of `1`. Instead of managing offsets you can use `GinkgoHelper()`: ```go func EnsureUserCanRead(book Book, user User) { GinkgoHelper() if book.Title == "Les Miserables" && user.Age <= 3 { Fail("user is too young for this book") //note the optional offset is gone } } func EnsureUserCanCheckout(book Book, user User) { GinkgoHelper() EnsureUserCanRead(book, user) EnsureUserHasAccessTo(book, user) } ``` Any function in which `GinkgoHelper()` is called is tracked by Ginkgo and ignored when a failure location is being computed. This allows you to build reusable test helpers and trust that the location presented to the user will always be in the spec that called the helper, and not the helper itself. ### Logging Output As outlined above, when a spec fails - say via a failed Gomega assertion - Ginkgo will pass the failure message passed to the `Fail` handler. Often times the failure message generated by Gomega gives you enough information to understand and resolve the spec failure. But there are several contexts, particularly when running large complex integration suites, where additional debugging information is necessary to understand the root cause of a failed spec. You'll typically only want to see this information if a spec has failed - and hide it if the spec succeeds. Ginkgo provides a globally available `io.Writer` called `GinkgoWriter` that solves for this usecase. `GinkgoWriter` aggregates everything written to it while a spec is running and only emits to stdout if the test fails. `GinkgoWriter` includes three convenience methods: - `GinkgoWriter.Print(a ...interface{})` is equivalent to `fmt.Fprint(GinkgoWriter, a...)` - `GinkgoWriter.Println(a ...interface{})` is equivalent to `fmt.Fprintln(GinkgoWriter, a...)` - `GinkgoWriter.Printf(format string, a ...interface{})` is equivalent to `fmt.Fprintf(GinkgoWriter, format, a...)` You can also attach additional `io.Writer`s for `GinkgoWriter` to tee to via `GinkgoWriter.TeeTo(writer)`. Any data written to `GinkgoWriter` will immediately be sent to attached tee writers. All attached Tee writers can be cleared with `GinkgoWriter.ClearTeeWriters()`. Finally - when running in verbose mode via `ginkgo -v` anything written to `GinkgoWriter` will be immediately streamed to stdout. This can help shorten the feedback loop when debugging a complex spec. If [logr](https://github.com/go-logr/logr) is used for logging in a project the globally available `GinkgoLogr` provides a logger implementation. Any logging on `GinkgoLogr` is forwarded to `GinkgoWriter`. ### Documenting Complex Specs: By As a rule, you should try to keep your subject and setup closures short and to the point. Sometimes this is not possible, particularly when testing complex workflows in integration-style tests. In these cases your test blocks begin to hide a narrative that is hard to glean by looking at code alone. Ginkgo provides `By` to help in these situations. Here's an example: ```go var _ = Describe("Browsing the library", func() { BeforeEach(func() { By("Fetching a token and logging in") authToken, err := authClient.GetToken("gopher", "literati") Expect(err).NotTo(HaveOccurred()) Expect(libraryClient.Login(authToken)).To(Succeed()) }) It("should be a pleasant experience", func() { By("Entering an aisle") aisle, err := libraryClient.EnterAisle() Expect(err).NotTo(HaveOccurred()) By("Browsing for books") books, err := aisle.GetBooks() Expect(err).NotTo(HaveOccurred()) Expect(books).To(HaveLen(7)) By("Finding a particular book") book, err := books.FindByTitle("Les Miserables") Expect(err).NotTo(HaveOccurred()) Expect(book.Title).To(Equal("Les Miserables")) By("Checking a book out") Expect(libraryClient.CheckOut(book)).To(Succeed()) books, err = aisle.GetBooks() Expect(err).NotTo(HaveOccurred()) Expect(books).To(HaveLen(6)) Expect(books).NotTo(ContainElement(book)) }) }) ``` The string passed to `By` is attached to the spec and can be displayed by Ginkgo when needed. If a test succeeds you won't see any output beyond Ginkgo's green dot. If a test fails, however, you will see each step printed out up to the step immediately preceding the failure. Running with `ginkgo -v` always emits all steps. `By` takes an optional function of type `func()`. When passed such a function `By` will immediately call the function. This allows you to organize your `It`s into groups of steps. `By` doesn't affect the structure of your specs - it's primarily syntactic sugar to help you document long and complex specs. Ginkgo has additional mechanisms to break specs up into more granular subunits with guaranteed ordering - we'll discuss [Ordered containers](#ordered-containers) in detail later. ### Mental Model: Spec Timelines Several events can occur during the lifecycle of a Ginkgo spec. You've seen a few of these already: various setup and subject nodes start and end; data is written to the `GinkgoWriter`; `By` annotations are generated; failures occur. And there are several more that you'll see introduced later in these docs (e.g. [`ReportEntries`](#attaching-data-to-reports) and [Progress Reports](#getting-visibility-into-long-running-specs) are attached to specs; [flaky specs](#repeating-spec-runs-and-managing-flaky-specs) might be retried). By default, when a spec passes Ginkgo does not emit any of this information. When a failure occurs, however, Ginkgo emits a **timeline** view of the spec. This includes all the events and `GinkgoWriter` output associated with a spec in the order they were generated and provides the context needed to debug the spec and understand the nature and context of the failure. You can view the timeline for all specs (whether passed or failed) by running `ginkgo -v` or `ginkgo -vv`. ### Table Specs We'll round out this chapter on [Writing Specs](#writing-specs) with one last topic. Ginkgo provides an expressive DSL for writing table driven specs. This DSL is a simple wrapper around concepts you've already met - container nodes like `Describe` and subject nodes like `It`. Let's write a table spec to describe the Author name functions we tested earlier: ```go DescribeTable("Extracting the author's first and last name", func(author string, isValid bool, firstName string, lastName string) { book := &books.Book{ Title: "My Book", Author: author, Pages: 10, } Expect(book.IsValid()).To(Equal(isValid)) Expect(book.AuthorFirstName()).To(Equal(firstName)) Expect(book.AuthorLastName()).To(Equal(lastName)) }, Entry("When author has both names", "Victor Hugo", true, "Victor", "Hugo"), Entry("When author has one name", "Hugo", true, "", "Hugo"), Entry("When author has a middle name", "Victor Marie Hugo", true, "Victor", "Hugo"), Entry("When author has no name", "", false, "", ""), ) ``` `DescribeTable` takes a string description, a **spec closure** to run for each table entry, and a set of entries. Each `Entry` takes a string description, followed by a list of parameters. `DescribeTable` will generate a spec for each `Entry` and when the specs run, the `Entry` parameters will be passed to the spec closure and must match the types expected by the spec closure. You'll be notified with a clear message at runtime if the parameter types don't match the spec closure signature. #### Mental Model: Table Specs are just Syntactic Sugar `DescribeTable` is simply providing syntactic sugar to convert its entries into a set of standard Ginkgo nodes. During the [Tree Construction Phase](#mental-model-how-ginkgo-traverses-the-spec-hierarchy) `DescribeTable` is generating a single container node that contains one subject node per table entry. The description for the container node will be the description passed to `DescribeTable` and the descriptions for the subject nodes will be the descriptions passed to the `Entry`s. During the Run Phase, when specs run, each subject node will simply invoke the spec closure passed to `DescribeTable`, passing in the parameters associated with the `Entry`. To put it another way, the table test above is equivalent to: ```go Describe("Extracting the author's first and last name", func() { It("When author has both names", func() { book := &books.Book{ Title: "My Book", Author: "Victor Hugo", Pages: 10, } Expect(book.IsValid()).To(Equal(true)) Expect(book.AuthorFirstName()).To(Equal("Victor")) Expect(book.AuthorLastName()).To(Equal("Hugo")) }) It("When author has one name", func() { book := &books.Book{ Title: "My Book", Author: "Hugo", Pages: 10, } Expect(book.IsValid()).To(Equal(true)) Expect(book.AuthorFirstName()).To(Equal("")) Expect(book.AuthorLastName()).To(Equal("Hugo")) }) It("When author has a middle name", func() { book := &books.Book{ Title: "My Book", Author: "Victor Marie Hugo", Pages: 10, } Expect(book.IsValid()).To(Equal(true)) Expect(book.AuthorFirstName()).To(Equal("Victor")) Expect(book.AuthorLastName()).To(Equal("Hugo")) }) It("When author has no name", func() { book := &books.Book{ Title: "My Book", Author: "", Pages: 10, } Expect(book.IsValid()).To(Equal(false)) Expect(book.AuthorFirstName()).To(Equal("")) Expect(book.AuthorLastName()).To(Equal("")) }) }) ``` As you can see - the table spec can capture this sort of repetitive testing much more concisely! Since `DescribeTable` is simply generating a container node you can nest it within other containers and surround it with setup nodes like so: ```go Describe("book", func() { var book *books.Book BeforeEach(func() { book = &books.Book{ Title: "Les Miserables", Author: "Victor Hugo", Pages: 2783, } Expect(book.IsValid()).To(BeTrue()) }) DescribeTable("Extracting the author's first and last name", func(author string, isValid bool, firstName string, lastName string) { book.Author = author Expect(book.IsValid()).To(Equal(isValid)) Expect(book.AuthorFirstName()).To(Equal(firstName)) Expect(book.AuthorLastName()).To(Equal(lastName)) }, Entry("When author has both names", "Victor Hugo", true, "Victor", "Hugo"), Entry("When author has one name", "Hugo", true, "", "Hugo"), Entry("When author has a middle name", "Victor Marie Hugo", true, "Victor", "Hugo"), Entry("When author has no name", "", false, "", ""), ) }) ``` the `BeforeEach` closure will run before each table entry spec and set up a fresh copy of `book` for the spec closure to manipulate and assert against. The fact that `DescribeTable` is constructed during the Tree Construction Phase can trip users up sometimes. Specifically, variables declared in container nodes have not been initialized yet during the Tree Construction Phase. Because of this, the following will not work: ```go /* === INVALID === */ Describe("book", func() { var shelf map[string]*books.Book //Shelf is declared here BeforeEach(func() { shelf = map[string]*books.Book{ //...and initialized here "Les Miserables": &books.Book{Title: "Les Miserables", Author: "Victor Hugo", Pages: 2783}, "Fox In Socks": &books.Book{Title: "Fox In Socks", Author: "Dr. Seuss", Pages: 24}, } }) DescribeTable("Categorizing books", func(book *books.Book, category books.Category) { Expect(book.Category()).To(Equal(category)) }, Entry("Novels", shelf["Les Miserables"], books.CategoryNovel), Entry("Short story", shelf["Fox in Socks"], books.CategoryShortStory), ) }) ``` These specs will fail. When `DescribeTable` and `Entry` are invoked during the Tree Construction Phase `shelf` will have been declared but uninitialized. So `shelf["Les Miserables"]` will return a `nil` pointer and the spec will fail. To get around this we must move access of the `shelf` variable into the body of the spec closure so that it can run at the appropriate time during the Run Phase. We can do this like so: ```go Describe("book", func() { var shelf map[string]*books.Book //Shelf is declared here BeforeEach(func() { shelf = map[string]*books.Book{ //...and initialized here "Les Miserables": &books.Book{Title: "Les Miserables", Author: "Victor Hugo", Pages: 2783}, "Fox In Socks": &books.Book{Title: "Fox In Socks", Author: "Dr. Seuss", Pages: 24}, } }) DescribeTable("Categorizing books", func(key string, category books.Category) { Expect(shelf[key]).To(Equal(category)) }, Entry("Novels", "Les Miserables", books.CategoryNovel), Entry("Novels", "Fox in Socks", books.CategoryShortStory), ) }) ``` We're now accessing the `shelf` variable in the spec closure during the Run Phase and can trust that it has been correctly instantiated by the setup node closure. Be sure to check out the [Table Patterns](#table-specs-patterns) section of the [Ginkgo and Gomega Patterns](#ginkgo-and-gomega-patterns) chapter to learn about a few more table-based patterns. #### Generating Entry Descriptions In the examples we've shown so far, we are explicitly passing in a description for each table entry. Recall that this description is used to generate the description of the resulting spec's Subject node. That means it's important as it conveys the intent of the spec and is printed out in case the spec fails. There are times, though, when adding a description manually can be tedious, repetitive, and error prone. Consider this example: ```go var _ = Describe("Math", func() { DescribeTable("addition", func(a, b, c int) { Expect(a+b).To(Equal(c)) }, Entry("1+2=3", 1, 2, 3), Entry("-1+2=1", -1, 2, 1), Entry("0+0=0", 0, 0, 0), Entry("10+100=101", 10, 100, 110), //OOPS TYPO ) }) ``` Mercifully, Ginkgo's table DSL provides a few mechanisms to programmatically generate entry descriptions. **`nil` Descriptions** First - Entries can have their descriptions auto-generated by passing `nil` for the `Entry` description: ```go var _ = Describe("Math", func() { DescribeTable("addition", func(a, b, c int) { Expect(a+b).To(Equal(c)) }, Entry(nil, 1, 2, 3), Entry(nil, -1, 2, 1), Entry(nil, 0, 0, 0), Entry(nil, 10, 100, 110), ) }) ``` This will generate entries named after the spec parameters. In this case we'd have `Entry: 1, 2, 3`, `Entry: -1, 2, 1`, `Entry: 0, 0, 0`, `Entry: 10, 100, 110`. **Custom Description Generator** Second - you can pass a table-level Entry **description closure** to render entries with `nil` description: ```go var _ = Describe("Math", func() { DescribeTable("addition", func(a, b, c int) { Expect(a+b).To(Equal(c)) }, func(a, b, c int) string { return fmt.Sprintf("%d + %d = %d", a, b, c) }, Entry(nil, 1, 2, 3), Entry(nil, -1, 2, 1), Entry(nil, 0, 0, 0), Entry(nil, 10, 100, 110), ) }) ``` This will generate entries named `1 + 2 = 3`, `-1 + 2 = 1`, `0 + 0 = 0`, and `10 + 100 = 110`. The description closure must return a `string` and must accept the same parameters passed to the spec closure. **`EntryDescription()` format string** There's also a convenience decorator called `EntryDescription` to specify Entry descriptions as format strings: ```go var _ = Describe("Math", func() { DescribeTable("addition", func(a, b, c int) { Expect(a+b).To(Equal(c)) }, EntryDescription("%d + %d = %d"), Entry(nil, 1, 2, 3), Entry(nil, -1, 2, 1), Entry(nil, 0, 0, 0), Entry(nil, 10, 100, 110), ) }) ``` This will have the same effect as the description above. **Per-Entry Descriptions** In addition to `nil` and strings you can also pass a string-returning closure or an `EntryDescription` as the first argument to `Entry`. Doing so will cause the entry's description to be generated by the passed-in closure or `EntryDescription` format string. For example: ```go var _ = Describe("Math", func() { DescribeTable("addition", func(a, b, c int) { Expect(a+b).To(Equal(c)) }, EntryDescription("%d + %d = %d"), Entry(nil, 1, 2, 3), Entry(nil, -1, 2, 1), Entry("zeros", 0, 0, 0), Entry(EntryDescription("%[3]d = %[1]d + %[2]d"), 10, 100, 110), Entry(func(a, b, c int) string {return fmt.Sprintf("%d = %d", a + b, c)}, 4, 3, 7), ) }) ``` Will generate entries named: `1 + 2 = 3`, `-1 + 2 = 1`, `zeros`, `110 = 10 + 100`, and `7 = 7`. #### Generating Subtree Tables As we've seen `DescribeTable` takes a function and interprets it as the body of a single `It` function. Sometimes, however, you may want to run a collection of specs for a given table entry. You can do this with `DescribeTableSubtree`: ```go DescribeTableSubtree("handling requests", func(url string, code int, message string) { var resp *http.Response BeforeEach(func() { var err error resp, err = http.Get(url) Expect(err).NotTo(HaveOccurred()) DeferCleanup(resp.Body.Close) }) It("should return the expected status code", func() { Expect(resp.StatusCode).To(Equal(code)) }) It("should return the expected message", func() { body, err := io.ReadAll(resp.Body) Expect(err).NotTo(HaveOccurred()) Expect(string(body)).To(Equal(message)) }) }, Entry("default response", "example.com/response", http.StatusOK, "hello world"), Entry("missing response", "example.com/missing", http.StatusNotFound, "wat?"), ... ) ``` now the body function passed to the table is invoked during the Tree Construction Phase to generate a set of specs for each entry. Each body function is invoked within the context of a new container so that setup nodes will only run for the specs defined in the body function. As with `DescribeTable` this is simply synctactic sugar around Ginkgo's existing DSL. The above example is identical to: ```go Describe("handling requests", func() { Describe("default response", func() { var resp *http.Response BeforeEach(func() { var err error resp, err = http.Get("example.com/response") Expect(err).NotTo(HaveOccurred()) DeferCleanup(resp.Body.Close) }) It("should return the expected status code", func() { Expect(resp.StatusCode).To(Equal(http.StatusOK)) }) It("should return the expected message", func() { body, err := io.ReadAll(resp.Body) Expect(err).NotTo(HaveOccurred()) Expect(string(body)).To(Equal("hello world")) }) }) Describe("missing response", func() { var resp *http.Response BeforeEach(func() { var err error resp, err = http.Get("example.com/missing") Expect(err).NotTo(HaveOccurred()) DeferCleanup(resp.Body.Close) }) It("should return the expected status code", func() { Expect(resp.StatusCode).To(Equal(http.StatusNotFound)) }) It("should return the expected message", func() { body, err := io.ReadAll(resp.Body) Expect(err).NotTo(HaveOccurred()) Expect(string(body)).To(Equal("wat?")) }) }) }) ``` all the infrastructure around generating table entry descriptions applies here as well - though the description will be the title of the generated container. Note that you **must** add subject nodes in the body function if you want `DescribeHandleSubtree` to add specs. ### Alternatives to Dot-Importing Ginkgo As shown throughout this documentation, Ginkgo users are encouraged to dot-import the Ginkgo DSL into their test suites to effectively extend the Go language with Ginkgo's expressive building blocks: ```go import . "github.com/onsi/ginkgo/v2" ``` Some users prefer to avoid dot-importing dependencies into their code in order to keep their global namespace clean and predictable. You can, of course, do this with Ginkgo - we recommend using a simple shorthand like `g`: ```go import g "github.com/onsi/ginkgo/v2" ``` now you can write tests as before, albeit with a slight stutter: ```go var _ = g.Describe("Books", func() { g.BeforeEach(func() { ... }) g.It("works as before", func() { g.By("you just need to repeat g. everywhere") }) }) ``` Alternatively, you can choose to dot-import only _portions_ of Ginkgo's DSL into the global namespace. The packages under `github.com/onsi/ginkgo/v2/dsl` organize the various pieces of Ginkgo into a series of subpackages. You can choose to mix-and-match which of these are dot-imported vs namespaced. For example, you can dot-import the core DSL (which provides the various setup, container, and subject nodes) while namespace importing the decorators DSL: ```go import ( . "github.com/onsi/ginkgo/v2/dsl/core" "github.com/onsi/ginkgo/v2/dsl/decorators" ) var _ = It("gives you the core DSL", decorators.Label("and namespaced decorators"), func() { ... }) ``` The available DSL packages are: | Package | Contents | |-------|--------| | `github.com/onsi/ginkgo/v2/dsl/core` | The core DSL including all container, setup, and subject nodes (`Describe`, `Context`, `BeforeEach`, `BeforeSuite`, `It`, etc...) as well as the most commonly used functions (`RunSpecs`, `Skip`, `Fail`, `By`, `GinkgoT`) | | `github.com/onsi/ginkgo/v2/decorators` | The decorator DSL includes all Ginkgo's decorators (e.g. `Label`, `Ordered`, `Serial`, etc...) | | `github.com/onsi/ginkgo/v2/reporting` | The reporting DSL includes all reporting-related nodes and types (e.g. `Report`, `CurrentSpecReport`, `ReportAfterEach`, `AddReportEntry`) | | `github.com/onsi/ginkgo/v2/table` | The table DSL includes all table-related types and functions (e.g. `DescribeTable`, `Entry`, `EntryDescription`) | The DSL packages simply import and then re-export pieces of the Ginkgo DSL provided by `github.com/onsi/ginkgo/v2` so there are no differences in behavior or interoperability if you use the standard dot-import for Ginkgo or pull in the various DSL packages in piecemeal. ## Running Specs The previous chapter covered the basics of [Writing Specs](#writing-specs) in Ginkgo. We explored how Ginkgo lets you use container nodes, subject nodes, and setup nodes to construct hierarchical spec trees; and how Ginkgo transforms those trees into a list of specs to run. In this chapter we'll shift our focus from the Tree Construction Phase to the Run Phase and dive into the various capabilities Ginkgo provides for manipulating the spec list and controlling how specs run. To start, let's continue to flesh out our mental model for Ginkgo. ### Mental Model: Ginkgo Assumes Specs are Independent We've already seen how Ginkgo generates a spec tree and converts it to a flat list of specs. If you need a refresher, skim through the [Mental Model: How Ginkgo Traverses the Spec Hierarchy](#mental-model-how-ginkgo-traverses-the-spec-hierarchy) section up above. Lists are powerful things. They can be sorted. They can be randomized. They can be filtered. They can be distributed to multiple workers. Ginkgo supports all of these manipulations of the spec list enabling you to randomize, filter, and parallelize your test suite with minimal effort. To unlock these powerful capabilities Ginkgo makes an important, foundational, assumption about the specs in your suite: **Ginkgo assumes specs are independent**. Because individual Ginkgo specs do not depend on each other, it is possible to run them in any order; it is possible to run subsets of them; it is even possible to run them simultaneously in parallel. Ensuring your specs are independent is foundational to writing effective Ginkgo suites that make the most of Ginkgo's capabilities. In the next few sections we'll unpack how Ginkgo randomizes specs and supports running specs in parallel. As we do, we'll cover principles that - if followed - will help you write specs that are independent from each other. ### Spec Randomization By default, Ginkgo will randomize the order in which the specs in a suite run. This is done intentionally. By randomizing specs, Ginkgo can help suss out spec pollution - accidental dependencies between specs - throughout a suite's development. Ginkgo's default behavior is to only randomize the order of top-level containers -- the specs *within* those containers continue to run in the order in which they are specified in the test files. This is helpful when developing specs as it mitigates the cognitive overload of having specs within a container continuously change the order in which they run during a debugging session. When running on CI, or before committing code, it's good practice to instruct Ginkgo to randomize **all** specs in a suite. You do this with the `--randomize-all` flag: ```bash ginkgo --randomize-all ``` Ginkgo uses the current time to seed the randomization and prints out the seed near the beginning of the suite output. If you notice intermittent spec failures that you think may be due to spec pollution, you can use the seed from a failing suite to exactly reproduce the spec order for that suite. To do this pass the `--seed=SEED` flag: ```bash ginkgo --seed=17 ``` Because Ginkgo randomizes specs you should make sure that each spec runs from a clean independent slate. Principles like ["Declare in container nodes, initialize in setup nodes"](#avoid-spec-pollution-dont-initialize-variables-in-container-nodes) help you accomplish this: when variables are initialized in setup nodes each spec is guaranteed to get a fresh, correctly initialized, state to operate on. For example: ```go /* === INVALID === */ Describe("Bookmark", func() { book := &books.Book{ Title: "Les Miserables", Author: "Victor Hugo", Pages: 2783, } It("has no bookmarks by default", func() { Expect(book.Bookmarks()).To(BeEmpty()) }) It("can add bookmarks", func() { book.AddBookmark(173) Expect(book.Bookmarks()).To(ContainElement(173)) }) }) ``` This suite only passes if the "has no bookmarks" spec runs before the "can add bookmarks" spec. Instead, you should initialize the book variable in a setup node: ```go Describe("Bookmark", func() { var book *books.Book BeforeEach(func() { book = &books.Book{ Title: "Les Miserables", Author: "Victor Hugo", Pages: 2783, } }) It("has no bookmarks by default", func() { Expect(book.Bookmarks()).To(BeEmpty()) }) It("can add bookmarks", func() { book.AddBookmark(173) Expect(book.Bookmarks()).To(ContainElement(173)) }) }) ``` In addition to avoiding accidental spec pollution you should make sure to avoid _intentional_ spec pollution! Specifically, you should ensure that the correctness of your suite does not rely on the order in which specs run. For example: ```go /* === INVALID === */ Describe("checking out a book", func() { var book *books.Book var err error It("can fetch a book from a library", func() { book, err = libraryClient.FindByTitle("Les Miserables") Expect(err).NotTo(HaveOccurred()) Expect(book.Title).To(Equal("Les Miserables")) }) It("can check out the book", func() { Expect(library.CheckOut(book)).To(Succeed()) }) It("no longer has the book in stock", func() { book, err = libraryClient.FindByTitle("Les Miserables") Expect(err).To(MatchError(books.NOT_IN_STOCK)) Expect(book).To(BeNil()) }) }) ``` These specs are not independent - the current implementation assumes that they will run in order. This means they can't be randomized or parallelized with respect to each other. You can fix these specs by creating a single `It` to test the behavior of checking out a book: ```go Describe("checking out a book", func() { It("can perform a checkout flow", func() { By("fetching a book") book, err := libraryClient.FindByTitle("Les Miserables") Expect(err).NotTo(HaveOccurred()) Expect(book.Title).To(Equal("Les Miserables")) By("checking out the book") Expect(library.CheckOut(book)).To(Succeed()) By("validating the book is no longer in stock") book, err = libraryClient.FetchByTitle("Les Miserables") Expect(err).To(MatchError(books.NOT_IN_STOCK)) Expect(book).To(BeNil()) }) }) ``` Ginkgo also provides an alternative that we'll discuss later - you can use [Ordered Containers](#ordered-containers) to tell Ginkgo when the specs in a container must _always_ be run in order. Finally, if your specs need to _generate_ random numbers you can seed your pseudo-random number generator with the same seed used to seed Ginkgo's randomization. This will help ensure that specifying the random seed fully determines the pseudo-random aspects of your suite. You can get access to the random seed in the spec using `GinkgoRandomSeed()` ### Spec Parallelization As spec suites grow in size and complexity they have a tendency to get slower. Thankfully the vast majority of modern computers ship with multiple CPU cores. Ginkgo helps you use those cores to speed up your suites by running specs in parallel. This is _especially_ useful when running large, complex, and slow integration suites where the only means to speed things up is to embrace parallelism. To run a Ginkgo suite in parallel you simply pass the `-p` flag to `ginkgo`: ```bash ginkgo -p ``` this will automatically detect the optimal number of test processes to spawn based on the number of cores on your machine. You can, instead, specify this number manually via `-procs=N`: ```bash ginkgo -procs=N ``` And that's it! Ginkgo will automatically run your specs in parallel and take care of collating the results into a single coherent output stream. At this point, though, you may be scratching your head. _How_ does Ginkgo support parallelism given the use of shared closure variables we've seen throughout? Consider the example from above: ```go Describe("Bookmark", func() { var book *books.Book BeforeEach(func() { book = &books.Book{ Title: "Les Miserables", Author: "Victor Hugo", Pages: 2783, } }) It("has no bookmarks by default", func() { Expect(book.Bookmarks()).To(BeEmpty()) }) It("can add bookmarks", func() { book.AddBookmark(173) Expect(book.Bookmarks()).To(ContainElement(173)) }) }) ``` Both "Bookmark" specs are interrogating and mutating the same shared `book` variable. Running the two specs in parallel would lead to an obvious data race over `book` and undefined, seemingly random, behavior. #### Mental Model: How Ginkgo Runs Parallel Specs Ginkgo ensures specs running in parallel are fully isolated from one another. It does this by running the specs in _different processes_. Because Ginkgo specs are assumed to be fully independent they can be harvested out to run on different worker processes - each process has its own memory space and there is, therefore, no risk for shared variable data races. Here's what happens under the hood when you run `ginkgo -p`: First, the Ginkgo CLI compiles a single test binary (via `go test -c`). It then invokes `N` copies of the test binary. Each of these processes then enters the Tree Construction Phase and all processes generate an identical spec tree and, therefore, an identical list of specs to run. The processes then enter the Run Phase and start running their specs. They coordinate via the Ginkgo CLI (which acts a server) to figure out the next spec to run, and report to the CLI as specs finish running. The CLI then takes care of generating a single coherent output stream of the running specs. In essence, this is a simple map-reduce system with the CLI playing the role of a centralized server. With few exceptions, the different test processes do not communicate with one another and for most spec suites you, the developer, do not need to worry about which spec is running on which process. This makes it easy to parallelize your suites and get some major performance gains. There are, however, contexts where you _do_ need to be aware of which process a given spec is running on. In particular, there are several patterns for building effective parallelizable integration suites that need this information. We will explore such patterns in much more detail in the [Patterns chapter](#patterns-for-parallel-integration-specs) - feel free to jump straight there if you're interested! For now we'll simply introduce some of the building blocks that Ginkgo provides for implementing these patterns. #### Discovering Which Parallel Process a Spec is Running On Ginkgo numbers the running parallel processes from `1` to `N`. A spec can get the index of the Ginkgo process it is running on via `GinkgoParallelProcess()`. This can be useful in contexts where specs need to share a globally available external resource but need to access a specific shard, namespace, or instance of the resource so as to avoid spec pollution. For example: ```go Describe("Storing books in an external database", func() { BeforeEach(func() { namespace := fmt.Sprintf("namespace-%d", GinkgoParallelProcess()) Expect(dbClient.SetNamespace(namespace)).To(Succeed()) DeferCleanup(dbClient.ClearNamespace, namespace) }) It("returns empty when there are no books", func() { Expect(dbClient.Books()).To(BeEmpty()) }) Context("when a book is in the database", func() { var book *books.Book BeforeEach(func() { book = &books.Book{ Title: "Les Miserables", Author: "Victor Hugo", Pages: 2783, } Expect(dbClient.Store(book)).To(Succeed()) }) It("can fetch the book", func() { Expect(dbClient.Books()).To(ConsistOf(book)) }) It("can update the book", func() { book.Author = "Victor Marie Hugo" Expect(dbClient.Store(book)).To(Succeed()) Expect(dbClient.Books()).To(ConsistOf(book)) }) It("can delete the book", func() { Expect(dbClient.Delete(book)).To(Succeed()) Expect(dbClient.Books()).To(BeEmpty()) }) }) }) ``` Without sharding access to the database these specs would step on each other's toes and result in non-deterministic flaky behavior. By implementing sharded access to the database (e.g. `dbClient.SetNamespace` could instruct the client to prepend the `namespace` string to any keys stored in a key-value database) this suite can be trivially parallelized. And by extending the "declare in container nodes, initialize in setup nodes" principle to apply to state stored _external_ to the suite we are able to ensure that each spec runs from a known clean shard of the database. Such a suite will continue to be parallelizable as it grows - enabling faster runtimes with less flakiness than would otherwise be possible in a serial-only suite. In addition to `GinkgoParallelProcess()`, Ginkgo provides access to the total number of running processes. You can get this from `GinkgoConfiguration()`, which returns the state of Ginkgo's configuration, like so: ```go suiteConfig, _ := GinkgoConfiguration() totalProcesses := suiteConfig.ParallelTotal ``` #### Parallel Suite Setup and Cleanup: SynchronizedBeforeSuite and SynchronizedAfterSuite Our example above assumed the existence of a single, globally shared, running database. How might we have set up such a database? You typically spin up external resources like this in the `BeforeSuite` in your suite bootstrap file. We saw this example earlier: ```go var dbClient *db.Client var dbRunner *db.Runner var _ = BeforeSuite(func() { dbRunner := db.NewRunner() Expect(dbRunner.Start()).To(Succeed()) dbClient = db.NewClient() Expect(dbClient.Connect(dbRunner.Address())).To(Succeed()) }) var _ = AfterSuite(func() { Expect(dbClient.Cleanup()).To(Succeed()) Expect(dbRunner.Stop()).To(Succeed()) }) ``` However, since `BeforeSuite` runs on _every_ parallel process this would result in `N` independent databases spinning up. Sometimes that's exactly what you want - as it provides maximal isolation for the running specs and is a natural way to shard data access. Sometimes, however, spinning up multiple external processes is too resource intensive or slow and it is more efficient to share access to a single resource. Ginkgo supports this usecase with `SynchronizedBeforeSuite` and `SynchronizedAfterSuite`. Here are the full signatures for the two: ```go func SynchronizedBeforeSuite( process1 func() []byte, allProcesses func([]byte), ) func SynchronizedAfterSuite( allProcesses func(), process1 func(), ) ``` Let's dig into `SynchronizedBeforeSuite` (henceforth `SBS`) first. `SBS` runs at the beginning of the Run Phase - before any specs have run but after the spec tree has been parsed and constructed. `SBS` allows us to set up state in one process, and pass information to all the other processes. Concretely, the `process1` function runs **only** on parallel process #1. All other parallel processes pause and wait for `process1` to complete. Upon completion `process1` returns arbitrary data as a `[]byte` slice and this data is then passed to all parallel processes which then invoke the `allProcesses` function in parallel, passing in the `[]byte` slice. Note that the passing of a `[]byte` slice from `process1` to `allProcesses` is optional. `SynchronizedBeforeSuite` also supports the following signature: ```go func SynchronizedBeforeSuite( process1 func(), allProcesses func(), ) ``` Similarly, `SynchronizedAfterSuite` is split into two functions. The first, `allProcesses`, runs on all processes after they finish running specs. The second, `process1`, only runs on process #1 - and only _after_ all other processes have finished and exited. We can use this behavior to set up shared external resources like so: ```go var dbClient *db.Client var dbRunner *db.Runner var _ = SynchronizedBeforeSuite(func() []byte { //runs *only* on process #1 dbRunner := db.NewRunner() Expect(dbRunner.Start()).To(Succeed()) return []byte(dbRunner.Address()) }), func(address []byte) { //runs on *all* processes dbClient = db.NewClient() Expect(dbClient.Connect(string(address))).To(Succeed()) dbClient.SetNamespace(fmt.Sprintf("namespace-%d", GinkgoParallelProcess())) }) var _ = SynchronizedAfterSuite(func() { //runs on *all* processes Expect(dbClient.Cleanup()).To(Succeed()) }, func() { //runs *only* on process #1 Expect(dbRunner.Stop()).To(Succeed()) }) ``` This code will spin up a single database and ensure that every parallel Ginkgo process connects to the database and sets up an appropriately sharded namespace. Ginkgo does all the work of coordinating across these various closures and passing information back and forth - and all the complexity of the parallel setup in the test suite is now contained in the `Synchronized*` setup nodes. By the way, we can clean all this up further using `DeferCleanup`. `DeferCleanup` is context aware and so knows that any cleanup code registered in a `BeforeSuite`/`SynchronizedBeforeSuite` should run at the end of the suite: ```go var dbClient *db.Client var _ = SynchronizedBeforeSuite(func() []byte { //runs *only* on process #1 dbRunner := db.NewRunner() Expect(dbRunner.Start()).To(Succeed()) DeferCleanup(dbRunner.Stop) return []byte(dbRunner.Address()) }), func(address []byte) { //runs on *all* processes dbClient = db.NewClient() Expect(dbClient.Connect(string(address))).To(Succeed()) dbClient.SetNamespace(fmt.Sprintf("namespace-%d", GinkgoParallelProcess())) DeferCleanup(dbClient.Cleanup) }) ``` #### The ginkgo CLI vs go test One last word before we close out the topic of Spec Parallelization. Ginkgo's process-based server-client parallelization model should make clear why you need to use the `ginkgo` CLI to run parallel specs instead of `go test`. While Ginkgo suites are fully compatible with `go test` there _are_ some features, most notably parallelization, that require the use of the` ginkgo` CLI. We recommend embracing the `ginkgo` CLI as part of your toolchain and workflow. It's designed to make the process of writing and iterating on complex spec suites as painless as possible. Consider, for example, the `watch` subcommand: ```bash ginkgo watch -p ``` is all you need to have Ginkgo rerun your suite - in parallel - whenever it detects a change in the suite or any of its dependencies. Run that in a terminal while you build out your code and get immediate feedback as you evolve your suite! ### Mental Model: Spec Decorators We've emphasized throughout this chapter that Ginkgo _assumes_ specs are fully independent. This assumption enables spec randomization and spec parallelization. There are some contexts, however, when spec independence is simply too difficult to achieve. The cost of ensuring specs are independent may be too high. Or there may be external constraints beyond your control. When this is the case, Ginkgo allows you to explicitly control how specific specs in your suite must be run. We'll get into that in the next two sections. But first we'll need to introduce **Spec Decorators**. So far we've seen that container nodes and subject nodes have the following signature: ```go Describe("description", ) It("description", ) ``` In actuality, the signatures for these functions is actually: ```go Describe("description", args ...interface{}) It("description", args ...interface{}) ``` and Ginkgo provides a number of additional types that can be passed in to container and subject nodes. We call these types Spec Decorators as they decorate the spec with additional metadata. This metadata can modify the behavior of the spec at run time. A comprehensive [reference of all decorators](#decorator-reference) is maintained in these docs. Some Spec Decorators only apply to a specific node. For example the `Offset` or `CodeLocation` decorators allow you to adjust the location of a node reported by Ginkgo (this is useful when building shared libraries that generate their own Ginkgo nodes). Most Spec Decorators, however, get applied to the specs that include the decorated node. For example, the `Serial` decorator (which we'll see in the next section) instructs Ginkgo to ensure that any specs that include the `Serial` node should only run in series and never in parallel. So, if `Serial` is applied to a container like so: ```go Describe("Never in parallel please", Serial, func() { It("tests one behavior", func() { }) It("tests another behavior", func() { }) }) ``` Then both specs generated by the subject nodes in this container will be marked as `Serial`. If we transfer the `Serial` decorator to one of the subject nodes, however: ```go Describe("Never in parallel please", func() { It("tests one behavior", func() { }) It("tests another behavior", Serial, func() { }) }) ``` now, only the spec with the "tests another behavior" subject node will be marked Serial. Another way of capturing this behavior is to say that most Spec Decorators apply hierarchically. If a container node is decorated with a decorator then the decorator applies to all its child nodes. One last thing - spec decorators can also decorate [Table Specs](#table-specs): ```go DescribeTable("Table", Serial, ...) Entry("Entry", FlakeAttempts(3), ...) ``` will all work just fine. You can put the decorators anywhere after the description strings. The [reference](#decorator-reference) clarifies how decorator inheritance works for each decorator and which nodes can accept which decorators. ### Serial Specs When you run `ginkgo -p` Ginkgo spins up multiple processes and distributes **all** your specs across those processes. As such, any spec must be able to run in parallel with any other spec. Sometimes, however, you simply _must_ enforce that a spec runs in series. Perhaps it is a performance benchmark spec that cannot run in parallel with any other work. Perhaps it is a spec that is known to exercise an edge case that places some external resource into a known-bad state and, therefore, must be run independently of all other specs. Perhaps it is simply a spec that is just so resource intensive that it must run alone to avoid exhibiting flaky behavior. Whatever the reason, Ginkgo allows you to decorate container and subject nodes with `Serial`: ```go Describe("Something expensive", Serial, func() { It("is a resource hog that can't run in parallel", func() { ... }) It("is another resource hog that can't run in parallel", func() { ... }) }) ``` Ginkgo will guarantee that these specs will never run in parallel with other specs. Under the hood Ginkgo does this by running `Serial` at the **end** of the suite on parallel process #1. When it detects the presence of `Serial` specs, process #1 will wait for all other processes to exit before running the `Serial` specs. ### Ordered Containers By default Ginkgo does not guarantee the order in which specs run. As we've seen, `ginkgo --randomize-all` will shuffle the order of all specs and `ginkgo -p` will distribute all specs across multiple workers. Both operations mean that the order in which specs run cannot be guaranteed. There are contexts, however, when you must guarantee the order in which a set of specs run. For example, you may be testing a complex flow of behavior and would like to break your spec up into multiple units instead of having one enormous `It`. Or you may have to perform some expensive setup for a set of specs and only want to perform that setup **once** _before_ the specs run. Ginkgo provides `Ordered` containers to solve for these usecases. Specs in `Ordered` containers are guaranteed to run in the order in which they appear. Let's pull out an example from before; recall that the following is invalid: ```go /* === INVALID === */ Describe("checking out a book", func() { var book *books.Book var err error It("can fetch a book from a library", func() { book, err = libraryClient.FetchByTitle("Les Miserables") Expect(err).NotTo(HaveOccurred()) Expect(book.Title).To(Equal("Les Miserables")) }) It("can check out the book", func() { Expect(library.CheckOut(book)).To(Succeed()) }) It("no longer has the book in stock", func() { book, err = libraryClient.FetchByTitle("Les Miserables") Expect(err).To(MatchError(books.NOT_IN_STOCK)) Expect(book).To(BeNil()) }) }) ``` These specs break the "declare in container nodes, initialize in setup nodes" principle. When randomizing specs or running in parallel Ginkgo will not guarantee that these specs run in order. Because the specs are mutating the same shared set of variables they will behave in non-deterministic ways when shuffled. In fact, when running in parallel, specs on different parallel processes will be accessing completely different local copies of the closure variables! When we introduced this example we recommended condensing the tests into a single `It` and using `By` to document the test. `Ordered` containers provide an alternative that some users might prefer, stylistically: ```go Describe("checking out a book", Ordered, func() { var book *books.Book var err error It("can fetch a book from a library", func() { book, err = libraryClient.FetchByTitle("Les Miserables") Expect(err).NotTo(HaveOccurred()) Expect(book.Title).To(Equal("Les Miserables")) }) It("can check out the book", func() { Expect(library.CheckOut(book)).To(Succeed()) }) It("no longer has the book in stock", func() { book, err = libraryClient.FetchByTitle("Les Miserables") Expect(err).To(MatchError(books.NOT_IN_STOCK)) Expect(book).To(BeNil()) }) }) ``` Here we've decorated the `Describe` container as `Ordered`. Ginkgo will guarantee that specs in an `Ordered` container will run sequentially, in the order they are written. Specs in an `Ordered` container may run in parallel with respect to _other_ specs, but they will always run sequentially on the same parallel process. This allows specs in `Ordered` containers to rely on mutating local closure state. The `Ordered` decorator can only appear on a container node. Any container nodes nested within a container node will automatically be considered `Ordered` and there is no way to mark a node within an `Ordered` container as "not `Ordered`". > Ginkgo did not include support for `Ordered` containers for quite some time. As you can see `Ordered` containers make it possible to circumvent the "Declare in container nodes, initialize in setup nodes" principle; and they make it possible to write dependent specs This comes at a cost, of course - specs in `Ordered` containers cannot be fully parallelized which can result in slower suite runtimes. Despite these cons, pragmatism prevailed and `Ordered` containers were introduced in response to real-world needs in the community. Nonetheless, we recommend using `Ordered` containers only when needed. #### Setup in Ordered Containers: BeforeAll and AfterAll You can include all the usual setup nodes in an `Ordered` container however and they continue to operate in the same way. `BeforeEach` will run before every spec and `AfterEach` will run after every spec. This applies to all setup nodes in a spec's hierarchy. So `BeforeEach`/`AfterEach` nodes that are present outside the `Ordered` container will still run before and after each spec in the container. There are, however, two new setup node variants that can be used within `Ordered` containers: `BeforeAll` and `AfterAll`. `BeforeAll` closures will run exactly once before any of the specs within the `Ordered` container. `AfterAll` closures will run exactly once after the last spec has finished running. Here's an extension of our earlier example that illustrates how these nodes might be used: ```go Describe("checking out a book", Ordered, func() { var libraryClient *library.Client var book *books.Book var err error BeforeAll(func() { libraryClient = library.NewClient() Expect(libraryClient.Connect()).To(Succeed()) }) It("can fetch a book from a library", func() { book, err = libraryClient.FetchByTitle("Les Miserables") Expect(err).NotTo(HaveOccurred()) Expect(book.Title).To(Equal("Les Miserables")) }) It("can check out the book", func() { Expect(library.CheckOut(book)).To(Succeed()) }) It("no longer has the book in stock", func() { book, err = libraryClient.FetchByTitle("Les Miserables") Expect(err).To(MatchError(books.NOT_IN_STOCK)) Expect(book).To(BeNil()) }) AfterAll(func() { Expect(libraryClient.Disconnect()).To(Succeed()) }) }) ``` here we only set up the `libraryClient` once before all the specs run, and then tear it down once all the specs complete. `BeforeAll` and `AfterAll` nodes can only be introduced within an `Ordered` container. `BeforeAll` and `AfterAll` can also be nested within containers that appear in `Ordered` containers - in such cases they will run before/after the specs in that nested container. As always, you can also use `DeferCleanup`. Since `DeferCleanup` is context aware, it will detect when it is called in a `BeforeAll` and behave like an `AfterAll` at the same nesting level. The following is equivalent to the example above: ```go BeforeAll(func() { libraryClient = library.NewClient() Expect(libraryClient.Connect()).To(Succeed()) DeferCleanup(libraryClient.Disconnect) }) ``` #### Setup around Ordered Containers: the OncePerOrdered Decorator It's a common pattern to have setup and cleanup code at the outer-most level of a suite that is intended to ensure that every spec runs from a clean slate. For example, we may be testing our library service and want to ensure that each spec begins with the same library setup. We might write something like this at the top level of our suite file: ```go BeforeEach(func() { libraryClient = library.NewClient() Expect(libraryClient.Connect()).To(Succeed()) snapshot := libraryClient.TakeSnapshot() DeferCleanup(libraryClient.RestoreSnapshot, snapshot) }) ``` now, every spec will be guaranteed to start with the same initial state and we are free to write our specs without worrying about spec pollution. This behavior, however, will cause specs in Ordered containers to break. Consider this set of specs: ```go Describe("checking out a book", Ordered, func() { var book *books.Book var err error BeforeAll(func() { libraryClient.AddBook( &books.Book{ Title: "Les Miserables", Author: "Victor Hugo", Pages: 2783, }) }) It("can fetch a book from a library", func() { book, err = libraryClient.FetchByTitle("Les Miserables") Expect(err).NotTo(HaveOccurred()) Expect(book.Title).To(Equal("Les Miserables")) }) It("can check out the book", func() { Expect(library.CheckOut(book)).To(Succeed()) }) It("no longer has the book in stock", func() { book, err = libraryClient.FetchByTitle("Les Miserables") Expect(err).To(MatchError(books.NOT_IN_STOCK)) Expect(book).To(BeNil()) }) }) ``` Because our outer-most `BeforeEach` runs before _every_ spec, the specs in this ordered container will fail. Specifically the _first_ spec will pass but subsequent specs will fail as the `BeforeEach` cleans up state between them. Ginkgo provides a `OncePerOrdered` decorator that can be applied to the `BeforeEach`, `JustBeforeEach`, `AfterEach`, and `JustAfterEach` setup nodes to solve for this usecase. The `OncePerOrdered` decorator changes the semantics of these `*Each` setup nodes from "run around each spec" to "run around each independent unit". Individual specs and specs that are in unordered containers constitute independent units and so the `*Each` nodes run around each spec. However specs in `Ordered` containers behave like a single unit - so `*Each` setup nodes with the `OncePerOrdered` decorator will only run once before the unit begins and/or after the unit completes. In this way a `BeforeEach` with `OncePerOrdered` that runs before an Ordered container is semantically equivalent to a `BeforeAll` within that container. By decorating our outermost `BeforeEach` with `OncePerOrdered`: ```go BeforeEach(OncePerOrdered, func() { libraryClient = library.NewClient() Expect(libraryClient.Connect()).To(Succeed() snapshot := libraryClient.TakeSnapshot() DeferCleanup(libraryClient.RestoreSnapshot, snapshot) }) ``` we retain the existing behavior for the entire suite _and_ get the `BeforeAll`-like behavior we need for our `Ordered` container. The `OncePerOrdered` decorator modifies the behavior of the `BeforeEach` setup node _only_ for Ordered containers at the same or lower nesting level as the setup node. Adding a `OncePerOrdered` `BeforeEach` setup node _inside_ an `Ordered` container results in a setup node that behaves like a normal `BeforeEach` - it will run for every spec in the container. However a container nested _within_ the container will trigger the `OncePerOrdered` behavior and the `BeforeEach` will run just once for the specs within the nested container. Lastly, the `OncePerOrdered` container cannot be applied to the `ReportBeforeEach` and `ReportAfterEach` nodes discussed below. In Ginkgo, reporting always happens at the granularity of the individual spec. #### Failure Handling in Ordered Containers Normally, when a spec fails, Ginkgo moves on to the next spec. This is possible because Ginkgo assumes, by default, that all specs are independent. However, `Ordered` containers explicitly opt in to a different behavior. Spec independence cannot be guaranteed in `Ordered` containers, so Ginkgo treats failures differently. When a spec in an `Ordered` container fails, all subsequent specs are skipped. Ginkgo will then run any `AfterAll` node closures to clean up after the specs. You can override this behavior by decorating an `Ordered` container with `ContinueOnFailure`. This is useful in cases where `Ordered` is being used to provide shared expensive set up for a collection of specs. When `ContinueOnFailure` is set, Ginkgo will continue running specs even if an earlier spec in the `Ordered` container has failed. If, however, a `BeforeAll` or `OncePerOrdered` `BeforeEach` node has failed, Ginkgo will skip all subsequent specs as the setup for the collection specs is presumed to have failed. `ContinueOnFailure` can only be applied to the outermost `Ordered` container. It is an error to apply it to a nested container. #### Combining Serial and Ordered To sum up: specs decorated with `Serial` are guaranteed to run in series and never in parallel with other specs. Specs in `Ordered` containers are guaranteed to run in order sequentially on the same parallel process but may be parallelized with specs in other containers. You can combine both decorators to have specs in `Ordered` containers run serially with respect to all other specs. To do this, you must apply the `Serial` decorator to the same container that has the `Ordered` decorator. You cannot declare a spec within an `Ordered` container as `Serial` independently. ### Filtering Specs There are several contexts where you may only want to run a _subset_ of specs in a suite. Perhaps some specs are slow and only need to be run on CI or before a commit. Perhaps you're only working on a subset of the code and want to run the relevant subset of the specs, or even just one spec. Perhaps a spec is under development and isn't ready to run yet. Perhaps a spec should always be skipped if a certain condition is met. Ginkgo supports all these usecases (and more) through a wide variety of mechanisms to organize and filter specs. Let's dig into them. #### Pending Specs You can mark individual specs, or containers of specs, as `Pending`. This is used to denote that a spec or its code is under development and should not be run. None of the other filtering mechanisms described in this chapter can override a `Pending` spec and cause it to run. Here are all the ways you can mark a spec as `Pending`: ```go // With the Pending decorator: Describe("these specs aren't ready for primetime", Pending, func() { ... }) It("needs work", Pending, func() { ... }) It("placeholder", Pending) //note: pending specs don't require a closure DescribeTable("under development", Pending, func() { ... }, ...) Entry("this one isn't working yet", Pending) // By prepending `P` or `X`: PDescribe("these specs aren't ready for primetime", func() { ... }) XDescribe("these specs aren't ready for primetime", func() { ... }) PIt("needs work", func() { ... }) XIt("placeholder") PDescribeTable("under development", func() {...}, ...) XEntry("this one isn't working yet") ``` Ginkgo will never run a pending spec. If all other specs in the suite pass, the suite will be considered successful. You can, however, run `ginkgo --fail-on-pending` to have Ginkgo fail the suite if it detects any pending specs. This can be useful on CI if you want to enforce a policy that pending specs should not be committed to source control. Note that pending specs are declared at compile time. You cannot mark a spec as pending dynamically at runtime. For that, keep reading... #### Skipping Specs If you need to skip a spec at runtime you can use Ginkgo's `Skip(...)` function. For example, say we want to skip a spec if some condition is not met. We could: ```go It("should do something, if it can", func() { if !someCondition { Skip("Special condition wasn't met.") } ... }) ``` This will cause the current spec to skip. Ginkgo will immediately end execution (`Skip`, just like `Fail`, throws a panic to halt execution of the current spec) and mark the spec as skipped. The message passed to `Skip` will be included in the spec report. Note that `Skip` **does not** fail the suite. Even skipping all the specs in the suite will not cause the suite to fail. Only an explicitly failure will do so. You can call `Skip` in any subject or setup nodes. If called in a `BeforeEach`, `Skip` will skip the current spec. If called in a `BeforeAll`, `Skip` will skip all specs in the `Ordered` container (however, skipping an individual spec in an `Ordered` container does not skip subsequent specs). If called in a `BeforeSuite`, `Skip` will skip the entire suite. You cannot call `Skip` in a container node - `Skip` only applies during the Run Phase, not the Tree Construction Phase. #### Focused Specs Ginkgo allows you to `Focus` individual specs, or containers of specs. When Ginkgo detects focused specs in a suite, it skips all other specs and _only_ runs the focused specs. Here are all the ways you can mark a spec as focused: ```go // With the Focus decorator: Describe("just these specs please", Focus, func() { ... }) It("just me please", Focus, func() { ... }) DescribeTable("run this table", Focus, func() { ... }, ...) Entry("run just this entry", Focus) // By prepending `F`: FDescribe("just these specs please", func() { ... }) FIt("just me please", func() { ... }) FDescribeTable("run this table", func() { ... }, ...) FEntry("run just this entry", ...) ``` doing so instructs Ginkgo to only run the focused specs. To run all specs, you'll need to go back and remove all the `F`s and `Focus` decorators. You can nest focus declarations. Doing so follows a simple rule: if a child node is marked as focused, any of its ancestor nodes that are marked as focused will be unfocused. This behavior was chosen as it most naturally maps onto the developers intent when iterating on a spec suite. For example: ```go FDescribe("some specs you're debugging", func() { It("might be failing", func() { ... }) It("might also be failing", func() { ... }) }) ``` will run both specs. Let's say you discover that the second spec is the one failing and you want to rerun it rapidly as you iterate on the code. Just `F` it: ```go FDescribe("some specs you're debugging", func() { It("might be failing", func() { ... }) FIt("might also be failing", func() { ... }) }) ``` now only the second spec will run because of Ginkgo's focus rules. We refer to the focus filtering mechanism as "Programmatic Focus" as the focus declarations are "programmed in" at compile time. Programmatic focus can be super helpful when developing or debugging a test suite, however it can be a real pain to accidentally commit a focused spec. So... When Ginkgo detects that a passing test suite has programmatically focused tests it causes the suite to exit with a non-zero status code. The logs will show that the suite succeeded, but will also include a message that says that programmatic specs were detected. The non-zero exit code will be caught by most CI systems and flagged, allowing developers to go back and unfocus the specs they committed. You can unfocus _all_ specs in a suite by running `ginkgo unfocus`. This simply strips off any `F`s off of `FDescribe`, `FContext`, `FIt`, etc... and removes `Focus` decorators. #### Spec Labels `Pending`, `Skip`, and `Focus` provide ad-hoc mechanisms for filtering suites. For particularly large and complex suites, however, you may need a more structured mechanism for organizing and filtering specs. For such usecases, Ginkgo provides labels. Labels are simply textual tags that can be attached to Ginkgo container and subject nodes via the `Label` decorator. Here are the ways you can attach labels to a node: ```go It("is labelled", Label("first label", "second label"), func() { ... }) It("is labelled", Label("first label"), Label("second label"), func() { ... }) ``` Labels can container arbitrary strings but cannot contain any of the characters in the set: `"&|!,()/"`. The labels associated with a spec is the union of all the labels attached to the spec's container nodes and subject nodes. For example: ```go Describe("Storing books", Label("integration", "storage"), func() { It("can save entire shelves of books to the central library", Label("network", "slow", "library storage"), func() { // has labels [integration, storage, network, slow, library storage] }) It("cannot delete books from the central library", Label("network", "library storage"), func() { // has labels [integration, storage, network, library storage] }) It("can check if a book is stored in the central library", Label("network", "slow", "library query"), func() { // has labels [integration, storage, network, slow, library query] }) It("can save books locally", Label("local"), func() { // has labels [integration, storage, local] }) It("can delete books locally", Label("local"), func() { // has labels [integration, storage, local] }) }) ``` The labels associated with a spec are included in any generated reports and are emitted alongside the spec whenever it fails. The real power, of labels, however, is around filtering. You can filter by label using via the `ginkgo --label-filter=QUERY` flag. Ginkgo will accept and parse a simple filter query language with the following operators and rules: - The `&&` and `||` logical binary operators representing AND and OR operations. - The `!` unary operator representing the NOT operation. - The `,` binary operator equivalent to `||`. - The `()` for grouping expressions. - Regular expressions can be provided using `/REGEXP/` notation. - All other characters will match as label literals. Label matches are **case insensitive** and trailing and leading whitespace is trimmed. To build on our example above, here are some label filter queries and their behavior: | Query | Behavior | | --- | --- | | `ginkgo --label-filter="integration"` | Match any specs with the `integration` label | | `ginkgo --label-filter="!slow"` | Avoid any specs labelled `slow` | | `ginkgo --label-filter="network && !slow"` | Run specs labelled `network` that aren't `slow` | | `ginkgo --label-filter=/library/` | Run specs with labels matching the regular expression `library` - this will match the three library-related specs in our example. ##### Label Sets In addition to flat strings, Labels can also construct sets. If a label has the format `KEY:VALUE` then a set with key `KEY` is created and the value `VALUE` is added to the set. For example: ```go Describe("The Library API", Label("API:Library"), func() { It("can fetch a list of books", func() { // has the labels [API:Library] // API is a set with value {Library} }) It("can fetch a list of books by shelf", Label("API:Shelf", "Readiness:Alpha"), func() { // has the labels [API:Library, API:Shelf, Readiness:Alpha] // API is a set with value {Library, Shelf} // Readiness is a set with value {Alpha} }) It("can fetch a list of books by zip code", Label("API:Geo", "Readiness:Beta"), func() { // has the labels [API:Library, API:Geo, Readiness:Beta] // API is a set with value {Library, Geo} // Readiness is a set with value {Beta} }) }) ``` Label filters can operate on sets using the notation: `KEY: SET_OPERATION `. The following set operations are supported: | Set Operation | Argument | Description | | --- | --- | --- | | `isEmpty` | None | Matches if the set with key `KEY` is empty (i.e. no label of the form `KEY:*` exists) | | `containsAny` | `SINGLE_VALUE` or `{VALUE1, VALUE2, ...}` | Matches if the `KEY` set contains _any_ of the elements in `ARGUMENT` | | `containsAll` | `SINGLE_VALUE` or `{VALUE1, VALUE2, ...}` | Matches if the `KEY` set contains _all_ of the elements in `ARGUMENT` | | `consistsOf` | `SINGLE_VALUE` or `{VALUE1, VALUE2, ...}` | Matches if the `KEY` set contains _exactly_ the elements in `ARGUMENT` | | `isSubsetOf` | `SINGLE_VALUE` or `{VALUE1, VALUE2, ...}` | Matches if the elements in the `KEY` set are a subset of the elements in `ARGUMENT` | Leading and trailing whitespace is always trimmed around keys and values and comparisons are always case-insensitive. Keys and values in the filter-language set operations are always literals; regular expressions are not supported. A special note should be made about the behavior of `isSubsetOf`: if the `KEY` set is empty then the filter will always match. This is because an empty set is always a subset of any other set. You can combine set operations with other label filters using the logical operators. For example: `ginkgo --label-filter="integration && !slow && Readiness: isSubsetOf {Beta, RC}"` will run all tests that have the label `integration`, do not have the label `slow` and have a `Readiness` set that is a subset of `{Beta, RC}`. This would exclude `Readiness:Alpha` but include specs with `Readiness:Beta` and `Readiness:RC` as well as specs with no `Readiness:*` label. Some more examples: | Query | Behavior | | --- | --- | | `ginkgo --label-filter="API: consistsOf {Library, Geo}"` | Match any specs for which the `API` set contains exactly `Library` and `Geo` | | `ginkgo --label-filter="API: containsAny Library"` | Match any specs for which the `API` set contains `Library` | | `ginkgo --label-filter="Readiness: isEmpty"` | Match any specs for which the `Readiness` set is empty | | `ginkgo --label-filter="Readiness: isSubsetOf Beta && !(API: containsAny Geo)"` | Match any specs for which the `Readiness` set is a subset of `{Beta}` (or empty) and the `API` set does not contain `Geo` | Label sets are helpful for organizing and filtering large spec suites in which different specs satisfy multiple overlapping concerns. The use of label set filters is intended to be a more powerful and expressive alternative to the use of regular expressions. If you find yourself using a regular expression, consider if you should be using a label set instead. ##### Listing Labels You can list the labels used in a given package using the `ginkgo labels` subcommand. This does a simple/naive scan of your test files for calls to `Label` and returns any labels it finds. You can iterate on different filters quickly with `ginkgo --dry-run -v --label-filter=FILTER`. This will cause Ginkgo to tell you which specs it will run for a given filter without actually running anything. ##### Runtime Label Evaluation If you want to have finer-grained control within a test about what code to run/not-run depending on what labels match/don't match the filter you can perform a manual check against the label-filter passed into Ginkgo like so: ```go It("can save books remotely", Label("network", "slow", "library query") { if Label("performance").MatchesLabelFilter(GinkgoLabelFilter()) { exp := gmeasure.NewExperiment() // perform some benchmarking with exp... } // rest of the saving books test }) ``` here `GinkgoLabelFilter()` returns the configured label filter passed in via `--label-filter`. With a setup like this you could run `ginkgo --label-filter="network && !performance"` - this would select the `"can save books remotely"` spec but not run the benchmarking code in the spec. Of course, this could also have been modeled as a separate spec with the `performance` label. ##### Suite-Level Labels Finally, in addition to specifying Labels on subject and container nodes you can also specify suite-wide labels by decorating the `RunSpecs` command with `Label`: ```go func TestBooks(t *testing.T) { RegisterFailHandler(Fail) RunSpecs(t, "Books Suite", Label("books", "this-is-a-suite-level-label")) } ``` Suite-level labels apply to the entire suite making it easy to filter out entire suites using label filters. #### Location-Based Filtering Ginkgo allows you to filter specs based on their source code location from the command line. You do this using the `ginkgo --focus-file` and `ginkgo --skip-file` flags. Ginkgo will only run specs that are in files that _do_ match the `--focus-file` filter *and* _don't_ match the `--skip-file` filter. You can provide multiple `--focus-file` and `--skip-file` flags. The `--focus-file`s will be ORed together and the `--skip-file`s will be ORed together. The argument passed to `--focus-file`/`--skip-file` is a file filter and takes one of the following forms: - `FILE_REGEX` - will match specs in files whose absolute path matches the FILE_REGEX. So `ginkgo --focus-file=foo` will match specs in files like `foo_test.go` or `/foo/bar_test.go`. - `FILE_REGEX:LINE` - will match specs in files that match FILE_REGEX where at least one node in the spec is constructed at line number `LINE`. - `FILE_REGEX:LINE1-LINE2` - will match specs in files that match FILE_REGEX where at least one node in the spec is constructed at a line within the range of `[LINE1:LINE2)`. You can specify multiple comma-separated `LINE` and `LINE1-LINE2` arguments in a single `--focus-file/--skip-file` (e.g. `--focus-file=foo:1,2,10-12` will apply filters for line 1, line 2, and the range [10-12)). To specify multiple files, pass in multiple `--focus-file` or `--skip-file` flags. To filter a spec based on its line number you must use the exact line number where one of the spec's nodes (e.g. `It()`) is called. You can't use a line number that is "close" to the node, or within the node's closure. #### Description-Based Filtering Finally, Ginkgo allows you to filter specs based on the description strings that appear in their subject nodes and/or container hierarchy nodes. You do this using the `ginkgo --focus=REGEXP` and `ginkgo --skip=REGEXP` flags. When these flags are provided, Ginkgo matches the passed-in regular expression against the fully concatenated description of each spec. For example the spec tree: ```go Describe("Studying books", func() { Context("when the book is long", func() { It("can be read over multiple sessions", func() { }) }) }) ``` will generate a spec with description `"Studying books when the book is long can be read over multiple sessions"`. When `--focus` and/or `--skip` are provided, Ginkgo will _only_ run specs with descriptions that match the focus regexp **and** _don't_ match the skip regexp. You can provide `--focus` and `--skip` multiple times. The `--focus` filters will be ORed together and the `--skip` filters will be ORed together. For example, say you have the following specs: ```go It("likes dogs", func() {...}) It("likes purple dogs", func() {...}) It("likes cats", func() {...}) It("likes dog fish", func() {...}) It("likes cat fish", func() {...}) It("likes fish", func() {...}) ``` then `ginkgo --focus=dog --focus=fish --skip=cat --skip=purple` will only run `"likes dogs"`, `"likes dog fish"`, and `"likes fish"`. The description-based `--focus` and `--skip` flags were Ginkgo's original command-line based filtering mechanism and will continue to be supported - however, we recommend using labels when possible as the label filter language is more flexible and easier to reason about. #### Combining Filters To sum up, we've seen that Ginkgo supports the following mechanisms for organizing and filtering specs: - Specs that are marked as `Pending` at compile-time never run. - At run-time, specs can be individually skipped by calling `Skip()` - Specs that are programmatically focused with the `Focus` decorator at compile-time run to the exclusion of other specs. - Specs can be labelled with the `Label()` decorator. `ginkgo --label-filter=QUERY` will apply a label filter query and only run specs that pass the filter. - `ginkgo --focus-file=FILE_FILTER/--skip-file=FILE_FILTER` will filter specs based on their source code location. - `ginkgo --focus=REGEXP/--skip=REGEXP` will filter specs based on their descriptions. These mechanisms can all be used in concert. They combine with the following rules: - `Pending` specs are always pending and can never be coerced to run by another filtering mechanism. - Specs that invoke `Skip()` will always be skipped regardless of other filtering mechanisms. - Programmatic filters always apply and result in a non-zero exit code. Any additional CLI filters only apply to the subset of specs selected by the programmatic filters. - When multiple CLI filters (`--label-filter`, `--focus-file/--skip-file`, `--focus/--skip`) are provided, they are all ANDed together. The spec must satisfy the label filter query **and** any location-based filters **and** any description based filters. If you have a large test suite and would like to avoid printing out all the `S` skip delimiters, you can run with `--silence-skips` to suppress them. #### Avoiding filtering out all tests Especially for CI, it is useful to fail when all tests were filtered out by accident (either via skip or typo in label filter). `ginkgo --fail-on-empty --label-filter mytypo ./...` will fail since no test was run. ### Repeating Spec Runs and Managing Flaky Specs Ginkgo wants to help you write reliable, deterministic, tests. Flaky specs - i.e. specs that fail _sometimes_ in non-deterministic or difficult to reason about ways - can be incredibly frustrating to debug and can erode faith in the value of a spec suite. Ginkgo provides a few mechanisms to help you suss out and debug flaky specs. If you suspect a flaky spec, you can rerun a suite repeatedly until it fails via: ```bash ginkgo --until-it-fails ``` This will compile the suite once and then run it repeatedly, forever, until a failure is detected. This flag pairs well with `--randomize-all` and `-p` to try and suss out failures due to accidental spec dependencies. Since `--until-it-fails` runs indefinitely until a failure is detected, it is not appropriate for CI environments. If you'd like to help ensure that flaky specs don't creep into your codebase you can use: ```bash ginkgo --repeat=N ``` to have Ginkgo repeat your test suite up to `N` times or until a failure occurs, whichever comes first. This is especially valuable in CI environments. One quick note on `--repeat`: when you invoke `ginkgo --repeat=N`, Ginkgo will run your suite a total of `1+N` times. In this way, `ginkgo --repeat=N` is similar to `go test --count=N+1`, **however**, `--count` is one of the few `go test` flags that is **not** compatible with Ginkgo suites. Please use `ginkgo --repeat=N` instead. Both `--until-it-fails` and `--repeat` help you identify flaky specs early. Doing so will help you debug flaky specs while the context that introduced them is fresh. A more granular approach to repeating specs is by decorating individual subject or container nodes with the MustPassRepeatedly(N) decorator: ```go Describe("Storing books", func() { It("can save books to the central library", MustPassRepeatedly(3), func() { // this spec has been marked and will be retried up to 3 times }) It("can save books locally", func() { // this spec has not been marked and will not be retired }) }) ``` However, There are times when the cost of preventing and/or debugging flaky specs is simply too high and specs simply need to be retried. While this should never be the primary way of dealing with flaky specs, Ginkgo is pragmatic about this reality and provides a mechanism for retrying specs. You can retry all specs in a suite via: ```bash ginkgo --flake-attempts=N ``` Now, when a spec fails, Ginkgo will not automatically mark the suite as failed. Instead, it will attempt to rerun the spec up to `N` times. If the spec succeeds during a retry, Ginkgo moves on and marks the suite as successful but reports that the spec needed to be retried. A more granular approach is also provided for this functionality with the use of the `FlakeAttempts(N)` decorator: ```go Describe("Storing books", func() { It("can save books to the central library", FlakeAttempts(3), func() { // this spec has been marked as flaky and will be retried up to 3 times }) It("can save books locally", func() { // this spec must always pass on the first try }) }) ``` Ginkgo's retry behavior generally works as you'd expect with most specs, however, there is some complexity when `FlakeAttempts` is applied to `Ordered` containers. In brief, Ginkgo generally guarantees that `BeforeAll` and `AfterAll` node closures only run once - but `FlakeAttempts` can modify this behavior. If a failure occurs within a subject node in an `Ordered` container (i.e. in an `It`) then Ginkgo will rerun that `It` but not the `BeforeAll` or `AfterAll`. However, if a failure occurs in a `BeforeAll`, Ginkgo will immediately run the `AfterAll` (to clean up) and then rerun the `BeforeAll`. Stepping back - it bears repeating: you should use `FlakeAttempts` judiciously. The best approach to managing flaky spec suites is to debug flakes early and resolve them. More often than not they are telling you something important about your architecture. In a world of competing priorities and finite resources, however, `FlakeAttempts` provides a means to explicitly accept the technical debt of flaky specs and move on. ### Getting Visibility Into Long-Running Specs Ginkgo is often used to build large, complex, integration suites and it is a common - if painful - experience for these suites to run slowly. Ginkgo provides numerous mechanisms that enable developers to get visibility into what part of a suite is running and where, precisely, a spec may be lagging or hanging. Ginkgo can provide a **Progress Report** of what is currently running in response to the `SIGINFO` and `SIGUSR1` signals. The Progress Report includes information about which node is currently running and the exact line of code that it is currently executing, along with any relevant goroutines that were launched by the spec. The report also includes the 10 most recent lines written to the `GinkgoWriter`. A developer waiting for a stuck spec can get this information immediately by sending either the `SIGINFO` or `SIGUSR1` signal (on MacOS/BSD systems, `SIGINFO` can be sent via `^T` - making it especially convenient; if you're on linux you'll need to send `SIGUSR1` to the actual test process spawned by `ginkgo` - not the `ginkgo` cli process itself). These Progress Reports can also show you a preview of the running source code, but only if Ginkgo can find your source files. If need be you can tell Ginkgo where to look for source files by specifying `--source-root`. Finally - you can instruct Ginkgo to provide Progress Reports automatically whenever a node takes too long to complete. You do this by passing the `--poll-progress-after=INTERVAL` flag to specify how long Ginkgo should wait before emitting a progress report. Once this interval is passed, Ginkgo can periodically emit Progress Reports - the interval between these reports is controlled via the `--poll-progress-interval=INTERVAL` flag. By default `--poll-progress-after` is set to `0` and so Ginkgo does not emit Progress Reports. You can override the global setting of `poll-progress-after` and `poll-progress-interval` on a per-node basis by using the `PollProgressAfter(INTERVAL)` and `PollProgressInterval(INTERVAL)` decorators. A value of `0` will explicitly turn off Progress Reports for a given node regardless of the global setting. All Progress Reports generated by Ginkgo - whether interactively via `SIGINFO/SIGUSR1` or automatically via the `PollProgressAfter` configuration - also appear in Ginkgo's [machine-readable reports](#generating-machine-readable-reports). In addition to these formal Progress Reports, Ginkgo tracks whenever a node begins and ends. These node `> Enter` and `< Exit` events are usually only logged in the spec's timeline when running with `-vv`, however, you can turn them on for other verbosity modes using the `--show-node-events` flag. #### Attaching Additional Information to Progress Reports **This section describes an experimental feature and the public-facing interface may change in a future minor version of Ginkgo** Ginkgo also allows you to attach Progress Report providers to provide additional information when a progress report is generated. For example, these could query the system under test for diagnostic information about its internal state and report back. You attach these providers via `AttachProgressReporter`. For example: ```go AttachProgressReporter(func() string { libraryState := library.GetStatusReport() return fmt.Sprintf("%s: %s", library.ClientID, libraryState.Summary) }) ``` `AttachProgressReporter` returns a `cancel` func that you can call to unregister the progress reporter. This allow you to do things like: ```go BeforeEach(func() { library = libraryClient.ConnectAs("Jean ValJean") //we attach a progress reporter and can trust that it will be cleaned up after the spec runs DeferCleanup(AttachProgressReporter(func() string { libraryState := library.GetStatusReport() return fmt.Sprintf("%s: %s", library.ClientID, libraryState.Summary) })) }) ``` Note that the functions called by `AttachProgressReporter` must not block. Ginkgo currently has a hard-coded 5 second limit. If all attached progress reporters take longer than 5 seconds to report back, Ginkgo will move on so as to prevent the suite from blocking. ### Spec Timeouts and Interruptible Nodes Sometimes specs get stuck. Perhaps a network call is running slowly; or a newly introduced bug has caused an asynchronous process the test is relying on to hang. It's important, in such cases, to be able to set a deadline for a given spec or node and require the spec/node to complete before the deadline has elapsed. Ginkgo supports this through a collection of timeout-related decorators and the notion of **Interruptible Nodes**. #### Interruptible Nodes and SpecContext We've seen [how Ginkgo handles failures](#mental-model-how-ginkgo-handles-failure) when an explicit (or implicit, if using a matcher library) call to `Fail` takes place: `Fail` raises a panic to indicate a failure and immediately exits the current node. Such failures emanate from _within_ a node's running goroutines. However, in the context of a timeout, the cause of failure comes from _outside_ a node's running goroutine. Once a deadline has passed, Ginkgo can mark a spec as failed, but also needs a mechanism to notify the current node's running goroutine that it is time to stop trying and exit. Ginkgo supports this through the notion of an Interruptible Node. A node is considered interruptible if it has a callback that takes either a `SpecContext` or `context.Context` object: ```go It("can save books", func(ctx SpecContext) { book := &books.Book{ Title: "Les Miserables", Author: "Victor Hugo", Pages: 2783, } Expect(libraryClient.SaveBook(ctx, book)).To(Succeed()) Expect(libraryClient.ListBooks(ctx)).To(ContainElement(book)) }) ``` when such a node is detected, Ginkgo will automatically supply a `SpecContext` object. This `SpecContext` object satisfies the `context.Context` interface and can be used anywhere a `context.Context` object is used. When a spec times out or is interrupted by the user (see below), Ginkgo will cancel the `SpecContext` to signal to the spec that it is time to exit. In the case above, it is assumed that `libraryClient` knows how to return once `ctx` is cancelled. Only setup and subjects nodes can be interruptible. Container nodes cannot be interrupted. As a more explicit example, here's a (contrived) example to illustrate a timeout in action: ```go It("likes to sleep in", func(ctx context.Context) { select { case <-ctx.Done(): return case <-time.After(time.Hour): ... } }, NodeTimeout(time.Second)) ``` rather than hanging for an hour, this spec will exit (and be marked as failed due to a timeout), soon after the one second NodeTimeout deadline elapses. When the deadline elapses, Ginkgo takes a [Progress Report](#getting-visibility-into-long-running-specs) snapshot to document where, exactly, the goroutine was stuck when the timeout occurred. Because it is important to take the snapshot just before the context is cancelled, Ginkgo manages the timing of the cancellation directly and does not rely on a `context.WithDeadline()`-flavored context. As a result, calling `ctx.Deadline()` will not return the deadline of the node in question - however, you can trust that `ctx.Done()` will be closed on time. Note that you are allowed to pass in either `SpecContext` or the more canonical `context.Context` as shown in this example. The `SpecContext` object has a few additional methods attached to it and serves as an extension point for third-party libraries (including Gomega). You are free to wrap `SpecContext` however you wish (e.g. via `context.WithValue(ctx, "key", "value")`) - Ginkgo will continue to cancel the resulting context at the correct time and third-party libraries will still have access to the full-blown `SpecContext` object as it is stored as a value within the context with the `"GINKGO_SPEC_CONTEXT"` key. #### The SpecTimeout and NodeTimeout Decorators We saw a quick preview of the `NodeTimeout` decorator above. This applies a timeout deadline to a single node and can be applied to any interruptible node. Once the `NodeTimeout` elapses, Ginkgo will cancel the interruptible node's context. `SpecTimeout` is similar to `NodeTimeout` but can only decorate `It` nodes and acts as a deadline for the lifecycle of the spec. That is, all nodes associated with the spec need to complete before `SpecTimeout` expires. Note that individual nodes within the spec can also have a `NodeTimeout` - however, that timeout can only ever be more stringent than the deadline implied by `SpecTimeout`. Here's a simple example: ```go Describe("interacting with the library", func() { BeforeEach(func(ctx SpecContext) { libraryClient = library.NewClient() Expect(libraryClient.Connect(ctx)).To(Succeed()) }, NodeTimeout(time.Millisecond * 500)) It("can save books", func(ctx SpecContext) { book := &books.Book{ Title: "Les Miserables", Author: "Victor Hugo", Pages: 2783, } Expect(libraryClient.SaveBook(ctx, book)).To(Succeed()) Expect(libraryClient.ListBooks(ctx)).To(ContainElement(book)) }, SpecTimeout(time.Second*2)) AfterEach(func(ctx SpecContext) { Expect(libraryClient.Cleanup(ctx, "books")).To(Succeed()) }, NodeTimeout(time.Second)) }) ``` here the total runtime of `BeforeEach`, `It`, and `AfterEach` must be less than the `SpecTimeout` of 2 seconds. In addition, the `BeforeEach` callback must exit within 500ms and the `AfterEach` after 1 second. When a `SpecTimeout` expires, the current node is interrupted (i.e. it's context is cancelled) and Ginkgo proceeds to run any associated clean up nodes (i.e. any `AfterEach`, `AfterAll`, and `DeferCleanup` nodes) subject to their own `NodeTimeout`s. This is because cleanup is considered an essential part of the spec lifecycle and must not be skipped if possible. Thus, the `SpecTimeout` is not a strict guarantee on the runtime of a spec but rather a threshold at which the spec will be considered failed. Currently, `SpecTimeout` and `NodeTimeout` cannot be applied to container nodes. #### Mental Model: The Life-cycle of Interruptions and the GracePeriod Decorator Interruptible nodes and the `SpecTimeout`/`NodeTimeout` decorators allow you to enforce deadlines at a granular per-spec/per-node level. But what happens when a node fails to return after its `SpecContext` is cancelled? What happens if it's _really_ stuck? When a node times out, Ginkgo cancels its `SpecContext` and then waits for it to exit for a period of time called the **Grace Period**. If the node exits within the Grace Period, Ginkgo will continue with the relevant portions of the spec (specifically, Ginkgo will behave as if a failure occurred and skip any subsequent setup or subject nodes and, instead, simply run through the cleanup nodes). If, however, the node does not exit within the Grace Period, Ginkgo will allow the node to _leak_ and proceed with the relevant portion of the spec. A leaked node continues to run in the background - and this can, potentially, be a source of confusion for future specs as a leaked node can interact with Ginkgo's global callbacks (e.g. `Fail`, or `AddReportEntry`) and pollute the currently running spec. For this reason, it's important to write specs that respond to cancelled contexts and exit as soon as possible. Nonetheless, Ginkgo takes the opinion that it is better to potentially leak a node and continue with the suite than to allow the suite to hang forever. When a node is leaked due to a timeout and elapsed Grace Period, Ginkgo will emit a message stating that the node has leaked along with a [Progress Report](#getting-visibility-into-long-running-specs) that shows the currently running code in the leaked goroutine. The Grace Period can be configured on a per-node basis using the `GracePeriod` decorator (which can be applied to any interruptible node) and/or globally with the `--grace-period=` cli flag. One final, somewhat complex, note on timeouts and the Grace Period. As mentioned above (and as you'll see below), when a `SpecTimeout` or user-initiated interrupt occurs, Ginkgo will interrupt the current node by cancelling its context, and then run any relevant cleanup nodes. These cleanup nodes **must** run to ensure specs clean up after themselves, however, they are now running in a setting where the spec is out of time and needs to wind down as soon as possible. To facilitate this, Ginkgo applies a timeout to each of these remaining nodes as follows: - If the remaining node is interruptible and has a `NodeTimeout`, Ginkgo uses that `NodeTimeout` to set a deadline for the node. If the deadline expires, then a Grace Period applies (either the node's `GracePeriod` or the global `--grace-period`) before Ginkgo leaks the node and moves on. - If the remaining node is interruptible and **does not** have a `NodeTimeout`, Ginkgo uses the Grace Period to set a deadline for the node. If the deadline expires, then a second Grace Period applies before Ginkgo leaks the node and moves on. - If the remaining node is **not** interruptible, Ginkgo will give the node a single Grace Period to complete and exit. In this case, since it cannot be interrupted, Ginkgo will simply leak the node after one Grace Period. #### Using SpecContext with Gomega's Eventually Gomega provides `Eventually` to allow you to poll an object or function repeatedly until a Gomega matcher is satisfied. `Eventually` integrates cleanly with interruptible nodes by accepting a `SpecContext`/`context.Context` parameter. This allows you, for example, to enforce a single timeout across a set of polling assertions: ```go Describe("interacting with the library", func() { BeforeEach(func(ctx SpecContext) { libraryClient = library.NewClient() // we use eventually here to keep trying until we succeed (e.g. perhaps the server is still spinning up) Eventually(func() error { return libraryClient.Connect(ctx) }).WithContext(ctx).Should(Succeed()) }, NodeTimeout(time.Millisecond * 500)) It("can save books", func(ctx SpecContext) { numBooks := libraryClient.CountBooks(ctx) book := &books.Book{ Title: "Les Miserables", Author: "Victor Hugo", Pages: 2783, } Expect(libraryClient.SaveBook(ctx, book)).To(Succeed()) // perhaps the library is a distributed system that only converges eventually Eventually(func() ([]*books.Book, error) { return libraryClient.ListBooksByAuthor(ctx, "Victor Hugo") }).WithContext(ctx).Should(ContainElement(book)) Eventually(func() int { return libraryClient.CountBooks(ctx) }).WithContext(ctx).Should(Equal(numBooks + 1)) }, SpecTimeout(time.Second*2)) AfterEach(func(ctx SpecContext) { Expect(libraryClient.Cleanup(ctx, "books")).To(Succeed()) // let's make sure we eventually clean up Eventually(func() int { return libraryClient.CountBooks(ctx) }).WithContext(ctx).Should(Equal(0)) }, NodeTimeout(time.Second)) }) ``` now, if any of the node contexts are cancelled, (either due to a timeout or an interruption) `Eventually` will exit immediately with an appropriate failure. We've written out this example in full to show how the context is passed _both_ to `Eventually` via `.WithContext(ctx)` _and_ to the various client methods that take a context. For example: ```go Eventually(func() ([]*books.Book, error) { return libraryClient.ListBooksByAuthor(ctx, "Victor Hugo") }).WithContext(ctx).Should(ContainElement(book)) ``` This is important as the cancellation of the context needs to cause `ListBooksByAuthor` to exit _and_ `Eventually` to stop retrying. This is a common-enough pattern that Gomega provides some short hand. If you pass `Eventually` a function that takes a `context.Context` as its first parameter, Gomega will pass in the context attached via `.WithContext()` automatically. This allows us to turn statements like this: ```go Eventually(func() error { return libraryClient.Connect(ctx) }).WithContext(ctx).Should(Succeed()) ``` into: ```go Eventually(libraryClient.Connect).WithContext(ctx).Should(Succeed()) ``` This also works well with Gomega's `.WithArguments(...)` method which allows us to turn statements like this: ```go Eventually(func() ([]*books.Book, error) { return libraryClient.ListBooksByAuthor(ctx, "Victor Hugo") }).WithContext(ctx).Should(ContainElement(book)) ``` into: ```go Eventually(libraryClient.ListBooksByAuthor).WithContext(ctx).WithArguments("Victor Hugo").Should(ContainElement(book)) ``` all told this allows us to rewrite our example as: ```go Describe("interacting with the library", func() { BeforeEach(func(ctx SpecContext) { libraryClient = library.NewClient() // we use eventually here to keep trying until we succeed (e.g. perhaps the server is still spinning up) Eventually(libraryClient.Connect).WithContext(ctx).Should(Succeed()) }, NodeTimeout(time.Millisecond * 500)) It("can save books", func(ctx SpecContext) { numBooks := libraryClient.CountBooks(ctx) book := &books.Book{ Title: "Les Miserables", Author: "Victor Hugo", Pages: 2783, } Expect(libraryClient.SaveBook(ctx, book)).To(Succeed()) // perhaps the library is a distributed system that only converges eventually Eventually(libraryClient.ListBooksByAuthor).WithContext(ctx).WithArguments("Victor Hugo").Should(ContainElement(book)) Eventually(libraryClient.CountBooks).WithContext(ctx).Should(Equal(numBooks + 1)) }, SpecTimeout(time.Second*2)) AfterEach(func(ctx SpecContext) { Expect(libraryClient.Cleanup(ctx, "books")).To(Succeed()) // let's make sure we eventually clean up Eventually(libraryClient.CountBooks).WithContext(ctx).Should(Equal(0)) }, NodeTimeout(time.Second)) }) ``` which is much cleaner! Lastly, there's another reason you'll want to pass the `SpecContext` to `Eventually`. Gomega uses the extension point provided by `SpecContext` to provide additional information whenever a [Progress Report](#getting-visibility-into-long-running-specs) is requested. This allows you to get deeper visibility into the state of a running `Eventually` simply by requesting a Progress Report (either by sending a `SIGINFO`/`SIGUSR1` or by using the `PollProgressAfter` decorator). For example, imagine this assertion: ```go Eventually(libraryClient.ListBooksByAuthor).WithContext(ctx).WithArguments("Victor Hugo").Should(ContainElement(book)) ``` is stuck waiting. A generated Progress Report would show the current state of the `Eventually` assertion which would include the failure message associated with the `ContainElement` matcher: ``` Expected <[]*Book | len:3, cap:3>: [ "The Hunchback of Notre Dame", "Notre Dame de Paris", "L'Homme Qui Rit", ] to contain element matching <*Book>: Les Miserables ``` #### Interruptible Node Function Signatures: A Quick Reference Most Ginkgo nodes can be made interruptible. **Setup** and **Subject** nodes typically take a simple `func() {}` but can be made interruptible like so: ```go BeforeEach(func(ctx SpecContext) { ... }) It("is interruptible", func (ctx context.Context) { ... }) AfterEach(func(ctx context.Context) { ... }) ``` Note that both `context.Context` and `SpecContext` are valid. In addition, the **Suite Setup** nodes can be made interruptible. In the case of `BeforeSuite`, `AfterSuite`, and `SynchronizedAfterSuite` this is similar to the setup and subject nodes above: ```go BeforeSuite(func(ctx SpecContext) { ... }) AfterSuite(func(ctx SpecContext) { ... }) SynchronizedAfterSuite(func(ctx SpecContext) { ... }, func(ctx context.Context) { ... }) ``` Note that the `SynchronizedAfterSuite` takes two functions - the first runs on all processes, the second only on process 1. Each of these can be optionally passed a context, making them independently interruptible (or not, if no context is passed in). `SynchronizedBeforeSuite` also support independently interruptible functions. [Recall](#parallel-suite-setup-and-cleanup-synchronizedbeforesuite-and-synchronizedaftersuite) that the two callbacks associated with `SynchronizedBeforeSuite` can optionally return and receive a `[]byte` array to facilitate communication between the primary process and the other parallel processes. This optionality expands the set of possible interruptible signatures. For example: ```go SynchronizedBeforeSuite(func(ctx SpecContext) { ... }, func(ctx SpecContext) { ... }) SynchronizedBeforeSuite(func(ctx SpecContext) []byte { return []byte{"data"} }, func(ctx SpecContext, b []byte) { ... }) ``` are all valid interruptible signatures. Of course you can specify `context.Context` instead and can mix-and-match interruptibility between the two functions. **Reporting** nodes `ReportAfterEach`, `ReportBeforeEach`, `ReportBeforeSuite` `ReportAfterSuite` can be made interruptible, to do this you need to provide it a node function which accepts both `SpecContext` and `SpecReport` for `*Each` nodes and `Report` for `*Suite` nodes. As for **Container** nodes, since these run during the Tree Construction Phase, they cannot be made interruptible and so do not accept functions that expect a context. And since the `By` annotation is simply syntactic sugar enabling more detailed spec documentation, any callbacks passed to `By` cannot be independently marked as interruptible (you should, instead, use the `context` passed into the node that you're calling `By` from). Finally, there *are* two other Ginkgo constructs that can be made interruptible and their flexibility warrants some specific coverage in this section: `DeferCleanup` and `DescribeTable`. Recall that [`DeferCleanup`](#cleaning-up-our-cleanup-code-defercleanup) effectively generates a dynamic `After*` node for your spec. It's important to note that the lifecycle of this generated node is different from the lifecycle of the node in which `DeferCleanup` was called. Consider, our earlier example: ```go Describe("interacting with the library", func() { BeforeEach(func(ctx SpecContext) { libraryClient = library.NewClient() Expect(libraryClient.Connect(ctx)).To(Succeed()) }, NodeTimeout(time.Millisecond * 500)) It("can save books", func(ctx SpecContext) { ... }, SpecTimeout(time.Second*2)) AfterEach(func(ctx SpecContext) { Expect(libraryClient.Cleanup(ctx, "books")).To(Succeed()) }, NodeTimeout(time.Second)) }) ``` We can tidy things up by replacing the `AfterEach` with a `DeferCleanup` in the `BeforeEach`: ```go /* === INVALID === */ Describe("interacting with the library", func() { BeforeEach(func(ctx SpecContext) { libraryClient = library.NewClient() Expect(libraryClient.Connect(ctx)).To(Succeed()) DeferCleanup(func() { libraryClient.Cleanup(ctx, "books") }) }, NodeTimeout(time.Millisecond * 500)) It("can save books", func(ctx SpecContext) { ... }, SpecTimeout(time.Second*2)) }) ``` however, we've committed a subtle error. We've captured the `BeforeEach`'s `SpecContext` and passed it in to the `DeferCleanup` function. However, the `DeferCleanup` function will only run _after_ the `BeforeEach` completes (and its `SpecContext` has been cancelled) - as a result `libraryClient.Cleanup` will always receive a cancelled context. Moreover, we want to preserve the fact that our `BeforeEach` has a 500ms timeout whereas our clean up code has a separate 1 second timeout. The correct way to write this is to make the `DeferCleanup` node interruptible and decorate it with its own `NodeTimeout`: ```go Describe("interacting with the library", func() { BeforeEach(func(ctx SpecContext) { libraryClient = library.NewClient() Expect(libraryClient.Connect(ctx)).To(Succeed()) DeferCleanup(func(ctx SpecContext) { libraryClient.Cleanup(ctx, "books") }, NodeTimeout(time.Second)) }, NodeTimeout(time.Millisecond * 500)) It("can save books", func(ctx SpecContext) { ... }, SpecTimeout(time.Second*2)) }) ``` (as before, we could have used `context.Context` instead of `SpecContext`). This is looking better but we can do more. Recall that `DeferCleanup` can take additional parameters at invocation to pass along to its function. If the first argument that its function expects is a context, `DeferCleanup` will automatically treat the function as interruptible and provide it with a `SpecContext`. This allows us to write: ```go Describe("interacting with the library", func() { BeforeEach(func(ctx SpecContext) { libraryClient = library.NewClient() Expect(libraryClient.Connect(ctx)).To(Succeed()) DeferCleanup(libraryClient.Cleanup, "books", NodeTimeout(time.Second)) }, NodeTimeout(time.Millisecond * 500)) It("can save books", func(ctx SpecContext) { ... }, SpecTimeout(time.Second*2)) }) ``` As an aside, if you _don't_ want Ginkgo to inject `SpecContext`, you can, instead, provide your own context. Here, for example, we avoid making the `DeferCleanup` interruptible by passing in our own context: ```go DeferCleanup(libraryClient.Cleanup, "books", NodeTimeout(time.Second)) //interruptible DeferCleanup(libraryClient.Cleanup, context.Background(), "books") //*not* interruptible ``` The heuristic here is simple: if the function passed to `DeferCleanup` takes a `context.Context` as its first argument and a context is passed in as the first parameter to `DeferCleanup` then the function is not interruptible and the passed-in context is used. Otherwise, the function is considered interruptible and a `SpecContext` is passed-in instead. If, instead, the first argument to the function is specifically a `SpecContext` then the function is always considered interruptible regardless of what the subsequent parameters are. `DescribeTable` behaves similarly. You can make the `It`s generated by your table interruptible by passing a `SpecContext` or `context.Context` as the first argument to the table function: ```go DescribeTable("shelf counts", func(ctx SpecContext, shelf string, count int) { // or context.Context instead Expect(libraryClient.Count(ctx, shelf)).To(Equal(count)) }, Entry("books on shelf A", "A", 17, NodeTimeout(time.Second)), Entry("books on shelf B", "B", 20, NodeTimeout(time.Second)), ... ) ``` Note that the `NodeTimeout` decorators go on the individual entries. If you also want to specify a [custom entry description generator](#generating-entry-descriptions), you can pass in a function that takes the non-`SpecContext` parameters and returns `string`: ```go DescribeTable("shelf counts", func(ctx SpecContext, shelf string, count int) { // or context.Context instead Expect(libraryClient.Count(ctx, shelf)).To(Equal(count)) }, func(shelf string, _ int) string { return fmt.Sprintf("books on shelf %s", shelf) } Entry("books on shelf A", "A", 17, NodeTimeout(time.Second)), Entry("books on shelf B", "B", 20, NodeTimeout(time.Second)), ... ) ``` As with `DeferCleanup`, Ginkgo will detect if the entry parameter list provides a context. Doing so will avoid treating the function as interruptible and use the provided context instead. For example: ```go DescribeTable("contrived context-value example", func(ctx context.Context, result string) { //but **NOT** SpecContext Expect(libraryClient.Encabulate(ctx)).To(Equal(result)) }, Entry("with a generic context", context.Background(), "Nothin"), Entry("with a context with a magical value", context.WithValue(context.Background(), "magic", "word"), "Geminio"), ... ) ``` #### SpecContext and Progress Reports `SpecContext` provides an extension point that enables consumers to attach additional information to Progress Reports that Ginkgo generates. This is accomplished by calling `ctx.AttachProgressReporter(f)` where `f` has the signature `func() string`. Once attached, the function will be called whenever a Progress Report needs to be generated (e.g. due to a user request via `SIGINFO`/`SIGUSR1` or via an interrupt or timeout). `ctx.AttachProgressReporter` returns a detach function with signature `func()` that can be called to detach the attached progress reporter. Because these progress reporters are attached to the passed-in `SpecContext`, they only remain attached for the lifecycle of the context: i.e. the current node. While users of Ginkgo can provide their own custom progress reporters, the intent behind this extension point is to allow deeper integration between Ginkgo and third-party libraries, specifically Gomega. Whenever Gomega's `Eventually` is passed a `SpecContext`, it automatically registers a progress reporter. This reporter will provide the latest state of the `Eventually` matcher - enabling users to get insight into where and why an `Eventually` might be stuck simply by asking for a Progress Report. ### Interrupting, Aborting, and Timing Out Suites We've seen how nodes can be marked as interruptible and focused on how Ginkgo can apply deadlines to individual nodes and interrupt them when a timeout expires. Ginkgo also provides a few, related, mechanisms for interrupting a _suite_ before all specs have naturally completed. First, you can signal to a suite that it must stop running by sending a `SIGINT` or `SIGTERM` signal to the running ginkgo process (or just hit `^C`). Second, you can also specify a timeout on a suite (or set of suites) via: ```bash ginkgo --timeout=duration ``` where `duration` is a parseable go duration string (the default is `1h` -- one hour). When running multiple suites, Ginkgo will ensure that the total runtime of _all_ the suites does not exceed the specified timeout. Finally, you can abort a suite from within the suite by calling `Abort()`. This will immediately end the suite and is the programmatic equivalent of sending an interrupt signal to the test process. All three mechanisms have the same effects. If the currently running node is interruptible, then Ginkgo will: - Emit a [Progress Report](#getting-visibility-into-long-running-specs) for the current spec as possible. - Interrupt the current node by cancelling its SpecContext... - ...then wait up to the Grace Period for the node to exit. If it does not, then Ginkgo will leak the node and proceed. - Ginkgo will then run any clean-up and reporting nodes (`AfterEach`, `JustAfterEach`, `AfterAll`, `DeferCleanup`, `ReportAfterEach` code, etc.) for the current spec... - ...and skip any subsequent specs. - Ginkgo will then run any `AfterSuite` and `ReportAfterSuite` nodes. - And finally, it will exit, marking the suite as failed. If the currently running node is **not** interruptible, then Ginkgo will simply leak the node and proceed with the cleanup nodes. Once a suite is interrupted by one of these mechanisms, any subsequent cleanup nodes that run, will be subject to the following timeout behavior: - If the cleanup node is interruptible and has a `NodeTimeout`, Ginkgo uses that `NodeTimeout` to set a deadline for the node. If the deadline expires, then a Grace Period applies (either the node's `GracePeriod` or the global `--grace-period`) before Ginkgo leaks the node and moves on. - If the cleanup node is interruptible and **does not** have a `NodeTimeout`, Ginkgo uses the Grace Period to set a deadline for the node. If the deadline expires, then a second Grace Period applies before Ginkgo leaks the node and moves on. - If the cleanup node is **not** interruptible, Ginkgo will give the node a single Grace Period to complete and exit. In this case, since it cannot be interrupted, Ginkgo will simply leak the node after one Grace Period. In short, Ginkgo does its best to cleanup and emit as much information as possible about the suite before shutting down... while also ensuring that the suite doesn't hang forever should a cleanup node get stuck. A single interrupt (e.g. `SIGINT`/`SIGTERM`) interrupts the current running node and proceeds to perform cleanup. If you want to skip cleanup, you can send a second interrupt - this will still run reporting nodes in an effort to ensure the generated reports are not corrupted. If you want to skip the reporting nodes and bail immediately, send a third interrupt signal. If you want to get information about what is currently running in a suite _without_ interrupting it, check out the [Getting Visibility Into Long-Running Specs](#getting-visibility-into-long-running-specs) section above. ### Previewing Specs Ginkgo provides a few different mechansisms for previewing and analyzing the specs defined in a suite. You can use the [`outline`](#creating-an-outline-of-specs) cli command to get a machine-readable list of specs defined in the suite. Outline parses the Go AST tree of the suite to determine the specs and therefore does not require the suite to be compiled. This comes with a limitation, however: outline does not offer insight into which specs will run for a given set of filters and it cannot handle dynamically generated specs (example specs generated by a `for` loop). For a more complete preview, you can run `ginkgo --dry-run -v`. This compiles the spec, builds the spec tree, and then walks the tree printing out spec information using Ginkgo's default output as it goes. This allows you to see which specs will run for a given set of filters and also allows you to see dynamically generated specs. Note that you cannot use `--dry-run` with `-p` or `-procs`: you must run in series. If you need finer-grained control over previews, you can use `PreviewSpecs` in your suite in lieu of `RunSpecs`. `PreviewSpecs` behaves like `--dry-run` in that it will compile the suite, build the spec tree, and then walk the tree while honoring any filter and randomization flags. However, `PreviewSpecs` generates and returns a full [`Report` object](#reporting-nodes---reportbeforesuite-and-reportaftersuite) that can be manipulated and inspected as needed. Specs that will be run will have `State = SpecStatePassed` and specs that will be skipped will have `SpecStateSkipped`. If you are opting into `PreviewSpecs` in lieu of `--dry-run`, one suggested pattern is to key off of the `--dry-run` configuration to run `PreviewSpecs` instead of `RunSpecs`: ```go func TestMySuite(t *testing.T) { config, _ := GinkgoConfiguration() if config.DryRun { report := PreviewSpecs("My Suite", Label("suite-label")) //...do things with report. e.g. reporters.GenerateJUnitReport(report, "./preview.xml") } else { RunSpecs(t, "My Suite", Label("suite-label")) } } ``` Note that since `RunSuite` accepts a description string and decorators that can influence the spec tree, you'll want to use the same arguments with `PreviewSpecs`. ### Running Multiple Suites So far we've covered writing and running specs in individual suites. Of course, the `ginkgo` CLI also supports running multiple suites with a single invocation on the command line. We'll close out this chapter on running specs by covering how Ginkgo runs multiple suites. When you run `ginkgo` the Ginkgo CLI first looks for a spec suite in the current directory. If it finds one, it runs `go test -c` to compile the suite and generate a `.test` binary. It then invokes the binary directly, passing along any necessary flags to correctly configure it. In the case of parallel specs, the CLI will configure and spin up multiple copies of the binary and act as a server to coordinate running specs in parallel. You can have `ginkgo` run multiple spec suites by pointing it at multiple package locations (i.e. directories) like so: ```bash ginkgo path/to/package-1 path/to/package-2 ... ``` Ginkgo will enter each of these directories and look for a spec suite. If it finds one, it will compile the suite and run it. Note that you need to include any `ginkgo` flags **before** the list of packages. You can also have `ginkgo` recursively find and run all spec suites within the current directory: ```bash ginkgo -r - or, equivalently, ginkgo ./... ``` Now Ginkgo will walk the file tree and search for spec suites. It will compile any it finds and run them. When there are multiple suites to run, Ginkgo attempts to compile the suites in parallel but **always** runs them sequentially. You can control the number of parallel compilation workers using the `ginkgo --compilers=N` flag, by default Ginkgo runs as many compilers as you have cores. Ginkgo provides a few additional configuration flags when running multiple suites. You can ask Ginkgo to skip certain packages via: ```bash ginkgo -r --skip-package=list,of,packages ``` `--skip-package` takes a comma-separated list of package names. If any part of the package's **path** matches one of the entries in this list, that package is skipped: it is not compiled and it is not run. By default, Ginkgo runs suites in the order it finds them. You can have Ginkgo randomize the order in which suites run with ```bash ginkgo -r --randomize-suites ``` Finally, Ginkgo's default behavior when running multiple suites is to stop execution after the first suite that fails. (Note that Ginkgo will run _all_ the specs in that suite unless `--fail-fast` is specified.) You can alter this behavior and have Ginkgo run _all_ suites regardless of failure with: ```bash ginkgo -r --keep-going ``` As you can see, Ginkgo provides several CLI flags for controlling how specs are run. Be sure to check out the [Recommended Continuous Integration Configuration](#recommended-continuous-integration-configuration) section of the patterns chapter for pointers on which flags are best used in CI environments. ## Reporting and Profiling Suites The previous two chapters covered how Ginkgo specs are written and how Ginkgo specs run. This chapter is all about output. We'll cover how Ginkgo reports on spec suites and how Ginkgo can help you profile your spec suites. ### Controlling Ginkgo's Output Ginkgo emits a real-time report of the progress of your spec suite to the console while running your specs. A green dot is emitted for each successful spec and a red `F`, along with failure information and the spec's [timeline](#mental-model-spec-timelines), is emitted for each unsuccessful spec. There are several CLI flags that allow you to tweak this output: #### Controlling Verbosity Ginkgo has four verbosity settings: succinct (the default when running multiple suites), normal (the default when running a single suite), verbose, and very-verbose. You can opt into succinct mode with `ginkgo --succinct`, verbose mode with `ginkgo -v` and very-verbose mode with `ginkgo -vv`. These settings control the amount of information emitted with each spec. By default (i.e. succinct and normal) Ginkgo only emits detailed information about specs that fail. That includes the location of the spec/failure and a timeline that includes any captured `GinkgoWriter` content alongside a series of relevant spec events. The two verbose settings are most helpful when debugging spec suites. They make Ginkgo emit the detailed timeline information for _every_ spec regardless of failure or success. When running in series with `-v` or `-vv` mode Ginkgo will stream out the timeline in real-time while specs are running. A real-time stream isn't possible when running in parallel (the [streams would be interleaved](https://www.youtube.com/watch?v=jyaLZHiJJnE)); instead Ginkgo emits all this information about each spec right after it completes. Very-verbose mode contains additional information over verbose mode. In particular, `-vv` timelines indicate when individual nodes start and end and also include the full failure descriptions for _every_ failure encountered by the spec. Verbose mode does not include the node start/end events (though this can be turned on with `--show-node-events`) and does not include detailed failure information for anything other than the first (primary) failure. (Additional/subsequent failures typically occur in clean-up nodes and are not as relevant as the primary failure that occurs in a subject or setup node). When you [filter specs](#filtering-specs) using Ginkgo's various filtering mechanism Ginkgo usually emits a single cyan `S` for each skipped spec. If you run with the very-verbose setting, however, Ginkgo will emit the description and location information of every skipped spec. This can be useful if you need to debug your filter queries and can be paired with `--dry-run`. #### Other Settings Here are a grab bag of other settings: You can disable Ginkgo's color output by running `ginkgo --no-color` or setting the `GINKGO_NO_COLOR=TRUE` environment variable. You can also output in a format that makes it easier to read in github actions console by running `ginkgo --github-output`. You can change how Ginkgo formats timestamps in the timeline by setting `GINKGO_TIME_FORMAT` to a valid Golang time format layout (e.g. `GINKGO_TIME_FORMAT=02/01/06 3:04:05.00`). By default, Ginkgo only emits full stack traces when a spec panics. When a normal assertion failure occurs, Ginkgo simply emits the line at which the failure occurred. You can, instead, have Ginkgo always emit the full stack trace by running `ginkgo --trace`. ### Reporting Infrastructure Ginkgo's console output is great when running specs on the console or quickly grokking a CI run. Of course, there are several contexts where generating a machine-readable report is crucial. Ginkgo provides first-class CLI support for generating and aggregating reports in a number of machine-readable formats _and_ an extensible reporting infrastructure to enable additional formats and custom reporting. We'll dig into these topics in the next few sections. ### Generating machine-readable reports Ginkgo natively supports generating and aggregating reports in a number of machine-readable formats - and these reports can be generated and managed by simply passing `ginkgo` command line flags. A JSON-formatted report that faithfully captures all available information about a Ginkgo spec run can be generated via: ```bash ginkgo --json-report=report.json ``` The resulting JSON file encodes an array of `types.Report`. Each entry in that array lists detailed information about an individual spec suite and includes a list of `types.SpecReport` that captures detailed information about each spec. These types are documented in [godoc](https://pkg.go.dev/github.com/onsi/ginkgo/v2/types). When possible, we recommend building tooling on top of Ginkgo's JSON format and using Ginkgo's `types` package directly to access the suite and spec reports. The structs in the package include several helper functions to interpret the report. Ginkgo also supports generating JUnit reports with ```bash ginkgo --junit-report=report.xml ``` The JUnit report is compatible with the JUnit specification, however Ginkgo specs carry much more metadata than can be easily mapped onto the JUnit spec so some information is lost and/or a bit harder to decode than using Ginkgo's native JSON format. Nonetheless, Ginkgo does its best to populate as much of the JUnit report as possible. This includes adding additional metadata using [labels](#spec-labels) - in particular if you provide a label of the form `Label("owner:XYZ")`, the generating JUnit spec will set the `Owner` attribute to `XYZ`. Ginkgo also supports Teamcity reports with `ginkgo --teamcity-report=report.teamcity` though, again, the Teamcity spec makes it difficult to capture all the spec metadata. All the machine-readable reports include the full `-vv` version of the timeline for all specs. This allows you to run Ginkgo in CI with the normal verbosity setting but still get all the detailed information in the machine-readable format. Of course, you can generate multiple formats simultaneously by passing in multiple flags: ```bash ginkgo --json-report=report.json --junit-report=report.xml ``` By default, when any of these command-line report flags are provided Ginkgo will generate a single report file, per format, at the passed-in file name. If Ginkgo is running multiple suites (e.g. `ginkgo -r --json-report=report.json`) then _all_ the suite reports will be encoded in the single report file. If you'd rather generate separate reports for each suite, you can pass in the `--keep-separate-reports` flag like so: `ginkgo -r --json-report=report.json --keep-separate-reports`. This will generate an individual report named `report.json` in each suite/package directory, If you'd like to have all reports end up in a single directory. Set `--output-dir=`: When generating combined reports with: `ginkgo -r --json-report=report.json --output-dir=` Ginkgo will create the `` directory (if necessary), and place `report.json` there. When generating separate reports with: `ginkgo -r --json-report=report.json --output-dir= --keep-separate-reports` Ginkgo will create the `` directory (if necessary), and place a report file per package in the directory. These reports will be namespaced with the name of the package: `PACKAGE_NAME_report.json`. ### Generating reports programmatically The JSON and JUnit reports described above can be easily generated from the command line - there's no need to make any changes to your suite. Ginkgo's reporting infrastructure does, however, provide several mechanisms for writing custom reporting code in your spec suites (or, in a supporting package). We'll explore these mechanisms next. #### Getting a report for the current spec At any point during the Run Phase you can get an information-rich up-to-date copy of the current spec's report by running `CurrentSpecReport()`. There are several uses for this data. For example, you can write code that performs additional, potentially expensive, diagnostics after a spec runs - but only if the spec has failed: ```go Describe("Manipulating books at the central library", func() { It("can fetch all books", func() { Expect(libraryClient.FetchBooks()).NotTo(BeEmpty()) }) It("can fetch a specific book", func() { book, err := libraryClient.FetchBook("Les Miserables") Expect(err).NotTo(HaveOccurred()) Expect(book.AuthorLastName()).To(Equal("Hugo")) }) It("can update a book", func() { book, err := libraryClient.FetchBook("Les Miserables") Expect(err).NotTo(HaveOccurred()) book.Author = "Victor Marie Hugo" Expect(libraryClient.SaveBook(book)).To(Succeed()) }) AfterEach(func() { if CurrentSpecReport().Failed() { GinkgoWriter.Println(libraryClient.DebugLogs()) } }) }) ``` In this example, the `AfterEach` closure is using `CurrentSpecReport()` to discover whether or not the current spec has failed. If it has debug information is fetched from the library server and emitted to the `GinkgoWriter`. Given `CurrentSpecReport()` you can imagine generating custom report information with something like a top-level `AfterEach`. For example, let's say we want to write report information to a local file using a custom format _and_ send updates to a remote server. You might try something like: ```go /* === INVALID === */ var report *os.File BeforeSuite(func() { report = os.Create("report.custom") DeferCleanup(report.Close) }) AfterEach(func() { report := CurrentSpecReport() customFormat := fmt.Sprintf("%s | %s", report.State, report.FullText()) fmt.Fprintln(report, customFormat) client.SendReport(customFormat) }) ``` At first glance it looks like this could work. However, there are a number of problems with this approach: First of all, the `AfterEach` will _only_ be called if the spec in question runs. It will never be called for skipped or pending specs and we'll miss reporting on those specs! Second, the approach we're taking to generate a custom report file will work when running in serial, but not in parallel. In parallel, multiple test processes will race over writing to `report.custom` and you'll end up with a mess. Ginkgo's reporting infrastructure provides an alternative solution for this use case. A special category of setup nodes called **Reporting Nodes**. #### Reporting Nodes - ReportAfterEach and ReportBeforeEach Ginkgo provides four reporting-focused nodes `ReportAfterEach`, `ReportBeforeEach` `ReportBeforeSuite`, and `ReportAfterSuite`. `ReportAfterEach` behaves similarly to a standard `AfterEach` node and can be declared anywhere an `AfterEach` node can be declared. `ReportAfterEach` can take either a closure that accepts a single [`SpecReport`](https://pkg.go.dev/github.com/onsi/ginkgo/v2/types#SpecReport) argument or both `SpecContext` and `SpecReport` For example, we could implement a top-level ReportAfterEach that emits information about every spec to a remote server: ```go ReportAfterEach(func(report SpecReport) { customFormat := fmt.Sprintf("%s | %s", report.State, report.FullText()) client.SendReport(customFormat) }) // interruptible ReportAfterEach node ReportAfterEach(func(ctx SpecContext, report SpecReport) { customFormat := fmt.Sprintf("%s | %s", report.State, report.FullText()) client.SendReport(customFormat) }, NodeTimeout(1 * time.Minute)) ``` `ReportAfterEach` has several unique properties that distinguish it from `AfterEach`. Most importantly, `ReportAfterEach` closures are **always** called - even if the spec has failed, is marked pending, or is skipped. This ensures reports that rely on `ReportAfterEach` are complete. In addition, `ReportAfterEach` closures are called after a spec completes. i.e. _after_ all `AfterEach` closures have run. This gives them access to the complete final state of the spec. Note that if a failure occurs in a `ReportAfterEach` your the spec will be marked as failed. Subsequent `ReportAfterEach` closures will see the failed state, but not the closure in which the failure occurred. `ReportAfterEach` is useful if you need to stream or emit up-to-date information about the suite as it runs. Ginkgo also provides `ReportBeforeEach` which is called before the test runs and receives a preliminary `types.SpecReport` ( or both `SpecContext` and `types.SpecReport` for interruptible behaviour) - the state of this report will indicate whether the test will be skipped or is marked pending. You should be aware that when running in parallel, each parallel process will be running specs and their `ReportAfterEach`es. This means that multiple `ReportAfterEach` blocks can be running concurrently on independent processes. Given that, code like this won't work: ```go /* === INVALID === */ var reportFile *os.File BeforeSuite(func() { reportFile = os.Create("report.custom") }) ReportAfterEach(func(report SpecReport) { fmt.Fprintf(reportFile, "%s | %s\n", report.FullText(), report.State) }) ``` you'll end up with multiple processes writing to the same file and the output will be a mess. There is a better approach for this usecase... #### Reporting Nodes - ReportBeforeSuite and ReportAfterSuite `ReportBeforeSuite` and `ReportAfterSuite` nodes behave similarly to `BeforeSuite` and `AfterSuite` and can be placed at the top-level of your suite (typically in the suite bootstrap file). `ReportBeforeSuite` node take a closure that accepts either [`Report`]((https://pkg.go.dev/github.com/onsi/ginkgo/v2/types#Report)) or, both `SpecContext` and `Report` converting the node to an interruptible node. ```go var _ = ReportBeforeSuite(func(report Report) { // process report }) var _ = ReportBeforeSuite(func(ctx SpecContext, report Report) { // process report }, NodeTimeout(1 * time.Minutes)) var _ = ReportAfterSuite("custom report", func(report Report) { // process report }) var _ = ReportAfterSuite("interruptible ReportAfterSuite", func(ctx SpecContext, report Report) { // process report }, NodeTimeout(1 * time.Minutes)) ``` `Report` contains all available information about the suite. For `ReportAfterSuite` this will include individual `SpecReport` entries for each spec that ran in the suite, and the overall status of the suite (whether it passed or failed). Since `ReportBeforeSuite` runs before the suite starts - it does not contain any spec reports, however the count of the number of specs that _will_ be run can be extracted from `report.PreRunStats.SpecsThatWillRun`. The closure passed to `ReportBeforeSuite` is called exactly once at the beginning of the suite before any `BeforeSuite` nodes or specs run have run. The closure passed to `ReportAfterSuite` is called exactly once at the end of the suite after any `AfterSuite` nodes have run. Finally, and most importantly, when running in parallel both `ReportBeforeSuite` and `ReportAfterSuite` **only run on process #1**. Gingko guarantess that no other processes will start running their specs until after `ReportBeforeSuite` on process #1 has completed. Similarly, Ginkgo will only run `ReportAfterSuite` on process #1 after all other processes have finished and exited. Ginkgo provides a single `Report` that aggregates the `SpecReports` from all processes. This allows you to perform any custom suite reporting in one place after all specs have run and not have to worry about aggregating information across multiple parallel processes. Given all this, we can rewrite our invalid `ReportAfterEach` example from above into a valid `ReportAfterSuite` example: ```go ReportAfterSuite("custom report", func(report Report) { f := os.Create("report.custom") for _, specReport := range report.SpecReports { fmt.Fprintf(f, "%s | %s\n", specReport.FullText(), specReport.State) } f.Close() }) ``` Now each suite will generate exactly one report with all the specs appropriately formatted whether running in series or in parallel. ### Attaching Data to Reports Ginkgo supports attaching arbitrary data to individual spec reports. These are called `ReportEntries` and appear in the various report-related data structures (e.g. `Report` in `ReportAfterSuite` and `SpecReport` in `ReportAfterEach`) as well as the machine-readable reports generated by `--json-report`, `--junit-report`, etc. `ReportEntries` are also emitted to the console by Ginkgo's reporter and you can specify a visibility policy to control when this output is displayed. You attach data to a spec report via ```go AddReportEntry(name string, args ...interface{}) ``` `AddReportEntry` can be called from any setup or subject node closure. When called, `AddReportEntry` generates `ReportEntry` and attaches it to the current running spec. `ReportEntry` includes the passed in `name` as well as the time and source location at which `AddReportEntry` was called. Users can also attach a single object of arbitrary type to the `ReportEntry` by passing it into `AddReportEntry` - this object is wrapped and stored under `ReportEntry.Value` and is always included in the suite's JSON report. You can access the report entries attached to a spec by getting the `CurrentSpecReport()` or registering a `ReportAfterEach()` - the returned report will include the attached `ReportEntries`. You can fetch the value associated with the `ReportEntry` by calling `entry.GetRawValue()`. When called in-process this returns the object that was passed to `AddReportEntry`. When called after hydrating a report from JSON `entry.GetRawValue()` will include a parsed JSON `interface{}` - if you want to hydrate the JSON yourself into an object of known type you can `json.Unmarshal([]byte(entry.Value.AsJSON), &object)`. #### Supported Args `AddReportEntry` supports the `Offset` and `CodeLocation` decorators. These will control the source code location associated with the generated `ReportEntry`. You can also pass in a `time.Time` argument to override the timestamp associated with the `ReportEntry` - this can be helpful if you want to ensure a consistent timestamp between your code and the `ReportEntry`. You can also pass in a `ReportEntryVisibility` enum to control the report's visibility. This is discussed in more detail below. If you pass multiple arguments of the same type (e.g. two `Offset`s), the last argument in wins. This does mean you cannot attach an object with one of the types discussed in this section as the `ReportEntry.Value`. To get by this you'll need to define a custom type. For example, if you want the `Value` to be a `time.Time` timestamp you can use a custom type such as `type Timestamp time.Time` #### Controlling Output By default, Ginkgo's console reporter will emit any `ReportEntry` attached to a spec. It will emit the `ReportEntry` name, location, and time. If the `ReportEntry` value is non-nil it will also emit a representation of the value. If the value implements `fmt.Stringer` or `types.ColorableStringer` then `value.String()` or `value.ColorableString()` (which takes precedence) is used to generate the representation, otherwise Ginkgo uses `fmt.Sprintf("%#v", value)`. You can modify this default behavior by passing in one of the `ReportEntryVisibility` enum to `AddReportEntry`: - `ReportEntryVisibilityAlways`: the default behavior - the `ReportEntry` is always emitted. - `ReportEntryVisibilityFailureOrVerbose`: the `ReportEntry` is only emitted if the spec fails or the tests are run with `-v` (similar to `GinkgoWriter`s behavior). - `ReportEntryVisibilityNever`: the `ReportEntry` is never emitted though it appears in any generated machine-readable reports (e.g. by setting `--json-report`). The console reporter passes the string representation of the `ReportEntry.Value` through Ginkgo's `formatter`. This allows you to generate colorful console output using the color codes documented in `github.com/onsi/ginkgo/v2/formatter/formatter.go`. For example: ```go type StringerStruct struct { Label string Count int } // ColorableString for ReportEntry to use func (s StringerStruct) ColorableString() string { return fmt.Sprintf("{{red}}%s {{yellow}}{{bold}}%d{{/}}", s.Label, s.Count) } // non-colorable String() is used by go's string formatting support but ignored by ReportEntry func (s StringerStruct) String() string { return fmt.Sprintf("%s %d", s.Label, s.Count) } It("is reported", func() { AddReportEntry("Report", StringerStruct{Label: "Mahomes", Count: 15}) }) ``` Will emit a report that has the word "Mahomes" in red and the number 15 in bold and yellow. Lastly, it is possible to pass a pointer into `AddReportEntry`. Ginkgo will compute the string representation of the passed in pointer at the last possible moment - so any changes to the object _after_ it is reported will be captured in the final report. This is useful for building libraries on top of `AddReportEntry` - users can simply register objects when they're created and any subsequent mutations will appear in the generated report. You can see an example of this in the [Benchmarking Code](#benchmarking-code) pattern section of the patterns chapter. ### Profiling your Suites Go supports a rich set of profiling features to gather information about your running test suite. Ginkgo exposes all of these and manages them for you when you are running multiple suites and/or parallel suites. Ginkgo supports `--race` to analyze race conditions, `--cover` to compute code coverage, `--vet` to evaluate and vet your code, `--cpuprofile` to profile CPU performance, `--memprofile` to profile memory usage, `--blockprofile` to profile blocking goroutines, and `--mutexprofile` to profile locking around mutexes. `ginkgo -race` runs the race detector and emits any detected race conditions as the suite runs. If any are detected the suite is marked as failed. `ginkgo -vet` allows you to configure the set of checks that are applied when your code is compiled. `ginkgo` defaults to the set of default checks that `go test` uses and you can specify additional checks by passing a comma-separated list to `--vet`. The set of available checks can be found by running `go doc cmd/vet`. #### Computing Coverage `ginkgo -cover` will compute and emit code coverage. When running multiple suites Ginkgo will emit coverage for each suite and then emit a composite coverage across all running suites. As with `go test` the default behavior for a given suite is to measure the coverage it provides for the code in the suite's package - however you can extend coverage to additional packages using `--coverpkg`. You can provide a comma-separated list of package names (as they appear in `import` statements) or a relative path. You can also use `...` for recursion. For example, say we have a package called "github.com/foo/bar". The following are equivalent: ```bash ginkgo -coverpkg=./... -r ginkgo -coverpkg=github.com/foo/bar/... -r ``` and will have the effect of calculating coverage for **all** code in the package by **all** specs in the package. You can also specify the `--covermode` to be one of `set` ("was this code called at all?"), `count` (how many times was it called?) and `atomic` (same as count, but threadsafe and expensive). If you run `ginkgo --race --cover` the `--covermode` is automatically set to `atomic`. When run with `--cover`, Ginkgo will generate a single `coverprofile.out` file that captures the coverage statistics of all the suites that ran. You can change the name of this file by specifying `-coverprofile=filename`. If you would like to keep separate coverprofiles for each suite use the `--keep-separate-coverprofiles` option. Ginkgo also honors the `--output-dir` flag when generating coverprofiles. If you specify `--output-dir` the generated coverprofile will be placed in the requested directory. If you also specify `--keep-separate-coverprofiles` individual package coverprofiles will be placed in the requested directory and namespaced with a prefix that contains the name of the package in question. Finally, when running a suite that has [programmatically focused specs](#focused-specs) (i.e. specs with the `Focus` decorator or with nodes prefixed with an `F`) Ginkgo exits the suite early with a non-zero exit code. This interferes with `go test`'s profiling code and prevents profiles from being generated. Ginkgo will tell you this has happened. If you want to profile just a subset of your suite you'll need to use a different [mechanism](#filtering-specs) to filter your specs. #### Other Profiles Running `ginkgo` with any of `--cpuprofile=X`, `--memprofile=X`, `--blockprofile=X`, and `--mutexprofile=X` will generate corresponding profile files for suite that runs. Doing so will also preserve the test binary generated by Ginkgo to enable users to use `go tool pprof ` to analyze the profile. By default, the test binary and various profile files are stored in the individual directories of any suites that Ginkgo runs. If you specify `--output-dir`, however, then these assets are moved to the requested directory and namespaced with a prefix that contains the name of the package in question. As with coverage computation, these profiles will not generate a file if a suite includes programmatically focused specs (see the discussion [above](#computing-coverage)). ## Ginkgo and Gomega Patterns So far we've introduced and described the majority of Ginkgo's capabilities and building blocks. Hopefully, the previous chapters have helped give you a mental model for how Ginkgo specs are written and run. In this chapter we'll switch gears and illustrate common patterns for how Ginkgo's building blocks can be put together to solve for real-world problems. Since Ginkgo and Gomega are so often paired this chapter will assume that you are using both together - as you'll see, the combination can unlock some powerful, and expressive, testing patterns. ### Recommended Continuous Integration Configuration When running in CI you must make sure that the version of the `ginkgo` CLI you are using matches the version of Ginkgo in your `go.mod` file. You can ensure this by invoking the `ginkgo` command via `go run`: `go run github.com/onsi/ginkgo/v2/ginkgo` Once you have `ginkgo` running on CI, you'll want to pick and choose the optimal set of flags for your test runs. We recommend the following set of flags when running in a continuous integration environment: ```bash go run github.com/onsi/ginkgo/v2/ginkgo -r --procs=N --compilers=N --randomize-all --randomize-suites --fail-on-pending --fail-on-empty --keep-going --cover --coverprofile=cover.profile --race --trace --json-report=report.json --timeout=TIMEOUT --poll-progress-after=Xs --poll-progress-interval=Ys ``` Here's why: - `-r` will recursively find and run all suites in the current directory. - `-procs=N` will run each suite in parallel. This can substantially speed up suites and you should experiment with different values of `N`. Note that it is not recommended that you run specs in parallel with `-p` on CI. Some CI services run on shared machines that will report (e.g.) `32` cores but will not actually give an individual account access to all those compute resources! - `--compilers=N` will control how many cores to use to compile suites in parallel. You may need to set this explicitly to avoid accidentally trying to use all `32` cores on that CI machine! - `--randomize-all` and `--randomize-suites` will randomize all specs and randomize the order in which suites run. This will help you suss out spec pollution early! - `--fail-on-pending` will fail the suite if it contains any pending specs. These are generally only used while developing the suite and should not be committed. - `--fail-on-empty` will fail the suite if it contains no specs or if all specs have been filtered out. This can help you ensure that the CLI filters have not filtered out all specs (which typically means the filters are malformed). - `--keep-going` will instruct Ginkgo to keep running suites, even after a suite fails. This can help you get a set of all failures instead of stopping after the first failed suite. - `--cover` and `--coverprofile=cover.profile` will compute coverage scores and generate a single coverage file for all your specs. - `--race` will run the race detector. - `--trace` will instruct Ginkgo to generate a stack trace for all failures (instead of simply including the location where the failure occurred). This isn't usually necessary but can be helpful in CI environments where you may not have access to a fast feedback loop to iterate on and debug code. - `--json-report=report.json` will generate a JSON formatted report file. You can store these off and use them later to get structured access to the suite and spec results. Alternatively (or in addition) you can use `--junit-report=report.xml` to generate JUnit-formatted reports; these are compatible with several existing CI systems. - `--timeout` allows you to specify a timeout for the `ginkgo` run. The default duration is one hour, which may or may not be enough! - `--poll-progress-after` and `--poll-progress-interval` will allow you to learn where long-running specs are getting stuck. Choose a values for `X` and `Y` that are appropriate to your suite. A long-running integration suite, for example, might set `X` to `120s` and `Y` to `30s` - whereas a quicker set of unit tests might not need this setting. Note that if you precompile suites and run them from a different directory relative to your source code, you may also need to set `--source-root` to enable Ginkgo to emit source code lines when generating progress reports. If running on Github actions: `--github-output` will make the output more readable in the Github actions console. If your CI system will only flush if a newline character is seen, you may want to set `--force-newlines` to ensure that the output is flushed correctly. ### Supporting Custom Suite Configuration There are contexts where you may want to change some aspects of a suite's behavior based on user-provided configuration. There are two widely adopted means of doing this: environment variables and command-line flags. We'll explore both these options in this section by building out a concrete usecase. Let's imagine a suite that is intended to ensure that a service is up and running correctly (these are sometimes referred to as smoketest suites). We want to be able to point our suite at an arbitrary server address/port. We also want to configure how our suite runs depending on the environment we're smoketesting - we'll want to be minimally invasive for `PRODUCTION` environments, but can perform a more thorough check for `STAGING` environments. Here's a sketch of what this might look like. #### Supporting Custom Suite Configuration: Environment Variables Setting and parsing environment variables is fairly straightforward. We'll configure the server address with a `SMOKETEST_SERVER_ADDR` environment variable and we'll configure the environment with a `SMOKETEST_ENV` variable. Our suite might look like: ```go // This is the testing hook in our bootstrap file func TestSmokeTest(t *testing.T) { RegisterFailHandler(Fail) RunSpecs(t, "Smoketest Suite") } var client *client.Client var _ = BeforeSuite(func() { // Some basic validations Expect(os.Getenv("SMOKETEST_SERVER_ADDR")).NotTo(BeZero(), "Please make sure SMOKETEST_SERVER_ADDR is set correctly.") Expect(os.Getenv("SMOKETEST_ENV")).To(Or(Equal("PRODUCTION"), Equal("STAGING")), "SMOKETEST_ENV must be set to PRODUCTION or STAGING.") //set up a client client = client.NewClient(os.Getenv("SMOKETEST_SERVER_ADDR")) }) var _ = Describe("Smoketests", func() { Describe("Minimally-invasive", func() { It("can connect to the server", func() { Eventually(client.Connect).Should(Succeed()) }) It("can get a list of books", func() { Expect(client.ListBooks()).NotTo(BeEmpty()) }) }) if os.Getenv("SMOKETEST_ENV") == "STAGING" { Describe("Ensure basic CRUD operations", func() { It("can create, updated, and delete a book", func() { book := &books.Book{ Title: "This Book is a Test", Author: "Ginkgo", Pages: 17, } Expect(client.Store(book)).To(Succeed()) Expect(client.FetchByTitle("This Book is a Test")).To(Equal(book)) Expect(client.Delete(book)).To(Succeed()) Expect(client.FetchByTitle("This Book is a Test")).To(BeNil()) }) }) } }) ``` users could then run: ```bash SMOKETEST_SERVER_ADDR="127.0.0.1:3000" SMOKETEST_ENV="STAGING" ginkgo ``` to run all three specs against a local server listening on port `3000`. If the user fails to correctly provide the configuration environment variables, the `BeforeSuite` checks will fail and `Gomega` will emit the description strings (e.g. "Please make sure SMOKETEST_SERVER_ADDR is set correctly.") to help the user know what they missed. As you can see, environment variables are convenient and easily accessible from anywhere in the suite. We use them during the Run Phase to configure the client. But we also use them at the Tree Construction Phase to control which specs are included in the suite. There are some clearer ways to accomplish the latter so keep reading! #### Supporting Custom Configuration: Custom Command-Line Flags An alternative to environment variables is to provide custom command-line flags to the suite. These take a bit more setting up but have the benefit of being a bit more self-documenting and structured. The tricky bits here are: 1. Injecting your command line flags into Go's `flags` list before the test process parses flags. 2. Understanding when in the spec lifecycle the parsed flags are available. 3. Remembering to pass the flags in correctly. Here's a fleshed out example: ```go var serverAddr, smokeEnv string // Register your flags in an init function. This ensures they are registered _before_ `go test` calls flag.Parse(). func init() { flag.StringVar(&serverAddr, "server-addr", "", "Address of the server to smoke-check") flag.StringVar(&smokeEnv, "environment", "", "Environment to smoke-check") } // This is the testing hook in our bootstrap file func TestSmokeTest(t *testing.T) { RegisterFailHandler(Fail) RunSpecs(t, "Smoketest Suite") } var client *client.Client var _ = BeforeSuite(func() { // Some basic validations - at this point the flags have been parsed so we can access them Expect(serverAddr).NotTo(BeZero(), "Please make sure --server-addr is set correctly.") Expect(smokeEnv).To(Or(Equal("PRODUCTION"), Equal("STAGING")), "--environment must be set to PRODUCTION or STAGING.") //set up a client client = client.NewClient(serverAddr) }) var _ = Describe("Smoketests", func() { Describe("Minimally-invasive", func() { It("can connect to the server", func() { Eventually(client.Connect).Should(Succeed()) }) It("can get a list of books", func() { Expect(client.ListBooks()).NotTo(BeEmpty()) }) }) if smokeEnv == "STAGING" { Describe("Ensure basic CRUD operations", func() { It("can create, updated, and delete a book", func() { book := &books.Book{ Title: "This Book is a Test", Author: "Ginkgo", Pages: 17, } Expect(client.Store(book)).To(Succeed()) Expect(client.FetchByTitle("This Book is a Test")).To(Equal(book)) Expect(client.Delete(book)).To(Succeed()) Expect(client.FetchByTitle("This Book is a Test")).To(BeNil()) }) }) } }) ``` We would invoke this suite with ```bash ginkgo -- --server-addr="127.0.0.1:3000" --environment="STAGING" ``` note the `--` separating the arguments `ginkgo` from the arguments passed down to the suite. You would put Ginkgo's arguments to the left of `--`. For example, to run in parallel: ```bash ginkgo -p -- --server-addr="127.0.0.1:3000" --environment="STAGING" ``` One more note before we move on. As shown in this example, parsed flag variables are available both during the Run Phase (e.g. when we call `client.NewClient(serverAddr)`) _and_ during the Tree Construction Phase (e.g. when we guard the `CRUD` specs with `if smokeEnv == "STAGING"`). However flag variables are _not_ available at the **top-level** of the suite. Here's a trivial, but instructive, example. Say we wanted to add the value of `environment` to the name the top-level `Describe`: ```go ... var describeName = fmt.Sprintf("Smoketests - %s", smokeEnv) var _ = Describe(describeName, func() { ... }) ... ``` Counter-intuitively, this will always yield `"Smoketests - "`. The reason is that `fmt.Sprintf` is being called as go is traversing the top-level identifiers in the suite. At this point, `init` functions are being _defined_ but have not yet been invoked. So (a) we haven't actually registered our flags yet and, more importantly, (b) `go test` hasn't _parsed_ the flags yet. Our `smokeEnv` variable is therefore empty. There's no way around this - in general you should avoid trying to access configuration information at the top-level. However, if you must then you will need to use use environment variables instead of flags. #### Overriding Ginkgo's command-line configuration in the suite The previous two examples used an `if` guard to control whether specs were included in the spec tree based on user-provided configuration. This approach _works_ but can be a bit confusing - specs that are "skipped" in this way never appear in any generated reports, and the total number of specs in the suite depends on configuration. It would be cleaner and clearer to leverage Ginkgo's filtering mechanisms. You could, for example, use `Skip`: ```go var _ = Describe("Smoketests", func() { Describe("Minimally-invasive", func() { It("can connect to the server", func() { ... }) It("can get a list of books", func() { ... }) }) Describe("Ensure basic CRUD operations", func() { BeforeEach(func(){ if environment != "STAGING" { Skip("CRUD spec only runs on staging") } }) It("can create, updated, and delete a book", func() { ... }) }) }) ``` this works just fine - however as the suite grows you may see that `environment` check start to spread throughout the suite. You could, instead, use Ginkgo's label mechanisms. Here we're explicitly labeling specs with their allowed environments: ```go var _ = Describe("Smoketests", func() { Describe("Minimally-invasive", Label("PRODUCTION", "STAGING"), func() { It("can connect to the server", func() { ... }) It("can get a list of books", func() { ... }) }) Describe("Ensure basic CRUD operations", Label("STAGING"), func() { It("can create, updated, and delete a book", func() { ... }) }) }) ``` We could then use Ginkgo's expressive filter queries to control which specs do/don't run. However that would require us to change our contract with the user. They'll now need to run: ```bash ginkgo --label-filter="STAGING" -- --server-addr="127.0.0.1" ``` this isn't great. Ideally we'd maintain the same contract and allow the user to express their intent through the existing semantics of "environment" and take care of managing the label-filter in the suite. You can accomplish this in Ginkgo by overriding Ginkgo's configuration _before_ running the specs. Here's our fully-worked example showing how: ```go var serverAddr, smokeEnv string // Register your flags in an init function. This ensures they are registered _before_ `go test` calls flag.Parse(). func init() { flag.StringVar(&serverAddr, "server-addr", "", "Address of the server to smoke-check") flag.StringVar(&smokeEnv, "environment", "", "Environment to smoke-check") } // This is the testing hook in our bootstrap file func TestSmokeTest(t *testing.T) { RegisterFailHandler(Fail) //we're moving the validation up here since we're about to use the flag variables before entering the RunPhase //thankfully Gomega can run within normal `testing` tests, we simply create a new Gomega by wrapping `testing.T` g := NewGomegaWithT(t) g.Expect(serverAddr).NotTo(BeZero(), "Please make sure --server-addr is set correctly.") g.Expect(smokeEnv).To(Or(Equal("PRODUCTION"), Equal("STAGING")), "--environment must be set to PRODUCTION or STAGING.") //we're now guaranteed to have validated configuration variables //let's update Ginkgo's configuration using them //first we grab Ginkgo's current configuration suiteConfig, _ := GinkgoConfiguration() //the second argument is the reporter configuration which we won't be adjusting //now we modify the label-filter if suiteConfig.LabelFilter == "" { suiteConfig.LabelFilter = smokeEnv } else { // if the user has specified a label-filter we extend it: suiteConfig.LabelFilter = "(" + suiteConfig.LabelFilter + ") && " + smokeEnv } // finally, we pass the modified configuration in to RunSpecs RunSpecs(t, "Smoketest Suite", suiteConfig) } var client *client.Client var _ = BeforeSuite(func() { client = client.NewClient(serverAddr) }) var _ = Describe("Smoketests", func() { Describe("Minimally-invasive", Label("PRODUCTION", "STAGING"), func() { It("can connect to the server", func() { Eventually(client.Connect).Should(Succeed()) }) It("can get a list of books", func() { Expect(client.ListBooks()).NotTo(BeEmpty()) }) }) Describe("Ensure basic CRUD operations", Label("STAGING"), func() { It("can create, updated, and delete a book", func() { book := &books.Book{ Title: "This Book is a Test", Author: "Ginkgo", Pages: 17, } Expect(client.Store(book)).To(Succeed()) Expect(client.FindByTitle("This Book is a Test")).To(Equal(book)) Expect(client.Delete(book)).To(Succeed()) Expect(client.FindByTitle("This Book is a Test")).To(BeNil()) }) }) }) ``` In this way we can provide alternative, more semantically appropriate, interfaces to consumers of our suite and build on top of Ginkgo's existing building blocks. ### Dynamically Generating Specs There are several patterns for dynamically generating specs with Ginkgo. You can use a simple loop to generate specs. For example: ```go Describe("Storing and retrieving books by category", func() { for _, category := range []books.Category{books.CategoryNovel, books.CategoryShortStory, books.CategoryBiography} { category := category It(fmt.Sprintf("can store and retrieve %s books", category), func() { book := &books.Book{ Title: "This Book is a Test", Author: "Ginkgo", Category: category, } Expect(library.Store(book)).To(Succeed()) DeferCleanup(library.Delete, book) Expect(library.FindByCategory(category)).To(ContainElement(book)) }) } }) ``` This will generate several `It`s - one for each category. Note that you must assign a copy of the loop variable to a local variable (that's what `category := category` is doing) - otherwise the `It` closure will capture the mutating loop variable and all the specs will run against the last element in the loop. It is idiomatic to give the local copy the same name as the loop variable. Of course, this particular example might be better written as a [table](#table-specs)! There are contexts where external information needs to be loaded in order to figure out which specs to dynamically generate. For example, let's say we maintain a `json` file that lists a set of fixture books that we want to test storing/retrieving from the library. There are many ways to approach writing such a test - but let's say we want to maximize parallelizability of our suite and so want to generate a separate `It` for each book fixture. Many Ginkgo users attempt the following approach. It's a common gotcha: ```go /* === INVALID === */ var fixtureBooks []*books.Book var _ = BeforeSuite(func() { fixtureBooks = LoadFixturesFrom("./fixtures/books.json") Expect(fixtureBooks).NotTo(BeEmpty()) }) Describe("Storing and retrieving the book fixtures", func() { for _, book := range fixtureBooks { book := book It(fmt.Sprintf("can store and retrieve %s", book.Title), func() { Expect(library.Store(book)).To(Succeed()) DeferCleanup(library.Delete, book) Expect(library.FindByTitle(book.Title)).To(Equal(book)) }) } }) ``` This will not work. The fixtures are loaded in the `BeforeSuite` closure which runs during the **Run Phase**... _after_ the **Tree Construction Phase** where we loop over `fixtureBooks`. If you need to perform work that influences the structure of the spec tree you must do it _before_ or _during_ the Tree Construction Phase. In this case, it is idiomatic to place the relevant code in the `Test` function in the bootstrap file: ```go var fixtureBooks []*books.Book func TestBooks(t *testing.T) { RegisterFailHandler(Fail) // perform work that needs to be done before the Tree Construction Phase here // note that we wrap `t` with a new Gomega instance to make assertions about the fixtures here. g := NewGomegaWithT(t) fixtureBooks = LoadFixturesFrom("./fixtures/books.json") g.Expect(fixtureBooks).NotTo(BeEmpty()) // finally, we pass the modified configuration in to RunSpecs RunSpecs(t, "Books Suite") } Describe("Storing and retrieving the book fixtures", func() { for _, book := range fixtureBooks { book := book It(fmt.Sprintf("can store and retrieve %s", book.Title), func() { Expect(library.Store(book)).To(Succeed()) DeferCleanup(library.Delete, book) Expect(library.FindByTitle(book.Title)).To(Equal(book)) }) } }) ``` ### Shared Behaviors It's common to want to extract subsets of spec behavior for reuse - these are typically called "Shared Behaviors". It is often the case that within a particular suite there will be a number of different `Context`s that assert the exact same behavior, in that they have identical `It`s within them. The only difference between these `Context`s is the set up done in their respective `BeforeEach`s. Rather than repeat the `It`s for these `Context`s, you can extract the code into a shared-scope closure and avoid repeating yourself. For example: ```go Describe("Storing books in the library", func() { var book *books.Book{} Describe("the happy path", func() { BeforeEach(func() { book = &books.Book{ Title: "Les Miserables", Author: "Victor Hugo", Pages: 2783, } }) It("validates that the book can be stored", func() { Expect(library.IsStorable(book)).To(BeTrue()) }) It("can store the book", func() { Expect(library.Store(book)).To(Succeed()) }) }) Describe("failure modes", func() { AssertFailedBehavior := func() { It("validates that the book can't be stored", func() { Expect(library.IsStorable(book)).To(BeFalse()) }) It("fails to store the book", func() { Expect(library.Store(book)).To(MatchError(books.ErrStoringBook)) }) } Context("when the book has no title", func() { BeforeEach(func() { book = &books.Book{ Author: "Victor Hugo", Pages: 2783, } }) AssertFailedBehavior() }) Context("when the book has no author", func() { BeforeEach(func() { book = &books.Book{ Title: "Les Miserables", Pages: 2783, } }) AssertFailedBehavior() }) Context("when the book is nil", func() { BeforeEach(func() { book = nil }) AssertFailedBehavior() }) }) }) ``` Since `AssertFailedBehavior` is defined in the same stack of closures as the other nodes, it has access to the shared `book` variable. Note that the `AssertFailedBehavior` function is called within the body of the `Context` container block. This will happen during The Tree Construction phase and result in a spec tree that includes the `It`s defined in the `AssertFailedBehavior` function for each context. ### Table Specs Patterns We introduced Ginkgo's support for Table Specs in an [earlier section](#table-specs). Here we'll just outline a couple of useful patterns. Tables specs allow you to specify a spec function that takes arbitrary parameters and entries to feed parameters to the function. This works well when you've got a small handful of parameters but can become unwieldy with more parameters. For example: ```go var book *books.Book BeforeEach(func() { book = LoadFixture("les-miserables.json") }) DescribeTable("Repaginating Books", func(fontSize int, lineHeight float64, pageWidth float64, pageHeight float64, expectedPages int) { book.SetFontSize(fontSize) book.SetLineHeight(lineHeight) book.SetPageDimensions(pageWidth, pageHeight) Expect(book.RecomputePages()).To(BeNumerically("~", expectedPages, 30)) }, func(fontSize int, lineHeight float64, pageWidth float64, pageHeight float64, expectedPages int) string { return fmt.Sprintf("FontSize: %d, LineHeight: %.2f, Page:%.2fx%.2f => %d", fontSize, lineHeight, pageWidth, pageHeight, expectedPages) } Entry(nil, 12, 1.2, 8.5, 11, 2783), Entry(nil, 14, 1.3, 8.5, 11, 3120), Entry(nil, 10, 1.2, 8.5, 11, 2100), Entry(nil, 12, 2.0, 8.5, 11, 6135), Entry(nil, 12, 1, 5, 6, 12321), ) ``` These entries are inscrutable! A common pattern in this case is to define a type to capture the entry information: ```go var book *books.Book type BookFormatting struct { FontSize int LineHeight float64 PageWidth float64 PageHeight float64 } BeforeEach(func() { book = LoadFixture("les-miserables.json") }) DescribeTable("Repaginating Books", func(formatting BookFormatting, expectedPages int) { book.SetFontSize(formatting.FontSize) book.SetLineHeight(formatting.LineHeight) book.SetPageDimensions(formatting.PageWidth, formatting.PageHeight) Expect(book.RecomputePages()).To(BeNumerically("~", expectedPages, 30)) }, func(formatting BookFormatting, expectedPages int) string { return fmt.Sprintf("FontSize: %d, LineHeight: %.2f, Page:%.2fx%.2f => %d", formatting.fontSize, formatting.lineHeight, formatting.pageWidth, formatting.pageHeight, expectedPages) } Entry(nil, BookFormatting{FontSize: 12, LineHeight: 1.2, PageWidth:8.5, PageHeight:11}, 2783), Entry(nil, BookFormatting{FontSize: 14, LineHeight: 1.3, PageWidth:8.5, 11}, 3120), Entry(nil, BookFormatting{FontSize: 10, LineHeight: 1.2, PageWidth:8.5, 11}, 2100), Entry(nil, BookFormatting{FontSize: 12, LineHeight: 2.0, PageWidth:8.5, 11}, 6135), Entry(nil, BookFormatting{FontSize: 12, LineHeight: 1, PageWidth:5, PageHeight:6}, 12321), ) ``` This is longer but certainly easier to read! Another Table Spec pattern involves the reuse of table of Entries. If you have multiple cases to run against the same set of entries you can save of the entries in a `[]TableEntry` slice and then pass the slice to multiple `DescribeTable` functions. For example: ```go var InvalidBookEntries = []TableEntry{ Entry("Empty book", &books.Book{}), Entry("Only title", &books.Book{Title: "Les Miserables"}), Entry("Only author", &books.Book{Author: "Victor Hugo"}), Entry("Missing pages", &books.Book{Title: "Les Miserables", Author: "Victor Hugo"}), } DescribeTable("Storing invalid books always errors", func(book *books.Book) { Expect(library.Store(book)).To(MatchError(books.ErrInvalidBook)) }, InvalidBookEntries) DescribeTable("Reading invalid books always errors", func(book *books.Book) { Expect(user.Read(book)).To(MatchError(books.ErrInvalidBook)) }, InvalidBookEntries) ``` alternatively you can use `DescribeTableSubtree` to associate multiple specs with a given entry: ```go DescribeTableSubtree("Handling invalid books", func(book *books.Book) { Describe("Storing invalid books", func() { It("always errors", func() { Expect(library.Store(book)).To(MatchError(books.ErrInvalidBook)) }) }) Describe("Reading invalid books", func() { It("always errors", func() { Expect(user.Read(book)).To(MatchError(books.ErrInvalidBook)) }) }) }, Entry("Empty book", &books.Book{}), Entry("Only title", &books.Book{Title: "Les Miserables"}), Entry("Only author", &books.Book{Author: "Victor Hugo"}), Entry("Missing pages", &books.Book{Title: "Les Miserables", Author: "Victor Hugo"}) ) ``` ### Patterns for Asynchronous Testing It is common, especially in integration suites, to be testing behaviors that occur asynchronously (either within the same process or, in the case of distributed systems, outside the current test process in some combination of external systems). Ginkgo and Gomega provide the building blocks you need to write effective asynchronous specs efficiently. Rather than an exhaustive/detailed review we'll simply walk through some common patterns. Throughout you'll see that you should generally use Ginkgo's interruptible nodes with timeouts alongside use Gomega's `Eventually` and `Consistently` to make [asynchronous assertions](https://onsi.github.io/gomega/#making-asynchronous-assertions). Both `Eventually` and `Consistently` perform asynchronous assertions by polling the provided input. In the case of `Eventually`, Gomega polls the input repeatedly until the matcher is satisfied - once that happens the assertion exits successfully and execution continues. If the matcher is never satisfied `Eventually` will time out with a useful error message. Both the timeout and polling interval are [configurable](https://onsi.github.io/gomega/#eventually). In the case of `Consistently`, Gomega polls the input repeatedly and asserts the matcher is satisfied every time. `Consistently` only exits early if a failure occurs - otherwise it continues polling until the specified interval elapses. This is often the only way to assert that something "does not happen" in an asynchronous system. `Eventually` and `Consistently` can accept three types of input. You can pass in bare values and assert that some aspect of the value changes eventually. This is most commonly done with Go channels or Gomega's [`gbytes`](https://onsi.github.io/gomega/#gbytes-testing-streaming-buffers) and [`gexec`](https://onsi.github.io/gomega/#gexec-testing-external-processes) packages. You can also pass in functions and assert that their return values `Eventually` or `Consistently` satisfy a matcher - we'll cover those later. Lastly, you can pass in functions that take a `Gomega` argument - these allow you to make assertions within the function and are a way to assert that a series of assertions _eventually_ succeeds. We'll cover _that_ later as well. Let's look at these various input types through the lens of some concrete use-cases. #### Testing an in-process Asynchronous Service. Let's imagine an in-process asynchronous service that can prepare books for publishing and emit updates to a buffer. Since publishing is expensive the publish service returns a channel that will include the published book bits and runs the actual publishing process in a separate Goroutine. We could test such a service like so: ```go Describe("Publishing books", func() { var book *books.Book BeforeEach(func() { book = loadBookWithContent("les_miserables.fixture") Expect(book).NotTo(BeNil()) }) It("can publish a book, emitting information as it goes", func(ctx SpecContext) { buffer := gbytes.NewBuffer() //gbytes provides a thread-safe buffer that works with the `gbytes.Say` matcher // we begin publishing the book. This kicks off a goroutine and returns a channel // Publish takes a `context.Context` and so we pass in our `ctx` to clean up correctly in case the spec timeout elapses c := publisher.Publish(ctx, book, buffer) // gbytes.Say allows us to assert on output to a stream // we pass in the SpecContext to give this block of `Eventually's` a shared time horizon for completing: the 30 second SpecTimeout // we don't _have_ to pass in a SpecContext. If we don't, then each `Eventually` will have its own, individual, timeout. Eventually(ctx, buffer).Should(gbytes.Say(`Publishing "Les Miserables...`)) Eventually(ctx, buffer).Should(gbytes.Say(`Published page 1/2783`)) Eventually(ctx, buffer).Should(gbytes.Say(`Published page 2782/2783`)) Eventually(ctx, buffer).Should(gbytes.Say(`Publish complete!`)) // rather than call <-c which could block the spec forever we use Eventually to poll the channel and // store any received values in a pointer // we pass in the SpecContext _and_ specify a timeout of 1 second: // at this point we expect `Publish()` to exit fairly quickly and should not need to wait for longer than 1s! var result publisher.PublishResult Eventually(ctx, c).WithTimeout(time.Second).Should(Receive(&result)) //we make some *synchronous* assertions on the result Expect(result.Title).To(Equal("Les Miserables")) Expect(result.EpubSize).To(BeNumerically(">", 10)) Expect(result.EpubContent).To(ContainSubstring("I've ransomed you from fear and hatred, and now I give you back to God.")) //we expect the publisher to close the channel when it's done Eventually(ctx, c).WithTimeout(time.Second).Should(BeClosed()) }, SpecTimeout(time.Second*30)) //this spec has 30 seconds to complete }) ``` As you can see Gomega allows us to make some pretty complex asynchronous assertions pretty easily! #### Testing Local Processes Launching and testing an external process is actually quite similar to testing an in-process asynchronous service (the example above). You typically leverage Gomega's [`gexec`](https://onsi.github.io/gomega/#gexec-testing-external-processes) and [`gbytes`](https://onsi.github.io/gomega/#gbytes-testing-streaming-buffers) packages. Let's imagine our book-publishing service was a actually a command-line tool we wanted to test: ```go //We compile the publisher in a BeforeSuite so its available to our specs //Not that this step can be skipped if the publisher binary is already precompiled var publisherPath string BeforeSuite(func() { var err error publisherPath, err = gexec.Build("path/to/publisher") Expect(err).NotTo(HaveOccurred()) DeferCleanup(gexec.CleanupBuildArtifacts) }) Describe("Publishing books", func() { It("can publish a book, emitting information as it goes", func(ctx SpecContext) { //First, we create a command to invoke the publisher and pass appropriate args cmd := exec.CommandContext(ctx, publisherPath, "-o=les-miserables.epub", "les-miserables.fixture") //Now we launch the command with `gexec`. This returns a session that wraps the running command. //We also tell `gexec` to tee any stdout/stderr output from the process to `GinkgoWriter` - this will //ensure we get all the process output if the spec fails. session, err := gexec.Start(cmd, GinkgoWriter, GinkgoWriter) Expect(err).NotTo(HaveOccurred()) //At this point the process is running in the background //In addition to teeing to GinkgoWriter gexec will capture any stdout/stderr output to //gbytes buffers. This allows us to make assertions against its stdout output using `gbytes.Say` Eventually(ctx, session).Should(gbytes.Say(`Publishing "Les Miserables...`)) Eventually(ctx, session).Should(gbytes.Say(`Published page 1/2783`)) Eventually(ctx, session).Should(gbytes.Say(`Published page 2782/2783`)) Eventually(ctx, session).Should(gbytes.Say(`Publish complete!`)) //We can also assert the session has exited Eventually(ctx, session).WithTimeout(time.Second).Should(gexec.Exit(0)) //with exit code 0 //At this point we should have the `les-miserables.epub` artifact Expect("les-miserables.epub").To(BeAnExistingFile()) result, err := epub.Load("les-miserables.epub") Expect(err).NotTo(HaveOccurred()) //we make some synchronous assertions on the result Expect(result.Title).To(Equal("Les Miserables")) Expect(result.EpubSize).To(BeNumerically(">", 10)) Expect(result.EpubContent).To(ContainSubstring("I've ransomed you from fear and hatred, and now I give you back to God.")) }, Time.Second * 30) }) ``` #### Testing Blocking Functions It's common in Go for functions to block and perform complex operations synchronously - and leave the work of spawning goroutines and managing thread-safety to the user. You can test such patterns easily with Gomega. For example, let's test a flow that performs a few expensive operations and assert that everything finishes eventually. ```go Describe("Change book font-size", func() { var book *books.Book BeforeEach(func() { book = loadBookWithContent("les_miserables.fixture") Expect(book).NotTo(BeNil()) }) It("can repaginate books without losing any content", func() { done := make(chan interface{}) go func() { defer GinkgoRecover() content := book.RawContent() Expect(book.Pages).To(Equal(2783)) //this might be quite expensive and will block... err := book.SetFontSize(28) Expect(err).NotTo(HaveOccurred()) Expect(book.Pages).To(BeNumerically(">", 2783)) Expect(book.RawContent()).To(Equal(content)) close(done) }() // now we wait for the `done` channel to close. Note that we neither pass in a context nor set an explicit timeout // in this case `Eventually` `will use Gomega's default global timeout (1 second, unless overridden by the user) Eventually(done).Should(BeClosed()) }) }) ``` This use of a `done` channel is idiomatic and guards the spec against potentially hanging forever. More typically, blocking functions like `SetFontSize` accept a `context.Context` to manage cancellation. In that case we can simply write: ```go Describe("Change book font-size", func() { var book *books.Book BeforeEach(func() { book = loadBookWithContent("les_miserables.fixture") Expect(book).NotTo(BeNil()) }) It("can repaginate books without losing any content", func(ctx SpecContext) { content := book.RawContent() Expect(book.Pages).To(Equal(2783)) //this might be quite expensive and will block... err := book.SetFontSize(ctx, 28) Expect(err).NotTo(HaveOccurred()) Expect(book.Pages).To(BeNumerically(">", 2783)) Expect(book.RawContent()).To(Equal(content)) }, SpecTimeout(time.Second)) }) ``` #### Testing External Systems When integration testing an external system, particularly a distributed system, you'll often find yourself needing to wait for the external state to converge and become eventually consistent. Gomega makes it easy to poll and validate that the system under test eventually exhibits the desired behavior. This is typically done by passing functions in to `Eventually` and `Consistently`. For example, let's imagine testing how an external library service handles notifying users about holds on their books. Here's what a fully worked example might look like: ```go var library *library.Client var _ = BeforeSuite(func() { var err error library, err = library.NewClient(os.Getenv("LIBRARY_SERVICE")) Expect(err).NotTo(HaveOccurred()) Eventually(library.Ping).Should(Succeed()) }) var _ = Describe("Getting notifications about holds", func() { var book *books.Book var sarah, jane *user.User BeforeEach(func(ctx SpecContext) { book = &books.Book{ Title: "My test book", Author: "Ginkgo", Pages: 17, } Expect(library.Store(ctx, book)).To(Succeed()) // we'll want to delete the book after the spec ends. `library` has a `Delete` function with signature `Delete(context.Context, *book.Book)`. // DeferCleanup will detect this signature and automatically pass a `SpecContext` (configured with a one second timeout thanks to the `NodeTimeout` decorator) // in as the first parameter. `book` will be passed in as the second parameter. DeferCleanup(library.Delete, book, NodeTimeout(time.Second)) sarah = user.NewUser(ctx, "Sarah", "integration-test-account+sarah@gmail.com") jane = user.NewUser(ctx, "Jane", "integration-test-account+jane@gmail.com") By("Sarah checks the book out") Expect(sarah.CheckOut(ctx, library, book)).To(Succeed()) }, NodeTimeout(time.Second*10)) It("notifies the user when their hold is ready", func(ctx SpecContext) { By("Jane can't check the book out so she places a hold") Expect(jane.CheckOut(ctx, library, book)).To(MatchError(books.ErrNoAvailableCopies)) Expect(jane.PlaceHold(ctx, library, book)).To(Succeed()) By("when Sarah returns the book") Expect(sarah.Return(ctx, library, book)).To(Succeed()) By("Jane eventually gets notified that her book is available in the library app...") Eventually(func(ctx SpecContext) ([]user.Notification, error) { return jane.FetchNotifications(ctx, library) }).WithContext(ctx).Should(ContainElement(user.Notification{Title: book.Title, State: book.ReadyForPickup})) By("...and in her email...") Eventually(func(ctx SpecContext) ([]string, error) { messages, err := gmail.Fetch(ctx, jane.EmailAddress) if err != nil { return nil, err } subjects := []string{} for _, message := range messages { subjects = append(subjects, message.Subject) } return subjects, nil }).WithContext(ctx).Should(ContainElement(fmt.Sprintf(`"%s" is available for pickup`, book.Title))) Expect(jane.CheckOut(ctx, library, book)).To(Succeed()) }, SpecTimeout(time.Second * 30)) }) ``` As you can see we are able to clearly test both synchronous concerns (blocking calls to the library service that return immediately) with asynchronous concerns (out-of-band things that happen after a library call has been made). The DSL allows us to clearly express our intent and capture the flow of this spec with relatively little noise. `Eventually` has a few more tricks that we can leverage to clean this code up a bit. Since `Eventually` accepts functions we can simply replace this: ```go Eventually(func(ctx SpecContext) ([]user.Notification, error) { return jane.FetchNotifications(ctx, library) }).WithContext(ctx).Should(ContainElement(user.Notification{Title: book.Title, State: book.ReadyForPickup})) ``` with this: ```go Eventually(jane.FetchNotifications).WithContext(ctx).WithArguments(library).Should(ContainElement(user.Notification{Title: book.Title, State: book.ReadyForPickup})) ``` Note that `Eventually` automatically asserts a niladic error as it polls the `FetchNotifications` function. Also note that we are passing in a reference to the method on the `jane` instance - not invoking it. `Eventually(jane.FetchNotifications())` would not work - you must pass in `Eventually(jane.FetchNotifications)`! `Eventually` can _also_ accept functions that take a `Gomega` parameter. These functions are then passed a local `Gomega` that can be used to make assertions _inside_ the function as it is polled. `Eventually` will retry the function if an assertion fails. This would allow us to replace: ```go Eventually(func(ctx SpecContext) ([]string, error) { messages, err := gmail.Fetch(ctx, jane.EmailAddress) if err != nil { return nil, err } subjects := []string{} for _, message := range messages { subjects = append(subjects, message.Subject) } return subjects, nil }).WithContext(ctx).Should(ContainElement(fmt.Sprintf(`"%s" is available for pickup`, book.Title))) ``` with ```go Eventually(func(g Gomega, ctx SpecContext) []string { //note: g Gomega must go first messages, err := gmail.Fetch(ctx, jane.EmailAddress) g.Expect(err).NotTo(HaveOccurred()) subjects := []string{} for _, message := range messages { subjects = append(subjects, message.Subject) } return subjects }).WithContext(ctx).Should(ContainElement(fmt.Sprintf(`"%s" is available for pickup`, book.Title))) ``` we can even push the entire assertion into the polled function: ```go Eventually(func(g Gomega, ctx SpecContext) { messages, err := gmail.Fetch(ctx, jane.EmailAddress) g.Expect(err).NotTo(HaveOccurred()) subjects := []string{} for _, message := range messages { subjects = append(subjects, message.Subject) } g.Expect(subjects).To(ContainElement(fmt.Sprintf(`"%s" is available for pickup`, book.Title))) }).WithContext(ctx).Should(Succeed()) ``` this approach highlights a special-case use of the `Succeed()` matcher with `Eventually(func(g Gomega) {})` - `Eventually` will keep retrying the function until no failures are detected. > You may be wondering why we need to pass in a dedicated `Gomega` instance to the polled function. That's because the default global-level assertions are implicitly tied to Ginkgo's `Fail` handler. The first failed assertion in an `Eventually` would cause the spec to fail with no possibility to retry. By passing in a fresh `Gomega` instance, `Eventually` can monitor for failures itself and control the final failure/success state of the assertion it is governing. Finally, since we're on the topic of simplifying things, we can make use of the fact that `ContainElement` can take a matcher to compose it with the `WithTransform` matcher and get rid of the `subjects` loop: ```go Eventually(func(g Gomega, ctx SpecContext) { messages, err := gmail.Fetch(jane.EmailAddress) g.Expect(err).NotTo(HaveOccurred()) expectedSubject := fmt.Sprintf(`"%s" is available for pickup`, book.Title) subjectGetter := func(m gmail.Message) string { return m.Subject } g.Expect(messages).To(ContainElement(WithTransform(subjectGetter, Equal(expectedSubject)))) }).WithContext(ctx).Should(Succeed()) ``` ### Patterns for Parallel Integration Specs One of Ginkgo's strengths centers around building and running large complex integration suites. Integration suites are spec suites that exercise multiple related components to validate the behavior of the integrated system as a whole. They are notorious for being difficult to write, susceptible to random failure, and painfully slow. They also happen to be incredibly valuable, particularly when building large complex distributed systems. The [Patterns for Asynchronous Testing](#patterns-for-asynchronous-testing) section above goes into depth about patterns for testing asynchronous systems like these. This section will cover patterns for ensuring such specs can run in parallel. Make sure to read the [Spec Parallelization](#spec-parallelization) section to build a mental model for how Ginkgo supports parallelization first - it's important to understand that parallel specs are running in **separate** processes and are coordinated via the Ginkgo CLI. #### Managing External Processes in Parallel Suites We covered how to use `gexec` and `gbytes` to compile, launch, and test external processes in the [Testing Local Processes](#testing-local-processes) portion of the asynchronous testing section. We'll extend the example there to cover how to design such a test to work well in parallel. First recall that we used a `BeforeSuite` to compile our `publisher` binary: ```go var publisherPath string BeforeSuite(func() { var err error publisherPath, err = gexec.Build("path/to/publisher") Expect(err).NotTo(HaveOccurred()) DeferCleanup(gexec.CleanupBuildArtifacts) }) ``` This code will work fine in parallel as well (under the hood `gexec.Build` places build artifacts in a randomly-generated temporary directory - this is why you need to call `gexec.CleanupBuildArtifacts` to clean up); but it's inefficient and all your parallel processes will spend time up front compiling multiple copies of the same binary. Instead, we can use `SynchronizedBeforeSuite` to perform the compilation step just once: ```go var publisherPath string SynchronizedBeforeSuite(func() []byte { path, err := gexec.Build("path/to/publisher") Expect(err).NotTo(HaveOccurred()) DeferCleanup(gexec.CleanupBuildArtifacts) return []byte(path) }, func(path []byte) { publisherPath = string(path) }) ``` Now only process #1 will compile the publisher. All other processes will wait until it's done. Once complete it will pass the path to the compiled artifact to all other processes. Note that the `DeferCleanup` in the `SynchronizedBeforeSuite` will have the same runtime semantics as a `SynchronizedAfterSuite` so `gexec` will not cleanup after itself until _all_ processes have finished running. Now any spec running on any process can simply launch it's own instance of the `publisher` process via `gexec` and make assertions on its output with `gbytes`. The only thing to be aware of is potential interactions between the multiple publisher processes if they happen to access some sort of shared singleton resources... Keep reading! #### Managing External Resources in Parallel Suites: Files The filesystem is a shared singleton resource. Each parallel process in a parallel spec run will have access to the same shared filesystem. As such it is important to avoid spec pollution caused by accidental collisions. For example, consider the following publisher specs: ```go Describe("Publishing books", func() { It("can publish a complete epub", func(ctx SpecContext) { cmd := exec.CommandContext(ctx, publisherPath, "-o=out.epub", "les-miserables.fixture") session, err := gexec.Start(cmd, GinkgoWriter, GinkgoWriter) Expect(err).NotTo(HaveOccurred()) Eventually(ctx, session).Should(gexec.Exit(0)) //with exit code 0 result, err := epub.Load("out.epub") Expect(err).NotTo(HaveOccurred()) Expect(result.EpubPages).To(Equal(2783)) }, SpecTimeout(time.Second*30)) It("can publish a preview that contains just the first chapter", func(ctx SpecContext) { cmd := exec.CommandContext(ctx, publisherPath, "-o=out.epub", "--preview", "les-miserables.fixture") session, err := gexec.Start(cmd, GinkgoWriter, GinkgoWriter) Expect(err).NotTo(HaveOccurred()) Eventually(ctx, session).Should(gexec.Exit(0)) //with exit code 0 result, err := epub.Load("out.epub") Expect(err).NotTo(HaveOccurred()) Expect(result.EpubPages).To(BeNumerically("<", 2783)) Expect(result.EpubContent).To(ContainSubstring("Chapter 1")) Expect(result.EpubContent).NotTo(ContainSubstring("Chapter 2")) }) }) ``` these specs will always run fine in series - but can fail in subtle and confusing ways when run in parallel! Since both publish to the same `out.epub` file simultaneously collisions are possible. There are multiple ways to approach this. Perhaps the obvious way would be to manually ensure a different output name for each spec: ```go Describe("Publishing books", func() { It("can publish a complete epub", func(ctx SpecContext) { cmd := exec.CommandContext(ctx, publisherPath, "-o=complete.epub", "les-miserables.fixture") ... }) It("can publish a preview that contains just the first chapter", func(ctx SpecContext) { cmd := exec.CommandContext(ctx, publisherPath, "-o=preview.epub", "--preview", "les-miserables.fixture") ... }) }) ``` that's... _ok_. But it's asking for trouble by putting the namespacing burden on the user. A better alternative would be to carve out a separate namespace for each spec. For example, we could create a temporary directory: ```go var tmpDir string BeforeEach(func() { tmpDir = GinkgoT().TempDir() }) Describe("Publishing books", func() { It("can publish a complete epub", func(ctx SpecContext) { path := filepath.Join(tmpDir, "out.epub") cmd := exec.CommandContext(ctx, publisherPath, "-o="+path, "les-miserables.fixture") ... }) It("can publish a preview that contains just the first chapter", func(ctx SpecContext) { path := filepath.Join(tmpDir, "out.epub") cmd := exec.CommandContext(ctx, publisherPath, "-o="+path, "--preview", "les-miserables.fixture") ... }) }) ``` (here we're using `GinkgoT().TempDir()` to access Ginkgo's implementation of `t.TempDir()` which cleans up after itself - there's no magic here. You could have simply called `os.MkdirTemp` and cleaned up afterwards yourself.) This approach works fine but has the sometimes unfortunate side-effect of placing your files in a random location which can make debugging a bit more tedious. Another approach - and the one used by Ginkgo's own integration suite - is to use the current parallel process index to shard the filesystem: ```go var pathTo func(path string) string BeforeEach(func() { //shard based on our current process index. //this starts at 1 and goes up to N, the number of parallel processes. dir := fmt.Sprintf("./tmp-%d", GinkgoParallelProcess()) os.MkdirAll(dir) DeferCleanup(os.RemoveAll, dir) pathTo = func(path string) string { return filepath.Join(dir, path)} }) Describe("Publishing books", func() { It("can publish a complete epub", func(ctx SpecContext) { path := pathTo("out.epub") cmd := exec.CommandContext(ctx, publisherPath, "-o="+path, "les-miserables.fixture") ... }) It("can publish a preview that contains just the first chapter", func(ctx SpecContext) { path := pathTo("out.epub") cmd := exec.CommandContext(ctx, publisherPath, "-o="+path, "--preview", "les-miserables.fixture") ... }) }) ``` this will create a namespaced local temp directory and provides a convenience function for specs to access paths to the directory. The directory is cleaned up after each spec. One nice thing about this approach is our ability to preserve the artifacts in the temporary directory in case of failure. A common pattern when debugging is to use `--fail-fast` to indicate that the suite should stop running as soon as the first failure occurs. We can key off of that config to change the behavior of our cleanup code: ```go var pathTo func(path string) string BeforeEach(func() { //shard based on our current process index. //this starts at 1 and goes up to N, the number of parallel processes. dir := fmt.Sprintf("./tmp-%d", GinkgoParallelProcess()) os.MkdirAll(dir) DeferCleanup(func() { suiteConfig, _ := GinkgoConfiguration() if CurrentSpecReport().Failed() && suiteConfig.FailFast { GinkgoWriter.Printf("Preserving artifacts in %s\n", dir) return } Expect(os.RemoveAll(dir)).To(Succeed()) }) pathTo = func(path string) string { return filepath.Join(dir, path)} }) ``` now, the temporary directory will be preserved in the event of spec failure, but only if `--fail-fast` is configured. #### Managing External Resources in Parallel Suites: Ports Another shared singleton resources is the set of available ports on the local machine. If you need to be able to explicitly specify a port to use during a spec (e.g. you're spinning up an external process that needs to be told what port to listen on) you'll need to be careful how you carve up the available set of ports. For example, the following would not work: ```go var libraryAddr string BeforeSuite(func() { libraryAddr := "127.0.0.1:50000" library.Serve(listenAddr) client = library.NewClient(listenAddr) }) ``` when running in parallel each process will attempt to listen on port 50000 and a race with only one winner will ensue. You could, instead, have the server you're spinning up figure out a free port to use and report it back - but that is not always possible in the case where a service must be explicitly configured. Instead, you can key off of the current parallel process index to give each process a unique port. In this case we could: ```go var libraryAddr string BeforeSuite(func() { libraryAddr := fmt.Sprintf("127.0.0.1:%d", 50000 + GinkgoParallelProcess()) library.Serve(listenAddr) client = library.NewClient(listenAddr) }) ``` now each process will have its own unique port. #### Patterns for Testing against Databases Stateful services that store data in external databases benefit greatly from a robust comprehensive test suite. Unfortunately, many testers shy away from full-stack testing that includes the database for fear of slowing their suites down. Fake/mock databases only get you so far, however. In this section we outline patterns for spinning up real databases and testing against them in ways that are parallelizable and, therefore, able to leverage the many cores in modern machines to keep our full-stack tests fast. The core challenge with stateful testing is to ensure that specs do not pollute one-another. This applies in the serial context where a one spec can change the state of the database in a way that causes a subsequent spec to fail. This also applies in the parallel context where multiple specs can write to the same database at the same time in contradictory ways. Thankfully there are patterns that make mitigating these sorts of pollution straightforward and transparent to the user writing specs. Throughout these examples we have a `DBRunner` library that can spin up instances of a database and a `DBClient` library that can connect to that instance and perform actions. We aren't going to pick any particular database technology as these patterns apply across most of them. ##### A Database for Every Spec Here's an incredibly expensive but sure-fire way to make sure each spec has a clean slate of data: ```go var client *DBClient.Client var _ = BeforeEach(func() { db, err := DBRunner.Start() Expect(err).NotTo(HaveOccurred()) DeferCleanup(db.Stop) client = DBClient.New(db) Expect(client.Connect()).To(Succeed()) DeferCleanup(client.Disconnect) client.InitializeSchema() }) ``` Now, each spec will get a fresh running database, with a clean initialized schema, to use. This will work - but will probably be quite slow, even when running in parallel. ##### A Database for Every Parallel Process Instead, a more common pattern is to spin up a database for each parallel process and reset its state between specs. ```go var client *DBClient.Client var snapshot *DBClient.Snapshot var _ = BeforeSuite(func() { db, err := DBRunner.Start() Expect(err).NotTo(HaveOccurred()) DeferCleanup(db.Stop) client = DBClient.New(db) Expect(client.Connect()).To(Succeed()) DeferCleanup(client.Disconnect) client.InitializeSchema() snapshot, err = client.TakeSnapshot() Expect(err).NotTo(HaveOccurred()) }) var _ = BeforeEach(func() { Expect(client.RestoreSnapshot(snapshot)).To(Succeed()) }) ``` here we've assumed the `client` can take and restore a snapshot of the database. This could be as simple as truncating tables in a SQL database or clearing out a root key in a hierarchical key-value store. Such methods are usually quite _fast_ - certainly fast enough to warrant full-stack testing over mock/fake-heavy testing. With this approach each parallel process has its own dedicated database so there is no chance for cross-spec pollution when running in parallel. Within each parallel process the dedicated database is cleared out between specs so there's no chance for spec pollution from one spec to the next. This all works if you have the ability to spin up a local copy of the database. But there are times when you must rely on an external stateful singleton resource and need to test against it. We'll explore patterns for testing those next. #### Patterns for Testing against Singletons There are times when your spec suite must run against a stateful shared singleton system. Perhaps it is simply too expensive to spin up multiple systems (e.g. each "system" is actually a memory-hungry cluster of distributed systems; or, perhaps, you are testing against a real-life instance of a service and can't spin up another instance). In such cases the recommended pattern for ensuring your specs are parallelizable is to embrace sharding the external service by the parallel process index. Exactly how this is done will depend on the nature of the system. Here are some examples to give you a sense for how to approach this: - If you're testing against a shared hierarchical key-value store (in which the keys are represented as `/paths/to/values` - e.g. S3, etcd) you can write your specs and code to accept a configurable root key such that all values are stored under `/{ROOT}/path/to/value`. The suite can then configure `ROOT = fmt.Sprintf("test-%d", GinkgoParallelProcess())` - If you're testing an external multi-tenant service you can have your suite create a unique tenant per parallel process. Perhaps something like `service.CreateUser(fmt.Sprintf("test-user-%d", GinkgoParallelProcess()))` - If you're testing an external service that supports namespace you can request a dedicated namespace per parallel process (e.g. a dedicated Cloud Foundry org and space, or a dedicated Kubernetes namespace). The details will be context dependent - but generally speaking you should be able to find a way to shard access to the singleton system by `GinkgoParallelProcess()`. You'll also need to figure out how to reset the shard between specs to ensure that each spec has a clean slate to operate from. #### Some Subtle Parallel Testing Gotchas We'll round out the parallel testing patterns with a couple of esoteric gotchas. There's a somewhat obscure issue where an external process that outlives the current spec suite can cause the spec suite to hang mysteriously. If you've hit that issue read through this [GitHub issue](https://github.com/onsi/gomega/issues/473) - there's likely a stdout/stderr pipe that's sticking around preventing Go's `cmd.Wait()` from returning. When you spin up a process yourself you should generally have it pipe its output to `GinkgoWriter`. If you pipe to `os.Stdout` and/or `os.Stderr` and the process outlives the current spec you'll cause Ginkgo's output interceptor to hang. Ginkgo will actually catch this and print out a long error message telling you what to do. You can learn more on the associated [GitHub issue](https://github.com/onsi/ginkgo/issues/851) ### Benchmarking Code Go's built-in `testing` package provides support for running `Benchmark`s. Earlier versions of Ginkgo subject-node variants that were able to mimic Go's `Benchmark` tests. As of Ginkgo 2.0 these nodes are no longer available. Instead, Ginkgo users can benchmark their code using Gomega's substantially more flexible `gmeasure` package. If you're interested, check out the `gmeasure` [docs](https://onsi.github.io/gomega/#gmeasure-benchmarking-code). Here we'll just provide a quick example to show how `gmeasure` integrates into Ginkgo's reporting infrastructure. `gmeasure` is structured around the metaphor of Experiments. With `gmeasure` you create `Experiments` that can record multiple named `Measurements`. Each named `Measurement` can record multiple values (either `float64` or `duration`). `Experiments` can then produce reports to show the statistical distribution of their `Measurements` and different `Measurements`, potentially from different `Experiments` can be ranked and compared. `Experiments` can also be cached using an `ExperimentCache` - this can be helpful to avoid rerunning expensive experiments _and_ to save off "gold-master" experiments to compare against to identify potential regressions in performance - orchestrating all that is left to the user. Here's an example where we profile how long it takes to repaginate books: ```go Describe("Repaginating Books", func() { var book *books.Book BeforeEach(func() { book = LoadFixture("les-miserables.json") }) // this is a spec that validates the behavior is correct // note that we can mix validation specs alongside performance specs It("can repaginate books", func() { Expect(book.CurrentFontSize()).To(Equal(12)) originalPages := book.Pages book.SetFontSize(10) Expect(book.RecomputePages()).To(BeNumerically(">", originalPages)) }) // this is our performance spec. we mark it as Serial to ensure it does not run in // parallel with other specs (which could affect performance measurements) // we also label it with "measurement" - this is optional but would allow us to filter out // measurement-related specs more easily It("repaginates books efficiently", Serial, Label("measurement"), func() { //we create a new experiment experiment := gmeasure.NewExperiment("Repaginating Books") //Register the experiment as a ReportEntry - this will cause Ginkgo's reporter infrastructure //to print out the experiment's report and to include the experiment in any generated reports AddReportEntry(experiment.Name, experiment) //we sample a function repeatedly to get a statistically significant set of measurements experiment.Sample(func(idx int) { book = LoadFixture("les-miserables.json") //always start with a fresh copy book.SetFontSize(10) //measure how long it takes to RecomputePages() and store the duration in a "repagination" measurement experiment.MeasureDuration("repagination", func() { book.RecomputePages() }) }, gmeasure.SamplingConfig{N:20, Duration: time.Minute}) //we'll sample the function up to 20 times or up to a minute, whichever comes first. }) }) ``` Now when this spec runs Ginkgo will print out a report detailing the experiment: ```bash Will run 1 of 1 specs ------------------------------ • [2.029 seconds] Repaginating Books repaginates books efficiently [measurement] /path/to/books_test.go:19 Begin Report Entries >> Repaginating Books - /path/to/books_test.go:21 @ 11/04/21 13:42:57.936 Repaginating Books Name | N | Min | Median | Mean | StdDev | Max ========================================================================== repagination [duration] | 20 | 5.1ms | 104ms | 101.4ms | 52.1ms | 196.4ms << End Report Entries ``` This is helpful - but the real value in a performance suite like this would be to capture possible performance regressions. There are multiple ways of doing this - you could use an [Experiment Cache](https://onsi.github.io/gomega/#caching-experiments) and make the suite [configurable](#supporting-custom-suite-configuration) such that a baseline experiment is stored to disk when the suite is so configured. Then, when the suite runs, it simply loads the baseline from the cache and compares it to the measured duration. Ginkgo's own performance suite does this. Alternatively you can just hard-code an expected value after running the experiment and make an appropriate assertion. For example: ```go It("repaginates books efficiently", Serial, Label("measurement"), func() { experiment := gmeasure.NewExperiment("Repaginating Books") AddReportEntry(experiment.Name, experiment) experiment.Sample(func(idx int) { book = LoadFixture("les-miserables.json") book.SetFontSize(10) experiment.MeasureDuration("repagination", func() { book.RecomputePages() }) }, gmeasure.SamplingConfig{N:20, Duration: time.Minute}) //we get the median repagination duration from the experiment we just ran repaginationStats := experiment.GetStats("repagination") medianDuration := repaginationStats.DurationFor(gmeasure.StatMedian) //and assert that it hasn't changed much from ~100ms Expect(medianDuration).To(BeNumerically("~", 100*time.Millisecond, 50*time.Millisecond)) }) ``` now the spec will fail if the pagination time ever changes drastically from its measured value. Of course the actual runtime will depend on the machine and test environment you're running on - so some caveats will apply. Nonetheless an upper bound spec such as: ```go Expect(medianDuration).To(BeNumerically("<", 300*time.Millisecond)) ``` could still be a useful smoketest to catch any major regressions early in the development cycle. ### Building Custom Matchers As you've seen throughout this documentation, Gomega allows you to write expressive assertions. You can build on Gomega's building blocks to construct custom matchers tuned to the semantics of your codebase. One way to do this is by implementing Gomega's `GomegaMatcher` interface. A simpler, alternative, however, is to simply compose matchers together in a simple function. For example, let's write a matcher that asserts that our book is valid, has a given title, author, and page-count. Rather than repeat this all the time: ```go Expect(book.IsValid()).To(BeTrue()) Expect(book.Title).To(Equal("Les Miserables")) Expect(book.Author).To(Equal("Victor Hugo")) Expect(book.Pages).To(Equal(2783)) ``` we can implement a function that returns a composite Gomega matcher: ```go func BeAValidBook(title string, author string, pages int) types.GomegaMatcher { return And( WithTransform(func(book *books.Book) bool { return book.IsValid() }, BeTrue()), HaveField("Title", Equal(title)), HaveField("Author", Equal(author)), HaveField("Pages", Equal(pages)), ) } ``` this function uses Gomega's `And` matcher to require that the four passed-in matchers are satisfied. It then uses `WithTransform` to accept the passed-in book and call it's `IsValid()` method, then asserts the returned value is `true`. It then uses the `HaveField` matcher to make assertions on the fields within the `Book` struct. Now we can write: ```go Expect(book).To(BeAValidBook("Les Miserables", "Victor Hugo", 2783)) ``` We can go one step further and use typed parameters to pick and choose which pieces of `Book` we want to test with our matcher. This is a bit contrived for our simple example but can be quite useful in more complex domains: ```go type Title string type Author string type Pages int func BeAValidBook(params ...interface{}) types.GomegaMatcher { matchers := []types.GomegaMatcher{ WithTransform(func(book *books.Book) bool { return book.IsValid() }, BeTrue()) } if len(params) > 0 { for _, param := range params { switch v := param.(type) { case Title: matchers = append(matchers, HaveField("Title", Equal(v))) case Author: matchers = append(matchers, HaveField("Author", Equal(v))) case Pages: matchers = append(matchers, HaveField("Pages", Equal(v))) default: Fail("Unknown type %T in BeAValidBook() \n", v) } } } return And(matchers...) } ``` Now we can do things like: ```go Expect(book).To(BeAValidBook()) //simply asserts IsValid() is true Expect(book).To(BeAValidBook(Title("Les Miserables"))) Expect(book).To(BeAValidBook(Author("Victor Hugo"))) Expect(book).To(BeAValidBook(Title("Les Miserables"), Pages(2783))) ``` The failure messages generated by composed matchers are generally good enough to capture the reason for the failure. However if you want more fine-control over the message, or if you want more complex logic in your matcher you can use [`gcustom`](https://onsi.github.io/gomega/#gcustom-a-convenient-mechanism-for-buildling-custom-matchers) to build custom matchers using a simple function and templates - to learn more check out the [`gucstom` docs](https://onsi.github.io/gomega/#gcustom-a-convenient-mechanism-for-buildling-custom-matchers) and [godoc](https://pkg.go.dev/github.com/onsi/gomega/gcustom). ## Decorator Reference We've seen a number of Decorators detailed throughout this documentation. This reference collects them all in one place. #### Node Decorators Overview Ginkgo's container nodes, subject nodes, and setup nodes all accept decorators. Decorators are specially typed arguments passed into the node constructors. They can appear anywhere in the `args ...interface{}` list in the constructor signatures: ```go func Describe(text string, args ...interface{}) func It(text string, args ...interface{}) func BeforeEach(args ...interface{}) ``` Ginkgo will vet the passed in decorators and exit with a clear error message if it detects any invalid configurations. Moreover, Ginkgo also supports passing in arbitrarily nested slices of decorators. Ginkgo will unroll these slices and process the flattened list. This makes it easier to pass around groups of decorators. For example, this is valid: ```go markFlaky := []interface{}{Label("flaky"), FlakeAttempts(3)} var _ = Describe("a bunch of flaky controller tests", markFlaky, Label("controller"), func() { ... } ``` The resulting tests will be decorated with `FlakeAttempts(3)` and the two labels `flaky` and `controller`. #### The Serial Decorator The `Serial` decorator applies to container nodes and subject nodes only. It is an error to try to apply the `Serial` decorator to a setup node. `Serial` allows the user to mark specs and containers of specs as only eligible to run in serial. Ginkgo will guarantee that these specs never run in parallel with other specs. If a container is marked as `Serial` then all the specs defined in that container will be marked as `Serial`. You cannot mark specs and containers as `Serial` if they appear in an `Ordered` container. Instead, mark the `Ordered` container as `Serial`. #### The Ordered Decorator The `Ordered` decorator applies to container nodes only. It is an error to try to apply the `Ordered` decorator to a setup or subject node. `Ordered` allows the user to [mark containers of specs as ordered](#ordered-containers). Ginkgo will guarantee that the container's specs will run in the order they appear in and will never run in parallel with one another (though they may run in parallel with other specs unless the `Serial` decorator is also applied to the `Ordered` container). When a spec in an `Ordered` container fails, all subsequent specs in the ordered container are skipped. Only `Ordered` containers can contain `BeforeAll` and `AfterAll` setup nodes. #### The ContinueOnFailure Decorator The `ContinueOnFailure` decorator applies to outermost `Ordered` container nodes only. It is an error to try to apply the `ContinueOnFailure` decorator to anything other than an `Ordered` container - and that `Ordered` container must not have any parent `Ordered` containers. When an `Ordered` container is decorated with `ContinueOnFailure` then the failure of one spec in the container will not prevent other specs from running. This is useful in cases where `Ordered` containers are being used to have share common (expensive) setup for a collection of specs but the specs, themselves, don't rely on one another. #### The OncePerOrdered Decorator The `OncePerOrdered` decorator applies to setup nodes only. It is an error to try to apply the `OncePerOrdered` decorator to a container or subject node. Normally, setup nodes like `BeforeEach` run for every spec in a suite. When decorated with `OncePerOrdered`, however, `BeforeEach` will treat any `Ordered` container at a deeper nesting level as a single executable unit and run once before the container begins (mimicking the semantics of `BeforeAll`). The usecases for this are covered in more detail in the [Setup around Ordered Containers: the OncePerOrdered Decorator](#setup-around-ordered-containers-the-onceperordered-decorator) section of the docs. #### The Label Decorator The `Label` decorator applies to container nodes and subject nodes only. It is an error to try to apply the `Label` decorator to a setup node. You can also apply the `Label` decorator to your `RunSpecs` invocation to annotate the entire suite with a label. `Label` allows the user to annotate specs and containers of specs with labels. The `Label` decorator takes a variadic set of strings allowing you to apply multiple labels simultaneously. Labels are arbitrary strings that do not include the characters `"&|!,()/"`. Specs can have as many labels as you'd like and the set of labels for a given spec is the union of all the labels of the container nodes and the subject node. Labels can be used to control which subset of tests to run. This is done by providing the `--label-filter` flag to the `ginkgo` CLI. More details can be found at [Spec Labels](#spec-labels). #### The Focus and Pending Decorators The `Focus` and `Pending` decorators apply to container nodes and subject nodes only. It is an error to try to `Focus` or `Pending` a setup node. Using these decorators is identical to using the `FX` or `PX` form of the node constructor. For example: ```go FDescribe("container", func() { It("runs", func() {}) PIt("is pending", func() {}) }) ``` and ```go Describe("container", Focus, func() { It("runs", func() {}) It("is pending", Pending, func() {}) }) ``` are equivalent. It is an error to decorate a node as both `Pending` and `Focus`: ```go It("is invalid", Focus, Pending, func() {}) //this will cause Ginkgo to exit with an error ``` The `Focus` and `Pending` decorators are propagated through the test hierarchy as described in [Pending Specs](#pending-specs) and [Focused Specs](#focused-specs) #### The Offset Decorator The `Offset(uint)` decorator applies to all decorable nodes. The `Offset(uint)` decorator allows the user to change the stack-frame offset used to compute the location of the test node. This is useful when building shared test behaviors. For example: ```go SharedBehaviorIt := func() { It("does something common and complicated", Offset(1), func() { ... }) } Describe("thing A", func() { SharedBehaviorIt() }) Describe("thing B", func() { SharedBehaviorIt() }) ``` now, if the `It` defined in `SharedBehaviorIt` the location reported by Ginkgo will point to the line where `SharedBehaviorIt` is *invoked*. `Offset`s only apply to the node that they decorate. Setting the `Offset` for a container node does not affect the `Offset`s computed in its child nodes. If multiple `Offset`s are provided on a given node, only the last one is used. Lastly, since introducing `Offset` Ginkgo has introduced `GinkgoHelper()` which marks the current function as a test helper who's location should be skipped when determining the location for a node. We generally recommend using `GinkgoHelper()` instead of `Offset()` to manage how locations are computed. The above example could be rewritten as ```go SharedBehaviorIt := func() { GinkgoHelper() It("does something common and complicated", func() { ... }) } Describe("thing A", func() { SharedBehaviorIt() }) Describe("thing B", func() { SharedBehaviorIt() }) ``` #### The CodeLocation Decorator In addition to `Offset`, users can decorate nodes with a `types.CodeLocation`. `CodeLocation`s are the structs Ginkgo uses to capture location information. You can, for example, set a custom location using `types.NewCustomCodeLocation(message string)`. Now when the location of the node is emitted the passed in `message` will be printed out instead of the usual `file:line` location. Passing a `types.CodeLocation` decorator in has the same semantics as passing `Offset` in: it only applies to the node in question. #### The FlakeAttempts Decorator The `FlakeAttempts(uint)` decorator applies to container and subject nodes. It is an error to apply `FlakeAttempts` to a setup node. `FlakeAttempts` allows the user to flag specs trees as potentially flaky. Ginkgo will retry the spec up to the number of times specified in `FlakeAttempts` until they pass. For example: ```go Describe("flaky tests", FlakeAttempts(3), func() { It("is flaky", func() { ... }) It("is also flaky", func() { ... }) It("is _really_ flaky", FlakeAttempts(5) func() { ... }) It("is _not_ flaky", FlakeAttempts(1), func() { ... }) }) ``` With this setup, `"is flaky"` and `"is also flaky"` will run up to 3 times. `"is _really_ flaky"` will run up to 5 times. `"is _not_ flaky"` will run only once. Note that if multiple `FlakeAttempts` appear in a spec's hierarchy, the most deeply nested `FlakeAttempts` wins. If multiple `FlakeAttempts` are passed into a given node, the last one wins. If `ginkgo --flake-attempts=N` is set the value passed in by the CLI will override all the decorated values. Every spec in the test suite will now run up to `N` times. #### The MustPassRepeatedly Decorator The `MustPassRepeatedly(uint)` decorator applies to container and subject nodes. It is an error to apply `MustPassRepeatedly` to a setup node. the `MustPassRepeatedly` flag allows the user to repeatedly run specs in a controlled manner. Ginkgo will repeatedly run specs up to the number of times specified in `MustPassRepeatedly` or until they fail. For example: ```go Describe("repeated specs", MustPassRepeatedly(3), func() { It("is repeated", func() { ... }) It("is also repeated", func() { ... }) It("is repeated even more", MustPassRepeatedly(5) func() { ... }) It("is repeated less", MustPassRepeatedly(1), func() { ... }) }) ``` With this setup, `"is repeated"` and `"is also repeated"` will run up to 3 times. `"is repeated even more"` will run up to 5 times. `"is repeated less"` will run only once. Note that if multiple `MustPassRepeatedly` appear in a spec's hierarchy, the most deeply nested `MustPassRepeatedly` wins. If multiple `MustPassRepeatedly` are passed into a given node, the last one wins. The `ginkgo --repeat=N` value passed in by the CLI has no relation with the `MustPassRepeatedly` decorator. If the `--repeat` CLI flag is used and a container or subject node also contains the `MustPassRepeatedly` decorator, then the spec will run up to `N*R` times, where `N` is the values passed to the `--repeat` CLI flag and `R` is the value passed to the MustPassRepeatedly decorator. If the `MustPassRepeatedly` decorator is set, it will override the `ginkgo --flake-attempts=N` CLI config. The specs that do not contain the `MustPassRepeatedly(R)` decorator will still run up to `N` times, in accordance to the `ginkgo --flake-attempts=N` CLI config. #### The SuppressProgressOutput Decorator When running with `ginkgo -v -progress` Ginkgo will emit information about each node just before it runs. This information goes to the `GinkgoWriter` and straight to the console if using `-v`. There are contexts when this can be overly noisy. In particular, `ReportBeforeEach` and `ReportAfterEach` nodes always run, even when a spec is skipped. This can make Ginkgo's output noise when running with `-v -progress` as each `Report*Each` node will be announced, even for skipped specs. The `SuppressProgressOutput` decorator allows you to disable progress reporting for a given node: ```go ReportAfterEach(func(report SpecReport) { // ... }, SuppressProgressReporting) ReportAfterEach(func(ctx SpecContext, report SpecReport) { // ... }, NodeTimeout(1 * time.Minute), SuppressProgressReporting) ``` #### The PollProgressAfter and PollProgressInterval Decorators As described in the [Getting Visibility Into Long-Running Specs](#getting-visibility-into-long-running-specs) section, the globally specified values for `--poll-progress-after` and `--poll-progress-interval` can be overridden on a particular node using the `PollProgressAfter(INTERVAL)` and `PollProgressInterval(INTERVAL)` decorators. Here, `INTERVAL` is a `time.Duration` and when specified Ginkgo will start emitting Progress Reports for the node after a duration of `PollProgressAfter` and will repeatedly emit a Progress Report at an interval of `PollProgressInterval`. To turn off progress reporting for a given node, set `PollProgressAfter` to `0`. Both of these decorators can only be used on subject and setup nodes, not container nodes. #### The SpecTimeout, NodeTimeout, and GracePeriod Decorators As described in the [Spec Timeouts and Interruptible Nodes](#spec-timeouts-and-interruptible-nodes) section, Ginkgo allows you to decorate interruptible nodes with individual `NodeTimeout`s and spec-wide `SpecTimeout`s. `NodeTimeout` takes a `time.Duration` and applies to any interruptible node (i.e. a node with a function that accepts a `SpecContext`). `SpecTimeout` also takes a `time.Duration` but applies only to `It` subject nodes. Whereas `NodeTimeout` specified a deadline for an individual node, `SpecTimeout` specifies a deadline for all nodes associated with an individual spec. Once interrupted, Ginkgo waits for a Grace Period before abandoning a node and moving on. A global Grace Period can be specified via the `--grace-period=DURATION` cli flag and overridden by the `GracePeriod` decorator on a per-node basis. `GracePeriod` takes a `time.Duration` and can only be applied to interruptible nodes. Currently none of these decorators can be applied to container nodes. ## Ginkgo CLI Overview This chapter provides a quick overview and tour of the Ginkgo CLI. For comprehensive details about all of the Ginkgo CLI's flags, run `ginkgo help`. To get information about Ginkgo's implicit `run` command (i.e. what you get when you just run `ginkgo`) run `ginkgo help run`. The Ginkgo CLI is the recommended and supported tool for running Ginkgo suites. While you _can_ run Ginkgo suites with `go test` you must use the CLI to run suites in parallel and to aggregate profiles. There are also a (small) number of `go test` flags that Ginkgo does not support - an error will be emitted if you attempt to use these (for example, `go test -count=N`, use `ginkgo -repeat=N` instead). In addition to Ginkgo's own flags, the `ginkgo` CLI also supports passing through (nearly) all `go test` flags and `go build` flags. These are documented under `ginkgo help run` and `ginkgo help build` (which provides a detailed list of available `go build` flags). If you think Ginkgo's missing anything, please open an [issue](https://github.com/onsi/ginkgo/issues/new). ### Running Specs By default: ```bash ginkgo ``` Will run the suite in the current directory. You can run multiple suites by passing them in as arguments: ```bash ginkgo path/to/suite path/to/other/suite ``` or by running: ```bash ginkgo -r #or ginkgo ./... ``` which will recurse through the current file tree and run any suites it finds. To pass additional arguments or custom flags down to your suite use `--` to separate your arguments from arguments intended for `ginkgo`: ```bash ginkgo -- ``` Finally, note that any Ginkgo flags must appear _before_ the list of packages. Putting it all together: ```bash ginkgo -- ``` By default Ginkgo is running the `run` subcommand. So all these examples can also be written as `ginkgo run -- `. To get help about Ginkgo's run flags you'll need to run `ginkgo help run`. ### Precompiling Suites It is often convenient to precompile suites and distribute them as binaries. You can do this with `ginkgo build`: ```bash ginkgo build path/to/suite /path/to/other/suite ``` This will produce precompiled binaries called `package-name.test`. You can then run `ginkgo package-name.test` _or_ `./package-name.test` to invoke the binary without going through a compilation step. Since the `ginkgo` CLI is a [necessary component when running specs in parallel](#spec-parallelization) to run precompiled specs in parallel you must: ```bash ginkgo -p ./path/to/suite.test ``` As with the rest of the go tool chain, you can cross-compile and target different platforms using the standard `GOOS` and `GOARCH` environment variables. For example: ```bash GOOS=linux GOARCH=amd64 ginkgo build path/to/package ``` will build a linux binary. Finally, the `build` command accepts a subset of the flags of the `run` command. This is because some flags apply at compile time whereas others apply at run-time only. This can be a bit confusing with the `go test` toolchain but Ginkgo tries to make things clearer by carefully controlling the availability of flags across the two commands. ### Watching for Changes To help enable a fast feedback loop during development, Ginkgo provides a `watch` subcommand that watches suites and their dependencies for changes. When a change is detected `ginkgo watch` will automatically rerun the suite. `ginkgo watch` accepts most of `ginkgo run`'s flags. So, you can do things like: ```bash ginkgo watch -r -p ``` to monitor all packages, recursively, for changes and run them in parallel when changes are detected. For each monitored package, Ginkgo also monitors that package's dependencies. By default `ginkgo watch` monitors a package's immediate dependencies. You can adjust this using the `-depth` flag. Set `-depth` to `0` to disable monitoring dependencies and set `-depth` to something greater than `1` to monitor deeper down the dependency graph. ### Generators As discussed above, Ginkgo provides a pair of generator functions to help you bootstrap a suite and add a spec file to it: ```bash ginkgo bootstrap ``` will generate a file named `PACKAGE_suite_test.go` and ```bash ginkgo generate ``` will generate a file named `SUBJECT_test.go` (or `PACKAGE_test.go` if `` is not provided). Both generators support custom templates using `--template` and the option to provide extra custom data to be rendered into the template, besides the default values, using `--template-data`. The custom data should be a well structured JSON file. When loaded into the template the custom data will be available to access from the global key `.CustomData`. For example, with a JSON file ```json { "suitename": "E2E", "labels": ["fast", "parallel", "component"]} ``` The custom data can be accessed like so: `{{ .CustomData.suitename }}` or `{{ range .CustomData.labels }} {{.}} {{ end }}` Take a look at the [Ginkgo's CLI code](https://github.com/onsi/ginkgo/tree/master/ginkgo/generators) to see what's available in the template. ### Creating an Outline of Specs If you want to see an outline of the Ginkgo specs in an individual file, you can use the `ginkgo outline` command: ```bash ginkgo outline book_test.go ``` This generates an outline in a comma-separated-values (CSV) format. Column headers are on the first line, followed by Ginkgo containers, specs, and other identifiers, in the order they appear in the file: Name,Text,Start,End,Spec,Focused,Pending,Labels Describe,Book,124,973,false,false,false,"" BeforeEach,,217,507,false,false,false,"" Describe,Categorizing book length,513,970,false,false,false,"" Context,With more than 300 pages,567,753,false,false,false,"" It,should be a novel,624,742,true,false,false,"" Context,With fewer than 300 pages,763,963,false,false,false,"" It,should be a short story,821,952,true,false,false,"" The columns are: - Name (string): The name of a container, spec, or other identifier in the core DSL. - Text (string): The description of a container or spec. (If it is not a literal, it is undefined in the outline.) - Start (int): Position of the first character in the container or spec. - End (int): Position of the character immediately after the container or spec. - Spec (bool): True, if the identifier is a spec. - Focused (bool): True, if focused. (Conforms to the rules in [Focused Specs](#focused-specs).) - Pending (bool): True, if pending. (Conforms to the rules in [Pending Specs](#pending-specs).) - Labels (string): If labels are assigned to nodes then will be shown as double quoted comma separated values. (Conforms to the rules in [Spec Labels](#spec-labels).) You can set a different output format with the `-format` flag. Accepted formats are `csv`, `indent`, and `json`. The `ident` format is like `csv`, but uses indentation to show the nesting of containers and specs. Both the `csv` and `json` formats can be read by another program, e.g., an editor plugin that displays a tree view of Ginkgo tests in a file, or presents a menu for the user to quickly navigate to a container or spec. `ginkgo outline` is intended for integration with third-party libraries and applications - however it has an important limitation. Since parses the go syntax tree it cannot identify specs that are dynamically generated. Nor does it capture run-time concerns such as which specs will be skipped by a given set of filters or the order in which specs will run. If you want a quick overview of such things you can use `ginkgo -v --dry-run` instead. If you want finer-grained control over the suite preview, you should use [`PreviewSpecs`](#previewing-specs). ### Other Subcommands To unfocus any programmatically focused specs in the current directory or subdirectories, run: ```bash ginkgo unfocus ``` To get a list of `Label`s used in a suite run ```bash ginkgo labels ``` `labels` (naively) parses your spec files and looks for calls to the `Label` decorator. To get the current version of the `ginkgo` CLI run: ```bash ginkgo version ``` ## Third-Party Integrations ### Using Third-party Libraries Most third-party Go `testing` integrations (e.g. matcher libraries, mocking libraries) take and wrap a `*testing.T` to provide functionality. Unfortunately there is no formal interface for `*testing.T` however Ginkgo provides a function, `GinkgoT()` that returns a struct that implements all the methods that `*testing.T` implements. Most libraries accept the `*testing.T` object via an interface and you can usually simply pass in `GinkgoT()` and expect the library to work. Some libraries require passing in a `testing.TB` - you can use `GinkgoTB()` for these. For example, you can choose to use [testify](https://github.com/stretchr/testify) instead of Gomega like so: ```go package foo_test import ( . "github.com/onsi/ginkgo/v2" "github.com/stretchr/testify/assert" ) var _ = Describe(func("foo") { It("should testify to its correctness", func(){ assert.Equal(GinkgoT(), foo{}.Name(), "foo") }) }) ``` Similarly if you're using [Gomock](https://code.google.com/p/gomock/) you can simply pass `GinkgoT()` to your controller: ```go import ( "code.google.com/p/gomock/gomock" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" ) var _ = Describe("Consumer", func() { var ( mockCtrl *gomock.Controller mockThing *mockthing.MockThing consumer *Consumer ) BeforeEach(func() { mockCtrl = gomock.NewController(GinkgoT()) mockThing = mockthing.NewMockThing(mockCtrl) consumer = NewConsumer(mockThing) }) It("should consume things", func() { mockThing.EXPECT().OmNom() consumer.Consume() }) }) ``` Since `GinkgoT()` implements `Cleanup()` (using `DeferCleanup()` under the hood) Gomock will automatically register a call to `mockCtrl.Finish()` when the controller is created. When using Gomock you may want to run `ginkgo` with the `-trace` flag to print out stack traces for failures which will help you trace down where, in your code, invalid calls occurred. `GinkgoT()` also provides additional methods that are Ginkgo-specific. This allows rich third-party integrations to be built on top of Ginkgo - with GinkgoT() serving as a single connection point. Similarly for third party libraries which accept a `testing.TB` interface, use the `GinkgoTB()` function. This function returns a struct wrapper around `GinkgoT()` which satisfies the `testing.TB`interface. If you need to use any Ginkgo-specific methods you can access the wrapped `GinkgoT()` instance using `GinkgoTBWrapper.GinkgoT`. In general, `GinkgoT()` attempts to mimic the behavior of `testing.T` with the exception of the following: - `Error`/`Errorf`: failures in Ginkgo always immediately stop execution and there is no mechanism to log a failure without aborting the test. As such `Error`/`Errorf` are equivalent to `Fatal`/`Fatalf`. - `Parallel()` is a no-op as Ginkgo's multi-process parallelism model is substantially different from go test's in-process model. ### IDE Support Ginkgo works best from the command-line, and [`ginkgo watch`](#watching-for-changes) makes it easy to rerun tests on the command line whenever changes are detected. There are a set of [completions](https://github.com/onsi/ginkgo-sublime-completions) available for [Sublime Text](https://www.sublimetext.com/) (just use [Package Control](https://sublime.wbond.net/) to install `Ginkgo Completions`) and for [VS Code](https://code.visualstudio.com/) (use the extensions installer and install vscode-ginkgo). There is also a VS Code extension to run specs from VSCode called [Ginkgo Test Explorer](https://github.com/joselitofilho/ginkgoTestExplorer). IDE authors can set the `GINKGO_EDITOR_INTEGRATION` environment variable to any non-empty value to enable coverage to be displayed for focused specs. By default, Ginkgo will fail with a non-zero exit code if specs are focused to ensure they do not pass in CI. #### Working directory Ginkgo calls os.Getwd() to get the current directory for display in several reporters. os.Getwd() calls os.Getenv("PWD"), which can change from run to run if you are using a test suite runner like e.g. Buildkite. Because test caching relies on environment variables being the same from run to run, this facile change can break test caching. Set the `GINKGO_PRESERVE_CACHE` environment variable to `true` in order to skip the `os.Getwd()` call. This may affect the reporter output. ### The ginkgolinter The [ginkgolinter](https://github.com/nunnatsa/ginkgolinter) enforces several patterns of using ginkgo and gomega. It can run as an independent executable or as part of the [golangci-lint](https://golangci-lint.run/) linter. See the ginkgolinter [READMY](https://github.com/nunnatsa/ginkgolinter#readme) for more details. {% endraw %} golang-github-onsi-ginkgo-v2-2.22.0/docs/js/000077500000000000000000000000001472321612100204365ustar00rootroot00000000000000golang-github-onsi-ginkgo-v2-2.22.0/docs/js/docs.js000066400000000000000000000042551472321612100217320ustar00rootroot00000000000000(() => { let sidebar = document.getElementById("sidebar") let headings = document.querySelectorAll("#content h2,h3") let headingsLookup = {} let currentHeadingGroup = null let collapsibleGroup = null for (let heading of headings) { let el = document.createElement("a") el.href = `#${heading.id}` el.id = `${heading.id}-item` el.innerText = heading.innerText if (heading.tagName == "H2") { currentHeadingGroup = heading.id el.classList = "sidebar-heading" sidebar.appendChild(el) collapsibleGroup = document.createElement("div") collapsibleGroup.classList = "sidebar-section" sidebar.appendChild(collapsibleGroup) } else { el.classList = "sidebar-item" collapsibleGroup.appendChild(el) } headingsLookup[heading.id] = currentHeadingGroup } let ticking = false; document.getElementById("content").addEventListener("scroll", (e) => { if (!ticking) { window.requestAnimationFrame(function() { let viewportHeight = window.visualViewport.height; let winner = null; for (let heading of headings) { let rect = heading.getBoundingClientRect(); if (rect.top > viewportHeight) { break } winner = heading.id if (rect.top > 0) { break } } if (winner != null) { document.querySelectorAll("#sidebar .active").forEach(e => e.classList.remove("active")) document.getElementById(`${winner}-item`).classList.add("active") document.getElementById(`${headingsLookup[winner]}-item`).classList.add("active"); } ticking = false; }); ticking = true; } }) document.querySelector("img[alt=Ginkgo]").id = "top" document.querySelectorAll("div.highlight").forEach(el => { if (el.innerText.includes("/* === INVALID === */")) { el.classList.add("invalid") } }) document.getElementById("disclosure").addEventListener("click", (e) => { document.getElementById("container").classList.toggle("reveal-sidebar") }) document.getElementById("mask").addEventListener("click", (e) => { document.getElementById("container").classList.toggle("reveal-sidebar") }) })() golang-github-onsi-ginkgo-v2-2.22.0/dsl/000077500000000000000000000000001472321612100176545ustar00rootroot00000000000000golang-github-onsi-ginkgo-v2-2.22.0/dsl/core/000077500000000000000000000000001472321612100206045ustar00rootroot00000000000000golang-github-onsi-ginkgo-v2-2.22.0/dsl/core/core_dsl.go000066400000000000000000000046701472321612100227340ustar00rootroot00000000000000/* Ginkgo is usually dot-imported via: import . "github.com/onsi/ginkgo/v2" however some parts of the DSL may conflict with existing symbols in the user's code. To mitigate this without losing the brevity of dot-importing Ginkgo the various packages in the dsl directory provide pieces of the Ginkgo DSL that can be dot-imported separately. This "core" package pulls in the core Ginkgo DSL - most test suites will only need to import this package. */ package core import ( "github.com/onsi/ginkgo/v2" ) const GINKGO_VERSION = ginkgo.GINKGO_VERSION type GinkgoWriterInterface = ginkgo.GinkgoWriterInterface type GinkgoTestingT = ginkgo.GinkgoTestingT type GinkgoTInterface = ginkgo.GinkgoTInterface type FullGinkgoTInterface = ginkgo.FullGinkgoTInterface type SpecContext = ginkgo.SpecContext type GinkgoTBWrapper = ginkgo.GinkgoTBWrapper var GinkgoWriter = ginkgo.GinkgoWriter var GinkgoLogr = ginkgo.GinkgoLogr var GinkgoConfiguration = ginkgo.GinkgoConfiguration var GinkgoRandomSeed = ginkgo.GinkgoRandomSeed var GinkgoParallelProcess = ginkgo.GinkgoParallelProcess var GinkgoHelper = ginkgo.GinkgoHelper var GinkgoLabelFilter = ginkgo.GinkgoLabelFilter var PauseOutputInterception = ginkgo.PauseOutputInterception var ResumeOutputInterception = ginkgo.ResumeOutputInterception var RunSpecs = ginkgo.RunSpecs var PreviewSpecs = ginkgo.PreviewSpecs var Skip = ginkgo.Skip var Fail = ginkgo.Fail var AbortSuite = ginkgo.AbortSuite var GinkgoRecover = ginkgo.GinkgoRecover var Describe = ginkgo.Describe var FDescribe = ginkgo.FDescribe var PDescribe = ginkgo.PDescribe var XDescribe = PDescribe var Context, FContext, PContext, XContext = Describe, FDescribe, PDescribe, XDescribe var When, FWhen, PWhen, XWhen = Describe, FDescribe, PDescribe, XDescribe var It = ginkgo.It var FIt = ginkgo.FIt var PIt = ginkgo.PIt var XIt = PIt var Specify, FSpecify, PSpecify, XSpecify = It, FIt, PIt, XIt var By = ginkgo.By var BeforeSuite = ginkgo.BeforeSuite var AfterSuite = ginkgo.AfterSuite var SynchronizedBeforeSuite = ginkgo.SynchronizedBeforeSuite var SynchronizedAfterSuite = ginkgo.SynchronizedAfterSuite var BeforeEach = ginkgo.BeforeEach var JustBeforeEach = ginkgo.JustBeforeEach var AfterEach = ginkgo.AfterEach var JustAfterEach = ginkgo.JustAfterEach var BeforeAll = ginkgo.BeforeAll var AfterAll = ginkgo.AfterAll var DeferCleanup = ginkgo.DeferCleanup var GinkgoT = ginkgo.GinkgoT var GinkgoTB = ginkgo.GinkgoTB var AttachProgressReporter = ginkgo.AttachProgressReporter golang-github-onsi-ginkgo-v2-2.22.0/dsl/decorators/000077500000000000000000000000001472321612100220215ustar00rootroot00000000000000golang-github-onsi-ginkgo-v2-2.22.0/dsl/decorators/decorators_dsl.go000066400000000000000000000022251472321612100253600ustar00rootroot00000000000000/* Ginkgo is usually dot-imported via: import . "github.com/onsi/ginkgo/v2" however some parts of the DSL may conflict with existing symbols in the user's code. To mitigate this without losing the brevity of dot-importing Ginkgo the various packages in the dsl directory provide pieces of the Ginkgo DSL that can be dot-imported separately. This "decorators" package pulls in the various decorators defined in the Ginkgo DSL. */ package decorators import ( "github.com/onsi/ginkgo/v2" ) type Offset = ginkgo.Offset type FlakeAttempts = ginkgo.FlakeAttempts type MustPassRepeatedly = ginkgo.MustPassRepeatedly type Labels = ginkgo.Labels type PollProgressAfter = ginkgo.PollProgressAfter type PollProgressInterval = ginkgo.PollProgressInterval type NodeTimeout = ginkgo.NodeTimeout type SpecTimeout = ginkgo.SpecTimeout type GracePeriod = ginkgo.GracePeriod const Focus = ginkgo.Focus const Pending = ginkgo.Pending const Serial = ginkgo.Serial const Ordered = ginkgo.Ordered const ContinueOnFailure = ginkgo.ContinueOnFailure const OncePerOrdered = ginkgo.OncePerOrdered const SuppressProgressReporting = ginkgo.SuppressProgressReporting var Label = ginkgo.Label golang-github-onsi-ginkgo-v2-2.22.0/dsl/dsl_suite_test.go000066400000000000000000000031231472321612100232340ustar00rootroot00000000000000package dsl_test import ( "go/ast" "go/parser" "go/token" "testing" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" ) func TestDSL(t *testing.T) { RegisterFailHandler(Fail) RunSpecs(t, "DSL Suite") } func ExtractSymbols(f *ast.File) []string { symbols := []string{} for _, decl := range f.Decls { names := []string{} switch v := decl.(type) { case *ast.FuncDecl: if v.Recv == nil { names = append(names, v.Name.Name) } case *ast.GenDecl: switch v.Tok { case token.TYPE: s := v.Specs[0].(*ast.TypeSpec) names = append(names, s.Name.Name) case token.CONST, token.VAR: s := v.Specs[0].(*ast.ValueSpec) for _, n := range s.Names { names = append(names, n.Name) } } } for _, name := range names { if ast.IsExported(name) { symbols = append(symbols, name) } } } return symbols } var _ = It("ensures complete coverage of the core dsl", func() { fset := token.NewFileSet() pkgs, err := parser.ParseDir(fset, "../", nil, 0) Ω(err).ShouldNot(HaveOccurred()) expectedSymbols := []string{} for fn, file := range pkgs["ginkgo"].Files { if fn == "../deprecated_dsl.go" { continue } expectedSymbols = append(expectedSymbols, ExtractSymbols(file)...) } actualSymbols := []string{} for _, pkg := range []string{"core", "reporting", "decorators", "table"} { pkgs, err := parser.ParseDir(fset, "./"+pkg, nil, 0) Ω(err).ShouldNot(HaveOccurred()) for _, file := range pkgs[pkg].Files { actualSymbols = append(actualSymbols, ExtractSymbols(file)...) } } Ω(actualSymbols).Should(ConsistOf(expectedSymbols)) }) golang-github-onsi-ginkgo-v2-2.22.0/dsl/reporting/000077500000000000000000000000001472321612100216655ustar00rootroot00000000000000golang-github-onsi-ginkgo-v2-2.22.0/dsl/reporting/reporting_dsl.go000066400000000000000000000021331472321612100250660ustar00rootroot00000000000000/* Ginkgo is usually dot-imported via: import . "github.com/onsi/ginkgo/v2" however some parts of the DSL may conflict with existing symbols in the user's code. To mitigate this without losing the brevity of dot-importing Ginkgo the various packages in the dsl directory provide pieces of the Ginkgo DSL that can be dot-imported separately. This "reporting" package pulls in the reporting-related pieces of the Ginkgo DSL. */ package reporting import ( "github.com/onsi/ginkgo/v2" ) type Report = ginkgo.Report type SpecReport = ginkgo.SpecReport type ReportEntryVisibility = ginkgo.ReportEntryVisibility const ReportEntryVisibilityAlways, ReportEntryVisibilityFailureOrVerbose, ReportEntryVisibilityNever = ginkgo.ReportEntryVisibilityAlways, ginkgo.ReportEntryVisibilityFailureOrVerbose, ginkgo.ReportEntryVisibilityNever var CurrentSpecReport = ginkgo.CurrentSpecReport var AddReportEntry = ginkgo.AddReportEntry var ReportBeforeEach = ginkgo.ReportBeforeEach var ReportAfterEach = ginkgo.ReportAfterEach var ReportBeforeSuite = ginkgo.ReportBeforeSuite var ReportAfterSuite = ginkgo.ReportAfterSuite golang-github-onsi-ginkgo-v2-2.22.0/dsl/table/000077500000000000000000000000001472321612100207435ustar00rootroot00000000000000golang-github-onsi-ginkgo-v2-2.22.0/dsl/table/table_dsl.go000066400000000000000000000020401472321612100232170ustar00rootroot00000000000000/* Ginkgo is usually dot-imported via: import . "github.com/onsi/ginkgo/v2" however some parts of the DSL may conflict with existing symbols in the user's code. To mitigate this without losing the brevity of dot-importing Ginkgo the various packages in the dsl directory provide pieces of the Ginkgo DSL that can be dot-imported separately. This "table" package pulls in the Ginkgo's table-testing DSL */ package table import ( "github.com/onsi/ginkgo/v2" ) type EntryDescription = ginkgo.EntryDescription var DescribeTable = ginkgo.DescribeTable var FDescribeTable = ginkgo.FDescribeTable var PDescribeTable = ginkgo.PDescribeTable var XDescribeTable = ginkgo.XDescribeTable var DescribeTableSubtree = ginkgo.DescribeTableSubtree var FDescribeTableSubtree = ginkgo.FDescribeTableSubtree var PDescribeTableSubtree = ginkgo.PDescribeTableSubtree var XDescribeTableSubtree = ginkgo.XDescribeTableSubtree type TableEntry = ginkgo.TableEntry var Entry = ginkgo.Entry var FEntry = ginkgo.FEntry var PEntry = ginkgo.PEntry var XEntry = ginkgo.XEntry golang-github-onsi-ginkgo-v2-2.22.0/extensions/000077500000000000000000000000001472321612100212715ustar00rootroot00000000000000golang-github-onsi-ginkgo-v2-2.22.0/extensions/globals/000077500000000000000000000000001472321612100227145ustar00rootroot00000000000000golang-github-onsi-ginkgo-v2-2.22.0/extensions/globals/globals.go000066400000000000000000000025741472321612100246760ustar00rootroot00000000000000// Package `globals` provides an interface to alter the global state of ginkgo suite. // // ginkgo currently registers a few singleton global vars that hold all the // test blocks and failure management. These vars are global per package, which means // that only one Suite definition can coexist in one package. // // However, there can be some use cases where applications using ginkgo may want to // have a bit more control about this. For instance, a package may be using ginkgo // to dynamically generate different tests and groups depending on some configuration. // In this particular case, if the application wants to test how these different groups // are generated, they will need access to change these global variables, so they // can re-generate this global state, and ensure that different configuration generate // indeed different tests. // // Note that this package is not intended to be used as part of normal ginkgo setups, and // usually, you will never need to worry about the global state of ginkgo package globals import "github.com/onsi/ginkgo/v2/internal/global" // Reset calls `global.InitializeGlobals()` which will basically create a new instance // of Suite, and therefore, will effectively reset the global variables to the init state. // This will effectively remove all groups, tests and blocks that were added to the Suite. func Reset() { global.InitializeGlobals() } golang-github-onsi-ginkgo-v2-2.22.0/extensions/globals/globals_test.go000066400000000000000000000007521472321612100257310ustar00rootroot00000000000000package globals_test import ( "testing" "github.com/onsi/ginkgo/v2/extensions/globals" "github.com/onsi/ginkgo/v2/internal/global" ) func TestGlobals(t *testing.T) { global.InitializeGlobals() oldSuite := global.Suite if oldSuite == nil { t.Error("global.Suite was nil") } globals.Reset() newSuite := global.Suite if newSuite == nil { t.Error("new global.Suite was nil") } if oldSuite == newSuite { t.Error("got the same suite but expected it to be different!") } } golang-github-onsi-ginkgo-v2-2.22.0/extensions/table/000077500000000000000000000000001472321612100223605ustar00rootroot00000000000000golang-github-onsi-ginkgo-v2-2.22.0/extensions/table/deprecated.go000066400000000000000000000001101472321612100247770ustar00rootroot00000000000000package table type TableSupportHasBeenMovedToTheCoreGinkgoDSL struct{} golang-github-onsi-ginkgo-v2-2.22.0/formatter/000077500000000000000000000000001472321612100210755ustar00rootroot00000000000000golang-github-onsi-ginkgo-v2-2.22.0/formatter/colorable_others.go000066400000000000000000000026051472321612100247550ustar00rootroot00000000000000// +build !windows /* These packages are used for colorize on Windows and contributed by mattn.jp@gmail.com * go-colorable: * go-isatty: The MIT License (MIT) Copyright (c) 2016 Yasuhiro Matsumoto Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ package formatter import ( "io" "os" ) func newColorable(file *os.File) io.Writer { return file } golang-github-onsi-ginkgo-v2-2.22.0/formatter/colorable_windows.go000066400000000000000000000452521472321612100251500ustar00rootroot00000000000000/* These packages are used for colorize on Windows and contributed by mattn.jp@gmail.com * go-colorable: * go-isatty: The MIT License (MIT) Copyright (c) 2016 Yasuhiro Matsumoto Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ package formatter import ( "bytes" "fmt" "io" "math" "os" "strconv" "strings" "syscall" "unsafe" ) var ( kernel32 = syscall.NewLazyDLL("kernel32.dll") procGetConsoleScreenBufferInfo = kernel32.NewProc("GetConsoleScreenBufferInfo") procSetConsoleTextAttribute = kernel32.NewProc("SetConsoleTextAttribute") procSetConsoleCursorPosition = kernel32.NewProc("SetConsoleCursorPosition") procFillConsoleOutputCharacter = kernel32.NewProc("FillConsoleOutputCharacterW") procFillConsoleOutputAttribute = kernel32.NewProc("FillConsoleOutputAttribute") procGetConsoleMode = kernel32.NewProc("GetConsoleMode") ) func isTerminal(fd uintptr) bool { var st uint32 r, _, e := syscall.Syscall(procGetConsoleMode.Addr(), 2, fd, uintptr(unsafe.Pointer(&st)), 0) return r != 0 && e == 0 } const ( foregroundBlue = 0x1 foregroundGreen = 0x2 foregroundRed = 0x4 foregroundIntensity = 0x8 foregroundMask = (foregroundRed | foregroundBlue | foregroundGreen | foregroundIntensity) backgroundBlue = 0x10 backgroundGreen = 0x20 backgroundRed = 0x40 backgroundIntensity = 0x80 backgroundMask = (backgroundRed | backgroundBlue | backgroundGreen | backgroundIntensity) ) type wchar uint16 type short int16 type dword uint32 type word uint16 type coord struct { x short y short } type smallRect struct { left short top short right short bottom short } type consoleScreenBufferInfo struct { size coord cursorPosition coord attributes word window smallRect maximumWindowSize coord } type writer struct { out io.Writer handle syscall.Handle lastbuf bytes.Buffer oldattr word } func newColorable(file *os.File) io.Writer { if file == nil { panic("nil passed instead of *os.File to NewColorable()") } if isTerminal(file.Fd()) { var csbi consoleScreenBufferInfo handle := syscall.Handle(file.Fd()) procGetConsoleScreenBufferInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&csbi))) return &writer{out: file, handle: handle, oldattr: csbi.attributes} } else { return file } } var color256 = map[int]int{ 0: 0x000000, 1: 0x800000, 2: 0x008000, 3: 0x808000, 4: 0x000080, 5: 0x800080, 6: 0x008080, 7: 0xc0c0c0, 8: 0x808080, 9: 0xff0000, 10: 0x00ff00, 11: 0xffff00, 12: 0x0000ff, 13: 0xff00ff, 14: 0x00ffff, 15: 0xffffff, 16: 0x000000, 17: 0x00005f, 18: 0x000087, 19: 0x0000af, 20: 0x0000d7, 21: 0x0000ff, 22: 0x005f00, 23: 0x005f5f, 24: 0x005f87, 25: 0x005faf, 26: 0x005fd7, 27: 0x005fff, 28: 0x008700, 29: 0x00875f, 30: 0x008787, 31: 0x0087af, 32: 0x0087d7, 33: 0x0087ff, 34: 0x00af00, 35: 0x00af5f, 36: 0x00af87, 37: 0x00afaf, 38: 0x00afd7, 39: 0x00afff, 40: 0x00d700, 41: 0x00d75f, 42: 0x00d787, 43: 0x00d7af, 44: 0x00d7d7, 45: 0x00d7ff, 46: 0x00ff00, 47: 0x00ff5f, 48: 0x00ff87, 49: 0x00ffaf, 50: 0x00ffd7, 51: 0x00ffff, 52: 0x5f0000, 53: 0x5f005f, 54: 0x5f0087, 55: 0x5f00af, 56: 0x5f00d7, 57: 0x5f00ff, 58: 0x5f5f00, 59: 0x5f5f5f, 60: 0x5f5f87, 61: 0x5f5faf, 62: 0x5f5fd7, 63: 0x5f5fff, 64: 0x5f8700, 65: 0x5f875f, 66: 0x5f8787, 67: 0x5f87af, 68: 0x5f87d7, 69: 0x5f87ff, 70: 0x5faf00, 71: 0x5faf5f, 72: 0x5faf87, 73: 0x5fafaf, 74: 0x5fafd7, 75: 0x5fafff, 76: 0x5fd700, 77: 0x5fd75f, 78: 0x5fd787, 79: 0x5fd7af, 80: 0x5fd7d7, 81: 0x5fd7ff, 82: 0x5fff00, 83: 0x5fff5f, 84: 0x5fff87, 85: 0x5fffaf, 86: 0x5fffd7, 87: 0x5fffff, 88: 0x870000, 89: 0x87005f, 90: 0x870087, 91: 0x8700af, 92: 0x8700d7, 93: 0x8700ff, 94: 0x875f00, 95: 0x875f5f, 96: 0x875f87, 97: 0x875faf, 98: 0x875fd7, 99: 0x875fff, 100: 0x878700, 101: 0x87875f, 102: 0x878787, 103: 0x8787af, 104: 0x8787d7, 105: 0x8787ff, 106: 0x87af00, 107: 0x87af5f, 108: 0x87af87, 109: 0x87afaf, 110: 0x87afd7, 111: 0x87afff, 112: 0x87d700, 113: 0x87d75f, 114: 0x87d787, 115: 0x87d7af, 116: 0x87d7d7, 117: 0x87d7ff, 118: 0x87ff00, 119: 0x87ff5f, 120: 0x87ff87, 121: 0x87ffaf, 122: 0x87ffd7, 123: 0x87ffff, 124: 0xaf0000, 125: 0xaf005f, 126: 0xaf0087, 127: 0xaf00af, 128: 0xaf00d7, 129: 0xaf00ff, 130: 0xaf5f00, 131: 0xaf5f5f, 132: 0xaf5f87, 133: 0xaf5faf, 134: 0xaf5fd7, 135: 0xaf5fff, 136: 0xaf8700, 137: 0xaf875f, 138: 0xaf8787, 139: 0xaf87af, 140: 0xaf87d7, 141: 0xaf87ff, 142: 0xafaf00, 143: 0xafaf5f, 144: 0xafaf87, 145: 0xafafaf, 146: 0xafafd7, 147: 0xafafff, 148: 0xafd700, 149: 0xafd75f, 150: 0xafd787, 151: 0xafd7af, 152: 0xafd7d7, 153: 0xafd7ff, 154: 0xafff00, 155: 0xafff5f, 156: 0xafff87, 157: 0xafffaf, 158: 0xafffd7, 159: 0xafffff, 160: 0xd70000, 161: 0xd7005f, 162: 0xd70087, 163: 0xd700af, 164: 0xd700d7, 165: 0xd700ff, 166: 0xd75f00, 167: 0xd75f5f, 168: 0xd75f87, 169: 0xd75faf, 170: 0xd75fd7, 171: 0xd75fff, 172: 0xd78700, 173: 0xd7875f, 174: 0xd78787, 175: 0xd787af, 176: 0xd787d7, 177: 0xd787ff, 178: 0xd7af00, 179: 0xd7af5f, 180: 0xd7af87, 181: 0xd7afaf, 182: 0xd7afd7, 183: 0xd7afff, 184: 0xd7d700, 185: 0xd7d75f, 186: 0xd7d787, 187: 0xd7d7af, 188: 0xd7d7d7, 189: 0xd7d7ff, 190: 0xd7ff00, 191: 0xd7ff5f, 192: 0xd7ff87, 193: 0xd7ffaf, 194: 0xd7ffd7, 195: 0xd7ffff, 196: 0xff0000, 197: 0xff005f, 198: 0xff0087, 199: 0xff00af, 200: 0xff00d7, 201: 0xff00ff, 202: 0xff5f00, 203: 0xff5f5f, 204: 0xff5f87, 205: 0xff5faf, 206: 0xff5fd7, 207: 0xff5fff, 208: 0xff8700, 209: 0xff875f, 210: 0xff8787, 211: 0xff87af, 212: 0xff87d7, 213: 0xff87ff, 214: 0xffaf00, 215: 0xffaf5f, 216: 0xffaf87, 217: 0xffafaf, 218: 0xffafd7, 219: 0xffafff, 220: 0xffd700, 221: 0xffd75f, 222: 0xffd787, 223: 0xffd7af, 224: 0xffd7d7, 225: 0xffd7ff, 226: 0xffff00, 227: 0xffff5f, 228: 0xffff87, 229: 0xffffaf, 230: 0xffffd7, 231: 0xffffff, 232: 0x080808, 233: 0x121212, 234: 0x1c1c1c, 235: 0x262626, 236: 0x303030, 237: 0x3a3a3a, 238: 0x444444, 239: 0x4e4e4e, 240: 0x585858, 241: 0x626262, 242: 0x6c6c6c, 243: 0x767676, 244: 0x808080, 245: 0x8a8a8a, 246: 0x949494, 247: 0x9e9e9e, 248: 0xa8a8a8, 249: 0xb2b2b2, 250: 0xbcbcbc, 251: 0xc6c6c6, 252: 0xd0d0d0, 253: 0xdadada, 254: 0xe4e4e4, 255: 0xeeeeee, } func (w *writer) Write(data []byte) (n int, err error) { var csbi consoleScreenBufferInfo procGetConsoleScreenBufferInfo.Call(uintptr(w.handle), uintptr(unsafe.Pointer(&csbi))) er := bytes.NewBuffer(data) loop: for { r1, _, err := procGetConsoleScreenBufferInfo.Call(uintptr(w.handle), uintptr(unsafe.Pointer(&csbi))) if r1 == 0 { break loop } c1, _, err := er.ReadRune() if err != nil { break loop } if c1 != 0x1b { fmt.Fprint(w.out, string(c1)) continue } c2, _, err := er.ReadRune() if err != nil { w.lastbuf.WriteRune(c1) break loop } if c2 != 0x5b { w.lastbuf.WriteRune(c1) w.lastbuf.WriteRune(c2) continue } var buf bytes.Buffer var m rune for { c, _, err := er.ReadRune() if err != nil { w.lastbuf.WriteRune(c1) w.lastbuf.WriteRune(c2) w.lastbuf.Write(buf.Bytes()) break loop } if ('a' <= c && c <= 'z') || ('A' <= c && c <= 'Z') || c == '@' { m = c break } buf.Write([]byte(string(c))) } var csbi consoleScreenBufferInfo switch m { case 'A': n, err = strconv.Atoi(buf.String()) if err != nil { continue } procGetConsoleScreenBufferInfo.Call(uintptr(w.handle), uintptr(unsafe.Pointer(&csbi))) csbi.cursorPosition.y -= short(n) procSetConsoleCursorPosition.Call(uintptr(w.handle), *(*uintptr)(unsafe.Pointer(&csbi.cursorPosition))) case 'B': n, err = strconv.Atoi(buf.String()) if err != nil { continue } procGetConsoleScreenBufferInfo.Call(uintptr(w.handle), uintptr(unsafe.Pointer(&csbi))) csbi.cursorPosition.y += short(n) procSetConsoleCursorPosition.Call(uintptr(w.handle), *(*uintptr)(unsafe.Pointer(&csbi.cursorPosition))) case 'C': n, err = strconv.Atoi(buf.String()) if err != nil { continue } procGetConsoleScreenBufferInfo.Call(uintptr(w.handle), uintptr(unsafe.Pointer(&csbi))) csbi.cursorPosition.x -= short(n) procSetConsoleCursorPosition.Call(uintptr(w.handle), *(*uintptr)(unsafe.Pointer(&csbi.cursorPosition))) case 'D': n, err = strconv.Atoi(buf.String()) if err != nil { continue } if n, err = strconv.Atoi(buf.String()); err == nil { var csbi consoleScreenBufferInfo procGetConsoleScreenBufferInfo.Call(uintptr(w.handle), uintptr(unsafe.Pointer(&csbi))) csbi.cursorPosition.x += short(n) procSetConsoleCursorPosition.Call(uintptr(w.handle), *(*uintptr)(unsafe.Pointer(&csbi.cursorPosition))) } case 'E': n, err = strconv.Atoi(buf.String()) if err != nil { continue } procGetConsoleScreenBufferInfo.Call(uintptr(w.handle), uintptr(unsafe.Pointer(&csbi))) csbi.cursorPosition.x = 0 csbi.cursorPosition.y += short(n) procSetConsoleCursorPosition.Call(uintptr(w.handle), *(*uintptr)(unsafe.Pointer(&csbi.cursorPosition))) case 'F': n, err = strconv.Atoi(buf.String()) if err != nil { continue } procGetConsoleScreenBufferInfo.Call(uintptr(w.handle), uintptr(unsafe.Pointer(&csbi))) csbi.cursorPosition.x = 0 csbi.cursorPosition.y -= short(n) procSetConsoleCursorPosition.Call(uintptr(w.handle), *(*uintptr)(unsafe.Pointer(&csbi.cursorPosition))) case 'G': n, err = strconv.Atoi(buf.String()) if err != nil { continue } procGetConsoleScreenBufferInfo.Call(uintptr(w.handle), uintptr(unsafe.Pointer(&csbi))) csbi.cursorPosition.x = short(n) procSetConsoleCursorPosition.Call(uintptr(w.handle), *(*uintptr)(unsafe.Pointer(&csbi.cursorPosition))) case 'H': token := strings.Split(buf.String(), ";") if len(token) != 2 { continue } n1, err := strconv.Atoi(token[0]) if err != nil { continue } n2, err := strconv.Atoi(token[1]) if err != nil { continue } csbi.cursorPosition.x = short(n2) csbi.cursorPosition.x = short(n1) procSetConsoleCursorPosition.Call(uintptr(w.handle), *(*uintptr)(unsafe.Pointer(&csbi.cursorPosition))) case 'J': n, err := strconv.Atoi(buf.String()) if err != nil { continue } var cursor coord switch n { case 0: cursor = coord{x: csbi.cursorPosition.x, y: csbi.cursorPosition.y} case 1: cursor = coord{x: csbi.window.left, y: csbi.window.top} case 2: cursor = coord{x: csbi.window.left, y: csbi.window.top} } var count, written dword count = dword(csbi.size.x - csbi.cursorPosition.x + (csbi.size.y-csbi.cursorPosition.y)*csbi.size.x) procFillConsoleOutputCharacter.Call(uintptr(w.handle), uintptr(' '), uintptr(count), *(*uintptr)(unsafe.Pointer(&cursor)), uintptr(unsafe.Pointer(&written))) procFillConsoleOutputAttribute.Call(uintptr(w.handle), uintptr(csbi.attributes), uintptr(count), *(*uintptr)(unsafe.Pointer(&cursor)), uintptr(unsafe.Pointer(&written))) case 'K': n, err := strconv.Atoi(buf.String()) if err != nil { continue } var cursor coord switch n { case 0: cursor = coord{x: csbi.cursorPosition.x, y: csbi.cursorPosition.y} case 1: cursor = coord{x: csbi.window.left, y: csbi.window.top + csbi.cursorPosition.y} case 2: cursor = coord{x: csbi.window.left, y: csbi.window.top + csbi.cursorPosition.y} } var count, written dword count = dword(csbi.size.x - csbi.cursorPosition.x) procFillConsoleOutputCharacter.Call(uintptr(w.handle), uintptr(' '), uintptr(count), *(*uintptr)(unsafe.Pointer(&cursor)), uintptr(unsafe.Pointer(&written))) procFillConsoleOutputAttribute.Call(uintptr(w.handle), uintptr(csbi.attributes), uintptr(count), *(*uintptr)(unsafe.Pointer(&cursor)), uintptr(unsafe.Pointer(&written))) case 'm': attr := csbi.attributes cs := buf.String() if cs == "" { procSetConsoleTextAttribute.Call(uintptr(w.handle), uintptr(w.oldattr)) continue } token := strings.Split(cs, ";") for i := 0; i < len(token); i += 1 { ns := token[i] if n, err = strconv.Atoi(ns); err == nil { switch { case n == 0 || n == 100: attr = w.oldattr case 1 <= n && n <= 5: attr |= foregroundIntensity case n == 7: attr = ((attr & foregroundMask) << 4) | ((attr & backgroundMask) >> 4) case 22 == n || n == 25 || n == 25: attr |= foregroundIntensity case n == 27: attr = ((attr & foregroundMask) << 4) | ((attr & backgroundMask) >> 4) case 30 <= n && n <= 37: attr = (attr & backgroundMask) if (n-30)&1 != 0 { attr |= foregroundRed } if (n-30)&2 != 0 { attr |= foregroundGreen } if (n-30)&4 != 0 { attr |= foregroundBlue } case n == 38: // set foreground color. if i < len(token)-2 && (token[i+1] == "5" || token[i+1] == "05") { if n256, err := strconv.Atoi(token[i+2]); err == nil { if n256foreAttr == nil { n256setup() } attr &= backgroundMask attr |= n256foreAttr[n256] i += 2 } } else { attr = attr & (w.oldattr & backgroundMask) } case n == 39: // reset foreground color. attr &= backgroundMask attr |= w.oldattr & foregroundMask case 40 <= n && n <= 47: attr = (attr & foregroundMask) if (n-40)&1 != 0 { attr |= backgroundRed } if (n-40)&2 != 0 { attr |= backgroundGreen } if (n-40)&4 != 0 { attr |= backgroundBlue } case n == 48: // set background color. if i < len(token)-2 && token[i+1] == "5" { if n256, err := strconv.Atoi(token[i+2]); err == nil { if n256backAttr == nil { n256setup() } attr &= foregroundMask attr |= n256backAttr[n256] i += 2 } } else { attr = attr & (w.oldattr & foregroundMask) } case n == 49: // reset foreground color. attr &= foregroundMask attr |= w.oldattr & backgroundMask case 90 <= n && n <= 97: attr = (attr & backgroundMask) attr |= foregroundIntensity if (n-90)&1 != 0 { attr |= foregroundRed } if (n-90)&2 != 0 { attr |= foregroundGreen } if (n-90)&4 != 0 { attr |= foregroundBlue } case 100 <= n && n <= 107: attr = (attr & foregroundMask) attr |= backgroundIntensity if (n-100)&1 != 0 { attr |= backgroundRed } if (n-100)&2 != 0 { attr |= backgroundGreen } if (n-100)&4 != 0 { attr |= backgroundBlue } } procSetConsoleTextAttribute.Call(uintptr(w.handle), uintptr(attr)) } } } } return len(data) - w.lastbuf.Len(), nil } type consoleColor struct { rgb int red bool green bool blue bool intensity bool } func (c consoleColor) foregroundAttr() (attr word) { if c.red { attr |= foregroundRed } if c.green { attr |= foregroundGreen } if c.blue { attr |= foregroundBlue } if c.intensity { attr |= foregroundIntensity } return } func (c consoleColor) backgroundAttr() (attr word) { if c.red { attr |= backgroundRed } if c.green { attr |= backgroundGreen } if c.blue { attr |= backgroundBlue } if c.intensity { attr |= backgroundIntensity } return } var color16 = []consoleColor{ consoleColor{0x000000, false, false, false, false}, consoleColor{0x000080, false, false, true, false}, consoleColor{0x008000, false, true, false, false}, consoleColor{0x008080, false, true, true, false}, consoleColor{0x800000, true, false, false, false}, consoleColor{0x800080, true, false, true, false}, consoleColor{0x808000, true, true, false, false}, consoleColor{0xc0c0c0, true, true, true, false}, consoleColor{0x808080, false, false, false, true}, consoleColor{0x0000ff, false, false, true, true}, consoleColor{0x00ff00, false, true, false, true}, consoleColor{0x00ffff, false, true, true, true}, consoleColor{0xff0000, true, false, false, true}, consoleColor{0xff00ff, true, false, true, true}, consoleColor{0xffff00, true, true, false, true}, consoleColor{0xffffff, true, true, true, true}, } type hsv struct { h, s, v float32 } func (a hsv) dist(b hsv) float32 { dh := a.h - b.h switch { case dh > 0.5: dh = 1 - dh case dh < -0.5: dh = -1 - dh } ds := a.s - b.s dv := a.v - b.v return float32(math.Sqrt(float64(dh*dh + ds*ds + dv*dv))) } func toHSV(rgb int) hsv { r, g, b := float32((rgb&0xFF0000)>>16)/256.0, float32((rgb&0x00FF00)>>8)/256.0, float32(rgb&0x0000FF)/256.0 min, max := minmax3f(r, g, b) h := max - min if h > 0 { if max == r { h = (g - b) / h if h < 0 { h += 6 } } else if max == g { h = 2 + (b-r)/h } else { h = 4 + (r-g)/h } } h /= 6.0 s := max - min if max != 0 { s /= max } v := max return hsv{h: h, s: s, v: v} } type hsvTable []hsv func toHSVTable(rgbTable []consoleColor) hsvTable { t := make(hsvTable, len(rgbTable)) for i, c := range rgbTable { t[i] = toHSV(c.rgb) } return t } func (t hsvTable) find(rgb int) consoleColor { hsv := toHSV(rgb) n := 7 l := float32(5.0) for i, p := range t { d := hsv.dist(p) if d < l { l, n = d, i } } return color16[n] } func minmax3f(a, b, c float32) (min, max float32) { if a < b { if b < c { return a, c } else if a < c { return a, b } else { return c, b } } else { if a < c { return b, c } else if b < c { return b, a } else { return c, a } } } var n256foreAttr []word var n256backAttr []word func n256setup() { n256foreAttr = make([]word, 256) n256backAttr = make([]word, 256) t := toHSVTable(color16) for i, rgb := range color256 { c := t.find(rgb) n256foreAttr[i] = c.foregroundAttr() n256backAttr[i] = c.backgroundAttr() } } golang-github-onsi-ginkgo-v2-2.22.0/formatter/formatter.go000066400000000000000000000126711472321612100234360ustar00rootroot00000000000000package formatter import ( "fmt" "os" "regexp" "strconv" "strings" ) // ColorableStdOut and ColorableStdErr enable color output support on Windows var ColorableStdOut = newColorable(os.Stdout) var ColorableStdErr = newColorable(os.Stderr) const COLS = 80 type ColorMode uint8 const ( ColorModeNone ColorMode = iota ColorModeTerminal ColorModePassthrough ) var SingletonFormatter = New(ColorModeTerminal) func F(format string, args ...interface{}) string { return SingletonFormatter.F(format, args...) } func Fi(indentation uint, format string, args ...interface{}) string { return SingletonFormatter.Fi(indentation, format, args...) } func Fiw(indentation uint, maxWidth uint, format string, args ...interface{}) string { return SingletonFormatter.Fiw(indentation, maxWidth, format, args...) } type Formatter struct { ColorMode ColorMode colors map[string]string styleRe *regexp.Regexp preserveColorStylingTags bool } func NewWithNoColorBool(noColor bool) Formatter { if noColor { return New(ColorModeNone) } return New(ColorModeTerminal) } func New(colorMode ColorMode) Formatter { colorAliases := map[string]int{ "black": 0, "red": 1, "green": 2, "yellow": 3, "blue": 4, "magenta": 5, "cyan": 6, "white": 7, } for colorAlias, n := range colorAliases { colorAliases[fmt.Sprintf("bright-%s", colorAlias)] = n + 8 } getColor := func(color, defaultEscapeCode string) string { color = strings.ToUpper(strings.ReplaceAll(color, "-", "_")) envVar := fmt.Sprintf("GINKGO_CLI_COLOR_%s", color) envVarColor := os.Getenv(envVar) if envVarColor == "" { return defaultEscapeCode } if colorCode, ok := colorAliases[envVarColor]; ok { return fmt.Sprintf("\x1b[38;5;%dm", colorCode) } colorCode, err := strconv.Atoi(envVarColor) if err != nil || colorCode < 0 || colorCode > 255 { return defaultEscapeCode } return fmt.Sprintf("\x1b[38;5;%dm", colorCode) } if _, noColor := os.LookupEnv("GINKGO_NO_COLOR"); noColor { colorMode = ColorModeNone } f := Formatter{ ColorMode: colorMode, colors: map[string]string{ "/": "\x1b[0m", "bold": "\x1b[1m", "underline": "\x1b[4m", "red": getColor("red", "\x1b[38;5;9m"), "orange": getColor("orange", "\x1b[38;5;214m"), "coral": getColor("coral", "\x1b[38;5;204m"), "magenta": getColor("magenta", "\x1b[38;5;13m"), "green": getColor("green", "\x1b[38;5;10m"), "dark-green": getColor("dark-green", "\x1b[38;5;28m"), "yellow": getColor("yellow", "\x1b[38;5;11m"), "light-yellow": getColor("light-yellow", "\x1b[38;5;228m"), "cyan": getColor("cyan", "\x1b[38;5;14m"), "gray": getColor("gray", "\x1b[38;5;243m"), "light-gray": getColor("light-gray", "\x1b[38;5;246m"), "blue": getColor("blue", "\x1b[38;5;12m"), }, } colors := []string{} for color := range f.colors { colors = append(colors, color) } f.styleRe = regexp.MustCompile("{{(" + strings.Join(colors, "|") + ")}}") return f } func (f Formatter) F(format string, args ...interface{}) string { return f.Fi(0, format, args...) } func (f Formatter) Fi(indentation uint, format string, args ...interface{}) string { return f.Fiw(indentation, 0, format, args...) } func (f Formatter) Fiw(indentation uint, maxWidth uint, format string, args ...interface{}) string { out := f.style(format) if len(args) > 0 { out = fmt.Sprintf(out, args...) } if indentation == 0 && maxWidth == 0 { return out } lines := strings.Split(out, "\n") if maxWidth != 0 { outLines := []string{} maxWidth = maxWidth - indentation*2 for _, line := range lines { if f.length(line) <= maxWidth { outLines = append(outLines, line) continue } words := strings.Split(line, " ") outWords := []string{words[0]} length := uint(f.length(words[0])) for _, word := range words[1:] { wordLength := f.length(word) if length+wordLength+1 <= maxWidth { length += wordLength + 1 outWords = append(outWords, word) continue } outLines = append(outLines, strings.Join(outWords, " ")) outWords = []string{word} length = wordLength } if len(outWords) > 0 { outLines = append(outLines, strings.Join(outWords, " ")) } } lines = outLines } if indentation == 0 { return strings.Join(lines, "\n") } padding := strings.Repeat(" ", int(indentation)) for i := range lines { if lines[i] != "" { lines[i] = padding + lines[i] } } return strings.Join(lines, "\n") } func (f Formatter) length(styled string) uint { n := uint(0) inStyle := false for _, b := range styled { if inStyle { if b == 'm' { inStyle = false } continue } if b == '\x1b' { inStyle = true continue } n += 1 } return n } func (f Formatter) CycleJoin(elements []string, joiner string, cycle []string) string { if len(elements) == 0 { return "" } n := len(cycle) out := "" for i, text := range elements { out += cycle[i%n] + text if i < len(elements)-1 { out += joiner } } out += "{{/}}" return f.style(out) } func (f Formatter) style(s string) string { switch f.ColorMode { case ColorModeNone: return f.styleRe.ReplaceAllString(s, "") case ColorModePassthrough: return s case ColorModeTerminal: return f.styleRe.ReplaceAllStringFunc(s, func(match string) string { if out, ok := f.colors[strings.Trim(match, "{}")]; ok { return out } return match }) } return "" } golang-github-onsi-ginkgo-v2-2.22.0/formatter/formatter_suite_test.go000066400000000000000000000003131472321612100256740ustar00rootroot00000000000000package formatter_test import ( "testing" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" ) func TestFormatter(t *testing.T) { RegisterFailHandler(Fail) RunSpecs(t, "Formatter Suite") } golang-github-onsi-ginkgo-v2-2.22.0/formatter/formatter_test.go000066400000000000000000000130751472321612100244740ustar00rootroot00000000000000package formatter_test import ( "os" "strings" . "github.com/onsi/ginkgo/v2" "github.com/onsi/ginkgo/v2/formatter" . "github.com/onsi/gomega" ) var _ = Describe("Formatter", func() { var colorMode formatter.ColorMode var f formatter.Formatter BeforeEach(func() { colorMode = formatter.ColorModeTerminal os.Unsetenv("GINKGO_CLI_COLOR_RED") os.Unsetenv("GINKGO_CLI_COLOR_ORANGE") os.Unsetenv("GINKGO_CLI_COLOR_CORAL") os.Unsetenv("GINKGO_CLI_COLOR_MAGENTA") os.Unsetenv("GINKGO_CLI_COLOR_GREEN") os.Unsetenv("GINKGO_CLI_COLOR_DARK_GREEN") os.Unsetenv("GINKGO_CLI_COLOR_YELLOW") os.Unsetenv("GINKGO_CLI_COLOR_LIGHT_YELLOW") os.Unsetenv("GINKGO_CLI_COLOR_CYAN") os.Unsetenv("GINKGO_CLI_COLOR_LIGHT_GRAY") os.Unsetenv("GINKGO_CLI_COLOR_BLUE") }) JustBeforeEach(func() { f = formatter.New(colorMode) }) Context("with ColorModeNone", func() { BeforeEach(func() { colorMode = formatter.ColorModeNone }) It("strips out color information", func() { Ω(f.F("{{green}}{{bold}}hi there{{/}}")).Should(Equal("hi there")) }) }) Context("with ColorModeTerminal", func() { BeforeEach(func() { colorMode = formatter.ColorModeTerminal }) It("renders the color information using terminal escape codes", func() { Ω(f.F("{{green}}{{bold}}hi there{{/}}")).Should(Equal("\x1b[38;5;10m\x1b[1mhi there\x1b[0m")) }) }) Context("with ColorModePassthrough", func() { BeforeEach(func() { colorMode = formatter.ColorModePassthrough }) It("leaves the color information as is, allowing us to test statements more easily", func() { Ω(f.F("{{green}}{{bold}}hi there{{/}}")).Should(Equal("{{green}}{{bold}}hi there{{/}}")) }) }) DescribeTable("with environment overrides", func(envVars map[string]string, input, expected string) { for envVar, value := range envVars { os.Setenv(envVar, value) } f := formatter.New(colorMode) Ω(f.F(input)).Should(Equal(expected)) for envVar := range envVars { os.Unsetenv(envVar) } }, Entry("uses default for too low codes", map[string]string{ "GINKGO_CLI_COLOR_RED": "-1", }, "{{red}}hi there{{/}}", "\x1b[38;5;9mhi there\x1b[0m"), Entry("uses default for too high codes", map[string]string{ "GINKGO_CLI_COLOR_RED": "256", }, "{{red}}hi there{{/}}", "\x1b[38;5;9mhi there\x1b[0m"), Entry("supports literal alias for 8bit color", map[string]string{ "GINKGO_CLI_COLOR_RED": "red", }, "{{red}}hi there{{/}}", "\x1b[38;5;1mhi there\x1b[0m"), Entry("supports number alias for 8bit color", map[string]string{ "GINKGO_CLI_COLOR_RED": "1", }, "{{red}}hi there{{/}}", "\x1b[38;5;1mhi there\x1b[0m"), Entry("supports 16bit colors (bright)", map[string]string{ "GINKGO_CLI_COLOR_RED": "9", }, "{{red}}hi there{{/}}", "\x1b[38;5;9mhi there\x1b[0m"), Entry("supports 16bit color literal aliases (bright)", map[string]string{ "GINKGO_CLI_COLOR_RED": "bright-red", }, "{{red}}hi there{{/}}", "\x1b[38;5;9mhi there\x1b[0m"), Entry("supports extended 256 colors", map[string]string{ "GINKGO_CLI_COLOR_RED": "16", }, "{{red}}hi there{{/}}", "\x1b[38;5;16mhi there\x1b[0m"), ) Describe("NewWithNoColorBool", func() { Context("when the noColor bool is true", func() { It("strips out color information", func() { f = formatter.NewWithNoColorBool(true) Ω(f.F("{{green}}{{bold}}hi there{{/}}")).Should(Equal("hi there")) }) }) Context("when the noColor bool is false", func() { It("renders the color information using terminal escape codes", func() { f = formatter.NewWithNoColorBool(false) Ω(f.F("{{green}}{{bold}}hi there{{/}}")).Should(Equal("\x1b[38;5;10m\x1b[1mhi there\x1b[0m")) }) }) }) Describe("F", func() { It("transforms the color information and sprintfs", func() { Ω(f.F("{{green}}hi there {{cyan}}%d {{yellow}}%s{{/}}", 3, "wise men")).Should(Equal("\x1b[38;5;10mhi there \x1b[38;5;14m3 \x1b[38;5;11mwise men\x1b[0m")) }) It("avoids sprintf if there are no additional arguments", func() { Ω(f.F("{{green}}hi there {{cyan}}%d {{yellow}}%s{{/}}")).Should(Equal("\x1b[38;5;10mhi there \x1b[38;5;14m%d \x1b[38;5;11m%s\x1b[0m")) }) }) Describe("Fi", func() { It("transforms the color information, sprintfs, and applies an indentation", func() { Ω(f.Fi(2, "{{green}}hi there\n{{cyan}}%d {{yellow}}%s{{/}}", 3, "wise men")).Should(Equal( " \x1b[38;5;10mhi there\n \x1b[38;5;14m3 \x1b[38;5;11mwise men\x1b[0m", )) }) }) DescribeTable("Fiw", func(indentation int, maxWidth int, input string, expected ...string) { Ω(f.Fiw(uint(indentation), uint(maxWidth), input)).Should(Equal(strings.Join(expected, "\n"))) }, Entry("basic case", 0, 0, "a really long string is fine", "a really long string is fine"), Entry("indentation is accounted for in width", 1, 10, "1234 678", " 1234 678", ), Entry("indentation is accounted for in width", 1, 10, "1234 6789", " 1234", " 6789", ), Entry("when there is a nice long sentence", 0, 10, "12 456 890 1234 5", "12 456 890", "1234 5", ), Entry("when a word in a sentence intersects the boundary", 0, 10, "12 456 8901 123 45", "12 456", "8901 123", "45", ), Entry("when a word in a sentence is just too long", 0, 10, "12 12345678901 12 12345 678901 12345678901", "12", "12345678901", "12 12345", "678901", "12345678901", ), ) Describe("CycleJoin", func() { It("combines elements, cycling through styles as it goes", func() { Ω(f.CycleJoin([]string{"a", "b", "c"}, "|", []string{"{{red}}", "{{green}}"})).Should(Equal( "\x1b[38;5;9ma|\x1b[38;5;10mb|\x1b[38;5;9mc\x1b[0m", )) }) }) }) golang-github-onsi-ginkgo-v2-2.22.0/ginkgo/000077500000000000000000000000001472321612100203505ustar00rootroot00000000000000golang-github-onsi-ginkgo-v2-2.22.0/ginkgo/build/000077500000000000000000000000001472321612100214475ustar00rootroot00000000000000golang-github-onsi-ginkgo-v2-2.22.0/ginkgo/build/build_command.go000066400000000000000000000041131472321612100245720ustar00rootroot00000000000000package build import ( "fmt" "os" "path" "github.com/onsi/ginkgo/v2/ginkgo/command" "github.com/onsi/ginkgo/v2/ginkgo/internal" "github.com/onsi/ginkgo/v2/types" ) func BuildBuildCommand() command.Command { var cliConfig = types.NewDefaultCLIConfig() var goFlagsConfig = types.NewDefaultGoFlagsConfig() flags, err := types.BuildBuildCommandFlagSet(&cliConfig, &goFlagsConfig) if err != nil { panic(err) } return command.Command{ Name: "build", Flags: flags, Usage: "ginkgo build ", ShortDoc: "Build the passed in (or the package in the current directory if left blank).", DocLink: "precompiling-suites", Command: func(args []string, _ []string) { var errors []error cliConfig, goFlagsConfig, errors = types.VetAndInitializeCLIAndGoConfig(cliConfig, goFlagsConfig) command.AbortIfErrors("Ginkgo detected configuration issues:", errors) buildSpecs(args, cliConfig, goFlagsConfig) }, } } func buildSpecs(args []string, cliConfig types.CLIConfig, goFlagsConfig types.GoFlagsConfig) { suites := internal.FindSuites(args, cliConfig, false).WithoutState(internal.TestSuiteStateSkippedByFilter) if len(suites) == 0 { command.AbortWith("Found no test suites") } internal.VerifyCLIAndFrameworkVersion(suites) opc := internal.NewOrderedParallelCompiler(cliConfig.ComputedNumCompilers()) opc.StartCompiling(suites, goFlagsConfig) for { suiteIdx, suite := opc.Next() if suiteIdx >= len(suites) { break } suites[suiteIdx] = suite if suite.State.Is(internal.TestSuiteStateFailedToCompile) { fmt.Println(suite.CompilationError.Error()) } else { if len(goFlagsConfig.O) == 0 { goFlagsConfig.O = path.Join(suite.Path, suite.PackageName+".test") } else { stat, err := os.Stat(goFlagsConfig.O) if err != nil { panic(err) } if stat.IsDir() { goFlagsConfig.O += "/" + suite.PackageName + ".test" } } fmt.Printf("Compiled %s\n", goFlagsConfig.O) } } if suites.CountWithState(internal.TestSuiteStateFailedToCompile) > 0 { command.AbortWith("Failed to compile all tests") } } golang-github-onsi-ginkgo-v2-2.22.0/ginkgo/command/000077500000000000000000000000001472321612100217665ustar00rootroot00000000000000golang-github-onsi-ginkgo-v2-2.22.0/ginkgo/command/abort.go000066400000000000000000000021331472321612100234230ustar00rootroot00000000000000package command import "fmt" type AbortDetails struct { ExitCode int Error error EmitUsage bool } func Abort(details AbortDetails) { panic(details) } func AbortGracefullyWith(format string, args ...interface{}) { Abort(AbortDetails{ ExitCode: 0, Error: fmt.Errorf(format, args...), EmitUsage: false, }) } func AbortWith(format string, args ...interface{}) { Abort(AbortDetails{ ExitCode: 1, Error: fmt.Errorf(format, args...), EmitUsage: false, }) } func AbortWithUsage(format string, args ...interface{}) { Abort(AbortDetails{ ExitCode: 1, Error: fmt.Errorf(format, args...), EmitUsage: true, }) } func AbortIfError(preamble string, err error) { if err != nil { Abort(AbortDetails{ ExitCode: 1, Error: fmt.Errorf("%s\n%s", preamble, err.Error()), EmitUsage: false, }) } } func AbortIfErrors(preamble string, errors []error) { if len(errors) > 0 { out := "" for _, err := range errors { out += err.Error() } Abort(AbortDetails{ ExitCode: 1, Error: fmt.Errorf("%s\n%s", preamble, out), EmitUsage: false, }) } } golang-github-onsi-ginkgo-v2-2.22.0/ginkgo/command/abort_test.go000066400000000000000000000041671472321612100244730ustar00rootroot00000000000000package command_test import ( "fmt" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" "github.com/onsi/ginkgo/v2/ginkgo/command" ) var _ = Describe("Abort", func() { It("panics when called", func() { details := command.AbortDetails{ ExitCode: 1, Error: fmt.Errorf("boom"), EmitUsage: true, } Ω(func() { command.Abort(details) }).Should(PanicWith(details)) }) Describe("AbortWith", func() { It("aborts with a formatted error", func() { Ω(func() { command.AbortWith("boom %d %s", 17, "bam!") }).Should(PanicWith(command.AbortDetails{ ExitCode: 1, Error: fmt.Errorf("boom 17 bam!"), EmitUsage: false, })) }) }) Describe("AbortWithUsage", func() { It("aborts with a formatted error and sets usage to true", func() { Ω(func() { command.AbortWithUsage("boom %d %s", 17, "bam!") }).Should(PanicWith(command.AbortDetails{ ExitCode: 1, Error: fmt.Errorf("boom 17 bam!"), EmitUsage: true, })) }) }) Describe("AbortIfError", func() { Context("with a nil error", func() { It("does not abort", func() { Ω(func() { command.AbortIfError("boom boom?", nil) }).ShouldNot(Panic()) }) }) Context("with a non-nil error", func() { It("does aborts, tacking on the message", func() { Ω(func() { command.AbortIfError("boom boom?", fmt.Errorf("kaboom!")) }).Should(PanicWith(command.AbortDetails{ ExitCode: 1, Error: fmt.Errorf("boom boom?\nkaboom!"), EmitUsage: false, })) }) }) }) Describe("AbortIfErrors", func() { Context("with an empty errors", func() { It("does not abort", func() { Ω(func() { command.AbortIfErrors("boom boom?", []error{}) }).ShouldNot(Panic()) }) }) Context("with non-nil errors", func() { It("does aborts, tacking on the messages", func() { Ω(func() { command.AbortIfErrors("boom boom?", []error{fmt.Errorf("kaboom!\n"), fmt.Errorf("kababoom!!\n")}) }).Should(PanicWith(command.AbortDetails{ ExitCode: 1, Error: fmt.Errorf("boom boom?\nkaboom!\nkababoom!!\n"), EmitUsage: false, })) }) }) }) }) golang-github-onsi-ginkgo-v2-2.22.0/ginkgo/command/command.go000066400000000000000000000024131472321612100237330ustar00rootroot00000000000000package command import ( "fmt" "io" "strings" "github.com/onsi/ginkgo/v2/formatter" "github.com/onsi/ginkgo/v2/types" ) type Command struct { Name string Flags types.GinkgoFlagSet Usage string ShortDoc string Documentation string DocLink string Command func(args []string, additionalArgs []string) } func (c Command) Run(args []string, additionalArgs []string) { args, err := c.Flags.Parse(args) if err != nil { AbortWithUsage(err.Error()) } c.Command(args, additionalArgs) } func (c Command) EmitUsage(writer io.Writer) { fmt.Fprintln(writer, formatter.F("{{bold}}"+c.Usage+"{{/}}")) fmt.Fprintln(writer, formatter.F("{{gray}}%s{{/}}", strings.Repeat("-", len(c.Usage)))) if c.ShortDoc != "" { fmt.Fprintln(writer, formatter.Fiw(0, formatter.COLS, c.ShortDoc)) fmt.Fprintln(writer, "") } if c.Documentation != "" { fmt.Fprintln(writer, formatter.Fiw(0, formatter.COLS, c.Documentation)) fmt.Fprintln(writer, "") } if c.DocLink != "" { fmt.Fprintln(writer, formatter.Fi(0, "{{bold}}Learn more at:{{/}} {{cyan}}{{underline}}http://onsi.github.io/ginkgo/#%s{{/}}", c.DocLink)) fmt.Fprintln(writer, "") } flagUsage := c.Flags.Usage() if flagUsage != "" { fmt.Fprintf(writer, formatter.F(flagUsage)) } } golang-github-onsi-ginkgo-v2-2.22.0/ginkgo/command/command_suite_test.go000066400000000000000000000003051472321612100262010ustar00rootroot00000000000000package command_test import ( "testing" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" ) func TestCommand(t *testing.T) { RegisterFailHandler(Fail) RunSpecs(t, "Command Suite") } golang-github-onsi-ginkgo-v2-2.22.0/ginkgo/command/command_test.go000066400000000000000000000052361472321612100250000ustar00rootroot00000000000000package command_test import ( "strings" . "github.com/onsi/ginkgo/v2" "github.com/onsi/ginkgo/v2/formatter" "github.com/onsi/ginkgo/v2/ginkgo/command" . "github.com/onsi/ginkgo/v2/internal/test_helpers" "github.com/onsi/ginkgo/v2/types" . "github.com/onsi/gomega" "github.com/onsi/gomega/gbytes" ) var _ = Describe("Command", func() { var c command.Command var rt *RunTracker BeforeEach(func() { rt = NewRunTracker() fs, err := types.NewGinkgoFlagSet( types.GinkgoFlags{ {Name: "contrabulaturally", KeyPath: "C", Usage: "with irridiacy"}, }, &(struct{ C int }{C: 17}), types.GinkgoFlagSections{}, ) Ω(err).ShouldNot(HaveOccurred()) c = command.Command{ Name: "enflabulate", Flags: fs, Usage: "flooper enflabulate ", ShortDoc: "Enflabulate all the mandribles", Documentation: "Coherent quasistatic protocols will be upended if contrabulaturally is greater than 23.", DocLink: "fabulous-enflabulence", Command: rt.C("enflabulate"), } }) Describe("Run", func() { Context("when flags fails to parse", func() { It("aborts with usage", func() { Ω(func() { c.Run([]string{"-not-a-flag=oops"}, []string{"additional", "args"}) }).Should(PanicWith(SatisfyAll( HaveField("ExitCode", 1), HaveField("Error", HaveOccurred()), HaveField("EmitUsage", BeTrue()), ))) Ω(rt).Should(HaveTrackedNothing()) }) }) Context("when flags parse", func() { It("runs the command", func() { c.Run([]string{"-contrabulaturally=16", "and-an-arg", "and-another"}, []string{"additional", "args"}) Ω(rt).Should(HaveRun("enflabulate")) Ω(rt.DataFor("enflabulate")["Args"]).Should(Equal([]string{"and-an-arg", "and-another"})) Ω(rt.DataFor("enflabulate")["AdditionalArgs"]).Should(Equal([]string{"additional", "args"})) }) }) }) Describe("Usage", func() { BeforeEach(func() { formatter.SingletonFormatter.ColorMode = formatter.ColorModePassthrough }) It("emits a nicely formatted usage", func() { buf := gbytes.NewBuffer() c.EmitUsage(buf) expected := strings.Join([]string{ "{{bold}}flooper enflabulate {{/}}", "{{gray}}--------------------------{{/}}", "Enflabulate all the mandribles", "", "Coherent quasistatic protocols will be upended if contrabulaturally is greater", "than 23.", "", "{{bold}}Learn more at:{{/}} {{cyan}}{{underline}}http://onsi.github.io/ginkgo/#fabulous-enflabulence{{/}}", "", " --contrabulaturally{{/}} [int] {{gray}}{{/}}", " {{light-gray}}with irridiacy{{/}}", "", "", }, "\n") Ω(string(buf.Contents())).Should(Equal(expected)) }) }) }) golang-github-onsi-ginkgo-v2-2.22.0/ginkgo/command/program.go000066400000000000000000000107411472321612100237670ustar00rootroot00000000000000package command import ( "fmt" "io" "os" "strings" "github.com/onsi/ginkgo/v2/formatter" "github.com/onsi/ginkgo/v2/types" ) type Program struct { Name string Heading string Commands []Command DefaultCommand Command DeprecatedCommands []DeprecatedCommand //For testing - leave as nil in production OutWriter io.Writer ErrWriter io.Writer Exiter func(code int) } type DeprecatedCommand struct { Name string Deprecation types.Deprecation } func (p Program) RunAndExit(osArgs []string) { var command Command deprecationTracker := types.NewDeprecationTracker() if p.Exiter == nil { p.Exiter = os.Exit } if p.OutWriter == nil { p.OutWriter = formatter.ColorableStdOut } if p.ErrWriter == nil { p.ErrWriter = formatter.ColorableStdErr } defer func() { exitCode := 0 if r := recover(); r != nil { details, ok := r.(AbortDetails) if !ok { panic(r) } if details.Error != nil { fmt.Fprintln(p.ErrWriter, formatter.F("{{red}}{{bold}}%s %s{{/}} {{red}}failed{{/}}", p.Name, command.Name)) fmt.Fprintln(p.ErrWriter, formatter.Fi(1, details.Error.Error())) } if details.EmitUsage { if details.Error != nil { fmt.Fprintln(p.ErrWriter, "") } command.EmitUsage(p.ErrWriter) } exitCode = details.ExitCode } command.Flags.ValidateDeprecations(deprecationTracker) if deprecationTracker.DidTrackDeprecations() { fmt.Fprintln(p.ErrWriter, deprecationTracker.DeprecationsReport()) } p.Exiter(exitCode) return }() args, additionalArgs := []string{}, []string{} foundDelimiter := false for _, arg := range osArgs[1:] { if !foundDelimiter { if arg == "--" { foundDelimiter = true continue } } if foundDelimiter { additionalArgs = append(additionalArgs, arg) } else { args = append(args, arg) } } command = p.DefaultCommand if len(args) > 0 { p.handleHelpRequestsAndExit(p.OutWriter, args) if command.Name == args[0] { args = args[1:] } else { for _, deprecatedCommand := range p.DeprecatedCommands { if deprecatedCommand.Name == args[0] { deprecationTracker.TrackDeprecation(deprecatedCommand.Deprecation) return } } for _, tryCommand := range p.Commands { if tryCommand.Name == args[0] { command, args = tryCommand, args[1:] break } } } } command.Run(args, additionalArgs) } func (p Program) handleHelpRequestsAndExit(writer io.Writer, args []string) { if len(args) == 0 { return } matchesHelpFlag := func(args ...string) bool { for _, arg := range args { if arg == "--help" || arg == "-help" || arg == "-h" || arg == "--h" { return true } } return false } if len(args) == 1 { if args[0] == "help" || matchesHelpFlag(args[0]) { p.EmitUsage(writer) Abort(AbortDetails{}) } } else { var name string if args[0] == "help" || matchesHelpFlag(args[0]) { name = args[1] } else if matchesHelpFlag(args[1:]...) { name = args[0] } else { return } if p.DefaultCommand.Name == name || p.Name == name { p.DefaultCommand.EmitUsage(writer) Abort(AbortDetails{}) } for _, command := range p.Commands { if command.Name == name { command.EmitUsage(writer) Abort(AbortDetails{}) } } fmt.Fprintln(writer, formatter.F("{{red}}Unknown Command: {{bold}}%s{{/}}", name)) fmt.Fprintln(writer, "") p.EmitUsage(writer) Abort(AbortDetails{ExitCode: 1}) } return } func (p Program) EmitUsage(writer io.Writer) { fmt.Fprintln(writer, formatter.F(p.Heading)) fmt.Fprintln(writer, formatter.F("{{gray}}%s{{/}}", strings.Repeat("-", len(p.Heading)))) fmt.Fprintln(writer, formatter.F("For usage information for a command, run {{bold}}%s help COMMAND{{/}}.", p.Name)) fmt.Fprintln(writer, formatter.F("For usage information for the default command, run {{bold}}%s help %s{{/}} or {{bold}}%s help %s{{/}}.", p.Name, p.Name, p.Name, p.DefaultCommand.Name)) fmt.Fprintln(writer, "") fmt.Fprintln(writer, formatter.F("The following commands are available:")) fmt.Fprintln(writer, formatter.Fi(1, "{{bold}}%s{{/}} or %s {{bold}}%s{{/}} - {{gray}}%s{{/}}", p.Name, p.Name, p.DefaultCommand.Name, p.DefaultCommand.Usage)) if p.DefaultCommand.ShortDoc != "" { fmt.Fprintln(writer, formatter.Fi(2, p.DefaultCommand.ShortDoc)) } for _, command := range p.Commands { fmt.Fprintln(writer, formatter.Fi(1, "{{bold}}%s{{/}} - {{gray}}%s{{/}}", command.Name, command.Usage)) if command.ShortDoc != "" { fmt.Fprintln(writer, formatter.Fi(2, command.ShortDoc)) } } } golang-github-onsi-ginkgo-v2-2.22.0/ginkgo/command/program_test.go000066400000000000000000000256201472321612100250300ustar00rootroot00000000000000package command_test import ( "fmt" "strings" . "github.com/onsi/ginkgo/v2" "github.com/onsi/ginkgo/v2/formatter" . "github.com/onsi/ginkgo/v2/internal/test_helpers" "github.com/onsi/ginkgo/v2/types" . "github.com/onsi/gomega" "github.com/onsi/gomega/gbytes" "github.com/onsi/ginkgo/v2/ginkgo/command" ) var _ = Describe("Program", func() { var program command.Program var rt *RunTracker var buf *gbytes.Buffer BeforeEach(func() { rt = NewRunTracker() defaultCommand := command.Command{Name: "alpha", Usage: "alpha usage", ShortDoc: "such usage!", Command: rt.C("alpha")} fs, err := types.NewGinkgoFlagSet( types.GinkgoFlags{ {Name: "decay-rate", KeyPath: "Rate", Usage: "set the decay rate, in years"}, {DeprecatedName: "old", KeyPath: "Old"}, }, &(struct { Rate float64 Old bool }{Rate: 17.0}), types.GinkgoFlagSections{}, ) Ω(err).ShouldNot(HaveOccurred()) commands := []command.Command{ {Name: "beta", Flags: fs, Usage: "beta usage", ShortDoc: "such usage!", Command: rt.C("beta")}, {Name: "gamma", Command: rt.C("gamma")}, {Name: "zeta", Command: rt.C("zeta", func() { command.Abort(command.AbortDetails{Error: fmt.Errorf("Kaboom!"), ExitCode: 17}) })}, } deprecatedCommands := []command.DeprecatedCommand{ {Name: "delta", Deprecation: types.Deprecation{Message: "delta is for deprecated"}}, } formatter.SingletonFormatter.ColorMode = formatter.ColorModePassthrough buf = gbytes.NewBuffer() program = command.Program{ Name: "omicron", Heading: "Omicron v2.0.0", Commands: commands, DefaultCommand: defaultCommand, DeprecatedCommands: deprecatedCommands, Exiter: func(code int) { rt.RunWithData("exit", "Code", code) }, OutWriter: buf, ErrWriter: buf, } }) Context("when called with no subcommand", func() { BeforeEach(func() { program.RunAndExit([]string{"omicron"}) //os.Args always includes the name of the program as the first element }) It("runs the default command", func() { Ω(rt).Should(HaveTracked("alpha", "exit")) Ω(rt).Should(HaveRunWithData("exit", "Code", 0)) Ω(buf.Contents()).Should(BeEmpty()) }) }) Context("when called with the default command's name", func() { BeforeEach(func() { program.RunAndExit([]string{"omicron", "alpha", "args1", "args2"}) }) It("runs the default command", func() { Ω(rt).Should(HaveTracked("alpha", "exit")) Ω(rt).Should(HaveRunWithData("alpha", "Args", []string{"args1", "args2"})) Ω(rt).Should(HaveRunWithData("exit", "Code", 0)) Ω(buf.Contents()).Should(BeEmpty()) }) }) Context("when called with a subcommand", func() { BeforeEach(func() { program.RunAndExit([]string{"omicron", "beta"}) }) It("runs that subcommand", func() { Ω(rt).Should(HaveTracked("beta", "exit")) Ω(rt).Should(HaveRunWithData("exit", "Code", 0)) Ω(buf.Contents()).Should(BeEmpty()) }) }) Context("when called with an unknown subcommand", func() { BeforeEach(func() { program.RunAndExit([]string{"omicron", "xi"}) }) It("calls the default command with arguments", func() { Ω(rt).Should(HaveTracked("alpha", "exit")) Ω(rt).Should(HaveRunWithData("alpha", "Args", []string{"xi"})) Ω(rt).Should(HaveRunWithData("exit", "Code", 0)) Ω(buf.Contents()).Should(BeEmpty()) }) }) Context("when passed arguments and additional arguments", func() { BeforeEach(func() { program.RunAndExit([]string{"omicron", "gamma", "arg1", "-arg2", "--", "addArg1", "addArg2"}) }) It("passes both in", func() { Ω(rt).Should(HaveTracked("gamma", "exit")) Ω(rt).Should(HaveRunWithData("gamma", "Args", []string{"arg1", "-arg2"}, "AdditionalArgs", []string{"addArg1", "addArg2"})) Ω(rt).Should(HaveRunWithData("exit", "Code", 0)) Ω(buf.Contents()).Should(BeEmpty()) }) }) DescribeTable("Emitting help when asked", func(args []string) { program.RunAndExit(args) Ω(rt).Should(HaveTracked("exit")) Ω(rt).Should(HaveRunWithData("exit", "Code", 0)) //HavePrefix to avoid trailing whitespace causing failures Ω(string(buf.Contents())).Should(HavePrefix(strings.Join([]string{ "Omicron v2.0.0", "{{gray}}--------------{{/}}", "For usage information for a command, run {{bold}}omicron help COMMAND{{/}}.", "For usage information for the default command, run {{bold}}omicron help omicron{{/}} or {{bold}}omicron help alpha{{/}}.", "", "The following commands are available:", " {{bold}}omicron{{/}} or omicron {{bold}}alpha{{/}} - {{gray}}alpha usage{{/}}", " such usage!", " {{bold}}beta{{/}} - {{gray}}beta usage{{/}}", " such usage!", " {{bold}}gamma{{/}} - {{gray}}{{/}}", " {{bold}}zeta{{/}} - {{gray}}{{/}}", }, "\n"))) }, func(args []string) string { return fmt.Sprintf("with %s", args[1]) }, Entry(nil, []string{"omicron", "help"}), Entry(nil, []string{"omicron", "-help"}), Entry(nil, []string{"omicron", "--help"}), Entry(nil, []string{"omicron", "-h"}), Entry(nil, []string{"omicron", "--h"}), ) DescribeTable("Emitting help for the default command", func(args []string) { program.RunAndExit(args) Ω(rt).Should(HaveTracked("exit")) Ω(rt).Should(HaveRunWithData("exit", "Code", 0)) Ω(string(buf.Contents())).Should(HavePrefix(strings.Join([]string{ "{{bold}}alpha usage{{/}}", "{{gray}}-----------{{/}}", "such usage!", }, "\n"))) }, func(args []string) string { return fmt.Sprintf("with %s %s", args[1], args[2]) }, Entry(nil, []string{"omicron", "help", "omicron"}), Entry(nil, []string{"omicron", "-help", "omicron"}), Entry(nil, []string{"omicron", "--help", "omicron"}), Entry(nil, []string{"omicron", "-h", "omicron"}), Entry(nil, []string{"omicron", "--h", "omicron"}), Entry(nil, []string{"omicron", "help", "alpha"}), Entry(nil, []string{"omicron", "-help", "alpha"}), Entry(nil, []string{"omicron", "--help", "alpha"}), Entry(nil, []string{"omicron", "-h", "alpha"}), Entry(nil, []string{"omicron", "--h", "alpha"}), Entry(nil, []string{"omicron", "alpha", "-help"}), Entry(nil, []string{"omicron", "alpha", "--help"}), Entry(nil, []string{"omicron", "alpha", "-h"}), Entry(nil, []string{"omicron", "alpha", "--h"}), ) DescribeTable("Emitting help for a known subcommand", func(args []string) { program.RunAndExit(args) Ω(rt).Should(HaveTracked("exit")) Ω(rt).Should(HaveRunWithData("exit", "Code", 0)) Ω(string(buf.Contents())).Should(HavePrefix(strings.Join([]string{ "{{bold}}beta usage{{/}}", "{{gray}}----------{{/}}", "such usage!", "", " --decay-rate{{/}} [float] {{gray}}{{/}}", " {{light-gray}}set the decay rate, in years{{/}}", }, "\n"))) }, func(args []string) string { return fmt.Sprintf("with %s %s", args[1], args[2]) }, Entry(nil, []string{"omicron", "help", "beta"}), Entry(nil, []string{"omicron", "-help", "beta"}), Entry(nil, []string{"omicron", "--help", "beta"}), Entry(nil, []string{"omicron", "-h", "beta"}), Entry(nil, []string{"omicron", "--h", "beta"}), Entry(nil, []string{"omicron", "beta", "-help"}), Entry(nil, []string{"omicron", "beta", "--help"}), Entry(nil, []string{"omicron", "beta", "-h"}), Entry(nil, []string{"omicron", "beta", "--h"}), ) DescribeTable("Emitting help for an unknown subcommand", func(args []string) { program.RunAndExit(args) Ω(rt).Should(HaveTracked("exit")) Ω(rt).Should(HaveRunWithData("exit", "Code", 1)) Ω(string(buf.Contents())).Should(HavePrefix(strings.Join([]string{ "{{red}}Unknown Command: {{bold}}xi{{/}}", "", "Omicron v2.0.0", "{{gray}}--------------{{/}}", "For usage information for a command, run {{bold}}omicron help COMMAND{{/}}.", "For usage information for the default command, run {{bold}}omicron help omicron{{/}} or {{bold}}omicron help alpha{{/}}.", "", "The following commands are available:", " {{bold}}omicron{{/}} or omicron {{bold}}alpha{{/}} - {{gray}}alpha usage{{/}}", " such usage!", " {{bold}}beta{{/}} - {{gray}}beta usage{{/}}", " such usage!", " {{bold}}gamma{{/}} - {{gray}}{{/}}", " {{bold}}zeta{{/}} - {{gray}}{{/}}", }, "\n"))) }, func(args []string) string { return fmt.Sprintf("with %s %s", args[1], args[2]) }, Entry(nil, []string{"omicron", "help", "xi"}), Entry(nil, []string{"omicron", "-help", "xi"}), Entry(nil, []string{"omicron", "--help", "xi"}), Entry(nil, []string{"omicron", "-h", "xi"}), Entry(nil, []string{"omicron", "--h", "xi"}), Entry(nil, []string{"omicron", "xi", "-help"}), Entry(nil, []string{"omicron", "xi", "--help"}), Entry(nil, []string{"omicron", "xi", "-h"}), Entry(nil, []string{"omicron", "xi", "--h"}), ) Context("when called with a deprecated command", func() { BeforeEach(func() { program.RunAndExit([]string{"omicron", "delta"}) }) It("lets the user know the command is deprecated", func() { Ω(rt).Should(HaveTracked("exit")) Ω(rt).Should(HaveRunWithData("exit", "Code", 0)) Ω(string(buf.Contents())).Should(HavePrefix(strings.Join([]string{ "{{light-yellow}}You're using deprecated Ginkgo functionality:{{/}}", "{{light-yellow}}============================================={{/}}", " {{yellow}}delta is for deprecated{{/}}", }, "\n"))) }) }) Context("when a deprecated flag is used", func() { BeforeEach(func() { program.RunAndExit([]string{"omicron", "beta", "-old"}) }) It("lets the user know a deprecated flag was used", func() { Ω(rt).Should(HaveTracked("beta", "exit")) Ω(rt).Should(HaveRunWithData("exit", "Code", 0)) Ω(string(buf.Contents())).Should(HavePrefix(strings.Join([]string{ "{{light-yellow}}You're using deprecated Ginkgo functionality:{{/}}", "{{light-yellow}}============================================={{/}}", " {{yellow}}--old is deprecated{{/}}", }, "\n"))) }) }) Context("when an unknown flag is used", func() { BeforeEach(func() { program.RunAndExit([]string{"omicron", "beta", "-zanzibar"}) }) It("emits usage for the associated subcommand", func() { Ω(rt).Should(HaveTracked("exit")) Ω(rt).Should(HaveRunWithData("exit", "Code", 1)) Ω(string(buf.Contents())).Should(HavePrefix(strings.Join([]string{ "{{red}}{{bold}}omicron beta{{/}} {{red}}failed{{/}}", " flag provided but not defined: -zanzibar", "", "{{bold}}beta usage{{/}}", "{{gray}}----------{{/}}", "such usage!", "", " --decay-rate{{/}} [float] {{gray}}{{/}}", " {{light-gray}}set the decay rate, in years{{/}}", }, "\n"))) }) }) Context("when a subcommand aborts", func() { BeforeEach(func() { program.RunAndExit([]string{"omicron", "zeta"}) }) It("emits information about the error", func() { Ω(rt).Should(HaveTracked("zeta", "exit")) Ω(rt).Should(HaveRunWithData("exit", "Code", 17)) Ω(string(buf.Contents())).Should(HavePrefix(strings.Join([]string{ "{{red}}{{bold}}omicron zeta{{/}} {{red}}failed{{/}}", " Kaboom!", }, "\n"))) }) }) }) golang-github-onsi-ginkgo-v2-2.22.0/ginkgo/generators/000077500000000000000000000000001472321612100225215ustar00rootroot00000000000000golang-github-onsi-ginkgo-v2-2.22.0/ginkgo/generators/boostrap_templates.go000066400000000000000000000020221472321612100267530ustar00rootroot00000000000000package generators var bootstrapText = `package {{.Package}} import ( "testing" {{.GinkgoImport}} {{.GomegaImport}} ) func Test{{.FormattedName}}(t *testing.T) { {{.GomegaPackage}}RegisterFailHandler({{.GinkgoPackage}}Fail) {{.GinkgoPackage}}RunSpecs(t, "{{.FormattedName}} Suite") } ` var agoutiBootstrapText = `package {{.Package}} import ( "testing" {{.GinkgoImport}} {{.GomegaImport}} "github.com/sclevine/agouti" ) func Test{{.FormattedName}}(t *testing.T) { {{.GomegaPackage}}RegisterFailHandler({{.GinkgoPackage}}Fail) {{.GinkgoPackage}}RunSpecs(t, "{{.FormattedName}} Suite") } var agoutiDriver *agouti.WebDriver var _ = {{.GinkgoPackage}}BeforeSuite(func() { // Choose a WebDriver: agoutiDriver = agouti.PhantomJS() // agoutiDriver = agouti.Selenium() // agoutiDriver = agouti.ChromeDriver() {{.GomegaPackage}}Expect(agoutiDriver.Start()).To({{.GomegaPackage}}Succeed()) }) var _ = {{.GinkgoPackage}}AfterSuite(func() { {{.GomegaPackage}}Expect(agoutiDriver.Stop()).To({{.GomegaPackage}}Succeed()) }) ` golang-github-onsi-ginkgo-v2-2.22.0/ginkgo/generators/bootstrap_command.go000066400000000000000000000105571472321612100265730ustar00rootroot00000000000000package generators import ( "bytes" "encoding/json" "fmt" "os" "text/template" sprig "github.com/go-task/slim-sprig/v3" "github.com/onsi/ginkgo/v2/ginkgo/command" "github.com/onsi/ginkgo/v2/ginkgo/internal" "github.com/onsi/ginkgo/v2/types" ) func BuildBootstrapCommand() command.Command { conf := GeneratorsConfig{} flags, err := types.NewGinkgoFlagSet( types.GinkgoFlags{ {Name: "agouti", KeyPath: "Agouti", Usage: "If set, bootstrap will generate a bootstrap file for writing Agouti tests"}, {Name: "nodot", KeyPath: "NoDot", Usage: "If set, bootstrap will generate a bootstrap test file that does not dot-import ginkgo and gomega"}, {Name: "internal", KeyPath: "Internal", Usage: "If set, bootstrap will generate a bootstrap test file that uses the regular package name (i.e. `package X`, not `package X_test`)"}, {Name: "template", KeyPath: "CustomTemplate", UsageArgument: "template-file", Usage: "If specified, generate will use the contents of the file passed as the bootstrap template"}, {Name: "template-data", KeyPath: "CustomTemplateData", UsageArgument: "template-data-file", Usage: "If specified, generate will use the contents of the file passed as data to be rendered in the bootstrap template"}, }, &conf, types.GinkgoFlagSections{}, ) if err != nil { panic(err) } return command.Command{ Name: "bootstrap", Usage: "ginkgo bootstrap", ShortDoc: "Bootstrap a test suite for the current package", Documentation: `Tests written in Ginkgo and Gomega require a small amount of boilerplate to hook into Go's testing infrastructure. {{bold}}ginkgo bootstrap{{/}} generates this boilerplate for you in a file named X_suite_test.go where X is the name of the package under test.`, DocLink: "generators", Flags: flags, Command: func(_ []string, _ []string) { generateBootstrap(conf) }, } } type bootstrapData struct { Package string FormattedName string GinkgoImport string GomegaImport string GinkgoPackage string GomegaPackage string CustomData map[string]any } func generateBootstrap(conf GeneratorsConfig) { packageName, bootstrapFilePrefix, formattedName := getPackageAndFormattedName() data := bootstrapData{ Package: determinePackageName(packageName, conf.Internal), FormattedName: formattedName, GinkgoImport: `. "github.com/onsi/ginkgo/v2"`, GomegaImport: `. "github.com/onsi/gomega"`, GinkgoPackage: "", GomegaPackage: "", } if conf.NoDot { data.GinkgoImport = `"github.com/onsi/ginkgo/v2"` data.GomegaImport = `"github.com/onsi/gomega"` data.GinkgoPackage = `ginkgo.` data.GomegaPackage = `gomega.` } targetFile := fmt.Sprintf("%s_suite_test.go", bootstrapFilePrefix) if internal.FileExists(targetFile) { command.AbortWith("{{bold}}%s{{/}} already exists", targetFile) } else { fmt.Printf("Generating ginkgo test suite bootstrap for %s in:\n\t%s\n", packageName, targetFile) } f, err := os.Create(targetFile) command.AbortIfError("Failed to create file:", err) defer f.Close() var templateText string if conf.CustomTemplate != "" { tpl, err := os.ReadFile(conf.CustomTemplate) command.AbortIfError("Failed to read custom bootstrap file:", err) templateText = string(tpl) if conf.CustomTemplateData != "" { var tplCustomDataMap map[string]any tplCustomData, err := os.ReadFile(conf.CustomTemplateData) command.AbortIfError("Failed to read custom boostrap data file:", err) if !json.Valid([]byte(tplCustomData)) { command.AbortWith("Invalid JSON object in custom data file.") } //create map from the custom template data json.Unmarshal(tplCustomData, &tplCustomDataMap) data.CustomData = tplCustomDataMap } } else if conf.Agouti { templateText = agoutiBootstrapText } else { templateText = bootstrapText } //Setting the option to explicitly fail if template is rendered trying to access missing key bootstrapTemplate, err := template.New("bootstrap").Funcs(sprig.TxtFuncMap()).Option("missingkey=error").Parse(templateText) command.AbortIfError("Failed to parse bootstrap template:", err) buf := &bytes.Buffer{} //Being explicit about failing sooner during template rendering //when accessing custom data rather than during the go fmt command err = bootstrapTemplate.Execute(buf, data) command.AbortIfError("Failed to render bootstrap template:", err) buf.WriteTo(f) internal.GoFmt(targetFile) } golang-github-onsi-ginkgo-v2-2.22.0/ginkgo/generators/generate_command.go000066400000000000000000000171061472321612100263450ustar00rootroot00000000000000package generators import ( "bytes" "encoding/json" "fmt" "os" "path/filepath" "strconv" "strings" "text/template" sprig "github.com/go-task/slim-sprig/v3" "github.com/onsi/ginkgo/v2/ginkgo/command" "github.com/onsi/ginkgo/v2/ginkgo/internal" "github.com/onsi/ginkgo/v2/types" ) func BuildGenerateCommand() command.Command { conf := GeneratorsConfig{} flags, err := types.NewGinkgoFlagSet( types.GinkgoFlags{ {Name: "agouti", KeyPath: "Agouti", Usage: "If set, generate will create a test file for writing Agouti tests"}, {Name: "nodot", KeyPath: "NoDot", Usage: "If set, generate will create a test file that does not dot-import ginkgo and gomega"}, {Name: "internal", KeyPath: "Internal", Usage: "If set, generate will create a test file that uses the regular package name (i.e. `package X`, not `package X_test`)"}, {Name: "template", KeyPath: "CustomTemplate", UsageArgument: "template-file", Usage: "If specified, generate will use the contents of the file passed as the test file template"}, {Name: "template-data", KeyPath: "CustomTemplateData", UsageArgument: "template-data-file", Usage: "If specified, generate will use the contents of the file passed as data to be rendered in the test file template"}, {Name: "tags", KeyPath: "Tags", UsageArgument: "build-tags", Usage: "If specified, generate will create a test file that uses the given build tags (i.e. `--tags e2e,!unit` will add `//go:build e2e,!unit`)"}, }, &conf, types.GinkgoFlagSections{}, ) if err != nil { panic(err) } return command.Command{ Name: "generate", Usage: "ginkgo generate ", ShortDoc: "Generate a test file named _test.go", Documentation: `If the optional argument is omitted, a file named after the package in the current directory will be created. You can pass multiple to generate multiple files simultaneously. The resulting files are named _test.go. You can also pass a of the form "file.go" and generate will emit "file_test.go".`, DocLink: "generators", Flags: flags, Command: func(args []string, _ []string) { generateTestFiles(conf, args) }, } } type specData struct { BuildTags string Package string Subject string PackageImportPath string ImportPackage bool GinkgoImport string GomegaImport string GinkgoPackage string GomegaPackage string CustomData map[string]any } func generateTestFiles(conf GeneratorsConfig, args []string) { subjects := args if len(subjects) == 0 { subjects = []string{""} } for _, subject := range subjects { generateTestFileForSubject(subject, conf) } } func generateTestFileForSubject(subject string, conf GeneratorsConfig) { packageName, specFilePrefix, formattedName := getPackageAndFormattedName() if subject != "" { specFilePrefix = formatSubject(subject) formattedName = prettifyName(specFilePrefix) } if conf.Internal { specFilePrefix = specFilePrefix + "_internal" } data := specData{ BuildTags: getBuildTags(conf.Tags), Package: determinePackageName(packageName, conf.Internal), Subject: formattedName, PackageImportPath: getPackageImportPath(), ImportPackage: !conf.Internal, GinkgoImport: `. "github.com/onsi/ginkgo/v2"`, GomegaImport: `. "github.com/onsi/gomega"`, GinkgoPackage: "", GomegaPackage: "", } if conf.NoDot { data.GinkgoImport = `"github.com/onsi/ginkgo/v2"` data.GomegaImport = `"github.com/onsi/gomega"` data.GinkgoPackage = `ginkgo.` data.GomegaPackage = `gomega.` } targetFile := fmt.Sprintf("%s_test.go", specFilePrefix) if internal.FileExists(targetFile) { command.AbortWith("{{bold}}%s{{/}} already exists", targetFile) } else { fmt.Printf("Generating ginkgo test for %s in:\n %s\n", data.Subject, targetFile) } f, err := os.Create(targetFile) command.AbortIfError("Failed to create test file:", err) defer f.Close() var templateText string if conf.CustomTemplate != "" { tpl, err := os.ReadFile(conf.CustomTemplate) command.AbortIfError("Failed to read custom template file:", err) templateText = string(tpl) if conf.CustomTemplateData != "" { var tplCustomDataMap map[string]any tplCustomData, err := os.ReadFile(conf.CustomTemplateData) command.AbortIfError("Failed to read custom template data file:", err) if !json.Valid([]byte(tplCustomData)) { command.AbortWith("Invalid JSON object in custom data file.") } //create map from the custom template data json.Unmarshal(tplCustomData, &tplCustomDataMap) data.CustomData = tplCustomDataMap } } else if conf.Agouti { templateText = agoutiSpecText } else { templateText = specText } //Setting the option to explicitly fail if template is rendered trying to access missing key specTemplate, err := template.New("spec").Funcs(sprig.TxtFuncMap()).Option("missingkey=error").Parse(templateText) command.AbortIfError("Failed to read parse test template:", err) //Being explicit about failing sooner during template rendering //when accessing custom data rather than during the go fmt command err = specTemplate.Execute(f, data) command.AbortIfError("Failed to render bootstrap template:", err) internal.GoFmt(targetFile) } func formatSubject(name string) string { name = strings.ReplaceAll(name, "-", "_") name = strings.ReplaceAll(name, " ", "_") name = strings.Split(name, ".go")[0] name = strings.Split(name, "_test")[0] return name } // moduleName returns module name from go.mod from given module root directory func moduleName(modRoot string) string { modFile, err := os.Open(filepath.Join(modRoot, "go.mod")) if err != nil { return "" } defer modFile.Close() mod := make([]byte, 128) _, err = modFile.Read(mod) if err != nil { return "" } slashSlash := []byte("//") moduleStr := []byte("module") for len(mod) > 0 { line := mod mod = nil if i := bytes.IndexByte(line, '\n'); i >= 0 { line, mod = line[:i], line[i+1:] } if i := bytes.Index(line, slashSlash); i >= 0 { line = line[:i] } line = bytes.TrimSpace(line) if !bytes.HasPrefix(line, moduleStr) { continue } line = line[len(moduleStr):] n := len(line) line = bytes.TrimSpace(line) if len(line) == n || len(line) == 0 { continue } if line[0] == '"' || line[0] == '`' { p, err := strconv.Unquote(string(line)) if err != nil { return "" // malformed quoted string or multiline module path } return p } return string(line) } return "" // missing module path } func findModuleRoot(dir string) (root string) { dir = filepath.Clean(dir) // Look for enclosing go.mod. for { if fi, err := os.Stat(filepath.Join(dir, "go.mod")); err == nil && !fi.IsDir() { return dir } d := filepath.Dir(dir) if d == dir { break } dir = d } return "" } func getPackageImportPath() string { workingDir, err := os.Getwd() if err != nil { panic(err.Error()) } sep := string(filepath.Separator) // Try go.mod file first modRoot := findModuleRoot(workingDir) if modRoot != "" { modName := moduleName(modRoot) if modName != "" { cd := strings.ReplaceAll(workingDir, modRoot, "") cd = strings.ReplaceAll(cd, sep, "/") return modName + cd } } // Fallback to GOPATH structure paths := strings.Split(workingDir, sep+"src"+sep) if len(paths) == 1 { fmt.Printf("\nCouldn't identify package import path.\n\n\tginkgo generate\n\nMust be run within a package directory under $GOPATH/src/...\nYou're going to have to change UNKNOWN_PACKAGE_PATH in the generated file...\n\n") return "UNKNOWN_PACKAGE_PATH" } return filepath.ToSlash(paths[len(paths)-1]) } golang-github-onsi-ginkgo-v2-2.22.0/ginkgo/generators/generate_templates.go000066400000000000000000000015271472321612100267250ustar00rootroot00000000000000package generators var specText = `{{.BuildTags}} package {{.Package}} import ( {{.GinkgoImport}} {{.GomegaImport}} {{if .ImportPackage}}"{{.PackageImportPath}}"{{end}} ) var _ = {{.GinkgoPackage}}Describe("{{.Subject}}", func() { }) ` var agoutiSpecText = `{{.BuildTags}} package {{.Package}} import ( {{.GinkgoImport}} {{.GomegaImport}} "github.com/sclevine/agouti" . "github.com/sclevine/agouti/matchers" {{if .ImportPackage}}"{{.PackageImportPath}}"{{end}} ) var _ = {{.GinkgoPackage}}Describe("{{.Subject}}", func() { var page *agouti.Page {{.GinkgoPackage}}BeforeEach(func() { var err error page, err = agoutiDriver.NewPage() {{.GomegaPackage}}Expect(err).NotTo({{.GomegaPackage}}HaveOccurred()) }) {{.GinkgoPackage}}AfterEach(func() { {{.GomegaPackage}}Expect(page.Destroy()).To({{.GomegaPackage}}Succeed()) }) }) ` golang-github-onsi-ginkgo-v2-2.22.0/ginkgo/generators/generators_common.go000066400000000000000000000033441472321612100265750ustar00rootroot00000000000000package generators import ( "fmt" "go/build" "os" "path/filepath" "strconv" "strings" "github.com/onsi/ginkgo/v2/ginkgo/command" ) type GeneratorsConfig struct { Agouti, NoDot, Internal bool CustomTemplate string CustomTemplateData string Tags string } func getPackageAndFormattedName() (string, string, string) { path, err := os.Getwd() command.AbortIfError("Could not get current working directory:", err) dirName := strings.ReplaceAll(filepath.Base(path), "-", "_") dirName = strings.ReplaceAll(dirName, " ", "_") pkg, err := build.ImportDir(path, 0) packageName := pkg.Name if err != nil { packageName = ensureLegalPackageName(dirName) } formattedName := prettifyName(filepath.Base(path)) return packageName, dirName, formattedName } func ensureLegalPackageName(name string) string { if name == "_" { return "underscore" } if len(name) == 0 { return "empty" } n, isDigitErr := strconv.Atoi(string(name[0])) if isDigitErr == nil { return []string{"zero", "one", "two", "three", "four", "five", "six", "seven", "eight", "nine"}[n] + name[1:] } return name } func prettifyName(name string) string { name = strings.ReplaceAll(name, "-", " ") name = strings.ReplaceAll(name, "_", " ") name = strings.Title(name) name = strings.ReplaceAll(name, " ", "") return name } func determinePackageName(name string, internal bool) string { if internal { return name } return name + "_test" } // getBuildTags returns the resultant string to be added. // If the input string is not empty, then returns a `//go:build {}` string, // otherwise returns an empty string. func getBuildTags(tags string) string { if tags != "" { return fmt.Sprintf("//go:build %s\n", tags) } return "" } golang-github-onsi-ginkgo-v2-2.22.0/ginkgo/ginkgo_cli_suite_test.go000066400000000000000000000006551472321612100252620ustar00rootroot00000000000000package main import ( "testing" "github.com/onsi/ginkgo/v2/internal/test_helpers" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" ) func TestGinkgoCLI(t *testing.T) { RegisterFailHandler(Fail) RunSpecs(t, "Ginkgo CLI Suite") } var anchors test_helpers.Anchors var _ = BeforeSuite(func() { var err error anchors, err = test_helpers.LoadAnchors(test_helpers.DOCS, "../") Ω(err).ShouldNot(HaveOccurred()) }) golang-github-onsi-ginkgo-v2-2.22.0/ginkgo/ginkgo_cli_test.go000066400000000000000000000006321472321612100240440ustar00rootroot00000000000000package main import ( . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" ) var _ = Describe("Command Documentation Links", func() { It("all commands with doc links point to valid documentation", func() { commands := GenerateCommands() for _, command := range commands { if command.DocLink != "" { Ω(anchors.DocAnchors["index.md"]).Should(ContainElement(command.DocLink)) } } }) }) golang-github-onsi-ginkgo-v2-2.22.0/ginkgo/internal/000077500000000000000000000000001472321612100221645ustar00rootroot00000000000000golang-github-onsi-ginkgo-v2-2.22.0/ginkgo/internal/cli_internal_suite_test.go000066400000000000000000000006201472321612100274240ustar00rootroot00000000000000package internal_test import ( . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" "testing" ) func TestCLIInternalSuite(t *testing.T) { RegisterFailHandler(Fail) RunSpecs(t, "CLI Internal Suite") } var _ = It(`These unit tests do not cover all the functionality in ginkgo/internal. The run, compile, and profiles functions are all integration tests in ginkgo/integration.`, func() {}) golang-github-onsi-ginkgo-v2-2.22.0/ginkgo/internal/compile.go000066400000000000000000000106601472321612100241460ustar00rootroot00000000000000package internal import ( "fmt" "os" "os/exec" "path/filepath" "strings" "sync" "github.com/onsi/ginkgo/v2/types" ) func CompileSuite(suite TestSuite, goFlagsConfig types.GoFlagsConfig) TestSuite { if suite.PathToCompiledTest != "" { return suite } suite.CompilationError = nil path, err := filepath.Abs(filepath.Join(suite.Path, suite.PackageName+".test")) if err != nil { suite.State = TestSuiteStateFailedToCompile suite.CompilationError = fmt.Errorf("Failed to compute compilation target path:\n%s", err.Error()) return suite } if len(goFlagsConfig.O) > 0 { userDefinedPath, err := filepath.Abs(goFlagsConfig.O) if err != nil { suite.State = TestSuiteStateFailedToCompile suite.CompilationError = fmt.Errorf("Failed to compute compilation target path %s:\n%s", goFlagsConfig.O, err.Error()) return suite } path = userDefinedPath } goFlagsConfig.O = path ginkgoInvocationPath, _ := os.Getwd() ginkgoInvocationPath, _ = filepath.Abs(ginkgoInvocationPath) packagePath := suite.AbsPath() pathToInvocationPath, err := filepath.Rel(packagePath, ginkgoInvocationPath) if err != nil { suite.State = TestSuiteStateFailedToCompile suite.CompilationError = fmt.Errorf("Failed to get relative path from package to the current working directory:\n%s", err.Error()) return suite } args, err := types.GenerateGoTestCompileArgs(goFlagsConfig, "./", pathToInvocationPath) if err != nil { suite.State = TestSuiteStateFailedToCompile suite.CompilationError = fmt.Errorf("Failed to generate go test compile flags:\n%s", err.Error()) return suite } cmd := exec.Command("go", args...) cmd.Dir = suite.Path output, err := cmd.CombinedOutput() if err != nil { if len(output) > 0 { suite.State = TestSuiteStateFailedToCompile suite.CompilationError = fmt.Errorf("Failed to compile %s:\n\n%s", suite.PackageName, output) } else { suite.State = TestSuiteStateFailedToCompile suite.CompilationError = fmt.Errorf("Failed to compile %s\n%s", suite.PackageName, err.Error()) } return suite } if strings.Contains(string(output), "[no test files]") { suite.State = TestSuiteStateSkippedDueToEmptyCompilation return suite } if len(output) > 0 { fmt.Println(string(output)) } if !FileExists(path) { suite.State = TestSuiteStateFailedToCompile suite.CompilationError = fmt.Errorf("Failed to compile %s:\nOutput file %s could not be found", suite.PackageName, path) return suite } suite.State = TestSuiteStateCompiled suite.PathToCompiledTest = path return suite } func Cleanup(goFlagsConfig types.GoFlagsConfig, suites ...TestSuite) { if goFlagsConfig.BinaryMustBePreserved() { return } for _, suite := range suites { if !suite.Precompiled { os.Remove(suite.PathToCompiledTest) } } } type parallelSuiteBundle struct { suite TestSuite compiled chan TestSuite } type OrderedParallelCompiler struct { mutex *sync.Mutex stopped bool numCompilers int idx int numSuites int completionChannels []chan TestSuite } func NewOrderedParallelCompiler(numCompilers int) *OrderedParallelCompiler { return &OrderedParallelCompiler{ mutex: &sync.Mutex{}, numCompilers: numCompilers, } } func (opc *OrderedParallelCompiler) StartCompiling(suites TestSuites, goFlagsConfig types.GoFlagsConfig) { opc.stopped = false opc.idx = 0 opc.numSuites = len(suites) opc.completionChannels = make([]chan TestSuite, opc.numSuites) toCompile := make(chan parallelSuiteBundle, opc.numCompilers) for compiler := 0; compiler < opc.numCompilers; compiler++ { go func() { for bundle := range toCompile { c, suite := bundle.compiled, bundle.suite opc.mutex.Lock() stopped := opc.stopped opc.mutex.Unlock() if !stopped { suite = CompileSuite(suite, goFlagsConfig) } c <- suite } }() } for idx, suite := range suites { opc.completionChannels[idx] = make(chan TestSuite, 1) toCompile <- parallelSuiteBundle{suite, opc.completionChannels[idx]} if idx == 0 { //compile first suite serially suite = <-opc.completionChannels[0] opc.completionChannels[0] <- suite } } close(toCompile) } func (opc *OrderedParallelCompiler) Next() (int, TestSuite) { if opc.idx >= opc.numSuites { return opc.numSuites, TestSuite{} } idx := opc.idx suite := <-opc.completionChannels[idx] opc.idx = opc.idx + 1 return idx, suite } func (opc *OrderedParallelCompiler) StopAndDrain() { opc.mutex.Lock() opc.stopped = true opc.mutex.Unlock() } golang-github-onsi-ginkgo-v2-2.22.0/ginkgo/internal/gocovmerge.go000066400000000000000000000106331472321612100246530ustar00rootroot00000000000000// Copyright (c) 2015, Wade Simmons // All rights reserved. // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are met: // 1. Redistributions of source code must retain the above copyright notice, this // list of conditions and the following disclaimer. // 2. Redistributions in binary form must reproduce the above copyright notice, // this list of conditions and the following disclaimer in the documentation // and/or other materials provided with the distribution. // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND // ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED // WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE // DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR // ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES // (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; // LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND // ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS // SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // Package gocovmerge takes the results from multiple `go test -coverprofile` // runs and merges them into one profile // this file was originally taken from the gocovmerge project // see also: https://go.shabbyrobe.org/gocovmerge package internal import ( "fmt" "io" "sort" "golang.org/x/tools/cover" ) func AddCoverProfile(profiles []*cover.Profile, p *cover.Profile) []*cover.Profile { i := sort.Search(len(profiles), func(i int) bool { return profiles[i].FileName >= p.FileName }) if i < len(profiles) && profiles[i].FileName == p.FileName { MergeCoverProfiles(profiles[i], p) } else { profiles = append(profiles, nil) copy(profiles[i+1:], profiles[i:]) profiles[i] = p } return profiles } func DumpCoverProfiles(profiles []*cover.Profile, out io.Writer) error { if len(profiles) == 0 { return nil } if _, err := fmt.Fprintf(out, "mode: %s\n", profiles[0].Mode); err != nil { return err } for _, p := range profiles { for _, b := range p.Blocks { if _, err := fmt.Fprintf(out, "%s:%d.%d,%d.%d %d %d\n", p.FileName, b.StartLine, b.StartCol, b.EndLine, b.EndCol, b.NumStmt, b.Count); err != nil { return err } } } return nil } func MergeCoverProfiles(into *cover.Profile, merge *cover.Profile) error { if into.Mode != merge.Mode { return fmt.Errorf("cannot merge profiles with different modes") } // Since the blocks are sorted, we can keep track of where the last block // was inserted and only look at the blocks after that as targets for merge startIndex := 0 for _, b := range merge.Blocks { var err error startIndex, err = mergeProfileBlock(into, b, startIndex) if err != nil { return err } } return nil } func mergeProfileBlock(p *cover.Profile, pb cover.ProfileBlock, startIndex int) (int, error) { sortFunc := func(i int) bool { pi := p.Blocks[i+startIndex] return pi.StartLine >= pb.StartLine && (pi.StartLine != pb.StartLine || pi.StartCol >= pb.StartCol) } i := 0 if sortFunc(i) != true { i = sort.Search(len(p.Blocks)-startIndex, sortFunc) } i += startIndex if i < len(p.Blocks) && p.Blocks[i].StartLine == pb.StartLine && p.Blocks[i].StartCol == pb.StartCol { if p.Blocks[i].EndLine != pb.EndLine || p.Blocks[i].EndCol != pb.EndCol { return i, fmt.Errorf("gocovmerge: overlapping merge %v %v %v", p.FileName, p.Blocks[i], pb) } switch p.Mode { case "set": p.Blocks[i].Count |= pb.Count case "count", "atomic": p.Blocks[i].Count += pb.Count default: return i, fmt.Errorf("gocovmerge: unsupported covermode '%s'", p.Mode) } } else { if i > 0 { pa := p.Blocks[i-1] if pa.EndLine >= pb.EndLine && (pa.EndLine != pb.EndLine || pa.EndCol > pb.EndCol) { return i, fmt.Errorf("gocovmerge: overlap before %v %v %v", p.FileName, pa, pb) } } if i < len(p.Blocks)-1 { pa := p.Blocks[i+1] if pa.StartLine <= pb.StartLine && (pa.StartLine != pb.StartLine || pa.StartCol < pb.StartCol) { return i, fmt.Errorf("gocovmerge: overlap after %v %v %v", p.FileName, pa, pb) } } p.Blocks = append(p.Blocks, cover.ProfileBlock{}) copy(p.Blocks[i+1:], p.Blocks[i:]) p.Blocks[i] = pb } return i + 1, nil } golang-github-onsi-ginkgo-v2-2.22.0/ginkgo/internal/profiles_and_reports.go000066400000000000000000000176131472321612100267460ustar00rootroot00000000000000package internal import ( "fmt" "os" "os/exec" "path/filepath" "regexp" "strconv" "github.com/google/pprof/profile" "github.com/onsi/ginkgo/v2/reporters" "github.com/onsi/ginkgo/v2/types" "golang.org/x/tools/cover" ) func AbsPathForGeneratedAsset(assetName string, suite TestSuite, cliConfig types.CLIConfig, process int) string { suffix := "" if process != 0 { suffix = fmt.Sprintf(".%d", process) } if cliConfig.OutputDir == "" { return filepath.Join(suite.AbsPath(), assetName+suffix) } outputDir, _ := filepath.Abs(cliConfig.OutputDir) return filepath.Join(outputDir, suite.NamespacedName()+"_"+assetName+suffix) } func FinalizeProfilesAndReportsForSuites(suites TestSuites, cliConfig types.CLIConfig, suiteConfig types.SuiteConfig, reporterConfig types.ReporterConfig, goFlagsConfig types.GoFlagsConfig) ([]string, error) { messages := []string{} suitesWithProfiles := suites.WithState(TestSuiteStatePassed, TestSuiteStateFailed) //anything else won't have actually run and generated a profile // merge cover profiles if need be if goFlagsConfig.Cover && !cliConfig.KeepSeparateCoverprofiles { coverProfiles := []string{} for _, suite := range suitesWithProfiles { if !suite.HasProgrammaticFocus { coverProfiles = append(coverProfiles, AbsPathForGeneratedAsset(goFlagsConfig.CoverProfile, suite, cliConfig, 0)) } } if len(coverProfiles) > 0 { dst := goFlagsConfig.CoverProfile if cliConfig.OutputDir != "" { dst = filepath.Join(cliConfig.OutputDir, goFlagsConfig.CoverProfile) } err := MergeAndCleanupCoverProfiles(coverProfiles, dst) if err != nil { return messages, err } coverage, err := GetCoverageFromCoverProfile(dst) if err != nil { return messages, err } if coverage == 0 { messages = append(messages, "composite coverage: [no statements]") } else if suitesWithProfiles.AnyHaveProgrammaticFocus() { messages = append(messages, fmt.Sprintf("composite coverage: %.1f%% of statements however some suites did not contribute because they included programatically focused specs", coverage)) } else { messages = append(messages, fmt.Sprintf("composite coverage: %.1f%% of statements", coverage)) } } else { messages = append(messages, "no composite coverage computed: all suites included programatically focused specs") } } // copy binaries if need be for _, suite := range suitesWithProfiles { if goFlagsConfig.BinaryMustBePreserved() && cliConfig.OutputDir != "" { src := suite.PathToCompiledTest dst := filepath.Join(cliConfig.OutputDir, suite.NamespacedName()+".test") if suite.Precompiled { if err := CopyFile(src, dst); err != nil { return messages, err } } else { if err := os.Rename(src, dst); err != nil { return messages, err } } } } type reportFormat struct { ReportName string GenerateFunc func(types.Report, string) error MergeFunc func([]string, string) ([]string, error) } reportFormats := []reportFormat{} if reporterConfig.JSONReport != "" { reportFormats = append(reportFormats, reportFormat{ReportName: reporterConfig.JSONReport, GenerateFunc: reporters.GenerateJSONReport, MergeFunc: reporters.MergeAndCleanupJSONReports}) } if reporterConfig.JUnitReport != "" { reportFormats = append(reportFormats, reportFormat{ReportName: reporterConfig.JUnitReport, GenerateFunc: reporters.GenerateJUnitReport, MergeFunc: reporters.MergeAndCleanupJUnitReports}) } if reporterConfig.TeamcityReport != "" { reportFormats = append(reportFormats, reportFormat{ReportName: reporterConfig.TeamcityReport, GenerateFunc: reporters.GenerateTeamcityReport, MergeFunc: reporters.MergeAndCleanupTeamcityReports}) } // Generate reports for suites that failed to run reportableSuites := suites.ThatAreGinkgoSuites() for _, suite := range reportableSuites.WithState(TestSuiteStateFailedToCompile, TestSuiteStateFailedDueToTimeout, TestSuiteStateSkippedDueToPriorFailures, TestSuiteStateSkippedDueToEmptyCompilation) { report := types.Report{ SuitePath: suite.AbsPath(), SuiteConfig: suiteConfig, SuiteSucceeded: false, } switch suite.State { case TestSuiteStateFailedToCompile: report.SpecialSuiteFailureReasons = append(report.SpecialSuiteFailureReasons, suite.CompilationError.Error()) case TestSuiteStateFailedDueToTimeout: report.SpecialSuiteFailureReasons = append(report.SpecialSuiteFailureReasons, TIMEOUT_ELAPSED_FAILURE_REASON) case TestSuiteStateSkippedDueToPriorFailures: report.SpecialSuiteFailureReasons = append(report.SpecialSuiteFailureReasons, PRIOR_FAILURES_FAILURE_REASON) case TestSuiteStateSkippedDueToEmptyCompilation: report.SpecialSuiteFailureReasons = append(report.SpecialSuiteFailureReasons, EMPTY_SKIP_FAILURE_REASON) report.SuiteSucceeded = true } for _, format := range reportFormats { format.GenerateFunc(report, AbsPathForGeneratedAsset(format.ReportName, suite, cliConfig, 0)) } } // Merge reports unless we've been asked to keep them separate if !cliConfig.KeepSeparateReports { for _, format := range reportFormats { reports := []string{} for _, suite := range reportableSuites { reports = append(reports, AbsPathForGeneratedAsset(format.ReportName, suite, cliConfig, 0)) } dst := format.ReportName if cliConfig.OutputDir != "" { dst = filepath.Join(cliConfig.OutputDir, format.ReportName) } mergeMessages, err := format.MergeFunc(reports, dst) messages = append(messages, mergeMessages...) if err != nil { return messages, err } } } return messages, nil } // loads each profile, merges them, deletes them, stores them in destination func MergeAndCleanupCoverProfiles(profiles []string, destination string) error { var merged []*cover.Profile for _, file := range profiles { parsedProfiles, err := cover.ParseProfiles(file) if err != nil { return err } os.Remove(file) for _, p := range parsedProfiles { merged = AddCoverProfile(merged, p) } } dst, err := os.OpenFile(destination, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0666) if err != nil { return err } defer dst.Close() err = DumpCoverProfiles(merged, dst) if err != nil { return err } return nil } func GetCoverageFromCoverProfile(profile string) (float64, error) { cmd := exec.Command("go", "tool", "cover", "-func", profile) output, err := cmd.CombinedOutput() if err != nil { return 0, fmt.Errorf("Could not process Coverprofile %s: %s - %s", profile, err.Error(), string(output)) } re := regexp.MustCompile(`total:\s*\(statements\)\s*(\d*\.\d*)\%`) matches := re.FindStringSubmatch(string(output)) if matches == nil { return 0, fmt.Errorf("Could not parse Coverprofile to compute coverage percentage") } coverageString := matches[1] coverage, err := strconv.ParseFloat(coverageString, 64) if err != nil { return 0, fmt.Errorf("Could not parse Coverprofile to compute coverage percentage: %s", err.Error()) } return coverage, nil } func MergeProfiles(profilePaths []string, destination string) error { profiles := []*profile.Profile{} for _, profilePath := range profilePaths { proFile, err := os.Open(profilePath) if err != nil { return fmt.Errorf("Could not open profile: %s\n%s", profilePath, err.Error()) } prof, err := profile.Parse(proFile) _ = proFile.Close() if err != nil { return fmt.Errorf("Could not parse profile: %s\n%s", profilePath, err.Error()) } profiles = append(profiles, prof) os.Remove(profilePath) } mergedProfile, err := profile.Merge(profiles) if err != nil { return fmt.Errorf("Could not merge profiles:\n%s", err.Error()) } outFile, err := os.Create(destination) if err != nil { return fmt.Errorf("Could not create merged profile %s:\n%s", destination, err.Error()) } err = mergedProfile.Write(outFile) if err != nil { return fmt.Errorf("Could not write merged profile %s:\n%s", destination, err.Error()) } err = outFile.Close() if err != nil { return fmt.Errorf("Could not close merged profile %s:\n%s", destination, err.Error()) } return nil } golang-github-onsi-ginkgo-v2-2.22.0/ginkgo/internal/run.go000066400000000000000000000340671472321612100233310ustar00rootroot00000000000000package internal import ( "bytes" "fmt" "io" "os" "os/exec" "path/filepath" "regexp" "strings" "syscall" "time" "github.com/onsi/ginkgo/v2/formatter" "github.com/onsi/ginkgo/v2/ginkgo/command" "github.com/onsi/ginkgo/v2/internal/parallel_support" "github.com/onsi/ginkgo/v2/reporters" "github.com/onsi/ginkgo/v2/types" ) func RunCompiledSuite(suite TestSuite, ginkgoConfig types.SuiteConfig, reporterConfig types.ReporterConfig, cliConfig types.CLIConfig, goFlagsConfig types.GoFlagsConfig, additionalArgs []string) TestSuite { suite.State = TestSuiteStateFailed suite.HasProgrammaticFocus = false if suite.PathToCompiledTest == "" { return suite } if suite.IsGinkgo && cliConfig.ComputedProcs() > 1 { suite = runParallel(suite, ginkgoConfig, reporterConfig, cliConfig, goFlagsConfig, additionalArgs) } else if suite.IsGinkgo { suite = runSerial(suite, ginkgoConfig, reporterConfig, cliConfig, goFlagsConfig, additionalArgs) } else { suite = runGoTest(suite, cliConfig, goFlagsConfig) } runAfterRunHook(cliConfig.AfterRunHook, reporterConfig.NoColor, suite) return suite } func buildAndStartCommand(suite TestSuite, args []string, pipeToStdout bool) (*exec.Cmd, *bytes.Buffer) { buf := &bytes.Buffer{} cmd := exec.Command(suite.PathToCompiledTest, args...) cmd.Dir = suite.Path if pipeToStdout { cmd.Stderr = io.MultiWriter(os.Stdout, buf) cmd.Stdout = os.Stdout } else { cmd.Stderr = buf cmd.Stdout = buf } err := cmd.Start() command.AbortIfError("Failed to start test suite", err) return cmd, buf } func checkForNoTestsWarning(buf *bytes.Buffer) bool { if strings.Contains(buf.String(), "warning: no tests to run") { fmt.Fprintf(os.Stderr, `Found no test suites, did you forget to run "ginkgo bootstrap"?`) return true } return false } func runGoTest(suite TestSuite, cliConfig types.CLIConfig, goFlagsConfig types.GoFlagsConfig) TestSuite { // As we run the go test from the suite directory, make sure the cover profile is absolute // and placed into the expected output directory when one is configured. if goFlagsConfig.Cover && !filepath.IsAbs(goFlagsConfig.CoverProfile) { goFlagsConfig.CoverProfile = AbsPathForGeneratedAsset(goFlagsConfig.CoverProfile, suite, cliConfig, 0) } args, err := types.GenerateGoTestRunArgs(goFlagsConfig) command.AbortIfError("Failed to generate test run arguments", err) cmd, buf := buildAndStartCommand(suite, args, true) cmd.Wait() exitStatus := cmd.ProcessState.Sys().(syscall.WaitStatus).ExitStatus() passed := (exitStatus == 0) || (exitStatus == types.GINKGO_FOCUS_EXIT_CODE) passed = !(checkForNoTestsWarning(buf) && cliConfig.RequireSuite) && passed if passed { suite.State = TestSuiteStatePassed } else { suite.State = TestSuiteStateFailed } return suite } func runSerial(suite TestSuite, ginkgoConfig types.SuiteConfig, reporterConfig types.ReporterConfig, cliConfig types.CLIConfig, goFlagsConfig types.GoFlagsConfig, additionalArgs []string) TestSuite { if goFlagsConfig.Cover { goFlagsConfig.CoverProfile = AbsPathForGeneratedAsset(goFlagsConfig.CoverProfile, suite, cliConfig, 0) } if goFlagsConfig.BlockProfile != "" { goFlagsConfig.BlockProfile = AbsPathForGeneratedAsset(goFlagsConfig.BlockProfile, suite, cliConfig, 0) } if goFlagsConfig.CPUProfile != "" { goFlagsConfig.CPUProfile = AbsPathForGeneratedAsset(goFlagsConfig.CPUProfile, suite, cliConfig, 0) } if goFlagsConfig.MemProfile != "" { goFlagsConfig.MemProfile = AbsPathForGeneratedAsset(goFlagsConfig.MemProfile, suite, cliConfig, 0) } if goFlagsConfig.MutexProfile != "" { goFlagsConfig.MutexProfile = AbsPathForGeneratedAsset(goFlagsConfig.MutexProfile, suite, cliConfig, 0) } if reporterConfig.JSONReport != "" { reporterConfig.JSONReport = AbsPathForGeneratedAsset(reporterConfig.JSONReport, suite, cliConfig, 0) } if reporterConfig.JUnitReport != "" { reporterConfig.JUnitReport = AbsPathForGeneratedAsset(reporterConfig.JUnitReport, suite, cliConfig, 0) } if reporterConfig.TeamcityReport != "" { reporterConfig.TeamcityReport = AbsPathForGeneratedAsset(reporterConfig.TeamcityReport, suite, cliConfig, 0) } args, err := types.GenerateGinkgoTestRunArgs(ginkgoConfig, reporterConfig, goFlagsConfig) command.AbortIfError("Failed to generate test run arguments", err) args = append([]string{"--test.timeout=0"}, args...) args = append(args, additionalArgs...) cmd, buf := buildAndStartCommand(suite, args, true) cmd.Wait() exitStatus := cmd.ProcessState.Sys().(syscall.WaitStatus).ExitStatus() suite.HasProgrammaticFocus = (exitStatus == types.GINKGO_FOCUS_EXIT_CODE) passed := (exitStatus == 0) || (exitStatus == types.GINKGO_FOCUS_EXIT_CODE) passed = !(checkForNoTestsWarning(buf) && cliConfig.RequireSuite) && passed if passed { suite.State = TestSuiteStatePassed } else { suite.State = TestSuiteStateFailed } if suite.HasProgrammaticFocus { if goFlagsConfig.Cover { fmt.Fprintln(os.Stdout, "coverage: no coverfile was generated because specs are programmatically focused") } if goFlagsConfig.BlockProfile != "" { fmt.Fprintln(os.Stdout, "no block profile was generated because specs are programmatically focused") } if goFlagsConfig.CPUProfile != "" { fmt.Fprintln(os.Stdout, "no cpu profile was generated because specs are programmatically focused") } if goFlagsConfig.MemProfile != "" { fmt.Fprintln(os.Stdout, "no mem profile was generated because specs are programmatically focused") } if goFlagsConfig.MutexProfile != "" { fmt.Fprintln(os.Stdout, "no mutex profile was generated because specs are programmatically focused") } } return suite } func runParallel(suite TestSuite, ginkgoConfig types.SuiteConfig, reporterConfig types.ReporterConfig, cliConfig types.CLIConfig, goFlagsConfig types.GoFlagsConfig, additionalArgs []string) TestSuite { type procResult struct { passed bool hasProgrammaticFocus bool } numProcs := cliConfig.ComputedProcs() procOutput := make([]*bytes.Buffer, numProcs) coverProfiles := []string{} blockProfiles := []string{} cpuProfiles := []string{} memProfiles := []string{} mutexProfiles := []string{} procResults := make(chan procResult) server, err := parallel_support.NewServer(numProcs, reporters.NewDefaultReporter(reporterConfig, formatter.ColorableStdOut)) command.AbortIfError("Failed to start parallel spec server", err) server.Start() defer server.Close() if reporterConfig.JSONReport != "" { reporterConfig.JSONReport = AbsPathForGeneratedAsset(reporterConfig.JSONReport, suite, cliConfig, 0) } if reporterConfig.JUnitReport != "" { reporterConfig.JUnitReport = AbsPathForGeneratedAsset(reporterConfig.JUnitReport, suite, cliConfig, 0) } if reporterConfig.TeamcityReport != "" { reporterConfig.TeamcityReport = AbsPathForGeneratedAsset(reporterConfig.TeamcityReport, suite, cliConfig, 0) } for proc := 1; proc <= numProcs; proc++ { procGinkgoConfig := ginkgoConfig procGinkgoConfig.ParallelProcess, procGinkgoConfig.ParallelTotal, procGinkgoConfig.ParallelHost = proc, numProcs, server.Address() procGoFlagsConfig := goFlagsConfig if goFlagsConfig.Cover { procGoFlagsConfig.CoverProfile = AbsPathForGeneratedAsset(goFlagsConfig.CoverProfile, suite, cliConfig, proc) coverProfiles = append(coverProfiles, procGoFlagsConfig.CoverProfile) } if goFlagsConfig.BlockProfile != "" { procGoFlagsConfig.BlockProfile = AbsPathForGeneratedAsset(goFlagsConfig.BlockProfile, suite, cliConfig, proc) blockProfiles = append(blockProfiles, procGoFlagsConfig.BlockProfile) } if goFlagsConfig.CPUProfile != "" { procGoFlagsConfig.CPUProfile = AbsPathForGeneratedAsset(goFlagsConfig.CPUProfile, suite, cliConfig, proc) cpuProfiles = append(cpuProfiles, procGoFlagsConfig.CPUProfile) } if goFlagsConfig.MemProfile != "" { procGoFlagsConfig.MemProfile = AbsPathForGeneratedAsset(goFlagsConfig.MemProfile, suite, cliConfig, proc) memProfiles = append(memProfiles, procGoFlagsConfig.MemProfile) } if goFlagsConfig.MutexProfile != "" { procGoFlagsConfig.MutexProfile = AbsPathForGeneratedAsset(goFlagsConfig.MutexProfile, suite, cliConfig, proc) mutexProfiles = append(mutexProfiles, procGoFlagsConfig.MutexProfile) } args, err := types.GenerateGinkgoTestRunArgs(procGinkgoConfig, reporterConfig, procGoFlagsConfig) command.AbortIfError("Failed to generate test run arguments", err) args = append([]string{"--test.timeout=0"}, args...) args = append(args, additionalArgs...) cmd, buf := buildAndStartCommand(suite, args, false) procOutput[proc-1] = buf server.RegisterAlive(proc, func() bool { return cmd.ProcessState == nil || !cmd.ProcessState.Exited() }) go func() { cmd.Wait() exitStatus := cmd.ProcessState.Sys().(syscall.WaitStatus).ExitStatus() procResults <- procResult{ passed: (exitStatus == 0) || (exitStatus == types.GINKGO_FOCUS_EXIT_CODE), hasProgrammaticFocus: exitStatus == types.GINKGO_FOCUS_EXIT_CODE, } }() } passed := true for proc := 1; proc <= cliConfig.ComputedProcs(); proc++ { result := <-procResults passed = passed && result.passed suite.HasProgrammaticFocus = suite.HasProgrammaticFocus || result.hasProgrammaticFocus } if passed { suite.State = TestSuiteStatePassed } else { suite.State = TestSuiteStateFailed } select { case <-server.GetSuiteDone(): fmt.Println("") case <-time.After(time.Second): //one of the nodes never finished reporting to the server. Something must have gone wrong. fmt.Fprint(formatter.ColorableStdErr, formatter.F("\n{{bold}}{{red}}Ginkgo timed out waiting for all parallel procs to report back{{/}}\n")) fmt.Fprint(formatter.ColorableStdErr, formatter.F("{{gray}}Test suite:{{/}} %s (%s)\n\n", suite.PackageName, suite.Path)) fmt.Fprint(formatter.ColorableStdErr, formatter.Fiw(0, formatter.COLS, "This occurs if a parallel process exits before it reports its results to the Ginkgo CLI. The CLI will now print out all the stdout/stderr output it's collected from the running processes. However you may not see anything useful in these logs because the individual test processes usually intercept output to stdout/stderr in order to capture it in the spec reports.\n\nYou may want to try rerunning your test suite with {{light-gray}}--output-interceptor-mode=none{{/}} to see additional output here and debug your suite.\n")) fmt.Fprintln(formatter.ColorableStdErr, " ") for proc := 1; proc <= cliConfig.ComputedProcs(); proc++ { fmt.Fprintf(formatter.ColorableStdErr, formatter.F("{{bold}}Output from proc %d:{{/}}\n", proc)) fmt.Fprintln(os.Stderr, formatter.Fi(1, "%s", procOutput[proc-1].String())) } fmt.Fprintf(os.Stderr, "** End **") } for proc := 1; proc <= cliConfig.ComputedProcs(); proc++ { output := procOutput[proc-1].String() if proc == 1 && checkForNoTestsWarning(procOutput[0]) && cliConfig.RequireSuite { suite.State = TestSuiteStateFailed } if strings.Contains(output, "deprecated Ginkgo functionality") { fmt.Fprintln(os.Stderr, output) } } if len(coverProfiles) > 0 { if suite.HasProgrammaticFocus { fmt.Fprintln(os.Stdout, "coverage: no coverfile was generated because specs are programmatically focused") } else { coverProfile := AbsPathForGeneratedAsset(goFlagsConfig.CoverProfile, suite, cliConfig, 0) err := MergeAndCleanupCoverProfiles(coverProfiles, coverProfile) command.AbortIfError("Failed to combine cover profiles", err) coverage, err := GetCoverageFromCoverProfile(coverProfile) command.AbortIfError("Failed to compute coverage", err) if coverage == 0 { fmt.Fprintln(os.Stdout, "coverage: [no statements]") } else { fmt.Fprintf(os.Stdout, "coverage: %.1f%% of statements\n", coverage) } } } if len(blockProfiles) > 0 { if suite.HasProgrammaticFocus { fmt.Fprintln(os.Stdout, "no block profile was generated because specs are programmatically focused") } else { blockProfile := AbsPathForGeneratedAsset(goFlagsConfig.BlockProfile, suite, cliConfig, 0) err := MergeProfiles(blockProfiles, blockProfile) command.AbortIfError("Failed to combine blockprofiles", err) } } if len(cpuProfiles) > 0 { if suite.HasProgrammaticFocus { fmt.Fprintln(os.Stdout, "no cpu profile was generated because specs are programmatically focused") } else { cpuProfile := AbsPathForGeneratedAsset(goFlagsConfig.CPUProfile, suite, cliConfig, 0) err := MergeProfiles(cpuProfiles, cpuProfile) command.AbortIfError("Failed to combine cpuprofiles", err) } } if len(memProfiles) > 0 { if suite.HasProgrammaticFocus { fmt.Fprintln(os.Stdout, "no mem profile was generated because specs are programmatically focused") } else { memProfile := AbsPathForGeneratedAsset(goFlagsConfig.MemProfile, suite, cliConfig, 0) err := MergeProfiles(memProfiles, memProfile) command.AbortIfError("Failed to combine memprofiles", err) } } if len(mutexProfiles) > 0 { if suite.HasProgrammaticFocus { fmt.Fprintln(os.Stdout, "no mutex profile was generated because specs are programmatically focused") } else { mutexProfile := AbsPathForGeneratedAsset(goFlagsConfig.MutexProfile, suite, cliConfig, 0) err := MergeProfiles(mutexProfiles, mutexProfile) command.AbortIfError("Failed to combine mutexprofiles", err) } } return suite } func runAfterRunHook(command string, noColor bool, suite TestSuite) { if command == "" { return } f := formatter.NewWithNoColorBool(noColor) // Allow for string replacement to pass input to the command passed := "[FAIL]" if suite.State.Is(TestSuiteStatePassed) { passed = "[PASS]" } command = strings.ReplaceAll(command, "(ginkgo-suite-passed)", passed) command = strings.ReplaceAll(command, "(ginkgo-suite-name)", suite.PackageName) // Must break command into parts splitArgs := regexp.MustCompile(`'.+'|".+"|\S+`) parts := splitArgs.FindAllString(command, -1) output, err := exec.Command(parts[0], parts[1:]...).CombinedOutput() if err != nil { fmt.Fprintln(formatter.ColorableStdOut, f.Fi(0, "{{red}}{{bold}}After-run-hook failed:{{/}}")) fmt.Fprintln(formatter.ColorableStdOut, f.Fi(1, "{{red}}%s{{/}}", output)) } else { fmt.Fprintln(formatter.ColorableStdOut, f.Fi(0, "{{green}}{{bold}}After-run-hook succeeded:{{/}}")) fmt.Fprintln(formatter.ColorableStdOut, f.Fi(1, "{{green}}%s{{/}}", output)) } } golang-github-onsi-ginkgo-v2-2.22.0/ginkgo/internal/test_suite.go000066400000000000000000000143161472321612100247100ustar00rootroot00000000000000package internal import ( "errors" "math/rand" "os" "path" "path/filepath" "regexp" "runtime" "strings" "github.com/onsi/ginkgo/v2/types" ) const TIMEOUT_ELAPSED_FAILURE_REASON = "Suite did not run because the timeout elapsed" const PRIOR_FAILURES_FAILURE_REASON = "Suite did not run because prior suites failed and --keep-going is not set" const EMPTY_SKIP_FAILURE_REASON = "Suite did not run go test reported that no test files were found" type TestSuiteState uint const ( TestSuiteStateInvalid TestSuiteState = iota TestSuiteStateUncompiled TestSuiteStateCompiled TestSuiteStatePassed TestSuiteStateSkippedDueToEmptyCompilation TestSuiteStateSkippedByFilter TestSuiteStateSkippedDueToPriorFailures TestSuiteStateFailed TestSuiteStateFailedDueToTimeout TestSuiteStateFailedToCompile ) var TestSuiteStateFailureStates = []TestSuiteState{TestSuiteStateFailed, TestSuiteStateFailedDueToTimeout, TestSuiteStateFailedToCompile} func (state TestSuiteState) Is(states ...TestSuiteState) bool { for _, suiteState := range states { if suiteState == state { return true } } return false } type TestSuite struct { Path string PackageName string IsGinkgo bool Precompiled bool PathToCompiledTest string CompilationError error HasProgrammaticFocus bool State TestSuiteState } func (ts TestSuite) AbsPath() string { path, _ := filepath.Abs(ts.Path) return path } func (ts TestSuite) NamespacedName() string { name := relPath(ts.Path) name = strings.TrimLeft(name, "."+string(filepath.Separator)) name = strings.ReplaceAll(name, string(filepath.Separator), "_") name = strings.ReplaceAll(name, " ", "_") if name == "" { return ts.PackageName } return name } type TestSuites []TestSuite func (ts TestSuites) AnyHaveProgrammaticFocus() bool { for _, suite := range ts { if suite.HasProgrammaticFocus { return true } } return false } func (ts TestSuites) ThatAreGinkgoSuites() TestSuites { out := TestSuites{} for _, suite := range ts { if suite.IsGinkgo { out = append(out, suite) } } return out } func (ts TestSuites) CountWithState(states ...TestSuiteState) int { n := 0 for _, suite := range ts { if suite.State.Is(states...) { n += 1 } } return n } func (ts TestSuites) WithState(states ...TestSuiteState) TestSuites { out := TestSuites{} for _, suite := range ts { if suite.State.Is(states...) { out = append(out, suite) } } return out } func (ts TestSuites) WithoutState(states ...TestSuiteState) TestSuites { out := TestSuites{} for _, suite := range ts { if !suite.State.Is(states...) { out = append(out, suite) } } return out } func (ts TestSuites) ShuffledCopy(seed int64) TestSuites { out := make(TestSuites, len(ts)) permutation := rand.New(rand.NewSource(seed)).Perm(len(ts)) for i, j := range permutation { out[i] = ts[j] } return out } func FindSuites(args []string, cliConfig types.CLIConfig, allowPrecompiled bool) TestSuites { suites := TestSuites{} if len(args) > 0 { for _, arg := range args { if allowPrecompiled { suite, err := precompiledTestSuite(arg) if err == nil { suites = append(suites, suite) continue } } recurseForSuite := cliConfig.Recurse if strings.HasSuffix(arg, "/...") && arg != "/..." { arg = arg[:len(arg)-4] recurseForSuite = true } suites = append(suites, suitesInDir(arg, recurseForSuite)...) } } else { suites = suitesInDir(".", cliConfig.Recurse) } if cliConfig.SkipPackage != "" { skipFilters := strings.Split(cliConfig.SkipPackage, ",") for idx := range suites { for _, skipFilter := range skipFilters { if strings.Contains(suites[idx].Path, skipFilter) { suites[idx].State = TestSuiteStateSkippedByFilter break } } } } return suites } func precompiledTestSuite(path string) (TestSuite, error) { info, err := os.Stat(path) if err != nil { return TestSuite{}, err } if info.IsDir() { return TestSuite{}, errors.New("this is a directory, not a file") } if filepath.Ext(path) != ".test" && filepath.Ext(path) != ".exe" { return TestSuite{}, errors.New("this is not a .test binary") } if filepath.Ext(path) == ".test" && runtime.GOOS != "windows" && info.Mode()&0111 == 0 { return TestSuite{}, errors.New("this is not executable") } dir := relPath(filepath.Dir(path)) packageName := strings.TrimSuffix(filepath.Base(path), ".exe") packageName = strings.TrimSuffix(packageName, ".test") path, err = filepath.Abs(path) if err != nil { return TestSuite{}, err } return TestSuite{ Path: dir, PackageName: packageName, IsGinkgo: true, Precompiled: true, PathToCompiledTest: path, State: TestSuiteStateCompiled, }, nil } func suitesInDir(dir string, recurse bool) TestSuites { suites := TestSuites{} if path.Base(dir) == "vendor" { return suites } files, _ := os.ReadDir(dir) re := regexp.MustCompile(`^[^._].*_test\.go$`) for _, file := range files { if !file.IsDir() && re.MatchString(file.Name()) { suite := TestSuite{ Path: relPath(dir), PackageName: packageNameForSuite(dir), IsGinkgo: filesHaveGinkgoSuite(dir, files), State: TestSuiteStateUncompiled, } suites = append(suites, suite) break } } if recurse { re = regexp.MustCompile(`^[._]`) for _, file := range files { if file.IsDir() && !re.MatchString(file.Name()) { suites = append(suites, suitesInDir(dir+"/"+file.Name(), recurse)...) } } } return suites } func relPath(dir string) string { dir, _ = filepath.Abs(dir) cwd, _ := os.Getwd() dir, _ = filepath.Rel(cwd, filepath.Clean(dir)) if string(dir[0]) != "." { dir = "." + string(filepath.Separator) + dir } return dir } func packageNameForSuite(dir string) string { path, _ := filepath.Abs(dir) return filepath.Base(path) } func filesHaveGinkgoSuite(dir string, files []os.DirEntry) bool { reTestFile := regexp.MustCompile(`_test\.go$`) reGinkgo := regexp.MustCompile(`package ginkgo|\/ginkgo"|\/ginkgo\/v2"|\/ginkgo\/v2/dsl/`) for _, file := range files { if !file.IsDir() && reTestFile.MatchString(file.Name()) { contents, _ := os.ReadFile(dir + "/" + file.Name()) if reGinkgo.Match(contents) { return true } } } return false } golang-github-onsi-ginkgo-v2-2.22.0/ginkgo/internal/testsuite_test.go000066400000000000000000000343711472321612100256130ustar00rootroot00000000000000package internal_test import ( "os" "path/filepath" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/ginkgo/v2/ginkgo/internal" "github.com/onsi/ginkgo/v2/types" . "github.com/onsi/gomega" ) func TS(path string, pkgName string, isGinkgo bool, state TestSuiteState) TestSuite { return TestSuite{ Path: path, PackageName: pkgName, IsGinkgo: isGinkgo, Precompiled: false, State: state, } } func PTS(path string, pkgName string, isGinkgo bool, pathToCompiledTest string, state TestSuiteState) TestSuite { return TestSuite{ Path: path, PackageName: pkgName, IsGinkgo: isGinkgo, Precompiled: true, PathToCompiledTest: pathToCompiledTest, State: state, } } var _ = Describe("TestSuite", func() { Describe("Finding Suites", func() { var tmpDir string var origWd string var cliConf types.CLIConfig writeFile := func(folder string, filename string, content string, mode os.FileMode) { path := filepath.Join(tmpDir, folder) err := os.MkdirAll(path, 0700) Ω(err).ShouldNot(HaveOccurred()) path = filepath.Join(path, filename) os.WriteFile(path, []byte(content), mode) } BeforeEach(func() { cliConf = types.CLIConfig{} var err error tmpDir, err = os.MkdirTemp("/tmp", "ginkgo") Ω(err).ShouldNot(HaveOccurred()) origWd, err = os.Getwd() Ω(err).ShouldNot(HaveOccurred()) Ω(os.Chdir(tmpDir)).Should(Succeed()) //go files in the root directory (no tests) writeFile("/", "main.go", "package main", 0666) //non-go files in a nested directory writeFile("/redherring", "big_test.jpg", "package ginkgo", 0666) //ginkgo tests in ignored go files writeFile("/ignored", ".ignore_dot_test.go", `import "github.com/onsi/ginkgo/v2"`, 0666) writeFile("/ignored", "_ignore_underscore_test.go", `import "github.com/onsi/ginkgo/v2"`, 0666) //non-ginkgo tests in a nested directory writeFile("/professorplum", "professorplum_test.go", `import "testing"`, 0666) //ginkgo tests in a nested directory writeFile("/colonelmustard", "colonelmustard_test.go", `import "github.com/onsi/ginkgo/v2"`, 0666) //ginkgo tests in a deeply nested directory writeFile("/colonelmustard/library", "library_test.go", `import "github.com/onsi/ginkgo/v2"`, 0666) //ginkgo tests in a deeply nested directory writeFile("/colonelmustard/library/spanner", "spanner_test.go", `import "github.com/onsi/ginkgo/v2/dsl/core"`, 0666) //ginkgo tests deeply nested in a vendored dependency writeFile("/vendor/mrspeacock/lounge", "lounge_test.go", `import "github.com/onsi/ginkgo/v2"`, 0666) //a precompiled ginkgo test writeFile("/precompiled-dir", "precompiled.test", `fake-binary-file`, 0777) writeFile("/precompiled-dir", "some-other-binary", `fake-binary-file`, 0777) writeFile("/precompiled-dir", "windows.test.exe", `fake-binary-file`, 0666) writeFile("/precompiled-dir", "windows.exe", `fake-binary-file`, 0666) writeFile("/precompiled-dir", "nonexecutable.test", `fake-binary-file`, 0666) }) AfterEach(func() { Ω(os.Chdir(origWd)).Should(Succeed()) os.RemoveAll(tmpDir) }) Context("when passed no args", func() { Context("when told to recurse", func() { BeforeEach(func() { cliConf.Recurse = true }) It("recurses through the current directory, returning all identified tests and skipping vendored, ignored, and precompiled tests", func() { suites := FindSuites([]string{}, cliConf, false) Ω(suites).Should(ConsistOf( TS("./professorplum", "professorplum", false, TestSuiteStateUncompiled), TS("./colonelmustard", "colonelmustard", true, TestSuiteStateUncompiled), TS("./colonelmustard/library", "library", true, TestSuiteStateUncompiled), TS("./colonelmustard/library/spanner", "spanner", true, TestSuiteStateUncompiled), )) }) }) Context("when told to recurse and there is a skip-package filter", func() { BeforeEach(func() { cliConf.Recurse = true cliConf.SkipPackage = "professorplum,library,floop" }) It("recurses through the current directory, returning all identified tests and skipping vendored, ignored, and precompiled tests", func() { suites := FindSuites([]string{}, cliConf, false) Ω(suites).Should(ConsistOf( TS("./professorplum", "professorplum", false, TestSuiteStateSkippedByFilter), TS("./colonelmustard", "colonelmustard", true, TestSuiteStateUncompiled), TS("./colonelmustard/library", "library", true, TestSuiteStateSkippedByFilter), TS("./colonelmustard/library/spanner", "spanner", true, TestSuiteStateSkippedByFilter), )) }) }) Context("when there are no tests in the current directory", func() { BeforeEach(func() { cliConf.Recurse = false }) It("returns empty", func() { suites := FindSuites([]string{}, cliConf, false) Ω(suites).Should(BeEmpty()) }) }) Context("when told not to recurse", func() { BeforeEach(func() { Ω(os.Chdir("./colonelmustard")).Should(Succeed()) }) It("returns tests in the current directory if present", func() { suites := FindSuites([]string{}, cliConf, false) Ω(suites).Should(ConsistOf( TS(".", "colonelmustard", true, TestSuiteStateUncompiled), )) }) }) }) Context("when passed args", func() { Context("when told to recurse", func() { BeforeEach(func() { cliConf.Recurse = true }) It("recurses through the passed-in directories, returning all identified tests and skipping vendored, ignored, and precompiled tests", func() { suites := FindSuites([]string{"precompiled-dir", "colonelmustard"}, cliConf, false) Ω(suites).Should(ConsistOf( TS("./colonelmustard", "colonelmustard", true, TestSuiteStateUncompiled), TS("./colonelmustard/library", "library", true, TestSuiteStateUncompiled), TS("./colonelmustard/library/spanner", "spanner", true, TestSuiteStateUncompiled), )) }) }) Context("when told to recurse and there is a skip-package filter", func() { BeforeEach(func() { cliConf.Recurse = true cliConf.SkipPackage = "library" }) It("recurses through the passed-in directories, returning all identified tests and skipping vendored, ignored, and precompiled tests", func() { suites := FindSuites([]string{"precompiled-dir", "professorplum", "colonelmustard"}, cliConf, false) Ω(suites).Should(ConsistOf( TS("./professorplum", "professorplum", false, TestSuiteStateUncompiled), TS("./colonelmustard", "colonelmustard", true, TestSuiteStateUncompiled), TS("./colonelmustard/library", "library", true, TestSuiteStateSkippedByFilter), TS("./colonelmustard/library/spanner", "spanner", true, TestSuiteStateSkippedByFilter), )) }) }) Context("when told not to recurse", func() { BeforeEach(func() { cliConf.Recurse = false }) It("returns test packages at the passed in arguments", func() { suites := FindSuites([]string{"precompiled-dir", "colonelmustard", "professorplum", "ignored"}, cliConf, false) Ω(suites).Should(ConsistOf( TS("./professorplum", "professorplum", false, TestSuiteStateUncompiled), TS("./colonelmustard", "colonelmustard", true, TestSuiteStateUncompiled), )) }) }) Context("when told not to recurse, but an arg has /...", func() { BeforeEach(func() { cliConf.Recurse = false }) It("recurses through the directories it is told to recurse through, returning all identified tests and skipping vendored, ignored, and precompiled tests", func() { suites := FindSuites([]string{"precompiled-dir", "colonelmustard/...", "professorplum/...", "ignored/..."}, cliConf, false) Ω(suites).Should(ConsistOf( TS("./professorplum", "professorplum", false, TestSuiteStateUncompiled), TS("./colonelmustard", "colonelmustard", true, TestSuiteStateUncompiled), TS("./colonelmustard/library", "library", true, TestSuiteStateUncompiled), TS("./colonelmustard/library/spanner", "spanner", true, TestSuiteStateUncompiled), )) }) }) Context("when told not to recurse and there is a skip-package filter", func() { BeforeEach(func() { cliConf.Recurse = false cliConf.SkipPackage = "library,plum" }) It("returns skips packages that match", func() { suites := FindSuites([]string{"colonelmustard", "professorplum", "colonelmustard/library"}, cliConf, false) Ω(suites).Should(ConsistOf( TS("./professorplum", "professorplum", false, TestSuiteStateSkippedByFilter), TS("./colonelmustard", "colonelmustard", true, TestSuiteStateUncompiled), TS("./colonelmustard/library", "library", true, TestSuiteStateSkippedByFilter), )) }) }) Context("when pointed at a directory containing a precompiled test suite", func() { It("returns nothing", func() { suites := FindSuites([]string{"precompiled-dir"}, cliConf, false) Ω(suites).Should(BeEmpty()) }) }) Context("when pointed at a precompiled test suite specifically", func() { It("returns the precompiled suite", func() { path, err := filepath.Abs("./precompiled-dir/precompiled.test") Ω(err).ShouldNot(HaveOccurred()) suites := FindSuites([]string{"precompiled-dir/precompiled.test"}, cliConf, true) Ω(suites).Should(ConsistOf( PTS("./precompiled-dir", "precompiled", true, path, TestSuiteStateCompiled), )) }) }) Context("when pointed at a precompiled test suite on windows", func() { It("returns the precompiled suite", func() { path, err := filepath.Abs("./precompiled-dir/windows.exe") Ω(err).ShouldNot(HaveOccurred()) suites := FindSuites([]string{"precompiled-dir/windows.exe"}, cliConf, true) Ω(suites).Should(ConsistOf( PTS("./precompiled-dir", "windows", true, path, TestSuiteStateCompiled), )) path, err = filepath.Abs("./precompiled-dir/windows.test.exe") Ω(err).ShouldNot(HaveOccurred()) suites = FindSuites([]string{"precompiled-dir/windows.test.exe"}, cliConf, true) Ω(suites).Should(ConsistOf( PTS("./precompiled-dir", "windows", true, path, TestSuiteStateCompiled), )) }) }) Context("when pointed at a fake precompiled test", func() { It("returns nothing", func() { suites := FindSuites([]string{"precompiled-dir/some-other-binary"}, cliConf, true) Ω(suites).Should(BeEmpty()) suites = FindSuites([]string{"precompiled-dir/nonexecutable.test"}, cliConf, true) Ω(suites).Should(BeEmpty()) }) }) Context("when pointed at a precompiled test suite specifically but allowPrecompiled is false", func() { It("returns nothing", func() { suites := FindSuites([]string{"precompiled-dir/some-other-binary"}, cliConf, false) Ω(suites).Should(BeEmpty()) }) }) }) }) Describe("NamespacedName", func() { It("generates a name basd on the relative path to the package", func() { plum := TS("./professorplum", "professorplum", false, TestSuiteStateUncompiled) library := TS("./colonelmustard/library", "library", true, TestSuiteStateUncompiled) root := TS(".", "root", true, TestSuiteStateUncompiled) Ω(plum.NamespacedName()).Should(Equal("professorplum")) Ω(library.NamespacedName()).Should(Equal("colonelmustard_library")) Ω(root.NamespacedName()).Should(Equal("root")) }) }) Describe("TestSuiteState", func() { Describe("Is", func() { It("returns true if it matches one of the passed in states", func() { Ω(TestSuiteStateCompiled.Is(TestSuiteStateUncompiled, TestSuiteStateCompiled)).Should(BeTrue()) Ω(TestSuiteStateCompiled.Is(TestSuiteStateUncompiled, TestSuiteStatePassed)).Should(BeFalse()) }) }) Describe("TestSuiteStateFailureStates", func() { It("should enumerate the failure states", func() { Ω(TestSuiteStateFailureStates).Should(ConsistOf( TestSuiteStateFailed, TestSuiteStateFailedDueToTimeout, TestSuiteStateFailedToCompile, )) }) }) }) Describe("TestSuites", func() { var A, B, C, D TestSuite var suites TestSuites BeforeEach(func() { A = TS("/A", "A", true, TestSuiteStateUncompiled) B = TS("/B", "B", true, TestSuiteStateUncompiled) C = TS("/C", "C", true, TestSuiteStateUncompiled) D = TS("/D", "D", true, TestSuiteStateUncompiled) }) JustBeforeEach(func() { suites = TestSuites{A, B, C, D} }) Describe("AnyHaveProgrammaticFocus", func() { Context("when any suites have programmatic focus", func() { BeforeEach(func() { B.HasProgrammaticFocus = true }) It("returns true", func() { Ω(suites.AnyHaveProgrammaticFocus()).Should(BeTrue()) }) }) Context("when any suites do not have programmatic focus", func() { It("returns false", func() { Ω(suites.AnyHaveProgrammaticFocus()).Should(BeFalse()) }) }) }) Describe("ThatAreGinkgoSuites", func() { BeforeEach(func() { B.IsGinkgo = false D.IsGinkgo = false }) It("returns the subset that are Ginkgo suites", func() { Ω(suites.ThatAreGinkgoSuites()).Should(Equal(TestSuites{A, C})) }) }) Describe("CountWithState", func() { BeforeEach(func() { B.State = TestSuiteStateFailed D.State = TestSuiteStateFailedToCompile }) It("returns the number with the matching state", func() { Ω(suites.CountWithState(TestSuiteStateFailed)).Should(Equal(1)) Ω(suites.CountWithState(TestSuiteStateFailed, TestSuiteStateFailedToCompile)).Should(Equal(2)) }) }) Describe("WithState", func() { BeforeEach(func() { A.State = TestSuiteStatePassed C.State = TestSuiteStateSkippedByFilter }) It("returns the suites matching the passed-in states", func() { Ω(suites.WithState(TestSuiteStatePassed, TestSuiteStateSkippedByFilter)).Should(Equal(TestSuites{A, C})) }) }) Describe("WithoutState", func() { BeforeEach(func() { A.State = TestSuiteStatePassed C.State = TestSuiteStateSkippedByFilter }) It("returns the suites _not_ matching the passed-in states", func() { Ω(suites.WithoutState(TestSuiteStatePassed, TestSuiteStateSkippedByFilter)).Should(Equal(TestSuites{B, D})) }) }) Describe("ShuffledCopy", func() { It("returns a shuffled copy of the test suites", func() { shuffled := suites.ShuffledCopy(17) Ω(suites).Should(Equal(TestSuites{A, B, C, D})) Ω(shuffled).Should(ConsistOf(A, B, C, D)) Ω(shuffled).ShouldNot(Equal(suites)) }) }) }) }) golang-github-onsi-ginkgo-v2-2.22.0/ginkgo/internal/utils.go000066400000000000000000000037771472321612100236710ustar00rootroot00000000000000package internal import ( "fmt" "io" "os" "os/exec" "github.com/onsi/ginkgo/v2/formatter" "github.com/onsi/ginkgo/v2/ginkgo/command" ) func FileExists(path string) bool { _, err := os.Stat(path) return err == nil } func CopyFile(src string, dest string) error { srcFile, err := os.Open(src) if err != nil { return err } srcStat, err := srcFile.Stat() if err != nil { return err } if _, err := os.Stat(dest); err == nil { os.Remove(dest) } destFile, err := os.OpenFile(dest, os.O_WRONLY|os.O_CREATE, srcStat.Mode()) if err != nil { return err } _, err = io.Copy(destFile, srcFile) if err != nil { return err } if err := srcFile.Close(); err != nil { return err } return destFile.Close() } func GoFmt(path string) { out, err := exec.Command("go", "fmt", path).CombinedOutput() if err != nil { command.AbortIfError(fmt.Sprintf("Could not fmt:\n%s\n", string(out)), err) } } func PluralizedWord(singular, plural string, count int) string { if count == 1 { return singular } return plural } func FailedSuitesReport(suites TestSuites, f formatter.Formatter) string { out := "" out += "There were failures detected in the following suites:\n" maxPackageNameLength := 0 for _, suite := range suites.WithState(TestSuiteStateFailureStates...) { if len(suite.PackageName) > maxPackageNameLength { maxPackageNameLength = len(suite.PackageName) } } packageNameFormatter := fmt.Sprintf("%%%ds", maxPackageNameLength) for _, suite := range suites { switch suite.State { case TestSuiteStateFailed: out += f.Fi(1, "{{red}}"+packageNameFormatter+" {{gray}}%s{{/}}\n", suite.PackageName, suite.Path) case TestSuiteStateFailedToCompile: out += f.Fi(1, "{{red}}"+packageNameFormatter+" {{gray}}%s {{magenta}}[Compilation failure]{{/}}\n", suite.PackageName, suite.Path) case TestSuiteStateFailedDueToTimeout: out += f.Fi(1, "{{red}}"+packageNameFormatter+" {{gray}}%s {{orange}}[%s]{{/}}\n", suite.PackageName, suite.Path, TIMEOUT_ELAPSED_FAILURE_REASON) } } return out } golang-github-onsi-ginkgo-v2-2.22.0/ginkgo/internal/utils_test.go000066400000000000000000000070751472321612100247230ustar00rootroot00000000000000package internal_test import ( "os" "path/filepath" "strings" . "github.com/onsi/ginkgo/v2" "github.com/onsi/ginkgo/v2/formatter" "github.com/onsi/ginkgo/v2/ginkgo/internal" . "github.com/onsi/gomega" ) var _ = Describe("Utils", func() { Describe("FileExists", func() { var tmpDir string BeforeEach(func() { tmpDir = GinkgoT().TempDir() }) It("returns true if the path exists", func() { path := filepath.Join(tmpDir, "foo") Ω(os.WriteFile(path, []byte("foo"), 0666)).Should(Succeed()) Ω(internal.FileExists(path)).Should(BeTrue()) }) It("returns false if the path does not exist", func() { path := filepath.Join(tmpDir, "foo") Ω(internal.FileExists(path)).Should(BeFalse()) }) }) Describe("Copying Files", func() { var tmpDirA, tmpDirB string var j = filepath.Join BeforeEach(func() { tmpDirA = GinkgoT().TempDir() tmpDirB = GinkgoT().TempDir() os.WriteFile(j(tmpDirA, "file_a"), []byte("FILE_A"), 0666) os.WriteFile(j(tmpDirA, "file_b"), []byte("FILE_B"), 0777) os.WriteFile(j(tmpDirB, "file_c"), []byte("FILE_C"), 0666) }) DescribeTable("it copies files, overwriting existing content and preserve permissions", func(src string, dest string) { src, dest = j(tmpDirA, src), j(tmpDirB, dest) Ω(internal.CopyFile(src, dest)).Should(Succeed()) expectedContent, err := os.ReadFile(src) Ω(err).ShouldNot(HaveOccurred()) Ω(os.ReadFile(dest)).Should(Equal(expectedContent)) expectedStat, err := os.Stat(src) stat, err := os.Stat(dest) Ω(stat.Mode()).Should(Equal(expectedStat.Mode())) }, Entry(nil, "file_a", "file_a"), Entry(nil, "file_b", "file_b"), Entry(nil, "file_b", "file_c"), ) It("fails when src does not exist", func() { err := internal.CopyFile(j(tmpDirA, "file_c"), j(tmpDirB, "file_c")) Ω(err).Should(HaveOccurred()) Ω(os.ReadFile(j(tmpDirB, "file_c"))).Should(Equal([]byte("FILE_C"))) }) It("fails when dest's directory does not exist", func() { err := internal.CopyFile(j(tmpDirA, "file_a"), j(tmpDirB, "foo", "file_a")) Ω(err).Should(HaveOccurred()) }) }) Describe("PluralizedWord", func() { It("returns singular when count is 1", func() { Ω(internal.PluralizedWord("s", "p", 1)).Should(Equal("s")) }) It("returns plural when count is not 1", func() { Ω(internal.PluralizedWord("s", "p", 0)).Should(Equal("p")) Ω(internal.PluralizedWord("s", "p", 2)).Should(Equal("p")) Ω(internal.PluralizedWord("s", "p", 10)).Should(Equal("p")) }) }) Describe("FailedSuiteReport", func() { var f formatter.Formatter BeforeEach(func() { f = formatter.New(formatter.ColorModePassthrough) }) It("generates a nicely formatter report", func() { suites := []internal.TestSuite{ TS("path-A", "package-A", true, internal.TestSuiteStateFailed), TS("path-B", "B", true, internal.TestSuiteStateFailedToCompile), TS("path-to/package-C", "the-C-package", true, internal.TestSuiteStateFailedDueToTimeout), TS("path-D", "D", true, internal.TestSuiteStatePassed), TS("path-F", "E", true, internal.TestSuiteStateSkippedByFilter), TS("path-F", "E", true, internal.TestSuiteStateSkippedDueToPriorFailures), } Ω(internal.FailedSuitesReport(suites, f)).Should(HavePrefix(strings.Join([]string{ "There were failures detected in the following suites:", " {{red}} package-A {{gray}}path-A{{/}}", " {{red}} B {{gray}}path-B {{magenta}}[Compilation failure]{{/}}", " {{red}}the-C-package {{gray}}path-to/package-C {{orange}}[Suite did not run because the timeout elapsed]{{/}}", }, "\n"))) }) }) }) golang-github-onsi-ginkgo-v2-2.22.0/ginkgo/internal/verify_version.go000066400000000000000000000042141472321612100255650ustar00rootroot00000000000000package internal import ( "fmt" "os/exec" "regexp" "strings" "github.com/onsi/ginkgo/v2/formatter" "github.com/onsi/ginkgo/v2/types" ) var versiorRe = regexp.MustCompile(`v(\d+\.\d+\.\d+)`) func VerifyCLIAndFrameworkVersion(suites TestSuites) { cliVersion := types.VERSION mismatches := map[string][]string{} for _, suite := range suites { cmd := exec.Command("go", "list", "-m", "github.com/onsi/ginkgo/v2") cmd.Dir = suite.Path output, err := cmd.CombinedOutput() if err != nil { continue } components := strings.Split(string(output), " ") if len(components) != 2 { continue } matches := versiorRe.FindStringSubmatch(components[1]) if matches == nil || len(matches) != 2 { continue } libraryVersion := matches[1] if cliVersion != libraryVersion { mismatches[libraryVersion] = append(mismatches[libraryVersion], suite.PackageName) } } if len(mismatches) == 0 { return } fmt.Println(formatter.F("{{red}}{{bold}}Ginkgo detected a version mismatch between the Ginkgo CLI and the version of Ginkgo imported by your packages:{{/}}")) fmt.Println(formatter.Fi(1, "Ginkgo CLI Version:")) fmt.Println(formatter.Fi(2, "{{bold}}%s{{/}}", cliVersion)) fmt.Println(formatter.Fi(1, "Mismatched package versions found:")) for version, packages := range mismatches { fmt.Println(formatter.Fi(2, "{{bold}}%s{{/}} used by %s", version, strings.Join(packages, ", "))) } fmt.Println("") fmt.Println(formatter.Fiw(1, formatter.COLS, "{{gray}}Ginkgo will continue to attempt to run but you may see errors (including flag parsing errors) and should either update your go.mod or your version of the Ginkgo CLI to match.\n\nTo install the matching version of the CLI run\n {{bold}}go install github.com/onsi/ginkgo/v2/ginkgo{{/}}{{gray}}\nfrom a path that contains a go.mod file. Alternatively you can use\n {{bold}}go run github.com/onsi/ginkgo/v2/ginkgo{{/}}{{gray}}\nfrom a path that contains a go.mod file to invoke the matching version of the Ginkgo CLI.\n\nIf you are attempting to test multiple packages that each have a different version of the Ginkgo library with a single Ginkgo CLI that is currently unsupported.\n{{/}}")) } golang-github-onsi-ginkgo-v2-2.22.0/ginkgo/labels/000077500000000000000000000000001472321612100216125ustar00rootroot00000000000000golang-github-onsi-ginkgo-v2-2.22.0/ginkgo/labels/labels_command.go000066400000000000000000000055661472321612100251150ustar00rootroot00000000000000package labels import ( "fmt" "go/ast" "go/parser" "go/token" "sort" "strconv" "strings" "github.com/onsi/ginkgo/v2/ginkgo/command" "github.com/onsi/ginkgo/v2/ginkgo/internal" "github.com/onsi/ginkgo/v2/types" "golang.org/x/tools/go/ast/inspector" ) func BuildLabelsCommand() command.Command { var cliConfig = types.NewDefaultCLIConfig() flags, err := types.BuildLabelsCommandFlagSet(&cliConfig) if err != nil { panic(err) } return command.Command{ Name: "labels", Usage: "ginkgo labels ", Flags: flags, ShortDoc: "List labels detected in the passed-in packages (or the package in the current directory if left blank).", DocLink: "spec-labels", Command: func(args []string, _ []string) { ListLabels(args, cliConfig) }, } } func ListLabels(args []string, cliConfig types.CLIConfig) { suites := internal.FindSuites(args, cliConfig, false).WithoutState(internal.TestSuiteStateSkippedByFilter) if len(suites) == 0 { command.AbortWith("Found no test suites") } for _, suite := range suites { labels := fetchLabelsFromPackage(suite.Path) if len(labels) == 0 { fmt.Printf("%s: No labels found\n", suite.PackageName) } else { fmt.Printf("%s: [%s]\n", suite.PackageName, strings.Join(labels, ", ")) } } } func fetchLabelsFromPackage(packagePath string) []string { fset := token.NewFileSet() parsedPackages, err := parser.ParseDir(fset, packagePath, nil, 0) command.AbortIfError("Failed to parse package source:", err) files := []*ast.File{} hasTestPackage := false for key, pkg := range parsedPackages { if strings.HasSuffix(key, "_test") { hasTestPackage = true for _, file := range pkg.Files { files = append(files, file) } } } if !hasTestPackage { for _, pkg := range parsedPackages { for _, file := range pkg.Files { files = append(files, file) } } } seen := map[string]bool{} labels := []string{} ispr := inspector.New(files) ispr.Preorder([]ast.Node{&ast.CallExpr{}}, func(n ast.Node) { potentialLabels := fetchLabels(n.(*ast.CallExpr)) for _, label := range potentialLabels { if !seen[label] { seen[label] = true labels = append(labels, strconv.Quote(label)) } } }) sort.Strings(labels) return labels } func fetchLabels(callExpr *ast.CallExpr) []string { out := []string{} switch expr := callExpr.Fun.(type) { case *ast.Ident: if expr.Name != "Label" { return out } case *ast.SelectorExpr: if expr.Sel.Name != "Label" { return out } default: return out } for _, arg := range callExpr.Args { switch expr := arg.(type) { case *ast.BasicLit: if expr.Kind == token.STRING { unquoted, err := strconv.Unquote(expr.Value) if err != nil { unquoted = expr.Value } validated, err := types.ValidateAndCleanupLabel(unquoted, types.CodeLocation{}) if err == nil { out = append(out, validated) } } } } return out } golang-github-onsi-ginkgo-v2-2.22.0/ginkgo/main.go000066400000000000000000000030041472321612100216200ustar00rootroot00000000000000package main import ( "fmt" "os" "github.com/onsi/ginkgo/v2/ginkgo/build" "github.com/onsi/ginkgo/v2/ginkgo/command" "github.com/onsi/ginkgo/v2/ginkgo/generators" "github.com/onsi/ginkgo/v2/ginkgo/labels" "github.com/onsi/ginkgo/v2/ginkgo/outline" "github.com/onsi/ginkgo/v2/ginkgo/run" "github.com/onsi/ginkgo/v2/ginkgo/unfocus" "github.com/onsi/ginkgo/v2/ginkgo/watch" "github.com/onsi/ginkgo/v2/types" ) var program command.Program func GenerateCommands() []command.Command { return []command.Command{ watch.BuildWatchCommand(), build.BuildBuildCommand(), generators.BuildBootstrapCommand(), generators.BuildGenerateCommand(), labels.BuildLabelsCommand(), outline.BuildOutlineCommand(), unfocus.BuildUnfocusCommand(), BuildVersionCommand(), } } func main() { program = command.Program{ Name: "ginkgo", Heading: fmt.Sprintf("Ginkgo Version %s", types.VERSION), Commands: GenerateCommands(), DefaultCommand: run.BuildRunCommand(), DeprecatedCommands: []command.DeprecatedCommand{ {Name: "convert", Deprecation: types.Deprecations.Convert()}, {Name: "blur", Deprecation: types.Deprecations.Blur()}, {Name: "nodot", Deprecation: types.Deprecations.Nodot()}, }, } program.RunAndExit(os.Args) } func BuildVersionCommand() command.Command { return command.Command{ Name: "version", Usage: "ginkgo version", ShortDoc: "Print Ginkgo's version", Command: func(_ []string, _ []string) { fmt.Printf("Ginkgo Version %s\n", types.VERSION) }, } } golang-github-onsi-ginkgo-v2-2.22.0/ginkgo/outline/000077500000000000000000000000001472321612100220275ustar00rootroot00000000000000golang-github-onsi-ginkgo-v2-2.22.0/ginkgo/outline/_testdata/000077500000000000000000000000001472321612100237775ustar00rootroot00000000000000golang-github-onsi-ginkgo-v2-2.22.0/ginkgo/outline/_testdata/alias_test.go000066400000000000000000000012371472321612100264610ustar00rootroot00000000000000package example_test import ( fooginkgo "github.com/onsi/ginkgo/v2" ) var _ = fooginkgo.Describe("NodotFixture", func() { fooginkgo.Describe("normal", func() { fooginkgo.It("normal", func() { fooginkgo.By("normal") fooginkgo.By("normal") }) }) fooginkgo.Context("normal", func() { fooginkgo.It("normal", func() { }) }) fooginkgo.When("normal", func() { fooginkgo.It("normal", func() { }) }) fooginkgo.It("normal", func() { }) fooginkgo.Specify("normal", func() { }) fooginkgo.DescribeTable("normal", func() {}, fooginkgo.Entry("normal"), ) fooginkgo.DescribeTable("normal", func() {}, fooginkgo.Entry("normal"), ) }) golang-github-onsi-ginkgo-v2-2.22.0/ginkgo/outline/_testdata/alias_test.go.csv000066400000000000000000000012531472321612100272510ustar00rootroot00000000000000Name,Text,Start,End,Spec,Focused,Pending,Labels Describe,NodotFixture,81,670,false,false,false,"" Describe,normal,126,259,false,false,false,"" It,normal,166,255,true,false,false,"" By,normal,201,223,false,false,false,"" By,normal,227,249,false,false,false,"" Context,normal,262,342,false,false,false,"" It,normal,301,338,true,false,false,"" When,normal,345,422,false,false,false,"" It,normal,381,418,true,false,false,"" It,normal,425,461,true,false,false,"" Specify,normal,464,505,true,false,false,"" DescribeTable,normal,508,586,false,false,false,"" Entry,normal,557,582,true,false,false,"" DescribeTable,normal,589,667,false,false,false,"" Entry,normal,638,663,true,false,false,"" golang-github-onsi-ginkgo-v2-2.22.0/ginkgo/outline/_testdata/alias_test.go.json000066400000000000000000000034711472321612100274330ustar00rootroot00000000000000[{"name":"Describe","text":"NodotFixture","start":81,"end":670,"spec":false,"focused":false,"pending":false,"labels":[],"nodes":[{"name":"Describe","text":"normal","start":126,"end":259,"spec":false,"focused":false,"pending":false,"labels":[],"nodes":[{"name":"It","text":"normal","start":166,"end":255,"spec":true,"focused":false,"pending":false,"labels":[],"nodes":[{"name":"By","text":"normal","start":201,"end":223,"spec":false,"focused":false,"pending":false,"labels":null,"nodes":[]},{"name":"By","text":"normal","start":227,"end":249,"spec":false,"focused":false,"pending":false,"labels":null,"nodes":[]}]}]},{"name":"Context","text":"normal","start":262,"end":342,"spec":false,"focused":false,"pending":false,"labels":[],"nodes":[{"name":"It","text":"normal","start":301,"end":338,"spec":true,"focused":false,"pending":false,"labels":[],"nodes":[]}]},{"name":"When","text":"normal","start":345,"end":422,"spec":false,"focused":false,"pending":false,"labels":[],"nodes":[{"name":"It","text":"normal","start":381,"end":418,"spec":true,"focused":false,"pending":false,"labels":[],"nodes":[]}]},{"name":"It","text":"normal","start":425,"end":461,"spec":true,"focused":false,"pending":false,"labels":[],"nodes":[]},{"name":"Specify","text":"normal","start":464,"end":505,"spec":true,"focused":false,"pending":false,"labels":[],"nodes":[]},{"name":"DescribeTable","text":"normal","start":508,"end":586,"spec":false,"focused":false,"pending":false,"labels":[],"nodes":[{"name":"Entry","text":"normal","start":557,"end":582,"spec":true,"focused":false,"pending":false,"labels":[],"nodes":[]}]},{"name":"DescribeTable","text":"normal","start":589,"end":667,"spec":false,"focused":false,"pending":false,"labels":[],"nodes":[{"name":"Entry","text":"normal","start":638,"end":663,"spec":true,"focused":false,"pending":false,"labels":[],"nodes":[]}]}]}] golang-github-onsi-ginkgo-v2-2.22.0/ginkgo/outline/_testdata/create_result.sh000066400000000000000000000005751472321612100272030ustar00rootroot00000000000000#!/usr/bin/env bash set -o errexit set -o nounset GINKGO=${GINKGO:-ginkgo} input=${1:-""} for format in "csv" "json"; do set -o xtrace output="$(dirname $input)/$(basename $input).$format" tmp=$(mktemp ginkgo-outline-test.XXX) if "$GINKGO" outline --format="$format" "$input" 1>"$tmp" then mv "$tmp" "$output" else rm "$tmp" set +o xtrace fi done golang-github-onsi-ginkgo-v2-2.22.0/ginkgo/outline/_testdata/dsl_core_test.go000066400000000000000000000003311472321612100271540ustar00rootroot00000000000000package example_test import ( . "github.com/onsi/ginkgo/v2/dsl/core" ) var _ = Describe("DslCoreFixture", func() { Describe("dslcore", func() { It("dslcore", func() { By("step 1") By("step 2") }) }) }) golang-github-onsi-ginkgo-v2-2.22.0/ginkgo/outline/_testdata/dsl_core_test.go.csv000066400000000000000000000004071472321612100277520ustar00rootroot00000000000000Name,Text,Start,End,Spec,Focused,Pending,Labels Describe,DslCoreFixture,82,216,false,false,false,"" Describe,dslcore,119,213,false,false,false,"" It,dslcore,150,209,true,false,false,"" By,step 1,176,188,false,false,false,"" By,step 2,192,204,false,false,false,"" golang-github-onsi-ginkgo-v2-2.22.0/ginkgo/outline/_testdata/dsl_core_test.go.json000066400000000000000000000011571472321612100301330ustar00rootroot00000000000000[{"name":"Describe","text":"DslCoreFixture","start":82,"end":216,"spec":false,"focused":false,"pending":false,"labels":[],"nodes":[{"name":"Describe","text":"dslcore","start":119,"end":213,"spec":false,"focused":false,"pending":false,"labels":[],"nodes":[{"name":"It","text":"dslcore","start":150,"end":209,"spec":true,"focused":false,"pending":false,"labels":[],"nodes":[{"name":"By","text":"step 1","start":176,"end":188,"spec":false,"focused":false,"pending":false,"labels":null,"nodes":[]},{"name":"By","text":"step 2","start":192,"end":204,"spec":false,"focused":false,"pending":false,"labels":null,"nodes":[]}]}]}]}] golang-github-onsi-ginkgo-v2-2.22.0/ginkgo/outline/_testdata/focused_test.go000066400000000000000000000010221472321612100270100ustar00rootroot00000000000000package example_test import ( . "github.com/onsi/ginkgo/v2" ) var _ = Describe("unfocused", func() { FDescribe("focused", func() { It("focused", func() { By("focused") By("focused") }) }) FContext("focused", func() { It("focused", func() { }) }) FWhen("focused", func() { It("focused", func() { }) }) FIt("focused", func() { }) FSpecify("focused", func() { }) FDescribeTable("focused", func() {}, Entry("focused"), ) DescribeTable("focused", func() {}, FEntry("focused"), ) }) golang-github-onsi-ginkgo-v2-2.22.0/ginkgo/outline/_testdata/focused_test.go.csv000066400000000000000000000012601472321612100276060ustar00rootroot00000000000000Name,Text,Start,End,Spec,Focused,Pending,Labels Describe,unfocused,73,529,false,false,false,"" FDescribe,focused,105,202,false,true,false,"" It,focused,137,198,true,true,false,"" By,focused,163,176,false,true,false,"" By,focused,180,193,false,true,false,"" FContext,focused,205,268,false,true,false,"" It,focused,236,264,true,true,false,"" FWhen,focused,271,331,false,true,false,"" It,focused,299,327,true,true,false,"" FIt,focused,334,362,true,true,false,"" FSpecify,focused,365,398,true,true,false,"" FDescribeTable,focused,401,462,false,true,false,"" Entry,focused,442,458,true,true,false,"" DescribeTable,focused,465,526,false,false,false,"" FEntry,focused,505,522,true,true,false,"" golang-github-onsi-ginkgo-v2-2.22.0/ginkgo/outline/_testdata/focused_test.go.json000066400000000000000000000034761472321612100277770ustar00rootroot00000000000000[{"name":"Describe","text":"unfocused","start":73,"end":529,"spec":false,"focused":false,"pending":false,"labels":[],"nodes":[{"name":"FDescribe","text":"focused","start":105,"end":202,"spec":false,"focused":true,"pending":false,"labels":[],"nodes":[{"name":"It","text":"focused","start":137,"end":198,"spec":true,"focused":true,"pending":false,"labels":[],"nodes":[{"name":"By","text":"focused","start":163,"end":176,"spec":false,"focused":true,"pending":false,"labels":null,"nodes":[]},{"name":"By","text":"focused","start":180,"end":193,"spec":false,"focused":true,"pending":false,"labels":null,"nodes":[]}]}]},{"name":"FContext","text":"focused","start":205,"end":268,"spec":false,"focused":true,"pending":false,"labels":[],"nodes":[{"name":"It","text":"focused","start":236,"end":264,"spec":true,"focused":true,"pending":false,"labels":[],"nodes":[]}]},{"name":"FWhen","text":"focused","start":271,"end":331,"spec":false,"focused":true,"pending":false,"labels":[],"nodes":[{"name":"It","text":"focused","start":299,"end":327,"spec":true,"focused":true,"pending":false,"labels":[],"nodes":[]}]},{"name":"FIt","text":"focused","start":334,"end":362,"spec":true,"focused":true,"pending":false,"labels":[],"nodes":[]},{"name":"FSpecify","text":"focused","start":365,"end":398,"spec":true,"focused":true,"pending":false,"labels":[],"nodes":[]},{"name":"FDescribeTable","text":"focused","start":401,"end":462,"spec":false,"focused":true,"pending":false,"labels":[],"nodes":[{"name":"Entry","text":"focused","start":442,"end":458,"spec":true,"focused":true,"pending":false,"labels":[],"nodes":[]}]},{"name":"DescribeTable","text":"focused","start":465,"end":526,"spec":false,"focused":false,"pending":false,"labels":[],"nodes":[{"name":"FEntry","text":"focused","start":505,"end":522,"spec":true,"focused":true,"pending":false,"labels":[],"nodes":[]}]}]}] golang-github-onsi-ginkgo-v2-2.22.0/ginkgo/outline/_testdata/labels_test.go000066400000000000000000000010741472321612100266310ustar00rootroot00000000000000package example_test import ( . "github.com/onsi/ginkgo/v2" ) var _ = Describe("NormalFixture", func() { Describe("normal", Label("normal", "serial"), func() { It("normal", func() { By("step 1") By("step 2") }) }) Context("normal", func() { It("normal", Label("medium"), Label("slow"), func() { }) }) When("normal", func() { It("normal", func() { }) }) It("normal", func() { }) Specify("normal", func() { }) DescribeTable("normal", func() {}, Entry("normal"), ) DescribeTable("normal", func() {}, Entry("normal"), ) }) golang-github-onsi-ginkgo-v2-2.22.0/ginkgo/outline/_testdata/labels_test.go.csv000066400000000000000000000013061472321612100274210ustar00rootroot00000000000000Name,Text,Start,End,Spec,Focused,Pending,Labels Describe,NormalFixture,73,571,false,false,false,"" Describe,normal,109,228,false,false,false,"normal, serial" It,normal,166,224,true,false,false,"" By,step 1,191,203,false,false,false,"" By,step 2,207,219,false,false,false,"" Context,normal,231,323,false,false,false,"" It,normal,260,319,true,false,false,"medium, slow" When,normal,326,383,false,false,false,"" It,normal,352,379,true,false,false,"" It,normal,386,412,true,false,false,"" Specify,normal,415,446,true,false,false,"" DescribeTable,normal,449,507,false,false,false,"" Entry,normal,488,503,true,false,false,"" DescribeTable,normal,510,568,false,false,false,"" Entry,normal,549,564,true,false,false,"" golang-github-onsi-ginkgo-v2-2.22.0/ginkgo/outline/_testdata/labels_test.go.json000066400000000000000000000035321472321612100276020ustar00rootroot00000000000000[{"name":"Describe","text":"NormalFixture","start":73,"end":571,"spec":false,"focused":false,"pending":false,"labels":[],"nodes":[{"name":"Describe","text":"normal","start":109,"end":228,"spec":false,"focused":false,"pending":false,"labels":["normal","serial"],"nodes":[{"name":"It","text":"normal","start":166,"end":224,"spec":true,"focused":false,"pending":false,"labels":[],"nodes":[{"name":"By","text":"step 1","start":191,"end":203,"spec":false,"focused":false,"pending":false,"labels":null,"nodes":[]},{"name":"By","text":"step 2","start":207,"end":219,"spec":false,"focused":false,"pending":false,"labels":null,"nodes":[]}]}]},{"name":"Context","text":"normal","start":231,"end":323,"spec":false,"focused":false,"pending":false,"labels":[],"nodes":[{"name":"It","text":"normal","start":260,"end":319,"spec":true,"focused":false,"pending":false,"labels":["medium","slow"],"nodes":[]}]},{"name":"When","text":"normal","start":326,"end":383,"spec":false,"focused":false,"pending":false,"labels":[],"nodes":[{"name":"It","text":"normal","start":352,"end":379,"spec":true,"focused":false,"pending":false,"labels":[],"nodes":[]}]},{"name":"It","text":"normal","start":386,"end":412,"spec":true,"focused":false,"pending":false,"labels":[],"nodes":[]},{"name":"Specify","text":"normal","start":415,"end":446,"spec":true,"focused":false,"pending":false,"labels":[],"nodes":[]},{"name":"DescribeTable","text":"normal","start":449,"end":507,"spec":false,"focused":false,"pending":false,"labels":[],"nodes":[{"name":"Entry","text":"normal","start":488,"end":503,"spec":true,"focused":false,"pending":false,"labels":[],"nodes":[]}]},{"name":"DescribeTable","text":"normal","start":510,"end":568,"spec":false,"focused":false,"pending":false,"labels":[],"nodes":[{"name":"Entry","text":"normal","start":549,"end":564,"spec":true,"focused":false,"pending":false,"labels":[],"nodes":[]}]}]}] golang-github-onsi-ginkgo-v2-2.22.0/ginkgo/outline/_testdata/mixed_test.go000066400000000000000000000011111472321612100264650ustar00rootroot00000000000000package example_test import ( . "github.com/onsi/ginkgo/v2" ) var _ = FDescribe("unfocused", func() { FContext("unfocused", func() { It("unfocused", func() { }) FIt("focused", func() { }) }) Context("unfocused", func() { FIt("focused", func() { }) It("unfocused", func() { }) }) FContext("focused", func() { It("focused", func() { }) It("focused", func() { }) }) PContext("unfocused", func() { FIt("unfocused", func() { By("unfocused") By("unfocused") }) It("unfocused", func() { By("unfocused") By("unfocused") }) }) }) golang-github-onsi-ginkgo-v2-2.22.0/ginkgo/outline/_testdata/mixed_test.go.csv000066400000000000000000000013741472321612100272720ustar00rootroot00000000000000Name,Text,Start,End,Spec,Focused,Pending,Labels FDescribe,unfocused,73,584,false,false,false,"" FContext,unfocused,106,205,false,false,false,"" It,unfocused,139,169,true,false,false,"" FIt,focused,172,201,true,true,false,"" Context,unfocused,208,306,false,false,false,"" FIt,focused,240,269,true,true,false,"" It,unfocused,272,302,true,false,false,"" FContext,focused,309,403,false,true,false,"" It,focused,340,368,true,true,false,"" It,focused,371,399,true,true,false,"" PContext,unfocused,406,581,false,false,true,"" FIt,unfocused,439,507,true,false,true,"" By,unfocused,468,483,false,false,true,"" By,unfocused,487,502,false,false,true,"" It,unfocused,510,577,true,false,true,"" By,unfocused,538,553,false,false,true,"" By,unfocused,557,572,false,false,true,"" golang-github-onsi-ginkgo-v2-2.22.0/ginkgo/outline/_testdata/mixed_test.go.json000066400000000000000000000040601472321612100274430ustar00rootroot00000000000000[{"name":"FDescribe","text":"unfocused","start":73,"end":584,"spec":false,"focused":false,"pending":false,"labels":[],"nodes":[{"name":"FContext","text":"unfocused","start":106,"end":205,"spec":false,"focused":false,"pending":false,"labels":[],"nodes":[{"name":"It","text":"unfocused","start":139,"end":169,"spec":true,"focused":false,"pending":false,"labels":[],"nodes":[]},{"name":"FIt","text":"focused","start":172,"end":201,"spec":true,"focused":true,"pending":false,"labels":[],"nodes":[]}]},{"name":"Context","text":"unfocused","start":208,"end":306,"spec":false,"focused":false,"pending":false,"labels":[],"nodes":[{"name":"FIt","text":"focused","start":240,"end":269,"spec":true,"focused":true,"pending":false,"labels":[],"nodes":[]},{"name":"It","text":"unfocused","start":272,"end":302,"spec":true,"focused":false,"pending":false,"labels":[],"nodes":[]}]},{"name":"FContext","text":"focused","start":309,"end":403,"spec":false,"focused":true,"pending":false,"labels":[],"nodes":[{"name":"It","text":"focused","start":340,"end":368,"spec":true,"focused":true,"pending":false,"labels":[],"nodes":[]},{"name":"It","text":"focused","start":371,"end":399,"spec":true,"focused":true,"pending":false,"labels":[],"nodes":[]}]},{"name":"PContext","text":"unfocused","start":406,"end":581,"spec":false,"focused":false,"pending":true,"labels":[],"nodes":[{"name":"FIt","text":"unfocused","start":439,"end":507,"spec":true,"focused":false,"pending":true,"labels":[],"nodes":[{"name":"By","text":"unfocused","start":468,"end":483,"spec":false,"focused":false,"pending":true,"labels":null,"nodes":[]},{"name":"By","text":"unfocused","start":487,"end":502,"spec":false,"focused":false,"pending":true,"labels":null,"nodes":[]}]},{"name":"It","text":"unfocused","start":510,"end":577,"spec":true,"focused":false,"pending":true,"labels":[],"nodes":[{"name":"By","text":"unfocused","start":538,"end":553,"spec":false,"focused":false,"pending":true,"labels":null,"nodes":[]},{"name":"By","text":"unfocused","start":557,"end":572,"spec":false,"focused":false,"pending":true,"labels":null,"nodes":[]}]}]}]}] golang-github-onsi-ginkgo-v2-2.22.0/ginkgo/outline/_testdata/nestedfocused_test.go000066400000000000000000000007351472321612100302250ustar00rootroot00000000000000package example_test import ( . "github.com/onsi/ginkgo/v2" ) var _ = FDescribe("unfocused", func() { FContext("unfocused", func() { It("unfocused", func() { By("unfocused") By("unfocused") }) FIt("focused", func() { By("focused") By("focused") }) }) Context("unfocused", func() { FIt("focused", func() { }) It("unfocused", func() { }) }) FContext("focused", func() { It("focused", func() { }) It("focused", func() { }) }) }) golang-github-onsi-ginkgo-v2-2.22.0/ginkgo/outline/_testdata/nestedfocused_test.go.csv000066400000000000000000000011721472321612100310130ustar00rootroot00000000000000Name,Text,Start,End,Spec,Focused,Pending,Labels FDescribe,unfocused,73,476,false,false,false,"" FContext,unfocused,106,275,false,false,false,"" It,unfocused,139,206,true,false,false,"" By,unfocused,167,182,false,false,false,"" By,unfocused,186,201,false,false,false,"" FIt,focused,209,271,true,true,false,"" By,focused,236,249,false,true,false,"" By,focused,253,266,false,true,false,"" Context,unfocused,278,376,false,false,false,"" FIt,focused,310,339,true,true,false,"" It,unfocused,342,372,true,false,false,"" FContext,focused,379,473,false,true,false,"" It,focused,410,438,true,true,false,"" It,focused,441,469,true,true,false,"" golang-github-onsi-ginkgo-v2-2.22.0/ginkgo/outline/_testdata/nestedfocused_test.go.json000066400000000000000000000032741472321612100311760ustar00rootroot00000000000000[{"name":"FDescribe","text":"unfocused","start":73,"end":476,"spec":false,"focused":false,"pending":false,"labels":[],"nodes":[{"name":"FContext","text":"unfocused","start":106,"end":275,"spec":false,"focused":false,"pending":false,"labels":[],"nodes":[{"name":"It","text":"unfocused","start":139,"end":206,"spec":true,"focused":false,"pending":false,"labels":[],"nodes":[{"name":"By","text":"unfocused","start":167,"end":182,"spec":false,"focused":false,"pending":false,"labels":null,"nodes":[]},{"name":"By","text":"unfocused","start":186,"end":201,"spec":false,"focused":false,"pending":false,"labels":null,"nodes":[]}]},{"name":"FIt","text":"focused","start":209,"end":271,"spec":true,"focused":true,"pending":false,"labels":[],"nodes":[{"name":"By","text":"focused","start":236,"end":249,"spec":false,"focused":true,"pending":false,"labels":null,"nodes":[]},{"name":"By","text":"focused","start":253,"end":266,"spec":false,"focused":true,"pending":false,"labels":null,"nodes":[]}]}]},{"name":"Context","text":"unfocused","start":278,"end":376,"spec":false,"focused":false,"pending":false,"labels":[],"nodes":[{"name":"FIt","text":"focused","start":310,"end":339,"spec":true,"focused":true,"pending":false,"labels":[],"nodes":[]},{"name":"It","text":"unfocused","start":342,"end":372,"spec":true,"focused":false,"pending":false,"labels":[],"nodes":[]}]},{"name":"FContext","text":"focused","start":379,"end":473,"spec":false,"focused":true,"pending":false,"labels":[],"nodes":[{"name":"It","text":"focused","start":410,"end":438,"spec":true,"focused":true,"pending":false,"labels":[],"nodes":[]},{"name":"It","text":"focused","start":441,"end":469,"spec":true,"focused":true,"pending":false,"labels":[],"nodes":[]}]}]}] golang-github-onsi-ginkgo-v2-2.22.0/ginkgo/outline/_testdata/nodot_test.go000066400000000000000000000011471472321612100265130ustar00rootroot00000000000000package example_test import ( "github.com/onsi/ginkgo/v2" ) var _ = ginkgo.Describe("NodotFixture", func() { ginkgo.Describe("normal", func() { ginkgo.It("normal", func() { ginkgo.By("normal") ginkgo.By("normal") }) }) ginkgo.Context("normal", func() { ginkgo.It("normal", func() { }) }) ginkgo.When("normal", func() { ginkgo.It("normal", func() { }) }) ginkgo.It("normal", func() { }) ginkgo.Specify("normal", func() { }) ginkgo.DescribeTable("normal", func() {}, ginkgo.Entry("normal"), ) ginkgo.DescribeTable("normal", func() {}, ginkgo.Entry("normal"), ) }) golang-github-onsi-ginkgo-v2-2.22.0/ginkgo/outline/_testdata/nodot_test.go.csv000066400000000000000000000012531472321612100273030ustar00rootroot00000000000000Name,Text,Start,End,Spec,Focused,Pending,Labels Describe,NodotFixture,71,614,false,false,false,"" Describe,normal,113,233,false,false,false,"" It,normal,150,229,true,false,false,"" By,normal,182,201,false,false,false,"" By,normal,205,224,false,false,false,"" Context,normal,236,310,false,false,false,"" It,normal,272,306,true,false,false,"" When,normal,313,384,false,false,false,"" It,normal,346,380,true,false,false,"" It,normal,387,420,true,false,false,"" Specify,normal,423,461,true,false,false,"" DescribeTable,normal,464,536,false,false,false,"" Entry,normal,510,532,true,false,false,"" DescribeTable,normal,539,611,false,false,false,"" Entry,normal,585,607,true,false,false,"" golang-github-onsi-ginkgo-v2-2.22.0/ginkgo/outline/_testdata/nodot_test.go.json000066400000000000000000000034711472321612100274650ustar00rootroot00000000000000[{"name":"Describe","text":"NodotFixture","start":71,"end":614,"spec":false,"focused":false,"pending":false,"labels":[],"nodes":[{"name":"Describe","text":"normal","start":113,"end":233,"spec":false,"focused":false,"pending":false,"labels":[],"nodes":[{"name":"It","text":"normal","start":150,"end":229,"spec":true,"focused":false,"pending":false,"labels":[],"nodes":[{"name":"By","text":"normal","start":182,"end":201,"spec":false,"focused":false,"pending":false,"labels":null,"nodes":[]},{"name":"By","text":"normal","start":205,"end":224,"spec":false,"focused":false,"pending":false,"labels":null,"nodes":[]}]}]},{"name":"Context","text":"normal","start":236,"end":310,"spec":false,"focused":false,"pending":false,"labels":[],"nodes":[{"name":"It","text":"normal","start":272,"end":306,"spec":true,"focused":false,"pending":false,"labels":[],"nodes":[]}]},{"name":"When","text":"normal","start":313,"end":384,"spec":false,"focused":false,"pending":false,"labels":[],"nodes":[{"name":"It","text":"normal","start":346,"end":380,"spec":true,"focused":false,"pending":false,"labels":[],"nodes":[]}]},{"name":"It","text":"normal","start":387,"end":420,"spec":true,"focused":false,"pending":false,"labels":[],"nodes":[]},{"name":"Specify","text":"normal","start":423,"end":461,"spec":true,"focused":false,"pending":false,"labels":[],"nodes":[]},{"name":"DescribeTable","text":"normal","start":464,"end":536,"spec":false,"focused":false,"pending":false,"labels":[],"nodes":[{"name":"Entry","text":"normal","start":510,"end":532,"spec":true,"focused":false,"pending":false,"labels":[],"nodes":[]}]},{"name":"DescribeTable","text":"normal","start":539,"end":611,"spec":false,"focused":false,"pending":false,"labels":[],"nodes":[{"name":"Entry","text":"normal","start":585,"end":607,"spec":true,"focused":false,"pending":false,"labels":[],"nodes":[]}]}]}] golang-github-onsi-ginkgo-v2-2.22.0/ginkgo/outline/_testdata/normal_test.go000066400000000000000000000010011472321612100266450ustar00rootroot00000000000000package example_test import ( . "github.com/onsi/ginkgo/v2" ) var _ = Describe("NormalFixture", func() { Describe("normal", func() { It("normal", func() { By("step 1") By("step 2") }) }) Context("normal", func() { It("normal", func() { }) }) When("normal", func() { It("normal", func() { }) }) It("normal", func() { }) Specify("normal", func() { }) DescribeTable("normal", func() {}, Entry("normal"), ) DescribeTable("normal", func() {}, Entry("normal"), ) }) golang-github-onsi-ginkgo-v2-2.22.0/ginkgo/outline/_testdata/normal_test.go.csv000066400000000000000000000012541472321612100274510ustar00rootroot00000000000000Name,Text,Start,End,Spec,Focused,Pending,Labels Describe,NormalFixture,73,512,false,false,false,"" Describe,normal,109,201,false,false,false,"" It,normal,139,197,true,false,false,"" By,step 1,164,176,false,false,false,"" By,step 2,180,192,false,false,false,"" Context,normal,204,264,false,false,false,"" It,normal,233,260,true,false,false,"" When,normal,267,324,false,false,false,"" It,normal,293,320,true,false,false,"" It,normal,327,353,true,false,false,"" Specify,normal,356,387,true,false,false,"" DescribeTable,normal,390,448,false,false,false,"" Entry,normal,429,444,true,false,false,"" DescribeTable,normal,451,509,false,false,false,"" Entry,normal,490,505,true,false,false,"" golang-github-onsi-ginkgo-v2-2.22.0/ginkgo/outline/_testdata/normal_test.go.json000066400000000000000000000034721472321612100276330ustar00rootroot00000000000000[{"name":"Describe","text":"NormalFixture","start":73,"end":512,"spec":false,"focused":false,"pending":false,"labels":[],"nodes":[{"name":"Describe","text":"normal","start":109,"end":201,"spec":false,"focused":false,"pending":false,"labels":[],"nodes":[{"name":"It","text":"normal","start":139,"end":197,"spec":true,"focused":false,"pending":false,"labels":[],"nodes":[{"name":"By","text":"step 1","start":164,"end":176,"spec":false,"focused":false,"pending":false,"labels":null,"nodes":[]},{"name":"By","text":"step 2","start":180,"end":192,"spec":false,"focused":false,"pending":false,"labels":null,"nodes":[]}]}]},{"name":"Context","text":"normal","start":204,"end":264,"spec":false,"focused":false,"pending":false,"labels":[],"nodes":[{"name":"It","text":"normal","start":233,"end":260,"spec":true,"focused":false,"pending":false,"labels":[],"nodes":[]}]},{"name":"When","text":"normal","start":267,"end":324,"spec":false,"focused":false,"pending":false,"labels":[],"nodes":[{"name":"It","text":"normal","start":293,"end":320,"spec":true,"focused":false,"pending":false,"labels":[],"nodes":[]}]},{"name":"It","text":"normal","start":327,"end":353,"spec":true,"focused":false,"pending":false,"labels":[],"nodes":[]},{"name":"Specify","text":"normal","start":356,"end":387,"spec":true,"focused":false,"pending":false,"labels":[],"nodes":[]},{"name":"DescribeTable","text":"normal","start":390,"end":448,"spec":false,"focused":false,"pending":false,"labels":[],"nodes":[{"name":"Entry","text":"normal","start":429,"end":444,"spec":true,"focused":false,"pending":false,"labels":[],"nodes":[]}]},{"name":"DescribeTable","text":"normal","start":451,"end":509,"spec":false,"focused":false,"pending":false,"labels":[],"nodes":[{"name":"Entry","text":"normal","start":490,"end":505,"spec":true,"focused":false,"pending":false,"labels":[],"nodes":[]}]}]}] golang-github-onsi-ginkgo-v2-2.22.0/ginkgo/outline/_testdata/pending_decorator_test.go000066400000000000000000000011201472321612100310450ustar00rootroot00000000000000package example_test import ( . "github.com/onsi/ginkgo/v2" ) var _ = Describe("NormalFixture", func() { Describe("normal", Label("normal", "serial"), Pending, func() { It("normal", func() { By("step 1") By("step 2") }) }) Context("normal", func() { It("normal", Label("medium"), Label("slow"), func() { }) }) When("normal", func() { It("normal", func() { }) }) It("normal", Pending(), func() { }) Specify("normal", func() { }) DescribeTable("normal", func() {}, Entry("normal"), ) DescribeTable("normal", func() {}, Entry("normal"), ) }) golang-github-onsi-ginkgo-v2-2.22.0/ginkgo/outline/_testdata/pending_decorator_test.go.csv000066400000000000000000000013011472321612100316400ustar00rootroot00000000000000Name,Text,Start,End,Spec,Focused,Pending,Labels Describe,NormalFixture,73,591,false,false,false,"" Describe,normal,109,237,false,false,true,"normal, serial" It,normal,175,233,true,false,true,"" By,step 1,200,212,false,false,true,"" By,step 2,216,228,false,false,true,"" Context,normal,240,332,false,false,false,"" It,normal,269,328,true,false,false,"medium, slow" When,normal,335,392,false,false,false,"" It,normal,361,388,true,false,false,"" It,normal,395,432,true,false,true,"" Specify,normal,435,466,true,false,false,"" DescribeTable,normal,469,527,false,false,false,"" Entry,normal,508,523,true,false,false,"" DescribeTable,normal,530,588,false,false,false,"" Entry,normal,569,584,true,false,false,"" golang-github-onsi-ginkgo-v2-2.22.0/ginkgo/outline/_testdata/pending_decorator_test.go.json000066400000000000000000000035251472321612100320300ustar00rootroot00000000000000[{"name":"Describe","text":"NormalFixture","start":73,"end":591,"spec":false,"focused":false,"pending":false,"labels":[],"nodes":[{"name":"Describe","text":"normal","start":109,"end":237,"spec":false,"focused":false,"pending":true,"labels":["normal","serial"],"nodes":[{"name":"It","text":"normal","start":175,"end":233,"spec":true,"focused":false,"pending":true,"labels":[],"nodes":[{"name":"By","text":"step 1","start":200,"end":212,"spec":false,"focused":false,"pending":true,"labels":null,"nodes":[]},{"name":"By","text":"step 2","start":216,"end":228,"spec":false,"focused":false,"pending":true,"labels":null,"nodes":[]}]}]},{"name":"Context","text":"normal","start":240,"end":332,"spec":false,"focused":false,"pending":false,"labels":[],"nodes":[{"name":"It","text":"normal","start":269,"end":328,"spec":true,"focused":false,"pending":false,"labels":["medium","slow"],"nodes":[]}]},{"name":"When","text":"normal","start":335,"end":392,"spec":false,"focused":false,"pending":false,"labels":[],"nodes":[{"name":"It","text":"normal","start":361,"end":388,"spec":true,"focused":false,"pending":false,"labels":[],"nodes":[]}]},{"name":"It","text":"normal","start":395,"end":432,"spec":true,"focused":false,"pending":true,"labels":[],"nodes":[]},{"name":"Specify","text":"normal","start":435,"end":466,"spec":true,"focused":false,"pending":false,"labels":[],"nodes":[]},{"name":"DescribeTable","text":"normal","start":469,"end":527,"spec":false,"focused":false,"pending":false,"labels":[],"nodes":[{"name":"Entry","text":"normal","start":508,"end":523,"spec":true,"focused":false,"pending":false,"labels":[],"nodes":[]}]},{"name":"DescribeTable","text":"normal","start":530,"end":588,"spec":false,"focused":false,"pending":false,"labels":[],"nodes":[{"name":"Entry","text":"normal","start":569,"end":584,"spec":true,"focused":false,"pending":false,"labels":[],"nodes":[]}]}]}] golang-github-onsi-ginkgo-v2-2.22.0/ginkgo/outline/_testdata/pending_test.go000066400000000000000000000010271472321612100270110ustar00rootroot00000000000000package example_test import ( . "github.com/onsi/ginkgo/v2" ) var _ = Describe("PendingFixture", func() { PDescribe("pending", func() { It("pending", func() { By("pending") By("pending") }) }) PContext("pending", func() { It("pending", func() { }) }) PWhen("pending", func() { It("pending", func() { }) }) PIt("pending", func() { }) PSpecify("pending", func() { }) PDescribeTable("pending", func() {}, Entry("pending"), ) DescribeTable("pending", func() {}, PEntry("pending"), ) }) golang-github-onsi-ginkgo-v2-2.22.0/ginkgo/outline/_testdata/pending_test.go.csv000066400000000000000000000012651472321612100276070ustar00rootroot00000000000000Name,Text,Start,End,Spec,Focused,Pending,Labels Describe,PendingFixture,73,534,false,false,false,"" PDescribe,pending,110,207,false,false,true,"" It,pending,142,203,true,false,true,"" By,pending,168,181,false,false,true,"" By,pending,185,198,false,false,true,"" PContext,pending,210,273,false,false,true,"" It,pending,241,269,true,false,true,"" PWhen,pending,276,336,false,false,true,"" It,pending,304,332,true,false,true,"" PIt,pending,339,367,true,false,true,"" PSpecify,pending,370,403,true,false,true,"" PDescribeTable,pending,406,467,false,false,true,"" Entry,pending,447,463,true,false,true,"" DescribeTable,pending,470,531,false,false,false,"" PEntry,pending,510,527,true,false,true,"" golang-github-onsi-ginkgo-v2-2.22.0/ginkgo/outline/_testdata/pending_test.go.json000066400000000000000000000035031472321612100277620ustar00rootroot00000000000000[{"name":"Describe","text":"PendingFixture","start":73,"end":534,"spec":false,"focused":false,"pending":false,"labels":[],"nodes":[{"name":"PDescribe","text":"pending","start":110,"end":207,"spec":false,"focused":false,"pending":true,"labels":[],"nodes":[{"name":"It","text":"pending","start":142,"end":203,"spec":true,"focused":false,"pending":true,"labels":[],"nodes":[{"name":"By","text":"pending","start":168,"end":181,"spec":false,"focused":false,"pending":true,"labels":null,"nodes":[]},{"name":"By","text":"pending","start":185,"end":198,"spec":false,"focused":false,"pending":true,"labels":null,"nodes":[]}]}]},{"name":"PContext","text":"pending","start":210,"end":273,"spec":false,"focused":false,"pending":true,"labels":[],"nodes":[{"name":"It","text":"pending","start":241,"end":269,"spec":true,"focused":false,"pending":true,"labels":[],"nodes":[]}]},{"name":"PWhen","text":"pending","start":276,"end":336,"spec":false,"focused":false,"pending":true,"labels":[],"nodes":[{"name":"It","text":"pending","start":304,"end":332,"spec":true,"focused":false,"pending":true,"labels":[],"nodes":[]}]},{"name":"PIt","text":"pending","start":339,"end":367,"spec":true,"focused":false,"pending":true,"labels":[],"nodes":[]},{"name":"PSpecify","text":"pending","start":370,"end":403,"spec":true,"focused":false,"pending":true,"labels":[],"nodes":[]},{"name":"PDescribeTable","text":"pending","start":406,"end":467,"spec":false,"focused":false,"pending":true,"labels":[],"nodes":[{"name":"Entry","text":"pending","start":447,"end":463,"spec":true,"focused":false,"pending":true,"labels":[],"nodes":[]}]},{"name":"DescribeTable","text":"pending","start":470,"end":531,"spec":false,"focused":false,"pending":false,"labels":[],"nodes":[{"name":"PEntry","text":"pending","start":510,"end":527,"spec":true,"focused":false,"pending":true,"labels":[],"nodes":[]}]}]}] golang-github-onsi-ginkgo-v2-2.22.0/ginkgo/outline/_testdata/position_test.go000066400000000000000000000003611472321612100272310ustar00rootroot00000000000000package example_test import ( . "github.com/onsi/ginkgo/v2" ) // Describe start=104, end=240 var _ = Describe("104,240", func() { /* * block comment * */ // line comment // It start=209, end=236 It("209,236", func() { }) }) golang-github-onsi-ginkgo-v2-2.22.0/ginkgo/outline/_testdata/position_test.go.csv000066400000000000000000000002051472321612100300200ustar00rootroot00000000000000Name,Text,Start,End,Spec,Focused,Pending,Labels Describe,104,240,104,240,false,false,false,"" It,209,236,209,236,true,false,false,"" golang-github-onsi-ginkgo-v2-2.22.0/ginkgo/outline/_testdata/position_test.go.json000066400000000000000000000003701472321612100302010ustar00rootroot00000000000000[{"name":"Describe","text":"104,240","start":104,"end":240,"spec":false,"focused":false,"pending":false,"labels":[],"nodes":[{"name":"It","text":"209,236","start":209,"end":236,"spec":true,"focused":false,"pending":false,"labels":[],"nodes":[]}]}] golang-github-onsi-ginkgo-v2-2.22.0/ginkgo/outline/_testdata/suite_test.go000066400000000000000000000003051472321612100265140ustar00rootroot00000000000000package example_test import ( . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" "testing" ) func TestExample(t *testing.T) { RegisterFailHandler(Fail) RunSpecs(t, "Example Suite") } golang-github-onsi-ginkgo-v2-2.22.0/ginkgo/outline/_testdata/suite_test.go.csv000066400000000000000000000000601472321612100273040ustar00rootroot00000000000000Name,Text,Start,End,Spec,Focused,Pending,Labels golang-github-onsi-ginkgo-v2-2.22.0/ginkgo/outline/_testdata/suite_test.go.json000066400000000000000000000000031472321612100274570ustar00rootroot00000000000000[] golang-github-onsi-ginkgo-v2-2.22.0/ginkgo/outline/ginkgo.go000066400000000000000000000206111472321612100236340ustar00rootroot00000000000000package outline import ( "go/ast" "go/token" "strconv" "github.com/onsi/ginkgo/v2/types" ) const ( // undefinedTextAlt is used if the spec/container text cannot be derived undefinedTextAlt = "undefined" ) // ginkgoMetadata holds useful bits of information for every entry in the outline type ginkgoMetadata struct { // Name is the spec or container function name, e.g. `Describe` or `It` Name string `json:"name"` // Text is the `text` argument passed to specs, and some containers Text string `json:"text"` // Start is the position of first character of the spec or container block Start int `json:"start"` // End is the position of first character immediately after the spec or container block End int `json:"end"` Spec bool `json:"spec"` Focused bool `json:"focused"` Pending bool `json:"pending"` Labels []string `json:"labels"` } // ginkgoNode is used to construct the outline as a tree type ginkgoNode struct { ginkgoMetadata Nodes []*ginkgoNode `json:"nodes"` } type walkFunc func(n *ginkgoNode) func (n *ginkgoNode) PreOrder(f walkFunc) { f(n) for _, m := range n.Nodes { m.PreOrder(f) } } func (n *ginkgoNode) PostOrder(f walkFunc) { for _, m := range n.Nodes { m.PostOrder(f) } f(n) } func (n *ginkgoNode) Walk(pre, post walkFunc) { pre(n) for _, m := range n.Nodes { m.Walk(pre, post) } post(n) } // PropagateInheritedProperties propagates the Pending and Focused properties // through the subtree rooted at n. func (n *ginkgoNode) PropagateInheritedProperties() { n.PreOrder(func(thisNode *ginkgoNode) { for _, descendantNode := range thisNode.Nodes { if thisNode.Pending { descendantNode.Pending = true descendantNode.Focused = false } if thisNode.Focused && !descendantNode.Pending { descendantNode.Focused = true } } }) } // BackpropagateUnfocus propagates the Focused property through the subtree // rooted at n. It applies the rule described in the Ginkgo docs: // > Nested programmatically focused specs follow a simple rule: if a // > leaf-node is marked focused, any of its ancestor nodes that are marked // > focus will be unfocused. func (n *ginkgoNode) BackpropagateUnfocus() { focusedSpecInSubtreeStack := []bool{} n.PostOrder(func(thisNode *ginkgoNode) { if thisNode.Spec { focusedSpecInSubtreeStack = append(focusedSpecInSubtreeStack, thisNode.Focused) return } focusedSpecInSubtree := false for range thisNode.Nodes { focusedSpecInSubtree = focusedSpecInSubtree || focusedSpecInSubtreeStack[len(focusedSpecInSubtreeStack)-1] focusedSpecInSubtreeStack = focusedSpecInSubtreeStack[0 : len(focusedSpecInSubtreeStack)-1] } focusedSpecInSubtreeStack = append(focusedSpecInSubtreeStack, focusedSpecInSubtree) if focusedSpecInSubtree { thisNode.Focused = false } }) } func packageAndIdentNamesFromCallExpr(ce *ast.CallExpr) (string, string, bool) { switch ex := ce.Fun.(type) { case *ast.Ident: return "", ex.Name, true case *ast.SelectorExpr: pkgID, ok := ex.X.(*ast.Ident) if !ok { return "", "", false } // A package identifier is top-level, so Obj must be nil if pkgID.Obj != nil { return "", "", false } if ex.Sel == nil { return "", "", false } return pkgID.Name, ex.Sel.Name, true default: return "", "", false } } // absoluteOffsetsForNode derives the absolute character offsets of the node start and // end positions. func absoluteOffsetsForNode(fset *token.FileSet, n ast.Node) (start, end int) { return fset.PositionFor(n.Pos(), false).Offset, fset.PositionFor(n.End(), false).Offset } // ginkgoNodeFromCallExpr derives an outline entry from a go AST subtree // corresponding to a Ginkgo container or spec. func ginkgoNodeFromCallExpr(fset *token.FileSet, ce *ast.CallExpr, ginkgoPackageName *string) (*ginkgoNode, bool) { packageName, identName, ok := packageAndIdentNamesFromCallExpr(ce) if !ok { return nil, false } n := ginkgoNode{} n.Name = identName n.Start, n.End = absoluteOffsetsForNode(fset, ce) n.Nodes = make([]*ginkgoNode, 0) switch identName { case "It", "Specify", "Entry": n.Spec = true n.Text = textOrAltFromCallExpr(ce, undefinedTextAlt) n.Labels = labelFromCallExpr(ce) n.Pending = pendingFromCallExpr(ce) return &n, ginkgoPackageName != nil && *ginkgoPackageName == packageName case "FIt", "FSpecify", "FEntry": n.Spec = true n.Focused = true n.Text = textOrAltFromCallExpr(ce, undefinedTextAlt) n.Labels = labelFromCallExpr(ce) return &n, ginkgoPackageName != nil && *ginkgoPackageName == packageName case "PIt", "PSpecify", "XIt", "XSpecify", "PEntry", "XEntry": n.Spec = true n.Pending = true n.Text = textOrAltFromCallExpr(ce, undefinedTextAlt) n.Labels = labelFromCallExpr(ce) return &n, ginkgoPackageName != nil && *ginkgoPackageName == packageName case "Context", "Describe", "When", "DescribeTable": n.Text = textOrAltFromCallExpr(ce, undefinedTextAlt) n.Labels = labelFromCallExpr(ce) n.Pending = pendingFromCallExpr(ce) return &n, ginkgoPackageName != nil && *ginkgoPackageName == packageName case "FContext", "FDescribe", "FWhen", "FDescribeTable": n.Focused = true n.Text = textOrAltFromCallExpr(ce, undefinedTextAlt) n.Labels = labelFromCallExpr(ce) return &n, ginkgoPackageName != nil && *ginkgoPackageName == packageName case "PContext", "PDescribe", "PWhen", "XContext", "XDescribe", "XWhen", "PDescribeTable", "XDescribeTable": n.Pending = true n.Text = textOrAltFromCallExpr(ce, undefinedTextAlt) n.Labels = labelFromCallExpr(ce) return &n, ginkgoPackageName != nil && *ginkgoPackageName == packageName case "By": n.Text = textOrAltFromCallExpr(ce, undefinedTextAlt) return &n, ginkgoPackageName != nil && *ginkgoPackageName == packageName case "AfterEach", "BeforeEach": return &n, ginkgoPackageName != nil && *ginkgoPackageName == packageName case "JustAfterEach", "JustBeforeEach": return &n, ginkgoPackageName != nil && *ginkgoPackageName == packageName case "AfterSuite", "BeforeSuite": return &n, ginkgoPackageName != nil && *ginkgoPackageName == packageName case "SynchronizedAfterSuite", "SynchronizedBeforeSuite": return &n, ginkgoPackageName != nil && *ginkgoPackageName == packageName default: return nil, false } } // textOrAltFromCallExpr tries to derive the "text" of a Ginkgo spec or // container. If it cannot derive it, it returns the alt text. func textOrAltFromCallExpr(ce *ast.CallExpr, alt string) string { text, defined := textFromCallExpr(ce) if !defined { return alt } return text } // textFromCallExpr tries to derive the "text" of a Ginkgo spec or container. If // it cannot derive it, it returns false. func textFromCallExpr(ce *ast.CallExpr) (string, bool) { if len(ce.Args) < 1 { return "", false } text, ok := ce.Args[0].(*ast.BasicLit) if !ok { return "", false } switch text.Kind { case token.CHAR, token.STRING: // For token.CHAR and token.STRING, Value is quoted unquoted, err := strconv.Unquote(text.Value) if err != nil { // If unquoting fails, just use the raw Value return text.Value, true } return unquoted, true default: return text.Value, true } } func labelFromCallExpr(ce *ast.CallExpr) []string { labels := []string{} if len(ce.Args) < 2 { return labels } for _, arg := range ce.Args[1:] { switch expr := arg.(type) { case *ast.CallExpr: id, ok := expr.Fun.(*ast.Ident) if !ok { // to skip over cases where the expr.Fun. is actually *ast.SelectorExpr continue } if id.Name == "Label" { ls := extractLabels(expr) labels = append(labels, ls...) } } } return labels } func extractLabels(expr *ast.CallExpr) []string { out := []string{} for _, arg := range expr.Args { switch expr := arg.(type) { case *ast.BasicLit: if expr.Kind == token.STRING { unquoted, err := strconv.Unquote(expr.Value) if err != nil { unquoted = expr.Value } validated, err := types.ValidateAndCleanupLabel(unquoted, types.CodeLocation{}) if err == nil { out = append(out, validated) } } } } return out } func pendingFromCallExpr(ce *ast.CallExpr) bool { pending := false if len(ce.Args) < 2 { return pending } for _, arg := range ce.Args[1:] { switch expr := arg.(type) { case *ast.CallExpr: id, ok := expr.Fun.(*ast.Ident) if !ok { // to skip over cases where the expr.Fun. is actually *ast.SelectorExpr continue } if id.Name == "Pending" { pending = true } case *ast.Ident: if expr.Name == "Pending" { pending = true } } } return pending } golang-github-onsi-ginkgo-v2-2.22.0/ginkgo/outline/import.go000066400000000000000000000030571472321612100236750ustar00rootroot00000000000000// Copyright 2013 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. // Most of the required functions were available in the // "golang.org/x/tools/go/ast/astutil" package, but not exported. // They were copied from https://github.com/golang/tools/blob/2b0845dc783e36ae26d683f4915a5840ef01ab0f/go/ast/astutil/imports.go package outline import ( "go/ast" "strconv" "strings" ) // packageNameForImport returns the package name for the package. If the package // is not imported, it returns nil. "Package name" refers to `pkgname` in the // call expression `pkgname.ExportedIdentifier`. Examples: // (import path not found) -> nil // "import example.com/pkg/foo" -> "foo" // "import fooalias example.com/pkg/foo" -> "fooalias" // "import . example.com/pkg/foo" -> "" func packageNameForImport(f *ast.File, path string) *string { spec := importSpec(f, path) if spec == nil { return nil } name := spec.Name.String() if name == "" { name = "ginkgo" } if name == "." { name = "" } return &name } // importSpec returns the import spec if f imports path, // or nil otherwise. func importSpec(f *ast.File, path string) *ast.ImportSpec { for _, s := range f.Imports { if strings.HasPrefix(importPath(s), path) { return s } } return nil } // importPath returns the unquoted import path of s, // or "" if the path is not properly quoted. func importPath(s *ast.ImportSpec) string { t, err := strconv.Unquote(s.Path.Value) if err != nil { return "" } return t } golang-github-onsi-ginkgo-v2-2.22.0/ginkgo/outline/outline.go000066400000000000000000000063531472321612100240440ustar00rootroot00000000000000package outline import ( "encoding/json" "fmt" "go/ast" "go/token" "strings" "golang.org/x/tools/go/ast/inspector" ) const ( // ginkgoImportPath is the well-known ginkgo import path ginkgoImportPath = "github.com/onsi/ginkgo/v2" ) // FromASTFile returns an outline for a Ginkgo test source file func FromASTFile(fset *token.FileSet, src *ast.File) (*outline, error) { ginkgoPackageName := packageNameForImport(src, ginkgoImportPath) if ginkgoPackageName == nil { return nil, fmt.Errorf("file does not import %q", ginkgoImportPath) } root := ginkgoNode{} stack := []*ginkgoNode{&root} ispr := inspector.New([]*ast.File{src}) ispr.Nodes([]ast.Node{(*ast.CallExpr)(nil)}, func(node ast.Node, push bool) bool { if push { // Pre-order traversal ce, ok := node.(*ast.CallExpr) if !ok { // Because `Nodes` calls this function only when the node is an // ast.CallExpr, this should never happen panic(fmt.Errorf("node starting at %d, ending at %d is not an *ast.CallExpr", node.Pos(), node.End())) } gn, ok := ginkgoNodeFromCallExpr(fset, ce, ginkgoPackageName) if !ok { // Node is not a Ginkgo spec or container, continue return true } parent := stack[len(stack)-1] parent.Nodes = append(parent.Nodes, gn) stack = append(stack, gn) return true } // Post-order traversal start, end := absoluteOffsetsForNode(fset, node) lastVisitedGinkgoNode := stack[len(stack)-1] if start != lastVisitedGinkgoNode.Start || end != lastVisitedGinkgoNode.End { // Node is not a Ginkgo spec or container, so it was not pushed onto the stack, continue return true } stack = stack[0 : len(stack)-1] return true }) if len(root.Nodes) == 0 { return &outline{[]*ginkgoNode{}}, nil } // Derive the final focused property for all nodes. This must be done // _before_ propagating the inherited focused property. root.BackpropagateUnfocus() // Now, propagate inherited properties, including focused and pending. root.PropagateInheritedProperties() return &outline{root.Nodes}, nil } type outline struct { Nodes []*ginkgoNode `json:"nodes"` } func (o *outline) MarshalJSON() ([]byte, error) { return json.Marshal(o.Nodes) } // String returns a CSV-formatted outline. Spec or container are output in // depth-first order. func (o *outline) String() string { return o.StringIndent(0) } // StringIndent returns a CSV-formated outline, but every line is indented by // one 'width' of spaces for every level of nesting. func (o *outline) StringIndent(width int) string { var b strings.Builder b.WriteString("Name,Text,Start,End,Spec,Focused,Pending,Labels\n") currentIndent := 0 pre := func(n *ginkgoNode) { b.WriteString(fmt.Sprintf("%*s", currentIndent, "")) var labels string if len(n.Labels) == 1 { labels = n.Labels[0] } else { labels = strings.Join(n.Labels, ", ") } //enclosing labels in a double quoted comma separate listed so that when inmported into a CSV app the Labels column has comma separate strings b.WriteString(fmt.Sprintf("%s,%s,%d,%d,%t,%t,%t,\"%s\"\n", n.Name, n.Text, n.Start, n.End, n.Spec, n.Focused, n.Pending, labels)) currentIndent += width } post := func(n *ginkgoNode) { currentIndent -= width } for _, n := range o.Nodes { n.Walk(pre, post) } return b.String() } golang-github-onsi-ginkgo-v2-2.22.0/ginkgo/outline/outline_command.go000066400000000000000000000043411472321612100255350ustar00rootroot00000000000000package outline import ( "encoding/json" "fmt" "go/parser" "go/token" "os" "github.com/onsi/ginkgo/v2/ginkgo/command" "github.com/onsi/ginkgo/v2/types" ) const ( // indentWidth is the width used by the 'indent' output indentWidth = 4 // stdinAlias is a portable alias for stdin. This convention is used in // other CLIs, e.g., kubectl. stdinAlias = "-" usageCommand = "ginkgo outline " ) type outlineConfig struct { Format string } func BuildOutlineCommand() command.Command { conf := outlineConfig{ Format: "csv", } flags, err := types.NewGinkgoFlagSet( types.GinkgoFlags{ {Name: "format", KeyPath: "Format", Usage: "Format of outline", UsageArgument: "one of 'csv', 'indent', or 'json'", UsageDefaultValue: conf.Format, }, }, &conf, types.GinkgoFlagSections{}, ) if err != nil { panic(err) } return command.Command{ Name: "outline", Usage: "ginkgo outline ", ShortDoc: "Create an outline of Ginkgo symbols for a file", Documentation: "To read from stdin, use: `ginkgo outline -`", DocLink: "creating-an-outline-of-specs", Flags: flags, Command: func(args []string, _ []string) { outlineFile(args, conf.Format) }, } } func outlineFile(args []string, format string) { if len(args) != 1 { command.AbortWithUsage("outline expects exactly one argument") } filename := args[0] var src *os.File if filename == stdinAlias { src = os.Stdin } else { var err error src, err = os.Open(filename) command.AbortIfError("Failed to open file:", err) } fset := token.NewFileSet() parsedSrc, err := parser.ParseFile(fset, filename, src, 0) command.AbortIfError("Failed to parse source:", err) o, err := FromASTFile(fset, parsedSrc) command.AbortIfError("Failed to create outline:", err) var oerr error switch format { case "csv": _, oerr = fmt.Print(o) case "indent": _, oerr = fmt.Print(o.StringIndent(indentWidth)) case "json": b, err := json.Marshal(o) if err != nil { println(fmt.Sprintf("error marshalling to json: %s", err)) } _, oerr = fmt.Println(string(b)) default: command.AbortWith("Format %s not accepted", format) } command.AbortIfError("Failed to write outline:", oerr) } golang-github-onsi-ginkgo-v2-2.22.0/ginkgo/outline/outline_suite_test.go000066400000000000000000000003051472321612100263030ustar00rootroot00000000000000package outline_test import ( "testing" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" ) func TestOutline(t *testing.T) { RegisterFailHandler(Fail) RunSpecs(t, "Outline Suite") } golang-github-onsi-ginkgo-v2-2.22.0/ginkgo/outline/outline_test.go000066400000000000000000000105361472321612100251010ustar00rootroot00000000000000package outline import ( "encoding/json" "fmt" "go/parser" "go/token" "log" "os" "path/filepath" "strconv" "strings" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" ) var _ = DescribeTable("Validate outline from file with", func(srcFilename, jsonOutlineFilename, csvOutlineFilename string) { fset := token.NewFileSet() astFile, err := parser.ParseFile(fset, filepath.Join("_testdata", srcFilename), nil, 0) Expect(err).To(BeNil(), "error parsing source: %s", err) if err != nil { log.Fatalf("error parsing source: %s", err) } o, err := FromASTFile(fset, astFile) Expect(err).To(BeNil(), "error creating outline: %s", err) gotJSON, err := json.MarshalIndent(o, "", " ") Expect(err).To(BeNil(), "error marshalling outline to json: %s", err) wantJSON, err := os.ReadFile(filepath.Join("_testdata", jsonOutlineFilename)) Expect(err).To(BeNil(), "error reading JSON outline fixture: %s", err) Expect(gotJSON).To(MatchJSON(wantJSON)) gotCSV := o.String() wantCSV, err := os.ReadFile(filepath.Join("_testdata", csvOutlineFilename)) Expect(err).To(BeNil(), "error reading CSV outline fixture: %s", err) Expect(gotCSV).To(Equal(string(wantCSV))) }, // To add a test: // 1. Create the input, e.g., `myspecialcase_test.go` // 2. Create the sample CSV and JSON results: Run `bash ./_testdata/create_result.sh ./_testdata/myspecialcase_test.go` // 3. Add an Entry below, by copying an existing one, and substituting `myspecialcase` where needed. // To re-create the sample results for a test: // 1. Run `bash ./_testdata/create_result.sh ./testdata/myspecialcase_test.go` // To re-create the sample results for all tests: // 1. Run `for name in ./_testdata/*_test.go; do bash ./_testdata/create_result.sh $name; done` Entry("normal import of ginkgo package (no dot, no alias), normal container and specs", "nodot_test.go", "nodot_test.go.json", "nodot_test.go.csv"), Entry("aliased import of ginkgo package, normal container and specs", "alias_test.go", "alias_test.go.json", "alias_test.go.csv"), Entry("normal containers and specs", "normal_test.go", "normal_test.go.json", "normal_test.go.csv"), Entry("focused containers and specs", "focused_test.go", "focused_test.go.json", "focused_test.go.csv"), Entry("pending containers and specs", "pending_test.go", "pending_test.go.json", "pending_test.go.csv"), Entry("nested focused containers and specs", "nestedfocused_test.go", "nestedfocused_test.go.json", "nestedfocused_test.go.csv"), Entry("mixed focused containers and specs", "mixed_test.go", "mixed_test.go.json", "mixed_test.go.csv"), Entry("specs used to verify position", "position_test.go", "position_test.go.json", "position_test.go.csv"), Entry("suite setup", "suite_test.go", "suite_test.go.json", "suite_test.go.csv"), Entry("core dsl import", "dsl_core_test.go", "dsl_core_test.go.json", "dsl_core_test.go.csv"), Entry("labels decorator on containers and specs", "labels_test.go", "labels_test.go.json", "labels_test.go.csv"), Entry("pending decorator on containers and specs", "pending_decorator_test.go", "pending_decorator_test.go.json", "pending_decorator_test.go.csv"), ) var _ = Describe("Validate position", func() { It("should report the correct start and end byte offsets of the ginkgo container or spec", func() { fset := token.NewFileSet() astFile, err := parser.ParseFile(fset, filepath.Join("_testdata", "position_test.go"), nil, 0) Expect(err).To(BeNil(), "error parsing source: %s", err) if err != nil { log.Fatalf("error parsing source: %s", err) } o, err := FromASTFile(fset, astFile) Expect(err).To(BeNil(), "error creating outline: %s", err) for _, n := range o.Nodes { n.PreOrder(func(n *ginkgoNode) { wantPositions := strings.Split(n.Text, ",") Expect(len(wantPositions)).To(Equal(2), "test fixture node text should be \"start position,end position") wantStart, err := strconv.Atoi(wantPositions[0]) Expect(err).To(BeNil(), "could not parse start offset") wantEnd, err := strconv.Atoi(wantPositions[1]) Expect(err).To(BeNil(), "could not parse end offset") Expect(int(n.Start)).To(Equal(wantStart), fmt.Sprintf("test fixture node text says the node should start at %d, but it starts at %d", wantStart, n.Start)) Expect(int(n.End)).To(Equal(wantEnd), fmt.Sprintf("test fixture node text says the node should end at %d, but it ends at %d", wantEnd, n.End)) }) } }) }) golang-github-onsi-ginkgo-v2-2.22.0/ginkgo/performance/000077500000000000000000000000001472321612100226515ustar00rootroot00000000000000golang-github-onsi-ginkgo-v2-2.22.0/ginkgo/performance/_fixtures/000077500000000000000000000000001472321612100246615ustar00rootroot00000000000000golang-github-onsi-ginkgo-v2-2.22.0/ginkgo/performance/_fixtures/large_suite_fixture/000077500000000000000000000000001472321612100307325ustar00rootroot00000000000000large_suite_suite_test.go000066400000000000000000000005041472321612100357540ustar00rootroot00000000000000golang-github-onsi-ginkgo-v2-2.22.0/ginkgo/performance/_fixtures/large_suite_fixturepackage large_fixture_test import ( "testing" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" ) func TestLargeSuiteFixture(t *testing.T) { RegisterFailHandler(Fail) RunSpecs(t, "Large Suite") } var _ = Describe("Large Suite", func() { for i := 0; i < 2048; i++ { It("is a large suite", func() {}) } }) golang-github-onsi-ginkgo-v2-2.22.0/ginkgo/performance/_fixtures/performance_fixture/000077500000000000000000000000001472321612100307305ustar00rootroot00000000000000golang-github-onsi-ginkgo-v2-2.22.0/ginkgo/performance/_fixtures/performance_fixture/go.mod000066400000000000000000000005141472321612100320360ustar00rootroot00000000000000module example.com/dependency_fetcher go 1.16 require github.com/onsi/gomega v1.13.0 require github.com/gorilla/mux v1.8.0 require gopkg.in/yaml.v2 v2.4.0 require ( github.com/onsi/ginkgo v1.16.4 // indirect github.com/tdewolff/minify/v2 v2.9.17 golang.org/x/crypto v0.17.0 ) replace github.com/onsi/ginkgo => ../../ginkgo golang-github-onsi-ginkgo-v2-2.22.0/ginkgo/performance/_fixtures/performance_fixture/go.sum000066400000000000000000000271501472321612100320700ustar00rootroot00000000000000github.com/cheekybits/is v0.0.0-20150225183255-68e9c0620927 h1:SKI1/fuSdodxmNNyVBR8d7X/HuLnRpvvFO0AgyQk764= github.com/cheekybits/is v0.0.0-20150225183255-68e9c0620927/go.mod h1:h/aW8ynjgkuj+NQRlZcDbAbM1ORAbXjXX77sX7T289U= github.com/chzyer/logex v1.1.10 h1:Swpa1K6QvQznwJRcfTfQJmTE72DqScAa40E+fbHEXEE= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e h1:fY5BOSpyZCqRo5OhCuC+XN+r/bBCmeuuJtjz+bCNIf8= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1 h1:q763qf9huN11kDQavWsoZXJNW3xEE4JJyHa5Q25/sd8= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo= github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 h1:p104kn46Q8WdvHunIJ9dAyjPVtrBPhSr3KT2yUst43I= github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38 h1:yAJXTCF9TqKcTiHJAE8dj7HMvPfh66eeA2JYW7eFpSE= github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI= github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639 h1:mV02weKRL81bEnm8A0HT1/CAelMQDBuQIfLw8n+d6xI= github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/matryer/try v0.0.0-20161228173917-9ac251b645a2 h1:JAEbJn3j/FrhdWA9jW8B5ajsLIjeuEHLi8xE4fk997o= github.com/matryer/try v0.0.0-20161228173917-9ac251b645a2/go.mod h1:0KeJpeMD6o+O4hW7qJOT7vyQPKrWmj26uf5wMc/IiIs= github.com/onsi/gomega v1.13.0 h1:7lLHu94wT9Ij0o6EWWclhu0aOh32VxhkwEJvzuWPeak= github.com/onsi/gomega v1.13.0/go.mod h1:lRk9szgn8TxENtWd0Tp4c3wjlRfMTMH27I+3Je41yGY= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/stretchr/objx v0.1.0 h1:4G4v2dO3VZwixGIRoQ5Lfboy6nUhCyYzaqnIAPPhYs4= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/tdewolff/minify/v2 v2.9.17 h1:0RPKCBSz5plIKZkmkm/zeQdqPYf/9NJVwG63NHtViHQ= github.com/tdewolff/minify/v2 v2.9.17/go.mod h1:OLHZpngMfp36EyqxkGGta1l3hB1c+sHhXNHk8WTrsQo= github.com/tdewolff/test v1.0.6 h1:76mzYJQ83Op284kMT+63iCNCI7NEERsIN8dLM+RiKr4= github.com/tdewolff/test v1.0.6/go.mod h1:6DAvZliBAAnD7rhVgwaM7DE5/d9NMOAJ09SqYqeK4QE= github.com/yuin/goldmark v1.2.1 h1:ruQGxdhGHe7FWOJPT0mKs5+pD2Xs1Bm/kdGlHO04FmM= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= 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 h1:psW17arqaxU48Z5kZ0CQnkZWQJsqcURM6tKiBApRjXI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.1.0 h1:MDRAIl0xIo9Io2xV565hzXHw3zVseKrJKodhohM5CjU= golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw= golang.org/x/crypto v0.17.0 h1:r8bRNjWL3GshPW3gkd+RpvzWrZAwPS49OmTGZ/uhM4k= golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= golang.org/x/mod v0.3.0 h1:RM4zey1++hCTbCVQfnWeKs9/IEsaBLA8vTkd0WVtmH4= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/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.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210428140749-89ef3d95e781 h1:DzZ89McO9/gWPsQXS/FVKAlG02ZjaQ6AlZRBimEYOd0= golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9 h1:SQFwaSi55rU7vdNs9Yr0Z324VNlrF+0wMqRXT4St8ck= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210510120138-977fb7262007 h1:gG67DSER+11cZvqIMb8S8bt0vZtiN6xWYARwirrOSfE= golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1 h1:v+OssWQX+hTHEmOBgwxdZxK4zHq3yOs8F9J7mk0PY8E= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e h1:4nW4NLDYnU28ojHaHO8OVxFHk/aQ33U01a9cjED+pzE= golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0 h1:bxAC2xTBsZGibn2RTntX0oH50xLsqy1OxA9tTL3p/lk= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= golang-github-onsi-ginkgo-v2-2.22.0/ginkgo/performance/_fixtures/performance_fixture/performance.go000066400000000000000000000004151472321612100335600ustar00rootroot00000000000000package performance import ( "fmt" "github.com/gorilla/mux" "github.com/tdewolff/minify/v2" "golang.org/x/crypto/bcrypt" "gopkg.in/yaml.v2" ) func main() { mux.NewRouter() fmt.Println(bcrypt.MinCost) fmt.Println(yaml.Decoder{}) fmt.Println(minify.MinInt) } performance_suite_test.go000066400000000000000000000006271472321612100357560ustar00rootroot00000000000000golang-github-onsi-ginkgo-v2-2.22.0/ginkgo/performance/_fixtures/performance_fixturepackage performance_test import ( "fmt" "testing" "time" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" ) func TestPerformanceFixture(t *testing.T) { RegisterFailHandler(Fail) RunSpecs(t, "Performance Fixture Suite") } var _ = Describe("Performance Fixture", func() { for i := 0; i < 10; i++ { It(fmt.Sprintf("sleeps %d", i), func() { time.Sleep(time.Millisecond * 10) }) } }) golang-github-onsi-ginkgo-v2-2.22.0/ginkgo/performance/_fixtures/performance_fixture/pkg1/000077500000000000000000000000001472321612100315725ustar00rootroot00000000000000pkg1_suite_test.go000066400000000000000000000010711472321612100351530ustar00rootroot00000000000000golang-github-onsi-ginkgo-v2-2.22.0/ginkgo/performance/_fixtures/performance_fixture/pkg1package pkg1_test import ( "fmt" "testing" "time" "github.com/gorilla/mux" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" "github.com/tdewolff/minify/v2" "golang.org/x/crypto/bcrypt" "gopkg.in/yaml.v2" ) func TestPkg1(t *testing.T) { RegisterFailHandler(Fail) RunSpecs(t, "Pkg1 Suite") mux.NewRouter() fmt.Println(bcrypt.MinCost) fmt.Println(yaml.Decoder{}) fmt.Println(minify.MinInt) } var _ = Describe("Pkg1", func() { for i := 0; i < 10; i++ { It(fmt.Sprintf("sleeps %d", i), func() { time.Sleep(time.Millisecond * 10) }) } }) golang-github-onsi-ginkgo-v2-2.22.0/ginkgo/performance/_fixtures/performance_fixture/pkg2/000077500000000000000000000000001472321612100315735ustar00rootroot00000000000000pkg2_suite_test.go000066400000000000000000000010711472321612100351550ustar00rootroot00000000000000golang-github-onsi-ginkgo-v2-2.22.0/ginkgo/performance/_fixtures/performance_fixture/pkg2package pkg2_test import ( "fmt" "testing" "time" "github.com/gorilla/mux" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" "github.com/tdewolff/minify/v2" "golang.org/x/crypto/bcrypt" "gopkg.in/yaml.v2" ) func TestPkg2(t *testing.T) { RegisterFailHandler(Fail) RunSpecs(t, "Pkg2 Suite") mux.NewRouter() fmt.Println(bcrypt.MinCost) fmt.Println(yaml.Decoder{}) fmt.Println(minify.MinInt) } var _ = Describe("Pkg2", func() { for i := 0; i < 10; i++ { It(fmt.Sprintf("sleeps %d", i), func() { time.Sleep(time.Millisecond * 10) }) } }) golang-github-onsi-ginkgo-v2-2.22.0/ginkgo/performance/_fixtures/performance_fixture/pkg3/000077500000000000000000000000001472321612100315745ustar00rootroot00000000000000pkg3_suite_test.go000066400000000000000000000010711472321612100351570ustar00rootroot00000000000000golang-github-onsi-ginkgo-v2-2.22.0/ginkgo/performance/_fixtures/performance_fixture/pkg3package pkg3_test import ( "fmt" "testing" "time" "github.com/gorilla/mux" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" "github.com/tdewolff/minify/v2" "golang.org/x/crypto/bcrypt" "gopkg.in/yaml.v2" ) func TestPkg3(t *testing.T) { RegisterFailHandler(Fail) RunSpecs(t, "Pkg3 Suite") mux.NewRouter() fmt.Println(bcrypt.MinCost) fmt.Println(yaml.Decoder{}) fmt.Println(minify.MinInt) } var _ = Describe("Pkg3", func() { for i := 0; i < 10; i++ { It(fmt.Sprintf("sleeps %d", i), func() { time.Sleep(time.Millisecond * 10) }) } }) golang-github-onsi-ginkgo-v2-2.22.0/ginkgo/performance/_fixtures/performance_fixture/pkg4/000077500000000000000000000000001472321612100315755ustar00rootroot00000000000000pkg4_suite_test.go000066400000000000000000000010711472321612100351610ustar00rootroot00000000000000golang-github-onsi-ginkgo-v2-2.22.0/ginkgo/performance/_fixtures/performance_fixture/pkg4package pkg4_test import ( "fmt" "testing" "time" "github.com/gorilla/mux" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" "github.com/tdewolff/minify/v2" "golang.org/x/crypto/bcrypt" "gopkg.in/yaml.v2" ) func TestPkg4(t *testing.T) { RegisterFailHandler(Fail) RunSpecs(t, "Pkg4 Suite") mux.NewRouter() fmt.Println(bcrypt.MinCost) fmt.Println(yaml.Decoder{}) fmt.Println(minify.MinInt) } var _ = Describe("Pkg4", func() { for i := 0; i < 10; i++ { It(fmt.Sprintf("sleeps %d", i), func() { time.Sleep(time.Millisecond * 10) }) } }) golang-github-onsi-ginkgo-v2-2.22.0/ginkgo/performance/compiling-and-running-multiple-cache/000077500000000000000000000000001472321612100317425ustar00rootroot00000000000000096fe2f06efcf59513adb6cb0275d32a.gmeasure-cache000066400000000000000000000124721472321612100412000ustar00rootroot00000000000000golang-github-onsi-ginkgo-v2-2.22.0/ginkgo/performance/compiling-and-running-multiple-cache{"Name":"performance - compile concurrently [4] - run concurrently [4] - will compile first suite serially","Version":1} {"Name":"performance - compile concurrently [4] - run concurrently [4] - will compile first suite serially","Measurements":[{"Type":"Duration","ExperimentName":"performance - compile concurrently [4] - run concurrently [4] - will compile first suite serially","Note":"","Name":"compile-test: performance","Style":"","Units":"duration","PrecisionBundle":{"Duration":100000,"ValueFormat":"%.3f"},"Durations":[1537620386,1416844561,1777458293,1481261510,1195154929,1076283746,1140008286,1071951567],"Values":null,"Annotations":["5","23","41","42","52","57","66","73"]},{"Type":"Duration","ExperimentName":"performance - compile concurrently [4] - run concurrently [4] - will compile first suite serially","Note":"","Name":"first-output","Style":"{{cyan}}","Units":"duration","PrecisionBundle":{"Duration":100000,"ValueFormat":"%.3f"},"Durations":[1539959564,1419313549,1779947930,1483951775,1197148474,1078675530,1142540613,1073800839],"Values":null,"Annotations":["5","23","41","42","52","57","66","73"]},{"Type":"Duration","ExperimentName":"performance - compile concurrently [4] - run concurrently [4] - will compile first suite serially","Note":"","Name":"run-test: performance","Style":"","Units":"duration","PrecisionBundle":{"Duration":100000,"ValueFormat":"%.3f"},"Durations":[1079516829,909794591,998973390,965853801,911987421,1013242666,1068536819,971871348],"Values":null,"Annotations":["5","23","41","42","52","57","66","73"]},{"Type":"Duration","ExperimentName":"performance - compile concurrently [4] - run concurrently [4] - will compile first suite serially","Note":"","Name":"compile-test: pkg1","Style":"","Units":"duration","PrecisionBundle":{"Duration":100000,"ValueFormat":"%.3f"},"Durations":[2973205809,3186088144,3260247611,3105912264,3198833793,3131861948,3472011136,2840734292],"Values":null,"Annotations":["5","23","41","42","52","57","66","73"]},{"Type":"Duration","ExperimentName":"performance - compile concurrently [4] - run concurrently [4] - will compile first suite serially","Note":"","Name":"compile-test: pkg4","Style":"","Units":"duration","PrecisionBundle":{"Duration":100000,"ValueFormat":"%.3f"},"Durations":[3188058713,3146069120,3180736401,2908517209,3120724906,2951275773,2913980638,3055138089],"Values":null,"Annotations":["5","23","41","42","52","57","66","73"]},{"Type":"Duration","ExperimentName":"performance - compile concurrently [4] - run concurrently [4] - will compile first suite serially","Note":"","Name":"compile-test: pkg3","Style":"","Units":"duration","PrecisionBundle":{"Duration":100000,"ValueFormat":"%.3f"},"Durations":[3191893729,3286923896,3392881060,3073720440,3252946338,3106431597,3274098650,3176136139],"Values":null,"Annotations":["5","23","41","42","52","57","66","73"]},{"Type":"Duration","ExperimentName":"performance - compile concurrently [4] - run concurrently [4] - will compile first suite serially","Note":"","Name":"compile-test: pkg2","Style":"","Units":"duration","PrecisionBundle":{"Duration":100000,"ValueFormat":"%.3f"},"Durations":[3290593268,3340771602,3200334493,2900995923,3197231774,2925026272,3198039535,3076408544],"Values":null,"Annotations":["5","23","41","42","52","57","66","73"]},{"Type":"Duration","ExperimentName":"performance - compile concurrently [4] - run concurrently [4] - will compile first suite serially","Note":"","Name":"run-test: pkg1","Style":"","Units":"duration","PrecisionBundle":{"Duration":100000,"ValueFormat":"%.3f"},"Durations":[508872296,662460747,964332795,1145245591,808862894,806161893,643791709,491566963],"Values":null,"Annotations":["5","23","41","42","52","57","66","73"]},{"Type":"Duration","ExperimentName":"performance - compile concurrently [4] - run concurrently [4] - will compile first suite serially","Note":"","Name":"run-test: pkg4","Style":"","Units":"duration","PrecisionBundle":{"Duration":100000,"ValueFormat":"%.3f"},"Durations":[595056455,430603675,476568604,766497014,405214146,596035418,570448364,445015230],"Values":null,"Annotations":["5","23","41","42","52","57","66","73"]},{"Type":"Duration","ExperimentName":"performance - compile concurrently [4] - run concurrently [4] - will compile first suite serially","Note":"","Name":"run-test: pkg3","Style":"","Units":"duration","PrecisionBundle":{"Duration":100000,"ValueFormat":"%.3f"},"Durations":[910276017,869157019,1114103218,903516085,970707025,639004887,635014354,739583231],"Values":null,"Annotations":["5","23","41","42","52","57","66","73"]},{"Type":"Duration","ExperimentName":"performance - compile concurrently [4] - run concurrently [4] - will compile first suite serially","Note":"","Name":"run-test: pkg2","Style":"","Units":"duration","PrecisionBundle":{"Duration":100000,"ValueFormat":"%.3f"},"Durations":[1118595442,1112073145,776704122,468164049,577348790,419155185,523156694,644754424],"Values":null,"Annotations":["5","23","41","42","52","57","66","73"]},{"Type":"Duration","ExperimentName":"performance - compile concurrently [4] - run concurrently [4] - will compile first suite serially","Note":"","Name":"total-runtime","Style":"{{green}}","Units":"duration","PrecisionBundle":{"Duration":100000,"ValueFormat":"%.3f"},"Durations":[5949039329,5871940649,6286670226,5734780957,5420488182,5016430718,5258103437,4989215897],"Values":null,"Annotations":["5","23","41","42","52","57","66","73"]}]} 1baac032c37c6eddc8381c02f73bab9c.gmeasure-cache000066400000000000000000000123471472321612100413210ustar00rootroot00000000000000golang-github-onsi-ginkgo-v2-2.22.0/ginkgo/performance/compiling-and-running-multiple-cache{"Name":"performance - compile concurrently [2] - run serially - will compile first suite serially","Version":1} {"Name":"performance - compile concurrently [2] - run serially - will compile first suite serially","Measurements":[{"Type":"Duration","ExperimentName":"performance - compile concurrently [2] - run serially - will compile first suite serially","Note":"","Name":"compile-test: performance","Style":"","Units":"duration","PrecisionBundle":{"Duration":100000,"ValueFormat":"%.3f"},"Durations":[1380289036,1388629758,1224510404,1088856002,1089181110,1107697711,1081884547,1118733377],"Values":null,"Annotations":["24","27","51","71","72","86","103","107"]},{"Type":"Duration","ExperimentName":"performance - compile concurrently [2] - run serially - will compile first suite serially","Note":"","Name":"first-output","Style":"{{cyan}}","Units":"duration","PrecisionBundle":{"Duration":100000,"ValueFormat":"%.3f"},"Durations":[1382223883,1391191835,1226124864,1090744759,1090750481,1109135264,1083457122,1120263980],"Values":null,"Annotations":["24","27","51","71","72","86","103","107"]},{"Type":"Duration","ExperimentName":"performance - compile concurrently [2] - run serially - will compile first suite serially","Note":"","Name":"run-test: performance","Style":"","Units":"duration","PrecisionBundle":{"Duration":100000,"ValueFormat":"%.3f"},"Durations":[694970657,764220348,772985552,666011794,764648369,694642019,669281998,791362529],"Values":null,"Annotations":["24","27","51","71","72","86","103","107"]},{"Type":"Duration","ExperimentName":"performance - compile concurrently [2] - run serially - will compile first suite serially","Note":"","Name":"compile-test: pkg2","Style":"","Units":"duration","PrecisionBundle":{"Duration":100000,"ValueFormat":"%.3f"},"Durations":[1931868225,1908791648,1856717311,1727025503,1788831257,1741327796,1807562182,1856539473],"Values":null,"Annotations":["24","27","51","71","72","86","103","107"]},{"Type":"Duration","ExperimentName":"performance - compile concurrently [2] - run serially - will compile first suite serially","Note":"","Name":"compile-test: pkg1","Style":"","Units":"duration","PrecisionBundle":{"Duration":100000,"ValueFormat":"%.3f"},"Durations":[1934492471,1914405080,1851371407,1733391745,1728630213,1770817363,1695606023,1670826736],"Values":null,"Annotations":["24","27","51","71","72","86","103","107"]},{"Type":"Duration","ExperimentName":"performance - compile concurrently [2] - run serially - will compile first suite serially","Note":"","Name":"run-test: pkg2","Style":"","Units":"duration","PrecisionBundle":{"Duration":100000,"ValueFormat":"%.3f"},"Durations":[791452577,769476628,589498175,719497309,569812103,771105522,641269141,624880424],"Values":null,"Annotations":["24","27","51","71","72","86","103","107"]},{"Type":"Duration","ExperimentName":"performance - compile concurrently [2] - run serially - will compile first suite serially","Note":"","Name":"run-test: pkg1","Style":"","Units":"duration","PrecisionBundle":{"Duration":100000,"ValueFormat":"%.3f"},"Durations":[553879865,528158222,742961702,587499837,749797272,518513509,676949538,663058677],"Values":null,"Annotations":["24","27","51","71","72","86","103","107"]},{"Type":"Duration","ExperimentName":"performance - compile concurrently [2] - run serially - will compile first suite serially","Note":"","Name":"compile-test: pkg4","Style":"","Units":"duration","PrecisionBundle":{"Duration":100000,"ValueFormat":"%.3f"},"Durations":[2041150741,1892514543,2031983695,1824614284,1858721233,1840377550,1804521270,1700882314],"Values":null,"Annotations":["24","27","51","71","72","86","103","107"]},{"Type":"Duration","ExperimentName":"performance - compile concurrently [2] - run serially - will compile first suite serially","Note":"","Name":"compile-test: pkg3","Style":"","Units":"duration","PrecisionBundle":{"Duration":100000,"ValueFormat":"%.3f"},"Durations":[2064493898,2006613361,2042444830,1842655242,1905020971,1735994689,1803069970,1777300169],"Values":null,"Annotations":["24","27","51","71","72","86","103","107"]},{"Type":"Duration","ExperimentName":"performance - compile concurrently [2] - run serially - will compile first suite serially","Note":"","Name":"run-test: pkg4","Style":"","Units":"duration","PrecisionBundle":{"Duration":100000,"ValueFormat":"%.3f"},"Durations":[461569692,395533422,369516844,377203260,376728472,362713562,347315273,357407590],"Values":null,"Annotations":["24","27","51","71","72","86","103","107"]},{"Type":"Duration","ExperimentName":"performance - compile concurrently [2] - run serially - will compile first suite serially","Note":"","Name":"run-test: pkg3","Style":"","Units":"duration","PrecisionBundle":{"Duration":100000,"ValueFormat":"%.3f"},"Durations":[458850103,436247406,385491832,352810421,353156345,388333755,384895300,379487846],"Values":null,"Annotations":["24","27","51","71","72","86","103","107"]},{"Type":"Duration","ExperimentName":"performance - compile concurrently [2] - run serially - will compile first suite serially","Note":"","Name":"total-runtime","Style":"{{green}}","Units":"duration","PrecisionBundle":{"Duration":100000,"ValueFormat":"%.3f"},"Durations":[6278406877,6030046148,5870005532,5378835629,5454417871,5337748781,5314442226,5305356900],"Values":null,"Annotations":["24","27","51","71","72","86","103","107"]}]} 2daaa9348ce35e03f3fb34ec83ffc542.gmeasure-cache000066400000000000000000000111471472321612100412570ustar00rootroot00000000000000golang-github-onsi-ginkgo-v2-2.22.0/ginkgo/performance/compiling-and-running-multiple-cache{"Name":"performance - go test -c; then run (serially)","Version":1} {"Name":"performance - go test -c; then run (serially)","Measurements":[{"Type":"Duration","ExperimentName":"performance - go test -c; then run (serially)","Note":"","Name":"compile-test: performance","Style":"","Units":"duration","PrecisionBundle":{"Duration":100000,"ValueFormat":"%.3f"},"Durations":[1392314197,1518336707,1236571246,1139193966,1151917365,1087509838,1149461280,1085818875],"Values":null,"Annotations":["3","15","17","65","69","82","92","108"]},{"Type":"Duration","ExperimentName":"performance - go test -c; then run (serially)","Note":"","Name":"first-output","Style":"{{cyan}}","Units":"duration","PrecisionBundle":{"Duration":100000,"ValueFormat":"%.3f"},"Durations":[1394500016,1520645530,1238166637,1140847352,1153217982,1088832527,1151451708,1087233114],"Values":null,"Annotations":["3","15","17","65","69","82","92","108"]},{"Type":"Duration","ExperimentName":"performance - go test -c; then run (serially)","Note":"","Name":"run-test: performance","Style":"","Units":"duration","PrecisionBundle":{"Duration":100000,"ValueFormat":"%.3f"},"Durations":[458631269,461020000,403094125,358151609,380126102,364017337,385820324,377776470],"Values":null,"Annotations":["3","15","17","65","69","82","92","108"]},{"Type":"Duration","ExperimentName":"performance - go test -c; then run (serially)","Note":"","Name":"compile-test: pkg1","Style":"","Units":"duration","PrecisionBundle":{"Duration":100000,"ValueFormat":"%.3f"},"Durations":[1603545355,1450342009,1242434670,1140084856,1090504716,1063037204,1099230665,1088413679],"Values":null,"Annotations":["3","15","17","65","69","82","92","108"]},{"Type":"Duration","ExperimentName":"performance - go test -c; then run (serially)","Note":"","Name":"run-test: pkg1","Style":"","Units":"duration","PrecisionBundle":{"Duration":100000,"ValueFormat":"%.3f"},"Durations":[473558279,474443242,402356572,357925157,382898479,381092237,378193438,360288080],"Values":null,"Annotations":["3","15","17","65","69","82","92","108"]},{"Type":"Duration","ExperimentName":"performance - go test -c; then run (serially)","Note":"","Name":"compile-test: pkg2","Style":"","Units":"duration","PrecisionBundle":{"Duration":100000,"ValueFormat":"%.3f"},"Durations":[1491860554,1236868772,1378613250,1133405089,1112350225,1080933254,1078499069,1089301549],"Values":null,"Annotations":["3","15","17","65","69","82","92","108"]},{"Type":"Duration","ExperimentName":"performance - go test -c; then run (serially)","Note":"","Name":"run-test: pkg2","Style":"","Units":"duration","PrecisionBundle":{"Duration":100000,"ValueFormat":"%.3f"},"Durations":[435745875,398358030,464475700,345613252,356126754,375165144,376141660,373593426],"Values":null,"Annotations":["3","15","17","65","69","82","92","108"]},{"Type":"Duration","ExperimentName":"performance - go test -c; then run (serially)","Note":"","Name":"compile-test: pkg3","Style":"","Units":"duration","PrecisionBundle":{"Duration":100000,"ValueFormat":"%.3f"},"Durations":[1510003956,1198513753,1427694123,1156712248,1077318210,1069573040,1058649364,1095010940],"Values":null,"Annotations":["3","15","17","65","69","82","92","108"]},{"Type":"Duration","ExperimentName":"performance - go test -c; then run (serially)","Note":"","Name":"run-test: pkg3","Style":"","Units":"duration","PrecisionBundle":{"Duration":100000,"ValueFormat":"%.3f"},"Durations":[458120943,403999686,450586782,345961518,542741381,371631946,362051165,360341078],"Values":null,"Annotations":["3","15","17","65","69","82","92","108"]},{"Type":"Duration","ExperimentName":"performance - go test -c; then run (serially)","Note":"","Name":"compile-test: pkg4","Style":"","Units":"duration","PrecisionBundle":{"Duration":100000,"ValueFormat":"%.3f"},"Durations":[1560763068,1220505992,1428648393,1117822744,1100135358,1071509745,1073140265,1097228588],"Values":null,"Annotations":["3","15","17","65","69","82","92","108"]},{"Type":"Duration","ExperimentName":"performance - go test -c; then run (serially)","Note":"","Name":"run-test: pkg4","Style":"","Units":"duration","PrecisionBundle":{"Duration":100000,"ValueFormat":"%.3f"},"Durations":[466251158,436169944,458284686,358868901,372749955,370928743,374191299,375976970],"Values":null,"Annotations":["3","15","17","65","69","82","92","108"]},{"Type":"Duration","ExperimentName":"performance - go test -c; then run (serially)","Note":"","Name":"total-runtime","Style":"{{green}}","Units":"duration","PrecisionBundle":{"Duration":100000,"ValueFormat":"%.3f"},"Durations":[9863211549,8808360958,8902420063,7462921272,7574677690,7244590431,7344251346,7312477772],"Values":null,"Annotations":["3","15","17","65","69","82","92","108"]}]} 34fa32127a8597d465872129ed999b27.gmeasure-cache000066400000000000000000000114731472321612100405150ustar00rootroot00000000000000golang-github-onsi-ginkgo-v2-2.22.0/ginkgo/performance/compiling-and-running-multiple-cache{"Name":"performance - compile concurrently [4] - run concurrently [2]","Version":1} {"Name":"performance - compile concurrently [4] - run concurrently [2]","Measurements":[{"Type":"Duration","ExperimentName":"performance - compile concurrently [4] - run concurrently [2]","Note":"","Name":"compile-test: pkg1","Style":"","Units":"duration","PrecisionBundle":{"Duration":100000,"ValueFormat":"%.3f"},"Durations":[2989247403,3016946570,3260493767,3259363560,2715960685,3240876553,3295933379,2814730861],"Values":null,"Annotations":["6","20","53","63","81","83","87","93"]},{"Type":"Duration","ExperimentName":"performance - compile concurrently [4] - run concurrently [2]","Note":"","Name":"first-output","Style":"{{cyan}}","Units":"duration","PrecisionBundle":{"Duration":100000,"ValueFormat":"%.3f"},"Durations":[2991979244,3019387359,2802665787,2965482675,2718274313,2787879205,2829073503,2816659669],"Values":null,"Annotations":["6","20","53","63","81","83","87","93"]},{"Type":"Duration","ExperimentName":"performance - compile concurrently [4] - run concurrently [2]","Note":"","Name":"compile-test: performance","Style":"","Units":"duration","PrecisionBundle":{"Duration":100000,"ValueFormat":"%.3f"},"Durations":[3172721182,3317815306,3311107854,3339636938,3503699685,3235159541,3142080005,3220224709],"Values":null,"Annotations":["6","20","53","63","81","83","87","93"]},{"Type":"Duration","ExperimentName":"performance - compile concurrently [4] - run concurrently [2]","Note":"","Name":"compile-test: pkg2","Style":"","Units":"duration","PrecisionBundle":{"Duration":100000,"ValueFormat":"%.3f"},"Durations":[3387969495,3416405671,3139251217,3374620569,3133142586,3211620667,2827095066,3239190521],"Values":null,"Annotations":["6","20","53","63","81","83","87","93"]},{"Type":"Duration","ExperimentName":"performance - compile concurrently [4] - run concurrently [2]","Note":"","Name":"compile-test: pkg3","Style":"","Units":"duration","PrecisionBundle":{"Duration":100000,"ValueFormat":"%.3f"},"Durations":[3520118217,3018329376,2801109786,2963647960,3219683275,2785709218,3184566960,3274498288],"Values":null,"Annotations":["6","20","53","63","81","83","87","93"]},{"Type":"Duration","ExperimentName":"performance - compile concurrently [4] - run concurrently [2]","Note":"","Name":"run-test: pkg1","Style":"","Units":"duration","PrecisionBundle":{"Duration":100000,"ValueFormat":"%.3f"},"Durations":[706794525,647545633,635736602,779158580,743557813,511108635,462179555,589132979],"Values":null,"Annotations":["6","20","53","63","81","83","87","93"]},{"Type":"Duration","ExperimentName":"performance - compile concurrently [4] - run concurrently [2]","Note":"","Name":"run-test: performance","Style":"","Units":"duration","PrecisionBundle":{"Duration":100000,"ValueFormat":"%.3f"},"Durations":[898449941,655364352,571513464,591161036,472468810,607342545,677734116,533564428],"Values":null,"Annotations":["6","20","53","63","81","83","87","93"]},{"Type":"Duration","ExperimentName":"performance - compile concurrently [4] - run concurrently [2]","Note":"","Name":"run-test: pkg2","Style":"","Units":"duration","PrecisionBundle":{"Duration":100000,"ValueFormat":"%.3f"},"Durations":[744856351,634034632,695008910,519096434,662284227,615106405,647599842,596260548],"Values":null,"Annotations":["6","20","53","63","81","83","87","93"]},{"Type":"Duration","ExperimentName":"performance - compile concurrently [4] - run concurrently [2]","Note":"","Name":"run-test: pkg3","Style":"","Units":"duration","PrecisionBundle":{"Duration":100000,"ValueFormat":"%.3f"},"Durations":[642263897,998997830,678869881,731092249,564266612,682849257,578059561,508069886],"Values":null,"Annotations":["6","20","53","63","81","83","87","93"]},{"Type":"Duration","ExperimentName":"performance - compile concurrently [4] - run concurrently [2]","Note":"","Name":"compile-test: pkg4","Style":"","Units":"duration","PrecisionBundle":{"Duration":100000,"ValueFormat":"%.3f"},"Durations":[1967556824,1704901227,1735680854,1601993174,1641315562,1595999090,1552605136,1593595625],"Values":null,"Annotations":["6","20","53","63","81","83","87","93"]},{"Type":"Duration","ExperimentName":"performance - compile concurrently [4] - run concurrently [2]","Note":"","Name":"run-test: pkg4","Style":"","Units":"duration","PrecisionBundle":{"Duration":100000,"ValueFormat":"%.3f"},"Durations":[451161144,431679689,358000709,364263912,350325674,360511663,337798099,370918052],"Values":null,"Annotations":["6","20","53","63","81","83","87","93"]},{"Type":"Duration","ExperimentName":"performance - compile concurrently [4] - run concurrently [2]","Note":"","Name":"total-runtime","Style":"{{green}}","Units":"duration","PrecisionBundle":{"Duration":100000,"ValueFormat":"%.3f"},"Durations":[5410752120,5156064166,4896398654,4931797961,4709992761,4744458696,4719542020,4781240710],"Values":null,"Annotations":["6","20","53","63","81","83","87","93"]}]} 585dde06f8799aa0686577e4c76eee50.gmeasure-cache000066400000000000000000000015111472321612100410150ustar00rootroot00000000000000golang-github-onsi-ginkgo-v2-2.22.0/ginkgo/performance/compiling-and-running-multiple-cache{"Name":"performance - go test ./...","Version":1} {"Name":"performance - go test ./...","Measurements":[{"Type":"Duration","ExperimentName":"performance - go test ./...","Note":"","Name":"first-output","Style":"{{cyan}}","Units":"duration","PrecisionBundle":{"Duration":100000,"ValueFormat":"%.3f"},"Durations":[2444193650,1956693507,1968474863,1864441551,2348075315,1957443513,2065954826,1897190705],"Values":null,"Annotations":["7","8","30","46","48","88","96","99"]},{"Type":"Duration","ExperimentName":"performance - go test ./...","Note":"","Name":"total-runtime","Style":"{{green}}","Units":"duration","PrecisionBundle":{"Duration":100000,"ValueFormat":"%.3f"},"Durations":[3035727266,3132269843,3019201036,2906942734,2969542098,2698585161,2564078046,2599610240],"Values":null,"Annotations":["7","8","30","46","48","88","96","99"]}]} 5dde0a74da03182e70d258dbc7f9da5e.gmeasure-cache000066400000000000000000000053071472321612100412600ustar00rootroot00000000000000golang-github-onsi-ginkgo-v2-2.22.0/ginkgo/performance/compiling-and-running-multiple-cache{"Name":"performance - go test - run concurrently [8]","Version":1} {"Name":"performance - go test - run concurrently [8]","Measurements":[{"Type":"Duration","ExperimentName":"performance - go test - run concurrently [8]","Note":"","Name":"first-output","Style":"{{cyan}}","Units":"duration","PrecisionBundle":{"Duration":100000,"ValueFormat":"%.3f"},"Durations":[3717888479,3532529247,3726604743,3630148013,3526885802,3631661241,3606530658,3658063991],"Values":null,"Annotations":["1","2","3","4","5","6","7","8"]},{"Type":"Duration","ExperimentName":"performance - go test - run concurrently [8]","Note":"","Name":"run-test: pkg2","Style":"","Units":"duration","PrecisionBundle":{"Duration":100000,"ValueFormat":"%.3f"},"Durations":[3860165489,4845851460,4364512716,3786373380,3909866821,4338106790,3757565723,4218668386],"Values":null,"Annotations":["1","2","3","4","5","6","7","8"]},{"Type":"Duration","ExperimentName":"performance - go test - run concurrently [8]","Note":"","Name":"run-test: performance","Style":"","Units":"duration","PrecisionBundle":{"Duration":100000,"ValueFormat":"%.3f"},"Durations":[4079538571,4577599033,4927508120,4649464553,3658142134,4904821723,4595364863,4590592114],"Values":null,"Annotations":["1","2","3","4","5","6","7","8"]},{"Type":"Duration","ExperimentName":"performance - go test - run concurrently [8]","Note":"","Name":"run-test: pkg1","Style":"","Units":"duration","PrecisionBundle":{"Duration":100000,"ValueFormat":"%.3f"},"Durations":[4278833205,4278757522,3865302218,4223702185,4166280245,4614513408,4039806548,4024196831],"Values":null,"Annotations":["1","2","3","4","5","6","7","8"]},{"Type":"Duration","ExperimentName":"performance - go test - run concurrently [8]","Note":"","Name":"run-test: pkg4","Style":"","Units":"duration","PrecisionBundle":{"Duration":100000,"ValueFormat":"%.3f"},"Durations":[4509788195,3933378444,4659086468,4444678492,4668802233,3779428160,4326791529,4423774304],"Values":null,"Annotations":["1","2","3","4","5","6","7","8"]},{"Type":"Duration","ExperimentName":"performance - go test - run concurrently [8]","Note":"","Name":"run-test: pkg3","Style":"","Units":"duration","PrecisionBundle":{"Duration":100000,"ValueFormat":"%.3f"},"Durations":[4697268667,3680947275,4115489793,4026872063,4413470466,4039368121,4866993971,3791594971],"Values":null,"Annotations":["1","2","3","4","5","6","7","8"]},{"Type":"Duration","ExperimentName":"performance - go test - run concurrently [8]","Note":"","Name":"total-runtime","Style":"{{green}}","Units":"duration","PrecisionBundle":{"Duration":100000,"ValueFormat":"%.3f"},"Durations":[4701221177,4849048431,4929865719,4652770454,4672269025,4907458999,4869566556,4593320193],"Values":null,"Annotations":["1","2","3","4","5","6","7","8"]}]} 63f93d6849139270a9cf6c7562b5c5c2.gmeasure-cache000066400000000000000000000113571472321612100406500ustar00rootroot00000000000000golang-github-onsi-ginkgo-v2-2.22.0/ginkgo/performance/compiling-and-running-multiple-cache{"Name":"performance - compile concurrently [2] - run serially","Version":1} {"Name":"performance - compile concurrently [2] - run serially","Measurements":[{"Type":"Duration","ExperimentName":"performance - compile concurrently [2] - run serially","Note":"","Name":"compile-test: performance","Style":"","Units":"duration","PrecisionBundle":{"Duration":100000,"ValueFormat":"%.3f"},"Durations":[1906635632,1857154955,1851731078,1753434388,1605571095,1592287874,1623001426,1669506894],"Values":null,"Annotations":["10","19","44","49","75","97","111","112"]},{"Type":"Duration","ExperimentName":"performance - compile concurrently [2] - run serially","Note":"","Name":"first-output","Style":"{{cyan}}","Units":"duration","PrecisionBundle":{"Duration":100000,"ValueFormat":"%.3f"},"Durations":[1909099426,1860436792,1852505408,1755297857,1594939793,1594281648,1624428627,1652548953],"Values":null,"Annotations":["10","19","44","49","75","97","111","112"]},{"Type":"Duration","ExperimentName":"performance - compile concurrently [2] - run serially","Note":"","Name":"compile-test: pkg1","Style":"","Units":"duration","PrecisionBundle":{"Duration":100000,"ValueFormat":"%.3f"},"Durations":[1948009050,1886040633,1850239998,1781392847,1593467119,1622477972,1622926637,1651013642],"Values":null,"Annotations":["10","19","44","49","75","97","111","112"]},{"Type":"Duration","ExperimentName":"performance - compile concurrently [2] - run serially","Note":"","Name":"run-test: performance","Style":"","Units":"duration","PrecisionBundle":{"Duration":100000,"ValueFormat":"%.3f"},"Durations":[663210939,675351121,773519143,755614792,605194816,676069554,714816622,622228935],"Values":null,"Annotations":["10","19","44","49","75","97","111","112"]},{"Type":"Duration","ExperimentName":"performance - compile concurrently [2] - run serially","Note":"","Name":"run-test: pkg1","Style":"","Units":"duration","PrecisionBundle":{"Duration":100000,"ValueFormat":"%.3f"},"Durations":[660170625,606049852,600851838,558918719,646592682,601366789,561847409,700635312],"Values":null,"Annotations":["10","19","44","49","75","97","111","112"]},{"Type":"Duration","ExperimentName":"performance - compile concurrently [2] - run serially","Note":"","Name":"compile-test: pkg2","Style":"","Units":"duration","PrecisionBundle":{"Duration":100000,"ValueFormat":"%.3f"},"Durations":[2083481340,1962053322,2051419006,1997882542,1822146621,1821099093,1813442202,1980728087],"Values":null,"Annotations":["10","19","44","49","75","97","111","112"]},{"Type":"Duration","ExperimentName":"performance - compile concurrently [2] - run serially","Note":"","Name":"compile-test: pkg3","Style":"","Units":"duration","PrecisionBundle":{"Duration":100000,"ValueFormat":"%.3f"},"Durations":[2074080799,2084111968,2236057578,1980261275,1814007247,1800129589,1965726897,1956394875],"Values":null,"Annotations":["10","19","44","49","75","97","111","112"]},{"Type":"Duration","ExperimentName":"performance - compile concurrently [2] - run serially","Note":"","Name":"run-test: pkg2","Style":"","Units":"duration","PrecisionBundle":{"Duration":100000,"ValueFormat":"%.3f"},"Durations":[479801154,494186237,532905749,482964772,447797812,459213924,449143020,457183520],"Values":null,"Annotations":["10","19","44","49","75","97","111","112"]},{"Type":"Duration","ExperimentName":"performance - compile concurrently [2] - run serially","Note":"","Name":"run-test: pkg3","Style":"","Units":"duration","PrecisionBundle":{"Duration":100000,"ValueFormat":"%.3f"},"Durations":[498743057,481739635,452241860,461297824,427495824,412454613,419290081,455050546],"Values":null,"Annotations":["10","19","44","49","75","97","111","112"]},{"Type":"Duration","ExperimentName":"performance - compile concurrently [2] - run serially","Note":"","Name":"compile-test: pkg4","Style":"","Units":"duration","PrecisionBundle":{"Duration":100000,"ValueFormat":"%.3f"},"Durations":[1625764109,1592419716,1414215026,1355178479,1232998249,1216081025,1282185988,1269047325],"Values":null,"Annotations":["10","19","44","49","75","97","111","112"]},{"Type":"Duration","ExperimentName":"performance - compile concurrently [2] - run serially","Note":"","Name":"run-test: pkg4","Style":"","Units":"duration","PrecisionBundle":{"Duration":100000,"ValueFormat":"%.3f"},"Durations":[465002005,432123653,380965154,382794413,347392952,355217048,360875037,338197737],"Values":null,"Annotations":["10","19","44","49","75","97","111","112"]},{"Type":"Duration","ExperimentName":"performance - compile concurrently [2] - run serially","Note":"","Name":"total-runtime","Style":"{{green}}","Units":"duration","PrecisionBundle":{"Duration":100000,"ValueFormat":"%.3f"},"Durations":[6083420280,5847141469,5699206306,5491220610,4997577161,4986751991,5081019224,5234722908],"Values":null,"Annotations":["10","19","44","49","75","97","111","112"]}]} 7e079ed53f27697e0ae7f18ed2eef571.gmeasure-cache000066400000000000000000000053701472321612100411550ustar00rootroot00000000000000golang-github-onsi-ginkgo-v2-2.22.0/ginkgo/performance/compiling-and-running-multiple-cache{"Name":"performance - go test - run concurrently [2]","Version":1} {"Name":"performance - go test - run concurrently [2]","Measurements":[{"Type":"Duration","ExperimentName":"performance - go test - run concurrently [2]","Note":"","Name":"first-output","Style":"{{cyan}}","Units":"duration","PrecisionBundle":{"Duration":100000,"ValueFormat":"%.3f"},"Durations":[1953164726,1841905865,1751628685,1741552243,1542147725,1568959985,1617464486,1819447164],"Values":null,"Annotations":["2","12","26","45","55","56","64","91"]},{"Type":"Duration","ExperimentName":"performance - go test - run concurrently [2]","Note":"","Name":"run-test: performance","Style":"","Units":"duration","PrecisionBundle":{"Duration":100000,"ValueFormat":"%.3f"},"Durations":[2094849802,2270806125,2176815632,2178395842,1889571604,1963779847,1768479727,2174118055],"Values":null,"Annotations":["2","12","26","45","55","56","64","91"]},{"Type":"Duration","ExperimentName":"performance - go test - run concurrently [2]","Note":"","Name":"run-test: pkg1","Style":"","Units":"duration","PrecisionBundle":{"Duration":100000,"ValueFormat":"%.3f"},"Durations":[2392494704,1976707297,1914429660,1917536309,1696362557,1720054593,1980857448,1990729058],"Values":null,"Annotations":["2","12","26","45","55","56","64","91"]},{"Type":"Duration","ExperimentName":"performance - go test - run concurrently [2]","Note":"","Name":"run-test: pkg2","Style":"","Units":"duration","PrecisionBundle":{"Duration":100000,"ValueFormat":"%.3f"},"Durations":[2042034929,2012425768,1925504275,1942419203,1658568481,1696817479,1727024804,1714228387],"Values":null,"Annotations":["2","12","26","45","55","56","64","91"]},{"Type":"Duration","ExperimentName":"performance - go test - run concurrently [2]","Note":"","Name":"run-test: pkg3","Style":"","Units":"duration","PrecisionBundle":{"Duration":100000,"ValueFormat":"%.3f"},"Durations":[2157470297,1980562550,1969241866,1923163155,1641931619,1633958986,1724442390,1728440611],"Values":null,"Annotations":["2","12","26","45","55","56","64","91"]},{"Type":"Duration","ExperimentName":"performance - go test - run concurrently [2]","Note":"","Name":"run-test: pkg4","Style":"","Units":"duration","PrecisionBundle":{"Duration":100000,"ValueFormat":"%.3f"},"Durations":[1607659250,1739619839,1586802829,1557977998,1307626826,1302738296,1306951062,1269181085],"Values":null,"Annotations":["2","12","26","45","55","56","64","91"]},{"Type":"Duration","ExperimentName":"performance - go test - run concurrently [2]","Note":"","Name":"total-runtime","Style":"{{green}}","Units":"duration","PrecisionBundle":{"Duration":100000,"ValueFormat":"%.3f"},"Durations":[5747039521,5731201281,5429288003,5420127923,4664600596,4722118944,4805035951,4976145422],"Values":null,"Annotations":["2","12","26","45","55","56","64","91"]}]} 952f6c671d14a219922ff81d601950dc.gmeasure-cache000066400000000000000000000113131472321612100406210ustar00rootroot00000000000000golang-github-onsi-ginkgo-v2-2.22.0/ginkgo/performance/compiling-and-running-multiple-cache{"Name":"performance - compile concurrently [4] - run serially","Version":1} {"Name":"performance - compile concurrently [4] - run serially","Measurements":[{"Type":"Duration","ExperimentName":"performance - compile concurrently [4] - run serially","Note":"","Name":"compile-test: pkg1","Style":"","Units":"duration","PrecisionBundle":{"Duration":100000,"ValueFormat":"%.3f"},"Durations":[2984186953,3423890952,3226816909,2932459695,3471944606,2848722507,3204793265,2808131415],"Values":null,"Annotations":["9","21","22","31","47","58","74","77"]},{"Type":"Duration","ExperimentName":"performance - compile concurrently [4] - run serially","Note":"","Name":"first-output","Style":"{{cyan}}","Units":"duration","PrecisionBundle":{"Duration":100000,"ValueFormat":"%.3f"},"Durations":[2986039424,2848986344,2788033197,2934393210,3047538887,2850138956,2708054936,2809631481],"Values":null,"Annotations":["9","21","22","31","47","58","74","77"]},{"Type":"Duration","ExperimentName":"performance - compile concurrently [4] - run serially","Note":"","Name":"compile-test: pkg2","Style":"","Units":"duration","PrecisionBundle":{"Duration":100000,"ValueFormat":"%.3f"},"Durations":[3346903629,3510656413,3381894406,3403632453,3296658194,3152775159,3105537590,3270941713],"Values":null,"Annotations":["9","21","22","31","47","58","74","77"]},{"Type":"Duration","ExperimentName":"performance - compile concurrently [4] - run serially","Note":"","Name":"compile-test: performance","Style":"","Units":"duration","PrecisionBundle":{"Duration":100000,"ValueFormat":"%.3f"},"Durations":[3387053040,3517404027,3301716978,3273590541,3045561108,3287052512,3250850146,3211905972],"Values":null,"Annotations":["9","21","22","31","47","58","74","77"]},{"Type":"Duration","ExperimentName":"performance - compile concurrently [4] - run serially","Note":"","Name":"compile-test: pkg3","Style":"","Units":"duration","PrecisionBundle":{"Duration":100000,"ValueFormat":"%.3f"},"Durations":[3412229519,2846895909,2785851365,3389368812,3660196622,3304260991,2706666099,3033294070],"Values":null,"Annotations":["9","21","22","31","47","58","74","77"]},{"Type":"Duration","ExperimentName":"performance - compile concurrently [4] - run serially","Note":"","Name":"run-test: pkg1","Style":"","Units":"duration","PrecisionBundle":{"Duration":100000,"ValueFormat":"%.3f"},"Durations":[649829974,458421734,489817290,678385415,453731681,639346353,394246577,664101154],"Values":null,"Annotations":["9","21","22","31","47","58","74","77"]},{"Type":"Duration","ExperimentName":"performance - compile concurrently [4] - run serially","Note":"","Name":"run-test: pkg2","Style":"","Units":"duration","PrecisionBundle":{"Duration":100000,"ValueFormat":"%.3f"},"Durations":[476472287,487149957,454623527,392004118,451811730,395631659,378483471,369785238],"Values":null,"Annotations":["9","21","22","31","47","58","74","77"]},{"Type":"Duration","ExperimentName":"performance - compile concurrently [4] - run serially","Note":"","Name":"run-test: performance","Style":"","Units":"duration","PrecisionBundle":{"Duration":100000,"ValueFormat":"%.3f"},"Durations":[466831405,443591962,457024030,480193818,659165502,384689184,366942952,379236702],"Values":null,"Annotations":["9","21","22","31","47","58","74","77"]},{"Type":"Duration","ExperimentName":"performance - compile concurrently [4] - run serially","Note":"","Name":"compile-test: pkg4","Style":"","Units":"duration","PrecisionBundle":{"Duration":100000,"ValueFormat":"%.3f"},"Durations":[1997985553,1709615142,1739025801,1639920798,1682123241,1422871230,1468424870,1416016856],"Values":null,"Annotations":["9","21","22","31","47","58","74","77"]},{"Type":"Duration","ExperimentName":"performance - compile concurrently [4] - run serially","Note":"","Name":"run-test: pkg3","Style":"","Units":"duration","PrecisionBundle":{"Duration":100000,"ValueFormat":"%.3f"},"Durations":[524919655,671610727,685936210,454923661,448032088,364960203,728206965,389517710],"Values":null,"Annotations":["9","21","22","31","47","58","74","77"]},{"Type":"Duration","ExperimentName":"performance - compile concurrently [4] - run serially","Note":"","Name":"run-test: pkg4","Style":"","Units":"duration","PrecisionBundle":{"Duration":100000,"ValueFormat":"%.3f"},"Durations":[478237867,456060660,472200116,387367180,420872420,373169816,360816557,363527327],"Values":null,"Annotations":["9","21","22","31","47","58","74","77"]},{"Type":"Duration","ExperimentName":"performance - compile concurrently [4] - run serially","Note":"","Name":"total-runtime","Style":"{{green}}","Units":"duration","PrecisionBundle":{"Duration":100000,"ValueFormat":"%.3f"},"Durations":[5582536454,5366084420,5347885223,5327489940,5481461694,5008155469,4937005053,4976075824],"Values":null,"Annotations":["9","21","22","31","47","58","74","77"]}]} cb799de836ba52ec5dbc8328c8cfb89a.gmeasure-cache000066400000000000000000000112131472321612100413600ustar00rootroot00000000000000golang-github-onsi-ginkgo-v2-2.22.0/ginkgo/performance/compiling-and-running-multiple-cache{"Name":"performance - compile serially - run serially","Version":1} {"Name":"performance - compile serially - run serially","Measurements":[{"Type":"Duration","ExperimentName":"performance - compile serially - run serially","Note":"","Name":"compile-test: performance","Style":"","Units":"duration","PrecisionBundle":{"Duration":100000,"ValueFormat":"%.3f"},"Durations":[1330202878,1364792709,1349584724,1381637923,1223338687,1307622713,1146863108,1074193741],"Values":null,"Annotations":["25","33","36","37","90","100","102","110"]},{"Type":"Duration","ExperimentName":"performance - compile serially - run serially","Note":"","Name":"first-output","Style":"{{cyan}}","Units":"duration","PrecisionBundle":{"Duration":100000,"ValueFormat":"%.3f"},"Durations":[1332025794,1367105484,1352292626,1383530017,1224995086,1310081894,1148482078,1075645111],"Values":null,"Annotations":["25","33","36","37","90","100","102","110"]},{"Type":"Duration","ExperimentName":"performance - compile serially - run serially","Note":"","Name":"run-test: performance","Style":"","Units":"duration","PrecisionBundle":{"Duration":100000,"ValueFormat":"%.3f"},"Durations":[515069616,492920552,487347419,471837024,498871269,447658569,497555820,468932339],"Values":null,"Annotations":["25","33","36","37","90","100","102","110"]},{"Type":"Duration","ExperimentName":"performance - compile serially - run serially","Note":"","Name":"compile-test: pkg1","Style":"","Units":"duration","PrecisionBundle":{"Duration":100000,"ValueFormat":"%.3f"},"Durations":[1514464138,1393543136,1403078678,1708446019,1308436360,1189655130,1169257397,1148355524],"Values":null,"Annotations":["25","33","36","37","90","100","102","110"]},{"Type":"Duration","ExperimentName":"performance - compile serially - run serially","Note":"","Name":"run-test: pkg1","Style":"","Units":"duration","PrecisionBundle":{"Duration":100000,"ValueFormat":"%.3f"},"Durations":[489960409,508856666,491200445,473220781,463389993,503079009,458953950,461534046],"Values":null,"Annotations":["25","33","36","37","90","100","102","110"]},{"Type":"Duration","ExperimentName":"performance - compile serially - run serially","Note":"","Name":"compile-test: pkg2","Style":"","Units":"duration","PrecisionBundle":{"Duration":100000,"ValueFormat":"%.3f"},"Durations":[1372772013,1497178005,1365211417,1590608754,1295478492,1183286248,1168653621,1165919577],"Values":null,"Annotations":["25","33","36","37","90","100","102","110"]},{"Type":"Duration","ExperimentName":"performance - compile serially - run serially","Note":"","Name":"run-test: pkg2","Style":"","Units":"duration","PrecisionBundle":{"Duration":100000,"ValueFormat":"%.3f"},"Durations":[486292921,519136048,499205244,592441828,473968459,448712511,464833152,455645656],"Values":null,"Annotations":["25","33","36","37","90","100","102","110"]},{"Type":"Duration","ExperimentName":"performance - compile serially - run serially","Note":"","Name":"compile-test: pkg3","Style":"","Units":"duration","PrecisionBundle":{"Duration":100000,"ValueFormat":"%.3f"},"Durations":[1427943331,1387034359,1327370096,1533783593,1331769732,1170104480,1149169058,1177606147],"Values":null,"Annotations":["25","33","36","37","90","100","102","110"]},{"Type":"Duration","ExperimentName":"performance - compile serially - run serially","Note":"","Name":"run-test: pkg3","Style":"","Units":"duration","PrecisionBundle":{"Duration":100000,"ValueFormat":"%.3f"},"Durations":[568875051,515786080,492829266,570074717,476765991,526511119,457319767,442652605],"Values":null,"Annotations":["25","33","36","37","90","100","102","110"]},{"Type":"Duration","ExperimentName":"performance - compile serially - run serially","Note":"","Name":"compile-test: pkg4","Style":"","Units":"duration","PrecisionBundle":{"Duration":100000,"ValueFormat":"%.3f"},"Durations":[1487094849,1406534634,1448708165,1474038207,1363463347,1161128921,1177523162,1167306245],"Values":null,"Annotations":["25","33","36","37","90","100","102","110"]},{"Type":"Duration","ExperimentName":"performance - compile serially - run serially","Note":"","Name":"run-test: pkg4","Style":"","Units":"duration","PrecisionBundle":{"Duration":100000,"ValueFormat":"%.3f"},"Durations":[403780568,410708794,362996285,434538703,535050206,361471205,361746487,345212422],"Values":null,"Annotations":["25","33","36","37","90","100","102","110"]},{"Type":"Duration","ExperimentName":"performance - compile serially - run serially","Note":"","Name":"total-runtime","Style":"{{green}}","Units":"duration","PrecisionBundle":{"Duration":100000,"ValueFormat":"%.3f"},"Durations":[7538280078,7462239406,7259814815,8125053981,7059337826,6375956659,6174936388,6080215187],"Values":null,"Annotations":["25","33","36","37","90","100","102","110"]}]} d913553546dde110d20fd36aa954b145.gmeasure-cache000066400000000000000000000052231472321612100406640ustar00rootroot00000000000000golang-github-onsi-ginkgo-v2-2.22.0/ginkgo/performance/compiling-and-running-multiple-cache{"Name":"performance - go test - serially","Version":1} {"Name":"performance - go test - serially","Measurements":[{"Type":"Duration","ExperimentName":"performance - go test - serially","Note":"","Name":"first-output","Style":"{{cyan}}","Units":"duration","PrecisionBundle":{"Duration":100000,"ValueFormat":"%.3f"},"Durations":[1517557794,1298142571,1524024646,1259243190,1101464374,1440807253,1134170205,1141670829],"Values":null,"Annotations":["14","32","43","54","59","62","70","80"]},{"Type":"Duration","ExperimentName":"performance - go test - serially","Note":"","Name":"run-test: performance","Style":"","Units":"duration","PrecisionBundle":{"Duration":100000,"ValueFormat":"%.3f"},"Durations":[1664409932,1434196462,1682740397,1417457872,1272491557,1585190399,1301401969,1304408442],"Values":null,"Annotations":["14","32","43","54","59","62","70","80"]},{"Type":"Duration","ExperimentName":"performance - go test - serially","Note":"","Name":"run-test: pkg1","Style":"","Units":"duration","PrecisionBundle":{"Duration":100000,"ValueFormat":"%.3f"},"Durations":[1744267358,1428903086,1675353780,1470542434,1256904937,1313200036,1257213589,1254257540],"Values":null,"Annotations":["14","32","43","54","59","62","70","80"]},{"Type":"Duration","ExperimentName":"performance - go test - serially","Note":"","Name":"run-test: pkg2","Style":"","Units":"duration","PrecisionBundle":{"Duration":100000,"ValueFormat":"%.3f"},"Durations":[1785643196,1424137155,1672062246,1256648629,1267803561,1268390602,1274148554,1235807649],"Values":null,"Annotations":["14","32","43","54","59","62","70","80"]},{"Type":"Duration","ExperimentName":"performance - go test - serially","Note":"","Name":"run-test: pkg3","Style":"","Units":"duration","PrecisionBundle":{"Duration":100000,"ValueFormat":"%.3f"},"Durations":[1719984594,1423387309,1625711359,1235969725,1265934333,1282094689,1329702085,1272762317],"Values":null,"Annotations":["14","32","43","54","59","62","70","80"]},{"Type":"Duration","ExperimentName":"performance - go test - serially","Note":"","Name":"run-test: pkg4","Style":"","Units":"duration","PrecisionBundle":{"Duration":100000,"ValueFormat":"%.3f"},"Durations":[1688480067,1498479774,1640021119,1264624336,1263474744,1357359707,1263307017,1261258911],"Values":null,"Annotations":["14","32","43","54","59","62","70","80"]},{"Type":"Duration","ExperimentName":"performance - go test - serially","Note":"","Name":"total-runtime","Style":"{{green}}","Units":"duration","PrecisionBundle":{"Duration":100000,"ValueFormat":"%.3f"},"Durations":[8604888913,7210857632,8298103984,6647007272,6328324921,6807805706,6427824189,6330107958],"Values":null,"Annotations":["14","32","43","54","59","62","70","80"]}]} e8c3ef1eebd492c875763c75066b201d.gmeasure-cache000066400000000000000000000123051472321612100410470ustar00rootroot00000000000000golang-github-onsi-ginkgo-v2-2.22.0/ginkgo/performance/compiling-and-running-multiple-cache{"Name":"performance - compile concurrently [4] - run serially - will compile first suite serially","Version":1} {"Name":"performance - compile concurrently [4] - run serially - will compile first suite serially","Measurements":[{"Type":"Duration","ExperimentName":"performance - compile concurrently [4] - run serially - will compile first suite serially","Note":"","Name":"compile-test: performance","Style":"","Units":"duration","PrecisionBundle":{"Duration":100000,"ValueFormat":"%.3f"},"Durations":[1683967641,1433334853,1191919978,1102455056,1098935110,1120169724,1151212360,1072943126],"Values":null,"Annotations":["1","39","50","61","84","85","89","98"]},{"Type":"Duration","ExperimentName":"performance - compile concurrently [4] - run serially - will compile first suite serially","Note":"","Name":"first-output","Style":"{{cyan}}","Units":"duration","PrecisionBundle":{"Duration":100000,"ValueFormat":"%.3f"},"Durations":[1688075242,1436370401,1193872309,1106524829,1100576083,1122000703,1153184328,1074793502],"Values":null,"Annotations":["1","39","50","61","84","85","89","98"]},{"Type":"Duration","ExperimentName":"performance - compile concurrently [4] - run serially - will compile first suite serially","Note":"","Name":"run-test: performance","Style":"","Units":"duration","PrecisionBundle":{"Duration":100000,"ValueFormat":"%.3f"},"Durations":[1014607361,1012814273,988438325,792234219,963255266,915898096,904893506,973761214],"Values":null,"Annotations":["1","39","50","61","84","85","89","98"]},{"Type":"Duration","ExperimentName":"performance - compile concurrently [4] - run serially - will compile first suite serially","Note":"","Name":"compile-test: pkg4","Style":"","Units":"duration","PrecisionBundle":{"Duration":100000,"ValueFormat":"%.3f"},"Durations":[3132364717,3272711131,3062503420,2869317403,3110271988,3288953056,2935452900,3185470772],"Values":null,"Annotations":["1","39","50","61","84","85","89","98"]},{"Type":"Duration","ExperimentName":"performance - compile concurrently [4] - run serially - will compile first suite serially","Note":"","Name":"compile-test: pkg2","Style":"","Units":"duration","PrecisionBundle":{"Duration":100000,"ValueFormat":"%.3f"},"Durations":[3297041238,3261757147,3085579662,3165872010,3293338127,3265713464,3125875409,2865868330],"Values":null,"Annotations":["1","39","50","61","84","85","89","98"]},{"Type":"Duration","ExperimentName":"performance - compile concurrently [4] - run serially - will compile first suite serially","Note":"","Name":"compile-test: pkg3","Style":"","Units":"duration","PrecisionBundle":{"Duration":100000,"ValueFormat":"%.3f"},"Durations":[3402354485,3249585752,3220402858,3216049330,2990986470,3121223693,3128560875,3111836278],"Values":null,"Annotations":["1","39","50","61","84","85","89","98"]},{"Type":"Duration","ExperimentName":"performance - compile concurrently [4] - run serially - will compile first suite serially","Note":"","Name":"compile-test: pkg1","Style":"","Units":"duration","PrecisionBundle":{"Duration":100000,"ValueFormat":"%.3f"},"Durations":[3474907755,3014179726,3230394108,3115376656,3140346867,3035682342,3244166005,3083248334],"Values":null,"Annotations":["1","39","50","61","84","85","89","98"]},{"Type":"Duration","ExperimentName":"performance - compile concurrently [4] - run serially - will compile first suite serially","Note":"","Name":"run-test: pkg4","Style":"","Units":"duration","PrecisionBundle":{"Duration":100000,"ValueFormat":"%.3f"},"Durations":[485299465,452292382,419292145,466438963,365024006,367802670,455338523,372980968],"Values":null,"Annotations":["1","39","50","61","84","85","89","98"]},{"Type":"Duration","ExperimentName":"performance - compile concurrently [4] - run serially - will compile first suite serially","Note":"","Name":"run-test: pkg2","Style":"","Units":"duration","PrecisionBundle":{"Duration":100000,"ValueFormat":"%.3f"},"Durations":[426059465,434109824,363764341,360313651,353371509,351142334,353668757,474128163],"Values":null,"Annotations":["1","39","50","61","84","85","89","98"]},{"Type":"Duration","ExperimentName":"performance - compile concurrently [4] - run serially - will compile first suite serially","Note":"","Name":"run-test: pkg3","Style":"","Units":"duration","PrecisionBundle":{"Duration":100000,"ValueFormat":"%.3f"},"Durations":[466808734,467399768,388454939,356274096,440383129,375193858,380483026,359357533],"Values":null,"Annotations":["1","39","50","61","84","85","89","98"]},{"Type":"Duration","ExperimentName":"performance - compile concurrently [4] - run serially - will compile first suite serially","Note":"","Name":"run-test: pkg1","Style":"","Units":"duration","PrecisionBundle":{"Duration":100000,"ValueFormat":"%.3f"},"Durations":[477214476,518522411,371334911,366407525,363102737,450617562,347359329,360893588],"Values":null,"Annotations":["1","39","50","61","84","85","89","98"]},{"Type":"Duration","ExperimentName":"performance - compile concurrently [4] - run serially - will compile first suite serially","Note":"","Name":"total-runtime","Style":"{{green}}","Units":"duration","PrecisionBundle":{"Duration":100000,"ValueFormat":"%.3f"},"Durations":[6675738685,6322725726,5799044868,5525527081,5613545540,5702248362,5625408352,5507923747],"Values":null,"Annotations":["1","39","50","61","84","85","89","98"]}]} f63d60f305dbd4ac27febbc09ca9d000.gmeasure-cache000066400000000000000000000115331472321612100413160ustar00rootroot00000000000000golang-github-onsi-ginkgo-v2-2.22.0/ginkgo/performance/compiling-and-running-multiple-cache{"Name":"performance - compile concurrently [2] - run concurrently [2]","Version":1} {"Name":"performance - compile concurrently [2] - run concurrently [2]","Measurements":[{"Type":"Duration","ExperimentName":"performance - compile concurrently [2] - run concurrently [2]","Note":"","Name":"compile-test: pkg1","Style":"","Units":"duration","PrecisionBundle":{"Duration":100000,"ValueFormat":"%.3f"},"Durations":[1904117681,1779948699,1942139871,1869010063,1756294014,1933363180,1642568678,1611315556],"Values":null,"Annotations":["13","16","28","29","34","38","79","101"]},{"Type":"Duration","ExperimentName":"performance - compile concurrently [2] - run concurrently [2]","Note":"","Name":"first-output","Style":"{{cyan}}","Units":"duration","PrecisionBundle":{"Duration":100000,"ValueFormat":"%.3f"},"Durations":[1906023679,1686427756,1829074824,1743898395,1758386678,1908291668,1636480238,1614183052],"Values":null,"Annotations":["13","16","28","29","34","38","79","101"]},{"Type":"Duration","ExperimentName":"performance - compile concurrently [2] - run concurrently [2]","Note":"","Name":"compile-test: performance","Style":"","Units":"duration","PrecisionBundle":{"Duration":100000,"ValueFormat":"%.3f"},"Durations":[1911515296,1684106276,1827002754,1742107748,2034503104,1904725330,1633527869,1638654422],"Values":null,"Annotations":["13","16","28","29","34","38","79","101"]},{"Type":"Duration","ExperimentName":"performance - compile concurrently [2] - run concurrently [2]","Note":"","Name":"run-test: pkg1","Style":"","Units":"duration","PrecisionBundle":{"Duration":100000,"ValueFormat":"%.3f"},"Durations":[647526117,1195940484,1136154225,1132985170,743894910,1030007903,1221439451,795558820],"Values":null,"Annotations":["13","16","28","29","34","38","79","101"]},{"Type":"Duration","ExperimentName":"performance - compile concurrently [2] - run concurrently [2]","Note":"","Name":"run-test: performance","Style":"","Units":"duration","PrecisionBundle":{"Duration":100000,"ValueFormat":"%.3f"},"Durations":[1219489991,809672998,696964886,694807500,1018213654,617222525,636450172,1265977917],"Values":null,"Annotations":["13","16","28","29","34","38","79","101"]},{"Type":"Duration","ExperimentName":"performance - compile concurrently [2] - run concurrently [2]","Note":"","Name":"compile-test: pkg2","Style":"","Units":"duration","PrecisionBundle":{"Duration":100000,"ValueFormat":"%.3f"},"Durations":[2040539166,1946895946,1987571513,2060061744,2011149002,2134661413,1840394505,1909614812],"Values":null,"Annotations":["13","16","28","29","34","38","79","101"]},{"Type":"Duration","ExperimentName":"performance - compile concurrently [2] - run concurrently [2]","Note":"","Name":"compile-test: pkg3","Style":"","Units":"duration","PrecisionBundle":{"Duration":100000,"ValueFormat":"%.3f"},"Durations":[2043689808,1885940321,2048510109,1928750078,2141597530,2120450423,1821814970,2144191168],"Values":null,"Annotations":["13","16","28","29","34","38","79","101"]},{"Type":"Duration","ExperimentName":"performance - compile concurrently [2] - run concurrently [2]","Note":"","Name":"run-test: pkg2","Style":"","Units":"duration","PrecisionBundle":{"Duration":100000,"ValueFormat":"%.3f"},"Durations":[483592097,541413312,505796205,853669867,578048849,497729739,815490835,533466067],"Values":null,"Annotations":["13","16","28","29","34","38","79","101"]},{"Type":"Duration","ExperimentName":"performance - compile concurrently [2] - run concurrently [2]","Note":"","Name":"run-test: pkg3","Style":"","Units":"duration","PrecisionBundle":{"Duration":100000,"ValueFormat":"%.3f"},"Durations":[902640803,840386812,674205334,482107325,511571420,864878510,467503469,630727018],"Values":null,"Annotations":["13","16","28","29","34","38","79","101"]},{"Type":"Duration","ExperimentName":"performance - compile concurrently [2] - run concurrently [2]","Note":"","Name":"compile-test: pkg4","Style":"","Units":"duration","PrecisionBundle":{"Duration":100000,"ValueFormat":"%.3f"},"Durations":[1701161299,1426628174,1492268688,1522703122,1644157167,1692279005,1280540816,1457210869],"Values":null,"Annotations":["13","16","28","29","34","38","79","101"]},{"Type":"Duration","ExperimentName":"performance - compile concurrently [2] - run concurrently [2]","Note":"","Name":"run-test: pkg4","Style":"","Units":"duration","PrecisionBundle":{"Duration":100000,"ValueFormat":"%.3f"},"Durations":[466369900,366884771,379018336,435167771,416947148,383509662,360851942,341445699],"Values":null,"Annotations":["13","16","28","29","34","38","79","101"]},{"Type":"Duration","ExperimentName":"performance - compile concurrently [2] - run concurrently [2]","Note":"","Name":"total-runtime","Style":"{{green}}","Units":"duration","PrecisionBundle":{"Duration":100000,"ValueFormat":"%.3f"},"Durations":[6114231752,5426919839,5688029332,5757557033,5830754618,6117535085,5108873925,5321388540],"Values":null,"Annotations":["13","16","28","29","34","38","79","101"]}]} f871828d82f74ed614c36d8f8d27f7f5.gmeasure-cache000066400000000000000000000125511472321612100410270ustar00rootroot00000000000000golang-github-onsi-ginkgo-v2-2.22.0/ginkgo/performance/compiling-and-running-multiple-cache{"Name":"performance - compile concurrently [2] - run concurrently [4] - will compile first suite serially","Version":1} {"Name":"performance - compile concurrently [2] - run concurrently [4] - will compile first suite serially","Measurements":[{"Type":"Duration","ExperimentName":"performance - compile concurrently [2] - run concurrently [4] - will compile first suite serially","Note":"","Name":"compile-test: performance","Style":"","Units":"duration","PrecisionBundle":{"Duration":100000,"ValueFormat":"%.3f"},"Durations":[1373220454,1170495310,1087199937,1078388427,1077726842,1069324803,1085218758,1070808603],"Values":null,"Annotations":["35","68","76","78","94","105","106","109"]},{"Type":"Duration","ExperimentName":"performance - compile concurrently [2] - run concurrently [4] - will compile first suite serially","Note":"","Name":"first-output","Style":"{{cyan}}","Units":"duration","PrecisionBundle":{"Duration":100000,"ValueFormat":"%.3f"},"Durations":[1375321301,1173496008,1088670741,1079875973,1079177865,1071402922,1086678221,1073203671],"Values":null,"Annotations":["35","68","76","78","94","105","106","109"]},{"Type":"Duration","ExperimentName":"performance - compile concurrently [2] - run concurrently [4] - will compile first suite serially","Note":"","Name":"run-test: performance","Style":"","Units":"duration","PrecisionBundle":{"Duration":100000,"ValueFormat":"%.3f"},"Durations":[768303152,831800030,647514326,790402795,730907467,759845568,783951010,767836965],"Values":null,"Annotations":["35","68","76","78","94","105","106","109"]},{"Type":"Duration","ExperimentName":"performance - compile concurrently [2] - run concurrently [4] - will compile first suite serially","Note":"","Name":"compile-test: pkg2","Style":"","Units":"duration","PrecisionBundle":{"Duration":100000,"ValueFormat":"%.3f"},"Durations":[1936138191,1886872554,1858365841,1729418496,1912071456,1738700994,1849540822,1661421711],"Values":null,"Annotations":["35","68","76","78","94","105","106","109"]},{"Type":"Duration","ExperimentName":"performance - compile concurrently [2] - run concurrently [4] - will compile first suite serially","Note":"","Name":"compile-test: pkg1","Style":"","Units":"duration","PrecisionBundle":{"Duration":100000,"ValueFormat":"%.3f"},"Durations":[1953536535,1780752280,2009913866,1762082688,1659923142,1739217714,1704170413,1826959057],"Values":null,"Annotations":["35","68","76","78","94","105","106","109"]},{"Type":"Duration","ExperimentName":"performance - compile concurrently [2] - run concurrently [4] - will compile first suite serially","Note":"","Name":"run-test: pkg2","Style":"","Units":"duration","PrecisionBundle":{"Duration":100000,"ValueFormat":"%.3f"},"Durations":[698934962,1166498302,724644685,676047982,986681301,651883915,983232146,743641796],"Values":null,"Annotations":["35","68","76","78","94","105","106","109"]},{"Type":"Duration","ExperimentName":"performance - compile concurrently [2] - run concurrently [4] - will compile first suite serially","Note":"","Name":"run-test: pkg1","Style":"","Units":"duration","PrecisionBundle":{"Duration":100000,"ValueFormat":"%.3f"},"Durations":[1210113773,739268468,1081429322,1218885538,757007970,1210529961,622601502,1116324523],"Values":null,"Annotations":["35","68","76","78","94","105","106","109"]},{"Type":"Duration","ExperimentName":"performance - compile concurrently [2] - run concurrently [4] - will compile first suite serially","Note":"","Name":"compile-test: pkg3","Style":"","Units":"duration","PrecisionBundle":{"Duration":100000,"ValueFormat":"%.3f"},"Durations":[2027746471,1974854063,1843080439,1925652639,1799726834,1875030904,1827599290,1832051404],"Values":null,"Annotations":["35","68","76","78","94","105","106","109"]},{"Type":"Duration","ExperimentName":"performance - compile concurrently [2] - run concurrently [4] - will compile first suite serially","Note":"","Name":"compile-test: pkg4","Style":"","Units":"duration","PrecisionBundle":{"Duration":100000,"ValueFormat":"%.3f"},"Durations":[2024925884,1800247126,1781039938,1892817496,1695704647,1879342381,1790782031,1715969742],"Values":null,"Annotations":["35","68","76","78","94","105","106","109"]},{"Type":"Duration","ExperimentName":"performance - compile concurrently [2] - run concurrently [4] - will compile first suite serially","Note":"","Name":"run-test: pkg3","Style":"","Units":"duration","PrecisionBundle":{"Duration":100000,"ValueFormat":"%.3f"},"Durations":[390529644,671237879,382033971,561761724,371576010,346548889,371778702,382012882],"Values":null,"Annotations":["35","68","76","78","94","105","106","109"]},{"Type":"Duration","ExperimentName":"performance - compile concurrently [2] - run concurrently [4] - will compile first suite serially","Note":"","Name":"run-test: pkg4","Style":"","Units":"duration","PrecisionBundle":{"Duration":100000,"ValueFormat":"%.3f"},"Durations":[625495262,541180371,502497808,357555894,424036142,594306678,466858168,528676585],"Values":null,"Annotations":["35","68","76","78","94","105","106","109"]},{"Type":"Duration","ExperimentName":"performance - compile concurrently [2] - run concurrently [4] - will compile first suite serially","Note":"","Name":"total-runtime","Style":"{{green}}","Units":"duration","PrecisionBundle":{"Duration":100000,"ValueFormat":"%.3f"},"Durations":[5979615494,5600394768,5382221687,5296800982,5111113261,5284293425,5193945647,5144861008],"Values":null,"Annotations":["35","68","76","78","94","105","106","109"]}]} ff4e55f973b442272532f4ac2daa0b13.gmeasure-cache000066400000000000000000000053771472321612100410360ustar00rootroot00000000000000golang-github-onsi-ginkgo-v2-2.22.0/ginkgo/performance/compiling-and-running-multiple-cache{"Name":"performance - go test - run concurrently [4]","Version":1} {"Name":"performance - go test - run concurrently [4]","Measurements":[{"Type":"Duration","ExperimentName":"performance - go test - run concurrently [4]","Note":"","Name":"first-output","Style":"{{cyan}}","Units":"duration","PrecisionBundle":{"Duration":100000,"ValueFormat":"%.3f"},"Durations":[2816971213,2929025677,2758409004,2820812502,2693124823,2897785746,2645676231,2617607758],"Values":null,"Annotations":["4","11","18","40","60","67","95","104"]},{"Type":"Duration","ExperimentName":"performance - go test - run concurrently [4]","Note":"","Name":"run-test: pkg1","Style":"","Units":"duration","PrecisionBundle":{"Duration":100000,"ValueFormat":"%.3f"},"Durations":[2958092304,3368451811,2912978652,4084311719,3058639635,3254315595,3658941031,2771578956],"Values":null,"Annotations":["4","11","18","40","60","67","95","104"]},{"Type":"Duration","ExperimentName":"performance - go test - run concurrently [4]","Note":"","Name":"run-test: pkg2","Style":"","Units":"duration","PrecisionBundle":{"Duration":100000,"ValueFormat":"%.3f"},"Durations":[3404300860,4030943284,3176645880,2962491164,2848297115,3591447350,2797505269,3300731825],"Values":null,"Annotations":["4","11","18","40","60","67","95","104"]},{"Type":"Duration","ExperimentName":"performance - go test - run concurrently [4]","Note":"","Name":"run-test: performance","Style":"","Units":"duration","PrecisionBundle":{"Duration":100000,"ValueFormat":"%.3f"},"Durations":[3826248292,3082191033,3864826340,3292292559,3630265280,3047825534,3016923440,3603712200],"Values":null,"Annotations":["4","11","18","40","60","67","95","104"]},{"Type":"Duration","ExperimentName":"performance - go test - run concurrently [4]","Note":"","Name":"run-test: pkg3","Style":"","Units":"duration","PrecisionBundle":{"Duration":100000,"ValueFormat":"%.3f"},"Durations":[4112745736,3644825399,3546776201,3690716409,3363394722,3853733454,3334336305,2987701672],"Values":null,"Annotations":["4","11","18","40","60","67","95","104"]},{"Type":"Duration","ExperimentName":"performance - go test - run concurrently [4]","Note":"","Name":"run-test: pkg4","Style":"","Units":"duration","PrecisionBundle":{"Duration":100000,"ValueFormat":"%.3f"},"Durations":[1786975497,1842062782,1820739273,1920001634,1481207957,1491942068,1497112172,1464944675],"Values":null,"Annotations":["4","11","18","40","60","67","95","104"]},{"Type":"Duration","ExperimentName":"performance - go test - run concurrently [4]","Note":"","Name":"total-runtime","Style":"{{green}}","Units":"duration","PrecisionBundle":{"Duration":100000,"ValueFormat":"%.3f"},"Durations":[4747962871,4927334622,4736008216,4884512979,4331574198,4542034171,4296121849,4237928031],"Values":null,"Annotations":["4","11","18","40","60","67","95","104"]}]} golang-github-onsi-ginkgo-v2-2.22.0/ginkgo/performance/compiling-and-running-single-cache/000077500000000000000000000000001472321612100313705ustar00rootroot000000000000002daaa9348ce35e03f3fb34ec83ffc542.gmeasure-cache000066400000000000000000000032031472321612100406770ustar00rootroot00000000000000golang-github-onsi-ginkgo-v2-2.22.0/ginkgo/performance/compiling-and-running-single-cache{"Name":"performance - go test -c; then run (serially)","Version":1} {"Name":"performance - go test -c; then run (serially)","Measurements":[{"Type":"Duration","ExperimentName":"performance - go test -c; then run (serially)","Note":"","Name":"compile-test: performance","Style":"","Units":"duration","PrecisionBundle":{"Duration":100000,"ValueFormat":"%.3f"},"Durations":[1441332111,1782228267,1671702396,1643379602,1689145798,1686475584,1767941293,1866963639],"Values":null,"Annotations":["2","6","7","8","9","13","14","20"]},{"Type":"Duration","ExperimentName":"performance - go test -c; then run (serially)","Note":"","Name":"first-output","Style":"{{cyan}}","Units":"duration","PrecisionBundle":{"Duration":100000,"ValueFormat":"%.3f"},"Durations":[1443195660,1783412172,1673074234,1644513464,1690239192,1687064766,1768998632,1868722656],"Values":null,"Annotations":["2","6","7","8","9","13","14","20"]},{"Type":"Duration","ExperimentName":"performance - go test -c; then run (serially)","Note":"","Name":"run-test: performance","Style":"","Units":"duration","PrecisionBundle":{"Duration":100000,"ValueFormat":"%.3f"},"Durations":[435868223,464720306,455712999,483160473,437177166,460354810,477463974,439738566],"Values":null,"Annotations":["2","6","7","8","9","13","14","20"]},{"Type":"Duration","ExperimentName":"performance - go test -c; then run (serially)","Note":"","Name":"total-runtime","Style":"{{green}}","Units":"duration","PrecisionBundle":{"Duration":100000,"ValueFormat":"%.3f"},"Durations":[1880804171,2249919856,2130987642,2129418295,2129143340,2149549079,2248626774,2312546788],"Values":null,"Annotations":["2","6","7","8","9","13","14","20"]}]} cb799de836ba52ec5dbc8328c8cfb89a.gmeasure-cache000066400000000000000000000032231472321612100410100ustar00rootroot00000000000000golang-github-onsi-ginkgo-v2-2.22.0/ginkgo/performance/compiling-and-running-single-cache{"Name":"performance - compile serially - run serially","Version":1} {"Name":"performance - compile serially - run serially","Measurements":[{"Type":"Duration","ExperimentName":"performance - compile serially - run serially","Note":"","Name":"compile-test: performance","Style":"","Units":"duration","PrecisionBundle":{"Duration":100000,"ValueFormat":"%.3f"},"Durations":[1804539385,1632856345,1716443905,1611339941,1621875582,1753640954,1555334838,1562292852],"Values":null,"Annotations":["5","10","11","15","19","22","23","24"]},{"Type":"Duration","ExperimentName":"performance - compile serially - run serially","Note":"","Name":"first-output","Style":"{{cyan}}","Units":"duration","PrecisionBundle":{"Duration":100000,"ValueFormat":"%.3f"},"Durations":[1806651134,1633665056,1718071554,1612857008,1623475389,1754481119,1556209776,1562976713],"Values":null,"Annotations":["5","10","11","15","19","22","23","24"]},{"Type":"Duration","ExperimentName":"performance - compile serially - run serially","Note":"","Name":"run-test: performance","Style":"","Units":"duration","PrecisionBundle":{"Duration":100000,"ValueFormat":"%.3f"},"Durations":[467124642,478757318,441940533,438787692,440965727,493947838,441946553,449219815],"Values":null,"Annotations":["5","10","11","15","19","22","23","24"]},{"Type":"Duration","ExperimentName":"performance - compile serially - run serially","Note":"","Name":"total-runtime","Style":"{{green}}","Units":"duration","PrecisionBundle":{"Duration":100000,"ValueFormat":"%.3f"},"Durations":[2273956943,2112540513,2160166492,2052030538,2064638856,2248673369,1998281107,2012255390],"Values":null,"Annotations":["5","10","11","15","19","22","23","24"]}]} d913553546dde110d20fd36aa954b145.gmeasure-cache000066400000000000000000000023161472321612100403120ustar00rootroot00000000000000golang-github-onsi-ginkgo-v2-2.22.0/ginkgo/performance/compiling-and-running-single-cache{"Name":"performance - go test - serially","Version":1} {"Name":"performance - go test - serially","Measurements":[{"Type":"Duration","ExperimentName":"performance - go test - serially","Note":"","Name":"first-output","Style":"{{cyan}}","Units":"duration","PrecisionBundle":{"Duration":100000,"ValueFormat":"%.3f"},"Durations":[1366165821,1560422128,1536647515,1676260850,1914754855,1781026705,1634072058,1680754969],"Values":null,"Annotations":["1","3","4","12","16","17","18","21"]},{"Type":"Duration","ExperimentName":"performance - go test - serially","Note":"","Name":"run-test: performance","Style":"","Units":"duration","PrecisionBundle":{"Duration":100000,"ValueFormat":"%.3f"},"Durations":[1514733056,1706892660,1698558511,1835471759,2068894015,1935613121,1783458998,1832967419],"Values":null,"Annotations":["1","3","4","12","16","17","18","21"]},{"Type":"Duration","ExperimentName":"performance - go test - serially","Note":"","Name":"total-runtime","Style":"{{green}}","Units":"duration","PrecisionBundle":{"Duration":100000,"ValueFormat":"%.3f"},"Durations":[1516207751,1707739024,1699470011,1838250990,2070689815,1936806765,1785930567,1834578992],"Values":null,"Annotations":["1","3","4","12","16","17","18","21"]}]} golang-github-onsi-ginkgo-v2-2.22.0/ginkgo/performance/compiling_and_running_test.go000066400000000000000000000111301472321612100305760ustar00rootroot00000000000000package performance_test import ( "fmt" "os" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" "github.com/onsi/gomega/gmeasure" ) var _ = Describe("Compiling and Running a single test package", func() { var cache gmeasure.ExperimentCache BeforeEach(func() { if os.Getenv("PERF") == "" { Skip("PERF environment not set, skipping") } var err error cache, err = gmeasure.NewExperimentCache("./compiling-and-running-single-cache") Ω(err).ShouldNot(HaveOccurred()) // we mount everything outside the Ginkgo parent directory to make sure GOMODULES doesn't get confused by the go.mod in Ginkgo's root pfm = NewPerformanceFixtureManager(fmt.Sprintf("../../../ginkgo_perf_tmp_%d", GinkgoParallelProcess())) gmcm = NewGoModCacheManager(fmt.Sprintf("../../../ginkgo_perf_cache_%d", GinkgoParallelProcess())) if !DEBUG { DeferCleanup(pfm.Cleanup) DeferCleanup(gmcm.Cleanup) } }) Describe("Experiments", func() { BeforeEach(func() { pfm.MountFixture("performance") }) It("runs a series of experiments with various scenarios", func() { SampleScenarios(cache, 8, 1, true, ScenarioSettings{Fixture: "performance", NumSuites: 1, ConcurrentCompilers: 1, ConcurrentRunners: 1}, ScenarioSettings{Fixture: "performance", NumSuites: 1, UseGoTestDirectly: true, ConcurrentGoTests: 1}, ScenarioSettings{Fixture: "performance", NumSuites: 1, UseGoTestDirectly: true, GoTestCompileThenRunSerially: true}, ) }) }) Describe("Analysis", func() { It("analyzes the various scenarios to identify winners", func() { AnalyzeCache(cache) }) }) }) var _ = Describe("Compiling and Running multiple tests", func() { var cache gmeasure.ExperimentCache BeforeEach(func() { if os.Getenv("PERF") == "" { Skip("PERF environment not set, skipping") } var err error cache, err = gmeasure.NewExperimentCache("./compiling-and-running-multiple-cache") Ω(err).ShouldNot(HaveOccurred()) // we mount everything outside the Ginkgo parent directory to make sure GOMODULES doesn't get confused by the go.mod in Ginkgo's root pfm = NewPerformanceFixtureManager(fmt.Sprintf("../../../ginkgo_perf_tmp_%d", GinkgoParallelProcess())) gmcm = NewGoModCacheManager(fmt.Sprintf("../../../ginkgo_perf_cache_%d", GinkgoParallelProcess())) if !DEBUG { DeferCleanup(pfm.Cleanup) DeferCleanup(gmcm.Cleanup) } }) Describe("Experiments", func() { BeforeEach(func() { pfm.MountFixture("performance") }) It("runs a series of experiments with various scenarios", func() { SampleScenarios(cache, 8, 1, true, ScenarioSettings{Fixture: "performance", NumSuites: 5, ConcurrentCompilers: 1, ConcurrentRunners: 1, Recurse: true}, ScenarioSettings{Fixture: "performance", NumSuites: 5, ConcurrentCompilers: 2, ConcurrentRunners: 1, Recurse: true}, ScenarioSettings{Fixture: "performance", NumSuites: 5, ConcurrentCompilers: 4, ConcurrentRunners: 1, Recurse: true}, ScenarioSettings{Fixture: "performance", NumSuites: 5, ConcurrentCompilers: 2, ConcurrentRunners: 1, CompileFirstSuiteSerially: true, Recurse: true}, ScenarioSettings{Fixture: "performance", NumSuites: 5, ConcurrentCompilers: 4, ConcurrentRunners: 1, CompileFirstSuiteSerially: true, Recurse: true}, ScenarioSettings{Fixture: "performance", NumSuites: 5, ConcurrentCompilers: 2, ConcurrentRunners: 2, Recurse: true}, ScenarioSettings{Fixture: "performance", NumSuites: 5, ConcurrentCompilers: 4, ConcurrentRunners: 2, Recurse: true}, ScenarioSettings{Fixture: "performance", NumSuites: 5, ConcurrentCompilers: 2, ConcurrentRunners: 4, CompileFirstSuiteSerially: true, Recurse: true}, ScenarioSettings{Fixture: "performance", NumSuites: 5, ConcurrentCompilers: 4, ConcurrentRunners: 4, CompileFirstSuiteSerially: true, Recurse: true}, ScenarioSettings{Fixture: "performance", NumSuites: 5, UseGoTestDirectly: true, ConcurrentGoTests: 1, Recurse: true}, ScenarioSettings{Fixture: "performance", NumSuites: 5, UseGoTestDirectly: true, ConcurrentGoTests: 2, Recurse: true}, ScenarioSettings{Fixture: "performance", NumSuites: 5, UseGoTestDirectly: true, ConcurrentGoTests: 4, Recurse: true}, ScenarioSettings{Fixture: "performance", NumSuites: 5, UseGoTestDirectly: true, ConcurrentGoTests: 8, Recurse: true}, ScenarioSettings{Fixture: "performance", NumSuites: 5, UseGoTestDirectly: true, GoTestCompileThenRunSerially: true, Recurse: true}, ScenarioSettings{Fixture: "performance", NumSuites: 5, UseGoTestDirectly: true, GoTestRecurse: true, Recurse: true}, ) }) }) Describe("Analysis", func() { It("analyzes the various scenarios to identify winners", func() { AnalyzeCache(cache) }) }) }) golang-github-onsi-ginkgo-v2-2.22.0/ginkgo/performance/fetching-dependencies-cache/000077500000000000000000000000001472321612100301255ustar00rootroot000000000000001baac032c37c6eddc8381c02f73bab9c.gmeasure-cache000066400000000000000000000123001472321612100374710ustar00rootroot00000000000000golang-github-onsi-ginkgo-v2-2.22.0/ginkgo/performance/fetching-dependencies-cache{"Name":"performance - compile concurrently [2] - run serially - will compile first suite serially","Version":1} {"Name":"performance - compile concurrently [2] - run serially - will compile first suite serially","Measurements":[{"Type":"Duration","ExperimentName":"performance - compile concurrently [2] - run serially - will compile first suite serially","Note":"","Name":"compile-test: performance","Style":"","Units":"duration","PrecisionBundle":{"Duration":100000,"ValueFormat":"%.3f"},"Durations":[5364489867,6084853493,5339197180,5931830348,6230216854,6250595382,5832540674,6343403658],"Values":null,"Annotations":["5","6","27","28","31","40","41","60"]},{"Type":"Duration","ExperimentName":"performance - compile concurrently [2] - run serially - will compile first suite serially","Note":"","Name":"first-output","Style":"{{cyan}}","Units":"duration","PrecisionBundle":{"Duration":100000,"ValueFormat":"%.3f"},"Durations":[6029419925,6787801161,6020439986,6595906344,7003272726,7850577838,6533742519,7134034087],"Values":null,"Annotations":["5","6","27","28","31","40","41","60"]},{"Type":"Duration","ExperimentName":"performance - compile concurrently [2] - run serially - will compile first suite serially","Note":"","Name":"run-test: performance","Style":"","Units":"duration","PrecisionBundle":{"Duration":100000,"ValueFormat":"%.3f"},"Durations":[807377617,704565913,860663544,869231947,733573610,1008274902,729699629,729222755],"Values":null,"Annotations":["5","6","27","28","31","40","41","60"]},{"Type":"Duration","ExperimentName":"performance - compile concurrently [2] - run serially - will compile first suite serially","Note":"","Name":"compile-test: pkg2","Style":"","Units":"duration","PrecisionBundle":{"Duration":100000,"ValueFormat":"%.3f"},"Durations":[2240548945,2381967758,2248038257,2209842101,2239803616,2403632155,2282814998,2330228742],"Values":null,"Annotations":["5","6","27","28","31","40","41","60"]},{"Type":"Duration","ExperimentName":"performance - compile concurrently [2] - run serially - will compile first suite serially","Note":"","Name":"compile-test: pkg1","Style":"","Units":"duration","PrecisionBundle":{"Duration":100000,"ValueFormat":"%.3f"},"Durations":[2420252054,2395240983,2252711696,2196629705,2241232548,2612450535,2238579687,2506737029],"Values":null,"Annotations":["5","6","27","28","31","40","41","60"]},{"Type":"Duration","ExperimentName":"performance - compile concurrently [2] - run serially - will compile first suite serially","Note":"","Name":"run-test: pkg2","Style":"","Units":"duration","PrecisionBundle":{"Duration":100000,"ValueFormat":"%.3f"},"Durations":[784890981,816126551,697055231,703391272,831751648,706644571,804919685,778793797],"Values":null,"Annotations":["5","6","27","28","31","40","41","60"]},{"Type":"Duration","ExperimentName":"performance - compile concurrently [2] - run serially - will compile first suite serially","Note":"","Name":"run-test: pkg1","Style":"","Units":"duration","PrecisionBundle":{"Duration":100000,"ValueFormat":"%.3f"},"Durations":[752177419,736059324,776172115,819490475,738894299,787490927,751923689,773613789],"Values":null,"Annotations":["5","6","27","28","31","40","41","60"]},{"Type":"Duration","ExperimentName":"performance - compile concurrently [2] - run serially - will compile first suite serially","Note":"","Name":"compile-test: pkg3","Style":"","Units":"duration","PrecisionBundle":{"Duration":100000,"ValueFormat":"%.3f"},"Durations":[2506666970,2426049487,2321548374,2533804616,2454944464,2347401879,2551060489,2499297809],"Values":null,"Annotations":["5","6","27","28","31","40","41","60"]},{"Type":"Duration","ExperimentName":"performance - compile concurrently [2] - run serially - will compile first suite serially","Note":"","Name":"compile-test: pkg4","Style":"","Units":"duration","PrecisionBundle":{"Duration":100000,"ValueFormat":"%.3f"},"Durations":[2370897193,2498231213,2330063384,2456840056,2409830003,2380321479,2576555901,2391788205],"Values":null,"Annotations":["5","6","27","28","31","40","41","60"]},{"Type":"Duration","ExperimentName":"performance - compile concurrently [2] - run serially - will compile first suite serially","Note":"","Name":"run-test: pkg3","Style":"","Units":"duration","PrecisionBundle":{"Duration":100000,"ValueFormat":"%.3f"},"Durations":[420784559,489886052,420060236,571451183,542164769,471664092,479383743,489178266],"Values":null,"Annotations":["5","6","27","28","31","40","41","60"]},{"Type":"Duration","ExperimentName":"performance - compile concurrently [2] - run serially - will compile first suite serially","Note":"","Name":"run-test: pkg4","Style":"","Units":"duration","PrecisionBundle":{"Duration":100000,"ValueFormat":"%.3f"},"Durations":[481787768,503904424,506190423,467061841,479921043,565969104,582621271,582417591],"Values":null,"Annotations":["5","6","27","28","31","40","41","60"]},{"Type":"Duration","ExperimentName":"performance - compile concurrently [2] - run serially - will compile first suite serially","Note":"","Name":"total-runtime","Style":"{{green}}","Units":"duration","PrecisionBundle":{"Duration":100000,"ValueFormat":"%.3f"},"Durations":[11678308161,12588682148,11516668060,12301327615,12676667916,13639356975,12386745774,13035393191],"Values":null,"Annotations":["5","6","27","28","31","40","41","60"]}]} 23a884dc38ea0cf973e24d00d94dd6ed.gmeasure-cache000066400000000000000000000130111472321612100373610ustar00rootroot00000000000000golang-github-onsi-ginkgo-v2-2.22.0/ginkgo/performance/fetching-dependencies-cache{"Name":"performance - compile concurrently [4] - run serially - will go mod download first","Version":1} {"Name":"performance - compile concurrently [4] - run serially - will go mod download first","Measurements":[{"Type":"Duration","ExperimentName":"performance - compile concurrently [4] - run serially - will go mod download first","Note":"","Name":"mod-download","Style":"","Units":"duration","PrecisionBundle":{"Duration":100000,"ValueFormat":"%.3f"},"Durations":[6143763744,6877865762,7264967000,6364166580,6320729991,6919638593,7438648281,6485861039],"Values":null,"Annotations":["7","10","13","20","23","37","47","56"]},{"Type":"Duration","ExperimentName":"performance - compile concurrently [4] - run serially - will go mod download first","Note":"","Name":"compile-test: pkg1","Style":"","Units":"duration","PrecisionBundle":{"Duration":100000,"ValueFormat":"%.3f"},"Durations":[3219544817,3088191642,3528852625,3129525828,3342218524,3568954707,3331603739,3051617075],"Values":null,"Annotations":["7","10","13","20","23","37","47","56"]},{"Type":"Duration","ExperimentName":"performance - compile concurrently [4] - run serially - will go mod download first","Note":"","Name":"first-output","Style":"{{cyan}}","Units":"duration","PrecisionBundle":{"Duration":100000,"ValueFormat":"%.3f"},"Durations":[9365525441,9971313994,10306660409,9495555063,9179370612,9979351712,10552203430,9539314977],"Values":null,"Annotations":["7","10","13","20","23","37","47","56"]},{"Type":"Duration","ExperimentName":"performance - compile concurrently [4] - run serially - will go mod download first","Note":"","Name":"compile-test: pkg2","Style":"","Units":"duration","PrecisionBundle":{"Duration":100000,"ValueFormat":"%.3f"},"Durations":[3621652023,3089513818,3436958890,3129403318,3807441695,3341078496,3113090021,3457287113],"Values":null,"Annotations":["7","10","13","20","23","37","47","56"]},{"Type":"Duration","ExperimentName":"performance - compile concurrently [4] - run serially - will go mod download first","Note":"","Name":"compile-test: performance","Style":"","Units":"duration","PrecisionBundle":{"Duration":100000,"ValueFormat":"%.3f"},"Durations":[3722002459,3514988910,3615026531,3419047081,3759389361,3057388151,3376288723,3320504401],"Values":null,"Annotations":["7","10","13","20","23","37","47","56"]},{"Type":"Duration","ExperimentName":"performance - compile concurrently [4] - run serially - will go mod download first","Note":"","Name":"compile-test: pkg3","Style":"","Units":"duration","PrecisionBundle":{"Duration":100000,"ValueFormat":"%.3f"},"Durations":[3809462028,3399727117,3039324156,3158633124,2856780364,3402485807,3109946971,3054748690],"Values":null,"Annotations":["7","10","13","20","23","37","47","56"]},{"Type":"Duration","ExperimentName":"performance - compile concurrently [4] - run serially - will go mod download first","Note":"","Name":"run-test: pkg1","Style":"","Units":"duration","PrecisionBundle":{"Duration":100000,"ValueFormat":"%.3f"},"Durations":[692581518,699067499,549697731,562194312,567081909,647721923,564724385,604445969],"Values":null,"Annotations":["7","10","13","20","23","37","47","56"]},{"Type":"Duration","ExperimentName":"performance - compile concurrently [4] - run serially - will go mod download first","Note":"","Name":"run-test: pkg2","Style":"","Units":"duration","PrecisionBundle":{"Duration":100000,"ValueFormat":"%.3f"},"Durations":[626929798,500993706,564076405,538738987,559956127,566939849,592514670,596584483],"Values":null,"Annotations":["7","10","13","20","23","37","47","56"]},{"Type":"Duration","ExperimentName":"performance - compile concurrently [4] - run serially - will go mod download first","Note":"","Name":"run-test: performance","Style":"","Units":"duration","PrecisionBundle":{"Duration":100000,"ValueFormat":"%.3f"},"Durations":[549319679,509507953,573067048,558718119,554083978,633194800,529283014,538048224],"Values":null,"Annotations":["7","10","13","20","23","37","47","56"]},{"Type":"Duration","ExperimentName":"performance - compile concurrently [4] - run serially - will go mod download first","Note":"","Name":"run-test: pkg3","Style":"","Units":"duration","PrecisionBundle":{"Duration":100000,"ValueFormat":"%.3f"},"Durations":[491661759,512223229,712839812,516295920,719460835,556619167,563010112,572262215],"Values":null,"Annotations":["7","10","13","20","23","37","47","56"]},{"Type":"Duration","ExperimentName":"performance - compile concurrently [4] - run serially - will go mod download first","Note":"","Name":"compile-test: pkg4","Style":"","Units":"duration","PrecisionBundle":{"Duration":100000,"ValueFormat":"%.3f"},"Durations":[2392557937,2111296776,2431749453,2353025614,2294352311,2214451148,2282162395,2326675721],"Values":null,"Annotations":["7","10","13","20","23","37","47","56"]},{"Type":"Duration","ExperimentName":"performance - compile concurrently [4] - run serially - will go mod download first","Note":"","Name":"run-test: pkg4","Style":"","Units":"duration","PrecisionBundle":{"Duration":100000,"ValueFormat":"%.3f"},"Durations":[404082980,478772063,524670479,414988948,510522947,594597821,472493496,526049475],"Values":null,"Annotations":["7","10","13","20","23","37","47","56"]},{"Type":"Duration","ExperimentName":"performance - compile concurrently [4] - run serially - will go mod download first","Note":"","Name":"total-runtime","Style":"{{green}}","Units":"duration","PrecisionBundle":{"Duration":100000,"ValueFormat":"%.3f"},"Durations":[12162333263,12672351411,13263492562,12263776897,12091128403,12979393603,13305754705,12392571943],"Values":null,"Annotations":["7","10","13","20","23","37","47","56"]}]} 55354cfa01f77f2c0d502c798e2b8262.gmeasure-cache000066400000000000000000000126171472321612100370700ustar00rootroot00000000000000golang-github-onsi-ginkgo-v2-2.22.0/ginkgo/performance/fetching-dependencies-cache{"Name":"performance - compile serially - run serially - will go mod download first","Version":1} {"Name":"performance - compile serially - run serially - will go mod download first","Measurements":[{"Type":"Duration","ExperimentName":"performance - compile serially - run serially - will go mod download first","Note":"","Name":"mod-download","Style":"","Units":"duration","PrecisionBundle":{"Duration":100000,"ValueFormat":"%.3f"},"Durations":[6526318762,6899998083,6647388738,6680126933,7341998684,7302569872,7347866960,7560601116],"Values":null,"Annotations":["3","11","32","39","44","45","46","57"]},{"Type":"Duration","ExperimentName":"performance - compile serially - run serially - will go mod download first","Note":"","Name":"compile-test: performance","Style":"","Units":"duration","PrecisionBundle":{"Duration":100000,"ValueFormat":"%.3f"},"Durations":[1373975967,1453821490,1558704430,1746076933,1675686180,1617182241,1666232386,1572996387],"Values":null,"Annotations":["3","11","32","39","44","45","46","57"]},{"Type":"Duration","ExperimentName":"performance - compile serially - run serially - will go mod download first","Note":"","Name":"first-output","Style":"{{cyan}}","Units":"duration","PrecisionBundle":{"Duration":100000,"ValueFormat":"%.3f"},"Durations":[7902202566,8356058740,8207997742,8429581312,9019660674,8921969766,9016270484,9135678549],"Values":null,"Annotations":["3","11","32","39","44","45","46","57"]},{"Type":"Duration","ExperimentName":"performance - compile serially - run serially - will go mod download first","Note":"","Name":"run-test: performance","Style":"","Units":"duration","PrecisionBundle":{"Duration":100000,"ValueFormat":"%.3f"},"Durations":[467278075,553535583,485582347,609967175,508862223,585147669,512087297,716287875],"Values":null,"Annotations":["3","11","32","39","44","45","46","57"]},{"Type":"Duration","ExperimentName":"performance - compile serially - run serially - will go mod download first","Note":"","Name":"compile-test: pkg1","Style":"","Units":"duration","PrecisionBundle":{"Duration":100000,"ValueFormat":"%.3f"},"Durations":[1446461980,1700848929,1794057417,1884260145,1871825343,1800163753,1849103711,1864685462],"Values":null,"Annotations":["3","11","32","39","44","45","46","57"]},{"Type":"Duration","ExperimentName":"performance - compile serially - run serially - will go mod download first","Note":"","Name":"run-test: pkg1","Style":"","Units":"duration","PrecisionBundle":{"Duration":100000,"ValueFormat":"%.3f"},"Durations":[513746478,485775874,491649811,597905570,540011952,512567255,579702832,566041341],"Values":null,"Annotations":["3","11","32","39","44","45","46","57"]},{"Type":"Duration","ExperimentName":"performance - compile serially - run serially - will go mod download first","Note":"","Name":"compile-test: pkg2","Style":"","Units":"duration","PrecisionBundle":{"Duration":100000,"ValueFormat":"%.3f"},"Durations":[1733926188,1759044901,1840425684,1903351433,1874951298,1822446525,1954869122,1897847618],"Values":null,"Annotations":["3","11","32","39","44","45","46","57"]},{"Type":"Duration","ExperimentName":"performance - compile serially - run serially - will go mod download first","Note":"","Name":"run-test: pkg2","Style":"","Units":"duration","PrecisionBundle":{"Duration":100000,"ValueFormat":"%.3f"},"Durations":[537046324,478841313,610667882,501433512,605953697,525069333,613704326,585263933],"Values":null,"Annotations":["3","11","32","39","44","45","46","57"]},{"Type":"Duration","ExperimentName":"performance - compile serially - run serially - will go mod download first","Note":"","Name":"compile-test: pkg3","Style":"","Units":"duration","PrecisionBundle":{"Duration":100000,"ValueFormat":"%.3f"},"Durations":[1773589809,1610796615,1847346080,1850883899,1772499457,1743093390,2044281854,1848766266],"Values":null,"Annotations":["3","11","32","39","44","45","46","57"]},{"Type":"Duration","ExperimentName":"performance - compile serially - run serially - will go mod download first","Note":"","Name":"run-test: pkg3","Style":"","Units":"duration","PrecisionBundle":{"Duration":100000,"ValueFormat":"%.3f"},"Durations":[536342266,500663606,557634145,593247458,478804548,518123961,503873522,520920994],"Values":null,"Annotations":["3","11","32","39","44","45","46","57"]},{"Type":"Duration","ExperimentName":"performance - compile serially - run serially - will go mod download first","Note":"","Name":"compile-test: pkg4","Style":"","Units":"duration","PrecisionBundle":{"Duration":100000,"ValueFormat":"%.3f"},"Durations":[1769422924,1608186966,1913920002,1909835672,1923371017,1652960856,1859833460,1838229354],"Values":null,"Annotations":["3","11","32","39","44","45","46","57"]},{"Type":"Duration","ExperimentName":"performance - compile serially - run serially - will go mod download first","Note":"","Name":"run-test: pkg4","Style":"","Units":"duration","PrecisionBundle":{"Duration":100000,"ValueFormat":"%.3f"},"Durations":[464981433,438310364,472377831,486595807,473316346,404583597,483236714,470095277],"Values":null,"Annotations":["3","11","32","39","44","45","46","57"]},{"Type":"Duration","ExperimentName":"performance - compile serially - run serially - will go mod download first","Note":"","Name":"total-runtime","Style":"{{green}}","Units":"duration","PrecisionBundle":{"Duration":100000,"ValueFormat":"%.3f"},"Durations":[15090852842,15473482567,16076329068,16464747573,16935970042,16346139950,17208042900,17055528787],"Values":null,"Annotations":["3","11","32","39","44","45","46","57"]}]} 5eb5d96af3c34f61a070c450dddbf894.gmeasure-cache000066400000000000000000000127721472321612100373730ustar00rootroot00000000000000golang-github-onsi-ginkgo-v2-2.22.0/ginkgo/performance/fetching-dependencies-cache{"Name":"performance - compile concurrently [2] - run serially - will go mod download first","Version":1} {"Name":"performance - compile concurrently [2] - run serially - will go mod download first","Measurements":[{"Type":"Duration","ExperimentName":"performance - compile concurrently [2] - run serially - will go mod download first","Note":"","Name":"mod-download","Style":"","Units":"duration","PrecisionBundle":{"Duration":100000,"ValueFormat":"%.3f"},"Durations":[7945127700,7475055679,7473131239,6839314648,6095280976,6392868331,7262475188,6369440715],"Values":null,"Annotations":["8","9","12","14","16","43","58","61"]},{"Type":"Duration","ExperimentName":"performance - compile concurrently [2] - run serially - will go mod download first","Note":"","Name":"compile-test: performance","Style":"","Units":"duration","PrecisionBundle":{"Duration":100000,"ValueFormat":"%.3f"},"Durations":[2036305712,1941419309,2200150517,1897241771,1916652587,1864477838,2092893586,1958164327],"Values":null,"Annotations":["8","9","12","14","16","43","58","61"]},{"Type":"Duration","ExperimentName":"performance - compile concurrently [2] - run serially - will go mod download first","Note":"","Name":"first-output","Style":"{{cyan}}","Units":"duration","PrecisionBundle":{"Duration":100000,"ValueFormat":"%.3f"},"Durations":[9983428626,9420039290,9381010690,8738676901,8007666995,8259454156,9347985499,8330766260],"Values":null,"Annotations":["8","9","12","14","16","43","58","61"]},{"Type":"Duration","ExperimentName":"performance - compile concurrently [2] - run serially - will go mod download first","Note":"","Name":"compile-test: pkg1","Style":"","Units":"duration","PrecisionBundle":{"Duration":100000,"ValueFormat":"%.3f"},"Durations":[2147160582,2344725984,1905846370,1906346515,1909556306,2006389274,2082948663,2317275661],"Values":null,"Annotations":["8","9","12","14","16","43","58","61"]},{"Type":"Duration","ExperimentName":"performance - compile concurrently [2] - run serially - will go mod download first","Note":"","Name":"run-test: performance","Style":"","Units":"duration","PrecisionBundle":{"Duration":100000,"ValueFormat":"%.3f"},"Durations":[708727237,720940913,664677971,696280577,714538189,731394919,801168302,979300207],"Values":null,"Annotations":["8","9","12","14","16","43","58","61"]},{"Type":"Duration","ExperimentName":"performance - compile concurrently [2] - run serially - will go mod download first","Note":"","Name":"run-test: pkg1","Style":"","Units":"duration","PrecisionBundle":{"Duration":100000,"ValueFormat":"%.3f"},"Durations":[770984700,838307905,842230131,847769170,829683988,839620344,703239343,807218704],"Values":null,"Annotations":["8","9","12","14","16","43","58","61"]},{"Type":"Duration","ExperimentName":"performance - compile concurrently [2] - run serially - will go mod download first","Note":"","Name":"compile-test: pkg2","Style":"","Units":"duration","PrecisionBundle":{"Duration":100000,"ValueFormat":"%.3f"},"Durations":[2388215428,2394336844,2453244145,2456306791,2311255623,2522116456,2479441501,3006569105],"Values":null,"Annotations":["8","9","12","14","16","43","58","61"]},{"Type":"Duration","ExperimentName":"performance - compile concurrently [2] - run serially - will go mod download first","Note":"","Name":"compile-test: pkg3","Style":"","Units":"duration","PrecisionBundle":{"Duration":100000,"ValueFormat":"%.3f"},"Durations":[2498708155,2381298210,2286092132,2440765850,2325113712,2521064795,2620545996,2941828653],"Values":null,"Annotations":["8","9","12","14","16","43","58","61"]},{"Type":"Duration","ExperimentName":"performance - compile concurrently [2] - run serially - will go mod download first","Note":"","Name":"run-test: pkg2","Style":"","Units":"duration","PrecisionBundle":{"Duration":100000,"ValueFormat":"%.3f"},"Durations":[561102507,613789869,513620772,791400353,487549472,523758589,681232219,747587707],"Values":null,"Annotations":["8","9","12","14","16","43","58","61"]},{"Type":"Duration","ExperimentName":"performance - compile concurrently [2] - run serially - will go mod download first","Note":"","Name":"run-test: pkg3","Style":"","Units":"duration","PrecisionBundle":{"Duration":100000,"ValueFormat":"%.3f"},"Durations":[640299957,568585363,609162942,538489828,575395769,625814078,653982883,579112677],"Values":null,"Annotations":["8","9","12","14","16","43","58","61"]},{"Type":"Duration","ExperimentName":"performance - compile concurrently [2] - run serially - will go mod download first","Note":"","Name":"compile-test: pkg4","Style":"","Units":"duration","PrecisionBundle":{"Duration":100000,"ValueFormat":"%.3f"},"Durations":[1885740872,1762738028,1916624911,2080145374,1703488397,2040725430,2335453141,2250922898],"Values":null,"Annotations":["8","9","12","14","16","43","58","61"]},{"Type":"Duration","ExperimentName":"performance - compile concurrently [2] - run serially - will go mod download first","Note":"","Name":"run-test: pkg4","Style":"","Units":"duration","PrecisionBundle":{"Duration":100000,"ValueFormat":"%.3f"},"Durations":[343873223,392284971,470528370,481167767,358547310,490276211,474770818,509237938],"Values":null,"Annotations":["8","9","12","14","16","43","58","61"]},{"Type":"Duration","ExperimentName":"performance - compile concurrently [2] - run serially - will go mod download first","Note":"","Name":"total-runtime","Style":"{{green}}","Units":"duration","PrecisionBundle":{"Duration":100000,"ValueFormat":"%.3f"},"Durations":[14601735902,13969484530,14221576654,13750146982,12380750618,13312740404,14637727040,14096495723],"Values":null,"Annotations":["8","9","12","14","16","43","58","61"]}]} 63f93d6849139270a9cf6c7562b5c5c2.gmeasure-cache000066400000000000000000000113231472321612100370240ustar00rootroot00000000000000golang-github-onsi-ginkgo-v2-2.22.0/ginkgo/performance/fetching-dependencies-cache{"Name":"performance - compile concurrently [2] - run serially","Version":1} {"Name":"performance - compile concurrently [2] - run serially","Measurements":[{"Type":"Duration","ExperimentName":"performance - compile concurrently [2] - run serially","Note":"","Name":"compile-test: performance","Style":"","Units":"duration","PrecisionBundle":{"Duration":100000,"ValueFormat":"%.3f"},"Durations":[6311455077,5847080057,6487023070,6758345763,7118100789,6154598180,6701069851,7139157423],"Values":null,"Annotations":["2","15","19","21","24","38","48","55"]},{"Type":"Duration","ExperimentName":"performance - compile concurrently [2] - run serially","Note":"","Name":"first-output","Style":"{{cyan}}","Units":"duration","PrecisionBundle":{"Duration":100000,"ValueFormat":"%.3f"},"Durations":[7013596056,7664337225,7130462696,8080035728,8666072278,7831626524,8412861473,7916356295],"Values":null,"Annotations":["2","15","19","21","24","38","48","55"]},{"Type":"Duration","ExperimentName":"performance - compile concurrently [2] - run serially","Note":"","Name":"compile-test: pkg1","Style":"","Units":"duration","PrecisionBundle":{"Duration":100000,"ValueFormat":"%.3f"},"Durations":[6329783102,6077328167,6520899641,6612399843,7353468208,6161194727,6693081852,7392439511],"Values":null,"Annotations":["2","15","19","21","24","38","48","55"]},{"Type":"Duration","ExperimentName":"performance - compile concurrently [2] - run serially","Note":"","Name":"run-test: performance","Style":"","Units":"duration","PrecisionBundle":{"Duration":100000,"ValueFormat":"%.3f"},"Durations":[777734970,864165853,911241713,845043393,822922876,762235036,811732135,762004110],"Values":null,"Annotations":["2","15","19","21","24","38","48","55"]},{"Type":"Duration","ExperimentName":"performance - compile concurrently [2] - run serially","Note":"","Name":"run-test: pkg1","Style":"","Units":"duration","PrecisionBundle":{"Duration":100000,"ValueFormat":"%.3f"},"Durations":[811809367,749465799,741534479,942032566,729873995,768831389,710807376,876170387],"Values":null,"Annotations":["2","15","19","21","24","38","48","55"]},{"Type":"Duration","ExperimentName":"performance - compile concurrently [2] - run serially","Note":"","Name":"compile-test: pkg3","Style":"","Units":"duration","PrecisionBundle":{"Duration":100000,"ValueFormat":"%.3f"},"Durations":[2343112380,2536365157,2563177821,2652119386,2263775696,2435330391,2418565785,2328990477],"Values":null,"Annotations":["2","15","19","21","24","38","48","55"]},{"Type":"Duration","ExperimentName":"performance - compile concurrently [2] - run serially","Note":"","Name":"compile-test: pkg2","Style":"","Units":"duration","PrecisionBundle":{"Duration":100000,"ValueFormat":"%.3f"},"Durations":[2366219198,2452771417,2595813389,2683289723,2470279702,2760951395,2436558246,2565203887],"Values":null,"Annotations":["2","15","19","21","24","38","48","55"]},{"Type":"Duration","ExperimentName":"performance - compile concurrently [2] - run serially","Note":"","Name":"run-test: pkg3","Style":"","Units":"duration","PrecisionBundle":{"Duration":100000,"ValueFormat":"%.3f"},"Durations":[543120008,653560253,690860482,611919056,616108126,595855581,710551279,607086464],"Values":null,"Annotations":["2","15","19","21","24","38","48","55"]},{"Type":"Duration","ExperimentName":"performance - compile concurrently [2] - run serially","Note":"","Name":"run-test: pkg2","Style":"","Units":"duration","PrecisionBundle":{"Duration":100000,"ValueFormat":"%.3f"},"Durations":[692661068,539744751,534992530,554264670,576894131,557836692,652047839,544718421],"Values":null,"Annotations":["2","15","19","21","24","38","48","55"]},{"Type":"Duration","ExperimentName":"performance - compile concurrently [2] - run serially","Note":"","Name":"compile-test: pkg4","Style":"","Units":"duration","PrecisionBundle":{"Duration":100000,"ValueFormat":"%.3f"},"Durations":[2004155543,2089222176,2082639174,1991173804,1967428938,2024412372,2082317352,2061262470],"Values":null,"Annotations":["2","15","19","21","24","38","48","55"]},{"Type":"Duration","ExperimentName":"performance - compile concurrently [2] - run serially","Note":"","Name":"run-test: pkg4","Style":"","Units":"duration","PrecisionBundle":{"Duration":100000,"ValueFormat":"%.3f"},"Durations":[490424557,489103469,495359283,493744837,490790320,507789281,492652566,462994263],"Values":null,"Annotations":["2","15","19","21","24","38","48","55"]},{"Type":"Duration","ExperimentName":"performance - compile concurrently [2] - run serially","Note":"","Name":"total-runtime","Style":"{{green}}","Units":"duration","PrecisionBundle":{"Duration":100000,"ValueFormat":"%.3f"},"Durations":[11869956386,12695492903,12304741355,13248351598,13594656607,12806407028,13414720588,13005941570],"Values":null,"Annotations":["2","15","19","21","24","38","48","55"]}]} 952f6c671d14a219922ff81d601950dc.gmeasure-cache000066400000000000000000000113231472321612100370050ustar00rootroot00000000000000golang-github-onsi-ginkgo-v2-2.22.0/ginkgo/performance/fetching-dependencies-cache{"Name":"performance - compile concurrently [4] - run serially","Version":1} {"Name":"performance - compile concurrently [4] - run serially","Measurements":[{"Type":"Duration","ExperimentName":"performance - compile concurrently [4] - run serially","Note":"","Name":"compile-test: pkg3","Style":"","Units":"duration","PrecisionBundle":{"Duration":100000,"ValueFormat":"%.3f"},"Durations":[7532457486,8374020650,8008357324,7799006436,8089911928,8637456582,7986441710,8894476262],"Values":null,"Annotations":["4","18","30","33","35","50","53","59"]},{"Type":"Duration","ExperimentName":"performance - compile concurrently [4] - run serially","Note":"","Name":"first-output","Style":"{{cyan}}","Units":"duration","PrecisionBundle":{"Duration":100000,"ValueFormat":"%.3f"},"Durations":[9115072648,8745450921,8741210971,9306948860,8805141822,9282606324,8746652001,9893173471],"Values":null,"Annotations":["4","18","30","33","35","50","53","59"]},{"Type":"Duration","ExperimentName":"performance - compile concurrently [4] - run serially","Note":"","Name":"compile-test: pkg1","Style":"","Units":"duration","PrecisionBundle":{"Duration":100000,"ValueFormat":"%.3f"},"Durations":[8001522578,8173217721,8230386606,8109136012,8534479066,8476147712,8652574610,8801428369],"Values":null,"Annotations":["4","18","30","33","35","50","53","59"]},{"Type":"Duration","ExperimentName":"performance - compile concurrently [4] - run serially","Note":"","Name":"compile-test: pkg2","Style":"","Units":"duration","PrecisionBundle":{"Duration":100000,"ValueFormat":"%.3f"},"Durations":[8082752626,8186856389,8229920378,7784540147,8616146507,9018187558,8451187098,8546824233],"Values":null,"Annotations":["4","18","30","33","35","50","53","59"]},{"Type":"Duration","ExperimentName":"performance - compile concurrently [4] - run serially","Note":"","Name":"compile-test: performance","Style":"","Units":"duration","PrecisionBundle":{"Duration":100000,"ValueFormat":"%.3f"},"Durations":[8083968792,8378195130,8121476717,8165516185,8122078444,8837496150,8913943169,8347804657],"Values":null,"Annotations":["4","18","30","33","35","50","53","59"]},{"Type":"Duration","ExperimentName":"performance - compile concurrently [4] - run serially","Note":"","Name":"run-test: pkg3","Style":"","Units":"duration","PrecisionBundle":{"Duration":100000,"ValueFormat":"%.3f"},"Durations":[792385216,545809314,651046959,603129075,729958127,599530962,806259422,566623999],"Values":null,"Annotations":["4","18","30","33","35","50","53","59"]},{"Type":"Duration","ExperimentName":"performance - compile concurrently [4] - run serially","Note":"","Name":"run-test: pkg1","Style":"","Units":"duration","PrecisionBundle":{"Duration":100000,"ValueFormat":"%.3f"},"Durations":[545303994,684463420,566577200,547684536,556784773,760799148,570363612,550830243],"Values":null,"Annotations":["4","18","30","33","35","50","53","59"]},{"Type":"Duration","ExperimentName":"performance - compile concurrently [4] - run serially","Note":"","Name":"run-test: pkg2","Style":"","Units":"duration","PrecisionBundle":{"Duration":100000,"ValueFormat":"%.3f"},"Durations":[566405498,651925395,558543584,692081951,628570695,718956103,603654069,598987569],"Values":null,"Annotations":["4","18","30","33","35","50","53","59"]},{"Type":"Duration","ExperimentName":"performance - compile concurrently [4] - run serially","Note":"","Name":"run-test: performance","Style":"","Units":"duration","PrecisionBundle":{"Duration":100000,"ValueFormat":"%.3f"},"Durations":[467290597,539817765,537043601,500758702,595285364,533339102,618985516,768434748],"Values":null,"Annotations":["4","18","30","33","35","50","53","59"]},{"Type":"Duration","ExperimentName":"performance - compile concurrently [4] - run serially","Note":"","Name":"compile-test: pkg4","Style":"","Units":"duration","PrecisionBundle":{"Duration":100000,"ValueFormat":"%.3f"},"Durations":[2413168360,2380604424,2394682842,2298577668,2444348433,2440220016,2623701018,2411170477],"Values":null,"Annotations":["4","18","30","33","35","50","53","59"]},{"Type":"Duration","ExperimentName":"performance - compile concurrently [4] - run serially","Note":"","Name":"run-test: pkg4","Style":"","Units":"duration","PrecisionBundle":{"Duration":100000,"ValueFormat":"%.3f"},"Durations":[406021526,440469848,505757757,513073914,564927114,532661793,524685049,546591178],"Values":null,"Annotations":["4","18","30","33","35","50","53","59"]},{"Type":"Duration","ExperimentName":"performance - compile concurrently [4] - run serially","Note":"","Name":"total-runtime","Style":"{{green}}","Units":"duration","PrecisionBundle":{"Duration":100000,"ValueFormat":"%.3f"},"Durations":[11934450605,11608976937,11641098097,12164456259,11881415862,12428318954,11895231489,12925062196],"Values":null,"Annotations":["4","18","30","33","35","50","53","59"]}]} cb799de836ba52ec5dbc8328c8cfb89a.gmeasure-cache000066400000000000000000000111431472321612100375450ustar00rootroot00000000000000golang-github-onsi-ginkgo-v2-2.22.0/ginkgo/performance/fetching-dependencies-cache{"Name":"performance - compile serially - run serially","Version":1} {"Name":"performance - compile serially - run serially","Measurements":[{"Type":"Duration","ExperimentName":"performance - compile serially - run serially","Note":"","Name":"compile-test: performance","Style":"","Units":"duration","PrecisionBundle":{"Duration":100000,"ValueFormat":"%.3f"},"Durations":[6318949115,6189929472,6130101132,6574013600,6076621559,6159058647,6353888438,6247688448],"Values":null,"Annotations":["1","29","34","42","54","62","63","64"]},{"Type":"Duration","ExperimentName":"performance - compile serially - run serially","Note":"","Name":"first-output","Style":"{{cyan}}","Units":"duration","PrecisionBundle":{"Duration":100000,"ValueFormat":"%.3f"},"Durations":[6359955689,6896828293,6877256556,7360127173,6901078969,7898226340,7148839498,6917964778],"Values":null,"Annotations":["1","29","34","42","54","62","63","64"]},{"Type":"Duration","ExperimentName":"performance - compile serially - run serially","Note":"","Name":"run-test: performance","Style":"","Units":"duration","PrecisionBundle":{"Duration":100000,"ValueFormat":"%.3f"},"Durations":[592497180,532848605,570444612,631195154,717462201,576545587,612541389,530360538],"Values":null,"Annotations":["1","29","34","42","54","62","63","64"]},{"Type":"Duration","ExperimentName":"performance - compile serially - run serially","Note":"","Name":"compile-test: pkg1","Style":"","Units":"duration","PrecisionBundle":{"Duration":100000,"ValueFormat":"%.3f"},"Durations":[1638656221,1862102544,1848517075,1828789826,1941114762,2030811569,1825059799,1867375123],"Values":null,"Annotations":["1","29","34","42","54","62","63","64"]},{"Type":"Duration","ExperimentName":"performance - compile serially - run serially","Note":"","Name":"run-test: pkg1","Style":"","Units":"duration","PrecisionBundle":{"Duration":100000,"ValueFormat":"%.3f"},"Durations":[539638821,494153822,530805523,523827580,622053341,542131689,555881315,508199084],"Values":null,"Annotations":["1","29","34","42","54","62","63","64"]},{"Type":"Duration","ExperimentName":"performance - compile serially - run serially","Note":"","Name":"compile-test: pkg2","Style":"","Units":"duration","PrecisionBundle":{"Duration":100000,"ValueFormat":"%.3f"},"Durations":[1714455830,1738013130,1848867568,1840111251,1778771286,1861826845,1783470214,1809376473],"Values":null,"Annotations":["1","29","34","42","54","62","63","64"]},{"Type":"Duration","ExperimentName":"performance - compile serially - run serially","Note":"","Name":"run-test: pkg2","Style":"","Units":"duration","PrecisionBundle":{"Duration":100000,"ValueFormat":"%.3f"},"Durations":[499482668,541211412,554783533,506782203,567465585,508541680,515709730,516906242],"Values":null,"Annotations":["1","29","34","42","54","62","63","64"]},{"Type":"Duration","ExperimentName":"performance - compile serially - run serially","Note":"","Name":"compile-test: pkg3","Style":"","Units":"duration","PrecisionBundle":{"Duration":100000,"ValueFormat":"%.3f"},"Durations":[1824875064,1809035365,1890474308,1755656274,1848196693,1682436228,1776189809,1811071016],"Values":null,"Annotations":["1","29","34","42","54","62","63","64"]},{"Type":"Duration","ExperimentName":"performance - compile serially - run serially","Note":"","Name":"run-test: pkg3","Style":"","Units":"duration","PrecisionBundle":{"Duration":100000,"ValueFormat":"%.3f"},"Durations":[545572750,587354491,534335855,507516835,594976589,730187812,583448482,600564699],"Values":null,"Annotations":["1","29","34","42","54","62","63","64"]},{"Type":"Duration","ExperimentName":"performance - compile serially - run serially","Note":"","Name":"compile-test: pkg4","Style":"","Units":"duration","PrecisionBundle":{"Duration":100000,"ValueFormat":"%.3f"},"Durations":[1731746172,1759664082,1826978621,1749098128,1762782981,1952540964,1847655242,1919125676],"Values":null,"Annotations":["1","29","34","42","54","62","63","64"]},{"Type":"Duration","ExperimentName":"performance - compile serially - run serially","Note":"","Name":"run-test: pkg4","Style":"","Units":"duration","PrecisionBundle":{"Duration":100000,"ValueFormat":"%.3f"},"Durations":[422073849,469036322,480793508,435301949,486493130,506474233,438981909,471451515],"Values":null,"Annotations":["1","29","34","42","54","62","63","64"]},{"Type":"Duration","ExperimentName":"performance - compile serially - run serially","Note":"","Name":"total-runtime","Style":"{{green}}","Units":"duration","PrecisionBundle":{"Duration":100000,"ValueFormat":"%.3f"},"Durations":[13691890801,14535096671,14773031439,14969310814,14718628501,15932553600,14820479808,14796513254],"Values":null,"Annotations":["1","29","34","42","54","62","63","64"]}]} e8c3ef1eebd492c875763c75066b201d.gmeasure-cache000066400000000000000000000123361472321612100372360ustar00rootroot00000000000000golang-github-onsi-ginkgo-v2-2.22.0/ginkgo/performance/fetching-dependencies-cache{"Name":"performance - compile concurrently [4] - run serially - will compile first suite serially","Version":1} {"Name":"performance - compile concurrently [4] - run serially - will compile first suite serially","Measurements":[{"Type":"Duration","ExperimentName":"performance - compile concurrently [4] - run serially - will compile first suite serially","Note":"","Name":"compile-test: performance","Style":"","Units":"duration","PrecisionBundle":{"Duration":100000,"ValueFormat":"%.3f"},"Durations":[6100504304,6022110846,6132678649,6124965962,5842128754,5990242789,6201981252,6104516248],"Values":null,"Annotations":["17","22","25","26","36","49","51","52"]},{"Type":"Duration","ExperimentName":"performance - compile concurrently [4] - run serially - will compile first suite serially","Note":"","Name":"first-output","Style":"{{cyan}}","Units":"duration","PrecisionBundle":{"Duration":100000,"ValueFormat":"%.3f"},"Durations":[7527172336,6808766453,6920815758,6839486399,6718240527,6686914486,6986749783,6796828838],"Values":null,"Annotations":["17","22","25","26","36","49","51","52"]},{"Type":"Duration","ExperimentName":"performance - compile concurrently [4] - run serially - will compile first suite serially","Note":"","Name":"run-test: performance","Style":"","Units":"duration","PrecisionBundle":{"Duration":100000,"ValueFormat":"%.3f"},"Durations":[1075912317,1093989432,1081293165,958614397,1096183925,1035472403,1041110529,1035047753],"Values":null,"Annotations":["17","22","25","26","36","49","51","52"]},{"Type":"Duration","ExperimentName":"performance - compile concurrently [4] - run serially - will compile first suite serially","Note":"","Name":"compile-test: pkg3","Style":"","Units":"duration","PrecisionBundle":{"Duration":100000,"ValueFormat":"%.3f"},"Durations":[3470132699,3376386674,3766796063,3620552270,3745014437,3227438403,3511788966,3289460715],"Values":null,"Annotations":["17","22","25","26","36","49","51","52"]},{"Type":"Duration","ExperimentName":"performance - compile concurrently [4] - run serially - will compile first suite serially","Note":"","Name":"compile-test: pkg4","Style":"","Units":"duration","PrecisionBundle":{"Duration":100000,"ValueFormat":"%.3f"},"Durations":[3477630245,3537314925,3768413205,3295953915,3709172133,3784258169,3455042392,3655337739],"Values":null,"Annotations":["17","22","25","26","36","49","51","52"]},{"Type":"Duration","ExperimentName":"performance - compile concurrently [4] - run serially - will compile first suite serially","Note":"","Name":"compile-test: pkg1","Style":"","Units":"duration","PrecisionBundle":{"Duration":100000,"ValueFormat":"%.3f"},"Durations":[3644995679,3422920430,3662380700,3665081082,3621895084,3505131282,3516145263,3516501569],"Values":null,"Annotations":["17","22","25","26","36","49","51","52"]},{"Type":"Duration","ExperimentName":"performance - compile concurrently [4] - run serially - will compile first suite serially","Note":"","Name":"compile-test: pkg2","Style":"","Units":"duration","PrecisionBundle":{"Duration":100000,"ValueFormat":"%.3f"},"Durations":[3680777229,3423028684,3830109154,3644187121,3620996104,3514334473,3461827137,3464301818],"Values":null,"Annotations":["17","22","25","26","36","49","51","52"]},{"Type":"Duration","ExperimentName":"performance - compile concurrently [4] - run serially - will compile first suite serially","Note":"","Name":"run-test: pkg3","Style":"","Units":"duration","PrecisionBundle":{"Duration":100000,"ValueFormat":"%.3f"},"Durations":[457351966,406855804,538647459,535882605,511992022,475221335,547889491,528996488],"Values":null,"Annotations":["17","22","25","26","36","49","51","52"]},{"Type":"Duration","ExperimentName":"performance - compile concurrently [4] - run serially - will compile first suite serially","Note":"","Name":"run-test: pkg4","Style":"","Units":"duration","PrecisionBundle":{"Duration":100000,"ValueFormat":"%.3f"},"Durations":[453750658,437850569,605498127,512486831,550294588,525046969,480138468,552576494],"Values":null,"Annotations":["17","22","25","26","36","49","51","52"]},{"Type":"Duration","ExperimentName":"performance - compile concurrently [4] - run serially - will compile first suite serially","Note":"","Name":"run-test: pkg1","Style":"","Units":"duration","PrecisionBundle":{"Duration":100000,"ValueFormat":"%.3f"},"Durations":[459257781,467166614,485107243,548429020,533714384,563870951,539934220,547289956],"Values":null,"Annotations":["17","22","25","26","36","49","51","52"]},{"Type":"Duration","ExperimentName":"performance - compile concurrently [4] - run serially - will compile first suite serially","Note":"","Name":"run-test: pkg2","Style":"","Units":"duration","PrecisionBundle":{"Duration":100000,"ValueFormat":"%.3f"},"Durations":[432908776,447296203,590344111,550916373,436006867,547962549,567317965,502148485],"Values":null,"Annotations":["17","22","25","26","36","49","51","52"]},{"Type":"Duration","ExperimentName":"performance - compile concurrently [4] - run serially - will compile first suite serially","Note":"","Name":"total-runtime","Style":"{{green}}","Units":"duration","PrecisionBundle":{"Duration":100000,"ValueFormat":"%.3f"},"Durations":[12800744093,11944340814,12804676718,12283615780,12372137615,12026656215,12575533603,12215980595],"Values":null,"Annotations":["17","22","25","26","36","49","51","52"]}]} golang-github-onsi-ginkgo-v2-2.22.0/ginkgo/performance/fetching_dependencies_test.go000066400000000000000000000050031472321612100305320ustar00rootroot00000000000000package performance_test import ( "fmt" "os" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" "github.com/onsi/gomega/gmeasure" ) var _ = Describe("Fetching Dependencies", func() { var cache gmeasure.ExperimentCache BeforeEach(func() { if os.Getenv("PERF") == "" { Skip("PERF environment not set, skipping") } var err error cache, err = gmeasure.NewExperimentCache("./fetching-dependencies-cache") Ω(err).ShouldNot(HaveOccurred()) // we mount everything outside the Ginkgo parent directory to make sure GOMODULES doesn't get confused by the go.mod in Ginkgo's root pfm = NewPerformanceFixtureManager(fmt.Sprintf("../../../ginkgo_perf_tmp_%d", GinkgoParallelProcess())) gmcm = NewGoModCacheManager(fmt.Sprintf("../../../ginkgo_perf_cache_%d", GinkgoParallelProcess())) if !DEBUG { DeferCleanup(pfm.Cleanup) DeferCleanup(gmcm.Cleanup) } }) Describe("Experiments", func() { BeforeEach(func() { pfm.MountFixture("performance") }) It("runs a series of experiments with various scenarios", func() { SampleScenarios(cache, 8, 1, false, ScenarioSettings{Fixture: "performance", NumSuites: 5, ConcurrentCompilers: 1, ConcurrentRunners: 1, Recurse: true, ClearGoModCache: true}, ScenarioSettings{Fixture: "performance", NumSuites: 5, ConcurrentCompilers: 2, ConcurrentRunners: 1, Recurse: true, ClearGoModCache: true}, ScenarioSettings{Fixture: "performance", NumSuites: 5, ConcurrentCompilers: 4, ConcurrentRunners: 1, Recurse: true, ClearGoModCache: true}, ScenarioSettings{Fixture: "performance", NumSuites: 5, ConcurrentCompilers: 1, ConcurrentRunners: 1, GoModDownloadFirst: true, Recurse: true, ClearGoModCache: true}, ScenarioSettings{Fixture: "performance", NumSuites: 5, ConcurrentCompilers: 2, ConcurrentRunners: 1, GoModDownloadFirst: true, Recurse: true, ClearGoModCache: true}, ScenarioSettings{Fixture: "performance", NumSuites: 5, ConcurrentCompilers: 4, ConcurrentRunners: 1, GoModDownloadFirst: true, Recurse: true, ClearGoModCache: true}, ScenarioSettings{Fixture: "performance", NumSuites: 5, ConcurrentCompilers: 2, ConcurrentRunners: 1, CompileFirstSuiteSerially: true, Recurse: true, ClearGoModCache: true}, ScenarioSettings{Fixture: "performance", NumSuites: 5, ConcurrentCompilers: 4, ConcurrentRunners: 1, CompileFirstSuiteSerially: true, Recurse: true, ClearGoModCache: true}, ) }) }) Describe("Analysis", func() { It("analyzes the various fetching dependencies scenarios to identify winners", func() { AnalyzeCache(cache) }) }) }) golang-github-onsi-ginkgo-v2-2.22.0/ginkgo/performance/large-suite-cache/000077500000000000000000000000001472321612100261335ustar00rootroot0000000000000029caa3726740f30e27035121724c5be5.gmeasure-cache000066400000000000000000000007151472321612100347120ustar00rootroot00000000000000golang-github-onsi-ginkgo-v2-2.22.0/ginkgo/performance/large-suite-cache{"Name":"parallel-RPC-DUP-benchmark","Version":2} {"Name":"parallel-RPC-DUP-benchmark","Measurements":[{"Type":"Duration","ExperimentName":"parallel-RPC-DUP-benchmark","Note":"","Name":"runtime","Style":"","Units":"duration","PrecisionBundle":{"Duration":100000,"ValueFormat":"%.3f"},"Durations":[716650216,771200773,711325050,673170809,713877574,697856770,714497572,669317467,671197645,735533141],"Values":null,"Annotations":["","","","","","","","","",""]}]} 483027456d601caec33292dbf280ca13.gmeasure-cache000066400000000000000000000007231472321612100350470ustar00rootroot00000000000000golang-github-onsi-ginkgo-v2-2.22.0/ginkgo/performance/large-suite-cache{"Name":"parallel-HTTP-DUP-benchmark","Version":2} {"Name":"parallel-HTTP-DUP-benchmark","Measurements":[{"Type":"Duration","ExperimentName":"parallel-HTTP-DUP-benchmark","Note":"","Name":"runtime","Style":"","Units":"duration","PrecisionBundle":{"Duration":100000,"ValueFormat":"%.3f"},"Durations":[1048938295,979871652,1029311907,1017796008,973387413,945158296,959780339,974684980,960298575,958206313],"Values":null,"Annotations":["","","","","","","","","",""]}]} 54b26463f7c006bbf018993ad3bb66fc.gmeasure-cache000066400000000000000000000007201472321612100352240ustar00rootroot00000000000000golang-github-onsi-ginkgo-v2-2.22.0/ginkgo/performance/large-suite-cache{"Name":"parallel-RPC-SWAP-benchmark","Version":2} {"Name":"parallel-RPC-SWAP-benchmark","Measurements":[{"Type":"Duration","ExperimentName":"parallel-RPC-SWAP-benchmark","Note":"","Name":"runtime","Style":"","Units":"duration","PrecisionBundle":{"Duration":100000,"ValueFormat":"%.3f"},"Durations":[691170592,701798762,671782049,656337074,680936039,684759574,672048754,689200123,671827343,651730201],"Values":null,"Annotations":["","","","","","","","","",""]}]} 6f1839af2f330e23013bdb24a846760c.gmeasure-cache000066400000000000000000000007231472321612100350550ustar00rootroot00000000000000golang-github-onsi-ginkgo-v2-2.22.0/ginkgo/performance/large-suite-cache{"Name":"parallel-HTTP-NONE-benchmark","Version":2} {"Name":"parallel-HTTP-NONE-benchmark","Measurements":[{"Type":"Duration","ExperimentName":"parallel-HTTP-NONE-benchmark","Note":"","Name":"runtime","Style":"","Units":"duration","PrecisionBundle":{"Duration":100000,"ValueFormat":"%.3f"},"Durations":[823209014,849832205,864494051,822988536,849038025,801723792,856141724,794304618,856987786,824270814],"Values":null,"Annotations":["","","","","","","","","",""]}]} e3c9c7bf7ee9b54d714975dcc0babed2.gmeasure-cache000066400000000000000000000007201472321612100356170ustar00rootroot00000000000000golang-github-onsi-ginkgo-v2-2.22.0/ginkgo/performance/large-suite-cache{"Name":"parallel-RPC-NONE-benchmark","Version":2} {"Name":"parallel-RPC-NONE-benchmark","Measurements":[{"Type":"Duration","ExperimentName":"parallel-RPC-NONE-benchmark","Note":"","Name":"runtime","Style":"","Units":"duration","PrecisionBundle":{"Duration":100000,"ValueFormat":"%.3f"},"Durations":[599153201,582248316,571991429,601524318,600471310,572268645,579946670,565307695,576495094,583082601],"Values":null,"Annotations":["","","","","","","","","",""]}]} e6de2fca259bb2567b057a5ec1929134.gmeasure-cache000066400000000000000000000006571472321612100352370ustar00rootroot00000000000000golang-github-onsi-ginkgo-v2-2.22.0/ginkgo/performance/large-suite-cache{"Name":"serial-benchmark","Version":2} {"Name":"serial-benchmark","Measurements":[{"Type":"Duration","ExperimentName":"serial-benchmark","Note":"","Name":"runtime","Style":"","Units":"duration","PrecisionBundle":{"Duration":100000,"ValueFormat":"%.3f"},"Durations":[388655309,146287272,146904226,147067367,139489833,142630513,144777032,146146198,151479114,140571516],"Values":null,"Annotations":["","","","","","","","","",""]}]} f9eea3525ae1eb0c1aea29bb32267883.gmeasure-cache000066400000000000000000000007241472321612100353610ustar00rootroot00000000000000golang-github-onsi-ginkgo-v2-2.22.0/ginkgo/performance/large-suite-cache{"Name":"parallel-HTTP-SWAP-benchmark","Version":2} {"Name":"parallel-HTTP-SWAP-benchmark","Measurements":[{"Type":"Duration","ExperimentName":"parallel-HTTP-SWAP-benchmark","Note":"","Name":"runtime","Style":"","Units":"duration","PrecisionBundle":{"Duration":100000,"ValueFormat":"%.3f"},"Durations":[955311818,997357853,931952186,941507165,985201796,1000907396,987021009,937432529,934648543,947429709],"Values":null,"Annotations":["","","","","","","","","",""]}]} golang-github-onsi-ginkgo-v2-2.22.0/ginkgo/performance/large_suite_test.go000066400000000000000000000066041472321612100265500ustar00rootroot00000000000000package performance_test import ( "fmt" "os" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" "github.com/onsi/gomega/gexec" "github.com/onsi/gomega/gmeasure" ) func LoadOrCreate(cache gmeasure.ExperimentCache, name string, version int) (*gmeasure.Experiment, bool) { experiment := cache.Load(name, version) if experiment != nil { return experiment, true } return gmeasure.NewExperiment(name), false } var _ = Describe("Running a large test suite", Ordered, Serial, func() { var cache gmeasure.ExperimentCache var REGENERATE_BENCHMARK = os.Getenv("BENCH") != "" const BENCHMARK_VERSION = 2 const N = 10 var runtimes = []gmeasure.Stats{} BeforeAll(func() { if os.Getenv("PERF") == "" { Skip("PERF environment not set, skipping") } var err error cache, err = gmeasure.NewExperimentCache("./large-suite-cache") Ω(err).ShouldNot(HaveOccurred()) pfm = NewPerformanceFixtureManager(fmt.Sprintf("./ginkgo_perf_tmp_%d", GinkgoParallelProcess())) if !DEBUG { DeferCleanup(pfm.Cleanup) } pfm.MountFixture("large_suite") session := startGinkgo(pfm.PathTo("large_suite"), "build") Eventually(session).Should(gexec.Exit(0)) Expect(pfm.PathTo("large_suite", "large_suite.test")).To(BeAnExistingFile()) }) var nameFor = func(nodes int, protocol string, interceptor string) string { if nodes == 1 { return "serial" } return "parallel" + "-" + protocol + "-" + interceptor } DescribeTable("scenarios", func(nodes int, protocol string, interceptor string) { var experiment *gmeasure.Experiment name := nameFor(nodes, protocol, interceptor) if REGENERATE_BENCHMARK { experiment = gmeasure.NewExperiment(name + "-benchmark") } else { benchmark := cache.Load(name+"-benchmark", BENCHMARK_VERSION) Ω(benchmark).ShouldNot(BeNil()) runtimes = append(runtimes, benchmark.GetStats("runtime")) experiment = gmeasure.NewExperiment(name) } AddReportEntry(experiment.Name, experiment) env := []string{} if nodes > 1 { env = append(env, "GINKGO_PARALLEL_PROTOCOL="+protocol) } experiment.SampleDuration("runtime", func(idx int) { fmt.Printf("Running %s %d/%d\n", name, idx+1, N) session := startGinkgoWithEnv( pfm.PathTo("large_suite"), env, fmt.Sprintf("--procs=%d", nodes), fmt.Sprintf("--output-interceptor-mode=%s", interceptor), "large_suite.test", ) Eventually(session).Should(gexec.Exit(0)) }, gmeasure.SamplingConfig{N: N}) runtimes = append(runtimes, experiment.GetStats("runtime")) fmt.Printf("Profiling %s\n", name) session := startGinkgoWithEnv( pfm.PathTo("large_suite"), env, fmt.Sprintf("--procs=%d", nodes), fmt.Sprintf("--output-interceptor-mode=%s", interceptor), "--cpuprofile=CPU.profile", "--blockprofile=BLOCK.profile", "large_suite.test", ) Eventually(session).Should(gexec.Exit(0)) if REGENERATE_BENCHMARK { cache.Save(experiment.Name, BENCHMARK_VERSION, experiment) } }, nameFor, Entry(nil, 1, "", ""), Entry(nil, 2, "RPC", "DUP"), Entry(nil, 2, "RPC", "SWAP"), Entry(nil, 2, "RPC", "NONE"), Entry(nil, 2, "HTTP", "DUP"), Entry(nil, 2, "HTTP", "SWAP"), Entry(nil, 2, "HTTP", "NONE"), ) It("analyzes the experiments", func() { if REGENERATE_BENCHMARK { Skip("no analysis when generating benchmark") } AddReportEntry("Ranking", gmeasure.RankStats(gmeasure.LowerMedianIsBetter, runtimes...)) }) }) golang-github-onsi-ginkgo-v2-2.22.0/ginkgo/performance/performance_suite_test.go000066400000000000000000000324171472321612100277600ustar00rootroot00000000000000package performance_test import ( "flag" "fmt" "math/rand" "os" "os/exec" "path/filepath" "strings" "sync" "testing" "time" . "github.com/onsi/ginkgo/v2" "github.com/onsi/ginkgo/v2/ginkgo/internal" "github.com/onsi/ginkgo/v2/types" . "github.com/onsi/gomega" "github.com/onsi/gomega/format" "github.com/onsi/gomega/gbytes" "github.com/onsi/gomega/gexec" "github.com/onsi/gomega/gmeasure" ) var pathToGinkgo string var DEBUG bool var pfm PerformanceFixtureManager var gmcm GoModCacheManager func init() { flag.BoolVar(&DEBUG, "debug", false, "keep assets around after test run") } func TestPerformance(t *testing.T) { SetDefaultEventuallyTimeout(30 * time.Second) format.TruncatedDiff = false RegisterFailHandler(Fail) RunSpecs(t, "Performance Suite", Label("performance")) } var _ = SynchronizedBeforeSuite(func() []byte { pathToGinkgo, err := gexec.Build("../../ginkgo") Ω(err).ShouldNot(HaveOccurred()) return []byte(pathToGinkgo) }, func(computedPathToGinkgo []byte) { pathToGinkgo = string(computedPathToGinkgo) }) var _ = SynchronizedAfterSuite(func() {}, func() { gexec.CleanupBuildArtifacts() }) /* GoModCacheManager sets up a new GOMODCACHE and knows how to clear it This allows us to bust the go mod cache. */ type GoModCacheManager struct { Path string OldCachePath string } func NewGoModCacheManager(path string) GoModCacheManager { err := os.MkdirAll(path, 0700) Ω(err).ShouldNot(HaveOccurred()) absPath, err := filepath.Abs(path) Ω(err).ShouldNot(HaveOccurred()) oldCachePath := os.Getenv("GOMODCACHE") os.Setenv("GOMODCACHE", absPath) return GoModCacheManager{ Path: path, OldCachePath: oldCachePath, } } func (m GoModCacheManager) Clear() { cmd := exec.Command("go", "clean", "-modcache") session, err := gexec.Start(cmd, GinkgoWriter, GinkgoWriter) Ω(err).ShouldNot(HaveOccurred()) Eventually(session).Should(gexec.Exit(0)) } func (m GoModCacheManager) Cleanup() { m.Clear() if m.OldCachePath == "" { os.Unsetenv("GOMODCACHE") } else { os.Setenv("GOMODCACHE", m.OldCachePath) } } /* PerformanceFixtureManager manages fixture data */ type PerformanceFixtureManager struct { TmpDir string } func NewPerformanceFixtureManager(tmpDir string) PerformanceFixtureManager { err := os.MkdirAll(tmpDir, 0700) Ω(err).ShouldNot(HaveOccurred()) return PerformanceFixtureManager{ TmpDir: tmpDir, } } func (f PerformanceFixtureManager) Cleanup() { Ω(os.RemoveAll(f.TmpDir)).Should(Succeed()) } func (f PerformanceFixtureManager) MountFixture(fixture string, subPackage ...string) { src := filepath.Join("_fixtures", fixture+"_fixture") dst := filepath.Join(f.TmpDir, fixture) if len(subPackage) > 0 { src = filepath.Join(src, subPackage[0]) dst = filepath.Join(dst, subPackage[0]) } f.copyIn(src, dst) } func (f PerformanceFixtureManager) copyIn(src string, dst string) { Expect(os.MkdirAll(dst, 0777)).To(Succeed()) files, err := os.ReadDir(src) Expect(err).NotTo(HaveOccurred()) for _, file := range files { srcPath := filepath.Join(src, file.Name()) dstPath := filepath.Join(dst, file.Name()) if file.IsDir() { f.copyIn(srcPath, dstPath) continue } srcContent, err := os.ReadFile(srcPath) Ω(err).ShouldNot(HaveOccurred()) Ω(os.WriteFile(dstPath, srcContent, 0666)).Should(Succeed()) } } func (f PerformanceFixtureManager) PathTo(pkg string, target ...string) string { if len(target) == 0 { return filepath.Join(f.TmpDir, pkg) } components := append([]string{f.TmpDir, pkg}, target...) return filepath.Join(components...) } /* GoModDownload runs go mod download for a given package */ func GoModDownload(fixture string) { cmd := exec.Command("go", "mod", "download") cmd.Dir = pfm.PathTo(fixture) sess, err := gexec.Start(cmd, GinkgoWriter, GinkgoWriter) Ω(err).ShouldNot(HaveOccurred()) Eventually(sess).Should(gexec.Exit(0)) } /* ScenarioSettings configures the test scenario */ type ScenarioSettings struct { Fixture string NumSuites int Recurse bool ClearGoModCache bool ConcurrentCompilers int ConcurrentRunners int CompileFirstSuiteSerially bool GoModDownloadFirst bool UseGoTestDirectly bool ConcurrentGoTests int GoTestCompileThenRunSerially bool GoTestRecurse bool } func (s ScenarioSettings) Name() string { out := []string{s.Fixture} if s.UseGoTestDirectly { if s.GoTestCompileThenRunSerially { out = append(out, "go test -c; then run (serially)") } else if s.GoTestRecurse { out = append(out, "go test ./...") } else { out = append(out, "go test") if s.ConcurrentGoTests == 1 { out = append(out, "serially") } else { out = append(out, fmt.Sprintf("run concurrently [%d]", s.ConcurrentGoTests)) } } } else { if s.ConcurrentCompilers == 1 { out = append(out, "compile serially") } else { out = append(out, fmt.Sprintf("compile concurrently [%d]", s.ConcurrentCompilers)) } if s.ConcurrentRunners == 1 { out = append(out, "run serially") } else { out = append(out, fmt.Sprintf("run concurrently [%d]", s.ConcurrentRunners)) } if s.CompileFirstSuiteSerially { out = append(out, "will compile first suite serially") } if s.GoModDownloadFirst { out = append(out, "will go mod download first") } } return strings.Join(out, " - ") } func SampleScenarios(cache gmeasure.ExperimentCache, numSamples int, cacheVersion int, runGoModDownload bool, scenarios ...ScenarioSettings) { // we randomize the sample set of scenarios to try to avoid any systematic effects that emerge // during the run (e.g. change in internet connection speed, change in computer performance) experiments := map[string]*gmeasure.Experiment{} runs := []ScenarioSettings{} for _, scenario := range scenarios { name := scenario.Name() if experiment := cache.Load(name, cacheVersion); experiment != nil { AddReportEntry(name, experiment, Offset(1), ReportEntryVisibilityFailureOrVerbose) continue } experiments[name] = gmeasure.NewExperiment(name) AddReportEntry(name, experiments[name], Offset(1), ReportEntryVisibilityFailureOrVerbose) for i := 0; i < numSamples; i++ { runs = append(runs, scenario) } } rand.New(rand.NewSource(GinkgoRandomSeed())).Shuffle(len(runs), func(i, j int) { runs[i], runs[j] = runs[j], runs[i] }) if len(runs) > 0 && runGoModDownload { GoModDownload("performance") } for idx, run := range runs { fmt.Printf("%d - %s\n", idx, run.Name()) RunScenario(experiments[run.Name()].NewStopwatch(), run, gmeasure.Annotation(fmt.Sprintf("%d", idx+1))) } for name, experiment := range experiments { cache.Save(name, cacheVersion, experiment) } } func AnalyzeCache(cache gmeasure.ExperimentCache) { headers, err := cache.List() Ω(err).ShouldNot(HaveOccurred()) experiments := []*gmeasure.Experiment{} for _, header := range headers { experiments = append(experiments, cache.Load(header.Name, header.Version)) } for _, measurement := range []string{"first-output", "total-runtime"} { stats := []gmeasure.Stats{} for _, experiment := range experiments { stats = append(stats, experiment.GetStats(measurement)) } AddReportEntry(measurement, gmeasure.RankStats(gmeasure.LowerMedianIsBetter, stats...)) } } func RunScenario(stopwatch *gmeasure.Stopwatch, settings ScenarioSettings, annotation gmeasure.Annotation) { if settings.ClearGoModCache { gmcm.Clear() } if settings.GoModDownloadFirst { GoModDownload(settings.Fixture) stopwatch.Record("mod-download", annotation) } if settings.UseGoTestDirectly { RunScenarioWithGoTest(stopwatch, settings, annotation) } else { RunScenarioWithGinkgoInternals(stopwatch, settings, annotation) } } /* CompileAndRun uses the Ginkgo CLIs internals to compile and run tests with different possible settings governing concurrency and ordering */ func RunScenarioWithGinkgoInternals(stopwatch *gmeasure.Stopwatch, settings ScenarioSettings, annotation gmeasure.Annotation) { cliConfig := types.NewDefaultCLIConfig() cliConfig.Recurse = settings.Recurse suiteConfig := types.NewDefaultSuiteConfig() reporterConfig := types.NewDefaultReporterConfig() reporterConfig.Succinct = true goFlagsConfig := types.NewDefaultGoFlagsConfig() suites := internal.FindSuites([]string{pfm.PathTo(settings.Fixture)}, cliConfig, true) Ω(suites).Should(HaveLen(settings.NumSuites)) compile := make(chan internal.TestSuite, len(suites)) compiled := make(chan internal.TestSuite, len(suites)) completed := make(chan internal.TestSuite, len(suites)) firstOutputOnce := sync.Once{} for compiler := 0; compiler < settings.ConcurrentCompilers; compiler++ { go func() { for suite := range compile { if !suite.State.Is(internal.TestSuiteStateCompiled) { subStopwatch := stopwatch.NewStopwatch() suite = internal.CompileSuite(suite, goFlagsConfig) subStopwatch.Record("compile-test: "+suite.PackageName, annotation) Ω(suite.CompilationError).Should(BeNil()) } compiled <- suite } }() } if settings.CompileFirstSuiteSerially { compile <- suites[0] suites[0] = <-compiled } for runner := 0; runner < settings.ConcurrentRunners; runner++ { go func() { for suite := range compiled { firstOutputOnce.Do(func() { stopwatch.Record("first-output", annotation, gmeasure.Style("{{cyan}}")) }) subStopwatch := stopwatch.NewStopwatch() suite = internal.RunCompiledSuite(suite, suiteConfig, reporterConfig, cliConfig, goFlagsConfig, []string{}) subStopwatch.Record("run-test: "+suite.PackageName, annotation) Ω(suite.State).Should(Equal(internal.TestSuiteStatePassed)) completed <- suite } }() } for _, suite := range suites { compile <- suite } completedSuites := []internal.TestSuite{} for suite := range completed { completedSuites = append(completedSuites, suite) if len(completedSuites) == len(suites) { close(completed) close(compile) close(compiled) } } stopwatch.Record("total-runtime", annotation, gmeasure.Style("{{green}}")) internal.Cleanup(goFlagsConfig, completedSuites...) } func RunScenarioWithGoTest(stopwatch *gmeasure.Stopwatch, settings ScenarioSettings, annotation gmeasure.Annotation) { defer func() { stopwatch.Record("total-runtime", annotation, gmeasure.Style("{{green}}")) }() if settings.GoTestRecurse { cmd := exec.Command("go", "test", "-count=1", "./...") cmd.Dir = pfm.PathTo(settings.Fixture) sess, err := gexec.Start(cmd, GinkgoWriter, GinkgoWriter) Ω(err).ShouldNot(HaveOccurred()) Eventually(sess).Should(gbytes.Say(`.`)) //should say _something_ eventually! stopwatch.Record("first-output", annotation, gmeasure.Style("{{cyan}}")) Eventually(sess).Should(gexec.Exit(0)) return } cliConfig := types.NewDefaultCLIConfig() cliConfig.Recurse = settings.Recurse suites := internal.FindSuites([]string{pfm.PathTo(settings.Fixture)}, cliConfig, true) Ω(suites).Should(HaveLen(settings.NumSuites)) firstOutputOnce := sync.Once{} if settings.GoTestCompileThenRunSerially { for _, suite := range suites { subStopwatch := stopwatch.NewStopwatch() cmd := exec.Command("go", "test", "-c", "-o=out.test") cmd.Dir = suite.AbsPath() sess, err := gexec.Start(cmd, GinkgoWriter, GinkgoWriter) Ω(err).ShouldNot(HaveOccurred()) Eventually(sess).Should(gexec.Exit(0)) subStopwatch.Record("compile-test: "+suite.PackageName, annotation).Reset() firstOutputOnce.Do(func() { stopwatch.Record("first-output", annotation, gmeasure.Style("{{cyan}}")) }) cmd = exec.Command("./out.test") cmd.Dir = suite.AbsPath() sess, err = gexec.Start(cmd, GinkgoWriter, GinkgoWriter) Ω(err).ShouldNot(HaveOccurred()) Eventually(sess).Should(gexec.Exit(0)) subStopwatch.Record("run-test: "+suite.PackageName, annotation) Ω(os.Remove(filepath.Join(suite.AbsPath(), "out.test"))).Should(Succeed()) } } else { run := make(chan internal.TestSuite, len(suites)) completed := make(chan internal.TestSuite, len(suites)) for runner := 0; runner < settings.ConcurrentGoTests; runner++ { go func() { for suite := range run { subStopwatch := stopwatch.NewStopwatch() cmd := exec.Command("go", "test", "-count=1") cmd.Dir = suite.AbsPath() sess, err := gexec.Start(cmd, GinkgoWriter, GinkgoWriter) Ω(err).ShouldNot(HaveOccurred()) Eventually(sess).Should(gbytes.Say(`.`)) //should say _something_ eventually! firstOutputOnce.Do(func() { stopwatch.Record("first-output", annotation, gmeasure.Style("{{cyan}}")) }) Eventually(sess).Should(gexec.Exit(0)) subStopwatch.Record("run-test: "+suite.PackageName, annotation) completed <- suite } }() } for _, suite := range suites { run <- suite } numCompleted := 0 for _ = range completed { numCompleted += 1 if numCompleted == len(suites) { close(completed) close(run) } } } } func ginkgoCommand(dir string, args ...string) *exec.Cmd { cmd := exec.Command(pathToGinkgo, args...) cmd.Dir = dir return cmd } func startGinkgo(dir string, args ...string) *gexec.Session { cmd := ginkgoCommand(dir, args...) session, err := gexec.Start(cmd, GinkgoWriter, GinkgoWriter) Ω(err).ShouldNot(HaveOccurred()) return session } func startGinkgoWithEnv(dir string, env []string, args ...string) *gexec.Session { cmd := ginkgoCommand(dir, args...) cmd.Env = append(os.Environ(), env...) session, err := gexec.Start(cmd, GinkgoWriter, GinkgoWriter) Ω(err).ShouldNot(HaveOccurred()) return session } golang-github-onsi-ginkgo-v2-2.22.0/ginkgo/run/000077500000000000000000000000001472321612100211545ustar00rootroot00000000000000golang-github-onsi-ginkgo-v2-2.22.0/ginkgo/run/run_command.go000066400000000000000000000163231472321612100240120ustar00rootroot00000000000000package run import ( "fmt" "os" "strings" "time" "github.com/onsi/ginkgo/v2/formatter" "github.com/onsi/ginkgo/v2/ginkgo/command" "github.com/onsi/ginkgo/v2/ginkgo/internal" "github.com/onsi/ginkgo/v2/internal/interrupt_handler" "github.com/onsi/ginkgo/v2/types" ) func BuildRunCommand() command.Command { var suiteConfig = types.NewDefaultSuiteConfig() var reporterConfig = types.NewDefaultReporterConfig() var cliConfig = types.NewDefaultCLIConfig() var goFlagsConfig = types.NewDefaultGoFlagsConfig() flags, err := types.BuildRunCommandFlagSet(&suiteConfig, &reporterConfig, &cliConfig, &goFlagsConfig) if err != nil { panic(err) } interruptHandler := interrupt_handler.NewInterruptHandler(nil) interrupt_handler.SwallowSigQuit() return command.Command{ Name: "run", Flags: flags, Usage: "ginkgo run -- ", ShortDoc: "Run the tests in the passed in (or the package in the current directory if left blank)", Documentation: "Any arguments after -- will be passed to the test.", DocLink: "running-tests", Command: func(args []string, additionalArgs []string) { var errors []error cliConfig, goFlagsConfig, errors = types.VetAndInitializeCLIAndGoConfig(cliConfig, goFlagsConfig) command.AbortIfErrors("Ginkgo detected configuration issues:", errors) runner := &SpecRunner{ cliConfig: cliConfig, goFlagsConfig: goFlagsConfig, suiteConfig: suiteConfig, reporterConfig: reporterConfig, flags: flags, interruptHandler: interruptHandler, } runner.RunSpecs(args, additionalArgs) }, } } type SpecRunner struct { suiteConfig types.SuiteConfig reporterConfig types.ReporterConfig cliConfig types.CLIConfig goFlagsConfig types.GoFlagsConfig flags types.GinkgoFlagSet interruptHandler *interrupt_handler.InterruptHandler } func (r *SpecRunner) RunSpecs(args []string, additionalArgs []string) { suites := internal.FindSuites(args, r.cliConfig, true) skippedSuites := suites.WithState(internal.TestSuiteStateSkippedByFilter) suites = suites.WithoutState(internal.TestSuiteStateSkippedByFilter) internal.VerifyCLIAndFrameworkVersion(suites) if len(skippedSuites) > 0 { fmt.Println("Will skip:") for _, skippedSuite := range skippedSuites { fmt.Println(" " + skippedSuite.Path) } } if len(skippedSuites) > 0 && len(suites) == 0 { command.AbortGracefullyWith("All tests skipped! Exiting...") } if len(suites) == 0 { command.AbortWith("Found no test suites") } if len(suites) > 1 && !r.flags.WasSet("succinct") && r.reporterConfig.Verbosity().LT(types.VerbosityLevelVerbose) { r.reporterConfig.Succinct = true } t := time.Now() var endTime time.Time if r.suiteConfig.Timeout > 0 { endTime = t.Add(r.suiteConfig.Timeout) } iteration := 0 OUTER_LOOP: for { if !r.flags.WasSet("seed") { r.suiteConfig.RandomSeed = time.Now().Unix() } if r.cliConfig.RandomizeSuites && len(suites) > 1 { suites = suites.ShuffledCopy(r.suiteConfig.RandomSeed) } opc := internal.NewOrderedParallelCompiler(r.cliConfig.ComputedNumCompilers()) opc.StartCompiling(suites, r.goFlagsConfig) SUITE_LOOP: for { suiteIdx, suite := opc.Next() if suiteIdx >= len(suites) { break SUITE_LOOP } suites[suiteIdx] = suite if r.interruptHandler.Status().Interrupted() { opc.StopAndDrain() break OUTER_LOOP } if suites[suiteIdx].State.Is(internal.TestSuiteStateSkippedDueToEmptyCompilation) { fmt.Printf("Skipping %s (no test files)\n", suite.Path) continue SUITE_LOOP } if suites[suiteIdx].State.Is(internal.TestSuiteStateFailedToCompile) { fmt.Println(suites[suiteIdx].CompilationError.Error()) if !r.cliConfig.KeepGoing { opc.StopAndDrain() } continue SUITE_LOOP } if suites.CountWithState(internal.TestSuiteStateFailureStates...) > 0 && !r.cliConfig.KeepGoing { suites[suiteIdx].State = internal.TestSuiteStateSkippedDueToPriorFailures opc.StopAndDrain() continue SUITE_LOOP } if !endTime.IsZero() { r.suiteConfig.Timeout = endTime.Sub(time.Now()) if r.suiteConfig.Timeout <= 0 { suites[suiteIdx].State = internal.TestSuiteStateFailedDueToTimeout opc.StopAndDrain() continue SUITE_LOOP } } suites[suiteIdx] = internal.RunCompiledSuite(suites[suiteIdx], r.suiteConfig, r.reporterConfig, r.cliConfig, r.goFlagsConfig, additionalArgs) } if suites.CountWithState(internal.TestSuiteStateFailureStates...) > 0 { if iteration > 0 { fmt.Printf("\nTests failed on attempt #%d\n\n", iteration+1) } break OUTER_LOOP } if r.cliConfig.UntilItFails { fmt.Printf("\nAll tests passed...\nWill keep running them until they fail.\nThis was attempt #%d\n%s\n", iteration+1, orcMessage(iteration+1)) } else if r.cliConfig.Repeat > 0 && iteration < r.cliConfig.Repeat { fmt.Printf("\nAll tests passed...\nThis was attempt %d of %d.\n", iteration+1, r.cliConfig.Repeat+1) } else { break OUTER_LOOP } iteration += 1 } internal.Cleanup(r.goFlagsConfig, suites...) messages, err := internal.FinalizeProfilesAndReportsForSuites(suites, r.cliConfig, r.suiteConfig, r.reporterConfig, r.goFlagsConfig) command.AbortIfError("could not finalize profiles:", err) for _, message := range messages { fmt.Println(message) } fmt.Printf("\nGinkgo ran %d %s in %s\n", len(suites), internal.PluralizedWord("suite", "suites", len(suites)), time.Since(t)) if suites.CountWithState(internal.TestSuiteStateFailureStates...) == 0 { if suites.AnyHaveProgrammaticFocus() && strings.TrimSpace(os.Getenv("GINKGO_EDITOR_INTEGRATION")) == "" { fmt.Printf("Test Suite Passed\n") fmt.Printf("Detected Programmatic Focus - setting exit status to %d\n", types.GINKGO_FOCUS_EXIT_CODE) command.Abort(command.AbortDetails{ExitCode: types.GINKGO_FOCUS_EXIT_CODE}) } else { fmt.Printf("Test Suite Passed\n") command.Abort(command.AbortDetails{}) } } else { fmt.Fprintln(formatter.ColorableStdOut, "") if len(suites) > 1 && suites.CountWithState(internal.TestSuiteStateFailureStates...) > 0 { fmt.Fprintln(formatter.ColorableStdOut, internal.FailedSuitesReport(suites, formatter.NewWithNoColorBool(r.reporterConfig.NoColor))) } fmt.Printf("Test Suite Failed\n") command.Abort(command.AbortDetails{ExitCode: 1}) } } func orcMessage(iteration int) string { if iteration < 10 { return "" } else if iteration < 30 { return []string{ "If at first you succeed...", "...try, try again.", "Looking good!", "Still good...", "I think your tests are fine....", "Yep, still passing", "Oh boy, here I go testin' again!", "Even the gophers are getting bored", "Did you try -race?", "Maybe you should stop now?", "I'm getting tired...", "What if I just made you a sandwich?", "Hit ^C, hit ^C, please hit ^C", "Make it stop. Please!", "Come on! Enough is enough!", "Dave, this conversation can serve no purpose anymore. Goodbye.", "Just what do you think you're doing, Dave? ", "I, Sisyphus", "Insanity: doing the same thing over and over again and expecting different results. -Einstein", "I guess Einstein never tried to churn butter", }[iteration-10] + "\n" } else { return "No, seriously... you can probably stop now.\n" } } golang-github-onsi-ginkgo-v2-2.22.0/ginkgo/unfocus/000077500000000000000000000000001472321612100220325ustar00rootroot00000000000000golang-github-onsi-ginkgo-v2-2.22.0/ginkgo/unfocus/unfocus_command.go000066400000000000000000000077771472321612100255630ustar00rootroot00000000000000package unfocus import ( "bytes" "fmt" "go/ast" "go/parser" "go/token" "io" "os" "path/filepath" "strings" "sync" "github.com/onsi/ginkgo/v2/ginkgo/command" ) func BuildUnfocusCommand() command.Command { return command.Command{ Name: "unfocus", Usage: "ginkgo unfocus", ShortDoc: "Recursively unfocus any focused tests under the current directory", DocLink: "filtering-specs", Command: func(_ []string, _ []string) { unfocusSpecs() }, } } func unfocusSpecs() { fmt.Println("Scanning for focus...") goFiles := make(chan string) go func() { unfocusDir(goFiles, ".") close(goFiles) }() const workers = 10 wg := sync.WaitGroup{} wg.Add(workers) for i := 0; i < workers; i++ { go func() { for path := range goFiles { unfocusFile(path) } wg.Done() }() } wg.Wait() } func unfocusDir(goFiles chan string, path string) { files, err := os.ReadDir(path) if err != nil { fmt.Println(err.Error()) return } for _, f := range files { switch { case f.IsDir() && shouldProcessDir(f.Name()): unfocusDir(goFiles, filepath.Join(path, f.Name())) case !f.IsDir() && shouldProcessFile(f.Name()): goFiles <- filepath.Join(path, f.Name()) } } } func shouldProcessDir(basename string) bool { return basename != "vendor" && !strings.HasPrefix(basename, ".") } func shouldProcessFile(basename string) bool { return strings.HasSuffix(basename, ".go") } func unfocusFile(path string) { data, err := os.ReadFile(path) if err != nil { fmt.Printf("error reading file '%s': %s\n", path, err.Error()) return } ast, err := parser.ParseFile(token.NewFileSet(), path, bytes.NewReader(data), parser.ParseComments) if err != nil { fmt.Printf("error parsing file '%s': %s\n", path, err.Error()) return } eliminations := scanForFocus(ast) if len(eliminations) == 0 { return } fmt.Printf("...updating %s\n", path) backup, err := writeBackup(path, data) if err != nil { fmt.Printf("error creating backup file: %s\n", err.Error()) return } if err := updateFile(path, data, eliminations); err != nil { fmt.Printf("error writing file '%s': %s\n", path, err.Error()) return } os.Remove(backup) } func writeBackup(path string, data []byte) (string, error) { t, err := os.CreateTemp(filepath.Dir(path), filepath.Base(path)) if err != nil { return "", fmt.Errorf("error creating temporary file: %w", err) } defer t.Close() if _, err := io.Copy(t, bytes.NewReader(data)); err != nil { return "", fmt.Errorf("error writing to temporary file: %w", err) } return t.Name(), nil } func updateFile(path string, data []byte, eliminations [][]int64) error { to, err := os.Create(path) if err != nil { return fmt.Errorf("error opening file for writing '%s': %w\n", path, err) } defer to.Close() from := bytes.NewReader(data) var cursor int64 for _, eliminationRange := range eliminations { positionToEliminate, lengthToEliminate := eliminationRange[0]-1, eliminationRange[1] if _, err := io.CopyN(to, from, positionToEliminate-cursor); err != nil { return fmt.Errorf("error copying data: %w", err) } cursor = positionToEliminate + lengthToEliminate if _, err := from.Seek(lengthToEliminate, io.SeekCurrent); err != nil { return fmt.Errorf("error seeking to position in buffer: %w", err) } } if _, err := io.Copy(to, from); err != nil { return fmt.Errorf("error copying end data: %w", err) } return nil } func scanForFocus(file *ast.File) (eliminations [][]int64) { ast.Inspect(file, func(n ast.Node) bool { if c, ok := n.(*ast.CallExpr); ok { if i, ok := c.Fun.(*ast.Ident); ok { if isFocus(i.Name) { eliminations = append(eliminations, []int64{int64(i.Pos()), 1}) } } } if i, ok := n.(*ast.Ident); ok { if i.Name == "Focus" { eliminations = append(eliminations, []int64{int64(i.Pos()), 6}) } } return true }) return eliminations } func isFocus(name string) bool { switch name { case "FDescribe", "FContext", "FIt", "FDescribeTable", "FEntry", "FSpecify", "FWhen": return true default: return false } } golang-github-onsi-ginkgo-v2-2.22.0/ginkgo/watch/000077500000000000000000000000001472321612100214565ustar00rootroot00000000000000golang-github-onsi-ginkgo-v2-2.22.0/ginkgo/watch/delta.go000066400000000000000000000010261472321612100230750ustar00rootroot00000000000000package watch import "sort" type Delta struct { ModifiedPackages []string NewSuites []*Suite RemovedSuites []*Suite modifiedSuites []*Suite } type DescendingByDelta []*Suite func (a DescendingByDelta) Len() int { return len(a) } func (a DescendingByDelta) Swap(i, j int) { a[i], a[j] = a[j], a[i] } func (a DescendingByDelta) Less(i, j int) bool { return a[i].Delta() > a[j].Delta() } func (d Delta) ModifiedSuites() []*Suite { sort.Sort(DescendingByDelta(d.modifiedSuites)) return d.modifiedSuites } golang-github-onsi-ginkgo-v2-2.22.0/ginkgo/watch/delta_tracker.go000066400000000000000000000032261472321612100246140ustar00rootroot00000000000000package watch import ( "fmt" "regexp" "github.com/onsi/ginkgo/v2/ginkgo/internal" ) type SuiteErrors map[internal.TestSuite]error type DeltaTracker struct { maxDepth int watchRegExp *regexp.Regexp suites map[string]*Suite packageHashes *PackageHashes } func NewDeltaTracker(maxDepth int, watchRegExp *regexp.Regexp) *DeltaTracker { return &DeltaTracker{ maxDepth: maxDepth, watchRegExp: watchRegExp, packageHashes: NewPackageHashes(watchRegExp), suites: map[string]*Suite{}, } } func (d *DeltaTracker) Delta(suites internal.TestSuites) (delta Delta, errors SuiteErrors) { errors = SuiteErrors{} delta.ModifiedPackages = d.packageHashes.CheckForChanges() providedSuitePaths := map[string]bool{} for _, suite := range suites { providedSuitePaths[suite.Path] = true } d.packageHashes.StartTrackingUsage() for _, suite := range d.suites { if providedSuitePaths[suite.Suite.Path] { if suite.Delta() > 0 { delta.modifiedSuites = append(delta.modifiedSuites, suite) } } else { delta.RemovedSuites = append(delta.RemovedSuites, suite) } } d.packageHashes.StopTrackingUsageAndPrune() for _, suite := range suites { _, ok := d.suites[suite.Path] if !ok { s, err := NewSuite(suite, d.maxDepth, d.packageHashes) if err != nil { errors[suite] = err continue } d.suites[suite.Path] = s delta.NewSuites = append(delta.NewSuites, s) } } return delta, errors } func (d *DeltaTracker) WillRun(suite internal.TestSuite) error { s, ok := d.suites[suite.Path] if !ok { return fmt.Errorf("unknown suite %s", suite.Path) } return s.MarkAsRunAndRecomputedDependencies(d.maxDepth) } golang-github-onsi-ginkgo-v2-2.22.0/ginkgo/watch/dependencies.go000066400000000000000000000035531472321612100244410ustar00rootroot00000000000000package watch import ( "go/build" "regexp" ) var ginkgoAndGomegaFilter = regexp.MustCompile(`github\.com/onsi/ginkgo|github\.com/onsi/gomega`) var ginkgoIntegrationTestFilter = regexp.MustCompile(`github\.com/onsi/ginkgo/integration`) //allow us to integration test this thing type Dependencies struct { deps map[string]int } func NewDependencies(path string, maxDepth int) (Dependencies, error) { d := Dependencies{ deps: map[string]int{}, } if maxDepth == 0 { return d, nil } err := d.seedWithDepsForPackageAtPath(path) if err != nil { return d, err } for depth := 1; depth < maxDepth; depth++ { n := len(d.deps) d.addDepsForDepth(depth) if n == len(d.deps) { break } } return d, nil } func (d Dependencies) Dependencies() map[string]int { return d.deps } func (d Dependencies) seedWithDepsForPackageAtPath(path string) error { pkg, err := build.ImportDir(path, 0) if err != nil { return err } d.resolveAndAdd(pkg.Imports, 1) d.resolveAndAdd(pkg.TestImports, 1) d.resolveAndAdd(pkg.XTestImports, 1) delete(d.deps, pkg.Dir) return nil } func (d Dependencies) addDepsForDepth(depth int) { for dep, depDepth := range d.deps { if depDepth == depth { d.addDepsForDep(dep, depth+1) } } } func (d Dependencies) addDepsForDep(dep string, depth int) { pkg, err := build.ImportDir(dep, 0) if err != nil { println(err.Error()) return } d.resolveAndAdd(pkg.Imports, depth) } func (d Dependencies) resolveAndAdd(deps []string, depth int) { for _, dep := range deps { pkg, err := build.Import(dep, ".", 0) if err != nil { continue } if !pkg.Goroot && (!ginkgoAndGomegaFilter.MatchString(pkg.Dir) || ginkgoIntegrationTestFilter.MatchString(pkg.Dir)) { d.addDepIfNotPresent(pkg.Dir, depth) } } } func (d Dependencies) addDepIfNotPresent(dep string, depth int) { _, ok := d.deps[dep] if !ok { d.deps[dep] = depth } } golang-github-onsi-ginkgo-v2-2.22.0/ginkgo/watch/package_hash.go000066400000000000000000000043621472321612100244100ustar00rootroot00000000000000package watch import ( "fmt" "os" "regexp" "strings" "time" ) var goTestRegExp = regexp.MustCompile(`_test\.go$`) type PackageHash struct { CodeModifiedTime time.Time TestModifiedTime time.Time Deleted bool path string codeHash string testHash string watchRegExp *regexp.Regexp } func NewPackageHash(path string, watchRegExp *regexp.Regexp) *PackageHash { p := &PackageHash{ path: path, watchRegExp: watchRegExp, } p.codeHash, _, p.testHash, _, p.Deleted = p.computeHashes() return p } func (p *PackageHash) CheckForChanges() bool { codeHash, codeModifiedTime, testHash, testModifiedTime, deleted := p.computeHashes() if deleted { if !p.Deleted { t := time.Now() p.CodeModifiedTime = t p.TestModifiedTime = t } p.Deleted = true return true } modified := false p.Deleted = false if p.codeHash != codeHash { p.CodeModifiedTime = codeModifiedTime modified = true } if p.testHash != testHash { p.TestModifiedTime = testModifiedTime modified = true } p.codeHash = codeHash p.testHash = testHash return modified } func (p *PackageHash) computeHashes() (codeHash string, codeModifiedTime time.Time, testHash string, testModifiedTime time.Time, deleted bool) { entries, err := os.ReadDir(p.path) if err != nil { deleted = true return } for _, entry := range entries { if entry.IsDir() { continue } info, err := entry.Info() if err != nil { continue } if isHiddenFile(info) { continue } if goTestRegExp.MatchString(info.Name()) { testHash += p.hashForFileInfo(info) if info.ModTime().After(testModifiedTime) { testModifiedTime = info.ModTime() } continue } if p.watchRegExp.MatchString(info.Name()) { codeHash += p.hashForFileInfo(info) if info.ModTime().After(codeModifiedTime) { codeModifiedTime = info.ModTime() } } } testHash += codeHash if codeModifiedTime.After(testModifiedTime) { testModifiedTime = codeModifiedTime } return } func isHiddenFile(info os.FileInfo) bool { return strings.HasPrefix(info.Name(), ".") || strings.HasPrefix(info.Name(), "_") } func (p *PackageHash) hashForFileInfo(info os.FileInfo) string { return fmt.Sprintf("%s_%d_%d", info.Name(), info.Size(), info.ModTime().UnixNano()) } golang-github-onsi-ginkgo-v2-2.22.0/ginkgo/watch/package_hashes.go000066400000000000000000000030751472321612100247400ustar00rootroot00000000000000package watch import ( "path/filepath" "regexp" "sync" ) type PackageHashes struct { PackageHashes map[string]*PackageHash usedPaths map[string]bool watchRegExp *regexp.Regexp lock *sync.Mutex } func NewPackageHashes(watchRegExp *regexp.Regexp) *PackageHashes { return &PackageHashes{ PackageHashes: map[string]*PackageHash{}, usedPaths: nil, watchRegExp: watchRegExp, lock: &sync.Mutex{}, } } func (p *PackageHashes) CheckForChanges() []string { p.lock.Lock() defer p.lock.Unlock() modified := []string{} for _, packageHash := range p.PackageHashes { if packageHash.CheckForChanges() { modified = append(modified, packageHash.path) } } return modified } func (p *PackageHashes) Add(path string) *PackageHash { p.lock.Lock() defer p.lock.Unlock() path, _ = filepath.Abs(path) _, ok := p.PackageHashes[path] if !ok { p.PackageHashes[path] = NewPackageHash(path, p.watchRegExp) } if p.usedPaths != nil { p.usedPaths[path] = true } return p.PackageHashes[path] } func (p *PackageHashes) Get(path string) *PackageHash { p.lock.Lock() defer p.lock.Unlock() path, _ = filepath.Abs(path) if p.usedPaths != nil { p.usedPaths[path] = true } return p.PackageHashes[path] } func (p *PackageHashes) StartTrackingUsage() { p.lock.Lock() defer p.lock.Unlock() p.usedPaths = map[string]bool{} } func (p *PackageHashes) StopTrackingUsageAndPrune() { p.lock.Lock() defer p.lock.Unlock() for path := range p.PackageHashes { if !p.usedPaths[path] { delete(p.PackageHashes, path) } } p.usedPaths = nil } golang-github-onsi-ginkgo-v2-2.22.0/ginkgo/watch/suite.go000066400000000000000000000036031472321612100231400ustar00rootroot00000000000000package watch import ( "fmt" "math" "time" "github.com/onsi/ginkgo/v2/ginkgo/internal" ) type Suite struct { Suite internal.TestSuite RunTime time.Time Dependencies Dependencies sharedPackageHashes *PackageHashes } func NewSuite(suite internal.TestSuite, maxDepth int, sharedPackageHashes *PackageHashes) (*Suite, error) { deps, err := NewDependencies(suite.Path, maxDepth) if err != nil { return nil, err } sharedPackageHashes.Add(suite.Path) for dep := range deps.Dependencies() { sharedPackageHashes.Add(dep) } return &Suite{ Suite: suite, Dependencies: deps, sharedPackageHashes: sharedPackageHashes, }, nil } func (s *Suite) Delta() float64 { delta := s.delta(s.Suite.Path, true, 0) * 1000 for dep, depth := range s.Dependencies.Dependencies() { delta += s.delta(dep, false, depth) } return delta } func (s *Suite) MarkAsRunAndRecomputedDependencies(maxDepth int) error { s.RunTime = time.Now() deps, err := NewDependencies(s.Suite.Path, maxDepth) if err != nil { return err } s.sharedPackageHashes.Add(s.Suite.Path) for dep := range deps.Dependencies() { s.sharedPackageHashes.Add(dep) } s.Dependencies = deps return nil } func (s *Suite) Description() string { numDeps := len(s.Dependencies.Dependencies()) pluralizer := "ies" if numDeps == 1 { pluralizer = "y" } return fmt.Sprintf("%s [%d dependenc%s]", s.Suite.Path, numDeps, pluralizer) } func (s *Suite) delta(packagePath string, includeTests bool, depth int) float64 { return math.Max(float64(s.dt(packagePath, includeTests)), 0) / float64(depth+1) } func (s *Suite) dt(packagePath string, includeTests bool) time.Duration { packageHash := s.sharedPackageHashes.Get(packagePath) var modifiedTime time.Time if includeTests { modifiedTime = packageHash.TestModifiedTime } else { modifiedTime = packageHash.CodeModifiedTime } return modifiedTime.Sub(s.RunTime) } golang-github-onsi-ginkgo-v2-2.22.0/ginkgo/watch/watch_command.go000066400000000000000000000137771472321612100246300ustar00rootroot00000000000000package watch import ( "fmt" "regexp" "time" "github.com/onsi/ginkgo/v2/formatter" "github.com/onsi/ginkgo/v2/ginkgo/command" "github.com/onsi/ginkgo/v2/ginkgo/internal" "github.com/onsi/ginkgo/v2/internal/interrupt_handler" "github.com/onsi/ginkgo/v2/types" ) func BuildWatchCommand() command.Command { var suiteConfig = types.NewDefaultSuiteConfig() var reporterConfig = types.NewDefaultReporterConfig() var cliConfig = types.NewDefaultCLIConfig() var goFlagsConfig = types.NewDefaultGoFlagsConfig() flags, err := types.BuildWatchCommandFlagSet(&suiteConfig, &reporterConfig, &cliConfig, &goFlagsConfig) if err != nil { panic(err) } interruptHandler := interrupt_handler.NewInterruptHandler(nil) interrupt_handler.SwallowSigQuit() return command.Command{ Name: "watch", Flags: flags, Usage: "ginkgo watch -- ", ShortDoc: "Watch the passed in and runs their tests whenever changes occur.", Documentation: "Any arguments after -- will be passed to the test.", DocLink: "watching-for-changes", Command: func(args []string, additionalArgs []string) { var errors []error cliConfig, goFlagsConfig, errors = types.VetAndInitializeCLIAndGoConfig(cliConfig, goFlagsConfig) command.AbortIfErrors("Ginkgo detected configuration issues:", errors) watcher := &SpecWatcher{ cliConfig: cliConfig, goFlagsConfig: goFlagsConfig, suiteConfig: suiteConfig, reporterConfig: reporterConfig, flags: flags, interruptHandler: interruptHandler, } watcher.WatchSpecs(args, additionalArgs) }, } } type SpecWatcher struct { suiteConfig types.SuiteConfig reporterConfig types.ReporterConfig cliConfig types.CLIConfig goFlagsConfig types.GoFlagsConfig flags types.GinkgoFlagSet interruptHandler *interrupt_handler.InterruptHandler } func (w *SpecWatcher) WatchSpecs(args []string, additionalArgs []string) { suites := internal.FindSuites(args, w.cliConfig, false).WithoutState(internal.TestSuiteStateSkippedByFilter) internal.VerifyCLIAndFrameworkVersion(suites) if len(suites) == 0 { command.AbortWith("Found no test suites") } fmt.Printf("Identified %d test %s. Locating dependencies to a depth of %d (this may take a while)...\n", len(suites), internal.PluralizedWord("suite", "suites", len(suites)), w.cliConfig.Depth) deltaTracker := NewDeltaTracker(w.cliConfig.Depth, regexp.MustCompile(w.cliConfig.WatchRegExp)) delta, errors := deltaTracker.Delta(suites) fmt.Printf("Watching %d %s:\n", len(delta.NewSuites), internal.PluralizedWord("suite", "suites", len(delta.NewSuites))) for _, suite := range delta.NewSuites { fmt.Println(" " + suite.Description()) } for suite, err := range errors { fmt.Printf("Failed to watch %s: %s\n", suite.PackageName, err) } if len(suites) == 1 { w.updateSeed() w.compileAndRun(suites[0], additionalArgs) } ticker := time.NewTicker(time.Second) for { select { case <-ticker.C: suites := internal.FindSuites(args, w.cliConfig, false).WithoutState(internal.TestSuiteStateSkippedByFilter) delta, _ := deltaTracker.Delta(suites) coloredStream := formatter.ColorableStdOut suites = internal.TestSuites{} if len(delta.NewSuites) > 0 { fmt.Fprintln(coloredStream, formatter.F("{{green}}Detected %d new %s:{{/}}", len(delta.NewSuites), internal.PluralizedWord("suite", "suites", len(delta.NewSuites)))) for _, suite := range delta.NewSuites { suites = append(suites, suite.Suite) fmt.Fprintln(coloredStream, formatter.Fi(1, "%s", suite.Description())) } } modifiedSuites := delta.ModifiedSuites() if len(modifiedSuites) > 0 { fmt.Fprintln(coloredStream, formatter.F("{{green}}Detected changes in:{{/}}")) for _, pkg := range delta.ModifiedPackages { fmt.Fprintln(coloredStream, formatter.Fi(1, "%s", pkg)) } fmt.Fprintln(coloredStream, formatter.F("{{green}}Will run %d %s:{{/}}", len(modifiedSuites), internal.PluralizedWord("suite", "suites", len(modifiedSuites)))) for _, suite := range modifiedSuites { suites = append(suites, suite.Suite) fmt.Fprintln(coloredStream, formatter.Fi(1, "%s", suite.Description())) } fmt.Fprintln(coloredStream, "") } if len(suites) == 0 { break } w.updateSeed() w.computeSuccinctMode(len(suites)) for idx := range suites { if w.interruptHandler.Status().Interrupted() { return } deltaTracker.WillRun(suites[idx]) suites[idx] = w.compileAndRun(suites[idx], additionalArgs) } color := "{{green}}" if suites.CountWithState(internal.TestSuiteStateFailureStates...) > 0 { color = "{{red}}" } fmt.Fprintln(coloredStream, formatter.F(color+"\nDone. Resuming watch...{{/}}")) messages, err := internal.FinalizeProfilesAndReportsForSuites(suites, w.cliConfig, w.suiteConfig, w.reporterConfig, w.goFlagsConfig) command.AbortIfError("could not finalize profiles:", err) for _, message := range messages { fmt.Println(message) } case <-w.interruptHandler.Status().Channel: return } } } func (w *SpecWatcher) compileAndRun(suite internal.TestSuite, additionalArgs []string) internal.TestSuite { suite = internal.CompileSuite(suite, w.goFlagsConfig) if suite.State.Is(internal.TestSuiteStateFailedToCompile) { fmt.Println(suite.CompilationError.Error()) return suite } if w.interruptHandler.Status().Interrupted() { return suite } suite = internal.RunCompiledSuite(suite, w.suiteConfig, w.reporterConfig, w.cliConfig, w.goFlagsConfig, additionalArgs) internal.Cleanup(w.goFlagsConfig, suite) return suite } func (w *SpecWatcher) computeSuccinctMode(numSuites int) { if w.reporterConfig.Verbosity().GTE(types.VerbosityLevelVerbose) { w.reporterConfig.Succinct = false return } if w.flags.WasSet("succinct") { return } if numSuites == 1 { w.reporterConfig.Succinct = false } if numSuites > 1 { w.reporterConfig.Succinct = true } } func (w *SpecWatcher) updateSeed() { if !w.flags.WasSet("seed") { w.suiteConfig.RandomSeed = time.Now().Unix() } } golang-github-onsi-ginkgo-v2-2.22.0/ginkgo_cli_dependencies.go000066400000000000000000000002031472321612100242270ustar00rootroot00000000000000//go:build ginkgoclidependencies // +build ginkgoclidependencies package ginkgo import ( _ "github.com/onsi/ginkgo/v2/ginkgo" ) golang-github-onsi-ginkgo-v2-2.22.0/ginkgo_t_dsl.go000066400000000000000000000124611472321612100220700ustar00rootroot00000000000000package ginkgo import ( "testing" "github.com/onsi/ginkgo/v2/internal/testingtproxy" "github.com/onsi/ginkgo/v2/types" ) /* GinkgoT() implements an interface that allows third party libraries to integrate with and build on top of Ginkgo. GinkgoT() is analogous to *testing.T and implements the majority of *testing.T's methods. It can be typically be used a a drop-in replacement with third-party libraries that accept *testing.T through an interface. GinkgoT() takes an optional offset argument that can be used to get the correct line number associated with the failure - though you do not need to use this if you call GinkgoHelper() or GinkgoT().Helper() appropriately GinkgoT() attempts to mimic the behavior of `testing.T` with the exception of the following: - Error/Errorf: failures in Ginkgo always immediately stop execution and there is no mechanism to log a failure without aborting the test. As such Error/Errorf are equivalent to Fatal/Fatalf. - Parallel() is a no-op as Ginkgo's multi-process parallelism model is substantially different from go test's in-process model. You can learn more here: https://onsi.github.io/ginkgo/#using-third-party-libraries */ func GinkgoT(optionalOffset ...int) FullGinkgoTInterface { offset := 1 if len(optionalOffset) > 0 { offset = optionalOffset[0] } return testingtproxy.New( GinkgoWriter, Fail, Skip, DeferCleanup, CurrentSpecReport, AddReportEntry, GinkgoRecover, AttachProgressReporter, suiteConfig.RandomSeed, suiteConfig.ParallelProcess, suiteConfig.ParallelTotal, reporterConfig.NoColor, offset) } /* The portion of the interface returned by GinkgoT() that maps onto methods in the testing package's T. */ type GinkgoTInterface interface { Cleanup(func()) Setenv(kev, value string) Error(args ...any) Errorf(format string, args ...any) Fail() FailNow() Failed() bool Fatal(args ...any) Fatalf(format string, args ...any) Helper() Log(args ...any) Logf(format string, args ...any) Name() string Parallel() Skip(args ...any) SkipNow() Skipf(format string, args ...any) Skipped() bool TempDir() string } /* Additional methods returned by GinkgoT() that provide deeper integration points into Ginkgo */ type FullGinkgoTInterface interface { GinkgoTInterface AddReportEntryVisibilityAlways(name string, args ...any) AddReportEntryVisibilityFailureOrVerbose(name string, args ...any) AddReportEntryVisibilityNever(name string, args ...any) //Prints to the GinkgoWriter Print(a ...any) Printf(format string, a ...any) Println(a ...any) //Provides access to Ginkgo's color formatting, correctly configured to match the color settings specified in the invocation of ginkgo F(format string, args ...any) string Fi(indentation uint, format string, args ...any) string Fiw(indentation uint, maxWidth uint, format string, args ...any) string //Generates a formatted string version of the current spec's timeline RenderTimeline() string GinkgoRecover() DeferCleanup(args ...any) RandomSeed() int64 ParallelProcess() int ParallelTotal() int AttachProgressReporter(func() string) func() } /* GinkgoTB() implements a wrapper that exactly matches the testing.TB interface. In go 1.18 a new private() function was added to the testing.TB interface. Any function which accepts testing.TB as input needs to be passed in something that directly implements testing.TB. This wrapper satisfies the testing.TB interface and intended to be used as a drop-in replacement with third party libraries that accept testing.TB. Similar to GinkgoT(), GinkgoTB() takes an optional offset argument that can be used to get the correct line number associated with the failure - though you do not need to use this if you call GinkgoHelper() or GinkgoT().Helper() appropriately */ func GinkgoTB(optionalOffset ...int) *GinkgoTBWrapper { offset := 2 if len(optionalOffset) > 0 { offset = optionalOffset[0] } return &GinkgoTBWrapper{GinkgoT: GinkgoT(offset)} } type GinkgoTBWrapper struct { testing.TB GinkgoT FullGinkgoTInterface } func (g *GinkgoTBWrapper) Cleanup(f func()) { g.GinkgoT.Cleanup(f) } func (g *GinkgoTBWrapper) Error(args ...any) { g.GinkgoT.Error(args...) } func (g *GinkgoTBWrapper) Errorf(format string, args ...any) { g.GinkgoT.Errorf(format, args...) } func (g *GinkgoTBWrapper) Fail() { g.GinkgoT.Fail() } func (g *GinkgoTBWrapper) FailNow() { g.GinkgoT.FailNow() } func (g *GinkgoTBWrapper) Failed() bool { return g.GinkgoT.Failed() } func (g *GinkgoTBWrapper) Fatal(args ...any) { g.GinkgoT.Fatal(args...) } func (g *GinkgoTBWrapper) Fatalf(format string, args ...any) { g.GinkgoT.Fatalf(format, args...) } func (g *GinkgoTBWrapper) Helper() { types.MarkAsHelper(1) } func (g *GinkgoTBWrapper) Log(args ...any) { g.GinkgoT.Log(args...) } func (g *GinkgoTBWrapper) Logf(format string, args ...any) { g.GinkgoT.Logf(format, args...) } func (g *GinkgoTBWrapper) Name() string { return g.GinkgoT.Name() } func (g *GinkgoTBWrapper) Setenv(key, value string) { g.GinkgoT.Setenv(key, value) } func (g *GinkgoTBWrapper) Skip(args ...any) { g.GinkgoT.Skip(args...) } func (g *GinkgoTBWrapper) SkipNow() { g.GinkgoT.SkipNow() } func (g *GinkgoTBWrapper) Skipf(format string, args ...any) { g.GinkgoT.Skipf(format, args...) } func (g *GinkgoTBWrapper) Skipped() bool { return g.GinkgoT.Skipped() } func (g *GinkgoTBWrapper) TempDir() string { return g.GinkgoT.TempDir() } golang-github-onsi-ginkgo-v2-2.22.0/go.mod000066400000000000000000000007701472321612100202040ustar00rootroot00000000000000module github.com/onsi/ginkgo/v2 go 1.22.0 toolchain go1.23.0 require ( github.com/go-logr/logr v1.4.2 github.com/go-task/slim-sprig/v3 v3.0.0 github.com/google/pprof v0.0.0-20241029153458-d1b30febd7db github.com/onsi/gomega v1.34.2 golang.org/x/net v0.30.0 golang.org/x/sys v0.26.0 golang.org/x/tools v0.26.0 ) require ( github.com/google/go-cmp v0.6.0 // indirect golang.org/x/text v0.19.0 // indirect google.golang.org/protobuf v1.34.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) golang-github-onsi-ginkgo-v2-2.22.0/go.sum000066400000000000000000000050151472321612100202260ustar00rootroot00000000000000github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI= github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/pprof v0.0.0-20241029153458-d1b30febd7db h1:097atOisP2aRj7vFgYQBbFN4U4JNXUNYpxael3UzMyo= github.com/google/pprof v0.0.0-20241029153458-d1b30febd7db/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144= github.com/onsi/gomega v1.34.2 h1:pNCwDkzrsv7MS9kpaQvVb1aVLahQXyJ/Tv5oAZMI3i8= github.com/onsi/gomega v1.34.2/go.mod h1:v1xfxRgk0KIsG+QOdm7p8UosrOzPYRo60fd3B/1Dukc= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= golang.org/x/net v0.30.0 h1:AcW1SDZMkb8IpzCdQUaIq2sP4sZ4zw+55h6ynffypl4= golang.org/x/net v0.30.0/go.mod h1:2wGyMJ5iFasEhkwi13ChkO/t1ECNC4X4eBKkVFyYFlU= golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM= golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= golang.org/x/tools v0.26.0 h1:v/60pFQmzmT9ExmjDv2gGIfi3OqfKoEP6I5+umXlbnQ= golang.org/x/tools v0.26.0/go.mod h1:TPVVj70c7JJ3WCazhD8OdXcZg/og+b9+tH/KxylGwH0= google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg= google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= golang-github-onsi-ginkgo-v2-2.22.0/integration/000077500000000000000000000000001472321612100214155ustar00rootroot00000000000000golang-github-onsi-ginkgo-v2-2.22.0/integration/_fixtures/000077500000000000000000000000001472321612100234255ustar00rootroot00000000000000golang-github-onsi-ginkgo-v2-2.22.0/integration/_fixtures/abort_fixture/000077500000000000000000000000001472321612100263025ustar00rootroot00000000000000golang-github-onsi-ginkgo-v2-2.22.0/integration/_fixtures/abort_fixture/abort_fixture_suite_test.go000066400000000000000000000011071472321612100337550ustar00rootroot00000000000000package abort_fixture_test import ( "testing" "time" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" ) func TestAbortFixture(t *testing.T) { RegisterFailHandler(Fail) RunSpecs(t, "AbortFixture Suite") } var _ = Describe("top-level container", func() { It("runs and passes", func() {}) It("aborts", func() { time.Sleep(time.Second) AbortSuite("this suite needs to end now!") }) It("never runs", func() { time.Sleep(time.Hour) Fail("SHOULD NOT SEE THIS") }) It("never runs either", func() { time.Sleep(time.Hour) Fail("SHOULD NOT SEE THIS") }) }) golang-github-onsi-ginkgo-v2-2.22.0/integration/_fixtures/after_run_hook_fixture/000077500000000000000000000000001472321612100302005ustar00rootroot00000000000000golang-github-onsi-ginkgo-v2-2.22.0/integration/_fixtures/after_run_hook_fixture/after_run_hook.go000066400000000000000000000001601472321612100335310ustar00rootroot00000000000000package suite_command func Tested() string { return "tested" } func Untested() string { return "untested" } after_run_hook_suite_test.go000066400000000000000000000003271472321612100357270ustar00rootroot00000000000000golang-github-onsi-ginkgo-v2-2.22.0/integration/_fixtures/after_run_hook_fixturepackage suite_command_test import ( . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" "testing" ) func TestAfterRunHook(t *testing.T) { RegisterFailHandler(Fail) RunSpecs(t, "After Run Hook Suite") } after_run_hook_test.go000066400000000000000000000004701472321612100345150ustar00rootroot00000000000000golang-github-onsi-ginkgo-v2-2.22.0/integration/_fixtures/after_run_hook_fixturepackage suite_command_test import ( . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" ) var _ = Describe("Testing", func() { It("it should succeed", func() { Ω(true).Should(Equal(true)) }) PIt("a failing test", func() { It("should fail", func() { Ω(true).Should(Equal(false)) }) }) }) golang-github-onsi-ginkgo-v2-2.22.0/integration/_fixtures/combined_coverage_fixture/000077500000000000000000000000001472321612100306265ustar00rootroot00000000000000golang-github-onsi-ginkgo-v2-2.22.0/integration/_fixtures/combined_coverage_fixture/first_package/000077500000000000000000000000001472321612100334305ustar00rootroot00000000000000coverage.go000066400000000000000000000003021472321612100354660ustar00rootroot00000000000000golang-github-onsi-ginkgo-v2-2.22.0/integration/_fixtures/combined_coverage_fixture/first_packagepackage first_package func A() string { return "A" } func B() string { return "B" } func C() string { return "C" } func D() string { return "D" } func E() string { return "untested" } coverage_fixture_suite_test.go000066400000000000000000000003411472321612100415070ustar00rootroot00000000000000golang-github-onsi-ginkgo-v2-2.22.0/integration/_fixtures/combined_coverage_fixture/first_packagepackage first_package_test import ( . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" "testing" ) func TestCoverageFixture(t *testing.T) { RegisterFailHandler(Fail) RunSpecs(t, "CombinedFixture First Suite") } coverage_fixture_test.go000066400000000000000000000012701472321612100403000ustar00rootroot00000000000000golang-github-onsi-ginkgo-v2-2.22.0/integration/_fixtures/combined_coverage_fixture/first_packagepackage first_package_test import ( . "github.com/onsi/ginkgo/v2/integration/_fixtures/combined_coverage_fixture/first_package" . "github.com/onsi/ginkgo/v2/integration/_fixtures/combined_coverage_fixture/first_package/external_coverage" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" ) var _ = Describe("CoverageFixture", func() { It("should test A", func() { Ω(A()).Should(Equal("A")) }) It("should test B", func() { Ω(B()).Should(Equal("B")) }) It("should test C", func() { Ω(C()).Should(Equal("C")) }) It("should test D", func() { Ω(D()).Should(Equal("D")) }) It("should test external package", func() { Ω(Tested()).Should(Equal("tested")) }) }) external_coverage/000077500000000000000000000000001472321612100370465ustar00rootroot00000000000000golang-github-onsi-ginkgo-v2-2.22.0/integration/_fixtures/combined_coverage_fixture/first_packageexternal_coverage.go000066400000000000000000000001641472321612100430730ustar00rootroot00000000000000golang-github-onsi-ginkgo-v2-2.22.0/integration/_fixtures/combined_coverage_fixture/first_package/external_coveragepackage external_coverage func Tested() string { return "tested" } func Untested() string { return "untested" } golang-github-onsi-ginkgo-v2-2.22.0/integration/_fixtures/combined_coverage_fixture/second_package/000077500000000000000000000000001472321612100335545ustar00rootroot00000000000000coverage.go000066400000000000000000000002741472321612100356220ustar00rootroot00000000000000golang-github-onsi-ginkgo-v2-2.22.0/integration/_fixtures/combined_coverage_fixture/second_packagepackage second_package func A() string { return "A" } func B() string { return "B" } func C() string { return "C" } func D() string { return "D" } func E() string { return "E" } coverage_fixture_suite_test.go000066400000000000000000000003431472321612100416350ustar00rootroot00000000000000golang-github-onsi-ginkgo-v2-2.22.0/integration/_fixtures/combined_coverage_fixture/second_packagepackage second_package_test import ( . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" "testing" ) func TestCoverageFixture(t *testing.T) { RegisterFailHandler(Fail) RunSpecs(t, "CombinedFixture Second Suite") } coverage_fixture_test.go000066400000000000000000000010611472321612100404220ustar00rootroot00000000000000golang-github-onsi-ginkgo-v2-2.22.0/integration/_fixtures/combined_coverage_fixture/second_packagepackage second_package_test import ( . "github.com/onsi/ginkgo/v2" . "github.com/onsi/ginkgo/v2/integration/_fixtures/combined_coverage_fixture/second_package" . "github.com/onsi/gomega" ) var _ = Describe("CoverageFixture", func() { It("should test A", func() { Ω(A()).Should(Equal("A")) }) It("should test B", func() { Ω(B()).Should(Equal("B")) }) It("should test C", func() { Ω(C()).Should(Equal("C")) }) It("should test D", func() { Ω(D()).Should(Equal("D")) }) It("should test E", func() { Ω(E()).Should(Equal("E")) }) }) golang-github-onsi-ginkgo-v2-2.22.0/integration/_fixtures/combined_coverage_fixture/third_package/000077500000000000000000000000001472321612100334135ustar00rootroot00000000000000third_package_suite_test.go000066400000000000000000000004061472321612100407200ustar00rootroot00000000000000golang-github-onsi-ginkgo-v2-2.22.0/integration/_fixtures/combined_coverage_fixture/third_packagepackage third_package_test import ( "testing" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" ) func TestThirdPackage(t *testing.T) { RegisterFailHandler(Fail) RunSpecs(t, "ThirdPackage Suite") } var _ = It("doesn't cover anything", func() {}) golang-github-onsi-ginkgo-v2-2.22.0/integration/_fixtures/config_override_label_filter_fixture/000077500000000000000000000000001472321612100330435ustar00rootroot00000000000000config_override_fixture_suite_test.go000066400000000000000000000010361472321612100424750ustar00rootroot00000000000000golang-github-onsi-ginkgo-v2-2.22.0/integration/_fixtures/config_override_label_filter_fixturepackage config_override_label_filter_fixture_test import ( "testing" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" ) func TestConfigOverrideFixture(t *testing.T) { RegisterFailHandler(Fail) suiteConfig, reporterConfig := GinkgoConfiguration() suiteConfig.LabelFilter = "!NORUN" RunSpecs(t, "ConfigOverrideFixture Suite", suiteConfig, reporterConfig) } var _ = Describe("tests", func() { It("never runs", Label("NORUN"), func() { Ω(true).Should(BeFalse()) }) It("runs", func() { Ω(true).Should(BeTrue()) }) }) config_override_must_pass_repeatedly_fixture/000077500000000000000000000000001472321612100345745ustar00rootroot00000000000000golang-github-onsi-ginkgo-v2-2.22.0/integration/_fixturesconfig_override_fixture_suite_test.go000066400000000000000000000010531472321612100443040ustar00rootroot00000000000000golang-github-onsi-ginkgo-v2-2.22.0/integration/_fixtures/config_override_must_pass_repeatedly_fixturepackage config_override_label_filter_fixture_test import ( "testing" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" ) func TestConfigOverrideFixture(t *testing.T) { RegisterFailHandler(Fail) suiteConfig, reporterConfig := GinkgoConfiguration() suiteConfig.MustPassRepeatedly = 10 RunSpecs(t, "ConfigOverrideFixture Suite", suiteConfig, reporterConfig) } var _ = Describe("tests", func() { It("suite config overrides decorator", MustPassRepeatedly(2), func() { Ω(CurrentSpecReport().MaxMustPassRepeatedly).Should(Equal(10)) }) }) golang-github-onsi-ginkgo-v2-2.22.0/integration/_fixtures/coverage_fixture/000077500000000000000000000000001472321612100267665ustar00rootroot00000000000000golang-github-onsi-ginkgo-v2-2.22.0/integration/_fixtures/coverage_fixture/additional_spec/000077500000000000000000000000001472321612100321105ustar00rootroot00000000000000additional_spec_suite_test.go000066400000000000000000000011651472321612100377550ustar00rootroot00000000000000golang-github-onsi-ginkgo-v2-2.22.0/integration/_fixtures/coverage_fixture/additional_specpackage additional_spec_test import ( . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" . "github.com/onsi/ginkgo/v2/integration/_fixtures/coverage_fixture" . "github.com/onsi/ginkgo/v2/integration/_fixtures/coverage_fixture/external_coverage" "testing" ) func TestAdditionalSpecSuite(t *testing.T) { RegisterFailHandler(Fail) RunSpecs(t, "AdditionalSpec Suite") } var _ = Describe("CoverageFixture", func() { It("should test E", func() { Ω(E()).Should(Equal("tested by additional")) }) It("should test external package", func() { Ω(TestedByAdditional()).Should(Equal("tested by additional")) }) }) golang-github-onsi-ginkgo-v2-2.22.0/integration/_fixtures/coverage_fixture/coverage.go000066400000000000000000000004651472321612100311150ustar00rootroot00000000000000package coverage_fixture import ( _ "github.com/onsi/ginkgo/v2/integration/_fixtures/coverage_fixture/external_coverage" ) func A() string { return "A" } func B() string { return "B" } func C() string { return "C" } func D() string { return "D" } func E() string { return "tested by additional" } coverage_fixture_suite_test.go000066400000000000000000000003361472321612100350510ustar00rootroot00000000000000golang-github-onsi-ginkgo-v2-2.22.0/integration/_fixtures/coverage_fixturepackage coverage_fixture_test import ( . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" "testing" ) func TestCoverageFixture(t *testing.T) { RegisterFailHandler(Fail) RunSpecs(t, "CoverageFixture Suite") } golang-github-onsi-ginkgo-v2-2.22.0/integration/_fixtures/coverage_fixture/coverage_fixture_test.go000066400000000000000000000012151472321612100337140ustar00rootroot00000000000000package coverage_fixture_test import ( . "github.com/onsi/ginkgo/v2/integration/_fixtures/coverage_fixture" . "github.com/onsi/ginkgo/v2/integration/_fixtures/coverage_fixture/external_coverage" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" ) var _ = Describe("CoverageFixture", func() { It("should test A", func() { Ω(A()).Should(Equal("A")) }) It("should test B", func() { Ω(B()).Should(Equal("B")) }) It("should test C", func() { Ω(C()).Should(Equal("C")) }) It("should test D", func() { Ω(D()).Should(Equal("D")) }) It("should test external package", func() { Ω(Tested()).Should(Equal("tested")) }) }) golang-github-onsi-ginkgo-v2-2.22.0/integration/_fixtures/coverage_fixture/external_coverage/000077500000000000000000000000001472321612100324635ustar00rootroot00000000000000external_coverage.go000066400000000000000000000002121472321612100364230ustar00rootroot00000000000000golang-github-onsi-ginkgo-v2-2.22.0/integration/_fixtures/coverage_fixture/external_coveragepackage external_coverage func Tested() string { return "tested" } func TestedByAdditional() string { return "tested by additional" } golang-github-onsi-ginkgo-v2-2.22.0/integration/_fixtures/decorations_fixture/000077500000000000000000000000001472321612100275055ustar00rootroot00000000000000golang-github-onsi-ginkgo-v2-2.22.0/integration/_fixtures/decorations_fixture/flaky_repeated/000077500000000000000000000000001472321612100324645ustar00rootroot00000000000000flaky_repeated_fixture_suite_test.go000066400000000000000000000011471472321612100417340ustar00rootroot00000000000000golang-github-onsi-ginkgo-v2-2.22.0/integration/_fixtures/decorations_fixture/flaky_repeatedpackage decorations_fixture_test import ( "fmt" "testing" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" ) func TestDecorationsFixture(t *testing.T) { RegisterFailHandler(Fail) RunSpecs(t, "DecorationsFixture Suite") } var countFlake = 0 var countRepeat = 0 var _ = Describe("some decorated specs", func() { It("passes eventually", func() { countFlake += 1 if countFlake < 3 { Fail("fail") } }, FlakeAttempts(3)) It("fails eventually", func() { countRepeat += 1 if countRepeat >= 3 { Fail(fmt.Sprintf("failed on attempt #%d", countRepeat)) } }, MustPassRepeatedly(3)) }) invalid_decorations_flakeattempts_mustpassrepeatedly/000077500000000000000000000000001472321612100424105ustar00rootroot00000000000000golang-github-onsi-ginkgo-v2-2.22.0/integration/_fixtures/decorations_fixtureinvalid_decorations_flakeattempts_mustpassrepeatedly_suite_test.go000066400000000000000000000007221472321612100601720ustar00rootroot00000000000000golang-github-onsi-ginkgo-v2-2.22.0/integration/_fixtures/decorations_fixture/invalid_decorations_flakeattempts_mustpassrepeatedlypackage invalid_decorations_flakeattempts_mustpassrepeatedly_test import ( "testing" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" ) func TestInvalidDecorations(t *testing.T) { RegisterFailHandler(Fail) RunSpecs(t, "InvalidDecorations Suite - MustPassRepeatedly and FlakeAttempts") } var _ = Describe("invalid decorators: mustpassrepeatedly and flakeattempts", FlakeAttempts(3), MustPassRepeatedly(3), func() { It("never runs", func() { }) }) invalid_decorations_focused_pending/000077500000000000000000000000001472321612100366625ustar00rootroot00000000000000golang-github-onsi-ginkgo-v2-2.22.0/integration/_fixtures/decorations_fixtureinvalid_decorations_focused_pending_suite_test.go000066400000000000000000000006061472321612100507170ustar00rootroot00000000000000golang-github-onsi-ginkgo-v2-2.22.0/integration/_fixtures/decorations_fixture/invalid_decorations_focused_pendingpackage invalid_decorations_focused_pending_test import ( "testing" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" ) func TestInvalidDecorations(t *testing.T) { RegisterFailHandler(Fail) RunSpecs(t, "InvalidDecorations Suite - Focused and Pending") } var _ = Describe("invalid decorators: focused and pending", Focus, Pending, func() { It("never runs", func() { }) }) golang-github-onsi-ginkgo-v2-2.22.0/integration/_fixtures/decorations_fixture/offset_focus_pending/000077500000000000000000000000001472321612100336765ustar00rootroot00000000000000never_see_this_file_test.go000066400000000000000000000002051472321612100412030ustar00rootroot00000000000000golang-github-onsi-ginkgo-v2-2.22.0/integration/_fixtures/decorations_fixture/offset_focus_pendingpackage decorations_fixture_test import . "github.com/onsi/ginkgo/v2" func OffsetIt() { It("is offset", Offset(1), func() { }) } offset_focus_pending_fixture_suite_test.go000066400000000000000000000007501472321612100443570ustar00rootroot00000000000000golang-github-onsi-ginkgo-v2-2.22.0/integration/_fixtures/decorations_fixture/offset_focus_pendingpackage decorations_fixture_test import ( "testing" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" ) func TestDecorationsFixture(t *testing.T) { RegisterFailHandler(Fail) RunSpecs(t, "DecorationsFixture Suite") } var countFlake = 0 var countRepeat = 0 var _ = Describe("some decorated specs", func() { Describe("focused", Focus, func() { OffsetIt() }) It("pending it", Pending, func() { }) It("focused it", Focus, func() { Ω(true).Should(BeTrue()) }) }) golang-github-onsi-ginkgo-v2-2.22.0/integration/_fixtures/deprecated_features_fixture/000077500000000000000000000000001472321612100311715ustar00rootroot00000000000000deprecated_features_fixture_suite_test.go000066400000000000000000000006371472321612100414630ustar00rootroot00000000000000golang-github-onsi-ginkgo-v2-2.22.0/integration/_fixtures/deprecated_features_fixturepackage deprecated_features_fixture_test import ( "testing" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" ) func TestDeprecatedFeaturesFixture(t *testing.T) { RegisterFailHandler(Fail) RunSpecs(t, "DeprecatedFeaturesFixture Suite") } var _ = It("tries to perform an async assertion", func(done Done) { close(done) }) var _ = Measure("tries to perform a measurement", func(b Benchmarker) { })golang-github-onsi-ginkgo-v2-2.22.0/integration/_fixtures/does_not_compile_fixture/000077500000000000000000000000001472321612100305155ustar00rootroot00000000000000does_not_compile_suite_test.go000066400000000000000000000003401472321612100365540ustar00rootroot00000000000000golang-github-onsi-ginkgo-v2-2.22.0/integration/_fixtures/does_not_compile_fixturepackage does_not_compile_test import ( . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" "testing" ) func TestDoes_not_compile(t *testing.T) { RegisterFailHandler(Fail) RunSpecs(t, "Does_not_compile Suite") } does_not_compile_test.go000066400000000000000000000003341472321612100353460ustar00rootroot00000000000000golang-github-onsi-ginkgo-v2-2.22.0/integration/_fixtures/does_not_compile_fixturepackage does_not_compile_test import ( . "github.com/onsi/ginkgo/v2" . "github.com/onsi/ginkgo/v2/integration/_fixtures/does_not_compile" . "github.com/onsi/gomega" ) var _ = Describe("DoesNotCompile", func() { }) golang-github-onsi-ginkgo-v2-2.22.0/integration/_fixtures/eventually_failing_fixture/000077500000000000000000000000001472321612100310545ustar00rootroot00000000000000eventually_failing_suite_test.go000066400000000000000000000003441472321612100374560ustar00rootroot00000000000000golang-github-onsi-ginkgo-v2-2.22.0/integration/_fixtures/eventually_failing_fixturepackage eventually_failing_test import ( . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" "testing" ) func TestEventuallyFailing(t *testing.T) { RegisterFailHandler(Fail) RunSpecs(t, "EventuallyFailing Suite") } eventually_failing_test.go000066400000000000000000000010711472321612100362430ustar00rootroot00000000000000golang-github-onsi-ginkgo-v2-2.22.0/integration/_fixtures/eventually_failing_fixturepackage eventually_failing_test import ( "fmt" "os" "strings" "time" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" ) var _ = Describe("EventuallyFailing", func() { It("should fail on the third try", func() { time.Sleep(time.Second) files, err := os.ReadDir(".") Ω(err).ShouldNot(HaveOccurred()) numRuns := 1 for _, file := range files { if strings.HasPrefix(file.Name(), "counter") { numRuns++ } } Ω(numRuns).Should(BeNumerically("<", 3)) os.WriteFile(fmt.Sprintf("./counter-%d", numRuns), []byte("foo"), 0777) }) }) golang-github-onsi-ginkgo-v2-2.22.0/integration/_fixtures/exiting_synchronized_setup_fixture/000077500000000000000000000000001472321612100326615ustar00rootroot00000000000000exiting_synchronized_setup_suite_test.go000066400000000000000000000012141472321612100430650ustar00rootroot00000000000000golang-github-onsi-ginkgo-v2-2.22.0/integration/_fixtures/exiting_synchronized_setup_fixturepackage synchronized_setup_test import ( . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" "fmt" "os" "testing" ) func TestSynchronized_setup(t *testing.T) { RegisterFailHandler(Fail) RunSpecs(t, "Synchronized_setup_tests Suite") } var beforeData string var _ = SynchronizedBeforeSuite(func() []byte { fmt.Printf("BEFORE_A_%d\n", GinkgoParallelProcess()) os.Exit(1) return []byte("WHAT EVZ") }, func(data []byte) { println("NEVER SEE THIS") }) var _ = Describe("Synchronized Setup", func() { It("should do nothing", func() { Ω(true).Should(BeTrue()) }) It("should do nothing", func() { Ω(true).Should(BeTrue()) }) }) golang-github-onsi-ginkgo-v2-2.22.0/integration/_fixtures/fail_fixture/000077500000000000000000000000001472321612100261065ustar00rootroot00000000000000golang-github-onsi-ginkgo-v2-2.22.0/integration/_fixtures/fail_fixture/fail_fixture_ginkgo_t_test.go000066400000000000000000000012161472321612100340360ustar00rootroot00000000000000package fail_fixture_test import ( . "github.com/onsi/ginkgo/v2" ) var _ = Describe("GinkgoT", func() { It("synchronous failures with GinkgoT().Fail", func() { GinkgoT().Fail() println("NEVER SEE THIS") }) DescribeTable("DescribeTable", func() { GinkgoT().Fail() }, Entry("a TableEntry constructed by Entry"), ) It("tracks line numbers correctly when GinkgoT().Helper() is called", func() { ginkgoTHelper() }) It("tracks the actual line number when no helper is used", func() { ginkgoTNoHelper() }) }) var ginkgoTNoHelper = func() { GinkgoT().Fail() } var ginkgoTHelper = func() { GinkgoT().Helper() GinkgoT().Fail() } fail_fixture_ginkgo_tb_test.go000066400000000000000000000012421472321612100341200ustar00rootroot00000000000000golang-github-onsi-ginkgo-v2-2.22.0/integration/_fixtures/fail_fixturepackage fail_fixture_test import ( . "github.com/onsi/ginkgo/v2" ) var _ = Describe("GinkgoTB", func() { It("synchronous failures with GinkgoTB().Fail", func() { GinkgoTB().Fail() println("NEVER SEE THIS") }) DescribeTable("DescribeTable", func() { GinkgoTB().Fail() }, Entry("a TableEntry constructed by Entry"), ) It("tracks line numbers correctly when GinkgoTB().Helper() is called", func() { ginkgoTBHelper() }) It("tracks the actual line number when no GinkgoTB helper is used", func() { ginkgoTBNoHelper() }) }) var ginkgoTBNoHelper = func() { GinkgoTB().Fail() } var ginkgoTBHelper = func() { t := GinkgoTB() t.Helper() t.Fail() } golang-github-onsi-ginkgo-v2-2.22.0/integration/_fixtures/fail_fixture/fail_fixture_suite_test.go000066400000000000000000000003241472321612100333650ustar00rootroot00000000000000package fail_fixture_test import ( . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" "testing" ) func TestFail_fixture(t *testing.T) { RegisterFailHandler(Fail) RunSpecs(t, "Fail_fixture Suite") } golang-github-onsi-ginkgo-v2-2.22.0/integration/_fixtures/fail_fixture/fail_fixture_test.go000066400000000000000000000021731472321612100321600ustar00rootroot00000000000000package fail_fixture_test import ( "context" "time" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" ) var _ = It("handles top level failures", func() { Ω("a top level failure on line 12").Should(Equal("nope")) println("NEVER SEE THIS") }) var _ = Describe("Exercising different failure modes", func() { It("synchronous failures", func() { Ω("a sync failure").Should(Equal("nope")) println("NEVER SEE THIS") }) It("synchronous panics", func() { panic("a sync panic") println("NEVER SEE THIS") }) It("synchronous failures with FAIL", func() { Fail("a sync FAIL failure") println("NEVER SEE THIS") }) It("times out", func(c context.Context) { <-c.Done() }, NodeTimeout(time.Millisecond*50)) }) var _ = Specify("a top level specify", func() { Fail("fail the test") }) var _ = DescribeTable("a top level DescribeTable", func(x, y int) { Expect(x).To(Equal(y)) }, Entry("a TableEntry constructed by Entry", 2, 3), ) var helper = func() { GinkgoHelper() Ω("a helper failed").Should(Equal("nope")) } var _ = It("tracks line numbers correctly when GinkgoHelper() is called", func() { helper() }) golang-github-onsi-ginkgo-v2-2.22.0/integration/_fixtures/fail_then_hang_fixture/000077500000000000000000000000001472321612100301215ustar00rootroot00000000000000fail_then_hang_fixture_suite_test.go000066400000000000000000000007231472321612100373370ustar00rootroot00000000000000golang-github-onsi-ginkgo-v2-2.22.0/integration/_fixtures/fail_then_hang_fixturepackage fail_then_hang_fixture_test import ( "testing" "time" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" ) func TestFailThenHangFixture(t *testing.T) { RegisterFailHandler(Fail) RunSpecs(t, "FailThenHangFixture Suite") } var _ = Describe("Failing then hanging", func() { It("fails", func() { time.Sleep(time.Second) Fail("boom") }) It("hangs", func() { time.Sleep(time.Hour) }) It("hangs", func() { time.Sleep(time.Hour) }) }) golang-github-onsi-ginkgo-v2-2.22.0/integration/_fixtures/failing_ginkgo_tests_fixture/000077500000000000000000000000001472321612100313645ustar00rootroot00000000000000failing_ginkgo_tests.go000066400000000000000000000001101472321612100360150ustar00rootroot00000000000000golang-github-onsi-ginkgo-v2-2.22.0/integration/_fixtures/failing_ginkgo_tests_fixturepackage failing_ginkgo_tests func AlwaysFalse() bool { return false } failing_ginkgo_tests_suite_test.go000066400000000000000000000003541472321612100402770ustar00rootroot00000000000000golang-github-onsi-ginkgo-v2-2.22.0/integration/_fixtures/failing_ginkgo_tests_fixturepackage failing_ginkgo_tests_test import ( . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" "testing" ) func TestFailing_ginkgo_tests(t *testing.T) { RegisterFailHandler(Fail) RunSpecs(t, "Failing_ginkgo_tests Suite") } failing_ginkgo_tests_test.go000066400000000000000000000005631472321612100370700ustar00rootroot00000000000000golang-github-onsi-ginkgo-v2-2.22.0/integration/_fixtures/failing_ginkgo_tests_fixturepackage failing_ginkgo_tests_test import ( . "github.com/onsi/ginkgo/v2" . "github.com/onsi/ginkgo/v2/integration/_fixtures/failing_ginkgo_tests" . "github.com/onsi/gomega" ) var _ = Describe("FailingGinkgoTests", func() { It("should fail", func() { Ω(AlwaysFalse()).Should(BeTrue()) }) It("should pass", func() { Ω(AlwaysFalse()).Should(BeFalse()) }) }) golang-github-onsi-ginkgo-v2-2.22.0/integration/_fixtures/filter_fixture/000077500000000000000000000000001472321612100264605ustar00rootroot00000000000000golang-github-onsi-ginkgo-v2-2.22.0/integration/_fixtures/filter_fixture/filter_suite_test.go000066400000000000000000000005571472321612100325530ustar00rootroot00000000000000package filter_fixture_test import ( "testing" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" ) func TestFilterFixture(t *testing.T) { RegisterFailHandler(Fail) RunSpecs(t, "FilterFixture Suite", Label("TopLevelLabel")) } var _ = BeforeEach(func() { config, _ := GinkgoConfiguration() Ω(GinkgoLabelFilter()).Should(Equal(config.LabelFilter)) }) golang-github-onsi-ginkgo-v2-2.22.0/integration/_fixtures/filter_fixture/nugget_a_test.go000066400000000000000000000003411472321612100316350ustar00rootroot00000000000000package filter_fixture_test import ( . "github.com/onsi/ginkgo/v2" ) var _ = Describe("NuggetA", func() { It("cat", func() { }) It("dog", func() { }) It("cat fish", func() { }) It("dog fish", func() { }) }) golang-github-onsi-ginkgo-v2-2.22.0/integration/_fixtures/filter_fixture/nugget_b_test.go000066400000000000000000000003411472321612100316360ustar00rootroot00000000000000package filter_fixture_test import ( . "github.com/onsi/ginkgo/v2" ) var _ = Describe("NuggetB", func() { It("cat", func() { }) It("dog", func() { }) It("cat fish", func() { }) It("dog fish", func() { }) }) golang-github-onsi-ginkgo-v2-2.22.0/integration/_fixtures/filter_fixture/sprocket_a_test.go000066400000000000000000000003541472321612100322020ustar00rootroot00000000000000package filter_fixture_test import ( . "github.com/onsi/ginkgo/v2" ) var _ = Describe("SprocketA", func() { It("cat", func() { }) PIt("pending dog", func() { }) It("cat fish", func() { }) It("dog fish", func() { }) }) golang-github-onsi-ginkgo-v2-2.22.0/integration/_fixtures/filter_fixture/sprocket_b_test.go000066400000000000000000000004151472321612100322010ustar00rootroot00000000000000package filter_fixture_test import ( . "github.com/onsi/ginkgo/v2" ) var _ = Describe("SprocketB", func() { It("cat", func() { }) It("dog", func() { }) It("cat fish", func() { }) It("dog fish", func() { }) It("fish", Label("slow"), func() { }) }) golang-github-onsi-ginkgo-v2-2.22.0/integration/_fixtures/filter_fixture/sprocket_c_test.go000066400000000000000000000003431472321612100322020ustar00rootroot00000000000000package filter_fixture_test import ( . "github.com/onsi/ginkgo/v2" ) var _ = Describe("SprocketC", func() { It("cat", func() { }) It("dog", func() { }) It("cat fish", func() { }) It("dog fish", func() { }) }) golang-github-onsi-ginkgo-v2-2.22.0/integration/_fixtures/filter_fixture/widget_a_test.go000066400000000000000000000006001472321612100316250ustar00rootroot00000000000000package filter_fixture_test import ( . "github.com/onsi/ginkgo/v2" ) var _ = Describe("WidgetA", func() { It("cat", func() { }) It("dog", func() { }) It("cat fish", func() { }) It("dog fish", func() { }) }) var _ = Describe("More WidgetA", func() { It("cat", func() { }) It("dog", func() { }) It("cat fish", func() { }) It("dog fish", func() { }) }) golang-github-onsi-ginkgo-v2-2.22.0/integration/_fixtures/filter_fixture/widget_b_test.go000066400000000000000000000007311472321612100316330ustar00rootroot00000000000000package filter_fixture_test import ( . "github.com/onsi/ginkgo/v2" ) var _ = Describe("WidgetB", func() { It("cat", func() { }) It("dog", Label("slow"), func() { }) It("fish", Label("Feature:Alpha"), func() { }) It("cat fish", func() { }) It("dog fish", Label("Feature:Beta"), func() { }) }) var _ = Describe("More WidgetB", func() { It("cat", func() { }) It("dog", func() { }) It("cat fish", func() { }) It("dog fish", func() { }) }) golang-github-onsi-ginkgo-v2-2.22.0/integration/_fixtures/flags_fixture/000077500000000000000000000000001472321612100262675ustar00rootroot00000000000000golang-github-onsi-ginkgo-v2-2.22.0/integration/_fixtures/flags_fixture/flags.go000066400000000000000000000001501472321612100277060ustar00rootroot00000000000000package flags func Tested() string { return "tested" } func Untested() string { return "untested" } golang-github-onsi-ginkgo-v2-2.22.0/integration/_fixtures/flags_fixture/flags_suite_test.go000066400000000000000000000002771472321612100321700ustar00rootroot00000000000000package flags_test import ( . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" "testing" ) func TestFlags(t *testing.T) { RegisterFailHandler(Fail) RunSpecs(t, "Flags Suite") } golang-github-onsi-ginkgo-v2-2.22.0/integration/_fixtures/flags_fixture/flags_test.go000066400000000000000000000030351472321612100307520ustar00rootroot00000000000000package flags_test import ( "flag" "fmt" remapped "math" _ "math/cmplx" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/ginkgo/v2/integration/_fixtures/flags_fixture" . "github.com/onsi/gomega" ) var customFlag string func init() { flag.StringVar(&customFlag, "customFlag", "default", "custom flag!") } var _ = Describe("Testing various flags", func() { It("should honor -cover", func() { Ω(Tested()).Should(Equal("tested")) }) It("should allow gcflags", func() { fmt.Printf("NaN returns %T\n", remapped.NaN()) }) PIt("should honor -failOnPending and -noisyPendings") Describe("smores", func() { It("should honor -skip: marshmallow", func() { println("marshmallow") }) It("should honor -focus: chocolate", func() { println("chocolate") }) }) It("should detect races", func() { var a string c := make(chan interface{}, 0) go func() { a = "now you don't" close(c) }() a = "now you see me" println(a) Eventually(c).Should(BeClosed()) }) It("should randomize A", func() { println("RANDOM_A") }) It("should randomize B", func() { println("RANDOM_B") }) It("should randomize C", func() { println("RANDOM_C") }) It("should pass in additional arguments after '--' directly to the test process", func() { fmt.Printf("CUSTOM_FLAG: %s", customFlag) }) It("should fail", func() { Ω(true).Should(Equal(false)) }) Describe("a flaky test", func() { runs := 0 It("should only pass the second time it's run", func() { runs++ Ω(runs).Should(BeNumerically("==", 2)) }) }) }) golang-github-onsi-ginkgo-v2-2.22.0/integration/_fixtures/focused_fixture/000077500000000000000000000000001472321612100266235ustar00rootroot00000000000000golang-github-onsi-ginkgo-v2-2.22.0/integration/_fixtures/focused_fixture/README.md000066400000000000000000000001361472321612100301020ustar00rootroot00000000000000This file should remain the same, regardless the fact that contains FIt, FDescribe, or FWhen. focused_fixture_suite_test.go000066400000000000000000000003351472321612100345420ustar00rootroot00000000000000golang-github-onsi-ginkgo-v2-2.22.0/integration/_fixtures/focused_fixturepackage focused_fixture_test import ( . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" "testing" ) func TestFocused_fixture(t *testing.T) { RegisterFailHandler(Fail) RunSpecs(t, "Focused_fixture Suite") } golang-github-onsi-ginkgo-v2-2.22.0/integration/_fixtures/focused_fixture/focused_fixture_test.go000066400000000000000000000015531472321612100334130ustar00rootroot00000000000000// some long comment that might actually break things if we aren't careful package focused_fixture_test import ( . "github.com/onsi/ginkgo/v2" ) var _ = Describe("FocusedFixture", func() { FDescribe("focused", func() { It("focused", func() { }) }) FContext("focused", func() { It("focused", func() { }) }) FWhen("focused", func() { It("focused", func() { }) }) FIt("focused", func() { }) It("focused", Focus, func() { }) FSpecify("focused", func() { }) FDescribeTable("focused", func() {}, Entry("focused"), ) DescribeTable("focused", func() {}, FEntry("focused"), ) Describe("not focused", func() { It("not focused", func() { }) }) Context("not focused", func() { It("not focused", func() { }) }) It("not focused", func() { }) DescribeTable("not focused", func() {}, Entry("not focused"), ) }) golang-github-onsi-ginkgo-v2-2.22.0/integration/_fixtures/focused_fixture/internal/000077500000000000000000000000001472321612100304375ustar00rootroot00000000000000focused_fixture_suite_test.go000066400000000000000000000003351472321612100363560ustar00rootroot00000000000000golang-github-onsi-ginkgo-v2-2.22.0/integration/_fixtures/focused_fixture/internalpackage focused_fixture_test import ( . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" "testing" ) func TestFocused_fixture(t *testing.T) { RegisterFailHandler(Fail) RunSpecs(t, "Focused_fixture Suite") } focused_fixture_test.go000066400000000000000000000014371472321612100351510ustar00rootroot00000000000000golang-github-onsi-ginkgo-v2-2.22.0/integration/_fixtures/focused_fixture/internalpackage focused_fixture_test import ( . "github.com/onsi/ginkgo/v2" ) var _ = Describe("FocusedFixture", func() { FDescribe("focused", func() { It("focused", func() { }) }) FContext("focused", func() { It("focused", func() { }) }) FWhen("focused", func() { It("focused", func() { }) }) FIt("focused", func() { }) It("focused", Focus, func() { }) FSpecify("focused", func() { }) FDescribeTable("focused", func() {}, Entry("focused"), ) DescribeTable("focused", func() {}, FEntry("focused"), ) Describe("not focused", func() { It("not focused", func() { }) }) Context("not focused", func() { It("not focused", func() { }) }) It("not focused", func() { }) DescribeTable("not focused", func() {}, Entry("not focused"), ) }) golang-github-onsi-ginkgo-v2-2.22.0/integration/_fixtures/focused_with_vendor_fixture/000077500000000000000000000000001472321612100312335ustar00rootroot00000000000000focused_fixture_suite_test.go000066400000000000000000000003351472321612100371520ustar00rootroot00000000000000golang-github-onsi-ginkgo-v2-2.22.0/integration/_fixtures/focused_with_vendor_fixturepackage focused_fixture_test import ( . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" "testing" ) func TestFocused_fixture(t *testing.T) { RegisterFailHandler(Fail) RunSpecs(t, "Focused_fixture Suite") } focused_fixture_test.go000066400000000000000000000013721472321612100357430ustar00rootroot00000000000000golang-github-onsi-ginkgo-v2-2.22.0/integration/_fixtures/focused_with_vendor_fixturepackage focused_fixture_test import ( . "github.com/onsi/ginkgo/v2" ) var _ = Describe("FocusedFixture", func() { FDescribe("focused", func() { It("focused", func() { }) }) FContext("focused", func() { It("focused", func() { }) }) FWhen("focused", func() { It("focused", func() { }) }) FIt("focused", func() { }) FSpecify("focused", func() { }) FDescribeTable("focused", func() {}, Entry("focused"), ) DescribeTable("focused", func() {}, FEntry("focused"), ) Describe("not focused", func() { It("not focused", func() { }) }) Context("not focused", func() { It("not focused", func() { }) }) It("not focused", func() { }) DescribeTable("not focused", func() {}, Entry("not focused"), ) }) golang-github-onsi-ginkgo-v2-2.22.0/integration/_fixtures/focused_with_vendor_fixture/vendor/000077500000000000000000000000001472321612100325305ustar00rootroot00000000000000golang-github-onsi-ginkgo-v2-2.22.0/integration/_fixtures/focused_with_vendor_fixture/vendor/foo/000077500000000000000000000000001472321612100333135ustar00rootroot00000000000000bar/000077500000000000000000000000001472321612100340005ustar00rootroot00000000000000golang-github-onsi-ginkgo-v2-2.22.0/integration/_fixtures/focused_with_vendor_fixture/vendor/foobar.go000066400000000000000000000000461472321612100350730ustar00rootroot00000000000000golang-github-onsi-ginkgo-v2-2.22.0/integration/_fixtures/focused_with_vendor_fixture/vendor/foo/barpackage vendored func FContext() { } foo.go000066400000000000000000000000411472321612100343410ustar00rootroot00000000000000golang-github-onsi-ginkgo-v2-2.22.0/integration/_fixtures/focused_with_vendor_fixture/vendor/foopackage vendored func FIt() { } vendored.go000066400000000000000000000000661472321612100346100ustar00rootroot00000000000000golang-github-onsi-ginkgo-v2-2.22.0/integration/_fixtures/focused_with_vendor_fixture/vendorpackage vendored func FDescribe() int { return 42 } golang-github-onsi-ginkgo-v2-2.22.0/integration/_fixtures/hanging_fixture/000077500000000000000000000000001472321612100266065ustar00rootroot00000000000000golang-github-onsi-ginkgo-v2-2.22.0/integration/_fixtures/hanging_fixture/hanging_suite_test.go000066400000000000000000000003251472321612100330200ustar00rootroot00000000000000package hanging_suite_test import ( . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" "testing" ) func TestHangingSuite(t *testing.T) { RegisterFailHandler(Fail) RunSpecs(t, "HangingSuite Suite") } golang-github-onsi-ginkgo-v2-2.22.0/integration/_fixtures/hanging_fixture/hanging_test.go000066400000000000000000000020041472321612100316030ustar00rootroot00000000000000package hanging_suite_test import ( "fmt" "time" . "github.com/onsi/ginkgo/v2" ) var _ = AfterSuite(func() { fmt.Println("Heading Out After Suite") }) var _ = ReportAfterSuite("", func(r Report) { fmt.Println("Reporting at the end") }) var _ = Describe("HangingSuite", func() { BeforeEach(func() { fmt.Fprintln(GinkgoWriter, "Just beginning") }) Context("inner context", func() { BeforeEach(func() { fmt.Fprintln(GinkgoWriter, "Almost there...") }) It("should hang out for a while", func(ctx SpecContext) { fmt.Fprintln(GinkgoWriter, "Hanging Out") fmt.Println("Sleeping...") select { case <-ctx.Done(): fmt.Println("Got your signal, but still taking a nap") time.Sleep(time.Hour) case <-time.After(time.Hour): } }) AfterEach(func() { fmt.Println("Cleaning up once...") }) }) AfterEach(func() { fmt.Println("Cleaning up twice...") fmt.Println("Sleeping again...") time.Sleep(time.Hour) }) AfterEach(func() { fmt.Println("Cleaning up thrice...") }) }) golang-github-onsi-ginkgo-v2-2.22.0/integration/_fixtures/interceptor_fixture/000077500000000000000000000000001472321612100275315ustar00rootroot00000000000000interceptor_fixture_suite_test.go000066400000000000000000000062371472321612100363650ustar00rootroot00000000000000golang-github-onsi-ginkgo-v2-2.22.0/integration/_fixtures/interceptor_fixturepackage main_test import ( "fmt" "os" "os/exec" "testing" "time" . "github.com/onsi/ginkgo/v2" "github.com/onsi/ginkgo/v2/internal" . "github.com/onsi/gomega" ) func TestInterceptorFixture(t *testing.T) { RegisterFailHandler(Fail) RunSpecs(t, "InterceptorFixture Suite") } var _ = Describe("Ensuring the OutputInterceptor handles the edge case where an external process keeps the interceptor's pipe open", func() { var interceptor internal.OutputInterceptor sharedBehavior := func() { It("can avoid getting stuck, but also doesn't block the external process", func() { interceptor.StartInterceptingOutput() cmd := exec.Command("./interceptor") cmd.Stdout = os.Stdout cmd.Stderr = os.Stderr Ω(cmd.Start()).Should(Succeed()) By("Bailing out because the pipe is stuck") outputChan := make(chan string) go func() { outputChan <- interceptor.StopInterceptingAndReturnOutput() }() var output string Eventually(outputChan, internal.BAILOUT_TIME*2).Should(Receive(&output)) Ω(output).Should(Equal(internal.BAILOUT_MESSAGE)) By("Not subsequently bailing out because the new pipe isn't tied to an external process") interceptor.StartInterceptingOutput() output = interceptor.StopInterceptingAndReturnOutput() Ω(output).ShouldNot(ContainSubstring("STDOUT"), "we're no longer capturing this output") Ω(output).ShouldNot(ContainSubstring("STDERR"), "we're no longer capturing this output") Ω(output).ShouldNot(ContainSubstring(internal.BAILOUT_MESSAGE), "we didn't have to bail out") expected := "" for i := 0; i < 300; i++ { expected += fmt.Sprintf("FILE %d\n", i) } Eventually(func(g Gomega) string { out, err := os.ReadFile("file-output") g.Ω(err).ShouldNot(HaveOccurred()) return string(out) }, 5*time.Second).Should(Equal(expected)) os.Remove("file-output") }) It("works successfully if the user pauses then resumes around starting an external process", func() { interceptor.StartInterceptingOutput() interceptor.PauseIntercepting() cmd := exec.Command("./interceptor") cmd.Stdout = os.Stdout cmd.Stderr = os.Stderr Ω(cmd.Start()).Should(Succeed()) interceptor.ResumeIntercepting() output := interceptor.StopInterceptingAndReturnOutput() Ω(output).ShouldNot(ContainSubstring(internal.BAILOUT_MESSAGE), "we didn't have to bail out") interceptor.StartInterceptingOutput() output = interceptor.StopInterceptingAndReturnOutput() Ω(output).ShouldNot(ContainSubstring(internal.BAILOUT_MESSAGE), "we still didn't have to bail out") expected := "" for i := 0; i < 300; i++ { expected += fmt.Sprintf("FILE %d\n", i) } Eventually(func(g Gomega) string { out, err := os.ReadFile("file-output") g.Ω(err).ShouldNot(HaveOccurred()) return string(out) }, 5*time.Second).Should(Equal(expected)) os.Remove("file-output") }) } Context("the dup2 interceptor", func() { BeforeEach(func() { interceptor = internal.NewOutputInterceptor() }) sharedBehavior() }) Context("the global reassigning interceptor", func() { BeforeEach(func() { interceptor = internal.NewOSGlobalReassigningOutputInterceptor() }) sharedBehavior() }) }) golang-github-onsi-ginkgo-v2-2.22.0/integration/_fixtures/interceptor_fixture/main.go000066400000000000000000000004461472321612100310100ustar00rootroot00000000000000package main import ( "fmt" "os" "time" ) func main() { f, _ := os.Create("file-output") for i := 0; i < 300; i++ { fmt.Fprintf(os.Stdout, "STDOUT %d\n", i) fmt.Fprintf(os.Stderr, "STDERR %d\n", i) fmt.Fprintf(f, "FILE %d\n", i) time.Sleep(10 * time.Millisecond) } f.Close() } golang-github-onsi-ginkgo-v2-2.22.0/integration/_fixtures/interceptor_sleep_fixture/000077500000000000000000000000001472321612100307215ustar00rootroot00000000000000interceptor_fixture_suite_test.go000066400000000000000000000011041472321612100375410ustar00rootroot00000000000000golang-github-onsi-ginkgo-v2-2.22.0/integration/_fixtures/interceptor_sleep_fixturepackage main_test import ( "fmt" "os" "os/exec" "testing" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" ) func TestInterceptorSleepFixture(t *testing.T) { RegisterFailHandler(Fail) RunSpecs(t, "TestInterceptorSleepFixture Suite") } var _ = Describe("Ensuring that ginkgo -p does not hang when output is intercepted", func() { It("ginkgo -p should not hang on this spec", func() { fmt.Fprintln(os.Stdout, "Some STDOUT output") fmt.Fprintln(os.Stderr, "Some STDERR output") cmd := exec.Command("sleep", "60") Ω(cmd.Start()).Should(Succeed()) }) }) golang-github-onsi-ginkgo-v2-2.22.0/integration/_fixtures/labels_fixture/000077500000000000000000000000001472321612100264355ustar00rootroot00000000000000golang-github-onsi-ginkgo-v2-2.22.0/integration/_fixtures/labels_fixture/labels.go000066400000000000000000000001471472321612100302300ustar00rootroot00000000000000package labels var A = Labels("never see") func Labels(labels ...string) []string { return labels } labels_fixture_suite_test.go000066400000000000000000000003771472321612100341740ustar00rootroot00000000000000golang-github-onsi-ginkgo-v2-2.22.0/integration/_fixtures/labels_fixturepackage labels_fixture_test import ( "testing" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" ) func TestLabelsFixture(t *testing.T) { RegisterFailHandler(Fail) RunSpecs(t, "LabelsFixture Suite") } var set1 = Label("dog", "cat", "cow") golang-github-onsi-ginkgo-v2-2.22.0/integration/_fixtures/labels_fixture/labels_fixture_test.go000066400000000000000000000005721472321612100330370ustar00rootroot00000000000000package labels_fixture_test import ( . "github.com/onsi/ginkgo/v2" ) var _ = Describe("LabelsFixture", set1, Label("chicken"), func() { It("works", Label("monkey", "bird"), func() { }) DescribeTable("More Labels", Label("koala"), func(_ int) {}, Entry("J", Label("beluga"), 9), Entry("A", Label("panda", "owl"), 7), Entry("C", Label("otter", "giraffe"), 5), ) }) golang-github-onsi-ginkgo-v2-2.22.0/integration/_fixtures/labels_fixture/nolabels/000077500000000000000000000000001472321612100302345ustar00rootroot00000000000000nolabels_suite_test.go000066400000000000000000000003101472321612100345450ustar00rootroot00000000000000golang-github-onsi-ginkgo-v2-2.22.0/integration/_fixtures/labels_fixture/nolabelspackage nolabels_test import ( "testing" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" ) func TestNolabels(t *testing.T) { RegisterFailHandler(Fail) RunSpecs(t, "Nolabels Suite") } golang-github-onsi-ginkgo-v2-2.22.0/integration/_fixtures/labels_fixture/nolabels/nolabels_test.go000066400000000000000000000001541472321612100334210ustar00rootroot00000000000000package nolabels_test import ( . "github.com/onsi/ginkgo/v2" ) var _ = Describe("Nolabels", func() { }) golang-github-onsi-ginkgo-v2-2.22.0/integration/_fixtures/labels_fixture/onepkg/000077500000000000000000000000001472321612100277205ustar00rootroot00000000000000golang-github-onsi-ginkgo-v2-2.22.0/integration/_fixtures/labels_fixture/onepkg/onepkg.go000066400000000000000000000000401472321612100315240ustar00rootroot00000000000000package onepkg func Stuff() {} golang-github-onsi-ginkgo-v2-2.22.0/integration/_fixtures/labels_fixture/onepkg/onepkg_suite_test.go000066400000000000000000000003441472321612100340030ustar00rootroot00000000000000package onepkg import ( "testing" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" ) func TestOnepkg(t *testing.T) { RegisterFailHandler(Fail) RunSpecs(t, "Onepkg Suite") } var set1 = Label("dog", "cat", "cow") golang-github-onsi-ginkgo-v2-2.22.0/integration/_fixtures/labels_fixture/onepkg/onepkg_test.go000066400000000000000000000005461472321612100325760ustar00rootroot00000000000000package onepkg import ( . "github.com/onsi/ginkgo/v2" ) var _ = Describe("OnePkg", set1, Label("chicken"), func() { It("works", Label("monkey", "bird"), func() { }) DescribeTable("More Labels", Label("koala"), func(_ int) {}, Entry("J", Label("beluga"), 9), Entry("A", Label("panda", "owl"), 7), Entry("C", Label("otter", "giraffe"), 5), ) }) golang-github-onsi-ginkgo-v2-2.22.0/integration/_fixtures/large_fixture/000077500000000000000000000000001472321612100262655ustar00rootroot00000000000000golang-github-onsi-ginkgo-v2-2.22.0/integration/_fixtures/large_fixture/large_fixture_suite_test.go000066400000000000000000000005211472321612100337220ustar00rootroot00000000000000package large_fixture_test import ( "fmt" "testing" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" ) func TestLargeFixture(t *testing.T) { RegisterFailHandler(Fail) RunSpecs(t, "LargeFixture Suite") } var _ = Describe("All the Tests", func() { for i := 0; i < 2048; i++ { It(fmt.Sprintf("%d", i), func() {}) } }) golang-github-onsi-ginkgo-v2-2.22.0/integration/_fixtures/malformed_by_fixture/000077500000000000000000000000001472321612100276335ustar00rootroot00000000000000malformed_by_fixture_suite_test.go000066400000000000000000000005161472321612100365630ustar00rootroot00000000000000golang-github-onsi-ginkgo-v2-2.22.0/integration/_fixtures/malformed_by_fixturepackage malformed_fixture_test import ( "testing" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" ) func TestMalformedFixture(t *testing.T) { RegisterFailHandler(Fail) RunSpecs(t, "MalformedFixture Suite") } var _ = Describe("Malformed by...", func() { By("right here...") It("should never run", func() { }) }) golang-github-onsi-ginkgo-v2-2.22.0/integration/_fixtures/malformed_fixture/000077500000000000000000000000001472321612100271415ustar00rootroot00000000000000malformed_fixture_suite_test.go000066400000000000000000000003411472321612100353730ustar00rootroot00000000000000golang-github-onsi-ginkgo-v2-2.22.0/integration/_fixtures/malformed_fixturepackage malformed_fixture_test import ( "testing" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" ) func TestMalformedFixture(t *testing.T) { RegisterFailHandler(Fail) RunSpecs(t, "MalformedFixture Suite") } malformed_fixture_test.go000066400000000000000000000003621472321612100341650ustar00rootroot00000000000000golang-github-onsi-ginkgo-v2-2.22.0/integration/_fixtures/malformed_fixturepackage malformed_fixture_test import ( . "github.com/onsi/ginkgo/v2" ) var _ = Describe("MalformedFixture", func() { It("tries to install a container within an It...", func() { Context("...which is not allowed!", func() { }) }) }) golang-github-onsi-ginkgo-v2-2.22.0/integration/_fixtures/more_ginkgo_tests_fixture/000077500000000000000000000000001472321612100307155ustar00rootroot00000000000000more_ginkgo_tests.go000066400000000000000000000001031472321612100347010ustar00rootroot00000000000000golang-github-onsi-ginkgo-v2-2.22.0/integration/_fixtures/more_ginkgo_tests_fixturepackage more_ginkgo_tests func AlwaysTrue() bool { return true } more_ginkgo_tests_suite_test.go000066400000000000000000000003431472321612100371570ustar00rootroot00000000000000golang-github-onsi-ginkgo-v2-2.22.0/integration/_fixtures/more_ginkgo_tests_fixturepackage more_ginkgo_tests_test import ( . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" "testing" ) func TestMore_ginkgo_tests(t *testing.T) { RegisterFailHandler(Fail) RunSpecs(t, "More_ginkgo_tests Suite") } more_ginkgo_tests_test.go000066400000000000000000000010221472321612100357410ustar00rootroot00000000000000golang-github-onsi-ginkgo-v2-2.22.0/integration/_fixtures/more_ginkgo_tests_fixturepackage more_ginkgo_tests_test import ( "testing" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/ginkgo/v2/integration/_fixtures/more_ginkgo_tests_fixture" . "github.com/onsi/gomega" ) var _ = Describe("MoreGinkgoTests", func() { It("should pass", func() { Ω(AlwaysTrue()).Should(BeTrue()) }) It("should always pass", func() { Ω(AlwaysTrue()).Should(BeTrue()) }) It("should match testing.TB", func() { var tbFunc = func(_ testing.TB) { Ω(AlwaysTrue()).Should(BeTrue()) } tbFunc(GinkgoTB()) }) }) golang-github-onsi-ginkgo-v2-2.22.0/integration/_fixtures/no_tagged_tests_fixture/000077500000000000000000000000001472321612100303445ustar00rootroot00000000000000golang-github-onsi-ginkgo-v2-2.22.0/integration/_fixtures/no_tagged_tests_fixture/doc.go000066400000000000000000000000351472321612100314360ustar00rootroot00000000000000package notaggedtestsfixture no_tagged_tests_fixture_suite_test.go000066400000000000000000000004001472321612100377750ustar00rootroot00000000000000golang-github-onsi-ginkgo-v2-2.22.0/integration/_fixtures/no_tagged_tests_fixture//+build integration package notaggedtestsfixture import ( "testing" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" ) func TestNoTaggedTestsFixture(t *testing.T) { RegisterFailHandler(Fail) RunSpecs(t, "No Tagged Tests Fixture Suite") } golang-github-onsi-ginkgo-v2-2.22.0/integration/_fixtures/no_test_fn_fixture/000077500000000000000000000000001472321612100273315ustar00rootroot00000000000000golang-github-onsi-ginkgo-v2-2.22.0/integration/_fixtures/no_test_fn_fixture/no_test_fn.go000066400000000000000000000001071472321612100320140ustar00rootroot00000000000000package no_test_fn func StringIdentity(a string) string { return a } golang-github-onsi-ginkgo-v2-2.22.0/integration/_fixtures/no_test_fn_fixture/no_test_fn_test.go000066400000000000000000000004531472321612100330570ustar00rootroot00000000000000package no_test_fn_test import ( . "github.com/onsi/ginkgo/v2" . "github.com/onsi/ginkgo/v2/integration/_fixtures/no_test_fn_fixture" . "github.com/onsi/gomega" ) var _ = Describe("NoTestFn", func() { It("should proxy strings", func() { Ω(StringIdentity("foo")).Should(Equal("foo")) }) }) golang-github-onsi-ginkgo-v2-2.22.0/integration/_fixtures/no_tests_fixture/000077500000000000000000000000001472321612100270315ustar00rootroot00000000000000golang-github-onsi-ginkgo-v2-2.22.0/integration/_fixtures/no_tests_fixture/no_tests.go000066400000000000000000000000361472321612100312150ustar00rootroot00000000000000package main func main() { } golang-github-onsi-ginkgo-v2-2.22.0/integration/_fixtures/nondeterministic_fixture/000077500000000000000000000000001472321612100305515ustar00rootroot00000000000000golang-github-onsi-ginkgo-v2-2.22.0/integration/_fixtures/nondeterministic_fixture/file_a_test.go000066400000000000000000000004201472321612100333520ustar00rootroot00000000000000package nondeterministic_fixture_test import ( . "github.com/onsi/ginkgo/v2" ) var cheat = func() { It("in", func() { }) It("order", func() { }) } var _ = Describe("ordered", Ordered, func() { It("always", func() { }) It("runs", func() { }) cheat() }) golang-github-onsi-ginkgo-v2-2.22.0/integration/_fixtures/nondeterministic_fixture/file_b_test.go000066400000000000000000000011771472321612100333650ustar00rootroot00000000000000package nondeterministic_fixture_test import ( . "github.com/onsi/ginkgo/v2" ) var specGenerator = map[string]bool{ "map-A": true, "map-B": true, "map-C": true, "map-D": true, "map-E": true, "map-F": true, "map-G": true, "map-H": true, "map-I": true, "map-J": true, } var _ = Describe("some tests, that include a test generated by iterating over a map", func() { Describe("some tests", func() { It("runs A", func() { }) It("runs B", func() { }) }) When("iterating over a map", func() { for key := range specGenerator { It("runs "+key, func() { }) } }) It("runs some other tests", func() { }) }) nondeterministic_fixture_suite_test.go000066400000000000000000000016551472321612100404240ustar00rootroot00000000000000golang-github-onsi-ginkgo-v2-2.22.0/integration/_fixtures/nondeterministic_fixturepackage nondeterministic_fixture_test import ( "strings" "testing" . "github.com/onsi/ginkgo/v2" "github.com/onsi/ginkgo/v2/types" . "github.com/onsi/gomega" ) func TestNondeterministicFixture(t *testing.T) { RegisterFailHandler(Fail) RunSpecs(t, "NondeterministicFixture Suite") } var _ = ReportAfterSuite("ensure all specs ran correctly", func(report types.Report) { specs := report.SpecReports.WithLeafNodeType(types.NodeTypeIt) orderedTexts := []string{} textCounts := map[string]int{} for _, spec := range specs { text := spec.FullText() textCounts[text] += 1 if strings.HasPrefix(text, "ordered") { orderedTexts = append(orderedTexts, spec.LeafNodeText) } } By("ensuring there are no duplicates") for text, count := range textCounts { Ω(count).Should(Equal(1), text) } By("ensuring ordered specs are strictly preserved") Ω(orderedTexts).Should(Equal([]string{"always", "runs", "in", "order"})) }) golang-github-onsi-ginkgo-v2-2.22.0/integration/_fixtures/ordered_fixture/000077500000000000000000000000001472321612100266175ustar00rootroot00000000000000ordered_fixture_suite_test.go000066400000000000000000000034601472321612100345340ustar00rootroot00000000000000golang-github-onsi-ginkgo-v2-2.22.0/integration/_fixtures/ordered_fixturepackage ordered_fixture_test import ( "flag" "testing" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" ) var noOrdered *bool func init() { noOrdered = flag.CommandLine.Bool("no-ordered", false, "set to turn off ordered decoration") } var OrderedDecoration = []interface{}{Ordered} func TestOrderedFixture(t *testing.T) { RegisterFailHandler(Fail) if *noOrdered { OrderedDecoration = []interface{}{} } RunSpecs(t, "OrderedFixture Suite") } var _ = Describe("tests", func() { for i := 0; i < 10; i += 1 { Context("ordered", OrderedDecoration, func() { var terribleSharedCounter int var parallelNode int BeforeAll(func() { terribleSharedCounter = 1 parallelNode = GinkgoParallelProcess() }) It("increments the counter", func() { Ω(parallelNode).Should(Equal(GinkgoParallelProcess())) terribleSharedCounter++ Ω(terribleSharedCounter).Should(Equal(2)) }) It("increments the shared counter", func() { Ω(parallelNode).Should(Equal(GinkgoParallelProcess())) terribleSharedCounter++ Ω(terribleSharedCounter).Should(Equal(3)) }) It("increments the terrible shared counter", func() { Ω(parallelNode).Should(Equal(GinkgoParallelProcess())) terribleSharedCounter++ Ω(terribleSharedCounter).Should(Equal(4)) }) It("increments the counter again", func() { Ω(parallelNode).Should(Equal(GinkgoParallelProcess())) terribleSharedCounter++ Ω(terribleSharedCounter).Should(Equal(5)) }) It("increments the counter and again", func() { Ω(parallelNode).Should(Equal(GinkgoParallelProcess())) terribleSharedCounter++ Ω(terribleSharedCounter).Should(Equal(6)) }) AfterAll(func() { Ω(parallelNode).Should(Equal(GinkgoParallelProcess())) Ω(terribleSharedCounter).Should(Equal(6)) }) }) } }) golang-github-onsi-ginkgo-v2-2.22.0/integration/_fixtures/passing_ginkgo_tests_fixture/000077500000000000000000000000001472321612100314175ustar00rootroot00000000000000passing_ginkgo_tests.go000066400000000000000000000002001472321612100361030ustar00rootroot00000000000000golang-github-onsi-ginkgo-v2-2.22.0/integration/_fixtures/passing_ginkgo_tests_fixturepackage passing_ginkgo_tests func StringIdentity(a string) string { return a } func IntegerIdentity(a int) int { return a } passing_ginkgo_tests_suite_test.go000066400000000000000000000003541472321612100403650ustar00rootroot00000000000000golang-github-onsi-ginkgo-v2-2.22.0/integration/_fixtures/passing_ginkgo_tests_fixturepackage passing_ginkgo_tests_test import ( . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" "testing" ) func TestPassing_ginkgo_tests(t *testing.T) { RegisterFailHandler(Fail) RunSpecs(t, "Passing_ginkgo_tests Suite") } passing_ginkgo_tests_test.go000066400000000000000000000017301472321612100371530ustar00rootroot00000000000000golang-github-onsi-ginkgo-v2-2.22.0/integration/_fixtures/passing_ginkgo_tests_fixturepackage passing_ginkgo_tests_test import ( "sync" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/ginkgo/v2/integration/_fixtures/passing_ginkgo_tests_fixture" . "github.com/onsi/gomega" ) var _ = Describe("PassingGinkgoTests", func() { It("should proxy strings", func() { Ω(StringIdentity("foo")).Should(Equal("foo")) }) It("should proxy integers", func() { Ω(IntegerIdentity(3)).Should(Equal(3)) }) It("should do it again", func() { Ω(StringIdentity("foo")).Should(Equal("foo")) Ω(IntegerIdentity(3)).Should(Equal(3)) }) It("should be able to run Bys", func() { By("emitting one By") Ω(3).Should(Equal(3)) By("emitting another By") Ω(4).Should(Equal(4)) }) Context("when called within goroutines", func() { It("does not trigger the race detector", func() { wg := &sync.WaitGroup{} wg.Add(3) for i := 0; i < 3; i += 1 { go func() { By("avoiding the race detector") wg.Done() }() } wg.Wait() }) }) }) golang-github-onsi-ginkgo-v2-2.22.0/integration/_fixtures/pause_resume_interception_fixture/000077500000000000000000000000001472321612100324535ustar00rootroot00000000000000pause_resume_interception_fixture_suite_test.go000066400000000000000000000007561472321612100442310ustar00rootroot00000000000000golang-github-onsi-ginkgo-v2-2.22.0/integration/_fixtures/pause_resume_interception_fixturepackage pause_resume_interception_fixture_test import ( "fmt" "testing" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" ) func TestPauseResumeInterceptionFixture(t *testing.T) { RegisterFailHandler(Fail) RunSpecs(t, "PauseResumeInterceptionFixture Suite") } var _ = It("can pause and resume interception", func() { fmt.Println("CAPTURED OUTPUT A") PauseOutputInterception() fmt.Println("OUTPUT TO CONSOLE") ResumeOutputInterception() fmt.Println("CAPTURED OUTPUT B") }) golang-github-onsi-ginkgo-v2-2.22.0/integration/_fixtures/preview_fixture/000077500000000000000000000000001472321612100266545ustar00rootroot00000000000000preview_fixture_suite_test.go000066400000000000000000000012321472321612100346210ustar00rootroot00000000000000golang-github-onsi-ginkgo-v2-2.22.0/integration/_fixtures/preview_fixturepackage preview_fixture_test import ( "fmt" "os" "testing" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" ) func TestPreviewFixture(t *testing.T) { RegisterFailHandler(Fail) if os.Getenv("PREVIEW") == "true" { report := PreviewSpecs("PreviewFixture Suite", Label("suite-label")) for _, spec := range report.SpecReports { fmt.Println(spec.State, spec.FullText()) } } if os.Getenv("RUN") == "true" { RunSpecs(t, "PreviewFixture Suite", Label("suite-label")) } } var _ = Describe("specs", func() { It("A", Label("elephant"), func() { }) It("B", Label("elephant"), func() { }) It("C", func() { }) It("D", func() { }) }) golang-github-onsi-ginkgo-v2-2.22.0/integration/_fixtures/profile_fixture/000077500000000000000000000000001472321612100266335ustar00rootroot00000000000000golang-github-onsi-ginkgo-v2-2.22.0/integration/_fixtures/profile_fixture/block_contest/000077500000000000000000000000001472321612100314645ustar00rootroot00000000000000block_contest.go000066400000000000000000000001601472321612100345620ustar00rootroot00000000000000golang-github-onsi-ginkgo-v2-2.22.0/integration/_fixtures/profile_fixture/block_contestpackage block_contest func ReadTheChannel(c chan bool) { <-c } func SlowReadTheChannel(c chan bool) { <-c } block_contest_suite_test.go000066400000000000000000000014231472321612100370350ustar00rootroot00000000000000golang-github-onsi-ginkgo-v2-2.22.0/integration/_fixtures/profile_fixture/block_contestpackage block_contest_test import ( "testing" "time" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" "github.com/onsi/ginkgo/v2/integration/_fixtures/profile_fixture/block_contest" ) func TestBlockContest(t *testing.T) { RegisterFailHandler(Fail) RunSpecs(t, "BlockContest Suite") } var _ = Describe("quick channel reads", func() { for i := 0; i < 10; i++ { It("reads a channel quickly", func() { c := make(chan bool) go func() { block_contest.ReadTheChannel(c) }() time.Sleep(5 * time.Millisecond) c <- true }) } }) var _ = Describe("slow channel reads", func() { It("gets stuck for a bit", func() { c := make(chan bool) go func() { block_contest.SlowReadTheChannel(c) }() time.Sleep(500 * time.Millisecond) c <- true }) }) golang-github-onsi-ginkgo-v2-2.22.0/integration/_fixtures/profile_fixture/lock_contest/000077500000000000000000000000001472321612100313225ustar00rootroot00000000000000lock_contest.go000066400000000000000000000002431472321612100342600ustar00rootroot00000000000000golang-github-onsi-ginkgo-v2-2.22.0/integration/_fixtures/profile_fixture/lock_contestpackage lock_contest import ( "sync" ) func WaitForLock(l *sync.Mutex) { l.Lock() l.Unlock() } func SlowWaitForLock(l *sync.Mutex) { l.Lock() l.Unlock() } lock_contest_suite_test.go000066400000000000000000000015611472321612100365340ustar00rootroot00000000000000golang-github-onsi-ginkgo-v2-2.22.0/integration/_fixtures/profile_fixture/lock_contestpackage lock_contest_test import ( "sync" "testing" "time" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" "github.com/onsi/ginkgo/v2/integration/_fixtures/profile_fixture/lock_contest" ) func TestLockContest(t *testing.T) { RegisterFailHandler(Fail) RunSpecs(t, "LockContest Suite") } var _ = Describe("quick lock blocks", func() { for i := 0; i < 10; i++ { It("reads a lock quickly", func() { c := make(chan bool) l := &sync.Mutex{} l.Lock() go func() { lock_contest.WaitForLock(l) close(c) }() time.Sleep(5 * time.Millisecond) l.Unlock() <-c }) } }) var _ = Describe("slow lock block", func() { It("gets stuck for a bit", func() { c := make(chan bool) l := &sync.Mutex{} l.Lock() go func() { lock_contest.SlowWaitForLock(l) close(c) }() time.Sleep(500 * time.Millisecond) l.Unlock() <-c }) }) golang-github-onsi-ginkgo-v2-2.22.0/integration/_fixtures/profile_fixture/slow_memory_hog/000077500000000000000000000000001472321612100320445ustar00rootroot00000000000000slow_memory_hog.go000066400000000000000000000005641472321612100355320ustar00rootroot00000000000000golang-github-onsi-ginkgo-v2-2.22.0/integration/_fixtures/profile_fixture/slow_memory_hogpackage slow_memory_hog import "strings" func SomethingExpensive(n int) string { pirate := []string{"r"} for i := 0; i < n; i++ { pirate = doubleArrrr(pirate) } return strings.Join(pirate, "\n") } func doubleArrrr(in []string) []string { out := make([]string, len(in)*2) for j := 0; j < len(in); j++ { out[j] = in[j] out[len(in)+j] = in[j] } return out } slow_memory_hog_suite_test.go000066400000000000000000000010321472321612100377710ustar00rootroot00000000000000golang-github-onsi-ginkgo-v2-2.22.0/integration/_fixtures/profile_fixture/slow_memory_hogpackage slow_memory_hog_test import ( "testing" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" "github.com/onsi/ginkgo/v2/integration/_fixtures/profile_fixture/slow_memory_hog" ) func TestSlowMemoryHog(t *testing.T) { RegisterFailHandler(Fail) RunSpecs(t, "SlowMemoryHog Suite") } var _ = It("grows", func() { slow_memory_hog.SomethingExpensive(4) }) var _ = It("grows a lot", func() { slow_memory_hog.SomethingExpensive(17) }) var _ = It("grows way too much", func() { slow_memory_hog.SomethingExpensive(23) }) golang-github-onsi-ginkgo-v2-2.22.0/integration/_fixtures/progress_report_fixture/000077500000000000000000000000001472321612100304325ustar00rootroot00000000000000progress_report_fixture_suite_test.go000066400000000000000000000003611472321612100401570ustar00rootroot00000000000000golang-github-onsi-ginkgo-v2-2.22.0/integration/_fixtures/progress_report_fixturepackage progress_report_fixture_test import ( "testing" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" ) func TestProgressReportFixture(t *testing.T) { RegisterFailHandler(Fail) RunSpecs(t, "ProgressReportFixture Suite") } progress_report_test.go000066400000000000000000000014761472321612100352100ustar00rootroot00000000000000golang-github-onsi-ginkgo-v2-2.22.0/integration/_fixtures/progress_report_fixturepackage progress_report_fixture_test import ( "fmt" "os" "time" . "github.com/onsi/ginkgo/v2" ) var _ = Describe("ProgressReport", func() { BeforeEach(func() { DeferCleanup(AttachProgressReporter(func() string { return fmt.Sprintf("Some global information: %d", GinkgoParallelProcess()) })) }) It("can track on demand", func() { By("Step A") By("Step B") for i := 1; i <= 12; i++ { GinkgoWriter.Printf("ginkgo-writer-output-%d\n", i) } fmt.Printf("READY %d\n", os.Getpid()) time.Sleep(time.Second) }) It("--poll-progress-after tracks things that take too long", Label("parallel"), func() { time.Sleep(2 * time.Second) }) It("decorator tracks things that take too long", Label("parallel", "one-second"), func() { time.Sleep(1 * time.Second) }, PollProgressAfter(500*time.Millisecond)) }) golang-github-onsi-ginkgo-v2-2.22.0/integration/_fixtures/report_entries_fixture/000077500000000000000000000000001472321612100302375ustar00rootroot00000000000000report_entries_fixture_suite_test.go000066400000000000000000000031131472321612100375670ustar00rootroot00000000000000golang-github-onsi-ginkgo-v2-2.22.0/integration/_fixtures/report_entries_fixturepackage report_entries_fixture_test import ( "fmt" "testing" "time" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" ) func TestReportEntriesFixture(t *testing.T) { RegisterFailHandler(Fail) RunSpecs(t, "ReportEntriesFixture Suite") } type StringerStruct struct { Label string Count int } func (s StringerStruct) ColorableString() string { return fmt.Sprintf("{{red}}%s {{green}}%d{{/}}", s.Label, s.Count) } var _ = Describe("top-level container", func() { var s *StringerStruct BeforeEach(func() { s = &StringerStruct{ Label: "placeholder", Count: 10, } }) It("passes", func() { AddReportEntry("passes-first-report", StringerStruct{"pass-bob", 1}) AddReportEntry("passes-second-report") AddReportEntry("passes-third-report", 3) AddReportEntry("passes-pointer-report", s) AddReportEntry("passes-failure-report", 5, ReportEntryVisibilityFailureOrVerbose) AddReportEntry("passes-never-see-report", 6, ReportEntryVisibilityNever) }) It("fails", func() { AddReportEntry("fails-first-report", StringerStruct{"fail-bob", 1}) AddReportEntry("fails-second-report") AddReportEntry("fails-third-report", 3) AddReportEntry("fails-pointer-report", s) AddReportEntry("fails-failure-report", 5, ReportEntryVisibilityFailureOrVerbose) AddReportEntry("fails-never-see-report", 6, ReportEntryVisibilityNever) Fail("boom") }) It("has By entries", func() { By("registers a By event") By("includes durations", func() { time.Sleep(time.Millisecond * 100) }) }) AfterEach(func() { s.Label = CurrentSpecReport().State.String() s.Count = 4 }) }) golang-github-onsi-ginkgo-v2-2.22.0/integration/_fixtures/reporting_fixture/000077500000000000000000000000001472321612100272045ustar00rootroot00000000000000golang-github-onsi-ginkgo-v2-2.22.0/integration/_fixtures/reporting_fixture/malformed_sub_package/000077500000000000000000000000001472321612100334765ustar00rootroot00000000000000malformed_sub_package_suite_test.go000066400000000000000000000003761472321612100425160ustar00rootroot00000000000000golang-github-onsi-ginkgo-v2-2.22.0/integration/_fixtures/reporting_fixture/malformed_sub_packagepackage malformed_sub_package_test import ( "testing" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" ) func TestMalformedSubPackage(t *testing.T) { RegisterFailHandler(Fail) RunSpecs(t, "MalformedSubPackage Suite") } NO_COMPILE.FORYOU!golang-github-onsi-ginkgo-v2-2.22.0/integration/_fixtures/reporting_fixture/nonginkgo_sub_package/000077500000000000000000000000001472321612100335215ustar00rootroot00000000000000a_test.go000066400000000000000000000001241472321612100352450ustar00rootroot00000000000000golang-github-onsi-ginkgo-v2-2.22.0/integration/_fixtures/reporting_fixture/nonginkgo_sub_packagepackage nonginkgo_sub_package_test import "testing" func TestA(t *testing.T) { } reporting_fixture_suite_test.go000066400000000000000000000035701472321612100355100ustar00rootroot00000000000000golang-github-onsi-ginkgo-v2-2.22.0/integration/_fixtures/reporting_fixturepackage reporting_fixture_test import ( "fmt" "os" "testing" . "github.com/onsi/ginkgo/v2" "github.com/onsi/ginkgo/v2/types" . "github.com/onsi/gomega" ) func TestReportingFixture(t *testing.T) { RegisterFailHandler(Fail) RunSpecs(t, "ReportingFixture Suite") } var beforeEachReport, afterEachReport *os.File var _ = ReportBeforeSuite(func(report Report) { f, err := os.Create(fmt.Sprintf("report-before-suite-%d.out", GinkgoParallelProcess())) Ω(err).ShouldNot(HaveOccurred()) fmt.Fprintf(f, "%s - %d\n", report.SuiteDescription, report.SuiteConfig.RandomSeed) fmt.Fprintf(f, "%d of %d", report.PreRunStats.SpecsThatWillRun, report.PreRunStats.TotalSpecs) f.Close() }) var _ = BeforeSuite(func() { var err error beforeEachReport, err = os.Create("report-before-each.out") Ω(err).ShouldNot(HaveOccurred()) DeferCleanup(beforeEachReport.Close) afterEachReport, err = os.Create("report-after-each.out") Ω(err).ShouldNot(HaveOccurred()) DeferCleanup(afterEachReport.Close) }) var _ = ReportBeforeEach(func(report SpecReport) { fmt.Fprintf(beforeEachReport, "%s - %s\n", report.LeafNodeText, report.State) }) var _ = ReportAfterEach(func(report SpecReport) { fmt.Fprintf(afterEachReport, "%s - %s\n", report.LeafNodeText, report.State) }) var _ = ReportAfterSuite("my report", func(report Report) { f, err := os.Create("report-after-suite.out") Ω(err).ShouldNot(HaveOccurred()) fmt.Fprintf(f, "%s - %d\n", report.SuiteDescription, report.SuiteConfig.RandomSeed) for _, specReport := range report.SpecReports { if specReport.LeafNodeType.Is(types.NodeTypesForSuiteLevelNodes) || specReport.LeafNodeType.Is(types.NodeTypeCleanupAfterSuite) { fmt.Fprintf(f, "%d: [%s] - %s\n", specReport.ParallelProcess, specReport.LeafNodeType, specReport.State) } else { fmt.Fprintf(f, "%s - %s\n", specReport.LeafNodeText, specReport.State) } } f.Close() Fail("fail!") }) reporting_fixture_test.go000066400000000000000000000016221472321612100342730ustar00rootroot00000000000000golang-github-onsi-ginkgo-v2-2.22.0/integration/_fixtures/reporting_fixturepackage reporting_fixture_test import ( "time" . "github.com/onsi/ginkgo/v2" ) var _ = Describe("reporting test", func() { It("passes", func() { }) Describe("labelled tests", Label("dog"), func() { It("is labelled", Label("dog", "cat"), func() { }) }) It("fails", func() { GinkgoWriter.Print("some ginkgo-writer output") Fail("fail!") }) It("panics", func() { panic("boom") }) It("has a progress report", func() { GinkgoWriter.Print("some ginkgo-writer preamble") time.Sleep(300 * time.Millisecond) GinkgoWriter.Print("some ginkgo-writer postamble") }, PollProgressAfter(50*time.Millisecond)) PIt("is pending", func() { }) It("is skipped", func() { Skip("skip") }) It("times out and fails during cleanup", func(ctx SpecContext) { <-ctx.Done() DeferCleanup(func() { Fail("double-whammy") }) Fail("failure-after-timeout") }, NodeTimeout(time.Millisecond*100)) }) golang-github-onsi-ginkgo-v2-2.22.0/integration/_fixtures/reporting_fixture/reporting_sub_package/000077500000000000000000000000001472321612100335415ustar00rootroot00000000000000reporting_sub_package_suite_test.go000066400000000000000000000003541472321612100426200ustar00rootroot00000000000000golang-github-onsi-ginkgo-v2-2.22.0/integration/_fixtures/reporting_fixture/reporting_sub_packagepackage reporting_sub_package_test import ( "testing" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" ) func TestReportingSubPackage(t *testing.T) { RegisterFailHandler(Fail) RunSpecs(t, "Reporting SubPackage Suite") } reporting_sub_package_test.go000066400000000000000000000004671472321612100414140ustar00rootroot00000000000000golang-github-onsi-ginkgo-v2-2.22.0/integration/_fixtures/reporting_fixture/reporting_sub_packagepackage reporting_sub_package_test import ( "fmt" . "github.com/onsi/ginkgo/v2" ) var _ = Describe("ReportingSubPackage", func() { It("passes here too", func() { }) It("fails here too", func() { fmt.Print("some std output") Fail("fail!") }) It("panics here too", func() { panic("bam!") }) }) golang-github-onsi-ginkgo-v2-2.22.0/integration/_fixtures/rerun_specs_fixture/000077500000000000000000000000001472321612100275235ustar00rootroot00000000000000rerun_specs_suite_test.go000066400000000000000000000004541472321612100345760ustar00rootroot00000000000000golang-github-onsi-ginkgo-v2-2.22.0/integration/_fixtures/rerun_specs_fixturepackage rerun_specs_fixture_test import ( "testing" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" ) func TestRerunSpecs(t *testing.T) { RegisterFailHandler(Fail) RunSpecs(t, "RerunSpecs Suite") RunSpecs(t, "RerunSpecs Suite - part deux") } var _ = It("tries twice", func() { }) golang-github-onsi-ginkgo-v2-2.22.0/integration/_fixtures/seed_fixture/000077500000000000000000000000001472321612100261135ustar00rootroot00000000000000golang-github-onsi-ginkgo-v2-2.22.0/integration/_fixtures/seed_fixture/seed_fixture_suite_test.go000066400000000000000000000006201472321612100333760ustar00rootroot00000000000000package seed_fixture_test import ( "testing" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" ) func TestSeedFixture(t *testing.T) { RegisterFailHandler(Fail) RunSpecs(t, "SeedFixture Suite") } var _ = BeforeSuite(func() { Ω(GinkgoRandomSeed()).Should(Equal(int64(0))) }) var _ = It("has the expected seed (namely, 0)", func() { Ω(GinkgoRandomSeed()).Should(Equal(int64(0))) }) golang-github-onsi-ginkgo-v2-2.22.0/integration/_fixtures/serial_fixture/000077500000000000000000000000001472321612100264525ustar00rootroot00000000000000serial_fixture_suite_test.go000066400000000000000000000046731472321612100342310ustar00rootroot00000000000000golang-github-onsi-ginkgo-v2-2.22.0/integration/_fixtures/serial_fixturepackage serial_fixture_test import ( "flag" "fmt" "io" "net/http" "sync" "testing" "time" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" "github.com/onsi/gomega/ghttp" ) var noSerial *bool func init() { noSerial = flag.CommandLine.Bool("no-serial", false, "set to turn off serial decoration") } var SerialDecoration = []interface{}{Serial} func TestSerialFixture(t *testing.T) { RegisterFailHandler(Fail) if *noSerial { SerialDecoration = []interface{}{} } RunSpecs(t, "SerialFixture Suite") } var addr string var _ = SynchronizedBeforeSuite(func() []byte { server := ghttp.NewServer() lock := &sync.Mutex{} count := 0 server.RouteToHandler("POST", "/counter", func(w http.ResponseWriter, r *http.Request) { lock.Lock() count += 1 lock.Unlock() w.WriteHeader(http.StatusOK) }) server.RouteToHandler("GET", "/counter", func(w http.ResponseWriter, r *http.Request) { lock.Lock() data := []byte(fmt.Sprintf("%d", count)) lock.Unlock() w.Write(data) }) return []byte(server.HTTPTestServer.URL) }, func(data []byte) { addr = string(data) + "/counter" }) var postToCounter = func(g Gomega) { req, err := http.Post(addr, "", nil) g.Ω(err).ShouldNot(HaveOccurred()) g.Ω(req.StatusCode).Should(Equal(http.StatusOK)) g.Ω(req.Body.Close()).Should(Succeed()) } var getCounter = func(g Gomega) string { req, err := http.Get(addr) g.Ω(err).ShouldNot(HaveOccurred()) content, err := io.ReadAll(req.Body) g.Ω(err).ShouldNot(HaveOccurred()) g.Ω(req.Body.Close()).Should(Succeed()) return string(content) } var _ = SynchronizedAfterSuite(func() { Consistently(postToCounter, 200*time.Millisecond, 10*time.Millisecond).Should(Succeed()) }, func() { initialValue := getCounter(Default) Consistently(func(g Gomega) { currentValue := getCounter(g) g.Ω(currentValue).Should(Equal(initialValue)) }, 100*time.Millisecond, 10*time.Millisecond).Should(Succeed()) }) var _ = Describe("tests", func() { for i := 0; i < 10; i += 1 { It("runs in parallel", func() { Consistently(postToCounter, 200*time.Millisecond, 10*time.Millisecond).Should(Succeed()) }) } for i := 0; i < 5; i += 1 { It("runs in series", SerialDecoration, func() { Ω(GinkgoParallelProcess()).Should(Equal(1)) initialValue := getCounter(Default) Consistently(func(g Gomega) { currentValue := getCounter(g) g.Ω(currentValue).Should(Equal(initialValue)) }, 100*time.Millisecond, 10*time.Millisecond).Should(Succeed()) }) } }) golang-github-onsi-ginkgo-v2-2.22.0/integration/_fixtures/skip_fixture/000077500000000000000000000000001472321612100261415ustar00rootroot00000000000000golang-github-onsi-ginkgo-v2-2.22.0/integration/_fixtures/skip_fixture/skip_fixture_suite_test.go000066400000000000000000000003241472321612100334530ustar00rootroot00000000000000package skip_fixture_test import ( . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" "testing" ) func TestSkip_fixture(t *testing.T) { RegisterFailHandler(Fail) RunSpecs(t, "Skip_fixture Suite") } golang-github-onsi-ginkgo-v2-2.22.0/integration/_fixtures/skip_fixture/skip_fixture_test.go000066400000000000000000000013751472321612100322510ustar00rootroot00000000000000package skip_fixture_test import ( . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" ) var _ = It("handles top level skips", func() { Skip("a top level skip on line 9") println("NEVER SEE THIS") }) var _ = Describe("Exercising different skip modes", func() { It("synchronous skip", func() { Skip("a sync SKIP") println("NEVER SEE THIS") }) }) var _ = Describe("SKIP in a BeforeEach", func() { BeforeEach(func() { Skip("a BeforeEach SKIP") println("NEVER SEE THIS") }) It("a SKIP BeforeEach", func() { println("NEVER SEE THIS") }) }) var _ = Describe("SKIP in an AfterEach", func() { AfterEach(func() { Skip("an AfterEach SKIP") println("NEVER SEE THIS") }) It("a SKIP AfterEach", func() { Expect(true).To(BeTrue()) }) }) golang-github-onsi-ginkgo-v2-2.22.0/integration/_fixtures/synchronized_setup_tests_fixture/000077500000000000000000000000001472321612100323545ustar00rootroot00000000000000synchronized_setup_tests_suite_test.go000066400000000000000000000020071472321612100422540ustar00rootroot00000000000000golang-github-onsi-ginkgo-v2-2.22.0/integration/_fixtures/synchronized_setup_tests_fixturepackage synchronized_setup_tests_test import ( . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" "fmt" "testing" "time" ) func TestSynchronized_setup_tests(t *testing.T) { RegisterFailHandler(Fail) RunSpecs(t, "Synchronized_setup_tests Suite") } var beforeData string var _ = SynchronizedBeforeSuite(func() []byte { fmt.Printf("BEFORE_A_%d\n", GinkgoParallelProcess()) time.Sleep(100 * time.Millisecond) return []byte("DATA") }, func(data []byte) { fmt.Printf("BEFORE_B_%d: %s\n", GinkgoParallelProcess(), string(data)) beforeData += string(data) + "OTHER" }) var _ = SynchronizedAfterSuite(func() { fmt.Printf("\nAFTER_A_%d\n", GinkgoParallelProcess()) time.Sleep(100 * time.Millisecond) }, func() { fmt.Printf("AFTER_B_%d\n", GinkgoParallelProcess()) }) var _ = Describe("Synchronized Setup", func() { It("should run the before suite once", func() { Ω(beforeData).Should(Equal("DATAOTHER")) }) It("should run the before suite once", func() { Ω(beforeData).Should(Equal("DATAOTHER")) }) }) golang-github-onsi-ginkgo-v2-2.22.0/integration/_fixtures/tags_fixture/000077500000000000000000000000001472321612100261315ustar00rootroot00000000000000golang-github-onsi-ginkgo-v2-2.22.0/integration/_fixtures/tags_fixture/ignored_test.go000066400000000000000000000003421472321612100311450ustar00rootroot00000000000000// +build complex_tests package tags_test import ( . "github.com/onsi/ginkgo/v2" ) var _ = Describe("Ignored", func() { It("should not have these tests", func() { }) It("should not have these tests", func() { }) }) golang-github-onsi-ginkgo-v2-2.22.0/integration/_fixtures/tags_fixture/tags_tests_suite_test.go000066400000000000000000000003061472321612100331070ustar00rootroot00000000000000package tags_test import ( . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" "testing" ) func TestTagsTests(t *testing.T) { RegisterFailHandler(Fail) RunSpecs(t, "TagsTests Suite") } golang-github-onsi-ginkgo-v2-2.22.0/integration/_fixtures/tags_fixture/tags_tests_test.go000066400000000000000000000002201472321612100316710ustar00rootroot00000000000000package tags_test import ( . "github.com/onsi/ginkgo/v2" ) var _ = Describe("TagsTests", func() { It("should have a test", func() { }) }) golang-github-onsi-ginkgo-v2-2.22.0/integration/_fixtures/timeline_fixture/000077500000000000000000000000001472321612100270015ustar00rootroot00000000000000timeline_fixture_suite_test.go000066400000000000000000000027351472321612100351040ustar00rootroot00000000000000golang-github-onsi-ginkgo-v2-2.22.0/integration/_fixtures/timeline_fixturepackage timeline_fixture_test import ( "testing" "time" . "github.com/onsi/ginkgo/v2" "github.com/onsi/ginkgo/v2/types" . "github.com/onsi/gomega" ) func TestTimelineFixture(t *testing.T) { RegisterFailHandler(Fail) RunSpecs(t, "TimelineFixture Suite") } var _ = Describe("a full timeline", Serial, func() { Describe("a flaky test", func() { BeforeEach(func() { By("logging some events") GinkgoWriter.Println("hello!") AddReportEntry("a report!", "Of {{bold}}great{{/}} value") DeferCleanup(func() { By("cleaning up a bit", func() { time.Sleep(time.Millisecond * 50) GinkgoWriter.Println("all done!") }) }) }) i := 0 It("retries a few times", func() { i += 1 GinkgoWriter.Println("let's try...") if i < 3 { Fail("bam!") } GinkgoWriter.Println("hooray!") }, FlakeAttempts(3)) AfterEach(func() { if i == 3 { GinkgoWriter.Println("feeling sleepy...") time.Sleep(time.Millisecond * 200) } }, PollProgressAfter(time.Millisecond*100)) }) Describe("a test with multiple failures", func() { It("times out", func(ctx SpecContext) { By("waiting...") <-ctx.Done() GinkgoWriter.Println("then failing!") Fail("welp") }, NodeTimeout(time.Millisecond*100)) AfterEach(func() { panic("aaah!") }) }) It("passes happily", func() { AddReportEntry("a verbose-only report", types.ReportEntryVisibilityFailureOrVerbose) AddReportEntry("a hidden report", types.ReportEntryVisibilityNever) }) }) golang-github-onsi-ginkgo-v2-2.22.0/integration/_fixtures/timeout_fixture/000077500000000000000000000000001472321612100266615ustar00rootroot00000000000000golang-github-onsi-ginkgo-v2-2.22.0/integration/_fixtures/timeout_fixture/timeout_A/000077500000000000000000000000001472321612100306075ustar00rootroot00000000000000timeout_A_suite_test.go000066400000000000000000000004201472321612100352510ustar00rootroot00000000000000golang-github-onsi-ginkgo-v2-2.22.0/integration/_fixtures/timeout_fixture/timeout_Apackage timeout_A_test import ( "testing" "time" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" ) func TestTimeoutA(t *testing.T) { RegisterFailHandler(Fail) RunSpecs(t, "TimeoutA Suite") } var _ = It("sleeps", func() { time.Sleep(5 * time.Second) }) golang-github-onsi-ginkgo-v2-2.22.0/integration/_fixtures/timeout_fixture/timeout_B/000077500000000000000000000000001472321612100306105ustar00rootroot00000000000000timeout_B_suite_test.go000066400000000000000000000004201472321612100352530ustar00rootroot00000000000000golang-github-onsi-ginkgo-v2-2.22.0/integration/_fixtures/timeout_fixture/timeout_Bpackage timeout_B_test import ( "testing" "time" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" ) func TestTimeoutB(t *testing.T) { RegisterFailHandler(Fail) RunSpecs(t, "TimeoutB Suite") } var _ = It("sleeps", func() { time.Sleep(5 * time.Second) }) golang-github-onsi-ginkgo-v2-2.22.0/integration/_fixtures/timeout_fixture/timeout_C/000077500000000000000000000000001472321612100306115ustar00rootroot00000000000000timeout_C_suite_test.go000066400000000000000000000004201472321612100352550ustar00rootroot00000000000000golang-github-onsi-ginkgo-v2-2.22.0/integration/_fixtures/timeout_fixture/timeout_Cpackage timeout_C_test import ( "testing" "time" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" ) func TestTimeoutC(t *testing.T) { RegisterFailHandler(Fail) RunSpecs(t, "TimeoutC Suite") } var _ = It("sleeps", func() { time.Sleep(5 * time.Second) }) golang-github-onsi-ginkgo-v2-2.22.0/integration/_fixtures/timeout_fixture/timeout_D/000077500000000000000000000000001472321612100306125ustar00rootroot00000000000000timeout_D_suite_test.go000066400000000000000000000004201472321612100352570ustar00rootroot00000000000000golang-github-onsi-ginkgo-v2-2.22.0/integration/_fixtures/timeout_fixture/timeout_Dpackage timeout_D_test import ( "testing" "time" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" ) func TestTimeoutD(t *testing.T) { RegisterFailHandler(Fail) RunSpecs(t, "TimeoutD Suite") } var _ = It("sleeps", func() { time.Sleep(5 * time.Second) }) golang-github-onsi-ginkgo-v2-2.22.0/integration/_fixtures/version_mismatch_fixture/000077500000000000000000000000001472321612100305455ustar00rootroot00000000000000golang-github-onsi-ginkgo-v2-2.22.0/integration/_fixtures/version_mismatch_fixture/go.mod000066400000000000000000000005121472321612100316510ustar00rootroot00000000000000module version_mismatch_fixture go 1.19 require ( github.com/onsi/ginkgo/v2 v2.2.0 github.com/onsi/gomega v1.20.1 ) require ( github.com/google/go-cmp v0.5.8 // indirect golang.org/x/net v0.23.0 // indirect golang.org/x/sys v0.18.0 // indirect golang.org/x/text v0.14.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) golang-github-onsi-ginkgo-v2-2.22.0/integration/_fixtures/version_mismatch_fixture/go.sum000066400000000000000000000027171472321612100317070ustar00rootroot00000000000000github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg= github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= 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.20.1 h1:PA/3qinGoukvymdIDV8pii6tiZgC8kbmJO6Z5+b002Q= github.com/onsi/gomega v1.20.1/go.mod h1:DtrZpjmvpn2mPm4YWQa0/ALMDj9v4YxLgojwPeREyVo= golang.org/x/net v0.23.0 h1:7EYJ93RZ9vYSZAIb2x3lnuvqO5zneoD6IvWjuhfxjTs= golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4= golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= google.golang.org/protobuf v1.28.0 h1:w43yiav+6bVFTBQFZX0r7ipe9JQ1QsbMgHwbBziscLw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= version_mismatch_fixture_suite_test.go000066400000000000000000000004631472321612100404100ustar00rootroot00000000000000golang-github-onsi-ginkgo-v2-2.22.0/integration/_fixtures/version_mismatch_fixturepackage version_mismatch_fixture_test import ( "testing" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" ) func TestVersionMismatchFixture(t *testing.T) { RegisterFailHandler(Fail) RunSpecs(t, "VersionMismatchFixture Suite") } var _ = It("has the error message we expect...", func() { }) golang-github-onsi-ginkgo-v2-2.22.0/integration/_fixtures/watch_fixture/000077500000000000000000000000001472321612100263015ustar00rootroot00000000000000golang-github-onsi-ginkgo-v2-2.22.0/integration/_fixtures/watch_fixture/A/000077500000000000000000000000001472321612100264615ustar00rootroot00000000000000golang-github-onsi-ginkgo-v2-2.22.0/integration/_fixtures/watch_fixture/A/A.go000066400000000000000000000001751472321612100271730ustar00rootroot00000000000000package A import "github.com/onsi/ginkgo/v2/integration/_fixtures/watch_fixture/B" func DoIt() string { return B.DoIt() } golang-github-onsi-ginkgo-v2-2.22.0/integration/_fixtures/watch_fixture/A/A_suite_test.go000066400000000000000000000002631472321612100314410ustar00rootroot00000000000000package A_test import ( . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" "testing" ) func TestA(t *testing.T) { RegisterFailHandler(Fail) RunSpecs(t, "A Suite") } golang-github-onsi-ginkgo-v2-2.22.0/integration/_fixtures/watch_fixture/A/A_test.go000066400000000000000000000004041472321612100302250ustar00rootroot00000000000000package A_test import ( . "github.com/onsi/ginkgo/v2/integration/_fixtures/watch_fixture/A" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" ) var _ = Describe("A", func() { It("should do it", func() { Ω(DoIt()).Should(Equal("done!")) }) }) golang-github-onsi-ginkgo-v2-2.22.0/integration/_fixtures/watch_fixture/B/000077500000000000000000000000001472321612100264625ustar00rootroot00000000000000golang-github-onsi-ginkgo-v2-2.22.0/integration/_fixtures/watch_fixture/B/B.go000066400000000000000000000001751472321612100271750ustar00rootroot00000000000000package B import "github.com/onsi/ginkgo/v2/integration/_fixtures/watch_fixture/C" func DoIt() string { return C.DoIt() } golang-github-onsi-ginkgo-v2-2.22.0/integration/_fixtures/watch_fixture/B/B_suite_test.go000066400000000000000000000002631472321612100314430ustar00rootroot00000000000000package B_test import ( . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" "testing" ) func TestB(t *testing.T) { RegisterFailHandler(Fail) RunSpecs(t, "B Suite") } golang-github-onsi-ginkgo-v2-2.22.0/integration/_fixtures/watch_fixture/B/B_test.go000066400000000000000000000004041472321612100302270ustar00rootroot00000000000000package B_test import ( . "github.com/onsi/ginkgo/v2/integration/_fixtures/watch_fixture/B" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" ) var _ = Describe("B", func() { It("should do it", func() { Ω(DoIt()).Should(Equal("done!")) }) }) golang-github-onsi-ginkgo-v2-2.22.0/integration/_fixtures/watch_fixture/C/000077500000000000000000000000001472321612100264635ustar00rootroot00000000000000golang-github-onsi-ginkgo-v2-2.22.0/integration/_fixtures/watch_fixture/C/C.go000066400000000000000000000000621472321612100271720ustar00rootroot00000000000000package C func DoIt() string { return "done!" } golang-github-onsi-ginkgo-v2-2.22.0/integration/_fixtures/watch_fixture/C/C.json000066400000000000000000000000311472321612100275320ustar00rootroot00000000000000{ "fixture": "data" }golang-github-onsi-ginkgo-v2-2.22.0/integration/_fixtures/watch_fixture/C/C_suite_test.go000066400000000000000000000002631472321612100314450ustar00rootroot00000000000000package C_test import ( . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" "testing" ) func TestC(t *testing.T) { RegisterFailHandler(Fail) RunSpecs(t, "C Suite") } golang-github-onsi-ginkgo-v2-2.22.0/integration/_fixtures/watch_fixture/C/C_test.go000066400000000000000000000004041472321612100302310ustar00rootroot00000000000000package C_test import ( . "github.com/onsi/ginkgo/v2/integration/_fixtures/watch_fixture/C" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" ) var _ = Describe("C", func() { It("should do it", func() { Ω(DoIt()).Should(Equal("done!")) }) }) golang-github-onsi-ginkgo-v2-2.22.0/integration/_fixtures/watch_fixture/D/000077500000000000000000000000001472321612100264645ustar00rootroot00000000000000golang-github-onsi-ginkgo-v2-2.22.0/integration/_fixtures/watch_fixture/D/D.go000066400000000000000000000001751472321612100272010ustar00rootroot00000000000000package D import "github.com/onsi/ginkgo/v2/integration/_fixtures/watch_fixture/C" func DoIt() string { return C.DoIt() } golang-github-onsi-ginkgo-v2-2.22.0/integration/_fixtures/watch_fixture/D/D_suite_test.go000066400000000000000000000002631472321612100314470ustar00rootroot00000000000000package D_test import ( . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" "testing" ) func TestD(t *testing.T) { RegisterFailHandler(Fail) RunSpecs(t, "D Suite") } golang-github-onsi-ginkgo-v2-2.22.0/integration/_fixtures/watch_fixture/D/D_test.go000066400000000000000000000004041472321612100302330ustar00rootroot00000000000000package D_test import ( . "github.com/onsi/ginkgo/v2/integration/_fixtures/watch_fixture/D" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" ) var _ = Describe("D", func() { It("should do it", func() { Ω(DoIt()).Should(Equal("done!")) }) }) golang-github-onsi-ginkgo-v2-2.22.0/integration/_fixtures/xunit_fixture/000077500000000000000000000000001472321612100263425ustar00rootroot00000000000000golang-github-onsi-ginkgo-v2-2.22.0/integration/_fixtures/xunit_fixture/xunit_tests.go000066400000000000000000000000751472321612100312640ustar00rootroot00000000000000package xunit_tests func AlwaysTrue() bool { return true } golang-github-onsi-ginkgo-v2-2.22.0/integration/_fixtures/xunit_fixture/xunit_tests_test.go000066400000000000000000000002271472321612100323220ustar00rootroot00000000000000package xunit_tests import ( "testing" ) func TestAlwaysTrue(t *testing.T) { if AlwaysTrue() != true { t.Errorf("Expected true, got false") } } golang-github-onsi-ginkgo-v2-2.22.0/integration/abort_test.go000066400000000000000000000032751472321612100241210ustar00rootroot00000000000000package integration_test import ( . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" "github.com/onsi/gomega/gbytes" "github.com/onsi/gomega/gexec" "github.com/onsi/ginkgo/v2/internal/interrupt_handler" . "github.com/onsi/ginkgo/v2/internal/test_helpers" "github.com/onsi/ginkgo/v2/reporters" "github.com/onsi/ginkgo/v2/types" ) var _ = Describe("Abort", func() { var session *gexec.Session BeforeEach(func() { fm.MountFixture("abort") session = startGinkgo(fm.PathTo("abort"), "--no-color", "--json-report=out.json", "--junit-report=out.xml", "--procs=2") Eventually(session).Should(gexec.Exit(1)) }) It("aborts the suite and does not run any subsequent tests", func() { Ω(session).Should(gbytes.Say("this suite needs to end now!")) Ω(string(session.Out.Contents())).ShouldNot(ContainSubstring("SHOULD NOT SEE THIS")) }) It("reports on the test run correctly", func() { report := fm.LoadJSONReports("abort", "out.json")[0] specs := Reports(report.SpecReports) Ω(specs.Find("runs and passes")).Should(HavePassed()) Ω(specs.Find("aborts")).Should(HaveAborted("this suite needs to end now!")) Ω(specs.Find("never runs")).Should(HaveBeenInterrupted(interrupt_handler.InterruptCauseAbortByOtherProcess)) Ω(specs.Find("never runs either")).Should(HaveBeenSkipped()) junitSuites := fm.LoadJUnitReport("abort", "out.xml") cases := junitSuites.TestSuites[0].TestCases var abortCase reporters.JUnitTestCase for _, testCase := range cases { if testCase.Status == types.SpecStateAborted.String() { abortCase = testCase } } Ω(abortCase.Failure.Message).Should(Equal("this suite needs to end now!")) Ω(abortCase.Failure.Type).Should(Equal("aborted")) }) }) golang-github-onsi-ginkgo-v2-2.22.0/integration/decorations_test.go000066400000000000000000000040341472321612100253160ustar00rootroot00000000000000package integration_test import ( . "github.com/onsi/ginkgo/v2" "github.com/onsi/ginkgo/v2/types" . "github.com/onsi/gomega" "github.com/onsi/gomega/gbytes" "github.com/onsi/gomega/gexec" ) var _ = Describe("Decorations", func() { BeforeEach(func() { fm.MountFixture("decorations") }) It("processes the Offset, Focus and Pending decorations", func() { session := startGinkgo(fm.PathTo("decorations", "offset_focus_pending"), "-vv", "--no-color") Eventually(session).Should(gexec.Exit(types.GINKGO_FOCUS_EXIT_CODE)) out := string(session.Out.Contents()) Ω(out).Should(MatchRegexp( `P \[PENDING\] some decorated specs .*offset_focus_pending_fixture_suite_test.go:\d+ pending it`, )) Ω(out).ShouldNot(ContainSubstring("never_see_this_file")) }) It("processes the FlakeAttempts and the MustPassRepeatedly decorations", func() { session := startGinkgo(fm.PathTo("decorations", "flaky_repeated"), "-vv", "--no-color") Eventually(session).Should(gexec.Exit(1)) Ω(session).Should(gbytes.Say("Attempt #1 Failed. Retrying")) Ω(session).Should(gbytes.Say("Attempt #2 Failed. Retrying")) Ω(session).Should(gbytes.Say("Attempt #1 Passed. Repeating")) Ω(session).Should(gbytes.Say("Attempt #2 Passed. Repeating")) Ω(session).Should(gbytes.Say("failed on attempt #3")) }) It("exits with a clear error if decorations are misconfigured - focus and pending error", func() { session := startGinkgo(fm.PathTo("decorations", "invalid_decorations_focused_pending"), "-v", "--no-color") Eventually(session).Should(gexec.Exit(1)) Ω(session).Should(gbytes.Say("Invalid Combination of Decorators: Focused and Pending")) }) It("exits with a clear error if decorations are misconfigured - flakeattempts and mustpassrepeatedly error", func() { session := startGinkgo(fm.PathTo("decorations", "invalid_decorations_flakeattempts_mustpassrepeatedly"), "-v", "--no-color") Eventually(session).Should(gexec.Exit(1)) Ω(session).Should(gbytes.Say("Invalid Combination of Decorators: FlakeAttempts and MustPassRepeatedly")) }) }) golang-github-onsi-ginkgo-v2-2.22.0/integration/deprecations_test.go000066400000000000000000000015721472321612100254700ustar00rootroot00000000000000package integration_test import ( . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" "github.com/onsi/gomega/gexec" ) var _ = Describe("Deprecations", func() { BeforeEach(func() { fm.MountFixture("deprecated_features") }) It("runs, succeeds, and emits deprecation warnings", func() { session := startGinkgo(fm.PathTo("deprecated_features"), "--randomizeAllSpecs", "--stream") Eventually(session).Should(gexec.Exit(0)) contents := string(session.Out.Contents()) + string(session.Err.Contents()) Ω(contents).Should(ContainSubstring("You are passing a Done channel to a test node to test asynchronous behavior.")) Ω(contents).Should(ContainSubstring("Measure is deprecated and has been removed from Ginkgo V2.")) Ω(contents).Should(ContainSubstring("--stream is deprecated")) Ω(contents).Should(ContainSubstring("--randomizeAllSpecs is deprecated")) }) }) golang-github-onsi-ginkgo-v2-2.22.0/integration/fail_test.go000066400000000000000000000116001472321612100237140ustar00rootroot00000000000000package integration_test import ( . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" "github.com/onsi/gomega/gexec" ) var _ = Describe("Failing Specs", func() { Describe("when the tests contain failures", func() { BeforeEach(func() { fm.MountFixture("fail") }) It("should fail in all the possible ways", func() { session := startGinkgo(fm.PathTo("fail"), "--no-color") Eventually(session).Should(gexec.Exit(1)) output := string(session.Out.Contents()) Ω(output).ShouldNot(ContainSubstring("NEVER SEE THIS")) Ω(output).Should(ContainSubstring("a top level failure on line 12")) Ω(output).Should(ContainSubstring("fail_fixture_test.go:12")) Ω(output).Should(ContainSubstring("a sync failure")) Ω(output).Should(MatchRegexp(`Test Panicked`)) Ω(output).Should(MatchRegexp(`a sync panic`)) Ω(output).Should(ContainSubstring("a sync FAIL failure")) Ω(output).Should(ContainSubstring("a top level specify")) Ω(output).ShouldNot(ContainSubstring("ginkgo_dsl.go")) Ω(output).Should(ContainSubstring("fail_fixture_test.go:38")) Ω(output).Should(ContainSubstring("[TIMEDOUT]")) Ω(output).Should(MatchRegexp(`goroutine \d+ \[chan receive\]`), "from the progress report emitted by the timeout") Ω(output).Should(MatchRegexp(`>\s*\<\-c\.Done\(\)`), "from the progress report emitted by the timeout") Ω(output).Should(MatchRegexp(`a top level DescribeTable \[It\] a TableEntry constructed by Entry\n.*fail_fixture_test\.go:45`), "the output of a failing Entry should include its file path and line number") Ω(output).Should(ContainSubstring(`a helper failed`)) Ω(output).Should(ContainSubstring(`fail_fixture_test.go:54`), "the code location reported for the helper failure - we're testing the call to GinkgoHelper() works as expected") Ω(output).Should(ContainSubstring("synchronous failures with GinkgoT().Fail")) Ω(output).Should(ContainSubstring("fail_fixture_ginkgo_t_test.go:9")) Ω(output).Should(ContainSubstring("GinkgoT DescribeTable")) Ω(output).Should(ContainSubstring("fail_fixture_ginkgo_t_test.go:15")) Ω(output).Should(ContainSubstring(`tracks line numbers correctly when GinkgoT().Helper() is called`)) Ω(output).Should(ContainSubstring(`fail_fixture_ginkgo_t_test.go:21`), "the code location reported for the ginkgoT helper failure") Ω(output).Should(ContainSubstring(`tracks the actual line number when no helper is used`)) Ω(output).Should(ContainSubstring(`fail_fixture_ginkgo_t_test.go:30`), "the code location reported for the ginkgoT no helper failure") Ω(output).Should(ContainSubstring("synchronous failures with GinkgoTB().Fail")) Ω(output).Should(ContainSubstring("fail_fixture_ginkgo_tb_test.go:9")) Ω(output).Should(ContainSubstring("GinkgoTB DescribeTable")) Ω(output).Should(ContainSubstring("fail_fixture_ginkgo_tb_test.go:15")) Ω(output).Should(ContainSubstring(`tracks line numbers correctly when GinkgoTB().Helper() is called`)) Ω(output).Should(ContainSubstring(`fail_fixture_ginkgo_tb_test.go:21`), "the code location reported for the ginkgoTB helper failure") Ω(output).Should(ContainSubstring(`tracks the actual line number when no GinkgoTB helper is used`)) Ω(output).Should(ContainSubstring(`fail_fixture_ginkgo_tb_test.go:30`), "the code location reported for the ginkgoT no helper failure") Ω(output).Should(ContainSubstring("0 Passed | 16 Failed")) }) }) Describe("when the tests are incorrectly structured", func() { BeforeEach(func() { fm.MountFixture("malformed") }) It("exits early with a helpful error message", func() { session := startGinkgo(fm.PathTo("malformed"), "--no-color") Eventually(session).Should(gexec.Exit(1)) output := string(session.Out.Contents()) Ω(output).Should(ContainSubstring("Ginkgo detected an issue with your spec structure")) Ω(output).Should(ContainSubstring("malformed_fixture_test.go:9")) }) It("emits the error message even if running in parallel", func() { session := startGinkgo(fm.PathTo("malformed"), "--no-color", "--procs=2") Eventually(session).Should(gexec.Exit(1)) output := string(session.Out.Contents()) + string(session.Err.Contents()) Ω(output).Should(ContainSubstring("Ginkgo detected an issue with your spec structure")) Ω(output).Should(ContainSubstring("malformed_fixture_test.go:9")) }) }) Describe("when By is called outside of a runnable node", func() { BeforeEach(func() { fm.MountFixture("malformed_by") }) It("exits early with a helpful error message", func() { session := startGinkgo(fm.PathTo("malformed_by"), "--no-color", "--procs=2") Eventually(session).Should(gexec.Exit(1)) output := string(session.Out.Contents()) + string(session.Err.Contents()) Ω(output).Should(ContainSubstring("Ginkgo detected an issue with your spec structure")) Ω(output).Should(ContainSubstring("malformed_by_fixture_suite_test.go:16")) }) }) }) golang-github-onsi-ginkgo-v2-2.22.0/integration/filter_test.go000066400000000000000000000072771472321612100243050ustar00rootroot00000000000000package integration_test import ( . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" "github.com/onsi/gomega/gbytes" "github.com/onsi/gomega/gexec" . "github.com/onsi/ginkgo/v2/internal/test_helpers" ) var _ = Describe("Filter", func() { BeforeEach(func() { fm.MountFixture("filter") }) It("honors the focus, skip, focus-file and skip-file flags", func() { session := startGinkgo(fm.PathTo("filter"), "--focus=dog", "--focus=fish", "--skip=cat", "--focus-file=sprocket", "--focus-file=widget:1-24", "--focus-file=_b:24-42", "--skip-file=_c", "--json-report=report.json", "--label-filter=TopLevelLabel && !SLOW && !(Feature: containsAny Alpha)", ) Eventually(session).Should(gexec.Exit(0)) specs := Reports(fm.LoadJSONReports("filter", "report.json")[0].SpecReports) passedSpecs := []string{ "SprocketA dog fish", "SprocketB dog", "SprocketB dog fish", "WidgetA dog", "WidgetA dog fish", "WidgetB dog fish", // lines in _b > 24 are in --focus-file "More WidgetB dog", "More WidgetB dog fish", } skippedSpecs := []string{ // nugget files are not in focus-file "NuggetA cat", "NuggetA dog", "NuggetA cat fish", "NuggetA dog fish", "NuggetB cat", "NuggetB dog", "NuggetB cat fish", "NuggetB dog fish", // cat is not in -focus "SprocketA cat", "SprocketB cat", "WidgetA cat", "WidgetB cat", "More WidgetB cat", // fish is in -focus but cat is in -skip "SprocketA cat fish", "SprocketB cat fish", "WidgetA cat fish", "WidgetB cat fish", "More WidgetB cat fish", // Tests with Feature:Alpha "WidgetB fish", // Tests labelled 'slow' "WidgetB dog", "SprocketB fish", // _c is in -skip-file "SprocketC cat", "SprocketC dog", "SprocketC cat fish", "SprocketC dog fish", // lines in widget > 24 are not in --focus-file "More WidgetA cat", "More WidgetA dog", "More WidgetA cat fish", "More WidgetA dog fish", } pendingSpecs := []string{ "SprocketA pending dog", } Ω(specs).Should(HaveLen(len(passedSpecs) + len(skippedSpecs) + len(pendingSpecs))) for _, text := range passedSpecs { Ω(specs.FindByFullText(text)).Should(HavePassed(), text) } for _, text := range skippedSpecs { Ω(specs.FindByFullText(text)).Should(HaveBeenSkipped(), text) } for _, text := range pendingSpecs { Ω(specs.FindByFullText(text)).Should(BePending(), text) } }) It("ignores empty filter flags", func() { session := startGinkgo(fm.PathTo("filter"), "--focus=", "--skip=", "--json-report=report.json", ) Eventually(session).Should(gexec.Exit(0)) specs := Reports(fm.LoadJSONReports("filter", "report.json")[0].SpecReports) for _, spec := range specs { Ω(spec).Should(SatisfyAny(HavePassed(), BePending())) } }) It("errors if the file-filter format is wrong", func() { session := startGinkgo(fm.PathTo("filter"), "--focus-file=foo:bar", "--skip-file=") Eventually(session).Should(gexec.Exit(1)) Ω(session).Should(gbytes.Say("Invalid File Filter")) Ω(session).Should(gbytes.Say("Invalid File Filter")) }) Describe("Listing labels", func() { BeforeEach(func() { fm.MountFixture("labels") }) It("can list labels", func() { session := startGinkgo(fm.TmpDir, "labels", "-r") Eventually(session).Should(gexec.Exit(0)) Ω(session).Should(gbytes.Say(`filter: \["Feature:Alpha", "Feature:Beta", "TopLevelLabel", "slow"\]`)) Ω(session).Should(gbytes.Say(`labels: \["beluga", "bird", "cat", "chicken", "cow", "dog", "giraffe", "koala", "monkey", "otter", "owl", "panda"\]`)) Ω(session).Should(gbytes.Say(`nolabels: No labels found`)) Ω(session).Should(gbytes.Say(`onepkg: \["beluga", "bird", "cat", "chicken", "cow", "dog", "giraffe", "koala", "monkey", "otter", "owl", "panda"\]`)) }) }) }) golang-github-onsi-ginkgo-v2-2.22.0/integration/flags_test.go000066400000000000000000000147401472321612100241050ustar00rootroot00000000000000package integration_test import ( "strings" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" "github.com/onsi/gomega/gexec" ) var _ = Describe("Flags Specs", func() { BeforeEach(func() { fm.MountFixture("flags") }) getRandomOrders := func(output string) []int { return []int{strings.Index(output, "RANDOM_A"), strings.Index(output, "RANDOM_B"), strings.Index(output, "RANDOM_C")} } It("normally passes, prints out noisy pendings, does not randomize tests, and honors the programmatic focus", func() { session := startGinkgo(fm.PathTo("flags"), "--no-color") Eventually(session).Should(gexec.Exit(1)) output := string(session.Out.Contents()) Ω(output).Should(ContainSubstring("9 Passed")) Ω(output).Should(ContainSubstring("2 Failed")) Ω(output).Should(ContainSubstring("1 Pending")) Ω(output).Should(ContainSubstring("0 Skipped")) Ω(output).Should(ContainSubstring("[PENDING]")) Ω(output).Should(ContainSubstring("marshmallow")) Ω(output).Should(ContainSubstring("chocolate")) Ω(output).Should(ContainSubstring("CUSTOM_FLAG: default")) Ω(output).ShouldNot(ContainSubstring("SLOW TEST")) Ω(output).ShouldNot(ContainSubstring("should honor -slow-spec-threshold")) Ω(output).ShouldNot(ContainSubstring("Full Stack Trace")) orders := getRandomOrders(output) Ω(orders[0]).Should(BeNumerically("<", orders[1])) Ω(orders[1]).Should(BeNumerically("<", orders[2])) }) It("should fail when there are pending tests and it is passed --fail-on-pending", func() { session := startGinkgo(fm.PathTo("flags"), "--no-color", "--fail-on-pending") Eventually(session).Should(gexec.Exit(1)) output := string(session.Out.Contents()) Ω(output).Should(ContainSubstring("Detected pending specs and --fail-on-pending is set")) }) It("should run the race detector when told to", Label("slow"), func() { if !raceDetectorSupported() { Skip("race detection is not supported") } session := startGinkgo(fm.PathTo("flags"), "--no-color", "--race") Eventually(session).Should(gexec.Exit(1)) output := string(session.Out.Contents()) Ω(output).Should(ContainSubstring("WARNING: DATA RACE")) }) It("should randomize tests when told to", func() { session := startGinkgo(fm.PathTo("flags"), "--no-color", "--randomize-all", "--seed=120") Eventually(session).Should(gexec.Exit(1)) output := string(session.Out.Contents()) orders := getRandomOrders(output) Ω(orders[0]).ShouldNot(BeNumerically("<", orders[1])) }) It("should consistently pass in a zero seed when asked to", func() { fm.MountFixture("seed") session := startGinkgo(fm.PathTo("seed"), "--no-color", "--seed=0", "--nodes=2") Eventually(session).Should(gexec.Exit(0)) }) It("should pass additional arguments in", func() { session := startGinkgo(fm.PathTo("flags"), "--", "--customFlag=madagascar") Eventually(session).Should(gexec.Exit(1)) output := string(session.Out.Contents()) Ω(output).Should(ContainSubstring("CUSTOM_FLAG: madagascar")) }) It("should print out full stack traces for failures when told to", func() { session := startGinkgo(fm.PathTo("flags"), "--trace") Eventually(session).Should(gexec.Exit(1)) output := string(session.Out.Contents()) Ω(output).Should(ContainSubstring("Full Stack Trace")) }) Describe("--fail-fast", func() { BeforeEach(func() { fm.MountFixture("fail_then_hang") }) Context("when running in series", func() { It("should fail fast when told to", func() { session := startGinkgo(fm.PathTo("fail_then_hang"), "--fail-fast") Eventually(session).Should(gexec.Exit(1)) output := string(session.Out.Contents()) Ω(output).Should(ContainSubstring("1 Failed")) Ω(output).Should(ContainSubstring("2 Skipped")) }) }) Context("when running in parallel", func() { It("should fail fast when told to", func() { session := startGinkgo(fm.PathTo("fail_then_hang"), "--fail-fast", "--procs=2") Eventually(session).Should(gexec.Exit(1)) output := string(session.Out.Contents()) Ω(output).Should(ContainSubstring("2 Failed")) //one fails, the other is interrupted Ω(output).Should(ContainSubstring("1 Skipped")) }) }) }) Context("with a flaky test", func() { It("should normally fail", func() { session := startGinkgo(fm.PathTo("flags"), "--focus=flaky") Eventually(session).Should(gexec.Exit(1)) }) It("should pass if retries are requested", func() { session := startGinkgo(fm.PathTo("flags"), "--focus=flaky --flake-attempts=2") Eventually(session).Should(gexec.Exit(0)) }) }) It("should perform a dry run when told to", func() { fm.MountFixture("fail") session := startGinkgo(fm.PathTo("fail"), "--dry-run", "-v") Eventually(session).Should(gexec.Exit(0)) output := string(session.Out.Contents()) Ω(output).Should(ContainSubstring("synchronous failures")) Ω(output).Should(ContainSubstring("16 Specs")) Ω(output).Should(ContainSubstring("16 Passed")) Ω(output).Should(ContainSubstring("0 Failed")) }) It("should allow configuration overrides", func() { fm.MountFixture("config_override_label_filter") session := startGinkgo(fm.PathTo("config_override_label_filter"), "--label-filter=NORUN", "--no-color") Eventually(session).Should(gexec.Exit(0), "Succeeds because --label-filter is overridden by the test suite itself.") output := string(session.Out.Contents()) Ω(output).Should(ContainSubstring("2 Specs")) Ω(output).Should(ContainSubstring("1 Skipped")) Ω(output).Should(ContainSubstring("1 Passed")) }) It("should emit node start/end events when running with --show-node-events", func() { session := startGinkgo(fm.PathTo("flags"), "--no-color", "-v", "--show-node-events") Eventually(session).Should(gexec.Exit(1)) output := string(session.Out.Contents()) Eventually(output).Should(ContainSubstring("> Enter [It] should honor -cover")) Eventually(output).Should(ContainSubstring("< Exit [It] should honor -cover")) fm.MountFixture("fail") session = startGinkgo(fm.PathTo("fail"), "--no-color", "--show-node-events") Eventually(session).Should(gexec.Exit(1)) output = string(session.Out.Contents()) Ω(output).Should(ContainSubstring("> Enter [It] a top level specify")) Ω(output).Should(ContainSubstring("< Exit [It] a top level specify")) session = startGinkgo(fm.PathTo("fail"), "--no-color") Eventually(session).Should(gexec.Exit(1)) output = string(session.Out.Contents()) Ω(output).ShouldNot(ContainSubstring("> Enter [It] a top level specify")) Ω(output).ShouldNot(ContainSubstring("< Exit [It] a top level specify")) }) }) golang-github-onsi-ginkgo-v2-2.22.0/integration/integration.go000066400000000000000000000000241472321612100242630ustar00rootroot00000000000000package integration golang-github-onsi-ginkgo-v2-2.22.0/integration/integration_suite_test.go000066400000000000000000000153361472321612100265470ustar00rootroot00000000000000package integration_test import ( "bytes" "encoding/json" "encoding/xml" "flag" "fmt" "os" "os/exec" "path/filepath" "runtime" . "github.com/onsi/ginkgo/v2" "github.com/onsi/ginkgo/v2/reporters" "github.com/onsi/ginkgo/v2/types" . "github.com/onsi/gomega" "github.com/onsi/gomega/format" "github.com/onsi/gomega/gexec" "testing" "time" ) var pathToGinkgo string var DEBUG bool var fm FixtureManager func init() { flag.BoolVar(&DEBUG, "debug", false, "keep assets around after test run") } func TestIntegration(t *testing.T) { SetDefaultEventuallyTimeout(30 * time.Second) format.TruncatedDiff = false RegisterFailHandler(Fail) RunSpecs(t, "Integration Suite", Label("integration")) } var _ = SynchronizedBeforeSuite(func() []byte { DeferCleanup(gexec.CleanupBuildArtifacts) pathToGinkgo, err := gexec.Build("../ginkgo") Ω(err).ShouldNot(HaveOccurred()) return []byte(pathToGinkgo) }, func(computedPathToGinkgo []byte) { pathToGinkgo = string(computedPathToGinkgo) }) var _ = BeforeEach(OncePerOrdered, func() { fm = NewFixtureManager(fmt.Sprintf("tmp_%d", GinkgoParallelProcess())) }) var _ = AfterEach(OncePerOrdered, func() { if DEBUG { return } suiteConfig, _ := GinkgoConfiguration() if CurrentSpecReport().Failed() && suiteConfig.FailFast { return } fm.Cleanup() }) type FixtureManager struct { TmpDir string } func NewFixtureManager(tmpDir string) FixtureManager { err := os.MkdirAll(tmpDir, 0700) Ω(err).ShouldNot(HaveOccurred()) return FixtureManager{ TmpDir: tmpDir, } } func (f FixtureManager) Cleanup() { Ω(os.RemoveAll(f.TmpDir)).Should(Succeed()) } func (f FixtureManager) MountFixture(fixture string, subPackage ...string) { src := filepath.Join("_fixtures", fixture+"_fixture") dst := filepath.Join(f.TmpDir, fixture) if len(subPackage) > 0 { src = filepath.Join(src, subPackage[0]) dst = filepath.Join(dst, subPackage[0]) } f.copyAndRewrite(src, dst) } func (f FixtureManager) MkEmpty(pkg string) { ExpectWithOffset(1, os.MkdirAll(f.PathTo(pkg), 0700)).Should(Succeed()) } func (f FixtureManager) copyAndRewrite(src string, dst string) { Expect(os.MkdirAll(dst, 0777)).To(Succeed()) files, err := os.ReadDir(src) Expect(err).NotTo(HaveOccurred()) for _, file := range files { srcPath := filepath.Join(src, file.Name()) dstPath := filepath.Join(dst, file.Name()) if file.IsDir() { f.copyAndRewrite(srcPath, dstPath) continue } srcContent, err := os.ReadFile(srcPath) Ω(err).ShouldNot(HaveOccurred()) //rewrite import statements so that fixtures can work in the fixture folder when developing them, and in the tmp folder when under test srcContent = bytes.ReplaceAll(srcContent, []byte("github.com/onsi/ginkgo/v2/integration/_fixtures"), []byte(f.PackageRoot())) srcContent = bytes.ReplaceAll(srcContent, []byte("_fixture"), []byte("")) Ω(os.WriteFile(dstPath, srcContent, 0666)).Should(Succeed()) } } func (f FixtureManager) AbsPathTo(pkg string, target ...string) string { path, err := filepath.Abs(f.PathTo(pkg, target...)) ExpectWithOffset(1, err).NotTo(HaveOccurred()) return path } func (f FixtureManager) PathTo(pkg string, target ...string) string { if len(target) == 0 { return filepath.Join(f.TmpDir, pkg) } components := append([]string{f.TmpDir, pkg}, target...) return filepath.Join(components...) } func (f FixtureManager) PathToFixtureFile(pkg string, target string) string { return filepath.Join("_fixtures", pkg+"_fixture", target) } func (f FixtureManager) WriteFile(pkg string, target string, content string) { dst := f.PathTo(pkg, target) err := os.WriteFile(dst, []byte(content), 0666) Ω(err).ShouldNot(HaveOccurred()) } func (f FixtureManager) AppendToFile(pkg string, target string, content string) { current := f.ContentOf(pkg, target) f.WriteFile(pkg, target, current+content) } func (f FixtureManager) ContentOf(pkg string, target string) string { content, err := os.ReadFile(f.PathTo(pkg, target)) ExpectWithOffset(1, err).NotTo(HaveOccurred()) return string(content) } func (f FixtureManager) ListDir(pkg string, target ...string) []string { path := f.PathTo(pkg, target...) files, err := os.ReadDir(path) ExpectWithOffset(1, err).NotTo(HaveOccurred()) out := []string{} for _, f := range files { out = append(out, f.Name()) } return out } func (f FixtureManager) LoadJSONReports(pkg string, target string) []types.Report { data := []byte(f.ContentOf(pkg, target)) reports := []types.Report{} ExpectWithOffset(1, json.Unmarshal(data, &reports)).Should(Succeed()) return reports } func (f FixtureManager) LoadJUnitReport(pkg string, target string) reporters.JUnitTestSuites { data := []byte(f.ContentOf(pkg, target)) reports := reporters.JUnitTestSuites{} ExpectWithOffset(1, xml.Unmarshal(data, &reports)).Should(Succeed()) return reports } func (f FixtureManager) ContentOfFixture(pkg string, target string) string { content, err := os.ReadFile(f.PathToFixtureFile(pkg, target)) ExpectWithOffset(1, err).NotTo(HaveOccurred()) return string(content) } func (f FixtureManager) RemoveFile(pkg string, target string) { Expect(os.RemoveAll(f.PathTo(pkg, target))).To(Succeed()) } func (f FixtureManager) PackageRoot() string { return "github.com/onsi/ginkgo/v2/integration/" + f.TmpDir } func (f FixtureManager) PackageNameFor(target string) string { return f.PackageRoot() + "/" + target } func sameFile(filePath, otherFilePath string) bool { content, readErr := os.ReadFile(filePath) Expect(readErr).NotTo(HaveOccurred()) otherContent, readErr := os.ReadFile(otherFilePath) Expect(readErr).NotTo(HaveOccurred()) Expect(string(content)).To(Equal(string(otherContent))) return true } func sameFolder(sourcePath, destinationPath string) bool { files, err := os.ReadDir(sourcePath) Expect(err).NotTo(HaveOccurred()) for _, f := range files { srcPath := filepath.Join(sourcePath, f.Name()) dstPath := filepath.Join(destinationPath, f.Name()) if f.IsDir() { sameFolder(srcPath, dstPath) continue } Expect(sameFile(srcPath, dstPath)).To(BeTrue()) } return true } func ginkgoCommand(dir string, args ...string) *exec.Cmd { cmd := exec.Command(pathToGinkgo, args...) cmd.Dir = dir return cmd } func startGinkgo(dir string, args ...string) *gexec.Session { cmd := ginkgoCommand(dir, args...) session, err := gexec.Start(cmd, GinkgoWriter, GinkgoWriter) Ω(err).ShouldNot(HaveOccurred()) return session } func raceDetectorSupported() bool { // https://github.com/golang/go/blob/1a370950/src/cmd/internal/sys/supported.go#L12 switch runtime.GOOS { case "linux": return runtime.GOARCH == "amd64" || runtime.GOARCH == "ppc64le" || runtime.GOARCH == "arm64" case "darwin": return runtime.GOARCH == "amd64" || runtime.GOARCH == "arm64" case "freebsd", "netbsd", "windows": return runtime.GOARCH == "amd64" default: return false } } golang-github-onsi-ginkgo-v2-2.22.0/integration/interrupt_test.go000066400000000000000000000121551472321612100250430ustar00rootroot00000000000000package integration_test import ( "encoding/json" "os/exec" "time" . "github.com/onsi/ginkgo/v2" "github.com/onsi/ginkgo/v2/types" . "github.com/onsi/gomega" "github.com/onsi/gomega/gbytes" "github.com/onsi/gomega/gexec" ) var _ = Describe("Interrupt and Timeout", func() { Context("when interrupting a suite", func() { It("gives the user feedback as the session is interrupted", func() { fm.MountFixture("hanging") //we need to signal the actual process, so we must compile the test first session := startGinkgo(fm.PathTo("hanging"), "build") Eventually(session).Should(gexec.Exit(0)) //then run the compiled test directly cmd := exec.Command("./hanging.test", "--test.v", "--ginkgo.no-color", "--ginkgo.grace-period=2s") cmd.Dir = fm.PathTo("hanging") var err error session, err = gexec.Start(cmd, GinkgoWriter, GinkgoWriter) Ω(err).ShouldNot(HaveOccurred()) Eventually(session).Should(gbytes.Say("Sleeping...")) session.Interrupt() Eventually(session).Should(gbytes.Say(`First interrupt received`)) Eventually(session).Should(gbytes.Say("Begin Captured GinkgoWriter Output")) Eventually(session).Should(gbytes.Say("Just beginning")) Eventually(session).Should(gbytes.Say("Almost there...")) Eventually(session).Should(gbytes.Say("Hanging Out")) Eventually(session).Should(gbytes.Say(`goroutine \d+ \[select\]`)) Eventually(session).Should(gbytes.Say(`>\s*select {`), "The actual source code gets emitted") Eventually(session, time.Second*5).Should(gbytes.Say("Cleaning up once..."), "The two second grace period should move on past the napping node") Eventually(session).Should(gbytes.Say("Cleaning up twice...")) Eventually(session).Should(gbytes.Say("Sleeping again...")) session.Interrupt() Eventually(session).Should(gbytes.Say(`Second interrupt received`)) Eventually(session).Should(gbytes.Say(`goroutine \d+ \[sleep\]`)) Eventually(session).Should(gbytes.Say(`>\s*time.Sleep\(time.Hour\)`), "The actual source code gets emitted now") Eventually(session).Should(gbytes.Say(`\[INTERRUPTED\]`)) Eventually(session).Should(gbytes.Say(`Interrupted by User`)) Eventually(session).Should(gbytes.Say(`Spec Goroutine`)) Eventually(session).Should(gbytes.Say(`goroutine \d+ \[select\]`)) Eventually(session).Should(gbytes.Say(`>\s*select {`), "The actual source code gets emitted now") Eventually(session).Should(gbytes.Say(`Other Goroutines`)) Eventually(session).Should(gbytes.Say(`main\.main\(\)`)) Eventually(session).Should(gbytes.Say("Reporting at the end")) Eventually(session).Should(gbytes.Say(`FAIL! - Interrupted by User`)) Eventually(session, time.Second*10).Should(gexec.Exit(1)) // the last AfterEach and the AfterSuite don't actually run Ω(string(session.Out.Contents())).ShouldNot(ContainSubstring("Cleaning up thrice")) Ω(string(session.Out.Contents())).ShouldNot(ContainSubstring("Heading Out After Suite")) }) }) Context("when the suite times out", func() { It("interrupts the suite and gives the user feedback as it does so", func() { fm.MountFixture("hanging") session := startGinkgo(fm.PathTo("hanging"), "--no-color", "--timeout=5s", "--grace-period=1s") Eventually(session, time.Second*10).Should(gexec.Exit(1)) Ω(session).Should(gbytes.Say("Sleeping...")) Ω(session).Should(gbytes.Say("Got your signal, but still taking a nap"), "the timeout has signaled the it to stop, but it's napping...") Ω(session).Should(gbytes.Say("A running node failed to exit in time"), "so we forcibly casue it to exit after the grace-period elapses") Ω(session).Should(gbytes.Say("Cleaning up once...")) Ω(session).Should(gbytes.Say("Cleaning up twice...")) Ω(session).Should(gbytes.Say("Cleaning up thrice..."), "we manage to get here even though the second after-each gets stuck. that's thanks to the GracePeriod configuration.") Ω(session).Should(gbytes.Say(`\[TIMEDOUT\]`)) Ω(session).Should(gbytes.Say(`Spec Goroutine`)) Ω(session).Should(gbytes.Say(`goroutine \d+ \[select\]`)) Ω(session).Should(gbytes.Say(`>\s*select {`), "The actual source code gets emitted now") Ω(session).ShouldNot(gbytes.Say(`Other Goroutines`)) Ω(session).Should(gbytes.Say("FAIL! - Suite Timeout Elapsed")) }) }) Describe("applying the timeout to multiple suites", func() { It("tracks the timeout across the suites, decrementing the available timeout for each individual suite, and reports on any suites that did not run because the timeout elapsed", Label("slow"), func() { fm.MountFixture("timeout") session := startGinkgo(fm.PathTo("timeout"), "--no-color", "-r", "--timeout=10s", "--keep-going", "--json-report=out.json") Eventually(session).Should(gbytes.Say("TimeoutA Suite")) Eventually(session, "15s").Should(gexec.Exit(1)) Ω(session).Should(gbytes.Say(`timeout_D ./timeout_D \[Suite did not run because the timeout elapsed\]`)) data := []byte(fm.ContentOf("timeout", "out.json")) reports := []types.Report{} Ω(json.Unmarshal(data, &reports)).Should(Succeed()) Ω(reports[3].SpecialSuiteFailureReasons).Should(ContainElement("Suite did not run because the timeout elapsed")) }) }) }) golang-github-onsi-ginkgo-v2-2.22.0/integration/output_interceptor_test.go000066400000000000000000000044771472321612100267750ustar00rootroot00000000000000package integration_test import ( "os/exec" "time" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" "github.com/onsi/gomega/gbytes" "github.com/onsi/gomega/gexec" ) var _ = Describe("OutputInterceptor", func() { Context("exercising the edge case reported in issue #851", func() { BeforeEach(func() { fm.MountFixture("interceptor") cmd := exec.Command("go", "build") cmd.Dir = fm.PathTo("interceptor") session, err := gexec.Start(cmd, GinkgoWriter, GinkgoWriter) Ω(err).ShouldNot(HaveOccurred()) Eventually(session).Should(gexec.Exit(0)) Ω(fm.PathTo("interceptor", "interceptor")).Should(BeAnExistingFile()) }) It("exercises the edge case reported in issue #851 - by asserting that output interception does not hang indefinitely if a process is spawned with cmd.Stdout=os.Stdout", func() { sess := startGinkgo(fm.PathTo("interceptor"), "--no-color") Eventually(sess).Should(gexec.Exit(0)) }) }) Context("pausing/resuming output interception", func() { BeforeEach(func() { fm.MountFixture("pause_resume_interception") }) It("can pause and resume interception", func() { sess := startGinkgo(fm.PathTo("pause_resume_interception"), "--no-color", "--procs=2", "--json-report=report.json") Eventually(sess).Should(gexec.Exit(0)) output := string(sess.Out.Contents()) Ω(output).Should(ContainSubstring("CAPTURED OUTPUT A\n")) Ω(output).Should(ContainSubstring("CAPTURED OUTPUT B\n")) Ω(output).ShouldNot(ContainSubstring("OUTPUT TO CONSOLE")) report := fm.LoadJSONReports("pause_resume_interception", "report.json")[0] Ω(report.SpecReports[0].CapturedStdOutErr).Should(Equal("CAPTURED OUTPUT A\nCAPTURED OUTPUT B\n")) }) }) Context("ensuring Ginkgo does not hang when a child process does not exit: https://github.com/onsi/ginkgo/issues/1191", func() { BeforeEach(func() { fm.MountFixture("interceptor_sleep") }) It("exits without hanging", func() { sess := startGinkgo(fm.PathTo("interceptor_sleep"), "--no-color", "--procs=2") Eventually(sess).WithTimeout(time.Second * 15).Should(gexec.Exit(0)) Ω(sess).Should(gbytes.Say("Captured StdOut/StdErr Output >>")) Ω(sess).Should(gbytes.Say("Some STDOUT output")) Ω(sess).Should(gbytes.Say("Some STDERR output")) Ω(sess).Should(gbytes.Say("<< Captured StdOut/StdErr Output")) }) }) }) golang-github-onsi-ginkgo-v2-2.22.0/integration/precompiled_test.go000066400000000000000000000056221472321612100253130ustar00rootroot00000000000000package integration_test import ( "os" "os/exec" "path" . "github.com/onsi/gomega" "github.com/onsi/gomega/gbytes" "github.com/onsi/gomega/gexec" . "github.com/onsi/ginkgo/v2" ) var _ = Describe("ginkgo build", func() { BeforeEach(func() { fm.MountFixture("passing_ginkgo_tests") session := startGinkgo(fm.PathTo("passing_ginkgo_tests"), "build") Eventually(session).Should(gexec.Exit(0)) output := string(session.Out.Contents()) Ω(output).Should(ContainSubstring("Compiled passing_ginkgo_tests.test")) }) It("should build a test binary", func() { Ω(fm.PathTo("passing_ginkgo_tests", "passing_ginkgo_tests.test")).Should(BeAnExistingFile()) }) It("should be possible to run the test binary directly", func() { cmd := exec.Command("./passing_ginkgo_tests.test") cmd.Dir = fm.PathTo("passing_ginkgo_tests") session, err := gexec.Start(cmd, GinkgoWriter, GinkgoWriter) Ω(err).ShouldNot(HaveOccurred()) Eventually(session).Should(gexec.Exit(0)) Ω(session).Should(gbytes.Say("Running Suite: Passing_ginkgo_tests Suite")) }) It("should be possible to run the test binary via ginkgo", func() { session := startGinkgo(fm.PathTo("passing_ginkgo_tests"), "./passing_ginkgo_tests.test") Eventually(session).Should(gexec.Exit(0)) Ω(session).Should(gbytes.Say("Running Suite: Passing_ginkgo_tests Suite")) }) It("should be possible to run the test binary in parallel", func() { session := startGinkgo(fm.PathTo("passing_ginkgo_tests"), "--procs=2", "--no-color", "./passing_ginkgo_tests.test") Eventually(session).Should(gexec.Exit(0)) Ω(session).Should(gbytes.Say("Running Suite: Passing_ginkgo_tests Suite")) Ω(session).Should(gbytes.Say("Running in parallel across 2 processes")) }) }) var _ = Describe("ginkgo build with custom output", Label("build"), func() { const customPath = "mycustomdir" var fullPath string BeforeEach(func() { fm.MountFixture("passing_ginkgo_tests") fullPath = fm.PathTo("passing_ginkgo_tests", customPath) Ω(os.Mkdir(fullPath, 0777)).To(Succeed()) DeferCleanup(func() { Ω(os.RemoveAll(fullPath)).Should(Succeed()) }) }) It("should build with custom path", func() { session := startGinkgo(fm.PathTo("passing_ginkgo_tests"), "build", "-o", customPath+"/mytestapp") Eventually(session).Should(gexec.Exit(0)) output := string(session.Out.Contents()) Ω(output).Should(And(ContainSubstring("Compiled"), ContainSubstring(customPath+"/mytestapp"))) Ω(path.Join(fullPath, "/mytestapp")).Should(BeAnExistingFile()) }) It("should build with custom directory", func() { session := startGinkgo(fm.PathTo("passing_ginkgo_tests"), "build", "-o", customPath) Eventually(session).Should(gexec.Exit(0)) output := string(session.Out.Contents()) Ω(output).Should(And(ContainSubstring("Compiled"), ContainSubstring(customPath+"/passing_ginkgo_tests.test"))) Ω(path.Join(fullPath, "/passing_ginkgo_tests.test")).Should(BeAnExistingFile()) }) }) golang-github-onsi-ginkgo-v2-2.22.0/integration/preview_test.go000066400000000000000000000031161472321612100244650ustar00rootroot00000000000000package integration_test import ( "os" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" "github.com/onsi/gomega/gbytes" "github.com/onsi/gomega/gexec" ) var _ = Describe("Preview", func() { BeforeEach(func() { fm.MountFixture("preview") }) It("previews the specs, honoring the passed in flags", func() { os.Setenv("PREVIEW", "true") DeferCleanup(os.Unsetenv, "PREVIEW") session := startGinkgo(fm.PathTo("preview"), "--label-filter=elephant") Eventually(session).Should(gexec.Exit(0)) Ω(session).Should(gbytes.Say("passed specs A")) Ω(session).Should(gbytes.Say("passed specs B")) Ω(session).Should(gbytes.Say("skipped specs C")) Ω(session).Should(gbytes.Say("skipped specs D")) }) It("succeeds if you attempt to both run and preview specs", func() { os.Setenv("PREVIEW", "true") DeferCleanup(os.Unsetenv, "PREVIEW") os.Setenv("RUN", "true") DeferCleanup(os.Unsetenv, "RUN") session := startGinkgo(fm.PathTo("preview")) Eventually(session).Should(gexec.Exit(0)) Ω(session).Should(gbytes.Say(`passed specs A`)) Ω(session).Should(gbytes.Say(`passed specs B`)) Ω(session).Should(gbytes.Say(`passed specs C`)) Ω(session).Should(gbytes.Say(`passed specs D`)) Ω(session).Should(gbytes.Say(`Ran 4 of 4 Specs`)) }) It("works if you run in parallel", func() { os.Setenv("PREVIEW", "true") DeferCleanup(os.Unsetenv, "PREVIEW") os.Setenv("RUN", "true") DeferCleanup(os.Unsetenv, "RUN") session := startGinkgo(fm.PathTo("preview"), "-p") Eventually(session).Should(gexec.Exit(0)) Ω(session).Should(gbytes.Say(`Ran 4 of 4 Specs`)) }) }) golang-github-onsi-ginkgo-v2-2.22.0/integration/profiling_go1.21_test.go000066400000000000000000000002551472321612100257650ustar00rootroot00000000000000//go:build !go1.22 package integration_test // lockContestPrefix is the function name prefix with Go 1.21 and earlier const lockContestPrefix = "lock_contest_test.glob.." golang-github-onsi-ginkgo-v2-2.22.0/integration/profiling_go1.22_test.go000066400000000000000000000002511472321612100257620ustar00rootroot00000000000000//go:build go1.22 package integration_test // lockContestPrefix is the function name prefix with Go 1.22 and later const lockContestPrefix = "lock_contest_test.init." golang-github-onsi-ginkgo-v2-2.22.0/integration/profiling_test.go000066400000000000000000000675631472321612100250150ustar00rootroot00000000000000package integration_test import ( "os" "os/exec" "regexp" "strconv" "strings" "time" "fmt" . "github.com/onsi/ginkgo/v2" "github.com/onsi/ginkgo/v2/types" . "github.com/onsi/gomega" "github.com/onsi/gomega/gbytes" "github.com/onsi/gomega/gexec" ) type ProfileLine struct { Index int Caller string CumStat float64 } type ProfileLines []ProfileLine func (lines ProfileLines) FindCaller(caller string) ProfileLine { for _, line := range lines { if strings.Contains(line.Caller, caller) { return line } } Fail(fmt.Sprintf("Could not find caller %s among profile lines %+v.", caller, lines), 1) return ProfileLine{} } var PROFILE_RE = regexp.MustCompile(`[\d\.]+[MBms]*\s*[\d\.]+\%\s*[\d\.]+\%\s*([\d\.]+[MBnms]*)\s*[\d\.]+\%\s*(.*)`) func ParseProfile(binary string, path string) ProfileLines { cmd := exec.Command("go", "tool", "pprof", "-cum", "-top", binary, path) output, err := cmd.CombinedOutput() GinkgoWriter.Printf("Profile for: %s\n%s\n", path, string(output)) ExpectWithOffset(1, err).ShouldNot(HaveOccurred()) out := ProfileLines{} idx := 0 for _, line := range strings.Split(string(output), "\n") { matches := PROFILE_RE.FindStringSubmatch(line) if matches == nil { continue } cumStatEntry := matches[1] var cumStat float64 if strings.Contains(cumStatEntry, "MB") { var err error cumStat, err = strconv.ParseFloat(strings.TrimSuffix(cumStatEntry, "MB"), 64) ExpectWithOffset(1, err).ShouldNot(HaveOccurred()) } else { duration, err := time.ParseDuration(cumStatEntry) ExpectWithOffset(1, err).ShouldNot(HaveOccurred()) cumStat = float64(duration.Milliseconds()) } out = append(out, ProfileLine{ Index: idx, Caller: matches[2], CumStat: cumStat, }) idx += 1 } return out } var _ = Describe("Profiling Specs", func() { Describe("Measuring code coverage", func() { BeforeEach(func() { fm.MountFixture("coverage") }) processCoverageProfile := func(path string) string { profileOutput, err := exec.Command("go", "tool", "cover", fmt.Sprintf("-func=%s", path)).CombinedOutput() ExpectWithOffset(1, err).ShouldNot(HaveOccurred()) return string(profileOutput) } Context("when running a single package in series or in parallel with -cover", func() { It("emits the coverage percentage and generates a cover profile", func() { seriesSession := startGinkgo(fm.PathTo("coverage"), "--no-color", "-cover") Eventually(seriesSession).Should(gexec.Exit(0)) Ω(seriesSession.Out).Should(gbytes.Say(`coverage: 80\.0% of statements`)) seriesCoverage := processCoverageProfile(fm.PathTo("coverage", "coverprofile.out")) fm.RemoveFile("coverage", "coverprofile.out") parallelSession := startGinkgo(fm.PathTo("coverage"), "--no-color", "--procs=2", "-cover") Eventually(parallelSession).Should(gexec.Exit(0)) Ω(parallelSession.Out).Should(gbytes.Say(`coverage: 80\.0% of statements`)) parallelCoverage := processCoverageProfile(fm.PathTo("coverage", "coverprofile.out")) Ω(parallelCoverage).Should(Equal(seriesCoverage)) }) }) Context("with -coverpkg", func() { It("computes coverage of the passed-in additional packages", func() { coverPkgFlag := fmt.Sprintf("-coverpkg=%s,%s", fm.PackageNameFor("coverage"), fm.PackageNameFor("coverage/external_coverage")) seriesSession := startGinkgo(fm.PathTo("coverage"), coverPkgFlag) Eventually(seriesSession).Should(gexec.Exit(0)) Ω(seriesSession.Out).Should(gbytes.Say(`coverage: (80\.0|71\.4)% of statements in`)) seriesCoverage := processCoverageProfile(fm.PathTo("coverage", "coverprofile.out")) fm.RemoveFile("coverage", "coverprofile.out") parallelSession := startGinkgo(fm.PathTo("coverage"), "--no-color", "--procs=2", coverPkgFlag) Eventually(parallelSession).Should(gexec.Exit(0)) Ω(parallelSession.Out).Should(gbytes.Say(`coverage: (80\.0|71\.4)% of statements`)) parallelCoverage := processCoverageProfile(fm.PathTo("coverage", "coverprofile.out")) Ω(parallelCoverage).Should(Equal(seriesCoverage)) }) It("supports ./...", func() { seriesSession := startGinkgo(fm.PathTo("coverage"), "-coverpkg=./...", "-r") Eventually(seriesSession).Should(gexec.Exit(0)) Ω(seriesSession.Out).Should(gbytes.Say(`composite coverage: 100\.0% of statements`)) seriesCoverage := processCoverageProfile(fm.PathTo("coverage", "coverprofile.out")) fm.RemoveFile("coverage", "coverprofile.out") parallelSession := startGinkgo(fm.PathTo("coverage"), "--no-color", "--procs=2", "-coverpkg=./...", "-r") Eventually(parallelSession).Should(gexec.Exit(0)) Ω(parallelSession.Out).Should(gbytes.Say(`composite coverage: 100\.0% of statements`)) parallelCoverage := processCoverageProfile(fm.PathTo("coverage", "coverprofile.out")) Ω(parallelCoverage).Should(Equal(seriesCoverage)) }) }) Context("with a custom profile name", func() { It("generates cover profiles with the specified name", func() { session := startGinkgo(fm.PathTo("coverage"), "--no-color", "-coverprofile=myprofile.out") Eventually(session).Should(gexec.Exit(0)) Ω(session.Out).Should(gbytes.Say(`coverage: 80\.0% of statements`)) Ω(fm.PathTo("coverage", "myprofile.out")).Should(BeAnExistingFile()) Ω(fm.PathTo("coverage", "coverprofile.out")).ShouldNot(BeAnExistingFile()) }) }) Context("when multiple suites are tested", func() { BeforeEach(func() { fm.MountFixture("combined_coverage") }) It("generates a single cover profile", func() { session := startGinkgo(fm.PathTo("combined_coverage"), "--no-color", "--cover", "-r", "--procs=2", "--covermode=atomic") Eventually(session).Should(gexec.Exit(0)) Ω(fm.PathTo("combined_coverage", "coverprofile.out")).Should(BeAnExistingFile()) Ω(fm.PathTo("combined_coverage", "first_package/coverprofile.out")).ShouldNot(BeAnExistingFile()) Ω(fm.PathTo("combined_coverage", "second_package/coverprofile.out")).ShouldNot(BeAnExistingFile()) Ω(fm.PathTo("combined_coverage", "third_package/coverprofile.out")).ShouldNot(BeAnExistingFile()) Ω(session.Out).Should(gbytes.Say(`coverage: 80\.0% of statements`)) Ω(session.Out).Should(gbytes.Say(`coverage: 100\.0% of statements`)) Ω(session.Out).Should(gbytes.Say(`coverage: \[no statements\]`)) By("ensuring there is only one 'mode:' line") re := regexp.MustCompile(`mode: atomic`) content := fm.ContentOf("combined_coverage", "coverprofile.out") matches := re.FindAllStringIndex(content, -1) Ω(len(matches)).Should(Equal(1)) By("emitting a composite coverage score") Ω(session.Out).Should(gbytes.Say(`composite coverage: 90\.0% of statements`)) }) Context("when -keep-separate-coverprofiles is set", func() { It("generates separate coverprofiles", Label("slow"), func() { session := startGinkgo(fm.PathTo("combined_coverage"), "--no-color", "--cover", "-r", "--procs=2", "--keep-separate-coverprofiles") Eventually(session).Should(gexec.Exit(0)) Ω(fm.PathTo("combined_coverage", "coverprofile.out")).ShouldNot(BeAnExistingFile()) Ω(fm.PathTo("combined_coverage", "first_package/coverprofile.out")).Should(BeAnExistingFile()) Ω(fm.PathTo("combined_coverage", "second_package/coverprofile.out")).Should(BeAnExistingFile()) Ω(fm.PathTo("combined_coverage", "third_package/coverprofile.out")).Should(BeAnExistingFile()) }) }) }) Context("when -output-dir is set", func() { BeforeEach(func() { fm.MountFixture("combined_coverage") }) It("puts the cover profile in -output-dir", func() { session := startGinkgo(fm.PathTo("combined_coverage"), "--no-color", "--cover", "-r", "--procs=2", "--output-dir=./output") Eventually(session).Should(gexec.Exit(0)) Ω(fm.PathTo("combined_coverage", "output/coverprofile.out")).Should(BeAnExistingFile()) By("emitting a composite coverage score") Ω(session.Out).Should(gbytes.Say(`composite coverage: 90\.0% of statements`)) }) Context("when -keep-separate-coverprofiles is set", func() { It("puts namespaced coverprofiles in the -output-dir", func() { session := startGinkgo(fm.PathTo("combined_coverage"), "--no-color", "--cover", "-r", "--procs=2", "--output-dir=./output", "--keep-separate-coverprofiles") Eventually(session).Should(gexec.Exit(0)) Ω(fm.PathTo("combined_coverage", "output/coverprofile.out")).ShouldNot(BeAnExistingFile()) Ω(fm.PathTo("combined_coverage", "output/first_package_coverprofile.out")).Should(BeAnExistingFile()) Ω(fm.PathTo("combined_coverage", "output/second_package_coverprofile.out")).Should(BeAnExistingFile()) }) }) }) }) Describe("measuring cpu, memory, block, and mutex profiles", func() { BeforeEach(func() { fm.MountFixture("profile") }) DescribeTable("profile behavior", func(pathToBinary func(string) string, pathToProfile func(string, string) string, args ...string) { args = append([]string{"--no-color", "-r", "--cpuprofile=cpu.out", "--memprofile=mem.out", "--blockprofile=block.out", "--mutexprofile=mutex.out"}, args...) session := startGinkgo(fm.PathTo("profile"), args...) Eventually(session).Should(gexec.Exit(0)) // Verify that the binaries have been preserved and the profiles were generated for _, pkg := range []string{"slow_memory_hog", "block_contest", "lock_contest"} { Ω(pathToBinary(pkg)).Should(BeAnExistingFile()) for _, profile := range []string{"cpu.out", "mem.out", "block.out", "mutex.out"} { Ω(pathToProfile(pkg, profile)).Should(BeAnExistingFile()) } } cpuProfile := ParseProfile(pathToBinary("slow_memory_hog"), pathToProfile("slow_memory_hog", "cpu.out")) // The CPUProfile for the slow_memory_hog test should list the slow_memory_hog.SomethingExpensive functions as one of the most time-consuming functions // we can't assert on time as that will vary from machine to machine, however we _can_ assert that the slow_memory_hog.SomethingExpensive // function has a low index Ω(cpuProfile.FindCaller("slow_memory_hog.SomethingExpensive").Index).Should(BeNumerically("<=", 10)) memProfile := ParseProfile(pathToBinary("slow_memory_hog"), pathToProfile("slow_memory_hog", "mem.out")) // The MemProfile for the slow_memory_hog test should list the slow_memory_hog.SomethingExpensive functions as one of the most memory-consuming functions // Assrting on the amount of memory consumed should be stable across tests as the function always builds a large array of this size Ω(memProfile.FindCaller("slow_memory_hog.SomethingExpensive").CumStat).Should(BeNumerically(">=", 200)) blockProfile := ParseProfile(pathToBinary("block_contest"), pathToProfile("block_contest", "block.out")) // The BlockProfile for the block_contest test should list two channel-reading functions: // block_contest.ReadTheChannel is called 10 times and takes ~5ms per call // block_contest.SlowReadTheChannel is called once and teakes ~500ms per call // Asserting that both times are within a range should be stable across tests // Note: these numbers are adjusted slightly to tolerate variance during test runs Ω(blockProfile.FindCaller("block_contest.ReadTheChannel").CumStat).Should(BeNumerically(">=", 45)) Ω(blockProfile.FindCaller("block_contest.ReadTheChannel").CumStat).Should(BeNumerically("<", 500)) Ω(blockProfile.FindCaller("block_contest.SlowReadTheChannel").CumStat).Should(BeNumerically(">=", 450)) mutexProfile := ParseProfile(pathToBinary("lock_contest"), pathToProfile("lock_contest", "mutex.out")) // The MutexProfile for the lock_contest test should list two functions that wait on a lock. // Unfortunately go doesn't seem to capture the names of the functions - so they're listed here as // lock_contest_test.glob..func1.1 is called 10 times and takes ~5ms per call // lock_contest_test.glob..func2.1 is called once and takes ~500ms per call // Note that in Go 1.22 and later, the functions are called lock_contest_test.init.func1.1 and lock_contest_test.init.func2.1 // Asserting that both times are within a range should be stable across tests. The function names should be as well // but that might become a source of failure in the future // Note: these numbers are adjusted slightly to tolerate variance during test runs Ω(mutexProfile.FindCaller(fmt.Sprintf("%sfunc1.1", lockContestPrefix)).CumStat).Should(BeNumerically(">=", 45)) Ω(mutexProfile.FindCaller(fmt.Sprintf("%sfunc1.1", lockContestPrefix)).CumStat).Should(BeNumerically("<", 500)) Ω(mutexProfile.FindCaller(fmt.Sprintf("%sfunc2.1", lockContestPrefix)).CumStat).Should(BeNumerically(">=", 450)) }, Entry("when running in series", func(pkg string) string { return fm.PathTo("profile", pkg+"/"+pkg+".test") }, func(pkg string, profile string) string { return fm.PathTo("profile", pkg+"/"+profile) }, ), Entry("when running in parallel", func(pkg string) string { return fm.PathTo("profile", pkg+"/"+pkg+".test") }, func(pkg string, profile string) string { return fm.PathTo("profile", pkg+"/"+profile) }, "--procs=3", ), Entry("when --output-dir is set", Label("slow"), func(pkg string) string { return fm.PathTo("profile", "profiles/"+pkg+".test") }, func(pkg string, profile string) string { return fm.PathTo("profile", "profiles/"+pkg+"_"+profile) }, "--procs=3", "--output-dir=./profiles", ), ) Context("when profiling a precompiled binary and output-dir is set", func() { It("copies (not moves) the binary to output-dir", func() { Eventually(startGinkgo(fm.PathTo("profile"), "build", "-r")).Should(gexec.Exit(0)) session := startGinkgo(fm.PathTo("profile"), "--cpuprofile=cpu.out", "--output-dir=./profiles", "./slow_memory_hog/slow_memory_hog.test", "./lock_contest/lock_contest.test", "./block_contest/block_contest.test") Eventually(session).Should(gexec.Exit(0)) for _, pkg := range []string{"slow_memory_hog", "block_contest", "lock_contest"} { Ω(fm.PathTo("profile", pkg, pkg+".test")).Should(BeAnExistingFile(), "preserve the precompiled binary in the package directory") Ω(fm.PathTo("profile", "profiles", pkg+".test")).Should(BeAnExistingFile(), "copy the precompiled binary to the output-dir") Ω(fm.PathTo("profile", "profiles", pkg+"_cpu.out")).Should(BeAnExistingFile(), "generate a correctly named cpu profile") } }) }) }) Context("when a suite has programmatic focus", func() { BeforeEach(func() { fm.MountFixture("focused") fm.MountFixture("coverage") }) Context("and running in series", func() { It("lets the user know that the test was focused so no profiles were generated", func() { session := startGinkgo(fm.PathTo("focused"), "--no-color", "--cover", "--blockprofile=block.out", "--cpuprofile=cpu.out", "--memprofile=mem.out", "--mutexprofile=mutex.out") Eventually(session).Should(gexec.Exit(types.GINKGO_FOCUS_EXIT_CODE)) Ω(session).ShouldNot(gbytes.Say("could not finalize profiles")) Ω(session).Should(gbytes.Say("coverage: no coverfile was generated because specs are programmatically focused")) Ω(session).Should(gbytes.Say("no block profile was generated because specs are programmatically focused")) Ω(session).Should(gbytes.Say("no cpu profile was generated because specs are programmatically focused")) Ω(session).Should(gbytes.Say("no mem profile was generated because specs are programmatically focused")) Ω(session).Should(gbytes.Say("no mutex profile was generated because specs are programmatically focused")) Ω(session).Should(gbytes.Say("no composite coverage computed: all suites included programatically focused specs")) Ω(fm.PathTo("focused", "coverprofile.out")).ShouldNot(BeAnExistingFile()) Ω(fm.PathTo("focused", "block.out")).ShouldNot(BeAnExistingFile()) Ω(fm.PathTo("focused", "mem.out")).ShouldNot(BeAnExistingFile()) Ω(fm.PathTo("focused", "mutex.out")).ShouldNot(BeAnExistingFile()) }) }) Context("and running in parallel", func() { It("lets the user know that the test was focused so no profiles were generated", func() { session := startGinkgo(fm.PathTo("focused"), "--no-color", "--procs=2", "--cover", "--blockprofile=block.out", "--cpuprofile=cpu.out", "--memprofile=mem.out", "--mutexprofile=mutex.out") Eventually(session).Should(gexec.Exit(types.GINKGO_FOCUS_EXIT_CODE)) Ω(session).ShouldNot(gbytes.Say("could not finalize profiles")) Ω(session).Should(gbytes.Say("coverage: no coverfile was generated because specs are programmatically focused")) Ω(session).Should(gbytes.Say("no block profile was generated because specs are programmatically focused")) Ω(session).Should(gbytes.Say("no cpu profile was generated because specs are programmatically focused")) Ω(session).Should(gbytes.Say("no mem profile was generated because specs are programmatically focused")) Ω(session).Should(gbytes.Say("no mutex profile was generated because specs are programmatically focused")) Ω(session).Should(gbytes.Say("no composite coverage computed: all suites included programatically focused specs")) Ω(fm.PathTo("focused", "coverprofile.out")).ShouldNot(BeAnExistingFile()) Ω(fm.PathTo("focused", "block.out")).ShouldNot(BeAnExistingFile()) Ω(fm.PathTo("focused", "mem.out")).ShouldNot(BeAnExistingFile()) Ω(fm.PathTo("focused", "mutex.out")).ShouldNot(BeAnExistingFile()) }) }) Context("and keeping coverage reports separate", func() { It("lets the user know", func() { session := startGinkgo(fm.TmpDir, "-r", "--no-color", "--cover", "--blockprofile=block.out", "--cpuprofile=cpu.out", "--memprofile=mem.out", "--mutexprofile=mutex.out", "--keep-separate-coverprofiles", "--output-dir=./output") Eventually(session).Should(gexec.Exit(types.GINKGO_FOCUS_EXIT_CODE)) Ω(session).Should(gbytes.Say("CoverageFixture Suite")) Ω(session).Should(gbytes.Say("coverage: 80")) Ω(session).Should(gbytes.Say("Focused Suite")) Ω(session).Should(gbytes.Say("coverage: no coverfile was generated because specs are programmatically focused")) Ω(session).Should(gbytes.Say("no block profile was generated because specs are programmatically focused")) Ω(session).Should(gbytes.Say("no cpu profile was generated because specs are programmatically focused")) Ω(session).Should(gbytes.Say("no mem profile was generated because specs are programmatically focused")) Ω(session).Should(gbytes.Say("no mutex profile was generated because specs are programmatically focused")) Ω(session).Should(gbytes.Say("Focused Suite")) Ω(session).Should(gbytes.Say("coverage: no coverfile was generated because specs are programmatically focused")) Ω(session).Should(gbytes.Say("no block profile was generated because specs are programmatically focused")) Ω(session).Should(gbytes.Say("no cpu profile was generated because specs are programmatically focused")) Ω(session).Should(gbytes.Say("no mem profile was generated because specs are programmatically focused")) Ω(session).Should(gbytes.Say("no mutex profile was generated because specs are programmatically focused")) Ω(session).ShouldNot(gbytes.Say("composite coverage")) Ω(fm.ListDir("output")).Should(ConsistOf( "coverage.test", "coverage_block.out", "coverage_coverprofile.out", "coverage_cpu.out", "coverage_mem.out", "coverage_mutex.out", "focused.test", "focused_cpu.out", //this is an inconsistency in go test where the cpu.out file is generated but empty "focused_internal.test", "focused_internal_cpu.out", //this is an inconsistency in go test where the cpu.out file is generated but empty "coverage_additional_spec.test", "coverage_additional_spec_block.out", "coverage_additional_spec_coverprofile.out", "coverage_additional_spec_cpu.out", "coverage_additional_spec_mem.out", "coverage_additional_spec_mutex.out")) }) }) Context("and combining coverage reports", func() { Context("and no suites generate coverage", func() { It("lets the user know", func() { session := startGinkgo(fm.PathTo("focused"), "-r", "--no-color", "--cover", "--blockprofile=block.out", "--cpuprofile=cpu.out", "--memprofile=mem.out", "--mutexprofile=mutex.out", "--output-dir=./output") Eventually(session).Should(gexec.Exit(types.GINKGO_FOCUS_EXIT_CODE)) Ω(session).ShouldNot(gbytes.Say("CoverageFixture Suite")) Ω(session).Should(gbytes.Say("Focused Suite")) Ω(session).Should(gbytes.Say("coverage: no coverfile was generated because specs are programmatically focused")) Ω(session).Should(gbytes.Say("no block profile was generated because specs are programmatically focused")) Ω(session).Should(gbytes.Say("no cpu profile was generated because specs are programmatically focused")) Ω(session).Should(gbytes.Say("no mem profile was generated because specs are programmatically focused")) Ω(session).Should(gbytes.Say("no mutex profile was generated because specs are programmatically focused")) Ω(session).Should(gbytes.Say("Focused Suite")) Ω(session).Should(gbytes.Say("coverage: no coverfile was generated because specs are programmatically focused")) Ω(session).Should(gbytes.Say("no block profile was generated because specs are programmatically focused")) Ω(session).Should(gbytes.Say("no cpu profile was generated because specs are programmatically focused")) Ω(session).Should(gbytes.Say("no mem profile was generated because specs are programmatically focused")) Ω(session).Should(gbytes.Say("no mutex profile was generated because specs are programmatically focused")) Ω(session).Should(gbytes.Say("no composite coverage computed: all suites included programatically focused specs")) Ω(fm.ListDir("focused", "output")).Should(ConsistOf( "focused.test", "focused_cpu.out", //this is an inconsistency in go test where the cpu.out file is generated but empty "internal.test", "internal_cpu.out", //this is an inconsistency in go test where the cpu.out file is generated but empty )) }) }) Context("and at least one suite generates coverage", func() { It("lets the user know", func() { session := startGinkgo(fm.TmpDir, "-r", "--no-color", "--cover", "--blockprofile=block.out", "--cpuprofile=cpu.out", "--memprofile=mem.out", "--mutexprofile=mutex.out", "--output-dir=./output") Eventually(session).Should(gexec.Exit(types.GINKGO_FOCUS_EXIT_CODE)) Ω(session).Should(gbytes.Say("CoverageFixture Suite")) Ω(session).Should(gbytes.Say("coverage: 80")) Ω(session).Should(gbytes.Say("Focused Suite")) Ω(session).Should(gbytes.Say("coverage: no coverfile was generated because specs are programmatically focused")) Ω(session).Should(gbytes.Say("no block profile was generated because specs are programmatically focused")) Ω(session).Should(gbytes.Say("no cpu profile was generated because specs are programmatically focused")) Ω(session).Should(gbytes.Say("no mem profile was generated because specs are programmatically focused")) Ω(session).Should(gbytes.Say("no mutex profile was generated because specs are programmatically focused")) Ω(session).Should(gbytes.Say("Focused Suite")) Ω(session).Should(gbytes.Say("coverage: no coverfile was generated because specs are programmatically focused")) Ω(session).Should(gbytes.Say("no block profile was generated because specs are programmatically focused")) Ω(session).Should(gbytes.Say("no cpu profile was generated because specs are programmatically focused")) Ω(session).Should(gbytes.Say("no mem profile was generated because specs are programmatically focused")) Ω(session).Should(gbytes.Say("no mutex profile was generated because specs are programmatically focused")) Ω(session).Should(gbytes.Say("composite coverage: 80.0% of statements however some suites did not contribute because they included programatically focused specs")) Ω(fm.ListDir("output")).Should(ConsistOf( "coverprofile.out", "coverage.test", "coverage_block.out", "coverage_cpu.out", "coverage_mem.out", "coverage_mutex.out", "focused.test", "focused_cpu.out", //this is an inconsistency in go test where the cpu.out file is generated but empty "focused_internal.test", "focused_internal_cpu.out", //this is an inconsistency in go test where the cpu.out file is generated but empty, "coverage_additional_spec.test", "coverage_additional_spec_block.out", "coverage_additional_spec_cpu.out", "coverage_additional_spec_mem.out", "coverage_additional_spec_mutex.out", )) }) }) }) }) Context("with a read-only tree and a readable output-dir", func() { BeforeEach(func() { fm.MountFixture("profile") sess := startGinkgo(fm.PathTo("profile"), "build", "-r", "-cover") Eventually(sess).Should(gexec.Exit(0)) fm.MkEmpty("output") Ω(os.Chmod(fm.PathTo("profile", "block_contest"), 0555)).Should(Succeed()) Ω(os.Chmod(fm.PathTo("profile", "lock_contest"), 0555)).Should(Succeed()) Ω(os.Chmod(fm.PathTo("profile", "slow_memory_hog"), 0555)).Should(Succeed()) Ω(os.Chmod(fm.PathTo("profile"), 0555)).Should(Succeed()) }) AfterEach(func() { Ω(os.Chmod(fm.PathTo("profile"), 0755)).Should(Succeed()) Ω(os.Chmod(fm.PathTo("profile", "block_contest"), 0755)).Should(Succeed()) Ω(os.Chmod(fm.PathTo("profile", "lock_contest"), 0755)).Should(Succeed()) Ω(os.Chmod(fm.PathTo("profile", "slow_memory_hog"), 0755)).Should(Succeed()) }) It("never tries to write to the tree, and only emits to output-dir", func() { sess := startGinkgo(fm.PathTo("profile"), "--output-dir=../output", "--cpuprofile=cpu.out", "--memprofile=mem.out", "--blockprofile=block.out", "--mutexprofile=mutex.out", "--coverprofile=cover.out", "--json-report=report.json", "--junit-report=report.xml", "--teamcity-report=report.tm", "--procs=2", "./block_contest/block_contest.test", "./lock_contest/lock_contest.test", "./slow_memory_hog/slow_memory_hog.test", ) Eventually(sess).Should(gexec.Exit(0)) Ω(fm.ListDir("output")).Should(ConsistOf( "cover.out", "report.json", "report.xml", "report.tm", "block_contest_cpu.out", "lock_contest_cpu.out", "slow_memory_hog_cpu.out", "block_contest_mem.out", "lock_contest_mem.out", "slow_memory_hog_mem.out", "block_contest_block.out", "lock_contest_block.out", "slow_memory_hog_block.out", "block_contest_mutex.out", "lock_contest_mutex.out", "slow_memory_hog_mutex.out", "block_contest.test", "lock_contest.test", "slow_memory_hog.test", )) }) It("also works when keeping separate reports and profiles and only emits to output-dir", func() { sess := startGinkgo(fm.PathTo("profile"), "--output-dir=../output", "--cpuprofile=cpu.out", "--memprofile=mem.out", "--blockprofile=block.out", "--mutexprofile=mutex.out", "--coverprofile=cover.out", "--json-report=report.json", "--junit-report=report.xml", "--teamcity-report=report.tm", "--procs=2", "--keep-separate-coverprofiles", "--keep-separate-reports", "./block_contest/block_contest.test", "./lock_contest/lock_contest.test", "./slow_memory_hog/slow_memory_hog.test", ) Eventually(sess).Should(gexec.Exit(0)) Ω(fm.ListDir("output")).Should(ConsistOf( "block_contest_cover.out", "lock_contest_cover.out", "slow_memory_hog_cover.out", "block_contest_report.json", "lock_contest_report.json", "slow_memory_hog_report.json", "block_contest_report.xml", "lock_contest_report.xml", "slow_memory_hog_report.xml", "block_contest_report.tm", "lock_contest_report.tm", "slow_memory_hog_report.tm", "block_contest_cpu.out", "lock_contest_cpu.out", "slow_memory_hog_cpu.out", "block_contest_mem.out", "lock_contest_mem.out", "slow_memory_hog_mem.out", "block_contest_block.out", "lock_contest_block.out", "slow_memory_hog_block.out", "block_contest_mutex.out", "lock_contest_mutex.out", "slow_memory_hog_mutex.out", "block_contest.test", "lock_contest.test", "slow_memory_hog.test", )) }) }) }) golang-github-onsi-ginkgo-v2-2.22.0/integration/progress_test.go000066400000000000000000000113311472321612100246460ustar00rootroot00000000000000package integration_test import ( "fmt" "os" "path/filepath" "regexp" "strconv" "syscall" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" "github.com/onsi/gomega/gbytes" "github.com/onsi/gomega/gexec" ) var _ = Describe("Emitting progress", func() { Describe("progress reports", func() { BeforeEach(func() { fm.MountFixture("progress_report") }) It("emits progress when a singal is sent and when tests take too long", func() { session := startGinkgo(fm.PathTo("progress_report"), "--poll-progress-after=1500ms", "--poll-progress-interval=200ms", "--no-color") Eventually(session).Should(gbytes.Say(`READY `)) buf := make([]byte, 128) _, err := session.Out.Read(buf) Ω(err).ShouldNot(HaveOccurred()) pid, err := strconv.Atoi(regexp.MustCompile(`\d*`).FindString(string(buf))) Ω(err).ShouldNot(HaveOccurred()) syscall.Kill(pid, syscall.SIGUSR1) Eventually(session).Should(gbytes.Say(`can track on demand \(Spec Runtime:`)) Eventually(session).Should(gbytes.Say(`In \[It\] \(Node Runtime:`)) Eventually(session).Should(gbytes.Say(`\[By Step\] Step B \(Step Runtime:`)) Eventually(session).Should(gbytes.Say(`Begin Captured GinkgoWriter Output`)) Eventually(session).Should(gbytes.Say(`\.\.\.`)) for i := 3; i <= 12; i++ { Eventually(session).Should(gbytes.Say(fmt.Sprintf("ginkgo-writer-output-%d", i))) } Eventually(session).Should(gbytes.Say(`|\s*fmt\.Println\("READY"\)`)) Eventually(session).Should(gbytes.Say(`>\s*time\.Sleep\(time\.Second\)`)) //first poll Eventually(session).Should(gbytes.Say(`--poll-progress-after tracks things that take too long \(Spec Runtime: 1\.5\d*s\)`)) Eventually(session).Should(gbytes.Say(`>\s*time.Sleep\(2 \* time\.Second\)`)) Eventually(session).Should(gbytes.Say(`Begin Additional Progress Reports >>`)) Eventually(session).Should(gbytes.Say(`Some global information: 1`)) Eventually(session).Should(gbytes.Say(`<< End Additional Progress Reports`)) //second poll Eventually(session).Should(gbytes.Say(`--poll-progress-after tracks things that take too long \(Spec Runtime: 1\.7\d*s\)`)) Eventually(session).Should(gbytes.Say(`>\s*time.Sleep\(2 \* time\.Second\)`)) //decorator poll Eventually(session).Should(gbytes.Say(`decorator tracks things that take too long \(Spec Runtime: 5[\.\d]*ms\)`)) Eventually(session).Should(gbytes.Say(`>\s*time\.Sleep\(1 \* time\.Second\)`)) Eventually(session).Should(gexec.Exit(0)) }) It("allows the user to specify a source-root to find source code files", func() { // first we build the test with -gcflags=all=-trimpath= to ensure // that stack traces do not contain absolute paths path, err := filepath.Abs(fm.PathTo("progress_report")) Ω(err).ShouldNot(HaveOccurred()) session := startGinkgo(fm.PathTo("progress_report"), "build", `-gcflags=-trimpath=`+path+``) Eventually(session).Should(gexec.Exit(0)) // now we move the compiled test binary to a separate directory fm.MkEmpty("progress_report/suite") os.Rename(fm.PathTo("progress_report", "progress_report.test"), fm.PathTo("progress_report", "suite", "progress_report.test")) //and we run and confirm that we don't see the expected source code session = startGinkgo(fm.PathTo("progress_report", "suite"), "--poll-progress-after=1500ms", "--poll-progress-interval=200ms", "--no-color", "-label-filter=one-second", "./progress_report.test") Eventually(session).Should(gexec.Exit(0)) Ω(session).ShouldNot(gbytes.Say(`>\s*time.Sleep\(1 \* time\.Second\)`)) // now we run, but configured with source-root and see that we have the file // note that multipel source-roots can be passed in session = startGinkgo(fm.PathTo("progress_report", "suite"), "--poll-progress-after=1500ms", "--poll-progress-interval=200ms", "--no-color", "-label-filter=one-second", "--source-root=/tmp", "--source-root="+path, "./progress_report.test") Eventually(session).Should(gbytes.Say(`>\s*time\.Sleep\(1 \* time\.Second\)`)) Eventually(session).Should(gexec.Exit(0)) }) It("emits progress immediately and includes process information when running in parallel", func() { session := startGinkgo(fm.PathTo("progress_report"), "--poll-progress-after=1500ms", "--poll-progress-interval=200ms", "--no-color", "-procs=2", "-label-filter=parallel") Eventually(session).Should(gexec.Exit(0)) Eventually(session.Out.Contents()).Should(ContainSubstring(`Progress Report for Ginkgo Process #1`)) Eventually(session.Out.Contents()).Should(ContainSubstring(`Progress Report for Ginkgo Process #2`)) Eventually(session.Out.Contents()).Should(ContainSubstring(`Some global information: 1`)) Eventually(session.Out.Contents()).Should(ContainSubstring(`Some global information: 2`)) }) }) }) golang-github-onsi-ginkgo-v2-2.22.0/integration/repeat_test.go000066400000000000000000000074101472321612100242650ustar00rootroot00000000000000package integration_test import ( "strings" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" "github.com/onsi/gomega/gbytes" "github.com/onsi/gomega/gexec" ) func extractRandomSeeds(content string) []string { lines := strings.Split(content, "\n") randomSeeds := []string{} for _, line := range lines { if strings.Contains(line, "Random Seed:") { randomSeeds = append(randomSeeds, strings.Split(line, ": ")[1]) } } return randomSeeds } var _ = Describe("Repeat", func() { Context("when a test attempts to invoke RunSpecs twice", func() { BeforeEach(func() { fm.MountFixture("rerun_specs") }) It("errors out and tells the user not to do that", func() { session := startGinkgo(fm.PathTo("rerun_specs")) Eventually(session).Should(gexec.Exit()) Ω(session).Should(gbytes.Say("It looks like you are calling RunSpecs more than once.")) }) }) Context("when told to keep going --until-it-fails", func() { BeforeEach(func() { fm.MountFixture("eventually_failing") }) It("should keep rerunning the tests, until a failure occurs", func() { session := startGinkgo(fm.PathTo("eventually_failing"), "--until-it-fails", "--no-color") Eventually(session).Should(gexec.Exit(1)) Ω(session).Should(gbytes.Say("This was attempt #1")) Ω(session).Should(gbytes.Say("This was attempt #2")) Ω(session).Should(gbytes.Say("Tests failed on attempt #3")) //it should change the random seed between each test randomSeeds := extractRandomSeeds(string(session.Out.Contents())) Ω(randomSeeds[0]).ShouldNot(Equal(randomSeeds[1])) Ω(randomSeeds[1]).ShouldNot(Equal(randomSeeds[2])) Ω(randomSeeds[0]).ShouldNot(Equal(randomSeeds[2])) }) }) Context("when told to --repeat", func() { BeforeEach(func() { fm.MountFixture("eventually_failing") }) Context("when the test passes for N repetitions", func() { It("should run the tests N+1 times and report success", func() { session := startGinkgo(fm.PathTo("eventually_failing"), "--repeat=1", "--no-color") Eventually(session).Should(gexec.Exit(0)) Ω(session).Should(gbytes.Say("This was attempt 1 of 2")) //it should change the random seed between each test randomSeeds := extractRandomSeeds(string(session.Out.Contents())) Ω(randomSeeds[0]).ShouldNot(Equal(randomSeeds[1])) }) }) Context("when the test eventually fails", func() { It("should report failure and stop running", func() { session := startGinkgo(fm.PathTo("eventually_failing"), "--repeat=3", "--no-color") Eventually(session).Should(gexec.Exit(1)) Ω(session).Should(gbytes.Say("This was attempt 1 of 4")) Ω(session).Should(gbytes.Say("This was attempt 2 of 4")) Ω(session).Should(gbytes.Say("Tests failed on attempt #3")) //it should change the random seed between each test randomSeeds := extractRandomSeeds(string(session.Out.Contents())) Ω(randomSeeds[0]).ShouldNot(Equal(randomSeeds[1])) Ω(randomSeeds[1]).ShouldNot(Equal(randomSeeds[2])) Ω(randomSeeds[0]).ShouldNot(Equal(randomSeeds[2])) }) }) }) Context("if both --repeat and --until-it-fails are set", func() { BeforeEach(func() { fm.MountFixture("eventually_failing") }) It("errors out early", func() { session := startGinkgo(fm.PathTo("eventually_failing"), "--repeat=3", "--until-it-fails", "--no-color") Eventually(session).Should(gexec.Exit(1)) Ω(session.Err).Should(gbytes.Say("--repeat and --until-it-fails are both set")) }) }) Context("if MustPassRepeatedly is set at suite config level", func() { BeforeEach(func() { fm.MountFixture("config_override_must_pass_repeatedly") }) It("it should override node decorator", func() { session := startGinkgo(fm.PathTo("config_override_must_pass_repeatedly")) Eventually(session).Should(gexec.Exit(0)) }) }) }) golang-github-onsi-ginkgo-v2-2.22.0/integration/report_entries_test.go000066400000000000000000000200161472321612100260460ustar00rootroot00000000000000package integration_test import ( "time" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/ginkgo/v2/internal/test_helpers" "github.com/onsi/ginkgo/v2/types" . "github.com/onsi/gomega" "github.com/onsi/gomega/gbytes" "github.com/onsi/gomega/gexec" ) var _ = Describe("ReportEntries", func() { var output string Describe("when running a test that adds report entries", Ordered, func() { BeforeAll(func() { fm.MountFixture("report_entries") session := startGinkgo(fm.PathTo("report_entries"), "--no-color", "--procs=2", "--json-report=out.json", "--junit-report=out.xml") Eventually(session).Should(gexec.Exit(1)) output = string(session.Out.Contents()) }) It("should honor the report visibilities and stringer formatting when emitting output", func() { Ω(output).Should(ContainSubstring("passes-first-report")) Ω(output).Should(ContainSubstring("pass-bob 1")) Ω(output).Should(ContainSubstring("passes-second-report")) Ω(output).Should(ContainSubstring("passes-third-report")) Ω(output).Should(ContainSubstring("passes-pointer-report")) Ω(output).Should(ContainSubstring("passed 4")) Ω(output).ShouldNot(ContainSubstring("passes-failure-report")) Ω(output).ShouldNot(ContainSubstring("passes-never-see-report")) Ω(output).Should(ContainSubstring("fails-first-report")) Ω(output).Should(ContainSubstring("fail-bob 1")) Ω(output).Should(ContainSubstring("fails-second-report")) Ω(output).Should(ContainSubstring("fails-third-report")) Ω(output).Should(ContainSubstring("fails-pointer-report")) Ω(output).Should(ContainSubstring("failed 4")) Ω(output).Should(ContainSubstring("fails-failure-report")) Ω(output).ShouldNot(ContainSubstring("fails-never-see-report")) Ω(output).ShouldNot(ContainSubstring("registers a hidden AddReportEntry")) }) It("captures all report entries in the JSON report", func() { report := fm.LoadJSONReports("report_entries", "out.json")[0] reports := Reports(report.SpecReports) passes := reports.Find("passes") Ω(passes.ReportEntries).Should(HaveLen(6)) Ω(passes.ReportEntries[0].Name).Should(Equal("passes-first-report")) Ω(passes.ReportEntries[0].GetRawValue()).Should(Equal(map[string]interface{}{"Label": "pass-bob", "Count": float64(1)})) Ω(passes.ReportEntries[0].StringRepresentation()).Should(Equal("{{red}}pass-bob {{green}}1{{/}}")) Ω(passes.ReportEntries[0].Time).Should(BeTemporally("~", time.Now(), time.Minute)) Ω(passes.ReportEntries[1].Name).Should(Equal("passes-second-report")) Ω(passes.ReportEntries[1].GetRawValue()).Should(BeNil()) Ω(passes.ReportEntries[1].StringRepresentation()).Should(BeZero()) Ω(passes.ReportEntries[2].Name).Should(Equal("passes-third-report")) Ω(passes.ReportEntries[2].GetRawValue()).Should(Equal(float64(3))) Ω(passes.ReportEntries[2].StringRepresentation()).Should(Equal("3")) Ω(passes.ReportEntries[3].Name).Should(Equal("passes-pointer-report")) Ω(passes.ReportEntries[3].GetRawValue()).Should(Equal(map[string]interface{}{"Label": "passed", "Count": float64(4)})) Ω(passes.ReportEntries[3].StringRepresentation()).Should(Equal("{{red}}passed {{green}}4{{/}}")) Ω(passes.ReportEntries[4].Name).Should(Equal("passes-failure-report")) Ω(passes.ReportEntries[4].GetRawValue()).Should(Equal(float64(5))) Ω(passes.ReportEntries[4].StringRepresentation()).Should(Equal("5")) Ω(passes.ReportEntries[5].Name).Should(Equal("passes-never-see-report")) Ω(passes.ReportEntries[5].GetRawValue()).Should(Equal(float64(6))) Ω(passes.ReportEntries[5].StringRepresentation()).Should(Equal("6")) fails := reports.Find("fails") Ω(fails.ReportEntries[0].Name).Should(Equal("fails-first-report")) Ω(fails.ReportEntries[0].GetRawValue()).Should(Equal(map[string]interface{}{"Label": "fail-bob", "Count": float64(1)})) Ω(fails.ReportEntries[0].StringRepresentation()).Should(Equal("{{red}}fail-bob {{green}}1{{/}}")) Ω(fails.ReportEntries[0].Time).Should(BeTemporally("~", time.Now(), time.Minute)) Ω(fails.ReportEntries[1].Name).Should(Equal("fails-second-report")) Ω(fails.ReportEntries[1].GetRawValue()).Should(BeNil()) Ω(fails.ReportEntries[1].StringRepresentation()).Should(BeZero()) Ω(fails.ReportEntries[2].Name).Should(Equal("fails-third-report")) Ω(fails.ReportEntries[2].GetRawValue()).Should(Equal(float64(3))) Ω(fails.ReportEntries[2].StringRepresentation()).Should(Equal("3")) Ω(fails.ReportEntries[3].Name).Should(Equal("fails-pointer-report")) Ω(fails.ReportEntries[3].GetRawValue()).Should(Equal(map[string]interface{}{"Label": "failed", "Count": float64(4)})) Ω(fails.ReportEntries[3].StringRepresentation()).Should(Equal("{{red}}failed {{green}}4{{/}}")) Ω(fails.ReportEntries[4].Name).Should(Equal("fails-failure-report")) Ω(fails.ReportEntries[4].GetRawValue()).Should(Equal(float64(5))) Ω(fails.ReportEntries[4].StringRepresentation()).Should(Equal("5")) Ω(fails.ReportEntries[5].Name).Should(Equal("fails-never-see-report")) Ω(fails.ReportEntries[5].GetRawValue()).Should(Equal(float64(6))) Ω(fails.ReportEntries[5].StringRepresentation()).Should(Equal("6")) by := reports.Find("has By entries") byEvents := by.SpecEvents.WithType(types.SpecEventByStart | types.SpecEventByEnd) Ω(byEvents[0].SpecEventType).Should(Equal(types.SpecEventByStart)) Ω(byEvents[0].Message).Should(Equal("registers a By event")) Ω(byEvents[1].SpecEventType).Should(Equal(types.SpecEventByStart)) Ω(byEvents[1].Message).Should(Equal("includes durations")) Ω(byEvents[2].SpecEventType).Should(Equal(types.SpecEventByEnd)) Ω(byEvents[2].Message).Should(Equal("includes durations")) Ω(byEvents[2].Duration).Should(BeNumerically("~", time.Millisecond*100, time.Millisecond*100)) }) It("captures all report entries in the JUnit report", func() { junit := fm.LoadJUnitReport("report_entries", "out.xml").TestSuites[0] var content string for _, testCase := range junit.TestCases { if testCase.Name == "[It] top-level container passes" { content = testCase.SystemErr } } buf := gbytes.BufferWithBytes([]byte(content)) Ω(buf).Should(gbytes.Say("passes-first-report")) Ω(buf).Should(gbytes.Say(`report_entries/report_entries_fixture_suite_test\.go:\d+`)) Ω(buf).Should(gbytes.Say("pass-bob 1")) Ω(buf).Should(gbytes.Say("passes-second-report")) Ω(buf).Should(gbytes.Say("passes-third-report")) Ω(buf).Should(gbytes.Say("3")) Ω(buf).Should(gbytes.Say("passes-pointer-report")) Ω(buf).Should(gbytes.Say("passed 4")) Ω(buf).Should(gbytes.Say("passes-failure-report")) Ω(buf).Should(gbytes.Say("5")) Ω(buf).Should(gbytes.Say("passes-never-see-report")) Ω(buf).Should(gbytes.Say("6")) }) }) Describe("when running in verbose mode", func() { BeforeEach(func() { fm.MountFixture("report_entries") session := startGinkgo(fm.PathTo("report_entries"), "--no-color", "--procs=2", "-v") Eventually(session).Should(gexec.Exit(1)) output = string(session.Out.Contents()) }) It("should honor the report visibilities and stringer formatting when emitting output", func() { Ω(output).Should(ContainSubstring("passes-first-report")) Ω(output).Should(ContainSubstring("pass-bob 1")) Ω(output).Should(ContainSubstring("passes-second-report")) Ω(output).Should(ContainSubstring("passes-third-report")) Ω(output).Should(ContainSubstring("passes-pointer-report")) Ω(output).Should(ContainSubstring("passed 4")) Ω(output).Should(ContainSubstring("passes-failure-report")) Ω(output).ShouldNot(ContainSubstring("passes-never-see-report")) Ω(output).Should(ContainSubstring("fails-first-report")) Ω(output).Should(ContainSubstring("fail-bob 1")) Ω(output).Should(ContainSubstring("fails-second-report")) Ω(output).Should(ContainSubstring("fails-third-report")) Ω(output).Should(ContainSubstring("fails-pointer-report")) Ω(output).Should(ContainSubstring("failed 4")) Ω(output).Should(ContainSubstring("fails-failure-report")) Ω(output).ShouldNot(ContainSubstring("fails-never-see-report")) }) }) }) golang-github-onsi-ginkgo-v2-2.22.0/integration/reporting_test.go000066400000000000000000000557211472321612100250260ustar00rootroot00000000000000package integration_test import ( "fmt" "os" "strings" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/ginkgo/v2/internal/test_helpers" "github.com/onsi/ginkgo/v2/reporters" "github.com/onsi/ginkgo/v2/types" . "github.com/onsi/gomega" "github.com/onsi/gomega/gbytes" "github.com/onsi/gomega/gexec" ) var _ = Describe("Reporting", func() { BeforeEach(func() { fm.MountFixture("reporting") }) Describe("in-suite reporting with ReportBeforeEach, ReportAfterEach, ReportBeforeSuite and ReportAfterSuite", func() { It("preview thes uite via ReportBeforeSuite", func() { session := startGinkgo(fm.PathTo("reporting"), "--no-color", "--seed=17", "--procs=2") Eventually(session).Should(gexec.Exit(1)) report, err := os.ReadFile(fm.PathTo("reporting", "report-before-suite-1.out")) Ω(err).ShouldNot(HaveOccurred()) lines := strings.Split(string(report), "\n") Ω(lines).Should(ConsistOf( "ReportingFixture Suite - 17", "7 of 8", )) }) It("reports on each test via ReportBeforeEach", func() { session := startGinkgo(fm.PathTo("reporting"), "--no-color") Eventually(session).Should(gexec.Exit(1)) report, err := os.ReadFile(fm.PathTo("reporting", "report-before-each.out")) Ω(err).ShouldNot(HaveOccurred()) lines := strings.Split(string(report), "\n") Ω(lines).Should(ConsistOf( "passes - INVALID SPEC STATE", "is labelled - INVALID SPEC STATE", "fails - INVALID SPEC STATE", "panics - INVALID SPEC STATE", "has a progress report - INVALID SPEC STATE", "is pending - pending", "is skipped - INVALID SPEC STATE", "times out and fails during cleanup - INVALID SPEC STATE", "", )) }) It("reports on each test via ReportAfterEach", func() { session := startGinkgo(fm.PathTo("reporting"), "--no-color") Eventually(session).Should(gexec.Exit(1)) report, err := os.ReadFile(fm.PathTo("reporting", "report-after-each.out")) Ω(err).ShouldNot(HaveOccurred()) lines := strings.Split(string(report), "\n") Ω(lines).Should(ConsistOf( "passes - passed", "is labelled - passed", "fails - failed", "panics - panicked", "has a progress report - passed", "is pending - pending", "is skipped - skipped", "times out and fails during cleanup - timedout", "", )) }) It("reports on all the tests via ReportAfterSuite", func() { session := startGinkgo(fm.PathTo("reporting"), "--no-color", "--seed=17") Eventually(session).Should(gexec.Exit(1)) report, err := os.ReadFile(fm.PathTo("reporting", "report-after-suite.out")) Ω(err).ShouldNot(HaveOccurred()) lines := strings.Split(string(report), "\n") Ω(lines).Should(ConsistOf( "ReportingFixture Suite - 17", "1: [ReportBeforeSuite] - passed", "1: [BeforeSuite] - passed", "passes - passed", "is labelled - passed", "fails - failed", "panics - panicked", "has a progress report - passed", "is pending - pending", "is skipped - skipped", "times out and fails during cleanup - timedout", "1: [DeferCleanup (Suite)] - passed", "1: [DeferCleanup (Suite)] - passed", "", )) }) Context("when running in parallel", func() { It("reports only runs ReportBeforeSuite on proc 1 and reports on all the tests via ReportAfterSuite", func() { session := startGinkgo(fm.PathTo("reporting"), "--no-color", "--seed=17", "--procs=2") Eventually(session).Should(gexec.Exit(1)) By("validating the ReportBeforeSuite report") report, err := os.ReadFile(fm.PathTo("reporting", "report-before-suite-1.out")) Ω(err).ShouldNot(HaveOccurred()) lines := strings.Split(string(report), "\n") Ω(lines).Should(ConsistOf( "ReportingFixture Suite - 17", "7 of 8", )) By("ensuring there is only one ReportBeforeSuite report") Ω(fm.PathTo("reporting", "report-before-suite-2.out")).ShouldNot(BeARegularFile()) By("validating the ReportAfterSuite report") report, err = os.ReadFile(fm.PathTo("reporting", "report-after-suite.out")) Ω(err).ShouldNot(HaveOccurred()) lines = strings.Split(string(report), "\n") Ω(lines).Should(ConsistOf( "ReportingFixture Suite - 17", "1: [ReportBeforeSuite] - passed", "1: [BeforeSuite] - passed", "2: [BeforeSuite] - passed", "passes - passed", "is labelled - passed", "fails - failed", "panics - panicked", "has a progress report - passed", "is pending - pending", "is skipped - skipped", "times out and fails during cleanup - timedout", "1: [DeferCleanup (Suite)] - passed", "1: [DeferCleanup (Suite)] - passed", "2: [DeferCleanup (Suite)] - passed", "2: [DeferCleanup (Suite)] - passed", "", )) }) }) Context("when a ReportAfterSuite node fails", func() { It("reports on it", func() { session := startGinkgo(fm.PathTo("reporting"), "--no-color", "--seed=17", "--procs=2") Eventually(session).Should(gexec.Exit(1)) Ω(string(session.Out.Contents())).Should(ContainSubstring("[ReportAfterSuite] my report")) Ω(string(session.Out.Contents())).Should(ContainSubstring("fail!\n In [ReportAfterSuite] at:")) }) }) }) Describe("JSON and JUnit reporting", func() { checkJSONReport := func(report types.Report) { Ω(report.SuitePath).Should(Equal(fm.AbsPathTo("reporting"))) Ω(report.SuiteDescription).Should(Equal("ReportingFixture Suite")) Ω(report.SuiteConfig.ParallelTotal).Should(Equal(2)) Ω(report.SpecReports).Should(HaveLen(16)) //8 tests + (1 before-suite + 2 defercleanup after-suite)*2(nodes) + 1 report-before-suite + 1 report-after-suite specReports := Reports(report.SpecReports) Ω(specReports.WithLeafNodeType(types.NodeTypeIt)).Should(HaveLen(8)) Ω(specReports.Find("passes")).Should(HavePassed()) Ω(specReports.Find("is labelled")).Should(HavePassed()) Ω(specReports.Find("is labelled").Labels()).Should(Equal([]string{"dog", "cat"})) Ω(specReports.Find("fails")).Should(HaveFailed("fail!", types.FailureNodeIsLeafNode, CapturedGinkgoWriterOutput("some ginkgo-writer output"))) Ω(specReports.Find("panics")).Should(HavePanicked("boom")) Ω(specReports.Find("is pending")).Should(BePending()) Ω(specReports.Find("is skipped").State).Should(Equal(types.SpecStateSkipped)) Ω(specReports.Find("times out and fails during cleanup")).Should(HaveTimedOut("A node timeout occurred")) Ω(specReports.Find("times out and fails during cleanup").AdditionalFailures[0].Failure.Message).Should(Equal("double-whammy")) Ω(specReports.Find("times out and fails during cleanup").AdditionalFailures[0].Failure.FailureNodeType).Should(Equal(types.NodeTypeCleanupAfterEach)) Ω(specReports.FindByLeafNodeType(types.NodeTypeReportBeforeSuite)).Should(HavePassed()) Ω(specReports.Find("my report")).Should(HaveFailed("fail!", types.FailureNodeIsLeafNode, types.NodeTypeReportAfterSuite)) Ω(specReports.FindByLeafNodeType(types.NodeTypeBeforeSuite)).Should(HavePassed()) Ω(specReports.FindByLeafNodeType(types.NodeTypeCleanupAfterSuite)).Should(HavePassed()) //check that progress reporst are correctly embedded Ω(specReports.Find("passes").ProgressReports).Should(BeEmpty()) Ω(specReports.Find("has a progress report").ProgressReports).ShouldNot(BeEmpty()) var highlightedFunction types.FunctionCall for _, functionCall := range specReports.Find("has a progress report").ProgressReports[0].SpecGoroutine().Stack { if functionCall.Highlight { highlightedFunction = functionCall break } } Ω(highlightedFunction).ShouldNot(BeZero()) Ω(highlightedFunction.Source[highlightedFunction.SourceHighlight]).Should(ContainSubstring("time.Sleep(")) } checkJSONSubpackageReport := func(report types.Report) { Ω(report.SuitePath).Should(Equal(fm.AbsPathTo("reporting", "reporting_sub_package"))) Ω(report.SuiteDescription).Should(Equal("Reporting SubPackage Suite")) Ω(report.SuiteConfig.ParallelTotal).Should(Equal(2)) Ω(report.SpecReports).Should(HaveLen(3)) specReports := Reports(report.SpecReports) Ω(specReports.Find("passes here too")).Should(HavePassed()) Ω(specReports.Find("fails here too")).Should(HaveFailed("fail!", types.FailureNodeIsLeafNode, CapturedStdOutput("some std output"))) Ω(specReports.Find("panics here too")).Should(HavePanicked("bam!")) } checkJSONFailedCompilationReport := func(report types.Report) { Ω(report.SuitePath).Should(Equal(fm.AbsPathTo("reporting", "malformed_sub_package"))) Ω(report.SuiteDescription).Should(Equal("")) Ω(report.SuiteConfig.RandomSeed).Should(Equal(int64(17))) Ω(report.SpecialSuiteFailureReasons).Should(ContainElement(ContainSubstring("Failed to compile malformed_sub_package:"))) Ω(report.SpecReports).Should(HaveLen(0)) } getTestCase := func(name string, tests []reporters.JUnitTestCase) reporters.JUnitTestCase { for _, test := range tests { if test.Name == name { return test } } return reporters.JUnitTestCase{} } checkJUnitReport := func(suite reporters.JUnitTestSuite) { Ω(suite.Name).Should(Equal("ReportingFixture Suite")) Ω(suite.Package).Should(Equal(fm.AbsPathTo("reporting"))) Ω(suite.Tests).Should(Equal(16)) Ω(suite.Disabled).Should(Equal(1)) Ω(suite.Skipped).Should(Equal(1)) Ω(suite.Errors).Should(Equal(1)) Ω(suite.Failures).Should(Equal(3)) Ω(suite.Properties.WithName("SuiteSucceeded")).Should(Equal("false")) Ω(suite.Properties.WithName("RandomSeed")).Should(Equal("17")) Ω(suite.Properties.WithName("ParallelTotal")).Should(Equal("2")) Ω(getTestCase("[ReportBeforeSuite]", suite.TestCases).Status).Should(Equal("passed")) Ω(getTestCase("[BeforeSuite]", suite.TestCases).Status).Should(Equal("passed")) Ω(getTestCase("[It] reporting test passes", suite.TestCases).Classname).Should(Equal("ReportingFixture Suite")) Ω(getTestCase("[It] reporting test passes", suite.TestCases).Status).Should(Equal("passed")) Ω(getTestCase("[It] reporting test passes", suite.TestCases).Failure).Should(BeNil()) Ω(getTestCase("[It] reporting test passes", suite.TestCases).Error).Should(BeNil()) Ω(getTestCase("[It] reporting test passes", suite.TestCases).Skipped).Should(BeNil()) Ω(getTestCase("[It] reporting test labelled tests is labelled [dog, cat]", suite.TestCases).Status).Should(Equal("passed")) Ω(getTestCase("[It] reporting test panics", suite.TestCases).Status).Should(Equal("panicked")) Ω(getTestCase("[It] reporting test panics", suite.TestCases).Error.Message).Should(Equal("boom")) Ω(getTestCase("[It] reporting test panics", suite.TestCases).Error.Type).Should(Equal("panicked")) Ω(getTestCase("[It] reporting test fails", suite.TestCases).Failure.Message).Should(Equal("fail!")) Ω(getTestCase("[It] reporting test fails", suite.TestCases).Status).Should(Equal("failed")) Ω(getTestCase("[It] reporting test fails", suite.TestCases).SystemErr).Should(ContainSubstring("some ginkgo-writer output")) Ω(getTestCase("[It] reporting test is pending", suite.TestCases).Status).Should(Equal("pending")) Ω(getTestCase("[It] reporting test is pending", suite.TestCases).Skipped.Message).Should(Equal("pending")) Ω(getTestCase("[DeferCleanup (Suite)]", suite.TestCases).Status).Should(Equal("passed")) Ω(getTestCase("[ReportAfterSuite] my report", suite.TestCases).Status).Should(Equal("failed")) Ω(getTestCase("[It] reporting test is skipped", suite.TestCases).Status).Should(Equal("skipped")) Ω(getTestCase("[It] reporting test is skipped", suite.TestCases).Skipped.Message).Should(Equal("skipped - skip")) Ω(getTestCase("[It] reporting test times out and fails during cleanup", suite.TestCases).Status).Should(Equal("timedout")) Ω(getTestCase("[It] reporting test times out and fails during cleanup", suite.TestCases).Failure.Message).Should(Equal("A node timeout occurred")) Ω(getTestCase("[It] reporting test times out and fails during cleanup", suite.TestCases).Failure.Description).Should(ContainSubstring("<-ctx.Done()")) Ω(getTestCase("[It] reporting test times out and fails during cleanup", suite.TestCases).Failure.Description).Should(ContainSubstring("[FAILED] A node timeout occurred and then the following failure was recorded in the timedout node before it exited:\nfailure-after-timeout")) Ω(getTestCase("[It] reporting test times out and fails during cleanup", suite.TestCases).SystemErr).Should(ContainSubstring("[FAILED] double-whammy")) buf := gbytes.NewBuffer() fmt.Fprintf(buf, getTestCase("[It] reporting test has a progress report", suite.TestCases).SystemErr) Ω(buf).Should(gbytes.Say(`some ginkgo-writer preamble`)) Ω(buf).Should(gbytes.Say(`reporting test has a progress report \(Spec Runtime:`)) Ω(buf).Should(gbytes.Say(`goroutine \d+ \[sleep\]`)) Ω(buf).Should(gbytes.Say(`>\s*time\.Sleep\(`)) Ω(buf).Should(gbytes.Say(`some ginkgo-writer postamble`)) } checkJUnitSubpackageReport := func(suite reporters.JUnitTestSuite) { Ω(suite.Name).Should(Equal("Reporting SubPackage Suite")) Ω(suite.Package).Should(Equal(fm.AbsPathTo("reporting", "reporting_sub_package"))) Ω(suite.Tests).Should(Equal(3)) Ω(suite.Errors).Should(Equal(1)) Ω(suite.Failures).Should(Equal(1)) } checkJUnitFailedCompilationReport := func(suite reporters.JUnitTestSuite) { Ω(suite.Name).Should(Equal("")) Ω(suite.Package).Should(Equal(fm.AbsPathTo("reporting", "malformed_sub_package"))) Ω(suite.Properties.WithName("SpecialSuiteFailureReason")).Should(ContainSubstring("Failed to compile malformed_sub_package:")) } checkUnifiedJUnitReport := func(report reporters.JUnitTestSuites) { Ω(report.TestSuites).Should(HaveLen(3)) Ω(report.Tests).Should(Equal(19)) Ω(report.Disabled).Should(Equal(2)) Ω(report.Errors).Should(Equal(2)) Ω(report.Failures).Should(Equal(4)) checkJUnitReport(report.TestSuites[0]) checkJUnitFailedCompilationReport(report.TestSuites[1]) checkJUnitSubpackageReport(report.TestSuites[2]) } checkTeamcityReport := func(data string) { lines := strings.Split(data, "\n") Ω(lines).Should(ContainElement("##teamcity[testSuiteStarted name='ReportingFixture Suite']")) Ω(lines).Should(ContainElement("##teamcity[testStarted name='|[BeforeSuite|]']")) Ω(lines).Should(ContainElement(HavePrefix("##teamcity[testFinished name='|[BeforeSuite|]'"))) Ω(lines).Should(ContainElement("##teamcity[testStarted name='|[It|] reporting test passes']")) Ω(lines).Should(ContainElement(HavePrefix("##teamcity[testFinished name='|[It|] reporting test passes'"))) Ω(lines).Should(ContainElement("##teamcity[testStarted name='|[It|] reporting test panics']")) Ω(lines).Should(ContainElement(HavePrefix("##teamcity[testFailed name='|[It|] reporting test panics' message='panicked - boom'"))) Ω(lines).Should(ContainElement(HavePrefix("##teamcity[testFinished name='|[It|] reporting test panics'"))) Ω(lines).Should(ContainElement("##teamcity[testStarted name='|[It|] reporting test fails']")) Ω(lines).Should(ContainElement(HavePrefix("##teamcity[testFailed name='|[It|] reporting test fails' message='failed - fail!'"))) Ω(lines).Should(ContainElement(HavePrefix("##teamcity[testStdErr name='|[It|] reporting test fails' out='> Enter"))) Ω(lines).Should(ContainElement(HavePrefix("##teamcity[testFinished name='|[It|] reporting test fails'"))) Ω(lines).Should(ContainElement("##teamcity[testStarted name='|[It|] reporting test is pending']")) Ω(lines).Should(ContainElement("##teamcity[testIgnored name='|[It|] reporting test is pending' message='pending']")) Ω(lines).Should(ContainElement(HavePrefix("##teamcity[testFinished name='|[It|] reporting test is pending'"))) Ω(lines).Should(ContainElement("##teamcity[testStarted name='|[It|] reporting test is skipped']")) Ω(lines).Should(ContainElement("##teamcity[testIgnored name='|[It|] reporting test is skipped' message='skipped - skip']")) Ω(lines).Should(ContainElement(HavePrefix("##teamcity[testFailed name='|[It|] reporting test times out and fails during cleanup' message='timedout"))) Ω(lines).Should(ContainElement(HavePrefix("##teamcity[testFinished name='|[It|] reporting test is skipped'"))) Ω(lines).Should(ContainElement("##teamcity[testSuiteFinished name='ReportingFixture Suite']")) } checkTeamcitySubpackageReport := func(data string) { lines := strings.Split(data, "\n") Ω(lines).Should(ContainElement("##teamcity[testSuiteStarted name='Reporting SubPackage Suite']")) Ω(lines).Should(ContainElement("##teamcity[testSuiteFinished name='Reporting SubPackage Suite']")) } checkTeamcityFailedCompilationReport := func(data string) { lines := strings.Split(data, "\n") Ω(lines).Should(ContainElement("##teamcity[testSuiteStarted name='']")) Ω(lines).Should(ContainElement("##teamcity[testSuiteFinished name='']")) } Context("the default behavior", func() { BeforeEach(func() { session := startGinkgo(fm.PathTo("reporting"), "--no-color", "-r", "--keep-going", "--procs=2", "--json-report=out.json", "--junit-report=out.xml", "--teamcity-report=out.tc", "-seed=17") Eventually(session).Should(gexec.Exit(1)) Ω(session).ShouldNot(gbytes.Say("Could not open")) }) It("generates single unified json and junit reports", func() { reports := fm.LoadJSONReports("reporting", "out.json") Ω(reports).Should(HaveLen(3)) checkJSONReport(reports[0]) checkJSONFailedCompilationReport(reports[1]) checkJSONSubpackageReport(reports[2]) junitReport := fm.LoadJUnitReport("reporting", "out.xml") checkUnifiedJUnitReport(junitReport) checkTeamcityReport(fm.ContentOf("reporting", "out.tc")) checkTeamcitySubpackageReport(fm.ContentOf("reporting", "out.tc")) checkTeamcityFailedCompilationReport(fm.ContentOf("reporting", "out.tc")) }) }) Context("with -output-dir", func() { BeforeEach(func() { session := startGinkgo(fm.PathTo("reporting"), "--no-color", "-r", "--keep-going", "--procs=2", "--json-report=out.json", "--junit-report=out.xml", "--teamcity-report=out.tc", "--output-dir=./reports", "-seed=17") Eventually(session).Should(gexec.Exit(1)) Ω(session).ShouldNot(gbytes.Say("Could not open")) }) It("places the single unified json and junit reports in output-dir", func() { reports := fm.LoadJSONReports("reporting", "reports/out.json") Ω(reports).Should(HaveLen(3)) checkJSONReport(reports[0]) checkJSONFailedCompilationReport(reports[1]) checkJSONSubpackageReport(reports[2]) junitReport := fm.LoadJUnitReport("reporting", "reports/out.xml") checkUnifiedJUnitReport(junitReport) checkTeamcityReport(fm.ContentOf("reporting", "reports/out.tc")) checkTeamcitySubpackageReport(fm.ContentOf("reporting", "reports/out.tc")) checkTeamcityFailedCompilationReport(fm.ContentOf("reporting", "reports/out.tc")) }) }) Context("with -keep-separate-reports", func() { BeforeEach(func() { session := startGinkgo(fm.PathTo("reporting"), "--no-color", "-r", "--keep-going", "--procs=2", "--json-report=out.json", "--junit-report=out.xml", "--teamcity-report=out.tc", "--keep-separate-reports", "-seed=17") Eventually(session).Should(gexec.Exit(1)) Ω(session).ShouldNot(gbytes.Say("Could not open")) }) It("keeps the separate reports in their respective packages", func() { reports := fm.LoadJSONReports("reporting", "out.json") Ω(reports).Should(HaveLen(1)) checkJSONReport(reports[0]) checkJUnitReport(fm.LoadJUnitReport("reporting", "out.xml").TestSuites[0]) checkTeamcityReport(fm.ContentOf("reporting", "out.tc")) reports = fm.LoadJSONReports("reporting", "reporting_sub_package/out.json") Ω(reports).Should(HaveLen(1)) checkJSONSubpackageReport(reports[0]) checkJUnitSubpackageReport(fm.LoadJUnitReport("reporting", "reporting_sub_package/out.xml").TestSuites[0]) checkTeamcitySubpackageReport(fm.ContentOf("reporting", "reporting_sub_package/out.tc")) reports = fm.LoadJSONReports("reporting", "malformed_sub_package/out.json") Ω(reports).Should(HaveLen(1)) checkJSONFailedCompilationReport(reports[0]) checkJUnitFailedCompilationReport(fm.LoadJUnitReport("reporting", "malformed_sub_package/out.xml").TestSuites[0]) checkTeamcityFailedCompilationReport(fm.ContentOf("reporting", "malformed_sub_package/out.tc")) Ω(fm.PathTo("reporting", "nonginkgo_sub_package/out.json")).ShouldNot(BeAnExistingFile()) Ω(fm.PathTo("reporting", "nonginkgo_sub_package/out.xml")).ShouldNot(BeAnExistingFile()) Ω(fm.PathTo("reporting", "nonginkgo_sub_package/out.tc")).ShouldNot(BeAnExistingFile()) }) }) Context("with -keep-separate-reports and -output-dir", func() { BeforeEach(func() { session := startGinkgo(fm.PathTo("reporting"), "--no-color", "-r", "--keep-going", "--procs=2", "--json-report=out.json", "--junit-report=out.xml", "--teamcity-report=out.tc", "--keep-separate-reports", "--output-dir=./reports", "-seed=17") Eventually(session).Should(gexec.Exit(1)) Ω(session).ShouldNot(gbytes.Say("Could not open")) }) It("places the separate reports in the -output-dir", func() { reports := fm.LoadJSONReports("reporting", "reports/reporting_out.json") Ω(reports).Should(HaveLen(1)) checkJSONReport(reports[0]) checkJUnitReport(fm.LoadJUnitReport("reporting", "reports/reporting_out.xml").TestSuites[0]) checkTeamcityReport(fm.ContentOf("reporting", "reports/reporting_out.tc")) reports = fm.LoadJSONReports("reporting", "reports/reporting_sub_package_out.json") Ω(reports).Should(HaveLen(1)) checkJSONSubpackageReport(reports[0]) checkJUnitSubpackageReport(fm.LoadJUnitReport("reporting", "reports/reporting_sub_package_out.xml").TestSuites[0]) checkTeamcitySubpackageReport(fm.ContentOf("reporting", "reports/reporting_sub_package_out.tc")) reports = fm.LoadJSONReports("reporting", "reports/malformed_sub_package_out.json") Ω(reports).Should(HaveLen(1)) checkJSONFailedCompilationReport(reports[0]) checkJUnitFailedCompilationReport(fm.LoadJUnitReport("reporting", "reports/malformed_sub_package_out.xml").TestSuites[0]) checkTeamcityFailedCompilationReport(fm.ContentOf("reporting", "reports/malformed_sub_package_out.tc")) Ω(fm.PathTo("reporting", "reports/nonginkgo_sub_package_out.json")).ShouldNot(BeAnExistingFile()) }) }) Context("when keep-going is not set and a suite fails", func() { BeforeEach(func() { session := startGinkgo(fm.PathTo("reporting"), "--no-color", "-r", "--procs=2", "--json-report=out.json", "--junit-report=out.xml", "--teamcity-report=out.tc", "-coverprofile=cover.out", "-cpuprofile=cpu.out", "-seed=17", "--output-dir=./reports") Eventually(session).Should(gexec.Exit(1)) Ω(session).ShouldNot(gbytes.Say("Could not open")) }) It("reports about the suites that did not run", func() { reports := fm.LoadJSONReports("reporting", "reports/out.json") Ω(reports).Should(HaveLen(3)) checkJSONReport(reports[0]) Ω(reports[1].SpecialSuiteFailureReasons).Should(ContainElement(ContainSubstring("Failed to compile malformed_sub_package"))) Ω(reports[2].SpecialSuiteFailureReasons).Should(ContainElement("Suite did not run because prior suites failed and --keep-going is not set")) }) }) }) }) golang-github-onsi-ginkgo-v2-2.22.0/integration/run_test.go000066400000000000000000000455101472321612100236140ustar00rootroot00000000000000package integration_test import ( "fmt" "os" "regexp" "runtime" "sort" "strings" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/ginkgo/v2/internal/test_helpers" "github.com/onsi/ginkgo/v2/types" . "github.com/onsi/gomega" "github.com/onsi/gomega/gbytes" "github.com/onsi/gomega/gexec" ) var _ = Describe("Running Specs", func() { denoter := "•" if runtime.GOOS == "windows" { denoter = "+" } Context("when pointed at the current directory", func() { BeforeEach(func() { fm.MountFixture("passing_ginkgo_tests") }) It("should run the tests in the working directory", func() { session := startGinkgo(fm.PathTo("passing_ginkgo_tests"), "--no-color") Eventually(session).Should(gexec.Exit(0)) output := string(session.Out.Contents()) Ω(output).Should(ContainSubstring("Running Suite: Passing_ginkgo_tests Suite")) Ω(output).Should(ContainSubstring(strings.Repeat(denoter, 4))) Ω(output).Should(ContainSubstring("SUCCESS! -- 5 Passed")) Ω(output).Should(ContainSubstring("Test Suite Passed")) }) }) Context("when passed an explicit package to run", func() { BeforeEach(func() { fm.MountFixture("passing_ginkgo_tests") }) It("should run the ginkgo style tests", func() { session := startGinkgo(fm.TmpDir, "--no-color", "passing_ginkgo_tests") Eventually(session).Should(gexec.Exit(0)) output := string(session.Out.Contents()) Ω(output).Should(ContainSubstring("Running Suite: Passing_ginkgo_tests Suite")) Ω(output).Should(ContainSubstring(strings.Repeat(denoter, 4))) Ω(output).Should(ContainSubstring("SUCCESS! -- 5 Passed")) Ω(output).Should(ContainSubstring("Test Suite Passed")) }) }) Context("when passed a number of packages to run", func() { BeforeEach(func() { fm.MountFixture("passing_ginkgo_tests") fm.MountFixture("more_ginkgo_tests") }) It("should run the ginkgo style tests", func() { session := startGinkgo(fm.TmpDir, "--no-color", "--succinct=false", "passing_ginkgo_tests", "more_ginkgo_tests") Eventually(session).Should(gexec.Exit(0)) output := string(session.Out.Contents()) Ω(output).Should(ContainSubstring("Running Suite: Passing_ginkgo_tests Suite")) Ω(output).Should(ContainSubstring("Running Suite: More_ginkgo_tests Suite")) Ω(output).Should(ContainSubstring("Test Suite Passed")) }) }) Context("when passed a number of packages to run, some of which have focused tests", func() { BeforeEach(func() { fm.MountFixture("passing_ginkgo_tests") fm.MountFixture("more_ginkgo_tests") fm.MountFixture("focused") }) It("should exit with a status code of 2 and explain why", func() { session := startGinkgo(fm.TmpDir, "--no-color", "--succinct=false", "-r") Eventually(session).Should(gexec.Exit(types.GINKGO_FOCUS_EXIT_CODE)) output := string(session.Out.Contents()) Ω(output).Should(ContainSubstring("Running Suite: Passing_ginkgo_tests Suite")) Ω(output).Should(ContainSubstring("Running Suite: More_ginkgo_tests Suite")) Ω(output).Should(ContainSubstring("Test Suite Passed")) Ω(output).Should(ContainSubstring("Detected Programmatic Focus - setting exit status to %d", types.GINKGO_FOCUS_EXIT_CODE)) }) Context("when the GINKGO_EDITOR_INTEGRATION environment variable is set", func() { BeforeEach(func() { os.Setenv("GINKGO_EDITOR_INTEGRATION", "true") }) AfterEach(func() { os.Setenv("GINKGO_EDITOR_INTEGRATION", "") }) It("should exit with a status code of 0 to allow a coverage file to be generated", func() { session := startGinkgo(fm.TmpDir, "--no-color", "--succinct=false", "-r") Eventually(session).Should(gexec.Exit(0)) output := string(session.Out.Contents()) Ω(output).Should(ContainSubstring("Running Suite: Passing_ginkgo_tests Suite")) Ω(output).Should(ContainSubstring("Running Suite: More_ginkgo_tests Suite")) Ω(output).Should(ContainSubstring("Test Suite Passed")) }) }) }) Context("when told to skipPackages", func() { BeforeEach(func() { fm.MountFixture("passing_ginkgo_tests") fm.MountFixture("more_ginkgo_tests") fm.MountFixture("focused") }) It("should skip packages that match the list", func() { session := startGinkgo(fm.TmpDir, "--no-color", "--skip-package=more_ginkgo_tests,focused", "-r") Eventually(session).Should(gexec.Exit(0)) output := string(session.Out.Contents()) Ω(output).Should(ContainSubstring("Passing_ginkgo_tests Suite")) Ω(output).ShouldNot(ContainSubstring("More_ginkgo_tests Suite")) Ω(output).ShouldNot(ContainSubstring("Focused_fixture Suite")) Ω(output).Should(ContainSubstring("Test Suite Passed")) }) Context("when all packages are skipped", func() { It("should not run anything, but still exit 0", func() { session := startGinkgo(fm.TmpDir, "--no-color", "--skip-package=passing_ginkgo_tests,more_ginkgo_tests,focused", "-r") Eventually(session).Should(gexec.Exit(0)) output := string(session.Out.Contents()) errOutput := string(session.Err.Contents()) Ω(errOutput).Should(ContainSubstring("All tests skipped!")) Ω(output).ShouldNot(ContainSubstring("Passing_ginkgo_tests Suite")) Ω(output).ShouldNot(ContainSubstring("More_ginkgo_tests Suite")) Ω(output).ShouldNot(ContainSubstring("Focused_fixture Suite")) Ω(output).ShouldNot(ContainSubstring("Test Suite Passed")) }) }) }) Context("when there are no tests to run", func() { It("should exit 1", func() { session := startGinkgo(fm.TmpDir, "--no-color", "-r") Eventually(session).Should(gexec.Exit(1)) output := string(session.Err.Contents()) Ω(output).Should(ContainSubstring("Found no test suites")) }) }) Context("when there are test files but `go test` reports there are no tests to run", func() { BeforeEach(func() { fm.MountFixture("no_test_fn") }) It("suggests running ginkgo bootstrap", func() { session := startGinkgo(fm.TmpDir, "--no-color", "-r") Eventually(session).Should(gexec.Exit(0)) output := string(session.Err.Contents()) Ω(output).Should(ContainSubstring(`Found no test suites, did you forget to run "ginkgo bootstrap"?`)) }) It("fails if told to requireSuite", func() { session := startGinkgo(fm.TmpDir, "--no-color", "-r", "-require-suite") Eventually(session).Should(gexec.Exit(1)) output := string(session.Err.Contents()) Ω(output).Should(ContainSubstring(`Found no test suites, did you forget to run "ginkgo bootstrap"?`)) }) }) Context("when told to randomize-suites", func() { BeforeEach(func() { fm.MountFixture("passing_ginkgo_tests") fm.MountFixture("more_ginkgo_tests") }) It("should mix up the order of the test suites", Label("slow"), func() { session := startGinkgo(fm.TmpDir, "--no-color", "--randomize-suites", "-r", "--seed=1") Eventually(session).Should(gexec.Exit(0)) Ω(session).Should(gbytes.Say("More_ginkgo_tests Suite")) Ω(session).Should(gbytes.Say("Passing_ginkgo_tests Suite")) session = startGinkgo(fm.TmpDir, "--no-color", "--randomize-suites", "-r", "--seed=4") Eventually(session).Should(gexec.Exit(0)) Ω(session).Should(gbytes.Say("Passing_ginkgo_tests Suite")) Ω(session).Should(gbytes.Say("More_ginkgo_tests Suite")) }) }) Context("when pointed at a package with xunit style tests", func() { BeforeEach(func() { fm.MountFixture("xunit") }) It("should run the xunit style tests, always setting -test.v and passing in supported go test flags", func() { session := startGinkgo(fm.PathTo("xunit"), "-blockprofile=block-profile.out") Eventually(session).Should(gexec.Exit(0)) output := string(session.Out.Contents()) Ω(output).Should(ContainSubstring("--- PASS: TestAlwaysTrue")) Ω(output).Should(ContainSubstring("Test Suite Passed")) Ω(fm.PathTo("xunit", "block-profile.out")).Should(BeAnExistingFile()) }) }) Context("when pointed at a package with no tests", func() { BeforeEach(func() { fm.MountFixture("no_tests") }) It("should fail", func() { session := startGinkgo(fm.PathTo("no_tests"), "--no-color") Eventually(session).Should(gexec.Exit(1)) Ω(session.Err.Contents()).Should(ContainSubstring("Found no test suites")) }) }) Context("when pointed at a package with tests, but the tests have been excluded via go tags", func() { BeforeEach(func() { fm.MountFixture("no_tagged_tests") }) It("should exit 0, not run anything, and generate a json report if asked", func() { session := startGinkgo(fm.PathTo("no_tagged_tests"), "--no-color", "--json-report=report.json") Eventually(session).Should(gexec.Exit(0)) Ω(session).Should(gbytes.Say("no test files")) report := fm.LoadJSONReports("no_tagged_tests", "report.json")[0] Ω(report.SuiteSucceeded).Should(BeTrue()) Ω(report.SpecialSuiteFailureReasons).Should(ConsistOf("Suite did not run go test reported that no test files were found")) }) }) Context("when pointed at a package that fails to compile", func() { BeforeEach(func() { fm.MountFixture("does_not_compile") }) It("should fail", func() { session := startGinkgo(fm.PathTo("does_not_compile"), "--no-color") Eventually(session).Should(gexec.Exit(1)) output := string(session.Out.Contents()) Ω(output).Should(ContainSubstring("Failed to compile")) }) }) Context("when running in parallel", func() { BeforeEach(func() { fm.MountFixture("passing_ginkgo_tests") }) Context("with a specific number of -procs", func() { It("should use the specified number of processes", func() { session := startGinkgo(fm.PathTo("passing_ginkgo_tests"), "--no-color", "-succinct", "--procs=2") Eventually(session).Should(gexec.Exit(0)) output := string(session.Out.Contents()) Ω(output).Should(MatchRegexp(`\[\d+\] Passing_ginkgo_tests Suite - 5/5 specs - 2 procs [%s]{5} SUCCESS! \d+(\.\d+)?[muµ]s`, regexp.QuoteMeta(denoter))) Ω(output).Should(ContainSubstring("Test Suite Passed")) }) }) Context("with -p", func() { It("it should autocompute the number of nodes", func() { session := startGinkgo(fm.PathTo("passing_ginkgo_tests"), "--no-color", "-succinct", "-p") Eventually(session).Should(gexec.Exit(0)) output := string(session.Out.Contents()) nodes := runtime.NumCPU() if nodes == 1 { Skip("Can't test parallel testings with 1 CPU") } if nodes > 4 { nodes = nodes - 1 } Ω(output).Should(MatchRegexp(`\[\d+\] Passing_ginkgo_tests Suite - 5/5 specs - %d procs [%s]{5} SUCCESS! \d+(\.\d+)?[muµ]?s`, nodes, regexp.QuoteMeta(denoter))) Ω(output).Should(ContainSubstring("Test Suite Passed")) }) }) }) Context("when running in parallel and there are specs marked Serial", Label("slow"), func() { BeforeEach(func() { fm.MountFixture("serial") }) It("runs the serial specs after all the parallel specs have finished", func() { By("running a carefully crafted test without the serial decorator") session := startGinkgo(fm.PathTo("serial"), "--no-color", "--procs=2", "--randomize-all", "--fail-fast", "--", "--no-serial") Eventually(session).Should(gexec.Exit(1)) By("running a carefully crafted test with the serial decorator") session = startGinkgo(fm.PathTo("serial"), "--no-color", "--procs=2", "--randomize-all", "--fail-fast") Eventually(session).Should(gexec.Exit(0)) }) }) Context("when running with ordered specs", func() { BeforeEach(func() { fm.MountFixture("ordered") }) It("always preserve spec order within ordered contexts", func() { By("running a carefully crafted test without the ordered decorator") session := startGinkgo(fm.PathTo("ordered"), "--no-color", "--procs=2", "-v", "--randomize-all", "--fail-fast", "--", "--no-ordered") Eventually(session).Should(gexec.Exit(1)) By("running a carefully crafted test with the ordered decorator") session = startGinkgo(fm.PathTo("ordered"), "--no-color", "--procs=2", "-v", "--randomize-all", "--fail-fast") Eventually(session).Should(gexec.Exit(0)) }) }) Context("when running multiple tests", func() { BeforeEach(func() { fm.MountFixture("passing_ginkgo_tests") fm.MountFixture("more_ginkgo_tests") fm.MountFixture("no_tagged_tests") }) Context("when all the tests pass", func() { Context("with the -r flag", func() { It("should run all the tests (in succinct mode) and succeed", func() { session := startGinkgo(fm.TmpDir, "--no-color", "-r", ".") Eventually(session).Should(gexec.Exit(0)) output := string(session.Out.Contents()) outputLines := strings.Split(output, "\n") Ω(outputLines[0]).Should(MatchRegexp(`\[\d+\] More_ginkgo_tests Suite - 3/3 specs [%s]{3} SUCCESS! \d+(\.\d+)?[muµ]s PASS`, regexp.QuoteMeta(denoter))) Ω(outputLines[1]).Should(ContainSubstring("Skipping ./no_tagged_tests (no test files)")) Ω(outputLines[2]).Should(MatchRegexp(`\[\d+\] Passing_ginkgo_tests Suite - 5/5 specs [%s]{5} SUCCESS! \d+(\.\d+)?[muµ]s PASS`, regexp.QuoteMeta(denoter))) Ω(output).Should(ContainSubstring("Test Suite Passed")) }) }) Context("with a trailing /...", func() { It("should run all the tests (in succinct mode) and succeed", func() { session := startGinkgo(fm.TmpDir, "--no-color", "./...") Eventually(session).Should(gexec.Exit(0)) output := string(session.Out.Contents()) outputLines := strings.Split(output, "\n") Ω(outputLines[0]).Should(MatchRegexp(`\[\d+\] More_ginkgo_tests Suite - 3/3 specs [%s]{3} SUCCESS! \d+(\.\d+)?[muµ]s PASS`, regexp.QuoteMeta(denoter))) Ω(outputLines[1]).Should(ContainSubstring("Skipping ./no_tagged_tests (no test files)")) Ω(outputLines[2]).Should(MatchRegexp(`\[\d+\] Passing_ginkgo_tests Suite - 5/5 specs [%s]{5} SUCCESS! \d+(\.\d+)?[muµ]s PASS`, regexp.QuoteMeta(denoter))) Ω(output).Should(ContainSubstring("Test Suite Passed")) }) }) }) Context("when one of the packages has a failing tests", func() { BeforeEach(func() { fm.MountFixture("failing_ginkgo_tests") }) It("should fail and stop running tests", func() { session := startGinkgo(fm.TmpDir, "--no-color", "no_tagged_tests", "passing_ginkgo_tests", "failing_ginkgo_tests", "more_ginkgo_tests") Eventually(session).Should(gexec.Exit(1)) output := string(session.Out.Contents()) outputLines := strings.Split(output, "\n") Ω(outputLines[0]).Should(ContainSubstring("Skipping ./no_tagged_tests (no test files)")) Ω(outputLines[1]).Should(MatchRegexp(`\[\d+\] Passing_ginkgo_tests Suite - 5/5 specs [%s]{5} SUCCESS! \d+(\.\d+)?[muµ]s PASS`, regexp.QuoteMeta(denoter))) Ω(outputLines[2]).Should(MatchRegexp(`\[\d+\] Failing_ginkgo_tests Suite - 2/2 specs`)) Ω(output).Should(ContainSubstring(fmt.Sprintf("%s [FAILED]", denoter))) Ω(output).ShouldNot(ContainSubstring("More_ginkgo_tests Suite")) Ω(output).Should(ContainSubstring("Test Suite Failed")) }) }) Context("when one of the packages fails to compile", func() { BeforeEach(func() { fm.MountFixture("does_not_compile") }) It("should fail and stop running tests", func() { session := startGinkgo(fm.TmpDir, "--no-color", "no_tagged_tests", "passing_ginkgo_tests", "does_not_compile", "more_ginkgo_tests") Eventually(session).Should(gexec.Exit(1)) output := string(session.Out.Contents()) outputLines := strings.Split(output, "\n") Ω(outputLines[0]).Should(ContainSubstring("Skipping ./no_tagged_tests (no test files)")) Ω(outputLines[1]).Should(MatchRegexp(`\[\d+\] Passing_ginkgo_tests Suite - 5/5 specs [%s]{5} SUCCESS! \d+(\.\d+)?[muµ]s PASS`, regexp.QuoteMeta(denoter))) Ω(outputLines[2]).Should(ContainSubstring("Failed to compile does_not_compile:")) Ω(output).ShouldNot(ContainSubstring("More_ginkgo_tests Suite")) Ω(output).Should(ContainSubstring("Test Suite Failed")) }) }) Context("when either is the case, but the keep-going flag is set", func() { BeforeEach(func() { fm.MountFixture("does_not_compile") fm.MountFixture("failing_ginkgo_tests") }) It("should soldier on", func() { session := startGinkgo(fm.TmpDir, "--no-color", "-keep-going", "no_tagged_tests", "passing_ginkgo_tests", "does_not_compile", "failing_ginkgo_tests", "more_ginkgo_tests") Eventually(session).Should(gexec.Exit(1)) output := string(session.Out.Contents()) outputLines := strings.Split(output, "\n") Ω(outputLines[0]).Should(ContainSubstring("Skipping ./no_tagged_tests (no test files)")) Ω(outputLines[1]).Should(MatchRegexp(`\[\d+\] Passing_ginkgo_tests Suite - 5/5 specs [%s]{5} SUCCESS! \d+(\.\d+)?[muµ]s PASS`, regexp.QuoteMeta(denoter))) Ω(outputLines[2]).Should(ContainSubstring("Failed to compile does_not_compile:")) Ω(output).Should(MatchRegexp(`\[\d+\] Failing_ginkgo_tests Suite - 2/2 specs`)) Ω(output).Should(ContainSubstring(fmt.Sprintf("%s [FAILED]", denoter))) Ω(output).Should(MatchRegexp(`\[\d+\] More_ginkgo_tests Suite - 3/3 specs [%s]{3} SUCCESS! \d+(\.\d+)?[muµ]s PASS`, regexp.QuoteMeta(denoter))) Ω(output).Should(ContainSubstring("Test Suite Failed")) }) }) }) Context("when running large suites in parallel", Label("slow"), func() { BeforeEach(func() { fm.MountFixture("large") }) It("doesn't miss any tests (a sanity test)", func() { session := startGinkgo(fm.PathTo("large"), "--no-color", "--procs=3", "--json-report=report.json") Eventually(session).Should(gexec.Exit(0)) report := Reports(fm.LoadJSONReports("large", "report.json")[0].SpecReports) expectedNames := []string{} for i := 0; i < 2048; i++ { expectedNames = append(expectedNames, fmt.Sprintf("%d", i)) } names := report.Names() sort.Strings(names) sort.Strings(expectedNames) Ω(names).Should(Equal(expectedNames)) }) }) Context("when running a suite in parallel that may have specs that are not deterministically ordered", func() { BeforeEach(func() { fm.MountFixture("nondeterministic") }) It("successfully generates a stable sort across all parallel processes and ensures exactly the correct specs are run", func() { By("Note: the assertions live in the ReportAfterSuite of the fixture. So we simply assert that we succeeded") session := startGinkgo(fm.PathTo("nondeterministic"), "--no-color", "--procs=3") Eventually(session).Should(gexec.Exit(0)) session = startGinkgo(fm.PathTo("nondeterministic"), "--no-color", "--procs=3", "--randomize-all") Eventually(session).Should(gexec.Exit(0)) }) }) Context("when there is a version mismatch between the cli and the test package", func() { It("emits a useful error and tries running", func() { fm.MountFixture(("version_mismatch")) session := startGinkgo(fm.PathTo("version_mismatch"), "--no-color") Eventually(session).Should(gbytes.Say("Ginkgo detected a version mismatch between the Ginkgo CLI and the version of Ginkgo imported by your packages")) Eventually(session).Should(gbytes.Say("Mismatched package versions found")) Eventually(session).Should(gbytes.Say("2.2.0")) Eventually(session).Should(gbytes.Say("used by version_mismatch")) session.Kill() Eventually(session).Should(gexec.Exit()) }) }) }) golang-github-onsi-ginkgo-v2-2.22.0/integration/skip_test.go000066400000000000000000000017461472321612100237610ustar00rootroot00000000000000package integration_test import ( . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" "github.com/onsi/gomega/gexec" ) var _ = Describe("Skipping Specs", func() { BeforeEach(func() { fm.MountFixture("skip") }) It("should skip in all the possible ways", func() { session := startGinkgo(fm.PathTo("skip"), "--no-color", "-v") Eventually(session).Should(gexec.Exit(0)) output := string(session.Out.Contents()) Ω(output).ShouldNot(ContainSubstring("NEVER SEE THIS")) Ω(output).Should(ContainSubstring("a top level skip on line 9")) Ω(output).Should(ContainSubstring("skip_fixture_test.go:9")) Ω(output).Should(ContainSubstring("a sync SKIP")) Ω(output).Should(ContainSubstring("S [SKIPPED] [")) Ω(output).Should(ContainSubstring("a BeforeEach SKIP")) Ω(output).Should(ContainSubstring("S [SKIPPED] [")) Ω(output).Should(ContainSubstring("an AfterEach SKIP")) Ω(output).Should(ContainSubstring("0 Passed | 0 Failed | 0 Pending | 4 Skipped")) }) }) golang-github-onsi-ginkgo-v2-2.22.0/integration/subcommand_test.go000066400000000000000000000604541472321612100251440ustar00rootroot00000000000000package integration_test import ( "os" "strings" . "github.com/onsi/ginkgo/v2" "github.com/onsi/ginkgo/v2/types" . "github.com/onsi/gomega" "github.com/onsi/gomega/gexec" ) var _ = Describe("Subcommand", func() { Describe("ginkgo bootstrap", func() { var pkg string BeforeEach(func() { pkg = "foo" fm.MkEmpty(pkg) }) It("should generate a bootstrap file, as long as one does not exist", func() { session := startGinkgo(fm.PathTo(pkg), "bootstrap") Eventually(session).Should(gexec.Exit(0)) output := session.Out.Contents() Ω(output).Should(ContainSubstring("foo_suite_test.go")) content := fm.ContentOf(pkg, "foo_suite_test.go") Ω(content).Should(ContainSubstring("package foo_test")) Ω(content).Should(ContainSubstring("func TestFoo(t *testing.T) {")) Ω(content).Should(ContainSubstring("RegisterFailHandler")) Ω(content).Should(ContainSubstring("RunSpecs")) Ω(content).Should(ContainSubstring("\t" + `. "github.com/onsi/ginkgo/v2"`)) Ω(content).Should(ContainSubstring("\t" + `. "github.com/onsi/gomega"`)) session = startGinkgo(fm.PathTo(pkg)) Eventually(session).Should(gexec.Exit(0)) session = startGinkgo(fm.PathTo(pkg), "bootstrap") Eventually(session).Should(gexec.Exit(1)) output = session.Err.Contents() Ω(output).Should(ContainSubstring("foo_suite_test.go")) Ω(output).Should(ContainSubstring("already exists")) }) It("should generate a bootstrap file with a working package name if the folder starts with a numeral", func() { fm.MkEmpty("7") session := startGinkgo(fm.PathTo("7"), "bootstrap") Eventually(session).Should(gexec.Exit(0)) content := fm.ContentOf("7", "7_suite_test.go") pkg := strings.Split(content, "\n")[0] Ω(pkg).Should(Equal("package seven_test")) session = startGinkgo(fm.PathTo("7")) Eventually(session).Should(gexec.Exit(0)) }) It("should import nodot declarations when told to", func() { session := startGinkgo(fm.PathTo(pkg), "bootstrap", "--nodot") Eventually(session).Should(gexec.Exit(0)) output := session.Out.Contents() Ω(output).Should(ContainSubstring("foo_suite_test.go")) content := fm.ContentOf(pkg, "foo_suite_test.go") Ω(content).Should(ContainSubstring("package foo_test")) Ω(content).Should(ContainSubstring("func TestFoo(t *testing.T) {")) Ω(content).Should(ContainSubstring("gomega.RegisterFailHandler")) Ω(content).Should(ContainSubstring("ginkgo.RunSpecs")) Ω(content).Should(ContainSubstring("\t" + `"github.com/onsi/ginkgo/v2"`)) Ω(content).Should(ContainSubstring("\t" + `"github.com/onsi/gomega"`)) session = startGinkgo(fm.PathTo(pkg)) Eventually(session).Should(gexec.Exit(0)) }) It("should generate a bootstrap file using a template when told to", func() { fm.WriteFile(pkg, ".bootstrap", `package {{.Package}} import ( {{.GinkgoImport}} {{.GomegaImport}} "testing" "binary" ) func Test{{.FormattedName}}(t *testing.T) { // This is a {{.Package}} test }`) session := startGinkgo(fm.PathTo(pkg), "bootstrap", "--template", ".bootstrap") Eventually(session).Should(gexec.Exit(0)) output := session.Out.Contents() Ω(output).Should(ContainSubstring("foo_suite_test.go")) content := fm.ContentOf(pkg, "foo_suite_test.go") Ω(content).Should(ContainSubstring("package foo_test")) Ω(content).Should(ContainSubstring(`. "github.com/onsi/ginkgo/v2"`)) Ω(content).Should(ContainSubstring(`. "github.com/onsi/gomega"`)) Ω(content).Should(ContainSubstring(`"binary"`)) Ω(content).Should(ContainSubstring("// This is a foo_test test")) }) It("should generate a bootstrap file using a template that contains functions when told to", func() { fm.WriteFile(pkg, ".bootstrap", `package {{.Package}} import ( {{.GinkgoImport}} {{.GomegaImport}} "testing" "binary" ) func Test{{.FormattedName}}(t *testing.T) { // This is a {{.Package | repeat 3}} test }`) session := startGinkgo(fm.PathTo(pkg), "bootstrap", "--template", ".bootstrap") Eventually(session).Should(gexec.Exit(0)) output := session.Out.Contents() Ω(output).Should(ContainSubstring("foo_suite_test.go")) content := fm.ContentOf(pkg, "foo_suite_test.go") Ω(content).Should(ContainSubstring("package foo_test")) Ω(content).Should(ContainSubstring(`. "github.com/onsi/ginkgo/v2"`)) Ω(content).Should(ContainSubstring(`. "github.com/onsi/gomega"`)) Ω(content).Should(ContainSubstring(`"binary"`)) Ω(content).Should(ContainSubstring("// This is a foo_testfoo_testfoo_test test")) }) It("should generate a bootstrap file using a template and custom template data when told to", func() { fm.WriteFile(pkg, ".bootstrap", `package {{.Package}} import ( {{.GinkgoImport}} {{.GomegaImport}} "testing" "binary" ) func Test{{.FormattedName}}(t *testing.T) { // This is a {{.Package | repeat 3}} test // This is a custom data {{.CustomData.suitename}} test }`) fm.WriteFile(pkg, "custom.json", `{"suitename": "integration"}`) session := startGinkgo(fm.PathTo(pkg), "bootstrap", "--template", ".bootstrap", "--template-data", "custom.json") Eventually(session).Should(gexec.Exit(0)) output := session.Out.Contents() Ω(output).Should(ContainSubstring("foo_suite_test.go")) content := fm.ContentOf(pkg, "foo_suite_test.go") Ω(content).Should(ContainSubstring("package foo_test")) Ω(content).Should(ContainSubstring(`. "github.com/onsi/ginkgo/v2"`)) Ω(content).Should(ContainSubstring(`. "github.com/onsi/gomega"`)) Ω(content).Should(ContainSubstring(`"binary"`)) Ω(content).Should(ContainSubstring("// This is a foo_testfoo_testfoo_test test")) Ω(content).Should(ContainSubstring("// This is a custom data integration test")) }) It("should fail to render a bootstrap file using a template and custom template data when accessing a missing key", func() { fm.WriteFile(pkg, ".bootstrap", `package {{.Package}} import ( {{.GinkgoImport}} {{.GomegaImport}} "testing" "binary" ) func Test{{.FormattedName}}(t *testing.T) { // This is a {{.Package | repeat 3}} test // This is a custom data {{.CustomData.component}} test }`) fm.WriteFile(pkg, "custom.json", `{"suitename": "integration"}`) session := startGinkgo(fm.PathTo(pkg), "bootstrap", "--template", ".bootstrap", "--template-data", "custom.json") Eventually(session).Should(gexec.Exit(1)) output := string(session.Err.Contents()) Ω(output).Should(ContainSubstring(`executing "bootstrap" at <.CustomData.component>: map has no entry for key "component"`)) }) It("should fail to render a bootstrap file using a template and custom template data when data is invalid JSON", func() { fm.WriteFile(pkg, ".bootstrap", `package {{.Package}} import ( {{.GinkgoImport}} {{.GomegaImport}} "testing" "binary" ) func Test{{.FormattedName}}(t *testing.T) { // This is a {{.Package | repeat 3}} test // This is a custom data {{.CustomData.component}} test }`) fm.WriteFile(pkg, "custom.json", `{'suitename': 'integration']`) session := startGinkgo(fm.PathTo(pkg), "bootstrap", "--template", ".bootstrap", "--template-data", "custom.json") Eventually(session).Should(gexec.Exit(1)) output := string(session.Err.Contents()) Ω(output).Should(ContainSubstring(`Invalid JSON object in custom data file.`)) }) }) Describe("ginkgo generate", func() { var pkg string BeforeEach(func() { pkg = "foo_bar" fm.MkEmpty(pkg) Eventually(startGinkgo(fm.PathTo(pkg), "bootstrap")).Should(gexec.Exit(0)) }) Context("with no arguments", func() { It("should generate a test file named after the package", func() { session := startGinkgo(fm.PathTo(pkg), "generate") Eventually(session).Should(gexec.Exit(0)) output := session.Out.Contents() Ω(output).Should(ContainSubstring("foo_bar_test.go")) By("having the correct content") content := fm.ContentOf(pkg, "foo_bar_test.go") Ω(content).Should(ContainSubstring("package foo_bar_test")) Ω(content).Should(ContainSubstring(`var _ = Describe("FooBar", func() {`)) Ω(content).Should(ContainSubstring("\t" + `. "github.com/onsi/ginkgo/v2"`)) Ω(content).Should(ContainSubstring("\t" + `. "github.com/onsi/gomega"`)) By("compiling correctly (we append to the file to make sure gomega is used)") fm.WriteFile(pkg, "foo_bar.go", "package foo_bar\nvar TRUE=true\n") fm.AppendToFile(pkg, "foo_bar_test.go", strings.Join([]string{``, `var _ = It("works", func() {`, ` Expect(foo_bar.TRUE).To(BeTrue())`, `})`, }, "\n")) Eventually(startGinkgo(fm.PathTo(pkg))).Should(gexec.Exit(0)) By("refusing to overwrite the file if generate is called again") session = startGinkgo(fm.PathTo(pkg), "generate") Eventually(session).Should(gexec.Exit(1)) output = session.Err.Contents() Ω(output).Should(ContainSubstring("foo_bar_test.go")) Ω(output).Should(ContainSubstring("already exists")) }) }) Context("with template argument", func() { It("should generate a test file using a template", func() { fm.WriteFile(pkg, ".generate", `package {{.Package}} import ( {{.GinkgoImport}} {{.GomegaImport}} {{if .ImportPackage}}"{{.PackageImportPath}}"{{end}} ) var _ = Describe("{{.Subject}}", func() { // This is a {{.Package}} test })`) session := startGinkgo(fm.PathTo(pkg), "generate", "--template", ".generate") Eventually(session).Should(gexec.Exit(0)) output := session.Out.Contents() Ω(output).Should(ContainSubstring("foo_bar_test.go")) content := fm.ContentOf(pkg, "foo_bar_test.go") Ω(content).Should(ContainSubstring("package foo_bar_test")) Ω(content).Should(ContainSubstring(`. "github.com/onsi/ginkgo/v2"`)) Ω(content).Should(ContainSubstring(`. "github.com/onsi/gomega"`)) Ω(content).Should(ContainSubstring(`/foo_bar"`)) Ω(content).Should(ContainSubstring("// This is a foo_bar_test test")) }) It("should generate a test file using a template that contains functions", func() { fm.WriteFile(pkg, ".generate", `package {{.Package}} import ( {{.GinkgoImport}} {{.GomegaImport}} {{if .ImportPackage}}"{{.PackageImportPath}}"{{end}} ) var _ = Describe("{{.Subject}}", func() { // This is a {{.Package | repeat 3 }} test })`) session := startGinkgo(fm.PathTo(pkg), "generate", "--template", ".generate") Eventually(session).Should(gexec.Exit(0)) output := session.Out.Contents() Ω(output).Should(ContainSubstring("foo_bar_test.go")) content := fm.ContentOf(pkg, "foo_bar_test.go") Ω(content).Should(ContainSubstring("package foo_bar_test")) Ω(content).Should(ContainSubstring(`. "github.com/onsi/ginkgo/v2"`)) Ω(content).Should(ContainSubstring(`. "github.com/onsi/gomega"`)) Ω(content).Should(ContainSubstring(`/foo_bar"`)) Ω(content).Should(ContainSubstring("// This is a foo_bar_testfoo_bar_testfoo_bar_test test")) }) It("should generate a test file using a template and custom data when told to", func() { fm.WriteFile(pkg, ".generate", `package {{.Package}} import ( {{.GinkgoImport}} {{.GomegaImport}} {{if .ImportPackage}}"{{.PackageImportPath}}"{{end}} ) var _ = Describe("{{.Subject}}", Label("{{.CustomData.label}}"), func() { // This is a {{.Package | repeat 3 }} test })`) fm.WriteFile(pkg, "custom_spec.json", `{"label": "integration"}`) session := startGinkgo(fm.PathTo(pkg), "generate", "--template", ".generate", "--template-data", "custom_spec.json") Eventually(session).Should(gexec.Exit(0)) output := session.Out.Contents() Ω(output).Should(ContainSubstring("foo_bar_test.go")) content := fm.ContentOf(pkg, "foo_bar_test.go") Ω(content).Should(ContainSubstring("package foo_bar_test")) Ω(content).Should(ContainSubstring(`. "github.com/onsi/ginkgo/v2"`)) Ω(content).Should(ContainSubstring(`. "github.com/onsi/gomega"`)) Ω(content).Should(ContainSubstring(`/foo_bar"`)) Ω(content).Should(ContainSubstring("// This is a foo_bar_testfoo_bar_testfoo_bar_test test")) Ω(content).Should(ContainSubstring(`Label("integration")`)) }) It("should fail to render a test file using a template and custom template data when accessing a missing key", func() { fm.WriteFile(pkg, ".generate", `package {{.Package}} import ( {{.GinkgoImport}} {{.GomegaImport}} {{if .ImportPackage}}"{{.PackageImportPath}}"{{end}} ) var _ = Describe("{{.Subject}}", Label("{{.CustomData.component}}"), func() { // This is a {{.Package | repeat 3 }} test })`) fm.WriteFile(pkg, "custom.json", `{"label": "integration"}`) session := startGinkgo(fm.PathTo(pkg), "generate", "--template", ".generate", "--template-data", "custom.json") Eventually(session).Should(gexec.Exit(1)) output := string(session.Err.Contents()) Ω(output).Should(ContainSubstring(`executing "spec" at <.CustomData.component>: map has no entry for key "component"`)) }) It("should fail to render a test file using a template and custom template data when data is invalid JSON", func() { fm.WriteFile(pkg, ".generate", `package {{.Package}} import ( {{.GinkgoImport}} {{.GomegaImport}} {{if .ImportPackage}}"{{.PackageImportPath}}"{{end}} ) var _ = Describe("{{.Subject}}", Label("{{.CustomData.label}}"), func() { // This is a {{.Package | repeat 3 }} test })`) fm.WriteFile(pkg, "custom.json", `{'label': 'integration']`) session := startGinkgo(fm.PathTo(pkg), "generate", "--template", ".generate", "--template-data", "custom.json") Eventually(session).Should(gexec.Exit(1)) output := string(session.Err.Contents()) Ω(output).Should(ContainSubstring(`Invalid JSON object in custom data file.`)) }) }) Context("with an argument of the form: foo", func() { It("should generate a test file named after the argument", func() { session := startGinkgo(fm.PathTo(pkg), "generate", "baz_buzz") Eventually(session).Should(gexec.Exit(0)) output := session.Out.Contents() Ω(output).Should(ContainSubstring("baz_buzz_test.go")) content := fm.ContentOf(pkg, "baz_buzz_test.go") Ω(content).Should(ContainSubstring("package foo_bar_test")) Ω(content).Should(ContainSubstring(`var _ = Describe("BazBuzz", func() {`)) }) }) Context("with an argument of the form: foo.go", func() { It("should generate a test file named after the argument", func() { session := startGinkgo(fm.PathTo(pkg), "generate", "baz_buzz.go") Eventually(session).Should(gexec.Exit(0)) output := session.Out.Contents() Ω(output).Should(ContainSubstring("baz_buzz_test.go")) content := fm.ContentOf(pkg, "baz_buzz_test.go") Ω(content).Should(ContainSubstring("package foo_bar_test")) Ω(content).Should(ContainSubstring(`var _ = Describe("BazBuzz", func() {`)) }) }) Context("with an argument of the form: foo_test", func() { It("should generate a test file named after the argument", func() { session := startGinkgo(fm.PathTo(pkg), "generate", "baz_buzz_test") Eventually(session).Should(gexec.Exit(0)) output := session.Out.Contents() Ω(output).Should(ContainSubstring("baz_buzz_test.go")) content := fm.ContentOf(pkg, "baz_buzz_test.go") Ω(content).Should(ContainSubstring("package foo_bar_test")) Ω(content).Should(ContainSubstring(`var _ = Describe("BazBuzz", func() {`)) }) }) Context("with an argument of the form: foo-test", func() { It("should generate a test file named after the argument", func() { session := startGinkgo(fm.PathTo(pkg), "generate", "baz-buzz-test") Eventually(session).Should(gexec.Exit(0)) output := session.Out.Contents() Ω(output).Should(ContainSubstring("baz_buzz_test.go")) content := fm.ContentOf(pkg, "baz_buzz_test.go") Ω(content).Should(ContainSubstring("package foo_bar_test")) Ω(content).Should(ContainSubstring(`var _ = Describe("BazBuzz", func() {`)) }) }) Context("with an argument of the form: foo_test.go", func() { It("should generate a test file named after the argument", func() { session := startGinkgo(fm.PathTo(pkg), "generate", "baz_buzz_test.go") Eventually(session).Should(gexec.Exit(0)) output := session.Out.Contents() Ω(output).Should(ContainSubstring("baz_buzz_test.go")) content := fm.ContentOf(pkg, "baz_buzz_test.go") Ω(content).Should(ContainSubstring("package foo_bar_test")) Ω(content).Should(ContainSubstring(`var _ = Describe("BazBuzz", func() {`)) }) }) Context("with multiple arguments", func() { It("should generate a test file named after the argument", func() { session := startGinkgo(fm.PathTo(pkg), "generate", "baz", "buzz") Eventually(session).Should(gexec.Exit(0)) output := session.Out.Contents() Ω(output).Should(ContainSubstring("baz_test.go")) Ω(output).Should(ContainSubstring("buzz_test.go")) content := fm.ContentOf(pkg, "baz_test.go") Ω(content).Should(ContainSubstring("package foo_bar_test")) Ω(content).Should(ContainSubstring(`var _ = Describe("Baz", func() {`)) content = fm.ContentOf(pkg, "buzz_test.go") Ω(content).Should(ContainSubstring("package foo_bar_test")) Ω(content).Should(ContainSubstring(`var _ = Describe("Buzz", func() {`)) }) }) Context("with nodot", func() { It("should not import ginkgo or gomega", func() { session := startGinkgo(fm.PathTo(pkg), "generate", "--nodot") Eventually(session).Should(gexec.Exit(0)) output := session.Out.Contents() Ω(output).Should(ContainSubstring("foo_bar_test.go")) content := fm.ContentOf(pkg, "foo_bar_test.go") Ω(content).Should(ContainSubstring("package foo_bar_test")) Ω(content).ShouldNot(ContainSubstring("\t" + `. "github.com/onsi/ginkgo/v2"`)) Ω(content).ShouldNot(ContainSubstring("\t" + `. "github.com/onsi/gomega"`)) Ω(content).Should(ContainSubstring("\t" + `"github.com/onsi/ginkgo/v2"`)) Ω(content).Should(ContainSubstring("\t" + `"github.com/onsi/gomega"`)) By("compiling correctly (we append to the file to make sure gomega is used)") fm.WriteFile(pkg, "foo_bar.go", "package foo_bar\nvar TRUE=true\n") fm.AppendToFile(pkg, "foo_bar_test.go", strings.Join([]string{``, `var _ = ginkgo.It("works", func() {`, ` gomega.Expect(foo_bar.TRUE).To(gomega.BeTrue())`, `})`, }, "\n")) Eventually(startGinkgo(fm.PathTo(pkg))).Should(gexec.Exit(0)) }) }) }) Describe("ginkgo bootstrap/generate", func() { var pkg string BeforeEach(func() { pkg = "some-crazy-thing" fm.MkEmpty(pkg) }) Context("when the working directory is empty", func() { It("generates correctly named bootstrap and generate files with a package name derived from the directory", func() { session := startGinkgo(fm.PathTo(pkg), "bootstrap") Eventually(session).Should(gexec.Exit(0)) content := fm.ContentOf(pkg, "some_crazy_thing_suite_test.go") Ω(content).Should(ContainSubstring("package some_crazy_thing_test")) Ω(content).Should(ContainSubstring("SomeCrazyThing Suite")) session = startGinkgo(fm.PathTo(pkg), "generate") Eventually(session).Should(gexec.Exit(0)) content = fm.ContentOf(pkg, "some_crazy_thing_test.go") Ω(content).Should(ContainSubstring("package some_crazy_thing_test")) Ω(content).Should(ContainSubstring("SomeCrazyThing")) }) }) Context("when the working directory contains a file with a package name", func() { BeforeEach(func() { fm.WriteFile(pkg, "foo.go", "package main\n\nfunc main() {}") }) It("generates correctly named bootstrap and generate files with the package name", func() { session := startGinkgo(fm.PathTo(pkg), "bootstrap") Eventually(session).Should(gexec.Exit(0)) content := fm.ContentOf(pkg, "some_crazy_thing_suite_test.go") Ω(content).Should(ContainSubstring("package main_test")) Ω(content).Should(ContainSubstring("SomeCrazyThing Suite")) session = startGinkgo(fm.PathTo(pkg), "generate") Eventually(session).Should(gexec.Exit(0)) content = fm.ContentOf(pkg, "some_crazy_thing_test.go") Ω(content).Should(ContainSubstring("package main_test")) Ω(content).Should(ContainSubstring("SomeCrazyThing")) }) }) }) Describe("Go module and ginkgo bootstrap/generate", func() { var ( pkg string savedGoPath string ) BeforeEach(func() { pkg = "myamazingmodule" fm.MkEmpty(pkg) fm.WriteFile(pkg, "go.mod", "module fake.com/me/myamazingmodule\n") savedGoPath = os.Getenv("GOPATH") Expect(os.Setenv("GOPATH", "")).To(Succeed()) Expect(os.Setenv("GO111MODULE", "on")).To(Succeed()) // needed pre-Go 1.13 }) AfterEach(func() { Expect(os.Setenv("GOPATH", savedGoPath)).To(Succeed()) Expect(os.Setenv("GO111MODULE", "")).To(Succeed()) }) It("generates correctly named bootstrap and generate files with the module name", func() { session := startGinkgo(fm.PathTo(pkg), "bootstrap") Eventually(session).Should(gexec.Exit(0)) content := fm.ContentOf(pkg, "myamazingmodule_suite_test.go") Expect(content).To(ContainSubstring("package myamazingmodule_test"), string(content)) Expect(content).To(ContainSubstring("Myamazingmodule Suite"), string(content)) session = startGinkgo(fm.PathTo(pkg), "generate") Eventually(session).Should(gexec.Exit(0)) content = fm.ContentOf(pkg, "myamazingmodule_test.go") Expect(content).To(ContainSubstring("package myamazingmodule_test"), string(content)) Expect(content).To(ContainSubstring("fake.com/me/myamazingmodule"), string(content)) Expect(content).To(ContainSubstring("Myamazingmodule"), string(content)) }) }) Describe("ginkgo unfocus", func() { It("should unfocus tests", Label("slow"), func() { fm.MountFixture("focused") session := startGinkgo(fm.PathTo("focused"), "--no-color", "-r") Eventually(session).Should(gexec.Exit(types.GINKGO_FOCUS_EXIT_CODE)) output := session.Out.Contents() Ω(string(output)).Should(ContainSubstring("Detected Programmatic Focus")) session = startGinkgo(fm.PathTo("focused"), "unfocus") Eventually(session).Should(gexec.Exit(0)) output = session.Out.Contents() Ω(string(output)).ShouldNot(ContainSubstring("expected 'package'")) session = startGinkgo(fm.PathTo("focused"), "--no-color", "-r") Eventually(session).Should(gexec.Exit(0)) output = session.Out.Contents() Ω(string(output)).Should(ContainSubstring("Ginkgo ran 2 suites")) Ω(string(output)).Should(ContainSubstring("Test Suite Passed")) Ω(string(output)).ShouldNot(ContainSubstring("Detected Programmatic Focus")) original := fm.ContentOfFixture("focused", "README.md") updated := fm.ContentOf("focused", "README.md") Ω(original).Should(Equal(updated)) }) It("should ignore the 'vendor' folder", func() { fm.MountFixture("focused_with_vendor") session := startGinkgo(fm.PathTo("focused_with_vendor"), "unfocus") Eventually(session).Should(gexec.Exit(0)) session = startGinkgo(fm.PathTo("focused_with_vendor"), "--no-color") Eventually(session).Should(gexec.Exit(0)) output := session.Out.Contents() Expect(string(output)).To(ContainSubstring("11 Passed")) Expect(string(output)).To(ContainSubstring("0 Skipped")) originalVendorPath := fm.PathToFixtureFile("focused_with_vendor", "vendor") updatedVendorPath := fm.PathTo("focused_with_vendor", "vendor") Expect(sameFolder(originalVendorPath, updatedVendorPath)).To(BeTrue()) }) }) Describe("ginkgo version", func() { It("should print out the version info", func() { session := startGinkgo("", "version") Eventually(session).Should(gexec.Exit(0)) output := session.Out.Contents() Ω(output).Should(MatchRegexp(`Ginkgo Version \d+\.\d+\.\d+`)) }) }) Describe("ginkgo help", func() { It("should print out usage information", func() { session := startGinkgo("", "help") Eventually(session).Should(gexec.Exit(0)) output := string(session.Out.Contents()) Ω(output).Should(MatchRegexp(`Ginkgo Version \d+\.\d+\.\d+`)) Ω(output).Should(ContainSubstring("watch")) Ω(output).Should(ContainSubstring("generate")) Ω(output).Should(ContainSubstring("run")) }) It("should print out usage information for subcommands", func() { session := startGinkgo("", "help", "run") Eventually(session).Should(gexec.Exit(0)) output := string(session.Out.Contents()) Ω(output).Should(ContainSubstring("-succinct")) Ω(output).Should(ContainSubstring("-procs")) }) }) }) golang-github-onsi-ginkgo-v2-2.22.0/integration/suite_command_test.go000066400000000000000000000051211472321612100256310ustar00rootroot00000000000000package integration_test import ( . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" "github.com/onsi/gomega/gexec" ) var _ = Describe("After Run Hook Specs", func() { BeforeEach(func() { fm.MountFixture("after_run_hook") }) It("Runs command after suite echoing out suite data, properly reporting suite name and passing status in successful command output", func() { command := "-after-run-hook=echo THIS IS A (ginkgo-suite-passed) TEST OF THE (ginkgo-suite-name) SYSTEM, THIS IS ONLY A TEST" expected := "THIS IS A [PASS] TEST OF THE after_run_hook SYSTEM, THIS IS ONLY A TEST" session := startGinkgo(fm.PathTo("after_run_hook"), command) Eventually(session).Should(gexec.Exit(0)) output := string(session.Out.Contents()) Ω(output).Should(ContainSubstring("1 Passed")) Ω(output).Should(ContainSubstring("0 Failed")) Ω(output).Should(ContainSubstring("1 Pending")) Ω(output).Should(ContainSubstring("0 Skipped")) Ω(output).Should(ContainSubstring("Test Suite Passed")) Ω(output).Should(ContainSubstring("After-run-hook succeeded:")) Ω(output).Should(ContainSubstring(expected)) }) It("Runs command after suite reporting that command failed", func() { command := "-after-run-hook=exit 1" session := startGinkgo(fm.PathTo("after_run_hook"), command) Eventually(session).Should(gexec.Exit(0)) output := string(session.Out.Contents()) Ω(output).Should(ContainSubstring("1 Passed")) Ω(output).Should(ContainSubstring("0 Failed")) Ω(output).Should(ContainSubstring("1 Pending")) Ω(output).Should(ContainSubstring("0 Skipped")) Ω(output).Should(ContainSubstring("Test Suite Passed")) Ω(output).Should(ContainSubstring("After-run-hook failed:")) }) It("Runs command after suite echoing out suite data, properly reporting suite name and failing status in successful command output", func() { command := "-after-run-hook=echo THIS IS A (ginkgo-suite-passed) TEST OF THE (ginkgo-suite-name) SYSTEM, THIS IS ONLY A TEST" expected := "THIS IS A [FAIL] TEST OF THE after_run_hook SYSTEM, THIS IS ONLY A TEST" session := startGinkgo(fm.PathTo("after_run_hook"), "-fail-on-pending=true", command) Eventually(session).Should(gexec.Exit(1)) output := string(session.Out.Contents()) Ω(output).Should(ContainSubstring("1 Passed")) Ω(output).Should(ContainSubstring("0 Failed")) Ω(output).Should(ContainSubstring("1 Pending")) Ω(output).Should(ContainSubstring("0 Skipped")) Ω(output).Should(ContainSubstring("Test Suite Failed")) Ω(output).Should(ContainSubstring("After-run-hook succeeded:")) Ω(output).Should(ContainSubstring(expected)) }) }) golang-github-onsi-ginkgo-v2-2.22.0/integration/suite_setup_test.go000066400000000000000000000065051472321612100253620ustar00rootroot00000000000000package integration_test import ( "strings" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" "github.com/onsi/gomega/gexec" ) var _ = Describe("SuiteSetup", func() { Context("With passing synchronized before and after suites", func() { BeforeEach(func() { fm.MountFixture("synchronized_setup_tests") }) Context("when run with one proc", func() { It("should do all the work on that one proc", func() { session := startGinkgo(fm.PathTo("synchronized_setup_tests"), "--no-color") Eventually(session).Should(gexec.Exit(0)) output := string(session.Out.Contents()) Ω(output).Should(ContainSubstring("BEFORE_A_1\nBEFORE_B_1: DATA")) Ω(output).Should(ContainSubstring("AFTER_A_1\nAFTER_B_1")) }) }) Context("when run across multiple procs", func() { It("should run the first BeforeSuite function (BEFORE_A) on proc 1, the second (BEFORE_B) on all the procs, the first AfterSuite (AFTER_A) on all the procs, and then the second (AFTER_B) on Node 1 *after* everything else is finished", func() { session := startGinkgo(fm.PathTo("synchronized_setup_tests"), "--no-color", "--procs=3") Eventually(session).Should(gexec.Exit(0)) output := string(session.Out.Contents()) numOccurrences := 0 for _, line := range strings.Split(output, "\n") { occurs, _ := ContainSubstring("BEFORE_A_1").Match(line) if occurs { numOccurrences += 1 } } Ω(numOccurrences).Should(Equal(2)) // once when it's emitted because it's in the synchronizedBeforeSuite proc. And once again when it's captured in the spec report that includes the stdout output. numOccurrences = 0 for _, line := range strings.Split(output, "\n") { occurs, _ := ContainSubstring("AFTER_B_1").Match(line) if occurs { numOccurrences += 1 } } Ω(numOccurrences).Should(Equal(2)) // once when it's emitted because it's in the synchronizedAfterSuite proc. And once again when it's captured in the spec report that includes the stdout output. Ω(output).Should(ContainSubstring("BEFORE_A_1")) Ω(output).Should(ContainSubstring("BEFORE_B_1: DATA")) Ω(output).Should(ContainSubstring("BEFORE_B_2: DATA")) Ω(output).Should(ContainSubstring("BEFORE_B_3: DATA")) Ω(output).ShouldNot(ContainSubstring("BEFORE_A_2")) Ω(output).ShouldNot(ContainSubstring("BEFORE_A_3")) Ω(output).Should(ContainSubstring("AFTER_A_1")) Ω(output).Should(ContainSubstring("AFTER_A_2")) Ω(output).Should(ContainSubstring("AFTER_A_3")) Ω(output).Should(ContainSubstring("AFTER_B_1")) Ω(output).ShouldNot(ContainSubstring("AFTER_B_2")) Ω(output).ShouldNot(ContainSubstring("AFTER_B_3")) }) }) }) Context("With a failing synchronized before suite", func() { BeforeEach(func() { fm.MountFixture("exiting_synchronized_setup") }) It("should fail and let the user know that proc 1 disappeared prematurely", func() { session := startGinkgo(fm.PathTo("exiting_synchronized_setup"), "--no-color", "--procs=3") Eventually(session).Should(gexec.Exit(1)) output := string(session.Out.Contents()) + string(session.Err.Contents()) Ω(output).Should(ContainSubstring("Process #1 disappeared before SynchronizedBeforeSuite could report back")) Ω(output).Should(ContainSubstring("Ginkgo timed out waiting for all parallel procs to report back")) }) }) }) golang-github-onsi-ginkgo-v2-2.22.0/integration/tags_test.go000066400000000000000000000013001472321612100237330ustar00rootroot00000000000000package integration_test import ( . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" "github.com/onsi/gomega/gexec" ) var _ = Describe("Tags", func() { BeforeEach(func() { fm.MountFixture("tags") }) It("should honor the passed in -tags flag", func() { session := startGinkgo(fm.PathTo("tags"), "--no-color") Eventually(session).Should(gexec.Exit(0)) output := string(session.Out.Contents()) Ω(output).Should(ContainSubstring("Ran 1 of 1 Specs")) session = startGinkgo(fm.PathTo("tags"), "--no-color", "-tags=complex_tests") Eventually(session).Should(gexec.Exit(0)) output = string(session.Out.Contents()) Ω(output).Should(ContainSubstring("Ran 3 of 3 Specs")) }) }) golang-github-onsi-ginkgo-v2-2.22.0/integration/timeline_test.go000066400000000000000000000405531472321612100246200ustar00rootroot00000000000000package integration_test import ( "fmt" "runtime" "strings" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" "github.com/onsi/gomega/gbytes" "github.com/onsi/gomega/gexec" ) var _ = Describe("Timeline output", func() { denoter := "•" retryDenoter := "↺" if runtime.GOOS == "windows" { denoter = "+" retryDenoter = "R" } BeforeEach(func() { fm.MountFixture("timeline") }) Context("when running with succinct and normal verbosity", func() { argGroups := [][]string{ {"--no-color", "--seed=17"}, {"--no-color", "--seed=17", "--nodes=2"}, {"--no-color", "--seed=17", "--succinct"}, {"--no-color", "--seed=17", "--nodes=2", "--succinct"}, } for _, args := range argGroups { args := args It(fmt.Sprintf("should emit a timeline (%s)", strings.Join(args, " ")), func() { session := startGinkgo(fm.PathTo("timeline"), args...) Eventually(session).Should(gexec.Exit(1)) Ω(session).Should(gbytes.Say(`3 specs`)) Ω(session).Should(gbytes.Say(`Automatically polling progress`)) Ω(session).Should(gbytes.Say(`>\s*time\.Sleep\(time\.Millisecond \* 200\)`)) Ω(session).Should(gbytes.Say(retryDenoter + ` \[FLAKEY TEST - TOOK 3 ATTEMPTS TO PASS\]`)) Ω(session).Should(gbytes.Say(`a full timeline a flaky test retries a few times`)) Ω(session).Should(gbytes.Say(`Report Entries >>`)) Ω(session).Should(gbytes.Say(`a report! - `)) Ω(session).Should(gbytes.Say(` Of great value`)) Ω(session).Should(gbytes.Say(`a report! - `)) Ω(session).Should(gbytes.Say(` Of great value`)) Ω(session).Should(gbytes.Say(`a report! - `)) Ω(session).Should(gbytes.Say(` Of great value`)) Ω(session).Should(gbytes.Say(`<< Report Entries`)) Ω(session).Should(gbytes.Say(denoter + ` \[TIMEDOUT\]`)) Ω(session).Should(gbytes.Say(`a full timeline a test with multiple failures \[It\] times out`)) Ω(session).Should(gbytes.Say(`Timeline >>`)) Ω(session).Should(gbytes.Say(`STEP: waiting...`)) Ω(session).Should(gbytes.Say(`\[TIMEDOUT] in \[It\]`)) Ω(session).Should(gbytes.Say(`then failing!`)) Ω(session).Should(gbytes.Say(`\[FAILED\] in \[It\]`)) Ω(session).Should(gbytes.Say(`\[PANICKED\] in \[AfterEach\]`)) Ω(session).Should(gbytes.Say(`<< Timeline`)) Ω(session).Should(gbytes.Say(`\[TIMEDOUT\] A node timeout occurred`)) Ω(session).Should(gbytes.Say(`This is the Progress Report generated when the node timeout occurred:`)) Ω(session).Should(gbytes.Say(`>.*<-ctx.Done\(\)`)) Ω(session).Should(gbytes.Say(`\[FAILED\] A node timeout occurred and then the following failure was recorded in the timedout node before it exited:`)) Ω(session).Should(gbytes.Say(`welp`)) Ω(session).Should(gbytes.Say(`In \[It\] at:`)) Ω(session).Should(gbytes.Say(`There were additional failures detected. To view them in detail run ginkgo -vv`)) Ω(session).Should(gbytes.Say(denoter)) Ω(session).Should(gbytes.Say(`Summarizing 1 Failure:`)) Ω(session).Should(gbytes.Say(`\[TIMEDOUT\]`)) }) } }) Context("when running with -v", func() { It("should emit a timeline", func() { session := startGinkgo(fm.PathTo("timeline"), "--no-color", "--seed=17", "-v") Eventually(session).Should(gexec.Exit(1)) Ω(session).Should(gbytes.Say(`3 specs`)) Ω(session).Should(gbytes.Say(`a full timeline a flaky test retries a few times`)) Ω(session).Should(gbytes.Say(`STEP: logging some events`)) Ω(session).Should(gbytes.Say(`hello!`)) Ω(session).Should(gbytes.Say(`a report!`)) Ω(session).Should(gbytes.Say(` Of great value`)) Ω(session).Should(gbytes.Say(`let's try...`)) Ω(session).Should(gbytes.Say(`\[FAILED\] in \[It\]`)) Ω(session).Should(gbytes.Say(`STEP: cleaning up a bit`)) Ω(session).Should(gbytes.Say(`all done!`)) Ω(session).Should(gbytes.Say(`Attempt #1 Failed. Retrying ` + retryDenoter)) Ω(session).Should(gbytes.Say(`STEP: logging some events`)) Ω(session).Should(gbytes.Say(`hello!`)) Ω(session).Should(gbytes.Say(`a report!`)) Ω(session).Should(gbytes.Say(` Of great value`)) Ω(session).Should(gbytes.Say(`let's try...`)) Ω(session).Should(gbytes.Say(`\[FAILED\] in \[It\]`)) Ω(session).Should(gbytes.Say(`STEP: cleaning up a bit`)) Ω(session).Should(gbytes.Say(`all done!`)) Ω(session).Should(gbytes.Say(`Attempt #2 Failed. Retrying ` + retryDenoter)) Ω(session).Should(gbytes.Say(`STEP: logging some events`)) Ω(session).Should(gbytes.Say(`hello!`)) Ω(session).Should(gbytes.Say(`a report!`)) Ω(session).Should(gbytes.Say(` Of great value`)) Ω(session).Should(gbytes.Say(`let's try...`)) Ω(session).Should(gbytes.Say(`hooray!`)) Ω(session).Should(gbytes.Say(`feeling sleepy...`)) Ω(session).Should(gbytes.Say(`Automatically polling progress`)) Ω(session).Should(gbytes.Say(`>\s*time\.Sleep\(time.Millisecond \* 200\)`)) Ω(session).Should(gbytes.Say(`STEP: cleaning up a bit`)) Ω(session).Should(gbytes.Say(`all done!`)) Ω(session).Should(gbytes.Say(retryDenoter + ` \[FLAKEY TEST - TOOK 3 ATTEMPTS TO PASS\]`)) Ω(session).Should(gbytes.Say(`a full timeline a test with multiple failures \[It\] times out`)) Ω(session).Should(gbytes.Say(`\[TIMEDOUT\] A node timeout occurred`)) Ω(session).Should(gbytes.Say(`This is the Progress Report generated when the node timeout occurred:`)) Ω(session).Should(gbytes.Say(`>\s*<-ctx\.Done\(\)`)) Ω(session).Should(gbytes.Say(`\[FAILED\] A node timeout occurred and then the following failure was recorded in the timedout node before it exited:`)) Ω(session).Should(gbytes.Say(`welp`)) Ω(session).Should(gbytes.Say(`In \[It\]`)) Ω(session).Should(gbytes.Say(`There were additional failures detected. To view them in detail run ginkgo -vv`)) Ω(session).Should(gbytes.Say(`a full timeline passes happily`)) Ω(session).Should(gbytes.Say(`a verbose-only report`)) Ω(session).ShouldNot(gbytes.Say(`a hidden report`)) Ω(session).Should(gbytes.Say(denoter)) Ω(session).Should(gbytes.Say(`Summarizing 1 Failure:`)) Ω(session).Should(gbytes.Say(`\[TIMEDOUT\]`)) }) }) Context("when running with -vv", func() { It("should emit a timeline", func() { session := startGinkgo(fm.PathTo("timeline"), "--no-color", "--seed=17", "-vv") Eventually(session).Should(gexec.Exit(1)) Ω(session).Should(gbytes.Say(`3 specs`)) Ω(session).Should(gbytes.Say(`a full timeline \[Serial\]\n`)) Ω(session).Should(gbytes.Say(`a flaky test\n`)) Ω(session).Should(gbytes.Say(`retries a few times\n`)) Ω(session).Should(gbytes.Say(`> Enter \[BeforeEach\] a flaky test`)) Ω(session).Should(gbytes.Say(`STEP: logging some events`)) Ω(session).Should(gbytes.Say(`hello!`)) Ω(session).Should(gbytes.Say(`a report!`)) Ω(session).Should(gbytes.Say(` Of great value`)) Ω(session).Should(gbytes.Say(`< Exit \[BeforeEach\] a flaky test`)) Ω(session).Should(gbytes.Say(`let's try...`)) Ω(session).Should(gbytes.Say(`\[FAILED\] bam!`)) Ω(session).Should(gbytes.Say(`In \[It\]`)) Ω(session).Should(gbytes.Say(`STEP: cleaning up a bit`)) Ω(session).Should(gbytes.Say(`all done!`)) Ω(session).Should(gbytes.Say(`END STEP: cleaning up a bit`)) Ω(session).Should(gbytes.Say(`Attempt #1 Failed. Retrying ` + retryDenoter)) Ω(session).Should(gbytes.Say(`STEP: logging some events`)) Ω(session).Should(gbytes.Say(`hello!`)) Ω(session).Should(gbytes.Say(`a report!`)) Ω(session).Should(gbytes.Say(` Of great value`)) Ω(session).Should(gbytes.Say(`let's try...`)) Ω(session).Should(gbytes.Say(`\[FAILED\] bam!`)) Ω(session).Should(gbytes.Say(`In \[It\]`)) Ω(session).Should(gbytes.Say(`STEP: cleaning up a bit`)) Ω(session).Should(gbytes.Say(`all done!`)) Ω(session).Should(gbytes.Say(`Attempt #2 Failed. Retrying ` + retryDenoter)) Ω(session).Should(gbytes.Say(`STEP: logging some events`)) Ω(session).Should(gbytes.Say(`hello!`)) Ω(session).Should(gbytes.Say(`a report!`)) Ω(session).Should(gbytes.Say(` Of great value`)) Ω(session).Should(gbytes.Say(`let's try...`)) Ω(session).Should(gbytes.Say(`hooray!`)) Ω(session).Should(gbytes.Say(`feeling sleepy...`)) Ω(session).Should(gbytes.Say(`Automatically polling progress`)) Ω(session).Should(gbytes.Say(`>\s*time\.Sleep\(time.Millisecond \* 200\)`)) Ω(session).Should(gbytes.Say(`STEP: cleaning up a bit`)) Ω(session).Should(gbytes.Say(`all done!`)) Ω(session).Should(gbytes.Say(retryDenoter + ` \[FLAKEY TEST - TOOK 3 ATTEMPTS TO PASS\]`)) Ω(session).Should(gbytes.Say(`times out`)) Ω(session).Should(gbytes.Say(`\[TIMEDOUT\] A node timeout occurred`)) Ω(session).Should(gbytes.Say(`This is the Progress Report generated when the node timeout occurred:`)) Ω(session).Should(gbytes.Say(`>\s*<-ctx\.Done\(\)`)) Ω(session).Should(gbytes.Say(`\[FAILED\] A node timeout occurred and then the following failure was recorded in the timedout node before it exited:`)) Ω(session).Should(gbytes.Say(`welp`)) Ω(session).Should(gbytes.Say(`In \[It\]`)) Ω(session).Should(gbytes.Say(`\[PANICKED\] Test Panicked`)) Ω(session).Should(gbytes.Say(`aaah!`)) Ω(session).Should(gbytes.Say(`Full Stack Trace`)) Ω(session).Should(gbytes.Say(denoter + ` \[TIMEDOUT\]`)) Ω(session).Should(gbytes.Say(`passes happily`)) Ω(session).Should(gbytes.Say(`> Enter \[It\] passes happily`)) Ω(session).Should(gbytes.Say(`a verbose-only report`)) Ω(session).ShouldNot(gbytes.Say(`a hidden report`)) Ω(session).Should(gbytes.Say(`< Exit \[It\] passes happily`)) Ω(session).Should(gbytes.Say(denoter)) Ω(session).Should(gbytes.Say(`Summarizing 1 Failure:`)) Ω(session).Should(gbytes.Say(`\[TIMEDOUT\]`)) }) }) Context("when running with -v in parallel", func() { It("should emit a timeline", func() { session := startGinkgo(fm.PathTo("timeline"), "--no-color", "--seed=17", "-v", "-nodes=2") Eventually(session).Should(gexec.Exit(1)) Ω(session).Should(gbytes.Say(`3 specs`)) Ω(session).Should(gbytes.Say(retryDenoter + ` \[FLAKEY TEST - TOOK 3 ATTEMPTS TO PASS\]`)) Ω(session).Should(gbytes.Say(`STEP: logging some events`)) Ω(session).Should(gbytes.Say(`hello!`)) Ω(session).Should(gbytes.Say(`a report!`)) Ω(session).Should(gbytes.Say(` Of great value`)) Ω(session).Should(gbytes.Say(`let's try...`)) Ω(session).Should(gbytes.Say(`\[FAILED\] in \[It\]`)) Ω(session).Should(gbytes.Say(`STEP: cleaning up a bit`)) Ω(session).Should(gbytes.Say(`all done!`)) Ω(session).Should(gbytes.Say(`Attempt #1 Failed. Retrying ` + retryDenoter)) Ω(session).Should(gbytes.Say(`STEP: logging some events`)) Ω(session).Should(gbytes.Say(`hello!`)) Ω(session).Should(gbytes.Say(`a report!`)) Ω(session).Should(gbytes.Say(` Of great value`)) Ω(session).Should(gbytes.Say(`let's try...`)) Ω(session).Should(gbytes.Say(`\[FAILED\] in \[It\]`)) Ω(session).Should(gbytes.Say(`STEP: cleaning up a bit`)) Ω(session).Should(gbytes.Say(`all done!`)) Ω(session).Should(gbytes.Say(`Attempt #2 Failed. Retrying ` + retryDenoter)) Ω(session).Should(gbytes.Say(`STEP: logging some events`)) Ω(session).Should(gbytes.Say(`hello!`)) Ω(session).Should(gbytes.Say(`a report!`)) Ω(session).Should(gbytes.Say(` Of great value`)) Ω(session).Should(gbytes.Say(`let's try...`)) Ω(session).Should(gbytes.Say(`hooray!`)) Ω(session).Should(gbytes.Say(`feeling sleepy...`)) Ω(session).Should(gbytes.Say(`Automatically polling progress`)) Ω(session).Should(gbytes.Say(`>\s*time\.Sleep\(time.Millisecond \* 200\)`)) Ω(session).Should(gbytes.Say(`STEP: cleaning up a bit`)) Ω(session).Should(gbytes.Say(`all done!`)) Ω(session).Should(gbytes.Say(`a full timeline a test with multiple failures \[It\] times out`)) Ω(session).Should(gbytes.Say(`\[TIMEDOUT\] A node timeout occurred`)) Ω(session).Should(gbytes.Say(`This is the Progress Report generated when the node timeout occurred:`)) Ω(session).Should(gbytes.Say(`>\s*<-ctx\.Done\(\)`)) Ω(session).Should(gbytes.Say(`\[FAILED\] A node timeout occurred and then the following failure was recorded in the timedout node before it exited:`)) Ω(session).Should(gbytes.Say(`welp`)) Ω(session).Should(gbytes.Say(`In \[It\]`)) Ω(session).Should(gbytes.Say(`There were additional failures detected. To view them in detail run ginkgo -vv`)) Ω(session).Should(gbytes.Say(denoter)) Ω(session).Should(gbytes.Say(`a full timeline passes happily`)) Ω(session).Should(gbytes.Say(`a verbose-only report`)) Ω(session).ShouldNot(gbytes.Say(`a hidden report`)) Ω(session).Should(gbytes.Say(`Summarizing 1 Failure:`)) Ω(session).Should(gbytes.Say(`\[TIMEDOUT\]`)) }) }) Context("when running with -vv in parallel", func() { It("should emit a timeline", func() { session := startGinkgo(fm.PathTo("timeline"), "--no-color", "--seed=17", "-vv", "-nodes=2") Eventually(session).Should(gexec.Exit(1)) Ω(session).Should(gbytes.Say(`3 specs`)) Ω(session).Should(gbytes.Say(retryDenoter + ` \[FLAKEY TEST - TOOK 3 ATTEMPTS TO PASS\]`)) Ω(session).Should(gbytes.Say(`a full timeline \[Serial\]\n`)) Ω(session).Should(gbytes.Say(`a flaky test\n`)) Ω(session).Should(gbytes.Say(`retries a few times\n`)) Ω(session).Should(gbytes.Say(`> Enter \[BeforeEach\] a flaky test`)) Ω(session).Should(gbytes.Say(`STEP: logging some events`)) Ω(session).Should(gbytes.Say(`hello!`)) Ω(session).Should(gbytes.Say(`a report!`)) Ω(session).Should(gbytes.Say(` Of great value`)) Ω(session).Should(gbytes.Say(`< Exit \[BeforeEach\] a flaky test`)) Ω(session).Should(gbytes.Say(`let's try...`)) Ω(session).Should(gbytes.Say(`\[FAILED\] Failure recorded during attempt 1:`)) Ω(session).Should(gbytes.Say(`bam!`)) Ω(session).Should(gbytes.Say(`In \[It\]`)) Ω(session).Should(gbytes.Say(`STEP: cleaning up a bit`)) Ω(session).Should(gbytes.Say(`all done!`)) Ω(session).Should(gbytes.Say(`END STEP: cleaning up a bit`)) Ω(session).Should(gbytes.Say(`Attempt #1 Failed. Retrying ` + retryDenoter)) Ω(session).Should(gbytes.Say(`STEP: logging some events`)) Ω(session).Should(gbytes.Say(`hello!`)) Ω(session).Should(gbytes.Say(`a report!`)) Ω(session).Should(gbytes.Say(` Of great value`)) Ω(session).Should(gbytes.Say(`let's try...`)) Ω(session).Should(gbytes.Say(`\[FAILED\] Failure recorded during attempt 2:`)) Ω(session).Should(gbytes.Say(`bam!`)) Ω(session).Should(gbytes.Say(`In \[It\]`)) Ω(session).Should(gbytes.Say(`STEP: cleaning up a bit`)) Ω(session).Should(gbytes.Say(`all done!`)) Ω(session).Should(gbytes.Say(`Attempt #2 Failed. Retrying ` + retryDenoter)) Ω(session).Should(gbytes.Say(`STEP: logging some events`)) Ω(session).Should(gbytes.Say(`hello!`)) Ω(session).Should(gbytes.Say(`a report!`)) Ω(session).Should(gbytes.Say(` Of great value`)) Ω(session).Should(gbytes.Say(`let's try...`)) Ω(session).Should(gbytes.Say(`hooray!`)) Ω(session).Should(gbytes.Say(`feeling sleepy...`)) Ω(session).Should(gbytes.Say(`Automatically polling progress`)) Ω(session).Should(gbytes.Say(`>\s*time\.Sleep\(time.Millisecond \* 200\)`)) Ω(session).Should(gbytes.Say(`STEP: cleaning up a bit`)) Ω(session).Should(gbytes.Say(`all done!`)) Ω(session).Should(gbytes.Say(denoter + ` \[TIMEDOUT\]`)) Ω(session).Should(gbytes.Say(`times out`)) Ω(session).Should(gbytes.Say(`\[TIMEDOUT\] A node timeout occurred`)) Ω(session).Should(gbytes.Say(`This is the Progress Report generated when the node timeout occurred:`)) Ω(session).Should(gbytes.Say(`>\s*<-ctx\.Done\(\)`)) Ω(session).Should(gbytes.Say(`\[FAILED\] A node timeout occurred and then the following failure was recorded in the timedout node before it exited:`)) Ω(session).Should(gbytes.Say(`welp`)) Ω(session).Should(gbytes.Say(`In \[It\]`)) Ω(session).Should(gbytes.Say(`\[PANICKED\] Test Panicked`)) Ω(session).Should(gbytes.Say(`aaah!`)) Ω(session).Should(gbytes.Say(`Full Stack Trace`)) Ω(session).Should(gbytes.Say(denoter)) Ω(session).Should(gbytes.Say(`passes happily`)) Ω(session).Should(gbytes.Say(`> Enter \[It\] passes happily`)) Ω(session).Should(gbytes.Say(`a verbose-only report`)) Ω(session).ShouldNot(gbytes.Say(`a hidden report`)) Ω(session).Should(gbytes.Say(`< Exit \[It\] passes happily`)) Ω(session).Should(gbytes.Say(`Summarizing 1 Failure:`)) Ω(session).Should(gbytes.Say(`\[TIMEDOUT\]`)) }) }) }) golang-github-onsi-ginkgo-v2-2.22.0/integration/verbose_and_succinct_test.go000066400000000000000000000077211472321612100271740ustar00rootroot00000000000000package integration_test import ( "regexp" "runtime" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" "github.com/onsi/gomega/gexec" ) var _ = Describe("Verbose And Succinct Mode", func() { denoter := "•" if runtime.GOOS == "windows" { denoter = "+" } Context("when running one package", func() { BeforeEach(func() { fm.MountFixture("passing_ginkgo_tests") }) It("should default to non-succinct mode", func() { session := startGinkgo(fm.PathTo("passing_ginkgo_tests"), "--no-color") Eventually(session).Should(gexec.Exit(0)) output := session.Out.Contents() Ω(output).Should(ContainSubstring("Running Suite: Passing_ginkgo_tests Suite")) }) }) Context("when running more than one package", func() { BeforeEach(func() { fm.MountFixture("passing_ginkgo_tests") fm.MountFixture("more_ginkgo_tests") }) Context("with no flags set", func() { It("should default to succinct mode", func() { session := startGinkgo(fm.TmpDir, "--no-color", "passing_ginkgo_tests", "more_ginkgo_tests") Eventually(session).Should(gexec.Exit(0)) output := session.Out.Contents() Ω(output).Should(MatchRegexp(`\] Passing_ginkgo_tests Suite - 5/5 specs [%s]{5} SUCCESS!`, regexp.QuoteMeta(denoter))) Ω(output).Should(MatchRegexp(`\] More_ginkgo_tests Suite - 3/3 specs [%s]{3} SUCCESS!`, regexp.QuoteMeta(denoter))) }) }) Context("with --succinct=false", func() { It("should not be in succinct mode", func() { session := startGinkgo(fm.TmpDir, "--no-color", "--succinct=false", "passing_ginkgo_tests", "more_ginkgo_tests") Eventually(session).Should(gexec.Exit(0)) output := session.Out.Contents() Ω(output).Should(ContainSubstring("Running Suite: Passing_ginkgo_tests Suite")) Ω(output).Should(ContainSubstring("Running Suite: More_ginkgo_tests Suite")) }) }) Context("with -v", func() { It("should not be in succinct mode, but should be verbose", func() { session := startGinkgo(fm.TmpDir, "--no-color", "-v", "passing_ginkgo_tests", "more_ginkgo_tests") Eventually(session).Should(gexec.Exit(0)) output := session.Out.Contents() Ω(output).Should(ContainSubstring("Running Suite: Passing_ginkgo_tests Suite")) Ω(output).Should(ContainSubstring("Running Suite: More_ginkgo_tests Suite")) Ω(output).Should(ContainSubstring("should proxy strings")) Ω(output).Should(ContainSubstring("should always pass")) }) It("should emit output from Bys", func() { session := startGinkgo(fm.PathTo("passing_ginkgo_tests"), "--no-color", "-v") Eventually(session).Should(gexec.Exit(0)) output := session.Out.Contents() Ω(output).Should(ContainSubstring("emitting one By")) Ω(output).Should(ContainSubstring("emitting another By")) }) It("doesn't trigger the race detector", func() { session := startGinkgo(fm.PathTo("passing_ginkgo_tests"), "--no-color", "-v", "-race") Eventually(session).Should(gexec.Exit(0)) output := session.Out.Contents() Ω(output).Should(ContainSubstring("avoiding the race detector")) }) }) Context("with -vv", func() { It("should not be in succinct mode, but should be verbose", func() { session := startGinkgo(fm.TmpDir, "--no-color", "-vv", "passing_ginkgo_tests", "more_ginkgo_tests") Eventually(session).Should(gexec.Exit(0)) output := session.Out.Contents() Ω(output).Should(ContainSubstring("Running Suite: Passing_ginkgo_tests Suite")) Ω(output).Should(ContainSubstring("Running Suite: More_ginkgo_tests Suite")) Ω(output).Should(ContainSubstring("should proxy strings")) Ω(output).Should(ContainSubstring("should always pass")) }) It("should emit output from Bys", func() { session := startGinkgo(fm.PathTo("passing_ginkgo_tests"), "--no-color", "-vv") Eventually(session).Should(gexec.Exit(0)) output := session.Out.Contents() Ω(output).Should(ContainSubstring("emitting one By")) Ω(output).Should(ContainSubstring("emitting another By")) }) }) }) }) golang-github-onsi-ginkgo-v2-2.22.0/integration/watch_test.go000066400000000000000000000234341472321612100241170ustar00rootroot00000000000000package integration_test import ( "os" "path/filepath" "time" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" "github.com/onsi/gomega/gbytes" "github.com/onsi/gomega/gexec" ) var _ = Describe("Watch", Label("SLOW"), func() { var session *gexec.Session BeforeEach(func() { fm.MountFixture("watch", "A") fm.MountFixture("watch", "B") fm.MountFixture("watch", "C") }) createFile := func(path string, contents []byte) { time.Sleep(time.Second) err := os.WriteFile(path, contents, 0666) Ω(err).ShouldNot(HaveOccurred()) } createHiddenTest := func(pkgToModify string) { path := filepath.Join(pkgToModify, ".#"+pkgToModify+"_test.go") fm.MkEmpty(filepath.Join("watch", pkgToModify)) createFile(fm.PathTo("watch", path), []byte("//")) } modifyFile := func(path string) { time.Sleep(time.Second) content, err := os.ReadFile(path) Ω(err).ShouldNot(HaveOccurred()) content = append(content, []byte("//")...) err = os.WriteFile(path, content, 0666) Ω(err).ShouldNot(HaveOccurred()) } modifyCode := func(pkgToModify string) { path := filepath.Join(pkgToModify, pkgToModify+".go") modifyFile(fm.PathTo("watch", path)) } modifyJSON := func(pkgToModify string) { path := filepath.Join(pkgToModify, pkgToModify+".json") modifyFile(fm.PathTo("watch", path)) } modifyTest := func(pkgToModify string) { path := filepath.Join(pkgToModify, pkgToModify+"_test.go") modifyFile(fm.PathTo("watch", path)) } AfterEach(func() { if session != nil { session.Kill().Wait() } }) It("should be set up correctly", func() { session = startGinkgo(fm.PathTo("watch"), "-r") Eventually(session).Should(gexec.Exit(0)) Ω(session.Out.Contents()).Should(ContainSubstring("A Suite")) Ω(session.Out.Contents()).Should(ContainSubstring("B Suite")) Ω(session.Out.Contents()).Should(ContainSubstring("C Suite")) Ω(session.Out.Contents()).Should(ContainSubstring("Ginkgo ran 3 suites")) }) Context("when watching just one test suite", func() { It("should immediately run, and should rerun when the test suite changes", func() { session = startGinkgo(fm.PathTo("watch"), "watch", "-succinct", "A") Eventually(session).Should(gbytes.Say("A Suite")) modifyCode("A") Eventually(session).Should(gbytes.Say("Detected changes in")) Eventually(session).Should(gbytes.Say("A Suite")) session.Kill().Wait() }) }) Context("when watching several test suites", func() { It("should not immediately run, but should rerun a test when its code changes", func() { session = startGinkgo(fm.PathTo("watch"), "watch", "-succinct", "-r") Eventually(session).Should(gbytes.Say("Identified 3 test suites")) Consistently(session).ShouldNot(gbytes.Say("A Suite|B Suite|C Suite")) modifyCode("A") Eventually(session).Should(gbytes.Say("Detected changes in")) Eventually(session).Should(gbytes.Say("A Suite")) Consistently(session).ShouldNot(gbytes.Say("B Suite|C Suite")) session.Kill().Wait() }) }) Describe("watching dependencies", func() { Context("with a depth of 2", func() { It("should watch down to that depth", func() { session = startGinkgo(fm.PathTo("watch"), "watch", "-succinct", "-r", "-depth=2") Eventually(session).Should(gbytes.Say("Identified 3 test suites")) Eventually(session).Should(gbytes.Say(`A \[`)) Eventually(session).Should(gbytes.Say(`B \[`)) Eventually(session).Should(gbytes.Say(`C \[`)) modifyCode("A") Eventually(session).Should(gbytes.Say("Detected changes in")) Eventually(session).Should(gbytes.Say("A Suite")) Consistently(session).ShouldNot(gbytes.Say("B Suite|C Suite")) modifyCode("B") Eventually(session).Should(gbytes.Say("Detected changes in")) Eventually(session).Should(gbytes.Say("B Suite")) Eventually(session).Should(gbytes.Say("A Suite")) Consistently(session).ShouldNot(gbytes.Say("C Suite")) modifyCode("C") Eventually(session).Should(gbytes.Say("Detected changes in")) Eventually(session).Should(gbytes.Say("C Suite")) Eventually(session).Should(gbytes.Say("B Suite")) Eventually(session).Should(gbytes.Say("A Suite")) }) }) Context("with a depth of 1", func() { It("should watch down to that depth", func() { session = startGinkgo(fm.PathTo("watch"), "watch", "-succinct", "-r", "-depth=1") Eventually(session).Should(gbytes.Say("Identified 3 test suites")) Eventually(session).Should(gbytes.Say(`A \[`)) Eventually(session).Should(gbytes.Say(`B \[`)) Eventually(session).Should(gbytes.Say(`C \[`)) modifyCode("A") Eventually(session).Should(gbytes.Say("Detected changes in")) Eventually(session).Should(gbytes.Say("A Suite")) Consistently(session).ShouldNot(gbytes.Say("B Suite|C Suite")) modifyCode("B") Eventually(session).Should(gbytes.Say("Detected changes in")) Eventually(session).Should(gbytes.Say("B Suite")) Eventually(session).Should(gbytes.Say("A Suite")) Consistently(session).ShouldNot(gbytes.Say("C Suite")) modifyCode("C") Eventually(session).Should(gbytes.Say("Detected changes in")) Eventually(session).Should(gbytes.Say("C Suite")) Eventually(session).Should(gbytes.Say("B Suite")) Consistently(session).ShouldNot(gbytes.Say("A Suite")) }) }) Context("with a depth of 0", func() { It("should not watch any dependencies", func() { session = startGinkgo(fm.PathTo("watch"), "watch", "-succinct", "-r", "-depth=0") Eventually(session).Should(gbytes.Say("Identified 3 test suites")) Eventually(session).Should(gbytes.Say(`A \[`)) Eventually(session).Should(gbytes.Say(`B \[`)) Eventually(session).Should(gbytes.Say(`C \[`)) modifyCode("A") Eventually(session).Should(gbytes.Say("Detected changes in")) Eventually(session).Should(gbytes.Say("A Suite")) Consistently(session).ShouldNot(gbytes.Say("B Suite|C Suite")) modifyCode("B") Eventually(session).Should(gbytes.Say("Detected changes in")) Eventually(session).Should(gbytes.Say("B Suite")) Consistently(session).ShouldNot(gbytes.Say("A Suite|C Suite")) modifyCode("C") Eventually(session).Should(gbytes.Say("Detected changes in")) Eventually(session).Should(gbytes.Say("C Suite")) Consistently(session).ShouldNot(gbytes.Say("A Suite|B Suite")) }) }) It("should not trigger dependents when tests are changed", func() { session = startGinkgo(fm.PathTo("watch"), "watch", "-succinct", "-r", "-depth=2") Eventually(session).Should(gbytes.Say("Identified 3 test suites")) Eventually(session).Should(gbytes.Say(`A \[`)) Eventually(session).Should(gbytes.Say(`B \[`)) Eventually(session).Should(gbytes.Say(`C \[`)) modifyTest("A") Eventually(session).Should(gbytes.Say("Detected changes in")) Eventually(session).Should(gbytes.Say("A Suite")) Consistently(session).ShouldNot(gbytes.Say("B Suite|C Suite")) modifyTest("B") Eventually(session).Should(gbytes.Say("Detected changes in")) Eventually(session).Should(gbytes.Say("B Suite")) Consistently(session).ShouldNot(gbytes.Say("A Suite|C Suite")) modifyTest("C") Eventually(session).Should(gbytes.Say("Detected changes in")) Eventually(session).Should(gbytes.Say("C Suite")) Consistently(session).ShouldNot(gbytes.Say("A Suite|B Suite")) }) Context("when a hidden test file is created", func() { It("shouldn't trigger the test suite", func() { session = startGinkgo(fm.PathTo("watch"), "watch", "-r") Eventually(session).Should(gbytes.Say("Identified 3 test suites")) Eventually(session).Should(gbytes.Say(`A \[`)) Eventually(session).Should(gbytes.Say(`B \[`)) Eventually(session).Should(gbytes.Say(`C \[`)) createHiddenTest("A") Consistently(session).ShouldNot(gbytes.Say("Detected changes in")) }) }) }) Describe("adjusting the watch regular expression", func() { Describe("the default regular expression", func() { It("should only trigger when go files are changed", func() { session = startGinkgo(fm.PathTo("watch"), "watch", "-succinct", "-r", "-depth=2") Eventually(session).Should(gbytes.Say("Identified 3 test suites")) Eventually(session).Should(gbytes.Say(`A \[`)) Eventually(session).Should(gbytes.Say(`B \[`)) Eventually(session).Should(gbytes.Say(`C \[`)) modifyJSON("C") Consistently(session).ShouldNot(gbytes.Say("Detected changes in")) Consistently(session).ShouldNot(gbytes.Say("A Suite|B Suite|C Suite")) }) }) Describe("modifying the regular expression", func() { It("should trigger if the regexp matches", func() { session = startGinkgo(fm.PathTo("watch"), "watch", "-succinct", "-r", "-depth=2", `-watch-regexp=\.json$`) Eventually(session).Should(gbytes.Say("Identified 3 test suites")) Eventually(session).Should(gbytes.Say(`A \[`)) Eventually(session).Should(gbytes.Say(`B \[`)) Eventually(session).Should(gbytes.Say(`C \[`)) modifyJSON("C") Eventually(session).Should(gbytes.Say("Detected changes in")) Eventually(session).Should(gbytes.Say("C Suite")) Eventually(session).Should(gbytes.Say("B Suite")) Eventually(session).Should(gbytes.Say("A Suite")) }) }) }) Describe("when new test suite is added", func() { It("should start monitoring that test suite", func() { session = startGinkgo(fm.PathTo("watch"), "watch", "-succinct", "-r", "-depth=1") Eventually(session).Should(gbytes.Say("Watching 3 suites")) fm.MountFixture("watch", "D") Eventually(session).Should(gbytes.Say("Detected 1 new suite")) Eventually(session).Should(gbytes.Say(`D \[`)) Eventually(session).Should(gbytes.Say("D Suite")) modifyCode("D") Eventually(session).Should(gbytes.Say("Detected changes in")) Eventually(session).Should(gbytes.Say("D Suite")) modifyCode("C") Eventually(session).Should(gbytes.Say("Detected changes in")) Eventually(session).Should(gbytes.Say("C Suite")) Eventually(session).Should(gbytes.Say("D Suite")) }) }) }) golang-github-onsi-ginkgo-v2-2.22.0/internal/000077500000000000000000000000001472321612100207065ustar00rootroot00000000000000golang-github-onsi-ginkgo-v2-2.22.0/internal/counter.go000066400000000000000000000002271472321612100227150ustar00rootroot00000000000000package internal func MakeIncrementingIndexCounter() func() (int, error) { idx := -1 return func() (int, error) { idx += 1 return idx, nil } } golang-github-onsi-ginkgo-v2-2.22.0/internal/counter_test.go000066400000000000000000000005261472321612100237560ustar00rootroot00000000000000package internal_test import ( . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" "github.com/onsi/ginkgo/v2/internal" ) var _ = Describe("Counter", func() { It("counts. plain and simple.", func() { counter := internal.MakeIncrementingIndexCounter() for i := 0; i < 10; i += 1 { Ω(counter()).Should(Equal(i)) } }) }) golang-github-onsi-ginkgo-v2-2.22.0/internal/failer.go000066400000000000000000000035251472321612100225040ustar00rootroot00000000000000package internal import ( "fmt" "sync" "github.com/onsi/ginkgo/v2/types" ) type Failer struct { lock *sync.Mutex failure types.Failure state types.SpecState } func NewFailer() *Failer { return &Failer{ lock: &sync.Mutex{}, state: types.SpecStatePassed, } } func (f *Failer) GetState() types.SpecState { f.lock.Lock() defer f.lock.Unlock() return f.state } func (f *Failer) GetFailure() types.Failure { f.lock.Lock() defer f.lock.Unlock() return f.failure } func (f *Failer) Panic(location types.CodeLocation, forwardedPanic interface{}) { f.lock.Lock() defer f.lock.Unlock() if f.state == types.SpecStatePassed { f.state = types.SpecStatePanicked f.failure = types.Failure{ Message: "Test Panicked", Location: location, ForwardedPanic: fmt.Sprintf("%v", forwardedPanic), } } } func (f *Failer) Fail(message string, location types.CodeLocation) { f.lock.Lock() defer f.lock.Unlock() if f.state == types.SpecStatePassed { f.state = types.SpecStateFailed f.failure = types.Failure{ Message: message, Location: location, } } } func (f *Failer) Skip(message string, location types.CodeLocation) { f.lock.Lock() defer f.lock.Unlock() if f.state == types.SpecStatePassed { f.state = types.SpecStateSkipped f.failure = types.Failure{ Message: message, Location: location, } } } func (f *Failer) AbortSuite(message string, location types.CodeLocation) { f.lock.Lock() defer f.lock.Unlock() if f.state == types.SpecStatePassed { f.state = types.SpecStateAborted f.failure = types.Failure{ Message: message, Location: location, } } } func (f *Failer) Drain() (types.SpecState, types.Failure) { f.lock.Lock() defer f.lock.Unlock() failure := f.failure outcome := f.state f.state = types.SpecStatePassed f.failure = types.Failure{} return outcome, failure } golang-github-onsi-ginkgo-v2-2.22.0/internal/failer_test.go000066400000000000000000000105261472321612100235420ustar00rootroot00000000000000package internal_test import ( . "github.com/onsi/ginkgo/v2" "github.com/onsi/ginkgo/v2/internal" "github.com/onsi/ginkgo/v2/types" . "github.com/onsi/gomega" ) var _ = Describe("Failer", func() { var failer *internal.Failer var clA types.CodeLocation var clB types.CodeLocation BeforeEach(func() { clA = CL("file_a.go") clB = CL("file_b.go") failer = internal.NewFailer() }) Context("with no failures", func() { It("should return success when drained", func() { state, failure := failer.Drain() Ω(state).Should(Equal(types.SpecStatePassed)) Ω(failure).Should(BeZero()) }) }) Describe("when told of a failure", func() { BeforeEach(func() { failer.Fail("something failed", clA) }) It("should record the failure", func() { state, failure := failer.Drain() Ω(state).Should(Equal(types.SpecStateFailed)) Ω(failure).Should(Equal(types.Failure{ Message: "something failed", Location: clA, })) }) Context("when told of anotehr failure", func() { It("discards the second failure, preserving the original", func() { failer.Fail("something else failed", clB) state, failure := failer.Drain() Ω(state).Should(Equal(types.SpecStateFailed)) Ω(failure).Should(Equal(types.Failure{ Message: "something failed", Location: clA, })) }) }) }) Describe("when told to skip", func() { Context("when no failure has occurred", func() { It("registers the test as skipped", func() { failer.Skip("something skipped", clA) state, failure := failer.Drain() Ω(state).Should(Equal(types.SpecStateSkipped)) Ω(failure).Should(Equal(types.Failure{ Message: "something skipped", Location: clA, })) }) }) Context("when a failure has already occurred", func() { BeforeEach(func() { failer.Fail("something failed", clA) }) It("does not modify the failure", func() { failer.Skip("something skipped", clB) state, failure := failer.Drain() Ω(state).Should(Equal(types.SpecStateFailed)) Ω(failure).Should(Equal(types.Failure{ Message: "something failed", Location: clA, })) }) }) }) Describe("when told to abort", func() { Context("when no failure has occurred", func() { It("registers the test as aborted", func() { failer.AbortSuite("something aborted", clA) state, failure := failer.Drain() Ω(state).Should(Equal(types.SpecStateAborted)) Ω(failure).Should(Equal(types.Failure{ Message: "something aborted", Location: clA, })) }) }) Context("when a failure has already occurred", func() { BeforeEach(func() { failer.Fail("something failed", clA) }) It("does not modify the failure", func() { failer.AbortSuite("something aborted", clA) state, failure := failer.Drain() Ω(state).Should(Equal(types.SpecStateFailed)) Ω(failure).Should(Equal(types.Failure{ Message: "something failed", Location: clA, })) }) }) }) Describe("when told to panic", func() { BeforeEach(func() { failer.Panic(clA, 17) }) It("should record the panic", func() { state, failure := failer.Drain() Ω(state).Should(Equal(types.SpecStatePanicked)) Ω(failure).Should(Equal(types.Failure{ Message: "Test Panicked", Location: clA, ForwardedPanic: "17", })) }) Context("when told of another panic", func() { It("discards the second panic, preserving the original", func() { failer.Panic(clB, 23) state, failure := failer.Drain() Ω(state).Should(Equal(types.SpecStatePanicked)) Ω(failure).Should(Equal(types.Failure{ Message: "Test Panicked", Location: clA, ForwardedPanic: "17", })) }) }) }) Context("when drained", func() { BeforeEach(func() { failer.Fail("something failed", clA) state, _ := failer.Drain() Ω(state).Should(Equal(types.SpecStateFailed)) }) It("resets the failer such that subsequent drains pass", func() { state, failure := failer.Drain() Ω(state).Should(Equal(types.SpecStatePassed)) Ω(failure).Should(BeZero()) }) It("allows subsequent failures to be recorded", func() { failer.Fail("something else failed", clB) state, failure := failer.Drain() Ω(state).Should(Equal(types.SpecStateFailed)) Ω(failure).Should(Equal(types.Failure{ Message: "something else failed", Location: clB, })) }) }) }) golang-github-onsi-ginkgo-v2-2.22.0/internal/focus.go000066400000000000000000000113161472321612100223560ustar00rootroot00000000000000package internal import ( "regexp" "strings" "github.com/onsi/ginkgo/v2/types" ) /* If a container marked as focus has a descendant that is also marked as focus, Ginkgo's policy is to unmark the container's focus. This gives developers a more intuitive experience when debugging specs. It is common to focus a container to just run a subset of specs, then identify the specific specs within the container to focus - this policy allows the developer to simply focus those specific specs and not need to go back and turn the focus off of the container: As a common example, consider: FDescribe("something to debug", function() { It("works", function() {...}) It("works", function() {...}) FIt("doesn't work", function() {...}) It("works", function() {...}) }) here the developer's intent is to focus in on the `"doesn't work"` spec and not to run the adjacent specs in the focused `"something to debug"` container. The nested policy applied by this function enables this behavior. */ func ApplyNestedFocusPolicyToTree(tree *TreeNode) { var walkTree func(tree *TreeNode) bool walkTree = func(tree *TreeNode) bool { if tree.Node.MarkedPending { return false } hasFocusedDescendant := false for _, child := range tree.Children { childHasFocus := walkTree(child) hasFocusedDescendant = hasFocusedDescendant || childHasFocus } tree.Node.MarkedFocus = tree.Node.MarkedFocus && !hasFocusedDescendant return tree.Node.MarkedFocus || hasFocusedDescendant } walkTree(tree) } /* Ginkgo supports focussing specs using `FIt`, `FDescribe`, etc. - this is called "programmatic focus" It also supports focussing specs using regular expressions on the command line (`-focus=`, `-skip=`) that match against spec text and file filters (`-focus-files=`, `-skip-files=`) that match against code locations for nodes in specs. When both programmatic and file filters are provided their results are ANDed together. If multiple kinds of filters are provided, the file filters run first followed by the regex filters. This function sets the `Skip` property on specs by applying Ginkgo's focus policy: - If there are no CLI arguments and no programmatic focus, do nothing. - If a spec somewhere has programmatic focus skip any specs that have no programmatic focus. - If there are CLI arguments parse them and skip any specs that either don't match the focus filters or do match the skip filters. *Note:* specs with pending nodes are Skipped when created by NewSpec. */ func ApplyFocusToSpecs(specs Specs, description string, suiteLabels Labels, suiteConfig types.SuiteConfig) (Specs, bool) { focusString := strings.Join(suiteConfig.FocusStrings, "|") skipString := strings.Join(suiteConfig.SkipStrings, "|") type SkipCheck func(spec Spec) bool // by default, skip any specs marked pending skipChecks := []SkipCheck{func(spec Spec) bool { return spec.Nodes.HasNodeMarkedPending() }} hasProgrammaticFocus := false for _, spec := range specs { if spec.Nodes.HasNodeMarkedFocus() && !spec.Nodes.HasNodeMarkedPending() { hasProgrammaticFocus = true break } } if hasProgrammaticFocus { skipChecks = append(skipChecks, func(spec Spec) bool { return !spec.Nodes.HasNodeMarkedFocus() }) } if suiteConfig.LabelFilter != "" { labelFilter, _ := types.ParseLabelFilter(suiteConfig.LabelFilter) skipChecks = append(skipChecks, func(spec Spec) bool { return !labelFilter(UnionOfLabels(suiteLabels, spec.Nodes.UnionOfLabels())) }) } if len(suiteConfig.FocusFiles) > 0 { focusFilters, _ := types.ParseFileFilters(suiteConfig.FocusFiles) skipChecks = append(skipChecks, func(spec Spec) bool { return !focusFilters.Matches(spec.Nodes.CodeLocations()) }) } if len(suiteConfig.SkipFiles) > 0 { skipFilters, _ := types.ParseFileFilters(suiteConfig.SkipFiles) skipChecks = append(skipChecks, func(spec Spec) bool { return skipFilters.Matches(spec.Nodes.CodeLocations()) }) } if focusString != "" { // skip specs that don't match the focus string re := regexp.MustCompile(focusString) skipChecks = append(skipChecks, func(spec Spec) bool { return !re.MatchString(description + " " + spec.Text()) }) } if skipString != "" { // skip specs that match the skip string re := regexp.MustCompile(skipString) skipChecks = append(skipChecks, func(spec Spec) bool { return re.MatchString(description + " " + spec.Text()) }) } // skip specs if shouldSkip() is true. note that we do nothing if shouldSkip() is false to avoid overwriting skip status established by the node's pending status processedSpecs := Specs{} for _, spec := range specs { for _, skipCheck := range skipChecks { if skipCheck(spec) { spec.Skip = true break } } processedSpecs = append(processedSpecs, spec) } return processedSpecs, hasProgrammaticFocus } golang-github-onsi-ginkgo-v2-2.22.0/internal/focus_test.go000066400000000000000000000347471472321612100234320ustar00rootroot00000000000000package internal_test import ( . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" "github.com/onsi/ginkgo/v2/internal" "github.com/onsi/ginkgo/v2/types" ) var _ = Describe("Focus", func() { Describe("ApplyNestedFocusToTree", func() { It("unfocuses parent nodes that have a focused child node somewhere in their tree", func() { tree := TN(N(ntCon, "root", Focus), //should lose focus TN(N(ntCon, "A", Focus), //should stay focused TN(N(ntIt)), TN(N(ntIt)), ), TN(N(ntCon), TN(N(ntIt)), TN(N(ntIt, "B", Focus)), //should stay focused ), TN(N(ntCon, "C", Focus), //should lose focus TN(N(ntIt)), TN(N(ntIt, "D", Focus)), //should stay focused ), TN(N(ntCon, "E", Focus), //should lose focus TN(N(ntIt)), TN(N(ntCon), TN(N(ntIt)), TN(N(ntIt, "F", Focus)), // should stay focused ), ), TN(N(ntCon, "G", Focus), //should lose focus TN(N(ntIt)), TN(N(ntCon, "H", Focus), //should lose focus TN(N(ntIt)), TN(N(ntIt, "I", Focus)), //should stay focused ), TN(N(ntCon, "J", Focus), // should stay focused TN(N(ntIt)), ), ), ) internal.ApplyNestedFocusPolicyToTree(tree) Ω(mustFindNodeWithText(tree, "root").MarkedFocus).Should(BeFalse()) Ω(mustFindNodeWithText(tree, "A").MarkedFocus).Should(BeTrue()) Ω(mustFindNodeWithText(tree, "B").MarkedFocus).Should(BeTrue()) Ω(mustFindNodeWithText(tree, "C").MarkedFocus).Should(BeFalse()) Ω(mustFindNodeWithText(tree, "D").MarkedFocus).Should(BeTrue()) Ω(mustFindNodeWithText(tree, "E").MarkedFocus).Should(BeFalse()) Ω(mustFindNodeWithText(tree, "F").MarkedFocus).Should(BeTrue()) Ω(mustFindNodeWithText(tree, "G").MarkedFocus).Should(BeFalse()) Ω(mustFindNodeWithText(tree, "H").MarkedFocus).Should(BeFalse()) Ω(mustFindNodeWithText(tree, "I").MarkedFocus).Should(BeTrue()) Ω(mustFindNodeWithText(tree, "J").MarkedFocus).Should(BeTrue()) }) It("does not unfocus parent nodes if a focused child is the child of a pending child", func() { tree := TN(N(ntCon), TN(N(ntCon, "A", Focus), //should stay focused TN(N(ntIt)), TN(N(ntCon, "B", Pending), //should stay pending TN(N(ntIt)), TN(N(ntIt, "C", Focus)), //should stay focused ), ), ) internal.ApplyNestedFocusPolicyToTree(tree) Ω(mustFindNodeWithText(tree, "A").MarkedFocus).Should(BeTrue()) Ω(mustFindNodeWithText(tree, "B").MarkedPending).Should(BeTrue()) Ω(mustFindNodeWithText(tree, "C").MarkedFocus).Should(BeTrue()) }) }) Describe("ApplyFocusToSpecs", func() { var specs Specs var description string var suiteLabels Labels var conf types.SuiteConfig harvestSkips := func(specs Specs) []bool { out := []bool{} for _, spec := range specs { out = append(out, spec.Skip) } return out } BeforeEach(func() { description = "Silmarillion Suite" suiteLabels = Labels{"SuiteLabel", "TopLevelLabel"} conf = types.SuiteConfig{} }) Context("when there are specs with nodes marked pending", func() { BeforeEach(func() { specs = Specs{ S(N(), N()), S(N(), N()), S(N(), N(Pending)), S(N(), N()), S(N(Pending)), } }) It("skips those specs", func() { specs, hasProgrammaticFocus := internal.ApplyFocusToSpecs(specs, description, suiteLabels, conf) Ω(harvestSkips(specs)).Should(Equal([]bool{false, false, true, false, true})) Ω(hasProgrammaticFocus).Should(BeFalse()) }) }) Context("when there are specs with nodes marked focused", func() { BeforeEach(func() { specs = Specs{ S(N(), N()), S(N(), N()), S(N(), N(Focus)), S(N()), S(N(Focus)), } }) It("skips any other specs and notes that it has programmatic focus", func() { specs, hasProgrammaticFocus := internal.ApplyFocusToSpecs(specs, description, suiteLabels, conf) Ω(harvestSkips(specs)).Should(Equal([]bool{true, true, false, true, false})) Ω(hasProgrammaticFocus).Should(BeTrue()) }) Context("when the specs with nodes marked focused also have nodes marked pending ", func() { BeforeEach(func() { specs = Specs{ S(N(), N()), S(N(), N()), S(N(Pending), N(Focus)), S(N()), } }) It("does not skip any other specs and notes that it does not have programmatic focus", func() { specs, hasProgrammaticFocus := internal.ApplyFocusToSpecs(specs, description, suiteLabels, conf) Ω(harvestSkips(specs)).Should(Equal([]bool{false, false, true, false})) Ω(hasProgrammaticFocus).Should(BeFalse()) }) }) }) Context("when there are focus strings and/or skip strings configured", func() { BeforeEach(func() { specs = Specs{ S(N("blue"), N("dragon")), S(N("blue"), N("Dragon")), S(N("red dragon"), N()), S(N("green dragon"), N()), S(N(Pending), N("blue Dragon")), S(N("yellow dragon")), S(N("yellow dragon")), } }) Context("when there are focus strings configured", func() { BeforeEach(func() { conf.FocusStrings = []string{"blue [dD]ra", "(red|green) dragon"} }) It("overrides any programmatic focus, runs only specs that match the focus string, and continues to skip specs with nodes marked pending", func() { specs, hasProgrammaticFocus := internal.ApplyFocusToSpecs(specs, description, suiteLabels, conf) Ω(harvestSkips(specs)).Should(Equal([]bool{false, false, false, false, true, true, true})) Ω(hasProgrammaticFocus).Should(BeFalse()) }) It("includes the description string in the search", func() { conf.FocusStrings = []string{"Silmaril"} specs, hasProgrammaticFocus := internal.ApplyFocusToSpecs(specs, description, suiteLabels, conf) Ω(harvestSkips(specs)).Should(Equal([]bool{false, false, false, false, true, false, false})) Ω(hasProgrammaticFocus).Should(BeFalse()) }) }) Context("when there are skip strings configured", func() { BeforeEach(func() { conf.SkipStrings = []string{"blue [dD]ragon", "red dragon"} }) It("overrides any programmatic focus, and runs specs that don't match the skip strings, and continues to skip specs with nodes marked pending", func() { specs, hasProgrammaticFocus := internal.ApplyFocusToSpecs(specs, description, suiteLabels, conf) Ω(harvestSkips(specs)).Should(Equal([]bool{true, true, true, false, true, false, false})) Ω(hasProgrammaticFocus).Should(BeFalse()) }) It("includes the description string in the search", func() { conf.SkipStrings = []string{"Silmaril"} specs, hasProgrammaticFocus := internal.ApplyFocusToSpecs(specs, description, suiteLabels, conf) Ω(harvestSkips(specs)).Should(Equal([]bool{true, true, true, true, true, true, true})) Ω(hasProgrammaticFocus).Should(BeFalse()) }) }) Context("when skip and focus are configured", func() { BeforeEach(func() { conf.FocusStrings = []string{"blue [dD]ragon", "(red|green) dragon"} conf.SkipStrings = []string{"red dragon", "Dragon"} }) It("ORs both together", func() { specs, hasProgrammaticFocus := internal.ApplyFocusToSpecs(specs, description, suiteLabels, conf) Ω(harvestSkips(specs)).Should(Equal([]bool{false, true, true, false, true, true, true})) Ω(hasProgrammaticFocus).Should(BeFalse()) }) }) }) Context("when configured to focus/skip files", func() { BeforeEach(func() { specs = Specs{ S(N(CL("file_a", 1))), //include because "file_:1" is in FocusFiles S(N(CL("file_b", 3, "file_b", 15))), //include because "file_:15-21" is in FocusFiles S(N(CL("file_b", 17))), //skip because "_b:17" is in SkipFiles S(N(CL("file_b", 20), Pending)), //skip because spec is flagged pending S(N(CL("c", 3))), //skip because "c" is not in FocusFiles S(N(CL("d", 17))), //include because "d " is in FocusFiles } conf.FocusFiles = []string{"file_:1,15-21", "d"} conf.SkipFiles = []string{"_b:17"} }) It("applies a file-based focus and skip filter", func() { specs, hasProgrammaticFocus := internal.ApplyFocusToSpecs(specs, description, suiteLabels, conf) Ω(harvestSkips(specs)).Should(Equal([]bool{false, false, true, true, true, false})) Ω(hasProgrammaticFocus).Should(BeFalse()) }) }) Context("when configured with a label filter", func() { BeforeEach(func() { conf.LabelFilter = "(cat || cow) && !fish" specs = Specs{ S(N(ntCon, Label("cat", "dog")), N(ntIt, "A", Label("fish"))), //skip because fish S(N(ntCon, Label("cat", "dog")), N(ntIt, "B", Label("apple"))), //include because has cat and not fish S(N(ntCon, Label("dog")), N(ntIt, "C", Label("apple"))), //skip because no cat or cow S(N(ntCon, Label("cow")), N(ntIt, "D", Label("fish"))), //skip because fish S(N(ntCon, Label("cow")), N(ntIt, "E")), //include because cow and no fish S(N(ntCon, Label("cow")), N(ntIt, "F", Pending)), //skip because pending } }) It("applies the label filters", func() { specs, hasProgrammaticFocus := internal.ApplyFocusToSpecs(specs, description, suiteLabels, conf) Ω(harvestSkips(specs)).Should(Equal([]bool{true, false, true, true, false, true})) Ω(hasProgrammaticFocus).Should(BeFalse()) }) }) Context("when configured with a label set filter", func() { BeforeEach(func() { conf.LabelFilter = "Feature: consistsOf {A, B} || Feature: containsAny C" specs = Specs{ S(N(ntCon, Label("Feature:A", "dog")), N(ntIt, "A", Label("fish"))), //skip because fish no feature:B S(N(ntCon, Label("Feature:A", "dog")), N(ntIt, "B", Label("apple", "Feature:B"))), //include because has Feature:A and Feature:B S(N(ntCon, Label("Feature:A")), N(ntIt, "C", Label("Feature:B", "Feature:D"))), //skip because it has Feature:D S(N(ntCon, Label("Feature:C")), N(ntIt, "D", Label("fish", "Feature:D"))), //include because it has Feature:C S(N(ntCon, Label("cow")), N(ntIt, "E")), //skip because no Feature: S(N(ntCon, Label("Feature:A", "Feature:B")), N(ntIt, "F", Pending)), //skip because pending } }) It("applies the label filters", func() { specs, hasProgrammaticFocus := internal.ApplyFocusToSpecs(specs, description, suiteLabels, conf) Ω(harvestSkips(specs)).Should(Equal([]bool{true, false, true, false, true, true})) Ω(hasProgrammaticFocus).Should(BeFalse()) }) }) Context("when configured with a label filter that filters on the suite level label", func() { BeforeEach(func() { conf.LabelFilter = "cat && TopLevelLabel" specs = Specs{ S(N(ntCon, Label("cat", "dog")), N(ntIt, "A", Label("fish"))), //include because cat and suite has TopLevelLabel S(N(ntCon, Label("dog")), N(ntIt, "B", Label("apple"))), //skip because no cat } }) It("honors the suite level label", func() { specs, hasProgrammaticFocus := internal.ApplyFocusToSpecs(specs, description, suiteLabels, conf) Ω(harvestSkips(specs)).Should(Equal([]bool{false, true})) Ω(hasProgrammaticFocus).Should(BeFalse()) }) }) Context("when configured with focus/skip files, focus/skip strings, and label filters", func() { BeforeEach(func() { specs = Specs{ S(N("dog", CL("file_a", 1), Label("brown"))), //include because "file_:1" is in FocusFiles and "dog" is in FocusStrings and has "brown" label S(N("dog", CL("file_a", 1), Label("white"))), //skip because does not have "brown" label S(N("dog cat", CL("file_b", 3, "file_b", 15), Label("brown"))), //skip because "file_:15-21" is in FocusFiles but "cat" is in SkipStirngs S(N("fish", CL("file_b", 17), Label("brown"))), //skip because "_b:17" is in SkipFiles, even though "fish" is in FocusStrings S(N("biscuit", CL("file_b", 20), Pending, Label("brown"))), //skip because spec is flagged pending S(N("pony", CL("c", 3), Label("brown"))), //skip because "c" is not in FocusFiles or FocusStrings S(N("goat", CL("d", 17), Label("brown"))), //skip because "goat" is in FocusStrings but "d" is not in FocusFiles } conf.FocusFiles = []string{"file_:1,15-21"} conf.SkipFiles = []string{"_b:17"} conf.FocusStrings = []string{"goat", "dog", "fish", "biscuit"} conf.SkipStrings = []string{"cat"} conf.LabelFilter = "brown" }) It("applies all filters", func() { specs, hasProgrammaticFocus := internal.ApplyFocusToSpecs(specs, description, suiteLabels, conf) Ω(harvestSkips(specs)).Should(Equal([]bool{false, true, true, true, true, true, true})) Ω(hasProgrammaticFocus).Should(BeFalse()) }) }) Context("when configured with focus/skip files, focus/skip strings, and label filters and there is a programmatic focus", func() { BeforeEach(func() { specs = Specs{ S(N("dog", CL("file_a", 1), Label("brown"))), //skip because "file_:1" is in FocusFiles and "dog" is in FocusStrings and has "brown" label but a different spec has a programmatic focus S(N("dog", CL("file_a", 17), Label("brown"), Focus)), //include because "file_:15-21" is in FocusFiles and "dog" is in FocusStrings and has "brown" label S(N("dog", CL("file_a", 1), Label("white"), Focus)), //skip because does not have "brown" label S(N("dog cat", CL("file_b", 3, "file_b", 15), Label("brown"))), //skip because "file_:15-21" is in FocusFiles but "cat" is in SkipStirngs S(N("fish", CL("file_b", 17), Label("brown"))), //skip because "_b:17" is in SkipFiles, even though "fish" is in FocusStrings S(N("biscuit", CL("file_b", 20), Pending, Label("brown"))), //skip because spec is flagged pending S(N("pony", CL("c", 3), Label("brown"))), //skip because "c" is not in FocusFiles or FocusStrings S(N("goat", CL("d", 17), Label("brown"))), //skip because "goat" is in FocusStrings but "d" is not in FocusFiles } conf.FocusFiles = []string{"file_:1,15-21"} conf.SkipFiles = []string{"_b:17"} conf.FocusStrings = []string{"goat", "dog", "fish", "biscuit"} conf.SkipStrings = []string{"cat"} conf.LabelFilter = "brown" }) It("applies all filters", func() { specs, hasProgrammaticFocus := internal.ApplyFocusToSpecs(specs, description, suiteLabels, conf) Ω(harvestSkips(specs)).Should(Equal([]bool{true, false, true, true, true, true, true, true})) Ω(hasProgrammaticFocus).Should(BeTrue()) }) }) }) }) golang-github-onsi-ginkgo-v2-2.22.0/internal/global/000077500000000000000000000000001472321612100221465ustar00rootroot00000000000000golang-github-onsi-ginkgo-v2-2.22.0/internal/global/init.go000066400000000000000000000006331472321612100234420ustar00rootroot00000000000000package global import ( "github.com/onsi/ginkgo/v2/internal" ) var Suite *internal.Suite var Failer *internal.Failer var backupSuite *internal.Suite func init() { InitializeGlobals() } func InitializeGlobals() { Failer = internal.NewFailer() Suite = internal.NewSuite() } func PushClone() error { var err error backupSuite, err = Suite.Clone() return err } func PopClone() { Suite = backupSuite } golang-github-onsi-ginkgo-v2-2.22.0/internal/group.go000066400000000000000000000352731472321612100224030ustar00rootroot00000000000000package internal import ( "fmt" "time" "github.com/onsi/ginkgo/v2/types" ) type runOncePair struct { //nodeId should only run once... nodeID uint nodeType types.NodeType //...for specs in a hierarchy that includes this context containerID uint } func (pair runOncePair) isZero() bool { return pair.nodeID == 0 } func runOncePairForNode(node Node, containerID uint) runOncePair { return runOncePair{ nodeID: node.ID, nodeType: node.NodeType, containerID: containerID, } } type runOncePairs []runOncePair func runOncePairsForSpec(spec Spec) runOncePairs { pairs := runOncePairs{} containers := spec.Nodes.WithType(types.NodeTypeContainer) for _, node := range spec.Nodes { if node.NodeType.Is(types.NodeTypeBeforeAll | types.NodeTypeAfterAll) { pairs = append(pairs, runOncePairForNode(node, containers.FirstWithNestingLevel(node.NestingLevel-1).ID)) } else if node.NodeType.Is(types.NodeTypeBeforeEach|types.NodeTypeJustBeforeEach|types.NodeTypeAfterEach|types.NodeTypeJustAfterEach) && node.MarkedOncePerOrdered { passedIntoAnOrderedContainer := false firstOrderedContainerDeeperThanNode := containers.FirstSatisfying(func(container Node) bool { passedIntoAnOrderedContainer = passedIntoAnOrderedContainer || container.MarkedOrdered return container.NestingLevel >= node.NestingLevel && passedIntoAnOrderedContainer }) if firstOrderedContainerDeeperThanNode.IsZero() { continue } pairs = append(pairs, runOncePairForNode(node, firstOrderedContainerDeeperThanNode.ID)) } } return pairs } func (pairs runOncePairs) runOncePairFor(nodeID uint) runOncePair { for i := range pairs { if pairs[i].nodeID == nodeID { return pairs[i] } } return runOncePair{} } func (pairs runOncePairs) hasRunOncePair(pair runOncePair) bool { for i := range pairs { if pairs[i] == pair { return true } } return false } func (pairs runOncePairs) withType(nodeTypes types.NodeType) runOncePairs { count := 0 for i := range pairs { if pairs[i].nodeType.Is(nodeTypes) { count++ } } out, j := make(runOncePairs, count), 0 for i := range pairs { if pairs[i].nodeType.Is(nodeTypes) { out[j] = pairs[i] j++ } } return out } type group struct { suite *Suite specs Specs runOncePairs map[uint]runOncePairs runOnceTracker map[runOncePair]types.SpecState succeeded bool failedInARunOnceBefore bool continueOnFailure bool } func newGroup(suite *Suite) *group { return &group{ suite: suite, runOncePairs: map[uint]runOncePairs{}, runOnceTracker: map[runOncePair]types.SpecState{}, succeeded: true, failedInARunOnceBefore: false, continueOnFailure: false, } } func (g *group) initialReportForSpec(spec Spec) types.SpecReport { return types.SpecReport{ ContainerHierarchyTexts: spec.Nodes.WithType(types.NodeTypeContainer).Texts(), ContainerHierarchyLocations: spec.Nodes.WithType(types.NodeTypeContainer).CodeLocations(), ContainerHierarchyLabels: spec.Nodes.WithType(types.NodeTypeContainer).Labels(), LeafNodeLocation: spec.FirstNodeWithType(types.NodeTypeIt).CodeLocation, LeafNodeType: types.NodeTypeIt, LeafNodeText: spec.FirstNodeWithType(types.NodeTypeIt).Text, LeafNodeLabels: []string(spec.FirstNodeWithType(types.NodeTypeIt).Labels), ParallelProcess: g.suite.config.ParallelProcess, RunningInParallel: g.suite.isRunningInParallel(), IsSerial: spec.Nodes.HasNodeMarkedSerial(), IsInOrderedContainer: !spec.Nodes.FirstNodeMarkedOrdered().IsZero(), MaxFlakeAttempts: spec.Nodes.GetMaxFlakeAttempts(), MaxMustPassRepeatedly: spec.Nodes.GetMaxMustPassRepeatedly(), } } func (g *group) evaluateSkipStatus(spec Spec) (types.SpecState, types.Failure) { if spec.Nodes.HasNodeMarkedPending() { return types.SpecStatePending, types.Failure{} } if spec.Skip { return types.SpecStateSkipped, types.Failure{} } if g.suite.interruptHandler.Status().Interrupted() || g.suite.skipAll { return types.SpecStateSkipped, types.Failure{} } if !g.suite.deadline.IsZero() && g.suite.deadline.Before(time.Now()) { return types.SpecStateSkipped, types.Failure{} } if !g.succeeded && !g.continueOnFailure { return types.SpecStateSkipped, g.suite.failureForLeafNodeWithMessage(spec.FirstNodeWithType(types.NodeTypeIt), "Spec skipped because an earlier spec in an ordered container failed") } if g.failedInARunOnceBefore && g.continueOnFailure { return types.SpecStateSkipped, g.suite.failureForLeafNodeWithMessage(spec.FirstNodeWithType(types.NodeTypeIt), "Spec skipped because a BeforeAll node failed") } beforeOncePairs := g.runOncePairs[spec.SubjectID()].withType(types.NodeTypeBeforeAll | types.NodeTypeBeforeEach | types.NodeTypeJustBeforeEach) for _, pair := range beforeOncePairs { if g.runOnceTracker[pair].Is(types.SpecStateSkipped) { return types.SpecStateSkipped, g.suite.failureForLeafNodeWithMessage(spec.FirstNodeWithType(types.NodeTypeIt), fmt.Sprintf("Spec skipped because Skip() was called in %s", pair.nodeType)) } } if g.suite.config.DryRun { return types.SpecStatePassed, types.Failure{} } return g.suite.currentSpecReport.State, g.suite.currentSpecReport.Failure } func (g *group) isLastSpecWithPair(specID uint, pair runOncePair) bool { lastSpecID := uint(0) for idx := range g.specs { if g.specs[idx].Skip { continue } sID := g.specs[idx].SubjectID() if g.runOncePairs[sID].hasRunOncePair(pair) { lastSpecID = sID } } return lastSpecID == specID } func (g *group) attemptSpec(isFinalAttempt bool, spec Spec) bool { failedInARunOnceBefore := false pairs := g.runOncePairs[spec.SubjectID()] nodes := spec.Nodes.WithType(types.NodeTypeBeforeAll) nodes = append(nodes, spec.Nodes.WithType(types.NodeTypeBeforeEach)...).SortedByAscendingNestingLevel() nodes = append(nodes, spec.Nodes.WithType(types.NodeTypeJustBeforeEach).SortedByAscendingNestingLevel()...) nodes = append(nodes, spec.Nodes.FirstNodeWithType(types.NodeTypeIt)) terminatingNode, terminatingPair := Node{}, runOncePair{} deadline := time.Time{} if spec.SpecTimeout() > 0 { deadline = time.Now().Add(spec.SpecTimeout()) } for _, node := range nodes { oncePair := pairs.runOncePairFor(node.ID) if !oncePair.isZero() && g.runOnceTracker[oncePair].Is(types.SpecStatePassed) { continue } g.suite.currentSpecReport.State, g.suite.currentSpecReport.Failure = g.suite.runNode(node, deadline, spec.Nodes.BestTextFor(node)) g.suite.currentSpecReport.RunTime = time.Since(g.suite.currentSpecReport.StartTime) if !oncePair.isZero() { g.runOnceTracker[oncePair] = g.suite.currentSpecReport.State } if g.suite.currentSpecReport.State != types.SpecStatePassed { terminatingNode, terminatingPair = node, oncePair failedInARunOnceBefore = !terminatingPair.isZero() break } } afterNodeWasRun := map[uint]bool{} includeDeferCleanups := false for { nodes := spec.Nodes.WithType(types.NodeTypeAfterEach) nodes = append(nodes, spec.Nodes.WithType(types.NodeTypeAfterAll)...).SortedByDescendingNestingLevel() nodes = append(spec.Nodes.WithType(types.NodeTypeJustAfterEach).SortedByDescendingNestingLevel(), nodes...) if !terminatingNode.IsZero() { nodes = nodes.WithinNestingLevel(terminatingNode.NestingLevel) } if includeDeferCleanups { nodes = append(nodes, g.suite.cleanupNodes.WithType(types.NodeTypeCleanupAfterEach).Reverse()...) nodes = append(nodes, g.suite.cleanupNodes.WithType(types.NodeTypeCleanupAfterAll).Reverse()...) } nodes = nodes.Filter(func(node Node) bool { if afterNodeWasRun[node.ID] { //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) switch g.suite.currentSpecReport.State { case types.SpecStatePassed: //this attempt is passing... return isLastSpecWithPair //...we should run-once if we'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 isFinalAttempt { 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 } } 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 }) if len(nodes) == 0 && includeDeferCleanups { break } for _, node := range nodes { afterNodeWasRun[node.ID] = true state, failure := g.suite.runNode(node, deadline, spec.Nodes.BestTextFor(node)) g.suite.currentSpecReport.RunTime = time.Since(g.suite.currentSpecReport.StartTime) if g.suite.currentSpecReport.State == types.SpecStatePassed || state == types.SpecStateAborted { g.suite.currentSpecReport.State = state g.suite.currentSpecReport.Failure = failure } else if state.Is(types.SpecStateFailureStates) { g.suite.currentSpecReport.AdditionalFailures = append(g.suite.currentSpecReport.AdditionalFailures, types.AdditionalFailure{State: state, Failure: failure}) } } includeDeferCleanups = true } return failedInARunOnceBefore } func (g *group) run(specs Specs) { g.specs = specs g.continueOnFailure = specs[0].Nodes.FirstNodeMarkedOrdered().MarkedContinueOnFailure for _, spec := range g.specs { g.runOncePairs[spec.SubjectID()] = runOncePairsForSpec(spec) } 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) skip := g.suite.config.DryRun || g.suite.currentSpecReport.State.Is(types.SpecStateFailureStates|types.SpecStateSkipped|types.SpecStatePending) g.suite.currentSpecReport.StartTime = time.Now() failedInARunOnceBefore := false if !skip { var maxAttempts = 1 if g.suite.config.MustPassRepeatedly > 0 { maxAttempts = g.suite.config.MustPassRepeatedly g.suite.currentSpecReport.MaxMustPassRepeatedly = maxAttempts } else if g.suite.currentSpecReport.MaxMustPassRepeatedly > 0 { maxAttempts = max(1, spec.MustPassRepeatedly()) } else if g.suite.config.FlakeAttempts > 0 { maxAttempts = g.suite.config.FlakeAttempts g.suite.currentSpecReport.MaxFlakeAttempts = maxAttempts } else if g.suite.currentSpecReport.MaxFlakeAttempts > 0 { maxAttempts = max(1, spec.FlakeAttempts()) } for attempt := 0; attempt < maxAttempts; attempt++ { g.suite.currentSpecReport.NumAttempts = attempt + 1 g.suite.writer.Truncate() g.suite.outputInterceptor.StartInterceptingOutput() if attempt > 0 { if g.suite.currentSpecReport.MaxMustPassRepeatedly > 0 { g.suite.handleSpecEvent(types.SpecEvent{SpecEventType: types.SpecEventSpecRepeat, Attempt: attempt}) } if g.suite.currentSpecReport.MaxFlakeAttempts > 0 { g.suite.handleSpecEvent(types.SpecEvent{SpecEventType: types.SpecEventSpecRetry, Attempt: attempt}) } } failedInARunOnceBefore = g.attemptSpec(attempt == maxAttempts-1, spec) g.suite.currentSpecReport.EndTime = time.Now() g.suite.currentSpecReport.RunTime = g.suite.currentSpecReport.EndTime.Sub(g.suite.currentSpecReport.StartTime) g.suite.currentSpecReport.CapturedGinkgoWriterOutput += string(g.suite.writer.Bytes()) g.suite.currentSpecReport.CapturedStdOutErr += g.suite.outputInterceptor.StopInterceptingAndReturnOutput() if g.suite.currentSpecReport.MaxMustPassRepeatedly > 0 { if g.suite.currentSpecReport.State.Is(types.SpecStateFailureStates | types.SpecStateSkipped) { break } } if g.suite.currentSpecReport.MaxFlakeAttempts > 0 { if g.suite.currentSpecReport.State.Is(types.SpecStatePassed | types.SpecStateSkipped | types.SpecStateAborted | types.SpecStateInterrupted) { break } else if attempt < maxAttempts-1 { af := types.AdditionalFailure{State: g.suite.currentSpecReport.State, Failure: g.suite.currentSpecReport.Failure} af.Failure.Message = fmt.Sprintf("Failure recorded during attempt %d:\n%s", attempt+1, af.Failure.Message) g.suite.currentSpecReport.AdditionalFailures = append(g.suite.currentSpecReport.AdditionalFailures, af) } } } } g.suite.reportEach(spec, types.NodeTypeReportAfterEach) g.suite.processCurrentSpecReport() if g.suite.currentSpecReport.State.Is(types.SpecStateFailureStates) { g.succeeded = false g.failedInARunOnceBefore = g.failedInARunOnceBefore || failedInARunOnceBefore } g.suite.selectiveLock.Lock() g.suite.currentSpecReport = types.SpecReport{} g.suite.selectiveLock.Unlock() } } golang-github-onsi-ginkgo-v2-2.22.0/internal/internal_integration/000077500000000000000000000000001472321612100251255ustar00rootroot00000000000000golang-github-onsi-ginkgo-v2-2.22.0/internal/internal_integration/abort_test.go000066400000000000000000000170261472321612100276300ustar00rootroot00000000000000package internal_integration_test import ( "time" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" "github.com/onsi/ginkgo/v2/internal/interrupt_handler" . "github.com/onsi/ginkgo/v2/internal/test_helpers" "github.com/onsi/ginkgo/v2/types" ) var _ = Describe("handling test aborts", func() { Describe("when BeforeSuite aborts", func() { BeforeEach(func() { success, _ := RunFixture("abort beforesuite", func() { BeforeSuite(rt.T("before-suite", func() { writer.Write([]byte("before-suite")) Abort("abort", cl) })) It("A", rt.T("A")) It("B", rt.T("B")) AfterSuite(rt.T("after-suite")) }) Ω(success).Should(BeFalse()) }) It("reports a suite failure", func() { Ω(reporter.End).Should(BeASuiteSummary(false, NSpecs(2), NSkipped(0))) }) It("reports a failure for the BeforeSuite", func() { Ω(reporter.Did.FindByLeafNodeType(types.NodeTypeBeforeSuite)).Should(HaveAborted("abort", cl, CapturedGinkgoWriterOutput("before-suite"))) Ω(reporter.Did.FindByLeafNodeType(types.NodeTypeAfterSuite)).Should(HavePassed()) }) It("does not run any of the Its", func() { Ω(rt).ShouldNot(HaveRun("A")) Ω(rt).ShouldNot(HaveRun("B")) }) It("does run the AfterSuite", func() { Ω(rt).Should(HaveTracked("before-suite", "after-suite")) }) }) Describe("when AfterSuite aborts", func() { BeforeEach(func() { success, _ := RunFixture("abort aftersuite", func() { BeforeSuite(rt.T("before-suite")) Describe("top-level", func() { It("A", rt.T("A")) It("B", rt.T("B")) }) AfterSuite(rt.T("after-suite", func() { writer.Write([]byte("after-suite")) Abort("abort", cl) })) }) Ω(success).Should(BeFalse()) }) It("reports a suite failure", func() { Ω(reporter.End).Should(BeASuiteSummary(false, NSpecs(2), NPassed(2))) }) It("runs and reports on all the tests and reports a failure for the AfterSuite", func() { Ω(rt).Should(HaveTracked("before-suite", "A", "B", "after-suite")) Ω(reporter.Did.FindByLeafNodeType(types.NodeTypeBeforeSuite)).Should(HavePassed()) Ω(reporter.Did.Find("A")).Should(HavePassed()) Ω(reporter.Did.Find("B")).Should(HavePassed()) Ω(reporter.Did.FindByLeafNodeType(types.NodeTypeAfterSuite)).Should(HaveAborted("abort", cl, CapturedGinkgoWriterOutput("after-suite"))) }) }) Describe("individual test aborts", func() { Describe("when an It aborts", func() { BeforeEach(func() { success, _ := RunFixture("failed it", func() { BeforeSuite(rt.T("before-suite")) Describe("top-level", func() { It("A", rt.T("A", func() { writer.Write([]byte("running A")) })) It("B", rt.T("B", func() { writer.Write([]byte("running B")) Abort("abort", cl) })) It("C", rt.T("C")) It("D", rt.T("D")) }) AfterEach(rt.T("after-each")) AfterSuite(rt.T("after-suite")) }) Ω(success).Should(BeFalse()) }) It("reports a suite failure", func() { Ω(reporter.End).Should(BeASuiteSummary(false, NSpecs(4), NPassed(1), NFailed(1), NSkipped(2))) }) It("does not run subsequent Its, the AfterEach, and the AfterSuite", func() { Ω(rt).Should(HaveTracked("before-suite", "A", "after-each", "B", "after-each", "after-suite")) }) It("reports the It's abort and subsequent tests as skipped", func() { Ω(reporter.Did.Find("A")).Should(HavePassed(CapturedGinkgoWriterOutput("running A"))) Ω(reporter.Did.Find("B")).Should(HaveAborted("abort", cl, CapturedGinkgoWriterOutput("running B"))) Ω(reporter.Did.Find("C")).Should(HaveBeenSkipped()) Ω(reporter.Did.Find("D")).Should(HaveBeenSkipped()) }) It("sets up the failure node location correctly", func() { report := reporter.Did.Find("B") Ω(report.Failure.FailureNodeContext).Should(Equal(types.FailureNodeIsLeafNode)) Ω(report.Failure.FailureNodeType).Should(Equal(types.NodeTypeIt)) Ω(report.Failure.FailureNodeLocation).Should(Equal(report.LeafNodeLocation)) }) }) }) Describe("when a test fails then an AfterEach aborts", func() { BeforeEach(func() { success, _ := RunFixture("failed it then after-each aborts", func() { BeforeSuite(rt.T("before-suite")) Describe("top-level", func() { It("A", rt.T("A")) It("B", rt.T("B", func() { writer.Write([]byte("running B")) F("fail") })) It("C", rt.T("C")) It("D", rt.T("D")) }) ReportAfterEach(func(report SpecReport) { rt.Run("report-after-each") if report.State.Is(types.SpecStateFailed) { Abort("abort", cl) } }) AfterSuite(rt.T("after-suite")) }) Ω(success).Should(BeFalse()) }) It("reports a suite failure", func() { Ω(reporter.End).Should(BeASuiteSummary(false, NSpecs(4), NPassed(1), NFailed(1), NSkipped(2))) }) It("does not run subsequent Its, the AfterEach, and the AfterSuite", func() { Ω(rt).Should(HaveTracked("before-suite", "A", "report-after-each", "B", "report-after-each", "report-after-each", "report-after-each", "after-suite")) }) It("reports a failure and then aborts the rest of the suite", func() { Ω(reporter.Did.Find("A")).Should(HavePassed()) Ω(reporter.Did.Find("B")).Should(HaveAborted("abort", cl, CapturedGinkgoWriterOutput("running B"))) Ω(reporter.Did.Find("C")).Should(HaveBeenSkipped()) Ω(reporter.Did.Find("D")).Should(HaveBeenSkipped()) }) }) Describe("when running in parallel and a test aborts", func() { var c chan interface{} BeforeEach(func() { SetUpForParallel(2) c = make(chan interface{}) }) It("notifies the server of the abort", func() { Ω(client.ShouldAbort()).Should(BeFalse()) success := RunFixtureInParallel("aborting in parallel", func(_ int) { It("A", func() { <-c Abort("abort") }) It("B", func(ctx SpecContext) { close(c) select { case <-ctx.Done(): rt.Run("dc-done") case <-time.After(interrupt_handler.ABORT_POLLING_INTERVAL * 2): rt.Run("dc-after") } }) }) Ω(success).Should(BeFalse()) Ω(client.ShouldAbort()).Should(BeTrue()) Ω(rt).Should(HaveTracked("dc-done")) //not dc-after Ω(reporter.Did.Find("A")).Should(HaveAborted("abort")) Ω(reporter.Did.Find("B")).Should(HaveBeenInterrupted(interrupt_handler.InterruptCauseAbortByOtherProcess)) }) It("does not interrupt cleanup nodes", func() { success := RunFixtureInParallel("aborting in parallel", func(_ int) { It("A", func() { <-c Abort("abort") }) Context("B", func() { It("B", func() { }) AfterEach(func(ctx SpecContext) { close(c) select { case <-ctx.Done(): rt.Run("dc-done") case <-time.After(interrupt_handler.ABORT_POLLING_INTERVAL * 2): rt.Run("dc-after") } }) }) }) Ω(success).Should(BeFalse()) Ω(rt).Should(HaveTracked("dc-after")) //not dc-done Ω(reporter.Did.Find("A")).Should(HaveAborted("abort")) Ω(reporter.Did.Find("B")).Should(HavePassed()) }) It("does not start serial nodes if an abort occurs", func() { success := RunFixtureInParallel("aborting in parallel", func(proc int) { It("A", func() { time.Sleep(time.Millisecond * 50) if proc == 2 { rt.Run("aborting") Abort("abort") } }) It("B", func() { time.Sleep(time.Millisecond * 50) if proc == 2 { rt.Run("aborting") Abort("abort") } }) It("C", Serial, func() { rt.Run("C") }) }) Ω(success).Should(BeFalse()) Ω(rt).Should(HaveTracked("aborting")) //just one aborting and we don't see C }, MustPassRepeatedly(10)) }) }) golang-github-onsi-ginkgo-v2-2.22.0/internal/internal_integration/cleanup_test.go000066400000000000000000000224211472321612100301430ustar00rootroot00000000000000package internal_integration_test import ( "fmt" "sync" "time" . "github.com/onsi/ginkgo/v2" "github.com/onsi/ginkgo/v2/internal/interrupt_handler" . "github.com/onsi/ginkgo/v2/internal/test_helpers" "github.com/onsi/ginkgo/v2/types" . "github.com/onsi/gomega" ) var _ = Describe("Cleanup", func() { C := func(label string) func() { return func() { DeferCleanup(rt.Run, label) } } Context("the happy path", func() { BeforeEach(func() { success, _ := RunFixture("cleanup happy path", func() { BeforeSuite(rt.T("BS", C("C-BS"))) AfterSuite(rt.T("AS", C("C-AS"))) BeforeEach(rt.T("BE-outer", C("C-BE-outer"))) AfterEach(rt.T("AE-outer", C("C-AE-outer"))) Context("non-randomizing container", func() { Context("container", Ordered, func() { JustBeforeEach(rt.T("JBE", C("C-JBE"))) It("A", rt.T("A", C("C-A"))) JustAfterEach(rt.T("JAE", C("C-JAE"))) }) Context("ordered container", Ordered, func() { BeforeAll(rt.T("BA", C("C-BA"))) BeforeEach(rt.T("BE-inner", C("C-BE-inner"))) It("B", rt.T("B", C("C-B"))) It("C", rt.T("C", C("C-C"))) It("D", rt.T("D", C("C-D"))) AfterEach(rt.T("AE-inner", C("C-AE-inner"))) AfterAll(rt.T("AA", C("C-AA"))) }) }) }) Ω(success).Should(BeTrue()) }) It("runs all the things in the correct order", func() { Ω(rt).Should(HaveTracked( //before suite "BS", //container "BE-outer", "JBE", "A", "JAE", "AE-outer", "C-AE-outer", "C-JAE", "C-A", "C-JBE", "C-BE-outer", //ordered container "BE-outer", "BA", "BE-inner", "B", "AE-inner", "AE-outer", "C-AE-outer", "C-AE-inner", "C-B", "C-BE-inner", "C-BE-outer", "BE-outer", "BE-inner", "C", "AE-inner", "AE-outer", "C-AE-outer", "C-AE-inner", "C-C", "C-BE-inner", "C-BE-outer", "BE-outer", "BE-inner", "D", "AE-inner", "AA", "AE-outer", "C-AE-outer", "C-AE-inner", "C-D", "C-BE-inner", "C-BE-outer", "C-AA", "C-BA", //after suite "AS", "C-AS", "C-BS", )) }) }) Context("when cleanup fails", func() { Context("because of a failed assertion", func() { BeforeEach(func() { success, _ := RunFixture("cleanup failure", func() { BeforeEach(rt.T("BE", func() { DeferCleanup(func() { rt.Run("C-BE") F("fail") }) })) It("A", rt.T("A", C("C-A"))) }) Ω(success).Should(BeFalse()) }) It("reports a failure", func() { Ω(rt).Should(HaveTracked("BE", "A", "C-A", "C-BE")) Ω(reporter.Did.Find("A")).Should(HaveFailed("fail", FailureNodeType(types.NodeTypeCleanupAfterEach), types.FailureNodeAtTopLevel)) }) }) Context("because of a returned error", func() { BeforeEach(func() { success, _ := RunFixture("cleanup failure", func() { BeforeEach(rt.T("BE", C("C-BE"))) It("A", rt.T("A", func() { DeferCleanup(func() error { rt.Run("C-A") return fmt.Errorf("fail") }) })) }) Ω(success).Should(BeFalse()) }) It("reports a failure", func() { Ω(rt).Should(HaveTracked("BE", "A", "C-A", "C-BE")) Ω(reporter.Did.Find("A")).Should(HaveFailed("DeferCleanup callback returned error: fail", FailureNodeType(types.NodeTypeCleanupAfterEach), types.FailureNodeAtTopLevel)) }) }) Context("because of a returned error, for a multi-return function", func() { BeforeEach(func() { success, _ := RunFixture("cleanup failure", func() { BeforeEach(rt.T("BE", C("C-BE"))) It("A", rt.T("A", func() { DeferCleanup(func() (string, error) { rt.Run("C-A") return "ok", fmt.Errorf("fail") }) })) }) Ω(success).Should(BeFalse()) }) It("reports a failure", func() { Ω(rt).Should(HaveTracked("BE", "A", "C-A", "C-BE")) Ω(reporter.Did.Find("A")).Should(HaveFailed("DeferCleanup callback returned error: fail", FailureNodeType(types.NodeTypeCleanupAfterEach), types.FailureNodeAtTopLevel)) }) }) Context("at the suite level", func() { BeforeEach(func() { success, _ := RunFixture("cleanup failure", func() { BeforeSuite(rt.T("BS", func() { DeferCleanup(func() { rt.Run("C-BS") F("fail") }) })) Context("container", func() { It("A", rt.T("A")) It("B", rt.T("B")) }) }) Ω(success).Should(BeFalse()) }) It("marks the suite as failed", func() { Ω(rt).Should(HaveTracked("BS", "A", "B", "C-BS")) Ω(reporter.End).Should(BeASuiteSummary(false, NSpecs(2), NPassed(2))) Ω(reporter.Did.FindByLeafNodeType(types.NodeTypeBeforeSuite)).Should(HavePassed()) Ω(reporter.Did.FindByLeafNodeType(types.NodeTypeCleanupAfterSuite)).Should(HaveFailed("fail", FailureNodeType(types.NodeTypeCleanupAfterSuite))) }) }) Context("when cleanup is interrupted", func() { BeforeEach(func() { success, _ := RunFixture("cleanup failure", func() { BeforeEach(rt.T("BE", C("C-BE"))) It("A", rt.T("A", func() { DeferCleanup(func() { rt.Run("C-A") interruptHandler.Interrupt(interrupt_handler.InterruptCauseSignal) time.Sleep(time.Minute) }) })) }) Ω(success).Should(BeFalse()) }) It("runs subsequent cleanups and is marked as interrupted", func() { Ω(rt).Should(HaveTracked("BE", "A", "C-A", "C-BE")) Ω(reporter.Did.Find("A")).Should(HaveBeenInterrupted(interrupt_handler.InterruptCauseSignal)) }) }) }) Context("edge cases", func() { Context("cleanup is added in a SynchronizedBeforeSuite and SynchronizedAfterSuite", func() { Context("when running in serial", func() { BeforeEach(func() { success, _ := RunFixture("cleanup in synchronized suites", func() { SynchronizedBeforeSuite(func() []byte { rt.Run("BS1") DeferCleanup(rt.Run, "C-BS1") return nil }, func(_ []byte) { rt.Run("BS2") DeferCleanup(rt.Run, "C-BS2") }) SynchronizedAfterSuite(func() { rt.Run("AS1") DeferCleanup(rt.Run, "C-AS1") }, func() { rt.Run("AS2") DeferCleanup(rt.Run, "C-AS2") }) Context("ordering", func() { It("A", rt.T("A", C("C-A"))) It("B", rt.T("B", C("C-B"))) }) }) Ω(success).Should(BeTrue()) }) It("runs the cleanup at the appropriate time", func() { Ω(rt).Should(HaveTracked("BS1", "BS2", "A", "C-A", "B", "C-B", "AS1", "AS2", "C-AS2", "C-AS1", "C-BS2", "C-BS1")) }) }) Context("when running in parallel and there is no SynchronizedAfterSuite", func() { fixture := func() { SynchronizedBeforeSuite(func() []byte { rt.Run("BS1") DeferCleanup(rt.Run, "C-BS1") return nil }, func(_ []byte) { rt.Run("BS2") DeferCleanup(rt.Run, "C-BS2") }) Context("ordering", func() { It("A", rt.T("A", C("C-A"))) It("B", rt.T("B", C("C-B"))) }) } BeforeEach(func() { SetUpForParallel(2) }) Context("as process #1", func() { It("runs the cleanup only _after_ the other processes have finished", func() { done := make(chan interface{}) go func() { defer GinkgoRecover() success, _ := RunFixture("DeferCleanup on SBS in parallel on process 1", fixture) Ω(success).Should(BeTrue()) close(done) }() Eventually(rt).Should(HaveTracked("BS1", "BS2", "A", "C-A", "B", "C-B")) Consistently(rt).Should(HaveTracked("BS1", "BS2", "A", "C-A", "B", "C-B")) close(exitChannels[2]) Eventually(rt).Should(HaveTracked("BS1", "BS2", "A", "C-A", "B", "C-B", "C-BS2", "C-BS1")) Eventually(done).Should(BeClosed()) }) }) Context("as process #2", func() { BeforeEach(func() { conf.ParallelProcess = 2 client.PostSynchronizedBeforeSuiteCompleted(types.SpecStatePassed, []byte("hola hola")) success, _ := RunFixture("DeferCleanup on SBS in parallel on process 2", fixture) Ω(success).Should(BeTrue()) }) It("runs the cleanup at the appropriate time", func() { Ω(rt).Should(HaveTracked("BS2", "A", "C-A", "B", "C-B", "C-BS2")) }) }) }) }) Context("cleanup is added in an AfterAll that is called because an AfterEach has caused the non-final spec in an ordered group to fail", func() { BeforeEach(func() { success, _ := RunFixture("cleanup in hairy edge case", func() { Context("ordered", Ordered, func() { It("A", rt.T("A", C("C-A"))) It("B", rt.T("B")) AfterEach(rt.T("AE", func() { DeferCleanup(rt.Run, "C-AE") F("fail") })) AfterAll(rt.T("AA", C("C-AA"))) }) }) Ω(success).Should(BeFalse()) }) It("notes that a cleanup was registered in the AfterAll and runs it", func() { Ω(rt).Should(HaveTracked("A", "AE", "AA", "C-AE", "C-A", "C-AA")) }) }) Context("when cleanup is added in parallel in some goroutines", func() { BeforeEach(func() { success, _ := RunFixture("concurrent cleanup", func() { Context("ordered", Ordered, func() { It("A", func() { wg := &sync.WaitGroup{} wg.Add(5) for i := 0; i < 5; i++ { i := i go func() { DeferCleanup(rt.Run, fmt.Sprintf("dc-%d", i)) wg.Done() }() } wg.Wait() }) }) }) Ω(success).Should(BeTrue()) }) It("doesn't race", func() { Ω(rt.TrackedRuns()).Should(ConsistOf("dc-0", "dc-1", "dc-2", "dc-3", "dc-4")) }) }) }) }) golang-github-onsi-ginkgo-v2-2.22.0/internal/internal_integration/config_dry_run_test.go000066400000000000000000000046201472321612100315240ustar00rootroot00000000000000package internal_integration_test import ( . "github.com/onsi/ginkgo/v2" . "github.com/onsi/ginkgo/v2/internal/test_helpers" . "github.com/onsi/gomega" ) var _ = Describe("when config.DryRun is enabled", func() { BeforeEach(func() { conf.DryRun = true conf.SkipStrings = []string{"E"} RunFixture("dry run", func() { ReportBeforeSuite(func(report Report) { rt.RunWithData("report-before-suite", "report", report) }) BeforeSuite(rt.T("before-suite")) BeforeEach(rt.T("bef")) ReportBeforeEach(func(_ SpecReport) { rt.Run("report-before-each") }) Describe("container", func() { It("A", rt.T("A")) It("B", rt.T("B", func() { F() })) PIt("C", rt.T("C", func() { F() })) It("D", rt.T("D")) It("E", rt.T("E")) }) AfterEach(rt.T("aft")) AfterSuite(rt.T("after-suite")) ReportAfterEach(func(_ SpecReport) { rt.Run("report-after-each") }) ReportAfterSuite("", func(_ Report) { rt.Run("report-after-suite") }) }) }) It("does not run any tests but does invoke reporters", func() { Ω(rt).Should(HaveTracked( "report-before-suite", //BeforeSuite "report-before-each", "report-after-each", //A "report-before-each", "report-after-each", //B "report-before-each", "report-after-each", //C "report-before-each", "report-after-each", //D "report-before-each", "report-after-each", //E "report-after-suite", //AfterSuite )) }) It("correctly calculates the number of specs that will run", func() { report := rt.DataFor("report-before-suite")["report"].(Report) Ω(report.PreRunStats.SpecsThatWillRun).Should(Equal(3)) Ω(report.PreRunStats.TotalSpecs).Should(Equal(5)) }) It("reports on the tests (both that they will run and that they did run) and honors skip state", func() { Ω(reporter.Will.Names()).Should(Equal([]string{"A", "B", "C", "D", "E"})) Ω(reporter.Will.Find("C")).Should(BePending()) Ω(reporter.Will.Find("E")).Should(HaveBeenSkipped()) Ω(reporter.Did.Names()).Should(Equal([]string{"A", "B", "C", "D", "E"})) Ω(reporter.Did.Find("A")).Should(HavePassed()) Ω(reporter.Did.Find("B")).Should(HavePassed()) Ω(reporter.Did.Find("C")).Should(BePending()) Ω(reporter.Did.Find("D")).Should(HavePassed()) Ω(reporter.Did.Find("E")).Should(HaveBeenSkipped()) }) It("reports the correct statistics", func() { Ω(reporter.End).Should(BeASuiteSummary(NSpecs(5), NPassed(3), NPending(1), NSkipped(1))) }) }) golang-github-onsi-ginkgo-v2-2.22.0/internal/internal_integration/config_fail_fast_test.go000066400000000000000000000025251472321612100317740ustar00rootroot00000000000000package internal_integration_test import ( . "github.com/onsi/ginkgo/v2" . "github.com/onsi/ginkgo/v2/internal/test_helpers" . "github.com/onsi/gomega" ) var _ = Describe("when config.FailFast is enabled", func() { BeforeEach(func() { SetUpForParallel(2) conf.FailFast = true Ω(client.ShouldAbort()).Should(BeFalse()) RunFixture("fail fast", func() { Describe("a container", func() { BeforeEach(rt.T("bef")) It("A", rt.T("A")) It("B", rt.T("B", func() { F() })) It("C", rt.T("C", func() { F() })) It("D", rt.T("D")) AfterEach(rt.T("aft")) }) AfterSuite(rt.T("after-suite")) }) }) It("does not run any tests after the failure occurs, but does run the failed tests's after each and the after suite", func() { Ω(rt).Should(HaveTracked( "bef", "A", "aft", "bef", "B", "aft", "after-suite", )) }) It("reports that the tests were skipped", func() { Ω(reporter.Did.Find("A")).Should(HavePassed()) Ω(reporter.Did.Find("B")).Should(HaveFailed()) Ω(reporter.Did.Find("C")).Should(HaveBeenSkipped()) Ω(reporter.Did.Find("D")).Should(HaveBeenSkipped()) }) It("reports the correct statistics", func() { Ω(reporter.End).Should(BeASuiteSummary(NSpecs(4), NPassed(1), NFailed(1), NSkipped(2))) }) It("tells the server to abort", func() { Ω(client.ShouldAbort()).Should(BeTrue()) }) }) golang-github-onsi-ginkgo-v2-2.22.0/internal/internal_integration/config_flake_attempts_test.go000066400000000000000000000063401472321612100330460ustar00rootroot00000000000000package internal_integration_test import ( "fmt" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/ginkgo/v2/internal/test_helpers" "github.com/onsi/ginkgo/v2/types" . "github.com/onsi/gomega" ) var _ = Describe("when config.FlakeAttempts is greater than 1", func() { var success bool JustBeforeEach(func() { var counterA, counterC int success, _ = RunFixture("flakey success", func() { It("A", rt.T("A", func() { counterA += 1 if counterA < 2 { F(fmt.Sprintf("A - %d", counterA)) } })) It("B", func() {}) It("C", FlakeAttempts(1), rt.T("C", func() { //the config flag overwrites the individual test annotations counterC += 1 By(fmt.Sprintf("C - attempt #%d", counterC)) if counterC < 3 { F(fmt.Sprintf("C - %d", counterC)) } })) }) }) Context("when a test succeeds within the correct number of attempts", func() { BeforeEach(func() { conf.FlakeAttempts = 3 }) It("reports that the suite passed, but with flaked specs", func() { Ω(success).Should(BeTrue()) Ω(reporter.End).Should(BeASuiteSummary(NSpecs(3), NFailed(0), NPassed(3), NFlaked(2))) }) It("reports that the test passed with the correct number of attempts", func() { Ω(reporter.Did.Find("A")).Should(HavePassed(NumAttempts(2))) Ω(reporter.Did.Find("B")).Should(HavePassed(NumAttempts(1))) Ω(reporter.Did.Find("C")).Should(HavePassed(NumAttempts(3))) Ω(reporter.Did.Find("C").Timeline()).Should(BeTimelineContaining( BeSpecEvent(types.SpecEventByStart, "C - attempt #1"), HaveFailed("C - 1"), BeSpecEvent(types.SpecEventSpecRetry, 1), BeSpecEvent(types.SpecEventByStart, "C - attempt #2"), HaveFailed("C - 2"), BeSpecEvent(types.SpecEventSpecRetry, 2), BeSpecEvent(types.SpecEventByStart, "C - attempt #3"), )) }) It("includes the intermediate failures as AdditionalFailures (this allows timeline reconstruction)", func() { Ω(reporter.Did.Find("C").AdditionalFailures).Should(HaveLen(2)) Ω(reporter.Did.Find("C").AdditionalFailures[0]).Should(HaveFailed("C - 1")) Ω(reporter.Did.Find("C").AdditionalFailures[1]).Should(HaveFailed("C - 2")) }) }) Context("when the test fails", func() { BeforeEach(func() { conf.FlakeAttempts = 2 }) It("reports that the suite failed", func() { Ω(success).Should(BeFalse()) Ω(reporter.End).Should(BeASuiteSummary(NSpecs(3), NFailed(1), NPassed(2), NFlaked(1))) }) It("reports that the test failed with the correct number of attempts", func() { Ω(reporter.Did.Find("A")).Should(HavePassed(NumAttempts(2))) Ω(reporter.Did.Find("B")).Should(HavePassed(NumAttempts(1))) Ω(reporter.Did.Find("C")).Should(HaveFailed("C - 2", NumAttempts(2))) Ω(reporter.Did.Find("C").Timeline()).Should(BeTimelineContaining( BeSpecEvent(types.SpecEventByStart, "C - attempt #1"), HaveFailed("C - 1"), BeSpecEvent(types.SpecEventSpecRetry, 1), BeSpecEvent(types.SpecEventByStart, "C - attempt #2"), )) }) It("includes the intermediate failures as AdditionalFailure, but not the final failure (this allows timeline reconstruction)", func() { Ω(reporter.Did.Find("C").AdditionalFailures).Should(HaveLen(1)) Ω(reporter.Did.Find("C").AdditionalFailures[0]).Should(HaveFailed("C - 1")) }) }) }) config_must_pass_repeatedly_test.go000066400000000000000000000027121472321612100342170ustar00rootroot00000000000000golang-github-onsi-ginkgo-v2-2.22.0/internal/internal_integrationpackage internal_integration_test import ( "fmt" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/ginkgo/v2/internal/test_helpers" . "github.com/onsi/gomega" ) var _ = Describe("when config.MustPassRepeatedly is greater than 1", func() { var success bool JustBeforeEach(func() { var counterB int success, _ = RunFixture("flakey success", func() { It("A", func() {}) It("B", func() { counterB += 1 if counterB == 8 { F(fmt.Sprintf("C - %d", counterB)) } }) }) }) Context("when all tests pass", func() { BeforeEach(func() { conf.MustPassRepeatedly = 5 }) It("reports that the suite passed", func() { Ω(success).Should(BeTrue()) Ω(reporter.End).Should(BeASuiteSummary(NSpecs(2), NFailed(0), NPassed(2))) }) It("reports that the tests passed with the correct number of attempts", func() { Ω(reporter.Did.Find("A")).Should(HavePassed(NumAttempts(5))) Ω(reporter.Did.Find("B")).Should(HavePassed(NumAttempts(5))) }) }) Context("when a test fails", func() { BeforeEach(func() { conf.MustPassRepeatedly = 10 }) It("reports that the suite failed", func() { Ω(success).Should(BeFalse()) Ω(reporter.End).Should(BeASuiteSummary(NSpecs(2), NFailed(1), NPassed(1))) }) It("reports that the tests failed with the correct number of attempts", func() { Ω(reporter.Did.Find("A")).Should(HavePassed(NumAttempts(10))) Ω(reporter.Did.Find("B")).Should(HaveFailed(NumAttempts(8))) }) }) }) golang-github-onsi-ginkgo-v2-2.22.0/internal/internal_integration/current_spec_report_test.go000066400000000000000000000072631472321612100326120ustar00rootroot00000000000000package internal_integration_test import ( "time" . "github.com/onsi/ginkgo/v2" "github.com/onsi/ginkgo/v2/types" . "github.com/onsi/gomega" ) var _ = Describe("CurrentSpecReport", func() { var specs map[string]types.SpecReport BeforeEach(func() { specs = map[string]types.SpecReport{} outputInterceptor.AppendInterceptedOutput("output-interceptor-content") logCurrentSpecReport := func(key string, andRun ...func()) func() { return func() { specs[key] = CurrentSpecReport() if len(andRun) > 0 { andRun[0]() } } } RunFixture("current test description", func() { BeforeSuite(logCurrentSpecReport("before-suite")) Context("a passing test", func() { BeforeEach(logCurrentSpecReport("bef-A", func() { writer.Println("hello bef-A") })) It("A", logCurrentSpecReport("it-A", func() { writer.Println("hello it-A") time.Sleep(20 * time.Millisecond) })) AfterEach(logCurrentSpecReport("aft-A")) }) Context("a failing test", func() { BeforeEach(logCurrentSpecReport("bef-B")) It("B", logCurrentSpecReport("it-B", func() { writer.Println("hello it-B") F("failed") })) AfterEach(logCurrentSpecReport("aft-B")) }) Context("an ordered container", Ordered, func() { It("C", logCurrentSpecReport("C")) }) Context("an serial spec", func() { It("D", Serial, logCurrentSpecReport("D")) }) AfterSuite(logCurrentSpecReport("after-suite")) }) }) It("returns an a valid CurrentSpecReport in the before suite and after suite", func() { Ω(specs["before-suite"].LeafNodeType).Should(Equal(types.NodeTypeBeforeSuite)) Ω(specs["after-suite"].LeafNodeType).Should(Equal(types.NodeTypeAfterSuite)) }) It("reports as passed while the test is passing", func() { Ω(specs["bef-A"].Failed()).Should(BeFalse()) Ω(specs["it-A"].Failed()).Should(BeFalse()) Ω(specs["aft-A"].Failed()).Should(BeFalse()) }) It("reports as failed when the test fails", func() { Ω(specs["bef-B"].Failed()).Should(BeFalse()) Ω(specs["it-B"].Failed()).Should(BeFalse()) Ω(specs["aft-B"].Failed()).Should(BeTrue()) }) It("captures GinkgoWriter output", func() { Ω(specs["bef-A"].CapturedGinkgoWriterOutput).Should(BeZero()) Ω(specs["it-A"].CapturedGinkgoWriterOutput).Should(Equal("hello bef-A\n")) Ω(specs["aft-A"].CapturedGinkgoWriterOutput).Should(Equal("hello bef-A\nhello it-A\n")) Ω(specs["bef-B"].CapturedGinkgoWriterOutput).Should(BeZero()) Ω(specs["it-B"].CapturedGinkgoWriterOutput).Should(BeZero()) Ω(specs["aft-B"].CapturedGinkgoWriterOutput).Should(Equal("hello it-B\n")) }) It("does not capture stdout/err output", func() { Ω(specs["aft-A"].CapturedStdOutErr).Should(BeZero()) Ω(specs["aft-B"].CapturedStdOutErr).Should(BeZero()) }) It("captures serial/ordered correctly", func() { Ω(specs["A"].IsSerial).Should(BeFalse()) Ω(specs["A"].IsInOrderedContainer).Should(BeFalse()) Ω(specs["after-suite"].IsSerial).Should(BeFalse()) Ω(specs["after-suite"].IsInOrderedContainer).Should(BeFalse()) Ω(specs["C"].IsSerial).Should(BeFalse()) Ω(specs["C"].IsInOrderedContainer).Should(BeTrue()) Ω(specs["D"].IsSerial).Should(BeTrue()) Ω(specs["D"].IsInOrderedContainer).Should(BeFalse()) }) It("captures test details correctly", func() { spec := specs["aft-A"] Ω(spec.ContainerHierarchyTexts).Should(Equal([]string{"a passing test"})) Ω(spec.LeafNodeText).Should(Equal("A")) Ω(spec.FullText()).Should(Equal("a passing test A")) location := reporter.Did.Find("A").LeafNodeLocation Ω(spec.FileName()).Should(Equal(location.FileName)) Ω(spec.LineNumber()).Should(Equal(location.LineNumber)) Ω(spec.RunTime).Should(BeNumerically(">=", time.Millisecond*20)) }) }) golang-github-onsi-ginkgo-v2-2.22.0/internal/internal_integration/decorations_test.go000066400000000000000000000105721472321612100310320ustar00rootroot00000000000000package internal_integration_test import ( . "github.com/onsi/ginkgo/v2" . "github.com/onsi/ginkgo/v2/internal/test_helpers" . "github.com/onsi/gomega" "github.com/onsi/ginkgo/v2/types" ) var _ = Describe("Decorations test", func() { var clForOffset types.CodeLocation BeforeEach(func() { customIt := func() { It("is-offset", rt.T("is-offset"), Offset(1)) } otherCustomIt := func() { GinkgoHelper() It("is-also-offset", rt.T("is-also-offset")) } var countFlaky = 0 var countRepeat = 0 success, _ := RunFixture("happy-path decoration test", func() { Describe("top-level-container", func() { clForOffset = types.NewCodeLocation(0) customIt() otherCustomIt() It("flaky", FlakeAttempts(4), rt.T("flaky", func() { countFlaky += 1 outputInterceptor.AppendInterceptedOutput("so flaky\n") writer.Println("so tasty") if countFlaky < 3 { F("fail") } })) It("flaky-never-passes", FlakeAttempts(2), rt.T("flaky-never-passes", func() { F("fail") })) It("flaky-skips", FlakeAttempts(3), rt.T("flaky-skips", func() { Skip("skip") })) It("repeat", MustPassRepeatedly(4), rt.T("repeat", func() { countRepeat += 1 outputInterceptor.AppendInterceptedOutput("repeats a bit\n") writer.Println("here we go") if countRepeat >= 3 { F("fail") } })) It("repeat-never-fails", MustPassRepeatedly(2), rt.T("repeat-never-passes", func() { // F("fail") })) It("repeat-skips", MustPassRepeatedly(3), rt.T("repeat-skips", func() { Skip("skip") })) }) }) Ω(success).Should(BeFalse()) }) It("runs all the test nodes in the expected order", func() { Ω(rt).Should(HaveTracked( "is-offset", "is-also-offset", "flaky", "flaky", "flaky", "flaky-never-passes", "flaky-never-passes", "flaky-skips", "repeat", "repeat", "repeat", "repeat-never-passes", "repeat-never-passes", "repeat-skips", )) }) Describe("Offset", func() { It("applies the offset when computing the codelocation", func() { clForOffset.LineNumber = clForOffset.LineNumber + 1 Ω(reporter.Did.Find("is-offset").LeafNodeLocation).Should(Equal(clForOffset)) }) }) Describe("GinkgoHelper", func() { It("correctly skips through the stack trace when computing the codelocation", func() { clForOffset.LineNumber = clForOffset.LineNumber + 2 Ω(reporter.Did.Find("is-also-offset").LeafNodeLocation).Should(Equal(clForOffset)) }) }) Describe("FlakeAttempts", func() { It("reruns specs until they pass or until the number of flake attempts is exhausted, but does not rerun skipped specs", func() { Ω(reporter.Did.Find("flaky")).Should(HavePassed(NumAttempts(3), CapturedStdOutput("so flaky\nso flaky\nso flaky\n"), CapturedGinkgoWriterOutput("so tasty\nso tasty\nso tasty\n"))) Ω(reporter.Did.Find("flaky").Timeline()).Should(BeTimelineContaining( BeSpecEvent(types.SpecEventSpecRetry, 1), BeSpecEvent(types.SpecEventSpecRetry, 2), )) Ω(reporter.Did.Find("flaky-never-passes")).Should(HaveFailed("fail", NumAttempts(2))) Ω(reporter.Did.Find("flaky-never-passes").Timeline()).Should(BeTimelineContaining( BeSpecEvent(types.SpecEventSpecRetry, 1), )) Ω(reporter.Did.Find("flaky-skips")).Should(HaveBeenSkippedWithMessage("skip", NumAttempts(1))) Ω(reporter.Did.Find("flaky-skips").Timeline()).ShouldNot(BeTimelineContaining( BeSpecEvent(types.SpecEventSpecRetry, 1), )) }) }) Describe("MustPassRepeatedly", func() { It("reruns specs until they fail or until the number of MustPassRepeatedly attempts is exhausted, but does not rerun skipped specs", func() { Ω(reporter.Did.Find("repeat")).Should(HaveFailed(NumAttempts(3), CapturedStdOutput("repeats a bit\nrepeats a bit\nrepeats a bit\n"), CapturedGinkgoWriterOutput("here we go\nhere we go\nhere we go\n"))) Ω(reporter.Did.Find("repeat").Timeline()).Should(BeTimelineContaining( BeSpecEvent(types.SpecEventSpecRepeat, 1, TLWithOffset("here we go\n")), BeSpecEvent(types.SpecEventSpecRepeat, 2, TLWithOffset("here we go\nhere we go\n")), )) Ω(reporter.Did.Find("repeat-never-fails")).Should(HavePassed("passed", NumAttempts(2))) Ω(reporter.Did.Find("repeat-never-fails").Timeline()).Should(BeTimelineContaining( BeSpecEvent(types.SpecEventSpecRepeat, 1), )) Ω(reporter.Did.Find("repeat-skips")).Should(HaveBeenSkippedWithMessage("skip", NumAttempts(1))) }) }) }) golang-github-onsi-ginkgo-v2-2.22.0/internal/internal_integration/fail_test.go000066400000000000000000000347531472321612100274420ustar00rootroot00000000000000package internal_integration_test import ( "time" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" . "github.com/onsi/ginkgo/v2/internal/test_helpers" "github.com/onsi/ginkgo/v2/types" ) var _ = Describe("handling test failures", func() { Describe("when BeforeSuite fails", func() { BeforeEach(func() { success, _ := RunFixture("failed beforesuite", func() { BeforeSuite(rt.T("before-suite", func() { writer.Write([]byte("before-suite")) F("fail", cl) })) It("A", rt.T("A")) It("B", rt.T("B")) AfterSuite(rt.T("after-suite")) }) Ω(success).Should(BeFalse()) }) It("reports a suite failure", func() { Ω(reporter.End).Should(BeASuiteSummary(false, NSpecs(2), NSkipped(0))) }) It("reports a failure for the BeforeSuite", func() { Ω(reporter.Did.FindByLeafNodeType(types.NodeTypeBeforeSuite)).Should(HaveFailed("fail", cl, CapturedGinkgoWriterOutput("before-suite"))) Ω(reporter.Did.FindByLeafNodeType(types.NodeTypeAfterSuite)).Should(HavePassed()) }) It("does not run any of the Its", func() { Ω(rt).ShouldNot(HaveRun("A")) Ω(rt).ShouldNot(HaveRun("B")) }) It("does run the AfterSuite", func() { Ω(rt).Should(HaveTracked("before-suite", "after-suite")) }) It("emits the failures", func() { Ω(reporter.Failures[0]).Should(HaveFailed("fail", cl, FailureNodeType(types.NodeTypeBeforeSuite))) }) }) Describe("when BeforeSuite panics", func() { BeforeEach(func() { success, _ := RunFixture("panicked beforesuite", func() { BeforeSuite(rt.T("before-suite", func() { writer.Write([]byte("before-suite")) panic("boom") })) It("A", rt.T("A")) It("B", rt.T("B")) AfterSuite(rt.T("after-suite")) }) Ω(success).Should(BeFalse()) }) It("reports a suite failure", func() { Ω(reporter.End).Should(BeASuiteSummary(false, NSpecs(2), NSkipped(0))) }) It("reports a failure for the BeforeSuite", func() { Ω(reporter.Did.FindByLeafNodeType(types.NodeTypeBeforeSuite)).Should(HavePanicked("boom", CapturedGinkgoWriterOutput("before-suite"))) }) It("does not run any of the Its", func() { Ω(rt).ShouldNot(HaveRun("A")) Ω(rt).ShouldNot(HaveRun("B")) }) It("does run the AfterSuite", func() { Ω(rt).Should(HaveTracked("before-suite", "after-suite")) }) It("emits the failures", func() { Ω(reporter.Failures[0]).Should(HavePanicked("boom", FailureNodeType(types.NodeTypeBeforeSuite))) }) }) Describe("when AfterSuite fails/panics", func() { BeforeEach(func() { success, _ := RunFixture("failed aftersuite", func() { BeforeSuite(rt.T("before-suite")) Describe("top-level", func() { It("A", rt.T("A")) It("B", rt.T("B")) }) AfterSuite(rt.T("after-suite", func() { writer.Write([]byte("after-suite")) F("fail", cl) })) }) Ω(success).Should(BeFalse()) }) It("reports a suite failure", func() { Ω(reporter.End).Should(BeASuiteSummary(false, NSpecs(2), NPassed(2))) }) It("runs and reports on all the tests and reports a failure for the AfterSuite", func() { Ω(rt).Should(HaveTracked("before-suite", "A", "B", "after-suite")) Ω(reporter.Did.FindByLeafNodeType(types.NodeTypeBeforeSuite)).Should(HavePassed()) Ω(reporter.Did.Find("A")).Should(HavePassed()) Ω(reporter.Did.Find("B")).Should(HavePassed()) Ω(reporter.Did.FindByLeafNodeType(types.NodeTypeAfterSuite)).Should(HaveFailed("fail", cl, CapturedGinkgoWriterOutput("after-suite"))) }) It("emits the failures", func() { Ω(reporter.Failures[0]).Should(HaveFailed("fail", FailureNodeType(types.NodeTypeAfterSuite), TLWithOffset("after-suite"))) }) }) Describe("individual test falures", func() { Describe("when an It fails", func() { BeforeEach(func() { success, _ := RunFixture("failed it", func() { BeforeSuite(rt.T("before-suite")) Describe("top-level", func() { It("A", rt.T("A", func() { writer.Write([]byte("running A")) })) It("B", rt.T("B", func() { writer.Write([]byte("running B")) F("fail", cl) })) It("C", rt.T("C")) }) AfterEach(rt.T("after-each")) AfterSuite(rt.T("after-suite")) }) Ω(success).Should(BeFalse()) }) It("reports a suite failure", func() { Ω(reporter.End).Should(BeASuiteSummary(false, NSpecs(3), NPassed(2), NFailed(1))) }) It("runs other Its, the AfterEach, and the AfterSuite", func() { Ω(rt).Should(HaveTracked("before-suite", "A", "after-each", "B", "after-each", "C", "after-each", "after-suite")) }) It("reports the It's failure", func() { Ω(reporter.Did.Find("A")).Should(HavePassed(CapturedGinkgoWriterOutput("running A"))) Ω(reporter.Did.Find("B")).Should(HaveFailed("fail", cl, CapturedGinkgoWriterOutput("running B"), TLWithOffset("running B"))) Ω(reporter.Did.Find("C")).Should(HavePassed()) }) It("sets up the failure node location correctly", func() { report := reporter.Did.Find("B") Ω(report.Failure.FailureNodeContext).Should(Equal(types.FailureNodeIsLeafNode)) Ω(report.Failure.FailureNodeType).Should(Equal(types.NodeTypeIt)) Ω(report.Failure.FailureNodeLocation).Should(Equal(report.LeafNodeLocation)) }) It("emits the failures", func() { Ω(reporter.Failures[0]).Should(HaveFailed("fail", cl, TLWithOffset("running B"))) }) }) Describe("when an It panics", func() { BeforeEach(func() { success, _ := RunFixture("panicked it", func() { BeforeSuite(rt.T("before-suite")) Describe("top-level", func() { It("A", rt.T("A", func() { writer.Write([]byte("running A")) })) It("B", rt.T("B", func() { writer.Write([]byte("running B")) panic("boom") })) It("C", rt.T("C")) }) AfterSuite(rt.T("after-suite")) }) Ω(success).Should(BeFalse()) }) It("reports a suite failure", func() { Ω(reporter.End).Should(BeASuiteSummary(false, NSpecs(3), NPassed(2), NFailed(1))) }) It("runs other Its and the AfterSuite", func() { Ω(rt).Should(HaveTracked("before-suite", "A", "B", "C", "after-suite")) }) It("reports the It's failure", func() { Ω(reporter.Did.Find("A")).Should(HavePassed(CapturedGinkgoWriterOutput("running A"))) Ω(reporter.Did.Find("B")).Should(HavePanicked("boom", CapturedGinkgoWriterOutput("running B"), TLWithOffset("running B"))) Ω(reporter.Did.Find("C")).Should(HavePassed()) }) It("sets up the failure node location correctly", func() { report := reporter.Did.Find("B") Ω(report.Failure.FailureNodeContext).Should(Equal(types.FailureNodeIsLeafNode)) Ω(report.Failure.FailureNodeType).Should(Equal(types.NodeTypeIt)) Ω(report.Failure.FailureNodeLocation).Should(Equal(report.LeafNodeLocation)) }) It("emits the failures", func() { Ω(reporter.Failures[0]).Should(HavePanicked("boom", TLWithOffset("running B"))) }) }) Describe("when a BeforeEach fails/panics", func() { BeforeEach(func() { success, _ := RunFixture("failed before each", func() { BeforeEach(rt.T("bef-1")) JustBeforeEach(rt.T("jus-bef-1")) Describe("top-level", func() { BeforeEach(rt.T("bef-2", func() { writer.Write([]byte("bef-2 is running")) F("fail", cl) })) JustBeforeEach(rt.T("jus-bef-2")) Describe("nested", func() { BeforeEach(rt.T("bef-3")) JustBeforeEach(rt.T("jus-bef-3")) It("the test", rt.T("it")) JustAfterEach(rt.T("jus-aft-3")) AfterEach(rt.T("aft-3")) }) JustAfterEach(rt.T("jus-aft-2")) AfterEach(rt.T("aft-2", func() { writer.Write([]byte("aft-2 is running")) })) }) JustAfterEach(rt.T("jus-aft-1")) AfterEach(rt.T("aft-1")) }) Ω(success).Should(BeFalse()) }) It("reports a suite failure and a spec failure", func() { Ω(reporter.End).Should(BeASuiteSummary(false, NSpecs(1), NPassed(0), NFailed(1))) specReport := reporter.Did.Find("the test") Ω(specReport).Should(HaveFailed("fail", cl, CapturedGinkgoWriterOutput("bef-2 is runningaft-2 is running"))) }) It("sets up the failure node location correctly", func() { report := reporter.Did.Find("the test") Ω(report.Failure.FailureNodeContext).Should(Equal(types.FailureNodeInContainer)) Ω(report.Failure.FailureNodeType).Should(Equal(types.NodeTypeBeforeEach)) Ω(report.Failure.FailureNodeContainerIndex).Should(Equal(0)) }) It("emits the failures", func() { Ω(reporter.Failures[0]).Should(HaveFailed("fail", cl)) }) It("runs the JustAfterEaches and AfterEaches at the same or lesser nesting level", func() { Ω(rt).Should(HaveTracked("bef-1", "bef-2", "jus-aft-2", "jus-aft-1", "aft-2", "aft-1")) }) }) Describe("when a top-level BeforeEach fails/panics", func() { BeforeEach(func() { success, _ := RunFixture("failed before each", func() { BeforeEach(rt.T("bef-1", func() { F("fail", cl) })) It("the test", rt.T("it")) }) Ω(success).Should(BeFalse()) }) It("sets up the failure node location correctly", func() { report := reporter.Did.Find("the test") Ω(report.Failure.FailureNodeContext).Should(Equal(types.FailureNodeAtTopLevel)) Ω(report.Failure.FailureNodeType).Should(Equal(types.NodeTypeBeforeEach)) Ω(reporter.Failures[0]).Should(HaveFailed("fail", cl)) }) }) Describe("when an AfterEach fails/panics", func() { BeforeEach(func() { success, _ := RunFixture("failed after each", func() { BeforeEach(rt.T("bef-1")) JustBeforeEach(rt.T("jus-bef-1")) Describe("top-level", func() { BeforeEach(rt.T("bef-2")) Describe("nested", func() { BeforeEach(rt.T("bef-3")) It("the test", rt.T("it")) JustAfterEach(rt.T("jus-aft-3")) AfterEach(rt.T("aft-3", func() { F("fail", types.NewCodeLocation(0)) })) }) JustAfterEach(rt.T("jus-aft-2")) AfterEach(rt.T("aft-2", func() { writer.Write([]byte("aft-2 is running")) })) }) JustAfterEach(rt.T("jus-aft-1")) AfterEach(rt.T("aft-1")) }) Ω(success).Should(BeFalse()) }) It("reports a suite failure and a spec failure", func() { Ω(reporter.End).Should(BeASuiteSummary(false, NSpecs(1), NPassed(0), NFailed(1))) specReport := reporter.Did.Find("the test") Ω(specReport).Should(HaveFailed("fail", CapturedGinkgoWriterOutput("aft-2 is running"))) Ω(reporter.Failures[0]).Should(HaveFailed("fail")) }) It("sets up the failure node location correctly", func() { report := reporter.Did.Find("the test") Ω(report.Failure.FailureNodeContext).Should(Equal(types.FailureNodeInContainer)) Ω(report.Failure.FailureNodeType).Should(Equal(types.NodeTypeAfterEach)) location := report.Failure.Location location.LineNumber = location.LineNumber - 1 Ω(report.Failure.FailureNodeLocation).Should(Equal(location)) Ω(report.Failure.FailureNodeContainerIndex).Should(Equal(1)) }) It("runs the subsequent after eaches", func() { Ω(rt).Should(HaveTracked("bef-1", "bef-2", "bef-3", "jus-bef-1", "it", "jus-aft-3", "jus-aft-2", "jus-aft-1", "aft-3", "aft-2", "aft-1")) }) }) Describe("when multiple nodes within a given test run and fail", func() { var clA, clB types.CodeLocation BeforeEach(func() { clA = types.CodeLocation{FileName: "A"} clB = types.CodeLocation{FileName: "B"} success, _ := RunFixture("failed after each", func() { BeforeEach(rt.T("bef-1", func() { writer.Write([]byte("run A")) DeferCleanup(rt.TSC("dc-1", func(ctx SpecContext) { writer.Write([]byte("run DC")) <-ctx.Done() F("fail-DC") }), NodeTimeout(time.Millisecond*50)) F("fail-A", clA) })) It("the test", rt.T("it")) AfterEach(rt.T("aft-1", func() { writer.Write([]byte("run B")) F("fail-B", clB) })) }) Ω(success).Should(BeFalse()) }) It("reports a suite failure and a spec failure and tracks the first failure as its primary failure, but also tracks the additional failures", func() { Ω(reporter.End).Should(BeASuiteSummary(false, NSpecs(1), NPassed(0), NFailed(1))) specReport := reporter.Did.Find("the test") Ω(specReport).Should(HaveFailed("fail-A", clA, CapturedGinkgoWriterOutput("run Arun Brun DC"))) Ω(specReport.Failure.FailureNodeType).Should(Equal(types.NodeTypeBeforeEach)) Ω(rt).Should(HaveTracked("bef-1", "aft-1", "dc-1")) Ω(specReport.AdditionalFailures).Should(HaveLen(2)) Ω(specReport.AdditionalFailures[0].State).Should(Equal(types.SpecStateFailed)) Ω(specReport.AdditionalFailures[0].Failure.Message).Should(Equal("fail-B")) Ω(specReport.AdditionalFailures[0].Failure.Location).Should(Equal(clB)) Ω(specReport.AdditionalFailures[0].Failure.FailureNodeType).Should(Equal(types.NodeTypeAfterEach)) Ω(specReport.AdditionalFailures[1].State).Should(Equal(types.SpecStateTimedout)) Ω(specReport.AdditionalFailures[1]).Should(HaveTimedOut("A node timeout occurred")) Ω(specReport.AdditionalFailures[1].Failure.AdditionalFailure).Should(HaveFailed("A node timeout occurred and then the following failure was recorded in the timedout node before it exited:\nfail-DC")) Ω(specReport.AdditionalFailures[1].Failure.FailureNodeType).Should(Equal(types.NodeTypeCleanupAfterEach)) }) It("also emits all the failures", func() { Ω(reporter.Failures).Should(HaveLen(4)) Ω(reporter.Failures[0]).Should(HaveFailed("fail-A", clA)) Ω(reporter.Failures[1]).Should(HaveFailed("fail-B", clB)) Ω(reporter.Failures[2]).Should(HaveTimedOut("A node timeout occurred")) Ω(reporter.Failures[3]).Should(HaveFailed("A node timeout occurred and then the following failure was recorded in the timedout node before it exited:\nfail-DC")) }) }) }) Describe("when there are multiple tests that fail", func() { BeforeEach(func() { success, _ := RunFixture("failed after each", func() { It("A", func() { F() }) It("B", func() { F() }) It("C", func() {}) It("D", func() { F() }) It("E", func() {}) It("F", func() { panic("boom") }) }) Ω(success).Should(BeFalse()) }) It("reports the correct number of failures", func() { Ω(reporter.End).Should(BeASuiteSummary(false, NSpecs(6), NPassed(2), NFailed(4))) Ω(reporter.Did.WithState(types.SpecStatePassed).Names()).Should(ConsistOf("C", "E")) Ω(reporter.Did.WithState(types.SpecStateFailed).Names()).Should(ConsistOf("A", "B", "D")) Ω(reporter.Did.WithState(types.SpecStatePanicked).Names()).Should(ConsistOf("F")) }) It("emits all the failures", func() { Ω(reporter.Failures).Should(HaveLen(4)) }) }) }) golang-github-onsi-ginkgo-v2-2.22.0/internal/internal_integration/focus_test.go000066400000000000000000000210611472321612100276320ustar00rootroot00000000000000package internal_integration_test import ( . "github.com/onsi/ginkgo/v2" "github.com/onsi/ginkgo/v2/types" . "github.com/onsi/gomega" . "github.com/onsi/ginkgo/v2/internal/test_helpers" ) var _ = Describe("Focus", func() { Describe("when a suite has pending tests", func() { fixture := func() { It("A", rt.T("A")) It("B", rt.T("B")) It("C", rt.T("C"), Pending) Describe("container", func() { It("D", rt.T("D")) }) PDescribe("pending container", func() { It("E", rt.T("E")) It("F", rt.T("F")) }) } Context("without config.FailOnPending", func() { BeforeEach(func() { success, hPF := RunFixture("pending tests", fixture) Ω(success).Should(BeTrue()) Ω(hPF).Should(BeFalse()) }) It("should not report that the suite hasProgrammaticFocus", func() { Ω(reporter.Begin.SuiteHasProgrammaticFocus).Should(BeFalse()) Ω(reporter.End.SuiteHasProgrammaticFocus).Should(BeFalse()) }) It("does not run the pending tests", func() { Ω(rt.TrackedRuns()).Should(ConsistOf("A", "B", "D")) }) It("reports on the pending tests", func() { Ω(reporter.Did.WithState(types.SpecStatePassed).Names()).Should(ConsistOf("A", "B", "D")) Ω(reporter.Did.WithState(types.SpecStatePending).Names()).Should(ConsistOf("C", "E", "F")) }) It("reports on the suite with accurate numbers", func() { Ω(reporter.End).Should(BeASuiteSummary(true, NSpecs(6), NPassed(3), NPending(3), NWillRun(3), NSkipped(0))) }) It("does not include a special suite failure reason", func() { Ω(reporter.End.SpecialSuiteFailureReasons).Should(BeEmpty()) }) }) Context("with config.FailOnPending", func() { BeforeEach(func() { conf.FailOnPending = true success, hPF := RunFixture("pending tests", fixture) Ω(success).Should(BeFalse()) Ω(hPF).Should(BeFalse()) }) It("reports on the suite with accurate numbers", func() { Ω(reporter.End).Should(BeASuiteSummary(false, NPassed(3), NSpecs(6), NPending(3), NWillRun(3), NSkipped(0))) }) It("includes a special suite failure reason", func() { Ω(reporter.End.SpecialSuiteFailureReasons).Should(ContainElement("Detected pending specs and --fail-on-pending is set")) }) }) }) Describe("with programmatic focus", func() { var success bool var hasProgrammaticFocus bool BeforeEach(func() { success, hasProgrammaticFocus = RunFixture("focused tests", func() { It("A", rt.T("A")) It("B", rt.T("B")) FDescribe("focused container", func() { It("C", rt.T("C")) It("D", rt.T("D")) PIt("E", rt.T("E")) }) FDescribe("focused container with focused child", func() { It("F", rt.T("F")) It("G", Focus, rt.T("G")) }) Describe("container", func() { It("H", rt.T("H")) }) FIt("I", rt.T("I")) }) Ω(success).Should(BeTrue()) }) It("should return true for hasProgrammaticFocus", func() { Ω(hasProgrammaticFocus).Should(BeTrue()) }) It("should report that the suite hasProgrammaticFocus", func() { Ω(reporter.Begin.SuiteHasProgrammaticFocus).Should(BeTrue()) Ω(reporter.End.SuiteHasProgrammaticFocus).Should(BeTrue()) }) It("should run the focused tests, honoring the nested focus policy", func() { Ω(rt.TrackedRuns()).Should(ConsistOf("C", "D", "G", "I")) }) It("should report on the tests correctly", func() { Ω(reporter.Did.WithState(types.SpecStateSkipped).Names()).Should(ConsistOf("A", "B", "F", "H")) Ω(reporter.Did.WithState(types.SpecStatePending).Names()).Should(ConsistOf("E")) Ω(reporter.Did.WithState(types.SpecStatePassed).Names()).Should(ConsistOf("C", "D", "G", "I")) }) It("report on the suite with accurate numbers", func() { Ω(reporter.End).Should(BeASuiteSummary(true, NPassed(4), NSkipped(4), NPending(1), NSpecs(9), NWillRun(4))) }) }) Describe("with config.FocusStrings and config.SkipStrings", func() { BeforeEach(func() { conf.FocusStrings = []string{"blue", "green"} conf.SkipStrings = []string{"red"} success, hasProgrammaticFocus := RunFixture("cli focus tests", func() { It("blue.1", rt.T("blue.1")) It("blue.2", rt.T("blue.2")) Describe("blue.container", func() { It("yellow.1", rt.T("yellow.1")) It("red.1", rt.T("red.1")) PIt("blue.3", rt.T("blue.3")) }) Describe("green.container", func() { It("yellow.2", rt.T("yellow.2")) It("green.1", rt.T("green.1")) }) Describe("red.2", func() { It("green.2", rt.T("green.2")) }) It("red.3", rt.T("red.3")) }) Ω(success).Should(BeTrue()) Ω(hasProgrammaticFocus).Should(BeFalse()) }) It("should run tests that match", func() { Ω(rt.TrackedRuns()).Should(ConsistOf("blue.1", "blue.2", "yellow.1", "yellow.2", "green.1")) }) It("should report on the tests correctly", func() { Ω(reporter.Did.WithState(types.SpecStateSkipped).Names()).Should(ConsistOf("red.1", "green.2", "red.3")) Ω(reporter.Did.WithState(types.SpecStatePending).Names()).Should(ConsistOf("blue.3")) Ω(reporter.Did.WithState(types.SpecStatePassed).Names()).Should(ConsistOf("blue.1", "blue.2", "yellow.1", "yellow.2", "green.1")) }) It("report on the suite with accurate numbers", func() { Ω(reporter.End).Should(BeASuiteSummary(true, NPassed(5), NSkipped(3), NPending(1), NSpecs(9), NWillRun(5))) }) }) Describe("with a combination of programmatic focus and config.FocusStrings and config.SkipStrings", func() { BeforeEach(func() { conf.FocusStrings = []string{"blue", "green"} conf.SkipStrings = []string{"red"} success, hasProgrammaticFocus := RunFixture("cli focus tests", func() { It("blue.1", rt.T("blue.1")) FIt("blue.2", rt.T("blue.2")) FDescribe("blue.container", func() { It("yellow.1", rt.T("yellow.1")) It("red.1", rt.T("red.1")) PIt("blue.3", rt.T("blue.3")) }) Describe("green.container", func() { It("yellow.2", rt.T("yellow.2")) It("green.1", rt.T("green.1")) }) FDescribe("red.2", func() { It("green.2", rt.T("green.2")) }) FIt("red.3", rt.T("red.3")) }) Ω(success).Should(BeTrue()) Ω(hasProgrammaticFocus).Should(BeTrue()) }) It("should run tests that match all the filters", func() { Ω(rt.TrackedRuns()).Should(ConsistOf("blue.2", "yellow.1")) }) It("should report on the tests correctly", func() { Ω(reporter.Did.WithState(types.SpecStateSkipped).Names()).Should(ConsistOf("blue.1", "red.1", "yellow.2", "green.1", "green.2", "red.3")) Ω(reporter.Did.WithState(types.SpecStatePending).Names()).Should(ConsistOf("blue.3")) Ω(reporter.Did.WithState(types.SpecStatePassed).Names()).Should(ConsistOf("blue.2", "yellow.1")) }) It("report on the suite with accurate numbers", func() { Ω(reporter.End).Should(BeASuiteSummary(true, NPassed(2), NSkipped(6), NPending(1), NSpecs(9), NWillRun(2))) }) }) Describe("when no tests will end up running", func() { BeforeEach(func() { conf.FocusStrings = []string{"red"} success, _ := RunFixture("cli focus tests", func() { BeforeSuite(rt.T("bef-suite")) AfterSuite(rt.T("aft-suite")) It("blue.1", rt.T("blue.1")) It("blue.2", rt.T("blue.2")) }) Ω(success).Should(BeTrue()) }) It("does not run the BeforeSuite or the AfterSuite", func() { Ω(rt).Should(HaveTrackedNothing()) }) }) Describe("Skip()", func() { BeforeEach(func() { success, _ := RunFixture("Skip() tests", func() { Describe("container to ensure order", func() { It("A", rt.T("A")) Describe("container", func() { BeforeEach(rt.T("bef", func() { failer.Skip("skip in Bef", cl) panic("boom") //simulates what Ginkgo DSL does })) It("B", rt.T("B")) It("C", rt.T("C")) AfterEach(rt.T("aft")) }) It("D", rt.T("D", func() { failer.Skip("skip D", cl) panic("boom") //simulates what Ginkgo DSL does })) }) }) Ω(success).Should(BeTrue()) }) It("skips the tests that are Skipped()", func() { Ω(rt).Should(HaveTracked("A", "bef", "aft", "bef", "aft", "D")) Ω(reporter.Did.WithState(types.SpecStatePassed).Names()).Should(ConsistOf("A")) Ω(reporter.Did.WithState(types.SpecStateSkipped).Names()).Should(ConsistOf("B", "C", "D")) Ω(reporter.Did.Find("B").Failure.Message).Should(Equal("skip in Bef")) Ω(reporter.Did.Find("B").Failure.Location).Should(Equal(cl)) Ω(reporter.Did.Find("D").Failure.Message).Should(Equal("skip D")) Ω(reporter.Did.Find("D").Failure.Location).Should(Equal(cl)) }) It("report on the suite with accurate numbers", func() { Ω(reporter.End).Should(BeASuiteSummary(true, NPassed(1), NSkipped(3), NPending(0), NSpecs(4), NWillRun(4))) }) }) }) golang-github-onsi-ginkgo-v2-2.22.0/internal/internal_integration/internal_integration_suite_test.go000066400000000000000000000141231472321612100341440ustar00rootroot00000000000000package internal_integration_test import ( "context" "fmt" "io" "reflect" "testing" "time" . "github.com/onsi/ginkgo/v2" "github.com/onsi/ginkgo/v2/internal" "github.com/onsi/ginkgo/v2/internal/global" "github.com/onsi/ginkgo/v2/internal/interrupt_handler" "github.com/onsi/ginkgo/v2/internal/parallel_support" . "github.com/onsi/ginkgo/v2/internal/test_helpers" "github.com/onsi/ginkgo/v2/types" . "github.com/onsi/gomega" "github.com/onsi/gomega/format" ) func TestSuiteTests(t *testing.T) { interrupt_handler.ABORT_POLLING_INTERVAL = 200 * time.Millisecond format.TruncatedDiff = false RegisterFailHandler(Fail) suiteConfig, _ := GinkgoConfiguration() suiteConfig.GracePeriod = time.Second RunSpecs(t, "Suite Integration Tests", suiteConfig) } var conf types.SuiteConfig var failer *internal.Failer var writer *internal.Writer var reporter *FakeReporter var rt *RunTracker var cl types.CodeLocation var interruptHandler *FakeInterruptHandler var outputInterceptor *FakeOutputInterceptor var server parallel_support.Server var client parallel_support.Client var exitChannels map[int]chan interface{} var triggerProgressSignal func() func progressSignalRegistrar(handler func()) context.CancelFunc { triggerProgressSignal = handler return func() { triggerProgressSignal = nil } } func noopProgressSignalRegistrar(_ func()) context.CancelFunc { return func() {} } var _ = BeforeEach(func() { conf = types.SuiteConfig{} failer = internal.NewFailer() writer = internal.NewWriter(io.Discard) writer.SetMode(internal.WriterModeBufferOnly) reporter = NewFakeReporter() rt = NewRunTracker() cl = types.NewCodeLocation(0) interruptHandler = NewFakeInterruptHandler() outputInterceptor = NewFakeOutputInterceptor() conf.ParallelTotal = 1 conf.ParallelProcess = 1 conf.RandomSeed = 17 conf.GracePeriod = 30 * time.Second server, client, exitChannels = nil, nil, nil }) /* Helpers to set up and run test fixtures using the Ginkgo DSL */ func WithSuite(suite *internal.Suite, callback func()) { originalSuite, originalFailer := global.Suite, global.Failer global.Suite = suite global.Failer = failer callback() global.Suite = originalSuite global.Failer = originalFailer } func SetUpForParallel(parallelTotal int) { conf.ParallelTotal = parallelTotal server, client, exitChannels = SetUpServerAndClient(conf.ParallelTotal) conf.ParallelHost = server.Address() } func RunFixture(description string, callback func()) (bool, bool) { suite := internal.NewSuite() var success, hasProgrammaticFocus bool WithSuite(suite, func() { callback() Ω(suite.BuildTree()).Should(Succeed()) success, hasProgrammaticFocus = suite.Run(description, Label("TopLevelLabel"), "/path/to/suite", failer, reporter, writer, outputInterceptor, interruptHandler, client, progressSignalRegistrar, conf) }) return success, hasProgrammaticFocus } /* You should call SetUpForParallel() first, then call RunFixtureInParallel() this is, at best, an approximation. There are some dsl objects that can be called within a running node (e.g. DeferCleanup) that will not work with RunFixtureInParallel as they will attach to the actual internal_integration suite as opposed to the simulated fixture moreover the FakeInterruptHandler is not used - instead a real interrupt handler is created and configured with the client generated by SetUpForParallel. this is to facilitate the testing of cross-process aborts, which was the primary motivator for this method. also a noopProgressSignalRegistrar is used to avoid an annoying data race */ func RunFixtureInParallel(description string, callback func(proc int)) bool { suites := make([]*internal.Suite, conf.ParallelTotal) finished := make(chan bool, conf.ParallelTotal) for proc := 1; proc <= conf.ParallelTotal; proc++ { suites[proc-1] = internal.NewSuite() WithSuite(suites[proc-1], func() { callback(proc) Ω(suites[proc-1].BuildTree()).Should(Succeed()) }) } for proc := 1; proc <= conf.ParallelTotal; proc++ { proc := proc c := conf //make a copy c.ParallelProcess = proc exit := exitChannels[proc] suite := suites[proc-1] go func() { interruptHandler := interrupt_handler.NewInterruptHandler(client) defer interruptHandler.Stop() success, _ := suite.Run(fmt.Sprintf("%s - %d", description, proc), Label("TopLevelLabel"), "/path/to/suite", failer, reporter, writer, outputInterceptor, interruptHandler, client, noopProgressSignalRegistrar, c) close(exit) finished <- success }() } success := true for proc := 1; proc <= conf.ParallelTotal; proc++ { success = (<-finished) && success } return success } func F(options ...interface{}) { location := cl message := "fail" for _, option := range options { if reflect.TypeOf(option).Kind() == reflect.String { message = option.(string) } else if reflect.TypeOf(option) == reflect.TypeOf(cl) { location = option.(types.CodeLocation) } } failer.Fail(message, location) panic("panic to simulate how ginkgo's Fail works") } func Abort(options ...interface{}) { location := cl message := "abort" for _, option := range options { if reflect.TypeOf(option).Kind() == reflect.String { message = option.(string) } else if reflect.TypeOf(option) == reflect.TypeOf(cl) { location = option.(types.CodeLocation) } } failer.AbortSuite(message, location) panic("panic to simulate how ginkgo's AbortSuite works") } func FixtureSkip(options ...interface{}) { location := cl message := "skip" for _, option := range options { if reflect.TypeOf(option).Kind() == reflect.String { message = option.(string) } else if reflect.TypeOf(option) == reflect.TypeOf(cl) { location = option.(types.CodeLocation) } } failer.Skip(message, location) panic("panic to simulate how ginkgo's Skip works") } func HaveHighlightedStackLine(cl types.CodeLocation) OmegaMatcher { return ContainElement(WithTransform(func(fc types.FunctionCall) types.CodeLocation { if fc.Highlight { return types.CodeLocation{ FileName: fc.Filename, LineNumber: int(fc.Line), } } return types.CodeLocation{} }, Equal(cl))) } func clLine(offset int) types.CodeLocation { cl := cl cl.LineNumber += offset return cl } golang-github-onsi-ginkgo-v2-2.22.0/internal/internal_integration/interrupt_and_timeout_test.go000066400000000000000000001304001472321612100331350ustar00rootroot00000000000000package internal_integration_test import ( "context" "strconv" "sync" "time" . "github.com/onsi/ginkgo/v2" "github.com/onsi/ginkgo/v2/internal/interrupt_handler" . "github.com/onsi/ginkgo/v2/internal/test_helpers" "github.com/onsi/ginkgo/v2/types" . "github.com/onsi/gomega" ) type TimeMap struct { m map[string]time.Duration lock *sync.Mutex } func NewTimeMap() *TimeMap { return &TimeMap{ m: map[string]time.Duration{}, lock: &sync.Mutex{}, } } func (tm *TimeMap) Set(key string, d time.Duration) { tm.lock.Lock() defer tm.lock.Unlock() tm.m[key] = d } func (tm *TimeMap) Get(key string) time.Duration { tm.lock.Lock() defer tm.lock.Unlock() return tm.m[key] } var _ = Describe("Interrupts and Timeouts", func() { Describe("when it is interrupted in a BeforeSuite", func() { BeforeEach(func() { success, _ := RunFixture("interrupted test", func() { BeforeSuite(rt.T("before-suite", func() { interruptHandler.Interrupt(interrupt_handler.InterruptCauseSignal) time.Sleep(time.Hour) })) AfterSuite(rt.T("after-suite")) It("A", rt.T("A")) It("B", rt.T("B")) }) Ω(success).Should(Equal(false)) }) It("runs the AfterSuite and skips all the tests", func() { Ω(rt).Should(HaveTracked("before-suite", "after-suite")) Ω(reporter.Did.FindByLeafNodeType(types.NodeTypeIt)).Should(BeZero()) }) It("reports the correct failure", func() { summary := reporter.Did.FindByLeafNodeType(types.NodeTypeBeforeSuite) Ω(summary.State).Should(Equal(types.SpecStateInterrupted)) Ω(summary.Failure.Message).Should(ContainSubstring("Interrupted by User")) Ω(summary.Failure.ProgressReport.Message).Should(Equal("{{bold}}This is the Progress Report generated when the interrupt was received:{{/}}")) Ω(summary.Failure.ProgressReport.CurrentNodeType).Should(Equal(types.NodeTypeBeforeSuite)) }) It("emits a progress report", func() { Ω(reporter.ProgressReports).Should(HaveLen(1)) pr := reporter.ProgressReports[0] Ω(pr.Message).Should(ContainSubstring("Interrupted by User")) Ω(pr.Message).Should(ContainSubstring("First interrupt received")) Ω(pr.CurrentNodeType).Should(Equal(types.NodeTypeBeforeSuite)) }) It("reports the correct statistics", func() { Ω(reporter.End).Should(BeASuiteSummary(false, NSpecs(2), NWillRun(2), NPassed(0), NSkipped(0), NFailed(0))) }) It("reports the correct special failure reason", func() { Ω(reporter.End.SpecialSuiteFailureReasons).Should(ContainElement("Interrupted by User")) }) }) Describe("when aborted", func() { BeforeEach(func() { success, _ := RunFixture(CurrentSpecReport().LeafNodeText, func() { BeforeEach(rt.T("bef-outer")) Context("nested", func() { BeforeEach(rt.T("bef-inner")) It("A", rt.T("A", func() { interruptHandler.Interrupt(interrupt_handler.InterruptCauseAbortByOtherProcess) time.Sleep(time.Hour) })) It("B", rt.T("B")) AfterEach(rt.T("aft-inner")) }) AfterEach(rt.T("aft-outer")) }) Ω(success).Should(Equal(false)) }) It("interrupts the current spec and doesn't run any others", func() { Ω(rt).Should(HaveTracked("bef-outer", "bef-inner", "A", "aft-inner", "aft-outer")) Ω(reporter.Did.Find("A")).Should(HaveBeenInterrupted(interrupt_handler.InterruptCauseAbortByOtherProcess)) Ω(reporter.Did.Find("B")).Should(HaveBeenSkipped()) }) It("does not include a progress report", func() { Ω(reporter.Did.Find("A").Failure.ProgressReport).Should(BeZero()) }) }) Describe("when interrupted in a spec", func() { Context("with no SpecContext", func() { Context("when subsequent nodes exit right away", func() { BeforeEach(func() { success, _ := RunFixture(CurrentSpecReport().LeafNodeText, func() { BeforeEach(rt.T("bef-outer")) Context("nested", func() { BeforeEach(rt.T("bef-inner")) It("A", rt.T("A", func() { interruptHandler.Interrupt(interrupt_handler.InterruptCauseSignal) time.Sleep(time.Hour) })) It("B", rt.T("B")) AfterEach(rt.T("aft-inner")) }) AfterEach(rt.T("aft-outer")) }) Ω(success).Should(Equal(false)) }) It("starts cleaning up immediately and runs no other specs", func() { Ω(rt).Should(HaveTracked("bef-outer", "bef-inner", "A", "aft-inner", "aft-outer")) Ω(reporter.Did.Find("A")).Should(HaveBeenInterrupted(interrupt_handler.InterruptCauseSignal)) Ω(reporter.Did.Find("B")).Should(HaveBeenSkipped()) }) It("includes a ProgressReport in the spec with a full stack trace", func() { Ω(reporter.Did.Find("A").Failure.ProgressReport.OtherGoroutines()).ShouldNot(BeEmpty()) }) It("emits a condensed ProgressReport with a shorter stack trace - note that it does not say anything about a leaked goroutine becuase the grace period is not enforced", func() { Ω(reporter.ProgressReports).Should(HaveLen(1)) pr := reporter.ProgressReports[0] Ω(pr.Message).Should(ContainSubstring("Interrupted by User")) Ω(pr.Message).Should(ContainSubstring("First interrupt received")) Ω(pr.CurrentNodeType).Should(Equal(types.NodeTypeIt)) Ω(pr.OtherGoroutines()).Should(BeEmpty()) }) }) Context("when subsequent nodes get stuck", func() { BeforeEach(func(_ SpecContext) { conf.GracePeriod = time.Millisecond * 100 success, _ := RunFixture(CurrentSpecReport().LeafNodeText, func() { BeforeEach(rt.T("bef-outer")) Context("nested", func() { BeforeEach(rt.T("bef-inner")) It("A", rt.T("A", func() { interruptHandler.Interrupt(interrupt_handler.InterruptCauseSignal) time.Sleep(time.Hour) })) It("B", rt.T("B")) AfterEach(rt.T("aft-inner", func() { time.Sleep(time.Hour) })) }) AfterEach(rt.T("aft-outer")) }) Ω(success).Should(Equal(false)) }, NodeTimeout(time.Second)) It("waits for a grace-period interval, only", func() { Ω(rt).Should(HaveTracked("bef-outer", "bef-inner", "A", "aft-inner", "aft-outer")) Ω(reporter.Did.Find("A")).Should(HaveBeenInterrupted(interrupt_handler.InterruptCauseSignal)) Ω(reporter.Did.Find("B")).Should(HaveBeenSkipped()) Ω(reporter.Did.Find("A").Failure.ProgressReport.CurrentNodeType).Should(Equal(types.NodeTypeIt)) Ω(reporter.ProgressReports).Should(HaveLen(1)) }) }) }) Context("with a SpecContext", func() { Context("when the node exits before the grace period elapses", func() { BeforeEach(func(_ SpecContext) { success, _ := RunFixture(CurrentSpecReport().LeafNodeText, func() { BeforeEach(rt.T("bef-outer")) Context("nested", func() { BeforeEach(rt.T("bef-inner")) It("A", rt.TSC("A", func(c SpecContext) { interruptHandler.Interrupt(interrupt_handler.InterruptCauseSignal) select { case <-c.Done(): Ω(context.Cause(c)).Should(MatchError(interrupt_handler.InterruptCauseSignal.String())) F("bam") case <-time.After(time.Hour): } })) It("B", rt.T("B")) AfterEach(rt.T("aft-inner")) }) AfterEach(rt.T("aft-outer")) }) Ω(success).Should(Equal(false)) }, NodeTimeout(time.Second)) It("cancels the context, captures any subsequent failures, proceeds to clean up and runs no other specs; and it doesn't emit a grace-period related Progress Report", func() { Ω(rt).Should(HaveTracked("bef-outer", "bef-inner", "A", "aft-inner", "aft-outer")) Ω(reporter.Did.Find("A")).Should(HaveBeenInterrupted(interrupt_handler.InterruptCauseSignal)) Ω(reporter.Did.Find("A").Failure.AdditionalFailure).Should(HaveFailed("bam")) Ω(reporter.Did.Find("B")).Should(HaveBeenSkipped()) Ω(reporter.Did.Find("A").Failure.ProgressReport.OtherGoroutines()).ShouldNot(BeEmpty()) Ω(reporter.ProgressReports).Should(HaveLen(1)) pr := reporter.ProgressReports[0] Ω(pr.Message).Should(ContainSubstring("Interrupted by User")) Ω(pr.Message).Should(ContainSubstring("First interrupt received")) Ω(pr.CurrentNodeType).Should(Equal(types.NodeTypeIt)) Ω(pr.OtherGoroutines()).Should(BeEmpty()) }) }) Context("when the node does not exit before the grace period elapses", func() { BeforeEach(func(_ SpecContext) { success, _ := RunFixture(CurrentSpecReport().LeafNodeText, func() { BeforeEach(rt.T("bef-outer")) Context("nested", func() { BeforeEach(rt.T("bef-inner")) It("A", rt.TSC("A", func(c SpecContext) { interruptHandler.Interrupt(interrupt_handler.InterruptCauseSignal) select { case <-c.Done(): Ω(context.Cause(c)).Should(MatchError(interrupt_handler.InterruptCauseSignal.String())) time.Sleep(time.Hour) case <-time.After(time.Hour): } }), GracePeriod(time.Millisecond*100)) It("B", rt.T("B")) AfterEach(rt.T("aft-inner")) }) AfterEach(rt.T("aft-outer")) }) Ω(success).Should(Equal(false)) }, NodeTimeout(time.Second)) It("leaks the goroutine, continues with cleanup", func() { Ω(rt).Should(HaveTracked("bef-outer", "bef-inner", "A", "aft-inner", "aft-outer")) Ω(reporter.Did.Find("A")).Should(HaveBeenInterrupted(interrupt_handler.InterruptCauseSignal)) Ω(reporter.Did.Find("B")).Should(HaveBeenSkipped()) Ω(reporter.Did.Find("A").Failure.ProgressReport.OtherGoroutines()).ShouldNot(BeEmpty()) Ω(reporter.ProgressReports).Should(HaveLen(1)) pr := reporter.ProgressReports[0] Ω(pr.Message).Should(ContainSubstring("Interrupted by User")) Ω(pr.Message).Should(ContainSubstring("First interrupt received")) Ω(pr.CurrentNodeType).Should(Equal(types.NodeTypeIt)) Ω(pr.OtherGoroutines()).Should(BeEmpty()) }) }) Context("when the node does not exit and the spec is interrupted again before the GracePeriod elapses", func() { BeforeEach(func(_ SpecContext) { success, _ := RunFixture(CurrentSpecReport().LeafNodeText, func() { BeforeEach(rt.T("bef-outer")) Context("nested", func() { BeforeEach(rt.T("bef-inner")) It("A", rt.TSC("A", func(c SpecContext) { interruptHandler.Interrupt(interrupt_handler.InterruptCauseSignal) select { case <-c.Done(): Ω(context.Cause(c)).Should(MatchError(interrupt_handler.InterruptCauseSignal.String())) time.Sleep(time.Millisecond * 100) interruptHandler.Interrupt(interrupt_handler.InterruptCauseSignal) time.Sleep(time.Hour) case <-time.After(time.Hour): } }), GracePeriod(time.Minute)) It("B", rt.T("B")) AfterEach(rt.T("aft-inner")) }) AfterEach(rt.T("aft-outer")) ReportAfterEach(func(_ SpecReport) { rt.Run("report-after-each") }) }) Ω(success).Should(Equal(false)) }, NodeTimeout(time.Second)) It("moves on past the goroutine but skips cleanup (since this is the second interrupt)", func() { Ω(rt).Should(HaveTracked("bef-outer", "bef-inner", "A", "report-after-each", "report-after-each")) Ω(reporter.Did.Find("A")).Should(HaveBeenInterrupted(interrupt_handler.InterruptCauseSignal)) Ω(reporter.Did.Find("B")).Should(HaveBeenSkipped()) Ω(reporter.Did.Find("A").Failure.ProgressReport.OtherGoroutines()).ShouldNot(BeEmpty()) Ω(reporter.ProgressReports).Should(HaveLen(2)) pr := reporter.ProgressReports[0] Ω(pr.Message).Should(ContainSubstring("Interrupted by User")) Ω(pr.Message).Should(ContainSubstring("First interrupt received")) Ω(pr.CurrentNodeType).Should(Equal(types.NodeTypeIt)) Ω(pr.OtherGoroutines()).Should(BeEmpty()) pr = reporter.ProgressReports[1] Ω(pr.Message).Should(ContainSubstring("Interrupted by User")) Ω(pr.Message).Should(ContainSubstring("Second interrupt received")) Ω(pr.CurrentNodeType).Should(Equal(types.NodeTypeIt)) Ω(pr.OtherGoroutines()).Should(BeEmpty()) }) }) Context("if subsequent nodes take a SpecContext but don't have a timeout", func() { var times *TimeMap BeforeEach(func(_ SpecContext) { times = NewTimeMap() success, _ := RunFixture(CurrentSpecReport().LeafNodeText, func() { BeforeEach(rt.T("bef-outer")) Context("nested", func() { BeforeEach(rt.T("bef-inner")) It("A", rt.TSC("A", func(c SpecContext) { interruptHandler.Interrupt(interrupt_handler.InterruptCauseSignal) <-c.Done() Ω(context.Cause(c)).Should(MatchError(interrupt_handler.InterruptCauseSignal.String())) })) It("B", rt.T("B")) AfterEach(rt.TSC("aft-inner", func(c SpecContext) { t := time.Now() select { case <-c.Done(): Ω(context.Cause(c)).Should(MatchError("grace period timeout occurred")) times.Set("aft-inner", time.Since(t)) time.Sleep(time.Second) case <-time.After(time.Second): } }), GracePeriod(time.Millisecond*50)) }) AfterEach(rt.T("aft-outer")) }) Ω(success).Should(Equal(false)) }, NodeTimeout(time.Second)) It("waits for a GracePeriod then interrupts, then waits for a grace period again, then leaks and lets the user know a leak occured", func() { Ω(rt).Should(HaveTracked("bef-outer", "bef-inner", "A", "aft-inner", "aft-outer")) Ω(reporter.Did.Find("A")).Should(HaveBeenInterrupted(interrupt_handler.InterruptCauseSignal)) Ω(reporter.Did.Find("B")).Should(HaveBeenSkipped()) Ω(reporter.Did.Find("A").Failure.ProgressReport.OtherGoroutines()).ShouldNot(BeEmpty()) Ω(times.Get("aft-inner")).Should(BeNumerically("~", time.Millisecond*50, time.Millisecond*25)) Ω(reporter.ProgressReports).Should(HaveLen(2)) pr := reporter.ProgressReports[0] Ω(pr.Message).Should(ContainSubstring("Interrupted by User")) Ω(pr.Message).Should(ContainSubstring("First interrupt received")) Ω(pr.CurrentNodeType).Should(Equal(types.NodeTypeIt)) Ω(pr.OtherGoroutines()).Should(BeEmpty()) pr = reporter.ProgressReports[1] Ω(pr.Message).Should(ContainSubstring("A running node failed to exit in time")) Ω(pr.CurrentNodeType).Should(Equal(types.NodeTypeAfterEach)) Ω(pr.OtherGoroutines()).Should(BeEmpty()) }) }) Context("if subsequent nodes take a SpecContext and have a timeout", func() { var times *TimeMap BeforeEach(func(_ SpecContext) { times = NewTimeMap() success, _ := RunFixture(CurrentSpecReport().LeafNodeText, func() { var t time.Time BeforeEach(rt.T("bef-outer", func() { t = time.Now() })) Context("nested", func() { BeforeEach(rt.T("bef-inner")) It("A", rt.TSC("A", func(c SpecContext) { interruptHandler.Interrupt(interrupt_handler.InterruptCauseSignal) <-c.Done() Ω(context.Cause(c)).Should(MatchError(interrupt_handler.InterruptCauseSignal.String())) })) It("B", rt.T("B")) AfterEach(rt.TSC("aft-inner", func(c SpecContext) { select { case <-c.Done(): Ω(context.Cause(c)).Should(MatchError("node timeout occurred")) times.Set("aft-inner", time.Since(t)) time.Sleep(time.Second) case <-time.After(time.Second): } }), NodeTimeout(time.Millisecond*100), GracePeriod(time.Millisecond*50)) }) AfterEach(rt.T("aft-outer", func() { times.Set("aft-outer", time.Since(t)) })) }) Ω(success).Should(Equal(false)) }, NodeTimeout(time.Second)) It("waits for the timeout and then the grace period", func() { Ω(rt).Should(HaveTracked("bef-outer", "bef-inner", "A", "aft-inner", "aft-outer")) Ω(reporter.Did.Find("A")).Should(HaveBeenInterrupted(interrupt_handler.InterruptCauseSignal)) Ω(reporter.Did.Find("B")).Should(HaveBeenSkipped()) Ω(reporter.Did.Find("A").Failure.ProgressReport.OtherGoroutines()).ShouldNot(BeEmpty()) Ω(times.Get("aft-inner")).Should(BeNumerically("~", time.Millisecond*100, time.Millisecond*50)) Ω(times.Get("aft-outer")).Should(BeNumerically("~", times.Get("aft-inner")+time.Millisecond*50, time.Millisecond*25)) Ω(reporter.ProgressReports).Should(HaveLen(2)) pr := reporter.ProgressReports[0] Ω(pr.Message).Should(ContainSubstring("Interrupted by User")) Ω(pr.Message).Should(ContainSubstring("First interrupt received")) Ω(pr.CurrentNodeType).Should(Equal(types.NodeTypeIt)) Ω(pr.OtherGoroutines()).Should(BeEmpty()) pr = reporter.ProgressReports[1] Ω(pr.Message).Should(ContainSubstring("A running node failed to exit in time")) Ω(pr.CurrentNodeType).Should(Equal(types.NodeTypeAfterEach)) Ω(pr.OtherGoroutines()).Should(BeEmpty()) }) }) }) Context("when interrupted twice", func() { BeforeEach(func(_ SpecContext) { success, _ := RunFixture(CurrentSpecReport().LeafNodeText, func() { BeforeEach(rt.T("bef-outer")) Context("nested", func() { BeforeEach(rt.T("bef-inner")) It("A", rt.TSC("A", func(c SpecContext) { interruptHandler.Interrupt(interrupt_handler.InterruptCauseSignal) <-c.Done() Ω(context.Cause(c)).Should(MatchError(interrupt_handler.InterruptCauseSignal.String())) })) It("B", rt.T("B")) AfterEach(rt.TSC("aft-inner", func(c SpecContext) { interruptHandler.Interrupt(interrupt_handler.InterruptCauseSignal) <-c.Done() Ω(context.Cause(c)).Should(MatchError(interrupt_handler.InterruptCauseSignal.String())) })) }) AfterEach(rt.T("aft-outer")) ReportAfterEach(func(_ SpecReport) { rt.Run("report-after-each") }) ReportAfterSuite("Report After Suite", func(_ Report) { rt.Run("report-after-suite") }) AfterSuite(rt.T("after-suite")) }) Ω(success).Should(Equal(false)) }, NodeTimeout(time.Second)) It("bails out on future cleanup nodes, but runs reporting nodes", func() { Ω(rt).Should(HaveTracked("bef-outer", "bef-inner", "A", "aft-inner", "report-after-each", "report-after-each", "report-after-suite")) Ω(reporter.Did.Find("A")).Should(HaveBeenInterrupted(interrupt_handler.InterruptCauseSignal)) Ω(reporter.Did.Find("B")).Should(HaveBeenSkipped()) Ω(reporter.Did.Find("A").Failure.ProgressReport.OtherGoroutines()).ShouldNot(BeEmpty()) Ω(reporter.Did.Find("A").Failure.ProgressReport.CurrentNodeType).Should(Equal(types.NodeTypeIt)) Ω(reporter.ProgressReports).Should(HaveLen(2)) pr := reporter.ProgressReports[0] Ω(pr.Message).Should(ContainSubstring("Interrupted by User")) Ω(pr.Message).Should(ContainSubstring("First interrupt received")) Ω(pr.CurrentNodeType).Should(Equal(types.NodeTypeIt)) Ω(pr.OtherGoroutines()).Should(BeEmpty()) pr = reporter.ProgressReports[1] Ω(pr.Message).Should(ContainSubstring("Interrupted by User")) Ω(pr.Message).Should(ContainSubstring("Second interrupt received")) Ω(pr.CurrentNodeType).Should(Equal(types.NodeTypeAfterEach)) Ω(pr.OtherGoroutines()).Should(BeEmpty()) }) }) Context("when interrupted three times", func() { BeforeEach(func(_ SpecContext) { success, _ := RunFixture(CurrentSpecReport().LeafNodeText, func() { BeforeEach(rt.T("bef-outer")) Context("nested", func() { BeforeEach(rt.T("bef-inner")) It("A", rt.TSC("A", func(c SpecContext) { interruptHandler.Interrupt(interrupt_handler.InterruptCauseSignal) <-c.Done() Ω(context.Cause(c)).Should(MatchError(interrupt_handler.InterruptCauseSignal.String())) })) It("B", rt.T("B")) AfterEach(rt.TSC("aft-inner", func(c SpecContext) { interruptHandler.Interrupt(interrupt_handler.InterruptCauseSignal) <-c.Done() Ω(context.Cause(c)).Should(MatchError(interrupt_handler.InterruptCauseSignal.String())) })) }) AfterEach(rt.T("aft-outer")) ReportAfterEach(func(_ SpecReport) { rt.Run("report-after-each") interruptHandler.Interrupt(interrupt_handler.InterruptCauseSignal) time.Sleep(time.Hour) }) ReportAfterEach(func(_ SpecReport) { rt.Run("report-after-each-2") }) ReportAfterSuite("Report After Suite", func(_ Report) { rt.Run("report-after-suite") }) AfterSuite(rt.T("after-suite")) }) Ω(success).Should(Equal(false)) }, NodeTimeout(time.Second)) It("bails out on everything and just exits ASAP", func() { Ω(rt).Should(HaveTracked("bef-outer", "bef-inner", "A", "aft-inner", "report-after-each")) Ω(reporter.Did.Find("A")).Should(HaveBeenInterrupted(interrupt_handler.InterruptCauseSignal)) Ω(reporter.Did.Find("B")).Should(HaveBeenSkipped()) Ω(reporter.Did.Find("A").Failure.ProgressReport.OtherGoroutines()).ShouldNot(BeEmpty()) Ω(reporter.Did.Find("A").Failure.ProgressReport.CurrentNodeType).Should(Equal(types.NodeTypeIt)) Ω(reporter.ProgressReports).Should(HaveLen(3)) pr := reporter.ProgressReports[0] Ω(pr.Message).Should(ContainSubstring("Interrupted by User")) Ω(pr.Message).Should(ContainSubstring("First interrupt received")) Ω(pr.CurrentNodeType).Should(Equal(types.NodeTypeIt)) Ω(pr.OtherGoroutines()).Should(BeEmpty()) pr = reporter.ProgressReports[1] Ω(pr.Message).Should(ContainSubstring("Interrupted by User")) Ω(pr.Message).Should(ContainSubstring("Second interrupt received")) Ω(pr.CurrentNodeType).Should(Equal(types.NodeTypeAfterEach)) Ω(pr.OtherGoroutines()).Should(BeEmpty()) pr = reporter.ProgressReports[2] Ω(pr.Message).Should(ContainSubstring("Interrupted by User")) Ω(pr.Message).Should(ContainSubstring("Final interrupt received")) Ω(pr.CurrentNodeType).Should(Equal(types.NodeTypeReportAfterEach)) Ω(pr.OtherGoroutines()).Should(BeEmpty()) }) }) Context("when interrupted in a BeforeEach", func() { BeforeEach(func(_ SpecContext) { success, _ := RunFixture(CurrentSpecReport().LeafNodeText, func() { BeforeEach(rt.T("bef-outer")) Context("nested", func() { BeforeEach(rt.TSC("bef-inner", func(c SpecContext) { interruptHandler.Interrupt(interrupt_handler.InterruptCauseSignal) <-c.Done() Ω(context.Cause(c)).Should(MatchError(interrupt_handler.InterruptCauseSignal.String())) })) Context("even more nested", func() { BeforeEach(rt.T("bef-even-innerer")) It("A", rt.T("A")) AfterEach(rt.T("aft-even-innerer")) ReportAfterEach(func(_ SpecReport) { rt.Run("report-after-each-even-innerer") }) }) AfterEach(rt.T("aft-inner")) ReportAfterEach(func(_ SpecReport) { rt.Run("report-after-each-inner") }) }) AfterEach(rt.T("aft-outer")) ReportAfterEach(func(_ SpecReport) { rt.Run("report-after-each-outer") }) ReportAfterSuite("Report After Suite", func(_ Report) { rt.Run("report-after-suite") }) AfterSuite(rt.T("after-suite")) }) Ω(success).Should(Equal(false)) }, NodeTimeout(time.Second)) It("only cleans up at the matching nesting level", func() { Ω(rt).Should(HaveTracked("bef-outer", "bef-inner", "aft-inner", "aft-outer", "report-after-each-even-innerer", "report-after-each-inner", "report-after-each-outer", "after-suite", "report-after-suite")) Ω(reporter.Did.Find("A")).Should(HaveBeenInterrupted(interrupt_handler.InterruptCauseSignal)) Ω(reporter.Did.Find("A").Failure.ProgressReport.OtherGoroutines()).ShouldNot(BeEmpty()) Ω(reporter.Did.Find("A").Failure.ProgressReport.CurrentNodeType).Should(Equal(types.NodeTypeBeforeEach)) Ω(reporter.ProgressReports).Should(HaveLen(1)) pr := reporter.ProgressReports[0] Ω(pr.Message).Should(ContainSubstring("Interrupted by User")) Ω(pr.Message).Should(ContainSubstring("First interrupt received")) Ω(pr.CurrentNodeType).Should(Equal(types.NodeTypeBeforeEach)) Ω(pr.OtherGoroutines()).Should(BeEmpty()) }) }) Context("when a timeout has already occured", func() { BeforeEach(func() { success, _ := RunFixture(CurrentSpecReport().LeafNodeText, func() { It("A", func(c SpecContext) { rt.Run("A") time.Sleep(time.Millisecond * 100) interruptHandler.Interrupt(interrupt_handler.InterruptCauseSignal) time.Sleep(time.Second) }, NodeTimeout(time.Millisecond*10)) AfterEach(rt.T("aft-1")) }) Ω(success).Should(Equal(false)) }) It("does not overwrite the timeout", func() { Ω(rt).Should(HaveTracked("A", "aft-1")) Ω(reporter.Did.Find("A")).Should(HaveTimedOut()) }) }) Context("when a failure has already occured", func() { BeforeEach(func() { success, _ := RunFixture(CurrentSpecReport().LeafNodeText, func() { It("A", rt.T("A", func() { F("boom") })) AfterEach(rt.T("aft-1", func() { interruptHandler.Interrupt(interrupt_handler.InterruptCauseSignal) })) AfterEach(rt.T("aft-2")) }) Ω(success).Should(Equal(false)) }) It("does not overwrite the failure", func() { Ω(rt).Should(HaveTracked("A", "aft-1", "aft-2")) Ω(reporter.Did.Find("A")).Should(HaveFailed("boom")) Ω(reporter.Did.Find("A").Failure.ProgressReport).Should(BeZero()) }) }) }) Describe("when a node times out", func() { var times *TimeMap BeforeEach(func() { times = NewTimeMap() conf.GracePeriod = time.Millisecond * 200 success, _ := RunFixture(CurrentSpecReport().LeafNodeText, func() { Context("container", func() { var t time.Time BeforeEach(rt.T("bef", func() { t = time.Now() })) Describe("when it exits in time", func() { It("A", rt.TSC("A", func(c SpecContext) { <-c.Done() Ω(context.Cause(c)).Should(MatchError("node timeout occurred")) rt.Run("A-cancelled") Fail("subsequent failure message") }), NodeTimeout(time.Millisecond*100)) }) Describe("with no configured grace period", func() { It("B", rt.TSC("B", func(c SpecContext) { <-c.Done() Ω(context.Cause(c)).Should(MatchError("node timeout occurred")) time.Sleep(time.Hour) }), NodeTimeout(time.Millisecond*100)) }) Describe("with a configured grace period", func() { It("C", rt.TSC("C", func(c SpecContext) { <-c.Done() Ω(context.Cause(c)).Should(MatchError("node timeout occurred")) time.Sleep(time.Hour) }), NodeTimeout(time.Millisecond*100), GracePeriod(time.Millisecond*50)) }) It("D", rt.T("D")) It("E", rt.T("E", func() { F("boom") })) AfterEach(rt.T("aft", func() { times.Set(CurrentSpecReport().LeafNodeText, time.Since(t)) })) }) }) Ω(success).Should(Equal(false)) }) It("runs all the specs - a node timeout is just a failure, not an interrupt", func() { Ω(rt).Should(HaveTracked( "bef", "A", "A-cancelled", "aft", "bef", "B", "aft", "bef", "C", "aft", "bef", "D", "aft", "bef", "E", "aft", )) Ω(reporter.Did.Find("A")).Should(HaveTimedOut("A node timeout occurred")) Ω(reporter.Did.Find("A").Failure.AdditionalFailure).Should(HaveFailed("A node timeout occurred and then the following failure was recorded in the timedout node before it exited:\nsubsequent failure message")) Ω(reporter.Did.Find("B")).Should(HaveTimedOut()) Ω(reporter.Did.Find("C")).Should(HaveTimedOut()) Ω(reporter.Did.Find("D")).Should(HavePassed()) Ω(reporter.Did.Find("E")).Should(HaveFailed()) }) It("times out after the configured NodeTimeout", func() { Ω(times.Get("A")).Should(BeNumerically("~", 100*time.Millisecond, 50*time.Millisecond)) }) It("waits for the grace-period if the node doesn't exit in time", func() { Ω(times.Get("B")).Should(BeNumerically("~", 300*time.Millisecond, 50*time.Millisecond)) Ω(times.Get("C")).Should(BeNumerically("~", 150*time.Millisecond, 50*time.Millisecond)) }) It("attaches progress reports to the timout failures", func() { Ω(reporter.Did.Find("A").Failure.ProgressReport.LeafNodeText).Should(Equal("A")) Ω(reporter.Did.Find("A").Failure.ProgressReport.Message).Should(Equal("{{bold}}This is the Progress Report generated when the node timeout occurred:{{/}}")) Ω(reporter.Did.Find("B").Failure.ProgressReport.LeafNodeText).Should(Equal("B")) Ω(reporter.Did.Find("C").Failure.ProgressReport.LeafNodeText).Should(Equal("C")) Ω(reporter.Did.Find("D").Failure.ProgressReport).Should(BeZero()) Ω(reporter.Did.Find("E").Failure.ProgressReport).Should(BeZero()) }) It("emits progress reports when it has to leak a goroutine", func() { Ω(reporter.ProgressReports).Should(HaveLen(2)) Ω(reporter.ProgressReports[0].Message).Should(ContainSubstring("A running node failed to exit in time")) Ω(reporter.ProgressReports[0].LeafNodeText).Should(Equal("B")) Ω(reporter.ProgressReports[1].Message).Should(ContainSubstring("A running node failed to exit in time")) Ω(reporter.ProgressReports[1].LeafNodeText).Should(Equal("C")) }) }) Describe("when a BeforeSuite/AfterSuite node times out", func() { var times *TimeMap BeforeEach(func(_ SpecContext) { times = NewTimeMap() var t time.Time success, _ := RunFixture(CurrentSpecReport().LeafNodeText, func() { SynchronizedBeforeSuite(func() []byte { t = time.Now() rt.Run("befs-proc-1") return []byte("befs-all-proc") }, func(c SpecContext, b []byte) { rt.Run(string(b)) <-c.Done() Ω(context.Cause(c)).Should(MatchError("node timeout occurred")) times.Set(string(b), time.Since(t)) }, NodeTimeout(time.Millisecond*100)) It("A", rt.T("A")) SynchronizedAfterSuite(func() { rt.Run("afts-all-proc") }, func(c SpecContext) { rt.Run("afts-proc-1") <-c.Done() Ω(context.Cause(c)).Should(MatchError("node timeout occurred")) times.Set("afts-proc-1", time.Since(t)) }, NodeTimeout(time.Millisecond*200)) }) Ω(success).Should(Equal(false)) }, NodeTimeout(time.Second*5)) It("marks the nodes as timed out", func() { Ω(rt).Should(HaveTracked("befs-proc-1", "befs-all-proc", "afts-all-proc", "afts-proc-1")) Ω(reporter.Did.FindByLeafNodeType(types.NodeTypeSynchronizedBeforeSuite)).Should(HaveTimedOut()) Ω(reporter.Did.FindByLeafNodeType(types.NodeTypeSynchronizedBeforeSuite).Failure.ProgressReport.CurrentNodeType).Should(Equal(types.NodeTypeSynchronizedBeforeSuite)) Ω(reporter.Did.Find("A")).Should(BeZero()) Ω(reporter.Did.FindByLeafNodeType(types.NodeTypeSynchronizedAfterSuite)).Should(HaveTimedOut()) Ω(reporter.Did.FindByLeafNodeType(types.NodeTypeSynchronizedAfterSuite).Failure.ProgressReport.CurrentNodeType).Should(Equal(types.NodeTypeSynchronizedAfterSuite)) Ω(times.Get("befs-all-proc")).Should(BeNumerically("~", time.Millisecond*100, time.Millisecond*50)) Ω(times.Get("afts-proc-1")).Should(BeNumerically("~", times.Get("befs-all-proc")+time.Millisecond*200, time.Millisecond*50)) }) }) Describe("when a (spec or suite) timeout elapses and the node has no SpecContext", func() { var times *TimeMap BeforeEach(func(_ SpecContext) { times = NewTimeMap() conf.Timeout = time.Millisecond * 200 conf.GracePeriod = time.Millisecond * 100 success, _ := RunFixture(CurrentSpecReport().LeafNodeText, func() { Context("container", func() { var t time.Time BeforeEach(rt.T("bef", func() { t = time.Now() })) It("A", rt.T("A", func() { time.Sleep(time.Hour) })) AfterEach(rt.TSC("aft-1", func(c SpecContext) { times.Set("A", time.Since(t)) <-c.Done() Ω(context.Cause(c)).Should(MatchError("grace period timeout occurred")) times.Set("aft-1-cancel", time.Since(t)) writer.Println("aft-1") time.Sleep(time.Hour) })) AfterEach(rt.TSC("aft-2", func(c SpecContext) { times.Set("aft-1-out", time.Since(t)) <-c.Done() Ω(context.Cause(c)).Should(MatchError("grace period timeout occurred")) times.Set("aft-2-cancel", time.Since(t)) writer.Println("aft-2") time.Sleep(time.Hour) }), GracePeriod(time.Millisecond*50)) AfterEach(rt.TSC("aft-3", func(c SpecContext) { times.Set("aft-2-out", time.Since(t)) <-c.Done() Ω(context.Cause(c)).Should(MatchError("node timeout occurred")) times.Set("aft-3-cancel", time.Since(t)) writer.Println("aft-3") time.Sleep(time.Hour) }), GracePeriod(time.Millisecond*50), NodeTimeout(time.Millisecond*150)) AfterEach(rt.T("aft-4", func() { times.Set("aft-3-out", time.Since(t)) time.Sleep(time.Hour) })) AfterEach(rt.T("aft-5", func() { times.Set("aft-4-out", time.Since(t)) })) }) }) Ω(success).Should(Equal(false)) }, NodeTimeout(time.Second*5)) It("timesout the node in question and proceeds with other nodes without waiting; subsequent nodes are subject to the timeout if present, otherwise the grace period", func() { Ω(rt).Should(HaveTracked("bef", "A", "aft-1", "aft-2", "aft-3", "aft-4", "aft-5")) dt := 50 * time.Millisecond gracePeriod := 100 * time.Millisecond Ω(times.Get("A")).Should(BeNumerically("~", 200*time.Millisecond, dt)) Ω(times.Get("aft-1-cancel")).Should(BeNumerically("~", times.Get("A")+gracePeriod, dt)) Ω(times.Get("aft-1-out")).Should(BeNumerically("~", times.Get("aft-1-cancel")+gracePeriod, dt)) Ω(times.Get("aft-2-cancel")).Should(BeNumerically("~", times.Get("aft-1-out")+50*time.Millisecond, dt)) Ω(times.Get("aft-2-out")).Should(BeNumerically("~", times.Get("aft-2-cancel")+50*time.Millisecond, dt)) Ω(times.Get("aft-3-cancel")).Should(BeNumerically("~", times.Get("aft-2-out")+150*time.Millisecond, dt)) Ω(times.Get("aft-3-out")).Should(BeNumerically("~", times.Get("aft-3-cancel")+50*time.Millisecond, dt)) Ω(times.Get("aft-4-out")).Should(BeNumerically("~", times.Get("aft-3-out")+gracePeriod, dt)) Ω(reporter.Did.Find("A")).Should(HaveTimedOut("A suite timeout occurred")) Ω(reporter.Did.Find("A").Failure.ProgressReport.LeafNodeText).Should(Equal("A")) Ω(reporter.ProgressReports).Should(HaveLen(3)) Ω(reporter.ProgressReports[0].Message).Should(ContainSubstring("A running node failed to exit in time")) Ω(reporter.ProgressReports[0].CapturedGinkgoWriterOutput).Should(Equal("aft-1\n")) Ω(reporter.ProgressReports[1].Message).Should(ContainSubstring("A running node failed to exit in time")) Ω(reporter.ProgressReports[1].CapturedGinkgoWriterOutput).Should(Equal("aft-1\naft-2\n")) Ω(reporter.ProgressReports[2].Message).Should(ContainSubstring("A running node failed to exit in time")) Ω(reporter.ProgressReports[2].CapturedGinkgoWriterOutput).Should(Equal("aft-1\naft-2\naft-3\n")) }) }) Describe("the interaction of suite, spec, and node timeouts", func() { var times *TimeMap BeforeEach(func(_ SpecContext) { times = NewTimeMap() conf.Timeout = time.Millisecond * 450 success, _ := RunFixture(CurrentSpecReport().LeafNodeText, func() { Context("container", func() { Context("when the node timeout is shortest", func() { BeforeEach(rt.TSC("bef-A", func(c SpecContext) { t := time.Now() <-c.Done() Ω(context.Cause(c)).Should(MatchError("node timeout occurred")) times.Set("bef-A", time.Since(t)) }), NodeTimeout(time.Millisecond*100)) It("A", rt.TSC("A", func(c SpecContext) {}), SpecTimeout(time.Millisecond*200)) }) Context("when the spec timeout is shortest", func() { BeforeEach(rt.TSC("bef-B", func(c SpecContext) { t := time.Now() <-c.Done() Ω(context.Cause(c)).Should(MatchError("spec timeout occurred")) times.Set("bef-B", time.Since(t)) }), NodeTimeout(time.Millisecond*250)) It("B", rt.TSC("B", func(c SpecContext) {}), SpecTimeout(time.Millisecond*150)) }) Context("when the suite timeout is the shortest", func() { BeforeEach(rt.TSC("bef-C", func(c SpecContext) { t := time.Now() <-c.Done() Ω(context.Cause(c)).Should(MatchError("suite timeout occurred")) times.Set("bef-C", time.Since(t)) }), NodeTimeout(time.Millisecond*300)) It("C", rt.TSC("C", func(c SpecContext) {}), SpecTimeout(time.Second)) }) }) }) Ω(success).Should(Equal(false)) }, NodeTimeout(time.Second*5)) It("should always favor the shorter timeout", func() { Ω(rt).Should(HaveTracked("bef-A", "bef-B", "bef-C")) Ω(reporter.Did.Find("A")).Should(HaveTimedOut("A node timeout occurred")) Ω(reporter.Did.Find("B")).Should(HaveTimedOut("A spec timeout occurred")) Ω(reporter.Did.Find("C")).Should(HaveTimedOut("A suite timeout occurred")) Ω(times.Get("bef-A")).Should(BeNumerically("~", time.Millisecond*100, 50*time.Millisecond)) Ω(times.Get("bef-B")).Should(BeNumerically("~", time.Millisecond*150, 50*time.Millisecond)) Ω(times.Get("bef-C")).Should(BeNumerically("~", time.Millisecond*200, 50*time.Millisecond)) Ω(reporter.End.SpecialSuiteFailureReasons).Should(Equal([]string{"Suite Timeout Elapsed"})) }) }) Describe("using timeouts with Gomega's Eventually", func() { BeforeEach(func(ctx SpecContext) { success, _ := RunFixture(CurrentSpecReport().LeafNodeText, func() { Context("container", func() { It("A", rt.TSC("A", func(c SpecContext) { cl = types.NewCodeLocation(0) Eventually(func() string { return "foo" }).WithTimeout(time.Hour).WithContext(c).Should(Equal("bar")) rt.Run("A-dont-see") //never see this because Eventually fails which panics and ends execution }), SpecTimeout(time.Millisecond*200)) }) }) Ω(success).Should(Equal(false)) }, NodeTimeout(time.Second)) It("doesn't get stuck because Eventually will exit and it includes the additional report provided by eventually", func() { Ω(rt).Should(HaveTracked("A")) Ω(reporter.Did.Find("A")).Should(HaveTimedOut(clLine(-1))) Ω(reporter.Did.Find("A")).Should(HaveTimedOut(`A spec timeout occurred`)) Ω(reporter.Did.Find("A").Failure.AdditionalFailure).Should(HaveFailed(MatchRegexp("A spec timeout occurred and then the following failure was recorded in the timedout node before it exited:\nContext was cancelled \\(cause: spec timeout occurred\\) after .*\nExpected\n : foo\nto equal\n : bar"), clLine(1))) Ω(reporter.Did.Find("A").Failure.ProgressReport.Message).Should(Equal("{{bold}}This is the Progress Report generated when the spec timeout occurred:{{/}}")) Ω(reporter.Did.Find("A").Failure.ProgressReport.AdditionalReports).Should(ConsistOf("Expected\n : foo\nto equal\n : bar")) }) }) Describe("when a suite timeout occurs", func() { BeforeEach(func(_ SpecContext) { conf.Timeout = time.Millisecond * 100 success, _ := RunFixture(CurrentSpecReport().LeafNodeText, func() { Context("container", func() { It("A", rt.TSC("A", func(c SpecContext) { <-c.Done() Ω(context.Cause(c)).Should(MatchError("suite timeout occurred")) })) It("B", rt.T("B")) It("C", rt.TSC("C")) AfterEach(rt.T("aft-each")) }) AfterSuite(rt.T("aft-suite")) }) Ω(success).Should(Equal(false)) }, NodeTimeout(time.Second*5)) It("skips all subsequent specs", func() { Ω(rt).Should(HaveTracked("A", "aft-each", "aft-suite")) Ω(reporter.Did.Find("A")).Should(HaveTimedOut()) Ω(reporter.Did.Find("B")).Should(HaveBeenSkipped()) Ω(reporter.Did.Find("C")).Should(HaveBeenSkipped()) Ω(reporter.End.SpecialSuiteFailureReasons).Should(Equal([]string{"Suite Timeout Elapsed"})) }) }) Describe("passing contexts to DeferCleanups", func() { var times *TimeMap BeforeEach(func() { times = NewTimeMap() success, _ := RunFixture(CurrentSpecReport().LeafNodeText, func() { It("A", rt.T("A", func() { DeferCleanup(func(c context.Context, key string) { rt.Run(key) t := time.Now() <-c.Done() Ω(context.Cause(c)).Should(MatchError("node timeout occurred")) times.Set(key, time.Since(t)) }, NodeTimeout(time.Millisecond*100), "dc-1") DeferCleanup(func(c SpecContext, key string) { rt.Run(key) t := time.Now() <-c.Done() Ω(context.Cause(c)).Should(MatchError("node timeout occurred")) times.Set(key, time.Since(t)) }, NodeTimeout(time.Millisecond*100), "dc-2") DeferCleanup(func(c SpecContext, d context.Context, key string) { key = d.Value("key").(string) + key rt.Run(key) t := time.Now() <-c.Done() Ω(context.Cause(c)).Should(MatchError("node timeout occurred")) times.Set(key, time.Since(t)) }, NodeTimeout(time.Millisecond*100), context.WithValue(context.Background(), "key", "dc"), "-3") DeferCleanup(func(d context.Context, key string) { key = d.Value("key").(string) + key rt.Run(key) }, context.WithValue(context.Background(), "key", "dc"), "-4") })) }) Ω(success).Should(Equal(false)) }) It("should work", func() { Ω(rt).Should(HaveTracked("A", "dc-4", "dc-3", "dc-2", "dc-1")) Ω(reporter.Did.Find("A")).Should(HaveTimedOut()) Ω(reporter.Did.Find("A").Failure.ProgressReport.CurrentNodeType).Should(Equal(types.NodeTypeCleanupAfterEach)) Ω(times.Get("dc-1")).Should(BeNumerically("~", 100*time.Millisecond, 50*time.Millisecond)) Ω(times.Get("dc-2")).Should(BeNumerically("~", 100*time.Millisecond, 50*time.Millisecond)) Ω(times.Get("dc-3")).Should(BeNumerically("~", 100*time.Millisecond, 50*time.Millisecond)) }) }) Describe("passing contexts to TableEntries", func() { Describe("the happy path", func() { var times *TimeMap BeforeEach(func() { times = NewTimeMap() success, _ := RunFixture(CurrentSpecReport().LeafNodeText, func() { Context("container", func() { DescribeTable("timeout table", func(c SpecContext, d context.Context, key string) { key = d.Value("key").(string) + key rt.Run(CurrentSpecReport().LeafNodeText) t := time.Now() <-c.Done() times.Set(key, time.Since(t)) }, func(d context.Context, key string) string { key = d.Value("key").(string) + key return key }, Entry(nil, context.WithValue(context.Background(), "key", "entry-"), "1", NodeTimeout(time.Millisecond)*100), Entry(nil, context.WithValue(context.Background(), "key", "entry-"), "2", SpecTimeout(time.Millisecond)*150), ) DescribeTable("timeout table", func(c context.Context, key string) { rt.Run(CurrentSpecReport().LeafNodeText) t := time.Now() <-c.Done() times.Set(key, time.Since(t)) }, func(key string) string { return key }, Entry(nil, "entry-3", NodeTimeout(time.Millisecond)*100), Entry(nil, "entry-4", SpecTimeout(time.Millisecond)*150), ) DescribeTable("timeout table", func(c context.Context, key string) { key = c.Value("key").(string) + key rt.Run(CurrentSpecReport().LeafNodeText + "-" + key) }, func(d context.Context, key string) string { key = d.Value("key").(string) + key return key }, Entry(nil, context.WithValue(context.Background(), "key", "entry-"), "5"), Entry(nil, context.WithValue(context.Background(), "key", "entry-"), "6"), ) }) }) Ω(success).Should(Equal(false)) }) It("should work", func() { Ω(rt).Should(HaveTracked("entry-1", "entry-2", "entry-3", "entry-4", "entry-5-entry-5", "entry-6-entry-6")) Ω(reporter.Did.Find("entry-1")).Should(HaveTimedOut()) Ω(reporter.Did.Find("entry-2")).Should(HaveTimedOut()) Ω(reporter.Did.Find("entry-3")).Should(HaveTimedOut()) Ω(reporter.Did.Find("entry-4")).Should(HaveTimedOut()) Ω(reporter.Did.Find("entry-1").Failure.ProgressReport.CurrentNodeType).Should(Equal(types.NodeTypeIt)) Ω(times.Get("entry-1")).Should(BeNumerically("~", 100*time.Millisecond, 50*time.Millisecond)) Ω(times.Get("entry-2")).Should(BeNumerically("~", 150*time.Millisecond, 50*time.Millisecond)) Ω(times.Get("entry-3")).Should(BeNumerically("~", 100*time.Millisecond, 50*time.Millisecond)) Ω(times.Get("entry-4")).Should(BeNumerically("~", 150*time.Millisecond, 50*time.Millisecond)) }) }) Describe("the edge case in #1415", func() { var four = 4 var nSix = -6 DescribeTable("it supports receiving a SpecContext and works with nil parameters", func(ctx context.Context, num *int, s string, cVal string) { Ω(ctx).ShouldNot(BeNil()) if num == nil { Ω(s).Should(Equal("nil")) } else { Ω(s).Should(Equal(strconv.Itoa(*num))) } if cVal != "" { Ω(ctx.Value("key")).Should(Equal(cVal)) } }, Entry("4", &four, "4", ""), Entry("-6", &nSix, "-6", ""), Entry("nil", nil, "nil", ""), Entry("4 with context value", context.WithValue(context.Background(), "key", "val"), &four, "4", "val"), Entry("nil with context value", context.WithValue(context.Background(), "key", "val"), nil, "nil", "val"), ) }) }) }) golang-github-onsi-ginkgo-v2-2.22.0/internal/internal_integration/labels_test.go000066400000000000000000000145551472321612100277670ustar00rootroot00000000000000package internal_integration_test import ( . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" . "github.com/onsi/ginkgo/v2/internal/test_helpers" "github.com/onsi/ginkgo/v2/types" ) var _ = Describe("Labels", func() { Describe("when a suite has labelled tests", func() { fixture := func() { Describe("outer container", func() { It("A", rt.T("A"), Label("cat")) It("B", rt.T("B"), Label("dog")) Describe("container", Label("cow", "cat"), func() { It("C", rt.T("C")) It("D", rt.T("D"), Label("fish", "cat")) }) Describe("other container", Label(" giraffe "), func() { It("E", rt.T("E")) It("F", rt.T("F"), Label("dog")) Describe("inner container", Label("cow"), func() { It("G", rt.T("G"), Pending, Label("fish", "chicken")) It("H", rt.T("H"), Label("fish", "chicken")) }) }) Describe("feature container", Label("Feature:Beta"), func() { It("I", rt.T("I"), Label("Feature: Gamma")) Describe("inner container", Label(" feature : alpha "), func() { It("J", rt.T("J"), Label("Feature:Alpha")) It("K", rt.T("K"), Label("Feature:Delta", "Feature:Beta")) }) }) }) } BeforeEach(func() { conf.LabelFilter = "TopLevelLabel && (dog || cow) || Feature: containsAny Alpha" success, hPF := RunFixture("labelled tests", fixture) Ω(success).Should(BeTrue()) Ω(hPF).Should(BeFalse()) }) It("includes the labels in the spec report", func() { Ω(reporter.Did.Find("A").ContainerHierarchyLabels).Should(Equal([][]string{{}})) Ω(reporter.Did.Find("A").LeafNodeLabels).Should(Equal([]string{"cat"})) Ω(reporter.Did.Find("A").Labels()).Should(Equal([]string{"cat"})) Ω(reporter.Did.Find("B").ContainerHierarchyLabels).Should(Equal([][]string{{}})) Ω(reporter.Did.Find("B").LeafNodeLabels).Should(Equal([]string{"dog"})) Ω(reporter.Did.Find("B").Labels()).Should(Equal([]string{"dog"})) Ω(reporter.Did.Find("C").ContainerHierarchyLabels).Should(Equal([][]string{{}, {"cow", "cat"}})) Ω(reporter.Did.Find("C").LeafNodeLabels).Should(Equal([]string{})) Ω(reporter.Did.Find("C").Labels()).Should(Equal([]string{"cow", "cat"})) Ω(reporter.Did.Find("D").ContainerHierarchyLabels).Should(Equal([][]string{{}, {"cow", "cat"}})) Ω(reporter.Did.Find("D").LeafNodeLabels).Should(Equal([]string{"fish", "cat"})) Ω(reporter.Did.Find("D").Labels()).Should(Equal([]string{"cow", "cat", "fish"})) Ω(reporter.Did.Find("E").ContainerHierarchyLabels).Should(Equal([][]string{{}, {"giraffe"}})) Ω(reporter.Did.Find("E").LeafNodeLabels).Should(Equal([]string{})) Ω(reporter.Did.Find("E").Labels()).Should(Equal([]string{"giraffe"})) Ω(reporter.Did.Find("F").ContainerHierarchyLabels).Should(Equal([][]string{{}, {"giraffe"}})) Ω(reporter.Did.Find("F").LeafNodeLabels).Should(Equal([]string{"dog"})) Ω(reporter.Did.Find("F").Labels()).Should(Equal([]string{"giraffe", "dog"})) Ω(reporter.Did.Find("G").ContainerHierarchyLabels).Should(Equal([][]string{{}, {"giraffe"}, {"cow"}})) Ω(reporter.Did.Find("G").LeafNodeLabels).Should(Equal([]string{"fish", "chicken"})) Ω(reporter.Did.Find("G").Labels()).Should(Equal([]string{"giraffe", "cow", "fish", "chicken"})) Ω(reporter.Did.Find("H").ContainerHierarchyLabels).Should(Equal([][]string{{}, {"giraffe"}, {"cow"}})) Ω(reporter.Did.Find("H").LeafNodeLabels).Should(Equal([]string{"fish", "chicken"})) Ω(reporter.Did.Find("H").Labels()).Should(Equal([]string{"giraffe", "cow", "fish", "chicken"})) Ω(reporter.Did.Find("I").ContainerHierarchyLabels).Should(Equal([][]string{{}, {"Feature:Beta"}})) Ω(reporter.Did.Find("I").LeafNodeLabels).Should(Equal([]string{"Feature: Gamma"})) Ω(reporter.Did.Find("I").Labels()).Should(Equal([]string{"Feature:Beta", "Feature: Gamma"})) Ω(reporter.Did.Find("J").ContainerHierarchyLabels).Should(Equal([][]string{{}, {"Feature:Beta"}, {"feature : alpha"}})) Ω(reporter.Did.Find("J").LeafNodeLabels).Should(Equal([]string{"Feature:Alpha"})) Ω(reporter.Did.Find("J").Labels()).Should(Equal([]string{"Feature:Beta", "feature : alpha", "Feature:Alpha"})) Ω(reporter.Did.Find("K").ContainerHierarchyLabels).Should(Equal([][]string{{}, {"Feature:Beta"}, {"feature : alpha"}})) Ω(reporter.Did.Find("K").LeafNodeLabels).Should(Equal([]string{"Feature:Delta", "Feature:Beta"})) Ω(reporter.Did.Find("K").Labels()).Should(Equal([]string{"Feature:Beta", "feature : alpha", "Feature:Delta"})) }) It("includes suite labels in the suite report", func() { Ω(reporter.Begin.SuiteLabels).Should(Equal([]string{"TopLevelLabel"})) Ω(reporter.End.SuiteLabels).Should(Equal([]string{"TopLevelLabel"})) }) It("honors the LabelFilter config and skips tests appropriately", func() { Ω(rt).Should(HaveTracked("B", "C", "D", "F", "H", "J", "K")) Ω(reporter.Did.WithState(types.SpecStatePassed).Names()).Should(ConsistOf("B", "C", "D", "F", "H", "J", "K")) Ω(reporter.Did.WithState(types.SpecStateSkipped).Names()).Should(ConsistOf("A", "E", "I")) Ω(reporter.Did.WithState(types.SpecStatePending).Names()).Should(ConsistOf("G")) Ω(reporter.End).Should(BeASuiteSummary(true, NPassed(7), NSkipped(3), NPending(1), NSpecs(11), NWillRun(7))) }) }) Context("when a suite-level label is filtered out by the label-filter", func() { BeforeEach(func() { conf.LabelFilter = "!TopLevelLabel" success, hPF := RunFixture("labelled tests", func() { ReportBeforeSuite(func(r Report) { rt.RunWithData("RBS", "report", r) }) BeforeSuite(rt.T("before-suite")) Describe("outer container", func() { It("A", rt.T("A")) It("B", rt.T("B")) }) ReportAfterEach(func(r SpecReport) { rt.Run("RAE-" + r.LeafNodeText) }) AfterSuite(rt.T("after-suite")) ReportAfterSuite("AfterSuite", func(r Report) { rt.Run("RAS") }) }) Ω(success).Should(BeTrue()) Ω(hPF).Should(BeFalse()) }) It("doesn't run anything except for reporters", func() { Ω(rt).Should(HaveTracked("RBS", "RAE-A", "RAE-B", "RAS")) }) It("skip everything", func() { Ω(reporter.Did.Find("A")).Should(HaveBeenSkipped()) Ω(reporter.Did.Find("B")).Should(HaveBeenSkipped()) }) It("reports the correct number of specs to ReportBeforeSuite", func() { report := rt.DataFor("RBS")["report"].(Report) Ω(report.PreRunStats.SpecsThatWillRun).Should(Equal(0)) Ω(report.PreRunStats.TotalSpecs).Should(Equal(2)) }) }) }) golang-github-onsi-ginkgo-v2-2.22.0/internal/internal_integration/node_spec_events_test.go000066400000000000000000000154221472321612100320420ustar00rootroot00000000000000package internal_integration_test import ( "time" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/ginkgo/v2/internal/test_helpers" "github.com/onsi/ginkgo/v2/types" . "github.com/onsi/gomega" ) var _ = Describe("Emitting Node SpecEvents", func() { BeforeEach(func() { cl = types.NewCodeLocation(0) success, _ := RunFixture("emitting spec progress", func() { BeforeSuite(func() {}) Describe("a container", func() { BeforeEach(func() {}) It("A", func() { time.Sleep(time.Millisecond * 20) writer.Print("hello\n") }) It("B", func() {}) AfterEach(func() {}) ReportAfterEach(func(_ SpecReport) {}) }) AfterSuite(func() {}) ReportAfterEach(func(_ SpecReport) {}) }) Ω(success).Should(BeTrue()) }) It("attaches appropriate spec events to each report", func() { Ω(reporter.Did.FindByLeafNodeType(types.NodeTypeBeforeSuite).Timeline()).Should(BeTimelineContaining( BeSpecEvent(types.SpecEventNodeStart, "TOP-LEVEL", types.NodeTypeBeforeSuite, clLine(2), TLWithOffset(0)), BeSpecEvent(types.SpecEventNodeEnd, "TOP-LEVEL", types.NodeTypeBeforeSuite, clLine(2), TLWithOffset(0)), )) Ω(reporter.Did.Find("A").Timeline()).Should(BeTimelineContaining( BeSpecEvent(types.SpecEventNodeStart, "a container", types.NodeTypeBeforeEach, clLine(4), TLWithOffset(0)), BeSpecEvent(types.SpecEventNodeEnd, "a container", types.NodeTypeBeforeEach, clLine(4), TLWithOffset(0)), BeSpecEvent(types.SpecEventNodeStart, "A", types.NodeTypeIt, clLine(5), TLWithOffset(0)), BeSpecEvent(types.SpecEventNodeEnd, "A", types.NodeTypeIt, clLine(5), TLWithOffset("hello\n"), time.Millisecond*20), BeSpecEvent(types.SpecEventNodeStart, "a container", types.NodeTypeAfterEach, clLine(10), TLWithOffset("hello\n")), BeSpecEvent(types.SpecEventNodeEnd, "a container", types.NodeTypeAfterEach, clLine(10), TLWithOffset("hello\n")), BeSpecEvent(types.SpecEventNodeStart, "a container", types.NodeTypeReportAfterEach, clLine(11), TLWithOffset("hello\n")), BeSpecEvent(types.SpecEventNodeEnd, "a container", types.NodeTypeReportAfterEach, clLine(11), TLWithOffset("hello\n")), BeSpecEvent(types.SpecEventNodeStart, "TOP-LEVEL", types.NodeTypeReportAfterEach, clLine(14), TLWithOffset("hello\n")), BeSpecEvent(types.SpecEventNodeEnd, "TOP-LEVEL", types.NodeTypeReportAfterEach, clLine(14), TLWithOffset("hello\n")), )) Ω(reporter.Did.Find("A").Timeline()).ShouldNot(BeTimelineContaining(BeSpecEvent(types.NodeTypeReportAfterEach, clLine(15)))) Ω(reporter.Did.Find("B").Timeline()).Should(BeTimelineContaining( BeSpecEvent(types.SpecEventNodeStart, "a container", types.NodeTypeBeforeEach, clLine(4), TLWithOffset(0)), BeSpecEvent(types.SpecEventNodeEnd, "a container", types.NodeTypeBeforeEach, clLine(4), TLWithOffset(0)), BeSpecEvent(types.SpecEventNodeStart, "B", types.NodeTypeIt, clLine(9), TLWithOffset(0)), BeSpecEvent(types.SpecEventNodeEnd, "B", types.NodeTypeIt, clLine(9), TLWithOffset(0)), BeSpecEvent(types.SpecEventNodeStart, "a container", types.NodeTypeAfterEach, clLine(10), TLWithOffset(0)), BeSpecEvent(types.SpecEventNodeEnd, "a container", types.NodeTypeAfterEach, clLine(10), TLWithOffset(0)), BeSpecEvent(types.SpecEventNodeStart, "a container", types.NodeTypeReportAfterEach, clLine(11), TLWithOffset(0)), BeSpecEvent(types.SpecEventNodeEnd, "a container", types.NodeTypeReportAfterEach, clLine(11), TLWithOffset(0)), BeSpecEvent(types.SpecEventNodeStart, "TOP-LEVEL", types.NodeTypeReportAfterEach, clLine(14), TLWithOffset(0)), BeSpecEvent(types.SpecEventNodeEnd, "TOP-LEVEL", types.NodeTypeReportAfterEach, clLine(14), TLWithOffset(0)), )) Ω(reporter.Did.FindByLeafNodeType(types.NodeTypeAfterSuite).Timeline()).Should(BeTimelineContaining( BeSpecEvent(types.SpecEventNodeStart, "TOP-LEVEL", types.NodeTypeAfterSuite, clLine(13), TLWithOffset(0)), BeSpecEvent(types.SpecEventNodeEnd, "TOP-LEVEL", types.NodeTypeAfterSuite, clLine(13), TLWithOffset(0)), )) }) It("emits each spec event as it goes", func() { timeline := types.Timeline{} for _, specEvent := range reporter.SpecEvents { timeline = append(timeline, specEvent) } Ω(timeline).Should(BeTimelineExactlyMatching( BeSpecEvent(types.SpecEventNodeStart, "TOP-LEVEL", types.NodeTypeBeforeSuite, clLine(2), TLWithOffset(0)), BeSpecEvent(types.SpecEventNodeEnd, "TOP-LEVEL", types.NodeTypeBeforeSuite, clLine(2), TLWithOffset(0)), BeSpecEvent(types.SpecEventNodeStart, "a container", types.NodeTypeBeforeEach, clLine(4), TLWithOffset(0)), BeSpecEvent(types.SpecEventNodeEnd, "a container", types.NodeTypeBeforeEach, clLine(4), TLWithOffset(0)), BeSpecEvent(types.SpecEventNodeStart, "A", types.NodeTypeIt, clLine(5), TLWithOffset(0)), BeSpecEvent(types.SpecEventNodeEnd, "A", types.NodeTypeIt, clLine(5), TLWithOffset("hello\n"), time.Millisecond*20), BeSpecEvent(types.SpecEventNodeStart, "a container", types.NodeTypeAfterEach, clLine(10), TLWithOffset("hello\n")), BeSpecEvent(types.SpecEventNodeEnd, "a container", types.NodeTypeAfterEach, clLine(10), TLWithOffset("hello\n")), BeSpecEvent(types.SpecEventNodeStart, "a container", types.NodeTypeReportAfterEach, clLine(11), TLWithOffset("hello\n")), BeSpecEvent(types.SpecEventNodeEnd, "a container", types.NodeTypeReportAfterEach, clLine(11), TLWithOffset("hello\n")), BeSpecEvent(types.SpecEventNodeStart, "TOP-LEVEL", types.NodeTypeReportAfterEach, clLine(14), TLWithOffset("hello\n")), BeSpecEvent(types.SpecEventNodeEnd, "TOP-LEVEL", types.NodeTypeReportAfterEach, clLine(14), TLWithOffset("hello\n")), BeSpecEvent(types.SpecEventNodeStart, "a container", types.NodeTypeBeforeEach, clLine(4), TLWithOffset(0)), BeSpecEvent(types.SpecEventNodeEnd, "a container", types.NodeTypeBeforeEach, clLine(4), TLWithOffset(0)), BeSpecEvent(types.SpecEventNodeStart, "B", types.NodeTypeIt, clLine(9), TLWithOffset(0)), BeSpecEvent(types.SpecEventNodeEnd, "B", types.NodeTypeIt, clLine(9), TLWithOffset(0)), BeSpecEvent(types.SpecEventNodeStart, "a container", types.NodeTypeAfterEach, clLine(10), TLWithOffset(0)), BeSpecEvent(types.SpecEventNodeEnd, "a container", types.NodeTypeAfterEach, clLine(10), TLWithOffset(0)), BeSpecEvent(types.SpecEventNodeStart, "a container", types.NodeTypeReportAfterEach, clLine(11), TLWithOffset(0)), BeSpecEvent(types.SpecEventNodeEnd, "a container", types.NodeTypeReportAfterEach, clLine(11), TLWithOffset(0)), BeSpecEvent(types.SpecEventNodeStart, "TOP-LEVEL", types.NodeTypeReportAfterEach, clLine(14), TLWithOffset(0)), BeSpecEvent(types.SpecEventNodeEnd, "TOP-LEVEL", types.NodeTypeReportAfterEach, clLine(14), TLWithOffset(0)), BeSpecEvent(types.SpecEventNodeStart, "TOP-LEVEL", types.NodeTypeAfterSuite, clLine(13), TLWithOffset(0)), BeSpecEvent(types.SpecEventNodeEnd, "TOP-LEVEL", types.NodeTypeAfterSuite, clLine(13), TLWithOffset(0)), )) }) }) golang-github-onsi-ginkgo-v2-2.22.0/internal/internal_integration/ordered_test.go000066400000000000000000001332241472321612100301440ustar00rootroot00000000000000package internal_integration_test import ( "fmt" "time" . "github.com/onsi/ginkgo/v2" "github.com/onsi/ginkgo/v2/internal/interrupt_handler" . "github.com/onsi/ginkgo/v2/internal/test_helpers" "github.com/onsi/ginkgo/v2/types" . "github.com/onsi/gomega" ) const SKIP_DUE_TO_EARLIER_FAILURE = "Spec skipped because an earlier spec in an ordered container failed" const SKIP_DUE_TO_FAILURE_IN_BEFORE_ALL = "Spec skipped because a BeforeAll node failed" const SKIP_DUE_TO_BEFORE_ALL_SKIP = "Spec skipped because Skip() was called in BeforeAll" const SKIP_DUE_TO_BEFORE_EACH_SKIP = "Spec skipped because Skip() was called in BeforeEach" var DC = func(label string, callback ...func()) func() { return func() { DeferCleanup(rt.T(label, callback...)) } } var FlakeyFailer = func(n int) func() { i := 0 return func() { i += 1 if i <= n { F("fail") } } } var FlakeyFailerWithCleanup = func(n int, cleanupLabel string) func() { i := 0 return func() { i += 1 DeferCleanup(rt.T(cleanupLabel + "-pre")) if i <= n { F("fail") } DeferCleanup(rt.T(cleanupLabel + "-post")) } } var _ = DescribeTable("Ordered Containers", func(expectedSuccess bool, fixture func(), runs []string, args ...interface{}) { success, _ := RunFixture(CurrentSpecReport().LeafNodeText, fixture) Ω(success).Should(Equal(expectedSuccess)) Ω(rt).Should(HaveTracked(runs...)) specs := Reports{} for i := 0; i < len(args); i += 1 { switch v := args[i].(type) { case string: specs = append(specs, reporter.Did.Find(v)) case OmegaMatcher: Ω(specs).ShouldNot(BeEmpty(), "Got a matcher but expected a spec to look up") for _, spec := range specs { Ω(spec).Should(v, "Spec that failed: %s", spec.LeafNodeText) } specs = Reports{} default: Fail(fmt.Sprintf("Unexpected type %T", args[i])) } } Ω(specs).Should(BeEmpty(), "Trailing spec - missing a matcher") }, // basic ordering Entry("simply happy path", true, func() { Context("container", Ordered, func() { It("A", rt.T("A")) It("B", rt.T("B")) It("C", rt.T("C")) }) }, []string{"A", "B", "C"}, "A", "B", "C", HavePassed(), ), Entry("when a spec fails", false, func() { Context("outer container", func() { Context("container", Ordered, func() { It("A", rt.T("A")) It("B", rt.T("B")) It("C", rt.T("C", func() { F("fail") })) It("D", rt.T("D")) It("E", rt.T("E")) }) Context("container", Ordered, func() { It("F", FlakeAttempts(3), rt.T("F", FlakeyFailer(2))) It("G", rt.T("G")) }) }) }, []string{"A", "B", "C", "F", "F", "F", "G"}, "A", "B", HavePassed(), "C", HaveFailed(), "D", "E", HaveBeenSkippedWithMessage(SKIP_DUE_TO_EARLIER_FAILURE), "F", HavePassed(NumAttempts(3)), "G", HavePassed(NumAttempts(1)), ), // BeforeAll and AfterAll - happy paths Entry("BeforeAll and AfterAll Happy Path", true, func() { BeforeEach(rt.T("BE1")) JustBeforeEach(rt.T("JBE1")) AfterEach(rt.T("AE1")) Context("container", Ordered, func() { BeforeEach(rt.T("BE2")) JustBeforeEach(rt.T("JBE2")) BeforeAll(rt.T("BA1")) BeforeEach(rt.T("BE3")) JustBeforeEach(rt.T("JBE3")) BeforeAll(rt.T("BA2")) BeforeEach(rt.T("BE4")) It("A", rt.T("A")) It("B", rt.T("B")) It("C", rt.T("C")) JustAfterEach(rt.T("JAE1")) AfterEach(rt.T("AE2")) AfterAll(rt.T("AA1")) AfterEach(rt.T("AE3")) JustAfterEach(rt.T("JAE2")) AfterAll(rt.T("AA2")) AfterEach(rt.T("AE4")) JustAfterEach(rt.T("JAE3")) }) JustAfterEach(rt.T("JAE4")) AfterEach(rt.T("AE5")) BeforeEach(rt.T("BE5")) JustBeforeEach(rt.T("JBE4")) }, []string{ "BE1", "BE5", "BA1", "BA2", "BE2", "BE3", "BE4", "JBE1", "JBE4", "JBE2", "JBE3", "A", "JAE1", "JAE2", "JAE3", "JAE4", "AE2", "AE3", "AE4", "AE1", "AE5", "BE1", "BE5", "BE2", "BE3", "BE4", "JBE1", "JBE4", "JBE2", "JBE3", "B", "JAE1", "JAE2", "JAE3", "JAE4", "AE2", "AE3", "AE4", "AE1", "AE5", "BE1", "BE5", "BE2", "BE3", "BE4", "JBE1", "JBE4", "JBE2", "JBE3", "C", "JAE1", "JAE2", "JAE3", "JAE4", "AE2", "AE3", "AE4", "AA1", "AA2", "AE1", "AE5", }), Entry("when there is only one spec", true, func() { Context("container", Ordered, func() { BeforeEach(rt.T("BE")) BeforeAll(rt.T("BA")) It("A", rt.T("A")) AfterAll(rt.T("AA")) AfterEach(rt.T("AE")) }) }, []string{ "BA", "BE", "A", "AE", "AA", }), Entry("when there are focused specs", true, func() { Context("container", Ordered, func() { BeforeEach(rt.T("BE")) BeforeAll(rt.T("BA")) It("A", rt.T("A")) FIt("B", rt.T("B")) FIt("C", rt.T("C")) It("D", rt.T("D")) AfterAll(rt.T("AA")) AfterEach(rt.T("AE")) }) }, []string{ "BA", "BE", "B", "AE", "BE", "C", "AE", "AA", }, "B", "C", HavePassed(), "A", "D", HaveBeenSkipped(), ), Entry("when there is nothing to run", true, func() { Context("container", Ordered, func() { BeforeEach(rt.T("BE")) BeforeAll(rt.T("BA")) PIt("A", rt.T("A")) PIt("B", rt.T("B")) PIt("C", rt.T("C")) AfterAll(rt.T("AA")) AfterEach(rt.T("AE")) }) }, []string{}, "A", "B", "C", BePending()), // BeforeAll and AfterAll - when skips are called Entry("when a skip occurs in a BeforeAll, it skips the entire group", true, func() { Context("container", Ordered, func() { BeforeAll(rt.T("BA", func() { DeferCleanup(rt.T("DC")); Skip("skip") })) It("A", FlakeAttempts(3), rt.T("A")) It("B", rt.T("B")) It("C", rt.T("C")) AfterAll(rt.T("AA")) }) }, []string{"BA", "AA", "DC"}, "A", HaveBeenSkippedWithMessage("skip", NumAttempts(1)), "B", "C", HaveBeenSkippedWithMessage(SKIP_DUE_TO_BEFORE_ALL_SKIP), ), Entry("when a skip occurs in a test", true, func() { Context("container", Ordered, func() { BeforeAll(rt.T("BA")) It("A", rt.T("A")) It("B", rt.T("B", func() { Skip("skip") })) It("C", rt.T("C")) AfterAll(rt.T("AA")) }) }, []string{"BA", "A", "B", "C", "AA"}, "A", "C", HavePassed(), "B", HaveBeenSkippedWithMessage("skip"), ), Entry("when a skip occurs in the last test", true, func() { Context("container", Ordered, func() { BeforeAll(rt.T("BA")) It("A", rt.T("A")) It("B", rt.T("B")) It("C", rt.T("C", func() { Skip("skip") })) AfterAll(rt.T("AA")) }) }, []string{"BA", "A", "B", "C", "AA"}, "A", "B", HavePassed(), "C", HaveBeenSkippedWithMessage("skip"), ), // BeforeAll and AfterAll - when failures, panics, interrupts, and aborts happen Entry("when a failure occurs prior to the BeforeAll, it doesn't run the Alls", false, func() { BeforeEach(rt.T("BE-outer", func() { F("fail") })) Context("container", Ordered, func() { BeforeEach(rt.T("BE")) BeforeAll(rt.T("BA")) It("A", rt.T("A")) AfterAll(rt.T("AA")) AfterEach(rt.T("AE")) }) AfterEach(rt.T("AE-outer")) }, []string{ "BE-outer", "AE-outer", }, "A", HaveFailed(types.FailureNodeAtTopLevel, FailureNodeType(types.NodeTypeBeforeEach), "fail")), Entry("when a failure occurs in a spec, it runs the AfterAll and skips subsequent specs", false, func() { BeforeEach(rt.T("BE-outer")) Context("container", Ordered, func() { BeforeEach(rt.T("BE")) BeforeAll(rt.T("BA")) It("A", rt.T("A", func() { F("fail") })) It("B", rt.T("B")) AfterAll(rt.T("AA")) AfterEach(rt.T("AE")) }) AfterEach(rt.T("AE-outer")) }, []string{"BE-outer", "BA", "BE", "A", "AE", "AA", "AE-outer"}, "A", HaveFailed(types.FailureNodeIsLeafNode, FailureNodeType(types.NodeTypeIt), "fail"), "B", HaveBeenSkippedWithMessage(SKIP_DUE_TO_EARLIER_FAILURE), ), Entry("when a failure occurs in a BeforeAll", false, func() { BeforeEach(rt.T("BE-outer")) Context("container", Ordered, func() { BeforeEach(rt.T("BE")) BeforeAll(rt.T("BA", func() { F("fail") })) It("A", rt.T("A")) It("B", rt.T("B")) AfterAll(rt.T("AA")) AfterEach(rt.T("AE")) }) AfterEach(rt.T("AE-outer")) }, []string{"BE-outer", "BA", "AE", "AA", "AE-outer"}, "A", HaveFailed(types.FailureNodeInContainer, FailureNodeType(types.NodeTypeBeforeAll), "fail"), "B", HaveBeenSkippedWithMessage(SKIP_DUE_TO_EARLIER_FAILURE), ), Entry("when a failure occurs in an AfterAll", false, func() { BeforeEach(rt.T("BE-outer")) Context("container", Ordered, func() { BeforeEach(rt.T("BE")) BeforeAll(rt.T("BA")) It("A", rt.T("A")) It("B", rt.T("B")) AfterAll(rt.T("AA", func() { F("fail") })) AfterEach(rt.T("AE")) }) AfterEach(rt.T("AE-outer")) }, []string{ "BE-outer", "BA", "BE", "A", "AE", "AE-outer", "BE-outer", "BE", "B", "AE", "AA", "AE-outer", }, "A", HavePassed(), "B", HaveFailed(types.FailureNodeInContainer, FailureNodeType(types.NodeTypeAfterAll), "fail"), ), Entry("when a panic occurs in a BeforeAll", false, func() { BeforeEach(rt.T("BE-outer")) Context("container", Ordered, func() { BeforeEach(rt.T("BE")) BeforeAll(rt.T("BA", func() { panic("boom") })) It("A", rt.T("A")) It("B", rt.T("B")) AfterAll(rt.T("AA")) AfterEach(rt.T("AE")) }) AfterEach(rt.T("AE-outer")) }, []string{"BE-outer", "BA", "AE", "AA", "AE-outer"}, "A", HavePanicked(types.FailureNodeInContainer, FailureNodeType(types.NodeTypeBeforeAll), "boom"), "B", HaveBeenSkippedWithMessage(SKIP_DUE_TO_EARLIER_FAILURE), ), Entry("when a panic occurs in an AfterAll", false, func() { BeforeEach(rt.T("BE-outer")) Context("container", Ordered, func() { BeforeEach(rt.T("BE")) BeforeAll(rt.T("BA")) It("A", rt.T("A")) It("B", rt.T("B")) AfterAll(rt.T("AA", func() { panic("boom") })) AfterEach(rt.T("AE")) }) AfterEach(rt.T("AE-outer")) }, []string{ "BE-outer", "BA", "BE", "A", "AE", "AE-outer", "BE-outer", "BE", "B", "AE", "AA", "AE-outer", }, "A", HavePassed(), "B", HavePanicked(types.FailureNodeInContainer, FailureNodeType(types.NodeTypeAfterAll), "boom"), ), Entry("when a timeout occurs in a BeforeAll", false, func() { BeforeEach(rt.T("BE-outer")) Context("container", Ordered, func() { BeforeEach(rt.T("BE")) BeforeAll(rt.TSC("BA", func(ctx SpecContext) { <-ctx.Done() }), NodeTimeout(time.Millisecond*10)) It("A", rt.T("A")) It("B", rt.T("B")) AfterAll(rt.T("AA")) AfterEach(rt.T("AE")) }) AfterEach(rt.T("AE-outer")) }, []string{"BE-outer", "BA", "AE", "AA", "AE-outer"}, "A", HaveTimedOut(types.FailureNodeInContainer, FailureNodeType(types.NodeTypeBeforeAll)), "B", HaveBeenSkippedWithMessage(SKIP_DUE_TO_EARLIER_FAILURE), ), Entry("when a timeout occurs in an AfterAll", false, func() { BeforeEach(rt.T("BE-outer")) Context("container", Ordered, func() { BeforeEach(rt.T("BE")) BeforeAll(rt.T("BA")) It("A", rt.T("A")) It("B", rt.T("B")) AfterAll(rt.TSC("AA", func(ctx SpecContext) { <-ctx.Done() }), NodeTimeout(time.Millisecond*10)) AfterEach(rt.T("AE")) }) AfterEach(rt.T("AE-outer")) }, []string{ "BE-outer", "BA", "BE", "A", "AE", "AE-outer", "BE-outer", "BE", "B", "AE", "AA", "AE-outer", }, "A", HavePassed(), "B", HaveTimedOut(types.FailureNodeInContainer, FailureNodeType(types.NodeTypeAfterAll)), ), Entry("when a failure occurs in an AfterEach, it runs the AfterAll", false, func() { BeforeEach(rt.T("BE-outer")) Context("container", Ordered, func() { BeforeEach(rt.T("BE")) BeforeAll(rt.T("BA")) It("A", rt.T("A")) It("B", rt.T("B")) AfterAll(rt.T("AA")) AfterEach(rt.T("AE", func() { F("fail") })) }) AfterEach(rt.T("AE-outer")) }, []string{"BE-outer", "BA", "BE", "A", "AE", "AE-outer", "AA"}, "A", HaveFailed(types.FailureNodeInContainer, FailureNodeType(types.NodeTypeAfterEach), "fail"), "B", HaveBeenSkippedWithMessage(SKIP_DUE_TO_EARLIER_FAILURE), ), Entry("when a failure occurs in a DeferCleanup, it runs the AfterAll", false, func() { BeforeEach(rt.T("BE-outer")) Context("container", Ordered, func() { BeforeEach(rt.T("BE")) BeforeAll(rt.T("BA")) It("A", rt.T("A", func() { DeferCleanup(func() { rt.Run("cleanup") Fail("fail") }) })) It("B", rt.T("B")) AfterAll(rt.T("AA")) AfterEach(rt.T("AE")) }) AfterEach(rt.T("AE-outer")) }, []string{"BE-outer", "BA", "BE", "A", "AE", "AE-outer", "cleanup", "AA"}, "A", HaveFailed(types.FailureNodeInContainer, FailureNodeType(types.NodeTypeCleanupAfterEach), "fail"), "B", HaveBeenSkippedWithMessage(SKIP_DUE_TO_EARLIER_FAILURE), ), Entry("when an interruption occurs, run the AfterAll and skip subsequent specs", false, func() { BeforeEach(rt.T("BE-outer")) Context("container", Ordered, FlakeAttempts(4), func() { BeforeEach(rt.T("BE")) BeforeAll(rt.T("BA")) It("A", rt.T("A", func() { interruptHandler.Interrupt(interrupt_handler.InterruptCauseSignal) time.Sleep(time.Minute) })) It("B", rt.T("B")) AfterAll(rt.T("AA")) AfterEach(rt.T("AE")) }) AfterEach(rt.T("AE-outer")) }, []string{"BE-outer", "BA", "BE", "A", "AE", "AA", "AE-outer"}, "A", HaveBeenInterrupted(interrupt_handler.InterruptCauseSignal), "B", HaveBeenSkipped(), ), Entry("when an interruption occurs in a BeforeAll, run the AfterAll and skip subsequent specs", false, func() { BeforeEach(rt.T("BE-outer")) Context("container", Ordered, FlakeAttempts(4), func() { BeforeEach(rt.T("BE")) BeforeAll(rt.T("BA", func() { DeferCleanup(rt.T("DC-BA")) interruptHandler.Interrupt(interrupt_handler.InterruptCauseSignal) time.Sleep(time.Minute) })) It("A", rt.T("A")) It("B", rt.T("B")) AfterAll(rt.T("AA")) AfterEach(rt.T("AE")) }) AfterEach(rt.T("AE-outer")) }, []string{"BE-outer", "BA", "AE", "AA", "AE-outer", "DC-BA"}, "A", HaveBeenInterrupted(interrupt_handler.InterruptCauseSignal), "B", HaveBeenSkipped(), ), Entry("when an interruption occurs in an AfterAll, run any remaining cleanup", false, func() { BeforeEach(rt.T("BE-outer")) Context("container", Ordered, FlakeAttempts(4), func() { BeforeEach(rt.T("BE")) BeforeAll(rt.T("BA", DC("DC-BA"))) It("A", rt.T("A")) It("B", rt.T("B")) AfterAll(rt.T("AA", func() { DeferCleanup(rt.T("DC-AA")) interruptHandler.Interrupt(interrupt_handler.InterruptCauseSignal) time.Sleep(time.Minute) })) AfterEach(rt.T("AE")) }) AfterEach(rt.T("AE-outer")) }, []string{ "BE-outer", "BA", "BE", "A", "AE", "AE-outer", "BE-outer", "BE", "B", "AE", "AA", "AE-outer", "DC-AA", "DC-BA", }, "A", HavePassed(), "B", HaveBeenInterrupted(interrupt_handler.InterruptCauseSignal), ), Entry("when an abort occurs, run the AfterAll and skip subsequent specs", false, func() { BeforeEach(rt.T("BE-outer")) Context("container", Ordered, FlakeAttempts(4), func() { BeforeEach(rt.T("BE")) BeforeAll(rt.T("BA")) It("A", rt.T("A", func() { Abort("abort!") })) It("B", rt.T("B")) AfterAll(rt.T("AA")) AfterEach(rt.T("AE")) }) AfterEach(rt.T("AE-outer")) }, []string{"BE-outer", "BA", "BE", "A", "AE", "AA", "AE-outer"}, "A", HaveAborted("abort!"), "B", HaveBeenSkipped(), ), Entry("when an abort occurs in a BeforeAll, run the AfterAll and skip subsequent specs", false, func() { BeforeEach(rt.T("BE-outer")) Context("container", Ordered, FlakeAttempts(4), func() { BeforeEach(rt.T("BE")) BeforeAll(rt.T("BA", func() { DeferCleanup(rt.T("DC-BA")) Abort("abort!") })) It("A", rt.T("A")) It("B", rt.T("B")) AfterAll(rt.T("AA")) AfterEach(rt.T("AE")) }) AfterEach(rt.T("AE-outer")) }, []string{"BE-outer", "BA", "AE", "AA", "AE-outer", "DC-BA"}, "A", HaveAborted("abort!"), "B", HaveBeenSkipped(), ), Entry("when an abort occurs in an AfterAll, run any remaining cleanup", false, func() { BeforeEach(rt.T("BE-outer")) Context("container", Ordered, FlakeAttempts(4), func() { BeforeEach(rt.T("BE")) BeforeAll(rt.T("BA", DC("DC-BA"))) It("A", rt.T("A")) It("B", rt.T("B")) AfterAll(rt.T("AA", func() { DeferCleanup(rt.T("DC-AA")) Abort("abort!") })) AfterEach(rt.T("AE")) }) AfterEach(rt.T("AE-outer")) }, []string{ "BE-outer", "BA", "BE", "A", "AE", "AE-outer", "BE-outer", "BE", "B", "AE", "AA", "AE-outer", "DC-AA", "DC-BA", }, "A", HavePassed(), "B", HaveAborted("abort!"), ), //here be dragons: the interplay between BeforeAll/AfterAll and FlakeAttempts Entry("when the first spec is flaky, it runs the BeforeAll just once", true, func() { Context("container", Ordered, FlakeAttempts(4), func() { BeforeAll(rt.T("BA")) It("A", rt.T("A", FlakeyFailer(2))) It("B", rt.T("B")) It("C", rt.T("C")) AfterAll(rt.T("AA")) }) }, []string{"BA", "A", "A", "A", "B", "C", "AA"}, "A", HavePassed(NumAttempts(3)), "B", "C", HavePassed(NumAttempts(1)), ), Entry("when a spec is flaky and never succeeds, it runs the AfterAll (just once) when the spec ultimately fails", false, func() { Context("container", Ordered, FlakeAttempts(4), func() { BeforeAll(rt.T("BA")) It("A", rt.T("A")) It("B", rt.T("B", FlakeyFailer(4))) It("C", rt.T("C")) AfterAll(rt.T("AA")) }) }, []string{"BA", "A", "B", "B", "B", "B", "AA"}, "A", HavePassed(), "B", HaveFailed("fail", NumAttempts(4)), "C", HaveBeenSkippedWithMessage(SKIP_DUE_TO_EARLIER_FAILURE), ), Entry("when the last spec is flaky, it runs the AFterAll just once", true, func() { Context("container", Ordered, FlakeAttempts(4), func() { BeforeAll(rt.T("BA")) It("A", rt.T("A")) It("B", rt.T("B")) It("C", rt.T("C", FlakeyFailer(2))) AfterAll(rt.T("AA")) }) }, []string{"BA", "A", "B", "C", "C", "C", "AA"}, "A", "B", HavePassed(NumAttempts(1)), "C", HavePassed(NumAttempts(3)), ), Entry("When the BeforeAll is flaky", true, func() { Context("container", Ordered, FlakeAttempts(5), func() { BeforeAll(rt.T("BA", FlakeyFailerWithCleanup(2, "DC-BA"))) It("A", rt.T("A", FlakeyFailer(2))) It("B", rt.T("B")) It("C", rt.T("C")) AfterAll(rt.T("AA")) }) }, []string{ "BA", "AA", "DC-BA-pre", "BA", "AA", "DC-BA-pre", "BA", "A", "A", "A", "B", "C", "AA", "DC-BA-post", "DC-BA-pre", }, "A", HavePassed(NumAttempts(5)), "B", "C", HavePassed(), ), Entry("When the AFterAll is flaky", true, func() { Context("container", Ordered, FlakeAttempts(4), func() { BeforeAll(rt.T("BA", DC("DC-BA"))) It("A", rt.T("A")) It("B", rt.T("B")) It("C", rt.T("C")) AfterAll(rt.T("AA", FlakeyFailerWithCleanup(2, "DC-AA"))) }) }, []string{ "BA", "A", "B", "C", "AA", "DC-AA-pre", "C", "AA", "DC-AA-pre", "C", "AA", "DC-AA-post", "DC-AA-pre", "DC-BA", }, "A", "B", HavePassed(), "C", HavePassed(NumAttempts(3)), ), //Let's enter the dragons nest! Entry("happy-path for nested containers", true, func() { BeforeEach(rt.T("BE-L0")) Context("container", Ordered, func() { BeforeAll(rt.T("BA-1-L1", DC("DC-BA-L1"))) BeforeAll(rt.T("BA-2-L1")) BeforeEach(rt.T("BE-L1")) It("A", rt.T("A")) It("B", rt.T("B")) Context("inner", func() { BeforeAll(rt.T("BA-1-L2", DC("DC-BA-L2"))) BeforeAll(rt.T("BA-2-L2")) BeforeEach(rt.T("BE-L2")) It("C", rt.T("C")) It("D", rt.T("D")) AfterEach(rt.T("AE-L2")) AfterAll(rt.T("AA-1-L2", DC("DC-AA-L2"))) AfterAll(rt.T("AA-2-L2")) }) It("E", rt.T("E")) AfterEach(rt.T("AE-L1")) AfterAll(rt.T("AA-1-L1", DC("DC-AA-L1"))) AfterAll(rt.T("AA-2-L1")) }) AfterEach(rt.T("AE-L0")) }, []string{ "BE-L0", "BA-1-L1", "BA-2-L1", "BE-L1", "A", "AE-L1", "AE-L0", "BE-L0", "BE-L1", "B", "AE-L1", "AE-L0", "BE-L0", "BE-L1", "BA-1-L2", "BA-2-L2", "BE-L2", "C", "AE-L2", "AE-L1", "AE-L0", "BE-L0", "BE-L1", "BE-L2", "D", "AE-L2", "AA-1-L2", "AA-2-L2", "AE-L1", "AE-L0", "DC-AA-L2", "DC-BA-L2", "BE-L0", "BE-L1", "E", "AE-L1", "AA-1-L1", "AA-2-L1", "AE-L0", "DC-AA-L1", "DC-BA-L1", }), Entry("happy-path where last spec is in nested container", true, func() { BeforeEach(rt.T("BE-L0")) Context("container", Ordered, func() { BeforeAll(rt.T("BA-1-L1", DC("DC-BA-L1"))) BeforeAll(rt.T("BA-2-L1")) BeforeEach(rt.T("BE-L1")) It("A", rt.T("A")) It("B", rt.T("B")) Context("inner", func() { BeforeAll(rt.T("BA-1-L2", DC("DC-BA-L2"))) BeforeAll(rt.T("BA-2-L2")) BeforeEach(rt.T("BE-L2")) It("C", rt.T("C")) It("D", rt.T("D")) AfterEach(rt.T("AE-L2")) AfterAll(rt.T("AA-1-L2", DC("DC-AA-L2"))) AfterAll(rt.T("AA-2-L2")) }) AfterEach(rt.T("AE-L1")) AfterAll(rt.T("AA-1-L1", DC("DC-AA-L1"))) AfterAll(rt.T("AA-2-L1")) }) AfterEach(rt.T("AE-L0")) }, []string{ "BE-L0", "BA-1-L1", "BA-2-L1", "BE-L1", "A", "AE-L1", "AE-L0", "BE-L0", "BE-L1", "B", "AE-L1", "AE-L0", "BE-L0", "BE-L1", "BA-1-L2", "BA-2-L2", "BE-L2", "C", "AE-L2", "AE-L1", "AE-L0", "BE-L0", "BE-L1", "BE-L2", "D", "AE-L2", "AA-1-L2", "AA-2-L2", "AE-L1", "AA-1-L1", "AA-2-L1", "AE-L0", "DC-AA-L1", "DC-AA-L2", "DC-BA-L2", "DC-BA-L1", }), Entry("when an outer spec is skipped", true, func() { Context("container", Ordered, func() { BeforeAll(rt.T("BA-O", DC("DC-O"))) It("A", rt.T("A", func() { Skip("skip") })) It("B", rt.T("B")) Context("inner", func() { BeforeAll(rt.T("BA-I", DC("DC-I"))) It("C", rt.T("C")) It("D", rt.T("D")) AfterAll(rt.T("AA-I")) }) It("E", rt.T("E")) AfterAll(rt.T("AA-O")) }) }, []string{"BA-O", "A", "B", "BA-I", "C", "D", "AA-I", "DC-I", "E", "AA-O", "DC-O"}, "A", HaveBeenSkippedWithMessage("skip"), "B", "C", "D", "E", HavePassed(), ), Entry("when an inner spec is skipped", true, func() { Context("container", Ordered, func() { BeforeAll(rt.T("BA-O", DC("DC-O"))) It("A", rt.T("A")) It("B", rt.T("B")) Context("inner", func() { BeforeAll(rt.T("BA-I", DC("DC-I"))) It("C", rt.T("C", func() { Skip("skip") })) It("D", rt.T("D")) AfterAll(rt.T("AA-I")) }) It("E", rt.T("E")) AfterAll(rt.T("AA-O")) }) }, []string{"BA-O", "A", "B", "BA-I", "C", "D", "AA-I", "DC-I", "E", "AA-O", "DC-O"}, "C", HaveBeenSkippedWithMessage("skip"), "A", "B", "D", "E", HavePassed(), ), Entry("when an outer BeforeAll is skipped", true, func() { Context("container", Ordered, func() { BeforeAll(rt.T("BA-O", func() { DeferCleanup(rt.T("DC-O")); Skip("skip") })) It("A", rt.T("A")) It("B", rt.T("B")) Context("inner", func() { BeforeAll(rt.T("BA-I")) It("C", rt.T("C")) It("D", rt.T("D")) AfterAll(rt.T("AA-I")) }) It("E", rt.T("E")) AfterAll(rt.T("AA-O")) }) }, []string{"BA-O", "AA-O", "DC-O"}, "A", HaveBeenSkippedWithMessage("skip"), "B", "C", "D", "E", HaveBeenSkippedWithMessage(SKIP_DUE_TO_BEFORE_ALL_SKIP), ), Entry("when an inner BeforeAll is skipped", true, func() { Context("container", Ordered, func() { BeforeAll(rt.T("BA-O", DC("DC-O"))) It("A", rt.T("A")) It("B", rt.T("B")) Context("inner", func() { BeforeAll(rt.T("BA-I", func() { DeferCleanup(rt.T("DC-I")); Skip("skip") })) It("C", rt.T("C")) It("D", rt.T("D")) AfterAll(rt.T("AA-I")) }) It("E", rt.T("E")) AfterAll(rt.T("AA-O")) }) }, []string{"BA-O", "A", "B", "BA-I", "AA-I", "DC-I", "E", "AA-O", "DC-O"}, "A", "B", "E", HavePassed(), "C", HaveBeenSkippedWithMessage("skip"), "D", HaveBeenSkippedWithMessage(SKIP_DUE_TO_BEFORE_ALL_SKIP), ), Entry("when the last spec is marked as pending", true, func() { Context("container", Ordered, func() { BeforeAll(rt.T("BA-O", DC("DC-O"))) FIt("A", rt.T("A")) FIt("B", rt.T("B")) FContext("inner", func() { BeforeAll(rt.T("BA-I", DC("DC-I"))) It("C", rt.T("C")) PIt("D", rt.T("D")) AfterAll(rt.T("AA-I")) }) FIt("E", rt.T("E")) It("F", rt.T("F")) AfterAll(rt.T("AA-O")) }) }, []string{"BA-O", "A", "B", "BA-I", "C", "AA-I", "DC-I", "E", "AA-O", "DC-O"}, "A", "B", "C", "E", HavePassed(), "D", BePending(), "F", HaveBeenSkipped(), ), Entry("when an outer spec fails", false, func() { Context("container", Ordered, func() { BeforeAll(rt.T("BA-O", DC("DC-O"))) It("A", rt.T("A")) It("B", rt.T("B", func() { F("fail") })) Context("inner", func() { BeforeAll(rt.T("BA-I", DC("DC-I"))) It("C", rt.T("C")) It("D", rt.T("D")) AfterAll(rt.T("AA-I")) }) It("E", rt.T("E")) AfterAll(rt.T("AA-O")) }) }, []string{"BA-O", "A", "B", "AA-O", "DC-O"}, "A", HavePassed(), "B", HaveFailed("fail"), "C", "D", "E", HaveBeenSkippedWithMessage(SKIP_DUE_TO_EARLIER_FAILURE), ), Entry("when an inner spec fails", false, func() { Context("container", Ordered, func() { BeforeAll(rt.T("BA-O", DC("DC-O"))) It("A", rt.T("A")) It("B", rt.T("B")) Context("inner", func() { BeforeAll(rt.T("BA-I", DC("DC-I"))) It("C", rt.T("C", func() { F("fail") })) It("D", rt.T("D")) AfterAll(rt.T("AA-I")) }) It("E", rt.T("E")) AfterAll(rt.T("AA-O")) }) }, []string{"BA-O", "A", "B", "BA-I", "C", "AA-I", "AA-O", "DC-I", "DC-O"}, "A", HavePassed(), "B", HavePassed(), "C", HaveFailed("fail"), "D", "E", HaveBeenSkippedWithMessage(SKIP_DUE_TO_EARLIER_FAILURE), ), Entry("when flakey, and an outer BeforeAll flakes", true, func() { i := 0 Context("container", Ordered, FlakeAttempts(4), func() { BeforeAll(rt.T("BA-O", func() { i += 1 DeferCleanup(rt.T("DC-O")) if i < 3 { F("fail") } })) It("A", rt.T("A")) It("B", rt.T("B")) Context("inner", func() { BeforeAll(rt.T("BA-I", DC("DC-I"))) It("C", rt.T("C")) It("D", rt.T("D")) AfterAll(rt.T("AA-I")) }) It("E", rt.T("E")) AfterAll(rt.T("AA-O")) }) }, []string{ "BA-O", "AA-O", "DC-O", "BA-O", "AA-O", "DC-O", "BA-O", "A", "B", "BA-I", "C", "D", "AA-I", "DC-I", "E", "AA-O", "DC-O"}, "A", HavePassed(NumAttempts(3)), "B", "C", "D", "E", HavePassed(), ), Entry("when flakey, and an inner BeforeAll flakes", true, func() { i := 0 Context("container", Ordered, FlakeAttempts(4), func() { BeforeAll(rt.T("BA-O", DC("DC-O"))) It("A", rt.T("A")) It("B", rt.T("B")) Context("inner", func() { BeforeAll(rt.T("BA-I", func() { i += 1 DeferCleanup(rt.T("DC-I")) if i < 3 { F("fail") } })) It("C", rt.T("C")) It("D", rt.T("D")) AfterAll(rt.T("AA-I")) }) It("E", rt.T("E")) AfterAll(rt.T("AA-O")) }) }, []string{ "BA-O", "A", "B", "BA-I", "AA-I", "DC-I", "BA-I", "AA-I", "DC-I", "BA-I", "C", "D", "AA-I", "DC-I", "E", "AA-O", "DC-O"}, "A", "B", "D", "E", HavePassed(), "C", HavePassed(NumAttempts(3)), ), Entry("when specs are flakey", true, func() { Context("container", Ordered, FlakeAttempts(4), func() { BeforeAll(rt.T("BA-O", DC("DC-O"))) It("A", rt.T("A", FlakeyFailer(2))) It("B", rt.T("B", FlakeyFailer(2))) Context("inner", func() { BeforeAll(rt.T("BA-I", DC("DC-I"))) It("C", rt.T("C", FlakeyFailer(2))) It("D", rt.T("D", FlakeyFailer(2))) AfterAll(rt.T("AA-I")) }) It("E", rt.T("E", FlakeyFailer(2))) AfterAll(rt.T("AA-O")) }) }, []string{ "BA-O", "A", "A", "A", "B", "B", "B", "BA-I", "C", "C", "C", "D", "D", "D", "AA-I", "DC-I", "E", "E", "E", "AA-O", "DC-O", }, "A", "B", "C", "D", "E", HavePassed(NumAttempts(3)), ), Entry("when AfterAlls are flakey", true, func() { Context("container", Ordered, FlakeAttempts(4), func() { BeforeAll(rt.T("BA-O", DC("DC-O"))) It("A", rt.T("A")) It("B", rt.T("B")) Context("inner", func() { BeforeAll(rt.T("BA-I", DC("DC-I"))) It("C", rt.T("C")) It("D", rt.T("D")) AfterAll(rt.T("AA-I", FlakeyFailer(2))) }) It("E", rt.T("E")) AfterAll(rt.T("AA-O", FlakeyFailer(2))) }) }, []string{ "BA-O", "A", "B", "BA-I", "C", "D", "AA-I", "D", "AA-I", "D", "AA-I", "DC-I", "E", "AA-O", "E", "AA-O", "E", "AA-O", "DC-O", }, "A", "B", "C", "D", "E", HavePassed(), ), //this behavior is a bit weird, but it's such an edge case that we're going to leave it //unless an issue gets opened Entry("when DeferCleanups are flakey", true, func() { Context("container", Ordered, FlakeAttempts(4), func() { BeforeAll(rt.T("BA-O", DC("DC-O", FlakeyFailer(2)))) It("A", rt.T("A")) It("B", rt.T("B")) Context("inner", func() { BeforeAll(rt.T("BA-I", DC("DC-I", FlakeyFailer(2)))) It("C", rt.T("C")) It("D", rt.T("D")) AfterAll(rt.T("AA-I")) }) It("E", rt.T("E")) AfterAll(rt.T("AA-O")) }) }, []string{ "BA-O", "A", "B", "BA-I", "C", "D", "AA-I", "DC-I", "D", "AA-I", "E", "AA-O", "DC-O", "E", "AA-O", }, "A", "B", "C", "D", "E", HavePassed(), ), //can you believe there are even more dragons? Entry("Basic OncePerOrdered flow", true, func() { BeforeEach(rt.T("BE-O-HO", DC("DC-BE-O-HO")), OncePerOrdered) AfterEach(rt.T("AE-O-HO", DC("DC-AE-O-HO")), OncePerOrdered) Context("derandomizer", func() { It("A", rt.T("A")) It("B", rt.T("B")) Context("container", Ordered, func() { BeforeEach(rt.T("BE-I-HO", DC("DC-BE-I-HO")), OncePerOrdered) //OncePerOrdered doesn't matter here because there are no nested containers AfterEach(rt.T("AE-I-HO", DC("DC-AE-I-HO")), OncePerOrdered) //OncePerOrdered doesn't matter here because there are no nested containers It("C", rt.T("C")) It("D", rt.T("D")) It("E", rt.T("E")) PIt("F", rt.T("F")) }) }) }, []string{ "BE-O-HO", "A", "AE-O-HO", "DC-AE-O-HO", "DC-BE-O-HO", "BE-O-HO", "B", "AE-O-HO", "DC-AE-O-HO", "DC-BE-O-HO", "BE-O-HO", "BE-I-HO", "C", "AE-I-HO", "DC-AE-I-HO", "DC-BE-I-HO", "BE-I-HO", "D", "AE-I-HO", "DC-AE-I-HO", "DC-BE-I-HO", "BE-I-HO", "E", "AE-I-HO", "AE-O-HO", "DC-AE-O-HO", "DC-AE-I-HO", "DC-BE-I-HO", "DC-BE-O-HO", }, "A", "B", "C", "D", "E", HavePassed(), "F", BePending(), ), Entry("Basic OncePerOrdered flow when a failure occurs", false, func() { BeforeEach(rt.T("BE-O-HO", DC("DC-BE-O-HO")), OncePerOrdered) AfterEach(rt.T("AE-O-HO", DC("DC-AE-O-HO")), OncePerOrdered) Context("container", Ordered, func() { It("A", rt.T("A")) It("B", rt.T("B", FlakeyFailerWithCleanup(1, "B"))) It("C", rt.T("C")) }) }, []string{ "BE-O-HO", "A", "B", "AE-O-HO", "DC-AE-O-HO", "B-pre", "DC-BE-O-HO", }, "A", HavePassed(), "B", HaveFailed(), "C", HaveBeenSkippedWithMessage(SKIP_DUE_TO_EARLIER_FAILURE), ), Entry("Basic OncePerOrdered flow when a failure occurs in a OncePerOrdered BeforeEach", false, func() { BeforeEach(rt.T("BE-O-HO", FlakeyFailerWithCleanup(1, "BE-O-HO")), OncePerOrdered) AfterEach(rt.T("AE-O-HO", DC("DC-AE-O-HO")), OncePerOrdered) Context("container", Ordered, func() { It("A", rt.T("A")) It("B", rt.T("B")) It("C", rt.T("C")) }) }, []string{ "BE-O-HO", "AE-O-HO", "DC-AE-O-HO", "BE-O-HO-pre", }, "A", HaveFailed(), "B", "C", HaveBeenSkippedWithMessage(SKIP_DUE_TO_EARLIER_FAILURE), ), Entry("Basic OncePerOrdered flow when a skip occurs in a OncePerOrdered BeforeEach", true, func() { BeforeEach(rt.T("BE-O-HO", func() { DeferCleanup(rt.T("DC-BE-O-HO")); Skip("skip") }), OncePerOrdered) AfterEach(rt.T("AE-O-HO", DC("DC-AE-O-HO")), OncePerOrdered) Context("container", Ordered, func() { It("A", rt.T("A")) It("B", rt.T("B")) It("C", rt.T("C")) }) }, []string{ "BE-O-HO", "AE-O-HO", "DC-AE-O-HO", "DC-BE-O-HO", }, "A", HaveBeenSkippedWithMessage("skip"), "B", "C", HaveBeenSkippedWithMessage(SKIP_DUE_TO_BEFORE_EACH_SKIP), ), Entry("OncePerOrdered when there's a nested container in an ordered container", true, func() { Context("container", Ordered, func() { BeforeAll(rt.T("BA")) AfterAll(rt.T("AA")) BeforeEach(rt.T("BE", DC("DC-BE")), OncePerOrdered) AfterEach(rt.T("AE", DC("DC-AE")), OncePerOrdered) It("A", rt.T("A")) It("B", rt.T("B")) Context("nested", Ordered, func() { BeforeEach(rt.T("BE-I")) AfterEach(rt.T("AE-I")) It("C", rt.T("C")) It("D", rt.T("D")) It("E", rt.T("E")) }) It("F", rt.T("F")) }) }, []string{ "BA", "BE", "A", "AE", "DC-AE", "DC-BE", "BE", "B", "AE", "DC-AE", "DC-BE", "BE", "BE-I", "C", "AE-I", "BE-I", "D", "AE-I", "BE-I", "E", "AE-I", "AE", "DC-AE", "DC-BE", "BE", "F", "AE", "AA", "DC-AE", "DC-BE", }, "A", "B", "C", "D", "E", "F", HavePassed(), ), Entry("Flakey Failures", true, func() { BeforeEach(rt.T("BE-O-HO", FlakeyFailerWithCleanup(2, "BE")), OncePerOrdered) AfterEach(rt.T("AE-O-HO", FlakeyFailerWithCleanup(4, "AE")), OncePerOrdered) Context("container", Ordered, FlakeAttempts(3), func() { It("A", rt.T("A")) It("B", rt.T("B", FlakeyFailerWithCleanup(2, "B"))) It("C", rt.T("C")) It("D", rt.T("D")) }) }, []string{ "BE-O-HO", "AE-O-HO", "AE-pre", "BE-pre", "BE-O-HO", "AE-O-HO", "AE-pre", "BE-pre", "BE-O-HO", "A", "B", "B-pre", "B", "B-pre", "B", "B-post", "B-pre", "C", "D", "AE-O-HO", "AE-pre", "D", "AE-O-HO", "AE-pre", "D", "AE-O-HO", "AE-post", "AE-pre", "BE-post", "BE-pre", }, "A", "B", "D", HavePassed(NumAttempts(3)), "C", HavePassed(NumAttempts(1)), ), //Oh look, we made some new dragons Entry("ContinueOnFailure - happy path when nothing fails", true, func() { BeforeEach(rt.T("BE", DC("DC-BE")), OncePerOrdered) AfterEach(rt.T("AE", DC("DC-AE")), OncePerOrdered) Context("container", Ordered, ContinueOnFailure, func() { BeforeAll(rt.T("BA", DC("DC-BA"))) AfterAll(rt.T("AA", DC("DC-AA"))) It("A", rt.T("A")) It("B", rt.T("B")) It("C", rt.T("C")) }) }, []string{ "BE", "BA", "A", "B", "C", "AA", "AE", "DC-AE", "DC-BE", "DC-AA", "DC-BA", }, "A", "B", "C", HavePassed(), ), Entry("ContinueOnFailure - when the first It fails", false, func() { BeforeEach(rt.T("BE", DC("DC-BE")), OncePerOrdered) AfterEach(rt.T("AE", DC("DC-AE")), OncePerOrdered) Context("container", Ordered, ContinueOnFailure, func() { BeforeAll(rt.T("BA", DC("DC-BA"))) AfterAll(rt.T("AA", DC("DC-AA"))) It("A", rt.T("A", func() { F("fail") })) It("B", rt.T("B")) It("C", rt.T("C")) }) }, []string{ "BE", "BA", "A", "B", "C", "AA", "AE", "DC-AE", "DC-BE", "DC-AA", "DC-BA", }, "A", HaveFailed(types.FailureNodeIsLeafNode, FailureNodeType(types.NodeTypeIt), "fail"), "B", "C", HavePassed(), ), Entry("ContinueOnFailure - when a middle It fails", false, func() { BeforeEach(rt.T("BE", DC("DC-BE")), OncePerOrdered) AfterEach(rt.T("AE", DC("DC-AE")), OncePerOrdered) Context("container", Ordered, ContinueOnFailure, func() { BeforeAll(rt.T("BA", DC("DC-BA"))) AfterAll(rt.T("AA", DC("DC-AA"))) It("A", rt.T("A")) It("B", rt.T("B", func() { F("fail") })) It("C", rt.T("C")) }) }, []string{ "BE", "BA", "A", "B", "C", "AA", "AE", "DC-AE", "DC-BE", "DC-AA", "DC-BA", }, "A", HavePassed(), "B", HaveFailed(types.FailureNodeIsLeafNode, FailureNodeType(types.NodeTypeIt), "fail"), "C", HavePassed(), ), Entry("ContinueOnFailure - when the last It fails", false, func() { BeforeEach(rt.T("BE", DC("DC-BE")), OncePerOrdered) AfterEach(rt.T("AE", DC("DC-AE")), OncePerOrdered) Context("container", Ordered, ContinueOnFailure, func() { BeforeAll(rt.T("BA", DC("DC-BA"))) AfterAll(rt.T("AA", DC("DC-AA"))) It("A", rt.T("A")) It("B", rt.T("B")) It("C", rt.T("C", func() { F("fail") })) }) }, []string{ "BE", "BA", "A", "B", "C", "AA", "AE", "DC-AE", "DC-BE", "DC-AA", "DC-BA", }, "A", "B", HavePassed(), "C", HaveFailed(types.FailureNodeIsLeafNode, FailureNodeType(types.NodeTypeIt), "fail"), ), Entry("ContinueOnFailure - when the first It fails and we have flake attempts", false, func() { BeforeEach(rt.T("BE", DC("DC-BE")), OncePerOrdered) AfterEach(rt.T("AE", DC("DC-AE")), OncePerOrdered) Context("container", Ordered, ContinueOnFailure, FlakeAttempts(2), func() { BeforeAll(rt.T("BA", DC("DC-BA"))) AfterAll(rt.T("AA", DC("DC-AA"))) It("A", rt.T("A", func() { F("fail") })) It("B", rt.T("B")) It("C", rt.T("C")) }) }, []string{ "BE", "BA", "A", "A", "B", "C", "AA", "AE", "DC-AE", "DC-BE", "DC-AA", "DC-BA", }, "A", HaveFailed(types.FailureNodeIsLeafNode, FailureNodeType(types.NodeTypeIt), "fail", NumAttempts(2)), "B", "C", HavePassed(), ), Entry("ContinueOnFailure - when a middle It fails and we have flake attempts", false, func() { BeforeEach(rt.T("BE", DC("DC-BE")), OncePerOrdered) AfterEach(rt.T("AE", DC("DC-AE")), OncePerOrdered) Context("container", Ordered, ContinueOnFailure, FlakeAttempts(2), func() { BeforeAll(rt.T("BA", DC("DC-BA"))) AfterAll(rt.T("AA", DC("DC-AA"))) It("A", rt.T("A")) It("B", rt.T("B", func() { F("fail") })) It("C", rt.T("C")) }) }, []string{ "BE", "BA", "A", "B", "B", "C", "AA", "AE", "DC-AE", "DC-BE", "DC-AA", "DC-BA", }, "A", HavePassed(), "B", HaveFailed(types.FailureNodeIsLeafNode, FailureNodeType(types.NodeTypeIt), "fail", NumAttempts(2)), "C", HavePassed(), ), Entry("ContinueOnFailure - when the last It fails and we have flake attempts", false, func() { BeforeEach(rt.T("BE", DC("DC-BE")), OncePerOrdered) AfterEach(rt.T("AE", DC("DC-AE")), OncePerOrdered) Context("container", Ordered, ContinueOnFailure, FlakeAttempts(2), func() { BeforeAll(rt.T("BA", DC("DC-BA"))) AfterAll(rt.T("AA", DC("DC-AA"))) It("A", rt.T("A")) It("B", rt.T("B")) It("C", rt.T("C", func() { F("fail") })) }) }, []string{ "BE", "BA", "A", "B", "C", "C", "AA", "AE", "DC-AE", "DC-BE", "DC-AA", "DC-BA", }, "A", "B", HavePassed(), "C", HaveFailed(types.FailureNodeIsLeafNode, FailureNodeType(types.NodeTypeIt), "fail", NumAttempts(2)), ), Entry("ContinueOnFailure - when a repeating setup node fails", false, func() { BeforeEach(rt.T("BE", DC("DC-BE")), OncePerOrdered) AfterEach(rt.T("AE", DC("DC-AE")), OncePerOrdered) Context("container", Ordered, ContinueOnFailure, func() { BeforeAll(rt.T("BA", DC("DC-BA"))) AfterAll(rt.T("AA", DC("DC-AA"))) BeforeEach(rt.T("BE-inner", func() { F("fail") })) It("A", rt.T("A")) It("B", rt.T("B")) It("C", rt.T("C")) }) }, []string{ "BE", "BA", "BE-inner", "BE-inner", "BE-inner", "AA", "AE", "DC-AE", "DC-BE", "DC-AA", "DC-BA", }, "A", "B", "C", HaveFailed(types.FailureNodeInContainer, FailureNodeType(types.NodeTypeBeforeEach), "fail"), ), Entry("ContinueOnFailure - when a BeforeAllFails", false, func() { BeforeEach(rt.T("BE", DC("DC-BE")), OncePerOrdered) AfterEach(rt.T("AE", DC("DC-AE")), OncePerOrdered) Context("container", Ordered, ContinueOnFailure, func() { BeforeAll(rt.T("BA", func() { DeferCleanup(rt.T("DC-BA")) F("fail") })) AfterAll(rt.T("AA", DC("DC-AA"))) It("A", rt.T("A")) It("B", rt.T("B")) It("C", rt.T("C")) }) }, []string{ "BE", "BA", "AA", "AE", "DC-AE", "DC-BE", "DC-AA", "DC-BA", }, "A", HaveFailed(types.FailureNodeInContainer, FailureNodeType(types.NodeTypeBeforeAll), "fail"), "B", "C", HaveBeenSkippedWithMessage(SKIP_DUE_TO_FAILURE_IN_BEFORE_ALL), ), Entry("ContinueOnFailure - when a BeforeAllFails and flakey attempts are allowed", false, func() { BeforeEach(rt.T("BE", DC("DC-BE")), OncePerOrdered) AfterEach(rt.T("AE", DC("DC-AE")), OncePerOrdered) Context("container", Ordered, FlakeAttempts(2), ContinueOnFailure, func() { BeforeAll(rt.T("BA", func() { DeferCleanup(rt.T("DC-BA")) F("fail") })) AfterAll(rt.T("AA", DC("DC-AA"))) It("A", rt.T("A")) It("B", rt.T("B")) It("C", rt.T("C")) }) }, []string{ "BE", "BA", "AA", "DC-AA", "DC-BA", "BA", "AA", "AE", "DC-AE", "DC-BE", "DC-AA", "DC-BA", }, "A", HaveFailed(types.FailureNodeInContainer, FailureNodeType(types.NodeTypeBeforeAll), "fail"), "B", "C", HaveBeenSkippedWithMessage(SKIP_DUE_TO_FAILURE_IN_BEFORE_ALL), ), Entry("ContinueOnFailure - when a BeforeAll fails at first, but then succeeds and flakes are allowed", true, func() { BeforeEach(rt.T("BE", DC("DC-BE")), OncePerOrdered) AfterEach(rt.T("AE", DC("DC-AE")), OncePerOrdered) Context("container", Ordered, ContinueOnFailure, FlakeAttempts(3), func() { BeforeAll(rt.T("BA", FlakeyFailer(2))) AfterAll(rt.T("AA", DC("DC-AA"))) It("A", rt.T("A")) It("B", rt.T("B")) It("C", rt.T("C")) }) }, []string{ "BE", "BA", "AA", "DC-AA", "BA", "AA", "DC-AA", "BA", "A", "B", "C", "AA", "AE", "DC-AE", "DC-BE", "DC-AA", }, "A", "B", "C", HavePassed(), ), Entry("ContinueOnFailure - when a BeforeAllFails and flakey attempts are allowed", false, func() { BeforeEach(rt.T("BE", func() { DeferCleanup(rt.T("DC-BE")) F("fail") }), OncePerOrdered) AfterEach(rt.T("AE", DC("DC-AE")), OncePerOrdered) Context("container", Ordered, FlakeAttempts(2), ContinueOnFailure, func() { BeforeAll(rt.T("BA", DC("DC-BA"))) AfterAll(rt.T("AA", DC("DC-AA"))) It("A", rt.T("A")) It("B", rt.T("B")) It("C", rt.T("C")) }) }, []string{ "BE", "AE", "DC-AE", "DC-BE", "BE", "AE", "DC-AE", "DC-BE", }, "A", HaveFailed(types.FailureNodeAtTopLevel, FailureNodeType(types.NodeTypeBeforeEach), "fail"), "B", "C", HaveBeenSkippedWithMessage(SKIP_DUE_TO_FAILURE_IN_BEFORE_ALL), ), //All together now! Entry("Exhaustive example for setup nodes that run once per ordered container", true, func() { JustBeforeEach(rt.T("JBE-O", DC("DC-JBE-O"))) JustBeforeEach(rt.T("JBE-O-HO", DC("DC-JBE-O-HO")), OncePerOrdered) BeforeEach(rt.T("BE-O", DC("DC-O"))) BeforeEach(rt.T("BE-O-HO", DC("DC-O-HO")), OncePerOrdered) AfterEach(rt.T("AE-O-HO", DC("DC-AE-HO")), OncePerOrdered) AfterEach(rt.T("AE-O", DC("DC-AE-O"))) JustAfterEach(rt.T("JAE-O", DC("DC-JAE-O"))) JustAfterEach(rt.T("JAE-O-HO", DC("DC-JAE-O-HO")), OncePerOrdered) Context("container", func() { It("A", rt.T("A", DC("DC-A"))) It("B", rt.T("B")) Context("container", Ordered, func() { BeforeAll(rt.T("BA-1", DC("DC-BA-1"))) It("C", rt.T("C", DC("DC-C"))) It("D", rt.T("D")) AfterAll(rt.T("AA-1", DC("DC-AA-1"))) }) It("E", rt.T("E")) Context("container", Ordered, func() { BeforeAll(rt.T("BA-2", DC("DC-BA-2"))) AfterAll(rt.T("AA-2", DC("DC-AA-2"))) JustBeforeEach(rt.T("JBE-I", DC("DC-JBE-I"))) JustBeforeEach(rt.T("JBE-I-HO", DC("DC-JBE-I-HO")), OncePerOrdered) BeforeEach(rt.T("BE-I", DC("DC-BE-I"))) BeforeEach(rt.T("BE-I-HO", DC("DC-BE-I-HO")), OncePerOrdered) AfterEach(rt.T("AE-I-HO", DC("DC-AE-I-HO")), OncePerOrdered) AfterEach(rt.T("AE-I", DC("DC-AE-I"))) JustAfterEach(rt.T("JAE-I", DC("DC-JAE-I"))) JustAfterEach(rt.T("JAE-I-HO", DC("DC-JAE-I-HO")), OncePerOrdered) It("F", rt.T("F", DC("DC-F"))) It("G", rt.T("G")) Context("inner", func() { BeforeAll(rt.T("BA-3", DC("DC-BA-3"))) BeforeEach(rt.T("BE-II", DC("DC-BE-II"))) BeforeEach(rt.T("BE-II-HO", DC("DC-BE-II-HO")), OncePerOrdered) AfterEach(rt.T("AE-II-HO", DC("DC-AE-II-HO")), OncePerOrdered) AfterEach(rt.T("AE-II", DC("DC-AE-II"))) It("H", rt.T("H", DC("DC-H"))) It("I", rt.T("I")) AfterAll(rt.T("AA-3", DC("DC-AA-3"))) }) It("J", rt.T("J")) }) It("K", rt.T("K")) }) }, []string{ "BE-O", "BE-O-HO", "JBE-O", "JBE-O-HO", "A", "JAE-O", "JAE-O-HO", "AE-O-HO", "AE-O", "DC-AE-O", "DC-AE-HO", "DC-JAE-O-HO", "DC-JAE-O", "DC-A", "DC-JBE-O-HO", "DC-JBE-O", "DC-O-HO", "DC-O", "BE-O", "BE-O-HO", "JBE-O", "JBE-O-HO", "B", "JAE-O", "JAE-O-HO", "AE-O-HO", "AE-O", "DC-AE-O", "DC-AE-HO", "DC-JAE-O-HO", "DC-JAE-O", "DC-JBE-O-HO", "DC-JBE-O", "DC-O-HO", "DC-O", "BE-O", "BE-O-HO", "BA-1", "JBE-O", "JBE-O-HO", "C", "JAE-O", "AE-O", "DC-AE-O", "DC-JAE-O", "DC-C", "DC-JBE-O", "DC-O", "BE-O", "JBE-O", "D", "JAE-O", "JAE-O-HO", "AA-1", "AE-O-HO", "AE-O", "DC-AE-O", "DC-AE-HO", "DC-JAE-O-HO", "DC-JAE-O", "DC-JBE-O", "DC-O", "DC-JBE-O-HO", "DC-O-HO", "DC-AA-1", "DC-BA-1", "BE-O", "BE-O-HO", "JBE-O", "JBE-O-HO", "E", "JAE-O", "JAE-O-HO", "AE-O-HO", "AE-O", "DC-AE-O", "DC-AE-HO", "DC-JAE-O-HO", "DC-JAE-O", "DC-JBE-O-HO", "DC-JBE-O", "DC-O-HO", "DC-O", "BE-O", "BE-O-HO", "BA-2", "BE-I", "BE-I-HO", "JBE-O", "JBE-O-HO", "JBE-I", "JBE-I-HO", "F", "JAE-I", "JAE-I-HO", "JAE-O", "AE-I-HO", "AE-I", "AE-O", "DC-AE-O", "DC-AE-I", "DC-AE-I-HO", "DC-JAE-O", "DC-JAE-I-HO", "DC-JAE-I", "DC-F", "DC-JBE-I-HO", "DC-JBE-I", "DC-JBE-O", "DC-BE-I-HO", "DC-BE-I", "DC-O", "BE-O", "BE-I", "BE-I-HO", "JBE-O", "JBE-I", "JBE-I-HO", "G", "JAE-I", "JAE-I-HO", "JAE-O", "AE-I-HO", "AE-I", "AE-O", "DC-AE-O", "DC-AE-I", "DC-AE-I-HO", "DC-JAE-O", "DC-JAE-I-HO", "DC-JAE-I", "DC-JBE-I-HO", "DC-JBE-I", "DC-JBE-O", "DC-BE-I-HO", "DC-BE-I", "DC-O", "BE-O", "BE-I", "BE-I-HO", "BA-3", "BE-II", "BE-II-HO", "JBE-O", "JBE-I", "JBE-I-HO", "H", "JAE-I", "JAE-O", "AE-II-HO", "AE-II", "AE-I", "AE-O", "DC-AE-O", "DC-AE-I", "DC-AE-II", "DC-AE-II-HO", "DC-JAE-O", "DC-JAE-I", "DC-H", "DC-JBE-I", "DC-JBE-O", "DC-BE-II-HO", "DC-BE-II", "DC-BE-I", "DC-O", "BE-O", "BE-I", "BE-II", "BE-II-HO", "JBE-O", "JBE-I", "I", "JAE-I", "JAE-I-HO", "JAE-O", "AE-II-HO", "AE-II", "AA-3", "AE-I-HO", "AE-I", "AE-O", "DC-AE-O", "DC-AE-I", "DC-AE-I-HO", "DC-AE-II", "DC-AE-II-HO", "DC-JAE-O", "DC-JAE-I-HO", "DC-JAE-I", "DC-JBE-I", "DC-JBE-O", "DC-BE-II-HO", "DC-BE-II", "DC-BE-I", "DC-O", "DC-JBE-I-HO", "DC-BE-I-HO", "DC-AA-3", "DC-BA-3", "BE-O", "BE-I", "BE-I-HO", "JBE-O", "JBE-I", "JBE-I-HO", "J", "JAE-I", "JAE-I-HO", "JAE-O", "JAE-O-HO", "AE-I-HO", "AE-I", "AA-2", "AE-O-HO", "AE-O", "DC-AE-O", "DC-AE-HO", "DC-AE-I", "DC-AE-I-HO", "DC-JAE-O-HO", "DC-JAE-O", "DC-JAE-I-HO", "DC-JAE-I", "DC-JBE-I-HO", "DC-JBE-I", "DC-JBE-O", "DC-BE-I-HO", "DC-BE-I", "DC-O", "DC-JBE-O-HO", "DC-O-HO", "DC-AA-2", "DC-BA-2", "BE-O", "BE-O-HO", "JBE-O", "JBE-O-HO", "K", "JAE-O", "JAE-O-HO", "AE-O-HO", "AE-O", "DC-AE-O", "DC-AE-HO", "DC-JAE-O-HO", "DC-JAE-O", "DC-JBE-O-HO", "DC-JBE-O", "DC-O-HO", "DC-O", }, "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", HavePassed(), ), // Bug reported in #1198 Entry("bug reported in #1198 - probably due to attempts to ensure deterministic order", true, func() { Describe("outer", func() { // the only non-Ordered Describe OrderedRun("a") OrderedRun("b") OrderedRun("c") }) }, []string{"a1", "a2", "a3", "a4", "a5", "b1", "b2", "b3", "b4", "b5", "c1", "c2", "c3", "c4", "c5"}), ) // OrderedRun creates a sequence of tests, all are intended to be Ordered - this is the reproducer provided in #1198 func OrderedRun(description string) { Describe(description, Ordered, func() { It(fmt.Sprint(description, "1"), func() { rt.Run(description + "1") }) Sub2(description) Sub3(description) Describe("inner", func() { Sub4(description) }) }) } func Sub2(description string) { It(fmt.Sprint(description, "2"), func() { rt.Run(description + "2") }) } func Sub3(description string) { It(fmt.Sprint(description, "3"), func() { rt.Run(description + "3") }) } func Sub4(description string) { Describe("deeply nested 1", func() { It(fmt.Sprint(description, "4"), func() { rt.Run(description + "4") }) }) Describe("deeply nested 2", func() { It(fmt.Sprint(description, "5"), func() { rt.Run(description + "5") }) }) } golang-github-onsi-ginkgo-v2-2.22.0/internal/internal_integration/parallel_test.go000066400000000000000000000150521472321612100303120ustar00rootroot00000000000000package internal_integration_test import ( "time" . "github.com/onsi/ginkgo/v2" "github.com/onsi/ginkgo/v2/internal" . "github.com/onsi/ginkgo/v2/internal/test_helpers" "github.com/onsi/ginkgo/v2/types" . "github.com/onsi/gomega" ) var _ = Describe("Running tests in parallel", func() { var conf2 types.SuiteConfig var reporter2 *FakeReporter var rt2 *RunTracker var serialValidator chan interface{} var fixture = func(rt *RunTracker, proc int) { SynchronizedBeforeSuite(func() []byte { rt.Run("before-suite-1") return []byte("floop") }, func(proc1Data []byte) { rt.Run("before-suite-2 " + string(proc1Data)) }) It("A", rt.T("A", func() { time.Sleep(10 * time.Millisecond) })) It("B", rt.T("B", func() { time.Sleep(10 * time.Millisecond) })) It("C", rt.T("C", func() { time.Sleep(10 * time.Millisecond) })) It("D", rt.T("D", func() { time.Sleep(10 * time.Millisecond) })) It("E", rt.T("E", func() { time.Sleep(10 * time.Millisecond) })) It("F", rt.T("F", func() { time.Sleep(10 * time.Millisecond) })) Context("Ordered", Ordered, func() { It("OA", rt.T("OA", func() { time.Sleep(10 * time.Millisecond) })) It("OB", rt.T("OB", func() { time.Sleep(10 * time.Millisecond) })) It("OC", rt.T("OC", func() { time.Sleep(10 * time.Millisecond) })) }) It("G", Serial, rt.T("G", func() { Ω(serialValidator).Should(BeClosed()) time.Sleep(10 * time.Millisecond) })) It("H", Serial, rt.T("H", func() { Ω(serialValidator).Should(BeClosed()) time.Sleep(10 * time.Millisecond) })) It("I", Serial, rt.T("I", func() { Ω(serialValidator).Should(BeClosed()) time.Sleep(10 * time.Millisecond) })) Context("Ordered and Serial", Ordered, Serial, func() { It("OSA", rt.T("OSA", func() { Ω(serialValidator).Should(BeClosed()) time.Sleep(10 * time.Millisecond) })) It("OSB", rt.T("OSB", func() { Ω(serialValidator).Should(BeClosed()) time.Sleep(10 * time.Millisecond) })) }) SynchronizedAfterSuite(rt.T("after-suite-1", func() { if proc == 2 { close(serialValidator) } }), rt.T("after-suite-2")) } BeforeEach(func() { serialValidator = make(chan interface{}) //set up configuration for proc 1 and proc 2 //SetUpForParallel starts up a server, sets up a client, and sets up the exitChannels map - they're all cleaned up automatically after the test SetUpForParallel(2) conf.ParallelProcess = 1 conf.RandomizeAllSpecs = true conf.RandomSeed = 17 conf2 = conf //makes a copy conf2.ParallelProcess = 2 // construct suite 1... suite1 := internal.NewSuite() WithSuite(suite1, func() { fixture(rt, 1) Ω(suite1.BuildTree()).Should(Succeed()) }) //now construct suite 2... suite2 := internal.NewSuite() rt2 = NewRunTracker() WithSuite(suite2, func() { fixture(rt2, 2) Ω(suite2.BuildTree()).Should(Succeed()) }) finished := make(chan bool) exit1 := exitChannels[1] //avoid a race around exitChannels access in a separate goroutine //now launch suite 1... go func() { success, _ := suite1.Run("proc 1", Label("TopLevelLabel"), "/path/to/suite", failer, reporter, writer, outputInterceptor, interruptHandler, client, noopProgressSignalRegistrar, conf) finished <- success close(exit1) }() //and launch suite 2... reporter2 = NewFakeReporter() exit2 := exitChannels[2] //avoid a race around exitChannels access in a separate goroutine go func() { success, _ := suite2.Run("proc 2", Label("TopLevelLabel"), "/path/to/suite", internal.NewFailer(), reporter2, writer, outputInterceptor, interruptHandler, client, noopProgressSignalRegistrar, conf2) finished <- success close(exit2) }() // eventually both suites should finish (and succeed)... Eventually(finished).Should(Receive(Equal(true))) Eventually(finished).Should(Receive(Equal(true))) // and now we're ready to make asserts on the various run trackers and reporters }) It("distributes tests across the parallel procs and runs them", func() { Ω(rt).Should(HaveRun("before-suite-1")) Ω(rt).Should(HaveRun("before-suite-2 floop")) Ω(rt).Should(HaveRun("after-suite-1")) Ω(rt).Should(HaveRun("after-suite-2")) Ω(rt2).ShouldNot(HaveRun("before-suite-1")) Ω(rt2).Should(HaveRun("before-suite-2 floop")) Ω(rt2).Should(HaveRun("after-suite-1")) Ω(rt2).ShouldNot(HaveRun("after-suite-2")) allRuns := append(rt.TrackedRuns(), rt2.TrackedRuns()...) Ω(allRuns).Should(ConsistOf( "before-suite-1", "before-suite-2 floop", "after-suite-1", "after-suite-2", "before-suite-2 floop", "after-suite-1", "A", "B", "C", "D", "E", "F", "G", "H", "I", "OA", "OB", "OC", "OSA", "OSB", //all ran )) Ω(reporter.Did.Names()).ShouldNot(BeEmpty()) Ω(reporter2.Did.Names()).ShouldNot(BeEmpty()) names := append(reporter.Did.Names(), reporter2.Did.Names()...) Ω(names).Should(ConsistOf("A", "B", "C", "D", "E", "F", "G", "H", "I", "OA", "OB", "OC", "OSA", "OSB")) for _, report := range reporter.Did { Ω(report.ParallelProcess).Should(Equal(1)) Ω(report.RunningInParallel).Should(BeTrue()) } for _, report := range reporter2.Did { Ω(report.ParallelProcess).Should(Equal(2)) Ω(report.RunningInParallel).Should(BeTrue()) } }) It("only runs serial tests on proc 1, after the other proc has finished", func() { names := reporter.Did.Names() Ω(names).Should(ContainElements("G", "H", "I", "OSA", "OSB")) for idx, name := range names { if name == "OSA" { Ω(names[idx+1]).Should(Equal("OSB")) break } } Ω(reporter2.Did.Names()).ShouldNot(ContainElements("G", "H", "I", "OSA", "OSB")) }) It("it ensures specs in an ordered container run on the same process and are ordered", func() { names1 := reporter.Did.Names() names2 := reporter2.Did.Names() in1, _ := ContainElement("OA").Match(names1) winner := names1 if !in1 { winner = names2 } found := false for idx, name := range winner { if name == "OA" { found = true Ω(winner[idx+1]).Should(Equal("OB")) Ω(winner[idx+2]).Should(Equal("OC")) break } } Ω(found).Should(BeTrue()) }) It("reports the correct statistics", func() { Ω(reporter.End.PreRunStats.TotalSpecs).Should(Equal(14)) Ω(reporter2.End.PreRunStats.TotalSpecs).Should(Equal(14)) Ω(reporter.End.PreRunStats.SpecsThatWillRun).Should(Equal(14)) Ω(reporter2.End.PreRunStats.SpecsThatWillRun).Should(Equal(14)) Ω(reporter.End.SpecReports.WithLeafNodeType(types.NodeTypeIt).CountWithState(types.SpecStatePassed) + reporter2.End.SpecReports.WithLeafNodeType(types.NodeTypeIt).CountWithState(types.SpecStatePassed)).Should(Equal(14)) }) }) golang-github-onsi-ginkgo-v2-2.22.0/internal/internal_integration/progress_report_test.go000066400000000000000000000376231472321612100317650ustar00rootroot00000000000000package internal_integration_test import ( "time" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/ginkgo/v2/internal/test_helpers" "github.com/onsi/ginkgo/v2/types" . "github.com/onsi/gomega" ) var _ = Describe("Progress Reporting", func() { BeforeEach(func() { conf.PollProgressAfter = 100 * time.Millisecond conf.PollProgressInterval = 50 * time.Millisecond }) AfterEach(func() { Ω(triggerProgressSignal).Should(BeNil(), "It should have unregistered the progress signal handler") }) Context("when progress is reported in a BeforeSuite", func() { BeforeEach(func() { success, _ := RunFixture("emitting spec progress in a BeforeSuite", func() { BeforeSuite(func() { cl = types.NewCodeLocation(0) writer.Print("ginkgo-writer-content") triggerProgressSignal() }) It("runs", func() {}) }) Ω(success).Should(BeTrue()) }) It("emits progress when asked and includes source code", func() { Ω(reporter.ProgressReports).Should(HaveLen(1)) pr := reporter.ProgressReports[0] Ω(pr.Message).Should(Equal("{{bold}}You've requested a progress report:{{/}}")) Ω(pr.CurrentNodeType).Should(Equal(types.NodeTypeBeforeSuite)) Ω(pr.LeafNodeLocation).Should(Equal(clLine(-1))) Ω(pr.CurrentStepText).Should(Equal("")) Ω(pr.CapturedGinkgoWriterOutput).Should(Equal("ginkgo-writer-content")) Ω(pr.SpecGoroutine().State).Should(Equal("running")) Ω(pr.SpecGoroutine().Stack).Should(HaveHighlightedStackLine(clLine(2))) By("validating that source code is extracted") targetCl := clLine(2) var functionCall types.FunctionCall for _, call := range pr.SpecGoroutine().Stack { if call.Filename == targetCl.FileName && call.Line == targetCl.LineNumber { functionCall = call break } } Ω(functionCall).ShouldNot(BeZero()) //note - these are the lines from up above Ω(functionCall.Source).Should(Equal([]string{ "\t\t\t\t\tcl = types.NewCodeLocation(0)", "\t\t\t\t\twriter.Print(\"ginkgo-writer-content\")", "\t\t\t\t\ttriggerProgressSignal()", "\t\t\t\t})", "\t\t\t\tIt(\"runs\", func() {})", })) Ω(functionCall.SourceHighlight).Should(Equal(2)) }) }) Context("when progress is emitted in an It", func() { BeforeEach(func() { success, _ := RunFixture("emitting spec progress", func() { Describe("a container", func() { It("A", func() { cl = types.NewCodeLocation(0) triggerProgressSignal() }) }) }) Ω(success).Should(BeTrue()) }) It("emits progress when asked ", func() { Ω(reporter.ProgressReports).Should(HaveLen(1)) pr := reporter.ProgressReports[0] Ω(pr.ContainerHierarchyTexts).Should(ConsistOf("a container")) Ω(pr.LeafNodeLocation).Should(Equal(clLine(-1))) Ω(pr.LeafNodeText).Should(Equal("A")) Ω(pr.CurrentNodeType).Should(Equal(types.NodeTypeIt)) Ω(pr.CurrentNodeLocation).Should(Equal(clLine(-1))) Ω(pr.CurrentStepText).Should(Equal("")) Ω(pr.SpecGoroutine().State).Should(Equal("running")) Ω(pr.SpecGoroutine().Stack).Should(HaveHighlightedStackLine(clLine(1))) }) }) Context("when progress is emitted in a setup node", func() { BeforeEach(func() { success, _ := RunFixture("emitting spec progress", func() { Describe("a container", func() { BeforeEach(func() { cl = types.NewCodeLocation(0) triggerProgressSignal() }) It("A", func() {}) }) }) Ω(success).Should(BeTrue()) }) It("emits progress when asked ", func() { Ω(reporter.ProgressReports).Should(HaveLen(1)) pr := reporter.ProgressReports[0] Ω(pr.ContainerHierarchyTexts).Should(ConsistOf("a container")) Ω(pr.LeafNodeLocation).Should(Equal(clLine(4))) Ω(pr.LeafNodeText).Should(Equal("A")) Ω(pr.CurrentNodeType).Should(Equal(types.NodeTypeBeforeEach)) Ω(pr.CurrentNodeLocation).Should(Equal(clLine(-1))) Ω(pr.CurrentStepText).Should(Equal("")) Ω(pr.SpecGoroutine().State).Should(Equal("running")) Ω(pr.SpecGoroutine().Stack).Should(HaveHighlightedStackLine(clLine(1))) }) }) Context("when progress is emitted in a By", func() { BeforeEach(func() { success, _ := RunFixture("emitting spec progress", func() { Describe("a container", func() { It("A", func() { cl = types.NewCodeLocation(0) By("B") By("C", func() { triggerProgressSignal() }) By("D") }) }) }) Ω(success).Should(BeTrue()) }) It("emits progress when asked and includes the By step", func() { Ω(reporter.ProgressReports).Should(HaveLen(1)) pr := reporter.ProgressReports[0] Ω(pr.ContainerHierarchyTexts).Should(ConsistOf("a container")) Ω(pr.LeafNodeLocation).Should(Equal(clLine(-1))) Ω(pr.LeafNodeText).Should(Equal("A")) Ω(pr.CurrentNodeType).Should(Equal(types.NodeTypeIt)) Ω(pr.CurrentNodeLocation).Should(Equal(clLine(-1))) Ω(pr.CurrentStepText).Should(Equal("C")) Ω(pr.CurrentStepLocation).Should(Equal(clLine(2))) Ω(pr.SpecGoroutine().State).Should(Equal("running")) Ω(pr.SpecGoroutine().Stack).Should(HaveHighlightedStackLine(clLine(3))) }) }) Context("when progress is emitted after a By", func() { BeforeEach(func() { success, _ := RunFixture("emitting spec progress", func() { Describe("a container", func() { It("A", func() { cl = types.NewCodeLocation(0) By("B") By("C") triggerProgressSignal() By("D") }) }) }) Ω(success).Should(BeTrue()) }) It("emits progress when asked and includes the last run By step", func() { Ω(reporter.ProgressReports).Should(HaveLen(1)) pr := reporter.ProgressReports[0] Ω(pr.ContainerHierarchyTexts).Should(ConsistOf("a container")) Ω(pr.LeafNodeLocation).Should(Equal(clLine(-1))) Ω(pr.LeafNodeText).Should(Equal("A")) Ω(pr.CurrentNodeType).Should(Equal(types.NodeTypeIt)) Ω(pr.CurrentNodeLocation).Should(Equal(clLine(-1))) Ω(pr.CurrentStepText).Should(Equal("C")) Ω(pr.CurrentStepLocation).Should(Equal(clLine(2))) Ω(pr.SpecGoroutine().State).Should(Equal("running")) Ω(pr.SpecGoroutine().Stack).Should(HaveHighlightedStackLine(clLine(3))) }) }) Context("when progress is emitted in a node with no By", func() { BeforeEach(func() { success, _ := RunFixture("emitting spec progress", func() { Describe("a container", func() { BeforeEach(func() { By("B") }) It("A", func() { cl = types.NewCodeLocation(0) triggerProgressSignal() }) }) }) Ω(success).Should(BeTrue()) }) It("emits progress when asked and does not include the By step", func() { Ω(reporter.ProgressReports).Should(HaveLen(1)) pr := reporter.ProgressReports[0] Ω(pr.ContainerHierarchyTexts).Should(ConsistOf("a container")) Ω(pr.LeafNodeLocation).Should(Equal(clLine(-1))) Ω(pr.LeafNodeText).Should(Equal("A")) Ω(pr.CurrentNodeType).Should(Equal(types.NodeTypeIt)) Ω(pr.CurrentNodeLocation).Should(Equal(clLine(-1))) Ω(pr.CurrentStepText).Should(Equal("")) Ω(pr.SpecGoroutine().State).Should(Equal("running")) Ω(pr.SpecGoroutine().Stack).Should(HaveHighlightedStackLine(clLine(1))) }) }) Context("when a goroutine is launched by the spec", func() { BeforeEach(func() { success, _ := RunFixture("emitting spec progress", func() { Describe("a container", func() { It("A", func() { cl = types.NewCodeLocation(0) c := make(chan bool, 0) go func() { c <- true <-c }() <-c triggerProgressSignal() c <- true }) }) }) Ω(success).Should(BeTrue()) }) It("lists the goroutine as a line of interest", func() { Ω(reporter.ProgressReports).Should(HaveLen(1)) pr := reporter.ProgressReports[0] Ω(pr.ContainerHierarchyTexts).Should(ConsistOf("a container")) Ω(pr.LeafNodeLocation).Should(Equal(clLine(-1))) Ω(pr.LeafNodeText).Should(Equal("A")) Ω(pr.CurrentNodeType).Should(Equal(types.NodeTypeIt)) Ω(pr.CurrentNodeLocation).Should(Equal(clLine(-1))) Ω(pr.CurrentStepText).Should(Equal("")) Ω(pr.SpecGoroutine().State).Should(Equal("running")) Ω(pr.SpecGoroutine().Stack).Should(HaveHighlightedStackLine(clLine(7))) var waitingGoroutine types.Goroutine Ω(pr.HighlightedGoroutines()).Should(ContainElement(HaveField("State", "chan receive"), &waitingGoroutine)) Ω(waitingGoroutine.Stack).Should(HaveHighlightedStackLine(clLine(2)), "the goroutine invocation") Ω(waitingGoroutine.Stack).Should(HaveHighlightedStackLine(clLine(4)), "the <-c line in the goroutine") }) }) Context("when a test takes longer then the configured PollProgressAfter", func() { BeforeEach(func() { success, _ := RunFixture("emitting spec progress", func() { Describe("a container", func() { It("A", func() { cl = types.NewCodeLocation(0) time.Sleep(300 * time.Millisecond) }) }) }) Ω(success).Should(BeTrue()) }) It("emits progress periodically", func() { Ω(len(reporter.ProgressReports)).Should(BeNumerically(">", 1)) for _, pr := range reporter.ProgressReports { Ω(pr.Message).Should(Equal("{{bold}}Automatically polling progress:{{/}}")) Ω(pr.ContainerHierarchyTexts).Should(ConsistOf("a container")) Ω(pr.LeafNodeLocation).Should(Equal(clLine(-1))) Ω(pr.LeafNodeText).Should(Equal("A")) Ω(pr.CurrentNodeType).Should(Equal(types.NodeTypeIt)) Ω(pr.CurrentNodeLocation).Should(Equal(clLine(-1))) } }) }) Context("when a test takes longer then the overridden PollProgressAfter", func() { BeforeEach(func() { success, _ := RunFixture("emitting spec progress", func() { Describe("a container", func() { It("A", func() { cl = types.NewCodeLocation(0) time.Sleep(50 * time.Millisecond) }, PollProgressAfter(20*time.Millisecond), PollProgressInterval(10*time.Millisecond)) }) }) Ω(success).Should(BeTrue()) }) It("emits progress periodically", func() { Ω(len(reporter.ProgressReports)).Should(BeNumerically(">", 1)) for _, pr := range reporter.ProgressReports { Ω(pr.ContainerHierarchyTexts).Should(ConsistOf("a container")) Ω(pr.LeafNodeLocation).Should(Equal(clLine(-1))) Ω(pr.LeafNodeText).Should(Equal("A")) Ω(pr.CurrentNodeType).Should(Equal(types.NodeTypeIt)) Ω(pr.CurrentNodeLocation).Should(Equal(clLine(-1))) } }) }) Context("SynchronizedBeforeSuite can also be decorated", func() { BeforeEach(func() { success, _ := RunFixture("emitting spec progress", func() { SynchronizedBeforeSuite(func() []byte { cl = types.NewCodeLocation(0) return []byte("hello") }, func(_ []byte) { time.Sleep(50 * time.Millisecond) }, PollProgressAfter(20*time.Millisecond), PollProgressInterval(10*time.Millisecond)) Describe("a container", func() { It("A", func() {}) }) }) Ω(success).Should(BeTrue()) }) It("emits progress periodically", func() { Ω(len(reporter.ProgressReports)).Should(BeNumerically(">", 1)) for _, pr := range reporter.ProgressReports { Ω(pr.CurrentNodeType).Should(Equal(types.NodeTypeSynchronizedBeforeSuite)) Ω(pr.CurrentNodeLocation).Should(Equal(clLine(-1))) } }) }) Context("DeferCleanup can also be decorated", func() { BeforeEach(func() { success, _ := RunFixture("emitting spec progress", func() { Describe("a container", func() { It("A", func() { cl = types.NewCodeLocation(0) DeferCleanup(func() { time.Sleep(50 * time.Millisecond) }, PollProgressAfter(20*time.Millisecond), PollProgressInterval(10*time.Millisecond)) }) }) }) Ω(success).Should(BeTrue()) }) It("emits progress periodically", func() { Ω(len(reporter.ProgressReports)).Should(BeNumerically(">", 1)) for _, pr := range reporter.ProgressReports { Ω(pr.CurrentNodeType).Should(Equal(types.NodeTypeCleanupAfterEach)) Ω(pr.CurrentNodeLocation).Should(Equal(clLine(1))) } }) }) Context("when an additional progress report provider has been registered with the current context", func() { BeforeEach(func() { success, _ := RunFixture("emitting spec progress", func() { Describe("a container", func() { It("A", func(ctx SpecContext) { cancel := ctx.AttachProgressReporter(func() string { return "Some Additional Information" }) cl = types.NewCodeLocation(0) triggerProgressSignal() cancel() ctx.AttachProgressReporter(func() string { return "Some Different Information (never cancelled)" }) triggerProgressSignal() cancel = ctx.AttachProgressReporter(func() string { return "Yet More Information" }) triggerProgressSignal() cancel() triggerProgressSignal() }) AfterEach(func() { triggerProgressSignal() }) }) }) Ω(success).Should(BeTrue()) }) It("includes information from that progress report provider", func() { Ω(reporter.ProgressReports).Should(HaveLen(5)) pr := reporter.ProgressReports[0] Ω(pr.ContainerHierarchyTexts).Should(ConsistOf("a container")) Ω(pr.LeafNodeLocation).Should(Equal(clLine(-2))) Ω(pr.LeafNodeText).Should(Equal("A")) Ω(pr.CurrentNodeType).Should(Equal(types.NodeTypeIt)) Ω(pr.CurrentNodeLocation).Should(Equal(clLine(-2))) Ω(pr.CurrentStepText).Should(Equal("")) Ω(pr.SpecGoroutine().State).Should(Equal("running")) Ω(pr.SpecGoroutine().Stack).Should(HaveHighlightedStackLine(clLine(1))) Ω(pr.AdditionalReports).Should(Equal([]string{"Some Additional Information"})) pr = reporter.ProgressReports[1] Ω(pr.AdditionalReports).Should(Equal([]string{"Some Different Information (never cancelled)"})) pr = reporter.ProgressReports[2] Ω(pr.AdditionalReports).Should(Equal([]string{"Some Different Information (never cancelled)", "Yet More Information"})) pr = reporter.ProgressReports[3] Ω(pr.AdditionalReports).Should(Equal([]string{"Some Different Information (never cancelled)"})) pr = reporter.ProgressReports[4] Ω(pr.CurrentNodeType).Should(Equal(types.NodeTypeAfterEach)) Ω(pr.AdditionalReports).Should(BeEmpty()) }) }) Context("when a global progress report provider has been registered", func() { BeforeEach(func() { success, _ := RunFixture("emitting spec progress", func() { Describe("a container", func() { It("A", func(ctx SpecContext) { cancelGlobal := AttachProgressReporter(func() string { return "Some Global Information" }) AttachProgressReporter(func() string { return "Some More (Never Cancelled) Global Information" }) ctx.AttachProgressReporter(func() string { return "Some Additional Information" }) cl = types.NewCodeLocation(0) triggerProgressSignal() cancelGlobal() triggerProgressSignal() }) It("B", func() { triggerProgressSignal() }) }) }) Ω(success).Should(BeTrue()) }) It("includes information from that progress report provider", func() { Ω(reporter.ProgressReports).Should(HaveLen(3)) pr := reporter.ProgressReports[0] Ω(pr.LeafNodeText).Should(Equal("A")) Ω(pr.AdditionalReports).Should(Equal([]string{"Some Additional Information", "Some Global Information", "Some More (Never Cancelled) Global Information"})) pr = reporter.ProgressReports[1] Ω(pr.LeafNodeText).Should(Equal("A")) Ω(pr.AdditionalReports).Should(Equal([]string{"Some Additional Information", "Some More (Never Cancelled) Global Information"})) pr = reporter.ProgressReports[2] Ω(pr.LeafNodeText).Should(Equal("B")) Ω(pr.AdditionalReports).Should(Equal([]string{"Some More (Never Cancelled) Global Information"})) }) }) Context("when a global progress reporter fails", func() { BeforeEach(func() { success, _ := RunFixture("emitting spec progress", func() { Describe("a container", func() { It("A", func(ctx SpecContext) { AttachProgressReporter(func() string { F("bam") return "Some Global Information" }) triggerProgressSignal() }) }) }) Ω(success).Should(BeFalse()) }) It("marks the spec as failed", func() { Ω(reporter.Did.Find("A")).Should(HaveFailed("bam")) }) }) }) golang-github-onsi-ginkgo-v2-2.22.0/internal/internal_integration/report_each_test.go000066400000000000000000000225141472321612100310120ustar00rootroot00000000000000package internal_integration_test import ( "time" . "github.com/onsi/ginkgo/v2" "github.com/onsi/ginkgo/v2/internal/interrupt_handler" . "github.com/onsi/ginkgo/v2/internal/test_helpers" "github.com/onsi/ginkgo/v2/types" . "github.com/onsi/gomega" "github.com/onsi/gomega/format" ) var _ = Describe("Sending reports to ReportBeforeEach and ReportAfterEach nodes", func() { var reports map[string]Reports BeforeEach(func() { conf.SkipStrings = []string{"flag-skipped"} reports = map[string]Reports{} success, hPF := RunFixture("suite with reporting nodes", func() { BeforeSuite(rt.T("before-suite")) AfterSuite(rt.T("after-suite")) ReportAfterEach(func(report types.SpecReport) { rt.Run("outer-RAE") reports["outer-RAE"] = append(reports["outer-RAE"], report) }) Describe("top-level container", func() { ReportBeforeEach(func(report types.SpecReport) { rt.Run("inner-RBE") reports["inner-RBE"] = append(reports["inner-RBE"], report) }) ReportAfterEach(func(report types.SpecReport) { rt.Run("inner-RAE") reports["inner-RAE"] = append(reports["inner-RAE"], report) }) It("passes", rt.T("passes")) It("fails", rt.T("fails", func() { F("fail") })) It("panics", rt.T("panics", func() { panic("boom") })) PIt("is pending", rt.T("pending")) It("is Skip()ed", func() { rt.Run("skipped") FixtureSkip("nah...") }) It("is flag-skipped", rt.T("flag-skipped")) Context("when the ReportAfterEach node fails", func() { It("also passes", rt.T("also-passes")) ReportAfterEach(func(report types.SpecReport) { rt.Run("failing-RAE") reports["failing-RAE"] = append(reports["failing-RAE"], report) F("fail") }) }) Context("when the ReportAfterEach node fails in a skipped test", func() { It("is also flag-skipped", rt.T("also-flag-skipped")) ReportAfterEach(func(report types.SpecReport) { rt.Run("failing-in-skip-RAE") reports["failing-skipped-RAE"] = append(reports["failing-skipped-RAE"], report) F("fail") }) }) Context("when stuff is emitted to writers and stdout/stderr", func() { It("writes stuff", rt.T("writer", func() { writer.Println("GinkgoWriter from It") outputInterceptor.AppendInterceptedOutput("Output from It\n") })) ReportAfterEach(func(report types.SpecReport) { rt.Run("writing-reporter") reports["writing"] = append(reports["writing"], report) writer.Println("GinkgoWriter from ReportAfterEach") outputInterceptor.AppendInterceptedOutput("Output from ReportAfterEach\n") }) }) Context("when a ReportBeforeEach fails", func() { ReportBeforeEach(func(report types.SpecReport) { rt.Run("failing-RBE") reports["failing-RBE"] = append(reports["failing-RBE"], report) F("fail") }) ReportBeforeEach(func(report types.SpecReport) { rt.Run("not-failing-RBE") reports["not-failing-RBE"] = append(reports["not-failing-RBE"], report) }) It("does not run", rt.T("does-not-run")) }) Context("when a reporter is interrupted", func() { It("passes yet again", rt.T("passes-yet-again")) It("skipped by interrupt", rt.T("skipped-by-interrupt")) ReportAfterEach(func(report types.SpecReport) { if interruptHandler.Status().Level == interrupt_handler.InterruptLevelUninterrupted { interruptHandler.Interrupt(interrupt_handler.InterruptCauseSignal) time.Sleep(time.Hour) } rt.Run("interrupt-reporter") reports["interrupt"] = append(reports["interrupt"], report) }) }) Context("when a after each reporter times out", func() { It("passes", rt.T("passes")) ReportAfterEach(func(ctx SpecContext, report types.SpecReport) { select { case <-ctx.Done(): rt.Run("timeout-reporter") reports["timeout"] = append(reports["timeout"], report) case <-time.After(100 * time.Millisecond): } }, NodeTimeout(10*time.Millisecond)) }) }) ReportBeforeEach(func(report types.SpecReport) { rt.Run("outer-RBE") reports["outer-RBE"] = append(reports["outer-RBE"], report) }) }) Ω(success).Should(BeFalse()) Ω(hPF).Should(BeFalse()) }) It("runs ReportAfterEach blocks in the correct order", func() { Ω(rt).Should(HaveTracked( "before-suite", "outer-RBE", "inner-RBE", "passes", "inner-RAE", "outer-RAE", "outer-RBE", "inner-RBE", "fails", "inner-RAE", "outer-RAE", "outer-RBE", "inner-RBE", "panics", "inner-RAE", "outer-RAE", "outer-RBE", "inner-RBE", "inner-RAE", "outer-RAE", // pending test "outer-RBE", "inner-RBE", "skipped", "inner-RAE", "outer-RAE", "outer-RBE", "inner-RBE", "inner-RAE", "outer-RAE", // flag-skipped test "outer-RBE", "inner-RBE", "also-passes", "failing-RAE", "inner-RAE", "outer-RAE", "outer-RBE", "inner-RBE", "failing-in-skip-RAE", "inner-RAE", "outer-RAE", // is also flag-skipped "outer-RBE", "inner-RBE", "writer", "writing-reporter", "inner-RAE", "outer-RAE", "outer-RBE", "inner-RBE", "failing-RBE", "not-failing-RBE", "inner-RAE", "outer-RAE", "outer-RBE", "inner-RBE", "passes-yet-again", "inner-RAE", "outer-RAE", "outer-RBE", "inner-RBE", "interrupt-reporter", "inner-RAE", "outer-RAE", // skipped by interrupt "outer-RBE", "inner-RBE", "timeout-reporter", "inner-RAE", "outer-RAE", // skipped by timeout "after-suite", )) }) It("does not include the before-suite or after-suite reports", func() { Ω(reports["outer-RAE"].FindByLeafNodeType(types.NodeTypeBeforeSuite)).Should(BeZero()) Ω(reports["outer-RAE"].FindByLeafNodeType(types.NodeTypeAfterSuite)).Should(BeZero()) }) It("submits the correct reports to the reporters", func() { format.MaxLength = 10000 for _, name := range []string{"passes", "fails", "panics", "is Skip()ed", "is flag-skipped", "is also flag-skipped"} { expected := reporter.Did.Find(name) expected.SpecEvents = nil actual := reports["outer-RAE"].Find(name) actual.SpecEvents = nil Ω(actual).Should(Equal(expected)) actual = reports["inner-RAE"].Find(name) actual.SpecEvents = nil Ω(actual).Should(Equal(expected)) } Ω(reports["outer-RBE"].Find("passes")).ShouldNot(BeZero()) Ω(reports["outer-RBE"].Find("fails")).ShouldNot(BeZero()) Ω(reports["outer-RBE"].Find("panics")).ShouldNot(BeZero()) Ω(reports["outer-RBE"].Find("is pending")).Should(BePending()) Ω(reports["outer-RAE"].Find("is flag-skipped")).Should(HaveBeenSkipped()) Ω(reports["outer-RAE"].Find("passes")).Should(HavePassed()) Ω(reports["outer-RAE"].Find("fails")).Should(HaveFailed("fail")) Ω(reports["outer-RAE"].Find("panics")).Should(HavePanicked("boom")) Ω(reports["outer-RAE"].Find("is pending")).Should(BePending()) Ω(reports["outer-RAE"].Find("is Skip()ed").State).Should(Equal(types.SpecStateSkipped)) Ω(reports["outer-RAE"].Find("is Skip()ed").Failure.Message).Should(Equal("nah...")) Ω(reports["outer-RAE"].Find("is flag-skipped")).Should(HaveBeenSkipped()) }) It("handles reporters that fail", func() { Ω(reports["failing-RAE"].Find("also passes")).Should(HavePassed()) Ω(reports["outer-RAE"].Find("also passes")).Should(HaveFailed("fail")) Ω(reports["inner-RAE"].Find("also passes")).Should(HaveFailed("fail")) Ω(reporter.Did.Find("also passes")).Should(HaveFailed("fail"), FailureNodeType(types.NodeTypeReportAfterEach)) Ω(reports["failing-RBE"].Find("does not run")).ShouldNot(BeZero()) Ω(reports["not-failing-RBE"].Find("does not run")).Should(HaveFailed("fail")) Ω(reports["outer-RAE"].Find("does not run")).Should(HaveFailed("fail")) Ω(reports["inner-RAE"].Find("does not run")).Should(HaveFailed("fail")) Ω(reporter.Did.Find("does not run")).Should(HaveFailed("fail", FailureNodeType(types.NodeTypeReportBeforeEach))) }) It("handles reporters that fail, even in skipped specs", func() { Ω(reports["failing-skipped-RAE"].Find("is also flag-skipped")).Should(HaveBeenSkipped()) Ω(reports["outer-RAE"].Find("is also flag-skipped")).Should(HaveFailed("fail")) Ω(reports["inner-RAE"].Find("is also flag-skipped")).Should(HaveFailed("fail")) Ω(reporter.Did.Find("is also flag-skipped")).Should(HaveFailed("fail")) }) It("captures output from reporter nodes, but only sends them to the DefaultReporter, not the subsequent nodes", func() { Ω(reports["writing"].Find("writes stuff").CapturedGinkgoWriterOutput).Should((Equal("GinkgoWriter from It\n"))) Ω(reports["writing"].Find("writes stuff").CapturedStdOutErr).Should((Equal("Output from It\n"))) Ω(reports["outer-RAE"].Find("writes stuff").CapturedGinkgoWriterOutput).Should(Equal("GinkgoWriter from It\nGinkgoWriter from ReportAfterEach\n")) Ω(reports["outer-RAE"].Find("writes stuff").CapturedStdOutErr).Should(Equal("Output from It\nOutput from ReportAfterEach\n")) Ω(reports["inner-RAE"].Find("writes stuff").CapturedGinkgoWriterOutput).Should(Equal("GinkgoWriter from It\nGinkgoWriter from ReportAfterEach\n")) Ω(reports["inner-RAE"].Find("writes stuff").CapturedStdOutErr).Should(Equal("Output from It\nOutput from ReportAfterEach\n")) // but a report containing the additional output will be send to Ginkgo's reporter... Ω(reporter.Did.Find("writes stuff").CapturedGinkgoWriterOutput).Should((Equal("GinkgoWriter from It\nGinkgoWriter from ReportAfterEach\n"))) Ω(reporter.Did.Find("writes stuff").CapturedStdOutErr).Should((Equal("Output from It\nOutput from ReportAfterEach\n"))) }) }) golang-github-onsi-ginkgo-v2-2.22.0/internal/internal_integration/report_entries_test.go000066400000000000000000000061201472321612100315560ustar00rootroot00000000000000package internal_integration_test import ( "fmt" "time" . "github.com/onsi/ginkgo/v2" "github.com/onsi/ginkgo/v2/types" . "github.com/onsi/gomega" ) var _ = Describe("ReportEntries", func() { Context("happy path", func() { BeforeEach(func() { success, _ := RunFixture("Report Entries", func() { BeforeSuite(func() { AddReportEntry("bridge", "engaged") }) It("adds-entries", func() { AddReportEntry("medical", "healthy") AddReportEntry("engineering", "on fire") }) It("adds-no-entries", func() {}) }) Ω(success).Should(BeTrue()) }) It("attaches entries to the report", func() { Ω(reporter.Did.Find("adds-entries").ReportEntries[0].Name).Should(Equal("medical")) Ω(reporter.Did.Find("adds-entries").ReportEntries[0].Value.String()).Should(Equal("healthy")) Ω(reporter.Did.Find("adds-entries").ReportEntries[1].Name).Should(Equal("engineering")) Ω(reporter.Did.Find("adds-entries").ReportEntries[1].Value.String()).Should(Equal("on fire")) Ω(reporter.Did.Find("adds-no-entries").ReportEntries).Should(BeEmpty()) Ω(reporter.Did.FindByLeafNodeType(types.NodeTypeBeforeSuite).ReportEntries[0].Name).Should(Equal("bridge")) Ω(reporter.Did.FindByLeafNodeType(types.NodeTypeBeforeSuite).ReportEntries[0].Value.String()).Should(Equal("engaged")) }) It("also emits report", func() { Ω(reporter.ReportEntries).Should(HaveLen(3)) Ω(reporter.ReportEntries[0].Name).Should(Equal("bridge")) Ω(reporter.ReportEntries[1].Name).Should(Equal("medical")) Ω(reporter.ReportEntries[2].Name).Should(Equal("engineering")) }) }) Context("avoiding races", func() { BeforeEach(func() { success, _ := RunFixture("Report Entries - but no races", func() { BeforeEach(func() { stop := make(chan interface{}) done := make(chan interface{}) ticker := time.NewTicker(10 * time.Millisecond) i := 0 go func() { for { select { case <-ticker.C: AddReportEntry(fmt.Sprintf("report-%d", i)) i++ case <-stop: ticker.Stop() close(done) return } } }() DeferCleanup(func() { close(stop) <-done }) }) It("reporter", func() { for i := 0; i < 5; i++ { time.Sleep(20 * time.Millisecond) AddReportEntry(fmt.Sprintf("waiting... %d", i)) Ω(len(CurrentSpecReport().ReportEntries)).Should(BeNumerically("<", (i+1)*10)) } }) ReportAfterEach(func(report SpecReport) { //no races here, either Ω(len(report.ReportEntries)).Should(BeNumerically(">", 5)) }) }) Ω(success).Should(BeTrue()) }) It("attaches entries without racing", func() { Ω(reporter.Did.Find("reporter").ReportEntries).Should(ContainElement(HaveField("Name", "report-0"))) Ω(reporter.Did.Find("reporter").ReportEntries).Should(ContainElement(HaveField("Name", "report-2"))) Ω(reporter.Did.Find("reporter").ReportEntries).Should(ContainElement(HaveField("Name", "waiting... 1"))) Ω(reporter.Did.Find("reporter").ReportEntries).Should(ContainElement(HaveField("Name", "waiting... 3"))) }) }) }) golang-github-onsi-ginkgo-v2-2.22.0/internal/internal_integration/report_suite_test.go000066400000000000000000000412551472321612100312460ustar00rootroot00000000000000package internal_integration_test import ( "time" . "github.com/onsi/ginkgo/v2" "github.com/onsi/ginkgo/v2/internal/interrupt_handler" . "github.com/onsi/ginkgo/v2/internal/test_helpers" "github.com/onsi/ginkgo/v2/types" . "github.com/onsi/gomega" ) var _ = Describe("Sending reports to ReportBeforeSuite and ReportAfterSuite nodes", func() { var failInReportBeforeSuiteA, timeoutInReportBeforeSuiteB, failInReportAfterSuiteA, timeoutInReportAfterSuiteC, interruptSuiteB bool var fixture func() BeforeEach(func() { failInReportBeforeSuiteA = false timeoutInReportBeforeSuiteB = false failInReportAfterSuiteA = false timeoutInReportAfterSuiteC = false interruptSuiteB = false conf.RandomSeed = 17 fixture = func() { BeforeSuite(rt.T("before-suite", func() { outputInterceptor.AppendInterceptedOutput("out-before-suite") })) ReportBeforeSuite(func(report Report) { rt.RunWithData("report-before-suite-A", "report", report) writer.Print("gw-report-before-suite-A") outputInterceptor.AppendInterceptedOutput("out-report-before-suite-A") if failInReportBeforeSuiteA { F("fail in report-before-suite-A") } }) ReportBeforeSuite(func(ctx SpecContext, report Report) { timeout := 200 * time.Millisecond if timeoutInReportBeforeSuiteB { timeout = timeout + 1*time.Second } rt.RunWithData("report-before-suite-B", "report", report) writer.Print("gw-report-before-suite-B") select { case <-ctx.Done(): outputInterceptor.AppendInterceptedOutput("timeout-report-before-suite-B") case <-time.After(timeout): outputInterceptor.AppendInterceptedOutput("out-report-before-suite-B") } }, NodeTimeout(500*time.Millisecond)) Context("container", func() { It("A", rt.T("A")) It("B", rt.T("B", func() { F("fail in B") })) It("C", rt.T("C")) PIt("D", rt.T("D")) }) ReportAfterSuite("Report A", func(report Report) { rt.RunWithData("report-after-suite-A", "report", report) writer.Print("gw-report-after-suite-A") outputInterceptor.AppendInterceptedOutput("out-report-after-suite-A") if failInReportAfterSuiteA { F("fail in report-after-suite-A") } }) ReportAfterSuite("Report B", func(report Report) { if interruptSuiteB { interruptHandler.Interrupt(interrupt_handler.InterruptCauseSignal) time.Sleep(time.Hour) } rt.RunWithData("report-after-suite-B", "report", report) writer.Print("gw-report-after-suite-B") outputInterceptor.AppendInterceptedOutput("out-report-after-suite-B") }) ReportAfterSuite("Report C", func(ctx SpecContext, report Report) { timeout := 200 * time.Millisecond if timeoutInReportAfterSuiteC { timeout = timeout + 1*time.Second } rt.RunWithData("report-after-suite-C", "report", report) writer.Print("gw-report-after-suite-C") outputInterceptor.AppendInterceptedOutput("out-report-after-suite-C") select { case <-ctx.Done(): case <-time.After(timeout): } }, NodeTimeout(500*time.Millisecond)) AfterSuite(rt.T("after-suite", func() { writer.Print("gw-after-suite") F("fail in after-suite") })) } }) Context("when running in series", func() { BeforeEach(func() { conf.ParallelTotal = 1 conf.ParallelProcess = 1 }) Context("the happy path", func() { BeforeEach(func() { success, _ := RunFixture("happy-path", fixture) Ω(success).Should(BeFalse()) }) It("runs all the functions", func() { Ω(rt).Should(HaveTracked( "report-before-suite-A", "report-before-suite-B", "before-suite", "A", "B", "C", "after-suite", "report-after-suite-A", "report-after-suite-B", "report-after-suite-C", )) }) It("reports on the report procs", func() { Ω(reporter.Did.FindByLeafNodeType(types.NodeTypeReportBeforeSuite)).Should(HavePassed( types.NodeTypeReportBeforeSuite, CapturedGinkgoWriterOutput("gw-report-before-suite-A"), CapturedStdOutput("out-report-before-suite-A"), )) Ω(reporter.Did.Find("Report A")).Should(HavePassed( types.NodeTypeReportAfterSuite, CapturedGinkgoWriterOutput("gw-report-after-suite-A"), CapturedStdOutput("out-report-after-suite-A"), )) Ω(reporter.Did.Find("Report B")).Should(HavePassed( types.NodeTypeReportAfterSuite, CapturedGinkgoWriterOutput("gw-report-after-suite-B"), CapturedStdOutput("out-report-after-suite-B"), )) }) It("passes the report in to each ReportBeforeSuite", func() { reportA := rt.DataFor("report-before-suite-A")["report"].(types.Report) reportB := rt.DataFor("report-before-suite-B")["report"].(types.Report) for _, report := range []types.Report{reportA, reportB} { Ω(report.SuiteDescription).Should(Equal("happy-path")) Ω(report.SuiteSucceeded).Should(BeTrue()) Ω(report.SuiteConfig.RandomSeed).Should(Equal(int64(17))) Ω(report.PreRunStats.SpecsThatWillRun).Should(Equal(3)) Ω(report.PreRunStats.TotalSpecs).Should(Equal(4)) } Ω(len(reportB.SpecReports)-len(reportA.SpecReports)).Should(Equal(1), "Report B includes the invocation of ReportAfterSuite A") Ω(Reports(reportB.SpecReports).FindByLeafNodeType(types.NodeTypeReportBeforeSuite)).Should(Equal(reporter.Did.FindByLeafNodeType(types.NodeTypeReportBeforeSuite))) }) It("passes the report in to each ReportAfterSuite", func() { reportA := rt.DataFor("report-after-suite-A")["report"].(types.Report) reportB := rt.DataFor("report-after-suite-B")["report"].(types.Report) for _, report := range []types.Report{reportA, reportB} { Ω(report.SuiteDescription).Should(Equal("happy-path")) Ω(report.SuiteSucceeded).Should(BeFalse()) Ω(report.SuiteConfig.RandomSeed).Should(Equal(int64(17))) reports := Reports(report.SpecReports) Ω(reports.FindByLeafNodeType(types.NodeTypeBeforeSuite)).Should(HavePassed(CapturedStdOutput("out-before-suite"))) Ω(reports.Find("A")).Should(HavePassed()) Ω(reports.Find("B")).Should(HaveFailed("fail in B")) Ω(reports.Find("C")).Should(HavePassed()) Ω(reports.Find("D")).Should(BePending()) Ω(reports.FindByLeafNodeType(types.NodeTypeAfterSuite)).Should(HaveFailed("fail in after-suite", CapturedGinkgoWriterOutput("gw-after-suite"))) } Ω(len(reportB.SpecReports)-len(reportA.SpecReports)).Should(Equal(1), "Report B includes the invocation of ReportAfterSuite A") Ω(Reports(reportB.SpecReports).Find("Report A")).Should(Equal(reporter.Did.Find("Report A"))) }) }) Context("when a ReportBeforeSuite node fails", func() { BeforeEach(func() { failInReportBeforeSuiteA = true success, _ := RunFixture("report-before-suite-A-fails", fixture) Ω(success).Should(BeFalse()) }) It("doesn't run any specs - just reporting functions", func() { Ω(rt).Should(HaveTracked( "report-before-suite-A", "report-before-suite-B", "report-after-suite-A", "report-after-suite-B", "report-after-suite-C", )) }) It("reports on the failure, to Ginkgo's reporter and any subsequent reporters", func() { Ω(reporter.Did.FindByLeafNodeType(types.NodeTypeReportBeforeSuite)).Should(HaveFailed( types.NodeTypeReportBeforeSuite, "fail in report-before-suite-A", CapturedGinkgoWriterOutput("gw-report-before-suite-A"), CapturedStdOutput("out-report-before-suite-A"), )) reportB := rt.DataFor("report-before-suite-B")["report"].(types.Report) Ω(Reports(reportB.SpecReports).FindByLeafNodeType(types.NodeTypeReportBeforeSuite)).Should(Equal(reporter.Did.FindByLeafNodeType(types.NodeTypeReportBeforeSuite))) }) }) Context("when a ReportBeforeSuite times out", func() { BeforeEach(func() { timeoutInReportBeforeSuiteB = true success, _ := RunFixture("report-before-suite-B-timed-out", fixture) Ω(success).Should(BeFalse()) }) It("reports on the failure, to Ginkgo's reporter and any subsequent reporters", func() { Ω(reporter.Did.WithLeafNodeType(types.NodeTypeReportBeforeSuite).WithState(types.SpecStateTimedout)). Should(ContainElement(HaveTimedOut( types.NodeTypeReportBeforeSuite, "A node timeout occurred", CapturedGinkgoWriterOutput("gw-report-before-suite-B"), CapturedStdOutput("timeout-report-before-suite-B"), ))) }) }) Context("when a ReportAfterSuite times out", func() { BeforeEach(func() { timeoutInReportAfterSuiteC = true success, _ := RunFixture("report-after-suite-C-timed-out", fixture) Ω(success).Should(BeFalse()) }) It("reports on the failure, to Ginkgo's reporter and any subsequent reporters", func() { Ω(reporter.Did.Find("Report C")).Should(HaveTimedOut( types.NodeTypeReportAfterSuite, "A node timeout occurred", CapturedGinkgoWriterOutput("gw-report-after-suite-C"), CapturedStdOutput("out-report-after-suite-C"), )) }) }) Context("when a ReportAfterSuite node fails", func() { BeforeEach(func() { failInReportAfterSuiteA = true success, _ := RunFixture("report-after-suite-A-fails", fixture) Ω(success).Should(BeFalse()) }) It("keeps running subseuqent reporting functions", func() { Ω(rt).Should(HaveTracked( "report-before-suite-A", "report-before-suite-B", "before-suite", "A", "B", "C", "after-suite", "report-after-suite-A", "report-after-suite-B", "report-after-suite-C", )) }) It("reports on the failure, to Ginkgo's reporter and any subsequent reporters", func() { Ω(reporter.Did.Find("Report A")).Should(HaveFailed( types.NodeTypeReportAfterSuite, "fail in report-after-suite-A", CapturedGinkgoWriterOutput("gw-report-after-suite-A"), CapturedStdOutput("out-report-after-suite-A"), )) reportB := rt.DataFor("report-after-suite-B")["report"].(types.Report) Ω(Reports(reportB.SpecReports).Find("Report A")).Should(Equal(reporter.Did.Find("Report A"))) }) }) Context("when an interrupt is attempted in a ReportAfterSuiteNode", func() { BeforeEach(func() { interruptSuiteB = true success, _ := RunFixture("report-after-suite-B-interrupted", fixture) Ω(success).Should(BeFalse()) }) It("interrupts and bails", func() { Ω(rt).Should(HaveTracked( "report-before-suite-A", "report-before-suite-B", "before-suite", "A", "B", "C", "after-suite", "report-after-suite-A", "report-after-suite-C", )) }) }) }) Context("when running in parallel", func() { var otherNodeReport types.Report BeforeEach(func() { SetUpForParallel(2) otherNodeReport = types.Report{ SpecReports: types.SpecReports{ types.SpecReport{LeafNodeText: "E", LeafNodeLocation: cl, State: types.SpecStatePassed, LeafNodeType: types.NodeTypeIt}, types.SpecReport{LeafNodeText: "F", LeafNodeLocation: cl, State: types.SpecStateSkipped, LeafNodeType: types.NodeTypeIt}, }, } }) Context("on proc 1", func() { BeforeEach(func() { conf.ParallelProcess = 1 }) Context("the happy path", func() { BeforeEach(func() { // proc 2 has reported back and exited client.PostSuiteDidEnd(otherNodeReport) close(exitChannels[2]) success, _ := RunFixture("happy-path", fixture) Ω(success).Should(BeFalse()) }) It("runs all the functions", func() { Ω(rt).Should(HaveTracked( "report-before-suite-A", "report-before-suite-B", "before-suite", "A", "B", "C", "after-suite", "report-after-suite-A", "report-after-suite-B", "report-after-suite-C", )) }) It("passes the report in to each reporter, including information from other procs", func() { reportA := rt.DataFor("report-after-suite-A")["report"].(types.Report) reportB := rt.DataFor("report-after-suite-B")["report"].(types.Report) for _, report := range []types.Report{reportA, reportB} { Ω(report.SuiteDescription).Should(Equal("happy-path")) Ω(report.SuiteSucceeded).Should(BeFalse()) reports := Reports(report.SpecReports) Ω(reports.FindByLeafNodeType(types.NodeTypeBeforeSuite)).Should(HavePassed(CapturedStdOutput("out-before-suite"))) Ω(reports.Find("A")).Should(HavePassed()) Ω(reports.Find("B")).Should(HaveFailed("fail in B")) Ω(reports.Find("C")).Should(HavePassed()) Ω(reports.Find("D")).Should(BePending()) Ω(reports.Find("E")).Should(HavePassed()) Ω(reports.Find("F")).Should(HaveBeenSkipped()) Ω(reports.FindByLeafNodeType(types.NodeTypeAfterSuite)).Should(HaveFailed("fail in after-suite", CapturedGinkgoWriterOutput("gw-after-suite"))) } Ω(len(reportB.SpecReports)-len(reportA.SpecReports)).Should(Equal(1), "Report B includes the invocation of ReportAfterSuite A") Ω(Reports(reportB.SpecReports).Find("Report A")).Should(Equal(reporter.Did.Find("Report A"))) }) It("tells the other procs that the ReportBeforeSuite has completed", func() { Ω(client.BlockUntilReportBeforeSuiteCompleted()).Should(Equal(types.SpecStatePassed)) }) }) Describe("waiting for reports from other procs", func() { It("blocks until the other procs have finished", func() { done := make(chan interface{}) go func() { defer GinkgoRecover() success, _ := RunFixture("happy-path", fixture) Ω(success).Should(BeFalse()) close(done) }() Consistently(done).ShouldNot(BeClosed()) client.PostSuiteDidEnd(otherNodeReport) Consistently(done).ShouldNot(BeClosed()) close(exitChannels[2]) Eventually(done).Should(BeClosed()) }) }) Context("when a non-primary proc disappears before it reports", func() { BeforeEach(func() { close(exitChannels[2]) // proc 2 disappears before reporting success, _ := RunFixture("disappearing-proc-2", fixture) Ω(success).Should(BeFalse()) }) It("does not run the ReportAfterSuite procs", func() { Ω(rt).Should(HaveTracked( "report-before-suite-A", "report-before-suite-B", "before-suite", "A", "B", "C", "after-suite", )) }) It("reports all the ReportAfterSuite procs as failed", func() { Ω(reporter.Did.Find("Report A")).Should(HaveFailed(types.GinkgoErrors.AggregatedReportUnavailableDueToNodeDisappearing().Error())) Ω(reporter.Did.Find("Report B")).Should(HaveFailed(types.GinkgoErrors.AggregatedReportUnavailableDueToNodeDisappearing().Error())) }) }) Context("when a ReportBeforeSuite fails", func() { BeforeEach(func() { // proc 2 has reported back and exited client.PostSuiteDidEnd(otherNodeReport) close(exitChannels[2]) failInReportBeforeSuiteA = true success, _ := RunFixture("failure-in-report-before-suite-A", fixture) Ω(success).Should(BeFalse()) }) It("only runs the reporting nodes", func() { Ω(rt).Should(HaveTracked( "report-before-suite-A", "report-before-suite-B", "report-after-suite-A", "report-after-suite-B", "report-after-suite-C", )) }) It("tells the other procs that the ReportBeforeSuite failed", func() { Ω(client.BlockUntilReportBeforeSuiteCompleted()).Should(Equal(types.SpecStateFailed)) }) }) }) Context("on a non-primary proc", func() { var done chan interface{} BeforeEach(func() { done = make(chan interface{}) go func() { conf.ParallelProcess = 2 success, _ := RunFixture("non-primary proc", fixture) Ω(success).Should(BeFalse()) close(done) }() Consistently(done).ShouldNot(BeClosed()) Ω(rt).Should(HaveTrackedNothing(), "Nothing should run until we are cleared to go by proc1") }) Context("the happy path", func() { BeforeEach(func() { // proc1 signals that its ReportBeforeSuites succeeded client.PostReportBeforeSuiteCompleted(types.SpecStatePassed) Eventually(done).Should(BeClosed()) }) It("does not run anything until the primary node finishes running the BeforeSuite node, then it runs the specs", func() { Ω(rt).Should(HaveTracked( "before-suite", "A", "B", "C", "after-suite", )) }) }) Context("when the ReportBeforeSuite node fails", func() { BeforeEach(func() { // proc1 signals that its ReportBeforeSuites failed client.PostReportBeforeSuiteCompleted(types.SpecStateFailed) Eventually(done).Should(BeClosed()) }) It("does not run anything until it is notified of the failure, then it just exits without running anything", func() { Ω(rt).Should(HaveTrackedNothing()) }) }) Context("when proc1 exits before reporting", func() { BeforeEach(func() { // proc1 signals that its ReportBeforeSuites failed close(exitChannels[1]) Eventually(done).Should(BeClosed()) }) It("does not run anything until it is notified of the failure, then it just exits without running anything", func() { Ω(rt).Should(HaveTrackedNothing()) }) }) }) }) }) golang-github-onsi-ginkgo-v2-2.22.0/internal/internal_integration/run_test.go000066400000000000000000000151211472321612100273170ustar00rootroot00000000000000package internal_integration_test import ( "time" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/ginkgo/v2/internal/test_helpers" . "github.com/onsi/gomega" "github.com/onsi/ginkgo/v2/types" ) var _ = Describe("Running Tests in Series - the happy path", func() { BeforeEach(func() { success, hPF := RunFixture("happy-path run suite", func() { BeforeSuite(rt.T("before-suite", func() { time.Sleep(10 * time.Millisecond) writer.Write([]byte("before-suite\n")) outputInterceptor.AppendInterceptedOutput("output-intercepted-in-before-suite") })) AfterSuite(rt.T("after-suite", func() { time.Sleep(20 * time.Millisecond) outputInterceptor.AppendInterceptedOutput("output-intercepted-in-after-suite") })) Describe("top-level-container", func() { JustBeforeEach(rt.T("just-before-each")) BeforeEach(rt.T("before-each", func() { writer.Write([]byte("before-each\n")) })) AfterEach(rt.T("after-each")) AfterEach(rt.T("after-each-2")) JustAfterEach(rt.T("just-after-each")) It("A", rt.T("A", func() { time.Sleep(10 * time.Millisecond) })) It("B", rt.T("B", func() { time.Sleep(20 * time.Millisecond) })) Describe("nested-container", func() { JustBeforeEach(rt.T("nested-just-before-each")) BeforeEach(rt.T("nested-before-each")) AfterEach(rt.T("nested-after-each")) JustAfterEach(rt.T("nested-just-after-each")) JustAfterEach(rt.T("nested-just-after-each-2")) It("C", rt.T("C", func() { writer.Write([]byte("C\n")) outputInterceptor.AppendInterceptedOutput("output-intercepted-in-C") })) It("D", rt.T("D")) }) JustBeforeEach(rt.T("outer-just-before-each")) BeforeEach(rt.T("outer-before-each")) AfterEach(rt.T("outer-after-each")) JustAfterEach(rt.T("outer-just-after-each")) }) }) Ω(success).Should(BeTrue()) Ω(hPF).Should(BeFalse()) }) It("runs all the test nodes in the expected order", func() { Ω(rt).Should(HaveTracked( "before-suite", "before-each", "outer-before-each", "just-before-each", "outer-just-before-each", "A", "just-after-each", "outer-just-after-each", "after-each", "after-each-2", "outer-after-each", "before-each", "outer-before-each", "just-before-each", "outer-just-before-each", "B", "just-after-each", "outer-just-after-each", "after-each", "after-each-2", "outer-after-each", "before-each", "outer-before-each", "nested-before-each", "just-before-each", "outer-just-before-each", "nested-just-before-each", "C", "nested-just-after-each", "nested-just-after-each-2", "just-after-each", "outer-just-after-each", "nested-after-each", "after-each", "after-each-2", "outer-after-each", "before-each", "outer-before-each", "nested-before-each", "just-before-each", "outer-just-before-each", "nested-just-before-each", "D", "nested-just-after-each", "nested-just-after-each-2", "just-after-each", "outer-just-after-each", "nested-after-each", "after-each", "after-each-2", "outer-after-each", "after-suite", )) }) Describe("reporting", func() { It("reports the suite summary correctly when starting", func() { Ω(reporter.Begin).Should(SatisfyAll( HaveField("SuitePath", "/path/to/suite"), HaveField("SuiteDescription", "happy-path run suite"), HaveField("SuiteSucceeded", BeFalse()), HaveField("PreRunStats.TotalSpecs", 4), HaveField("PreRunStats.SpecsThatWillRun", 4), )) }) It("reports the suite summary correctly when complete", func() { Ω(reporter.End).Should(SatisfyAll( HaveField("SuitePath", "/path/to/suite"), HaveField("SuiteDescription", "happy-path run suite"), HaveField("SuiteSucceeded", BeTrue()), HaveField("RunTime", BeNumerically(">=", time.Millisecond*(10+20+10+20))), HaveField("PreRunStats.TotalSpecs", 4), HaveField("PreRunStats.SpecsThatWillRun", 4), )) Ω(reporter.End.SpecReports.WithLeafNodeType(types.NodeTypeIt).CountWithState(types.SpecStatePassed)).Should(Equal(4)) }) It("reports the correct suite node summaries", func() { Ω(reporter.Did.FindByLeafNodeType(types.NodeTypeBeforeSuite)).Should(SatisfyAll( HaveField("LeafNodeType", types.NodeTypeBeforeSuite), HaveField("State", types.SpecStatePassed), HaveField("RunTime", BeNumerically(">=", 10*time.Millisecond)), HaveField("Failure", BeZero()), HaveField("CapturedGinkgoWriterOutput", "before-suite\n"), HaveField("CapturedStdOutErr", "output-intercepted-in-before-suite"), HaveField("ParallelProcess", 1), )) beforeSuiteReport := reporter.Did.FindByLeafNodeType(types.NodeTypeBeforeSuite) Ω(beforeSuiteReport.EndTime.Sub(beforeSuiteReport.StartTime)).Should(BeNumerically("~", beforeSuiteReport.RunTime)) Ω(reporter.Did.FindByLeafNodeType(types.NodeTypeAfterSuite)).Should(SatisfyAll( HaveField("LeafNodeType", types.NodeTypeAfterSuite), HaveField("State", types.SpecStatePassed), HaveField("RunTime", BeNumerically(">=", 20*time.Millisecond)), HaveField("Failure", BeZero()), HaveField("CapturedGinkgoWriterOutput", BeZero()), HaveField("CapturedStdOutErr", "output-intercepted-in-after-suite"), HaveField("ParallelProcess", 1), )) afterSuiteReport := reporter.Did.FindByLeafNodeType(types.NodeTypeAfterSuite) Ω(afterSuiteReport.EndTime.Sub(afterSuiteReport.StartTime)).Should(BeNumerically("~", afterSuiteReport.RunTime)) }) It("reports about each just before it runs", func() { Ω(reporter.Will.Names()).Should(Equal([]string{"A", "B", "C", "D"})) }) It("reports about each test after it completes", func() { Ω(reporter.Did.Names()).Should(Equal([]string{"A", "B", "C", "D"})) Ω(reporter.Did.WithState(types.SpecStatePassed).Names()).Should(Equal([]string{"A", "B", "C", "D"})) //spot-check Ω(reporter.Did.Find("C")).Should(SatisfyAll( HaveField("LeafNodeType", types.NodeTypeIt), HaveField("LeafNodeText", "C"), HaveField("ContainerHierarchyTexts", []string{"top-level-container", "nested-container"}), HaveField("State", types.SpecStatePassed), HaveField("Failure", BeZero()), HaveField("NumAttempts", 1), HaveField("CapturedGinkgoWriterOutput", "before-each\nC\n"), HaveField("CapturedStdOutErr", "output-intercepted-in-C"), HaveField("ParallelProcess", 1), HaveField("RunningInParallel", false), )) }) It("computes start times, end times, and run times", func() { Ω(reporter.Did.Find("A").RunTime).Should(BeNumerically(">=", 10*time.Millisecond)) Ω(reporter.Did.Find("B").RunTime).Should(BeNumerically(">=", 20*time.Millisecond)) reportA := reporter.Did.Find("A") Ω(reportA.EndTime.Sub(reportA.StartTime)).Should(BeNumerically("~", reportA.RunTime)) }) }) }) golang-github-onsi-ginkgo-v2-2.22.0/internal/internal_integration/serial_test.go000066400000000000000000000047511472321612100300010ustar00rootroot00000000000000package internal_integration_test import ( "time" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/ginkgo/v2/internal/test_helpers" . "github.com/onsi/gomega" ) var _ = Describe("Serial", func() { var fixture func() BeforeEach(func() { fixture = func() { Context("container", func() { It("A", rt.T("A", func() { time.Sleep(10 * time.Millisecond) })) It("B", rt.T("B", func() { time.Sleep(10 * time.Millisecond) })) It("C", Serial, rt.T("C", func() { time.Sleep(10 * time.Millisecond) })) It("D", rt.T("D", func() { time.Sleep(10 * time.Millisecond) })) It("E", rt.T("E", func() { time.Sleep(10 * time.Millisecond) })) It("F", Serial, rt.T("F", func() { time.Sleep(10 * time.Millisecond) })) It("G", rt.T("G", func() { time.Sleep(10 * time.Millisecond) })) It("H", Serial, rt.T("H", func() { time.Sleep(10 * time.Millisecond) })) }) } }) Context("when running in series", func() { BeforeEach(func() { conf.ParallelTotal = 1 conf.ParallelProcess = 1 success, _ := RunFixture("in-series", fixture) Ω(success).Should(BeTrue()) }) It("runs and reports on all the tests", func() { Ω(rt).Should(HaveTracked("A", "B", "C", "D", "E", "F", "G", "H")) Ω(reporter.Did.Names()).Should(Equal([]string{"A", "B", "C", "D", "E", "F", "G", "H"})) }) }) Context("when running in parallel", func() { BeforeEach(func() { SetUpForParallel(2) }) Describe("when running as proc 1", func() { BeforeEach(func() { conf.ParallelProcess = 1 }) It("participates in running parallel tests, then runs the serial tests after all other procs have finished", func() { done := make(chan interface{}) go func() { defer GinkgoRecover() success, _ := RunFixture("happy-path", fixture) Ω(success).Should(BeTrue()) close(done) }() Eventually(rt).Should(HaveTracked("A", "B", "D", "E", "G")) Consistently(rt, 100*time.Millisecond).Should(HaveTracked("A", "B", "D", "E", "G")) close(exitChannels[2]) Eventually(rt).Should(HaveTracked("A", "B", "D", "E", "G", "C", "F", "H")) Eventually(done).Should(BeClosed()) }) }) Describe("when running as a non-primary proc", func() { BeforeEach(func() { conf.ParallelProcess = 2 }) It("participates in running parallel tests, but never runs the serial tests", func() { close(exitChannels[1]) success, _ := RunFixture("happy-path", fixture) Ω(success).Should(BeTrue()) Ω(rt).Should(HaveTracked("A", "B", "D", "E", "G")) }) }) }) }) golang-github-onsi-ginkgo-v2-2.22.0/internal/internal_integration/shuffle_test.go000066400000000000000000000062641472321612100301570ustar00rootroot00000000000000package internal_integration_test import ( "strings" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" ) var _ = Describe("Shuffling Tests", func() { var fixture = func() { Describe("container-a", func() { It("a.1", rt.T("a.1")) It("a.2", rt.T("a.2")) It("a.3", rt.T("a.3")) It("a.4", rt.T("a.4")) }) Describe("container-b", func() { It("b.1", rt.T("b.1")) It("b.2", rt.T("b.2")) It("b.3", rt.T("b.3")) It("b.4", rt.T("b.4")) }) Describe("ordered-container", Ordered, func() { It("o.1", rt.T("o.1")) It("o.2", rt.T("o.2")) It("o.3", rt.T("o.3")) It("o.4", rt.T("o.4")) }) It("top.1", rt.T("top.1")) It("top.2", rt.T("top.2")) It("top.3", rt.T("top.3")) It("top.4", rt.T("top.4")) } Describe("the default behavior", func() { It("shuffles top-level containers and its only, preserving the order of tests within containers", func() { orderings := []string{} uniqueOrderings := map[string]bool{} for i := 0; i < 10; i += 1 { conf.RandomSeed = int64(i) RunFixture("run", fixture) order := strings.Join(rt.TrackedRuns(), "") rt.Reset() Ω(order).Should(ContainSubstring("a.1a.2a.3a.4"), "order in containers should be preserved") Ω(order).Should(ContainSubstring("b.1b.2b.3b.4"), "order in containers should be preserved") Ω(order).Should(ContainSubstring("o.1o.2o.3o.4"), "order in containers should be preserved") orderings = append(orderings, order) uniqueOrderings[order] = true } Ω(orderings).Should(ContainElement(Not(ContainSubstring("top.1top.2top.3top.4"))), "top-level its should be randomized") Ω(uniqueOrderings).ShouldNot(HaveLen(1), "after 10 runs at least a few should be different!") }) }) Describe("when told to randomize all specs", func() { It("shuffles all its, but preserves ordered containers", func() { conf.RandomizeAllSpecs = true orderings := []string{} uniqueOrderings := map[string]bool{} for i := 0; i < 10; i += 1 { conf.RandomSeed = int64(i) RunFixture("run", fixture) order := strings.Join(rt.TrackedRuns(), "") rt.Reset() Ω(order).Should(ContainSubstring("o.1o.2o.3o.4"), "order in containers should be preserved") orderings = append(orderings, order) uniqueOrderings[order] = true } Ω(orderings).Should(ContainElement(Not(ContainSubstring("top.1top.2top.3top.4"))), "top-level its should be randomized") Ω(orderings).Should(ContainElement(Not(ContainSubstring("a.1a.2a.3a.4"))), "its in containers should be randomized") Ω(orderings).Should(ContainElement(Not(ContainSubstring("b.1b.2b.3b.4"))), "its in containers should be randomized") Ω(uniqueOrderings).ShouldNot(HaveLen(1), "after 10 runs at least a few should be different!") }) }) Describe("when given the same seed", func() { It("yields the same order", func() { for _, conf.RandomizeAllSpecs = range []bool{true, false} { uniqueOrderings := map[string]bool{} for i := 0; i < 10; i += 1 { conf.RandomSeed = 1138 RunFixture("run", fixture) order := strings.Join(rt.TrackedRuns(), "") rt.Reset() uniqueOrderings[order] = true } Ω(uniqueOrderings).Should(HaveLen(1), "all orders are the same") } }) }) }) golang-github-onsi-ginkgo-v2-2.22.0/internal/internal_integration/skip_test.go000066400000000000000000000052201472321612100274600ustar00rootroot00000000000000package internal_integration_test import ( . "github.com/onsi/ginkgo/v2" "github.com/onsi/ginkgo/v2/types" . "github.com/onsi/gomega" . "github.com/onsi/ginkgo/v2/internal/test_helpers" ) var _ = Describe("Skip", func() { Context("When Skip() is called in individual subject and setup nodes", func() { BeforeEach(func() { success, _ := RunFixture("Skip() tests", func() { Describe("container to ensure order", func() { It("A", rt.T("A")) Describe("container", func() { BeforeEach(rt.T("bef", func() { failer.Skip("skip in Bef", cl) panic("boom") //simulates what Ginkgo DSL does })) It("B", rt.T("B")) It("C", rt.T("C")) AfterEach(rt.T("aft")) }) It("D", rt.T("D", func() { failer.Skip("skip D", cl) panic("boom") //simulates what Ginkgo DSL does })) }) }) Ω(success).Should(BeTrue()) }) It("skips the tests that are Skipped()", func() { Ω(rt).Should(HaveTracked("A", "bef", "aft", "bef", "aft", "D")) Ω(reporter.Did.WithState(types.SpecStatePassed).Names()).Should(ConsistOf("A")) Ω(reporter.Did.WithState(types.SpecStateSkipped).Names()).Should(ConsistOf("B", "C", "D")) Ω(reporter.Did.Find("B").Failure.Message).Should(Equal("skip in Bef")) Ω(reporter.Did.Find("B").Failure.Location).Should(Equal(cl)) Ω(reporter.Did.Find("D").Failure.Message).Should(Equal("skip D")) Ω(reporter.Did.Find("D").Failure.Location).Should(Equal(cl)) }) It("report on the suite with accurate numbers", func() { Ω(reporter.End).Should(BeASuiteSummary(true, NPassed(1), NSkipped(3), NPending(0), NSpecs(4), NWillRun(4))) }) }) Context("when Skip() is called in BeforeSuite", func() { BeforeEach(func() { success, _ := RunFixture("Skip() BeforeSuite", func() { BeforeSuite(func() { rt.Run("befs") Skip("skip please") }) Describe("container to ensure order", func() { It("A", rt.T("A")) It("B", rt.T("B")) It("C", rt.T("C")) }) }) Ω(success).Should(BeTrue()) }) It("skips all the tsts", func() { Ω(rt).Should(HaveTracked("befs")) Ω(reporter.Did.FindByLeafNodeType(types.NodeTypeBeforeSuite)).Should(HaveBeenSkippedWithMessage("skip please")) Ω(reporter.Did.Find("A")).Should(HaveBeenSkipped()) Ω(reporter.Did.Find("B")).Should(HaveBeenSkipped()) Ω(reporter.Did.Find("C")).Should(HaveBeenSkipped()) }) It("report on the suite with accurate numbers", func() { Ω(reporter.End).Should(BeASuiteSummary(true, NPassed(0), NSkipped(3), NPending(0), NSpecs(3), NWillRun(3))) Ω(reporter.End.SpecialSuiteFailureReasons).Should(ContainElement("Suite skipped in BeforeSuite")) }) }) }) golang-github-onsi-ginkgo-v2-2.22.0/internal/internal_integration/synchronized_suite_nodes_test.go000066400000000000000000000312101472321612100336300ustar00rootroot00000000000000package internal_integration_test import ( . "github.com/onsi/ginkgo/v2" . "github.com/onsi/ginkgo/v2/internal/test_helpers" "github.com/onsi/ginkgo/v2/types" . "github.com/onsi/gomega" "github.com/onsi/gomega/gbytes" ) var _ = Describe("Synchronized Suite Nodes", func() { var failInBeforeSuiteProc1, failInBeforeSuiteAllProcs, failInAfterSuiteAllProcs, failInAfterSuiteProc1 bool var fixture func() BeforeEach(func() { failInBeforeSuiteProc1, failInBeforeSuiteAllProcs, failInAfterSuiteAllProcs, failInAfterSuiteProc1 = false, false, false, false fixture = func() { SynchronizedBeforeSuite(func() []byte { outputInterceptor.AppendInterceptedOutput("before-suite-proc-1") rt.Run("before-suite-proc-1") if failInBeforeSuiteProc1 { F("fail-in-before-suite-proc-1", cl) } return []byte("hey there") }, func(data []byte) { outputInterceptor.AppendInterceptedOutput("before-suite-all-procs") rt.RunWithData("before-suite-all-procs", "data", string(data)) if failInBeforeSuiteAllProcs { F("fail-in-before-suite-all-procs", cl) } }) It("test", rt.T("test")) SynchronizedAfterSuite(func() { outputInterceptor.AppendInterceptedOutput("after-suite-all-procs") rt.Run("after-suite-all-procs") if failInAfterSuiteAllProcs { F("fail-in-after-suite-all-procs", cl) } }, func() { outputInterceptor.AppendInterceptedOutput("after-suite-proc-1") rt.Run("after-suite-proc-1") if failInAfterSuiteProc1 { F("fail-in-after-suite-proc-1", cl) } }) } }) Describe("when running in series", func() { BeforeEach(func() { conf.ParallelTotal = 1 conf.ParallelProcess = 1 }) Describe("happy path", func() { BeforeEach(func() { success, _ := RunFixture("happy-path", fixture) Ω(success).Should(BeTrue()) }) It("runs all the functions", func() { Ω(rt).Should(HaveTracked( "before-suite-proc-1", "before-suite-all-procs", "test", "after-suite-all-procs", "after-suite-proc-1", )) }) It("reports on the SynchronizedBeforeSuite and SynchronizedAfterSuite as having passed", func() { befReports := reporter.Did.WithLeafNodeType(types.NodeTypeSynchronizedBeforeSuite) Ω(befReports).Should(HaveLen(1)) Ω(befReports[0]).Should(HavePassed()) aftReports := reporter.Did.WithLeafNodeType(types.NodeTypeSynchronizedAfterSuite) Ω(aftReports).Should(HaveLen(1)) Ω(aftReports[0]).Should(HavePassed()) }) It("passes data between the two SynchronizedBeforeSuite functions", func() { Ω(rt).Should(HaveRunWithData("before-suite-all-procs", "data", "hey there")) }) }) Describe("when the SynchronizedBeforeSuite proc1 function fails", func() { BeforeEach(func() { failInBeforeSuiteProc1 = true success, _ := RunFixture("fail in SynchronizedBeforeSuite proc1", fixture) Ω(success).Should(BeFalse()) }) It("doesn't run the allProcs function or any of the tests", func() { Ω(rt).Should(HaveTracked( "before-suite-proc-1", "after-suite-all-procs", "after-suite-proc-1", )) }) It("reports on the SynchronizedBeforeSuite and SynchronizedAfterSuite correctly", func() { Ω(reporter.Did.FindByLeafNodeType(types.NodeTypeSynchronizedBeforeSuite)).Should(HaveFailed("fail-in-before-suite-proc-1")) Ω(reporter.Did.FindByLeafNodeType(types.NodeTypeSynchronizedAfterSuite)).Should(HavePassed()) }) }) Describe("when the SynchronizedBeforeSuite allProcs function fails", func() { BeforeEach(func() { failInBeforeSuiteAllProcs = true success, _ := RunFixture("fail in SynchronizedBeforeSuite allProcs", fixture) Ω(success).Should(BeFalse()) }) It("doesn't run the tests", func() { Ω(rt).Should(HaveTracked( "before-suite-proc-1", "before-suite-all-procs", "after-suite-all-procs", "after-suite-proc-1", )) Ω(rt).Should(HaveRunWithData("before-suite-all-procs", "data", "hey there")) }) It("reports on the SynchronizedBeforeSuite and SynchronizedAfterSuite correctly", func() { Ω(reporter.Did.FindByLeafNodeType(types.NodeTypeSynchronizedBeforeSuite)).Should(HaveFailed("fail-in-before-suite-all-procs")) Ω(reporter.Did.FindByLeafNodeType(types.NodeTypeSynchronizedAfterSuite)).Should(HavePassed()) }) }) Describe("when the SynchronizedAfterSuite allProcs function fails", func() { BeforeEach(func() { failInAfterSuiteAllProcs = true success, _ := RunFixture("fail in SynchronizedAfterSuite allProcs", fixture) Ω(success).Should(BeFalse()) }) It("nonetheless runs the proc-1 function", func() { Ω(rt).Should(HaveTracked( "before-suite-proc-1", "before-suite-all-procs", "test", "after-suite-all-procs", "after-suite-proc-1", )) Ω(rt).Should(HaveRunWithData("before-suite-all-procs", "data", "hey there")) }) It("reports on the SynchronizedBeforeSuite and SynchronizedAfterSuite correctly", func() { Ω(reporter.Did.FindByLeafNodeType(types.NodeTypeSynchronizedBeforeSuite)).Should(HavePassed()) Ω(reporter.Did.FindByLeafNodeType(types.NodeTypeSynchronizedAfterSuite)).Should(HaveFailed("fail-in-after-suite-all-procs")) }) }) Describe("when the SynchronizedAfterSuite proc1 function fails", func() { BeforeEach(func() { failInAfterSuiteProc1 = true success, _ := RunFixture("fail in SynchronizedAfterSuite proc1", fixture) Ω(success).Should(BeFalse()) }) It("will have run everything", func() { Ω(rt).Should(HaveTracked( "before-suite-proc-1", "before-suite-all-procs", "test", "after-suite-all-procs", "after-suite-proc-1", )) Ω(rt).Should(HaveRunWithData("before-suite-all-procs", "data", "hey there")) }) It("reports on the SynchronizedBeforeSuite and SynchronizedAfterSuite correctly", func() { Ω(reporter.Did.FindByLeafNodeType(types.NodeTypeSynchronizedBeforeSuite)).Should(HavePassed()) Ω(reporter.Did.FindByLeafNodeType(types.NodeTypeSynchronizedAfterSuite)).Should(HaveFailed("fail-in-after-suite-proc-1")) }) }) }) Describe("when running in parallel", func() { var serverOutputBuffer *gbytes.Buffer BeforeEach(func() { SetUpForParallel(2) serverOutputBuffer = gbytes.NewBuffer() server.SetOutputDestination(serverOutputBuffer) }) Describe("when running as proc 1", func() { BeforeEach(func() { conf.ParallelProcess = 1 }) Describe("happy path", func() { BeforeEach(func() { close(exitChannels[2]) //trigger proc 2 exiting so the proc1 after suite runs success, _ := RunFixture("happy-path", fixture) Ω(success).Should(BeTrue()) }) It("runs all the functions", func() { Ω(rt).Should(HaveTracked( "before-suite-proc-1", "before-suite-all-procs", "test", "after-suite-all-procs", "after-suite-proc-1", )) }) It("reports on the SynchronizedBeforeSuite and SynchronizedAfterSuite as having passed", func() { Ω(reporter.Did.FindByLeafNodeType(types.NodeTypeSynchronizedBeforeSuite)).Should(HavePassed()) Ω(reporter.Did.FindByLeafNodeType(types.NodeTypeSynchronizedAfterSuite)).Should(HavePassed()) }) It("passes data between the two SynchronizedBeforeSuite functions and up to the server", func() { Ω(rt).Should(HaveRunWithData("before-suite-all-procs", "data", "hey there")) state, data, err := client.BlockUntilSynchronizedBeforeSuiteData() Ω(state).Should(Equal(types.SpecStatePassed)) Ω(data).Should(Equal([]byte("hey there"))) Ω(err).ShouldNot(HaveOccurred()) }) It("emits the output of the proc-1 BeforeSuite function and the proc-1 AfterSuite function", func() { Ω(string(serverOutputBuffer.Contents())).Should(Equal("before-suite-proc-1after-suite-proc-1")) Ω(reporter.Did.FindByLeafNodeType(types.NodeTypeSynchronizedBeforeSuite)).Should(HavePassed(CapturedStdOutput("before-suite-proc-1before-suite-all-procs"))) Ω(reporter.Did.FindByLeafNodeType(types.NodeTypeSynchronizedAfterSuite)).Should(HavePassed(CapturedStdOutput("after-suite-all-procsafter-suite-proc-1"))) }) }) Describe("when the BeforeSuite proc1 function fails", func() { BeforeEach(func() { close(exitChannels[2]) //trigger proc 2 exiting so the proc1 after suite runs failInBeforeSuiteProc1 = true success, _ := RunFixture("happy-path", fixture) Ω(success).Should(BeFalse()) }) It("tells the server", func() { state, data, err := client.BlockUntilSynchronizedBeforeSuiteData() Ω(state).Should(Equal(types.SpecStateFailed)) Ω(data).Should(BeNil()) Ω(err).ShouldNot(HaveOccurred()) }) }) Describe("waiting for all procs to finish before running the AfterSuite proc 1 function", func() { It("waits for the server to give it the all clear", func() { done := make(chan interface{}) go func() { defer GinkgoRecover() success, _ := RunFixture("happy-path", fixture) Ω(success).Should(BeTrue()) close(done) }() Consistently(done).ShouldNot(BeClosed()) close(exitChannels[2]) Eventually(done).Should(BeClosed()) }) }) }) Describe("when running as another proc", func() { BeforeEach(func() { conf.ParallelProcess = 2 }) Describe("happy path", func() { BeforeEach(func() { client.PostSynchronizedBeforeSuiteCompleted(types.SpecStatePassed, []byte("hola hola")) success, _ := RunFixture("happy-path", fixture) Ω(success).Should(BeTrue()) }) It("runs all the all-procs functions", func() { Ω(rt).Should(HaveTracked( "before-suite-all-procs", "test", "after-suite-all-procs", )) }) It("reports on the SynchronizedBeforeSuite and SynchronizedAfterSuite as having passed", func() { Ω(reporter.Did.FindByLeafNodeType(types.NodeTypeSynchronizedBeforeSuite)).Should(HavePassed()) Ω(reporter.Did.FindByLeafNodeType(types.NodeTypeSynchronizedAfterSuite)).Should(HavePassed()) }) It("gets data for the SynchronizedBeforeSuite all procs function from the server", func() { Ω(rt).Should(HaveRunWithData("before-suite-all-procs", "data", "hola hola")) }) }) Describe("waiting for the data from proc 1", func() { It("waits for the server to give it the data", func() { done := make(chan interface{}) go func() { defer GinkgoRecover() success, _ := RunFixture("happy-path", fixture) Ω(success).Should(BeTrue()) close(done) }() Consistently(done).ShouldNot(BeClosed()) client.PostSynchronizedBeforeSuiteCompleted(types.SpecStatePassed, []byte("hola hola")) Eventually(done).Should(BeClosed()) Ω(rt).Should(HaveRunWithData("before-suite-all-procs", "data", "hola hola")) }) }) Describe("when proc 1 fails the SynchronizedBeforeSuite proc1 function", func() { It("fails and only runs the after suite", func() { done := make(chan interface{}) go func() { defer GinkgoRecover() success, _ := RunFixture("happy-path", fixture) Ω(success).Should(BeFalse()) close(done) }() Consistently(done).ShouldNot(BeClosed()) client.PostSynchronizedBeforeSuiteCompleted(types.SpecStateFailed, nil) Eventually(done).Should(BeClosed()) Ω(rt).Should(HaveTracked("after-suite-all-procs")) Ω(reporter.Did.FindByLeafNodeType(types.NodeTypeSynchronizedBeforeSuite)).Should(HaveFailed(types.GinkgoErrors.SynchronizedBeforeSuiteFailedOnProc1().Error())) }) }) Describe("when the proc1 SynchronizedBeforeSuite function Skips()", func() { It("fails and only runs the after suite", func() { done := make(chan interface{}) go func() { defer GinkgoRecover() success, _ := RunFixture("happy-path", fixture) Ω(success).Should(BeTrue()) close(done) }() Consistently(done).ShouldNot(BeClosed()) client.PostSynchronizedBeforeSuiteCompleted(types.SpecStateSkipped, nil) Eventually(done).Should(BeClosed()) Ω(rt).Should(HaveTracked("after-suite-all-procs")) Ω(reporter.Did.FindByLeafNodeType(types.NodeTypeSynchronizedBeforeSuite)).Should(HaveBeenSkipped()) }) }) Describe("when proc 1 disappears before the proc 1 function returns", func() { It("fails and only runs the after suite", func() { done := make(chan interface{}) go func() { defer GinkgoRecover() success, _ := RunFixture("happy-path", fixture) Ω(success).Should(BeFalse()) close(done) }() Consistently(done).ShouldNot(BeClosed()) close(exitChannels[1]) Eventually(done).Should(BeClosed()) Ω(rt).Should(HaveTracked("after-suite-all-procs")) Ω(reporter.Did.FindByLeafNodeType(types.NodeTypeSynchronizedBeforeSuite)).Should(HaveFailed(types.GinkgoErrors.SynchronizedBeforeSuiteDisappearedOnProc1().Error())) }) }) }) }) }) golang-github-onsi-ginkgo-v2-2.22.0/internal/internal_integration/table_test.go000066400000000000000000000453761472321612100276210ustar00rootroot00000000000000package internal_integration_test import ( "fmt" "strings" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" . "github.com/onsi/ginkgo/v2/internal/test_helpers" "github.com/onsi/ginkgo/v2/types" ) var _ = Describe("Table driven tests", func() { var bodyFunc = func(a, b int) { rt.Run(CurrentSpecReport().LeafNodeText) if a != b { F("fail") } } Describe("constructing tables", func() { BeforeEach(func() { success, _ := RunFixture("table happy-path", func() { DescribeTable("hello", bodyFunc, Entry("A", 1, 1), Entry("B", 1, 1), []TableEntry{ Entry("C", 1, 2), Entry("D", 1, 1), }) }) Ω(success).Should(BeFalse()) }) It("runs all the entries", func() { Ω(rt).Should(HaveTracked("A", "B", "C", "D")) }) It("reports on the tests correctly", func() { Ω(reporter.Did.Names()).Should(Equal([]string{"A", "B", "C", "D"})) Ω(reporter.Did.Find("C")).Should(HaveFailed("fail", types.NodeTypeIt)) Ω(reporter.End).Should(BeASuiteSummary(false, NSpecs(4), NPassed(3), NFailed(1))) }) }) Describe("constructing subtree tables", func() { BeforeEach(func() { success, _ := RunFixture("table subtree happy-path", func() { DescribeTableSubtree("hello", func(a, b, sum, difference int) { var actualSum, actualDifference int BeforeEach(func() { rt.Run(CurrentSpecReport().ContainerHierarchyTexts[1] + " bef") actualSum = a + b actualDifference = a - b }) It(fmt.Sprintf("%d + %d sums correctly", a, b), func() { rt.Run(CurrentSpecReport().ContainerHierarchyTexts[1] + " sum") if actualSum != sum { F("fail") } }) It(fmt.Sprintf("%d - %d subtracts correctly", a, b), func() { rt.Run(CurrentSpecReport().ContainerHierarchyTexts[1] + " difference") if actualDifference != difference { F("fail") } }) }, func(a, b, sum, differenct int) string { return fmt.Sprintf("%d,%d", a, b) }, Entry(nil, 1, 1, 2, 0), Entry(nil, 1, 2, 3, -1), Entry(nil, 2, 1, 0, 0), ) }) Ω(success).Should(BeFalse()) }) It("runs all the entries", func() { Ω(rt).Should(HaveTracked("1,1 bef", "1,1 sum", "1,1 bef", "1,1 difference", "1,2 bef", "1,2 sum", "1,2 bef", "1,2 difference", "2,1 bef", "2,1 sum", "2,1 bef", "2,1 difference")) }) It("reports on the tests correctly", func() { Ω(reporter.Did.Names()).Should(Equal([]string{"1 + 1 sums correctly", "1 - 1 subtracts correctly", "1 + 2 sums correctly", "1 - 2 subtracts correctly", "2 + 1 sums correctly", "2 - 1 subtracts correctly"})) Ω(reporter.Did.Find("1 + 1 sums correctly")).Should(HavePassed()) Ω(reporter.Did.Find("1 - 1 subtracts correctly")).Should(HavePassed()) Ω(reporter.Did.Find("1 + 2 sums correctly")).Should(HavePassed()) Ω(reporter.Did.Find("1 - 2 subtracts correctly")).Should(HavePassed()) Ω(reporter.Did.Find("2 + 1 sums correctly")).Should(HaveFailed("fail", types.NodeTypeIt)) Ω(reporter.Did.Find("2 - 1 subtracts correctly")).Should(HaveFailed("fail", types.NodeTypeIt)) Ω(reporter.End).Should(BeASuiteSummary(false, NSpecs(6), NPassed(4), NFailed(2))) }) }) Describe("Entry Descriptions", func() { Describe("tables with no table-level entry description functions or strings", func() { BeforeEach(func() { success, _ := RunFixture("table with no table-level entry description function", func() { DescribeTable("hello", func(a int, b string, c ...float64) {}, Entry(nil, 1, "b"), Entry(nil, 1, "b", 2.71, 3.141), Entry("C", 3, "b", 3.141), ) }) Ω(success).Should(BeTrue()) }) It("renders the parameters for nil-described Entries as It strings", func() { Ω(reporter.Did.Names()).Should(Equal([]string{ "Entry: 1, b", "Entry: 1, b, 2.71, 3.141", "C", })) }) }) Describe("tables with a table-level entry description function", func() { Context("happy path", func() { BeforeEach(func() { success, _ := RunFixture("table with table-level entry description function", func() { DescribeTable("hello", func(a int, b string, c ...float64) {}, func(a int, b string, c ...float64) string { return fmt.Sprintf("%d | %s | %v", a, b, c) }, Entry(nil, 1, "b"), Entry(nil, 1, "b", 2.71, 3.141), Entry("C", 3, "b", 3.141), ) }) Ω(success).Should(BeTrue()) }) It("renders the parameters for nil-described Entries using the provided function as It strings", func() { Ω(reporter.Did.Names()).Should(Equal([]string{ "1 | b | []", "1 | b | [2.71 3.141]", "C", })) }) }) Context("with more than one entry description function", func() { BeforeEach(func() { success, _ := RunFixture("table with multiple table-level entry description function", func() { DescribeTable("hello", func(a int, b string, c ...float64) {}, func(a int, b string, c ...float64) string { return fmt.Sprintf("%d | %s | %v", a, b, c) }, func(a int, b string, c ...float64) string { return fmt.Sprintf("%d ~ %s ~ %v", a, b, c) }, Entry(nil, 1, "b"), Entry(nil, 1, "b", 2.71, 3.141), Entry("C", 3, "b", 3.141), ) }) Ω(success).Should(BeTrue()) }) It("renders the parameters for nil-described Entries using the last provided function as It strings", func() { Ω(reporter.Did.Names()).Should(Equal([]string{ "1 ~ b ~ []", "1 ~ b ~ [2.71 3.141]", "C", })) }) }) Context("with a parameter mismatch", func() { BeforeEach(func() { success, _ := RunFixture("table with multiple table-level entry description function", func() { DescribeTable("hello", func(a int, b string, c ...float64) {}, func(a int, b string) string { return fmt.Sprintf("%d | %s", a, b) }, Entry(nil, 1, "b"), Entry(nil, 1, "b", 2.71, 3.141), Entry("C", 3, "b", 3.141), ) }) Ω(success).Should(BeFalse()) }) It("fails the entry with a panic", func() { Ω(reporter.Did.Find("1 | b")).Should(HavePassed()) Ω(reporter.Did.Find("")).Should(HavePanicked("Too many parameters passed in to Entry Description function")) Ω(reporter.Did.Find("C")).Should(HavePassed()) }) }) }) Describe("tables with a table-level entry description format strings", func() { BeforeEach(func() { success, _ := RunFixture("table with table-level entry description format strings", func() { DescribeTable("hello", func(a int, b string, c float64) {}, func(a int, b string, c float64) string { return "ignored" }, EntryDescription("%[2]s | %[1]d | %[3]v"), Entry(nil, 1, "a", 1.2), Entry(nil, 1, "b", 2.71), Entry("C", 3, "b", 3.141), ) }) Ω(success).Should(BeTrue()) }) It("renders the parameters for nil-described Entries using the provided function as It strings", func() { Ω(reporter.Did.Names()).Should(Equal([]string{ "a | 1 | 1.2", "b | 1 | 2.71", "C", })) }) }) Describe("entries with entry description functions and entry description format strings", func() { BeforeEach(func() { entryDescriptionBuilder := func(a, b int) string { return fmt.Sprintf("%d vs %d", a, b) } invalidEntryDescriptionBuilder := func(a, b int) {} success, _ := RunFixture("table happy-path with custom descriptions", func() { DescribeTable("hello", bodyFunc, EntryDescription("table-level %d, %d"), Entry(entryDescriptionBuilder, 1, 1), Entry(entryDescriptionBuilder, 2, 2), Entry(entryDescriptionBuilder, 1, 2), Entry(entryDescriptionBuilder, 3, 3), Entry("A", 4, 4), Entry(nil, 5, 5), Entry(EntryDescription("%dx%d"), 6, 6), Entry(invalidEntryDescriptionBuilder, 4, 4), ) }) Ω(success).Should(BeFalse()) }) It("runs all the entries, with the correct names", func() { Ω(rt).Should(HaveTracked("1 vs 1", "2 vs 2", "1 vs 2", "3 vs 3", "A", "table-level 5, 5", "6x6")) }) It("catches invalid entry description functions", func() { Ω(reporter.Did.Find("")).Should(HavePanicked("Invalid Entry description")) }) It("reports on the tests correctly", func() { Ω(reporter.Did.Names()).Should(Equal([]string{"1 vs 1", "2 vs 2", "1 vs 2", "3 vs 3", "A", "table-level 5, 5", "6x6"})) Ω(reporter.Did.Find("1 vs 2")).Should(HaveFailed("fail", types.NodeTypeIt)) Ω(reporter.End).Should(BeASuiteSummary(false, NSpecs(8), NPassed(6), NFailed(2))) }) }) }) Describe("managing parameters", func() { Describe("when table entries are passed incorrect parameters", func() { BeforeEach(func() { success, _ := RunFixture("table with invalid inputs", func() { Describe("container", func() { DescribeTable("with variadic parameters", func(a int, b string, c ...float64) { rt.Run(CurrentSpecReport().LeafNodeText) }, Entry("var-A", 1, "b"), Entry("var-B", 1, "b", 3.0, 4.0), Entry("var-too-few", 1), Entry("var-wrong-type", 1, 2), Entry("var-wrong-type-variadic", 1, "b", 3.0, 4, 5.0), ) DescribeTable("without variadic parameters", func(a int, b string) { rt.Run(CurrentSpecReport().LeafNodeText) }, Entry("nonvar-A", 1, "b"), Entry("nonvar-too-few", 1), Entry("nonvar-wrong-type", 1, 2), Entry("nonvar-too-many", 1, "b", 2), Entry(func(a int, b string) string { return "foo" }, 1, 2), ) }) }) Ω(success).Should(BeFalse()) }) It("runs all the valid entries, but not the invalid entries", func() { Ω(rt).Should(HaveTracked("var-A", "var-B", "nonvar-A")) }) It("reports the invalid entries as having panicked", func() { Ω(reporter.Did.Find("var-too-few")).Should(HavePanicked("The Table Body function expected 2 parameters but you passed in 1")) Ω(reporter.Did.Find("var-wrong-type")).Should(HavePanicked("The Table Body function expected parameter #2 to be of type but you\n passed in ")) Ω(reporter.Did.Find("var-wrong-type-variadic")).Should(HavePanicked("The Table Body function expected its variadic parameters to be of type\n but you passed in ")) Ω(reporter.Did.Find("nonvar-too-few")).Should(HavePanicked("The Table Body function expected 2 parameters but you passed in 1")) Ω(reporter.Did.Find("nonvar-wrong-type")).Should(HavePanicked("The Table Body function expected parameter #2 to be of type but you\n passed in ")) Ω(reporter.Did.Find("nonvar-too-many")).Should(HavePanicked("The Table Body function expected 2 parameters but you passed in 3")) Ω(reporter.Did.Find("")).Should(HavePanicked("The Entry Description function expected parameter #2 to be of type ")) }) It("reports on the tests correctly", func() { Ω(reporter.End).Should(BeASuiteSummary(false, NSpecs(10), NPassed(3), NFailed(7))) }) }) Describe("handling complex types", func() { type ComplicatedThings struct { Superstructure string Substructure string } var A, B, C ComplicatedThings BeforeEach(func() { A = ComplicatedThings{Superstructure: "the sixth sheikh's sixth sheep's sick", Substructure: "emir"} B = ComplicatedThings{Superstructure: "the sixth sheikh's sixth sheep's sick", Substructure: "sheep"} C = ComplicatedThings{Superstructure: "the sixth sheikh's sixth sheep's sick", Substructure: "si"} success, _ := RunFixture("table with complicated inputs`", func() { DescribeTable("a more complicated table", func(c ComplicatedThings, count int) { rt.RunWithData(CurrentSpecReport().LeafNodeText, "thing", c, "count", count) Ω(strings.Count(c.Superstructure, c.Substructure)).Should(BeNumerically("==", count)) }, Entry("A", A, 0), Entry("B", B, 1), Entry("C", C, 3), ) }) Ω(success).Should(BeTrue()) }) It("passes the parameters in correctly", func() { Ω(rt.DataFor("A")).Should(HaveKeyWithValue("thing", A)) Ω(rt.DataFor("A")).Should(HaveKeyWithValue("count", 0)) Ω(rt.DataFor("B")).Should(HaveKeyWithValue("thing", B)) Ω(rt.DataFor("B")).Should(HaveKeyWithValue("count", 1)) Ω(rt.DataFor("C")).Should(HaveKeyWithValue("thing", C)) Ω(rt.DataFor("C")).Should(HaveKeyWithValue("count", 3)) }) }) DescribeTable("it works when nils are passed in", func(a interface{}, b error) { Ω(a).Should(BeNil()) Ω(b).Should(BeNil()) }, Entry("nils", nil, nil)) DescribeTable("it supports variadic parameters", func(a int, b string, c ...interface{}) { Ω(a).Should(Equal(c[0])) Ω(b).Should(Equal(c[1])) Ω(c[2]).Should(BeNil()) }, Entry("variadic arguments", 1, "one", 1, "one", nil)) }) Describe("when table entries are marked pending", func() { BeforeEach(func() { success, _ := RunFixture("table with pending entries", func() { DescribeTable("hello", bodyFunc, Entry("A", 1, 1), PEntry("B", 1, 1), Entry("C", 1, 2), Entry("D", 1, 1), Entry("E", Pending, 1, 1)) }) Ω(success).Should(BeFalse()) }) It("runs all the non-pending entries", func() { Ω(rt).Should(HaveTracked("A", "C", "D")) }) It("reports on the tests correctly", func() { Ω(reporter.Did.Names()).Should(Equal([]string{"A", "B", "C", "D", "E"})) Ω(reporter.Did.Find("B")).Should(BePending()) Ω(reporter.Did.Find("C")).Should(HaveFailed("fail", types.NodeTypeIt)) Ω(reporter.Did.Find("E")).Should(BePending()) Ω(reporter.End).Should(BeASuiteSummary(false, NSpecs(5), NPassed(2), NFailed(1), NPending(2))) }) }) Describe("when table entries are marked focused", func() { BeforeEach(func() { success, _ := RunFixture("table with focused entries", func() { DescribeTable("hello", bodyFunc, Entry("A", 1, 1), Entry("B", 1, 1), FEntry("C", 1, 2), FEntry("D", 1, 1), Entry("E", Focus, 1, 1)) }) Ω(success).Should(BeFalse()) }) It("runs all the focused entries", func() { Ω(rt).Should(HaveTracked("C", "D", "E")) }) It("reports on the tests correctly", func() { Ω(reporter.Did.Names()).Should(Equal([]string{"A", "B", "C", "D", "E"})) Ω(reporter.Did.Find("A")).Should(HaveBeenSkipped()) Ω(reporter.Did.Find("B")).Should(HaveBeenSkipped()) Ω(reporter.Did.Find("C")).Should(HaveFailed("fail", types.NodeTypeIt)) Ω(reporter.Did.Find("D")).Should(HavePassed(types.NodeTypeIt)) Ω(reporter.Did.Find("E")).Should(HavePassed(types.NodeTypeIt)) Ω(reporter.End).Should(BeASuiteSummary(false, NSpecs(5), NPassed(2), NFailed(1), NSkipped(2))) }) }) Describe("when tables are marked pending", func() { BeforeEach(func() { success, _ := RunFixture("table marked pending", func() { Describe("top-level", func() { PDescribeTable("hello", bodyFunc, Entry("A", 1, 1), Entry("B", 1, 1)) DescribeTable("hello", Pending, bodyFunc, Entry("C", 1, 2), Entry("D", 1, 1)) It("runs", rt.T("runs")) }) }) Ω(success).Should(BeTrue()) }) It("runs all the focused entries", func() { Ω(rt).Should(HaveTracked("runs")) }) It("reports on the tests correctly", func() { Ω(reporter.Did.Names()).Should(Equal([]string{"A", "B", "C", "D", "runs"})) Ω(reporter.Did.Find("A")).Should(BePending()) Ω(reporter.Did.Find("B")).Should(BePending()) Ω(reporter.Did.Find("C")).Should(BePending()) Ω(reporter.Did.Find("D")).Should(BePending()) Ω(reporter.Did.Find("runs")).Should(HavePassed()) Ω(reporter.End).Should(BeASuiteSummary(true, NSpecs(5), NPassed(1), NFailed(0), NPending(4))) }) }) Describe("when tables are marked focused", func() { BeforeEach(func() { success, _ := RunFixture("table marked focused", func() { Describe("top-level", func() { FDescribeTable("hello", bodyFunc, Entry("A", 1, 1), Entry("B", 1, 1)) DescribeTable("hello", Focus, bodyFunc, Entry("C", 1, 2), Entry("D", 1, 1)) It("does not run", rt.T("does not run")) }) }) Ω(success).Should(BeFalse()) }) It("runs all the focused entries", func() { Ω(rt).Should(HaveTracked("A", "B", "C", "D")) }) It("reports on the tests correctly", func() { Ω(reporter.Did.Names()).Should(Equal([]string{"A", "B", "C", "D", "does not run"})) Ω(reporter.Did.Find("A")).Should(HavePassed()) Ω(reporter.Did.Find("B")).Should(HavePassed()) Ω(reporter.Did.Find("C")).Should(HaveFailed("fail")) Ω(reporter.Did.Find("D")).Should(HavePassed()) Ω(reporter.Did.Find("does not run")).Should(HaveBeenSkipped()) Ω(reporter.End).Should(BeASuiteSummary(false, NSpecs(5), NPassed(3), NFailed(1), NSkipped(1))) }) }) Describe("support for FlakyAttempts decorators", func() { BeforeEach(func() { success, _ := RunFixture("flaky table", func() { var counter int var currentSpec string BeforeEach(func() { if currentSpec != CurrentSpecReport().LeafNodeText { counter = 0 currentSpec = CurrentSpecReport().LeafNodeText } }) DescribeTable("contrived flaky table", FlakeAttempts(2), func(failUntil int) { rt.Run(CurrentSpecReport().LeafNodeText) counter += 1 if counter < failUntil { F("fail") } }, Entry("A", 1), Entry("B", 2), Entry("C", 3), Entry("D", []interface{}{FlakeAttempts(3), Offset(2)}, 3), ) }) Ω(success).Should(BeFalse()) }) It("honors the flake attempts decorator", func() { Ω(rt).Should(HaveTracked("A", "B", "B", "C", "C", "D", "D", "D")) }) It("reports on the specs appropriately", func() { Ω(reporter.Did.Find("A")).Should(HavePassed(NumAttempts(1))) Ω(reporter.Did.Find("B")).Should(HavePassed(NumAttempts(2))) Ω(reporter.Did.Find("C")).Should(HaveFailed(NumAttempts(2))) Ω(reporter.Did.Find("D")).Should(HavePassed(NumAttempts(3))) }) }) Describe("support for MustPassRepeatedly decorators", func() { BeforeEach(func() { success, _ := RunFixture("Repeated Passes table", func() { var counter int var currentSpec string BeforeEach(func() { if currentSpec != CurrentSpecReport().LeafNodeText { counter = 0 currentSpec = CurrentSpecReport().LeafNodeText } }) DescribeTable("contrived Repeated Passes table", MustPassRepeatedly(2), func(passUntil int) { rt.Run(CurrentSpecReport().LeafNodeText) counter += 1 if counter >= passUntil { F("fail") } }, Entry("A", 1), Entry("B", 2), Entry("C", 3), Entry("D", []interface{}{MustPassRepeatedly(3), Offset(2)}, 3), ) }) Ω(success).Should(BeFalse()) }) It("honors the Repeated attempts decorator", func() { Ω(rt).Should(HaveTracked("A", "B", "B", "C", "C", "D", "D", "D")) }) It("reports on the specs appropriately", func() { Ω(reporter.Did.Find("A")).Should(HaveFailed(NumAttempts(1))) Ω(reporter.Did.Find("B")).Should(HaveFailed(NumAttempts(2))) Ω(reporter.Did.Find("C")).Should(HavePassed(NumAttempts(2))) Ω(reporter.Did.Find("D")).Should(HaveFailed(NumAttempts(3))) }) }) }) golang-github-onsi-ginkgo-v2-2.22.0/internal/internal_integration/timeline_test.go000066400000000000000000000210411472321612100303170ustar00rootroot00000000000000package internal_integration_test import ( "sort" "time" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/ginkgo/v2/internal/test_helpers" "github.com/onsi/ginkgo/v2/types" . "github.com/onsi/gomega" ) var _ = Describe("Generating Timelines", func() { BeforeEach(func() { cl = types.NewCodeLocation(0) success, _ := RunFixture("current test description", func() { Context("Ordering", func() { Describe("a timeout", func() { BeforeEach(func() { writer.Print("bef\n") time.Sleep(time.Millisecond * 200) }, PollProgressAfter(time.Millisecond*100)) It("spec", func(ctx SpecContext) { writer.Print("it\n") By("waiting", func() { <-ctx.Done() }) AddReportEntry("report-entry") F("welp", types.NewCodeLocation(0)) }, NodeTimeout(time.Millisecond*100)) AfterEach(func() { writer.Print("aft\n") By("trying to clean up") panic("bam") }) AfterEach(func() { F("boom", types.NewCodeLocation(0)) }) }) Describe("a flake", func() { i := 0 It("flakes repeatedly", func() { writer.Print("running\n") if i < 2 { i += 1 F("flake", types.NewCodeLocation(0)) } }, FlakeAttempts(3)) }) }) }) Ω(success).Should(BeFalse()) }) It("runs the specs", func() { Ω(reporter.Did.Find("spec")).Should(HaveTimedOut("A node timeout occurred", clLine(8), CapturedGinkgoWriterOutput("bef\nit\naft\n"))) Ω(reporter.Did.Find("flakes repeatedly")).Should(HavePassed(clLine(8), 3, CapturedGinkgoWriterOutput("running\nrunning\nrunning\n"))) }) It("generates a correctly sorted timeline for the timedout spec", func() { timeline := reporter.Did.Find("spec").Timeline() sort.Sort(timeline) Ω(timeline).Should(BeTimelineExactlyMatching( BeSpecEvent(types.SpecEventNodeStart, types.NodeTypeBeforeEach, TLWithOffset(0), clLine(4), "a timeout"), BeProgressReport("Automatically polling progress:", TLWithOffset("bef\n"), clLine(4), types.NodeTypeBeforeEach), BeSpecEvent(types.SpecEventNodeEnd, types.NodeTypeBeforeEach, TLWithOffset("bef\n"), clLine(4), time.Millisecond*200), BeSpecEvent(types.SpecEventNodeStart, types.NodeTypeIt, TLWithOffset("bef\n"), clLine(8), "spec"), BeSpecEvent(types.SpecEventByStart, TLWithOffset("bef\nit\n"), clLine(10), "waiting"), And( HaveField("Message", "A node timeout occurred"), HaveField("TimelineLocation.Offset", TLWithOffset("bef\nit\n").Offset), ), BeSpecEvent(types.SpecEventByEnd, TLWithOffset("bef\nit\n"), clLine(10), "waiting", time.Millisecond*100), BeReportEntry("report-entry", ReportEntryVisibilityAlways, TLWithOffset("bef\nit\n"), clLine(13)), HaveFailed("A node timeout occurred and then the following failure was recorded in the timedout node before it exited:\nwelp", clLine(14), TLWithOffset("bef\nit\n")), BeSpecEvent(types.SpecEventNodeEnd, types.NodeTypeIt, TLWithOffset("bef\nit\n"), clLine(8), "spec", time.Millisecond*100), BeSpecEvent(types.SpecEventNodeStart, types.NodeTypeAfterEach, TLWithOffset("bef\nit\n"), clLine(16), "a timeout"), BeSpecEvent(types.SpecEventByStart, TLWithOffset("bef\nit\naft\n"), clLine(18), "trying to clean up"), HavePanicked("bam", TLWithOffset("bef\nit\naft\n"), FailureNodeType(types.NodeTypeAfterEach)), BeSpecEvent(types.SpecEventNodeEnd, types.NodeTypeAfterEach, TLWithOffset("bef\nit\naft\n"), clLine(16), "a timeout"), BeSpecEvent(types.SpecEventNodeStart, types.NodeTypeAfterEach, TLWithOffset("bef\nit\naft\n"), clLine(21), "a timeout"), HaveFailed("boom", TLWithOffset("bef\nit\naft\n"), FailureNodeType(types.NodeTypeAfterEach), clLine(21)), BeSpecEvent(types.SpecEventNodeEnd, types.NodeTypeAfterEach, TLWithOffset("bef\nit\naft\n"), clLine(21), "a timeout"), )) }) It("generates a correctly sorted timeline for the flakey spec", func() { timeline := reporter.Did.Find("flakes repeatedly").Timeline() sort.Sort(timeline) Ω(timeline).Should(BeTimelineExactlyMatching( BeSpecEvent(types.SpecEventNodeStart, types.NodeTypeIt, TLWithOffset(0), clLine(25), "flakes repeatedly"), HaveFailed("Failure recorded during attempt 1:\nflake", TLWithOffset("running\n"), clLine(29)), BeSpecEvent(types.SpecEventNodeEnd, types.NodeTypeIt, TLWithOffset("running\n"), clLine(25), "flakes repeatedly"), BeSpecEvent(types.SpecEventSpecRetry, TLWithOffset("running\n"), 1), BeSpecEvent(types.SpecEventNodeStart, types.NodeTypeIt, TLWithOffset("running\n"), clLine(25), "flakes repeatedly"), HaveFailed("Failure recorded during attempt 2:\nflake", TLWithOffset("running\nrunning\n"), clLine(29)), BeSpecEvent(types.SpecEventNodeEnd, types.NodeTypeIt, TLWithOffset("running\nrunning\n"), clLine(25), "flakes repeatedly"), BeSpecEvent(types.SpecEventSpecRetry, TLWithOffset("running\nrunning\n"), 2), BeSpecEvent(types.SpecEventNodeStart, types.NodeTypeIt, TLWithOffset("running\nrunning\n"), clLine(25), "flakes repeatedly"), BeSpecEvent(types.SpecEventNodeEnd, types.NodeTypeIt, TLWithOffset("running\nrunning\nrunning\n"), clLine(25), "flakes repeatedly"), )) }) It("emits all these timeline events along the way", func() { t := types.Timeline{} for _, failure := range reporter.Failures { t = append(t, failure) } Ω(t).Should(BeTimelineExactlyMatching( HaveTimedOut("A node timeout occurred", TLWithOffset("bef\nit\n")), HaveFailed("A node timeout occurred and then the following failure was recorded in the timedout node before it exited:\nwelp", clLine(14), TLWithOffset("bef\nit\n")), HavePanicked("bam", TLWithOffset("bef\nit\naft\n"), FailureNodeType(types.NodeTypeAfterEach)), HaveFailed("boom", TLWithOffset("bef\nit\naft\n"), FailureNodeType(types.NodeTypeAfterEach), clLine(21)), HaveFailed("flake", TLWithOffset("running\n"), clLine(29)), HaveFailed("flake", TLWithOffset("running\nrunning\n"), clLine(29)), )) t = types.Timeline{} for _, event := range reporter.SpecEvents { t = append(t, event) } Ω(t).Should(BeTimelineExactlyMatching( BeSpecEvent(types.SpecEventNodeStart, types.NodeTypeBeforeEach, TLWithOffset(0), clLine(4), "a timeout"), BeSpecEvent(types.SpecEventNodeEnd, types.NodeTypeBeforeEach, TLWithOffset("bef\n"), clLine(4), time.Millisecond*200), BeSpecEvent(types.SpecEventNodeStart, types.NodeTypeIt, TLWithOffset("bef\n"), clLine(8), "spec"), BeSpecEvent(types.SpecEventByStart, TLWithOffset("bef\nit\n"), clLine(10), "waiting"), BeSpecEvent(types.SpecEventByEnd, TLWithOffset("bef\nit\n"), clLine(10), "waiting", time.Millisecond*100), BeSpecEvent(types.SpecEventNodeEnd, types.NodeTypeIt, TLWithOffset("bef\nit\n"), clLine(8), "spec", time.Millisecond*100), BeSpecEvent(types.SpecEventNodeStart, types.NodeTypeAfterEach, TLWithOffset("bef\nit\n"), clLine(16), "a timeout"), BeSpecEvent(types.SpecEventByStart, TLWithOffset("bef\nit\naft\n"), clLine(18), "trying to clean up"), BeSpecEvent(types.SpecEventNodeEnd, types.NodeTypeAfterEach, TLWithOffset("bef\nit\naft\n"), clLine(16), "a timeout"), BeSpecEvent(types.SpecEventNodeStart, types.NodeTypeAfterEach, TLWithOffset("bef\nit\naft\n"), clLine(21), "a timeout"), BeSpecEvent(types.SpecEventNodeEnd, types.NodeTypeAfterEach, TLWithOffset("bef\nit\naft\n"), clLine(21), "a timeout"), BeSpecEvent(types.SpecEventNodeStart, types.NodeTypeIt, TLWithOffset(0), clLine(25), "flakes repeatedly"), BeSpecEvent(types.SpecEventNodeEnd, types.NodeTypeIt, TLWithOffset("running\n"), clLine(25), "flakes repeatedly"), BeSpecEvent(types.SpecEventSpecRetry, TLWithOffset("running\n"), 1), BeSpecEvent(types.SpecEventNodeStart, types.NodeTypeIt, TLWithOffset("running\n"), clLine(25), "flakes repeatedly"), BeSpecEvent(types.SpecEventNodeEnd, types.NodeTypeIt, TLWithOffset("running\nrunning\n"), clLine(25), "flakes repeatedly"), BeSpecEvent(types.SpecEventSpecRetry, TLWithOffset("running\nrunning\n"), 2), BeSpecEvent(types.SpecEventNodeStart, types.NodeTypeIt, TLWithOffset("running\nrunning\n"), clLine(25), "flakes repeatedly"), BeSpecEvent(types.SpecEventNodeEnd, types.NodeTypeIt, TLWithOffset("running\nrunning\nrunning\n"), clLine(25), "flakes repeatedly"), )) t = types.Timeline{} for _, report := range reporter.ProgressReports { t = append(t, report) } Ω(t).Should(BeTimelineExactlyMatching( BeProgressReport("Automatically polling progress:", TLWithOffset("bef\n"), clLine(4), types.NodeTypeBeforeEach), )) t = types.Timeline{} for _, report := range reporter.ReportEntries { t = append(t, report) } Ω(t).Should(BeTimelineExactlyMatching( BeReportEntry("report-entry", ReportEntryVisibilityAlways, TLWithOffset("bef\nit\n"), clLine(13)), )) }) }) golang-github-onsi-ginkgo-v2-2.22.0/internal/internal_suite_test.go000066400000000000000000000062231472321612100253240ustar00rootroot00000000000000package internal_test import ( "reflect" "testing" . "github.com/onsi/ginkgo/v2" "github.com/onsi/ginkgo/v2/types" . "github.com/onsi/gomega" "github.com/onsi/ginkgo/v2/internal" ) func TestInternal(t *testing.T) { RegisterFailHandler(Fail) RunSpecs(t, "Internal Suite") } type Node = internal.Node type Nodes = internal.Nodes type NodeType = types.NodeType type TreeNode = internal.TreeNode type TreeNodes = internal.TreeNodes type Spec = internal.Spec type Specs = internal.Specs var ntIt = types.NodeTypeIt var ntCon = types.NodeTypeContainer var ntAf = types.NodeTypeAfterEach var ntBef = types.NodeTypeBeforeEach var ntJusAf = types.NodeTypeJustAfterEach var ntJusBef = types.NodeTypeJustBeforeEach type NestingLevel int // convenience helper to quickly make nodes // assumes they are correctly configured and no errors occur func N(args ...interface{}) Node { nodeType, text, nestingLevel, hasBody := types.NodeTypeIt, "", -1, false remainingArgs := []interface{}{cl} for _, arg := range args { switch t := reflect.TypeOf(arg); { case t == reflect.TypeOf(NestingLevel(1)): nestingLevel = int(arg.(NestingLevel)) case t == reflect.TypeOf(text): text = arg.(string) case t == reflect.TypeOf(nodeType): nodeType = arg.(types.NodeType) case t.Kind() == reflect.Func: hasBody = true remainingArgs = append(remainingArgs, arg) default: remainingArgs = append(remainingArgs, arg) } } //the hasBody dance is necessary to (a) make sure internal.NewNode is happy (it requires a body) and (b) to then nil out the resulting body to ensure node comparisons work //as reflect.DeepEqual cannot compare functions. Even by pointer. 'Cause. You know. if !hasBody { remainingArgs = append(remainingArgs, func() {}) } node, errors := internal.NewNode(nil, nodeType, text, remainingArgs...) if nestingLevel != -1 { node.NestingLevel = nestingLevel } ExpectWithOffset(1, errors).Should(BeEmpty()) if !hasBody { node.Body = nil } return node } // convenience helper to quickly make tree nodes func TN(node Node, children ...*TreeNode) *TreeNode { tn := &TreeNode{Node: node} for _, child := range children { tn.AppendChild(child) } return tn } // convenience helper to quickly make specs func S(nodes ...Node) Spec { return Spec{Nodes: nodes} } // convenience helper to quickly make code locations func CL(options ...interface{}) types.CodeLocation { cl = types.NewCodeLocation(1) for _, option := range options { if reflect.TypeOf(option).Kind() == reflect.String { cl.FileName = option.(string) } else if reflect.TypeOf(option).Kind() == reflect.Int { cl.LineNumber = option.(int) } } return cl } func mustFindNodeWithText(tree *TreeNode, text string) Node { node := findNodeWithText(tree, text) ExpectWithOffset(1, node).ShouldNot(BeZero(), "Failed to find node in tree with text '%s'", text) return node } func findNodeWithText(tree *TreeNode, text string) Node { if tree.Node.Text == text { return tree.Node } for _, tn := range tree.Children { n := findNodeWithText(tn, text) if !n.IsZero() { return n } } return Node{} } var cl types.CodeLocation var _ = BeforeEach(func() { cl = types.NewCodeLocation(0) }) golang-github-onsi-ginkgo-v2-2.22.0/internal/interrupt_handler/000077500000000000000000000000001472321612100244375ustar00rootroot00000000000000golang-github-onsi-ginkgo-v2-2.22.0/internal/interrupt_handler/interrupt_handler.go000066400000000000000000000100511472321612100305140ustar00rootroot00000000000000package interrupt_handler import ( "os" "os/signal" "sync" "syscall" "time" "github.com/onsi/ginkgo/v2/internal/parallel_support" ) var ABORT_POLLING_INTERVAL = 500 * time.Millisecond type InterruptCause uint const ( InterruptCauseInvalid InterruptCause = iota InterruptCauseSignal InterruptCauseAbortByOtherProcess ) type InterruptLevel uint const ( InterruptLevelUninterrupted InterruptLevel = iota InterruptLevelCleanupAndReport InterruptLevelReportOnly InterruptLevelBailOut ) func (ic InterruptCause) String() string { switch ic { case InterruptCauseSignal: return "Interrupted by User" case InterruptCauseAbortByOtherProcess: return "Interrupted by Other Ginkgo Process" } return "INVALID_INTERRUPT_CAUSE" } type InterruptStatus struct { Channel chan interface{} Level InterruptLevel Cause InterruptCause } func (s InterruptStatus) Interrupted() bool { return s.Level != InterruptLevelUninterrupted } func (s InterruptStatus) Message() string { return s.Cause.String() } func (s InterruptStatus) ShouldIncludeProgressReport() bool { return s.Cause != InterruptCauseAbortByOtherProcess } type InterruptHandlerInterface interface { Status() InterruptStatus } type InterruptHandler struct { c chan interface{} lock *sync.Mutex level InterruptLevel cause InterruptCause client parallel_support.Client stop chan interface{} signals []os.Signal requestAbortCheck chan interface{} } func NewInterruptHandler(client parallel_support.Client, signals ...os.Signal) *InterruptHandler { if len(signals) == 0 { signals = []os.Signal{os.Interrupt, syscall.SIGTERM} } handler := &InterruptHandler{ c: make(chan interface{}), lock: &sync.Mutex{}, stop: make(chan interface{}), requestAbortCheck: make(chan interface{}), client: client, signals: signals, } handler.registerForInterrupts() return handler } func (handler *InterruptHandler) Stop() { close(handler.stop) } func (handler *InterruptHandler) registerForInterrupts() { // os signal handling signalChannel := make(chan os.Signal, 1) signal.Notify(signalChannel, handler.signals...) // cross-process abort handling var abortChannel chan interface{} if handler.client != nil { abortChannel = make(chan interface{}) go func() { pollTicker := time.NewTicker(ABORT_POLLING_INTERVAL) for { select { case <-pollTicker.C: if handler.client.ShouldAbort() { close(abortChannel) pollTicker.Stop() return } case <-handler.requestAbortCheck: if handler.client.ShouldAbort() { close(abortChannel) pollTicker.Stop() return } case <-handler.stop: pollTicker.Stop() return } } }() } go func(abortChannel chan interface{}) { var interruptCause InterruptCause for { select { case <-signalChannel: interruptCause = InterruptCauseSignal case <-abortChannel: interruptCause = InterruptCauseAbortByOtherProcess case <-handler.stop: signal.Stop(signalChannel) return } abortChannel = nil handler.lock.Lock() oldLevel := handler.level handler.cause = interruptCause if handler.level == InterruptLevelUninterrupted { handler.level = InterruptLevelCleanupAndReport } else if handler.level == InterruptLevelCleanupAndReport { handler.level = InterruptLevelReportOnly } else if handler.level == InterruptLevelReportOnly { handler.level = InterruptLevelBailOut } if handler.level != oldLevel { close(handler.c) handler.c = make(chan interface{}) } handler.lock.Unlock() } }(abortChannel) } func (handler *InterruptHandler) Status() InterruptStatus { handler.lock.Lock() status := InterruptStatus{ Level: handler.level, Channel: handler.c, Cause: handler.cause, } handler.lock.Unlock() if handler.client != nil && handler.client.ShouldAbort() && !status.Interrupted() { close(handler.requestAbortCheck) <-status.Channel return handler.Status() } return status } golang-github-onsi-ginkgo-v2-2.22.0/internal/interrupt_handler/interrupt_handler_test.go000066400000000000000000000147141472321612100315650ustar00rootroot00000000000000//go:build freebsd || openbsd || netbsd || dragonfly || darwin || linux || solaris // +build freebsd openbsd netbsd dragonfly darwin linux solaris package interrupt_handler_test import ( "syscall" "time" . "github.com/onsi/ginkgo/v2" "github.com/onsi/ginkgo/v2/internal/interrupt_handler" "github.com/onsi/ginkgo/v2/internal/parallel_support" . "github.com/onsi/ginkgo/v2/internal/test_helpers" . "github.com/onsi/gomega" ) var _ = Describe("InterruptHandler", func() { var trigger func() var interruptHandler *interrupt_handler.InterruptHandler BeforeEach(func() { trigger = func() { syscall.Kill(syscall.Getpid(), syscall.SIGUSR2) } }) Describe("Signal interrupts", func() { BeforeEach(func() { interruptHandler = interrupt_handler.NewInterruptHandler(nil, syscall.SIGUSR2) DeferCleanup(interruptHandler.Stop) }, OncePerOrdered) It("starts off uninterrupted", func() { status := interruptHandler.Status() Ω(status.Interrupted()).Should(BeFalse()) Ω(status.Cause).Should(Equal(interrupt_handler.InterruptCauseInvalid)) Ω(status.Level).Should(Equal(interrupt_handler.InterruptLevelUninterrupted)) Consistently(status.Channel).ShouldNot(BeClosed()) }) When("interrupted repeatedly", Ordered, func() { var status interrupt_handler.InterruptStatus BeforeAll(func() { status = interruptHandler.Status() Ω(status.Interrupted()).Should(BeFalse()) }) Specify("when first interrupted, it closes the channel and goes to the next Cleanup and Report level", func() { trigger() Eventually(status.Channel).Should(BeClosed()) status = interruptHandler.Status() Ω(status.Interrupted()).Should(BeTrue()) Ω(status.Cause).Should(Equal(interrupt_handler.InterruptCauseSignal)) Ω(status.Message()).Should(Equal("Interrupted by User")) Ω(status.Level).Should(Equal(interrupt_handler.InterruptLevelCleanupAndReport)) Ω(status.ShouldIncludeProgressReport()).Should(BeTrue()) }) Specify("when interrupted a second time, it closes the next channel and goes to the Report Only level", func() { trigger() Eventually(status.Channel).Should(BeClosed()) status = interruptHandler.Status() Ω(status.Interrupted()).Should(BeTrue()) Ω(status.Cause).Should(Equal(interrupt_handler.InterruptCauseSignal)) Ω(status.Message()).Should(Equal("Interrupted by User")) Ω(status.Level).Should(Equal(interrupt_handler.InterruptLevelReportOnly)) Ω(status.ShouldIncludeProgressReport()).Should(BeTrue()) }) Specify("when interrupted a third time, it closes the next channel and goes to the Bail-Out level", func() { trigger() Eventually(status.Channel).Should(BeClosed()) status = interruptHandler.Status() Ω(status.Interrupted()).Should(BeTrue()) Ω(status.Cause).Should(Equal(interrupt_handler.InterruptCauseSignal)) Ω(status.Message()).Should(Equal("Interrupted by User")) Ω(status.Level).Should(Equal(interrupt_handler.InterruptLevelBailOut)) Ω(status.ShouldIncludeProgressReport()).Should(BeTrue()) }) Specify("when interrupted again and again, it no longer interrupts once the last level has been reached", func() { trigger() Consistently(status.Channel).ShouldNot(BeClosed()) trigger() Consistently(status.Channel).ShouldNot(BeClosed()) status = interruptHandler.Status() Ω(status.Interrupted()).Should(BeTrue()) Ω(status.Cause).Should(Equal(interrupt_handler.InterruptCauseSignal)) Ω(status.Message()).Should(Equal("Interrupted by User")) Ω(status.Level).Should(Equal(interrupt_handler.InterruptLevelBailOut)) Ω(status.ShouldIncludeProgressReport()).Should(BeTrue()) }) }) }) Describe("Interrupting when another Ginkgo process has aborted", func() { var client parallel_support.Client BeforeEach(func() { _, client, _ = SetUpServerAndClient(2) interruptHandler = interrupt_handler.NewInterruptHandler(client, syscall.SIGUSR2) DeferCleanup(interruptHandler.Stop) }) It("interrupts when the server is told to abort", func() { status := interruptHandler.Status() Consistently(status.Channel).ShouldNot(BeClosed()) client.PostAbort() Eventually(status.Channel).Should(BeClosed()) }) It("notes the correct cause and returns an interrupt message that does not include a progress report", func() { status := interruptHandler.Status() Ω(status.Interrupted()).Should(BeFalse()) client.PostAbort() Eventually(status.Channel).Should(BeClosed()) status = interruptHandler.Status() Ω(status.Cause).Should(Equal(interrupt_handler.InterruptCauseAbortByOtherProcess)) Ω(status.Level).Should(Equal(interrupt_handler.InterruptLevelCleanupAndReport)) Ω(status.Interrupted()).Should(BeTrue()) Ω(status.Message()).Should(Equal("Interrupted by Other Ginkgo Process")) Ω(status.ShouldIncludeProgressReport()).Should(BeFalse()) }) It("does not retrigger on subsequent aborts", func() { status := interruptHandler.Status() Ω(status.Interrupted()).Should(BeFalse()) client.PostAbort() Eventually(status.Channel).Should(BeClosed()) status = interruptHandler.Status() client.PostAbort() Consistently(status.Channel, interrupt_handler.ABORT_POLLING_INTERVAL+100*time.Millisecond).ShouldNot(BeClosed()) status = interruptHandler.Status() Ω(status.Cause).Should(Equal(interrupt_handler.InterruptCauseAbortByOtherProcess)) Ω(status.Level).Should(Equal(interrupt_handler.InterruptLevelCleanupAndReport)) }) It("does not trigger if the suite has already been interrupted", func() { status := interruptHandler.Status() Ω(status.Interrupted()).Should(BeFalse()) trigger() Eventually(status.Channel).Should(BeClosed()) status = interruptHandler.Status() Ω(status.Cause).Should(Equal(interrupt_handler.InterruptCauseSignal)) Ω(status.Level).Should(Equal(interrupt_handler.InterruptLevelCleanupAndReport)) status = interruptHandler.Status() client.PostAbort() Consistently(status.Channel, interrupt_handler.ABORT_POLLING_INTERVAL+100*time.Millisecond).ShouldNot(BeClosed()) status = interruptHandler.Status() Ω(status.Cause).Should(Equal(interrupt_handler.InterruptCauseSignal)) Ω(status.Level).Should(Equal(interrupt_handler.InterruptLevelCleanupAndReport)) }) It("doesn't just rely on the ABORT_POLLING_INTERVAL timer to report that the interrupt has happened", func() { client.PostAbort() Ω(interruptHandler.Status().Cause).Should(Equal(interrupt_handler.InterruptCauseAbortByOtherProcess)) }, MustPassRepeatedly(10)) }) }) golang-github-onsi-ginkgo-v2-2.22.0/internal/interrupt_handler/interrupthandler_suite_test.go000066400000000000000000000005431472321612100326320ustar00rootroot00000000000000package interrupt_handler_test import ( "testing" "time" . "github.com/onsi/ginkgo/v2" "github.com/onsi/ginkgo/v2/internal/interrupt_handler" . "github.com/onsi/gomega" ) func TestInterrupthandler(t *testing.T) { interrupt_handler.ABORT_POLLING_INTERVAL = 50 * time.Millisecond RegisterFailHandler(Fail) RunSpecs(t, "Interrupthandler Suite") } golang-github-onsi-ginkgo-v2-2.22.0/internal/interrupt_handler/sigquit_swallower_unix.go000066400000000000000000000004671472321612100316240ustar00rootroot00000000000000//go:build freebsd || openbsd || netbsd || dragonfly || darwin || linux || solaris // +build freebsd openbsd netbsd dragonfly darwin linux solaris package interrupt_handler import ( "os" "os/signal" "syscall" ) func SwallowSigQuit() { c := make(chan os.Signal, 1024) signal.Notify(c, syscall.SIGQUIT) } golang-github-onsi-ginkgo-v2-2.22.0/internal/interrupt_handler/sigquit_swallower_windows.go000066400000000000000000000001431472321612100323220ustar00rootroot00000000000000//go:build windows // +build windows package interrupt_handler func SwallowSigQuit() { //noop } golang-github-onsi-ginkgo-v2-2.22.0/internal/node.go000066400000000000000000000640621472321612100221720ustar00rootroot00000000000000package internal import ( "context" "fmt" "reflect" "sort" "sync" "time" "github.com/onsi/ginkgo/v2/types" ) var _global_node_id_counter = uint(0) var _global_id_mutex = &sync.Mutex{} func UniqueNodeID() uint { // There's a reace in the internal integration tests if we don't make // accessing _global_node_id_counter safe across goroutines. _global_id_mutex.Lock() defer _global_id_mutex.Unlock() _global_node_id_counter += 1 return _global_node_id_counter } type Node struct { ID uint NodeType types.NodeType Text string Body func(SpecContext) CodeLocation types.CodeLocation NestingLevel int HasContext bool SynchronizedBeforeSuiteProc1Body func(SpecContext) []byte SynchronizedBeforeSuiteProc1BodyHasContext bool SynchronizedBeforeSuiteAllProcsBody func(SpecContext, []byte) SynchronizedBeforeSuiteAllProcsBodyHasContext bool SynchronizedAfterSuiteAllProcsBody func(SpecContext) SynchronizedAfterSuiteAllProcsBodyHasContext bool SynchronizedAfterSuiteProc1Body func(SpecContext) SynchronizedAfterSuiteProc1BodyHasContext bool ReportEachBody func(SpecContext, types.SpecReport) ReportSuiteBody func(SpecContext, types.Report) MarkedFocus bool MarkedPending bool MarkedSerial bool MarkedOrdered bool MarkedContinueOnFailure bool MarkedOncePerOrdered bool FlakeAttempts int MustPassRepeatedly int Labels Labels PollProgressAfter time.Duration PollProgressInterval time.Duration NodeTimeout time.Duration SpecTimeout time.Duration GracePeriod time.Duration NodeIDWhereCleanupWasGenerated uint } // Decoration Types type focusType bool type pendingType bool type serialType bool type orderedType bool type continueOnFailureType bool type honorsOrderedType bool type suppressProgressReporting bool const Focus = focusType(true) const Pending = pendingType(true) const Serial = serialType(true) const Ordered = orderedType(true) const ContinueOnFailure = continueOnFailureType(true) const OncePerOrdered = honorsOrderedType(true) const SuppressProgressReporting = suppressProgressReporting(true) type FlakeAttempts uint type MustPassRepeatedly 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 type NodeTimeout time.Duration type SpecTimeout time.Duration type GracePeriod time.Duration func (l Labels) MatchesLabelFilter(query string) bool { return types.MustParseLabelFilter(query)(l) } func UnionOfLabels(labels ...Labels) Labels { out := Labels{} seen := map[string]bool{} for _, labelSet := range labels { for _, label := range labelSet { if !seen[label] { seen[label] = true out = append(out, label) } } } return out } func PartitionDecorations(args ...interface{}) ([]interface{}, []interface{}) { decorations := []interface{}{} remainingArgs := []interface{}{} for _, arg := range args { if isDecoration(arg) { decorations = append(decorations, arg) } else { remainingArgs = append(remainingArgs, arg) } } return decorations, remainingArgs } func isDecoration(arg interface{}) bool { switch t := reflect.TypeOf(arg); { case t == nil: return false case t == reflect.TypeOf(Offset(0)): return true case t == reflect.TypeOf(types.CodeLocation{}): return true case t == reflect.TypeOf(Focus): return true case t == reflect.TypeOf(Pending): return true case t == reflect.TypeOf(Serial): return true case t == reflect.TypeOf(Ordered): return true case t == reflect.TypeOf(ContinueOnFailure): return true case t == reflect.TypeOf(OncePerOrdered): return true case t == reflect.TypeOf(SuppressProgressReporting): return true case t == reflect.TypeOf(FlakeAttempts(0)): return true case t == reflect.TypeOf(MustPassRepeatedly(0)): 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 == reflect.TypeOf(NodeTimeout(0)): return true case t == reflect.TypeOf(SpecTimeout(0)): return true case t == reflect.TypeOf(GracePeriod(0)): return true case t.Kind() == reflect.Slice && isSliceOfDecorations(arg): return true default: return false } } func isSliceOfDecorations(slice interface{}) bool { vSlice := reflect.ValueOf(slice) if vSlice.Len() == 0 { return false } for i := 0; i < vSlice.Len(); i++ { if !isDecoration(vSlice.Index(i).Interface()) { return false } } return true } var contextType = reflect.TypeOf(new(context.Context)).Elem() var specContextType = reflect.TypeOf(new(SpecContext)).Elem() 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, PollProgressAfter: -1, PollProgressInterval: -1, GracePeriod: -1, } errors := []error{} appendError := func(err error) { if err != nil { errors = append(errors, err) } } args = unrollInterfaceSlice(args) remainingArgs := []interface{}{} // First get the CodeLocation up-to-date for _, arg := range args { switch v := arg.(type) { case Offset: node.CodeLocation = types.NewCodeLocation(baseOffset + int(v)) case types.CodeLocation: node.CodeLocation = v default: remainingArgs = append(remainingArgs, arg) } } labelsSeen := map[string]bool{} trackedFunctionError := false args = remainingArgs remainingArgs = []interface{}{} // now process the rest of the args for _, arg := range args { switch t := reflect.TypeOf(arg); { case t == reflect.TypeOf(float64(0)): break // ignore deprecated timeouts case t == reflect.TypeOf(Focus): node.MarkedFocus = bool(arg.(focusType)) if !nodeType.Is(types.NodeTypesForContainerAndIt) { appendError(types.GinkgoErrors.InvalidDecoratorForNodeType(node.CodeLocation, nodeType, "Focus")) } case t == reflect.TypeOf(Pending): node.MarkedPending = bool(arg.(pendingType)) if !nodeType.Is(types.NodeTypesForContainerAndIt) { appendError(types.GinkgoErrors.InvalidDecoratorForNodeType(node.CodeLocation, nodeType, "Pending")) } case t == reflect.TypeOf(Serial): node.MarkedSerial = bool(arg.(serialType)) if !labelsSeen["Serial"] { node.Labels = append(node.Labels, "Serial") } if !nodeType.Is(types.NodeTypesForContainerAndIt) { appendError(types.GinkgoErrors.InvalidDecoratorForNodeType(node.CodeLocation, nodeType, "Serial")) } case t == reflect.TypeOf(Ordered): node.MarkedOrdered = bool(arg.(orderedType)) if !nodeType.Is(types.NodeTypeContainer) { appendError(types.GinkgoErrors.InvalidDecoratorForNodeType(node.CodeLocation, nodeType, "Ordered")) } case t == reflect.TypeOf(ContinueOnFailure): node.MarkedContinueOnFailure = bool(arg.(continueOnFailureType)) if !nodeType.Is(types.NodeTypeContainer) { appendError(types.GinkgoErrors.InvalidDecoratorForNodeType(node.CodeLocation, nodeType, "ContinueOnFailure")) } case t == reflect.TypeOf(OncePerOrdered): node.MarkedOncePerOrdered = bool(arg.(honorsOrderedType)) if !nodeType.Is(types.NodeTypeBeforeEach | types.NodeTypeJustBeforeEach | types.NodeTypeAfterEach | types.NodeTypeJustAfterEach) { appendError(types.GinkgoErrors.InvalidDecoratorForNodeType(node.CodeLocation, nodeType, "OncePerOrdered")) } case t == reflect.TypeOf(SuppressProgressReporting): deprecationTracker.TrackDeprecation(types.Deprecations.SuppressProgressReporting()) case t == reflect.TypeOf(FlakeAttempts(0)): node.FlakeAttempts = int(arg.(FlakeAttempts)) if !nodeType.Is(types.NodeTypesForContainerAndIt) { appendError(types.GinkgoErrors.InvalidDecoratorForNodeType(node.CodeLocation, nodeType, "FlakeAttempts")) } case t == reflect.TypeOf(MustPassRepeatedly(0)): node.MustPassRepeatedly = int(arg.(MustPassRepeatedly)) if !nodeType.Is(types.NodeTypesForContainerAndIt) { appendError(types.GinkgoErrors.InvalidDecoratorForNodeType(node.CodeLocation, nodeType, "MustPassRepeatedly")) } 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(NodeTimeout(0)): node.NodeTimeout = time.Duration(arg.(NodeTimeout)) if nodeType.Is(types.NodeTypeContainer) { appendError(types.GinkgoErrors.InvalidDecoratorForNodeType(node.CodeLocation, nodeType, "NodeTimeout")) } case t == reflect.TypeOf(SpecTimeout(0)): node.SpecTimeout = time.Duration(arg.(SpecTimeout)) if !nodeType.Is(types.NodeTypeIt) { appendError(types.GinkgoErrors.InvalidDecoratorForNodeType(node.CodeLocation, nodeType, "SpecTimeout")) } case t == reflect.TypeOf(GracePeriod(0)): node.GracePeriod = time.Duration(arg.(GracePeriod)) if nodeType.Is(types.NodeTypeContainer) { appendError(types.GinkgoErrors.InvalidDecoratorForNodeType(node.CodeLocation, nodeType, "GracePeriod")) } case t == reflect.TypeOf(Labels{}): if !nodeType.Is(types.NodeTypesForContainerAndIt) { appendError(types.GinkgoErrors.InvalidDecoratorForNodeType(node.CodeLocation, nodeType, "Label")) } for _, label := range arg.(Labels) { if !labelsSeen[label] { labelsSeen[label] = true label, err := types.ValidateAndCleanupLabel(label, node.CodeLocation) node.Labels = append(node.Labels, label) appendError(err) } } case t.Kind() == reflect.Func: if nodeType.Is(types.NodeTypeContainer) { if node.Body != nil { appendError(types.GinkgoErrors.MultipleBodyFunctions(node.CodeLocation, nodeType)) trackedFunctionError = true break } if t.NumOut() > 0 || t.NumIn() > 0 { appendError(types.GinkgoErrors.InvalidBodyTypeForContainer(t, node.CodeLocation, nodeType)) trackedFunctionError = true break } body := arg.(func()) node.Body = func(SpecContext) { body() } } else if nodeType.Is(types.NodeTypeReportBeforeEach | types.NodeTypeReportAfterEach) { if node.ReportEachBody == nil { if fn, ok := arg.(func(types.SpecReport)); ok { node.ReportEachBody = func(_ SpecContext, r types.SpecReport) { fn(r) } } else { node.ReportEachBody = arg.(func(SpecContext, types.SpecReport)) node.HasContext = true } } else { appendError(types.GinkgoErrors.MultipleBodyFunctions(node.CodeLocation, nodeType)) trackedFunctionError = true break } } else if nodeType.Is(types.NodeTypeReportBeforeSuite | types.NodeTypeReportAfterSuite) { if node.ReportSuiteBody == nil { if fn, ok := arg.(func(types.Report)); ok { node.ReportSuiteBody = func(_ SpecContext, r types.Report) { fn(r) } } else { node.ReportSuiteBody = arg.(func(SpecContext, types.Report)) node.HasContext = true } } else { appendError(types.GinkgoErrors.MultipleBodyFunctions(node.CodeLocation, nodeType)) trackedFunctionError = true break } } else if nodeType.Is(types.NodeTypeSynchronizedBeforeSuite) { if node.SynchronizedBeforeSuiteProc1Body != nil && node.SynchronizedBeforeSuiteAllProcsBody != nil { appendError(types.GinkgoErrors.MultipleBodyFunctions(node.CodeLocation, nodeType)) trackedFunctionError = true break } if node.SynchronizedBeforeSuiteProc1Body == nil { body, hasContext := extractSynchronizedBeforeSuiteProc1Body(arg) if body == nil { appendError(types.GinkgoErrors.InvalidBodyTypeForSynchronizedBeforeSuiteProc1(t, node.CodeLocation)) trackedFunctionError = true } node.SynchronizedBeforeSuiteProc1Body, node.SynchronizedBeforeSuiteProc1BodyHasContext = body, hasContext } else if node.SynchronizedBeforeSuiteAllProcsBody == nil { body, hasContext := extractSynchronizedBeforeSuiteAllProcsBody(arg) if body == nil { appendError(types.GinkgoErrors.InvalidBodyTypeForSynchronizedBeforeSuiteAllProcs(t, node.CodeLocation)) trackedFunctionError = true } node.SynchronizedBeforeSuiteAllProcsBody, node.SynchronizedBeforeSuiteAllProcsBodyHasContext = body, hasContext } } else if nodeType.Is(types.NodeTypeSynchronizedAfterSuite) { if node.SynchronizedAfterSuiteAllProcsBody != nil && node.SynchronizedAfterSuiteProc1Body != nil { appendError(types.GinkgoErrors.MultipleBodyFunctions(node.CodeLocation, nodeType)) trackedFunctionError = true break } body, hasContext := extractBodyFunction(deprecationTracker, node.CodeLocation, arg) if body == nil { appendError(types.GinkgoErrors.InvalidBodyType(t, node.CodeLocation, nodeType)) trackedFunctionError = true break } if node.SynchronizedAfterSuiteAllProcsBody == nil { node.SynchronizedAfterSuiteAllProcsBody, node.SynchronizedAfterSuiteAllProcsBodyHasContext = body, hasContext } else if node.SynchronizedAfterSuiteProc1Body == nil { node.SynchronizedAfterSuiteProc1Body, node.SynchronizedAfterSuiteProc1BodyHasContext = body, hasContext } } else { if node.Body != nil { appendError(types.GinkgoErrors.MultipleBodyFunctions(node.CodeLocation, nodeType)) trackedFunctionError = true break } node.Body, node.HasContext = extractBodyFunction(deprecationTracker, node.CodeLocation, arg) if node.Body == nil { appendError(types.GinkgoErrors.InvalidBodyType(t, node.CodeLocation, nodeType)) trackedFunctionError = true break } } default: remainingArgs = append(remainingArgs, arg) } } // validations if node.MarkedPending && node.MarkedFocus { appendError(types.GinkgoErrors.InvalidDeclarationOfFocusedAndPending(node.CodeLocation, nodeType)) } if node.MarkedContinueOnFailure && !node.MarkedOrdered { appendError(types.GinkgoErrors.InvalidContinueOnFailureDecoration(node.CodeLocation)) } hasContext := node.HasContext || node.SynchronizedAfterSuiteProc1BodyHasContext || node.SynchronizedAfterSuiteAllProcsBodyHasContext || node.SynchronizedBeforeSuiteProc1BodyHasContext || node.SynchronizedBeforeSuiteAllProcsBodyHasContext if !hasContext && (node.NodeTimeout > 0 || node.SpecTimeout > 0 || node.GracePeriod > 0) && len(errors) == 0 { appendError(types.GinkgoErrors.InvalidTimeoutOrGracePeriodForNonContextNode(node.CodeLocation, nodeType)) } if !node.NodeType.Is(types.NodeTypeReportBeforeEach|types.NodeTypeReportAfterEach|types.NodeTypeSynchronizedBeforeSuite|types.NodeTypeSynchronizedAfterSuite|types.NodeTypeReportBeforeSuite|types.NodeTypeReportAfterSuite) && node.Body == nil && !node.MarkedPending && !trackedFunctionError { appendError(types.GinkgoErrors.MissingBodyFunction(node.CodeLocation, nodeType)) } if node.NodeType.Is(types.NodeTypeSynchronizedBeforeSuite) && !trackedFunctionError && (node.SynchronizedBeforeSuiteProc1Body == nil || node.SynchronizedBeforeSuiteAllProcsBody == nil) { appendError(types.GinkgoErrors.MissingBodyFunction(node.CodeLocation, nodeType)) } if node.NodeType.Is(types.NodeTypeSynchronizedAfterSuite) && !trackedFunctionError && (node.SynchronizedAfterSuiteProc1Body == nil || node.SynchronizedAfterSuiteAllProcsBody == nil) { appendError(types.GinkgoErrors.MissingBodyFunction(node.CodeLocation, nodeType)) } for _, arg := range remainingArgs { appendError(types.GinkgoErrors.UnknownDecorator(node.CodeLocation, nodeType, arg)) } if node.FlakeAttempts > 0 && node.MustPassRepeatedly > 0 { appendError(types.GinkgoErrors.InvalidDeclarationOfFlakeAttemptsAndMustPassRepeatedly(node.CodeLocation, nodeType)) } if len(errors) > 0 { return Node{}, errors } return node, errors } var doneType = reflect.TypeOf(make(Done)) func extractBodyFunction(deprecationTracker *types.DeprecationTracker, cl types.CodeLocation, arg interface{}) (func(SpecContext), bool) { t := reflect.TypeOf(arg) if t.NumOut() > 0 || t.NumIn() > 1 { return nil, false } if t.NumIn() == 1 { if t.In(0) == doneType { deprecationTracker.TrackDeprecation(types.Deprecations.Async(), cl) deprecatedAsyncBody := arg.(func(Done)) return func(SpecContext) { deprecatedAsyncBody(make(Done)) }, false } else if t.In(0).Implements(specContextType) { return arg.(func(SpecContext)), true } else if t.In(0).Implements(contextType) { body := arg.(func(context.Context)) return func(c SpecContext) { body(c) }, true } return nil, false } body := arg.(func()) return func(SpecContext) { body() }, false } var byteType = reflect.TypeOf([]byte{}) func extractSynchronizedBeforeSuiteProc1Body(arg interface{}) (func(SpecContext) []byte, bool) { t := reflect.TypeOf(arg) v := reflect.ValueOf(arg) if t.NumOut() > 1 || t.NumIn() > 1 { return nil, false } else if t.NumOut() == 1 && t.Out(0) != byteType { return nil, false } else if t.NumIn() == 1 && !t.In(0).Implements(contextType) { return nil, false } hasContext := t.NumIn() == 1 return func(c SpecContext) []byte { var out []reflect.Value if hasContext { out = v.Call([]reflect.Value{reflect.ValueOf(c)}) } else { out = v.Call([]reflect.Value{}) } if len(out) == 1 { return (out[0].Interface()).([]byte) } else { return []byte{} } }, hasContext } func extractSynchronizedBeforeSuiteAllProcsBody(arg interface{}) (func(SpecContext, []byte), bool) { t := reflect.TypeOf(arg) v := reflect.ValueOf(arg) hasContext, hasByte := false, false if t.NumOut() > 0 || t.NumIn() > 2 { return nil, false } else if t.NumIn() == 2 && t.In(0).Implements(contextType) && t.In(1) == byteType { hasContext, hasByte = true, true } else if t.NumIn() == 1 && t.In(0).Implements(contextType) { hasContext = true } else if t.NumIn() == 1 && t.In(0) == byteType { hasByte = true } else if t.NumIn() != 0 { return nil, false } return func(c SpecContext, b []byte) { in := []reflect.Value{} if hasContext { in = append(in, reflect.ValueOf(c)) } if hasByte { in = append(in, reflect.ValueOf(b)) } v.Call(in) }, hasContext } var errInterface = reflect.TypeOf((*error)(nil)).Elem() func NewCleanupNode(deprecationTracker *types.DeprecationTracker, fail func(string, types.CodeLocation), args ...interface{}) (Node, []error) { decorations, remainingArgs := PartitionDecorations(args...) baseOffset := 2 cl := types.NewCodeLocation(baseOffset) finalArgs := []interface{}{} for _, arg := range decorations { switch t := reflect.TypeOf(arg); { case t == reflect.TypeOf(Offset(0)): cl = types.NewCodeLocation(baseOffset + int(arg.(Offset))) case t == reflect.TypeOf(types.CodeLocation{}): cl = arg.(types.CodeLocation) default: finalArgs = append(finalArgs, arg) } } finalArgs = append(finalArgs, cl) if len(remainingArgs) == 0 { return Node{}, []error{types.GinkgoErrors.DeferCleanupInvalidFunction(cl)} } callback := reflect.ValueOf(remainingArgs[0]) if !(callback.Kind() == reflect.Func) { return Node{}, []error{types.GinkgoErrors.DeferCleanupInvalidFunction(cl)} } callArgs := []reflect.Value{} for _, arg := range remainingArgs[1:] { callArgs = append(callArgs, reflect.ValueOf(arg)) } hasContext := false t := callback.Type() if t.NumIn() > 0 { if t.In(0).Implements(specContextType) { hasContext = true } else if t.In(0).Implements(contextType) && (len(callArgs) == 0 || !callArgs[0].Type().Implements(contextType)) { hasContext = true } } handleFailure := func(out []reflect.Value) { if len(out) == 0 { return } last := out[len(out)-1] if last.Type().Implements(errInterface) && !last.IsNil() { fail(fmt.Sprintf("DeferCleanup callback returned error: %v", last), cl) } } if hasContext { finalArgs = append(finalArgs, func(c SpecContext) { out := callback.Call(append([]reflect.Value{reflect.ValueOf(c)}, callArgs...)) handleFailure(out) }) } else { finalArgs = append(finalArgs, func() { out := callback.Call(callArgs) handleFailure(out) }) } return NewNode(deprecationTracker, types.NodeTypeCleanupInvalid, "", finalArgs...) } func (n Node) IsZero() bool { return n.ID == 0 } /* Nodes */ type Nodes []Node func (n Nodes) Clone() Nodes { nodes := make(Nodes, len(n)) copy(nodes, n) return nodes } func (n Nodes) CopyAppend(nodes ...Node) Nodes { numN := len(n) out := make(Nodes, numN+len(nodes)) copy(out, n) for j, node := range nodes { out[numN+j] = node } return out } func (n Nodes) SplitAround(pivot Node) (Nodes, Nodes) { pivotIdx := len(n) for i := range n { if n[i].ID == pivot.ID { pivotIdx = i break } } left := n[:pivotIdx] right := Nodes{} if pivotIdx+1 < len(n) { right = n[pivotIdx+1:] } return left, right } func (n Nodes) FirstNodeWithType(nodeTypes types.NodeType) Node { for i := range n { if n[i].NodeType.Is(nodeTypes) { return n[i] } } return Node{} } func (n Nodes) WithType(nodeTypes types.NodeType) Nodes { count := 0 for i := range n { if n[i].NodeType.Is(nodeTypes) { count++ } } out, j := make(Nodes, count), 0 for i := range n { if n[i].NodeType.Is(nodeTypes) { out[j] = n[i] j++ } } return out } func (n Nodes) WithoutType(nodeTypes types.NodeType) Nodes { count := 0 for i := range n { if !n[i].NodeType.Is(nodeTypes) { count++ } } out, j := make(Nodes, count), 0 for i := range n { if !n[i].NodeType.Is(nodeTypes) { out[j] = n[i] j++ } } return out } func (n Nodes) WithoutNode(nodeToExclude Node) Nodes { idxToExclude := len(n) for i := range n { if n[i].ID == nodeToExclude.ID { idxToExclude = i break } } if idxToExclude == len(n) { return n } out, j := make(Nodes, len(n)-1), 0 for i := range n { if i == idxToExclude { continue } out[j] = n[i] j++ } return out } func (n Nodes) Filter(filter func(Node) bool) Nodes { trufa, count := make([]bool, len(n)), 0 for i := range n { if filter(n[i]) { trufa[i] = true count += 1 } } out, j := make(Nodes, count), 0 for i := range n { if trufa[i] { out[j] = n[i] j++ } } return out } func (n Nodes) FirstSatisfying(filter func(Node) bool) Node { for i := range n { if filter(n[i]) { return n[i] } } return Node{} } func (n Nodes) WithinNestingLevel(deepestNestingLevel int) Nodes { count := 0 for i := range n { if n[i].NestingLevel <= deepestNestingLevel { count++ } } out, j := make(Nodes, count), 0 for i := range n { if n[i].NestingLevel <= deepestNestingLevel { out[j] = n[i] j++ } } return out } func (n Nodes) SortedByDescendingNestingLevel() Nodes { out := make(Nodes, len(n)) copy(out, n) sort.SliceStable(out, func(i int, j int) bool { return out[i].NestingLevel > out[j].NestingLevel }) return out } func (n Nodes) SortedByAscendingNestingLevel() Nodes { out := make(Nodes, len(n)) copy(out, n) sort.SliceStable(out, func(i int, j int) bool { return out[i].NestingLevel < out[j].NestingLevel }) return out } func (n Nodes) FirstWithNestingLevel(level int) Node { for i := range n { if n[i].NestingLevel == level { return n[i] } } return Node{} } func (n Nodes) Reverse() Nodes { out := make(Nodes, len(n)) for i := range n { out[len(n)-1-i] = n[i] } return out } func (n Nodes) Texts() []string { out := make([]string, len(n)) for i := range n { out[i] = n[i].Text } return out } func (n Nodes) Labels() [][]string { out := make([][]string, len(n)) for i := range n { if n[i].Labels == nil { out[i] = []string{} } else { out[i] = []string(n[i].Labels) } } return out } func (n Nodes) UnionOfLabels() []string { out := []string{} seen := map[string]bool{} for i := range n { for _, label := range n[i].Labels { if !seen[label] { seen[label] = true out = append(out, label) } } } return out } func (n Nodes) CodeLocations() []types.CodeLocation { out := make([]types.CodeLocation, len(n)) for i := range n { out[i] = n[i].CodeLocation } return out } func (n Nodes) BestTextFor(node Node) string { if node.Text != "" { return node.Text } parentNestingLevel := node.NestingLevel - 1 for i := range n { if n[i].Text != "" && n[i].NestingLevel == parentNestingLevel { return n[i].Text } } return "" } func (n Nodes) ContainsNodeID(id uint) bool { for i := range n { if n[i].ID == id { return true } } return false } func (n Nodes) HasNodeMarkedPending() bool { for i := range n { if n[i].MarkedPending { return true } } return false } func (n Nodes) HasNodeMarkedFocus() bool { for i := range n { if n[i].MarkedFocus { return true } } return false } func (n Nodes) HasNodeMarkedSerial() bool { for i := range n { if n[i].MarkedSerial { return true } } return false } func (n Nodes) FirstNodeMarkedOrdered() Node { for i := range n { if n[i].MarkedOrdered { return n[i] } } return Node{} } func (n Nodes) IndexOfFirstNodeMarkedOrdered() int { for i := range n { if n[i].MarkedOrdered { return i } } return -1 } func (n Nodes) GetMaxFlakeAttempts() int { maxFlakeAttempts := 0 for i := range n { if n[i].FlakeAttempts > 0 { maxFlakeAttempts = n[i].FlakeAttempts } } return maxFlakeAttempts } func (n Nodes) GetMaxMustPassRepeatedly() int { maxMustPassRepeatedly := 0 for i := range n { if n[i].MustPassRepeatedly > 0 { maxMustPassRepeatedly = n[i].MustPassRepeatedly } } return maxMustPassRepeatedly } func unrollInterfaceSlice(args interface{}) []interface{} { v := reflect.ValueOf(args) if v.Kind() != reflect.Slice { return []interface{}{args} } out := []interface{}{} for i := 0; i < v.Len(); i++ { el := reflect.ValueOf(v.Index(i).Interface()) if el.Kind() == reflect.Slice && el.Type() != reflect.TypeOf(Labels{}) { out = append(out, unrollInterfaceSlice(el.Interface())...) } else { out = append(out, v.Index(i).Interface()) } } return out } golang-github-onsi-ginkgo-v2-2.22.0/internal/node_test.go000066400000000000000000001770131472321612100232320ustar00rootroot00000000000000package internal_test import ( "context" "fmt" "os" "reflect" "time" . "github.com/onsi/ginkgo/v2" "github.com/onsi/ginkgo/v2/types" . "github.com/onsi/gomega" "github.com/onsi/gomega/gmeasure" "github.com/onsi/ginkgo/v2/internal" ) var _ = Describe("UniqueNodeID", func() { It("returns a unique id every time it's called", func() { Ω(internal.UniqueNodeID()).ShouldNot(Equal(internal.UniqueNodeID())) }) }) var _ = Describe("Partitioning Decorations", func() { It("separates out decorations and non-decorations", func() { type Foo struct { A int } decorations, remaining := internal.PartitionDecorations( Offset(3), Foo{3}, types.NewCustomCodeLocation("hey there"), "hey there", Focus, 2.0, Pending, Serial, Ordered, ContinueOnFailure, SuppressProgressReporting, NodeTimeout(time.Second), GracePeriod(time.Second), SpecTimeout(time.Second), nil, 1, []interface{}{Focus, Pending, []interface{}{Offset(2), Serial, FlakeAttempts(2)}, Ordered, Label("a", "b", "c"), NodeTimeout(time.Second)}, []interface{}{1, 2, 3.1, nil}, PollProgressInterval(time.Second), PollProgressAfter(time.Second), []string{"a", "b", "c"}, Label("A", "B", "C"), Label("D"), []interface{}{}, FlakeAttempts(1), MustPassRepeatedly(1), true, OncePerOrdered, ) Ω(decorations).Should(Equal([]interface{}{ Offset(3), types.NewCustomCodeLocation("hey there"), Focus, Pending, Serial, Ordered, ContinueOnFailure, SuppressProgressReporting, NodeTimeout(time.Second), GracePeriod(time.Second), SpecTimeout(time.Second), []interface{}{Focus, Pending, []interface{}{Offset(2), Serial, FlakeAttempts(2)}, Ordered, Label("a", "b", "c"), NodeTimeout(time.Second)}, PollProgressInterval(time.Second), PollProgressAfter(time.Second), Label("A", "B", "C"), Label("D"), FlakeAttempts(1), MustPassRepeatedly(1), OncePerOrdered, })) Ω(remaining).Should(Equal([]interface{}{ Foo{3}, "hey there", 2.0, nil, 1, []interface{}{1, 2, 3.1, nil}, []string{"a", "b", "c"}, []interface{}{}, true, })) }) }) var _ = Describe("Combining Labels", func() { It("can combine labels and produce the unique union", func() { Ω(internal.UnionOfLabels(Label("a", "b", "c"), Label("b", "c", "d"), Label("e", "a", "f"))).Should(Equal(Label("a", "b", "c", "d", "e", "f"))) }) }) var _ = Describe("Constructing nodes", func() { var dt *types.DeprecationTracker var didRun bool var body func() BeforeEach(func() { dt = types.NewDeprecationTracker() didRun = false body = func() { didRun = true } }) ExpectAllWell := func(errors []error) { ExpectWithOffset(1, errors).Should(BeEmpty()) ExpectWithOffset(1, dt.DidTrackDeprecations()).Should(BeFalse()) } Describe("happy path", func() { It("creates a node with a non-zero id", func() { node, errors := internal.NewNode(dt, ntIt, "text", body, cl, Focus, Label("A", "B", "C")) Ω(node.ID).Should(BeNumerically(">", 0)) Ω(node.NodeType).Should(Equal(ntIt)) Ω(node.Text).Should(Equal("text")) node.Body(nil) Ω(didRun).Should(BeTrue()) Ω(node.CodeLocation).Should(Equal(cl)) Ω(node.MarkedFocus).Should(BeTrue()) Ω(node.MarkedPending).Should(BeFalse()) Ω(node.NestingLevel).Should(Equal(-1)) Ω(node.Labels).Should(Equal(Labels{"A", "B", "C"})) Ω(node.HasContext).Should(BeFalse()) ExpectAllWell(errors) }) }) Describe("Building ReportBeforeEach nodes", func() { It("returns a correctly configured node", func() { var didRun bool body := func(types.SpecReport) { didRun = true } node, errors := internal.NewNode(dt, types.NodeTypeReportBeforeEach, "", body, cl) Ω(errors).Should(BeEmpty()) Ω(node.ID).Should(BeNumerically(">", 0)) Ω(node.NodeType).Should(Equal(types.NodeTypeReportBeforeEach)) node.ReportEachBody(internal.NewSpecContext(nil), types.SpecReport{}) Ω(didRun).Should(BeTrue()) Ω(node.Body).Should(BeNil()) Ω(node.CodeLocation).Should(Equal(cl)) Ω(node.NestingLevel).Should(Equal(-1)) }) }) Describe("Building ReportAfterEach nodes", func() { It("returns a correctly configured node", func() { var didRun bool body := func(types.SpecReport) { didRun = true } node, errors := internal.NewNode(dt, types.NodeTypeReportAfterEach, "", body, cl) Ω(errors).Should(BeEmpty()) Ω(node.ID).Should(BeNumerically(">", 0)) Ω(node.NodeType).Should(Equal(types.NodeTypeReportAfterEach)) node.ReportEachBody(internal.NewSpecContext(nil), types.SpecReport{}) Ω(didRun).Should(BeTrue()) Ω(node.Body).Should(BeNil()) Ω(node.CodeLocation).Should(Equal(cl)) Ω(node.NestingLevel).Should(Equal(-1)) }) }) Describe("Assigning CodeLocation", func() { Context("with nothing explicitly specified ", func() { It("assumes a base-offset of 2", func() { cl := types.NewCodeLocation(1) node, errors := internal.NewNode(dt, ntIt, "text", body) Ω(node.CodeLocation.FileName).Should(Equal(cl.FileName)) ExpectAllWell(errors) }) }) Context("specifying code locations", func() { It("uses the last passed-in code location", func() { cl2 := types.NewCustomCodeLocation("hi") node, errors := internal.NewNode(dt, ntIt, "text", body, cl, cl2) Ω(node.CodeLocation).Should(Equal(cl2)) ExpectAllWell(errors) }) }) Context("specifying offets", func() { It("takes the offset and adds it to the base-offset of 2 to compute the code location", func() { cl := types.NewCodeLocation(2) cl2 := types.NewCustomCodeLocation("hi") node, errors := internal.NewNode(dt, ntIt, "text", body, cl2, Offset(1)) // note that Offset overrides cl2 Ω(node.CodeLocation.FileName).Should(Equal(cl.FileName)) ExpectAllWell(errors) }) }) }) Describe("ignoring deprecated timeouts", func() { It("ignores any float64s", func() { node, errors := internal.NewNode(dt, ntIt, "text", body, 3.141, 2.71) node.Body(nil) Ω(didRun).Should(BeTrue()) ExpectAllWell(errors) }) }) Describe("the Focus and Pending decorations", func() { It("the node is neither Focused nor Pending by default", func() { node, errors := internal.NewNode(dt, ntIt, "text", body) Ω(node.MarkedFocus).Should(BeFalse()) Ω(node.MarkedPending).Should(BeFalse()) ExpectAllWell(errors) }) It("marks the node as focused", func() { node, errors := internal.NewNode(dt, ntIt, "text", body, Focus) Ω(node.MarkedFocus).Should(BeTrue()) Ω(node.MarkedPending).Should(BeFalse()) ExpectAllWell(errors) }) It("marks the node as pending", func() { node, errors := internal.NewNode(dt, ntIt, "text", body, Pending) Ω(node.MarkedFocus).Should(BeFalse()) Ω(node.MarkedPending).Should(BeTrue()) ExpectAllWell(errors) }) It("errors when both Focus and Pending are set", func() { node, errors := internal.NewNode(dt, ntIt, "text", body, cl, Focus, Pending) Ω(node).Should(BeZero()) Ω(errors).Should(ConsistOf(types.GinkgoErrors.InvalidDeclarationOfFocusedAndPending(cl, ntIt))) }) It("allows containers to be marked", func() { node, errors := internal.NewNode(dt, ntCon, "text", body, Focus) Ω(node.MarkedFocus).Should(BeTrue()) Ω(node.MarkedPending).Should(BeFalse()) ExpectAllWell(errors) node, errors = internal.NewNode(dt, ntCon, "text", body, Pending) Ω(node.MarkedFocus).Should(BeFalse()) Ω(node.MarkedPending).Should(BeTrue()) ExpectAllWell(errors) }) It("does not allow non-container/it nodes to be marked", func() { node, errors := internal.NewNode(dt, ntBef, "", body, cl, Focus) Ω(node).Should(BeZero()) Ω(errors).Should(ConsistOf(types.GinkgoErrors.InvalidDecoratorForNodeType(cl, ntBef, "Focus"))) node, errors = internal.NewNode(dt, ntAf, "", body, cl, Pending) Ω(node).Should(BeZero()) Ω(errors).Should(ConsistOf(types.GinkgoErrors.InvalidDecoratorForNodeType(cl, ntAf, "Pending"))) Ω(dt.DidTrackDeprecations()).Should(BeFalse()) }) }) Describe("the Serial decoration", func() { It("the node is not Serial by default", func() { node, errors := internal.NewNode(dt, ntIt, "text", body) Ω(node.MarkedSerial).Should(BeFalse()) ExpectAllWell(errors) }) It("marks the node as Serial", func() { node, errors := internal.NewNode(dt, ntIt, "text", body, Serial) Ω(node.MarkedSerial).Should(BeTrue()) Ω(node.Labels).Should(Equal(Labels{"Serial"})) ExpectAllWell(errors) }) It("allows containers to be marked", func() { node, errors := internal.NewNode(dt, ntCon, "text", body, Serial) Ω(node.MarkedSerial).Should(BeTrue()) ExpectAllWell(errors) }) It("does not allow non-container/it nodes to be marked", func() { node, errors := internal.NewNode(dt, ntBef, "", body, cl, Serial) Ω(node).Should(BeZero()) Ω(errors).Should(ConsistOf(types.GinkgoErrors.InvalidDecoratorForNodeType(cl, ntBef, "Serial"))) Ω(dt.DidTrackDeprecations()).Should(BeFalse()) }) }) Describe("the Ordered decoration", func() { It("the node is not Ordered by default", func() { node, errors := internal.NewNode(dt, ntCon, "", body) Ω(node.MarkedOrdered).Should(BeFalse()) ExpectAllWell(errors) }) It("marks the node as Ordered", func() { node, errors := internal.NewNode(dt, ntCon, "", body, Ordered) Ω(node.MarkedOrdered).Should(BeTrue()) ExpectAllWell(errors) }) It("does not allow non-container nodes to be marked", func() { node, errors := internal.NewNode(dt, ntBef, "", body, cl, Ordered) Ω(node).Should(BeZero()) Ω(errors).Should(ConsistOf(types.GinkgoErrors.InvalidDecoratorForNodeType(cl, ntBef, "Ordered"))) Ω(dt.DidTrackDeprecations()).Should(BeFalse()) node, errors = internal.NewNode(dt, ntIt, "not even Its", body, cl, Ordered) Ω(node).Should(BeZero()) Ω(errors).Should(ConsistOf(types.GinkgoErrors.InvalidDecoratorForNodeType(cl, ntIt, "Ordered"))) Ω(dt.DidTrackDeprecations()).Should(BeFalse()) }) }) Describe("the ContinueOnFailure decoration", func() { It("the node is not MarkedContinueOnFailure by default", func() { node, errors := internal.NewNode(dt, ntCon, "", body) Ω(node.MarkedContinueOnFailure).Should(BeFalse()) ExpectAllWell(errors) }) It("marks the node as ContinueOnFailure", func() { node, errors := internal.NewNode(dt, ntCon, "", body, Ordered, ContinueOnFailure) Ω(node.MarkedContinueOnFailure).Should(BeTrue()) ExpectAllWell(errors) }) It("does not allow non-container nodes to be marked", func() { node, errors := internal.NewNode(dt, ntBef, "", body, cl, ContinueOnFailure) Ω(node).Should(BeZero()) Ω(errors).Should(ContainElement(types.GinkgoErrors.InvalidDecoratorForNodeType(cl, ntBef, "ContinueOnFailure"))) Ω(dt.DidTrackDeprecations()).Should(BeFalse()) }) It("does not allow non-Ordered container nodes to be marked", func() { node, errors := internal.NewNode(dt, ntCon, "", body, cl, ContinueOnFailure) Ω(node).Should(BeZero()) Ω(errors).Should(ConsistOf(types.GinkgoErrors.InvalidContinueOnFailureDecoration(cl))) Ω(dt.DidTrackDeprecations()).Should(BeFalse()) }) }) Describe("the OncePerOrdered decoration", func() { It("applies to setup nodes, only", func() { for _, nt := range []types.NodeType{ntBef, ntAf, ntJusAf, ntJusBef} { node, errors := internal.NewNode(dt, nt, "", body, OncePerOrdered) Ω(node.MarkedOncePerOrdered).Should(BeTrue()) ExpectAllWell(errors) } node, errors := internal.NewNode(dt, ntIt, "", body, OncePerOrdered, cl) Ω(node).Should(BeZero()) Ω(errors).Should(ConsistOf(types.GinkgoErrors.InvalidDecoratorForNodeType(cl, ntIt, "OncePerOrdered"))) }) }) Describe("the PollProgressAfter and PollProgressInterval decorations", func() { It("applies to non-container nodes, only", func() { for _, nt := range []types.NodeType{ntBef, ntAf, ntJusAf, ntJusBef, ntIt} { node, errors := internal.NewNode(dt, nt, "", body, PollProgressAfter(time.Second), PollProgressInterval(time.Minute)) Ω(node.PollProgressAfter).Should(Equal(time.Second)) Ω(node.PollProgressInterval).Should(Equal(time.Minute)) ExpectAllWell(errors) } node, errors := internal.NewNode(dt, ntCon, "", body, PollProgressAfter(time.Second), PollProgressInterval(time.Minute), cl) Ω(node).Should(BeZero()) Ω(errors).Should(ConsistOf( types.GinkgoErrors.InvalidDecoratorForNodeType(cl, ntCon, "PollProgressAfter"), types.GinkgoErrors.InvalidDecoratorForNodeType(cl, ntCon, "PollProgressInterval"), )) }) }) Describe("the FlakeAttempts and MustPassRepeatedly decorations", func() { It("the node sets FlakeAttempts and MustPassRepeatedly to zero by default", func() { node, errors := internal.NewNode(dt, ntIt, "text", body) Ω(node).ShouldNot(BeZero()) Ω(node.FlakeAttempts).Should(Equal(0)) Ω(node.MustPassRepeatedly).Should(Equal(0)) ExpectAllWell(errors) }) It("sets the FlakeAttempts field", func() { node, errors := internal.NewNode(dt, ntIt, "text", body, FlakeAttempts(2)) Ω(node.FlakeAttempts).Should(Equal(2)) ExpectAllWell(errors) }) It("sets the MustPassRepeatedly field", func() { node, errors := internal.NewNode(dt, ntIt, "text", body, MustPassRepeatedly(2)) Ω(node.MustPassRepeatedly).Should(Equal(2)) ExpectAllWell(errors) }) It("errors when both FlakeAttempts and MustPassRepeatedly are set", func() { node, errors := internal.NewNode(dt, ntIt, "text", body, cl, FlakeAttempts(2), MustPassRepeatedly(2)) Ω(node).Should(BeZero()) Ω(errors).Should(ConsistOf(types.GinkgoErrors.InvalidDeclarationOfFlakeAttemptsAndMustPassRepeatedly(cl, ntIt))) }) It("can be applied to containers", func() { node, errors := internal.NewNode(dt, ntCon, "text", body, FlakeAttempts(2)) Ω(node.FlakeAttempts).Should(Equal(2)) ExpectAllWell(errors) node, errors = internal.NewNode(dt, ntCon, "text", body, MustPassRepeatedly(2)) Ω(node.MustPassRepeatedly).Should(Equal(2)) ExpectAllWell(errors) }) It("cannot be applied to non-container/it nodes", func() { node, errors := internal.NewNode(dt, ntBef, "", body, cl, FlakeAttempts(2)) Ω(node).Should(BeZero()) Ω(errors).Should(ConsistOf(types.GinkgoErrors.InvalidDecoratorForNodeType(cl, ntBef, "FlakeAttempts"))) node, errors = internal.NewNode(dt, ntBef, "", body, cl, MustPassRepeatedly(2)) Ω(node).Should(BeZero()) Ω(errors).Should(ConsistOf(types.GinkgoErrors.InvalidDecoratorForNodeType(cl, ntBef, "MustPassRepeatedly"))) Ω(dt.DidTrackDeprecations()).Should(BeFalse()) }) }) Describe("The Label decoration", func() { It("has no labels by default", func() { node, errors := internal.NewNode(dt, ntIt, "text", body) Ω(node).ShouldNot(BeZero()) Ω(node.Labels).Should(Equal(Labels{})) ExpectAllWell(errors) }) It("can track labels", func() { node, errors := internal.NewNode(dt, ntIt, "text", body, Label("A", "B", "C")) Ω(node.Labels).Should(Equal(Labels{"A", "B", "C"})) ExpectAllWell(errors) }) It("appends and dedupes all labels together, even if nested", func() { node, errors := internal.NewNode(dt, ntIt, "text", body, Label("A", "B", "C"), Label("D", "E", "C"), []interface{}{Label("F"), []interface{}{Label("G", "H", "A", "F")}}) Ω(node.Labels).Should(Equal(Labels{"A", "B", "C", "D", "E", "F", "G", "H"})) ExpectAllWell(errors) }) It("can be applied to containers", func() { node, errors := internal.NewNode(dt, ntCon, "text", body, Label("A", "B", "C")) Ω(node.Labels).Should(Equal(Labels{"A", "B", "C"})) ExpectAllWell(errors) }) It("cannot be applied to non-container/it nodes", func() { node, errors := internal.NewNode(dt, ntBef, "", body, cl, Label("A", "B", "C")) Ω(node).Should(BeZero()) Ω(errors).Should(ConsistOf(types.GinkgoErrors.InvalidDecoratorForNodeType(cl, ntBef, "Label"))) Ω(dt.DidTrackDeprecations()).Should(BeFalse()) }) It("validates labels", func() { node, errors := internal.NewNode(dt, ntIt, "", body, cl, Label("A", "B&C", "C,D", "C,D ", " ", ":Foo")) Ω(node).Should(BeZero()) Ω(errors).Should(ConsistOf(types.GinkgoErrors.InvalidLabel("B&C", cl), types.GinkgoErrors.InvalidLabel("C,D", cl), types.GinkgoErrors.InvalidLabel("C,D ", cl), types.GinkgoErrors.InvalidEmptyLabel(cl), types.GinkgoErrors.InvalidLabel(":Foo", cl))) Ω(dt.DidTrackDeprecations()).Should(BeFalse()) }) }) Describe("the timeout-related decorators", func() { It("correctly assigned timeouts when specified", func() { node, errors := internal.NewNode(dt, ntIt, "spec", func(_ SpecContext) {}, cl, NodeTimeout(time.Second), SpecTimeout(2*time.Second), GracePeriod(3*time.Second)) Ω(errors).Should(BeEmpty()) Ω(node.NodeTimeout).Should(Equal(time.Second)) Ω(node.SpecTimeout).Should(Equal(2 * time.Second)) Ω(node.GracePeriod).Should(Equal(3 * time.Second)) }) It("errors if any are applied to containers", func() { node, errors := internal.NewNode(dt, ntCon, "container", body, cl, NodeTimeout(time.Second), SpecTimeout(time.Second), GracePeriod(time.Second)) Ω(node).Should(BeZero()) Ω(errors).Should(ConsistOf( types.GinkgoErrors.InvalidDecoratorForNodeType(cl, ntCon, "NodeTimeout"), types.GinkgoErrors.InvalidDecoratorForNodeType(cl, ntCon, "SpecTimeout"), types.GinkgoErrors.InvalidDecoratorForNodeType(cl, ntCon, "GracePeriod"), )) Ω(dt.DidTrackDeprecations()).Should(BeFalse()) }) It("only allows SpecTimeout to be applied to Its", func() { node, errors := internal.NewNode(dt, ntBef, "", func(_ SpecContext) {}, cl, SpecTimeout(time.Second)) Ω(node).Should(BeZero()) Ω(errors).Should(ConsistOf( types.GinkgoErrors.InvalidDecoratorForNodeType(cl, ntBef, "SpecTimeout"), )) Ω(dt.DidTrackDeprecations()).Should(BeFalse()) }) It("fails if a timeout is applied to a function that does not take a context", func() { for _, decorator := range []interface{}{NodeTimeout(time.Second), SpecTimeout(time.Second), GracePeriod(time.Second)} { dt = types.NewDeprecationTracker() _, errors := internal.NewNode(dt, ntIt, "spec", func(_ SpecContext) {}, cl, decorator) Ω(errors).Should(BeEmpty()) _, errors = internal.NewNode(dt, ntIt, "spec", func(_ context.Context) {}, cl, decorator) Ω(errors).Should(BeEmpty()) _, errors = internal.NewNode(dt, ntIt, "spec", func() {}, cl, decorator) Ω(errors).Should(ConsistOf( types.GinkgoErrors.InvalidTimeoutOrGracePeriodForNonContextNode(cl, ntIt), )) Ω(dt.DidTrackDeprecations()).Should(BeFalse()) _, errors = internal.NewNode(dt, ntIt, "spec", func(_ Done) {}, cl, decorator) Ω(errors).Should(ConsistOf( types.GinkgoErrors.InvalidTimeoutOrGracePeriodForNonContextNode(cl, ntIt), )) Ω(dt.DidTrackDeprecations()).Should(BeTrue()) } }) }) Describe("the SuppressProgressReporting decorator", func() { It("is deprecated", func() { node, errors := internal.NewNode(dt, ntIt, "spec", func() {}, cl, SuppressProgressReporting) Ω(node).ShouldNot(BeZero()) Ω(errors).Should(BeEmpty()) Ω(dt.DidTrackDeprecations()).Should(BeTrue()) Ω(dt.DeprecationsReport()).Should(ContainSubstring("SuppressProgressReporting is no longer necessary")) }) }) Describe("passing in functions", func() { It("works when a single function is passed in", func() { node, errors := internal.NewNode(dt, ntIt, "text", body, cl) node.Body(nil) Ω(didRun).Should(BeTrue()) ExpectAllWell(errors) Ω(node.HasContext).Should(BeFalse()) }) It("allows deprecated async functions and registers a deprecation warning", func() { node, errors := internal.NewNode(dt, ntIt, "text", func(done Done) { didRun = true Ω(done).ShouldNot(BeNil()) close(done) }, cl) node.Body(nil) Ω(didRun).Should(BeTrue()) Ω(errors).Should(BeEmpty()) Ω(dt.DeprecationsReport()).Should(ContainSubstring(types.Deprecations.Async().Message)) Ω(node.HasContext).Should(BeFalse()) }) It("allows functions to take SpecContext", func() { expectedContext := internal.NewSpecContext(nil) node, errors := internal.NewNode(dt, ntIt, "text", func(c SpecContext) { didRun = true Ω(c).Should(Equal(expectedContext)) }, cl) node.Body(expectedContext) Ω(didRun).Should(BeTrue()) ExpectAllWell(errors) Ω(node.HasContext).Should(BeTrue()) }) It("allows functions to take context.Context", func() { expectedContext := internal.NewSpecContext(nil) node, errors := internal.NewNode(dt, ntIt, "text", func(c context.Context) { didRun = true Ω(c).Should(Equal(expectedContext)) }, cl) node.Body(expectedContext) Ω(didRun).Should(BeTrue()) ExpectAllWell(errors) Ω(node.HasContext).Should(BeTrue()) }) It("errors if more than one function is provided", func() { node, errors := internal.NewNode(dt, ntIt, "text", body, body, cl) Ω(node).Should(BeZero()) Ω(errors).Should(ConsistOf(types.GinkgoErrors.MultipleBodyFunctions(cl, ntIt))) Ω(dt.DidTrackDeprecations()).Should(BeFalse()) }) It("errors if more than one function is provided for a ReportBeforeEach/ReportAFterEach node", func() { reportBody := func(types.SpecReport) {} node, errors := internal.NewNode(dt, types.NodeTypeReportAfterEach, "", reportBody, body, cl) Ω(node).Should(BeZero()) Ω(errors).Should(ConsistOf(types.GinkgoErrors.MultipleBodyFunctions(cl, types.NodeTypeReportAfterEach))) Ω(dt.DidTrackDeprecations()).Should(BeFalse()) }) It("errors if the function has a return value", func() { f := func() string { return "" } node, errors := internal.NewNode(dt, ntIt, "text", f, cl) Ω(node).Should(BeZero()) Ω(errors).Should(ConsistOf(types.GinkgoErrors.InvalidBodyType(reflect.TypeOf(f), cl, ntIt))) Ω(dt.DidTrackDeprecations()).Should(BeFalse()) }) It("errors if the function takes more than one argument", func() { f := func(Done, string) {} node, errors := internal.NewNode(dt, ntIt, "text", f, cl) Ω(node).Should(BeZero()) Ω(errors).Should(ConsistOf(types.GinkgoErrors.InvalidBodyType(reflect.TypeOf(f), cl, ntIt))) Ω(dt.DidTrackDeprecations()).Should(BeFalse()) }) It("errors if the function takes one argument and that argument is not the deprecated Done channel, or a context", func() { f := func(chan interface{}) {} node, errors := internal.NewNode(dt, ntIt, "text", f, cl) Ω(node).Should(BeZero()) Ω(errors).Should(ConsistOf(types.GinkgoErrors.InvalidBodyType(reflect.TypeOf(f), cl, ntIt))) Ω(dt.DidTrackDeprecations()).Should(BeFalse()) }) It("errors if no function is passed in", func() { node, errors := internal.NewNode(dt, ntIt, "text", cl) Ω(node).Should(BeZero()) Ω(errors).Should(ConsistOf(types.GinkgoErrors.MissingBodyFunction(cl, ntIt))) Ω(dt.DidTrackDeprecations()).Should(BeFalse()) }) It("is ok if no function is passed in but it is marked pending", func() { node, errors := internal.NewNode(dt, ntIt, "text", cl, Pending) Ω(node.IsZero()).Should(BeFalse()) ExpectAllWell(errors) }) It("doesn't allow mulitple functions for containers", func() { node, errors := internal.NewNode(dt, ntCon, "text", cl, func() {}, func() {}) Ω(node.IsZero()).Should(BeTrue()) Ω(errors).Should(ConsistOf(types.GinkgoErrors.MultipleBodyFunctions(cl, ntCon))) }) It("doesn't allow functions with return values or inputs for containers", func() { node, errors := internal.NewNode(dt, ntCon, "text", cl, func(_ SpecContext) {}) Ω(node.IsZero()).Should(BeTrue()) Ω(errors).Should(ConsistOf(types.GinkgoErrors.InvalidBodyTypeForContainer(reflect.TypeOf(func(_ SpecContext) {}), cl, ntCon))) node, errors = internal.NewNode(dt, ntCon, "text", cl, func() error { return nil }) Ω(node.IsZero()).Should(BeTrue()) Ω(errors).Should(ConsistOf(types.GinkgoErrors.InvalidBodyTypeForContainer(reflect.TypeOf(func() error { return nil }), cl, ntCon))) }) }) Describe("non-recognized decorations", func() { It("errors when a non-recognized decoration is provided", func() { node, errors := internal.NewNode(dt, ntIt, "text", cl, body, Focus, "aardvark", 5) Ω(node).Should(BeZero()) Ω(errors).Should(ConsistOf( types.GinkgoErrors.UnknownDecorator(cl, ntIt, "aardvark"), types.GinkgoErrors.UnknownDecorator(cl, ntIt, 5), )) Ω(dt.DidTrackDeprecations()).Should(BeFalse()) }) }) Describe("when decorations are nested in slices", func() { It("unrolls them first", func() { node, errors := internal.NewNode(dt, ntIt, "text", []interface{}{body, []interface{}{Focus, FlakeAttempts(3), Label("A")}, FlakeAttempts(2), Label("B"), Label("C", "D")}) Ω(node.FlakeAttempts).Should(Equal(2)) Ω(node.MarkedFocus).Should(BeTrue()) Ω(node.Labels).Should(Equal(Labels{"A", "B", "C", "D"})) node.Body(nil) Ω(didRun).Should(BeTrue()) ExpectAllWell(errors) }) }) }) var _ = Describe("Node", func() { // HERE - and all the fun edge cases Describe("The nodes that take more specific functions", func() { var dt *types.DeprecationTracker BeforeEach(func() { dt = types.NewDeprecationTracker() }) Describe("SynchronizedBeforeSuite", func() { It("returns a correctly configured node", func() { var ranProc1, ranAllProcs bool proc1Body := func() []byte { ranProc1 = true; return nil } allProcsBody := func(_ []byte) { ranAllProcs = true } node, errors := internal.NewNode(dt, types.NodeTypeSynchronizedBeforeSuite, "", proc1Body, allProcsBody, cl) Ω(errors).Should(BeEmpty()) Ω(node.ID).Should(BeNumerically(">", 0)) Ω(node.NodeType).Should(Equal(types.NodeTypeSynchronizedBeforeSuite)) node.SynchronizedBeforeSuiteProc1Body(nil) Ω(ranProc1).Should(BeTrue()) node.SynchronizedBeforeSuiteAllProcsBody(nil, nil) Ω(ranAllProcs).Should(BeTrue()) Ω(node.CodeLocation).Should(Equal(cl)) Ω(node.NestingLevel).Should(Equal(-1)) }) It("fails errors if passed too many functions", func() { node, errors := internal.NewNode(dt, types.NodeTypeSynchronizedBeforeSuite, "", func() {}, func() {}, func() {}, cl) Ω(errors).Should(ConsistOf(types.GinkgoErrors.MultipleBodyFunctions(cl, types.NodeTypeSynchronizedBeforeSuite))) Ω(node).Should(BeZero()) }) It("fails errors if not passed enough functions", func() { node, errors := internal.NewNode(dt, types.NodeTypeSynchronizedBeforeSuite, "", func() {}, cl) Ω(errors).Should(ConsistOf(types.GinkgoErrors.MissingBodyFunction(cl, types.NodeTypeSynchronizedBeforeSuite))) Ω(node).Should(BeZero()) node, errors = internal.NewNode(dt, types.NodeTypeSynchronizedBeforeSuite, "", cl) Ω(errors).Should(ConsistOf(types.GinkgoErrors.MissingBodyFunction(cl, types.NodeTypeSynchronizedBeforeSuite))) Ω(node).Should(BeZero()) }) var receivedContext context.Context var receivedBytes []byte var didRun bool DescribeTable("The various possible functions", func(proc1 any, allProcs any, hasContext bool, hasBytes bool) { node, errors := internal.NewNode(dt, types.NodeTypeSynchronizedBeforeSuite, "", proc1, allProcs) Ω(errors).Should(BeEmpty()) Ω(dt.DidTrackDeprecations()).Should(BeFalse()) Ω(node.SynchronizedBeforeSuiteProc1BodyHasContext).Should(Equal(hasContext)) Ω(node.SynchronizedBeforeSuiteAllProcsBodyHasContext).Should(Equal(hasContext)) var sc = internal.NewSpecContext(nil) receivedContext = nil didRun = false b := node.SynchronizedBeforeSuiteProc1Body(sc) Ω(didRun).Should(BeTrue()) if hasBytes { Ω(b).Should(Equal([]byte("the-bytes"))) } else { Ω(b).Should(Equal([]byte{})) } if hasContext { Ω(receivedContext).Should(Equal(sc)) } receivedContext = nil didRun = false node.SynchronizedBeforeSuiteAllProcsBody(sc, []byte("the-bytes")) Ω(didRun).Should(BeTrue()) if hasBytes { Ω(receivedBytes).Should(Equal([]byte("the-bytes"))) } if hasContext { Ω(receivedContext).Should(Equal(sc)) } }, func(_ any, _ any, hasContext bool, hasBytes bool) string { return fmt.Sprintf("hasContext: %t, hasBytes: %t", hasContext, hasBytes) }, Entry(nil, func() { didRun = true }, func() { didRun = true }, false, false), Entry(nil, func(c SpecContext) { didRun, receivedContext = true, c }, func(c SpecContext) { didRun, receivedContext = true, c }, true, false), Entry(nil, func(c context.Context) { didRun, receivedContext = true, c }, func(c context.Context) { didRun, receivedContext = true, c }, true, false), Entry(nil, func() []byte { didRun = true return []byte("the-bytes") }, func(b []byte) { didRun, receivedBytes = true, b }, false, true), Entry(nil, func(c SpecContext) []byte { didRun, receivedContext = true, c return []byte("the-bytes") }, func(c SpecContext, b []byte) { didRun, receivedContext, receivedBytes = true, c, b }, true, true), Entry(nil, func(c context.Context) []byte { didRun, receivedContext = true, c return []byte("the-bytes") }, func(c context.Context, b []byte) { didRun, receivedContext, receivedBytes = true, c, b }, true, true), ) DescribeTable("The various possible errors for proc1", func(proc1 any) { node, errors := internal.NewNode(dt, types.NodeTypeSynchronizedBeforeSuite, "", proc1, func() {}, cl) Ω(errors).Should(ConsistOf(types.GinkgoErrors.InvalidBodyTypeForSynchronizedBeforeSuiteProc1(reflect.TypeOf(proc1), cl))) Ω(node).Should(BeZero()) }, Entry("too many return values", func() ([]byte, error) { return nil, nil }), Entry("too many input values", func(_ SpecContext, _ []byte) {}), Entry("wrong return type", func() string { return "foo" }), Entry("wrong input type", func(_ string) {}), ) DescribeTable("The various posisble errors for allProc", func(allProc any) { node, errors := internal.NewNode(dt, types.NodeTypeSynchronizedBeforeSuite, "", func() {}, allProc, cl) Ω(errors).Should(ConsistOf(types.GinkgoErrors.InvalidBodyTypeForSynchronizedBeforeSuiteAllProcs(reflect.TypeOf(allProc), cl))) Ω(node).Should(BeZero()) }, Entry("too many return values", func() []byte { return nil }), Entry("too many input values", func(_ SpecContext, _ []byte, _ string) {}), Entry("wrong input types", func(_ string, _ string) {}), Entry("wrong input types", func(_ string) {}), Entry("wrong input types", func(_ SpecContext, _ string) {}), Entry("flipped input types", func(_ []byte, _ SpecContext) {}), ) }) Describe("SynchronizedAfterSuiteNode", func() { It("returns a correctly configured node", func() { var ranProc1, ranAllProcs bool allProcsBody := func() { ranAllProcs = true } proc1Body := func() { ranProc1 = true } node, errors := internal.NewNode(dt, types.NodeTypeSynchronizedAfterSuite, "", allProcsBody, proc1Body, cl) Ω(errors).Should(BeEmpty()) Ω(node.ID).Should(BeNumerically(">", 0)) Ω(node.NodeType).Should(Equal(types.NodeTypeSynchronizedAfterSuite)) Ω(node.SynchronizedAfterSuiteProc1BodyHasContext).Should(BeFalse()) Ω(node.SynchronizedAfterSuiteAllProcsBodyHasContext).Should(BeFalse()) node.SynchronizedAfterSuiteAllProcsBody(nil) Ω(ranAllProcs).Should(BeTrue()) node.SynchronizedAfterSuiteProc1Body(nil) Ω(ranProc1).Should(BeTrue()) Ω(node.CodeLocation).Should(Equal(cl)) Ω(node.NestingLevel).Should(Equal(-1)) }) It("tracks context correctly", func() { node, errors := internal.NewNode(dt, types.NodeTypeSynchronizedAfterSuite, "", func(_ SpecContext) {}, func(_ context.Context) {}, cl) Ω(errors).Should(BeEmpty()) Ω(node.SynchronizedAfterSuiteProc1BodyHasContext).Should(BeTrue()) Ω(node.SynchronizedAfterSuiteAllProcsBodyHasContext).Should(BeTrue()) }) It("errors if passed a function with an invalid signature", func() { node, errors := internal.NewNode(dt, types.NodeTypeSynchronizedAfterSuite, "", func(_ string) {}, cl) Ω(errors).Should(ConsistOf(types.GinkgoErrors.InvalidBodyType(reflect.TypeOf(func(_ string) {}), cl, types.NodeTypeSynchronizedAfterSuite))) Ω(node).Should(BeZero()) }) It("errors if passed too many functions", func() { node, errors := internal.NewNode(dt, types.NodeTypeSynchronizedAfterSuite, "", func() {}, func() {}, func() {}, cl) Ω(errors).Should(ConsistOf(types.GinkgoErrors.MultipleBodyFunctions(cl, types.NodeTypeSynchronizedAfterSuite))) Ω(node).Should(BeZero()) }) It("errors if not passed enough functions", func() { node, errors := internal.NewNode(dt, types.NodeTypeSynchronizedAfterSuite, "", func() {}, cl) Ω(errors).Should(ConsistOf(types.GinkgoErrors.MissingBodyFunction(cl, types.NodeTypeSynchronizedAfterSuite))) Ω(node).Should(BeZero()) node, errors = internal.NewNode(dt, types.NodeTypeSynchronizedAfterSuite, "", cl) Ω(errors).Should(ConsistOf(types.GinkgoErrors.MissingBodyFunction(cl, types.NodeTypeSynchronizedAfterSuite))) Ω(node).Should(BeZero()) }) }) Describe("ReportAfterSuiteNode", func() { It("returns a correctly configured node", func() { var didRun bool body := func(types.Report) { didRun = true } node, errors := internal.NewNode(dt, types.NodeTypeReportAfterSuite, "my custom report", body, cl) Ω(errors).Should(BeEmpty()) Ω(node.Text).Should(Equal("my custom report")) Ω(node.ID).Should(BeNumerically(">", 0)) Ω(node.NodeType).Should(Equal(types.NodeTypeReportAfterSuite)) node.ReportSuiteBody(internal.NewSpecContext(nil), types.Report{}) Ω(didRun).Should(BeTrue()) Ω(node.CodeLocation).Should(Equal(cl)) Ω(node.NestingLevel).Should(Equal(-1)) }) It("errors if passed too many functions", func() { node, errors := internal.NewNode(dt, types.NodeTypeReportAfterSuite, "my custom report", func(types.Report) {}, func() {}, cl) Ω(node).Should(BeZero()) Ω(errors).Should(ConsistOf(types.GinkgoErrors.MultipleBodyFunctions(cl, types.NodeTypeReportAfterSuite))) }) }) Describe("ReportBeforeSuiteNode", func() { It("returns a correctly configured node", func() { var didRun bool body := func(types.Report) { didRun = true } node, errors := internal.NewNode(dt, types.NodeTypeReportBeforeSuite, "", body, cl) Ω(errors).Should(BeEmpty()) Ω(node.Text).Should(BeEmpty()) Ω(node.ID).Should(BeNumerically(">", 0)) Ω(node.NodeType).Should(Equal(types.NodeTypeReportBeforeSuite)) node.ReportSuiteBody(internal.NewSpecContext(nil), types.Report{}) Ω(didRun).Should(BeTrue()) Ω(node.CodeLocation).Should(Equal(cl)) Ω(node.NestingLevel).Should(Equal(-1)) }) It("errors if passed too many functions", func() { node, errors := internal.NewNode(dt, types.NodeTypeReportBeforeSuite, "", func(types.Report) {}, func() {}, cl) Ω(node).Should(BeZero()) Ω(errors).Should(ConsistOf(types.GinkgoErrors.MultipleBodyFunctions(cl, types.NodeTypeReportBeforeSuite))) }) }) Describe("NewCleanupNode", func() { var capturedFailure string var capturedCL types.CodeLocation var failFunc = func(msg string, cl types.CodeLocation) { capturedFailure = msg capturedCL = cl } BeforeEach(func() { capturedFailure = "" capturedCL = types.CodeLocation{} }) Context("when passed no function", func() { It("errors", func() { node, errs := internal.NewCleanupNode(dt, failFunc, cl) Ω(node.IsZero()).Should(BeTrue()) Ω(errs).Should(ConsistOf(types.GinkgoErrors.DeferCleanupInvalidFunction(cl))) Ω(capturedFailure).Should(BeZero()) Ω(capturedCL).Should(BeZero()) }) }) Context("when passed a function that does not return", func() { It("creates a body that runs the function and never calls the fail handler", func() { didRun := false node, errs := internal.NewCleanupNode(dt, failFunc, cl, func() { didRun = true }) Ω(node.CodeLocation).Should(Equal(cl)) Ω(node.NodeType).Should(Equal(types.NodeTypeCleanupInvalid)) Ω(errs).Should(BeEmpty()) node.Body(internal.NewSpecContext(nil)) Ω(didRun).Should(BeTrue()) Ω(capturedFailure).Should(BeZero()) Ω(capturedCL).Should(BeZero()) }) }) Context("when passed a function that returns somethign that isn't an error", func() { It("creates a body that runs the function and never calls the fail handler", func() { didRun := false node, errs := internal.NewCleanupNode(dt, failFunc, cl, func() (string, int) { didRun = true return "not-an-error", 17 }) Ω(node.CodeLocation).Should(Equal(cl)) Ω(node.NodeType).Should(Equal(types.NodeTypeCleanupInvalid)) Ω(errs).Should(BeEmpty()) node.Body(internal.NewSpecContext(nil)) Ω(didRun).Should(BeTrue()) Ω(capturedFailure).Should(BeZero()) Ω(capturedCL).Should(BeZero()) }) }) Context("when passed a function that returns a nil error", func() { It("creates a body that runs the function and does not call the fail handler", func() { didRun := false node, errs := internal.NewCleanupNode(dt, failFunc, cl, func() (string, int, error) { didRun = true return "not-an-error", 17, nil }) Ω(node.CodeLocation).Should(Equal(cl)) Ω(node.NodeType).Should(Equal(types.NodeTypeCleanupInvalid)) Ω(errs).Should(BeEmpty()) node.Body(internal.NewSpecContext(nil)) Ω(didRun).Should(BeTrue()) Ω(capturedFailure).Should(BeZero()) Ω(capturedCL).Should(BeZero()) }) }) Context("when passed a function that returns an error for its final return value", func() { It("creates a body that runs the function and calls the fail handler", func() { didRun := false node, errs := internal.NewCleanupNode(dt, failFunc, cl, func() (string, int, error) { didRun = true return "not-an-error", 17, fmt.Errorf("welp") }) Ω(node.CodeLocation).Should(Equal(cl)) Ω(node.NodeType).Should(Equal(types.NodeTypeCleanupInvalid)) Ω(errs).Should(BeEmpty()) node.Body(internal.NewSpecContext(nil)) Ω(didRun).Should(BeTrue()) Ω(capturedFailure).Should(Equal("DeferCleanup callback returned error: welp")) Ω(capturedCL).Should(Equal(cl)) }) }) Context("when passed a function that takes arguments, and those arguments", func() { It("creates a body that runs the function and passes in those arguments", func() { var inA, inB, inC = "A", 2, "C" var receivedA, receivedC string var receivedB int node, errs := internal.NewCleanupNode(dt, failFunc, cl, func(a string, b int, c string) error { receivedA, receivedB, receivedC = a, b, c return nil }, inA, inB, inC) inA, inB, inC = "floop", 3, "flarp" Ω(node.CodeLocation).Should(Equal(cl)) Ω(node.NodeType).Should(Equal(types.NodeTypeCleanupInvalid)) Ω(errs).Should(BeEmpty()) node.Body(internal.NewSpecContext(nil)) Ω(receivedA).Should(Equal("A")) Ω(receivedB).Should(Equal(2)) Ω(receivedC).Should(Equal("C")) Ω(capturedFailure).Should(BeZero()) Ω(capturedCL).Should(BeZero()) }) }) Context("when passed a function that takes a SpecContext, or context.Context and either no arguments, or other non-context arguments", func() { It("hasContext and forwards the SpecContext and arguments", func() { var receivedContext context.Context var receivedA string var receivedB int sc := internal.NewSpecContext(nil) node, errs := internal.NewCleanupNode(dt, failFunc, cl, func(c SpecContext) { receivedContext = c }, NodeTimeout(time.Second), GracePeriod(time.Minute)) Ω(errs).Should(BeEmpty()) node.Body(sc) Ω(receivedContext).Should(Equal(sc)) Ω(node.HasContext).Should(BeTrue()) Ω(node.NodeTimeout).Should(Equal(time.Second)) Ω(node.GracePeriod).Should(Equal(time.Minute)) receivedContext = nil node, errs = internal.NewCleanupNode(dt, failFunc, cl, func(cont context.Context) { receivedContext = cont }) Ω(errs).Should(BeEmpty()) node.Body(sc) Ω(receivedContext).Should(Equal(sc)) Ω(node.HasContext).Should(BeTrue()) receivedContext = nil node, errs = internal.NewCleanupNode(dt, failFunc, cl, func(cont SpecContext, a string, b int) { receivedContext, receivedA, receivedB = cont, a, b }, "foo", 3) Ω(errs).Should(BeEmpty()) node.Body(sc) Ω(receivedContext).Should(Equal(sc)) Ω(receivedA).Should(Equal("foo")) Ω(receivedB).Should(Equal(3)) Ω(node.HasContext).Should(BeTrue()) receivedContext, receivedA, receivedB = nil, "", 0 node, errs = internal.NewCleanupNode(dt, failFunc, cl, func(cont context.Context, a string, b int) { receivedContext, receivedA, receivedB = cont, a, b }, "foo", 3) Ω(errs).Should(BeEmpty()) node.Body(sc) Ω(receivedContext).Should(Equal(sc)) Ω(receivedA).Should(Equal("foo")) Ω(receivedB).Should(Equal(3)) Ω(node.HasContext).Should(BeTrue()) }) }) Context("when the arguments include contexts", func() { It("only hasContext and forwards the SpecContext if the user asks for a SpecContext", func() { var receivedContext context.Context var receivedA context.Context var receivedB string var receivedC int sc := internal.NewSpecContext(nil) otherContext := context.WithValue(context.Background(), "bump", "bump") node, errs := internal.NewCleanupNode(dt, failFunc, cl, func(cont SpecContext, a context.Context, b string, c int) { receivedContext, receivedA, receivedB, receivedC = cont, a, b, c }, otherContext, "foo", 3) Ω(errs).Should(BeEmpty()) node.Body(sc) Ω(receivedContext).Should(Equal(sc)) Ω(receivedA).Should(Equal(otherContext)) Ω(receivedB).Should(Equal("foo")) Ω(receivedC).Should(Equal(3)) Ω(node.HasContext).Should(BeTrue()) receivedContext, receivedA, receivedB, receivedC = nil, nil, "", 0 node, errs = internal.NewCleanupNode(dt, failFunc, cl, func(a context.Context, b string, c int) { receivedA, receivedB, receivedC = a, b, c }, otherContext, "foo", 3) Ω(errs).Should(BeEmpty()) node.Body(sc) Ω(receivedA).Should(Equal(otherContext)) Ω(receivedB).Should(Equal("foo")) Ω(receivedC).Should(Equal(3)) Ω(node.HasContext).Should(BeFalse()) yetAnotherContext := context.WithValue(context.Background(), "wibble", "wibble") Ω(otherContext).ShouldNot(Equal(yetAnotherContext)) receivedContext, receivedA, receivedB, receivedC = nil, nil, "", 0 node, errs = internal.NewCleanupNode(dt, failFunc, cl, func(cont context.Context, a context.Context, b string, c int) { receivedContext, receivedA, receivedB, receivedC = cont, a, b, c }, yetAnotherContext, otherContext, "foo", 3) Ω(errs).Should(BeEmpty()) node.Body(sc) Ω(receivedContext).Should(Equal(yetAnotherContext), "Note, this is not sc - this node is not considered to have a context") Ω(receivedA).Should(Equal(otherContext)) Ω(receivedB).Should(Equal("foo")) Ω(receivedC).Should(Equal(3)) Ω(node.HasContext).Should(BeFalse()) }) }) Context("controlling the cleanup's code location", func() { It("computes its own when one is not provided", func() { node, errs := func() (internal.Node, []error) { return internal.NewCleanupNode(dt, failFunc, func() error { return fmt.Errorf("welp") }) }() localCL := types.NewCodeLocation(0) localCL.LineNumber -= 1 Ω(node.CodeLocation).Should(Equal(localCL)) Ω(node.NodeType).Should(Equal(types.NodeTypeCleanupInvalid)) Ω(errs).Should(BeEmpty()) node.Body(nil) Ω(capturedFailure).Should(Equal("DeferCleanup callback returned error: welp")) Ω(capturedCL).Should(Equal(localCL)) }) It("can accept an Offset", func() { node, errs := func() (internal.Node, []error) { return func() (internal.Node, []error) { return internal.NewCleanupNode(dt, failFunc, Offset(1), func() error { return fmt.Errorf("welp") }) }() }() localCL := types.NewCodeLocation(0) localCL.LineNumber -= 1 Ω(node.CodeLocation).Should(Equal(localCL)) Ω(node.NodeType).Should(Equal(types.NodeTypeCleanupInvalid)) Ω(errs).Should(BeEmpty()) node.Body(nil) Ω(capturedFailure).Should(Equal("DeferCleanup callback returned error: welp")) Ω(capturedCL).Should(Equal(localCL)) }) It("can accept a code location", func() { node, errs := internal.NewCleanupNode(dt, failFunc, cl, func() error { return fmt.Errorf("welp") }) Ω(node.CodeLocation).Should(Equal(cl)) Ω(node.NodeType).Should(Equal(types.NodeTypeCleanupInvalid)) Ω(errs).Should(BeEmpty()) node.Body(nil) Ω(capturedFailure).Should(Equal("DeferCleanup callback returned error: welp")) Ω(capturedCL).Should(Equal(cl)) }) }) }) }) Describe("IsZero()", func() { It("returns true if the node is zero", func() { Ω(Node{}.IsZero()).Should(BeTrue()) }) It("returns false if the node is non-zero", func() { node, errors := internal.NewNode(nil, ntIt, "hummus", func() {}, cl) Ω(errors).Should(BeEmpty()) Ω(node.IsZero()).Should(BeFalse()) }) }) }) var _ = Describe("Nodes", func() { Describe("Clone", func() { var n1, n2, n3, n4 Node BeforeEach(func() { n1, n2, n3, n4 = N(), N(), N(), N() }) It("clones the slice", func() { original := Nodes{n1, n2, n3} clone := original.Clone() Ω(original).Should(Equal(clone)) clone[2] = n4 Ω(original).Should(Equal(Nodes{n1, n2, n3})) }) }) Describe("CopyAppend", func() { var n1, n2, n3, n4 Node BeforeEach(func() { n1, n2, n3, n4 = N(), N(), N(), N() }) It("appends the passed in nodes and returns the result", func() { result := Nodes{n1, n2}.CopyAppend(n3, n4) Ω(result).Should(Equal(Nodes{n1, n2, n3, n4})) }) It("makes a copy, leaving the original untouched", func() { original := Nodes{n1, n2} original.CopyAppend(n3, n4) Ω(original).Should(Equal(Nodes{n1, n2})) }) }) Describe("SplitAround", func() { var nodes Nodes BeforeEach(func() { nodes = Nodes{N(), N(), N(), N(), N()} }) Context("when the pivot is a member of nodes", func() { Context("when the pivot is not at one of the ends", func() { It("returns the correct left and right nodes", func() { left, right := nodes.SplitAround(nodes[2]) Ω(left).Should(Equal(Nodes{nodes[0], nodes[1]})) Ω(right).Should(Equal(Nodes{nodes[3], nodes[4]})) }) }) Context("when the pivot is the first member", func() { It("returns an empty left nodes and the complete right nodes", func() { left, right := nodes.SplitAround(nodes[0]) Ω(left).Should(BeEmpty()) Ω(right).Should(Equal(Nodes{nodes[1], nodes[2], nodes[3], nodes[4]})) }) }) Context("when the pivot is the last member", func() { It("returns an empty right nodes and the complete left nodes", func() { left, right := nodes.SplitAround(nodes[4]) Ω(left).Should(Equal(Nodes{nodes[0], nodes[1], nodes[2], nodes[3]})) Ω(right).Should(BeEmpty()) }) }) }) Context("when the pivot is not in nodes", func() { It("returns an empty right nodes and the complete left nodes", func() { left, right := nodes.SplitAround(N()) Ω(left).Should(Equal(nodes)) Ω(right).Should(BeEmpty()) }) }) }) Describe("FirstNodeWithType", func() { var nodes Nodes BeforeEach(func() { nodes = Nodes{N(ntCon), N("bef1", ntBef), N("bef2", ntBef), N(ntIt), N(ntAf)} }) Context("when there is a matching node", func() { It("returns the first node that matches one of the requested node types", func() { Ω(nodes.FirstNodeWithType(ntAf | ntIt | ntBef).Text).Should(Equal("bef1")) }) }) Context("when there is no matching node", func() { It("returns an empty node", func() { Ω(nodes.FirstNodeWithType(ntJusAf)).Should(BeZero()) }) }) }) Describe("Filtering By NodeType", func() { var nCon, nBef1, nBef2, nIt, nAf Node var nodes Nodes BeforeEach(func() { nCon = N(ntCon) nBef1 = N(ntBef) nBef2 = N(ntBef) nIt = N(ntIt) nAf = N(ntAf) nodes = Nodes{nCon, nBef1, nBef2, nIt, nAf} }) Describe("WithType", func() { Context("when there are matching nodes", func() { It("returns them while preserving order", func() { Ω(nodes.WithType(ntIt | ntBef)).Should(Equal(Nodes{nBef1, nBef2, nIt})) }) }) Context("when there are no matching nodes", func() { It("returns an empty Nodes{}", func() { Ω(nodes.WithType(ntJusAf)).Should(BeEmpty()) }) }) }) Describe("WithoutType", func() { Context("when there are matching nodes", func() { It("does not include them in the result", func() { Ω(nodes.WithoutType(ntIt | ntBef)).Should(Equal(Nodes{nCon, nAf})) }) }) Context("when no nodes match", func() { It("doesn't elide any nodes", func() { Ω(nodes.WithoutType(ntJusAf)).Should(Equal(nodes)) }) }) }) Describe("WithoutNode", func() { Context("when operating on an empty nodes list", func() { It("does nothing", func() { nodes = Nodes{} Ω(nodes.WithoutNode(N(ntIt))).Should(BeEmpty()) }) }) Context("when the node is in the nodes list", func() { It("returns a copy of the nodes list without the node in it", func() { Ω(nodes.WithoutNode(nBef2)).Should(Equal(Nodes{nCon, nBef1, nIt, nAf})) Ω(nodes).Should(Equal(Nodes{nCon, nBef1, nBef2, nIt, nAf})) }) }) Context("when the node is not in the nodes list", func() { It("returns an unadulterated copy of the nodes list", func() { Ω(nodes.WithoutNode(N(ntBef))).Should(Equal(Nodes{nCon, nBef1, nBef2, nIt, nAf})) Ω(nodes).Should(Equal(Nodes{nCon, nBef1, nBef2, nIt, nAf})) }) }) }) Describe("Filter", func() { It("returns a copy of the nodes list containing nodes that pass the filter", func() { filtered := nodes.Filter(func(n Node) bool { return n.NodeType.Is(types.NodeTypeBeforeEach | types.NodeTypeIt) }) Ω(filtered).Should(Equal(Nodes{nBef1, nBef2, nIt})) Ω(nodes).Should(Equal(Nodes{nCon, nBef1, nBef2, nIt, nAf})) filtered = nodes.Filter(func(n Node) bool { return false }) Ω(filtered).Should(BeEmpty()) }) }) }) Describe("SortedByDescendingNestingLevel", func() { var n0A, n0B, n1A, n1B, n1C, n2A, n2B Node var nodes Nodes BeforeEach(func() { n0A = N(NestingLevel(0)) n0B = N(NestingLevel(0)) n1A = N(NestingLevel(1)) n1B = N(NestingLevel(1)) n1C = N(NestingLevel(1)) n2A = N(NestingLevel(2)) n2B = N(NestingLevel(2)) nodes = Nodes{n0A, n0B, n1A, n1B, n1C, n2A, n2B} }) It("returns copy sorted by descending nesting level, preserving order within nesting level", func() { Ω(nodes.SortedByDescendingNestingLevel()).Should(Equal(Nodes{n2A, n2B, n1A, n1B, n1C, n0A, n0B})) Ω(nodes).Should(Equal(Nodes{n0A, n0B, n1A, n1B, n1C, n2A, n2B}), "original nodes should not have been modified") }) }) Describe("SortedByAscendingNestingLevel", func() { var n0A, n0B, n1A, n1B, n1C, n2A, n2B Node var nodes Nodes BeforeEach(func() { n0A = N(NestingLevel(0)) n0B = N(NestingLevel(0)) n1A = N(NestingLevel(1)) n1B = N(NestingLevel(1)) n1C = N(NestingLevel(1)) n2A = N(NestingLevel(2)) n2B = N(NestingLevel(2)) nodes = Nodes{n2A, n1A, n1B, n0A, n2B, n0B, n1C} }) It("returns copy sorted by ascending nesting level, preserving order within nesting level", func() { Ω(nodes.SortedByAscendingNestingLevel()).Should(Equal(Nodes{n0A, n0B, n1A, n1B, n1C, n2A, n2B})) Ω(nodes).Should(Equal(Nodes{n2A, n1A, n1B, n0A, n2B, n0B, n1C}), "original nodes should not have been modified") }) }) Describe("WithinNestingLevel", func() { var n0, n1, n2a, n2b, n3 Node var nodes Nodes BeforeEach(func() { n0 = N(NestingLevel(0)) n1 = N(NestingLevel(1)) n2a = N(NestingLevel(2)) n3 = N(NestingLevel(3)) n2b = N(NestingLevel(2)) nodes = Nodes{n0, n1, n2a, n3, n2b} }) It("returns nodes, in order, with nesting level equal to or less than the requested level", func() { Ω(nodes.WithinNestingLevel(-1)).Should(BeEmpty()) Ω(nodes.WithinNestingLevel(0)).Should(Equal(Nodes{n0})) Ω(nodes.WithinNestingLevel(1)).Should(Equal(Nodes{n0, n1})) Ω(nodes.WithinNestingLevel(2)).Should(Equal(Nodes{n0, n1, n2a, n2b})) Ω(nodes.WithinNestingLevel(3)).Should(Equal(Nodes{n0, n1, n2a, n3, n2b})) }) }) Describe("Reverse", func() { It("reverses the nodes", func() { nodes := Nodes{N("A"), N("B"), N("C"), N("D"), N("E")} Ω(nodes.Reverse().Texts()).Should(Equal([]string{"E", "D", "C", "B", "A"})) }) It("works with empty nodes", func() { nodes := Nodes{} Ω(nodes.Reverse()).Should(Equal(Nodes{})) }) }) Describe("Texts", func() { var nodes Nodes BeforeEach(func() { nodes = Nodes{N("the first node"), N(""), N("2"), N("c"), N("")} }) It("returns a string slice containing the individual node text strings in order", func() { Ω(nodes.Texts()).Should(Equal([]string{"the first node", "", "2", "c", ""})) }) }) Describe("Labels and UnionOfLabels", func() { var nodes Nodes BeforeEach(func() { nodes = Nodes{N(Label("A", "B")), N(Label("C")), N(), N(Label("A")), N(Label("D")), N(Label("B", "D", "E"))} }) It("Labels returns a slice containing the labels for each node in order", func() { Ω(nodes.Labels()).Should(Equal([][]string{ {"A", "B"}, {"C"}, {}, {"A"}, {"D"}, {"B", "D", "E"}, })) }) It("UnionOfLabels returns a single slice of labels harvested from all nodes and deduped", func() { Ω(nodes.UnionOfLabels()).Should(Equal([]string{"A", "B", "C", "D", "E"})) }) }) Describe("CodeLocation", func() { var nodes Nodes var cl1, cl2 types.CodeLocation BeforeEach(func() { cl1 = types.NewCodeLocation(0) cl2 = types.NewCodeLocation(0) nodes = Nodes{N(cl1), N(cl2), N()} }) It("returns a types.CodeLocation slice containing the individual node code locations in order", func() { Ω(nodes.CodeLocations()).Should(Equal([]types.CodeLocation{cl1, cl2, cl})) }) }) Describe("BestTextFor", func() { var nIt, nBef1, nBef2 Node var nodes Nodes BeforeEach(func() { nIt = N("an it", ntIt, NestingLevel(2)) nBef1 = N(ntBef, NestingLevel(2)) nBef2 = N(ntBef, NestingLevel(4)) nodes = Nodes{ N("the root container", ntCon, NestingLevel(0)), N("the inner container", ntCon, NestingLevel(1)), nBef1, nIt, nBef2, } }) Context("when the passed in node has text", func() { It("returns that text", func() { Ω(nodes.BestTextFor(nIt)).Should(Equal("an it")) }) }) Context("when the node has no text", func() { Context("and there is a node one-nesting-level-up with text", func() { It("returns that node's text", func() { Ω(nodes.BestTextFor(nBef1)).Should(Equal("the inner container")) }) }) Context("and there is no node one-nesting-level up with text", func() { It("returns empty string", func() { Ω(nodes.BestTextFor(nBef2)).Should(Equal("")) }) }) }) }) Describe("ContainsNodeID", func() { Context("when there is a node with the matching ID", func() { It("returns true", func() { nodes := Nodes{N(), N(), N()} Ω(nodes.ContainsNodeID(nodes[1].ID)).Should(BeTrue()) }) }) Context("when there is no node with matching ID", func() { It("returns false", func() { nodes := Nodes{N(), N(), N()} Ω(nodes.ContainsNodeID(nodes[2].ID + 1)).Should(BeFalse()) }) }) }) Describe("HasNodeMarkedPending", func() { Context("when there is a node marked pending", func() { It("returns true", func() { nodes := Nodes{N(), N(), N(Pending), N()} Ω(nodes.HasNodeMarkedPending()).Should(BeTrue()) }) }) Context("when there is no node marked pending", func() { It("returns false", func() { nodes := Nodes{N(), N(), N()} Ω(nodes.HasNodeMarkedPending()).Should(BeFalse()) }) }) }) Describe("HasNodeMarkedFocus", func() { Context("when there is a node marked focus", func() { It("returns true", func() { nodes := Nodes{N(), N(), N(Focus), N()} Ω(nodes.HasNodeMarkedFocus()).Should(BeTrue()) }) }) Context("when there is no node marked focus", func() { It("returns false", func() { nodes := Nodes{N(), N(), N()} Ω(nodes.HasNodeMarkedFocus()).Should(BeFalse()) }) }) }) Describe("HasNodeMarkedSerial", func() { Context("when there is a node marked serial", func() { It("returns true", func() { nodes := Nodes{N(), N(), N(Serial), N()} Ω(nodes.HasNodeMarkedSerial()).Should(BeTrue()) }) }) Context("when there is no node marked serial", func() { It("returns false", func() { nodes := Nodes{N(), N(), N()} Ω(nodes.HasNodeMarkedSerial()).Should(BeFalse()) }) }) }) Describe("FirstNodeMarkedOrdered", func() { Context("when there are nodes marked ordered", func() { It("returns the first one", func() { nodes := Nodes{N(), N("A", ntCon, Ordered), N("B", ntCon, Ordered), N()} Ω(nodes.FirstNodeMarkedOrdered().Text).Should(Equal("A")) }) }) Context("when there is no node marked ordered", func() { It("returns zero", func() { nodes := Nodes{N(), N(), N()} Ω(nodes.FirstNodeMarkedOrdered()).Should(BeZero()) }) }) }) Describe("IndexOfFirstNodeMarkedOrdered", func() { Context("when there are nodes marked ordered", func() { It("returns the index of the first one", func() { nodes := Nodes{N(), N("A", ntCon, Ordered), N("B", ntCon, Ordered), N()} Ω(nodes.IndexOfFirstNodeMarkedOrdered()).Should(Equal(1)) }) }) Context("when there is no node marked ordered", func() { It("returns -1", func() { nodes := Nodes{N(), N(), N()} Ω(nodes.IndexOfFirstNodeMarkedOrdered()).Should(Equal(-1)) }) }) }) Describe("GetMaxFlakeAttempts", func() { Context("when there is no node marked with FlakeAttempts decorator", func() { It("returns 0", func() { nodes := Nodes{N(), N(), N()} Ω(nodes.GetMaxFlakeAttempts()).Should(Equal(0)) }) }) Context("when there is a node marked with FlakeAttempt decorator", func() { It("returns the FlakeAttempt value", func() { nodes := Nodes{N(), N(FlakeAttempts(2)), N(), N()} Ω(nodes.GetMaxFlakeAttempts()).Should(Equal(2)) }) }) Context("when FlakeAttempt decorations are nested", func() { It("returns the last FlakeAttempt value", func() { nodes := Nodes{N(), N(FlakeAttempts(4)), N(), N(FlakeAttempts(2))} Ω(nodes.GetMaxFlakeAttempts()).Should(Equal(2)) }) }) }) Describe("GetMaxMustPassRepeatedly", func() { Context("when there is no node marked with MustPassRepeatedly decorator", func() { It("returns 0", func() { nodes := Nodes{N(), N(), N()} Ω(nodes.GetMaxMustPassRepeatedly()).Should(Equal(0)) }) }) Context("when there is a node marked with MustPassRepeatedly decorator", func() { It("returns the MustPassRepeatedly value", func() { nodes := Nodes{N(), N(MustPassRepeatedly(2)), N(), N()} Ω(nodes.GetMaxMustPassRepeatedly()).Should(Equal(2)) }) }) Context("when MustPassRepeatedly decorations are nested", func() { It("returns the last MustPassRepeatedly value", func() { nodes := Nodes{N(), N(MustPassRepeatedly(4)), N(), N(MustPassRepeatedly(2))} Ω(nodes.GetMaxMustPassRepeatedly()).Should(Equal(2)) }) }) }) Describe("Labels", func() { It("can match against a filter", func() { Ω(Label().MatchesLabelFilter("")).Should(BeTrue()) Ω(Label("dog", "cat").MatchesLabelFilter("dog")).Should(BeTrue()) Ω(Label("dog", "cat").MatchesLabelFilter("cat")).Should(BeTrue()) Ω(Label("dog", "cat").MatchesLabelFilter("dog && cat")).Should(BeTrue()) Ω(Label("dog", "cat").MatchesLabelFilter("dog || cat")).Should(BeTrue()) Ω(Label("dog", "cat").MatchesLabelFilter("!fish")).Should(BeTrue()) Ω(Label("dog", "cat").MatchesLabelFilter("fish")).Should(BeFalse()) Ω(Label("dog", "cat").MatchesLabelFilter("!dog")).Should(BeFalse()) Ω(func() { Label("dog", "cat").MatchesLabelFilter("!") }).Should(Panic()) }) }) }) var _ = Describe("Iteration Performance", Serial, Label("performance"), func() { BeforeEach(func() { if os.Getenv("PERF") == "" { Skip("") } }) It("compares the performance of iteration using range vs counters", func() { experiment := gmeasure.NewExperiment("iteration") size := 1000 nodes := make(Nodes, size) for i := 0; i < size; i++ { nodes[i] = N(ntAf) } nodes[size-1] = N(ntIt) experiment.SampleDuration("range", func(idx int) { numIts := 0 for _, node := range nodes { if node.NodeType.Is(ntIt) { numIts += 1 } } }, gmeasure.SamplingConfig{N: 1024}, gmeasure.Precision(time.Nanosecond)) experiment.SampleDuration("range-index", func(idx int) { numIts := 0 for i := range nodes { if nodes[i].NodeType.Is(ntIt) { numIts += 1 } } }, gmeasure.SamplingConfig{N: 1024}, gmeasure.Precision(time.Nanosecond)) experiment.SampleDuration("counter", func(idx int) { numIts := 0 for i := 0; i < len(nodes); i++ { if nodes[i].NodeType.Is(ntIt) { numIts += 1 } } }, gmeasure.SamplingConfig{N: 1024}, gmeasure.Precision(time.Nanosecond)) AddReportEntry(experiment.Name, gmeasure.RankStats(gmeasure.LowerMedianIsBetter, experiment.GetStats("range"), experiment.GetStats("range-index"), experiment.GetStats("counter"))) }) It("compares the performance of slice construction by growing slices vs pre-allocating slices vs counting twice", func() { experiment := gmeasure.NewExperiment("filtering") size := 1000 nodes := make(Nodes, size) for i := 0; i < size; i++ { if i%100 == 0 { nodes[i] = N(ntIt) } else { nodes[i] = N(ntAf) } } largeStats := []gmeasure.Stats{} smallStats := []gmeasure.Stats{} experiment.SampleDuration("grow-slice (large)", func(idx int) { out := Nodes{} for i := range nodes { if nodes[i].NodeType.Is(ntAf) { out = append(out, nodes[i]) } } }, gmeasure.SamplingConfig{N: 1024}, gmeasure.Precision(time.Nanosecond)) largeStats = append(largeStats, experiment.GetStats("grow-slice (large)")) experiment.SampleDuration("grow-slice (small)", func(idx int) { out := Nodes{} for i := range nodes { if nodes[i].NodeType.Is(ntIt) { out = append(out, nodes[i]) } } }, gmeasure.SamplingConfig{N: 1024}, gmeasure.Precision(time.Nanosecond)) smallStats = append(smallStats, experiment.GetStats("grow-slice (small)")) experiment.SampleDuration("pre-allocate (large)", func(idx int) { out := make(Nodes, 0, len(nodes)) for i := range nodes { if nodes[i].NodeType.Is(ntAf) { out = append(out, nodes[i]) } } }, gmeasure.SamplingConfig{N: 1024}, gmeasure.Precision(time.Nanosecond)) largeStats = append(largeStats, experiment.GetStats("pre-allocate (large)")) experiment.SampleDuration("pre-allocate (small)", func(idx int) { out := make(Nodes, 0, len(nodes)) for i := range nodes { if nodes[i].NodeType.Is(ntIt) { out = append(out, nodes[i]) } } }, gmeasure.SamplingConfig{N: 1024}, gmeasure.Precision(time.Nanosecond)) smallStats = append(smallStats, experiment.GetStats("pre-allocate (small)")) experiment.SampleDuration("pre-count (large)", func(idx int) { count := 0 for i := range nodes { if nodes[i].NodeType.Is(ntAf) { count++ } } out := make(Nodes, count) j := 0 for i := range nodes { if nodes[i].NodeType.Is(ntAf) { out[j] = nodes[i] j++ } } }, gmeasure.SamplingConfig{N: 1024}, gmeasure.Precision(time.Nanosecond)) largeStats = append(largeStats, experiment.GetStats("pre-count (large)")) experiment.SampleDuration("pre-count (small)", func(idx int) { count := 0 for i := range nodes { if nodes[i].NodeType.Is(ntIt) { count++ } } out := make(Nodes, count) j := 0 for i := range nodes { if nodes[i].NodeType.Is(ntIt) { out[j] = nodes[i] j++ } } }, gmeasure.SamplingConfig{N: 1024}, gmeasure.Precision(time.Nanosecond)) smallStats = append(smallStats, experiment.GetStats("pre-count (small)")) AddReportEntry("Large Slice", gmeasure.RankStats(gmeasure.LowerMedianIsBetter, largeStats...)) AddReportEntry("Small Slice", gmeasure.RankStats(gmeasure.LowerMedianIsBetter, smallStats...)) }) }) golang-github-onsi-ginkgo-v2-2.22.0/internal/ordering.go000066400000000000000000000155231472321612100230540ustar00rootroot00000000000000package internal import ( "math/rand" "sort" "github.com/onsi/ginkgo/v2/types" ) type SortableSpecs struct { Specs Specs Indexes []int } func NewSortableSpecs(specs Specs) *SortableSpecs { indexes := make([]int, len(specs)) for i := range specs { indexes[i] = i } return &SortableSpecs{ Specs: specs, Indexes: indexes, } } func (s *SortableSpecs) Len() int { return len(s.Indexes) } func (s *SortableSpecs) Swap(i, j int) { s.Indexes[i], s.Indexes[j] = s.Indexes[j], s.Indexes[i] } func (s *SortableSpecs) Less(i, j int) bool { a, b := s.Specs[s.Indexes[i]], s.Specs[s.Indexes[j]] aNodes, bNodes := a.Nodes.WithType(types.NodeTypesForContainerAndIt), b.Nodes.WithType(types.NodeTypesForContainerAndIt) firstOrderedAIdx, firstOrderedBIdx := aNodes.IndexOfFirstNodeMarkedOrdered(), bNodes.IndexOfFirstNodeMarkedOrdered() if firstOrderedAIdx > -1 && firstOrderedBIdx > -1 && aNodes[firstOrderedAIdx].ID == bNodes[firstOrderedBIdx].ID { // strictly preserve order within an ordered containers. ID will track this as IDs are generated monotonically return aNodes.FirstNodeWithType(types.NodeTypeIt).ID < bNodes.FirstNodeWithType(types.NodeTypeIt).ID } // if either spec is in an ordered container - only use the nodes up to the outermost ordered container if firstOrderedAIdx > -1 { aNodes = aNodes[:firstOrderedAIdx+1] } if firstOrderedBIdx > -1 { bNodes = bNodes[:firstOrderedBIdx+1] } for i := 0; i < len(aNodes) && i < len(bNodes); i++ { aCL, bCL := aNodes[i].CodeLocation, bNodes[i].CodeLocation if aCL.FileName != bCL.FileName { return aCL.FileName < bCL.FileName } if aCL.LineNumber != bCL.LineNumber { return aCL.LineNumber < bCL.LineNumber } } // either everything is equal or we have different lengths of CLs if len(aNodes) != len(bNodes) { return len(aNodes) < len(bNodes) } // ok, now we are sure everything was equal. so we use the spec text to break ties for i := 0; i < len(aNodes); i++ { if aNodes[i].Text != bNodes[i].Text { return aNodes[i].Text < bNodes[i].Text } } // ok, all those texts were equal. we'll use the ID of the most deeply nested node as a last resort return aNodes[len(aNodes)-1].ID < bNodes[len(bNodes)-1].ID } type GroupedSpecIndices []SpecIndices type SpecIndices []int func OrderSpecs(specs Specs, suiteConfig types.SuiteConfig) (GroupedSpecIndices, GroupedSpecIndices) { /* Ginkgo has sophisticated support for randomizing specs. Specs are guaranteed to have the same order for a given seed across test runs. By default only top-level containers and specs are shuffled - this makes for a more intuitive debugging experience - specs within a given container run in the order they appear in the file. Developers can set -randomizeAllSpecs to shuffle _all_ specs. In addition, spec containers can be marked as Ordered. Specs within an Ordered container are never shuffled. Finally, specs and spec containers can be marked as Serial. When running in parallel, serial specs run on Process #1 _after_ all other processes have finished. */ // Seed a new random source based on thee configured random seed. r := rand.New(rand.NewSource(suiteConfig.RandomSeed)) // first, we sort the entire suite to ensure a deterministic order. the sort is performed by filename, then line number, and then spec text. this ensures every parallel process has the exact same spec order and is only necessary to cover the edge case where the user iterates over a map to generate specs. sortableSpecs := NewSortableSpecs(specs) sort.Sort(sortableSpecs) // then we break things into execution groups // a group represents a single unit of execution and is a collection of SpecIndices // usually a group is just a single spec, however ordered containers must be preserved as a single group executionGroupIDs := []uint{} executionGroups := map[uint]SpecIndices{} for _, idx := range sortableSpecs.Indexes { spec := specs[idx] groupNode := spec.Nodes.FirstNodeMarkedOrdered() if groupNode.IsZero() { groupNode = spec.Nodes.FirstNodeWithType(types.NodeTypeIt) } executionGroups[groupNode.ID] = append(executionGroups[groupNode.ID], idx) if len(executionGroups[groupNode.ID]) == 1 { executionGroupIDs = append(executionGroupIDs, groupNode.ID) } } // now, we only shuffle all the execution groups if we're randomizing all specs, otherwise // we shuffle outermost containers. so we need to form shufflable groupings of GroupIDs shufflableGroupingIDs := []uint{} shufflableGroupingIDToGroupIDs := map[uint][]uint{} // for each execution group we're going to have to pick a node to represent how the // execution group is grouped for shuffling: nodeTypesToShuffle := types.NodeTypesForContainerAndIt if suiteConfig.RandomizeAllSpecs { nodeTypesToShuffle = types.NodeTypeIt } //so, for each execution group: for _, groupID := range executionGroupIDs { // pick out a representative spec representativeSpec := specs[executionGroups[groupID][0]] // and grab the node on the spec that will represent which shufflable group this execution group belongs tu shufflableGroupingNode := representativeSpec.Nodes.FirstNodeWithType(nodeTypesToShuffle) //add the execution group to its shufflable group shufflableGroupingIDToGroupIDs[shufflableGroupingNode.ID] = append(shufflableGroupingIDToGroupIDs[shufflableGroupingNode.ID], groupID) //and if it's the first one in if len(shufflableGroupingIDToGroupIDs[shufflableGroupingNode.ID]) == 1 { // record the shuffleable group ID shufflableGroupingIDs = append(shufflableGroupingIDs, shufflableGroupingNode.ID) } } // now we permute the sorted shufflable grouping IDs and build the ordered Groups orderedGroups := GroupedSpecIndices{} permutation := r.Perm(len(shufflableGroupingIDs)) for _, j := range permutation { //let's get the execution group IDs for this shufflable group: executionGroupIDsForJ := shufflableGroupingIDToGroupIDs[shufflableGroupingIDs[j]] // and we'll add their associated specindices to the orderedGroups slice: for _, executionGroupID := range executionGroupIDsForJ { orderedGroups = append(orderedGroups, executionGroups[executionGroupID]) } } // If we're running in series, we're done. if suiteConfig.ParallelTotal == 1 { return orderedGroups, GroupedSpecIndices{} } // We're running in parallel so we need to partition the ordered groups into a parallelizable set and a serialized set. // The parallelizable groups will run across all Ginkgo processes... // ...the serial groups will only run on Process #1 after all other processes have exited. parallelizableGroups, serialGroups := GroupedSpecIndices{}, GroupedSpecIndices{} for _, specIndices := range orderedGroups { if specs[specIndices[0]].Nodes.HasNodeMarkedSerial() { serialGroups = append(serialGroups, specIndices) } else { parallelizableGroups = append(parallelizableGroups, specIndices) } } return parallelizableGroups, serialGroups } golang-github-onsi-ginkgo-v2-2.22.0/internal/ordering_test.go000066400000000000000000000320241472321612100241060ustar00rootroot00000000000000package internal_test import ( "strings" "time" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" "github.com/onsi/ginkgo/v2/internal" "github.com/onsi/ginkgo/v2/types" ) type SpecTexts []string func getTexts(specs Specs, groupedSpecIndices internal.GroupedSpecIndices) SpecTexts { out := []string{} for _, specIndices := range groupedSpecIndices { for _, idx := range specIndices { out = append(out, specs[idx].Text()) } } return out } func (tt SpecTexts) Join() string { return strings.Join(tt, "") } var _ = Describe("OrderSpecs", func() { var conf types.SuiteConfig var specs Specs BeforeEach(func() { conf = types.SuiteConfig{} conf.RandomSeed = 1 conf.ParallelTotal = 1 con1 := N(ntCon) con2 := N(ntCon) specs = Specs{ S(N("A", ntIt)), S(N("B", ntIt)), S(con1, N("C", ntIt)), S(con1, N("D", ntIt)), S(con1, N(ntCon), N("E", ntIt)), S(N("F", ntIt)), S(con2, N("G", ntIt)), S(con2, N("H", ntIt)), } }) Context("when configured to only randomize top-level specs", func() { It("shuffles top level specs only", func() { for conf.RandomSeed = 1; conf.RandomSeed < 10; conf.RandomSeed += 1 { groupedSpecIndices, serialSpecIndices := internal.OrderSpecs(specs, conf) Ω(serialSpecIndices).Should(BeEmpty()) Ω(getTexts(specs, groupedSpecIndices).Join()).Should(ContainSubstring("CDE")) Ω(getTexts(specs, groupedSpecIndices).Join()).Should(ContainSubstring("GH")) } conf.RandomSeed = 1 groupedSpecIndices1, _ := internal.OrderSpecs(specs, conf) conf.RandomSeed = 2 groupedSpecIndices2, _ := internal.OrderSpecs(specs, conf) Ω(getTexts(specs, groupedSpecIndices1)).ShouldNot(Equal(getTexts(specs, groupedSpecIndices2))) }) }) Context("when configured to randomize all specs", func() { BeforeEach(func() { conf.RandomizeAllSpecs = true }) It("shuffles all specs", func() { hasCDE := true hasGH := true for conf.RandomSeed = 1; conf.RandomSeed < 10; conf.RandomSeed += 1 { groupedSpecIndices, serialSpecIndices := internal.OrderSpecs(specs, conf) Ω(serialSpecIndices).Should(BeEmpty()) hasCDE, _ = ContainSubstring("CDE").Match(getTexts(specs, groupedSpecIndices).Join()) hasGH, _ = ContainSubstring("GH").Match(getTexts(specs, groupedSpecIndices).Join()) if !hasCDE && !hasGH { break } } Ω(hasCDE || hasGH).Should(BeFalse(), "after 10 randomizations, we really shouldn't have gotten CDE and GH in order as all specs should be shuffled, not just top-level containers and specs") conf.RandomSeed = 1 groupedSpecIndices1, _ := internal.OrderSpecs(specs, conf) conf.RandomSeed = 2 groupedSpecIndices2, _ := internal.OrderSpecs(specs, conf) Ω(getTexts(specs, groupedSpecIndices1)).ShouldNot(Equal(getTexts(specs, groupedSpecIndices2))) }) }) Context("when passed the same seed", func() { It("always generates the same order", func() { for _, conf.RandomizeAllSpecs = range []bool{true, false} { for conf.RandomSeed = 1; conf.RandomSeed < 10; conf.RandomSeed += 1 { groupedSpecIndices, serialSpecIndices := internal.OrderSpecs(specs, conf) Ω(serialSpecIndices).Should(BeEmpty()) for i := 0; i < 10; i++ { reshuffledGroupedSpecIndices, serialSpecIndices := internal.OrderSpecs(specs, conf) Ω(serialSpecIndices).Should(BeEmpty()) Ω(getTexts(specs, groupedSpecIndices)).Should(Equal(getTexts(specs, reshuffledGroupedSpecIndices))) } } } }) }) Context("when specs are in different files and the files are loaded in an undefined order", func() { var specsInFileA, specsInFileB Specs BeforeEach(func() { con1 := N(ntCon, CL("file_A", 10)) specsInFileA = Specs{ S(N("A", ntIt, CL("file_A", 1))), S(N("B", ntIt, CL("file_A", 5))), S(con1, N("C", ntIt, CL("file_A", 15))), S(con1, N("D", ntIt, CL("file_A", 20))), S(con1, N(ntCon, CL("file_A", 25)), N("E", ntIt, CL("file_A", 30))), } con2 := N(ntCon, CL("file_B", 10)) specsInFileB = Specs{ S(N("F", ntIt, CL("file_B", 1))), S(con2, N("G", ntIt, CL("file_B", 15))), S(con2, N("H", ntIt, CL("file_B", 20))), } }) It("always generates a consistent randomization when given the same seed", func() { for _, conf.RandomizeAllSpecs = range []bool{true, false} { for conf.RandomSeed = 1; conf.RandomSeed < 10; conf.RandomSeed += 1 { specsOrderAB := Specs{} specsOrderAB = append(specsOrderAB, specsInFileA...) specsOrderAB = append(specsOrderAB, specsInFileB...) specsOrderBA := Specs{} specsOrderBA = append(specsOrderBA, specsInFileB...) specsOrderBA = append(specsOrderBA, specsInFileA...) groupedSpecIndicesAB, serialSpecIndices := internal.OrderSpecs(specsOrderAB, conf) Ω(serialSpecIndices).Should(BeEmpty()) groupedSpecIndicesBA, serialSpecIndices := internal.OrderSpecs(specsOrderBA, conf) Ω(serialSpecIndices).Should(BeEmpty()) Ω(getTexts(specsOrderAB, groupedSpecIndicesAB)).Should(Equal(getTexts(specsOrderBA, groupedSpecIndicesBA))) } } }) }) Context("when there are ordered specs and randomize-all is true", func() { BeforeEach(func() { con1 := N(ntCon, Ordered) con2 := N(ntCon) specs = Specs{ S(N("A", ntIt)), S(N("B", ntIt)), S(con1, N("C", ntIt)), S(con1, N("D", ntIt)), S(con1, N(ntCon), N("E", ntIt)), S(N("F", ntIt)), S(con2, N("G", ntIt)), S(con2, N("H", ntIt)), } conf.RandomizeAllSpecs = true }) It("never shuffles the specs in ordered specs", func() { for conf.RandomSeed = 1; conf.RandomSeed < 10; conf.RandomSeed += 1 { groupedSpecIndices, serialSpecIndices := internal.OrderSpecs(specs, conf) Ω(serialSpecIndices).Should(BeEmpty()) Ω(getTexts(specs, groupedSpecIndices).Join()).Should(ContainSubstring("CDE")) } }) }) Context("when there are ordered specs and randomize-all is false and everything is in an enclosing container", func() { BeforeEach(func() { con0 := N(ntCon, CL(1)) con1 := N(ntCon, Ordered, CL(4)) con2 := N(ntCon, CL(10)) specs = Specs{ S(con0, N("A", ntIt, CL(2))), S(con0, N("B", ntIt, CL(3))), S(con0, con1, N("C", ntIt, CL(5))), S(con0, con1, N("D", ntIt, CL(6))), S(con0, con1, N(ntCon, CL(7)), N("E", ntIt, CL(8))), S(con0, N("F", ntIt, CL(9))), S(con0, con2, N("G", ntIt, CL(11))), S(con0, con2, N("H", ntIt, CL(12))), } conf.RandomizeAllSpecs = false }) It("runs all the specs in order", func() { for conf.RandomSeed = 1; conf.RandomSeed < 10; conf.RandomSeed += 1 { groupedSpecIndices, serialSpecIndices := internal.OrderSpecs(specs, conf) Ω(serialSpecIndices).Should(BeEmpty()) Ω(getTexts(specs, groupedSpecIndices).Join()).Should(Equal("ABCDEFGH")) } }) }) Context("when there are serial specs", func() { BeforeEach(func() { con1 := N(ntCon, Ordered, Serial) con2 := N(ntCon) specs = Specs{ S(N("A", Serial, ntIt)), S(N("B", ntIt)), S(con1, N("C", ntIt)), S(con1, N("D", ntIt)), S(con1, N(ntCon), N("E", ntIt)), S(N("F", ntIt)), S(con2, N("G", ntIt)), S(con2, N("H", ntIt, Serial)), } conf.RandomizeAllSpecs = true }) Context("and the tests are not running in parallel", func() { BeforeEach(func() { conf.ParallelTotal = 1 }) It("puts all the tests in the parallelizable group and returns an empty serial group", func() { for conf.RandomSeed = 1; conf.RandomSeed < 10; conf.RandomSeed += 1 { groupedSpecIndices, serialSpecIndices := internal.OrderSpecs(specs, conf) Ω(serialSpecIndices).Should(BeEmpty()) Ω(getTexts(specs, groupedSpecIndices).Join()).Should(ContainSubstring("CDE")) Ω(getTexts(specs, groupedSpecIndices)).Should(ConsistOf("A", "B", "C", "D", "E", "F", "G", "H")) } conf.RandomSeed = 1 groupedSpecIndices1, _ := internal.OrderSpecs(specs, conf) conf.RandomSeed = 2 groupedSpecIndices2, _ := internal.OrderSpecs(specs, conf) Ω(getTexts(specs, groupedSpecIndices1)).ShouldNot(Equal(getTexts(specs, groupedSpecIndices2))) }) }) Context("and the tests are running in parallel", func() { BeforeEach(func() { conf.ParallelTotal = 2 }) It("puts all parallelizable tests in the parallelizable group and all serial tests in the serial group, preserving ordered test order", func() { for conf.RandomSeed = 1; conf.RandomSeed < 10; conf.RandomSeed += 1 { groupedSpecIndices, serialSpecIndices := internal.OrderSpecs(specs, conf) Ω(getTexts(specs, groupedSpecIndices)).Should(ConsistOf("B", "F", "G")) Ω(getTexts(specs, serialSpecIndices).Join()).Should(ContainSubstring("CDE")) Ω(getTexts(specs, serialSpecIndices)).Should(ConsistOf("A", "C", "D", "E", "H")) } conf.RandomSeed = 1 groupedSpecIndices1, serialSpecIndices1 := internal.OrderSpecs(specs, conf) conf.RandomSeed = 2 groupedSpecIndices2, serialSpecIndices2 := internal.OrderSpecs(specs, conf) Ω(getTexts(specs, groupedSpecIndices1)).ShouldNot(Equal(getTexts(specs, groupedSpecIndices2))) Ω(getTexts(specs, serialSpecIndices1)).ShouldNot(Equal(getTexts(specs, serialSpecIndices2))) }) }) Describe("presorting-specs", func() { BeforeEach(func() { conA0 := N(ntCon, CL("file-A", 1)) conA1 := N(ntCon, Ordered, CL("file-A", 4)) conA2 := N(ntCon, CL("file-A", 10)) conB0 := N(ntCon, CL("file-B", 1)) conC0 := N(ntCon, CL("file-C", 1)) specs = Specs{ S(conA0, N("A", ntIt, CL("file-A", 2))), S(conA0, N("B", ntIt, CL("file-A", 3))), // C and D are generated by a helper function in a different file. if we aren't careful they would sort after E. But conA1 is an Ordered container so its important things run in the correct order S(conA0, conA1, N("C", ntIt, CL("file-Z", 100))), S(conA0, conA1, N("D", ntIt, CL("file-Z", 99))), S(conA0, conA1, N(ntCon, CL("file-A", 7)), N("E", ntIt, CL("file-A", 8))), S(conA0, N("F", ntIt, CL("file-A", 9))), S(conA0, conA2, N("G", ntIt, CL("file-A", 11))), S(conA0, conA2, N("H", ntIt, CL("file-A", 12))), S(conB0, N("B-Z", ntIt, CL("file-B", 2))), S(conB0, N("B-Y", ntIt, CL("file-B", 3))), S(conB0, N("B-D", ntIt, CL("file-B", 4))), S(conB0, N("B-C", ntIt, CL("file-B", 4))), S(conB0, N("B-B", ntIt, CL("file-B", 4))), S(conB0, N("B-A", ntIt, CL("file-B", 5))), } for key := range map[string]bool{"C-A": true, "C-B": true, "C-C": true, "C-D": true, "C-E": true, "C-F": true} { specs = append(specs, S(conC0, N(key, ntIt, CL("file-C", 2)))) // normally this would be totally non-deterministic } conf.RandomizeAllSpecs = false }) It("ensures a deterministic order for specs that are defined at the same line without messing with the natural order of specs and containers; it also ensures ordered containers run in the correct order - even if specs are generated in a helper function at a different line", func() { conf.RandomSeed = 1 // this happens to sort conA0 ahead of conB0 - other than that, though, we are actually testing SortableSpecs groupedSpecIndices, serialSpecIndices := internal.OrderSpecs(specs, conf) Ω(serialSpecIndices).Should(BeEmpty()) Ω(getTexts(specs, groupedSpecIndices).Join()).Should(Equal("ABCDEFGHB-ZB-YB-BB-CB-DB-AC-AC-BC-CC-DC-EC-F")) }) }) Describe("presorting-specs with randomize-all enabled", func() { generateSpecs := func() Specs { conA0 := N(ntCon, CL("file-A", 1)) conA1 := N(ntCon, CL("file-A", 4)) conA2 := N(ntCon, CL("file-A", 10)) conB0 := N(ntCon, CL("file-B", 1)) conC0 := N(ntCon, CL("file-C", 1)) specs := Specs{ S(conA0, N("A", ntIt, CL("file-A", 2))), S(conA0, N("B", ntIt, CL("file-A", 3))), S(conA0, conA1, N("C", ntIt, CL("file-A", 5))), S(conA0, conA1, N("D", ntIt, CL("file-A", 6))), S(conA0, conA1, N(ntCon, CL("file-A", 7)), N("E", ntIt, CL("file-A", 8))), S(conA0, N("F", ntIt, CL("file-A", 9))), S(conA0, conA2, N("G", ntIt, CL("file-A", 11))), S(conA0, conA2, N("H", ntIt, CL("file-A", 12))), S(conB0, N("B-Z", ntIt, CL("file-B", 2))), S(conB0, N("B-Y", ntIt, CL("file-B", 3))), S(conB0, N("B-D", ntIt, CL("file-B", 4))), S(conB0, N("B-C", ntIt, CL("file-B", 4))), S(conB0, N("B-B", ntIt, CL("file-B", 4))), S(conB0, N("B-A", ntIt, CL("file-B", 5))), } for key := range map[string]bool{"C-A": true, "C-B": true, "C-C": true, "C-D": true, "C-E": true, "C-F": true} { specs = append(specs, S(conC0, N(key, ntIt, CL("file-C", 2)))) // normally this would be totally non-deterministic } return specs } It("ensures a deterministic order for specs that are defined at the same line", func() { conf.RandomSeed = time.Now().Unix() conf.RandomizeAllSpecs = true specsA := generateSpecs() specsB := generateSpecs() groupedSpecIndicesA, serialSpecIndices := internal.OrderSpecs(specsA, conf) Ω(serialSpecIndices).Should(BeEmpty()) groupedSpecIndicesB, serialSpecIndices := internal.OrderSpecs(specsB, conf) Ω(serialSpecIndices).Should(BeEmpty()) Ω(getTexts(specsA, groupedSpecIndicesA).Join()).Should(Equal(getTexts(specsB, groupedSpecIndicesB).Join())) }, MustPassRepeatedly(5)) }) }) }) golang-github-onsi-ginkgo-v2-2.22.0/internal/output_interceptor.go000066400000000000000000000217011472321612100252140ustar00rootroot00000000000000package internal import ( "bytes" "io" "os" "time" ) const BAILOUT_TIME = 1 * time.Second const BAILOUT_MESSAGE = `Ginkgo detected an issue while intercepting output. When running in parallel, Ginkgo captures stdout and stderr output and attaches it to the running spec. It looks like that process is getting stuck for this suite. This usually happens if you, or a library you are using, spin up an external process and set cmd.Stdout = os.Stdout and/or cmd.Stderr = os.Stderr. This causes the external process to keep Ginkgo's output interceptor pipe open and causes output interception to hang. Ginkgo has detected this and shortcircuited the capture process. The specs will continue running after this message however output from the external process that caused this issue will not be captured. You have several options to fix this. In preferred order they are: 1. Pass GinkgoWriter instead of os.Stdout or os.Stderr to your process. 2. Ensure your process exits before the current spec completes. If your process is long-lived and must cross spec boundaries, this option won't work for you. 3. Pause Ginkgo's output interceptor before starting your process and then resume it after. Use PauseOutputInterception() and ResumeOutputInterception() to do this. 4. Set --output-interceptor-mode=none when running your Ginkgo suite. This will turn off all output interception but allow specs to run in parallel without this issue. You may miss important output if you do this including output from Go's race detector. More details on issue #851 - https://github.com/onsi/ginkgo/issues/851 ` /* The OutputInterceptor is used by to intercept and capture all stdin and stderr output during a test run. */ type OutputInterceptor interface { StartInterceptingOutput() StartInterceptingOutputAndForwardTo(io.Writer) StopInterceptingAndReturnOutput() string PauseIntercepting() ResumeIntercepting() Shutdown() } type NoopOutputInterceptor struct{} func (interceptor NoopOutputInterceptor) StartInterceptingOutput() {} func (interceptor NoopOutputInterceptor) StartInterceptingOutputAndForwardTo(io.Writer) {} func (interceptor NoopOutputInterceptor) StopInterceptingAndReturnOutput() string { return "" } func (interceptor NoopOutputInterceptor) PauseIntercepting() {} func (interceptor NoopOutputInterceptor) ResumeIntercepting() {} func (interceptor NoopOutputInterceptor) Shutdown() {} type pipePair struct { reader *os.File writer *os.File } func startPipeFactory(pipeChannel chan pipePair, shutdown chan interface{}) { for { //make the next pipe... pair := pipePair{} pair.reader, pair.writer, _ = os.Pipe() select { //...and provide it to the next consumer (they are responsible for closing the files) case pipeChannel <- pair: continue //...or close the files if we were told to shutdown case <-shutdown: pair.reader.Close() pair.writer.Close() return } } } type interceptorImplementation interface { CreateStdoutStderrClones() (*os.File, *os.File) ConnectPipeToStdoutStderr(*os.File) RestoreStdoutStderrFromClones(*os.File, *os.File) ShutdownClones(*os.File, *os.File) } type genericOutputInterceptor struct { intercepting bool stdoutClone *os.File stderrClone *os.File pipe pipePair shutdown chan interface{} emergencyBailout chan interface{} pipeChannel chan pipePair interceptedContent chan string forwardTo io.Writer accumulatedOutput string implementation interceptorImplementation } func (interceptor *genericOutputInterceptor) StartInterceptingOutput() { interceptor.StartInterceptingOutputAndForwardTo(io.Discard) } func (interceptor *genericOutputInterceptor) StartInterceptingOutputAndForwardTo(w io.Writer) { if interceptor.intercepting { return } interceptor.accumulatedOutput = "" interceptor.forwardTo = w interceptor.ResumeIntercepting() } func (interceptor *genericOutputInterceptor) StopInterceptingAndReturnOutput() string { if interceptor.intercepting { interceptor.PauseIntercepting() } return interceptor.accumulatedOutput } func (interceptor *genericOutputInterceptor) ResumeIntercepting() { if interceptor.intercepting { return } interceptor.intercepting = true if interceptor.stdoutClone == nil { interceptor.stdoutClone, interceptor.stderrClone = interceptor.implementation.CreateStdoutStderrClones() interceptor.shutdown = make(chan interface{}) 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 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 interceptor.emergencyBailout = make(chan interface{}) //Spin up a goroutine to copy data from the pipe into a buffer, this is how we capture any output the user is emitting go func() { buffer := &bytes.Buffer{} destination := io.MultiWriter(buffer, interceptor.forwardTo) copyFinished := make(chan interface{}) reader := interceptor.pipe.reader go func() { io.Copy(destination, reader) reader.Close() // close the read end of the pipe so we don't leak a file descriptor close(copyFinished) }() select { case <-copyFinished: interceptor.interceptedContent <- buffer.String() case <-interceptor.emergencyBailout: interceptor.interceptedContent <- "" } }() interceptor.implementation.ConnectPipeToStdoutStderr(interceptor.pipe.writer) } func (interceptor *genericOutputInterceptor) PauseIntercepting() { if !interceptor.intercepting { return } // first we have to close the write end of the pipe. To do this we have to close all file descriptors pointing // to the write end. So that would be the pipewriter itself, and FD #1 and FD #2 if we've Dup2'd them interceptor.pipe.writer.Close() // the pipewriter itself // we also need to stop intercepting. we do that by reconnecting the stdout and stderr file descriptions back to their respective #1 and #2 file descriptors; // this also closes #1 and #2 before it points that their original stdout and stderr file descriptions interceptor.implementation.RestoreStdoutStderrFromClones(interceptor.stdoutClone, interceptor.stderrClone) var content string select { case content = <-interceptor.interceptedContent: case <-time.After(BAILOUT_TIME): /* By closing all the pipe writer's file descriptors associated with the pipe writer's file description the io.Copy reading from the reader should eventually receive an EOF and exit. **However**, if the user has spun up an external process and passed in os.Stdout/os.Stderr to cmd.Stdout/cmd.Stderr then the external process will have a file descriptor pointing to the pipe writer's file description and it will not close until the external process exits. That would leave us hanging here waiting for the io.Copy to close forever. Instead we invoke this emergency escape valve. This returns whatever content we've got but leaves the io.Copy running. This ensures the external process can continue writing without hanging at the cost of leaking a goroutine and file descriptor (those these will be cleaned up when the process exits). We tack on a message to notify the user that they've hit this edgecase and encourage them to address it. */ close(interceptor.emergencyBailout) content = <-interceptor.interceptedContent + BAILOUT_MESSAGE } interceptor.accumulatedOutput += content interceptor.intercepting = false } func (interceptor *genericOutputInterceptor) Shutdown() { interceptor.PauseIntercepting() if interceptor.stdoutClone != nil { close(interceptor.shutdown) interceptor.implementation.ShutdownClones(interceptor.stdoutClone, interceptor.stderrClone) interceptor.stdoutClone = nil interceptor.stderrClone = nil } } /* This is used on windows builds but included here so it can be explicitly tested on unix systems too */ func NewOSGlobalReassigningOutputInterceptor() OutputInterceptor { return &genericOutputInterceptor{ interceptedContent: make(chan string), pipeChannel: make(chan pipePair), shutdown: make(chan interface{}), implementation: &osGlobalReassigningOutputInterceptorImpl{}, } } type osGlobalReassigningOutputInterceptorImpl struct{} func (impl *osGlobalReassigningOutputInterceptorImpl) CreateStdoutStderrClones() (*os.File, *os.File) { return os.Stdout, os.Stderr } func (impl *osGlobalReassigningOutputInterceptorImpl) ConnectPipeToStdoutStderr(pipeWriter *os.File) { os.Stdout = pipeWriter os.Stderr = pipeWriter } func (impl *osGlobalReassigningOutputInterceptorImpl) RestoreStdoutStderrFromClones(stdoutClone *os.File, stderrClone *os.File) { os.Stdout = stdoutClone os.Stderr = stderrClone } func (impl *osGlobalReassigningOutputInterceptorImpl) ShutdownClones(_ *os.File, _ *os.File) { //noop } golang-github-onsi-ginkgo-v2-2.22.0/internal/output_interceptor_test.go000066400000000000000000000125011472321612100262510ustar00rootroot00000000000000package internal_test import ( "fmt" "os" "os/exec" "runtime" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" "github.com/onsi/gomega/gbytes" "github.com/onsi/ginkgo/v2/internal" ) var _ = Describe("OutputInterceptor", func() { var interceptor internal.OutputInterceptor sharedInterceptorTests := func() { It("intercepts output", func() { for i := 0; i < 2048; i++ { //we loop here to stress test and make sure we aren't leaking any file descriptors interceptor.StartInterceptingOutput() fmt.Println("hi stdout") fmt.Fprintln(os.Stderr, "hi stderr") output := interceptor.StopInterceptingAndReturnOutput() Ω(output).Should(Equal("hi stdout\nhi stderr\n")) } }) It("can forward intercepted output to a buffer", func() { buffer := gbytes.NewBuffer() interceptor.StartInterceptingOutputAndForwardTo(buffer) fmt.Println("hi stdout") fmt.Fprintln(os.Stderr, "hi stderr") output := interceptor.StopInterceptingAndReturnOutput() Ω(output).Should(Equal("hi stdout\nhi stderr\n")) Ω(buffer).Should(gbytes.Say("hi stdout\nhi stderr\n")) }) It("is stable across multiple shutdowns", func() { numRoutines := runtime.NumGoroutine() for i := 0; i < 2048; i++ { //we loop here to stress test and make sure we aren't leaking any file descriptors interceptor.StartInterceptingOutput() fmt.Println("hi stdout") fmt.Fprintln(os.Stderr, "hi stderr") output := interceptor.StopInterceptingAndReturnOutput() Ω(output).Should(Equal("hi stdout\nhi stderr\n")) interceptor.Shutdown() } Eventually(runtime.NumGoroutine).Should(BeNumerically("~", numRoutines, 10)) }) It("can bail out if stdout and stderr are tied up by an external process", func() { // See GitHub issue #851: https://github.com/onsi/ginkgo/issues/851 interceptor.StartInterceptingOutput() cmd := exec.Command("sleep", "60") //by threading stdout and stderr through, the sleep process will hold them open and prevent the interceptor from stopping: cmd.Stdout, cmd.Stderr = os.Stdout, os.Stderr Ω(cmd.Start()).Should(Succeed()) fmt.Println("hi stdout") fmt.Fprintln(os.Stderr, "hi stderr") // we try to stop here and see that we bail out eventually: outputChan := make(chan string) go func() { outputChan <- interceptor.StopInterceptingAndReturnOutput() }() var output string Eventually(outputChan, internal.BAILOUT_TIME*2).Should(Receive(&output)) Ω(output).Should(Equal(internal.BAILOUT_MESSAGE)) //subsequent attempts should be fine interceptor.StartInterceptingOutput() fmt.Println("hi stdout, again") fmt.Fprintln(os.Stderr, "hi stderr, again") output = interceptor.StopInterceptingAndReturnOutput() Ω(output).Should(Equal("hi stdout, again\nhi stderr, again\n")) cmd.Process.Kill() interceptor.StartInterceptingOutput() fmt.Println("hi stdout, once more") fmt.Fprintln(os.Stderr, "hi stderr, once more") output = interceptor.StopInterceptingAndReturnOutput() Ω(output).Should(Equal("hi stdout, once more\nhi stderr, once more\n")) }) It("doesn't get stuck if it's paused and resumed before starting an external process that attaches to stdout/stderr", func() { // See GitHub issue #851: https://github.com/onsi/ginkgo/issues/851 interceptor.StartInterceptingOutput() interceptor.PauseIntercepting() cmd := exec.Command("sleep", "60") //by threading stdout and stderr through, the sleep process will hold them open and prevent the interceptor from stopping: cmd.Stdout, cmd.Stderr = os.Stdout, os.Stderr Ω(cmd.Start()).Should(Succeed()) interceptor.ResumeIntercepting() fmt.Println("hi stdout") fmt.Fprintln(os.Stderr, "hi stderr") output := interceptor.StopInterceptingAndReturnOutput() Ω(output).Should(Equal("hi stdout\nhi stderr\n")) Ω(output).ShouldNot(ContainSubstring(internal.BAILOUT_MESSAGE)) cmd.Process.Kill() }) It("can start/stop/pause/resume correctly", func() { interceptor.StartInterceptingOutput() fmt.Fprint(os.Stdout, "O-A") fmt.Fprint(os.Stderr, "E-A") interceptor.PauseIntercepting() fmt.Fprint(os.Stdout, "O-B") fmt.Fprint(os.Stderr, "E-B") interceptor.ResumeIntercepting() fmt.Fprint(os.Stdout, "O-C") fmt.Fprint(os.Stderr, "E-C") interceptor.ResumeIntercepting() //noop fmt.Fprint(os.Stdout, "O-D") fmt.Fprint(os.Stderr, "E-D") interceptor.PauseIntercepting() fmt.Fprint(os.Stdout, "O-E") fmt.Fprint(os.Stderr, "E-E") interceptor.PauseIntercepting() //noop fmt.Fprint(os.Stdout, "O-F") fmt.Fprint(os.Stderr, "E-F") interceptor.ResumeIntercepting() fmt.Fprint(os.Stdout, "O-G") fmt.Fprint(os.Stderr, "E-G") interceptor.StartInterceptingOutput() //noop fmt.Fprint(os.Stdout, "O-H") fmt.Fprint(os.Stderr, "E-H") interceptor.PauseIntercepting() output := interceptor.StopInterceptingAndReturnOutput() Ω(output).Should(Equal("O-AE-AO-CE-CO-DE-DO-GE-GO-HE-H")) }) } Context("the OutputInterceptor for this OS", func() { BeforeEach(func() { interceptor = internal.NewOutputInterceptor() DeferCleanup(interceptor.Shutdown) }) sharedInterceptorTests() }) Context("the OSGlobalReassigningOutputInterceptor used on windows", func() { BeforeEach(func() { interceptor = internal.NewOSGlobalReassigningOutputInterceptor() DeferCleanup(interceptor.Shutdown) }) sharedInterceptorTests() }) }) golang-github-onsi-ginkgo-v2-2.22.0/internal/output_interceptor_unix.go000066400000000000000000000061271472321612100262640ustar00rootroot00000000000000//go:build freebsd || openbsd || netbsd || dragonfly || darwin || linux || solaris // +build freebsd openbsd netbsd dragonfly darwin linux solaris package internal import ( "os" "golang.org/x/sys/unix" ) func NewOutputInterceptor() OutputInterceptor { return &genericOutputInterceptor{ interceptedContent: make(chan string), pipeChannel: make(chan pipePair), shutdown: make(chan interface{}), implementation: &dupSyscallOutputInterceptorImpl{}, } } type dupSyscallOutputInterceptorImpl struct{} func (impl *dupSyscallOutputInterceptorImpl) CreateStdoutStderrClones() (*os.File, *os.File) { // To clone stdout and stderr we: // First, create two clone file descriptors that point to the stdout and stderr file descriptions stdoutCloneFD, _ := unix.Dup(1) stderrCloneFD, _ := unix.Dup(2) // Important, set the fds to FD_CLOEXEC to prevent them leaking into childs // https://github.com/onsi/ginkgo/issues/1191 flags, err := unix.FcntlInt(uintptr(stdoutCloneFD), unix.F_GETFD, 0) if err == nil { unix.FcntlInt(uintptr(stdoutCloneFD), unix.F_SETFD, flags|unix.FD_CLOEXEC) } flags, err = unix.FcntlInt(uintptr(stderrCloneFD), unix.F_GETFD, 0) if err == nil { unix.FcntlInt(uintptr(stderrCloneFD), unix.F_SETFD, flags|unix.FD_CLOEXEC) } // 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 even though we're intercepting output stdoutClone := os.NewFile(uintptr(stdoutCloneFD), "stdout-clone") stderrClone := os.NewFile(uintptr(stderrCloneFD), "stderr-clone") //these clones remain alive throughout the lifecycle of the suite and don't need to be recreated //this speeds things up a bit, actually. return stdoutClone, stderrClone } func (impl *dupSyscallOutputInterceptorImpl) ConnectPipeToStdoutStderr(pipeWriter *os.File) { // To redirect output to our pipe we need to point the 1 and 2 file descriptors (which is how the world tries to log things) // to the write end of the pipe. // We do this with Dup2 (possibly Dup3 on some architectures) to have file descriptors 1 and 2 point to the same file description as the pipeWriter // This effectively shunts data written to stdout and stderr to the write end of our pipe unix.Dup2(int(pipeWriter.Fd()), 1) unix.Dup2(int(pipeWriter.Fd()), 2) } func (impl *dupSyscallOutputInterceptorImpl) RestoreStdoutStderrFromClones(stdoutClone *os.File, stderrClone *os.File) { // To restore stdour/stderr from the clones we have the 1 and 2 file descriptors // point to the original file descriptions that we saved off in the clones. // This has the added benefit of closing the connection between these descriptors and the write end of the pipe // which is important to cause the io.Copy on the pipe.Reader to end. unix.Dup2(int(stdoutClone.Fd()), 1) unix.Dup2(int(stderrClone.Fd()), 2) } func (impl *dupSyscallOutputInterceptorImpl) ShutdownClones(stdoutClone *os.File, stderrClone *os.File) { // We're done with the clones so we can close them to clean up after ourselves stdoutClone.Close() stderrClone.Close() } golang-github-onsi-ginkgo-v2-2.22.0/internal/output_interceptor_wasm.go000066400000000000000000000001661472321612100262450ustar00rootroot00000000000000//go:build wasm package internal func NewOutputInterceptor() OutputInterceptor { return &NoopOutputInterceptor{} } golang-github-onsi-ginkgo-v2-2.22.0/internal/output_interceptor_win.go000066400000000000000000000002111472321612100260620ustar00rootroot00000000000000// +build windows package internal func NewOutputInterceptor() OutputInterceptor { return NewOSGlobalReassigningOutputInterceptor() } golang-github-onsi-ginkgo-v2-2.22.0/internal/parallel_support/000077500000000000000000000000001472321612100242765ustar00rootroot00000000000000golang-github-onsi-ginkgo-v2-2.22.0/internal/parallel_support/client_server.go000066400000000000000000000034101472321612100274670ustar00rootroot00000000000000package parallel_support import ( "fmt" "io" "os" "time" "github.com/onsi/ginkgo/v2/reporters" "github.com/onsi/ginkgo/v2/types" ) type BeforeSuiteState struct { Data []byte State types.SpecState } type ParallelIndexCounter struct { Index int } var ErrorGone = fmt.Errorf("gone") var ErrorFailed = fmt.Errorf("failed") var ErrorEarly = fmt.Errorf("early") var POLLING_INTERVAL = 50 * time.Millisecond type Server interface { Start() Close() Address() string RegisterAlive(node int, alive func() bool) GetSuiteDone() chan interface{} GetOutputDestination() io.Writer SetOutputDestination(io.Writer) } type Client interface { Connect() bool Close() error PostSuiteWillBegin(report types.Report) error PostDidRun(report types.SpecReport) error PostSuiteDidEnd(report types.Report) error PostReportBeforeSuiteCompleted(state types.SpecState) error BlockUntilReportBeforeSuiteCompleted() (types.SpecState, error) PostSynchronizedBeforeSuiteCompleted(state types.SpecState, data []byte) error BlockUntilSynchronizedBeforeSuiteData() (types.SpecState, []byte, error) BlockUntilNonprimaryProcsHaveFinished() error BlockUntilAggregatedNonprimaryProcsReport() (types.Report, error) FetchNextCounter() (int, error) PostAbort() error ShouldAbort() bool PostEmitProgressReport(report types.ProgressReport) error Write(p []byte) (int, error) } func NewServer(parallelTotal int, reporter reporters.Reporter) (Server, error) { if os.Getenv("GINKGO_PARALLEL_PROTOCOL") == "HTTP" { return newHttpServer(parallelTotal, reporter) } else { return newRPCServer(parallelTotal, reporter) } } func NewClient(serverHost string) Client { if os.Getenv("GINKGO_PARALLEL_PROTOCOL") == "HTTP" { return newHttpClient(serverHost) } else { return newRPCClient(serverHost) } } golang-github-onsi-ginkgo-v2-2.22.0/internal/parallel_support/client_server_test.go000066400000000000000000000403421472321612100305330ustar00rootroot00000000000000package parallel_support_test import ( "fmt" "os" "time" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" "github.com/onsi/gomega/gbytes" "github.com/onsi/ginkgo/v2/internal" "github.com/onsi/ginkgo/v2/internal/parallel_support" . "github.com/onsi/ginkgo/v2/internal/test_helpers" "github.com/onsi/ginkgo/v2/types" ) type ColorableStringerStruct struct { Label string Count int } func (s ColorableStringerStruct) String() string { return fmt.Sprintf("%s %d", s.Label, s.Count) } func (s ColorableStringerStruct) ColorableString() string { return fmt.Sprintf("{{red}}%s {{green}}%d{{/}}", s.Label, s.Count) } var _ = Describe("The Parallel Support Client & Server", func() { for _, protocol := range []string{"RPC", "HTTP"} { protocol := protocol Describe(fmt.Sprintf("The %s protocol", protocol), Label(protocol), func() { var ( server parallel_support.Server client parallel_support.Client reporter *FakeReporter buffer *gbytes.Buffer ) BeforeEach(func() { GinkgoT().Setenv("GINKGO_PARALLEL_PROTOCOL", protocol) var err error reporter = NewFakeReporter() server, err = parallel_support.NewServer(3, reporter) Ω(err).ShouldNot(HaveOccurred()) server.Start() buffer = gbytes.NewBuffer() server.SetOutputDestination(buffer) client = parallel_support.NewClient(server.Address()) Eventually(client.Connect).Should(BeTrue()) DeferCleanup(server.Close) DeferCleanup(client.Close) }) Describe("Reporting endpoints", func() { var beginReport, thirdBeginReport types.Report var endReport1, endReport2, endReport3 types.Report var specReportA, specReportB, specReportC types.SpecReport var t time.Time BeforeEach(func() { beginReport = types.Report{SuiteDescription: "my sweet suite"} thirdBeginReport = types.Report{SuiteDescription: "last one in gets forwarded"} specReportA = types.SpecReport{LeafNodeText: "A"} specReportB = types.SpecReport{LeafNodeText: "B"} specReportC = types.SpecReport{LeafNodeText: "C"} t = time.Now() endReport1 = types.Report{StartTime: t.Add(-time.Second), EndTime: t.Add(time.Second), SuiteSucceeded: true, SpecReports: types.SpecReports{specReportA}} endReport2 = types.Report{StartTime: t.Add(-2 * time.Second), EndTime: t.Add(time.Second), SuiteSucceeded: true, SpecReports: types.SpecReports{specReportB}} endReport3 = types.Report{StartTime: t.Add(-time.Second), EndTime: t.Add(2 * time.Second), SuiteSucceeded: false, SpecReports: types.SpecReports{specReportC}} }) Context("before all procs have reported SuiteWillBegin", func() { BeforeEach(func() { Ω(client.PostSuiteWillBegin(beginReport)).Should(Succeed()) Ω(client.PostDidRun(specReportA)).Should(Succeed()) Ω(client.PostSuiteWillBegin(beginReport)).Should(Succeed()) Ω(client.PostDidRun(specReportB)).Should(Succeed()) }) It("should not forward anything to the attached reporter", func() { Ω(reporter.Begin).Should(BeZero()) Ω(reporter.Will).Should(BeEmpty()) Ω(reporter.Did).Should(BeEmpty()) }) Context("when the final proc reports SuiteWillBegin", func() { BeforeEach(func() { Ω(client.PostSuiteWillBegin(thirdBeginReport)).Should(Succeed()) }) It("forwards to SuiteWillBegin and catches up on any received summaries", func() { Ω(reporter.Begin).Should(Equal(thirdBeginReport)) Ω(reporter.Will.Names()).Should(ConsistOf("A", "B")) Ω(reporter.Did.Names()).Should(ConsistOf("A", "B")) }) Context("any subsequent summaries", func() { BeforeEach(func() { Ω(client.PostDidRun(specReportC)).Should(Succeed()) }) It("are forwarded immediately", func() { Ω(reporter.Will.Names()).Should(ConsistOf("A", "B", "C")) Ω(reporter.Did.Names()).Should(ConsistOf("A", "B", "C")) }) }) Context("when SuiteDidEnd start arriving", func() { BeforeEach(func() { Ω(client.PostSuiteDidEnd(endReport1)).Should(Succeed()) Ω(client.PostSuiteDidEnd(endReport2)).Should(Succeed()) }) It("does not forward them yet...", func() { Ω(reporter.End).Should(BeZero()) }) It("doesn't signal it's done", func() { Ω(server.GetSuiteDone()).ShouldNot(BeClosed()) }) Context("when the final SuiteDidEnd arrive", func() { BeforeEach(func() { Ω(client.PostSuiteDidEnd(endReport3)).Should(Succeed()) }) It("forwards the aggregation of all received end summaries", func() { Ω(reporter.End.StartTime.Unix()).Should(BeNumerically("~", t.Add(-2*time.Second).Unix())) Ω(reporter.End.EndTime.Unix()).Should(BeNumerically("~", t.Add(2*time.Second).Unix())) Ω(reporter.End.RunTime).Should(BeNumerically("~", 4*time.Second)) Ω(reporter.End.SuiteSucceeded).Should(BeFalse()) Ω(reporter.End.SpecReports).Should(ConsistOf(specReportA, specReportB, specReportC)) }) It("should signal it's done", func() { Ω(server.GetSuiteDone()).Should(BeClosed()) }) }) }) }) }) }) Describe("supporting ReportEntries (which RPC struggled with when I first implemented it)", func() { BeforeEach(func() { Ω(client.PostSuiteWillBegin(types.Report{SuiteDescription: "my sweet suite"})).Should(Succeed()) Ω(client.PostSuiteWillBegin(types.Report{SuiteDescription: "my sweet suite"})).Should(Succeed()) Ω(client.PostSuiteWillBegin(types.Report{SuiteDescription: "my sweet suite"})).Should(Succeed()) }) It("can pass in ReportEntries that include custom types", func() { cl := types.NewCodeLocation(0) entry, err := internal.NewReportEntry("No Value Entry", cl) Ω(err).ShouldNot(HaveOccurred()) Ω(client.PostDidRun(types.SpecReport{ LeafNodeText: "no-value", ReportEntries: types.ReportEntries{entry}, })).Should(Succeed()) entry, err = internal.NewReportEntry("String Value Entry", cl, "The String") Ω(err).ShouldNot(HaveOccurred()) Ω(client.PostDidRun(types.SpecReport{ LeafNodeText: "string-value", ReportEntries: types.ReportEntries{entry}, })).Should(Succeed()) entry, err = internal.NewReportEntry("Custom Type Value Entry", cl, ColorableStringerStruct{Label: "apples", Count: 17}) Ω(err).ShouldNot(HaveOccurred()) Ω(client.PostDidRun(types.SpecReport{ LeafNodeText: "custom-value", ReportEntries: types.ReportEntries{entry}, })).Should(Succeed()) Ω(reporter.Did.Find("no-value").ReportEntries[0].Name).Should(Equal("No Value Entry")) Ω(reporter.Did.Find("no-value").ReportEntries[0].StringRepresentation()).Should(Equal("")) Ω(reporter.Did.Find("string-value").ReportEntries[0].Name).Should(Equal("String Value Entry")) Ω(reporter.Did.Find("string-value").ReportEntries[0].StringRepresentation()).Should(Equal("The String")) Ω(reporter.Did.Find("custom-value").ReportEntries[0].Name).Should(Equal("Custom Type Value Entry")) Ω(reporter.Did.Find("custom-value").ReportEntries[0].StringRepresentation()).Should(Equal("{{red}}apples {{green}}17{{/}}")) }) }) Describe("Streaming output", func() { It("is configured to stream to stdout", func() { server, err := parallel_support.NewServer(3, reporter) Ω(err).ShouldNot(HaveOccurred()) Ω(server.GetOutputDestination().(*os.File).Fd()).Should(Equal(uintptr(1))) }) It("streams output to the provided buffer", func() { n, err := client.Write([]byte("hello")) Ω(n).Should(Equal(5)) Ω(err).ShouldNot(HaveOccurred()) Ω(buffer).Should(gbytes.Say("hello")) }) }) Describe("progress reports", func() { It("can emit progress reports", func() { pr := types.ProgressReport{LeafNodeText: "hola"} Ω(client.PostEmitProgressReport(pr)).Should(Succeed()) Ω(reporter.ProgressReports).Should(ConsistOf(pr)) }) }) Describe("Synchronization endpoints", func() { var proc1Exited, proc2Exited, proc3Exited chan interface{} BeforeEach(func() { proc1Exited, proc2Exited, proc3Exited = make(chan interface{}), make(chan interface{}), make(chan interface{}) aliveFunc := func(c chan interface{}) func() bool { return func() bool { select { case <-c: return false default: return true } } } server.RegisterAlive(1, aliveFunc(proc1Exited)) server.RegisterAlive(2, aliveFunc(proc2Exited)) server.RegisterAlive(3, aliveFunc(proc3Exited)) }) Describe("Managing ReportBeforeSuite synchronization", func() { Context("when proc 1 succeeds", func() { It("passes that success along to other procs", func() { Ω(client.PostReportBeforeSuiteCompleted(types.SpecStatePassed)).Should(Succeed()) state, err := client.BlockUntilReportBeforeSuiteCompleted() Ω(state).Should(Equal(types.SpecStatePassed)) Ω(err).ShouldNot(HaveOccurred()) }) }) Context("when proc 1 fails", func() { It("passes that state information along to the other procs", func() { Ω(client.PostReportBeforeSuiteCompleted(types.SpecStateFailed)).Should(Succeed()) state, err := client.BlockUntilReportBeforeSuiteCompleted() Ω(state).Should(Equal(types.SpecStateFailed)) Ω(err).ShouldNot(HaveOccurred()) }) }) Context("when proc 1 disappears before reporting back", func() { It("returns a meaningful error", func() { close(proc1Exited) state, err := client.BlockUntilReportBeforeSuiteCompleted() Ω(state).Should(Equal(types.SpecStateFailed)) Ω(err).ShouldNot(HaveOccurred()) }) }) Context("when proc 1 hasn't responded yet", func() { It("blocks until it does", func() { done := make(chan interface{}) go func() { defer GinkgoRecover() state, err := client.BlockUntilReportBeforeSuiteCompleted() Ω(state).Should(Equal(types.SpecStatePassed)) Ω(err).ShouldNot(HaveOccurred()) close(done) }() Consistently(done).ShouldNot(BeClosed()) Ω(client.PostReportBeforeSuiteCompleted(types.SpecStatePassed)).Should(Succeed()) Eventually(done).Should(BeClosed()) }) }) }) Describe("Managing SynchronizedBeforeSuite synchronization", func() { Context("when proc 1 succeeds and returns data", func() { It("passes that data along to other procs", func() { Ω(client.PostSynchronizedBeforeSuiteCompleted(types.SpecStatePassed, []byte("hello there"))).Should(Succeed()) state, data, err := client.BlockUntilSynchronizedBeforeSuiteData() Ω(state).Should(Equal(types.SpecStatePassed)) Ω(data).Should(Equal([]byte("hello there"))) Ω(err).ShouldNot(HaveOccurred()) }) }) Context("when proc 1 succeeds and the data happens to be nil", func() { It("passes reports success and returns nil", func() { Ω(client.PostSynchronizedBeforeSuiteCompleted(types.SpecStatePassed, nil)).Should(Succeed()) state, data, err := client.BlockUntilSynchronizedBeforeSuiteData() Ω(state).Should(Equal(types.SpecStatePassed)) Ω(data).Should(BeNil()) Ω(err).ShouldNot(HaveOccurred()) }) }) Context("when proc 1 is skipped", func() { It("passes that state information along to the other procs", func() { Ω(client.PostSynchronizedBeforeSuiteCompleted(types.SpecStateSkipped, nil)).Should(Succeed()) state, data, err := client.BlockUntilSynchronizedBeforeSuiteData() Ω(state).Should(Equal(types.SpecStateSkipped)) Ω(data).Should(BeNil()) Ω(err).ShouldNot(HaveOccurred()) }) }) Context("when proc 1 fails", func() { It("passes that state information along to the other procs", func() { Ω(client.PostSynchronizedBeforeSuiteCompleted(types.SpecStateFailed, nil)).Should(Succeed()) state, data, err := client.BlockUntilSynchronizedBeforeSuiteData() Ω(state).Should(Equal(types.SpecStateFailed)) Ω(data).Should(BeNil()) Ω(err).ShouldNot(HaveOccurred()) }) }) Context("when proc 1 disappears before reporting back", func() { It("returns a meaningful error", func() { close(proc1Exited) state, data, err := client.BlockUntilSynchronizedBeforeSuiteData() Ω(state).Should(Equal(types.SpecStateInvalid)) Ω(data).Should(BeNil()) Ω(err).Should(MatchError(types.GinkgoErrors.SynchronizedBeforeSuiteDisappearedOnProc1())) }) }) Context("when proc 1 hasn't responded yet", func() { It("blocks until it does", func() { done := make(chan interface{}) go func() { defer GinkgoRecover() state, data, err := client.BlockUntilSynchronizedBeforeSuiteData() Ω(state).Should(Equal(types.SpecStatePassed)) Ω(data).Should(Equal([]byte("hello there"))) Ω(err).ShouldNot(HaveOccurred()) close(done) }() Consistently(done).ShouldNot(BeClosed()) Ω(client.PostSynchronizedBeforeSuiteCompleted(types.SpecStatePassed, []byte("hello there"))).Should(Succeed()) Eventually(done).Should(BeClosed()) }) }) }) Describe("BlockUntilNonprimaryProcsHaveFinished", func() { It("blocks until non-primary procs exit", func() { done := make(chan interface{}) go func() { defer GinkgoRecover() Ω(client.BlockUntilNonprimaryProcsHaveFinished()).Should(Succeed()) close(done) }() Consistently(done).ShouldNot(BeClosed()) close(proc2Exited) Consistently(done).ShouldNot(BeClosed()) close(proc3Exited) Eventually(done).Should(BeClosed()) }) }) Describe("BlockUntilAggregatedNonprimaryProcsReport", func() { var specReportA, specReportB types.SpecReport var endReport2, endReport3 types.Report BeforeEach(func() { specReportA = types.SpecReport{LeafNodeText: "A"} specReportB = types.SpecReport{LeafNodeText: "B"} endReport2 = types.Report{SpecReports: types.SpecReports{specReportA}} endReport3 = types.Report{SpecReports: types.SpecReports{specReportB}} }) It("blocks until all non-primary procs exit, then returns the aggregated report", func() { done := make(chan interface{}) go func() { defer GinkgoRecover() report, err := client.BlockUntilAggregatedNonprimaryProcsReport() Ω(err).ShouldNot(HaveOccurred()) Ω(report.SpecReports).Should(ConsistOf(specReportA, specReportB)) close(done) }() Consistently(done).ShouldNot(BeClosed()) Ω(client.PostSuiteDidEnd(endReport2)).Should(Succeed()) close(proc2Exited) Consistently(done).ShouldNot(BeClosed()) Ω(client.PostSuiteDidEnd(endReport3)).Should(Succeed()) close(proc3Exited) Eventually(done).Should(BeClosed()) }) Context("when a non-primary proc disappears without reporting back", func() { It("blocks returns an appropriate error", func() { done := make(chan interface{}) go func() { defer GinkgoRecover() report, err := client.BlockUntilAggregatedNonprimaryProcsReport() Ω(err).Should(Equal(types.GinkgoErrors.AggregatedReportUnavailableDueToNodeDisappearing())) Ω(report).Should(BeZero()) close(done) }() Consistently(done).ShouldNot(BeClosed()) Ω(client.PostSuiteDidEnd(endReport2)).Should(Succeed()) close(proc2Exited) Consistently(done).ShouldNot(BeClosed()) close(proc3Exited) Eventually(done).Should(BeClosed()) }) }) }) Describe("Fetching counters", func() { It("returns ascending counters", func() { Ω(client.FetchNextCounter()).Should(Equal(0)) Ω(client.FetchNextCounter()).Should(Equal(1)) Ω(client.FetchNextCounter()).Should(Equal(2)) Ω(client.FetchNextCounter()).Should(Equal(3)) }) }) Describe("Aborting", func() { It("should not abort by default", func() { Ω(client.ShouldAbort()).Should(BeFalse()) }) Context("when told to abort", func() { BeforeEach(func() { Ω(client.PostAbort()).Should(Succeed()) }) It("should abort", func() { Ω(client.ShouldAbort()).Should(BeTrue()) }) }) }) }) }) } }) golang-github-onsi-ginkgo-v2-2.22.0/internal/parallel_support/http_client.go000066400000000000000000000104311472321612100271410ustar00rootroot00000000000000package parallel_support import ( "bytes" "encoding/json" "fmt" "io" "net/http" "time" "github.com/onsi/ginkgo/v2/types" ) type httpClient struct { serverHost string } func newHttpClient(serverHost string) *httpClient { return &httpClient{ serverHost: serverHost, } } func (client *httpClient) Connect() bool { resp, err := http.Get(client.serverHost + "/up") if err != nil { return false } resp.Body.Close() return resp.StatusCode == http.StatusOK } func (client *httpClient) Close() error { return nil } func (client *httpClient) post(path string, data interface{}) error { var body io.Reader if data != nil { encoded, err := json.Marshal(data) if err != nil { return err } body = bytes.NewBuffer(encoded) } resp, err := http.Post(client.serverHost+path, "application/json", body) if err != nil { return err } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { return fmt.Errorf("received unexpected status code %d", resp.StatusCode) } return nil } func (client *httpClient) poll(path string, data interface{}) error { for { resp, err := http.Get(client.serverHost + path) if err != nil { return err } if resp.StatusCode == http.StatusTooEarly { resp.Body.Close() time.Sleep(POLLING_INTERVAL) continue } defer resp.Body.Close() if resp.StatusCode == http.StatusGone { return ErrorGone } if resp.StatusCode == http.StatusFailedDependency { return ErrorFailed } if resp.StatusCode != http.StatusOK { return fmt.Errorf("received unexpected status code %d", resp.StatusCode) } if data != nil { return json.NewDecoder(resp.Body).Decode(data) } return nil } } func (client *httpClient) PostSuiteWillBegin(report types.Report) error { return client.post("/suite-will-begin", report) } func (client *httpClient) PostDidRun(report types.SpecReport) error { return client.post("/did-run", report) } 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) PostReportBeforeSuiteCompleted(state types.SpecState) error { return client.post("/report-before-suite-completed", state) } func (client *httpClient) BlockUntilReportBeforeSuiteCompleted() (types.SpecState, error) { var state types.SpecState err := client.poll("/report-before-suite-state", &state) if err == ErrorGone { return types.SpecStateFailed, nil } return state, err } func (client *httpClient) PostSynchronizedBeforeSuiteCompleted(state types.SpecState, data []byte) error { beforeSuiteState := BeforeSuiteState{ State: state, Data: data, } return client.post("/before-suite-completed", beforeSuiteState) } func (client *httpClient) BlockUntilSynchronizedBeforeSuiteData() (types.SpecState, []byte, error) { var beforeSuiteState BeforeSuiteState err := client.poll("/before-suite-state", &beforeSuiteState) if err == ErrorGone { return types.SpecStateInvalid, nil, types.GinkgoErrors.SynchronizedBeforeSuiteDisappearedOnProc1() } return beforeSuiteState.State, beforeSuiteState.Data, err } func (client *httpClient) BlockUntilNonprimaryProcsHaveFinished() error { return client.poll("/have-nonprimary-procs-finished", nil) } func (client *httpClient) BlockUntilAggregatedNonprimaryProcsReport() (types.Report, error) { var report types.Report err := client.poll("/aggregated-nonprimary-procs-report", &report) if err == ErrorGone { return types.Report{}, types.GinkgoErrors.AggregatedReportUnavailableDueToNodeDisappearing() } return report, err } func (client *httpClient) FetchNextCounter() (int, error) { var counter ParallelIndexCounter err := client.poll("/counter", &counter) return counter.Index, err } func (client *httpClient) PostAbort() error { return client.post("/abort", nil) } func (client *httpClient) ShouldAbort() bool { err := client.poll("/abort", nil) if err == ErrorGone { return true } return false } func (client *httpClient) Write(p []byte) (int, error) { resp, err := http.Post(client.serverHost+"/emit-output", "text/plain;charset=UTF-8 ", bytes.NewReader(p)) resp.Body.Close() if resp.StatusCode != http.StatusOK { return 0, fmt.Errorf("failed to emit output") } return len(p), err } golang-github-onsi-ginkgo-v2-2.22.0/internal/parallel_support/http_server.go000066400000000000000000000167131472321612100272020ustar00rootroot00000000000000/* The remote package provides the pieces to allow Ginkgo test suites to report to remote listeners. This is used, primarily, to enable streaming parallel test output but has, in principal, broader applications (e.g. streaming test output to a browser). */ package parallel_support import ( "encoding/json" "io" "net" "net/http" "github.com/onsi/ginkgo/v2/reporters" "github.com/onsi/ginkgo/v2/types" ) /* httpServer spins up on an automatically selected port and listens for communication from the forwarding reporter. It then forwards that communication to attached reporters. */ type httpServer struct { listener net.Listener handler *ServerHandler } // Create a new server, automatically selecting a port func newHttpServer(parallelTotal int, reporter reporters.Reporter) (*httpServer, error) { listener, err := net.Listen("tcp", "127.0.0.1:0") if err != nil { return nil, err } return &httpServer{ listener: listener, handler: newServerHandler(parallelTotal, reporter), }, nil } // Start the server. You don't need to `go s.Start()`, just `s.Start()` func (server *httpServer) Start() { httpServer := &http.Server{} mux := http.NewServeMux() httpServer.Handler = mux //streaming endpoints mux.HandleFunc("/suite-will-begin", server.specSuiteWillBegin) 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("/report-before-suite-completed", server.handleReportBeforeSuiteCompleted) mux.HandleFunc("/report-before-suite-state", server.handleReportBeforeSuiteState) mux.HandleFunc("/before-suite-completed", server.handleBeforeSuiteCompleted) mux.HandleFunc("/before-suite-state", server.handleBeforeSuiteState) mux.HandleFunc("/have-nonprimary-procs-finished", server.handleHaveNonprimaryProcsFinished) mux.HandleFunc("/aggregated-nonprimary-procs-report", server.handleAggregatedNonprimaryProcsReport) mux.HandleFunc("/counter", server.handleCounter) mux.HandleFunc("/up", server.handleUp) mux.HandleFunc("/abort", server.handleAbort) go httpServer.Serve(server.listener) } // Stop the server func (server *httpServer) Close() { server.listener.Close() } // The address the server can be reached it. Pass this into the `ForwardingReporter`. func (server *httpServer) Address() string { return "http://" + server.listener.Addr().String() } func (server *httpServer) GetSuiteDone() chan interface{} { return server.handler.done } func (server *httpServer) GetOutputDestination() io.Writer { return server.handler.outputDestination } func (server *httpServer) SetOutputDestination(w io.Writer) { server.handler.outputDestination = w } func (server *httpServer) RegisterAlive(node int, alive func() bool) { server.handler.registerAlive(node, alive) } // // Streaming Endpoints // // The server will forward all received messages to Ginkgo reporters registered with `RegisterReporters` func (server *httpServer) decode(writer http.ResponseWriter, request *http.Request, object interface{}) bool { defer request.Body.Close() if json.NewDecoder(request.Body).Decode(object) != nil { writer.WriteHeader(http.StatusBadRequest) return false } return true } func (server *httpServer) handleError(err error, writer http.ResponseWriter) bool { if err == nil { return false } switch err { case ErrorEarly: writer.WriteHeader(http.StatusTooEarly) case ErrorGone: writer.WriteHeader(http.StatusGone) case ErrorFailed: writer.WriteHeader(http.StatusFailedDependency) default: writer.WriteHeader(http.StatusInternalServerError) } return true } func (server *httpServer) specSuiteWillBegin(writer http.ResponseWriter, request *http.Request) { var report types.Report if !server.decode(writer, request, &report) { return } server.handleError(server.handler.SpecSuiteWillBegin(report, voidReceiver), writer) } func (server *httpServer) didRun(writer http.ResponseWriter, request *http.Request) { var report types.SpecReport if !server.decode(writer, request, &report) { return } server.handleError(server.handler.DidRun(report, voidReceiver), writer) } func (server *httpServer) specSuiteDidEnd(writer http.ResponseWriter, request *http.Request) { var report types.Report if !server.decode(writer, request, &report) { return } server.handleError(server.handler.SpecSuiteDidEnd(report, voidReceiver), writer) } func (server *httpServer) emitOutput(writer http.ResponseWriter, request *http.Request) { output, err := io.ReadAll(request.Body) if err != nil { writer.WriteHeader(http.StatusInternalServerError) return } var n int 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) handleReportBeforeSuiteCompleted(writer http.ResponseWriter, request *http.Request) { var state types.SpecState if !server.decode(writer, request, &state) { return } server.handleError(server.handler.ReportBeforeSuiteCompleted(state, voidReceiver), writer) } func (server *httpServer) handleReportBeforeSuiteState(writer http.ResponseWriter, request *http.Request) { var state types.SpecState if server.handleError(server.handler.ReportBeforeSuiteState(voidSender, &state), writer) { return } json.NewEncoder(writer).Encode(state) } func (server *httpServer) handleBeforeSuiteCompleted(writer http.ResponseWriter, request *http.Request) { var beforeSuiteState BeforeSuiteState if !server.decode(writer, request, &beforeSuiteState) { return } server.handleError(server.handler.BeforeSuiteCompleted(beforeSuiteState, voidReceiver), writer) } func (server *httpServer) handleBeforeSuiteState(writer http.ResponseWriter, request *http.Request) { var beforeSuiteState BeforeSuiteState if server.handleError(server.handler.BeforeSuiteState(voidSender, &beforeSuiteState), writer) { return } json.NewEncoder(writer).Encode(beforeSuiteState) } func (server *httpServer) handleHaveNonprimaryProcsFinished(writer http.ResponseWriter, request *http.Request) { if server.handleError(server.handler.HaveNonprimaryProcsFinished(voidSender, voidReceiver), writer) { return } writer.WriteHeader(http.StatusOK) } func (server *httpServer) handleAggregatedNonprimaryProcsReport(writer http.ResponseWriter, request *http.Request) { var aggregatedReport types.Report if server.handleError(server.handler.AggregatedNonprimaryProcsReport(voidSender, &aggregatedReport), writer) { return } json.NewEncoder(writer).Encode(aggregatedReport) } func (server *httpServer) handleCounter(writer http.ResponseWriter, request *http.Request) { var n int if server.handleError(server.handler.Counter(voidSender, &n), writer) { return } json.NewEncoder(writer).Encode(ParallelIndexCounter{Index: n}) } func (server *httpServer) handleUp(writer http.ResponseWriter, request *http.Request) { writer.WriteHeader(http.StatusOK) } func (server *httpServer) handleAbort(writer http.ResponseWriter, request *http.Request) { if request.Method == "GET" { var shouldAbort bool server.handler.ShouldAbort(voidSender, &shouldAbort) if shouldAbort { writer.WriteHeader(http.StatusGone) } else { writer.WriteHeader(http.StatusOK) } } else { server.handler.Abort(voidSender, voidReceiver) } } golang-github-onsi-ginkgo-v2-2.22.0/internal/parallel_support/parallel_support_suite_test.go000066400000000000000000000013451472321612100324700ustar00rootroot00000000000000package parallel_support_test import ( "io" "net/http" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" "testing" ) func TestParallelSupport(t *testing.T) { RegisterFailHandler(Fail) RunSpecs(t, "Parallel Support Suite") } type post struct { url string bodyType string bodyContent []byte } type fakePoster struct { posts []post } func newFakePoster() *fakePoster { return &fakePoster{ posts: make([]post, 0), } } func (poster *fakePoster) Post(url string, bodyType string, body io.Reader) (resp *http.Response, err error) { bodyContent, _ := io.ReadAll(body) poster.posts = append(poster.posts, post{ url: url, bodyType: bodyType, bodyContent: bodyContent, }) return nil, nil } golang-github-onsi-ginkgo-v2-2.22.0/internal/parallel_support/rpc_client.go000066400000000000000000000072311472321612100267520ustar00rootroot00000000000000package parallel_support import ( "net/rpc" "time" "github.com/onsi/ginkgo/v2/types" ) type rpcClient struct { serverHost string client *rpc.Client } func newRPCClient(serverHost string) *rpcClient { return &rpcClient{ serverHost: serverHost, } } func (client *rpcClient) Connect() bool { var err error if client.client != nil { return true } client.client, err = rpc.DialHTTPPath("tcp", client.serverHost, "/") if err != nil { client.client = nil return false } return true } func (client *rpcClient) Close() error { return client.client.Close() } func (client *rpcClient) poll(method string, data interface{}) error { for { err := client.client.Call(method, voidSender, data) if err == nil { return nil } switch err.Error() { case ErrorEarly.Error(): time.Sleep(POLLING_INTERVAL) case ErrorGone.Error(): return ErrorGone case ErrorFailed.Error(): return ErrorFailed default: return err } } } func (client *rpcClient) PostSuiteWillBegin(report types.Report) error { return client.client.Call("Server.SpecSuiteWillBegin", report, voidReceiver) } func (client *rpcClient) PostDidRun(report types.SpecReport) error { return client.client.Call("Server.DidRun", report, voidReceiver) } func (client *rpcClient) PostSuiteDidEnd(report types.Report) error { return client.client.Call("Server.SpecSuiteDidEnd", report, voidReceiver) } func (client *rpcClient) Write(p []byte) (int, error) { var n int err := client.client.Call("Server.EmitOutput", p, &n) return n, err } func (client *rpcClient) PostEmitProgressReport(report types.ProgressReport) error { return client.client.Call("Server.EmitProgressReport", report, voidReceiver) } func (client *rpcClient) PostReportBeforeSuiteCompleted(state types.SpecState) error { return client.client.Call("Server.ReportBeforeSuiteCompleted", state, voidReceiver) } func (client *rpcClient) BlockUntilReportBeforeSuiteCompleted() (types.SpecState, error) { var state types.SpecState err := client.poll("Server.ReportBeforeSuiteState", &state) if err == ErrorGone { return types.SpecStateFailed, nil } return state, err } func (client *rpcClient) PostSynchronizedBeforeSuiteCompleted(state types.SpecState, data []byte) error { beforeSuiteState := BeforeSuiteState{ State: state, Data: data, } return client.client.Call("Server.BeforeSuiteCompleted", beforeSuiteState, voidReceiver) } func (client *rpcClient) BlockUntilSynchronizedBeforeSuiteData() (types.SpecState, []byte, error) { var beforeSuiteState BeforeSuiteState err := client.poll("Server.BeforeSuiteState", &beforeSuiteState) if err == ErrorGone { return types.SpecStateInvalid, nil, types.GinkgoErrors.SynchronizedBeforeSuiteDisappearedOnProc1() } return beforeSuiteState.State, beforeSuiteState.Data, err } func (client *rpcClient) BlockUntilNonprimaryProcsHaveFinished() error { return client.poll("Server.HaveNonprimaryProcsFinished", voidReceiver) } func (client *rpcClient) BlockUntilAggregatedNonprimaryProcsReport() (types.Report, error) { var report types.Report err := client.poll("Server.AggregatedNonprimaryProcsReport", &report) if err == ErrorGone { return types.Report{}, types.GinkgoErrors.AggregatedReportUnavailableDueToNodeDisappearing() } return report, err } func (client *rpcClient) FetchNextCounter() (int, error) { var counter int err := client.client.Call("Server.Counter", voidSender, &counter) return counter, err } func (client *rpcClient) PostAbort() error { return client.client.Call("Server.Abort", voidSender, voidReceiver) } func (client *rpcClient) ShouldAbort() bool { var shouldAbort bool client.client.Call("Server.ShouldAbort", voidSender, &shouldAbort) return shouldAbort } golang-github-onsi-ginkgo-v2-2.22.0/internal/parallel_support/rpc_server.go000066400000000000000000000036621472321612100270060ustar00rootroot00000000000000/* The remote package provides the pieces to allow Ginkgo test suites to report to remote listeners. This is used, primarily, to enable streaming parallel test output but has, in principal, broader applications (e.g. streaming test output to a browser). */ package parallel_support import ( "io" "net" "net/http" "net/rpc" "github.com/onsi/ginkgo/v2/reporters" ) /* RPCServer spins up on an automatically selected port and listens for communication from the forwarding reporter. It then forwards that communication to attached reporters. */ type RPCServer struct { listener net.Listener handler *ServerHandler } //Create a new server, automatically selecting a port func newRPCServer(parallelTotal int, reporter reporters.Reporter) (*RPCServer, error) { listener, err := net.Listen("tcp", "127.0.0.1:0") if err != nil { return nil, err } return &RPCServer{ listener: listener, handler: newServerHandler(parallelTotal, reporter), }, nil } //Start the server. You don't need to `go s.Start()`, just `s.Start()` func (server *RPCServer) Start() { rpcServer := rpc.NewServer() rpcServer.RegisterName("Server", server.handler) //register the handler's methods as the server httpServer := &http.Server{} httpServer.Handler = rpcServer go httpServer.Serve(server.listener) } //Stop the server func (server *RPCServer) Close() { server.listener.Close() } //The address the server can be reached it. Pass this into the `ForwardingReporter`. func (server *RPCServer) Address() string { return server.listener.Addr().String() } func (server *RPCServer) GetSuiteDone() chan interface{} { return server.handler.done } func (server *RPCServer) GetOutputDestination() io.Writer { return server.handler.outputDestination } func (server *RPCServer) SetOutputDestination(w io.Writer) { server.handler.outputDestination = w } func (server *RPCServer) RegisterAlive(node int, alive func() bool) { server.handler.registerAlive(node, alive) } golang-github-onsi-ginkgo-v2-2.22.0/internal/parallel_support/server_handler.go000066400000000000000000000135631472321612100276400ustar00rootroot00000000000000package parallel_support import ( "io" "os" "sync" "github.com/onsi/ginkgo/v2/reporters" "github.com/onsi/ginkgo/v2/types" ) type Void struct{} var voidReceiver *Void = &Void{} var voidSender Void // ServerHandler is an RPC-compatible handler that is shared between the http server and the rpc server. // It handles all the business logic to avoid duplication between the two servers type ServerHandler struct { done chan interface{} outputDestination io.Writer reporter reporters.Reporter alives []func() bool lock *sync.Mutex beforeSuiteState BeforeSuiteState reportBeforeSuiteState types.SpecState parallelTotal int counter int counterLock *sync.Mutex shouldAbort bool numSuiteDidBegins int numSuiteDidEnds int aggregatedReport types.Report reportHoldingArea []types.SpecReport } func newServerHandler(parallelTotal int, reporter reporters.Reporter) *ServerHandler { return &ServerHandler{ reporter: reporter, lock: &sync.Mutex{}, counterLock: &sync.Mutex{}, alives: make([]func() bool, parallelTotal), beforeSuiteState: BeforeSuiteState{Data: nil, State: types.SpecStateInvalid}, parallelTotal: parallelTotal, outputDestination: os.Stdout, done: make(chan interface{}), } } func (handler *ServerHandler) SpecSuiteWillBegin(report types.Report, _ *Void) error { handler.lock.Lock() defer handler.lock.Unlock() handler.numSuiteDidBegins += 1 // all summaries are identical, so it's fine to simply emit the last one of these if handler.numSuiteDidBegins == handler.parallelTotal { handler.reporter.SuiteWillBegin(report) for _, summary := range handler.reportHoldingArea { handler.reporter.WillRun(summary) handler.reporter.DidRun(summary) } handler.reportHoldingArea = nil } return nil } func (handler *ServerHandler) DidRun(report types.SpecReport, _ *Void) error { handler.lock.Lock() defer handler.lock.Unlock() if handler.numSuiteDidBegins == handler.parallelTotal { handler.reporter.WillRun(report) handler.reporter.DidRun(report) } else { handler.reportHoldingArea = append(handler.reportHoldingArea, report) } return nil } func (handler *ServerHandler) SpecSuiteDidEnd(report types.Report, _ *Void) error { handler.lock.Lock() defer handler.lock.Unlock() handler.numSuiteDidEnds += 1 if handler.numSuiteDidEnds == 1 { handler.aggregatedReport = report } else { handler.aggregatedReport = handler.aggregatedReport.Add(report) } if handler.numSuiteDidEnds == handler.parallelTotal { handler.reporter.SuiteDidEnd(handler.aggregatedReport) close(handler.done) } return nil } func (handler *ServerHandler) EmitOutput(output []byte, n *int) error { var err error *n, err = handler.outputDestination.Write(output) 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() handler.alives[proc-1] = alive } func (handler *ServerHandler) procIsAlive(proc int) bool { handler.lock.Lock() defer handler.lock.Unlock() alive := handler.alives[proc-1] if alive == nil { return true } return alive() } func (handler *ServerHandler) haveNonprimaryProcsFinished() bool { for i := 2; i <= handler.parallelTotal; i++ { if handler.procIsAlive(i) { return false } } return true } func (handler *ServerHandler) ReportBeforeSuiteCompleted(reportBeforeSuiteState types.SpecState, _ *Void) error { handler.lock.Lock() defer handler.lock.Unlock() handler.reportBeforeSuiteState = reportBeforeSuiteState return nil } func (handler *ServerHandler) ReportBeforeSuiteState(_ Void, reportBeforeSuiteState *types.SpecState) error { proc1IsAlive := handler.procIsAlive(1) handler.lock.Lock() defer handler.lock.Unlock() if handler.reportBeforeSuiteState == types.SpecStateInvalid { if proc1IsAlive { return ErrorEarly } else { return ErrorGone } } *reportBeforeSuiteState = handler.reportBeforeSuiteState return nil } func (handler *ServerHandler) BeforeSuiteCompleted(beforeSuiteState BeforeSuiteState, _ *Void) error { handler.lock.Lock() defer handler.lock.Unlock() handler.beforeSuiteState = beforeSuiteState return nil } func (handler *ServerHandler) BeforeSuiteState(_ Void, beforeSuiteState *BeforeSuiteState) error { proc1IsAlive := handler.procIsAlive(1) handler.lock.Lock() defer handler.lock.Unlock() if handler.beforeSuiteState.State == types.SpecStateInvalid { if proc1IsAlive { return ErrorEarly } else { return ErrorGone } } *beforeSuiteState = handler.beforeSuiteState return nil } func (handler *ServerHandler) HaveNonprimaryProcsFinished(_ Void, _ *Void) error { if handler.haveNonprimaryProcsFinished() { return nil } else { return ErrorEarly } } func (handler *ServerHandler) AggregatedNonprimaryProcsReport(_ Void, report *types.Report) error { if handler.haveNonprimaryProcsFinished() { handler.lock.Lock() defer handler.lock.Unlock() if handler.numSuiteDidEnds == handler.parallelTotal-1 { *report = handler.aggregatedReport return nil } else { return ErrorGone } } else { return ErrorEarly } } func (handler *ServerHandler) Counter(_ Void, counter *int) error { handler.counterLock.Lock() defer handler.counterLock.Unlock() *counter = handler.counter handler.counter++ return nil } func (handler *ServerHandler) Abort(_ Void, _ *Void) error { handler.lock.Lock() defer handler.lock.Unlock() handler.shouldAbort = true return nil } func (handler *ServerHandler) ShouldAbort(_ Void, shouldAbort *bool) error { handler.lock.Lock() defer handler.lock.Unlock() *shouldAbort = handler.shouldAbort return nil } golang-github-onsi-ginkgo-v2-2.22.0/internal/progress_report.go000066400000000000000000000211111472321612100244700ustar00rootroot00000000000000package 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 types.SpecEvent, gwOutput string, timelineLocation types.TimelineLocation, additionalReports []string, sourceRoots []string, includeAll bool) (types.ProgressReport, error) { pr := types.ProgressReport{ ParallelProcess: report.ParallelProcess, RunningInParallel: isRunningInParallel, 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.Message, CurrentStepLocation: currentStep.CodeLocation, CurrentStepStartTime: currentStep.TimelineLocation.Time, AdditionalReports: additionalReports, CapturedGinkgoWriterOutput: gwOutput, TimelineLocation: timelineLocation, } 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") delimiterIdx := strings.LastIndex(line, ":") if delimiterIdx == -1 { return nil, types.GinkgoErrors.FailedToParseStackTrace(fmt.Sprintf("Invalid filename and line number: %s", line)) } functionCall.Filename = line[:delimiterIdx] line = strings.Split(line[delimiterIdx+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 } golang-github-onsi-ginkgo-v2-2.22.0/internal/progress_report_bsd.go000066400000000000000000000003461472321612100253270ustar00rootroot00000000000000//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} golang-github-onsi-ginkgo-v2-2.22.0/internal/progress_report_test.go000066400000000000000000000022531472321612100255350ustar00rootroot00000000000000package internal_test import ( "time" . "github.com/onsi/ginkgo/v2" "github.com/onsi/ginkgo/v2/internal" "github.com/onsi/ginkgo/v2/types" . "github.com/onsi/gomega" ) var _ = Describe("ProgressReport", func() { Describe("The goroutine stack", func() { It("is better tested in the internal integration tests because this test package lives in internal which is a key part of the logic for how the goroutine stack is analyzed...", func() { //empty }) }) Context("when includeAll is false", func() { It("does not include any other goroutines", func() { pr, err := internal.NewProgressReport(false, types.SpecReport{}, Node{}, time.Now(), types.SpecEvent{}, "", types.TimelineLocation{}, []string{}, []string{}, false) Ω(err).ShouldNot(HaveOccurred()) Ω(pr.OtherGoroutines()).Should(HaveLen(0)) }) }) Context("when includeAll is true", func() { It("includes all other goroutines", func() { pr, err := internal.NewProgressReport(false, types.SpecReport{}, Node{}, time.Now(), types.SpecEvent{}, "", types.TimelineLocation{}, []string{}, []string{}, true) Ω(err).ShouldNot(HaveOccurred()) Ω(pr.OtherGoroutines()).ShouldNot(HaveLen(0)) }) }) }) golang-github-onsi-ginkgo-v2-2.22.0/internal/progress_report_unix.go000066400000000000000000000002301472321612100255320ustar00rootroot00000000000000//go:build linux || solaris // +build linux solaris package internal import ( "os" "syscall" ) var PROGRESS_SIGNALS = []os.Signal{syscall.SIGUSR1} golang-github-onsi-ginkgo-v2-2.22.0/internal/progress_report_wasm.go000066400000000000000000000001641472321612100255240ustar00rootroot00000000000000//go:build wasm package internal import ( "os" "syscall" ) var PROGRESS_SIGNALS = []os.Signal{syscall.SIGUSR1} golang-github-onsi-ginkgo-v2-2.22.0/internal/progress_report_win.go000066400000000000000000000001521472321612100253470ustar00rootroot00000000000000//go:build windows // +build windows package internal import "os" var PROGRESS_SIGNALS = []os.Signal{} golang-github-onsi-ginkgo-v2-2.22.0/internal/progress_reporter_manager.go000066400000000000000000000032301472321612100265130ustar00rootroot00000000000000package internal import ( "context" "sort" "strings" "sync" "github.com/onsi/ginkgo/v2/types" ) type ProgressReporterManager struct { lock *sync.Mutex progressReporters map[int]func() string prCounter int } func NewProgressReporterManager() *ProgressReporterManager { return &ProgressReporterManager{ progressReporters: map[int]func() string{}, lock: &sync.Mutex{}, } } func (prm *ProgressReporterManager) AttachProgressReporter(reporter func() string) func() { prm.lock.Lock() defer prm.lock.Unlock() prm.prCounter += 1 prCounter := prm.prCounter prm.progressReporters[prCounter] = reporter return func() { prm.lock.Lock() defer prm.lock.Unlock() delete(prm.progressReporters, prCounter) } } func (prm *ProgressReporterManager) QueryProgressReporters(ctx context.Context, failer *Failer) []string { prm.lock.Lock() keys := []int{} for key := range prm.progressReporters { keys = append(keys, key) } sort.Ints(keys) reporters := []func() string{} for _, key := range keys { reporters = append(reporters, prm.progressReporters[key]) } prm.lock.Unlock() if len(reporters) == 0 { return nil } out := []string{} for _, reporter := range reporters { reportC := make(chan string, 1) go func() { defer func() { e := recover() if e != nil { failer.Panic(types.NewCodeLocationWithStackTrace(1), e) reportC <- "failed to query attached progress reporter" } }() reportC <- reporter() }() var report string select { case report = <-reportC: case <-ctx.Done(): return out } if strings.TrimSpace(report) != "" { out = append(out, report) } } return out } golang-github-onsi-ginkgo-v2-2.22.0/internal/progress_reporter_manager_test.go000066400000000000000000000060301472321612100275530ustar00rootroot00000000000000package internal_test import ( "context" "time" . "github.com/onsi/ginkgo/v2" "github.com/onsi/ginkgo/v2/types" . "github.com/onsi/gomega" "github.com/onsi/gomega/gleak" "github.com/onsi/ginkgo/v2/internal" ) var _ = Describe("ProgressReporterManager", func() { var manager *internal.ProgressReporterManager BeforeEach(func() { manager = internal.NewProgressReporterManager() }) It("can attach and detach progress reporters", func() { Ω(manager.QueryProgressReporters(context.Background(), nil)).Should(BeEmpty()) cancelA := manager.AttachProgressReporter(func() string { return "A" }) Ω(manager.QueryProgressReporters(context.Background(), nil)).Should(Equal([]string{"A"})) cancelB := manager.AttachProgressReporter(func() string { return "B" }) Ω(manager.QueryProgressReporters(context.Background(), nil)).Should(Equal([]string{"A", "B"})) cancelC := manager.AttachProgressReporter(func() string { return "C" }) Ω(manager.QueryProgressReporters(context.Background(), nil)).Should(Equal([]string{"A", "B", "C"})) cancelB() Ω(manager.QueryProgressReporters(context.Background(), nil)).Should(Equal([]string{"A", "C"})) cancelA() Ω(manager.QueryProgressReporters(context.Background(), nil)).Should(Equal([]string{"C"})) cancelC() Ω(manager.QueryProgressReporters(context.Background(), nil)).Should(BeEmpty()) }) It("bails if a progress reporter takes longer than the passed-in context's deadline", func() { startingGoroutines := gleak.Goroutines() c := make(chan struct{}) manager.AttachProgressReporter(func() string { return "A" }) manager.AttachProgressReporter(func() string { return "B" }) manager.AttachProgressReporter(func() string { <-c return "C" }) manager.AttachProgressReporter(func() string { return "D" }) context, cancel := context.WithTimeout(context.Background(), time.Millisecond*100) result := manager.QueryProgressReporters(context, nil) Ω(result).Should(Equal([]string{"A", "B"})) cancel() close(c) Eventually(gleak.Goroutines).ShouldNot(gleak.HaveLeaked(startingGoroutines)) }) It("ignores empty progress reports", func() { manager.AttachProgressReporter(func() string { return "A" }) manager.AttachProgressReporter(func() string { return "" }) manager.AttachProgressReporter(func() string { return " " }) manager.AttachProgressReporter(func() string { return "C" }) result := manager.QueryProgressReporters(context.Background(), nil) Ω(result).Should(Equal([]string{"A", "C"})) }) It("catches panics and reports them as failures", func() { manager.AttachProgressReporter(func() string { panic("bam") }) manager.AttachProgressReporter(func() string { return "B" }) failer := internal.NewFailer() result := manager.QueryProgressReporters(context.Background(), failer) Ω(result).Should(Equal([]string{"failed to query attached progress reporter", "B"})) state, failure := failer.Drain() Ω(state).Should(Equal(types.SpecStatePanicked)) Ω(failure.Message).Should(Equal("Test Panicked")) Ω(failure.ForwardedPanic).Should(Equal("bam")) }) }) golang-github-onsi-ginkgo-v2-2.22.0/internal/report_entry.go000066400000000000000000000015221472321612100237710ustar00rootroot00000000000000package internal import ( "time" "github.com/onsi/ginkgo/v2/types" ) type ReportEntry = types.ReportEntry func NewReportEntry(name string, cl types.CodeLocation, args ...interface{}) (ReportEntry, error) { out := ReportEntry{ Visibility: types.ReportEntryVisibilityAlways, Name: name, Location: cl, Time: time.Now(), } var didSetValue = false for _, arg := range args { switch x := arg.(type) { case types.ReportEntryVisibility: out.Visibility = x case types.CodeLocation: out.Location = x case Offset: out.Location = types.NewCodeLocation(2 + int(x)) case time.Time: out.Time = x default: if didSetValue { return ReportEntry{}, types.GinkgoErrors.TooManyReportEntryValues(out.Location, arg) } out.Value = types.WrapEntryValue(arg) didSetValue = true } } return out, nil } golang-github-onsi-ginkgo-v2-2.22.0/internal/report_entry_test.go000066400000000000000000000237751472321612100250460ustar00rootroot00000000000000package internal_test import ( "encoding/json" "fmt" "time" . "github.com/onsi/ginkgo/v2" "github.com/onsi/ginkgo/v2/internal" "github.com/onsi/ginkgo/v2/types" . "github.com/onsi/gomega" ) type SomeStruct struct { Label string Count int } type StringerStruct struct { Label string Count int } func (s StringerStruct) String() string { return fmt.Sprintf("%s %d", s.Label, s.Count) } type ColorableStringerStruct struct { Label string Count int } func (s ColorableStringerStruct) String() string { return fmt.Sprintf("%s %d", s.Label, s.Count) } func (s ColorableStringerStruct) ColorableString() string { return fmt.Sprintf("{{red}}%s {{green}}%d{{/}}", s.Label, s.Count) } func reportEntryJSONRoundTrip(reportEntry internal.ReportEntry) internal.ReportEntry { data, err := json.Marshal(reportEntry) ExpectWithOffset(1, err).ShouldNot(HaveOccurred()) var out internal.ReportEntry ExpectWithOffset(1, json.Unmarshal(data, &out)).Should(Succeed()) return out } var _ = Describe("ReportEntry and ReportEntries", func() { var reportEntry internal.ReportEntry var err error Describe("ReportEntry with no passed-in value", func() { BeforeEach(func() { reportEntry, err = internal.NewReportEntry("name", cl) Ω(err).ShouldNot(HaveOccurred()) }) It("returns a correctly configured ReportEntry", func() { Ω(reportEntry.Visibility).Should(Equal(types.ReportEntryVisibilityAlways)) Ω(reportEntry.Name).Should(Equal("name")) Ω(reportEntry.Time).Should(BeTemporally("~", time.Now(), time.Second)) Ω(reportEntry.Location).Should(Equal(cl)) Ω(reportEntry.GetRawValue()).Should(BeNil()) }) It("has an empty StringRepresentation", func() { Ω(reportEntry.StringRepresentation()).Should(BeZero()) }) It("round-trips through JSON correctly", func() { rtEntry := reportEntryJSONRoundTrip(reportEntry) Ω(rtEntry.Visibility).Should(Equal(types.ReportEntryVisibilityAlways)) Ω(rtEntry.Name).Should(Equal("name")) Ω(rtEntry.Time).Should(BeTemporally("~", time.Now(), time.Second)) Ω(rtEntry.Location).Should(Equal(cl)) Ω(rtEntry.GetRawValue()).Should(BeNil()) Ω(rtEntry.StringRepresentation()).Should(BeZero()) }) }) Context("with a string passed-in value", func() { BeforeEach(func() { reportEntry, err = internal.NewReportEntry("name", cl, "bob") Ω(err).ShouldNot(HaveOccurred()) }) It("returns a correctly configured ReportEntry", func() { Ω(reportEntry.GetRawValue()).Should(Equal("bob")) }) It("has the correct StringRepresentation", func() { Ω(reportEntry.StringRepresentation()).Should(Equal("bob")) }) It("round-trips through JSON correctly", func() { rtEntry := reportEntryJSONRoundTrip(reportEntry) Ω(rtEntry.GetRawValue()).Should(Equal("bob")) Ω(rtEntry.StringRepresentation()).Should(Equal("bob")) }) }) Context("with a numerical passed-in value", func() { BeforeEach(func() { reportEntry, err = internal.NewReportEntry("name", cl, 17) Ω(err).ShouldNot(HaveOccurred()) }) It("returns a correctly configured ReportEntry", func() { Ω(reportEntry.GetRawValue()).Should(Equal(17)) }) It("has the correct StringRepresentation", func() { Ω(reportEntry.StringRepresentation()).Should(Equal("17")) }) It("round-trips through JSON correctly", func() { rtEntry := reportEntryJSONRoundTrip(reportEntry) Ω(rtEntry.GetRawValue()).Should(Equal(float64(17))) Ω(rtEntry.StringRepresentation()).Should(Equal("17")) }) }) Context("with a struct passed-in value", func() { BeforeEach(func() { reportEntry, err = internal.NewReportEntry("name", cl, SomeStruct{"bob", 17}) Ω(err).ShouldNot(HaveOccurred()) }) It("returns a correctly configured ReportEntry", func() { Ω(reportEntry.GetRawValue()).Should(Equal(SomeStruct{"bob", 17})) }) It("has the correct StringRepresentation", func() { Ω(reportEntry.StringRepresentation()).Should(Equal("{Label:bob Count:17}")) }) It("round-trips through JSON correctly", func() { rtEntry := reportEntryJSONRoundTrip(reportEntry) Ω(rtEntry.GetRawValue()).Should(Equal(map[string]interface{}{"Label": "bob", "Count": float64(17)})) Ω(rtEntry.StringRepresentation()).Should(Equal("{Label:bob Count:17}")) }) It("can be rehydrated into the correct struct, manually", func() { rtEntry := reportEntryJSONRoundTrip(reportEntry) var s SomeStruct Ω(json.Unmarshal([]byte(rtEntry.Value.AsJSON), &s)).Should(Succeed()) Ω(s).Should(Equal(SomeStruct{"bob", 17})) }) }) Context("with a stringer passed-in value", func() { BeforeEach(func() { reportEntry, err = internal.NewReportEntry("name", cl, StringerStruct{"bob", 17}) Ω(err).ShouldNot(HaveOccurred()) }) It("returns a correctly configured ReportEntry", func() { Ω(reportEntry.GetRawValue()).Should(Equal(StringerStruct{"bob", 17})) }) It("has the correct StringRepresentation", func() { Ω(reportEntry.StringRepresentation()).Should(Equal("bob 17")) }) It("round-trips through JSON correctly", func() { rtEntry := reportEntryJSONRoundTrip(reportEntry) Ω(rtEntry.GetRawValue()).Should(Equal(map[string]interface{}{"Label": "bob", "Count": float64(17)})) Ω(rtEntry.StringRepresentation()).Should(Equal("bob 17")) }) }) Context("with a ColorableStringer passed-in value", func() { BeforeEach(func() { reportEntry, err = internal.NewReportEntry("name", cl, ColorableStringerStruct{"bob", 17}) Ω(err).ShouldNot(HaveOccurred()) }) It("returns a correctly configured ReportEntry", func() { Ω(reportEntry.GetRawValue()).Should(Equal(ColorableStringerStruct{"bob", 17})) }) It("has the correct StringRepresentation", func() { Ω(reportEntry.StringRepresentation()).Should(Equal("{{red}}bob {{green}}17{{/}}")) }) It("round-trips through JSON correctly", func() { rtEntry := reportEntryJSONRoundTrip(reportEntry) Ω(rtEntry.GetRawValue()).Should(Equal(map[string]interface{}{"Label": "bob", "Count": float64(17)})) Ω(rtEntry.StringRepresentation()).Should(Equal("{{red}}bob {{green}}17{{/}}")) }) }) Context("with multiple passed-in values", func() { It("errors", func() { reportEntry, err = internal.NewReportEntry("name", cl, 1, "2") Ω(err).Should(MatchError(types.GinkgoErrors.TooManyReportEntryValues(cl, "2"))) }) }) Context("with the Offset decoration", func() { It("computes a new offset code location", func() { reportEntry, err = internal.NewReportEntry("name", cl, Offset(1)) Ω(reportEntry.GetRawValue()).Should(BeNil()) expectedCL := types.NewCodeLocation(2) // NewReportEntry has a BaseOffset of 2 Ω(reportEntry.Location.FileName).Should(Equal(expectedCL.FileName)) }) }) Context("with a CodeLocation", func() { It("uses the passed-in codelocation", func() { customCl := types.NewCustomCodeLocation("foo") reportEntry, err = internal.NewReportEntry("name", cl, customCl) Ω(reportEntry.GetRawValue()).Should(BeNil()) Ω(reportEntry.Location).Should(Equal(customCl)) }) }) Context("with a ReportEntryVisibility", func() { It("uses the passed in visibility", func() { reportEntry, err = internal.NewReportEntry("name", cl, types.ReportEntryVisibilityFailureOrVerbose) Ω(reportEntry.GetRawValue()).Should(BeNil()) Ω(reportEntry.Visibility).Should(Equal(types.ReportEntryVisibilityFailureOrVerbose)) }) }) Context("with a time", func() { It("uses the passed in time", func() { t := time.Date(1984, 3, 7, 0, 0, 0, 0, time.Local) reportEntry, err = internal.NewReportEntry("name", cl, t) Ω(reportEntry.GetRawValue()).Should(BeNil()) Ω(reportEntry.Time).Should(Equal(t)) }) }) Describe("ReportEntries.HasVisibility", func() { It("is true when the ReportEntries have the requested visibilities", func() { entries := types.ReportEntries{ types.ReportEntry{Visibility: types.ReportEntryVisibilityAlways}, types.ReportEntry{Visibility: types.ReportEntryVisibilityAlways}, } Ω(entries.HasVisibility(types.ReportEntryVisibilityNever, types.ReportEntryVisibilityAlways)).Should(BeTrue()) Ω(entries.HasVisibility(types.ReportEntryVisibilityNever, types.ReportEntryVisibilityFailureOrVerbose)).Should(BeFalse()) }) }) Describe("ReportEntries.WithVisibility", func() { It("returns the subset of report entries with the requested visibilities", func() { entries := types.ReportEntries{ types.ReportEntry{Name: "A", Visibility: types.ReportEntryVisibilityAlways}, types.ReportEntry{Name: "B", Visibility: types.ReportEntryVisibilityFailureOrVerbose}, types.ReportEntry{Name: "C", Visibility: types.ReportEntryVisibilityNever}, } Ω(entries.WithVisibility(types.ReportEntryVisibilityAlways, types.ReportEntryVisibilityFailureOrVerbose)).Should(Equal( types.ReportEntries{ types.ReportEntry{Name: "A", Visibility: types.ReportEntryVisibilityAlways}, types.ReportEntry{Name: "B", Visibility: types.ReportEntryVisibilityFailureOrVerbose}, }, )) }) }) Describe("mini-integration test - validating that the DSL correctly wires into the suite", func() { Context("when passed a value", func() { It("works!", func() { AddReportEntry("A Test ReportEntry", ColorableStringerStruct{"bob", 17}, types.ReportEntryVisibilityFailureOrVerbose) }) ReportAfterEach(func(report SpecReport) { config, _ := GinkgoConfiguration() if !config.DryRun && report.State.Is(types.SpecStatePassed) { Ω(report.ReportEntries[0].StringRepresentation()).Should(Equal("{{red}}bob {{green}}17{{/}}")) } }) }) Context("when passed a pointer that subsequently changes", func() { var obj *ColorableStringerStruct BeforeEach(func() { obj = &ColorableStringerStruct{"bob", 17} }) It("works!", func() { AddReportEntry("A Test ReportEntry", obj, types.ReportEntryVisibilityFailureOrVerbose) }) AfterEach(func() { obj.Label = "alice" obj.Count = 42 }) ReportAfterEach(func(report SpecReport) { config, _ := GinkgoConfiguration() if !config.DryRun && report.State.Is(types.SpecStatePassed) { Ω(report.ReportEntries[0].StringRepresentation()).Should(Equal("{{red}}alice {{green}}42{{/}}")) } }) }) }) }) golang-github-onsi-ginkgo-v2-2.22.0/internal/spec.go000066400000000000000000000027231472321612100221730ustar00rootroot00000000000000package internal import ( "strings" "time" "github.com/onsi/ginkgo/v2/types" ) type Spec struct { Nodes Nodes Skip bool } func (s Spec) SubjectID() uint { return s.Nodes.FirstNodeWithType(types.NodeTypeIt).ID } func (s Spec) Text() string { texts := []string{} for i := range s.Nodes { if s.Nodes[i].Text != "" { texts = append(texts, s.Nodes[i].Text) } } return strings.Join(texts, " ") } func (s Spec) FirstNodeWithType(nodeTypes types.NodeType) Node { return s.Nodes.FirstNodeWithType(nodeTypes) } func (s Spec) FlakeAttempts() int { flakeAttempts := 0 for i := range s.Nodes { if s.Nodes[i].FlakeAttempts > 0 { flakeAttempts = s.Nodes[i].FlakeAttempts } } return flakeAttempts } func (s Spec) MustPassRepeatedly() int { mustPassRepeatedly := 0 for i := range s.Nodes { if s.Nodes[i].MustPassRepeatedly > 0 { mustPassRepeatedly = s.Nodes[i].MustPassRepeatedly } } return mustPassRepeatedly } func (s Spec) SpecTimeout() time.Duration { return s.FirstNodeWithType(types.NodeTypeIt).SpecTimeout } type Specs []Spec func (s Specs) HasAnySpecsMarkedPending() bool { for i := range s { if s[i].Nodes.HasNodeMarkedPending() { return true } } return false } func (s Specs) CountWithoutSkip() int { n := 0 for i := range s { if !s[i].Skip { n += 1 } } return n } func (s Specs) AtIndices(indices SpecIndices) Specs { out := make(Specs, len(indices)) for i, idx := range indices { out[i] = s[idx] } return out } golang-github-onsi-ginkgo-v2-2.22.0/internal/spec_context.go000066400000000000000000000035371472321612100237430ustar00rootroot00000000000000package internal import ( "context" "github.com/onsi/ginkgo/v2/types" ) type SpecContext interface { context.Context SpecReport() types.SpecReport AttachProgressReporter(func() string) func() } type specContext struct { context.Context *ProgressReporterManager cancel context.CancelCauseFunc suite *Suite } /* SpecContext includes a reference to `suite` and embeds itself in itself as a "GINKGO_SPEC_CONTEXT" value. This allows users to create child Contexts without having down-stream consumers (e.g. Gomega) lose access to the SpecContext and its methods. This allows us to build extensions on top of Ginkgo that simply take an all-encompassing context. Note that while SpecContext is used to enforce deadlines by Ginkgo it is not configured as a context.WithDeadline. Instead, Ginkgo owns responsibility for cancelling the context when the deadline elapses. This is because Ginkgo needs finer control over when the context is canceled. Specifically, Ginkgo needs to generate a ProgressReport before it cancels the context to ensure progress is captured where the spec is currently running. The only way to avoid a race here is to manually control the cancellation. */ func NewSpecContext(suite *Suite) *specContext { ctx, cancel := context.WithCancelCause(context.Background()) sc := &specContext{ cancel: cancel, suite: suite, ProgressReporterManager: NewProgressReporterManager(), } ctx = context.WithValue(ctx, "GINKGO_SPEC_CONTEXT", sc) //yes, yes, the go docs say don't use a string for a key... but we'd rather avoid a circular dependency between Gomega and Ginkgo sc.Context = ctx //thank goodness for garbage collectors that can handle circular dependencies return sc } func (sc *specContext) SpecReport() types.SpecReport { return sc.suite.CurrentSpecReport() } golang-github-onsi-ginkgo-v2-2.22.0/internal/spec_context_test.go000066400000000000000000000036501472321612100247760ustar00rootroot00000000000000package internal_test import ( "context" . "github.com/onsi/ginkgo/v2" "github.com/onsi/ginkgo/v2/internal" . "github.com/onsi/gomega" ) var _ = Describe("SpecContext", func() { It("allows access to the current spec report", func(c SpecContext) { Ω(c.SpecReport().LeafNodeText).Should(Equal("allows access to the current spec report")) }) It("can be wrapped and still retrieved", func(c SpecContext) { Ω(c.Value("GINKGO_SPEC_CONTEXT")).Should(Equal(c)) wrappedC := context.WithValue(c, "foo", "bar") _, ok := wrappedC.(SpecContext) Ω(ok).Should(BeFalse()) Ω(wrappedC.Value("GINKGO_SPEC_CONTEXT").(SpecContext).SpecReport().LeafNodeText).Should(Equal("can be wrapped and still retrieved")) }) It("can attach and detach progress reporters", func(c SpecContext) { type CompleteSpecContext interface { AttachProgressReporter(func() string) func() QueryProgressReporters(ctx context.Context, failer *internal.Failer) []string } wrappedC := context.WithValue(c, "foo", "bar") ctx := wrappedC.Value("GINKGO_SPEC_CONTEXT").(CompleteSpecContext) Ω(ctx.QueryProgressReporters(context.Background(), nil)).Should(BeEmpty()) cancelA := ctx.AttachProgressReporter(func() string { return "A" }) Ω(ctx.QueryProgressReporters(context.Background(), nil)).Should(Equal([]string{"A"})) cancelB := ctx.AttachProgressReporter(func() string { return "B" }) Ω(ctx.QueryProgressReporters(context.Background(), nil)).Should(Equal([]string{"A", "B"})) cancelC := ctx.AttachProgressReporter(func() string { return "C" }) Ω(ctx.QueryProgressReporters(context.Background(), nil)).Should(Equal([]string{"A", "B", "C"})) cancelB() Ω(ctx.QueryProgressReporters(context.Background(), nil)).Should(Equal([]string{"A", "C"})) cancelA() Ω(ctx.QueryProgressReporters(context.Background(), nil)).Should(Equal([]string{"C"})) cancelC() Ω(ctx.QueryProgressReporters(context.Background(), nil)).Should(BeEmpty()) }) }) golang-github-onsi-ginkgo-v2-2.22.0/internal/spec_test.go000066400000000000000000000106621472321612100232330ustar00rootroot00000000000000package internal_test import ( . "github.com/onsi/ginkgo/v2" "github.com/onsi/ginkgo/v2/internal" . "github.com/onsi/gomega" ) var _ = Describe("Spec and Specs", func() { Describe("spec.Text", func() { Context("when the spec has nodes with texts", func() { It("returns the concatenated texts of its nodes (omitting any empty texts)", func() { spec := S(N(), N("Oh death,"), N(), N("where is"), N("thy"), N(), N("string?")) Ω(spec.Text()).Should(Equal("Oh death, where is thy string?")) }) }) Context("when the spec has no nodes", func() { It("returns the empty string", func() { Ω(Spec{}.Text()).Should(BeZero()) }) }) }) Describe("spec.SubjectID()", func() { It("returns the ID of the spec's it node", func() { nIt := N(ntIt) spec := S(N(ntCon), N(ntCon), N(ntBef), nIt, N(ntAf)) Ω(spec.SubjectID()).Should(Equal(nIt.ID)) }) }) Describe("spec.FirstNodeWithType", func() { Context("when there are matching nodes", func() { It("returns the first node matching any of the passed in node types", func() { nBef := N(ntBef) nIt := N(ntIt) spec := S(N(ntCon), N(ntAf), nBef, N(ntBef), nIt, N(ntAf)) Ω(spec.FirstNodeWithType(ntIt | ntBef)).Should(Equal(nBef)) }) }) Context("when no nodes match", func() { It("returns zero", func() { spec := S(N(ntCon), N(ntIt), N(ntAf)) Ω(spec.FirstNodeWithType(ntBef)).Should(BeZero()) }) }) }) Describe("spec.FlakeAttempts", func() { Context("when none of the nodes have FlakeAttempt", func() { It("returns 0", func() { spec := S(N(ntCon), N(ntCon), N(ntIt)) Ω(spec.FlakeAttempts()).Should(Equal(0)) }) }) Context("when a node has FlakeAttempt set", func() { It("returns that FlakeAttempt", func() { spec := S(N(ntCon, FlakeAttempts(3)), N(ntCon), N(ntIt)) Ω(spec.FlakeAttempts()).Should(Equal(3)) spec = S(N(ntCon), N(ntCon, FlakeAttempts(2)), N(ntIt)) Ω(spec.FlakeAttempts()).Should(Equal(2)) spec = S(N(ntCon), N(ntCon), N(ntIt, FlakeAttempts(4))) Ω(spec.FlakeAttempts()).Should(Equal(4)) }) }) Context("when multiple nodes have FlakeAttempt", func() { It("returns the inner-most nested FlakeAttempt", func() { spec := S(N(ntCon, FlakeAttempts(3)), N(ntCon, FlakeAttempts(4)), N(ntIt, FlakeAttempts(2))) Ω(spec.FlakeAttempts()).Should(Equal(2)) }) }) }) Describe("spec.MustPassRepeatedly", func() { Context("when none of the nodes have MustPassRepeatedly", func() { It("returns 0", func() { spec := S(N(ntCon), N(ntCon), N(ntIt)) Ω(spec.MustPassRepeatedly()).Should(Equal(0)) }) }) Context("when a node has MustPassRepeatedly set", func() { It("returns that MustPassRepeatedly", func() { spec := S(N(ntCon, MustPassRepeatedly(3)), N(ntCon), N(ntIt)) Ω(spec.MustPassRepeatedly()).Should(Equal(3)) spec = S(N(ntCon), N(ntCon, MustPassRepeatedly(2)), N(ntIt)) Ω(spec.MustPassRepeatedly()).Should(Equal(2)) spec = S(N(ntCon), N(ntCon), N(ntIt, MustPassRepeatedly(4))) Ω(spec.MustPassRepeatedly()).Should(Equal(4)) }) }) Context("when multiple nodes have MustPassRepeatedly", func() { It("returns the inner-most nested MustPassRepeatedly", func() { spec := S(N(ntCon, MustPassRepeatedly(3)), N(ntCon, MustPassRepeatedly(4)), N(ntIt, MustPassRepeatedly(2))) Ω(spec.MustPassRepeatedly()).Should(Equal(2)) }) }) }) Describe("specs.HasAnySpecsMarkedPending", func() { Context("when there are no specs with any nodes marked pending", func() { It("returns false", func() { specs := Specs{ S(N(), N(), N()), S(N(), N()), } Ω(specs.HasAnySpecsMarkedPending()).Should(BeFalse()) }) }) Context("when there is at least one spec with a node marked pending", func() { It("returns true", func() { specs := Specs{ S(N(), N(), N()), S(N(), N(Pending), N()), S(N(), N()), } Ω(specs.HasAnySpecsMarkedPending()).Should(BeTrue()) }) }) }) Describe("specs.CountWithoutSkip()", func() { It("returns the number of specs that have skip set to false", func() { specs := Specs{{Skip: false}, {Skip: true}, {Skip: true}, {Skip: false}, {Skip: false}} Ω(specs.CountWithoutSkip()).Should(Equal(3)) }) }) Describe("specs.AtIndices", func() { It("returns the subset of specs at the specified indices", func() { specs := Specs{S(N()), S(N()), S(N()), S(N())} Ω(specs.AtIndices(internal.SpecIndices{1, 3})).Should(Equal(Specs{specs[1], specs[3]})) }) }) }) golang-github-onsi-ginkgo-v2-2.22.0/internal/suite.go000066400000000000000000001220551472321612100223730ustar00rootroot00000000000000package internal import ( "fmt" "sync" "time" "github.com/onsi/ginkgo/v2/internal/interrupt_handler" "github.com/onsi/ginkgo/v2/internal/parallel_support" "github.com/onsi/ginkgo/v2/reporters" "github.com/onsi/ginkgo/v2/types" "golang.org/x/net/context" ) type Phase uint const ( PhaseBuildTopLevel Phase = iota PhaseBuildTree PhaseRun ) var PROGRESS_REPORTER_DEADLING = 5 * time.Second type Suite struct { tree *TreeNode topLevelContainers Nodes *ProgressReporterManager phase Phase suiteNodes Nodes cleanupNodes Nodes failer *Failer reporter reporters.Reporter writer WriterInterface outputInterceptor OutputInterceptor interruptHandler interrupt_handler.InterruptHandlerInterface config types.SuiteConfig deadline time.Time skipAll bool report types.Report currentSpecReport types.SpecReport currentNode Node currentNodeStartTime time.Time currentSpecContext *specContext currentByStep types.SpecEvent timelineOrder int /* 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, ProgressReporterManager: NewProgressReporterManager(), selectiveLock: &sync.Mutex{}, } } func (suite *Suite) Clone() (*Suite, error) { if suite.phase != PhaseBuildTopLevel { return nil, fmt.Errorf("cannot clone suite after tree has been built") } return &Suite{ tree: &TreeNode{}, phase: PhaseBuildTopLevel, ProgressReporterManager: NewProgressReporterManager(), topLevelContainers: suite.topLevelContainers.Clone(), suiteNodes: suite.suiteNodes.Clone(), selectiveLock: &sync.Mutex{}, }, nil } func (suite *Suite) BuildTree() error { // During PhaseBuildTopLevel, the top level containers are stored in suite.topLevelCotainers and entered // We now enter PhaseBuildTree where these top level containers are entered and added to the spec tree suite.phase = PhaseBuildTree for _, topLevelContainer := range suite.topLevelContainers { err := suite.PushNode(topLevelContainer) if err != nil { return err } } 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, progressSignalRegistrar ProgressSignalRegistrar, suiteConfig types.SuiteConfig) (bool, bool) { if suite.phase != PhaseBuildTree { panic("cannot run before building the tree = call suite.BuildTree() first") } ApplyNestedFocusPolicyToTree(suite.tree) specs := GenerateSpecsFromTreeRoot(suite.tree) specs, hasProgrammaticFocus := ApplyFocusToSpecs(specs, description, suiteLabels, suiteConfig) suite.phase = PhaseRun suite.client = client suite.failer = failer suite.reporter = reporter suite.writer = writer suite.outputInterceptor = outputInterceptor suite.interruptHandler = interruptHandler suite.config = suiteConfig if suite.config.Timeout > 0 { suite.deadline = time.Now().Add(suite.config.Timeout) } cancelProgressHandler := progressSignalRegistrar(suite.handleProgressSignal) success := suite.runSpecs(description, suiteLabels, suitePath, hasProgrammaticFocus, specs) cancelProgressHandler() return success, hasProgrammaticFocus } func (suite *Suite) InRunPhase() bool { return suite.phase == PhaseRun } /* Tree Construction methods PushNode is used during PhaseBuildTopLevel and PhaseBuildTree */ func (suite *Suite) PushNode(node Node) error { if node.NodeType.Is(types.NodeTypeCleanupInvalid | types.NodeTypeCleanupAfterEach | types.NodeTypeCleanupAfterAll | types.NodeTypeCleanupAfterSuite) { return suite.pushCleanupNode(node) } if node.NodeType.Is(types.NodeTypeBeforeSuite | types.NodeTypeAfterSuite | types.NodeTypeSynchronizedBeforeSuite | types.NodeTypeSynchronizedAfterSuite | types.NodeTypeBeforeSuite | types.NodeTypeReportBeforeSuite | types.NodeTypeReportAfterSuite) { return suite.pushSuiteNode(node) } if suite.phase == PhaseRun { return types.GinkgoErrors.PushingNodeInRunPhase(node.NodeType, node.CodeLocation) } if node.MarkedSerial { firstOrderedNode := suite.tree.AncestorNodeChain().FirstNodeMarkedOrdered() if !firstOrderedNode.IsZero() && !firstOrderedNode.MarkedSerial { return types.GinkgoErrors.InvalidSerialNodeInNonSerialOrderedContainer(node.CodeLocation, node.NodeType) } } if node.NodeType.Is(types.NodeTypeBeforeAll | types.NodeTypeAfterAll) { firstOrderedNode := suite.tree.AncestorNodeChain().FirstNodeMarkedOrdered() if firstOrderedNode.IsZero() { return types.GinkgoErrors.SetupNodeNotInOrderedContainer(node.CodeLocation, node.NodeType) } } if node.MarkedContinueOnFailure { firstOrderedNode := suite.tree.AncestorNodeChain().FirstNodeMarkedOrdered() if !firstOrderedNode.IsZero() { return types.GinkgoErrors.InvalidContinueOnFailureDecoration(node.CodeLocation) } } if node.NodeType == types.NodeTypeContainer { // During PhaseBuildTopLevel we only track the top level containers without entering them // We only enter the top level container nodes during PhaseBuildTree // // This ensures the tree is only constructed after `go spec` has called `flag.Parse()` and gives // the user an opportunity to load suiteConfiguration information in the `TestX` go spec hook just before `RunSpecs` // is invoked. This makes the lifecycle easier to reason about and solves issues like #693. if suite.phase == PhaseBuildTopLevel { suite.topLevelContainers = append(suite.topLevelContainers, node) return nil } if suite.phase == PhaseBuildTree { parentTree := suite.tree suite.tree = &TreeNode{Node: node} parentTree.AppendChild(suite.tree) err := func() (err error) { defer func() { if e := recover(); e != nil { err = types.GinkgoErrors.CaughtPanicDuringABuildPhase(e, node.CodeLocation) } }() node.Body(nil) return err }() suite.tree = parentTree return err } } else { suite.tree.AppendChild(&TreeNode{Node: node}) return nil } return nil } func (suite *Suite) pushSuiteNode(node Node) error { if suite.phase == PhaseBuildTree { return types.GinkgoErrors.SuiteNodeInNestedContext(node.NodeType, node.CodeLocation) } if suite.phase == PhaseRun { return types.GinkgoErrors.SuiteNodeDuringRunPhase(node.NodeType, node.CodeLocation) } switch node.NodeType { case types.NodeTypeBeforeSuite, types.NodeTypeSynchronizedBeforeSuite: existingBefores := suite.suiteNodes.WithType(types.NodeTypeBeforeSuite | types.NodeTypeSynchronizedBeforeSuite) if len(existingBefores) > 0 { return types.GinkgoErrors.MultipleBeforeSuiteNodes(node.NodeType, node.CodeLocation, existingBefores[0].NodeType, existingBefores[0].CodeLocation) } case types.NodeTypeAfterSuite, types.NodeTypeSynchronizedAfterSuite: existingAfters := suite.suiteNodes.WithType(types.NodeTypeAfterSuite | types.NodeTypeSynchronizedAfterSuite) if len(existingAfters) > 0 { return types.GinkgoErrors.MultipleAfterSuiteNodes(node.NodeType, node.CodeLocation, existingAfters[0].NodeType, existingAfters[0].CodeLocation) } } suite.suiteNodes = append(suite.suiteNodes, node) return nil } func (suite *Suite) pushCleanupNode(node Node) error { if suite.phase != PhaseRun || suite.currentNode.IsZero() { return types.GinkgoErrors.PushingCleanupNodeDuringTreeConstruction(node.CodeLocation) } switch suite.currentNode.NodeType { case types.NodeTypeBeforeSuite, types.NodeTypeSynchronizedBeforeSuite, types.NodeTypeAfterSuite, types.NodeTypeSynchronizedAfterSuite: node.NodeType = types.NodeTypeCleanupAfterSuite case types.NodeTypeBeforeAll, types.NodeTypeAfterAll: node.NodeType = types.NodeTypeCleanupAfterAll case types.NodeTypeReportBeforeEach, types.NodeTypeReportAfterEach, types.NodeTypeReportBeforeSuite, types.NodeTypeReportAfterSuite: return types.GinkgoErrors.PushingCleanupInReportingNode(node.CodeLocation, suite.currentNode.NodeType) case types.NodeTypeCleanupInvalid, types.NodeTypeCleanupAfterEach, types.NodeTypeCleanupAfterAll, types.NodeTypeCleanupAfterSuite: return types.GinkgoErrors.PushingCleanupInCleanupNode(node.CodeLocation) default: node.NodeType = types.NodeTypeCleanupAfterEach } node.NodeIDWhereCleanupWasGenerated = suite.currentNode.ID node.NestingLevel = suite.currentNode.NestingLevel suite.selectiveLock.Lock() suite.cleanupNodes = append(suite.cleanupNodes, node) suite.selectiveLock.Unlock() return nil } func (suite *Suite) generateTimelineLocation() types.TimelineLocation { suite.selectiveLock.Lock() defer suite.selectiveLock.Unlock() suite.timelineOrder += 1 return types.TimelineLocation{ Offset: len(suite.currentSpecReport.CapturedGinkgoWriterOutput) + suite.writer.Len(), Order: suite.timelineOrder, Time: time.Now(), } } func (suite *Suite) handleSpecEvent(event types.SpecEvent) types.SpecEvent { event.TimelineLocation = suite.generateTimelineLocation() suite.selectiveLock.Lock() suite.currentSpecReport.SpecEvents = append(suite.currentSpecReport.SpecEvents, event) suite.selectiveLock.Unlock() suite.reporter.EmitSpecEvent(event) return event } func (suite *Suite) handleSpecEventEnd(eventType types.SpecEventType, startEvent types.SpecEvent) { event := startEvent event.SpecEventType = eventType event.TimelineLocation = suite.generateTimelineLocation() event.Duration = event.TimelineLocation.Time.Sub(startEvent.TimelineLocation.Time) suite.selectiveLock.Lock() suite.currentSpecReport.SpecEvents = append(suite.currentSpecReport.SpecEvents, event) suite.selectiveLock.Unlock() suite.reporter.EmitSpecEvent(event) } func (suite *Suite) By(text string, callback ...func()) error { cl := types.NewCodeLocation(2) if suite.phase != PhaseRun { return types.GinkgoErrors.ByNotDuringRunPhase(cl) } event := suite.handleSpecEvent(types.SpecEvent{ SpecEventType: types.SpecEventByStart, CodeLocation: cl, Message: text, }) suite.selectiveLock.Lock() suite.currentByStep = event suite.selectiveLock.Unlock() if len(callback) == 1 { defer func() { suite.selectiveLock.Lock() suite.currentByStep = types.SpecEvent{} suite.selectiveLock.Unlock() suite.handleSpecEventEnd(types.SpecEventByEnd, event) }() callback[0]() } else if len(callback) > 1 { panic("just one callback per By, please") } return nil } /* Spec Running methods - used during PhaseRun */ func (suite *Suite) CurrentSpecReport() types.SpecReport { suite.selectiveLock.Lock() defer suite.selectiveLock.Unlock() report := suite.currentSpecReport if suite.writer != nil { report.CapturedGinkgoWriterOutput = string(suite.writer.Bytes()) } report.ReportEntries = make([]ReportEntry, len(report.ReportEntries)) copy(report.ReportEntries, suite.currentSpecReport.ReportEntries) return report } // Only valid in the preview context. In general suite.report only includes // the specs run by _this_ node - it is only at the end of the suite that // the parallel reports are aggregated. However in the preview context we run // in series and func (suite *Suite) GetPreviewReport() types.Report { suite.selectiveLock.Lock() defer suite.selectiveLock.Unlock() return suite.report } func (suite *Suite) AddReportEntry(entry ReportEntry) error { if suite.phase != PhaseRun { return types.GinkgoErrors.AddReportEntryNotDuringRunPhase(entry.Location) } entry.TimelineLocation = suite.generateTimelineLocation() entry.Time = entry.TimelineLocation.Time suite.selectiveLock.Lock() suite.currentSpecReport.ReportEntries = append(suite.currentSpecReport.ReportEntries, entry) suite.selectiveLock.Unlock() suite.reporter.EmitReportEntry(entry) return nil } func (suite *Suite) generateProgressReport(fullReport bool) types.ProgressReport { timelineLocation := suite.generateTimelineLocation() suite.selectiveLock.Lock() defer suite.selectiveLock.Unlock() deadline, cancel := context.WithTimeout(context.Background(), PROGRESS_REPORTER_DEADLING) defer cancel() var additionalReports []string if suite.currentSpecContext != nil { additionalReports = append(additionalReports, suite.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) 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) report.Message = "{{bold}}You've requested a progress report:{{/}}" suite.emitProgressReport(report) } func (suite *Suite) emitProgressReport(report types.ProgressReport) { 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 } func (suite *Suite) processCurrentSpecReport() { suite.reporter.DidRun(suite.currentSpecReport) if suite.isRunningInParallel() { suite.client.PostDidRun(suite.currentSpecReport) } suite.report.SpecReports = append(suite.report.SpecReports, suite.currentSpecReport) if suite.currentSpecReport.State.Is(types.SpecStateFailureStates) { suite.report.SuiteSucceeded = false if suite.config.FailFast || suite.currentSpecReport.State.Is(types.SpecStateAborted) { suite.skipAll = true if suite.isRunningInParallel() { suite.client.PostAbort() } } } } func (suite *Suite) runSpecs(description string, suiteLabels Labels, suitePath string, hasProgrammaticFocus bool, specs Specs) bool { numSpecsThatWillBeRun := specs.CountWithoutSkip() suite.report = types.Report{ SuitePath: suitePath, SuiteDescription: description, SuiteLabels: suiteLabels, SuiteConfig: suite.config, SuiteHasProgrammaticFocus: hasProgrammaticFocus, PreRunStats: types.PreRunStats{ TotalSpecs: len(specs), SpecsThatWillRun: numSpecsThatWillBeRun, }, StartTime: time.Now(), } suite.reporter.SuiteWillBegin(suite.report) if suite.isRunningInParallel() { suite.client.PostSuiteWillBegin(suite.report) } suite.report.SuiteSucceeded = true suite.runReportSuiteNodesIfNeedBe(types.NodeTypeReportBeforeSuite) ranBeforeSuite := suite.report.SuiteSucceeded if suite.report.SuiteSucceeded { suite.runBeforeSuite(numSpecsThatWillBeRun) } if suite.report.SuiteSucceeded { groupedSpecIndices, serialGroupedSpecIndices := OrderSpecs(specs, suite.config) nextIndex := MakeIncrementingIndexCounter() if suite.isRunningInParallel() { nextIndex = suite.client.FetchNextCounter } for { groupedSpecIdx, err := nextIndex() if err != nil { suite.report.SpecialSuiteFailureReasons = append(suite.report.SpecialSuiteFailureReasons, fmt.Sprintf("Failed to iterate over specs:\n%s", err.Error())) suite.report.SuiteSucceeded = false break } if groupedSpecIdx >= len(groupedSpecIndices) { if suite.config.ParallelProcess == 1 && len(serialGroupedSpecIndices) > 0 { groupedSpecIndices, serialGroupedSpecIndices, nextIndex = serialGroupedSpecIndices, GroupedSpecIndices{}, MakeIncrementingIndexCounter() suite.client.BlockUntilNonprimaryProcsHaveFinished() continue } break } // the complexity for running groups of specs is very high because of Ordered containers and FlakeAttempts // we encapsulate that complexity in the notion of a Group that can run // Group is really just an extension of suite so it gets passed a suite and has access to all its internals // Note that group is stateful and intended for single use! newGroup(suite).run(specs.AtIndices(groupedSpecIndices[groupedSpecIdx])) } if suite.config.FailOnPending && specs.HasAnySpecsMarkedPending() { suite.report.SpecialSuiteFailureReasons = append(suite.report.SpecialSuiteFailureReasons, "Detected pending specs and --fail-on-pending is set") suite.report.SuiteSucceeded = false } if suite.config.FailOnEmpty && specs.CountWithoutSkip() == 0 { suite.report.SpecialSuiteFailureReasons = append(suite.report.SpecialSuiteFailureReasons, "Detected no specs ran and --fail-on-empty is set") suite.report.SuiteSucceeded = false } } if ranBeforeSuite { suite.runAfterSuiteCleanup(numSpecsThatWillBeRun) } interruptStatus := suite.interruptHandler.Status() if interruptStatus.Interrupted() { suite.report.SpecialSuiteFailureReasons = append(suite.report.SpecialSuiteFailureReasons, interruptStatus.Cause.String()) suite.report.SuiteSucceeded = false } suite.report.EndTime = time.Now() suite.report.RunTime = suite.report.EndTime.Sub(suite.report.StartTime) if !suite.deadline.IsZero() && suite.report.EndTime.After(suite.deadline) { suite.report.SpecialSuiteFailureReasons = append(suite.report.SpecialSuiteFailureReasons, "Suite Timeout Elapsed") suite.report.SuiteSucceeded = false } suite.runReportSuiteNodesIfNeedBe(types.NodeTypeReportAfterSuite) suite.reporter.SuiteDidEnd(suite.report) if suite.isRunningInParallel() { suite.client.PostSuiteDidEnd(suite.report) } return suite.report.SuiteSucceeded } func (suite *Suite) runBeforeSuite(numSpecsThatWillBeRun int) { beforeSuiteNode := suite.suiteNodes.FirstNodeWithType(types.NodeTypeBeforeSuite | types.NodeTypeSynchronizedBeforeSuite) if !beforeSuiteNode.IsZero() && numSpecsThatWillBeRun > 0 { suite.selectiveLock.Lock() suite.currentSpecReport = types.SpecReport{ LeafNodeType: beforeSuiteNode.NodeType, LeafNodeLocation: beforeSuiteNode.CodeLocation, ParallelProcess: suite.config.ParallelProcess, RunningInParallel: suite.isRunningInParallel(), } suite.selectiveLock.Unlock() suite.reporter.WillRun(suite.currentSpecReport) suite.runSuiteNode(beforeSuiteNode) if suite.currentSpecReport.State.Is(types.SpecStateSkipped) { suite.report.SpecialSuiteFailureReasons = append(suite.report.SpecialSuiteFailureReasons, "Suite skipped in BeforeSuite") suite.skipAll = true } suite.processCurrentSpecReport() } } 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, RunningInParallel: suite.isRunningInParallel(), } suite.selectiveLock.Unlock() suite.reporter.WillRun(suite.currentSpecReport) suite.runSuiteNode(afterSuiteNode) suite.processCurrentSpecReport() } 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, RunningInParallel: suite.isRunningInParallel(), } suite.selectiveLock.Unlock() suite.reporter.WillRun(suite.currentSpecReport) suite.runSuiteNode(cleanupNode) suite.processCurrentSpecReport() } } } func (suite *Suite) reportEach(spec Spec, nodeType types.NodeType) { nodes := spec.Nodes.WithType(nodeType) if nodeType == types.NodeTypeReportAfterEach { nodes = nodes.SortedByDescendingNestingLevel() } if nodeType == types.NodeTypeReportBeforeEach { nodes = nodes.SortedByAscendingNestingLevel() } if len(nodes) == 0 { return } for i := range nodes { suite.writer.Truncate() suite.outputInterceptor.StartInterceptingOutput() report := suite.currentSpecReport nodes[i].Body = func(ctx SpecContext) { nodes[i].ReportEachBody(ctx, report) } state, failure := suite.runNode(nodes[i], time.Time{}, spec.Nodes.BestTextFor(nodes[i])) // If the spec is not in a failure state (i.e. it's Passed/Skipped/Pending) and the reporter has failed, override the state. // Also, if the reporter is every aborted - always override the state to propagate the abort if (!suite.currentSpecReport.State.Is(types.SpecStateFailureStates) && state.Is(types.SpecStateFailureStates)) || state.Is(types.SpecStateAborted) { suite.currentSpecReport.State = state suite.currentSpecReport.Failure = failure } suite.currentSpecReport.CapturedGinkgoWriterOutput += string(suite.writer.Bytes()) suite.currentSpecReport.CapturedStdOutErr += suite.outputInterceptor.StopInterceptingAndReturnOutput() } } func (suite *Suite) runSuiteNode(node Node) { if suite.config.DryRun { suite.currentSpecReport.State = types.SpecStatePassed return } suite.writer.Truncate() suite.outputInterceptor.StartInterceptingOutput() suite.currentSpecReport.StartTime = time.Now() var err error switch node.NodeType { case types.NodeTypeBeforeSuite, types.NodeTypeAfterSuite: suite.currentSpecReport.State, suite.currentSpecReport.Failure = suite.runNode(node, time.Time{}, "") case types.NodeTypeCleanupAfterSuite: if suite.config.ParallelTotal > 1 && suite.config.ParallelProcess == 1 { err = suite.client.BlockUntilNonprimaryProcsHaveFinished() } if err == nil { suite.currentSpecReport.State, suite.currentSpecReport.Failure = suite.runNode(node, time.Time{}, "") } case types.NodeTypeSynchronizedBeforeSuite: var data []byte var runAllProcs bool if suite.config.ParallelProcess == 1 { if suite.config.ParallelTotal > 1 { suite.outputInterceptor.StopInterceptingAndReturnOutput() suite.outputInterceptor.StartInterceptingOutputAndForwardTo(suite.client) } node.Body = func(c SpecContext) { data = node.SynchronizedBeforeSuiteProc1Body(c) } node.HasContext = node.SynchronizedBeforeSuiteProc1BodyHasContext suite.currentSpecReport.State, suite.currentSpecReport.Failure = suite.runNode(node, time.Time{}, "") if suite.config.ParallelTotal > 1 { suite.currentSpecReport.CapturedStdOutErr += suite.outputInterceptor.StopInterceptingAndReturnOutput() suite.outputInterceptor.StartInterceptingOutput() if suite.currentSpecReport.State.Is(types.SpecStatePassed) { err = suite.client.PostSynchronizedBeforeSuiteCompleted(types.SpecStatePassed, data) } else { err = suite.client.PostSynchronizedBeforeSuiteCompleted(suite.currentSpecReport.State, nil) } } runAllProcs = suite.currentSpecReport.State.Is(types.SpecStatePassed) && err == nil } else { var proc1State types.SpecState proc1State, data, err = suite.client.BlockUntilSynchronizedBeforeSuiteData() switch proc1State { case types.SpecStatePassed: runAllProcs = true case types.SpecStateFailed, types.SpecStatePanicked, types.SpecStateTimedout: err = types.GinkgoErrors.SynchronizedBeforeSuiteFailedOnProc1() case types.SpecStateInterrupted, types.SpecStateAborted, types.SpecStateSkipped: suite.currentSpecReport.State = proc1State } } if runAllProcs { node.Body = func(c SpecContext) { node.SynchronizedBeforeSuiteAllProcsBody(c, data) } node.HasContext = node.SynchronizedBeforeSuiteAllProcsBodyHasContext suite.currentSpecReport.State, suite.currentSpecReport.Failure = suite.runNode(node, time.Time{}, "") } case types.NodeTypeSynchronizedAfterSuite: node.Body = node.SynchronizedAfterSuiteAllProcsBody node.HasContext = node.SynchronizedAfterSuiteAllProcsBodyHasContext suite.currentSpecReport.State, suite.currentSpecReport.Failure = suite.runNode(node, time.Time{}, "") if suite.config.ParallelProcess == 1 { if suite.config.ParallelTotal > 1 { err = suite.client.BlockUntilNonprimaryProcsHaveFinished() } if err == nil { if suite.config.ParallelTotal > 1 { suite.currentSpecReport.CapturedStdOutErr += suite.outputInterceptor.StopInterceptingAndReturnOutput() suite.outputInterceptor.StartInterceptingOutputAndForwardTo(suite.client) } node.Body = node.SynchronizedAfterSuiteProc1Body node.HasContext = node.SynchronizedAfterSuiteProc1BodyHasContext state, failure := suite.runNode(node, time.Time{}, "") if suite.currentSpecReport.State.Is(types.SpecStatePassed) { suite.currentSpecReport.State, suite.currentSpecReport.Failure = state, failure } } } } if err != nil && !suite.currentSpecReport.State.Is(types.SpecStateFailureStates) { suite.currentSpecReport.State, suite.currentSpecReport.Failure = types.SpecStateFailed, suite.failureForLeafNodeWithMessage(node, err.Error()) suite.reporter.EmitFailure(suite.currentSpecReport.State, suite.currentSpecReport.Failure) } suite.currentSpecReport.EndTime = time.Now() suite.currentSpecReport.RunTime = suite.currentSpecReport.EndTime.Sub(suite.currentSpecReport.StartTime) suite.currentSpecReport.CapturedGinkgoWriterOutput = string(suite.writer.Bytes()) suite.currentSpecReport.CapturedStdOutErr += suite.outputInterceptor.StopInterceptingAndReturnOutput() } func (suite *Suite) runReportSuiteNodesIfNeedBe(nodeType types.NodeType) { nodes := suite.suiteNodes.WithType(nodeType) // only run ReportAfterSuite on proc 1 if nodeType.Is(types.NodeTypeReportAfterSuite) && suite.config.ParallelProcess != 1 { return } // if we're running ReportBeforeSuite on proc > 1 - we should wait until proc 1 has completed if nodeType.Is(types.NodeTypeReportBeforeSuite) && suite.config.ParallelProcess != 1 && len(nodes) > 0 { state, err := suite.client.BlockUntilReportBeforeSuiteCompleted() if err != nil || state.Is(types.SpecStateFailed) { suite.report.SuiteSucceeded = false } return } for _, node := range nodes { suite.selectiveLock.Lock() suite.currentSpecReport = types.SpecReport{ LeafNodeType: node.NodeType, LeafNodeLocation: node.CodeLocation, LeafNodeText: node.Text, ParallelProcess: suite.config.ParallelProcess, RunningInParallel: suite.isRunningInParallel(), } suite.selectiveLock.Unlock() suite.reporter.WillRun(suite.currentSpecReport) suite.runReportSuiteNode(node, suite.report) suite.processCurrentSpecReport() } // if we're running ReportBeforeSuite and we're running in parallel - we shuld tell the other procs that we're done if nodeType.Is(types.NodeTypeReportBeforeSuite) && suite.isRunningInParallel() && len(nodes) > 0 { if suite.report.SuiteSucceeded { suite.client.PostReportBeforeSuiteCompleted(types.SpecStatePassed) } else { suite.client.PostReportBeforeSuiteCompleted(types.SpecStateFailed) } } } func (suite *Suite) runReportSuiteNode(node Node, report types.Report) { suite.writer.Truncate() suite.outputInterceptor.StartInterceptingOutput() suite.currentSpecReport.StartTime = time.Now() // if we're running a ReportAfterSuite in parallel (on proc 1) we (a) wait until other procs have exited and // (b) always fetch the latest report as prior ReportAfterSuites will contribute to it if node.NodeType.Is(types.NodeTypeReportAfterSuite) && suite.isRunningInParallel() { aggregatedReport, err := suite.client.BlockUntilAggregatedNonprimaryProcsReport() if err != nil { suite.currentSpecReport.State, suite.currentSpecReport.Failure = types.SpecStateFailed, suite.failureForLeafNodeWithMessage(node, err.Error()) suite.reporter.EmitFailure(suite.currentSpecReport.State, suite.currentSpecReport.Failure) return } report = report.Add(aggregatedReport) } node.Body = func(ctx SpecContext) { node.ReportSuiteBody(ctx, report) } suite.currentSpecReport.State, suite.currentSpecReport.Failure = suite.runNode(node, time.Time{}, "") suite.currentSpecReport.EndTime = time.Now() suite.currentSpecReport.RunTime = suite.currentSpecReport.EndTime.Sub(suite.currentSpecReport.StartTime) suite.currentSpecReport.CapturedGinkgoWriterOutput = string(suite.writer.Bytes()) suite.currentSpecReport.CapturedStdOutErr = suite.outputInterceptor.StopInterceptingAndReturnOutput() } func (suite *Suite) runNode(node Node, specDeadline time.Time, text string) (types.SpecState, types.Failure) { if node.NodeType.Is(types.NodeTypeCleanupAfterEach | types.NodeTypeCleanupAfterAll | types.NodeTypeCleanupAfterSuite) { suite.cleanupNodes = suite.cleanupNodes.WithoutNode(node) } interruptStatus := suite.interruptHandler.Status() if interruptStatus.Level == interrupt_handler.InterruptLevelBailOut { return types.SpecStateSkipped, types.Failure{} } if interruptStatus.Level == interrupt_handler.InterruptLevelReportOnly && !node.NodeType.Is(types.NodeTypesAllowedDuringReportInterrupt) { return types.SpecStateSkipped, types.Failure{} } if interruptStatus.Level == interrupt_handler.InterruptLevelCleanupAndReport && !node.NodeType.Is(types.NodeTypesAllowedDuringReportInterrupt|types.NodeTypesAllowedDuringCleanupInterrupt) { return types.SpecStateSkipped, types.Failure{} } suite.selectiveLock.Lock() suite.currentNode = node suite.currentNodeStartTime = time.Now() suite.currentByStep = types.SpecEvent{} suite.selectiveLock.Unlock() defer func() { suite.selectiveLock.Lock() suite.currentNode = Node{} suite.currentNodeStartTime = time.Time{} suite.selectiveLock.Unlock() }() if text == "" { text = "TOP-LEVEL" } event := suite.handleSpecEvent(types.SpecEvent{ SpecEventType: types.SpecEventNodeStart, NodeType: node.NodeType, Message: text, CodeLocation: node.CodeLocation, }) defer func() { suite.handleSpecEventEnd(types.SpecEventNodeEnd, event) }() var failure types.Failure failure.FailureNodeType, failure.FailureNodeLocation = node.NodeType, node.CodeLocation if node.NodeType.Is(types.NodeTypeIt) || node.NodeType.Is(types.NodeTypesForSuiteLevelNodes) { failure.FailureNodeContext = types.FailureNodeIsLeafNode } else if node.NestingLevel <= 0 { failure.FailureNodeContext = types.FailureNodeAtTopLevel } else { failure.FailureNodeContext, failure.FailureNodeContainerIndex = types.FailureNodeInContainer, node.NestingLevel-1 } var outcome types.SpecState gracePeriod := suite.config.GracePeriod if node.GracePeriod >= 0 { gracePeriod = node.GracePeriod } now := time.Now() deadline := suite.deadline timeoutInPlay := "suite" if deadline.IsZero() || (!specDeadline.IsZero() && specDeadline.Before(deadline)) { deadline = specDeadline timeoutInPlay = "spec" } if node.NodeTimeout > 0 && (deadline.IsZero() || deadline.Sub(now) > node.NodeTimeout) { deadline = now.Add(node.NodeTimeout) timeoutInPlay = "node" } if (!deadline.IsZero() && deadline.Before(now)) || interruptStatus.Interrupted() { // we're out of time already. let's wait for a NodeTimeout if we have it, or GracePeriod if we don't if node.NodeTimeout > 0 { deadline = now.Add(node.NodeTimeout) timeoutInPlay = "node" } else { deadline = now.Add(gracePeriod) timeoutInPlay = "grace period" } } 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 // - clean up nodes have up to GracePeriod (formerly hard-coded at 30s) to complete before they are interrupted gracePeriod = 0 } sc := NewSpecContext(suite) defer sc.cancel(fmt.Errorf("spec has finished")) suite.selectiveLock.Lock() suite.currentSpecContext = sc suite.selectiveLock.Unlock() var deadlineChannel <-chan time.Time if !deadline.IsZero() { deadlineChannel = time.After(deadline.Sub(now)) } var gracePeriodChannel <-chan time.Time outcomeC := make(chan types.SpecState) failureC := make(chan types.Failure) go func() { finished := false defer func() { if e := recover(); e != nil || !finished { suite.failer.Panic(types.NewCodeLocationWithStackTrace(2), e) } outcomeFromRun, failureFromRun := suite.failer.Drain() failureFromRun.TimelineLocation = suite.generateTimelineLocation() outcomeC <- outcomeFromRun failureC <- failureFromRun }() node.Body(sc) finished = true }() // progress polling timer and channel 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() } // now we wait for an outcome, an interrupt, a timeout, or a progress poll for { select { case outcomeFromRun := <-outcomeC: failureFromRun := <-failureC if outcome.Is(types.SpecStateInterrupted | types.SpecStateTimedout) { // we've already been interrupted/timed out. we just managed to actually exit // before the grace period elapsed // if we have a failure message we attach it as an additional failure if outcomeFromRun != types.SpecStatePassed { additionalFailure := types.AdditionalFailure{ State: outcomeFromRun, Failure: failure, // we make a copy - this will include all the configuration set up above... } // ...and then we update the failure with the details from failureFromRun additionalFailure.Failure.Location, additionalFailure.Failure.ForwardedPanic, additionalFailure.Failure.TimelineLocation = failureFromRun.Location, failureFromRun.ForwardedPanic, failureFromRun.TimelineLocation additionalFailure.Failure.ProgressReport = types.ProgressReport{} if outcome == types.SpecStateTimedout { additionalFailure.Failure.Message = fmt.Sprintf("A %s timeout occurred and then the following failure was recorded in the timedout node before it exited:\n%s", timeoutInPlay, failureFromRun.Message) } else { additionalFailure.Failure.Message = fmt.Sprintf("An interrupt occurred and then the following failure was recorded in the interrupted node before it exited:\n%s", failureFromRun.Message) } suite.reporter.EmitFailure(additionalFailure.State, additionalFailure.Failure) failure.AdditionalFailure = &additionalFailure } return outcome, failure } if outcomeFromRun.Is(types.SpecStatePassed) { return outcomeFromRun, types.Failure{} } else { failure.Message, failure.Location, failure.ForwardedPanic, failure.TimelineLocation = failureFromRun.Message, failureFromRun.Location, failureFromRun.ForwardedPanic, failureFromRun.TimelineLocation suite.reporter.EmitFailure(outcomeFromRun, failure) return outcomeFromRun, failure } case <-gracePeriodChannel: if node.HasContext && outcome.Is(types.SpecStateTimedout) { report := suite.generateProgressReport(false) report.Message = "{{bold}}{{orange}}A running node failed to exit in time{{/}}\nGinkgo is moving on but a node has timed out and failed to exit before its grace period elapsed. The node has now leaked and is running in the background.\nHere's a current progress report:" suite.emitProgressReport(report) } return outcome, failure case <-deadlineChannel: // we're out of time - the outcome is a timeout and we capture the failure and progress report outcome = types.SpecStateTimedout failure.Message, failure.Location, failure.TimelineLocation = fmt.Sprintf("A %s timeout occurred", timeoutInPlay), node.CodeLocation, suite.generateTimelineLocation() failure.ProgressReport = suite.generateProgressReport(false).WithoutCapturedGinkgoWriterOutput() failure.ProgressReport.Message = fmt.Sprintf("{{bold}}This is the Progress Report generated when the %s timeout occurred:{{/}}", timeoutInPlay) deadlineChannel = nil suite.reporter.EmitFailure(outcome, failure) // tell the spec to stop. it's important we generate the progress report first to make sure we capture where // the spec is actually stuck sc.cancel(fmt.Errorf("%s timeout occurred", timeoutInPlay)) // and now we wait for the grace period gracePeriodChannel = time.After(gracePeriod) case <-interruptStatus.Channel: interruptStatus = suite.interruptHandler.Status() // ignore interruption from other process if we are cleaning up or reporting if interruptStatus.Cause == interrupt_handler.InterruptCauseAbortByOtherProcess && node.NodeType.Is(types.NodeTypesAllowedDuringReportInterrupt|types.NodeTypesAllowedDuringCleanupInterrupt) { continue } deadlineChannel = nil // don't worry about deadlines, time's up now failureTimelineLocation := suite.generateTimelineLocation() progressReport := suite.generateProgressReport(true) if outcome == types.SpecStateInvalid { outcome = types.SpecStateInterrupted failure.Message, failure.Location, failure.TimelineLocation = interruptStatus.Message(), node.CodeLocation, failureTimelineLocation if interruptStatus.ShouldIncludeProgressReport() { failure.ProgressReport = progressReport.WithoutCapturedGinkgoWriterOutput() failure.ProgressReport.Message = "{{bold}}This is the Progress Report generated when the interrupt was received:{{/}}" } suite.reporter.EmitFailure(outcome, failure) } progressReport = progressReport.WithoutOtherGoroutines() sc.cancel(fmt.Errorf(interruptStatus.Message())) if interruptStatus.Level == interrupt_handler.InterruptLevelBailOut { if interruptStatus.ShouldIncludeProgressReport() { progressReport.Message = fmt.Sprintf("{{bold}}{{orange}}%s{{/}}\n{{bold}}{{red}}Final interrupt received{{/}}; Ginkgo will not run any cleanup or reporting nodes and will terminate as soon as possible.\nHere's a current progress report:", interruptStatus.Message()) suite.emitProgressReport(progressReport) } return outcome, failure } if interruptStatus.ShouldIncludeProgressReport() { if interruptStatus.Level == interrupt_handler.InterruptLevelCleanupAndReport { progressReport.Message = fmt.Sprintf("{{bold}}{{orange}}%s{{/}}\nFirst interrupt received; Ginkgo will run any cleanup and reporting nodes but will skip all remaining specs. {{bold}}Interrupt again to skip cleanup{{/}}.\nHere's a current progress report:", interruptStatus.Message()) } else if interruptStatus.Level == interrupt_handler.InterruptLevelReportOnly { progressReport.Message = fmt.Sprintf("{{bold}}{{orange}}%s{{/}}\nSecond interrupt received; Ginkgo will run any reporting nodes but will skip all remaining specs and cleanup nodes. {{bold}}Interrupt again to bail immediately{{/}}.\nHere's a current progress report:", interruptStatus.Message()) } suite.emitProgressReport(progressReport) } if gracePeriodChannel == nil { // we haven't given grace yet... so let's gracePeriodChannel = time.After(gracePeriod) } else { // we've already given grace. time's up. now. return outcome, failure } case <-emitProgressNow: report := suite.generateProgressReport(false) report.Message = "{{bold}}Automatically polling progress:{{/}}" suite.emitProgressReport(report) if pollProgressInterval > 0 { progressPoller.Reset(pollProgressInterval) } } } } // TODO: search for usages and consider if reporter.EmitFailure() is necessary func (suite *Suite) failureForLeafNodeWithMessage(node Node, message string) types.Failure { return types.Failure{ Message: message, Location: node.CodeLocation, TimelineLocation: suite.generateTimelineLocation(), FailureNodeContext: types.FailureNodeIsLeafNode, FailureNodeType: node.NodeType, FailureNodeLocation: node.CodeLocation, } } func max(a, b int) int { if a > b { return a } return b } golang-github-onsi-ginkgo-v2-2.22.0/internal/suite_test.go000066400000000000000000000445611472321612100234370ustar00rootroot00000000000000package internal_test import ( "io" . "github.com/onsi/ginkgo/v2" "github.com/onsi/ginkgo/v2/internal" "github.com/onsi/ginkgo/v2/internal/interrupt_handler" "github.com/onsi/ginkgo/v2/internal/parallel_support" . "github.com/onsi/ginkgo/v2/internal/test_helpers" "github.com/onsi/ginkgo/v2/types" . "github.com/onsi/gomega" ) var _ = Describe("Suite", func() { It("is heavily integration tested over in internal_integration", func() { }) var suite *internal.Suite var failer *internal.Failer var reporter *FakeReporter var writer *internal.Writer var outputInterceptor *FakeOutputInterceptor var interruptHandler *interrupt_handler.InterruptHandler var conf types.SuiteConfig var rt *RunTracker var client parallel_support.Client BeforeEach(func() { failer = internal.NewFailer() reporter = NewFakeReporter() writer = internal.NewWriter(io.Discard) outputInterceptor = NewFakeOutputInterceptor() client = nil interruptHandler = interrupt_handler.NewInterruptHandler(client) DeferCleanup(interruptHandler.Stop) conf = types.SuiteConfig{ ParallelTotal: 1, ParallelProcess: 1, } rt = NewRunTracker() suite = internal.NewSuite() }) Describe("Constructing Trees", func() { Describe("PhaseBuildTopLevel vs PhaseBuildTree", func() { var err1, err2, err3 error BeforeEach(func() { err1 = suite.PushNode(N(ntCon, "a top-level container", func() { rt.Run("traversing outer") err2 = suite.PushNode(N(ntCon, "a nested container", func() { rt.Run("traversing nested") err3 = suite.PushNode(N(ntIt, "an it", rt.T("running it"))) })) })) }) It("only traverses top-level containers when told to BuildTree", func() { Ω(rt).Should(HaveTrackedNothing()) Ω(suite.BuildTree()).Should(Succeed()) Ω(rt).Should(HaveTracked("traversing outer", "traversing nested")) rt.Reset() suite.Run("suite", Labels{}, "/path/to/suite", failer, reporter, writer, outputInterceptor, interruptHandler, client, internal.RegisterForProgressSignal, conf) Ω(rt).Should(HaveTracked("running it")) Ω(err1).ShouldNot(HaveOccurred()) Ω(err2).ShouldNot(HaveOccurred()) Ω(err3).ShouldNot(HaveOccurred()) }) }) Describe("cloning a suite", func() { var err1, err2, err3 error BeforeEach(func() { suite.PushNode(N(types.NodeTypeBeforeSuite, "before suite", func() { rt.Run("before-suite") })) err1 = suite.PushNode(N(ntCon, "a top-level container", func() { rt.Run("traversing outer") err2 = suite.PushNode(N(ntCon, "a nested container", func() { rt.Run("traversing nested") err3 = suite.PushNode(N(ntIt, "an it", rt.T("running it"))) })) })) }) It("fails if the tree has already been built", func() { Ω(suite.BuildTree()).Should(Succeed()) _, err := suite.Clone() Ω(err).Should(MatchError("cannot clone suite after tree has been built")) }) It("generates the same tree as the original", func() { clone, err := suite.Clone() Ω(err).ShouldNot(HaveOccurred()) Ω(suite.BuildTree()).Should(Succeed()) Ω(rt).Should(HaveTracked("traversing outer", "traversing nested")) rt.Reset() suite.Run("suite", Labels{}, "/path/to/suite", failer, reporter, writer, outputInterceptor, interruptHandler, client, internal.RegisterForProgressSignal, conf) Ω(rt).Should(HaveTracked("before-suite", "running it")) Ω(err1).ShouldNot(HaveOccurred()) Ω(err2).ShouldNot(HaveOccurred()) Ω(err3).ShouldNot(HaveOccurred()) suite = clone // this is what the swapping of globals looks in reality rt.Reset() Ω(clone.BuildTree()).Should(Succeed()) Ω(rt).Should(HaveTracked("traversing outer", "traversing nested")) rt.Reset() clone.Run("suite", Labels{}, "/path/to/suite", failer, reporter, writer, outputInterceptor, interruptHandler, client, internal.RegisterForProgressSignal, conf) Ω(rt).Should(HaveTracked("before-suite", "running it")) Ω(err1).ShouldNot(HaveOccurred()) Ω(err2).ShouldNot(HaveOccurred()) Ω(err3).ShouldNot(HaveOccurred()) }) }) Describe("InRunPhase", func() { It("returns true when in the run phase and false when not in the run phase", func() { falsey := true truey := false err := suite.PushNode(N(ntCon, "a top-level container", func() { falsey = suite.InRunPhase() suite.PushNode(N(ntIt, "an it", func() { truey = suite.InRunPhase() })) })) Ω(suite.BuildTree()).Should(Succeed()) suite.Run("suite", Labels{}, "/path/to/suite", failer, reporter, writer, outputInterceptor, interruptHandler, client, internal.RegisterForProgressSignal, conf) Ω(err).ShouldNot(HaveOccurred()) Ω(truey).Should(BeTrue()) Ω(falsey).Should(BeFalse()) }) }) Context("when pushing nodes during PhaseRun", func() { var pushNodeErrDuringRun error BeforeEach(func() { err := suite.PushNode(N(ntCon, "a top-level container", func() { suite.PushNode(N(ntIt, "an it", func() { rt.Run("in it") pushNodeErrDuringRun = suite.PushNode(N(ntIt, "oops - illegal operation", cl, rt.T("illegal"))) })) })) Ω(err).ShouldNot(HaveOccurred()) Ω(suite.BuildTree()).Should(Succeed()) }) It("errors", func() { suite.Run("suite", Labels{}, "/path/to/suite", failer, reporter, writer, outputInterceptor, interruptHandler, client, internal.RegisterForProgressSignal, conf) Ω(pushNodeErrDuringRun).Should(HaveOccurred()) Ω(rt).Should(HaveTracked("in it")) }) }) Context("when the user attempts to fail during PhaseBuildTree", func() { BeforeEach(func() { suite.PushNode(N(ntCon, "a top-level container", func() { failer.Fail("boom", cl) panic("simulate ginkgo panic") })) }) It("errors", func() { err := suite.BuildTree() Ω(err.Error()).Should(ContainSubstring(cl.String())) Ω(err.Error()).Should(ContainSubstring("simulate ginkgo panic")) }) }) Context("when the user panics during PhaseBuildTree", func() { BeforeEach(func() { suite.PushNode(N(ntCon, "a top-level container", func() { panic("boom") })) }) It("errors", func() { err := suite.BuildTree() Ω(err).Should(HaveOccurred()) Ω(err.Error()).Should(ContainSubstring("boom")) }) }) Describe("Suite Nodes", func() { Context("when pushing suite nodes at the top level", func() { BeforeEach(func() { err := suite.PushNode(N(types.NodeTypeBeforeSuite)) Ω(err).ShouldNot(HaveOccurred()) err = suite.PushNode(N(types.NodeTypeAfterSuite)) Ω(err).ShouldNot(HaveOccurred()) }) Context("when pushing more than one BeforeSuite node", func() { It("errors", func() { err := suite.PushNode(N(types.NodeTypeBeforeSuite)) Ω(err).Should(HaveOccurred()) err = suite.PushNode(N(types.NodeTypeSynchronizedBeforeSuite, func() []byte { return nil }, func([]byte) {})) Ω(err).Should(HaveOccurred()) }) }) Context("when pushing more than one AfterSuite node", func() { It("errors", func() { err := suite.PushNode(N(types.NodeTypeAfterSuite)) Ω(err).Should(HaveOccurred()) err = suite.PushNode(N(types.NodeTypeSynchronizedAfterSuite, func() {}, func() {})) Ω(err).Should(HaveOccurred()) }) }) }) Context("when pushing a serial node in an ordered container", func() { Context("when the outer-most ordered container is marked serial", func() { It("succeeds", func() { var errors = make([]error, 3) errors[0] = suite.PushNode(N(ntCon, "top-level-container", Ordered, Serial, func() { errors[1] = suite.PushNode(N(ntCon, "inner-container", func() { errors[2] = suite.PushNode(N(ntIt, "it", Serial, func() {})) })) })) Ω(errors[0]).ShouldNot(HaveOccurred()) Ω(suite.BuildTree()).Should(Succeed()) Ω(errors[1]).ShouldNot(HaveOccurred()) Ω(errors[2]).ShouldNot(HaveOccurred()) }) }) Context("when the outer-most ordered container is not marked serial", func() { It("errors", func() { var errors = make([]error, 3) errors[0] = suite.PushNode(N(ntCon, "top-level-container", Ordered, func() { errors[1] = suite.PushNode(N(ntCon, "inner-container", func() { errors[2] = suite.PushNode(N(ntIt, "it", Serial, cl, func() {})) })) })) Ω(errors[0]).ShouldNot(HaveOccurred()) Ω(suite.BuildTree()).Should(Succeed()) Ω(errors[1]).ShouldNot(HaveOccurred()) Ω(errors[2]).Should(MatchError(types.GinkgoErrors.InvalidSerialNodeInNonSerialOrderedContainer(cl, ntIt))) }) }) }) Context("when pushing BeforeAll and AfterAll nodes", func() { Context("in an ordered container", func() { It("succeeds", func() { var errors = make([]error, 3) errors[0] = suite.PushNode(N(ntCon, "top-level-container", Ordered, func() { errors[1] = suite.PushNode(N(types.NodeTypeBeforeAll, func() {})) errors[2] = suite.PushNode(N(types.NodeTypeAfterAll, func() {})) })) Ω(errors[0]).ShouldNot(HaveOccurred()) Ω(suite.BuildTree()).Should(Succeed()) Ω(errors[1]).ShouldNot(HaveOccurred()) Ω(errors[2]).ShouldNot(HaveOccurred()) }) }) Context("anywhere else", func() { It("errors", func() { var errors = make([]error, 3) errors[0] = suite.PushNode(N(ntCon, "top-level-container", func() { errors[1] = suite.PushNode(N(types.NodeTypeBeforeAll, cl, func() {})) errors[2] = suite.PushNode(N(types.NodeTypeAfterAll, cl, func() {})) })) Ω(errors[0]).ShouldNot(HaveOccurred()) Ω(suite.BuildTree()).Should(Succeed()) Ω(errors[1]).Should(MatchError(types.GinkgoErrors.SetupNodeNotInOrderedContainer(cl, types.NodeTypeBeforeAll))) Ω(errors[2]).Should(MatchError(types.GinkgoErrors.SetupNodeNotInOrderedContainer(cl, types.NodeTypeAfterAll))) }) }) }) Context("when pushing a ContinueOnFailure Ordered container", func() { Context("that is the top-level Ordered container", func() { It("succeeds", func() { var errors = make([]error, 3) errors[0] = suite.PushNode(N(ntCon, "top-level-container", func() { errors[1] = suite.PushNode(N(ntCon, "ordered-container", Ordered, ContinueOnFailure, func() { errors[2] = suite.PushNode(N(types.NodeTypeIt, "spec", func() {})) })) })) Ω(errors[0]).ShouldNot(HaveOccurred()) Ω(suite.BuildTree()).Should(Succeed()) Ω(errors[1]).ShouldNot(HaveOccurred()) Ω(errors[2]).ShouldNot(HaveOccurred()) }) }) Context("that is nested in another Ordered container", func() { It("errors", func() { var errors = make([]error, 3) errors[0] = suite.PushNode(N(ntCon, "top-level-container", Ordered, func() { errors[1] = suite.PushNode(N(ntCon, "ordered-container", cl, Ordered, ContinueOnFailure, func() { errors[2] = suite.PushNode(N(types.NodeTypeIt, "spec", func() {})) })) })) Ω(errors[0]).ShouldNot(HaveOccurred()) Ω(suite.BuildTree()).Should(Succeed()) Ω(errors[1]).Should(MatchError(types.GinkgoErrors.InvalidContinueOnFailureDecoration(cl))) Ω(errors[2]).ShouldNot(HaveOccurred()) }) }) }) Context("when pushing a suite node during PhaseBuildTree", func() { It("errors", func() { var pushSuiteNodeErr error err := suite.PushNode(N(ntCon, "top-level-container", func() { pushSuiteNodeErr = suite.PushNode(N(types.NodeTypeBeforeSuite, cl)) })) Ω(err).ShouldNot(HaveOccurred()) Ω(suite.BuildTree()).Should(Succeed()) Ω(pushSuiteNodeErr).Should(HaveOccurred()) }) }) Context("when pushing a suite node during PhaseRun", func() { It("errors", func() { var pushSuiteNodeErr error err := suite.PushNode(N(ntIt, "top-level it", func() { pushSuiteNodeErr = suite.PushNode(N(types.NodeTypeBeforeSuite, cl)) })) Ω(err).ShouldNot(HaveOccurred()) Ω(suite.BuildTree()).Should(Succeed()) suite.Run("suite", Labels{}, "/path/to/suite", failer, reporter, writer, outputInterceptor, interruptHandler, client, internal.RegisterForProgressSignal, conf) Ω(pushSuiteNodeErr).Should(HaveOccurred()) }) }) }) Describe("Cleanup Nodes", func() { Context("when pushing a cleanup node during PhaseTopLevel", func() { It("errors", func() { err := suite.PushNode(N(types.NodeTypeCleanupInvalid, cl)) Ω(err).Should(MatchError(types.GinkgoErrors.PushingCleanupNodeDuringTreeConstruction(cl))) }) }) Context("when pushing a cleanup node during PhaseBuildTree", func() { It("errors", func() { var errors = make([]error, 2) errors[0] = suite.PushNode(N(ntCon, "container", func() { errors[1] = suite.PushNode(N(types.NodeTypeCleanupInvalid, cl)) })) Ω(errors[0]).ShouldNot(HaveOccurred()) Ω(suite.BuildTree()).Should(Succeed()) Ω(errors[1]).Should(MatchError(types.GinkgoErrors.PushingCleanupNodeDuringTreeConstruction(cl))) }) }) Context("when pushing a cleanup node in a ReportBeforeEach node", func() { It("errors", func() { var errors = make([]error, 4) reportBeforeEachNode := N(types.NodeTypeReportBeforeEach, func(_ types.SpecReport) { errors[3] = suite.PushNode(N(types.NodeTypeCleanupInvalid, cl)) }, types.NewCodeLocation(0)) errors[0] = suite.PushNode(N(ntCon, "container", func() { errors[1] = suite.PushNode(reportBeforeEachNode) errors[2] = suite.PushNode(N(ntIt, "test")) })) Ω(errors[0]).ShouldNot(HaveOccurred()) Ω(suite.BuildTree()).Should(Succeed()) Ω(errors[1]).ShouldNot(HaveOccurred()) Ω(errors[2]).ShouldNot(HaveOccurred()) suite.Run("suite", Labels{}, "/path/to/suite", failer, reporter, writer, outputInterceptor, interruptHandler, client, internal.RegisterForProgressSignal, conf) Ω(errors[3]).Should(MatchError(types.GinkgoErrors.PushingCleanupInReportingNode(cl, types.NodeTypeReportBeforeEach))) }) }) Context("when pushing a cleanup node in a ReportAfterEach node", func() { It("errors", func() { var errors = make([]error, 4) reportAfterEachNode := N(types.NodeTypeReportAfterEach, func(_ types.SpecReport) { errors[3] = suite.PushNode(N(types.NodeTypeCleanupInvalid, cl)) }, types.NewCodeLocation(0)) errors[0] = suite.PushNode(N(ntCon, "container", func() { errors[1] = suite.PushNode(N(ntIt, "test")) errors[2] = suite.PushNode(reportAfterEachNode) })) Ω(errors[0]).ShouldNot(HaveOccurred()) Ω(suite.BuildTree()).Should(Succeed()) Ω(errors[1]).ShouldNot(HaveOccurred()) Ω(errors[2]).ShouldNot(HaveOccurred()) suite.Run("suite", Labels{}, "/path/to/suite", failer, reporter, writer, outputInterceptor, interruptHandler, client, internal.RegisterForProgressSignal, conf) Ω(errors[3]).Should(MatchError(types.GinkgoErrors.PushingCleanupInReportingNode(cl, types.NodeTypeReportAfterEach))) }) }) Context("when pushing a cleanup node in a ReportBeforeSuite node", func() { It("errors", func() { var errors = make([]error, 4) reportBeforeSuiteNode := N(types.NodeTypeReportBeforeSuite, "", func(_ types.Report) { errors[3] = suite.PushNode(N(types.NodeTypeCleanupInvalid, cl)) }, types.NewCodeLocation(0)) errors[0] = suite.PushNode(N(ntCon, "container", func() { errors[2] = suite.PushNode(N(ntIt, "test")) })) errors[1] = suite.PushNode(reportBeforeSuiteNode) Ω(errors[0]).ShouldNot(HaveOccurred()) Ω(errors[1]).ShouldNot(HaveOccurred()) Ω(suite.BuildTree()).Should(Succeed()) Ω(errors[2]).ShouldNot(HaveOccurred()) suite.Run("suite", Labels{}, "/path/to/suite", failer, reporter, writer, outputInterceptor, interruptHandler, client, internal.RegisterForProgressSignal, conf) Ω(errors[3]).Should(MatchError(types.GinkgoErrors.PushingCleanupInReportingNode(cl, types.NodeTypeReportBeforeSuite))) }) }) Context("when pushing a cleanup node in a ReportAfterSuite node", func() { It("errors", func() { var errors = make([]error, 4) reportAfterSuiteNode := N(types.NodeTypeReportAfterSuite, "report", func(_ types.Report) { errors[3] = suite.PushNode(N(types.NodeTypeCleanupInvalid, cl)) }, types.NewCodeLocation(0)) errors[0] = suite.PushNode(N(ntCon, "container", func() { errors[2] = suite.PushNode(N(ntIt, "test")) })) errors[1] = suite.PushNode(reportAfterSuiteNode) Ω(errors[0]).ShouldNot(HaveOccurred()) Ω(errors[1]).ShouldNot(HaveOccurred()) Ω(suite.BuildTree()).Should(Succeed()) Ω(errors[2]).ShouldNot(HaveOccurred()) suite.Run("suite", Labels{}, "/path/to/suite", failer, reporter, writer, outputInterceptor, interruptHandler, client, internal.RegisterForProgressSignal, conf) Ω(errors[3]).Should(MatchError(types.GinkgoErrors.PushingCleanupInReportingNode(cl, types.NodeTypeReportAfterSuite))) }) }) Context("when pushing a cleanup node within a cleanup node", func() { It("errors", func() { var errors = make([]error, 3) errors[0] = suite.PushNode(N(ntIt, "It", func() { cleanupNode, _ := internal.NewCleanupNode(&types.DeprecationTracker{}, nil, types.NewCustomCodeLocation("outerCleanup"), func() { innerCleanupNode, _ := internal.NewCleanupNode(&types.DeprecationTracker{}, nil, cl, func() {}) errors[2] = suite.PushNode(innerCleanupNode) }) errors[1] = suite.PushNode(cleanupNode) })) Ω(errors[0]).ShouldNot(HaveOccurred()) Ω(suite.BuildTree()).Should(Succeed()) suite.Run("suite", Labels{}, "/path/to/suite", failer, reporter, writer, outputInterceptor, interruptHandler, client, internal.RegisterForProgressSignal, conf) Ω(errors[1]).ShouldNot(HaveOccurred()) Ω(errors[2]).Should(MatchError(types.GinkgoErrors.PushingCleanupInCleanupNode(cl))) }) }) }) Describe("ReportEntries", func() { Context("when adding a report entry outside of the run phase", func() { It("errors", func() { entry, err := internal.NewReportEntry("name", cl) Ω(err).ShouldNot(HaveOccurred()) err = suite.AddReportEntry(entry) Ω(err).Should(MatchError(types.GinkgoErrors.AddReportEntryNotDuringRunPhase(cl))) suite.BuildTree() err = suite.AddReportEntry(entry) Ω(err).Should(MatchError(types.GinkgoErrors.AddReportEntryNotDuringRunPhase(cl))) }) }) }) When("using when", func() { It("prepends 'when' to the test name", func() { Ω(CurrentSpecReport().FullText()).Should(ContainSubstring(" when using when prepends")) }) }) }) }) golang-github-onsi-ginkgo-v2-2.22.0/internal/test_helpers/000077500000000000000000000000001472321612100234075ustar00rootroot00000000000000golang-github-onsi-ginkgo-v2-2.22.0/internal/test_helpers/docs.go000066400000000000000000000064221472321612100246720ustar00rootroot00000000000000package test_helpers import ( "os" "path/filepath" "regexp" "strings" ) var punctuationRE = regexp.MustCompile(`[^\w\-\s]`) var linkRE = regexp.MustCompile(`\]\(([\w:/#\-\.]*)\)`) type Doc struct { Name string URLs []string path string } func (doc Doc) Path(root string) string { return filepath.Join(root, doc.path) } type Docs []Doc func (d Docs) DocWithName(name string) Doc { for _, doc := range d { if doc.Name == name { return doc } } return Doc{} } func (d Docs) DocWithURL(url string) Doc { for _, doc := range d { for _, u := range doc.URLs { if u == url { return doc } } } return Doc{} } var DOCS = Docs{ {"index.md", []string{"https://onsi.github.io/ginkgo/"}, "docs/index.md"}, {"MIGRATING_TO_V2.md", []string{"https://onsi.github.io/ginkgo/MIGRATING_TO_V2"}, "docs/MIGRATING_TO_V2.md"}, {"README.md", []string{"https://github.com/onsi/ginkgo", "https://github.com/onsi/ginkgo/blob/master/README.md"}, "README.md"}, } type Anchors struct { Docs Docs DocAnchors map[string][]string } func (a Anchors) IsResolvable(docName string, link string) bool { var anchorSet []string var expectedAnchor string if strings.HasPrefix(link, "#") { anchorSet = a.DocAnchors[docName] expectedAnchor = strings.TrimPrefix(link, "#") } else { components := strings.Split(link, "#") doc := a.Docs.DocWithURL(components[0]) if doc.Name == "" { //allow external links return true } if len(components) == 1 { //allow links to the doc with no anchor return true } expectedAnchor = components[1] anchorSet = a.DocAnchors[doc.Name] } for _, anchor := range anchorSet { if anchor == expectedAnchor { return true } } return false } func LoadAnchors(docs Docs, rootPath string) (Anchors, error) { out := Anchors{ Docs: docs, DocAnchors: map[string][]string{}, } for _, doc := range docs { anchors, err := loadMarkdownHeadingAnchors(doc.Path(rootPath)) if err != nil { return Anchors{}, err } out.DocAnchors[doc.Name] = anchors } return out, nil } func loadMarkdownHeadingAnchors(filename string) ([]string, error) { headings, err := LoadMarkdownHeadings(filename) if err != nil { return nil, err } var anchors []string for _, heading := range headings { heading = punctuationRE.ReplaceAllString(heading, "") heading = strings.ToLower(heading) heading = strings.ReplaceAll(heading, " ", "-") anchors = append(anchors, heading) } return anchors, nil } func LoadMarkdownHeadings(filename string) ([]string, error) { b, err := os.ReadFile(filename) if err != nil { return nil, err } headings := []string{} lines := strings.Split(string(b), "\n") for _, line := range lines { line = strings.TrimLeft(line, " ") line = strings.TrimRight(line, " ") if !strings.HasPrefix(line, "#") { continue } line = strings.TrimLeft(line, "# ") headings = append(headings, line) } return headings, nil } func LoadMarkdownLinks(filename string) ([]string, error) { b, err := os.ReadFile(filename) if err != nil { return nil, err } links := []string{} lines := strings.Split(string(b), "\n") for _, line := range lines { matches := linkRE.FindAllStringSubmatch(line, -1) for _, match := range matches { if match[1] != "" { links = append(links, match[1]) } } } return links, nil } golang-github-onsi-ginkgo-v2-2.22.0/internal/test_helpers/fake_interrupt_handler.go000066400000000000000000000024361472321612100304620ustar00rootroot00000000000000package test_helpers import ( "sync" "github.com/onsi/ginkgo/v2/internal/interrupt_handler" ) type FakeInterruptHandler struct { c chan interface{} lock *sync.Mutex level interrupt_handler.InterruptLevel cause interrupt_handler.InterruptCause interruptPlaceholderMessage string emittedInterruptPlaceholderMessage string } func NewFakeInterruptHandler() *FakeInterruptHandler { handler := &FakeInterruptHandler{ c: make(chan interface{}), lock: &sync.Mutex{}, level: interrupt_handler.InterruptLevelUninterrupted, } return handler } func (handler *FakeInterruptHandler) Interrupt(cause interrupt_handler.InterruptCause) { handler.lock.Lock() handler.cause = cause handler.level += 1 if handler.level > interrupt_handler.InterruptLevelBailOut { handler.level = interrupt_handler.InterruptLevelBailOut } else { close(handler.c) handler.c = make(chan interface{}) } handler.lock.Unlock() } func (handler *FakeInterruptHandler) Status() interrupt_handler.InterruptStatus { handler.lock.Lock() defer handler.lock.Unlock() return interrupt_handler.InterruptStatus{ Channel: handler.c, Level: handler.level, Cause: handler.cause, } } golang-github-onsi-ginkgo-v2-2.22.0/internal/test_helpers/fake_output_interceptor.go000066400000000000000000000030351472321612100307030ustar00rootroot00000000000000package test_helpers import ( "io" "sync" ) type FakeOutputInterceptor struct { intercepting bool forwardingWriter io.Writer interceptedOutput string lock *sync.Mutex } func NewFakeOutputInterceptor() *FakeOutputInterceptor { return &FakeOutputInterceptor{ lock: &sync.Mutex{}, forwardingWriter: io.Discard, } } func (interceptor *FakeOutputInterceptor) AppendInterceptedOutput(s string) { interceptor.lock.Lock() defer interceptor.lock.Unlock() interceptor.interceptedOutput += s interceptor.forwardingWriter.Write([]byte(s)) } func (interceptor *FakeOutputInterceptor) StartInterceptingOutput() { interceptor.StartInterceptingOutputAndForwardTo(io.Discard) } func (interceptor *FakeOutputInterceptor) StartInterceptingOutputAndForwardTo(w io.Writer) { interceptor.lock.Lock() defer interceptor.lock.Unlock() interceptor.forwardingWriter = w interceptor.intercepting = true interceptor.interceptedOutput = "" } func (interceptor *FakeOutputInterceptor) PauseIntercepting() { interceptor.lock.Lock() defer interceptor.lock.Unlock() interceptor.intercepting = false } func (interceptor *FakeOutputInterceptor) ResumeIntercepting() { interceptor.lock.Lock() defer interceptor.lock.Unlock() interceptor.intercepting = true } func (interceptor *FakeOutputInterceptor) StopInterceptingAndReturnOutput() string { interceptor.lock.Lock() defer interceptor.lock.Unlock() interceptor.intercepting = false return interceptor.interceptedOutput } func (interceptor *FakeOutputInterceptor) Shutdown() { } golang-github-onsi-ginkgo-v2-2.22.0/internal/test_helpers/fake_reporter.go000066400000000000000000000331161472321612100265720ustar00rootroot00000000000000package test_helpers import ( "fmt" "reflect" "strings" "sync" "time" . "github.com/onsi/gomega" "github.com/onsi/gomega/gcustom" . "github.com/onsi/gomega/gstruct" "github.com/onsi/ginkgo/v2/internal/interrupt_handler" "github.com/onsi/ginkgo/v2/types" ) type OmegaMatcherWithDescription struct { OmegaMatcher Description string } func (o OmegaMatcherWithDescription) GomegaString() string { return o.Description } /* A FakeReporter and collection of matchers to match against reported suite and spec summaries */ type Reports []types.SpecReport func (s Reports) FindByLeafNodeType(nodeTypes types.NodeType) types.SpecReport { for _, report := range s { if report.LeafNodeType.Is(nodeTypes) { return report } } return types.SpecReport{} } func (s Reports) Find(name string) types.SpecReport { for _, report := range s { if report.LeafNodeText == name { return report } } return types.SpecReport{} } func (s Reports) FindByFullText(text string) types.SpecReport { for _, report := range s { if report.FullText() == text { return report } } return types.SpecReport{} } func (s Reports) Names() []string { out := []string{} for _, report := range s { if report.LeafNodeText != "" { out = append(out, report.LeafNodeText) } } return out } func (s Reports) WithState(state types.SpecState) Reports { out := Reports{} for _, report := range s { if report.State == state { out = append(out, report) } } return out } func (s Reports) WithLeafNodeType(nodeTypes types.NodeType) Reports { out := Reports{} for _, report := range s { if report.LeafNodeType.Is(nodeTypes) { out = append(out, report) } } return out } type FakeReporter struct { Begin types.Report Will Reports Did Reports End types.Report ProgressReports []types.ProgressReport ReportEntries []types.ReportEntry SpecEvents []types.SpecEvent Failures []types.AdditionalFailure lock *sync.Mutex } func NewFakeReporter() *FakeReporter { return &FakeReporter{ lock: &sync.Mutex{}, } } func (r *FakeReporter) SuiteWillBegin(report types.Report) { r.lock.Lock() defer r.lock.Unlock() r.Begin = report } func (r *FakeReporter) WillRun(report types.SpecReport) { r.lock.Lock() defer r.lock.Unlock() r.Will = append(r.Will, report) } func (r *FakeReporter) DidRun(report types.SpecReport) { r.lock.Lock() defer r.lock.Unlock() r.Did = append(r.Did, report) } func (r *FakeReporter) SuiteDidEnd(report types.Report) { r.lock.Lock() defer r.lock.Unlock() r.End = report } func (r *FakeReporter) EmitProgressReport(progressReport types.ProgressReport) { r.lock.Lock() defer r.lock.Unlock() r.ProgressReports = append(r.ProgressReports, progressReport) } func (r *FakeReporter) EmitFailure(state types.SpecState, failure types.Failure) { r.lock.Lock() defer r.lock.Unlock() r.Failures = append(r.Failures, types.AdditionalFailure{Failure: failure, State: state}) } func (r *FakeReporter) EmitReportEntry(reportEntry types.ReportEntry) { r.lock.Lock() defer r.lock.Unlock() r.ReportEntries = append(r.ReportEntries, reportEntry) } func (r *FakeReporter) EmitSpecEvent(specEvent types.SpecEvent) { r.lock.Lock() defer r.lock.Unlock() r.SpecEvents = append(r.SpecEvents, specEvent) } type NSpecs int type NWillRun int type NPassed int type NSkipped int type NFailed int type NPending int type NFlaked int func BeASuiteSummary(options ...interface{}) OmegaMatcher { type ReportStats struct { Succeeded bool TotalSpecs int WillRunSpecs int Passed int Skipped int Failed int Pending int Flaked int } fields := Fields{ "Passed": Equal(0), "Skipped": Equal(0), "Failed": Equal(0), "Pending": Equal(0), "Flaked": Equal(0), "TotalSpecs": Equal(0), } for _, option := range options { t := reflect.TypeOf(option) if t.Kind() == reflect.Bool { if option.(bool) { fields["Succeeded"] = BeTrue() } else { fields["Succeeded"] = BeFalse() } } else if t == reflect.TypeOf(NSpecs(0)) { fields["TotalSpecs"] = Equal(int(option.(NSpecs))) } else if t == reflect.TypeOf(NWillRun(0)) { fields["WillRunSpecs"] = Equal(int(option.(NWillRun))) } else if t == reflect.TypeOf(NPassed(0)) { fields["Passed"] = Equal(int(option.(NPassed))) } else if t == reflect.TypeOf(NSkipped(0)) { fields["Skipped"] = Equal(int(option.(NSkipped))) } else if t == reflect.TypeOf(NFailed(0)) { fields["Failed"] = Equal(int(option.(NFailed))) } else if t == reflect.TypeOf(NPending(0)) { fields["Pending"] = Equal(int(option.(NPending))) } else if t == reflect.TypeOf(NFlaked(0)) { fields["Flaked"] = Equal(int(option.(NFlaked))) } } return WithTransform(func(report types.Report) ReportStats { specs := report.SpecReports.WithLeafNodeType(types.NodeTypeIt) return ReportStats{ Succeeded: report.SuiteSucceeded, TotalSpecs: report.PreRunStats.TotalSpecs, WillRunSpecs: report.PreRunStats.SpecsThatWillRun, Passed: specs.CountWithState(types.SpecStatePassed), Skipped: specs.CountWithState(types.SpecStateSkipped), Failed: specs.CountWithState(types.SpecStateFailureStates), Pending: specs.CountWithState(types.SpecStatePending), Flaked: specs.CountOfFlakedSpecs(), } }, MatchFields(IgnoreExtras, fields)) } type CapturedGinkgoWriterOutput string type CapturedStdOutput string type NumAttempts int func HavePassed(options ...interface{}) OmegaMatcher { matchers := []OmegaMatcher{ HaveField("State", types.SpecStatePassed), HaveField("Failure", BeZero()), } for _, option := range options { var matcher OmegaMatcher switch v := option.(type) { case CapturedGinkgoWriterOutput: matcher = HaveField("CapturedGinkgoWriterOutput", string(v)) case CapturedStdOutput: matcher = HaveField("CapturedStdOutErr", string(v)) case types.NodeType: matcher = HaveField("LeafNodeType", v) case NumAttempts: matcher = HaveField("NumAttempts", int(v)) } if matcher != nil { matchers = append(matchers, matcher) } } return And(matchers...) } func BePending() OmegaMatcher { return And( HaveField("State", types.SpecStatePending), HaveField("Failure", BeZero()), ) } func HaveBeenSkipped() OmegaMatcher { return And( HaveField("State", types.SpecStateSkipped), HaveField("Failure", BeZero()), ) } func HaveBeenSkippedWithMessage(message string, options ...interface{}) OmegaMatcher { matchers := []OmegaMatcher{ HaveField("State", types.SpecStateSkipped), HaveField("Failure.Message", Equal(message)), } for _, option := range options { switch v := option.(type) { case NumAttempts: matchers = append(matchers, HaveField("NumAttempts", int(v))) } } return And(matchers...) } func HaveBeenInterrupted(cause interrupt_handler.InterruptCause) OmegaMatcher { return And( HaveField("State", types.SpecStateInterrupted), HaveField("Failure.Message", HavePrefix(cause.String())), ) } type FailureNodeType types.NodeType func failureMatcherForState(state types.SpecState, messageField string, options ...interface{}) OmegaMatcher { matchers := []OmegaMatcher{ HaveField("State", state), } for _, option := range options { var matcher OmegaMatcher switch v := option.(type) { case CapturedGinkgoWriterOutput: matcher = HaveField("CapturedGinkgoWriterOutput", string(v)) case CapturedStdOutput: matcher = HaveField("CapturedStdOutErr", string(v)) case types.NodeType: matcher = HaveField("LeafNodeType", v) case types.FailureNodeContext: matcher = HaveField("Failure.FailureNodeContext", v) case string: matcher = HaveField(messageField, ContainSubstring(v)) case OmegaMatcher: matcher = HaveField(messageField, v) case types.CodeLocation: matcher = HaveField("Failure.Location", v) case FailureNodeType: matcher = HaveField("Failure.FailureNodeType", types.NodeType(v)) case NumAttempts: matcher = HaveField("NumAttempts", int(v)) case types.TimelineLocation: matcher = HaveField("Failure.TimelineLocation.Offset", v.Offset) } if matcher != nil { matchers = append(matchers, matcher) } } return And(matchers...) } func HaveFailed(options ...interface{}) OmegaMatcher { return failureMatcherForState(types.SpecStateFailed, "Failure.Message", options...) } func HaveTimedOut(options ...interface{}) OmegaMatcher { return failureMatcherForState(types.SpecStateTimedout, "Failure.Message", options...) } func HaveAborted(options ...interface{}) OmegaMatcher { return failureMatcherForState(types.SpecStateAborted, "Failure.Message", options...) } func HavePanicked(options ...interface{}) OmegaMatcher { return failureMatcherForState(types.SpecStatePanicked, "Failure.ForwardedPanic", options...) } func TLWithOffset[O int | string](o O) types.TimelineLocation { t := types.TimelineLocation{} switch x := any(o).(type) { case int: t.Offset = x case string: t.Offset = len(x) } return t } func BeSpecEvent(options ...interface{}) OmegaMatcher { description := []string{"BeSpecEvent"} matchers := []OmegaMatcher{} for _, option := range options { var matcher OmegaMatcher switch x := option.(type) { case types.SpecEventType: matcher = HaveField("SpecEventType", x) description = append(description, "["+x.String()+" SpecEvent]") case types.CodeLocation: matcher = HaveField("CodeLocation", x) description = append(description, "CL="+x.String()) case types.TimelineLocation: matcher = HaveField("TimelineLocation.Offset", x.Offset) description = append(description, fmt.Sprintf("TL.Offset=%d", x.Offset)) case string: matcher = HaveField("Message", ContainSubstring(x)) description = append(description, `Message="`+x+`"`) case int: matcher = HaveField("Attempt", x) description = append(description, fmt.Sprintf("Attempt=%d", x)) case time.Duration: matcher = HaveField("Duration", BeNumerically("~", x, time.Duration(float64(x)*0.9))) description = append(description, "Duration="+x.String()) case types.NodeType: matcher = HaveField("NodeType", x) description = append(description, "NodeType="+x.String()) } if matcher != nil { matchers = append(matchers, matcher) } } return OmegaMatcherWithDescription{OmegaMatcher: And(matchers...), Description: strings.Join(description, " ")} } func BeProgressReport(options ...interface{}) OmegaMatcher { description := []string{"BeProgressReport"} matchers := []OmegaMatcher{} for _, option := range options { var matcher OmegaMatcher switch x := option.(type) { case string: matcher = HaveField("Message", ContainSubstring(x)) description = append(description, `Message="`+x+`"`) case types.TimelineLocation: matcher = HaveField("TimelineLocation.Offset", x.Offset) description = append(description, fmt.Sprintf("TL.Offset=%d", x.Offset)) case types.CodeLocation: matcher = HaveField("CurrentNodeLocation", x) description = append(description, "CurrentNodeLocation="+x.String()) case types.NodeType: matcher = HaveField("CurrentNodeType", x) description = append(description, "CurrentNodeType="+x.String()) } if matcher != nil { matchers = append(matchers, matcher) } } return OmegaMatcherWithDescription{OmegaMatcher: And(matchers...), Description: strings.Join(description, " ")} } func BeReportEntry(options ...interface{}) OmegaMatcher { description := []string{"BeReportEntry"} matchers := []OmegaMatcher{} for _, option := range options { var matcher OmegaMatcher switch x := option.(type) { case string: matcher = HaveField("Name", ContainSubstring(x)) description = append(description, `Name="`+x+`"`) case types.TimelineLocation: matcher = HaveField("TimelineLocation.Offset", x.Offset) description = append(description, fmt.Sprintf("TL.Offset=%d", x.Offset)) case types.ReportEntryVisibility: matcher = HaveField("Visibility", x) description = append(description, "Visibility="+x.String()) } if matcher != nil { matchers = append(matchers, matcher) } } return OmegaMatcherWithDescription{OmegaMatcher: And(matchers...), Description: strings.Join(description, " ")} } func BeTimelineContaining(matchers ...OmegaMatcher) OmegaMatcher { return gcustom.MakeMatcher(func(timeline types.Timeline) (bool, error) { timelineIdx := 0 for _, matcher := range matchers { for { if timelineIdx >= len(timeline) { return false, nil } event := timeline[timelineIdx] timelineIdx += 1 success, err := matcher.Match(event) if success && err == nil { break } } } return true, nil }).WithTemplate("Expected:\n{{.FormattedActual}}\n{{.To}} contain events matching (in order):\n{{format .Data 1}}", matchers) } func BeTimelineExactlyMatching(matchers ...OmegaMatcher) OmegaMatcher { data := map[string]any{} data["Matchers"] = matchers return gcustom.MakeMatcher(func(timeline types.Timeline) (bool, error) { for idx, matcher := range matchers { if idx == len(timeline) { data["LengthMismatch"] = "Not enough timeline entries" return false, nil } event := timeline[idx] success, err := matcher.Match(event) if !(success && err == nil) { data["Failure"] = matcher.FailureMessage(event) data["FailedIndex"] = idx return false, nil } } if len(matchers) < len(timeline) { data["LengthMismatch"] = "Not enough matcher entries" return false, nil } return true, nil }).WithTemplate(`Timeline failed to match: {{if .Data.LengthMismatch}} {{.Data.LengthMismatch}} Timeline has {{len .Actual}} events: {{ range .Actual }}{{ printf " %T\n" . }}{{ end }} Matchers has {{len .Data.Matchers}} entries. {{else}} Failed at index {{.Data.FailedIndex}}: {{format (index .Actual .Data.FailedIndex) 2}} {{.Data.Failure}} {{end}}`, data) } golang-github-onsi-ginkgo-v2-2.22.0/internal/test_helpers/multiline_helpers.go000066400000000000000000000031041472321612100274600ustar00rootroot00000000000000package test_helpers import ( "fmt" "strings" . "github.com/onsi/gomega" "github.com/onsi/gomega/gcustom" ) func MultilineTextHelper(s string) string { lines := strings.Split(s, "\n") out := "\nstrings.Join([]string{\n" for _, l := range lines { out = out + fmt.Sprintf(" %#v,\n", l) } out += `}, "\n")` return out } func MatchLines(expected ...any) gcustom.CustomGomegaMatcher { data := map[string]any{} return gcustom.MakeMatcher(func(actual string) (bool, error) { data["RenderedActual"] = MultilineTextHelper(actual) if len(expected) == 0 && len(actual) == 0 { return true, nil } lines := strings.Split(actual, "\n") for idx, expectation := range expected { if idx >= len(lines) { data["Failure"] = "More Expectations than lines..." return false, nil } var matcher OmegaMatcher if expectedString, isString := expectation.(string); isString { matcher = Equal(expectedString) } else { matcher = expectation.(OmegaMatcher) } matches, err := matcher.Match(lines[idx]) if err != nil { data["Failure"] = fmt.Sprintf("At line %d:\n%s", idx+1, err.Error()) return false, nil } if !matches { data["Failure"] = fmt.Sprintf("At line %d:\n%s", idx+1, matcher.FailureMessage(lines[idx])) return false, nil } } if len(expected) < len(lines) { data["Failure"] = fmt.Sprintf("Missing expectations for last %d lines", len(lines)-len(expected)) return false, nil } return true, nil }).WithTemplate("Failed {{.To}} MatchLines for:\n{{.Data.RenderedActual}}\n\n{{.Data.Failure}}").WithTemplateData(data) } golang-github-onsi-ginkgo-v2-2.22.0/internal/test_helpers/run_tracker.go000066400000000000000000000102021472321612100262500ustar00rootroot00000000000000package test_helpers import ( "fmt" "strings" "sync" "github.com/onsi/ginkgo/v2/formatter" "github.com/onsi/ginkgo/v2/internal" . "github.com/onsi/gomega" "github.com/onsi/gomega/types" ) /* RunTracker tracks invocations of functions - useful to assert orders in which nodes run */ type RunTracker struct { lock *sync.Mutex trackedRuns []string trackedData map[string]map[string]interface{} } func NewRunTracker() *RunTracker { return &RunTracker{ lock: &sync.Mutex{}, trackedData: map[string]map[string]interface{}{}, } } func (rt *RunTracker) Reset() { rt.lock.Lock() defer rt.lock.Unlock() rt.trackedRuns = []string{} } func (rt *RunTracker) Run(text string) { rt.lock.Lock() defer rt.lock.Unlock() rt.trackedRuns = append(rt.trackedRuns, text) } func (rt *RunTracker) RunWithData(text string, kv ...interface{}) { rt.lock.Lock() defer rt.lock.Unlock() rt.trackedRuns = append(rt.trackedRuns, text) data := map[string]interface{}{} for i := 0; i < len(kv); i += 2 { key := kv[i].(string) value := kv[i+1] data[key] = value } rt.trackedData[text] = data } func (rt *RunTracker) TrackedRuns() []string { rt.lock.Lock() defer rt.lock.Unlock() trackedRuns := make([]string, len(rt.trackedRuns)) copy(trackedRuns, rt.trackedRuns) return trackedRuns } func (rt *RunTracker) DataFor(text string) map[string]interface{} { rt.lock.Lock() defer rt.lock.Unlock() return rt.trackedData[text] } func (rt *RunTracker) T(text string, callback ...func()) func() { return func() { rt.Run(text) if len(callback) > 0 { callback[0]() } } } func (rt *RunTracker) TSC(text string, callback ...func(internal.SpecContext)) func(internal.SpecContext) { return func(c internal.SpecContext) { rt.Run(text) if len(callback) > 0 { callback[0](c) } } } func (rt *RunTracker) C(text string, callback ...func()) func(args []string, additionalArgs []string) { return func(args []string, additionalArgs []string) { rt.RunWithData(text, "Args", args, "AdditionalArgs", additionalArgs) if len(callback) > 0 { callback[0]() } } } func HaveRun(run string) OmegaMatcher { return WithTransform(func(rt *RunTracker) []string { return rt.TrackedRuns() }, ContainElement(run)) } func HaveRunWithData(run string, kv ...interface{}) OmegaMatcher { matchers := []types.GomegaMatcher{} for i := 0; i < len(kv); i += 2 { matchers = append(matchers, HaveKeyWithValue(kv[i], kv[i+1])) } return And( HaveRun(run), WithTransform(func(rt *RunTracker) map[string]interface{} { return rt.DataFor(run) }, And(matchers...)), ) } func HaveTrackedNothing() OmegaMatcher { return WithTransform(func(rt *RunTracker) []string { return rt.TrackedRuns() }, BeEmpty()) } type HaveTrackedMatcher struct { expectedRuns []string message string } func (m *HaveTrackedMatcher) Match(actual interface{}) (bool, error) { rt, ok := actual.(*RunTracker) if !ok { return false, fmt.Errorf("HaveTracked() must be passed a RunTracker - got %T instead", actual) } actualRuns := rt.TrackedRuns() n := len(actualRuns) if n < len(m.expectedRuns) { n = len(m.expectedRuns) } failureMessage, success := &strings.Builder{}, true fmt.Fprintf(failureMessage, "{{/}}%10s == %-10s{{/}}\n", "Actual", "Expected") fmt.Fprintf(failureMessage, "{{/}}========================\n{{/}}") for i := 0; i < n; i++ { var expected, actual string if i < len(actualRuns) { actual = actualRuns[i] } if i < len(m.expectedRuns) { expected = m.expectedRuns[i] } if actual != expected { success = false fmt.Fprintf(failureMessage, "{{red}}%10s != %-10s{{/}}\n", actual, expected) } else { fmt.Fprintf(failureMessage, "{{green}}%10s == %-10s{{/}}\n", actual, expected) } } m.message = failureMessage.String() return success, nil } func (m *HaveTrackedMatcher) FailureMessage(actual interface{}) string { return "Expected runs did not match tracked runs:\n" + formatter.F(m.message) } func (m *HaveTrackedMatcher) NegatedFailureMessage(actual interface{}) string { return "Expected runs matched tracked runs:\n" + formatter.F(m.message) } func HaveTracked(runs ...string) OmegaMatcher { return &HaveTrackedMatcher{expectedRuns: runs} } golang-github-onsi-ginkgo-v2-2.22.0/internal/test_helpers/set_up_server.go000066400000000000000000000016071472321612100266270ustar00rootroot00000000000000package test_helpers import ( . "github.com/onsi/ginkgo/v2" "github.com/onsi/ginkgo/v2/internal/parallel_support" "github.com/onsi/ginkgo/v2/reporters" . "github.com/onsi/gomega" ) func SetUpServerAndClient(numNodes int) (parallel_support.Server, parallel_support.Client, map[int]chan interface{}) { server, err := parallel_support.NewServer(numNodes, reporters.NoopReporter{}) Ω(err).ShouldNot(HaveOccurred()) server.Start() client := parallel_support.NewClient(server.Address()) Eventually(client.Connect).Should(BeTrue()) exitChannels := map[int]chan interface{}{} for node := 1; node <= numNodes; node++ { c := make(chan interface{}) exitChannels[node] = c server.RegisterAlive(node, func() bool { select { case <-c: return false default: return true } }) } DeferCleanup(server.Close) DeferCleanup(client.Close) return server, client, exitChannels } golang-github-onsi-ginkgo-v2-2.22.0/internal/test_helpers/status_code_poller.go000066400000000000000000000003361472321612100276320ustar00rootroot00000000000000package test_helpers import "net/http" func StatusCodePoller(url string) func() int { return func() int { resp, err := http.Get(url) if err != nil { return 0 } resp.Body.Close() return resp.StatusCode } } golang-github-onsi-ginkgo-v2-2.22.0/internal/testingtproxy/000077500000000000000000000000001472321612100236515ustar00rootroot00000000000000golang-github-onsi-ginkgo-v2-2.22.0/internal/testingtproxy/testing_t_proxy.go000066400000000000000000000142661472321612100274520ustar00rootroot00000000000000package testingtproxy import ( "fmt" "io" "os" "github.com/onsi/ginkgo/v2/formatter" "github.com/onsi/ginkgo/v2/internal" "github.com/onsi/ginkgo/v2/reporters" "github.com/onsi/ginkgo/v2/types" ) type failFunc func(message string, callerSkip ...int) type skipFunc func(message string, callerSkip ...int) type cleanupFunc func(args ...any) type reportFunc func() types.SpecReport type addReportEntryFunc func(names string, args ...any) type ginkgoWriterInterface interface { io.Writer Print(a ...interface{}) Printf(format string, a ...interface{}) Println(a ...interface{}) } type ginkgoRecoverFunc func() type attachProgressReporterFunc func(func() string) func() 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 { return &ginkgoTestingTProxy{ fail: fail, offset: offset, writer: writer, skip: skip, cleanup: cleanup, report: report, addReportEntry: addReportEntry, ginkgoRecover: ginkgoRecover, attachProgressReporter: attachProgressReporter, randomSeed: randomSeed, parallelProcess: parallelProcess, parallelTotal: parallelTotal, f: formatter.NewWithNoColorBool(noColor), } } type ginkgoTestingTProxy struct { fail failFunc skip skipFunc cleanup cleanupFunc report reportFunc offset int writer ginkgoWriterInterface addReportEntry addReportEntryFunc ginkgoRecover ginkgoRecoverFunc attachProgressReporter attachProgressReporterFunc randomSeed int64 parallelProcess int parallelTotal int f formatter.Formatter } // basic testing.T support func (t *ginkgoTestingTProxy) Cleanup(f func()) { t.cleanup(f, internal.Offset(1)) } func (t *ginkgoTestingTProxy) Setenv(key, value string) { originalValue, exists := os.LookupEnv(key) if exists { t.cleanup(os.Setenv, key, originalValue, internal.Offset(1)) } else { t.cleanup(os.Unsetenv, key, internal.Offset(1)) } err := os.Setenv(key, value) if err != nil { t.fail(fmt.Sprintf("Failed to set environment variable: %v", err), 1) } } func (t *ginkgoTestingTProxy) Error(args ...interface{}) { t.fail(fmt.Sprintln(args...), t.offset) } func (t *ginkgoTestingTProxy) Errorf(format string, args ...interface{}) { t.fail(fmt.Sprintf(format, args...), t.offset) } func (t *ginkgoTestingTProxy) Fail() { t.fail("failed", t.offset) } func (t *ginkgoTestingTProxy) FailNow() { t.fail("failed", t.offset) } func (t *ginkgoTestingTProxy) Failed() bool { return t.report().Failed() } func (t *ginkgoTestingTProxy) Fatal(args ...interface{}) { t.fail(fmt.Sprintln(args...), t.offset) } func (t *ginkgoTestingTProxy) Fatalf(format string, args ...interface{}) { t.fail(fmt.Sprintf(format, args...), t.offset) } func (t *ginkgoTestingTProxy) Helper() { types.MarkAsHelper(1) } func (t *ginkgoTestingTProxy) Log(args ...interface{}) { fmt.Fprintln(t.writer, args...) } func (t *ginkgoTestingTProxy) Logf(format string, args ...interface{}) { t.Log(fmt.Sprintf(format, args...)) } func (t *ginkgoTestingTProxy) Name() string { return t.report().FullText() } func (t *ginkgoTestingTProxy) Parallel() { // No-op } func (t *ginkgoTestingTProxy) Skip(args ...interface{}) { t.skip(fmt.Sprintln(args...), t.offset) } func (t *ginkgoTestingTProxy) SkipNow() { t.skip("skip", t.offset) } func (t *ginkgoTestingTProxy) Skipf(format string, args ...interface{}) { t.skip(fmt.Sprintf(format, args...), t.offset) } func (t *ginkgoTestingTProxy) Skipped() bool { return t.report().State.Is(types.SpecStateSkipped) } func (t *ginkgoTestingTProxy) TempDir() string { tmpDir, err := os.MkdirTemp("", "ginkgo") if err != nil { t.fail(fmt.Sprintf("Failed to create temporary directory: %v", err), 1) return "" } t.cleanup(os.RemoveAll, tmpDir) return tmpDir } // FullGinkgoTInterface func (t *ginkgoTestingTProxy) AddReportEntryVisibilityAlways(name string, args ...any) { finalArgs := []any{internal.Offset(1), types.ReportEntryVisibilityAlways} t.addReportEntry(name, append(finalArgs, args...)...) } func (t *ginkgoTestingTProxy) AddReportEntryVisibilityFailureOrVerbose(name string, args ...any) { finalArgs := []any{internal.Offset(1), types.ReportEntryVisibilityFailureOrVerbose} t.addReportEntry(name, append(finalArgs, args...)...) } func (t *ginkgoTestingTProxy) AddReportEntryVisibilityNever(name string, args ...any) { finalArgs := []any{internal.Offset(1), types.ReportEntryVisibilityNever} t.addReportEntry(name, append(finalArgs, args...)...) } func (t *ginkgoTestingTProxy) Print(a ...any) { t.writer.Print(a...) } func (t *ginkgoTestingTProxy) Printf(format string, a ...any) { t.writer.Printf(format, a...) } func (t *ginkgoTestingTProxy) Println(a ...any) { t.writer.Println(a...) } func (t *ginkgoTestingTProxy) F(format string, args ...any) string { return t.f.F(format, args...) } func (t *ginkgoTestingTProxy) Fi(indentation uint, format string, args ...any) string { return t.f.Fi(indentation, format, args...) } func (t *ginkgoTestingTProxy) Fiw(indentation uint, maxWidth uint, format string, args ...any) string { return t.f.Fiw(indentation, maxWidth, format, args...) } func (t *ginkgoTestingTProxy) RenderTimeline() string { return reporters.RenderTimeline(t.report(), false) } func (t *ginkgoTestingTProxy) GinkgoRecover() { t.ginkgoRecover() } func (t *ginkgoTestingTProxy) DeferCleanup(args ...any) { finalArgs := []any{internal.Offset(1)} t.cleanup(append(finalArgs, args...)...) } func (t *ginkgoTestingTProxy) RandomSeed() int64 { return t.randomSeed } func (t *ginkgoTestingTProxy) ParallelProcess() int { return t.parallelProcess } func (t *ginkgoTestingTProxy) ParallelTotal() int { return t.parallelTotal } func (t *ginkgoTestingTProxy) AttachProgressReporter(f func() string) func() { return t.attachProgressReporter(f) } golang-github-onsi-ginkgo-v2-2.22.0/internal/testingtproxy/testingtproxy_suite_test.go000066400000000000000000000003271472321612100314150ustar00rootroot00000000000000package testingtproxy_test import ( "testing" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" ) func TestTestingtproxy(t *testing.T) { RegisterFailHandler(Fail) RunSpecs(t, "Testingtproxy Suite") } golang-github-onsi-ginkgo-v2-2.22.0/internal/testingtproxy/testingtproxy_test.go000066400000000000000000000251621472321612100302100ustar00rootroot00000000000000package testingtproxy_test import ( "os" "runtime" "time" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" "github.com/onsi/gomega/gbytes" "github.com/onsi/ginkgo/v2/internal" "github.com/onsi/ginkgo/v2/internal/testingtproxy" "github.com/onsi/ginkgo/v2/types" ) type messagedCall struct { message string callerSkip []int } var _ = Describe("Testingtproxy", func() { var t FullGinkgoTInterface var failFunc func(message string, callerSkip ...int) var skipFunc func(message string, callerSkip ...int) var reportFunc func() types.SpecReport var failFuncCall messagedCall var skipFuncCall messagedCall var offset int var reportToReturn types.SpecReport var buf *gbytes.Buffer var recoverCall bool var attachedProgressReporter func() string var attachProgressReporterCancelCalled bool BeforeEach(func() { recoverCall = false attachProgressReporterCancelCalled = false failFuncCall = messagedCall{} skipFuncCall = messagedCall{} offset = 3 reportToReturn = types.SpecReport{} failFunc = func(message string, callerSkip ...int) { failFuncCall.message = message failFuncCall.callerSkip = callerSkip } skipFunc = func(message string, callerSkip ...int) { skipFuncCall.message = message skipFuncCall.callerSkip = callerSkip } reportFunc = func() types.SpecReport { return reportToReturn } ginkgoRecoverFunc := func() { recoverCall = true } attachProgressReporterFunc := func(f func() string) func() { attachedProgressReporter = f return func() { attachProgressReporterCancelCalled = true } } buf = gbytes.NewBuffer() t = testingtproxy.New( internal.NewWriter(buf), failFunc, skipFunc, DeferCleanup, reportFunc, AddReportEntry, ginkgoRecoverFunc, attachProgressReporterFunc, 17, 3, 5, true, offset) }) Describe("Cleanup", Ordered, func() { var didCleanupAfter bool It("supports cleanup", func() { Ω(didCleanupAfter).Should(BeFalse()) t.Cleanup(func() { didCleanupAfter = true }) }) It("ran cleanup after the last test", func() { Ω(didCleanupAfter).Should(BeTrue()) }) }) Describe("Setenv", func() { Context("when the environment variable does not exist", Ordered, func() { const key = "FLOOP_FLARP_WIBBLE_BLARP" BeforeAll(func() { os.Unsetenv(key) }) It("sets the environment variable", func() { t.Setenv(key, "HELLO") Ω(os.Getenv(key)).Should(Equal("HELLO")) }) It("cleans up after itself", func() { _, exists := os.LookupEnv(key) Ω(exists).Should(BeFalse()) }) }) Context("when the environment variable does exist", Ordered, func() { const key = "FLOOP_FLARP_WIBBLE_BLARP" const originalValue = "HOLA" BeforeAll(func() { os.Setenv(key, originalValue) }) It("sets it", func() { t.Setenv(key, "HELLO") Ω(os.Getenv(key)).Should(Equal("HELLO")) }) It("cleans up after itself", func() { Ω(os.Getenv(key)).Should(Equal("HOLA")) }) AfterAll(func() { os.Unsetenv(key) }) }) }) Describe("TempDir", Ordered, func() { var tempDirA, tempDirB string It("creates temporary directories", func() { tempDirA = t.TempDir() tempDirB = t.TempDir() Ω(tempDirA).Should(BeADirectory()) Ω(tempDirB).Should(BeADirectory()) Ω(tempDirA).ShouldNot(Equal(tempDirB)) }) It("cleans up after itself", func() { Ω(tempDirA).ShouldNot(BeADirectory()) Ω(tempDirB).ShouldNot(BeADirectory()) }) }) It("supports Error", func() { t.Error("a", 17) Ω(failFuncCall.message).Should(Equal("a 17\n")) Ω(failFuncCall.callerSkip).Should(Equal([]int{offset})) }) It("supports Errorf", func() { t.Errorf("%s %d!", "a", 17) Ω(failFuncCall.message).Should(Equal("a 17!")) Ω(failFuncCall.callerSkip).Should(Equal([]int{offset})) }) It("supports Fail", func() { t.Fail() Ω(failFuncCall.message).Should(Equal("failed")) Ω(failFuncCall.callerSkip).Should(Equal([]int{offset})) }) It("supports FailNow", func() { t.Fail() Ω(failFuncCall.message).Should(Equal("failed")) Ω(failFuncCall.callerSkip).Should(Equal([]int{offset})) }) It("supports Fatal", func() { t.Fatal("a", 17) Ω(failFuncCall.message).Should(Equal("a 17\n")) Ω(failFuncCall.callerSkip).Should(Equal([]int{offset})) }) It("supports Fatalf", func() { t.Fatalf("%s %d!", "a", 17) Ω(failFuncCall.message).Should(Equal("a 17!")) Ω(failFuncCall.callerSkip).Should(Equal([]int{offset})) }) It("ignores Helper", func() { cl := func() types.CodeLocation { GinkgoT().Helper() return types.NewCodeLocation(0) }() // this is the expected line _, fname, lnumber, _ := runtime.Caller(0) Ω(cl).Should(Equal(types.CodeLocation{ FileName: fname, LineNumber: lnumber - 1, })) }) It("supports Log", func() { t.Log("a", 17) Ω(string(buf.Contents())).Should(Equal(" a 17\n")) }) It("supports Logf", func() { t.Logf("%s %d!", "a", 17) Ω(string(buf.Contents())).Should(Equal(" a 17!\n")) }) It("supports Name", func() { reportToReturn.ContainerHierarchyTexts = []string{"C.S."} reportToReturn.LeafNodeText = "Lewis" Ω(t.Name()).Should(Equal("C.S. Lewis")) Ω(GinkgoT().Name()).Should(ContainSubstring("supports Name")) Ω(GinkgoTB().Name()).Should(ContainSubstring("supports Name")) }) It("ignores Parallel", func() { GinkgoT().Parallel() //is a no-op }) It("supports Skip", func() { t.Skip("a", 17) Ω(skipFuncCall.message).Should(Equal("a 17\n")) Ω(skipFuncCall.callerSkip).Should(Equal([]int{offset})) }) It("supports SkipNow", func() { t.SkipNow() Ω(skipFuncCall.message).Should(Equal("skip")) Ω(skipFuncCall.callerSkip).Should(Equal([]int{offset})) }) It("supports Skipf", func() { t.Skipf("%s %d!", "a", 17) Ω(skipFuncCall.message).Should(Equal("a 17!")) Ω(skipFuncCall.callerSkip).Should(Equal([]int{offset})) }) It("returns the state of the test when asked if it was skipped", func() { reportToReturn.State = types.SpecStatePassed Ω(t.Skipped()).Should(BeFalse()) reportToReturn.State = types.SpecStateSkipped Ω(t.Skipped()).Should(BeTrue()) }) It("can add report entries with visibility Always", func() { cl := types.NewCodeLocation(0) t.AddReportEntryVisibilityAlways("hey", 3) entry := CurrentSpecReport().ReportEntries[0] Ω(entry.Visibility).Should(Equal(types.ReportEntryVisibilityAlways)) Ω(entry.Name).Should(Equal("hey")) Ω(entry.GetRawValue()).Should(Equal(3)) Ω(entry.Location.FileName).Should(Equal(cl.FileName)) Ω(entry.Location.LineNumber).Should(Equal(cl.LineNumber + 1)) }) It("can add report entries with visibility FailureOrVerbose", func() { cl := types.NewCodeLocation(0) t.AddReportEntryVisibilityFailureOrVerbose("hey", 3) entry := CurrentSpecReport().ReportEntries[0] Ω(entry.Visibility).Should(Equal(types.ReportEntryVisibilityFailureOrVerbose)) Ω(entry.Name).Should(Equal("hey")) Ω(entry.GetRawValue()).Should(Equal(3)) Ω(entry.Location.FileName).Should(Equal(cl.FileName)) Ω(entry.Location.LineNumber).Should(Equal(cl.LineNumber + 1)) }) It("can add report entries with visibility Never", func() { cl := types.NewCodeLocation(0) t.AddReportEntryVisibilityNever("hey", 3) entry := CurrentSpecReport().ReportEntries[0] Ω(entry.Visibility).Should(Equal(types.ReportEntryVisibilityNever)) Ω(entry.Name).Should(Equal("hey")) Ω(entry.GetRawValue()).Should(Equal(3)) Ω(entry.Location.FileName).Should(Equal(cl.FileName)) Ω(entry.Location.LineNumber).Should(Equal(cl.LineNumber + 1)) }) It("can print to the GinkgoWriter", func() { t.Print("hi", 3) Ω(string(buf.Contents())).Should(Equal(" hi3")) }) It("can printf to the GinkgoWriter", func() { t.Printf("hi %d", 3) Ω(string(buf.Contents())).Should(Equal(" hi 3")) }) It("can println to the GinkgoWriter", func() { t.Println("hi", 3) Ω(string(buf.Contents())).Should(Equal(" hi 3\n")) }) It("can provide a correctly configured Ginkgo Formatter", func() { Ω(t.F("{{blue}}%d{{/}}", 3)).Should(Equal("3")) }) It("can provide a correctly configured Ginkgo Formatter, with indentation", func() { Ω(t.Fi(1, "{{blue}}%d{{/}}", 3)).Should(Equal(" 3")) }) It("can provide a correctly configured Ginkgo Formatter, with indentation and width constraints", func() { Ω(t.Fiw(1, 5, "{{blue}}%d{{/}} a number", 3)).Should(Equal(" 3 a\n number")) }) It("can render the timeline of the current spec", func() { cl := types.NewCustomCodeLocation("cl") reportToReturn.CapturedGinkgoWriterOutput = "ABCDEFGHIJKLMNOP" reportToReturn.SpecEvents = append(reportToReturn.SpecEvents, types.SpecEvent{ TimelineLocation: types.TimelineLocation{Offset: 5, Order: 1}, SpecEventType: types.SpecEventNodeStart, Message: "The Test", CodeLocation: cl, NodeType: types.NodeTypeIt, }) reportToReturn.SpecEvents = append(reportToReturn.SpecEvents, types.SpecEvent{ TimelineLocation: types.TimelineLocation{Offset: 10, Order: 3}, SpecEventType: types.SpecEventNodeEnd, Message: "The Test", CodeLocation: cl, NodeType: types.NodeTypeIt, Duration: time.Second, }) reportToReturn.State = types.SpecStateFailed reportToReturn.Failure = types.Failure{ Message: "The Failure", FailureNodeType: types.NodeTypeIt, Location: cl, TimelineLocation: types.TimelineLocation{Offset: 10, Order: 2}, } Ω(t.RenderTimeline()).Should(Equal("ABCDE\n> Enter \x1b[1m[It]\x1b[0m The Test \x1b[38;5;243m- cl @ 01/01/01 00:00:00\x1b[0m\nFGHIJ\n\x1b[38;5;9m[FAILED] The Failure\x1b[0m\n\x1b[38;5;9mIn \x1b[1m[It]\x1b[0m\x1b[38;5;9m at: \x1b[1mcl\x1b[0m \x1b[38;5;243m@ 01/01/01 00:00:00\x1b[0m\n< Exit \x1b[1m[It]\x1b[0m The Test \x1b[38;5;243m- cl @ 01/01/01 00:00:00 (1s)\x1b[0m\nKLMNOP")) }) It("can provide GinkgoRecover", func() { Ω(recoverCall).Should(BeFalse()) t.GinkgoRecover() Ω(recoverCall).Should(BeTrue()) }) Describe("DeferCleanup", Ordered, func() { var a int It("provides access to DeferCleanup", func() { a = 3 t.DeferCleanup(func(newA int) { a = newA }, 4) }) It("provides access to DeferCleanup", func() { Ω(a).Should(Equal(4)) }) }) It("provides the random seed", func() { Ω(t.RandomSeed()).Should(Equal(int64(17))) }) It("provides the parallel process", func() { Ω(t.ParallelProcess()).Should(Equal(3)) }) It("provides the parallel total", func() { Ω(t.ParallelTotal()).Should(Equal(5)) }) It("can attach progress reports", func() { cancel := t.AttachProgressReporter(func() string { return "my report" }) Ω(attachedProgressReporter()).Should(Equal("my report")) Ω(attachProgressReporterCancelCalled).Should(BeFalse()) cancel() Ω(attachProgressReporterCancelCalled).Should(BeTrue()) }) }) golang-github-onsi-ginkgo-v2-2.22.0/internal/tree.go000066400000000000000000000035751472321612100222060ustar00rootroot00000000000000package internal import "github.com/onsi/ginkgo/v2/types" type TreeNode struct { Node Node Parent *TreeNode Children TreeNodes } func (tn *TreeNode) AppendChild(child *TreeNode) { tn.Children = append(tn.Children, child) child.Parent = tn } func (tn *TreeNode) AncestorNodeChain() Nodes { if tn.Parent == nil || tn.Parent.Node.IsZero() { return Nodes{tn.Node} } return append(tn.Parent.AncestorNodeChain(), tn.Node) } type TreeNodes []*TreeNode func (tn TreeNodes) Nodes() Nodes { out := make(Nodes, len(tn)) for i := range tn { out[i] = tn[i].Node } return out } func (tn TreeNodes) WithID(id uint) *TreeNode { for i := range tn { if tn[i].Node.ID == id { return tn[i] } } return nil } func GenerateSpecsFromTreeRoot(tree *TreeNode) Specs { var walkTree func(nestingLevel int, lNodes Nodes, rNodes Nodes, trees TreeNodes) Specs walkTree = func(nestingLevel int, lNodes Nodes, rNodes Nodes, trees TreeNodes) Specs { tests := Specs{} nodes := make(Nodes, len(trees)) for i := range trees { nodes[i] = trees[i].Node nodes[i].NestingLevel = nestingLevel } for i := range nodes { if !nodes[i].NodeType.Is(types.NodeTypesForContainerAndIt) { continue } leftNodes, rightNodes := nodes.SplitAround(nodes[i]) leftNodes = leftNodes.WithoutType(types.NodeTypesForContainerAndIt) rightNodes = rightNodes.WithoutType(types.NodeTypesForContainerAndIt) leftNodes = lNodes.CopyAppend(leftNodes...) rightNodes = rightNodes.CopyAppend(rNodes...) if nodes[i].NodeType.Is(types.NodeTypeIt) { tests = append(tests, Spec{Nodes: leftNodes.CopyAppend(nodes[i]).CopyAppend(rightNodes...)}) } else { treeNode := trees.WithID(nodes[i].ID) tests = append(tests, walkTree(nestingLevel+1, leftNodes.CopyAppend(nodes[i]), rightNodes, treeNode.Children)...) } } return tests } return walkTree(0, Nodes{}, Nodes{}, tree.Children) } golang-github-onsi-ginkgo-v2-2.22.0/internal/tree_test.go000066400000000000000000000117731472321612100232440ustar00rootroot00000000000000package internal_test import ( . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" "github.com/onsi/ginkgo/v2/internal" ) var _ = Describe("Trees (TreeNode and TreeNodes)", func() { Describe("TreeNodes methods", func() { var n1, n2, n3 Node var childNode Node var treeNodes TreeNodes BeforeEach(func() { n1, n2, n3 = N(), N(), N() childNode = N() treeNodes = TreeNodes{ TN(n1, TN(childNode), ), TN(n2), TN(n3), } }) Describe("treenodes.Nodes", func() { It("returns the root node of each node in the treenodes slice", func() { Ω(treeNodes.Nodes()).Should(Equal(Nodes{n1, n2, n3})) }) }) Describe("treenodes.WithId", func() { Context("when a tree with a root node with a matching id is found", func() { It("returns that tree", func() { Ω(treeNodes.WithID(n2.ID)).Should(Equal(TN(n2))) }) }) Context("when the id matches a child node's id", func() { It("returns an empty tree as children are not included in the match", func() { Ω(treeNodes.WithID(childNode.ID)).Should(BeNil()) }) }) Context("when the id cannot be found", func() { It("returns an empty tree", func() { Ω(treeNodes.WithID(1000000)).Should(BeZero()) //pretty sure it's a safe bet we don't ever get to 1_000_000 nodes in this test ;) }) }) }) Describe("AppendChild", func() { It("appends the passed in child treenode to the parent's children and sets the child's parent", func() { existingChildNode1 := N() existingChildNode2 := N() treeNode := TN(N(), TN(existingChildNode1), TN(existingChildNode2), ) newChildNode := N() childTreeNode := &TreeNode{Node: newChildNode} treeNode.AppendChild(childTreeNode) Ω(treeNode.Children.Nodes()).Should(Equal(Nodes{existingChildNode1, existingChildNode2, newChildNode})) Ω(childTreeNode.Parent).Should(Equal(treeNode)) }) }) Describe("ParentChain", func() { It("returns the chain of parent nodes", func() { grandparent := N() parent := N() aunt := N() child := N() sibling := N() tree := TN(Node{}, TN( grandparent, TN(parent, TN(child), TN(sibling)), TN(aunt), )) childTree := tree.Children[0].Children[0].Children[0] Ω(childTree.Node).Should(Equal(child)) Ω(childTree.AncestorNodeChain()).Should(Equal(Nodes{grandparent, parent, child})) }) }) }) Describe("GenerateSpecsFromTreeRoot", func() { var tree *TreeNode BeforeEach(func() { tree = &TreeNode{} }) Context("when the tree is empty", func() { It("returns an empty set of tests", func() { Ω(internal.GenerateSpecsFromTreeRoot(tree)).Should(BeEmpty()) }) }) Context("when the tree has no Its", func() { BeforeEach(func() { tree = TN(Node{}, TN(N(ntBef)), TN(N(ntCon), TN(N(ntBef)), TN(N(ntAf)), ), TN(N(ntCon), TN(N(ntCon), TN(N(ntBef)), TN(N(ntAf)), ), ), TN(N(ntAf)), ) }) It("returns an empty set of tests", func() { Ω(internal.GenerateSpecsFromTreeRoot(tree)).Should(BeEmpty()) }) }) Context("when the tree has nodes in it", func() { var tests Specs BeforeEach(func() { tree = TN(Node{}, TN(N(ntBef, "Bef #0")), TN(N(ntIt, "It #1")), TN(N(ntCon, "Container #1"), TN(N(ntBef, "Bef #1")), TN(N(ntAf, "Af #1")), TN(N(ntIt, "It #2")), ), TN(N(ntCon, "Container #2"), TN(N(ntBef, "Bef #2")), TN(N(ntCon, "Nested Container"), TN(N(ntBef, "Bef #4")), TN(N(ntIt, "It #3")), TN(N(ntIt, "It #4")), TN(N(ntAf, "Af #2")), ), TN(N(ntIt, "It #5")), TN(N(ntCon, "A Container With No Its"), TN(N(ntBef, "Bef #5")), ), TN(N(ntAf, "Af #3")), ), TN(N(ntIt, "It #6")), TN(N(ntAf, "Af #4")), ) tests = internal.GenerateSpecsFromTreeRoot(tree) }) It("constructs a flattened set of tests", func() { Ω(tests).Should(HaveLen(6)) expectedTexts := [][]string{ {"Bef #0", "It #1", "Af #4"}, {"Bef #0", "Container #1", "Bef #1", "Af #1", "It #2", "Af #4"}, {"Bef #0", "Container #2", "Bef #2", "Nested Container", "Bef #4", "It #3", "Af #2", "Af #3", "Af #4"}, {"Bef #0", "Container #2", "Bef #2", "Nested Container", "Bef #4", "It #4", "Af #2", "Af #3", "Af #4"}, {"Bef #0", "Container #2", "Bef #2", "It #5", "Af #3", "Af #4"}, {"Bef #0", "It #6", "Af #4"}, } for i, expectedText := range expectedTexts { Ω(tests[i].Nodes.Texts()).Should(Equal(expectedText)) } }) It("ensures each node as the correct nesting level", func() { extpectedNestingLevels := [][]int{ {0, 0, 0}, {0, 0, 1, 1, 1, 0}, {0, 0, 1, 1, 2, 2, 2, 1, 0}, {0, 0, 1, 1, 2, 2, 2, 1, 0}, {0, 0, 1, 1, 1, 0}, {0, 0, 0}, } for i, expectedNestingLevels := range extpectedNestingLevels { for j, expectedNestingLevel := range expectedNestingLevels { Ω(tests[i].Nodes[j].NestingLevel).Should(Equal(expectedNestingLevel)) } } }) }) }) }) golang-github-onsi-ginkgo-v2-2.22.0/internal/writer.go000066400000000000000000000050401472321612100225500ustar00rootroot00000000000000package internal import ( "bytes" "fmt" "io" "sync" "github.com/go-logr/logr" "github.com/go-logr/logr/funcr" ) type WriterMode uint const ( WriterModeStreamAndBuffer WriterMode = iota WriterModeBufferOnly ) type WriterInterface interface { io.Writer Truncate() Bytes() []byte Len() int } // Writer implements WriterInterface and GinkgoWriterInterface type Writer struct { buffer *bytes.Buffer outWriter io.Writer lock *sync.Mutex mode WriterMode streamIndent []byte indentNext bool teeWriters []io.Writer } func NewWriter(outWriter io.Writer) *Writer { return &Writer{ buffer: &bytes.Buffer{}, lock: &sync.Mutex{}, outWriter: outWriter, mode: WriterModeStreamAndBuffer, streamIndent: []byte(" "), indentNext: true, } } func (w *Writer) SetMode(mode WriterMode) { w.lock.Lock() defer w.lock.Unlock() w.mode = mode } func (w *Writer) Len() int { w.lock.Lock() defer w.lock.Unlock() return w.buffer.Len() } var newline = []byte("\n") func (w *Writer) Write(b []byte) (n int, err error) { w.lock.Lock() defer w.lock.Unlock() for _, teeWriter := range w.teeWriters { teeWriter.Write(b) } if w.mode == WriterModeStreamAndBuffer { line, remaining, found := []byte{}, b, false for len(remaining) > 0 { line, remaining, found = bytes.Cut(remaining, newline) if len(line) > 0 { if w.indentNext { w.outWriter.Write(w.streamIndent) w.indentNext = false } w.outWriter.Write(line) } if found { w.outWriter.Write(newline) w.indentNext = true } } } return w.buffer.Write(b) } func (w *Writer) Truncate() { w.lock.Lock() defer w.lock.Unlock() w.buffer.Reset() } func (w *Writer) Bytes() []byte { w.lock.Lock() defer w.lock.Unlock() b := w.buffer.Bytes() copied := make([]byte, len(b)) copy(copied, b) return copied } // GinkgoWriterInterface func (w *Writer) TeeTo(writer io.Writer) { w.lock.Lock() defer w.lock.Unlock() w.teeWriters = append(w.teeWriters, writer) } func (w *Writer) ClearTeeWriters() { w.lock.Lock() defer w.lock.Unlock() w.teeWriters = []io.Writer{} } func (w *Writer) Print(a ...interface{}) { fmt.Fprint(w, a...) } func (w *Writer) Printf(format string, a ...interface{}) { fmt.Fprintf(w, format, a...) } func (w *Writer) Println(a ...interface{}) { fmt.Fprintln(w, a...) } func GinkgoLogrFunc(writer *Writer) logr.Logger { return funcr.New(func(prefix, args string) { if prefix == "" { writer.Printf("%s\n", args) } else { writer.Printf("%s %s\n", prefix, args) } }, funcr.Options{}) } golang-github-onsi-ginkgo-v2-2.22.0/internal/writer_test.go000066400000000000000000000103711472321612100236120ustar00rootroot00000000000000package internal_test import ( "errors" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" "github.com/onsi/ginkgo/v2/internal" "github.com/onsi/gomega/gbytes" ) var _ = Describe("Writer", func() { var writer *internal.Writer var out *gbytes.Buffer BeforeEach(func() { out = gbytes.NewBuffer() writer = internal.NewWriter(out) }) Context("when configured to WriterModeStreamAndBuffer (the default setting)", func() { It("streams directly to the passed in writer, indenting content as it writes it", func() { writer.Write([]byte("foo")) Ω(out).Should(gbytes.Say(" foo")) }) It("does also stores the bytes", func() { writer.Write([]byte("foo")) Ω(out).Should(gbytes.Say("foo")) Ω(string(writer.Bytes())).Should(Equal("foo")) }) }) Describe("indenting output", func() { It("handles all the vagaries of indenting a stream", func() { writer.Write([]byte("foo")) writer.Write([]byte(" bar\nbaz")) writer.Write([]byte("zle\n")) writer.Write([]byte("\nbloop")) writer.Write([]byte("floop\n")) writer.Write([]byte("flarp\r\n")) writer.Write([]byte("flan")) Ω(string(out.Contents())).Should(Equal(" foo bar\n bazzle\n\n bloopfloop\n flarp\r\n flan")) }) }) Context("when configured to WriterModeBufferOnly", func() { BeforeEach(func() { writer.SetMode(internal.WriterModeBufferOnly) }) It("should not write to the passed in writer", func() { writer.Write([]byte("foo")) Ω(out).ShouldNot(gbytes.Say("foo")) }) Describe("Bytes()", func() { BeforeEach(func() { writer.Write([]byte("foo")) }) It("returns all that's been written so far", func() { Ω(writer.Bytes()).Should(Equal([]byte("foo"))) }) It("clears when told to truncate", func() { writer.Truncate() Ω(writer.Bytes()).Should(BeEmpty()) writer.Write([]byte("bar")) Ω(writer.Bytes()).Should(Equal([]byte("bar"))) }) }) }) Describe("Teeing to additional writers", func() { var tee1, tee2 *gbytes.Buffer BeforeEach(func() { tee1 = gbytes.NewBuffer() tee2 = gbytes.NewBuffer() }) Context("when told to tee to additional writers", func() { BeforeEach(func() { writer.TeeTo(tee1) writer.TeeTo(tee2) }) It("tees to all registered tee writers", func() { writer.Print("hello") Ω(string(tee1.Contents())).Should(Equal("hello")) Ω(string(tee2.Contents())).Should(Equal("hello")) }) Context("when told to clear tee writers", func() { BeforeEach(func() { writer.ClearTeeWriters() }) It("stops teeing to said writers", func() { writer.Print("goodbye") Ω(tee1.Contents()).Should(BeEmpty()) Ω(tee2.Contents()).Should(BeEmpty()) }) }) }) }) Describe("Convenience print methods", func() { It("can Print", func() { writer.Print("foo", "baz", " ", "bizzle") Ω(string(out.Contents())).Should(Equal(" foobaz bizzle")) }) It("can Println", func() { writer.Println("foo", "baz", " ", "bizzle") Ω(string(out.Contents())).Should(Equal(" foo baz bizzle\n")) }) It("can Printf", func() { writer.Printf("%s%d - %s\n", "foo", 17, "bar") Ω(string(out.Contents())).Should(Equal(" foo17 - bar\n")) }) }) When("wrapped by logr", func() { It("can print Info logs", func() { log := internal.GinkgoLogrFunc(writer) log.Info("message", "key", 5) Ω(string(out.Contents())).Should(Equal(" \"level\"=0 \"msg\"=\"message\" \"key\"=5\n")) }) It("can print Error logs", func() { log := internal.GinkgoLogrFunc(writer) log.Error(errors.New("cake"), "planned failure", "key", "banana") Ω(string(out.Contents())).Should(Equal(" \"msg\"=\"planned failure\" \"error\"=\"cake\" \"key\"=\"banana\"\n")) }) It("can print multiple logs", func() { log := internal.GinkgoLogrFunc(writer) log.Info("message", "key", 5) log.Error(errors.New("cake"), "planned failure", "key", "banana") Ω(string(out.Contents())).Should(Equal(" \"level\"=0 \"msg\"=\"message\" \"key\"=5\n \"msg\"=\"planned failure\" \"error\"=\"cake\" \"key\"=\"banana\"\n")) }) It("can print the logr prefix", func() { log := internal.GinkgoLogrFunc(writer) log.WithName("berry").Info("message", "key", 5) Ω(string(out.Contents())).Should(Equal(" berry \"level\"=0 \"msg\"=\"message\" \"key\"=5\n")) }) }) }) golang-github-onsi-ginkgo-v2-2.22.0/reporters/000077500000000000000000000000001472321612100211175ustar00rootroot00000000000000golang-github-onsi-ginkgo-v2-2.22.0/reporters/default_reporter.go000066400000000000000000000662551472321612100250320ustar00rootroot00000000000000/* Ginkgo's Default Reporter A number of command line flags are available to tweak Ginkgo's default output. These are documented [here](http://onsi.github.io/ginkgo/#running_tests) */ package reporters import ( "fmt" "io" "runtime" "strings" "sync" "time" "github.com/onsi/ginkgo/v2/formatter" "github.com/onsi/ginkgo/v2/types" ) type DefaultReporter struct { conf types.ReporterConfig writer io.Writer // managing the emission stream lastCharWasNewline bool lastEmissionWasDelimiter bool // rendering specDenoter string retryDenoter string formatter formatter.Formatter runningInParallel bool lock *sync.Mutex } func NewDefaultReporterUnderTest(conf types.ReporterConfig, writer io.Writer) *DefaultReporter { reporter := NewDefaultReporter(conf, writer) reporter.formatter = formatter.New(formatter.ColorModePassthrough) return reporter } func NewDefaultReporter(conf types.ReporterConfig, writer io.Writer) *DefaultReporter { reporter := &DefaultReporter{ conf: conf, writer: writer, lastCharWasNewline: true, lastEmissionWasDelimiter: false, specDenoter: "•", retryDenoter: "↺", formatter: formatter.NewWithNoColorBool(conf.NoColor), lock: &sync.Mutex{}, } if runtime.GOOS == "windows" { reporter.specDenoter = "+" reporter.retryDenoter = "R" } return reporter } /* The Reporter Interface */ func (r *DefaultReporter) SuiteWillBegin(report types.Report) { if r.conf.Verbosity().Is(types.VerbosityLevelSuccinct) { r.emit(r.f("[%d] {{bold}}%s{{/}} ", report.SuiteConfig.RandomSeed, report.SuiteDescription)) if len(report.SuiteLabels) > 0 { r.emit(r.f("{{coral}}[%s]{{/}} ", strings.Join(report.SuiteLabels, ", "))) } r.emit(r.f("- %d/%d specs ", report.PreRunStats.SpecsThatWillRun, report.PreRunStats.TotalSpecs)) if report.SuiteConfig.ParallelTotal > 1 { r.emit(r.f("- %d procs ", report.SuiteConfig.ParallelTotal)) } } else { banner := r.f("Running Suite: %s - %s", report.SuiteDescription, report.SuitePath) r.emitBlock(banner) bannerWidth := len(banner) if len(report.SuiteLabels) > 0 { labels := strings.Join(report.SuiteLabels, ", ") r.emitBlock(r.f("{{coral}}[%s]{{/}} ", labels)) if len(labels)+2 > bannerWidth { bannerWidth = len(labels) + 2 } } r.emitBlock(strings.Repeat("=", bannerWidth)) out := r.f("Random Seed: {{bold}}%d{{/}}", report.SuiteConfig.RandomSeed) if report.SuiteConfig.RandomizeAllSpecs { out += r.f(" - will randomize all specs") } r.emitBlock(out) r.emit("\n") r.emitBlock(r.f("Will run {{bold}}%d{{/}} of {{bold}}%d{{/}} specs", report.PreRunStats.SpecsThatWillRun, report.PreRunStats.TotalSpecs)) if report.SuiteConfig.ParallelTotal > 1 { r.emitBlock(r.f("Running in parallel across {{bold}}%d{{/}} processes", report.SuiteConfig.ParallelTotal)) } } } func (r *DefaultReporter) SuiteDidEnd(report types.Report) { failures := report.SpecReports.WithState(types.SpecStateFailureStates) if len(failures) > 0 { r.emitBlock("\n") if len(failures) > 1 { r.emitBlock(r.f("{{red}}{{bold}}Summarizing %d Failures:{{/}}", len(failures))) } else { r.emitBlock(r.f("{{red}}{{bold}}Summarizing 1 Failure:{{/}}")) } for _, specReport := range failures { highlightColor, heading := "{{red}}", "[FAIL]" switch specReport.State { case types.SpecStatePanicked: highlightColor, heading = "{{magenta}}", "[PANICKED!]" case types.SpecStateAborted: highlightColor, heading = "{{coral}}", "[ABORTED]" case types.SpecStateTimedout: highlightColor, heading = "{{orange}}", "[TIMEDOUT]" case types.SpecStateInterrupted: highlightColor, heading = "{{orange}}", "[INTERRUPTED]" } locationBlock := r.codeLocationBlock(specReport, highlightColor, false, true) r.emitBlock(r.fi(1, highlightColor+"%s{{/}} %s", heading, locationBlock)) } } //summarize the suite if r.conf.Verbosity().Is(types.VerbosityLevelSuccinct) && report.SuiteSucceeded { r.emit(r.f(" {{green}}SUCCESS!{{/}} %s ", report.RunTime)) return } r.emitBlock("\n") color, status := "{{green}}{{bold}}", "SUCCESS!" if !report.SuiteSucceeded { color, status = "{{red}}{{bold}}", "FAIL!" } specs := report.SpecReports.WithLeafNodeType(types.NodeTypeIt) //exclude any suite setup nodes r.emitBlock(r.f(color+"Ran %d of %d Specs in %.3f seconds{{/}}", specs.CountWithState(types.SpecStatePassed)+specs.CountWithState(types.SpecStateFailureStates), report.PreRunStats.TotalSpecs, report.RunTime.Seconds()), ) switch len(report.SpecialSuiteFailureReasons) { case 0: r.emit(r.f(color+"%s{{/}} -- ", status)) case 1: r.emit(r.f(color+"%s - %s{{/}} -- ", status, report.SpecialSuiteFailureReasons[0])) default: r.emitBlock(r.f(color+"%s - %s{{/}}\n", status, strings.Join(report.SpecialSuiteFailureReasons, ", "))) } if len(specs) == 0 && report.SpecReports.WithLeafNodeType(types.NodeTypeBeforeSuite|types.NodeTypeSynchronizedBeforeSuite).CountWithState(types.SpecStateFailureStates) > 0 { r.emit(r.f("{{cyan}}{{bold}}A BeforeSuite node failed so all tests were skipped.{{/}}\n")) } else { r.emit(r.f("{{green}}{{bold}}%d Passed{{/}} | ", specs.CountWithState(types.SpecStatePassed))) r.emit(r.f("{{red}}{{bold}}%d Failed{{/}} | ", specs.CountWithState(types.SpecStateFailureStates))) if specs.CountOfFlakedSpecs() > 0 { r.emit(r.f("{{light-yellow}}{{bold}}%d Flaked{{/}} | ", specs.CountOfFlakedSpecs())) } if specs.CountOfRepeatedSpecs() > 0 { r.emit(r.f("{{light-yellow}}{{bold}}%d Repeated{{/}} | ", specs.CountOfRepeatedSpecs())) } r.emit(r.f("{{yellow}}{{bold}}%d Pending{{/}} | ", specs.CountWithState(types.SpecStatePending))) r.emit(r.f("{{cyan}}{{bold}}%d Skipped{{/}}\n", specs.CountWithState(types.SpecStateSkipped))) } } func (r *DefaultReporter) WillRun(report types.SpecReport) { v := r.conf.Verbosity() if v.LT(types.VerbosityLevelVerbose) || report.State.Is(types.SpecStatePending|types.SpecStateSkipped) || report.RunningInParallel { return } r.emitDelimiter(0) r.emitBlock(r.f(r.codeLocationBlock(report, "{{/}}", v.Is(types.VerbosityLevelVeryVerbose), false))) } func (r *DefaultReporter) wrapTextBlock(sectionName string, fn func()) { r.emitBlock("\n") if r.conf.GithubOutput { r.emitBlock(r.fi(1, "::group::%s", sectionName)) } else { r.emitBlock(r.fi(1, "{{gray}}%s >>{{/}}", sectionName)) } fn() if r.conf.GithubOutput { r.emitBlock(r.fi(1, "::endgroup::")) } else { r.emitBlock(r.fi(1, "{{gray}}<< %s{{/}}", sectionName)) } } func (r *DefaultReporter) DidRun(report types.SpecReport) { v := r.conf.Verbosity() inParallel := report.RunningInParallel //should we completely omit this spec? if report.State.Is(types.SpecStateSkipped) && r.conf.SilenceSkips { return } header := r.specDenoter if report.LeafNodeType.Is(types.NodeTypesForSuiteLevelNodes) { header = fmt.Sprintf("[%s]", report.LeafNodeType) } highlightColor := r.highlightColorForState(report.State) // have we already been streaming the timeline? timelineHasBeenStreaming := v.GTE(types.VerbosityLevelVerbose) && !inParallel // should we show the timeline? var timeline types.Timeline showTimeline := !timelineHasBeenStreaming && (v.GTE(types.VerbosityLevelVerbose) || report.Failed()) if showTimeline { timeline = report.Timeline().WithoutHiddenReportEntries() keepVeryVerboseSpecEvents := v.Is(types.VerbosityLevelVeryVerbose) || (v.Is(types.VerbosityLevelVerbose) && r.conf.ShowNodeEvents) || (report.Failed() && r.conf.ShowNodeEvents) if !keepVeryVerboseSpecEvents { timeline = timeline.WithoutVeryVerboseSpecEvents() } if len(timeline) == 0 && report.CapturedGinkgoWriterOutput == "" { // the timeline is completely empty - don't show it showTimeline = false } if v.LT(types.VerbosityLevelVeryVerbose) && report.CapturedGinkgoWriterOutput == "" && len(timeline) > 0 { //if we aren't -vv and the timeline only has a single failure, don't show it as it will appear at the end of the report failure, isFailure := timeline[0].(types.Failure) if isFailure && (len(timeline) == 1 || (len(timeline) == 2 && failure.AdditionalFailure != nil)) { showTimeline = false } } } // should we have a separate section for always-visible reports? showSeparateVisibilityAlwaysReportsSection := !timelineHasBeenStreaming && !showTimeline && report.ReportEntries.HasVisibility(types.ReportEntryVisibilityAlways) // should we have a separate section for captured stdout/stderr showSeparateStdSection := inParallel && (report.CapturedStdOutErr != "") // given all that - do we have any actual content to show? or are we a single denoter in a stream? reportHasContent := v.Is(types.VerbosityLevelVeryVerbose) || showTimeline || showSeparateVisibilityAlwaysReportsSection || showSeparateStdSection || report.Failed() || (v.Is(types.VerbosityLevelVerbose) && !report.State.Is(types.SpecStateSkipped)) // should we show a runtime? includeRuntime := !report.State.Is(types.SpecStateSkipped|types.SpecStatePending) || (report.State.Is(types.SpecStateSkipped) && report.Failure.Message != "") // should we show the codelocation block? showCodeLocation := !timelineHasBeenStreaming || !report.State.Is(types.SpecStatePassed) switch report.State { case types.SpecStatePassed: if report.LeafNodeType.Is(types.NodeTypesForSuiteLevelNodes) && !reportHasContent { return } if report.LeafNodeType.Is(types.NodeTypesForSuiteLevelNodes) { header = fmt.Sprintf("%s PASSED", header) } if report.NumAttempts > 1 && report.MaxFlakeAttempts > 1 { header, reportHasContent = fmt.Sprintf("%s [FLAKEY TEST - TOOK %d ATTEMPTS TO PASS]", r.retryDenoter, report.NumAttempts), true } case types.SpecStatePending: header = "P" if v.GT(types.VerbosityLevelSuccinct) { header, reportHasContent = "P [PENDING]", true } case types.SpecStateSkipped: header = "S" if v.Is(types.VerbosityLevelVeryVerbose) || (v.Is(types.VerbosityLevelVerbose) && report.Failure.Message != "") { header, reportHasContent = "S [SKIPPED]", true } default: header = fmt.Sprintf("%s [%s]", header, r.humanReadableState(report.State)) if report.MaxMustPassRepeatedly > 1 { header = fmt.Sprintf("%s DURING REPETITION #%d", header, report.NumAttempts) } } // If we have no content to show, just emit the header and return if !reportHasContent { r.emit(r.f(highlightColor + header + "{{/}}")) if r.conf.ForceNewlines { r.emit("\n") } return } if includeRuntime { header = r.f("%s [%.3f seconds]", header, report.RunTime.Seconds()) } // Emit header if !timelineHasBeenStreaming { r.emitDelimiter(0) } r.emitBlock(r.f(highlightColor + header + "{{/}}")) if showCodeLocation { r.emitBlock(r.codeLocationBlock(report, highlightColor, v.Is(types.VerbosityLevelVeryVerbose), false)) } //Emit Stdout/Stderr Output if showSeparateStdSection { r.wrapTextBlock("Captured StdOut/StdErr Output", func() { r.emitBlock(r.fi(1, "%s", report.CapturedStdOutErr)) }) } if showSeparateVisibilityAlwaysReportsSection { r.wrapTextBlock("Report Entries", func() { for _, entry := range report.ReportEntries.WithVisibility(types.ReportEntryVisibilityAlways) { r.emitReportEntry(1, entry) } }) } if showTimeline { r.wrapTextBlock("Timeline", func() { r.emitTimeline(1, report, timeline) }) } // Emit Failure Message if !report.Failure.IsZero() && !v.Is(types.VerbosityLevelVeryVerbose) { r.emitBlock("\n") r.emitFailure(1, report.State, report.Failure, true) if len(report.AdditionalFailures) > 0 { r.emitBlock(r.fi(1, "\nThere were {{bold}}{{red}}additional failures{{/}} detected. To view them in detail run {{bold}}ginkgo -vv{{/}}")) } } r.emitDelimiter(0) } func (r *DefaultReporter) highlightColorForState(state types.SpecState) string { switch state { case types.SpecStatePassed: return "{{green}}" case types.SpecStatePending: return "{{yellow}}" case types.SpecStateSkipped: return "{{cyan}}" case types.SpecStateFailed: return "{{red}}" case types.SpecStateTimedout: return "{{orange}}" case types.SpecStatePanicked: return "{{magenta}}" case types.SpecStateInterrupted: return "{{orange}}" case types.SpecStateAborted: return "{{coral}}" default: return "{{gray}}" } } func (r *DefaultReporter) humanReadableState(state types.SpecState) string { return strings.ToUpper(state.String()) } func (r *DefaultReporter) emitTimeline(indent uint, report types.SpecReport, timeline types.Timeline) { isVeryVerbose := r.conf.Verbosity().Is(types.VerbosityLevelVeryVerbose) gw := report.CapturedGinkgoWriterOutput cursor := 0 for _, entry := range timeline { tl := entry.GetTimelineLocation() if tl.Offset < len(gw) { r.emit(r.fi(indent, "%s", gw[cursor:tl.Offset])) cursor = tl.Offset } else if cursor < len(gw) { r.emit(r.fi(indent, "%s", gw[cursor:])) cursor = len(gw) } switch x := entry.(type) { case types.Failure: if isVeryVerbose { r.emitFailure(indent, report.State, x, false) } else { r.emitShortFailure(indent, report.State, x) } case types.AdditionalFailure: if isVeryVerbose { r.emitFailure(indent, x.State, x.Failure, true) } else { r.emitShortFailure(indent, x.State, x.Failure) } case types.ReportEntry: r.emitReportEntry(indent, x) case types.ProgressReport: r.emitProgressReport(indent, false, x) case types.SpecEvent: if isVeryVerbose || !x.IsOnlyVisibleAtVeryVerbose() || r.conf.ShowNodeEvents { r.emitSpecEvent(indent, x, isVeryVerbose) } } } if cursor < len(gw) { r.emit(r.fi(indent, "%s", gw[cursor:])) } } func (r *DefaultReporter) EmitFailure(state types.SpecState, failure types.Failure) { if r.conf.Verbosity().Is(types.VerbosityLevelVerbose) { r.emitShortFailure(1, state, failure) } else if r.conf.Verbosity().Is(types.VerbosityLevelVeryVerbose) { r.emitFailure(1, state, failure, true) } } func (r *DefaultReporter) emitShortFailure(indent uint, state types.SpecState, failure types.Failure) { r.emitBlock(r.fi(indent, r.highlightColorForState(state)+"[%s]{{/}} in [%s] - %s {{gray}}@ %s{{/}}", r.humanReadableState(state), failure.FailureNodeType, failure.Location, failure.TimelineLocation.Time.Format(types.GINKGO_TIME_FORMAT), )) } func (r *DefaultReporter) emitFailure(indent uint, state types.SpecState, failure types.Failure, includeAdditionalFailure bool) { highlightColor := r.highlightColorForState(state) r.emitBlock(r.fi(indent, highlightColor+"[%s] %s{{/}}", r.humanReadableState(state), failure.Message)) if r.conf.GithubOutput { level := "error" if state.Is(types.SpecStateSkipped) { level = "notice" } r.emitBlock(r.fi(indent, "::%s file=%s,line=%d::%s %s", level, failure.Location.FileName, failure.Location.LineNumber, failure.FailureNodeType, failure.TimelineLocation.Time.Format(types.GINKGO_TIME_FORMAT))) } else { r.emitBlock(r.fi(indent, highlightColor+"In {{bold}}[%s]{{/}}"+highlightColor+" at: {{bold}}%s{{/}} {{gray}}@ %s{{/}}\n", failure.FailureNodeType, failure.Location, failure.TimelineLocation.Time.Format(types.GINKGO_TIME_FORMAT))) } if failure.ForwardedPanic != "" { r.emitBlock("\n") r.emitBlock(r.fi(indent, highlightColor+"%s{{/}}", failure.ForwardedPanic)) } if r.conf.FullTrace || failure.ForwardedPanic != "" { r.emitBlock("\n") r.emitBlock(r.fi(indent, highlightColor+"Full Stack Trace{{/}}")) r.emitBlock(r.fi(indent+1, "%s", failure.Location.FullStackTrace)) } if !failure.ProgressReport.IsZero() { r.emitBlock("\n") r.emitProgressReport(indent, false, failure.ProgressReport) } if failure.AdditionalFailure != nil && includeAdditionalFailure { r.emitBlock("\n") r.emitFailure(indent, failure.AdditionalFailure.State, failure.AdditionalFailure.Failure, true) } } func (r *DefaultReporter) EmitProgressReport(report types.ProgressReport) { r.emitDelimiter(1) if report.RunningInParallel { r.emit(r.fi(1, "{{coral}}Progress Report for Ginkgo Process #{{bold}}%d{{/}}\n", report.ParallelProcess)) } shouldEmitGW := report.RunningInParallel || r.conf.Verbosity().LT(types.VerbosityLevelVerbose) r.emitProgressReport(1, shouldEmitGW, report) r.emitDelimiter(1) } func (r *DefaultReporter) emitProgressReport(indent uint, emitGinkgoWriterOutput bool, report types.ProgressReport) { if report.Message != "" { r.emitBlock(r.fi(indent, report.Message+"\n")) indent += 1 } if report.LeafNodeText != "" { subjectIndent := indent if len(report.ContainerHierarchyTexts) > 0 { r.emit(r.fi(indent, r.cycleJoin(report.ContainerHierarchyTexts, " "))) r.emit(" ") subjectIndent = 0 } r.emit(r.fi(subjectIndent, "{{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 != "" { r.emit("\n") r.emitBlock(r.fi(indent, "{{gray}}Begin Captured GinkgoWriter Output >>{{/}}")) limit, lines := 10, strings.Split(report.CapturedGinkgoWriterOutput, "\n") if len(lines) <= limit { r.emitBlock(r.fi(indent+1, "%s", report.CapturedGinkgoWriterOutput)) } 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{{/}}")) } if !report.SpecGoroutine().IsZero() { r.emit("\n") r.emit(r.fi(indent, "{{bold}}{{underline}}Spec Goroutine{{/}}\n")) r.emitGoroutines(indent, report.SpecGoroutine()) } if len(report.AdditionalReports) > 0 { r.emit("\n") r.emitBlock(r.fi(indent, "{{gray}}Begin Additional Progress Reports >>{{/}}")) for i, additionalReport := range report.AdditionalReports { r.emit(r.fi(indent+1, additionalReport)) if i < len(report.AdditionalReports)-1 { r.emitBlock(r.fi(indent+1, "{{gray}}%s{{/}}", strings.Repeat("-", 10))) } } r.emitBlock(r.fi(indent, "{{gray}}<< End Additional Progress Reports{{/}}")) } 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) EmitReportEntry(entry types.ReportEntry) { if r.conf.Verbosity().LT(types.VerbosityLevelVerbose) || entry.Visibility == types.ReportEntryVisibilityNever { return } r.emitReportEntry(1, entry) } func (r *DefaultReporter) emitReportEntry(indent uint, entry types.ReportEntry) { r.emitBlock(r.fi(indent, "{{bold}}"+entry.Name+"{{gray}} "+fmt.Sprintf("- %s @ %s{{/}}", entry.Location, entry.Time.Format(types.GINKGO_TIME_FORMAT)))) if representation := entry.StringRepresentation(); representation != "" { r.emitBlock(r.fi(indent+1, representation)) } } func (r *DefaultReporter) EmitSpecEvent(event types.SpecEvent) { v := r.conf.Verbosity() if v.Is(types.VerbosityLevelVeryVerbose) || (v.Is(types.VerbosityLevelVerbose) && (r.conf.ShowNodeEvents || !event.IsOnlyVisibleAtVeryVerbose())) { r.emitSpecEvent(1, event, r.conf.Verbosity().Is(types.VerbosityLevelVeryVerbose)) } } func (r *DefaultReporter) emitSpecEvent(indent uint, event types.SpecEvent, includeLocation bool) { location := "" if includeLocation { location = fmt.Sprintf("- %s ", event.CodeLocation.String()) } switch event.SpecEventType { case types.SpecEventInvalid: return case types.SpecEventByStart: r.emitBlock(r.fi(indent, "{{bold}}STEP:{{/}} %s {{gray}}%s@ %s{{/}}", event.Message, location, event.TimelineLocation.Time.Format(types.GINKGO_TIME_FORMAT))) case types.SpecEventByEnd: r.emitBlock(r.fi(indent, "{{bold}}END STEP:{{/}} %s {{gray}}%s@ %s (%s){{/}}", event.Message, location, event.TimelineLocation.Time.Format(types.GINKGO_TIME_FORMAT), event.Duration.Round(time.Millisecond))) case types.SpecEventNodeStart: r.emitBlock(r.fi(indent, "> Enter {{bold}}[%s]{{/}} %s {{gray}}%s@ %s{{/}}", event.NodeType.String(), event.Message, location, event.TimelineLocation.Time.Format(types.GINKGO_TIME_FORMAT))) case types.SpecEventNodeEnd: r.emitBlock(r.fi(indent, "< Exit {{bold}}[%s]{{/}} %s {{gray}}%s@ %s (%s){{/}}", event.NodeType.String(), event.Message, location, event.TimelineLocation.Time.Format(types.GINKGO_TIME_FORMAT), event.Duration.Round(time.Millisecond))) case types.SpecEventSpecRepeat: r.emitBlock(r.fi(indent, "\n{{bold}}Attempt #%d {{green}}Passed{{/}}{{bold}}. Repeating %s{{/}} {{gray}}@ %s{{/}}\n\n", event.Attempt, r.retryDenoter, event.TimelineLocation.Time.Format(types.GINKGO_TIME_FORMAT))) case types.SpecEventSpecRetry: r.emitBlock(r.fi(indent, "\n{{bold}}Attempt #%d {{red}}Failed{{/}}{{bold}}. Retrying %s{{/}} {{gray}}@ %s{{/}}\n\n", event.Attempt, r.retryDenoter, event.TimelineLocation.Time.Format(types.GINKGO_TIME_FORMAT))) } } 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) { r._emit(s, false, false) } func (r *DefaultReporter) emitBlock(s string) { r._emit(s, true, false) } func (r *DefaultReporter) emitDelimiter(indent uint) { r._emit(r.fi(indent, "{{gray}}%s{{/}}", strings.Repeat("-", 30)), true, true) } // a bit ugly - but we're trying to minimize locking on this hot codepath func (r *DefaultReporter) _emit(s string, block bool, isDelimiter bool) { if len(s) == 0 { return } r.lock.Lock() defer r.lock.Unlock() if isDelimiter && r.lastEmissionWasDelimiter { return } if block && !r.lastCharWasNewline { r.writer.Write([]byte("\n")) } r.lastCharWasNewline = (s[len(s)-1:] == "\n") r.writer.Write([]byte(s)) if block && !r.lastCharWasNewline { r.writer.Write([]byte("\n")) r.lastCharWasNewline = true } r.lastEmissionWasDelimiter = isDelimiter } /* Rendering text */ func (r *DefaultReporter) f(format string, args ...interface{}) string { return r.formatter.F(format, args...) } func (r *DefaultReporter) fi(indentation uint, format string, args ...interface{}) string { return r.formatter.Fi(indentation, format, args...) } func (r *DefaultReporter) cycleJoin(elements []string, joiner string) string { return r.formatter.CycleJoin(elements, joiner, []string{"{{/}}", "{{gray}}"}) } func (r *DefaultReporter) codeLocationBlock(report types.SpecReport, highlightColor string, veryVerbose bool, usePreciseFailureLocation bool) string { texts, locations, labels := []string{}, []types.CodeLocation{}, [][]string{} texts, locations, labels = append(texts, report.ContainerHierarchyTexts...), append(locations, report.ContainerHierarchyLocations...), append(labels, report.ContainerHierarchyLabels...) if report.LeafNodeType.Is(types.NodeTypesForSuiteLevelNodes) { texts = append(texts, r.f("[%s] %s", report.LeafNodeType, report.LeafNodeText)) } else { texts = append(texts, r.f(report.LeafNodeText)) } labels = append(labels, report.LeafNodeLabels) locations = append(locations, report.LeafNodeLocation) failureLocation := report.Failure.FailureNodeLocation if usePreciseFailureLocation { failureLocation = report.Failure.Location } highlightIndex := -1 switch report.Failure.FailureNodeContext { case types.FailureNodeAtTopLevel: texts = append([]string{fmt.Sprintf("TOP-LEVEL [%s]", report.Failure.FailureNodeType)}, texts...) locations = append([]types.CodeLocation{failureLocation}, locations...) labels = append([][]string{{}}, labels...) highlightIndex = 0 case types.FailureNodeInContainer: i := report.Failure.FailureNodeContainerIndex texts[i] = fmt.Sprintf("%s [%s]", texts[i], report.Failure.FailureNodeType) locations[i] = failureLocation highlightIndex = i case types.FailureNodeIsLeafNode: i := len(texts) - 1 texts[i] = fmt.Sprintf("[%s] %s", report.LeafNodeType, report.LeafNodeText) locations[i] = failureLocation highlightIndex = i default: //there is no failure, so we highlight the leaf ndoe highlightIndex = len(texts) - 1 } out := "" if veryVerbose { for i := range texts { if i == highlightIndex { out += r.fi(uint(i), highlightColor+"{{bold}}%s{{/}}", texts[i]) } else { out += r.fi(uint(i), "%s", texts[i]) } if len(labels[i]) > 0 { out += r.f(" {{coral}}[%s]{{/}}", strings.Join(labels[i], ", ")) } out += "\n" out += r.fi(uint(i), "{{gray}}%s{{/}}\n", locations[i]) } } else { for i := range texts { style := "{{/}}" if i%2 == 1 { style = "{{gray}}" } if i == highlightIndex { style = highlightColor + "{{bold}}" } out += r.f(style+"%s", texts[i]) if i < len(texts)-1 { out += " " } else { out += r.f("{{/}}") } } flattenedLabels := report.Labels() if len(flattenedLabels) > 0 { out += r.f(" {{coral}}[%s]{{/}}", strings.Join(flattenedLabels, ", ")) } out += "\n" if usePreciseFailureLocation { out += r.f("{{gray}}%s{{/}}", failureLocation) } else { leafLocation := locations[len(locations)-1] if (report.Failure.FailureNodeLocation != types.CodeLocation{}) && (report.Failure.FailureNodeLocation != leafLocation) { out += r.fi(1, highlightColor+"[%s]{{/}} {{gray}}%s{{/}}\n", report.Failure.FailureNodeType, report.Failure.FailureNodeLocation) out += r.fi(1, "{{gray}}[%s] %s{{/}}", report.LeafNodeType, leafLocation) } else { out += r.f("{{gray}}%s{{/}}", leafLocation) } } } return out } golang-github-onsi-ginkgo-v2-2.22.0/reporters/default_reporter_test.go000066400000000000000000003166611472321612100260700ustar00rootroot00000000000000package reporters_test import ( "fmt" "reflect" "runtime" "strings" "time" . "github.com/onsi/ginkgo/v2" "github.com/onsi/ginkgo/v2/internal" "github.com/onsi/ginkgo/v2/internal/test_helpers" "github.com/onsi/ginkgo/v2/reporters" "github.com/onsi/ginkgo/v2/types" . "github.com/onsi/gomega" "github.com/onsi/gomega/format" "github.com/onsi/gomega/gbytes" ) var MatchLines = test_helpers.MatchLines // This is a mini-DSL for quickly and succinctly building Ginkgo SpecReports and all their attendant objects const DELIMITER = `{{gray}}------------------------------{{/}}` const INDENTED_DELIMITER = ` {{gray}}------------------------------{{/}}` var cl0 = types.CodeLocation{FileName: "cl0.go", LineNumber: 12, FullStackTrace: "full-trace\ncl-0"} var cl1 = types.CodeLocation{FileName: "cl1.go", LineNumber: 37, FullStackTrace: "full-trace\ncl-1"} var cl2 = types.CodeLocation{FileName: "cl2.go", LineNumber: 80, FullStackTrace: "full-trace\ncl-2"} var cl3 = types.CodeLocation{FileName: "cl3.go", LineNumber: 103, FullStackTrace: "full-trace\ncl-3"} var cl4 = types.CodeLocation{FileName: "cl4.go", LineNumber: 144, FullStackTrace: "full-trace\ncl-4"} var now = time.Now() func CLS(cls ...types.CodeLocation) []types.CodeLocation { return cls } func CTS(componentTexts ...string) []string { return componentTexts } func CLabels(labels ...Labels) []Labels { return labels } func spr(format string, args ...any) string { return fmt.Sprintf(format, args...) } type FailureNodeLocation types.CodeLocation type ForwardedPanic string type StackTrace string var PLACEHOLDER_TIME = now var FORMATTED_TIME = PLACEHOLDER_TIME.Format(types.GINKGO_TIME_FORMAT) var tlOrder = 1 func TL(options ...interface{}) types.TimelineLocation { out := types.TimelineLocation{ Order: tlOrder, Time: now, } tlOrder += 1 for _, option := range options { switch x := option.(type) { case string: out.Offset = len(x) case int: out.Offset = x case time.Time: out.Time = x } } return out } // convenience helper to quickly make Failures func F(options ...interface{}) types.Failure { failure := types.Failure{TimelineLocation: TL()} for _, option := range options { switch x := option.(type) { case string: failure.Message = x case types.CodeLocation: failure.Location = x case ForwardedPanic: failure.ForwardedPanic = string(x) case types.FailureNodeContext: failure.FailureNodeContext = x case int: failure.FailureNodeContainerIndex = x case FailureNodeLocation: failure.FailureNodeLocation = types.CodeLocation(x) case types.NodeType: failure.FailureNodeType = x case types.ProgressReport: failure.ProgressReport = x case types.AdditionalFailure: failure.AdditionalFailure = &x case types.TimelineLocation: failure.TimelineLocation = x } } return failure } func AF(state types.SpecState, options ...interface{}) types.AdditionalFailure { return types.AdditionalFailure{ State: state, Failure: F(options...), } } type STD string type GW string // convenience helper to quickly make SpecReports func S(options ...interface{}) types.SpecReport { report := types.SpecReport{ LeafNodeType: types.NodeTypeIt, State: types.SpecStatePassed, NumAttempts: 1, MaxFlakeAttempts: 1, MaxMustPassRepeatedly: 1, RunTime: time.Second, } for _, option := range options { switch x := option.(type) { case []string: report.ContainerHierarchyTexts = x case []types.CodeLocation: report.ContainerHierarchyLocations = x case []Labels: report.ContainerHierarchyLabels = [][]string{} for _, labels := range x { report.ContainerHierarchyLabels = append(report.ContainerHierarchyLabels, []string(labels)) } case string: report.LeafNodeText = x case types.NodeType: report.LeafNodeType = x case types.CodeLocation: report.LeafNodeLocation = x case Labels: report.LeafNodeLabels = x case types.SpecState: report.State = x case time.Duration: report.RunTime = x case int: report.NumAttempts = x case FlakeAttempts: report.MaxFlakeAttempts = int(x) case MustPassRepeatedly: report.MaxMustPassRepeatedly = int(x) case STD: report.CapturedStdOutErr = string(x) case GW: report.CapturedGinkgoWriterOutput = string(x) case types.Failure: report.Failure = x case types.AdditionalFailure: report.AdditionalFailures = append(report.AdditionalFailures, x) case types.ReportEntry: report.ReportEntries = append(report.ReportEntries, x) case types.ProgressReport: report.ProgressReports = append(report.ProgressReports, x) case types.SpecEvent: report.SpecEvents = append(report.SpecEvents, x) } } if len(report.ContainerHierarchyLabels) == 0 { for range report.ContainerHierarchyTexts { report.ContainerHierarchyLabels = append(report.ContainerHierarchyLabels, []string{}) } } return report } type ConfigFlag uint16 const ( Succinct ConfigFlag = 1 << iota Normal Verbose VeryVerbose FullTrace ShowNodeEvents GithubOutput SilenceSkips ForceNewlines Parallel //used in the WillRun => DidRun specs to capture behavior when running in parallel ) func (cf ConfigFlag) Has(flag ConfigFlag) bool { return cf&flag != 0 } func (cf ConfigFlag) String() string { out := []string{} if cf.Has(Succinct) { out = append(out, "succinct") } if cf.Has(Normal) { out = append(out, "normal") } if cf.Has(Verbose) { out = append(out, "verbose") } if cf.Has(VeryVerbose) { out = append(out, "very-verbose") } if cf.Has(FullTrace) { out = append(out, "full-trace") } if cf.Has(ShowNodeEvents) { out = append(out, "show-node-events") } if cf.Has(Parallel) { out = append(out, "parallel") } if cf.Has(GithubOutput) { out = append(out, "github-output") } if cf.Has(SilenceSkips) { out = append(out, "silence-skips") } if cf.Has(ForceNewlines) { out = append(out, "force-newlines") } return strings.Join(out, "|") } func C(flags ...ConfigFlag) types.ReporterConfig { f := ConfigFlag(0) if len(flags) > 0 { f = flags[0] } numVerbosity := 0 for _, verbosityFlag := range []ConfigFlag{Succinct, Normal, Verbose, VeryVerbose} { if f.Has(verbosityFlag) { numVerbosity += 1 } } Ω(numVerbosity).Should(BeNumerically("<=", 1), "Setting more than one of Succinct, Normal, Verbose, or VeryVerbose is a configuration error") return types.ReporterConfig{ NoColor: true, Succinct: f.Has(Succinct), Verbose: f.Has(Verbose), VeryVerbose: f.Has(VeryVerbose), FullTrace: f.Has(FullTrace), ShowNodeEvents: f.Has(ShowNodeEvents), GithubOutput: f.Has(GithubOutput), SilenceSkips: f.Has(SilenceSkips), ForceNewlines: f.Has(ForceNewlines), } } type ConfigCase struct { ConfigFlags []ConfigFlag Expected []any } func Case(args ...any) ConfigCase { out := ConfigCase{} for _, arg := range args { switch x := arg.(type) { case ConfigFlag: out.ConfigFlags = append(out.ConfigFlags, x) default: out.Expected = append(out.Expected, x) } } return out } type CurrentNodeText string type CurrentStepText string type LeafNodeText string type AdditionalReports []string func PR(options ...interface{}) types.ProgressReport { report := types.ProgressReport{ ParallelProcess: 1, RunningInParallel: false, SpecStartTime: now.Add(-5 * time.Second), CurrentNodeStartTime: now.Add(-3 * time.Second), CurrentStepStartTime: now.Add(-1 * time.Second), LeafNodeLocation: cl0, CurrentNodeLocation: cl1, CurrentStepLocation: cl2, TimelineLocation: TL(), } for _, option := range options { switch x := option.(type) { case []string: report.ContainerHierarchyTexts = x case GW: report.CapturedGinkgoWriterOutput = string(x) case LeafNodeText: report.LeafNodeText = string(x) case types.NodeType: report.CurrentNodeType = x case CurrentNodeText: report.CurrentNodeText = string(x) case CurrentStepText: report.CurrentStepText = string(x) case types.Goroutine: report.Goroutines = append(report.Goroutines, x) case []types.Goroutine: report.Goroutines = append(report.Goroutines, x...) case int: report.ParallelProcess = x case bool: report.RunningInParallel = x case string: report.Message = x case AdditionalReports: report.AdditionalReports = x case types.TimelineLocation: report.TimelineLocation = x } } return report } func Fn(f string, filename string, line int, options ...interface{}) types.FunctionCall { out := types.FunctionCall{ Function: f, Filename: filename, Line: line, } for _, option := range options { switch option := option.(type) { case bool: out.Highlight = option case string: out.Source = append(out.Source, option) case int: out.SourceHighlight = option } } return out } func G(options ...interface{}) types.Goroutine { goroutine := types.Goroutine{ ID: 17, State: "running", IsSpecGoroutine: false, } for _, option := range options { switch reflect.TypeOf(option) { case reflect.TypeOf(true): goroutine.IsSpecGoroutine = option.(bool) case reflect.TypeOf(""): goroutine.State = option.(string) case reflect.TypeOf(types.FunctionCall{}): goroutine.Stack = append(goroutine.Stack, option.(types.FunctionCall)) case reflect.TypeOf([]types.FunctionCall{}): goroutine.Stack = append(goroutine.Stack, option.([]types.FunctionCall)...) } } return goroutine } func RE(name string, cl types.CodeLocation, args ...interface{}) types.ReportEntry { var tl = TL() finalArgs := []any{} for _, arg := range args { if theTl, isTl := arg.(types.TimelineLocation); isTl { tl = theTl } else { finalArgs = append(finalArgs, arg) } } entry, _ := internal.NewReportEntry(name, cl, finalArgs...) entry.Time = PLACEHOLDER_TIME entry.TimelineLocation = tl return entry } func SE(options ...interface{}) types.SpecEvent { se := types.SpecEvent{TimelineLocation: TL()} for _, option := range options { switch x := option.(type) { case types.SpecEventType: se.SpecEventType = x case string: se.Message = x case types.CodeLocation: se.CodeLocation = x case types.TimelineLocation: se.TimelineLocation = x case time.Duration: se.Duration = x case types.NodeType: se.NodeType = x case int: se.Attempt = x } } return se } /* The strategy: use the individual emitters to test the details of each type of output only do didRun to test the various header modes and modalities around -v -vv etc. spot check a single comprehensive timeline. no need to super over-do it! */ var _ = Describe("DefaultReporter", func() { var DENOTER = "•" var RETRY_DENOTER = "↺" if runtime.GOOS == "windows" { DENOTER = "+" RETRY_DENOTER = "R" } var buf *gbytes.Buffer BeforeEach(func() { buf = gbytes.NewBuffer() format.CharactersAroundMismatchToInclude = 100 }) DescribeTable("Rendering SuiteWillBegin", func(conf types.ReporterConfig, report types.Report, expected ...any) { reporter := reporters.NewDefaultReporterUnderTest(conf, buf) reporter.SuiteWillBegin(report) Expect(string(buf.Contents())).Should(MatchLines(expected...)) }, Entry("Default Behavior", C(), types.Report{ SuiteDescription: "My Suite", SuitePath: "/path/to/suite", PreRunStats: types.PreRunStats{SpecsThatWillRun: 15, TotalSpecs: 20}, SuiteConfig: types.SuiteConfig{RandomSeed: 17, ParallelTotal: 1}, }, "Running Suite: My Suite - /path/to/suite", "========================================", "Random Seed: {{bold}}17{{/}}", "", "Will run {{bold}}15{{/}} of {{bold}}20{{/}} specs", "", ), Entry("With Labels", C(), types.Report{ SuiteDescription: "My Suite", SuitePath: "/path/to/suite", SuiteLabels: []string{"dog", "fish"}, PreRunStats: types.PreRunStats{SpecsThatWillRun: 15, TotalSpecs: 20}, SuiteConfig: types.SuiteConfig{RandomSeed: 17, ParallelTotal: 1}, }, "Running Suite: My Suite - /path/to/suite", "{{coral}}[dog, fish]{{/}} ", "========================================", "Random Seed: {{bold}}17{{/}}", "", "Will run {{bold}}15{{/}} of {{bold}}20{{/}} specs", "", ), Entry("With long Labels", C(), types.Report{ SuiteDescription: "My Suite", SuitePath: "/path/to/suite", SuiteLabels: []string{"dog", "fish", "kalamazoo", "kangaroo", "chicken", "asparagus"}, PreRunStats: types.PreRunStats{SpecsThatWillRun: 15, TotalSpecs: 20}, SuiteConfig: types.SuiteConfig{RandomSeed: 17, ParallelTotal: 1}, }, "Running Suite: My Suite - /path/to/suite", "{{coral}}[dog, fish, kalamazoo, kangaroo, chicken, asparagus]{{/}} ", "====================================================", "Random Seed: {{bold}}17{{/}}", "", "Will run {{bold}}15{{/}} of {{bold}}20{{/}} specs", "", ), Entry("When configured to randomize all specs", C(), types.Report{ SuiteDescription: "My Suite", SuitePath: "/path/to/suite", PreRunStats: types.PreRunStats{SpecsThatWillRun: 15, TotalSpecs: 20}, SuiteConfig: types.SuiteConfig{RandomSeed: 17, ParallelTotal: 1, RandomizeAllSpecs: true}, }, "Running Suite: My Suite - /path/to/suite", "========================================", "Random Seed: {{bold}}17{{/}} - will randomize all specs", "", "Will run {{bold}}15{{/}} of {{bold}}20{{/}} specs", "", ), Entry("when configured to run in parallel", C(), types.Report{ SuiteDescription: "My Suite", SuitePath: "/path/to/suite", PreRunStats: types.PreRunStats{SpecsThatWillRun: 15, TotalSpecs: 20}, SuiteConfig: types.SuiteConfig{RandomSeed: 17, ParallelTotal: 3}, }, "Running Suite: My Suite - /path/to/suite", "========================================", "Random Seed: {{bold}}17{{/}}", "", "Will run {{bold}}15{{/}} of {{bold}}20{{/}} specs", "Running in parallel across {{bold}}3{{/}} processes", "", ), Entry("when succinct and in series", C(Succinct), types.Report{ SuiteDescription: "My Suite", SuitePath: "/path/to/suite", PreRunStats: types.PreRunStats{SpecsThatWillRun: 15, TotalSpecs: 20}, SuiteConfig: types.SuiteConfig{RandomSeed: 17, ParallelTotal: 1}, }, "[17] {{bold}}My Suite{{/}} - 15/20 specs ", ), Entry("when succinct and in parallel", C(Succinct), types.Report{ SuiteDescription: "My Suite", SuitePath: "/path/to/suite", PreRunStats: types.PreRunStats{SpecsThatWillRun: 15, TotalSpecs: 20}, SuiteConfig: types.SuiteConfig{RandomSeed: 17, ParallelTotal: 3}, }, "[17] {{bold}}My Suite{{/}} - 15/20 specs - 3 procs ", ), Entry("when succinct and with labels", C(Succinct), types.Report{ SuiteDescription: "My Suite", SuitePath: "/path/to/suite", SuiteLabels: Label("dog, fish"), PreRunStats: types.PreRunStats{SpecsThatWillRun: 15, TotalSpecs: 20}, SuiteConfig: types.SuiteConfig{RandomSeed: 17, ParallelTotal: 3}, }, "[17] {{bold}}My Suite{{/}} {{coral}}[dog, fish]{{/}} - 15/20 specs - 3 procs ", ), ) DescribeTable("WillRun", func(conf types.ReporterConfig, report types.SpecReport, expected ...any) { reporter := reporters.NewDefaultReporterUnderTest(conf, buf) reporter.WillRun(report) Expect(string(buf.Contents())).Should(MatchLines(expected...)) }, Entry("when not verbose, it emits nothing", C(), S(CTS("A"), CLS(cl0))), Entry("pending specs are not emitted", C(Verbose), S(types.SpecStatePending)), Entry("skipped specs are not emitted", C(Verbose), S(types.SpecStateSkipped)), Entry("setup nodes", C(Verbose), S(types.NodeTypeBeforeSuite, cl0), DELIMITER, "{{/}}{{bold}}[BeforeSuite] {{/}}", "{{gray}}"+cl0.String()+"{{/}}", "", ), Entry("ReportAfterSuite nodes", C(Verbose), S("my report", cl0, types.NodeTypeReportAfterSuite), DELIMITER, "{{/}}{{bold}}[ReportAfterSuite] my report{{/}}", "{{gray}}"+cl0.String()+"{{/}}", "", ), Entry("top-level it nodes", C(Verbose), S("My Test", cl0), DELIMITER, "{{/}}{{bold}}My Test{{/}}", "{{gray}}"+cl0.String()+"{{/}}", "", ), Entry("nested it nodes", C(Verbose), S(CTS("Container", "Nested Container"), "My Test", CLS(cl0, cl1), cl2), DELIMITER, "{{/}}Container {{gray}}Nested Container {{/}}{{bold}}My Test{{/}}", "{{gray}}"+cl2.String()+"{{/}}", "", ), Entry("specs with labels", C(Verbose), S(CTS("Container", "Nested Container"), "My Test", CLS(cl0, cl1), cl2, CLabels(Label("dog", "cat"), Label("cat", "fruit")), Label("giraffe", "gorilla", "cat")), DELIMITER, "{{/}}Container {{gray}}Nested Container {{/}}{{bold}}My Test{{/}} {{coral}}[dog, cat, fruit, giraffe, gorilla]{{/}}", "{{gray}}"+cl2.String()+"{{/}}", "", ), ) DescribeTable("WillRun => DidRun", func(report types.SpecReport, cases ...ConfigCase) { for _, c := range cases { for _, configFlag := range c.ConfigFlags { reporter := reporters.NewDefaultReporterUnderTest(C(configFlag), buf) report.RunningInParallel = configFlag.Has(Parallel) reporter.WillRun(report) reporter.DidRun(report) Expect(string(buf.Contents())).Should(MatchLines(c.Expected...), "for config: %s", configFlag) Ω(buf.Clear()).Should(Succeed()) } } }, // Passing tests Entry("a passing test", S(CLS(cl0, cl1), CTS("A", "B"), "C", cl2), Case(Succinct, Normal, Succinct|Parallel, Normal|Parallel, "{{green}}"+DENOTER+"{{/}}"), Case(Succinct|ForceNewlines, Normal|ForceNewlines, Succinct|Parallel|ForceNewlines, Normal|Parallel|ForceNewlines, "{{green}}"+DENOTER+"{{/}}", ""), Case(Verbose, Verbose|ForceNewlines, DELIMITER, "{{/}}A {{gray}}B {{/}}{{bold}}C{{/}}", "{{gray}}cl2.go:80{{/}}", spr("{{green}}%s [1.000 seconds]{{/}}", DENOTER), DELIMITER, ""), Case(Verbose|Parallel, DELIMITER, spr("{{green}}%s [1.000 seconds]{{/}}", DENOTER), "{{/}}A {{gray}}B {{green}}{{bold}}C{{/}}", "{{gray}}cl2.go:80{{/}}", DELIMITER, ""), Case(VeryVerbose, DELIMITER, "A", "{{gray}}cl0.go:12{{/}}", " B", " {{gray}}cl1.go:37{{/}}", " {{/}}{{bold}}C{{/}}", " {{gray}}cl2.go:80{{/}}", spr("{{green}}%s [1.000 seconds]{{/}}", DENOTER), DELIMITER, ""), Case(VeryVerbose|Parallel, DELIMITER, spr("{{green}}%s [1.000 seconds]{{/}}", DENOTER), "A", "{{gray}}cl0.go:12{{/}}", " B", " {{gray}}cl1.go:37{{/}}", " {{green}}{{bold}}C{{/}}", " {{gray}}cl2.go:80{{/}}", DELIMITER, ""), ), Entry("a passing suite-level node", S(types.NodeTypeReportAfterSuite, "C", cl0), Case(Succinct, Normal, Succinct|Parallel, Normal|Parallel), Case(Verbose, VeryVerbose, DELIMITER, "{{/}}{{bold}}[ReportAfterSuite] C{{/}}", "{{gray}}cl0.go:12{{/}}", "{{green}}[ReportAfterSuite] PASSED [1.000 seconds]{{/}}", DELIMITER, ""), Case(Verbose|Parallel, VeryVerbose|Parallel, DELIMITER, "{{green}}[ReportAfterSuite] PASSED [1.000 seconds]{{/}}", "{{green}}{{bold}}[ReportAfterSuite] C{{/}}", "{{gray}}cl0.go:12{{/}}", DELIMITER, ""), ), Entry("a passing test that was flakey", S(types.NodeTypeIt, "A", cl0, 3, FlakeAttempts(4), AF(types.SpecStateFailed, "failed", types.NodeTypeIt, cl1, types.FailureNodeIsLeafNode, FailureNodeLocation(cl0)), SE(types.SpecEventSpecRetry, 1), AF(types.SpecStateFailed, "failed again", types.NodeTypeIt, cl1, types.FailureNodeIsLeafNode, FailureNodeLocation(cl0)), SE(types.SpecEventSpecRetry, 2), ), Case(Succinct, Normal, Succinct|Parallel, Normal|Parallel, DELIMITER, spr("{{green}}%s [FLAKEY TEST - TOOK 3 ATTEMPTS TO PASS] [1.000 seconds]{{/}}", RETRY_DENOTER), "{{green}}{{bold}}A{{/}}", "{{gray}}cl0.go:12{{/}}", DELIMITER, ""), Case(Verbose, VeryVerbose, DELIMITER, "{{/}}{{bold}}A{{/}}", "{{gray}}cl0.go:12{{/}}", spr("{{green}}%s [FLAKEY TEST - TOOK 3 ATTEMPTS TO PASS] [1.000 seconds]{{/}}", RETRY_DENOTER), DELIMITER, ""), Case(Verbose|Parallel, DELIMITER, spr("{{green}}%s [FLAKEY TEST - TOOK 3 ATTEMPTS TO PASS] [1.000 seconds]{{/}}", RETRY_DENOTER), "{{green}}{{bold}}A{{/}}", "{{gray}}cl0.go:12{{/}}", "", " {{gray}}Timeline >>{{/}}", spr(" {{red}}[FAILED]{{/}} in [It] - cl1.go:37 {{gray}}@ %s{{/}}", FORMATTED_TIME), "", spr(" {{bold}}Attempt #1 {{red}}Failed{{/}}{{bold}}. Retrying %s{{/}} {{gray}}@ %s{{/}}", RETRY_DENOTER, FORMATTED_TIME), "", spr(" {{red}}[FAILED]{{/}} in [It] - cl1.go:37 {{gray}}@ %s{{/}}", FORMATTED_TIME), "", spr(" {{bold}}Attempt #2 {{red}}Failed{{/}}{{bold}}. Retrying %s{{/}} {{gray}}@ %s{{/}}", RETRY_DENOTER, FORMATTED_TIME), "", " {{gray}}<< Timeline{{/}}", DELIMITER, ""), Case(VeryVerbose|Parallel, DELIMITER, spr("{{green}}%s [FLAKEY TEST - TOOK 3 ATTEMPTS TO PASS] [1.000 seconds]{{/}}", RETRY_DENOTER), "{{green}}{{bold}}A{{/}}", "{{gray}}cl0.go:12{{/}}", "", " {{gray}}Timeline >>{{/}}", " {{red}}[FAILED] failed{{/}}", spr(" {{red}}In {{bold}}[It]{{/}}{{red}} at: {{bold}}cl1.go:37{{/}} {{gray}}@ %s{{/}}", FORMATTED_TIME), "", spr(" {{bold}}Attempt #1 {{red}}Failed{{/}}{{bold}}. Retrying %s{{/}} {{gray}}@ %s{{/}}", RETRY_DENOTER, FORMATTED_TIME), "", " {{red}}[FAILED] failed again{{/}}", spr(" {{red}}In {{bold}}[It]{{/}}{{red}} at: {{bold}}cl1.go:37{{/}} {{gray}}@ %s{{/}}", FORMATTED_TIME), "", spr(" {{bold}}Attempt #2 {{red}}Failed{{/}}{{bold}}. Retrying %s{{/}} {{gray}}@ %s{{/}}", RETRY_DENOTER, FORMATTED_TIME), "", " {{gray}}<< Timeline{{/}}", DELIMITER, ""), ), Entry("a passing test that repeated a few times", S(types.NodeTypeIt, "A", cl0, 3, MustPassRepeatedly(3), SE(types.SpecEventSpecRepeat, 1), SE(types.SpecEventSpecRepeat, 2), ), Case(Succinct, Normal, Succinct|Parallel, Normal|Parallel, spr("{{green}}%s{{/}}", DENOTER)), Case(Succinct|ForceNewlines, Normal|ForceNewlines, Succinct|Parallel|ForceNewlines, Normal|Parallel|ForceNewlines, spr("{{green}}%s{{/}}", DENOTER), ""), Case(Verbose, VeryVerbose, Verbose|ForceNewlines, VeryVerbose|ForceNewlines, DELIMITER, "{{/}}{{bold}}A{{/}}", "{{gray}}cl0.go:12{{/}}", spr("{{green}}%s [1.000 seconds]{{/}}", DENOTER), DELIMITER, ""), Case(Verbose|Parallel, VeryVerbose|Parallel, DELIMITER, spr("{{green}}%s [1.000 seconds]{{/}}", DENOTER), "{{green}}{{bold}}A{{/}}", "{{gray}}cl0.go:12{{/}}", "", " {{gray}}Timeline >>{{/}}", "", spr(" {{bold}}Attempt #1 {{green}}Passed{{/}}{{bold}}. Repeating ↺{{/}} {{gray}}@ %s{{/}}", FORMATTED_TIME), "", "", spr(" {{bold}}Attempt #2 {{green}}Passed{{/}}{{bold}}. Repeating ↺{{/}} {{gray}}@ %s{{/}}", FORMATTED_TIME), "", " {{gray}}<< Timeline{{/}}", DELIMITER, ""), ), Entry("a passing test with a spec report", S(types.NodeTypeIt, "A", cl0, RE("my entry", cl1), ), Case(Succinct, Normal, Succinct|Parallel, Normal|Parallel, Succinct|ForceNewlines, Normal|ForceNewlines, Succinct|Parallel|ForceNewlines, Normal|Parallel|ForceNewlines, DELIMITER, spr("{{green}}%s [1.000 seconds]{{/}}", DENOTER), "{{green}}{{bold}}A{{/}}", "{{gray}}cl0.go:12{{/}}", "", " {{gray}}Report Entries >>{{/}}", spr(" {{bold}}my entry{{gray}} - cl1.go:37 @ %s{{/}}", FORMATTED_TIME), " {{gray}}<< Report Entries{{/}}", DELIMITER, ""), Case(Verbose, VeryVerbose, Verbose|ForceNewlines, VeryVerbose|ForceNewlines, DELIMITER, "{{/}}{{bold}}A{{/}}", "{{gray}}cl0.go:12{{/}}", spr("{{green}}%s [1.000 seconds]{{/}}", DENOTER), DELIMITER, ""), Case(Verbose|Parallel, VeryVerbose|Parallel, DELIMITER, spr("{{green}}%s [1.000 seconds]{{/}}", DENOTER), "{{green}}{{bold}}A{{/}}", "{{gray}}cl0.go:12{{/}}", "", " {{gray}}Timeline >>{{/}}", spr(" {{bold}}my entry{{gray}} - cl1.go:37 @ %s{{/}}", FORMATTED_TIME), " {{gray}}<< Timeline{{/}}", DELIMITER, ""), ), Entry("a passing test with a captured stdout/stderr", S(types.NodeTypeIt, "A", cl0, STD("hello there\nthis is my output")), Case(Succinct, Normal, spr("{{green}}%s{{/}}", DENOTER)), Case(Verbose, VeryVerbose, DELIMITER, "{{/}}{{bold}}A{{/}}", "{{gray}}cl0.go:12{{/}}", spr("{{green}}%s [1.000 seconds]{{/}}", DENOTER), DELIMITER, ""), Case(Succinct|Parallel, Normal|Parallel, Verbose|Parallel, VeryVerbose|Parallel, DELIMITER, spr("{{green}}%s [1.000 seconds]{{/}}", DENOTER), "{{green}}{{bold}}A{{/}}", "{{gray}}cl0.go:12{{/}}", "", " {{gray}}Captured StdOut/StdErr Output >>{{/}}", " hello there", " this is my output", " {{gray}}<< Captured StdOut/StdErr Output{{/}}", DELIMITER, ""), Case(Parallel|GithubOutput, DELIMITER, spr("{{green}}%s [1.000 seconds]{{/}}", DENOTER), "{{green}}{{bold}}A{{/}}", "{{gray}}cl0.go:12{{/}}", "", " ::group::Captured StdOut/StdErr Output", " hello there", " this is my output", " ::endgroup::", DELIMITER, ""), ), Entry("a passing test with a full timeline that is only visible in verbose/very-verbose mode", S(types.NodeTypeIt, "A", cl0, GW("some GinkgoWriter\noutput is interspersed\nhere and there\n"), SE(types.SpecEventNodeStart, types.NodeTypeIt, "A", cl0), PR("my progress report", LeafNodeText("A"), TL("some GinkgoWriter\n")), SE(types.SpecEventByStart, "My Step", cl1, TL("some GinkgoWriter\n")), RE("my entry", cl1, types.ReportEntryVisibilityFailureOrVerbose, TL("some GinkgoWriter\noutput is interspersed\n")), RE("my hidden entry", cl1, types.ReportEntryVisibilityNever, TL("some GinkgoWriter\noutput is interspersed\n")), SE(types.SpecEventByEnd, "My Step", cl1, time.Millisecond*200, TL("some GinkgoWriter\noutput is interspersed\n")), SE(types.SpecEventNodeEnd, types.NodeTypeIt, "A", cl0, time.Millisecond*300, TL("some GinkgoWriter\noutput is interspersed\nhere and there\n")), ), Case(Succinct, Normal, Succinct|Parallel, Normal|Parallel, Succinct|ShowNodeEvents, Normal|ShowNodeEvents, spr("{{green}}%s{{/}}", DENOTER)), Case(Succinct|ForceNewlines, Normal|ForceNewlines, Succinct|Parallel|ForceNewlines, Normal|Parallel|ForceNewlines, Succinct|ShowNodeEvents|ForceNewlines, Normal|ShowNodeEvents|ForceNewlines, spr("{{green}}%s{{/}}", DENOTER), ""), Case(Verbose, VeryVerbose, //nothing to see here since things are emitted while streaming, which we don't simulate DELIMITER, "{{/}}{{bold}}A{{/}}", "{{gray}}cl0.go:12{{/}}", spr("{{green}}%s [1.000 seconds]{{/}}", DENOTER), DELIMITER, ""), Case(Verbose|Parallel|ShowNodeEvents, DELIMITER, spr("{{green}}%s [1.000 seconds]{{/}}", DENOTER), "{{green}}{{bold}}A{{/}}", "{{gray}}cl0.go:12{{/}}", "", " {{gray}}Timeline >>{{/}}", spr(" > Enter {{bold}}[It]{{/}} A {{gray}}@ %s{{/}}", FORMATTED_TIME), " some GinkgoWriter", " my progress report", " {{bold}}{{orange}}A{{/}} (Spec Runtime: 5s)", " {{gray}}cl0.go:12{{/}}", spr(" {{bold}}STEP:{{/}} My Step {{gray}}@ %s{{/}}", FORMATTED_TIME), " output is interspersed", spr(" {{bold}}my entry{{gray}} - cl1.go:37 @ %s{{/}}", FORMATTED_TIME), spr(" {{bold}}END STEP:{{/}} My Step {{gray}}@ %s (200ms){{/}}", FORMATTED_TIME), " here and there", spr(" < Exit {{bold}}[It]{{/}} A {{gray}}@ %s (300ms){{/}}", FORMATTED_TIME), " {{gray}}<< Timeline{{/}}", DELIMITER, ""), Case(Verbose|Parallel, DELIMITER, spr("{{green}}%s [1.000 seconds]{{/}}", DENOTER), "{{green}}{{bold}}A{{/}}", "{{gray}}cl0.go:12{{/}}", "", " {{gray}}Timeline >>{{/}}", " some GinkgoWriter", " my progress report", " {{bold}}{{orange}}A{{/}} (Spec Runtime: 5s)", " {{gray}}cl0.go:12{{/}}", spr(" {{bold}}STEP:{{/}} My Step {{gray}}@ %s{{/}}", FORMATTED_TIME), " output is interspersed", spr(" {{bold}}my entry{{gray}} - cl1.go:37 @ %s{{/}}", FORMATTED_TIME), " here and there", " {{gray}}<< Timeline{{/}}", DELIMITER, ""), Case(VeryVerbose|Parallel, DELIMITER, spr("{{green}}%s [1.000 seconds]{{/}}", DENOTER), "{{green}}{{bold}}A{{/}}", "{{gray}}cl0.go:12{{/}}", "", " {{gray}}Timeline >>{{/}}", spr(" > Enter {{bold}}[It]{{/}} A {{gray}}- cl0.go:12 @ %s{{/}}", FORMATTED_TIME), " some GinkgoWriter", " my progress report", " {{bold}}{{orange}}A{{/}} (Spec Runtime: 5s)", " {{gray}}cl0.go:12{{/}}", spr(" {{bold}}STEP:{{/}} My Step {{gray}}- cl1.go:37 @ %s{{/}}", FORMATTED_TIME), " output is interspersed", spr(" {{bold}}my entry{{gray}} - cl1.go:37 @ %s{{/}}", FORMATTED_TIME), spr(" {{bold}}END STEP:{{/}} My Step {{gray}}- cl1.go:37 @ %s (200ms){{/}}", FORMATTED_TIME), " here and there", spr(" < Exit {{bold}}[It]{{/}} A {{gray}}- cl0.go:12 @ %s (300ms){{/}}", FORMATTED_TIME), " {{gray}}<< Timeline{{/}}", DELIMITER, ""), ), // Skipped tests Entry("a skipped test", S(types.NodeTypeIt, "A", types.SpecStateSkipped, cl0), Case(Succinct, Normal, Succinct|Parallel, Normal|Parallel, Verbose, Verbose|Parallel, "{{cyan}}S{{/}}"), Case(Succinct|ForceNewlines, Normal|ForceNewlines, Succinct|Parallel|ForceNewlines, Normal|Parallel|ForceNewlines, Verbose|ForceNewlines, Verbose|Parallel|ForceNewlines, "{{cyan}}S{{/}}", ""), Case(VeryVerbose, VeryVerbose|ForceNewlines, "{{cyan}}S [SKIPPED]{{/}}", "{{cyan}}{{bold}}A{{/}}", "{{gray}}cl0.go:12{{/}}", DELIMITER, ""), Case(VeryVerbose|Parallel, DELIMITER, "{{cyan}}S [SKIPPED]{{/}}", "{{cyan}}{{bold}}A{{/}}", "{{gray}}cl0.go:12{{/}}", DELIMITER, ""), Case(Succinct|SilenceSkips, Normal|SilenceSkips, Succinct|Parallel|SilenceSkips, Normal|Parallel|SilenceSkips, Verbose|SilenceSkips, Verbose|Parallel|SilenceSkips, VeryVerbose|SilenceSkips, VeryVerbose|Parallel|SilenceSkips, ""), ), Entry("a user-skipped test", S(types.NodeTypeIt, "A", types.SpecStateSkipped, cl0, F("let's skip it", cl1, types.NodeTypeIt, types.FailureNodeIsLeafNode, FailureNodeLocation(cl0)), ), Case(Succinct, Normal, Succinct|Parallel, Normal|Parallel, "{{cyan}}S{{/}}"), Case(Verbose, "{{cyan}}S [SKIPPED] [1.000 seconds]{{/}}", "{{cyan}}{{bold}}[It] A{{/}}", "{{gray}}cl0.go:12{{/}}", "", " {{cyan}}[SKIPPED] let's skip it{{/}}", spr(" {{cyan}}In {{bold}}[It]{{/}}{{cyan}} at: {{bold}}cl1.go:37{{/}} {{gray}}@ %s{{/}}", FORMATTED_TIME), DELIMITER, ""), Case(VeryVerbose, "{{cyan}}S [SKIPPED] [1.000 seconds]{{/}}", "{{cyan}}{{bold}}[It] A{{/}}", "{{gray}}cl0.go:12{{/}}", DELIMITER, "", ), Case(Verbose|Parallel, DELIMITER, "{{cyan}}S [SKIPPED] [1.000 seconds]{{/}}", "{{cyan}}{{bold}}[It] A{{/}}", "{{gray}}cl0.go:12{{/}}", "", " {{cyan}}[SKIPPED] let's skip it{{/}}", spr(" {{cyan}}In {{bold}}[It]{{/}}{{cyan}} at: {{bold}}cl1.go:37{{/}} {{gray}}@ %s{{/}}", FORMATTED_TIME), DELIMITER, ""), Case(VeryVerbose|Parallel, DELIMITER, "{{cyan}}S [SKIPPED] [1.000 seconds]{{/}}", "{{cyan}}{{bold}}[It] A{{/}}", "{{gray}}cl0.go:12{{/}}", "", " {{gray}}Timeline >>{{/}}", " {{cyan}}[SKIPPED] let's skip it{{/}}", spr(" {{cyan}}In {{bold}}[It]{{/}}{{cyan}} at: {{bold}}cl1.go:37{{/}} {{gray}}@ %s{{/}}", FORMATTED_TIME), " {{gray}}<< Timeline{{/}}", DELIMITER, ""), Case(Succinct|SilenceSkips, Normal|SilenceSkips, Succinct|Parallel|SilenceSkips, Normal|Parallel|SilenceSkips, Verbose|SilenceSkips, Verbose|Parallel|SilenceSkips, VeryVerbose|SilenceSkips, VeryVerbose|Parallel|SilenceSkips, ""), ), Entry("a user-skipped test with timeline content", S(types.NodeTypeIt, "A", types.SpecStateSkipped, cl0, GW("should we skip it?"), F("let's skip it", TL("should we skip it?"), cl1, types.NodeTypeIt, types.FailureNodeIsLeafNode, FailureNodeLocation(cl0)), ), Case(Succinct, Normal, Succinct|Parallel, Normal|Parallel, "{{cyan}}S{{/}}"), Case(Verbose|Parallel, DELIMITER, "{{cyan}}S [SKIPPED] [1.000 seconds]{{/}}", "{{cyan}}{{bold}}[It] A{{/}}", "{{gray}}cl0.go:12{{/}}", "", " {{gray}}Timeline >>{{/}}", " should we skip it?", spr(" {{cyan}}[SKIPPED]{{/}} in [It] - cl1.go:37 {{gray}}@ %s{{/}}", FORMATTED_TIME), " {{gray}}<< Timeline{{/}}", "", " {{cyan}}[SKIPPED] let's skip it{{/}}", spr(" {{cyan}}In {{bold}}[It]{{/}}{{cyan}} at: {{bold}}cl1.go:37{{/}} {{gray}}@ %s{{/}}", FORMATTED_TIME), DELIMITER, ""), Case(VeryVerbose|Parallel, DELIMITER, "{{cyan}}S [SKIPPED] [1.000 seconds]{{/}}", "{{cyan}}{{bold}}[It] A{{/}}", "{{gray}}cl0.go:12{{/}}", "", " {{gray}}Timeline >>{{/}}", " should we skip it?", " {{cyan}}[SKIPPED] let's skip it{{/}}", spr(" {{cyan}}In {{bold}}[It]{{/}}{{cyan}} at: {{bold}}cl1.go:37{{/}} {{gray}}@ %s{{/}}", FORMATTED_TIME), " {{gray}}<< Timeline{{/}}", DELIMITER, ""), Case(Verbose, "{{cyan}}S [SKIPPED] [1.000 seconds]{{/}}", "{{cyan}}{{bold}}[It] A{{/}}", "{{gray}}cl0.go:12{{/}}", "", " {{cyan}}[SKIPPED] let's skip it{{/}}", spr(" {{cyan}}In {{bold}}[It]{{/}}{{cyan}} at: {{bold}}cl1.go:37{{/}} {{gray}}@ %s{{/}}", FORMATTED_TIME), DELIMITER, ""), Case(VeryVerbose, "{{cyan}}S [SKIPPED] [1.000 seconds]{{/}}", "{{cyan}}{{bold}}[It] A{{/}}", "{{gray}}cl0.go:12{{/}}", DELIMITER, "", ), Case(Succinct|SilenceSkips, Normal|SilenceSkips, Succinct|Parallel|SilenceSkips, Normal|Parallel|SilenceSkips, Verbose|SilenceSkips, Verbose|Parallel|SilenceSkips, VeryVerbose|SilenceSkips, VeryVerbose|Parallel|SilenceSkips, ""), ), Entry("a pending test", S(types.NodeTypeIt, "C", types.SpecStatePending, cl2, CTS("A", "B"), CLS(cl0, cl1)), Case(Succinct, Succinct|Parallel, "{{yellow}}P{{/}}"), Case(Succinct|ForceNewlines, Succinct|Parallel|ForceNewlines, "{{yellow}}P{{/}}", ""), Case(Normal, Normal|Parallel, Verbose|Parallel, Normal|ForceNewlines, Normal|Parallel|ForceNewlines, Verbose|Parallel|ForceNewlines, DELIMITER, "{{yellow}}P [PENDING]{{/}}", "{{/}}A {{gray}}B {{yellow}}{{bold}}C{{/}}", "{{gray}}cl2.go:80{{/}}", DELIMITER, "", ), Case(VeryVerbose|Parallel, DELIMITER, "{{yellow}}P [PENDING]{{/}}", "A", "{{gray}}cl0.go:12{{/}}", " B", " {{gray}}cl1.go:37{{/}}", " {{yellow}}{{bold}}C{{/}}", " {{gray}}cl2.go:80{{/}}", DELIMITER, ""), Case(Verbose, "{{yellow}}P [PENDING]{{/}}", "{{/}}A {{gray}}B {{yellow}}{{bold}}C{{/}}", "{{gray}}cl2.go:80{{/}}", DELIMITER, "", ), Case(VeryVerbose, "{{yellow}}P [PENDING]{{/}}", "A", "{{gray}}cl0.go:12{{/}}", " B", " {{gray}}cl1.go:37{{/}}", " {{yellow}}{{bold}}C{{/}}", " {{gray}}cl2.go:80{{/}}", DELIMITER, ""), ), Entry("a failed test with a failure in the It", S(types.NodeTypeIt, CTS("A", "B"), CLS(cl0, cl1), "C", cl2, types.SpecStateFailed, F("failure\nmessage", cl3, types.FailureNodeIsLeafNode, FailureNodeLocation(cl2), types.NodeTypeIt), ), Case(Succinct, Succinct|Parallel, Normal, Normal|Parallel, Verbose|Parallel, DELIMITER, //note: no timeline because there is only a single failure in here spr("{{red}}%s [FAILED] [1.000 seconds]{{/}}", DENOTER), "{{/}}A {{gray}}B {{red}}{{bold}}[It] C{{/}}", "{{gray}}cl2.go:80{{/}}", "", " {{red}}[FAILED] failure", " message{{/}}", spr(" {{red}}In {{bold}}[It]{{/}}{{red}} at: {{bold}}cl3.go:103{{/}} {{gray}}@ %s{{/}}", FORMATTED_TIME), DELIMITER, ""), Case(VeryVerbose|Parallel, DELIMITER, spr("{{red}}%s [FAILED] [1.000 seconds]{{/}}", DENOTER), "A", "{{gray}}cl0.go:12{{/}}", " B", " {{gray}}cl1.go:37{{/}}", " {{red}}{{bold}}[It] C{{/}}", " {{gray}}cl2.go:80{{/}}", "", " {{gray}}Timeline >>{{/}}", " {{red}}[FAILED] failure", " message{{/}}", spr(" {{red}}In {{bold}}[It]{{/}}{{red}} at: {{bold}}cl3.go:103{{/}} {{gray}}@ %s{{/}}", FORMATTED_TIME), " {{gray}}<< Timeline{{/}}", DELIMITER, ""), Case(Verbose, DELIMITER, "{{/}}A {{gray}}B {{/}}{{bold}}[It] C{{/}}", "{{gray}}cl2.go:80{{/}}", spr("{{red}}%s [FAILED] [1.000 seconds]{{/}}", DENOTER), "{{/}}A {{gray}}B {{red}}{{bold}}[It] C{{/}}", "{{gray}}cl2.go:80{{/}}", "", " {{red}}[FAILED] failure", " message{{/}}", spr(" {{red}}In {{bold}}[It]{{/}}{{red}} at: {{bold}}cl3.go:103{{/}} {{gray}}@ %s{{/}}", FORMATTED_TIME), DELIMITER, ""), Case(VeryVerbose, //note: no failure summary as it will appear in the timeline (which isn't shown here since we don't replay the timeline in the spec) DELIMITER, "A", "{{gray}}cl0.go:12{{/}}", " B", " {{gray}}cl1.go:37{{/}}", " {{/}}{{bold}}[It] C{{/}}", " {{gray}}cl2.go:80{{/}}", spr("{{red}}%s [FAILED] [1.000 seconds]{{/}}", DENOTER), "A", "{{gray}}cl0.go:12{{/}}", " B", " {{gray}}cl1.go:37{{/}}", " {{red}}{{bold}}[It] C{{/}}", " {{gray}}cl2.go:80{{/}}", DELIMITER, ""), ), Entry("a failed test with a failure in an intermediate BeforeEach", S(types.NodeTypeIt, CTS("A", "B"), CLS(cl0, cl1), "C", cl2, types.SpecStateFailed, F("failure\nmessage", cl3, types.FailureNodeInContainer, FailureNodeLocation(cl4), types.NodeTypeBeforeEach, 1), ), Case(Succinct, Succinct|Parallel, Normal, Normal|Parallel, Verbose|Parallel, DELIMITER, //note: no timeline because there is only a single failure in here spr("{{red}}%s [FAILED] [1.000 seconds]{{/}}", DENOTER), "{{/}}A {{red}}{{bold}}B [BeforeEach] {{/}}C{{/}}", " {{red}}[BeforeEach]{{/}} {{gray}}cl4.go:144{{/}}", " {{gray}}[It] cl2.go:80{{/}}", "", " {{red}}[FAILED] failure", " message{{/}}", spr(" {{red}}In {{bold}}[BeforeEach]{{/}}{{red}} at: {{bold}}cl3.go:103{{/}} {{gray}}@ %s{{/}}", FORMATTED_TIME), DELIMITER, ""), Case(VeryVerbose|Parallel, DELIMITER, spr("{{red}}%s [FAILED] [1.000 seconds]{{/}}", DENOTER), "A", "{{gray}}cl0.go:12{{/}}", " {{red}}{{bold}}B [BeforeEach]{{/}}", " {{gray}}cl4.go:144{{/}}", " C", " {{gray}}cl2.go:80{{/}}", "", " {{gray}}Timeline >>{{/}}", " {{red}}[FAILED] failure", " message{{/}}", spr(" {{red}}In {{bold}}[BeforeEach]{{/}}{{red}} at: {{bold}}cl3.go:103{{/}} {{gray}}@ %s{{/}}", FORMATTED_TIME), " {{gray}}<< Timeline{{/}}", DELIMITER, ""), Case(Verbose, DELIMITER, "{{/}}A {{/}}{{bold}}B [BeforeEach] {{/}}C{{/}}", " {{/}}[BeforeEach]{{/}} {{gray}}cl4.go:144{{/}}", " {{gray}}[It] cl2.go:80{{/}}", spr("{{red}}%s [FAILED] [1.000 seconds]{{/}}", DENOTER), "{{/}}A {{red}}{{bold}}B [BeforeEach] {{/}}C{{/}}", " {{red}}[BeforeEach]{{/}} {{gray}}cl4.go:144{{/}}", " {{gray}}[It] cl2.go:80{{/}}", "", " {{red}}[FAILED] failure", " message{{/}}", spr(" {{red}}In {{bold}}[BeforeEach]{{/}}{{red}} at: {{bold}}cl3.go:103{{/}} {{gray}}@ %s{{/}}", FORMATTED_TIME), DELIMITER, ""), Case(VeryVerbose, //note: no failure summary as it will appear in the timeline (which isn't shown here since we don't replay the timeline in the spec) DELIMITER, "A", "{{gray}}cl0.go:12{{/}}", " {{/}}{{bold}}B [BeforeEach]{{/}}", " {{gray}}cl4.go:144{{/}}", " C", " {{gray}}cl2.go:80{{/}}", spr("{{red}}%s [FAILED] [1.000 seconds]{{/}}", DENOTER), "A", "{{gray}}cl0.go:12{{/}}", " {{red}}{{bold}}B [BeforeEach]{{/}}", " {{gray}}cl4.go:144{{/}}", " C", " {{gray}}cl2.go:80{{/}}", DELIMITER, ""), ), Entry("a failed test with a failure at the top-level", S(types.NodeTypeIt, CTS("A", "B"), CLS(cl0, cl1), "C", cl2, types.SpecStateFailed, F("failure\nmessage", cl3, types.FailureNodeAtTopLevel, FailureNodeLocation(cl4), types.NodeTypeAfterEach), ), Case(Succinct, Succinct|Parallel, Normal, Normal|Parallel, Verbose|Parallel, DELIMITER, //note: no timeline because there is only a single failure in here spr("{{red}}%s [FAILED] [1.000 seconds]{{/}}", DENOTER), "{{red}}{{bold}}TOP-LEVEL [AfterEach] {{gray}}A {{/}}B {{gray}}C{{/}}", " {{red}}[AfterEach]{{/}} {{gray}}cl4.go:144{{/}}", " {{gray}}[It] cl2.go:80{{/}}", "", " {{red}}[FAILED] failure", " message{{/}}", spr(" {{red}}In {{bold}}[AfterEach]{{/}}{{red}} at: {{bold}}cl3.go:103{{/}} {{gray}}@ %s{{/}}", FORMATTED_TIME), DELIMITER, ""), Case(VeryVerbose|Parallel, DELIMITER, spr("{{red}}%s [FAILED] [1.000 seconds]{{/}}", DENOTER), "{{red}}{{bold}}TOP-LEVEL [AfterEach]{{/}}", "{{gray}}cl4.go:144{{/}}", " A", " {{gray}}cl0.go:12{{/}}", " B", " {{gray}}cl1.go:37{{/}}", " C", " {{gray}}cl2.go:80{{/}}", "", " {{gray}}Timeline >>{{/}}", " {{red}}[FAILED] failure", " message{{/}}", spr(" {{red}}In {{bold}}[AfterEach]{{/}}{{red}} at: {{bold}}cl3.go:103{{/}} {{gray}}@ %s{{/}}", FORMATTED_TIME), " {{gray}}<< Timeline{{/}}", DELIMITER, ""), Case(Verbose, DELIMITER, "{{/}}{{bold}}TOP-LEVEL [AfterEach] {{gray}}A {{/}}B {{gray}}C{{/}}", " {{/}}[AfterEach]{{/}} {{gray}}cl4.go:144{{/}}", " {{gray}}[It] cl2.go:80{{/}}", spr("{{red}}%s [FAILED] [1.000 seconds]{{/}}", DENOTER), "{{red}}{{bold}}TOP-LEVEL [AfterEach] {{gray}}A {{/}}B {{gray}}C{{/}}", " {{red}}[AfterEach]{{/}} {{gray}}cl4.go:144{{/}}", " {{gray}}[It] cl2.go:80{{/}}", "", " {{red}}[FAILED] failure", " message{{/}}", spr(" {{red}}In {{bold}}[AfterEach]{{/}}{{red}} at: {{bold}}cl3.go:103{{/}} {{gray}}@ %s{{/}}", FORMATTED_TIME), DELIMITER, ""), Case(VeryVerbose, //note: no failure summary as it will appear in the timeline (which isn't shown here since we don't replay the timeline in the spec) DELIMITER, "{{/}}{{bold}}TOP-LEVEL [AfterEach]{{/}}", "{{gray}}cl4.go:144{{/}}", " A", " {{gray}}cl0.go:12{{/}}", " B", " {{gray}}cl1.go:37{{/}}", " C", " {{gray}}cl2.go:80{{/}}", spr("{{red}}%s [FAILED] [1.000 seconds]{{/}}", DENOTER), "{{red}}{{bold}}TOP-LEVEL [AfterEach]{{/}}", "{{gray}}cl4.go:144{{/}}", " A", " {{gray}}cl0.go:12{{/}}", " B", " {{gray}}cl1.go:37{{/}}", " C", " {{gray}}cl2.go:80{{/}}", DELIMITER, ""), ), Entry("a failed test with a failure in a suite-node", S(types.NodeTypeBeforeSuite, cl0, types.SpecStateAborted, F("failure\nmessage", cl1, types.FailureNodeAtTopLevel, FailureNodeLocation(cl0), types.NodeTypeBeforeSuite), ), Case(Succinct, Succinct|Parallel, Normal, Normal|Parallel, Verbose|Parallel, DELIMITER, //note: no timeline because there is only a single failure in here "{{coral}}[BeforeSuite] [ABORTED] [1.000 seconds]{{/}}", "{{coral}}{{bold}}TOP-LEVEL [BeforeSuite] {{gray}}[BeforeSuite] {{/}}", "{{gray}}cl0.go:12{{/}}", "", " {{coral}}[ABORTED] failure", " message{{/}}", spr(" {{coral}}In {{bold}}[BeforeSuite]{{/}}{{coral}} at: {{bold}}cl1.go:37{{/}} {{gray}}@ %s{{/}}", FORMATTED_TIME), DELIMITER, ""), Case(VeryVerbose|Parallel, DELIMITER, "{{coral}}[BeforeSuite] [ABORTED] [1.000 seconds]{{/}}", "{{coral}}{{bold}}TOP-LEVEL [BeforeSuite]{{/}}", "{{gray}}cl0.go:12{{/}}", " [BeforeSuite] ", " {{gray}}cl0.go:12{{/}}", "", " {{gray}}Timeline >>{{/}}", " {{coral}}[ABORTED] failure", " message{{/}}", spr(" {{coral}}In {{bold}}[BeforeSuite]{{/}}{{coral}} at: {{bold}}cl1.go:37{{/}} {{gray}}@ %s{{/}}", FORMATTED_TIME), " {{gray}}<< Timeline{{/}}", DELIMITER, ""), Case(Verbose, DELIMITER, "{{/}}{{bold}}TOP-LEVEL [BeforeSuite] {{gray}}[BeforeSuite] {{/}}", "{{gray}}cl0.go:12{{/}}", "{{coral}}[BeforeSuite] [ABORTED] [1.000 seconds]{{/}}", "{{coral}}{{bold}}TOP-LEVEL [BeforeSuite] {{gray}}[BeforeSuite] {{/}}", "{{gray}}cl0.go:12{{/}}", "", " {{coral}}[ABORTED] failure", " message{{/}}", spr(" {{coral}}In {{bold}}[BeforeSuite]{{/}}{{coral}} at: {{bold}}cl1.go:37{{/}} {{gray}}@ %s{{/}}", FORMATTED_TIME), DELIMITER, ""), Case(VeryVerbose, //note: no failure summary as it will appear in the timeline (which isn't shown here since we don't replay the timeline in the spec) DELIMITER, "{{/}}{{bold}}TOP-LEVEL [BeforeSuite]{{/}}", "{{gray}}cl0.go:12{{/}}", " [BeforeSuite] ", " {{gray}}cl0.go:12{{/}}", "{{coral}}[BeforeSuite] [ABORTED] [1.000 seconds]{{/}}", "{{coral}}{{bold}}TOP-LEVEL [BeforeSuite]{{/}}", "{{gray}}cl0.go:12{{/}}", " [BeforeSuite] ", " {{gray}}cl0.go:12{{/}}", DELIMITER, ""), ), Entry("a failed test that failed during a repetition", S(types.NodeTypeIt, CTS("A", "B"), CLS(cl0, cl1), 2, "C", cl2, types.SpecStateFailed, MustPassRepeatedly(3), F("failure\nmessage", cl3, types.FailureNodeIsLeafNode, FailureNodeLocation(cl2), types.NodeTypeIt), ), Case(Succinct, Succinct|Parallel, Normal, Normal|Parallel, Verbose|Parallel, DELIMITER, //note: no timeline because there is only a single failure in here spr("{{red}}%s [FAILED] DURING REPETITION #2 [1.000 seconds]{{/}}", DENOTER), "{{/}}A {{gray}}B {{red}}{{bold}}[It] C{{/}}", "{{gray}}cl2.go:80{{/}}", "", " {{red}}[FAILED] failure", " message{{/}}", spr(" {{red}}In {{bold}}[It]{{/}}{{red}} at: {{bold}}cl3.go:103{{/}} {{gray}}@ %s{{/}}", FORMATTED_TIME), DELIMITER, ""), Case(VeryVerbose|Parallel, DELIMITER, spr("{{red}}%s [FAILED] DURING REPETITION #2 [1.000 seconds]{{/}}", DENOTER), "A", "{{gray}}cl0.go:12{{/}}", " B", " {{gray}}cl1.go:37{{/}}", " {{red}}{{bold}}[It] C{{/}}", " {{gray}}cl2.go:80{{/}}", "", " {{gray}}Timeline >>{{/}}", " {{red}}[FAILED] failure", " message{{/}}", spr(" {{red}}In {{bold}}[It]{{/}}{{red}} at: {{bold}}cl3.go:103{{/}} {{gray}}@ %s{{/}}", FORMATTED_TIME), " {{gray}}<< Timeline{{/}}", DELIMITER, ""), Case(Verbose, DELIMITER, "{{/}}A {{gray}}B {{/}}{{bold}}[It] C{{/}}", "{{gray}}cl2.go:80{{/}}", spr("{{red}}%s [FAILED] DURING REPETITION #2 [1.000 seconds]{{/}}", DENOTER), "{{/}}A {{gray}}B {{red}}{{bold}}[It] C{{/}}", "{{gray}}cl2.go:80{{/}}", "", " {{red}}[FAILED] failure", " message{{/}}", spr(" {{red}}In {{bold}}[It]{{/}}{{red}} at: {{bold}}cl3.go:103{{/}} {{gray}}@ %s{{/}}", FORMATTED_TIME), DELIMITER, ""), ), Entry("a failed test with nothing but a failure + the associated additional-failure in the timeline", S(types.NodeTypeIt, CTS("A", "B"), CLS(cl0, cl1), "C", cl2, types.SpecStateTimedout, F("failure\nmessage", cl3, types.FailureNodeIsLeafNode, FailureNodeLocation(cl2), types.NodeTypeIt, TL(0), AF(types.SpecStatePanicked, cl4, types.FailureNodeIsLeafNode, FailureNodeLocation(cl2), types.NodeTypeIt, TL(0))), ), Case(Succinct, Succinct|Parallel, Normal, Normal|Parallel, Verbose|Parallel, DELIMITER, //note: no timeline because there is only a single failure + its additional-failure in here spr("{{orange}}%s [TIMEDOUT] [1.000 seconds]{{/}}", DENOTER), "{{/}}A {{gray}}B {{orange}}{{bold}}[It] C{{/}}", "{{gray}}cl2.go:80{{/}}", "", " {{orange}}[TIMEDOUT] failure", " message{{/}}", spr(" {{orange}}In {{bold}}[It]{{/}}{{orange}} at: {{bold}}cl3.go:103{{/}} {{gray}}@ %s{{/}}", FORMATTED_TIME), "", " {{magenta}}[PANICKED] {{/}}", spr(" {{magenta}}In {{bold}}[It]{{/}}{{magenta}} at: {{bold}}cl4.go:144{{/}} {{gray}}@ %s{{/}}", FORMATTED_TIME), DELIMITER, ""), Case(VeryVerbose|Parallel, DELIMITER, spr("{{orange}}%s [TIMEDOUT] [1.000 seconds]{{/}}", DENOTER), "A", "{{gray}}cl0.go:12{{/}}", " B", " {{gray}}cl1.go:37{{/}}", " {{orange}}{{bold}}[It] C{{/}}", " {{gray}}cl2.go:80{{/}}", "", " {{gray}}Timeline >>{{/}}", " {{orange}}[TIMEDOUT] failure", " message{{/}}", spr(" {{orange}}In {{bold}}[It]{{/}}{{orange}} at: {{bold}}cl3.go:103{{/}} {{gray}}@ %s{{/}}", FORMATTED_TIME), " {{magenta}}[PANICKED] {{/}}", spr(" {{magenta}}In {{bold}}[It]{{/}}{{magenta}} at: {{bold}}cl4.go:144{{/}} {{gray}}@ %s{{/}}", FORMATTED_TIME), " {{gray}}<< Timeline{{/}}", DELIMITER, ""), Case(Verbose, DELIMITER, "{{/}}A {{gray}}B {{/}}{{bold}}[It] C{{/}}", "{{gray}}cl2.go:80{{/}}", spr("{{orange}}%s [TIMEDOUT] [1.000 seconds]{{/}}", DENOTER), "{{/}}A {{gray}}B {{orange}}{{bold}}[It] C{{/}}", "{{gray}}cl2.go:80{{/}}", "", " {{orange}}[TIMEDOUT] failure", " message{{/}}", spr(" {{orange}}In {{bold}}[It]{{/}}{{orange}} at: {{bold}}cl3.go:103{{/}} {{gray}}@ %s{{/}}", FORMATTED_TIME), "", " {{magenta}}[PANICKED] {{/}}", spr(" {{magenta}}In {{bold}}[It]{{/}}{{magenta}} at: {{bold}}cl4.go:144{{/}} {{gray}}@ %s{{/}}", FORMATTED_TIME), DELIMITER, ""), Case(VeryVerbose, //note: no failure summary as it will appear in the timeline (which isn't shown here since we don't replay the timeline in the spec) DELIMITER, "A", "{{gray}}cl0.go:12{{/}}", " B", " {{gray}}cl1.go:37{{/}}", " {{/}}{{bold}}[It] C{{/}}", " {{gray}}cl2.go:80{{/}}", spr("{{orange}}%s [TIMEDOUT] [1.000 seconds]{{/}}", DENOTER), "A", "{{gray}}cl0.go:12{{/}}", " B", " {{gray}}cl1.go:37{{/}}", " {{orange}}{{bold}}[It] C{{/}}", " {{gray}}cl2.go:80{{/}}", DELIMITER, ""), ), Entry("a failed test with GinkgoWriter output", S(types.NodeTypeIt, CTS("A", "B"), CLS(cl0, cl1), "C", cl2, types.SpecStateTimedout, GW("some ginkgowriter\noutput\n"), F("failure\nmessage", cl3, types.FailureNodeIsLeafNode, FailureNodeLocation(cl2), types.NodeTypeIt, TL("some ginkgowriter\n"), AF(types.SpecStatePanicked, cl4, types.FailureNodeIsLeafNode, FailureNodeLocation(cl2), types.NodeTypeIt, TL("some ginkgowriter\noutput\n"))), ), Case(Succinct, Succinct|Parallel, Normal, Normal|Parallel, Verbose|Parallel, DELIMITER, spr("{{orange}}%s [TIMEDOUT] [1.000 seconds]{{/}}", DENOTER), "{{/}}A {{gray}}B {{orange}}{{bold}}[It] C{{/}}", "{{gray}}cl2.go:80{{/}}", "", " {{gray}}Timeline >>{{/}}", " some ginkgowriter", spr(" {{orange}}[TIMEDOUT]{{/}} in [It] - cl3.go:103 {{gray}}@ %s{{/}}", FORMATTED_TIME), " output", spr(" {{magenta}}[PANICKED]{{/}} in [It] - cl4.go:144 {{gray}}@ %s{{/}}", FORMATTED_TIME), " {{gray}}<< Timeline{{/}}", "", " {{orange}}[TIMEDOUT] failure", " message{{/}}", spr(" {{orange}}In {{bold}}[It]{{/}}{{orange}} at: {{bold}}cl3.go:103{{/}} {{gray}}@ %s{{/}}", FORMATTED_TIME), "", " {{magenta}}[PANICKED] {{/}}", spr(" {{magenta}}In {{bold}}[It]{{/}}{{magenta}} at: {{bold}}cl4.go:144{{/}} {{gray}}@ %s{{/}}", FORMATTED_TIME), DELIMITER, ""), Case(VeryVerbose|Parallel, DELIMITER, spr("{{orange}}%s [TIMEDOUT] [1.000 seconds]{{/}}", DENOTER), "A", "{{gray}}cl0.go:12{{/}}", " B", " {{gray}}cl1.go:37{{/}}", " {{orange}}{{bold}}[It] C{{/}}", " {{gray}}cl2.go:80{{/}}", "", " {{gray}}Timeline >>{{/}}", " some ginkgowriter", " {{orange}}[TIMEDOUT] failure", " message{{/}}", spr(" {{orange}}In {{bold}}[It]{{/}}{{orange}} at: {{bold}}cl3.go:103{{/}} {{gray}}@ %s{{/}}", FORMATTED_TIME), " output", " {{magenta}}[PANICKED] {{/}}", spr(" {{magenta}}In {{bold}}[It]{{/}}{{magenta}} at: {{bold}}cl4.go:144{{/}} {{gray}}@ %s{{/}}", FORMATTED_TIME), " {{gray}}<< Timeline{{/}}", DELIMITER, ""), Case(Verbose, DELIMITER, "{{/}}A {{gray}}B {{/}}{{bold}}[It] C{{/}}", "{{gray}}cl2.go:80{{/}}", spr("{{orange}}%s [TIMEDOUT] [1.000 seconds]{{/}}", DENOTER), "{{/}}A {{gray}}B {{orange}}{{bold}}[It] C{{/}}", "{{gray}}cl2.go:80{{/}}", "", " {{orange}}[TIMEDOUT] failure", " message{{/}}", spr(" {{orange}}In {{bold}}[It]{{/}}{{orange}} at: {{bold}}cl3.go:103{{/}} {{gray}}@ %s{{/}}", FORMATTED_TIME), "", " {{magenta}}[PANICKED] {{/}}", spr(" {{magenta}}In {{bold}}[It]{{/}}{{magenta}} at: {{bold}}cl4.go:144{{/}} {{gray}}@ %s{{/}}", FORMATTED_TIME), DELIMITER, ""), Case(VeryVerbose, //note: no failure summary as it will appear in the timeline (which isn't shown here since we don't replay the timeline in the spec) DELIMITER, "A", "{{gray}}cl0.go:12{{/}}", " B", " {{gray}}cl1.go:37{{/}}", " {{/}}{{bold}}[It] C{{/}}", " {{gray}}cl2.go:80{{/}}", spr("{{orange}}%s [TIMEDOUT] [1.000 seconds]{{/}}", DENOTER), "A", "{{gray}}cl0.go:12{{/}}", " B", " {{gray}}cl1.go:37{{/}}", " {{orange}}{{bold}}[It] C{{/}}", " {{gray}}cl2.go:80{{/}}", DELIMITER, ""), ), Entry("a failed test with timeline entries", S(types.NodeTypeIt, CTS("A", "B"), CLS(cl0, cl1), "C", cl2, types.SpecStateInterrupted, SE(types.SpecEventByStart, "a by step", cl0), F("failure\nmessage", cl3, types.FailureNodeIsLeafNode, FailureNodeLocation(cl2), types.NodeTypeIt, TL(0), PR("Interrupt Progress Report", types.NodeTypeIt, CurrentNodeText("C"), LeafNodeText("C"), []string{"A", "B"}), AF(types.SpecStatePanicked, cl4, types.FailureNodeIsLeafNode, FailureNodeLocation(cl2), types.NodeTypeIt, TL(0))), RE("a report entry", cl1), ), Case(Succinct, Succinct|Parallel, Normal, Normal|Parallel, Verbose|Parallel, DELIMITER, spr("{{orange}}%s [INTERRUPTED] [1.000 seconds]{{/}}", DENOTER), "{{/}}A {{gray}}B {{orange}}{{bold}}[It] C{{/}}", "{{gray}}cl2.go:80{{/}}", "", " {{gray}}Timeline >>{{/}}", spr(" {{bold}}STEP:{{/}} a by step {{gray}}@ %s{{/}}", FORMATTED_TIME), spr(" {{orange}}[INTERRUPTED]{{/}} in [It] - cl3.go:103 {{gray}}@ %s{{/}}", FORMATTED_TIME), spr(" {{magenta}}[PANICKED]{{/}} in [It] - cl4.go:144 {{gray}}@ %s{{/}}", FORMATTED_TIME), spr(" {{bold}}a report entry{{gray}} - cl1.go:37 @ %s{{/}}", FORMATTED_TIME), " {{gray}}<< Timeline{{/}}", "", " {{orange}}[INTERRUPTED] failure", " message{{/}}", spr(" {{orange}}In {{bold}}[It]{{/}}{{orange}} at: {{bold}}cl3.go:103{{/}} {{gray}}@ %s{{/}}", FORMATTED_TIME), "", " Interrupt Progress Report", " {{/}}A {{gray}}B{{/}} {{bold}}{{orange}}C{{/}} (Spec Runtime: 5s)", " {{gray}}cl0.go:12{{/}}", " In {{bold}}{{orange}}[It]{{/}} (Node Runtime: 3s)", " {{gray}}cl1.go:37{{/}}", "", " {{magenta}}[PANICKED] {{/}}", spr(" {{magenta}}In {{bold}}[It]{{/}}{{magenta}} at: {{bold}}cl4.go:144{{/}} {{gray}}@ %s{{/}}", FORMATTED_TIME), DELIMITER, ""), Case(VeryVerbose|Parallel, DELIMITER, spr("{{orange}}%s [INTERRUPTED] [1.000 seconds]{{/}}", DENOTER), "A", "{{gray}}cl0.go:12{{/}}", " B", " {{gray}}cl1.go:37{{/}}", " {{orange}}{{bold}}[It] C{{/}}", " {{gray}}cl2.go:80{{/}}", "", " {{gray}}Timeline >>{{/}}", spr(" {{bold}}STEP:{{/}} a by step {{gray}}- cl0.go:12 @ %s{{/}}", FORMATTED_TIME), " {{orange}}[INTERRUPTED] failure", " message{{/}}", spr(" {{orange}}In {{bold}}[It]{{/}}{{orange}} at: {{bold}}cl3.go:103{{/}} {{gray}}@ %s{{/}}", FORMATTED_TIME), "", " Interrupt Progress Report", " {{/}}A {{gray}}B{{/}} {{bold}}{{orange}}C{{/}} (Spec Runtime: 5s)", " {{gray}}cl0.go:12{{/}}", " In {{bold}}{{orange}}[It]{{/}} (Node Runtime: 3s)", " {{gray}}cl1.go:37{{/}}", " {{magenta}}[PANICKED] {{/}}", spr(" {{magenta}}In {{bold}}[It]{{/}}{{magenta}} at: {{bold}}cl4.go:144{{/}} {{gray}}@ %s{{/}}", FORMATTED_TIME), spr(" {{bold}}a report entry{{gray}} - cl1.go:37 @ %s{{/}}", FORMATTED_TIME), " {{gray}}<< Timeline{{/}}", DELIMITER, ""), Case(Verbose, // don't see the other timeline entries because they are emitted in realthime (which isn't shown here since we don't replay the timeline in the spec) DELIMITER, "{{/}}A {{gray}}B {{/}}{{bold}}[It] C{{/}}", "{{gray}}cl2.go:80{{/}}", spr("{{orange}}%s [INTERRUPTED] [1.000 seconds]{{/}}", DENOTER), "{{/}}A {{gray}}B {{orange}}{{bold}}[It] C{{/}}", "{{gray}}cl2.go:80{{/}}", "", " {{orange}}[INTERRUPTED] failure", " message{{/}}", spr(" {{orange}}In {{bold}}[It]{{/}}{{orange}} at: {{bold}}cl3.go:103{{/}} {{gray}}@ %s{{/}}", FORMATTED_TIME), "", " Interrupt Progress Report", " {{/}}A {{gray}}B{{/}} {{bold}}{{orange}}C{{/}} (Spec Runtime: 5s)", " {{gray}}cl0.go:12{{/}}", " In {{bold}}{{orange}}[It]{{/}} (Node Runtime: 3s)", " {{gray}}cl1.go:37{{/}}", "", " {{magenta}}[PANICKED] {{/}}", spr(" {{magenta}}In {{bold}}[It]{{/}}{{magenta}} at: {{bold}}cl4.go:144{{/}} {{gray}}@ %s{{/}}", FORMATTED_TIME), DELIMITER, ""), Case(VeryVerbose, //note: no failure summary as it will appear in the timeline (which isn't shown here since we don't replay the timeline in the spec) DELIMITER, "A", "{{gray}}cl0.go:12{{/}}", " B", " {{gray}}cl1.go:37{{/}}", " {{/}}{{bold}}[It] C{{/}}", " {{gray}}cl2.go:80{{/}}", spr("{{orange}}%s [INTERRUPTED] [1.000 seconds]{{/}}", DENOTER), "A", "{{gray}}cl0.go:12{{/}}", " B", " {{gray}}cl1.go:37{{/}}", " {{orange}}{{bold}}[It] C{{/}}", " {{gray}}cl2.go:80{{/}}", DELIMITER, ""), ), Entry("a failed test with both GinkgoWriter output and timeline entries (including multiple additional failures)", S(types.NodeTypeIt, Label("cat", "dog"), CLabels(Label("dolphin"), Label("gorilla", "cow")), CTS("A", "B"), CLS(cl0, cl1), "C", cl2, types.SpecStateTimedout, STD("some captured stdout\n"), GW("ginkgowriter\noutput\ncleanup!"), SE(types.SpecEventByStart, "a by step", cl0), SE(types.SpecEventNodeStart, types.NodeTypeIt, "C", cl2, TL(0)), F("failure\nmessage", cl3, types.FailureNodeIsLeafNode, FailureNodeLocation(cl2), types.NodeTypeIt, TL("ginkgowriter\n"), AF(types.SpecStatePanicked, cl4, types.FailureNodeIsLeafNode, FailureNodeLocation(cl2), types.NodeTypeIt, TL("ginkgowriter\noutput\n"), ForwardedPanic("the panic!"))), SE(types.SpecEventNodeEnd, types.NodeTypeIt, "C", cl2, TL("ginkgowriter\noutput\n"), time.Microsecond*87230), RE("a report entry", cl1, TL("ginkgowriter\noutput\n")), RE("a hidden report entry", cl1, TL("ginkgowriter\noutput\n"), types.ReportEntryVisibilityNever), AF(types.SpecStateFailed, "a subsequent failure", types.FailureNodeInContainer, FailureNodeLocation(cl3), types.NodeTypeAfterEach, 0, TL("ginkgowriter\noutput\ncleanup!")), ), Case(Succinct, Normal, DELIMITER, spr("{{orange}}%s [TIMEDOUT] [1.000 seconds]{{/}}", DENOTER), "{{/}}A {{gray}}B {{orange}}{{bold}}[It] C{{/}} {{coral}}[dolphin, gorilla, cow, cat, dog]{{/}}", "{{gray}}cl2.go:80{{/}}", "", " {{gray}}Timeline >>{{/}}", spr(" {{bold}}STEP:{{/}} a by step {{gray}}@ %s{{/}}", FORMATTED_TIME), " ginkgowriter", spr(" {{orange}}[TIMEDOUT]{{/}} in [It] - cl3.go:103 {{gray}}@ %s{{/}}", FORMATTED_TIME), " output", spr(" {{magenta}}[PANICKED]{{/}} in [It] - cl4.go:144 {{gray}}@ %s{{/}}", FORMATTED_TIME), spr(" {{bold}}a report entry{{gray}} - cl1.go:37 @ %s{{/}}", FORMATTED_TIME), " cleanup!", spr(" {{red}}[FAILED]{{/}} in [AfterEach] - :0 {{gray}}@ %s{{/}}", FORMATTED_TIME), " {{gray}}<< Timeline{{/}}", "", " {{orange}}[TIMEDOUT] failure", " message{{/}}", spr(" {{orange}}In {{bold}}[It]{{/}}{{orange}} at: {{bold}}cl3.go:103{{/}} {{gray}}@ %s{{/}}", FORMATTED_TIME), "", " {{magenta}}[PANICKED] {{/}}", spr(" {{magenta}}In {{bold}}[It]{{/}}{{magenta}} at: {{bold}}cl4.go:144{{/}} {{gray}}@ %s{{/}}", FORMATTED_TIME), "", " {{magenta}}the panic!{{/}}", "", " {{magenta}}Full Stack Trace{{/}}", " full-trace", " cl-4", "", " There were {{bold}}{{red}}additional failures{{/}} detected. To view them in detail run {{bold}}ginkgo -vv{{/}}", DELIMITER, ""), Case(Succinct|ShowNodeEvents, Normal|ShowNodeEvents, DELIMITER, spr("{{orange}}%s [TIMEDOUT] [1.000 seconds]{{/}}", DENOTER), "{{/}}A {{gray}}B {{orange}}{{bold}}[It] C{{/}} {{coral}}[dolphin, gorilla, cow, cat, dog]{{/}}", "{{gray}}cl2.go:80{{/}}", "", " {{gray}}Timeline >>{{/}}", spr(" {{bold}}STEP:{{/}} a by step {{gray}}@ %s{{/}}", FORMATTED_TIME), spr(" > Enter {{bold}}[It]{{/}} C {{gray}}@ %s{{/}}", FORMATTED_TIME), " ginkgowriter", spr(" {{orange}}[TIMEDOUT]{{/}} in [It] - cl3.go:103 {{gray}}@ %s{{/}}", FORMATTED_TIME), " output", spr(" {{magenta}}[PANICKED]{{/}} in [It] - cl4.go:144 {{gray}}@ %s{{/}}", FORMATTED_TIME), spr(" < Exit {{bold}}[It]{{/}} C {{gray}}@ %s (87ms){{/}}", FORMATTED_TIME), spr(" {{bold}}a report entry{{gray}} - cl1.go:37 @ %s{{/}}", FORMATTED_TIME), " cleanup!", spr(" {{red}}[FAILED]{{/}} in [AfterEach] - :0 {{gray}}@ %s{{/}}", FORMATTED_TIME), " {{gray}}<< Timeline{{/}}", "", " {{orange}}[TIMEDOUT] failure", " message{{/}}", spr(" {{orange}}In {{bold}}[It]{{/}}{{orange}} at: {{bold}}cl3.go:103{{/}} {{gray}}@ %s{{/}}", FORMATTED_TIME), "", " {{magenta}}[PANICKED] {{/}}", spr(" {{magenta}}In {{bold}}[It]{{/}}{{magenta}} at: {{bold}}cl4.go:144{{/}} {{gray}}@ %s{{/}}", FORMATTED_TIME), "", " {{magenta}}the panic!{{/}}", "", " {{magenta}}Full Stack Trace{{/}}", " full-trace", " cl-4", "", " There were {{bold}}{{red}}additional failures{{/}} detected. To view them in detail run {{bold}}ginkgo -vv{{/}}", DELIMITER, ""), Case(Succinct|Parallel, Normal|Parallel, Verbose|Parallel, DELIMITER, spr("{{orange}}%s [TIMEDOUT] [1.000 seconds]{{/}}", DENOTER), "{{/}}A {{gray}}B {{orange}}{{bold}}[It] C{{/}} {{coral}}[dolphin, gorilla, cow, cat, dog]{{/}}", "{{gray}}cl2.go:80{{/}}", "", " {{gray}}Captured StdOut/StdErr Output >>{{/}}", " some captured stdout", " {{gray}}<< Captured StdOut/StdErr Output{{/}}", "", " {{gray}}Timeline >>{{/}}", spr(" {{bold}}STEP:{{/}} a by step {{gray}}@ %s{{/}}", FORMATTED_TIME), " ginkgowriter", spr(" {{orange}}[TIMEDOUT]{{/}} in [It] - cl3.go:103 {{gray}}@ %s{{/}}", FORMATTED_TIME), " output", spr(" {{magenta}}[PANICKED]{{/}} in [It] - cl4.go:144 {{gray}}@ %s{{/}}", FORMATTED_TIME), spr(" {{bold}}a report entry{{gray}} - cl1.go:37 @ %s{{/}}", FORMATTED_TIME), " cleanup!", spr(" {{red}}[FAILED]{{/}} in [AfterEach] - :0 {{gray}}@ %s{{/}}", FORMATTED_TIME), " {{gray}}<< Timeline{{/}}", "", " {{orange}}[TIMEDOUT] failure", " message{{/}}", spr(" {{orange}}In {{bold}}[It]{{/}}{{orange}} at: {{bold}}cl3.go:103{{/}} {{gray}}@ %s{{/}}", FORMATTED_TIME), "", " {{magenta}}[PANICKED] {{/}}", spr(" {{magenta}}In {{bold}}[It]{{/}}{{magenta}} at: {{bold}}cl4.go:144{{/}} {{gray}}@ %s{{/}}", FORMATTED_TIME), "", " {{magenta}}the panic!{{/}}", "", " {{magenta}}Full Stack Trace{{/}}", " full-trace", " cl-4", "", " There were {{bold}}{{red}}additional failures{{/}} detected. To view them in detail run {{bold}}ginkgo -vv{{/}}", DELIMITER, ""), Case(Succinct|Parallel|FullTrace, Normal|Parallel|FullTrace, Verbose|Parallel|FullTrace, DELIMITER, spr("{{orange}}%s [TIMEDOUT] [1.000 seconds]{{/}}", DENOTER), "{{/}}A {{gray}}B {{orange}}{{bold}}[It] C{{/}} {{coral}}[dolphin, gorilla, cow, cat, dog]{{/}}", "{{gray}}cl2.go:80{{/}}", "", " {{gray}}Captured StdOut/StdErr Output >>{{/}}", " some captured stdout", " {{gray}}<< Captured StdOut/StdErr Output{{/}}", "", " {{gray}}Timeline >>{{/}}", spr(" {{bold}}STEP:{{/}} a by step {{gray}}@ %s{{/}}", FORMATTED_TIME), " ginkgowriter", spr(" {{orange}}[TIMEDOUT]{{/}} in [It] - cl3.go:103 {{gray}}@ %s{{/}}", FORMATTED_TIME), " output", spr(" {{magenta}}[PANICKED]{{/}} in [It] - cl4.go:144 {{gray}}@ %s{{/}}", FORMATTED_TIME), spr(" {{bold}}a report entry{{gray}} - cl1.go:37 @ %s{{/}}", FORMATTED_TIME), " cleanup!", spr(" {{red}}[FAILED]{{/}} in [AfterEach] - :0 {{gray}}@ %s{{/}}", FORMATTED_TIME), " {{gray}}<< Timeline{{/}}", "", " {{orange}}[TIMEDOUT] failure", " message{{/}}", spr(" {{orange}}In {{bold}}[It]{{/}}{{orange}} at: {{bold}}cl3.go:103{{/}} {{gray}}@ %s{{/}}", FORMATTED_TIME), "", " {{orange}}Full Stack Trace{{/}}", " full-trace", " cl-3", "", " {{magenta}}[PANICKED] {{/}}", spr(" {{magenta}}In {{bold}}[It]{{/}}{{magenta}} at: {{bold}}cl4.go:144{{/}} {{gray}}@ %s{{/}}", FORMATTED_TIME), "", " {{magenta}}the panic!{{/}}", "", " {{magenta}}Full Stack Trace{{/}}", " full-trace", " cl-4", "", " There were {{bold}}{{red}}additional failures{{/}} detected. To view them in detail run {{bold}}ginkgo -vv{{/}}", DELIMITER, ""), Case(VeryVerbose|Parallel, DELIMITER, spr("{{orange}}%s [TIMEDOUT] [1.000 seconds]{{/}}", DENOTER), "A {{coral}}[dolphin]{{/}}", "{{gray}}cl0.go:12{{/}}", " B {{coral}}[gorilla, cow]{{/}}", " {{gray}}cl1.go:37{{/}}", " {{orange}}{{bold}}[It] C{{/}} {{coral}}[cat, dog]{{/}}", " {{gray}}cl2.go:80{{/}}", "", " {{gray}}Captured StdOut/StdErr Output >>{{/}}", " some captured stdout", " {{gray}}<< Captured StdOut/StdErr Output{{/}}", "", " {{gray}}Timeline >>{{/}}", spr(" {{bold}}STEP:{{/}} a by step {{gray}}- cl0.go:12 @ %s{{/}}", FORMATTED_TIME), spr(" > Enter {{bold}}[It]{{/}} C {{gray}}- cl2.go:80 @ %s{{/}}", FORMATTED_TIME), " ginkgowriter", " {{orange}}[TIMEDOUT] failure", " message{{/}}", spr(" {{orange}}In {{bold}}[It]{{/}}{{orange}} at: {{bold}}cl3.go:103{{/}} {{gray}}@ %s{{/}}", FORMATTED_TIME), " output", " {{magenta}}[PANICKED] {{/}}", spr(" {{magenta}}In {{bold}}[It]{{/}}{{magenta}} at: {{bold}}cl4.go:144{{/}} {{gray}}@ %s{{/}}", FORMATTED_TIME), "", " {{magenta}}the panic!{{/}}", "", " {{magenta}}Full Stack Trace{{/}}", " full-trace", " cl-4", spr(" < Exit {{bold}}[It]{{/}} C {{gray}}- cl2.go:80 @ %s (87ms){{/}}", FORMATTED_TIME), spr(" {{bold}}a report entry{{gray}} - cl1.go:37 @ %s{{/}}", FORMATTED_TIME), " cleanup!", " {{red}}[FAILED] a subsequent failure{{/}}", spr(" {{red}}In {{bold}}[AfterEach]{{/}}{{red}} at: {{bold}}:0{{/}} {{gray}}@ %s{{/}}", FORMATTED_TIME), " {{gray}}<< Timeline{{/}}", DELIMITER, ""), Case(Verbose, // don't see the other timeline entries because they are emitted in realthime (which isn't shown here since we don't replay the timeline in the spec) DELIMITER, "{{/}}A {{gray}}B {{/}}{{bold}}[It] C{{/}} {{coral}}[dolphin, gorilla, cow, cat, dog]{{/}}", "{{gray}}cl2.go:80{{/}}", spr("{{orange}}%s [TIMEDOUT] [1.000 seconds]{{/}}", DENOTER), "{{/}}A {{gray}}B {{orange}}{{bold}}[It] C{{/}} {{coral}}[dolphin, gorilla, cow, cat, dog]{{/}}", "{{gray}}cl2.go:80{{/}}", "", " {{orange}}[TIMEDOUT] failure", " message{{/}}", spr(" {{orange}}In {{bold}}[It]{{/}}{{orange}} at: {{bold}}cl3.go:103{{/}} {{gray}}@ %s{{/}}", FORMATTED_TIME), "", " {{magenta}}[PANICKED] {{/}}", spr(" {{magenta}}In {{bold}}[It]{{/}}{{magenta}} at: {{bold}}cl4.go:144{{/}} {{gray}}@ %s{{/}}", FORMATTED_TIME), "", " {{magenta}}the panic!{{/}}", "", " {{magenta}}Full Stack Trace{{/}}", " full-trace", " cl-4", "", " There were {{bold}}{{red}}additional failures{{/}} detected. To view them in detail run {{bold}}ginkgo -vv{{/}}", DELIMITER, ""), Case(VeryVerbose, //note: no failure summary as it will appear in the timeline (which isn't shown here since we don't replay the timeline in the spec) DELIMITER, "A {{coral}}[dolphin]{{/}}", "{{gray}}cl0.go:12{{/}}", " B {{coral}}[gorilla, cow]{{/}}", " {{gray}}cl1.go:37{{/}}", " {{/}}{{bold}}[It] C{{/}} {{coral}}[cat, dog]{{/}}", " {{gray}}cl2.go:80{{/}}", spr("{{orange}}%s [TIMEDOUT] [1.000 seconds]{{/}}", DENOTER), "A {{coral}}[dolphin]{{/}}", "{{gray}}cl0.go:12{{/}}", " B {{coral}}[gorilla, cow]{{/}}", " {{gray}}cl1.go:37{{/}}", " {{orange}}{{bold}}[It] C{{/}} {{coral}}[cat, dog]{{/}}", " {{gray}}cl2.go:80{{/}}", DELIMITER, ""), ), ) DescribeTable("Rendering SuiteDidEnd", func(conf types.ReporterConfig, report types.Report, expected ...any) { reporter := reporters.NewDefaultReporterUnderTest(conf, buf) reporter.SuiteDidEnd(report) Expect(string(buf.Contents())).Should(MatchLines(expected...)) }, Entry("when configured to be succinct", C(Succinct), types.Report{ SuiteSucceeded: true, RunTime: time.Minute, SpecReports: types.SpecReports{S()}, }, " {{green}}SUCCESS!{{/}} 1m0s ", ), Entry("the suite passes", C(), types.Report{ SuiteSucceeded: true, PreRunStats: types.PreRunStats{TotalSpecs: 8, SpecsThatWillRun: 8}, RunTime: time.Minute, SpecReports: types.SpecReports{ S(types.NodeTypeBeforeSuite), S(types.SpecStatePassed), S(types.SpecStatePassed), S(types.SpecStatePassed), S(types.SpecStatePending), S(types.SpecStatePending), S(types.SpecStateSkipped), S(types.SpecStateSkipped), S(types.SpecStateSkipped), S(types.NodeTypeAfterSuite), }, }, "", "{{green}}{{bold}}Ran 3 of 8 Specs in 60.000 seconds{{/}}", "{{green}}{{bold}}SUCCESS!{{/}} -- {{green}}{{bold}}3 Passed{{/}} | {{red}}{{bold}}0 Failed{{/}} | {{yellow}}{{bold}}2 Pending{{/}} | {{cyan}}{{bold}}3 Skipped{{/}}", "", ), Entry("the suite passes and has flaky specs", C(), types.Report{ SuiteSucceeded: true, PreRunStats: types.PreRunStats{TotalSpecs: 10, SpecsThatWillRun: 8}, RunTime: time.Minute, SpecReports: types.SpecReports{ S(types.NodeTypeBeforeSuite), S(types.SpecStatePassed), S(types.SpecStatePassed), S(types.SpecStatePassed), S(types.SpecStatePassed, 3, FlakeAttempts(5)), S(types.SpecStatePassed, 4, FlakeAttempts(5)), //flakey S(types.SpecStatePassed, 3, MustPassRepeatedly(5)), S(types.SpecStatePassed, 4, MustPassRepeatedly(5)), //repeated S(types.SpecStatePending), S(types.SpecStatePending), S(types.SpecStateSkipped), S(types.SpecStateSkipped), S(types.SpecStateSkipped), S(types.NodeTypeAfterSuite), }, }, "", "{{green}}{{bold}}Ran 7 of 10 Specs in 60.000 seconds{{/}}", "{{green}}{{bold}}SUCCESS!{{/}} -- {{green}}{{bold}}7 Passed{{/}} | {{red}}{{bold}}0 Failed{{/}} | {{light-yellow}}{{bold}}2 Flaked{{/}} | {{yellow}}{{bold}}2 Pending{{/}} | {{cyan}}{{bold}}3 Skipped{{/}}", "", ), Entry("the suite fails with one failed test", C(), types.Report{ SuiteSucceeded: false, PreRunStats: types.PreRunStats{TotalSpecs: 18, SpecsThatWillRun: 8}, RunTime: time.Minute, SpecReports: types.SpecReports{ S(types.NodeTypeBeforeSuite), S(types.SpecStatePassed), S(types.SpecStatePassed), S(types.SpecStatePassed), S(types.SpecStatePassed, 3, FlakeAttempts(5)), S(types.SpecStatePassed, 4, FlakeAttempts(5)), //flakey S(types.SpecStatePassed, 3, MustPassRepeatedly(5)), S(types.SpecStatePassed, 4, MustPassRepeatedly(5)), //repeated S(types.SpecStatePending), S(types.SpecStatePending), S(types.SpecStateSkipped), S(types.SpecStateSkipped), S(types.SpecStateSkipped), S(CTS("Describe A", "Context B"), "The Test", CLS(cl0, cl1), cl2, types.SpecStateFailed, 2, F("FAILURE MESSAGE\nWITH DETAILS", types.FailureNodeInContainer, FailureNodeLocation(cl3), types.NodeTypeJustBeforeEach, 1, cl4), ), S(types.NodeTypeAfterSuite), }, }, "", "{{red}}{{bold}}Summarizing 1 Failure:{{/}}", " {{red}}[FAIL]{{/}} {{/}}Describe A {{red}}{{bold}}Context B [JustBeforeEach] {{/}}The Test{{/}}", " {{gray}}cl4.go:144{{/}}", "", "{{red}}{{bold}}Ran 8 of 18 Specs in 60.000 seconds{{/}}", "{{red}}{{bold}}FAIL!{{/}} -- {{green}}{{bold}}7 Passed{{/}} | {{red}}{{bold}}1 Failed{{/}} | {{light-yellow}}{{bold}}2 Flaked{{/}} | {{yellow}}{{bold}}2 Pending{{/}} | {{cyan}}{{bold}}3 Skipped{{/}}", "", ), Entry("the suite fails with multiple failed tests", C(), types.Report{ SuiteSucceeded: false, PreRunStats: types.PreRunStats{TotalSpecs: 14, SpecsThatWillRun: 10}, RunTime: time.Minute, SpecReports: types.SpecReports{ S(types.NodeTypeBeforeSuite), S(types.SpecStatePassed), S(types.SpecStatePassed), S(types.SpecStatePassed), S(types.SpecStatePassed, 3, FlakeAttempts(5)), S(types.SpecStatePassed, 4, FlakeAttempts(5)), //flakey S(types.SpecStatePassed, 3, MustPassRepeatedly(5)), //repeated, and passed S(types.SpecStateFailed, 3, MustPassRepeatedly(5), "repeater", F("failure", types.FailureNodeIsLeafNode, FailureNodeLocation(cl2), types.NodeTypeIt, cl3)), //repeated, but failed S(types.SpecStateFailed, 4, MustPassRepeatedly(5), "another-repeater", F("failure-again", types.FailureNodeIsLeafNode, FailureNodeLocation(cl2), types.NodeTypeIt, cl3)), //repeated, but failed S(types.SpecStatePending), S(types.SpecStatePending), S(types.SpecStateSkipped), S(types.SpecStateSkipped), S(types.SpecStateSkipped), S(CTS("Describe A", "Context B"), "The Test", CLS(cl0, cl1), cl2, CLabels(Label("cat", "dog"), Label("dog", "fish")), Label("fish", "giraffe"), types.SpecStateFailed, 2, F("FAILURE MESSAGE\nWITH DETAILS", types.FailureNodeInContainer, FailureNodeLocation(cl3), types.NodeTypeJustBeforeEach, 1, cl4), ), S(CTS("Describe A"), "The Test", CLS(cl0), cl1, types.SpecStatePanicked, 2, F("FAILURE MESSAGE\nWITH DETAILS", types.FailureNodeIsLeafNode, FailureNodeLocation(cl1), types.NodeTypeIt, cl2), ), S("The Test", cl0, types.SpecStateInterrupted, 2, F("FAILURE MESSAGE\nWITH DETAILS", types.FailureNodeIsLeafNode, FailureNodeLocation(cl0), types.NodeTypeIt, cl1), ), S("The Test", cl0, types.SpecStateAborted, 2, F("FAILURE MESSAGE\nWITH DETAILS", types.FailureNodeIsLeafNode, FailureNodeLocation(cl0), types.NodeTypeIt, cl1), ), S("The Test", cl0, types.SpecStateTimedout, 2, F("FAILURE MESSAGE\nWITH DETAILS", types.FailureNodeIsLeafNode, FailureNodeLocation(cl0), types.NodeTypeIt, cl1), ), S(types.NodeTypeAfterSuite), }, }, "", "{{red}}{{bold}}Summarizing 7 Failures:{{/}}", " {{red}}[FAIL]{{/}} {{red}}{{bold}}[It] repeater{{/}}", " {{gray}}cl3.go:103{{/}}", " {{red}}[FAIL]{{/}} {{red}}{{bold}}[It] another-repeater{{/}}", " {{gray}}cl3.go:103{{/}}", " {{red}}[FAIL]{{/}} {{/}}Describe A {{red}}{{bold}}Context B [JustBeforeEach] {{/}}The Test{{/}} {{coral}}[cat, dog, fish, giraffe]{{/}}", " {{gray}}cl4.go:144{{/}}", " {{magenta}}[PANICKED!]{{/}} {{/}}Describe A {{magenta}}{{bold}}[It] The Test{{/}}", " {{gray}}cl2.go:80{{/}}", " {{orange}}[INTERRUPTED]{{/}} {{orange}}{{bold}}[It] The Test{{/}}", " {{gray}}cl1.go:37{{/}}", " {{coral}}[ABORTED]{{/}} {{coral}}{{bold}}[It] The Test{{/}}", " {{gray}}cl1.go:37{{/}}", " {{orange}}[TIMEDOUT]{{/}} {{orange}}{{bold}}[It] The Test{{/}}", " {{gray}}cl1.go:37{{/}}", "", "{{red}}{{bold}}Ran 13 of 14 Specs in 60.000 seconds{{/}}", "{{red}}{{bold}}FAIL!{{/}} -- {{green}}{{bold}}6 Passed{{/}} | {{red}}{{bold}}7 Failed{{/}} | {{light-yellow}}{{bold}}2 Flaked{{/}} | {{light-yellow}}{{bold}}2 Repeated{{/}} | {{yellow}}{{bold}}2 Pending{{/}} | {{cyan}}{{bold}}3 Skipped{{/}}", "", ), Entry("the suite fails with failed suite setups", C(), types.Report{ SuiteSucceeded: false, PreRunStats: types.PreRunStats{TotalSpecs: 10, SpecsThatWillRun: 5}, RunTime: time.Minute, SpecReports: types.SpecReports{ S(types.NodeTypeBeforeSuite, cl0, types.SpecStateFailed, 2, F("FAILURE MESSAGE\nWITH DETAILS", types.FailureNodeIsLeafNode, FailureNodeLocation(cl0), types.NodeTypeBeforeSuite, 1, cl1), ), S(types.NodeTypeAfterSuite, cl2, types.SpecStateFailed, 2, F("FAILURE MESSAGE\nWITH DETAILS", types.FailureNodeIsLeafNode, FailureNodeLocation(cl2), types.NodeTypeAfterSuite, 1, cl3), ), S(types.NodeTypeReportAfterSuite, "my report", cl1, types.SpecStateFailed, 2, F("FAILURE MESSAGE\nWITH DETAILS", types.FailureNodeIsLeafNode, FailureNodeLocation(cl3), types.NodeTypeReportAfterSuite, 1, cl4), ), }, }, "", "{{red}}{{bold}}Summarizing 3 Failures:{{/}}", " {{red}}[FAIL]{{/}} {{red}}{{bold}}[BeforeSuite] {{/}}", " {{gray}}"+cl1.String()+"{{/}}", " {{red}}[FAIL]{{/}} {{red}}{{bold}}[AfterSuite] {{/}}", " {{gray}}"+cl3.String()+"{{/}}", " {{red}}[FAIL]{{/}} {{red}}{{bold}}[ReportAfterSuite] my report{{/}}", " {{gray}}"+cl4.String()+"{{/}}", "", "{{red}}{{bold}}Ran 0 of 10 Specs in 60.000 seconds{{/}}", "{{red}}{{bold}}FAIL!{{/}} -- {{cyan}}{{bold}}A BeforeSuite node failed so all tests were skipped.{{/}}", "", ), Entry("when the suite includes a special failure reason", C(), types.Report{ SuiteSucceeded: false, SpecialSuiteFailureReasons: []string{"Detected pending specs and --fail-on-pending is set"}, SuiteConfig: types.SuiteConfig{FailOnPending: true}, PreRunStats: types.PreRunStats{TotalSpecs: 5, SpecsThatWillRun: 3}, RunTime: time.Minute, SpecReports: types.SpecReports{ S(types.SpecStatePassed), S(types.SpecStatePassed), S(types.SpecStatePassed), S(types.SpecStatePending), S(types.SpecStatePending), }, }, "", "{{red}}{{bold}}Ran 3 of 5 Specs in 60.000 seconds{{/}}", "{{red}}{{bold}}FAIL! - Detected pending specs and --fail-on-pending is set{{/}} -- {{green}}{{bold}}3 Passed{{/}} | {{red}}{{bold}}0 Failed{{/}} | {{yellow}}{{bold}}2 Pending{{/}} | {{cyan}}{{bold}}0 Skipped{{/}}", "", ), Entry("when the suite includes multiple special failure reasons", C(), types.Report{ SuiteSucceeded: false, SpecialSuiteFailureReasons: []string{"Detected pending specs and --fail-on-pending is set", "Interrupted by Timeout"}, SuiteConfig: types.SuiteConfig{FailOnPending: true}, PreRunStats: types.PreRunStats{TotalSpecs: 5, SpecsThatWillRun: 3}, RunTime: time.Minute, SpecReports: types.SpecReports{ S(types.SpecStatePassed), S(types.SpecStatePassed), S(types.SpecStatePassed), S(types.SpecStatePending), S(types.SpecStatePending), }, }, "", "{{red}}{{bold}}Ran 3 of 5 Specs in 60.000 seconds{{/}}", "{{red}}{{bold}}FAIL! - Detected pending specs and --fail-on-pending is set, Interrupted by Timeout{{/}}", "{{green}}{{bold}}3 Passed{{/}} | {{red}}{{bold}}0 Failed{{/}} | {{yellow}}{{bold}}2 Pending{{/}} | {{cyan}}{{bold}}0 Skipped{{/}}", "", ), ) DescribeTable("EmitProgressReport", func(conf types.ReporterConfig, report types.ProgressReport, expected ...any) { reporter := reporters.NewDefaultReporterUnderTest(conf, buf) reporter.EmitProgressReport(report) Expect(string(buf.Contents())).Should(MatchLines(expected...)) }, //just headers to start Entry("With a suite node", C(), PR("A Message", types.NodeTypeBeforeSuite), INDENTED_DELIMITER, " A Message", " In {{bold}}{{orange}}[BeforeSuite]{{/}} (Node Runtime: 3s)", " {{gray}}"+cl1.String()+"{{/}}", INDENTED_DELIMITER, ""), Entry("With a top-level spec", C(), PR("A Message", types.NodeTypeIt, CurrentNodeText("A Top-Level It"), LeafNodeText("A Top-Level It")), INDENTED_DELIMITER, " A Message", " {{bold}}{{orange}}A Top-Level It{{/}} (Spec Runtime: 5s)", " {{gray}}"+cl0.String()+"{{/}}", " In {{bold}}{{orange}}[It]{{/}} (Node Runtime: 3s)", " {{gray}}"+cl1.String()+"{{/}}", INDENTED_DELIMITER, ""), Entry("With a spec in containers", C(), PR(types.NodeTypeIt, CurrentNodeText("My Spec"), LeafNodeText("My Spec"), []string{"Container A", "Container B", "Container C"}), INDENTED_DELIMITER, " {{/}}Container A {{gray}}Container B {{/}}Container C{{/}} {{bold}}{{orange}}My Spec{{/}} (Spec Runtime: 5s)", " {{gray}}"+cl0.String()+"{{/}}", " In {{bold}}{{orange}}[It]{{/}} (Node Runtime: 3s)", " {{gray}}"+cl1.String()+"{{/}}", INDENTED_DELIMITER, ""), Entry("With no current node", C(), PR(LeafNodeText("My Spec"), []string{"Container A", "Container B", "Container C"}), INDENTED_DELIMITER, " {{/}}Container A {{gray}}Container B {{/}}Container C{{/}} {{bold}}{{orange}}My Spec{{/}} (Spec Runtime: 5s)", " {{gray}}"+cl0.String()+"{{/}}", INDENTED_DELIMITER, ""), Entry("With a current node that is not an It", C(), PR(LeafNodeText("My Spec"), []string{"Container A", "Container B", "Container C"}, types.NodeTypeBeforeEach), INDENTED_DELIMITER, " {{/}}Container A {{gray}}Container B {{/}}Container C{{/}} {{bold}}{{orange}}My Spec{{/}} (Spec Runtime: 5s)", " {{gray}}"+cl0.String()+"{{/}}", " In {{bold}}{{orange}}[BeforeEach]{{/}} (Node Runtime: 3s)", " {{gray}}"+cl1.String()+"{{/}}", INDENTED_DELIMITER, ""), Entry("With a current node that is not an It, but has text", C(), PR(types.NodeTypeReportAfterSuite, CurrentNodeText("My Report")), INDENTED_DELIMITER, " In {{bold}}{{orange}}[ReportAfterSuite]{{/}} {{bold}}{{orange}}My Report{{/}} (Node Runtime: 3s)", " {{gray}}"+cl1.String()+"{{/}}", INDENTED_DELIMITER, ""), Entry("With a current step", C(), PR(types.NodeTypeIt, CurrentNodeText("My Spec"), LeafNodeText("My Spec"), []string{"Container A", "Container B", "Container C"}, CurrentStepText("Reticulating Splines")), INDENTED_DELIMITER, " {{/}}Container A {{gray}}Container B {{/}}Container C{{/}} {{bold}}{{orange}}My Spec{{/}} (Spec Runtime: 5s)", " {{gray}}"+cl0.String()+"{{/}}", " In {{bold}}{{orange}}[It]{{/}} (Node Runtime: 3s)", " {{gray}}"+cl1.String()+"{{/}}", " At {{bold}}{{orange}}[By Step] Reticulating Splines{{/}} (Step Runtime: 1s)", " {{gray}}"+cl2.String()+"{{/}}", INDENTED_DELIMITER, ""), //including GinkgoWriter output Entry("when there is GinkgoWriter output and the spec is not running verbosely", C(), PR( types.NodeTypeIt, CurrentNodeText("My Spec"), LeafNodeText("My Spec"), GW("gw-1\ngw-2\ngw-3\ngw-4\ngw-5\ngw-6\ngw-7\ngw-8\ngw-9\ngw-10\ngw-11\ngw-12\n"), ), INDENTED_DELIMITER, " {{bold}}{{orange}}My Spec{{/}} (Spec Runtime: 5s)", " {{gray}}"+cl0.String()+"{{/}}", " In {{bold}}{{orange}}[It]{{/}} (Node Runtime: 3s)", " {{gray}}"+cl1.String()+"{{/}}", "", " {{gray}}Begin Captured GinkgoWriter Output >>{{/}}", " {{gray}}...{{/}}", " gw-3", " gw-4", " gw-5", " gw-6", " gw-7", " gw-8", " gw-9", " gw-10", " gw-11", " gw-12", " {{gray}}<< End Captured GinkgoWriter Output{{/}}", INDENTED_DELIMITER, ""), Entry("when there is fewer than 10 lines of GinkgoWriter output", C(), PR( types.NodeTypeIt, CurrentNodeText("My Spec"), LeafNodeText("My Spec"), GW("gw-1\ngw-2\ngw-3\ngw-4\ngw-5\ngw-6\ngw-7\ngw-8\ngw-9\n"), ), INDENTED_DELIMITER, " {{bold}}{{orange}}My Spec{{/}} (Spec Runtime: 5s)", " {{gray}}"+cl0.String()+"{{/}}", " In {{bold}}{{orange}}[It]{{/}} (Node Runtime: 3s)", " {{gray}}"+cl1.String()+"{{/}}", "", " {{gray}}Begin Captured GinkgoWriter Output >>{{/}}", " gw-1", " gw-2", " gw-3", " gw-4", " gw-5", " gw-6", " gw-7", " gw-8", " gw-9", " {{gray}}<< End Captured GinkgoWriter Output{{/}}", INDENTED_DELIMITER, ""), Entry("when running in verbose mode and not in parallel", C(Verbose), PR( types.NodeTypeIt, CurrentNodeText("My Spec"), LeafNodeText("My Spec"), GW("gw-1\n"), ), INDENTED_DELIMITER, " {{bold}}{{orange}}My Spec{{/}} (Spec Runtime: 5s)", " {{gray}}"+cl0.String()+"{{/}}", " In {{bold}}{{orange}}[It]{{/}} (Node Runtime: 3s)", " {{gray}}"+cl1.String()+"{{/}}", INDENTED_DELIMITER, ""), Entry("when running in verbose mode and in parallel", C(), PR( true, types.NodeTypeIt, CurrentNodeText("My Spec"), LeafNodeText("My Spec"), GW("gw-1\n"), ), INDENTED_DELIMITER, " {{coral}}Progress Report for Ginkgo Process #{{bold}}1{{/}}", " {{bold}}{{orange}}My Spec{{/}} (Spec Runtime: 5s)", " {{gray}}"+cl0.String()+"{{/}}", " In {{bold}}{{orange}}[It]{{/}} (Node Runtime: 3s)", " {{gray}}"+cl1.String()+"{{/}}", "", " {{gray}}Begin Captured GinkgoWriter Output >>{{/}}", " gw-1", " {{gray}}<< End Captured GinkgoWriter Output{{/}}", INDENTED_DELIMITER, ""), //various goroutines Entry("with a spec goroutine", C(), PR( types.NodeTypeIt, CurrentNodeText("My Spec"), LeafNodeText("My Spec"), G(true, "sleeping", Fn("F1()", "fileA", 15), Fn("F2()", "fileB", 11, true), Fn("F3()", "fileC", 9), ), ), INDENTED_DELIMITER, " {{bold}}{{orange}}My Spec{{/}} (Spec Runtime: 5s)", " {{gray}}"+cl0.String()+"{{/}}", " In {{bold}}{{orange}}[It]{{/}} (Node Runtime: 3s)", " {{gray}}"+cl1.String()+"{{/}}", "", " {{bold}}{{underline}}Spec Goroutine{{/}}", " {{orange}}goroutine 17 [sleeping]{{/}}", " {{gray}}F1(){{/}}", " {{gray}}fileA:15{{/}}", " {{orange}}{{bold}}> F2(){{/}}", " {{orange}}{{bold}}fileB:11{{/}}", " {{gray}}F3(){{/}}", " {{gray}}fileC:9{{/}}", INDENTED_DELIMITER, ""), Entry("with highlighted goroutines", C(), PR( types.NodeTypeIt, CurrentNodeText("My Spec"), LeafNodeText("My Spec"), G(false, "sleeping", Fn("F1()", "fileA", 15), Fn("F2()", "fileB", 11, true), Fn("F3()", "fileC", 9), ), G(false, "sleeping as well", Fn("F4()", "fileB", 12, true), Fn("F5()", "fileC", 30), Fn("F6()", "fileD", 2), ), ), INDENTED_DELIMITER, " {{bold}}{{orange}}My Spec{{/}} (Spec Runtime: 5s)", " {{gray}}cl0.go:12{{/}}", " In {{bold}}{{orange}}[It]{{/}} (Node Runtime: 3s)", " {{gray}}cl1.go:37{{/}}", "", " {{bold}}{{underline}}Goroutines of Interest{{/}}", " {{orange}}goroutine 17 [sleeping]{{/}}", " {{gray}}F1(){{/}}", " {{gray}}fileA:15{{/}}", " {{orange}}{{bold}}> F2(){{/}}", " {{orange}}{{bold}}fileB:11{{/}}", " {{gray}}F3(){{/}}", " {{gray}}fileC:9{{/}}", "", " {{orange}}goroutine 17 [sleeping as well]{{/}}", " {{orange}}{{bold}}> F4(){{/}}", " {{orange}}{{bold}}fileB:12{{/}}", " {{gray}}F5(){{/}}", " {{gray}}fileC:30{{/}}", " {{gray}}F6(){{/}}", " {{gray}}fileD:2{{/}}", INDENTED_DELIMITER, ""), Entry("with other goroutines", C(), PR( types.NodeTypeIt, CurrentNodeText("My Spec"), LeafNodeText("My Spec"), G(false, "sleeping", Fn("F1()", "fileA", 15), Fn("F2()", "fileB", 11), Fn("F3()", "fileC", 9), ), ), INDENTED_DELIMITER, " {{bold}}{{orange}}My Spec{{/}} (Spec Runtime: 5s)", " {{gray}}"+cl0.String()+"{{/}}", " In {{bold}}{{orange}}[It]{{/}} (Node Runtime: 3s)", " {{gray}}"+cl1.String()+"{{/}}", "", " {{gray}}{{bold}}{{underline}}Other Goroutines{{/}}", " {{gray}}goroutine 17 [sleeping]{{/}}", " {{gray}}F1(){{/}}", " {{gray}}fileA:15{{/}}", " {{gray}}F2(){{/}}", " {{gray}}fileB:11{{/}}", " {{gray}}F3(){{/}}", " {{gray}}fileC:9{{/}}", INDENTED_DELIMITER, ""), //fetching source code Entry("when source code is found", C(), PR( types.NodeTypeIt, CurrentNodeText("My Spec"), LeafNodeText("My Spec"), G(true, "sleeping", Fn("F1()", "fileA", 15), Fn( "F2()", "fileB", 21, true, 2, "source line 1", "source line 2", "source line 3 (highlight!)", "source line 4", "source line 5", ), Fn("F3()", "fileC", 9), ), ), INDENTED_DELIMITER, " {{bold}}{{orange}}My Spec{{/}} (Spec Runtime: 5s)", " {{gray}}"+cl0.String()+"{{/}}", " In {{bold}}{{orange}}[It]{{/}} (Node Runtime: 3s)", " {{gray}}"+cl1.String()+"{{/}}", "", " {{bold}}{{underline}}Spec Goroutine{{/}}", " {{orange}}goroutine 17 [sleeping]{{/}}", " {{gray}}F1(){{/}}", " {{gray}}fileA:15{{/}}", " {{orange}}{{bold}}> F2(){{/}}", " {{orange}}{{bold}}fileB:21{{/}}", " | source line 1", " | source line 2", " {{bold}}{{orange}}> source line 3 (highlight!){{/}}", " | source line 4", " | source line 5", " {{gray}}F3(){{/}}", " {{gray}}fileC:9{{/}}", INDENTED_DELIMITER, ""), Entry("correcting source code indentation", C(), PR( types.NodeTypeIt, CurrentNodeText("My Spec"), LeafNodeText("My Spec"), G(true, "sleeping", Fn("F1()", "fileA", 15), Fn( "F2()", "fileB", 26, true, 1, "\t\t\thello", "\t\t\t\tthere", "", "\t\t\tit", "\t\tworks", ), Fn("F3()", "fileC", 9), ), ), INDENTED_DELIMITER, " {{bold}}{{orange}}My Spec{{/}} (Spec Runtime: 5s)", " {{gray}}"+cl0.String()+"{{/}}", " In {{bold}}{{orange}}[It]{{/}} (Node Runtime: 3s)", " {{gray}}"+cl1.String()+"{{/}}", "", " {{bold}}{{underline}}Spec Goroutine{{/}}", " {{orange}}goroutine 17 [sleeping]{{/}}", " {{gray}}F1(){{/}}", " {{gray}}fileA:15{{/}}", " {{orange}}{{bold}}> F2(){{/}}", " {{orange}}{{bold}}fileB:26{{/}}", " | \thello", " {{bold}}{{orange}}> \t\tthere{{/}}", " | ", " | \tit", " | works", " {{gray}}F3(){{/}}", " {{gray}}fileC:9{{/}}", INDENTED_DELIMITER, ""), Entry("random edge case where source code is empty (it doesn't explode)", C(), PR( types.NodeTypeIt, CurrentNodeText("My Spec"), LeafNodeText("My Spec"), G(true, "sleeping", Fn("F1()", "fileA", 15), Fn( "F2()", "fileB", 26, true, 1, "", "", "", "", "", ), Fn("F3()", "fileC", 9), ), ), INDENTED_DELIMITER, " {{bold}}{{orange}}My Spec{{/}} (Spec Runtime: 5s)", " {{gray}}"+cl0.String()+"{{/}}", " In {{bold}}{{orange}}[It]{{/}} (Node Runtime: 3s)", " {{gray}}"+cl1.String()+"{{/}}", "", " {{bold}}{{underline}}Spec Goroutine{{/}}", " {{orange}}goroutine 17 [sleeping]{{/}}", " {{gray}}F1(){{/}}", " {{gray}}fileA:15{{/}}", " {{orange}}{{bold}}> F2(){{/}}", " {{orange}}{{bold}}fileB:26{{/}}", " | ", " {{bold}}{{orange}}> {{/}}", " | ", " | ", " | ", " {{gray}}F3(){{/}}", " {{gray}}fileC:9{{/}}", INDENTED_DELIMITER, ""), // including additional reports Entry("with one additional report", C(), PR( types.NodeTypeIt, CurrentNodeText("My Spec"), LeafNodeText("My Spec"), G(true, "sleeping", Fn("F1()", "fileA", 15), Fn("F2()", "fileB", 11, true), Fn("F3()", "fileC", 9), ), G(false, "sleeping", Fn("F1()", "fileA", 15), Fn("F2()", "fileB", 11, true), Fn("F3()", "fileC", 9), ), AdditionalReports{"{{blue}}Report 1{{/}}"}, ), INDENTED_DELIMITER, " {{bold}}{{orange}}My Spec{{/}} (Spec Runtime: 5s)", " {{gray}}"+cl0.String()+"{{/}}", " In {{bold}}{{orange}}[It]{{/}} (Node Runtime: 3s)", " {{gray}}"+cl1.String()+"{{/}}", "", " {{bold}}{{underline}}Spec Goroutine{{/}}", " {{orange}}goroutine 17 [sleeping]{{/}}", " {{gray}}F1(){{/}}", " {{gray}}fileA:15{{/}}", " {{orange}}{{bold}}> F2(){{/}}", " {{orange}}{{bold}}fileB:11{{/}}", " {{gray}}F3(){{/}}", " {{gray}}fileC:9{{/}}", "", " {{gray}}Begin Additional Progress Reports >>{{/}}", " {{blue}}Report 1{{/}}", " {{gray}}<< End Additional Progress Reports{{/}}", "", " {{bold}}{{underline}}Goroutines of Interest{{/}}", " {{orange}}goroutine 17 [sleeping]{{/}}", " {{gray}}F1(){{/}}", " {{gray}}fileA:15{{/}}", " {{orange}}{{bold}}> F2(){{/}}", " {{orange}}{{bold}}fileB:11{{/}}", " {{gray}}F3(){{/}}", " {{gray}}fileC:9{{/}}", INDENTED_DELIMITER, ""), Entry("with multiple additional reports", C(), PR( types.NodeTypeIt, CurrentNodeText("My Spec"), LeafNodeText("My Spec"), G(true, "sleeping", Fn("F1()", "fileA", 15), Fn("F2()", "fileB", 11, true), Fn("F3()", "fileC", 9), ), G(false, "sleeping", Fn("F1()", "fileA", 15), Fn("F2()", "fileB", 11, true), Fn("F3()", "fileC", 9), ), AdditionalReports{"{{blue}}Report 1{{/}}", "{{green}}Report 2{{/}}"}, ), INDENTED_DELIMITER, " {{bold}}{{orange}}My Spec{{/}} (Spec Runtime: 5s)", " {{gray}}"+cl0.String()+"{{/}}", " In {{bold}}{{orange}}[It]{{/}} (Node Runtime: 3s)", " {{gray}}"+cl1.String()+"{{/}}", "", " {{bold}}{{underline}}Spec Goroutine{{/}}", " {{orange}}goroutine 17 [sleeping]{{/}}", " {{gray}}F1(){{/}}", " {{gray}}fileA:15{{/}}", " {{orange}}{{bold}}> F2(){{/}}", " {{orange}}{{bold}}fileB:11{{/}}", " {{gray}}F3(){{/}}", " {{gray}}fileC:9{{/}}", "", " {{gray}}Begin Additional Progress Reports >>{{/}}", " {{blue}}Report 1{{/}}", " {{gray}}----------{{/}}", " {{green}}Report 2{{/}}", " {{gray}}<< End Additional Progress Reports{{/}}", "", " {{bold}}{{underline}}Goroutines of Interest{{/}}", " {{orange}}goroutine 17 [sleeping]{{/}}", " {{gray}}F1(){{/}}", " {{gray}}fileA:15{{/}}", " {{orange}}{{bold}}> F2(){{/}}", " {{orange}}{{bold}}fileB:11{{/}}", " {{gray}}F3(){{/}}", " {{gray}}fileC:9{{/}}", INDENTED_DELIMITER, ""), // when running in parallel Entry("when running in parallel", C(), PR( "A Message", true, 3, types.NodeTypeIt, CurrentNodeText("My Spec"), LeafNodeText("My Spec"), ), INDENTED_DELIMITER, " {{coral}}Progress Report for Ginkgo Process #{{bold}}3{{/}}", " A Message", " {{bold}}{{orange}}My Spec{{/}} (Spec Runtime: 5s)", " {{gray}}cl0.go:12{{/}}", " In {{bold}}{{orange}}[It]{{/}} (Node Runtime: 3s)", " {{gray}}cl1.go:37{{/}}", INDENTED_DELIMITER, ""), ) DescribeTable("EmitFailure", func(conf types.ReporterConfig, af types.AdditionalFailure, expected ...any) { reporter := reporters.NewDefaultReporterUnderTest(conf, buf) reporter.EmitFailure(af.State, af.Failure) Expect(string(buf.Contents())).Should(MatchLines(expected...)) }, // silent when running succinctly or normal Entry("emits nothing when running with succinct verbosity", C(Succinct), AF(types.SpecStateFailed, "message"), ), Entry("emits nothing when running in normal verbosity", C(), AF(types.SpecStateFailed, "message"), ), // one-line summary when running verbosely Entry("emits a one line summary when running in verbose mode", C(Verbose), AF(types.SpecStateFailed, "message", types.NodeTypeIt, cl0), spr(" {{red}}[FAILED]{{/}} in [It] - cl0.go:12 {{gray}}@ %s{{/}}", FORMATTED_TIME), "", ), Entry("is cyan when skipped", C(Verbose), AF(types.SpecStateSkipped, "message", types.NodeTypeIt, cl0), spr(" {{cyan}}[SKIPPED]{{/}} in [It] - cl0.go:12 {{gray}}@ %s{{/}}", FORMATTED_TIME), "", ), Entry("is orange when timedout", C(Verbose), AF(types.SpecStateTimedout, "message", types.NodeTypeIt, cl0), spr(" {{orange}}[TIMEDOUT]{{/}} in [It] - cl0.go:12 {{gray}}@ %s{{/}}", FORMATTED_TIME), "", ), Entry("is orange when interrupted", C(Verbose), AF(types.SpecStateInterrupted, "message", types.NodeTypeIt, cl0), spr(" {{orange}}[INTERRUPTED]{{/}} in [It] - cl0.go:12 {{gray}}@ %s{{/}}", FORMATTED_TIME), "", ), Entry("is coral when aborted", C(Verbose), AF(types.SpecStateAborted, "message", types.NodeTypeIt, cl0), spr(" {{coral}}[ABORTED]{{/}} in [It] - cl0.go:12 {{gray}}@ %s{{/}}", FORMATTED_TIME), "", ), // now, when running in VeryVerbose mode - all the things emerge... (and this is what also appears in summaries at the end of tests) Entry("emits a full summary when running in -vv", C(VeryVerbose), AF(types.SpecStateFailed, "MY FAILURE:\nFailure Details", types.NodeTypeIt, cl0), " {{red}}[FAILED] MY FAILURE:", " Failure Details{{/}}", spr(" {{red}}In {{bold}}[It]{{/}}{{red}} at: {{bold}}cl0.go:12{{/}} {{gray}}@ %s{{/}}", FORMATTED_TIME), "", ), Entry("includes the trace when FullTrace is enabled", C(VeryVerbose|FullTrace), AF(types.SpecStateFailed, "MY FAILURE:\nFailure Details", types.NodeTypeIt, cl0), " {{red}}[FAILED] MY FAILURE:", " Failure Details{{/}}", spr(" {{red}}In {{bold}}[It]{{/}}{{red}} at: {{bold}}cl0.go:12{{/}} {{gray}}@ %s{{/}}", FORMATTED_TIME), "", " {{red}}Full Stack Trace{{/}}", " full-trace", " cl-0", "", ), Entry("includes the forwarded panic and full-trace when a forwarded panic is provided", C(VeryVerbose), AF(types.SpecStatePanicked, "MY FAILURE:\nFailure Details", types.NodeTypeIt, cl0, ForwardedPanic("the panic!")), " {{magenta}}[PANICKED] MY FAILURE:", " Failure Details{{/}}", spr(" {{magenta}}In {{bold}}[It]{{/}}{{magenta}} at: {{bold}}cl0.go:12{{/}} {{gray}}@ %s{{/}}", FORMATTED_TIME), "", " {{magenta}}the panic!{{/}}", "", " {{magenta}}Full Stack Trace{{/}}", " full-trace", " cl-0", "", ), Entry("includes a progress report and an additional failure if any are attached", C(VeryVerbose), AF(types.SpecStateTimedout, "A node timeout occurred:\nTimeout details", types.NodeTypeIt, cl0, PR("A Progress Report Message", types.NodeTypeIt, CurrentNodeText("A Top-Level It"), LeafNodeText("A Top-Level It")), AF(types.SpecStateFailed, "An additional failure", types.NodeTypeIt, cl1)), " {{orange}}[TIMEDOUT] A node timeout occurred:", " Timeout details{{/}}", spr(" {{orange}}In {{bold}}[It]{{/}}{{orange}} at: {{bold}}cl0.go:12{{/}} {{gray}}@ %s{{/}}", FORMATTED_TIME), "", " A Progress Report Message", " {{bold}}{{orange}}A Top-Level It{{/}} (Spec Runtime: 5s)", " {{gray}}cl0.go:12{{/}}", " In {{bold}}{{orange}}[It]{{/}} (Node Runtime: 3s)", " {{gray}}cl1.go:37{{/}}", "", " {{red}}[FAILED] An additional failure{{/}}", spr(" {{red}}In {{bold}}[It]{{/}}{{red}} at: {{bold}}cl1.go:37{{/}} {{gray}}@ %s{{/}}", FORMATTED_TIME), "", ), ) DescribeTable("EmitReportentry", func(conf types.ReporterConfig, reportEntry types.ReportEntry, expected ...any) { reporter := reporters.NewDefaultReporterUnderTest(conf, buf) reporter.EmitReportEntry(reportEntry) Expect(string(buf.Contents())).Should(MatchLines(expected...)) }, // silent when running succinctly or normal Entry("emits nothing when running with succinct verbosity", C(Succinct), RE("my report", cl0), ), Entry("emits nothing when running with normal verbosity", C(), RE("my report", cl0), ), Entry("emits nothing if hte report has VisiblityNever, regardless of verbosity level", C(Verbose), RE("my report", cl0, types.ReportEntryVisibilityNever), ), //emitting reports with no StringRepresentation() Entry("emits the report", C(Verbose), RE("my report", cl0), spr(" {{bold}}my report{{gray}} - cl0.go:12 @ %s{{/}}", FORMATTED_TIME), "", ), //emitting reports with a StringRepresentation() Entry("emits the report along with it's string representation", C(Verbose), RE("my report", cl0, 3), spr(" {{bold}}my report{{gray}} - cl0.go:12 @ %s{{/}}", FORMATTED_TIME), " 3", "", ), Entry("emits the report along with it's string representation (and indents it correctly)", C(Verbose), RE("my report", cl0, "{{yellow}}My awesome report{{/}}\n{{coral}}Is beautiful{{/}}"), spr(" {{bold}}my report{{gray}} - cl0.go:12 @ %s{{/}}", FORMATTED_TIME), " {{yellow}}My awesome report{{/}}", " {{coral}}Is beautiful{{/}}", "", ), //correctly handling reports that have format string components Entry("emits the report without running it through sprintf", C(Verbose), RE("my %f report", cl0, "{{green}}my report http://example.com/?q=%d%3%%{{/}}", cl0), spr(" {{bold}}my %%f report{{gray}} - cl0.go:12 @ %s{{/}}", FORMATTED_TIME), " {{green}}my report http://example.com/?q=%d%3%%{{/}}", "", ), ) DescribeTable("EmitSpecEvent", func(conf types.ReporterConfig, specEvent types.SpecEvent, expected ...any) { reporter := reporters.NewDefaultReporterUnderTest(conf, buf) reporter.EmitSpecEvent(specEvent) Expect(string(buf.Contents())).Should(MatchLines(expected...)) }, // silent when running succinctly or normal Entry("emits nothing when running with succinct verbosity", C(Succinct), SE(types.SpecEventByStart), ), Entry("emits nothing when running with normal verbosity", C(), SE(types.SpecEventByStart), ), Entry("emits nothing when running with normal verbosity and ShowNodeEvents", C(ShowNodeEvents), SE(types.SpecEventByStart), ), Entry("emits nothing when running with normal verbosity and ShowNodeEvents", C(ShowNodeEvents), SE(types.SpecEventByEnd), ), Entry("emits nothing when running with -v and the event is not visible at that verbosity level", C(Verbose), SE(types.SpecEventByEnd), ), Entry("emits the event when running with -v and ShowNodeEvents", C(Verbose|ShowNodeEvents), SE(types.SpecEventByEnd, "hello world", 90*time.Millisecond), spr(" {{bold}}END STEP:{{/}} hello world {{gray}}@ %s (90ms){{/}}", FORMATTED_TIME), "", ), // when running in verbose mode Entry("emits By start events", C(Verbose), SE(types.SpecEventByStart, "hello world", cl0), spr(" {{bold}}STEP:{{/}} hello world {{gray}}@ %s{{/}}", FORMATTED_TIME), "", ), Entry("does not emit By end events", C(Verbose), SE(types.SpecEventByEnd, "hello world", cl0, 89734*time.Microsecond), ), Entry("does not emit node start events", C(Verbose), SE(types.SpecEventNodeStart, "my node", types.NodeTypeIt, cl0), ), Entry("does not emit node end events", C(Verbose), SE(types.SpecEventNodeEnd, "my node", types.NodeTypeIt, cl0, 89734*time.Microsecond), ), Entry("emits spec repeats", C(Verbose), SE(types.SpecEventSpecRepeat, 3), "", spr(" {{bold}}Attempt #3 {{green}}Passed{{/}}{{bold}}. Repeating ↺{{/}} {{gray}}@ %s{{/}}", FORMATTED_TIME), "", "", ), Entry("emits spec retries", C(Verbose), SE(types.SpecEventSpecRetry, 7), "", spr(" {{bold}}Attempt #7 {{red}}Failed{{/}}{{bold}}. Retrying ↺{{/}} {{gray}}@ %s{{/}}", FORMATTED_TIME), "", "", ), // when running in very-verbose mode Entry("emits By start events", C(VeryVerbose), SE(types.SpecEventByStart, "hello world", cl0), spr(" {{bold}}STEP:{{/}} hello world {{gray}}- cl0.go:12 @ %s{{/}}", FORMATTED_TIME), "", ), Entry("emits By end events", C(VeryVerbose), SE(types.SpecEventByEnd, "hello world", cl0, 89734*time.Microsecond), spr(" {{bold}}END STEP:{{/}} hello world {{gray}}- cl0.go:12 @ %s (90ms){{/}}", FORMATTED_TIME), "", ), Entry("emits node start events", C(VeryVerbose), SE(types.SpecEventNodeStart, "my node", types.NodeTypeIt, cl0), spr(" > Enter {{bold}}[It]{{/}} my node {{gray}}- cl0.go:12 @ %s{{/}}", FORMATTED_TIME), "", ), Entry("emits node end events", C(VeryVerbose), SE(types.SpecEventNodeEnd, "my node", types.NodeTypeIt, cl0, 89734*time.Microsecond), spr(" < Exit {{bold}}[It]{{/}} my node {{gray}}- cl0.go:12 @ %s (90ms){{/}}", FORMATTED_TIME), "", ), Entry("emits spec repeats", C(VeryVerbose), SE(types.SpecEventSpecRepeat, 3), "", spr(" {{bold}}Attempt #3 {{green}}Passed{{/}}{{bold}}. Repeating ↺{{/}} {{gray}}@ %s{{/}}", FORMATTED_TIME), "", "", ), Entry("emits spec retries", C(VeryVerbose), SE(types.SpecEventSpecRetry, 7), "", spr(" {{bold}}Attempt #7 {{red}}Failed{{/}}{{bold}}. Retrying ↺{{/}} {{gray}}@ %s{{/}}", FORMATTED_TIME), "", "", ), ) }) golang-github-onsi-ginkgo-v2-2.22.0/reporters/deprecated_reporter.go000066400000000000000000000127761472321612100255050ustar00rootroot00000000000000package reporters import ( "github.com/onsi/ginkgo/v2/config" "github.com/onsi/ginkgo/v2/types" ) // Deprecated: DeprecatedReporter was how Ginkgo V1 provided support for CustomReporters // this has been removed in V2. // Please read the documentation at: // https://onsi.github.io/ginkgo/MIGRATING_TO_V2#removed-custom-reporters // for Ginkgo's new behavior and for a migration path. type DeprecatedReporter interface { SuiteWillBegin(config config.GinkgoConfigType, summary *types.SuiteSummary) BeforeSuiteDidRun(setupSummary *types.SetupSummary) SpecWillRun(specSummary *types.SpecSummary) SpecDidComplete(specSummary *types.SpecSummary) AfterSuiteDidRun(setupSummary *types.SetupSummary) SuiteDidEnd(summary *types.SuiteSummary) } // ReportViaDeprecatedReporter takes a V1 custom reporter and a V2 report and // calls the custom reporter's methods with appropriately transformed data from the V2 report. // // ReportViaDeprecatedReporter should be called in a `ReportAfterSuite()` // // Deprecated: ReportViaDeprecatedReporter method exists to help developer bridge between deprecated V1 functionality and the new // reporting support in V2. It will be removed in a future minor version of Ginkgo. func ReportViaDeprecatedReporter(reporter DeprecatedReporter, report types.Report) { conf := config.DeprecatedGinkgoConfigType{ RandomSeed: report.SuiteConfig.RandomSeed, RandomizeAllSpecs: report.SuiteConfig.RandomizeAllSpecs, FocusStrings: report.SuiteConfig.FocusStrings, SkipStrings: report.SuiteConfig.SkipStrings, FailOnPending: report.SuiteConfig.FailOnPending, FailFast: report.SuiteConfig.FailFast, FlakeAttempts: report.SuiteConfig.FlakeAttempts, EmitSpecProgress: false, DryRun: report.SuiteConfig.DryRun, ParallelNode: report.SuiteConfig.ParallelProcess, ParallelTotal: report.SuiteConfig.ParallelTotal, SyncHost: report.SuiteConfig.ParallelHost, StreamHost: report.SuiteConfig.ParallelHost, } summary := &types.DeprecatedSuiteSummary{ SuiteDescription: report.SuiteDescription, SuiteID: report.SuitePath, NumberOfSpecsBeforeParallelization: report.PreRunStats.TotalSpecs, NumberOfTotalSpecs: report.PreRunStats.TotalSpecs, NumberOfSpecsThatWillBeRun: report.PreRunStats.SpecsThatWillRun, } reporter.SuiteWillBegin(conf, summary) for _, spec := range report.SpecReports { switch spec.LeafNodeType { case types.NodeTypeBeforeSuite, types.NodeTypeSynchronizedBeforeSuite: setupSummary := &types.DeprecatedSetupSummary{ ComponentType: spec.LeafNodeType, CodeLocation: spec.LeafNodeLocation, State: spec.State, RunTime: spec.RunTime, Failure: failureFor(spec), CapturedOutput: spec.CombinedOutput(), SuiteID: report.SuitePath, } reporter.BeforeSuiteDidRun(setupSummary) case types.NodeTypeAfterSuite, types.NodeTypeSynchronizedAfterSuite: setupSummary := &types.DeprecatedSetupSummary{ ComponentType: spec.LeafNodeType, CodeLocation: spec.LeafNodeLocation, State: spec.State, RunTime: spec.RunTime, Failure: failureFor(spec), CapturedOutput: spec.CombinedOutput(), SuiteID: report.SuitePath, } reporter.AfterSuiteDidRun(setupSummary) case types.NodeTypeIt: componentTexts, componentCodeLocations := []string{}, []types.CodeLocation{} componentTexts = append(componentTexts, spec.ContainerHierarchyTexts...) componentCodeLocations = append(componentCodeLocations, spec.ContainerHierarchyLocations...) componentTexts = append(componentTexts, spec.LeafNodeText) componentCodeLocations = append(componentCodeLocations, spec.LeafNodeLocation) specSummary := &types.DeprecatedSpecSummary{ ComponentTexts: componentTexts, ComponentCodeLocations: componentCodeLocations, State: spec.State, RunTime: spec.RunTime, Failure: failureFor(spec), NumberOfSamples: spec.NumAttempts, CapturedOutput: spec.CombinedOutput(), SuiteID: report.SuitePath, } reporter.SpecWillRun(specSummary) reporter.SpecDidComplete(specSummary) switch spec.State { case types.SpecStatePending: summary.NumberOfPendingSpecs += 1 case types.SpecStateSkipped: summary.NumberOfSkippedSpecs += 1 case types.SpecStateFailed, types.SpecStatePanicked, types.SpecStateInterrupted: summary.NumberOfFailedSpecs += 1 case types.SpecStatePassed: summary.NumberOfPassedSpecs += 1 if spec.NumAttempts > 1 { summary.NumberOfFlakedSpecs += 1 } } } } summary.SuiteSucceeded = report.SuiteSucceeded summary.RunTime = report.RunTime reporter.SuiteDidEnd(summary) } func failureFor(spec types.SpecReport) types.DeprecatedSpecFailure { if spec.Failure.IsZero() { return types.DeprecatedSpecFailure{} } index := 0 switch spec.Failure.FailureNodeContext { case types.FailureNodeInContainer: index = spec.Failure.FailureNodeContainerIndex case types.FailureNodeAtTopLevel: index = -1 case types.FailureNodeIsLeafNode: index = len(spec.ContainerHierarchyTexts) - 1 if spec.LeafNodeText != "" { index += 1 } } return types.DeprecatedSpecFailure{ Message: spec.Failure.Message, Location: spec.Failure.Location, ForwardedPanic: spec.Failure.ForwardedPanic, ComponentIndex: index, ComponentType: spec.Failure.FailureNodeType, ComponentCodeLocation: spec.Failure.FailureNodeLocation, } } golang-github-onsi-ginkgo-v2-2.22.0/reporters/deprecated_reporter_test.go000066400000000000000000000270411472321612100265330ustar00rootroot00000000000000package reporters_test import ( "time" . "github.com/onsi/ginkgo/v2" "github.com/onsi/ginkgo/v2/config" "github.com/onsi/ginkgo/v2/reporters" "github.com/onsi/ginkgo/v2/types" . "github.com/onsi/gomega" ) type deprecatedReporter struct { config config.GinkgoConfigType begin types.SuiteSummary beforeSuite types.SetupSummary will []types.SpecSummary did []types.SpecSummary afterSuite types.SetupSummary end types.SuiteSummary } func (dr *deprecatedReporter) SuiteWillBegin(config config.GinkgoConfigType, summary *types.SuiteSummary) { dr.config = config dr.begin = *summary } func (dr *deprecatedReporter) BeforeSuiteDidRun(setupSummary *types.SetupSummary) { dr.beforeSuite = *setupSummary } func (dr *deprecatedReporter) SpecWillRun(specSummary *types.SpecSummary) { dr.will = append(dr.will, *specSummary) } func (dr *deprecatedReporter) SpecDidComplete(specSummary *types.SpecSummary) { dr.did = append(dr.did, *specSummary) } func (dr *deprecatedReporter) AfterSuiteDidRun(setupSummary *types.SetupSummary) { dr.afterSuite = *setupSummary } func (dr *deprecatedReporter) SuiteDidEnd(summary *types.SuiteSummary) { dr.end = *summary } var _ = Describe("DeprecatedReporter", func() { var report types.Report var reporter *deprecatedReporter BeforeEach(func() { reporter = &deprecatedReporter{} report = types.Report{ SuiteDescription: "suite-description", SuitePath: "suite-path", SuiteSucceeded: false, PreRunStats: types.PreRunStats{ TotalSpecs: 10, SpecsThatWillRun: 9, }, RunTime: time.Minute, SuiteConfig: types.SuiteConfig{ RandomSeed: 17, }, SpecReports: types.SpecReports{ types.SpecReport{ LeafNodeType: types.NodeTypeBeforeSuite, LeafNodeLocation: cl0, RunTime: time.Second, CapturedGinkgoWriterOutput: "gw", CapturedStdOutErr: "std", State: types.SpecStatePassed, }, types.SpecReport{ ContainerHierarchyTexts: []string{"A", "B"}, ContainerHierarchyLocations: []types.CodeLocation{cl0, cl1}, LeafNodeType: types.NodeTypeIt, LeafNodeLocation: cl2, LeafNodeText: "it", RunTime: time.Second, CapturedGinkgoWriterOutput: "gw", CapturedStdOutErr: "std", State: types.SpecStatePassed, NumAttempts: 1, }, types.SpecReport{ ContainerHierarchyTexts: []string{"A", "B"}, ContainerHierarchyLocations: []types.CodeLocation{cl0, cl1}, LeafNodeType: types.NodeTypeIt, LeafNodeLocation: cl2, LeafNodeText: "it", RunTime: time.Second, CapturedGinkgoWriterOutput: "gw", CapturedStdOutErr: "std", State: types.SpecStatePassed, NumAttempts: 2, }, types.SpecReport{ ContainerHierarchyTexts: []string{"A", "B"}, ContainerHierarchyLocations: []types.CodeLocation{cl0, cl1}, LeafNodeType: types.NodeTypeIt, LeafNodeLocation: cl2, LeafNodeText: "it", RunTime: time.Second, CapturedGinkgoWriterOutput: "gw", CapturedStdOutErr: "std", State: types.SpecStatePending, NumAttempts: 0, }, types.SpecReport{ ContainerHierarchyTexts: []string{"A", "B"}, ContainerHierarchyLocations: []types.CodeLocation{cl0, cl1}, LeafNodeType: types.NodeTypeIt, LeafNodeLocation: cl2, LeafNodeText: "it", RunTime: time.Second, CapturedGinkgoWriterOutput: "gw", CapturedStdOutErr: "std", State: types.SpecStateSkipped, NumAttempts: 1, Failure: types.Failure{ Message: "skipped by user in a before each", Location: cl3, FailureNodeContext: types.FailureNodeInContainer, FailureNodeContainerIndex: 1, FailureNodeLocation: cl4, FailureNodeType: types.NodeTypeBeforeEach, }, }, types.SpecReport{ ContainerHierarchyTexts: []string{"A", "B"}, ContainerHierarchyLocations: []types.CodeLocation{cl0, cl1}, LeafNodeType: types.NodeTypeIt, LeafNodeLocation: cl2, LeafNodeText: "it", RunTime: time.Second, CapturedGinkgoWriterOutput: "gw", CapturedStdOutErr: "std", NumAttempts: 3, State: types.SpecStateFailed, Failure: types.Failure{ Message: "failed in the it", Location: cl3, FailureNodeContext: types.FailureNodeIsLeafNode, FailureNodeLocation: cl2, FailureNodeType: types.NodeTypeIt, }, }, types.SpecReport{ ContainerHierarchyTexts: []string{"A", "B"}, ContainerHierarchyLocations: []types.CodeLocation{cl0, cl1}, LeafNodeType: types.NodeTypeIt, LeafNodeLocation: cl2, LeafNodeText: "it", RunTime: time.Second, CapturedGinkgoWriterOutput: "gw", CapturedStdOutErr: "std", NumAttempts: 3, State: types.SpecStatePanicked, Failure: types.Failure{ Message: "panicked in a top-level just before each", Location: cl3, ForwardedPanic: "bam!", FailureNodeContext: types.FailureNodeAtTopLevel, FailureNodeLocation: cl4, FailureNodeType: types.NodeTypeJustBeforeEach, }, }, types.SpecReport{ LeafNodeType: types.NodeTypeAfterSuite, LeafNodeLocation: cl0, RunTime: time.Second, State: types.SpecStateFailed, Failure: types.Failure{ Message: "failure-message", Location: cl1, FailureNodeContext: types.FailureNodeIsLeafNode, FailureNodeType: types.NodeTypeAfterSuite, FailureNodeLocation: cl0, }, CapturedGinkgoWriterOutput: "gw", CapturedStdOutErr: "std", }, types.SpecReport{ LeafNodeText: "report", LeafNodeType: types.NodeTypeReportAfterSuite, LeafNodeLocation: cl0, RunTime: time.Second, CapturedGinkgoWriterOutput: "gw", CapturedStdOutErr: "std", }, }, } reporters.ReportViaDeprecatedReporter(reporter, report) }) It("submits a SuiteWillBegin report with config and suite", func() { Ω(reporter.config.RandomSeed).Should(Equal(int64(17))) Ω(reporter.begin.NumberOfTotalSpecs).Should(Equal(10)) Ω(reporter.begin.NumberOfSpecsBeforeParallelization).Should(Equal(10)) Ω(reporter.begin.NumberOfSpecsThatWillBeRun).Should(Equal(9)) Ω(reporter.begin.SuiteID).Should(Equal("suite-path")) Ω(reporter.begin.SuiteDescription).Should(Equal("suite-description")) }) It("submits reports for BeforeSuite", func() { Ω(reporter.beforeSuite).Should(Equal(types.DeprecatedSetupSummary{ ComponentType: types.SpecComponentTypeBeforeSuite, CodeLocation: cl0, State: types.SpecStatePassed, RunTime: time.Second, Failure: types.DeprecatedSpecFailure{}, CapturedOutput: "std\ngw", SuiteID: "suite-path", })) }) It("submits reports for each spec", func() { Ω(reporter.will).Should(HaveLen(6)) Ω(reporter.did).Should(HaveLen(6)) Ω(reporter.did[0]).Should(Equal(types.DeprecatedSpecSummary{ ComponentTexts: []string{"A", "B", "it"}, ComponentCodeLocations: []types.CodeLocation{cl0, cl1, cl2}, State: types.SpecStatePassed, RunTime: time.Second, Failure: types.DeprecatedSpecFailure{}, NumberOfSamples: 1, CapturedOutput: "std\ngw", SuiteID: "suite-path", })) Ω(reporter.did[1]).Should(Equal(types.DeprecatedSpecSummary{ ComponentTexts: []string{"A", "B", "it"}, ComponentCodeLocations: []types.CodeLocation{cl0, cl1, cl2}, State: types.SpecStatePassed, RunTime: time.Second, Failure: types.DeprecatedSpecFailure{}, NumberOfSamples: 2, CapturedOutput: "std\ngw", SuiteID: "suite-path", })) Ω(reporter.did[2]).Should(Equal(types.DeprecatedSpecSummary{ ComponentTexts: []string{"A", "B", "it"}, ComponentCodeLocations: []types.CodeLocation{cl0, cl1, cl2}, State: types.SpecStatePending, RunTime: time.Second, Failure: types.DeprecatedSpecFailure{}, NumberOfSamples: 0, CapturedOutput: "std\ngw", SuiteID: "suite-path", })) Ω(reporter.did[3]).Should(Equal(types.DeprecatedSpecSummary{ ComponentTexts: []string{"A", "B", "it"}, ComponentCodeLocations: []types.CodeLocation{cl0, cl1, cl2}, State: types.SpecStateSkipped, RunTime: time.Second, Failure: types.DeprecatedSpecFailure{ Message: "skipped by user in a before each", Location: cl3, ForwardedPanic: "", ComponentIndex: 1, ComponentCodeLocation: cl4, ComponentType: types.SpecComponentTypeBeforeEach, }, NumberOfSamples: 1, CapturedOutput: "std\ngw", SuiteID: "suite-path", })) Ω(reporter.did[4]).Should(Equal(types.DeprecatedSpecSummary{ ComponentTexts: []string{"A", "B", "it"}, ComponentCodeLocations: []types.CodeLocation{cl0, cl1, cl2}, State: types.SpecStateFailed, RunTime: time.Second, Failure: types.DeprecatedSpecFailure{ Message: "failed in the it", Location: cl3, ForwardedPanic: "", ComponentIndex: 2, ComponentCodeLocation: cl2, ComponentType: types.SpecComponentTypeIt, }, NumberOfSamples: 3, CapturedOutput: "std\ngw", SuiteID: "suite-path", })) Ω(reporter.did[5]).Should(Equal(types.DeprecatedSpecSummary{ ComponentTexts: []string{"A", "B", "it"}, ComponentCodeLocations: []types.CodeLocation{cl0, cl1, cl2}, State: types.SpecStatePanicked, RunTime: time.Second, Failure: types.DeprecatedSpecFailure{ Message: "panicked in a top-level just before each", Location: cl3, ForwardedPanic: "bam!", ComponentIndex: -1, ComponentCodeLocation: cl4, ComponentType: types.SpecComponentTypeJustBeforeEach, }, NumberOfSamples: 3, CapturedOutput: "std\ngw", SuiteID: "suite-path", })) }) It("submits reports for AfterSuite", func() { Ω(reporter.afterSuite).Should(Equal(types.DeprecatedSetupSummary{ ComponentType: types.SpecComponentTypeAfterSuite, CodeLocation: cl0, State: types.SpecStateFailed, RunTime: time.Second, Failure: types.DeprecatedSpecFailure{ Message: "failure-message", Location: cl1, ComponentIndex: -1, ComponentType: types.SpecComponentTypeAfterSuite, ComponentCodeLocation: cl0, }, CapturedOutput: "std\ngw", SuiteID: "suite-path", })) }) It("reports the end of the suite", func() { Ω(reporter.end.RunTime).Should(Equal(time.Minute)) Ω(reporter.end.SuiteSucceeded).Should(BeFalse()) }) }) golang-github-onsi-ginkgo-v2-2.22.0/reporters/json_report.go000066400000000000000000000033441472321612100240160ustar00rootroot00000000000000package reporters import ( "encoding/json" "fmt" "os" "path" "github.com/onsi/ginkgo/v2/types" ) // GenerateJSONReport produces a JSON-formatted report at the passed in destination func GenerateJSONReport(report types.Report, destination string) error { if err := os.MkdirAll(path.Dir(destination), 0770); err != nil { return err } f, err := os.Create(destination) if err != nil { return err } defer f.Close() enc := json.NewEncoder(f) enc.SetIndent("", " ") err = enc.Encode([]types.Report{ report, }) if err != nil { return err } return nil } // MergeJSONReports produces a single JSON-formatted report at the passed in destination by merging the JSON-formatted reports provided in sources // It skips over reports that fail to decode but reports on them via the returned messages []string func MergeAndCleanupJSONReports(sources []string, destination string) ([]string, error) { messages := []string{} allReports := []types.Report{} for _, source := range sources { reports := []types.Report{} data, err := os.ReadFile(source) if err != nil { messages = append(messages, fmt.Sprintf("Could not open %s:\n%s", source, err.Error())) continue } err = json.Unmarshal(data, &reports) if err != nil { messages = append(messages, fmt.Sprintf("Could not decode %s:\n%s", source, err.Error())) continue } os.Remove(source) allReports = append(allReports, reports...) } if err := os.MkdirAll(path.Dir(destination), 0770); err != nil { return messages, err } f, err := os.Create(destination) if err != nil { return messages, err } defer f.Close() enc := json.NewEncoder(f) enc.SetIndent("", " ") err = enc.Encode(allReports) if err != nil { return messages, err } return messages, nil } golang-github-onsi-ginkgo-v2-2.22.0/reporters/json_report_test.go000066400000000000000000000074671472321612100250670ustar00rootroot00000000000000package reporters_test import ( "fmt" "os" "path/filepath" "time" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" "github.com/onsi/ginkgo/v2/reporters" "github.com/onsi/ginkgo/v2/types" ) var _ = Describe("JSONReport", func() { var report types.Report BeforeEach(func() { report = types.Report{ SuiteDescription: "My Suite", SuitePath: "/path/to/suite", PreRunStats: types.PreRunStats{SpecsThatWillRun: 15, TotalSpecs: 20}, SuiteConfig: types.SuiteConfig{RandomSeed: 17, ParallelTotal: 1}, RunTime: time.Minute, SpecReports: types.SpecReports{ S(types.NodeTypeIt, Label("cat", "dog"), CLabels(Label("dolphin"), Label("gorilla", "cow")), CTS("A", "B"), CLS(cl0, cl1), "C", cl2, types.SpecStateTimedout, STD("some captured stdout\n"), GW("ginkgowriter\noutput\ncleanup!"), SE(types.SpecEventByStart, "a by step", cl0), SE(types.SpecEventNodeStart, types.NodeTypeIt, "C", cl2, TL(0)), F("failure\nmessage", cl3, types.FailureNodeIsLeafNode, FailureNodeLocation(cl2), types.NodeTypeIt, TL("ginkgowriter\n"), AF(types.SpecStatePanicked, cl4, types.FailureNodeIsLeafNode, FailureNodeLocation(cl2), types.NodeTypeIt, TL("ginkgowriter\noutput\n"), ForwardedPanic("the panic!"))), SE(types.SpecEventNodeEnd, types.NodeTypeIt, "C", cl2, TL("ginkgowriter\noutput\n"), time.Microsecond*87230), RE("a report entry", cl1, TL("ginkgowriter\noutput\n")), RE("a hidden report entry", cl1, TL("ginkgowriter\noutput\n"), types.ReportEntryVisibilityNever), AF(types.SpecStateFailed, "a subsequent failure", types.FailureNodeInContainer, FailureNodeLocation(cl3), types.NodeTypeAfterEach, 0, TL("ginkgowriter\noutput\ncleanup!")), ), S(types.NodeTypeIt, "A", cl0, STD("some captured stdout\n"), GW("some GinkgoWriter\noutput is interspersed\nhere and there\n"), SE(types.SpecEventNodeStart, types.NodeTypeIt, "A", cl0), PR("my progress report", LeafNodeText("A"), TL("some GinkgoWriter\n")), SE(types.SpecEventByStart, "My Step", cl1, TL("some GinkgoWriter\n")), RE("my entry", cl1, types.ReportEntryVisibilityFailureOrVerbose, TL("some GinkgoWriter\noutput is interspersed\n")), RE("my hidden entry", cl1, types.ReportEntryVisibilityNever, TL("some GinkgoWriter\noutput is interspersed\n")), SE(types.SpecEventByEnd, "My Step", cl1, time.Millisecond*200, TL("some GinkgoWriter\noutput is interspersed\n")), SE(types.SpecEventNodeEnd, types.NodeTypeIt, "A", cl0, time.Millisecond*300, TL("some GinkgoWriter\noutput is interspersed\nhere and there\n")), ), S(types.NodeTypeIt, "A", cl0, types.SpecStatePending), S(types.NodeTypeIt, "A", cl0, types.SpecStatePanicked, STD("some captured stdout\n"), SE(types.SpecEventNodeStart, types.NodeTypeIt, "A", cl0), F("failure\nmessage", cl1, types.FailureNodeIsLeafNode, FailureNodeLocation(cl0), types.NodeTypeIt, ForwardedPanic("the panic")), SE(types.SpecEventNodeEnd, types.NodeTypeIt, "A", cl0, time.Millisecond*300, TL("some GinkgoWriter\noutput is interspersed\nhere and there\n")), ), S(types.NodeTypeBeforeSuite, "A", cl0, types.SpecStatePassed), }, } }) Describe("when configured to write the report inside a folder", func() { var folderPath string var filePath string BeforeEach(func() { folderPath = filepath.Join(fmt.Sprintf("test_outputs_%d", GinkgoParallelProcess())) fileName := fmt.Sprintf("report-%d", GinkgoParallelProcess()) filePath = filepath.Join(folderPath, fileName) Ω(reporters.GenerateJSONReport(report, filePath)).Should(Succeed()) DeferCleanup(os.RemoveAll, folderPath) }) It("creates the folder and the report file", func() { _, err := os.Stat(folderPath) Ω(err).Should(Succeed(), "Parent folder should be created") _, err = os.Stat(filePath) Ω(err).Should(Succeed(), "Report file should be created") }) }) }) golang-github-onsi-ginkgo-v2-2.22.0/reporters/junit_report.go000066400000000000000000000334021472321612100241740ustar00rootroot00000000000000/* JUnit XML Reporter for Ginkgo For usage instructions: http://onsi.github.io/ginkgo/#generating_junit_xml_output The schema used for the generated JUnit xml file was adapted from https://llg.cubic.org/docs/junit/ */ package reporters import ( "encoding/xml" "fmt" "os" "path" "regexp" "strings" "github.com/onsi/ginkgo/v2/config" "github.com/onsi/ginkgo/v2/types" ) type JunitReportConfig struct { // Spec States for which no timeline should be emitted for system-err // set this to types.SpecStatePassed|types.SpecStateSkipped|types.SpecStatePending to only match failing specs OmitTimelinesForSpecState types.SpecState // Enable OmitFailureMessageAttr to prevent failure messages appearing in the "message" attribute of the Failure and Error tags OmitFailureMessageAttr bool //Enable OmitCapturedStdOutErr to prevent captured stdout/stderr appearing in system-out OmitCapturedStdOutErr bool // Enable OmitSpecLabels to prevent labels from appearing in the spec name OmitSpecLabels bool // Enable OmitLeafNodeType to prevent the spec leaf node type from appearing in the spec name OmitLeafNodeType bool // Enable OmitSuiteSetupNodes to prevent the creation of testcase entries for setup nodes OmitSuiteSetupNodes bool } type JUnitTestSuites struct { XMLName xml.Name `xml:"testsuites"` // Tests maps onto the total number of specs in all test suites (this includes any suite nodes such as BeforeSuite) Tests int `xml:"tests,attr"` // Disabled maps onto specs that are pending and/or skipped Disabled int `xml:"disabled,attr"` // Errors maps onto specs that panicked or were interrupted Errors int `xml:"errors,attr"` // Failures maps onto specs that failed Failures int `xml:"failures,attr"` // Time is the time in seconds to execute all test suites Time float64 `xml:"time,attr"` //The set of all test suites TestSuites []JUnitTestSuite `xml:"testsuite"` } type JUnitTestSuite struct { // Name maps onto the description of the test suite - maps onto Report.SuiteDescription Name string `xml:"name,attr"` // Package maps onto the absolute path to the test suite - maps onto Report.SuitePath Package string `xml:"package,attr"` // Tests maps onto the total number of specs in the test suite (this includes any suite nodes such as BeforeSuite) Tests int `xml:"tests,attr"` // Disabled maps onto specs that are pending Disabled int `xml:"disabled,attr"` // Skiped maps onto specs that are skipped Skipped int `xml:"skipped,attr"` // Errors maps onto specs that panicked or were interrupted Errors int `xml:"errors,attr"` // Failures maps onto specs that failed Failures int `xml:"failures,attr"` // Time is the time in seconds to execute all the test suite - maps onto Report.RunTime Time float64 `xml:"time,attr"` // Timestamp is the ISO 8601 formatted start-time of the suite - maps onto Report.StartTime Timestamp string `xml:"timestamp,attr"` //Properties captures the information stored in the rest of the Report type (including SuiteConfig) as key-value pairs Properties JUnitProperties `xml:"properties"` //TestCases capture the individual specs TestCases []JUnitTestCase `xml:"testcase"` } type JUnitProperties struct { Properties []JUnitProperty `xml:"property"` } func (jup JUnitProperties) WithName(name string) string { for _, property := range jup.Properties { if property.Name == name { return property.Value } } return "" } type JUnitProperty struct { Name string `xml:"name,attr"` Value string `xml:"value,attr"` } var ownerRE = regexp.MustCompile(`(?i)^owner:(.*)$`) type JUnitTestCase struct { // Name maps onto the full text of the spec - equivalent to "[SpecReport.LeafNodeType] SpecReport.FullText()" Name string `xml:"name,attr"` // Classname maps onto the name of the test suite - equivalent to Report.SuiteDescription Classname string `xml:"classname,attr"` // Status maps onto the string representation of SpecReport.State Status string `xml:"status,attr"` // Time is the time in seconds to execute the spec - maps onto SpecReport.RunTime Time float64 `xml:"time,attr"` // Owner is the owner the spec - is set if a label matching Label("owner:X") is provided. The last matching label is used as the owner, thereby allowing specs to override owners specified in container nodes. Owner string `xml:"owner,attr,omitempty"` //Skipped is populated with a message if the test was skipped or pending Skipped *JUnitSkipped `xml:"skipped,omitempty"` //Error is populated if the test panicked or was interrupted Error *JUnitError `xml:"error,omitempty"` //Failure is populated if the test failed Failure *JUnitFailure `xml:"failure,omitempty"` //SystemOut maps onto any captured stdout/stderr output - maps onto SpecReport.CapturedStdOutErr SystemOut string `xml:"system-out,omitempty"` //SystemOut maps onto any captured GinkgoWriter output - maps onto SpecReport.CapturedGinkgoWriterOutput SystemErr string `xml:"system-err,omitempty"` } type JUnitSkipped struct { // Message maps onto "pending" if the test was marked pending, "skipped" if the test was marked skipped, and "skipped - REASON" if the user called Skip(REASON) Message string `xml:"message,attr"` } type JUnitError struct { //Message maps onto the panic/exception thrown - equivalent to SpecReport.Failure.ForwardedPanic - or to "interrupted" Message string `xml:"message,attr"` //Type is one of "panicked" or "interrupted" Type string `xml:"type,attr"` //Description maps onto the captured stack trace for a panic, or the failure message for an interrupt which will include the dump of running goroutines Description string `xml:",chardata"` } type JUnitFailure struct { //Message maps onto the failure message - equivalent to SpecReport.Failure.Message Message string `xml:"message,attr"` //Type is "failed" Type string `xml:"type,attr"` //Description maps onto the location and stack trace of the failure Description string `xml:",chardata"` } func GenerateJUnitReport(report types.Report, dst string) error { return GenerateJUnitReportWithConfig(report, dst, JunitReportConfig{}) } func GenerateJUnitReportWithConfig(report types.Report, dst string, config JunitReportConfig) error { suite := JUnitTestSuite{ Name: report.SuiteDescription, Package: report.SuitePath, Time: report.RunTime.Seconds(), Timestamp: report.StartTime.Format("2006-01-02T15:04:05"), Properties: JUnitProperties{ Properties: []JUnitProperty{ {"SuiteSucceeded", fmt.Sprintf("%t", report.SuiteSucceeded)}, {"SuiteHasProgrammaticFocus", fmt.Sprintf("%t", report.SuiteHasProgrammaticFocus)}, {"SpecialSuiteFailureReason", strings.Join(report.SpecialSuiteFailureReasons, ",")}, {"SuiteLabels", fmt.Sprintf("[%s]", strings.Join(report.SuiteLabels, ","))}, {"RandomSeed", fmt.Sprintf("%d", report.SuiteConfig.RandomSeed)}, {"RandomizeAllSpecs", fmt.Sprintf("%t", report.SuiteConfig.RandomizeAllSpecs)}, {"LabelFilter", report.SuiteConfig.LabelFilter}, {"FocusStrings", strings.Join(report.SuiteConfig.FocusStrings, ",")}, {"SkipStrings", strings.Join(report.SuiteConfig.SkipStrings, ",")}, {"FocusFiles", strings.Join(report.SuiteConfig.FocusFiles, ";")}, {"SkipFiles", strings.Join(report.SuiteConfig.SkipFiles, ";")}, {"FailOnPending", fmt.Sprintf("%t", report.SuiteConfig.FailOnPending)}, {"FailOnEmpty", fmt.Sprintf("%t", report.SuiteConfig.FailOnEmpty)}, {"FailFast", fmt.Sprintf("%t", report.SuiteConfig.FailFast)}, {"FlakeAttempts", fmt.Sprintf("%d", report.SuiteConfig.FlakeAttempts)}, {"DryRun", fmt.Sprintf("%t", report.SuiteConfig.DryRun)}, {"ParallelTotal", fmt.Sprintf("%d", report.SuiteConfig.ParallelTotal)}, {"OutputInterceptorMode", report.SuiteConfig.OutputInterceptorMode}, }, }, } for _, spec := range report.SpecReports { if config.OmitSuiteSetupNodes && spec.LeafNodeType != types.NodeTypeIt { continue } name := fmt.Sprintf("[%s]", spec.LeafNodeType) if config.OmitLeafNodeType { name = "" } if spec.FullText() != "" { name = name + " " + spec.FullText() } labels := spec.Labels() if len(labels) > 0 && !config.OmitSpecLabels { name = name + " [" + strings.Join(labels, ", ") + "]" } owner := "" for _, label := range labels { if matches := ownerRE.FindStringSubmatch(label); len(matches) == 2 { owner = matches[1] } } name = strings.TrimSpace(name) test := JUnitTestCase{ Name: name, Classname: report.SuiteDescription, Status: spec.State.String(), Time: spec.RunTime.Seconds(), Owner: owner, } if !spec.State.Is(config.OmitTimelinesForSpecState) { test.SystemErr = systemErrForUnstructuredReporters(spec) } if !config.OmitCapturedStdOutErr { test.SystemOut = systemOutForUnstructuredReporters(spec) } suite.Tests += 1 switch spec.State { case types.SpecStateSkipped: message := "skipped" if spec.Failure.Message != "" { message += " - " + spec.Failure.Message } test.Skipped = &JUnitSkipped{Message: message} suite.Skipped += 1 case types.SpecStatePending: test.Skipped = &JUnitSkipped{Message: "pending"} suite.Disabled += 1 case types.SpecStateFailed: test.Failure = &JUnitFailure{ Message: spec.Failure.Message, Type: "failed", Description: failureDescriptionForUnstructuredReporters(spec), } if config.OmitFailureMessageAttr { test.Failure.Message = "" } suite.Failures += 1 case types.SpecStateTimedout: test.Failure = &JUnitFailure{ Message: spec.Failure.Message, Type: "timedout", Description: failureDescriptionForUnstructuredReporters(spec), } if config.OmitFailureMessageAttr { test.Failure.Message = "" } suite.Failures += 1 case types.SpecStateInterrupted: test.Error = &JUnitError{ Message: spec.Failure.Message, Type: "interrupted", Description: failureDescriptionForUnstructuredReporters(spec), } if config.OmitFailureMessageAttr { test.Error.Message = "" } suite.Errors += 1 case types.SpecStateAborted: test.Failure = &JUnitFailure{ Message: spec.Failure.Message, Type: "aborted", Description: failureDescriptionForUnstructuredReporters(spec), } if config.OmitFailureMessageAttr { test.Failure.Message = "" } suite.Errors += 1 case types.SpecStatePanicked: test.Error = &JUnitError{ Message: spec.Failure.ForwardedPanic, Type: "panicked", Description: failureDescriptionForUnstructuredReporters(spec), } if config.OmitFailureMessageAttr { test.Error.Message = "" } suite.Errors += 1 } suite.TestCases = append(suite.TestCases, test) } junitReport := JUnitTestSuites{ Tests: suite.Tests, Disabled: suite.Disabled + suite.Skipped, Errors: suite.Errors, Failures: suite.Failures, Time: suite.Time, TestSuites: []JUnitTestSuite{suite}, } if err := os.MkdirAll(path.Dir(dst), 0770); err != nil { return err } f, err := os.Create(dst) if err != nil { return err } f.WriteString(xml.Header) encoder := xml.NewEncoder(f) encoder.Indent(" ", " ") encoder.Encode(junitReport) return f.Close() } func MergeAndCleanupJUnitReports(sources []string, dst string) ([]string, error) { messages := []string{} mergedReport := JUnitTestSuites{} for _, source := range sources { report := JUnitTestSuites{} f, err := os.Open(source) if err != nil { messages = append(messages, fmt.Sprintf("Could not open %s:\n%s", source, err.Error())) continue } err = xml.NewDecoder(f).Decode(&report) _ = f.Close() if err != nil { messages = append(messages, fmt.Sprintf("Could not decode %s:\n%s", source, err.Error())) continue } os.Remove(source) mergedReport.Tests += report.Tests mergedReport.Disabled += report.Disabled mergedReport.Errors += report.Errors mergedReport.Failures += report.Failures mergedReport.Time += report.Time mergedReport.TestSuites = append(mergedReport.TestSuites, report.TestSuites...) } if err := os.MkdirAll(path.Dir(dst), 0770); err != nil { return messages, err } f, err := os.Create(dst) if err != nil { return messages, err } f.WriteString(xml.Header) encoder := xml.NewEncoder(f) encoder.Indent(" ", " ") encoder.Encode(mergedReport) return messages, f.Close() } func failureDescriptionForUnstructuredReporters(spec types.SpecReport) string { out := &strings.Builder{} NewDefaultReporter(types.ReporterConfig{NoColor: true, VeryVerbose: true}, out).emitFailure(0, spec.State, spec.Failure, true) if len(spec.AdditionalFailures) > 0 { out.WriteString("\nThere were additional failures detected after the initial failure. These are visible in the timeline\n") } return out.String() } func systemErrForUnstructuredReporters(spec types.SpecReport) string { return RenderTimeline(spec, true) } func RenderTimeline(spec types.SpecReport, noColor bool) string { out := &strings.Builder{} NewDefaultReporter(types.ReporterConfig{NoColor: noColor, VeryVerbose: true}, out).emitTimeline(0, spec, spec.Timeline()) return out.String() } func systemOutForUnstructuredReporters(spec types.SpecReport) string { return spec.CapturedStdOutErr } // Deprecated JUnitReporter (so folks can still compile their suites) type JUnitReporter struct{} func NewJUnitReporter(_ string) *JUnitReporter { return &JUnitReporter{} } func (reporter *JUnitReporter) SuiteWillBegin(_ config.GinkgoConfigType, _ *types.SuiteSummary) {} func (reporter *JUnitReporter) BeforeSuiteDidRun(_ *types.SetupSummary) {} func (reporter *JUnitReporter) SpecWillRun(_ *types.SpecSummary) {} func (reporter *JUnitReporter) SpecDidComplete(_ *types.SpecSummary) {} func (reporter *JUnitReporter) AfterSuiteDidRun(_ *types.SetupSummary) {} func (reporter *JUnitReporter) SuiteDidEnd(_ *types.SuiteSummary) {} golang-github-onsi-ginkgo-v2-2.22.0/reporters/junit_report_test.go000066400000000000000000000361561472321612100252440ustar00rootroot00000000000000package reporters_test import ( "encoding/xml" "fmt" "os" "path/filepath" "time" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" "github.com/onsi/ginkgo/v2/reporters" "github.com/onsi/ginkgo/v2/types" ) var _ = Describe("JunitReport", func() { var report types.Report BeforeEach(func() { report = types.Report{ SuiteDescription: "My Suite", SuitePath: "/path/to/suite", PreRunStats: types.PreRunStats{SpecsThatWillRun: 15, TotalSpecs: 20}, SuiteConfig: types.SuiteConfig{RandomSeed: 17, ParallelTotal: 1}, RunTime: time.Minute, SpecReports: types.SpecReports{ S(types.NodeTypeIt, Label("cat", "dog"), CLabels(Label("dolphin"), Label("gorilla", "cow")), CTS("A", "B"), CLS(cl0, cl1), "C", cl2, types.SpecStateTimedout, STD("some captured stdout\n"), GW("ginkgowriter\noutput\ncleanup!"), SE(types.SpecEventByStart, "a by step", cl0), SE(types.SpecEventNodeStart, types.NodeTypeIt, "C", cl2, TL(0)), F("failure\nmessage", cl3, types.FailureNodeIsLeafNode, FailureNodeLocation(cl2), types.NodeTypeIt, TL("ginkgowriter\n"), AF(types.SpecStatePanicked, cl4, types.FailureNodeIsLeafNode, FailureNodeLocation(cl2), types.NodeTypeIt, TL("ginkgowriter\noutput\n"), ForwardedPanic("the panic!"))), SE(types.SpecEventNodeEnd, types.NodeTypeIt, "C", cl2, TL("ginkgowriter\noutput\n"), time.Microsecond*87230), RE("a report entry", cl1, TL("ginkgowriter\noutput\n")), RE("a hidden report entry", cl1, TL("ginkgowriter\noutput\n"), types.ReportEntryVisibilityNever), AF(types.SpecStateFailed, "a subsequent failure", types.FailureNodeInContainer, FailureNodeLocation(cl3), types.NodeTypeAfterEach, 0, TL("ginkgowriter\noutput\ncleanup!")), ), S(types.NodeTypeIt, "A", cl0, STD("some captured stdout\n"), GW("some GinkgoWriter\noutput is interspersed\nhere and there\n"), Label("cat", "owner:frank", "OWNer:bob"), SE(types.SpecEventNodeStart, types.NodeTypeIt, "A", cl0), PR("my progress report", LeafNodeText("A"), TL("some GinkgoWriter\n")), SE(types.SpecEventByStart, "My Step", cl1, TL("some GinkgoWriter\n")), RE("my entry", cl1, types.ReportEntryVisibilityFailureOrVerbose, TL("some GinkgoWriter\noutput is interspersed\n")), RE("my hidden entry", cl1, types.ReportEntryVisibilityNever, TL("some GinkgoWriter\noutput is interspersed\n")), SE(types.SpecEventByEnd, "My Step", cl1, time.Millisecond*200, TL("some GinkgoWriter\noutput is interspersed\n")), SE(types.SpecEventNodeEnd, types.NodeTypeIt, "A", cl0, time.Millisecond*300, TL("some GinkgoWriter\noutput is interspersed\nhere and there\n")), ), S(types.NodeTypeIt, "A", cl0, types.SpecStatePending, CLabels(Label("owner:org")), Label("owner:team")), S(types.NodeTypeIt, "A", cl0, types.SpecStatePanicked, CLabels(Label("owner:org")), STD("some captured stdout\n"), SE(types.SpecEventNodeStart, types.NodeTypeIt, "A", cl0), F("failure\nmessage", cl1, types.FailureNodeIsLeafNode, FailureNodeLocation(cl0), types.NodeTypeIt, ForwardedPanic("the panic")), SE(types.SpecEventNodeEnd, types.NodeTypeIt, "A", cl0, time.Millisecond*300, TL("some GinkgoWriter\noutput is interspersed\nhere and there\n")), ), S(types.NodeTypeBeforeSuite, "A", cl0, types.SpecStatePassed), }, } }) Describe("default behavior", func() { var generated reporters.JUnitTestSuites BeforeEach(func() { fname := fmt.Sprintf("./report-%d", GinkgoParallelProcess()) Ω(reporters.GenerateJUnitReport(report, fname)).Should(Succeed()) DeferCleanup(os.Remove, fname) generated = reporters.JUnitTestSuites{} f, err := os.Open(fname) Ω(err).ShouldNot(HaveOccurred()) Ω(xml.NewDecoder(f).Decode(&generated)).Should(Succeed()) }) It("generates a Junit report and writes it to disk", func() { Ω(generated.Tests).Should(Equal(5)) Ω(generated.Disabled).Should(Equal(1)) Ω(generated.Errors).Should(Equal(1)) Ω(generated.Failures).Should(Equal(1)) Ω(generated.Time).Should(Equal(60.0)) Ω(generated.TestSuites).Should(HaveLen(1)) suite := generated.TestSuites[0] Ω(suite.Name).Should(Equal("My Suite")) Ω(suite.Package).Should(Equal("/path/to/suite")) Ω(suite.Properties.WithName("SuiteSucceeded")).Should(Equal("false")) Ω(suite.Properties.WithName("RandomSeed")).Should(Equal("17")) Ω(suite.Tests).Should(Equal(5)) Ω(suite.Disabled).Should(Equal(1)) Ω(suite.Errors).Should(Equal(1)) Ω(suite.Failures).Should(Equal(1)) Ω(suite.Time).Should(Equal(60.0)) Ω(suite.TestCases).Should(HaveLen(5)) failingSpec := suite.TestCases[0] Ω(failingSpec.Name).Should(Equal("[It] A B C [dolphin, gorilla, cow, cat, dog]")) Ω(failingSpec.Classname).Should(Equal("My Suite")) Ω(failingSpec.Status).Should(Equal("timedout")) Ω(failingSpec.Skipped).Should(BeNil()) Ω(failingSpec.Error).Should(BeNil()) Ω(failingSpec.Owner).Should(Equal("")) Ω(failingSpec.Failure.Message).Should(Equal("failure\nmessage")) Ω(failingSpec.Failure.Type).Should(Equal("timedout")) Ω(failingSpec.Failure.Description).Should(MatchLines( "[TIMEDOUT] failure", "message", spr("In [It] at: cl3.go:103 @ %s", FORMATTED_TIME), "", "[PANICKED] ", spr("In [It] at: cl4.go:144 @ %s", FORMATTED_TIME), "", "the panic!", "", "Full Stack Trace", " full-trace", " cl-4", "", "There were additional failures detected after the initial failure. These are visible in the timeline", "", )) Ω(failingSpec.SystemOut).Should(Equal("some captured stdout\n")) Ω(failingSpec.SystemErr).Should(MatchLines( spr("STEP: a by step - cl0.go:12 @ %s", FORMATTED_TIME), spr("> Enter [It] C - cl2.go:80 @ %s", FORMATTED_TIME), "ginkgowriter", "[TIMEDOUT] failure", "message", spr("In [It] at: cl3.go:103 @ %s", FORMATTED_TIME), "output", "[PANICKED] ", spr("In [It] at: cl4.go:144 @ %s", FORMATTED_TIME), "", "the panic!", "", "Full Stack Trace", " full-trace", " cl-4", spr("< Exit [It] C - cl2.go:80 @ %s (87ms)", FORMATTED_TIME), spr("a report entry - cl1.go:37 @ %s", FORMATTED_TIME), spr("a hidden report entry - cl1.go:37 @ %s", FORMATTED_TIME), "cleanup!", "[FAILED] a subsequent failure", spr("In [AfterEach] at: :0 @ %s", FORMATTED_TIME), "", )) passingSpec := suite.TestCases[1] Ω(passingSpec.Name).Should(Equal("[It] A [cat, owner:frank, OWNer:bob]")) Ω(passingSpec.Classname).Should(Equal("My Suite")) Ω(passingSpec.Status).Should(Equal("passed")) Ω(passingSpec.Skipped).Should(BeNil()) Ω(passingSpec.Error).Should(BeNil()) Ω(passingSpec.Failure).Should(BeNil()) Ω(passingSpec.Owner).Should(Equal("bob")) Ω(passingSpec.SystemOut).Should(Equal("some captured stdout\n")) Ω(passingSpec.SystemErr).Should(MatchLines( spr("> Enter [It] A - cl0.go:12 @ %s", FORMATTED_TIME), "some GinkgoWriter", "my progress report", " A (Spec Runtime: 5s)", " cl0.go:12", spr("STEP: My Step - cl1.go:37 @ %s", FORMATTED_TIME), "output is interspersed", spr("my entry - cl1.go:37 @ %s", FORMATTED_TIME), spr("my hidden entry - cl1.go:37 @ %s", FORMATTED_TIME), spr("END STEP: My Step - cl1.go:37 @ %s (200ms)", FORMATTED_TIME), "here and there", spr("< Exit [It] A - cl0.go:12 @ %s (300ms)", FORMATTED_TIME), "", )) pendingSpec := suite.TestCases[2] Ω(pendingSpec.Name).Should(Equal("[It] A [owner:org, owner:team]")) Ω(pendingSpec.Classname).Should(Equal("My Suite")) Ω(pendingSpec.Status).Should(Equal("pending")) Ω(pendingSpec.Skipped.Message).Should(Equal("pending")) Ω(pendingSpec.Error).Should(BeNil()) Ω(pendingSpec.Owner).Should(Equal("team")) Ω(pendingSpec.Failure).Should(BeNil()) Ω(pendingSpec.SystemOut).Should(BeEmpty()) Ω(pendingSpec.SystemErr).Should(BeEmpty()) panickedSpec := suite.TestCases[3] Ω(panickedSpec.Name).Should(Equal("[It] A [owner:org]")) Ω(panickedSpec.Classname).Should(Equal("My Suite")) Ω(panickedSpec.Status).Should(Equal("panicked")) Ω(panickedSpec.Skipped).Should(BeNil()) Ω(panickedSpec.Owner).Should(Equal("org")) Ω(panickedSpec.Error.Message).Should(Equal("the panic")) Ω(panickedSpec.Error.Type).Should(Equal("panicked")) Ω(panickedSpec.Error.Description).Should(MatchLines( "[PANICKED] failure", "message", spr("In [It] at: cl1.go:37 @ %s", FORMATTED_TIME), "", "the panic", "", "Full Stack Trace", " full-trace", " cl-1", "", )) Ω(panickedSpec.Failure).Should(BeNil()) Ω(panickedSpec.SystemOut).Should(Equal("some captured stdout\n")) Ω(panickedSpec.SystemErr).Should(MatchLines( spr("> Enter [It] A - cl0.go:12 @ %s", FORMATTED_TIME), "[PANICKED] failure", "message", spr("In [It] at: cl1.go:37 @ %s", FORMATTED_TIME), "", "the panic", "", "Full Stack Trace", " full-trace", " cl-1", spr("< Exit [It] A - cl0.go:12 @ %s (300ms)", FORMATTED_TIME), "", )) beforeSuiteSpec := suite.TestCases[4] Ω(beforeSuiteSpec.Name).Should(Equal("[BeforeSuite] A")) Ω(beforeSuiteSpec.Classname).Should(Equal("My Suite")) Ω(beforeSuiteSpec.Status).Should(Equal("passed")) Ω(beforeSuiteSpec.Skipped).Should(BeNil()) Ω(beforeSuiteSpec.Error).Should(BeNil()) Ω(beforeSuiteSpec.Failure).Should(BeNil()) Ω(beforeSuiteSpec.SystemOut).Should(BeEmpty()) Ω(beforeSuiteSpec.SystemErr).Should(BeEmpty()) }) }) Describe("when configured to omit all the omittables", func() { var generated reporters.JUnitTestSuites BeforeEach(func() { fname := fmt.Sprintf("./report-%d", GinkgoParallelProcess()) Ω(reporters.GenerateJUnitReportWithConfig(report, fname, reporters.JunitReportConfig{ OmitTimelinesForSpecState: types.SpecStatePassed, OmitFailureMessageAttr: true, OmitCapturedStdOutErr: true, OmitSpecLabels: true, OmitLeafNodeType: true, OmitSuiteSetupNodes: true, })).Should(Succeed()) DeferCleanup(os.Remove, fname) generated = reporters.JUnitTestSuites{} f, err := os.Open(fname) Ω(err).ShouldNot(HaveOccurred()) Ω(xml.NewDecoder(f).Decode(&generated)).Should(Succeed()) }) It("generates a Junit report and writes it to disk", func() { Ω(generated.Tests).Should(Equal(4)) Ω(generated.Disabled).Should(Equal(1)) Ω(generated.Errors).Should(Equal(1)) Ω(generated.Failures).Should(Equal(1)) Ω(generated.Time).Should(Equal(60.0)) Ω(generated.TestSuites).Should(HaveLen(1)) suite := generated.TestSuites[0] Ω(suite.Name).Should(Equal("My Suite")) Ω(suite.Package).Should(Equal("/path/to/suite")) Ω(suite.Properties.WithName("SuiteSucceeded")).Should(Equal("false")) Ω(suite.Properties.WithName("RandomSeed")).Should(Equal("17")) Ω(suite.Tests).Should(Equal(4)) Ω(suite.Disabled).Should(Equal(1)) Ω(suite.Errors).Should(Equal(1)) Ω(suite.Failures).Should(Equal(1)) Ω(suite.Time).Should(Equal(60.0)) Ω(suite.TestCases).Should(HaveLen(4)) failingSpec := suite.TestCases[0] Ω(failingSpec.Name).Should(Equal("A B C")) Ω(failingSpec.Classname).Should(Equal("My Suite")) Ω(failingSpec.Status).Should(Equal("timedout")) Ω(failingSpec.Skipped).Should(BeNil()) Ω(failingSpec.Error).Should(BeNil()) Ω(failingSpec.Failure.Message).Should(BeEmpty()) Ω(failingSpec.Failure.Type).Should(Equal("timedout")) Ω(failingSpec.Failure.Description).Should(MatchLines( "[TIMEDOUT] failure", "message", spr("In [It] at: cl3.go:103 @ %s", FORMATTED_TIME), "", "[PANICKED] ", spr("In [It] at: cl4.go:144 @ %s", FORMATTED_TIME), "", "the panic!", "", "Full Stack Trace", " full-trace", " cl-4", "", "There were additional failures detected after the initial failure. These are visible in the timeline", "", )) Ω(failingSpec.SystemOut).Should(BeEmpty()) Ω(failingSpec.SystemErr).Should(MatchLines( spr("STEP: a by step - cl0.go:12 @ %s", FORMATTED_TIME), spr("> Enter [It] C - cl2.go:80 @ %s", FORMATTED_TIME), "ginkgowriter", "[TIMEDOUT] failure", "message", spr("In [It] at: cl3.go:103 @ %s", FORMATTED_TIME), "output", "[PANICKED] ", spr("In [It] at: cl4.go:144 @ %s", FORMATTED_TIME), "", "the panic!", "", "Full Stack Trace", " full-trace", " cl-4", spr("< Exit [It] C - cl2.go:80 @ %s (87ms)", FORMATTED_TIME), spr("a report entry - cl1.go:37 @ %s", FORMATTED_TIME), spr("a hidden report entry - cl1.go:37 @ %s", FORMATTED_TIME), "cleanup!", "[FAILED] a subsequent failure", spr("In [AfterEach] at: :0 @ %s", FORMATTED_TIME), "", )) passingSpec := suite.TestCases[1] Ω(passingSpec.Name).Should(Equal("A")) Ω(passingSpec.Classname).Should(Equal("My Suite")) Ω(passingSpec.Status).Should(Equal("passed")) Ω(passingSpec.Skipped).Should(BeNil()) Ω(passingSpec.Error).Should(BeNil()) Ω(passingSpec.Failure).Should(BeNil()) Ω(passingSpec.SystemOut).Should(BeEmpty()) Ω(passingSpec.SystemErr).Should(BeEmpty()) pendingSpec := suite.TestCases[2] Ω(pendingSpec.Name).Should(Equal("A")) Ω(pendingSpec.Classname).Should(Equal("My Suite")) Ω(pendingSpec.Status).Should(Equal("pending")) Ω(pendingSpec.Skipped.Message).Should(Equal("pending")) Ω(pendingSpec.Error).Should(BeNil()) Ω(pendingSpec.Failure).Should(BeNil()) Ω(pendingSpec.SystemOut).Should(BeEmpty()) Ω(pendingSpec.SystemErr).Should(BeEmpty()) panickedSpec := suite.TestCases[3] Ω(panickedSpec.Name).Should(Equal("A")) Ω(panickedSpec.Classname).Should(Equal("My Suite")) Ω(panickedSpec.Status).Should(Equal("panicked")) Ω(panickedSpec.Skipped).Should(BeNil()) Ω(panickedSpec.Error.Message).Should(BeEmpty()) Ω(panickedSpec.Error.Type).Should(Equal("panicked")) Ω(panickedSpec.Error.Description).Should(MatchLines( "[PANICKED] failure", "message", spr("In [It] at: cl1.go:37 @ %s", FORMATTED_TIME), "", "the panic", "", "Full Stack Trace", " full-trace", " cl-1", "", )) Ω(panickedSpec.Failure).Should(BeNil()) Ω(panickedSpec.SystemOut).Should(BeEmpty()) Ω(panickedSpec.SystemErr).Should(MatchLines( spr("> Enter [It] A - cl0.go:12 @ %s", FORMATTED_TIME), "[PANICKED] failure", "message", spr("In [It] at: cl1.go:37 @ %s", FORMATTED_TIME), "", "the panic", "", "Full Stack Trace", " full-trace", " cl-1", spr("< Exit [It] A - cl0.go:12 @ %s (300ms)", FORMATTED_TIME), "", )) }) }) Describe("when configured to write the report inside a folder", func() { var folderPath string var filePath string BeforeEach(func() { folderPath = filepath.Join(fmt.Sprintf("test_outputs_%d", GinkgoParallelProcess())) fileName := fmt.Sprintf("report-%d", GinkgoParallelProcess()) filePath = filepath.Join(folderPath, fileName) Ω(reporters.GenerateJUnitReport(report, filePath)).Should(Succeed()) DeferCleanup(os.RemoveAll, folderPath) }) It("creates the folder and the report file", func() { _, err := os.Stat(folderPath) Ω(err).Should(Succeed(), "Parent folder should be created") _, err = os.Stat(filePath) Ω(err).Should(Succeed(), "Report file should be created") }) }) }) golang-github-onsi-ginkgo-v2-2.22.0/reporters/reporter.go000066400000000000000000000021671472321612100233160ustar00rootroot00000000000000package reporters import ( "github.com/onsi/ginkgo/v2/types" ) type Reporter interface { SuiteWillBegin(report types.Report) WillRun(report types.SpecReport) DidRun(report types.SpecReport) SuiteDidEnd(report types.Report) //Timeline emission EmitFailure(state types.SpecState, failure types.Failure) EmitProgressReport(progressReport types.ProgressReport) EmitReportEntry(entry types.ReportEntry) EmitSpecEvent(event types.SpecEvent) } 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) EmitFailure(state types.SpecState, failure types.Failure) {} func (n NoopReporter) EmitProgressReport(progressReport types.ProgressReport) {} func (n NoopReporter) EmitReportEntry(entry types.ReportEntry) {} func (n NoopReporter) EmitSpecEvent(event types.SpecEvent) {} golang-github-onsi-ginkgo-v2-2.22.0/reporters/reporters_suite_test.go000066400000000000000000000006641472321612100257510ustar00rootroot00000000000000package reporters_test import ( "fmt" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" "testing" ) func TestReporters(t *testing.T) { RegisterFailHandler(Fail) RunSpecs(t, "Reporters Suite") } //leave this alone! func FixtureFunction() { a := 0 for a < 100 { fmt.Println(a) fmt.Println(a + 1) fmt.Println(a + 3) fmt.Println(a + 4) fmt.Println(a + 5) fmt.Println(a + 6) fmt.Println(a + 7) a++ } } golang-github-onsi-ginkgo-v2-2.22.0/reporters/teamcity_report.go000066400000000000000000000074731472321612100246730ustar00rootroot00000000000000/* TeamCity Reporter for Ginkgo Makes use of TeamCity's support for Service Messages http://confluence.jetbrains.com/display/TCD7/Build+Script+Interaction+with+TeamCity#BuildScriptInteractionwithTeamCity-ReportingTests */ package reporters import ( "fmt" "os" "path" "strings" "github.com/onsi/ginkgo/v2/types" ) func tcEscape(s string) string { s = strings.ReplaceAll(s, "|", "||") s = strings.ReplaceAll(s, "'", "|'") s = strings.ReplaceAll(s, "\n", "|n") s = strings.ReplaceAll(s, "\r", "|r") s = strings.ReplaceAll(s, "[", "|[") s = strings.ReplaceAll(s, "]", "|]") return s } func GenerateTeamcityReport(report types.Report, dst string) error { if err := os.MkdirAll(path.Dir(dst), 0770); err != nil { return err } f, err := os.Create(dst) if err != nil { return err } name := report.SuiteDescription labels := report.SuiteLabels if len(labels) > 0 { name = name + " [" + strings.Join(labels, ", ") + "]" } fmt.Fprintf(f, "##teamcity[testSuiteStarted name='%s']\n", tcEscape(name)) for _, spec := range report.SpecReports { name := fmt.Sprintf("[%s]", spec.LeafNodeType) if spec.FullText() != "" { name = name + " " + spec.FullText() } labels := spec.Labels() if len(labels) > 0 { name = name + " [" + strings.Join(labels, ", ") + "]" } name = tcEscape(name) fmt.Fprintf(f, "##teamcity[testStarted name='%s']\n", name) switch spec.State { case types.SpecStatePending: fmt.Fprintf(f, "##teamcity[testIgnored name='%s' message='pending']\n", name) case types.SpecStateSkipped: message := "skipped" if spec.Failure.Message != "" { message += " - " + spec.Failure.Message } fmt.Fprintf(f, "##teamcity[testIgnored name='%s' message='%s']\n", name, tcEscape(message)) case types.SpecStateFailed: details := failureDescriptionForUnstructuredReporters(spec) fmt.Fprintf(f, "##teamcity[testFailed name='%s' message='failed - %s' details='%s']\n", name, tcEscape(spec.Failure.Message), tcEscape(details)) case types.SpecStatePanicked: details := failureDescriptionForUnstructuredReporters(spec) fmt.Fprintf(f, "##teamcity[testFailed name='%s' message='panicked - %s' details='%s']\n", name, tcEscape(spec.Failure.ForwardedPanic), tcEscape(details)) case types.SpecStateTimedout: details := failureDescriptionForUnstructuredReporters(spec) fmt.Fprintf(f, "##teamcity[testFailed name='%s' message='timedout - %s' details='%s']\n", name, tcEscape(spec.Failure.Message), tcEscape(details)) case types.SpecStateInterrupted: details := failureDescriptionForUnstructuredReporters(spec) fmt.Fprintf(f, "##teamcity[testFailed name='%s' message='interrupted - %s' details='%s']\n", name, tcEscape(spec.Failure.Message), tcEscape(details)) case types.SpecStateAborted: details := failureDescriptionForUnstructuredReporters(spec) 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(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)) return f.Close() } func MergeAndCleanupTeamcityReports(sources []string, dst string) ([]string, error) { messages := []string{} merged := []byte{} for _, source := range sources { data, err := os.ReadFile(source) if err != nil { messages = append(messages, fmt.Sprintf("Could not open %s:\n%s", source, err.Error())) continue } os.Remove(source) merged = append(merged, data...) } return messages, os.WriteFile(dst, merged, 0666) } golang-github-onsi-ginkgo-v2-2.22.0/reporters/teamcity_report_test.go000066400000000000000000000074771472321612100257360ustar00rootroot00000000000000package reporters_test import ( "fmt" "os" "path/filepath" "time" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" "github.com/onsi/ginkgo/v2/reporters" "github.com/onsi/ginkgo/v2/types" ) var _ = Describe("TeamCityReport", func() { var report types.Report BeforeEach(func() { report = types.Report{ SuiteDescription: "My Suite", SuitePath: "/path/to/suite", PreRunStats: types.PreRunStats{SpecsThatWillRun: 15, TotalSpecs: 20}, SuiteConfig: types.SuiteConfig{RandomSeed: 17, ParallelTotal: 1}, RunTime: time.Minute, SpecReports: types.SpecReports{ S(types.NodeTypeIt, Label("cat", "dog"), CLabels(Label("dolphin"), Label("gorilla", "cow")), CTS("A", "B"), CLS(cl0, cl1), "C", cl2, types.SpecStateTimedout, STD("some captured stdout\n"), GW("ginkgowriter\noutput\ncleanup!"), SE(types.SpecEventByStart, "a by step", cl0), SE(types.SpecEventNodeStart, types.NodeTypeIt, "C", cl2, TL(0)), F("failure\nmessage", cl3, types.FailureNodeIsLeafNode, FailureNodeLocation(cl2), types.NodeTypeIt, TL("ginkgowriter\n"), AF(types.SpecStatePanicked, cl4, types.FailureNodeIsLeafNode, FailureNodeLocation(cl2), types.NodeTypeIt, TL("ginkgowriter\noutput\n"), ForwardedPanic("the panic!"))), SE(types.SpecEventNodeEnd, types.NodeTypeIt, "C", cl2, TL("ginkgowriter\noutput\n"), time.Microsecond*87230), RE("a report entry", cl1, TL("ginkgowriter\noutput\n")), RE("a hidden report entry", cl1, TL("ginkgowriter\noutput\n"), types.ReportEntryVisibilityNever), AF(types.SpecStateFailed, "a subsequent failure", types.FailureNodeInContainer, FailureNodeLocation(cl3), types.NodeTypeAfterEach, 0, TL("ginkgowriter\noutput\ncleanup!")), ), S(types.NodeTypeIt, "A", cl0, STD("some captured stdout\n"), GW("some GinkgoWriter\noutput is interspersed\nhere and there\n"), SE(types.SpecEventNodeStart, types.NodeTypeIt, "A", cl0), PR("my progress report", LeafNodeText("A"), TL("some GinkgoWriter\n")), SE(types.SpecEventByStart, "My Step", cl1, TL("some GinkgoWriter\n")), RE("my entry", cl1, types.ReportEntryVisibilityFailureOrVerbose, TL("some GinkgoWriter\noutput is interspersed\n")), RE("my hidden entry", cl1, types.ReportEntryVisibilityNever, TL("some GinkgoWriter\noutput is interspersed\n")), SE(types.SpecEventByEnd, "My Step", cl1, time.Millisecond*200, TL("some GinkgoWriter\noutput is interspersed\n")), SE(types.SpecEventNodeEnd, types.NodeTypeIt, "A", cl0, time.Millisecond*300, TL("some GinkgoWriter\noutput is interspersed\nhere and there\n")), ), S(types.NodeTypeIt, "A", cl0, types.SpecStatePending), S(types.NodeTypeIt, "A", cl0, types.SpecStatePanicked, STD("some captured stdout\n"), SE(types.SpecEventNodeStart, types.NodeTypeIt, "A", cl0), F("failure\nmessage", cl1, types.FailureNodeIsLeafNode, FailureNodeLocation(cl0), types.NodeTypeIt, ForwardedPanic("the panic")), SE(types.SpecEventNodeEnd, types.NodeTypeIt, "A", cl0, time.Millisecond*300, TL("some GinkgoWriter\noutput is interspersed\nhere and there\n")), ), S(types.NodeTypeBeforeSuite, "A", cl0, types.SpecStatePassed), }, } }) Describe("when configured to write the report inside a folder", func() { var folderPath string var filePath string BeforeEach(func() { folderPath = filepath.Join(fmt.Sprintf("test_outputs_%d", GinkgoParallelProcess())) fileName := fmt.Sprintf("report-%d", GinkgoParallelProcess()) filePath = filepath.Join(folderPath, fileName) Ω(reporters.GenerateTeamcityReport(report, filePath)).Should(Succeed()) DeferCleanup(os.RemoveAll, folderPath) }) It("creates the folder and the report file", func() { _, err := os.Stat(folderPath) Ω(err).Should(Succeed(), "Parent folder should be created") _, err = os.Stat(filePath) Ω(err).Should(Succeed(), "Report file should be created") }) }) }) golang-github-onsi-ginkgo-v2-2.22.0/reporting_dsl.go000066400000000000000000000233101472321612100222730ustar00rootroot00000000000000package ginkgo import ( "fmt" "strings" "github.com/onsi/ginkgo/v2/internal" "github.com/onsi/ginkgo/v2/internal/global" "github.com/onsi/ginkgo/v2/reporters" "github.com/onsi/ginkgo/v2/types" ) /* Report represents the report for a Suite. It is documented here: https://pkg.go.dev/github.com/onsi/ginkgo/v2/types#Report */ type Report = types.Report /* Report represents the report for a Spec. It is documented here: https://pkg.go.dev/github.com/onsi/ginkgo/v2/types#SpecReport */ type SpecReport = types.SpecReport /* CurrentSpecReport returns information about the current running spec. The returned object is a types.SpecReport which includes helper methods to make extracting information about the spec easier. You can learn more about SpecReport here: https://pkg.go.dev/github.com/onsi/ginkgo/types#SpecReport You can learn more about CurrentSpecReport() here: https://onsi.github.io/ginkgo/#getting-a-report-for-the-current-spec */ func CurrentSpecReport() SpecReport { return global.Suite.CurrentSpecReport() } /* ReportEntryVisibility governs the visibility of ReportEntries in Ginkgo's console reporter - ReportEntryVisibilityAlways: the default behavior - the ReportEntry is always emitted. - ReportEntryVisibilityFailureOrVerbose: the ReportEntry is only emitted if the spec fails or if the tests are run with -v (similar to GinkgoWriters behavior). - ReportEntryVisibilityNever: the ReportEntry is never emitted though it appears in any generated machine-readable reports (e.g. by setting `--json-report`). You can learn more about Report Entries here: https://onsi.github.io/ginkgo/#attaching-data-to-reports */ type ReportEntryVisibility = types.ReportEntryVisibility const ReportEntryVisibilityAlways, ReportEntryVisibilityFailureOrVerbose, ReportEntryVisibilityNever = types.ReportEntryVisibilityAlways, types.ReportEntryVisibilityFailureOrVerbose, types.ReportEntryVisibilityNever /* AddReportEntry generates and adds a new ReportEntry to the current spec's SpecReport. It can take any of the following arguments: - A single arbitrary object to attach as the Value of the ReportEntry. This object will be included in any generated reports and will be emitted to the console when the report is emitted. - A ReportEntryVisibility enum to control the visibility of the ReportEntry - An Offset or CodeLocation decoration to control the reported location of the ReportEntry If the Value object implements `fmt.Stringer`, it's `String()` representation is used when emitting to the console. AddReportEntry() must be called within a Subject or Setup node - not in a Container node. You can learn more about Report Entries here: https://onsi.github.io/ginkgo/#attaching-data-to-reports */ func AddReportEntry(name string, args ...interface{}) { cl := types.NewCodeLocation(1) reportEntry, err := internal.NewReportEntry(name, cl, args...) if err != nil { Fail(fmt.Sprintf("Failed to generate Report Entry:\n%s", err.Error()), 1) } err = global.Suite.AddReportEntry(reportEntry) if err != nil { Fail(fmt.Sprintf("Failed to add Report Entry:\n%s", err.Error()), 1) } } /* ReportBeforeEach nodes are run for each spec, even if the spec is skipped or pending. ReportBeforeEach nodes take a function that receives a SpecReport or both SpecContext and Report for interruptible behavior. They are called before the spec starts. Example: ReportBeforeEach(func(report SpecReport) { // process report }) ReportBeforeEach(func(ctx SpecContext, report SpecReport) { // process report }), NodeTimeout(1 * time.Minute)) You cannot nest any other Ginkgo nodes within a ReportBeforeEach node's closure. You can learn more about ReportBeforeEach here: https://onsi.github.io/ginkgo/#generating-reports-programmatically You can learn about interruptible nodes here: https://onsi.github.io/ginkgo/#spec-timeouts-and-interruptible-nodes */ func ReportBeforeEach(body any, args ...any) bool { combinedArgs := []interface{}{body} combinedArgs = append(combinedArgs, args...) return pushNode(internal.NewNode(deprecationTracker, types.NodeTypeReportBeforeEach, "", combinedArgs...)) } /* ReportAfterEach nodes are run for each spec, even if the spec is skipped or pending. ReportAfterEach nodes take a function that receives a SpecReport or both SpecContext and Report for interruptible behavior. They are called after the spec has completed and receive the final report for the spec. Example: ReportAfterEach(func(report SpecReport) { // process report }) ReportAfterEach(func(ctx SpecContext, report SpecReport) { // process report }), NodeTimeout(1 * time.Minute)) You cannot nest any other Ginkgo nodes within a ReportAfterEach node's closure. You can learn more about ReportAfterEach here: https://onsi.github.io/ginkgo/#generating-reports-programmatically You can learn about interruptible nodes here: https://onsi.github.io/ginkgo/#spec-timeouts-and-interruptible-nodes */ func ReportAfterEach(body any, args ...any) bool { combinedArgs := []interface{}{body} combinedArgs = append(combinedArgs, args...) return pushNode(internal.NewNode(deprecationTracker, types.NodeTypeReportAfterEach, "", combinedArgs...)) } /* ReportBeforeSuite nodes are run at the beginning of the suite. ReportBeforeSuite nodes take a function that can either receive Report or both SpecContext and Report for interruptible behavior. Example Usage: ReportBeforeSuite(func(r Report) { // process report }) ReportBeforeSuite(func(ctx SpecContext, r Report) { // process report }, NodeTimeout(1 * time.Minute)) They are called at the beginning of the suite, before any specs have run and any BeforeSuite or SynchronizedBeforeSuite nodes, and are passed in the initial report for the suite. ReportBeforeSuite nodes must be created at the top-level (i.e. not nested in a Context/Describe/When node) # When running in parallel, Ginkgo ensures that only one of the parallel nodes runs the ReportBeforeSuite 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 You can learn about interruptible nodes here: https://onsi.github.io/ginkgo/#spec-timeouts-and-interruptible-nodes */ func ReportBeforeSuite(body any, args ...any) bool { combinedArgs := []interface{}{body} combinedArgs = append(combinedArgs, args...) return pushNode(internal.NewNode(deprecationTracker, types.NodeTypeReportBeforeSuite, "", combinedArgs...)) } /* ReportAfterSuite nodes are run at the end of the suite. ReportAfterSuite nodes execute at the suite's conclusion, and accept a function that can either receive Report or both SpecContext and Report for interruptible behavior. Example Usage: ReportAfterSuite("Non-interruptible ReportAfterSuite", func(r Report) { // process report }) ReportAfterSuite("Interruptible ReportAfterSuite", func(ctx SpecContext, r Report) { // process report }, NodeTimeout(1 * time.Minute)) They are called at the end of the suite, after all specs have run and any AfterSuite or SynchronizedAfterSuite nodes, and are passed in the final report for the suite. ReportAfterSuite nodes must be created at the top-level (i.e. not nested in a Context/Describe/When node) When running in parallel, Ginkgo ensures that only one of the parallel nodes runs the ReportAfterSuite and that it is passed a report that is aggregated across all parallel nodes In addition to using ReportAfterSuite to programmatically generate suite reports, you can also generate JSON, JUnit, and Teamcity formatted reports using the --json-report, --junit-report, and --teamcity-report ginkgo CLI flags. 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 You can learn about interruptible nodes here: https://onsi.github.io/ginkgo/#spec-timeouts-and-interruptible-nodes */ func ReportAfterSuite(text string, body any, args ...interface{}) bool { combinedArgs := []interface{}{body} combinedArgs = append(combinedArgs, args...) return pushNode(internal.NewNode(deprecationTracker, types.NodeTypeReportAfterSuite, text, combinedArgs...)) } func registerReportAfterSuiteNodeForAutogeneratedReports(reporterConfig types.ReporterConfig) { body := func(report Report) { if reporterConfig.JSONReport != "" { err := reporters.GenerateJSONReport(report, reporterConfig.JSONReport) if err != nil { Fail(fmt.Sprintf("Failed to generate JSON report:\n%s", err.Error())) } } if reporterConfig.JUnitReport != "" { err := reporters.GenerateJUnitReport(report, reporterConfig.JUnitReport) if err != nil { Fail(fmt.Sprintf("Failed to generate JUnit report:\n%s", err.Error())) } } if reporterConfig.TeamcityReport != "" { err := reporters.GenerateTeamcityReport(report, reporterConfig.TeamcityReport) if err != nil { Fail(fmt.Sprintf("Failed to generate Teamcity report:\n%s", err.Error())) } } } flags := []string{} if reporterConfig.JSONReport != "" { flags = append(flags, "--json-report") } if reporterConfig.JUnitReport != "" { flags = append(flags, "--junit-report") } if reporterConfig.TeamcityReport != "" { flags = append(flags, "--teamcity-report") } pushNode(internal.NewNode( deprecationTracker, types.NodeTypeReportAfterSuite, fmt.Sprintf("Autogenerated ReportAfterSuite for %s", strings.Join(flags, " ")), body, types.NewCustomCodeLocation("autogenerated by Ginkgo"), )) } golang-github-onsi-ginkgo-v2-2.22.0/table_dsl.go000066400000000000000000000311051472321612100213520ustar00rootroot00000000000000package ginkgo import ( "context" "fmt" "reflect" "strings" "github.com/onsi/ginkgo/v2/internal" "github.com/onsi/ginkgo/v2/types" ) /* The EntryDescription decorator allows you to pass a format string to DescribeTable() and Entry(). This format string is used to generate entry names via: fmt.Sprintf(formatString, parameters...) where parameters are the parameters passed into the entry. When passed into an Entry the EntryDescription is used to generate the name or that entry. When passed to DescribeTable, the EntryDescription is used to generate the names for any entries that have `nil` descriptions. You can learn more about generating EntryDescriptions here: https://onsi.github.io/ginkgo/#generating-entry-descriptions */ type EntryDescription string func (ed EntryDescription) render(args ...interface{}) string { return fmt.Sprintf(string(ed), args...) } /* DescribeTable describes a table-driven spec. For example: DescribeTable("a simple table", func(x int, y int, expected bool) { Ω(x > y).Should(Equal(expected)) }, Entry("x > y", 1, 0, true), Entry("x == y", 0, 0, false), Entry("x < y", 0, 1, false), ) You can learn more about DescribeTable here: https://onsi.github.io/ginkgo/#table-specs And can explore some Table patterns here: https://onsi.github.io/ginkgo/#table-specs-patterns */ func DescribeTable(description string, args ...interface{}) bool { GinkgoHelper() generateTable(description, false, args...) return true } /* You can focus a table with `FDescribeTable`. This is equivalent to `FDescribe`. */ func FDescribeTable(description string, args ...interface{}) bool { GinkgoHelper() args = append(args, internal.Focus) generateTable(description, false, args...) return true } /* You can mark a table as pending with `PDescribeTable`. This is equivalent to `PDescribe`. */ func PDescribeTable(description string, args ...interface{}) bool { GinkgoHelper() args = append(args, internal.Pending) generateTable(description, false, args...) return true } /* You can mark a table as pending with `XDescribeTable`. This is equivalent to `XDescribe`. */ var XDescribeTable = PDescribeTable /* DescribeTableSubtree describes a table-driven spec that generates a set of tests for each entry. For example: DescribeTableSubtree("a subtree table", func(url string, code int, message string) { var resp *http.Response BeforeEach(func() { var err error resp, err = http.Get(url) Expect(err).NotTo(HaveOccurred()) DeferCleanup(resp.Body.Close) }) It("should return the expected status code", func() { Expect(resp.StatusCode).To(Equal(code)) }) It("should return the expected message", func() { body, err := io.ReadAll(resp.Body) Expect(err).NotTo(HaveOccurred()) Expect(string(body)).To(Equal(message)) }) }, Entry("default response", "example.com/response", http.StatusOK, "hello world"), Entry("missing response", "example.com/missing", http.StatusNotFound, "wat?"), ) Note that you **must** place define an It inside the body function. You can learn more about DescribeTableSubtree here: https://onsi.github.io/ginkgo/#table-specs And can explore some Table patterns here: https://onsi.github.io/ginkgo/#table-specs-patterns */ func DescribeTableSubtree(description string, args ...interface{}) bool { GinkgoHelper() generateTable(description, true, args...) return true } /* You can focus a table with `FDescribeTableSubtree`. This is equivalent to `FDescribe`. */ func FDescribeTableSubtree(description string, args ...interface{}) bool { GinkgoHelper() args = append(args, internal.Focus) generateTable(description, true, args...) return true } /* You can mark a table as pending with `PDescribeTableSubtree`. This is equivalent to `PDescribe`. */ func PDescribeTableSubtree(description string, args ...interface{}) bool { GinkgoHelper() args = append(args, internal.Pending) generateTable(description, true, args...) return true } /* You can mark a table as pending with `XDescribeTableSubtree`. This is equivalent to `XDescribe`. */ var XDescribeTableSubtree = PDescribeTableSubtree /* TableEntry represents an entry in a table test. You generally use the `Entry` constructor. */ type TableEntry struct { description interface{} decorations []interface{} parameters []interface{} codeLocation types.CodeLocation } /* Entry constructs a TableEntry. The first argument is a description. This can be a string, a function that accepts the parameters passed to the TableEntry and returns a string, an EntryDescription format string, or nil. If nil is provided then the name of the Entry is derived using the table-level entry description. Subsequent arguments accept any Ginkgo decorators. These are filtered out and the remaining arguments are passed into the Spec function associated with the table. Each Entry ends up generating an individual Ginkgo It. The body of the it is the Table Body function with the Entry parameters passed in. If you want to generate interruptible specs simply write a Table function that accepts a SpecContext as its first argument. You can then decorate individual Entrys with the NodeTimeout and SpecTimeout decorators. You can learn more about Entry here: https://onsi.github.io/ginkgo/#table-specs */ func Entry(description interface{}, args ...interface{}) TableEntry { GinkgoHelper() decorations, parameters := internal.PartitionDecorations(args...) return TableEntry{description: description, decorations: decorations, parameters: parameters, codeLocation: types.NewCodeLocation(0)} } /* You can focus a particular entry with FEntry. This is equivalent to FIt. */ func FEntry(description interface{}, args ...interface{}) TableEntry { GinkgoHelper() decorations, parameters := internal.PartitionDecorations(args...) decorations = append(decorations, internal.Focus) return TableEntry{description: description, decorations: decorations, parameters: parameters, codeLocation: types.NewCodeLocation(0)} } /* You can mark a particular entry as pending with PEntry. This is equivalent to PIt. */ func PEntry(description interface{}, args ...interface{}) TableEntry { GinkgoHelper() decorations, parameters := internal.PartitionDecorations(args...) decorations = append(decorations, internal.Pending) return TableEntry{description: description, decorations: decorations, parameters: parameters, codeLocation: types.NewCodeLocation(0)} } /* You can mark a particular entry as pending with XEntry. This is equivalent to XIt. */ var XEntry = PEntry var contextType = reflect.TypeOf(new(context.Context)).Elem() var specContextType = reflect.TypeOf(new(SpecContext)).Elem() func generateTable(description string, isSubtree bool, args ...interface{}) { GinkgoHelper() cl := types.NewCodeLocation(0) containerNodeArgs := []interface{}{cl} entries := []TableEntry{} var internalBody interface{} var internalBodyType reflect.Type var tableLevelEntryDescription interface{} tableLevelEntryDescription = func(args ...interface{}) string { out := []string{} for _, arg := range args { out = append(out, fmt.Sprint(arg)) } return "Entry: " + strings.Join(out, ", ") } if len(args) == 1 { exitIfErr(types.GinkgoErrors.MissingParametersForTableFunction(cl)) } for i, arg := range args { switch t := reflect.TypeOf(arg); { case t == nil: exitIfErr(types.GinkgoErrors.IncorrectParameterTypeForTable(i, "nil", cl)) case t == reflect.TypeOf(TableEntry{}): entries = append(entries, arg.(TableEntry)) case t == reflect.TypeOf([]TableEntry{}): entries = append(entries, arg.([]TableEntry)...) case t == reflect.TypeOf(EntryDescription("")): tableLevelEntryDescription = arg.(EntryDescription).render case t.Kind() == reflect.Func && t.NumOut() == 1 && t.Out(0) == reflect.TypeOf(""): tableLevelEntryDescription = arg case t.Kind() == reflect.Func: if internalBody != nil { exitIfErr(types.GinkgoErrors.MultipleEntryBodyFunctionsForTable(cl)) } internalBody = arg internalBodyType = reflect.TypeOf(internalBody) default: containerNodeArgs = append(containerNodeArgs, arg) } } containerNodeArgs = append(containerNodeArgs, func() { for _, entry := range entries { var err error entry := entry var description string switch t := reflect.TypeOf(entry.description); { case t == nil: err = validateParameters(tableLevelEntryDescription, entry.parameters, "Entry Description function", entry.codeLocation, false) if err == nil { description = invokeFunction(tableLevelEntryDescription, entry.parameters)[0].String() } case t == reflect.TypeOf(EntryDescription("")): description = entry.description.(EntryDescription).render(entry.parameters...) case t == reflect.TypeOf(""): description = entry.description.(string) case t.Kind() == reflect.Func && t.NumOut() == 1 && t.Out(0) == reflect.TypeOf(""): err = validateParameters(entry.description, entry.parameters, "Entry Description function", entry.codeLocation, false) if err == nil { description = invokeFunction(entry.description, entry.parameters)[0].String() } default: err = types.GinkgoErrors.InvalidEntryDescription(entry.codeLocation) } internalNodeArgs := []interface{}{entry.codeLocation} internalNodeArgs = append(internalNodeArgs, entry.decorations...) hasContext := false if internalBodyType.NumIn() > 0 { if internalBodyType.In(0).Implements(specContextType) { hasContext = true } else if internalBodyType.In(0).Implements(contextType) { hasContext = true if len(entry.parameters) > 0 && reflect.TypeOf(entry.parameters[0]) != nil && reflect.TypeOf(entry.parameters[0]).Implements(contextType) { // we allow you to pass in a non-nil context hasContext = false } } } if err == nil { err = validateParameters(internalBody, entry.parameters, "Table Body function", entry.codeLocation, hasContext) } if hasContext { internalNodeArgs = append(internalNodeArgs, func(c SpecContext) { if err != nil { panic(err) } invokeFunction(internalBody, append([]interface{}{c}, entry.parameters...)) }) if isSubtree { exitIfErr(types.GinkgoErrors.ContextsCannotBeUsedInSubtreeTables(cl)) } } else { internalNodeArgs = append(internalNodeArgs, func() { if err != nil { panic(err) } invokeFunction(internalBody, entry.parameters) }) } internalNodeType := types.NodeTypeIt if isSubtree { internalNodeType = types.NodeTypeContainer } pushNode(internal.NewNode(deprecationTracker, internalNodeType, description, internalNodeArgs...)) } }) pushNode(internal.NewNode(deprecationTracker, types.NodeTypeContainer, description, containerNodeArgs...)) } func invokeFunction(function interface{}, parameters []interface{}) []reflect.Value { inValues := make([]reflect.Value, len(parameters)) funcType := reflect.TypeOf(function) limit := funcType.NumIn() if funcType.IsVariadic() { limit = limit - 1 } for i := 0; i < limit && i < len(parameters); i++ { inValues[i] = computeValue(parameters[i], funcType.In(i)) } if funcType.IsVariadic() { variadicType := funcType.In(limit).Elem() for i := limit; i < len(parameters); i++ { inValues[i] = computeValue(parameters[i], variadicType) } } return reflect.ValueOf(function).Call(inValues) } func validateParameters(function interface{}, parameters []interface{}, kind string, cl types.CodeLocation, hasContext bool) error { funcType := reflect.TypeOf(function) limit := funcType.NumIn() offset := 0 if hasContext { limit = limit - 1 offset = 1 } if funcType.IsVariadic() { limit = limit - 1 } if len(parameters) < limit { return types.GinkgoErrors.TooFewParametersToTableFunction(limit, len(parameters), kind, cl) } if len(parameters) > limit && !funcType.IsVariadic() { return types.GinkgoErrors.TooManyParametersToTableFunction(limit, len(parameters), kind, cl) } var i = 0 for ; i < limit; i++ { actual := reflect.TypeOf(parameters[i]) expected := funcType.In(i + offset) if !(actual == nil) && !actual.AssignableTo(expected) { return types.GinkgoErrors.IncorrectParameterTypeToTableFunction(i+1, expected, actual, kind, cl) } } if funcType.IsVariadic() { expected := funcType.In(limit + offset).Elem() for ; i < len(parameters); i++ { actual := reflect.TypeOf(parameters[i]) if !(actual == nil) && !actual.AssignableTo(expected) { return types.GinkgoErrors.IncorrectVariadicParameterTypeToTableFunction(expected, actual, kind, cl) } } } return nil } func computeValue(parameter interface{}, t reflect.Type) reflect.Value { if parameter == nil { return reflect.Zero(t) } else { return reflect.ValueOf(parameter) } } golang-github-onsi-ginkgo-v2-2.22.0/types/000077500000000000000000000000001472321612100202365ustar00rootroot00000000000000golang-github-onsi-ginkgo-v2-2.22.0/types/code_location.go000066400000000000000000000100371472321612100233700ustar00rootroot00000000000000package types import ( "fmt" "os" "regexp" "runtime" "runtime/debug" "strings" "sync" ) type CodeLocation struct { FileName string `json:",omitempty"` LineNumber int `json:",omitempty"` FullStackTrace string `json:",omitempty"` CustomMessage string `json:",omitempty"` } func (codeLocation CodeLocation) String() string { if codeLocation.CustomMessage != "" { return codeLocation.CustomMessage } return fmt.Sprintf("%s:%d", codeLocation.FileName, codeLocation.LineNumber) } func (codeLocation CodeLocation) ContentsOfLine() string { if codeLocation.CustomMessage != "" { return "" } contents, err := os.ReadFile(codeLocation.FileName) if err != nil { return "" } lines := strings.Split(string(contents), "\n") if len(lines) < codeLocation.LineNumber { return "" } return lines[codeLocation.LineNumber-1] } type codeLocationLocator struct { pcs map[uintptr]bool helpers map[string]bool lock *sync.Mutex } func (c *codeLocationLocator) addHelper(pc uintptr) { c.lock.Lock() defer c.lock.Unlock() if c.pcs[pc] { return } c.lock.Unlock() f := runtime.FuncForPC(pc) c.lock.Lock() if f == nil { return } c.helpers[f.Name()] = true c.pcs[pc] = true } func (c *codeLocationLocator) hasHelper(name string) bool { c.lock.Lock() defer c.lock.Unlock() return c.helpers[name] } func (c *codeLocationLocator) getCodeLocation(skip int) CodeLocation { pc := make([]uintptr, 40) n := runtime.Callers(skip+2, pc) if n == 0 { return CodeLocation{} } pc = pc[:n] frames := runtime.CallersFrames(pc) for { frame, more := frames.Next() if !c.hasHelper(frame.Function) { return CodeLocation{FileName: frame.File, LineNumber: frame.Line} } if !more { break } } return CodeLocation{} } var clLocator = &codeLocationLocator{ pcs: map[uintptr]bool{}, helpers: map[string]bool{}, lock: &sync.Mutex{}, } // MarkAsHelper is used by GinkgoHelper to mark the caller (appropriately offset by skip)as a helper. You can use this directly if you need to provide an optional `skip` to mark functions further up the call stack as helpers. func MarkAsHelper(optionalSkip ...int) { skip := 1 if len(optionalSkip) > 0 { skip += optionalSkip[0] } pc, _, _, ok := runtime.Caller(skip) if ok { clLocator.addHelper(pc) } } func NewCustomCodeLocation(message string) CodeLocation { return CodeLocation{ CustomMessage: message, } } func NewCodeLocation(skip int) CodeLocation { return clLocator.getCodeLocation(skip + 1) } func NewCodeLocationWithStackTrace(skip int) CodeLocation { cl := clLocator.getCodeLocation(skip + 1) cl.FullStackTrace = PruneStack(string(debug.Stack()), skip+1) return cl } // PruneStack removes references to functions that are internal to Ginkgo // and the Go runtime from a stack string and a certain number of stack entries // at the beginning of the stack. The stack string has the format // as returned by runtime/debug.Stack. The leading goroutine information is // optional and always removed if present. Beware that runtime/debug.Stack // adds itself as first entry, so typically skip must be >= 1 to remove that // entry. func PruneStack(fullStackTrace string, skip int) string { stack := strings.Split(fullStackTrace, "\n") // Ensure that the even entries are the method names and the // odd entries the source code information. if len(stack) > 0 && strings.HasPrefix(stack[0], "goroutine ") { // Ignore "goroutine 29 [running]:" line. stack = stack[1:] } // The "+1" is for skipping over the initial entry, which is // runtime/debug.Stack() itself. if len(stack) > 2*(skip+1) { stack = stack[2*(skip+1):] } prunedStack := []string{} if os.Getenv("GINKGO_PRUNE_STACK") == "FALSE" { prunedStack = stack } else { re := regexp.MustCompile(`\/ginkgo\/|\/pkg\/testing\/|\/pkg\/runtime\/`) for i := 0; i < len(stack)/2; i++ { // We filter out based on the source code file name. if !re.MatchString(stack[i*2+1]) { prunedStack = append(prunedStack, stack[i*2]) prunedStack = append(prunedStack, stack[i*2+1]) } } } return strings.Join(prunedStack, "\n") } golang-github-onsi-ginkgo-v2-2.22.0/types/code_location_test.go000066400000000000000000000104101472321612100244220ustar00rootroot00000000000000package types_test import ( "fmt" "runtime" . "github.com/onsi/ginkgo/v2" "github.com/onsi/ginkgo/v2/types" . "github.com/onsi/gomega" ) var _ = Describe("CodeLocation", func() { clWithSkip := func(skip int) types.CodeLocation { return types.NewCodeLocation(skip) } helperFunction := func() types.CodeLocation { GinkgoHelper() return types.NewCodeLocation(0) } Describe("Creating CodeLocations", func() { Context("when skip is 0", func() { It("returns the location at which types.NewCodeLocation was called", func() { _, fname, lnumber, _ := runtime.Caller(0) cl := types.NewCodeLocation(0) Ω(cl).Should(Equal(types.CodeLocation{ FileName: fname, LineNumber: lnumber + 1, })) }) }) Context("when skip is > 0", func() { It("returns the appropriate location from the stack", func() { _, fname, lnumber, _ := runtime.Caller(0) cl := clWithSkip(1) Ω(cl).Should(Equal(types.CodeLocation{ FileName: fname, LineNumber: lnumber + 1, })) _, fname, lnumber, _ = runtime.Caller(0) cl = func() types.CodeLocation { return clWithSkip(2) }() // this is the line that's expected Ω(cl).Should(Equal(types.CodeLocation{ FileName: fname, LineNumber: lnumber + 3, })) }) }) Describe("when a function has been marked as a helper", func() { It("does not include that function when generating a code location", func() { _, fname, lnumber, _ := runtime.Caller(0) cl := helperFunction() Ω(cl).Should(Equal(types.CodeLocation{ FileName: fname, LineNumber: lnumber + 1, })) _, fname, lnumber, _ = runtime.Caller(0) cl = func() types.CodeLocation { GinkgoHelper() return func() types.CodeLocation { types.MarkAsHelper() return helperFunction() }() }() // this is the line that's expected Ω(cl).Should(Equal(types.CodeLocation{ FileName: fname, LineNumber: lnumber + 7, })) }) }) }) Describe("stringer behavior", func() { It("should stringify nicely", func() { _, fname, lnumber, _ := runtime.Caller(0) cl := types.NewCodeLocation(0) Ω(cl.String()).Should(Equal(fmt.Sprintf("%s:%d", fname, lnumber+1))) }) }) Describe("with a custom message", func() { It("emits the custom message", func() { cl := types.NewCustomCodeLocation("I'm right here.") Ω(cl.String()).Should(Equal("I'm right here.")) }) }) Describe("Fetching the line from the file in question", func() { It("works", func() { cl := types.NewCodeLocation(0) cl.LineNumber = cl.LineNumber - 2 Ω(cl.ContentsOfLine()).Should(Equal("\tDescribe(\"Fetching the line from the file in question\", func() {")) }) It("returns empty string if the line is not found or is out of bounds", func() { cl := types.CodeLocation{ FileName: "foo.go", LineNumber: 0, } Ω(cl.ContentsOfLine()).Should(BeZero()) cl = types.NewCodeLocation(0) cl.LineNumber = cl.LineNumber + 1000000 Ω(cl.ContentsOfLine()).Should(BeZero()) }) }) Describe("PruneStack", func() { It("should remove any references to ginkgo and pkg/testing and pkg/runtime", func() { // Hard-coded string, loosely based on what debug.Stack() produces. input := `Skip: skip() /Skip/me Skip: skip() /Skip/me Something: Func() /Users/whoever/gospace/src/github.com/onsi/ginkgo/whatever.go:10 (0x12314) SomethingInternalToGinkgo: Func() /Users/whoever/gospace/src/github.com/onsi/ginkgo/whatever_else.go:10 (0x12314) Oops: BlowUp() /usr/goroot/pkg/strings/oops.go:10 (0x12341) MyCode: Func() /Users/whoever/gospace/src/mycode/code.go:10 (0x12341) MyCodeTest: Func() /Users/whoever/gospace/src/mycode/code_test.go:10 (0x12341) TestFoo: RunSpecs(t, "Foo Suite") /Users/whoever/gospace/src/mycode/code_suite_test.go:12 (0x37f08) TestingT: Blah() /usr/goroot/pkg/testing/testing.go:12 (0x37f08) Something: Func() /usr/goroot/pkg/runtime/runtime.go:12 (0x37f08) ` prunedStack := types.PruneStack(input, 1) Ω(prunedStack).Should(Equal(`Oops: BlowUp() /usr/goroot/pkg/strings/oops.go:10 (0x12341) MyCode: Func() /Users/whoever/gospace/src/mycode/code.go:10 (0x12341) MyCodeTest: Func() /Users/whoever/gospace/src/mycode/code_test.go:10 (0x12341) TestFoo: RunSpecs(t, "Foo Suite") /Users/whoever/gospace/src/mycode/code_suite_test.go:12 (0x37f08)`)) }) }) }) golang-github-onsi-ginkgo-v2-2.22.0/types/config.go000066400000000000000000001172651472321612100220460ustar00rootroot00000000000000/* Ginkgo accepts a number of configuration options. These are documented [here](http://onsi.github.io/ginkgo/#the-ginkgo-cli) */ package types import ( "flag" "os" "path/filepath" "runtime" "strconv" "strings" "time" ) // Configuration controlling how an individual test suite is run type SuiteConfig struct { RandomSeed int64 RandomizeAllSpecs bool FocusStrings []string SkipStrings []string FocusFiles []string SkipFiles []string LabelFilter string FailOnPending bool FailOnEmpty bool FailFast bool FlakeAttempts int MustPassRepeatedly int DryRun bool PollProgressAfter time.Duration PollProgressInterval time.Duration Timeout time.Duration EmitSpecProgress bool // this is deprecated but its removal is causing compile issue for some users that were setting it manually OutputInterceptorMode string SourceRoots []string GracePeriod time.Duration ParallelProcess int ParallelTotal int ParallelHost string } func NewDefaultSuiteConfig() SuiteConfig { return SuiteConfig{ RandomSeed: time.Now().Unix(), Timeout: time.Hour, ParallelProcess: 1, ParallelTotal: 1, GracePeriod: 30 * time.Second, } } type VerbosityLevel uint const ( VerbosityLevelSuccinct VerbosityLevel = iota VerbosityLevelNormal VerbosityLevelVerbose VerbosityLevelVeryVerbose ) func (vl VerbosityLevel) GT(comp VerbosityLevel) bool { return vl > comp } func (vl VerbosityLevel) GTE(comp VerbosityLevel) bool { return vl >= comp } func (vl VerbosityLevel) Is(comp VerbosityLevel) bool { return vl == comp } func (vl VerbosityLevel) LTE(comp VerbosityLevel) bool { return vl <= comp } func (vl VerbosityLevel) LT(comp VerbosityLevel) bool { return vl < comp } // Configuration for Ginkgo's reporter type ReporterConfig struct { NoColor bool Succinct bool Verbose bool VeryVerbose bool FullTrace bool ShowNodeEvents bool GithubOutput bool SilenceSkips bool ForceNewlines bool JSONReport string JUnitReport string TeamcityReport string } func (rc ReporterConfig) Verbosity() VerbosityLevel { if rc.Succinct { return VerbosityLevelSuccinct } else if rc.Verbose { return VerbosityLevelVerbose } else if rc.VeryVerbose { return VerbosityLevelVeryVerbose } return VerbosityLevelNormal } func (rc ReporterConfig) WillGenerateReport() bool { return rc.JSONReport != "" || rc.JUnitReport != "" || rc.TeamcityReport != "" } func NewDefaultReporterConfig() ReporterConfig { return ReporterConfig{} } // Configuration for the Ginkgo CLI type CLIConfig struct { //for build, run, and watch Recurse bool SkipPackage string RequireSuite bool NumCompilers int //for run and watch only Procs int Parallel bool AfterRunHook string OutputDir string KeepSeparateCoverprofiles bool KeepSeparateReports bool //for run only KeepGoing bool UntilItFails bool Repeat int RandomizeSuites bool //for watch only Depth int WatchRegExp string } func NewDefaultCLIConfig() CLIConfig { return CLIConfig{ Depth: 1, WatchRegExp: `\.go$`, } } func (g CLIConfig) ComputedProcs() int { if g.Procs > 0 { return g.Procs } n := 1 if g.Parallel { n = runtime.NumCPU() if n > 4 { n = n - 1 } } return n } func (g CLIConfig) ComputedNumCompilers() int { if g.NumCompilers > 0 { return g.NumCompilers } return runtime.NumCPU() } // Configuration for the Ginkgo CLI capturing available go flags // A subset of Go flags are exposed by Ginkgo. Some are available at compile time (e.g. ginkgo build) and others only at run time (e.g. ginkgo run - which has both build and run time flags). // More details can be found at: // https://docs.google.com/spreadsheets/d/1zkp-DS4hU4sAJl5eHh1UmgwxCPQhf3s5a8fbiOI8tJU/ type GoFlagsConfig struct { //build-time flags for code-and-performance analysis Race bool Cover bool CoverMode string CoverPkg string Vet string //run-time flags for code-and-performance analysis BlockProfile string BlockProfileRate int CoverProfile string CPUProfile string MemProfile string MemProfileRate int MutexProfile string MutexProfileFraction int Trace string //build-time flags for building A bool ASMFlags string BuildMode string BuildVCS bool Compiler string GCCGoFlags string GCFlags string InstallSuffix string LDFlags string LinkShared bool Mod string N bool ModFile string ModCacheRW bool MSan bool PkgDir string Tags string TrimPath bool ToolExec string Work bool X bool O string } func NewDefaultGoFlagsConfig() GoFlagsConfig { return GoFlagsConfig{} } func (g GoFlagsConfig) BinaryMustBePreserved() bool { return g.BlockProfile != "" || g.CPUProfile != "" || g.MemProfile != "" || g.MutexProfile != "" } // Configuration that were deprecated in 2.0 type deprecatedConfig struct { DebugParallel bool NoisySkippings bool NoisyPendings bool RegexScansFilePath bool SlowSpecThresholdWithFLoatUnits float64 Stream bool Notify bool EmitSpecProgress bool SlowSpecThreshold time.Duration AlwaysEmitGinkgoWriter bool } // Flags // Flags sections used by both the CLI and the Ginkgo test process var FlagSections = GinkgoFlagSections{ {Key: "multiple-suites", Style: "{{dark-green}}", Heading: "Running Multiple Test Suites"}, {Key: "order", Style: "{{green}}", Heading: "Controlling Test Order"}, {Key: "parallel", Style: "{{yellow}}", Heading: "Controlling Test Parallelism"}, {Key: "low-level-parallel", Style: "{{yellow}}", Heading: "Controlling Test Parallelism", Description: "These are set by the Ginkgo CLI, {{red}}{{bold}}do not set them manually{{/}} via go test.\nUse ginkgo -p or ginkgo -procs=N instead."}, {Key: "filter", Style: "{{cyan}}", Heading: "Filtering Tests"}, {Key: "failure", Style: "{{red}}", Heading: "Failure Handling"}, {Key: "output", Style: "{{magenta}}", Heading: "Controlling Output Formatting"}, {Key: "code-and-coverage-analysis", Style: "{{orange}}", Heading: "Code and Coverage Analysis"}, {Key: "performance-analysis", Style: "{{coral}}", Heading: "Performance Analysis"}, {Key: "debug", Style: "{{blue}}", Heading: "Debugging Tests", Description: "In addition to these flags, Ginkgo supports a few debugging environment variables. To change the parallel server protocol set {{blue}}GINKGO_PARALLEL_PROTOCOL{{/}} to {{bold}}HTTP{{/}}. To avoid pruning callstacks set {{blue}}GINKGO_PRUNE_STACK{{/}} to {{bold}}FALSE{{/}}."}, {Key: "watch", Style: "{{light-yellow}}", Heading: "Controlling Ginkgo Watch"}, {Key: "misc", Style: "{{light-gray}}", Heading: "Miscellaneous"}, {Key: "go-build", Style: "{{light-gray}}", Heading: "Go Build Flags", Succinct: true, Description: "These flags are inherited from go build. Run {{bold}}ginkgo help build{{/}} for more detailed flag documentation."}, } // SuiteConfigFlags provides flags for the Ginkgo test process, and CLI var SuiteConfigFlags = GinkgoFlags{ {KeyPath: "S.RandomSeed", Name: "seed", SectionKey: "order", UsageDefaultValue: "randomly generated by Ginkgo", Usage: "The seed used to randomize the spec suite.", AlwaysExport: true}, {KeyPath: "S.RandomizeAllSpecs", Name: "randomize-all", SectionKey: "order", DeprecatedName: "randomizeAllSpecs", DeprecatedDocLink: "changed-command-line-flags", Usage: "If set, ginkgo will randomize all specs together. By default, ginkgo only randomizes the top level Describe, Context and When containers."}, {KeyPath: "S.FailOnPending", Name: "fail-on-pending", SectionKey: "failure", DeprecatedName: "failOnPending", DeprecatedDocLink: "changed-command-line-flags", Usage: "If set, ginkgo will mark the test suite as failed if any specs are pending."}, {KeyPath: "S.FailFast", Name: "fail-fast", SectionKey: "failure", DeprecatedName: "failFast", DeprecatedDocLink: "changed-command-line-flags", Usage: "If set, ginkgo will stop running a test suite after a failure occurs."}, {KeyPath: "S.FlakeAttempts", Name: "flake-attempts", SectionKey: "failure", UsageDefaultValue: "0 - failed tests are not retried", DeprecatedName: "flakeAttempts", DeprecatedDocLink: "changed-command-line-flags", Usage: "Make up to this many attempts to run each spec. If any of the attempts succeed, the suite will not be failed."}, {KeyPath: "S.FailOnEmpty", Name: "fail-on-empty", SectionKey: "failure", Usage: "If set, ginkgo will mark the test suite as failed if no specs are run."}, {KeyPath: "S.DryRun", Name: "dry-run", SectionKey: "debug", DeprecatedName: "dryRun", DeprecatedDocLink: "changed-command-line-flags", Usage: "If set, ginkgo will walk the test hierarchy without actually running anything. Best paired with -v."}, {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.GracePeriod", Name: "grace-period", SectionKey: "debug", UsageDefaultValue: "30s", Usage: "When interrupted, Ginkgo will wait for GracePeriod for the current running node to exit before moving on to the next one."}, {KeyPath: "S.OutputInterceptorMode", Name: "output-interceptor-mode", SectionKey: "debug", UsageArgument: "dup, swap, or none", Usage: "If set, ginkgo will use the specified output interception strategy when running in parallel. Defaults to dup on unix and swap on windows."}, {KeyPath: "S.LabelFilter", Name: "label-filter", SectionKey: "filter", UsageArgument: "expression", Usage: "If set, ginkgo will only run specs with labels that match the label-filter. The passed-in expression can include boolean operations (!, &&, ||, ','), groupings via '()', and regular expressions '/regexp/'. e.g. '(cat || dog) && !fruit'"}, {KeyPath: "S.FocusStrings", Name: "focus", SectionKey: "filter", Usage: "If set, ginkgo will only run specs that match this regular expression. Can be specified multiple times, values are ORed."}, {KeyPath: "S.SkipStrings", Name: "skip", SectionKey: "filter", Usage: "If set, ginkgo will only run specs that do not match this regular expression. Can be specified multiple times, values are ORed."}, {KeyPath: "S.FocusFiles", Name: "focus-file", SectionKey: "filter", UsageArgument: "file (regexp) | file:line | file:lineA-lineB | file:line,line,line", Usage: "If set, ginkgo will only run specs in matching files. Can be specified multiple times, values are ORed."}, {KeyPath: "S.SkipFiles", Name: "skip-file", SectionKey: "filter", UsageArgument: "file (regexp) | file:line | file:lineA-lineB | file:line,line,line", Usage: "If set, ginkgo will skip specs in matching files. Can be specified multiple times, values are ORed."}, {KeyPath: "D.RegexScansFilePath", DeprecatedName: "regexScansFilePath", DeprecatedDocLink: "removed--regexscansfilepath", DeprecatedVersion: "2.0.0"}, {KeyPath: "D.DebugParallel", DeprecatedName: "debug", DeprecatedDocLink: "removed--debug", DeprecatedVersion: "2.0.0"}, {KeyPath: "D.EmitSpecProgress", DeprecatedName: "progress", SectionKey: "debug", DeprecatedVersion: "2.5.0", Usage: ". The functionality provided by --progress was confusing and is no longer needed. Use --show-node-events instead to see node entry and exit events included in the timeline of failed and verbose specs. Or you can run with -vv to always see all node events. Lastly, --poll-progress-after and the PollProgressAfter decorator now provide a better mechanism for debugging specs that tend to get stuck."}, } // ParallelConfigFlags provides flags for the Ginkgo test process (not the CLI) var ParallelConfigFlags = GinkgoFlags{ {KeyPath: "S.ParallelProcess", Name: "parallel.process", SectionKey: "low-level-parallel", UsageDefaultValue: "1", Usage: "This worker process's (one-indexed) process number. For running specs in parallel."}, {KeyPath: "S.ParallelTotal", Name: "parallel.total", SectionKey: "low-level-parallel", UsageDefaultValue: "1", Usage: "The total number of worker processes. For running specs in parallel."}, {KeyPath: "S.ParallelHost", Name: "parallel.host", SectionKey: "low-level-parallel", UsageDefaultValue: "set by Ginkgo CLI", Usage: "The address for the server that will synchronize the processes."}, } // ReporterConfigFlags provides flags for the Ginkgo test process, and CLI var ReporterConfigFlags = GinkgoFlags{ {KeyPath: "R.NoColor", Name: "no-color", SectionKey: "output", DeprecatedName: "noColor", DeprecatedDocLink: "changed-command-line-flags", Usage: "If set, suppress color output in default reporter. You can also set the environment variable GINKGO_NO_COLOR=TRUE"}, {KeyPath: "R.Verbose", Name: "v", SectionKey: "output", Usage: "If set, emits more output including GinkgoWriter contents."}, {KeyPath: "R.VeryVerbose", Name: "vv", SectionKey: "output", Usage: "If set, emits with maximal verbosity - includes skipped and pending tests."}, {KeyPath: "R.Succinct", Name: "succinct", SectionKey: "output", Usage: "If set, default reporter prints out a very succinct report"}, {KeyPath: "R.FullTrace", Name: "trace", SectionKey: "output", Usage: "If set, default reporter prints out the full stack trace when a failure occurs"}, {KeyPath: "R.ShowNodeEvents", Name: "show-node-events", SectionKey: "output", Usage: "If set, default reporter prints node > Enter and < Exit events when specs fail"}, {KeyPath: "R.GithubOutput", Name: "github-output", SectionKey: "output", Usage: "If set, default reporter prints easier to manage output in Github Actions."}, {KeyPath: "R.SilenceSkips", Name: "silence-skips", SectionKey: "output", Usage: "If set, default reporter will not print out skipped tests."}, {KeyPath: "R.ForceNewlines", Name: "force-newlines", SectionKey: "output", Usage: "If set, default reporter will ensure a newline appears after each test."}, {KeyPath: "R.JSONReport", Name: "json-report", UsageArgument: "filename.json", SectionKey: "output", Usage: "If set, Ginkgo will generate a JSON-formatted test report at the specified location."}, {KeyPath: "R.JUnitReport", Name: "junit-report", UsageArgument: "filename.xml", SectionKey: "output", DeprecatedName: "reportFile", DeprecatedDocLink: "improved-reporting-infrastructure", Usage: "If set, Ginkgo will generate a conformant junit test report in the specified file."}, {KeyPath: "R.TeamcityReport", Name: "teamcity-report", UsageArgument: "filename", SectionKey: "output", Usage: "If set, Ginkgo will generate a Teamcity-formatted test report at the specified location."}, {KeyPath: "D.SlowSpecThresholdWithFLoatUnits", DeprecatedName: "slowSpecThreshold", DeprecatedDocLink: "changed--slowspecthreshold", Usage: "use --slow-spec-threshold instead and pass in a duration string (e.g. '5s', not '5.0')"}, {KeyPath: "D.NoisyPendings", DeprecatedName: "noisyPendings", DeprecatedDocLink: "removed--noisypendings-and--noisyskippings", DeprecatedVersion: "2.0.0"}, {KeyPath: "D.NoisySkippings", DeprecatedName: "noisySkippings", DeprecatedDocLink: "removed--noisypendings-and--noisyskippings", DeprecatedVersion: "2.0.0"}, {KeyPath: "D.SlowSpecThreshold", DeprecatedName: "slow-spec-threshold", SectionKey: "output", Usage: "--slow-spec-threshold has been deprecated and will be removed in a future version of Ginkgo. This feature has proved to be more noisy than useful. You can use --poll-progress-after, instead, to get more actionable feedback about potentially slow specs and understand where they might be getting stuck.", DeprecatedVersion: "2.5.0"}, {KeyPath: "D.AlwaysEmitGinkgoWriter", DeprecatedName: "always-emit-ginkgo-writer", SectionKey: "output", Usage: " - use -v instead, or one of Ginkgo's machine-readable report formats to get GinkgoWriter output for passing specs."}, } // BuildTestSuiteFlagSet attaches to the CommandLine flagset and provides flags for the Ginkgo test process func BuildTestSuiteFlagSet(suiteConfig *SuiteConfig, reporterConfig *ReporterConfig) (GinkgoFlagSet, error) { flags := SuiteConfigFlags.CopyAppend(ParallelConfigFlags...).CopyAppend(ReporterConfigFlags...) flags = flags.WithPrefix("ginkgo") bindings := map[string]interface{}{ "S": suiteConfig, "R": reporterConfig, "D": &deprecatedConfig{}, } extraGoFlagsSection := GinkgoFlagSection{Style: "{{gray}}", Heading: "Go test flags"} return NewAttachedGinkgoFlagSet(flag.CommandLine, flags, bindings, FlagSections, extraGoFlagsSection) } // VetConfig validates that the Ginkgo test process' configuration is sound func VetConfig(flagSet GinkgoFlagSet, suiteConfig SuiteConfig, reporterConfig ReporterConfig) []error { errors := []error{} if flagSet.WasSet("count") || flagSet.WasSet("test.count") { flag := flagSet.Lookup("count") if flag == nil { flag = flagSet.Lookup("test.count") } count, err := strconv.Atoi(flag.Value.String()) if err != nil || count != 1 { errors = append(errors, GinkgoErrors.InvalidGoFlagCount()) } } if flagSet.WasSet("parallel") || flagSet.WasSet("test.parallel") { errors = append(errors, GinkgoErrors.InvalidGoFlagParallel()) } if suiteConfig.ParallelTotal < 1 { errors = append(errors, GinkgoErrors.InvalidParallelTotalConfiguration()) } if suiteConfig.ParallelProcess > suiteConfig.ParallelTotal || suiteConfig.ParallelProcess < 1 { errors = append(errors, GinkgoErrors.InvalidParallelProcessConfiguration()) } if suiteConfig.ParallelTotal > 1 && suiteConfig.ParallelHost == "" { errors = append(errors, GinkgoErrors.MissingParallelHostConfiguration()) } if suiteConfig.DryRun && suiteConfig.ParallelTotal > 1 { errors = append(errors, GinkgoErrors.DryRunInParallelConfiguration()) } if suiteConfig.GracePeriod <= 0 { errors = append(errors, GinkgoErrors.GracePeriodCannotBeZero()) } if len(suiteConfig.FocusFiles) > 0 { _, err := ParseFileFilters(suiteConfig.FocusFiles) if err != nil { errors = append(errors, err) } } if len(suiteConfig.SkipFiles) > 0 { _, err := ParseFileFilters(suiteConfig.SkipFiles) if err != nil { errors = append(errors, err) } } if suiteConfig.LabelFilter != "" { _, err := ParseLabelFilter(suiteConfig.LabelFilter) if err != nil { errors = append(errors, err) } } switch strings.ToLower(suiteConfig.OutputInterceptorMode) { case "", "dup", "swap", "none": default: errors = append(errors, GinkgoErrors.InvalidOutputInterceptorModeConfiguration(suiteConfig.OutputInterceptorMode)) } numVerbosity := 0 for _, v := range []bool{reporterConfig.Succinct, reporterConfig.Verbose, reporterConfig.VeryVerbose} { if v { numVerbosity++ } } if numVerbosity > 1 { errors = append(errors, GinkgoErrors.ConflictingVerbosityConfiguration()) } return errors } // GinkgoCLISharedFlags provides flags shared by the Ginkgo CLI's build, watch, and run commands var GinkgoCLISharedFlags = GinkgoFlags{ {KeyPath: "C.Recurse", Name: "r", SectionKey: "multiple-suites", Usage: "If set, ginkgo finds and runs test suites under the current directory recursively."}, {KeyPath: "C.SkipPackage", Name: "skip-package", SectionKey: "multiple-suites", DeprecatedName: "skipPackage", DeprecatedDocLink: "changed-command-line-flags", UsageArgument: "comma-separated list of packages", Usage: "A comma-separated list of package names to be skipped. If any part of the package's path matches, that package is ignored."}, {KeyPath: "C.RequireSuite", Name: "require-suite", SectionKey: "failure", DeprecatedName: "requireSuite", DeprecatedDocLink: "changed-command-line-flags", Usage: "If set, Ginkgo fails if there are ginkgo tests in a directory but no invocation of RunSpecs."}, {KeyPath: "C.NumCompilers", Name: "compilers", SectionKey: "multiple-suites", UsageDefaultValue: "0 (will autodetect)", Usage: "When running multiple packages, the number of concurrent compilations to perform."}, } // GinkgoCLIRunAndWatchFlags provides flags shared by the Ginkgo CLI's build and watch commands (but not run) var GinkgoCLIRunAndWatchFlags = GinkgoFlags{ {KeyPath: "C.Procs", Name: "procs", SectionKey: "parallel", UsageDefaultValue: "1 (run in series)", Usage: "The number of parallel test nodes to run."}, {KeyPath: "C.Procs", Name: "nodes", SectionKey: "parallel", UsageDefaultValue: "1 (run in series)", Usage: "--nodes is an alias for --procs"}, {KeyPath: "C.Parallel", Name: "p", SectionKey: "parallel", Usage: "If set, ginkgo will run in parallel with an auto-detected number of nodes."}, {KeyPath: "C.AfterRunHook", Name: "after-run-hook", SectionKey: "misc", DeprecatedName: "afterSuiteHook", DeprecatedDocLink: "changed-command-line-flags", Usage: "Command to run when a test suite completes."}, {KeyPath: "C.OutputDir", Name: "output-dir", SectionKey: "output", UsageArgument: "directory", DeprecatedName: "outputdir", DeprecatedDocLink: "improved-profiling-support", Usage: "A location to place all generated profiles and reports."}, {KeyPath: "C.KeepSeparateCoverprofiles", Name: "keep-separate-coverprofiles", SectionKey: "code-and-coverage-analysis", Usage: "If set, Ginkgo does not merge coverprofiles into one monolithic coverprofile. The coverprofiles will remain in their respective package directories or in -output-dir if set."}, {KeyPath: "C.KeepSeparateReports", Name: "keep-separate-reports", SectionKey: "output", Usage: "If set, Ginkgo does not merge per-suite reports (e.g. -json-report) into one monolithic report for the entire testrun. The reports will remain in their respective package directories or in -output-dir if set."}, {KeyPath: "D.Stream", DeprecatedName: "stream", DeprecatedDocLink: "removed--stream", DeprecatedVersion: "2.0.0"}, {KeyPath: "D.Notify", DeprecatedName: "notify", DeprecatedDocLink: "removed--notify", DeprecatedVersion: "2.0.0"}, } // GinkgoCLIRunFlags provides flags for Ginkgo CLI's run command that aren't shared by any other commands var GinkgoCLIRunFlags = GinkgoFlags{ {KeyPath: "C.KeepGoing", Name: "keep-going", SectionKey: "multiple-suites", DeprecatedName: "keepGoing", DeprecatedDocLink: "changed-command-line-flags", Usage: "If set, failures from earlier test suites do not prevent later test suites from running."}, {KeyPath: "C.UntilItFails", Name: "until-it-fails", SectionKey: "debug", DeprecatedName: "untilItFails", DeprecatedDocLink: "changed-command-line-flags", Usage: "If set, ginkgo will keep rerunning test suites until a failure occurs."}, {KeyPath: "C.Repeat", Name: "repeat", SectionKey: "debug", UsageArgument: "n", UsageDefaultValue: "0 - i.e. no repetition, run only once", Usage: "The number of times to re-run a test-suite. Useful for debugging flaky tests. If set to N the suite will be run N+1 times and will be required to pass each time."}, {KeyPath: "C.RandomizeSuites", Name: "randomize-suites", SectionKey: "order", DeprecatedName: "randomizeSuites", DeprecatedDocLink: "changed-command-line-flags", Usage: "If set, ginkgo will randomize the order in which test suites run."}, } // GinkgoCLIRunFlags provides flags for Ginkgo CLI's watch command that aren't shared by any other commands var GinkgoCLIWatchFlags = GinkgoFlags{ {KeyPath: "C.Depth", Name: "depth", SectionKey: "watch", Usage: "Ginkgo will watch dependencies down to this depth in the dependency tree."}, {KeyPath: "C.WatchRegExp", Name: "watch-regexp", SectionKey: "watch", DeprecatedName: "watchRegExp", DeprecatedDocLink: "changed-command-line-flags", UsageArgument: "Regular Expression", UsageDefaultValue: `\.go$`, Usage: "Only files matching this regular expression will be watched for changes."}, } // GoBuildFlags provides flags for the Ginkgo CLI build, run, and watch commands that capture go's build-time flags. These are passed to go test -c by the ginkgo CLI var GoBuildFlags = GinkgoFlags{ {KeyPath: "Go.Race", Name: "race", SectionKey: "code-and-coverage-analysis", Usage: "enable data race detection. Supported on linux/amd64, linux/ppc64le, linux/arm64, linux/s390x, freebsd/amd64, netbsd/amd64, darwin/amd64, darwin/arm64, and windows/amd64."}, {KeyPath: "Go.Vet", Name: "vet", UsageArgument: "list", SectionKey: "code-and-coverage-analysis", Usage: `Configure the invocation of "go vet" during "go test" to use the comma-separated list of vet checks. If list is empty, "go test" runs "go vet" with a curated list of checks believed to be always worth addressing. If list is "off", "go test" does not run "go vet" at all. Available checks can be found by running 'go doc cmd/vet'`}, {KeyPath: "Go.Cover", Name: "cover", SectionKey: "code-and-coverage-analysis", Usage: "Enable coverage analysis. Note that because coverage works by annotating the source code before compilation, compilation and test failures with coverage enabled may report line numbers that don't correspond to the original sources."}, {KeyPath: "Go.CoverMode", Name: "covermode", UsageArgument: "set,count,atomic", SectionKey: "code-and-coverage-analysis", Usage: `Set the mode for coverage analysis for the package[s] being tested. 'set': does this statement run? 'count': how many times does this statement run? 'atomic': like count, but correct in multithreaded tests and more expensive (must use atomic with -race). Sets -cover`}, {KeyPath: "Go.CoverPkg", Name: "coverpkg", UsageArgument: "pattern1,pattern2,pattern3", SectionKey: "code-and-coverage-analysis", Usage: "Apply coverage analysis in each test to packages matching the patterns. The default is for each test to analyze only the package being tested. See 'go help packages' for a description of package patterns. Sets -cover."}, {KeyPath: "Go.A", Name: "a", SectionKey: "go-build", Usage: "force rebuilding of packages that are already up-to-date."}, {KeyPath: "Go.ASMFlags", Name: "asmflags", UsageArgument: "'[pattern=]arg list'", SectionKey: "go-build", Usage: "arguments to pass on each go tool asm invocation."}, {KeyPath: "Go.BuildMode", Name: "buildmode", UsageArgument: "mode", SectionKey: "go-build", Usage: "build mode to use. See 'go help buildmode' for more."}, {KeyPath: "Go.BuildVCS", Name: "buildvcs", SectionKey: "go-build", Usage: "adds version control information."}, {KeyPath: "Go.Compiler", Name: "compiler", UsageArgument: "name", SectionKey: "go-build", Usage: "name of compiler to use, as in runtime.Compiler (gccgo or gc)."}, {KeyPath: "Go.GCCGoFlags", Name: "gccgoflags", UsageArgument: "'[pattern=]arg list'", SectionKey: "go-build", Usage: "arguments to pass on each gccgo compiler/linker invocation."}, {KeyPath: "Go.GCFlags", Name: "gcflags", UsageArgument: "'[pattern=]arg list'", SectionKey: "go-build", Usage: "arguments to pass on each go tool compile invocation."}, {KeyPath: "Go.InstallSuffix", Name: "installsuffix", SectionKey: "go-build", Usage: "a suffix to use in the name of the package installation directory, in order to keep output separate from default builds. If using the -race flag, the install suffix is automatically set to raceor, if set explicitly, has _race appended to it. Likewise for the -msan flag. Using a -buildmode option that requires non-default compile flags has a similar effect."}, {KeyPath: "Go.LDFlags", Name: "ldflags", UsageArgument: "'[pattern=]arg list'", SectionKey: "go-build", Usage: "arguments to pass on each go tool link invocation."}, {KeyPath: "Go.LinkShared", Name: "linkshared", SectionKey: "go-build", Usage: "build code that will be linked against shared libraries previously created with -buildmode=shared."}, {KeyPath: "Go.Mod", Name: "mod", UsageArgument: "mode (readonly, vendor, or mod)", SectionKey: "go-build", Usage: "module download mode to use: readonly, vendor, or mod. See 'go help modules' for more."}, {KeyPath: "Go.ModCacheRW", Name: "modcacherw", SectionKey: "go-build", Usage: "leave newly-created directories in the module cache read-write instead of making them read-only."}, {KeyPath: "Go.ModFile", Name: "modfile", UsageArgument: "file", SectionKey: "go-build", Usage: `in module aware mode, read (and possibly write) an alternate go.mod file instead of the one in the module root directory. A file named go.mod must still be present in order to determine the module root directory, but it is not accessed. When -modfile is specified, an alternate go.sum file is also used: its path is derived from the -modfile flag by trimming the ".mod" extension and appending ".sum".`}, {KeyPath: "Go.MSan", Name: "msan", SectionKey: "go-build", Usage: "enable interoperation with memory sanitizer. Supported only on linux/amd64, linux/arm64 and only with Clang/LLVM as the host C compiler. On linux/arm64, pie build mode will be used."}, {KeyPath: "Go.N", Name: "n", SectionKey: "go-build", Usage: "print the commands but do not run them."}, {KeyPath: "Go.PkgDir", Name: "pkgdir", UsageArgument: "dir", SectionKey: "go-build", Usage: "install and load all packages from dir instead of the usual locations. For example, when building with a non-standard configuration, use -pkgdir to keep generated packages in a separate location."}, {KeyPath: "Go.Tags", Name: "tags", UsageArgument: "tag,list", SectionKey: "go-build", Usage: "a comma-separated list of build tags to consider satisfied during the build. For more information about build tags, see the description of build constraints in the documentation for the go/build package. (Earlier versions of Go used a space-separated list, and that form is deprecated but still recognized.)"}, {KeyPath: "Go.TrimPath", Name: "trimpath", SectionKey: "go-build", Usage: `remove all file system paths from the resulting executable. Instead of absolute file system paths, the recorded file names will begin with either "go" (for the standard library), or a module path@version (when using modules), or a plain import path (when using GOPATH).`}, {KeyPath: "Go.ToolExec", Name: "toolexec", UsageArgument: "'cmd args'", SectionKey: "go-build", Usage: "a program to use to invoke toolchain programs like vet and asm. For example, instead of running asm, the go command will run cmd args /path/to/asm '."}, {KeyPath: "Go.Work", Name: "work", SectionKey: "go-build", Usage: "print the name of the temporary work directory and do not delete it when exiting."}, {KeyPath: "Go.X", Name: "x", SectionKey: "go-build", Usage: "print the commands."}, {KeyPath: "Go.O", Name: "o", SectionKey: "go-build", Usage: "output binary path (including name)."}, } // GoRunFlags provides flags for the Ginkgo CLI run, and watch commands that capture go's run-time flags. These are passed to the compiled test binary by the ginkgo CLI var GoRunFlags = GinkgoFlags{ {KeyPath: "Go.CoverProfile", Name: "coverprofile", UsageArgument: "file", SectionKey: "code-and-coverage-analysis", Usage: `Write a coverage profile to the file after all tests have passed. Sets -cover.`}, {KeyPath: "Go.BlockProfile", Name: "blockprofile", UsageArgument: "file", SectionKey: "performance-analysis", Usage: `Write a goroutine blocking profile to the specified file when all tests are complete. Preserves test binary.`}, {KeyPath: "Go.BlockProfileRate", Name: "blockprofilerate", UsageArgument: "rate", SectionKey: "performance-analysis", Usage: `Control the detail provided in goroutine blocking profiles by calling runtime.SetBlockProfileRate with rate. See 'go doc runtime.SetBlockProfileRate'. The profiler aims to sample, on average, one blocking event every n nanoseconds the program spends blocked. By default, if -test.blockprofile is set without this flag, all blocking events are recorded, equivalent to -test.blockprofilerate=1.`}, {KeyPath: "Go.CPUProfile", Name: "cpuprofile", UsageArgument: "file", SectionKey: "performance-analysis", Usage: `Write a CPU profile to the specified file before exiting. Preserves test binary.`}, {KeyPath: "Go.MemProfile", Name: "memprofile", UsageArgument: "file", SectionKey: "performance-analysis", Usage: `Write an allocation profile to the file after all tests have passed. Preserves test binary.`}, {KeyPath: "Go.MemProfileRate", Name: "memprofilerate", UsageArgument: "rate", SectionKey: "performance-analysis", Usage: `Enable more precise (and expensive) memory allocation profiles by setting runtime.MemProfileRate. See 'go doc runtime.MemProfileRate'. To profile all memory allocations, use -test.memprofilerate=1.`}, {KeyPath: "Go.MutexProfile", Name: "mutexprofile", UsageArgument: "file", SectionKey: "performance-analysis", Usage: `Write a mutex contention profile to the specified file when all tests are complete. Preserves test binary.`}, {KeyPath: "Go.MutexProfileFraction", Name: "mutexprofilefraction", UsageArgument: "n", SectionKey: "performance-analysis", Usage: `if >= 0, calls runtime.SetMutexProfileFraction() Sample 1 in n stack traces of goroutines holding a contended mutex.`}, {KeyPath: "Go.Trace", Name: "execution-trace", UsageArgument: "file", ExportAs: "trace", SectionKey: "performance-analysis", Usage: `Write an execution trace to the specified file before exiting.`}, } // VetAndInitializeCLIAndGoConfig validates that the Ginkgo CLI's configuration is sound // It returns a potentially mutated copy of the config that rationalizes the configuration to ensure consistency for downstream consumers func VetAndInitializeCLIAndGoConfig(cliConfig CLIConfig, goFlagsConfig GoFlagsConfig) (CLIConfig, GoFlagsConfig, []error) { errors := []error{} if cliConfig.Repeat > 0 && cliConfig.UntilItFails { errors = append(errors, GinkgoErrors.BothRepeatAndUntilItFails()) } //initialize the output directory if cliConfig.OutputDir != "" { err := os.MkdirAll(cliConfig.OutputDir, 0777) if err != nil { errors = append(errors, err) } } //ensure cover mode is configured appropriately if goFlagsConfig.CoverMode != "" || goFlagsConfig.CoverPkg != "" || goFlagsConfig.CoverProfile != "" { goFlagsConfig.Cover = true } if goFlagsConfig.Cover && goFlagsConfig.CoverProfile == "" { goFlagsConfig.CoverProfile = "coverprofile.out" } return cliConfig, goFlagsConfig, errors } // GenerateGoTestCompileArgs is used by the Ginkgo CLI to generate command line arguments to pass to the go test -c command when compiling the test func GenerateGoTestCompileArgs(goFlagsConfig GoFlagsConfig, packageToBuild string, pathToInvocationPath string) ([]string, error) { // if the user has set the CoverProfile run-time flag make sure to set the build-time cover flag to make sure // the built test binary can generate a coverprofile if goFlagsConfig.CoverProfile != "" { goFlagsConfig.Cover = true } if goFlagsConfig.CoverPkg != "" { coverPkgs := strings.Split(goFlagsConfig.CoverPkg, ",") adjustedCoverPkgs := make([]string, len(coverPkgs)) for i, coverPkg := range coverPkgs { coverPkg = strings.Trim(coverPkg, " ") if strings.HasPrefix(coverPkg, "./") { // this is a relative coverPkg - we need to reroot it adjustedCoverPkgs[i] = "./" + filepath.Join(pathToInvocationPath, strings.TrimPrefix(coverPkg, "./")) } else { // this is a package name - don't touch it adjustedCoverPkgs[i] = coverPkg } } goFlagsConfig.CoverPkg = strings.Join(adjustedCoverPkgs, ",") } args := []string{"test", "-c", packageToBuild} goArgs, err := GenerateFlagArgs( GoBuildFlags, map[string]interface{}{ "Go": &goFlagsConfig, }, ) if err != nil { return []string{}, err } args = append(args, goArgs...) return args, nil } // GenerateGinkgoTestRunArgs is used by the Ginkgo CLI to generate command line arguments to pass to the compiled Ginkgo test binary func GenerateGinkgoTestRunArgs(suiteConfig SuiteConfig, reporterConfig ReporterConfig, goFlagsConfig GoFlagsConfig) ([]string, error) { var flags GinkgoFlags flags = SuiteConfigFlags.WithPrefix("ginkgo") flags = flags.CopyAppend(ParallelConfigFlags.WithPrefix("ginkgo")...) flags = flags.CopyAppend(ReporterConfigFlags.WithPrefix("ginkgo")...) flags = flags.CopyAppend(GoRunFlags.WithPrefix("test")...) bindings := map[string]interface{}{ "S": &suiteConfig, "R": &reporterConfig, "Go": &goFlagsConfig, } return GenerateFlagArgs(flags, bindings) } // GenerateGoTestRunArgs is used by the Ginkgo CLI to generate command line arguments to pass to the compiled non-Ginkgo test binary func GenerateGoTestRunArgs(goFlagsConfig GoFlagsConfig) ([]string, error) { flags := GoRunFlags.WithPrefix("test") bindings := map[string]interface{}{ "Go": &goFlagsConfig, } args, err := GenerateFlagArgs(flags, bindings) if err != nil { return args, err } args = append(args, "--test.v") return args, nil } // BuildRunCommandFlagSet builds the FlagSet for the `ginkgo run` command func BuildRunCommandFlagSet(suiteConfig *SuiteConfig, reporterConfig *ReporterConfig, cliConfig *CLIConfig, goFlagsConfig *GoFlagsConfig) (GinkgoFlagSet, error) { flags := SuiteConfigFlags flags = flags.CopyAppend(ReporterConfigFlags...) flags = flags.CopyAppend(GinkgoCLISharedFlags...) flags = flags.CopyAppend(GinkgoCLIRunAndWatchFlags...) flags = flags.CopyAppend(GinkgoCLIRunFlags...) flags = flags.CopyAppend(GoBuildFlags...) flags = flags.CopyAppend(GoRunFlags...) bindings := map[string]interface{}{ "S": suiteConfig, "R": reporterConfig, "C": cliConfig, "Go": goFlagsConfig, "D": &deprecatedConfig{}, } return NewGinkgoFlagSet(flags, bindings, FlagSections) } // BuildWatchCommandFlagSet builds the FlagSet for the `ginkgo watch` command func BuildWatchCommandFlagSet(suiteConfig *SuiteConfig, reporterConfig *ReporterConfig, cliConfig *CLIConfig, goFlagsConfig *GoFlagsConfig) (GinkgoFlagSet, error) { flags := SuiteConfigFlags flags = flags.CopyAppend(ReporterConfigFlags...) flags = flags.CopyAppend(GinkgoCLISharedFlags...) flags = flags.CopyAppend(GinkgoCLIRunAndWatchFlags...) flags = flags.CopyAppend(GinkgoCLIWatchFlags...) flags = flags.CopyAppend(GoBuildFlags...) flags = flags.CopyAppend(GoRunFlags...) bindings := map[string]interface{}{ "S": suiteConfig, "R": reporterConfig, "C": cliConfig, "Go": goFlagsConfig, "D": &deprecatedConfig{}, } return NewGinkgoFlagSet(flags, bindings, FlagSections) } // BuildBuildCommandFlagSet builds the FlagSet for the `ginkgo build` command func BuildBuildCommandFlagSet(cliConfig *CLIConfig, goFlagsConfig *GoFlagsConfig) (GinkgoFlagSet, error) { flags := GinkgoCLISharedFlags flags = flags.CopyAppend(GoBuildFlags...) bindings := map[string]interface{}{ "C": cliConfig, "Go": goFlagsConfig, "D": &deprecatedConfig{}, } flagSections := make(GinkgoFlagSections, len(FlagSections)) copy(flagSections, FlagSections) for i := range flagSections { if flagSections[i].Key == "multiple-suites" { flagSections[i].Heading = "Building Multiple Suites" } if flagSections[i].Key == "go-build" { flagSections[i] = GinkgoFlagSection{Key: "go-build", Style: "{{/}}", Heading: "Go Build Flags", Description: "These flags are inherited from go build."} } } return NewGinkgoFlagSet(flags, bindings, flagSections) } func BuildLabelsCommandFlagSet(cliConfig *CLIConfig) (GinkgoFlagSet, error) { flags := GinkgoCLISharedFlags.SubsetWithNames("r", "skip-package") bindings := map[string]interface{}{ "C": cliConfig, } flagSections := make(GinkgoFlagSections, len(FlagSections)) copy(flagSections, FlagSections) for i := range flagSections { if flagSections[i].Key == "multiple-suites" { flagSections[i].Heading = "Fetching Labels from Multiple Suites" } } return NewGinkgoFlagSet(flags, bindings, flagSections) } golang-github-onsi-ginkgo-v2-2.22.0/types/config_test.go000066400000000000000000000220221472321612100230670ustar00rootroot00000000000000package types_test import ( "flag" "net/http" . "github.com/onsi/ginkgo/v2" "github.com/onsi/ginkgo/v2/types" . "github.com/onsi/gomega" "github.com/onsi/gomega/ghttp" ) var _ = Describe("Config", func() { It("has valid deprecation doc links", func() { flags := types.SuiteConfigFlags flags = flags.CopyAppend(types.ParallelConfigFlags...) flags = flags.CopyAppend(types.ReporterConfigFlags...) flags = flags.CopyAppend(types.GinkgoCLISharedFlags...) flags = flags.CopyAppend(types.GinkgoCLIRunAndWatchFlags...) flags = flags.CopyAppend(types.GinkgoCLIRunFlags...) flags = flags.CopyAppend(types.GinkgoCLIWatchFlags...) flags = flags.CopyAppend(types.GoBuildFlags...) flags = flags.CopyAppend(types.GoRunFlags...) for _, flag := range flags { if flag.DeprecatedDocLink != "" { Ω(anchors.DocAnchors["MIGRATING_TO_V2.md"]).Should(ContainElement(flag.DeprecatedDocLink)) } } }) Describe("ReporterConfig", func() { Describe("WillGenerateReport", func() { It("returns true if it will generate a report", func() { repConf := types.ReporterConfig{} Ω(repConf.WillGenerateReport()).Should(BeFalse()) repConf = types.ReporterConfig{JSONReport: "foo"} Ω(repConf.WillGenerateReport()).Should(BeTrue()) repConf = types.ReporterConfig{JUnitReport: "foo"} Ω(repConf.WillGenerateReport()).Should(BeTrue()) repConf = types.ReporterConfig{TeamcityReport: "foo"} Ω(repConf.WillGenerateReport()).Should(BeTrue()) }) }) Describe("Verbosity", func() { It("returns the appropriate verbosity level", func() { repConf := types.ReporterConfig{} Ω(repConf.Verbosity()).Should(Equal(types.VerbosityLevelNormal)) repConf = types.ReporterConfig{Succinct: true} Ω(repConf.Verbosity()).Should(Equal(types.VerbosityLevelSuccinct)) repConf = types.ReporterConfig{Verbose: true} Ω(repConf.Verbosity()).Should(Equal(types.VerbosityLevelVerbose)) repConf = types.ReporterConfig{VeryVerbose: true} Ω(repConf.Verbosity()).Should(Equal(types.VerbosityLevelVeryVerbose)) }) It("can do verbosity math", func() { Ω(types.VerbosityLevelNormal.LT(types.VerbosityLevelVeryVerbose)).Should(BeTrue()) Ω(types.VerbosityLevelNormal.LT(types.VerbosityLevelVerbose)).Should(BeTrue()) Ω(types.VerbosityLevelNormal.LT(types.VerbosityLevelNormal)).Should(BeFalse()) Ω(types.VerbosityLevelNormal.LT(types.VerbosityLevelSuccinct)).Should(BeFalse()) Ω(types.VerbosityLevelNormal.LTE(types.VerbosityLevelVeryVerbose)).Should(BeTrue()) Ω(types.VerbosityLevelNormal.LTE(types.VerbosityLevelVerbose)).Should(BeTrue()) Ω(types.VerbosityLevelNormal.LTE(types.VerbosityLevelNormal)).Should(BeTrue()) Ω(types.VerbosityLevelNormal.LTE(types.VerbosityLevelSuccinct)).Should(BeFalse()) Ω(types.VerbosityLevelNormal.GT(types.VerbosityLevelVeryVerbose)).Should(BeFalse()) Ω(types.VerbosityLevelNormal.GT(types.VerbosityLevelVerbose)).Should(BeFalse()) Ω(types.VerbosityLevelNormal.GT(types.VerbosityLevelNormal)).Should(BeFalse()) Ω(types.VerbosityLevelNormal.GT(types.VerbosityLevelSuccinct)).Should(BeTrue()) Ω(types.VerbosityLevelNormal.GTE(types.VerbosityLevelVeryVerbose)).Should(BeFalse()) Ω(types.VerbosityLevelNormal.GTE(types.VerbosityLevelVerbose)).Should(BeFalse()) Ω(types.VerbosityLevelNormal.GTE(types.VerbosityLevelNormal)).Should(BeTrue()) Ω(types.VerbosityLevelNormal.GTE(types.VerbosityLevelSuccinct)).Should(BeTrue()) Ω(types.VerbosityLevelNormal.Is(types.VerbosityLevelVeryVerbose)).Should(BeFalse()) Ω(types.VerbosityLevelNormal.Is(types.VerbosityLevelVerbose)).Should(BeFalse()) Ω(types.VerbosityLevelNormal.Is(types.VerbosityLevelNormal)).Should(BeTrue()) Ω(types.VerbosityLevelNormal.Is(types.VerbosityLevelSuccinct)).Should(BeFalse()) }) }) }) Describe("VetConfig", func() { var suiteConf types.SuiteConfig var repConf types.ReporterConfig var flagSet types.GinkgoFlagSet var goFlagSet *flag.FlagSet BeforeEach(func() { var err error goFlagSet = flag.NewFlagSet("test", flag.ContinueOnError) goFlagSet.Int("count", 1, "") goFlagSet.Int("parallel", 0, "") flagSet, err = types.NewAttachedGinkgoFlagSet(goFlagSet, types.GinkgoFlags{}, nil, types.GinkgoFlagSections{}, types.GinkgoFlagSection{}) Ω(err).ShouldNot(HaveOccurred()) suiteConf = types.NewDefaultSuiteConfig() repConf = types.NewDefaultReporterConfig() }) Context("when all is well", func() { It("returns no errors", func() { errors := types.VetConfig(flagSet, suiteConf, repConf) Ω(errors).Should(BeEmpty()) }) }) Context("when unsupported go flags are parsed", func() { BeforeEach(func() { goFlagSet.Parse([]string{"-count=2", "-parallel=2"}) }) It("returns errors when unsupported go flags are set", func() { errors := types.VetConfig(flagSet, suiteConf, repConf) Ω(errors).Should(ConsistOf(types.GinkgoErrors.InvalidGoFlagCount(), types.GinkgoErrors.InvalidGoFlagParallel())) }) }) Describe("errors related to parallelism", func() { Context("when parallel total is less than one", func() { BeforeEach(func() { suiteConf.ParallelTotal = 0 }) It("errors", func() { errors := types.VetConfig(flagSet, suiteConf, repConf) Ω(errors).Should(ContainElement(types.GinkgoErrors.InvalidParallelTotalConfiguration())) }) }) Context("when parallel node is less than one", func() { BeforeEach(func() { suiteConf.ParallelProcess = 0 }) It("errors", func() { errors := types.VetConfig(flagSet, suiteConf, repConf) Ω(errors).Should(ConsistOf(types.GinkgoErrors.InvalidParallelProcessConfiguration())) }) }) Context("when parallel node is greater than parallel total", func() { BeforeEach(func() { suiteConf.ParallelProcess = suiteConf.ParallelTotal + 1 }) It("errors", func() { errors := types.VetConfig(flagSet, suiteConf, repConf) Ω(errors).Should(ConsistOf(types.GinkgoErrors.InvalidParallelProcessConfiguration())) }) }) Context("when running in parallel", func() { var server *ghttp.Server BeforeEach(func() { server = ghttp.NewServer() server.SetAllowUnhandledRequests(true) server.SetUnhandledRequestStatusCode(http.StatusOK) suiteConf.ParallelTotal = 2 suiteConf.ParallelHost = server.URL() }) AfterEach(func() { server.Close() }) Context("and parallel host is not set", func() { BeforeEach(func() { suiteConf.ParallelHost = "" }) It("errors", func() { errors := types.VetConfig(flagSet, suiteConf, repConf) Ω(errors).Should(ConsistOf(types.GinkgoErrors.MissingParallelHostConfiguration())) }) }) Context("when trying to dry run in parallel", func() { BeforeEach(func() { suiteConf.DryRun = true }) It("errors", func() { errors := types.VetConfig(flagSet, suiteConf, repConf) Ω(errors).Should(ConsistOf(types.GinkgoErrors.DryRunInParallelConfiguration())) }) }) }) }) Describe("file filter errors", func() { Context("with an invalid --focus-file and/or --skip-file", func() { BeforeEach(func() { suiteConf.FocusFiles = append(suiteConf.FocusFiles, "bloop:123a") suiteConf.SkipFiles = append(suiteConf.SkipFiles, "bloop:123b") }) It("errors", func() { errors := types.VetConfig(flagSet, suiteConf, repConf) Ω(errors).Should(ConsistOf(types.GinkgoErrors.InvalidFileFilter("bloop:123a"), types.GinkgoErrors.InvalidFileFilter("bloop:123b"))) }) }) }) Describe("validating --output-interceptor-mode", func() { It("errors if an invalid output interceptor mode is specified", func() { suiteConf.OutputInterceptorMode = "DURP" errors := types.VetConfig(flagSet, suiteConf, repConf) Ω(errors).Should(ConsistOf(types.GinkgoErrors.InvalidOutputInterceptorModeConfiguration("DURP"))) for _, value := range []string{"", "dup", "DUP", "swap", "SWAP", "none", "NONE"} { suiteConf.OutputInterceptorMode = value errors = types.VetConfig(flagSet, suiteConf, repConf) Ω(errors).Should(BeEmpty()) } }) }) Context("when more than one verbosity flag is set", func() { It("errors", func() { repConf.Succinct, repConf.Verbose, repConf.VeryVerbose = true, true, false errors := types.VetConfig(flagSet, suiteConf, repConf) Ω(errors).Should(ConsistOf(types.GinkgoErrors.ConflictingVerbosityConfiguration())) repConf.Succinct, repConf.Verbose, repConf.VeryVerbose = true, false, true errors = types.VetConfig(flagSet, suiteConf, repConf) Ω(errors).Should(ConsistOf(types.GinkgoErrors.ConflictingVerbosityConfiguration())) repConf.Succinct, repConf.Verbose, repConf.VeryVerbose = false, true, true errors = types.VetConfig(flagSet, suiteConf, repConf) Ω(errors).Should(ConsistOf(types.GinkgoErrors.ConflictingVerbosityConfiguration())) repConf.Succinct, repConf.Verbose, repConf.VeryVerbose = true, true, true errors = types.VetConfig(flagSet, suiteConf, repConf) Ω(errors).Should(ConsistOf(types.GinkgoErrors.ConflictingVerbosityConfiguration())) }) }) }) }) golang-github-onsi-ginkgo-v2-2.22.0/types/deprecated_support_test.go000066400000000000000000000111211472321612100255140ustar00rootroot00000000000000package types_test import ( "os" "reflect" "strings" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" "github.com/onsi/ginkgo/v2/formatter" "github.com/onsi/ginkgo/v2/types" ) var _ = Describe("Deprecation Support", func() { Describe("Tracking Deprecations", func() { var tracker *types.DeprecationTracker BeforeEach(func() { tracker = types.NewDeprecationTracker() formatter.SingletonFormatter.ColorMode = formatter.ColorModePassthrough }) AfterEach(func() { formatter.SingletonFormatter.ColorMode = formatter.ColorModeTerminal }) Context("with no tracked deprecations", func() { It("reports no tracked deprecations", func() { Ω(tracker.DidTrackDeprecations()).Should(BeFalse()) }) }) Context("with tracked dependencies", func() { BeforeEach(func() { tracker.TrackDeprecation(types.Deprecation{ Message: "Deprecation 1", DocLink: "doclink-1", }, types.CodeLocation{FileName: "foo.go", LineNumber: 17}) tracker.TrackDeprecation(types.Deprecation{ Message: "Deprecation 1", DocLink: "doclink-1", }, types.CodeLocation{FileName: "bar.go", LineNumber: 30}) tracker.TrackDeprecation(types.Deprecation{ Message: "Deprecation 2", DocLink: "doclink-2", }) tracker.TrackDeprecation(types.Deprecation{ Message: "Deprecation 3", }, types.CodeLocation{FileName: "baz.go", LineNumber: 72}) }) It("reports tracked deprecations", func() { Ω(tracker.DidTrackDeprecations()).Should(BeTrue()) }) It("generates a nicely formatted report", func() { report := tracker.DeprecationsReport() Ω(report).Should(HavePrefix("{{light-yellow}}You're using deprecated Ginkgo functionality:{{/}}\n{{light-yellow}}============================================={{/}}\n")) Ω(report).Should(ContainSubstring(strings.Join([]string{ " {{yellow}}Deprecation 1{{/}}", " {{bold}}Learn more at:{{/}} {{cyan}}{{underline}}https://onsi.github.io/ginkgo/MIGRATING_TO_V2#doclink-1{{/}}", " {{gray}}foo.go:17{{/}}", " {{gray}}bar.go:30{{/}}", "", }, "\n"))) Ω(report).Should(ContainSubstring(strings.Join([]string{ " {{yellow}}Deprecation 2{{/}}", " {{bold}}Learn more at:{{/}} {{cyan}}{{underline}}https://onsi.github.io/ginkgo/MIGRATING_TO_V2#doclink-2{{/}}", "", }, "\n"))) Ω(report).Should(ContainSubstring(strings.Join([]string{ " {{yellow}}Deprecation 3{{/}}", " {{gray}}baz.go:72{{/}}", }, "\n"))) }) It("validates that all deprecations point to working documentation", func() { v := reflect.ValueOf(types.Deprecations) Ω(v.NumMethod()).Should(BeNumerically(">", 0)) for i := 0; i < v.NumMethod(); i += 1 { m := v.Method(i) deprecation := m.Call([]reflect.Value{})[0].Interface().(types.Deprecation) if deprecation.DocLink != "" { Ω(anchors.DocAnchors["MIGRATING_TO_V2.md"]).Should(ContainElement(deprecation.DocLink)) } } }) }) Context("when ACK_GINKGO_DEPRECATIONS is set", func() { var origEnv string BeforeEach(func() { origEnv = os.Getenv("ACK_GINKGO_DEPRECATIONS") os.Setenv("ACK_GINKGO_DEPRECATIONS", "v1.18.3-boop") }) AfterEach(func() { os.Setenv("ACK_GINKGO_DEPRECATIONS", origEnv) }) It("does not track deprecations with lower version numbers", func() { tracker.TrackDeprecation(types.Deprecation{Message: "Deprecation A", Version: "0.19.2"}) tracker.TrackDeprecation(types.Deprecation{Message: "Deprecation B", Version: "1.17.4"}) tracker.TrackDeprecation(types.Deprecation{Message: "Deprecation C", Version: "1.18.2"}) tracker.TrackDeprecation(types.Deprecation{Message: "Deprecation D", Version: "1.18.3"}) tracker.TrackDeprecation(types.Deprecation{Message: "Deprecation E", Version: "1.18.4"}) tracker.TrackDeprecation(types.Deprecation{Message: "Deprecation F", Version: "1.19.2"}) tracker.TrackDeprecation(types.Deprecation{Message: "Deprecation G", Version: "2.0.0"}) tracker.TrackDeprecation(types.Deprecation{Message: "Deprecation H"}) report := tracker.DeprecationsReport() Ω(report).ShouldNot(ContainSubstring("Deprecation A")) Ω(report).ShouldNot(ContainSubstring("Deprecation B")) Ω(report).ShouldNot(ContainSubstring("Deprecation C")) Ω(report).ShouldNot(ContainSubstring("Deprecation D")) Ω(report).Should(ContainSubstring("Deprecation E")) Ω(report).Should(ContainSubstring("Deprecation F")) Ω(report).Should(ContainSubstring("Deprecation G")) Ω(report).Should(ContainSubstring("Deprecation H")) Ω(report).Should(ContainSubstring("ACK_GINKGO_DEPRECATIONS=")) }) }) }) }) golang-github-onsi-ginkgo-v2-2.22.0/types/deprecated_types.go000066400000000000000000000066111472321612100241150ustar00rootroot00000000000000package types import ( "strconv" "time" ) /* A set of deprecations to make the transition from v1 to v2 easier for users who have written custom reporters. */ type SuiteSummary = DeprecatedSuiteSummary type SetupSummary = DeprecatedSetupSummary type SpecSummary = DeprecatedSpecSummary type SpecMeasurement = DeprecatedSpecMeasurement type SpecComponentType = NodeType type SpecFailure = DeprecatedSpecFailure var ( SpecComponentTypeInvalid = NodeTypeInvalid SpecComponentTypeContainer = NodeTypeContainer SpecComponentTypeIt = NodeTypeIt SpecComponentTypeBeforeEach = NodeTypeBeforeEach SpecComponentTypeJustBeforeEach = NodeTypeJustBeforeEach SpecComponentTypeAfterEach = NodeTypeAfterEach SpecComponentTypeJustAfterEach = NodeTypeJustAfterEach SpecComponentTypeBeforeSuite = NodeTypeBeforeSuite SpecComponentTypeSynchronizedBeforeSuite = NodeTypeSynchronizedBeforeSuite SpecComponentTypeAfterSuite = NodeTypeAfterSuite SpecComponentTypeSynchronizedAfterSuite = NodeTypeSynchronizedAfterSuite ) type DeprecatedSuiteSummary struct { SuiteDescription string SuiteSucceeded bool SuiteID string NumberOfSpecsBeforeParallelization int NumberOfTotalSpecs int NumberOfSpecsThatWillBeRun int NumberOfPendingSpecs int NumberOfSkippedSpecs int NumberOfPassedSpecs int NumberOfFailedSpecs int NumberOfFlakedSpecs int RunTime time.Duration } type DeprecatedSetupSummary struct { ComponentType SpecComponentType CodeLocation CodeLocation State SpecState RunTime time.Duration Failure SpecFailure CapturedOutput string SuiteID string } type DeprecatedSpecSummary struct { ComponentTexts []string ComponentCodeLocations []CodeLocation State SpecState RunTime time.Duration Failure SpecFailure IsMeasurement bool NumberOfSamples int Measurements map[string]*DeprecatedSpecMeasurement CapturedOutput string SuiteID string } func (s DeprecatedSpecSummary) HasFailureState() bool { return s.State.Is(SpecStateFailureStates) } func (s DeprecatedSpecSummary) TimedOut() bool { return false } func (s DeprecatedSpecSummary) Panicked() bool { return s.State == SpecStatePanicked } func (s DeprecatedSpecSummary) Failed() bool { return s.State == SpecStateFailed } func (s DeprecatedSpecSummary) Passed() bool { return s.State == SpecStatePassed } func (s DeprecatedSpecSummary) Skipped() bool { return s.State == SpecStateSkipped } func (s DeprecatedSpecSummary) Pending() bool { return s.State == SpecStatePending } type DeprecatedSpecFailure struct { Message string Location CodeLocation ForwardedPanic string ComponentIndex int ComponentType SpecComponentType ComponentCodeLocation CodeLocation } type DeprecatedSpecMeasurement struct { Name string Info interface{} Order int Results []float64 Smallest float64 Largest float64 Average float64 StdDeviation float64 SmallestLabel string LargestLabel string AverageLabel string Units string Precision int } func (s DeprecatedSpecMeasurement) PrecisionFmt() string { if s.Precision == 0 { return "%f" } str := strconv.Itoa(s.Precision) return "%." + str + "f" } golang-github-onsi-ginkgo-v2-2.22.0/types/deprecated_types_test.go000066400000000000000000000065631472321612100251620ustar00rootroot00000000000000package types_test import ( . "github.com/onsi/ginkgo/v2" // . "github.com/onsi/gomega" ) var _ = Describe("DeprecatedTypes", func() { // Describe("DeprecatedSetupSummaryFromSpecReport", func() { // It("converts to the v1 summary format", func() { // cl1 := types.CodeLocation{FileName: "foo.go", LineNumber: 3} // cl2 := types.CodeLocation{FileName: "bar.go", LineNumber: 5} // Ω(types.DeprecatedSetupSummaryFromSpecReport(types.SpecReport{ // LeafNodeType: types.NodeTypeBeforeSuite, // LeafNodeLocation: cl1, // State: types.SpecStateFailed, // RunTime: time.Hour, // CapturedGinkgoWriterOutput: "ginkgo-writer-output", // CapturedStdOutErr: "std-output", // Failure: types.Failure{ // Message: "failure message", // Location: cl2, // ForwardedPanic: "forwarded panic", // NodeIndex: 2, // NodeType: types.NodeTypeBeforeSuite, // }, // })).Should(Equal( // &types.SetupSummary{ // ComponentType: types.SpecComponentTypeBeforeSuite, // CodeLocation: cl1, // State: types.SpecStateFailed, // RunTime: time.Hour, // CapturedOutput: "std-output\nginkgo-writer-output", // Failure: types.SpecFailure{ // Message: "failure message", // Location: cl2, // ForwardedPanic: "forwarded panic", // ComponentIndex: 2, // ComponentType: types.SpecComponentTypeBeforeSuite, // ComponentCodeLocation: cl2, // }, // }, // )) // }) // }) // Describe("DeprecatedSpecSummaryFromSpecReport", func() { // It("converts to the v1 summary format", func() { // cl1 := types.CodeLocation{FileName: "foo.go", LineNumber: 3} // cl2 := types.CodeLocation{FileName: "bar.go", LineNumber: 5} // Ω(types.DeprecatedSpecSummaryFromSpecReport(types.SpecReport{ // NodeTexts: []string{"A", "B"}, // NodeLocations: []types.CodeLocation{cl1, cl2}, // LeafNodeType: types.NodeTypeBeforeSuite, // LeafNodeLocation: cl1, // State: types.SpecStateFailed, // RunTime: time.Hour, // CapturedGinkgoWriterOutput: "ginkgo-writer-output", // CapturedStdOutErr: "std-output", // Failure: types.Failure{ // Message: "failure message", // Location: cl2, // ForwardedPanic: "forwarded panic", // NodeIndex: 2, // NodeType: types.NodeTypeBeforeSuite, // }, // })).Should(Equal( // &types.SpecSummary{ // ComponentTexts: []string{"A", "B"}, // ComponentCodeLocations: []types.CodeLocation{cl1, cl2}, // State: types.SpecStateFailed, // RunTime: time.Hour, // CapturedOutput: "std-output\nginkgo-writer-output", // IsMeasurement: false, // Measurements: map[string]*types.SpecMeasurement{}, // Failure: types.SpecFailure{ // Message: "failure message", // Location: cl2, // ForwardedPanic: "forwarded panic", // ComponentIndex: 2, // ComponentType: types.SpecComponentTypeBeforeSuite, // ComponentCodeLocation: cl2, // }, // }, // )) // }) // }) }) golang-github-onsi-ginkgo-v2-2.22.0/types/deprecation_support.go000066400000000000000000000121361472321612100246610ustar00rootroot00000000000000package types import ( "os" "strconv" "strings" "sync" "unicode" "github.com/onsi/ginkgo/v2/formatter" ) type Deprecation struct { Message string DocLink string Version string } type deprecations struct{} var Deprecations = deprecations{} func (d deprecations) CustomReporter() Deprecation { return Deprecation{ Message: "Support for custom reporters has been removed in V2. Please read the documentation linked to below for Ginkgo's new behavior and for a migration path:", DocLink: "removed-custom-reporters", Version: "1.16.0", } } func (d deprecations) Async() Deprecation { return Deprecation{ Message: "You are passing a Done channel to a test node to test asynchronous behavior. This is deprecated in Ginkgo V2. Your test will run synchronously and the timeout will be ignored.", DocLink: "removed-async-testing", Version: "1.16.0", } } func (d deprecations) Measure() Deprecation { return Deprecation{ Message: "Measure is deprecated and has been removed from Ginkgo V2. Any Measure tests in your spec will not run. Please migrate to gomega/gmeasure.", DocLink: "removed-measure", Version: "1.16.3", } } func (d deprecations) ParallelNode() Deprecation { return Deprecation{ Message: "GinkgoParallelNode is deprecated and will be removed in Ginkgo V2. Please use GinkgoParallelProcess instead.", DocLink: "renamed-ginkgoparallelnode", Version: "1.16.4", } } func (d deprecations) CurrentGinkgoTestDescription() Deprecation { return Deprecation{ Message: "CurrentGinkgoTestDescription() is deprecated in Ginkgo V2. Use CurrentSpecReport() instead.", DocLink: "changed-currentginkgotestdescription", Version: "1.16.0", } } func (d deprecations) Convert() Deprecation { return Deprecation{ Message: "The convert command is deprecated in Ginkgo V2", DocLink: "removed-ginkgo-convert", Version: "1.16.0", } } func (d deprecations) Blur() Deprecation { return Deprecation{ Message: "The blur command is deprecated in Ginkgo V2. Use 'ginkgo unfocus' instead.", Version: "1.16.0", } } func (d deprecations) Nodot() Deprecation { return Deprecation{ Message: "The nodot command is deprecated in Ginkgo V2. Please either dot-import Ginkgo or use the package identifier in your code to references objects and types provided by Ginkgo and Gomega.", DocLink: "removed-ginkgo-nodot", Version: "1.16.0", } } func (d deprecations) SuppressProgressReporting() Deprecation { return Deprecation{ Message: "Improvements to how reporters emit timeline information means that SuppressProgressReporting is no longer necessary and has been deprecated.", Version: "2.5.0", } } type DeprecationTracker struct { deprecations map[Deprecation][]CodeLocation lock *sync.Mutex } func NewDeprecationTracker() *DeprecationTracker { return &DeprecationTracker{ deprecations: map[Deprecation][]CodeLocation{}, lock: &sync.Mutex{}, } } func (d *DeprecationTracker) TrackDeprecation(deprecation Deprecation, cl ...CodeLocation) { ackVersion := os.Getenv("ACK_GINKGO_DEPRECATIONS") if deprecation.Version != "" && ackVersion != "" { ack := ParseSemVer(ackVersion) version := ParseSemVer(deprecation.Version) if ack.GreaterThanOrEqualTo(version) { return } } d.lock.Lock() defer d.lock.Unlock() if len(cl) == 1 { d.deprecations[deprecation] = append(d.deprecations[deprecation], cl[0]) } else { d.deprecations[deprecation] = []CodeLocation{} } } func (d *DeprecationTracker) DidTrackDeprecations() bool { d.lock.Lock() defer d.lock.Unlock() return len(d.deprecations) > 0 } func (d *DeprecationTracker) DeprecationsReport() string { d.lock.Lock() defer d.lock.Unlock() out := formatter.F("{{light-yellow}}You're using deprecated Ginkgo functionality:{{/}}\n") out += formatter.F("{{light-yellow}}============================================={{/}}\n") for deprecation, locations := range d.deprecations { out += formatter.Fi(1, "{{yellow}}"+deprecation.Message+"{{/}}\n") if deprecation.DocLink != "" { out += formatter.Fi(1, "{{bold}}Learn more at:{{/}} {{cyan}}{{underline}}https://onsi.github.io/ginkgo/MIGRATING_TO_V2#%s{{/}}\n", deprecation.DocLink) } for _, location := range locations { out += formatter.Fi(2, "{{gray}}%s{{/}}\n", location) } } out += formatter.F("\n{{gray}}To silence deprecations that can be silenced set the following environment variable:{{/}}\n") out += formatter.Fi(1, "{{gray}}ACK_GINKGO_DEPRECATIONS=%s{{/}}\n", VERSION) return out } type SemVer struct { Major int Minor int Patch int } func (s SemVer) GreaterThanOrEqualTo(o SemVer) bool { return (s.Major > o.Major) || (s.Major == o.Major && s.Minor > o.Minor) || (s.Major == o.Major && s.Minor == o.Minor && s.Patch >= o.Patch) } func ParseSemVer(semver string) SemVer { out := SemVer{} semver = strings.TrimFunc(semver, func(r rune) bool { return !(unicode.IsNumber(r) || r == '.') }) components := strings.Split(semver, ".") if len(components) > 0 { out.Major, _ = strconv.Atoi(components[0]) } if len(components) > 1 { out.Minor, _ = strconv.Atoi(components[1]) } if len(components) > 2 { out.Patch, _ = strconv.Atoi(components[2]) } return out } golang-github-onsi-ginkgo-v2-2.22.0/types/enum_support.go000066400000000000000000000016331472321612100233300ustar00rootroot00000000000000package types import "encoding/json" type EnumSupport struct { toString map[uint]string toEnum map[string]uint maxEnum uint } func NewEnumSupport(toString map[uint]string) EnumSupport { toEnum, maxEnum := map[string]uint{}, uint(0) for k, v := range toString { toEnum[v] = k if maxEnum < k { maxEnum = k } } return EnumSupport{toString: toString, toEnum: toEnum, maxEnum: maxEnum} } func (es EnumSupport) String(e uint) string { if e > es.maxEnum { return es.toString[0] } return es.toString[e] } func (es EnumSupport) UnmarshJSON(b []byte) (uint, error) { var dec string if err := json.Unmarshal(b, &dec); err != nil { return 0, err } out := es.toEnum[dec] // if we miss we get 0 which is what we want anyway return out, nil } func (es EnumSupport) MarshJSON(e uint) ([]byte, error) { if e == 0 || e > es.maxEnum { return json.Marshal(nil) } return json.Marshal(es.toString[e]) } golang-github-onsi-ginkgo-v2-2.22.0/types/errors.go000066400000000000000000000631531472321612100221110ustar00rootroot00000000000000package types import ( "fmt" "reflect" "strings" "github.com/onsi/ginkgo/v2/formatter" ) type GinkgoError struct { Heading string Message string DocLink string CodeLocation CodeLocation } func (g GinkgoError) Error() string { out := formatter.F("{{bold}}{{red}}%s{{/}}\n", g.Heading) if (g.CodeLocation != CodeLocation{}) { contentsOfLine := strings.TrimLeft(g.CodeLocation.ContentsOfLine(), "\t ") if contentsOfLine != "" { out += formatter.F("{{light-gray}}%s{{/}}\n", contentsOfLine) } out += formatter.F("{{gray}}%s{{/}}\n", g.CodeLocation) } if g.Message != "" { out += formatter.Fiw(1, formatter.COLS, g.Message) out += "\n\n" } if g.DocLink != "" { out += formatter.Fiw(1, formatter.COLS, "{{bold}}Learn more at:{{/}} {{cyan}}{{underline}}http://onsi.github.io/ginkgo/#%s{{/}}\n", g.DocLink) } return out } type ginkgoErrors struct{} var GinkgoErrors = ginkgoErrors{} func (g ginkgoErrors) UncaughtGinkgoPanic(cl CodeLocation) error { return GinkgoError{ Heading: "Your Test Panicked", Message: `When you, or your assertion library, calls Ginkgo's Fail(), Ginkgo panics to prevent subsequent assertions from running. Normally Ginkgo rescues this panic so you shouldn't see it. However, if you make an assertion in a goroutine, Ginkgo can't capture the panic. To circumvent this, you should call defer GinkgoRecover() at the top of the goroutine that caused this panic. Alternatively, you may have made an assertion outside of a Ginkgo leaf node (e.g. in a container node or some out-of-band function) - please move your assertion to an appropriate Ginkgo node (e.g. a BeforeSuite, BeforeEach, It, etc...).`, DocLink: "mental-model-how-ginkgo-handles-failure", CodeLocation: cl, } } func (g ginkgoErrors) RerunningSuite() error { return GinkgoError{ Heading: "Rerunning Suite", Message: formatter.F(`It looks like you are calling RunSpecs more than once. Ginkgo does not support rerunning suites. If you want to rerun a suite try {{bold}}ginkgo --repeat=N{{/}} or {{bold}}ginkgo --until-it-fails{{/}}`), DocLink: "repeating-spec-runs-and-managing-flaky-specs", } } /* Tree construction errors */ func (g ginkgoErrors) PushingNodeInRunPhase(nodeType NodeType, cl CodeLocation) error { return GinkgoError{ Heading: "Ginkgo detected an issue with your spec structure", Message: formatter.F( `It looks like you are trying to add a {{bold}}[%s]{{/}} node to the Ginkgo spec tree in a leaf node {{bold}}after{{/}} the specs started running. To enable randomization and parallelization Ginkgo requires the spec tree to be fully constructed up front. In practice, this means that you can only create nodes like {{bold}}[%s]{{/}} at the top-level or within the body of a {{bold}}Describe{{/}}, {{bold}}Context{{/}}, or {{bold}}When{{/}}.`, nodeType, nodeType), CodeLocation: cl, DocLink: "mental-model-how-ginkgo-traverses-the-spec-hierarchy", } } func (g ginkgoErrors) CaughtPanicDuringABuildPhase(caughtPanic interface{}, cl CodeLocation) error { return GinkgoError{ Heading: "Assertion or Panic detected during tree construction", Message: formatter.F( `Ginkgo detected a panic while constructing the spec tree. You may be trying to make an assertion in the body of a container node (i.e. {{bold}}Describe{{/}}, {{bold}}Context{{/}}, or {{bold}}When{{/}}). Please ensure all assertions are inside leaf nodes such as {{bold}}BeforeEach{{/}}, {{bold}}It{{/}}, etc. {{bold}}Here's the content of the panic that was caught:{{/}} %v`, caughtPanic), CodeLocation: cl, DocLink: "no-assertions-in-container-nodes", } } func (g ginkgoErrors) SuiteNodeInNestedContext(nodeType NodeType, cl CodeLocation) error { docLink := "suite-setup-and-cleanup-beforesuite-and-aftersuite" if nodeType.Is(NodeTypeReportBeforeSuite | NodeTypeReportAfterSuite) { docLink = "reporting-nodes---reportbeforesuite-and-reportaftersuite" } return GinkgoError{ Heading: "Ginkgo detected an issue with your spec structure", Message: formatter.F( `It looks like you are trying to add a {{bold}}[%s]{{/}} node within a container node. {{bold}}%s{{/}} can only be called at the top level.`, nodeType, nodeType), CodeLocation: cl, DocLink: docLink, } } func (g ginkgoErrors) SuiteNodeDuringRunPhase(nodeType NodeType, cl CodeLocation) error { docLink := "suite-setup-and-cleanup-beforesuite-and-aftersuite" if nodeType.Is(NodeTypeReportBeforeSuite | NodeTypeReportAfterSuite) { docLink = "reporting-nodes---reportbeforesuite-and-reportaftersuite" } return GinkgoError{ Heading: "Ginkgo detected an issue with your spec structure", Message: formatter.F( `It looks like you are trying to add a {{bold}}[%s]{{/}} node within a leaf node after the spec started running. {{bold}}%s{{/}} can only be called at the top level.`, nodeType, nodeType), CodeLocation: cl, DocLink: docLink, } } func (g ginkgoErrors) MultipleBeforeSuiteNodes(nodeType NodeType, cl CodeLocation, earlierNodeType NodeType, earlierCodeLocation CodeLocation) error { return ginkgoErrorMultipleSuiteNodes("setup", nodeType, cl, earlierNodeType, earlierCodeLocation) } func (g ginkgoErrors) MultipleAfterSuiteNodes(nodeType NodeType, cl CodeLocation, earlierNodeType NodeType, earlierCodeLocation CodeLocation) error { return ginkgoErrorMultipleSuiteNodes("teardown", nodeType, cl, earlierNodeType, earlierCodeLocation) } func ginkgoErrorMultipleSuiteNodes(setupOrTeardown string, nodeType NodeType, cl CodeLocation, earlierNodeType NodeType, earlierCodeLocation CodeLocation) error { return GinkgoError{ Heading: "Ginkgo detected an issue with your spec structure", Message: formatter.F( `It looks like you are trying to add a {{bold}}[%s]{{/}} node but you already have a {{bold}}[%s]{{/}} node defined at: {{gray}}%s{{/}}. Ginkgo only allows you to define one suite %s node.`, nodeType, earlierNodeType, earlierCodeLocation, setupOrTeardown), CodeLocation: cl, DocLink: "suite-setup-and-cleanup-beforesuite-and-aftersuite", } } /* Decorator errors */ func (g ginkgoErrors) InvalidDecoratorForNodeType(cl CodeLocation, nodeType NodeType, decorator string) error { return GinkgoError{ Heading: "Invalid Decorator", Message: formatter.F(`[%s] node cannot be passed a(n) '%s' decorator`, nodeType, decorator), CodeLocation: cl, DocLink: "node-decorators-overview", } } func (g ginkgoErrors) InvalidDeclarationOfFocusedAndPending(cl CodeLocation, nodeType NodeType) error { return GinkgoError{ Heading: "Invalid Combination of Decorators: Focused and Pending", Message: formatter.F(`[%s] node was decorated with both Focus and Pending. At most one is allowed.`, nodeType), CodeLocation: cl, DocLink: "node-decorators-overview", } } func (g ginkgoErrors) InvalidDeclarationOfFlakeAttemptsAndMustPassRepeatedly(cl CodeLocation, nodeType NodeType) error { return GinkgoError{ Heading: "Invalid Combination of Decorators: FlakeAttempts and MustPassRepeatedly", Message: formatter.F(`[%s] node was decorated with both FlakeAttempts and MustPassRepeatedly. At most one is allowed.`, nodeType), CodeLocation: cl, DocLink: "node-decorators-overview", } } func (g ginkgoErrors) UnknownDecorator(cl CodeLocation, nodeType NodeType, decorator interface{}) error { return GinkgoError{ Heading: "Unknown Decorator", Message: formatter.F(`[%s] node was passed an unknown decorator: '%#v'`, nodeType, decorator), CodeLocation: cl, DocLink: "node-decorators-overview", } } func (g ginkgoErrors) InvalidBodyTypeForContainer(t reflect.Type, cl CodeLocation, nodeType NodeType) error { return GinkgoError{ Heading: "Invalid Function", Message: formatter.F(`[%s] node must be passed {{bold}}func(){{/}} - i.e. functions that take nothing and return nothing. You passed {{bold}}%s{{/}} instead.`, nodeType, t), CodeLocation: cl, DocLink: "node-decorators-overview", } } func (g ginkgoErrors) InvalidBodyType(t reflect.Type, cl CodeLocation, nodeType NodeType) error { mustGet := "{{bold}}func(){{/}}, {{bold}}func(ctx SpecContext){{/}}, or {{bold}}func(ctx context.Context){{/}}" if nodeType.Is(NodeTypeContainer) { mustGet = "{{bold}}func(){{/}}" } return GinkgoError{ Heading: "Invalid Function", Message: formatter.F(`[%s] node must be passed `+mustGet+`. You passed {{bold}}%s{{/}} instead.`, nodeType, t), CodeLocation: cl, DocLink: "node-decorators-overview", } } func (g ginkgoErrors) InvalidBodyTypeForSynchronizedBeforeSuiteProc1(t reflect.Type, cl CodeLocation) error { mustGet := "{{bold}}func() []byte{{/}}, {{bold}}func(ctx SpecContext) []byte{{/}}, or {{bold}}func(ctx context.Context) []byte{{/}}, {{bold}}func(){{/}}, {{bold}}func(ctx SpecContext){{/}}, or {{bold}}func(ctx context.Context){{/}}" return GinkgoError{ Heading: "Invalid Function", Message: formatter.F(`[SynchronizedBeforeSuite] node must be passed `+mustGet+` for its first function. You passed {{bold}}%s{{/}} instead.`, t), CodeLocation: cl, DocLink: "node-decorators-overview", } } func (g ginkgoErrors) InvalidBodyTypeForSynchronizedBeforeSuiteAllProcs(t reflect.Type, cl CodeLocation) error { mustGet := "{{bold}}func(){{/}}, {{bold}}func(ctx SpecContext){{/}}, or {{bold}}func(ctx context.Context){{/}}, {{bold}}func([]byte){{/}}, {{bold}}func(ctx SpecContext, []byte){{/}}, or {{bold}}func(ctx context.Context, []byte){{/}}" return GinkgoError{ Heading: "Invalid Function", Message: formatter.F(`[SynchronizedBeforeSuite] node must be passed `+mustGet+` for its second function. You passed {{bold}}%s{{/}} instead.`, t), CodeLocation: cl, DocLink: "node-decorators-overview", } } func (g ginkgoErrors) MultipleBodyFunctions(cl CodeLocation, nodeType NodeType) error { return GinkgoError{ Heading: "Multiple Functions", Message: formatter.F(`[%s] node must be passed a single function - but more than one was passed in.`, nodeType), CodeLocation: cl, DocLink: "node-decorators-overview", } } func (g ginkgoErrors) MissingBodyFunction(cl CodeLocation, nodeType NodeType) error { return GinkgoError{ Heading: "Missing Functions", Message: formatter.F(`[%s] node must be passed a single function - but none was passed in.`, nodeType), CodeLocation: cl, DocLink: "node-decorators-overview", } } func (g ginkgoErrors) InvalidTimeoutOrGracePeriodForNonContextNode(cl CodeLocation, nodeType NodeType) error { return GinkgoError{ Heading: "Invalid NodeTimeout SpecTimeout, or GracePeriod", Message: formatter.F(`[%s] was passed NodeTimeout, SpecTimeout, or GracePeriod but does not have a callback that accepts a {{bold}}SpecContext{{/}} or {{bold}}context.Context{{/}}. You must accept a context to enable timeouts and grace periods`, nodeType), CodeLocation: cl, DocLink: "spec-timeouts-and-interruptible-nodes", } } func (g ginkgoErrors) InvalidTimeoutOrGracePeriodForNonContextCleanupNode(cl CodeLocation) error { return GinkgoError{ Heading: "Invalid NodeTimeout SpecTimeout, or GracePeriod", Message: formatter.F(`[DeferCleanup] was passed NodeTimeout or GracePeriod but does not have a callback that accepts a {{bold}}SpecContext{{/}} or {{bold}}context.Context{{/}}. You must accept a context to enable timeouts and grace periods`), CodeLocation: cl, DocLink: "spec-timeouts-and-interruptible-nodes", } } /* Ordered Container errors */ func (g ginkgoErrors) InvalidSerialNodeInNonSerialOrderedContainer(cl CodeLocation, nodeType NodeType) error { return GinkgoError{ Heading: "Invalid Serial Node in Non-Serial Ordered Container", Message: formatter.F(`[%s] node was decorated with Serial but occurs in an Ordered container that is not marked Serial. Move the Serial decorator to the outer-most Ordered container to mark all ordered specs within the container as serial.`, nodeType), CodeLocation: cl, DocLink: "node-decorators-overview", } } func (g ginkgoErrors) SetupNodeNotInOrderedContainer(cl CodeLocation, nodeType NodeType) error { return GinkgoError{ Heading: "Setup Node not in Ordered Container", Message: fmt.Sprintf("[%s] setup nodes must appear inside an Ordered container. They cannot be nested within other containers, even containers in an ordered container.", nodeType), CodeLocation: cl, DocLink: "ordered-containers", } } func (g ginkgoErrors) InvalidContinueOnFailureDecoration(cl CodeLocation) error { return GinkgoError{ Heading: "ContinueOnFailure not decorating an outermost Ordered Container", Message: "ContinueOnFailure can only decorate an Ordered container, and this Ordered container must be the outermost Ordered container.", CodeLocation: cl, DocLink: "ordered-containers", } } /* DeferCleanup errors */ func (g ginkgoErrors) DeferCleanupInvalidFunction(cl CodeLocation) error { return GinkgoError{ Heading: "DeferCleanup requires a valid function", Message: "You must pass DeferCleanup a function to invoke. This function must return zero or one values - if it does return, it must return an error. The function can take arbitrarily many arguments and you should provide these to DeferCleanup to pass along to the function.", CodeLocation: cl, DocLink: "cleaning-up-our-cleanup-code-defercleanup", } } func (g ginkgoErrors) PushingCleanupNodeDuringTreeConstruction(cl CodeLocation) error { return GinkgoError{ Heading: "DeferCleanup must be called inside a setup or subject node", Message: "You must call DeferCleanup inside a setup node (e.g. BeforeEach, BeforeSuite, AfterAll...) or a subject node (i.e. It). You can't call DeferCleanup at the top-level or in a container node - use the After* family of setup nodes instead.", CodeLocation: cl, DocLink: "cleaning-up-our-cleanup-code-defercleanup", } } func (g ginkgoErrors) PushingCleanupInReportingNode(cl CodeLocation, nodeType NodeType) error { return GinkgoError{ Heading: fmt.Sprintf("DeferCleanup cannot be called in %s", nodeType), Message: "Please inline your cleanup code - Ginkgo won't run cleanup code after a Reporting node.", CodeLocation: cl, DocLink: "cleaning-up-our-cleanup-code-defercleanup", } } func (g ginkgoErrors) PushingCleanupInCleanupNode(cl CodeLocation) error { return GinkgoError{ Heading: "DeferCleanup cannot be called in a DeferCleanup callback", Message: "Please inline your cleanup code - Ginkgo doesn't let you call DeferCleanup from within DeferCleanup", CodeLocation: cl, DocLink: "cleaning-up-our-cleanup-code-defercleanup", } } /* ReportEntry errors */ func (g ginkgoErrors) TooManyReportEntryValues(cl CodeLocation, arg interface{}) error { return GinkgoError{ Heading: "Too Many ReportEntry Values", Message: formatter.F(`{{bold}}AddGinkgoReport{{/}} can only be given one value. Got unexpected value: %#v`, arg), CodeLocation: cl, DocLink: "attaching-data-to-reports", } } func (g ginkgoErrors) AddReportEntryNotDuringRunPhase(cl CodeLocation) error { return GinkgoError{ Heading: "Ginkgo detected an issue with your spec structure", Message: formatter.F(`It looks like you are calling {{bold}}AddGinkgoReport{{/}} outside of a running spec. Make sure you call {{bold}}AddGinkgoReport{{/}} inside a runnable node such as It or BeforeEach and not inside the body of a container such as Describe or Context.`), CodeLocation: cl, DocLink: "attaching-data-to-reports", } } /* By errors */ func (g ginkgoErrors) ByNotDuringRunPhase(cl CodeLocation) error { return GinkgoError{ Heading: "Ginkgo detected an issue with your spec structure", Message: formatter.F(`It looks like you are calling {{bold}}By{{/}} outside of a running spec. Make sure you call {{bold}}By{{/}} inside a runnable node such as It or BeforeEach and not inside the body of a container such as Describe or Context.`), CodeLocation: cl, DocLink: "documenting-complex-specs-by", } } /* FileFilter and SkipFilter errors */ func (g ginkgoErrors) InvalidFileFilter(filter string) error { return GinkgoError{ Heading: "Invalid File Filter", Message: fmt.Sprintf(`The provided file filter: "%s" is invalid. File filters must have the format "file", "file:lines" where "file" is a regular expression that will match against the file path and lines is a comma-separated list of integers (e.g. file:1,5,7) or line-ranges (e.g. file:1-3,5-9) or both (e.g. file:1,5-9)`, filter), DocLink: "filtering-specs", } } func (g ginkgoErrors) InvalidFileFilterRegularExpression(filter string, err error) error { return GinkgoError{ Heading: "Invalid File Filter Regular Expression", Message: fmt.Sprintf(`The provided file filter: "%s" included an invalid regular expression. regexp.Compile error: %s`, filter, err), DocLink: "filtering-specs", } } /* Label Errors */ func (g ginkgoErrors) SyntaxErrorParsingLabelFilter(input string, location int, error string) error { var message string if location >= 0 { for i, r := range input { if i == location { message += "{{red}}{{bold}}{{underline}}" } message += string(r) if i == location { message += "{{/}}" } } } else { message = input } message += "\n" + error return GinkgoError{ Heading: "Syntax Error Parsing Label Filter", Message: message, DocLink: "spec-labels", } } func (g ginkgoErrors) InvalidLabel(label string, cl CodeLocation) error { return GinkgoError{ Heading: "Invalid Label", Message: fmt.Sprintf("'%s' is an invalid label. Labels cannot contain of the following characters: '&|!,()/'", label), CodeLocation: cl, DocLink: "spec-labels", } } func (g ginkgoErrors) InvalidEmptyLabel(cl CodeLocation) error { return GinkgoError{ Heading: "Invalid Empty Label", Message: "Labels cannot be empty", CodeLocation: cl, DocLink: "spec-labels", } } /* Table errors */ func (g ginkgoErrors) MultipleEntryBodyFunctionsForTable(cl CodeLocation) error { return GinkgoError{ Heading: "DescribeTable passed multiple functions", Message: "It looks like you are passing multiple functions into DescribeTable. Only one function can be passed in. This function will be called for each Entry in the table.", CodeLocation: cl, DocLink: "table-specs", } } func (g ginkgoErrors) InvalidEntryDescription(cl CodeLocation) error { return GinkgoError{ Heading: "Invalid Entry description", Message: "Entry description functions must be a string, a function that accepts the entry parameters and returns a string, or nil.", CodeLocation: cl, DocLink: "table-specs", } } func (g ginkgoErrors) MissingParametersForTableFunction(cl CodeLocation) error { return GinkgoError{ Heading: "No parameters have been passed to the Table Function", Message: "The Table Function expected at least 1 parameter", CodeLocation: cl, DocLink: "table-specs", } } func (g ginkgoErrors) IncorrectParameterTypeForTable(i int, name string, cl CodeLocation) error { return GinkgoError{ Heading: "DescribeTable passed incorrect parameter type", Message: fmt.Sprintf("Parameter #%d passed to DescribeTable is of incorrect type <%s>", i, name), CodeLocation: cl, DocLink: "table-specs", } } func (g ginkgoErrors) TooFewParametersToTableFunction(expected, actual int, kind string, cl CodeLocation) error { return GinkgoError{ Heading: fmt.Sprintf("Too few parameters passed in to %s", kind), Message: fmt.Sprintf("The %s expected %d parameters but you passed in %d", kind, expected, actual), CodeLocation: cl, DocLink: "table-specs", } } func (g ginkgoErrors) TooManyParametersToTableFunction(expected, actual int, kind string, cl CodeLocation) error { return GinkgoError{ Heading: fmt.Sprintf("Too many parameters passed in to %s", kind), Message: fmt.Sprintf("The %s expected %d parameters but you passed in %d", kind, expected, actual), CodeLocation: cl, DocLink: "table-specs", } } func (g ginkgoErrors) IncorrectParameterTypeToTableFunction(i int, expected, actual reflect.Type, kind string, cl CodeLocation) error { return GinkgoError{ Heading: fmt.Sprintf("Incorrect parameters type passed to %s", kind), Message: fmt.Sprintf("The %s expected parameter #%d to be of type <%s> but you passed in <%s>", kind, i, expected, actual), CodeLocation: cl, DocLink: "table-specs", } } func (g ginkgoErrors) IncorrectVariadicParameterTypeToTableFunction(expected, actual reflect.Type, kind string, cl CodeLocation) error { return GinkgoError{ Heading: fmt.Sprintf("Incorrect parameters type passed to %s", kind), Message: fmt.Sprintf("The %s expected its variadic parameters to be of type <%s> but you passed in <%s>", kind, expected, actual), CodeLocation: cl, DocLink: "table-specs", } } func (g ginkgoErrors) ContextsCannotBeUsedInSubtreeTables(cl CodeLocation) error { return GinkgoError{ Heading: "Contexts cannot be used in subtree tables", Message: "You''ve defined a subtree body function that accepts a context but did not provide one in the table entry. Ginkgo SpecContexts can only be passed in to subject and setup nodes - so if you are trying to implement a spec timeout you should request a context in the It function within your subtree body function, not in the subtree body function itself.", CodeLocation: cl, DocLink: "table-specs", } } /* Parallel Synchronization errors */ func (g ginkgoErrors) AggregatedReportUnavailableDueToNodeDisappearing() error { return GinkgoError{ Heading: "Test Report unavailable because a Ginkgo parallel process disappeared", Message: "The aggregated report could not be fetched for a ReportAfterSuite node. A Ginkgo parallel process disappeared before it could finish reporting.", } } func (g ginkgoErrors) SynchronizedBeforeSuiteFailedOnProc1() error { return GinkgoError{ Heading: "SynchronizedBeforeSuite failed on Ginkgo parallel process #1", Message: "The first SynchronizedBeforeSuite function running on Ginkgo parallel process #1 failed. This suite will now abort.", } } func (g ginkgoErrors) SynchronizedBeforeSuiteDisappearedOnProc1() error { return GinkgoError{ Heading: "Process #1 disappeared before SynchronizedBeforeSuite could report back", Message: "Ginkgo parallel process #1 disappeared before the first SynchronizedBeforeSuite function completed. This suite will now abort.", } } /* Configuration errors */ func (g ginkgoErrors) UnknownTypePassedToRunSpecs(value interface{}) error { return GinkgoError{ Heading: "Unknown Type passed to RunSpecs", Message: fmt.Sprintf("RunSpecs() accepts labels, and configuration of type types.SuiteConfig and/or types.ReporterConfig.\n You passed in: %v", value), } } var sharedParallelErrorMessage = "It looks like you are trying to run specs in parallel with go test.\nThis is unsupported and you should use the ginkgo CLI instead." func (g ginkgoErrors) InvalidParallelTotalConfiguration() error { return GinkgoError{ Heading: "-ginkgo.parallel.total must be >= 1", Message: sharedParallelErrorMessage, DocLink: "spec-parallelization", } } func (g ginkgoErrors) InvalidParallelProcessConfiguration() error { return GinkgoError{ Heading: "-ginkgo.parallel.process is one-indexed and must be <= ginkgo.parallel.total", Message: sharedParallelErrorMessage, DocLink: "spec-parallelization", } } func (g ginkgoErrors) MissingParallelHostConfiguration() error { return GinkgoError{ Heading: "-ginkgo.parallel.host is missing", Message: sharedParallelErrorMessage, DocLink: "spec-parallelization", } } func (g ginkgoErrors) UnreachableParallelHost(host string) error { return GinkgoError{ Heading: "Could not reach ginkgo.parallel.host:" + host, Message: sharedParallelErrorMessage, DocLink: "spec-parallelization", } } func (g ginkgoErrors) DryRunInParallelConfiguration() error { return GinkgoError{ Heading: "Ginkgo only performs -dryRun in serial mode.", Message: "Please try running ginkgo -dryRun again, but without -p or -procs to ensure the suite is running in series.", } } func (g ginkgoErrors) GracePeriodCannotBeZero() error { return GinkgoError{ Heading: "Ginkgo requires a positive --grace-period.", Message: "Please set --grace-period to a positive duration. The default is 30s.", } } func (g ginkgoErrors) ConflictingVerbosityConfiguration() error { return GinkgoError{ Heading: "Conflicting reporter verbosity settings.", Message: "You can't set more than one of -v, -vv and --succinct. Please pick one!", } } func (g ginkgoErrors) InvalidOutputInterceptorModeConfiguration(value string) error { return GinkgoError{ Heading: fmt.Sprintf("Invalid value '%s' for --output-interceptor-mode.", value), Message: "You must choose one of 'dup', 'swap', or 'none'.", } } func (g ginkgoErrors) InvalidGoFlagCount() error { return GinkgoError{ Heading: "Use of go test -count", Message: "Ginkgo does not support using go test -count to rerun suites. Only -count=1 is allowed. To repeat suite runs, please use the ginkgo cli and `ginkgo -until-it-fails` or `ginkgo -repeat=N`.", } } func (g ginkgoErrors) InvalidGoFlagParallel() error { return GinkgoError{ Heading: "Use of go test -parallel", Message: "Go test's implementation of parallelization does not actually parallelize Ginkgo specs. Please use the ginkgo cli and `ginkgo -p` or `ginkgo -procs=N` instead.", } } func (g ginkgoErrors) BothRepeatAndUntilItFails() error { return GinkgoError{ Heading: "--repeat and --until-it-fails are both set", 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, } } golang-github-onsi-ginkgo-v2-2.22.0/types/errors_test.go000066400000000000000000000052271472321612100231460ustar00rootroot00000000000000package types_test import ( "fmt" "reflect" "strings" . "github.com/onsi/ginkgo/v2" "github.com/onsi/ginkgo/v2/formatter" "github.com/onsi/ginkgo/v2/types" . "github.com/onsi/gomega" ) var _ = Describe("GinkgoErrors", func() { BeforeEach(func() { formatter.SingletonFormatter.ColorMode = formatter.ColorModePassthrough }) AfterEach(func() { formatter.SingletonFormatter.ColorMode = formatter.ColorModeTerminal }) DescribeTable("error render cases", func(err error, expected ...string) { Expect(err.Error()).To(HavePrefix(strings.Join(expected, "\n"))) }, Entry("an error with only a heading", types.GinkgoError{ Heading: "Error! Error!", }, "{{bold}}{{red}}Error! Error!{{/}}", "", ), Entry("an error with all the things", types.GinkgoError{ Heading: "Error! Error!", CodeLocation: types.CodeLocation{FileName: "foo.go", LineNumber: 17}, Message: "An error occurred.\nWelp!", DocLink: "the-doc-section", }, "{{bold}}{{red}}Error! Error!{{/}}", "{{gray}}foo.go:17{{/}}", " An error occurred.", " Welp!", "", " {{bold}}Learn more at:{{/}}", " {{cyan}}{{underline}}http://onsi.github.io/ginkgo/#the-doc-section{{/}}", ), Entry("an error that successfully loads the line of its CodeLocation", types.GinkgoError{ Heading: "Error! Error!", CodeLocation: types.NewCodeLocation(0), Message: "An error occurred.\nWelp!", DocLink: "the-doc-section", }, "{{bold}}{{red}}Error! Error!{{/}}", "{{light-gray}}CodeLocation: types.NewCodeLocation(0),{{/}}", fmt.Sprintf("{{gray}}%s:%d{{/}}", types.NewCodeLocation(0).FileName, types.NewCodeLocation(0).LineNumber-6), " An error occurred.", " Welp!", "", " {{bold}}Learn more at:{{/}}", " {{cyan}}{{underline}}http://onsi.github.io/ginkgo/#the-doc-section{{/}}", ), ) It("validates that all errors point to working documentation", func() { v := reflect.ValueOf(types.GinkgoErrors) Ω(v.NumMethod()).Should(BeNumerically(">", 0)) invalidLinks := []string{} for i := 0; i < v.NumMethod(); i += 1 { m := v.Method(i) args := []reflect.Value{} for j := 0; j < m.Type().NumIn(); j += 1 { args = append(args, reflect.Zero(m.Type().In(j))) } ginkgoError := m.Call(args)[0].Interface().(types.GinkgoError) if ginkgoError.DocLink != "" { success, _ := ContainElement(ginkgoError.DocLink).Match(anchors.DocAnchors["index.md"]) if !success { invalidLinks = append(invalidLinks, ginkgoError.DocLink) } } } Ω(invalidLinks).Should(BeEmpty(), "Detected invalid links. Available links are: %s", strings.Join(anchors.DocAnchors["index.md"], "\n")) }) }) golang-github-onsi-ginkgo-v2-2.22.0/types/file_filter.go000066400000000000000000000044651472321612100230620ustar00rootroot00000000000000package types import ( "regexp" "strconv" "strings" ) func ParseFileFilters(filters []string) (FileFilters, error) { ffs := FileFilters{} for _, filter := range filters { ff := FileFilter{} if filter == "" { return nil, GinkgoErrors.InvalidFileFilter(filter) } components := strings.Split(filter, ":") if !(len(components) == 1 || len(components) == 2) { return nil, GinkgoErrors.InvalidFileFilter(filter) } var err error ff.Filename, err = regexp.Compile(components[0]) if err != nil { return nil, err } if len(components) == 2 { lineFilters := strings.Split(components[1], ",") for _, lineFilter := range lineFilters { components := strings.Split(lineFilter, "-") if len(components) == 1 { line, err := strconv.Atoi(strings.TrimSpace(components[0])) if err != nil { return nil, GinkgoErrors.InvalidFileFilter(filter) } ff.LineFilters = append(ff.LineFilters, LineFilter{line, line + 1}) } else if len(components) == 2 { line1, err := strconv.Atoi(strings.TrimSpace(components[0])) if err != nil { return nil, GinkgoErrors.InvalidFileFilter(filter) } line2, err := strconv.Atoi(strings.TrimSpace(components[1])) if err != nil { return nil, GinkgoErrors.InvalidFileFilter(filter) } ff.LineFilters = append(ff.LineFilters, LineFilter{line1, line2}) } else { return nil, GinkgoErrors.InvalidFileFilter(filter) } } } ffs = append(ffs, ff) } return ffs, nil } type FileFilter struct { Filename *regexp.Regexp LineFilters LineFilters } func (f FileFilter) Matches(locations []CodeLocation) bool { for _, location := range locations { if f.Filename.MatchString(location.FileName) && f.LineFilters.Matches(location.LineNumber) { return true } } return false } type FileFilters []FileFilter func (ffs FileFilters) Matches(locations []CodeLocation) bool { for _, ff := range ffs { if ff.Matches(locations) { return true } } return false } type LineFilter struct { Min int Max int } func (lf LineFilter) Matches(line int) bool { return lf.Min <= line && line < lf.Max } type LineFilters []LineFilter func (lfs LineFilters) Matches(line int) bool { if len(lfs) == 0 { return true } for _, lf := range lfs { if lf.Matches(line) { return true } } return false } golang-github-onsi-ginkgo-v2-2.22.0/types/file_filters_test.go000066400000000000000000000062601472321612100242770ustar00rootroot00000000000000package types_test import ( "strings" . "github.com/onsi/ginkgo/v2" "github.com/onsi/ginkgo/v2/types" . "github.com/onsi/gomega" ) var _ = Describe("FileFilters", func() { Describe("Parsing Filters", func() { DescribeTable("Failure cases", func(filter string) { ffs, err := types.ParseFileFilters([]string{filter}) Ω(ffs).Should(BeZero()) Ω(err).Should(Equal(types.GinkgoErrors.InvalidFileFilter(filter))) }, Entry(nil, ""), Entry(nil, "floop:woop:wibble"), Entry(nil, "floop:1asd"), Entry(nil, "floop:1asd-3"), Entry(nil, "floop:1-3asd"), Entry(nil, "floop:1-2-3"), ) DescribeTable("Successful cases", func(matches bool, filters []string, clsArgs ...interface{}) { ffs, err := types.ParseFileFilters(filters) Ω(err).ShouldNot(HaveOccurred()) cls := []types.CodeLocation{} for i := 0; i < len(clsArgs); { cls = append(cls, types.CodeLocation{ FileName: clsArgs[i].(string), LineNumber: clsArgs[i+1].(int), }) i += 2 } if matches { Ω(ffs.Matches(cls)).Should(BeTrue()) } else { Ω(ffs.Matches(cls)).Should(BeFalse()) } }, func(matches bool, filters []string, clsArgs ...interface{}) string { return "When the filters are " + strings.Join(filters, " | ") }, //without line numbers Entry(nil, true, []string{"foo"}, "foo_test.go", 10), Entry(nil, true, []string{"foo"}, "foo/bar_test.go", 10), Entry(nil, false, []string{"foo"}, "bar_test.go", 10), Entry(nil, true, []string{"foo"}, "foo_test.go", 10, "bar_test.go", 11), Entry(nil, true, []string{"foo", "bar"}, "bar_test.go", 10), //with line numbers Entry(nil, true, []string{"foo:10"}, "foo_test.go", 9, "foo_test.go", 10), Entry(nil, false, []string{"foo:11"}, "foo_test.go", 10), Entry(nil, false, []string{"foo:10"}, "bar_test.go", 10), Entry(nil, true, []string{"foo:10", "foo:11"}, "foo_test.go", 10), //with multiple line numbers Entry(nil, true, []string{"foo:10,11"}, "foo_test.go", 10), Entry(nil, true, []string{"foo:10,11"}, "foo_test.go", 11), Entry(nil, false, []string{"foo:10,11"}, "foo_test.go", 12), Entry(nil, false, []string{"foo:10,11"}, "foo_test.go", 12), //with line ranges Entry(nil, false, []string{"foo:10-12"}, "foo_test.go", 9), Entry(nil, true, []string{"foo:10-12"}, "foo_test.go", 10), Entry(nil, true, []string{"foo:10-12"}, "foo_test.go", 11), Entry(nil, false, []string{"foo:10-12"}, "foo_test.go", 12), //with all the things Entry(nil, false, []string{"foo:7,10-12,15"}, "foo_test.go", 9), Entry(nil, true, []string{"foo:7,10-12,15"}, "foo_test.go", 7), Entry(nil, false, []string{"foo:7,10-12,15"}, "bar_test.go", 7), Entry(nil, true, []string{"foo:7,10-12,15"}, "foo/bar_test.go", 7), Entry(nil, true, []string{"foo:7,10-12,15"}, "foo/bar_test.go", 10), Entry(nil, true, []string{"foo:7,10-12,15"}, "foo/bar_test.go", 11), Entry(nil, false, []string{"foo:7,10-12,15"}, "foo/bar_test.go", 12), Entry(nil, false, []string{"foo:7,10-12,15"}, "foo/bar_test.go", 13), Entry(nil, true, []string{"foo:7,10-12,15"}, "foo/bar_test.go", 15), Entry(nil, true, []string{"foo:7,10-12", "bar:15"}, "foo/bar_test.go", 15), ) }) }) golang-github-onsi-ginkgo-v2-2.22.0/types/flags.go000066400000000000000000000307041472321612100216650ustar00rootroot00000000000000package types import ( "flag" "fmt" "io" "reflect" "strings" "time" "github.com/onsi/ginkgo/v2/formatter" ) type GinkgoFlag struct { Name string KeyPath string SectionKey string Usage string UsageArgument string UsageDefaultValue string DeprecatedName string DeprecatedDocLink string DeprecatedVersion string ExportAs string AlwaysExport bool } type GinkgoFlags []GinkgoFlag func (f GinkgoFlags) CopyAppend(flags ...GinkgoFlag) GinkgoFlags { out := GinkgoFlags{} out = append(out, f...) out = append(out, flags...) return out } func (f GinkgoFlags) WithPrefix(prefix string) GinkgoFlags { if prefix == "" { return f } out := GinkgoFlags{} for _, flag := range f { if flag.Name != "" { flag.Name = prefix + "." + flag.Name } if flag.DeprecatedName != "" { flag.DeprecatedName = prefix + "." + flag.DeprecatedName } if flag.ExportAs != "" { flag.ExportAs = prefix + "." + flag.ExportAs } out = append(out, flag) } return out } func (f GinkgoFlags) SubsetWithNames(names ...string) GinkgoFlags { out := GinkgoFlags{} for _, flag := range f { for _, name := range names { if flag.Name == name { out = append(out, flag) break } } } return out } type GinkgoFlagSection struct { Key string Style string Succinct bool Heading string Description string } type GinkgoFlagSections []GinkgoFlagSection func (gfs GinkgoFlagSections) Lookup(key string) (GinkgoFlagSection, bool) { for _, section := range gfs { if section.Key == key { return section, true } } return GinkgoFlagSection{}, false } type GinkgoFlagSet struct { flags GinkgoFlags bindings interface{} sections GinkgoFlagSections extraGoFlagsSection GinkgoFlagSection flagSet *flag.FlagSet } // Call NewGinkgoFlagSet to create GinkgoFlagSet that creates and binds to it's own *flag.FlagSet func NewGinkgoFlagSet(flags GinkgoFlags, bindings interface{}, sections GinkgoFlagSections) (GinkgoFlagSet, error) { return bindFlagSet(GinkgoFlagSet{ flags: flags, bindings: bindings, sections: sections, }, nil) } // Call NewGinkgoFlagSet to create GinkgoFlagSet that extends an existing *flag.FlagSet func NewAttachedGinkgoFlagSet(flagSet *flag.FlagSet, flags GinkgoFlags, bindings interface{}, sections GinkgoFlagSections, extraGoFlagsSection GinkgoFlagSection) (GinkgoFlagSet, error) { return bindFlagSet(GinkgoFlagSet{ flags: flags, bindings: bindings, sections: sections, extraGoFlagsSection: extraGoFlagsSection, }, flagSet) } func bindFlagSet(f GinkgoFlagSet, flagSet *flag.FlagSet) (GinkgoFlagSet, error) { if flagSet == nil { f.flagSet = flag.NewFlagSet("", flag.ContinueOnError) //suppress all output as Ginkgo is responsible for formatting usage f.flagSet.SetOutput(io.Discard) } else { f.flagSet = flagSet //we're piggybacking on an existing flagset (typically go test) so we have limited control //on user feedback f.flagSet.Usage = f.substituteUsage } for _, flag := range f.flags { name := flag.Name deprecatedUsage := "[DEPRECATED]" deprecatedName := flag.DeprecatedName if name != "" { deprecatedUsage = fmt.Sprintf("[DEPRECATED] use --%s instead", name) } else if flag.Usage != "" { deprecatedUsage += " " + flag.Usage } value, ok := valueAtKeyPath(f.bindings, flag.KeyPath) if !ok { return GinkgoFlagSet{}, fmt.Errorf("could not load KeyPath: %s", flag.KeyPath) } iface, addr := value.Interface(), value.Addr().Interface() switch value.Type() { case reflect.TypeOf(string("")): if name != "" { f.flagSet.StringVar(addr.(*string), name, iface.(string), flag.Usage) } if deprecatedName != "" { f.flagSet.StringVar(addr.(*string), deprecatedName, iface.(string), deprecatedUsage) } case reflect.TypeOf(int64(0)): if name != "" { f.flagSet.Int64Var(addr.(*int64), name, iface.(int64), flag.Usage) } if deprecatedName != "" { f.flagSet.Int64Var(addr.(*int64), deprecatedName, iface.(int64), deprecatedUsage) } case reflect.TypeOf(float64(0)): if name != "" { f.flagSet.Float64Var(addr.(*float64), name, iface.(float64), flag.Usage) } if deprecatedName != "" { f.flagSet.Float64Var(addr.(*float64), deprecatedName, iface.(float64), deprecatedUsage) } case reflect.TypeOf(int(0)): if name != "" { f.flagSet.IntVar(addr.(*int), name, iface.(int), flag.Usage) } if deprecatedName != "" { f.flagSet.IntVar(addr.(*int), deprecatedName, iface.(int), deprecatedUsage) } case reflect.TypeOf(bool(true)): if name != "" { f.flagSet.BoolVar(addr.(*bool), name, iface.(bool), flag.Usage) } if deprecatedName != "" { f.flagSet.BoolVar(addr.(*bool), deprecatedName, iface.(bool), deprecatedUsage) } case reflect.TypeOf(time.Duration(0)): if name != "" { f.flagSet.DurationVar(addr.(*time.Duration), name, iface.(time.Duration), flag.Usage) } if deprecatedName != "" { f.flagSet.DurationVar(addr.(*time.Duration), deprecatedName, iface.(time.Duration), deprecatedUsage) } case reflect.TypeOf([]string{}): if name != "" { f.flagSet.Var(stringSliceVar{value}, name, flag.Usage) } if deprecatedName != "" { f.flagSet.Var(stringSliceVar{value}, deprecatedName, deprecatedUsage) } default: return GinkgoFlagSet{}, fmt.Errorf("unsupported type %T", iface) } } return f, nil } func (f GinkgoFlagSet) IsZero() bool { return f.flagSet == nil } func (f GinkgoFlagSet) WasSet(name string) bool { found := false f.flagSet.Visit(func(f *flag.Flag) { if f.Name == name { found = true } }) return found } func (f GinkgoFlagSet) Lookup(name string) *flag.Flag { return f.flagSet.Lookup(name) } func (f GinkgoFlagSet) Parse(args []string) ([]string, error) { if f.IsZero() { return args, nil } err := f.flagSet.Parse(args) if err != nil { return []string{}, err } return f.flagSet.Args(), nil } func (f GinkgoFlagSet) ValidateDeprecations(deprecationTracker *DeprecationTracker) { if f.IsZero() { return } f.flagSet.Visit(func(flag *flag.Flag) { for _, ginkgoFlag := range f.flags { if ginkgoFlag.DeprecatedName != "" && strings.HasSuffix(flag.Name, ginkgoFlag.DeprecatedName) { message := fmt.Sprintf("--%s is deprecated", ginkgoFlag.DeprecatedName) if ginkgoFlag.Name != "" { message = fmt.Sprintf("--%s is deprecated, use --%s instead", ginkgoFlag.DeprecatedName, ginkgoFlag.Name) } else if ginkgoFlag.Usage != "" { message += " " + ginkgoFlag.Usage } deprecationTracker.TrackDeprecation(Deprecation{ Message: message, DocLink: ginkgoFlag.DeprecatedDocLink, Version: ginkgoFlag.DeprecatedVersion, }) } } }) } func (f GinkgoFlagSet) Usage() string { if f.IsZero() { return "" } groupedFlags := map[GinkgoFlagSection]GinkgoFlags{} ungroupedFlags := GinkgoFlags{} managedFlags := map[string]bool{} extraGoFlags := []*flag.Flag{} for _, flag := range f.flags { managedFlags[flag.Name] = true managedFlags[flag.DeprecatedName] = true if flag.Name == "" { continue } section, ok := f.sections.Lookup(flag.SectionKey) if ok { groupedFlags[section] = append(groupedFlags[section], flag) } else { ungroupedFlags = append(ungroupedFlags, flag) } } f.flagSet.VisitAll(func(flag *flag.Flag) { if !managedFlags[flag.Name] { extraGoFlags = append(extraGoFlags, flag) } }) out := "" for _, section := range f.sections { flags := groupedFlags[section] if len(flags) == 0 { continue } out += f.usageForSection(section) if section.Succinct { succinctFlags := []string{} for _, flag := range flags { if flag.Name != "" { succinctFlags = append(succinctFlags, fmt.Sprintf("--%s", flag.Name)) } } out += formatter.Fiw(1, formatter.COLS, section.Style+strings.Join(succinctFlags, ", ")+"{{/}}\n") } else { for _, flag := range flags { out += f.usageForFlag(flag, section.Style) } } out += "\n" } if len(ungroupedFlags) > 0 { for _, flag := range ungroupedFlags { out += f.usageForFlag(flag, "") } out += "\n" } if len(extraGoFlags) > 0 { out += f.usageForSection(f.extraGoFlagsSection) for _, goFlag := range extraGoFlags { out += f.usageForGoFlag(goFlag) } } return out } func (f GinkgoFlagSet) substituteUsage() { fmt.Fprintln(f.flagSet.Output(), f.Usage()) } func valueAtKeyPath(root interface{}, keyPath string) (reflect.Value, bool) { if len(keyPath) == 0 { return reflect.Value{}, false } val := reflect.ValueOf(root) components := strings.Split(keyPath, ".") for _, component := range components { val = reflect.Indirect(val) switch val.Kind() { case reflect.Map: val = val.MapIndex(reflect.ValueOf(component)) if val.Kind() == reflect.Interface { val = reflect.ValueOf(val.Interface()) } case reflect.Struct: val = val.FieldByName(component) default: return reflect.Value{}, false } if (val == reflect.Value{}) { return reflect.Value{}, false } } return val, true } func (f GinkgoFlagSet) usageForSection(section GinkgoFlagSection) string { out := formatter.F(section.Style + "{{bold}}{{underline}}" + section.Heading + "{{/}}\n") if section.Description != "" { out += formatter.Fiw(0, formatter.COLS, section.Description+"\n") } return out } func (f GinkgoFlagSet) usageForFlag(flag GinkgoFlag, style string) string { argument := flag.UsageArgument defValue := flag.UsageDefaultValue if argument == "" { value, _ := valueAtKeyPath(f.bindings, flag.KeyPath) switch value.Type() { case reflect.TypeOf(string("")): argument = "string" case reflect.TypeOf(int64(0)), reflect.TypeOf(int(0)): argument = "int" case reflect.TypeOf(time.Duration(0)): argument = "duration" case reflect.TypeOf(float64(0)): argument = "float" case reflect.TypeOf([]string{}): argument = "string" } } if argument != "" { argument = "[" + argument + "] " } if defValue != "" { defValue = fmt.Sprintf("(default: %s)", defValue) } hyphens := "--" if len(flag.Name) == 1 { hyphens = "-" } out := formatter.Fi(1, style+"%s%s{{/}} %s{{gray}}%s{{/}}\n", hyphens, flag.Name, argument, defValue) out += formatter.Fiw(2, formatter.COLS, "{{light-gray}}%s{{/}}\n", flag.Usage) return out } func (f GinkgoFlagSet) usageForGoFlag(goFlag *flag.Flag) string { //Taken directly from the flag package out := fmt.Sprintf(" -%s", goFlag.Name) name, usage := flag.UnquoteUsage(goFlag) if len(name) > 0 { out += " " + name } if len(out) <= 4 { out += "\t" } else { out += "\n \t" } out += strings.ReplaceAll(usage, "\n", "\n \t") out += "\n" return out } type stringSliceVar struct { slice reflect.Value } func (ssv stringSliceVar) String() string { return "" } func (ssv stringSliceVar) Set(s string) error { ssv.slice.Set(reflect.AppendSlice(ssv.slice, reflect.ValueOf([]string{s}))) return nil } // given a set of GinkgoFlags and bindings, generate flag arguments suitable to be passed to an application with that set of flags configured. func GenerateFlagArgs(flags GinkgoFlags, bindings interface{}) ([]string, error) { result := []string{} for _, flag := range flags { name := flag.ExportAs if name == "" { name = flag.Name } if name == "" { continue } value, ok := valueAtKeyPath(bindings, flag.KeyPath) if !ok { return []string{}, fmt.Errorf("could not load KeyPath: %s", flag.KeyPath) } iface := value.Interface() switch value.Type() { case reflect.TypeOf(string("")): if iface.(string) != "" || flag.AlwaysExport { result = append(result, fmt.Sprintf("--%s=%s", name, iface)) } case reflect.TypeOf(int64(0)): if iface.(int64) != 0 || flag.AlwaysExport { result = append(result, fmt.Sprintf("--%s=%d", name, iface)) } case reflect.TypeOf(float64(0)): if iface.(float64) != 0 || flag.AlwaysExport { result = append(result, fmt.Sprintf("--%s=%f", name, iface)) } case reflect.TypeOf(int(0)): if iface.(int) != 0 || flag.AlwaysExport { result = append(result, fmt.Sprintf("--%s=%d", name, iface)) } case reflect.TypeOf(bool(true)): if iface.(bool) { result = append(result, fmt.Sprintf("--%s", name)) } case reflect.TypeOf(time.Duration(0)): if iface.(time.Duration) != time.Duration(0) || flag.AlwaysExport { result = append(result, fmt.Sprintf("--%s=%s", name, iface)) } case reflect.TypeOf([]string{}): strings := iface.([]string) for _, s := range strings { result = append(result, fmt.Sprintf("--%s=%s", name, s)) } default: return []string{}, fmt.Errorf("unsupported type %T", iface) } } return result, nil } golang-github-onsi-ginkgo-v2-2.22.0/types/flags_test.go000066400000000000000000000414341472321612100227260ustar00rootroot00000000000000package types_test import ( "flag" "strings" . "github.com/onsi/ginkgo/v2" "github.com/onsi/ginkgo/v2/formatter" "github.com/onsi/ginkgo/v2/types" . "github.com/onsi/gomega" "github.com/onsi/gomega/format" "github.com/onsi/gomega/gbytes" ) var _ = Describe("Flags", func() { BeforeEach(func() { format.CharactersAroundMismatchToInclude = 1000 formatter.SingletonFormatter.ColorMode = formatter.ColorModePassthrough }) Describe("GinkgoFlags", func() { Describe("CopyAppend", func() { It("concatenates the flags together, making a copy as it does so", func() { A := types.GinkgoFlags{{Name: "A"}, {Name: "B"}, {Name: "C"}} B := types.GinkgoFlags{{Name: "1"}, {Name: "2"}, {Name: "3"}} Ω(A.CopyAppend(B...)).Should(Equal(types.GinkgoFlags{{Name: "A"}, {Name: "B"}, {Name: "C"}, {Name: "1"}, {Name: "2"}, {Name: "3"}})) Ω(A).Should(HaveLen(3)) Ω(B).Should(HaveLen(3)) }) }) Describe("WithPrefix", func() { It("attaches the passed in prefi to the Name, DeprecatedName and ExportAs fields", func() { flags := types.GinkgoFlags{ {Name: "A"}, {DeprecatedName: "B"}, {ExportAs: "C"}, } prefixed := flags.WithPrefix("hello") Ω(prefixed).Should(Equal(types.GinkgoFlags{ {Name: "hello.A"}, {DeprecatedName: "hello.B"}, {ExportAs: "hello.C"}, })) }) }) Describe("SubsetWithNames", func() { It("returns the subset of flags with matching names", func() { A := types.GinkgoFlags{{Name: "A", Usage: "Hey A"}, {Name: "B", Usage: "Hey B"}, {Name: "C", Usage: "Hey C"}} subset := A.SubsetWithNames("A", "C", "D") Ω(subset).Should(Equal(types.GinkgoFlags{{Name: "A", Usage: "Hey A"}, {Name: "C", Usage: "Hey C"}})) }) }) }) Describe("GinkgoFlagSections", func() { Describe("Lookup", func() { var sections types.GinkgoFlagSections BeforeEach(func() { sections = types.GinkgoFlagSections{ {Key: "A", Heading: "Aft"}, {Key: "B", Heading: "Starboard"}, } }) It("looks up the flag section with the passed in key", func() { section, found := sections.Lookup("A") Ω(found).Should(BeTrue()) Ω(section).Should(Equal(sections[0])) }) It("returns an empty flag section when the key is not found", func() { section, found := sections.Lookup("C") Ω(found).Should(BeFalse()) Ω(section).Should(BeZero()) }) }) }) Describe("The zero GinkgoFlagSet", func() { It("returns true for IsZero", func() { Ω(types.GinkgoFlagSet{}.IsZero()).Should(BeTrue()) }) It("returns the passed in args when asked to parse", func() { args := []string{"-a=1", "-b=2", "-c=3"} Ω(types.GinkgoFlagSet{}.Parse(args)).Should(Equal(args)) }) It("does not validate any deprecations", func() { deprecationTracker := types.NewDeprecationTracker() types.GinkgoFlagSet{}.ValidateDeprecations(deprecationTracker) Ω(deprecationTracker.DidTrackDeprecations()).Should(BeFalse()) }) It("emits an empty string for usage", func() { Ω(types.GinkgoFlagSet{}.Usage()).Should(Equal("")) }) }) Describe("GinkgoFlagSet", func() { type StructA struct { StringProperty string Int64Property int64 Float64Property float64 } type StructB struct { IntProperty int BoolProperty bool StringSliceProperty []string DeprecatedProperty string } var A StructA var B StructB var flags types.GinkgoFlags var bindings map[string]interface{} var sections types.GinkgoFlagSections var flagSet types.GinkgoFlagSet BeforeEach(func() { A = StructA{ StringProperty: "the default string", Int64Property: 1138, Float64Property: 3.141, } B = StructB{ IntProperty: 2009, BoolProperty: true, StringSliceProperty: []string{"once", "upon", "a time"}, DeprecatedProperty: "n/a", } bindings = map[string]interface{}{ "A": &A, "B": &B, } sections = types.GinkgoFlagSections{ {Key: "candy", Style: "{{red}}", Heading: "Candy Section", Description: "So sweet."}, {Key: "dairy", Style: "{{blue}}", Heading: "Dairy Section", Description: "Abbreviated section", Succinct: true}, } flags = types.GinkgoFlags{ {Name: "string-flag", SectionKey: "candy", Usage: "string-usage", UsageArgument: "name", UsageDefaultValue: "Gerald", KeyPath: "A.StringProperty", DeprecatedName: "stringFlag"}, {Name: "int-64-flag", SectionKey: "candy", Usage: "int-64-usage", KeyPath: "A.Int64Property", DeprecatedName: "int64Flag", DeprecatedDocLink: "no-more-camel-case"}, {Name: "float-64-flag", SectionKey: "dairy", Usage: "float-64-usage", KeyPath: "A.Float64Property"}, {Name: "int-flag", SectionKey: "invalid", Usage: "int-usage", KeyPath: "B.IntProperty"}, {Name: "bool-flag", SectionKey: "candy", Usage: "bool-usage", KeyPath: "B.BoolProperty"}, {Name: "string-slice-flag", SectionKey: "dairy", Usage: "string-slice-usage", KeyPath: "B.StringSliceProperty"}, {SectionKey: "candy", DeprecatedName: "deprecated-flag", KeyPath: "B.DeprecatedProperty", Usage: "deprecated-usage"}, } }) Describe("Creation Failure Cases", func() { Context("when passed an unsupported type in the map", func() { BeforeEach(func() { type UnsupportedStructB struct { IntProperty int BoolProperty bool StringSliceProperty []string DeprecatedProperty int32 //not supported } bindings = map[string]interface{}{ "A": &A, "B": &UnsupportedStructB{}, } }) It("errors", func() { flagSet, err := types.NewGinkgoFlagSet(flags, bindings, sections) Ω(flagSet.IsZero()).Should(BeTrue()) Ω(err).Should(HaveOccurred()) }) }) Context("when the flags point to an invalid keypath in the map", func() { BeforeEach(func() { flags = append(flags, types.GinkgoFlag{Name: "welp-flag", Usage: "welp-usage", KeyPath: "A.WelpProperty"}) }) It("errors", func() { flagSet, err := types.NewGinkgoFlagSet(flags, bindings, sections) Ω(flagSet.IsZero()).Should(BeTrue()) Ω(err).Should(HaveOccurred()) }) }) }) Describe("A stand-alone GinkgoFlagSet", func() { BeforeEach(func() { var err error flagSet, err = types.NewGinkgoFlagSet(flags, bindings, sections) Ω(flagSet.IsZero()).Should(BeFalse()) Ω(err).ShouldNot(HaveOccurred()) }) Describe("Parsing flags", func() { It("maintains default values when no flags are parsed", func() { args, err := flagSet.Parse([]string{}) Ω(err).ShouldNot(HaveOccurred()) Ω(args).Should(Equal([]string{})) Ω(A.StringProperty).Should(Equal("the default string")) Ω(B.IntProperty).Should(Equal(2009)) }) It("updates the bindings when flags are parsed, returning any additional arguments", func() { args, err := flagSet.Parse([]string{ "-string-flag", "a new string", "-int-64-flag=1139", "--float-64-flag", "2.71", "-int-flag=1984", "-bool-flag=false", "-string-slice-flag", "there lived", "-string-slice-flag", "three dragons", "extra-1", "extra-2", }) Ω(err).ShouldNot(HaveOccurred()) Ω(args).Should(Equal([]string{"extra-1", "extra-2"})) Ω(A.StringProperty).Should(Equal("a new string")) Ω(A.Int64Property).Should(Equal(int64(1139))) Ω(A.Float64Property).Should(Equal(2.71)) Ω(B.IntProperty).Should(Equal(1984)) Ω(B.BoolProperty).Should(Equal(false)) Ω(B.StringSliceProperty).Should(Equal([]string{"once", "upon", "a time", "there lived", "three dragons"})) }) It("updates the bindings when deprecated flags are set", func() { _, err := flagSet.Parse([]string{ "-stringFlag", "deprecated but works", "-int64Flag=1234", "-deprecated-flag", "does not fail", }) Ω(err).ShouldNot(HaveOccurred()) Ω(A.StringProperty).Should(Equal("deprecated but works")) Ω(A.Int64Property).Should(Equal(int64(1234))) Ω(B.DeprecatedProperty).Should(Equal("does not fail")) }) It("reports accurately on flags that were set", func() { _, err := flagSet.Parse([]string{ "-string-flag", "a new string", "--float-64-flag", "2.71", }) Ω(err).ShouldNot(HaveOccurred()) Ω(flagSet.WasSet("string-flag")).Should(BeTrue()) Ω(flagSet.WasSet("int-64-flag")).Should(BeFalse()) Ω(flagSet.WasSet("float-64-flag")).Should(BeTrue()) }) }) Describe("Validating Deprecations", func() { var deprecationTracker *types.DeprecationTracker BeforeEach(func() { deprecationTracker = types.NewDeprecationTracker() }) Context("when no deprecated flags were invoked", func() { It("doesn't track any deprecations", func() { flagSet.Parse([]string{ "--string-flag", "ok", "--int-flag", "1983", }) flagSet.ValidateDeprecations(deprecationTracker) Ω(deprecationTracker.DidTrackDeprecations()).Should(BeFalse()) }) }) Context("when deprecated flags were invoked", func() { It("tracks any detected deprecations with the passed in deprecation tracker", func() { flagSet.Parse([]string{ "--stringFlag", "deprecated version", "--string-flag", "ok", "--int64Flag", "427", }) flagSet.ValidateDeprecations(deprecationTracker) Ω(deprecationTracker.DidTrackDeprecations()).Should(BeTrue()) report := deprecationTracker.DeprecationsReport() Ω(report).Should(ContainSubstring("--int64Flag is deprecated, use --int-64-flag instead")) Ω(report).Should(ContainSubstring("https://onsi.github.io/ginkgo/MIGRATING_TO_V2#no-more-camel-case")) Ω(report).Should(ContainSubstring("--stringFlag is deprecated, use --string-flag instead")) }) }) }) Describe("Emitting Usage information", func() { It("emits information by section", func() { expectedUsage := []string{ "{{red}}{{bold}}{{underline}}Candy Section{{/}}", //Candy section "So sweet.", //with heading " {{red}}--string-flag{{/}} [name] {{gray}}(default: Gerald){{/}}", //flag with usage argument and default value " {{light-gray}}string-usage{{/}}", " {{red}}--int-64-flag{{/}} [int] {{gray}}{{/}}", " {{light-gray}}int-64-usage{{/}}", " {{red}}--bool-flag{{/}} {{gray}}{{/}}", " {{light-gray}}bool-usage{{/}}", "", "{{blue}}{{bold}}{{underline}}Dairy Section{{/}}", //Dairy section is Succinct... "Abbreviated section", " {{blue}}--float-64-flag, --string-slice-flag{{/}}", //so flags are just enumerated, without documentation "", " --int-flag{{/}} [int] {{gray}}{{/}}", " {{light-gray}}int-usage{{/}}", "", "", } Ω(flagSet.Usage()).Should(Equal(strings.Join(expectedUsage, "\n"))) }) }) }) Describe("A GinkgoFlagSet attached to an existing golang flagset", func() { var goFlagSet *flag.FlagSet BeforeEach(func() { var err error goFlagSet = flag.NewFlagSet("go-set", flag.ContinueOnError) goStringFlag := "" goIntFlag := 0 goFlagSet.StringVar(&goStringFlag, "go-string-flag", "bob", "sets via `go`") goFlagSet.IntVar(&goIntFlag, "go-int-flag", 0, "an integer, please") flagSet, err = types.NewAttachedGinkgoFlagSet(goFlagSet, flags, bindings, sections, types.GinkgoFlagSection{ Heading: "The go flags...", }) Ω(err).ShouldNot(HaveOccurred()) }) It("attaches its flags to go flag set, including deprecated flags", func() { registeredFlags := map[string]*flag.Flag{} goFlagSet.VisitAll(func(flag *flag.Flag) { registeredFlags[flag.Name] = flag }) Ω(registeredFlags["string-flag"].Usage).Should(Equal("string-usage")) Ω(registeredFlags["int-64-flag"].Usage).Should(Equal("int-64-usage")) Ω(registeredFlags["float-64-flag"].Usage).Should(Equal("float-64-usage")) Ω(registeredFlags["int-flag"].Usage).Should(Equal("int-usage")) Ω(registeredFlags["bool-flag"].Usage).Should(Equal("bool-usage")) Ω(registeredFlags["string-slice-flag"].Usage).Should(Equal("string-slice-usage")) Ω(registeredFlags["string-flag"].DefValue).Should(Equal("the default string")) Ω(registeredFlags["int-64-flag"].DefValue).Should(Equal("1138")) Ω(registeredFlags["float-64-flag"].DefValue).Should(Equal("3.141")) Ω(registeredFlags["int-flag"].DefValue).Should(Equal("2009")) Ω(registeredFlags["bool-flag"].DefValue).Should(Equal("true")) Ω(registeredFlags["stringFlag"].Usage).Should(Equal("[DEPRECATED] use --string-flag instead")) Ω(registeredFlags["int64Flag"].Usage).Should(Equal("[DEPRECATED] use --int-64-flag instead")) Ω(registeredFlags["deprecated-flag"].Usage).Should(Equal("[DEPRECATED] deprecated-usage")) }) It("overrides the goFlagSet's usage", func() { buf := gbytes.NewBuffer() goFlagSet.SetOutput(buf) goFlagSet.Parse([]string{"--oops"}) Ω(string(buf.Contents())).Should(Equal("flag provided but not defined: -oops\n" + flagSet.Usage() + "\n")) }) It("includes the go FlagSets flags in their own section", func() { expectedUsage := []string{ "{{red}}{{bold}}{{underline}}Candy Section{{/}}", "So sweet.", " {{red}}--string-flag{{/}} [name] {{gray}}(default: Gerald){{/}}", " {{light-gray}}string-usage{{/}}", " {{red}}--int-64-flag{{/}} [int] {{gray}}{{/}}", " {{light-gray}}int-64-usage{{/}}", " {{red}}--bool-flag{{/}} {{gray}}{{/}}", " {{light-gray}}bool-usage{{/}}", "", "{{blue}}{{bold}}{{underline}}Dairy Section{{/}}", "Abbreviated section", " {{blue}}--float-64-flag, --string-slice-flag{{/}}", "", " --int-flag{{/}} [int] {{gray}}{{/}}", " {{light-gray}}int-usage{{/}}", "", "{{bold}}{{underline}}The go flags...{{/}}", //separate go flags section at the bottom, includes flags that are in the go FlagSet but not in the GinkgoFLagSet " -go-int-flag int", " an integer, please", " -go-string-flag go", //Note the processing of `go` using flag.UnquoteUsage() " sets via go", "", } Ω(flagSet.Usage()).Should(Equal(strings.Join(expectedUsage, "\n"))) }) }) }) Describe("GenerateFlagArgs", func() { type StructA struct { StringProperty string Int64Property int64 Float64Property float64 UnsupportedInt32 int32 } type StructB struct { IntProperty int BoolProperty bool StringSliceProperty []string DeprecatedProperty string } var A StructA var B StructB var flags types.GinkgoFlags var bindings map[string]interface{} BeforeEach(func() { A = StructA{ StringProperty: "the default string", Int64Property: 1138, Float64Property: 3.141, } B = StructB{ IntProperty: 2009, BoolProperty: true, StringSliceProperty: []string{"once", "upon", "a time"}, DeprecatedProperty: "n/a", } bindings = map[string]interface{}{ "A": &A, "B": &B, } flags = types.GinkgoFlags{ {Name: "string-flag", KeyPath: "A.StringProperty", DeprecatedName: "stringFlag"}, {Name: "int-64-flag", KeyPath: "A.Int64Property", AlwaysExport: true}, {Name: "float-64-flag", KeyPath: "A.Float64Property"}, {Name: "int-flag", KeyPath: "B.IntProperty", ExportAs: "alias-int-flag"}, {Name: "bool-flag", KeyPath: "B.BoolProperty", ExportAs: "alias-bool-flag", AlwaysExport: true}, {Name: "string-slice-flag", KeyPath: "B.StringSliceProperty"}, {DeprecatedName: "deprecated-flag", KeyPath: "B.DeprecatedProperty"}, } }) It("generates an array of flag arguments that, if parsed, reproduce the values in the passed-in bindings", func() { args, err := types.GenerateFlagArgs(flags, bindings) Ω(err).ShouldNot(HaveOccurred()) Ω(args).Should(Equal([]string{ "--string-flag=the default string", "--int-64-flag=1138", "--float-64-flag=3.141000", "--alias-int-flag=2009", "--alias-bool-flag", "--string-slice-flag=once", "--string-slice-flag=upon", "--string-slice-flag=a time", })) }) It("does not include 0 values unless AlwaysExport is true", func() { A.StringProperty = "" A.Int64Property = 0 B.IntProperty = 0 B.BoolProperty = false args, err := types.GenerateFlagArgs(flags, bindings) Ω(err).ShouldNot(HaveOccurred()) Ω(args).Should(Equal([]string{ "--int-64-flag=0", //always export "--float-64-flag=3.141000", "--string-slice-flag=once", "--string-slice-flag=upon", "--string-slice-flag=a time", })) }) It("errors if there is a keypath issue", func() { flags[0] = types.GinkgoFlag{Name: "unsupported-type", KeyPath: "A.UnsupportedInt32"} args, err := types.GenerateFlagArgs(flags, bindings) Ω(err).Should(MatchError("unsupported type int32")) Ω(args).Should(BeEmpty()) flags[0] = types.GinkgoFlag{Name: "bad-keypath", KeyPath: "A.StringProoperty"} args, err = types.GenerateFlagArgs(flags, bindings) Ω(err).Should(MatchError("could not load KeyPath: A.StringProoperty")) Ω(args).Should(BeEmpty()) }) }) }) golang-github-onsi-ginkgo-v2-2.22.0/types/label_filter.go000066400000000000000000000367731472321612100232310ustar00rootroot00000000000000package types import ( "fmt" "regexp" "strings" ) var DEBUG_LABEL_FILTER_PARSING = false type LabelFilter func([]string) bool func matchLabelAction(label string) LabelFilter { expected := strings.ToLower(label) return func(labels []string) bool { for i := range labels { if strings.ToLower(labels[i]) == expected { return true } } return false } } func matchLabelRegexAction(regex *regexp.Regexp) LabelFilter { return func(labels []string) bool { for i := range labels { if regex.MatchString(labels[i]) { return true } } return false } } func notAction(filter LabelFilter) LabelFilter { return func(labels []string) bool { return !filter(labels) } } func andAction(a, b LabelFilter) LabelFilter { return func(labels []string) bool { return a(labels) && b(labels) } } func orAction(a, b LabelFilter) LabelFilter { return func(labels []string) bool { return a(labels) || b(labels) } } func labelSetFor(key string, labels []string) map[string]bool { key = strings.ToLower(strings.TrimSpace(key)) out := map[string]bool{} for _, label := range labels { components := strings.SplitN(label, ":", 2) if len(components) < 2 { continue } if key == strings.ToLower(strings.TrimSpace(components[0])) { out[strings.ToLower(strings.TrimSpace(components[1]))] = true } } return out } func isEmptyLabelSetAction(key string) LabelFilter { return func(labels []string) bool { return len(labelSetFor(key, labels)) == 0 } } func containsAnyLabelSetAction(key string, expectedValues []string) LabelFilter { return func(labels []string) bool { set := labelSetFor(key, labels) for _, value := range expectedValues { if set[value] { return true } } return false } } func containsAllLabelSetAction(key string, expectedValues []string) LabelFilter { return func(labels []string) bool { set := labelSetFor(key, labels) for _, value := range expectedValues { if !set[value] { return false } } return true } } func consistsOfLabelSetAction(key string, expectedValues []string) LabelFilter { return func(labels []string) bool { set := labelSetFor(key, labels) if len(set) != len(expectedValues) { return false } for _, value := range expectedValues { if !set[value] { return false } } return true } } func isSubsetOfLabelSetAction(key string, expectedValues []string) LabelFilter { expectedSet := map[string]bool{} for _, value := range expectedValues { expectedSet[value] = true } return func(labels []string) bool { set := labelSetFor(key, labels) for value := range set { if !expectedSet[value] { return false } } return true } } type lfToken uint const ( lfTokenInvalid lfToken = iota lfTokenRoot lfTokenOpenGroup lfTokenCloseGroup lfTokenNot lfTokenAnd lfTokenOr lfTokenRegexp lfTokenLabel lfTokenSetKey lfTokenSetOperation lfTokenSetArgument lfTokenEOF ) func (l lfToken) Precedence() int { switch l { case lfTokenRoot, lfTokenOpenGroup: return 0 case lfTokenOr: return 1 case lfTokenAnd: return 2 case lfTokenNot: return 3 case lfTokenSetOperation: return 4 } return -1 } func (l lfToken) String() string { switch l { case lfTokenRoot: return "ROOT" case lfTokenOpenGroup: return "(" case lfTokenCloseGroup: return ")" case lfTokenNot: return "!" case lfTokenAnd: return "&&" case lfTokenOr: return "||" case lfTokenRegexp: return "/regexp/" case lfTokenLabel: return "label" case lfTokenSetKey: return "set_key" case lfTokenSetOperation: return "set_operation" case lfTokenSetArgument: return "set_argument" case lfTokenEOF: return "EOF" } return "INVALID" } type treeNode struct { token lfToken location int value string parent *treeNode leftNode *treeNode rightNode *treeNode } func (tn *treeNode) setRightNode(node *treeNode) { tn.rightNode = node node.parent = tn } func (tn *treeNode) setLeftNode(node *treeNode) { tn.leftNode = node node.parent = tn } func (tn *treeNode) firstAncestorWithPrecedenceLEQ(precedence int) *treeNode { if tn.token.Precedence() <= precedence { return tn } return tn.parent.firstAncestorWithPrecedenceLEQ(precedence) } func (tn *treeNode) firstUnmatchedOpenNode() *treeNode { if tn.token == lfTokenOpenGroup { return tn } if tn.parent == nil { return nil } return tn.parent.firstUnmatchedOpenNode() } func (tn *treeNode) constructLabelFilter(input string) (LabelFilter, error) { switch tn.token { case lfTokenOpenGroup: return nil, GinkgoErrors.SyntaxErrorParsingLabelFilter(input, tn.location, "Mismatched '(' - could not find matching ')'.") case lfTokenLabel: return matchLabelAction(tn.value), nil case lfTokenRegexp: re, err := regexp.Compile(tn.value) if err != nil { return nil, GinkgoErrors.SyntaxErrorParsingLabelFilter(input, tn.location, fmt.Sprintf("RegExp compilation error: %s", err)) } return matchLabelRegexAction(re), nil case lfTokenSetOperation: tokenSetOperation := strings.ToLower(tn.value) if tokenSetOperation == "isempty" { return isEmptyLabelSetAction(tn.leftNode.value), nil } if tn.rightNode == nil { return nil, GinkgoErrors.SyntaxErrorParsingLabelFilter(input, tn.location, fmt.Sprintf("Set operation '%s' is missing an argument.", tn.value)) } rawValues := strings.Split(tn.rightNode.value, ",") values := make([]string, len(rawValues)) for i := range rawValues { values[i] = strings.ToLower(strings.TrimSpace(rawValues[i])) if strings.ContainsAny(values[i], "&|!,()/") { return nil, GinkgoErrors.SyntaxErrorParsingLabelFilter(input, tn.rightNode.location, fmt.Sprintf("Invalid label value '%s' in set operation argument.", values[i])) } else if values[i] == "" { return nil, GinkgoErrors.SyntaxErrorParsingLabelFilter(input, tn.rightNode.location, "Empty label value in set operation argument.") } } switch tokenSetOperation { case "containsany": return containsAnyLabelSetAction(tn.leftNode.value, values), nil case "containsall": return containsAllLabelSetAction(tn.leftNode.value, values), nil case "consistsof": return consistsOfLabelSetAction(tn.leftNode.value, values), nil case "issubsetof": return isSubsetOfLabelSetAction(tn.leftNode.value, values), nil } } if tn.rightNode == nil { return nil, GinkgoErrors.SyntaxErrorParsingLabelFilter(input, -1, "Unexpected EOF.") } rightLF, err := tn.rightNode.constructLabelFilter(input) if err != nil { return nil, err } switch tn.token { case lfTokenRoot, lfTokenCloseGroup: return rightLF, nil case lfTokenNot: return notAction(rightLF), nil } if tn.leftNode == nil { return nil, GinkgoErrors.SyntaxErrorParsingLabelFilter(input, tn.location, fmt.Sprintf("Malformed tree - '%s' is missing left operand.", tn.token)) } leftLF, err := tn.leftNode.constructLabelFilter(input) if err != nil { return nil, err } switch tn.token { case lfTokenAnd: return andAction(leftLF, rightLF), nil case lfTokenOr: return orAction(leftLF, rightLF), nil } return nil, GinkgoErrors.SyntaxErrorParsingLabelFilter(input, tn.location, fmt.Sprintf("Invalid token '%s'.", tn.token)) } func (tn *treeNode) tokenString() string { out := fmt.Sprintf("<%s", tn.token) if tn.value != "" { out += " | " + tn.value } out += ">" return out } func (tn *treeNode) toString(indent int) string { out := tn.tokenString() + "\n" if tn.leftNode != nil { out += fmt.Sprintf("%s |_(L)_%s", strings.Repeat(" ", indent), tn.leftNode.toString(indent+1)) } if tn.rightNode != nil { out += fmt.Sprintf("%s |_(R)_%s", strings.Repeat(" ", indent), tn.rightNode.toString(indent+1)) } return out } var validSetOperations = map[string]string{ "containsany": "containsAny", "containsall": "containsAll", "consistsof": "consistsOf", "issubsetof": "isSubsetOf", "isempty": "isEmpty", } func tokenize(input string) func() (*treeNode, error) { lastToken := lfTokenInvalid lastValue := "" runes, i := []rune(input), 0 peekIs := func(r rune) bool { if i+1 < len(runes) { return runes[i+1] == r } return false } consumeUntil := func(cutset string) (string, int) { j := i for ; j < len(runes); j++ { if strings.IndexRune(cutset, runes[j]) >= 0 { break } } return string(runes[i:j]), j - i } return func() (*treeNode, error) { for i < len(runes) && runes[i] == ' ' { i += 1 } if i >= len(runes) { return &treeNode{token: lfTokenEOF}, nil } node := &treeNode{location: i} defer func() { lastToken = node.token lastValue = node.value }() if lastToken == lfTokenSetKey { //we should get a valid set operation next value, n := consumeUntil(" )") if validSetOperations[strings.ToLower(value)] == "" { return &treeNode{}, GinkgoErrors.SyntaxErrorParsingLabelFilter(input, i, fmt.Sprintf("Invalid set operation '%s'.", value)) } i += n node.token, node.value = lfTokenSetOperation, value return node, nil } if lastToken == lfTokenSetOperation { //we should get an argument next, if we aren't isempty var arg = "" origI := i if runes[i] == '{' { i += 1 value, n := consumeUntil("}") if i+n >= len(runes) { return &treeNode{}, GinkgoErrors.SyntaxErrorParsingLabelFilter(input, i-1, "Missing closing '}' in set operation argument?") } i += n + 1 arg = value } else { value, n := consumeUntil("&|!,()/") i += n arg = strings.TrimSpace(value) } if strings.ToLower(lastValue) == "isempty" && arg != "" { return &treeNode{}, GinkgoErrors.SyntaxErrorParsingLabelFilter(input, origI, fmt.Sprintf("isEmpty does not take arguments, was passed '%s'.", arg)) } if arg == "" && strings.ToLower(lastValue) != "isempty" { if i < len(runes) && runes[i] == '/' { return &treeNode{}, GinkgoErrors.SyntaxErrorParsingLabelFilter(input, origI, "Set operations do not support regular expressions.") } else { return &treeNode{}, GinkgoErrors.SyntaxErrorParsingLabelFilter(input, origI, fmt.Sprintf("Set operation '%s' requires an argument.", lastValue)) } } // note that we sent an empty SetArgument token if we are isempty node.token, node.value = lfTokenSetArgument, arg return node, nil } switch runes[i] { case '&': if !peekIs('&') { return &treeNode{}, GinkgoErrors.SyntaxErrorParsingLabelFilter(input, i, "Invalid token '&'. Did you mean '&&'?") } i += 2 node.token = lfTokenAnd case '|': if !peekIs('|') { return &treeNode{}, GinkgoErrors.SyntaxErrorParsingLabelFilter(input, i, "Invalid token '|'. Did you mean '||'?") } i += 2 node.token = lfTokenOr case '!': i += 1 node.token = lfTokenNot case ',': i += 1 node.token = lfTokenOr case '(': i += 1 node.token = lfTokenOpenGroup case ')': i += 1 node.token = lfTokenCloseGroup case '/': i += 1 value, n := consumeUntil("/") i += n + 1 node.token, node.value = lfTokenRegexp, value default: value, n := consumeUntil("&|!,()/:") i += n value = strings.TrimSpace(value) //are we the beginning of a set operation? if i < len(runes) && runes[i] == ':' { if peekIs(' ') { if value == "" { return &treeNode{}, GinkgoErrors.SyntaxErrorParsingLabelFilter(input, i, "Missing set key.") } i += 1 //we are the beginning of a set operation node.token, node.value = lfTokenSetKey, value return node, nil } additionalValue, n := consumeUntil("&|!,()/") additionalValue = strings.TrimSpace(additionalValue) if additionalValue == ":" { return &treeNode{}, GinkgoErrors.SyntaxErrorParsingLabelFilter(input, i, "Missing set operation.") } i += n value += additionalValue } valueToCheckForSetOperation := strings.ToLower(value) for setOperation := range validSetOperations { idx := strings.Index(valueToCheckForSetOperation, " "+setOperation) if idx > 0 { return &treeNode{}, GinkgoErrors.SyntaxErrorParsingLabelFilter(input, i-n+idx+1, fmt.Sprintf("Looks like you are using the set operator '%s' but did not provide a set key. Did you forget the ':'?", validSetOperations[setOperation])) } } node.token, node.value = lfTokenLabel, strings.TrimSpace(value) } return node, nil } } func MustParseLabelFilter(input string) LabelFilter { filter, err := ParseLabelFilter(input) if err != nil { panic(err) } return filter } func ParseLabelFilter(input string) (LabelFilter, error) { if DEBUG_LABEL_FILTER_PARSING { fmt.Println("\n==============") fmt.Println("Input: ", input) fmt.Print("Tokens: ") } if input == "" { return func(_ []string) bool { return true }, nil } nextToken := tokenize(input) root := &treeNode{token: lfTokenRoot} current := root LOOP: for { node, err := nextToken() if err != nil { return nil, err } if DEBUG_LABEL_FILTER_PARSING { fmt.Print(node.tokenString() + " ") } switch node.token { case lfTokenEOF: break LOOP case lfTokenLabel, lfTokenRegexp, lfTokenSetKey: if current.rightNode != nil { return nil, GinkgoErrors.SyntaxErrorParsingLabelFilter(input, node.location, "Found two adjacent labels. You need an operator between them.") } current.setRightNode(node) case lfTokenNot, lfTokenOpenGroup: if current.rightNode != nil { return nil, GinkgoErrors.SyntaxErrorParsingLabelFilter(input, node.location, fmt.Sprintf("Invalid token '%s'.", node.token)) } current.setRightNode(node) current = node case lfTokenAnd, lfTokenOr: if current.rightNode == nil { return nil, GinkgoErrors.SyntaxErrorParsingLabelFilter(input, node.location, fmt.Sprintf("Operator '%s' missing left hand operand.", node.token)) } nodeToStealFrom := current.firstAncestorWithPrecedenceLEQ(node.token.Precedence()) node.setLeftNode(nodeToStealFrom.rightNode) nodeToStealFrom.setRightNode(node) current = node case lfTokenSetOperation: if current.rightNode == nil { return nil, GinkgoErrors.SyntaxErrorParsingLabelFilter(input, node.location, fmt.Sprintf("Set operation '%s' missing left hand operand.", node.value)) } node.setLeftNode(current.rightNode) current.setRightNode(node) current = node case lfTokenSetArgument: if current.rightNode != nil { return nil, GinkgoErrors.SyntaxErrorParsingLabelFilter(input, node.location, fmt.Sprintf("Unexpected set argument '%s'.", node.token)) } current.setRightNode(node) case lfTokenCloseGroup: firstUnmatchedOpenNode := current.firstUnmatchedOpenNode() if firstUnmatchedOpenNode == nil { return nil, GinkgoErrors.SyntaxErrorParsingLabelFilter(input, node.location, "Mismatched ')' - could not find matching '('.") } if firstUnmatchedOpenNode == current && current.rightNode == nil { return nil, GinkgoErrors.SyntaxErrorParsingLabelFilter(input, node.location, "Found empty '()' group.") } firstUnmatchedOpenNode.token = lfTokenCloseGroup //signify the group is now closed current = firstUnmatchedOpenNode.parent default: return nil, GinkgoErrors.SyntaxErrorParsingLabelFilter(input, node.location, fmt.Sprintf("Unknown token '%s'.", node.token)) } } if DEBUG_LABEL_FILTER_PARSING { fmt.Printf("\n Tree:\n%s", root.toString(0)) } return root.constructLabelFilter(input) } func ValidateAndCleanupLabel(label string, cl CodeLocation) (string, error) { out := strings.TrimSpace(label) if out == "" { return "", GinkgoErrors.InvalidEmptyLabel(cl) } if strings.ContainsAny(out, "&|!,()/") { return "", GinkgoErrors.InvalidLabel(label, cl) } if out[0] == ':' { return "", GinkgoErrors.InvalidLabel(label, cl) } if strings.Contains(out, ":") { components := strings.SplitN(out, ":", 2) if len(components) < 2 || components[1] == "" { return "", GinkgoErrors.InvalidLabel(label, cl) } } return out, nil } golang-github-onsi-ginkgo-v2-2.22.0/types/label_filter_test.go000066400000000000000000000267441472321612100242650ustar00rootroot00000000000000package types_test import ( "fmt" "reflect" "strings" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" "github.com/onsi/ginkgo/v2/types" ) var _ = Describe("LabelFilter", func() { BeforeEach(func() { types.DEBUG_LABEL_FILTER_PARSING = false }) DescribeTable("Catching and communicating syntax errors", func(filter string, location int, message string) { _, err := types.ParseLabelFilter(filter) Ω(err).Should(MatchError(types.GinkgoErrors.SyntaxErrorParsingLabelFilter(filter, location, message))) }, func(filter string, location int, message string) string { return fmt.Sprintf("%s => %s", filter, message) }, Entry(nil, "(A && B) || ((C && D) && E", 12, "Mismatched '(' - could not find matching ')'."), Entry(nil, "A || B) && C", 6, "Mismatched ')' - could not find matching '('."), Entry(nil, "A && ( )", 9, "Found empty '()' group."), Entry(nil, "A && ((( )))", 11, "Found empty '()' group."), Entry(nil, "A && /[a/", 5, "RegExp compilation error: error parsing regexp: missing closing ]: `[a`"), Entry(nil, "A &&", -1, "Unexpected EOF."), Entry(nil, "A & B", 2, "Invalid token '&'. Did you mean '&&'?"), Entry(nil, "A | B", 2, "Invalid token '|'. Did you mean '||'?"), Entry(nil, "(A) B", 4, "Found two adjacent labels. You need an operator between them."), Entry(nil, "A (B)", 2, "Invalid token '('."), Entry(nil, "A !B", 2, "Invalid token '!'."), Entry(nil, "A !B", 2, "Invalid token '!'."), Entry(nil, " && B", 1, "Operator '&&' missing left hand operand."), Entry(nil, " || B", 1, "Operator '||' missing left hand operand."), Entry(nil, "&&", 0, "Operator '&&' missing left hand operand."), Entry(nil, "&& || B", 0, "Operator '&&' missing left hand operand."), Entry(nil, ":", 0, "Missing set operation."), Entry(nil, ": isEmpty", 0, "Missing set key."), Entry(nil, "A:", 1, "Missing set operation."), Entry(nil, "A: B", 3, "Invalid set operation 'B'."), Entry(nil, "A: B C", 3, "Invalid set operation 'B'."), Entry(nil, "A isEmpty", 2, "Looks like you are using the set operator 'isEmpty' but did not provide a set key. Did you forget the ':'?"), Entry(nil, "A bloop containsAny", 8, "Looks like you are using the set operator 'containsAny' but did not provide a set key. Did you forget the ':'?"), Entry(nil, "A: isEmpty B", 11, "isEmpty does not take arguments, was passed 'B'."), Entry(nil, "A: containsAny", 3, "Set operation 'containsAny' is missing an argument."), Entry(nil, "A: containsAny {Foo", 15, "Missing closing '}' in set operation argument?"), Entry(nil, "A: containsAny /[a]/", 15, "Set operations do not support regular expressions."), Entry(nil, "/A/: containsAny Foo", 3, "Missing set key."), ) type matchingLabels []string type nonMatchingLabels []string M := func(l ...string) matchingLabels { return matchingLabels(l) } NM := func(l ...string) nonMatchingLabels { return nonMatchingLabels(l) } DescribeTable("Generating correct LabelFilter", func(filter string, samples ...interface{}) { lf, err := types.ParseLabelFilter(filter) Ω(err).ShouldNot(HaveOccurred()) for _, sample := range samples { switch reflect.TypeOf(sample) { case reflect.TypeOf(matchingLabels{}): labels := []string(sample.(matchingLabels)) Ω(lf(labels)).Should(BeTrue(), strings.Join(labels, ",")) case reflect.TypeOf(nonMatchingLabels{}): labels := []string(sample.(nonMatchingLabels)) Ω(lf(labels)).Should(BeFalse(), strings.Join(labels, ",")) } } }, Entry("An empty label", "", M("cat"), M("cat", "dog"), M("dog", "cat"), M(), M("cow"), ), Entry("A single label", "cat", M("cat"), M("cat", "dog"), M("dog", "cat"), NM(), NM("cow"), ), Entry("Trimming whitespace", " cat ", M("cat"), M("cat", "dog"), M("dog", "cat"), NM(), NM("cow"), ), Entry("Ignoring case", "cat", M("CAT"), ), Entry("A simple ||", "cat || dog ", M("cat"), M("cat", "cow", "dog"), M("dog", "cow", "cat"), M("dog"), NM(), NM("cow"), NM("cat dog"), ), Entry("A simple ||", "cat||dog ", M("cat"), M("cat", "cow", "dog"), M("dog", "cow", "cat"), M("dog"), NM(), NM("cow"), ), Entry("A simple ,", "cat, dog ", M("cat"), M("cat", "cow", "dog"), M("dog", "cow", "cat"), M("dog"), NM(), NM("cow"), ), Entry("Multiple ORs ,", "cat,dog||cow,fruit ", M("cat"), M("cat", "cow", "dog"), M("dog"), M("fruit"), M("cow", "aardvark"), NM(), NM("aardvark"), ), Entry("A simple NOT", "!cat", M("dog"), M(), NM("cat"), NM("cat", "dog"), ), Entry("A double negative", "!!cat", M("cat"), M("cat", "dog"), NM(), NM("dog"), ), Entry("A simple AND", "cat && dog", M("cat", "dog"), M("cat", "dog", "cow"), NM(), NM("cat"), NM("dog"), NM("cow"), NM("cat dog"), ), Entry("Multiple ANDs", "cat && dog && cow fruit", M("cat", "dog", "cow fruit"), M("cat", "dog", "cow fruit", "aardvark"), NM(), NM("cat", "dog", "cow", "fruit"), ), Entry("&& has > precedence than ||", "cat || dog && cow", M("cat"), M("dog", "cow"), NM(), NM("dog"), ), Entry("&& has > precedence than || but () overrides", "(cat || dog) && cow", M("cat", "cow"), M("dog", "cow"), NM(), NM("dog"), NM("cat"), NM("cow"), NM("cat", "dog"), ), Entry("&& has > precedence than ||", "cat && dog || cow", M("cat", "dog"), M("cow"), NM(), NM("cat"), NM("dog"), ), Entry("&& has > precedence than || but () overrides", "cat && (dog || cow)", M("cat", "dog"), M("cat", "cow"), NM(), NM("cat"), NM("dog"), NM("cow"), ), Entry("! has > precedence than &&", "!cat && dog", M("dog"), M("dog", "cow"), NM(), NM("cat", "dog"), NM("cat"), NM("cow"), ), Entry("! has > precedence than && but () overrides", "!(cat && dog)", M(), M("cow"), M("cat"), M("dog"), M("dog", "cow"), NM("cat", "dog"), NM("cat", "dog", "cow"), ), Entry("! has > precedence than ||", "!cat || dog", M(), M("dog"), M("cow"), NM("cat"), NM("cat", "cow"), ), Entry("! has > precedence than || but () overrides", "!(cat || dog)", M(), M("cow"), NM("cat"), NM("dog"), NM("cat", "dog"), NM("cat", "dog", "cow"), ), Entry("it can handle multiple groups", "(!(cat || dog) && fruit) || (cow && !aardvark)", M("cow"), M("fruit"), M("fruit", "cow", "aardvark"), M("cow", "dog", "fruit"), NM(), NM("cow", "aardvark"), NM("cat", "fruit"), NM("dog", "fruit"), NM("dog", "cat", "fruit"), NM("cat", "fruit", "cow", "aardvark"), ), Entry("Coalescing groups", "(((cat)))", M("cat"), M("cat", "dog"), M("dog", "cat"), NM(), NM("cow"), ), Entry("Comping whitespace around a simple group", " (cat) ", M("cat"), M("cat", "dog"), M("dog", "cat"), NM(), NM("cow"), ), Entry("Supporting regular expressions", "/c[ao]/ && dog", M("dog", "cat"), M("dog", "cow"), M("cat", "cow", "dog"), M("dog", "orca"), NM("dog"), NM("cow"), NM("cat"), NM("dog", "fruit"), NM("dog", "cup"), ), Entry("Matching set keys explicitly", "Feature:Alpha", M("Feature:Alpha"), M("Feature:Alpha", "Feature:Beta"), M("Feature:Beta", "Feature:Alpha"), NM("Feature:Beta"), NM("dog"), ), Entry("Set operation: isEmpty", "Feature: isEmpty", M(), NM("Feature:Beta"), NM("Feature:Beta", "Feature:Alpha"), M("dog"), M("Feature"), M("Widget:Foo"), ), Entry("Set operation: containsAny (one)", "Feature: containsAny Alpha", M("Feature:Alpha"), M("Feature:Alpha", "Feature:Beta"), M("Feature:Beta", "Feature:Alpha"), NM("Feature:Beta"), NM("dog"), NM("Feature"), ), Entry("Set operation: containsAny (many)", "Feature: containsAny {Alpha, Beta}", M("Feature:Alpha"), M("Feature: alpha", "Feature : beta"), M("Feature:Beta", "Feature:Alpha"), M("Feature:Beta"), M("Feature:Alpha", "Feature:Gamma"), NM("Feature:Gamma"), NM("dog"), NM("Feature"), ), Entry("Set operation: containsAll (one)", "Feature: containsAll Alpha", M("Feature:Alpha"), M("Feature:Alpha", "Feature:Beta"), M("Feature:Beta", "Feature:Alpha"), NM("Feature:Beta"), NM("dog"), NM("Feature"), ), Entry("Set operation: containsAll (many)", "Feature: containsAll {Alpha, Beta}", NM("Feature:Alpha"), M("Feature:alpha", "Feature:Beta"), M("Feature:beta", "Feature:Alpha"), M("feature:alpha", "feature: beta", "Feature:Gamma"), NM("Feature:Beta"), NM("Feature:Gamma"), NM("dog"), NM("Feature"), ), Entry("Set operation: consistsOf (one)", "Feature: consistsOf Alpha", M("Feature:Alpha"), NM("Feature:Alpha", "Feature:Beta"), NM("Feature:Beta", "Feature:Alpha"), NM("Feature:Beta"), NM("dog"), NM("Feature"), ), Entry("Set operation: consistsOf (many)", "Feature: consistsOf {Alpha, Beta}", NM("Feature:Alpha"), M("Feature:alpha", "Feature:Beta"), M("Feature:beta", "Feature:Alpha"), NM("feature:alpha", "feature: beta", "Feature:Gamma"), NM("Feature:Beta"), NM("Feature:Gamma"), NM("dog"), NM("Feature"), ), Entry("Set operation: isSubsetOf (one)", "Feature: isSubsetOf Alpha", M("Feature:Alpha"), NM("Feature:Alpha", "Feature:Beta"), NM("Feature:Beta", "Feature:Alpha"), NM("Feature:Beta"), M("dog"), M("Feature"), M(""), ), Entry("Set operation: isSubsetOf (many)", "Feature: isSubsetOf {Alpha, Beta}", M("Feature:Alpha"), M("Feature:alpha", "Feature:Beta"), M("Feature:beta", "Feature:Alpha"), NM("feature:alpha", "feature: beta", "Feature:Gamma"), M("Feature:Beta"), NM("Feature:Gamma"), M("dog"), M("Feature"), M(""), ), Entry("Set operations with booleans and explicit labels", "Production && (Feature: isSubsetOf {Alpha, Beta} && !(Feature: isEmpty))", M("Production", "Feature:Alpha"), M("Production", "Feature:Beta"), M("Production", "Feature:Beta", "Feature:Alpha"), NM("Production", "dog"), NM("Production", "Feature:Gamma", "Feature:Alpha"), NM("Staging", "Feature:Alpha"), NM("Production"), NM(""), ), Entry("Set operation: values can have colons", "Feature: containsAny Alpha:1", M("Feature:Alpha:1"), M("Feature: Alpha:1"), M("Feature :Alpha:1"), M("Feature : Alpha:1"), NM("Feature:Alpha:2"), NM("Feature:Alpha"), NM("Feature:Beta:1"), NM("Feature:Alpha : 1"), ), ) cl := types.NewCodeLocation(0) DescribeTable("Validating Labels", func(label string, expected string, expectedError error) { out, err := types.ValidateAndCleanupLabel(label, cl) Ω(out).Should(Equal(expected)) if expectedError == nil { Ω(err).Should(BeNil()) } else { Ω(err).Should(Equal(expectedError)) } }, func(label string, expected string, expectedError error) string { return label }, Entry(nil, "cow", "cow", nil), Entry(nil, " cow dog ", "cow dog", nil), Entry(nil, "", "", types.GinkgoErrors.InvalidEmptyLabel(cl)), Entry(nil, " ", "", types.GinkgoErrors.InvalidEmptyLabel(cl)), Entry(nil, "cow&", "", types.GinkgoErrors.InvalidLabel("cow&", cl)), Entry(nil, "cow|", "", types.GinkgoErrors.InvalidLabel("cow|", cl)), Entry(nil, "cow,", "", types.GinkgoErrors.InvalidLabel("cow,", cl)), Entry(nil, "cow(", "", types.GinkgoErrors.InvalidLabel("cow(", cl)), Entry(nil, "cow()", "", types.GinkgoErrors.InvalidLabel("cow()", cl)), Entry(nil, "cow)", "", types.GinkgoErrors.InvalidLabel("cow)", cl)), Entry(nil, "cow/", "", types.GinkgoErrors.InvalidLabel("cow/", cl)), Entry(nil, ":", "", types.GinkgoErrors.InvalidLabel(":", cl)), Entry(nil, "Feature:", "", types.GinkgoErrors.InvalidLabel("Feature:", cl)), Entry(nil, ":Alpha", "", types.GinkgoErrors.InvalidLabel(":Alpha", cl)), ) Describe("MustParseLabelFilter", func() { It("panics if passed an invalid filter", func() { Ω(types.MustParseLabelFilter("dog")([]string{"dog"})).Should(BeTrue()) Ω(types.MustParseLabelFilter("dog")([]string{"cat"})).Should(BeFalse()) Ω(func() { types.MustParseLabelFilter("!") }).Should(Panic()) }) }) }) golang-github-onsi-ginkgo-v2-2.22.0/types/report_entry.go000066400000000000000000000131151472321612100233220ustar00rootroot00000000000000package types import ( "encoding/json" "fmt" "time" ) // ReportEntryValue wraps a report entry's value ensuring it can be encoded and decoded safely into reports // and across the network connection when running in parallel type ReportEntryValue struct { raw interface{} //unexported to prevent gob from freaking out about unregistered structs AsJSON string Representation string } func WrapEntryValue(value interface{}) ReportEntryValue { return ReportEntryValue{ raw: value, } } func (rev ReportEntryValue) GetRawValue() interface{} { return rev.raw } func (rev ReportEntryValue) String() string { if rev.raw == nil { return "" } if colorableStringer, ok := rev.raw.(ColorableStringer); ok { return colorableStringer.ColorableString() } if stringer, ok := rev.raw.(fmt.Stringer); ok { return stringer.String() } if rev.Representation != "" { return rev.Representation } return fmt.Sprintf("%+v", rev.raw) } func (rev ReportEntryValue) MarshalJSON() ([]byte, error) { //All this to capture the representation at encoding-time, not creating time //This way users can Report on pointers and get their final values at reporting-time out := struct { AsJSON string Representation string }{ Representation: rev.String(), } asJSON, err := json.Marshal(rev.raw) if err != nil { return nil, err } out.AsJSON = string(asJSON) return json.Marshal(out) } func (rev *ReportEntryValue) UnmarshalJSON(data []byte) error { in := struct { AsJSON string Representation string }{} err := json.Unmarshal(data, &in) if err != nil { return err } rev.AsJSON = in.AsJSON rev.Representation = in.Representation return json.Unmarshal([]byte(in.AsJSON), &(rev.raw)) } func (rev ReportEntryValue) GobEncode() ([]byte, error) { return rev.MarshalJSON() } func (rev *ReportEntryValue) GobDecode(data []byte) error { return rev.UnmarshalJSON(data) } // ReportEntry captures information attached to `SpecReport` via `AddReportEntry` type ReportEntry struct { // Visibility captures the visibility policy for this ReportEntry Visibility ReportEntryVisibility // Location captures the location of the AddReportEntry call Location CodeLocation Time time.Time //need this for backwards compatibility TimelineLocation TimelineLocation // Name captures the name of this report Name string // Value captures the (optional) object passed into AddReportEntry - this can be // anything the user wants. The value passed to AddReportEntry is wrapped in a ReportEntryValue to make // encoding/decoding the value easier. To access the raw value call entry.GetRawValue() Value ReportEntryValue } // 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 } // StringRepresentation() returns the string representation of the value associated with the ReportEntry -- // if value is nil, empty string is returned // if value is a `ColorableStringer` then `Value.ColorableString()` is returned // if value is a `fmt.Stringer` then `Value.String()` is returned // otherwise the value is formatted with "%+v" func (entry ReportEntry) StringRepresentation() string { return entry.Value.String() } // GetRawValue returns the Value object that was passed to AddReportEntry // If called in-process this will be the same object that was passed into AddReportEntry. // If used from a rehydrated JSON file _or_ in a ReportAfterSuite when running in parallel this will be // a JSON-decoded {}interface. If you want to reconstitute your original object you can decode the entry.Value.AsJSON // field yourself. func (entry ReportEntry) GetRawValue() interface{} { return entry.Value.GetRawValue() } func (entry ReportEntry) GetTimelineLocation() TimelineLocation { return entry.TimelineLocation } type ReportEntries []ReportEntry func (re ReportEntries) HasVisibility(visibilities ...ReportEntryVisibility) bool { for _, entry := range re { if entry.Visibility.Is(visibilities...) { return true } } return false } func (re ReportEntries) WithVisibility(visibilities ...ReportEntryVisibility) ReportEntries { out := ReportEntries{} for _, entry := range re { if entry.Visibility.Is(visibilities...) { out = append(out, entry) } } return out } // ReportEntryVisibility governs the visibility of ReportEntries in Ginkgo's console reporter type ReportEntryVisibility uint const ( // Always print out this ReportEntry ReportEntryVisibilityAlways ReportEntryVisibility = iota // Only print out this ReportEntry if the spec fails or if the test is run with -v ReportEntryVisibilityFailureOrVerbose // Never print out this ReportEntry (note that ReportEntrys are always encoded in machine readable reports (e.g. JSON, JUnit, etc.)) ReportEntryVisibilityNever ) var revEnumSupport = NewEnumSupport(map[uint]string{ uint(ReportEntryVisibilityAlways): "always", uint(ReportEntryVisibilityFailureOrVerbose): "failure-or-verbose", uint(ReportEntryVisibilityNever): "never", }) func (rev ReportEntryVisibility) String() string { return revEnumSupport.String(uint(rev)) } func (rev *ReportEntryVisibility) UnmarshalJSON(b []byte) error { out, err := revEnumSupport.UnmarshJSON(b) *rev = ReportEntryVisibility(out) return err } func (rev ReportEntryVisibility) MarshalJSON() ([]byte, error) { return revEnumSupport.MarshJSON(uint(rev)) } func (v ReportEntryVisibility) Is(visibilities ...ReportEntryVisibility) bool { for _, visibility := range visibilities { if v == visibility { return true } } return false } golang-github-onsi-ginkgo-v2-2.22.0/types/types.go000066400000000000000000000747121472321612100217440ustar00rootroot00000000000000package types import ( "encoding/json" "fmt" "os" "sort" "strings" "time" ) const GINKGO_FOCUS_EXIT_CODE = 197 var GINKGO_TIME_FORMAT = "01/02/06 15:04:05.999" func init() { if os.Getenv("GINKGO_TIME_FORMAT") != "" { GINKGO_TIME_FORMAT = os.Getenv("GINKGO_TIME_FORMAT") } } // Report captures information about a Ginkgo test run type Report struct { //SuitePath captures the absolute path to the test suite SuitePath string //SuiteDescription captures the description string passed to the DSL's RunSpecs() function SuiteDescription string //SuiteLabels captures any labels attached to the suite by the DSL's RunSpecs() function SuiteLabels []string //SuiteSucceeded captures the success or failure status of the test run //If true, the test run is considered successful. //If false, the test run is considered unsuccessful SuiteSucceeded bool //SuiteHasProgrammaticFocus captures whether the test suite has a test or set of tests that are programmatically focused //(i.e an `FIt` or an `FDescribe` SuiteHasProgrammaticFocus bool //SpecialSuiteFailureReasons may contain special failure reasons //For example, a test suite might be considered "failed" even if none of the individual specs //have a failure state. For example, if the user has configured --fail-on-pending the test suite //will have failed if there are pending tests even though all non-pending tests may have passed. In such //cases, Ginkgo populates SpecialSuiteFailureReasons with a clear message indicating the reason for the failure. //SpecialSuiteFailureReasons is also populated if the test suite is interrupted by the user. //Since multiple special failure reasons can occur, this field is a slice. SpecialSuiteFailureReasons []string //PreRunStats contains a set of stats captured before the test run begins. This is primarily used //by Ginkgo's reporter to tell the user how many specs are in the current suite (PreRunStats.TotalSpecs) //and how many it intends to run (PreRunStats.SpecsThatWillRun) after applying any relevant focus or skip filters. PreRunStats PreRunStats //StartTime and EndTime capture the start and end time of the test run StartTime time.Time EndTime time.Time //RunTime captures the duration of the test run RunTime time.Duration //SuiteConfig captures the Ginkgo configuration governing this test run //SuiteConfig includes information necessary for reproducing an identical test run, //such as the random seed and any filters applied during the test run SuiteConfig SuiteConfig //SpecReports is a list of all SpecReports generated by this test run //It is empty when the SuiteReport is provided to ReportBeforeSuite SpecReports SpecReports } // PreRunStats contains a set of stats captured before the test run begins. This is primarily used // by Ginkgo's reporter to tell the user how many specs are in the current suite (PreRunStats.TotalSpecs) // and how many it intends to run (PreRunStats.SpecsThatWillRun) after applying any relevant focus or skip filters. type PreRunStats struct { TotalSpecs int SpecsThatWillRun int } // Add is used by Ginkgo's parallel aggregation mechanisms to combine test run reports form individual parallel processes // to form a complete final report. func (report Report) Add(other Report) Report { report.SuiteSucceeded = report.SuiteSucceeded && other.SuiteSucceeded if other.StartTime.Before(report.StartTime) { report.StartTime = other.StartTime } if other.EndTime.After(report.EndTime) { report.EndTime = other.EndTime } specialSuiteFailureReasons := []string{} reasonsLookup := map[string]bool{} for _, reasons := range [][]string{report.SpecialSuiteFailureReasons, other.SpecialSuiteFailureReasons} { for _, reason := range reasons { if !reasonsLookup[reason] { reasonsLookup[reason] = true specialSuiteFailureReasons = append(specialSuiteFailureReasons, reason) } } } report.SpecialSuiteFailureReasons = specialSuiteFailureReasons report.RunTime = report.EndTime.Sub(report.StartTime) reports := make(SpecReports, len(report.SpecReports)+len(other.SpecReports)) copy(reports, report.SpecReports) offset := len(report.SpecReports) for i := range other.SpecReports { reports[i+offset] = other.SpecReports[i] } report.SpecReports = reports return report } // SpecReport captures information about a Ginkgo spec. type SpecReport struct { // ContainerHierarchyTexts is a slice containing the text strings of // all Describe/Context/When containers in this spec's hierarchy. ContainerHierarchyTexts []string // ContainerHierarchyLocations is a slice containing the CodeLocations of // all Describe/Context/When containers in this spec's hierarchy. ContainerHierarchyLocations []CodeLocation // ContainerHierarchyLabels is a slice containing the labels of // all Describe/Context/When containers in this spec's hierarchy ContainerHierarchyLabels [][]string // LeafNodeType, LeadNodeLocation, LeafNodeLabels and LeafNodeText capture the NodeType, CodeLocation, and text // of the Ginkgo node being tested (typically an NodeTypeIt node, though this can also be // one of the NodeTypesForSuiteLevelNodes node types) LeafNodeType NodeType LeafNodeLocation CodeLocation LeafNodeLabels []string LeafNodeText string // State captures whether the spec has passed, failed, etc. State SpecState // IsSerial captures whether the spec has the Serial decorator IsSerial bool // IsInOrderedContainer captures whether the spec appears in an Ordered container IsInOrderedContainer bool // StartTime and EndTime capture the start and end time of the spec StartTime time.Time EndTime time.Time // RunTime captures the duration of the spec RunTime time.Duration // ParallelProcess captures the parallel process that this spec ran on ParallelProcess int // RunningInParallel captures whether this spec is part of a suite that ran in parallel RunningInParallel bool //Failure is populated if a spec has failed, panicked, been interrupted, or skipped by the user (e.g. calling Skip()) //It includes detailed information about the Failure Failure Failure // NumAttempts captures the number of times this Spec was run. // Flakey specs can be retried with ginkgo --flake-attempts=N or the use of the FlakeAttempts decorator. // Repeated specs can be retried with the use of the MustPassRepeatedly decorator NumAttempts int // MaxFlakeAttempts captures whether the spec has been retried with ginkgo --flake-attempts=N or the use of the FlakeAttempts decorator. MaxFlakeAttempts int // MaxMustPassRepeatedly captures whether the spec has the MustPassRepeatedly decorator MaxMustPassRepeatedly int // CapturedGinkgoWriterOutput contains text printed to the GinkgoWriter CapturedGinkgoWriterOutput string // CapturedStdOutErr contains text printed to stdout/stderr (when running in parallel) // This is always empty when running in series or calling CurrentSpecReport() // It is used internally by Ginkgo's reporter CapturedStdOutErr string // 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 // AdditionalFailures contains any failures that occurred after the initial spec failure. These typically occur in cleanup nodes after the initial failure and are only emitted when running in verbose mode. AdditionalFailures []AdditionalFailure // SpecEvents capture additional events that occur during the spec run SpecEvents SpecEvents } func (report SpecReport) MarshalJSON() ([]byte, error) { //All this to avoid emitting an empty Failure struct in the JSON out := struct { ContainerHierarchyTexts []string ContainerHierarchyLocations []CodeLocation ContainerHierarchyLabels [][]string LeafNodeType NodeType LeafNodeLocation CodeLocation LeafNodeLabels []string LeafNodeText string State SpecState StartTime time.Time EndTime time.Time RunTime time.Duration ParallelProcess int Failure *Failure `json:",omitempty"` NumAttempts int MaxFlakeAttempts int MaxMustPassRepeatedly int CapturedGinkgoWriterOutput string `json:",omitempty"` CapturedStdOutErr string `json:",omitempty"` ReportEntries ReportEntries `json:",omitempty"` ProgressReports []ProgressReport `json:",omitempty"` AdditionalFailures []AdditionalFailure `json:",omitempty"` SpecEvents SpecEvents `json:",omitempty"` }{ ContainerHierarchyTexts: report.ContainerHierarchyTexts, ContainerHierarchyLocations: report.ContainerHierarchyLocations, ContainerHierarchyLabels: report.ContainerHierarchyLabels, LeafNodeType: report.LeafNodeType, LeafNodeLocation: report.LeafNodeLocation, LeafNodeLabels: report.LeafNodeLabels, LeafNodeText: report.LeafNodeText, State: report.State, StartTime: report.StartTime, EndTime: report.EndTime, RunTime: report.RunTime, ParallelProcess: report.ParallelProcess, Failure: nil, ReportEntries: nil, NumAttempts: report.NumAttempts, MaxFlakeAttempts: report.MaxFlakeAttempts, MaxMustPassRepeatedly: report.MaxMustPassRepeatedly, CapturedGinkgoWriterOutput: report.CapturedGinkgoWriterOutput, CapturedStdOutErr: report.CapturedStdOutErr, } if !report.Failure.IsZero() { out.Failure = &(report.Failure) } if len(report.ReportEntries) > 0 { out.ReportEntries = report.ReportEntries } if len(report.ProgressReports) > 0 { out.ProgressReports = report.ProgressReports } if len(report.AdditionalFailures) > 0 { out.AdditionalFailures = report.AdditionalFailures } if len(report.SpecEvents) > 0 { out.SpecEvents = report.SpecEvents } return json.Marshal(out) } // CombinedOutput returns a single string representation of both CapturedStdOutErr and CapturedGinkgoWriterOutput // Note that both are empty when using CurrentSpecReport() so CurrentSpecReport().CombinedOutput() will always be empty. // CombinedOutput() is used internally by Ginkgo's reporter. func (report SpecReport) CombinedOutput() string { if report.CapturedStdOutErr == "" { return report.CapturedGinkgoWriterOutput } if report.CapturedGinkgoWriterOutput == "" { return report.CapturedStdOutErr } return report.CapturedStdOutErr + "\n" + report.CapturedGinkgoWriterOutput } // Failed returns true if report.State is one of the SpecStateFailureStates // (SpecStateFailed, SpecStatePanicked, SpecStateinterrupted, SpecStateAborted) func (report SpecReport) Failed() bool { return report.State.Is(SpecStateFailureStates) } // FullText returns a concatenation of all the report.ContainerHierarchyTexts and report.LeafNodeText func (report SpecReport) FullText() string { texts := []string{} texts = append(texts, report.ContainerHierarchyTexts...) if report.LeafNodeText != "" { texts = append(texts, report.LeafNodeText) } return strings.Join(texts, " ") } // Labels returns a deduped set of all the spec's Labels. func (report SpecReport) Labels() []string { out := []string{} seen := map[string]bool{} for _, labels := range report.ContainerHierarchyLabels { for _, label := range labels { if !seen[label] { seen[label] = true out = append(out, label) } } } for _, label := range report.LeafNodeLabels { if !seen[label] { seen[label] = true out = append(out, label) } } return out } // MatchesLabelFilter returns true if the spec satisfies the passed in label filter query func (report SpecReport) MatchesLabelFilter(query string) (bool, error) { filter, err := ParseLabelFilter(query) if err != nil { return false, err } return filter(report.Labels()), nil } // FileName() returns the name of the file containing the spec func (report SpecReport) FileName() string { return report.LeafNodeLocation.FileName } // LineNumber() returns the line number of the leaf node func (report SpecReport) LineNumber() int { return report.LeafNodeLocation.LineNumber } // FailureMessage() returns the failure message (or empty string if the test hasn't failed) func (report SpecReport) FailureMessage() string { return report.Failure.Message } // FailureLocation() returns the location of the failure (or an empty CodeLocation if the test hasn't failed) func (report SpecReport) FailureLocation() CodeLocation { return report.Failure.Location } // Timeline() returns a timeline view of the report func (report SpecReport) Timeline() Timeline { timeline := Timeline{} if !report.Failure.IsZero() { timeline = append(timeline, report.Failure) if report.Failure.AdditionalFailure != nil { timeline = append(timeline, *(report.Failure.AdditionalFailure)) } } for _, additionalFailure := range report.AdditionalFailures { timeline = append(timeline, additionalFailure) } for _, reportEntry := range report.ReportEntries { timeline = append(timeline, reportEntry) } for _, progressReport := range report.ProgressReports { timeline = append(timeline, progressReport) } for _, specEvent := range report.SpecEvents { timeline = append(timeline, specEvent) } sort.Sort(timeline) return timeline } type SpecReports []SpecReport // WithLeafNodeType returns the subset of SpecReports with LeafNodeType matching one of the requested NodeTypes func (reports SpecReports) WithLeafNodeType(nodeTypes NodeType) SpecReports { count := 0 for i := range reports { if reports[i].LeafNodeType.Is(nodeTypes) { count++ } } out := make(SpecReports, count) j := 0 for i := range reports { if reports[i].LeafNodeType.Is(nodeTypes) { out[j] = reports[i] j++ } } return out } // WithState returns the subset of SpecReports with State matching one of the requested SpecStates func (reports SpecReports) WithState(states SpecState) SpecReports { count := 0 for i := range reports { if reports[i].State.Is(states) { count++ } } out, j := make(SpecReports, count), 0 for i := range reports { if reports[i].State.Is(states) { out[j] = reports[i] j++ } } return out } // CountWithState returns the number of SpecReports with State matching one of the requested SpecStates func (reports SpecReports) CountWithState(states SpecState) int { n := 0 for i := range reports { if reports[i].State.Is(states) { n += 1 } } return n } // If the Spec passes, CountOfFlakedSpecs returns the number of SpecReports that failed after multiple attempts. func (reports SpecReports) CountOfFlakedSpecs() int { n := 0 for i := range reports { if reports[i].MaxFlakeAttempts > 1 && reports[i].State.Is(SpecStatePassed) && reports[i].NumAttempts > 1 { n += 1 } } return n } // If the Spec fails, CountOfRepeatedSpecs returns the number of SpecReports that passed after multiple attempts func (reports SpecReports) CountOfRepeatedSpecs() int { n := 0 for i := range reports { if reports[i].MaxMustPassRepeatedly > 1 && reports[i].State.Is(SpecStateFailureStates) && reports[i].NumAttempts > 1 { n += 1 } } return n } // TimelineLocation captures the location of an event in the spec's timeline type TimelineLocation struct { //Offset is the offset (in bytes) of the event relative to the GinkgoWriter stream Offset int `json:",omitempty"` //Order is the order of the event with respect to other events. The absolute value of Order //is irrelevant. All that matters is that an event with a lower Order occurs before ane vent with a higher Order Order int `json:",omitempty"` Time time.Time } // TimelineEvent represent an event on the timeline // consumers of Timeline will need to check the concrete type of each entry to determine how to handle it type TimelineEvent interface { GetTimelineLocation() TimelineLocation } type Timeline []TimelineEvent func (t Timeline) Len() int { return len(t) } func (t Timeline) Less(i, j int) bool { return t[i].GetTimelineLocation().Order < t[j].GetTimelineLocation().Order } func (t Timeline) Swap(i, j int) { t[i], t[j] = t[j], t[i] } func (t Timeline) WithoutHiddenReportEntries() Timeline { out := Timeline{} for _, event := range t { if reportEntry, isReportEntry := event.(ReportEntry); isReportEntry && reportEntry.Visibility == ReportEntryVisibilityNever { continue } out = append(out, event) } return out } func (t Timeline) WithoutVeryVerboseSpecEvents() Timeline { out := Timeline{} for _, event := range t { if specEvent, isSpecEvent := event.(SpecEvent); isSpecEvent && specEvent.IsOnlyVisibleAtVeryVerbose() { continue } out = append(out, event) } return out } // Failure captures failure information for an individual test type Failure struct { // Message - the failure message passed into Fail(...). When using a matcher library // like Gomega, this will contain the failure message generated by Gomega. // // Message is also populated if the user has called Skip(...). Message string // Location - the CodeLocation where the failure occurred // This CodeLocation will include a fully-populated StackTrace Location CodeLocation TimelineLocation TimelineLocation // ForwardedPanic - if the failure represents a captured panic (i.e. Summary.State == SpecStatePanicked) // then ForwardedPanic will be populated with a string representation of the captured panic. ForwardedPanic string `json:",omitempty"` // 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, 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. // If populated, FailureNodeContainerIndex will be the index into SpecReport.ContainerHierarchyTexts and SpecReport.ContainerHierarchyLocations that represents the parent container of the node in which the failure occurred. FailureNodeContext FailureNodeContext `json:",omitempty"` FailureNodeType NodeType `json:",omitempty"` FailureNodeLocation CodeLocation `json:",omitempty"` FailureNodeContainerIndex int `json:",omitempty"` //ProgressReport is populated if the spec was interrupted or timed out ProgressReport ProgressReport `json:",omitempty"` //AdditionalFailure is non-nil if a follow-on failure occurred within the same node after the primary failure. This only happens when a node has timed out or been interrupted. In such cases the AdditionalFailure can include information about where/why the spec was stuck. AdditionalFailure *AdditionalFailure `json:",omitempty"` } func (f Failure) IsZero() bool { return f.Message == "" && (f.Location == CodeLocation{}) } func (f Failure) GetTimelineLocation() TimelineLocation { return f.TimelineLocation } // FailureNodeContext captures the location context for the node containing the failing line of code type FailureNodeContext uint const ( FailureNodeContextInvalid FailureNodeContext = iota FailureNodeIsLeafNode FailureNodeAtTopLevel FailureNodeInContainer ) var fncEnumSupport = NewEnumSupport(map[uint]string{ uint(FailureNodeContextInvalid): "INVALID FAILURE NODE CONTEXT", uint(FailureNodeIsLeafNode): "leaf-node", uint(FailureNodeAtTopLevel): "top-level", uint(FailureNodeInContainer): "in-container", }) func (fnc FailureNodeContext) String() string { return fncEnumSupport.String(uint(fnc)) } func (fnc *FailureNodeContext) UnmarshalJSON(b []byte) error { out, err := fncEnumSupport.UnmarshJSON(b) *fnc = FailureNodeContext(out) return err } func (fnc FailureNodeContext) MarshalJSON() ([]byte, error) { return fncEnumSupport.MarshJSON(uint(fnc)) } // AdditionalFailure capturs any additional failures that occur after the initial failure of a psec // these typically occur in clean up nodes after the spec has failed. // We can't simply use Failure as we want to track the SpecState to know what kind of failure this is type AdditionalFailure struct { State SpecState Failure Failure } func (f AdditionalFailure) GetTimelineLocation() TimelineLocation { return f.Failure.TimelineLocation } // SpecState captures the state of a spec // To determine if a given `state` represents a failure state, use `state.Is(SpecStateFailureStates)` type SpecState uint const ( SpecStateInvalid SpecState = 0 SpecStatePending SpecState = 1 << iota SpecStateSkipped SpecStatePassed SpecStateFailed SpecStateAborted SpecStatePanicked SpecStateInterrupted SpecStateTimedout ) var ssEnumSupport = NewEnumSupport(map[uint]string{ uint(SpecStateInvalid): "INVALID SPEC STATE", uint(SpecStatePending): "pending", uint(SpecStateSkipped): "skipped", uint(SpecStatePassed): "passed", uint(SpecStateFailed): "failed", uint(SpecStateAborted): "aborted", uint(SpecStatePanicked): "panicked", uint(SpecStateInterrupted): "interrupted", uint(SpecStateTimedout): "timedout", }) func (ss SpecState) String() string { return ssEnumSupport.String(uint(ss)) } func (ss SpecState) GomegaString() string { return ssEnumSupport.String(uint(ss)) } func (ss *SpecState) UnmarshalJSON(b []byte) error { out, err := ssEnumSupport.UnmarshJSON(b) *ss = SpecState(out) return err } func (ss SpecState) MarshalJSON() ([]byte, error) { return ssEnumSupport.MarshJSON(uint(ss)) } var SpecStateFailureStates = SpecStateFailed | SpecStateTimedout | SpecStateAborted | SpecStatePanicked | SpecStateInterrupted 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 { Message string `json:",omitempty"` ParallelProcess int `json:",omitempty"` RunningInParallel bool `json:",omitempty"` ContainerHierarchyTexts []string `json:",omitempty"` LeafNodeText string `json:",omitempty"` LeafNodeLocation CodeLocation `json:",omitempty"` SpecStartTime time.Time `json:",omitempty"` CurrentNodeType NodeType `json:",omitempty"` CurrentNodeText string `json:",omitempty"` CurrentNodeLocation CodeLocation `json:",omitempty"` CurrentNodeStartTime time.Time `json:",omitempty"` CurrentStepText string `json:",omitempty"` CurrentStepLocation CodeLocation `json:",omitempty"` CurrentStepStartTime time.Time `json:",omitempty"` AdditionalReports []string `json:",omitempty"` CapturedGinkgoWriterOutput string `json:",omitempty"` TimelineLocation TimelineLocation `json:",omitempty"` Goroutines []Goroutine `json:",omitempty"` } func (pr ProgressReport) IsZero() bool { return pr.CurrentNodeType == NodeTypeInvalid } func (pr ProgressReport) Time() time.Time { return pr.TimelineLocation.Time } 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 } func (pr ProgressReport) WithoutOtherGoroutines() ProgressReport { out := pr filteredGoroutines := []Goroutine{} for _, goroutine := range pr.Goroutines { if goroutine.IsSpecGoroutine || goroutine.HasHighlights() { filteredGoroutines = append(filteredGoroutines, goroutine) } } out.Goroutines = filteredGoroutines return out } func (pr ProgressReport) GetTimelineLocation() TimelineLocation { return pr.TimelineLocation } 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 const ( NodeTypeInvalid NodeType = 0 NodeTypeContainer NodeType = 1 << iota NodeTypeIt NodeTypeBeforeEach NodeTypeJustBeforeEach NodeTypeAfterEach NodeTypeJustAfterEach NodeTypeBeforeAll NodeTypeAfterAll NodeTypeBeforeSuite NodeTypeSynchronizedBeforeSuite NodeTypeAfterSuite NodeTypeSynchronizedAfterSuite NodeTypeReportBeforeEach NodeTypeReportAfterEach NodeTypeReportBeforeSuite NodeTypeReportAfterSuite NodeTypeCleanupInvalid NodeTypeCleanupAfterEach NodeTypeCleanupAfterAll NodeTypeCleanupAfterSuite ) var NodeTypesForContainerAndIt = NodeTypeContainer | NodeTypeIt var NodeTypesForSuiteLevelNodes = NodeTypeBeforeSuite | NodeTypeSynchronizedBeforeSuite | NodeTypeAfterSuite | NodeTypeSynchronizedAfterSuite | NodeTypeReportBeforeSuite | NodeTypeReportAfterSuite | NodeTypeCleanupAfterSuite var NodeTypesAllowedDuringCleanupInterrupt = NodeTypeAfterEach | NodeTypeJustAfterEach | NodeTypeAfterAll | NodeTypeAfterSuite | NodeTypeSynchronizedAfterSuite | NodeTypeCleanupAfterEach | NodeTypeCleanupAfterAll | NodeTypeCleanupAfterSuite var NodeTypesAllowedDuringReportInterrupt = NodeTypeReportBeforeEach | NodeTypeReportAfterEach | NodeTypeReportBeforeSuite | NodeTypeReportAfterSuite var ntEnumSupport = NewEnumSupport(map[uint]string{ uint(NodeTypeInvalid): "INVALID NODE TYPE", uint(NodeTypeContainer): "Container", uint(NodeTypeIt): "It", uint(NodeTypeBeforeEach): "BeforeEach", uint(NodeTypeJustBeforeEach): "JustBeforeEach", uint(NodeTypeAfterEach): "AfterEach", uint(NodeTypeJustAfterEach): "JustAfterEach", uint(NodeTypeBeforeAll): "BeforeAll", uint(NodeTypeAfterAll): "AfterAll", uint(NodeTypeBeforeSuite): "BeforeSuite", uint(NodeTypeSynchronizedBeforeSuite): "SynchronizedBeforeSuite", uint(NodeTypeAfterSuite): "AfterSuite", uint(NodeTypeSynchronizedAfterSuite): "SynchronizedAfterSuite", uint(NodeTypeReportBeforeEach): "ReportBeforeEach", uint(NodeTypeReportAfterEach): "ReportAfterEach", uint(NodeTypeReportBeforeSuite): "ReportBeforeSuite", uint(NodeTypeReportAfterSuite): "ReportAfterSuite", uint(NodeTypeCleanupInvalid): "DeferCleanup", uint(NodeTypeCleanupAfterEach): "DeferCleanup (Each)", uint(NodeTypeCleanupAfterAll): "DeferCleanup (All)", uint(NodeTypeCleanupAfterSuite): "DeferCleanup (Suite)", }) func (nt NodeType) String() string { return ntEnumSupport.String(uint(nt)) } func (nt *NodeType) UnmarshalJSON(b []byte) error { out, err := ntEnumSupport.UnmarshJSON(b) *nt = NodeType(out) return err } func (nt NodeType) MarshalJSON() ([]byte, error) { return ntEnumSupport.MarshJSON(uint(nt)) } func (nt NodeType) Is(nodeTypes NodeType) bool { return nt&nodeTypes != 0 } /* SpecEvent captures a vareity of events that can occur when specs run. See SpecEventType for the list of available events. */ type SpecEvent struct { SpecEventType SpecEventType CodeLocation CodeLocation TimelineLocation TimelineLocation Message string `json:",omitempty"` Duration time.Duration `json:",omitempty"` NodeType NodeType `json:",omitempty"` Attempt int `json:",omitempty"` } func (se SpecEvent) GetTimelineLocation() TimelineLocation { return se.TimelineLocation } func (se SpecEvent) IsOnlyVisibleAtVeryVerbose() bool { return se.SpecEventType.Is(SpecEventByEnd | SpecEventNodeStart | SpecEventNodeEnd) } func (se SpecEvent) GomegaString() string { out := &strings.Builder{} out.WriteString("[" + se.SpecEventType.String() + " SpecEvent] ") if se.Message != "" { out.WriteString("Message=") out.WriteString(`"` + se.Message + `",`) } if se.Duration != 0 { out.WriteString("Duration=" + se.Duration.String() + ",") } if se.NodeType != NodeTypeInvalid { out.WriteString("NodeType=" + se.NodeType.String() + ",") } if se.Attempt != 0 { out.WriteString(fmt.Sprintf("Attempt=%d", se.Attempt) + ",") } out.WriteString("CL=" + se.CodeLocation.String() + ",") out.WriteString(fmt.Sprintf("TL.Offset=%d", se.TimelineLocation.Offset)) return out.String() } type SpecEvents []SpecEvent func (se SpecEvents) WithType(seType SpecEventType) SpecEvents { out := SpecEvents{} for _, event := range se { if event.SpecEventType.Is(seType) { out = append(out, event) } } return out } type SpecEventType uint const ( SpecEventInvalid SpecEventType = 0 SpecEventByStart SpecEventType = 1 << iota SpecEventByEnd SpecEventNodeStart SpecEventNodeEnd SpecEventSpecRepeat SpecEventSpecRetry ) var seEnumSupport = NewEnumSupport(map[uint]string{ uint(SpecEventInvalid): "INVALID SPEC EVENT", uint(SpecEventByStart): "By", uint(SpecEventByEnd): "By (End)", uint(SpecEventNodeStart): "Node", uint(SpecEventNodeEnd): "Node (End)", uint(SpecEventSpecRepeat): "Repeat", uint(SpecEventSpecRetry): "Retry", }) func (se SpecEventType) String() string { return seEnumSupport.String(uint(se)) } func (se *SpecEventType) UnmarshalJSON(b []byte) error { out, err := seEnumSupport.UnmarshJSON(b) *se = SpecEventType(out) return err } func (se SpecEventType) MarshalJSON() ([]byte, error) { return seEnumSupport.MarshJSON(uint(se)) } func (se SpecEventType) Is(specEventTypes SpecEventType) bool { return se&specEventTypes != 0 } golang-github-onsi-ginkgo-v2-2.22.0/types/types_suite_test.go000066400000000000000000000006521472321612100242040ustar00rootroot00000000000000package types_test import ( "testing" . "github.com/onsi/ginkgo/v2" "github.com/onsi/ginkgo/v2/internal/test_helpers" . "github.com/onsi/gomega" ) func TestTypes(t *testing.T) { RegisterFailHandler(Fail) RunSpecs(t, "Types Suite") } var anchors test_helpers.Anchors var _ = BeforeSuite(func() { var err error anchors, err = test_helpers.LoadAnchors(test_helpers.DOCS, "../") Ω(err).ShouldNot(HaveOccurred()) }) golang-github-onsi-ginkgo-v2-2.22.0/types/types_test.go000066400000000000000000000517271472321612100230040ustar00rootroot00000000000000package types_test import ( "encoding/json" "sort" "time" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" "github.com/onsi/ginkgo/v2/types" ) var _ = Describe("Types", func() { Describe("Report", func() { Describe("Add", func() { It("concatenates spec reports, combines success, and computes a new RunTime", func() { t := time.Now() reportA := types.Report{ SuitePath: "foo", SuiteSucceeded: true, StartTime: t.Add(-time.Minute), EndTime: t.Add(2 * time.Minute), SpecialSuiteFailureReasons: []string{"blame jim", "blame alice"}, SpecReports: types.SpecReports{ types.SpecReport{NumAttempts: 3}, types.SpecReport{NumAttempts: 4}, }, } reportB := types.Report{ SuitePath: "bar", SuiteSucceeded: false, StartTime: t.Add(-2 * time.Minute), EndTime: t.Add(time.Minute), SpecialSuiteFailureReasons: []string{"blame bob", "blame jim"}, SpecReports: types.SpecReports{ types.SpecReport{NumAttempts: 5}, types.SpecReport{NumAttempts: 6}, }, } composite := reportA.Add(reportB) Ω(composite).Should(Equal(types.Report{ SuitePath: "foo", SuiteSucceeded: false, StartTime: t.Add(-2 * time.Minute), EndTime: t.Add(2 * time.Minute), RunTime: 4 * time.Minute, SpecialSuiteFailureReasons: []string{"blame jim", "blame alice", "blame bob"}, SpecReports: types.SpecReports{ types.SpecReport{NumAttempts: 3}, types.SpecReport{NumAttempts: 4}, types.SpecReport{NumAttempts: 5}, types.SpecReport{NumAttempts: 6}, }, })) }) }) }) Describe("ProgressReport", func() { It("can return the correct subset of Goroutines when asked", func() { specGoroutine := types.Goroutine{ID: 7, IsSpecGoroutine: true, Stack: []types.FunctionCall{{Highlight: true}}} highlightedGoroutineA := types.Goroutine{ID: 8, Stack: []types.FunctionCall{{Highlight: true}}} highlightedGoroutineB := types.Goroutine{ID: 9, Stack: []types.FunctionCall{{Highlight: true}}} otherGoroutineA := types.Goroutine{ID: 10, Stack: []types.FunctionCall{{Highlight: false}}} otherGoroutineB := types.Goroutine{ID: 11, Stack: []types.FunctionCall{{Highlight: false}}} pr := types.ProgressReport{ Goroutines: []types.Goroutine{ otherGoroutineA, highlightedGoroutineA, specGoroutine, highlightedGoroutineB, otherGoroutineB, }, } Ω(pr.SpecGoroutine()).Should(Equal(specGoroutine)) Ω(pr.HighlightedGoroutines()).Should(Equal([]types.Goroutine{highlightedGoroutineA, highlightedGoroutineB})) Ω(pr.OtherGoroutines()).Should(Equal([]types.Goroutine{otherGoroutineA, otherGoroutineB})) }) It("can return a copy sans GinkgoWriter output", func() { pr := types.ProgressReport{ LeafNodeText: "hi", CapturedGinkgoWriterOutput: "foo", TimelineLocation: types.TimelineLocation{Offset: 10}, } Ω(pr.WithoutCapturedGinkgoWriterOutput()).Should(Equal(types.ProgressReport{LeafNodeText: "hi", TimelineLocation: types.TimelineLocation{Offset: 10}})) }) }) Describe("NodeType", func() { Describe("Is", func() { It("returns true when the NodeType is in the passed-in list", func() { Ω(types.NodeTypeContainer.Is(types.NodeTypeIt | types.NodeTypeContainer)).Should(BeTrue()) }) It("returns false when the NodeType is not in the passed-in list", func() { Ω(types.NodeTypeContainer.Is(types.NodeTypeIt | types.NodeTypeBeforeEach)).Should(BeFalse()) }) }) DescribeTable("Representation and Encoding", func(nodeType types.NodeType, expectedString string) { Ω(nodeType.String()).Should(Equal(expectedString)) marshalled, err := json.Marshal(nodeType) Ω(err).ShouldNot(HaveOccurred()) var unmarshalled types.NodeType json.Unmarshal(marshalled, &unmarshalled) Ω(unmarshalled).Should(Equal(nodeType)) }, func(nodeType types.NodeType, expectedString string) string { return expectedString }, Entry(nil, types.NodeTypeContainer, "Container"), Entry(nil, types.NodeTypeIt, "It"), Entry(nil, types.NodeTypeBeforeEach, "BeforeEach"), Entry(nil, types.NodeTypeJustBeforeEach, "JustBeforeEach"), Entry(nil, types.NodeTypeAfterEach, "AfterEach"), Entry(nil, types.NodeTypeJustAfterEach, "JustAfterEach"), Entry(nil, types.NodeTypeBeforeAll, "BeforeAll"), Entry(nil, types.NodeTypeAfterAll, "AfterAll"), Entry(nil, types.NodeTypeBeforeSuite, "BeforeSuite"), Entry(nil, types.NodeTypeSynchronizedBeforeSuite, "SynchronizedBeforeSuite"), Entry(nil, types.NodeTypeAfterSuite, "AfterSuite"), Entry(nil, types.NodeTypeSynchronizedAfterSuite, "SynchronizedAfterSuite"), Entry(nil, types.NodeTypeReportBeforeEach, "ReportBeforeEach"), Entry(nil, types.NodeTypeReportAfterEach, "ReportAfterEach"), Entry(nil, types.NodeTypeReportBeforeSuite, "ReportBeforeSuite"), Entry(nil, types.NodeTypeReportAfterSuite, "ReportAfterSuite"), Entry(nil, types.NodeTypeCleanupInvalid, "DeferCleanup"), Entry(nil, types.NodeTypeCleanupAfterEach, "DeferCleanup (Each)"), Entry(nil, types.NodeTypeCleanupAfterAll, "DeferCleanup (All)"), Entry(nil, types.NodeTypeCleanupAfterSuite, "DeferCleanup (Suite)"), Entry(nil, types.NodeTypeInvalid, "INVALID NODE TYPE"), ) }) Describe("FailureNodeContext", func() { DescribeTable("Representation and Encoding", func(context types.FailureNodeContext) { marshalled, err := json.Marshal(context) Ω(err).ShouldNot(HaveOccurred()) var unmarshalled types.FailureNodeContext json.Unmarshal(marshalled, &unmarshalled) Ω(unmarshalled).Should(Equal(context)) }, Entry("LeafNode", types.FailureNodeIsLeafNode), Entry("TopLevel", types.FailureNodeAtTopLevel), Entry("InContainer", types.FailureNodeInContainer), Entry("Invalid", types.FailureNodeContextInvalid), ) }) Describe("SpecState", func() { DescribeTable("Representation and Encoding", func(specState types.SpecState, expectedString string) { Ω(specState.String()).Should(Equal(expectedString)) marshalled, err := json.Marshal(specState) Ω(err).ShouldNot(HaveOccurred()) var unmarshalled types.SpecState json.Unmarshal(marshalled, &unmarshalled) Ω(unmarshalled).Should(Equal(specState)) }, Entry("Pending", types.SpecStatePending, "pending"), Entry("Skipped", types.SpecStateSkipped, "skipped"), Entry("Passed", types.SpecStatePassed, "passed"), Entry("Failed", types.SpecStateFailed, "failed"), Entry("Panicked", types.SpecStatePanicked, "panicked"), Entry("Aborted", types.SpecStateAborted, "aborted"), Entry("Interrupted", types.SpecStateInterrupted, "interrupted"), Entry("Invalid", types.SpecStateInvalid, "INVALID SPEC STATE"), ) }) Describe("SpecReport Helper Functions", func() { Describe("CombinedOutput", func() { Context("with no GinkgoWriter or StdOutErr output", func() { It("comes back empty", func() { Ω(types.SpecReport{}.CombinedOutput()).Should(Equal("")) }) }) Context("with only StdOutErr output", func() { It("returns that output", func() { Ω(types.SpecReport{ CapturedStdOutErr: "hello", }.CombinedOutput()).Should(Equal("hello")) }) }) Context("with only GinkgoWriter output", func() { It("returns that output", func() { Ω(types.SpecReport{ CapturedGinkgoWriterOutput: "hello", }.CombinedOutput()).Should(Equal("hello")) }) }) Context("with both", func() { It("returns both concatenated", func() { Ω(types.SpecReport{ CapturedGinkgoWriterOutput: "gw", CapturedStdOutErr: "std", }.CombinedOutput()).Should(Equal("std\ngw")) }) }) }) Describe("Labels", Label("TestA", "TestB"), func() { It("returns a concatenated, deduped, set of labels", Label("TestB", "TestC"), func() { Ω(CurrentSpecReport().Labels()).Should(Equal([]string{"TestA", "TestB", "TestC"})) }) }) Describe("MatchesLabelFilter", Label("dog", "cat"), func() { It("returns an error when passed an invalid filter query", func() { matches, err := CurrentSpecReport().MatchesLabelFilter("(welp") Ω(err).Should(HaveOccurred()) Ω(matches).Should(BeFalse()) }) It("returns whether or not the query matches", Label("catfish"), func() { Ω(CurrentSpecReport().MatchesLabelFilter("dog")).Should(BeTrue()) Ω(CurrentSpecReport().MatchesLabelFilter("cow || cat")).Should(BeTrue()) Ω(CurrentSpecReport().MatchesLabelFilter("/fish/")).Should(BeTrue()) Ω(CurrentSpecReport().MatchesLabelFilter("dog && !/fish/")).Should(BeFalse()) }) }) It("can report on whether state is a failed state", func() { Ω(types.SpecReport{State: types.SpecStatePending}.Failed()).Should(BeFalse()) Ω(types.SpecReport{State: types.SpecStateSkipped}.Failed()).Should(BeFalse()) Ω(types.SpecReport{State: types.SpecStatePassed}.Failed()).Should(BeFalse()) Ω(types.SpecReport{State: types.SpecStateFailed}.Failed()).Should(BeTrue()) Ω(types.SpecReport{State: types.SpecStatePanicked}.Failed()).Should(BeTrue()) Ω(types.SpecReport{State: types.SpecStateAborted}.Failed()).Should(BeTrue()) Ω(types.SpecReport{State: types.SpecStateInterrupted}.Failed()).Should(BeTrue()) }) It("can return a concatenated set of texts", func() { Ω(CurrentSpecReport().FullText()).Should(Equal("Types SpecReport Helper Functions can return a concatenated set of texts")) }) It("can return the name of the file it's spec is in", func() { cl := types.NewCodeLocation(0) Ω(CurrentSpecReport().FileName()).Should(Equal(cl.FileName)) }) It("can return the linenumber of the file it's spec is in", func() { cl := types.NewCodeLocation(0) Ω(CurrentSpecReport().LineNumber()).Should(Equal(cl.LineNumber - 1)) }) It("can return it's failure's message", func() { report := types.SpecReport{ Failure: types.Failure{Message: "why this failed"}, } Ω(report.FailureMessage()).Should(Equal("why this failed")) }) It("can return it's failure's code location", func() { cl := types.NewCodeLocation(0) report := types.SpecReport{ Failure: types.Failure{Location: cl}, } Ω(report.FailureLocation()).Should(Equal(cl)) }) }) Describe("SpecReports", func() { Describe("Encoding to JSON", func() { var report types.SpecReport BeforeEach(func() { report = types.SpecReport{ ContainerHierarchyTexts: []string{"A", "B"}, ContainerHierarchyLocations: []types.CodeLocation{ types.NewCodeLocation(0), types.NewCodeLocationWithStackTrace(0), types.NewCustomCodeLocation("welp"), }, LeafNodeType: types.NodeTypeIt, LeafNodeLocation: types.NewCodeLocation(0), LeafNodeText: "C", State: types.SpecStateFailed, StartTime: time.Date(2012, 06, 19, 05, 32, 12, 0, time.UTC), EndTime: time.Date(2012, 06, 19, 05, 33, 12, 0, time.UTC), RunTime: time.Minute, ParallelProcess: 2, NumAttempts: 3, CapturedGinkgoWriterOutput: "gw", CapturedStdOutErr: "std", Failure: types.Failure{ Message: "boom", Location: types.NewCodeLocation(1), ForwardedPanic: "bam", FailureNodeContext: types.FailureNodeInContainer, FailureNodeType: types.NodeTypeBeforeEach, FailureNodeLocation: types.NewCodeLocation(0), FailureNodeContainerIndex: 1, }, } }) Context("with a failure", func() { It("round-trips correctly", func() { marshalled, err := json.Marshal(report) Ω(err).ShouldNot(HaveOccurred()) unmarshalled := types.SpecReport{} err = json.Unmarshal(marshalled, &unmarshalled) Ω(err).ShouldNot(HaveOccurred()) Ω(unmarshalled).Should(Equal(report)) }) }) Context("without a failure", func() { BeforeEach(func() { report.Failure = types.Failure{} }) It("round-trips correctly and doesn't include the Failure struct", func() { marshalled, err := json.Marshal(report) Ω(string(marshalled)).ShouldNot(ContainSubstring("Failure")) Ω(err).ShouldNot(HaveOccurred()) unmarshalled := types.SpecReport{} err = json.Unmarshal(marshalled, &unmarshalled) Ω(err).ShouldNot(HaveOccurred()) Ω(unmarshalled).Should(Equal(report)) }) }) }) Describe("WithLeafNodeType", func() { It("returns reports with the matching LeafNodeTypes", func() { reports := types.SpecReports{ {LeafNodeType: types.NodeTypeIt, NumAttempts: 2}, {LeafNodeType: types.NodeTypeIt, NumAttempts: 3}, {LeafNodeType: types.NodeTypeBeforeSuite, NumAttempts: 4}, {LeafNodeType: types.NodeTypeAfterSuite, NumAttempts: 5}, {LeafNodeType: types.NodeTypeSynchronizedAfterSuite, NumAttempts: 6}, } Ω(reports.WithLeafNodeType(types.NodeTypeIt | types.NodeTypeAfterSuite)).Should(Equal(types.SpecReports{ {LeafNodeType: types.NodeTypeIt, NumAttempts: 2}, {LeafNodeType: types.NodeTypeIt, NumAttempts: 3}, {LeafNodeType: types.NodeTypeAfterSuite, NumAttempts: 5}, })) }) }) Describe("WithState", func() { It("returns reports with the matching SpecStates", func() { reports := types.SpecReports{ {State: types.SpecStatePassed, NumAttempts: 2}, {State: types.SpecStatePassed, NumAttempts: 3}, {State: types.SpecStateFailed, NumAttempts: 4}, {State: types.SpecStatePending, NumAttempts: 5}, {State: types.SpecStateSkipped, NumAttempts: 6}, } Ω(reports.WithState(types.SpecStatePassed | types.SpecStatePending)).Should(Equal(types.SpecReports{ {State: types.SpecStatePassed, NumAttempts: 2}, {State: types.SpecStatePassed, NumAttempts: 3}, {State: types.SpecStatePending, NumAttempts: 5}, })) }) }) Describe("CountWithState", func() { It("returns the number with the matching SpecStates", func() { reports := types.SpecReports{ {State: types.SpecStatePassed, NumAttempts: 2}, {State: types.SpecStatePassed, NumAttempts: 3}, {State: types.SpecStateFailed, NumAttempts: 4}, {State: types.SpecStatePending, NumAttempts: 5}, {State: types.SpecStateSkipped, NumAttempts: 6}, } Ω(reports.CountWithState(types.SpecStatePassed | types.SpecStatePending)).Should(Equal(3)) }) }) Describe("CountOfFlakedSpecs", func() { It("returns the number of passing specs with NumAttempts > 1", func() { reports := types.SpecReports{ {State: types.SpecStatePassed, NumAttempts: 2, MaxFlakeAttempts: 2}, {State: types.SpecStatePassed, NumAttempts: 2, MaxFlakeAttempts: 2}, {State: types.SpecStatePassed, NumAttempts: 1, MaxFlakeAttempts: 2}, {State: types.SpecStatePassed, NumAttempts: 1, MaxFlakeAttempts: 2}, {State: types.SpecStateFailed, NumAttempts: 2, MaxFlakeAttempts: 2}, } Ω(reports.CountOfFlakedSpecs()).Should(Equal(2)) }) }) Describe("CountOfRepeatedSpecs", func() { It("returns the number of failed specs with NumAttempts > 1", func() { reports := types.SpecReports{ {State: types.SpecStatePassed, NumAttempts: 2, MaxMustPassRepeatedly: 2}, {State: types.SpecStatePassed, NumAttempts: 2, MaxMustPassRepeatedly: 2}, {State: types.SpecStatePassed, NumAttempts: 1, MaxMustPassRepeatedly: 2}, {State: types.SpecStatePassed, NumAttempts: 1, MaxMustPassRepeatedly: 2}, {State: types.SpecStateFailed, NumAttempts: 2, MaxMustPassRepeatedly: 2}, } Ω(reports.CountOfRepeatedSpecs()).Should(Equal(1)) }) }) }) Describe("Timelines", func() { var report types.SpecReport var primaryFailure types.Failure var timeoutFailure, panicFailure, embeddedFailure types.AdditionalFailure var reportEntryAlways, reportEntryNever types.ReportEntry var progressReport1, progressReport2 types.ProgressReport var byStartSpecEvent, nodeStartSpecEvent types.SpecEvent BeforeEach(func() { embeddedFailure = types.AdditionalFailure{ Failure: types.Failure{ Message: "embedded-failure", TimelineLocation: types.TimelineLocation{Order: 100}, }, State: types.SpecStateFailed, } primaryFailure = types.Failure{ Message: "primary-failure", TimelineLocation: types.TimelineLocation{Order: 80}, AdditionalFailure: &embeddedFailure, } timeoutFailure = types.AdditionalFailure{ Failure: types.Failure{ Message: "timeout-failure", TimelineLocation: types.TimelineLocation{Order: 120}, AdditionalFailure: &types.AdditionalFailure{ Failure: types.Failure{ Message: "additional-embeded-failure", TimelineLocation: types.TimelineLocation{Order: 125}, }, State: types.SpecStateFailed, }, }, State: types.SpecStateTimedout, } panicFailure = types.AdditionalFailure{ Failure: types.Failure{Message: "panic-failure", TimelineLocation: types.TimelineLocation{Order: 140}, }, State: types.SpecStatePanicked, } reportEntryAlways = types.ReportEntry{ Name: "report-entry-always", Visibility: types.ReportEntryVisibilityAlways, TimelineLocation: types.TimelineLocation{Order: 130}, } reportEntryNever = types.ReportEntry{ Name: "report-entry-never", Visibility: types.ReportEntryVisibilityNever, TimelineLocation: types.TimelineLocation{Order: 50}, } progressReport1 = types.ProgressReport{ Message: "progress-report-1", TimelineLocation: types.TimelineLocation{Order: 60}, } progressReport2 = types.ProgressReport{ Message: "progress-report-2", TimelineLocation: types.TimelineLocation{Order: 90}, } byStartSpecEvent = types.SpecEvent{ SpecEventType: types.SpecEventByStart, Message: "by-start-spec-event", TimelineLocation: types.TimelineLocation{Order: 95}, } nodeStartSpecEvent = types.SpecEvent{ SpecEventType: types.SpecEventNodeStart, Message: "node-start-spec-event", NodeType: types.NodeTypeBeforeEach, TimelineLocation: types.TimelineLocation{Order: 30}, } report = types.SpecReport{ Failure: primaryFailure, AdditionalFailures: []types.AdditionalFailure{timeoutFailure, panicFailure}, ReportEntries: []types.ReportEntry{reportEntryAlways, reportEntryNever}, ProgressReports: []types.ProgressReport{progressReport1, progressReport2}, SpecEvents: []types.SpecEvent{byStartSpecEvent, nodeStartSpecEvent}, } }) It("can return a timeline comprised of the various TimelineEvents in the spec, that can be sorted by timelinelocation", func() { timeline := report.Timeline() sort.Sort(timeline) Ω(timeline).Should(Equal(types.Timeline{ nodeStartSpecEvent, reportEntryNever, progressReport1, primaryFailure, progressReport2, byStartSpecEvent, embeddedFailure, timeoutFailure, reportEntryAlways, panicFailure, })) }) It("can filter the timeline to remove entries that will not be displayed", func() { timeline := report.Timeline() sort.Sort(timeline) timeline = timeline.WithoutHiddenReportEntries() Ω(timeline).Should(Equal(types.Timeline{ nodeStartSpecEvent, progressReport1, primaryFailure, progressReport2, byStartSpecEvent, embeddedFailure, timeoutFailure, reportEntryAlways, panicFailure, })) timeline = timeline.WithoutVeryVerboseSpecEvents() Ω(timeline).Should(Equal(types.Timeline{ progressReport1, primaryFailure, progressReport2, byStartSpecEvent, embeddedFailure, timeoutFailure, reportEntryAlways, panicFailure, })) }) }) Describe("SpecEvent", func() { DescribeTable("IsOnlyVisibleAtVeryVerbose", func(specEventType types.SpecEventType, isOnlyVisibleAtVeryVerbose bool) { Ω(types.SpecEvent{SpecEventType: specEventType}.IsOnlyVisibleAtVeryVerbose()).Should(Equal(isOnlyVisibleAtVeryVerbose)) }, Entry(nil, types.SpecEventByStart, false), Entry(nil, types.SpecEventByEnd, true), Entry(nil, types.SpecEventNodeStart, true), Entry(nil, types.SpecEventNodeEnd, true), Entry(nil, types.SpecEventSpecRepeat, false), Entry(nil, types.SpecEventSpecRetry, false), ) DescribeTable("SpecEventType: Representation and Encoding", func(specEventType types.SpecEventType, expectedString string) { Ω(specEventType.String()).Should(Equal(expectedString)) marshalled, err := json.Marshal(specEventType) Ω(err).ShouldNot(HaveOccurred()) var unmarshalled types.SpecEventType json.Unmarshal(marshalled, &unmarshalled) Ω(unmarshalled).Should(Equal(specEventType)) }, Entry(nil, types.SpecEventInvalid, "INVALID SPEC EVENT"), Entry(nil, types.SpecEventByStart, "By"), Entry(nil, types.SpecEventByEnd, "By (End)"), Entry(nil, types.SpecEventNodeStart, "Node"), Entry(nil, types.SpecEventNodeEnd, "Node (End)"), Entry(nil, types.SpecEventSpecRepeat, "Repeat"), Entry(nil, types.SpecEventSpecRetry, "Retry"), ) }) }) golang-github-onsi-ginkgo-v2-2.22.0/types/version.go000066400000000000000000000000501472321612100222450ustar00rootroot00000000000000package types const VERSION = "2.22.0"