Codebase list golang-gomega / 859ed53
[ Arnaud Rebillout ] [ Debian Janitor ] New upstream release. Debian Janitor 2 years ago
84 changed file(s) with 6905 addition(s) and 2012 deletion(s). Raw diff Collapse all Expand all
0 github: [onsi]
0 version: 2
1 updates:
2 - package-ecosystem: gomod
3 directory: "/"
4 schedule:
5 interval: daily
6 time: '01:00'
7 open-pull-requests-limit: 5
0 # For most projects, this workflow file will not need changing; you simply need
1 # to commit it to your repository.
2 #
3 # You may wish to alter this file to override the set of languages analyzed,
4 # or to provide custom queries or build logic.
5 #
6 # ******** NOTE ********
7 # We have attempted to detect the languages in your repository. Please check
8 # the `language` matrix defined below to confirm you have the correct set of
9 # supported CodeQL languages.
10 #
11 name: "CodeQL"
12
13 on:
14 push:
15 branches: [ master ]
16 pull_request:
17 # The branches below must be a subset of the branches above
18 branches: [ master ]
19 schedule:
20 - cron: '39 17 * * 3'
21
22 jobs:
23 analyze:
24 name: Analyze
25 runs-on: ubuntu-latest
26
27 strategy:
28 fail-fast: false
29 matrix:
30 language: [ 'go' ]
31 # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python' ]
32 # Learn more:
33 # https://docs.github.com/en/free-pro-team@latest/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#changing-the-languages-that-are-analyzed
34
35 steps:
36 - name: Checkout repository
37 uses: actions/checkout@v2
38
39 # Initializes the CodeQL tools for scanning.
40 - name: Initialize CodeQL
41 uses: github/codeql-action/init@v1
42 with:
43 languages: ${{ matrix.language }}
44 # If you wish to specify custom queries, you can do so here or in a config file.
45 # By default, queries listed here will override any specified in a config file.
46 # Prefix the list here with "+" to use these queries and those in the config file.
47 # queries: ./path/to/local/query, your-org/your-repo/queries@main
48
49 # Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
50 # If this step fails, then you should remove it and run the build manually (see below)
51 - name: Autobuild
52 uses: github/codeql-action/autobuild@v1
53
54 # ℹ️ Command-line programs to run using the OS shell.
55 # 📚 https://git.io/JvXDl
56
57 # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines
58 # and modify them (or add more) to build your code if your project
59 # uses a compiled language
60
61 #- run: |
62 # make bootstrap
63 # make release
64
65 - name: Perform CodeQL Analysis
66 uses: github/codeql-action/analyze@v1
0 name: test
1
2 on: [push, pull_request]
3
4 jobs:
5 build:
6 runs-on: ubuntu-latest
7 strategy:
8 matrix:
9 version: [ '1.15', '1.16' ]
10 name: Go ${{ matrix.version }}
11 steps:
12 - uses: actions/setup-go@v2
13 with:
14 go-version: ${{ matrix.version }}
15 - uses: actions/checkout@v2
16 - uses: actions/cache@v2
17 with:
18 path: |
19 ~/.cache/go-build
20 ~/go/pkg/mod
21 key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
22 restore-keys: |
23 ${{ runner.os }}-go-
24 - run: make test
00 language: go
1 arch:
2 - amd64
3 - ppc64le
14
25 go:
3 - 1.14.x
6 - gotip
7 - 1.16.x
48 - 1.15.x
5 - gotip
69
710 env:
811 - GO111MODULE=on
912
10 install:
11 - go get -v ./...
12 - go build ./...
13 - go get github.com/onsi/ginkgo
14 - go install github.com/onsi/ginkgo/ginkgo
13 install: skip
1514
16 script: make test
15 script:
16 - go mod tidy && git diff --exit-code go.mod go.sum
17 - make test
0 ## 1.15.0
1
2 ### Fixes
3 The previous version (1.14.0) introduced a change to allow `Eventually` and `Consistently` to support functions that make assertions. This was accomplished by overriding the global fail handler when running the callbacks passed to `Eventually/Consistently` in order to capture any resulting errors. Issue #457 uncovered a flaw with this approach: when multiple `Eventually`s are running concurrently they race when overriding the singleton global fail handler.
4
5 1.15.0 resolves this by requiring users who want to make assertions in `Eventually/Consistently` call backs to explicitly pass in a function that takes a `Gomega` as an argument. The passed-in `Gomega` instance can be used to make assertions. Any failures will cause `Eventually` to retry the callback. This cleaner interface avoids the issue of swapping out globals but comes at the cost of changing the contract introduced in v1.14.0. As such 1.15.0 introduces a breaking change with respect to 1.14.0 - however we expect that adoption of this feature in 1.14.0 remains limited.
6
7 In addition, 1.15.0 cleans up some of Gomega's internals. Most users shouldn't notice any differences stemming from the refactoring that was made.
8
9 ## 1.14.0
10
11 ### Features
12 - gmeasure.SamplingConfig now suppers a MinSamplingInterval [e94dbca]
13 - Eventually and Consistently support functions that make assertions [2f04e6e]
14 - Eventually and Consistently now allow their passed-in functions to make assertions.
15 These assertions must pass or the function is considered to have failed and is retried.
16 - Eventually and Consistently can now take functions with no return values. These implicitly return nil
17 if they contain no failed assertion. Otherwise they return an error wrapping the first assertion failure. This allows
18 these functions to be used with the Succeed() matcher.
19 - Introduce InterceptGomegaFailure - an analogue to InterceptGomegaFailures - that captures the first assertion failure
20 and halts execution in its passed-in callback.
21
22 ### Fixes
23 - Call Verify GHTTPWithGomega receiver funcs (#454) [496e6fd]
24 - Build a binary with an expected name (#446) [7356360]
25
26 ## 1.13.0
27
28 ### Features
29 - gmeasure provides BETA support for benchmarking (#447) [8f2dfbf]
30 - Set consistently and eventually defaults on init (#443) [12eb778]
31
32 ## 1.12.0
33
34 ### Features
35 - Add Satisfy() matcher (#437) [c548f31]
36 - tweak truncation message [3360b8c]
37 - Add format.GomegaStringer (#427) [cc80b6f]
38 - Add Clear() method to gbytes.Buffer [c3c0920]
39
40 ### Fixes
41 - Fix error message in BeNumericallyMatcher (#432) [09c074a]
42 - Bump github.com/onsi/ginkgo from 1.12.1 to 1.16.2 (#442) [e5f6ea0]
43 - Bump github.com/golang/protobuf from 1.4.3 to 1.5.2 (#431) [adae3bf]
44 - Bump golang.org/x/net (#441) [3275b35]
45
46 ## 1.11.0
47
48 ### Features
49 - feature: add index to gstruct element func (#419) [334e00d]
50 - feat(gexec) Add CompileTest functions. Close #410 (#411) [47c613f]
51
52 ### Fixes
53 - Check more carefully for nils in WithTransform (#423) [3c60a15]
54 - fix: typo in Makefile [b82522a]
55 - Allow WithTransform function to accept a nil value (#422) [b75d2f2]
56 - fix: print value type for interface{} containers (#409) [f08e2dc]
57 - fix(BeElementOf): consistently flatten expected values [1fa9468]
58
59 ## 1.10.5
60
61 ### Fixes
62 - fix: collections matchers should display type of expectation (#408) [6b4eb5a]
63 - fix(ContainElements): consistently flatten expected values [073b880]
64 - fix(ConsistOf): consistently flatten expected values [7266efe]
65
66 ## 1.10.4
67
68 ### Fixes
69 - update golang net library to more recent version without vulnerability (#406) [817a8b9]
70 - Correct spelling: alloted -> allotted (#403) [0bae715]
71 - fix a panic in MessageWithDiff with long message (#402) [ea06b9b]
72
073 ## 1.10.3
174
275 ### Fixes
0 FROM golang:1.15
0 test:
1 [ -z "`gofmt -s -w -l -e .`" ]
2 go vet
3 ginkgo -p -r --randomizeAllSpecs --failOnPending --randomizeSuites --race
0 ###### Help ###################################################################
41
5 .PHONY: test
2 .DEFAULT_GOAL = help
3
4 .PHONY: help
5
6 help: ## list Makefile targets
7 @grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}'
8
9 ###### Targets ################################################################
10
11 test: version download fmt vet ginkgo ## Runs all build, static analysis, and test steps
12
13 download: ## Download dependencies
14 go mod download
15
16 vet: ## Run static code analysis
17 go vet ./...
18
19 ginkgo: ## Run tests using Ginkgo
20 go run github.com/onsi/ginkgo/ginkgo -p -r --randomizeAllSpecs --failOnPending --randomizeSuites --race
21
22 fmt: ## Checks that the code is formatted correcty
23 @@if [ -n "$$(gofmt -s -e -l -d .)" ]; then \
24 echo "gofmt check failed: run 'gofmt -s -e -l -w .'"; \
25 exit 1; \
26 fi
27
28 docker_test: ## Run tests in a container via docker-compose
29 docker-compose build test && docker-compose run --rm test make test
30
31 version: ## Display the version of Go
32 @@go version
00 ![Gomega: Ginkgo's Preferred Matcher Library](http://onsi.github.io/gomega/images/gomega.png)
11
2 [![Build Status](https://travis-ci.org/onsi/gomega.svg?branch=master)](https://travis-ci.org/onsi/gomega)
2 [![test](https://github.com/onsi/gomega/actions/workflows/test.yml/badge.svg)](https://github.com/onsi/gomega/actions/workflows/test.yml)
33
44 Jump straight to the [docs](http://onsi.github.io/gomega/) to learn about Gomega, including a list of [all available matchers](http://onsi.github.io/gomega/#provided-matchers).
55
0 golang-gomega (1.10.3-2) UNRELEASED; urgency=medium
0 golang-gomega (1.15.0-1) UNRELEASED; urgency=medium
11
2 [ Arnaud Rebillout ]
23 * Team upload.
34 * Rename package to golang-github-onsi-gomega-dev.
45 * Update copyright for 'matchers/support/goraph'.
56 * Bump debhelper compat to 13.
67
7 -- Arnaud Rebillout <elboulangero@gmail.com> Wed, 25 Nov 2020 10:15:02 +0700
8 [ Debian Janitor ]
9 * New upstream release.
10
11 -- Arnaud Rebillout <elboulangero@gmail.com> Thu, 19 Aug 2021 02:16:35 -0000
812
913 golang-gomega (1.10.3-1) unstable; urgency=medium
1014
0 version: '3.0'
1
2 services:
3 test:
4 build:
5 dockerfile: Dockerfile
6 context: .
7 working_dir: /app
8 volumes:
9 - ${PWD}:/app
66 package format
77
88 import (
9 "context"
910 "fmt"
1011 "reflect"
1112 "strconv"
1617 // Use MaxDepth to set the maximum recursion depth when printing deeply nested objects
1718 var MaxDepth = uint(10)
1819
20 // MaxLength of the string representation of an object.
21 // If MaxLength is set to 0, the Object will not be truncated.
22 var MaxLength = 4000
23
1924 /*
2025 By default, all objects (even those that implement fmt.Stringer and fmt.GoStringer) are recursively inspected to generate output.
2126
4348 // after the first diff location in a truncated string assertion error message.
4449 var CharactersAroundMismatchToInclude uint = 5
4550
46 // Ctx interface defined here to keep backwards compatibility with go < 1.7
47 // It matches the context.Context interface
48 type Ctx interface {
49 Deadline() (deadline time.Time, ok bool)
50 Done() <-chan struct{}
51 Err() error
52 Value(key interface{}) interface{}
53 }
54
55 var contextType = reflect.TypeOf((*Ctx)(nil)).Elem()
51 var contextType = reflect.TypeOf((*context.Context)(nil)).Elem()
5652 var timeType = reflect.TypeOf(time.Time{})
5753
5854 //The default indentation string emitted by the format package
5955 var Indent = " "
6056
6157 var longFormThreshold = 20
58
59 // GomegaStringer allows for custom formating of objects for gomega.
60 type GomegaStringer interface {
61 // GomegaString will be used to custom format an object.
62 // It does not follow UseStringerRepresentation value and will always be called regardless.
63 // It also ignores the MaxLength value.
64 GomegaString() string
65 }
6266
6367 /*
6468 Generates a formatted matcher success/failure message of the form:
104108
105109 tabLength := 4
106110 spaceFromMessageToActual := tabLength + len("<string>: ") - len(message)
107 padding := strings.Repeat(" ", spaceFromMessageToActual+spacesBeforeFormattedMismatch) + "|"
111
112 paddingCount := spaceFromMessageToActual + spacesBeforeFormattedMismatch
113 if paddingCount < 0 {
114 return Message(formattedActual, message, formattedExpected)
115 }
116
117 padding := strings.Repeat(" ", paddingCount) + "|"
108118 return Message(formattedActual, message+padding, formattedExpected)
109119 }
110120
160170 return 0
161171 }
162172
173 const truncateHelpText = `
174 Gomega truncated this representation as it exceeds 'format.MaxLength'.
175 Consider having the object provide a custom 'GomegaStringer' representation
176 or adjust the parameters in Gomega's 'format' package.
177
178 Learn more here: https://onsi.github.io/gomega/#adjusting-output
179 `
180
181 func truncateLongStrings(s string) string {
182 if MaxLength > 0 && len(s) > MaxLength {
183 var sb strings.Builder
184 for i, r := range s {
185 if i < MaxLength {
186 sb.WriteRune(r)
187 continue
188 }
189 break
190 }
191
192 sb.WriteString("...\n")
193 sb.WriteString(truncateHelpText)
194
195 return sb.String()
196 }
197 return s
198 }
199
163200 /*
164201 Pretty prints the passed in object at the passed in indentation level.
165202
174211 func Object(object interface{}, indentation uint) string {
175212 indent := strings.Repeat(Indent, int(indentation))
176213 value := reflect.ValueOf(object)
177 return fmt.Sprintf("%s<%s>: %s", indent, formatType(object), formatValue(value, indentation))
214 return fmt.Sprintf("%s<%s>: %s", indent, formatType(value), formatValue(value, indentation))
178215 }
179216
180217 /*
194231 return result
195232 }
196233
197 func formatType(object interface{}) string {
198 t := reflect.TypeOf(object)
199 if t == nil {
234 func formatType(v reflect.Value) string {
235 switch v.Kind() {
236 case reflect.Invalid:
200237 return "nil"
201 }
202 switch t.Kind() {
203238 case reflect.Chan:
204 v := reflect.ValueOf(object)
205 return fmt.Sprintf("%T | len:%d, cap:%d", object, v.Len(), v.Cap())
239 return fmt.Sprintf("%s | len:%d, cap:%d", v.Type(), v.Len(), v.Cap())
206240 case reflect.Ptr:
207 return fmt.Sprintf("%T | %p", object, object)
241 return fmt.Sprintf("%s | 0x%x", v.Type(), v.Pointer())
208242 case reflect.Slice:
209 v := reflect.ValueOf(object)
210 return fmt.Sprintf("%T | len:%d, cap:%d", object, v.Len(), v.Cap())
243 return fmt.Sprintf("%s | len:%d, cap:%d", v.Type(), v.Len(), v.Cap())
211244 case reflect.Map:
212 v := reflect.ValueOf(object)
213 return fmt.Sprintf("%T | len:%d", object, v.Len())
245 return fmt.Sprintf("%s | len:%d", v.Type(), v.Len())
214246 default:
215 return fmt.Sprintf("%T", object)
247 return fmt.Sprintf("%s", v.Type())
216248 }
217249 }
218250
225257 return "nil"
226258 }
227259
228 if UseStringerRepresentation {
229 if value.CanInterface() {
230 obj := value.Interface()
260 if value.CanInterface() {
261 obj := value.Interface()
262
263 // GomegaStringer will take precedence to other representations and disregards UseStringerRepresentation
264 if x, ok := obj.(GomegaStringer); ok {
265 // do not truncate a user-defined GoMegaString() value
266 return x.GomegaString()
267 }
268
269 if UseStringerRepresentation {
231270 switch x := obj.(type) {
232271 case fmt.GoStringer:
233 return x.GoString()
272 return truncateLongStrings(x.GoString())
234273 case fmt.Stringer:
235 return x.String()
274 return truncateLongStrings(x.String())
236275 }
237276 }
238277 }
263302 case reflect.Ptr:
264303 return formatValue(value.Elem(), indentation)
265304 case reflect.Slice:
266 return formatSlice(value, indentation)
305 return truncateLongStrings(formatSlice(value, indentation))
267306 case reflect.String:
268 return formatString(value.String(), indentation)
307 return truncateLongStrings(formatString(value.String(), indentation))
269308 case reflect.Array:
270 return formatSlice(value, indentation)
309 return truncateLongStrings(formatSlice(value, indentation))
271310 case reflect.Map:
272 return formatMap(value, indentation)
311 return truncateLongStrings(formatMap(value, indentation))
273312 case reflect.Struct:
274313 if value.Type() == timeType && value.CanInterface() {
275314 t, _ := value.Interface().(time.Time)
276315 return t.Format(time.RFC3339Nano)
277316 }
278 return formatStruct(value, indentation)
317 return truncateLongStrings(formatStruct(value, indentation))
279318 case reflect.Interface:
280 return formatValue(value.Elem(), indentation)
319 return formatInterface(value, indentation)
281320 default:
282321 if value.CanInterface() {
283 return fmt.Sprintf("%#v", value.Interface())
284 }
285 return fmt.Sprintf("%#v", value)
322 return truncateLongStrings(fmt.Sprintf("%#v", value.Interface()))
323 }
324 return truncateLongStrings(fmt.Sprintf("%#v", value))
286325 }
287326 }
288327
372411 return fmt.Sprintf("{%s}", strings.Join(result, ", "))
373412 }
374413
414 func formatInterface(v reflect.Value, indentation uint) string {
415 return fmt.Sprintf("<%s>%s", formatType(v.Elem()), formatValue(v.Elem(), indentation))
416 }
417
375418 func isNilValue(a reflect.Value) bool {
376419 switch a.Kind() {
377420 case reflect.Invalid:
00 package format_test
11
22 import (
3 "context"
34 "fmt"
45 "strings"
56 "time"
7273 return "string"
7374 }
7475
75 type ctx struct {
76 }
77
78 func (c *ctx) Deadline() (deadline time.Time, ok bool) {
79 return time.Time{}, false
80 }
81
82 func (c *ctx) Done() <-chan struct{} {
83 return nil
84 }
85
86 func (c *ctx) Err() error {
87 return nil
88 }
89
90 func (c *ctx) Value(key interface{}) interface{} {
91 return nil
76 type gomegaStringer struct {
77 }
78
79 func (g gomegaStringer) GomegaString() string {
80 return "gomegastring"
81 }
82
83 type gomegaStringerLong struct {
84 }
85
86 func (g gomegaStringerLong) GomegaString() string {
87 return strings.Repeat("s", MaxLength*2)
9288 }
9389
9490 var _ = Describe("Format", func() {
112108 for i := range arr {
113109 arr[i] = entriesSwitch
114110 }
115 return "{" + strings.Join(arr, ", ") + "}"
111 return "{\\s*" + strings.Join(arr, ",\\s* ") + ",?\\s*}"
116112 }
117113
118114 Describe("Message", func() {
119115 Context("with only an actual value", func() {
116 BeforeEach(func() {
117 MaxLength = 4000
118 })
119
120120 It("should print out an indented formatted representation of the value and the message", func() {
121121 Expect(Message(3, "to be three.")).Should(Equal("Expected\n <int>: 3\nto be three."))
122 })
123
124 It("should print out an indented formatted representation of the value and the message, and trucate it when too long", func() {
125 tooLong := strings.Repeat("s", MaxLength+1)
126 tooLongResult := strings.Repeat("s", MaxLength) + "...\n" + TruncatedHelpText()
127 Expect(Message(tooLong, "to be truncated")).Should(Equal("Expected\n <string>: " + tooLongResult + "\nto be truncated"))
128 })
129
130 It("should print out an indented formatted representation of the value and the message, and not trucate it when MaxLength = 0", func() {
131 MaxLength = 0
132 tooLong := strings.Repeat("s", MaxLength+1)
133 Expect(Message(tooLong, "to be truncated")).Should(Equal("Expected\n <string>: " + tooLong + "\nto be truncated"))
122134 })
123135 })
124136
171183 stringB := "something_else"
172184
173185 Expect(MessageWithDiff(stringA, "to equal", stringB)).Should(Equal(expectedSpecialCharacterFailureMessage))
186 })
187
188 It("handles negative padding length", func() {
189 stringWithB := "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
190 stringWithZ := "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaazaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
191 longMessage := "to equal very long message"
192
193 Expect(MessageWithDiff(stringWithB, longMessage, stringWithZ)).Should(Equal(expectedDiffLongMessage))
174194 })
175195
176196 Context("With truncated diff disabled", func() {
535555 })
536556 })
537557
558 Describe("formatting nested interface{} types", func() {
559 It("should print out the types of the container and value", func() {
560 Expect(Object([]interface{}{"foo"}, 1)).
561 To(match("[]interface {} | len:1, cap:1", `[<string>"foo"]`))
562
563 Expect(Object(map[string]interface{}{"foo": true}, 1)).
564 To(match("map[string]interface {} | len:1", `{"foo": <bool>true}`))
565
566 Expect(Object(struct{ A interface{} }{A: 1}, 1)).
567 To(match("struct { A interface {} }", "{A: <int>1}"))
568
569 v := struct{ A interface{} }{A: struct{ B string }{B: "foo"}}
570 Expect(Object(v, 1)).To(match(`struct { A interface {} }`, `{
571 A: <struct { B string }>{B: "foo"},
572 }`))
573 })
574 })
575
538576 Describe("formatting times", func() {
539577 It("should format time as RFC3339", func() {
540578 t := time.Date(2016, 10, 31, 9, 57, 23, 12345, time.UTC)
583621 byteArrValue: \[17, 20, 32\],
584622 mapValue: %s,
585623 structValue: {Exported: "exported"},
586 interfaceValue: {"a key": 17},
624 interfaceValue: <map\[string\]int \| len:1>{"a key": 17},
587625 }`, s.chanValue, s.funcValue, hashMatchingRegexp(`"a key": 20`, `"b key": 30`))
588626
589627 Expect(Object(s, 1)).Should(matchRegexp(`format_test\.SecretiveStruct`, expected))
599637 outerHash["integer"] = 2
600638 outerHash["map"] = innerHash
601639
602 expected := hashMatchingRegexp(`"integer": 2`, `"map": {"inner": 3}`)
640 expected := hashMatchingRegexp(`"integer": <int>2`, `"map": <map\[string\]int \| len:1>{"inner": 3}`)
603641 Expect(Object(outerHash, 1)).Should(matchRegexp(`map\[string\]interface {} \| len:2`, expected))
604642 })
605643 })
645683 Expect(Object(Stringer{}, 1)).Should(ContainSubstring("<format_test.Stringer>: string"))
646684 })
647685 })
686
687 When("passed a GomegaStringer", func() {
688 It("should use what GomegaString() returns", func() {
689 Expect(Object(gomegaStringer{}, 1)).Should(ContainSubstring("<format_test.gomegaStringer>: gomegastring"))
690 UseStringerRepresentation = false
691 Expect(Object(gomegaStringer{}, 1)).Should(ContainSubstring("<format_test.gomegaStringer>: gomegastring"))
692 })
693
694 It("should use what GomegaString() returns, disregarding MaxLength", func() {
695 Expect(Object(gomegaStringerLong{}, 1)).Should(Equal(" <format_test.gomegaStringerLong>: " + strings.Repeat("s", MaxLength*2)))
696 UseStringerRepresentation = false
697 Expect(Object(gomegaStringerLong{}, 1)).Should(Equal(" <format_test.gomegaStringerLong>: " + strings.Repeat("s", MaxLength*2)))
698 })
699 })
648700 })
649701
650702 Describe("Printing a context.Context field", func() {
651703
652704 type structWithContext struct {
653 Context Ctx
705 Context context.Context
654706 Value string
655707 }
656708
657 context := ctx{}
658 objWithContext := structWithContext{Value: "some-value", Context: &context}
709 objWithContext := structWithContext{Value: "some-value", Context: context.TODO()}
659710
660711 It("Suppresses the content by default", func() {
661712 Expect(Object(objWithContext, 1)).Should(ContainSubstring("<suppressed context>"))
662713 })
663714
664 It("Doesn't supress the context if it's the object being printed", func() {
665 Expect(Object(context, 1)).ShouldNot(MatchRegexp("^.*<suppressed context>$"))
715 It("Doesn't suppress the context if it's the object being printed", func() {
716 Expect(Object(context.TODO(), 1)).ShouldNot(MatchRegexp("^.*<suppressed context>$"))
666717 })
667718
668719 Context("PrintContextObjects is set", func() {
760811 to equal |
761812 <string>: "...z..."
762813 `)
814
815 var expectedDiffLongMessage = strings.TrimSpace(`
816 Expected
817 <string>: "...aaaaabaaaaa..."
818 to equal very long message
819 <string>: "...aaaaazaaaaa..."
820 `)
0 /*
1 Gomega's format test helper package.
2 */
3
4 package format
5
6 // TruncateHelpText returns truncateHelpText.
7 // This function is only accessible during tests.
8 func TruncatedHelpText() string {
9 return truncateHelpText
10 }
105105 b.readCursor += uint64(n)
106106
107107 return n, nil
108 }
109
110 /*
111 Clear clears out the buffer's contents
112 */
113 func (b *Buffer) Clear() error {
114 b.lock.Lock()
115 defer b.lock.Unlock()
116
117 if b.closed {
118 return errors.New("attempt to clear closed buffer")
119 }
120
121 b.contents = []byte{}
122 b.readCursor = 0
123 return nil
108124 }
109125
110126 /*
180180 })
181181 })
182182
183 Describe("clearing the buffer", func() {
184 It("should clear out the contents of the buffer", func() {
185 buffer.Write([]byte("abc"))
186 Expect(buffer).To(Say("ab"))
187 Expect(buffer.Clear()).To(Succeed())
188 Expect(buffer).NotTo(Say("c"))
189 Expect(buffer.Contents()).To(BeEmpty())
190 buffer.Write([]byte("123"))
191 Expect(buffer).To(Say("123"))
192 Expect(buffer.Contents()).To(Equal([]byte("123")))
193 })
194
195 It("should error when the buffer is closed", func() {
196 buffer.Write([]byte("abc"))
197 buffer.Close()
198 err := buffer.Clear()
199 Expect(err).To(HaveOccurred())
200 })
201 })
202
183203 Describe("closing the buffer", func() {
184204 It("should error when further write attempts are made", func() {
185205 _, err := buffer.Write([]byte("abc"))
88 // ErrTimeout is returned by TimeoutCloser, TimeoutReader, and TimeoutWriter when the underlying Closer/Reader/Writer does not return within the specified timeout
99 var ErrTimeout = errors.New("timeout occurred")
1010
11 // TimeoutCloser returns an io.Closer that wraps the passed-in io.Closer. If the underlying Closer fails to close within the alloted timeout ErrTimeout is returned.
11 // TimeoutCloser returns an io.Closer that wraps the passed-in io.Closer. If the underlying Closer fails to close within the allotted timeout ErrTimeout is returned.
1212 func TimeoutCloser(c io.Closer, timeout time.Duration) io.Closer {
1313 return timeoutReaderWriterCloser{c: c, d: timeout}
1414 }
1515
16 // TimeoutReader returns an io.Reader that wraps the passed-in io.Reader. If the underlying Reader fails to read within the alloted timeout ErrTimeout is returned.
16 // TimeoutReader returns an io.Reader that wraps the passed-in io.Reader. If the underlying Reader fails to read within the allotted timeout ErrTimeout is returned.
1717 func TimeoutReader(r io.Reader, timeout time.Duration) io.Reader {
1818 return timeoutReaderWriterCloser{r: r, d: timeout}
1919 }
2020
21 // TimeoutWriter returns an io.Writer that wraps the passed-in io.Writer. If the underlying Writer fails to write within the alloted timeout ErrTimeout is returned.
21 // TimeoutWriter returns an io.Writer that wraps the passed-in io.Writer. If the underlying Writer fails to write within the allotted timeout ErrTimeout is returned.
2222 func TimeoutWriter(w io.Writer, timeout time.Duration) io.Writer {
2323 return timeoutReaderWriterCloser{w: w, d: timeout}
2424 }
0 package main_test
1
2 import "testing"
3
4 func Test(t *testing.T) {
5 t.Log("Hum, it seems okay.")
6 }
4545 return doBuild(gopath, packagePath, nil, args...)
4646 }
4747
48 func doBuild(gopath, packagePath string, env []string, args ...string) (compiledPath string, err error) {
49 executable, err := newExecutablePath(gopath, packagePath)
50 if err != nil {
51 return "", err
52 }
53
54 cmdArgs := append([]string{"build"}, args...)
55 cmdArgs = append(cmdArgs, "-o", executable, packagePath)
56
57 build := exec.Command("go", cmdArgs...)
58 build.Env = replaceGoPath(os.Environ(), gopath)
59 build.Env = append(build.Env, env...)
60
61 output, err := build.CombinedOutput()
62 if err != nil {
63 return "", fmt.Errorf("Failed to build %s:\n\nError:\n%s\n\nOutput:\n%s", packagePath, err, string(output))
64 }
65
66 return executable, nil
67 }
68
69 /*
70 CompileTest uses go test to compile the test package at packagePath. The resulting binary is saved off in a temporary directory.
71 A path pointing to this binary is returned.
72
73 CompileTest uses the $GOPATH set in your environment. If $GOPATH is not set and you are using Go 1.8+,
74 it will use the default GOPATH instead. It passes the variadic args on to `go test`.
75 */
76 func CompileTest(packagePath string, args ...string) (compiledPath string, err error) {
77 return doCompileTest(build.Default.GOPATH, packagePath, nil, args...)
78 }
79
80 /*
81 GetAndCompileTest is identical to CompileTest but `go get` the package before compiling tests.
82 */
83 func GetAndCompileTest(packagePath string, args ...string) (compiledPath string, err error) {
84 if err := getForTest(build.Default.GOPATH, packagePath, nil); err != nil {
85 return "", err
86 }
87
88 return doCompileTest(build.Default.GOPATH, packagePath, nil, args...)
89 }
90
91 /*
92 CompileTestWithEnvironment is identical to CompileTest but allows you to specify env vars to be set at build time.
93 */
94 func CompileTestWithEnvironment(packagePath string, env []string, args ...string) (compiledPath string, err error) {
95 return doCompileTest(build.Default.GOPATH, packagePath, env, args...)
96 }
97
98 /*
99 GetAndCompileTestWithEnvironment is identical to GetAndCompileTest but allows you to specify env vars to be set at build time.
100 */
101 func GetAndCompileTestWithEnvironment(packagePath string, env []string, args ...string) (compiledPath string, err error) {
102 if err := getForTest(build.Default.GOPATH, packagePath, env); err != nil {
103 return "", err
104 }
105
106 return doCompileTest(build.Default.GOPATH, packagePath, env, args...)
107 }
108
109 /*
110 CompileTestIn is identical to CompileTest but allows you to specify a custom $GOPATH (the first argument).
111 */
112 func CompileTestIn(gopath string, packagePath string, args ...string) (compiledPath string, err error) {
113 return doCompileTest(gopath, packagePath, nil, args...)
114 }
115
116 /*
117 GetAndCompileTestIn is identical to GetAndCompileTest but allows you to specify a custom $GOPATH (the first argument).
118 */
119 func GetAndCompileTestIn(gopath string, packagePath string, args ...string) (compiledPath string, err error) {
120 if err := getForTest(gopath, packagePath, nil); err != nil {
121 return "", err
122 }
123
124 return doCompileTest(gopath, packagePath, nil, args...)
125 }
126
127 func isLocalPackage(packagePath string) bool {
128 return strings.HasPrefix(packagePath, ".")
129 }
130
131 func getForTest(gopath, packagePath string, env []string) error {
132 if isLocalPackage(packagePath) {
133 return nil
134 }
135
136 return doGet(gopath, packagePath, env, "-t")
137 }
138
139 func doGet(gopath, packagePath string, env []string, args ...string) error {
140 args = append(args, packagePath)
141 args = append([]string{"get"}, args...)
142
143 goGet := exec.Command("go", args...)
144 goGet.Dir = gopath
145 goGet.Env = replaceGoPath(os.Environ(), gopath)
146 goGet.Env = append(goGet.Env, env...)
147
148 output, err := goGet.CombinedOutput()
149 if err != nil {
150 return fmt.Errorf("Failed to get %s:\n\nError:\n%s\n\nOutput:\n%s", packagePath, err, string(output))
151 }
152
153 return nil
154 }
155
156 func doCompileTest(gopath, packagePath string, env []string, args ...string) (compiledPath string, err error) {
157 executable, err := newExecutablePath(gopath, packagePath, ".test")
158 if err != nil {
159 return "", err
160 }
161
162 cmdArgs := append([]string{"test", "-c"}, args...)
163 cmdArgs = append(cmdArgs, "-o", executable, packagePath)
164
165 build := exec.Command("go", cmdArgs...)
166 build.Env = replaceGoPath(os.Environ(), gopath)
167 build.Env = append(build.Env, env...)
168
169 output, err := build.CombinedOutput()
170 if err != nil {
171 return "", fmt.Errorf("Failed to build %s:\n\nError:\n%s\n\nOutput:\n%s", packagePath, err, string(output))
172 }
173
174 return executable, nil
175 }
176
48177 func replaceGoPath(environ []string, newGoPath string) []string {
49178 newEnviron := []string{}
50179 for _, v := range environ {
55184 return append(newEnviron, "GOPATH="+newGoPath)
56185 }
57186
58 func doBuild(gopath, packagePath string, env []string, args ...string) (compiledPath string, err error) {
187 func newExecutablePath(gopath, packagePath string, suffixes ...string) (string, error) {
59188 tmpDir, err := temporaryDirectory()
60189 if err != nil {
61190 return "", err
66195 }
67196
68197 executable := filepath.Join(tmpDir, path.Base(packagePath))
198
69199 if runtime.GOOS == "windows" {
70200 executable += ".exe"
71 }
72
73 cmdArgs := append([]string{"build"}, args...)
74 cmdArgs = append(cmdArgs, "-o", executable, packagePath)
75
76 build := exec.Command("go", cmdArgs...)
77 build.Env = replaceGoPath(os.Environ(), gopath)
78 build.Env = append(build.Env, env...)
79
80 output, err := build.CombinedOutput()
81 if err != nil {
82 return "", fmt.Errorf("Failed to build %s:\n\nError:\n%s\n\nOutput:\n%s", packagePath, err, string(output))
83201 }
84202
85203 return executable, nil
1313 var packagePath = "./_fixture/firefly"
1414
1515 var _ = Describe(".Build", func() {
16 When("there have been previous calls to Build", func() {
17 BeforeEach(func() {
18 _, err := gexec.Build(packagePath)
16 When("there have been previous calls to CompileTest", func() {
17 BeforeEach(func() {
18 _, err := gexec.CompileTest(packagePath)
1919 Expect(err).ShouldNot(HaveOccurred())
2020 })
2121
2323 compiledPath, err := gexec.Build(packagePath)
2424 Expect(err).ShouldNot(HaveOccurred())
2525 Expect(compiledPath).Should(BeAnExistingFile())
26 Expect(filepath.Base(compiledPath)).Should(MatchRegexp(`firefly(|.exe)$`))
2627 })
2728
2829 Context("and CleanupBuildArtifacts has been called", func() {
3132 })
3233
3334 It("compiles the specified package", func() {
34 var err error
35 fireflyPath, err = gexec.Build(packagePath)
35 fireflyPath, err := gexec.Build(packagePath)
36 Expect(err).ShouldNot(HaveOccurred())
37 Expect(fireflyPath).Should(BeAnExistingFile())
38 })
39 })
40 })
41
42 When("there have been previous calls to Build", func() {
43 BeforeEach(func() {
44 _, err := gexec.Build(packagePath)
45 Expect(err).ShouldNot(HaveOccurred())
46 })
47
48 It("compiles the specified package", func() {
49 compiledPath, err := gexec.Build(packagePath)
50 Expect(err).ShouldNot(HaveOccurred())
51 Expect(compiledPath).Should(BeAnExistingFile())
52 })
53
54 Context("and CleanupBuildArtifacts has been called", func() {
55 BeforeEach(func() {
56 gexec.CleanupBuildArtifacts()
57 })
58
59 It("compiles the specified package", func() {
60 fireflyPath, err := gexec.Build(packagePath)
3661 Expect(err).ShouldNot(HaveOccurred())
3762 Expect(fireflyPath).Should(BeAnExistingFile())
3863 })
92117 })
93118
94119 It("appends the gopath env var", func() {
95 _, err := gexec.BuildIn(gopath, target)
96 Expect(err).NotTo(HaveOccurred())
120 compiledPath, err := gexec.BuildIn(gopath, target)
121 Expect(err).NotTo(HaveOccurred())
122 Expect(compiledPath).Should(BeAnExistingFile())
97123 })
98124
99125 It("resets GOPATH to its original value", func() {
100126 _, err := gexec.BuildIn(gopath, target)
127 Expect(err).NotTo(HaveOccurred())
128 Expect(os.Getenv("GOPATH")).To(Equal(filepath.Join(os.TempDir(), "emptyFakeGopath")))
129 })
130 })
131
132 var _ = Describe(".CompileTest", func() {
133 Context("a remote package", func() {
134 const remotePackage = "github.com/onsi/ginkgo/types"
135
136 It("compiles the specified test package", func() {
137 compiledPath, err := gexec.GetAndCompileTest(remotePackage)
138 Expect(err).ShouldNot(HaveOccurred())
139 Expect(compiledPath).Should(BeAnExistingFile())
140 })
141 })
142
143 When("there have been previous calls to CompileTest", func() {
144 BeforeEach(func() {
145 _, err := gexec.CompileTest(packagePath)
146 Expect(err).ShouldNot(HaveOccurred())
147 })
148
149 It("compiles the specified test package", func() {
150 compiledPath, err := gexec.CompileTest(packagePath)
151 Expect(err).ShouldNot(HaveOccurred())
152 Expect(compiledPath).Should(BeAnExistingFile())
153 })
154
155 Context("and CleanupBuildArtifacts has been called", func() {
156 BeforeEach(func() {
157 gexec.CleanupBuildArtifacts()
158 })
159
160 It("compiles the specified test package", func() {
161 fireflyTestPath, err := gexec.CompileTest(packagePath)
162 Expect(err).ShouldNot(HaveOccurred())
163 Expect(fireflyTestPath).Should(BeAnExistingFile())
164 })
165 })
166 })
167
168 When("there have been previous calls to Build", func() {
169 BeforeEach(func() {
170 _, err := gexec.Build(packagePath)
171 Expect(err).ShouldNot(HaveOccurred())
172 })
173
174 It("compiles the specified test package", func() {
175 compiledPath, err := gexec.CompileTest(packagePath)
176 Expect(err).ShouldNot(HaveOccurred())
177 Expect(compiledPath).Should(BeAnExistingFile())
178 })
179
180 Context("and CleanupBuildArtifacts has been called", func() {
181 BeforeEach(func() {
182 gexec.CleanupBuildArtifacts()
183 })
184
185 It("compiles the specified test package", func() {
186 fireflyTestPath, err := gexec.CompileTest(packagePath)
187 Expect(err).ShouldNot(HaveOccurred())
188 Expect(fireflyTestPath).Should(BeAnExistingFile())
189 })
190 })
191 })
192 })
193
194 var _ = Describe(".CompileTestWithEnvironment", func() {
195 var err error
196 env := []string{
197 "GOOS=linux",
198 "GOARCH=amd64",
199 }
200
201 Context("a remote package", func() {
202 const remotePackage = "github.com/onsi/ginkgo/types"
203
204 It("compiles the specified test package with the specified env vars", func() {
205 compiledPath, err := gexec.GetAndCompileTestWithEnvironment(remotePackage, env)
206 Expect(err).ShouldNot(HaveOccurred())
207 Expect(compiledPath).Should(BeAnExistingFile())
208 })
209 })
210
211 It("compiles the specified test package with the specified env vars", func() {
212 compiledPath, err := gexec.CompileTestWithEnvironment(packagePath, env)
213 Expect(err).ShouldNot(HaveOccurred())
214 Expect(compiledPath).Should(BeAnExistingFile())
215 })
216
217 It("returns the environment to a good state", func() {
218 _, err = gexec.CompileTestWithEnvironment(packagePath, env)
219 Expect(err).ShouldNot(HaveOccurred())
220 Expect(os.Environ()).ShouldNot(ContainElement("GOOS=linux"))
221 })
222 })
223
224 var _ = Describe(".CompiledTestIn", func() {
225 const (
226 target = "github.com/onsi/gomega/gexec/_fixture/firefly"
227 )
228
229 var (
230 original string
231 gopath string
232 )
233
234 BeforeEach(func() {
235 var err error
236 original = os.Getenv("GOPATH")
237 gopath, err = ioutil.TempDir("", "")
238 Expect(err).NotTo(HaveOccurred())
239 copyFile(filepath.Join("_fixture", "firefly", "main.go"), filepath.Join(gopath, "src", target), "main.go")
240 Expect(os.Setenv("GOPATH", filepath.Join(os.TempDir(), "emptyFakeGopath"))).To(Succeed())
241 Expect(os.Environ()).To(ContainElement(fmt.Sprintf("GOPATH=%s", filepath.Join(os.TempDir(), "emptyFakeGopath"))))
242 })
243
244 AfterEach(func() {
245 if original == "" {
246 Expect(os.Unsetenv("GOPATH")).To(Succeed())
247 } else {
248 Expect(os.Setenv("GOPATH", original)).To(Succeed())
249 }
250 if gopath != "" {
251 os.RemoveAll(gopath)
252 }
253 })
254
255 Context("a remote package", func() {
256 const remotePackage = "github.com/onsi/ginkgo/types"
257
258 It("compiles the specified test package", func() {
259 compiledPath, err := gexec.GetAndCompileTestIn(gopath, remotePackage)
260 Expect(err).ShouldNot(HaveOccurred())
261 Expect(compiledPath).Should(BeAnExistingFile())
262 })
263 })
264
265 It("appends the gopath env var", func() {
266 compiledPath, err := gexec.CompileTestIn(gopath, target)
267 Expect(err).NotTo(HaveOccurred())
268 Expect(compiledPath).Should(BeAnExistingFile())
269 })
270
271 It("resets GOPATH to its original value", func() {
272 _, err := gexec.CompileTestIn(gopath, target)
101273 Expect(err).NotTo(HaveOccurred())
102274 Expect(os.Getenv("GOPATH")).To(Equal(filepath.Join(os.TempDir(), "emptyFakeGopath")))
103275 })
2020 var session *Session
2121
2222 BeforeEach(func() {
23 var err error
23 fireflyPath, err := Build("./_fixture/firefly")
24 Expect(err).ShouldNot(HaveOccurred())
25
2426 command = exec.Command(fireflyPath, "0")
2527 session, err = Start(command, nil, nil)
2628 Expect(err).ShouldNot(HaveOccurred())
77 "testing"
88 )
99
10 var fireflyPath string
11
1210 func TestGexec(t *testing.T) {
13 BeforeSuite(func() {
14 var err error
15 fireflyPath, err = gexec.Build("./_fixture/firefly")
16 Expect(err).ShouldNot(HaveOccurred())
17 })
18
1911 AfterSuite(func() {
2012 gexec.CleanupBuildArtifacts()
2113 })
1616 )
1717
1818 var _ = Describe("Session", func() {
19 var command *exec.Cmd
20 var session *Session
21
22 var outWriter, errWriter io.Writer
23
24 BeforeEach(func() {
25 outWriter = nil
26 errWriter = nil
19 Context("firefly binary", func() {
20 var fireflyPath string
21 var command *exec.Cmd
22 var session *Session
23
24 var outWriter, errWriter io.Writer
25
26 BeforeEach(func() {
27 outWriter = nil
28 errWriter = nil
29
30 var err error
31 fireflyPath, err = Build("./_fixture/firefly")
32 Expect(err).ShouldNot(HaveOccurred())
33
34 })
35
36 JustBeforeEach(func() {
37 command = exec.Command(fireflyPath)
38 var err error
39 session, err = Start(command, outWriter, errWriter)
40 Expect(err).ShouldNot(HaveOccurred())
41 })
42
43 Context("running a command", func() {
44 It("should start the process", func() {
45 Expect(command.Process).ShouldNot(BeNil())
46 })
47
48 It("should wrap the process's stdout and stderr with gbytes buffers", func(done Done) {
49 Eventually(session.Out).Should(Say("We've done the impossible, and that makes us mighty"))
50 Eventually(session.Err).Should(Say("Ah, curse your sudden but inevitable betrayal!"))
51 defer session.Out.CancelDetects()
52
53 select {
54 case <-session.Out.Detect("Can we maybe vote on the whole murdering people issue"):
55 Eventually(session).Should(Exit(0))
56 case <-session.Out.Detect("I swear by my pretty floral bonnet, I will end you."):
57 Eventually(session).Should(Exit(1))
58 case <-session.Out.Detect("My work's illegal, but at least it's honest."):
59 Eventually(session).Should(Exit(2))
60 }
61
62 close(done)
63 })
64
65 It("should satisfy the gbytes.BufferProvider interface, passing Stdout", func() {
66 Eventually(session).Should(Say("We've done the impossible, and that makes us mighty"))
67 Eventually(session).Should(Exit())
68 })
69 })
70
71 Describe("providing the exit code", func() {
72 It("should provide the app's exit code", func() {
73 Expect(session.ExitCode()).Should(Equal(-1))
74
75 Eventually(session).Should(Exit())
76 Expect(session.ExitCode()).Should(BeNumerically(">=", 0))
77 Expect(session.ExitCode()).Should(BeNumerically("<", 3))
78 })
79 })
80
81 Describe("wait", func() {
82 It("should wait till the command exits", func() {
83 Expect(session.ExitCode()).Should(Equal(-1))
84 Expect(session.Wait().ExitCode()).Should(BeNumerically(">=", 0))
85 Expect(session.Wait().ExitCode()).Should(BeNumerically("<", 3))
86 })
87 })
88
89 Describe("exited", func() {
90 It("should close when the command exits", func() {
91 Eventually(session.Exited).Should(BeClosed())
92 Expect(session.ExitCode()).ShouldNot(Equal(-1))
93 })
94 })
95
96 Describe("kill", func() {
97 It("should kill the command", func() {
98 session, err := Start(exec.Command("sleep", "10000000"), GinkgoWriter, GinkgoWriter)
99 Expect(err).ShouldNot(HaveOccurred())
100
101 session.Kill()
102 Eventually(session).Should(Exit(128 + 9))
103 })
104 })
105
106 Describe("interrupt", func() {
107 It("should interrupt the command", func() {
108 session, err := Start(exec.Command("sleep", "10000000"), GinkgoWriter, GinkgoWriter)
109 Expect(err).ShouldNot(HaveOccurred())
110
111 session.Interrupt()
112 Eventually(session).Should(Exit(128 + 2))
113 })
114 })
115
116 Describe("terminate", func() {
117 It("should terminate the command", func() {
118 session, err := Start(exec.Command("sleep", "10000000"), GinkgoWriter, GinkgoWriter)
119 Expect(err).ShouldNot(HaveOccurred())
120
121 session.Terminate()
122 Eventually(session).Should(Exit(128 + 15))
123 })
124 })
125
126 Describe("signal", func() {
127 It("should send the signal to the command", func() {
128 session, err := Start(exec.Command("sleep", "10000000"), GinkgoWriter, GinkgoWriter)
129 Expect(err).ShouldNot(HaveOccurred())
130
131 session.Signal(syscall.SIGABRT)
132 Eventually(session).Should(Exit(128 + 6))
133 })
134
135 It("should ignore sending a signal if the command did not start", func() {
136 session, err := Start(exec.Command("notexisting"), GinkgoWriter, GinkgoWriter)
137 Expect(err).To(HaveOccurred())
138
139 Expect(func() { session.Signal(syscall.SIGUSR1) }).NotTo(Panic())
140 })
141 })
142
143 Context("tracking sessions", func() {
144 BeforeEach(func() {
145 KillAndWait()
146 })
147
148 Describe("kill", func() {
149 It("should kill all the started sessions", func() {
150 session1, err := Start(exec.Command("sleep", "10000000"), GinkgoWriter, GinkgoWriter)
151 Expect(err).ShouldNot(HaveOccurred())
152
153 session2, err := Start(exec.Command("sleep", "10000000"), GinkgoWriter, GinkgoWriter)
154 Expect(err).ShouldNot(HaveOccurred())
155
156 session3, err := Start(exec.Command("sleep", "10000000"), GinkgoWriter, GinkgoWriter)
157 Expect(err).ShouldNot(HaveOccurred())
158
159 Kill()
160
161 Eventually(session1).Should(Exit(128 + 9))
162 Eventually(session2).Should(Exit(128 + 9))
163 Eventually(session3).Should(Exit(128 + 9))
164 })
165
166 It("should not track unstarted sessions", func() {
167 _, err := Start(exec.Command("does not exist", "10000000"), GinkgoWriter, GinkgoWriter)
168 Expect(err).Should(HaveOccurred())
169
170 session2, err := Start(exec.Command("sleep", "10000000"), GinkgoWriter, GinkgoWriter)
171 Expect(err).ShouldNot(HaveOccurred())
172
173 session3, err := Start(exec.Command("sleep", "10000000"), GinkgoWriter, GinkgoWriter)
174 Expect(err).ShouldNot(HaveOccurred())
175
176 Kill()
177
178 Eventually(session2).Should(Exit(128 + 9))
179 Eventually(session3).Should(Exit(128 + 9))
180 })
181
182 })
183
184 Describe("killAndWait", func() {
185 It("should kill all the started sessions and wait for them to finish", func() {
186 session1, err := Start(exec.Command("sleep", "10000000"), GinkgoWriter, GinkgoWriter)
187 Expect(err).ShouldNot(HaveOccurred())
188
189 session2, err := Start(exec.Command("sleep", "10000000"), GinkgoWriter, GinkgoWriter)
190 Expect(err).ShouldNot(HaveOccurred())
191
192 session3, err := Start(exec.Command("sleep", "10000000"), GinkgoWriter, GinkgoWriter)
193 Expect(err).ShouldNot(HaveOccurred())
194
195 KillAndWait()
196 Expect(session1).Should(Exit(128+9), "Should have exited")
197 Expect(session2).Should(Exit(128+9), "Should have exited")
198 Expect(session3).Should(Exit(128+9), "Should have exited")
199 })
200 })
201
202 Describe("terminate", func() {
203 It("should terminate all the started sessions", func() {
204 session1, err := Start(exec.Command("sleep", "10000000"), GinkgoWriter, GinkgoWriter)
205 Expect(err).ShouldNot(HaveOccurred())
206
207 session2, err := Start(exec.Command("sleep", "10000000"), GinkgoWriter, GinkgoWriter)
208 Expect(err).ShouldNot(HaveOccurred())
209
210 session3, err := Start(exec.Command("sleep", "10000000"), GinkgoWriter, GinkgoWriter)
211 Expect(err).ShouldNot(HaveOccurred())
212
213 Terminate()
214
215 Eventually(session1).Should(Exit(128 + 15))
216 Eventually(session2).Should(Exit(128 + 15))
217 Eventually(session3).Should(Exit(128 + 15))
218 })
219 })
220
221 Describe("terminateAndWait", func() {
222 It("should terminate all the started sessions, and wait for them to exit", func() {
223 session1, err := Start(exec.Command("sleep", "10000000"), GinkgoWriter, GinkgoWriter)
224 Expect(err).ShouldNot(HaveOccurred())
225
226 session2, err := Start(exec.Command("sleep", "10000000"), GinkgoWriter, GinkgoWriter)
227 Expect(err).ShouldNot(HaveOccurred())
228
229 session3, err := Start(exec.Command("sleep", "10000000"), GinkgoWriter, GinkgoWriter)
230 Expect(err).ShouldNot(HaveOccurred())
231
232 TerminateAndWait()
233
234 Expect(session1).Should(Exit(128+15), "Should have exited")
235 Expect(session2).Should(Exit(128+15), "Should have exited")
236 Expect(session3).Should(Exit(128+15), "Should have exited")
237 })
238 })
239
240 Describe("signal", func() {
241 It("should signal all the started sessions", func() {
242 session1, err := Start(exec.Command("sleep", "10000000"), GinkgoWriter, GinkgoWriter)
243 Expect(err).ShouldNot(HaveOccurred())
244
245 session2, err := Start(exec.Command("sleep", "10000000"), GinkgoWriter, GinkgoWriter)
246 Expect(err).ShouldNot(HaveOccurred())
247
248 session3, err := Start(exec.Command("sleep", "10000000"), GinkgoWriter, GinkgoWriter)
249 Expect(err).ShouldNot(HaveOccurred())
250
251 Signal(syscall.SIGABRT)
252
253 Eventually(session1).Should(Exit(128 + 6))
254 Eventually(session2).Should(Exit(128 + 6))
255 Eventually(session3).Should(Exit(128 + 6))
256 })
257 })
258
259 Describe("interrupt", func() {
260 It("should interrupt all the started sessions, and not wait", func() {
261 session1, err := Start(exec.Command("sleep", "10000000"), GinkgoWriter, GinkgoWriter)
262 Expect(err).ShouldNot(HaveOccurred())
263
264 session2, err := Start(exec.Command("sleep", "10000000"), GinkgoWriter, GinkgoWriter)
265 Expect(err).ShouldNot(HaveOccurred())
266
267 session3, err := Start(exec.Command("sleep", "10000000"), GinkgoWriter, GinkgoWriter)
268 Expect(err).ShouldNot(HaveOccurred())
269
270 Interrupt()
271
272 Eventually(session1).Should(Exit(128 + 2))
273 Eventually(session2).Should(Exit(128 + 2))
274 Eventually(session3).Should(Exit(128 + 2))
275 })
276 })
277 })
278
279 When("the command exits", func() {
280 It("should close the buffers", func() {
281 Eventually(session).Should(Exit())
282
283 Expect(session.Out.Closed()).Should(BeTrue())
284 Expect(session.Err.Closed()).Should(BeTrue())
285
286 Expect(session.Out).Should(Say("We've done the impossible, and that makes us mighty"))
287 })
288
289 var So = It
290
291 So("this means that eventually should short circuit", func() {
292 t := time.Now()
293 failures := InterceptGomegaFailures(func() {
294 Eventually(session).Should(Say("blah blah blah blah blah"))
295 })
296 Expect(time.Since(t)).Should(BeNumerically("<=", 500*time.Millisecond))
297 Expect(failures).Should(HaveLen(1))
298 })
299 })
300
301 When("wrapping out and err", func() {
302 var (
303 outWriterBuffer, errWriterBuffer *Buffer
304 )
305
306 BeforeEach(func() {
307 outWriterBuffer = NewBuffer()
308 outWriter = outWriterBuffer
309 errWriterBuffer = NewBuffer()
310 errWriter = errWriterBuffer
311 })
312
313 It("should route to both the provided writers and the gbytes buffers", func() {
314 Eventually(session.Out).Should(Say("We've done the impossible, and that makes us mighty"))
315 Eventually(session.Err).Should(Say("Ah, curse your sudden but inevitable betrayal!"))
316
317 Expect(outWriterBuffer.Contents()).Should(ContainSubstring("We've done the impossible, and that makes us mighty"))
318 Expect(errWriterBuffer.Contents()).Should(ContainSubstring("Ah, curse your sudden but inevitable betrayal!"))
319
320 Eventually(session).Should(Exit())
321
322 Expect(outWriterBuffer.Contents()).Should(Equal(session.Out.Contents()))
323 Expect(errWriterBuffer.Contents()).Should(Equal(session.Err.Contents()))
324 })
325
326 When("discarding the output of the command", func() {
327 BeforeEach(func() {
328 outWriter = ioutil.Discard
329 errWriter = ioutil.Discard
330 })
331
332 It("executes succesfuly", func() {
333 Eventually(session).Should(Exit())
334 })
335 })
336 })
27337 })
28338
29 JustBeforeEach(func() {
30 command = exec.Command(fireflyPath)
31 var err error
32 session, err = Start(command, outWriter, errWriter)
33 Expect(err).ShouldNot(HaveOccurred())
34 })
35
36 Context("running a command", func() {
37 It("should start the process", func() {
38 Expect(command.Process).ShouldNot(BeNil())
39 })
40
41 It("should wrap the process's stdout and stderr with gbytes buffers", func(done Done) {
42 Eventually(session.Out).Should(Say("We've done the impossible, and that makes us mighty"))
43 Eventually(session.Err).Should(Say("Ah, curse your sudden but inevitable betrayal!"))
44 defer session.Out.CancelDetects()
45
46 select {
47 case <-session.Out.Detect("Can we maybe vote on the whole murdering people issue"):
48 Eventually(session).Should(Exit(0))
49 case <-session.Out.Detect("I swear by my pretty floral bonnet, I will end you."):
50 Eventually(session).Should(Exit(1))
51 case <-session.Out.Detect("My work's illegal, but at least it's honest."):
52 Eventually(session).Should(Exit(2))
53 }
54
55 close(done)
56 })
57
58 It("should satisfy the gbytes.BufferProvider interface, passing Stdout", func() {
59 Eventually(session).Should(Say("We've done the impossible, and that makes us mighty"))
60 Eventually(session).Should(Exit())
61 })
62 })
63
64 Describe("providing the exit code", func() {
65 It("should provide the app's exit code", func() {
66 Expect(session.ExitCode()).Should(Equal(-1))
67
68 Eventually(session).Should(Exit())
69 Expect(session.ExitCode()).Should(BeNumerically(">=", 0))
70 Expect(session.ExitCode()).Should(BeNumerically("<", 3))
71 })
72 })
73
74 Describe("wait", func() {
75 It("should wait till the command exits", func() {
76 Expect(session.ExitCode()).Should(Equal(-1))
77 Expect(session.Wait().ExitCode()).Should(BeNumerically(">=", 0))
78 Expect(session.Wait().ExitCode()).Should(BeNumerically("<", 3))
79 })
80 })
81
82 Describe("exited", func() {
83 It("should close when the command exits", func() {
84 Eventually(session.Exited).Should(BeClosed())
85 Expect(session.ExitCode()).ShouldNot(Equal(-1))
86 })
87 })
88
89 Describe("kill", func() {
90 It("should kill the command", func() {
91 session, err := Start(exec.Command("sleep", "10000000"), GinkgoWriter, GinkgoWriter)
339 Context("firefly tests", func() {
340 var fireflyTestPath string
341 var command *exec.Cmd
342 var session *Session
343
344 var outWriter, errWriter io.Writer
345
346 BeforeEach(func() {
347 outWriter = nil
348 errWriter = nil
349
350 var err error
351 fireflyTestPath, err = CompileTest("./_fixture/firefly")
92352 Expect(err).ShouldNot(HaveOccurred())
93
94 session.Kill()
95 Eventually(session).Should(Exit(128 + 9))
96 })
97 })
98
99 Describe("interrupt", func() {
100 It("should interrupt the command", func() {
101 session, err := Start(exec.Command("sleep", "10000000"), GinkgoWriter, GinkgoWriter)
353 })
354
355 JustBeforeEach(func() {
356 command = exec.Command(fireflyTestPath)
357 var err error
358 session, err = Start(command, outWriter, errWriter)
102359 Expect(err).ShouldNot(HaveOccurred())
103
104 session.Interrupt()
105 Eventually(session).Should(Exit(128 + 2))
106 })
107 })
108
109 Describe("terminate", func() {
110 It("should terminate the command", func() {
111 session, err := Start(exec.Command("sleep", "10000000"), GinkgoWriter, GinkgoWriter)
112 Expect(err).ShouldNot(HaveOccurred())
113
114 session.Terminate()
115 Eventually(session).Should(Exit(128 + 15))
116 })
117 })
118
119 Describe("signal", func() {
120 It("should send the signal to the command", func() {
121 session, err := Start(exec.Command("sleep", "10000000"), GinkgoWriter, GinkgoWriter)
122 Expect(err).ShouldNot(HaveOccurred())
123
124 session.Signal(syscall.SIGABRT)
125 Eventually(session).Should(Exit(128 + 6))
126 })
127
128 It("should ignore sending a signal if the command did not start", func() {
129 session, err := Start(exec.Command("notexisting"), GinkgoWriter, GinkgoWriter)
130 Expect(err).To(HaveOccurred())
131
132 Expect(func() { session.Signal(syscall.SIGUSR1) }).NotTo(Panic())
133 })
134 })
135
136 Context("tracking sessions", func() {
137 BeforeEach(func() {
138 KillAndWait()
139 })
140
141 Describe("kill", func() {
142 It("should kill all the started sessions", func() {
143 session1, err := Start(exec.Command("sleep", "10000000"), GinkgoWriter, GinkgoWriter)
144 Expect(err).ShouldNot(HaveOccurred())
145
146 session2, err := Start(exec.Command("sleep", "10000000"), GinkgoWriter, GinkgoWriter)
147 Expect(err).ShouldNot(HaveOccurred())
148
149 session3, err := Start(exec.Command("sleep", "10000000"), GinkgoWriter, GinkgoWriter)
150 Expect(err).ShouldNot(HaveOccurred())
151
152 Kill()
153
154 Eventually(session1).Should(Exit(128 + 9))
155 Eventually(session2).Should(Exit(128 + 9))
156 Eventually(session3).Should(Exit(128 + 9))
157 })
158
159 It("should not track unstarted sessions", func() {
160 _, err := Start(exec.Command("does not exist", "10000000"), GinkgoWriter, GinkgoWriter)
161 Expect(err).Should(HaveOccurred())
162
163 session2, err := Start(exec.Command("sleep", "10000000"), GinkgoWriter, GinkgoWriter)
164 Expect(err).ShouldNot(HaveOccurred())
165
166 session3, err := Start(exec.Command("sleep", "10000000"), GinkgoWriter, GinkgoWriter)
167 Expect(err).ShouldNot(HaveOccurred())
168
169 Kill()
170
171 Eventually(session2).Should(Exit(128 + 9))
172 Eventually(session3).Should(Exit(128 + 9))
173 })
174
175 })
176
177 Describe("killAndWait", func() {
178 It("should kill all the started sessions and wait for them to finish", func() {
179 session1, err := Start(exec.Command("sleep", "10000000"), GinkgoWriter, GinkgoWriter)
180 Expect(err).ShouldNot(HaveOccurred())
181
182 session2, err := Start(exec.Command("sleep", "10000000"), GinkgoWriter, GinkgoWriter)
183 Expect(err).ShouldNot(HaveOccurred())
184
185 session3, err := Start(exec.Command("sleep", "10000000"), GinkgoWriter, GinkgoWriter)
186 Expect(err).ShouldNot(HaveOccurred())
187
188 KillAndWait()
189 Expect(session1).Should(Exit(128+9), "Should have exited")
190 Expect(session2).Should(Exit(128+9), "Should have exited")
191 Expect(session3).Should(Exit(128+9), "Should have exited")
192 })
193 })
194
195 Describe("terminate", func() {
196 It("should terminate all the started sessions", func() {
197 session1, err := Start(exec.Command("sleep", "10000000"), GinkgoWriter, GinkgoWriter)
198 Expect(err).ShouldNot(HaveOccurred())
199
200 session2, err := Start(exec.Command("sleep", "10000000"), GinkgoWriter, GinkgoWriter)
201 Expect(err).ShouldNot(HaveOccurred())
202
203 session3, err := Start(exec.Command("sleep", "10000000"), GinkgoWriter, GinkgoWriter)
204 Expect(err).ShouldNot(HaveOccurred())
205
206 Terminate()
207
208 Eventually(session1).Should(Exit(128 + 15))
209 Eventually(session2).Should(Exit(128 + 15))
210 Eventually(session3).Should(Exit(128 + 15))
211 })
212 })
213
214 Describe("terminateAndWait", func() {
215 It("should terminate all the started sessions, and wait for them to exit", func() {
216 session1, err := Start(exec.Command("sleep", "10000000"), GinkgoWriter, GinkgoWriter)
217 Expect(err).ShouldNot(HaveOccurred())
218
219 session2, err := Start(exec.Command("sleep", "10000000"), GinkgoWriter, GinkgoWriter)
220 Expect(err).ShouldNot(HaveOccurred())
221
222 session3, err := Start(exec.Command("sleep", "10000000"), GinkgoWriter, GinkgoWriter)
223 Expect(err).ShouldNot(HaveOccurred())
224
225 TerminateAndWait()
226
227 Expect(session1).Should(Exit(128+15), "Should have exited")
228 Expect(session2).Should(Exit(128+15), "Should have exited")
229 Expect(session3).Should(Exit(128+15), "Should have exited")
230 })
231 })
232
233 Describe("signal", func() {
234 It("should signal all the started sessions", func() {
235 session1, err := Start(exec.Command("sleep", "10000000"), GinkgoWriter, GinkgoWriter)
236 Expect(err).ShouldNot(HaveOccurred())
237
238 session2, err := Start(exec.Command("sleep", "10000000"), GinkgoWriter, GinkgoWriter)
239 Expect(err).ShouldNot(HaveOccurred())
240
241 session3, err := Start(exec.Command("sleep", "10000000"), GinkgoWriter, GinkgoWriter)
242 Expect(err).ShouldNot(HaveOccurred())
243
244 Signal(syscall.SIGABRT)
245
246 Eventually(session1).Should(Exit(128 + 6))
247 Eventually(session2).Should(Exit(128 + 6))
248 Eventually(session3).Should(Exit(128 + 6))
249 })
250 })
251
252 Describe("interrupt", func() {
253 It("should interrupt all the started sessions, and not wait", func() {
254 session1, err := Start(exec.Command("sleep", "10000000"), GinkgoWriter, GinkgoWriter)
255 Expect(err).ShouldNot(HaveOccurred())
256
257 session2, err := Start(exec.Command("sleep", "10000000"), GinkgoWriter, GinkgoWriter)
258 Expect(err).ShouldNot(HaveOccurred())
259
260 session3, err := Start(exec.Command("sleep", "10000000"), GinkgoWriter, GinkgoWriter)
261 Expect(err).ShouldNot(HaveOccurred())
262
263 Interrupt()
264
265 Eventually(session1).Should(Exit(128 + 2))
266 Eventually(session2).Should(Exit(128 + 2))
267 Eventually(session3).Should(Exit(128 + 2))
268 })
269 })
270 })
271
272 When("the command exits", func() {
273 It("should close the buffers", func() {
274 Eventually(session).Should(Exit())
275
276 Expect(session.Out.Closed()).Should(BeTrue())
277 Expect(session.Err.Closed()).Should(BeTrue())
278
279 Expect(session.Out).Should(Say("We've done the impossible, and that makes us mighty"))
280 })
281
282 var So = It
283
284 So("this means that eventually should short circuit", func() {
285 t := time.Now()
286 failures := InterceptGomegaFailures(func() {
287 Eventually(session).Should(Say("blah blah blah blah blah"))
288 })
289 Expect(time.Since(t)).Should(BeNumerically("<=", 500*time.Millisecond))
290 Expect(failures).Should(HaveLen(1))
291 })
292 })
293
294 When("wrapping out and err", func() {
295 var (
296 outWriterBuffer, errWriterBuffer *Buffer
297 )
298
299 BeforeEach(func() {
300 outWriterBuffer = NewBuffer()
301 outWriter = outWriterBuffer
302 errWriterBuffer = NewBuffer()
303 errWriter = errWriterBuffer
304 })
305
306 It("should route to both the provided writers and the gbytes buffers", func() {
307 Eventually(session.Out).Should(Say("We've done the impossible, and that makes us mighty"))
308 Eventually(session.Err).Should(Say("Ah, curse your sudden but inevitable betrayal!"))
309
310 Expect(outWriterBuffer.Contents()).Should(ContainSubstring("We've done the impossible, and that makes us mighty"))
311 Expect(errWriterBuffer.Contents()).Should(ContainSubstring("Ah, curse your sudden but inevitable betrayal!"))
312
313 Eventually(session).Should(Exit())
314
315 Expect(outWriterBuffer.Contents()).Should(Equal(session.Out.Contents()))
316 Expect(errWriterBuffer.Contents()).Should(Equal(session.Err.Contents()))
317 })
318
319 When("discarding the output of the command", func() {
360 })
361
362 When("wrapping out and err", func() {
363 var (
364 outWriterBuffer, errWriterBuffer *Buffer
365 )
366
320367 BeforeEach(func() {
321 outWriter = ioutil.Discard
322 errWriter = ioutil.Discard
323 })
324
325 It("executes succesfuly", func() {
368 outWriterBuffer = NewBuffer()
369 outWriter = outWriterBuffer
370 errWriterBuffer = NewBuffer()
371 errWriter = errWriterBuffer
372 })
373
374 It("should route to both the provided writers and the gbytes buffers", func() {
375 Eventually(session.Out).Should(Say("PASS"))
376 Eventually(session.Err).Should(Say(""))
377
378 Expect(outWriterBuffer.Contents()).Should(ContainSubstring("PASS"))
379 Expect(errWriterBuffer.Contents()).Should(BeEmpty())
380
326381 Eventually(session).Should(Exit())
382
383 Expect(outWriterBuffer.Contents()).Should(Equal(session.Out.Contents()))
384 Expect(errWriterBuffer.Contents()).Should(Equal(session.Err.Contents()))
385 })
386
387 When("discarding the output of the command", func() {
388 BeforeEach(func() {
389 outWriter = ioutil.Discard
390 errWriter = ioutil.Discard
391 })
392
393 It("executes succesfuly", func() {
394 Eventually(session).Should(Exit())
395 })
327396 })
328397 })
329398 })
108108 //(recall that a `http.Header` is a mapping from string (key) to []string (values))
109109 //It is a convenience wrapper around `VerifyHeader` that allows you to avoid having to create an `http.Header` object.
110110 func (g GHTTPWithGomega) VerifyHeaderKV(key string, values ...string) http.HandlerFunc {
111 return VerifyHeader(http.Header{key: values})
111 return g.VerifyHeader(http.Header{key: values})
112112 }
113113
114114 //VerifyBody returns a handler that verifies that the body of the request matches the passed in byte array.
130130 //VerifyJSON also verifies that the request's content type is application/json
131131 func (g GHTTPWithGomega) VerifyJSON(expectedJSON string) http.HandlerFunc {
132132 return CombineHandlers(
133 VerifyMimeType("application/json"),
133 g.VerifyMimeType("application/json"),
134134 func(w http.ResponseWriter, req *http.Request) {
135135 body, err := ioutil.ReadAll(req.Body)
136136 req.Body.Close()
147147 data, err := json.Marshal(object)
148148 g.gomega.Expect(err).ShouldNot(HaveOccurred())
149149 return CombineHandlers(
150 VerifyMimeType("application/json"),
151 VerifyJSON(string(data)),
150 g.VerifyMimeType("application/json"),
151 g.VerifyJSON(string(data)),
152152 )
153153 }
154154
170170 //
171171 //It is a convenience wrapper around `VerifyForm` that lets you avoid having to create a `url.Values` object.
172172 func (g GHTTPWithGomega) VerifyFormKV(key string, values ...string) http.HandlerFunc {
173 return VerifyForm(url.Values{key: values})
173 return g.VerifyForm(url.Values{key: values})
174174 }
175175
176176 //VerifyProtoRepresenting returns a handler that verifies that the body of the request is a valid protobuf
179179 //VerifyProtoRepresenting also verifies that the request's content type is application/x-protobuf
180180 func (g GHTTPWithGomega) VerifyProtoRepresenting(expected proto.Message) http.HandlerFunc {
181181 return CombineHandlers(
182 VerifyContentType("application/x-protobuf"),
182 g.VerifyContentType("application/x-protobuf"),
183183 func(w http.ResponseWriter, req *http.Request) {
184184 body, err := ioutil.ReadAll(req.Body)
185185 g.gomega.Expect(err).ShouldNot(HaveOccurred())
258258 s.AppendHandlers(func(w http.ResponseWriter, req *http.Request) {
259259 // Expect(true).Should(BeFalse()) <-- would be nice to do it this way, but the test just can't be written this way
260260
261 By("We're cheating a bit here -- we're throwing a GINKGO_PANIC which simulates a failed assertion")
262 panic(GINKGO_PANIC)
261 By("We're cheating a bit here -- we're pretending to throw a Ginkgo panic which simulates a failed assertion")
262 panic("defer GinkgoRecover()")
263263 })
264264 })
265265
0 package gmeasure
1
2 import (
3 "crypto/md5"
4 "encoding/json"
5 "fmt"
6 "io/ioutil"
7 "os"
8 "path/filepath"
9 )
10
11 const CACHE_EXT = ".gmeasure-cache"
12
13 /*
14 ExperimentCache provides a director-and-file based cache of experiments
15 */
16 type ExperimentCache struct {
17 Path string
18 }
19
20 /*
21 NewExperimentCache creates and initializes a new cache. Path must point to a directory (if path does not exist, NewExperimentCache will create a directory at path).
22
23 Cached Experiments are stored as separate files in the cache directory - the filename is a hash of the Experiment name. Each file contains two JSON-encoded objects - a CachedExperimentHeader that includes the experiment's name and cache version number, and then the Experiment itself.
24 */
25 func NewExperimentCache(path string) (ExperimentCache, error) {
26 stat, err := os.Stat(path)
27 if os.IsNotExist(err) {
28 err := os.MkdirAll(path, 0777)
29 if err != nil {
30 return ExperimentCache{}, err
31 }
32 } else if !stat.IsDir() {
33 return ExperimentCache{}, fmt.Errorf("%s is not a directory", path)
34 }
35
36 return ExperimentCache{
37 Path: path,
38 }, nil
39 }
40
41 /*
42 CachedExperimentHeader captures the name of the Cached Experiment and its Version
43 */
44 type CachedExperimentHeader struct {
45 Name string
46 Version int
47 }
48
49 func (cache ExperimentCache) hashOf(name string) string {
50 return fmt.Sprintf("%x", md5.Sum([]byte(name)))
51 }
52
53 func (cache ExperimentCache) readHeader(filename string) (CachedExperimentHeader, error) {
54 out := CachedExperimentHeader{}
55 f, err := os.Open(filepath.Join(cache.Path, filename))
56 if err != nil {
57 return out, err
58 }
59 defer f.Close()
60 err = json.NewDecoder(f).Decode(&out)
61 return out, err
62 }
63
64 /*
65 List returns a list of all Cached Experiments found in the cache.
66 */
67 func (cache ExperimentCache) List() ([]CachedExperimentHeader, error) {
68 out := []CachedExperimentHeader{}
69 infos, err := ioutil.ReadDir(cache.Path)
70 if err != nil {
71 return out, err
72 }
73 for _, info := range infos {
74 if filepath.Ext(info.Name()) != CACHE_EXT {
75 continue
76 }
77 header, err := cache.readHeader(info.Name())
78 if err != nil {
79 return out, err
80 }
81 out = append(out, header)
82 }
83 return out, nil
84 }
85
86 /*
87 Clear empties out the cache - this will delete any and all detected cache files in the cache directory. Use with caution!
88 */
89 func (cache ExperimentCache) Clear() error {
90 infos, err := ioutil.ReadDir(cache.Path)
91 if err != nil {
92 return err
93 }
94 for _, info := range infos {
95 if filepath.Ext(info.Name()) != CACHE_EXT {
96 continue
97 }
98 err := os.Remove(filepath.Join(cache.Path, info.Name()))
99 if err != nil {
100 return err
101 }
102 }
103 return nil
104 }
105
106 /*
107 Load fetches an experiment from the cache. Lookup occurs by name. Load requires that the version numer in the cache is equal to or greater than the passed-in version.
108
109 If an experiment with corresponding name and version >= the passed-in version is found, it is unmarshaled and returned.
110
111 If no experiment is found, or the cached version is smaller than the passed-in version, Load will return nil.
112
113 When paired with Ginkgo you can cache experiments and prevent potentially expensive recomputation with this pattern:
114
115 const EXPERIMENT_VERSION = 1 //bump this to bust the cache and recompute _all_ experiments
116
117 Describe("some experiments", func() {
118 var cache gmeasure.ExperimentCache
119 var experiment *gmeasure.Experiment
120
121 BeforeEach(func() {
122 cache = gmeasure.NewExperimentCache("./gmeasure-cache")
123 name := CurrentSpecReport().LeafNodeText
124 experiment = cache.Load(name, EXPERIMENT_VERSION)
125 if experiment != nil {
126 AddReportEntry(experiment)
127 Skip("cached")
128 }
129 experiment = gmeasure.NewExperiment(name)
130 AddReportEntry(experiment)
131 })
132
133 It("foo runtime", func() {
134 experiment.SampleDuration("runtime", func() {
135 //do stuff
136 }, gmeasure.SamplingConfig{N:100})
137 })
138
139 It("bar runtime", func() {
140 experiment.SampleDuration("runtime", func() {
141 //do stuff
142 }, gmeasure.SamplingConfig{N:100})
143 })
144
145 AfterEach(func() {
146 if !CurrentSpecReport().State.Is(types.SpecStateSkipped) {
147 cache.Save(experiment.Name, EXPERIMENT_VERSION, experiment)
148 }
149 })
150 })
151 */
152 func (cache ExperimentCache) Load(name string, version int) *Experiment {
153 path := filepath.Join(cache.Path, cache.hashOf(name)+CACHE_EXT)
154 f, err := os.Open(path)
155 if err != nil {
156 return nil
157 }
158 defer f.Close()
159 dec := json.NewDecoder(f)
160 header := CachedExperimentHeader{}
161 dec.Decode(&header)
162 if header.Version < version {
163 return nil
164 }
165 out := NewExperiment("")
166 err = dec.Decode(out)
167 if err != nil {
168 return nil
169 }
170 return out
171 }
172
173 /*
174 Save stores the passed-in experiment to the cache with the passed-in name and version.
175 */
176 func (cache ExperimentCache) Save(name string, version int, experiment *Experiment) error {
177 path := filepath.Join(cache.Path, cache.hashOf(name)+CACHE_EXT)
178 f, err := os.Create(path)
179 if err != nil {
180 return err
181 }
182 defer f.Close()
183 enc := json.NewEncoder(f)
184 err = enc.Encode(CachedExperimentHeader{
185 Name: name,
186 Version: version,
187 })
188 if err != nil {
189 return err
190 }
191 return enc.Encode(experiment)
192 }
193
194 /*
195 Delete removes the experiment with the passed-in name from the cache
196 */
197 func (cache ExperimentCache) Delete(name string) error {
198 path := filepath.Join(cache.Path, cache.hashOf(name)+CACHE_EXT)
199 return os.Remove(path)
200 }
0 package gmeasure_test
1
2 import (
3 "fmt"
4 "os"
5
6 . "github.com/onsi/ginkgo"
7 . "github.com/onsi/gomega"
8 "github.com/onsi/gomega/gmeasure"
9 )
10
11 var _ = Describe("Cache", func() {
12 var path string
13 var cache gmeasure.ExperimentCache
14 var e1, e2 *gmeasure.Experiment
15
16 BeforeEach(func() {
17 var err error
18 path = fmt.Sprintf("./cache-%d", GinkgoParallelNode())
19 cache, err = gmeasure.NewExperimentCache(path)
20 Ω(err).ShouldNot(HaveOccurred())
21 e1 = gmeasure.NewExperiment("Experiment-1")
22 e1.RecordValue("foo", 32)
23 e2 = gmeasure.NewExperiment("Experiment-2")
24 e2.RecordValue("bar", 64)
25 })
26
27 AfterEach(func() {
28 Ω(os.RemoveAll(path)).Should(Succeed())
29 })
30
31 Describe("when creating a cache that points to a file", func() {
32 It("errors", func() {
33 f, err := os.Create("cache-temp-file")
34 Ω(err).ShouldNot(HaveOccurred())
35 f.Close()
36 cache, err := gmeasure.NewExperimentCache("cache-temp-file")
37 Ω(err).Should(MatchError("cache-temp-file is not a directory"))
38 Ω(cache).Should(BeZero())
39 Ω(os.RemoveAll("cache-temp-file")).Should(Succeed())
40 })
41 })
42
43 Describe("the happy path", func() {
44 It("can save, load, list, delete, and clear the cache", func() {
45 Ω(cache.Save("e1", 1, e1)).Should(Succeed())
46 Ω(cache.Save("e2", 7, e2)).Should(Succeed())
47
48 Ω(cache.Load("e1", 1)).Should(Equal(e1))
49 Ω(cache.Load("e2", 7)).Should(Equal(e2))
50
51 Ω(cache.List()).Should(ConsistOf(
52 gmeasure.CachedExperimentHeader{"e1", 1},
53 gmeasure.CachedExperimentHeader{"e2", 7},
54 ))
55
56 Ω(cache.Delete("e2")).Should(Succeed())
57 Ω(cache.Load("e1", 1)).Should(Equal(e1))
58 Ω(cache.Load("e2", 7)).Should(BeNil())
59 Ω(cache.List()).Should(ConsistOf(
60 gmeasure.CachedExperimentHeader{"e1", 1},
61 ))
62
63 Ω(cache.Clear()).Should(Succeed())
64 Ω(cache.List()).Should(BeEmpty())
65 Ω(cache.Load("e1", 1)).Should(BeNil())
66 Ω(cache.Load("e2", 7)).Should(BeNil())
67 })
68 })
69
70 Context("with an empty cache", func() {
71 It("should list nothing", func() {
72 Ω(cache.List()).Should(BeEmpty())
73 })
74
75 It("should not error when clearing", func() {
76 Ω(cache.Clear()).Should(Succeed())
77 })
78
79 It("returs nil when loading a non-existing experiment", func() {
80 Ω(cache.Load("floop", 17)).Should(BeNil())
81 })
82 })
83
84 Describe("version management", func() {
85 BeforeEach(func() {
86 Ω(cache.Save("e1", 7, e1)).Should(Succeed())
87 })
88
89 Context("when the cached version is older than the requested version", func() {
90 It("returns nil", func() {
91 Ω(cache.Load("e1", 8)).Should(BeNil())
92 })
93 })
94
95 Context("when the cached version equals the requested version", func() {
96 It("returns the cached version", func() {
97 Ω(cache.Load("e1", 7)).Should(Equal(e1))
98 })
99 })
100
101 Context("when the cached version is newer than the requested version", func() {
102 It("returns the cached version", func() {
103 Ω(cache.Load("e1", 6)).Should(Equal(e1))
104 })
105 })
106 })
107
108 })
0 package gmeasure
1
2 import "encoding/json"
3
4 type enumSupport struct {
5 toString map[uint]string
6 toEnum map[string]uint
7 maxEnum uint
8 }
9
10 func newEnumSupport(toString map[uint]string) enumSupport {
11 toEnum, maxEnum := map[string]uint{}, uint(0)
12 for k, v := range toString {
13 toEnum[v] = k
14 if maxEnum < k {
15 maxEnum = k
16 }
17 }
18 return enumSupport{toString: toString, toEnum: toEnum, maxEnum: maxEnum}
19 }
20
21 func (es enumSupport) String(e uint) string {
22 if e > es.maxEnum {
23 return es.toString[0]
24 }
25 return es.toString[e]
26 }
27
28 func (es enumSupport) UnmarshJSON(b []byte) (uint, error) {
29 var dec string
30 if err := json.Unmarshal(b, &dec); err != nil {
31 return 0, err
32 }
33 out := es.toEnum[dec] // if we miss we get 0 which is what we want anyway
34 return out, nil
35 }
36
37 func (es enumSupport) MarshJSON(e uint) ([]byte, error) {
38 if e == 0 || e > es.maxEnum {
39 return json.Marshal(nil)
40 }
41 return json.Marshal(es.toString[e])
42 }
0 /*
1 Package gomega/gmeasure provides support for benchmarking and measuring code. It is intended as a more robust replacement for Ginkgo V1's Measure nodes.
2
3 **gmeasure IS CURRENTLY IN BETA - THE API MAY CHANGE IN THE NEAR-FUTURE. gmeasure WILL BE CONSIDERED GA WHEN Ginkgo V2 IS GA.
4
5 gmeasure is organized around the metaphor of an Experiment that can record multiple Measurements. A Measurement is a named collection of data points and gmeasure supports
6 measuring Values (of type float64) and Durations (of type time.Duration).
7
8 Experiments allows the user to record Measurements directly by passing in Values (i.e. float64) or Durations (i.e. time.Duration)
9 or to measure measurements by passing in functions to measure. When measuring functions Experiments take care of timing the duration of functions (for Duration measurements)
10 and/or recording returned values (for Value measurements). Experiments also support sampling functions - when told to sample Experiments will run functions repeatedly
11 and measure and record results. The sampling behavior is configured by passing in a SamplingConfig that can control the maximum number of samples, the maximum duration for sampling (or both)
12 and the number of concurrent samples to take.
13
14 Measurements can be decorated with additional information. This is supported by passing in special typed decorators when recording measurements. These include:
15
16 - Units("any string") - to attach units to a Value Measurement (Duration Measurements always have units of "duration")
17 - Style("any Ginkgo color style string") - to attach styling to a Measurement. This styling is used when rendering console information about the measurement in reports. Color style strings are documented at TODO.
18 - Precision(integer or time.Duration) - to attach precision to a Measurement. This controls how many decimal places to show for Value Measurements and how to round Duration Measurements when rendering them to screen.
19
20 In addition, individual data points in a Measurement can be annotated with an Annotation("any string"). The annotation is associated with the individual data point and is intended to convey additional context about the data point.
21
22 Once measurements are complete, an Experiment can generate a comprehensive report by calling its String() or ColorableString() method.
23
24 Users can also access and analyze the resulting Measurements directly. Use Experiment.Get(NAME) to fetch the Measurement named NAME. This returned struct will have fields containing
25 all the data points and annotations recorded by the experiment. You can subsequently fetch the Measurement.Stats() to get a Stats struct that contains basic statistical information about the
26 Measurement (min, max, median, mean, standard deviation). You can order these Stats objects using RankStats() to identify best/worst performers across multpile experiments or measurements.
27
28 gmeasure also supports caching Experiments via an ExperimentCache. The cache supports storing and retreiving experiments by name and version. This allows you to rerun code without
29 repeating expensive experiments that may not have changed (which can be controlled by the cache version number). It also enables you to compare new experiment runs with older runs to detect
30 variations in performance/behavior.
31
32 When used with Ginkgo, you can emit experiment reports and encode them in test reports easily using Ginkgo V2's support for Report Entries.
33 Simply pass your experiment to AddReportEntry to get a report every time the tests run. You can also use AddReportEntry with Measurements to emit all the captured data
34 and Rankings to emit measurement summaries in rank order.
35
36 Finally, Experiments provide an additional mechanism to measure durations called a Stopwatch. The Stopwatch makes it easy to pepper code with statements that measure elapsed time across
37 different sections of code and can be useful when debugging or evaluating bottlenecks in a given codepath.
38 */
39 package gmeasure
40
41 import (
42 "fmt"
43 "math"
44 "reflect"
45 "sync"
46 "time"
47
48 "github.com/onsi/gomega/gmeasure/table"
49 )
50
51 /*
52 SamplingConfig configures the Sample family of experiment methods.
53 These methods invoke passed-in functions repeatedly to sample and record a given measurement.
54 SamplingConfig is used to control the maximum number of samples or time spent sampling (or both). When both are specified sampling ends as soon as one of the conditions is met.
55 SamplingConfig can also ensure a minimum interval between samples and can enable concurrent sampling.
56 */
57 type SamplingConfig struct {
58 // N - the maximum number of samples to record
59 N int
60 // Duration - the maximum amount of time to spend recording samples
61 Duration time.Duration
62 // MinSamplingInterval - the minimum time that must elapse between samplings. It is an error to specify both MinSamplingInterval and NumParallel.
63 MinSamplingInterval time.Duration
64 // NumParallel - the number of parallel workers to spin up to record samples. It is an error to specify both MinSamplingInterval and NumParallel.
65 NumParallel int
66 }
67
68 // The Units decorator allows you to specify units (an arbitrary string) when recording values. It is ignored when recording durations.
69 //
70 // e := gmeasure.NewExperiment("My Experiment")
71 // e.RecordValue("length", 3.141, gmeasure.Units("inches"))
72 //
73 // Units are only set the first time a value of a given name is recorded. In the example above any subsequent calls to e.RecordValue("length", X) will maintain the "inches" units even if a new set of Units("UNIT") are passed in later.
74 type Units string
75
76 // The Annotation decorator allows you to attach an annotation to a given recorded data-point:
77 //
78 // For example:
79 //
80 // e := gmeasure.NewExperiment("My Experiment")
81 // e.RecordValue("length", 3.141, gmeasure.Annotation("bob"))
82 // e.RecordValue("length", 2.71, gmeasure.Annotation("jane"))
83 //
84 // ...will result in a Measurement named "length" that records two values )[3.141, 2.71]) annotation with (["bob", "jane"])
85 type Annotation string
86
87 // The Style decorator allows you to associate a style with a measurement. This is used to generate colorful console reports using Ginkgo V2's
88 // console formatter. Styles are strings in curly brackets that correspond to a color or style.
89 //
90 // For example:
91 //
92 // e := gmeasure.NewExperiment("My Experiment")
93 // e.RecordValue("length", 3.141, gmeasure.Style("{{blue}}{{bold}}"))
94 // e.RecordValue("length", 2.71)
95 // e.RecordDuration("cooking time", 3 * time.Second, gmeasure.Style("{{red}}{{underline}}"))
96 // e.RecordDuration("cooking time", 2 * time.Second)
97 //
98 // will emit a report with blue bold entries for the length measurement and red underlined entries for the cooking time measurement.
99 //
100 // Units are only set the first time a value or duration of a given name is recorded. In the example above any subsequent calls to e.RecordValue("length", X) will maintain the "{{blue}}{{bold}}" style even if a new Style is passed in later.
101 type Style string
102
103 // The PrecisionBundle decorator controls the rounding of value and duration measurements. See Precision().
104 type PrecisionBundle struct {
105 Duration time.Duration
106 ValueFormat string
107 }
108
109 // Precision() allows you to specify the precision of a value or duration measurement - this precision is used when rendering the measurement to screen.
110 //
111 // To control the precision of Value measurements, pass Precision an integer. This will denote the number of decimal places to render (equivalen to the format string "%.Nf")
112 // To control the precision of Duration measurements, pass Precision a time.Duration. Duration measurements will be rounded oo the nearest time.Duration when rendered.
113 //
114 // For example:
115 //
116 // e := gmeasure.NewExperiment("My Experiment")
117 // e.RecordValue("length", 3.141, gmeasure.Precision(2))
118 // e.RecordValue("length", 2.71)
119 // e.RecordDuration("cooking time", 3214 * time.Millisecond, gmeasure.Precision(100*time.Millisecond))
120 // e.RecordDuration("cooking time", 2623 * time.Millisecond)
121 func Precision(p interface{}) PrecisionBundle {
122 out := DefaultPrecisionBundle
123 switch reflect.TypeOf(p) {
124 case reflect.TypeOf(time.Duration(0)):
125 out.Duration = p.(time.Duration)
126 case reflect.TypeOf(int(0)):
127 out.ValueFormat = fmt.Sprintf("%%.%df", p.(int))
128 default:
129 panic("invalid precision type, must be time.Duration or int")
130 }
131 return out
132 }
133
134 // DefaultPrecisionBundle captures the default precisions for Vale and Duration measurements.
135 var DefaultPrecisionBundle = PrecisionBundle{
136 Duration: 100 * time.Microsecond,
137 ValueFormat: "%.3f",
138 }
139
140 type extractedDecorations struct {
141 annotation Annotation
142 units Units
143 precisionBundle PrecisionBundle
144 style Style
145 }
146
147 func extractDecorations(args []interface{}) extractedDecorations {
148 var out extractedDecorations
149 out.precisionBundle = DefaultPrecisionBundle
150
151 for _, arg := range args {
152 switch reflect.TypeOf(arg) {
153 case reflect.TypeOf(out.annotation):
154 out.annotation = arg.(Annotation)
155 case reflect.TypeOf(out.units):
156 out.units = arg.(Units)
157 case reflect.TypeOf(out.precisionBundle):
158 out.precisionBundle = arg.(PrecisionBundle)
159 case reflect.TypeOf(out.style):
160 out.style = arg.(Style)
161 default:
162 panic(fmt.Sprintf("unrecognized argument %#v", arg))
163 }
164 }
165
166 return out
167 }
168
169 /*
170 Experiment is gmeasure's core data type. You use experiments to record Measurements and generate reports.
171 Experiments are thread-safe and all methods can be called from multiple goroutines.
172 */
173 type Experiment struct {
174 Name string
175
176 // Measurements includes all Measurements recorded by this experiment. You should access them by name via Get() and GetStats()
177 Measurements Measurements
178 lock *sync.Mutex
179 }
180
181 /*
182 NexExperiment creates a new experiment with the passed-in name.
183
184 When using Ginkgo we recommend immediately registering the experiment as a ReportEntry:
185
186 experiment = NewExperiment("My Experiment")
187 AddReportEntry(experiment.Name, experiment)
188
189 this will ensure an experiment report is emitted as part of the test output and exported with any test reports.
190 */
191 func NewExperiment(name string) *Experiment {
192 experiment := &Experiment{
193 Name: name,
194 lock: &sync.Mutex{},
195 }
196 return experiment
197 }
198
199 func (e *Experiment) report(enableStyling bool) string {
200 t := table.NewTable()
201 t.TableStyle.EnableTextStyling = enableStyling
202 t.AppendRow(table.R(
203 table.C("Name"), table.C("N"), table.C("Min"), table.C("Median"), table.C("Mean"), table.C("StdDev"), table.C("Max"),
204 table.Divider("="),
205 "{{bold}}",
206 ))
207
208 for _, measurement := range e.Measurements {
209 r := table.R(measurement.Style)
210 t.AppendRow(r)
211 switch measurement.Type {
212 case MeasurementTypeNote:
213 r.AppendCell(table.C(measurement.Note))
214 case MeasurementTypeValue, MeasurementTypeDuration:
215 name := measurement.Name
216 if measurement.Units != "" {
217 name += " [" + measurement.Units + "]"
218 }
219 r.AppendCell(table.C(name))
220 r.AppendCell(measurement.Stats().cells()...)
221 }
222 }
223
224 out := e.Name + "\n"
225 if enableStyling {
226 out = "{{bold}}" + out + "{{/}}"
227 }
228 out += t.Render()
229 return out
230 }
231
232 /*
233 ColorableString returns a Ginkgo formatted summary of the experiment and all its Measurements.
234 It is called automatically by Ginkgo's reporting infrastructure when the Experiment is registered as a ReportEntry via AddReportEntry.
235 */
236 func (e *Experiment) ColorableString() string {
237 return e.report(true)
238 }
239
240 /*
241 ColorableString returns an unformatted summary of the experiment and all its Measurements.
242 */
243 func (e *Experiment) String() string {
244 return e.report(false)
245 }
246
247 /*
248 RecordNote records a Measurement of type MeasurementTypeNote - this is simply a textual note to annotate the experiment. It will be emitted in any experiment reports.
249
250 RecordNote supports the Style() decoration.
251 */
252 func (e *Experiment) RecordNote(note string, args ...interface{}) {
253 decorations := extractDecorations(args)
254
255 e.lock.Lock()
256 defer e.lock.Unlock()
257 e.Measurements = append(e.Measurements, Measurement{
258 ExperimentName: e.Name,
259 Type: MeasurementTypeNote,
260 Note: note,
261 Style: string(decorations.style),
262 })
263 }
264
265 /*
266 RecordDuration records the passed-in duration on a Duration Measurement with the passed-in name. If the Measurement does not exist it is created.
267
268 RecordDuration supports the Style(), Precision(), and Annotation() decorations.
269 */
270 func (e *Experiment) RecordDuration(name string, duration time.Duration, args ...interface{}) {
271 decorations := extractDecorations(args)
272 e.recordDuration(name, duration, decorations)
273 }
274
275 /*
276 MeasureDuration runs the passed-in callback and times how long it takes to complete. The resulting duration is recorded on a Duration Measurement with the passed-in name. If the Measurement does not exist it is created.
277
278 MeasureDuration supports the Style(), Precision(), and Annotation() decorations.
279 */
280 func (e *Experiment) MeasureDuration(name string, callback func(), args ...interface{}) time.Duration {
281 t := time.Now()
282 callback()
283 duration := time.Since(t)
284 e.RecordDuration(name, duration, args...)
285 return duration
286 }
287
288 /*
289 SampleDuration samples the passed-in callback and times how long it takes to complete each sample.
290 The resulting durations are recorded on a Duration Measurement with the passed-in name. If the Measurement does not exist it is created.
291
292 The callback is given a zero-based index that increments by one between samples. The Sampling is configured via the passed-in SamplingConfig
293
294 SampleDuration supports the Style(), Precision(), and Annotation() decorations. When passed an Annotation() the same annotation is applied to all sample measurements.
295 */
296 func (e *Experiment) SampleDuration(name string, callback func(idx int), samplingConfig SamplingConfig, args ...interface{}) {
297 decorations := extractDecorations(args)
298 e.Sample(func(idx int) {
299 t := time.Now()
300 callback(idx)
301 duration := time.Since(t)
302 e.recordDuration(name, duration, decorations)
303 }, samplingConfig)
304 }
305
306 /*
307 SampleDuration samples the passed-in callback and times how long it takes to complete each sample.
308 The resulting durations are recorded on a Duration Measurement with the passed-in name. If the Measurement does not exist it is created.
309
310 The callback is given a zero-based index that increments by one between samples. The callback must return an Annotation - this annotation is attached to the measured duration.
311
312 The Sampling is configured via the passed-in SamplingConfig
313
314 SampleAnnotatedDuration supports the Style() and Precision() decorations.
315 */
316 func (e *Experiment) SampleAnnotatedDuration(name string, callback func(idx int) Annotation, samplingConfig SamplingConfig, args ...interface{}) {
317 decorations := extractDecorations(args)
318 e.Sample(func(idx int) {
319 t := time.Now()
320 decorations.annotation = callback(idx)
321 duration := time.Since(t)
322 e.recordDuration(name, duration, decorations)
323 }, samplingConfig)
324 }
325
326 func (e *Experiment) recordDuration(name string, duration time.Duration, decorations extractedDecorations) {
327 e.lock.Lock()
328 defer e.lock.Unlock()
329 idx := e.Measurements.IdxWithName(name)
330 if idx == -1 {
331 measurement := Measurement{
332 ExperimentName: e.Name,
333 Type: MeasurementTypeDuration,
334 Name: name,
335 Units: "duration",
336 Durations: []time.Duration{duration},
337 PrecisionBundle: decorations.precisionBundle,
338 Style: string(decorations.style),
339 Annotations: []string{string(decorations.annotation)},
340 }
341 e.Measurements = append(e.Measurements, measurement)
342 } else {
343 if e.Measurements[idx].Type != MeasurementTypeDuration {
344 panic(fmt.Sprintf("attempting to record duration with name '%s'. That name is already in-use for recording values.", name))
345 }
346 e.Measurements[idx].Durations = append(e.Measurements[idx].Durations, duration)
347 e.Measurements[idx].Annotations = append(e.Measurements[idx].Annotations, string(decorations.annotation))
348 }
349 }
350
351 /*
352 NewStopwatch() returns a stopwatch configured to record duration measurements with this experiment.
353 */
354 func (e *Experiment) NewStopwatch() *Stopwatch {
355 return newStopwatch(e)
356 }
357
358 /*
359 RecordValue records the passed-in value on a Value Measurement with the passed-in name. If the Measurement does not exist it is created.
360
361 RecordValue supports the Style(), Units(), Precision(), and Annotation() decorations.
362 */
363 func (e *Experiment) RecordValue(name string, value float64, args ...interface{}) {
364 decorations := extractDecorations(args)
365 e.recordValue(name, value, decorations)
366 }
367
368 /*
369 MeasureValue runs the passed-in callback and records the return value on a Value Measurement with the passed-in name. If the Measurement does not exist it is created.
370
371 MeasureValue supports the Style(), Units(), Precision(), and Annotation() decorations.
372 */
373 func (e *Experiment) MeasureValue(name string, callback func() float64, args ...interface{}) float64 {
374 value := callback()
375 e.RecordValue(name, value, args...)
376 return value
377 }
378
379 /*
380 SampleValue samples the passed-in callback and records the return value on a Value Measurement with the passed-in name. If the Measurement does not exist it is created.
381
382 The callback is given a zero-based index that increments by one between samples. The callback must return a float64. The Sampling is configured via the passed-in SamplingConfig
383
384 SampleValue supports the Style(), Units(), Precision(), and Annotation() decorations. When passed an Annotation() the same annotation is applied to all sample measurements.
385 */
386 func (e *Experiment) SampleValue(name string, callback func(idx int) float64, samplingConfig SamplingConfig, args ...interface{}) {
387 decorations := extractDecorations(args)
388 e.Sample(func(idx int) {
389 value := callback(idx)
390 e.recordValue(name, value, decorations)
391 }, samplingConfig)
392 }
393
394 /*
395 SampleAnnotatedValue samples the passed-in callback and records the return value on a Value Measurement with the passed-in name. If the Measurement does not exist it is created.
396
397 The callback is given a zero-based index that increments by one between samples. The callback must return a float64 and an Annotation - the annotation is attached to the recorded value.
398
399 The Sampling is configured via the passed-in SamplingConfig
400
401 SampleValue supports the Style(), Units(), and Precision() decorations.
402 */
403 func (e *Experiment) SampleAnnotatedValue(name string, callback func(idx int) (float64, Annotation), samplingConfig SamplingConfig, args ...interface{}) {
404 decorations := extractDecorations(args)
405 e.Sample(func(idx int) {
406 var value float64
407 value, decorations.annotation = callback(idx)
408 e.recordValue(name, value, decorations)
409 }, samplingConfig)
410 }
411
412 func (e *Experiment) recordValue(name string, value float64, decorations extractedDecorations) {
413 e.lock.Lock()
414 defer e.lock.Unlock()
415 idx := e.Measurements.IdxWithName(name)
416 if idx == -1 {
417 measurement := Measurement{
418 ExperimentName: e.Name,
419 Type: MeasurementTypeValue,
420 Name: name,
421 Style: string(decorations.style),
422 Units: string(decorations.units),
423 PrecisionBundle: decorations.precisionBundle,
424 Values: []float64{value},
425 Annotations: []string{string(decorations.annotation)},
426 }
427 e.Measurements = append(e.Measurements, measurement)
428 } else {
429 if e.Measurements[idx].Type != MeasurementTypeValue {
430 panic(fmt.Sprintf("attempting to record value with name '%s'. That name is already in-use for recording durations.", name))
431 }
432 e.Measurements[idx].Values = append(e.Measurements[idx].Values, value)
433 e.Measurements[idx].Annotations = append(e.Measurements[idx].Annotations, string(decorations.annotation))
434 }
435 }
436
437 /*
438 Sample samples the passed-in callback repeatedly. The sampling is governed by the passed in SamplingConfig.
439
440 The SamplingConfig can limit the total number of samples and/or the total time spent sampling the callback.
441 The SamplingConfig can also instruct Sample to run with multiple concurrent workers.
442
443 The callback is called with a zero-based index that incerements by one between samples.
444 */
445 func (e *Experiment) Sample(callback func(idx int), samplingConfig SamplingConfig) {
446 if samplingConfig.N == 0 && samplingConfig.Duration == 0 {
447 panic("you must specify at least one of SamplingConfig.N and SamplingConfig.Duration")
448 }
449 if samplingConfig.MinSamplingInterval > 0 && samplingConfig.NumParallel > 1 {
450 panic("you cannot specify both SamplingConfig.MinSamplingInterval and SamplingConfig.NumParallel")
451 }
452 maxTime := time.Now().Add(100000 * time.Hour)
453 if samplingConfig.Duration > 0 {
454 maxTime = time.Now().Add(samplingConfig.Duration)
455 }
456 maxN := math.MaxInt64
457 if samplingConfig.N > 0 {
458 maxN = samplingConfig.N
459 }
460 numParallel := 1
461 if samplingConfig.NumParallel > numParallel {
462 numParallel = samplingConfig.NumParallel
463 }
464 minSamplingInterval := samplingConfig.MinSamplingInterval
465
466 work := make(chan int)
467 if numParallel > 1 {
468 for worker := 0; worker < numParallel; worker++ {
469 go func() {
470 for idx := range work {
471 callback(idx)
472 }
473 }()
474 }
475 }
476
477 idx := 0
478 var avgDt time.Duration
479 for {
480 t := time.Now()
481 if numParallel > 1 {
482 work <- idx
483 } else {
484 callback(idx)
485 }
486 dt := time.Since(t)
487 if numParallel == 1 && dt < minSamplingInterval {
488 time.Sleep(minSamplingInterval - dt)
489 dt = time.Since(t)
490 }
491 if idx >= numParallel {
492 avgDt = (avgDt*time.Duration(idx-numParallel) + dt) / time.Duration(idx-numParallel+1)
493 }
494 idx += 1
495 if idx >= maxN {
496 return
497 }
498 if time.Now().Add(avgDt).After(maxTime) {
499 return
500 }
501 }
502 }
503
504 /*
505 Get returns the Measurement with the associated name. If no Measurement is found a zero Measurement{} is returned.
506 */
507 func (e *Experiment) Get(name string) Measurement {
508 e.lock.Lock()
509 defer e.lock.Unlock()
510 idx := e.Measurements.IdxWithName(name)
511 if idx == -1 {
512 return Measurement{}
513 }
514 return e.Measurements[idx]
515 }
516
517 /*
518 GetStats returns the Stats for the Measurement with the associated name. If no Measurement is found a zero Stats{} is returned.
519
520 experiment.GetStats(name) is equivalent to experiment.Get(name).Stats()
521 */
522 func (e *Experiment) GetStats(name string) Stats {
523 measurement := e.Get(name)
524 e.lock.Lock()
525 defer e.lock.Unlock()
526 return measurement.Stats()
527 }
0 package gmeasure_test
1
2 import (
3 "fmt"
4 "strings"
5 "sync"
6 "time"
7
8 . "github.com/onsi/ginkgo"
9 . "github.com/onsi/gomega"
10
11 "github.com/onsi/gomega/gmeasure"
12 )
13
14 var _ = Describe("Experiment", func() {
15 var e *gmeasure.Experiment
16 BeforeEach(func() {
17 e = gmeasure.NewExperiment("Test Experiment")
18 })
19
20 Describe("Recording Notes", func() {
21 It("creates a note Measurement", func() {
22 e.RecordNote("I'm a note", gmeasure.Style("{{blue}}"))
23 measurement := e.Measurements[0]
24 Ω(measurement.Type).Should(Equal(gmeasure.MeasurementTypeNote))
25 Ω(measurement.ExperimentName).Should(Equal("Test Experiment"))
26 Ω(measurement.Note).Should(Equal("I'm a note"))
27 Ω(measurement.Style).Should(Equal("{{blue}}"))
28 })
29 })
30
31 Describe("Recording Durations", func() {
32 commonMeasurementAssertions := func() gmeasure.Measurement {
33 measurement := e.Get("runtime")
34 Ω(measurement.Type).Should(Equal(gmeasure.MeasurementTypeDuration))
35 Ω(measurement.ExperimentName).Should(Equal("Test Experiment"))
36 Ω(measurement.Name).Should(Equal("runtime"))
37 Ω(measurement.Units).Should(Equal("duration"))
38 Ω(measurement.Style).Should(Equal("{{red}}"))
39 Ω(measurement.PrecisionBundle.Duration).Should(Equal(time.Millisecond))
40 return measurement
41 }
42
43 BeforeEach(func() {
44 e.RecordDuration("runtime", time.Second, gmeasure.Annotation("first"), gmeasure.Style("{{red}}"), gmeasure.Precision(time.Millisecond), gmeasure.Units("ignored"))
45 })
46
47 Describe("RecordDuration", func() {
48 It("generates a measurement and records the passed-in duration along with any relevant decorations", func() {
49 e.RecordDuration("runtime", time.Minute, gmeasure.Annotation("second"))
50 measurement := commonMeasurementAssertions()
51 Ω(measurement.Durations).Should(Equal([]time.Duration{time.Second, time.Minute}))
52 Ω(measurement.Annotations).Should(Equal([]string{"first", "second"}))
53 })
54 })
55
56 Describe("MeasureDuration", func() {
57 It("measure the duration of the passed-in function", func() {
58 e.MeasureDuration("runtime", func() {
59 time.Sleep(200 * time.Millisecond)
60 }, gmeasure.Annotation("second"))
61 measurement := commonMeasurementAssertions()
62 Ω(measurement.Durations[0]).Should(Equal(time.Second))
63 Ω(measurement.Durations[1]).Should(BeNumerically("~", 200*time.Millisecond, 20*time.Millisecond))
64 Ω(measurement.Annotations).Should(Equal([]string{"first", "second"}))
65 })
66 })
67
68 Describe("SampleDuration", func() {
69 It("samples the passed-in function according to SampleConfig and records the measured durations", func() {
70 e.SampleDuration("runtime", func(_ int) {
71 time.Sleep(100 * time.Millisecond)
72 }, gmeasure.SamplingConfig{N: 3}, gmeasure.Annotation("sampled"))
73 measurement := commonMeasurementAssertions()
74 Ω(measurement.Durations[0]).Should(Equal(time.Second))
75 Ω(measurement.Durations[1]).Should(BeNumerically("~", 100*time.Millisecond, 20*time.Millisecond))
76 Ω(measurement.Durations[2]).Should(BeNumerically("~", 100*time.Millisecond, 20*time.Millisecond))
77 Ω(measurement.Durations[3]).Should(BeNumerically("~", 100*time.Millisecond, 20*time.Millisecond))
78 Ω(measurement.Annotations).Should(Equal([]string{"first", "sampled", "sampled", "sampled"}))
79 })
80 })
81
82 Describe("SampleAnnotatedDuration", func() {
83 It("samples the passed-in function according to SampleConfig and records the measured durations and returned annotations", func() {
84 e.SampleAnnotatedDuration("runtime", func(idx int) gmeasure.Annotation {
85 time.Sleep(100 * time.Millisecond)
86 return gmeasure.Annotation(fmt.Sprintf("sampled-%d", idx+1))
87 }, gmeasure.SamplingConfig{N: 3}, gmeasure.Annotation("ignored"))
88 measurement := commonMeasurementAssertions()
89 Ω(measurement.Durations[0]).Should(Equal(time.Second))
90 Ω(measurement.Durations[1]).Should(BeNumerically("~", 100*time.Millisecond, 20*time.Millisecond))
91 Ω(measurement.Durations[2]).Should(BeNumerically("~", 100*time.Millisecond, 20*time.Millisecond))
92 Ω(measurement.Durations[3]).Should(BeNumerically("~", 100*time.Millisecond, 20*time.Millisecond))
93 Ω(measurement.Annotations).Should(Equal([]string{"first", "sampled-1", "sampled-2", "sampled-3"}))
94 })
95 })
96 })
97
98 Describe("Stopwatch Support", func() {
99 It("can generate a new stopwatch tied to the experiment", func() {
100 s := e.NewStopwatch()
101 time.Sleep(50 * time.Millisecond)
102 s.Record("runtime", gmeasure.Annotation("first")).Reset()
103 time.Sleep(100 * time.Millisecond)
104 s.Record("runtime", gmeasure.Annotation("second")).Reset()
105 time.Sleep(150 * time.Millisecond)
106 s.Record("runtime", gmeasure.Annotation("third"))
107 measurement := e.Get("runtime")
108 Ω(measurement.Durations[0]).Should(BeNumerically("~", 50*time.Millisecond, 20*time.Millisecond))
109 Ω(measurement.Durations[1]).Should(BeNumerically("~", 100*time.Millisecond, 20*time.Millisecond))
110 Ω(measurement.Durations[2]).Should(BeNumerically("~", 150*time.Millisecond, 20*time.Millisecond))
111 Ω(measurement.Annotations).Should(Equal([]string{"first", "second", "third"}))
112 })
113 })
114
115 Describe("Recording Values", func() {
116 commonMeasurementAssertions := func() gmeasure.Measurement {
117 measurement := e.Get("sprockets")
118 Ω(measurement.Type).Should(Equal(gmeasure.MeasurementTypeValue))
119 Ω(measurement.ExperimentName).Should(Equal("Test Experiment"))
120 Ω(measurement.Name).Should(Equal("sprockets"))
121 Ω(measurement.Units).Should(Equal("widgets"))
122 Ω(measurement.Style).Should(Equal("{{yellow}}"))
123 Ω(measurement.PrecisionBundle.ValueFormat).Should(Equal("%.0f"))
124 return measurement
125 }
126
127 BeforeEach(func() {
128 e.RecordValue("sprockets", 3.2, gmeasure.Annotation("first"), gmeasure.Style("{{yellow}}"), gmeasure.Precision(0), gmeasure.Units("widgets"))
129 })
130
131 Describe("RecordValue", func() {
132 It("generates a measurement and records the passed-in value along with any relevant decorations", func() {
133 e.RecordValue("sprockets", 17.4, gmeasure.Annotation("second"))
134 measurement := commonMeasurementAssertions()
135 Ω(measurement.Values).Should(Equal([]float64{3.2, 17.4}))
136 Ω(measurement.Annotations).Should(Equal([]string{"first", "second"}))
137 })
138 })
139
140 Describe("MeasureValue", func() {
141 It("records the value returned by the passed-in function", func() {
142 e.MeasureValue("sprockets", func() float64 {
143 return 17.4
144 }, gmeasure.Annotation("second"))
145 measurement := commonMeasurementAssertions()
146 Ω(measurement.Values).Should(Equal([]float64{3.2, 17.4}))
147 Ω(measurement.Annotations).Should(Equal([]string{"first", "second"}))
148 })
149 })
150
151 Describe("SampleValue", func() {
152 It("samples the passed-in function according to SampleConfig and records the resulting values", func() {
153 e.SampleValue("sprockets", func(idx int) float64 {
154 return 17.4 + float64(idx)
155 }, gmeasure.SamplingConfig{N: 3}, gmeasure.Annotation("sampled"))
156 measurement := commonMeasurementAssertions()
157 Ω(measurement.Values).Should(Equal([]float64{3.2, 17.4, 18.4, 19.4}))
158 Ω(measurement.Annotations).Should(Equal([]string{"first", "sampled", "sampled", "sampled"}))
159 })
160 })
161
162 Describe("SampleAnnotatedValue", func() {
163 It("samples the passed-in function according to SampleConfig and records the returned values and annotations", func() {
164 e.SampleAnnotatedValue("sprockets", func(idx int) (float64, gmeasure.Annotation) {
165 return 17.4 + float64(idx), gmeasure.Annotation(fmt.Sprintf("sampled-%d", idx+1))
166 }, gmeasure.SamplingConfig{N: 3}, gmeasure.Annotation("ignored"))
167 measurement := commonMeasurementAssertions()
168 Ω(measurement.Values).Should(Equal([]float64{3.2, 17.4, 18.4, 19.4}))
169 Ω(measurement.Annotations).Should(Equal([]string{"first", "sampled-1", "sampled-2", "sampled-3"}))
170 })
171 })
172 })
173
174 Describe("Sampling", func() {
175 var indices []int
176 BeforeEach(func() {
177 indices = []int{}
178 })
179
180 ints := func(n int) []int {
181 out := []int{}
182 for i := 0; i < n; i++ {
183 out = append(out, i)
184 }
185 return out
186 }
187
188 It("calls the function repeatedly passing in an index", func() {
189 e.Sample(func(idx int) {
190 indices = append(indices, idx)
191 }, gmeasure.SamplingConfig{N: 3})
192
193 Ω(indices).Should(Equal(ints(3)))
194 })
195
196 It("can cap the maximum number of samples", func() {
197 e.Sample(func(idx int) {
198 indices = append(indices, idx)
199 }, gmeasure.SamplingConfig{N: 10, Duration: time.Minute})
200
201 Ω(indices).Should(Equal(ints(10)))
202 })
203
204 It("can cap the maximum sample time", func() {
205 e.Sample(func(idx int) {
206 indices = append(indices, idx)
207 time.Sleep(10 * time.Millisecond)
208 }, gmeasure.SamplingConfig{N: 100, Duration: 100 * time.Millisecond, MinSamplingInterval: 5 * time.Millisecond})
209
210 Ω(len(indices)).Should(BeNumerically("~", 10, 3))
211 Ω(indices).Should(Equal(ints(len(indices))))
212 })
213
214 It("can ensure a minimum interval between samples", func() {
215 times := map[int]time.Time{}
216 e.Sample(func(idx int) {
217 times[idx] = time.Now()
218 }, gmeasure.SamplingConfig{N: 10, Duration: 200 * time.Millisecond, MinSamplingInterval: 50 * time.Millisecond, NumParallel: 1})
219
220 Ω(len(times)).Should(BeNumerically("~", 4, 2))
221 Ω(times[1]).Should(BeTemporally(">", times[0], 50*time.Millisecond))
222 Ω(times[2]).Should(BeTemporally(">", times[1], 50*time.Millisecond))
223 })
224
225 It("can run samples in parallel", func() {
226 lock := &sync.Mutex{}
227
228 e.Sample(func(idx int) {
229 lock.Lock()
230 indices = append(indices, idx)
231 lock.Unlock()
232 time.Sleep(10 * time.Millisecond)
233 }, gmeasure.SamplingConfig{N: 100, Duration: 100 * time.Millisecond, NumParallel: 3})
234
235 lock.Lock()
236 defer lock.Unlock()
237 Ω(len(indices)).Should(BeNumerically("~", 30, 10))
238 Ω(indices).Should(ConsistOf(ints(len(indices))))
239 })
240
241 It("panics if the SamplingConfig does not specify a ceiling", func() {
242 Expect(func() {
243 e.Sample(func(_ int) {}, gmeasure.SamplingConfig{MinSamplingInterval: time.Second})
244 }).To(PanicWith("you must specify at least one of SamplingConfig.N and SamplingConfig.Duration"))
245 })
246
247 It("panics if the SamplingConfig includes both a minimum interval and a directive to run in parallel", func() {
248 Expect(func() {
249 e.Sample(func(_ int) {}, gmeasure.SamplingConfig{N: 10, MinSamplingInterval: time.Second, NumParallel: 2})
250 }).To(PanicWith("you cannot specify both SamplingConfig.MinSamplingInterval and SamplingConfig.NumParallel"))
251 })
252 })
253
254 Describe("recording multiple entries", func() {
255 It("always appends to the correct measurement (by name)", func() {
256 e.RecordDuration("alpha", time.Second)
257 e.RecordDuration("beta", time.Minute)
258 e.RecordValue("gamma", 1)
259 e.RecordValue("delta", 2.71)
260 e.RecordDuration("alpha", 2*time.Second)
261 e.RecordDuration("beta", 2*time.Minute)
262 e.RecordValue("gamma", 2)
263 e.RecordValue("delta", 3.141)
264
265 Ω(e.Measurements).Should(HaveLen(4))
266 Ω(e.Get("alpha").Durations).Should(Equal([]time.Duration{time.Second, 2 * time.Second}))
267 Ω(e.Get("beta").Durations).Should(Equal([]time.Duration{time.Minute, 2 * time.Minute}))
268 Ω(e.Get("gamma").Values).Should(Equal([]float64{1, 2}))
269 Ω(e.Get("delta").Values).Should(Equal([]float64{2.71, 3.141}))
270 })
271
272 It("panics if you incorrectly mix types", func() {
273 e.RecordDuration("runtime", time.Second)
274 Ω(func() {
275 e.RecordValue("runtime", 3.141)
276 }).Should(PanicWith("attempting to record value with name 'runtime'. That name is already in-use for recording durations."))
277
278 e.RecordValue("sprockets", 2)
279 Ω(func() {
280 e.RecordDuration("sprockets", time.Minute)
281 }).Should(PanicWith("attempting to record duration with name 'sprockets'. That name is already in-use for recording values."))
282 })
283 })
284
285 Describe("Decorators", func() {
286 It("uses the default precisions when none is specified", func() {
287 e.RecordValue("sprockets", 2)
288 e.RecordDuration("runtime", time.Minute)
289
290 Ω(e.Get("sprockets").PrecisionBundle.ValueFormat).Should(Equal("%.3f"))
291 Ω(e.Get("runtime").PrecisionBundle.Duration).Should(Equal(100 * time.Microsecond))
292 })
293
294 It("panics if an unsupported type is passed into Precision", func() {
295 Ω(func() {
296 gmeasure.Precision("aardvark")
297 }).Should(PanicWith("invalid precision type, must be time.Duration or int"))
298 })
299
300 It("panics if an unrecognized argumnet is passed in", func() {
301 Ω(func() {
302 e.RecordValue("sprockets", 2, "boom")
303 }).Should(PanicWith(`unrecognized argument "boom"`))
304 })
305 })
306
307 Describe("Getting Measurements", func() {
308 Context("when the Measurement does not exist", func() {
309 It("returns the zero Measurement", func() {
310 Ω(e.Get("not here")).Should(BeZero())
311 })
312 })
313 })
314
315 Describe("Getting Stats", func() {
316 It("returns the Measurement's Stats", func() {
317 e.RecordValue("alpha", 1)
318 e.RecordValue("alpha", 2)
319 e.RecordValue("alpha", 3)
320 Ω(e.GetStats("alpha")).Should(Equal(e.Get("alpha").Stats()))
321 })
322 })
323
324 Describe("Generating Reports", func() {
325 BeforeEach(func() {
326 e.RecordNote("A note")
327 e.RecordValue("sprockets", 7, gmeasure.Units("widgets"), gmeasure.Precision(0), gmeasure.Style("{{yellow}}"), gmeasure.Annotation("sprockets-1"))
328 e.RecordDuration("runtime", time.Second, gmeasure.Precision(100*time.Millisecond), gmeasure.Style("{{red}}"), gmeasure.Annotation("runtime-1"))
329 e.RecordNote("A blue note", gmeasure.Style("{{blue}}"))
330 e.RecordValue("gear ratio", 10.3, gmeasure.Precision(2), gmeasure.Style("{{green}}"), gmeasure.Annotation("ratio-1"))
331
332 e.RecordValue("sprockets", 8, gmeasure.Annotation("sprockets-2"))
333 e.RecordValue("sprockets", 9, gmeasure.Annotation("sprockets-3"))
334
335 e.RecordDuration("runtime", 2*time.Second, gmeasure.Annotation("runtime-2"))
336 e.RecordValue("gear ratio", 13.758, gmeasure.Precision(2), gmeasure.Annotation("ratio-2"))
337 })
338
339 It("emits a nicely formatted table", func() {
340 expected := strings.Join([]string{
341 "Test Experiment",
342 "Name | N | Min | Median | Mean | StdDev | Max ",
343 "=============================================================================",
344 "A note ",
345 "-----------------------------------------------------------------------------",
346 "sprockets [widgets] | 3 | 7 | 8 | 8 | 1 | 9 ",
347 " | | sprockets-1 | | | | sprockets-3",
348 "-----------------------------------------------------------------------------",
349 "runtime [duration] | 2 | 1s | 1.5s | 1.5s | 500ms | 2s ",
350 " | | runtime-1 | | | | runtime-2 ",
351 "-----------------------------------------------------------------------------",
352 "A blue note ",
353 "-----------------------------------------------------------------------------",
354 "gear ratio | 2 | 10.30 | 12.03 | 12.03 | 1.73 | 13.76 ",
355 " | | ratio-1 | | | | ratio-2 ",
356 "",
357 }, "\n")
358 Ω(e.String()).Should(Equal(expected))
359 })
360
361 It("can also emit a styled table", func() {
362 expected := strings.Join([]string{
363 "{{bold}}Test Experiment",
364 "{{/}}{{bold}}Name {{/}} | {{bold}}N{{/}} | {{bold}}Min {{/}} | {{bold}}Median{{/}} | {{bold}}Mean {{/}} | {{bold}}StdDev{{/}} | {{bold}}Max {{/}}",
365 "=============================================================================",
366 "A note ",
367 "-----------------------------------------------------------------------------",
368 "{{yellow}}sprockets [widgets]{{/}} | {{yellow}}3{{/}} | {{yellow}}7 {{/}} | {{yellow}}8 {{/}} | {{yellow}}8 {{/}} | {{yellow}}1 {{/}} | {{yellow}}9 {{/}}",
369 " | | {{yellow}}sprockets-1{{/}} | | | | {{yellow}}sprockets-3{{/}}",
370 "-----------------------------------------------------------------------------",
371 "{{red}}runtime [duration] {{/}} | {{red}}2{{/}} | {{red}}1s {{/}} | {{red}}1.5s {{/}} | {{red}}1.5s {{/}} | {{red}}500ms {{/}} | {{red}}2s {{/}}",
372 " | | {{red}}runtime-1 {{/}} | | | | {{red}}runtime-2 {{/}}",
373 "-----------------------------------------------------------------------------",
374 "{{blue}}A blue note {{/}}",
375 "-----------------------------------------------------------------------------",
376 "{{green}}gear ratio {{/}} | {{green}}2{{/}} | {{green}}10.30 {{/}} | {{green}}12.03 {{/}} | {{green}}12.03{{/}} | {{green}}1.73 {{/}} | {{green}}13.76 {{/}}",
377 " | | {{green}}ratio-1 {{/}} | | | | {{green}}ratio-2 {{/}}",
378 "",
379 }, "\n")
380 Ω(e.ColorableString()).Should(Equal(expected))
381 })
382 })
383 })
0 package gmeasure_test
1
2 import (
3 "testing"
4
5 . "github.com/onsi/ginkgo"
6 . "github.com/onsi/gomega"
7 )
8
9 func TestGmeasure(t *testing.T) {
10 RegisterFailHandler(Fail)
11 RunSpecs(t, "Gmeasure Suite")
12 }
0 package gmeasure
1
2 import (
3 "fmt"
4 "math"
5 "sort"
6 "time"
7
8 "github.com/onsi/gomega/gmeasure/table"
9 )
10
11 type MeasurementType uint
12
13 const (
14 MeasurementTypeInvalid MeasurementType = iota
15 MeasurementTypeNote
16 MeasurementTypeDuration
17 MeasurementTypeValue
18 )
19
20 var letEnumSupport = newEnumSupport(map[uint]string{uint(MeasurementTypeInvalid): "INVALID LOG ENTRY TYPE", uint(MeasurementTypeNote): "Note", uint(MeasurementTypeDuration): "Duration", uint(MeasurementTypeValue): "Value"})
21
22 func (s MeasurementType) String() string { return letEnumSupport.String(uint(s)) }
23 func (s *MeasurementType) UnmarshalJSON(b []byte) error {
24 out, err := letEnumSupport.UnmarshJSON(b)
25 *s = MeasurementType(out)
26 return err
27 }
28 func (s MeasurementType) MarshalJSON() ([]byte, error) { return letEnumSupport.MarshJSON(uint(s)) }
29
30 /*
31 Measurement records all captured data for a given measurement. You generally don't make Measurements directly - but you can fetch them from Experiments using Get().
32
33 When using Ginkgo, you can register Measurements as Report Entries via AddReportEntry. This will emit all the captured data points when Ginkgo generates the report.
34 */
35 type Measurement struct {
36 // Type is the MeasurementType - one of MeasurementTypeNote, MeasurementTypeDuration, or MeasurementTypeValue
37 Type MeasurementType
38
39 // ExperimentName is the name of the experiment that this Measurement is associated with
40 ExperimentName string
41
42 // If Type is MeasurementTypeNote, Note is populated with the note text.
43 Note string
44
45 // If Type is MeasurementTypeDuration or MeasurementTypeValue, Name is the name of the recorded measurement
46 Name string
47
48 // Style captures the styling information (if any) for this Measurement
49 Style string
50
51 // Units capture the units (if any) for this Measurement. Units is set to "duration" if the Type is MeasurementTypeDuration
52 Units string
53
54 // PrecisionBundle captures the precision to use when rendering data for this Measurement.
55 // If Type is MeasurementTypeDuration then PrecisionBundle.Duration is used to round any durations before presentation.
56 // If Type is MeasurementTypeValue then PrecisionBundle.ValueFormat is used to format any values before presentation
57 PrecisionBundle PrecisionBundle
58
59 // If Type is MeasurementTypeDuration, Durations will contain all durations recorded for this measurement
60 Durations []time.Duration
61
62 // If Type is MeasurementTypeValue, Values will contain all float64s recorded for this measurement
63 Values []float64
64
65 // If Type is MeasurementTypeDuration or MeasurementTypeValue then Annotations will include string annotations for all recorded Durations or Values.
66 // If the user does not pass-in an Annotation() decoration for a particular value or duration, the corresponding entry in the Annotations slice will be the empty string ""
67 Annotations []string
68 }
69
70 type Measurements []Measurement
71
72 func (m Measurements) IdxWithName(name string) int {
73 for idx, measurement := range m {
74 if measurement.Name == name {
75 return idx
76 }
77 }
78
79 return -1
80 }
81
82 func (m Measurement) report(enableStyling bool) string {
83 out := ""
84 style := m.Style
85 if !enableStyling {
86 style = ""
87 }
88 switch m.Type {
89 case MeasurementTypeNote:
90 out += fmt.Sprintf("%s - Note\n%s\n", m.ExperimentName, m.Note)
91 if style != "" {
92 out = style + out + "{{/}}"
93 }
94 return out
95 case MeasurementTypeValue, MeasurementTypeDuration:
96 out += fmt.Sprintf("%s - %s", m.ExperimentName, m.Name)
97 if m.Units != "" {
98 out += " [" + m.Units + "]"
99 }
100 if style != "" {
101 out = style + out + "{{/}}"
102 }
103 out += "\n"
104 out += m.Stats().String() + "\n"
105 }
106 t := table.NewTable()
107 t.TableStyle.EnableTextStyling = enableStyling
108 switch m.Type {
109 case MeasurementTypeValue:
110 t.AppendRow(table.R(table.C("Value", table.AlignTypeCenter), table.C("Annotation", table.AlignTypeCenter), table.Divider("="), style))
111 for idx := range m.Values {
112 t.AppendRow(table.R(
113 table.C(fmt.Sprintf(m.PrecisionBundle.ValueFormat, m.Values[idx]), table.AlignTypeRight),
114 table.C(m.Annotations[idx], "{{gray}}", table.AlignTypeLeft),
115 ))
116 }
117 case MeasurementTypeDuration:
118 t.AppendRow(table.R(table.C("Duration", table.AlignTypeCenter), table.C("Annotation", table.AlignTypeCenter), table.Divider("="), style))
119 for idx := range m.Durations {
120 t.AppendRow(table.R(
121 table.C(m.Durations[idx].Round(m.PrecisionBundle.Duration).String(), style, table.AlignTypeRight),
122 table.C(m.Annotations[idx], "{{gray}}", table.AlignTypeLeft),
123 ))
124 }
125 }
126 out += t.Render()
127 return out
128 }
129
130 /*
131 ColorableString generates a styled report that includes all the data points for this Measurement.
132 It is called automatically by Ginkgo's reporting infrastructure when the Measurement is registered as a ReportEntry via AddReportEntry.
133 */
134 func (m Measurement) ColorableString() string {
135 return m.report(true)
136 }
137
138 /*
139 String generates an unstyled report that includes all the data points for this Measurement.
140 */
141 func (m Measurement) String() string {
142 return m.report(false)
143 }
144
145 /*
146 Stats returns a Stats struct summarizing the statistic of this measurement
147 */
148 func (m Measurement) Stats() Stats {
149 if m.Type == MeasurementTypeInvalid || m.Type == MeasurementTypeNote {
150 return Stats{}
151 }
152
153 out := Stats{
154 ExperimentName: m.ExperimentName,
155 MeasurementName: m.Name,
156 Style: m.Style,
157 Units: m.Units,
158 PrecisionBundle: m.PrecisionBundle,
159 }
160
161 switch m.Type {
162 case MeasurementTypeValue:
163 out.Type = StatsTypeValue
164 out.N = len(m.Values)
165 if out.N == 0 {
166 return out
167 }
168 indices, sum := make([]int, len(m.Values)), 0.0
169 for idx, v := range m.Values {
170 indices[idx] = idx
171 sum += v
172 }
173 sort.Slice(indices, func(i, j int) bool {
174 return m.Values[indices[i]] < m.Values[indices[j]]
175 })
176 out.ValueBundle = map[Stat]float64{
177 StatMin: m.Values[indices[0]],
178 StatMax: m.Values[indices[out.N-1]],
179 StatMean: sum / float64(out.N),
180 StatStdDev: 0.0,
181 }
182 out.AnnotationBundle = map[Stat]string{
183 StatMin: m.Annotations[indices[0]],
184 StatMax: m.Annotations[indices[out.N-1]],
185 }
186
187 if out.N%2 == 0 {
188 out.ValueBundle[StatMedian] = (m.Values[indices[out.N/2]] + m.Values[indices[out.N/2-1]]) / 2.0
189 } else {
190 out.ValueBundle[StatMedian] = m.Values[indices[(out.N-1)/2]]
191 }
192
193 for _, v := range m.Values {
194 out.ValueBundle[StatStdDev] += (v - out.ValueBundle[StatMean]) * (v - out.ValueBundle[StatMean])
195 }
196 out.ValueBundle[StatStdDev] = math.Sqrt(out.ValueBundle[StatStdDev] / float64(out.N))
197 case MeasurementTypeDuration:
198 out.Type = StatsTypeDuration
199 out.N = len(m.Durations)
200 if out.N == 0 {
201 return out
202 }
203 indices, sum := make([]int, len(m.Durations)), time.Duration(0)
204 for idx, v := range m.Durations {
205 indices[idx] = idx
206 sum += v
207 }
208 sort.Slice(indices, func(i, j int) bool {
209 return m.Durations[indices[i]] < m.Durations[indices[j]]
210 })
211 out.DurationBundle = map[Stat]time.Duration{
212 StatMin: m.Durations[indices[0]],
213 StatMax: m.Durations[indices[out.N-1]],
214 StatMean: sum / time.Duration(out.N),
215 }
216 out.AnnotationBundle = map[Stat]string{
217 StatMin: m.Annotations[indices[0]],
218 StatMax: m.Annotations[indices[out.N-1]],
219 }
220
221 if out.N%2 == 0 {
222 out.DurationBundle[StatMedian] = (m.Durations[indices[out.N/2]] + m.Durations[indices[out.N/2-1]]) / 2
223 } else {
224 out.DurationBundle[StatMedian] = m.Durations[indices[(out.N-1)/2]]
225 }
226 stdDev := 0.0
227 for _, v := range m.Durations {
228 stdDev += float64(v-out.DurationBundle[StatMean]) * float64(v-out.DurationBundle[StatMean])
229 }
230 out.DurationBundle[StatStdDev] = time.Duration(math.Sqrt(stdDev / float64(out.N)))
231 }
232
233 return out
234 }
0 package gmeasure_test
1
2 import (
3 "math"
4 "strings"
5 "time"
6
7 . "github.com/onsi/ginkgo"
8 . "github.com/onsi/gomega"
9 "github.com/onsi/gomega/gmeasure"
10 )
11
12 var _ = Describe("Measurement", func() {
13 var e *gmeasure.Experiment
14 var measurement gmeasure.Measurement
15
16 BeforeEach(func() {
17 e = gmeasure.NewExperiment("Test Experiment")
18 })
19
20 Describe("Note Measurement", func() {
21 BeforeEach(func() {
22 e.RecordNote("I'm a red note", gmeasure.Style("{{red}}"))
23 measurement = e.Measurements[0]
24 })
25
26 Describe("Generating Stats", func() {
27 It("returns an empty stats", func() {
28 Ω(measurement.Stats()).Should(BeZero())
29 })
30 })
31
32 Describe("Emitting an unstyled report", func() {
33 It("does not include styling", func() {
34 Ω(measurement.String()).Should(Equal("Test Experiment - Note\nI'm a red note\n"))
35 })
36 })
37
38 Describe("Emitting a styled report", func() {
39 It("does include styling", func() {
40 Ω(measurement.ColorableString()).Should(Equal("{{red}}Test Experiment - Note\nI'm a red note\n{{/}}"))
41 })
42 })
43 })
44
45 Describe("Value Measurement", func() {
46 var min, median, mean, stdDev, max float64
47 BeforeEach(func() {
48 e.RecordValue("flange widths", 7.128, gmeasure.Annotation("A"), gmeasure.Precision(2), gmeasure.Units("inches"), gmeasure.Style("{{blue}}"))
49 e.RecordValue("flange widths", 3.141, gmeasure.Annotation("B"))
50 e.RecordValue("flange widths", 9.28223, gmeasure.Annotation("C"))
51 e.RecordValue("flange widths", 14.249, gmeasure.Annotation("D"))
52 e.RecordValue("flange widths", 8.975, gmeasure.Annotation("E"))
53 measurement = e.Measurements[0]
54 min = 3.141
55 max = 14.249
56 median = 8.975
57 mean = (7.128 + 3.141 + 9.28223 + 14.249 + 8.975) / 5.0
58 stdDev = (7.128-mean)*(7.128-mean) + (3.141-mean)*(3.141-mean) + (9.28223-mean)*(9.28223-mean) + (14.249-mean)*(14.249-mean) + (8.975-mean)*(8.975-mean)
59 stdDev = math.Sqrt(stdDev / 5.0)
60 })
61
62 Describe("Generating Stats", func() {
63 It("generates a correctly configured Stats with correct values", func() {
64 stats := measurement.Stats()
65 Ω(stats.ExperimentName).Should(Equal("Test Experiment"))
66 Ω(stats.MeasurementName).Should(Equal("flange widths"))
67 Ω(stats.Style).Should(Equal("{{blue}}"))
68 Ω(stats.Units).Should(Equal("inches"))
69 Ω(stats.PrecisionBundle.ValueFormat).Should(Equal("%.2f"))
70
71 Ω(stats.ValueBundle[gmeasure.StatMin]).Should(Equal(min))
72 Ω(stats.AnnotationBundle[gmeasure.StatMin]).Should(Equal("B"))
73 Ω(stats.ValueBundle[gmeasure.StatMax]).Should(Equal(max))
74 Ω(stats.AnnotationBundle[gmeasure.StatMax]).Should(Equal("D"))
75 Ω(stats.ValueBundle[gmeasure.StatMedian]).Should(Equal(median))
76 Ω(stats.ValueBundle[gmeasure.StatMean]).Should(Equal(mean))
77 Ω(stats.ValueBundle[gmeasure.StatStdDev]).Should(Equal(stdDev))
78 })
79 })
80
81 Describe("Emitting an unstyled report", func() {
82 It("does not include styling", func() {
83 expected := strings.Join([]string{
84 "Test Experiment - flange widths [inches]",
85 "3.14 < [8.97] | <8.56> ±3.59 < 14.25",
86 "Value | Annotation",
87 "==================",
88 " 7.13 | A ",
89 "------------------",
90 " 3.14 | B ",
91 "------------------",
92 " 9.28 | C ",
93 "------------------",
94 "14.25 | D ",
95 "------------------",
96 " 8.97 | E ",
97 "",
98 }, "\n")
99 Ω(measurement.String()).Should(Equal(expected))
100 })
101 })
102
103 Describe("Emitting a styled report", func() {
104 It("does include styling", func() {
105 expected := strings.Join([]string{
106 "{{blue}}Test Experiment - flange widths [inches]{{/}}",
107 "3.14 < [8.97] | <8.56> ±3.59 < 14.25",
108 "{{blue}}Value{{/}} | {{blue}}Annotation{{/}}",
109 "==================",
110 " 7.13 | {{gray}}A {{/}}",
111 "------------------",
112 " 3.14 | {{gray}}B {{/}}",
113 "------------------",
114 " 9.28 | {{gray}}C {{/}}",
115 "------------------",
116 "14.25 | {{gray}}D {{/}}",
117 "------------------",
118 " 8.97 | {{gray}}E {{/}}",
119 "",
120 }, "\n")
121 Ω(measurement.ColorableString()).Should(Equal(expected))
122 })
123 })
124
125 Describe("Computing medians", func() {
126 Context("with an odd number of values", func() {
127 It("returns the middle element", func() {
128 e.RecordValue("odd", 5)
129 e.RecordValue("odd", 1)
130 e.RecordValue("odd", 2)
131 e.RecordValue("odd", 4)
132 e.RecordValue("odd", 3)
133
134 Ω(e.GetStats("odd").ValueBundle[gmeasure.StatMedian]).Should(Equal(3.0))
135 })
136 })
137
138 Context("when an even number of values", func() {
139 It("returns the mean of the two middle elements", func() {
140 e.RecordValue("even", 1)
141 e.RecordValue("even", 2)
142 e.RecordValue("even", 4)
143 e.RecordValue("even", 3)
144
145 Ω(e.GetStats("even").ValueBundle[gmeasure.StatMedian]).Should(Equal(2.5))
146 })
147 })
148 })
149 })
150
151 Describe("Duration Measurement", func() {
152 var min, median, mean, stdDev, max time.Duration
153 BeforeEach(func() {
154 e.RecordDuration("runtime", 7128*time.Millisecond, gmeasure.Annotation("A"), gmeasure.Precision(time.Millisecond*100), gmeasure.Style("{{blue}}"))
155 e.RecordDuration("runtime", 3141*time.Millisecond, gmeasure.Annotation("B"))
156 e.RecordDuration("runtime", 9282*time.Millisecond, gmeasure.Annotation("C"))
157 e.RecordDuration("runtime", 14249*time.Millisecond, gmeasure.Annotation("D"))
158 e.RecordDuration("runtime", 8975*time.Millisecond, gmeasure.Annotation("E"))
159 measurement = e.Measurements[0]
160 min = 3141 * time.Millisecond
161 max = 14249 * time.Millisecond
162 median = 8975 * time.Millisecond
163 mean = ((7128 + 3141 + 9282 + 14249 + 8975) * time.Millisecond) / 5
164 stdDev = time.Duration(math.Sqrt((float64(7128*time.Millisecond-mean)*float64(7128*time.Millisecond-mean) + float64(3141*time.Millisecond-mean)*float64(3141*time.Millisecond-mean) + float64(9282*time.Millisecond-mean)*float64(9282*time.Millisecond-mean) + float64(14249*time.Millisecond-mean)*float64(14249*time.Millisecond-mean) + float64(8975*time.Millisecond-mean)*float64(8975*time.Millisecond-mean)) / 5.0))
165 })
166
167 Describe("Generating Stats", func() {
168 It("generates a correctly configured Stats with correct values", func() {
169 stats := measurement.Stats()
170 Ω(stats.ExperimentName).Should(Equal("Test Experiment"))
171 Ω(stats.MeasurementName).Should(Equal("runtime"))
172 Ω(stats.Style).Should(Equal("{{blue}}"))
173 Ω(stats.Units).Should(Equal("duration"))
174 Ω(stats.PrecisionBundle.Duration).Should(Equal(time.Millisecond * 100))
175
176 Ω(stats.DurationBundle[gmeasure.StatMin]).Should(Equal(min))
177 Ω(stats.AnnotationBundle[gmeasure.StatMin]).Should(Equal("B"))
178 Ω(stats.DurationBundle[gmeasure.StatMax]).Should(Equal(max))
179 Ω(stats.AnnotationBundle[gmeasure.StatMax]).Should(Equal("D"))
180 Ω(stats.DurationBundle[gmeasure.StatMedian]).Should(Equal(median))
181 Ω(stats.DurationBundle[gmeasure.StatMean]).Should(Equal(mean))
182 Ω(stats.DurationBundle[gmeasure.StatStdDev]).Should(Equal(stdDev))
183 })
184 })
185
186 Describe("Emitting an unstyled report", func() {
187 It("does not include styling", func() {
188 expected := strings.Join([]string{
189 "Test Experiment - runtime [duration]",
190 "3.1s < [9s] | <8.6s> ±3.6s < 14.2s",
191 "Duration | Annotation",
192 "=====================",
193 " 7.1s | A ",
194 "---------------------",
195 " 3.1s | B ",
196 "---------------------",
197 " 9.3s | C ",
198 "---------------------",
199 " 14.2s | D ",
200 "---------------------",
201 " 9s | E ",
202 "",
203 }, "\n")
204 Ω(measurement.String()).Should(Equal(expected))
205 })
206 })
207
208 Describe("Emitting a styled report", func() {
209 It("does include styling", func() {
210 expected := strings.Join([]string{
211 "{{blue}}Test Experiment - runtime [duration]{{/}}",
212 "3.1s < [9s] | <8.6s> ±3.6s < 14.2s",
213 "{{blue}}Duration{{/}} | {{blue}}Annotation{{/}}",
214 "=====================",
215 "{{blue}} 7.1s{{/}} | {{gray}}A {{/}}",
216 "---------------------",
217 "{{blue}} 3.1s{{/}} | {{gray}}B {{/}}",
218 "---------------------",
219 "{{blue}} 9.3s{{/}} | {{gray}}C {{/}}",
220 "---------------------",
221 "{{blue}} 14.2s{{/}} | {{gray}}D {{/}}",
222 "---------------------",
223 "{{blue}} 9s{{/}} | {{gray}}E {{/}}",
224 "",
225 }, "\n")
226 Ω(measurement.ColorableString()).Should(Equal(expected))
227 })
228 })
229
230 Describe("Computing medians", func() {
231 Context("with an odd number of values", func() {
232 It("returns the middle element", func() {
233 e.RecordDuration("odd", 5*time.Second)
234 e.RecordDuration("odd", 1*time.Second)
235 e.RecordDuration("odd", 2*time.Second)
236 e.RecordDuration("odd", 4*time.Second)
237 e.RecordDuration("odd", 3*time.Second)
238
239 Ω(e.GetStats("odd").DurationBundle[gmeasure.StatMedian]).Should(Equal(3 * time.Second))
240 })
241 })
242
243 Context("when an even number of values", func() {
244 It("returns the mean of the two middle elements", func() {
245 e.RecordDuration("even", 1*time.Second)
246 e.RecordDuration("even", 2*time.Second)
247 e.RecordDuration("even", 4*time.Second)
248 e.RecordDuration("even", 3*time.Second)
249
250 Ω(e.GetStats("even").DurationBundle[gmeasure.StatMedian]).Should(Equal(2500 * time.Millisecond))
251 })
252 })
253 })
254 })
255 })
0 package gmeasure
1
2 import (
3 "fmt"
4 "sort"
5
6 "github.com/onsi/gomega/gmeasure/table"
7 )
8
9 /*
10 RankingCriteria is an enum representing the criteria by which Stats should be ranked. The enum names should be self explanatory. e.g. LowerMeanIsBetter means that Stats with lower mean values are considered more beneficial, with the lowest mean being declared the "winner" .
11 */
12 type RankingCriteria uint
13
14 const (
15 LowerMeanIsBetter RankingCriteria = iota
16 HigherMeanIsBetter
17 LowerMedianIsBetter
18 HigherMedianIsBetter
19 LowerMinIsBetter
20 HigherMinIsBetter
21 LowerMaxIsBetter
22 HigherMaxIsBetter
23 )
24
25 var rcEnumSupport = newEnumSupport(map[uint]string{uint(LowerMeanIsBetter): "Lower Mean is Better", uint(HigherMeanIsBetter): "Higher Mean is Better", uint(LowerMedianIsBetter): "Lower Median is Better", uint(HigherMedianIsBetter): "Higher Median is Better", uint(LowerMinIsBetter): "Lower Mins is Better", uint(HigherMinIsBetter): "Higher Min is Better", uint(LowerMaxIsBetter): "Lower Max is Better", uint(HigherMaxIsBetter): "Higher Max is Better"})
26
27 func (s RankingCriteria) String() string { return rcEnumSupport.String(uint(s)) }
28 func (s *RankingCriteria) UnmarshalJSON(b []byte) error {
29 out, err := rcEnumSupport.UnmarshJSON(b)
30 *s = RankingCriteria(out)
31 return err
32 }
33 func (s RankingCriteria) MarshalJSON() ([]byte, error) { return rcEnumSupport.MarshJSON(uint(s)) }
34
35 /*
36 Ranking ranks a set of Stats by a specified RankingCritera. Use RankStats to create a Ranking.
37
38 When using Ginkgo, you can register Rankings as Report Entries via AddReportEntry. This will emit a formatted table representing the Stats in rank-order when Ginkgo generates the report.
39 */
40 type Ranking struct {
41 Criteria RankingCriteria
42 Stats []Stats
43 }
44
45 /*
46 RankStats creates a new ranking of the passed-in stats according to the passed-in criteria.
47 */
48 func RankStats(criteria RankingCriteria, stats ...Stats) Ranking {
49 sort.Slice(stats, func(i int, j int) bool {
50 switch criteria {
51 case LowerMeanIsBetter:
52 return stats[i].FloatFor(StatMean) < stats[j].FloatFor(StatMean)
53 case HigherMeanIsBetter:
54 return stats[i].FloatFor(StatMean) > stats[j].FloatFor(StatMean)
55 case LowerMedianIsBetter:
56 return stats[i].FloatFor(StatMedian) < stats[j].FloatFor(StatMedian)
57 case HigherMedianIsBetter:
58 return stats[i].FloatFor(StatMedian) > stats[j].FloatFor(StatMedian)
59 case LowerMinIsBetter:
60 return stats[i].FloatFor(StatMin) < stats[j].FloatFor(StatMin)
61 case HigherMinIsBetter:
62 return stats[i].FloatFor(StatMin) > stats[j].FloatFor(StatMin)
63 case LowerMaxIsBetter:
64 return stats[i].FloatFor(StatMax) < stats[j].FloatFor(StatMax)
65 case HigherMaxIsBetter:
66 return stats[i].FloatFor(StatMax) > stats[j].FloatFor(StatMax)
67 }
68 return false
69 })
70
71 out := Ranking{
72 Criteria: criteria,
73 Stats: stats,
74 }
75
76 return out
77 }
78
79 /*
80 Winner returns the Stats with the most optimal rank based on the specified ranking criteria. For example, if the RankingCriteria is LowerMaxIsBetter then the Stats with the lowest value or duration for StatMax will be returned as the "winner"
81 */
82 func (c Ranking) Winner() Stats {
83 if len(c.Stats) == 0 {
84 return Stats{}
85 }
86 return c.Stats[0]
87 }
88
89 func (c Ranking) report(enableStyling bool) string {
90 if len(c.Stats) == 0 {
91 return "Empty Ranking"
92 }
93 t := table.NewTable()
94 t.TableStyle.EnableTextStyling = enableStyling
95 t.AppendRow(table.R(
96 table.C("Experiment"), table.C("Name"), table.C("N"), table.C("Min"), table.C("Median"), table.C("Mean"), table.C("StdDev"), table.C("Max"),
97 table.Divider("="),
98 "{{bold}}",
99 ))
100
101 for idx, stats := range c.Stats {
102 name := stats.MeasurementName
103 if stats.Units != "" {
104 name = name + " [" + stats.Units + "]"
105 }
106 experimentName := stats.ExperimentName
107 style := stats.Style
108 if idx == 0 {
109 style = "{{bold}}" + style
110 name += "\n*Winner*"
111 experimentName += "\n*Winner*"
112 }
113 r := table.R(style)
114 t.AppendRow(r)
115 r.AppendCell(table.C(experimentName), table.C(name))
116 r.AppendCell(stats.cells()...)
117
118 }
119 out := fmt.Sprintf("Ranking Criteria: %s\n", c.Criteria)
120 if enableStyling {
121 out = "{{bold}}" + out + "{{/}}"
122 }
123 out += t.Render()
124 return out
125 }
126
127 /*
128 ColorableString generates a styled report that includes a table of the rank-ordered Stats
129 It is called automatically by Ginkgo's reporting infrastructure when the Ranking is registered as a ReportEntry via AddReportEntry.
130 */
131 func (c Ranking) ColorableString() string {
132 return c.report(true)
133 }
134
135 /*
136 String generates an unstyled report that includes a table of the rank-ordered Stats
137 */
138 func (c Ranking) String() string {
139 return c.report(false)
140 }
0 package gmeasure_test
1
2 import (
3 "strings"
4 "time"
5
6 . "github.com/onsi/ginkgo"
7 . "github.com/onsi/ginkgo/extensions/table"
8 . "github.com/onsi/gomega"
9 "github.com/onsi/gomega/gmeasure"
10 )
11
12 var _ = Describe("Rank", func() {
13 var A, B, C, D gmeasure.Stats
14
15 Describe("Ranking Values", func() {
16 makeStats := func(name string, min float64, max float64, mean float64, median float64) gmeasure.Stats {
17 return gmeasure.Stats{
18 Type: gmeasure.StatsTypeValue,
19 ExperimentName: "Exp-" + name,
20 MeasurementName: name,
21 N: 100,
22 PrecisionBundle: gmeasure.Precision(2),
23 ValueBundle: map[gmeasure.Stat]float64{
24 gmeasure.StatMin: min,
25 gmeasure.StatMax: max,
26 gmeasure.StatMean: mean,
27 gmeasure.StatMedian: median,
28 gmeasure.StatStdDev: 2.0,
29 },
30 }
31 }
32
33 BeforeEach(func() {
34 A = makeStats("A", 1, 2, 3, 4)
35 B = makeStats("B", 2, 3, 4, 1)
36 C = makeStats("C", 3, 4, 1, 2)
37 D = makeStats("D", 4, 1, 2, 3)
38 })
39
40 DescribeTable("ranking by criteria",
41 func(criteria gmeasure.RankingCriteria, expectedOrder func() []gmeasure.Stats) {
42 ranking := gmeasure.RankStats(criteria, A, B, C, D)
43 expected := expectedOrder()
44 Ω(ranking.Winner()).Should(Equal(expected[0]))
45 Ω(ranking.Stats).Should(Equal(expected))
46 },
47 Entry("entry", gmeasure.LowerMeanIsBetter, func() []gmeasure.Stats { return []gmeasure.Stats{C, D, A, B} }),
48 Entry("entry", gmeasure.HigherMeanIsBetter, func() []gmeasure.Stats { return []gmeasure.Stats{B, A, D, C} }),
49 Entry("entry", gmeasure.LowerMedianIsBetter, func() []gmeasure.Stats { return []gmeasure.Stats{B, C, D, A} }),
50 Entry("entry", gmeasure.HigherMedianIsBetter, func() []gmeasure.Stats { return []gmeasure.Stats{A, D, C, B} }),
51 Entry("entry", gmeasure.LowerMinIsBetter, func() []gmeasure.Stats { return []gmeasure.Stats{A, B, C, D} }),
52 Entry("entry", gmeasure.HigherMinIsBetter, func() []gmeasure.Stats { return []gmeasure.Stats{D, C, B, A} }),
53 Entry("entry", gmeasure.LowerMaxIsBetter, func() []gmeasure.Stats { return []gmeasure.Stats{D, A, B, C} }),
54 Entry("entry", gmeasure.HigherMaxIsBetter, func() []gmeasure.Stats { return []gmeasure.Stats{C, B, A, D} }),
55 )
56
57 Describe("Generating Reports", func() {
58 It("can generate an unstyled report", func() {
59 ranking := gmeasure.RankStats(gmeasure.LowerMeanIsBetter, A, B, C, D)
60 Ω(ranking.String()).Should(Equal(strings.Join([]string{
61 "Ranking Criteria: Lower Mean is Better",
62 "Experiment | Name | N | Min | Median | Mean | StdDev | Max ",
63 "==================================================================",
64 "Exp-C | C | 100 | 3.00 | 2.00 | 1.00 | 2.00 | 4.00",
65 "*Winner* | *Winner* | | | | | | ",
66 "------------------------------------------------------------------",
67 "Exp-D | D | 100 | 4.00 | 3.00 | 2.00 | 2.00 | 1.00",
68 "------------------------------------------------------------------",
69 "Exp-A | A | 100 | 1.00 | 4.00 | 3.00 | 2.00 | 2.00",
70 "------------------------------------------------------------------",
71 "Exp-B | B | 100 | 2.00 | 1.00 | 4.00 | 2.00 | 3.00",
72 "",
73 }, "\n")))
74 })
75
76 It("can generate a styled report", func() {
77 ranking := gmeasure.RankStats(gmeasure.LowerMeanIsBetter, A, B, C, D)
78 Ω(ranking.ColorableString()).Should(Equal(strings.Join([]string{
79 "{{bold}}Ranking Criteria: Lower Mean is Better",
80 "{{/}}{{bold}}Experiment{{/}} | {{bold}}Name {{/}} | {{bold}}N {{/}} | {{bold}}Min {{/}} | {{bold}}Median{{/}} | {{bold}}Mean{{/}} | {{bold}}StdDev{{/}} | {{bold}}Max {{/}}",
81 "==================================================================",
82 "{{bold}}Exp-C {{/}} | {{bold}}C {{/}} | {{bold}}100{{/}} | {{bold}}3.00{{/}} | {{bold}}2.00 {{/}} | {{bold}}1.00{{/}} | {{bold}}2.00 {{/}} | {{bold}}4.00{{/}}",
83 "{{bold}}*Winner* {{/}} | {{bold}}*Winner*{{/}} | | | | | | ",
84 "------------------------------------------------------------------",
85 "Exp-D | D | 100 | 4.00 | 3.00 | 2.00 | 2.00 | 1.00",
86 "------------------------------------------------------------------",
87 "Exp-A | A | 100 | 1.00 | 4.00 | 3.00 | 2.00 | 2.00",
88 "------------------------------------------------------------------",
89 "Exp-B | B | 100 | 2.00 | 1.00 | 4.00 | 2.00 | 3.00",
90 "",
91 }, "\n")))
92 })
93 })
94 })
95
96 Describe("Ranking Durations", func() {
97 makeStats := func(name string, min time.Duration, max time.Duration, mean time.Duration, median time.Duration) gmeasure.Stats {
98 return gmeasure.Stats{
99 Type: gmeasure.StatsTypeDuration,
100 ExperimentName: "Exp-" + name,
101 MeasurementName: name,
102 N: 100,
103 PrecisionBundle: gmeasure.Precision(time.Millisecond * 100),
104 DurationBundle: map[gmeasure.Stat]time.Duration{
105 gmeasure.StatMin: min,
106 gmeasure.StatMax: max,
107 gmeasure.StatMean: mean,
108 gmeasure.StatMedian: median,
109 gmeasure.StatStdDev: 2.0,
110 },
111 }
112 }
113
114 BeforeEach(func() {
115 A = makeStats("A", 1*time.Second, 2*time.Second, 3*time.Second, 4*time.Second)
116 B = makeStats("B", 2*time.Second, 3*time.Second, 4*time.Second, 1*time.Second)
117 C = makeStats("C", 3*time.Second, 4*time.Second, 1*time.Second, 2*time.Second)
118 D = makeStats("D", 4*time.Second, 1*time.Second, 2*time.Second, 3*time.Second)
119 })
120
121 DescribeTable("ranking by criteria",
122 func(criteria gmeasure.RankingCriteria, expectedOrder func() []gmeasure.Stats) {
123 ranking := gmeasure.RankStats(criteria, A, B, C, D)
124 expected := expectedOrder()
125 Ω(ranking.Winner()).Should(Equal(expected[0]))
126 Ω(ranking.Stats).Should(Equal(expected))
127 },
128 Entry("entry", gmeasure.LowerMeanIsBetter, func() []gmeasure.Stats { return []gmeasure.Stats{C, D, A, B} }),
129 Entry("entry", gmeasure.HigherMeanIsBetter, func() []gmeasure.Stats { return []gmeasure.Stats{B, A, D, C} }),
130 Entry("entry", gmeasure.LowerMedianIsBetter, func() []gmeasure.Stats { return []gmeasure.Stats{B, C, D, A} }),
131 Entry("entry", gmeasure.HigherMedianIsBetter, func() []gmeasure.Stats { return []gmeasure.Stats{A, D, C, B} }),
132 Entry("entry", gmeasure.LowerMinIsBetter, func() []gmeasure.Stats { return []gmeasure.Stats{A, B, C, D} }),
133 Entry("entry", gmeasure.HigherMinIsBetter, func() []gmeasure.Stats { return []gmeasure.Stats{D, C, B, A} }),
134 Entry("entry", gmeasure.LowerMaxIsBetter, func() []gmeasure.Stats { return []gmeasure.Stats{D, A, B, C} }),
135 Entry("entry", gmeasure.HigherMaxIsBetter, func() []gmeasure.Stats { return []gmeasure.Stats{C, B, A, D} }),
136 )
137
138 Describe("Generating Reports", func() {
139 It("can generate an unstyled report", func() {
140 ranking := gmeasure.RankStats(gmeasure.LowerMeanIsBetter, A, B, C, D)
141 Ω(ranking.String()).Should(Equal(strings.Join([]string{
142 "Ranking Criteria: Lower Mean is Better",
143 "Experiment | Name | N | Min | Median | Mean | StdDev | Max",
144 "================================================================",
145 "Exp-C | C | 100 | 3s | 2s | 1s | 0s | 4s ",
146 "*Winner* | *Winner* | | | | | | ",
147 "----------------------------------------------------------------",
148 "Exp-D | D | 100 | 4s | 3s | 2s | 0s | 1s ",
149 "----------------------------------------------------------------",
150 "Exp-A | A | 100 | 1s | 4s | 3s | 0s | 2s ",
151 "----------------------------------------------------------------",
152 "Exp-B | B | 100 | 2s | 1s | 4s | 0s | 3s ",
153 "",
154 }, "\n")))
155 })
156
157 It("can generate a styled report", func() {
158 ranking := gmeasure.RankStats(gmeasure.LowerMeanIsBetter, A, B, C, D)
159 Ω(ranking.ColorableString()).Should(Equal(strings.Join([]string{
160 "{{bold}}Ranking Criteria: Lower Mean is Better",
161 "{{/}}{{bold}}Experiment{{/}} | {{bold}}Name {{/}} | {{bold}}N {{/}} | {{bold}}Min{{/}} | {{bold}}Median{{/}} | {{bold}}Mean{{/}} | {{bold}}StdDev{{/}} | {{bold}}Max{{/}}",
162 "================================================================",
163 "{{bold}}Exp-C {{/}} | {{bold}}C {{/}} | {{bold}}100{{/}} | {{bold}}3s {{/}} | {{bold}}2s {{/}} | {{bold}}1s {{/}} | {{bold}}0s {{/}} | {{bold}}4s {{/}}",
164 "{{bold}}*Winner* {{/}} | {{bold}}*Winner*{{/}} | | | | | | ",
165 "----------------------------------------------------------------",
166 "Exp-D | D | 100 | 4s | 3s | 2s | 0s | 1s ",
167 "----------------------------------------------------------------",
168 "Exp-A | A | 100 | 1s | 4s | 3s | 0s | 2s ",
169 "----------------------------------------------------------------",
170 "Exp-B | B | 100 | 2s | 1s | 4s | 0s | 3s ",
171 "",
172 }, "\n")))
173 })
174 })
175 })
176
177 })
0 package gmeasure
1
2 import (
3 "fmt"
4 "time"
5
6 "github.com/onsi/gomega/gmeasure/table"
7 )
8
9 /*
10 Stat is an enum representing the statistics you can request of a Stats struct
11 */
12 type Stat uint
13
14 const (
15 StatInvalid Stat = iota
16 StatMin
17 StatMax
18 StatMean
19 StatMedian
20 StatStdDev
21 )
22
23 var statEnumSupport = newEnumSupport(map[uint]string{uint(StatInvalid): "INVALID STAT", uint(StatMin): "Min", uint(StatMax): "Max", uint(StatMean): "Mean", uint(StatMedian): "Median", uint(StatStdDev): "StdDev"})
24
25 func (s Stat) String() string { return statEnumSupport.String(uint(s)) }
26 func (s *Stat) UnmarshalJSON(b []byte) error {
27 out, err := statEnumSupport.UnmarshJSON(b)
28 *s = Stat(out)
29 return err
30 }
31 func (s Stat) MarshalJSON() ([]byte, error) { return statEnumSupport.MarshJSON(uint(s)) }
32
33 type StatsType uint
34
35 const (
36 StatsTypeInvalid StatsType = iota
37 StatsTypeValue
38 StatsTypeDuration
39 )
40
41 var statsTypeEnumSupport = newEnumSupport(map[uint]string{uint(StatsTypeInvalid): "INVALID STATS TYPE", uint(StatsTypeValue): "StatsTypeValue", uint(StatsTypeDuration): "StatsTypeDuration"})
42
43 func (s StatsType) String() string { return statsTypeEnumSupport.String(uint(s)) }
44 func (s *StatsType) UnmarshalJSON(b []byte) error {
45 out, err := statsTypeEnumSupport.UnmarshJSON(b)
46 *s = StatsType(out)
47 return err
48 }
49 func (s StatsType) MarshalJSON() ([]byte, error) { return statsTypeEnumSupport.MarshJSON(uint(s)) }
50
51 /*
52 Stats records the key statistics for a given measurement. You generally don't make Stats directly - but you can fetch them from Experiments using GetStats() and from Measurements using Stats().
53
54 When using Ginkgo, you can register Measurements as Report Entries via AddReportEntry. This will emit all the captured data points when Ginkgo generates the report.
55 */
56 type Stats struct {
57 // Type is the StatType - one of StatTypeDuration or StatTypeValue
58 Type StatsType
59
60 // ExperimentName is the name of the Experiment that recorded the Measurement from which this Stat is derived
61 ExperimentName string
62
63 // MeasurementName is the name of the Measurement from which this Stat is derived
64 MeasurementName string
65
66 // Units captures the Units of the Measurement from which this Stat is derived
67 Units string
68
69 // Style captures the Style of the Measurement from which this Stat is derived
70 Style string
71
72 // PrecisionBundle captures the precision to use when rendering data for this Measurement.
73 // If Type is StatTypeDuration then PrecisionBundle.Duration is used to round any durations before presentation.
74 // If Type is StatTypeValue then PrecisionBundle.ValueFormat is used to format any values before presentation
75 PrecisionBundle PrecisionBundle
76
77 // N represents the total number of data points in the Meassurement from which this Stat is derived
78 N int
79
80 // If Type is StatTypeValue, ValueBundle will be populated with float64s representing this Stat's statistics
81 ValueBundle map[Stat]float64
82
83 // If Type is StatTypeDuration, DurationBundle will be populated with float64s representing this Stat's statistics
84 DurationBundle map[Stat]time.Duration
85
86 // AnnotationBundle is populated with Annotations corresponding to the data points that can be associated with a Stat.
87 // For example AnnotationBundle[StatMin] will return the Annotation for the data point that has the minimum value/duration.
88 AnnotationBundle map[Stat]string
89 }
90
91 // String returns a minimal summary of the stats of the form "MIN < [MEDIAN] | <MEAN> ±STDDEV < MAX"
92 func (s Stats) String() string {
93 return fmt.Sprintf("%s < [%s] | <%s> ±%s < %s", s.StringFor(StatMin), s.StringFor(StatMedian), s.StringFor(StatMean), s.StringFor(StatStdDev), s.StringFor(StatMax))
94 }
95
96 // ValueFor returns the float64 value for a particular Stat. You should only use this if the Stats has Type StatsTypeValue
97 // For example:
98 //
99 // median := experiment.GetStats("length").ValueFor(gmeasure.StatMedian)
100 //
101 // will return the median data point for the "length" Measurement.
102 func (s Stats) ValueFor(stat Stat) float64 {
103 return s.ValueBundle[stat]
104 }
105
106 // DurationFor returns the time.Duration for a particular Stat. You should only use this if the Stats has Type StatsTypeDuration
107 // For example:
108 //
109 // mean := experiment.GetStats("runtime").ValueFor(gmeasure.StatMean)
110 //
111 // will return the mean duration for the "runtime" Measurement.
112 func (s Stats) DurationFor(stat Stat) time.Duration {
113 return s.DurationBundle[stat]
114 }
115
116 // FloatFor returns a float64 representation of the passed-in Stat.
117 // When Type is StatsTypeValue this is equivalent to s.ValueFor(stat).
118 // When Type is StatsTypeDuration this is equivalent to float64(s.DurationFor(stat))
119 func (s Stats) FloatFor(stat Stat) float64 {
120 switch s.Type {
121 case StatsTypeValue:
122 return s.ValueFor(stat)
123 case StatsTypeDuration:
124 return float64(s.DurationFor(stat))
125 }
126 return 0
127 }
128
129 // StringFor returns a formatted string representation of the passed-in Stat.
130 // The formatting honors the precision directives provided in stats.PrecisionBundle
131 func (s Stats) StringFor(stat Stat) string {
132 switch s.Type {
133 case StatsTypeValue:
134 return fmt.Sprintf(s.PrecisionBundle.ValueFormat, s.ValueFor(stat))
135 case StatsTypeDuration:
136 return s.DurationFor(stat).Round(s.PrecisionBundle.Duration).String()
137 }
138 return ""
139 }
140
141 func (s Stats) cells() []table.Cell {
142 out := []table.Cell{}
143 out = append(out, table.C(fmt.Sprintf("%d", s.N)))
144 for _, stat := range []Stat{StatMin, StatMedian, StatMean, StatStdDev, StatMax} {
145 content := s.StringFor(stat)
146 if s.AnnotationBundle[stat] != "" {
147 content += "\n" + s.AnnotationBundle[stat]
148 }
149 out = append(out, table.C(content))
150 }
151 return out
152 }
0 package gmeasure_test
1
2 import (
3 "time"
4
5 . "github.com/onsi/ginkgo"
6 . "github.com/onsi/gomega"
7 "github.com/onsi/gomega/gmeasure"
8 )
9
10 var _ = Describe("Stats", func() {
11 var stats gmeasure.Stats
12
13 Describe("Stats representing values", func() {
14 BeforeEach(func() {
15 stats = gmeasure.Stats{
16 Type: gmeasure.StatsTypeValue,
17 ExperimentName: "My Test Experiment",
18 MeasurementName: "Sprockets",
19 Units: "widgets",
20 N: 100,
21 PrecisionBundle: gmeasure.Precision(2),
22 ValueBundle: map[gmeasure.Stat]float64{
23 gmeasure.StatMin: 17.48992,
24 gmeasure.StatMax: 293.4820,
25 gmeasure.StatMean: 187.3023,
26 gmeasure.StatMedian: 87.2235,
27 gmeasure.StatStdDev: 73.6394,
28 },
29 }
30 })
31
32 Describe("String()", func() {
33 It("returns a one-line summary", func() {
34 Ω(stats.String()).Should(Equal("17.49 < [87.22] | <187.30> ±73.64 < 293.48"))
35 })
36 })
37
38 Describe("ValueFor()", func() {
39 It("returns the value for the requested stat", func() {
40 Ω(stats.ValueFor(gmeasure.StatMin)).Should(Equal(17.48992))
41 Ω(stats.ValueFor(gmeasure.StatMean)).Should(Equal(187.3023))
42 })
43 })
44
45 Describe("FloatFor", func() {
46 It("returns the requested stat as a float", func() {
47 Ω(stats.FloatFor(gmeasure.StatMin)).Should(Equal(17.48992))
48 Ω(stats.FloatFor(gmeasure.StatMean)).Should(Equal(187.3023))
49 })
50 })
51
52 Describe("StringFor", func() {
53 It("returns the requested stat rendered with the configured precision", func() {
54 Ω(stats.StringFor(gmeasure.StatMin)).Should(Equal("17.49"))
55 Ω(stats.StringFor(gmeasure.StatMean)).Should(Equal("187.30"))
56 })
57 })
58 })
59
60 Describe("Stats representing durations", func() {
61 BeforeEach(func() {
62 stats = gmeasure.Stats{
63 Type: gmeasure.StatsTypeDuration,
64 ExperimentName: "My Test Experiment",
65 MeasurementName: "Runtime",
66 N: 100,
67 PrecisionBundle: gmeasure.Precision(time.Millisecond * 100),
68 DurationBundle: map[gmeasure.Stat]time.Duration{
69 gmeasure.StatMin: 17375 * time.Millisecond,
70 gmeasure.StatMax: 890321 * time.Millisecond,
71 gmeasure.StatMean: 328712 * time.Millisecond,
72 gmeasure.StatMedian: 552390 * time.Millisecond,
73 gmeasure.StatStdDev: 186259 * time.Millisecond,
74 },
75 }
76 })
77 Describe("String()", func() {
78 It("returns a one-line summary", func() {
79 Ω(stats.String()).Should(Equal("17.4s < [9m12.4s] | <5m28.7s> ±3m6.3s < 14m50.3s"))
80 })
81 })
82 Describe("DurationFor()", func() {
83 It("returns the duration for the requested stat", func() {
84 Ω(stats.DurationFor(gmeasure.StatMin)).Should(Equal(17375 * time.Millisecond))
85 Ω(stats.DurationFor(gmeasure.StatMean)).Should(Equal(328712 * time.Millisecond))
86 })
87 })
88
89 Describe("FloatFor", func() {
90 It("returns the float64 representation for the requested duration stat", func() {
91 Ω(stats.FloatFor(gmeasure.StatMin)).Should(Equal(float64(17375 * time.Millisecond)))
92 Ω(stats.FloatFor(gmeasure.StatMean)).Should(Equal(float64(328712 * time.Millisecond)))
93 })
94 })
95
96 Describe("StringFor", func() {
97 It("returns the requested stat rendered with the configured precision", func() {
98 Ω(stats.StringFor(gmeasure.StatMin)).Should(Equal("17.4s"))
99 Ω(stats.StringFor(gmeasure.StatMean)).Should(Equal("5m28.7s"))
100 })
101 })
102 })
103 })
0 package gmeasure
1
2 import "time"
3
4 /*
5 Stopwatch provides a convenient abstraction for recording durations. There are two ways to make a Stopwatch:
6
7 You can make a Stopwatch from an Experiment via experiment.NewStopwatch(). This is how you first get a hold of a Stopwatch.
8
9 You can subsequently call stopwatch.NewStopwatch() to get a fresh Stopwatch.
10 This is only necessary if you need to record durations on a different goroutine as a single Stopwatch is not considered thread-safe.
11
12 The Stopwatch starts as soon as it is created. You can Pause() the stopwatch and Reset() it as needed.
13
14 Stopwatches refer back to their parent Experiment. They use this reference to record any measured durations back with the Experiment.
15 */
16 type Stopwatch struct {
17 Experiment *Experiment
18 t time.Time
19 pauseT time.Time
20 pauseDuration time.Duration
21 running bool
22 }
23
24 func newStopwatch(experiment *Experiment) *Stopwatch {
25 return &Stopwatch{
26 Experiment: experiment,
27 t: time.Now(),
28 running: true,
29 }
30 }
31
32 /*
33 NewStopwatch returns a new Stopwatch pointing to the same Experiment as this Stopwatch
34 */
35 func (s *Stopwatch) NewStopwatch() *Stopwatch {
36 return newStopwatch(s.Experiment)
37 }
38
39 /*
40 Record captures the amount of time that has passed since the Stopwatch was created or most recently Reset(). It records the duration on it's associated Experiment in a Measurement with the passed-in name.
41
42 Record takes all the decorators that experiment.RecordDuration takes (e.g. Annotation("...") can be used to annotate this duration)
43
44 Note that Record does not Reset the Stopwatch. It does, however, return the Stopwatch so the following pattern is common:
45
46 stopwatch := experiment.NewStopwatch()
47 // first expensive operation
48 stopwatch.Record("first operation").Reset() //records the duration of the first operation and resets the stopwatch.
49 // second expensive operation
50 stopwatch.Record("second operation").Reset() //records the duration of the second operation and resets the stopwatch.
51
52 omitting the Reset() after the first operation would cause the duration recorded for the second operation to include the time elapsed by both the first _and_ second operations.
53
54 The Stopwatch must be running (i.e. not paused) when Record is called.
55 */
56 func (s *Stopwatch) Record(name string, args ...interface{}) *Stopwatch {
57 if !s.running {
58 panic("stopwatch is not running - call Resume or Reset before calling Record")
59 }
60 duration := time.Since(s.t) - s.pauseDuration
61 s.Experiment.RecordDuration(name, duration, args...)
62 return s
63 }
64
65 /*
66 Reset resets the Stopwatch. Subsequent recorded durations will measure the time elapsed from the moment Reset was called.
67 If the Stopwatch was Paused it is unpaused after calling Reset.
68 */
69 func (s *Stopwatch) Reset() *Stopwatch {
70 s.running = true
71 s.t = time.Now()
72 s.pauseDuration = 0
73 return s
74 }
75
76 /*
77 Pause pauses the Stopwatch. While pasued the Stopwatch does not accumulate elapsed time. This is useful for ignoring expensive operations that are incidental to the behavior you are attempting to characterize.
78 Note: You must call Resume() before you can Record() subsequent measurements.
79
80 For example:
81
82 stopwatch := experiment.NewStopwatch()
83 // first expensive operation
84 stopwatch.Record("first operation").Reset()
85 // second expensive operation - part 1
86 stopwatch.Pause()
87 // something expensive that we don't care about
88 stopwatch.Resume()
89 // second expensive operation - part 2
90 stopwatch.Record("second operation").Reset() // the recorded duration captures the time elapsed during parts 1 and 2 of the second expensive operation, but not the bit in between
91
92
93 The Stopwatch must be running when Pause is called.
94 */
95 func (s *Stopwatch) Pause() *Stopwatch {
96 if !s.running {
97 panic("stopwatch is not running - call Resume or Reset before calling Pause")
98 }
99 s.running = false
100 s.pauseT = time.Now()
101 return s
102 }
103
104 /*
105 Resume resumes a paused Stopwatch. Any time that elapses after Resume is called will be accumulated as elapsed time when a subsequent duration is Recorded.
106
107 The Stopwatch must be Paused when Resume is called
108 */
109 func (s *Stopwatch) Resume() *Stopwatch {
110 if s.running {
111 panic("stopwatch is running - call Pause before calling Resume")
112 }
113 s.running = true
114 s.pauseDuration = s.pauseDuration + time.Since(s.pauseT)
115 return s
116 }
0 package gmeasure_test
1
2 import (
3 "time"
4
5 . "github.com/onsi/ginkgo"
6 . "github.com/onsi/gomega"
7 "github.com/onsi/gomega/gmeasure"
8 )
9
10 var _ = Describe("Stopwatch", func() {
11 var e *gmeasure.Experiment
12 var stopwatch *gmeasure.Stopwatch
13
14 BeforeEach(func() {
15 e = gmeasure.NewExperiment("My Test Experiment")
16 stopwatch = e.NewStopwatch()
17 })
18
19 It("records durations", func() {
20 time.Sleep(100 * time.Millisecond)
21 stopwatch.Record("recordings", gmeasure.Annotation("A"))
22 time.Sleep(100 * time.Millisecond)
23 stopwatch.Record("recordings", gmeasure.Annotation("B")).Reset()
24 time.Sleep(100 * time.Millisecond)
25 stopwatch.Record("recordings", gmeasure.Annotation("C")).Reset()
26 time.Sleep(100 * time.Millisecond)
27 stopwatch.Pause()
28 time.Sleep(100 * time.Millisecond)
29 stopwatch.Resume()
30 time.Sleep(100 * time.Millisecond)
31 stopwatch.Pause()
32 time.Sleep(100 * time.Millisecond)
33 stopwatch.Resume()
34 time.Sleep(100 * time.Millisecond)
35 stopwatch.Record("recordings", gmeasure.Annotation("D"))
36 durations := e.Get("recordings").Durations
37 annotations := e.Get("recordings").Annotations
38 Ω(annotations).Should(Equal([]string{"A", "B", "C", "D"}))
39 Ω(durations[0]).Should(BeNumerically("~", 100*time.Millisecond, 50*time.Millisecond))
40 Ω(durations[1]).Should(BeNumerically("~", 200*time.Millisecond, 50*time.Millisecond))
41 Ω(durations[2]).Should(BeNumerically("~", 100*time.Millisecond, 50*time.Millisecond))
42 Ω(durations[3]).Should(BeNumerically("~", 300*time.Millisecond, 50*time.Millisecond))
43
44 })
45
46 It("panics when asked to record but not running", func() {
47 stopwatch.Pause()
48 Ω(func() {
49 stopwatch.Record("A")
50 }).Should(PanicWith("stopwatch is not running - call Resume or Reset before calling Record"))
51 })
52
53 It("panics when paused but not running", func() {
54 stopwatch.Pause()
55 Ω(func() {
56 stopwatch.Pause()
57 }).Should(PanicWith("stopwatch is not running - call Resume or Reset before calling Pause"))
58 })
59
60 It("panics when asked to resume but not paused", func() {
61 Ω(func() {
62 stopwatch.Resume()
63 }).Should(PanicWith("stopwatch is running - call Pause before calling Resume"))
64 })
65 })
0 package table
1
2 // This is a temporary package - Table will move to github.com/onsi/consolable once some more dust settles
3
4 import (
5 "reflect"
6 "strings"
7 "unicode/utf8"
8 )
9
10 type AlignType uint
11
12 const (
13 AlignTypeLeft AlignType = iota
14 AlignTypeCenter
15 AlignTypeRight
16 )
17
18 type Divider string
19
20 type Row struct {
21 Cells []Cell
22 Divider string
23 Style string
24 }
25
26 func R(args ...interface{}) *Row {
27 r := &Row{
28 Divider: "-",
29 }
30 for _, arg := range args {
31 switch reflect.TypeOf(arg) {
32 case reflect.TypeOf(Divider("")):
33 r.Divider = string(arg.(Divider))
34 case reflect.TypeOf(r.Style):
35 r.Style = arg.(string)
36 case reflect.TypeOf(Cell{}):
37 r.Cells = append(r.Cells, arg.(Cell))
38 }
39 }
40 return r
41 }
42
43 func (r *Row) AppendCell(cells ...Cell) *Row {
44 r.Cells = append(r.Cells, cells...)
45 return r
46 }
47
48 func (r *Row) Render(widths []int, totalWidth int, tableStyle TableStyle, isLastRow bool) string {
49 out := ""
50 if len(r.Cells) == 1 {
51 out += strings.Join(r.Cells[0].render(totalWidth, r.Style, tableStyle), "\n") + "\n"
52 } else {
53 if len(r.Cells) != len(widths) {
54 panic("row vs width mismatch")
55 }
56 renderedCells := make([][]string, len(r.Cells))
57 maxHeight := 0
58 for colIdx, cell := range r.Cells {
59 renderedCells[colIdx] = cell.render(widths[colIdx], r.Style, tableStyle)
60 if len(renderedCells[colIdx]) > maxHeight {
61 maxHeight = len(renderedCells[colIdx])
62 }
63 }
64 for colIdx := range r.Cells {
65 for len(renderedCells[colIdx]) < maxHeight {
66 renderedCells[colIdx] = append(renderedCells[colIdx], strings.Repeat(" ", widths[colIdx]))
67 }
68 }
69 border := strings.Repeat(" ", tableStyle.Padding)
70 if tableStyle.VerticalBorders {
71 border += "|" + border
72 }
73 for lineIdx := 0; lineIdx < maxHeight; lineIdx++ {
74 for colIdx := range r.Cells {
75 out += renderedCells[colIdx][lineIdx]
76 if colIdx < len(r.Cells)-1 {
77 out += border
78 }
79 }
80 out += "\n"
81 }
82 }
83 if tableStyle.HorizontalBorders && !isLastRow && r.Divider != "" {
84 out += strings.Repeat(string(r.Divider), totalWidth) + "\n"
85 }
86
87 return out
88 }
89
90 type Cell struct {
91 Contents []string
92 Style string
93 Align AlignType
94 }
95
96 func C(contents string, args ...interface{}) Cell {
97 c := Cell{
98 Contents: strings.Split(contents, "\n"),
99 }
100 for _, arg := range args {
101 switch reflect.TypeOf(arg) {
102 case reflect.TypeOf(c.Style):
103 c.Style = arg.(string)
104 case reflect.TypeOf(c.Align):
105 c.Align = arg.(AlignType)
106 }
107 }
108 return c
109 }
110
111 func (c Cell) Width() (int, int) {
112 w, minW := 0, 0
113 for _, line := range c.Contents {
114 lineWidth := utf8.RuneCountInString(line)
115 if lineWidth > w {
116 w = lineWidth
117 }
118 for _, word := range strings.Split(line, " ") {
119 wordWidth := utf8.RuneCountInString(word)
120 if wordWidth > minW {
121 minW = wordWidth
122 }
123 }
124 }
125 return w, minW
126 }
127
128 func (c Cell) alignLine(line string, width int) string {
129 lineWidth := utf8.RuneCountInString(line)
130 if lineWidth == width {
131 return line
132 }
133 if lineWidth < width {
134 gap := width - lineWidth
135 switch c.Align {
136 case AlignTypeLeft:
137 return line + strings.Repeat(" ", gap)
138 case AlignTypeRight:
139 return strings.Repeat(" ", gap) + line
140 case AlignTypeCenter:
141 leftGap := gap / 2
142 rightGap := gap - leftGap
143 return strings.Repeat(" ", leftGap) + line + strings.Repeat(" ", rightGap)
144 }
145 }
146 return line
147 }
148
149 func (c Cell) splitWordToWidth(word string, width int) []string {
150 out := []string{}
151 n, subWord := 0, ""
152 for _, c := range word {
153 subWord += string(c)
154 n += 1
155 if n == width-1 {
156 out = append(out, subWord+"-")
157 n, subWord = 0, ""
158 }
159 }
160 return out
161 }
162
163 func (c Cell) splitToWidth(line string, width int) []string {
164 lineWidth := utf8.RuneCountInString(line)
165 if lineWidth <= width {
166 return []string{line}
167 }
168
169 outLines := []string{}
170 words := strings.Split(line, " ")
171 outWords := []string{words[0]}
172 length := utf8.RuneCountInString(words[0])
173 if length > width {
174 splitWord := c.splitWordToWidth(words[0], width)
175 lastIdx := len(splitWord) - 1
176 outLines = append(outLines, splitWord[:lastIdx]...)
177 outWords = []string{splitWord[lastIdx]}
178 length = utf8.RuneCountInString(splitWord[lastIdx])
179 }
180
181 for _, word := range words[1:] {
182 wordLength := utf8.RuneCountInString(word)
183 if length+wordLength+1 <= width {
184 length += wordLength + 1
185 outWords = append(outWords, word)
186 continue
187 }
188 outLines = append(outLines, strings.Join(outWords, " "))
189
190 outWords = []string{word}
191 length = wordLength
192 if length > width {
193 splitWord := c.splitWordToWidth(word, width)
194 lastIdx := len(splitWord) - 1
195 outLines = append(outLines, splitWord[:lastIdx]...)
196 outWords = []string{splitWord[lastIdx]}
197 length = utf8.RuneCountInString(splitWord[lastIdx])
198 }
199 }
200 if len(outWords) > 0 {
201 outLines = append(outLines, strings.Join(outWords, " "))
202 }
203
204 return outLines
205 }
206
207 func (c Cell) render(width int, style string, tableStyle TableStyle) []string {
208 out := []string{}
209 for _, line := range c.Contents {
210 out = append(out, c.splitToWidth(line, width)...)
211 }
212 for idx := range out {
213 out[idx] = c.alignLine(out[idx], width)
214 }
215
216 if tableStyle.EnableTextStyling {
217 style = style + c.Style
218 if style != "" {
219 for idx := range out {
220 out[idx] = style + out[idx] + "{{/}}"
221 }
222 }
223 }
224
225 return out
226 }
227
228 type TableStyle struct {
229 Padding int
230 VerticalBorders bool
231 HorizontalBorders bool
232 MaxTableWidth int
233 MaxColWidth int
234 EnableTextStyling bool
235 }
236
237 var DefaultTableStyle = TableStyle{
238 Padding: 1,
239 VerticalBorders: true,
240 HorizontalBorders: true,
241 MaxTableWidth: 120,
242 MaxColWidth: 40,
243 EnableTextStyling: true,
244 }
245
246 type Table struct {
247 Rows []*Row
248
249 TableStyle TableStyle
250 }
251
252 func NewTable() *Table {
253 return &Table{
254 TableStyle: DefaultTableStyle,
255 }
256 }
257
258 func (t *Table) AppendRow(row *Row) *Table {
259 t.Rows = append(t.Rows, row)
260 return t
261 }
262
263 func (t *Table) Render() string {
264 out := ""
265 totalWidth, widths := t.computeWidths()
266 for rowIdx, row := range t.Rows {
267 out += row.Render(widths, totalWidth, t.TableStyle, rowIdx == len(t.Rows)-1)
268 }
269 return out
270 }
271
272 func (t *Table) computeWidths() (int, []int) {
273 nCol := 0
274 for _, row := range t.Rows {
275 if len(row.Cells) > nCol {
276 nCol = len(row.Cells)
277 }
278 }
279
280 // lets compute the contribution to width from the borders + padding
281 borderWidth := t.TableStyle.Padding
282 if t.TableStyle.VerticalBorders {
283 borderWidth += 1 + t.TableStyle.Padding
284 }
285 totalBorderWidth := borderWidth * (nCol - 1)
286
287 // lets compute the width of each column
288 widths := make([]int, nCol)
289 minWidths := make([]int, nCol)
290 for colIdx := range widths {
291 for _, row := range t.Rows {
292 if colIdx >= len(row.Cells) {
293 // ignore rows with fewer columns
294 continue
295 }
296 w, minWid := row.Cells[colIdx].Width()
297 if w > widths[colIdx] {
298 widths[colIdx] = w
299 }
300 if minWid > minWidths[colIdx] {
301 minWidths[colIdx] = minWid
302 }
303 }
304 }
305
306 // do we already fit?
307 if sum(widths)+totalBorderWidth <= t.TableStyle.MaxTableWidth {
308 // yes! we're done
309 return sum(widths) + totalBorderWidth, widths
310 }
311
312 // clamp the widths and minWidths to MaxColWidth
313 for colIdx := range widths {
314 widths[colIdx] = min(widths[colIdx], t.TableStyle.MaxColWidth)
315 minWidths[colIdx] = min(minWidths[colIdx], t.TableStyle.MaxColWidth)
316 }
317
318 // do we fit now?
319 if sum(widths)+totalBorderWidth <= t.TableStyle.MaxTableWidth {
320 // yes! we're done
321 return sum(widths) + totalBorderWidth, widths
322 }
323
324 // hmm... still no... can we possibly squeeze the table in without violating minWidths?
325 if sum(minWidths)+totalBorderWidth >= t.TableStyle.MaxTableWidth {
326 // nope - we're just going to have to exceed MaxTableWidth
327 return sum(minWidths) + totalBorderWidth, minWidths
328 }
329
330 // looks like we don't fit yet, but we should be able to fit without violating minWidths
331 // lets start scaling down
332 n := 0
333 for sum(widths)+totalBorderWidth > t.TableStyle.MaxTableWidth {
334 budget := t.TableStyle.MaxTableWidth - totalBorderWidth
335 baseline := sum(widths)
336
337 for colIdx := range widths {
338 widths[colIdx] = max((widths[colIdx]*budget)/baseline, minWidths[colIdx])
339 }
340 n += 1
341 if n > 100 {
342 break // in case we somehow fail to converge
343 }
344 }
345
346 return sum(widths) + totalBorderWidth, widths
347 }
348
349 func sum(s []int) int {
350 out := 0
351 for _, v := range s {
352 out += v
353 }
354 return out
355 }
356
357 func min(a int, b int) int {
358 if a < b {
359 return a
360 }
361 return b
362 }
363
364 func max(a int, b int) int {
365 if a > b {
366 return a
367 }
368 return b
369 }
22 go 1.14
33
44 require (
5 github.com/golang/protobuf v1.4.2
6 github.com/onsi/ginkgo v1.12.1
7 golang.org/x/net v0.0.0-20201006153459-a7d1128ccaa0
8 golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543
9 gopkg.in/yaml.v2 v2.3.0
5 github.com/golang/protobuf v1.5.2
6 github.com/onsi/ginkgo v1.16.4
7 golang.org/x/net v0.0.0-20210428140749-89ef3d95e781
8 gopkg.in/yaml.v2 v2.4.0
109 )
0 github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I=
0 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
1 github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
12 github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
2 github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM=
3 github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
4 github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
5 github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 h1:p104kn46Q8WdvHunIJ9dAyjPVtrBPhSr3KT2yUst43I=
6 github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
37 github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
48 github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
59 github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
610 github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
711 github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
812 github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
9 github.com/golang/protobuf v1.4.2 h1:+Z5KGCizgyZCbGh1KZqA0fcLLkwbsjIzS4aV2v7wJX0=
1013 github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
14 github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
15 github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw=
16 github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
1117 github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
1218 github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
13 github.com/google/go-cmp v0.4.0 h1:xsAVV57WRhGj6kEIi8ReJzQlHHqcBYCElAvkovg3B/4=
1419 github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
15 github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI=
20 github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
21 github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
1622 github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
17 github.com/nxadm/tail v1.4.4 h1:DQuhQpB1tVlglWS2hLQ5OV6B5r8aGxSrPc5Qo6uTN78=
1823 github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
19 github.com/onsi/ginkgo v1.6.0 h1:Ix8l273rp3QzYgXSR+c8d1fTG7UPgYkOSELPhiY/YGw=
24 github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE=
25 github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU=
2026 github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
21 github.com/onsi/ginkgo v1.12.1 h1:mFwc4LvZ0xpSvDZ3E+k8Yte0hLOMxXUlP+yXtJqkYfQ=
2227 github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
28 github.com/onsi/ginkgo v1.16.4 h1:29JGrr5oVBm5ulCWet69zQkzWipVXIol6ygQUe/EzNc=
29 github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0=
2330 github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
31 github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
32 github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
33 github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
34 github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
35 github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
2436 golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
37 golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
2538 golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
26 golang.org/x/net v0.0.0-20180906233101-161cd47e91fd h1:nTDtHvHSdCn1m6ITfMRqtOd/9+7a3s8RBNOZ3eYZzJA=
39 golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
2740 golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
2841 golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
29 golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7 h1:AeiKBIuRw3UomYXSbLy0Mc2dDLfdtbT/IVn4keq83P0=
42 golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
3043 golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
31 golang.org/x/net v0.0.0-20201006153459-a7d1128ccaa0 h1:wBouT66WTYFXdxfVdz9sVWARVd/2vfGcmI45D2gj45M=
32 golang.org/x/net v0.0.0-20201006153459-a7d1128ccaa0/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
33 golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f h1:wMNYb4v58l5UBM7MYRLPG6ZhfOqbKu7X5eyFl8ZhKvA=
44 golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
45 golang.org/x/net v0.0.0-20210428140749-89ef3d95e781 h1:DzZ89McO9/gWPsQXS/FVKAlG02ZjaQ6AlZRBimEYOd0=
46 golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk=
3447 golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
35 golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e h1:o3PsSEY8E4eXWkXrIP9YJALUkVZqzHJT5DOasTyn8Vs=
48 golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
49 golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
3650 golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
3751 golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
3852 golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
3953 golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
40 golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e h1:N7DeIrjYszNmSW409R3frPPwglRwMkXSBzwVbkOjLLA=
54 golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
4155 golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
42 golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd h1:xhmwyvizuTgC2qz7ZlMluP20uW+C3Rm0FD/WLDX8884=
4356 golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
4457 golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
45 golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
58 golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
59 golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
60 golang.org/x/sys v0.0.0-20210423082822-04245dca01da h1:b3NXsE2LusjYGGjL5bxEVZZORm/YEFFrWFjR8eFrw/c=
61 golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
62 golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
4663 golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
47 golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k=
4864 golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
65 golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M=
66 golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
4967 golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
50 golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
68 golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
69 golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e h1:4nW4NLDYnU28ojHaHO8OVxFHk/aQ33U01a9cjED+pzE=
70 golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
71 golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
72 golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
5173 golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
74 golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
75 golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
5276 google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
5377 google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
5478 google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
5579 google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
5680 google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
57 google.golang.org/protobuf v1.23.0 h1:4MY060fB1DLGMB/7MBTLnwQUY6+F09GEiz6SsrNqyzM=
5881 google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
82 google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
83 google.golang.org/protobuf v1.26.0 h1:bxAC2xTBsZGibn2RTntX0oH50xLsqy1OxA9tTL3p/lk=
84 google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
5985 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
6086 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
61 gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4=
6287 gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
6388 gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
6489 gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
65 gopkg.in/yaml.v2 v2.2.4 h1:/eiJrUcujPVeJ3xlSWaiNi3uSVmDGBK1pDHUHAnao1I=
90 gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
6691 gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
67 gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU=
6892 gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
93 gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
94 gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
1313 package gomega
1414
1515 import (
16 "errors"
1617 "fmt"
17 "reflect"
1818 "time"
1919
20 "github.com/onsi/gomega/internal/assertion"
21 "github.com/onsi/gomega/internal/asyncassertion"
22 "github.com/onsi/gomega/internal/testingtsupport"
20 "github.com/onsi/gomega/internal"
2321 "github.com/onsi/gomega/types"
2422 )
2523
26 const GOMEGA_VERSION = "1.10.3"
27
28 const nilFailHandlerPanic = `You are trying to make an assertion, but Gomega's fail handler is nil.
24 const GOMEGA_VERSION = "1.15.0"
25
26 const nilGomegaPanic = `You are trying to make an assertion, but haven't registered Gomega's fail handler.
2927 If you're using Ginkgo then you probably forgot to put your assertion in an It().
3028 Alternatively, you may have forgotten to register a fail handler with RegisterFailHandler() or RegisterTestingT().
3129 Depending on your vendoring solution you may be inadvertently importing gomega and subpackages (e.g. ghhtp, gexec,...) from different locations.
3230 `
3331
34 var globalFailWrapper *types.GomegaFailWrapper
35
36 var defaultEventuallyTimeout = time.Second
37 var defaultEventuallyPollingInterval = 10 * time.Millisecond
38 var defaultConsistentlyDuration = 100 * time.Millisecond
39 var defaultConsistentlyPollingInterval = 10 * time.Millisecond
32 // Gomega describes the essential Gomega DSL. This interface allows libraries
33 // to abstract between the standard package-level function implementations
34 // and alternatives like *WithT.
35 //
36 // The types in the top-level DSL have gotten a bit messy due to earlier depracations that avoid stuttering
37 // and due to an accidental use of a concrete type (*WithT) in an earlier release.
38 //
39 // As of 1.15 both the WithT and Ginkgo variants of Gomega are implemented by the same underlying object
40 // however one (the Ginkgo variant) is exported as an interface (types.Gomega) whereas the other (the withT variant)
41 // is shared as a concrete type (*WithT, which is aliased to *internal.Gomega). 1.15 did not clean this mess up to ensure
42 // that declarations of *WithT in existing code are not broken by the upgrade to 1.15.
43 type Gomega = types.Gomega
44
45 // DefaultGomega supplies the standard package-level implementation
46 var Default = Gomega(internal.NewGomega(internal.FetchDefaultDurationBundle()))
47
48 // NewGomega returns an instance of Gomega wired into the passed-in fail handler.
49 // You generally don't need to use this when using Ginkgo - RegisterFailHandler will wire up the global gomega
50 // However creating a NewGomega with a custom fail handler can be useful in contexts where you want to use Gomega's
51 // rich ecosystem of matchers without causing a test to fail. For example, to aggregate a series of potential failures
52 // or for use in a non-test setting.
53 func NewGomega(fail types.GomegaFailHandler) Gomega {
54 return internal.NewGomega(Default.(*internal.Gomega).DurationBundle).ConfigureWithFailHandler(fail)
55 }
56
57 // WithT wraps a *testing.T and provides `Expect`, `Eventually`, and `Consistently` methods. This allows you to leverage
58 // Gomega's rich ecosystem of matchers in standard `testing` test suites.
59 //
60 // Use `NewWithT` to instantiate a `WithT`
61 //
62 // As of 1.15 both the WithT and Ginkgo variants of Gomega are implemented by the same underlying object
63 // however one (the Ginkgo variant) is exported as an interface (types.Gomega) whereas the other (the withT variant)
64 // is shared as a concrete type (*WithT, which is aliased to *internal.Gomega). 1.15 did not clean this mess up to ensure
65 // that declarations of *WithT in existing code are not broken by the upgrade to 1.15.
66 type WithT = internal.Gomega
67
68 // GomegaWithT is deprecated in favor of gomega.WithT, which does not stutter.
69 type GomegaWithT = WithT
70
71 // NewWithT takes a *testing.T and returngs a `gomega.WithT` allowing you to use `Expect`, `Eventually`, and `Consistently` along with
72 // Gomega's rich ecosystem of matchers in standard `testing` test suits.
73 //
74 // func TestFarmHasCow(t *testing.T) {
75 // g := gomega.NewWithT(t)
76 //
77 // f := farm.New([]string{"Cow", "Horse"})
78 // g.Expect(f.HasCow()).To(BeTrue(), "Farm should have cow")
79 // }
80 func NewWithT(t types.GomegaTestingT) *WithT {
81 return internal.NewGomega(Default.(*internal.Gomega).DurationBundle).ConfigureWithT(t)
82 }
83
84 // NewGomegaWithT is deprecated in favor of gomega.NewWithT, which does not stutter.
85 var NewGomegaWithT = NewWithT
4086
4187 // RegisterFailHandler connects Ginkgo to Gomega. When a matcher fails
4288 // the fail handler passed into RegisterFailHandler is called.
43 func RegisterFailHandler(handler types.GomegaFailHandler) {
44 RegisterFailHandlerWithT(testingtsupport.EmptyTWithHelper{}, handler)
45 }
46
47 // RegisterFailHandlerWithT ensures that the given types.TWithHelper and fail handler
48 // are used globally.
49 func RegisterFailHandlerWithT(t types.TWithHelper, handler types.GomegaFailHandler) {
50 if handler == nil {
51 globalFailWrapper = nil
52 return
53 }
54
55 globalFailWrapper = &types.GomegaFailWrapper{
56 Fail: handler,
57 TWithHelper: t,
58 }
89 func RegisterFailHandler(fail types.GomegaFailHandler) {
90 Default.(*internal.Gomega).ConfigureWithFailHandler(fail)
91 }
92
93 // RegisterFailHandlerWithT is deprecated and will be removed in a future release.
94 // users should use RegisterFailHandler, or RegisterTestingT
95 func RegisterFailHandlerWithT(_ types.GomegaTestingT, fail types.GomegaFailHandler) {
96 fmt.Println("RegisterFailHandlerWithT is deprecated. Please use RegisterFailHandler or RegisterTestingT instead.")
97 Default.(*internal.Gomega).ConfigureWithFailHandler(fail)
5998 }
6099
61100 // RegisterTestingT connects Gomega to Golang's XUnit style
62 // Testing.T tests. It is now deprecated and you should use NewWithT() instead.
63 //
64 // Legacy Documentation:
65 //
66 // You'll need to call this at the top of each XUnit style test:
67 //
68 // func TestFarmHasCow(t *testing.T) {
69 // RegisterTestingT(t)
70 //
71 // f := farm.New([]string{"Cow", "Horse"})
72 // Expect(f.HasCow()).To(BeTrue(), "Farm should have cow")
73 // }
74 //
75 // Note that this *testing.T is registered *globally* by Gomega (this is why you don't have to
76 // pass `t` down to the matcher itself). This means that you cannot run the XUnit style tests
77 // in parallel as the global fail handler cannot point to more than one testing.T at a time.
78 //
79 // NewWithT() does not have this limitation
80 //
81 // (As an aside: Ginkgo gets around this limitation by running parallel tests in different *processes*).
101 // Testing.T tests. It is now deprecated and you should use NewWithT() instead to get a fresh instance of Gomega for each test.
82102 func RegisterTestingT(t types.GomegaTestingT) {
83 tWithHelper, hasHelper := t.(types.TWithHelper)
84 if !hasHelper {
85 RegisterFailHandler(testingtsupport.BuildTestingTGomegaFailWrapper(t).Fail)
86 return
87 }
88 RegisterFailHandlerWithT(tWithHelper, testingtsupport.BuildTestingTGomegaFailWrapper(t).Fail)
103 Default.(*internal.Gomega).ConfigureWithT(t)
89104 }
90105
91106 // InterceptGomegaFailures runs a given callback and returns an array of
92107 // failure messages generated by any Gomega assertions within the callback.
93 //
94 // This is accomplished by temporarily replacing the *global* fail handler
95 // with a fail handler that simply annotates failures. The original fail handler
96 // is reset when InterceptGomegaFailures returns.
108 // Exeuction continues after the first failure allowing users to collect all failures
109 // in the callback.
97110 //
98111 // This is most useful when testing custom matchers, but can also be used to check
99112 // on a value using a Gomega assertion without causing a test failure.
100113 func InterceptGomegaFailures(f func()) []string {
101 originalHandler := globalFailWrapper.Fail
114 originalHandler := Default.(*internal.Gomega).Fail
102115 failures := []string{}
103 RegisterFailHandler(func(message string, callerSkip ...int) {
116 Default.(*internal.Gomega).Fail = func(message string, callerSkip ...int) {
104117 failures = append(failures, message)
105 })
118 }
119 defer func() {
120 Default.(*internal.Gomega).Fail = originalHandler
121 }()
106122 f()
107 RegisterFailHandler(originalHandler)
108123 return failures
124 }
125
126 // InterceptGomegaFailure runs a given callback and returns the first
127 // failure message generated by any Gomega assertions within the callback, wrapped in an error.
128 //
129 // The callback ceases execution as soon as the first failed assertion occurs, however Gomega
130 // does not register a failure with the FailHandler registered via RegisterFailHandler - it is up
131 // to the user to decide what to do with the returned error
132 func InterceptGomegaFailure(f func()) (err error) {
133 originalHandler := Default.(*internal.Gomega).Fail
134 Default.(*internal.Gomega).Fail = func(message string, callerSkip ...int) {
135 err = errors.New(message)
136 panic("stop execution")
137 }
138
139 defer func() {
140 Default.(*internal.Gomega).Fail = originalHandler
141 if e := recover(); e != nil {
142 if err == nil {
143 panic(e)
144 }
145 }
146 }()
147
148 f()
149 return err
150 }
151
152 func ensureDefaultGomegaIsConfigured() {
153 if !Default.(*internal.Gomega).IsConfigured() {
154 panic(nilGomegaPanic)
155 }
109156 }
110157
111158 // Ω wraps an actual value allowing assertions to be made on it:
126173 //
127174 // Ω and Expect are identical
128175 func Ω(actual interface{}, extra ...interface{}) Assertion {
129 return ExpectWithOffset(0, actual, extra...)
176 ensureDefaultGomegaIsConfigured()
177 return Default.Ω(actual, extra...)
130178 }
131179
132180 // Expect wraps an actual value allowing assertions to be made on it:
147195 //
148196 // Expect and Ω are identical
149197 func Expect(actual interface{}, extra ...interface{}) Assertion {
150 return ExpectWithOffset(0, actual, extra...)
198 ensureDefaultGomegaIsConfigured()
199 return Default.Expect(actual, extra...)
151200 }
152201
153202 // ExpectWithOffset wraps an actual value allowing assertions to be made on it:
160209 // error message to refer to the calling line in the test (as opposed to the line in the helper function)
161210 // set the first argument of `ExpectWithOffset` appropriately.
162211 func ExpectWithOffset(offset int, actual interface{}, extra ...interface{}) Assertion {
163 if globalFailWrapper == nil {
164 panic(nilFailHandlerPanic)
165 }
166 return assertion.New(actual, globalFailWrapper, offset, extra...)
167 }
168
169 // Eventually wraps an actual value allowing assertions to be made on it.
170 // The assertion is tried periodically until it passes or a timeout occurs.
171 //
172 // Both the timeout and polling interval are configurable as optional arguments:
173 // The first optional argument is the timeout
174 // The second optional argument is the polling interval
175 //
176 // Both intervals can either be specified as time.Duration, parsable duration strings or as floats/integers. In the
177 // last case they are interpreted as seconds.
178 //
179 // If Eventually is passed an actual that is a function taking no arguments and returning at least one value,
180 // then Eventually will call the function periodically and try the matcher against the function's first return value.
181 //
182 // Example:
183 //
184 // Eventually(func() int {
185 // return thingImPolling.Count()
186 // }).Should(BeNumerically(">=", 17))
187 //
188 // Note that this example could be rewritten:
189 //
190 // Eventually(thingImPolling.Count).Should(BeNumerically(">=", 17))
191 //
192 // If the function returns more than one value, then Eventually will pass the first value to the matcher and
193 // assert that all other values are nil/zero.
194 // This allows you to pass Eventually a function that returns a value and an error - a common pattern in Go.
195 //
196 // For example, consider a method that returns a value and an error:
197 // func FetchFromDB() (string, error)
198 //
199 // Then
200 // Eventually(FetchFromDB).Should(Equal("hasselhoff"))
201 //
202 // Will pass only if the the returned error is nil and the returned string passes the matcher.
203 //
204 // Eventually's default timeout is 1 second, and its default polling interval is 10ms
212 ensureDefaultGomegaIsConfigured()
213 return Default.ExpectWithOffset(offset, actual, extra...)
214 }
215
216 /*
217 Eventually enables making assertions on asynchronous behavior.
218
219 Eventually checks that an assertion *eventually* passes. Eventually blocks when called and attempts an assertion periodically until it passes or a timeout occurs. Both the timeout and polling interval are configurable as optional arguments.
220 The first optional argument is the timeout (which defaults to 1s), the second is the polling interval (which defaults to 10ms). Both intervals can be specified as time.Duration, parsable duration strings or floats/integers (in which case they are interpreted as seconds).
221
222 Eventually works with any Gomega compatible matcher and supports making assertions against three categories of actual value:
223
224 **Category 1: Making Eventually assertions on values**
225
226 There are several examples of values that can change over time. These can be passed in to Eventually and will be passed to the matcher repeatedly until a match occurs. For example:
227
228 c := make(chan bool)
229 go DoStuff(c)
230 Eventually(c, "50ms").Should(BeClosed())
231
232 will poll the channel repeatedly until it is closed. In this example `Eventually` will block until either the specified timeout of 50ms has elapsed or the channel is closed, whichever comes first.
233
234 Several Gomega libraries allow you to use Eventually in this way. For example, the gomega/gexec package allows you to block until a *gexec.Session exits successfuly via:
235
236 Eventually(session).Should(gexec.Exit(0))
237
238 And the gomega/gbytes package allows you to monitor a streaming *gbytes.Buffer until a given string is seen:
239
240 Eventually(buffer).Should(gbytes.Say("hello there"))
241
242 In these examples, both `session` and `buffer` are designed to be thread-safe when polled by the `Exit` and `Say` matchers. This is not true in general of most raw values, so while it is tempting to do something like:
243
244 // THIS IS NOT THREAD-SAFE
245 var s *string
246 go mutateStringEventually(s)
247 Eventually(s).Should(Equal("I've changed"))
248
249 this will trigger Go's race detector as the goroutine polling via Eventually will race over the value of s with the goroutine mutating the string. For cases like this you can use channels or introduce your own locking around s by passing Eventually a function.
250
251 **Category 2: Make Eventually assertions on functions**
252
253 Eventually can be passed functions that **take no arguments** and **return at least one value**. When configured this way, Eventually will poll the function repeatedly and pass the first returned value to the matcher.
254
255 For example:
256
257 Eventually(func() int {
258 return client.FetchCount()
259 }).Should(BeNumerically(">=", 17))
260
261 will repeatedly poll client.FetchCount until the BeNumerically matcher is satisfied. (Note that this example could have been written as Eventually(client.FetchCount).Should(BeNumerically(">=", 17)))
262
263 If multple values are returned by the function, Eventually will pass the first value to the matcher and require that all others are zero-valued. This allows you to pass Eventually a function that returns a value and an error - a common patternin Go.
264
265 For example, consider a method that returns a value and an error:
266 func FetchFromDB() (string, error)
267
268 Then
269 Eventually(FetchFromDB).Should(Equal("got it"))
270
271 will pass only if and when the returned error is nil *and* the returned string satisfies the matcher.
272
273 It is important to note that the function passed into Eventually is invoked *synchronously* when polled. Eventually does not (in fact, it cannot) kill the function if it takes longer to return than Eventually's configured timeout. You should design your functions with this in mind.
274
275 **Category 3: Making assertions _in_ the function passed into Eventually**
276
277 When testing complex systems it can be valuable to assert that a _set_ of assertions passes Eventually. Eventually supports this by accepting functions that take a single Gomega argument and return zero or more values.
278
279 Here's an example that makes some asssertions and returns a value and error:
280
281 Eventually(func(g Gomega) (Widget, error) {
282 ids, err := client.FetchIDs()
283 g.Expect(err).NotTo(HaveOccurred())
284 g.Expect(ids).To(ContainElement(1138))
285 return client.FetchWidget(1138)
286 }).Should(Equal(expectedWidget))
287
288 will pass only if all the assertions in the polled function pass and the return value satisfied the matcher.
289
290 Eventually also supports a special case polling function that takes a single Gomega argument and returns no values. Eventually assumes such a function is making assertions and is designed to work with the Succeed matcher to validate that all assertions have passed.
291 For example:
292
293 Eventually(func(g Gomega) {
294 model, err := client.Find(1138)
295 g.Expect(err).NotTo(HaveOccurred())
296 g.Expect(model.Reticulate()).To(Succeed())
297 g.Expect(model.IsReticulated()).To(BeTrue())
298 g.Expect(model.Save()).To(Succeed())
299 }).Should(Succeed())
300
301 will rerun the function until all assertions pass.
302 */
205303 func Eventually(actual interface{}, intervals ...interface{}) AsyncAssertion {
206 return EventuallyWithOffset(0, actual, intervals...)
304 ensureDefaultGomegaIsConfigured()
305 return Default.Eventually(actual, intervals...)
207306 }
208307
209308 // EventuallyWithOffset operates like Eventually but takes an additional
210309 // initial argument to indicate an offset in the call stack. This is useful when building helper
211310 // functions that contain matchers. To learn more, read about `ExpectWithOffset`.
212311 func EventuallyWithOffset(offset int, actual interface{}, intervals ...interface{}) AsyncAssertion {
213 if globalFailWrapper == nil {
214 panic(nilFailHandlerPanic)
215 }
216 timeoutInterval := defaultEventuallyTimeout
217 pollingInterval := defaultEventuallyPollingInterval
218 if len(intervals) > 0 {
219 timeoutInterval = toDuration(intervals[0])
220 }
221 if len(intervals) > 1 {
222 pollingInterval = toDuration(intervals[1])
223 }
224 return asyncassertion.New(asyncassertion.AsyncAssertionTypeEventually, actual, globalFailWrapper, timeoutInterval, pollingInterval, offset)
225 }
226
227 // Consistently wraps an actual value allowing assertions to be made on it.
228 // The assertion is tried periodically and is required to pass for a period of time.
229 //
230 // Both the total time and polling interval are configurable as optional arguments:
231 // The first optional argument is the duration that Consistently will run for
232 // The second optional argument is the polling interval
233 //
234 // Both intervals can either be specified as time.Duration, parsable duration strings or as floats/integers. In the
235 // last case they are interpreted as seconds.
236 //
237 // If Consistently is passed an actual that is a function taking no arguments and returning at least one value,
238 // then Consistently will call the function periodically and try the matcher against the function's first return value.
239 //
240 // If the function returns more than one value, then Consistently will pass the first value to the matcher and
241 // assert that all other values are nil/zero.
242 // This allows you to pass Consistently a function that returns a value and an error - a common pattern in Go.
243 //
244 // Consistently is useful in cases where you want to assert that something *does not happen* over a period of time.
245 // For example, you want to assert that a goroutine does *not* send data down a channel. In this case, you could:
246 //
247 // Consistently(channel).ShouldNot(Receive())
248 //
249 // Consistently's default duration is 100ms, and its default polling interval is 10ms
312 ensureDefaultGomegaIsConfigured()
313 return Default.EventuallyWithOffset(offset, actual, intervals...)
314 }
315
316 /*
317 Consistently, like Eventually, enables making assertions on asynchronous behavior.
318
319 Consistently blocks when called for a specified duration. During that duration Consistently repeatedly polls its matcher and ensures that it is satisfied. If the matcher is consistently satisfied, then Consistently will pass. Otherwise Consistently will fail.
320
321 Both the total waiting duration and the polling interval are configurable as optional arguments. The first optional arugment is the duration that Consistently will run for (defaults to 100ms), and the second argument is the polling interval (defaults to 10ms). As with Eventually, these intervals can be passed in as time.Duration, parsable duration strings or an integer or float number of seconds.
322
323 Consistently accepts the same three categories of actual as Eventually, check the Eventually docs to learn more.
324
325 Consistently is useful in cases where you want to assert that something *does not happen* for a period of time. For example, you may want to assert that a goroutine does *not* send data down a channel. In this case you could write:
326
327 Consistently(channel, "200ms").ShouldNot(Receive())
328
329 This will block for 200 milliseconds and repeatedly check the channel and ensure nothing has been received.
330 */
250331 func Consistently(actual interface{}, intervals ...interface{}) AsyncAssertion {
251 return ConsistentlyWithOffset(0, actual, intervals...)
332 ensureDefaultGomegaIsConfigured()
333 return Default.Consistently(actual, intervals...)
252334 }
253335
254336 // ConsistentlyWithOffset operates like Consistently but takes an additional
255337 // initial argument to indicate an offset in the call stack. This is useful when building helper
256338 // functions that contain matchers. To learn more, read about `ExpectWithOffset`.
257339 func ConsistentlyWithOffset(offset int, actual interface{}, intervals ...interface{}) AsyncAssertion {
258 if globalFailWrapper == nil {
259 panic(nilFailHandlerPanic)
260 }
261 timeoutInterval := defaultConsistentlyDuration
262 pollingInterval := defaultConsistentlyPollingInterval
263 if len(intervals) > 0 {
264 timeoutInterval = toDuration(intervals[0])
265 }
266 if len(intervals) > 1 {
267 pollingInterval = toDuration(intervals[1])
268 }
269 return asyncassertion.New(asyncassertion.AsyncAssertionTypeConsistently, actual, globalFailWrapper, timeoutInterval, pollingInterval, offset)
340 ensureDefaultGomegaIsConfigured()
341 return Default.ConsistentlyWithOffset(offset, actual, intervals...)
270342 }
271343
272344 // SetDefaultEventuallyTimeout sets the default timeout duration for Eventually. Eventually will repeatedly poll your condition until it succeeds, or until this timeout elapses.
273345 func SetDefaultEventuallyTimeout(t time.Duration) {
274 defaultEventuallyTimeout = t
346 Default.SetDefaultEventuallyTimeout(t)
275347 }
276348
277349 // SetDefaultEventuallyPollingInterval sets the default polling interval for Eventually.
278350 func SetDefaultEventuallyPollingInterval(t time.Duration) {
279 defaultEventuallyPollingInterval = t
351 Default.SetDefaultEventuallyPollingInterval(t)
280352 }
281353
282354 // SetDefaultConsistentlyDuration sets the default duration for Consistently. Consistently will verify that your condition is satisfied for this long.
283355 func SetDefaultConsistentlyDuration(t time.Duration) {
284 defaultConsistentlyDuration = t
356 Default.SetDefaultConsistentlyDuration(t)
285357 }
286358
287359 // SetDefaultConsistentlyPollingInterval sets the default polling interval for Consistently.
288360 func SetDefaultConsistentlyPollingInterval(t time.Duration) {
289 defaultConsistentlyPollingInterval = t
361 Default.SetDefaultConsistentlyPollingInterval(t)
290362 }
291363
292364 // AsyncAssertion is returned by Eventually and Consistently and polls the actual value passed into Eventually against
304376 //
305377 // Eventually(myChannel).Should(Receive(), "Something should have come down the pipe.")
306378 // Consistently(myChannel).ShouldNot(Receive(), func() string { return "Nothing should have come down the pipe." })
307 type AsyncAssertion interface {
308 Should(matcher types.GomegaMatcher, optionalDescription ...interface{}) bool
309 ShouldNot(matcher types.GomegaMatcher, optionalDescription ...interface{}) bool
310 }
379 type AsyncAssertion = types.AsyncAssertion
311380
312381 // GomegaAsyncAssertion is deprecated in favor of AsyncAssertion, which does not stutter.
313 type GomegaAsyncAssertion = AsyncAssertion
382 type GomegaAsyncAssertion = types.AsyncAssertion
314383
315384 // Assertion is returned by Ω and Expect and compares the actual value to the matcher
316385 // passed to the Should/ShouldNot and To/ToNot/NotTo methods.
329398 // Example:
330399 //
331400 // Ω(farm.HasCow()).Should(BeTrue(), "Farm %v should have a cow", farm)
332 type Assertion interface {
333 Should(matcher types.GomegaMatcher, optionalDescription ...interface{}) bool
334 ShouldNot(matcher types.GomegaMatcher, optionalDescription ...interface{}) bool
335
336 To(matcher types.GomegaMatcher, optionalDescription ...interface{}) bool
337 ToNot(matcher types.GomegaMatcher, optionalDescription ...interface{}) bool
338 NotTo(matcher types.GomegaMatcher, optionalDescription ...interface{}) bool
339 }
401 type Assertion = types.Assertion
340402
341403 // GomegaAssertion is deprecated in favor of Assertion, which does not stutter.
342 type GomegaAssertion = Assertion
404 type GomegaAssertion = types.Assertion
343405
344406 // OmegaMatcher is deprecated in favor of the better-named and better-organized types.GomegaMatcher but sticks around to support existing code that uses it
345 type OmegaMatcher types.GomegaMatcher
346
347 // WithT wraps a *testing.T and provides `Expect`, `Eventually`, and `Consistently` methods. This allows you to leverage
348 // Gomega's rich ecosystem of matchers in standard `testing` test suites.
349 //
350 // Use `NewWithT` to instantiate a `WithT`
351 type WithT struct {
352 t types.GomegaTestingT
353 }
354
355 // GomegaWithT is deprecated in favor of gomega.WithT, which does not stutter.
356 type GomegaWithT = WithT
357
358 // NewWithT takes a *testing.T and returngs a `gomega.WithT` allowing you to use `Expect`, `Eventually`, and `Consistently` along with
359 // Gomega's rich ecosystem of matchers in standard `testing` test suits.
360 //
361 // func TestFarmHasCow(t *testing.T) {
362 // g := gomega.NewWithT(t)
363 //
364 // f := farm.New([]string{"Cow", "Horse"})
365 // g.Expect(f.HasCow()).To(BeTrue(), "Farm should have cow")
366 // }
367 func NewWithT(t types.GomegaTestingT) *WithT {
368 return &WithT{
369 t: t,
370 }
371 }
372
373 // NewGomegaWithT is deprecated in favor of gomega.NewWithT, which does not stutter.
374 func NewGomegaWithT(t types.GomegaTestingT) *GomegaWithT {
375 return NewWithT(t)
376 }
377
378 // ExpectWithOffset is used to make assertions. See documentation for ExpectWithOffset.
379 func (g *WithT) ExpectWithOffset(offset int, actual interface{}, extra ...interface{}) Assertion {
380 return assertion.New(actual, testingtsupport.BuildTestingTGomegaFailWrapper(g.t), offset, extra...)
381 }
382
383 // EventuallyWithOffset is used to make asynchronous assertions. See documentation for EventuallyWithOffset.
384 func (g *WithT) EventuallyWithOffset(offset int, actual interface{}, intervals ...interface{}) AsyncAssertion {
385 timeoutInterval := defaultEventuallyTimeout
386 pollingInterval := defaultEventuallyPollingInterval
387 if len(intervals) > 0 {
388 timeoutInterval = toDuration(intervals[0])
389 }
390 if len(intervals) > 1 {
391 pollingInterval = toDuration(intervals[1])
392 }
393 return asyncassertion.New(asyncassertion.AsyncAssertionTypeEventually, actual, testingtsupport.BuildTestingTGomegaFailWrapper(g.t), timeoutInterval, pollingInterval, offset)
394 }
395
396 // ConsistentlyWithOffset is used to make asynchronous assertions. See documentation for ConsistentlyWithOffset.
397 func (g *WithT) ConsistentlyWithOffset(offset int, actual interface{}, intervals ...interface{}) AsyncAssertion {
398 timeoutInterval := defaultConsistentlyDuration
399 pollingInterval := defaultConsistentlyPollingInterval
400 if len(intervals) > 0 {
401 timeoutInterval = toDuration(intervals[0])
402 }
403 if len(intervals) > 1 {
404 pollingInterval = toDuration(intervals[1])
405 }
406 return asyncassertion.New(asyncassertion.AsyncAssertionTypeConsistently, actual, testingtsupport.BuildTestingTGomegaFailWrapper(g.t), timeoutInterval, pollingInterval, offset)
407 }
408
409 // Expect is used to make assertions. See documentation for Expect.
410 func (g *WithT) Expect(actual interface{}, extra ...interface{}) Assertion {
411 return g.ExpectWithOffset(0, actual, extra...)
412 }
413
414 // Eventually is used to make asynchronous assertions. See documentation for Eventually.
415 func (g *WithT) Eventually(actual interface{}, intervals ...interface{}) AsyncAssertion {
416 return g.EventuallyWithOffset(0, actual, intervals...)
417 }
418
419 // Consistently is used to make asynchronous assertions. See documentation for Consistently.
420 func (g *WithT) Consistently(actual interface{}, intervals ...interface{}) AsyncAssertion {
421 return g.ConsistentlyWithOffset(0, actual, intervals...)
422 }
423
424 func toDuration(input interface{}) time.Duration {
425 duration, ok := input.(time.Duration)
426 if ok {
427 return duration
428 }
429
430 value := reflect.ValueOf(input)
431 kind := reflect.TypeOf(input).Kind()
432
433 if reflect.Int <= kind && kind <= reflect.Int64 {
434 return time.Duration(value.Int()) * time.Second
435 } else if reflect.Uint <= kind && kind <= reflect.Uint64 {
436 return time.Duration(value.Uint()) * time.Second
437 } else if reflect.Float32 <= kind && kind <= reflect.Float64 {
438 return time.Duration(value.Float() * float64(time.Second))
439 } else if reflect.String == kind {
440 duration, err := time.ParseDuration(value.String())
441 if err != nil {
442 panic(fmt.Sprintf("%#v is not a valid parsable duration string.", input))
443 }
444 return duration
445 }
446
447 panic(fmt.Sprintf("%v is not a valid interval. Must be time.Duration, parsable duration string or a number.", input))
448 }
449
450 // Gomega describes the essential Gomega DSL. This interface allows libraries
451 // to abstract between the standard package-level function implementations
452 // and alternatives like *WithT.
453 type Gomega interface {
454 Expect(actual interface{}, extra ...interface{}) Assertion
455 Eventually(actual interface{}, intervals ...interface{}) AsyncAssertion
456 Consistently(actual interface{}, intervals ...interface{}) AsyncAssertion
457 }
458
459 type globalFailHandlerGomega struct{}
460
461 // DefaultGomega supplies the standard package-level implementation
462 var Default Gomega = globalFailHandlerGomega{}
463
464 // Expect is used to make assertions. See documentation for Expect.
465 func (globalFailHandlerGomega) Expect(actual interface{}, extra ...interface{}) Assertion {
466 return Expect(actual, extra...)
467 }
468
469 // Eventually is used to make asynchronous assertions. See documentation for Eventually.
470 func (globalFailHandlerGomega) Eventually(actual interface{}, extra ...interface{}) AsyncAssertion {
471 return Eventually(actual, extra...)
472 }
473
474 // Consistently is used to make asynchronous assertions. See documentation for Consistently.
475 func (globalFailHandlerGomega) Consistently(actual interface{}, extra ...interface{}) AsyncAssertion {
476 return Consistently(actual, extra...)
477 }
407 type OmegaMatcher = types.GomegaMatcher
66 "fmt"
77 "reflect"
88 "runtime/debug"
9 "strconv"
910
1011 "github.com/onsi/gomega/format"
1112 errorsutil "github.com/onsi/gomega/gstruct/errors"
2324 // "b": Equal("b"),
2425 // }))
2526 func MatchAllElements(identifier Identifier, elements Elements) types.GomegaMatcher {
27 return &ElementsMatcher{
28 Identifier: identifier,
29 Elements: elements,
30 }
31 }
32
33 //MatchAllElementsWithIndex succeeds if every element of a slice matches the element matcher it maps to
34 //through the id with index function, and every element matcher is matched.
35 // idFn := func(index int, element interface{}) string {
36 // return strconv.Itoa(index)
37 // }
38 //
39 // Expect([]string{"a", "b"}).To(MatchAllElements(idFn, Elements{
40 // "0": Equal("a"),
41 // "1": Equal("b"),
42 // }))
43 func MatchAllElementsWithIndex(identifier IdentifierWithIndex, elements Elements) types.GomegaMatcher {
2644 return &ElementsMatcher{
2745 Identifier: identifier,
2846 Elements: elements,
4664 // "d": Equal("d"),
4765 // }))
4866 func MatchElements(identifier Identifier, options Options, elements Elements) types.GomegaMatcher {
67 return &ElementsMatcher{
68 Identifier: identifier,
69 Elements: elements,
70 IgnoreExtras: options&IgnoreExtras != 0,
71 IgnoreMissing: options&IgnoreMissing != 0,
72 AllowDuplicates: options&AllowDuplicates != 0,
73 }
74 }
75
76 //MatchElementsWithIndex succeeds if each element of a slice matches the element matcher it maps to
77 //through the id with index function. It can ignore extra elements and/or missing elements.
78 // idFn := func(index int, element interface{}) string {
79 // return strconv.Itoa(index)
80 // }
81 //
82 // Expect([]string{"a", "b", "c"}).To(MatchElements(idFn, IgnoreExtras, Elements{
83 // "0": Equal("a"),
84 // "1": Equal("b"),
85 // }))
86 // Expect([]string{"a", "c"}).To(MatchElements(idFn, IgnoreMissing, Elements{
87 // "0": Equal("a"),
88 // "1": Equal("b"),
89 // "2": Equal("c"),
90 // "3": Equal("d"),
91 // }))
92 func MatchElementsWithIndex(identifier IdentifierWithIndex, options Options, elements Elements) types.GomegaMatcher {
4993 return &ElementsMatcher{
5094 Identifier: identifier,
5195 Elements: elements,
62106 // Matchers for each element.
63107 Elements Elements
64108 // Function mapping an element to the string key identifying its matcher.
65 Identifier Identifier
109 Identifier Identify
66110
67111 // Whether to ignore extra elements or consider it an error.
68112 IgnoreExtras bool
81125 // Function for identifying (mapping) elements.
82126 type Identifier func(element interface{}) string
83127
128 // Calls the underlying fucntion with the provided params.
129 // Identifier drops the index.
130 func (i Identifier) WithIndexAndElement(index int, element interface{}) string {
131 return i(element)
132 }
133
134 // Uses the index and element to generate an element name
135 type IdentifierWithIndex func(index int, element interface{}) string
136
137 // Calls the underlying fucntion with the provided params.
138 // IdentifierWithIndex uses the index.
139 func (i IdentifierWithIndex) WithIndexAndElement(index int, element interface{}) string {
140 return i(index, element)
141 }
142
143 // Interface for identifing the element
144 type Identify interface {
145 WithIndexAndElement(i int, element interface{}) string
146 }
147
148 // IndexIdentity is a helper function for using an index as
149 // the key in the element map
150 func IndexIdentity(index int, _ interface{}) string {
151 return strconv.Itoa(index)
152 }
153
84154 func (m *ElementsMatcher) Match(actual interface{}) (success bool, err error) {
85155 if reflect.TypeOf(actual).Kind() != reflect.Slice {
86156 return false, fmt.Errorf("%v is type %T, expected slice", actual, actual)
105175 elements := map[string]bool{}
106176 for i := 0; i < val.Len(); i++ {
107177 element := val.Index(i).Interface()
108 id := m.Identifier(element)
178 id := m.Identifier.WithIndexAndElement(i, element)
109179 if elements[id] {
110180 if !m.AllowDuplicates {
111181 errs = append(errs, fmt.Errorf("found duplicate element ID %s", id))
136136 Expect(nils).Should(m, "should allow an uninitialized slice")
137137 })
138138 })
139
140 Context("with index identifier", func() {
141 allElements := []string{"a", "b"}
142 missingElements := []string{"a"}
143 extraElements := []string{"a", "b", "c"}
144 duplicateElements := []string{"a", "a", "b"}
145 empty := []string{}
146 var nils []string
147
148 It("should use index", func() {
149 m := MatchAllElementsWithIndex(IndexIdentity, Elements{
150 "0": Equal("a"),
151 "1": Equal("b"),
152 })
153 Expect(allElements).Should(m, "should match all elements")
154 Expect(missingElements).ShouldNot(m, "should fail with missing elements")
155 Expect(extraElements).ShouldNot(m, "should fail with extra elements")
156 Expect(duplicateElements).ShouldNot(m, "should fail with duplicate elements")
157 Expect(nils).ShouldNot(m, "should fail with an uninitialized slice")
158
159 m = MatchAllElementsWithIndex(IndexIdentity, Elements{
160 "0": Equal("a"),
161 "1": Equal("fail"),
162 })
163 Expect(allElements).ShouldNot(m, "should run nested matchers")
164
165 m = MatchAllElementsWithIndex(IndexIdentity, Elements{})
166 Expect(empty).Should(m, "should handle empty slices")
167 Expect(allElements).ShouldNot(m, "should handle only empty slices")
168 Expect(nils).Should(m, "should handle nil slices")
169 })
170 })
139171 })
140172
141173 func id(element interface{}) string {
+0
-109
internal/assertion/assertion.go less more
0 package assertion
1
2 import (
3 "fmt"
4 "reflect"
5
6 "github.com/onsi/gomega/types"
7 )
8
9 type Assertion struct {
10 actualInput interface{}
11 failWrapper *types.GomegaFailWrapper
12 offset int
13 extra []interface{}
14 }
15
16 func New(actualInput interface{}, failWrapper *types.GomegaFailWrapper, offset int, extra ...interface{}) *Assertion {
17 return &Assertion{
18 actualInput: actualInput,
19 failWrapper: failWrapper,
20 offset: offset,
21 extra: extra,
22 }
23 }
24
25 func (assertion *Assertion) Should(matcher types.GomegaMatcher, optionalDescription ...interface{}) bool {
26 assertion.failWrapper.TWithHelper.Helper()
27 return assertion.vetExtras(optionalDescription...) && assertion.match(matcher, true, optionalDescription...)
28 }
29
30 func (assertion *Assertion) ShouldNot(matcher types.GomegaMatcher, optionalDescription ...interface{}) bool {
31 assertion.failWrapper.TWithHelper.Helper()
32 return assertion.vetExtras(optionalDescription...) && assertion.match(matcher, false, optionalDescription...)
33 }
34
35 func (assertion *Assertion) To(matcher types.GomegaMatcher, optionalDescription ...interface{}) bool {
36 assertion.failWrapper.TWithHelper.Helper()
37 return assertion.vetExtras(optionalDescription...) && assertion.match(matcher, true, optionalDescription...)
38 }
39
40 func (assertion *Assertion) ToNot(matcher types.GomegaMatcher, optionalDescription ...interface{}) bool {
41 assertion.failWrapper.TWithHelper.Helper()
42 return assertion.vetExtras(optionalDescription...) && assertion.match(matcher, false, optionalDescription...)
43 }
44
45 func (assertion *Assertion) NotTo(matcher types.GomegaMatcher, optionalDescription ...interface{}) bool {
46 assertion.failWrapper.TWithHelper.Helper()
47 return assertion.vetExtras(optionalDescription...) && assertion.match(matcher, false, optionalDescription...)
48 }
49
50 func (assertion *Assertion) buildDescription(optionalDescription ...interface{}) string {
51 switch len(optionalDescription) {
52 case 0:
53 return ""
54 case 1:
55 if describe, ok := optionalDescription[0].(func() string); ok {
56 return describe() + "\n"
57 }
58 }
59 return fmt.Sprintf(optionalDescription[0].(string), optionalDescription[1:]...) + "\n"
60 }
61
62 func (assertion *Assertion) match(matcher types.GomegaMatcher, desiredMatch bool, optionalDescription ...interface{}) bool {
63 matches, err := matcher.Match(assertion.actualInput)
64 assertion.failWrapper.TWithHelper.Helper()
65 if err != nil {
66 description := assertion.buildDescription(optionalDescription...)
67 assertion.failWrapper.Fail(description+err.Error(), 2+assertion.offset)
68 return false
69 }
70 if matches != desiredMatch {
71 var message string
72 if desiredMatch {
73 message = matcher.FailureMessage(assertion.actualInput)
74 } else {
75 message = matcher.NegatedFailureMessage(assertion.actualInput)
76 }
77 description := assertion.buildDescription(optionalDescription...)
78 assertion.failWrapper.Fail(description+message, 2+assertion.offset)
79 return false
80 }
81
82 return true
83 }
84
85 func (assertion *Assertion) vetExtras(optionalDescription ...interface{}) bool {
86 success, message := vetExtras(assertion.extra)
87 if success {
88 return true
89 }
90
91 description := assertion.buildDescription(optionalDescription...)
92 assertion.failWrapper.TWithHelper.Helper()
93 assertion.failWrapper.Fail(description+message, 2+assertion.offset)
94 return false
95 }
96
97 func vetExtras(extras []interface{}) (bool, string) {
98 for i, extra := range extras {
99 if extra != nil {
100 zeroValue := reflect.Zero(reflect.TypeOf(extra)).Interface()
101 if !reflect.DeepEqual(zeroValue, extra) {
102 message := fmt.Sprintf("Unexpected non-nil/non-zero extra argument at index %d:\n\t<%T>: %#v", i+1, extra, extra)
103 return false, message
104 }
105 }
106 }
107 return true, ""
108 }
+0
-13
internal/assertion/assertion_suite_test.go less more
0 package assertion_test
1
2 import (
3 . "github.com/onsi/ginkgo"
4 . "github.com/onsi/gomega"
5
6 "testing"
7 )
8
9 func TestAssertion(t *testing.T) {
10 RegisterFailHandler(Fail)
11 RunSpecs(t, "Assertion Suite")
12 }
+0
-277
internal/assertion/assertion_test.go less more
0 package assertion_test
1
2 import (
3 "errors"
4
5 "github.com/onsi/gomega/internal/testingtsupport"
6
7 . "github.com/onsi/ginkgo"
8 . "github.com/onsi/gomega"
9 "github.com/onsi/gomega/internal/assertion"
10 "github.com/onsi/gomega/internal/fakematcher"
11 "github.com/onsi/gomega/types"
12 )
13
14 var _ = Describe("Assertion", func() {
15 var (
16 a *assertion.Assertion
17 failureMessage string
18 failureCallerSkip int
19 matcher *fakematcher.FakeMatcher
20 )
21
22 input := "The thing I'm testing"
23
24 var fakeFailWrapper = &types.GomegaFailWrapper{
25 Fail: func(message string, callerSkip ...int) {
26 failureMessage = message
27 if len(callerSkip) == 1 {
28 failureCallerSkip = callerSkip[0]
29 }
30 },
31 TWithHelper: testingtsupport.EmptyTWithHelper{},
32 }
33
34 BeforeEach(func() {
35 matcher = &fakematcher.FakeMatcher{}
36 failureMessage = ""
37 failureCallerSkip = 0
38 a = assertion.New(input, fakeFailWrapper, 1)
39 })
40
41 When("called", func() {
42 It("should pass the provided input value to the matcher", func() {
43 a.Should(matcher)
44
45 Expect(matcher.ReceivedActual).Should(Equal(input))
46 matcher.ReceivedActual = ""
47
48 a.ShouldNot(matcher)
49
50 Expect(matcher.ReceivedActual).Should(Equal(input))
51 matcher.ReceivedActual = ""
52
53 a.To(matcher)
54
55 Expect(matcher.ReceivedActual).Should(Equal(input))
56 matcher.ReceivedActual = ""
57
58 a.ToNot(matcher)
59
60 Expect(matcher.ReceivedActual).Should(Equal(input))
61 matcher.ReceivedActual = ""
62
63 a.NotTo(matcher)
64
65 Expect(matcher.ReceivedActual).Should(Equal(input))
66 })
67 })
68
69 When("the matcher succeeds", func() {
70 BeforeEach(func() {
71 matcher.MatchesToReturn = true
72 matcher.ErrToReturn = nil
73 })
74
75 Context("and a positive assertion is being made", func() {
76 It("should not call the failure callback", func() {
77 a.Should(matcher)
78 Expect(failureMessage).Should(Equal(""))
79 })
80
81 It("should be true", func() {
82 Expect(a.Should(matcher)).Should(BeTrue())
83 })
84 })
85
86 Context("and a negative assertion is being made", func() {
87 It("should call the failure callback", func() {
88 a.ShouldNot(matcher)
89 Expect(failureMessage).Should(Equal("negative: The thing I'm testing"))
90 Expect(failureCallerSkip).Should(Equal(3))
91 })
92
93 It("should be false", func() {
94 Expect(a.ShouldNot(matcher)).Should(BeFalse())
95 })
96 })
97
98 Context("and the optional description is a function", func() {
99 It("should not evaluate that function", func() {
100 evaluated := false
101 a.Should(matcher, func() string {
102 evaluated = true
103 return "A description"
104 })
105 Expect(evaluated).Should(BeFalse())
106 })
107 })
108 })
109
110 When("the matcher fails", func() {
111 BeforeEach(func() {
112 matcher.MatchesToReturn = false
113 matcher.ErrToReturn = nil
114 })
115
116 Context("and a positive assertion is being made", func() {
117 It("should call the failure callback", func() {
118 a.Should(matcher)
119 Expect(failureMessage).Should(Equal("positive: The thing I'm testing"))
120 Expect(failureCallerSkip).Should(Equal(3))
121 })
122
123 It("should be false", func() {
124 Expect(a.Should(matcher)).Should(BeFalse())
125 })
126 })
127
128 Context("and a negative assertion is being made", func() {
129 It("should not call the failure callback", func() {
130 a.ShouldNot(matcher)
131 Expect(failureMessage).Should(Equal(""))
132 })
133
134 It("should be true", func() {
135 Expect(a.ShouldNot(matcher)).Should(BeTrue())
136 })
137 })
138 })
139
140 Context("When reporting a failure", func() {
141 BeforeEach(func() {
142 matcher.MatchesToReturn = false
143 matcher.ErrToReturn = nil
144 })
145
146 Context("and there is an optional description", func() {
147 It("should append the description to the failure message", func() {
148 a.Should(matcher, "A description")
149 Expect(failureMessage).Should(Equal("A description\npositive: The thing I'm testing"))
150 Expect(failureCallerSkip).Should(Equal(3))
151 })
152 })
153
154 Context("and there are multiple arguments to the optional description", func() {
155 It("should append the formatted description to the failure message", func() {
156 a.Should(matcher, "A description of [%d]", 3)
157 Expect(failureMessage).Should(Equal("A description of [3]\npositive: The thing I'm testing"))
158 Expect(failureCallerSkip).Should(Equal(3))
159 })
160 })
161
162 Context("and the optional description is a function", func() {
163 It("should append the description to the failure message", func() {
164 a.Should(matcher, func() string { return "A description" })
165 Expect(failureMessage).Should(Equal("A description\npositive: The thing I'm testing"))
166 Expect(failureCallerSkip).Should(Equal(3))
167 })
168 })
169 })
170
171 Context("When the matcher returns an error", func() {
172 BeforeEach(func() {
173 matcher.ErrToReturn = errors.New("Kaboom!")
174 })
175
176 Context("and a positive assertion is being made", func() {
177 It("should call the failure callback", func() {
178 matcher.MatchesToReturn = true
179 a.Should(matcher)
180 Expect(failureMessage).Should(Equal("Kaboom!"))
181 Expect(failureCallerSkip).Should(Equal(3))
182 })
183 })
184
185 Context("and a negative assertion is being made", func() {
186 It("should call the failure callback", func() {
187 matcher.MatchesToReturn = false
188 a.ShouldNot(matcher)
189 Expect(failureMessage).Should(Equal("Kaboom!"))
190 Expect(failureCallerSkip).Should(Equal(3))
191 })
192 })
193
194 It("should always be false", func() {
195 Expect(a.Should(matcher)).Should(BeFalse())
196 Expect(a.ShouldNot(matcher)).Should(BeFalse())
197 })
198 })
199
200 When("there are extra parameters", func() {
201 It("(a simple example)", func() {
202 Expect(func() (string, int, error) {
203 return "foo", 0, nil
204 }()).Should(Equal("foo"))
205 })
206
207 When("the parameters are all nil or zero", func() {
208 It("should invoke the matcher", func() {
209 matcher.MatchesToReturn = true
210 matcher.ErrToReturn = nil
211
212 var typedNil []string
213 a = assertion.New(input, fakeFailWrapper, 1, 0, nil, typedNil)
214
215 result := a.Should(matcher)
216 Expect(result).Should(BeTrue())
217 Expect(matcher.ReceivedActual).Should(Equal(input))
218
219 Expect(failureMessage).Should(BeZero())
220 })
221 })
222
223 When("any of the parameters are not nil or zero", func() {
224 It("should call the failure callback", func() {
225 matcher.MatchesToReturn = false
226 matcher.ErrToReturn = nil
227
228 a = assertion.New(input, fakeFailWrapper, 1, errors.New("foo"))
229 result := a.Should(matcher)
230 Expect(result).Should(BeFalse())
231 Expect(matcher.ReceivedActual).Should(BeZero(), "The matcher doesn't even get called")
232 Expect(failureMessage).Should(ContainSubstring("foo"))
233 failureMessage = ""
234
235 a = assertion.New(input, fakeFailWrapper, 1, nil, 1)
236 result = a.ShouldNot(matcher)
237 Expect(result).Should(BeFalse())
238 Expect(failureMessage).Should(ContainSubstring("1"))
239 failureMessage = ""
240
241 a = assertion.New(input, fakeFailWrapper, 1, nil, 0, []string{"foo"})
242 result = a.To(matcher)
243 Expect(result).Should(BeFalse())
244 Expect(failureMessage).Should(ContainSubstring("foo"))
245 failureMessage = ""
246
247 a = assertion.New(input, fakeFailWrapper, 1, nil, 0, []string{"foo"})
248 result = a.ToNot(matcher)
249 Expect(result).Should(BeFalse())
250 Expect(failureMessage).Should(ContainSubstring("foo"))
251 failureMessage = ""
252
253 a = assertion.New(input, fakeFailWrapper, 1, nil, 0, []string{"foo"})
254 result = a.NotTo(matcher)
255 Expect(result).Should(BeFalse())
256 Expect(failureMessage).Should(ContainSubstring("foo"))
257 Expect(failureCallerSkip).Should(Equal(3))
258 })
259 })
260 })
261
262 Context("Making an assertion without a registered fail handler", func() {
263 It("should panic", func() {
264 defer func() {
265 e := recover()
266 RegisterFailHandler(Fail)
267 if e == nil {
268 Fail("expected a panic to have occurred")
269 }
270 }()
271
272 RegisterFailHandler(nil)
273 Expect(true).Should(BeTrue())
274 })
275 })
276 })
0 package internal
1
2 import (
3 "fmt"
4 "reflect"
5
6 "github.com/onsi/gomega/types"
7 )
8
9 type Assertion struct {
10 actualInput interface{}
11 offset int
12 extra []interface{}
13 g *Gomega
14 }
15
16 func NewAssertion(actualInput interface{}, g *Gomega, offset int, extra ...interface{}) *Assertion {
17 return &Assertion{
18 actualInput: actualInput,
19 offset: offset,
20 extra: extra,
21 g: g,
22 }
23 }
24
25 func (assertion *Assertion) Should(matcher types.GomegaMatcher, optionalDescription ...interface{}) bool {
26 assertion.g.THelper()
27 return assertion.vetExtras(optionalDescription...) && assertion.match(matcher, true, optionalDescription...)
28 }
29
30 func (assertion *Assertion) ShouldNot(matcher types.GomegaMatcher, optionalDescription ...interface{}) bool {
31 assertion.g.THelper()
32 return assertion.vetExtras(optionalDescription...) && assertion.match(matcher, false, optionalDescription...)
33 }
34
35 func (assertion *Assertion) To(matcher types.GomegaMatcher, optionalDescription ...interface{}) bool {
36 assertion.g.THelper()
37 return assertion.vetExtras(optionalDescription...) && assertion.match(matcher, true, optionalDescription...)
38 }
39
40 func (assertion *Assertion) ToNot(matcher types.GomegaMatcher, optionalDescription ...interface{}) bool {
41 assertion.g.THelper()
42 return assertion.vetExtras(optionalDescription...) && assertion.match(matcher, false, optionalDescription...)
43 }
44
45 func (assertion *Assertion) NotTo(matcher types.GomegaMatcher, optionalDescription ...interface{}) bool {
46 assertion.g.THelper()
47 return assertion.vetExtras(optionalDescription...) && assertion.match(matcher, false, optionalDescription...)
48 }
49
50 func (assertion *Assertion) buildDescription(optionalDescription ...interface{}) string {
51 switch len(optionalDescription) {
52 case 0:
53 return ""
54 case 1:
55 if describe, ok := optionalDescription[0].(func() string); ok {
56 return describe() + "\n"
57 }
58 }
59 return fmt.Sprintf(optionalDescription[0].(string), optionalDescription[1:]...) + "\n"
60 }
61
62 func (assertion *Assertion) match(matcher types.GomegaMatcher, desiredMatch bool, optionalDescription ...interface{}) bool {
63 matches, err := matcher.Match(assertion.actualInput)
64 assertion.g.THelper()
65 if err != nil {
66 description := assertion.buildDescription(optionalDescription...)
67 assertion.g.Fail(description+err.Error(), 2+assertion.offset)
68 return false
69 }
70 if matches != desiredMatch {
71 var message string
72 if desiredMatch {
73 message = matcher.FailureMessage(assertion.actualInput)
74 } else {
75 message = matcher.NegatedFailureMessage(assertion.actualInput)
76 }
77 description := assertion.buildDescription(optionalDescription...)
78 assertion.g.Fail(description+message, 2+assertion.offset)
79 return false
80 }
81
82 return true
83 }
84
85 func (assertion *Assertion) vetExtras(optionalDescription ...interface{}) bool {
86 success, message := vetExtras(assertion.extra)
87 if success {
88 return true
89 }
90
91 description := assertion.buildDescription(optionalDescription...)
92 assertion.g.THelper()
93 assertion.g.Fail(description+message, 2+assertion.offset)
94 return false
95 }
96
97 func vetExtras(extras []interface{}) (bool, string) {
98 for i, extra := range extras {
99 if extra != nil {
100 zeroValue := reflect.Zero(reflect.TypeOf(extra)).Interface()
101 if !reflect.DeepEqual(zeroValue, extra) {
102 message := fmt.Sprintf("Unexpected non-nil/non-zero extra argument at index %d:\n\t<%T>: %#v", i+1, extra, extra)
103 return false, message
104 }
105 }
106 }
107 return true, ""
108 }
0 package internal_test
1
2 import (
3 . "github.com/onsi/ginkgo"
4 . "github.com/onsi/ginkgo/extensions/table"
5 . "github.com/onsi/gomega"
6 )
7
8 var _ = Describe("Making Synchronous Assertions", func() {
9 var SHOULD_MATCH = true
10 var SHOULD_NOT_MATCH = false
11 var IT_PASSES = true
12 var IT_FAILS = false
13
14 Extras := func(extras ...interface{}) []interface{} {
15 return extras
16 }
17
18 OptionalDescription := func(optionalDescription ...interface{}) []interface{} {
19 return optionalDescription
20 }
21
22 DescribeTable(
23 "the various cases",
24 func(actual interface{}, extras []interface{}, optionalDescription []interface{}, isPositiveAssertion bool, expectedFailureMessage string, expectedReturnValue bool) {
25 if isPositiveAssertion {
26 ig := NewInstrumentedGomega()
27 returnValue := ig.G.Expect(actual, extras...).To(SpecMatch(), optionalDescription...)
28 Expect(returnValue).To(Equal(expectedReturnValue))
29 Expect(ig.FailureMessage).To(Equal(expectedFailureMessage))
30 if expectedFailureMessage != "" {
31 Expect(ig.FailureSkip).To(Equal([]int{2}))
32 }
33 Expect(ig.RegisteredHelpers).To(ContainElement("(*Assertion).To"))
34
35 ig = NewInstrumentedGomega()
36 returnValue = ig.G.ExpectWithOffset(3, actual, extras...).To(SpecMatch(), optionalDescription...)
37 Expect(returnValue).To(Equal(expectedReturnValue))
38 Expect(ig.FailureMessage).To(Equal(expectedFailureMessage))
39 if expectedFailureMessage != "" {
40 Expect(ig.FailureSkip).To(Equal([]int{5}))
41 }
42 Expect(ig.RegisteredHelpers).To(ContainElement("(*Assertion).To"))
43
44 ig = NewInstrumentedGomega()
45 returnValue = ig.G.Ω(actual, extras...).Should(SpecMatch(), optionalDescription...)
46 Expect(returnValue).To(Equal(expectedReturnValue))
47 Expect(ig.FailureMessage).To(Equal(expectedFailureMessage))
48 if expectedFailureMessage != "" {
49 Expect(ig.FailureSkip).To(Equal([]int{2}))
50 }
51 Expect(ig.RegisteredHelpers).To(ContainElement("(*Assertion).Should"))
52 } else {
53 ig := NewInstrumentedGomega()
54 returnValue := ig.G.Expect(actual, extras...).ToNot(SpecMatch(), optionalDescription...)
55 Expect(returnValue).To(Equal(expectedReturnValue))
56 Expect(ig.FailureMessage).To(Equal(expectedFailureMessage))
57 if expectedFailureMessage != "" {
58 Expect(ig.FailureSkip).To(Equal([]int{2}))
59 }
60 Expect(ig.RegisteredHelpers).To(ContainElement("(*Assertion).ToNot"))
61
62 ig = NewInstrumentedGomega()
63 returnValue = ig.G.Expect(actual, extras...).NotTo(SpecMatch(), optionalDescription...)
64 Expect(returnValue).To(Equal(expectedReturnValue))
65 Expect(ig.FailureMessage).To(Equal(expectedFailureMessage))
66 if expectedFailureMessage != "" {
67 Expect(ig.FailureSkip).To(Equal([]int{2}))
68 }
69 Expect(ig.RegisteredHelpers).To(ContainElement("(*Assertion).NotTo"))
70
71 ig = NewInstrumentedGomega()
72 returnValue = ig.G.ExpectWithOffset(3, actual, extras...).NotTo(SpecMatch(), optionalDescription...)
73 Expect(returnValue).To(Equal(expectedReturnValue))
74 Expect(ig.FailureMessage).To(Equal(expectedFailureMessage))
75 if expectedFailureMessage != "" {
76 Expect(ig.FailureSkip).To(Equal([]int{5}))
77 }
78 Expect(ig.RegisteredHelpers).To(ContainElement("(*Assertion).NotTo"))
79
80 ig = NewInstrumentedGomega()
81 returnValue = ig.G.Ω(actual, extras...).ShouldNot(SpecMatch(), optionalDescription...)
82 Expect(returnValue).To(Equal(expectedReturnValue))
83 Expect(ig.FailureMessage).To(Equal(expectedFailureMessage))
84 if expectedFailureMessage != "" {
85 Expect(ig.FailureSkip).To(Equal([]int{2}))
86 }
87 Expect(ig.RegisteredHelpers).To(ContainElement("(*Assertion).ShouldNot"))
88 }
89 },
90 Entry(
91 "when the matcher matches and a positive assertion is being made",
92 MATCH, Extras(), OptionalDescription(),
93 SHOULD_MATCH, "", IT_PASSES,
94 ),
95 Entry(
96 "when the matcher matches and a negative assertion is being made",
97 MATCH, Extras(), OptionalDescription(),
98 SHOULD_NOT_MATCH, "negative: match", IT_FAILS,
99 ),
100 Entry(
101 "when the matcher does not match and a positive assertion is being made",
102 NO_MATCH, Extras(), OptionalDescription(),
103 SHOULD_MATCH, "positive: no match", IT_FAILS,
104 ),
105 Entry(
106 "when the matcher does not match and a negative assertion is being made",
107 NO_MATCH, Extras(), OptionalDescription(),
108 SHOULD_NOT_MATCH, "", IT_PASSES,
109 ),
110 Entry(
111 "when the matcher returns an error and a positive assertion is being made",
112 ERR_MATCH, Extras(), OptionalDescription(),
113 SHOULD_MATCH, "spec matcher error", IT_FAILS,
114 ),
115 Entry(
116 "when the matcher returns an error and a negative assertion is being made",
117 ERR_MATCH, Extras(), OptionalDescription(),
118 SHOULD_NOT_MATCH, "spec matcher error", IT_FAILS,
119 ),
120 Entry(
121 "when a failure occurs and there is a single optional description",
122 NO_MATCH, Extras(), OptionalDescription("a description"),
123 SHOULD_MATCH, "a description\npositive: no match", IT_FAILS,
124 ),
125 Entry(
126 "when a failure occurs and there are multiple optional descriptions",
127 NO_MATCH, Extras(), OptionalDescription("a description of [%d]", 3),
128 SHOULD_MATCH, "a description of [3]\npositive: no match", IT_FAILS,
129 ),
130 Entry(
131 "when a failure occurs and the optional description is a function",
132 NO_MATCH, Extras(), OptionalDescription(func() string { return "a description" }),
133 SHOULD_MATCH, "a description\npositive: no match", IT_FAILS,
134 ),
135 Entry(
136 "when the matcher matches and zero-valued extra parameters are included, it passes",
137 MATCH, Extras(0, "", struct{ Foo string }{}, nil), OptionalDescription(),
138 SHOULD_MATCH, "", IT_PASSES,
139 ),
140 Entry(
141 "when the matcher matches but a non-zero-valued extra parameter is included, it fails",
142 MATCH, Extras(1, "bam", struct{ Foo string }{Foo: "foo"}, nil), OptionalDescription(),
143 SHOULD_MATCH, "Unexpected non-nil/non-zero extra argument at index 1:\n\t<int>: 1", IT_FAILS,
144 ),
145 )
146 })
0 package internal
1
2 import (
3 "errors"
4 "fmt"
5 "reflect"
6 "runtime"
7 "time"
8
9 "github.com/onsi/gomega/types"
10 )
11
12 type AsyncAssertionType uint
13
14 const (
15 AsyncAssertionTypeEventually AsyncAssertionType = iota
16 AsyncAssertionTypeConsistently
17 )
18
19 type AsyncAssertion struct {
20 asyncType AsyncAssertionType
21
22 actualIsFunc bool
23 actualValue interface{}
24 actualFunc func() ([]reflect.Value, error)
25
26 timeoutInterval time.Duration
27 pollingInterval time.Duration
28 offset int
29 g *Gomega
30 }
31
32 func NewAsyncAssertion(asyncType AsyncAssertionType, actualInput interface{}, g *Gomega, timeoutInterval time.Duration, pollingInterval time.Duration, offset int) *AsyncAssertion {
33 out := &AsyncAssertion{
34 asyncType: asyncType,
35 timeoutInterval: timeoutInterval,
36 pollingInterval: pollingInterval,
37 offset: offset,
38 g: g,
39 }
40
41 switch actualType := reflect.TypeOf(actualInput); {
42 case actualType.Kind() != reflect.Func:
43 out.actualValue = actualInput
44 case actualType.NumIn() == 0 && actualType.NumOut() > 0:
45 out.actualIsFunc = true
46 out.actualFunc = func() ([]reflect.Value, error) {
47 return reflect.ValueOf(actualInput).Call([]reflect.Value{}), nil
48 }
49 case actualType.NumIn() == 1 && actualType.In(0).Implements(reflect.TypeOf((*types.Gomega)(nil)).Elem()):
50 out.actualIsFunc = true
51 out.actualFunc = func() (values []reflect.Value, err error) {
52 var assertionFailure error
53 assertionCapturingGomega := NewGomega(g.DurationBundle).ConfigureWithFailHandler(func(message string, callerSkip ...int) {
54 skip := 0
55 if len(callerSkip) > 0 {
56 skip = callerSkip[0]
57 }
58 _, file, line, _ := runtime.Caller(skip + 1)
59 assertionFailure = fmt.Errorf("Assertion in callback at %s:%d failed:\n%s", file, line, message)
60 panic("stop execution")
61 })
62
63 defer func() {
64 if actualType.NumOut() == 0 {
65 if assertionFailure == nil {
66 values = []reflect.Value{reflect.Zero(reflect.TypeOf((*error)(nil)).Elem())}
67 } else {
68 values = []reflect.Value{reflect.ValueOf(assertionFailure)}
69 }
70 } else {
71 err = assertionFailure
72 }
73 if e := recover(); e != nil && assertionFailure == nil {
74 panic(e)
75 }
76 }()
77
78 values = reflect.ValueOf(actualInput).Call([]reflect.Value{reflect.ValueOf(assertionCapturingGomega)})
79 return
80 }
81 default:
82 msg := fmt.Sprintf("The function passed to Gomega's async assertions should either take no arguments and return values, or take a single Gomega interface that it can use to make assertions within the body of the function. When taking a Gomega interface the function can optionally return values or return nothing. The function you passed takes %d arguments and returns %d values.", actualType.NumIn(), actualType.NumOut())
83 g.Fail(msg, offset+4)
84 }
85
86 return out
87 }
88
89 func (assertion *AsyncAssertion) Should(matcher types.GomegaMatcher, optionalDescription ...interface{}) bool {
90 assertion.g.THelper()
91 return assertion.match(matcher, true, optionalDescription...)
92 }
93
94 func (assertion *AsyncAssertion) ShouldNot(matcher types.GomegaMatcher, optionalDescription ...interface{}) bool {
95 assertion.g.THelper()
96 return assertion.match(matcher, false, optionalDescription...)
97 }
98
99 func (assertion *AsyncAssertion) buildDescription(optionalDescription ...interface{}) string {
100 switch len(optionalDescription) {
101 case 0:
102 return ""
103 case 1:
104 if describe, ok := optionalDescription[0].(func() string); ok {
105 return describe() + "\n"
106 }
107 }
108 return fmt.Sprintf(optionalDescription[0].(string), optionalDescription[1:]...) + "\n"
109 }
110
111 func (assertion *AsyncAssertion) pollActual() (interface{}, error) {
112 if !assertion.actualIsFunc {
113 return assertion.actualValue, nil
114 }
115
116 values, err := assertion.actualFunc()
117 if err != nil {
118 return nil, err
119 }
120 extras := []interface{}{}
121 for _, value := range values[1:] {
122 extras = append(extras, value.Interface())
123 }
124 success, message := vetExtras(extras)
125 if !success {
126 return nil, errors.New(message)
127 }
128
129 return values[0].Interface(), nil
130 }
131
132 func (assertion *AsyncAssertion) matcherMayChange(matcher types.GomegaMatcher, value interface{}) bool {
133 if assertion.actualIsFunc {
134 return true
135 }
136 return types.MatchMayChangeInTheFuture(matcher, value)
137 }
138
139 func (assertion *AsyncAssertion) match(matcher types.GomegaMatcher, desiredMatch bool, optionalDescription ...interface{}) bool {
140 timer := time.Now()
141 timeout := time.After(assertion.timeoutInterval)
142
143 var matches bool
144 var err error
145 mayChange := true
146 value, err := assertion.pollActual()
147 if err == nil {
148 mayChange = assertion.matcherMayChange(matcher, value)
149 matches, err = matcher.Match(value)
150 }
151
152 assertion.g.THelper()
153
154 fail := func(preamble string) {
155 errMsg := ""
156 message := ""
157 if err != nil {
158 errMsg = "Error: " + err.Error()
159 } else {
160 if desiredMatch {
161 message = matcher.FailureMessage(value)
162 } else {
163 message = matcher.NegatedFailureMessage(value)
164 }
165 }
166 assertion.g.THelper()
167 description := assertion.buildDescription(optionalDescription...)
168 assertion.g.Fail(fmt.Sprintf("%s after %.3fs.\n%s%s%s", preamble, time.Since(timer).Seconds(), description, message, errMsg), 3+assertion.offset)
169 }
170
171 if assertion.asyncType == AsyncAssertionTypeEventually {
172 for {
173 if err == nil && matches == desiredMatch {
174 return true
175 }
176
177 if !mayChange {
178 fail("No future change is possible. Bailing out early")
179 return false
180 }
181
182 select {
183 case <-time.After(assertion.pollingInterval):
184 value, err = assertion.pollActual()
185 if err == nil {
186 mayChange = assertion.matcherMayChange(matcher, value)
187 matches, err = matcher.Match(value)
188 }
189 case <-timeout:
190 fail("Timed out")
191 return false
192 }
193 }
194 } else if assertion.asyncType == AsyncAssertionTypeConsistently {
195 for {
196 if !(err == nil && matches == desiredMatch) {
197 fail("Failed")
198 return false
199 }
200
201 if !mayChange {
202 return true
203 }
204
205 select {
206 case <-time.After(assertion.pollingInterval):
207 value, err = assertion.pollActual()
208 if err == nil {
209 mayChange = assertion.matcherMayChange(matcher, value)
210 matches, err = matcher.Match(value)
211 }
212 case <-timeout:
213 return true
214 }
215 }
216 }
217
218 return false
219 }
0 package internal_test
1
2 import (
3 "errors"
4 "runtime"
5 "time"
6
7 . "github.com/onsi/ginkgo"
8 . "github.com/onsi/gomega"
9 )
10
11 var _ = Describe("Asynchronous Assertions", func() {
12 var ig *InstrumentedGomega
13 BeforeEach(func() {
14 ig = NewInstrumentedGomega()
15 })
16
17 Describe("Basic Eventually support", func() {
18 Context("the positive case", func() {
19 It("polls the function and matcher until a match occurs", func() {
20 counter := 0
21 ig.G.Eventually(func() string {
22 counter++
23 if counter > 5 {
24 return MATCH
25 }
26 return NO_MATCH
27 }).Should(SpecMatch())
28 Ω(counter).Should(Equal(6))
29 Ω(ig.FailureMessage).Should(BeZero())
30 })
31
32 It("continues polling even if the matcher errors", func() {
33 counter := 0
34 ig.G.Eventually(func() string {
35 counter++
36 if counter > 5 {
37 return MATCH
38 }
39 return ERR_MATCH
40 }).Should(SpecMatch())
41 Ω(counter).Should(Equal(6))
42 Ω(ig.FailureMessage).Should(BeZero())
43 })
44
45 It("times out eventually if the assertion doesn't match in time", func() {
46 counter := 0
47 ig.G.Eventually(func() string {
48 counter++
49 if counter > 100 {
50 return MATCH
51 }
52 return NO_MATCH
53 }, "200ms", "20ms").Should(SpecMatch())
54 Ω(counter).Should(BeNumerically(">", 2))
55 Ω(counter).Should(BeNumerically("<", 20))
56 Ω(ig.FailureMessage).Should(ContainSubstring("Timed out after"))
57 Ω(ig.FailureMessage).Should(ContainSubstring("positive: no match"))
58 Ω(ig.FailureSkip).Should(Equal([]int{3}))
59 })
60 })
61
62 Context("the negative case", func() {
63 It("polls the function and matcher until a match does not occur", func() {
64 counter := 0
65 ig.G.Eventually(func() string {
66 counter++
67 if counter > 5 {
68 return NO_MATCH
69 }
70 return MATCH
71 }).ShouldNot(SpecMatch())
72 Ω(counter).Should(Equal(6))
73 Ω(ig.FailureMessage).Should(BeZero())
74 })
75
76 It("continues polling when the matcher errors - an error does not count as a successful non-match", func() {
77 counter := 0
78 ig.G.Eventually(func() string {
79 counter++
80 if counter > 5 {
81 return NO_MATCH
82 }
83 return ERR_MATCH
84 }).ShouldNot(SpecMatch())
85 Ω(counter).Should(Equal(6))
86 Ω(ig.FailureMessage).Should(BeZero())
87 })
88
89 It("times out eventually if the assertion doesn't match in time", func() {
90 counter := 0
91 ig.G.Eventually(func() string {
92 counter++
93 if counter > 100 {
94 return NO_MATCH
95 }
96 return MATCH
97 }, "200ms", "20ms").ShouldNot(SpecMatch())
98 Ω(counter).Should(BeNumerically(">", 2))
99 Ω(counter).Should(BeNumerically("<", 20))
100 Ω(ig.FailureMessage).Should(ContainSubstring("Timed out after"))
101 Ω(ig.FailureMessage).Should(ContainSubstring("negative: match"))
102 Ω(ig.FailureSkip).Should(Equal([]int{3}))
103 })
104 })
105
106 Context("when a failure occurs", func() {
107 It("registers the appropriate helper functions", func() {
108 ig.G.Eventually(NO_MATCH, "50ms", "10ms").Should(SpecMatch())
109 Ω(ig.FailureMessage).Should(ContainSubstring("Timed out after"))
110 Ω(ig.FailureMessage).Should(ContainSubstring("positive: no match"))
111 Ω(ig.FailureSkip).Should(Equal([]int{3}))
112 Ω(ig.RegisteredHelpers).Should(ContainElement("(*AsyncAssertion).Should"))
113 Ω(ig.RegisteredHelpers).Should(ContainElement("(*AsyncAssertion).match"))
114 })
115
116 It("renders the matcher's error if an error occured", func() {
117 ig.G.Eventually(ERR_MATCH, "50ms", "10ms").Should(SpecMatch())
118 Ω(ig.FailureMessage).Should(ContainSubstring("Timed out after"))
119 Ω(ig.FailureMessage).Should(ContainSubstring("Error: spec matcher error"))
120 })
121
122 It("renders the optional description", func() {
123 ig.G.Eventually(NO_MATCH, "50ms", "10ms").Should(SpecMatch(), "boop")
124 Ω(ig.FailureMessage).Should(ContainSubstring("boop"))
125 })
126
127 It("formats and renders the optional description when there are multiple arguments", func() {
128 ig.G.Eventually(NO_MATCH, "50ms", "10ms").Should(SpecMatch(), "boop %d", 17)
129 Ω(ig.FailureMessage).Should(ContainSubstring("boop 17"))
130 })
131
132 It("calls the optional description if it is a function", func() {
133 ig.G.Eventually(NO_MATCH, "50ms", "10ms").Should(SpecMatch(), func() string { return "boop" })
134 Ω(ig.FailureMessage).Should(ContainSubstring("boop"))
135 })
136 })
137 })
138
139 Describe("Basic Consistently support", func() {
140 Context("the positive case", func() {
141 It("polls the function and matcher ensuring a match occurs consistently", func() {
142 counter := 0
143 ig.G.Consistently(func() string {
144 counter++
145 return MATCH
146 }, "50ms", "10ms").Should(SpecMatch())
147 Ω(counter).Should(BeNumerically(">", 1))
148 Ω(counter).Should(BeNumerically("<", 7))
149 Ω(ig.FailureMessage).Should(BeZero())
150 })
151
152 It("fails if the matcher ever errors", func() {
153 counter := 0
154 ig.G.Consistently(func() string {
155 counter++
156 if counter == 3 {
157 return ERR_MATCH
158 }
159 return MATCH
160 }, "50ms", "10ms").Should(SpecMatch())
161 Ω(counter).Should(Equal(3))
162 Ω(ig.FailureMessage).Should(ContainSubstring("Failed after"))
163 Ω(ig.FailureMessage).Should(ContainSubstring("Error: spec matcher error"))
164 })
165
166 It("fails if the matcher doesn't match at any point", func() {
167 counter := 0
168 ig.G.Consistently(func() string {
169 counter++
170 if counter == 3 {
171 return NO_MATCH
172 }
173 return MATCH
174 }, "50ms", "10ms").Should(SpecMatch())
175 Ω(counter).Should(Equal(3))
176 Ω(ig.FailureMessage).Should(ContainSubstring("Failed after"))
177 Ω(ig.FailureMessage).Should(ContainSubstring("positive: no match"))
178 })
179 })
180
181 Context("the negative case", func() {
182 It("polls the function and matcher ensuring a match never occurs", func() {
183 counter := 0
184 ig.G.Consistently(func() string {
185 counter++
186 return NO_MATCH
187 }, "50ms", "10ms").ShouldNot(SpecMatch())
188 Ω(counter).Should(BeNumerically(">", 1))
189 Ω(counter).Should(BeNumerically("<", 7))
190 Ω(ig.FailureMessage).Should(BeZero())
191 })
192
193 It("fails if the matcher ever errors", func() {
194 counter := 0
195 ig.G.Consistently(func() string {
196 counter++
197 if counter == 3 {
198 return ERR_MATCH
199 }
200 return NO_MATCH
201 }, "50ms", "10ms").ShouldNot(SpecMatch())
202 Ω(counter).Should(Equal(3))
203 Ω(ig.FailureMessage).Should(ContainSubstring("Failed after"))
204 Ω(ig.FailureMessage).Should(ContainSubstring("Error: spec matcher error"))
205 })
206
207 It("fails if the matcher matches at any point", func() {
208 counter := 0
209 ig.G.Consistently(func() string {
210 counter++
211 if counter == 3 {
212 return MATCH
213 }
214 return NO_MATCH
215 }, "50ms", "10ms").ShouldNot(SpecMatch())
216 Ω(counter).Should(Equal(3))
217 Ω(ig.FailureMessage).Should(ContainSubstring("Failed after"))
218 Ω(ig.FailureMessage).Should(ContainSubstring("negative: match"))
219 })
220 })
221
222 Context("when a failure occurs", func() {
223 It("registers the appropriate helper functions", func() {
224 ig.G.Consistently(NO_MATCH).Should(SpecMatch())
225 Ω(ig.FailureMessage).Should(ContainSubstring("Failed after"))
226 Ω(ig.FailureMessage).Should(ContainSubstring("positive: no match"))
227 Ω(ig.FailureSkip).Should(Equal([]int{3}))
228 Ω(ig.RegisteredHelpers).Should(ContainElement("(*AsyncAssertion).Should"))
229 Ω(ig.RegisteredHelpers).Should(ContainElement("(*AsyncAssertion).match"))
230 })
231
232 It("renders the matcher's error if an error occured", func() {
233 ig.G.Consistently(ERR_MATCH).Should(SpecMatch())
234 Ω(ig.FailureMessage).Should(ContainSubstring("Failed after"))
235 Ω(ig.FailureMessage).Should(ContainSubstring("Error: spec matcher error"))
236 })
237
238 It("renders the optional description", func() {
239 ig.G.Consistently(NO_MATCH).Should(SpecMatch(), "boop")
240 Ω(ig.FailureMessage).Should(ContainSubstring("boop"))
241 })
242
243 It("formats and renders the optional description when there are multiple arguments", func() {
244 ig.G.Consistently(NO_MATCH).Should(SpecMatch(), "boop %d", 17)
245 Ω(ig.FailureMessage).Should(ContainSubstring("boop 17"))
246 })
247
248 It("calls the optional description if it is a function", func() {
249 ig.G.Consistently(NO_MATCH).Should(SpecMatch(), func() string { return "boop" })
250 Ω(ig.FailureMessage).Should(ContainSubstring("boop"))
251 })
252 })
253 })
254
255 Describe("the passed-in actual", func() {
256 type Foo struct{ Bar string }
257
258 Context("when passed a value", func() {
259 It("(eventually) continuously checks on the value until a match occurs", func() {
260 c := make(chan bool)
261 go func() {
262 time.Sleep(100 * time.Millisecond)
263 close(c)
264 }()
265 ig.G.Eventually(c, "1s", "10ms").Should(BeClosed())
266 Ω(ig.FailureMessage).Should(BeZero())
267 })
268
269 It("(consistently) continuously checks on the value ensuring a match always occurs", func() {
270 c := make(chan bool)
271 close(c)
272 ig.G.Consistently(c, "50ms", "10ms").Should(BeClosed())
273 Ω(ig.FailureMessage).Should(BeZero())
274 })
275 })
276
277 Context("when passed a function that takes no arguments and returns one value", func() {
278 It("(eventually) polls the function until the returned value satisfies the matcher", func() {
279 counter := 0
280 ig.G.Eventually(func() int {
281 counter += 1
282 return counter
283 }, "1s", "10ms").Should(BeNumerically(">", 5))
284 Ω(ig.FailureMessage).Should(BeZero())
285 })
286
287 It("(consistently) polls the function ensuring the returned value satisfies the matcher", func() {
288 counter := 0
289 ig.G.Consistently(func() int {
290 counter += 1
291 return counter
292 }, "50ms", "10ms").Should(BeNumerically("<", 20))
293 Ω(counter).Should(BeNumerically(">", 2))
294 Ω(ig.FailureMessage).Should(BeZero())
295 })
296
297 It("works when the function returns nil", func() {
298 counter := 0
299 ig.G.Eventually(func() error {
300 counter += 1
301 if counter > 5 {
302 return nil
303 }
304 return errors.New("oops")
305 }, "1s", "10ms").Should(BeNil())
306 Ω(ig.FailureMessage).Should(BeZero())
307 })
308 })
309
310 Context("when passed a function that takes no arguments and returns mutliple values", func() {
311 Context("with Eventually", func() {
312 It("polls the function until the first returned value satisfies the matcher _and_ all additional values are zero", func() {
313 counter, s, f, err := 0, "hi", Foo{Bar: "hi"}, errors.New("hi")
314 ig.G.Eventually(func() (int, string, Foo, error) {
315 switch counter += 1; counter {
316 case 2:
317 s = ""
318 case 3:
319 f = Foo{}
320 case 4:
321 err = nil
322 }
323 return counter, s, f, err
324 }, "1s", "10ms").Should(BeNumerically("<", 100))
325 Ω(ig.FailureMessage).Should(BeZero())
326 Ω(counter).Should(Equal(4))
327 })
328
329 It("reports on the non-zero value if it times out", func() {
330 ig.G.Eventually(func() (int, string, Foo, error) {
331 return 1, "", Foo{Bar: "hi"}, nil
332 }, "30ms", "10ms").Should(BeNumerically("<", 100))
333 Ω(ig.FailureMessage).Should(ContainSubstring("Error: Unexpected non-nil/non-zero extra argument at index 2:"))
334 Ω(ig.FailureMessage).Should(ContainSubstring(`Foo{Bar:"hi"}`))
335 })
336
337 Context("when making a ShouldNot assertion", func() {
338 It("doesn't succeed until the matcher is (not) satisfied with the first returned value _and_ all additional values are zero", func() {
339 counter, s, f, err := 0, "hi", Foo{Bar: "hi"}, errors.New("hi")
340 ig.G.Eventually(func() (int, string, Foo, error) {
341 switch counter += 1; counter {
342 case 2:
343 s = ""
344 case 3:
345 f = Foo{}
346 case 4:
347 err = nil
348 }
349 return counter, s, f, err
350 }, "1s", "10ms").ShouldNot(BeNumerically("<", 0))
351 Ω(ig.FailureMessage).Should(BeZero())
352 Ω(counter).Should(Equal(4))
353 })
354 })
355 })
356
357 Context("with Consistently", func() {
358 It("polls the function and succeeds if all the values are zero and the matcher is consistently satisfied", func() {
359 var err error
360 counter, s, f := 0, "", Foo{}
361 ig.G.Consistently(func() (int, string, Foo, error) {
362 counter += 1
363 return counter, s, f, err
364 }, "50ms", "10ms").Should(BeNumerically("<", 100))
365 Ω(ig.FailureMessage).Should(BeZero())
366 Ω(counter).Should(BeNumerically(">", 2))
367 })
368
369 It("polls the function and fails any of the values are non-zero", func() {
370 var err error
371 counter, s, f := 0, "", Foo{}
372 ig.G.Consistently(func() (int, string, Foo, error) {
373 counter += 1
374 if counter == 3 {
375 f = Foo{Bar: "welp"}
376 }
377 return counter, s, f, err
378 }, "50ms", "10ms").Should(BeNumerically("<", 100))
379 Ω(ig.FailureMessage).Should(ContainSubstring("Error: Unexpected non-nil/non-zero extra argument at index 2:"))
380 Ω(ig.FailureMessage).Should(ContainSubstring(`Foo{Bar:"welp"}`))
381 Ω(counter).Should(Equal(3))
382 })
383
384 Context("when making a ShouldNot assertion", func() {
385 It("succeeds if all additional values are zero", func() {
386 var err error
387 counter, s, f := 0, "", Foo{}
388 ig.G.Consistently(func() (int, string, Foo, error) {
389 counter += 1
390 return counter, s, f, err
391 }, "50ms", "10ms").ShouldNot(BeNumerically(">", 100))
392 Ω(ig.FailureMessage).Should(BeZero())
393 Ω(counter).Should(BeNumerically(">", 2))
394 })
395
396 It("fails if any additional values are ever non-zero", func() {
397 var err error
398 counter, s, f := 0, "", Foo{}
399 ig.G.Consistently(func() (int, string, Foo, error) {
400 counter += 1
401 if counter == 3 {
402 s = "welp"
403 }
404 return counter, s, f, err
405 }, "50ms", "10ms").ShouldNot(BeNumerically(">", 100))
406 Ω(ig.FailureMessage).Should(ContainSubstring("Error: Unexpected non-nil/non-zero extra argument at index 1:"))
407 Ω(ig.FailureMessage).Should(ContainSubstring(`<string>: "welp"`))
408 Ω(counter).Should(Equal(3))
409 })
410 })
411 })
412 })
413
414 Context("when passed a function that takes a Gomega argument and returns values", func() {
415 Context("with Eventually", func() {
416 It("passes in a Gomega and passes if the matcher matches, all extra values are zero, and there are no failed assertions", func() {
417 counter, s, f, err := 0, "hi", Foo{Bar: "hi"}, errors.New("hi")
418 ig.G.Eventually(func(g Gomega) (int, string, Foo, error) {
419 switch counter += 1; counter {
420 case 2:
421 s = ""
422 case 3:
423 f = Foo{}
424 case 4:
425 err = nil
426 }
427 if counter == 5 {
428 g.Expect(true).To(BeTrue())
429 } else {
430 g.Expect(false).To(BeTrue())
431 panic("boom") //never see since the expectation stops execution
432 }
433 return counter, s, f, err
434 }, "1s", "10ms").Should(BeNumerically("<", 100))
435 Ω(ig.FailureMessage).Should(BeZero())
436 Ω(counter).Should(Equal(5))
437 })
438
439 It("times out if assertions in the function never succeed and reports on the error", func() {
440 _, file, line, _ := runtime.Caller(0)
441 ig.G.Eventually(func(g Gomega) int {
442 g.Expect(false).To(BeTrue())
443 return 10
444 }, "30ms", "10ms").Should(Equal(10))
445 Ω(ig.FailureMessage).Should(ContainSubstring("Error: Assertion in callback at %s:%d failed:", file, line+2))
446 Ω(ig.FailureMessage).Should(ContainSubstring("Expected\n <bool>: false\nto be true"))
447 })
448
449 It("forwards panics", func() {
450 Ω(func() {
451 ig.G.Eventually(func(g Gomega) int {
452 g.Expect(true).To(BeTrue())
453 panic("boom")
454 }, "30ms", "10ms").Should(Equal(10))
455 }).Should(PanicWith("boom"))
456 Ω(ig.FailureMessage).Should(BeEmpty())
457 })
458
459 Context("when making a ShouldNot assertion", func() {
460 It("doesn't succeed until all extra values are zero, there are no failed assertions, and the matcher is (not) satisfied", func() {
461 counter, s, f, err := 0, "hi", Foo{Bar: "hi"}, errors.New("hi")
462 ig.G.Eventually(func(g Gomega) (int, string, Foo, error) {
463 switch counter += 1; counter {
464 case 2:
465 s = ""
466 case 3:
467 f = Foo{}
468 case 4:
469 err = nil
470 }
471 if counter == 5 {
472 g.Expect(true).To(BeTrue())
473 } else {
474 g.Expect(false).To(BeTrue())
475 panic("boom") //never see since the expectation stops execution
476 }
477 return counter, s, f, err
478 }, "1s", "10ms").ShouldNot(BeNumerically("<", 0))
479 Ω(ig.FailureMessage).Should(BeZero())
480 Ω(counter).Should(Equal(5))
481 })
482 })
483
484 It("fails if an assertion is never satisfied", func() {
485 _, file, line, _ := runtime.Caller(0)
486 ig.G.Eventually(func(g Gomega) int {
487 g.Expect(false).To(BeTrue())
488 return 9
489 }, "30ms", "10ms").ShouldNot(Equal(10))
490 Ω(ig.FailureMessage).Should(ContainSubstring("Error: Assertion in callback at %s:%d failed:", file, line+2))
491 Ω(ig.FailureMessage).Should(ContainSubstring("Expected\n <bool>: false\nto be true"))
492 })
493 })
494
495 Context("with Consistently", func() {
496 It("passes in a Gomega and passes if the matcher matches, all extra values are zero, and there are no failed assertions", func() {
497 var err error
498 counter, s, f := 0, "", Foo{}
499 ig.G.Consistently(func(g Gomega) (int, string, Foo, error) {
500 counter += 1
501 g.Expect(true).To(BeTrue())
502 return counter, s, f, err
503 }, "50ms", "10ms").Should(BeNumerically("<", 100))
504 Ω(ig.FailureMessage).Should(BeZero())
505 Ω(counter).Should(BeNumerically(">", 2))
506 })
507
508 It("fails if the passed-in gomega ever hits a failure", func() {
509 var err error
510 counter, s, f := 0, "", Foo{}
511 _, file, line, _ := runtime.Caller(0)
512 ig.G.Consistently(func(g Gomega) (int, string, Foo, error) {
513 counter += 1
514 g.Expect(true).To(BeTrue())
515 if counter == 3 {
516 g.Expect(false).To(BeTrue())
517 panic("boom") //never see this
518 }
519 return counter, s, f, err
520 }, "50ms", "10ms").Should(BeNumerically("<", 100))
521 Ω(ig.FailureMessage).Should(ContainSubstring("Error: Assertion in callback at %s:%d failed:", file, line+5))
522 Ω(ig.FailureMessage).Should(ContainSubstring("Expected\n <bool>: false\nto be true"))
523 Ω(counter).Should(Equal(3))
524 })
525
526 It("forwards panics", func() {
527 Ω(func() {
528 ig.G.Consistently(func(g Gomega) int {
529 g.Expect(true).To(BeTrue())
530 panic("boom")
531 }, "50ms", "10ms").Should(Equal(10))
532 }).Should(PanicWith("boom"))
533 Ω(ig.FailureMessage).Should(BeEmpty())
534 })
535
536 Context("when making a ShouldNot assertion", func() {
537 It("succeeds if any interior assertions always pass", func() {
538 ig.G.Consistently(func(g Gomega) int {
539 g.Expect(true).To(BeTrue())
540 return 9
541 }, "50ms", "10ms").ShouldNot(Equal(10))
542 Ω(ig.FailureMessage).Should(BeEmpty())
543 })
544
545 It("fails if any interior assertions ever fail", func() {
546 counter := 0
547 _, file, line, _ := runtime.Caller(0)
548 ig.G.Consistently(func(g Gomega) int {
549 g.Expect(true).To(BeTrue())
550 counter += 1
551 if counter == 3 {
552 g.Expect(false).To(BeTrue())
553 panic("boom") //never see this
554 }
555 return 9
556 }, "50ms", "10ms").ShouldNot(Equal(10))
557 Ω(ig.FailureMessage).Should(ContainSubstring("Error: Assertion in callback at %s:%d failed:", file, line+5))
558 Ω(ig.FailureMessage).Should(ContainSubstring("Expected\n <bool>: false\nto be true"))
559 })
560 })
561 })
562 })
563
564 Context("when passed a function that takes a Gomega argument and returns nothing", func() {
565 Context("with Eventually", func() {
566 It("returns the first failed assertion as an error and so should Succeed() if the callback ever runs without issue", func() {
567 counter := 0
568 ig.G.Eventually(func(g Gomega) {
569 counter += 1
570 if counter < 5 {
571 g.Expect(false).To(BeTrue())
572 g.Expect("bloop").To(Equal("blarp"))
573 }
574 }, "1s", "10ms").Should(Succeed())
575 Ω(counter).Should(Equal(5))
576 Ω(ig.FailureMessage).Should(BeZero())
577 })
578
579 It("returns the first failed assertion as an error and so should timeout if the callback always fails", func() {
580 counter := 0
581 ig.G.Eventually(func(g Gomega) {
582 counter += 1
583 if counter < 5000 {
584 g.Expect(false).To(BeTrue())
585 g.Expect("bloop").To(Equal("blarp"))
586 }
587 }, "100ms", "10ms").Should(Succeed())
588 Ω(counter).Should(BeNumerically(">", 1))
589 Ω(ig.FailureMessage).Should(ContainSubstring("Expected success, but got an error"))
590 Ω(ig.FailureMessage).Should(ContainSubstring("<bool>: false"))
591 Ω(ig.FailureMessage).Should(ContainSubstring("to be true"))
592 Ω(ig.FailureMessage).ShouldNot(ContainSubstring("bloop"))
593 })
594
595 It("returns the first failed assertion as an error and should satisy ShouldNot(Succeed) eventually", func() {
596 counter := 0
597 ig.G.Eventually(func(g Gomega) {
598 counter += 1
599 if counter > 5 {
600 g.Expect(false).To(BeTrue())
601 g.Expect("bloop").To(Equal("blarp"))
602 }
603 }, "100ms", "10ms").ShouldNot(Succeed())
604 Ω(counter).Should(Equal(6))
605 Ω(ig.FailureMessage).Should(BeZero())
606 })
607
608 It("should fail to ShouldNot(Succeed) eventually if an error never occurs", func() {
609 ig.G.Eventually(func(g Gomega) {
610 g.Expect(true).To(BeTrue())
611 }, "50ms", "10ms").ShouldNot(Succeed())
612 Ω(ig.FailureMessage).Should(ContainSubstring("Timed out after"))
613 Ω(ig.FailureMessage).Should(ContainSubstring("Expected failure, but got no error."))
614 })
615 })
616
617 Context("with Consistently", func() {
618 It("returns the first failed assertion as an error and so should Succeed() if the callback always runs without issue", func() {
619 counter := 0
620 ig.G.Consistently(func(g Gomega) {
621 counter += 1
622 g.Expect(true).To(BeTrue())
623 }, "50ms", "10ms").Should(Succeed())
624 Ω(counter).Should(BeNumerically(">", 2))
625 Ω(ig.FailureMessage).Should(BeZero())
626 })
627
628 It("returns the first failed assertion as an error and so should fail if the callback ever fails", func() {
629 counter := 0
630 ig.G.Consistently(func(g Gomega) {
631 counter += 1
632 g.Expect(true).To(BeTrue())
633 if counter == 3 {
634 g.Expect(false).To(BeTrue())
635 g.Expect("bloop").To(Equal("blarp"))
636 }
637 }, "50ms", "10ms").Should(Succeed())
638 Ω(ig.FailureMessage).Should(ContainSubstring("Expected success, but got an error"))
639 Ω(ig.FailureMessage).Should(ContainSubstring("<bool>: false"))
640 Ω(ig.FailureMessage).Should(ContainSubstring("to be true"))
641 Ω(ig.FailureMessage).ShouldNot(ContainSubstring("bloop"))
642 Ω(counter).Should(Equal(3))
643 })
644
645 It("returns the first failed assertion as an error and should satisy ShouldNot(Succeed) consistently if an error always occur", func() {
646 counter := 0
647 ig.G.Consistently(func(g Gomega) {
648 counter += 1
649 g.Expect(true).To(BeFalse())
650 }, "50ms", "10ms").ShouldNot(Succeed())
651 Ω(counter).Should(BeNumerically(">", 2))
652 Ω(ig.FailureMessage).Should(BeZero())
653 })
654
655 It("should fail to satisfy ShouldNot(Succeed) consistently if an error ever does not occur", func() {
656 counter := 0
657 ig.G.Consistently(func(g Gomega) {
658 counter += 1
659 if counter == 3 {
660 g.Expect(true).To(BeTrue())
661 } else {
662 g.Expect(false).To(BeTrue())
663 }
664 }, "50ms", "10ms").ShouldNot(Succeed())
665 Ω(ig.FailureMessage).Should(ContainSubstring("Failed after"))
666 Ω(ig.FailureMessage).Should(ContainSubstring("Expected failure, but got no error."))
667 Ω(counter).Should(Equal(3))
668 })
669 })
670 })
671
672 Describe("when passed an invalid function", func() {
673 It("errors immediately", func() {
674 ig.G.Eventually(func() {})
675 Ω(ig.FailureMessage).Should(Equal("The function passed to Gomega's async assertions should either take no arguments and return values, or take a single Gomega interface that it can use to make assertions within the body of the function. When taking a Gomega interface the function can optionally return values or return nothing. The function you passed takes 0 arguments and returns 0 values."))
676 Ω(ig.FailureSkip).Should(Equal([]int{4}))
677
678 ig = NewInstrumentedGomega()
679 ig.G.Eventually(func(g Gomega, foo string) {})
680 Ω(ig.FailureMessage).Should(Equal("The function passed to Gomega's async assertions should either take no arguments and return values, or take a single Gomega interface that it can use to make assertions within the body of the function. When taking a Gomega interface the function can optionally return values or return nothing. The function you passed takes 2 arguments and returns 0 values."))
681 Ω(ig.FailureSkip).Should(Equal([]int{4}))
682
683 ig = NewInstrumentedGomega()
684 ig.G.Eventually(func(foo string) {})
685 Ω(ig.FailureMessage).Should(Equal("The function passed to Gomega's async assertions should either take no arguments and return values, or take a single Gomega interface that it can use to make assertions within the body of the function. When taking a Gomega interface the function can optionally return values or return nothing. The function you passed takes 1 arguments and returns 0 values."))
686 Ω(ig.FailureSkip).Should(Equal([]int{4}))
687 })
688 })
689 })
690
691 Describe("when using OracleMatchers", func() {
692 It("stops and gives up with an appropriate failure message if the OracleMatcher says things can't change", func() {
693 c := make(chan bool)
694 close(c)
695
696 t := time.Now()
697 ig.G.Eventually(c, "100ms", "10ms").Should(Receive(), "Receive is an OracleMatcher that gives up if the channel is closed")
698 Ω(time.Since(t)).Should(BeNumerically("<", 90*time.Millisecond))
699 Ω(ig.FailureMessage).Should(ContainSubstring("No future change is possible."))
700 Ω(ig.FailureMessage).Should(ContainSubstring("The channel is closed."))
701 })
702
703 It("never gives up if actual is a function", func() {
704 c := make(chan bool)
705 close(c)
706
707 t := time.Now()
708 ig.G.Eventually(func() chan bool { return c }, "100ms", "10ms").Should(Receive(), "Receive is an OracleMatcher that gives up if the channel is closed")
709 Ω(time.Since(t)).Should(BeNumerically(">=", 90*time.Millisecond))
710 Ω(ig.FailureMessage).ShouldNot(ContainSubstring("No future change is possible."))
711 Ω(ig.FailureMessage).Should(ContainSubstring("Timed out after"))
712 })
713 })
714 })
+0
-198
internal/asyncassertion/async_assertion.go less more
0 // untested sections: 2
1
2 package asyncassertion
3
4 import (
5 "errors"
6 "fmt"
7 "reflect"
8 "time"
9
10 "github.com/onsi/gomega/internal/oraclematcher"
11 "github.com/onsi/gomega/types"
12 )
13
14 type AsyncAssertionType uint
15
16 const (
17 AsyncAssertionTypeEventually AsyncAssertionType = iota
18 AsyncAssertionTypeConsistently
19 )
20
21 type AsyncAssertion struct {
22 asyncType AsyncAssertionType
23 actualInput interface{}
24 timeoutInterval time.Duration
25 pollingInterval time.Duration
26 failWrapper *types.GomegaFailWrapper
27 offset int
28 }
29
30 func New(asyncType AsyncAssertionType, actualInput interface{}, failWrapper *types.GomegaFailWrapper, timeoutInterval time.Duration, pollingInterval time.Duration, offset int) *AsyncAssertion {
31 actualType := reflect.TypeOf(actualInput)
32 if actualType.Kind() == reflect.Func {
33 if actualType.NumIn() != 0 || actualType.NumOut() == 0 {
34 panic("Expected a function with no arguments and one or more return values.")
35 }
36 }
37
38 return &AsyncAssertion{
39 asyncType: asyncType,
40 actualInput: actualInput,
41 failWrapper: failWrapper,
42 timeoutInterval: timeoutInterval,
43 pollingInterval: pollingInterval,
44 offset: offset,
45 }
46 }
47
48 func (assertion *AsyncAssertion) Should(matcher types.GomegaMatcher, optionalDescription ...interface{}) bool {
49 assertion.failWrapper.TWithHelper.Helper()
50 return assertion.match(matcher, true, optionalDescription...)
51 }
52
53 func (assertion *AsyncAssertion) ShouldNot(matcher types.GomegaMatcher, optionalDescription ...interface{}) bool {
54 assertion.failWrapper.TWithHelper.Helper()
55 return assertion.match(matcher, false, optionalDescription...)
56 }
57
58 func (assertion *AsyncAssertion) buildDescription(optionalDescription ...interface{}) string {
59 switch len(optionalDescription) {
60 case 0:
61 return ""
62 case 1:
63 if describe, ok := optionalDescription[0].(func() string); ok {
64 return describe() + "\n"
65 }
66 }
67 return fmt.Sprintf(optionalDescription[0].(string), optionalDescription[1:]...) + "\n"
68 }
69
70 func (assertion *AsyncAssertion) actualInputIsAFunction() bool {
71 actualType := reflect.TypeOf(assertion.actualInput)
72 return actualType.Kind() == reflect.Func && actualType.NumIn() == 0 && actualType.NumOut() > 0
73 }
74
75 func (assertion *AsyncAssertion) pollActual() (interface{}, error) {
76 if assertion.actualInputIsAFunction() {
77 values := reflect.ValueOf(assertion.actualInput).Call([]reflect.Value{})
78
79 extras := []interface{}{}
80 for _, value := range values[1:] {
81 extras = append(extras, value.Interface())
82 }
83
84 success, message := vetExtras(extras)
85
86 if !success {
87 return nil, errors.New(message)
88 }
89
90 return values[0].Interface(), nil
91 }
92
93 return assertion.actualInput, nil
94 }
95
96 func (assertion *AsyncAssertion) matcherMayChange(matcher types.GomegaMatcher, value interface{}) bool {
97 if assertion.actualInputIsAFunction() {
98 return true
99 }
100
101 return oraclematcher.MatchMayChangeInTheFuture(matcher, value)
102 }
103
104 func (assertion *AsyncAssertion) match(matcher types.GomegaMatcher, desiredMatch bool, optionalDescription ...interface{}) bool {
105 timer := time.Now()
106 timeout := time.After(assertion.timeoutInterval)
107
108 var matches bool
109 var err error
110 mayChange := true
111 value, err := assertion.pollActual()
112 if err == nil {
113 mayChange = assertion.matcherMayChange(matcher, value)
114 matches, err = matcher.Match(value)
115 }
116
117 assertion.failWrapper.TWithHelper.Helper()
118
119 fail := func(preamble string) {
120 errMsg := ""
121 message := ""
122 if err != nil {
123 errMsg = "Error: " + err.Error()
124 } else {
125 if desiredMatch {
126 message = matcher.FailureMessage(value)
127 } else {
128 message = matcher.NegatedFailureMessage(value)
129 }
130 }
131 assertion.failWrapper.TWithHelper.Helper()
132 description := assertion.buildDescription(optionalDescription...)
133 assertion.failWrapper.Fail(fmt.Sprintf("%s after %.3fs.\n%s%s%s", preamble, time.Since(timer).Seconds(), description, message, errMsg), 3+assertion.offset)
134 }
135
136 if assertion.asyncType == AsyncAssertionTypeEventually {
137 for {
138 if err == nil && matches == desiredMatch {
139 return true
140 }
141
142 if !mayChange {
143 fail("No future change is possible. Bailing out early")
144 return false
145 }
146
147 select {
148 case <-time.After(assertion.pollingInterval):
149 value, err = assertion.pollActual()
150 if err == nil {
151 mayChange = assertion.matcherMayChange(matcher, value)
152 matches, err = matcher.Match(value)
153 }
154 case <-timeout:
155 fail("Timed out")
156 return false
157 }
158 }
159 } else if assertion.asyncType == AsyncAssertionTypeConsistently {
160 for {
161 if !(err == nil && matches == desiredMatch) {
162 fail("Failed")
163 return false
164 }
165
166 if !mayChange {
167 return true
168 }
169
170 select {
171 case <-time.After(assertion.pollingInterval):
172 value, err = assertion.pollActual()
173 if err == nil {
174 mayChange = assertion.matcherMayChange(matcher, value)
175 matches, err = matcher.Match(value)
176 }
177 case <-timeout:
178 return true
179 }
180 }
181 }
182
183 return false
184 }
185
186 func vetExtras(extras []interface{}) (bool, string) {
187 for i, extra := range extras {
188 if extra != nil {
189 zeroValue := reflect.Zero(reflect.TypeOf(extra)).Interface()
190 if !reflect.DeepEqual(zeroValue, extra) {
191 message := fmt.Sprintf("Unexpected non-nil/non-zero extra argument at index %d:\n\t<%T>: %#v", i+1, extra, extra)
192 return false, message
193 }
194 }
195 }
196 return true, ""
197 }
+0
-13
internal/asyncassertion/async_assertion_suite_test.go less more
0 package asyncassertion_test
1
2 import (
3 . "github.com/onsi/ginkgo"
4 . "github.com/onsi/gomega"
5
6 "testing"
7 )
8
9 func TestAsyncAssertion(t *testing.T) {
10 RegisterFailHandler(Fail)
11 RunSpecs(t, "AsyncAssertion Suite")
12 }
+0
-389
internal/asyncassertion/async_assertion_test.go less more
0 package asyncassertion_test
1
2 import (
3 "errors"
4 "time"
5
6 "github.com/onsi/gomega/internal/testingtsupport"
7
8 . "github.com/onsi/ginkgo"
9 . "github.com/onsi/gomega"
10 "github.com/onsi/gomega/internal/asyncassertion"
11 "github.com/onsi/gomega/types"
12 )
13
14 var _ = Describe("Async Assertion", func() {
15 var (
16 failureMessage string
17 callerSkip int
18 )
19
20 var fakeFailWrapper = &types.GomegaFailWrapper{
21 Fail: func(message string, skip ...int) {
22 failureMessage = message
23 callerSkip = skip[0]
24 },
25 TWithHelper: testingtsupport.EmptyTWithHelper{},
26 }
27
28 BeforeEach(func() {
29 failureMessage = ""
30 callerSkip = 0
31 })
32
33 Describe("Eventually", func() {
34 Context("the positive case", func() {
35 It("should poll the function and matcher", func() {
36 counter := 0
37 a := asyncassertion.New(asyncassertion.AsyncAssertionTypeEventually, func() int {
38 counter++
39 return counter
40 }, fakeFailWrapper, time.Duration(0.2*float64(time.Second)), time.Duration(0.02*float64(time.Second)), 1)
41
42 a.Should(BeNumerically("==", 5))
43 Expect(failureMessage).Should(BeZero())
44 })
45
46 It("should continue when the matcher errors", func() {
47 counter := 0
48 a := asyncassertion.New(asyncassertion.AsyncAssertionTypeEventually, func() interface{} {
49 counter++
50 if counter == 5 {
51 return "not-a-number" //this should cause the matcher to error
52 }
53 return counter
54 }, fakeFailWrapper, time.Duration(0.2*float64(time.Second)), time.Duration(0.02*float64(time.Second)), 1)
55
56 a.Should(BeNumerically("==", 5), "My description %d", 2)
57
58 Expect(failureMessage).Should(ContainSubstring("Timed out after"))
59 Expect(failureMessage).Should(ContainSubstring("My description 2"))
60 Expect(callerSkip).Should(Equal(4))
61 })
62
63 It("should be able to timeout", func() {
64 counter := 0
65 a := asyncassertion.New(asyncassertion.AsyncAssertionTypeEventually, func() int {
66 counter++
67 return counter
68 }, fakeFailWrapper, time.Duration(0.2*float64(time.Second)), time.Duration(0.02*float64(time.Second)), 1)
69
70 a.Should(BeNumerically(">", 100), "My description %d", 2)
71
72 Expect(counter).Should(BeNumerically(">", 8))
73 Expect(counter).Should(BeNumerically("<=", 10))
74 Expect(failureMessage).Should(ContainSubstring("Timed out after"))
75 Expect(failureMessage).Should(MatchRegexp(`\<int\>: \d`), "Should pass the correct value to the matcher message formatter.")
76 Expect(failureMessage).Should(ContainSubstring("My description 2"))
77 Expect(callerSkip).Should(Equal(4))
78 })
79
80 When("the optional description is a function", func() {
81 It("should append the description to the failure message", func() {
82 counter := 0
83 a := asyncassertion.New(asyncassertion.AsyncAssertionTypeEventually, func() interface{} {
84 counter++
85 if counter == 5 {
86 return "not-a-number" //this should cause the matcher to error
87 }
88 return counter
89 }, fakeFailWrapper, time.Duration(0.2*float64(time.Second)), time.Duration(0.02*float64(time.Second)), 1)
90
91 a.Should(BeNumerically("==", 5), func() string { return "My description" })
92
93 Expect(failureMessage).Should(ContainSubstring("Timed out after"))
94 Expect(failureMessage).Should(ContainSubstring("My description"))
95 Expect(callerSkip).Should(Equal(4))
96 })
97
98 Context("and there is no failure", func() {
99 It("should not evaluate that function", func() {
100 counter := 0
101 a := asyncassertion.New(asyncassertion.AsyncAssertionTypeEventually, func() int {
102 counter++
103 return counter
104 }, fakeFailWrapper, time.Duration(0.2*float64(time.Second)), time.Duration(0.02*float64(time.Second)), 1)
105
106 evaluated := false
107 a.Should(BeNumerically("==", 5), func() string {
108 evaluated = true
109 return "A description"
110 })
111
112 Expect(failureMessage).Should(BeZero())
113 Expect(evaluated).Should(BeFalse())
114 })
115 })
116 })
117 })
118
119 Context("the negative case", func() {
120 It("should poll the function and matcher", func() {
121 counter := 0
122 a := asyncassertion.New(asyncassertion.AsyncAssertionTypeEventually, func() int {
123 counter += 1
124 return counter
125 }, fakeFailWrapper, time.Duration(0.2*float64(time.Second)), time.Duration(0.02*float64(time.Second)), 1)
126
127 a.ShouldNot(BeNumerically("<", 3))
128
129 Expect(counter).Should(Equal(3))
130 Expect(failureMessage).Should(BeZero())
131 })
132
133 It("should timeout when the matcher errors", func() {
134 a := asyncassertion.New(asyncassertion.AsyncAssertionTypeEventually, func() interface{} {
135 return 0 //this should cause the matcher to error
136 }, fakeFailWrapper, time.Duration(0.2*float64(time.Second)), time.Duration(0.02*float64(time.Second)), 1)
137
138 a.ShouldNot(HaveLen(0), "My description %d", 2)
139
140 Expect(failureMessage).Should(ContainSubstring("Timed out after"))
141 Expect(failureMessage).Should(ContainSubstring("Error:"))
142 Expect(failureMessage).Should(ContainSubstring("My description 2"))
143 Expect(callerSkip).Should(Equal(4))
144 })
145
146 It("should be able to timeout", func() {
147 a := asyncassertion.New(asyncassertion.AsyncAssertionTypeEventually, func() int {
148 return 0
149 }, fakeFailWrapper, time.Duration(0.1*float64(time.Second)), time.Duration(0.02*float64(time.Second)), 1)
150
151 a.ShouldNot(Equal(0), "My description %d", 2)
152
153 Expect(failureMessage).Should(ContainSubstring("Timed out after"))
154 Expect(failureMessage).Should(ContainSubstring("<int>: 0"), "Should pass the correct value to the matcher message formatter.")
155 Expect(failureMessage).Should(ContainSubstring("My description 2"))
156 Expect(callerSkip).Should(Equal(4))
157 })
158 })
159
160 Context("with a function that returns multiple values", func() {
161 It("should eventually succeed if the additional arguments are nil", func() {
162 i := 0
163 Eventually(func() (int, error) {
164 i++
165 return i, nil
166 }).Should(Equal(10))
167 })
168
169 It("should eventually timeout if the additional arguments are not nil", func() {
170 i := 0
171 a := asyncassertion.New(asyncassertion.AsyncAssertionTypeEventually, func() (int, error) {
172 i++
173 return i, errors.New("bam")
174 }, fakeFailWrapper, time.Duration(0.2*float64(time.Second)), time.Duration(0.02*float64(time.Second)), 1)
175 a.Should(Equal(2))
176
177 Expect(failureMessage).Should(ContainSubstring("Timed out after"))
178 Expect(failureMessage).Should(ContainSubstring("Error:"))
179 Expect(failureMessage).Should(ContainSubstring("bam"))
180 Expect(callerSkip).Should(Equal(4))
181 })
182 })
183
184 Context("Making an assertion without a registered fail handler", func() {
185 It("should panic", func() {
186 defer func() {
187 e := recover()
188 RegisterFailHandler(Fail)
189 if e == nil {
190 Fail("expected a panic to have occurred")
191 }
192 }()
193
194 RegisterFailHandler(nil)
195 c := make(chan bool, 1)
196 c <- true
197 Eventually(c).Should(Receive())
198 })
199 })
200 })
201
202 Describe("Consistently", func() {
203 Describe("The positive case", func() {
204 When("the matcher consistently passes for the duration", func() {
205 It("should pass", func() {
206 calls := 0
207 a := asyncassertion.New(asyncassertion.AsyncAssertionTypeConsistently, func() string {
208 calls++
209 return "foo"
210 }, fakeFailWrapper, time.Duration(0.2*float64(time.Second)), time.Duration(0.02*float64(time.Second)), 1)
211
212 a.Should(Equal("foo"))
213 Expect(calls).Should(BeNumerically(">", 8))
214 Expect(calls).Should(BeNumerically("<=", 10))
215 Expect(failureMessage).Should(BeZero())
216 })
217 })
218
219 When("the matcher fails at some point", func() {
220 It("should fail", func() {
221 calls := 0
222 a := asyncassertion.New(asyncassertion.AsyncAssertionTypeConsistently, func() interface{} {
223 calls++
224 if calls > 5 {
225 return "bar"
226 }
227 return "foo"
228 }, fakeFailWrapper, time.Duration(0.2*float64(time.Second)), time.Duration(0.02*float64(time.Second)), 1)
229
230 a.Should(Equal("foo"))
231 Expect(failureMessage).Should(ContainSubstring("to equal"))
232 Expect(callerSkip).Should(Equal(4))
233 })
234 })
235
236 When("the matcher errors at some point", func() {
237 It("should fail", func() {
238 calls := 0
239 a := asyncassertion.New(asyncassertion.AsyncAssertionTypeConsistently, func() interface{} {
240 calls++
241 if calls > 5 {
242 return 3
243 }
244 return []int{1, 2, 3}
245 }, fakeFailWrapper, time.Duration(0.2*float64(time.Second)), time.Duration(0.02*float64(time.Second)), 1)
246
247 a.Should(HaveLen(3))
248 Expect(failureMessage).Should(ContainSubstring("HaveLen matcher expects"))
249 Expect(callerSkip).Should(Equal(4))
250 })
251 })
252 })
253
254 Describe("The negative case", func() {
255 When("the matcher consistently passes for the duration", func() {
256 It("should pass", func() {
257 c := make(chan bool)
258 a := asyncassertion.New(asyncassertion.AsyncAssertionTypeConsistently, c, fakeFailWrapper, time.Duration(0.2*float64(time.Second)), time.Duration(0.02*float64(time.Second)), 1)
259
260 a.ShouldNot(Receive())
261 Expect(failureMessage).Should(BeZero())
262 })
263 })
264
265 When("the matcher fails at some point", func() {
266 It("should fail", func() {
267 c := make(chan bool)
268 go func() {
269 time.Sleep(time.Duration(100 * time.Millisecond))
270 c <- true
271 }()
272
273 a := asyncassertion.New(asyncassertion.AsyncAssertionTypeConsistently, c, fakeFailWrapper, time.Duration(0.2*float64(time.Second)), time.Duration(0.02*float64(time.Second)), 1)
274
275 a.ShouldNot(Receive())
276 Expect(failureMessage).Should(ContainSubstring("not to receive anything"))
277 })
278 })
279
280 When("the matcher errors at some point", func() {
281 It("should fail", func() {
282 calls := 0
283 a := asyncassertion.New(asyncassertion.AsyncAssertionTypeConsistently, func() interface{} {
284 calls++
285 return calls
286 }, fakeFailWrapper, time.Duration(0.2*float64(time.Second)), time.Duration(0.02*float64(time.Second)), 1)
287
288 a.ShouldNot(BeNumerically(">", 5))
289 Expect(failureMessage).Should(ContainSubstring("not to be >"))
290 Expect(callerSkip).Should(Equal(4))
291 })
292 })
293 })
294
295 Context("with a function that returns multiple values", func() {
296 It("should consistently succeed if the additional arguments are nil", func() {
297 i := 2
298 Consistently(func() (int, error) {
299 i++
300 return i, nil
301 }).Should(BeNumerically(">=", 2))
302 })
303
304 It("should eventually timeout if the additional arguments are not nil", func() {
305 i := 2
306 a := asyncassertion.New(asyncassertion.AsyncAssertionTypeEventually, func() (int, error) {
307 i++
308 return i, errors.New("bam")
309 }, fakeFailWrapper, time.Duration(0.2*float64(time.Second)), time.Duration(0.02*float64(time.Second)), 1)
310 a.Should(BeNumerically(">=", 2))
311
312 Expect(failureMessage).Should(ContainSubstring("Error:"))
313 Expect(failureMessage).Should(ContainSubstring("bam"))
314 Expect(callerSkip).Should(Equal(4))
315 })
316 })
317
318 Context("Making an assertion without a registered fail handler", func() {
319 It("should panic", func() {
320 defer func() {
321 e := recover()
322 RegisterFailHandler(Fail)
323 if e == nil {
324 Fail("expected a panic to have occurred")
325 }
326 }()
327
328 RegisterFailHandler(nil)
329 c := make(chan bool)
330 Consistently(c).ShouldNot(Receive())
331 })
332 })
333 })
334
335 When("passed a function with the wrong # or arguments & returns", func() {
336 It("should panic", func() {
337 Expect(func() {
338 asyncassertion.New(asyncassertion.AsyncAssertionTypeEventually, func() {}, fakeFailWrapper, 0, 0, 1)
339 }).Should(Panic())
340
341 Expect(func() {
342 asyncassertion.New(asyncassertion.AsyncAssertionTypeEventually, func(a string) int { return 0 }, fakeFailWrapper, 0, 0, 1)
343 }).Should(Panic())
344
345 Expect(func() {
346 asyncassertion.New(asyncassertion.AsyncAssertionTypeEventually, func() int { return 0 }, fakeFailWrapper, 0, 0, 1)
347 }).ShouldNot(Panic())
348
349 Expect(func() {
350 asyncassertion.New(asyncassertion.AsyncAssertionTypeEventually, func() (int, error) { return 0, nil }, fakeFailWrapper, 0, 0, 1)
351 }).ShouldNot(Panic())
352 })
353 })
354
355 Describe("bailing early", func() {
356 When("actual is a value", func() {
357 It("Eventually should bail out and fail early if the matcher says to", func() {
358 c := make(chan bool)
359 close(c)
360
361 t := time.Now()
362 failures := InterceptGomegaFailures(func() {
363 Eventually(c, 0.1).Should(Receive())
364 })
365 Expect(time.Since(t)).Should(BeNumerically("<", 90*time.Millisecond))
366
367 Expect(failures).Should(HaveLen(1))
368 })
369 })
370
371 When("actual is a function", func() {
372 It("should never bail early", func() {
373 c := make(chan bool)
374 close(c)
375
376 t := time.Now()
377 failures := InterceptGomegaFailures(func() {
378 Eventually(func() chan bool {
379 return c
380 }, 0.1).Should(Receive())
381 })
382 Expect(time.Since(t)).Should(BeNumerically(">=", 90*time.Millisecond))
383
384 Expect(failures).Should(HaveLen(1))
385 })
386 })
387 })
388 })
0 package internal_test
1
2 import (
3 "errors"
4 "runtime"
5 "time"
6
7 . "github.com/onsi/ginkgo"
8 . "github.com/onsi/gomega"
9 "github.com/onsi/gomega/internal"
10 )
11
12 func getGlobalDurationBundle() internal.DurationBundle {
13 return Default.(*internal.Gomega).DurationBundle
14 }
15
16 func setGlobalDurationBundle(bundle internal.DurationBundle) {
17 SetDefaultEventuallyTimeout(bundle.EventuallyTimeout)
18 SetDefaultEventuallyPollingInterval(bundle.EventuallyPollingInterval)
19 SetDefaultConsistentlyDuration(bundle.ConsistentlyDuration)
20 SetDefaultConsistentlyPollingInterval(bundle.ConsistentlyPollingInterval)
21 }
22
23 var _ = Describe("Gomega DSL", func() {
24 var globalDurationBundle internal.DurationBundle
25
26 BeforeEach(func() {
27 globalDurationBundle = getGlobalDurationBundle()
28 })
29
30 AfterEach(func() {
31 RegisterFailHandler(Fail)
32 setGlobalDurationBundle(globalDurationBundle)
33 })
34
35 Describe("The Default, global, Gomega", func() {
36 It("exists", func() {
37 Ω(Default).ShouldNot(BeNil())
38 })
39
40 It("is wired up via the global DSL", func() {
41 counter := 0
42 Eventually(func() int {
43 counter += 1
44 return counter
45 }).Should(Equal(5))
46 Ω(counter).Should(Equal(5))
47 })
48 })
49
50 Describe("NewGomega", func() {
51 It("creates and configures a new Gomega, using the global duration bundle", func() {
52 bundle := internal.DurationBundle{
53 EventuallyTimeout: time.Minute,
54 EventuallyPollingInterval: 2 * time.Minute,
55 ConsistentlyDuration: 3 * time.Minute,
56 ConsistentlyPollingInterval: 4 * time.Minute,
57 }
58 setGlobalDurationBundle(bundle)
59
60 var calledWith string
61 g := NewGomega(func(message string, skip ...int) {
62 calledWith = message
63 })
64
65 gAsStruct := g.(*internal.Gomega)
66 Ω(gAsStruct.DurationBundle).Should(Equal(bundle))
67
68 g.Ω(true).Should(BeFalse())
69 Ω(calledWith).Should(Equal("Expected\n <bool>: true\nto be false"))
70 })
71 })
72
73 Describe("NewWithT", func() {
74 It("creates and configure a new Gomega with the passed-in T, using the global duration bundle", func() {
75 bundle := internal.DurationBundle{
76 EventuallyTimeout: time.Minute,
77 EventuallyPollingInterval: 2 * time.Minute,
78 ConsistentlyDuration: 3 * time.Minute,
79 ConsistentlyPollingInterval: 4 * time.Minute,
80 }
81 setGlobalDurationBundle(bundle)
82
83 fakeT := &FakeGomegaTestingT{}
84 g := NewWithT(fakeT)
85
86 Ω(g.DurationBundle).Should(Equal(bundle))
87
88 g.Ω(true).Should(BeFalse())
89 Ω(fakeT.CalledFatalf).Should(Equal("\nExpected\n <bool>: true\nto be false"))
90 Ω(fakeT.CalledHelper).Should(BeTrue())
91 })
92 })
93
94 Describe("RegisterFailHandler", func() {
95 It("overrides the global fail handler", func() {
96 var calledWith string
97 RegisterFailHandler(func(message string, skip ...int) {
98 calledWith = message
99 })
100
101 Ω(true).Should(BeFalse())
102
103 RegisterFailHandler(Fail)
104 Ω(calledWith).Should(Equal("Expected\n <bool>: true\nto be false"))
105 })
106 })
107
108 Describe("RegisterTestingT", func() {
109 It("overrides the global fail handler", func() {
110 fakeT := &FakeGomegaTestingT{}
111 RegisterTestingT(fakeT)
112
113 Ω(true).Should(BeFalse())
114 RegisterFailHandler(Fail)
115 Ω(fakeT.CalledFatalf).Should(Equal("\nExpected\n <bool>: true\nto be false"))
116 Ω(fakeT.CalledHelper).Should(BeTrue())
117 })
118 })
119
120 Describe("InterceptGomegaFailures", func() {
121 Context("when no failures occur", func() {
122 It("returns an empty array", func() {
123 Expect(InterceptGomegaFailures(func() {
124 Expect("hi").To(Equal("hi"))
125 })).To(BeEmpty())
126 })
127 })
128
129 Context("when failures occur", func() {
130 It("does not stop execution and returns all the failures as strings", func() {
131 Expect(InterceptGomegaFailures(func() {
132 Expect("hi").To(Equal("bye"))
133 Expect(3).To(Equal(2))
134 })).To(Equal([]string{
135 "Expected\n <string>: hi\nto equal\n <string>: bye",
136 "Expected\n <int>: 3\nto equal\n <int>: 2",
137 }))
138
139 })
140 })
141 })
142
143 Describe("InterceptGomegaFailure", func() {
144 Context("when no failures occur", func() {
145 It("returns nil", func() {
146 Expect(InterceptGomegaFailure(func() {
147 Expect("hi").To(Equal("hi"))
148 })).To(BeNil())
149 })
150 })
151
152 Context("when failures occur", func() {
153 It("returns the first failure and stops execution", func() {
154 gotThere := false
155 Expect(InterceptGomegaFailure(func() {
156 Expect("hi").To(Equal("bye"))
157 gotThere = true
158 Expect(3).To(Equal(2))
159 })).To(Equal(errors.New("Expected\n <string>: hi\nto equal\n <string>: bye")))
160 Expect(gotThere).To(BeFalse())
161 })
162 })
163
164 Context("when the function panics", func() {
165 It("panics", func() {
166 Expect(func() {
167 InterceptGomegaFailure(func() {
168 panic("boom")
169 })
170 }).To(PanicWith("boom"))
171 })
172 })
173 })
174
175 Context("Making an assertion without a registered fail handler", func() {
176 It("should panic", func() {
177 defer func() {
178 e := recover()
179 RegisterFailHandler(Fail)
180 if e == nil {
181 Fail("expected a panic to have occurred")
182 }
183 }()
184
185 RegisterFailHandler(nil)
186 Expect(true).Should(BeTrue())
187 })
188 })
189
190 Describe("specifying default durations globally", func() {
191 It("should update the durations on the Default gomega", func() {
192 bundle := internal.DurationBundle{
193 EventuallyTimeout: time.Minute,
194 EventuallyPollingInterval: 2 * time.Minute,
195 ConsistentlyDuration: 3 * time.Minute,
196 ConsistentlyPollingInterval: 4 * time.Minute,
197 }
198
199 SetDefaultEventuallyTimeout(bundle.EventuallyTimeout)
200 SetDefaultEventuallyPollingInterval(bundle.EventuallyPollingInterval)
201 SetDefaultConsistentlyDuration(bundle.ConsistentlyDuration)
202 SetDefaultConsistentlyPollingInterval(bundle.ConsistentlyPollingInterval)
203
204 Ω(Default.(*internal.Gomega).DurationBundle).Should(Equal(bundle))
205 })
206 })
207
208 Describe("Offsets", func() {
209 AfterEach(func() {
210 RegisterFailHandler(Fail)
211 })
212
213 It("computes the correct offsets", func() {
214 doubleNested := func(eventually bool) {
215 func() {
216 if eventually {
217 EventuallyWithOffset(2, true, "10ms", "5ms").Should(BeFalse())
218 } else {
219 ExpectWithOffset(2, true).To(BeFalse())
220 }
221 }()
222 }
223
224 reportedFile, reportedLine := "", 0
225 captureLocation := func(message string, skip ...int) {
226 _, reportedFile, reportedLine, _ = runtime.Caller(skip[0] + 1)
227 }
228
229 _, thisFile, anchorLine, _ := runtime.Caller(0) // 0
230 RegisterFailHandler(captureLocation) // 1
231 Expect(true).To(BeFalse()) // *2*
232 RegisterFailHandler(Fail) // 3
233 Ω(reportedFile).Should(Equal(thisFile)) // 4
234 Ω(reportedLine - anchorLine).Should(Equal(2)) // 5
235 RegisterFailHandler(captureLocation) // 6
236 doubleNested(false) // *7*
237 RegisterFailHandler(Fail) // 8
238 Ω(reportedFile).Should(Equal(thisFile)) // 9
239 Ω(reportedLine - anchorLine).Should(Equal(7)) // 10
240 RegisterFailHandler(captureLocation) // 11
241 Eventually(true, "10ms", "5ms").Should(BeFalse()) // *12*
242 RegisterFailHandler(Fail) // 13
243 Ω(reportedFile).Should(Equal(thisFile)) // 14
244 Ω(reportedLine - anchorLine).Should(Equal(12)) // 15
245 RegisterFailHandler(captureLocation) // 16
246 doubleNested(true) // *17*
247 RegisterFailHandler(Fail) // 18
248 Ω(reportedFile).Should(Equal(thisFile)) // 19
249 Ω(reportedLine - anchorLine).Should(Equal(17)) // 20
250 })
251 })
252 })
0 package internal
1
2 import (
3 "fmt"
4 "os"
5 "reflect"
6 "time"
7 )
8
9 type DurationBundle struct {
10 EventuallyTimeout time.Duration
11 EventuallyPollingInterval time.Duration
12 ConsistentlyDuration time.Duration
13 ConsistentlyPollingInterval time.Duration
14 }
15
16 const (
17 EventuallyTimeoutEnvVarName = "GOMEGA_DEFAULT_EVENTUALLY_TIMEOUT"
18 EventuallyPollingIntervalEnvVarName = "GOMEGA_DEFAULT_EVENTUALLY_POLLING_INTERVAL"
19
20 ConsistentlyDurationEnvVarName = "GOMEGA_DEFAULT_CONSISTENTLY_DURATION"
21 ConsistentlyPollingIntervalEnvVarName = "GOMEGA_DEFAULT_CONSISTENTLY_POLLING_INTERVAL"
22 )
23
24 func FetchDefaultDurationBundle() DurationBundle {
25 return DurationBundle{
26 EventuallyTimeout: durationFromEnv(EventuallyTimeoutEnvVarName, time.Second),
27 EventuallyPollingInterval: durationFromEnv(EventuallyPollingIntervalEnvVarName, 10*time.Millisecond),
28
29 ConsistentlyDuration: durationFromEnv(ConsistentlyDurationEnvVarName, 100*time.Millisecond),
30 ConsistentlyPollingInterval: durationFromEnv(ConsistentlyPollingIntervalEnvVarName, 10*time.Millisecond),
31 }
32 }
33
34 func durationFromEnv(key string, defaultDuration time.Duration) time.Duration {
35 value := os.Getenv(key)
36 if value == "" {
37 return defaultDuration
38 }
39 duration, err := time.ParseDuration(value)
40 if err != nil {
41 panic(fmt.Sprintf("Expected a duration when using %s! Parse error %v", key, err))
42 }
43 return duration
44 }
45
46 func toDuration(input interface{}) time.Duration {
47 duration, ok := input.(time.Duration)
48 if ok {
49 return duration
50 }
51
52 value := reflect.ValueOf(input)
53 kind := reflect.TypeOf(input).Kind()
54
55 if reflect.Int <= kind && kind <= reflect.Int64 {
56 return time.Duration(value.Int()) * time.Second
57 } else if reflect.Uint <= kind && kind <= reflect.Uint64 {
58 return time.Duration(value.Uint()) * time.Second
59 } else if reflect.Float32 <= kind && kind <= reflect.Float64 {
60 return time.Duration(value.Float() * float64(time.Second))
61 } else if reflect.String == kind {
62 duration, err := time.ParseDuration(value.String())
63 if err != nil {
64 panic(fmt.Sprintf("%#v is not a valid parsable duration string.", input))
65 }
66 return duration
67 }
68
69 panic(fmt.Sprintf("%v is not a valid interval. Must be time.Duration, parsable duration string or a number.", input))
70 }
0 package internal_test
1
2 import (
3 "os"
4 "time"
5
6 . "github.com/onsi/ginkgo"
7 . "github.com/onsi/gomega"
8
9 "github.com/onsi/gomega/internal"
10 )
11
12 var _ = Describe("DurationBundle and Duration Support", func() {
13 Describe("fetching default durations from the environment", func() {
14 var envVars []string
15 var originalValues map[string]string
16
17 BeforeEach(func() {
18 envVars = []string{internal.EventuallyTimeoutEnvVarName, internal.EventuallyPollingIntervalEnvVarName, internal.ConsistentlyDurationEnvVarName, internal.ConsistentlyPollingIntervalEnvVarName}
19 originalValues = map[string]string{}
20
21 for _, envVar := range envVars {
22 originalValues[envVar] = os.Getenv(envVar)
23 }
24 })
25
26 AfterEach(func() {
27 for _, envVar := range envVars {
28 Ω(os.Setenv(envVar, originalValues[envVar])).Should(Succeed())
29 }
30 })
31
32 Context("with no environment set", func() {
33 BeforeEach(func() {
34 for _, envVar := range envVars {
35 os.Unsetenv(envVar)
36 }
37 })
38
39 It("returns the default bundle", func() {
40 bundle := internal.FetchDefaultDurationBundle()
41 Ω(bundle.EventuallyTimeout).Should(Equal(time.Second))
42 Ω(bundle.EventuallyPollingInterval).Should(Equal(10 * time.Millisecond))
43 Ω(bundle.ConsistentlyDuration).Should(Equal(100 * time.Millisecond))
44 Ω(bundle.ConsistentlyPollingInterval).Should(Equal(10 * time.Millisecond))
45 })
46 })
47
48 Context("with a valid environment set", func() {
49 BeforeEach(func() {
50 os.Setenv(internal.EventuallyTimeoutEnvVarName, "1m")
51 os.Setenv(internal.EventuallyPollingIntervalEnvVarName, "2s")
52 os.Setenv(internal.ConsistentlyDurationEnvVarName, "1h")
53 os.Setenv(internal.ConsistentlyPollingIntervalEnvVarName, "3ms")
54 })
55
56 It("returns an appropriate bundle", func() {
57 bundle := internal.FetchDefaultDurationBundle()
58 Ω(bundle.EventuallyTimeout).Should(Equal(time.Minute))
59 Ω(bundle.EventuallyPollingInterval).Should(Equal(2 * time.Second))
60 Ω(bundle.ConsistentlyDuration).Should(Equal(time.Hour))
61 Ω(bundle.ConsistentlyPollingInterval).Should(Equal(3 * time.Millisecond))
62 })
63 })
64
65 Context("with an invalid environment set", func() {
66 BeforeEach(func() {
67 os.Setenv(internal.EventuallyTimeoutEnvVarName, "chicken nuggets")
68 })
69
70 It("panics", func() {
71 Ω(func() {
72 internal.FetchDefaultDurationBundle()
73 }).Should(PanicWith(`Expected a duration when using GOMEGA_DEFAULT_EVENTUALLY_TIMEOUT! Parse error time: invalid duration "chicken nuggets"`))
74 })
75 })
76 })
77
78 Describe("specifying default durations on a Gomega instance", func() {
79 It("is supported", func() {
80 ig := NewInstrumentedGomega()
81 ig.G.SetDefaultConsistentlyDuration(50 * time.Millisecond)
82 ig.G.SetDefaultConsistentlyPollingInterval(5 * time.Millisecond)
83 ig.G.SetDefaultEventuallyTimeout(200 * time.Millisecond)
84 ig.G.SetDefaultEventuallyPollingInterval(20 * time.Millisecond)
85
86 counter := 0
87 t := time.Now()
88 ig.G.Consistently(func() bool {
89 counter += 1
90 return true
91 }).Should(BeTrue())
92 dt := time.Since(t)
93 Ω(dt).Should(BeNumerically("~", 50*time.Millisecond, 25*time.Millisecond))
94 Ω(counter).Should(BeNumerically("~", 10, 5))
95
96 t = time.Now()
97 counter = 0
98 ig.G.Eventually(func() bool {
99 counter += 1
100 if counter >= 6 {
101 return true
102 }
103 return false
104 }).Should(BeTrue())
105 dt = time.Since(t)
106 Ω(dt).Should(BeNumerically("~", 120*time.Millisecond, 20*time.Millisecond))
107 })
108 })
109
110 Describe("specifying durations", func() {
111 It("supports passing in a duration", func() {
112 t := time.Now()
113 Consistently(true, 50*time.Millisecond).Should(BeTrue())
114 Ω(time.Since(t)).Should(BeNumerically("~", 50*time.Millisecond, 30*time.Millisecond))
115 })
116
117 It("supports passing in a raw integer # of seconds", func() {
118 t := time.Now()
119 Consistently(true, 1).Should(BeTrue())
120 Ω(time.Since(t)).Should(BeNumerically("~", time.Second, 100*time.Millisecond))
121 })
122
123 It("supports passing in an unsigned integer # of seconds", func() {
124 t := time.Now()
125 Consistently(true, uint(1)).Should(BeTrue())
126 Ω(time.Since(t)).Should(BeNumerically("~", time.Second, 100*time.Millisecond))
127 })
128
129 It("supports passing in a float number of seconds", func() {
130 t := time.Now()
131 Consistently(true, 0.05).Should(BeTrue())
132 Ω(time.Since(t)).Should(BeNumerically("~", 50*time.Millisecond, 30*time.Millisecond))
133 })
134
135 It("supports passing in a duration string", func() {
136 t := time.Now()
137 Consistently(true, "50ms").Should(BeTrue())
138 Ω(time.Since(t)).Should(BeNumerically("~", 50*time.Millisecond, 30*time.Millisecond))
139 })
140
141 It("panics when the duration string can't be parsed", func() {
142 Ω(func() {
143 Consistently(true, "fries").Should(BeTrue())
144 }).Should(PanicWith(`"fries" is not a valid parsable duration string.`))
145 })
146
147 It("panics if anything else is passed in", func() {
148 Ω(func() {
149 Consistently(true, true).Should(BeTrue())
150 }).Should(PanicWith("true is not a valid interval. Must be time.Duration, parsable duration string or a number."))
151 })
152 })
153 })
+0
-23
internal/fakematcher/fake_matcher.go less more
0 package fakematcher
1
2 import "fmt"
3
4 type FakeMatcher struct {
5 ReceivedActual interface{}
6 MatchesToReturn bool
7 ErrToReturn error
8 }
9
10 func (matcher *FakeMatcher) Match(actual interface{}) (bool, error) {
11 matcher.ReceivedActual = actual
12
13 return matcher.MatchesToReturn, matcher.ErrToReturn
14 }
15
16 func (matcher *FakeMatcher) FailureMessage(actual interface{}) string {
17 return fmt.Sprintf("positive: %v", actual)
18 }
19
20 func (matcher *FakeMatcher) NegatedFailureMessage(actual interface{}) string {
21 return fmt.Sprintf("negative: %v", actual)
22 }
0 package internal
1
2 import (
3 "time"
4
5 "github.com/onsi/gomega/types"
6 )
7
8 type Gomega struct {
9 Fail types.GomegaFailHandler
10 THelper func()
11 DurationBundle DurationBundle
12 }
13
14 func NewGomega(bundle DurationBundle) *Gomega {
15 return &Gomega{
16 Fail: nil,
17 THelper: nil,
18 DurationBundle: bundle,
19 }
20 }
21
22 func (g *Gomega) IsConfigured() bool {
23 return g.Fail != nil && g.THelper != nil
24 }
25
26 func (g *Gomega) ConfigureWithFailHandler(fail types.GomegaFailHandler) *Gomega {
27 g.Fail = fail
28 g.THelper = func() {}
29 return g
30 }
31
32 func (g *Gomega) ConfigureWithT(t types.GomegaTestingT) *Gomega {
33 g.Fail = func(message string, _ ...int) {
34 t.Helper()
35 t.Fatalf("\n%s", message)
36 }
37 g.THelper = t.Helper
38 return g
39 }
40
41 func (g *Gomega) Ω(atual interface{}, extra ...interface{}) types.Assertion {
42 return g.ExpectWithOffset(0, atual, extra...)
43 }
44
45 func (g *Gomega) Expect(atual interface{}, extra ...interface{}) types.Assertion {
46 return g.ExpectWithOffset(0, atual, extra...)
47 }
48
49 func (g *Gomega) ExpectWithOffset(offset int, actual interface{}, extra ...interface{}) types.Assertion {
50 return NewAssertion(actual, g, offset, extra...)
51 }
52
53 func (g *Gomega) Eventually(actual interface{}, intervals ...interface{}) types.AsyncAssertion {
54 return g.EventuallyWithOffset(0, actual, intervals...)
55 }
56
57 func (g *Gomega) EventuallyWithOffset(offset int, actual interface{}, intervals ...interface{}) types.AsyncAssertion {
58 timeoutInterval := g.DurationBundle.EventuallyTimeout
59 pollingInterval := g.DurationBundle.EventuallyPollingInterval
60 if len(intervals) > 0 {
61 timeoutInterval = toDuration(intervals[0])
62 }
63 if len(intervals) > 1 {
64 pollingInterval = toDuration(intervals[1])
65 }
66
67 return NewAsyncAssertion(AsyncAssertionTypeEventually, actual, g, timeoutInterval, pollingInterval, offset)
68 }
69
70 func (g *Gomega) Consistently(actual interface{}, intervals ...interface{}) types.AsyncAssertion {
71 return g.ConsistentlyWithOffset(0, actual, intervals...)
72 }
73
74 func (g *Gomega) ConsistentlyWithOffset(offset int, actual interface{}, intervals ...interface{}) types.AsyncAssertion {
75 timeoutInterval := g.DurationBundle.ConsistentlyDuration
76 pollingInterval := g.DurationBundle.ConsistentlyPollingInterval
77 if len(intervals) > 0 {
78 timeoutInterval = toDuration(intervals[0])
79 }
80 if len(intervals) > 1 {
81 pollingInterval = toDuration(intervals[1])
82 }
83
84 return NewAsyncAssertion(AsyncAssertionTypeConsistently, actual, g, timeoutInterval, pollingInterval, offset)
85 }
86
87 func (g *Gomega) SetDefaultEventuallyTimeout(t time.Duration) {
88 g.DurationBundle.EventuallyTimeout = t
89 }
90
91 func (g *Gomega) SetDefaultEventuallyPollingInterval(t time.Duration) {
92 g.DurationBundle.EventuallyPollingInterval = t
93 }
94
95 func (g *Gomega) SetDefaultConsistentlyDuration(t time.Duration) {
96 g.DurationBundle.ConsistentlyDuration = t
97 }
98
99 func (g *Gomega) SetDefaultConsistentlyPollingInterval(t time.Duration) {
100 g.DurationBundle.ConsistentlyPollingInterval = t
101 }
0 package internal_test
1
2 import (
3 "runtime"
4
5 . "github.com/onsi/ginkgo"
6 . "github.com/onsi/gomega"
7 "github.com/onsi/gomega/internal"
8 )
9
10 var _ = Describe("Gomega", func() {
11 It("is mostly tested in assertion_test and async_assertion_test", func() {
12
13 })
14 Describe("when initialized", func() {
15 var g *internal.Gomega
16
17 BeforeEach(func() {
18 g = internal.NewGomega(internal.DurationBundle{})
19 Ω(g.Fail).Should(BeNil())
20 Ω(g.THelper).Should(BeNil())
21 })
22
23 It("should be registered as unconfigured", func() {
24 Ω(g.IsConfigured()).Should(BeFalse())
25 })
26
27 Context("when configured with a fail handler", func() {
28 It("registers the fail handler and a no-op helper", func() {
29 var capturedMessage string
30 g.ConfigureWithFailHandler(func(message string, skip ...int) {
31 capturedMessage = message
32 })
33 Ω(g.IsConfigured()).Should(BeTrue())
34
35 g.Fail("hi bob")
36 Ω(capturedMessage).Should(Equal("hi bob"))
37 Ω(g.THelper).ShouldNot(Panic())
38 })
39 })
40
41 Context("when configured with a T", func() {
42 It("registers a fail handler an the T's helper", func() {
43 fake := &FakeGomegaTestingT{}
44 g.ConfigureWithT(fake)
45 Ω(g.IsConfigured()).Should(BeTrue())
46
47 g.Fail("hi bob")
48 Ω(fake.CalledHelper).Should(BeTrue())
49 Ω(fake.CalledFatalf).Should(Equal("\nhi bob"))
50
51 fake.CalledHelper = false
52 g.THelper()
53 Ω(fake.CalledHelper).Should(BeTrue())
54 })
55 })
56 })
57
58 Describe("Offset", func() {
59 It("computes the correct offsets", func() {
60 doubleNested := func(g Gomega, eventually bool) {
61 func() {
62 if eventually {
63 g.EventuallyWithOffset(2, true, "10ms", "5ms").Should(BeFalse())
64 } else {
65 g.ExpectWithOffset(2, true).To(BeFalse())
66 }
67 }()
68 }
69
70 reportedFile, reportedLine := "", 0
71 _, thisFile, anchorLine, _ := runtime.Caller(0) // 0
72 g := NewGomega(func(message string, skip ...int) { // 1
73 _, reportedFile, reportedLine, _ = runtime.Caller(skip[0] + 1) // 2
74 }) // 3
75 g.Expect(true).To(BeFalse()) // *4*
76 Ω(reportedFile).Should(Equal(thisFile)) // 5
77 Ω(reportedLine - anchorLine).Should(Equal(4)) // 6
78 doubleNested(g, false) // *7*
79 Ω(reportedFile).Should(Equal(thisFile)) // 8
80 Ω(reportedLine - anchorLine).Should(Equal(7)) // 9
81 g.Eventually(true, "10ms", "5ms").Should(BeFalse()) // *10*
82 Ω(reportedFile).Should(Equal(thisFile)) // 11
83 Ω(reportedLine - anchorLine).Should(Equal(10)) // 12
84 doubleNested(g, true) // *13*
85 Ω(reportedFile).Should(Equal(thisFile)) // 14
86 Ω(reportedLine - anchorLine).Should(Equal(13)) // 15
87 })
88 })
89 })
0 package internal_test
1
2 import (
3 "errors"
4 "fmt"
5 "runtime"
6 "strings"
7 "testing"
8
9 . "github.com/onsi/ginkgo"
10 . "github.com/onsi/gomega"
11 "github.com/onsi/gomega/internal"
12 )
13
14 func TestInternal(t *testing.T) {
15 RegisterFailHandler(Fail)
16 RunSpecs(t, "Internal Suite")
17 }
18
19 // InstrumentedGomega
20 type InstrumentedGomega struct {
21 G *internal.Gomega
22 FailureMessage string
23 FailureSkip []int
24 RegisteredHelpers []string
25 }
26
27 func NewInstrumentedGomega() *InstrumentedGomega {
28 out := &InstrumentedGomega{}
29
30 out.G = internal.NewGomega(internal.FetchDefaultDurationBundle())
31 out.G.Fail = func(message string, skip ...int) {
32 out.FailureMessage = message
33 out.FailureSkip = skip
34 }
35 out.G.THelper = func() {
36 pc, _, _, _ := runtime.Caller(1)
37 f := runtime.FuncForPC(pc)
38 funcName := strings.TrimPrefix(f.Name(), "github.com/onsi/gomega/internal.")
39 out.RegisteredHelpers = append(out.RegisteredHelpers, funcName)
40 }
41
42 return out
43 }
44
45 // TestMatcher
46 var MATCH = "match"
47 var NO_MATCH = "no match"
48 var ERR_MATCH = "err match"
49 var TEST_MATCHER_ERR = errors.New("spec matcher error")
50
51 type SpecMatcher struct{}
52
53 func (matcher SpecMatcher) Match(actual interface{}) (bool, error) {
54 switch actual {
55 case MATCH:
56 return true, nil
57 case NO_MATCH:
58 return false, nil
59 case ERR_MATCH:
60 return false, TEST_MATCHER_ERR
61 }
62 return false, fmt.Errorf("unkown actual %v", actual)
63 }
64
65 func (matcher SpecMatcher) FailureMessage(actual interface{}) string {
66 return fmt.Sprintf("positive: %s", actual)
67 }
68
69 func (matcher SpecMatcher) NegatedFailureMessage(actual interface{}) string {
70 return fmt.Sprintf("negative: %s", actual)
71 }
72
73 func SpecMatch() SpecMatcher {
74 return SpecMatcher{}
75 }
76
77 //FakeGomegaTestingT
78 type FakeGomegaTestingT struct {
79 CalledHelper bool
80 CalledFatalf string
81 }
82
83 func (f *FakeGomegaTestingT) Helper() {
84 f.CalledHelper = true
85 }
86
87 func (f *FakeGomegaTestingT) Fatalf(s string, args ...interface{}) {
88 f.CalledFatalf = fmt.Sprintf(s, args...)
89 }
+0
-25
internal/oraclematcher/oracle_matcher.go less more
0 package oraclematcher
1
2 import "github.com/onsi/gomega/types"
3
4 /*
5 GomegaMatchers that also match the OracleMatcher interface can convey information about
6 whether or not their result will change upon future attempts.
7
8 This allows `Eventually` and `Consistently` to short circuit if success becomes impossible.
9
10 For example, a process' exit code can never change. So, gexec's Exit matcher returns `true`
11 for `MatchMayChangeInTheFuture` until the process exits, at which point it returns `false` forevermore.
12 */
13 type OracleMatcher interface {
14 MatchMayChangeInTheFuture(actual interface{}) bool
15 }
16
17 func MatchMayChangeInTheFuture(matcher types.GomegaMatcher, value interface{}) bool {
18 oracleMatcher, ok := matcher.(OracleMatcher)
19 if !ok {
20 return true
21 }
22
23 return oracleMatcher.MatchMayChangeInTheFuture(value)
24 }
+0
-60
internal/testingtsupport/testing_t_support.go less more
0 package testingtsupport
1
2 import (
3 "regexp"
4 "runtime/debug"
5 "strings"
6
7 "github.com/onsi/gomega/types"
8 )
9
10 var StackTracePruneRE = regexp.MustCompile(`\/gomega\/|\/ginkgo\/|\/pkg\/testing\/|\/pkg\/runtime\/`)
11
12 type EmptyTWithHelper struct{}
13
14 func (e EmptyTWithHelper) Helper() {}
15
16 type gomegaTestingT interface {
17 Fatalf(format string, args ...interface{})
18 }
19
20 func BuildTestingTGomegaFailWrapper(t gomegaTestingT) *types.GomegaFailWrapper {
21 tWithHelper, hasHelper := t.(types.TWithHelper)
22 if !hasHelper {
23 tWithHelper = EmptyTWithHelper{}
24 }
25
26 fail := func(message string, callerSkip ...int) {
27 if hasHelper {
28 tWithHelper.Helper()
29 t.Fatalf("\n%s", message)
30 } else {
31 skip := 2
32 if len(callerSkip) > 0 {
33 skip += callerSkip[0]
34 }
35 stackTrace := pruneStack(string(debug.Stack()), skip)
36 t.Fatalf("\n%s\n%s\n", stackTrace, message)
37 }
38 }
39
40 return &types.GomegaFailWrapper{
41 Fail: fail,
42 TWithHelper: tWithHelper,
43 }
44 }
45
46 func pruneStack(fullStackTrace string, skip int) string {
47 stack := strings.Split(fullStackTrace, "\n")[1:]
48 if len(stack) > 2*skip {
49 stack = stack[2*skip:]
50 }
51 prunedStack := []string{}
52 for i := 0; i < len(stack)/2; i++ {
53 if !StackTracePruneRE.Match([]byte(stack[i*2])) {
54 prunedStack = append(prunedStack, stack[i*2])
55 prunedStack = append(prunedStack, stack[i*2+1])
56 }
57 }
58 return strings.Join(prunedStack, "\n")
59 }
00 package testingtsupport_test
11
22 import (
3 "regexp"
4 "time"
5
6 "github.com/onsi/gomega/internal/testingtsupport"
7
83 . "github.com/onsi/gomega"
94
10 "fmt"
115 "testing"
126 )
137
1610 Ω(true).Should(BeTrue())
1711 }
1812
19 type FakeTWithHelper struct {
20 LastFatal string
13 func TestNewGomegaWithT(t *testing.T) {
14 g := NewWithT(t)
15 g.Expect(true).To(BeTrue())
2116 }
22
23 func (f *FakeTWithHelper) Fatalf(format string, args ...interface{}) {
24 f.LastFatal = fmt.Sprintf(format, args...)
25 }
26
27 func TestGomegaWithTWithoutHelper(t *testing.T) {
28 g := NewGomegaWithT(t)
29
30 testingtsupport.StackTracePruneRE = regexp.MustCompile(`\/ginkgo\/`)
31
32 f := &FakeTWithHelper{}
33 testG := NewGomegaWithT(f)
34
35 testG.Expect("foo").To(Equal("foo"))
36 g.Expect(f.LastFatal).To(BeZero())
37
38 testG.Expect("foo").To(Equal("bar"))
39 g.Expect(f.LastFatal).To(ContainSubstring("<string>: foo"))
40 g.Expect(f.LastFatal).To(ContainSubstring("testingtsupport_test"), "It should include a stacktrace")
41
42 testG.Eventually("foo2", time.Millisecond).Should(Equal("bar"))
43 g.Expect(f.LastFatal).To(ContainSubstring("<string>: foo2"))
44
45 testG.Consistently("foo3", time.Millisecond).Should(Equal("bar"))
46 g.Expect(f.LastFatal).To(ContainSubstring("<string>: foo3"))
47 }
48
49 type FakeTWithoutHelper struct {
50 LastFatal string
51 HelperCount int
52 }
53
54 func (f *FakeTWithoutHelper) Fatalf(format string, args ...interface{}) {
55 f.LastFatal = fmt.Sprintf(format, args...)
56 }
57
58 func (f *FakeTWithoutHelper) Helper() {
59 f.HelperCount += 1
60 }
61
62 func (f *FakeTWithoutHelper) ResetHelper() {
63 f.HelperCount = 0
64 }
65
66 func TestGomegaWithTWithHelper(t *testing.T) {
67 g := NewGomegaWithT(t)
68
69 f := &FakeTWithoutHelper{}
70 testG := NewGomegaWithT(f)
71
72 testG.Expect("foo").To(Equal("foo"))
73 g.Expect(f.LastFatal).To(BeZero())
74 g.Expect(f.HelperCount).To(BeNumerically(">", 0))
75 f.ResetHelper()
76
77 testG.Expect("foo").To(Equal("bar"))
78 g.Expect(f.LastFatal).To(ContainSubstring("<string>: foo"))
79 g.Expect(f.LastFatal).NotTo(ContainSubstring("testingtsupport_test"), "It should _not_ include a stacktrace")
80 g.Expect(f.HelperCount).To(BeNumerically(">", 0))
81 f.ResetHelper()
82
83 testG.Eventually("foo2", time.Millisecond).Should(Equal("bar"))
84 g.Expect(f.LastFatal).To(ContainSubstring("<string>: foo2"))
85 g.Expect(f.HelperCount).To(BeNumerically(">", 0))
86 f.ResetHelper()
87
88 testG.Consistently("foo3", time.Millisecond).Should(Equal("bar"))
89 g.Expect(f.LastFatal).To(ContainSubstring("<string>: foo3"))
90 g.Expect(f.HelperCount).To(BeNumerically(">", 0))
91 }
33 "fmt"
44
55 "github.com/onsi/gomega/format"
6 "github.com/onsi/gomega/internal/oraclematcher"
76 "github.com/onsi/gomega/types"
87 )
98
5150 if m.firstFailedMatcher == nil {
5251 // so all matchers succeeded.. Any one of them changing would change the result.
5352 for _, matcher := range m.Matchers {
54 if oraclematcher.MatchMayChangeInTheFuture(matcher, actual) {
53 if types.MatchMayChangeInTheFuture(matcher, actual) {
5554 return true
5655 }
5756 }
5857 return false // none of were going to change
5958 }
6059 // one of the matchers failed.. it must be able to change in order to affect the result
61 return oraclematcher.MatchMayChangeInTheFuture(m.firstFailedMatcher, actual)
60 return types.MatchMayChangeInTheFuture(m.firstFailedMatcher, actual)
6261 }
1717 return false, fmt.Errorf("BeElement matcher expects actual to be typed")
1818 }
1919
20 length := len(matcher.Elements)
21 valueAt := func(i int) interface{} {
22 return matcher.Elements[i]
23 }
24 // Special handling of a single element of type Array or Slice
25 if length == 1 && isArrayOrSlice(valueAt(0)) {
26 element := valueAt(0)
27 value := reflect.ValueOf(element)
28 length = value.Len()
29 valueAt = func(i int) interface{} {
30 return value.Index(i).Interface()
31 }
32 }
33
3420 var lastError error
35 for i := 0; i < length; i++ {
36 matcher := &EqualMatcher{Expected: valueAt(i)}
21 for _, m := range flatten(matcher.Elements) {
22 matcher := &EqualMatcher{Expected: m}
3723 success, err := matcher.Match(actual)
3824 if err != nil {
3925 lastError = err
4834 }
4935
5036 func (matcher *BeElementOfMatcher) FailureMessage(actual interface{}) (message string) {
51 return format.Message(actual, "to be an element of", matcher.Elements)
37 return format.Message(actual, "to be an element of", presentable(matcher.Elements))
5238 }
5339
5440 func (matcher *BeElementOfMatcher) NegatedFailureMessage(actual interface{}) (message string) {
55 return format.Message(actual, "not to be an element of", matcher.Elements)
41 return format.Message(actual, "not to be an element of", presentable(matcher.Elements))
5642 }
3232 })
3333
3434 When("passed a correctly typed nil", func() {
35 It("should operate succesfully on the passed in value", func() {
35 It("should operate successfully on the passed in value", func() {
3636 var nilSlice []int
3737 Expect(1).ShouldNot(BeElementOf(nilSlice))
3838
5555
5656 It("builds failure message", func() {
5757 actual := BeElementOf(1, 2).FailureMessage(123)
58 Expect(actual).To(Equal("Expected\n <int>: 123\nto be an element of\n <[]interface {} | len:2, cap:2>: [1, 2]"))
58 Expect(actual).To(Equal("Expected\n <int>: 123\nto be an element of\n <[]int | len:2, cap:2>: [1, 2]"))
5959 })
6060
6161 It("builds negated failure message", func() {
6262 actual := BeElementOf(1, 2).NegatedFailureMessage(123)
63 Expect(actual).To(Equal("Expected\n <int>: 123\nnot to be an element of\n <[]interface {} | len:2, cap:2>: [1, 2]"))
63 Expect(actual).To(Equal("Expected\n <int>: 123\nnot to be an element of\n <[]int | len:2, cap:2>: [1, 2]"))
6464 })
6565 })
4444 return false, fmt.Errorf("Expected a number. Got:\n%s", format.Object(matcher.CompareTo[0], 1))
4545 }
4646 if len(matcher.CompareTo) == 2 && !isNumber(matcher.CompareTo[1]) {
47 return false, fmt.Errorf("Expected a number. Got:\n%s", format.Object(matcher.CompareTo[0], 1))
47 return false, fmt.Errorf("Expected a number. Got:\n%s", format.Object(matcher.CompareTo[1], 1))
4848 }
4949
5050 switch matcher.Comparator {
142142 success, err = (&BeNumericallyMatcher{Comparator: "~", CompareTo: []interface{}{3.0, "foo"}}).Match(5.0)
143143 Expect(success).Should(BeFalse())
144144 Expect(err).Should(HaveOccurred())
145 Expect(err.Error()).Should(ContainSubstring("foo"))
145146
146147 success, err = (&BeNumericallyMatcher{Comparator: "==", CompareTo: []interface{}{"bar"}}).Match(5)
147148 Expect(success).Should(BeFalse())
5656 return
5757 }
5858
59 func matchers(expectedElems []interface{}) (matchers []interface{}) {
60 elems := expectedElems
61 if len(expectedElems) == 1 && isArrayOrSlice(expectedElems[0]) {
62 elems = []interface{}{}
63 value := reflect.ValueOf(expectedElems[0])
64 for i := 0; i < value.Len(); i++ {
65 elems = append(elems, value.Index(i).Interface())
66 }
59 func flatten(elems []interface{}) []interface{} {
60 if len(elems) != 1 || !isArrayOrSlice(elems[0]) {
61 return elems
6762 }
6863
69 for _, e := range elems {
64 value := reflect.ValueOf(elems[0])
65 flattened := make([]interface{}, value.Len())
66 for i := 0; i < value.Len(); i++ {
67 flattened[i] = value.Index(i).Interface()
68 }
69 return flattened
70 }
71
72 func matchers(expectedElems []interface{}) (matchers []interface{}) {
73 for _, e := range flatten(expectedElems) {
7074 matcher, isMatcher := e.(omegaMatcher)
7175 if !isMatcher {
7276 matcher = &EqualMatcher{Expected: e}
7478 matchers = append(matchers, matcher)
7579 }
7680 return
81 }
82
83 func presentable(elems []interface{}) interface{} {
84 elems = flatten(elems)
85
86 if len(elems) == 0 {
87 return []interface{}{}
88 }
89
90 sv := reflect.ValueOf(elems)
91 tt := sv.Index(0).Elem().Type()
92 for i := 1; i < sv.Len(); i++ {
93 if sv.Index(i).Elem().Type() != tt {
94 return elems
95 }
96 }
97
98 ss := reflect.MakeSlice(reflect.SliceOf(tt), sv.Len(), sv.Len())
99 for i := 0; i < sv.Len(); i++ {
100 ss.Index(i).Set(sv.Index(i).Elem())
101 }
102
103 return ss.Interface()
77104 }
78105
79106 func valuesOf(actual interface{}) []interface{} {
94121 }
95122
96123 func (matcher *ConsistOfMatcher) FailureMessage(actual interface{}) (message string) {
97 message = format.Message(actual, "to consist of", matcher.Elements)
124 message = format.Message(actual, "to consist of", presentable(matcher.Elements))
98125 message = appendMissingElements(message, matcher.missingElements)
99126 if len(matcher.extraElements) > 0 {
100127 message = fmt.Sprintf("%s\nthe extra elements were\n%s", message,
101 format.Object(matcher.extraElements, 1))
128 format.Object(presentable(matcher.extraElements), 1))
102129 }
103130 return
104131 }
108135 return message
109136 }
110137 return fmt.Sprintf("%s\nthe missing elements were\n%s", message,
111 format.Object(missingElements, 1))
138 format.Object(presentable(missingElements), 1))
112139 }
113140
114141 func (matcher *ConsistOfMatcher) NegatedFailureMessage(actual interface{}) (message string) {
115 return format.Message(actual, "not to consist of", matcher.Elements)
142 return format.Message(actual, "not to consist of", presentable(matcher.Elements))
116143 }
106106 Expect(failures).To(ConsistOf(MatchRegexp(expected)))
107107 })
108108 })
109
110 When("expected was specified as an array", func() {
111 It("flattens the array in the expectation message", func() {
112 failures := InterceptGomegaFailures(func() {
113 Expect([]string{"A", "B", "C"}).To(ConsistOf([]string{"A", "B"}))
114 })
115
116 expected := `Expected\n.*\["A", "B", "C"\]\nto consist of\n.*: \["A", "B"\]\nthe extra elements were\n.*\["C"\]`
117 Expect(failures).To(ConsistOf(MatchRegexp(expected)))
118 })
119
120 It("flattens the array in the negated expectation message", func() {
121 failures := InterceptGomegaFailures(func() {
122 Expect([]string{"A", "B"}).NotTo(ConsistOf([]string{"A", "B"}))
123 })
124
125 expected := `Expected\n.*\["A", "B"\]\nnot to consist of\n.*: \["A", "B"\]`
126 Expect(failures).To(ConsistOf(MatchRegexp(expected)))
127 })
128 })
129
130 When("the expected values are the same type", func() {
131 It("uses that type for the expectation slice", func() {
132 failures := InterceptGomegaFailures(func() {
133 Expect([]string{"A", "B"}).To(ConsistOf("A", "C"))
134 })
135
136 expected := `to consist of
137 \s*<\[\]string \| len:2, cap:2>: \["A", "C"\]
138 the missing elements were
139 \s*<\[\]string \| len:1, cap:1>: \["C"\]
140 the extra elements were
141 \s*<\[\]string \| len:1, cap:1>: \["B"\]`
142 Expect(failures).To(ConsistOf(MatchRegexp(expected)))
143 })
144
145 It("uses that type for the negated expectation slice", func() {
146 failures := InterceptGomegaFailures(func() {
147 Expect([]uint64{1, 2}).NotTo(ConsistOf(uint64(1), uint64(2)))
148 })
149
150 expected := `not to consist of\n\s*<\[\]uint64 \| len:2, cap:2>: \[1, 2\]`
151 Expect(failures).To(ConsistOf(MatchRegexp(expected)))
152 })
153 })
154
155 When("the expected values are different types", func() {
156 It("uses interface{} for the expectation slice", func() {
157 failures := InterceptGomegaFailures(func() {
158 Expect([]interface{}{1, true}).To(ConsistOf(1, "C"))
159 })
160
161 expected := `to consist of
162 \s*<\[\]interface {} \| len:2, cap:2>: \[<int>1, <string>"C"\]
163 the missing elements were
164 \s*<\[\]string \| len:1, cap:1>: \["C"\]
165 the extra elements were
166 \s*<\[\]bool \| len:1, cap:1>: \[true\]`
167 Expect(failures).To(ConsistOf(MatchRegexp(expected)))
168 })
169
170 It("uses interface{} for the negated expectation slice", func() {
171 failures := InterceptGomegaFailures(func() {
172 Expect([]interface{}{1, "B"}).NotTo(ConsistOf(1, "B"))
173 })
174
175 expected := `not to consist of\n\s*<\[\]interface {} \| len:2, cap:2>: \[<int>1, <string>"B"\]`
176 Expect(failures).To(ConsistOf(MatchRegexp(expected)))
177 })
178 })
109179 })
110180 })
3434 }
3535
3636 func (matcher *ContainElementsMatcher) FailureMessage(actual interface{}) (message string) {
37 message = format.Message(actual, "to contain elements", matcher.Elements)
37 message = format.Message(actual, "to contain elements", presentable(matcher.Elements))
3838 return appendMissingElements(message, matcher.missingElements)
3939 }
4040
4141 func (matcher *ContainElementsMatcher) NegatedFailureMessage(actual interface{}) (message string) {
42 return format.Message(actual, "not to contain elements", matcher.Elements)
42 return format.Message(actual, "not to contain elements", presentable(matcher.Elements))
4343 }
8181 expected := "Expected\n.*\\[2\\]\nto contain elements\n.*\\[1, 2, 3\\]\nthe missing elements were\n.*\\[1, 3\\]"
8282 Expect(failures).To(ContainElements(MatchRegexp(expected)))
8383 })
84
85 When("expected was specified as an array", func() {
86 It("flattens the array in the expectation message", func() {
87 failures := InterceptGomegaFailures(func() {
88 Expect([]string{"A", "B", "C"}).To(ContainElements([]string{"A", "D"}))
89 })
90
91 expected := `Expected\n.*\["A", "B", "C"\]\nto contain elements\n.*: \["A", "D"\]\nthe missing elements were\n.*\["D"\]`
92 Expect(failures).To(ConsistOf(MatchRegexp(expected)))
93 })
94
95 It("flattens the array in the negated expectation message", func() {
96 failures := InterceptGomegaFailures(func() {
97 Expect([]string{"A", "B"}).NotTo(ContainElements([]string{"A", "B"}))
98 })
99
100 expected := `Expected\n.*\["A", "B"\]\nnot to contain elements\n.*: \["A", "B"\]`
101 Expect(failures).To(ConsistOf(MatchRegexp(expected)))
102 })
103 })
104
105 When("the expected values are the same type", func() {
106 It("uses that type for the expectation slice", func() {
107 failures := InterceptGomegaFailures(func() {
108 Expect([]string{"A", "B"}).To(ContainElements("A", "B", "C"))
109 })
110
111 expected := `to contain elements
112 \s*<\[\]string \| len:3, cap:3>: \["A", "B", "C"\]
113 the missing elements were
114 \s*<\[\]string \| len:1, cap:1>: \["C"\]`
115 Expect(failures).To(ConsistOf(MatchRegexp(expected)))
116 })
117
118 It("uses that type for the negated expectation slice", func() {
119 failures := InterceptGomegaFailures(func() {
120 Expect([]uint64{1, 2}).NotTo(ContainElements(uint64(1), uint64(2)))
121 })
122
123 expected := `not to contain elements\n\s*<\[\]uint64 \| len:2, cap:2>: \[1, 2\]`
124 Expect(failures).To(ConsistOf(MatchRegexp(expected)))
125 })
126 })
127
128 When("the expected values are different types", func() {
129 It("uses interface{} for the expectation slice", func() {
130 failures := InterceptGomegaFailures(func() {
131 Expect([]interface{}{1, true}).To(ContainElements(1, "C"))
132 })
133
134 expected := `to contain elements
135 \s*<\[\]interface {} \| len:2, cap:2>: \[<int>1, <string>"C"\]
136 the missing elements were
137 \s*<\[\]string \| len:1, cap:1>: \["C"\]`
138 Expect(failures).To(ConsistOf(MatchRegexp(expected)))
139 })
140
141 It("uses interface{} for the negated expectation slice", func() {
142 failures := InterceptGomegaFailures(func() {
143 Expect([]interface{}{1, "B"}).NotTo(ContainElements(1, "B"))
144 })
145
146 expected := `not to contain elements\n\s*<\[\]interface {} \| len:2, cap:2>: \[<int>1, <string>"B"\]`
147 Expect(failures).To(ConsistOf(MatchRegexp(expected)))
148 })
149 })
84150 })
85151 })
00 package matchers
11
22 import (
3 "errors"
34 "fmt"
45 "reflect"
56
67 "github.com/onsi/gomega/format"
7 "golang.org/x/xerrors"
88 )
99
1010 type MatchErrorMatcher struct {
2424 expected := matcher.Expected
2525
2626 if isError(expected) {
27 return reflect.DeepEqual(actualErr, expected) || xerrors.Is(actualErr, expected.(error)), nil
27 return reflect.DeepEqual(actualErr, expected) || errors.Is(actualErr, expected.(error)), nil
2828 }
2929
3030 if isString(expected) {
66 . "github.com/onsi/ginkgo"
77 . "github.com/onsi/gomega"
88 . "github.com/onsi/gomega/matchers"
9 "golang.org/x/xerrors"
109 )
1110
1211 type CustomError struct {
3332
3433 It("should succeed when any error in the chain matches the passed error", func() {
3534 innerErr := errors.New("inner error")
36 outerErr := xerrors.Errorf("outer error wrapping: %w", innerErr)
35 outerErr := fmt.Errorf("outer error wrapping: %w", innerErr)
3736
3837 Expect(outerErr).Should(MatchError(innerErr))
3938 })
00 package matchers
11
22 import (
3 "github.com/onsi/gomega/internal/oraclematcher"
43 "github.com/onsi/gomega/types"
54 )
65
2524 }
2625
2726 func (m *NotMatcher) MatchMayChangeInTheFuture(actual interface{}) bool {
28 return oraclematcher.MatchMayChangeInTheFuture(m.Matcher, actual) // just return m.Matcher's value
27 return types.MatchMayChangeInTheFuture(m.Matcher, actual) // just return m.Matcher's value
2928 }
33 "fmt"
44
55 "github.com/onsi/gomega/format"
6 "github.com/onsi/gomega/internal/oraclematcher"
76 "github.com/onsi/gomega/types"
87 )
98
5352
5453 if m.firstSuccessfulMatcher != nil {
5554 // one of the matchers succeeded.. it must be able to change in order to affect the result
56 return oraclematcher.MatchMayChangeInTheFuture(m.firstSuccessfulMatcher, actual)
55 return types.MatchMayChangeInTheFuture(m.firstSuccessfulMatcher, actual)
5756 } else {
5857 // so all matchers failed.. Any one of them changing would change the result.
5958 for _, matcher := range m.Matchers {
60 if oraclematcher.MatchMayChangeInTheFuture(matcher, actual) {
59 if types.MatchMayChangeInTheFuture(matcher, actual) {
6160 return true
6261 }
6362 }
0 package matchers
1
2 import (
3 "fmt"
4 "reflect"
5
6 "github.com/onsi/gomega/format"
7 )
8
9 type SatisfyMatcher struct {
10 Predicate interface{}
11
12 // cached type
13 predicateArgType reflect.Type
14 }
15
16 func NewSatisfyMatcher(predicate interface{}) *SatisfyMatcher {
17 if predicate == nil {
18 panic("predicate cannot be nil")
19 }
20 predicateType := reflect.TypeOf(predicate)
21 if predicateType.Kind() != reflect.Func {
22 panic("predicate must be a function")
23 }
24 if predicateType.NumIn() != 1 {
25 panic("predicate must have 1 argument")
26 }
27 if predicateType.NumOut() != 1 || predicateType.Out(0).Kind() != reflect.Bool {
28 panic("predicate must return bool")
29 }
30
31 return &SatisfyMatcher{
32 Predicate: predicate,
33 predicateArgType: predicateType.In(0),
34 }
35 }
36
37 func (m *SatisfyMatcher) Match(actual interface{}) (success bool, err error) {
38 // prepare a parameter to pass to the predicate
39 var param reflect.Value
40 if actual != nil && reflect.TypeOf(actual).AssignableTo(m.predicateArgType) {
41 // The dynamic type of actual is compatible with the predicate argument.
42 param = reflect.ValueOf(actual)
43
44 } else if actual == nil && m.predicateArgType.Kind() == reflect.Interface {
45 // The dynamic type of actual is unknown, so there's no way to make its
46 // reflect.Value. Create a nil of the predicate argument, which is known.
47 param = reflect.Zero(m.predicateArgType)
48
49 } else {
50 return false, fmt.Errorf("predicate expects '%s' but we have '%T'", m.predicateArgType, actual)
51 }
52
53 // call the predicate with `actual`
54 fn := reflect.ValueOf(m.Predicate)
55 result := fn.Call([]reflect.Value{param})
56 return result[0].Bool(), nil
57 }
58
59 func (m *SatisfyMatcher) FailureMessage(actual interface{}) (message string) {
60 return format.Message(actual, "to satisfy predicate", m.Predicate)
61 }
62
63 func (m *SatisfyMatcher) NegatedFailureMessage(actual interface{}) (message string) {
64 return format.Message(actual, "to not satisfy predicate", m.Predicate)
65 }
0 package matchers_test
1
2 import (
3 "errors"
4
5 . "github.com/onsi/ginkgo"
6 . "github.com/onsi/gomega"
7 )
8
9 var _ = Describe("SatisfyMatcher", func() {
10
11 var isEven = func(x int) bool { return x%2 == 0 }
12
13 Context("Panic if predicate is invalid", func() {
14 panicsWithPredicate := func(predicate interface{}) {
15 ExpectWithOffset(1, func() { Satisfy(predicate) }).To(Panic())
16 }
17 It("nil", func() {
18 panicsWithPredicate(nil)
19 })
20 Context("Invalid number of args, but correct return value count", func() {
21 It("zero", func() {
22 panicsWithPredicate(func() int { return 5 })
23 })
24 It("two", func() {
25 panicsWithPredicate(func(i, j int) int { return 5 })
26 })
27 })
28 Context("Invalid return types, but correct number of arguments", func() {
29 It("zero", func() {
30 panicsWithPredicate(func(i int) {})
31 })
32 It("two", func() {
33 panicsWithPredicate(func(i int) (int, int) { return 5, 6 })
34 })
35 It("invalid type", func() {
36 panicsWithPredicate(func(i int) string { return "" })
37 })
38 })
39 })
40
41 When("the actual value is incompatible", func() {
42 It("fails to pass int to func(string)", func() {
43 actual, predicate := int(0), func(string) bool { return false }
44 success, err := Satisfy(predicate).Match(actual)
45 Expect(success).To(BeFalse())
46 Expect(err).To(HaveOccurred())
47 Expect(err.Error()).To(ContainSubstring("expects 'string'"))
48 Expect(err.Error()).To(ContainSubstring("have 'int'"))
49 })
50
51 It("fails to pass string to func(interface)", func() {
52 actual, predicate := "bang", func(error) bool { return false }
53 success, err := Satisfy(predicate).Match(actual)
54 Expect(success).To(BeFalse())
55 Expect(err).To(HaveOccurred())
56 Expect(err.Error()).To(ContainSubstring("expects 'error'"))
57 Expect(err.Error()).To(ContainSubstring("have 'string'"))
58 })
59
60 It("fails to pass nil interface to func(int)", func() {
61 actual, predicate := error(nil), func(int) bool { return false }
62 success, err := Satisfy(predicate).Match(actual)
63 Expect(success).To(BeFalse())
64 Expect(err).To(HaveOccurred())
65 Expect(err.Error()).To(ContainSubstring("expects 'int'"))
66 Expect(err.Error()).To(ContainSubstring("have '<nil>'"))
67 })
68
69 It("fails to pass nil interface to func(pointer)", func() {
70 actual, predicate := error(nil), func(*string) bool { return false }
71 success, err := Satisfy(predicate).Match(actual)
72 Expect(success).To(BeFalse())
73 Expect(err).To(HaveOccurred())
74 Expect(err.Error()).To(ContainSubstring("expects '*string'"))
75 Expect(err.Error()).To(ContainSubstring("have '<nil>'"))
76 })
77 })
78
79 It("works with positive cases", func() {
80 Expect(2).To(Satisfy(isEven))
81
82 // transform expects interface
83 takesError := func(error) bool { return true }
84 Expect(nil).To(Satisfy(takesError), "handles nil actual values")
85 Expect(errors.New("abc")).To(Satisfy(takesError))
86 })
87
88 It("works with negative cases", func() {
89 Expect(1).ToNot(Satisfy(isEven))
90 })
91
92 Context("failure messages", func() {
93 When("match fails", func() {
94 It("gives a descriptive message", func() {
95 m := Satisfy(isEven)
96 Expect(m.Match(1)).To(BeFalse())
97 Expect(m.FailureMessage(1)).To(ContainSubstring("Expected\n <int>: 1\nto satisfy predicate\n <func(int) bool>: "))
98 })
99 })
100
101 When("match succeeds, but expected it to fail", func() {
102 It("gives a descriptive message", func() {
103 m := Not(Satisfy(isEven))
104 Expect(m.Match(2)).To(BeFalse())
105 Expect(m.FailureMessage(2)).To(ContainSubstring("Expected\n <int>: 2\nto not satisfy predicate\n <func(int) bool>: "))
106 })
107 })
108
109 Context("actual value is incompatible with predicate's argument type", func() {
110 It("gracefully fails", func() {
111 m := Satisfy(isEven)
112 result, err := m.Match("hi") // give it a string but predicate expects int; doesn't panic
113 Expect(result).To(BeFalse())
114 Expect(err).To(MatchError("predicate expects 'int' but we have 'string'"))
115 })
116 })
117 })
118 })
33 "fmt"
44 "reflect"
55
6 "github.com/onsi/gomega/internal/oraclematcher"
76 "github.com/onsi/gomega/types"
87 )
98
3938 }
4039
4140 func (m *WithTransformMatcher) Match(actual interface{}) (bool, error) {
42 // return error if actual's type is incompatible with Transform function's argument type
43 actualType := reflect.TypeOf(actual)
44 if !actualType.AssignableTo(m.transformArgType) {
45 return false, fmt.Errorf("Transform function expects '%s' but we have '%s'", m.transformArgType, actualType)
41 // prepare a parameter to pass to the Transform function
42 var param reflect.Value
43 if actual != nil && reflect.TypeOf(actual).AssignableTo(m.transformArgType) {
44 // The dynamic type of actual is compatible with the transform argument.
45 param = reflect.ValueOf(actual)
46
47 } else if actual == nil && m.transformArgType.Kind() == reflect.Interface {
48 // The dynamic type of actual is unknown, so there's no way to make its
49 // reflect.Value. Create a nil of the transform argument, which is known.
50 param = reflect.Zero(m.transformArgType)
51
52 } else {
53 return false, fmt.Errorf("Transform function expects '%s' but we have '%T'", m.transformArgType, actual)
4654 }
4755
4856 // call the Transform function with `actual`
4957 fn := reflect.ValueOf(m.Transform)
50 result := fn.Call([]reflect.Value{reflect.ValueOf(actual)})
58 result := fn.Call([]reflect.Value{param})
5159 m.transformedValue = result[0].Interface() // expect exactly one value
5260
5361 return m.Matcher.Match(m.transformedValue)
6775 // Querying the next matcher is fine if the transformer always will return the same value.
6876 // But if the transformer is non-deterministic and returns a different value each time, then there
6977 // is no point in querying the next matcher, since it can only comment on the last transformed value.
70 return oraclematcher.MatchMayChangeInTheFuture(m.Matcher, m.transformedValue)
78 return types.MatchMayChangeInTheFuture(m.Matcher, m.transformedValue)
7179 }
3636 })
3737 })
3838
39 When("the actual value is incompatible", func() {
40 It("fails to pass int to func(string)", func() {
41 actual, transform := int(0), func(string) int { return 0 }
42 success, err := WithTransform(transform, Equal(0)).Match(actual)
43 Expect(success).To(BeFalse())
44 Expect(err).To(HaveOccurred())
45 Expect(err.Error()).To(ContainSubstring("function expects 'string'"))
46 Expect(err.Error()).To(ContainSubstring("have 'int'"))
47 })
48
49 It("fails to pass string to func(interface)", func() {
50 actual, transform := "bang", func(error) int { return 0 }
51 success, err := WithTransform(transform, Equal(0)).Match(actual)
52 Expect(success).To(BeFalse())
53 Expect(err).To(HaveOccurred())
54 Expect(err.Error()).To(ContainSubstring("function expects 'error'"))
55 Expect(err.Error()).To(ContainSubstring("have 'string'"))
56 })
57
58 It("fails to pass nil interface to func(int)", func() {
59 actual, transform := error(nil), func(int) int { return 0 }
60 success, err := WithTransform(transform, Equal(0)).Match(actual)
61 Expect(success).To(BeFalse())
62 Expect(err).To(HaveOccurred())
63 Expect(err.Error()).To(ContainSubstring("function expects 'int'"))
64 Expect(err.Error()).To(ContainSubstring("have '<nil>'"))
65 })
66
67 It("fails to pass nil interface to func(pointer)", func() {
68 actual, transform := error(nil), func(*string) int { return 0 }
69 success, err := WithTransform(transform, Equal(0)).Match(actual)
70 Expect(success).To(BeFalse())
71 Expect(err).To(HaveOccurred())
72 Expect(err.Error()).To(ContainSubstring("function expects '*string'"))
73 Expect(err.Error()).To(ContainSubstring("have '<nil>'"))
74 })
75 })
76
3977 It("works with positive cases", func() {
4078 Expect(1).To(WithTransform(plus1, Equal(2)))
4179 Expect(1).To(WithTransform(plus1, WithTransform(plus1, Equal(3))))
5088 Expect(S{1, "hi"}).To(WithTransform(transformer, Equal("hi")))
5189
5290 // transform expects interface
53 errString := func(e error) string { return e.Error() }
91 errString := func(e error) string {
92 if e == nil {
93 return "safe"
94 }
95 return e.Error()
96 }
97 Expect(nil).To(WithTransform(errString, Equal("safe")), "handles nil actual values")
5498 Expect(errors.New("abc")).To(WithTransform(errString, Equal("abc")))
5599 })
56100
473473 func WithTransform(transform interface{}, matcher types.GomegaMatcher) types.GomegaMatcher {
474474 return matchers.NewWithTransformMatcher(transform, matcher)
475475 }
476
477 //Satisfy matches the actual value against the `predicate` function.
478 //The given predicate must be a function of one paramter that returns bool.
479 // var isEven = func(i int) bool { return i%2 == 0 }
480 // Expect(2).To(Satisfy(isEven))
481 func Satisfy(predicate interface{}) types.GomegaMatcher {
482 return matchers.NewSatisfyMatcher(predicate)
483 }
00 package types
11
2 type TWithHelper interface {
3 Helper()
4 }
2 import (
3 "time"
4 )
55
66 type GomegaFailHandler func(message string, callerSkip ...int)
77
8 type GomegaFailWrapper struct {
9 Fail GomegaFailHandler
10 TWithHelper TWithHelper
8 //A simple *testing.T interface wrapper
9 type GomegaTestingT interface {
10 Helper()
11 Fatalf(format string, args ...interface{})
1112 }
1213
13 //A simple *testing.T interface wrapper
14 type GomegaTestingT interface {
15 Fatalf(format string, args ...interface{})
14 // Gomega represents an object that can perform synchronous and assynchronous assertions with Gomega matchers
15 type Gomega interface {
16 Ω(actual interface{}, extra ...interface{}) Assertion
17 Expect(actual interface{}, extra ...interface{}) Assertion
18 ExpectWithOffset(offset int, actual interface{}, extra ...interface{}) Assertion
19
20 Eventually(actual interface{}, intervals ...interface{}) AsyncAssertion
21 EventuallyWithOffset(offset int, actual interface{}, intervals ...interface{}) AsyncAssertion
22
23 Consistently(actual interface{}, intervals ...interface{}) AsyncAssertion
24 ConsistentlyWithOffset(offset int, actual interface{}, intervals ...interface{}) AsyncAssertion
25
26 SetDefaultEventuallyTimeout(time.Duration)
27 SetDefaultEventuallyPollingInterval(time.Duration)
28 SetDefaultConsistentlyDuration(time.Duration)
29 SetDefaultConsistentlyPollingInterval(time.Duration)
1630 }
1731
1832 //All Gomega matchers must implement the GomegaMatcher interface
2337 FailureMessage(actual interface{}) (message string)
2438 NegatedFailureMessage(actual interface{}) (message string)
2539 }
40
41 /*
42 GomegaMatchers that also match the OracleMatcher interface can convey information about
43 whether or not their result will change upon future attempts.
44
45 This allows `Eventually` and `Consistently` to short circuit if success becomes impossible.
46
47 For example, a process' exit code can never change. So, gexec's Exit matcher returns `true`
48 for `MatchMayChangeInTheFuture` until the process exits, at which point it returns `false` forevermore.
49 */
50 type OracleMatcher interface {
51 MatchMayChangeInTheFuture(actual interface{}) bool
52 }
53
54 func MatchMayChangeInTheFuture(matcher GomegaMatcher, value interface{}) bool {
55 oracleMatcher, ok := matcher.(OracleMatcher)
56 if !ok {
57 return true
58 }
59
60 return oracleMatcher.MatchMayChangeInTheFuture(value)
61 }
62
63 // AsyncAssertions are returned by Eventually and Consistently and enable matchers to be polled repeatedly to ensure
64 // they are eventually satisfied
65 type AsyncAssertion interface {
66 Should(matcher GomegaMatcher, optionalDescription ...interface{}) bool
67 ShouldNot(matcher GomegaMatcher, optionalDescription ...interface{}) bool
68 }
69
70 // Assertions are returned by Ω and Expect and enable assertions against Gomega matchers
71 type Assertion interface {
72 Should(matcher GomegaMatcher, optionalDescription ...interface{}) bool
73 ShouldNot(matcher GomegaMatcher, optionalDescription ...interface{}) bool
74
75 To(matcher GomegaMatcher, optionalDescription ...interface{}) bool
76 ToNot(matcher GomegaMatcher, optionalDescription ...interface{}) bool
77 NotTo(matcher GomegaMatcher, optionalDescription ...interface{}) bool
78 }