aboutsummaryrefslogtreecommitdiff
path: root/vendor/github.com/onsi/gomega/internal/assertion.go
blob: 7b7bdd149ae8b23d2b4b24027a77e74239592e38 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
package internal

import (
	"fmt"
	"reflect"

	"github.com/onsi/gomega/types"
)

type Assertion struct {
	actuals     []interface{} // actual value plus all extra values
	actualIndex int           // value to pass to the matcher
	vet         vetinari      // the vet to call before calling Gomega matcher
	offset      int
	g           *Gomega
}

// ...obligatory discworld reference, as "vetineer" doesn't sound ... quite right.
type vetinari func(assertion *Assertion, optionalDescription ...interface{}) bool

func NewAssertion(actualInput interface{}, g *Gomega, offset int, extra ...interface{}) *Assertion {
	return &Assertion{
		actuals:     append([]interface{}{actualInput}, extra...),
		actualIndex: 0,
		vet:         (*Assertion).vetActuals,
		offset:      offset,
		g:           g,
	}
}

func (assertion *Assertion) WithOffset(offset int) types.Assertion {
	assertion.offset = offset
	return assertion
}

func (assertion *Assertion) Error() types.Assertion {
	return &Assertion{
		actuals:     assertion.actuals,
		actualIndex: len(assertion.actuals) - 1,
		vet:         (*Assertion).vetError,
		offset:      assertion.offset,
		g:           assertion.g,
	}
}

func (assertion *Assertion) Should(matcher types.GomegaMatcher, optionalDescription ...interface{}) bool {
	assertion.g.THelper()
	vetOptionalDescription("Assertion", optionalDescription...)
	return assertion.vet(assertion, optionalDescription...) && assertion.match(matcher, true, optionalDescription...)
}

func (assertion *Assertion) ShouldNot(matcher types.GomegaMatcher, optionalDescription ...interface{}) bool {
	assertion.g.THelper()
	vetOptionalDescription("Assertion", optionalDescription...)
	return assertion.vet(assertion, optionalDescription...) && assertion.match(matcher, false, optionalDescription...)
}

func (assertion *Assertion) To(matcher types.GomegaMatcher, optionalDescription ...interface{}) bool {
	assertion.g.THelper()
	vetOptionalDescription("Assertion", optionalDescription...)
	return assertion.vet(assertion, optionalDescription...) && assertion.match(matcher, true, optionalDescription...)
}

func (assertion *Assertion) ToNot(matcher types.GomegaMatcher, optionalDescription ...interface{}) bool {
	assertion.g.THelper()
	vetOptionalDescription("Assertion", optionalDescription...)
	return assertion.vet(assertion, optionalDescription...) && assertion.match(matcher, false, optionalDescription...)
}

func (assertion *Assertion) NotTo(matcher types.GomegaMatcher, optionalDescription ...interface{}) bool {
	assertion.g.THelper()
	vetOptionalDescription("Assertion", optionalDescription...)
	return assertion.vet(assertion, optionalDescription...) && assertion.match(matcher, false, optionalDescription...)
}

func (assertion *Assertion) buildDescription(optionalDescription ...interface{}) string {
	switch len(optionalDescription) {
	case 0:
		return ""
	case 1:
		if describe, ok := optionalDescription[0].(func() string); ok {
			return describe() + "\n"
		}
	}
	return fmt.Sprintf(optionalDescription[0].(string), optionalDescription[1:]...) + "\n"
}

func (assertion *Assertion) match(matcher types.GomegaMatcher, desiredMatch bool, optionalDescription ...interface{}) bool {
	actualInput := assertion.actuals[assertion.actualIndex]
	matches, err := matcher.Match(actualInput)
	assertion.g.THelper()
	if err != nil {
		description := assertion.buildDescription(optionalDescription...)
		assertion.g.Fail(description+err.Error(), 2+assertion.offset)
		return false
	}
	if matches != desiredMatch {
		var message string
		if desiredMatch {
			message = matcher.FailureMessage(actualInput)
		} else {
			message = matcher.NegatedFailureMessage(actualInput)
		}
		description := assertion.buildDescription(optionalDescription...)
		assertion.g.Fail(description+message, 2+assertion.offset)
		return false
	}

	return true
}

// vetActuals vets the actual values, with the (optional) exception of a
// specific value, such as the first value in case non-error assertions, or the
// last value in case of Error()-based assertions.
func (assertion *Assertion) vetActuals(optionalDescription ...interface{}) bool {
	success, message := vetActuals(assertion.actuals, assertion.actualIndex)
	if success {
		return true
	}

	description := assertion.buildDescription(optionalDescription...)
	assertion.g.THelper()
	assertion.g.Fail(description+message, 2+assertion.offset)
	return false
}

// vetError vets the actual values, except for the final error value, in case
// the final error value is non-zero. Otherwise, it doesn't vet the actual
// values, as these are allowed to take on any values unless there is a non-zero
// error value.
func (assertion *Assertion) vetError(optionalDescription ...interface{}) bool {
	if err := assertion.actuals[assertion.actualIndex]; err != nil {
		// Go error result idiom: all other actual values must be zero values.
		return assertion.vetActuals(optionalDescription...)
	}
	return true
}

// vetActuals vets a slice of actual values, optionally skipping a particular
// value slice element, such as the first or last value slice element.
func vetActuals(actuals []interface{}, skipIndex int) (bool, string) {
	for i, actual := range actuals {
		if i == skipIndex {
			continue
		}
		if actual != nil {
			zeroValue := reflect.Zero(reflect.TypeOf(actual)).Interface()
			if !reflect.DeepEqual(zeroValue, actual) {
				message := fmt.Sprintf("Unexpected non-nil/non-zero argument at index %d:\n\t<%T>: %#v", i, actual, actual)
				return false, message
			}
		}
	}
	return true, ""
}