Add e2e tests

This commit is contained in:
Manuel de Brito Fontes 2017-10-17 19:50:27 -03:00
parent 99a355f25d
commit 601fb7dacf
1163 changed files with 289217 additions and 14195 deletions

5
vendor/github.com/onsi/ginkgo/.gitignore generated vendored Normal file
View file

@ -0,0 +1,5 @@
.DS_Store
TODO
tmp/**/*
*.coverprofile
.vscode

15
vendor/github.com/onsi/ginkgo/.travis.yml generated vendored Normal file
View file

@ -0,0 +1,15 @@
language: go
go:
- 1.5.x
- 1.6.x
- 1.7.x
- 1.8.x
install:
- go get -v -t ./...
- go get golang.org/x/tools/cmd/cover
- go get github.com/onsi/gomega
- go install github.com/onsi/ginkgo/ginkgo
- export PATH=$PATH:$HOME/gopath/bin
script: $HOME/gopath/bin/ginkgo -r --randomizeAllSpecs --randomizeSuites --race --trace

152
vendor/github.com/onsi/ginkgo/CHANGELOG.md generated vendored Normal file
View file

@ -0,0 +1,152 @@
## 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 <path-to-package>` 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 interupted 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 occured 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!

12
vendor/github.com/onsi/ginkgo/CONTRIBUTING.md generated vendored Normal file
View file

@ -0,0 +1,12 @@
# Contributing to Ginkgo
Your contributions to Ginkgo are essential for its long-term maintenance and improvement. To make a contribution:
- 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:
- If you're adding functionality to the Ginkgo library, make sure to add appropriate unit and/or integration tests (under the `integration` folder).
- If you're adding functionality to the Ginkgo CLI note that there are very few unit tests. Please add an integration test.
- Please run all tests locally (`ginkgo -r -p`) and make sure they go green before submitting the PR
- Update the documentation. In addition to standard `godoc` comments Ginkgo has extensive documentation on the `gh-pages` branch. If relevant, please submit a docs PR to that branch alongside your code PR.
Thanks for supporting Ginkgo!

20
vendor/github.com/onsi/ginkgo/LICENSE generated vendored Normal file
View file

@ -0,0 +1,20 @@
Copyright (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.

117
vendor/github.com/onsi/ginkgo/README.md generated vendored Normal file
View file

@ -0,0 +1,117 @@
![Ginkgo: A Golang BDD Testing Framework](http://onsi.github.io/ginkgo/images/ginkgo.png)
[![Build Status](https://travis-ci.org/onsi/ginkgo.svg)](https://travis-ci.org/onsi/ginkgo)
Jump to the [docs](http://onsi.github.io/ginkgo/) to learn more. To start rolling your Ginkgo tests *now* [keep reading](#set-me-up)!
If you have a question, comment, bug report, feature request, etc. please open a GitHub issue.
## Feature List
- Ginkgo uses Go's `testing` package and can live alongside your existing `testing` tests. It's easy to [bootstrap](http://onsi.github.io/ginkgo/#bootstrapping-a-suite) and start writing your [first tests](http://onsi.github.io/ginkgo/#adding-specs-to-a-suite)
- Structure your BDD-style tests expressively:
- Nestable [`Describe` and `Context` container blocks](http://onsi.github.io/ginkgo/#organizing-specs-with-containers-describe-and-context)
- [`BeforeEach` and `AfterEach` blocks](http://onsi.github.io/ginkgo/#extracting-common-setup-beforeeach) for setup and teardown
- [`It` blocks](http://onsi.github.io/ginkgo/#individual-specs-) that hold your assertions
- [`JustBeforeEach` blocks](http://onsi.github.io/ginkgo/#separating-creation-and-configuration-justbeforeeach) that separate creation from configuration (also known as the subject action pattern).
- [`BeforeSuite` and `AfterSuite` blocks](http://onsi.github.io/ginkgo/#global-setup-and-teardown-beforesuite-and-aftersuite) to prep for and cleanup after a suite.
- A comprehensive test runner that lets you:
- Mark specs as [pending](http://onsi.github.io/ginkgo/#pending-specs)
- [Focus](http://onsi.github.io/ginkgo/#focused-specs) individual specs, and groups of specs, either programmatically or on the command line
- Run your tests in [random order](http://onsi.github.io/ginkgo/#spec-permutation), and then reuse random seeds to replicate the same order.
- Break up your test suite into parallel processes for straightforward [test parallelization](http://onsi.github.io/ginkgo/#parallel-specs)
- `ginkgo`: a command line interface with plenty of handy command line arguments for [running your tests](http://onsi.github.io/ginkgo/#running-tests) and [generating](http://onsi.github.io/ginkgo/#generators) test files. Here are a few choice examples:
- `ginkgo -nodes=N` runs your tests in `N` parallel processes and print out coherent output in realtime
- `ginkgo -cover` runs your tests using Golang's code coverage tool
- `ginkgo convert` converts an XUnit-style `testing` package to a Ginkgo-style package
- `ginkgo -focus="REGEXP"` and `ginkgo -skip="REGEXP"` allow you to specify a subset of tests to run via regular expression
- `ginkgo -r` runs all tests suites under the current directory
- `ginkgo -v` prints out identifying information for each tests just before it runs
And much more: run `ginkgo help` for details!
The `ginkgo` CLI is convenient, but purely optional -- Ginkgo works just fine with `go test`
- `ginkgo watch` [watches](https://onsi.github.io/ginkgo/#watching-for-changes) packages *and their dependencies* for changes, then reruns tests. Run tests immediately as you develop!
- Built-in support for testing [asynchronicity](http://onsi.github.io/ginkgo/#asynchronous-tests)
- Built-in support for [benchmarking](http://onsi.github.io/ginkgo/#benchmark-tests) your code. Control the number of benchmark samples as you gather runtimes and other, arbitrary, bits of numerical information about your code.
- [Completions for Sublime Text](https://github.com/onsi/ginkgo-sublime-completions): just use [Package Control](https://sublime.wbond.net/) to install `Ginkgo Completions`.
- [Completions for VSCode](https://github.com/onsi/vscode-ginkgo): just use VSCode's extension installer to install `vscode-ginkgo`.
- Straightforward support for third-party testing libraries such as [Gomock](https://code.google.com/p/gomock/) and [Testify](https://github.com/stretchr/testify). Check out the [docs](http://onsi.github.io/ginkgo/#third-party-integrations) for details.
- A modular architecture that lets you easily:
- Write [custom reporters](http://onsi.github.io/ginkgo/#writing-custom-reporters) (for example, Ginkgo comes with a [JUnit XML reporter](http://onsi.github.io/ginkgo/#generating-junit-xml-output) and a TeamCity reporter).
- [Adapt an existing matcher library (or write your own!)](http://onsi.github.io/ginkgo/#using-other-matcher-libraries) to work with Ginkgo
## [Gomega](http://github.com/onsi/gomega): Ginkgo's Preferred Matcher Library
Ginkgo is best paired with Gomega. Learn more about Gomega [here](http://onsi.github.io/gomega/)
## [Agouti](http://github.com/sclevine/agouti): A Golang Acceptance Testing Framework
Agouti allows you run WebDriver integration tests. Learn more about Agouti [here](http://agouti.org)
## Set Me Up!
You'll need Golang v1.3+ (Ubuntu users: you probably have Golang v1.0 -- you'll need to upgrade!)
```bash
go get github.com/onsi/ginkgo/ginkgo # installs the ginkgo CLI
go get github.com/onsi/gomega # fetches the matcher library
cd path/to/package/you/want/to/test
ginkgo bootstrap # set up a new ginkgo suite
ginkgo generate # will create a sample test file. edit this file and add your tests then...
go test # to run your tests
ginkgo # also runs your tests
```
## I'm new to Go: What are my testing options?
Of course, I heartily recommend [Ginkgo](https://github.com/onsi/ginkgo) and [Gomega](https://github.com/onsi/gomega). Both packages are seeing heavy, daily, production use on a number of projects and boast a mature and comprehensive feature-set.
With that said, it's great to know what your options are :)
### What Golang gives you out of the box
Testing is a first class citizen in Golang, however Go's built-in testing primitives are somewhat limited: The [testing](http://golang.org/pkg/testing) package provides basic XUnit style tests and no assertion library.
### Matcher libraries for Golang's XUnit style tests
A number of matcher libraries have been written to augment Go's built-in XUnit style tests. Here are two that have gained traction:
- [testify](https://github.com/stretchr/testify)
- [gocheck](http://labix.org/gocheck)
You can also use Ginkgo's matcher library [Gomega](https://github.com/onsi/gomega) in [XUnit style tests](http://onsi.github.io/gomega/#using-gomega-with-golangs-xunitstyle-tests)
### BDD style testing frameworks
There are a handful of BDD-style testing frameworks written for Golang. Here are a few:
- [Ginkgo](https://github.com/onsi/ginkgo) ;)
- [GoConvey](https://github.com/smartystreets/goconvey)
- [Goblin](https://github.com/franela/goblin)
- [Mao](https://github.com/azer/mao)
- [Zen](https://github.com/pranavraja/zen)
Finally, @shageman has [put together](https://github.com/shageman/gotestit) a comprehensive comparison of golang testing libraries.
Go explore!
## License
Ginkgo is MIT-Licensed

187
vendor/github.com/onsi/ginkgo/config/config.go generated vendored Normal file
View file

@ -0,0 +1,187 @@
/*
Ginkgo accepts a number of configuration options.
These are documented [here](http://onsi.github.io/ginkgo/#the_ginkgo_cli)
You can also learn more via
ginkgo help
or (I kid you not):
go test -asdf
*/
package config
import (
"flag"
"time"
"fmt"
)
const VERSION = "1.4.0"
type GinkgoConfigType struct {
RandomSeed int64
RandomizeAllSpecs bool
RegexScansFilePath bool
FocusString string
SkipString string
SkipMeasurements bool
FailOnPending bool
FailFast bool
FlakeAttempts int
EmitSpecProgress bool
DryRun bool
ParallelNode int
ParallelTotal int
SyncHost string
StreamHost string
}
var GinkgoConfig = GinkgoConfigType{}
type DefaultReporterConfigType struct {
NoColor bool
SlowSpecThreshold float64
NoisyPendings bool
Succinct bool
Verbose bool
FullTrace bool
}
var DefaultReporterConfig = DefaultReporterConfigType{}
func processPrefix(prefix string) string {
if prefix != "" {
prefix = prefix + "."
}
return prefix
}
func Flags(flagSet *flag.FlagSet, prefix string, includeParallelFlags bool) {
prefix = processPrefix(prefix)
flagSet.Int64Var(&(GinkgoConfig.RandomSeed), prefix+"seed", time.Now().Unix(), "The seed used to randomize the spec suite.")
flagSet.BoolVar(&(GinkgoConfig.RandomizeAllSpecs), prefix+"randomizeAllSpecs", false, "If set, ginkgo will randomize all specs together. By default, ginkgo only randomizes the top level Describe/Context groups.")
flagSet.BoolVar(&(GinkgoConfig.SkipMeasurements), prefix+"skipMeasurements", false, "If set, ginkgo will skip any measurement specs.")
flagSet.BoolVar(&(GinkgoConfig.FailOnPending), prefix+"failOnPending", false, "If set, ginkgo will mark the test suite as failed if any specs are pending.")
flagSet.BoolVar(&(GinkgoConfig.FailFast), prefix+"failFast", false, "If set, ginkgo will stop running a test suite after a failure occurs.")
flagSet.BoolVar(&(GinkgoConfig.DryRun), prefix+"dryRun", false, "If set, ginkgo will walk the test hierarchy without actually running anything. Best paired with -v.")
flagSet.StringVar(&(GinkgoConfig.FocusString), prefix+"focus", "", "If set, ginkgo will only run specs that match this regular expression.")
flagSet.StringVar(&(GinkgoConfig.SkipString), prefix+"skip", "", "If set, ginkgo will only run specs that do not match this regular expression.")
flagSet.BoolVar(&(GinkgoConfig.RegexScansFilePath), prefix+"regexScansFilePath", false, "If set, ginkgo regex matching also will look at the file path (code location).")
flagSet.IntVar(&(GinkgoConfig.FlakeAttempts), prefix+"flakeAttempts", 1, "Make up to this many attempts to run each spec. Please note that if any of the attempts succeed, the suite will not be failed. But any failures will still be recorded.")
flagSet.BoolVar(&(GinkgoConfig.EmitSpecProgress), prefix+"progress", false, "If set, ginkgo will emit progress information as each spec runs to the GinkgoWriter.")
if includeParallelFlags {
flagSet.IntVar(&(GinkgoConfig.ParallelNode), prefix+"parallel.node", 1, "This worker node's (one-indexed) node number. For running specs in parallel.")
flagSet.IntVar(&(GinkgoConfig.ParallelTotal), prefix+"parallel.total", 1, "The total number of worker nodes. For running specs in parallel.")
flagSet.StringVar(&(GinkgoConfig.SyncHost), prefix+"parallel.synchost", "", "The address for the server that will synchronize the running nodes.")
flagSet.StringVar(&(GinkgoConfig.StreamHost), prefix+"parallel.streamhost", "", "The address for the server that the running nodes should stream data to.")
}
flagSet.BoolVar(&(DefaultReporterConfig.NoColor), prefix+"noColor", false, "If set, suppress color output in default reporter.")
flagSet.Float64Var(&(DefaultReporterConfig.SlowSpecThreshold), prefix+"slowSpecThreshold", 5.0, "(in seconds) Specs that take longer to run than this threshold are flagged as slow by the default reporter.")
flagSet.BoolVar(&(DefaultReporterConfig.NoisyPendings), prefix+"noisyPendings", true, "If set, default reporter will shout about pending tests.")
flagSet.BoolVar(&(DefaultReporterConfig.Verbose), prefix+"v", false, "If set, default reporter print out all specs as they begin.")
flagSet.BoolVar(&(DefaultReporterConfig.Succinct), prefix+"succinct", false, "If set, default reporter prints out a very succinct report")
flagSet.BoolVar(&(DefaultReporterConfig.FullTrace), prefix+"trace", false, "If set, default reporter prints out the full stack trace when a failure occurs")
}
func BuildFlagArgs(prefix string, ginkgo GinkgoConfigType, reporter DefaultReporterConfigType) []string {
prefix = processPrefix(prefix)
result := make([]string, 0)
if ginkgo.RandomSeed > 0 {
result = append(result, fmt.Sprintf("--%sseed=%d", prefix, ginkgo.RandomSeed))
}
if ginkgo.RandomizeAllSpecs {
result = append(result, fmt.Sprintf("--%srandomizeAllSpecs", prefix))
}
if ginkgo.SkipMeasurements {
result = append(result, fmt.Sprintf("--%sskipMeasurements", prefix))
}
if ginkgo.FailOnPending {
result = append(result, fmt.Sprintf("--%sfailOnPending", prefix))
}
if ginkgo.FailFast {
result = append(result, fmt.Sprintf("--%sfailFast", prefix))
}
if ginkgo.DryRun {
result = append(result, fmt.Sprintf("--%sdryRun", prefix))
}
if ginkgo.FocusString != "" {
result = append(result, fmt.Sprintf("--%sfocus=%s", prefix, ginkgo.FocusString))
}
if ginkgo.SkipString != "" {
result = append(result, fmt.Sprintf("--%sskip=%s", prefix, ginkgo.SkipString))
}
if ginkgo.FlakeAttempts > 1 {
result = append(result, fmt.Sprintf("--%sflakeAttempts=%d", prefix, ginkgo.FlakeAttempts))
}
if ginkgo.EmitSpecProgress {
result = append(result, fmt.Sprintf("--%sprogress", prefix))
}
if ginkgo.ParallelNode != 0 {
result = append(result, fmt.Sprintf("--%sparallel.node=%d", prefix, ginkgo.ParallelNode))
}
if ginkgo.ParallelTotal != 0 {
result = append(result, fmt.Sprintf("--%sparallel.total=%d", prefix, ginkgo.ParallelTotal))
}
if ginkgo.StreamHost != "" {
result = append(result, fmt.Sprintf("--%sparallel.streamhost=%s", prefix, ginkgo.StreamHost))
}
if ginkgo.SyncHost != "" {
result = append(result, fmt.Sprintf("--%sparallel.synchost=%s", prefix, ginkgo.SyncHost))
}
if ginkgo.RegexScansFilePath {
result = append(result, fmt.Sprintf("--%sregexScansFilePath", prefix))
}
if reporter.NoColor {
result = append(result, fmt.Sprintf("--%snoColor", prefix))
}
if reporter.SlowSpecThreshold > 0 {
result = append(result, fmt.Sprintf("--%sslowSpecThreshold=%.5f", prefix, reporter.SlowSpecThreshold))
}
if !reporter.NoisyPendings {
result = append(result, fmt.Sprintf("--%snoisyPendings=false", prefix))
}
if reporter.Verbose {
result = append(result, fmt.Sprintf("--%sv", prefix))
}
if reporter.Succinct {
result = append(result, fmt.Sprintf("--%ssuccinct", prefix))
}
if reporter.FullTrace {
result = append(result, fmt.Sprintf("--%strace", prefix))
}
return result
}

569
vendor/github.com/onsi/ginkgo/ginkgo_dsl.go generated vendored Normal file
View file

@ -0,0 +1,569 @@
/*
Ginkgo is a BDD-style testing framework for Golang
The godoc documentation describes Ginkgo's API. More comprehensive documentation (with examples!) is available at http://onsi.github.io/ginkgo/
Ginkgo's preferred matcher library is [Gomega](http://github.com/onsi/gomega)
Ginkgo on Github: http://github.com/onsi/ginkgo
Ginkgo is MIT-Licensed
*/
package ginkgo
import (
"flag"
"fmt"
"io"
"net/http"
"os"
"strings"
"time"
"github.com/onsi/ginkgo/config"
"github.com/onsi/ginkgo/internal/codelocation"
"github.com/onsi/ginkgo/internal/failer"
"github.com/onsi/ginkgo/internal/remote"
"github.com/onsi/ginkgo/internal/suite"
"github.com/onsi/ginkgo/internal/testingtproxy"
"github.com/onsi/ginkgo/internal/writer"
"github.com/onsi/ginkgo/reporters"
"github.com/onsi/ginkgo/reporters/stenographer"
"github.com/onsi/ginkgo/types"
)
const GINKGO_VERSION = config.VERSION
const GINKGO_PANIC = `
Your test failed.
Ginkgo panics to prevent subsequent assertions from running.
Normally Ginkgo rescues this panic so you shouldn't see it.
But, 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.
`
const defaultTimeout = 1
var globalSuite *suite.Suite
var globalFailer *failer.Failer
func init() {
config.Flags(flag.CommandLine, "ginkgo", true)
GinkgoWriter = writer.New(os.Stdout)
globalFailer = failer.New()
globalSuite = suite.New(globalFailer)
}
//GinkgoWriter implements an io.Writer
//When running in verbose mode 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.
var GinkgoWriter io.Writer
//The interface by which Ginkgo receives *testing.T
type GinkgoTestingT interface {
Fail()
}
//GinkgoRandomSeed returns the seed used to randomize spec execution order. It is
//useful for seeding your own pseudorandom number generators (PRNGs) to ensure
//consistent executions from run to run, where your tests contain variability (for
//example, when selecting random test data).
func GinkgoRandomSeed() int64 {
return config.GinkgoConfig.RandomSeed
}
//GinkgoParallelNode returns the parallel node number for the current ginkgo process
//The node number is 1-indexed
func GinkgoParallelNode() int {
return config.GinkgoConfig.ParallelNode
}
//Some matcher libraries or legacy codebases require a *testing.T
//GinkgoT implements an interface analogous to *testing.T and can be used if
//the library in question accepts *testing.T through an interface
//
// For example, with testify:
// assert.Equal(GinkgoT(), 123, 123, "they should be equal")
//
// Or with gomock:
// gomock.NewController(GinkgoT())
//
// GinkgoT() takes an optional offset argument that can be used to get the
// correct line number associated with the failure.
func GinkgoT(optionalOffset ...int) GinkgoTInterface {
offset := 3
if len(optionalOffset) > 0 {
offset = optionalOffset[0]
}
return testingtproxy.New(GinkgoWriter, Fail, offset)
}
//The interface returned by GinkgoT(). This covers most of the methods
//in the testing package's T.
type GinkgoTInterface interface {
Fail()
Error(args ...interface{})
Errorf(format string, args ...interface{})
FailNow()
Fatal(args ...interface{})
Fatalf(format string, args ...interface{})
Log(args ...interface{})
Logf(format string, args ...interface{})
Failed() bool
Parallel()
Skip(args ...interface{})
Skipf(format string, args ...interface{})
SkipNow()
Skipped() bool
}
//Custom Ginkgo test reporters must implement the Reporter interface.
//
//The custom reporter is passed in a SuiteSummary when the suite begins and ends,
//and a SpecSummary just before a spec begins and just after a spec ends
type Reporter reporters.Reporter
//Asynchronous specs are given a channel of the Done type. You must close or write to the channel
//to tell Ginkgo that your async test is done.
type Done chan<- interface{}
//GinkgoTestDescription represents the information about the current running test returned by CurrentGinkgoTestDescription
// FullTestText: a concatenation of ComponentTexts and the TestText
// ComponentTexts: a list of all texts for the Describes & Contexts leading up to the current test
// TestText: the text in the actual It or Measure node
// IsMeasurement: true if the current test is a measurement
// FileName: the name of the file containing the current test
// LineNumber: the line number for the current test
// Failed: if the current test has failed, this will be true (useful in an AfterEach)
type GinkgoTestDescription struct {
FullTestText string
ComponentTexts []string
TestText string
IsMeasurement bool
FileName string
LineNumber int
Failed bool
}
//CurrentGinkgoTestDescripton returns information about the current running test.
func CurrentGinkgoTestDescription() GinkgoTestDescription {
summary, ok := globalSuite.CurrentRunningSpecSummary()
if !ok {
return GinkgoTestDescription{}
}
subjectCodeLocation := summary.ComponentCodeLocations[len(summary.ComponentCodeLocations)-1]
return GinkgoTestDescription{
ComponentTexts: summary.ComponentTexts[1:],
FullTestText: strings.Join(summary.ComponentTexts[1:], " "),
TestText: summary.ComponentTexts[len(summary.ComponentTexts)-1],
IsMeasurement: summary.IsMeasurement,
FileName: subjectCodeLocation.FileName,
LineNumber: subjectCodeLocation.LineNumber,
Failed: summary.HasFailureState(),
}
}
//Measurement tests receive a Benchmarker.
//
//You use the Time() function to time how long the passed in body function takes to run
//You use the RecordValue() function to track arbitrary numerical measurements.
//The RecordValueWithPrecision() function can be used alternatively to provide the unit
//and resolution of the numeric measurement.
//The optional info argument is passed to the test reporter and can be used to
// provide the measurement data to a custom reporter with context.
//
//See http://onsi.github.io/ginkgo/#benchmark_tests for more details
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{})
}
//RunSpecs is the entry point for the Ginkgo test runner.
//You must call this within a Golang testing TestX(t *testing.T) function.
//
//To bootstrap a test suite you can use the Ginkgo CLI:
//
// ginkgo bootstrap
func RunSpecs(t GinkgoTestingT, description string) bool {
specReporters := []Reporter{buildDefaultReporter()}
return RunSpecsWithCustomReporters(t, description, specReporters)
}
//To run your tests with Ginkgo's default reporter and your custom reporter(s), replace
//RunSpecs() with this method.
func RunSpecsWithDefaultAndCustomReporters(t GinkgoTestingT, description string, specReporters []Reporter) bool {
specReporters = append([]Reporter{buildDefaultReporter()}, specReporters...)
return RunSpecsWithCustomReporters(t, description, specReporters)
}
//To run your tests with your custom reporter(s) (and *not* Ginkgo's default reporter), replace
//RunSpecs() with this method. Note that parallel tests will not work correctly without the default reporter
func RunSpecsWithCustomReporters(t GinkgoTestingT, description string, specReporters []Reporter) bool {
writer := GinkgoWriter.(*writer.Writer)
writer.SetStream(config.DefaultReporterConfig.Verbose)
reporters := make([]reporters.Reporter, len(specReporters))
for i, reporter := range specReporters {
reporters[i] = reporter
}
passed, hasFocusedTests := globalSuite.Run(t, description, reporters, writer, config.GinkgoConfig)
if passed && hasFocusedTests {
fmt.Println("PASS | FOCUSED")
os.Exit(types.GINKGO_FOCUS_EXIT_CODE)
}
return passed
}
func buildDefaultReporter() Reporter {
remoteReportingServer := config.GinkgoConfig.StreamHost
if remoteReportingServer == "" {
stenographer := stenographer.New(!config.DefaultReporterConfig.NoColor, config.GinkgoConfig.FlakeAttempts > 1)
return reporters.NewDefaultReporter(config.DefaultReporterConfig, stenographer)
} else {
return remote.NewForwardingReporter(remoteReportingServer, &http.Client{}, remote.NewOutputInterceptor())
}
}
//Skip notifies Ginkgo that the current spec should be skipped.
func Skip(message string, callerSkip ...int) {
skip := 0
if len(callerSkip) > 0 {
skip = callerSkip[0]
}
globalFailer.Skip(message, codelocation.New(skip+1))
panic(GINKGO_PANIC)
}
//Fail notifies Ginkgo that the current spec has failed. (Gomega will call Fail for you automatically when an assertion fails.)
func Fail(message string, callerSkip ...int) {
skip := 0
if len(callerSkip) > 0 {
skip = callerSkip[0]
}
globalFailer.Fail(message, codelocation.New(skip+1))
panic(GINKGO_PANIC)
}
//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. Ginkgo does this for you
//if the panic originates in a Ginkgo node (an It, BeforeEach, etc...)
//
//Unfortunately, if a panic originates on a goroutine *launched* from one of these nodes 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.
func GinkgoRecover() {
e := recover()
if e != nil {
globalFailer.Panic(codelocation.New(1), e)
}
}
//Describe blocks allow you to organize your specs. A Describe block can contain any number of
//BeforeEach, AfterEach, JustBeforeEach, It, and Measurement blocks.
//
//In addition you can nest Describe and Context blocks. Describe and Context blocks are functionally
//equivalent. The difference is purely semantic -- you typical Describe the behavior of an object
//or method and, within that Describe, outline a number of Contexts.
func Describe(text string, body func()) bool {
globalSuite.PushContainerNode(text, body, types.FlagTypeNone, codelocation.New(1))
return true
}
//You can focus the tests within a describe block using FDescribe
func FDescribe(text string, body func()) bool {
globalSuite.PushContainerNode(text, body, types.FlagTypeFocused, codelocation.New(1))
return true
}
//You can mark the tests within a describe block as pending using PDescribe
func PDescribe(text string, body func()) bool {
globalSuite.PushContainerNode(text, body, types.FlagTypePending, codelocation.New(1))
return true
}
//You can mark the tests within a describe block as pending using XDescribe
func XDescribe(text string, body func()) bool {
globalSuite.PushContainerNode(text, body, types.FlagTypePending, codelocation.New(1))
return true
}
//Context blocks allow you to organize your specs. A Context block can contain any number of
//BeforeEach, AfterEach, JustBeforeEach, It, and Measurement blocks.
//
//In addition you can nest Describe and Context blocks. Describe and Context blocks are functionally
//equivalent. The difference is purely semantic -- you typical Describe the behavior of an object
//or method and, within that Describe, outline a number of Contexts.
func Context(text string, body func()) bool {
globalSuite.PushContainerNode(text, body, types.FlagTypeNone, codelocation.New(1))
return true
}
//You can focus the tests within a describe block using FContext
func FContext(text string, body func()) bool {
globalSuite.PushContainerNode(text, body, types.FlagTypeFocused, codelocation.New(1))
return true
}
//You can mark the tests within a describe block as pending using PContext
func PContext(text string, body func()) bool {
globalSuite.PushContainerNode(text, body, types.FlagTypePending, codelocation.New(1))
return true
}
//You can mark the tests within a describe block as pending using XContext
func XContext(text string, body func()) bool {
globalSuite.PushContainerNode(text, body, types.FlagTypePending, codelocation.New(1))
return true
}
//It blocks contain your test code and assertions. You cannot nest any other Ginkgo blocks
//within an It block.
//
//Ginkgo will normally run It blocks synchronously. To perform asynchronous tests, pass a
//function that accepts a Done channel. When you do this, you can also provide an optional timeout.
func It(text string, body interface{}, timeout ...float64) bool {
globalSuite.PushItNode(text, body, types.FlagTypeNone, codelocation.New(1), parseTimeout(timeout...))
return true
}
//You can focus individual Its using FIt
func FIt(text string, body interface{}, timeout ...float64) bool {
globalSuite.PushItNode(text, body, types.FlagTypeFocused, codelocation.New(1), parseTimeout(timeout...))
return true
}
//You can mark Its as pending using PIt
func PIt(text string, _ ...interface{}) bool {
globalSuite.PushItNode(text, func() {}, types.FlagTypePending, codelocation.New(1), 0)
return true
}
//You can mark Its as pending using XIt
func XIt(text string, _ ...interface{}) bool {
globalSuite.PushItNode(text, func() {}, types.FlagTypePending, codelocation.New(1), 0)
return true
}
//Specify blocks are aliases for It blocks and allow for more natural wording in situations
//which "It" does not fit into a natural sentence flow. All the same protocols apply for Specify blocks
//which apply to It blocks.
func Specify(text string, body interface{}, timeout ...float64) bool {
return It(text, body, timeout...)
}
//You can focus individual Specifys using FSpecify
func FSpecify(text string, body interface{}, timeout ...float64) bool {
return FIt(text, body, timeout...)
}
//You can mark Specifys as pending using PSpecify
func PSpecify(text string, is ...interface{}) bool {
return PIt(text, is...)
}
//You can mark Specifys as pending using XSpecify
func XSpecify(text string, is ...interface{}) bool {
return XIt(text, is...)
}
//By allows you to better document large Its.
//
//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 a particular workflow.
//
//By allows you to document such flows. By must be called within a runnable node (It, BeforeEach, Measure, etc...)
//By will simply log the passed in text to the GinkgoWriter. If By is handed a function it will immediately run the function.
func By(text string, callbacks ...func()) {
preamble := "\x1b[1mSTEP\x1b[0m"
if config.DefaultReporterConfig.NoColor {
preamble = "STEP"
}
fmt.Fprintln(GinkgoWriter, preamble+": "+text)
if len(callbacks) == 1 {
callbacks[0]()
}
if len(callbacks) > 1 {
panic("just one callback per By, please")
}
}
//Measure blocks run the passed in body function repeatedly (determined by the samples argument)
//and accumulate metrics provided to the Benchmarker by the body function.
//
//The body function must have the signature:
// func(b Benchmarker)
func Measure(text string, body interface{}, samples int) bool {
globalSuite.PushMeasureNode(text, body, types.FlagTypeNone, codelocation.New(1), samples)
return true
}
//You can focus individual Measures using FMeasure
func FMeasure(text string, body interface{}, samples int) bool {
globalSuite.PushMeasureNode(text, body, types.FlagTypeFocused, codelocation.New(1), samples)
return true
}
//You can mark Maeasurements as pending using PMeasure
func PMeasure(text string, _ ...interface{}) bool {
globalSuite.PushMeasureNode(text, func(b Benchmarker) {}, types.FlagTypePending, codelocation.New(1), 0)
return true
}
//You can mark Maeasurements as pending using XMeasure
func XMeasure(text string, _ ...interface{}) bool {
globalSuite.PushMeasureNode(text, func(b Benchmarker) {}, types.FlagTypePending, codelocation.New(1), 0)
return true
}
//BeforeSuite blocks are run just once before any specs are run. When running in parallel, each
//parallel node process will call BeforeSuite.
//
//BeforeSuite blocks can be made asynchronous by providing a body function that accepts a Done channel
//
//You may only register *one* BeforeSuite handler per test suite. You typically do so in your bootstrap file at the top level.
func BeforeSuite(body interface{}, timeout ...float64) bool {
globalSuite.SetBeforeSuiteNode(body, codelocation.New(1), parseTimeout(timeout...))
return true
}
//AfterSuite blocks are *always* run after all the specs regardless of whether specs have passed or failed.
//Moreover, if Ginkgo receives an interrupt signal (^C) it will attempt to run the AfterSuite before exiting.
//
//When running in parallel, each parallel node process will call AfterSuite.
//
//AfterSuite blocks can be made asynchronous by providing a body function that accepts a Done channel
//
//You may only register *one* AfterSuite handler per test suite. You typically do so in your bootstrap file at the top level.
func AfterSuite(body interface{}, timeout ...float64) bool {
globalSuite.SetAfterSuiteNode(body, codelocation.New(1), parseTimeout(timeout...))
return true
}
//SynchronizedBeforeSuite blocks are primarily meant to solve the problem of setting up singleton external resources shared across
//nodes when running tests in parallel. For example, say you have a shared database that you can only start one instance of that
//must be used in your tests. When running in parallel, only one node should set up the database and all other nodes should wait
//until that node is done before running.
//
//SynchronizedBeforeSuite accomplishes this by taking *two* function arguments. The first is only run on parallel node #1. The second is
//run on all nodes, but *only* after the first function completes succesfully. Ginkgo also makes it possible to send data from the first function (on Node 1)
//to the second function (on all the other nodes).
//
//The functions have the following signatures. The first function (which only runs on node 1) has the signature:
//
// func() []byte
//
//or, to run asynchronously:
//
// func(done Done) []byte
//
//The byte array returned by the first function is then passed to the second function, which has the signature:
//
// func(data []byte)
//
//or, to run asynchronously:
//
// func(data []byte, done Done)
//
//Here's a simple pseudo-code example that starts a shared database on Node 1 and shares the database's address with the other nodes:
//
// var dbClient db.Client
// var dbRunner db.Runner
//
// var _ = SynchronizedBeforeSuite(func() []byte {
// dbRunner = db.NewRunner()
// err := dbRunner.Start()
// Ω(err).ShouldNot(HaveOccurred())
// return []byte(dbRunner.URL)
// }, func(data []byte) {
// dbClient = db.NewClient()
// err := dbClient.Connect(string(data))
// Ω(err).ShouldNot(HaveOccurred())
// })
func SynchronizedBeforeSuite(node1Body interface{}, allNodesBody interface{}, timeout ...float64) bool {
globalSuite.SetSynchronizedBeforeSuiteNode(
node1Body,
allNodesBody,
codelocation.New(1),
parseTimeout(timeout...),
)
return true
}
//SynchronizedAfterSuite blocks complement the SynchronizedBeforeSuite blocks in solving the problem of setting up
//external singleton resources shared across nodes when running tests in parallel.
//
//SynchronizedAfterSuite accomplishes this by taking *two* function arguments. The first runs on all nodes. The second runs only on parallel node #1
//and *only* after all other nodes have finished and exited. This ensures that node 1, and any resources it is running, remain alive until
//all other nodes are finished.
//
//Both functions have the same signature: either func() or func(done Done) to run asynchronously.
//
//Here's a pseudo-code example that complements that given in SynchronizedBeforeSuite. Here, SynchronizedAfterSuite is used to tear down the shared database
//only after all nodes have finished:
//
// var _ = SynchronizedAfterSuite(func() {
// dbClient.Cleanup()
// }, func() {
// dbRunner.Stop()
// })
func SynchronizedAfterSuite(allNodesBody interface{}, node1Body interface{}, timeout ...float64) bool {
globalSuite.SetSynchronizedAfterSuiteNode(
allNodesBody,
node1Body,
codelocation.New(1),
parseTimeout(timeout...),
)
return true
}
//BeforeEach blocks are run before It blocks. When multiple BeforeEach blocks are defined in nested
//Describe and Context blocks the outermost BeforeEach blocks are run first.
//
//Like It blocks, BeforeEach blocks can be made asynchronous by providing a body function that accepts
//a Done channel
func BeforeEach(body interface{}, timeout ...float64) bool {
globalSuite.PushBeforeEachNode(body, codelocation.New(1), parseTimeout(timeout...))
return true
}
//JustBeforeEach blocks are run before It blocks but *after* all BeforeEach blocks. For more details,
//read the [documentation](http://onsi.github.io/ginkgo/#separating_creation_and_configuration_)
//
//Like It blocks, BeforeEach blocks can be made asynchronous by providing a body function that accepts
//a Done channel
func JustBeforeEach(body interface{}, timeout ...float64) bool {
globalSuite.PushJustBeforeEachNode(body, codelocation.New(1), parseTimeout(timeout...))
return true
}
//AfterEach blocks are run after It blocks. When multiple AfterEach blocks are defined in nested
//Describe and Context blocks the innermost AfterEach blocks are run first.
//
//Like It blocks, AfterEach blocks can be made asynchronous by providing a body function that accepts
//a Done channel
func AfterEach(body interface{}, timeout ...float64) bool {
globalSuite.PushAfterEachNode(body, codelocation.New(1), parseTimeout(timeout...))
return true
}
func parseTimeout(timeout ...float64) time.Duration {
if len(timeout) == 0 {
return time.Duration(defaultTimeout * int64(time.Second))
} else {
return time.Duration(timeout[0] * float64(time.Second))
}
}

View file

@ -0,0 +1,32 @@
package codelocation
import (
"regexp"
"runtime"
"runtime/debug"
"strings"
"github.com/onsi/ginkgo/types"
)
func New(skip int) types.CodeLocation {
_, file, line, _ := runtime.Caller(skip + 1)
stackTrace := PruneStack(string(debug.Stack()), skip)
return types.CodeLocation{FileName: file, LineNumber: line, FullStackTrace: stackTrace}
}
func PruneStack(fullStackTrace string, skip int) string {
stack := strings.Split(fullStackTrace, "\n")
if len(stack) > 2*(skip+1) {
stack = stack[2*(skip+1):]
}
prunedStack := []string{}
re := regexp.MustCompile(`\/ginkgo\/|\/pkg\/testing\/|\/pkg\/runtime\/`)
for i := 0; i < len(stack)/2; i++ {
if !re.Match([]byte(stack[i*2])) {
prunedStack = append(prunedStack, stack[i*2])
prunedStack = append(prunedStack, stack[i*2+1])
}
}
return strings.Join(prunedStack, "\n")
}

View file

@ -0,0 +1,13 @@
package codelocation_test
import (
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
"testing"
)
func TestCodelocation(t *testing.T) {
RegisterFailHandler(Fail)
RunSpecs(t, "CodeLocation Suite")
}

View file

@ -0,0 +1,79 @@
package codelocation_test
import (
. "github.com/onsi/ginkgo"
"github.com/onsi/ginkgo/internal/codelocation"
"github.com/onsi/ginkgo/types"
. "github.com/onsi/gomega"
"runtime"
)
var _ = Describe("CodeLocation", func() {
var (
codeLocation types.CodeLocation
expectedFileName string
expectedLineNumber int
)
caller0 := func() {
codeLocation = codelocation.New(1)
}
caller1 := func() {
_, expectedFileName, expectedLineNumber, _ = runtime.Caller(0)
expectedLineNumber += 2
caller0()
}
BeforeEach(func() {
caller1()
})
It("should use the passed in skip parameter to pick out the correct file & line number", func() {
Ω(codeLocation.FileName).Should(Equal(expectedFileName))
Ω(codeLocation.LineNumber).Should(Equal(expectedLineNumber))
})
Describe("stringer behavior", func() {
It("should stringify nicely", func() {
Ω(codeLocation.String()).Should(ContainSubstring("code_location_test.go:%d", expectedLineNumber))
})
})
//There's no better way than to test this private method as it
//goes out of its way to prune out ginkgo related code in the stack trace
Describe("PruneStack", func() {
It("should remove any references to ginkgo and pkg/testing and pkg/runtime", func() {
input := `/Skip/me
Skip: skip()
/Skip/me
Skip: skip()
/Users/whoever/gospace/src/github.com/onsi/ginkgo/whatever.go:10 (0x12314)
Something: Func()
/Users/whoever/gospace/src/github.com/onsi/ginkgo/whatever_else.go:10 (0x12314)
SomethingInternalToGinkgo: Func()
/usr/goroot/pkg/strings/oops.go:10 (0x12341)
Oops: BlowUp()
/Users/whoever/gospace/src/mycode/code.go:10 (0x12341)
MyCode: Func()
/Users/whoever/gospace/src/mycode/code_test.go:10 (0x12341)
MyCodeTest: Func()
/Users/whoever/gospace/src/mycode/code_suite_test.go:12 (0x37f08)
TestFoo: RunSpecs(t, "Foo Suite")
/usr/goroot/pkg/testing/testing.go:12 (0x37f08)
TestingT: Blah()
/usr/goroot/pkg/runtime/runtime.go:12 (0x37f08)
Something: Func()
`
prunedStack := codelocation.PruneStack(input, 1)
Ω(prunedStack).Should(Equal(`/usr/goroot/pkg/strings/oops.go:10 (0x12341)
Oops: BlowUp()
/Users/whoever/gospace/src/mycode/code.go:10 (0x12341)
MyCode: Func()
/Users/whoever/gospace/src/mycode/code_test.go:10 (0x12341)
MyCodeTest: Func()
/Users/whoever/gospace/src/mycode/code_suite_test.go:12 (0x37f08)
TestFoo: RunSpecs(t, "Foo Suite")`))
})
})
})

View file

@ -0,0 +1,151 @@
package containernode
import (
"math/rand"
"sort"
"github.com/onsi/ginkgo/internal/leafnodes"
"github.com/onsi/ginkgo/types"
)
type subjectOrContainerNode struct {
containerNode *ContainerNode
subjectNode leafnodes.SubjectNode
}
func (n subjectOrContainerNode) text() string {
if n.containerNode != nil {
return n.containerNode.Text()
} else {
return n.subjectNode.Text()
}
}
type CollatedNodes struct {
Containers []*ContainerNode
Subject leafnodes.SubjectNode
}
type ContainerNode struct {
text string
flag types.FlagType
codeLocation types.CodeLocation
setupNodes []leafnodes.BasicNode
subjectAndContainerNodes []subjectOrContainerNode
}
func New(text string, flag types.FlagType, codeLocation types.CodeLocation) *ContainerNode {
return &ContainerNode{
text: text,
flag: flag,
codeLocation: codeLocation,
}
}
func (container *ContainerNode) Shuffle(r *rand.Rand) {
sort.Sort(container)
permutation := r.Perm(len(container.subjectAndContainerNodes))
shuffledNodes := make([]subjectOrContainerNode, len(container.subjectAndContainerNodes))
for i, j := range permutation {
shuffledNodes[i] = container.subjectAndContainerNodes[j]
}
container.subjectAndContainerNodes = shuffledNodes
}
func (node *ContainerNode) BackPropagateProgrammaticFocus() bool {
if node.flag == types.FlagTypePending {
return false
}
shouldUnfocus := false
for _, subjectOrContainerNode := range node.subjectAndContainerNodes {
if subjectOrContainerNode.containerNode != nil {
shouldUnfocus = subjectOrContainerNode.containerNode.BackPropagateProgrammaticFocus() || shouldUnfocus
} else {
shouldUnfocus = (subjectOrContainerNode.subjectNode.Flag() == types.FlagTypeFocused) || shouldUnfocus
}
}
if shouldUnfocus {
if node.flag == types.FlagTypeFocused {
node.flag = types.FlagTypeNone
}
return true
}
return node.flag == types.FlagTypeFocused
}
func (node *ContainerNode) Collate() []CollatedNodes {
return node.collate([]*ContainerNode{})
}
func (node *ContainerNode) collate(enclosingContainers []*ContainerNode) []CollatedNodes {
collated := make([]CollatedNodes, 0)
containers := make([]*ContainerNode, len(enclosingContainers))
copy(containers, enclosingContainers)
containers = append(containers, node)
for _, subjectOrContainer := range node.subjectAndContainerNodes {
if subjectOrContainer.containerNode != nil {
collated = append(collated, subjectOrContainer.containerNode.collate(containers)...)
} else {
collated = append(collated, CollatedNodes{
Containers: containers,
Subject: subjectOrContainer.subjectNode,
})
}
}
return collated
}
func (node *ContainerNode) PushContainerNode(container *ContainerNode) {
node.subjectAndContainerNodes = append(node.subjectAndContainerNodes, subjectOrContainerNode{containerNode: container})
}
func (node *ContainerNode) PushSubjectNode(subject leafnodes.SubjectNode) {
node.subjectAndContainerNodes = append(node.subjectAndContainerNodes, subjectOrContainerNode{subjectNode: subject})
}
func (node *ContainerNode) PushSetupNode(setupNode leafnodes.BasicNode) {
node.setupNodes = append(node.setupNodes, setupNode)
}
func (node *ContainerNode) SetupNodesOfType(nodeType types.SpecComponentType) []leafnodes.BasicNode {
nodes := []leafnodes.BasicNode{}
for _, setupNode := range node.setupNodes {
if setupNode.Type() == nodeType {
nodes = append(nodes, setupNode)
}
}
return nodes
}
func (node *ContainerNode) Text() string {
return node.text
}
func (node *ContainerNode) CodeLocation() types.CodeLocation {
return node.codeLocation
}
func (node *ContainerNode) Flag() types.FlagType {
return node.flag
}
//sort.Interface
func (node *ContainerNode) Len() int {
return len(node.subjectAndContainerNodes)
}
func (node *ContainerNode) Less(i, j int) bool {
return node.subjectAndContainerNodes[i].text() < node.subjectAndContainerNodes[j].text()
}
func (node *ContainerNode) Swap(i, j int) {
node.subjectAndContainerNodes[i], node.subjectAndContainerNodes[j] = node.subjectAndContainerNodes[j], node.subjectAndContainerNodes[i]
}

View file

@ -0,0 +1,13 @@
package containernode_test
import (
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
"testing"
)
func TestContainernode(t *testing.T) {
RegisterFailHandler(Fail)
RunSpecs(t, "Containernode Suite")
}

View file

@ -0,0 +1,212 @@
package containernode_test
import (
"github.com/onsi/ginkgo/internal/leafnodes"
"math/rand"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
"github.com/onsi/ginkgo/internal/codelocation"
. "github.com/onsi/ginkgo/internal/containernode"
"github.com/onsi/ginkgo/types"
)
var _ = Describe("Container Node", func() {
var (
codeLocation types.CodeLocation
container *ContainerNode
)
BeforeEach(func() {
codeLocation = codelocation.New(0)
container = New("description text", types.FlagTypeFocused, codeLocation)
})
Describe("creating a container node", func() {
It("can answer questions about itself", func() {
Ω(container.Text()).Should(Equal("description text"))
Ω(container.Flag()).Should(Equal(types.FlagTypeFocused))
Ω(container.CodeLocation()).Should(Equal(codeLocation))
})
})
Describe("pushing setup nodes", func() {
It("can append setup nodes of various types and fetch them by type", func() {
befA := leafnodes.NewBeforeEachNode(func() {}, codelocation.New(0), 0, nil, 0)
befB := leafnodes.NewBeforeEachNode(func() {}, codelocation.New(0), 0, nil, 0)
aftA := leafnodes.NewAfterEachNode(func() {}, codelocation.New(0), 0, nil, 0)
aftB := leafnodes.NewAfterEachNode(func() {}, codelocation.New(0), 0, nil, 0)
jusBefA := leafnodes.NewJustBeforeEachNode(func() {}, codelocation.New(0), 0, nil, 0)
jusBefB := leafnodes.NewJustBeforeEachNode(func() {}, codelocation.New(0), 0, nil, 0)
container.PushSetupNode(befA)
container.PushSetupNode(befB)
container.PushSetupNode(aftA)
container.PushSetupNode(aftB)
container.PushSetupNode(jusBefA)
container.PushSetupNode(jusBefB)
subject := leafnodes.NewItNode("subject", func() {}, types.FlagTypeNone, codelocation.New(0), 0, nil, 0)
container.PushSubjectNode(subject)
Ω(container.SetupNodesOfType(types.SpecComponentTypeBeforeEach)).Should(Equal([]leafnodes.BasicNode{befA, befB}))
Ω(container.SetupNodesOfType(types.SpecComponentTypeAfterEach)).Should(Equal([]leafnodes.BasicNode{aftA, aftB}))
Ω(container.SetupNodesOfType(types.SpecComponentTypeJustBeforeEach)).Should(Equal([]leafnodes.BasicNode{jusBefA, jusBefB}))
Ω(container.SetupNodesOfType(types.SpecComponentTypeIt)).Should(BeEmpty()) //subjects are not setup nodes
})
})
Context("With appended containers and subject nodes", func() {
var (
itA, itB, innerItA, innerItB leafnodes.SubjectNode
innerContainer *ContainerNode
)
BeforeEach(func() {
itA = leafnodes.NewItNode("Banana", func() {}, types.FlagTypeNone, codelocation.New(0), 0, nil, 0)
itB = leafnodes.NewItNode("Apple", func() {}, types.FlagTypeNone, codelocation.New(0), 0, nil, 0)
innerItA = leafnodes.NewItNode("inner A", func() {}, types.FlagTypeNone, codelocation.New(0), 0, nil, 0)
innerItB = leafnodes.NewItNode("inner B", func() {}, types.FlagTypeNone, codelocation.New(0), 0, nil, 0)
innerContainer = New("Orange", types.FlagTypeNone, codelocation.New(0))
container.PushSubjectNode(itA)
container.PushContainerNode(innerContainer)
innerContainer.PushSubjectNode(innerItA)
innerContainer.PushSubjectNode(innerItB)
container.PushSubjectNode(itB)
})
Describe("Collating", func() {
It("should return a collated set of containers and subject nodes in the correct order", func() {
collated := container.Collate()
Ω(collated).Should(HaveLen(4))
Ω(collated[0]).Should(Equal(CollatedNodes{
Containers: []*ContainerNode{container},
Subject: itA,
}))
Ω(collated[1]).Should(Equal(CollatedNodes{
Containers: []*ContainerNode{container, innerContainer},
Subject: innerItA,
}))
Ω(collated[2]).Should(Equal(CollatedNodes{
Containers: []*ContainerNode{container, innerContainer},
Subject: innerItB,
}))
Ω(collated[3]).Should(Equal(CollatedNodes{
Containers: []*ContainerNode{container},
Subject: itB,
}))
})
})
Describe("Backpropagating Programmatic Focus", func() {
//This allows inner focused specs to override the focus of outer focussed
//specs and more closely maps to what a developer wants to happen
//when debugging a test suite
Context("when a parent is focused *and* an inner subject is focused", func() {
BeforeEach(func() {
container = New("description text", types.FlagTypeFocused, codeLocation)
itA = leafnodes.NewItNode("A", func() {}, types.FlagTypeNone, codelocation.New(0), 0, nil, 0)
container.PushSubjectNode(itA)
innerContainer = New("Orange", types.FlagTypeNone, codelocation.New(0))
container.PushContainerNode(innerContainer)
innerItA = leafnodes.NewItNode("inner A", func() {}, types.FlagTypeFocused, codelocation.New(0), 0, nil, 0)
innerContainer.PushSubjectNode(innerItA)
})
It("should unfocus the parent", func() {
container.BackPropagateProgrammaticFocus()
Ω(container.Flag()).Should(Equal(types.FlagTypeNone))
Ω(itA.Flag()).Should(Equal(types.FlagTypeNone))
Ω(innerContainer.Flag()).Should(Equal(types.FlagTypeNone))
Ω(innerItA.Flag()).Should(Equal(types.FlagTypeFocused))
})
})
Context("when a parent is focused *and* an inner container is focused", func() {
BeforeEach(func() {
container = New("description text", types.FlagTypeFocused, codeLocation)
itA = leafnodes.NewItNode("A", func() {}, types.FlagTypeNone, codelocation.New(0), 0, nil, 0)
container.PushSubjectNode(itA)
innerContainer = New("Orange", types.FlagTypeFocused, codelocation.New(0))
container.PushContainerNode(innerContainer)
innerItA = leafnodes.NewItNode("inner A", func() {}, types.FlagTypeNone, codelocation.New(0), 0, nil, 0)
innerContainer.PushSubjectNode(innerItA)
})
It("should unfocus the parent", func() {
container.BackPropagateProgrammaticFocus()
Ω(container.Flag()).Should(Equal(types.FlagTypeNone))
Ω(itA.Flag()).Should(Equal(types.FlagTypeNone))
Ω(innerContainer.Flag()).Should(Equal(types.FlagTypeFocused))
Ω(innerItA.Flag()).Should(Equal(types.FlagTypeNone))
})
})
Context("when a parent is pending and a child is focused", func() {
BeforeEach(func() {
container = New("description text", types.FlagTypeFocused, codeLocation)
itA = leafnodes.NewItNode("A", func() {}, types.FlagTypeNone, codelocation.New(0), 0, nil, 0)
container.PushSubjectNode(itA)
innerContainer = New("Orange", types.FlagTypePending, codelocation.New(0))
container.PushContainerNode(innerContainer)
innerItA = leafnodes.NewItNode("inner A", func() {}, types.FlagTypeFocused, codelocation.New(0), 0, nil, 0)
innerContainer.PushSubjectNode(innerItA)
})
It("should not do anything", func() {
container.BackPropagateProgrammaticFocus()
Ω(container.Flag()).Should(Equal(types.FlagTypeFocused))
Ω(itA.Flag()).Should(Equal(types.FlagTypeNone))
Ω(innerContainer.Flag()).Should(Equal(types.FlagTypePending))
Ω(innerItA.Flag()).Should(Equal(types.FlagTypeFocused))
})
})
})
Describe("Shuffling", func() {
var unshuffledCollation []CollatedNodes
BeforeEach(func() {
unshuffledCollation = container.Collate()
r := rand.New(rand.NewSource(17))
container.Shuffle(r)
})
It("should sort, and then shuffle, the top level contents of the container", func() {
shuffledCollation := container.Collate()
Ω(shuffledCollation).Should(HaveLen(len(unshuffledCollation)))
Ω(shuffledCollation).ShouldNot(Equal(unshuffledCollation))
for _, entry := range unshuffledCollation {
Ω(shuffledCollation).Should(ContainElement(entry))
}
innerAIndex, innerBIndex := 0, 0
for i, entry := range shuffledCollation {
if entry.Subject == innerItA {
innerAIndex = i
} else if entry.Subject == innerItB {
innerBIndex = i
}
}
Ω(innerAIndex).Should(Equal(innerBIndex - 1))
})
})
})
})

View file

@ -0,0 +1,92 @@
package failer
import (
"fmt"
"sync"
"github.com/onsi/ginkgo/types"
)
type Failer struct {
lock *sync.Mutex
failure types.SpecFailure
state types.SpecState
}
func New() *Failer {
return &Failer{
lock: &sync.Mutex{},
state: types.SpecStatePassed,
}
}
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.SpecFailure{
Message: "Test Panicked",
Location: location,
ForwardedPanic: fmt.Sprintf("%v", forwardedPanic),
}
}
}
func (f *Failer) Timeout(location types.CodeLocation) {
f.lock.Lock()
defer f.lock.Unlock()
if f.state == types.SpecStatePassed {
f.state = types.SpecStateTimedOut
f.failure = types.SpecFailure{
Message: "Timed out",
Location: location,
}
}
}
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.SpecFailure{
Message: message,
Location: location,
}
}
}
func (f *Failer) Drain(componentType types.SpecComponentType, componentIndex int, componentCodeLocation types.CodeLocation) (types.SpecFailure, types.SpecState) {
f.lock.Lock()
defer f.lock.Unlock()
failure := f.failure
outcome := f.state
if outcome != types.SpecStatePassed {
failure.ComponentType = componentType
failure.ComponentIndex = componentIndex
failure.ComponentCodeLocation = componentCodeLocation
}
f.state = types.SpecStatePassed
f.failure = types.SpecFailure{}
return failure, outcome
}
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.SpecFailure{
Message: message,
Location: location,
}
}
}

View file

@ -0,0 +1,13 @@
package failer_test
import (
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
"testing"
)
func TestFailer(t *testing.T) {
RegisterFailHandler(Fail)
RunSpecs(t, "Failer Suite")
}

View file

@ -0,0 +1,141 @@
package failer_test
import (
. "github.com/onsi/ginkgo"
. "github.com/onsi/ginkgo/internal/failer"
. "github.com/onsi/gomega"
"github.com/onsi/ginkgo/internal/codelocation"
"github.com/onsi/ginkgo/types"
)
var _ = Describe("Failer", func() {
var (
failer *Failer
codeLocationA types.CodeLocation
codeLocationB types.CodeLocation
)
BeforeEach(func() {
codeLocationA = codelocation.New(0)
codeLocationB = codelocation.New(0)
failer = New()
})
Context("with no failures", func() {
It("should return success when drained", func() {
failure, state := failer.Drain(types.SpecComponentTypeIt, 3, codeLocationB)
Ω(failure).Should(BeZero())
Ω(state).Should(Equal(types.SpecStatePassed))
})
})
Describe("Skip", func() {
It("should handle failures", func() {
failer.Skip("something skipped", codeLocationA)
failure, state := failer.Drain(types.SpecComponentTypeIt, 3, codeLocationB)
Ω(failure).Should(Equal(types.SpecFailure{
Message: "something skipped",
Location: codeLocationA,
ForwardedPanic: "",
ComponentType: types.SpecComponentTypeIt,
ComponentIndex: 3,
ComponentCodeLocation: codeLocationB,
}))
Ω(state).Should(Equal(types.SpecStateSkipped))
})
})
Describe("Fail", func() {
It("should handle failures", func() {
failer.Fail("something failed", codeLocationA)
failure, state := failer.Drain(types.SpecComponentTypeIt, 3, codeLocationB)
Ω(failure).Should(Equal(types.SpecFailure{
Message: "something failed",
Location: codeLocationA,
ForwardedPanic: "",
ComponentType: types.SpecComponentTypeIt,
ComponentIndex: 3,
ComponentCodeLocation: codeLocationB,
}))
Ω(state).Should(Equal(types.SpecStateFailed))
})
})
Describe("Panic", func() {
It("should handle panics", func() {
failer.Panic(codeLocationA, "some forwarded panic")
failure, state := failer.Drain(types.SpecComponentTypeIt, 3, codeLocationB)
Ω(failure).Should(Equal(types.SpecFailure{
Message: "Test Panicked",
Location: codeLocationA,
ForwardedPanic: "some forwarded panic",
ComponentType: types.SpecComponentTypeIt,
ComponentIndex: 3,
ComponentCodeLocation: codeLocationB,
}))
Ω(state).Should(Equal(types.SpecStatePanicked))
})
})
Describe("Timeout", func() {
It("should handle timeouts", func() {
failer.Timeout(codeLocationA)
failure, state := failer.Drain(types.SpecComponentTypeIt, 3, codeLocationB)
Ω(failure).Should(Equal(types.SpecFailure{
Message: "Timed out",
Location: codeLocationA,
ForwardedPanic: "",
ComponentType: types.SpecComponentTypeIt,
ComponentIndex: 3,
ComponentCodeLocation: codeLocationB,
}))
Ω(state).Should(Equal(types.SpecStateTimedOut))
})
})
Context("when multiple failures are registered", func() {
BeforeEach(func() {
failer.Fail("something failed", codeLocationA)
failer.Fail("something else failed", codeLocationA)
})
It("should only report the first one when drained", func() {
failure, state := failer.Drain(types.SpecComponentTypeIt, 3, codeLocationB)
Ω(failure).Should(Equal(types.SpecFailure{
Message: "something failed",
Location: codeLocationA,
ForwardedPanic: "",
ComponentType: types.SpecComponentTypeIt,
ComponentIndex: 3,
ComponentCodeLocation: codeLocationB,
}))
Ω(state).Should(Equal(types.SpecStateFailed))
})
It("should report subsequent failures after being drained", func() {
failer.Drain(types.SpecComponentTypeIt, 3, codeLocationB)
failer.Fail("yet another thing failed", codeLocationA)
failure, state := failer.Drain(types.SpecComponentTypeIt, 3, codeLocationB)
Ω(failure).Should(Equal(types.SpecFailure{
Message: "yet another thing failed",
Location: codeLocationA,
ForwardedPanic: "",
ComponentType: types.SpecComponentTypeIt,
ComponentIndex: 3,
ComponentCodeLocation: codeLocationB,
}))
Ω(state).Should(Equal(types.SpecStateFailed))
})
It("should report sucess on subsequent drains if no errors occur", func() {
failer.Drain(types.SpecComponentTypeIt, 3, codeLocationB)
failure, state := failer.Drain(types.SpecComponentTypeIt, 3, codeLocationB)
Ω(failure).Should(BeZero())
Ω(state).Should(Equal(types.SpecStatePassed))
})
})
})

View file

@ -0,0 +1,103 @@
package leafnodes
import (
"math"
"time"
"sync"
"github.com/onsi/ginkgo/types"
)
type benchmarker struct {
mu sync.Mutex
measurements map[string]*types.SpecMeasurement
orderCounter int
}
func newBenchmarker() *benchmarker {
return &benchmarker{
measurements: make(map[string]*types.SpecMeasurement, 0),
}
}
func (b *benchmarker) Time(name string, body func(), info ...interface{}) (elapsedTime time.Duration) {
t := time.Now()
body()
elapsedTime = time.Since(t)
b.mu.Lock()
defer b.mu.Unlock()
measurement := b.getMeasurement(name, "Fastest Time", "Slowest Time", "Average Time", "s", 3, info...)
measurement.Results = append(measurement.Results, elapsedTime.Seconds())
return
}
func (b *benchmarker) RecordValue(name string, value float64, info ...interface{}) {
measurement := b.getMeasurement(name, "Smallest", " Largest", " Average", "", 3, info...)
b.mu.Lock()
defer b.mu.Unlock()
measurement.Results = append(measurement.Results, value)
}
func (b *benchmarker) RecordValueWithPrecision(name string, value float64, units string, precision int, info ...interface{}) {
measurement := b.getMeasurement(name, "Smallest", " Largest", " Average", units, precision, info...)
b.mu.Lock()
defer b.mu.Unlock()
measurement.Results = append(measurement.Results, value)
}
func (b *benchmarker) getMeasurement(name string, smallestLabel string, largestLabel string, averageLabel string, units string, precision int, info ...interface{}) *types.SpecMeasurement {
measurement, ok := b.measurements[name]
if !ok {
var computedInfo interface{}
computedInfo = nil
if len(info) > 0 {
computedInfo = info[0]
}
measurement = &types.SpecMeasurement{
Name: name,
Info: computedInfo,
Order: b.orderCounter,
SmallestLabel: smallestLabel,
LargestLabel: largestLabel,
AverageLabel: averageLabel,
Units: units,
Precision: precision,
Results: make([]float64, 0),
}
b.measurements[name] = measurement
b.orderCounter++
}
return measurement
}
func (b *benchmarker) measurementsReport() map[string]*types.SpecMeasurement {
b.mu.Lock()
defer b.mu.Unlock()
for _, measurement := range b.measurements {
measurement.Smallest = math.MaxFloat64
measurement.Largest = -math.MaxFloat64
sum := float64(0)
sumOfSquares := float64(0)
for _, result := range measurement.Results {
if result > measurement.Largest {
measurement.Largest = result
}
if result < measurement.Smallest {
measurement.Smallest = result
}
sum += result
sumOfSquares += result * result
}
n := float64(len(measurement.Results))
measurement.Average = sum / n
measurement.StdDeviation = math.Sqrt(sumOfSquares/n - (sum/n)*(sum/n))
}
return b.measurements
}

View file

@ -0,0 +1,19 @@
package leafnodes
import (
"github.com/onsi/ginkgo/types"
)
type BasicNode interface {
Type() types.SpecComponentType
Run() (types.SpecState, types.SpecFailure)
CodeLocation() types.CodeLocation
}
type SubjectNode interface {
BasicNode
Text() string
Flag() types.FlagType
Samples() int
}

View file

@ -0,0 +1,46 @@
package leafnodes
import (
"github.com/onsi/ginkgo/internal/failer"
"github.com/onsi/ginkgo/types"
"time"
)
type ItNode struct {
runner *runner
flag types.FlagType
text string
}
func NewItNode(text string, body interface{}, flag types.FlagType, codeLocation types.CodeLocation, timeout time.Duration, failer *failer.Failer, componentIndex int) *ItNode {
return &ItNode{
runner: newRunner(body, codeLocation, timeout, failer, types.SpecComponentTypeIt, componentIndex),
flag: flag,
text: text,
}
}
func (node *ItNode) Run() (outcome types.SpecState, failure types.SpecFailure) {
return node.runner.run()
}
func (node *ItNode) Type() types.SpecComponentType {
return types.SpecComponentTypeIt
}
func (node *ItNode) Text() string {
return node.text
}
func (node *ItNode) Flag() types.FlagType {
return node.flag
}
func (node *ItNode) CodeLocation() types.CodeLocation {
return node.runner.codeLocation
}
func (node *ItNode) Samples() int {
return 1
}

View file

@ -0,0 +1,22 @@
package leafnodes_test
import (
. "github.com/onsi/ginkgo"
. "github.com/onsi/ginkgo/internal/leafnodes"
. "github.com/onsi/gomega"
"github.com/onsi/ginkgo/internal/codelocation"
"github.com/onsi/ginkgo/types"
)
var _ = Describe("It Nodes", func() {
It("should report the correct type, text, flag, and code location", func() {
codeLocation := codelocation.New(0)
it := NewItNode("my it node", func() {}, types.FlagTypeFocused, codeLocation, 0, nil, 3)
Ω(it.Type()).Should(Equal(types.SpecComponentTypeIt))
Ω(it.Flag()).Should(Equal(types.FlagTypeFocused))
Ω(it.Text()).Should(Equal("my it node"))
Ω(it.CodeLocation()).Should(Equal(codeLocation))
Ω(it.Samples()).Should(Equal(1))
})
})

View file

@ -0,0 +1,13 @@
package leafnodes_test
import (
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
"testing"
)
func TestLeafNode(t *testing.T) {
RegisterFailHandler(Fail)
RunSpecs(t, "LeafNode Suite")
}

View file

@ -0,0 +1,61 @@
package leafnodes
import (
"github.com/onsi/ginkgo/internal/failer"
"github.com/onsi/ginkgo/types"
"reflect"
)
type MeasureNode struct {
runner *runner
text string
flag types.FlagType
samples int
benchmarker *benchmarker
}
func NewMeasureNode(text string, body interface{}, flag types.FlagType, codeLocation types.CodeLocation, samples int, failer *failer.Failer, componentIndex int) *MeasureNode {
benchmarker := newBenchmarker()
wrappedBody := func() {
reflect.ValueOf(body).Call([]reflect.Value{reflect.ValueOf(benchmarker)})
}
return &MeasureNode{
runner: newRunner(wrappedBody, codeLocation, 0, failer, types.SpecComponentTypeMeasure, componentIndex),
text: text,
flag: flag,
samples: samples,
benchmarker: benchmarker,
}
}
func (node *MeasureNode) Run() (outcome types.SpecState, failure types.SpecFailure) {
return node.runner.run()
}
func (node *MeasureNode) MeasurementsReport() map[string]*types.SpecMeasurement {
return node.benchmarker.measurementsReport()
}
func (node *MeasureNode) Type() types.SpecComponentType {
return types.SpecComponentTypeMeasure
}
func (node *MeasureNode) Text() string {
return node.text
}
func (node *MeasureNode) Flag() types.FlagType {
return node.flag
}
func (node *MeasureNode) CodeLocation() types.CodeLocation {
return node.runner.codeLocation
}
func (node *MeasureNode) Samples() int {
return node.samples
}

View file

@ -0,0 +1,154 @@
package leafnodes_test
import (
. "github.com/onsi/ginkgo"
. "github.com/onsi/ginkgo/internal/leafnodes"
. "github.com/onsi/gomega"
"github.com/onsi/ginkgo/internal/codelocation"
Failer "github.com/onsi/ginkgo/internal/failer"
"github.com/onsi/ginkgo/types"
"time"
)
var _ = Describe("Measure Nodes", func() {
It("should report the correct type, text, flag, and code location", func() {
codeLocation := codelocation.New(0)
measure := NewMeasureNode("my measure node", func(b Benchmarker) {}, types.FlagTypeFocused, codeLocation, 10, nil, 3)
Ω(measure.Type()).Should(Equal(types.SpecComponentTypeMeasure))
Ω(measure.Flag()).Should(Equal(types.FlagTypeFocused))
Ω(measure.Text()).Should(Equal("my measure node"))
Ω(measure.CodeLocation()).Should(Equal(codeLocation))
Ω(measure.Samples()).Should(Equal(10))
})
Describe("benchmarking", func() {
var measure *MeasureNode
Describe("Value", func() {
BeforeEach(func() {
measure = NewMeasureNode("the measurement", func(b Benchmarker) {
b.RecordValue("foo", 7, "info!")
b.RecordValue("foo", 2)
b.RecordValue("foo", 3)
b.RecordValue("bar", 0.3)
b.RecordValue("bar", 0.1)
b.RecordValue("bar", 0.5)
b.RecordValue("bar", 0.7)
}, types.FlagTypeFocused, codelocation.New(0), 1, Failer.New(), 3)
Ω(measure.Run()).Should(Equal(types.SpecStatePassed))
})
It("records passed in values and reports on them", func() {
report := measure.MeasurementsReport()
Ω(report).Should(HaveLen(2))
Ω(report["foo"].Name).Should(Equal("foo"))
Ω(report["foo"].Info).Should(Equal("info!"))
Ω(report["foo"].Order).Should(Equal(0))
Ω(report["foo"].SmallestLabel).Should(Equal("Smallest"))
Ω(report["foo"].LargestLabel).Should(Equal(" Largest"))
Ω(report["foo"].AverageLabel).Should(Equal(" Average"))
Ω(report["foo"].Units).Should(Equal(""))
Ω(report["foo"].Results).Should(Equal([]float64{7, 2, 3}))
Ω(report["foo"].Smallest).Should(BeNumerically("==", 2))
Ω(report["foo"].Largest).Should(BeNumerically("==", 7))
Ω(report["foo"].Average).Should(BeNumerically("==", 4))
Ω(report["foo"].StdDeviation).Should(BeNumerically("~", 2.16, 0.01))
Ω(report["bar"].Name).Should(Equal("bar"))
Ω(report["bar"].Info).Should(BeNil())
Ω(report["bar"].SmallestLabel).Should(Equal("Smallest"))
Ω(report["bar"].Order).Should(Equal(1))
Ω(report["bar"].LargestLabel).Should(Equal(" Largest"))
Ω(report["bar"].AverageLabel).Should(Equal(" Average"))
Ω(report["bar"].Units).Should(Equal(""))
Ω(report["bar"].Results).Should(Equal([]float64{0.3, 0.1, 0.5, 0.7}))
Ω(report["bar"].Smallest).Should(BeNumerically("==", 0.1))
Ω(report["bar"].Largest).Should(BeNumerically("==", 0.7))
Ω(report["bar"].Average).Should(BeNumerically("==", 0.4))
Ω(report["bar"].StdDeviation).Should(BeNumerically("~", 0.22, 0.01))
})
})
Describe("Value with precision", func() {
BeforeEach(func() {
measure = NewMeasureNode("the measurement", func(b Benchmarker) {
b.RecordValueWithPrecision("foo", 7, "ms", 7, "info!")
b.RecordValueWithPrecision("foo", 2, "ms", 6)
b.RecordValueWithPrecision("foo", 3, "ms", 5)
b.RecordValueWithPrecision("bar", 0.3, "ns", 4)
b.RecordValueWithPrecision("bar", 0.1, "ns", 3)
b.RecordValueWithPrecision("bar", 0.5, "ns", 2)
b.RecordValueWithPrecision("bar", 0.7, "ns", 1)
}, types.FlagTypeFocused, codelocation.New(0), 1, Failer.New(), 3)
Ω(measure.Run()).Should(Equal(types.SpecStatePassed))
})
It("records passed in values and reports on them", func() {
report := measure.MeasurementsReport()
Ω(report).Should(HaveLen(2))
Ω(report["foo"].Name).Should(Equal("foo"))
Ω(report["foo"].Info).Should(Equal("info!"))
Ω(report["foo"].Order).Should(Equal(0))
Ω(report["foo"].SmallestLabel).Should(Equal("Smallest"))
Ω(report["foo"].LargestLabel).Should(Equal(" Largest"))
Ω(report["foo"].AverageLabel).Should(Equal(" Average"))
Ω(report["foo"].Units).Should(Equal("ms"))
Ω(report["foo"].Results).Should(Equal([]float64{7, 2, 3}))
Ω(report["foo"].Smallest).Should(BeNumerically("==", 2))
Ω(report["foo"].Largest).Should(BeNumerically("==", 7))
Ω(report["foo"].Average).Should(BeNumerically("==", 4))
Ω(report["foo"].StdDeviation).Should(BeNumerically("~", 2.16, 0.01))
Ω(report["bar"].Name).Should(Equal("bar"))
Ω(report["bar"].Info).Should(BeNil())
Ω(report["bar"].SmallestLabel).Should(Equal("Smallest"))
Ω(report["bar"].Order).Should(Equal(1))
Ω(report["bar"].LargestLabel).Should(Equal(" Largest"))
Ω(report["bar"].AverageLabel).Should(Equal(" Average"))
Ω(report["bar"].Units).Should(Equal("ns"))
Ω(report["bar"].Results).Should(Equal([]float64{0.3, 0.1, 0.5, 0.7}))
Ω(report["bar"].Smallest).Should(BeNumerically("==", 0.1))
Ω(report["bar"].Largest).Should(BeNumerically("==", 0.7))
Ω(report["bar"].Average).Should(BeNumerically("==", 0.4))
Ω(report["bar"].StdDeviation).Should(BeNumerically("~", 0.22, 0.01))
})
})
Describe("Time", func() {
BeforeEach(func() {
measure = NewMeasureNode("the measurement", func(b Benchmarker) {
b.Time("foo", func() {
time.Sleep(100 * time.Millisecond)
}, "info!")
b.Time("foo", func() {
time.Sleep(200 * time.Millisecond)
})
b.Time("foo", func() {
time.Sleep(170 * time.Millisecond)
})
}, types.FlagTypeFocused, codelocation.New(0), 1, Failer.New(), 3)
Ω(measure.Run()).Should(Equal(types.SpecStatePassed))
})
It("records passed in values and reports on them", func() {
report := measure.MeasurementsReport()
Ω(report).Should(HaveLen(1))
Ω(report["foo"].Name).Should(Equal("foo"))
Ω(report["foo"].Info).Should(Equal("info!"))
Ω(report["foo"].SmallestLabel).Should(Equal("Fastest Time"))
Ω(report["foo"].LargestLabel).Should(Equal("Slowest Time"))
Ω(report["foo"].AverageLabel).Should(Equal("Average Time"))
Ω(report["foo"].Units).Should(Equal("s"))
Ω(report["foo"].Results).Should(HaveLen(3))
Ω(report["foo"].Results[0]).Should(BeNumerically("~", 0.1, 0.01))
Ω(report["foo"].Results[1]).Should(BeNumerically("~", 0.2, 0.01))
Ω(report["foo"].Results[2]).Should(BeNumerically("~", 0.17, 0.01))
Ω(report["foo"].Smallest).Should(BeNumerically("~", 0.1, 0.01))
Ω(report["foo"].Largest).Should(BeNumerically("~", 0.2, 0.01))
Ω(report["foo"].Average).Should(BeNumerically("~", 0.16, 0.01))
Ω(report["foo"].StdDeviation).Should(BeNumerically("~", 0.04, 0.01))
})
})
})
})

View file

@ -0,0 +1,113 @@
package leafnodes
import (
"fmt"
"github.com/onsi/ginkgo/internal/codelocation"
"github.com/onsi/ginkgo/internal/failer"
"github.com/onsi/ginkgo/types"
"reflect"
"time"
)
type runner struct {
isAsync bool
asyncFunc func(chan<- interface{})
syncFunc func()
codeLocation types.CodeLocation
timeoutThreshold time.Duration
nodeType types.SpecComponentType
componentIndex int
failer *failer.Failer
}
func newRunner(body interface{}, codeLocation types.CodeLocation, timeout time.Duration, failer *failer.Failer, nodeType types.SpecComponentType, componentIndex int) *runner {
bodyType := reflect.TypeOf(body)
if bodyType.Kind() != reflect.Func {
panic(fmt.Sprintf("Expected a function but got something else at %v", codeLocation))
}
runner := &runner{
codeLocation: codeLocation,
timeoutThreshold: timeout,
failer: failer,
nodeType: nodeType,
componentIndex: componentIndex,
}
switch bodyType.NumIn() {
case 0:
runner.syncFunc = body.(func())
return runner
case 1:
if !(bodyType.In(0).Kind() == reflect.Chan && bodyType.In(0).Elem().Kind() == reflect.Interface) {
panic(fmt.Sprintf("Must pass a Done channel to function at %v", codeLocation))
}
wrappedBody := func(done chan<- interface{}) {
bodyValue := reflect.ValueOf(body)
bodyValue.Call([]reflect.Value{reflect.ValueOf(done)})
}
runner.isAsync = true
runner.asyncFunc = wrappedBody
return runner
}
panic(fmt.Sprintf("Too many arguments to function at %v", codeLocation))
}
func (r *runner) run() (outcome types.SpecState, failure types.SpecFailure) {
if r.isAsync {
return r.runAsync()
} else {
return r.runSync()
}
}
func (r *runner) runAsync() (outcome types.SpecState, failure types.SpecFailure) {
done := make(chan interface{}, 1)
go func() {
finished := false
defer func() {
if e := recover(); e != nil || !finished {
r.failer.Panic(codelocation.New(2), e)
select {
case <-done:
break
default:
close(done)
}
}
}()
r.asyncFunc(done)
finished = true
}()
select {
case <-done:
case <-time.After(r.timeoutThreshold):
r.failer.Timeout(r.codeLocation)
}
failure, outcome = r.failer.Drain(r.nodeType, r.componentIndex, r.codeLocation)
return
}
func (r *runner) runSync() (outcome types.SpecState, failure types.SpecFailure) {
finished := false
defer func() {
if e := recover(); e != nil || !finished {
r.failer.Panic(codelocation.New(2), e)
}
failure, outcome = r.failer.Drain(r.nodeType, r.componentIndex, r.codeLocation)
}()
r.syncFunc()
finished = true
return
}

View file

@ -0,0 +1,41 @@
package leafnodes
import (
"github.com/onsi/ginkgo/internal/failer"
"github.com/onsi/ginkgo/types"
"time"
)
type SetupNode struct {
runner *runner
}
func (node *SetupNode) Run() (outcome types.SpecState, failure types.SpecFailure) {
return node.runner.run()
}
func (node *SetupNode) Type() types.SpecComponentType {
return node.runner.nodeType
}
func (node *SetupNode) CodeLocation() types.CodeLocation {
return node.runner.codeLocation
}
func NewBeforeEachNode(body interface{}, codeLocation types.CodeLocation, timeout time.Duration, failer *failer.Failer, componentIndex int) *SetupNode {
return &SetupNode{
runner: newRunner(body, codeLocation, timeout, failer, types.SpecComponentTypeBeforeEach, componentIndex),
}
}
func NewAfterEachNode(body interface{}, codeLocation types.CodeLocation, timeout time.Duration, failer *failer.Failer, componentIndex int) *SetupNode {
return &SetupNode{
runner: newRunner(body, codeLocation, timeout, failer, types.SpecComponentTypeAfterEach, componentIndex),
}
}
func NewJustBeforeEachNode(body interface{}, codeLocation types.CodeLocation, timeout time.Duration, failer *failer.Failer, componentIndex int) *SetupNode {
return &SetupNode{
runner: newRunner(body, codeLocation, timeout, failer, types.SpecComponentTypeJustBeforeEach, componentIndex),
}
}

View file

@ -0,0 +1,40 @@
package leafnodes_test
import (
. "github.com/onsi/ginkgo"
"github.com/onsi/ginkgo/types"
. "github.com/onsi/gomega"
. "github.com/onsi/ginkgo/internal/leafnodes"
"github.com/onsi/ginkgo/internal/codelocation"
)
var _ = Describe("Setup Nodes", func() {
Describe("BeforeEachNodes", func() {
It("should report the correct type and code location", func() {
codeLocation := codelocation.New(0)
beforeEach := NewBeforeEachNode(func() {}, codeLocation, 0, nil, 3)
Ω(beforeEach.Type()).Should(Equal(types.SpecComponentTypeBeforeEach))
Ω(beforeEach.CodeLocation()).Should(Equal(codeLocation))
})
})
Describe("AfterEachNodes", func() {
It("should report the correct type and code location", func() {
codeLocation := codelocation.New(0)
afterEach := NewAfterEachNode(func() {}, codeLocation, 0, nil, 3)
Ω(afterEach.Type()).Should(Equal(types.SpecComponentTypeAfterEach))
Ω(afterEach.CodeLocation()).Should(Equal(codeLocation))
})
})
Describe("JustBeforeEachNodes", func() {
It("should report the correct type and code location", func() {
codeLocation := codelocation.New(0)
justBeforeEach := NewJustBeforeEachNode(func() {}, codeLocation, 0, nil, 3)
Ω(justBeforeEach.Type()).Should(Equal(types.SpecComponentTypeJustBeforeEach))
Ω(justBeforeEach.CodeLocation()).Should(Equal(codeLocation))
})
})
})

View file

@ -0,0 +1,356 @@
package leafnodes_test
import (
. "github.com/onsi/ginkgo"
. "github.com/onsi/ginkgo/internal/leafnodes"
. "github.com/onsi/gomega"
"reflect"
"time"
"github.com/onsi/ginkgo/internal/codelocation"
Failer "github.com/onsi/ginkgo/internal/failer"
"github.com/onsi/ginkgo/types"
)
type runnable interface {
Run() (outcome types.SpecState, failure types.SpecFailure)
CodeLocation() types.CodeLocation
}
func SynchronousSharedRunnerBehaviors(build func(body interface{}, timeout time.Duration, failer *Failer.Failer, componentCodeLocation types.CodeLocation) runnable, componentType types.SpecComponentType, componentIndex int) {
var (
outcome types.SpecState
failure types.SpecFailure
failer *Failer.Failer
componentCodeLocation types.CodeLocation
innerCodeLocation types.CodeLocation
didRun bool
)
BeforeEach(func() {
failer = Failer.New()
componentCodeLocation = codelocation.New(0)
innerCodeLocation = codelocation.New(0)
didRun = false
})
Describe("synchronous functions", func() {
Context("when the function passes", func() {
BeforeEach(func() {
outcome, failure = build(func() {
didRun = true
}, 0, failer, componentCodeLocation).Run()
})
It("should have a succesful outcome", func() {
Ω(didRun).Should(BeTrue())
Ω(outcome).Should(Equal(types.SpecStatePassed))
Ω(failure).Should(BeZero())
})
})
Context("when a failure occurs", func() {
BeforeEach(func() {
outcome, failure = build(func() {
didRun = true
failer.Fail("bam", innerCodeLocation)
panic("should not matter")
}, 0, failer, componentCodeLocation).Run()
})
It("should return the failure", func() {
Ω(didRun).Should(BeTrue())
Ω(outcome).Should(Equal(types.SpecStateFailed))
Ω(failure).Should(Equal(types.SpecFailure{
Message: "bam",
Location: innerCodeLocation,
ForwardedPanic: "",
ComponentIndex: componentIndex,
ComponentType: componentType,
ComponentCodeLocation: componentCodeLocation,
}))
})
})
Context("when a panic occurs", func() {
BeforeEach(func() {
outcome, failure = build(func() {
didRun = true
innerCodeLocation = codelocation.New(0)
panic("ack!")
}, 0, failer, componentCodeLocation).Run()
})
It("should return the panic", func() {
Ω(didRun).Should(BeTrue())
Ω(outcome).Should(Equal(types.SpecStatePanicked))
Ω(failure.ForwardedPanic).Should(Equal("ack!"))
})
})
Context("when a panic occurs with a nil value", func() {
BeforeEach(func() {
outcome, failure = build(func() {
didRun = true
innerCodeLocation = codelocation.New(0)
panic(nil)
}, 0, failer, componentCodeLocation).Run()
})
It("should return the nil-valued panic", func() {
Ω(didRun).Should(BeTrue())
Ω(outcome).Should(Equal(types.SpecStatePanicked))
Ω(failure.ForwardedPanic).Should(Equal("<nil>"))
})
})
})
}
func AsynchronousSharedRunnerBehaviors(build func(body interface{}, timeout time.Duration, failer *Failer.Failer, componentCodeLocation types.CodeLocation) runnable, componentType types.SpecComponentType, componentIndex int) {
var (
outcome types.SpecState
failure types.SpecFailure
failer *Failer.Failer
componentCodeLocation types.CodeLocation
innerCodeLocation types.CodeLocation
didRun bool
)
BeforeEach(func() {
failer = Failer.New()
componentCodeLocation = codelocation.New(0)
innerCodeLocation = codelocation.New(0)
didRun = false
})
Describe("asynchronous functions", func() {
var timeoutDuration time.Duration
BeforeEach(func() {
timeoutDuration = time.Duration(1 * float64(time.Second))
})
Context("when running", func() {
It("should run the function as a goroutine, and block until it's done", func() {
proveAsync := make(chan bool)
build(func(done Done) {
didRun = true
proveAsync <- true
close(done)
}, timeoutDuration, failer, componentCodeLocation).Run()
Eventually(proveAsync).Should(Receive(Equal(true)))
})
})
Context("when the function passes", func() {
BeforeEach(func() {
outcome, failure = build(func(done Done) {
didRun = true
close(done)
}, timeoutDuration, failer, componentCodeLocation).Run()
})
It("should have a succesful outcome", func() {
Ω(didRun).Should(BeTrue())
Ω(outcome).Should(Equal(types.SpecStatePassed))
Ω(failure).Should(BeZero())
})
})
Context("when the function fails", func() {
BeforeEach(func() {
outcome, failure = build(func(done Done) {
didRun = true
failer.Fail("bam", innerCodeLocation)
time.Sleep(20 * time.Millisecond)
panic("doesn't matter")
close(done)
}, 10*time.Millisecond, failer, componentCodeLocation).Run()
})
It("should return the failure", func() {
Ω(didRun).Should(BeTrue())
Ω(outcome).Should(Equal(types.SpecStateFailed))
Ω(failure).Should(Equal(types.SpecFailure{
Message: "bam",
Location: innerCodeLocation,
ForwardedPanic: "",
ComponentIndex: componentIndex,
ComponentType: componentType,
ComponentCodeLocation: componentCodeLocation,
}))
})
})
Context("when the function times out", func() {
var guard chan struct{}
BeforeEach(func() {
guard = make(chan struct{})
outcome, failure = build(func(done Done) {
didRun = true
time.Sleep(20 * time.Millisecond)
close(guard)
panic("doesn't matter")
close(done)
}, 10*time.Millisecond, failer, componentCodeLocation).Run()
})
It("should return the timeout", func() {
<-guard
Ω(didRun).Should(BeTrue())
Ω(outcome).Should(Equal(types.SpecStateTimedOut))
Ω(failure).Should(Equal(types.SpecFailure{
Message: "Timed out",
Location: componentCodeLocation,
ForwardedPanic: "",
ComponentIndex: componentIndex,
ComponentType: componentType,
ComponentCodeLocation: componentCodeLocation,
}))
})
})
Context("when the function panics", func() {
BeforeEach(func() {
outcome, failure = build(func(done Done) {
didRun = true
innerCodeLocation = codelocation.New(0)
panic("ack!")
}, 100*time.Millisecond, failer, componentCodeLocation).Run()
})
It("should return the panic", func() {
Ω(didRun).Should(BeTrue())
Ω(outcome).Should(Equal(types.SpecStatePanicked))
Ω(failure.ForwardedPanic).Should(Equal("ack!"))
})
})
Context("when the function panics with a nil value", func() {
BeforeEach(func() {
outcome, failure = build(func(done Done) {
didRun = true
innerCodeLocation = codelocation.New(0)
panic(nil)
}, 100*time.Millisecond, failer, componentCodeLocation).Run()
})
It("should return the nil-valued panic", func() {
Ω(didRun).Should(BeTrue())
Ω(outcome).Should(Equal(types.SpecStatePanicked))
Ω(failure.ForwardedPanic).Should(Equal("<nil>"))
})
})
})
}
func InvalidSharedRunnerBehaviors(build func(body interface{}, timeout time.Duration, failer *Failer.Failer, componentCodeLocation types.CodeLocation) runnable, componentType types.SpecComponentType) {
var (
failer *Failer.Failer
componentCodeLocation types.CodeLocation
)
BeforeEach(func() {
failer = Failer.New()
componentCodeLocation = codelocation.New(0)
})
Describe("invalid functions", func() {
Context("when passed something that's not a function", func() {
It("should panic", func() {
Ω(func() {
build("not a function", 0, failer, componentCodeLocation)
}).Should(Panic())
})
})
Context("when the function takes the wrong kind of argument", func() {
It("should panic", func() {
Ω(func() {
build(func(oops string) {}, 0, failer, componentCodeLocation)
}).Should(Panic())
})
})
Context("when the function takes more than one argument", func() {
It("should panic", func() {
Ω(func() {
build(func(done Done, oops string) {}, 0, failer, componentCodeLocation)
}).Should(Panic())
})
})
})
}
var _ = Describe("Shared RunnableNode behavior", func() {
Describe("It Nodes", func() {
build := func(body interface{}, timeout time.Duration, failer *Failer.Failer, componentCodeLocation types.CodeLocation) runnable {
return NewItNode("", body, types.FlagTypeFocused, componentCodeLocation, timeout, failer, 3)
}
SynchronousSharedRunnerBehaviors(build, types.SpecComponentTypeIt, 3)
AsynchronousSharedRunnerBehaviors(build, types.SpecComponentTypeIt, 3)
InvalidSharedRunnerBehaviors(build, types.SpecComponentTypeIt)
})
Describe("Measure Nodes", func() {
build := func(body interface{}, _ time.Duration, failer *Failer.Failer, componentCodeLocation types.CodeLocation) runnable {
return NewMeasureNode("", func(Benchmarker) {
reflect.ValueOf(body).Call([]reflect.Value{})
}, types.FlagTypeFocused, componentCodeLocation, 10, failer, 3)
}
SynchronousSharedRunnerBehaviors(build, types.SpecComponentTypeMeasure, 3)
})
Describe("BeforeEach Nodes", func() {
build := func(body interface{}, timeout time.Duration, failer *Failer.Failer, componentCodeLocation types.CodeLocation) runnable {
return NewBeforeEachNode(body, componentCodeLocation, timeout, failer, 3)
}
SynchronousSharedRunnerBehaviors(build, types.SpecComponentTypeBeforeEach, 3)
AsynchronousSharedRunnerBehaviors(build, types.SpecComponentTypeBeforeEach, 3)
InvalidSharedRunnerBehaviors(build, types.SpecComponentTypeBeforeEach)
})
Describe("AfterEach Nodes", func() {
build := func(body interface{}, timeout time.Duration, failer *Failer.Failer, componentCodeLocation types.CodeLocation) runnable {
return NewAfterEachNode(body, componentCodeLocation, timeout, failer, 3)
}
SynchronousSharedRunnerBehaviors(build, types.SpecComponentTypeAfterEach, 3)
AsynchronousSharedRunnerBehaviors(build, types.SpecComponentTypeAfterEach, 3)
InvalidSharedRunnerBehaviors(build, types.SpecComponentTypeAfterEach)
})
Describe("JustBeforeEach Nodes", func() {
build := func(body interface{}, timeout time.Duration, failer *Failer.Failer, componentCodeLocation types.CodeLocation) runnable {
return NewJustBeforeEachNode(body, componentCodeLocation, timeout, failer, 3)
}
SynchronousSharedRunnerBehaviors(build, types.SpecComponentTypeJustBeforeEach, 3)
AsynchronousSharedRunnerBehaviors(build, types.SpecComponentTypeJustBeforeEach, 3)
InvalidSharedRunnerBehaviors(build, types.SpecComponentTypeJustBeforeEach)
})
})

View file

@ -0,0 +1,54 @@
package leafnodes
import (
"github.com/onsi/ginkgo/internal/failer"
"github.com/onsi/ginkgo/types"
"time"
)
type SuiteNode interface {
Run(parallelNode int, parallelTotal int, syncHost string) bool
Passed() bool
Summary() *types.SetupSummary
}
type simpleSuiteNode struct {
runner *runner
outcome types.SpecState
failure types.SpecFailure
runTime time.Duration
}
func (node *simpleSuiteNode) Run(parallelNode int, parallelTotal int, syncHost string) bool {
t := time.Now()
node.outcome, node.failure = node.runner.run()
node.runTime = time.Since(t)
return node.outcome == types.SpecStatePassed
}
func (node *simpleSuiteNode) Passed() bool {
return node.outcome == types.SpecStatePassed
}
func (node *simpleSuiteNode) Summary() *types.SetupSummary {
return &types.SetupSummary{
ComponentType: node.runner.nodeType,
CodeLocation: node.runner.codeLocation,
State: node.outcome,
RunTime: node.runTime,
Failure: node.failure,
}
}
func NewBeforeSuiteNode(body interface{}, codeLocation types.CodeLocation, timeout time.Duration, failer *failer.Failer) SuiteNode {
return &simpleSuiteNode{
runner: newRunner(body, codeLocation, timeout, failer, types.SpecComponentTypeBeforeSuite, 0),
}
}
func NewAfterSuiteNode(body interface{}, codeLocation types.CodeLocation, timeout time.Duration, failer *failer.Failer) SuiteNode {
return &simpleSuiteNode{
runner: newRunner(body, codeLocation, timeout, failer, types.SpecComponentTypeAfterSuite, 0),
}
}

View file

@ -0,0 +1,230 @@
package leafnodes_test
import (
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
. "github.com/onsi/ginkgo/internal/leafnodes"
"time"
"github.com/onsi/ginkgo/internal/codelocation"
Failer "github.com/onsi/ginkgo/internal/failer"
"github.com/onsi/ginkgo/types"
)
var _ = Describe("SuiteNodes", func() {
Describe("BeforeSuite nodes", func() {
var befSuite SuiteNode
var failer *Failer.Failer
var codeLocation types.CodeLocation
var innerCodeLocation types.CodeLocation
var outcome bool
BeforeEach(func() {
failer = Failer.New()
codeLocation = codelocation.New(0)
innerCodeLocation = codelocation.New(0)
})
Context("when the body passes", func() {
BeforeEach(func() {
befSuite = NewBeforeSuiteNode(func() {
time.Sleep(10 * time.Millisecond)
}, codeLocation, 0, failer)
outcome = befSuite.Run(0, 0, "")
})
It("should return true when run and report as passed", func() {
Ω(outcome).Should(BeTrue())
Ω(befSuite.Passed()).Should(BeTrue())
})
It("should have the correct summary", func() {
summary := befSuite.Summary()
Ω(summary.ComponentType).Should(Equal(types.SpecComponentTypeBeforeSuite))
Ω(summary.CodeLocation).Should(Equal(codeLocation))
Ω(summary.State).Should(Equal(types.SpecStatePassed))
Ω(summary.RunTime).Should(BeNumerically(">=", 10*time.Millisecond))
Ω(summary.Failure).Should(BeZero())
})
})
Context("when the body fails", func() {
BeforeEach(func() {
befSuite = NewBeforeSuiteNode(func() {
failer.Fail("oops", innerCodeLocation)
}, codeLocation, 0, failer)
outcome = befSuite.Run(0, 0, "")
})
It("should return false when run and report as failed", func() {
Ω(outcome).Should(BeFalse())
Ω(befSuite.Passed()).Should(BeFalse())
})
It("should have the correct summary", func() {
summary := befSuite.Summary()
Ω(summary.State).Should(Equal(types.SpecStateFailed))
Ω(summary.Failure.Message).Should(Equal("oops"))
Ω(summary.Failure.Location).Should(Equal(innerCodeLocation))
Ω(summary.Failure.ForwardedPanic).Should(BeEmpty())
Ω(summary.Failure.ComponentIndex).Should(Equal(0))
Ω(summary.Failure.ComponentType).Should(Equal(types.SpecComponentTypeBeforeSuite))
Ω(summary.Failure.ComponentCodeLocation).Should(Equal(codeLocation))
})
})
Context("when the body times out", func() {
BeforeEach(func() {
befSuite = NewBeforeSuiteNode(func(done Done) {
}, codeLocation, time.Millisecond, failer)
outcome = befSuite.Run(0, 0, "")
})
It("should return false when run and report as failed", func() {
Ω(outcome).Should(BeFalse())
Ω(befSuite.Passed()).Should(BeFalse())
})
It("should have the correct summary", func() {
summary := befSuite.Summary()
Ω(summary.State).Should(Equal(types.SpecStateTimedOut))
Ω(summary.Failure.ForwardedPanic).Should(BeEmpty())
Ω(summary.Failure.ComponentIndex).Should(Equal(0))
Ω(summary.Failure.ComponentType).Should(Equal(types.SpecComponentTypeBeforeSuite))
Ω(summary.Failure.ComponentCodeLocation).Should(Equal(codeLocation))
})
})
Context("when the body panics", func() {
BeforeEach(func() {
befSuite = NewBeforeSuiteNode(func() {
panic("bam")
}, codeLocation, 0, failer)
outcome = befSuite.Run(0, 0, "")
})
It("should return false when run and report as failed", func() {
Ω(outcome).Should(BeFalse())
Ω(befSuite.Passed()).Should(BeFalse())
})
It("should have the correct summary", func() {
summary := befSuite.Summary()
Ω(summary.State).Should(Equal(types.SpecStatePanicked))
Ω(summary.Failure.ForwardedPanic).Should(Equal("bam"))
Ω(summary.Failure.ComponentIndex).Should(Equal(0))
Ω(summary.Failure.ComponentType).Should(Equal(types.SpecComponentTypeBeforeSuite))
Ω(summary.Failure.ComponentCodeLocation).Should(Equal(codeLocation))
})
})
})
Describe("AfterSuite nodes", func() {
var aftSuite SuiteNode
var failer *Failer.Failer
var codeLocation types.CodeLocation
var innerCodeLocation types.CodeLocation
var outcome bool
BeforeEach(func() {
failer = Failer.New()
codeLocation = codelocation.New(0)
innerCodeLocation = codelocation.New(0)
})
Context("when the body passes", func() {
BeforeEach(func() {
aftSuite = NewAfterSuiteNode(func() {
time.Sleep(10 * time.Millisecond)
}, codeLocation, 0, failer)
outcome = aftSuite.Run(0, 0, "")
})
It("should return true when run and report as passed", func() {
Ω(outcome).Should(BeTrue())
Ω(aftSuite.Passed()).Should(BeTrue())
})
It("should have the correct summary", func() {
summary := aftSuite.Summary()
Ω(summary.ComponentType).Should(Equal(types.SpecComponentTypeAfterSuite))
Ω(summary.CodeLocation).Should(Equal(codeLocation))
Ω(summary.State).Should(Equal(types.SpecStatePassed))
Ω(summary.RunTime).Should(BeNumerically(">=", 10*time.Millisecond))
Ω(summary.Failure).Should(BeZero())
})
})
Context("when the body fails", func() {
BeforeEach(func() {
aftSuite = NewAfterSuiteNode(func() {
failer.Fail("oops", innerCodeLocation)
}, codeLocation, 0, failer)
outcome = aftSuite.Run(0, 0, "")
})
It("should return false when run and report as failed", func() {
Ω(outcome).Should(BeFalse())
Ω(aftSuite.Passed()).Should(BeFalse())
})
It("should have the correct summary", func() {
summary := aftSuite.Summary()
Ω(summary.State).Should(Equal(types.SpecStateFailed))
Ω(summary.Failure.Message).Should(Equal("oops"))
Ω(summary.Failure.Location).Should(Equal(innerCodeLocation))
Ω(summary.Failure.ForwardedPanic).Should(BeEmpty())
Ω(summary.Failure.ComponentIndex).Should(Equal(0))
Ω(summary.Failure.ComponentType).Should(Equal(types.SpecComponentTypeAfterSuite))
Ω(summary.Failure.ComponentCodeLocation).Should(Equal(codeLocation))
})
})
Context("when the body times out", func() {
BeforeEach(func() {
aftSuite = NewAfterSuiteNode(func(done Done) {
}, codeLocation, time.Millisecond, failer)
outcome = aftSuite.Run(0, 0, "")
})
It("should return false when run and report as failed", func() {
Ω(outcome).Should(BeFalse())
Ω(aftSuite.Passed()).Should(BeFalse())
})
It("should have the correct summary", func() {
summary := aftSuite.Summary()
Ω(summary.State).Should(Equal(types.SpecStateTimedOut))
Ω(summary.Failure.ForwardedPanic).Should(BeEmpty())
Ω(summary.Failure.ComponentIndex).Should(Equal(0))
Ω(summary.Failure.ComponentType).Should(Equal(types.SpecComponentTypeAfterSuite))
Ω(summary.Failure.ComponentCodeLocation).Should(Equal(codeLocation))
})
})
Context("when the body panics", func() {
BeforeEach(func() {
aftSuite = NewAfterSuiteNode(func() {
panic("bam")
}, codeLocation, 0, failer)
outcome = aftSuite.Run(0, 0, "")
})
It("should return false when run and report as failed", func() {
Ω(outcome).Should(BeFalse())
Ω(aftSuite.Passed()).Should(BeFalse())
})
It("should have the correct summary", func() {
summary := aftSuite.Summary()
Ω(summary.State).Should(Equal(types.SpecStatePanicked))
Ω(summary.Failure.ForwardedPanic).Should(Equal("bam"))
Ω(summary.Failure.ComponentIndex).Should(Equal(0))
Ω(summary.Failure.ComponentType).Should(Equal(types.SpecComponentTypeAfterSuite))
Ω(summary.Failure.ComponentCodeLocation).Should(Equal(codeLocation))
})
})
})
})

View file

@ -0,0 +1,89 @@
package leafnodes
import (
"encoding/json"
"github.com/onsi/ginkgo/internal/failer"
"github.com/onsi/ginkgo/types"
"io/ioutil"
"net/http"
"time"
)
type synchronizedAfterSuiteNode struct {
runnerA *runner
runnerB *runner
outcome types.SpecState
failure types.SpecFailure
runTime time.Duration
}
func NewSynchronizedAfterSuiteNode(bodyA interface{}, bodyB interface{}, codeLocation types.CodeLocation, timeout time.Duration, failer *failer.Failer) SuiteNode {
return &synchronizedAfterSuiteNode{
runnerA: newRunner(bodyA, codeLocation, timeout, failer, types.SpecComponentTypeAfterSuite, 0),
runnerB: newRunner(bodyB, codeLocation, timeout, failer, types.SpecComponentTypeAfterSuite, 0),
}
}
func (node *synchronizedAfterSuiteNode) Run(parallelNode int, parallelTotal int, syncHost string) bool {
node.outcome, node.failure = node.runnerA.run()
if parallelNode == 1 {
if parallelTotal > 1 {
node.waitUntilOtherNodesAreDone(syncHost)
}
outcome, failure := node.runnerB.run()
if node.outcome == types.SpecStatePassed {
node.outcome, node.failure = outcome, failure
}
}
return node.outcome == types.SpecStatePassed
}
func (node *synchronizedAfterSuiteNode) Passed() bool {
return node.outcome == types.SpecStatePassed
}
func (node *synchronizedAfterSuiteNode) Summary() *types.SetupSummary {
return &types.SetupSummary{
ComponentType: node.runnerA.nodeType,
CodeLocation: node.runnerA.codeLocation,
State: node.outcome,
RunTime: node.runTime,
Failure: node.failure,
}
}
func (node *synchronizedAfterSuiteNode) waitUntilOtherNodesAreDone(syncHost string) {
for {
if node.canRun(syncHost) {
return
}
time.Sleep(50 * time.Millisecond)
}
}
func (node *synchronizedAfterSuiteNode) canRun(syncHost string) bool {
resp, err := http.Get(syncHost + "/RemoteAfterSuiteData")
if err != nil || resp.StatusCode != http.StatusOK {
return false
}
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return false
}
resp.Body.Close()
afterSuiteData := types.RemoteAfterSuiteData{}
err = json.Unmarshal(body, &afterSuiteData)
if err != nil {
return false
}
return afterSuiteData.CanRun
}

View file

@ -0,0 +1,196 @@
package leafnodes_test
import (
. "github.com/onsi/ginkgo"
. "github.com/onsi/ginkgo/internal/leafnodes"
"github.com/onsi/ginkgo/types"
. "github.com/onsi/gomega"
"sync"
"github.com/onsi/gomega/ghttp"
"net/http"
"github.com/onsi/ginkgo/internal/codelocation"
Failer "github.com/onsi/ginkgo/internal/failer"
"time"
)
var _ = Describe("SynchronizedAfterSuiteNode", func() {
var failer *Failer.Failer
var node SuiteNode
var codeLocation types.CodeLocation
var innerCodeLocation types.CodeLocation
var outcome bool
var server *ghttp.Server
var things []string
var lock *sync.Mutex
BeforeEach(func() {
things = []string{}
server = ghttp.NewServer()
codeLocation = codelocation.New(0)
innerCodeLocation = codelocation.New(0)
failer = Failer.New()
lock = &sync.Mutex{}
})
AfterEach(func() {
server.Close()
})
newNode := func(bodyA interface{}, bodyB interface{}) SuiteNode {
return NewSynchronizedAfterSuiteNode(bodyA, bodyB, codeLocation, time.Millisecond, failer)
}
ranThing := func(thing string) {
lock.Lock()
defer lock.Unlock()
things = append(things, thing)
}
thingsThatRan := func() []string {
lock.Lock()
defer lock.Unlock()
return things
}
Context("when not running in parallel", func() {
Context("when all is well", func() {
BeforeEach(func() {
node = newNode(func() {
ranThing("A")
}, func() {
ranThing("B")
})
outcome = node.Run(1, 1, server.URL())
})
It("should run A, then B", func() {
Ω(thingsThatRan()).Should(Equal([]string{"A", "B"}))
})
It("should report success", func() {
Ω(outcome).Should(BeTrue())
Ω(node.Passed()).Should(BeTrue())
Ω(node.Summary().State).Should(Equal(types.SpecStatePassed))
})
})
Context("when A fails", func() {
BeforeEach(func() {
node = newNode(func() {
ranThing("A")
failer.Fail("bam", innerCodeLocation)
}, func() {
ranThing("B")
})
outcome = node.Run(1, 1, server.URL())
})
It("should still run B", func() {
Ω(thingsThatRan()).Should(Equal([]string{"A", "B"}))
})
It("should report failure", func() {
Ω(outcome).Should(BeFalse())
Ω(node.Passed()).Should(BeFalse())
Ω(node.Summary().State).Should(Equal(types.SpecStateFailed))
})
})
Context("when B fails", func() {
BeforeEach(func() {
node = newNode(func() {
ranThing("A")
}, func() {
ranThing("B")
failer.Fail("bam", innerCodeLocation)
})
outcome = node.Run(1, 1, server.URL())
})
It("should run all the things", func() {
Ω(thingsThatRan()).Should(Equal([]string{"A", "B"}))
})
It("should report failure", func() {
Ω(outcome).Should(BeFalse())
Ω(node.Passed()).Should(BeFalse())
Ω(node.Summary().State).Should(Equal(types.SpecStateFailed))
})
})
})
Context("when running in parallel", func() {
Context("as the first node", func() {
BeforeEach(func() {
server.AppendHandlers(ghttp.CombineHandlers(
ghttp.VerifyRequest("GET", "/RemoteAfterSuiteData"),
func(writer http.ResponseWriter, request *http.Request) {
ranThing("Request1")
},
ghttp.RespondWithJSONEncoded(200, types.RemoteAfterSuiteData{false}),
), ghttp.CombineHandlers(
ghttp.VerifyRequest("GET", "/RemoteAfterSuiteData"),
func(writer http.ResponseWriter, request *http.Request) {
ranThing("Request2")
},
ghttp.RespondWithJSONEncoded(200, types.RemoteAfterSuiteData{false}),
), ghttp.CombineHandlers(
ghttp.VerifyRequest("GET", "/RemoteAfterSuiteData"),
func(writer http.ResponseWriter, request *http.Request) {
ranThing("Request3")
},
ghttp.RespondWithJSONEncoded(200, types.RemoteAfterSuiteData{true}),
))
node = newNode(func() {
ranThing("A")
}, func() {
ranThing("B")
})
outcome = node.Run(1, 3, server.URL())
})
It("should run A and, when the server says its time, run B", func() {
Ω(thingsThatRan()).Should(Equal([]string{"A", "Request1", "Request2", "Request3", "B"}))
})
It("should report success", func() {
Ω(outcome).Should(BeTrue())
Ω(node.Passed()).Should(BeTrue())
Ω(node.Summary().State).Should(Equal(types.SpecStatePassed))
})
})
Context("as any other node", func() {
BeforeEach(func() {
node = newNode(func() {
ranThing("A")
}, func() {
ranThing("B")
})
outcome = node.Run(2, 3, server.URL())
})
It("should run A, and not run B", func() {
Ω(thingsThatRan()).Should(Equal([]string{"A"}))
})
It("should not talk to the server", func() {
Ω(server.ReceivedRequests()).Should(BeEmpty())
})
It("should report success", func() {
Ω(outcome).Should(BeTrue())
Ω(node.Passed()).Should(BeTrue())
Ω(node.Summary().State).Should(Equal(types.SpecStatePassed))
})
})
})
})

View file

@ -0,0 +1,182 @@
package leafnodes
import (
"bytes"
"encoding/json"
"github.com/onsi/ginkgo/internal/failer"
"github.com/onsi/ginkgo/types"
"io/ioutil"
"net/http"
"reflect"
"time"
)
type synchronizedBeforeSuiteNode struct {
runnerA *runner
runnerB *runner
data []byte
outcome types.SpecState
failure types.SpecFailure
runTime time.Duration
}
func NewSynchronizedBeforeSuiteNode(bodyA interface{}, bodyB interface{}, codeLocation types.CodeLocation, timeout time.Duration, failer *failer.Failer) SuiteNode {
node := &synchronizedBeforeSuiteNode{}
node.runnerA = newRunner(node.wrapA(bodyA), codeLocation, timeout, failer, types.SpecComponentTypeBeforeSuite, 0)
node.runnerB = newRunner(node.wrapB(bodyB), codeLocation, timeout, failer, types.SpecComponentTypeBeforeSuite, 0)
return node
}
func (node *synchronizedBeforeSuiteNode) Run(parallelNode int, parallelTotal int, syncHost string) bool {
t := time.Now()
defer func() {
node.runTime = time.Since(t)
}()
if parallelNode == 1 {
node.outcome, node.failure = node.runA(parallelTotal, syncHost)
} else {
node.outcome, node.failure = node.waitForA(syncHost)
}
if node.outcome != types.SpecStatePassed {
return false
}
node.outcome, node.failure = node.runnerB.run()
return node.outcome == types.SpecStatePassed
}
func (node *synchronizedBeforeSuiteNode) runA(parallelTotal int, syncHost string) (types.SpecState, types.SpecFailure) {
outcome, failure := node.runnerA.run()
if parallelTotal > 1 {
state := types.RemoteBeforeSuiteStatePassed
if outcome != types.SpecStatePassed {
state = types.RemoteBeforeSuiteStateFailed
}
json := (types.RemoteBeforeSuiteData{
Data: node.data,
State: state,
}).ToJSON()
http.Post(syncHost+"/BeforeSuiteState", "application/json", bytes.NewBuffer(json))
}
return outcome, failure
}
func (node *synchronizedBeforeSuiteNode) waitForA(syncHost string) (types.SpecState, types.SpecFailure) {
failure := func(message string) types.SpecFailure {
return types.SpecFailure{
Message: message,
Location: node.runnerA.codeLocation,
ComponentType: node.runnerA.nodeType,
ComponentIndex: node.runnerA.componentIndex,
ComponentCodeLocation: node.runnerA.codeLocation,
}
}
for {
resp, err := http.Get(syncHost + "/BeforeSuiteState")
if err != nil || resp.StatusCode != http.StatusOK {
return types.SpecStateFailed, failure("Failed to fetch BeforeSuite state")
}
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return types.SpecStateFailed, failure("Failed to read BeforeSuite state")
}
resp.Body.Close()
beforeSuiteData := types.RemoteBeforeSuiteData{}
err = json.Unmarshal(body, &beforeSuiteData)
if err != nil {
return types.SpecStateFailed, failure("Failed to decode BeforeSuite state")
}
switch beforeSuiteData.State {
case types.RemoteBeforeSuiteStatePassed:
node.data = beforeSuiteData.Data
return types.SpecStatePassed, types.SpecFailure{}
case types.RemoteBeforeSuiteStateFailed:
return types.SpecStateFailed, failure("BeforeSuite on Node 1 failed")
case types.RemoteBeforeSuiteStateDisappeared:
return types.SpecStateFailed, failure("Node 1 disappeared before completing BeforeSuite")
}
time.Sleep(50 * time.Millisecond)
}
return types.SpecStateFailed, failure("Shouldn't get here!")
}
func (node *synchronizedBeforeSuiteNode) Passed() bool {
return node.outcome == types.SpecStatePassed
}
func (node *synchronizedBeforeSuiteNode) Summary() *types.SetupSummary {
return &types.SetupSummary{
ComponentType: node.runnerA.nodeType,
CodeLocation: node.runnerA.codeLocation,
State: node.outcome,
RunTime: node.runTime,
Failure: node.failure,
}
}
func (node *synchronizedBeforeSuiteNode) wrapA(bodyA interface{}) interface{} {
typeA := reflect.TypeOf(bodyA)
if typeA.Kind() != reflect.Func {
panic("SynchronizedBeforeSuite expects a function as its first argument")
}
takesNothing := typeA.NumIn() == 0
takesADoneChannel := typeA.NumIn() == 1 && typeA.In(0).Kind() == reflect.Chan && typeA.In(0).Elem().Kind() == reflect.Interface
returnsBytes := typeA.NumOut() == 1 && typeA.Out(0).Kind() == reflect.Slice && typeA.Out(0).Elem().Kind() == reflect.Uint8
if !((takesNothing || takesADoneChannel) && returnsBytes) {
panic("SynchronizedBeforeSuite's first argument should be a function that returns []byte and either takes no arguments or takes a Done channel.")
}
if takesADoneChannel {
return func(done chan<- interface{}) {
out := reflect.ValueOf(bodyA).Call([]reflect.Value{reflect.ValueOf(done)})
node.data = out[0].Interface().([]byte)
}
}
return func() {
out := reflect.ValueOf(bodyA).Call([]reflect.Value{})
node.data = out[0].Interface().([]byte)
}
}
func (node *synchronizedBeforeSuiteNode) wrapB(bodyB interface{}) interface{} {
typeB := reflect.TypeOf(bodyB)
if typeB.Kind() != reflect.Func {
panic("SynchronizedBeforeSuite expects a function as its second argument")
}
returnsNothing := typeB.NumOut() == 0
takesBytesOnly := typeB.NumIn() == 1 && typeB.In(0).Kind() == reflect.Slice && typeB.In(0).Elem().Kind() == reflect.Uint8
takesBytesAndDone := typeB.NumIn() == 2 &&
typeB.In(0).Kind() == reflect.Slice && typeB.In(0).Elem().Kind() == reflect.Uint8 &&
typeB.In(1).Kind() == reflect.Chan && typeB.In(1).Elem().Kind() == reflect.Interface
if !((takesBytesOnly || takesBytesAndDone) && returnsNothing) {
panic("SynchronizedBeforeSuite's second argument should be a function that returns nothing and either takes []byte or ([]byte, Done)")
}
if takesBytesAndDone {
return func(done chan<- interface{}) {
reflect.ValueOf(bodyB).Call([]reflect.Value{reflect.ValueOf(node.data), reflect.ValueOf(done)})
}
}
return func() {
reflect.ValueOf(bodyB).Call([]reflect.Value{reflect.ValueOf(node.data)})
}
}

View file

@ -0,0 +1,445 @@
package leafnodes_test
import (
. "github.com/onsi/ginkgo"
. "github.com/onsi/ginkgo/internal/leafnodes"
. "github.com/onsi/gomega"
"github.com/onsi/gomega/ghttp"
"net/http"
"github.com/onsi/ginkgo/internal/codelocation"
Failer "github.com/onsi/ginkgo/internal/failer"
"github.com/onsi/ginkgo/types"
"time"
)
var _ = Describe("SynchronizedBeforeSuiteNode", func() {
var failer *Failer.Failer
var node SuiteNode
var codeLocation types.CodeLocation
var innerCodeLocation types.CodeLocation
var outcome bool
var server *ghttp.Server
BeforeEach(func() {
server = ghttp.NewServer()
codeLocation = codelocation.New(0)
innerCodeLocation = codelocation.New(0)
failer = Failer.New()
})
AfterEach(func() {
server.Close()
})
newNode := func(bodyA interface{}, bodyB interface{}) SuiteNode {
return NewSynchronizedBeforeSuiteNode(bodyA, bodyB, codeLocation, time.Millisecond, failer)
}
Describe("when not running in parallel", func() {
Context("when all is well", func() {
var data []byte
BeforeEach(func() {
data = nil
node = newNode(func() []byte {
return []byte("my data")
}, func(d []byte) {
data = d
})
outcome = node.Run(1, 1, server.URL())
})
It("should run A, then B passing the output from A to B", func() {
Ω(data).Should(Equal([]byte("my data")))
})
It("should report success", func() {
Ω(outcome).Should(BeTrue())
Ω(node.Passed()).Should(BeTrue())
Ω(node.Summary().State).Should(Equal(types.SpecStatePassed))
})
})
Context("when A fails", func() {
var ranB bool
BeforeEach(func() {
ranB = false
node = newNode(func() []byte {
failer.Fail("boom", innerCodeLocation)
return nil
}, func([]byte) {
ranB = true
})
outcome = node.Run(1, 1, server.URL())
})
It("should not run B", func() {
Ω(ranB).Should(BeFalse())
})
It("should report failure", func() {
Ω(outcome).Should(BeFalse())
Ω(node.Passed()).Should(BeFalse())
Ω(node.Summary().State).Should(Equal(types.SpecStateFailed))
})
})
Context("when B fails", func() {
BeforeEach(func() {
node = newNode(func() []byte {
return nil
}, func([]byte) {
failer.Fail("boom", innerCodeLocation)
})
outcome = node.Run(1, 1, server.URL())
})
It("should report failure", func() {
Ω(outcome).Should(BeFalse())
Ω(node.Passed()).Should(BeFalse())
Ω(node.Summary().State).Should(Equal(types.SpecStateFailed))
})
})
Context("when A times out", func() {
var ranB bool
BeforeEach(func() {
ranB = false
node = newNode(func(Done) []byte {
time.Sleep(time.Second)
return nil
}, func([]byte) {
ranB = true
})
outcome = node.Run(1, 1, server.URL())
})
It("should not run B", func() {
Ω(ranB).Should(BeFalse())
})
It("should report failure", func() {
Ω(outcome).Should(BeFalse())
Ω(node.Passed()).Should(BeFalse())
Ω(node.Summary().State).Should(Equal(types.SpecStateTimedOut))
})
})
Context("when B times out", func() {
BeforeEach(func() {
node = newNode(func() []byte {
return nil
}, func([]byte, Done) {
time.Sleep(time.Second)
})
outcome = node.Run(1, 1, server.URL())
})
It("should report failure", func() {
Ω(outcome).Should(BeFalse())
Ω(node.Passed()).Should(BeFalse())
Ω(node.Summary().State).Should(Equal(types.SpecStateTimedOut))
})
})
})
Describe("when running in parallel", func() {
var ranB bool
var parallelNode, parallelTotal int
BeforeEach(func() {
ranB = false
parallelNode, parallelTotal = 1, 3
})
Context("as the first node, it runs A", func() {
var expectedState types.RemoteBeforeSuiteData
BeforeEach(func() {
parallelNode, parallelTotal = 1, 3
})
JustBeforeEach(func() {
server.AppendHandlers(ghttp.CombineHandlers(
ghttp.VerifyRequest("POST", "/BeforeSuiteState"),
ghttp.VerifyJSONRepresenting(expectedState),
))
outcome = node.Run(parallelNode, parallelTotal, server.URL())
})
Context("when A succeeds", func() {
BeforeEach(func() {
expectedState = types.RemoteBeforeSuiteData{[]byte("my data"), types.RemoteBeforeSuiteStatePassed}
node = newNode(func() []byte {
return []byte("my data")
}, func([]byte) {
ranB = true
})
})
It("should post about A succeeding", func() {
Ω(server.ReceivedRequests()).Should(HaveLen(1))
})
It("should run B", func() {
Ω(ranB).Should(BeTrue())
})
It("should report success", func() {
Ω(outcome).Should(BeTrue())
})
})
Context("when A fails", func() {
BeforeEach(func() {
expectedState = types.RemoteBeforeSuiteData{nil, types.RemoteBeforeSuiteStateFailed}
node = newNode(func() []byte {
panic("BAM")
return []byte("my data")
}, func([]byte) {
ranB = true
})
})
It("should post about A failing", func() {
Ω(server.ReceivedRequests()).Should(HaveLen(1))
})
It("should not run B", func() {
Ω(ranB).Should(BeFalse())
})
It("should report failure", func() {
Ω(outcome).Should(BeFalse())
})
})
})
Context("as the Nth node", func() {
var statusCode int
var response interface{}
var ranA bool
var bData []byte
BeforeEach(func() {
ranA = false
bData = nil
statusCode = http.StatusOK
server.AppendHandlers(ghttp.CombineHandlers(
ghttp.VerifyRequest("GET", "/BeforeSuiteState"),
ghttp.RespondWith(http.StatusOK, string((types.RemoteBeforeSuiteData{nil, types.RemoteBeforeSuiteStatePending}).ToJSON())),
), ghttp.CombineHandlers(
ghttp.VerifyRequest("GET", "/BeforeSuiteState"),
ghttp.RespondWith(http.StatusOK, string((types.RemoteBeforeSuiteData{nil, types.RemoteBeforeSuiteStatePending}).ToJSON())),
), ghttp.CombineHandlers(
ghttp.VerifyRequest("GET", "/BeforeSuiteState"),
ghttp.RespondWithJSONEncodedPtr(&statusCode, &response),
))
node = newNode(func() []byte {
ranA = true
return nil
}, func(data []byte) {
bData = data
})
parallelNode, parallelTotal = 2, 3
})
Context("when A on node1 succeeds", func() {
BeforeEach(func() {
response = types.RemoteBeforeSuiteData{[]byte("my data"), types.RemoteBeforeSuiteStatePassed}
outcome = node.Run(parallelNode, parallelTotal, server.URL())
})
It("should not run A", func() {
Ω(ranA).Should(BeFalse())
})
It("should poll for A", func() {
Ω(server.ReceivedRequests()).Should(HaveLen(3))
})
It("should run B when the polling succeeds", func() {
Ω(bData).Should(Equal([]byte("my data")))
})
It("should succeed", func() {
Ω(outcome).Should(BeTrue())
Ω(node.Passed()).Should(BeTrue())
})
})
Context("when A on node1 fails", func() {
BeforeEach(func() {
response = types.RemoteBeforeSuiteData{[]byte("my data"), types.RemoteBeforeSuiteStateFailed}
outcome = node.Run(parallelNode, parallelTotal, server.URL())
})
It("should not run A", func() {
Ω(ranA).Should(BeFalse())
})
It("should poll for A", func() {
Ω(server.ReceivedRequests()).Should(HaveLen(3))
})
It("should not run B", func() {
Ω(bData).Should(BeNil())
})
It("should fail", func() {
Ω(outcome).Should(BeFalse())
Ω(node.Passed()).Should(BeFalse())
summary := node.Summary()
Ω(summary.State).Should(Equal(types.SpecStateFailed))
Ω(summary.Failure.Message).Should(Equal("BeforeSuite on Node 1 failed"))
Ω(summary.Failure.Location).Should(Equal(codeLocation))
Ω(summary.Failure.ComponentType).Should(Equal(types.SpecComponentTypeBeforeSuite))
Ω(summary.Failure.ComponentIndex).Should(Equal(0))
Ω(summary.Failure.ComponentCodeLocation).Should(Equal(codeLocation))
})
})
Context("when node1 disappears", func() {
BeforeEach(func() {
response = types.RemoteBeforeSuiteData{[]byte("my data"), types.RemoteBeforeSuiteStateDisappeared}
outcome = node.Run(parallelNode, parallelTotal, server.URL())
})
It("should not run A", func() {
Ω(ranA).Should(BeFalse())
})
It("should poll for A", func() {
Ω(server.ReceivedRequests()).Should(HaveLen(3))
})
It("should not run B", func() {
Ω(bData).Should(BeNil())
})
It("should fail", func() {
Ω(outcome).Should(BeFalse())
Ω(node.Passed()).Should(BeFalse())
summary := node.Summary()
Ω(summary.State).Should(Equal(types.SpecStateFailed))
Ω(summary.Failure.Message).Should(Equal("Node 1 disappeared before completing BeforeSuite"))
Ω(summary.Failure.Location).Should(Equal(codeLocation))
Ω(summary.Failure.ComponentType).Should(Equal(types.SpecComponentTypeBeforeSuite))
Ω(summary.Failure.ComponentIndex).Should(Equal(0))
Ω(summary.Failure.ComponentCodeLocation).Should(Equal(codeLocation))
})
})
})
})
Describe("construction", func() {
Describe("the first function", func() {
Context("when the first function returns a byte array", func() {
Context("and takes nothing", func() {
It("should be fine", func() {
Ω(func() {
newNode(func() []byte { return nil }, func([]byte) {})
}).ShouldNot(Panic())
})
})
Context("and takes a done function", func() {
It("should be fine", func() {
Ω(func() {
newNode(func(Done) []byte { return nil }, func([]byte) {})
}).ShouldNot(Panic())
})
})
Context("and takes more than one thing", func() {
It("should panic", func() {
Ω(func() {
newNode(func(Done, Done) []byte { return nil }, func([]byte) {})
}).Should(Panic())
})
})
Context("and takes something else", func() {
It("should panic", func() {
Ω(func() {
newNode(func(bool) []byte { return nil }, func([]byte) {})
}).Should(Panic())
})
})
})
Context("when the first function does not return a byte array", func() {
It("should panic", func() {
Ω(func() {
newNode(func() {}, func([]byte) {})
}).Should(Panic())
Ω(func() {
newNode(func() []int { return nil }, func([]byte) {})
}).Should(Panic())
})
})
})
Describe("the second function", func() {
Context("when the second function takes a byte array", func() {
It("should be fine", func() {
Ω(func() {
newNode(func() []byte { return nil }, func([]byte) {})
}).ShouldNot(Panic())
})
})
Context("when it also takes a done channel", func() {
It("should be fine", func() {
Ω(func() {
newNode(func() []byte { return nil }, func([]byte, Done) {})
}).ShouldNot(Panic())
})
})
Context("if it takes anything else", func() {
It("should panic", func() {
Ω(func() {
newNode(func() []byte { return nil }, func([]byte, chan bool) {})
}).Should(Panic())
Ω(func() {
newNode(func() []byte { return nil }, func(string) {})
}).Should(Panic())
})
})
Context("if it takes nothing at all", func() {
It("should panic", func() {
Ω(func() {
newNode(func() []byte { return nil }, func() {})
}).Should(Panic())
})
})
Context("if it returns something", func() {
It("should panic", func() {
Ω(func() {
newNode(func() []byte { return nil }, func([]byte) []byte { return nil })
}).Should(Panic())
})
})
})
})
})

View file

@ -0,0 +1,249 @@
/*
Aggregator is a reporter used by the Ginkgo CLI to aggregate and present parallel test output
coherently as tests complete. You shouldn't need to use this in your code. To run tests in parallel:
ginkgo -nodes=N
where N is the number of nodes you desire.
*/
package remote
import (
"time"
"github.com/onsi/ginkgo/config"
"github.com/onsi/ginkgo/reporters/stenographer"
"github.com/onsi/ginkgo/types"
)
type configAndSuite struct {
config config.GinkgoConfigType
summary *types.SuiteSummary
}
type Aggregator struct {
nodeCount int
config config.DefaultReporterConfigType
stenographer stenographer.Stenographer
result chan bool
suiteBeginnings chan configAndSuite
aggregatedSuiteBeginnings []configAndSuite
beforeSuites chan *types.SetupSummary
aggregatedBeforeSuites []*types.SetupSummary
afterSuites chan *types.SetupSummary
aggregatedAfterSuites []*types.SetupSummary
specCompletions chan *types.SpecSummary
completedSpecs []*types.SpecSummary
suiteEndings chan *types.SuiteSummary
aggregatedSuiteEndings []*types.SuiteSummary
specs []*types.SpecSummary
startTime time.Time
}
func NewAggregator(nodeCount int, result chan bool, config config.DefaultReporterConfigType, stenographer stenographer.Stenographer) *Aggregator {
aggregator := &Aggregator{
nodeCount: nodeCount,
result: result,
config: config,
stenographer: stenographer,
suiteBeginnings: make(chan configAndSuite, 0),
beforeSuites: make(chan *types.SetupSummary, 0),
afterSuites: make(chan *types.SetupSummary, 0),
specCompletions: make(chan *types.SpecSummary, 0),
suiteEndings: make(chan *types.SuiteSummary, 0),
}
go aggregator.mux()
return aggregator
}
func (aggregator *Aggregator) SpecSuiteWillBegin(config config.GinkgoConfigType, summary *types.SuiteSummary) {
aggregator.suiteBeginnings <- configAndSuite{config, summary}
}
func (aggregator *Aggregator) BeforeSuiteDidRun(setupSummary *types.SetupSummary) {
aggregator.beforeSuites <- setupSummary
}
func (aggregator *Aggregator) AfterSuiteDidRun(setupSummary *types.SetupSummary) {
aggregator.afterSuites <- setupSummary
}
func (aggregator *Aggregator) SpecWillRun(specSummary *types.SpecSummary) {
//noop
}
func (aggregator *Aggregator) SpecDidComplete(specSummary *types.SpecSummary) {
aggregator.specCompletions <- specSummary
}
func (aggregator *Aggregator) SpecSuiteDidEnd(summary *types.SuiteSummary) {
aggregator.suiteEndings <- summary
}
func (aggregator *Aggregator) mux() {
loop:
for {
select {
case configAndSuite := <-aggregator.suiteBeginnings:
aggregator.registerSuiteBeginning(configAndSuite)
case setupSummary := <-aggregator.beforeSuites:
aggregator.registerBeforeSuite(setupSummary)
case setupSummary := <-aggregator.afterSuites:
aggregator.registerAfterSuite(setupSummary)
case specSummary := <-aggregator.specCompletions:
aggregator.registerSpecCompletion(specSummary)
case suite := <-aggregator.suiteEndings:
finished, passed := aggregator.registerSuiteEnding(suite)
if finished {
aggregator.result <- passed
break loop
}
}
}
}
func (aggregator *Aggregator) registerSuiteBeginning(configAndSuite configAndSuite) {
aggregator.aggregatedSuiteBeginnings = append(aggregator.aggregatedSuiteBeginnings, configAndSuite)
if len(aggregator.aggregatedSuiteBeginnings) == 1 {
aggregator.startTime = time.Now()
}
if len(aggregator.aggregatedSuiteBeginnings) != aggregator.nodeCount {
return
}
aggregator.stenographer.AnnounceSuite(configAndSuite.summary.SuiteDescription, configAndSuite.config.RandomSeed, configAndSuite.config.RandomizeAllSpecs, aggregator.config.Succinct)
totalNumberOfSpecs := 0
if len(aggregator.aggregatedSuiteBeginnings) > 0 {
totalNumberOfSpecs = configAndSuite.summary.NumberOfSpecsBeforeParallelization
}
aggregator.stenographer.AnnounceTotalNumberOfSpecs(totalNumberOfSpecs, aggregator.config.Succinct)
aggregator.stenographer.AnnounceAggregatedParallelRun(aggregator.nodeCount, aggregator.config.Succinct)
aggregator.flushCompletedSpecs()
}
func (aggregator *Aggregator) registerBeforeSuite(setupSummary *types.SetupSummary) {
aggregator.aggregatedBeforeSuites = append(aggregator.aggregatedBeforeSuites, setupSummary)
aggregator.flushCompletedSpecs()
}
func (aggregator *Aggregator) registerAfterSuite(setupSummary *types.SetupSummary) {
aggregator.aggregatedAfterSuites = append(aggregator.aggregatedAfterSuites, setupSummary)
aggregator.flushCompletedSpecs()
}
func (aggregator *Aggregator) registerSpecCompletion(specSummary *types.SpecSummary) {
aggregator.completedSpecs = append(aggregator.completedSpecs, specSummary)
aggregator.specs = append(aggregator.specs, specSummary)
aggregator.flushCompletedSpecs()
}
func (aggregator *Aggregator) flushCompletedSpecs() {
if len(aggregator.aggregatedSuiteBeginnings) != aggregator.nodeCount {
return
}
for _, setupSummary := range aggregator.aggregatedBeforeSuites {
aggregator.announceBeforeSuite(setupSummary)
}
for _, specSummary := range aggregator.completedSpecs {
aggregator.announceSpec(specSummary)
}
for _, setupSummary := range aggregator.aggregatedAfterSuites {
aggregator.announceAfterSuite(setupSummary)
}
aggregator.aggregatedBeforeSuites = []*types.SetupSummary{}
aggregator.completedSpecs = []*types.SpecSummary{}
aggregator.aggregatedAfterSuites = []*types.SetupSummary{}
}
func (aggregator *Aggregator) announceBeforeSuite(setupSummary *types.SetupSummary) {
aggregator.stenographer.AnnounceCapturedOutput(setupSummary.CapturedOutput)
if setupSummary.State != types.SpecStatePassed {
aggregator.stenographer.AnnounceBeforeSuiteFailure(setupSummary, aggregator.config.Succinct, aggregator.config.FullTrace)
}
}
func (aggregator *Aggregator) announceAfterSuite(setupSummary *types.SetupSummary) {
aggregator.stenographer.AnnounceCapturedOutput(setupSummary.CapturedOutput)
if setupSummary.State != types.SpecStatePassed {
aggregator.stenographer.AnnounceAfterSuiteFailure(setupSummary, aggregator.config.Succinct, aggregator.config.FullTrace)
}
}
func (aggregator *Aggregator) announceSpec(specSummary *types.SpecSummary) {
if aggregator.config.Verbose && specSummary.State != types.SpecStatePending && specSummary.State != types.SpecStateSkipped {
aggregator.stenographer.AnnounceSpecWillRun(specSummary)
}
aggregator.stenographer.AnnounceCapturedOutput(specSummary.CapturedOutput)
switch specSummary.State {
case types.SpecStatePassed:
if specSummary.IsMeasurement {
aggregator.stenographer.AnnounceSuccesfulMeasurement(specSummary, aggregator.config.Succinct)
} else if specSummary.RunTime.Seconds() >= aggregator.config.SlowSpecThreshold {
aggregator.stenographer.AnnounceSuccesfulSlowSpec(specSummary, aggregator.config.Succinct)
} else {
aggregator.stenographer.AnnounceSuccesfulSpec(specSummary)
}
case types.SpecStatePending:
aggregator.stenographer.AnnouncePendingSpec(specSummary, aggregator.config.NoisyPendings && !aggregator.config.Succinct)
case types.SpecStateSkipped:
aggregator.stenographer.AnnounceSkippedSpec(specSummary, aggregator.config.Succinct, aggregator.config.FullTrace)
case types.SpecStateTimedOut:
aggregator.stenographer.AnnounceSpecTimedOut(specSummary, aggregator.config.Succinct, aggregator.config.FullTrace)
case types.SpecStatePanicked:
aggregator.stenographer.AnnounceSpecPanicked(specSummary, aggregator.config.Succinct, aggregator.config.FullTrace)
case types.SpecStateFailed:
aggregator.stenographer.AnnounceSpecFailed(specSummary, aggregator.config.Succinct, aggregator.config.FullTrace)
}
}
func (aggregator *Aggregator) registerSuiteEnding(suite *types.SuiteSummary) (finished bool, passed bool) {
aggregator.aggregatedSuiteEndings = append(aggregator.aggregatedSuiteEndings, suite)
if len(aggregator.aggregatedSuiteEndings) < aggregator.nodeCount {
return false, false
}
aggregatedSuiteSummary := &types.SuiteSummary{}
aggregatedSuiteSummary.SuiteSucceeded = true
for _, suiteSummary := range aggregator.aggregatedSuiteEndings {
if suiteSummary.SuiteSucceeded == false {
aggregatedSuiteSummary.SuiteSucceeded = false
}
aggregatedSuiteSummary.NumberOfSpecsThatWillBeRun += suiteSummary.NumberOfSpecsThatWillBeRun
aggregatedSuiteSummary.NumberOfTotalSpecs += suiteSummary.NumberOfTotalSpecs
aggregatedSuiteSummary.NumberOfPassedSpecs += suiteSummary.NumberOfPassedSpecs
aggregatedSuiteSummary.NumberOfFailedSpecs += suiteSummary.NumberOfFailedSpecs
aggregatedSuiteSummary.NumberOfPendingSpecs += suiteSummary.NumberOfPendingSpecs
aggregatedSuiteSummary.NumberOfSkippedSpecs += suiteSummary.NumberOfSkippedSpecs
aggregatedSuiteSummary.NumberOfFlakedSpecs += suiteSummary.NumberOfFlakedSpecs
}
aggregatedSuiteSummary.RunTime = time.Since(aggregator.startTime)
aggregator.stenographer.SummarizeFailures(aggregator.specs)
aggregator.stenographer.AnnounceSpecRunCompletion(aggregatedSuiteSummary, aggregator.config.Succinct)
return true, aggregatedSuiteSummary.SuiteSucceeded
}

View file

@ -0,0 +1,315 @@
package remote_test
import (
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
"time"
"github.com/onsi/ginkgo/config"
. "github.com/onsi/ginkgo/internal/remote"
st "github.com/onsi/ginkgo/reporters/stenographer"
"github.com/onsi/ginkgo/types"
)
var _ = Describe("Aggregator", func() {
var (
aggregator *Aggregator
reporterConfig config.DefaultReporterConfigType
stenographer *st.FakeStenographer
result chan bool
ginkgoConfig1 config.GinkgoConfigType
ginkgoConfig2 config.GinkgoConfigType
suiteSummary1 *types.SuiteSummary
suiteSummary2 *types.SuiteSummary
beforeSummary *types.SetupSummary
afterSummary *types.SetupSummary
specSummary *types.SpecSummary
suiteDescription string
)
BeforeEach(func() {
reporterConfig = config.DefaultReporterConfigType{
NoColor: false,
SlowSpecThreshold: 0.1,
NoisyPendings: true,
Succinct: false,
Verbose: true,
}
stenographer = st.NewFakeStenographer()
result = make(chan bool, 1)
aggregator = NewAggregator(2, result, reporterConfig, stenographer)
//
// now set up some fixture data
//
ginkgoConfig1 = config.GinkgoConfigType{
RandomSeed: 1138,
RandomizeAllSpecs: true,
ParallelNode: 1,
ParallelTotal: 2,
}
ginkgoConfig2 = config.GinkgoConfigType{
RandomSeed: 1138,
RandomizeAllSpecs: true,
ParallelNode: 2,
ParallelTotal: 2,
}
suiteDescription = "My Parallel Suite"
suiteSummary1 = &types.SuiteSummary{
SuiteDescription: suiteDescription,
NumberOfSpecsBeforeParallelization: 30,
NumberOfTotalSpecs: 17,
NumberOfSpecsThatWillBeRun: 15,
NumberOfPendingSpecs: 1,
NumberOfSkippedSpecs: 1,
}
suiteSummary2 = &types.SuiteSummary{
SuiteDescription: suiteDescription,
NumberOfSpecsBeforeParallelization: 30,
NumberOfTotalSpecs: 13,
NumberOfSpecsThatWillBeRun: 8,
NumberOfPendingSpecs: 2,
NumberOfSkippedSpecs: 3,
}
beforeSummary = &types.SetupSummary{
State: types.SpecStatePassed,
CapturedOutput: "BeforeSuiteOutput",
}
afterSummary = &types.SetupSummary{
State: types.SpecStatePassed,
CapturedOutput: "AfterSuiteOutput",
}
specSummary = &types.SpecSummary{
State: types.SpecStatePassed,
CapturedOutput: "SpecOutput",
}
})
call := func(method string, args ...interface{}) st.FakeStenographerCall {
return st.NewFakeStenographerCall(method, args...)
}
beginSuite := func() {
stenographer.Reset()
aggregator.SpecSuiteWillBegin(ginkgoConfig2, suiteSummary2)
aggregator.SpecSuiteWillBegin(ginkgoConfig1, suiteSummary1)
Eventually(func() interface{} {
return len(stenographer.Calls())
}).Should(BeNumerically(">=", 3))
}
Describe("Announcing the beginning of the suite", func() {
Context("When one of the parallel-suites starts", func() {
BeforeEach(func() {
aggregator.SpecSuiteWillBegin(ginkgoConfig2, suiteSummary2)
})
It("should be silent", func() {
Consistently(func() interface{} { return stenographer.Calls() }).Should(BeEmpty())
})
})
Context("once all of the parallel-suites have started", func() {
BeforeEach(func() {
aggregator.SpecSuiteWillBegin(ginkgoConfig2, suiteSummary2)
aggregator.SpecSuiteWillBegin(ginkgoConfig1, suiteSummary1)
Eventually(func() interface{} {
return stenographer.Calls()
}).Should(HaveLen(3))
})
It("should announce the beginning of the suite", func() {
Ω(stenographer.Calls()).Should(HaveLen(3))
Ω(stenographer.Calls()[0]).Should(Equal(call("AnnounceSuite", suiteDescription, ginkgoConfig1.RandomSeed, true, false)))
Ω(stenographer.Calls()[1]).Should(Equal(call("AnnounceTotalNumberOfSpecs", 30, false)))
Ω(stenographer.Calls()[2]).Should(Equal(call("AnnounceAggregatedParallelRun", 2, false)))
})
})
})
Describe("Announcing specs and before suites", func() {
Context("when the parallel-suites have not all started", func() {
BeforeEach(func() {
aggregator.BeforeSuiteDidRun(beforeSummary)
aggregator.AfterSuiteDidRun(afterSummary)
aggregator.SpecDidComplete(specSummary)
})
It("should not announce any specs", func() {
Consistently(func() interface{} { return stenographer.Calls() }).Should(BeEmpty())
})
Context("when the parallel-suites subsequently start", func() {
BeforeEach(func() {
beginSuite()
})
It("should announce the specs, the before suites and the after suites", func() {
Eventually(func() interface{} {
return stenographer.Calls()
}).Should(ContainElement(call("AnnounceSuccesfulSpec", specSummary)))
Ω(stenographer.Calls()).Should(ContainElement(call("AnnounceCapturedOutput", beforeSummary.CapturedOutput)))
Ω(stenographer.Calls()).Should(ContainElement(call("AnnounceCapturedOutput", afterSummary.CapturedOutput)))
})
})
})
Context("When the parallel-suites have all started", func() {
BeforeEach(func() {
beginSuite()
stenographer.Reset()
})
Context("When a spec completes", func() {
BeforeEach(func() {
aggregator.BeforeSuiteDidRun(beforeSummary)
aggregator.SpecDidComplete(specSummary)
aggregator.AfterSuiteDidRun(afterSummary)
Eventually(func() interface{} {
return stenographer.Calls()
}).Should(HaveLen(5))
})
It("should announce the captured output of the BeforeSuite", func() {
Ω(stenographer.Calls()[0]).Should(Equal(call("AnnounceCapturedOutput", beforeSummary.CapturedOutput)))
})
It("should announce that the spec will run (when in verbose mode)", func() {
Ω(stenographer.Calls()[1]).Should(Equal(call("AnnounceSpecWillRun", specSummary)))
})
It("should announce the captured stdout of the spec", func() {
Ω(stenographer.Calls()[2]).Should(Equal(call("AnnounceCapturedOutput", specSummary.CapturedOutput)))
})
It("should announce completion", func() {
Ω(stenographer.Calls()[3]).Should(Equal(call("AnnounceSuccesfulSpec", specSummary)))
})
It("should announce the captured output of the AfterSuite", func() {
Ω(stenographer.Calls()[4]).Should(Equal(call("AnnounceCapturedOutput", afterSummary.CapturedOutput)))
})
})
})
})
Describe("Announcing the end of the suite", func() {
BeforeEach(func() {
beginSuite()
stenographer.Reset()
})
Context("When one of the parallel-suites ends", func() {
BeforeEach(func() {
aggregator.SpecSuiteDidEnd(suiteSummary2)
})
It("should be silent", func() {
Consistently(func() interface{} { return stenographer.Calls() }).Should(BeEmpty())
})
It("should not notify the channel", func() {
Ω(result).Should(BeEmpty())
})
})
Context("once all of the parallel-suites end", func() {
BeforeEach(func() {
time.Sleep(200 * time.Millisecond)
suiteSummary1.SuiteSucceeded = true
suiteSummary1.NumberOfPassedSpecs = 15
suiteSummary1.NumberOfFailedSpecs = 0
suiteSummary1.NumberOfFlakedSpecs = 3
suiteSummary2.SuiteSucceeded = false
suiteSummary2.NumberOfPassedSpecs = 5
suiteSummary2.NumberOfFailedSpecs = 3
suiteSummary2.NumberOfFlakedSpecs = 4
aggregator.SpecSuiteDidEnd(suiteSummary2)
aggregator.SpecSuiteDidEnd(suiteSummary1)
Eventually(func() interface{} {
return stenographer.Calls()
}).Should(HaveLen(2))
})
It("should announce the end of the suite", func() {
compositeSummary := stenographer.Calls()[1].Args[0].(*types.SuiteSummary)
Ω(compositeSummary.SuiteSucceeded).Should(BeFalse())
Ω(compositeSummary.NumberOfSpecsThatWillBeRun).Should(Equal(23))
Ω(compositeSummary.NumberOfTotalSpecs).Should(Equal(30))
Ω(compositeSummary.NumberOfPassedSpecs).Should(Equal(20))
Ω(compositeSummary.NumberOfFailedSpecs).Should(Equal(3))
Ω(compositeSummary.NumberOfPendingSpecs).Should(Equal(3))
Ω(compositeSummary.NumberOfSkippedSpecs).Should(Equal(4))
Ω(compositeSummary.NumberOfFlakedSpecs).Should(Equal(7))
Ω(compositeSummary.RunTime.Seconds()).Should(BeNumerically(">", 0.2))
})
})
Context("when all the parallel-suites pass", func() {
BeforeEach(func() {
suiteSummary1.SuiteSucceeded = true
suiteSummary2.SuiteSucceeded = true
aggregator.SpecSuiteDidEnd(suiteSummary2)
aggregator.SpecSuiteDidEnd(suiteSummary1)
Eventually(func() interface{} {
return stenographer.Calls()
}).Should(HaveLen(2))
})
It("should report success", func() {
compositeSummary := stenographer.Calls()[1].Args[0].(*types.SuiteSummary)
Ω(compositeSummary.SuiteSucceeded).Should(BeTrue())
})
It("should notify the channel that it succeded", func(done Done) {
Ω(<-result).Should(BeTrue())
close(done)
})
})
Context("when one of the parallel-suites fails", func() {
BeforeEach(func() {
suiteSummary1.SuiteSucceeded = true
suiteSummary2.SuiteSucceeded = false
aggregator.SpecSuiteDidEnd(suiteSummary2)
aggregator.SpecSuiteDidEnd(suiteSummary1)
Eventually(func() interface{} {
return stenographer.Calls()
}).Should(HaveLen(2))
})
It("should report failure", func() {
compositeSummary := stenographer.Calls()[1].Args[0].(*types.SuiteSummary)
Ω(compositeSummary.SuiteSucceeded).Should(BeFalse())
})
It("should notify the channel that it failed", func(done Done) {
Ω(<-result).Should(BeFalse())
close(done)
})
})
})
})

View file

@ -0,0 +1,17 @@
package remote_test
type fakeOutputInterceptor struct {
DidStartInterceptingOutput bool
DidStopInterceptingOutput bool
InterceptedOutput string
}
func (interceptor *fakeOutputInterceptor) StartInterceptingOutput() error {
interceptor.DidStartInterceptingOutput = true
return nil
}
func (interceptor *fakeOutputInterceptor) StopInterceptingAndReturnOutput() (string, error) {
interceptor.DidStopInterceptingOutput = true
return interceptor.InterceptedOutput, nil
}

View file

@ -0,0 +1,33 @@
package remote_test
import (
"io"
"io/ioutil"
"net/http"
)
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, _ := ioutil.ReadAll(body)
poster.posts = append(poster.posts, post{
url: url,
bodyType: bodyType,
bodyContent: bodyContent,
})
return nil, nil
}

View file

@ -0,0 +1,90 @@
package remote
import (
"bytes"
"encoding/json"
"io"
"net/http"
"github.com/onsi/ginkgo/config"
"github.com/onsi/ginkgo/types"
)
//An interface to net/http's client to allow the injection of fakes under test
type Poster interface {
Post(url string, bodyType string, body io.Reader) (resp *http.Response, err error)
}
/*
The ForwardingReporter is a Ginkgo reporter that forwards information to
a Ginkgo remote server.
When streaming parallel test output, this repoter is automatically installed by Ginkgo.
This is accomplished by passing in the GINKGO_REMOTE_REPORTING_SERVER environment variable to `go test`, the Ginkgo test runner
detects this environment variable (which should contain the host of the server) and automatically installs a ForwardingReporter
in place of Ginkgo's DefaultReporter.
*/
type ForwardingReporter struct {
serverHost string
poster Poster
outputInterceptor OutputInterceptor
}
func NewForwardingReporter(serverHost string, poster Poster, outputInterceptor OutputInterceptor) *ForwardingReporter {
return &ForwardingReporter{
serverHost: serverHost,
poster: poster,
outputInterceptor: outputInterceptor,
}
}
func (reporter *ForwardingReporter) post(path string, data interface{}) {
encoded, _ := json.Marshal(data)
buffer := bytes.NewBuffer(encoded)
reporter.poster.Post(reporter.serverHost+path, "application/json", buffer)
}
func (reporter *ForwardingReporter) SpecSuiteWillBegin(conf config.GinkgoConfigType, summary *types.SuiteSummary) {
data := struct {
Config config.GinkgoConfigType `json:"config"`
Summary *types.SuiteSummary `json:"suite-summary"`
}{
conf,
summary,
}
reporter.outputInterceptor.StartInterceptingOutput()
reporter.post("/SpecSuiteWillBegin", data)
}
func (reporter *ForwardingReporter) BeforeSuiteDidRun(setupSummary *types.SetupSummary) {
output, _ := reporter.outputInterceptor.StopInterceptingAndReturnOutput()
reporter.outputInterceptor.StartInterceptingOutput()
setupSummary.CapturedOutput = output
reporter.post("/BeforeSuiteDidRun", setupSummary)
}
func (reporter *ForwardingReporter) SpecWillRun(specSummary *types.SpecSummary) {
reporter.post("/SpecWillRun", specSummary)
}
func (reporter *ForwardingReporter) SpecDidComplete(specSummary *types.SpecSummary) {
output, _ := reporter.outputInterceptor.StopInterceptingAndReturnOutput()
reporter.outputInterceptor.StartInterceptingOutput()
specSummary.CapturedOutput = output
reporter.post("/SpecDidComplete", specSummary)
}
func (reporter *ForwardingReporter) AfterSuiteDidRun(setupSummary *types.SetupSummary) {
output, _ := reporter.outputInterceptor.StopInterceptingAndReturnOutput()
reporter.outputInterceptor.StartInterceptingOutput()
setupSummary.CapturedOutput = output
reporter.post("/AfterSuiteDidRun", setupSummary)
}
func (reporter *ForwardingReporter) SpecSuiteDidEnd(summary *types.SuiteSummary) {
reporter.outputInterceptor.StopInterceptingAndReturnOutput()
reporter.post("/SpecSuiteDidEnd", summary)
}

View file

@ -0,0 +1,180 @@
package remote_test
import (
"encoding/json"
. "github.com/onsi/ginkgo"
"github.com/onsi/ginkgo/config"
. "github.com/onsi/ginkgo/internal/remote"
"github.com/onsi/ginkgo/types"
. "github.com/onsi/gomega"
)
var _ = Describe("ForwardingReporter", func() {
var (
reporter *ForwardingReporter
interceptor *fakeOutputInterceptor
poster *fakePoster
suiteSummary *types.SuiteSummary
specSummary *types.SpecSummary
setupSummary *types.SetupSummary
serverHost string
)
BeforeEach(func() {
serverHost = "http://127.0.0.1:7788"
poster = newFakePoster()
interceptor = &fakeOutputInterceptor{
InterceptedOutput: "The intercepted output!",
}
reporter = NewForwardingReporter(serverHost, poster, interceptor)
suiteSummary = &types.SuiteSummary{
SuiteDescription: "My Test Suite",
}
setupSummary = &types.SetupSummary{
State: types.SpecStatePassed,
}
specSummary = &types.SpecSummary{
ComponentTexts: []string{"My", "Spec"},
State: types.SpecStatePassed,
}
})
Context("When a suite begins", func() {
BeforeEach(func() {
reporter.SpecSuiteWillBegin(config.GinkgoConfig, suiteSummary)
})
It("should start intercepting output", func() {
Ω(interceptor.DidStartInterceptingOutput).Should(BeTrue())
})
It("should POST the SuiteSummary and Ginkgo Config to the Ginkgo server", func() {
Ω(poster.posts).Should(HaveLen(1))
Ω(poster.posts[0].url).Should(Equal("http://127.0.0.1:7788/SpecSuiteWillBegin"))
Ω(poster.posts[0].bodyType).Should(Equal("application/json"))
var sentData struct {
SentConfig config.GinkgoConfigType `json:"config"`
SentSuiteSummary *types.SuiteSummary `json:"suite-summary"`
}
err := json.Unmarshal(poster.posts[0].bodyContent, &sentData)
Ω(err).ShouldNot(HaveOccurred())
Ω(sentData.SentConfig).Should(Equal(config.GinkgoConfig))
Ω(sentData.SentSuiteSummary).Should(Equal(suiteSummary))
})
})
Context("when a BeforeSuite completes", func() {
BeforeEach(func() {
reporter.BeforeSuiteDidRun(setupSummary)
})
It("should stop, then start intercepting output", func() {
Ω(interceptor.DidStopInterceptingOutput).Should(BeTrue())
Ω(interceptor.DidStartInterceptingOutput).Should(BeTrue())
})
It("should POST the SetupSummary to the Ginkgo server", func() {
Ω(poster.posts).Should(HaveLen(1))
Ω(poster.posts[0].url).Should(Equal("http://127.0.0.1:7788/BeforeSuiteDidRun"))
Ω(poster.posts[0].bodyType).Should(Equal("application/json"))
var summary *types.SetupSummary
err := json.Unmarshal(poster.posts[0].bodyContent, &summary)
Ω(err).ShouldNot(HaveOccurred())
setupSummary.CapturedOutput = interceptor.InterceptedOutput
Ω(summary).Should(Equal(setupSummary))
})
})
Context("when an AfterSuite completes", func() {
BeforeEach(func() {
reporter.AfterSuiteDidRun(setupSummary)
})
It("should stop, then start intercepting output", func() {
Ω(interceptor.DidStopInterceptingOutput).Should(BeTrue())
Ω(interceptor.DidStartInterceptingOutput).Should(BeTrue())
})
It("should POST the SetupSummary to the Ginkgo server", func() {
Ω(poster.posts).Should(HaveLen(1))
Ω(poster.posts[0].url).Should(Equal("http://127.0.0.1:7788/AfterSuiteDidRun"))
Ω(poster.posts[0].bodyType).Should(Equal("application/json"))
var summary *types.SetupSummary
err := json.Unmarshal(poster.posts[0].bodyContent, &summary)
Ω(err).ShouldNot(HaveOccurred())
setupSummary.CapturedOutput = interceptor.InterceptedOutput
Ω(summary).Should(Equal(setupSummary))
})
})
Context("When a spec will run", func() {
BeforeEach(func() {
reporter.SpecWillRun(specSummary)
})
It("should POST the SpecSummary to the Ginkgo server", func() {
Ω(poster.posts).Should(HaveLen(1))
Ω(poster.posts[0].url).Should(Equal("http://127.0.0.1:7788/SpecWillRun"))
Ω(poster.posts[0].bodyType).Should(Equal("application/json"))
var summary *types.SpecSummary
err := json.Unmarshal(poster.posts[0].bodyContent, &summary)
Ω(err).ShouldNot(HaveOccurred())
Ω(summary).Should(Equal(specSummary))
})
Context("When a spec completes", func() {
BeforeEach(func() {
specSummary.State = types.SpecStatePanicked
reporter.SpecDidComplete(specSummary)
})
It("should POST the SpecSummary to the Ginkgo server and include any intercepted output", func() {
Ω(poster.posts).Should(HaveLen(2))
Ω(poster.posts[1].url).Should(Equal("http://127.0.0.1:7788/SpecDidComplete"))
Ω(poster.posts[1].bodyType).Should(Equal("application/json"))
var summary *types.SpecSummary
err := json.Unmarshal(poster.posts[1].bodyContent, &summary)
Ω(err).ShouldNot(HaveOccurred())
specSummary.CapturedOutput = interceptor.InterceptedOutput
Ω(summary).Should(Equal(specSummary))
})
It("should stop, then start intercepting output", func() {
Ω(interceptor.DidStopInterceptingOutput).Should(BeTrue())
Ω(interceptor.DidStartInterceptingOutput).Should(BeTrue())
})
})
})
Context("When a suite ends", func() {
BeforeEach(func() {
reporter.SpecSuiteDidEnd(suiteSummary)
})
It("should POST the SuiteSummary to the Ginkgo server", func() {
Ω(poster.posts).Should(HaveLen(1))
Ω(poster.posts[0].url).Should(Equal("http://127.0.0.1:7788/SpecSuiteDidEnd"))
Ω(poster.posts[0].bodyType).Should(Equal("application/json"))
var summary *types.SuiteSummary
err := json.Unmarshal(poster.posts[0].bodyContent, &summary)
Ω(err).ShouldNot(HaveOccurred())
Ω(summary).Should(Equal(suiteSummary))
})
})
})

View file

@ -0,0 +1,10 @@
package remote
/*
The OutputInterceptor is used by the ForwardingReporter to
intercept and capture all stdin and stderr output during a test run.
*/
type OutputInterceptor interface {
StartInterceptingOutput() error
StopInterceptingAndReturnOutput() (string, error)
}

View file

@ -0,0 +1,55 @@
// +build freebsd openbsd netbsd dragonfly darwin linux solaris
package remote
import (
"errors"
"io/ioutil"
"os"
)
func NewOutputInterceptor() OutputInterceptor {
return &outputInterceptor{}
}
type outputInterceptor struct {
redirectFile *os.File
intercepting bool
}
func (interceptor *outputInterceptor) StartInterceptingOutput() error {
if interceptor.intercepting {
return errors.New("Already intercepting output!")
}
interceptor.intercepting = true
var err error
interceptor.redirectFile, err = ioutil.TempFile("", "ginkgo-output")
if err != nil {
return err
}
// Call a function in ./syscall_dup_*.go
// If building for everything other than linux_arm64,
// use a "normal" syscall.Dup2(oldfd, newfd) call. If building for linux_arm64 (which doesn't have syscall.Dup2)
// call syscall.Dup3(oldfd, newfd, 0). They are nearly identical, see: http://linux.die.net/man/2/dup3
syscallDup(int(interceptor.redirectFile.Fd()), 1)
syscallDup(int(interceptor.redirectFile.Fd()), 2)
return nil
}
func (interceptor *outputInterceptor) StopInterceptingAndReturnOutput() (string, error) {
if !interceptor.intercepting {
return "", errors.New("Not intercepting output!")
}
interceptor.redirectFile.Close()
output, err := ioutil.ReadFile(interceptor.redirectFile.Name())
os.Remove(interceptor.redirectFile.Name())
interceptor.intercepting = false
return string(output), err
}

View file

@ -0,0 +1,33 @@
// +build windows
package remote
import (
"errors"
)
func NewOutputInterceptor() OutputInterceptor {
return &outputInterceptor{}
}
type outputInterceptor struct {
intercepting bool
}
func (interceptor *outputInterceptor) StartInterceptingOutput() error {
if interceptor.intercepting {
return errors.New("Already intercepting output!")
}
interceptor.intercepting = true
// not working on windows...
return nil
}
func (interceptor *outputInterceptor) StopInterceptingAndReturnOutput() (string, error) {
// not working on windows...
interceptor.intercepting = false
return "", nil
}

View file

@ -0,0 +1,13 @@
package remote_test
import (
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
"testing"
)
func TestRemote(t *testing.T) {
RegisterFailHandler(Fail)
RunSpecs(t, "Remote Spec Forwarding Suite")
}

224
vendor/github.com/onsi/ginkgo/internal/remote/server.go generated vendored Normal file
View file

@ -0,0 +1,224 @@
/*
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 remote
import (
"encoding/json"
"io/ioutil"
"net"
"net/http"
"sync"
"github.com/onsi/ginkgo/internal/spec_iterator"
"github.com/onsi/ginkgo/config"
"github.com/onsi/ginkgo/reporters"
"github.com/onsi/ginkgo/types"
)
/*
Server spins up on an automatically selected port and listens for communication from the forwarding reporter.
It then forwards that communication to attached reporters.
*/
type Server struct {
listener net.Listener
reporters []reporters.Reporter
alives []func() bool
lock *sync.Mutex
beforeSuiteData types.RemoteBeforeSuiteData
parallelTotal int
counter int
}
//Create a new server, automatically selecting a port
func NewServer(parallelTotal int) (*Server, error) {
listener, err := net.Listen("tcp", "127.0.0.1:0")
if err != nil {
return nil, err
}
return &Server{
listener: listener,
lock: &sync.Mutex{},
alives: make([]func() bool, parallelTotal),
beforeSuiteData: types.RemoteBeforeSuiteData{nil, types.RemoteBeforeSuiteStatePending},
parallelTotal: parallelTotal,
}, nil
}
//Start the server. You don't need to `go s.Start()`, just `s.Start()`
func (server *Server) Start() {
httpServer := &http.Server{}
mux := http.NewServeMux()
httpServer.Handler = mux
//streaming endpoints
mux.HandleFunc("/SpecSuiteWillBegin", server.specSuiteWillBegin)
mux.HandleFunc("/BeforeSuiteDidRun", server.beforeSuiteDidRun)
mux.HandleFunc("/AfterSuiteDidRun", server.afterSuiteDidRun)
mux.HandleFunc("/SpecWillRun", server.specWillRun)
mux.HandleFunc("/SpecDidComplete", server.specDidComplete)
mux.HandleFunc("/SpecSuiteDidEnd", server.specSuiteDidEnd)
//synchronization endpoints
mux.HandleFunc("/BeforeSuiteState", server.handleBeforeSuiteState)
mux.HandleFunc("/RemoteAfterSuiteData", server.handleRemoteAfterSuiteData)
mux.HandleFunc("/counter", server.handleCounter)
mux.HandleFunc("/has-counter", server.handleHasCounter) //for backward compatibility
go httpServer.Serve(server.listener)
}
//Stop the server
func (server *Server) Close() {
server.listener.Close()
}
//The address the server can be reached it. Pass this into the `ForwardingReporter`.
func (server *Server) Address() string {
return "http://" + server.listener.Addr().String()
}
//
// Streaming Endpoints
//
//The server will forward all received messages to Ginkgo reporters registered with `RegisterReporters`
func (server *Server) readAll(request *http.Request) []byte {
defer request.Body.Close()
body, _ := ioutil.ReadAll(request.Body)
return body
}
func (server *Server) RegisterReporters(reporters ...reporters.Reporter) {
server.reporters = reporters
}
func (server *Server) specSuiteWillBegin(writer http.ResponseWriter, request *http.Request) {
body := server.readAll(request)
var data struct {
Config config.GinkgoConfigType `json:"config"`
Summary *types.SuiteSummary `json:"suite-summary"`
}
json.Unmarshal(body, &data)
for _, reporter := range server.reporters {
reporter.SpecSuiteWillBegin(data.Config, data.Summary)
}
}
func (server *Server) beforeSuiteDidRun(writer http.ResponseWriter, request *http.Request) {
body := server.readAll(request)
var setupSummary *types.SetupSummary
json.Unmarshal(body, &setupSummary)
for _, reporter := range server.reporters {
reporter.BeforeSuiteDidRun(setupSummary)
}
}
func (server *Server) afterSuiteDidRun(writer http.ResponseWriter, request *http.Request) {
body := server.readAll(request)
var setupSummary *types.SetupSummary
json.Unmarshal(body, &setupSummary)
for _, reporter := range server.reporters {
reporter.AfterSuiteDidRun(setupSummary)
}
}
func (server *Server) specWillRun(writer http.ResponseWriter, request *http.Request) {
body := server.readAll(request)
var specSummary *types.SpecSummary
json.Unmarshal(body, &specSummary)
for _, reporter := range server.reporters {
reporter.SpecWillRun(specSummary)
}
}
func (server *Server) specDidComplete(writer http.ResponseWriter, request *http.Request) {
body := server.readAll(request)
var specSummary *types.SpecSummary
json.Unmarshal(body, &specSummary)
for _, reporter := range server.reporters {
reporter.SpecDidComplete(specSummary)
}
}
func (server *Server) specSuiteDidEnd(writer http.ResponseWriter, request *http.Request) {
body := server.readAll(request)
var suiteSummary *types.SuiteSummary
json.Unmarshal(body, &suiteSummary)
for _, reporter := range server.reporters {
reporter.SpecSuiteDidEnd(suiteSummary)
}
}
//
// Synchronization Endpoints
//
func (server *Server) RegisterAlive(node int, alive func() bool) {
server.lock.Lock()
defer server.lock.Unlock()
server.alives[node-1] = alive
}
func (server *Server) nodeIsAlive(node int) bool {
server.lock.Lock()
defer server.lock.Unlock()
alive := server.alives[node-1]
if alive == nil {
return true
}
return alive()
}
func (server *Server) handleBeforeSuiteState(writer http.ResponseWriter, request *http.Request) {
if request.Method == "POST" {
dec := json.NewDecoder(request.Body)
dec.Decode(&(server.beforeSuiteData))
} else {
beforeSuiteData := server.beforeSuiteData
if beforeSuiteData.State == types.RemoteBeforeSuiteStatePending && !server.nodeIsAlive(1) {
beforeSuiteData.State = types.RemoteBeforeSuiteStateDisappeared
}
enc := json.NewEncoder(writer)
enc.Encode(beforeSuiteData)
}
}
func (server *Server) handleRemoteAfterSuiteData(writer http.ResponseWriter, request *http.Request) {
afterSuiteData := types.RemoteAfterSuiteData{
CanRun: true,
}
for i := 2; i <= server.parallelTotal; i++ {
afterSuiteData.CanRun = afterSuiteData.CanRun && !server.nodeIsAlive(i)
}
enc := json.NewEncoder(writer)
enc.Encode(afterSuiteData)
}
func (server *Server) handleCounter(writer http.ResponseWriter, request *http.Request) {
c := spec_iterator.Counter{}
server.lock.Lock()
c.Index = server.counter
server.counter = server.counter + 1
server.lock.Unlock()
json.NewEncoder(writer).Encode(c)
}
func (server *Server) handleHasCounter(writer http.ResponseWriter, request *http.Request) {
writer.Write([]byte(""))
}

View file

@ -0,0 +1,269 @@
package remote_test
import (
. "github.com/onsi/ginkgo"
. "github.com/onsi/ginkgo/internal/remote"
. "github.com/onsi/gomega"
"github.com/onsi/ginkgo/config"
"github.com/onsi/ginkgo/reporters"
"github.com/onsi/ginkgo/types"
"bytes"
"encoding/json"
"net/http"
)
var _ = Describe("Server", func() {
var (
server *Server
)
BeforeEach(func() {
var err error
server, err = NewServer(3)
Ω(err).ShouldNot(HaveOccurred())
server.Start()
})
AfterEach(func() {
server.Close()
})
Describe("Streaming endpoints", func() {
var (
reporterA, reporterB *reporters.FakeReporter
forwardingReporter *ForwardingReporter
suiteSummary *types.SuiteSummary
setupSummary *types.SetupSummary
specSummary *types.SpecSummary
)
BeforeEach(func() {
reporterA = reporters.NewFakeReporter()
reporterB = reporters.NewFakeReporter()
server.RegisterReporters(reporterA, reporterB)
forwardingReporter = NewForwardingReporter(server.Address(), &http.Client{}, &fakeOutputInterceptor{})
suiteSummary = &types.SuiteSummary{
SuiteDescription: "My Test Suite",
}
setupSummary = &types.SetupSummary{
State: types.SpecStatePassed,
}
specSummary = &types.SpecSummary{
ComponentTexts: []string{"My", "Spec"},
State: types.SpecStatePassed,
}
})
It("should make its address available", func() {
Ω(server.Address()).Should(MatchRegexp(`http://127.0.0.1:\d{2,}`))
})
Describe("/SpecSuiteWillBegin", func() {
It("should decode and forward the Ginkgo config and suite summary", func(done Done) {
forwardingReporter.SpecSuiteWillBegin(config.GinkgoConfig, suiteSummary)
Ω(reporterA.Config).Should(Equal(config.GinkgoConfig))
Ω(reporterB.Config).Should(Equal(config.GinkgoConfig))
Ω(reporterA.BeginSummary).Should(Equal(suiteSummary))
Ω(reporterB.BeginSummary).Should(Equal(suiteSummary))
close(done)
})
})
Describe("/BeforeSuiteDidRun", func() {
It("should decode and forward the setup summary", func() {
forwardingReporter.BeforeSuiteDidRun(setupSummary)
Ω(reporterA.BeforeSuiteSummary).Should(Equal(setupSummary))
Ω(reporterB.BeforeSuiteSummary).Should(Equal(setupSummary))
})
})
Describe("/AfterSuiteDidRun", func() {
It("should decode and forward the setup summary", func() {
forwardingReporter.AfterSuiteDidRun(setupSummary)
Ω(reporterA.AfterSuiteSummary).Should(Equal(setupSummary))
Ω(reporterB.AfterSuiteSummary).Should(Equal(setupSummary))
})
})
Describe("/SpecWillRun", func() {
It("should decode and forward the spec summary", func(done Done) {
forwardingReporter.SpecWillRun(specSummary)
Ω(reporterA.SpecWillRunSummaries[0]).Should(Equal(specSummary))
Ω(reporterB.SpecWillRunSummaries[0]).Should(Equal(specSummary))
close(done)
})
})
Describe("/SpecDidComplete", func() {
It("should decode and forward the spec summary", func(done Done) {
forwardingReporter.SpecDidComplete(specSummary)
Ω(reporterA.SpecSummaries[0]).Should(Equal(specSummary))
Ω(reporterB.SpecSummaries[0]).Should(Equal(specSummary))
close(done)
})
})
Describe("/SpecSuiteDidEnd", func() {
It("should decode and forward the suite summary", func(done Done) {
forwardingReporter.SpecSuiteDidEnd(suiteSummary)
Ω(reporterA.EndSummary).Should(Equal(suiteSummary))
Ω(reporterB.EndSummary).Should(Equal(suiteSummary))
close(done)
})
})
})
Describe("Synchronization endpoints", func() {
Describe("GETting and POSTing BeforeSuiteState", func() {
getBeforeSuite := func() types.RemoteBeforeSuiteData {
resp, err := http.Get(server.Address() + "/BeforeSuiteState")
Ω(err).ShouldNot(HaveOccurred())
Ω(resp.StatusCode).Should(Equal(http.StatusOK))
r := types.RemoteBeforeSuiteData{}
decoder := json.NewDecoder(resp.Body)
err = decoder.Decode(&r)
Ω(err).ShouldNot(HaveOccurred())
return r
}
postBeforeSuite := func(r types.RemoteBeforeSuiteData) {
resp, err := http.Post(server.Address()+"/BeforeSuiteState", "application/json", bytes.NewReader(r.ToJSON()))
Ω(err).ShouldNot(HaveOccurred())
Ω(resp.StatusCode).Should(Equal(http.StatusOK))
}
Context("when the first node's Alive has not been registered yet", func() {
It("should return pending", func() {
state := getBeforeSuite()
Ω(state).Should(Equal(types.RemoteBeforeSuiteData{nil, types.RemoteBeforeSuiteStatePending}))
state = getBeforeSuite()
Ω(state).Should(Equal(types.RemoteBeforeSuiteData{nil, types.RemoteBeforeSuiteStatePending}))
})
})
Context("when the first node is Alive but has not responded yet", func() {
BeforeEach(func() {
server.RegisterAlive(1, func() bool {
return true
})
})
It("should return pending", func() {
state := getBeforeSuite()
Ω(state).Should(Equal(types.RemoteBeforeSuiteData{nil, types.RemoteBeforeSuiteStatePending}))
state = getBeforeSuite()
Ω(state).Should(Equal(types.RemoteBeforeSuiteData{nil, types.RemoteBeforeSuiteStatePending}))
})
})
Context("when the first node has responded", func() {
var state types.RemoteBeforeSuiteData
BeforeEach(func() {
server.RegisterAlive(1, func() bool {
return false
})
state = types.RemoteBeforeSuiteData{
Data: []byte("my data"),
State: types.RemoteBeforeSuiteStatePassed,
}
postBeforeSuite(state)
})
It("should return the passed in state", func() {
returnedState := getBeforeSuite()
Ω(returnedState).Should(Equal(state))
})
})
Context("when the first node is no longer Alive and has not responded yet", func() {
BeforeEach(func() {
server.RegisterAlive(1, func() bool {
return false
})
})
It("should return disappeared", func() {
state := getBeforeSuite()
Ω(state).Should(Equal(types.RemoteBeforeSuiteData{nil, types.RemoteBeforeSuiteStateDisappeared}))
state = getBeforeSuite()
Ω(state).Should(Equal(types.RemoteBeforeSuiteData{nil, types.RemoteBeforeSuiteStateDisappeared}))
})
})
})
Describe("GETting RemoteAfterSuiteData", func() {
getRemoteAfterSuiteData := func() bool {
resp, err := http.Get(server.Address() + "/RemoteAfterSuiteData")
Ω(err).ShouldNot(HaveOccurred())
Ω(resp.StatusCode).Should(Equal(http.StatusOK))
a := types.RemoteAfterSuiteData{}
decoder := json.NewDecoder(resp.Body)
err = decoder.Decode(&a)
Ω(err).ShouldNot(HaveOccurred())
return a.CanRun
}
Context("when there are unregistered nodes", func() {
BeforeEach(func() {
server.RegisterAlive(2, func() bool {
return false
})
})
It("should return false", func() {
Ω(getRemoteAfterSuiteData()).Should(BeFalse())
})
})
Context("when all none-node-1 nodes are still running", func() {
BeforeEach(func() {
server.RegisterAlive(2, func() bool {
return true
})
server.RegisterAlive(3, func() bool {
return false
})
})
It("should return false", func() {
Ω(getRemoteAfterSuiteData()).Should(BeFalse())
})
})
Context("when all none-1 nodes are done", func() {
BeforeEach(func() {
server.RegisterAlive(2, func() bool {
return false
})
server.RegisterAlive(3, func() bool {
return false
})
})
It("should return true", func() {
Ω(getRemoteAfterSuiteData()).Should(BeTrue())
})
})
})
})
})

View file

@ -0,0 +1,11 @@
// +build linux,arm64
package remote
import "syscall"
// linux_arm64 doesn't have syscall.Dup2 which ginkgo uses, so
// use the nearly identical syscall.Dup3 instead
func syscallDup(oldfd int, newfd int) (err error) {
return syscall.Dup3(oldfd, newfd, 0)
}

View file

@ -0,0 +1,9 @@
// +build solaris
package remote
import "golang.org/x/sys/unix"
func syscallDup(oldfd int, newfd int) (err error) {
return unix.Dup2(oldfd, newfd)
}

View file

@ -0,0 +1,11 @@
// +build !linux !arm64
// +build !windows
// +build !solaris
package remote
import "syscall"
func syscallDup(oldfd int, newfd int) (err error) {
return syscall.Dup2(oldfd, newfd)
}

206
vendor/github.com/onsi/ginkgo/internal/spec/spec.go generated vendored Normal file
View file

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

View file

@ -0,0 +1,13 @@
package spec_test
import (
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
"testing"
)
func TestSpec(t *testing.T) {
RegisterFailHandler(Fail)
RunSpecs(t, "Spec Suite")
}

View file

@ -0,0 +1,657 @@
package spec_test
import (
"time"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
"github.com/onsi/gomega/gbytes"
. "github.com/onsi/ginkgo/internal/spec"
"github.com/onsi/ginkgo/internal/codelocation"
"github.com/onsi/ginkgo/internal/containernode"
Failer "github.com/onsi/ginkgo/internal/failer"
"github.com/onsi/ginkgo/internal/leafnodes"
"github.com/onsi/ginkgo/types"
)
var noneFlag = types.FlagTypeNone
var focusedFlag = types.FlagTypeFocused
var pendingFlag = types.FlagTypePending
var _ = Describe("Spec", func() {
var (
failer *Failer.Failer
codeLocation types.CodeLocation
nodesThatRan []string
spec *Spec
buffer *gbytes.Buffer
)
newBody := func(text string, fail bool) func() {
return func() {
nodesThatRan = append(nodesThatRan, text)
if fail {
failer.Fail(text, codeLocation)
}
}
}
newIt := func(text string, flag types.FlagType, fail bool) *leafnodes.ItNode {
return leafnodes.NewItNode(text, newBody(text, fail), flag, codeLocation, 0, failer, 0)
}
newItWithBody := func(text string, body interface{}) *leafnodes.ItNode {
return leafnodes.NewItNode(text, body, noneFlag, codeLocation, 0, failer, 0)
}
newMeasure := func(text string, flag types.FlagType, fail bool, samples int) *leafnodes.MeasureNode {
return leafnodes.NewMeasureNode(text, func(Benchmarker) {
nodesThatRan = append(nodesThatRan, text)
if fail {
failer.Fail(text, codeLocation)
}
}, flag, codeLocation, samples, failer, 0)
}
newBef := func(text string, fail bool) leafnodes.BasicNode {
return leafnodes.NewBeforeEachNode(newBody(text, fail), codeLocation, 0, failer, 0)
}
newAft := func(text string, fail bool) leafnodes.BasicNode {
return leafnodes.NewAfterEachNode(newBody(text, fail), codeLocation, 0, failer, 0)
}
newJusBef := func(text string, fail bool) leafnodes.BasicNode {
return leafnodes.NewJustBeforeEachNode(newBody(text, fail), codeLocation, 0, failer, 0)
}
newContainer := func(text string, flag types.FlagType, setupNodes ...leafnodes.BasicNode) *containernode.ContainerNode {
c := containernode.New(text, flag, codeLocation)
for _, node := range setupNodes {
c.PushSetupNode(node)
}
return c
}
containers := func(containers ...*containernode.ContainerNode) []*containernode.ContainerNode {
return containers
}
BeforeEach(func() {
buffer = gbytes.NewBuffer()
failer = Failer.New()
codeLocation = codelocation.New(0)
nodesThatRan = []string{}
})
Describe("marking specs focused and pending", func() {
It("should satisfy various caes", func() {
cases := []struct {
ContainerFlags []types.FlagType
SubjectFlag types.FlagType
Pending bool
Focused bool
}{
{[]types.FlagType{}, noneFlag, false, false},
{[]types.FlagType{}, focusedFlag, false, true},
{[]types.FlagType{}, pendingFlag, true, false},
{[]types.FlagType{noneFlag}, noneFlag, false, false},
{[]types.FlagType{focusedFlag}, noneFlag, false, true},
{[]types.FlagType{pendingFlag}, noneFlag, true, false},
{[]types.FlagType{noneFlag}, focusedFlag, false, true},
{[]types.FlagType{focusedFlag}, focusedFlag, false, true},
{[]types.FlagType{pendingFlag}, focusedFlag, true, true},
{[]types.FlagType{noneFlag}, pendingFlag, true, false},
{[]types.FlagType{focusedFlag}, pendingFlag, true, true},
{[]types.FlagType{pendingFlag}, pendingFlag, true, false},
{[]types.FlagType{focusedFlag, noneFlag}, noneFlag, false, true},
{[]types.FlagType{noneFlag, focusedFlag}, noneFlag, false, true},
{[]types.FlagType{pendingFlag, noneFlag}, noneFlag, true, false},
{[]types.FlagType{noneFlag, pendingFlag}, noneFlag, true, false},
{[]types.FlagType{focusedFlag, pendingFlag}, noneFlag, true, true},
}
for i, c := range cases {
subject := newIt("it node", c.SubjectFlag, false)
containers := []*containernode.ContainerNode{}
for _, flag := range c.ContainerFlags {
containers = append(containers, newContainer("container", flag))
}
spec := New(subject, containers, false)
Ω(spec.Pending()).Should(Equal(c.Pending), "Case %d: %#v", i, c)
Ω(spec.Focused()).Should(Equal(c.Focused), "Case %d: %#v", i, c)
if c.Pending {
Ω(spec.Summary("").State).Should(Equal(types.SpecStatePending))
}
}
})
})
Describe("Skip", func() {
It("should be skipped", func() {
spec := New(newIt("it node", noneFlag, false), containers(newContainer("container", noneFlag)), false)
Ω(spec.Skipped()).Should(BeFalse())
spec.Skip()
Ω(spec.Skipped()).Should(BeTrue())
Ω(spec.Summary("").State).Should(Equal(types.SpecStateSkipped))
})
})
Describe("IsMeasurement", func() {
It("should be true if the subject is a measurement node", func() {
spec := New(newIt("it node", noneFlag, false), containers(newContainer("container", noneFlag)), false)
Ω(spec.IsMeasurement()).Should(BeFalse())
Ω(spec.Summary("").IsMeasurement).Should(BeFalse())
Ω(spec.Summary("").NumberOfSamples).Should(Equal(1))
spec = New(newMeasure("measure node", noneFlag, false, 10), containers(newContainer("container", noneFlag)), false)
Ω(spec.IsMeasurement()).Should(BeTrue())
Ω(spec.Summary("").IsMeasurement).Should(BeTrue())
Ω(spec.Summary("").NumberOfSamples).Should(Equal(10))
})
})
Describe("Passed", func() {
It("should pass when the subject passed", func() {
spec := New(newIt("it node", noneFlag, false), containers(), false)
spec.Run(buffer)
Ω(spec.Passed()).Should(BeTrue())
Ω(spec.Failed()).Should(BeFalse())
Ω(spec.Summary("").State).Should(Equal(types.SpecStatePassed))
Ω(spec.Summary("").Failure).Should(BeZero())
})
})
Describe("Flaked", func() {
It("should work if Run is called twice and gets different results", func() {
i := 0
spec := New(newItWithBody("flaky it", func() {
i++
if i == 1 {
failer.Fail("oops", codeLocation)
}
}), containers(), false)
spec.Run(buffer)
Ω(spec.Passed()).Should(BeFalse())
Ω(spec.Failed()).Should(BeTrue())
Ω(spec.Flaked()).Should(BeFalse())
Ω(spec.Summary("").State).Should(Equal(types.SpecStateFailed))
Ω(spec.Summary("").Failure.Message).Should(Equal("oops"))
spec.Run(buffer)
Ω(spec.Passed()).Should(BeTrue())
Ω(spec.Failed()).Should(BeFalse())
Ω(spec.Flaked()).Should(BeTrue())
Ω(spec.Summary("").State).Should(Equal(types.SpecStatePassed))
})
})
Describe("Failed", func() {
It("should be failed if the failure was panic", func() {
spec := New(newItWithBody("panicky it", func() {
panic("bam")
}), containers(), false)
spec.Run(buffer)
Ω(spec.Passed()).Should(BeFalse())
Ω(spec.Failed()).Should(BeTrue())
Ω(spec.Summary("").State).Should(Equal(types.SpecStatePanicked))
Ω(spec.Summary("").Failure.Message).Should(Equal("Test Panicked"))
Ω(spec.Summary("").Failure.ForwardedPanic).Should(Equal("bam"))
})
It("should be failed if the failure was a timeout", func() {
spec := New(newItWithBody("sleepy it", func(done Done) {}), containers(), false)
spec.Run(buffer)
Ω(spec.Passed()).Should(BeFalse())
Ω(spec.Failed()).Should(BeTrue())
Ω(spec.Summary("").State).Should(Equal(types.SpecStateTimedOut))
Ω(spec.Summary("").Failure.Message).Should(Equal("Timed out"))
})
It("should be failed if the failure was... a failure", func() {
spec := New(newItWithBody("failing it", func() {
failer.Fail("bam", codeLocation)
}), containers(), false)
spec.Run(buffer)
Ω(spec.Passed()).Should(BeFalse())
Ω(spec.Failed()).Should(BeTrue())
Ω(spec.Summary("").State).Should(Equal(types.SpecStateFailed))
Ω(spec.Summary("").Failure.Message).Should(Equal("bam"))
})
})
Describe("Concatenated string", func() {
It("should concatenate the texts of the containers and the subject", func() {
spec := New(
newIt("it node", noneFlag, false),
containers(
newContainer("outer container", noneFlag),
newContainer("inner container", noneFlag),
),
false,
)
Ω(spec.ConcatenatedString()).Should(Equal("outer container inner container it node"))
})
})
Describe("running it specs", func() {
Context("with just an it", func() {
Context("that succeeds", func() {
It("should run the it and report on its success", func() {
spec := New(newIt("it node", noneFlag, false), containers(), false)
spec.Run(buffer)
Ω(spec.Passed()).Should(BeTrue())
Ω(spec.Failed()).Should(BeFalse())
Ω(nodesThatRan).Should(Equal([]string{"it node"}))
})
})
Context("that fails", func() {
It("should run the it and report on its success", func() {
spec := New(newIt("it node", noneFlag, true), containers(), false)
spec.Run(buffer)
Ω(spec.Passed()).Should(BeFalse())
Ω(spec.Failed()).Should(BeTrue())
Ω(spec.Summary("").Failure.Message).Should(Equal("it node"))
Ω(nodesThatRan).Should(Equal([]string{"it node"}))
})
})
})
Context("with a full set of setup nodes", func() {
var failingNodes map[string]bool
BeforeEach(func() {
failingNodes = map[string]bool{}
})
JustBeforeEach(func() {
spec = New(
newIt("it node", noneFlag, failingNodes["it node"]),
containers(
newContainer("outer container", noneFlag,
newBef("outer bef A", failingNodes["outer bef A"]),
newBef("outer bef B", failingNodes["outer bef B"]),
newJusBef("outer jusbef A", failingNodes["outer jusbef A"]),
newJusBef("outer jusbef B", failingNodes["outer jusbef B"]),
newAft("outer aft A", failingNodes["outer aft A"]),
newAft("outer aft B", failingNodes["outer aft B"]),
),
newContainer("inner container", noneFlag,
newBef("inner bef A", failingNodes["inner bef A"]),
newBef("inner bef B", failingNodes["inner bef B"]),
newJusBef("inner jusbef A", failingNodes["inner jusbef A"]),
newJusBef("inner jusbef B", failingNodes["inner jusbef B"]),
newAft("inner aft A", failingNodes["inner aft A"]),
newAft("inner aft B", failingNodes["inner aft B"]),
),
),
false,
)
spec.Run(buffer)
})
Context("that all pass", func() {
It("should walk through the nodes in the correct order", func() {
Ω(spec.Passed()).Should(BeTrue())
Ω(spec.Failed()).Should(BeFalse())
Ω(nodesThatRan).Should(Equal([]string{
"outer bef A",
"outer bef B",
"inner bef A",
"inner bef B",
"outer jusbef A",
"outer jusbef B",
"inner jusbef A",
"inner jusbef B",
"it node",
"inner aft A",
"inner aft B",
"outer aft A",
"outer aft B",
}))
})
})
Context("when the subject fails", func() {
BeforeEach(func() {
failingNodes["it node"] = true
})
It("should run the afters", func() {
Ω(spec.Passed()).Should(BeFalse())
Ω(spec.Failed()).Should(BeTrue())
Ω(nodesThatRan).Should(Equal([]string{
"outer bef A",
"outer bef B",
"inner bef A",
"inner bef B",
"outer jusbef A",
"outer jusbef B",
"inner jusbef A",
"inner jusbef B",
"it node",
"inner aft A",
"inner aft B",
"outer aft A",
"outer aft B",
}))
Ω(spec.Summary("").Failure.Message).Should(Equal("it node"))
})
})
Context("when an inner before fails", func() {
BeforeEach(func() {
failingNodes["inner bef A"] = true
})
It("should not run any other befores, but it should run the subsequent afters", func() {
Ω(spec.Passed()).Should(BeFalse())
Ω(spec.Failed()).Should(BeTrue())
Ω(nodesThatRan).Should(Equal([]string{
"outer bef A",
"outer bef B",
"inner bef A",
"inner aft A",
"inner aft B",
"outer aft A",
"outer aft B",
}))
Ω(spec.Summary("").Failure.Message).Should(Equal("inner bef A"))
})
})
Context("when an outer before fails", func() {
BeforeEach(func() {
failingNodes["outer bef B"] = true
})
It("should not run any other befores, but it should run the subsequent afters", func() {
Ω(spec.Passed()).Should(BeFalse())
Ω(spec.Failed()).Should(BeTrue())
Ω(nodesThatRan).Should(Equal([]string{
"outer bef A",
"outer bef B",
"outer aft A",
"outer aft B",
}))
Ω(spec.Summary("").Failure.Message).Should(Equal("outer bef B"))
})
})
Context("when an after fails", func() {
BeforeEach(func() {
failingNodes["inner aft B"] = true
})
It("should run all other afters, but mark the test as failed", func() {
Ω(spec.Passed()).Should(BeFalse())
Ω(spec.Failed()).Should(BeTrue())
Ω(nodesThatRan).Should(Equal([]string{
"outer bef A",
"outer bef B",
"inner bef A",
"inner bef B",
"outer jusbef A",
"outer jusbef B",
"inner jusbef A",
"inner jusbef B",
"it node",
"inner aft A",
"inner aft B",
"outer aft A",
"outer aft B",
}))
Ω(spec.Summary("").Failure.Message).Should(Equal("inner aft B"))
})
})
Context("when a just before each fails", func() {
BeforeEach(func() {
failingNodes["outer jusbef B"] = true
})
It("should run the afters, but not the subject", func() {
Ω(spec.Passed()).Should(BeFalse())
Ω(spec.Failed()).Should(BeTrue())
Ω(nodesThatRan).Should(Equal([]string{
"outer bef A",
"outer bef B",
"inner bef A",
"inner bef B",
"outer jusbef A",
"outer jusbef B",
"inner aft A",
"inner aft B",
"outer aft A",
"outer aft B",
}))
Ω(spec.Summary("").Failure.Message).Should(Equal("outer jusbef B"))
})
})
Context("when an after fails after an earlier node has failed", func() {
BeforeEach(func() {
failingNodes["it node"] = true
failingNodes["inner aft B"] = true
})
It("should record the earlier failure", func() {
Ω(spec.Passed()).Should(BeFalse())
Ω(spec.Failed()).Should(BeTrue())
Ω(nodesThatRan).Should(Equal([]string{
"outer bef A",
"outer bef B",
"inner bef A",
"inner bef B",
"outer jusbef A",
"outer jusbef B",
"inner jusbef A",
"inner jusbef B",
"it node",
"inner aft A",
"inner aft B",
"outer aft A",
"outer aft B",
}))
Ω(spec.Summary("").Failure.Message).Should(Equal("it node"))
})
})
})
})
Describe("running measurement specs", func() {
Context("when the measurement succeeds", func() {
It("should run N samples", func() {
spec = New(
newMeasure("measure node", noneFlag, false, 3),
containers(
newContainer("container", noneFlag,
newBef("bef A", false),
newJusBef("jusbef A", false),
newAft("aft A", false),
),
),
false,
)
spec.Run(buffer)
Ω(spec.Passed()).Should(BeTrue())
Ω(spec.Failed()).Should(BeFalse())
Ω(nodesThatRan).Should(Equal([]string{
"bef A",
"jusbef A",
"measure node",
"aft A",
"bef A",
"jusbef A",
"measure node",
"aft A",
"bef A",
"jusbef A",
"measure node",
"aft A",
}))
})
})
Context("when the measurement fails", func() {
It("should bail after the failure occurs", func() {
spec = New(
newMeasure("measure node", noneFlag, true, 3),
containers(
newContainer("container", noneFlag,
newBef("bef A", false),
newJusBef("jusbef A", false),
newAft("aft A", false),
),
),
false,
)
spec.Run(buffer)
Ω(spec.Passed()).Should(BeFalse())
Ω(spec.Failed()).Should(BeTrue())
Ω(nodesThatRan).Should(Equal([]string{
"bef A",
"jusbef A",
"measure node",
"aft A",
}))
})
})
})
Describe("Summary", func() {
var (
subjectCodeLocation types.CodeLocation
outerContainerCodeLocation types.CodeLocation
innerContainerCodeLocation types.CodeLocation
summary *types.SpecSummary
)
BeforeEach(func() {
subjectCodeLocation = codelocation.New(0)
outerContainerCodeLocation = codelocation.New(0)
innerContainerCodeLocation = codelocation.New(0)
spec = New(
leafnodes.NewItNode("it node", func() {
time.Sleep(10 * time.Millisecond)
}, noneFlag, subjectCodeLocation, 0, failer, 0),
containers(
containernode.New("outer container", noneFlag, outerContainerCodeLocation),
containernode.New("inner container", noneFlag, innerContainerCodeLocation),
),
false,
)
spec.Run(buffer)
Ω(spec.Passed()).Should(BeTrue())
summary = spec.Summary("suite id")
})
It("should have the suite id", func() {
Ω(summary.SuiteID).Should(Equal("suite id"))
})
It("should have the component texts and code locations", func() {
Ω(summary.ComponentTexts).Should(Equal([]string{"outer container", "inner container", "it node"}))
Ω(summary.ComponentCodeLocations).Should(Equal([]types.CodeLocation{outerContainerCodeLocation, innerContainerCodeLocation, subjectCodeLocation}))
})
It("should have a runtime", func() {
Ω(summary.RunTime).Should(BeNumerically(">=", 10*time.Millisecond))
})
It("should not be a measurement, or have a measurement summary", func() {
Ω(summary.IsMeasurement).Should(BeFalse())
Ω(summary.Measurements).Should(BeEmpty())
})
})
Describe("Summaries for measurements", func() {
var summary *types.SpecSummary
BeforeEach(func() {
spec = New(leafnodes.NewMeasureNode("measure node", func(b Benchmarker) {
b.RecordValue("a value", 7, "some info")
b.RecordValueWithPrecision("another value", 8, "ns", 5, "more info")
}, noneFlag, codeLocation, 4, failer, 0), containers(), false)
spec.Run(buffer)
Ω(spec.Passed()).Should(BeTrue())
summary = spec.Summary("suite id")
})
It("should include the number of samples", func() {
Ω(summary.NumberOfSamples).Should(Equal(4))
})
It("should be a measurement", func() {
Ω(summary.IsMeasurement).Should(BeTrue())
})
It("should have the measurements report", func() {
Ω(summary.Measurements).Should(HaveKey("a value"))
report := summary.Measurements["a value"]
Ω(report.Name).Should(Equal("a value"))
Ω(report.Info).Should(Equal("some info"))
Ω(report.Results).Should(Equal([]float64{7, 7, 7, 7}))
Ω(summary.Measurements).Should(HaveKey("another value"))
report = summary.Measurements["another value"]
Ω(report.Name).Should(Equal("another value"))
Ω(report.Info).Should(Equal("more info"))
Ω(report.Results).Should(Equal([]float64{8, 8, 8, 8}))
Ω(report.Units).Should(Equal("ns"))
Ω(report.Precision).Should(Equal(5))
})
})
Describe("When told to emit progress", func() {
It("should emit progress to the writer as it runs Befores, JustBefores, Afters, and Its", func() {
spec = New(
newIt("it node", noneFlag, false),
containers(
newContainer("outer container", noneFlag,
newBef("outer bef A", false),
newJusBef("outer jusbef A", false),
newAft("outer aft A", false),
),
newContainer("inner container", noneFlag,
newBef("inner bef A", false),
newJusBef("inner jusbef A", false),
newAft("inner aft A", false),
),
),
true,
)
spec.Run(buffer)
Ω(buffer).Should(gbytes.Say(`\[BeforeEach\] outer container`))
Ω(buffer).Should(gbytes.Say(`\[BeforeEach\] inner container`))
Ω(buffer).Should(gbytes.Say(`\[JustBeforeEach\] outer container`))
Ω(buffer).Should(gbytes.Say(`\[JustBeforeEach\] inner container`))
Ω(buffer).Should(gbytes.Say(`\[It\] it node`))
Ω(buffer).Should(gbytes.Say(`\[AfterEach\] inner container`))
Ω(buffer).Should(gbytes.Say(`\[AfterEach\] outer container`))
})
It("should emit progress to the writer as it runs Befores, JustBefores, Afters, and Measures", func() {
spec = New(
newMeasure("measure node", noneFlag, false, 2),
containers(),
true,
)
spec.Run(buffer)
Ω(buffer).Should(gbytes.Say(`\[Measure\] measure node`))
Ω(buffer).Should(gbytes.Say(`\[Measure\] measure node`))
})
})
})

123
vendor/github.com/onsi/ginkgo/internal/spec/specs.go generated vendored Normal file
View file

@ -0,0 +1,123 @@
package spec
import (
"math/rand"
"regexp"
"sort"
)
type Specs struct {
specs []*Spec
hasProgrammaticFocus bool
RegexScansFilePath bool
}
func NewSpecs(specs []*Spec) *Specs {
return &Specs{
specs: specs,
}
}
func (e *Specs) Specs() []*Spec {
return e.specs
}
func (e *Specs) HasProgrammaticFocus() bool {
return e.hasProgrammaticFocus
}
func (e *Specs) Shuffle(r *rand.Rand) {
sort.Sort(e)
permutation := r.Perm(len(e.specs))
shuffledSpecs := make([]*Spec, len(e.specs))
for i, j := range permutation {
shuffledSpecs[i] = e.specs[j]
}
e.specs = shuffledSpecs
}
func (e *Specs) ApplyFocus(description string, focusString string, skipString string) {
if focusString == "" && skipString == "" {
e.applyProgrammaticFocus()
} else {
e.applyRegExpFocusAndSkip(description, focusString, skipString)
}
}
func (e *Specs) applyProgrammaticFocus() {
e.hasProgrammaticFocus = false
for _, spec := range e.specs {
if spec.Focused() && !spec.Pending() {
e.hasProgrammaticFocus = true
break
}
}
if e.hasProgrammaticFocus {
for _, spec := range e.specs {
if !spec.Focused() {
spec.Skip()
}
}
}
}
// toMatch returns a byte[] to be used by regex matchers. When adding new behaviours to the matching function,
// this is the place which we append to.
func (e *Specs) toMatch(description string, spec *Spec) []byte {
if e.RegexScansFilePath {
return []byte(
description + " " +
spec.ConcatenatedString() + " " +
spec.subject.CodeLocation().FileName)
} else {
return []byte(
description + " " +
spec.ConcatenatedString())
}
}
func (e *Specs) applyRegExpFocusAndSkip(description string, focusString string, skipString string) {
for _, spec := range e.specs {
matchesFocus := true
matchesSkip := false
toMatch := e.toMatch(description, spec)
if focusString != "" {
focusFilter := regexp.MustCompile(focusString)
matchesFocus = focusFilter.Match([]byte(toMatch))
}
if skipString != "" {
skipFilter := regexp.MustCompile(skipString)
matchesSkip = skipFilter.Match([]byte(toMatch))
}
if !matchesFocus || matchesSkip {
spec.Skip()
}
}
}
func (e *Specs) SkipMeasurements() {
for _, spec := range e.specs {
if spec.IsMeasurement() {
spec.Skip()
}
}
}
//sort.Interface
func (e *Specs) Len() int {
return len(e.specs)
}
func (e *Specs) Less(i, j int) bool {
return e.specs[i].ConcatenatedString() < e.specs[j].ConcatenatedString()
}
func (e *Specs) Swap(i, j int) {
e.specs[i], e.specs[j] = e.specs[j], e.specs[i]
}

View file

@ -0,0 +1,287 @@
package spec_test
import (
"math/rand"
. "github.com/onsi/ginkgo"
. "github.com/onsi/ginkgo/internal/spec"
. "github.com/onsi/gomega"
"github.com/onsi/ginkgo/internal/codelocation"
"github.com/onsi/ginkgo/internal/containernode"
"github.com/onsi/ginkgo/internal/leafnodes"
"github.com/onsi/ginkgo/types"
)
var _ = Describe("Specs", func() {
var specs *Specs
newSpec := func(text string, flag types.FlagType) *Spec {
subject := leafnodes.NewItNode(text, func() {}, flag, codelocation.New(0), 0, nil, 0)
return New(subject, []*containernode.ContainerNode{}, false)
}
newMeasureSpec := func(text string, flag types.FlagType) *Spec {
subject := leafnodes.NewMeasureNode(text, func(Benchmarker) {}, flag, codelocation.New(0), 0, nil, 0)
return New(subject, []*containernode.ContainerNode{}, false)
}
newSpecs := func(args ...interface{}) *Specs {
specs := []*Spec{}
for index := 0; index < len(args)-1; index += 2 {
specs = append(specs, newSpec(args[index].(string), args[index+1].(types.FlagType)))
}
return NewSpecs(specs)
}
specTexts := func(specs *Specs) []string {
texts := []string{}
for _, spec := range specs.Specs() {
texts = append(texts, spec.ConcatenatedString())
}
return texts
}
willRunTexts := func(specs *Specs) []string {
texts := []string{}
for _, spec := range specs.Specs() {
if !(spec.Skipped() || spec.Pending()) {
texts = append(texts, spec.ConcatenatedString())
}
}
return texts
}
skippedTexts := func(specs *Specs) []string {
texts := []string{}
for _, spec := range specs.Specs() {
if spec.Skipped() {
texts = append(texts, spec.ConcatenatedString())
}
}
return texts
}
pendingTexts := func(specs *Specs) []string {
texts := []string{}
for _, spec := range specs.Specs() {
if spec.Pending() {
texts = append(texts, spec.ConcatenatedString())
}
}
return texts
}
Describe("Shuffling specs", func() {
It("should shuffle the specs using the passed in randomizer", func() {
specs17 := newSpecs("C", noneFlag, "A", noneFlag, "B", noneFlag)
specs17.Shuffle(rand.New(rand.NewSource(17)))
texts17 := specTexts(specs17)
specs17Again := newSpecs("C", noneFlag, "A", noneFlag, "B", noneFlag)
specs17Again.Shuffle(rand.New(rand.NewSource(17)))
texts17Again := specTexts(specs17Again)
specs15 := newSpecs("C", noneFlag, "A", noneFlag, "B", noneFlag)
specs15.Shuffle(rand.New(rand.NewSource(15)))
texts15 := specTexts(specs15)
specsUnshuffled := newSpecs("C", noneFlag, "A", noneFlag, "B", noneFlag)
textsUnshuffled := specTexts(specsUnshuffled)
Ω(textsUnshuffled).Should(Equal([]string{"C", "A", "B"}))
Ω(texts17).Should(Equal(texts17Again))
Ω(texts17).ShouldNot(Equal(texts15))
Ω(texts17).ShouldNot(Equal(textsUnshuffled))
Ω(texts15).ShouldNot(Equal(textsUnshuffled))
Ω(texts17).Should(HaveLen(3))
Ω(texts17).Should(ContainElement("A"))
Ω(texts17).Should(ContainElement("B"))
Ω(texts17).Should(ContainElement("C"))
Ω(texts15).Should(HaveLen(3))
Ω(texts15).Should(ContainElement("A"))
Ω(texts15).Should(ContainElement("B"))
Ω(texts15).Should(ContainElement("C"))
})
})
Describe("with no programmatic focus", func() {
BeforeEach(func() {
specs = newSpecs("A1", noneFlag, "A2", noneFlag, "B1", noneFlag, "B2", pendingFlag)
specs.ApplyFocus("", "", "")
})
It("should not report as having programmatic specs", func() {
Ω(specs.HasProgrammaticFocus()).Should(BeFalse())
})
})
Describe("Applying focus/skip", func() {
var description, focusString, skipString string
BeforeEach(func() {
description, focusString, skipString = "", "", ""
})
JustBeforeEach(func() {
specs = newSpecs("A1", focusedFlag, "A2", noneFlag, "B1", focusedFlag, "B2", pendingFlag)
specs.ApplyFocus(description, focusString, skipString)
})
Context("with neither a focus string nor a skip string", func() {
It("should apply the programmatic focus", func() {
Ω(willRunTexts(specs)).Should(Equal([]string{"A1", "B1"}))
Ω(skippedTexts(specs)).Should(Equal([]string{"A2", "B2"}))
Ω(pendingTexts(specs)).Should(BeEmpty())
})
It("should report as having programmatic specs", func() {
Ω(specs.HasProgrammaticFocus()).Should(BeTrue())
})
})
Context("with a focus regexp", func() {
BeforeEach(func() {
focusString = "A"
})
It("should override the programmatic focus", func() {
Ω(willRunTexts(specs)).Should(Equal([]string{"A1", "A2"}))
Ω(skippedTexts(specs)).Should(Equal([]string{"B1", "B2"}))
Ω(pendingTexts(specs)).Should(BeEmpty())
})
It("should not report as having programmatic specs", func() {
Ω(specs.HasProgrammaticFocus()).Should(BeFalse())
})
})
Context("with a focus regexp", func() {
BeforeEach(func() {
focusString = "B"
})
It("should not override any pendings", func() {
Ω(willRunTexts(specs)).Should(Equal([]string{"B1"}))
Ω(skippedTexts(specs)).Should(Equal([]string{"A1", "A2"}))
Ω(pendingTexts(specs)).Should(Equal([]string{"B2"}))
})
})
Context("with a description", func() {
BeforeEach(func() {
description = "C"
focusString = "C"
})
It("should include the description in the focus determination", func() {
Ω(willRunTexts(specs)).Should(Equal([]string{"A1", "A2", "B1"}))
Ω(skippedTexts(specs)).Should(BeEmpty())
Ω(pendingTexts(specs)).Should(Equal([]string{"B2"}))
})
})
Context("with a description", func() {
BeforeEach(func() {
description = "C"
skipString = "C"
})
It("should include the description in the focus determination", func() {
Ω(willRunTexts(specs)).Should(BeEmpty())
Ω(skippedTexts(specs)).Should(Equal([]string{"A1", "A2", "B1", "B2"}))
Ω(pendingTexts(specs)).Should(BeEmpty())
})
})
Context("with a skip regexp", func() {
BeforeEach(func() {
skipString = "A"
})
It("should override the programmatic focus", func() {
Ω(willRunTexts(specs)).Should(Equal([]string{"B1"}))
Ω(skippedTexts(specs)).Should(Equal([]string{"A1", "A2"}))
Ω(pendingTexts(specs)).Should(Equal([]string{"B2"}))
})
It("should not report as having programmatic specs", func() {
Ω(specs.HasProgrammaticFocus()).Should(BeFalse())
})
})
Context("with both a focus and a skip regexp", func() {
BeforeEach(func() {
focusString = "1"
skipString = "B"
})
It("should AND the two", func() {
Ω(willRunTexts(specs)).Should(Equal([]string{"A1"}))
Ω(skippedTexts(specs)).Should(Equal([]string{"A2", "B1", "B2"}))
Ω(pendingTexts(specs)).Should(BeEmpty())
})
It("should not report as having programmatic specs", func() {
Ω(specs.HasProgrammaticFocus()).Should(BeFalse())
})
})
})
Describe("With a focused spec within a pending context and a pending spec within a focused context", func() {
BeforeEach(func() {
pendingInFocused := New(
leafnodes.NewItNode("PendingInFocused", func() {}, pendingFlag, codelocation.New(0), 0, nil, 0),
[]*containernode.ContainerNode{
containernode.New("", focusedFlag, codelocation.New(0)),
}, false)
focusedInPending := New(
leafnodes.NewItNode("FocusedInPending", func() {}, focusedFlag, codelocation.New(0), 0, nil, 0),
[]*containernode.ContainerNode{
containernode.New("", pendingFlag, codelocation.New(0)),
}, false)
specs = NewSpecs([]*Spec{
newSpec("A", noneFlag),
newSpec("B", noneFlag),
pendingInFocused,
focusedInPending,
})
specs.ApplyFocus("", "", "")
})
It("should not have a programmatic focus and should run all tests", func() {
Ω(willRunTexts(specs)).Should(Equal([]string{"A", "B"}))
Ω(skippedTexts(specs)).Should(BeEmpty())
Ω(pendingTexts(specs)).Should(ConsistOf(ContainSubstring("PendingInFocused"), ContainSubstring("FocusedInPending")))
})
})
Describe("skipping measurements", func() {
BeforeEach(func() {
specs = NewSpecs([]*Spec{
newSpec("A", noneFlag),
newSpec("B", noneFlag),
newSpec("C", pendingFlag),
newMeasureSpec("measurementA", noneFlag),
newMeasureSpec("measurementB", pendingFlag),
})
})
It("should skip measurements", func() {
Ω(willRunTexts(specs)).Should(Equal([]string{"A", "B", "measurementA"}))
Ω(skippedTexts(specs)).Should(BeEmpty())
Ω(pendingTexts(specs)).Should(Equal([]string{"C", "measurementB"}))
specs.SkipMeasurements()
Ω(willRunTexts(specs)).Should(Equal([]string{"A", "B"}))
Ω(skippedTexts(specs)).Should(Equal([]string{"measurementA", "measurementB"}))
Ω(pendingTexts(specs)).Should(Equal([]string{"C"}))
})
})
})

View file

@ -0,0 +1,55 @@
package spec_iterator
func ParallelizedIndexRange(length int, parallelTotal int, parallelNode int) (startIndex int, count int) {
if length == 0 {
return 0, 0
}
// We have more nodes than tests. Trivial case.
if parallelTotal >= length {
if parallelNode > length {
return 0, 0
} else {
return parallelNode - 1, 1
}
}
// This is the minimum amount of tests that a node will be required to run
minTestsPerNode := length / parallelTotal
// This is the maximum amount of tests that a node will be required to run
// The algorithm guarantees that this would be equal to at least the minimum amount
// and at most one more
maxTestsPerNode := minTestsPerNode
if length%parallelTotal != 0 {
maxTestsPerNode++
}
// Number of nodes that will have to run the maximum amount of tests per node
numMaxLoadNodes := length % parallelTotal
// Number of nodes that precede the current node and will have to run the maximum amount of tests per node
var numPrecedingMaxLoadNodes int
if parallelNode > numMaxLoadNodes {
numPrecedingMaxLoadNodes = numMaxLoadNodes
} else {
numPrecedingMaxLoadNodes = parallelNode - 1
}
// Number of nodes that precede the current node and will have to run the minimum amount of tests per node
var numPrecedingMinLoadNodes int
if parallelNode <= numMaxLoadNodes {
numPrecedingMinLoadNodes = 0
} else {
numPrecedingMinLoadNodes = parallelNode - numMaxLoadNodes - 1
}
// Evaluate the test start index and number of tests to run
startIndex = numPrecedingMaxLoadNodes*maxTestsPerNode + numPrecedingMinLoadNodes*minTestsPerNode
if parallelNode > numMaxLoadNodes {
count = minTestsPerNode
} else {
count = maxTestsPerNode
}
return
}

View file

@ -0,0 +1,149 @@
package spec_iterator_test
import (
. "github.com/onsi/ginkgo"
. "github.com/onsi/ginkgo/internal/spec_iterator"
. "github.com/onsi/gomega"
)
var _ = Describe("ParallelizedIndexRange", func() {
var startIndex, count int
It("should return the correct index range for 4 tests on 2 nodes", func() {
startIndex, count = ParallelizedIndexRange(4, 2, 1)
Ω(startIndex).Should(Equal(0))
Ω(count).Should(Equal(2))
startIndex, count = ParallelizedIndexRange(4, 2, 2)
Ω(startIndex).Should(Equal(2))
Ω(count).Should(Equal(2))
})
It("should return the correct index range for 5 tests on 2 nodes", func() {
startIndex, count = ParallelizedIndexRange(5, 2, 1)
Ω(startIndex).Should(Equal(0))
Ω(count).Should(Equal(3))
startIndex, count = ParallelizedIndexRange(5, 2, 2)
Ω(startIndex).Should(Equal(3))
Ω(count).Should(Equal(2))
})
It("should return the correct index range for 5 tests on 3 nodes", func() {
startIndex, count = ParallelizedIndexRange(5, 3, 1)
Ω(startIndex).Should(Equal(0))
Ω(count).Should(Equal(2))
startIndex, count = ParallelizedIndexRange(5, 3, 2)
Ω(startIndex).Should(Equal(2))
Ω(count).Should(Equal(2))
startIndex, count = ParallelizedIndexRange(5, 3, 3)
Ω(startIndex).Should(Equal(4))
Ω(count).Should(Equal(1))
})
It("should return the correct index range for 5 tests on 4 nodes", func() {
startIndex, count = ParallelizedIndexRange(5, 4, 1)
Ω(startIndex).Should(Equal(0))
Ω(count).Should(Equal(2))
startIndex, count = ParallelizedIndexRange(5, 4, 2)
Ω(startIndex).Should(Equal(2))
Ω(count).Should(Equal(1))
startIndex, count = ParallelizedIndexRange(5, 4, 3)
Ω(startIndex).Should(Equal(3))
Ω(count).Should(Equal(1))
startIndex, count = ParallelizedIndexRange(5, 4, 4)
Ω(startIndex).Should(Equal(4))
Ω(count).Should(Equal(1))
})
It("should return the correct index range for 5 tests on 5 nodes", func() {
startIndex, count = ParallelizedIndexRange(5, 5, 1)
Ω(startIndex).Should(Equal(0))
Ω(count).Should(Equal(1))
startIndex, count = ParallelizedIndexRange(5, 5, 2)
Ω(startIndex).Should(Equal(1))
Ω(count).Should(Equal(1))
startIndex, count = ParallelizedIndexRange(5, 5, 3)
Ω(startIndex).Should(Equal(2))
Ω(count).Should(Equal(1))
startIndex, count = ParallelizedIndexRange(5, 5, 4)
Ω(startIndex).Should(Equal(3))
Ω(count).Should(Equal(1))
startIndex, count = ParallelizedIndexRange(5, 5, 5)
Ω(startIndex).Should(Equal(4))
Ω(count).Should(Equal(1))
})
It("should return the correct index range for 5 tests on 6 nodes", func() {
startIndex, count = ParallelizedIndexRange(5, 6, 1)
Ω(startIndex).Should(Equal(0))
Ω(count).Should(Equal(1))
startIndex, count = ParallelizedIndexRange(5, 6, 2)
Ω(startIndex).Should(Equal(1))
Ω(count).Should(Equal(1))
startIndex, count = ParallelizedIndexRange(5, 6, 3)
Ω(startIndex).Should(Equal(2))
Ω(count).Should(Equal(1))
startIndex, count = ParallelizedIndexRange(5, 6, 4)
Ω(startIndex).Should(Equal(3))
Ω(count).Should(Equal(1))
startIndex, count = ParallelizedIndexRange(5, 6, 5)
Ω(startIndex).Should(Equal(4))
Ω(count).Should(Equal(1))
startIndex, count = ParallelizedIndexRange(5, 6, 6)
Ω(count).Should(Equal(0))
})
It("should return the correct index range for 5 tests on 7 nodes", func() {
startIndex, count = ParallelizedIndexRange(5, 7, 6)
Ω(count).Should(Equal(0))
startIndex, count = ParallelizedIndexRange(5, 7, 7)
Ω(count).Should(Equal(0))
})
It("should return the correct index range for 11 tests on 7 nodes", func() {
startIndex, count = ParallelizedIndexRange(11, 7, 1)
Ω(startIndex).Should(Equal(0))
Ω(count).Should(Equal(2))
startIndex, count = ParallelizedIndexRange(11, 7, 2)
Ω(startIndex).Should(Equal(2))
Ω(count).Should(Equal(2))
startIndex, count = ParallelizedIndexRange(11, 7, 3)
Ω(startIndex).Should(Equal(4))
Ω(count).Should(Equal(2))
startIndex, count = ParallelizedIndexRange(11, 7, 4)
Ω(startIndex).Should(Equal(6))
Ω(count).Should(Equal(2))
startIndex, count = ParallelizedIndexRange(11, 7, 5)
Ω(startIndex).Should(Equal(8))
Ω(count).Should(Equal(1))
startIndex, count = ParallelizedIndexRange(11, 7, 6)
Ω(startIndex).Should(Equal(9))
Ω(count).Should(Equal(1))
startIndex, count = ParallelizedIndexRange(11, 7, 7)
Ω(startIndex).Should(Equal(10))
Ω(count).Should(Equal(1))
})
})

View file

@ -0,0 +1,60 @@
package spec_iterator
import (
"encoding/json"
"errors"
"fmt"
"net/http"
"github.com/onsi/ginkgo/internal/spec"
)
type ParallelIterator struct {
specs []*spec.Spec
host string
client *http.Client
}
func NewParallelIterator(specs []*spec.Spec, host string) *ParallelIterator {
return &ParallelIterator{
specs: specs,
host: host,
client: &http.Client{},
}
}
func (s *ParallelIterator) Next() (*spec.Spec, error) {
resp, err := s.client.Get(s.host + "/counter")
if err != nil {
return nil, err
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return nil, errors.New(fmt.Sprintf("unexpected status code %d", resp.StatusCode))
}
var counter Counter
err = json.NewDecoder(resp.Body).Decode(&counter)
if err != nil {
return nil, err
}
if counter.Index >= len(s.specs) {
return nil, ErrClosed
}
return s.specs[counter.Index], nil
}
func (s *ParallelIterator) NumberOfSpecsPriorToIteration() int {
return len(s.specs)
}
func (s *ParallelIterator) NumberOfSpecsToProcessIfKnown() (int, bool) {
return -1, false
}
func (s *ParallelIterator) NumberOfSpecsThatWillBeRunIfKnown() (int, bool) {
return -1, false
}

View file

@ -0,0 +1,112 @@
package spec_iterator_test
import (
"net/http"
. "github.com/onsi/ginkgo/internal/spec_iterator"
"github.com/onsi/gomega/ghttp"
"github.com/onsi/ginkgo/internal/codelocation"
"github.com/onsi/ginkgo/internal/containernode"
"github.com/onsi/ginkgo/internal/leafnodes"
"github.com/onsi/ginkgo/internal/spec"
"github.com/onsi/ginkgo/types"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
)
var _ = Describe("ParallelSpecIterator", func() {
var specs []*spec.Spec
var iterator *ParallelIterator
var server *ghttp.Server
newSpec := func(text string, flag types.FlagType) *spec.Spec {
subject := leafnodes.NewItNode(text, func() {}, flag, codelocation.New(0), 0, nil, 0)
return spec.New(subject, []*containernode.ContainerNode{}, false)
}
BeforeEach(func() {
specs = []*spec.Spec{
newSpec("A", types.FlagTypePending),
newSpec("B", types.FlagTypeNone),
newSpec("C", types.FlagTypeNone),
newSpec("D", types.FlagTypeNone),
}
specs[3].Skip()
server = ghttp.NewServer()
iterator = NewParallelIterator(specs, "http://"+server.Addr())
})
AfterEach(func() {
server.Close()
})
It("should report the total number of specs", func() {
Ω(iterator.NumberOfSpecsPriorToIteration()).Should(Equal(4))
})
It("should not report the number to be processed", func() {
n, known := iterator.NumberOfSpecsToProcessIfKnown()
Ω(n).Should(Equal(-1))
Ω(known).Should(BeFalse())
})
It("should not report the number that will be run", func() {
n, known := iterator.NumberOfSpecsThatWillBeRunIfKnown()
Ω(n).Should(Equal(-1))
Ω(known).Should(BeFalse())
})
Describe("iterating", func() {
Describe("when the server returns well-formed responses", func() {
BeforeEach(func() {
server.AppendHandlers(
ghttp.RespondWithJSONEncoded(http.StatusOK, Counter{0}),
ghttp.RespondWithJSONEncoded(http.StatusOK, Counter{1}),
ghttp.RespondWithJSONEncoded(http.StatusOK, Counter{3}),
ghttp.RespondWithJSONEncoded(http.StatusOK, Counter{4}),
)
})
It("should return the specs in question", func() {
Ω(iterator.Next()).Should(Equal(specs[0]))
Ω(iterator.Next()).Should(Equal(specs[1]))
Ω(iterator.Next()).Should(Equal(specs[3]))
spec, err := iterator.Next()
Ω(spec).Should(BeNil())
Ω(err).Should(MatchError(ErrClosed))
})
})
Describe("when the server 404s", func() {
BeforeEach(func() {
server.AppendHandlers(
ghttp.RespondWith(http.StatusNotFound, ""),
)
})
It("should return an error", func() {
spec, err := iterator.Next()
Ω(spec).Should(BeNil())
Ω(err).Should(MatchError("unexpected status code 404"))
})
})
Describe("when the server returns gibberish", func() {
BeforeEach(func() {
server.AppendHandlers(
ghttp.RespondWith(http.StatusOK, "ß"),
)
})
It("should error", func() {
spec, err := iterator.Next()
Ω(spec).Should(BeNil())
Ω(err).ShouldNot(BeNil())
})
})
})
})

View file

@ -0,0 +1,45 @@
package spec_iterator
import (
"github.com/onsi/ginkgo/internal/spec"
)
type SerialIterator struct {
specs []*spec.Spec
index int
}
func NewSerialIterator(specs []*spec.Spec) *SerialIterator {
return &SerialIterator{
specs: specs,
index: 0,
}
}
func (s *SerialIterator) Next() (*spec.Spec, error) {
if s.index >= len(s.specs) {
return nil, ErrClosed
}
spec := s.specs[s.index]
s.index += 1
return spec, nil
}
func (s *SerialIterator) NumberOfSpecsPriorToIteration() int {
return len(s.specs)
}
func (s *SerialIterator) NumberOfSpecsToProcessIfKnown() (int, bool) {
return len(s.specs), true
}
func (s *SerialIterator) NumberOfSpecsThatWillBeRunIfKnown() (int, bool) {
count := 0
for _, s := range s.specs {
if !s.Skipped() && !s.Pending() {
count += 1
}
}
return count, true
}

View file

@ -0,0 +1,64 @@
package spec_iterator_test
import (
. "github.com/onsi/ginkgo/internal/spec_iterator"
"github.com/onsi/ginkgo/internal/codelocation"
"github.com/onsi/ginkgo/internal/containernode"
"github.com/onsi/ginkgo/internal/leafnodes"
"github.com/onsi/ginkgo/internal/spec"
"github.com/onsi/ginkgo/types"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
)
var _ = Describe("SerialSpecIterator", func() {
var specs []*spec.Spec
var iterator *SerialIterator
newSpec := func(text string, flag types.FlagType) *spec.Spec {
subject := leafnodes.NewItNode(text, func() {}, flag, codelocation.New(0), 0, nil, 0)
return spec.New(subject, []*containernode.ContainerNode{}, false)
}
BeforeEach(func() {
specs = []*spec.Spec{
newSpec("A", types.FlagTypePending),
newSpec("B", types.FlagTypeNone),
newSpec("C", types.FlagTypeNone),
newSpec("D", types.FlagTypeNone),
}
specs[3].Skip()
iterator = NewSerialIterator(specs)
})
It("should report the total number of specs", func() {
Ω(iterator.NumberOfSpecsPriorToIteration()).Should(Equal(4))
})
It("should report the number to be processed", func() {
n, known := iterator.NumberOfSpecsToProcessIfKnown()
Ω(n).Should(Equal(4))
Ω(known).Should(BeTrue())
})
It("should report the number that will be run", func() {
n, known := iterator.NumberOfSpecsThatWillBeRunIfKnown()
Ω(n).Should(Equal(2))
Ω(known).Should(BeTrue())
})
Describe("iterating", func() {
It("should return the specs in order", func() {
Ω(iterator.Next()).Should(Equal(specs[0]))
Ω(iterator.Next()).Should(Equal(specs[1]))
Ω(iterator.Next()).Should(Equal(specs[2]))
Ω(iterator.Next()).Should(Equal(specs[3]))
spec, err := iterator.Next()
Ω(spec).Should(BeNil())
Ω(err).Should(MatchError(ErrClosed))
})
})
})

View file

@ -0,0 +1,47 @@
package spec_iterator
import "github.com/onsi/ginkgo/internal/spec"
type ShardedParallelIterator struct {
specs []*spec.Spec
index int
maxIndex int
}
func NewShardedParallelIterator(specs []*spec.Spec, total int, node int) *ShardedParallelIterator {
startIndex, count := ParallelizedIndexRange(len(specs), total, node)
return &ShardedParallelIterator{
specs: specs,
index: startIndex,
maxIndex: startIndex + count,
}
}
func (s *ShardedParallelIterator) Next() (*spec.Spec, error) {
if s.index >= s.maxIndex {
return nil, ErrClosed
}
spec := s.specs[s.index]
s.index += 1
return spec, nil
}
func (s *ShardedParallelIterator) NumberOfSpecsPriorToIteration() int {
return len(s.specs)
}
func (s *ShardedParallelIterator) NumberOfSpecsToProcessIfKnown() (int, bool) {
return s.maxIndex - s.index, true
}
func (s *ShardedParallelIterator) NumberOfSpecsThatWillBeRunIfKnown() (int, bool) {
count := 0
for i := s.index; i < s.maxIndex; i += 1 {
if !s.specs[i].Skipped() && !s.specs[i].Pending() {
count += 1
}
}
return count, true
}

View file

@ -0,0 +1,62 @@
package spec_iterator_test
import (
. "github.com/onsi/ginkgo/internal/spec_iterator"
"github.com/onsi/ginkgo/internal/codelocation"
"github.com/onsi/ginkgo/internal/containernode"
"github.com/onsi/ginkgo/internal/leafnodes"
"github.com/onsi/ginkgo/internal/spec"
"github.com/onsi/ginkgo/types"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
)
var _ = Describe("ShardedParallelSpecIterator", func() {
var specs []*spec.Spec
var iterator *ShardedParallelIterator
newSpec := func(text string, flag types.FlagType) *spec.Spec {
subject := leafnodes.NewItNode(text, func() {}, flag, codelocation.New(0), 0, nil, 0)
return spec.New(subject, []*containernode.ContainerNode{}, false)
}
BeforeEach(func() {
specs = []*spec.Spec{
newSpec("A", types.FlagTypePending),
newSpec("B", types.FlagTypeNone),
newSpec("C", types.FlagTypeNone),
newSpec("D", types.FlagTypeNone),
}
specs[3].Skip()
iterator = NewShardedParallelIterator(specs, 2, 1)
})
It("should report the total number of specs", func() {
Ω(iterator.NumberOfSpecsPriorToIteration()).Should(Equal(4))
})
It("should report the number to be processed", func() {
n, known := iterator.NumberOfSpecsToProcessIfKnown()
Ω(n).Should(Equal(2))
Ω(known).Should(BeTrue())
})
It("should report the number that will be run", func() {
n, known := iterator.NumberOfSpecsThatWillBeRunIfKnown()
Ω(n).Should(Equal(1))
Ω(known).Should(BeTrue())
})
Describe("iterating", func() {
It("should return the specs in order", func() {
Ω(iterator.Next()).Should(Equal(specs[0]))
Ω(iterator.Next()).Should(Equal(specs[1]))
spec, err := iterator.Next()
Ω(spec).Should(BeNil())
Ω(err).Should(MatchError(ErrClosed))
})
})
})

View file

@ -0,0 +1,20 @@
package spec_iterator
import (
"errors"
"github.com/onsi/ginkgo/internal/spec"
)
var ErrClosed = errors.New("no more specs to run")
type SpecIterator interface {
Next() (*spec.Spec, error)
NumberOfSpecsPriorToIteration() int
NumberOfSpecsToProcessIfKnown() (int, bool)
NumberOfSpecsThatWillBeRunIfKnown() (int, bool)
}
type Counter struct {
Index int `json:"index"`
}

View file

@ -0,0 +1,13 @@
package spec_iterator_test
import (
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
"testing"
)
func TestSpecIterator(t *testing.T) {
RegisterFailHandler(Fail)
RunSpecs(t, "SpecIterator Suite")
}

View file

@ -0,0 +1,15 @@
package specrunner
import (
"crypto/rand"
"fmt"
)
func randomID() string {
b := make([]byte, 8)
_, err := rand.Read(b)
if err != nil {
return ""
}
return fmt.Sprintf("%x-%x-%x-%x", b[0:2], b[2:4], b[4:6], b[6:8])
}

View file

@ -0,0 +1,408 @@
package specrunner
import (
"fmt"
"os"
"os/signal"
"sync"
"syscall"
"github.com/onsi/ginkgo/internal/spec_iterator"
"github.com/onsi/ginkgo/config"
"github.com/onsi/ginkgo/internal/leafnodes"
"github.com/onsi/ginkgo/internal/spec"
Writer "github.com/onsi/ginkgo/internal/writer"
"github.com/onsi/ginkgo/reporters"
"github.com/onsi/ginkgo/types"
"time"
)
type SpecRunner struct {
description string
beforeSuiteNode leafnodes.SuiteNode
iterator spec_iterator.SpecIterator
afterSuiteNode leafnodes.SuiteNode
reporters []reporters.Reporter
startTime time.Time
suiteID string
runningSpec *spec.Spec
writer Writer.WriterInterface
config config.GinkgoConfigType
interrupted bool
processedSpecs []*spec.Spec
lock *sync.Mutex
}
func New(description string, beforeSuiteNode leafnodes.SuiteNode, iterator spec_iterator.SpecIterator, afterSuiteNode leafnodes.SuiteNode, reporters []reporters.Reporter, writer Writer.WriterInterface, config config.GinkgoConfigType) *SpecRunner {
return &SpecRunner{
description: description,
beforeSuiteNode: beforeSuiteNode,
iterator: iterator,
afterSuiteNode: afterSuiteNode,
reporters: reporters,
writer: writer,
config: config,
suiteID: randomID(),
lock: &sync.Mutex{},
}
}
func (runner *SpecRunner) Run() bool {
if runner.config.DryRun {
runner.performDryRun()
return true
}
runner.reportSuiteWillBegin()
go runner.registerForInterrupts()
suitePassed := runner.runBeforeSuite()
if suitePassed {
suitePassed = runner.runSpecs()
}
runner.blockForeverIfInterrupted()
suitePassed = runner.runAfterSuite() && suitePassed
runner.reportSuiteDidEnd(suitePassed)
return suitePassed
}
func (runner *SpecRunner) performDryRun() {
runner.reportSuiteWillBegin()
if runner.beforeSuiteNode != nil {
summary := runner.beforeSuiteNode.Summary()
summary.State = types.SpecStatePassed
runner.reportBeforeSuite(summary)
}
for {
spec, err := runner.iterator.Next()
if err == spec_iterator.ErrClosed {
break
}
if err != nil {
fmt.Println("failed to iterate over tests:\n" + err.Error())
break
}
runner.processedSpecs = append(runner.processedSpecs, spec)
summary := spec.Summary(runner.suiteID)
runner.reportSpecWillRun(summary)
if summary.State == types.SpecStateInvalid {
summary.State = types.SpecStatePassed
}
runner.reportSpecDidComplete(summary, false)
}
if runner.afterSuiteNode != nil {
summary := runner.afterSuiteNode.Summary()
summary.State = types.SpecStatePassed
runner.reportAfterSuite(summary)
}
runner.reportSuiteDidEnd(true)
}
func (runner *SpecRunner) runBeforeSuite() bool {
if runner.beforeSuiteNode == nil || runner.wasInterrupted() {
return true
}
runner.writer.Truncate()
conf := runner.config
passed := runner.beforeSuiteNode.Run(conf.ParallelNode, conf.ParallelTotal, conf.SyncHost)
if !passed {
runner.writer.DumpOut()
}
runner.reportBeforeSuite(runner.beforeSuiteNode.Summary())
return passed
}
func (runner *SpecRunner) runAfterSuite() bool {
if runner.afterSuiteNode == nil {
return true
}
runner.writer.Truncate()
conf := runner.config
passed := runner.afterSuiteNode.Run(conf.ParallelNode, conf.ParallelTotal, conf.SyncHost)
if !passed {
runner.writer.DumpOut()
}
runner.reportAfterSuite(runner.afterSuiteNode.Summary())
return passed
}
func (runner *SpecRunner) runSpecs() bool {
suiteFailed := false
skipRemainingSpecs := false
for {
spec, err := runner.iterator.Next()
if err == spec_iterator.ErrClosed {
break
}
if err != nil {
fmt.Println("failed to iterate over tests:\n" + err.Error())
suiteFailed = true
break
}
runner.processedSpecs = append(runner.processedSpecs, spec)
if runner.wasInterrupted() {
break
}
if skipRemainingSpecs {
spec.Skip()
}
if !spec.Skipped() && !spec.Pending() {
if passed := runner.runSpec(spec); !passed {
suiteFailed = true
}
} else if spec.Pending() && runner.config.FailOnPending {
runner.reportSpecWillRun(spec.Summary(runner.suiteID))
suiteFailed = true
runner.reportSpecDidComplete(spec.Summary(runner.suiteID), spec.Failed())
} else {
runner.reportSpecWillRun(spec.Summary(runner.suiteID))
runner.reportSpecDidComplete(spec.Summary(runner.suiteID), spec.Failed())
}
if spec.Failed() && runner.config.FailFast {
skipRemainingSpecs = true
}
}
return !suiteFailed
}
func (runner *SpecRunner) runSpec(spec *spec.Spec) (passed bool) {
maxAttempts := 1
if runner.config.FlakeAttempts > 0 {
// uninitialized configs count as 1
maxAttempts = runner.config.FlakeAttempts
}
for i := 0; i < maxAttempts; i++ {
runner.reportSpecWillRun(spec.Summary(runner.suiteID))
runner.runningSpec = spec
spec.Run(runner.writer)
runner.runningSpec = nil
runner.reportSpecDidComplete(spec.Summary(runner.suiteID), spec.Failed())
if !spec.Failed() {
return true
}
}
return false
}
func (runner *SpecRunner) CurrentSpecSummary() (*types.SpecSummary, bool) {
if runner.runningSpec == nil {
return nil, false
}
return runner.runningSpec.Summary(runner.suiteID), true
}
func (runner *SpecRunner) registerForInterrupts() {
c := make(chan os.Signal, 1)
signal.Notify(c, os.Interrupt, syscall.SIGTERM)
<-c
signal.Stop(c)
runner.markInterrupted()
go runner.registerForHardInterrupts()
runner.writer.DumpOutWithHeader(`
Received interrupt. Emitting contents of GinkgoWriter...
---------------------------------------------------------
`)
if runner.afterSuiteNode != nil {
fmt.Fprint(os.Stderr, `
---------------------------------------------------------
Received interrupt. Running AfterSuite...
^C again to terminate immediately
`)
runner.runAfterSuite()
}
runner.reportSuiteDidEnd(false)
os.Exit(1)
}
func (runner *SpecRunner) registerForHardInterrupts() {
c := make(chan os.Signal, 1)
signal.Notify(c, os.Interrupt, syscall.SIGTERM)
<-c
fmt.Fprintln(os.Stderr, "\nReceived second interrupt. Shutting down.")
os.Exit(1)
}
func (runner *SpecRunner) blockForeverIfInterrupted() {
runner.lock.Lock()
interrupted := runner.interrupted
runner.lock.Unlock()
if interrupted {
select {}
}
}
func (runner *SpecRunner) markInterrupted() {
runner.lock.Lock()
defer runner.lock.Unlock()
runner.interrupted = true
}
func (runner *SpecRunner) wasInterrupted() bool {
runner.lock.Lock()
defer runner.lock.Unlock()
return runner.interrupted
}
func (runner *SpecRunner) reportSuiteWillBegin() {
runner.startTime = time.Now()
summary := runner.suiteWillBeginSummary()
for _, reporter := range runner.reporters {
reporter.SpecSuiteWillBegin(runner.config, summary)
}
}
func (runner *SpecRunner) reportBeforeSuite(summary *types.SetupSummary) {
for _, reporter := range runner.reporters {
reporter.BeforeSuiteDidRun(summary)
}
}
func (runner *SpecRunner) reportAfterSuite(summary *types.SetupSummary) {
for _, reporter := range runner.reporters {
reporter.AfterSuiteDidRun(summary)
}
}
func (runner *SpecRunner) reportSpecWillRun(summary *types.SpecSummary) {
runner.writer.Truncate()
for _, reporter := range runner.reporters {
reporter.SpecWillRun(summary)
}
}
func (runner *SpecRunner) reportSpecDidComplete(summary *types.SpecSummary, failed bool) {
if failed && len(summary.CapturedOutput) == 0 {
summary.CapturedOutput = string(runner.writer.Bytes())
}
for i := len(runner.reporters) - 1; i >= 1; i-- {
runner.reporters[i].SpecDidComplete(summary)
}
if failed {
runner.writer.DumpOut()
}
runner.reporters[0].SpecDidComplete(summary)
}
func (runner *SpecRunner) reportSuiteDidEnd(success bool) {
summary := runner.suiteDidEndSummary(success)
summary.RunTime = time.Since(runner.startTime)
for _, reporter := range runner.reporters {
reporter.SpecSuiteDidEnd(summary)
}
}
func (runner *SpecRunner) countSpecsThatRanSatisfying(filter func(ex *spec.Spec) bool) (count int) {
count = 0
for _, spec := range runner.processedSpecs {
if filter(spec) {
count++
}
}
return count
}
func (runner *SpecRunner) suiteDidEndSummary(success bool) *types.SuiteSummary {
numberOfSpecsThatWillBeRun := runner.countSpecsThatRanSatisfying(func(ex *spec.Spec) bool {
return !ex.Skipped() && !ex.Pending()
})
numberOfPendingSpecs := runner.countSpecsThatRanSatisfying(func(ex *spec.Spec) bool {
return ex.Pending()
})
numberOfSkippedSpecs := runner.countSpecsThatRanSatisfying(func(ex *spec.Spec) bool {
return ex.Skipped()
})
numberOfPassedSpecs := runner.countSpecsThatRanSatisfying(func(ex *spec.Spec) bool {
return ex.Passed()
})
numberOfFlakedSpecs := runner.countSpecsThatRanSatisfying(func(ex *spec.Spec) bool {
return ex.Flaked()
})
numberOfFailedSpecs := runner.countSpecsThatRanSatisfying(func(ex *spec.Spec) bool {
return ex.Failed()
})
if runner.beforeSuiteNode != nil && !runner.beforeSuiteNode.Passed() && !runner.config.DryRun {
var known bool
numberOfSpecsThatWillBeRun, known = runner.iterator.NumberOfSpecsThatWillBeRunIfKnown()
if !known {
numberOfSpecsThatWillBeRun = runner.iterator.NumberOfSpecsPriorToIteration()
}
numberOfFailedSpecs = numberOfSpecsThatWillBeRun
}
return &types.SuiteSummary{
SuiteDescription: runner.description,
SuiteSucceeded: success,
SuiteID: runner.suiteID,
NumberOfSpecsBeforeParallelization: runner.iterator.NumberOfSpecsPriorToIteration(),
NumberOfTotalSpecs: len(runner.processedSpecs),
NumberOfSpecsThatWillBeRun: numberOfSpecsThatWillBeRun,
NumberOfPendingSpecs: numberOfPendingSpecs,
NumberOfSkippedSpecs: numberOfSkippedSpecs,
NumberOfPassedSpecs: numberOfPassedSpecs,
NumberOfFailedSpecs: numberOfFailedSpecs,
NumberOfFlakedSpecs: numberOfFlakedSpecs,
}
}
func (runner *SpecRunner) suiteWillBeginSummary() *types.SuiteSummary {
numTotal, known := runner.iterator.NumberOfSpecsToProcessIfKnown()
if !known {
numTotal = -1
}
numToRun, known := runner.iterator.NumberOfSpecsThatWillBeRunIfKnown()
if !known {
numToRun = -1
}
return &types.SuiteSummary{
SuiteDescription: runner.description,
SuiteID: runner.suiteID,
NumberOfSpecsBeforeParallelization: runner.iterator.NumberOfSpecsPriorToIteration(),
NumberOfTotalSpecs: numTotal,
NumberOfSpecsThatWillBeRun: numToRun,
NumberOfPendingSpecs: -1,
NumberOfSkippedSpecs: -1,
NumberOfPassedSpecs: -1,
NumberOfFailedSpecs: -1,
NumberOfFlakedSpecs: -1,
}
}

View file

@ -0,0 +1,13 @@
package specrunner_test
import (
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
"testing"
)
func TestSpecRunner(t *testing.T) {
RegisterFailHandler(Fail)
RunSpecs(t, "Spec Runner Suite")
}

View file

@ -0,0 +1,779 @@
package specrunner_test
import (
. "github.com/onsi/ginkgo"
"github.com/onsi/ginkgo/internal/spec_iterator"
. "github.com/onsi/ginkgo/internal/specrunner"
"github.com/onsi/ginkgo/types"
. "github.com/onsi/gomega"
"github.com/onsi/ginkgo/config"
"github.com/onsi/ginkgo/internal/codelocation"
"github.com/onsi/ginkgo/internal/containernode"
Failer "github.com/onsi/ginkgo/internal/failer"
"github.com/onsi/ginkgo/internal/leafnodes"
"github.com/onsi/ginkgo/internal/spec"
Writer "github.com/onsi/ginkgo/internal/writer"
"github.com/onsi/ginkgo/reporters"
)
var noneFlag = types.FlagTypeNone
var focusedFlag = types.FlagTypeFocused
var pendingFlag = types.FlagTypePending
var _ = Describe("Spec Runner", func() {
var (
reporter1 *reporters.FakeReporter
reporter2 *reporters.FakeReporter
failer *Failer.Failer
writer *Writer.FakeGinkgoWriter
thingsThatRan []string
runner *SpecRunner
)
newBefSuite := func(text string, fail bool) leafnodes.SuiteNode {
return leafnodes.NewBeforeSuiteNode(func() {
writer.AddEvent(text)
thingsThatRan = append(thingsThatRan, text)
if fail {
failer.Fail(text, codelocation.New(0))
}
}, codelocation.New(0), 0, failer)
}
newAftSuite := func(text string, fail bool) leafnodes.SuiteNode {
return leafnodes.NewAfterSuiteNode(func() {
writer.AddEvent(text)
thingsThatRan = append(thingsThatRan, text)
if fail {
failer.Fail(text, codelocation.New(0))
}
}, codelocation.New(0), 0, failer)
}
newSpec := func(text string, flag types.FlagType, fail bool) *spec.Spec {
subject := leafnodes.NewItNode(text, func() {
writer.AddEvent(text)
thingsThatRan = append(thingsThatRan, text)
if fail {
failer.Fail(text, codelocation.New(0))
}
}, flag, codelocation.New(0), 0, failer, 0)
return spec.New(subject, []*containernode.ContainerNode{}, false)
}
newFlakySpec := func(text string, flag types.FlagType, failures int) *spec.Spec {
runs := 0
subject := leafnodes.NewItNode(text, func() {
writer.AddEvent(text)
thingsThatRan = append(thingsThatRan, text)
runs++
if runs < failures {
failer.Fail(text, codelocation.New(0))
}
}, flag, codelocation.New(0), 0, failer, 0)
return spec.New(subject, []*containernode.ContainerNode{}, false)
}
newSpecWithBody := func(text string, body interface{}) *spec.Spec {
subject := leafnodes.NewItNode(text, body, noneFlag, codelocation.New(0), 0, failer, 0)
return spec.New(subject, []*containernode.ContainerNode{}, false)
}
newRunner := func(config config.GinkgoConfigType, beforeSuiteNode leafnodes.SuiteNode, afterSuiteNode leafnodes.SuiteNode, specs ...*spec.Spec) *SpecRunner {
iterator := spec_iterator.NewSerialIterator(specs)
return New("description", beforeSuiteNode, iterator, afterSuiteNode, []reporters.Reporter{reporter1, reporter2}, writer, config)
}
BeforeEach(func() {
reporter1 = reporters.NewFakeReporter()
reporter2 = reporters.NewFakeReporter()
writer = Writer.NewFake()
failer = Failer.New()
thingsThatRan = []string{}
})
Describe("Running and Reporting", func() {
var specA, pendingSpec, anotherPendingSpec, failedSpec, specB, skippedSpec *spec.Spec
var willRunCalls, didCompleteCalls []string
var conf config.GinkgoConfigType
JustBeforeEach(func() {
willRunCalls = []string{}
didCompleteCalls = []string{}
specA = newSpec("spec A", noneFlag, false)
pendingSpec = newSpec("pending spec", pendingFlag, false)
anotherPendingSpec = newSpec("another pending spec", pendingFlag, false)
failedSpec = newSpec("failed spec", noneFlag, true)
specB = newSpec("spec B", noneFlag, false)
skippedSpec = newSpec("skipped spec", noneFlag, false)
skippedSpec.Skip()
reporter1.SpecWillRunStub = func(specSummary *types.SpecSummary) {
willRunCalls = append(willRunCalls, "Reporter1")
}
reporter2.SpecWillRunStub = func(specSummary *types.SpecSummary) {
willRunCalls = append(willRunCalls, "Reporter2")
}
reporter1.SpecDidCompleteStub = func(specSummary *types.SpecSummary) {
didCompleteCalls = append(didCompleteCalls, "Reporter1")
}
reporter2.SpecDidCompleteStub = func(specSummary *types.SpecSummary) {
didCompleteCalls = append(didCompleteCalls, "Reporter2")
}
runner = newRunner(conf, newBefSuite("BefSuite", false), newAftSuite("AftSuite", false), specA, pendingSpec, anotherPendingSpec, failedSpec, specB, skippedSpec)
runner.Run()
})
BeforeEach(func() {
conf = config.GinkgoConfigType{RandomSeed: 17}
})
It("should skip skipped/pending tests", func() {
Ω(thingsThatRan).Should(Equal([]string{"BefSuite", "spec A", "failed spec", "spec B", "AftSuite"}))
})
It("should report to any attached reporters", func() {
Ω(reporter1.Config).Should(Equal(reporter2.Config))
Ω(reporter1.BeforeSuiteSummary).Should(Equal(reporter2.BeforeSuiteSummary))
Ω(reporter1.BeginSummary).Should(Equal(reporter2.BeginSummary))
Ω(reporter1.SpecWillRunSummaries).Should(Equal(reporter2.SpecWillRunSummaries))
Ω(reporter1.SpecSummaries).Should(Equal(reporter2.SpecSummaries))
Ω(reporter1.AfterSuiteSummary).Should(Equal(reporter2.AfterSuiteSummary))
Ω(reporter1.EndSummary).Should(Equal(reporter2.EndSummary))
})
It("should report that a spec did end in reverse order", func() {
Ω(willRunCalls[0:4]).Should(Equal([]string{"Reporter1", "Reporter2", "Reporter1", "Reporter2"}))
Ω(didCompleteCalls[0:4]).Should(Equal([]string{"Reporter2", "Reporter1", "Reporter2", "Reporter1"}))
})
It("should report the passed in config", func() {
Ω(reporter1.Config.RandomSeed).Should(BeNumerically("==", 17))
})
It("should report the beginning of the suite", func() {
Ω(reporter1.BeginSummary.SuiteDescription).Should(Equal("description"))
Ω(reporter1.BeginSummary.SuiteID).Should(MatchRegexp("[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}"))
Ω(reporter1.BeginSummary.NumberOfSpecsBeforeParallelization).Should(Equal(6))
Ω(reporter1.BeginSummary.NumberOfTotalSpecs).Should(Equal(6))
Ω(reporter1.BeginSummary.NumberOfSpecsThatWillBeRun).Should(Equal(3))
Ω(reporter1.BeginSummary.NumberOfPendingSpecs).Should(Equal(-1))
Ω(reporter1.BeginSummary.NumberOfSkippedSpecs).Should(Equal(-1))
})
It("should report the end of the suite", func() {
Ω(reporter1.EndSummary.SuiteDescription).Should(Equal("description"))
Ω(reporter1.EndSummary.SuiteSucceeded).Should(BeFalse())
Ω(reporter1.EndSummary.SuiteID).Should(MatchRegexp("[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}"))
Ω(reporter1.EndSummary.NumberOfSpecsBeforeParallelization).Should(Equal(6))
Ω(reporter1.EndSummary.NumberOfTotalSpecs).Should(Equal(6))
Ω(reporter1.EndSummary.NumberOfSpecsThatWillBeRun).Should(Equal(3))
Ω(reporter1.EndSummary.NumberOfPendingSpecs).Should(Equal(2))
Ω(reporter1.EndSummary.NumberOfSkippedSpecs).Should(Equal(1))
Ω(reporter1.EndSummary.NumberOfPassedSpecs).Should(Equal(2))
Ω(reporter1.EndSummary.NumberOfFailedSpecs).Should(Equal(1))
})
Context("when told to perform a dry run", func() {
BeforeEach(func() {
conf.DryRun = true
})
It("should report to the reporters", func() {
Ω(reporter1.Config).Should(Equal(reporter2.Config))
Ω(reporter1.BeforeSuiteSummary).Should(Equal(reporter2.BeforeSuiteSummary))
Ω(reporter1.BeginSummary).Should(Equal(reporter2.BeginSummary))
Ω(reporter1.SpecWillRunSummaries).Should(Equal(reporter2.SpecWillRunSummaries))
Ω(reporter1.SpecSummaries).Should(Equal(reporter2.SpecSummaries))
Ω(reporter1.AfterSuiteSummary).Should(Equal(reporter2.AfterSuiteSummary))
Ω(reporter1.EndSummary).Should(Equal(reporter2.EndSummary))
})
It("should not actually run anything", func() {
Ω(thingsThatRan).Should(BeEmpty())
})
It("report before and after suites as passed", func() {
Ω(reporter1.BeforeSuiteSummary.State).Should(Equal(types.SpecStatePassed))
Ω(reporter1.AfterSuiteSummary.State).Should(Equal(types.SpecStatePassed))
})
It("should report specs as passed", func() {
summaries := reporter1.SpecSummaries
Ω(summaries).Should(HaveLen(6))
Ω(summaries[0].ComponentTexts).Should(ContainElement("spec A"))
Ω(summaries[0].State).Should(Equal(types.SpecStatePassed))
Ω(summaries[1].ComponentTexts).Should(ContainElement("pending spec"))
Ω(summaries[1].State).Should(Equal(types.SpecStatePending))
Ω(summaries[2].ComponentTexts).Should(ContainElement("another pending spec"))
Ω(summaries[2].State).Should(Equal(types.SpecStatePending))
Ω(summaries[3].ComponentTexts).Should(ContainElement("failed spec"))
Ω(summaries[3].State).Should(Equal(types.SpecStatePassed))
Ω(summaries[4].ComponentTexts).Should(ContainElement("spec B"))
Ω(summaries[4].State).Should(Equal(types.SpecStatePassed))
Ω(summaries[5].ComponentTexts).Should(ContainElement("skipped spec"))
Ω(summaries[5].State).Should(Equal(types.SpecStateSkipped))
})
It("should report the end of the suite", func() {
Ω(reporter1.EndSummary.SuiteDescription).Should(Equal("description"))
Ω(reporter1.EndSummary.SuiteSucceeded).Should(BeTrue())
Ω(reporter1.EndSummary.SuiteID).Should(MatchRegexp("[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}"))
Ω(reporter1.EndSummary.NumberOfSpecsBeforeParallelization).Should(Equal(6))
Ω(reporter1.EndSummary.NumberOfTotalSpecs).Should(Equal(6))
Ω(reporter1.EndSummary.NumberOfSpecsThatWillBeRun).Should(Equal(3))
Ω(reporter1.EndSummary.NumberOfPendingSpecs).Should(Equal(2))
Ω(reporter1.EndSummary.NumberOfSkippedSpecs).Should(Equal(1))
Ω(reporter1.EndSummary.NumberOfPassedSpecs).Should(Equal(0))
Ω(reporter1.EndSummary.NumberOfFailedSpecs).Should(Equal(0))
})
})
})
Describe("reporting on specs", func() {
var proceed chan bool
var ready chan bool
var finished chan bool
BeforeEach(func() {
ready = make(chan bool)
proceed = make(chan bool)
finished = make(chan bool)
skippedSpec := newSpec("SKIP", noneFlag, false)
skippedSpec.Skip()
runner = newRunner(
config.GinkgoConfigType{},
newBefSuite("BefSuite", false),
newAftSuite("AftSuite", false),
skippedSpec,
newSpec("PENDING", pendingFlag, false),
newSpecWithBody("RUN", func() {
close(ready)
<-proceed
}),
)
go func() {
runner.Run()
close(finished)
}()
})
It("should report about pending/skipped specs", func() {
<-ready
Ω(reporter1.SpecWillRunSummaries).Should(HaveLen(3))
Ω(reporter1.SpecWillRunSummaries[0].ComponentTexts[0]).Should(Equal("SKIP"))
Ω(reporter1.SpecWillRunSummaries[1].ComponentTexts[0]).Should(Equal("PENDING"))
Ω(reporter1.SpecWillRunSummaries[2].ComponentTexts[0]).Should(Equal("RUN"))
Ω(reporter1.SpecSummaries[0].ComponentTexts[0]).Should(Equal("SKIP"))
Ω(reporter1.SpecSummaries[1].ComponentTexts[0]).Should(Equal("PENDING"))
Ω(reporter1.SpecSummaries).Should(HaveLen(2))
close(proceed)
<-finished
Ω(reporter1.SpecSummaries).Should(HaveLen(3))
Ω(reporter1.SpecSummaries[2].ComponentTexts[0]).Should(Equal("RUN"))
})
})
Describe("Running and Reporting when there's flakes", func() {
var specA, pendingSpec, flakySpec, failedSpec, specB, skippedSpec *spec.Spec
var willRunCalls, didCompleteCalls []string
var conf config.GinkgoConfigType
var failedSpecFlag = noneFlag
JustBeforeEach(func() {
willRunCalls = []string{}
didCompleteCalls = []string{}
specA = newSpec("spec A", noneFlag, false)
pendingSpec = newSpec("pending spec", pendingFlag, false)
flakySpec = newFlakySpec("flaky spec", noneFlag, 3)
failedSpec = newSpec("failed spec", failedSpecFlag, true)
specB = newSpec("spec B", noneFlag, false)
skippedSpec = newSpec("skipped spec", noneFlag, false)
skippedSpec.Skip()
reporter1.SpecWillRunStub = func(specSummary *types.SpecSummary) {
willRunCalls = append(willRunCalls, "Reporter1")
}
reporter2.SpecWillRunStub = func(specSummary *types.SpecSummary) {
willRunCalls = append(willRunCalls, "Reporter2")
}
reporter1.SpecDidCompleteStub = func(specSummary *types.SpecSummary) {
didCompleteCalls = append(didCompleteCalls, "Reporter1")
}
reporter2.SpecDidCompleteStub = func(specSummary *types.SpecSummary) {
didCompleteCalls = append(didCompleteCalls, "Reporter2")
}
runner = newRunner(conf, newBefSuite("BefSuite", false), newAftSuite("AftSuite", false), specA, pendingSpec, flakySpec, failedSpec, specB, skippedSpec)
runner.Run()
})
BeforeEach(func() {
failedSpecFlag = noneFlag
conf = config.GinkgoConfigType{
RandomSeed: 17,
FlakeAttempts: 5,
}
})
It("should skip skipped/pending tests", func() {
Ω(thingsThatRan).Should(Equal([]string{"BefSuite", "spec A", "flaky spec", "flaky spec", "flaky spec", "failed spec", "failed spec", "failed spec", "failed spec", "failed spec", "spec B", "AftSuite"}))
})
It("should report to any attached reporters", func() {
Ω(reporter1.Config).Should(Equal(reporter2.Config))
Ω(reporter1.BeforeSuiteSummary).Should(Equal(reporter2.BeforeSuiteSummary))
Ω(reporter1.BeginSummary).Should(Equal(reporter2.BeginSummary))
Ω(reporter1.SpecWillRunSummaries).Should(Equal(reporter2.SpecWillRunSummaries))
Ω(reporter1.SpecSummaries).Should(Equal(reporter2.SpecSummaries))
Ω(reporter1.AfterSuiteSummary).Should(Equal(reporter2.AfterSuiteSummary))
Ω(reporter1.EndSummary).Should(Equal(reporter2.EndSummary))
})
It("should report that a spec did end in reverse order", func() {
Ω(willRunCalls[0:4]).Should(Equal([]string{"Reporter1", "Reporter2", "Reporter1", "Reporter2"}))
Ω(didCompleteCalls[0:4]).Should(Equal([]string{"Reporter2", "Reporter1", "Reporter2", "Reporter1"}))
})
It("should report the passed in config", func() {
Ω(reporter1.Config.RandomSeed).Should(BeNumerically("==", 17))
})
It("should report the beginning of the suite", func() {
Ω(reporter1.BeginSummary.SuiteDescription).Should(Equal("description"))
Ω(reporter1.BeginSummary.SuiteID).Should(MatchRegexp("[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}"))
Ω(reporter1.BeginSummary.NumberOfSpecsBeforeParallelization).Should(Equal(6))
Ω(reporter1.BeginSummary.NumberOfTotalSpecs).Should(Equal(6))
Ω(reporter1.BeginSummary.NumberOfSpecsThatWillBeRun).Should(Equal(4))
Ω(reporter1.BeginSummary.NumberOfPendingSpecs).Should(Equal(-1))
Ω(reporter1.BeginSummary.NumberOfSkippedSpecs).Should(Equal(-1))
})
It("should report the end of the suite", func() {
Ω(reporter1.EndSummary.SuiteDescription).Should(Equal("description"))
Ω(reporter1.EndSummary.SuiteSucceeded).Should(BeFalse())
Ω(reporter1.EndSummary.SuiteID).Should(MatchRegexp("[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}"))
Ω(reporter1.EndSummary.NumberOfSpecsBeforeParallelization).Should(Equal(6))
Ω(reporter1.EndSummary.NumberOfTotalSpecs).Should(Equal(6))
Ω(reporter1.EndSummary.NumberOfSpecsThatWillBeRun).Should(Equal(4))
Ω(reporter1.EndSummary.NumberOfPendingSpecs).Should(Equal(1))
Ω(reporter1.EndSummary.NumberOfSkippedSpecs).Should(Equal(1))
Ω(reporter1.EndSummary.NumberOfPassedSpecs).Should(Equal(3))
Ω(reporter1.EndSummary.NumberOfFailedSpecs).Should(Equal(1))
Ω(reporter1.EndSummary.NumberOfFlakedSpecs).Should(Equal(1))
})
Context("when nothing fails", func() {
BeforeEach(func() {
failedSpecFlag = pendingFlag
})
It("the suite should pass even with flakes", func() {
Ω(reporter1.EndSummary.SuiteSucceeded).Should(BeTrue())
Ω(reporter1.EndSummary.NumberOfFlakedSpecs).Should(Equal(1))
})
})
Context("when told to perform a dry run", func() {
BeforeEach(func() {
conf.DryRun = true
})
It("should report to the reporters", func() {
Ω(reporter1.Config).Should(Equal(reporter2.Config))
Ω(reporter1.BeforeSuiteSummary).Should(Equal(reporter2.BeforeSuiteSummary))
Ω(reporter1.BeginSummary).Should(Equal(reporter2.BeginSummary))
Ω(reporter1.SpecWillRunSummaries).Should(Equal(reporter2.SpecWillRunSummaries))
Ω(reporter1.SpecSummaries).Should(Equal(reporter2.SpecSummaries))
Ω(reporter1.AfterSuiteSummary).Should(Equal(reporter2.AfterSuiteSummary))
Ω(reporter1.EndSummary).Should(Equal(reporter2.EndSummary))
})
It("should not actually run anything", func() {
Ω(thingsThatRan).Should(BeEmpty())
})
It("report before and after suites as passed", func() {
Ω(reporter1.BeforeSuiteSummary.State).Should(Equal(types.SpecStatePassed))
Ω(reporter1.AfterSuiteSummary.State).Should(Equal(types.SpecStatePassed))
})
It("should report specs as passed", func() {
summaries := reporter1.SpecSummaries
Ω(summaries).Should(HaveLen(6))
Ω(summaries[0].ComponentTexts).Should(ContainElement("spec A"))
Ω(summaries[0].State).Should(Equal(types.SpecStatePassed))
Ω(summaries[1].ComponentTexts).Should(ContainElement("pending spec"))
Ω(summaries[1].State).Should(Equal(types.SpecStatePending))
Ω(summaries[2].ComponentTexts).Should(ContainElement("flaky spec"))
Ω(summaries[2].State).Should(Equal(types.SpecStatePassed))
Ω(summaries[3].ComponentTexts).Should(ContainElement("failed spec"))
Ω(summaries[3].State).Should(Equal(types.SpecStatePassed))
Ω(summaries[4].ComponentTexts).Should(ContainElement("spec B"))
Ω(summaries[4].State).Should(Equal(types.SpecStatePassed))
Ω(summaries[5].ComponentTexts).Should(ContainElement("skipped spec"))
Ω(summaries[5].State).Should(Equal(types.SpecStateSkipped))
})
It("should report the end of the suite", func() {
Ω(reporter1.EndSummary.SuiteDescription).Should(Equal("description"))
Ω(reporter1.EndSummary.SuiteSucceeded).Should(BeTrue())
Ω(reporter1.EndSummary.SuiteID).Should(MatchRegexp("[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}"))
Ω(reporter1.EndSummary.NumberOfSpecsBeforeParallelization).Should(Equal(6))
Ω(reporter1.EndSummary.NumberOfTotalSpecs).Should(Equal(6))
Ω(reporter1.EndSummary.NumberOfSpecsThatWillBeRun).Should(Equal(4))
Ω(reporter1.EndSummary.NumberOfPendingSpecs).Should(Equal(1))
Ω(reporter1.EndSummary.NumberOfSkippedSpecs).Should(Equal(1))
Ω(reporter1.EndSummary.NumberOfPassedSpecs).Should(Equal(0))
Ω(reporter1.EndSummary.NumberOfFailedSpecs).Should(Equal(0))
})
})
})
Describe("Running BeforeSuite & AfterSuite", func() {
var success bool
var befSuite leafnodes.SuiteNode
var aftSuite leafnodes.SuiteNode
Context("with a nil BeforeSuite & AfterSuite", func() {
BeforeEach(func() {
runner = newRunner(
config.GinkgoConfigType{},
nil,
nil,
newSpec("A", noneFlag, false),
newSpec("B", noneFlag, false),
)
success = runner.Run()
})
It("should not report about the BeforeSuite", func() {
Ω(reporter1.BeforeSuiteSummary).Should(BeNil())
})
It("should not report about the AfterSuite", func() {
Ω(reporter1.AfterSuiteSummary).Should(BeNil())
})
It("should run the specs", func() {
Ω(thingsThatRan).Should(Equal([]string{"A", "B"}))
})
})
Context("when the BeforeSuite & AfterSuite pass", func() {
BeforeEach(func() {
befSuite = newBefSuite("BefSuite", false)
aftSuite = newBefSuite("AftSuite", false)
runner = newRunner(
config.GinkgoConfigType{},
befSuite,
aftSuite,
newSpec("A", noneFlag, false),
newSpec("B", noneFlag, false),
)
success = runner.Run()
})
It("should run the BeforeSuite, the AfterSuite and the specs", func() {
Ω(thingsThatRan).Should(Equal([]string{"BefSuite", "A", "B", "AftSuite"}))
})
It("should report about the BeforeSuite", func() {
Ω(reporter1.BeforeSuiteSummary).Should(Equal(befSuite.Summary()))
})
It("should report about the AfterSuite", func() {
Ω(reporter1.AfterSuiteSummary).Should(Equal(aftSuite.Summary()))
})
It("should report success", func() {
Ω(success).Should(BeTrue())
Ω(reporter1.EndSummary.SuiteSucceeded).Should(BeTrue())
Ω(reporter1.EndSummary.NumberOfFailedSpecs).Should(Equal(0))
})
It("should not dump the writer", func() {
Ω(writer.EventStream).ShouldNot(ContainElement("DUMP"))
})
})
Context("when the BeforeSuite fails", func() {
BeforeEach(func() {
befSuite = newBefSuite("BefSuite", true)
aftSuite = newBefSuite("AftSuite", false)
skipped := newSpec("Skipped", noneFlag, false)
skipped.Skip()
runner = newRunner(
config.GinkgoConfigType{},
befSuite,
aftSuite,
newSpec("A", noneFlag, false),
newSpec("B", noneFlag, false),
newSpec("Pending", pendingFlag, false),
skipped,
)
success = runner.Run()
})
It("should not run the specs, but it should run the AfterSuite", func() {
Ω(thingsThatRan).Should(Equal([]string{"BefSuite", "AftSuite"}))
})
It("should report about the BeforeSuite", func() {
Ω(reporter1.BeforeSuiteSummary).Should(Equal(befSuite.Summary()))
})
It("should report about the AfterSuite", func() {
Ω(reporter1.AfterSuiteSummary).Should(Equal(aftSuite.Summary()))
})
It("should report failure", func() {
Ω(success).Should(BeFalse())
Ω(reporter1.EndSummary.SuiteSucceeded).Should(BeFalse())
Ω(reporter1.EndSummary.NumberOfFailedSpecs).Should(Equal(2))
Ω(reporter1.EndSummary.NumberOfSpecsThatWillBeRun).Should(Equal(2))
})
It("should dump the writer", func() {
Ω(writer.EventStream).Should(ContainElement("DUMP"))
})
})
Context("when some other test fails", func() {
BeforeEach(func() {
aftSuite = newBefSuite("AftSuite", false)
runner = newRunner(
config.GinkgoConfigType{},
nil,
aftSuite,
newSpec("A", noneFlag, true),
)
success = runner.Run()
})
It("should still run the AfterSuite", func() {
Ω(thingsThatRan).Should(Equal([]string{"A", "AftSuite"}))
})
It("should report about the AfterSuite", func() {
Ω(reporter1.AfterSuiteSummary).Should(Equal(aftSuite.Summary()))
})
It("should report failure", func() {
Ω(success).Should(BeFalse())
Ω(reporter1.EndSummary.SuiteSucceeded).Should(BeFalse())
Ω(reporter1.EndSummary.NumberOfFailedSpecs).Should(Equal(1))
Ω(reporter1.EndSummary.NumberOfSpecsThatWillBeRun).Should(Equal(1))
})
})
Context("when the AfterSuite fails", func() {
BeforeEach(func() {
befSuite = newBefSuite("BefSuite", false)
aftSuite = newBefSuite("AftSuite", true)
runner = newRunner(
config.GinkgoConfigType{},
befSuite,
aftSuite,
newSpec("A", noneFlag, false),
newSpec("B", noneFlag, false),
)
success = runner.Run()
})
It("should run everything", func() {
Ω(thingsThatRan).Should(Equal([]string{"BefSuite", "A", "B", "AftSuite"}))
})
It("should report about the BeforeSuite", func() {
Ω(reporter1.BeforeSuiteSummary).Should(Equal(befSuite.Summary()))
})
It("should report about the AfterSuite", func() {
Ω(reporter1.AfterSuiteSummary).Should(Equal(aftSuite.Summary()))
})
It("should report failure", func() {
Ω(success).Should(BeFalse())
Ω(reporter1.EndSummary.SuiteSucceeded).Should(BeFalse())
Ω(reporter1.EndSummary.NumberOfFailedSpecs).Should(Equal(0))
})
It("should dump the writer", func() {
Ω(writer.EventStream).Should(ContainElement("DUMP"))
})
})
})
Describe("When instructed to fail fast", func() {
BeforeEach(func() {
conf := config.GinkgoConfigType{
FailFast: true,
}
runner = newRunner(conf, nil, newAftSuite("after-suite", false), newSpec("passing", noneFlag, false), newSpec("failing", noneFlag, true), newSpec("dont-see", noneFlag, true), newSpec("dont-see", noneFlag, true))
})
It("should return false, report failure, and not run anything past the failing test", func() {
Ω(runner.Run()).Should(BeFalse())
Ω(reporter1.EndSummary.SuiteSucceeded).Should(BeFalse())
Ω(thingsThatRan).Should(Equal([]string{"passing", "failing", "after-suite"}))
})
It("should announce the subsequent specs as skipped", func() {
runner.Run()
Ω(reporter1.SpecSummaries).Should(HaveLen(4))
Ω(reporter1.SpecSummaries[2].State).Should(Equal(types.SpecStateSkipped))
Ω(reporter1.SpecSummaries[3].State).Should(Equal(types.SpecStateSkipped))
})
It("should mark all subsequent specs as skipped", func() {
runner.Run()
Ω(reporter1.EndSummary.NumberOfSkippedSpecs).Should(Equal(2))
})
})
Describe("Marking failure and success", func() {
Context("when all tests pass", func() {
BeforeEach(func() {
runner = newRunner(config.GinkgoConfigType{}, nil, nil, newSpec("passing", noneFlag, false), newSpec("pending", pendingFlag, false))
})
It("should return true and report success", func() {
Ω(runner.Run()).Should(BeTrue())
Ω(reporter1.EndSummary.SuiteSucceeded).Should(BeTrue())
})
})
Context("when a test fails", func() {
BeforeEach(func() {
runner = newRunner(config.GinkgoConfigType{}, nil, nil, newSpec("failing", noneFlag, true), newSpec("pending", pendingFlag, false))
})
It("should return false and report failure", func() {
Ω(runner.Run()).Should(BeFalse())
Ω(reporter1.EndSummary.SuiteSucceeded).Should(BeFalse())
})
})
Context("when there is a pending test, but pendings count as failures", func() {
BeforeEach(func() {
runner = newRunner(config.GinkgoConfigType{FailOnPending: true}, nil, nil, newSpec("passing", noneFlag, false), newSpec("pending", pendingFlag, false))
})
It("should return false and report failure", func() {
Ω(runner.Run()).Should(BeFalse())
Ω(reporter1.EndSummary.SuiteSucceeded).Should(BeFalse())
})
})
})
Describe("Managing the writer", func() {
BeforeEach(func() {
runner = newRunner(
config.GinkgoConfigType{},
nil,
nil,
newSpec("A", noneFlag, false),
newSpec("B", noneFlag, true),
newSpec("C", noneFlag, false),
)
reporter1.SpecWillRunStub = func(specSummary *types.SpecSummary) {
writer.AddEvent("R1.WillRun")
}
reporter2.SpecWillRunStub = func(specSummary *types.SpecSummary) {
writer.AddEvent("R2.WillRun")
}
reporter1.SpecDidCompleteStub = func(specSummary *types.SpecSummary) {
writer.AddEvent("R1.DidComplete")
}
reporter2.SpecDidCompleteStub = func(specSummary *types.SpecSummary) {
writer.AddEvent("R2.DidComplete")
}
runner.Run()
})
It("should truncate between tests, but only dump if a test fails", func() {
Ω(writer.EventStream).Should(Equal([]string{
"TRUNCATE",
"R1.WillRun",
"R2.WillRun",
"A",
"R2.DidComplete",
"R1.DidComplete",
"TRUNCATE",
"R1.WillRun",
"R2.WillRun",
"B",
"BYTES",
"R2.DidComplete",
"DUMP",
"R1.DidComplete",
"TRUNCATE",
"R1.WillRun",
"R2.WillRun",
"C",
"R2.DidComplete",
"R1.DidComplete",
}))
})
})
Describe("CurrentSpecSummary", func() {
It("should return the spec summary for the currently running spec", func() {
var summary *types.SpecSummary
runner = newRunner(
config.GinkgoConfigType{},
nil,
nil,
newSpec("A", noneFlag, false),
newSpecWithBody("B", func() {
var ok bool
summary, ok = runner.CurrentSpecSummary()
Ω(ok).Should(BeTrue())
}),
newSpec("C", noneFlag, false),
)
runner.Run()
Ω(summary.ComponentTexts).Should(Equal([]string{"B"}))
summary, ok := runner.CurrentSpecSummary()
Ω(summary).Should(BeNil())
Ω(ok).Should(BeFalse())
})
})
Describe("generating a suite id", func() {
It("should generate an id randomly", func() {
runnerA := newRunner(config.GinkgoConfigType{}, nil, nil)
runnerA.Run()
IDA := reporter1.BeginSummary.SuiteID
runnerB := newRunner(config.GinkgoConfigType{}, nil, nil)
runnerB.Run()
IDB := reporter1.BeginSummary.SuiteID
IDRegexp := "[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}"
Ω(IDA).Should(MatchRegexp(IDRegexp))
Ω(IDB).Should(MatchRegexp(IDRegexp))
Ω(IDA).ShouldNot(Equal(IDB))
})
})
})

183
vendor/github.com/onsi/ginkgo/internal/suite/suite.go generated vendored Normal file
View file

@ -0,0 +1,183 @@
package suite
import (
"math/rand"
"net/http"
"time"
"github.com/onsi/ginkgo/internal/spec_iterator"
"github.com/onsi/ginkgo/config"
"github.com/onsi/ginkgo/internal/containernode"
"github.com/onsi/ginkgo/internal/failer"
"github.com/onsi/ginkgo/internal/leafnodes"
"github.com/onsi/ginkgo/internal/spec"
"github.com/onsi/ginkgo/internal/specrunner"
"github.com/onsi/ginkgo/internal/writer"
"github.com/onsi/ginkgo/reporters"
"github.com/onsi/ginkgo/types"
)
type ginkgoTestingT interface {
Fail()
}
type Suite struct {
topLevelContainer *containernode.ContainerNode
currentContainer *containernode.ContainerNode
containerIndex int
beforeSuiteNode leafnodes.SuiteNode
afterSuiteNode leafnodes.SuiteNode
runner *specrunner.SpecRunner
failer *failer.Failer
running bool
}
func New(failer *failer.Failer) *Suite {
topLevelContainer := containernode.New("[Top Level]", types.FlagTypeNone, types.CodeLocation{})
return &Suite{
topLevelContainer: topLevelContainer,
currentContainer: topLevelContainer,
failer: failer,
containerIndex: 1,
}
}
func (suite *Suite) Run(t ginkgoTestingT, description string, reporters []reporters.Reporter, writer writer.WriterInterface, config config.GinkgoConfigType) (bool, bool) {
if config.ParallelTotal < 1 {
panic("ginkgo.parallel.total must be >= 1")
}
if config.ParallelNode > config.ParallelTotal || config.ParallelNode < 1 {
panic("ginkgo.parallel.node is one-indexed and must be <= ginkgo.parallel.total")
}
r := rand.New(rand.NewSource(config.RandomSeed))
suite.topLevelContainer.Shuffle(r)
iterator, hasProgrammaticFocus := suite.generateSpecsIterator(description, config)
suite.runner = specrunner.New(description, suite.beforeSuiteNode, iterator, suite.afterSuiteNode, reporters, writer, config)
suite.running = true
success := suite.runner.Run()
if !success {
t.Fail()
}
return success, hasProgrammaticFocus
}
func (suite *Suite) generateSpecsIterator(description string, config config.GinkgoConfigType) (spec_iterator.SpecIterator, bool) {
specsSlice := []*spec.Spec{}
suite.topLevelContainer.BackPropagateProgrammaticFocus()
for _, collatedNodes := range suite.topLevelContainer.Collate() {
specsSlice = append(specsSlice, spec.New(collatedNodes.Subject, collatedNodes.Containers, config.EmitSpecProgress))
}
specs := spec.NewSpecs(specsSlice)
specs.RegexScansFilePath = config.RegexScansFilePath
if config.RandomizeAllSpecs {
specs.Shuffle(rand.New(rand.NewSource(config.RandomSeed)))
}
specs.ApplyFocus(description, config.FocusString, config.SkipString)
if config.SkipMeasurements {
specs.SkipMeasurements()
}
var iterator spec_iterator.SpecIterator
if config.ParallelTotal > 1 {
iterator = spec_iterator.NewParallelIterator(specs.Specs(), config.SyncHost)
resp, err := http.Get(config.SyncHost + "/has-counter")
if err != nil || resp.StatusCode != http.StatusOK {
iterator = spec_iterator.NewShardedParallelIterator(specs.Specs(), config.ParallelTotal, config.ParallelNode)
}
} else {
iterator = spec_iterator.NewSerialIterator(specs.Specs())
}
return iterator, specs.HasProgrammaticFocus()
}
func (suite *Suite) CurrentRunningSpecSummary() (*types.SpecSummary, bool) {
return suite.runner.CurrentSpecSummary()
}
func (suite *Suite) SetBeforeSuiteNode(body interface{}, codeLocation types.CodeLocation, timeout time.Duration) {
if suite.beforeSuiteNode != nil {
panic("You may only call BeforeSuite once!")
}
suite.beforeSuiteNode = leafnodes.NewBeforeSuiteNode(body, codeLocation, timeout, suite.failer)
}
func (suite *Suite) SetAfterSuiteNode(body interface{}, codeLocation types.CodeLocation, timeout time.Duration) {
if suite.afterSuiteNode != nil {
panic("You may only call AfterSuite once!")
}
suite.afterSuiteNode = leafnodes.NewAfterSuiteNode(body, codeLocation, timeout, suite.failer)
}
func (suite *Suite) SetSynchronizedBeforeSuiteNode(bodyA interface{}, bodyB interface{}, codeLocation types.CodeLocation, timeout time.Duration) {
if suite.beforeSuiteNode != nil {
panic("You may only call BeforeSuite once!")
}
suite.beforeSuiteNode = leafnodes.NewSynchronizedBeforeSuiteNode(bodyA, bodyB, codeLocation, timeout, suite.failer)
}
func (suite *Suite) SetSynchronizedAfterSuiteNode(bodyA interface{}, bodyB interface{}, codeLocation types.CodeLocation, timeout time.Duration) {
if suite.afterSuiteNode != nil {
panic("You may only call AfterSuite once!")
}
suite.afterSuiteNode = leafnodes.NewSynchronizedAfterSuiteNode(bodyA, bodyB, codeLocation, timeout, suite.failer)
}
func (suite *Suite) PushContainerNode(text string, body func(), flag types.FlagType, codeLocation types.CodeLocation) {
container := containernode.New(text, flag, codeLocation)
suite.currentContainer.PushContainerNode(container)
previousContainer := suite.currentContainer
suite.currentContainer = container
suite.containerIndex++
body()
suite.containerIndex--
suite.currentContainer = previousContainer
}
func (suite *Suite) PushItNode(text string, body interface{}, flag types.FlagType, codeLocation types.CodeLocation, timeout time.Duration) {
if suite.running {
suite.failer.Fail("You may only call It from within a Describe or Context", codeLocation)
}
suite.currentContainer.PushSubjectNode(leafnodes.NewItNode(text, body, flag, codeLocation, timeout, suite.failer, suite.containerIndex))
}
func (suite *Suite) PushMeasureNode(text string, body interface{}, flag types.FlagType, codeLocation types.CodeLocation, samples int) {
if suite.running {
suite.failer.Fail("You may only call Measure from within a Describe or Context", codeLocation)
}
suite.currentContainer.PushSubjectNode(leafnodes.NewMeasureNode(text, body, flag, codeLocation, samples, suite.failer, suite.containerIndex))
}
func (suite *Suite) PushBeforeEachNode(body interface{}, codeLocation types.CodeLocation, timeout time.Duration) {
if suite.running {
suite.failer.Fail("You may only call BeforeEach from within a Describe or Context", codeLocation)
}
suite.currentContainer.PushSetupNode(leafnodes.NewBeforeEachNode(body, codeLocation, timeout, suite.failer, suite.containerIndex))
}
func (suite *Suite) PushJustBeforeEachNode(body interface{}, codeLocation types.CodeLocation, timeout time.Duration) {
if suite.running {
suite.failer.Fail("You may only call JustBeforeEach from within a Describe or Context", codeLocation)
}
suite.currentContainer.PushSetupNode(leafnodes.NewJustBeforeEachNode(body, codeLocation, timeout, suite.failer, suite.containerIndex))
}
func (suite *Suite) PushAfterEachNode(body interface{}, codeLocation types.CodeLocation, timeout time.Duration) {
if suite.running {
suite.failer.Fail("You may only call AfterEach from within a Describe or Context", codeLocation)
}
suite.currentContainer.PushSetupNode(leafnodes.NewAfterEachNode(body, codeLocation, timeout, suite.failer, suite.containerIndex))
}

View file

@ -0,0 +1,35 @@
package suite_test
import (
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
"testing"
)
func Test(t *testing.T) {
RegisterFailHandler(Fail)
RunSpecs(t, "Suite")
}
var numBeforeSuiteRuns = 0
var numAfterSuiteRuns = 0
var _ = BeforeSuite(func() {
numBeforeSuiteRuns++
})
var _ = AfterSuite(func() {
numAfterSuiteRuns++
Ω(numBeforeSuiteRuns).Should(Equal(1))
Ω(numAfterSuiteRuns).Should(Equal(1))
})
//Fakes
type fakeTestingT struct {
didFail bool
}
func (fakeT *fakeTestingT) Fail() {
fakeT.didFail = true
}

View file

@ -0,0 +1,371 @@
package suite_test
import (
"bytes"
. "github.com/onsi/ginkgo"
. "github.com/onsi/ginkgo/internal/suite"
. "github.com/onsi/gomega"
"math/rand"
"time"
"github.com/onsi/ginkgo/config"
"github.com/onsi/ginkgo/internal/codelocation"
Failer "github.com/onsi/ginkgo/internal/failer"
Writer "github.com/onsi/ginkgo/internal/writer"
"github.com/onsi/ginkgo/reporters"
"github.com/onsi/ginkgo/types"
)
var _ = Describe("Suite", func() {
var (
specSuite *Suite
fakeT *fakeTestingT
fakeR *reporters.FakeReporter
writer *Writer.FakeGinkgoWriter
failer *Failer.Failer
)
BeforeEach(func() {
writer = Writer.NewFake()
fakeT = &fakeTestingT{}
fakeR = reporters.NewFakeReporter()
failer = Failer.New()
specSuite = New(failer)
})
Describe("running a suite", func() {
var (
runOrder []string
randomizeAllSpecs bool
randomSeed int64
focusString string
parallelNode int
parallelTotal int
runResult bool
hasProgrammaticFocus bool
)
var f = func(runText string) func() {
return func() {
runOrder = append(runOrder, runText)
}
}
BeforeEach(func() {
randomizeAllSpecs = false
randomSeed = 11
parallelNode = 1
parallelTotal = 1
focusString = ""
runOrder = make([]string, 0)
specSuite.SetBeforeSuiteNode(f("BeforeSuite"), codelocation.New(0), 0)
specSuite.PushBeforeEachNode(f("top BE"), codelocation.New(0), 0)
specSuite.PushJustBeforeEachNode(f("top JBE"), codelocation.New(0), 0)
specSuite.PushAfterEachNode(f("top AE"), codelocation.New(0), 0)
specSuite.PushContainerNode("container", func() {
specSuite.PushBeforeEachNode(f("BE"), codelocation.New(0), 0)
specSuite.PushJustBeforeEachNode(f("JBE"), codelocation.New(0), 0)
specSuite.PushAfterEachNode(f("AE"), codelocation.New(0), 0)
specSuite.PushItNode("it", f("IT"), types.FlagTypeNone, codelocation.New(0), 0)
specSuite.PushContainerNode("inner container", func() {
specSuite.PushItNode("inner it", f("inner IT"), types.FlagTypeNone, codelocation.New(0), 0)
}, types.FlagTypeNone, codelocation.New(0))
}, types.FlagTypeNone, codelocation.New(0))
specSuite.PushContainerNode("container 2", func() {
specSuite.PushBeforeEachNode(f("BE 2"), codelocation.New(0), 0)
specSuite.PushItNode("it 2", f("IT 2"), types.FlagTypeNone, codelocation.New(0), 0)
}, types.FlagTypeNone, codelocation.New(0))
specSuite.PushItNode("top level it", f("top IT"), types.FlagTypeNone, codelocation.New(0), 0)
specSuite.SetAfterSuiteNode(f("AfterSuite"), codelocation.New(0), 0)
})
JustBeforeEach(func() {
runResult, hasProgrammaticFocus = specSuite.Run(fakeT, "suite description", []reporters.Reporter{fakeR}, writer, config.GinkgoConfigType{
RandomSeed: randomSeed,
RandomizeAllSpecs: randomizeAllSpecs,
FocusString: focusString,
ParallelNode: parallelNode,
ParallelTotal: parallelTotal,
})
})
It("provides the config and suite description to the reporter", func() {
Ω(fakeR.Config.RandomSeed).Should(Equal(int64(randomSeed)))
Ω(fakeR.Config.RandomizeAllSpecs).Should(Equal(randomizeAllSpecs))
Ω(fakeR.BeginSummary.SuiteDescription).Should(Equal("suite description"))
})
It("reports that the BeforeSuite node ran", func() {
Ω(fakeR.BeforeSuiteSummary).ShouldNot(BeNil())
})
It("reports that the AfterSuite node ran", func() {
Ω(fakeR.AfterSuiteSummary).ShouldNot(BeNil())
})
It("provides information about the current test", func() {
description := CurrentGinkgoTestDescription()
Ω(description.ComponentTexts).Should(Equal([]string{"Suite", "running a suite", "provides information about the current test"}))
Ω(description.FullTestText).Should(Equal("Suite running a suite provides information about the current test"))
Ω(description.TestText).Should(Equal("provides information about the current test"))
Ω(description.IsMeasurement).Should(BeFalse())
Ω(description.FileName).Should(ContainSubstring("suite_test.go"))
Ω(description.LineNumber).Should(BeNumerically(">", 50))
Ω(description.LineNumber).Should(BeNumerically("<", 150))
Ω(description.Failed).Should(BeFalse())
})
Measure("should run measurements", func(b Benchmarker) {
r := rand.New(rand.NewSource(time.Now().UnixNano()))
runtime := b.Time("sleeping", func() {
sleepTime := time.Duration(r.Float64() * 0.01 * float64(time.Second))
time.Sleep(sleepTime)
})
Ω(runtime.Seconds()).Should(BeNumerically("<=", 1))
Ω(runtime.Seconds()).Should(BeNumerically(">=", 0))
randomValue := r.Float64() * 10.0
b.RecordValue("random value", randomValue)
Ω(randomValue).Should(BeNumerically("<=", 10.0))
Ω(randomValue).Should(BeNumerically(">=", 0.0))
b.RecordValueWithPrecision("specific value", 123.4567, "ms", 2)
b.RecordValueWithPrecision("specific value", 234.5678, "ms", 2)
}, 10)
It("creates a node hierarchy, converts it to a spec collection, and runs it", func() {
Ω(runOrder).Should(Equal([]string{
"BeforeSuite",
"top BE", "BE", "top JBE", "JBE", "IT", "AE", "top AE",
"top BE", "BE", "top JBE", "JBE", "inner IT", "AE", "top AE",
"top BE", "BE 2", "top JBE", "IT 2", "top AE",
"top BE", "top JBE", "top IT", "top AE",
"AfterSuite",
}))
})
Context("when told to randomize all specs", func() {
BeforeEach(func() {
randomizeAllSpecs = true
})
It("does", func() {
Ω(runOrder).Should(Equal([]string{
"BeforeSuite",
"top BE", "top JBE", "top IT", "top AE",
"top BE", "BE", "top JBE", "JBE", "inner IT", "AE", "top AE",
"top BE", "BE", "top JBE", "JBE", "IT", "AE", "top AE",
"top BE", "BE 2", "top JBE", "IT 2", "top AE",
"AfterSuite",
}))
})
})
Context("when provided with a filter", func() {
BeforeEach(func() {
focusString = `inner|\d`
})
It("converts the filter to a regular expression and uses it to filter the running specs", func() {
Ω(runOrder).Should(Equal([]string{
"BeforeSuite",
"top BE", "BE", "top JBE", "JBE", "inner IT", "AE", "top AE",
"top BE", "BE 2", "top JBE", "IT 2", "top AE",
"AfterSuite",
}))
})
It("should not report a programmatic focus", func() {
Ω(hasProgrammaticFocus).Should(BeFalse())
})
})
Context("with a programatically focused spec", func() {
BeforeEach(func() {
specSuite.PushItNode("focused it", f("focused it"), types.FlagTypeFocused, codelocation.New(0), 0)
specSuite.PushContainerNode("focused container", func() {
specSuite.PushItNode("inner focused it", f("inner focused it"), types.FlagTypeFocused, codelocation.New(0), 0)
specSuite.PushItNode("inner unfocused it", f("inner unfocused it"), types.FlagTypeNone, codelocation.New(0), 0)
}, types.FlagTypeFocused, codelocation.New(0))
})
It("should only run the focused test, applying backpropagation to favor most deeply focused leaf nodes", func() {
Ω(runOrder).Should(Equal([]string{
"BeforeSuite",
"top BE", "top JBE", "focused it", "top AE",
"top BE", "top JBE", "inner focused it", "top AE",
"AfterSuite",
}))
})
It("should report a programmatic focus", func() {
Ω(hasProgrammaticFocus).Should(BeTrue())
})
})
Context("when the specs pass", func() {
It("doesn't report a failure", func() {
Ω(fakeT.didFail).Should(BeFalse())
})
It("should return true", func() {
Ω(runResult).Should(BeTrue())
})
})
Context("when a spec fails", func() {
var location types.CodeLocation
BeforeEach(func() {
specSuite.PushItNode("top level it", func() {
location = codelocation.New(0)
failer.Fail("oops!", location)
}, types.FlagTypeNone, codelocation.New(0), 0)
})
It("should return false", func() {
Ω(runResult).Should(BeFalse())
})
It("reports a failure", func() {
Ω(fakeT.didFail).Should(BeTrue())
})
It("generates the correct failure data", func() {
Ω(fakeR.SpecSummaries[0].Failure.Message).Should(Equal("oops!"))
Ω(fakeR.SpecSummaries[0].Failure.Location).Should(Equal(location))
})
})
Context("when runnable nodes are nested within other runnable nodes", func() {
Context("when an It is nested", func() {
BeforeEach(func() {
specSuite.PushItNode("top level it", func() {
specSuite.PushItNode("nested it", f("oops"), types.FlagTypeNone, codelocation.New(0), 0)
}, types.FlagTypeNone, codelocation.New(0), 0)
})
It("should fail", func() {
Ω(fakeT.didFail).Should(BeTrue())
})
})
Context("when a Measure is nested", func() {
BeforeEach(func() {
specSuite.PushItNode("top level it", func() {
specSuite.PushMeasureNode("nested measure", func(Benchmarker) {}, types.FlagTypeNone, codelocation.New(0), 10)
}, types.FlagTypeNone, codelocation.New(0), 0)
})
It("should fail", func() {
Ω(fakeT.didFail).Should(BeTrue())
})
})
Context("when a BeforeEach is nested", func() {
BeforeEach(func() {
specSuite.PushItNode("top level it", func() {
specSuite.PushBeforeEachNode(f("nested bef"), codelocation.New(0), 0)
}, types.FlagTypeNone, codelocation.New(0), 0)
})
It("should fail", func() {
Ω(fakeT.didFail).Should(BeTrue())
})
})
Context("when a JustBeforeEach is nested", func() {
BeforeEach(func() {
specSuite.PushItNode("top level it", func() {
specSuite.PushJustBeforeEachNode(f("nested jbef"), codelocation.New(0), 0)
}, types.FlagTypeNone, codelocation.New(0), 0)
})
It("should fail", func() {
Ω(fakeT.didFail).Should(BeTrue())
})
})
Context("when a AfterEach is nested", func() {
BeforeEach(func() {
specSuite.PushItNode("top level it", func() {
specSuite.PushAfterEachNode(f("nested aft"), codelocation.New(0), 0)
}, types.FlagTypeNone, codelocation.New(0), 0)
})
It("should fail", func() {
Ω(fakeT.didFail).Should(BeTrue())
})
})
})
})
Describe("BeforeSuite", func() {
Context("when setting BeforeSuite more than once", func() {
It("should panic", func() {
specSuite.SetBeforeSuiteNode(func() {}, codelocation.New(0), 0)
Ω(func() {
specSuite.SetBeforeSuiteNode(func() {}, codelocation.New(0), 0)
}).Should(Panic())
})
})
})
Describe("AfterSuite", func() {
Context("when setting AfterSuite more than once", func() {
It("should panic", func() {
specSuite.SetAfterSuiteNode(func() {}, codelocation.New(0), 0)
Ω(func() {
specSuite.SetAfterSuiteNode(func() {}, codelocation.New(0), 0)
}).Should(Panic())
})
})
})
Describe("By", func() {
It("writes to the GinkgoWriter", func() {
originalGinkgoWriter := GinkgoWriter
buffer := &bytes.Buffer{}
GinkgoWriter = buffer
By("Saying Hello GinkgoWriter")
GinkgoWriter = originalGinkgoWriter
Ω(buffer.String()).Should(ContainSubstring("STEP"))
Ω(buffer.String()).Should(ContainSubstring(": Saying Hello GinkgoWriter\n"))
})
It("calls the passed-in callback if present", func() {
a := 0
By("calling the callback", func() {
a = 1
})
Ω(a).Should(Equal(1))
})
It("panics if there is more than one callback", func() {
Ω(func() {
By("registering more than one callback", func() {}, func() {})
}).Should(Panic())
})
})
Describe("GinkgoRandomSeed", func() {
It("returns the current config's random seed", func() {
Ω(GinkgoRandomSeed()).Should(Equal(config.GinkgoConfig.RandomSeed))
})
})
})

View file

@ -0,0 +1,76 @@
package testingtproxy
import (
"fmt"
"io"
)
type failFunc func(message string, callerSkip ...int)
func New(writer io.Writer, fail failFunc, offset int) *ginkgoTestingTProxy {
return &ginkgoTestingTProxy{
fail: fail,
offset: offset,
writer: writer,
}
}
type ginkgoTestingTProxy struct {
fail failFunc
offset int
writer io.Writer
}
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) 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) 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) Failed() bool {
return false
}
func (t *ginkgoTestingTProxy) Parallel() {
}
func (t *ginkgoTestingTProxy) Skip(args ...interface{}) {
fmt.Println(args...)
}
func (t *ginkgoTestingTProxy) Skipf(format string, args ...interface{}) {
t.Skip(fmt.Sprintf(format, args...))
}
func (t *ginkgoTestingTProxy) SkipNow() {
}
func (t *ginkgoTestingTProxy) Skipped() bool {
return false
}

View file

@ -0,0 +1,36 @@
package writer
type FakeGinkgoWriter struct {
EventStream []string
}
func NewFake() *FakeGinkgoWriter {
return &FakeGinkgoWriter{
EventStream: []string{},
}
}
func (writer *FakeGinkgoWriter) AddEvent(event string) {
writer.EventStream = append(writer.EventStream, event)
}
func (writer *FakeGinkgoWriter) Truncate() {
writer.EventStream = append(writer.EventStream, "TRUNCATE")
}
func (writer *FakeGinkgoWriter) DumpOut() {
writer.EventStream = append(writer.EventStream, "DUMP")
}
func (writer *FakeGinkgoWriter) DumpOutWithHeader(header string) {
writer.EventStream = append(writer.EventStream, "DUMP_WITH_HEADER: "+header)
}
func (writer *FakeGinkgoWriter) Bytes() []byte {
writer.EventStream = append(writer.EventStream, "BYTES")
return nil
}
func (writer *FakeGinkgoWriter) Write(data []byte) (n int, err error) {
return 0, nil
}

View file

@ -0,0 +1,81 @@
package writer
import (
"bytes"
"io"
"sync"
)
type WriterInterface interface {
io.Writer
Truncate()
DumpOut()
DumpOutWithHeader(header string)
Bytes() []byte
}
type Writer struct {
buffer *bytes.Buffer
outWriter io.Writer
lock *sync.Mutex
stream bool
}
func New(outWriter io.Writer) *Writer {
return &Writer{
buffer: &bytes.Buffer{},
lock: &sync.Mutex{},
outWriter: outWriter,
stream: true,
}
}
func (w *Writer) SetStream(stream bool) {
w.lock.Lock()
defer w.lock.Unlock()
w.stream = stream
}
func (w *Writer) Write(b []byte) (n int, err error) {
w.lock.Lock()
defer w.lock.Unlock()
n, err = w.buffer.Write(b)
if w.stream {
return w.outWriter.Write(b)
}
return n, err
}
func (w *Writer) Truncate() {
w.lock.Lock()
defer w.lock.Unlock()
w.buffer.Reset()
}
func (w *Writer) DumpOut() {
w.lock.Lock()
defer w.lock.Unlock()
if !w.stream {
w.buffer.WriteTo(w.outWriter)
}
}
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
}
func (w *Writer) DumpOutWithHeader(header string) {
w.lock.Lock()
defer w.lock.Unlock()
if !w.stream && w.buffer.Len() > 0 {
w.outWriter.Write([]byte(header))
w.buffer.WriteTo(w.outWriter)
}
}

View file

@ -0,0 +1,13 @@
package writer_test
import (
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
"testing"
)
func TestWriter(t *testing.T) {
RegisterFailHandler(Fail)
RunSpecs(t, "Writer Suite")
}

View file

@ -0,0 +1,75 @@
package writer_test
import (
"github.com/onsi/gomega/gbytes"
. "github.com/onsi/ginkgo"
. "github.com/onsi/ginkgo/internal/writer"
. "github.com/onsi/gomega"
)
var _ = Describe("Writer", func() {
var writer *Writer
var out *gbytes.Buffer
BeforeEach(func() {
out = gbytes.NewBuffer()
writer = New(out)
})
It("should stream directly to the outbuffer by default", func() {
writer.Write([]byte("foo"))
Ω(out).Should(gbytes.Say("foo"))
})
It("should not emit the header when asked to DumpOutWitHeader", func() {
writer.Write([]byte("foo"))
writer.DumpOutWithHeader("my header")
Ω(out).ShouldNot(gbytes.Say("my header"))
Ω(out).Should(gbytes.Say("foo"))
})
Context("when told not to stream", func() {
BeforeEach(func() {
writer.SetStream(false)
})
It("should only write to the buffer when told to DumpOut", func() {
writer.Write([]byte("foo"))
Ω(out).ShouldNot(gbytes.Say("foo"))
writer.DumpOut()
Ω(out).Should(gbytes.Say("foo"))
})
It("should truncate the internal buffer when told to truncate", func() {
writer.Write([]byte("foo"))
writer.Truncate()
writer.DumpOut()
Ω(out).ShouldNot(gbytes.Say("foo"))
writer.Write([]byte("bar"))
writer.DumpOut()
Ω(out).Should(gbytes.Say("bar"))
})
Describe("emitting a header", func() {
Context("when the buffer has content", func() {
It("should emit the header followed by the content", func() {
writer.Write([]byte("foo"))
writer.DumpOutWithHeader("my header")
Ω(out).Should(gbytes.Say("my header"))
Ω(out).Should(gbytes.Say("foo"))
})
})
Context("when the buffer has no content", func() {
It("should not emit the header", func() {
writer.DumpOutWithHeader("my header")
Ω(out).ShouldNot(gbytes.Say("my header"))
})
})
})
})
})

View file

@ -0,0 +1,84 @@
/*
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 (
"github.com/onsi/ginkgo/config"
"github.com/onsi/ginkgo/reporters/stenographer"
"github.com/onsi/ginkgo/types"
)
type DefaultReporter struct {
config config.DefaultReporterConfigType
stenographer stenographer.Stenographer
specSummaries []*types.SpecSummary
}
func NewDefaultReporter(config config.DefaultReporterConfigType, stenographer stenographer.Stenographer) *DefaultReporter {
return &DefaultReporter{
config: config,
stenographer: stenographer,
}
}
func (reporter *DefaultReporter) SpecSuiteWillBegin(config config.GinkgoConfigType, summary *types.SuiteSummary) {
reporter.stenographer.AnnounceSuite(summary.SuiteDescription, config.RandomSeed, config.RandomizeAllSpecs, reporter.config.Succinct)
if config.ParallelTotal > 1 {
reporter.stenographer.AnnounceParallelRun(config.ParallelNode, config.ParallelTotal, reporter.config.Succinct)
} else {
reporter.stenographer.AnnounceNumberOfSpecs(summary.NumberOfSpecsThatWillBeRun, summary.NumberOfTotalSpecs, reporter.config.Succinct)
}
}
func (reporter *DefaultReporter) BeforeSuiteDidRun(setupSummary *types.SetupSummary) {
if setupSummary.State != types.SpecStatePassed {
reporter.stenographer.AnnounceBeforeSuiteFailure(setupSummary, reporter.config.Succinct, reporter.config.FullTrace)
}
}
func (reporter *DefaultReporter) AfterSuiteDidRun(setupSummary *types.SetupSummary) {
if setupSummary.State != types.SpecStatePassed {
reporter.stenographer.AnnounceAfterSuiteFailure(setupSummary, reporter.config.Succinct, reporter.config.FullTrace)
}
}
func (reporter *DefaultReporter) SpecWillRun(specSummary *types.SpecSummary) {
if reporter.config.Verbose && !reporter.config.Succinct && specSummary.State != types.SpecStatePending && specSummary.State != types.SpecStateSkipped {
reporter.stenographer.AnnounceSpecWillRun(specSummary)
}
}
func (reporter *DefaultReporter) SpecDidComplete(specSummary *types.SpecSummary) {
switch specSummary.State {
case types.SpecStatePassed:
if specSummary.IsMeasurement {
reporter.stenographer.AnnounceSuccesfulMeasurement(specSummary, reporter.config.Succinct)
} else if specSummary.RunTime.Seconds() >= reporter.config.SlowSpecThreshold {
reporter.stenographer.AnnounceSuccesfulSlowSpec(specSummary, reporter.config.Succinct)
} else {
reporter.stenographer.AnnounceSuccesfulSpec(specSummary)
}
case types.SpecStatePending:
reporter.stenographer.AnnouncePendingSpec(specSummary, reporter.config.NoisyPendings && !reporter.config.Succinct)
case types.SpecStateSkipped:
reporter.stenographer.AnnounceSkippedSpec(specSummary, reporter.config.Succinct, reporter.config.FullTrace)
case types.SpecStateTimedOut:
reporter.stenographer.AnnounceSpecTimedOut(specSummary, reporter.config.Succinct, reporter.config.FullTrace)
case types.SpecStatePanicked:
reporter.stenographer.AnnounceSpecPanicked(specSummary, reporter.config.Succinct, reporter.config.FullTrace)
case types.SpecStateFailed:
reporter.stenographer.AnnounceSpecFailed(specSummary, reporter.config.Succinct, reporter.config.FullTrace)
}
reporter.specSummaries = append(reporter.specSummaries, specSummary)
}
func (reporter *DefaultReporter) SpecSuiteDidEnd(summary *types.SuiteSummary) {
reporter.stenographer.SummarizeFailures(reporter.specSummaries)
reporter.stenographer.AnnounceSpecRunCompletion(summary, reporter.config.Succinct)
}

View file

@ -0,0 +1,414 @@
package reporters_test
import (
"time"
. "github.com/onsi/ginkgo"
"github.com/onsi/ginkgo/config"
"github.com/onsi/ginkgo/reporters"
st "github.com/onsi/ginkgo/reporters/stenographer"
"github.com/onsi/ginkgo/types"
. "github.com/onsi/gomega"
)
var _ = Describe("DefaultReporter", func() {
var (
reporter *reporters.DefaultReporter
reporterConfig config.DefaultReporterConfigType
stenographer *st.FakeStenographer
ginkgoConfig config.GinkgoConfigType
suite *types.SuiteSummary
spec *types.SpecSummary
)
BeforeEach(func() {
stenographer = st.NewFakeStenographer()
reporterConfig = config.DefaultReporterConfigType{
NoColor: false,
SlowSpecThreshold: 0.1,
NoisyPendings: false,
Verbose: true,
FullTrace: true,
}
reporter = reporters.NewDefaultReporter(reporterConfig, stenographer)
})
call := func(method string, args ...interface{}) st.FakeStenographerCall {
return st.NewFakeStenographerCall(method, args...)
}
Describe("SpecSuiteWillBegin", func() {
BeforeEach(func() {
suite = &types.SuiteSummary{
SuiteDescription: "A Sweet Suite",
NumberOfTotalSpecs: 10,
NumberOfSpecsThatWillBeRun: 8,
}
ginkgoConfig = config.GinkgoConfigType{
RandomSeed: 1138,
RandomizeAllSpecs: true,
}
})
Context("when a serial (non-parallel) suite begins", func() {
BeforeEach(func() {
ginkgoConfig.ParallelTotal = 1
reporter.SpecSuiteWillBegin(ginkgoConfig, suite)
})
It("should announce the suite, then announce the number of specs", func() {
Ω(stenographer.Calls()).Should(HaveLen(2))
Ω(stenographer.Calls()[0]).Should(Equal(call("AnnounceSuite", "A Sweet Suite", ginkgoConfig.RandomSeed, true, false)))
Ω(stenographer.Calls()[1]).Should(Equal(call("AnnounceNumberOfSpecs", 8, 10, false)))
})
})
Context("when a parallel suite begins", func() {
BeforeEach(func() {
ginkgoConfig.ParallelTotal = 2
ginkgoConfig.ParallelNode = 1
suite.NumberOfSpecsBeforeParallelization = 20
reporter.SpecSuiteWillBegin(ginkgoConfig, suite)
})
It("should announce the suite, announce that it's a parallel run, then announce the number of specs", func() {
Ω(stenographer.Calls()).Should(HaveLen(2))
Ω(stenographer.Calls()[0]).Should(Equal(call("AnnounceSuite", "A Sweet Suite", ginkgoConfig.RandomSeed, true, false)))
Ω(stenographer.Calls()[1]).Should(Equal(call("AnnounceParallelRun", 1, 2, false)))
})
})
})
Describe("BeforeSuiteDidRun", func() {
Context("when the BeforeSuite passes", func() {
It("should announce nothing", func() {
reporter.BeforeSuiteDidRun(&types.SetupSummary{
State: types.SpecStatePassed,
})
Ω(stenographer.Calls()).Should(BeEmpty())
})
})
Context("when the BeforeSuite fails", func() {
It("should announce the failure", func() {
summary := &types.SetupSummary{
State: types.SpecStateFailed,
}
reporter.BeforeSuiteDidRun(summary)
Ω(stenographer.Calls()).Should(HaveLen(1))
Ω(stenographer.Calls()[0]).Should(Equal(call("AnnounceBeforeSuiteFailure", summary, false, true)))
})
})
})
Describe("AfterSuiteDidRun", func() {
Context("when the AfterSuite passes", func() {
It("should announce nothing", func() {
reporter.AfterSuiteDidRun(&types.SetupSummary{
State: types.SpecStatePassed,
})
Ω(stenographer.Calls()).Should(BeEmpty())
})
})
Context("when the AfterSuite fails", func() {
It("should announce the failure", func() {
summary := &types.SetupSummary{
State: types.SpecStateFailed,
}
reporter.AfterSuiteDidRun(summary)
Ω(stenographer.Calls()).Should(HaveLen(1))
Ω(stenographer.Calls()[0]).Should(Equal(call("AnnounceAfterSuiteFailure", summary, false, true)))
})
})
})
Describe("SpecWillRun", func() {
Context("When running in verbose mode", func() {
Context("and the spec will run", func() {
BeforeEach(func() {
spec = &types.SpecSummary{}
reporter.SpecWillRun(spec)
})
It("should announce that the spec will run", func() {
Ω(stenographer.Calls()).Should(HaveLen(1))
Ω(stenographer.Calls()[0]).Should(Equal(call("AnnounceSpecWillRun", spec)))
})
})
Context("and the spec will not run", func() {
Context("because it is pending", func() {
BeforeEach(func() {
spec = &types.SpecSummary{
State: types.SpecStatePending,
}
reporter.SpecWillRun(spec)
})
It("should announce nothing", func() {
Ω(stenographer.Calls()).Should(BeEmpty())
})
})
Context("because it is skipped", func() {
BeforeEach(func() {
spec = &types.SpecSummary{
State: types.SpecStateSkipped,
}
reporter.SpecWillRun(spec)
})
It("should announce nothing", func() {
Ω(stenographer.Calls()).Should(BeEmpty())
})
})
})
})
Context("When running in verbose & succinct mode", func() {
BeforeEach(func() {
reporterConfig.Succinct = true
reporter = reporters.NewDefaultReporter(reporterConfig, stenographer)
spec = &types.SpecSummary{}
reporter.SpecWillRun(spec)
})
It("should announce nothing", func() {
Ω(stenographer.Calls()).Should(BeEmpty())
})
})
Context("When not running in verbose mode", func() {
BeforeEach(func() {
reporterConfig.Verbose = false
reporter = reporters.NewDefaultReporter(reporterConfig, stenographer)
spec = &types.SpecSummary{}
reporter.SpecWillRun(spec)
})
It("should announce nothing", func() {
Ω(stenographer.Calls()).Should(BeEmpty())
})
})
})
Describe("SpecDidComplete", func() {
JustBeforeEach(func() {
reporter.SpecDidComplete(spec)
})
BeforeEach(func() {
spec = &types.SpecSummary{}
})
Context("When the spec passed", func() {
BeforeEach(func() {
spec.State = types.SpecStatePassed
})
Context("When the spec was a measurement", func() {
BeforeEach(func() {
spec.IsMeasurement = true
})
It("should announce the measurement", func() {
Ω(stenographer.Calls()[0]).Should(Equal(call("AnnounceSuccesfulMeasurement", spec, false)))
})
})
Context("When the spec is slow", func() {
BeforeEach(func() {
spec.RunTime = time.Second
})
It("should announce that it was slow", func() {
Ω(stenographer.Calls()[0]).Should(Equal(call("AnnounceSuccesfulSlowSpec", spec, false)))
})
})
Context("Otherwise", func() {
It("should announce the succesful spec", func() {
Ω(stenographer.Calls()[0]).Should(Equal(call("AnnounceSuccesfulSpec", spec)))
})
})
})
Context("When the spec is pending", func() {
BeforeEach(func() {
spec.State = types.SpecStatePending
})
It("should announce the pending spec, succinctly", func() {
Ω(stenographer.Calls()[0]).Should(Equal(call("AnnouncePendingSpec", spec, false)))
})
})
Context("When the spec is skipped", func() {
BeforeEach(func() {
spec.State = types.SpecStateSkipped
})
It("should announce the skipped spec", func() {
Ω(stenographer.Calls()[0]).Should(Equal(call("AnnounceSkippedSpec", spec, false, true)))
})
})
Context("When the spec timed out", func() {
BeforeEach(func() {
spec.State = types.SpecStateTimedOut
})
It("should announce the timedout spec", func() {
Ω(stenographer.Calls()[0]).Should(Equal(call("AnnounceSpecTimedOut", spec, false, true)))
})
})
Context("When the spec panicked", func() {
BeforeEach(func() {
spec.State = types.SpecStatePanicked
})
It("should announce the panicked spec", func() {
Ω(stenographer.Calls()[0]).Should(Equal(call("AnnounceSpecPanicked", spec, false, true)))
})
})
Context("When the spec failed", func() {
BeforeEach(func() {
spec.State = types.SpecStateFailed
})
It("should announce the failed spec", func() {
Ω(stenographer.Calls()[0]).Should(Equal(call("AnnounceSpecFailed", spec, false, true)))
})
})
Context("in noisy pendings mode", func() {
BeforeEach(func() {
reporterConfig.Succinct = false
reporterConfig.NoisyPendings = true
reporter = reporters.NewDefaultReporter(reporterConfig, stenographer)
})
Context("When the spec is pending", func() {
BeforeEach(func() {
spec.State = types.SpecStatePending
})
It("should announce the pending spec, noisily", func() {
Ω(stenographer.Calls()[0]).Should(Equal(call("AnnouncePendingSpec", spec, true)))
})
})
})
Context("in succinct mode", func() {
BeforeEach(func() {
reporterConfig.Succinct = true
reporter = reporters.NewDefaultReporter(reporterConfig, stenographer)
})
Context("When the spec passed", func() {
BeforeEach(func() {
spec.State = types.SpecStatePassed
})
Context("When the spec was a measurement", func() {
BeforeEach(func() {
spec.IsMeasurement = true
})
It("should announce the measurement", func() {
Ω(stenographer.Calls()[0]).Should(Equal(call("AnnounceSuccesfulMeasurement", spec, true)))
})
})
Context("When the spec is slow", func() {
BeforeEach(func() {
spec.RunTime = time.Second
})
It("should announce that it was slow", func() {
Ω(stenographer.Calls()[0]).Should(Equal(call("AnnounceSuccesfulSlowSpec", spec, true)))
})
})
Context("Otherwise", func() {
It("should announce the succesful spec", func() {
Ω(stenographer.Calls()[0]).Should(Equal(call("AnnounceSuccesfulSpec", spec)))
})
})
})
Context("When the spec is pending", func() {
BeforeEach(func() {
spec.State = types.SpecStatePending
})
It("should announce the pending spec, succinctly", func() {
Ω(stenographer.Calls()[0]).Should(Equal(call("AnnouncePendingSpec", spec, false)))
})
})
Context("When the spec is skipped", func() {
BeforeEach(func() {
spec.State = types.SpecStateSkipped
})
It("should announce the skipped spec", func() {
Ω(stenographer.Calls()[0]).Should(Equal(call("AnnounceSkippedSpec", spec, true, true)))
})
})
Context("When the spec timed out", func() {
BeforeEach(func() {
spec.State = types.SpecStateTimedOut
})
It("should announce the timedout spec", func() {
Ω(stenographer.Calls()[0]).Should(Equal(call("AnnounceSpecTimedOut", spec, true, true)))
})
})
Context("When the spec panicked", func() {
BeforeEach(func() {
spec.State = types.SpecStatePanicked
})
It("should announce the panicked spec", func() {
Ω(stenographer.Calls()[0]).Should(Equal(call("AnnounceSpecPanicked", spec, true, true)))
})
})
Context("When the spec failed", func() {
BeforeEach(func() {
spec.State = types.SpecStateFailed
})
It("should announce the failed spec", func() {
Ω(stenographer.Calls()[0]).Should(Equal(call("AnnounceSpecFailed", spec, true, true)))
})
})
})
})
Describe("SpecSuiteDidEnd", func() {
BeforeEach(func() {
suite = &types.SuiteSummary{}
reporter.SpecSuiteDidEnd(suite)
})
It("should announce the spec run's completion", func() {
Ω(stenographer.Calls()[1]).Should(Equal(call("AnnounceSpecRunCompletion", suite, false)))
})
})
})

View file

@ -0,0 +1,59 @@
package reporters
import (
"github.com/onsi/ginkgo/config"
"github.com/onsi/ginkgo/types"
)
//FakeReporter is useful for testing purposes
type FakeReporter struct {
Config config.GinkgoConfigType
BeginSummary *types.SuiteSummary
BeforeSuiteSummary *types.SetupSummary
SpecWillRunSummaries []*types.SpecSummary
SpecSummaries []*types.SpecSummary
AfterSuiteSummary *types.SetupSummary
EndSummary *types.SuiteSummary
SpecWillRunStub func(specSummary *types.SpecSummary)
SpecDidCompleteStub func(specSummary *types.SpecSummary)
}
func NewFakeReporter() *FakeReporter {
return &FakeReporter{
SpecWillRunSummaries: make([]*types.SpecSummary, 0),
SpecSummaries: make([]*types.SpecSummary, 0),
}
}
func (fakeR *FakeReporter) SpecSuiteWillBegin(config config.GinkgoConfigType, summary *types.SuiteSummary) {
fakeR.Config = config
fakeR.BeginSummary = summary
}
func (fakeR *FakeReporter) BeforeSuiteDidRun(setupSummary *types.SetupSummary) {
fakeR.BeforeSuiteSummary = setupSummary
}
func (fakeR *FakeReporter) SpecWillRun(specSummary *types.SpecSummary) {
if fakeR.SpecWillRunStub != nil {
fakeR.SpecWillRunStub(specSummary)
}
fakeR.SpecWillRunSummaries = append(fakeR.SpecWillRunSummaries, specSummary)
}
func (fakeR *FakeReporter) SpecDidComplete(specSummary *types.SpecSummary) {
if fakeR.SpecDidCompleteStub != nil {
fakeR.SpecDidCompleteStub(specSummary)
}
fakeR.SpecSummaries = append(fakeR.SpecSummaries, specSummary)
}
func (fakeR *FakeReporter) AfterSuiteDidRun(setupSummary *types.SetupSummary) {
fakeR.AfterSuiteSummary = setupSummary
}
func (fakeR *FakeReporter) SpecSuiteDidEnd(summary *types.SuiteSummary) {
fakeR.EndSummary = summary
}

View file

@ -0,0 +1,147 @@
/*
JUnit XML Reporter for Ginkgo
For usage instructions: http://onsi.github.io/ginkgo/#generating_junit_xml_output
*/
package reporters
import (
"encoding/xml"
"fmt"
"os"
"strings"
"github.com/onsi/ginkgo/config"
"github.com/onsi/ginkgo/types"
)
type JUnitTestSuite struct {
XMLName xml.Name `xml:"testsuite"`
TestCases []JUnitTestCase `xml:"testcase"`
Tests int `xml:"tests,attr"`
Failures int `xml:"failures,attr"`
Time float64 `xml:"time,attr"`
}
type JUnitTestCase struct {
Name string `xml:"name,attr"`
ClassName string `xml:"classname,attr"`
FailureMessage *JUnitFailureMessage `xml:"failure,omitempty"`
Skipped *JUnitSkipped `xml:"skipped,omitempty"`
Time float64 `xml:"time,attr"`
SystemOut string `xml:"system-out,omitempty"`
}
type JUnitFailureMessage struct {
Type string `xml:"type,attr"`
Message string `xml:",chardata"`
}
type JUnitSkipped struct {
XMLName xml.Name `xml:"skipped"`
}
type JUnitReporter struct {
suite JUnitTestSuite
filename string
testSuiteName string
}
//NewJUnitReporter creates a new JUnit XML reporter. The XML will be stored in the passed in filename.
func NewJUnitReporter(filename string) *JUnitReporter {
return &JUnitReporter{
filename: filename,
}
}
func (reporter *JUnitReporter) SpecSuiteWillBegin(config config.GinkgoConfigType, summary *types.SuiteSummary) {
reporter.suite = JUnitTestSuite{
TestCases: []JUnitTestCase{},
}
reporter.testSuiteName = summary.SuiteDescription
}
func (reporter *JUnitReporter) SpecWillRun(specSummary *types.SpecSummary) {
}
func (reporter *JUnitReporter) BeforeSuiteDidRun(setupSummary *types.SetupSummary) {
reporter.handleSetupSummary("BeforeSuite", setupSummary)
}
func (reporter *JUnitReporter) AfterSuiteDidRun(setupSummary *types.SetupSummary) {
reporter.handleSetupSummary("AfterSuite", setupSummary)
}
func failureMessage(failure types.SpecFailure) string {
return fmt.Sprintf("%s\n%s\n%s", failure.ComponentCodeLocation.String(), failure.Message, failure.Location.String())
}
func (reporter *JUnitReporter) handleSetupSummary(name string, setupSummary *types.SetupSummary) {
if setupSummary.State != types.SpecStatePassed {
testCase := JUnitTestCase{
Name: name,
ClassName: reporter.testSuiteName,
}
testCase.FailureMessage = &JUnitFailureMessage{
Type: reporter.failureTypeForState(setupSummary.State),
Message: failureMessage(setupSummary.Failure),
}
testCase.SystemOut = setupSummary.CapturedOutput
testCase.Time = setupSummary.RunTime.Seconds()
reporter.suite.TestCases = append(reporter.suite.TestCases, testCase)
}
}
func (reporter *JUnitReporter) SpecDidComplete(specSummary *types.SpecSummary) {
testCase := JUnitTestCase{
Name: strings.Join(specSummary.ComponentTexts[1:], " "),
ClassName: reporter.testSuiteName,
}
if specSummary.State == types.SpecStateFailed || specSummary.State == types.SpecStateTimedOut || specSummary.State == types.SpecStatePanicked {
testCase.FailureMessage = &JUnitFailureMessage{
Type: reporter.failureTypeForState(specSummary.State),
Message: failureMessage(specSummary.Failure),
}
testCase.SystemOut = specSummary.CapturedOutput
}
if specSummary.State == types.SpecStateSkipped || specSummary.State == types.SpecStatePending {
testCase.Skipped = &JUnitSkipped{}
}
testCase.Time = specSummary.RunTime.Seconds()
reporter.suite.TestCases = append(reporter.suite.TestCases, testCase)
}
func (reporter *JUnitReporter) SpecSuiteDidEnd(summary *types.SuiteSummary) {
reporter.suite.Tests = summary.NumberOfSpecsThatWillBeRun
reporter.suite.Time = summary.RunTime.Seconds()
reporter.suite.Failures = summary.NumberOfFailedSpecs
file, err := os.Create(reporter.filename)
if err != nil {
fmt.Printf("Failed to create JUnit report file: %s\n\t%s", reporter.filename, err.Error())
}
defer file.Close()
file.WriteString(xml.Header)
encoder := xml.NewEncoder(file)
encoder.Indent(" ", " ")
err = encoder.Encode(reporter.suite)
if err != nil {
fmt.Printf("Failed to generate JUnit report\n\t%s", err.Error())
}
}
func (reporter *JUnitReporter) failureTypeForState(state types.SpecState) string {
switch state {
case types.SpecStateFailed:
return "Failure"
case types.SpecStateTimedOut:
return "Timeout"
case types.SpecStatePanicked:
return "Panic"
default:
return ""
}
}

View file

@ -0,0 +1,247 @@
package reporters_test
import (
"encoding/xml"
"io/ioutil"
"os"
"time"
. "github.com/onsi/ginkgo"
"github.com/onsi/ginkgo/config"
"github.com/onsi/ginkgo/internal/codelocation"
"github.com/onsi/ginkgo/reporters"
"github.com/onsi/ginkgo/types"
. "github.com/onsi/gomega"
)
var _ = Describe("JUnit Reporter", func() {
var (
outputFile string
reporter Reporter
)
readOutputFile := func() reporters.JUnitTestSuite {
bytes, err := ioutil.ReadFile(outputFile)
Ω(err).ShouldNot(HaveOccurred())
var suite reporters.JUnitTestSuite
err = xml.Unmarshal(bytes, &suite)
Ω(err).ShouldNot(HaveOccurred())
return suite
}
BeforeEach(func() {
f, err := ioutil.TempFile("", "output")
Ω(err).ShouldNot(HaveOccurred())
f.Close()
outputFile = f.Name()
reporter = reporters.NewJUnitReporter(outputFile)
reporter.SpecSuiteWillBegin(config.GinkgoConfigType{}, &types.SuiteSummary{
SuiteDescription: "My test suite",
NumberOfSpecsThatWillBeRun: 1,
})
})
AfterEach(func() {
os.RemoveAll(outputFile)
})
Describe("a passing test", func() {
BeforeEach(func() {
beforeSuite := &types.SetupSummary{
State: types.SpecStatePassed,
}
reporter.BeforeSuiteDidRun(beforeSuite)
afterSuite := &types.SetupSummary{
State: types.SpecStatePassed,
}
reporter.AfterSuiteDidRun(afterSuite)
spec := &types.SpecSummary{
ComponentTexts: []string{"[Top Level]", "A", "B", "C"},
State: types.SpecStatePassed,
RunTime: 5 * time.Second,
}
reporter.SpecWillRun(spec)
reporter.SpecDidComplete(spec)
reporter.SpecSuiteDidEnd(&types.SuiteSummary{
NumberOfSpecsThatWillBeRun: 1,
NumberOfFailedSpecs: 0,
RunTime: 10 * time.Second,
})
})
It("should record the test as passing", func() {
output := readOutputFile()
Ω(output.Tests).Should(Equal(1))
Ω(output.Failures).Should(Equal(0))
Ω(output.Time).Should(Equal(10.0))
Ω(output.TestCases).Should(HaveLen(1))
Ω(output.TestCases[0].Name).Should(Equal("A B C"))
Ω(output.TestCases[0].ClassName).Should(Equal("My test suite"))
Ω(output.TestCases[0].FailureMessage).Should(BeNil())
Ω(output.TestCases[0].Skipped).Should(BeNil())
Ω(output.TestCases[0].Time).Should(Equal(5.0))
})
})
Describe("when the BeforeSuite fails", func() {
var beforeSuite *types.SetupSummary
BeforeEach(func() {
beforeSuite = &types.SetupSummary{
State: types.SpecStateFailed,
RunTime: 3 * time.Second,
Failure: types.SpecFailure{
Message: "failed to setup",
ComponentCodeLocation: codelocation.New(0),
Location: codelocation.New(2),
},
}
reporter.BeforeSuiteDidRun(beforeSuite)
reporter.SpecSuiteDidEnd(&types.SuiteSummary{
NumberOfSpecsThatWillBeRun: 1,
NumberOfFailedSpecs: 1,
RunTime: 10 * time.Second,
})
})
It("should record the test as having failed", func() {
output := readOutputFile()
Ω(output.Tests).Should(Equal(1))
Ω(output.Failures).Should(Equal(1))
Ω(output.Time).Should(Equal(10.0))
Ω(output.TestCases[0].Name).Should(Equal("BeforeSuite"))
Ω(output.TestCases[0].Time).Should(Equal(3.0))
Ω(output.TestCases[0].ClassName).Should(Equal("My test suite"))
Ω(output.TestCases[0].FailureMessage.Type).Should(Equal("Failure"))
Ω(output.TestCases[0].FailureMessage.Message).Should(ContainSubstring("failed to setup"))
Ω(output.TestCases[0].FailureMessage.Message).Should(ContainSubstring(beforeSuite.Failure.ComponentCodeLocation.String()))
Ω(output.TestCases[0].FailureMessage.Message).Should(ContainSubstring(beforeSuite.Failure.Location.String()))
Ω(output.TestCases[0].Skipped).Should(BeNil())
})
})
Describe("when the AfterSuite fails", func() {
var afterSuite *types.SetupSummary
BeforeEach(func() {
afterSuite = &types.SetupSummary{
State: types.SpecStateFailed,
RunTime: 3 * time.Second,
Failure: types.SpecFailure{
Message: "failed to setup",
ComponentCodeLocation: codelocation.New(0),
Location: codelocation.New(2),
},
}
reporter.AfterSuiteDidRun(afterSuite)
reporter.SpecSuiteDidEnd(&types.SuiteSummary{
NumberOfSpecsThatWillBeRun: 1,
NumberOfFailedSpecs: 1,
RunTime: 10 * time.Second,
})
})
It("should record the test as having failed", func() {
output := readOutputFile()
Ω(output.Tests).Should(Equal(1))
Ω(output.Failures).Should(Equal(1))
Ω(output.Time).Should(Equal(10.0))
Ω(output.TestCases[0].Name).Should(Equal("AfterSuite"))
Ω(output.TestCases[0].Time).Should(Equal(3.0))
Ω(output.TestCases[0].ClassName).Should(Equal("My test suite"))
Ω(output.TestCases[0].FailureMessage.Type).Should(Equal("Failure"))
Ω(output.TestCases[0].FailureMessage.Message).Should(ContainSubstring("failed to setup"))
Ω(output.TestCases[0].FailureMessage.Message).Should(ContainSubstring(afterSuite.Failure.ComponentCodeLocation.String()))
Ω(output.TestCases[0].FailureMessage.Message).Should(ContainSubstring(afterSuite.Failure.Location.String()))
Ω(output.TestCases[0].Skipped).Should(BeNil())
})
})
specStateCases := []struct {
state types.SpecState
message string
}{
{types.SpecStateFailed, "Failure"},
{types.SpecStateTimedOut, "Timeout"},
{types.SpecStatePanicked, "Panic"},
}
for _, specStateCase := range specStateCases {
specStateCase := specStateCase
Describe("a failing test", func() {
var spec *types.SpecSummary
BeforeEach(func() {
spec = &types.SpecSummary{
ComponentTexts: []string{"[Top Level]", "A", "B", "C"},
State: specStateCase.state,
RunTime: 5 * time.Second,
Failure: types.SpecFailure{
ComponentCodeLocation: codelocation.New(0),
Location: codelocation.New(2),
Message: "I failed",
},
}
reporter.SpecWillRun(spec)
reporter.SpecDidComplete(spec)
reporter.SpecSuiteDidEnd(&types.SuiteSummary{
NumberOfSpecsThatWillBeRun: 1,
NumberOfFailedSpecs: 1,
RunTime: 10 * time.Second,
})
})
It("should record test as failing", func() {
output := readOutputFile()
Ω(output.Tests).Should(Equal(1))
Ω(output.Failures).Should(Equal(1))
Ω(output.Time).Should(Equal(10.0))
Ω(output.TestCases[0].Name).Should(Equal("A B C"))
Ω(output.TestCases[0].ClassName).Should(Equal("My test suite"))
Ω(output.TestCases[0].FailureMessage.Type).Should(Equal(specStateCase.message))
Ω(output.TestCases[0].FailureMessage.Message).Should(ContainSubstring("I failed"))
Ω(output.TestCases[0].FailureMessage.Message).Should(ContainSubstring(spec.Failure.ComponentCodeLocation.String()))
Ω(output.TestCases[0].FailureMessage.Message).Should(ContainSubstring(spec.Failure.Location.String()))
Ω(output.TestCases[0].Skipped).Should(BeNil())
})
})
}
for _, specStateCase := range []types.SpecState{types.SpecStatePending, types.SpecStateSkipped} {
specStateCase := specStateCase
Describe("a skipped test", func() {
var spec *types.SpecSummary
BeforeEach(func() {
spec = &types.SpecSummary{
ComponentTexts: []string{"[Top Level]", "A", "B", "C"},
State: specStateCase,
RunTime: 5 * time.Second,
}
reporter.SpecWillRun(spec)
reporter.SpecDidComplete(spec)
reporter.SpecSuiteDidEnd(&types.SuiteSummary{
NumberOfSpecsThatWillBeRun: 1,
NumberOfFailedSpecs: 0,
RunTime: 10 * time.Second,
})
})
It("should record test as failing", func() {
output := readOutputFile()
Ω(output.Tests).Should(Equal(1))
Ω(output.Failures).Should(Equal(0))
Ω(output.Time).Should(Equal(10.0))
Ω(output.TestCases[0].Name).Should(Equal("A B C"))
Ω(output.TestCases[0].Skipped).ShouldNot(BeNil())
})
})
}
})

15
vendor/github.com/onsi/ginkgo/reporters/reporter.go generated vendored Normal file
View file

@ -0,0 +1,15 @@
package reporters
import (
"github.com/onsi/ginkgo/config"
"github.com/onsi/ginkgo/types"
)
type Reporter interface {
SpecSuiteWillBegin(config config.GinkgoConfigType, summary *types.SuiteSummary)
BeforeSuiteDidRun(setupSummary *types.SetupSummary)
SpecWillRun(specSummary *types.SpecSummary)
SpecDidComplete(specSummary *types.SpecSummary)
AfterSuiteDidRun(setupSummary *types.SetupSummary)
SpecSuiteDidEnd(summary *types.SuiteSummary)
}

View file

@ -0,0 +1,13 @@
package reporters_test
import (
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
"testing"
)
func TestReporters(t *testing.T) {
RegisterFailHandler(Fail)
RunSpecs(t, "Reporters Suite")
}

View file

@ -0,0 +1,64 @@
package stenographer
import (
"fmt"
"strings"
)
func (s *consoleStenographer) colorize(colorCode string, format string, args ...interface{}) string {
var out string
if len(args) > 0 {
out = fmt.Sprintf(format, args...)
} else {
out = format
}
if s.color {
return fmt.Sprintf("%s%s%s", colorCode, out, defaultStyle)
} else {
return out
}
}
func (s *consoleStenographer) printBanner(text string, bannerCharacter string) {
fmt.Fprintln(s.w, text)
fmt.Fprintln(s.w, strings.Repeat(bannerCharacter, len(text)))
}
func (s *consoleStenographer) printNewLine() {
fmt.Fprintln(s.w, "")
}
func (s *consoleStenographer) printDelimiter() {
fmt.Fprintln(s.w, s.colorize(grayColor, "%s", strings.Repeat("-", 30)))
}
func (s *consoleStenographer) print(indentation int, format string, args ...interface{}) {
fmt.Fprint(s.w, s.indent(indentation, format, args...))
}
func (s *consoleStenographer) println(indentation int, format string, args ...interface{}) {
fmt.Fprintln(s.w, s.indent(indentation, format, args...))
}
func (s *consoleStenographer) indent(indentation int, format string, args ...interface{}) string {
var text string
if len(args) > 0 {
text = fmt.Sprintf(format, args...)
} else {
text = format
}
stringArray := strings.Split(text, "\n")
padding := ""
if indentation >= 0 {
padding = strings.Repeat(" ", indentation)
}
for i, s := range stringArray {
stringArray[i] = fmt.Sprintf("%s%s", padding, s)
}
return strings.Join(stringArray, "\n")
}

View file

@ -0,0 +1,142 @@
package stenographer
import (
"sync"
"github.com/onsi/ginkgo/types"
)
func NewFakeStenographerCall(method string, args ...interface{}) FakeStenographerCall {
return FakeStenographerCall{
Method: method,
Args: args,
}
}
type FakeStenographer struct {
calls []FakeStenographerCall
lock *sync.Mutex
}
type FakeStenographerCall struct {
Method string
Args []interface{}
}
func NewFakeStenographer() *FakeStenographer {
stenographer := &FakeStenographer{
lock: &sync.Mutex{},
}
stenographer.Reset()
return stenographer
}
func (stenographer *FakeStenographer) Calls() []FakeStenographerCall {
stenographer.lock.Lock()
defer stenographer.lock.Unlock()
return stenographer.calls
}
func (stenographer *FakeStenographer) Reset() {
stenographer.lock.Lock()
defer stenographer.lock.Unlock()
stenographer.calls = make([]FakeStenographerCall, 0)
}
func (stenographer *FakeStenographer) CallsTo(method string) []FakeStenographerCall {
stenographer.lock.Lock()
defer stenographer.lock.Unlock()
results := make([]FakeStenographerCall, 0)
for _, call := range stenographer.calls {
if call.Method == method {
results = append(results, call)
}
}
return results
}
func (stenographer *FakeStenographer) registerCall(method string, args ...interface{}) {
stenographer.lock.Lock()
defer stenographer.lock.Unlock()
stenographer.calls = append(stenographer.calls, NewFakeStenographerCall(method, args...))
}
func (stenographer *FakeStenographer) AnnounceSuite(description string, randomSeed int64, randomizingAll bool, succinct bool) {
stenographer.registerCall("AnnounceSuite", description, randomSeed, randomizingAll, succinct)
}
func (stenographer *FakeStenographer) AnnounceAggregatedParallelRun(nodes int, succinct bool) {
stenographer.registerCall("AnnounceAggregatedParallelRun", nodes, succinct)
}
func (stenographer *FakeStenographer) AnnounceParallelRun(node int, nodes int, succinct bool) {
stenographer.registerCall("AnnounceParallelRun", node, nodes, succinct)
}
func (stenographer *FakeStenographer) AnnounceNumberOfSpecs(specsToRun int, total int, succinct bool) {
stenographer.registerCall("AnnounceNumberOfSpecs", specsToRun, total, succinct)
}
func (stenographer *FakeStenographer) AnnounceTotalNumberOfSpecs(total int, succinct bool) {
stenographer.registerCall("AnnounceTotalNumberOfSpecs", total, succinct)
}
func (stenographer *FakeStenographer) AnnounceSpecRunCompletion(summary *types.SuiteSummary, succinct bool) {
stenographer.registerCall("AnnounceSpecRunCompletion", summary, succinct)
}
func (stenographer *FakeStenographer) AnnounceSpecWillRun(spec *types.SpecSummary) {
stenographer.registerCall("AnnounceSpecWillRun", spec)
}
func (stenographer *FakeStenographer) AnnounceBeforeSuiteFailure(summary *types.SetupSummary, succinct bool, fullTrace bool) {
stenographer.registerCall("AnnounceBeforeSuiteFailure", summary, succinct, fullTrace)
}
func (stenographer *FakeStenographer) AnnounceAfterSuiteFailure(summary *types.SetupSummary, succinct bool, fullTrace bool) {
stenographer.registerCall("AnnounceAfterSuiteFailure", summary, succinct, fullTrace)
}
func (stenographer *FakeStenographer) AnnounceCapturedOutput(output string) {
stenographer.registerCall("AnnounceCapturedOutput", output)
}
func (stenographer *FakeStenographer) AnnounceSuccesfulSpec(spec *types.SpecSummary) {
stenographer.registerCall("AnnounceSuccesfulSpec", spec)
}
func (stenographer *FakeStenographer) AnnounceSuccesfulSlowSpec(spec *types.SpecSummary, succinct bool) {
stenographer.registerCall("AnnounceSuccesfulSlowSpec", spec, succinct)
}
func (stenographer *FakeStenographer) AnnounceSuccesfulMeasurement(spec *types.SpecSummary, succinct bool) {
stenographer.registerCall("AnnounceSuccesfulMeasurement", spec, succinct)
}
func (stenographer *FakeStenographer) AnnouncePendingSpec(spec *types.SpecSummary, noisy bool) {
stenographer.registerCall("AnnouncePendingSpec", spec, noisy)
}
func (stenographer *FakeStenographer) AnnounceSkippedSpec(spec *types.SpecSummary, succinct bool, fullTrace bool) {
stenographer.registerCall("AnnounceSkippedSpec", spec, succinct, fullTrace)
}
func (stenographer *FakeStenographer) AnnounceSpecTimedOut(spec *types.SpecSummary, succinct bool, fullTrace bool) {
stenographer.registerCall("AnnounceSpecTimedOut", spec, succinct, fullTrace)
}
func (stenographer *FakeStenographer) AnnounceSpecPanicked(spec *types.SpecSummary, succinct bool, fullTrace bool) {
stenographer.registerCall("AnnounceSpecPanicked", spec, succinct, fullTrace)
}
func (stenographer *FakeStenographer) AnnounceSpecFailed(spec *types.SpecSummary, succinct bool, fullTrace bool) {
stenographer.registerCall("AnnounceSpecFailed", spec, succinct, fullTrace)
}
func (stenographer *FakeStenographer) SummarizeFailures(summaries []*types.SpecSummary) {
stenographer.registerCall("SummarizeFailures", summaries)
}

View file

@ -0,0 +1,573 @@
/*
The stenographer is used by Ginkgo's reporters to generate output.
Move along, nothing to see here.
*/
package stenographer
import (
"fmt"
"io"
"runtime"
"strings"
"github.com/onsi/ginkgo/reporters/stenographer/support/go-colorable"
"github.com/onsi/ginkgo/types"
)
const defaultStyle = "\x1b[0m"
const boldStyle = "\x1b[1m"
const redColor = "\x1b[91m"
const greenColor = "\x1b[32m"
const yellowColor = "\x1b[33m"
const cyanColor = "\x1b[36m"
const grayColor = "\x1b[90m"
const lightGrayColor = "\x1b[37m"
type cursorStateType int
const (
cursorStateTop cursorStateType = iota
cursorStateStreaming
cursorStateMidBlock
cursorStateEndBlock
)
type Stenographer interface {
AnnounceSuite(description string, randomSeed int64, randomizingAll bool, succinct bool)
AnnounceAggregatedParallelRun(nodes int, succinct bool)
AnnounceParallelRun(node int, nodes int, succinct bool)
AnnounceTotalNumberOfSpecs(total int, succinct bool)
AnnounceNumberOfSpecs(specsToRun int, total int, succinct bool)
AnnounceSpecRunCompletion(summary *types.SuiteSummary, succinct bool)
AnnounceSpecWillRun(spec *types.SpecSummary)
AnnounceBeforeSuiteFailure(summary *types.SetupSummary, succinct bool, fullTrace bool)
AnnounceAfterSuiteFailure(summary *types.SetupSummary, succinct bool, fullTrace bool)
AnnounceCapturedOutput(output string)
AnnounceSuccesfulSpec(spec *types.SpecSummary)
AnnounceSuccesfulSlowSpec(spec *types.SpecSummary, succinct bool)
AnnounceSuccesfulMeasurement(spec *types.SpecSummary, succinct bool)
AnnouncePendingSpec(spec *types.SpecSummary, noisy bool)
AnnounceSkippedSpec(spec *types.SpecSummary, succinct bool, fullTrace bool)
AnnounceSpecTimedOut(spec *types.SpecSummary, succinct bool, fullTrace bool)
AnnounceSpecPanicked(spec *types.SpecSummary, succinct bool, fullTrace bool)
AnnounceSpecFailed(spec *types.SpecSummary, succinct bool, fullTrace bool)
SummarizeFailures(summaries []*types.SpecSummary)
}
func New(color bool, enableFlakes bool) Stenographer {
denoter := "•"
if runtime.GOOS == "windows" {
denoter = "+"
}
return &consoleStenographer{
color: color,
denoter: denoter,
cursorState: cursorStateTop,
enableFlakes: enableFlakes,
w: colorable.NewColorableStdout(),
}
}
type consoleStenographer struct {
color bool
denoter string
cursorState cursorStateType
enableFlakes bool
w io.Writer
}
var alternatingColors = []string{defaultStyle, grayColor}
func (s *consoleStenographer) AnnounceSuite(description string, randomSeed int64, randomizingAll bool, succinct bool) {
if succinct {
s.print(0, "[%d] %s ", randomSeed, s.colorize(boldStyle, description))
return
}
s.printBanner(fmt.Sprintf("Running Suite: %s", description), "=")
s.print(0, "Random Seed: %s", s.colorize(boldStyle, "%d", randomSeed))
if randomizingAll {
s.print(0, " - Will randomize all specs")
}
s.printNewLine()
}
func (s *consoleStenographer) AnnounceParallelRun(node int, nodes int, succinct bool) {
if succinct {
s.print(0, "- node #%d ", node)
return
}
s.println(0,
"Parallel test node %s/%s.",
s.colorize(boldStyle, "%d", node),
s.colorize(boldStyle, "%d", nodes),
)
s.printNewLine()
}
func (s *consoleStenographer) AnnounceAggregatedParallelRun(nodes int, succinct bool) {
if succinct {
s.print(0, "- %d nodes ", nodes)
return
}
s.println(0,
"Running in parallel across %s nodes",
s.colorize(boldStyle, "%d", nodes),
)
s.printNewLine()
}
func (s *consoleStenographer) AnnounceNumberOfSpecs(specsToRun int, total int, succinct bool) {
if succinct {
s.print(0, "- %d/%d specs ", specsToRun, total)
s.stream()
return
}
s.println(0,
"Will run %s of %s specs",
s.colorize(boldStyle, "%d", specsToRun),
s.colorize(boldStyle, "%d", total),
)
s.printNewLine()
}
func (s *consoleStenographer) AnnounceTotalNumberOfSpecs(total int, succinct bool) {
if succinct {
s.print(0, "- %d specs ", total)
s.stream()
return
}
s.println(0,
"Will run %s specs",
s.colorize(boldStyle, "%d", total),
)
s.printNewLine()
}
func (s *consoleStenographer) AnnounceSpecRunCompletion(summary *types.SuiteSummary, succinct bool) {
if succinct && summary.SuiteSucceeded {
s.print(0, " %s %s ", s.colorize(greenColor, "SUCCESS!"), summary.RunTime)
return
}
s.printNewLine()
color := greenColor
if !summary.SuiteSucceeded {
color = redColor
}
s.println(0, s.colorize(boldStyle+color, "Ran %d of %d Specs in %.3f seconds", summary.NumberOfSpecsThatWillBeRun, summary.NumberOfTotalSpecs, summary.RunTime.Seconds()))
status := ""
if summary.SuiteSucceeded {
status = s.colorize(boldStyle+greenColor, "SUCCESS!")
} else {
status = s.colorize(boldStyle+redColor, "FAIL!")
}
flakes := ""
if s.enableFlakes {
flakes = " | " + s.colorize(yellowColor+boldStyle, "%d Flaked", summary.NumberOfFlakedSpecs)
}
s.print(0,
"%s -- %s | %s | %s | %s ",
status,
s.colorize(greenColor+boldStyle, "%d Passed", summary.NumberOfPassedSpecs),
s.colorize(redColor+boldStyle, "%d Failed", summary.NumberOfFailedSpecs)+flakes,
s.colorize(yellowColor+boldStyle, "%d Pending", summary.NumberOfPendingSpecs),
s.colorize(cyanColor+boldStyle, "%d Skipped", summary.NumberOfSkippedSpecs),
)
}
func (s *consoleStenographer) AnnounceSpecWillRun(spec *types.SpecSummary) {
s.startBlock()
for i, text := range spec.ComponentTexts[1 : len(spec.ComponentTexts)-1] {
s.print(0, s.colorize(alternatingColors[i%2], text)+" ")
}
indentation := 0
if len(spec.ComponentTexts) > 2 {
indentation = 1
s.printNewLine()
}
index := len(spec.ComponentTexts) - 1
s.print(indentation, s.colorize(boldStyle, spec.ComponentTexts[index]))
s.printNewLine()
s.print(indentation, s.colorize(lightGrayColor, spec.ComponentCodeLocations[index].String()))
s.printNewLine()
s.midBlock()
}
func (s *consoleStenographer) AnnounceBeforeSuiteFailure(summary *types.SetupSummary, succinct bool, fullTrace bool) {
s.announceSetupFailure("BeforeSuite", summary, succinct, fullTrace)
}
func (s *consoleStenographer) AnnounceAfterSuiteFailure(summary *types.SetupSummary, succinct bool, fullTrace bool) {
s.announceSetupFailure("AfterSuite", summary, succinct, fullTrace)
}
func (s *consoleStenographer) announceSetupFailure(name string, summary *types.SetupSummary, succinct bool, fullTrace bool) {
s.startBlock()
var message string
switch summary.State {
case types.SpecStateFailed:
message = "Failure"
case types.SpecStatePanicked:
message = "Panic"
case types.SpecStateTimedOut:
message = "Timeout"
}
s.println(0, s.colorize(redColor+boldStyle, "%s [%.3f seconds]", message, summary.RunTime.Seconds()))
indentation := s.printCodeLocationBlock([]string{name}, []types.CodeLocation{summary.CodeLocation}, summary.ComponentType, 0, summary.State, true)
s.printNewLine()
s.printFailure(indentation, summary.State, summary.Failure, fullTrace)
s.endBlock()
}
func (s *consoleStenographer) AnnounceCapturedOutput(output string) {
if output == "" {
return
}
s.startBlock()
s.println(0, output)
s.midBlock()
}
func (s *consoleStenographer) AnnounceSuccesfulSpec(spec *types.SpecSummary) {
s.print(0, s.colorize(greenColor, s.denoter))
s.stream()
}
func (s *consoleStenographer) AnnounceSuccesfulSlowSpec(spec *types.SpecSummary, succinct bool) {
s.printBlockWithMessage(
s.colorize(greenColor, "%s [SLOW TEST:%.3f seconds]", s.denoter, spec.RunTime.Seconds()),
"",
spec,
succinct,
)
}
func (s *consoleStenographer) AnnounceSuccesfulMeasurement(spec *types.SpecSummary, succinct bool) {
s.printBlockWithMessage(
s.colorize(greenColor, "%s [MEASUREMENT]", s.denoter),
s.measurementReport(spec, succinct),
spec,
succinct,
)
}
func (s *consoleStenographer) AnnouncePendingSpec(spec *types.SpecSummary, noisy bool) {
if noisy {
s.printBlockWithMessage(
s.colorize(yellowColor, "P [PENDING]"),
"",
spec,
false,
)
} else {
s.print(0, s.colorize(yellowColor, "P"))
s.stream()
}
}
func (s *consoleStenographer) AnnounceSkippedSpec(spec *types.SpecSummary, succinct bool, fullTrace bool) {
// Skips at runtime will have a non-empty spec.Failure. All others should be succinct.
if succinct || spec.Failure == (types.SpecFailure{}) {
s.print(0, s.colorize(cyanColor, "S"))
s.stream()
} else {
s.startBlock()
s.println(0, s.colorize(cyanColor+boldStyle, "S [SKIPPING]%s [%.3f seconds]", s.failureContext(spec.Failure.ComponentType), spec.RunTime.Seconds()))
indentation := s.printCodeLocationBlock(spec.ComponentTexts, spec.ComponentCodeLocations, spec.Failure.ComponentType, spec.Failure.ComponentIndex, spec.State, succinct)
s.printNewLine()
s.printSkip(indentation, spec.Failure)
s.endBlock()
}
}
func (s *consoleStenographer) AnnounceSpecTimedOut(spec *types.SpecSummary, succinct bool, fullTrace bool) {
s.printSpecFailure(fmt.Sprintf("%s... Timeout", s.denoter), spec, succinct, fullTrace)
}
func (s *consoleStenographer) AnnounceSpecPanicked(spec *types.SpecSummary, succinct bool, fullTrace bool) {
s.printSpecFailure(fmt.Sprintf("%s! Panic", s.denoter), spec, succinct, fullTrace)
}
func (s *consoleStenographer) AnnounceSpecFailed(spec *types.SpecSummary, succinct bool, fullTrace bool) {
s.printSpecFailure(fmt.Sprintf("%s Failure", s.denoter), spec, succinct, fullTrace)
}
func (s *consoleStenographer) SummarizeFailures(summaries []*types.SpecSummary) {
failingSpecs := []*types.SpecSummary{}
for _, summary := range summaries {
if summary.HasFailureState() {
failingSpecs = append(failingSpecs, summary)
}
}
if len(failingSpecs) == 0 {
return
}
s.printNewLine()
s.printNewLine()
plural := "s"
if len(failingSpecs) == 1 {
plural = ""
}
s.println(0, s.colorize(redColor+boldStyle, "Summarizing %d Failure%s:", len(failingSpecs), plural))
for _, summary := range failingSpecs {
s.printNewLine()
if summary.HasFailureState() {
if summary.TimedOut() {
s.print(0, s.colorize(redColor+boldStyle, "[Timeout...] "))
} else if summary.Panicked() {
s.print(0, s.colorize(redColor+boldStyle, "[Panic!] "))
} else if summary.Failed() {
s.print(0, s.colorize(redColor+boldStyle, "[Fail] "))
}
s.printSpecContext(summary.ComponentTexts, summary.ComponentCodeLocations, summary.Failure.ComponentType, summary.Failure.ComponentIndex, summary.State, true)
s.printNewLine()
s.println(0, s.colorize(lightGrayColor, summary.Failure.Location.String()))
}
}
}
func (s *consoleStenographer) startBlock() {
if s.cursorState == cursorStateStreaming {
s.printNewLine()
s.printDelimiter()
} else if s.cursorState == cursorStateMidBlock {
s.printNewLine()
}
}
func (s *consoleStenographer) midBlock() {
s.cursorState = cursorStateMidBlock
}
func (s *consoleStenographer) endBlock() {
s.printDelimiter()
s.cursorState = cursorStateEndBlock
}
func (s *consoleStenographer) stream() {
s.cursorState = cursorStateStreaming
}
func (s *consoleStenographer) printBlockWithMessage(header string, message string, spec *types.SpecSummary, succinct bool) {
s.startBlock()
s.println(0, header)
indentation := s.printCodeLocationBlock(spec.ComponentTexts, spec.ComponentCodeLocations, types.SpecComponentTypeInvalid, 0, spec.State, succinct)
if message != "" {
s.printNewLine()
s.println(indentation, message)
}
s.endBlock()
}
func (s *consoleStenographer) printSpecFailure(message string, spec *types.SpecSummary, succinct bool, fullTrace bool) {
s.startBlock()
s.println(0, s.colorize(redColor+boldStyle, "%s%s [%.3f seconds]", message, s.failureContext(spec.Failure.ComponentType), spec.RunTime.Seconds()))
indentation := s.printCodeLocationBlock(spec.ComponentTexts, spec.ComponentCodeLocations, spec.Failure.ComponentType, spec.Failure.ComponentIndex, spec.State, succinct)
s.printNewLine()
s.printFailure(indentation, spec.State, spec.Failure, fullTrace)
s.endBlock()
}
func (s *consoleStenographer) failureContext(failedComponentType types.SpecComponentType) string {
switch failedComponentType {
case types.SpecComponentTypeBeforeSuite:
return " in Suite Setup (BeforeSuite)"
case types.SpecComponentTypeAfterSuite:
return " in Suite Teardown (AfterSuite)"
case types.SpecComponentTypeBeforeEach:
return " in Spec Setup (BeforeEach)"
case types.SpecComponentTypeJustBeforeEach:
return " in Spec Setup (JustBeforeEach)"
case types.SpecComponentTypeAfterEach:
return " in Spec Teardown (AfterEach)"
}
return ""
}
func (s *consoleStenographer) printSkip(indentation int, spec types.SpecFailure) {
s.println(indentation, s.colorize(cyanColor, spec.Message))
s.printNewLine()
s.println(indentation, spec.Location.String())
}
func (s *consoleStenographer) printFailure(indentation int, state types.SpecState, failure types.SpecFailure, fullTrace bool) {
if state == types.SpecStatePanicked {
s.println(indentation, s.colorize(redColor+boldStyle, failure.Message))
s.println(indentation, s.colorize(redColor, failure.ForwardedPanic))
s.println(indentation, failure.Location.String())
s.printNewLine()
s.println(indentation, s.colorize(redColor, "Full Stack Trace"))
s.println(indentation, failure.Location.FullStackTrace)
} else {
s.println(indentation, s.colorize(redColor, failure.Message))
s.printNewLine()
s.println(indentation, failure.Location.String())
if fullTrace {
s.printNewLine()
s.println(indentation, s.colorize(redColor, "Full Stack Trace"))
s.println(indentation, failure.Location.FullStackTrace)
}
}
}
func (s *consoleStenographer) printSpecContext(componentTexts []string, componentCodeLocations []types.CodeLocation, failedComponentType types.SpecComponentType, failedComponentIndex int, state types.SpecState, succinct bool) int {
startIndex := 1
indentation := 0
if len(componentTexts) == 1 {
startIndex = 0
}
for i := startIndex; i < len(componentTexts); i++ {
if (state.IsFailure() || state == types.SpecStateSkipped) && i == failedComponentIndex {
color := redColor
if state == types.SpecStateSkipped {
color = cyanColor
}
blockType := ""
switch failedComponentType {
case types.SpecComponentTypeBeforeSuite:
blockType = "BeforeSuite"
case types.SpecComponentTypeAfterSuite:
blockType = "AfterSuite"
case types.SpecComponentTypeBeforeEach:
blockType = "BeforeEach"
case types.SpecComponentTypeJustBeforeEach:
blockType = "JustBeforeEach"
case types.SpecComponentTypeAfterEach:
blockType = "AfterEach"
case types.SpecComponentTypeIt:
blockType = "It"
case types.SpecComponentTypeMeasure:
blockType = "Measurement"
}
if succinct {
s.print(0, s.colorize(color+boldStyle, "[%s] %s ", blockType, componentTexts[i]))
} else {
s.println(indentation, s.colorize(color+boldStyle, "%s [%s]", componentTexts[i], blockType))
s.println(indentation, s.colorize(grayColor, "%s", componentCodeLocations[i]))
}
} else {
if succinct {
s.print(0, s.colorize(alternatingColors[i%2], "%s ", componentTexts[i]))
} else {
s.println(indentation, componentTexts[i])
s.println(indentation, s.colorize(grayColor, "%s", componentCodeLocations[i]))
}
}
indentation++
}
return indentation
}
func (s *consoleStenographer) printCodeLocationBlock(componentTexts []string, componentCodeLocations []types.CodeLocation, failedComponentType types.SpecComponentType, failedComponentIndex int, state types.SpecState, succinct bool) int {
indentation := s.printSpecContext(componentTexts, componentCodeLocations, failedComponentType, failedComponentIndex, state, succinct)
if succinct {
if len(componentTexts) > 0 {
s.printNewLine()
s.print(0, s.colorize(lightGrayColor, "%s", componentCodeLocations[len(componentCodeLocations)-1]))
}
s.printNewLine()
indentation = 1
} else {
indentation--
}
return indentation
}
func (s *consoleStenographer) orderedMeasurementKeys(measurements map[string]*types.SpecMeasurement) []string {
orderedKeys := make([]string, len(measurements))
for key, measurement := range measurements {
orderedKeys[measurement.Order] = key
}
return orderedKeys
}
func (s *consoleStenographer) measurementReport(spec *types.SpecSummary, succinct bool) string {
if len(spec.Measurements) == 0 {
return "Found no measurements"
}
message := []string{}
orderedKeys := s.orderedMeasurementKeys(spec.Measurements)
if succinct {
message = append(message, fmt.Sprintf("%s samples:", s.colorize(boldStyle, "%d", spec.NumberOfSamples)))
for _, key := range orderedKeys {
measurement := spec.Measurements[key]
message = append(message, fmt.Sprintf(" %s - %s: %s%s, %s: %s%s ± %s%s, %s: %s%s",
s.colorize(boldStyle, "%s", measurement.Name),
measurement.SmallestLabel,
s.colorize(greenColor, measurement.PrecisionFmt(), measurement.Smallest),
measurement.Units,
measurement.AverageLabel,
s.colorize(cyanColor, measurement.PrecisionFmt(), measurement.Average),
measurement.Units,
s.colorize(cyanColor, measurement.PrecisionFmt(), measurement.StdDeviation),
measurement.Units,
measurement.LargestLabel,
s.colorize(redColor, measurement.PrecisionFmt(), measurement.Largest),
measurement.Units,
))
}
} else {
message = append(message, fmt.Sprintf("Ran %s samples:", s.colorize(boldStyle, "%d", spec.NumberOfSamples)))
for _, key := range orderedKeys {
measurement := spec.Measurements[key]
info := ""
if measurement.Info != nil {
message = append(message, fmt.Sprintf("%v", measurement.Info))
}
message = append(message, fmt.Sprintf("%s:\n%s %s: %s%s\n %s: %s%s\n %s: %s%s ± %s%s",
s.colorize(boldStyle, "%s", measurement.Name),
info,
measurement.SmallestLabel,
s.colorize(greenColor, measurement.PrecisionFmt(), measurement.Smallest),
measurement.Units,
measurement.LargestLabel,
s.colorize(redColor, measurement.PrecisionFmt(), measurement.Largest),
measurement.Units,
measurement.AverageLabel,
s.colorize(cyanColor, measurement.PrecisionFmt(), measurement.Average),
measurement.Units,
s.colorize(cyanColor, measurement.PrecisionFmt(), measurement.StdDeviation),
measurement.Units,
))
}
}
return strings.Join(message, "\n")
}

View file

@ -0,0 +1,6 @@
## Colorize Windows
These packages are used for colorize on Windows and contributed by mattn.jp@gmail.com
* go-colorable: <https://github.com/mattn/go-colorable>
* go-isatty: <https://github.com/mattn/go-isatty>

View file

@ -0,0 +1,21 @@
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.

View file

@ -0,0 +1,43 @@
# go-colorable
Colorable writer for windows.
For example, most of logger packages doesn't show colors on windows. (I know we can do it with ansicon. But I don't want.)
This package is possible to handle escape sequence for ansi color on windows.
## Too Bad!
![](https://raw.githubusercontent.com/mattn/go-colorable/gh-pages/bad.png)
## So Good!
![](https://raw.githubusercontent.com/mattn/go-colorable/gh-pages/good.png)
## Usage
```go
logrus.SetFormatter(&logrus.TextFormatter{ForceColors: true})
logrus.SetOutput(colorable.NewColorableStdout())
logrus.Info("succeeded")
logrus.Warn("not correct")
logrus.Error("something error")
logrus.Fatal("panic")
```
You can compile above code on non-windows OSs.
## Installation
```
$ go get github.com/mattn/go-colorable
```
# License
MIT
# Author
Yasuhiro Matsumoto (a.k.a mattn)

View file

@ -0,0 +1,24 @@
// +build !windows
package colorable
import (
"io"
"os"
)
func NewColorable(file *os.File) io.Writer {
if file == nil {
panic("nil passed instead of *os.File to NewColorable()")
}
return file
}
func NewColorableStdout() io.Writer {
return os.Stdout
}
func NewColorableStderr() io.Writer {
return os.Stderr
}

View file

@ -0,0 +1,783 @@
package colorable
import (
"bytes"
"fmt"
"io"
"math"
"os"
"strconv"
"strings"
"syscall"
"unsafe"
"github.com/onsi/ginkgo/reporters/stenographer/support/go-isatty"
)
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
}
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")
)
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 isatty.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
}
}
func NewColorableStdout() io.Writer {
return NewColorable(os.Stdout)
}
func NewColorableStderr() io.Writer {
return NewColorable(os.Stderr)
}
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()
}
}

View file

@ -0,0 +1,57 @@
package colorable
import (
"bytes"
"fmt"
"io"
)
type NonColorable struct {
out io.Writer
lastbuf bytes.Buffer
}
func NewNonColorable(w io.Writer) io.Writer {
return &NonColorable{out: w}
}
func (w *NonColorable) Write(data []byte) (n int, err error) {
er := bytes.NewBuffer(data)
loop:
for {
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
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 == '@' {
break
}
buf.Write([]byte(string(c)))
}
}
return len(data) - w.lastbuf.Len(), nil
}

View file

@ -0,0 +1,9 @@
Copyright (c) Yasuhiro MATSUMOTO <mattn.jp@gmail.com>
MIT License (Expat)
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.

View file

@ -0,0 +1,37 @@
# go-isatty
isatty for golang
## Usage
```go
package main
import (
"fmt"
"github.com/mattn/go-isatty"
"os"
)
func main() {
if isatty.IsTerminal(os.Stdout.Fd()) {
fmt.Println("Is Terminal")
} else {
fmt.Println("Is Not Terminal")
}
}
```
## Installation
```
$ go get github.com/mattn/go-isatty
```
# License
MIT
# Author
Yasuhiro Matsumoto (a.k.a mattn)

View file

@ -0,0 +1,2 @@
// Package isatty implements interface to isatty
package isatty

View file

@ -0,0 +1,9 @@
// +build appengine
package isatty
// IsTerminal returns true if the file descriptor is terminal which
// is always false on on appengine classic which is a sandboxed PaaS.
func IsTerminal(fd uintptr) bool {
return false
}

View file

@ -0,0 +1,18 @@
// +build darwin freebsd openbsd netbsd
// +build !appengine
package isatty
import (
"syscall"
"unsafe"
)
const ioctlReadTermios = syscall.TIOCGETA
// IsTerminal return true if the file descriptor is terminal.
func IsTerminal(fd uintptr) bool {
var termios syscall.Termios
_, _, err := syscall.Syscall6(syscall.SYS_IOCTL, fd, ioctlReadTermios, uintptr(unsafe.Pointer(&termios)), 0, 0, 0)
return err == 0
}

View file

@ -0,0 +1,18 @@
// +build linux
// +build !appengine
package isatty
import (
"syscall"
"unsafe"
)
const ioctlReadTermios = syscall.TCGETS
// IsTerminal return true if the file descriptor is terminal.
func IsTerminal(fd uintptr) bool {
var termios syscall.Termios
_, _, err := syscall.Syscall6(syscall.SYS_IOCTL, fd, ioctlReadTermios, uintptr(unsafe.Pointer(&termios)), 0, 0, 0)
return err == 0
}

View file

@ -0,0 +1,16 @@
// +build solaris
// +build !appengine
package isatty
import (
"golang.org/x/sys/unix"
)
// IsTerminal returns true if the given file descriptor is a terminal.
// see: http://src.illumos.org/source/xref/illumos-gate/usr/src/lib/libbc/libc/gen/common/isatty.c
func IsTerminal(fd uintptr) bool {
var termio unix.Termio
err := unix.IoctlSetTermio(int(fd), unix.TCGETA, &termio)
return err == nil
}

View file

@ -0,0 +1,19 @@
// +build windows
// +build !appengine
package isatty
import (
"syscall"
"unsafe"
)
var kernel32 = syscall.NewLazyDLL("kernel32.dll")
var procGetConsoleMode = kernel32.NewProc("GetConsoleMode")
// IsTerminal return true if the file descriptor is terminal.
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
}

Some files were not shown because too many files have changed in this diff Show more