vendor update for E2E framework

Signed-off-by: Madhu Rajanna <madhupr007@gmail.com>
This commit is contained in:
Madhu Rajanna
2019-05-31 15:15:11 +05:30
parent 9bb23e4e32
commit d300da19b7
2149 changed files with 598692 additions and 14107 deletions

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.

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

@ -0,0 +1,200 @@
/*
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.8.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
DebugParallel bool
ParallelNode int
ParallelTotal int
SyncHost string
StreamHost string
}
var GinkgoConfig = GinkgoConfigType{}
type DefaultReporterConfigType struct {
NoColor bool
SlowSpecThreshold float64
NoisyPendings bool
NoisySkippings 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 and When 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.")
flagSet.BoolVar(&(GinkgoConfig.DebugParallel), prefix+"debug", false, "If set, ginkgo will emit node output to files when running in parallel.")
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.NoisySkippings), prefix+"noisySkippings", true, "If set, default reporter will shout about skipping 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.DebugParallel {
result = append(result, fmt.Sprintf("--%sdebug", 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.NoisySkippings {
result = append(result, fmt.Sprintf("--%snoisySkippings=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
}

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

@ -0,0 +1,619 @@
/*
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"
colorable "github.com/onsi/ginkgo/reporters/stenographer/support/go-colorable"
"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
Duration time.Duration
}
//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(),
Duration: summary.RunTime,
}
}
//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(specReporters, buildDefaultReporter())
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 && strings.TrimSpace(os.Getenv("GINKGO_EDITOR_INTEGRATION")) == "" {
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, colorable.NewColorableStdout())
return reporters.NewDefaultReporter(config.DefaultReporterConfig, stenographer)
} else {
debugFile := ""
if config.GinkgoConfig.DebugParallel {
debugFile = fmt.Sprintf("ginkgo-node-%d.log", config.GinkgoConfig.ParallelNode)
}
return remote.NewForwardingReporter(config.DefaultReporterConfig, remoteReportingServer, &http.Client{}, remote.NewOutputInterceptor(), GinkgoWriter.(*writer.Writer), debugFile)
}
}
//Skip notifies Ginkgo that the current spec was 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, Context and When blocks. Describe, Context and When 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 and Whens.
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, Context and When blocks. Describe, Context and When 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 and Whens.
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
}
//When blocks allow you to organize your specs. A When block can contain any number of
//BeforeEach, AfterEach, JustBeforeEach, It, and Measurement blocks.
//
//In addition you can nest Describe, Context and When blocks. Describe, Context and When 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 and Whens.
func When(text string, body func()) bool {
globalSuite.PushContainerNode("when "+text, body, types.FlagTypeNone, codelocation.New(1))
return true
}
//You can focus the tests within a describe block using FWhen
func FWhen(text string, body func()) bool {
globalSuite.PushContainerNode("when "+text, body, types.FlagTypeFocused, codelocation.New(1))
return true
}
//You can mark the tests within a describe block as pending using PWhen
func PWhen(text string, body func()) bool {
globalSuite.PushContainerNode("when "+text, body, types.FlagTypePending, codelocation.New(1))
return true
}
//You can mark the tests within a describe block as pending using XWhen
func XWhen(text string, body func()) bool {
globalSuite.PushContainerNode("when "+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 {
globalSuite.PushItNode(text, body, types.FlagTypeNone, codelocation.New(1), parseTimeout(timeout...))
return true
}
//You can focus individual Specifys using FSpecify
func FSpecify(text string, body interface{}, timeout ...float64) bool {
globalSuite.PushItNode(text, body, types.FlagTypeFocused, codelocation.New(1), parseTimeout(timeout...))
return true
}
//You can mark Specifys as pending using PSpecify
func PSpecify(text string, is ...interface{}) bool {
globalSuite.PushItNode(text, func() {}, types.FlagTypePending, codelocation.New(1), 0)
return true
}
//You can mark Specifys as pending using XSpecify
func XSpecify(text string, is ...interface{}) bool {
globalSuite.PushItNode(text, func() {}, types.FlagTypePending, codelocation.New(1), 0)
return true
}
//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 Measurements 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 Measurements 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
}
//JustAfterEach blocks are run after It blocks but *before* all AfterEach blocks. For more details,
//read the [documentation](http://onsi.github.io/ginkgo/#separating_creation_and_configuration_)
//
//Like It blocks, JustAfterEach blocks can be made asynchronous by providing a body function that accepts
//a Done channel
func JustAfterEach(body interface{}, timeout ...float64) bool {
globalSuite.PushJustAfterEachNode(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,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,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,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{}) {
b.mu.Lock()
measurement := b.getMeasurement(name, "Smallest", " Largest", " Average", "", 3, info...)
defer b.mu.Unlock()
measurement.Results = append(measurement.Results, value)
}
func (b *benchmarker) RecordValueWithPrecision(name string, value float64, units string, precision int, info ...interface{}) {
b.mu.Lock()
measurement := b.getMeasurement(name, "Smallest", " Largest", " Average", units, precision, info...)
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,47 @@
package leafnodes
import (
"time"
"github.com/onsi/ginkgo/internal/failer"
"github.com/onsi/ginkgo/types"
)
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,62 @@
package leafnodes
import (
"reflect"
"github.com/onsi/ginkgo/internal/failer"
"github.com/onsi/ginkgo/types"
)
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,117 @@
package leafnodes
import (
"fmt"
"reflect"
"time"
"github.com/onsi/ginkgo/internal/codelocation"
"github.com/onsi/ginkgo/internal/failer"
"github.com/onsi/ginkgo/types"
)
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
}()
// If this goroutine gets no CPU time before the select block,
// the <-done case may complete even if the test took longer than the timeoutThreshold.
// This can cause flaky behaviour, but we haven't seen it in the wild.
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,48 @@
package leafnodes
import (
"time"
"github.com/onsi/ginkgo/internal/failer"
"github.com/onsi/ginkgo/types"
)
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),
}
}
func NewJustAfterEachNode(body interface{}, codeLocation types.CodeLocation, timeout time.Duration, failer *failer.Failer, componentIndex int) *SetupNode {
return &SetupNode{
runner: newRunner(body, codeLocation, timeout, failer, types.SpecComponentTypeJustAfterEach, componentIndex),
}
}

View File

@ -0,0 +1,55 @@
package leafnodes
import (
"time"
"github.com/onsi/ginkgo/internal/failer"
"github.com/onsi/ginkgo/types"
)
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,90 @@
package leafnodes
import (
"encoding/json"
"io/ioutil"
"net/http"
"time"
"github.com/onsi/ginkgo/internal/failer"
"github.com/onsi/ginkgo/types"
)
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,181 @@
package leafnodes
import (
"bytes"
"encoding/json"
"io/ioutil"
"net/http"
"reflect"
"time"
"github.com/onsi/ginkgo/internal/failer"
"github.com/onsi/ginkgo/types"
)
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)
}
}
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,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.NoisySkippings, 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,147 @@
package remote
import (
"bytes"
"encoding/json"
"fmt"
"io"
"net/http"
"os"
"github.com/onsi/ginkgo/internal/writer"
"github.com/onsi/ginkgo/reporters"
"github.com/onsi/ginkgo/reporters/stenographer"
"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
debugMode bool
debugFile *os.File
nestedReporter *reporters.DefaultReporter
}
func NewForwardingReporter(config config.DefaultReporterConfigType, serverHost string, poster Poster, outputInterceptor OutputInterceptor, ginkgoWriter *writer.Writer, debugFile string) *ForwardingReporter {
reporter := &ForwardingReporter{
serverHost: serverHost,
poster: poster,
outputInterceptor: outputInterceptor,
}
if debugFile != "" {
var err error
reporter.debugMode = true
reporter.debugFile, err = os.Create(debugFile)
if err != nil {
fmt.Println(err.Error())
os.Exit(1)
}
if !config.Verbose {
//if verbose is true then the GinkgoWriter emits to stdout. Don't _also_ redirect GinkgoWriter output as that will result in duplication.
ginkgoWriter.AndRedirectTo(reporter.debugFile)
}
outputInterceptor.StreamTo(reporter.debugFile) //This is not working
stenographer := stenographer.New(false, true, reporter.debugFile)
config.Succinct = false
config.Verbose = true
config.FullTrace = true
reporter.nestedReporter = reporters.NewDefaultReporter(config, stenographer)
}
return reporter
}
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()
if reporter.debugMode {
reporter.nestedReporter.SpecSuiteWillBegin(conf, summary)
reporter.debugFile.Sync()
}
reporter.post("/SpecSuiteWillBegin", data)
}
func (reporter *ForwardingReporter) BeforeSuiteDidRun(setupSummary *types.SetupSummary) {
output, _ := reporter.outputInterceptor.StopInterceptingAndReturnOutput()
reporter.outputInterceptor.StartInterceptingOutput()
setupSummary.CapturedOutput = output
if reporter.debugMode {
reporter.nestedReporter.BeforeSuiteDidRun(setupSummary)
reporter.debugFile.Sync()
}
reporter.post("/BeforeSuiteDidRun", setupSummary)
}
func (reporter *ForwardingReporter) SpecWillRun(specSummary *types.SpecSummary) {
if reporter.debugMode {
reporter.nestedReporter.SpecWillRun(specSummary)
reporter.debugFile.Sync()
}
reporter.post("/SpecWillRun", specSummary)
}
func (reporter *ForwardingReporter) SpecDidComplete(specSummary *types.SpecSummary) {
output, _ := reporter.outputInterceptor.StopInterceptingAndReturnOutput()
reporter.outputInterceptor.StartInterceptingOutput()
specSummary.CapturedOutput = output
if reporter.debugMode {
reporter.nestedReporter.SpecDidComplete(specSummary)
reporter.debugFile.Sync()
}
reporter.post("/SpecDidComplete", specSummary)
}
func (reporter *ForwardingReporter) AfterSuiteDidRun(setupSummary *types.SetupSummary) {
output, _ := reporter.outputInterceptor.StopInterceptingAndReturnOutput()
reporter.outputInterceptor.StartInterceptingOutput()
setupSummary.CapturedOutput = output
if reporter.debugMode {
reporter.nestedReporter.AfterSuiteDidRun(setupSummary)
reporter.debugFile.Sync()
}
reporter.post("/AfterSuiteDidRun", setupSummary)
}
func (reporter *ForwardingReporter) SpecSuiteDidEnd(summary *types.SuiteSummary) {
reporter.outputInterceptor.StopInterceptingAndReturnOutput()
if reporter.debugMode {
reporter.nestedReporter.SpecSuiteDidEnd(summary)
reporter.debugFile.Sync()
}
reporter.post("/SpecSuiteDidEnd", summary)
}

View File

@ -0,0 +1,13 @@
package remote
import "os"
/*
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)
StreamTo(*os.File)
}

View File

@ -0,0 +1,83 @@
// +build freebsd openbsd netbsd dragonfly darwin linux solaris
package remote
import (
"errors"
"io/ioutil"
"os"
"github.com/hpcloud/tail"
)
func NewOutputInterceptor() OutputInterceptor {
return &outputInterceptor{}
}
type outputInterceptor struct {
redirectFile *os.File
streamTarget *os.File
intercepting bool
tailer *tail.Tail
doneTailing chan 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)
if interceptor.streamTarget != nil {
interceptor.tailer, _ = tail.TailFile(interceptor.redirectFile.Name(), tail.Config{Follow: true})
interceptor.doneTailing = make(chan bool)
go func() {
for line := range interceptor.tailer.Lines {
interceptor.streamTarget.Write([]byte(line.Text + "\n"))
}
close(interceptor.doneTailing)
}()
}
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
if interceptor.streamTarget != nil {
interceptor.tailer.Stop()
interceptor.tailer.Cleanup()
<-interceptor.doneTailing
interceptor.streamTarget.Sync()
}
return string(output), err
}
func (interceptor *outputInterceptor) StreamTo(out *os.File) {
interceptor.streamTarget = out
}

View File

@ -0,0 +1,36 @@
// +build windows
package remote
import (
"errors"
"os"
)
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
}
func (interceptor *outputInterceptor) StreamTo(*os.File) {}

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{Data: nil, State: 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,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)
}

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

@ -0,0 +1,247 @@
package spec
import (
"fmt"
"io"
"time"
"sync"
"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
startTime time.Time
failure types.SpecFailure
previousFailures bool
stateMutex *sync.Mutex
}
func New(subject leafnodes.SubjectNode, containers []*containernode.ContainerNode, announceProgress bool) *Spec {
spec := &Spec{
subject: subject,
containers: containers,
focused: subject.Flag() == types.FlagTypeFocused,
announceProgress: announceProgress,
stateMutex: &sync.Mutex{},
}
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.setState(types.SpecStatePending)
}
}
func (spec *Spec) Skip() {
spec.setState(types.SpecStateSkipped)
}
func (spec *Spec) Failed() bool {
return spec.getState() == types.SpecStateFailed || spec.getState() == types.SpecStatePanicked || spec.getState() == types.SpecStateTimedOut
}
func (spec *Spec) Passed() bool {
return spec.getState() == types.SpecStatePassed
}
func (spec *Spec) Flaked() bool {
return spec.getState() == types.SpecStatePassed && spec.previousFailures
}
func (spec *Spec) Pending() bool {
return spec.getState() == types.SpecStatePending
}
func (spec *Spec) Skipped() bool {
return spec.getState() == 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()
runTime := spec.runTime
if runTime == 0 && !spec.startTime.IsZero() {
runTime = time.Since(spec.startTime)
}
return &types.SpecSummary{
IsMeasurement: spec.IsMeasurement(),
NumberOfSamples: spec.subject.Samples(),
ComponentTexts: componentTexts,
ComponentCodeLocations: componentCodeLocations,
State: spec.getState(),
RunTime: 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.getState() == types.SpecStateFailed {
spec.previousFailures = true
}
spec.startTime = time.Now()
defer func() {
spec.runTime = time.Since(spec.startTime)
}()
for sample := 0; sample < spec.subject.Samples(); sample++ {
spec.runSample(sample, writer)
if spec.getState() != types.SpecStatePassed {
return
}
}
}
func (spec *Spec) getState() types.SpecState {
spec.stateMutex.Lock()
defer spec.stateMutex.Unlock()
return spec.state
}
func (spec *Spec) setState(state types.SpecState) {
spec.stateMutex.Lock()
defer spec.stateMutex.Unlock()
spec.state = state
}
func (spec *Spec) runSample(sample int, writer io.Writer) {
spec.setState(types.SpecStatePassed)
spec.failure = types.SpecFailure{}
innerMostContainerIndexToUnwind := -1
defer func() {
for i := innerMostContainerIndexToUnwind; i >= 0; i-- {
container := spec.containers[i]
for _, justAfterEach := range container.SetupNodesOfType(types.SpecComponentTypeJustAfterEach) {
spec.announceSetupNode(writer, "JustAfterEach", container, justAfterEach)
justAfterEachState, justAfterEachFailure := justAfterEach.Run()
if justAfterEachState != types.SpecStatePassed && spec.state == types.SpecStatePassed {
spec.state = justAfterEachState
spec.failure = justAfterEachFailure
}
}
}
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.getState() == types.SpecStatePassed {
spec.setState(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)
s, f := beforeEach.Run()
spec.failure = f
spec.setState(s)
if spec.getState() != types.SpecStatePassed {
return
}
}
}
for _, container := range spec.containers {
for _, justBeforeEach := range container.SetupNodesOfType(types.SpecComponentTypeJustBeforeEach) {
spec.announceSetupNode(writer, "JustBeforeEach", container, justBeforeEach)
s, f := justBeforeEach.Run()
spec.failure = f
spec.setState(s)
if spec.getState() != types.SpecStatePassed {
return
}
}
}
spec.announceSubject(writer, spec.subject)
s, f := spec.subject.Run()
spec.failure = f
spec.setState(s)
}
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()
}

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

@ -0,0 +1,144 @@
package spec
import (
"math/rand"
"regexp"
"sort"
)
type Specs struct {
specs []*Spec
names []string
hasProgrammaticFocus bool
RegexScansFilePath bool
}
func NewSpecs(specs []*Spec) *Specs {
names := make([]string, len(specs))
for i, spec := range specs {
names[i] = spec.ConcatenatedString()
}
return &Specs{
specs: specs,
names: names,
}
}
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))
names := make([]string, len(e.specs))
for i, j := range permutation {
shuffledSpecs[i] = e.specs[j]
names[i] = e.names[j]
}
e.specs = shuffledSpecs
e.names = names
}
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, i int) []byte {
if i > len(e.names) {
return nil
}
if e.RegexScansFilePath {
return []byte(
description + " " +
e.names[i] + " " +
e.specs[i].subject.CodeLocation().FileName)
} else {
return []byte(
description + " " +
e.names[i])
}
}
func (e *Specs) applyRegExpFocusAndSkip(description string, focusString string, skipString string) {
var focusFilter *regexp.Regexp
if focusString != "" {
focusFilter = regexp.MustCompile(focusString)
}
var skipFilter *regexp.Regexp
if skipString != "" {
skipFilter = regexp.MustCompile(skipString)
}
for i, spec := range e.specs {
matchesFocus := true
matchesSkip := false
toMatch := e.toMatch(description, i)
if focusFilter != nil {
matchesFocus = focusFilter.Match([]byte(toMatch))
}
if skipFilter != nil {
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.names[i] < e.names[j]
}
func (e *Specs) Swap(i, j int) {
e.names[i], e.names[j] = e.names[j], e.names[i]
e.specs[i], e.specs[j] = e.specs[j], e.specs[i]
}

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,59 @@
package spec_iterator
import (
"encoding/json"
"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, fmt.Errorf("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,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,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,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,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,411 @@
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()
signalRegistered := make(chan struct{})
go runner.registerForInterrupts(signalRegistered)
<-signalRegistered
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(signalRegistered chan struct{}) {
c := make(chan os.Signal, 1)
signal.Notify(c, os.Interrupt, syscall.SIGTERM)
close(signalRegistered)
<-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,
}
}

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

@ -0,0 +1,190 @@
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, Context or When", 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, Context or When", 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, Context or When", 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, Context or When", codeLocation)
}
suite.currentContainer.PushSetupNode(leafnodes.NewJustBeforeEachNode(body, codeLocation, timeout, suite.failer, suite.containerIndex))
}
func (suite *Suite) PushJustAfterEachNode(body interface{}, codeLocation types.CodeLocation, timeout time.Duration) {
if suite.running {
suite.failer.Fail("You may only call JustAfterEach from within a Describe or Context", codeLocation)
}
suite.currentContainer.PushSetupNode(leafnodes.NewJustAfterEachNode(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, Context or When", codeLocation)
}
suite.currentContainer.PushSetupNode(leafnodes.NewAfterEachNode(body, codeLocation, timeout, suite.failer, suite.containerIndex))
}

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,89 @@
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
redirector io.Writer
}
func New(outWriter io.Writer) *Writer {
return &Writer{
buffer: &bytes.Buffer{},
lock: &sync.Mutex{},
outWriter: outWriter,
stream: true,
}
}
func (w *Writer) AndRedirectTo(writer io.Writer) {
w.redirector = writer
}
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.redirector != nil {
w.redirector.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,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.NoisySkippings, 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,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,152 @@
/*
JUnit XML Reporter for Ginkgo
For usage instructions: http://onsi.github.io/ginkgo/#generating_junit_xml_output
*/
package reporters
import (
"encoding/xml"
"fmt"
"math"
"os"
"strings"
"github.com/onsi/ginkgo/config"
"github.com/onsi/ginkgo/types"
)
type JUnitTestSuite struct {
XMLName xml.Name `xml:"testsuite"`
TestCases []JUnitTestCase `xml:"testcase"`
Name string `xml:"name,attr"`
Tests int `xml:"tests,attr"`
Failures int `xml:"failures,attr"`
Errors int `xml:"errors,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{
Name: summary.SuiteDescription,
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 = math.Trunc(summary.RunTime.Seconds()*1000) / 1000
reporter.suite.Failures = summary.NumberOfFailedSpecs
reporter.suite.Errors = 0
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 ""
}
}

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,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,572 @@
/*
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/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, writer io.Writer) Stenographer {
denoter := "•"
if runtime.GOOS == "windows" {
denoter = "+"
}
return &consoleStenographer{
color: color,
denoter: denoter,
cursorState: cursorStateTop,
enableFlakes: enableFlakes,
w: writer,
}
}
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\n",
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,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,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,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
}

View File

@ -0,0 +1,93 @@
/*
TeamCity Reporter for Ginkgo
Makes use of TeamCity's support for Service Messages
http://confluence.jetbrains.com/display/TCD7/Build+Script+Interaction+with+TeamCity#BuildScriptInteractionwithTeamCity-ReportingTests
*/
package reporters
import (
"fmt"
"io"
"strings"
"github.com/onsi/ginkgo/config"
"github.com/onsi/ginkgo/types"
)
const (
messageId = "##teamcity"
)
type TeamCityReporter struct {
writer io.Writer
testSuiteName string
}
func NewTeamCityReporter(writer io.Writer) *TeamCityReporter {
return &TeamCityReporter{
writer: writer,
}
}
func (reporter *TeamCityReporter) SpecSuiteWillBegin(config config.GinkgoConfigType, summary *types.SuiteSummary) {
reporter.testSuiteName = escape(summary.SuiteDescription)
fmt.Fprintf(reporter.writer, "%s[testSuiteStarted name='%s']", messageId, reporter.testSuiteName)
}
func (reporter *TeamCityReporter) BeforeSuiteDidRun(setupSummary *types.SetupSummary) {
reporter.handleSetupSummary("BeforeSuite", setupSummary)
}
func (reporter *TeamCityReporter) AfterSuiteDidRun(setupSummary *types.SetupSummary) {
reporter.handleSetupSummary("AfterSuite", setupSummary)
}
func (reporter *TeamCityReporter) handleSetupSummary(name string, setupSummary *types.SetupSummary) {
if setupSummary.State != types.SpecStatePassed {
testName := escape(name)
fmt.Fprintf(reporter.writer, "%s[testStarted name='%s']", messageId, testName)
message := escape(setupSummary.Failure.ComponentCodeLocation.String())
details := escape(setupSummary.Failure.Message)
fmt.Fprintf(reporter.writer, "%s[testFailed name='%s' message='%s' details='%s']", messageId, testName, message, details)
durationInMilliseconds := setupSummary.RunTime.Seconds() * 1000
fmt.Fprintf(reporter.writer, "%s[testFinished name='%s' duration='%v']", messageId, testName, durationInMilliseconds)
}
}
func (reporter *TeamCityReporter) SpecWillRun(specSummary *types.SpecSummary) {
testName := escape(strings.Join(specSummary.ComponentTexts[1:], " "))
fmt.Fprintf(reporter.writer, "%s[testStarted name='%s']", messageId, testName)
}
func (reporter *TeamCityReporter) SpecDidComplete(specSummary *types.SpecSummary) {
testName := escape(strings.Join(specSummary.ComponentTexts[1:], " "))
if specSummary.State == types.SpecStateFailed || specSummary.State == types.SpecStateTimedOut || specSummary.State == types.SpecStatePanicked {
message := escape(specSummary.Failure.ComponentCodeLocation.String())
details := escape(specSummary.Failure.Message)
fmt.Fprintf(reporter.writer, "%s[testFailed name='%s' message='%s' details='%s']", messageId, testName, message, details)
}
if specSummary.State == types.SpecStateSkipped || specSummary.State == types.SpecStatePending {
fmt.Fprintf(reporter.writer, "%s[testIgnored name='%s']", messageId, testName)
}
durationInMilliseconds := specSummary.RunTime.Seconds() * 1000
fmt.Fprintf(reporter.writer, "%s[testFinished name='%s' duration='%v']", messageId, testName, durationInMilliseconds)
}
func (reporter *TeamCityReporter) SpecSuiteDidEnd(summary *types.SuiteSummary) {
fmt.Fprintf(reporter.writer, "%s[testSuiteFinished name='%s']", messageId, reporter.testSuiteName)
}
func escape(output string) string {
output = strings.Replace(output, "|", "||", -1)
output = strings.Replace(output, "'", "|'", -1)
output = strings.Replace(output, "\n", "|n", -1)
output = strings.Replace(output, "\r", "|r", -1)
output = strings.Replace(output, "[", "|[", -1)
output = strings.Replace(output, "]", "|]", -1)
return output
}

15
vendor/github.com/onsi/ginkgo/types/code_location.go generated vendored Normal file
View File

@ -0,0 +1,15 @@
package types
import (
"fmt"
)
type CodeLocation struct {
FileName string
LineNumber int
FullStackTrace string
}
func (codeLocation CodeLocation) String() string {
return fmt.Sprintf("%s:%d", codeLocation.FileName, codeLocation.LineNumber)
}

30
vendor/github.com/onsi/ginkgo/types/synchronization.go generated vendored Normal file
View File

@ -0,0 +1,30 @@
package types
import (
"encoding/json"
)
type RemoteBeforeSuiteState int
const (
RemoteBeforeSuiteStateInvalid RemoteBeforeSuiteState = iota
RemoteBeforeSuiteStatePending
RemoteBeforeSuiteStatePassed
RemoteBeforeSuiteStateFailed
RemoteBeforeSuiteStateDisappeared
)
type RemoteBeforeSuiteData struct {
Data []byte
State RemoteBeforeSuiteState
}
func (r RemoteBeforeSuiteData) ToJSON() []byte {
data, _ := json.Marshal(r)
return data
}
type RemoteAfterSuiteData struct {
CanRun bool
}

174
vendor/github.com/onsi/ginkgo/types/types.go generated vendored Normal file
View File

@ -0,0 +1,174 @@
package types
import (
"strconv"
"time"
)
const GINKGO_FOCUS_EXIT_CODE = 197
/*
SuiteSummary represents the a summary of the test suite and is passed to both
Reporter.SpecSuiteWillBegin
Reporter.SpecSuiteDidEnd
this is unfortunate as these two methods should receive different objects. When running in parallel
each node does not deterministically know how many specs it will end up running.
Unfortunately making such a change would break backward compatibility.
Until Ginkgo 2.0 comes out we will continue to reuse this struct but populate unkown fields
with -1.
*/
type SuiteSummary struct {
SuiteDescription string
SuiteSucceeded bool
SuiteID string
NumberOfSpecsBeforeParallelization int
NumberOfTotalSpecs int
NumberOfSpecsThatWillBeRun int
NumberOfPendingSpecs int
NumberOfSkippedSpecs int
NumberOfPassedSpecs int
NumberOfFailedSpecs int
// Flaked specs are those that failed initially, but then passed on a
// subsequent try.
NumberOfFlakedSpecs int
RunTime time.Duration
}
type SpecSummary struct {
ComponentTexts []string
ComponentCodeLocations []CodeLocation
State SpecState
RunTime time.Duration
Failure SpecFailure
IsMeasurement bool
NumberOfSamples int
Measurements map[string]*SpecMeasurement
CapturedOutput string
SuiteID string
}
func (s SpecSummary) HasFailureState() bool {
return s.State.IsFailure()
}
func (s SpecSummary) TimedOut() bool {
return s.State == SpecStateTimedOut
}
func (s SpecSummary) Panicked() bool {
return s.State == SpecStatePanicked
}
func (s SpecSummary) Failed() bool {
return s.State == SpecStateFailed
}
func (s SpecSummary) Passed() bool {
return s.State == SpecStatePassed
}
func (s SpecSummary) Skipped() bool {
return s.State == SpecStateSkipped
}
func (s SpecSummary) Pending() bool {
return s.State == SpecStatePending
}
type SetupSummary struct {
ComponentType SpecComponentType
CodeLocation CodeLocation
State SpecState
RunTime time.Duration
Failure SpecFailure
CapturedOutput string
SuiteID string
}
type SpecFailure struct {
Message string
Location CodeLocation
ForwardedPanic string
ComponentIndex int
ComponentType SpecComponentType
ComponentCodeLocation CodeLocation
}
type SpecMeasurement struct {
Name string
Info interface{}
Order int
Results []float64
Smallest float64
Largest float64
Average float64
StdDeviation float64
SmallestLabel string
LargestLabel string
AverageLabel string
Units string
Precision int
}
func (s SpecMeasurement) PrecisionFmt() string {
if s.Precision == 0 {
return "%f"
}
str := strconv.Itoa(s.Precision)
return "%." + str + "f"
}
type SpecState uint
const (
SpecStateInvalid SpecState = iota
SpecStatePending
SpecStateSkipped
SpecStatePassed
SpecStateFailed
SpecStatePanicked
SpecStateTimedOut
)
func (state SpecState) IsFailure() bool {
return state == SpecStateTimedOut || state == SpecStatePanicked || state == SpecStateFailed
}
type SpecComponentType uint
const (
SpecComponentTypeInvalid SpecComponentType = iota
SpecComponentTypeContainer
SpecComponentTypeBeforeSuite
SpecComponentTypeAfterSuite
SpecComponentTypeBeforeEach
SpecComponentTypeJustBeforeEach
SpecComponentTypeJustAfterEach
SpecComponentTypeAfterEach
SpecComponentTypeIt
SpecComponentTypeMeasure
)
type FlagType uint
const (
FlagTypeNone FlagType = iota
FlagTypeFocused
FlagTypePending
)

20
vendor/github.com/onsi/gomega/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.

382
vendor/github.com/onsi/gomega/format/format.go generated vendored Normal file
View File

@ -0,0 +1,382 @@
/*
Gomega's format package pretty-prints objects. It explores input objects recursively and generates formatted, indented output with type information.
*/
package format
import (
"fmt"
"reflect"
"strconv"
"strings"
"time"
)
// Use MaxDepth to set the maximum recursion depth when printing deeply nested objects
var MaxDepth = uint(10)
/*
By default, all objects (even those that implement fmt.Stringer and fmt.GoStringer) are recursively inspected to generate output.
Set UseStringerRepresentation = true to use GoString (for fmt.GoStringers) or String (for fmt.Stringer) instead.
Note that GoString and String don't always have all the information you need to understand why a test failed!
*/
var UseStringerRepresentation = false
/*
Print the content of context objects. By default it will be suppressed.
Set PrintContextObjects = true to enable printing of the context internals.
*/
var PrintContextObjects = false
// TruncatedDiff choose if we should display a truncated pretty diff or not
var TruncatedDiff = true
// Ctx interface defined here to keep backwards compatability with go < 1.7
// It matches the context.Context interface
type Ctx interface {
Deadline() (deadline time.Time, ok bool)
Done() <-chan struct{}
Err() error
Value(key interface{}) interface{}
}
var contextType = reflect.TypeOf((*Ctx)(nil)).Elem()
var timeType = reflect.TypeOf(time.Time{})
//The default indentation string emitted by the format package
var Indent = " "
var longFormThreshold = 20
/*
Generates a formatted matcher success/failure message of the form:
Expected
<pretty printed actual>
<message>
<pretty printed expected>
If expected is omited, then the message looks like:
Expected
<pretty printed actual>
<message>
*/
func Message(actual interface{}, message string, expected ...interface{}) string {
if len(expected) == 0 {
return fmt.Sprintf("Expected\n%s\n%s", Object(actual, 1), message)
}
return fmt.Sprintf("Expected\n%s\n%s\n%s", Object(actual, 1), message, Object(expected[0], 1))
}
/*
Generates a nicely formatted matcher success / failure message
Much like Message(...), but it attempts to pretty print diffs in strings
Expected
<string>: "...aaaaabaaaaa..."
to equal |
<string>: "...aaaaazaaaaa..."
*/
func MessageWithDiff(actual, message, expected string) string {
if TruncatedDiff && len(actual) >= truncateThreshold && len(expected) >= truncateThreshold {
diffPoint := findFirstMismatch(actual, expected)
formattedActual := truncateAndFormat(actual, diffPoint)
formattedExpected := truncateAndFormat(expected, diffPoint)
spacesBeforeFormattedMismatch := findFirstMismatch(formattedActual, formattedExpected)
tabLength := 4
spaceFromMessageToActual := tabLength + len("<string>: ") - len(message)
padding := strings.Repeat(" ", spaceFromMessageToActual+spacesBeforeFormattedMismatch) + "|"
return Message(formattedActual, message+padding, formattedExpected)
}
return Message(actual, message, expected)
}
func truncateAndFormat(str string, index int) string {
leftPadding := `...`
rightPadding := `...`
start := index - charactersAroundMismatchToInclude
if start < 0 {
start = 0
leftPadding = ""
}
// slice index must include the mis-matched character
lengthOfMismatchedCharacter := 1
end := index + charactersAroundMismatchToInclude + lengthOfMismatchedCharacter
if end > len(str) {
end = len(str)
rightPadding = ""
}
return fmt.Sprintf("\"%s\"", leftPadding+str[start:end]+rightPadding)
}
func findFirstMismatch(a, b string) int {
aSlice := strings.Split(a, "")
bSlice := strings.Split(b, "")
for index, str := range aSlice {
if index > len(bSlice)-1 {
return index
}
if str != bSlice[index] {
return index
}
}
if len(b) > len(a) {
return len(a) + 1
}
return 0
}
const (
truncateThreshold = 50
charactersAroundMismatchToInclude = 5
)
/*
Pretty prints the passed in object at the passed in indentation level.
Object recurses into deeply nested objects emitting pretty-printed representations of their components.
Modify format.MaxDepth to control how deep the recursion is allowed to go
Set format.UseStringerRepresentation to true to return object.GoString() or object.String() when available instead of
recursing into the object.
Set PrintContextObjects to true to print the content of objects implementing context.Context
*/
func Object(object interface{}, indentation uint) string {
indent := strings.Repeat(Indent, int(indentation))
value := reflect.ValueOf(object)
return fmt.Sprintf("%s<%s>: %s", indent, formatType(object), formatValue(value, indentation))
}
/*
IndentString takes a string and indents each line by the specified amount.
*/
func IndentString(s string, indentation uint) string {
components := strings.Split(s, "\n")
result := ""
indent := strings.Repeat(Indent, int(indentation))
for i, component := range components {
result += indent + component
if i < len(components)-1 {
result += "\n"
}
}
return result
}
func formatType(object interface{}) string {
t := reflect.TypeOf(object)
if t == nil {
return "nil"
}
switch t.Kind() {
case reflect.Chan:
v := reflect.ValueOf(object)
return fmt.Sprintf("%T | len:%d, cap:%d", object, v.Len(), v.Cap())
case reflect.Ptr:
return fmt.Sprintf("%T | %p", object, object)
case reflect.Slice:
v := reflect.ValueOf(object)
return fmt.Sprintf("%T | len:%d, cap:%d", object, v.Len(), v.Cap())
case reflect.Map:
v := reflect.ValueOf(object)
return fmt.Sprintf("%T | len:%d", object, v.Len())
default:
return fmt.Sprintf("%T", object)
}
}
func formatValue(value reflect.Value, indentation uint) string {
if indentation > MaxDepth {
return "..."
}
if isNilValue(value) {
return "nil"
}
if UseStringerRepresentation {
if value.CanInterface() {
obj := value.Interface()
switch x := obj.(type) {
case fmt.GoStringer:
return x.GoString()
case fmt.Stringer:
return x.String()
}
}
}
if !PrintContextObjects {
if value.Type().Implements(contextType) && indentation > 1 {
return "<suppressed context>"
}
}
switch value.Kind() {
case reflect.Bool:
return fmt.Sprintf("%v", value.Bool())
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
return fmt.Sprintf("%v", value.Int())
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
return fmt.Sprintf("%v", value.Uint())
case reflect.Uintptr:
return fmt.Sprintf("0x%x", value.Uint())
case reflect.Float32, reflect.Float64:
return fmt.Sprintf("%v", value.Float())
case reflect.Complex64, reflect.Complex128:
return fmt.Sprintf("%v", value.Complex())
case reflect.Chan:
return fmt.Sprintf("0x%x", value.Pointer())
case reflect.Func:
return fmt.Sprintf("0x%x", value.Pointer())
case reflect.Ptr:
return formatValue(value.Elem(), indentation)
case reflect.Slice:
return formatSlice(value, indentation)
case reflect.String:
return formatString(value.String(), indentation)
case reflect.Array:
return formatSlice(value, indentation)
case reflect.Map:
return formatMap(value, indentation)
case reflect.Struct:
if value.Type() == timeType && value.CanInterface() {
t, _ := value.Interface().(time.Time)
return t.Format(time.RFC3339Nano)
}
return formatStruct(value, indentation)
case reflect.Interface:
return formatValue(value.Elem(), indentation)
default:
if value.CanInterface() {
return fmt.Sprintf("%#v", value.Interface())
}
return fmt.Sprintf("%#v", value)
}
}
func formatString(object interface{}, indentation uint) string {
if indentation == 1 {
s := fmt.Sprintf("%s", object)
components := strings.Split(s, "\n")
result := ""
for i, component := range components {
if i == 0 {
result += component
} else {
result += Indent + component
}
if i < len(components)-1 {
result += "\n"
}
}
return fmt.Sprintf("%s", result)
} else {
return fmt.Sprintf("%q", object)
}
}
func formatSlice(v reflect.Value, indentation uint) string {
if v.Kind() == reflect.Slice && v.Type().Elem().Kind() == reflect.Uint8 && isPrintableString(string(v.Bytes())) {
return formatString(v.Bytes(), indentation)
}
l := v.Len()
result := make([]string, l)
longest := 0
for i := 0; i < l; i++ {
result[i] = formatValue(v.Index(i), indentation+1)
if len(result[i]) > longest {
longest = len(result[i])
}
}
if longest > longFormThreshold {
indenter := strings.Repeat(Indent, int(indentation))
return fmt.Sprintf("[\n%s%s,\n%s]", indenter+Indent, strings.Join(result, ",\n"+indenter+Indent), indenter)
}
return fmt.Sprintf("[%s]", strings.Join(result, ", "))
}
func formatMap(v reflect.Value, indentation uint) string {
l := v.Len()
result := make([]string, l)
longest := 0
for i, key := range v.MapKeys() {
value := v.MapIndex(key)
result[i] = fmt.Sprintf("%s: %s", formatValue(key, indentation+1), formatValue(value, indentation+1))
if len(result[i]) > longest {
longest = len(result[i])
}
}
if longest > longFormThreshold {
indenter := strings.Repeat(Indent, int(indentation))
return fmt.Sprintf("{\n%s%s,\n%s}", indenter+Indent, strings.Join(result, ",\n"+indenter+Indent), indenter)
}
return fmt.Sprintf("{%s}", strings.Join(result, ", "))
}
func formatStruct(v reflect.Value, indentation uint) string {
t := v.Type()
l := v.NumField()
result := []string{}
longest := 0
for i := 0; i < l; i++ {
structField := t.Field(i)
fieldEntry := v.Field(i)
representation := fmt.Sprintf("%s: %s", structField.Name, formatValue(fieldEntry, indentation+1))
result = append(result, representation)
if len(representation) > longest {
longest = len(representation)
}
}
if longest > longFormThreshold {
indenter := strings.Repeat(Indent, int(indentation))
return fmt.Sprintf("{\n%s%s,\n%s}", indenter+Indent, strings.Join(result, ",\n"+indenter+Indent), indenter)
}
return fmt.Sprintf("{%s}", strings.Join(result, ", "))
}
func isNilValue(a reflect.Value) bool {
switch a.Kind() {
case reflect.Invalid:
return true
case reflect.Chan, reflect.Func, reflect.Interface, reflect.Map, reflect.Ptr, reflect.Slice:
return a.IsNil()
}
return false
}
/*
Returns true when the string is entirely made of printable runes, false otherwise.
*/
func isPrintableString(str string) bool {
for _, runeValue := range str {
if !strconv.IsPrint(runeValue) {
return false
}
}
return true
}

429
vendor/github.com/onsi/gomega/gomega_dsl.go generated vendored Normal file
View File

@ -0,0 +1,429 @@
/*
Gomega is the Ginkgo BDD-style testing framework's preferred matcher library.
The godoc documentation describes Gomega's API. More comprehensive documentation (with examples!) is available at http://onsi.github.io/gomega/
Gomega on Github: http://github.com/onsi/gomega
Learn more about Ginkgo online: http://onsi.github.io/ginkgo
Ginkgo on Github: http://github.com/onsi/ginkgo
Gomega is MIT-Licensed
*/
package gomega
import (
"fmt"
"reflect"
"time"
"github.com/onsi/gomega/internal/assertion"
"github.com/onsi/gomega/internal/asyncassertion"
"github.com/onsi/gomega/internal/testingtsupport"
"github.com/onsi/gomega/types"
)
const GOMEGA_VERSION = "1.5.0"
const nilFailHandlerPanic = `You are trying to make an assertion, but Gomega's fail handler is nil.
If you're using Ginkgo then you probably forgot to put your assertion in an It().
Alternatively, you may have forgotten to register a fail handler with RegisterFailHandler() or RegisterTestingT().
Depending on your vendoring solution you may be inadvertently importing gomega and subpackages (e.g. ghhtp, gexec,...) from different locations.
`
var globalFailWrapper *types.GomegaFailWrapper
var defaultEventuallyTimeout = time.Second
var defaultEventuallyPollingInterval = 10 * time.Millisecond
var defaultConsistentlyDuration = 100 * time.Millisecond
var defaultConsistentlyPollingInterval = 10 * time.Millisecond
// RegisterFailHandler connects Ginkgo to Gomega. When a matcher fails
// the fail handler passed into RegisterFailHandler is called.
func RegisterFailHandler(handler types.GomegaFailHandler) {
RegisterFailHandlerWithT(testingtsupport.EmptyTWithHelper{}, handler)
}
// RegisterFailHandlerWithT ensures that the given types.TWithHelper and fail handler
// are used globally.
func RegisterFailHandlerWithT(t types.TWithHelper, handler types.GomegaFailHandler) {
if handler == nil {
globalFailWrapper = nil
return
}
globalFailWrapper = &types.GomegaFailWrapper{
Fail: handler,
TWithHelper: t,
}
}
// RegisterTestingT connects Gomega to Golang's XUnit style
// Testing.T tests. It is now deprecated and you should use NewWithT() instead.
//
// Legacy Documentation:
//
// You'll need to call this at the top of each XUnit style test:
//
// func TestFarmHasCow(t *testing.T) {
// RegisterTestingT(t)
//
// f := farm.New([]string{"Cow", "Horse"})
// Expect(f.HasCow()).To(BeTrue(), "Farm should have cow")
// }
//
// Note that this *testing.T is registered *globally* by Gomega (this is why you don't have to
// pass `t` down to the matcher itself). This means that you cannot run the XUnit style tests
// in parallel as the global fail handler cannot point to more than one testing.T at a time.
//
// NewWithT() does not have this limitation
//
// (As an aside: Ginkgo gets around this limitation by running parallel tests in different *processes*).
func RegisterTestingT(t types.GomegaTestingT) {
tWithHelper, hasHelper := t.(types.TWithHelper)
if !hasHelper {
RegisterFailHandler(testingtsupport.BuildTestingTGomegaFailWrapper(t).Fail)
return
}
RegisterFailHandlerWithT(tWithHelper, testingtsupport.BuildTestingTGomegaFailWrapper(t).Fail)
}
// InterceptGomegaFailures runs a given callback and returns an array of
// failure messages generated by any Gomega assertions within the callback.
//
// This is accomplished by temporarily replacing the *global* fail handler
// with a fail handler that simply annotates failures. The original fail handler
// is reset when InterceptGomegaFailures returns.
//
// This is most useful when testing custom matchers, but can also be used to check
// on a value using a Gomega assertion without causing a test failure.
func InterceptGomegaFailures(f func()) []string {
originalHandler := globalFailWrapper.Fail
failures := []string{}
RegisterFailHandler(func(message string, callerSkip ...int) {
failures = append(failures, message)
})
f()
RegisterFailHandler(originalHandler)
return failures
}
// Ω wraps an actual value allowing assertions to be made on it:
// Ω("foo").Should(Equal("foo"))
//
// If Ω is passed more than one argument it will pass the *first* argument to the matcher.
// All subsequent arguments will be required to be nil/zero.
//
// This is convenient if you want to make an assertion on a method/function that returns
// a value and an error - a common patter in Go.
//
// For example, given a function with signature:
// func MyAmazingThing() (int, error)
//
// Then:
// Ω(MyAmazingThing()).Should(Equal(3))
// Will succeed only if `MyAmazingThing()` returns `(3, nil)`
//
// Ω and Expect are identical
func Ω(actual interface{}, extra ...interface{}) Assertion {
return ExpectWithOffset(0, actual, extra...)
}
// Expect wraps an actual value allowing assertions to be made on it:
// Expect("foo").To(Equal("foo"))
//
// If Expect is passed more than one argument it will pass the *first* argument to the matcher.
// All subsequent arguments will be required to be nil/zero.
//
// This is convenient if you want to make an assertion on a method/function that returns
// a value and an error - a common patter in Go.
//
// For example, given a function with signature:
// func MyAmazingThing() (int, error)
//
// Then:
// Expect(MyAmazingThing()).Should(Equal(3))
// Will succeed only if `MyAmazingThing()` returns `(3, nil)`
//
// Expect and Ω are identical
func Expect(actual interface{}, extra ...interface{}) Assertion {
return ExpectWithOffset(0, actual, extra...)
}
// ExpectWithOffset wraps an actual value allowing assertions to be made on it:
// ExpectWithOffset(1, "foo").To(Equal("foo"))
//
// Unlike `Expect` and `Ω`, `ExpectWithOffset` takes an additional integer argument
// this is used to modify the call-stack offset when computing line numbers.
//
// This is most useful in helper functions that make assertions. If you want Gomega's
// error message to refer to the calling line in the test (as opposed to the line in the helper function)
// set the first argument of `ExpectWithOffset` appropriately.
func ExpectWithOffset(offset int, actual interface{}, extra ...interface{}) Assertion {
if globalFailWrapper == nil {
panic(nilFailHandlerPanic)
}
return assertion.New(actual, globalFailWrapper, offset, extra...)
}
// Eventually wraps an actual value allowing assertions to be made on it.
// The assertion is tried periodically until it passes or a timeout occurs.
//
// Both the timeout and polling interval are configurable as optional arguments:
// The first optional argument is the timeout
// The second optional argument is the polling interval
//
// Both intervals can either be specified as time.Duration, parsable duration strings or as floats/integers. In the
// last case they are interpreted as seconds.
//
// If Eventually is passed an actual that is a function taking no arguments and returning at least one value,
// then Eventually will call the function periodically and try the matcher against the function's first return value.
//
// Example:
//
// Eventually(func() int {
// return thingImPolling.Count()
// }).Should(BeNumerically(">=", 17))
//
// Note that this example could be rewritten:
//
// Eventually(thingImPolling.Count).Should(BeNumerically(">=", 17))
//
// If the function returns more than one value, then Eventually will pass the first value to the matcher and
// assert that all other values are nil/zero.
// This allows you to pass Eventually a function that returns a value and an error - a common pattern in Go.
//
// For example, consider a method that returns a value and an error:
// func FetchFromDB() (string, error)
//
// Then
// Eventually(FetchFromDB).Should(Equal("hasselhoff"))
//
// Will pass only if the the returned error is nil and the returned string passes the matcher.
//
// Eventually's default timeout is 1 second, and its default polling interval is 10ms
func Eventually(actual interface{}, intervals ...interface{}) AsyncAssertion {
return EventuallyWithOffset(0, actual, intervals...)
}
// EventuallyWithOffset operates like Eventually but takes an additional
// initial argument to indicate an offset in the call stack. This is useful when building helper
// functions that contain matchers. To learn more, read about `ExpectWithOffset`.
func EventuallyWithOffset(offset int, actual interface{}, intervals ...interface{}) AsyncAssertion {
if globalFailWrapper == nil {
panic(nilFailHandlerPanic)
}
timeoutInterval := defaultEventuallyTimeout
pollingInterval := defaultEventuallyPollingInterval
if len(intervals) > 0 {
timeoutInterval = toDuration(intervals[0])
}
if len(intervals) > 1 {
pollingInterval = toDuration(intervals[1])
}
return asyncassertion.New(asyncassertion.AsyncAssertionTypeEventually, actual, globalFailWrapper, timeoutInterval, pollingInterval, offset)
}
// Consistently wraps an actual value allowing assertions to be made on it.
// The assertion is tried periodically and is required to pass for a period of time.
//
// Both the total time and polling interval are configurable as optional arguments:
// The first optional argument is the duration that Consistently will run for
// The second optional argument is the polling interval
//
// Both intervals can either be specified as time.Duration, parsable duration strings or as floats/integers. In the
// last case they are interpreted as seconds.
//
// If Consistently is passed an actual that is a function taking no arguments and returning at least one value,
// then Consistently will call the function periodically and try the matcher against the function's first return value.
//
// If the function returns more than one value, then Consistently will pass the first value to the matcher and
// assert that all other values are nil/zero.
// This allows you to pass Consistently a function that returns a value and an error - a common pattern in Go.
//
// Consistently is useful in cases where you want to assert that something *does not happen* over a period of tiem.
// For example, you want to assert that a goroutine does *not* send data down a channel. In this case, you could:
//
// Consistently(channel).ShouldNot(Receive())
//
// Consistently's default duration is 100ms, and its default polling interval is 10ms
func Consistently(actual interface{}, intervals ...interface{}) AsyncAssertion {
return ConsistentlyWithOffset(0, actual, intervals...)
}
// ConsistentlyWithOffset operates like Consistnetly but takes an additional
// initial argument to indicate an offset in the call stack. This is useful when building helper
// functions that contain matchers. To learn more, read about `ExpectWithOffset`.
func ConsistentlyWithOffset(offset int, actual interface{}, intervals ...interface{}) AsyncAssertion {
if globalFailWrapper == nil {
panic(nilFailHandlerPanic)
}
timeoutInterval := defaultConsistentlyDuration
pollingInterval := defaultConsistentlyPollingInterval
if len(intervals) > 0 {
timeoutInterval = toDuration(intervals[0])
}
if len(intervals) > 1 {
pollingInterval = toDuration(intervals[1])
}
return asyncassertion.New(asyncassertion.AsyncAssertionTypeConsistently, actual, globalFailWrapper, timeoutInterval, pollingInterval, offset)
}
// SetDefaultEventuallyTimeout sets the default timeout duration for Eventually. Eventually will repeatedly poll your condition until it succeeds, or until this timeout elapses.
func SetDefaultEventuallyTimeout(t time.Duration) {
defaultEventuallyTimeout = t
}
// SetDefaultEventuallyPollingInterval sets the default polling interval for Eventually.
func SetDefaultEventuallyPollingInterval(t time.Duration) {
defaultEventuallyPollingInterval = t
}
// SetDefaultConsistentlyDuration sets the default duration for Consistently. Consistently will verify that your condition is satsified for this long.
func SetDefaultConsistentlyDuration(t time.Duration) {
defaultConsistentlyDuration = t
}
// SetDefaultConsistentlyPollingInterval sets the default polling interval for Consistently.
func SetDefaultConsistentlyPollingInterval(t time.Duration) {
defaultConsistentlyPollingInterval = t
}
// AsyncAssertion is returned by Eventually and Consistently and polls the actual value passed into Eventually against
// the matcher passed to the Should and ShouldNot methods.
//
// Both Should and ShouldNot take a variadic optionalDescription argument. This is passed on to
// fmt.Sprintf() and is used to annotate failure messages. This allows you to make your failure messages more
// descriptive.
//
// Both Should and ShouldNot return a boolean that is true if the assertion passed and false if it failed.
//
// Example:
//
// Eventually(myChannel).Should(Receive(), "Something should have come down the pipe.")
// Consistently(myChannel).ShouldNot(Receive(), "Nothing should have come down the pipe.")
type AsyncAssertion interface {
Should(matcher types.GomegaMatcher, optionalDescription ...interface{}) bool
ShouldNot(matcher types.GomegaMatcher, optionalDescription ...interface{}) bool
}
// GomegaAsyncAssertion is deprecated in favor of AsyncAssertion, which does not stutter.
type GomegaAsyncAssertion = AsyncAssertion
// Assertion is returned by Ω and Expect and compares the actual value to the matcher
// passed to the Should/ShouldNot and To/ToNot/NotTo methods.
//
// Typically Should/ShouldNot are used with Ω and To/ToNot/NotTo are used with Expect
// though this is not enforced.
//
// All methods take a variadic optionalDescription argument. This is passed on to fmt.Sprintf()
// and is used to annotate failure messages.
//
// All methods return a bool that is true if hte assertion passed and false if it failed.
//
// Example:
//
// Ω(farm.HasCow()).Should(BeTrue(), "Farm %v should have a cow", farm)
type Assertion interface {
Should(matcher types.GomegaMatcher, optionalDescription ...interface{}) bool
ShouldNot(matcher types.GomegaMatcher, optionalDescription ...interface{}) bool
To(matcher types.GomegaMatcher, optionalDescription ...interface{}) bool
ToNot(matcher types.GomegaMatcher, optionalDescription ...interface{}) bool
NotTo(matcher types.GomegaMatcher, optionalDescription ...interface{}) bool
}
// GomegaAssertion is deprecated in favor of Assertion, which does not stutter.
type GomegaAssertion = Assertion
// OmegaMatcher is deprecated in favor of the better-named and better-organized types.GomegaMatcher but sticks around to support existing code that uses it
type OmegaMatcher types.GomegaMatcher
// WithT wraps a *testing.T and provides `Expect`, `Eventually`, and `Consistently` methods. This allows you to leverage
// Gomega's rich ecosystem of matchers in standard `testing` test suites.
//
// Use `NewWithT` to instantiate a `WithT`
type WithT struct {
t types.GomegaTestingT
}
// GomegaWithT is deprecated in favor of gomega.WithT, which does not stutter.
type GomegaWithT = WithT
// NewWithT takes a *testing.T and returngs a `gomega.WithT` allowing you to use `Expect`, `Eventually`, and `Consistently` along with
// Gomega's rich ecosystem of matchers in standard `testing` test suits.
//
// func TestFarmHasCow(t *testing.T) {
// g := gomega.NewWithT(t)
//
// f := farm.New([]string{"Cow", "Horse"})
// g.Expect(f.HasCow()).To(BeTrue(), "Farm should have cow")
// }
func NewWithT(t types.GomegaTestingT) *WithT {
return &WithT{
t: t,
}
}
// NewGomegaWithT is deprecated in favor of gomega.NewWithT, which does not stutter.
func NewGomegaWithT(t types.GomegaTestingT) *GomegaWithT {
return NewWithT(t)
}
// Expect is used to make assertions. See documentation for Expect.
func (g *WithT) Expect(actual interface{}, extra ...interface{}) Assertion {
return assertion.New(actual, testingtsupport.BuildTestingTGomegaFailWrapper(g.t), 0, extra...)
}
// Eventually is used to make asynchronous assertions. See documentation for Eventually.
func (g *WithT) Eventually(actual interface{}, intervals ...interface{}) AsyncAssertion {
timeoutInterval := defaultEventuallyTimeout
pollingInterval := defaultEventuallyPollingInterval
if len(intervals) > 0 {
timeoutInterval = toDuration(intervals[0])
}
if len(intervals) > 1 {
pollingInterval = toDuration(intervals[1])
}
return asyncassertion.New(asyncassertion.AsyncAssertionTypeEventually, actual, testingtsupport.BuildTestingTGomegaFailWrapper(g.t), timeoutInterval, pollingInterval, 0)
}
// Consistently is used to make asynchronous assertions. See documentation for Consistently.
func (g *WithT) Consistently(actual interface{}, intervals ...interface{}) AsyncAssertion {
timeoutInterval := defaultConsistentlyDuration
pollingInterval := defaultConsistentlyPollingInterval
if len(intervals) > 0 {
timeoutInterval = toDuration(intervals[0])
}
if len(intervals) > 1 {
pollingInterval = toDuration(intervals[1])
}
return asyncassertion.New(asyncassertion.AsyncAssertionTypeConsistently, actual, testingtsupport.BuildTestingTGomegaFailWrapper(g.t), timeoutInterval, pollingInterval, 0)
}
func toDuration(input interface{}) time.Duration {
duration, ok := input.(time.Duration)
if ok {
return duration
}
value := reflect.ValueOf(input)
kind := reflect.TypeOf(input).Kind()
if reflect.Int <= kind && kind <= reflect.Int64 {
return time.Duration(value.Int()) * time.Second
} else if reflect.Uint <= kind && kind <= reflect.Uint64 {
return time.Duration(value.Uint()) * time.Second
} else if reflect.Float32 <= kind && kind <= reflect.Float64 {
return time.Duration(value.Float() * float64(time.Second))
} else if reflect.String == kind {
duration, err := time.ParseDuration(value.String())
if err != nil {
panic(fmt.Sprintf("%#v is not a valid parsable duration string.", input))
}
return duration
}
panic(fmt.Sprintf("%v is not a valid interval. Must be time.Duration, parsable duration string or a number.", input))
}

View File

@ -0,0 +1,105 @@
package assertion
import (
"fmt"
"reflect"
"github.com/onsi/gomega/types"
)
type Assertion struct {
actualInput interface{}
failWrapper *types.GomegaFailWrapper
offset int
extra []interface{}
}
func New(actualInput interface{}, failWrapper *types.GomegaFailWrapper, offset int, extra ...interface{}) *Assertion {
return &Assertion{
actualInput: actualInput,
failWrapper: failWrapper,
offset: offset,
extra: extra,
}
}
func (assertion *Assertion) Should(matcher types.GomegaMatcher, optionalDescription ...interface{}) bool {
assertion.failWrapper.TWithHelper.Helper()
return assertion.vetExtras(optionalDescription...) && assertion.match(matcher, true, optionalDescription...)
}
func (assertion *Assertion) ShouldNot(matcher types.GomegaMatcher, optionalDescription ...interface{}) bool {
assertion.failWrapper.TWithHelper.Helper()
return assertion.vetExtras(optionalDescription...) && assertion.match(matcher, false, optionalDescription...)
}
func (assertion *Assertion) To(matcher types.GomegaMatcher, optionalDescription ...interface{}) bool {
assertion.failWrapper.TWithHelper.Helper()
return assertion.vetExtras(optionalDescription...) && assertion.match(matcher, true, optionalDescription...)
}
func (assertion *Assertion) ToNot(matcher types.GomegaMatcher, optionalDescription ...interface{}) bool {
assertion.failWrapper.TWithHelper.Helper()
return assertion.vetExtras(optionalDescription...) && assertion.match(matcher, false, optionalDescription...)
}
func (assertion *Assertion) NotTo(matcher types.GomegaMatcher, optionalDescription ...interface{}) bool {
assertion.failWrapper.TWithHelper.Helper()
return assertion.vetExtras(optionalDescription...) && assertion.match(matcher, false, optionalDescription...)
}
func (assertion *Assertion) buildDescription(optionalDescription ...interface{}) string {
switch len(optionalDescription) {
case 0:
return ""
default:
return fmt.Sprintf(optionalDescription[0].(string), optionalDescription[1:]...) + "\n"
}
}
func (assertion *Assertion) match(matcher types.GomegaMatcher, desiredMatch bool, optionalDescription ...interface{}) bool {
matches, err := matcher.Match(assertion.actualInput)
description := assertion.buildDescription(optionalDescription...)
assertion.failWrapper.TWithHelper.Helper()
if err != nil {
assertion.failWrapper.Fail(description+err.Error(), 2+assertion.offset)
return false
}
if matches != desiredMatch {
var message string
if desiredMatch {
message = matcher.FailureMessage(assertion.actualInput)
} else {
message = matcher.NegatedFailureMessage(assertion.actualInput)
}
assertion.failWrapper.Fail(description+message, 2+assertion.offset)
return false
}
return true
}
func (assertion *Assertion) vetExtras(optionalDescription ...interface{}) bool {
success, message := vetExtras(assertion.extra)
if success {
return true
}
description := assertion.buildDescription(optionalDescription...)
assertion.failWrapper.TWithHelper.Helper()
assertion.failWrapper.Fail(description+message, 2+assertion.offset)
return false
}
func vetExtras(extras []interface{}) (bool, string) {
for i, extra := range extras {
if extra != nil {
zeroValue := reflect.Zero(reflect.TypeOf(extra)).Interface()
if !reflect.DeepEqual(zeroValue, extra) {
message := fmt.Sprintf("Unexpected non-nil/non-zero extra argument at index %d:\n\t<%T>: %#v", i+1, extra, extra)
return false, message
}
}
}
return true, ""
}

View File

@ -0,0 +1,194 @@
package asyncassertion
import (
"errors"
"fmt"
"reflect"
"time"
"github.com/onsi/gomega/internal/oraclematcher"
"github.com/onsi/gomega/types"
)
type AsyncAssertionType uint
const (
AsyncAssertionTypeEventually AsyncAssertionType = iota
AsyncAssertionTypeConsistently
)
type AsyncAssertion struct {
asyncType AsyncAssertionType
actualInput interface{}
timeoutInterval time.Duration
pollingInterval time.Duration
failWrapper *types.GomegaFailWrapper
offset int
}
func New(asyncType AsyncAssertionType, actualInput interface{}, failWrapper *types.GomegaFailWrapper, timeoutInterval time.Duration, pollingInterval time.Duration, offset int) *AsyncAssertion {
actualType := reflect.TypeOf(actualInput)
if actualType.Kind() == reflect.Func {
if actualType.NumIn() != 0 || actualType.NumOut() == 0 {
panic("Expected a function with no arguments and one or more return values.")
}
}
return &AsyncAssertion{
asyncType: asyncType,
actualInput: actualInput,
failWrapper: failWrapper,
timeoutInterval: timeoutInterval,
pollingInterval: pollingInterval,
offset: offset,
}
}
func (assertion *AsyncAssertion) Should(matcher types.GomegaMatcher, optionalDescription ...interface{}) bool {
assertion.failWrapper.TWithHelper.Helper()
return assertion.match(matcher, true, optionalDescription...)
}
func (assertion *AsyncAssertion) ShouldNot(matcher types.GomegaMatcher, optionalDescription ...interface{}) bool {
assertion.failWrapper.TWithHelper.Helper()
return assertion.match(matcher, false, optionalDescription...)
}
func (assertion *AsyncAssertion) buildDescription(optionalDescription ...interface{}) string {
switch len(optionalDescription) {
case 0:
return ""
default:
return fmt.Sprintf(optionalDescription[0].(string), optionalDescription[1:]...) + "\n"
}
}
func (assertion *AsyncAssertion) actualInputIsAFunction() bool {
actualType := reflect.TypeOf(assertion.actualInput)
return actualType.Kind() == reflect.Func && actualType.NumIn() == 0 && actualType.NumOut() > 0
}
func (assertion *AsyncAssertion) pollActual() (interface{}, error) {
if assertion.actualInputIsAFunction() {
values := reflect.ValueOf(assertion.actualInput).Call([]reflect.Value{})
extras := []interface{}{}
for _, value := range values[1:] {
extras = append(extras, value.Interface())
}
success, message := vetExtras(extras)
if !success {
return nil, errors.New(message)
}
return values[0].Interface(), nil
}
return assertion.actualInput, nil
}
func (assertion *AsyncAssertion) matcherMayChange(matcher types.GomegaMatcher, value interface{}) bool {
if assertion.actualInputIsAFunction() {
return true
}
return oraclematcher.MatchMayChangeInTheFuture(matcher, value)
}
func (assertion *AsyncAssertion) match(matcher types.GomegaMatcher, desiredMatch bool, optionalDescription ...interface{}) bool {
timer := time.Now()
timeout := time.After(assertion.timeoutInterval)
description := assertion.buildDescription(optionalDescription...)
var matches bool
var err error
mayChange := true
value, err := assertion.pollActual()
if err == nil {
mayChange = assertion.matcherMayChange(matcher, value)
matches, err = matcher.Match(value)
}
assertion.failWrapper.TWithHelper.Helper()
fail := func(preamble string) {
errMsg := ""
message := ""
if err != nil {
errMsg = "Error: " + err.Error()
} else {
if desiredMatch {
message = matcher.FailureMessage(value)
} else {
message = matcher.NegatedFailureMessage(value)
}
}
assertion.failWrapper.TWithHelper.Helper()
assertion.failWrapper.Fail(fmt.Sprintf("%s after %.3fs.\n%s%s%s", preamble, time.Since(timer).Seconds(), description, message, errMsg), 3+assertion.offset)
}
if assertion.asyncType == AsyncAssertionTypeEventually {
for {
if err == nil && matches == desiredMatch {
return true
}
if !mayChange {
fail("No future change is possible. Bailing out early")
return false
}
select {
case <-time.After(assertion.pollingInterval):
value, err = assertion.pollActual()
if err == nil {
mayChange = assertion.matcherMayChange(matcher, value)
matches, err = matcher.Match(value)
}
case <-timeout:
fail("Timed out")
return false
}
}
} else if assertion.asyncType == AsyncAssertionTypeConsistently {
for {
if !(err == nil && matches == desiredMatch) {
fail("Failed")
return false
}
if !mayChange {
return true
}
select {
case <-time.After(assertion.pollingInterval):
value, err = assertion.pollActual()
if err == nil {
mayChange = assertion.matcherMayChange(matcher, value)
matches, err = matcher.Match(value)
}
case <-timeout:
return true
}
}
}
return false
}
func vetExtras(extras []interface{}) (bool, string) {
for i, extra := range extras {
if extra != nil {
zeroValue := reflect.Zero(reflect.TypeOf(extra)).Interface()
if !reflect.DeepEqual(zeroValue, extra) {
message := fmt.Sprintf("Unexpected non-nil/non-zero extra argument at index %d:\n\t<%T>: %#v", i+1, extra, extra)
return false, message
}
}
}
return true, ""
}

View File

@ -0,0 +1,25 @@
package oraclematcher
import "github.com/onsi/gomega/types"
/*
GomegaMatchers that also match the OracleMatcher interface can convey information about
whether or not their result will change upon future attempts.
This allows `Eventually` and `Consistently` to short circuit if success becomes impossible.
For example, a process' exit code can never change. So, gexec's Exit matcher returns `true`
for `MatchMayChangeInTheFuture` until the process exits, at which point it returns `false` forevermore.
*/
type OracleMatcher interface {
MatchMayChangeInTheFuture(actual interface{}) bool
}
func MatchMayChangeInTheFuture(matcher types.GomegaMatcher, value interface{}) bool {
oracleMatcher, ok := matcher.(OracleMatcher)
if !ok {
return true
}
return oracleMatcher.MatchMayChangeInTheFuture(value)
}

View File

@ -0,0 +1,60 @@
package testingtsupport
import (
"regexp"
"runtime/debug"
"strings"
"github.com/onsi/gomega/types"
)
var StackTracePruneRE = regexp.MustCompile(`\/gomega\/|\/ginkgo\/|\/pkg\/testing\/|\/pkg\/runtime\/`)
type EmptyTWithHelper struct{}
func (e EmptyTWithHelper) Helper() {}
type gomegaTestingT interface {
Fatalf(format string, args ...interface{})
}
func BuildTestingTGomegaFailWrapper(t gomegaTestingT) *types.GomegaFailWrapper {
tWithHelper, hasHelper := t.(types.TWithHelper)
if !hasHelper {
tWithHelper = EmptyTWithHelper{}
}
fail := func(message string, callerSkip ...int) {
if hasHelper {
tWithHelper.Helper()
t.Fatalf("\n%s", message)
} else {
skip := 2
if len(callerSkip) > 0 {
skip += callerSkip[0]
}
stackTrace := pruneStack(string(debug.Stack()), skip)
t.Fatalf("\n%s\n%s\n", stackTrace, message)
}
}
return &types.GomegaFailWrapper{
Fail: fail,
TWithHelper: tWithHelper,
}
}
func pruneStack(fullStackTrace string, skip int) string {
stack := strings.Split(fullStackTrace, "\n")[1:]
if len(stack) > 2*skip {
stack = stack[2*skip:]
}
prunedStack := []string{}
for i := 0; i < len(stack)/2; i++ {
if !StackTracePruneRE.Match([]byte(stack[i*2])) {
prunedStack = append(prunedStack, stack[i*2])
prunedStack = append(prunedStack, stack[i*2+1])
}
}
return strings.Join(prunedStack, "\n")
}

427
vendor/github.com/onsi/gomega/matchers.go generated vendored Normal file
View File

@ -0,0 +1,427 @@
package gomega
import (
"time"
"github.com/onsi/gomega/matchers"
"github.com/onsi/gomega/types"
)
//Equal uses reflect.DeepEqual to compare actual with expected. Equal is strict about
//types when performing comparisons.
//It is an error for both actual and expected to be nil. Use BeNil() instead.
func Equal(expected interface{}) types.GomegaMatcher {
return &matchers.EqualMatcher{
Expected: expected,
}
}
//BeEquivalentTo is more lax than Equal, allowing equality between different types.
//This is done by converting actual to have the type of expected before
//attempting equality with reflect.DeepEqual.
//It is an error for actual and expected to be nil. Use BeNil() instead.
func BeEquivalentTo(expected interface{}) types.GomegaMatcher {
return &matchers.BeEquivalentToMatcher{
Expected: expected,
}
}
//BeIdenticalTo uses the == operator to compare actual with expected.
//BeIdenticalTo is strict about types when performing comparisons.
//It is an error for both actual and expected to be nil. Use BeNil() instead.
func BeIdenticalTo(expected interface{}) types.GomegaMatcher {
return &matchers.BeIdenticalToMatcher{
Expected: expected,
}
}
//BeNil succeeds if actual is nil
func BeNil() types.GomegaMatcher {
return &matchers.BeNilMatcher{}
}
//BeTrue succeeds if actual is true
func BeTrue() types.GomegaMatcher {
return &matchers.BeTrueMatcher{}
}
//BeFalse succeeds if actual is false
func BeFalse() types.GomegaMatcher {
return &matchers.BeFalseMatcher{}
}
//HaveOccurred succeeds if actual is a non-nil error
//The typical Go error checking pattern looks like:
// err := SomethingThatMightFail()
// Expect(err).ShouldNot(HaveOccurred())
func HaveOccurred() types.GomegaMatcher {
return &matchers.HaveOccurredMatcher{}
}
//Succeed passes if actual is a nil error
//Succeed is intended to be used with functions that return a single error value. Instead of
// err := SomethingThatMightFail()
// Expect(err).ShouldNot(HaveOccurred())
//
//You can write:
// Expect(SomethingThatMightFail()).Should(Succeed())
//
//It is a mistake to use Succeed with a function that has multiple return values. Gomega's Ω and Expect
//functions automatically trigger failure if any return values after the first return value are non-zero/non-nil.
//This means that Ω(MultiReturnFunc()).ShouldNot(Succeed()) can never pass.
func Succeed() types.GomegaMatcher {
return &matchers.SucceedMatcher{}
}
//MatchError succeeds if actual is a non-nil error that matches the passed in string/error.
//
//These are valid use-cases:
// Expect(err).Should(MatchError("an error")) //asserts that err.Error() == "an error"
// Expect(err).Should(MatchError(SomeError)) //asserts that err == SomeError (via reflect.DeepEqual)
//
//It is an error for err to be nil or an object that does not implement the Error interface
func MatchError(expected interface{}) types.GomegaMatcher {
return &matchers.MatchErrorMatcher{
Expected: expected,
}
}
//BeClosed succeeds if actual is a closed channel.
//It is an error to pass a non-channel to BeClosed, it is also an error to pass nil
//
//In order to check whether or not the channel is closed, Gomega must try to read from the channel
//(even in the `ShouldNot(BeClosed())` case). You should keep this in mind if you wish to make subsequent assertions about
//values coming down the channel.
//
//Also, if you are testing that a *buffered* channel is closed you must first read all values out of the channel before
//asserting that it is closed (it is not possible to detect that a buffered-channel has been closed until all its buffered values are read).
//
//Finally, as a corollary: it is an error to check whether or not a send-only channel is closed.
func BeClosed() types.GomegaMatcher {
return &matchers.BeClosedMatcher{}
}
//Receive succeeds if there is a value to be received on actual.
//Actual must be a channel (and cannot be a send-only channel) -- anything else is an error.
//
//Receive returns immediately and never blocks:
//
//- If there is nothing on the channel `c` then Expect(c).Should(Receive()) will fail and Ω(c).ShouldNot(Receive()) will pass.
//
//- If the channel `c` is closed then Expect(c).Should(Receive()) will fail and Ω(c).ShouldNot(Receive()) will pass.
//
//- If there is something on the channel `c` ready to be read, then Expect(c).Should(Receive()) will pass and Ω(c).ShouldNot(Receive()) will fail.
//
//If you have a go-routine running in the background that will write to channel `c` you can:
// Eventually(c).Should(Receive())
//
//This will timeout if nothing gets sent to `c` (you can modify the timeout interval as you normally do with `Eventually`)
//
//A similar use-case is to assert that no go-routine writes to a channel (for a period of time). You can do this with `Consistently`:
// Consistently(c).ShouldNot(Receive())
//
//You can pass `Receive` a matcher. If you do so, it will match the received object against the matcher. For example:
// Expect(c).Should(Receive(Equal("foo")))
//
//When given a matcher, `Receive` will always fail if there is nothing to be received on the channel.
//
//Passing Receive a matcher is especially useful when paired with Eventually:
//
// Eventually(c).Should(Receive(ContainSubstring("bar")))
//
//will repeatedly attempt to pull values out of `c` until a value matching "bar" is received.
//
//Finally, if you want to have a reference to the value *sent* to the channel you can pass the `Receive` matcher a pointer to a variable of the appropriate type:
// var myThing thing
// Eventually(thingChan).Should(Receive(&myThing))
// Expect(myThing.Sprocket).Should(Equal("foo"))
// Expect(myThing.IsValid()).Should(BeTrue())
func Receive(args ...interface{}) types.GomegaMatcher {
var arg interface{}
if len(args) > 0 {
arg = args[0]
}
return &matchers.ReceiveMatcher{
Arg: arg,
}
}
//BeSent succeeds if a value can be sent to actual.
//Actual must be a channel (and cannot be a receive-only channel) that can sent the type of the value passed into BeSent -- anything else is an error.
//In addition, actual must not be closed.
//
//BeSent never blocks:
//
//- If the channel `c` is not ready to receive then Expect(c).Should(BeSent("foo")) will fail immediately
//- If the channel `c` is eventually ready to receive then Eventually(c).Should(BeSent("foo")) will succeed.. presuming the channel becomes ready to receive before Eventually's timeout
//- If the channel `c` is closed then Expect(c).Should(BeSent("foo")) and Ω(c).ShouldNot(BeSent("foo")) will both fail immediately
//
//Of course, the value is actually sent to the channel. The point of `BeSent` is less to make an assertion about the availability of the channel (which is typically an implementation detail that your test should not be concerned with).
//Rather, the point of `BeSent` is to make it possible to easily and expressively write tests that can timeout on blocked channel sends.
func BeSent(arg interface{}) types.GomegaMatcher {
return &matchers.BeSentMatcher{
Arg: arg,
}
}
//MatchRegexp succeeds if actual is a string or stringer that matches the
//passed-in regexp. Optional arguments can be provided to construct a regexp
//via fmt.Sprintf().
func MatchRegexp(regexp string, args ...interface{}) types.GomegaMatcher {
return &matchers.MatchRegexpMatcher{
Regexp: regexp,
Args: args,
}
}
//ContainSubstring succeeds if actual is a string or stringer that contains the
//passed-in substring. Optional arguments can be provided to construct the substring
//via fmt.Sprintf().
func ContainSubstring(substr string, args ...interface{}) types.GomegaMatcher {
return &matchers.ContainSubstringMatcher{
Substr: substr,
Args: args,
}
}
//HavePrefix succeeds if actual is a string or stringer that contains the
//passed-in string as a prefix. Optional arguments can be provided to construct
//via fmt.Sprintf().
func HavePrefix(prefix string, args ...interface{}) types.GomegaMatcher {
return &matchers.HavePrefixMatcher{
Prefix: prefix,
Args: args,
}
}
//HaveSuffix succeeds if actual is a string or stringer that contains the
//passed-in string as a suffix. Optional arguments can be provided to construct
//via fmt.Sprintf().
func HaveSuffix(suffix string, args ...interface{}) types.GomegaMatcher {
return &matchers.HaveSuffixMatcher{
Suffix: suffix,
Args: args,
}
}
//MatchJSON succeeds if actual is a string or stringer of JSON that matches
//the expected JSON. The JSONs are decoded and the resulting objects are compared via
//reflect.DeepEqual so things like key-ordering and whitespace shouldn't matter.
func MatchJSON(json interface{}) types.GomegaMatcher {
return &matchers.MatchJSONMatcher{
JSONToMatch: json,
}
}
//MatchXML succeeds if actual is a string or stringer of XML that matches
//the expected XML. The XMLs are decoded and the resulting objects are compared via
//reflect.DeepEqual so things like whitespaces shouldn't matter.
func MatchXML(xml interface{}) types.GomegaMatcher {
return &matchers.MatchXMLMatcher{
XMLToMatch: xml,
}
}
//MatchYAML succeeds if actual is a string or stringer of YAML that matches
//the expected YAML. The YAML's are decoded and the resulting objects are compared via
//reflect.DeepEqual so things like key-ordering and whitespace shouldn't matter.
func MatchYAML(yaml interface{}) types.GomegaMatcher {
return &matchers.MatchYAMLMatcher{
YAMLToMatch: yaml,
}
}
//BeEmpty succeeds if actual is empty. Actual must be of type string, array, map, chan, or slice.
func BeEmpty() types.GomegaMatcher {
return &matchers.BeEmptyMatcher{}
}
//HaveLen succeeds if actual has the passed-in length. Actual must be of type string, array, map, chan, or slice.
func HaveLen(count int) types.GomegaMatcher {
return &matchers.HaveLenMatcher{
Count: count,
}
}
//HaveCap succeeds if actual has the passed-in capacity. Actual must be of type array, chan, or slice.
func HaveCap(count int) types.GomegaMatcher {
return &matchers.HaveCapMatcher{
Count: count,
}
}
//BeZero succeeds if actual is the zero value for its type or if actual is nil.
func BeZero() types.GomegaMatcher {
return &matchers.BeZeroMatcher{}
}
//ContainElement succeeds if actual contains the passed in element.
//By default ContainElement() uses Equal() to perform the match, however a
//matcher can be passed in instead:
// Expect([]string{"Foo", "FooBar"}).Should(ContainElement(ContainSubstring("Bar")))
//
//Actual must be an array, slice or map.
//For maps, ContainElement searches through the map's values.
func ContainElement(element interface{}) types.GomegaMatcher {
return &matchers.ContainElementMatcher{
Element: element,
}
}
//ConsistOf succeeds if actual contains precisely the elements passed into the matcher. The ordering of the elements does not matter.
//By default ConsistOf() uses Equal() to match the elements, however custom matchers can be passed in instead. Here are some examples:
//
// Expect([]string{"Foo", "FooBar"}).Should(ConsistOf("FooBar", "Foo"))
// Expect([]string{"Foo", "FooBar"}).Should(ConsistOf(ContainSubstring("Bar"), "Foo"))
// Expect([]string{"Foo", "FooBar"}).Should(ConsistOf(ContainSubstring("Foo"), ContainSubstring("Foo")))
//
//Actual must be an array, slice or map. For maps, ConsistOf matches against the map's values.
//
//You typically pass variadic arguments to ConsistOf (as in the examples above). However, if you need to pass in a slice you can provided that it
//is the only element passed in to ConsistOf:
//
// Expect([]string{"Foo", "FooBar"}).Should(ConsistOf([]string{"FooBar", "Foo"}))
//
//Note that Go's type system does not allow you to write this as ConsistOf([]string{"FooBar", "Foo"}...) as []string and []interface{} are different types - hence the need for this special rule.
func ConsistOf(elements ...interface{}) types.GomegaMatcher {
return &matchers.ConsistOfMatcher{
Elements: elements,
}
}
//HaveKey succeeds if actual is a map with the passed in key.
//By default HaveKey uses Equal() to perform the match, however a
//matcher can be passed in instead:
// Expect(map[string]string{"Foo": "Bar", "BazFoo": "Duck"}).Should(HaveKey(MatchRegexp(`.+Foo$`)))
func HaveKey(key interface{}) types.GomegaMatcher {
return &matchers.HaveKeyMatcher{
Key: key,
}
}
//HaveKeyWithValue succeeds if actual is a map with the passed in key and value.
//By default HaveKeyWithValue uses Equal() to perform the match, however a
//matcher can be passed in instead:
// Expect(map[string]string{"Foo": "Bar", "BazFoo": "Duck"}).Should(HaveKeyWithValue("Foo", "Bar"))
// Expect(map[string]string{"Foo": "Bar", "BazFoo": "Duck"}).Should(HaveKeyWithValue(MatchRegexp(`.+Foo$`), "Bar"))
func HaveKeyWithValue(key interface{}, value interface{}) types.GomegaMatcher {
return &matchers.HaveKeyWithValueMatcher{
Key: key,
Value: value,
}
}
//BeNumerically performs numerical assertions in a type-agnostic way.
//Actual and expected should be numbers, though the specific type of
//number is irrelevant (float32, float64, uint8, etc...).
//
//There are six, self-explanatory, supported comparators:
// Expect(1.0).Should(BeNumerically("==", 1))
// Expect(1.0).Should(BeNumerically("~", 0.999, 0.01))
// Expect(1.0).Should(BeNumerically(">", 0.9))
// Expect(1.0).Should(BeNumerically(">=", 1.0))
// Expect(1.0).Should(BeNumerically("<", 3))
// Expect(1.0).Should(BeNumerically("<=", 1.0))
func BeNumerically(comparator string, compareTo ...interface{}) types.GomegaMatcher {
return &matchers.BeNumericallyMatcher{
Comparator: comparator,
CompareTo: compareTo,
}
}
//BeTemporally compares time.Time's like BeNumerically
//Actual and expected must be time.Time. The comparators are the same as for BeNumerically
// Expect(time.Now()).Should(BeTemporally(">", time.Time{}))
// Expect(time.Now()).Should(BeTemporally("~", time.Now(), time.Second))
func BeTemporally(comparator string, compareTo time.Time, threshold ...time.Duration) types.GomegaMatcher {
return &matchers.BeTemporallyMatcher{
Comparator: comparator,
CompareTo: compareTo,
Threshold: threshold,
}
}
//BeAssignableToTypeOf succeeds if actual is assignable to the type of expected.
//It will return an error when one of the values is nil.
// Expect(0).Should(BeAssignableToTypeOf(0)) // Same values
// Expect(5).Should(BeAssignableToTypeOf(-1)) // different values same type
// Expect("foo").Should(BeAssignableToTypeOf("bar")) // different values same type
// Expect(struct{ Foo string }{}).Should(BeAssignableToTypeOf(struct{ Foo string }{}))
func BeAssignableToTypeOf(expected interface{}) types.GomegaMatcher {
return &matchers.AssignableToTypeOfMatcher{
Expected: expected,
}
}
//Panic succeeds if actual is a function that, when invoked, panics.
//Actual must be a function that takes no arguments and returns no results.
func Panic() types.GomegaMatcher {
return &matchers.PanicMatcher{}
}
//BeAnExistingFile succeeds if a file exists.
//Actual must be a string representing the abs path to the file being checked.
func BeAnExistingFile() types.GomegaMatcher {
return &matchers.BeAnExistingFileMatcher{}
}
//BeARegularFile succeeds if a file exists and is a regular file.
//Actual must be a string representing the abs path to the file being checked.
func BeARegularFile() types.GomegaMatcher {
return &matchers.BeARegularFileMatcher{}
}
//BeADirectory succeeds if a file exists and is a directory.
//Actual must be a string representing the abs path to the file being checked.
func BeADirectory() types.GomegaMatcher {
return &matchers.BeADirectoryMatcher{}
}
//And succeeds only if all of the given matchers succeed.
//The matchers are tried in order, and will fail-fast if one doesn't succeed.
// Expect("hi").To(And(HaveLen(2), Equal("hi"))
//
//And(), Or(), Not() and WithTransform() allow matchers to be composed into complex expressions.
func And(ms ...types.GomegaMatcher) types.GomegaMatcher {
return &matchers.AndMatcher{Matchers: ms}
}
//SatisfyAll is an alias for And().
// Expect("hi").Should(SatisfyAll(HaveLen(2), Equal("hi")))
func SatisfyAll(matchers ...types.GomegaMatcher) types.GomegaMatcher {
return And(matchers...)
}
//Or succeeds if any of the given matchers succeed.
//The matchers are tried in order and will return immediately upon the first successful match.
// Expect("hi").To(Or(HaveLen(3), HaveLen(2))
//
//And(), Or(), Not() and WithTransform() allow matchers to be composed into complex expressions.
func Or(ms ...types.GomegaMatcher) types.GomegaMatcher {
return &matchers.OrMatcher{Matchers: ms}
}
//SatisfyAny is an alias for Or().
// Expect("hi").SatisfyAny(Or(HaveLen(3), HaveLen(2))
func SatisfyAny(matchers ...types.GomegaMatcher) types.GomegaMatcher {
return Or(matchers...)
}
//Not negates the given matcher; it succeeds if the given matcher fails.
// Expect(1).To(Not(Equal(2))
//
//And(), Or(), Not() and WithTransform() allow matchers to be composed into complex expressions.
func Not(matcher types.GomegaMatcher) types.GomegaMatcher {
return &matchers.NotMatcher{Matcher: matcher}
}
//WithTransform applies the `transform` to the actual value and matches it against `matcher`.
//The given transform must be a function of one parameter that returns one value.
// var plus1 = func(i int) int { return i + 1 }
// Expect(1).To(WithTransform(plus1, Equal(2))
//
//And(), Or(), Not() and WithTransform() allow matchers to be composed into complex expressions.
func WithTransform(transform interface{}, matcher types.GomegaMatcher) types.GomegaMatcher {
return matchers.NewWithTransformMatcher(transform, matcher)
}

63
vendor/github.com/onsi/gomega/matchers/and.go generated vendored Normal file
View File

@ -0,0 +1,63 @@
package matchers
import (
"fmt"
"github.com/onsi/gomega/format"
"github.com/onsi/gomega/internal/oraclematcher"
"github.com/onsi/gomega/types"
)
type AndMatcher struct {
Matchers []types.GomegaMatcher
// state
firstFailedMatcher types.GomegaMatcher
}
func (m *AndMatcher) Match(actual interface{}) (success bool, err error) {
m.firstFailedMatcher = nil
for _, matcher := range m.Matchers {
success, err := matcher.Match(actual)
if !success || err != nil {
m.firstFailedMatcher = matcher
return false, err
}
}
return true, nil
}
func (m *AndMatcher) FailureMessage(actual interface{}) (message string) {
return m.firstFailedMatcher.FailureMessage(actual)
}
func (m *AndMatcher) NegatedFailureMessage(actual interface{}) (message string) {
// not the most beautiful list of matchers, but not bad either...
return format.Message(actual, fmt.Sprintf("To not satisfy all of these matchers: %s", m.Matchers))
}
func (m *AndMatcher) MatchMayChangeInTheFuture(actual interface{}) bool {
/*
Example with 3 matchers: A, B, C
Match evaluates them: T, F, <?> => F
So match is currently F, what should MatchMayChangeInTheFuture() return?
Seems like it only depends on B, since currently B MUST change to allow the result to become T
Match eval: T, T, T => T
So match is currently T, what should MatchMayChangeInTheFuture() return?
Seems to depend on ANY of them being able to change to F.
*/
if m.firstFailedMatcher == nil {
// so all matchers succeeded.. Any one of them changing would change the result.
for _, matcher := range m.Matchers {
if oraclematcher.MatchMayChangeInTheFuture(matcher, actual) {
return true
}
}
return false // none of were going to change
}
// one of the matchers failed.. it must be able to change in order to affect the result
return oraclematcher.MatchMayChangeInTheFuture(m.firstFailedMatcher, actual)
}

View File

@ -0,0 +1,35 @@
package matchers
import (
"fmt"
"reflect"
"github.com/onsi/gomega/format"
)
type AssignableToTypeOfMatcher struct {
Expected interface{}
}
func (matcher *AssignableToTypeOfMatcher) Match(actual interface{}) (success bool, err error) {
if actual == nil && matcher.Expected == nil {
return false, fmt.Errorf("Refusing to compare <nil> to <nil>.\nBe explicit and use BeNil() instead. This is to avoid mistakes where both sides of an assertion are erroneously uninitialized.")
} else if matcher.Expected == nil {
return false, fmt.Errorf("Refusing to compare type to <nil>.\nBe explicit and use BeNil() instead. This is to avoid mistakes where both sides of an assertion are erroneously uninitialized.")
} else if actual == nil {
return false, nil
}
actualType := reflect.TypeOf(actual)
expectedType := reflect.TypeOf(matcher.Expected)
return actualType.AssignableTo(expectedType), nil
}
func (matcher *AssignableToTypeOfMatcher) FailureMessage(actual interface{}) string {
return format.Message(actual, fmt.Sprintf("to be assignable to the type: %T", matcher.Expected))
}
func (matcher *AssignableToTypeOfMatcher) NegatedFailureMessage(actual interface{}) string {
return format.Message(actual, fmt.Sprintf("not to be assignable to the type: %T", matcher.Expected))
}

View File

@ -0,0 +1,14 @@
package matchers
import (
"encoding/xml"
"strings"
)
type attributesSlice []xml.Attr
func (attrs attributesSlice) Len() int { return len(attrs) }
func (attrs attributesSlice) Less(i, j int) bool {
return strings.Compare(attrs[i].Name.Local, attrs[j].Name.Local) == -1
}
func (attrs attributesSlice) Swap(i, j int) { attrs[i], attrs[j] = attrs[j], attrs[i] }

View File

@ -0,0 +1,54 @@
package matchers
import (
"fmt"
"os"
"github.com/onsi/gomega/format"
)
type notADirectoryError struct {
os.FileInfo
}
func (t notADirectoryError) Error() string {
fileInfo := os.FileInfo(t)
switch {
case fileInfo.Mode().IsRegular():
return "file is a regular file"
default:
return fmt.Sprintf("file mode is: %s", fileInfo.Mode().String())
}
}
type BeADirectoryMatcher struct {
expected interface{}
err error
}
func (matcher *BeADirectoryMatcher) Match(actual interface{}) (success bool, err error) {
actualFilename, ok := actual.(string)
if !ok {
return false, fmt.Errorf("BeADirectoryMatcher matcher expects a file path")
}
fileInfo, err := os.Stat(actualFilename)
if err != nil {
matcher.err = err
return false, nil
}
if !fileInfo.Mode().IsDir() {
matcher.err = notADirectoryError{fileInfo}
return false, nil
}
return true, nil
}
func (matcher *BeADirectoryMatcher) FailureMessage(actual interface{}) (message string) {
return format.Message(actual, fmt.Sprintf("to be a directory: %s", matcher.err))
}
func (matcher *BeADirectoryMatcher) NegatedFailureMessage(actual interface{}) (message string) {
return format.Message(actual, fmt.Sprintf("not be a directory"))
}

View File

@ -0,0 +1,54 @@
package matchers
import (
"fmt"
"os"
"github.com/onsi/gomega/format"
)
type notARegularFileError struct {
os.FileInfo
}
func (t notARegularFileError) Error() string {
fileInfo := os.FileInfo(t)
switch {
case fileInfo.IsDir():
return "file is a directory"
default:
return fmt.Sprintf("file mode is: %s", fileInfo.Mode().String())
}
}
type BeARegularFileMatcher struct {
expected interface{}
err error
}
func (matcher *BeARegularFileMatcher) Match(actual interface{}) (success bool, err error) {
actualFilename, ok := actual.(string)
if !ok {
return false, fmt.Errorf("BeARegularFileMatcher matcher expects a file path")
}
fileInfo, err := os.Stat(actualFilename)
if err != nil {
matcher.err = err
return false, nil
}
if !fileInfo.Mode().IsRegular() {
matcher.err = notARegularFileError{fileInfo}
return false, nil
}
return true, nil
}
func (matcher *BeARegularFileMatcher) FailureMessage(actual interface{}) (message string) {
return format.Message(actual, fmt.Sprintf("to be a regular file: %s", matcher.err))
}
func (matcher *BeARegularFileMatcher) NegatedFailureMessage(actual interface{}) (message string) {
return format.Message(actual, fmt.Sprintf("not be a regular file"))
}

View File

@ -0,0 +1,38 @@
package matchers
import (
"fmt"
"os"
"github.com/onsi/gomega/format"
)
type BeAnExistingFileMatcher struct {
expected interface{}
}
func (matcher *BeAnExistingFileMatcher) Match(actual interface{}) (success bool, err error) {
actualFilename, ok := actual.(string)
if !ok {
return false, fmt.Errorf("BeAnExistingFileMatcher matcher expects a file path")
}
if _, err = os.Stat(actualFilename); err != nil {
switch {
case os.IsNotExist(err):
return false, nil
default:
return false, err
}
}
return true, nil
}
func (matcher *BeAnExistingFileMatcher) FailureMessage(actual interface{}) (message string) {
return format.Message(actual, fmt.Sprintf("to exist"))
}
func (matcher *BeAnExistingFileMatcher) NegatedFailureMessage(actual interface{}) (message string) {
return format.Message(actual, fmt.Sprintf("not to exist"))
}

View File

@ -0,0 +1,46 @@
package matchers
import (
"fmt"
"reflect"
"github.com/onsi/gomega/format"
)
type BeClosedMatcher struct {
}
func (matcher *BeClosedMatcher) Match(actual interface{}) (success bool, err error) {
if !isChan(actual) {
return false, fmt.Errorf("BeClosed matcher expects a channel. Got:\n%s", format.Object(actual, 1))
}
channelType := reflect.TypeOf(actual)
channelValue := reflect.ValueOf(actual)
if channelType.ChanDir() == reflect.SendDir {
return false, fmt.Errorf("BeClosed matcher cannot determine if a send-only channel is closed or open. Got:\n%s", format.Object(actual, 1))
}
winnerIndex, _, open := reflect.Select([]reflect.SelectCase{
{Dir: reflect.SelectRecv, Chan: channelValue},
{Dir: reflect.SelectDefault},
})
var closed bool
if winnerIndex == 0 {
closed = !open
} else if winnerIndex == 1 {
closed = false
}
return closed, nil
}
func (matcher *BeClosedMatcher) FailureMessage(actual interface{}) (message string) {
return format.Message(actual, "to be closed")
}
func (matcher *BeClosedMatcher) NegatedFailureMessage(actual interface{}) (message string) {
return format.Message(actual, "to be open")
}

View File

@ -0,0 +1,27 @@
package matchers
import (
"fmt"
"github.com/onsi/gomega/format"
)
type BeEmptyMatcher struct {
}
func (matcher *BeEmptyMatcher) Match(actual interface{}) (success bool, err error) {
length, ok := lengthOf(actual)
if !ok {
return false, fmt.Errorf("BeEmpty matcher expects a string/array/map/channel/slice. Got:\n%s", format.Object(actual, 1))
}
return length == 0, nil
}
func (matcher *BeEmptyMatcher) FailureMessage(actual interface{}) (message string) {
return format.Message(actual, "to be empty")
}
func (matcher *BeEmptyMatcher) NegatedFailureMessage(actual interface{}) (message string) {
return format.Message(actual, "not to be empty")
}

View File

@ -0,0 +1,34 @@
package matchers
import (
"fmt"
"reflect"
"github.com/onsi/gomega/format"
)
type BeEquivalentToMatcher struct {
Expected interface{}
}
func (matcher *BeEquivalentToMatcher) Match(actual interface{}) (success bool, err error) {
if actual == nil && matcher.Expected == nil {
return false, fmt.Errorf("Both actual and expected must not be nil.")
}
convertedActual := actual
if actual != nil && matcher.Expected != nil && reflect.TypeOf(actual).ConvertibleTo(reflect.TypeOf(matcher.Expected)) {
convertedActual = reflect.ValueOf(actual).Convert(reflect.TypeOf(matcher.Expected)).Interface()
}
return reflect.DeepEqual(convertedActual, matcher.Expected), nil
}
func (matcher *BeEquivalentToMatcher) FailureMessage(actual interface{}) (message string) {
return format.Message(actual, "to be equivalent to", matcher.Expected)
}
func (matcher *BeEquivalentToMatcher) NegatedFailureMessage(actual interface{}) (message string) {
return format.Message(actual, "not to be equivalent to", matcher.Expected)
}

View File

@ -0,0 +1,26 @@
package matchers
import (
"fmt"
"github.com/onsi/gomega/format"
)
type BeFalseMatcher struct {
}
func (matcher *BeFalseMatcher) Match(actual interface{}) (success bool, err error) {
if !isBool(actual) {
return false, fmt.Errorf("Expected a boolean. Got:\n%s", format.Object(actual, 1))
}
return actual == false, nil
}
func (matcher *BeFalseMatcher) FailureMessage(actual interface{}) (message string) {
return format.Message(actual, "to be false")
}
func (matcher *BeFalseMatcher) NegatedFailureMessage(actual interface{}) (message string) {
return format.Message(actual, "not to be false")
}

View File

@ -0,0 +1,37 @@
package matchers
import (
"fmt"
"runtime"
"github.com/onsi/gomega/format"
)
type BeIdenticalToMatcher struct {
Expected interface{}
}
func (matcher *BeIdenticalToMatcher) Match(actual interface{}) (success bool, matchErr error) {
if actual == nil && matcher.Expected == nil {
return false, fmt.Errorf("Refusing to compare <nil> to <nil>.\nBe explicit and use BeNil() instead. This is to avoid mistakes where both sides of an assertion are erroneously uninitialized.")
}
defer func() {
if r := recover(); r != nil {
if _, ok := r.(runtime.Error); ok {
success = false
matchErr = nil
}
}
}()
return actual == matcher.Expected, nil
}
func (matcher *BeIdenticalToMatcher) FailureMessage(actual interface{}) string {
return format.Message(actual, "to be identical to", matcher.Expected)
}
func (matcher *BeIdenticalToMatcher) NegatedFailureMessage(actual interface{}) string {
return format.Message(actual, "not to be identical to", matcher.Expected)
}

View File

@ -0,0 +1,18 @@
package matchers
import "github.com/onsi/gomega/format"
type BeNilMatcher struct {
}
func (matcher *BeNilMatcher) Match(actual interface{}) (success bool, err error) {
return isNil(actual), nil
}
func (matcher *BeNilMatcher) FailureMessage(actual interface{}) (message string) {
return format.Message(actual, "to be nil")
}
func (matcher *BeNilMatcher) NegatedFailureMessage(actual interface{}) (message string) {
return format.Message(actual, "not to be nil")
}

View File

@ -0,0 +1,132 @@
package matchers
import (
"fmt"
"math"
"github.com/onsi/gomega/format"
)
type BeNumericallyMatcher struct {
Comparator string
CompareTo []interface{}
}
func (matcher *BeNumericallyMatcher) FailureMessage(actual interface{}) (message string) {
return matcher.FormatFailureMessage(actual, false)
}
func (matcher *BeNumericallyMatcher) NegatedFailureMessage(actual interface{}) (message string) {
return matcher.FormatFailureMessage(actual, true)
}
func (matcher *BeNumericallyMatcher) FormatFailureMessage(actual interface{}, negated bool) (message string) {
if len(matcher.CompareTo) == 1 {
message = fmt.Sprintf("to be %s", matcher.Comparator)
} else {
message = fmt.Sprintf("to be within %v of %s", matcher.CompareTo[1], matcher.Comparator)
}
if negated {
message = "not " + message
}
return format.Message(actual, message, matcher.CompareTo[0])
}
func (matcher *BeNumericallyMatcher) Match(actual interface{}) (success bool, err error) {
if len(matcher.CompareTo) == 0 || len(matcher.CompareTo) > 2 {
return false, fmt.Errorf("BeNumerically requires 1 or 2 CompareTo arguments. Got:\n%s", format.Object(matcher.CompareTo, 1))
}
if !isNumber(actual) {
return false, fmt.Errorf("Expected a number. Got:\n%s", format.Object(actual, 1))
}
if !isNumber(matcher.CompareTo[0]) {
return false, fmt.Errorf("Expected a number. Got:\n%s", format.Object(matcher.CompareTo[0], 1))
}
if len(matcher.CompareTo) == 2 && !isNumber(matcher.CompareTo[1]) {
return false, fmt.Errorf("Expected a number. Got:\n%s", format.Object(matcher.CompareTo[0], 1))
}
switch matcher.Comparator {
case "==", "~", ">", ">=", "<", "<=":
default:
return false, fmt.Errorf("Unknown comparator: %s", matcher.Comparator)
}
if isFloat(actual) || isFloat(matcher.CompareTo[0]) {
var secondOperand float64 = 1e-8
if len(matcher.CompareTo) == 2 {
secondOperand = toFloat(matcher.CompareTo[1])
}
success = matcher.matchFloats(toFloat(actual), toFloat(matcher.CompareTo[0]), secondOperand)
} else if isInteger(actual) {
var secondOperand int64 = 0
if len(matcher.CompareTo) == 2 {
secondOperand = toInteger(matcher.CompareTo[1])
}
success = matcher.matchIntegers(toInteger(actual), toInteger(matcher.CompareTo[0]), secondOperand)
} else if isUnsignedInteger(actual) {
var secondOperand uint64 = 0
if len(matcher.CompareTo) == 2 {
secondOperand = toUnsignedInteger(matcher.CompareTo[1])
}
success = matcher.matchUnsignedIntegers(toUnsignedInteger(actual), toUnsignedInteger(matcher.CompareTo[0]), secondOperand)
} else {
return false, fmt.Errorf("Failed to compare:\n%s\n%s:\n%s", format.Object(actual, 1), matcher.Comparator, format.Object(matcher.CompareTo[0], 1))
}
return success, nil
}
func (matcher *BeNumericallyMatcher) matchIntegers(actual, compareTo, threshold int64) (success bool) {
switch matcher.Comparator {
case "==", "~":
diff := actual - compareTo
return -threshold <= diff && diff <= threshold
case ">":
return (actual > compareTo)
case ">=":
return (actual >= compareTo)
case "<":
return (actual < compareTo)
case "<=":
return (actual <= compareTo)
}
return false
}
func (matcher *BeNumericallyMatcher) matchUnsignedIntegers(actual, compareTo, threshold uint64) (success bool) {
switch matcher.Comparator {
case "==", "~":
if actual < compareTo {
actual, compareTo = compareTo, actual
}
return actual-compareTo <= threshold
case ">":
return (actual > compareTo)
case ">=":
return (actual >= compareTo)
case "<":
return (actual < compareTo)
case "<=":
return (actual <= compareTo)
}
return false
}
func (matcher *BeNumericallyMatcher) matchFloats(actual, compareTo, threshold float64) (success bool) {
switch matcher.Comparator {
case "~":
return math.Abs(actual-compareTo) <= threshold
case "==":
return (actual == compareTo)
case ">":
return (actual > compareTo)
case ">=":
return (actual >= compareTo)
case "<":
return (actual < compareTo)
case "<=":
return (actual <= compareTo)
}
return false
}

View File

@ -0,0 +1,71 @@
package matchers
import (
"fmt"
"reflect"
"github.com/onsi/gomega/format"
)
type BeSentMatcher struct {
Arg interface{}
channelClosed bool
}
func (matcher *BeSentMatcher) Match(actual interface{}) (success bool, err error) {
if !isChan(actual) {
return false, fmt.Errorf("BeSent expects a channel. Got:\n%s", format.Object(actual, 1))
}
channelType := reflect.TypeOf(actual)
channelValue := reflect.ValueOf(actual)
if channelType.ChanDir() == reflect.RecvDir {
return false, fmt.Errorf("BeSent matcher cannot be passed a receive-only channel. Got:\n%s", format.Object(actual, 1))
}
argType := reflect.TypeOf(matcher.Arg)
assignable := argType.AssignableTo(channelType.Elem())
if !assignable {
return false, fmt.Errorf("Cannot pass:\n%s to the channel:\n%s\nThe types don't match.", format.Object(matcher.Arg, 1), format.Object(actual, 1))
}
argValue := reflect.ValueOf(matcher.Arg)
defer func() {
if e := recover(); e != nil {
success = false
err = fmt.Errorf("Cannot send to a closed channel")
matcher.channelClosed = true
}
}()
winnerIndex, _, _ := reflect.Select([]reflect.SelectCase{
{Dir: reflect.SelectSend, Chan: channelValue, Send: argValue},
{Dir: reflect.SelectDefault},
})
var didSend bool
if winnerIndex == 0 {
didSend = true
}
return didSend, nil
}
func (matcher *BeSentMatcher) FailureMessage(actual interface{}) (message string) {
return format.Message(actual, "to send:", matcher.Arg)
}
func (matcher *BeSentMatcher) NegatedFailureMessage(actual interface{}) (message string) {
return format.Message(actual, "not to send:", matcher.Arg)
}
func (matcher *BeSentMatcher) MatchMayChangeInTheFuture(actual interface{}) bool {
if !isChan(actual) {
return false
}
return !matcher.channelClosed
}

View File

@ -0,0 +1,66 @@
package matchers
import (
"fmt"
"time"
"github.com/onsi/gomega/format"
)
type BeTemporallyMatcher struct {
Comparator string
CompareTo time.Time
Threshold []time.Duration
}
func (matcher *BeTemporallyMatcher) FailureMessage(actual interface{}) (message string) {
return format.Message(actual, fmt.Sprintf("to be %s", matcher.Comparator), matcher.CompareTo)
}
func (matcher *BeTemporallyMatcher) NegatedFailureMessage(actual interface{}) (message string) {
return format.Message(actual, fmt.Sprintf("not to be %s", matcher.Comparator), matcher.CompareTo)
}
func (matcher *BeTemporallyMatcher) Match(actual interface{}) (bool, error) {
// predicate to test for time.Time type
isTime := func(t interface{}) bool {
_, ok := t.(time.Time)
return ok
}
if !isTime(actual) {
return false, fmt.Errorf("Expected a time.Time. Got:\n%s", format.Object(actual, 1))
}
switch matcher.Comparator {
case "==", "~", ">", ">=", "<", "<=":
default:
return false, fmt.Errorf("Unknown comparator: %s", matcher.Comparator)
}
var threshold = time.Millisecond
if len(matcher.Threshold) == 1 {
threshold = matcher.Threshold[0]
}
return matcher.matchTimes(actual.(time.Time), matcher.CompareTo, threshold), nil
}
func (matcher *BeTemporallyMatcher) matchTimes(actual, compareTo time.Time, threshold time.Duration) (success bool) {
switch matcher.Comparator {
case "==":
return actual.Equal(compareTo)
case "~":
diff := actual.Sub(compareTo)
return -threshold <= diff && diff <= threshold
case ">":
return actual.After(compareTo)
case ">=":
return !actual.Before(compareTo)
case "<":
return actual.Before(compareTo)
case "<=":
return !actual.After(compareTo)
}
return false
}

View File

@ -0,0 +1,26 @@
package matchers
import (
"fmt"
"github.com/onsi/gomega/format"
)
type BeTrueMatcher struct {
}
func (matcher *BeTrueMatcher) Match(actual interface{}) (success bool, err error) {
if !isBool(actual) {
return false, fmt.Errorf("Expected a boolean. Got:\n%s", format.Object(actual, 1))
}
return actual.(bool), nil
}
func (matcher *BeTrueMatcher) FailureMessage(actual interface{}) (message string) {
return format.Message(actual, "to be true")
}
func (matcher *BeTrueMatcher) NegatedFailureMessage(actual interface{}) (message string) {
return format.Message(actual, "not to be true")
}

View File

@ -0,0 +1,28 @@
package matchers
import (
"reflect"
"github.com/onsi/gomega/format"
)
type BeZeroMatcher struct {
}
func (matcher *BeZeroMatcher) Match(actual interface{}) (success bool, err error) {
if actual == nil {
return true, nil
}
zeroValue := reflect.Zero(reflect.TypeOf(actual)).Interface()
return reflect.DeepEqual(zeroValue, actual), nil
}
func (matcher *BeZeroMatcher) FailureMessage(actual interface{}) (message string) {
return format.Message(actual, "to be zero-valued")
}
func (matcher *BeZeroMatcher) NegatedFailureMessage(actual interface{}) (message string) {
return format.Message(actual, "not to be zero-valued")
}

80
vendor/github.com/onsi/gomega/matchers/consist_of.go generated vendored Normal file
View File

@ -0,0 +1,80 @@
package matchers
import (
"fmt"
"reflect"
"github.com/onsi/gomega/format"
"github.com/onsi/gomega/matchers/support/goraph/bipartitegraph"
)
type ConsistOfMatcher struct {
Elements []interface{}
}
func (matcher *ConsistOfMatcher) Match(actual interface{}) (success bool, err error) {
if !isArrayOrSlice(actual) && !isMap(actual) {
return false, fmt.Errorf("ConsistOf matcher expects an array/slice/map. Got:\n%s", format.Object(actual, 1))
}
elements := matcher.Elements
if len(matcher.Elements) == 1 && isArrayOrSlice(matcher.Elements[0]) {
elements = []interface{}{}
value := reflect.ValueOf(matcher.Elements[0])
for i := 0; i < value.Len(); i++ {
elements = append(elements, value.Index(i).Interface())
}
}
matchers := []interface{}{}
for _, element := range elements {
matcher, isMatcher := element.(omegaMatcher)
if !isMatcher {
matcher = &EqualMatcher{Expected: element}
}
matchers = append(matchers, matcher)
}
values := matcher.valuesOf(actual)
if len(values) != len(matchers) {
return false, nil
}
neighbours := func(v, m interface{}) (bool, error) {
match, err := m.(omegaMatcher).Match(v)
return match && err == nil, nil
}
bipartiteGraph, err := bipartitegraph.NewBipartiteGraph(values, matchers, neighbours)
if err != nil {
return false, err
}
return len(bipartiteGraph.LargestMatching()) == len(values), nil
}
func (matcher *ConsistOfMatcher) valuesOf(actual interface{}) []interface{} {
value := reflect.ValueOf(actual)
values := []interface{}{}
if isMap(actual) {
keys := value.MapKeys()
for i := 0; i < value.Len(); i++ {
values = append(values, value.MapIndex(keys[i]).Interface())
}
} else {
for i := 0; i < value.Len(); i++ {
values = append(values, value.Index(i).Interface())
}
}
return values
}
func (matcher *ConsistOfMatcher) FailureMessage(actual interface{}) (message string) {
return format.Message(actual, "to consist of", matcher.Elements)
}
func (matcher *ConsistOfMatcher) NegatedFailureMessage(actual interface{}) (message string) {
return format.Message(actual, "not to consist of", matcher.Elements)
}

View File

@ -0,0 +1,56 @@
package matchers
import (
"fmt"
"reflect"
"github.com/onsi/gomega/format"
)
type ContainElementMatcher struct {
Element interface{}
}
func (matcher *ContainElementMatcher) Match(actual interface{}) (success bool, err error) {
if !isArrayOrSlice(actual) && !isMap(actual) {
return false, fmt.Errorf("ContainElement matcher expects an array/slice/map. Got:\n%s", format.Object(actual, 1))
}
elemMatcher, elementIsMatcher := matcher.Element.(omegaMatcher)
if !elementIsMatcher {
elemMatcher = &EqualMatcher{Expected: matcher.Element}
}
value := reflect.ValueOf(actual)
var keys []reflect.Value
if isMap(actual) {
keys = value.MapKeys()
}
var lastError error
for i := 0; i < value.Len(); i++ {
var success bool
var err error
if isMap(actual) {
success, err = elemMatcher.Match(value.MapIndex(keys[i]).Interface())
} else {
success, err = elemMatcher.Match(value.Index(i).Interface())
}
if err != nil {
lastError = err
continue
}
if success {
return true, nil
}
}
return false, lastError
}
func (matcher *ContainElementMatcher) FailureMessage(actual interface{}) (message string) {
return format.Message(actual, "to contain element matching", matcher.Element)
}
func (matcher *ContainElementMatcher) NegatedFailureMessage(actual interface{}) (message string) {
return format.Message(actual, "not to contain element matching", matcher.Element)
}

View File

@ -0,0 +1,38 @@
package matchers
import (
"fmt"
"strings"
"github.com/onsi/gomega/format"
)
type ContainSubstringMatcher struct {
Substr string
Args []interface{}
}
func (matcher *ContainSubstringMatcher) Match(actual interface{}) (success bool, err error) {
actualString, ok := toString(actual)
if !ok {
return false, fmt.Errorf("ContainSubstring matcher requires a string or stringer. Got:\n%s", format.Object(actual, 1))
}
return strings.Contains(actualString, matcher.stringToMatch()), nil
}
func (matcher *ContainSubstringMatcher) stringToMatch() string {
stringToMatch := matcher.Substr
if len(matcher.Args) > 0 {
stringToMatch = fmt.Sprintf(matcher.Substr, matcher.Args...)
}
return stringToMatch
}
func (matcher *ContainSubstringMatcher) FailureMessage(actual interface{}) (message string) {
return format.Message(actual, "to contain substring", matcher.stringToMatch())
}
func (matcher *ContainSubstringMatcher) NegatedFailureMessage(actual interface{}) (message string) {
return format.Message(actual, "not to contain substring", matcher.stringToMatch())
}

View File

@ -0,0 +1,42 @@
package matchers
import (
"bytes"
"fmt"
"reflect"
"github.com/onsi/gomega/format"
)
type EqualMatcher struct {
Expected interface{}
}
func (matcher *EqualMatcher) Match(actual interface{}) (success bool, err error) {
if actual == nil && matcher.Expected == nil {
return false, fmt.Errorf("Refusing to compare <nil> to <nil>.\nBe explicit and use BeNil() instead. This is to avoid mistakes where both sides of an assertion are erroneously uninitialized.")
}
// Shortcut for byte slices.
// Comparing long byte slices with reflect.DeepEqual is very slow,
// so use bytes.Equal if actual and expected are both byte slices.
if actualByteSlice, ok := actual.([]byte); ok {
if expectedByteSlice, ok := matcher.Expected.([]byte); ok {
return bytes.Equal(actualByteSlice, expectedByteSlice), nil
}
}
return reflect.DeepEqual(actual, matcher.Expected), nil
}
func (matcher *EqualMatcher) FailureMessage(actual interface{}) (message string) {
actualString, actualOK := actual.(string)
expectedString, expectedOK := matcher.Expected.(string)
if actualOK && expectedOK {
return format.MessageWithDiff(actualString, "to equal", expectedString)
}
return format.Message(actual, "to equal", matcher.Expected)
}
func (matcher *EqualMatcher) NegatedFailureMessage(actual interface{}) (message string) {
return format.Message(actual, "not to equal", matcher.Expected)
}

View File

@ -0,0 +1,28 @@
package matchers
import (
"fmt"
"github.com/onsi/gomega/format"
)
type HaveCapMatcher struct {
Count int
}
func (matcher *HaveCapMatcher) Match(actual interface{}) (success bool, err error) {
length, ok := capOf(actual)
if !ok {
return false, fmt.Errorf("HaveCap matcher expects a array/channel/slice. Got:\n%s", format.Object(actual, 1))
}
return length == matcher.Count, nil
}
func (matcher *HaveCapMatcher) FailureMessage(actual interface{}) (message string) {
return fmt.Sprintf("Expected\n%s\nto have capacity %d", format.Object(actual, 1), matcher.Count)
}
func (matcher *HaveCapMatcher) NegatedFailureMessage(actual interface{}) (message string) {
return fmt.Sprintf("Expected\n%s\nnot to have capacity %d", format.Object(actual, 1), matcher.Count)
}

View File

@ -0,0 +1,54 @@
package matchers
import (
"fmt"
"reflect"
"github.com/onsi/gomega/format"
)
type HaveKeyMatcher struct {
Key interface{}
}
func (matcher *HaveKeyMatcher) Match(actual interface{}) (success bool, err error) {
if !isMap(actual) {
return false, fmt.Errorf("HaveKey matcher expects a map. Got:%s", format.Object(actual, 1))
}
keyMatcher, keyIsMatcher := matcher.Key.(omegaMatcher)
if !keyIsMatcher {
keyMatcher = &EqualMatcher{Expected: matcher.Key}
}
keys := reflect.ValueOf(actual).MapKeys()
for i := 0; i < len(keys); i++ {
success, err := keyMatcher.Match(keys[i].Interface())
if err != nil {
return false, fmt.Errorf("HaveKey's key matcher failed with:\n%s%s", format.Indent, err.Error())
}
if success {
return true, nil
}
}
return false, nil
}
func (matcher *HaveKeyMatcher) FailureMessage(actual interface{}) (message string) {
switch matcher.Key.(type) {
case omegaMatcher:
return format.Message(actual, "to have key matching", matcher.Key)
default:
return format.Message(actual, "to have key", matcher.Key)
}
}
func (matcher *HaveKeyMatcher) NegatedFailureMessage(actual interface{}) (message string) {
switch matcher.Key.(type) {
case omegaMatcher:
return format.Message(actual, "not to have key matching", matcher.Key)
default:
return format.Message(actual, "not to have key", matcher.Key)
}
}

View File

@ -0,0 +1,74 @@
package matchers
import (
"fmt"
"reflect"
"github.com/onsi/gomega/format"
)
type HaveKeyWithValueMatcher struct {
Key interface{}
Value interface{}
}
func (matcher *HaveKeyWithValueMatcher) Match(actual interface{}) (success bool, err error) {
if !isMap(actual) {
return false, fmt.Errorf("HaveKeyWithValue matcher expects a map. Got:%s", format.Object(actual, 1))
}
keyMatcher, keyIsMatcher := matcher.Key.(omegaMatcher)
if !keyIsMatcher {
keyMatcher = &EqualMatcher{Expected: matcher.Key}
}
valueMatcher, valueIsMatcher := matcher.Value.(omegaMatcher)
if !valueIsMatcher {
valueMatcher = &EqualMatcher{Expected: matcher.Value}
}
keys := reflect.ValueOf(actual).MapKeys()
for i := 0; i < len(keys); i++ {
success, err := keyMatcher.Match(keys[i].Interface())
if err != nil {
return false, fmt.Errorf("HaveKeyWithValue's key matcher failed with:\n%s%s", format.Indent, err.Error())
}
if success {
actualValue := reflect.ValueOf(actual).MapIndex(keys[i])
success, err := valueMatcher.Match(actualValue.Interface())
if err != nil {
return false, fmt.Errorf("HaveKeyWithValue's value matcher failed with:\n%s%s", format.Indent, err.Error())
}
return success, nil
}
}
return false, nil
}
func (matcher *HaveKeyWithValueMatcher) FailureMessage(actual interface{}) (message string) {
str := "to have {key: value}"
if _, ok := matcher.Key.(omegaMatcher); ok {
str += " matching"
} else if _, ok := matcher.Value.(omegaMatcher); ok {
str += " matching"
}
expect := make(map[interface{}]interface{}, 1)
expect[matcher.Key] = matcher.Value
return format.Message(actual, str, expect)
}
func (matcher *HaveKeyWithValueMatcher) NegatedFailureMessage(actual interface{}) (message string) {
kStr := "not to have key"
if _, ok := matcher.Key.(omegaMatcher); ok {
kStr = "not to have key matching"
}
vStr := "or that key's value not be"
if _, ok := matcher.Value.(omegaMatcher); ok {
vStr = "or to have that key's value not matching"
}
return format.Message(actual, kStr, matcher.Key, vStr, matcher.Value)
}

View File

@ -0,0 +1,28 @@
package matchers
import (
"fmt"
"github.com/onsi/gomega/format"
)
type HaveLenMatcher struct {
Count int
}
func (matcher *HaveLenMatcher) Match(actual interface{}) (success bool, err error) {
length, ok := lengthOf(actual)
if !ok {
return false, fmt.Errorf("HaveLen matcher expects a string/array/map/channel/slice. Got:\n%s", format.Object(actual, 1))
}
return length == matcher.Count, nil
}
func (matcher *HaveLenMatcher) FailureMessage(actual interface{}) (message string) {
return fmt.Sprintf("Expected\n%s\nto have length %d", format.Object(actual, 1), matcher.Count)
}
func (matcher *HaveLenMatcher) NegatedFailureMessage(actual interface{}) (message string) {
return fmt.Sprintf("Expected\n%s\nnot to have length %d", format.Object(actual, 1), matcher.Count)
}

View File

@ -0,0 +1,33 @@
package matchers
import (
"fmt"
"github.com/onsi/gomega/format"
)
type HaveOccurredMatcher struct {
}
func (matcher *HaveOccurredMatcher) Match(actual interface{}) (success bool, err error) {
// is purely nil?
if actual == nil {
return false, nil
}
// must be an 'error' type
if !isError(actual) {
return false, fmt.Errorf("Expected an error-type. Got:\n%s", format.Object(actual, 1))
}
// must be non-nil (or a pointer to a non-nil)
return !isNil(actual), nil
}
func (matcher *HaveOccurredMatcher) FailureMessage(actual interface{}) (message string) {
return fmt.Sprintf("Expected an error to have occurred. Got:\n%s", format.Object(actual, 1))
}
func (matcher *HaveOccurredMatcher) NegatedFailureMessage(actual interface{}) (message string) {
return fmt.Sprintf("Unexpected error:\n%s\n%s\n%s", format.Object(actual, 1), format.IndentString(actual.(error).Error(), 1), "occurred")
}

View File

@ -0,0 +1,36 @@
package matchers
import (
"fmt"
"github.com/onsi/gomega/format"
)
type HavePrefixMatcher struct {
Prefix string
Args []interface{}
}
func (matcher *HavePrefixMatcher) Match(actual interface{}) (success bool, err error) {
actualString, ok := toString(actual)
if !ok {
return false, fmt.Errorf("HavePrefix matcher requires a string or stringer. Got:\n%s", format.Object(actual, 1))
}
prefix := matcher.prefix()
return len(actualString) >= len(prefix) && actualString[0:len(prefix)] == prefix, nil
}
func (matcher *HavePrefixMatcher) prefix() string {
if len(matcher.Args) > 0 {
return fmt.Sprintf(matcher.Prefix, matcher.Args...)
}
return matcher.Prefix
}
func (matcher *HavePrefixMatcher) FailureMessage(actual interface{}) (message string) {
return format.Message(actual, "to have prefix", matcher.prefix())
}
func (matcher *HavePrefixMatcher) NegatedFailureMessage(actual interface{}) (message string) {
return format.Message(actual, "not to have prefix", matcher.prefix())
}

View File

@ -0,0 +1,36 @@
package matchers
import (
"fmt"
"github.com/onsi/gomega/format"
)
type HaveSuffixMatcher struct {
Suffix string
Args []interface{}
}
func (matcher *HaveSuffixMatcher) Match(actual interface{}) (success bool, err error) {
actualString, ok := toString(actual)
if !ok {
return false, fmt.Errorf("HaveSuffix matcher requires a string or stringer. Got:\n%s", format.Object(actual, 1))
}
suffix := matcher.suffix()
return len(actualString) >= len(suffix) && actualString[len(actualString)-len(suffix):] == suffix, nil
}
func (matcher *HaveSuffixMatcher) suffix() string {
if len(matcher.Args) > 0 {
return fmt.Sprintf(matcher.Suffix, matcher.Args...)
}
return matcher.Suffix
}
func (matcher *HaveSuffixMatcher) FailureMessage(actual interface{}) (message string) {
return format.Message(actual, "to have suffix", matcher.suffix())
}
func (matcher *HaveSuffixMatcher) NegatedFailureMessage(actual interface{}) (message string) {
return format.Message(actual, "not to have suffix", matcher.suffix())
}

View File

@ -0,0 +1,51 @@
package matchers
import (
"fmt"
"reflect"
"github.com/onsi/gomega/format"
)
type MatchErrorMatcher struct {
Expected interface{}
}
func (matcher *MatchErrorMatcher) Match(actual interface{}) (success bool, err error) {
if isNil(actual) {
return false, fmt.Errorf("Expected an error, got nil")
}
if !isError(actual) {
return false, fmt.Errorf("Expected an error. Got:\n%s", format.Object(actual, 1))
}
actualErr := actual.(error)
if isError(matcher.Expected) {
return reflect.DeepEqual(actualErr, matcher.Expected), nil
}
if isString(matcher.Expected) {
return actualErr.Error() == matcher.Expected, nil
}
var subMatcher omegaMatcher
var hasSubMatcher bool
if matcher.Expected != nil {
subMatcher, hasSubMatcher = (matcher.Expected).(omegaMatcher)
if hasSubMatcher {
return subMatcher.Match(actualErr.Error())
}
}
return false, fmt.Errorf("MatchError must be passed an error, string, or Matcher that can match on strings. Got:\n%s", format.Object(matcher.Expected, 1))
}
func (matcher *MatchErrorMatcher) FailureMessage(actual interface{}) (message string) {
return format.Message(actual, "to match error", matcher.Expected)
}
func (matcher *MatchErrorMatcher) NegatedFailureMessage(actual interface{}) (message string) {
return format.Message(actual, "not to match error", matcher.Expected)
}

View File

@ -0,0 +1,65 @@
package matchers
import (
"bytes"
"encoding/json"
"fmt"
"github.com/onsi/gomega/format"
)
type MatchJSONMatcher struct {
JSONToMatch interface{}
firstFailurePath []interface{}
}
func (matcher *MatchJSONMatcher) Match(actual interface{}) (success bool, err error) {
actualString, expectedString, err := matcher.prettyPrint(actual)
if err != nil {
return false, err
}
var aval interface{}
var eval interface{}
// this is guarded by prettyPrint
json.Unmarshal([]byte(actualString), &aval)
json.Unmarshal([]byte(expectedString), &eval)
var equal bool
equal, matcher.firstFailurePath = deepEqual(aval, eval)
return equal, nil
}
func (matcher *MatchJSONMatcher) FailureMessage(actual interface{}) (message string) {
actualString, expectedString, _ := matcher.prettyPrint(actual)
return formattedMessage(format.Message(actualString, "to match JSON of", expectedString), matcher.firstFailurePath)
}
func (matcher *MatchJSONMatcher) NegatedFailureMessage(actual interface{}) (message string) {
actualString, expectedString, _ := matcher.prettyPrint(actual)
return formattedMessage(format.Message(actualString, "not to match JSON of", expectedString), matcher.firstFailurePath)
}
func (matcher *MatchJSONMatcher) prettyPrint(actual interface{}) (actualFormatted, expectedFormatted string, err error) {
actualString, ok := toString(actual)
if !ok {
return "", "", fmt.Errorf("MatchJSONMatcher matcher requires a string, stringer, or []byte. Got actual:\n%s", format.Object(actual, 1))
}
expectedString, ok := toString(matcher.JSONToMatch)
if !ok {
return "", "", fmt.Errorf("MatchJSONMatcher matcher requires a string, stringer, or []byte. Got expected:\n%s", format.Object(matcher.JSONToMatch, 1))
}
abuf := new(bytes.Buffer)
ebuf := new(bytes.Buffer)
if err := json.Indent(abuf, []byte(actualString), "", " "); err != nil {
return "", "", fmt.Errorf("Actual '%s' should be valid JSON, but it is not.\nUnderlying error:%s", actualString, err)
}
if err := json.Indent(ebuf, []byte(expectedString), "", " "); err != nil {
return "", "", fmt.Errorf("Expected '%s' should be valid JSON, but it is not.\nUnderlying error:%s", expectedString, err)
}
return abuf.String(), ebuf.String(), nil
}

View File

@ -0,0 +1,43 @@
package matchers
import (
"fmt"
"regexp"
"github.com/onsi/gomega/format"
)
type MatchRegexpMatcher struct {
Regexp string
Args []interface{}
}
func (matcher *MatchRegexpMatcher) Match(actual interface{}) (success bool, err error) {
actualString, ok := toString(actual)
if !ok {
return false, fmt.Errorf("RegExp matcher requires a string or stringer.\nGot:%s", format.Object(actual, 1))
}
match, err := regexp.Match(matcher.regexp(), []byte(actualString))
if err != nil {
return false, fmt.Errorf("RegExp match failed to compile with error:\n\t%s", err.Error())
}
return match, nil
}
func (matcher *MatchRegexpMatcher) FailureMessage(actual interface{}) (message string) {
return format.Message(actual, "to match regular expression", matcher.regexp())
}
func (matcher *MatchRegexpMatcher) NegatedFailureMessage(actual interface{}) (message string) {
return format.Message(actual, "not to match regular expression", matcher.regexp())
}
func (matcher *MatchRegexpMatcher) regexp() string {
re := matcher.Regexp
if len(matcher.Args) > 0 {
re = fmt.Sprintf(matcher.Regexp, matcher.Args...)
}
return re
}

View File

@ -0,0 +1,134 @@
package matchers
import (
"bytes"
"encoding/xml"
"errors"
"fmt"
"io"
"reflect"
"sort"
"strings"
"github.com/onsi/gomega/format"
"golang.org/x/net/html/charset"
)
type MatchXMLMatcher struct {
XMLToMatch interface{}
}
func (matcher *MatchXMLMatcher) Match(actual interface{}) (success bool, err error) {
actualString, expectedString, err := matcher.formattedPrint(actual)
if err != nil {
return false, err
}
aval, err := parseXmlContent(actualString)
if err != nil {
return false, fmt.Errorf("Actual '%s' should be valid XML, but it is not.\nUnderlying error:%s", actualString, err)
}
eval, err := parseXmlContent(expectedString)
if err != nil {
return false, fmt.Errorf("Expected '%s' should be valid XML, but it is not.\nUnderlying error:%s", expectedString, err)
}
return reflect.DeepEqual(aval, eval), nil
}
func (matcher *MatchXMLMatcher) FailureMessage(actual interface{}) (message string) {
actualString, expectedString, _ := matcher.formattedPrint(actual)
return fmt.Sprintf("Expected\n%s\nto match XML of\n%s", actualString, expectedString)
}
func (matcher *MatchXMLMatcher) NegatedFailureMessage(actual interface{}) (message string) {
actualString, expectedString, _ := matcher.formattedPrint(actual)
return fmt.Sprintf("Expected\n%s\nnot to match XML of\n%s", actualString, expectedString)
}
func (matcher *MatchXMLMatcher) formattedPrint(actual interface{}) (actualString, expectedString string, err error) {
var ok bool
actualString, ok = toString(actual)
if !ok {
return "", "", fmt.Errorf("MatchXMLMatcher matcher requires a string, stringer, or []byte. Got actual:\n%s", format.Object(actual, 1))
}
expectedString, ok = toString(matcher.XMLToMatch)
if !ok {
return "", "", fmt.Errorf("MatchXMLMatcher matcher requires a string, stringer, or []byte. Got expected:\n%s", format.Object(matcher.XMLToMatch, 1))
}
return actualString, expectedString, nil
}
func parseXmlContent(content string) (*xmlNode, error) {
allNodes := []*xmlNode{}
dec := newXmlDecoder(strings.NewReader(content))
for {
tok, err := dec.Token()
if err != nil {
if err == io.EOF {
break
}
return nil, fmt.Errorf("failed to decode next token: %v", err) // untested section
}
lastNodeIndex := len(allNodes) - 1
var lastNode *xmlNode
if len(allNodes) > 0 {
lastNode = allNodes[lastNodeIndex]
} else {
lastNode = &xmlNode{}
}
switch tok := tok.(type) {
case xml.StartElement:
attrs := attributesSlice(tok.Attr)
sort.Sort(attrs)
allNodes = append(allNodes, &xmlNode{XMLName: tok.Name, XMLAttr: tok.Attr})
case xml.EndElement:
if len(allNodes) > 1 {
allNodes[lastNodeIndex-1].Nodes = append(allNodes[lastNodeIndex-1].Nodes, lastNode)
allNodes = allNodes[:lastNodeIndex]
}
case xml.CharData:
lastNode.Content = append(lastNode.Content, tok.Copy()...)
case xml.Comment:
lastNode.Comments = append(lastNode.Comments, tok.Copy()) // untested section
case xml.ProcInst:
lastNode.ProcInsts = append(lastNode.ProcInsts, tok.Copy())
}
}
if len(allNodes) == 0 {
return nil, errors.New("found no nodes")
}
firstNode := allNodes[0]
trimParentNodesContentSpaces(firstNode)
return firstNode, nil
}
func newXmlDecoder(reader io.Reader) *xml.Decoder {
dec := xml.NewDecoder(reader)
dec.CharsetReader = charset.NewReaderLabel
return dec
}
func trimParentNodesContentSpaces(node *xmlNode) {
if len(node.Nodes) > 0 {
node.Content = bytes.TrimSpace(node.Content)
for _, childNode := range node.Nodes {
trimParentNodesContentSpaces(childNode)
}
}
}
type xmlNode struct {
XMLName xml.Name
Comments []xml.Comment
ProcInsts []xml.ProcInst
XMLAttr []xml.Attr
Content []byte
Nodes []*xmlNode
}

View File

@ -0,0 +1,76 @@
package matchers
import (
"fmt"
"strings"
"github.com/onsi/gomega/format"
"gopkg.in/yaml.v2"
)
type MatchYAMLMatcher struct {
YAMLToMatch interface{}
firstFailurePath []interface{}
}
func (matcher *MatchYAMLMatcher) Match(actual interface{}) (success bool, err error) {
actualString, expectedString, err := matcher.toStrings(actual)
if err != nil {
return false, err
}
var aval interface{}
var eval interface{}
if err := yaml.Unmarshal([]byte(actualString), &aval); err != nil {
return false, fmt.Errorf("Actual '%s' should be valid YAML, but it is not.\nUnderlying error:%s", actualString, err)
}
if err := yaml.Unmarshal([]byte(expectedString), &eval); err != nil {
return false, fmt.Errorf("Expected '%s' should be valid YAML, but it is not.\nUnderlying error:%s", expectedString, err)
}
var equal bool
equal, matcher.firstFailurePath = deepEqual(aval, eval)
return equal, nil
}
func (matcher *MatchYAMLMatcher) FailureMessage(actual interface{}) (message string) {
actualString, expectedString, _ := matcher.toNormalisedStrings(actual)
return formattedMessage(format.Message(actualString, "to match YAML of", expectedString), matcher.firstFailurePath)
}
func (matcher *MatchYAMLMatcher) NegatedFailureMessage(actual interface{}) (message string) {
actualString, expectedString, _ := matcher.toNormalisedStrings(actual)
return formattedMessage(format.Message(actualString, "not to match YAML of", expectedString), matcher.firstFailurePath)
}
func (matcher *MatchYAMLMatcher) toNormalisedStrings(actual interface{}) (actualFormatted, expectedFormatted string, err error) {
actualString, expectedString, err := matcher.toStrings(actual)
return normalise(actualString), normalise(expectedString), err
}
func normalise(input string) string {
var val interface{}
err := yaml.Unmarshal([]byte(input), &val)
if err != nil {
panic(err) // unreachable since Match already calls Unmarshal
}
output, err := yaml.Marshal(val)
if err != nil {
panic(err) // untested section, unreachable since we Unmarshal above
}
return strings.TrimSpace(string(output))
}
func (matcher *MatchYAMLMatcher) toStrings(actual interface{}) (actualFormatted, expectedFormatted string, err error) {
actualString, ok := toString(actual)
if !ok {
return "", "", fmt.Errorf("MatchYAMLMatcher matcher requires a string, stringer, or []byte. Got actual:\n%s", format.Object(actual, 1))
}
expectedString, ok := toString(matcher.YAMLToMatch)
if !ok {
return "", "", fmt.Errorf("MatchYAMLMatcher matcher requires a string, stringer, or []byte. Got expected:\n%s", format.Object(matcher.YAMLToMatch, 1))
}
return actualString, expectedString, nil
}

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