parent
e9cbaff4f6
commit
3570f78f69
@ -0,0 +1,18 @@
|
||||
Copyright (c) 2013, Space Monkey, Inc.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
this software and associated documentation files (the "Software"), to deal in
|
||||
the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||
the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
@ -0,0 +1,89 @@
|
||||
gls
|
||||
===
|
||||
|
||||
Goroutine local storage
|
||||
|
||||
### IMPORTANT NOTE ###
|
||||
|
||||
It is my duty to point you to https://blog.golang.org/context, which is how
|
||||
Google solves all of the problems you'd perhaps consider using this package
|
||||
for at scale.
|
||||
|
||||
One downside to Google's approach is that *all* of your functions must have
|
||||
a new first argument, but after clearing that hurdle everything else is much
|
||||
better.
|
||||
|
||||
If you aren't interested in this warning, read on.
|
||||
|
||||
### Huhwaht? Why? ###
|
||||
|
||||
Every so often, a thread shows up on the
|
||||
[golang-nuts](https://groups.google.com/d/forum/golang-nuts) asking for some
|
||||
form of goroutine-local-storage, or some kind of goroutine id, or some kind of
|
||||
context. There are a few valid use cases for goroutine-local-storage, one of
|
||||
the most prominent being log line context. One poster was interested in being
|
||||
able to log an HTTP request context id in every log line in the same goroutine
|
||||
as the incoming HTTP request, without having to change every library and
|
||||
function call he was interested in logging.
|
||||
|
||||
This would be pretty useful. Provided that you could get some kind of
|
||||
goroutine-local-storage, you could call
|
||||
[log.SetOutput](http://golang.org/pkg/log/#SetOutput) with your own logging
|
||||
writer that checks goroutine-local-storage for some context information and
|
||||
adds that context to your log lines.
|
||||
|
||||
But alas, Andrew Gerrand's typically diplomatic answer to the question of
|
||||
goroutine-local variables was:
|
||||
|
||||
> We wouldn't even be having this discussion if thread local storage wasn't
|
||||
> useful. But every feature comes at a cost, and in my opinion the cost of
|
||||
> threadlocals far outweighs their benefits. They're just not a good fit for
|
||||
> Go.
|
||||
|
||||
So, yeah, that makes sense. That's a pretty good reason for why the language
|
||||
won't support a specific and (relatively) unuseful feature that requires some
|
||||
runtime changes, just for the sake of a little bit of log improvement.
|
||||
|
||||
But does Go require runtime changes?
|
||||
|
||||
### How it works ###
|
||||
|
||||
Go has pretty fantastic introspective and reflective features, but one thing Go
|
||||
doesn't give you is any kind of access to the stack pointer, or frame pointer,
|
||||
or goroutine id, or anything contextual about your current stack. It gives you
|
||||
access to your list of callers, but only along with program counters, which are
|
||||
fixed at compile time.
|
||||
|
||||
But it does give you the stack.
|
||||
|
||||
So, we define 16 special functions and embed base-16 tags into the stack using
|
||||
the call order of those 16 functions. Then, we can read our tags back out of
|
||||
the stack looking at the callers list.
|
||||
|
||||
We then use these tags as an index into a traditional map for implementing
|
||||
this library.
|
||||
|
||||
### What are people saying? ###
|
||||
|
||||
"Wow, that's horrifying."
|
||||
|
||||
"This is the most terrible thing I have seen in a very long time."
|
||||
|
||||
"Where is it getting a context from? Is this serializing all the requests?
|
||||
What the heck is the client being bound to? What are these tags? Why does he
|
||||
need callers? Oh god no. No no no."
|
||||
|
||||
### Docs ###
|
||||
|
||||
Please see the docs at http://godoc.org/github.com/jtolds/gls
|
||||
|
||||
### Related ###
|
||||
|
||||
If you're okay relying on the string format of the current runtime stacktrace
|
||||
including a unique goroutine id (not guaranteed by the spec or anything, but
|
||||
very unlikely to change within a Go release), you might be able to squeeze
|
||||
out a bit more performance by using this similar library, inspired by some
|
||||
code Brad Fitzpatrick wrote for debugging his HTTP/2 library:
|
||||
https://github.com/tylerb/gls (in contrast, jtolds/gls doesn't require
|
||||
any knowledge of the string format of the runtime stacktrace, which
|
||||
probably adds unnecessary overhead).
|
@ -0,0 +1,150 @@
|
||||
// Package gls implements goroutine-local storage.
|
||||
package gls
|
||||
|
||||
import (
|
||||
"runtime"
|
||||
"sync"
|
||||
)
|
||||
|
||||
const (
|
||||
maxCallers = 64
|
||||
)
|
||||
|
||||
var (
|
||||
stackTagPool = &idPool{}
|
||||
mgrRegistry = make(map[*ContextManager]bool)
|
||||
mgrRegistryMtx sync.RWMutex
|
||||
)
|
||||
|
||||
// Values is simply a map of key types to value types. Used by SetValues to
|
||||
// set multiple values at once.
|
||||
type Values map[interface{}]interface{}
|
||||
|
||||
func currentStack(skip int) []uintptr {
|
||||
stack := make([]uintptr, maxCallers)
|
||||
return stack[:runtime.Callers(2+skip, stack)]
|
||||
}
|
||||
|
||||
// ContextManager is the main entrypoint for interacting with
|
||||
// Goroutine-local-storage. You can have multiple independent ContextManagers
|
||||
// at any given time. ContextManagers are usually declared globally for a given
|
||||
// class of context variables. You should use NewContextManager for
|
||||
// construction.
|
||||
type ContextManager struct {
|
||||
mtx sync.RWMutex
|
||||
values map[uint]Values
|
||||
}
|
||||
|
||||
// NewContextManager returns a brand new ContextManager. It also registers the
|
||||
// new ContextManager in the ContextManager registry which is used by the Go
|
||||
// method. ContextManagers are typically defined globally at package scope.
|
||||
func NewContextManager() *ContextManager {
|
||||
mgr := &ContextManager{values: make(map[uint]Values)}
|
||||
mgrRegistryMtx.Lock()
|
||||
defer mgrRegistryMtx.Unlock()
|
||||
mgrRegistry[mgr] = true
|
||||
return mgr
|
||||
}
|
||||
|
||||
// Unregister removes a ContextManager from the global registry, used by the
|
||||
// Go method. Only intended for use when you're completely done with a
|
||||
// ContextManager. Use of Unregister at all is rare.
|
||||
func (m *ContextManager) Unregister() {
|
||||
mgrRegistryMtx.Lock()
|
||||
defer mgrRegistryMtx.Unlock()
|
||||
delete(mgrRegistry, m)
|
||||
}
|
||||
|
||||
// SetValues takes a collection of values and a function to call for those
|
||||
// values to be set in. Anything further down the stack will have the set
|
||||
// values available through GetValue. SetValues will add new values or replace
|
||||
// existing values of the same key and will not mutate or change values for
|
||||
// previous stack frames.
|
||||
// SetValues is slow (makes a copy of all current and new values for the new
|
||||
// gls-context) in order to reduce the amount of lookups GetValue requires.
|
||||
func (m *ContextManager) SetValues(new_values Values, context_call func()) {
|
||||
if len(new_values) == 0 {
|
||||
context_call()
|
||||
return
|
||||
}
|
||||
|
||||
tags := readStackTags(currentStack(1))
|
||||
|
||||
m.mtx.Lock()
|
||||
values := new_values
|
||||
for _, tag := range tags {
|
||||
if existing_values, ok := m.values[tag]; ok {
|
||||
// oh, we found existing values, let's make a copy
|
||||
values = make(Values, len(existing_values)+len(new_values))
|
||||
for key, val := range existing_values {
|
||||
values[key] = val
|
||||
}
|
||||
for key, val := range new_values {
|
||||
values[key] = val
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
new_tag := stackTagPool.Acquire()
|
||||
m.values[new_tag] = values
|
||||
m.mtx.Unlock()
|
||||
defer func() {
|
||||
m.mtx.Lock()
|
||||
delete(m.values, new_tag)
|
||||
m.mtx.Unlock()
|
||||
stackTagPool.Release(new_tag)
|
||||
}()
|
||||
|
||||
addStackTag(new_tag, context_call)
|
||||
}
|
||||
|
||||
// GetValue will return a previously set value, provided that the value was set
|
||||
// by SetValues somewhere higher up the stack. If the value is not found, ok
|
||||
// will be false.
|
||||
func (m *ContextManager) GetValue(key interface{}) (value interface{}, ok bool) {
|
||||
|
||||
tags := readStackTags(currentStack(1))
|
||||
m.mtx.RLock()
|
||||
defer m.mtx.RUnlock()
|
||||
for _, tag := range tags {
|
||||
if values, ok := m.values[tag]; ok {
|
||||
value, ok := values[key]
|
||||
return value, ok
|
||||
}
|
||||
}
|
||||
return "", false
|
||||
}
|
||||
|
||||
func (m *ContextManager) getValues() Values {
|
||||
tags := readStackTags(currentStack(2))
|
||||
m.mtx.RLock()
|
||||
defer m.mtx.RUnlock()
|
||||
for _, tag := range tags {
|
||||
if values, ok := m.values[tag]; ok {
|
||||
return values
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Go preserves ContextManager values and Goroutine-local-storage across new
|
||||
// goroutine invocations. The Go method makes a copy of all existing values on
|
||||
// all registered context managers and makes sure they are still set after
|
||||
// kicking off the provided function in a new goroutine. If you don't use this
|
||||
// Go method instead of the standard 'go' keyword, you will lose values in
|
||||
// ContextManagers, as goroutines have brand new stacks.
|
||||
func Go(cb func()) {
|
||||
mgrRegistryMtx.RLock()
|
||||
defer mgrRegistryMtx.RUnlock()
|
||||
|
||||
for mgr, _ := range mgrRegistry {
|
||||
values := mgr.getValues()
|
||||
if len(values) > 0 {
|
||||
mgr_copy := mgr
|
||||
cb_copy := cb
|
||||
cb = func() { mgr_copy.SetValues(values, cb_copy) }
|
||||
}
|
||||
}
|
||||
|
||||
go cb()
|
||||
}
|
@ -0,0 +1,13 @@
|
||||
package gls
|
||||
|
||||
var (
|
||||
symPool = &idPool{}
|
||||
)
|
||||
|
||||
// ContextKey is a throwaway value you can use as a key to a ContextManager
|
||||
type ContextKey struct{ id uint }
|
||||
|
||||
// GenSym will return a brand new, never-before-used ContextKey
|
||||
func GenSym() ContextKey {
|
||||
return ContextKey{id: symPool.Acquire()}
|
||||
}
|
@ -0,0 +1,34 @@
|
||||
package gls
|
||||
|
||||
// though this could probably be better at keeping ids smaller, the goal of
|
||||
// this class is to keep a registry of the smallest unique integer ids
|
||||
// per-process possible
|
||||
|
||||
import (
|
||||
"sync"
|
||||
)
|
||||
|
||||
type idPool struct {
|
||||
mtx sync.Mutex
|
||||
released []uint
|
||||
max_id uint
|
||||
}
|
||||
|
||||
func (p *idPool) Acquire() (id uint) {
|
||||
p.mtx.Lock()
|
||||
defer p.mtx.Unlock()
|
||||
if len(p.released) > 0 {
|
||||
id = p.released[len(p.released)-1]
|
||||
p.released = p.released[:len(p.released)-1]
|
||||
return id
|
||||
}
|
||||
id = p.max_id
|
||||
p.max_id++
|
||||
return id
|
||||
}
|
||||
|
||||
func (p *idPool) Release(id uint) {
|
||||
p.mtx.Lock()
|
||||
defer p.mtx.Unlock()
|
||||
p.released = append(p.released, id)
|
||||
}
|
@ -0,0 +1,93 @@
|
||||
package gls
|
||||
|
||||
// so, basically, we're going to encode integer tags in base-16 on the stack
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"runtime"
|
||||
)
|
||||
|
||||
const (
|
||||
bitWidth = 4
|
||||
)
|
||||
|
||||
func addStackTag(tag uint, context_call func()) {
|
||||
if context_call == nil {
|
||||
return
|
||||
}
|
||||
markS(tag, context_call)
|
||||
}
|
||||
|
||||
func markS(tag uint, cb func()) { _m(tag, cb) }
|
||||
func mark0(tag uint, cb func()) { _m(tag, cb) }
|
||||
func mark1(tag uint, cb func()) { _m(tag, cb) }
|
||||
func mark2(tag uint, cb func()) { _m(tag, cb) }
|
||||
func mark3(tag uint, cb func()) { _m(tag, cb) }
|
||||
func mark4(tag uint, cb func()) { _m(tag, cb) }
|
||||
func mark5(tag uint, cb func()) { _m(tag, cb) }
|
||||
func mark6(tag uint, cb func()) { _m(tag, cb) }
|
||||
func mark7(tag uint, cb func()) { _m(tag, cb) }
|
||||
func mark8(tag uint, cb func()) { _m(tag, cb) }
|
||||
func mark9(tag uint, cb func()) { _m(tag, cb) }
|
||||
func markA(tag uint, cb func()) { _m(tag, cb) }
|
||||
func markB(tag uint, cb func()) { _m(tag, cb) }
|
||||
func markC(tag uint, cb func()) { _m(tag, cb) }
|
||||
func markD(tag uint, cb func()) { _m(tag, cb) }
|
||||
func markE(tag uint, cb func()) { _m(tag, cb) }
|
||||
func markF(tag uint, cb func()) { _m(tag, cb) }
|
||||
|
||||
var pc_lookup = make(map[uintptr]int8, 17)
|
||||
var mark_lookup [16]func(uint, func())
|
||||
|
||||
func init() {
|
||||
setEntries := func(f func(uint, func()), v int8) {
|
||||
pc_lookup[reflect.ValueOf(f).Pointer()] = v
|
||||
if v >= 0 {
|
||||
mark_lookup[v] = f
|
||||
}
|
||||
}
|
||||
setEntries(markS, -0x1)
|
||||
setEntries(mark0, 0x0)
|
||||
setEntries(mark1, 0x1)
|
||||
setEntries(mark2, 0x2)
|
||||
setEntries(mark3, 0x3)
|
||||
setEntries(mark4, 0x4)
|
||||
setEntries(mark5, 0x5)
|
||||
setEntries(mark6, 0x6)
|
||||
setEntries(mark7, 0x7)
|
||||
setEntries(mark8, 0x8)
|
||||
setEntries(mark9, 0x9)
|
||||
setEntries(markA, 0xa)
|
||||
setEntries(markB, 0xb)
|
||||
setEntries(markC, 0xc)
|
||||
setEntries(markD, 0xd)
|
||||
setEntries(markE, 0xe)
|
||||
setEntries(markF, 0xf)
|
||||
}
|
||||
|
||||
func _m(tag_remainder uint, cb func()) {
|
||||
if tag_remainder == 0 {
|
||||
cb()
|
||||
} else {
|
||||
mark_lookup[tag_remainder&0xf](tag_remainder>>bitWidth, cb)
|
||||
}
|
||||
}
|
||||
|
||||
func readStackTags(stack []uintptr) (tags []uint) {
|
||||
var current_tag uint
|
||||
for _, pc := range stack {
|
||||
pc = runtime.FuncForPC(pc).Entry()
|
||||
val, ok := pc_lookup[pc]
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
if val < 0 {
|
||||
tags = append(tags, current_tag)
|
||||
current_tag = 0
|
||||
continue
|
||||
}
|
||||
current_tag <<= bitWidth
|
||||
current_tag += uint(val)
|
||||
}
|
||||
return
|
||||
}
|
@ -0,0 +1,2 @@
|
||||
.DS_Store
|
||||
Thumbs.db
|
@ -0,0 +1,14 @@
|
||||
language: go
|
||||
|
||||
go:
|
||||
- 1.2
|
||||
- 1.3
|
||||
- 1.4
|
||||
- 1.5
|
||||
|
||||
install:
|
||||
- go get -t ./...
|
||||
|
||||
script: go test -v
|
||||
|
||||
sudo: false
|
@ -0,0 +1,23 @@
|
||||
Copyright (c) 2015 SmartyStreets, LLC
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
||||
NOTE: Various optional and subordinate components carry their own licensing
|
||||
requirements and restrictions. Use of those components is subject to the terms
|
||||
and conditions outlined the respective license of each component.
|
@ -0,0 +1,554 @@
|
||||
# assertions
|
||||
--
|
||||
import "github.com/smartystreets/assertions"
|
||||
|
||||
Package assertions contains the implementations for all assertions which are
|
||||
referenced in goconvey's `convey` package
|
||||
(github.com/smartystreets/goconvey/convey) for use with the So(...) method. They
|
||||
can also be used in traditional Go test functions and even in applicaitons.
|
||||
|
||||
## Usage
|
||||
|
||||
#### func GoConveyMode
|
||||
|
||||
```go
|
||||
func GoConveyMode(yes bool)
|
||||
```
|
||||
GoConveyMode provides control over JSON serialization of failures. When using
|
||||
the assertions in this package from the convey package JSON results are very
|
||||
helpful and can be rendered in a DIFF view. In that case, this function will be
|
||||
called with a true value to enable the JSON serialization. By default, the
|
||||
assertions in this package will not serializer a JSON result, making standalone
|
||||
ussage more convenient.
|
||||
|
||||
#### func ShouldAlmostEqual
|
||||
|
||||
```go
|
||||
func ShouldAlmostEqual(actual interface{}, expected ...interface{}) string
|
||||
```
|
||||
ShouldAlmostEqual makes sure that two parameters are close enough to being
|
||||
equal. The acceptable delta may be specified with a third argument, or a very
|
||||
small default delta will be used.
|
||||
|
||||
#### func ShouldBeBetween
|
||||
|
||||
```go
|
||||
func ShouldBeBetween(actual interface{}, expected ...interface{}) string
|
||||
```
|
||||
ShouldBeBetween receives exactly three parameters: an actual value, a lower
|
||||
bound, and an upper bound. It ensures that the actual value is between both
|
||||
bounds (but not equal to either of them).
|
||||
|
||||
#### func ShouldBeBetweenOrEqual
|
||||
|
||||
```go
|
||||
func ShouldBeBetweenOrEqual(actual interface{}, expected ...interface{}) string
|
||||
```
|
||||
ShouldBeBetweenOrEqual receives exactly three parameters: an actual value, a
|
||||
lower bound, and an upper bound. It ensures that the actual value is between
|
||||
both bounds or equal to one of them.
|
||||
|
||||
#### func ShouldBeBlank
|
||||
|
||||
```go
|
||||
func ShouldBeBlank(actual interface{}, expected ...interface{}) string
|
||||
```
|
||||
ShouldBeBlank receives exactly 1 string parameter and ensures that it is equal
|
||||
to "".
|
||||
|
||||
#### func ShouldBeChronological
|
||||
|
||||
```go
|
||||
func ShouldBeChronological(actual interface{}, expected ...interface{}) string
|
||||
```
|
||||
ShouldBeChronological receives a []time.Time slice and asserts that the are in
|
||||
chronological order starting with the first time.Time as the earliest.
|
||||
|
||||
#### func ShouldBeEmpty
|
||||
|
||||
```go
|
||||
func ShouldBeEmpty(actual interface{}, expected ...interface{}) string
|
||||
```
|
||||
ShouldBeEmpty receives a single parameter (actual) and determines whether or not
|
||||
calling len(actual) would return `0`. It obeys the rules specified by the len
|
||||
function for determining length: http://golang.org/pkg/builtin/#len
|
||||
|
||||
#### func ShouldBeFalse
|
||||
|
||||
```go
|
||||
func ShouldBeFalse(actual interface{}, expected ...interface{}) string
|
||||
```
|
||||
ShouldBeFalse receives a single parameter and ensures that it is false.
|
||||
|
||||
#### func ShouldBeGreaterThan
|
||||
|
||||
```go
|
||||
func ShouldBeGreaterThan(actual interface{}, expected ...interface{}) string
|
||||
```
|
||||
ShouldBeGreaterThan receives exactly two parameters and ensures that the first
|
||||
is greater than the second.
|
||||
|
||||
#### func ShouldBeGreaterThanOrEqualTo
|
||||
|
||||
```go
|
||||
func ShouldBeGreaterThanOrEqualTo(actual interface{}, expected ...interface{}) string
|
||||
```
|
||||
ShouldBeGreaterThanOrEqualTo receives exactly two parameters and ensures that
|
||||
the first is greater than or equal to the second.
|
||||
|
||||
#### func ShouldBeIn
|
||||
|
||||
```go
|
||||
func ShouldBeIn(actual interface{}, expected ...interface{}) string
|
||||
```
|
||||
ShouldBeIn receives at least 2 parameters. The first is a proposed member of the
|
||||
collection that is passed in either as the second parameter, or of the
|
||||
collection that is comprised of all the remaining parameters. This assertion
|
||||
ensures that the proposed member is in the collection (using ShouldEqual).
|
||||
|
||||
#### func ShouldBeLessThan
|
||||
|
||||
```go
|
||||
func ShouldBeLessThan(actual interface{}, expected ...interface{}) string
|
||||
```
|
||||
ShouldBeLessThan receives exactly two parameters and ensures that the first is
|
||||
less than the second.
|
||||
|
||||
#### func ShouldBeLessThanOrEqualTo
|
||||
|
||||
```go
|
||||
func ShouldBeLessThanOrEqualTo(actual interface{}, expected ...interface{}) string
|
||||
```
|
||||
ShouldBeLessThan receives exactly two parameters and ensures that the first is
|
||||
less than or equal to the second.
|
||||
|
||||
#### func ShouldBeNil
|
||||
|
||||
```go
|
||||
func ShouldBeNil(actual interface{}, expected ...interface{}) string
|
||||
```
|
||||
ShouldBeNil receives a single parameter and ensures that it is nil.
|
||||
|
||||
#### func ShouldBeTrue
|
||||
|
||||
```go
|
||||
func ShouldBeTrue(actual interface{}, expected ...interface{}) string
|
||||
```
|
||||
ShouldBeTrue receives a single parameter and ensures that it is true.
|
||||
|
||||
#### func ShouldBeZeroValue
|
||||
|
||||
```go
|
||||
func ShouldBeZeroValue(actual interface{}, expected ...interface{}) string
|
||||
```
|
||||
ShouldBeZeroValue receives a single parameter and ensures that it is the Go
|
||||
equivalent of the default value, or "zero" value.
|
||||
|
||||
#### func ShouldContain
|
||||
|
||||
```go
|
||||
func ShouldContain(actual interface{}, expected ...interface{}) string
|
||||
```
|
||||
ShouldContain receives exactly two parameters. The first is a slice and the
|
||||
second is a proposed member. Membership is determined using ShouldEqual.
|
||||
|
||||
#### func ShouldContainKey
|
||||
|
||||
```go
|
||||
func ShouldContainKey(actual interface{}, expected ...interface{}) string
|
||||
```
|
||||
ShouldContainKey receives exactly two parameters. The first is a map and the
|
||||
second is a proposed key. Keys are compared with a simple '=='.
|
||||
|
||||
#### func ShouldContainSubstring
|
||||
|
||||
```go
|
||||
func ShouldContainSubstring(actual interface{}, expected ...interface{}) string
|
||||
```
|
||||
ShouldContainSubstring receives exactly 2 string parameters and ensures that the
|
||||
first contains the second as a substring.
|
||||
|
||||
#### func ShouldEndWith
|
||||
|
||||
```go
|
||||
func ShouldEndWith(actual interface{}, expected ...interface{}) string
|
||||
```
|
||||
ShouldEndWith receives exactly 2 string parameters and ensures that the first
|
||||
ends with the second.
|
||||
|
||||
#### func ShouldEqual
|
||||
|
||||
```go
|
||||
func ShouldEqual(actual interface{}, expected ...interface{}) string
|
||||
```
|
||||
ShouldEqual receives exactly two parameters and does an equality check.
|
||||
|
||||
#### func ShouldEqualTrimSpace
|
||||
|
||||
```go
|
||||
func ShouldEqualTrimSpace(actual interface{}, expected ...interface{}) string
|
||||
```
|
||||
ShouldEqualTrimSpace receives exactly 2 string parameters and ensures that the
|
||||
first is equal to the second after removing all leading and trailing whitespace
|
||||
using strings.TrimSpace(first).
|
||||
|
||||
#### func ShouldEqualWithout
|
||||
|
||||
```go
|
||||
func ShouldEqualWithout(actual interface{}, expected ...interface{}) string
|
||||
```
|
||||
ShouldEqualWithout receives exactly 3 string parameters and ensures that the
|
||||
first is equal to the second after removing all instances of the third from the
|
||||
first using strings.Replace(first, third, "", -1).
|
||||
|
||||
#### func ShouldHappenAfter
|
||||
|
||||
```go
|
||||
func ShouldHappenAfter(actual interface{}, expected ...interface{}) string
|
||||
```
|
||||
ShouldHappenAfter receives exactly 2 time.Time arguments and asserts that the
|
||||
first happens after the second.
|
||||
|
||||
#### func ShouldHappenBefore
|
||||
|
||||
```go
|
||||
func ShouldHappenBefore(actual interface{}, expected ...interface{}) string
|
||||
```
|
||||
ShouldHappenBefore receives exactly 2 time.Time arguments and asserts that the
|
||||
first happens before the second.
|
||||
|
||||
#### func ShouldHappenBetween
|
||||
|
||||
```go
|
||||
func ShouldHappenBetween(actual interface{}, expected ...interface{}) string
|
||||
```
|
||||
ShouldHappenBetween receives exactly 3 time.Time arguments and asserts that the
|
||||
first happens between (not on) the second and third.
|
||||
|
||||
#### func ShouldHappenOnOrAfter
|
||||
|
||||
```go
|
||||
func ShouldHappenOnOrAfter(actual interface{}, expected ...interface{}) string
|
||||
```
|
||||
ShouldHappenOnOrAfter receives exactly 2 time.Time arguments and asserts that
|
||||
the first happens on or after the second.
|
||||
|
||||
#### func ShouldHappenOnOrBefore
|
||||
|
||||
```go
|
||||
func ShouldHappenOnOrBefore(actual interface{}, expected ...interface{}) string
|
||||
```
|
||||
ShouldHappenOnOrBefore receives exactly 2 time.Time arguments and asserts that
|
||||
the first happens on or before the second.
|
||||
|
||||
#### func ShouldHappenOnOrBetween
|
||||
|
||||
```go
|
||||
func ShouldHappenOnOrBetween(actual interface{}, expected ...interface{}) string
|
||||
```
|
||||
ShouldHappenOnOrBetween receives exactly 3 time.Time arguments and asserts that
|
||||
the first happens between or on the second and third.
|
||||
|
||||
#### func ShouldHappenWithin
|
||||
|
||||
```go
|
||||
func ShouldHappenWithin(actual interface{}, expected ...interface{}) string
|
||||
```
|
||||
ShouldHappenWithin receives a time.Time, a time.Duration, and a time.Time (3
|
||||
arguments) and asserts that the first time.Time happens within or on the
|
||||
duration specified relative to the other time.Time.
|
||||
|
||||
#### func ShouldHaveLength
|
||||
|
||||
```go
|
||||
func ShouldHaveLength(actual interface{}, expected ...interface{}) string
|
||||
```
|
||||
ShouldHaveLength receives a collection and a positive integer and asserts that
|
||||
the length of the collection is equal to the integer provided.
|
||||
|
||||
#### func ShouldHaveSameTypeAs
|
||||
|
||||
```go
|
||||
func ShouldHaveSameTypeAs(actual interface{}, expected ...interface{}) string
|
||||
```
|
||||
ShouldHaveSameTypeAs receives exactly two parameters and compares their
|
||||
underlying types for equality.
|
||||
|
||||
#### func ShouldImplement
|
||||
|
||||
```go
|
||||
func ShouldImplement(actual interface{}, expectedList ...interface{}) string
|
||||
```
|
||||
ShouldImplement receives exactly two parameters and ensures that the first
|
||||
implements the interface type of the second.
|
||||
|
||||
#### func ShouldNotAlmostEqual
|
||||
|
||||
```go
|
||||
func ShouldNotAlmostEqual(actual interface{}, expected ...interface{}) string
|
||||
```
|
||||
ShouldNotAlmostEqual is the inverse of ShouldAlmostEqual
|
||||
|
||||
#### func ShouldNotBeBetween
|
||||
|
||||
```go
|
||||
func ShouldNotBeBetween(actual interface{}, expected ...interface{}) string
|
||||
```
|
||||
ShouldNotBeBetween receives exactly three parameters: an actual value, a lower
|
||||
bound, and an upper bound. It ensures that the actual value is NOT between both
|
||||
bounds.
|
||||
|
||||
#### func ShouldNotBeBetweenOrEqual
|
||||
|
||||
```go
|
||||
func ShouldNotBeBetweenOrEqual(actual interface{}, expected ...interface{}) string
|
||||
```
|
||||
ShouldNotBeBetweenOrEqual receives exactly three parameters: an actual value, a
|
||||
lower bound, and an upper bound. It ensures that the actual value is nopt
|
||||
between the bounds nor equal to either of them.
|
||||
|
||||
#### func ShouldNotBeBlank
|
||||
|
||||
```go
|
||||
func ShouldNotBeBlank(actual interface{}, expected ...interface{}) string
|
||||
```
|
||||
ShouldNotBeBlank receives exactly 1 string parameter and ensures that it is
|
||||
equal to "".
|
||||
|
||||
#### func ShouldNotBeEmpty
|
||||
|
||||
```go
|
||||
func ShouldNotBeEmpty(actual interface{}, expected ...interface{}) string
|
||||
```
|
||||
ShouldNotBeEmpty receives a single parameter (actual) and determines whether or
|
||||
not calling len(actual) would return a value greater than zero. It obeys the
|
||||
rules specified by the `len` function for determining length:
|
||||
http://golang.org/pkg/builtin/#len
|
||||
|
||||
#### func ShouldNotBeIn
|
||||
|
||||
```go
|
||||
func ShouldNotBeIn(actual interface{}, expected ...interface{}) string
|
||||
```
|
||||
ShouldNotBeIn receives at least 2 parameters. The first is a proposed member of
|
||||
the collection that is passed in either as the second parameter, or of the
|
||||
collection that is comprised of all the remaining parameters. This assertion
|
||||
ensures that the proposed member is NOT in the collection (using ShouldEqual).
|
||||
|
||||
#### func ShouldNotBeNil
|
||||
|
||||
```go
|
||||
func ShouldNotBeNil(actual interface{}, expected ...interface{}) string
|
||||
```
|
||||
ShouldNotBeNil receives a single parameter and ensures that it is not nil.
|
||||
|
||||
#### func ShouldNotContain
|
||||
|
||||
```go
|
||||
func ShouldNotContain(actual interface{}, expected ...interface{}) string
|
||||
```
|
||||
ShouldNotContain receives exactly two parameters. The first is a slice and the
|
||||
second is a proposed member. Membership is determinied using ShouldEqual.
|
||||
|
||||
#### func ShouldNotContainKey
|
||||
|
||||
```go
|
||||
func ShouldNotContainKey(actual interface{}, expected ...interface{}) string
|
||||
```
|
||||
ShouldNotContainKey receives exactly two parameters. The first is a map and the
|
||||
second is a proposed absent key. Keys are compared with a simple '=='.
|
||||
|
||||
#### func ShouldNotContainSubstring
|
||||
|
||||
```go
|
||||
func ShouldNotContainSubstring(actual interface{}, expected ...interface{}) string
|
||||
```
|
||||
ShouldNotContainSubstring receives exactly 2 string parameters and ensures that
|
||||
the first does NOT contain the second as a substring.
|
||||
|
||||
#### func ShouldNotEndWith
|
||||
|
||||
```go
|
||||
func ShouldNotEndWith(actual interface{}, expected ...interface{}) string
|
||||
```
|
||||
ShouldEndWith receives exactly 2 string parameters and ensures that the first
|
||||
does not end with the second.
|
||||
|
||||
#### func ShouldNotEqual
|
||||
|
||||
```go
|
||||
func ShouldNotEqual(actual interface{}, expected ...interface{}) string
|
||||
```
|
||||
ShouldNotEqual receives exactly two parameters and does an inequality check.
|
||||
|
||||
#### func ShouldNotHappenOnOrBetween
|
||||
|
||||
```go
|
||||
func ShouldNotHappenOnOrBetween(actual interface{}, expected ...interface{}) string
|
||||
```
|
||||
ShouldNotHappenOnOrBetween receives exactly 3 time.Time arguments and asserts
|
||||
that the first does NOT happen between or on the second or third.
|
||||
|
||||
#### func ShouldNotHappenWithin
|
||||
|
||||
```go
|
||||
func ShouldNotHappenWithin(actual interface{}, expected ...interface{}) string
|
||||
```
|
||||
ShouldNotHappenWithin receives a time.Time, a time.Duration, and a time.Time (3
|
||||
arguments) and asserts that the first time.Time does NOT happen within or on the
|
||||
duration specified relative to the other time.Time.
|
||||
|
||||
#### func ShouldNotHaveSameTypeAs
|
||||
|
||||
```go
|
||||
func ShouldNotHaveSameTypeAs(actual interface{}, expected ...interface{}) string
|
||||
```
|
||||
ShouldNotHaveSameTypeAs receives exactly two parameters and compares their
|
||||
underlying types for inequality.
|
||||
|
||||
#### func ShouldNotImplement
|
||||
|
||||
```go
|
||||
func ShouldNotImplement(actual interface{}, expectedList ...interface{}) string
|
||||
```
|
||||
ShouldNotImplement receives exactly two parameters and ensures that the first
|
||||
does NOT implement the interface type of the second.
|
||||
|
||||
#### func ShouldNotPanic
|
||||
|
||||
```go
|
||||
func ShouldNotPanic(actual interface{}, expected ...interface{}) (message string)
|
||||
```
|
||||
ShouldNotPanic receives a void, niladic function and expects to execute the
|
||||
function without any panic.
|
||||
|
||||
#### func ShouldNotPanicWith
|
||||
|
||||
```go
|
||||
func ShouldNotPanicWith(actual interface{}, expected ...interface{}) (message string)
|
||||
```
|
||||
ShouldNotPanicWith receives a void, niladic function and expects to recover a
|
||||
panic whose content differs from the second argument.
|
||||
|
||||
#### func ShouldNotPointTo
|
||||
|
||||
```go
|
||||
func ShouldNotPointTo(actual interface{}, expected ...interface{}) string
|
||||
```
|
||||
ShouldNotPointTo receives exactly two parameters and checks to see that they
|
||||
point to different addresess.
|
||||
|
||||
#### func ShouldNotResemble
|
||||
|
||||
```go
|
||||
func ShouldNotResemble(actual interface{}, expected ...interface{}) string
|
||||
```
|
||||
ShouldNotResemble receives exactly two parameters and does an inverse deep equal
|
||||
check (see reflect.DeepEqual)
|
||||
|
||||
#### func ShouldNotStartWith
|
||||
|
||||
```go
|
||||
func ShouldNotStartWith(actual interface{}, expected ...interface{}) string
|
||||
```
|
||||
ShouldNotStartWith receives exactly 2 string parameters and ensures that the
|
||||
first does not start with the second.
|
||||
|
||||
#### func ShouldPanic
|
||||
|
||||
```go
|
||||
func ShouldPanic(actual interface{}, expected ...interface{}) (message string)
|
||||
```
|
||||
ShouldPanic receives a void, niladic function and expects to recover a panic.
|
||||
|
||||
#### func ShouldPanicWith
|
||||
|
||||
```go
|
||||
func ShouldPanicWith(actual interface{}, expected ...interface{}) (message string)
|
||||
```
|
||||
ShouldPanicWith receives a void, niladic function and expects to recover a panic
|
||||
with the second argument as the content.
|
||||
|
||||
#### func ShouldPointTo
|
||||
|
||||
```go
|
||||
func ShouldPointTo(actual interface{}, expected ...interface{}) string
|
||||
```
|
||||
ShouldPointTo receives exactly two parameters and checks to see that they point
|
||||
to the same address.
|
||||
|
||||
#### func ShouldResemble
|
||||
|
||||
```go
|
||||
func ShouldResemble(actual interface{}, expected ...interface{}) string
|
||||
```
|
||||
ShouldResemble receives exactly two parameters and does a deep equal check (see
|
||||
reflect.DeepEqual)
|
||||
|
||||
#### func ShouldStartWith
|
||||
|
||||
```go
|
||||
func ShouldStartWith(actual interface{}, expected ...interface{}) string
|
||||
```
|
||||
ShouldStartWith receives exactly 2 string parameters and ensures that the first
|
||||
starts with the second.
|
||||
|
||||
#### func So
|
||||
|
||||
```go
|
||||
func So(actual interface{}, assert assertion, expected ...interface{}) (bool, string)
|
||||
```
|
||||
So is a convenience function (as opposed to an inconvenience function?) for
|
||||
running assertions on arbitrary arguments in any context, be it for testing or
|
||||
even application logging. It allows you to perform assertion-like behavior (and
|
||||
get nicely formatted messages detailing discrepancies) but without the program
|
||||
blowing up or panicking. All that is required is to import this package and call
|
||||
`So` with one of the assertions exported by this package as the second
|
||||
parameter. The first return parameter is a boolean indicating if the assertion
|
||||
was true. The second return parameter is the well-formatted message showing why
|
||||
an assertion was incorrect, or blank if the assertion was correct.
|
||||
|
||||
Example:
|
||||
|
||||
if ok, message := So(x, ShouldBeGreaterThan, y); !ok {
|
||||
log.Println(message)
|
||||
}
|
||||
|
||||
#### type Assertion
|
||||
|
||||
```go
|
||||
type Assertion struct {
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
#### func New
|
||||
|
||||
```go
|
||||
func New(t testingT) *Assertion
|
||||
```
|
||||
New swallows the *testing.T struct and prints failed assertions using t.Error.
|
||||
Example: assertions.New(t).So(1, should.Equal, 1)
|
||||
|
||||
#### func (*Assertion) Failed
|
||||
|
||||
```go
|
||||
func (this *Assertion) Failed() bool
|
||||
```
|
||||
Failed reports whether any calls to So (on this Assertion instance) have failed.
|
||||
|
||||
#### func (*Assertion) So
|
||||
|
||||
```go
|
||||
func (this *Assertion) So(actual interface{}, assert assertion, expected ...interface{}) bool
|
||||
```
|
||||
So calls the standalone So function and additionally, calls t.Error in failure
|
||||
scenarios.
|
||||
|
||||
#### type Serializer
|
||||
|
||||
```go
|
||||
type Serializer interface {
|
||||
// contains filtered or unexported methods
|
||||
}
|
||||
```
|
@ -0,0 +1,3 @@
|
||||
#ignore
|
||||
-timeout=1s
|
||||
-coverpkg=github.com/smartystreets/assertions,github.com/smartystreets/assertions/internal/oglematchers
|
@ -0,0 +1,250 @@
|
||||
package assertions
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
|
||||
"github.com/smartystreets/assertions/internal/oglematchers"
|
||||
)
|
||||
|
||||
// ShouldContain receives exactly two parameters. The first is a slice and the
|
||||
// second is a proposed member. Membership is determined using ShouldEqual.
|
||||
func ShouldContain(actual interface{}, expected ...interface{}) string {
|
||||
if fail := need(1, expected); fail != success {
|
||||
return fail
|
||||
}
|
||||
|
||||
if matchError := oglematchers.Contains(expected[0]).Matches(actual); matchError != nil {
|
||||
typeName := reflect.TypeOf(actual)
|
||||
|
||||
if fmt.Sprintf("%v", matchError) == "which is not a slice or array" {
|
||||
return fmt.Sprintf(shouldHaveBeenAValidCollection, typeName)
|
||||
}
|
||||
return fmt.Sprintf(shouldHaveContained, typeName, expected[0])
|
||||
}
|
||||
return success
|
||||
}
|
||||
|
||||
// ShouldNotContain receives exactly two parameters. The first is a slice and the
|
||||
// second is a proposed member. Membership is determinied using ShouldEqual.
|
||||
func ShouldNotContain(actual interface{}, expected ...interface{}) string {
|
||||
if fail := need(1, expected); fail != success {
|
||||
return fail
|
||||
}
|
||||
typeName := reflect.TypeOf(actual)
|
||||
|
||||
if matchError := oglematchers.Contains(expected[0]).Matches(actual); matchError != nil {
|
||||
if fmt.Sprintf("%v", matchError) == "which is not a slice or array" {
|
||||
return fmt.Sprintf(shouldHaveBeenAValidCollection, typeName)
|
||||
}
|
||||
return success
|
||||
}
|
||||
return fmt.Sprintf(shouldNotHaveContained, typeName, expected[0])
|
||||
}
|
||||
|
||||
// ShouldContainKey receives exactly two parameters. The first is a map and the
|
||||
// second is a proposed key. Keys are compared with a simple '=='.
|
||||
func ShouldContainKey(actual interface{}, expected ...interface{}) string {
|
||||
if fail := need(1, expected); fail != success {
|
||||
return fail
|
||||
}
|
||||
|
||||
keys, isMap := mapKeys(actual)
|
||||
if !isMap {
|
||||
return fmt.Sprintf(shouldHaveBeenAValidMap, reflect.TypeOf(actual))
|
||||
}
|
||||
|
||||
if !keyFound(keys, expected[0]) {
|
||||
return fmt.Sprintf(shouldHaveContainedKey, reflect.TypeOf(actual), expected)
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
// ShouldNotContainKey receives exactly two parameters. The first is a map and the
|
||||
// second is a proposed absent key. Keys are compared with a simple '=='.
|
||||
func ShouldNotContainKey(actual interface{}, expected ...interface{}) string {
|
||||
if fail := need(1, expected); fail != success {
|
||||
return fail
|
||||
}
|
||||
|
||||
keys, isMap := mapKeys(actual)
|
||||
if !isMap {
|
||||
return fmt.Sprintf(shouldHaveBeenAValidMap, reflect.TypeOf(actual))
|
||||
}
|
||||
|
||||
if keyFound(keys, expected[0]) {
|
||||
return fmt.Sprintf(shouldNotHaveContainedKey, reflect.TypeOf(actual), expected)
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
func mapKeys(m interface{}) ([]reflect.Value, bool) {
|
||||
value := reflect.ValueOf(m)
|
||||
if value.Kind() != reflect.Map {
|
||||
return nil, false
|
||||
}
|
||||
return value.MapKeys(), true
|
||||
}
|
||||
func keyFound(keys []reflect.Value, expectedKey interface{}) bool {
|
||||
found := false
|
||||
for _, key := range keys {
|
||||
if key.Interface() == expectedKey {
|
||||
found = true
|
||||
}
|
||||
}
|
||||
return found
|
||||
}
|
||||
|
||||
// ShouldBeIn receives at least 2 parameters. The first is a proposed member of the collection
|
||||
// that is passed in either as the second parameter, or of the collection that is comprised
|
||||
// of all the remaining parameters. This assertion ensures that the proposed member is in
|
||||
// the collection (using ShouldEqual).
|
||||
func ShouldBeIn(actual interface{}, expected ...interface{}) string {
|
||||
if fail := atLeast(1, expected); fail != success {
|
||||
return fail
|
||||
}
|
||||
|
||||
if len(expected) == 1 {
|
||||
return shouldBeIn(actual, expected[0])
|
||||
}
|
||||
return shouldBeIn(actual, expected)
|
||||
}
|
||||
func shouldBeIn(actual interface{}, expected interface{}) string {
|
||||
if matchError := oglematchers.Contains(actual).Matches(expected); matchError != nil {
|
||||
return fmt.Sprintf(shouldHaveBeenIn, actual, reflect.TypeOf(expected))
|
||||
}
|
||||
return success
|
||||
}
|
||||
|
||||
// ShouldNotBeIn receives at least 2 parameters. The first is a proposed member of the collection
|
||||
// that is passed in either as the second parameter, or of the collection that is comprised
|
||||
// of all the remaining parameters. This assertion ensures that the proposed member is NOT in
|
||||
// the collection (using ShouldEqual).
|
||||
func ShouldNotBeIn(actual interface{}, expected ...interface{}) string {
|
||||
if fail := atLeast(1, expected); fail != success {
|
||||
return fail
|
||||
}
|
||||
|
||||
if len(expected) == 1 {
|
||||
return shouldNotBeIn(actual, expected[0])
|
||||
}
|
||||
return shouldNotBeIn(actual, expected)
|
||||
}
|
||||
func shouldNotBeIn(actual interface{}, expected interface{}) string {
|
||||
if matchError := oglematchers.Contains(actual).Matches(expected); matchError == nil {
|
||||
return fmt.Sprintf(shouldNotHaveBeenIn, actual, reflect.TypeOf(expected))
|
||||
}
|
||||
return success
|
||||
}
|
||||
|
||||
// ShouldBeEmpty receives a single parameter (actual) and determines whether or not
|
||||
// calling len(actual) would return `0`. It obeys the rules specified by the len
|
||||
// function for determining length: http://golang.org/pkg/builtin/#len
|
||||
func ShouldBeEmpty(actual interface{}, expected ...interface{}) string {
|
||||
if fail := need(0, expected); fail != success {
|
||||
return fail
|
||||
}
|
||||
|
||||
if actual == nil {
|
||||
return success
|
||||
}
|
||||
|
||||
value := reflect.ValueOf(actual)
|
||||
switch value.Kind() {
|
||||
case reflect.Slice:
|
||||
if value.Len() == 0 {
|
||||
return success
|
||||
}
|
||||
case reflect.Chan:
|
||||
if value.Len() == 0 {
|
||||
return success
|
||||
}
|
||||
case reflect.Map:
|
||||
if value.Len() == 0 {
|
||||
return success
|
||||
}
|
||||
case reflect.String:
|
||||
if value.Len() == 0 {
|
||||
return success
|
||||
}
|
||||
case reflect.Ptr:
|
||||
elem := value.Elem()
|
||||
kind := elem.Kind()
|
||||
if (kind == reflect.Slice || kind == reflect.Array) && elem.Len() == 0 {
|
||||
return success
|
||||
}
|
||||
}
|
||||
|
||||
return fmt.Sprintf(shouldHaveBeenEmpty, actual)
|
||||
}
|
||||
|
||||
// ShouldNotBeEmpty receives a single parameter (actual) and determines whether or not
|
||||
// calling len(actual) would return a value greater than zero. It obeys the rules
|
||||
// specified by the `len` function for determining length: http://golang.org/pkg/builtin/#len
|
||||
func ShouldNotBeEmpty(actual interface{}, expected ...interface{}) string {
|
||||
if fail := need(0, expected); fail != success {
|
||||
return fail
|
||||
}
|
||||
|
||||
if empty := ShouldBeEmpty(actual, expected...); empty != success {
|
||||
return success
|
||||
}
|
||||
return fmt.Sprintf(shouldNotHaveBeenEmpty, actual)
|
||||
}
|
||||
|
||||
// ShouldHaveLength receives 2 parameters. The first is a collection to check
|
||||
// the length of, the second being the expected length. It obeys the rules
|
||||
// specified by the len function for determining length:
|
||||
// http://golang.org/pkg/builtin/#len
|
||||
func ShouldHaveLength(actual interface{}, expected ...interface{}) string {
|
||||
if fail := need(1, expected); fail != success {
|
||||
return fail
|
||||
}
|
||||
|
||||
var expectedLen int64
|
||||
lenValue := reflect.ValueOf(expected[0])
|
||||
switch lenValue.Kind() {
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||
expectedLen = lenValue.Int()
|
||||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
|
||||
expectedLen = int64(lenValue.Uint())
|
||||
default:
|
||||
return fmt.Sprintf(shouldHaveBeenAValidInteger, reflect.TypeOf(expected[0]))
|
||||
}
|
||||
|
||||
if expectedLen < 0 {
|
||||
return fmt.Sprintf(shouldHaveBeenAValidLength, expected[0])
|
||||
}
|
||||
|
||||
value := reflect.ValueOf(actual)
|
||||
switch value.Kind() {
|
||||
case reflect.Slice:
|
||||
if int64(value.Len()) == expectedLen {
|
||||
return success
|
||||
}
|
||||
case reflect.Chan:
|
||||
if int64(value.Len()) == expectedLen {
|
||||
return success
|
||||
}
|
||||
case reflect.Map:
|
||||
if int64(value.Len()) == expectedLen {
|
||||
return success
|
||||
}
|
||||
case reflect.String:
|
||||
if int64(value.Len()) == expectedLen {
|
||||
return success
|
||||
}
|
||||
case reflect.Ptr:
|
||||
elem := value.Elem()
|
||||
kind := elem.Kind()
|
||||
if (kind == reflect.Slice || kind == reflect.Array) && int64(elem.Len()) == expectedLen {
|
||||
return success
|
||||
}
|
||||
default:
|
||||
return fmt.Sprintf(shouldHaveBeenAValidCollection, reflect.TypeOf(actual))
|
||||
}
|
||||
|
||||
return fmt.Sprintf(shouldHaveHadLength, actual, expectedLen)
|
||||
}
|
@ -0,0 +1,99 @@
|
||||
// Package assertions contains the implementations for all assertions which
|
||||
// are referenced in goconvey's `convey` package
|
||||
// (github.com/smartystreets/goconvey/convey) for use with the So(...) method.
|
||||
// They can also be used in traditional Go test functions and even in
|
||||
// applicaitons.
|
||||
package assertions
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"runtime"
|
||||
)
|
||||
|
||||
// By default we use a no-op serializer. The actual Serializer provides a JSON
|
||||
// representation of failure results on selected assertions so the goconvey
|
||||
// web UI can display a convenient diff.
|
||||
var serializer Serializer = new(noopSerializer)
|
||||
|
||||
// GoConveyMode provides control over JSON serialization of failures. When
|
||||
// using the assertions in this package from the convey package JSON results
|
||||
// are very helpful and can be rendered in a DIFF view. In that case, this function
|
||||
// will be called with a true value to enable the JSON serialization. By default,
|
||||
// the assertions in this package will not serializer a JSON result, making
|
||||
// standalone ussage more convenient.
|
||||
func GoConveyMode(yes bool) {
|
||||
if yes {
|
||||
serializer = newSerializer()
|
||||
} else {
|
||||
serializer = new(noopSerializer)
|
||||
}
|
||||
}
|
||||
|
||||
type testingT interface {
|
||||
Error(args ...interface{})
|
||||
}
|
||||
|
||||
type Assertion struct {
|
||||
t testingT
|
||||
failed bool
|
||||
}
|
||||
|
||||
// New swallows the *testing.T struct and prints failed assertions using t.Error.
|
||||
// Example: assertions.New(t).So(1, should.Equal, 1)
|
||||
func New(t testingT) *Assertion {
|
||||
return &Assertion{t: t}
|
||||
}
|
||||
|
||||
// Failed reports whether any calls to So (on this Assertion instance) have failed.
|
||||
func (this *Assertion) Failed() bool {
|
||||
return this.failed
|
||||
}
|
||||
|
||||
// So calls the standalone So function and additionally, calls t.Error in failure scenarios.
|
||||
func (this *Assertion) So(actual interface{}, assert assertion, expected ...interface{}) bool {
|
||||
ok, result := So(actual, assert, expected...)
|
||||
if !ok {
|
||||
this.failed = true
|
||||
_, file, line, _ := runtime.Caller(1)
|
||||
this.t.Error(fmt.Sprintf("\n%s:%d\n%s", file, line, result))
|
||||
}
|
||||
return ok
|
||||
}
|
||||
|
||||
// So is a convenience function (as opposed to an inconvenience function?)
|
||||
// for running assertions on arbitrary arguments in any context, be it for testing or even
|
||||
// application logging. It allows you to perform assertion-like behavior (and get nicely
|
||||
// formatted messages detailing discrepancies) but without the program blowing up or panicking.
|
||||
// All that is required is to import this package and call `So` with one of the assertions
|
||||
// exported by this package as the second parameter.
|
||||
// The first return parameter is a boolean indicating if the assertion was true. The second
|
||||
// return parameter is the well-formatted message showing why an assertion was incorrect, or
|
||||
// blank if the assertion was correct.
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// if ok, message := So(x, ShouldBeGreaterThan, y); !ok {
|
||||
// log.Println(message)
|
||||
// }
|
||||
//
|
||||
func So(actual interface{}, assert assertion, expected ...interface{}) (bool, string) {
|
||||
if result := so(actual, assert, expected...); len(result) == 0 {
|
||||
return true, result
|
||||
} else {
|
||||
return false, result
|
||||
}
|
||||
}
|
||||
|
||||
// so is like So, except that it only returns the string message, which is blank if the
|
||||
// assertion passed. Used to facilitate testing.
|
||||
func so(actual interface{}, assert func(interface{}, ...interface{}) string, expected ...interface{}) string {
|
||||
return assert(actual, expected...)
|
||||
}
|
||||
|
||||
// assertion is an alias for a function with a signature that the So()
|
||||
// function can handle. Any future or custom assertions should conform to this
|
||||
// method signature. The return value should be an empty string if the assertion
|
||||
// passes and a well-formed failure message if not.
|
||||
type assertion func(actual interface{}, expected ...interface{}) string
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////
|
@ -0,0 +1,286 @@
|
||||
package assertions
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"math"
|
||||
"reflect"
|
||||
"strings"
|
||||
|
||||
"github.com/smartystreets/assertions/internal/oglematchers"
|
||||
)
|
||||
|
||||
// default acceptable delta for ShouldAlmostEqual
|
||||
const defaultDelta = 0.0000000001
|
||||
|
||||
// ShouldEqual receives exactly two parameters and does an equality check.
|
||||
func ShouldEqual(actual interface{}, expected ...interface{}) string {
|
||||
if message := need(1, expected); message != success {
|
||||
return message
|
||||
}
|
||||
return shouldEqual(actual, expected[0])
|
||||
}
|
||||
func shouldEqual(actual, expected interface{}) (message string) {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
message = serializer.serialize(expected, actual, fmt.Sprintf(shouldHaveBeenEqual, expected, actual))
|
||||
return
|
||||
}
|
||||
}()
|
||||
|
||||
if matchError := oglematchers.Equals(expected).Matches(actual); matchError != nil {
|
||||
expectedSyntax := fmt.Sprintf("%v", expected)
|
||||
actualSyntax := fmt.Sprintf("%v", actual)
|
||||
if expectedSyntax == actualSyntax && reflect.TypeOf(expected) != reflect.TypeOf(actual) {
|
||||
message = fmt.Sprintf(shouldHaveBeenEqualTypeMismatch, expected, expected, actual, actual)
|
||||
} else {
|
||||
message = fmt.Sprintf(shouldHaveBeenEqual, expected, actual)
|
||||
}
|
||||
message = serializer.serialize(expected, actual, message)
|
||||
return
|
||||
}
|
||||
|
||||
return success
|
||||
}
|
||||
|
||||
// ShouldNotEqual receives exactly two parameters and does an inequality check.
|
||||
func ShouldNotEqual(actual interface{}, expected ...interface{}) string {
|
||||
if fail := need(1, expected); fail != success {
|
||||
return fail
|
||||
} else if ShouldEqual(actual, expected[0]) == success {
|
||||
return fmt.Sprintf(shouldNotHaveBeenEqual, actual, expected[0])
|
||||
}
|
||||
return success
|
||||
}
|
||||
|
||||
// ShouldAlmostEqual makes sure that two parameters are close enough to being equal.
|
||||
// The acceptable delta may be specified with a third argument,
|
||||
// or a very small default delta will be used.
|
||||
func ShouldAlmostEqual(actual interface{}, expected ...interface{}) string {
|
||||
actualFloat, expectedFloat, deltaFloat, err := cleanAlmostEqualInput(actual, expected...)
|
||||
|
||||
if err != "" {
|
||||
return err
|
||||
}
|
||||
|
||||
if math.Abs(actualFloat-expectedFloat) <= deltaFloat {
|
||||
return success
|
||||
} else {
|
||||
return fmt.Sprintf(shouldHaveBeenAlmostEqual, actualFloat, expectedFloat)
|
||||
}
|
||||
}
|
||||
|
||||
// ShouldNotAlmostEqual is the inverse of ShouldAlmostEqual
|
||||
func ShouldNotAlmostEqual(actual interface{}, expected ...interface{}) string {
|
||||
actualFloat, expectedFloat, deltaFloat, err := cleanAlmostEqualInput(actual, expected...)
|
||||
|
||||
if err != "" {
|
||||
return err
|
||||
}
|
||||
|
||||
if math.Abs(actualFloat-expectedFloat) > deltaFloat {
|
||||
return success
|
||||
} else {
|
||||
return fmt.Sprintf(shouldHaveNotBeenAlmostEqual, actualFloat, expectedFloat)
|
||||
}
|
||||
}
|
||||
|
||||
func cleanAlmostEqualInput(actual interface{}, expected ...interface{}) (float64, float64, float64, string) {
|
||||
deltaFloat := 0.0000000001
|
||||
|
||||
if len(expected) == 0 {
|
||||
return 0.0, 0.0, 0.0, "This assertion requires exactly one comparison value and an optional delta (you provided neither)"
|
||||
} else if len(expected) == 2 {
|
||||
delta, err := getFloat(expected[1])
|
||||
|
||||
if err != nil {
|
||||
return 0.0, 0.0, 0.0, "delta must be a numerical type"
|
||||
}
|
||||
|
||||
deltaFloat = delta
|
||||
} else if len(expected) > 2 {
|
||||
return 0.0, 0.0, 0.0, "This assertion requires exactly one comparison value and an optional delta (you provided more values)"
|
||||
}
|
||||
|
||||
actualFloat, err := getFloat(actual)
|
||||
|
||||
if err != nil {
|
||||
return 0.0, 0.0, 0.0, err.Error()
|
||||
}
|
||||
|
||||
expectedFloat, err := getFloat(expected[0])
|
||||
|
||||
if err != nil {
|
||||
return 0.0, 0.0, 0.0, err.Error()
|
||||
}
|
||||
|
||||
return actualFloat, expectedFloat, deltaFloat, ""
|
||||
}
|
||||
|
||||
// returns the float value of any real number, or error if it is not a numerical type
|
||||
func getFloat(num interface{}) (float64, error) {
|
||||
numValue := reflect.ValueOf(num)
|
||||
numKind := numValue.Kind()
|
||||
|
||||
if numKind == reflect.Int ||
|
||||
numKind == reflect.Int8 ||
|
||||
numKind == reflect.Int16 ||
|
||||
numKind == reflect.Int32 ||
|
||||
numKind == reflect.Int64 {
|
||||
return float64(numValue.Int()), nil
|
||||
} else if numKind == reflect.Uint ||
|
||||
numKind == reflect.Uint8 ||
|
||||
numKind == reflect.Uint16 ||
|
||||
numKind == reflect.Uint32 ||
|
||||
numKind == reflect.Uint64 {
|
||||
return float64(numValue.Uint()), nil
|
||||
} else if numKind == reflect.Float32 ||
|
||||
numKind == reflect.Float64 {
|
||||
return numValue.Float(), nil
|
||||
} else {
|
||||
return 0.0, errors.New("must be a numerical type, but was " + numKind.String())
|
||||
}
|
||||
}
|
||||
|
||||
// ShouldResemble receives exactly two parameters and does a deep equal check (see reflect.DeepEqual)
|
||||
func ShouldResemble(actual interface{}, expected ...interface{}) string {
|
||||
if message := need(1, expected); message != success {
|
||||
return message
|
||||
}
|
||||
|
||||
if matchError := oglematchers.DeepEquals(expected[0]).Matches(actual); matchError != nil {
|
||||
expectedSyntax := fmt.Sprintf("%#v", expected[0])
|
||||
actualSyntax := fmt.Sprintf("%#v", actual)
|
||||
var message string
|
||||
if expectedSyntax == actualSyntax {
|
||||
message = fmt.Sprintf(shouldHaveResembledTypeMismatch, expected[0], expected[0], actual, actual)
|
||||
} else {
|
||||
message = fmt.Sprintf(shouldHaveResembled, expected[0], actual)
|
||||
}
|
||||
return serializer.serializeDetailed(expected[0], actual, message)
|
||||
}
|
||||
|
||||
return success
|
||||
}
|
||||
|
||||
// ShouldNotResemble receives exactly two parameters and does an inverse deep equal check (see reflect.DeepEqual)
|
||||
func ShouldNotResemble(actual interface{}, expected ...interface{}) string {
|
||||
if message := need(1, expected); message != success {
|
||||
return message
|
||||
} else if ShouldResemble(actual, expected[0]) == success {
|
||||
return fmt.Sprintf(shouldNotHaveResembled, actual, expected[0])
|
||||
}
|
||||
return success
|
||||
}
|
||||
|
||||
// ShouldPointTo receives exactly two parameters and checks to see that they point to the same address.
|
||||
func ShouldPointTo(actual interface{}, expected ...interface{}) string {
|
||||
if message := need(1, expected); message != success {
|
||||
return message
|
||||
}
|
||||
return shouldPointTo(actual, expected[0])
|
||||
|
||||
}
|
||||
func shouldPointTo(actual, expected interface{}) string {
|
||||
actualValue := reflect.ValueOf(actual)
|
||||
expectedValue := reflect.ValueOf(expected)
|
||||
|
||||
if ShouldNotBeNil(actual) != success {
|
||||
return fmt.Sprintf(shouldHaveBeenNonNilPointer, "first", "nil")
|
||||
} else if ShouldNotBeNil(expected) != success {
|
||||
return fmt.Sprintf(shouldHaveBeenNonNilPointer, "second", "nil")
|
||||
} else if actualValue.Kind() != reflect.Ptr {
|
||||
return fmt.Sprintf(shouldHaveBeenNonNilPointer, "first", "not")
|
||||
} else if expectedValue.Kind() != reflect.Ptr {
|
||||
return fmt.Sprintf(shouldHaveBeenNonNilPointer, "second", "not")
|
||||
} else if ShouldEqual(actualValue.Pointer(), expectedValue.Pointer()) != success {
|
||||
actualAddress := reflect.ValueOf(actual).Pointer()
|
||||
expectedAddress := reflect.ValueOf(expected).Pointer()
|
||||
return serializer.serialize(expectedAddress, actualAddress, fmt.Sprintf(shouldHavePointedTo,
|
||||
actual, actualAddress,
|
||||
expected, expectedAddress))
|
||||
}
|
||||
return success
|
||||
}
|
||||
|
||||
// ShouldNotPointTo receives exactly two parameters and checks to see that they point to different addresess.
|
||||
func ShouldNotPointTo(actual interface{}, expected ...interface{}) string {
|
||||
if message := need(1, expected); message != success {
|
||||
return message
|
||||
}
|
||||
compare := ShouldPointTo(actual, expected[0])
|
||||
if strings.HasPrefix(compare, shouldBePointers) {
|
||||
return compare
|
||||
} else if compare == success {
|
||||
return fmt.Sprintf(shouldNotHavePointedTo, actual, expected[0], reflect.ValueOf(actual).Pointer())
|
||||
}
|
||||
return success
|
||||
}
|
||||
|
||||
// ShouldBeNil receives a single parameter and ensures that it is nil.
|
||||
func ShouldBeNil(actual interface{}, expected ...interface{}) string {
|
||||
if fail := need(0, expected); fail != success {
|
||||
return fail
|
||||
} else if actual == nil {
|
||||
return success
|
||||
} else if interfaceHasNilValue(actual) {
|
||||
return success
|
||||
}
|
||||
return fmt.Sprintf(shouldHaveBeenNil, actual)
|
||||
}
|
||||
func interfaceHasNilValue(actual interface{}) bool {
|
||||
value := reflect.ValueOf(actual)
|
||||
kind := value.Kind()
|
||||
nilable := kind == reflect.Slice ||
|
||||
kind == reflect.Chan ||
|
||||
kind == reflect.Func ||
|
||||
kind == reflect.Ptr ||
|
||||
kind == reflect.Map
|
||||
|
||||
// Careful: reflect.Value.IsNil() will panic unless it's an interface, chan, map, func, slice, or ptr
|
||||
// Reference: http://golang.org/pkg/reflect/#Value.IsNil
|
||||
return nilable && value.IsNil()
|
||||
}
|
||||
|
||||
// ShouldNotBeNil receives a single parameter and ensures that it is not nil.
|
||||
func ShouldNotBeNil(actual interface{}, expected ...interface{}) string {
|
||||
if fail := need(0, expected); fail != success {
|
||||
return fail
|
||||
} else if ShouldBeNil(actual) == success {
|
||||
return fmt.Sprintf(shouldNotHaveBeenNil, actual)
|
||||
}
|
||||
return success
|
||||
}
|
||||
|
||||
// ShouldBeTrue receives a single parameter and ensures that it is true.
|
||||
func ShouldBeTrue(actual interface{}, expected ...interface{}) string {
|
||||
if fail := need(0, expected); fail != success {
|
||||
return fail
|
||||
} else if actual != true {
|
||||
return fmt.Sprintf(shouldHaveBeenTrue, actual)
|
||||
}
|
||||
return success
|
||||
}
|
||||
|
||||
// ShouldBeFalse receives a single parameter and ensures that it is false.
|
||||
func ShouldBeFalse(actual interface{}, expected ...interface{}) string {
|
||||
if fail := need(0, expected); fail != success {
|
||||
return fail
|
||||
} else if actual != false {
|
||||
return fmt.Sprintf(shouldHaveBeenFalse, actual)
|
||||
}
|
||||
return success
|
||||
}
|
||||
|
||||
// ShouldBeZeroValue receives a single parameter and ensures that it is
|
||||
// the Go equivalent of the default value, or "zero" value.
|
||||
func ShouldBeZeroValue(actual interface{}, expected ...interface{}) string {
|
||||
if fail := need(0, expected); fail != success {
|
||||
return fail
|
||||
}
|
||||
zeroVal := reflect.Zero(reflect.TypeOf(actual)).Interface()
|
||||
if !reflect.DeepEqual(zeroVal, actual) {
|
||||
return serializer.serialize(zeroVal, actual, fmt.Sprintf(shouldHaveBeenZeroValue, actual))
|
||||
}
|
||||
return success
|
||||
}
|
@ -0,0 +1,23 @@
|
||||
package assertions
|
||||
|
||||
import "fmt"
|
||||
|
||||
const (
|
||||
success = ""
|
||||
needExactValues = "This assertion requires exactly %d comparison values (you provided %d)."
|
||||
needNonEmptyCollection = "This assertion requires at least 1 comparison value (you provided 0)."
|
||||
)
|
||||
|
||||
func need(needed int, expected []interface{}) string {
|
||||
if len(expected) != needed {
|
||||
return fmt.Sprintf(needExactValues, needed, len(expected))
|
||||
}
|
||||
return success
|
||||
}
|
||||
|
||||
func atLeast(minimum int, expected []interface{}) string {
|
||||
if len(expected) < 1 {
|
||||
return needNonEmptyCollection
|
||||
}
|
||||
return success
|
||||
}
|
@ -0,0 +1,5 @@
|
||||
*.6
|
||||
6.out
|
||||
_obj/
|
||||
_test/
|
||||
_testmain.go
|
@ -0,0 +1,4 @@
|
||||
# Cf. http://docs.travis-ci.com/user/getting-started/
|
||||
# Cf. http://docs.travis-ci.com/user/languages/go/
|
||||
|
||||
language: go
|
@ -0,0 +1,202 @@
|
||||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
@ -0,0 +1,58 @@
|
||||
[![GoDoc](https://godoc.org/github.com/smartystreets/assertions/internal/oglematchers?status.svg)](https://godoc.org/github.com/smartystreets/assertions/internal/oglematchers)
|
||||
|
||||
`oglematchers` is a package for the Go programming language containing a set of
|
||||
matchers, useful in a testing or mocking framework, inspired by and mostly
|
||||
compatible with [Google Test][googletest] for C++ and
|
||||
[Google JS Test][google-js-test]. The package is used by the
|
||||
[ogletest][ogletest] testing framework and [oglemock][oglemock] mocking
|
||||
framework, which may be more directly useful to you, but can be generically used
|
||||
elsewhere as well.
|
||||
|
||||
A "matcher" is simply an object with a `Matches` method defining a set of golang
|
||||
values matched by the matcher, and a `Description` method describing that set.
|
||||
For example, here are some matchers:
|
||||
|
||||
```go
|
||||
// Numbers
|
||||
Equals(17.13)
|
||||
LessThan(19)
|
||||
|
||||
// Strings
|
||||
Equals("taco")
|
||||
HasSubstr("burrito")
|
||||
MatchesRegex("t.*o")
|
||||
|
||||
// Combining matchers
|
||||
AnyOf(LessThan(17), GreaterThan(19))
|
||||
```
|
||||
|
||||
There are lots more; see [here][reference] for a reference. You can also add
|
||||
your own simply by implementing the `oglematchers.Matcher` interface.
|
||||
|
||||
|
||||
Installation
|
||||
------------
|
||||
|
||||
First, make sure you have installed Go 1.0.2 or newer. See
|
||||
[here][golang-install] for instructions.
|
||||
|
||||
Use the following command to install `oglematchers` and keep it up to date:
|
||||
|
||||
go get -u github.com/smartystreets/assertions/internal/oglematchers
|
||||
|
||||
|
||||
Documentation
|
||||
-------------
|
||||
|
||||
See [here][reference] for documentation. Alternatively, you can install the
|
||||
package and then use `godoc`:
|
||||
|
||||
godoc github.com/smartystreets/assertions/internal/oglematchers
|
||||
|
||||
|
||||
[reference]: http://godoc.org/github.com/smartystreets/assertions/internal/oglematchers
|
||||
[golang-install]: http://golang.org/doc/install.html
|
||||
[googletest]: http://code.google.com/p/googletest/
|
||||
[google-js-test]: http://code.google.com/p/google-js-test/
|
||||
[ogletest]: http://github.com/smartystreets/assertions/internal/ogletest
|
||||
[oglemock]: http://github.com/smartystreets/assertions/internal/oglemock
|
@ -0,0 +1,70 @@
|
||||
// Copyright 2011 Aaron Jacobs. All Rights Reserved.
|
||||
// Author: aaronjjacobs@gmail.com (Aaron Jacobs)
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package oglematchers
|
||||
|
||||
import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
// AllOf accepts a set of matchers S and returns a matcher that follows the
|
||||
// algorithm below when considering a candidate c:
|
||||
//
|
||||
// 1. Return true if for every Matcher m in S, m matches c.
|
||||
//
|
||||
// 2. Otherwise, if there is a matcher m in S such that m returns a fatal
|
||||
// error for c, return that matcher's error message.
|
||||
//
|
||||
// 3. Otherwise, return false with the error from some wrapped matcher.
|
||||
//
|
||||
// This is akin to a logical AND operation for matchers.
|
||||
func AllOf(matchers ...Matcher) Matcher {
|
||||
return &allOfMatcher{matchers}
|
||||
}
|
||||
|
||||
type allOfMatcher struct {
|
||||
wrappedMatchers []Matcher
|
||||
}
|
||||
|
||||
func (m *allOfMatcher) Description() string {
|
||||
// Special case: the empty set.
|
||||
if len(m.wrappedMatchers) == 0 {
|
||||
return "is anything"
|
||||
}
|
||||
|
||||
// Join the descriptions for the wrapped matchers.
|
||||
wrappedDescs := make([]string, len(m.wrappedMatchers))
|
||||
for i, wrappedMatcher := range m.wrappedMatchers {
|
||||
wrappedDescs[i] = wrappedMatcher.Description()
|
||||
}
|
||||
|
||||
return strings.Join(wrappedDescs, ", and ")
|
||||
}
|
||||
|
||||
func (m *allOfMatcher) Matches(c interface{}) (err error) {
|
||||
for _, wrappedMatcher := range m.wrappedMatchers {
|
||||
if wrappedErr := wrappedMatcher.Matches(c); wrappedErr != nil {
|
||||
err = wrappedErr
|
||||
|
||||
// If the error is fatal, return immediately with this error.
|
||||
_, ok := wrappedErr.(*FatalError)
|
||||
if ok {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
@ -0,0 +1,32 @@
|
||||
// Copyright 2011 Aaron Jacobs. All Rights Reserved.
|
||||
// Author: aaronjjacobs@gmail.com (Aaron Jacobs)
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package oglematchers
|
||||
|
||||
// Any returns a matcher that matches any value.
|
||||
func Any() Matcher {
|
||||
return &anyMatcher{}
|
||||
}
|
||||
|
||||
type anyMatcher struct {
|
||||
}
|
||||
|
||||
func (m *anyMatcher) Description() string {
|
||||
return "is anything"
|
||||
}
|
||||
|
||||
func (m *anyMatcher) Matches(c interface{}) error {
|
||||
return nil
|
||||
}
|
@ -0,0 +1,94 @@
|
||||
// Copyright 2011 Aaron Jacobs. All Rights Reserved.
|
||||
// Author: aaronjjacobs@gmail.com (Aaron Jacobs)
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package oglematchers
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// AnyOf accepts a set of values S and returns a matcher that follows the
|
||||
// algorithm below when considering a candidate c:
|
||||
//
|
||||
// 1. If there exists a value m in S such that m implements the Matcher
|
||||
// interface and m matches c, return true.
|
||||
//
|
||||
// 2. Otherwise, if there exists a value v in S such that v does not implement
|
||||
// the Matcher interface and the matcher Equals(v) matches c, return true.
|
||||
//
|
||||
// 3. Otherwise, if there is a value m in S such that m implements the Matcher
|
||||
// interface and m returns a fatal error for c, return that fatal error.
|
||||
//
|
||||
// 4. Otherwise, return false.
|
||||
//
|
||||
// This is akin to a logical OR operation for matchers, with non-matchers x
|
||||
// being treated as Equals(x).
|
||||
func AnyOf(vals ...interface{}) Matcher {
|
||||
// Get ahold of a type variable for the Matcher interface.
|
||||
var dummy *Matcher
|
||||
matcherType := reflect.TypeOf(dummy).Elem()
|
||||
|
||||
// Create a matcher for each value, or use the value itself if it's already a
|
||||
// matcher.
|
||||
wrapped := make([]Matcher, len(vals))
|
||||
for i, v := range vals {
|
||||
t := reflect.TypeOf(v)
|
||||
if t != nil && t.Implements(matcherType) {
|
||||
wrapped[i] = v.(Matcher)
|
||||
} else {
|
||||
wrapped[i] = Equals(v)
|
||||
}
|
||||
}
|
||||
|
||||
return &anyOfMatcher{wrapped}
|
||||
}
|
||||
|
||||
type anyOfMatcher struct {
|
||||
wrapped []Matcher
|
||||
}
|
||||
|
||||
func (m *anyOfMatcher) Description() string {
|
||||
wrappedDescs := make([]string, len(m.wrapped))
|
||||
for i, matcher := range m.wrapped {
|
||||
wrappedDescs[i] = matcher.Description()
|
||||
}
|
||||
|
||||
return fmt.Sprintf("or(%s)", strings.Join(wrappedDescs, ", "))
|
||||
}
|
||||
|
||||
func (m *anyOfMatcher) Matches(c interface{}) (err error) {
|
||||
err = errors.New("")
|
||||
|
||||
// Try each matcher in turn.
|
||||
for _, matcher := range m.wrapped {
|
||||
wrappedErr := matcher.Matches(c)
|
||||
|
||||
// Return immediately if there's a match.
|
||||
if wrappedErr == nil {
|
||||
err = nil
|
||||
return
|
||||
}
|
||||
|
||||
// Note the fatal error, if any.
|
||||
if _, isFatal := wrappedErr.(*FatalError); isFatal {
|
||||
err = wrappedErr
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
@ -0,0 +1,61 @@
|
||||
// Copyright 2012 Aaron Jacobs. All Rights Reserved.
|
||||
// Author: aaronjjacobs@gmail.com (Aaron Jacobs)
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package oglematchers
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
)
|
||||
|
||||
// Return a matcher that matches arrays slices with at least one element that
|
||||
// matches the supplied argument. If the argument x is not itself a Matcher,
|
||||
// this is equivalent to Contains(Equals(x)).
|
||||
func Contains(x interface{}) Matcher {
|
||||
var result containsMatcher
|
||||
var ok bool
|
||||
|
||||
if result.elementMatcher, ok = x.(Matcher); !ok {
|
||||
result.elementMatcher = Equals(x)
|
||||
}
|
||||
|
||||
return &result
|
||||
}
|
||||
|
||||
type containsMatcher struct {
|
||||
elementMatcher Matcher
|
||||
}
|
||||
|
||||
func (m *containsMatcher) Description() string {
|
||||
return fmt.Sprintf("contains: %s", m.elementMatcher.Description())
|
||||
}
|
||||
|
||||
func (m *containsMatcher) Matches(candidate interface{}) error {
|
||||
// The candidate must be a slice or an array.
|
||||
v := reflect.ValueOf(candidate)
|
||||
if v.Kind() != reflect.Slice && v.Kind() != reflect.Array {
|
||||
return NewFatalError("which is not a slice or array")
|
||||
}
|
||||
|
||||
// Check each element.
|
||||
for i := 0; i < v.Len(); i++ {
|
||||
elem := v.Index(i)
|
||||
if matchErr := m.elementMatcher.Matches(elem.Interface()); matchErr == nil {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
return fmt.Errorf("")
|
||||
}
|
88
vendor/github.com/smartystreets/assertions/internal/oglematchers/deep_equals.go
generated
vendored
88
vendor/github.com/smartystreets/assertions/internal/oglematchers/deep_equals.go
generated
vendored
@ -0,0 +1,88 @@
|
||||
// Copyright 2012 Aaron Jacobs. All Rights Reserved.
|
||||
// Author: aaronjjacobs@gmail.com (Aaron Jacobs)
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package oglematchers
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"reflect"
|
||||
)
|
||||
|
||||
var byteSliceType reflect.Type = reflect.TypeOf([]byte{})
|
||||
|
||||
// DeepEquals returns a matcher that matches based on 'deep equality', as
|
||||
// defined by the reflect package. This matcher requires that values have
|
||||
// identical types to x.
|
||||
func DeepEquals(x interface{}) Matcher {
|
||||
return &deepEqualsMatcher{x}
|
||||
}
|
||||
|
||||
type deepEqualsMatcher struct {
|
||||
x interface{}
|
||||
}
|
||||
|
||||
func (m *deepEqualsMatcher) Description() string {
|
||||
xDesc := fmt.Sprintf("%v", m.x)
|
||||
xValue := reflect.ValueOf(m.x)
|
||||
|
||||
// Special case: fmt.Sprintf presents nil slices as "[]", but
|
||||
// reflect.DeepEqual makes a distinction between nil and empty slices. Make
|
||||
// this less confusing.
|
||||
if xValue.Kind() == reflect.Slice && xValue.IsNil() {
|
||||
xDesc = "<nil slice>"
|
||||
}
|
||||
|
||||
return fmt.Sprintf("deep equals: %s", xDesc)
|
||||
}
|
||||
|
||||
func (m *deepEqualsMatcher) Matches(c interface{}) error {
|
||||
// Make sure the types match.
|
||||
ct := reflect.TypeOf(c)
|
||||
xt := reflect.TypeOf(m.x)
|
||||
|
||||
if ct != xt {
|
||||
return NewFatalError(fmt.Sprintf("which is of type %v", ct))
|
||||
}
|
||||
|
||||
// Special case: handle byte slices more efficiently.
|
||||
cValue := reflect.ValueOf(c)
|
||||
xValue := reflect.ValueOf(m.x)
|
||||
|
||||
if ct == byteSliceType && !cValue.IsNil() && !xValue.IsNil() {
|
||||
xBytes := m.x.([]byte)
|
||||
cBytes := c.([]byte)
|
||||
|
||||
if bytes.Equal(cBytes, xBytes) {
|
||||
return nil
|
||||
}
|
||||
|
||||
return errors.New("")
|
||||
}
|
||||
|
||||
// Defer to the reflect package.
|
||||
if reflect.DeepEqual(m.x, c) {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Special case: if the comparison failed because c is the nil slice, given
|
||||
// an indication of this (since its value is printed as "[]").
|
||||
if cValue.Kind() == reflect.Slice && cValue.IsNil() {
|
||||
return errors.New("which is nil")
|
||||
}
|
||||
|
||||
return errors.New("")
|
||||
}
|
91
vendor/github.com/smartystreets/assertions/internal/oglematchers/elements_are.go
generated
vendored
91
vendor/github.com/smartystreets/assertions/internal/oglematchers/elements_are.go
generated
vendored
@ -0,0 +1,91 @@
|
||||
// Copyright 2012 Aaron Jacobs. All Rights Reserved.
|
||||
// Author: aaronjjacobs@gmail.com (Aaron Jacobs)
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package oglematchers
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Given a list of arguments M, ElementsAre returns a matcher that matches
|
||||
// arrays and slices A where all of the following hold:
|
||||
//
|
||||
// * A is the same length as M.
|
||||
//
|
||||
// * For each i < len(A) where M[i] is a matcher, A[i] matches M[i].
|
||||
//
|
||||
// * For each i < len(A) where M[i] is not a matcher, A[i] matches
|
||||
// Equals(M[i]).
|
||||
//
|
||||
func ElementsAre(M ...interface{}) Matcher {
|
||||
// Copy over matchers, or convert to Equals(x) for non-matcher x.
|
||||
subMatchers := make([]Matcher, len(M))
|
||||
for i, x := range M {
|
||||
if matcher, ok := x.(Matcher); ok {
|
||||
subMatchers[i] = matcher
|
||||
continue
|
||||
}
|
||||
|
||||
subMatchers[i] = Equals(x)
|
||||
}
|
||||
|
||||
return &elementsAreMatcher{subMatchers}
|
||||
}
|
||||
|
||||
type elementsAreMatcher struct {
|
||||
subMatchers []Matcher
|
||||
}
|
||||
|
||||
func (m *elementsAreMatcher) Description() string {
|
||||
subDescs := make([]string, len(m.subMatchers))
|
||||
for i, sm := range m.subMatchers {
|
||||
subDescs[i] = sm.Description()
|
||||
}
|
||||
|
||||
return fmt.Sprintf("elements are: [%s]", strings.Join(subDescs, ", "))
|
||||
}
|
||||
|
||||
func (m *elementsAreMatcher) Matches(candidates interface{}) error {
|
||||
// The candidate must be a slice or an array.
|
||||
v := reflect.ValueOf(candidates)
|
||||
if v.Kind() != reflect.Slice && v.Kind() != reflect.Array {
|
||||
return NewFatalError("which is not a slice or array")
|
||||
}
|
||||
|
||||
// The length must be correct.
|
||||
if v.Len() != len(m.subMatchers) {
|
||||
return errors.New(fmt.Sprintf("which is of length %d", v.Len()))
|
||||
}
|
||||
|
||||
// Check each element.
|
||||
for i, subMatcher := range m.subMatchers {
|
||||
c := v.Index(i)
|
||||
if matchErr := subMatcher.Matches(c.Interface()); matchErr != nil {
|
||||
// Return an errors indicating which element doesn't match. If the
|
||||
// matcher error was fatal, make this one fatal too.
|
||||
err := errors.New(fmt.Sprintf("whose element %d doesn't match", i))
|
||||
if _, isFatal := matchErr.(*FatalError); isFatal {
|
||||
err = NewFatalError(err.Error())
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
@ -0,0 +1,541 @@
|
||||
// Copyright 2011 Aaron Jacobs. All Rights Reserved.
|
||||
// Author: aaronjjacobs@gmail.com (Aaron Jacobs)
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package oglematchers
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"math"
|
||||
"reflect"
|
||||
)
|
||||
|
||||
// Equals(x) returns a matcher that matches values v such that v and x are
|
||||
// equivalent. This includes the case when the comparison v == x using Go's
|
||||
// built-in comparison operator is legal (except for structs, which this
|
||||
// matcher does not support), but for convenience the following rules also
|
||||
// apply:
|
||||
//
|
||||
// * Type checking is done based on underlying types rather than actual
|
||||
// types, so that e.g. two aliases for string can be compared:
|
||||
//
|
||||
// type stringAlias1 string
|
||||
// type stringAlias2 string
|
||||
//
|
||||
// a := "taco"
|
||||
// b := stringAlias1("taco")
|
||||
// c := stringAlias2("taco")
|
||||
//
|
||||
// ExpectTrue(a == b) // Legal, passes
|
||||
// ExpectTrue(b == c) // Illegal, doesn't compile
|
||||
//
|
||||
// ExpectThat(a, Equals(b)) // Passes
|
||||
// ExpectThat(b, Equals(c)) // Passes
|
||||
//
|
||||
// * Values of numeric type are treated as if they were abstract numbers, and
|
||||
// compared accordingly. Therefore Equals(17) will match int(17),
|
||||
// int16(17), uint(17), float32(17), complex64(17), and so on.
|
||||
//
|
||||
// If you want a stricter matcher that contains no such cleverness, see
|
||||
// IdenticalTo instead.
|
||||
//
|
||||
// Arrays are supported by this matcher, but do not participate in the
|
||||
// exceptions above. Two arrays compared with this matcher must have identical
|
||||
// types, and their element type must itself be comparable according to Go's ==
|
||||
// operator.
|
||||
func Equals(x interface{}) Matcher {
|
||||
v := reflect.ValueOf(x)
|
||||
|
||||
// This matcher doesn't support structs.
|
||||
if v.Kind() == reflect.Struct {
|
||||
panic(fmt.Sprintf("oglematchers.Equals: unsupported kind %v", v.Kind()))
|
||||
}
|
||||
|
||||
// The == operator is not defined for non-nil slices.
|
||||
if v.Kind() == reflect.Slice && v.Pointer() != uintptr(0) {
|
||||
panic(fmt.Sprintf("oglematchers.Equals: non-nil slice"))
|
||||
}
|
||||
|
||||
return &equalsMatcher{v}
|
||||
}
|
||||
|
||||
type equalsMatcher struct {
|
||||
expectedValue reflect.Value
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////
|
||||
// Numeric types
|
||||
////////////////////////////////////////////////////////////////////////
|
||||
|
||||
func isSignedInteger(v reflect.Value) bool {
|
||||
k := v.Kind()
|
||||
return k >= reflect.Int && k <= reflect.Int64
|
||||
}
|
||||
|
||||
func isUnsignedInteger(v reflect.Value) bool {
|
||||
k := v.Kind()
|
||||
return k >= reflect.Uint && k <= reflect.Uintptr
|
||||
}
|
||||
|
||||
func isInteger(v reflect.Value) bool {
|
||||
return isSignedInteger(v) || isUnsignedInteger(v)
|
||||
}
|
||||
|
||||
func isFloat(v reflect.Value) bool {
|
||||
k := v.Kind()
|
||||
return k == reflect.Float32 || k == reflect.Float64
|
||||
}
|
||||
|
||||
func isComplex(v reflect.Value) bool {
|
||||
k := v.Kind()
|
||||
return k == reflect.Complex64 || k == reflect.Complex128
|
||||
}
|
||||
|
||||
func checkAgainstInt64(e int64, c reflect.Value) (err error) {
|
||||
err = errors.New("")
|
||||
|
||||
switch {
|
||||
case isSignedInteger(c):
|
||||
if c.Int() == e {
|
||||
err = nil
|
||||
}
|
||||
|
||||
case isUnsignedInteger(c):
|
||||
u := c.Uint()
|
||||
if u <= math.MaxInt64 && int64(u) == e {
|
||||
err = nil
|
||||
}
|
||||
|
||||
// Turn around the various floating point types so that the checkAgainst*
|
||||
// functions for them can deal with precision issues.
|
||||
case isFloat(c), isComplex(c):
|
||||
return Equals(c.Interface()).Matches(e)
|
||||
|
||||
default:
|
||||
err = NewFatalError("which is not numeric")
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func checkAgainstUint64(e uint64, c reflect.Value) (err error) {
|
||||
err = errors.New("")
|
||||
|
||||
switch {
|
||||
case isSignedInteger(c):
|
||||
i := c.Int()
|
||||
if i >= 0 && uint64(i) == e {
|
||||
err = nil
|
||||
}
|
||||
|
||||
case isUnsignedInteger(c):
|
||||
if c.Uint() == e {
|
||||
err = nil
|
||||
}
|
||||
|
||||
// Turn around the various floating point types so that the checkAgainst*
|
||||
// functions for them can deal with precision issues.
|
||||
case isFloat(c), isComplex(c):
|
||||
return Equals(c.Interface()).Matches(e)
|
||||
|
||||
default:
|
||||
err = NewFatalError("which is not numeric")
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func checkAgainstFloat32(e float32, c reflect.Value) (err error) {
|
||||
err = errors.New("")
|
||||
|
||||
switch {
|
||||
case isSignedInteger(c):
|
||||
if float32(c.Int()) == e {
|
||||
err = nil
|
||||
}
|
||||
|
||||
case isUnsignedInteger(c):
|
||||
if float32(c.Uint()) == e {
|
||||
err = nil
|
||||
}
|
||||
|
||||
case isFloat(c):
|
||||
// Compare using float32 to avoid a false sense of precision; otherwise
|
||||
// e.g. Equals(float32(0.1)) won't match float32(0.1).
|
||||
if float32(c.Float()) == e {
|
||||
err = nil
|
||||
}
|
||||
|
||||
case isComplex(c):
|
||||
comp := c.Complex()
|
||||
rl := real(comp)
|
||||
im := imag(comp)
|
||||
|
||||
// Compare using float32 to avoid a false sense of precision; otherwise
|
||||
// e.g. Equals(float32(0.1)) won't match (0.1 + 0i).
|
||||
if im == 0 && float32(rl) == e {
|
||||
err = nil
|
||||
}
|
||||
|
||||
default:
|
||||
err = NewFatalError("which is not numeric")
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func checkAgainstFloat64(e float64, c reflect.Value) (err error) {
|
||||
err = errors.New("")
|
||||
|
||||
ck := c.Kind()
|
||||
|
||||
switch {
|
||||
case isSignedInteger(c):
|
||||
if float64(c.Int()) == e {
|
||||
err = nil
|
||||
}
|
||||
|
||||
case isUnsignedInteger(c):
|
||||
if float64(c.Uint()) == e {
|
||||
err = nil
|
||||
}
|
||||
|
||||
// If the actual value is lower precision, turn the comparison around so we
|
||||
// apply the low-precision rules. Otherwise, e.g. Equals(0.1) may not match
|
||||
// float32(0.1).
|
||||
case ck == reflect.Float32 || ck == reflect.Complex64:
|
||||
return Equals(c.Interface()).Matches(e)
|
||||
|
||||
// Otherwise, compare with double precision.
|
||||
case isFloat(c):
|
||||
if c.Float() == e {
|
||||
err = nil
|
||||
}
|
||||
|
||||
case isComplex(c):
|
||||
comp := c.Complex()
|
||||
rl := real(comp)
|
||||
im := imag(comp)
|
||||
|
||||
if im == 0 && rl == e {
|
||||
err = nil
|
||||
}
|
||||
|
||||
default:
|
||||
err = NewFatalError("which is not numeric")
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func checkAgainstComplex64(e complex64, c reflect.Value) (err error) {
|
||||
err = errors.New("")
|
||||
realPart := real(e)
|
||||
imaginaryPart := imag(e)
|
||||
|
||||
switch {
|
||||
case isInteger(c) || isFloat(c):
|
||||
// If we have no imaginary part, then we should just compare against the
|
||||
// real part. Otherwise, we can't be equal.
|
||||
if imaginaryPart != 0 {
|
||||
return
|
||||
}
|
||||
|
||||
return checkAgainstFloat32(realPart, c)
|
||||
|
||||
case isComplex(c):
|
||||
// Compare using complex64 to avoid a false sense of precision; otherwise
|
||||
// e.g. Equals(0.1 + 0i) won't match float32(0.1).
|
||||
if complex64(c.Complex()) == e {
|
||||
err = nil
|
||||
}
|
||||
|
||||
default:
|
||||
err = NewFatalError("which is not numeric")
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func checkAgainstComplex128(e complex128, c reflect.Value) (err error) {
|
||||
err = errors.New("")
|
||||
realPart := real(e)
|
||||
imaginaryPart := imag(e)
|
||||
|
||||
switch {
|
||||
case isInteger(c) || isFloat(c):
|
||||
// If we have no imaginary part, then we should just compare against the
|
||||
// real part. Otherwise, we can't be equal.
|
||||
if imaginaryPart != 0 {
|
||||
return
|
||||
}
|
||||
|
||||
return checkAgainstFloat64(realPart, c)
|
||||
|
||||
case isComplex(c):
|
||||
if c.Complex() == e {
|
||||
err = nil
|
||||
}
|
||||
|
||||
default:
|
||||
err = NewFatalError("which is not numeric")
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////
|
||||
// Other types
|
||||
////////////////////////////////////////////////////////////////////////
|
||||
|
||||
func checkAgainstBool(e bool, c reflect.Value) (err error) {
|
||||
if c.Kind() != reflect.Bool {
|
||||
err = NewFatalError("which is not a bool")
|
||||
return
|
||||
}
|
||||
|
||||
err = errors.New("")
|
||||
if c.Bool() == e {
|
||||
err = nil
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func checkAgainstChan(e reflect.Value, c reflect.Value) (err error) {
|
||||
// Create a description of e's type, e.g. "chan int".
|
||||
typeStr := fmt.Sprintf("%s %s", e.Type().ChanDir(), e.Type().Elem())
|
||||
|
||||
// Make sure c is a chan of the correct type.
|
||||
if c.Kind() != reflect.Chan ||
|
||||
c.Type().ChanDir() != e.Type().ChanDir() ||
|
||||
c.Type().Elem() != e.Type().Elem() {
|
||||
err = NewFatalError(fmt.Sprintf("which is not a %s", typeStr))
|
||||
return
|
||||
}
|
||||
|
||||
err = errors.New("")
|
||||
if c.Pointer() == e.Pointer() {
|
||||
err = nil
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func checkAgainstFunc(e reflect.Value, c reflect.Value) (err error) {
|
||||
// Make sure c is a function.
|
||||
if c.Kind() != reflect.Func {
|
||||
err = NewFatalError("which is not a function")
|
||||
return
|
||||
}
|
||||
|
||||
err = errors.New("")
|
||||
if c.Pointer() == e.Pointer() {
|
||||
err = nil
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func checkAgainstMap(e reflect.Value, c reflect.Value) (err error) {
|
||||
// Make sure c is a map.
|
||||
if c.Kind() != reflect.Map {
|
||||
err = NewFatalError("which is not a map")
|
||||
return
|
||||
}
|
||||
|
||||
err = errors.New("")
|
||||
if c.Pointer() == e.Pointer() {
|
||||
err = nil
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func checkAgainstPtr(e reflect.Value, c reflect.Value) (err error) {
|
||||
// Create a description of e's type, e.g. "*int".
|
||||
typeStr := fmt.Sprintf("*%v", e.Type().Elem())
|
||||
|
||||
// Make sure c is a pointer of the correct type.
|
||||
if c.Kind() != reflect.Ptr ||
|
||||
c.Type().Elem() != e.Type().Elem() {
|
||||
err = NewFatalError(fmt.Sprintf("which is not a %s", typeStr))
|
||||
return
|
||||
}
|
||||
|
||||
err = errors.New("")
|
||||
if c.Pointer() == e.Pointer() {
|
||||
err = nil
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func checkAgainstSlice(e reflect.Value, c reflect.Value) (err error) {
|
||||
// Create a description of e's type, e.g. "[]int".
|
||||
typeStr := fmt.Sprintf("[]%v", e.Type().Elem())
|
||||
|
||||
// Make sure c is a slice of the correct type.
|
||||
if c.Kind() != reflect.Slice ||
|
||||
c.Type().Elem() != e.Type().Elem() {
|
||||
err = NewFatalError(fmt.Sprintf("which is not a %s", typeStr))
|
||||
return
|
||||
}
|
||||
|
||||
err = errors.New("")
|
||||
if c.Pointer() == e.Pointer() {
|
||||
err = nil
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func checkAgainstString(e reflect.Value, c reflect.Value) (err error) {
|
||||
// Make sure c is a string.
|
||||
if c.Kind() != reflect.String {
|
||||
err = NewFatalError("which is not a string")
|
||||
return
|
||||
}
|
||||
|
||||
err = errors.New("")
|
||||
if c.String() == e.String() {
|
||||
err = nil
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func checkAgainstArray(e reflect.Value, c reflect.Value) (err error) {
|
||||
// Create a description of e's type, e.g. "[2]int".
|
||||
typeStr := fmt.Sprintf("%v", e.Type())
|
||||
|
||||
// Make sure c is the correct type.
|
||||
if c.Type() != e.Type() {
|
||||
err = NewFatalError(fmt.Sprintf("which is not %s", typeStr))
|
||||
return
|
||||
}
|
||||
|
||||
// Check for equality.
|
||||
if e.Interface() != c.Interface() {
|
||||
err = errors.New("")
|
||||
return
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func checkAgainstUnsafePointer(e reflect.Value, c reflect.Value) (err error) {
|
||||
// Make sure c is a pointer.
|
||||
if c.Kind() != reflect.UnsafePointer {
|
||||
err = NewFatalError("which is not a unsafe.Pointer")
|
||||
return
|
||||
}
|
||||
|
||||
err = errors.New("")
|
||||
if c.Pointer() == e.Pointer() {
|
||||
err = nil
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func checkForNil(c reflect.Value) (err error) {
|
||||
err = errors.New("")
|
||||
|
||||
// Make sure it is legal to call IsNil.
|
||||
switch c.Kind() {
|
||||
case reflect.Invalid:
|
||||
case reflect.Chan:
|
||||
case reflect.Func:
|
||||
case reflect.Interface:
|
||||
case reflect.Map:
|
||||
case reflect.Ptr:
|
||||
case reflect.Slice:
|
||||
|
||||
default:
|
||||
err = NewFatalError("which cannot be compared to nil")
|
||||
return
|
||||
}
|
||||
|
||||
// Ask whether the value is nil. Handle a nil literal (kind Invalid)
|
||||
// specially, since it's not legal to call IsNil there.
|
||||
if c.Kind() == reflect.Invalid || c.IsNil() {
|
||||
err = nil
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////
|
||||
// Public implementation
|
||||
////////////////////////////////////////////////////////////////////////
|
||||
|
||||
func (m *equalsMatcher) Matches(candidate interface{}) error {
|
||||
e := m.expectedValue
|
||||
c := reflect.ValueOf(candidate)
|
||||
ek := e.Kind()
|
||||
|
||||
switch {
|
||||
case ek == reflect.Bool:
|
||||
return checkAgainstBool(e.Bool(), c)
|
||||
|
||||
case isSignedInteger(e):
|
||||
return checkAgainstInt64(e.Int(), c)
|
||||
|
||||
case isUnsignedInteger(e):
|
||||
return checkAgainstUint64(e.Uint(), c)
|
||||
|
||||
case ek == reflect.Float32:
|
||||
return checkAgainstFloat32(float32(e.Float()), c)
|
||||
|
||||
case ek == reflect.Float64:
|
||||
return checkAgainstFloat64(e.Float(), c)
|
||||
|
||||
case ek == reflect.Complex64:
|
||||
return checkAgainstComplex64(complex64(e.Complex()), c)
|
||||
|
||||
case ek == reflect.Complex128:
|
||||
return checkAgainstComplex128(complex128(e.Complex()), c)
|
||||
|
||||
case ek == reflect.Chan:
|
||||
return checkAgainstChan(e, c)
|
||||
|
||||
case ek == reflect.Func:
|
||||
return checkAgainstFunc(e, c)
|
||||
|
||||
case ek == reflect.Map:
|
||||
return checkAgainstMap(e, c)
|
||||
|
||||
case ek == reflect.Ptr:
|
||||
return checkAgainstPtr(e, c)
|
||||
|
||||
case ek == reflect.Slice:
|
||||
return checkAgainstSlice(e, c)
|
||||
|
||||
case ek == reflect.String:
|
||||
return checkAgainstString(e, c)
|
||||
|
||||
case ek == reflect.Array:
|
||||
return checkAgainstArray(e, c)
|
||||
|
||||
case ek == reflect.UnsafePointer:
|
||||
return checkAgainstUnsafePointer(e, c)
|
||||
|
||||
case ek == reflect.Invalid:
|
||||
return checkForNil(c)
|
||||
}
|
||||
|
||||
panic(fmt.Sprintf("equalsMatcher.Matches: unexpected kind: %v", ek))
|
||||
}
|
||||
|
||||
func (m *equalsMatcher) Description() string {
|
||||
// Special case: handle nil.
|
||||
if !m.expectedValue.IsValid() {
|
||||
return "is nil"
|
||||
}
|
||||
|
||||
return fmt.Sprintf("%v", m.expectedValue.Interface())
|
||||
}
|
@ -0,0 +1,51 @@
|
||||
// Copyright 2011 Aaron Jacobs. All Rights Reserved.
|
||||
// Author: aaronjjacobs@gmail.com (Aaron Jacobs)
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package oglematchers
|
||||
|
||||
// Error returns a matcher that matches non-nil values implementing the
|
||||
// built-in error interface for whom the return value of Error() matches the
|
||||
// supplied matcher.
|
||||
//
|
||||
// For example:
|
||||
//
|
||||
// err := errors.New("taco burrito")
|
||||
//
|
||||
// Error(Equals("taco burrito")) // matches err
|
||||
// Error(HasSubstr("taco")) // matches err
|
||||
// Error(HasSubstr("enchilada")) // doesn't match err
|
||||
//
|
||||
func Error(m Matcher) Matcher {
|
||||
return &errorMatcher{m}
|
||||
}
|
||||
|
||||
type errorMatcher struct {
|
||||
wrappedMatcher Matcher
|
||||
}
|
||||
|
||||
func (m *errorMatcher) Description() string {
|
||||
return "error " + m.wrappedMatcher.Description()
|
||||
}
|
||||
|
||||
func (m *errorMatcher) Matches(c interface{}) error {
|
||||
// Make sure that c is an error.
|
||||
e, ok := c.(error)
|
||||
if !ok {
|
||||
return NewFatalError("which is not an error")
|
||||
}
|
||||
|
||||
// Pass on the error text to the wrapped matcher.
|
||||
return m.wrappedMatcher.Matches(e.Error())
|
||||
}
|
39
vendor/github.com/smartystreets/assertions/internal/oglematchers/greater_or_equal.go
generated
vendored
39
vendor/github.com/smartystreets/assertions/internal/oglematchers/greater_or_equal.go
generated
vendored
@ -0,0 +1,39 @@
|
||||
// Copyright 2011 Aaron Jacobs. All Rights Reserved.
|
||||
// Author: aaronjjacobs@gmail.com (Aaron Jacobs)
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package oglematchers
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
)
|
||||
|
||||
// GreaterOrEqual returns a matcher that matches integer, floating point, or
|
||||
// strings values v such that v >= x. Comparison is not defined between numeric
|
||||
// and string types, but is defined between all integer and floating point
|
||||
// types.
|
||||
//
|
||||
// x must itself be an integer, floating point, or string type; otherwise,
|
||||
// GreaterOrEqual will panic.
|
||||
func GreaterOrEqual(x interface{}) Matcher {
|
||||
desc := fmt.Sprintf("greater than or equal to %v", x)
|
||||
|
||||
// Special case: make it clear that strings are strings.
|
||||
if reflect.TypeOf(x).Kind() == reflect.String {
|
||||
desc = fmt.Sprintf("greater than or equal to \"%s\"", x)
|
||||
}
|
||||
|
||||
return transformDescription(Not(LessThan(x)), desc)
|
||||
}
|
39
vendor/github.com/smartystreets/assertions/internal/oglematchers/greater_than.go
generated
vendored
39
vendor/github.com/smartystreets/assertions/internal/oglematchers/greater_than.go
generated
vendored
@ -0,0 +1,39 @@
|
||||
// Copyright 2011 Aaron Jacobs. All Rights Reserved.
|
||||
// Author: aaronjjacobs@gmail.com (Aaron Jacobs)
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package oglematchers
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
)
|
||||
|
||||
// GreaterThan returns a matcher that matches integer, floating point, or
|
||||
// strings values v such that v > x. Comparison is not defined between numeric
|
||||
// and string types, but is defined between all integer and floating point
|
||||
// types.
|
||||
//
|
||||
// x must itself be an integer, floating point, or string type; otherwise,
|
||||
// GreaterThan will panic.
|
||||
func GreaterThan(x interface{}) Matcher {
|
||||
desc := fmt.Sprintf("greater than %v", x)
|
||||
|
||||
// Special case: make it clear that strings are strings.
|
||||
if reflect.TypeOf(x).Kind() == reflect.String {
|
||||
desc = fmt.Sprintf("greater than \"%s\"", x)
|
||||
}
|
||||
|
||||
return transformDescription(Not(LessOrEqual(x)), desc)
|
||||
}
|
37
vendor/github.com/smartystreets/assertions/internal/oglematchers/has_same_type_as.go
generated
vendored
37
vendor/github.com/smartystreets/assertions/internal/oglematchers/has_same_type_as.go
generated
vendored
@ -0,0 +1,37 @@
|
||||
// Copyright 2015 Aaron Jacobs. All Rights Reserved.
|
||||
// Author: aaronjjacobs@gmail.com (Aaron Jacobs)
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package oglematchers
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
)
|
||||
|
||||
// HasSameTypeAs returns a matcher that matches values with exactly the same
|
||||
// type as the supplied prototype.
|
||||
func HasSameTypeAs(p interface{}) Matcher {
|
||||
expected := reflect.TypeOf(p)
|
||||
pred := func(c interface{}) error {
|
||||
actual := reflect.TypeOf(c)
|
||||
if actual != expected {
|
||||
return fmt.Errorf("which has type %v", actual)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
return NewMatcher(pred, fmt.Sprintf("has type %v", expected))
|
||||
}
|
46
vendor/github.com/smartystreets/assertions/internal/oglematchers/has_substr.go
generated
vendored
46
vendor/github.com/smartystreets/assertions/internal/oglematchers/has_substr.go
generated
vendored
@ -0,0 +1,46 @@
|
||||
// Copyright 2011 Aaron Jacobs. All Rights Reserved.
|
||||
// Author: aaronjjacobs@gmail.com (Aaron Jacobs)
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package oglematchers
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// HasSubstr returns a matcher that matches strings containing s as a
|
||||
// substring.
|
||||
func HasSubstr(s string) Matcher {
|
||||
return NewMatcher(
|
||||
func(c interface{}) error { return hasSubstr(s, c) },
|
||||
fmt.Sprintf("has substring \"%s\"", s))
|
||||
}
|
||||
|
||||
func hasSubstr(needle string, c interface{}) error {
|
||||
v := reflect.ValueOf(c)
|
||||
if v.Kind() != reflect.String {
|
||||
return NewFatalError("which is not a string")
|
||||
}
|
||||
|
||||
// Perform the substring search.
|
||||
haystack := v.String()
|
||||
if strings.Contains(haystack, needle) {
|
||||
return nil
|
||||
}
|
||||
|
||||
return errors.New("")
|
||||
}
|
134
vendor/github.com/smartystreets/assertions/internal/oglematchers/identical_to.go
generated
vendored
134
vendor/github.com/smartystreets/assertions/internal/oglematchers/identical_to.go
generated
vendored
@ -0,0 +1,134 @@
|
||||
// Copyright 2012 Aaron Jacobs. All Rights Reserved.
|
||||
// Author: aaronjjacobs@gmail.com (Aaron Jacobs)
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package oglematchers
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"reflect"
|
||||
)
|
||||
|
||||
// Is the type comparable according to the definition here?
|
||||
//
|
||||
// http://weekly.golang.org/doc/go_spec.html#Comparison_operators
|
||||
//
|
||||
func isComparable(t reflect.Type) bool {
|
||||
switch t.Kind() {
|
||||
case reflect.Array:
|
||||
return isComparable(t.Elem())
|
||||
|
||||
case reflect.Struct:
|
||||
for i := 0; i < t.NumField(); i++ {
|
||||
if !isComparable(t.Field(i).Type) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
|
||||
case reflect.Slice, reflect.Map, reflect.Func:
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// Should the supplied type be allowed as an argument to IdenticalTo?
|
||||
func isLegalForIdenticalTo(t reflect.Type) (bool, error) {
|
||||
// Allow the zero type.
|
||||
if t == nil {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// Reference types are always okay; we compare pointers.
|
||||
switch t.Kind() {
|
||||
case reflect.Slice, reflect.Map, reflect.Func, reflect.Chan:
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// Reject other non-comparable types.
|
||||
if !isComparable(t) {
|
||||
return false, errors.New(fmt.Sprintf("%v is not comparable", t))
|
||||
}
|
||||
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// IdenticalTo(x) returns a matcher that matches values v with type identical
|
||||
// to x such that:
|
||||
//
|
||||
// 1. If v and x are of a reference type (slice, map, function, channel), then
|
||||
// they are either both nil or are references to the same object.
|
||||
//
|
||||
// 2. Otherwise, if v and x are not of a reference type but have a valid type,
|
||||
// then v == x.
|
||||
//
|
||||
// If v and x are both the invalid type (which results from the predeclared nil
|
||||
// value, or from nil interface variables), then the matcher is satisfied.
|
||||
//
|
||||
// This function will panic if x is of a value type that is not comparable. For
|
||||
// example, x cannot be an array of functions.
|
||||
func IdenticalTo(x interface{}) Matcher {
|
||||
t := reflect.TypeOf(x)
|
||||
|
||||
// Reject illegal arguments.
|
||||
if ok, err := isLegalForIdenticalTo(t); !ok {
|
||||
panic("IdenticalTo: " + err.Error())
|
||||
}
|
||||
|
||||
return &identicalToMatcher{x}
|
||||
}
|
||||
|
||||
type identicalToMatcher struct {
|
||||
x interface{}
|
||||
}
|
||||
|
||||
func (m *identicalToMatcher) Description() string {
|
||||
t := reflect.TypeOf(m.x)
|
||||
return fmt.Sprintf("identical to <%v> %v", t, m.x)
|
||||
}
|
||||
|
||||
func (m *identicalToMatcher) Matches(c interface{}) error {
|
||||
// Make sure the candidate's type is correct.
|
||||
t := reflect.TypeOf(m.x)
|
||||
if ct := reflect.TypeOf(c); t != ct {
|
||||
return NewFatalError(fmt.Sprintf("which is of type %v", ct))
|
||||
}
|
||||
|
||||
// Special case: two values of the invalid type are always identical.
|
||||
if t == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Handle reference types.
|
||||
switch t.Kind() {
|
||||
case reflect.Slice, reflect.Map, reflect.Func, reflect.Chan:
|
||||
xv := reflect.ValueOf(m.x)
|
||||
cv := reflect.ValueOf(c)
|
||||
if xv.Pointer() == cv.Pointer() {
|
||||
return nil
|
||||
}
|
||||
|
||||
return errors.New("which is not an identical reference")
|
||||
}
|
||||
|
||||
// Are the values equal?
|
||||
if m.x == c {
|
||||
return nil
|
||||
}
|
||||
|
||||
return errors.New("")
|
||||
}
|
41
vendor/github.com/smartystreets/assertions/internal/oglematchers/less_or_equal.go
generated
vendored
41
vendor/github.com/smartystreets/assertions/internal/oglematchers/less_or_equal.go
generated
vendored
@ -0,0 +1,41 @@
|
||||
// Copyright 2011 Aaron Jacobs. All Rights Reserved.
|
||||
// Author: aaronjjacobs@gmail.com (Aaron Jacobs)
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package oglematchers
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
)
|
||||
|
||||
// LessOrEqual returns a matcher that matches integer, floating point, or
|
||||
// strings values v such that v <= x. Comparison is not defined between numeric
|
||||
// and string types, but is defined between all integer and floating point
|
||||
// types.
|
||||
//
|
||||
// x must itself be an integer, floating point, or string type; otherwise,
|
||||
// LessOrEqual will panic.
|
||||
func LessOrEqual(x interface{}) Matcher {
|
||||
desc := fmt.Sprintf("less than or equal to %v", x)
|
||||
|
||||
// Special case: make it clear that strings are strings.
|
||||
if reflect.TypeOf(x).Kind() == reflect.String {
|
||||
desc = fmt.Sprintf("less than or equal to \"%s\"", x)
|
||||
}
|
||||
|
||||
// Put LessThan last so that its error messages will be used in the event of
|
||||
// failure.
|
||||
return transformDescription(AnyOf(Equals(x), LessThan(x)), desc)
|
||||
}
|
152
vendor/github.com/smartystreets/assertions/internal/oglematchers/less_than.go
generated
vendored
152
vendor/github.com/smartystreets/assertions/internal/oglematchers/less_than.go
generated
vendored
@ -0,0 +1,152 @@
|
||||
// Copyright 2011 Aaron Jacobs. All Rights Reserved.
|
||||
// Author: aaronjjacobs@gmail.com (Aaron Jacobs)
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package oglematchers
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"math"
|
||||
"reflect"
|
||||
)
|
||||
|
||||
// LessThan returns a matcher that matches integer, floating point, or strings
|
||||
// values v such that v < x. Comparison is not defined between numeric and
|
||||
// string types, but is defined between all integer and floating point types.
|
||||
//
|
||||
// x must itself be an integer, floating point, or string type; otherwise,
|
||||
// LessThan will panic.
|
||||
func LessThan(x interface{}) Matcher {
|
||||
v := reflect.ValueOf(x)
|
||||
kind := v.Kind()
|
||||
|
||||
switch {
|
||||
case isInteger(v):
|
||||
case isFloat(v):
|
||||
case kind == reflect.String:
|
||||
|
||||
default:
|
||||
panic(fmt.Sprintf("LessThan: unexpected kind %v", kind))
|
||||
}
|
||||
|
||||
return &lessThanMatcher{v}
|
||||
}
|
||||
|
||||
type lessThanMatcher struct {
|
||||
limit reflect.Value
|
||||
}
|
||||
|
||||
func (m *lessThanMatcher) Description() string {
|
||||
// Special case: make it clear that strings are strings.
|
||||
if m.limit.Kind() == reflect.String {
|
||||
return fmt.Sprintf("less than \"%s\"", m.limit.String())
|
||||
}
|
||||
|
||||
return fmt.Sprintf("less than %v", m.limit.Interface())
|
||||
}
|
||||
|
||||
func compareIntegers(v1, v2 reflect.Value) (err error) {
|
||||
err = errors.New("")
|
||||
|
||||
switch {
|
||||
case isSignedInteger(v1) && isSignedInteger(v2):
|
||||
if v1.Int() < v2.Int() {
|
||||
err = nil
|
||||
}
|
||||
return
|
||||
|
||||
case isSignedInteger(v1) && isUnsignedInteger(v2):
|
||||
if v1.Int() < 0 || uint64(v1.Int()) < v2.Uint() {
|
||||
err = nil
|
||||
}
|
||||
return
|
||||
|
||||
case isUnsignedInteger(v1) && isSignedInteger(v2):
|
||||
if v1.Uint() <= math.MaxInt64 && int64(v1.Uint()) < v2.Int() {
|
||||
err = nil
|
||||
}
|
||||
return
|
||||
|
||||
case isUnsignedInteger(v1) && isUnsignedInteger(v2):
|
||||
if v1.Uint() < v2.Uint() {
|
||||
err = nil
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
panic(fmt.Sprintf("compareIntegers: %v %v", v1, v2))
|
||||
}
|
||||
|
||||
func getFloat(v reflect.Value) float64 {
|
||||
switch {
|
||||
case isSignedInteger(v):
|
||||
return float64(v.Int())
|
||||
|
||||
case isUnsignedInteger(v):
|
||||
return float64(v.Uint())
|
||||
|
||||
case isFloat(v):
|
||||
return v.Float()
|
||||
}
|
||||
|
||||
panic(fmt.Sprintf("getFloat: %v", v))
|
||||
}
|
||||
|
||||
func (m *lessThanMatcher) Matches(c interface{}) (err error) {
|
||||
v1 := reflect.ValueOf(c)
|
||||
v2 := m.limit
|
||||
|
||||
err = errors.New("")
|
||||
|
||||
// Handle strings as a special case.
|
||||
if v1.Kind() == reflect.String && v2.Kind() == reflect.String {
|
||||
if v1.String() < v2.String() {
|
||||
err = nil
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// If we get here, we require that we are dealing with integers or floats.
|
||||
v1Legal := isInteger(v1) || isFloat(v1)
|
||||
v2Legal := isInteger(v2) || isFloat(v2)
|
||||
if !v1Legal || !v2Legal {
|
||||
err = NewFatalError("which is not comparable")
|
||||
return
|
||||
}
|
||||
|
||||
// Handle the various comparison cases.
|
||||
switch {
|
||||
// Both integers
|
||||
case isInteger(v1) && isInteger(v2):
|
||||
return compareIntegers(v1, v2)
|
||||
|
||||
// At least one float32
|
||||
case v1.Kind() == reflect.Float32 || v2.Kind() == reflect.Float32:
|
||||
if float32(getFloat(v1)) < float32(getFloat(v2)) {
|
||||
err = nil
|
||||
}
|
||||
return
|
||||
|
||||
// At least one float64
|
||||
case v1.Kind() == reflect.Float64 || v2.Kind() == reflect.Float64:
|
||||
if getFloat(v1) < getFloat(v2) {
|
||||
err = nil
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// We shouldn't get here.
|
||||
panic(fmt.Sprintf("lessThanMatcher.Matches: Shouldn't get here: %v %v", v1, v2))
|
||||
}
|
@ -0,0 +1,86 @@
|
||||
// Copyright 2011 Aaron Jacobs. All Rights Reserved.
|
||||
// Author: aaronjjacobs@gmail.com (Aaron Jacobs)
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// Package oglematchers provides a set of matchers useful in a testing or
|
||||
// mocking framework. These matchers are inspired by and mostly compatible with
|
||||
// Google Test for C++ and Google JS Test.
|
||||
//
|
||||
// This package is used by github.com/smartystreets/assertions/internal/ogletest and
|
||||
// github.com/smartystreets/assertions/internal/oglemock, which may be more directly useful if you're not
|
||||
// writing your own testing package or defining your own matchers.
|
||||
package oglematchers
|
||||
|
||||
// A Matcher is some predicate implicitly defining a set of values that it
|
||||
// matches. For example, GreaterThan(17) matches all numeric values greater
|
||||
// than 17, and HasSubstr("taco") matches all strings with the substring
|
||||
// "taco".
|
||||
//
|
||||
// Matchers are typically exposed to tests via constructor functions like
|
||||
// HasSubstr. In order to implement such a function you can either define your
|
||||
// own matcher type or use NewMatcher.
|
||||
type Matcher interface {
|
||||
// Check whether the supplied value belongs to the the set defined by the
|
||||
// matcher. Return a non-nil error if and only if it does not.
|
||||
//
|
||||
// The error describes why the value doesn't match. The error text is a
|
||||
// relative clause that is suitable for being placed after the value. For
|
||||
// example, a predicate that matches strings with a particular substring may,
|
||||
// when presented with a numerical value, return the following error text:
|
||||
//
|
||||
// "which is not a string"
|
||||
//
|
||||
// Then the failure message may look like:
|
||||
//
|
||||
// Expected: has substring "taco"
|
||||
// Actual: 17, which is not a string
|
||||
//
|
||||
// If the error is self-apparent based on the description of the matcher, the
|
||||
// error text may be empty (but the error still non-nil). For example:
|
||||
//
|
||||
// Expected: 17
|
||||
// Actual: 19
|
||||
//
|
||||
// If you are implementing a new matcher, see also the documentation on
|
||||
// FatalError.
|
||||
Matches(candidate interface{}) error
|
||||
|
||||
// Description returns a string describing the property that values matching
|
||||
// this matcher have, as a verb phrase where the subject is the value. For
|
||||
// example, "is greather than 17" or "has substring "taco"".
|
||||
Description() string
|
||||
}
|
||||
|
||||
// FatalError is an implementation of the error interface that may be returned
|
||||
// from matchers, indicating the error should be propagated. Returning a
|
||||
// *FatalError indicates that the matcher doesn't process values of the
|
||||
// supplied type, or otherwise doesn't know how to handle the value.
|
||||
//
|
||||
// For example, if GreaterThan(17) returned false for the value "taco" without
|
||||
// a fatal error, then Not(GreaterThan(17)) would return true. This is
|
||||
// technically correct, but is surprising and may mask failures where the wrong
|
||||
// sort of matcher is accidentally used. Instead, GreaterThan(17) can return a
|
||||
// fatal error, which will be propagated by Not().
|
||||
type FatalError struct {
|
||||
errorText string
|
||||
}
|
||||
|
||||
// NewFatalError creates a FatalError struct with the supplied error text.
|
||||
func NewFatalError(s string) *FatalError {
|
||||
return &FatalError{s}
|
||||
}
|
||||
|
||||
func (e *FatalError) Error() string {
|
||||
return e.errorText
|
||||
}
|
69
vendor/github.com/smartystreets/assertions/internal/oglematchers/matches_regexp.go
generated
vendored
69
vendor/github.com/smartystreets/assertions/internal/oglematchers/matches_regexp.go
generated
vendored
@ -0,0 +1,69 @@
|
||||
// Copyright 2011 Aaron Jacobs. All Rights Reserved.
|
||||
// Author: aaronjjacobs@gmail.com (Aaron Jacobs)
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package oglematchers
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"regexp"
|
||||
)
|
||||
|
||||
// MatchesRegexp returns a matcher that matches strings and byte slices whose
|
||||
// contents match the supplied regular expression. The semantics are those of
|
||||
// regexp.Match. In particular, that means the match is not implicitly anchored
|
||||
// to the ends of the string: MatchesRegexp("bar") will match "foo bar baz".
|
||||
func MatchesRegexp(pattern string) Matcher {
|
||||
re, err := regexp.Compile(pattern)
|
||||
if err != nil {
|
||||
panic("MatchesRegexp: " + err.Error())
|
||||
}
|
||||
|
||||
return &matchesRegexpMatcher{re}
|
||||
}
|
||||
|
||||
type matchesRegexpMatcher struct {
|
||||
re *regexp.Regexp
|
||||
}
|
||||
|
||||
func (m *matchesRegexpMatcher) Description() string {
|
||||
return fmt.Sprintf("matches regexp \"%s\"", m.re.String())
|
||||
}
|
||||
|
||||
func (m *matchesRegexpMatcher) Matches(c interface{}) (err error) {
|
||||
v := reflect.ValueOf(c)
|
||||
isString := v.Kind() == reflect.String
|
||||
isByteSlice := v.Kind() == reflect.Slice && v.Elem().Kind() == reflect.Uint8
|
||||
|
||||
err = errors.New("")
|
||||
|
||||
switch {
|
||||
case isString:
|
||||
if m.re.MatchString(v.String()) {
|
||||
err = nil
|
||||
}
|
||||
|
||||
case isByteSlice:
|
||||
if m.re.Match(v.Bytes()) {
|
||||
err = nil
|
||||
}
|
||||
|
||||
default:
|
||||
err = NewFatalError("which is not a string or []byte")
|
||||
}
|
||||
|
||||
return
|
||||
}
|
43
vendor/github.com/smartystreets/assertions/internal/oglematchers/new_matcher.go
generated
vendored
43
vendor/github.com/smartystreets/assertions/internal/oglematchers/new_matcher.go
generated
vendored
@ -0,0 +1,43 @@
|
||||
// Copyright 2015 Aaron Jacobs. All Rights Reserved.
|
||||
// Author: aaronjjacobs@gmail.com (Aaron Jacobs)
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package oglematchers
|
||||
|
||||
// Create a matcher with the given description and predicate function, which
|
||||
// will be invoked to handle calls to Matchers.
|
||||
//
|
||||
// Using this constructor may be a convenience over defining your own type that
|
||||
// implements Matcher if you do not need any logic in your Description method.
|
||||
func NewMatcher(
|
||||
predicate func(interface{}) error,
|
||||
description string) Matcher {
|
||||
return &predicateMatcher{
|
||||
predicate: predicate,
|
||||
description: description,
|
||||
}
|
||||
}
|
||||
|
||||
type predicateMatcher struct {
|
||||
predicate func(interface{}) error
|
||||
description string
|
||||
}
|
||||
|
||||
func (pm *predicateMatcher) Matches(c interface{}) error {
|
||||
return pm.predicate(c)
|
||||
}
|
||||
|
||||
func (pm *predicateMatcher) Description() string {
|
||||
return pm.description
|
||||
}
|
@ -0,0 +1,53 @@
|
||||
// Copyright 2011 Aaron Jacobs. All Rights Reserved.
|
||||
// Author: aaronjjacobs@gmail.com (Aaron Jacobs)
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package oglematchers
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// Not returns a matcher that inverts the set of values matched by the wrapped
|
||||
// matcher. It does not transform the result for values for which the wrapped
|
||||
// matcher returns a fatal error.
|
||||
func Not(m Matcher) Matcher {
|
||||
return ¬Matcher{m}
|
||||
}
|
||||
|
||||
type notMatcher struct {
|
||||
wrapped Matcher
|
||||
}
|
||||
|
||||
func (m *notMatcher) Matches(c interface{}) (err error) {
|
||||
err = m.wrapped.Matches(c)
|
||||
|
||||
// Did the wrapped matcher say yes?
|
||||
if err == nil {
|
||||
return errors.New("")
|
||||
}
|
||||
|
||||
// Did the wrapped matcher return a fatal error?
|
||||
if _, isFatal := err.(*FatalError); isFatal {
|
||||
return err
|
||||
}
|
||||
|
||||
// The wrapped matcher returned a non-fatal error.
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *notMatcher) Description() string {
|
||||
return fmt.Sprintf("not(%s)", m.wrapped.Description())
|
||||
}
|
@ -0,0 +1,74 @@
|
||||
// Copyright 2011 Aaron Jacobs. All Rights Reserved.
|
||||
// Author: aaronjjacobs@gmail.com (Aaron Jacobs)
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package oglematchers
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"reflect"
|
||||
)
|
||||
|
||||
// Panics matches zero-arg functions which, when invoked, panic with an error
|
||||
// that matches the supplied matcher.
|
||||
//
|
||||
// NOTE(jacobsa): This matcher cannot detect the case where the function panics
|
||||
// using panic(nil), by design of the language. See here for more info:
|
||||
//
|
||||
// http://goo.gl/9aIQL
|
||||
//
|
||||
func Panics(m Matcher) Matcher {
|
||||
return &panicsMatcher{m}
|
||||
}
|
||||
|
||||
type panicsMatcher struct {
|
||||
wrappedMatcher Matcher
|
||||
}
|
||||
|
||||
func (m *panicsMatcher) Description() string {
|
||||
return "panics with: " + m.wrappedMatcher.Description()
|
||||
}
|
||||
|
||||
func (m *panicsMatcher) Matches(c interface{}) (err error) {
|
||||
// Make sure c is a zero-arg function.
|
||||
v := reflect.ValueOf(c)
|
||||
if v.Kind() != reflect.Func || v.Type().NumIn() != 0 {
|
||||
err = NewFatalError("which is not a zero-arg function")
|
||||
return
|
||||
}
|
||||
|
||||
// Call the function and check its panic error.
|
||||
defer func() {
|
||||
if e := recover(); e != nil {
|
||||
err = m.wrappedMatcher.Matches(e)
|
||||
|
||||
// Set a clearer error message if the matcher said no.
|
||||
if err != nil {
|
||||
wrappedClause := ""
|
||||
if err.Error() != "" {
|
||||
wrappedClause = ", " + err.Error()
|
||||
}
|
||||
|
||||
err = errors.New(fmt.Sprintf("which panicked with: %v%s", e, wrappedClause))
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
v.Call([]reflect.Value{})
|
||||
|
||||
// If we get here, the function didn't panic.
|
||||
err = errors.New("which didn't panic")
|
||||
return
|
||||
}
|
@ -0,0 +1,65 @@
|
||||
// Copyright 2012 Aaron Jacobs. All Rights Reserved.
|
||||
// Author: aaronjjacobs@gmail.com (Aaron Jacobs)
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package oglematchers
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"reflect"
|
||||
)
|
||||
|
||||
// Return a matcher that matches non-nil pointers whose pointee matches the
|
||||
// wrapped matcher.
|
||||
func Pointee(m Matcher) Matcher {
|
||||
return &pointeeMatcher{m}
|
||||
}
|
||||
|
||||
type pointeeMatcher struct {
|
||||
wrapped Matcher
|
||||
}
|
||||
|
||||
func (m *pointeeMatcher) Matches(c interface{}) (err error) {
|
||||
// Make sure the candidate is of the appropriate type.
|
||||
cv := reflect.ValueOf(c)
|
||||
if !cv.IsValid() || cv.Kind() != reflect.Ptr {
|
||||
return NewFatalError("which is not a pointer")
|
||||
}
|
||||
|
||||
// Make sure the candidate is non-nil.
|
||||
if cv.IsNil() {
|
||||
return NewFatalError("")
|
||||
}
|
||||
|
||||
// Defer to the wrapped matcher. Fix up empty errors so that failure messages
|
||||
// are more helpful than just printing a pointer for "Actual".
|
||||
pointee := cv.Elem().Interface()
|
||||
err = m.wrapped.Matches(pointee)
|
||||
if err != nil && err.Error() == "" {
|
||||
s := fmt.Sprintf("whose pointee is %v", pointee)
|
||||
|
||||
if _, ok := err.(*FatalError); ok {
|
||||
err = NewFatalError(s)
|
||||
} else {
|
||||
err = errors.New(s)
|
||||
}
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (m *pointeeMatcher) Description() string {
|
||||
return fmt.Sprintf("pointee(%s)", m.wrapped.Description())
|
||||
}
|
@ -0,0 +1,36 @@
|
||||
// Copyright 2011 Aaron Jacobs. All Rights Reserved.
|
||||
// Author: aaronjjacobs@gmail.com (Aaron Jacobs)
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package oglematchers
|
||||
|
||||
// transformDescription returns a matcher that is equivalent to the supplied
|
||||
// one, except that it has the supplied description instead of the one attached
|
||||
// to the existing matcher.
|
||||
func transformDescription(m Matcher, newDesc string) Matcher {
|
||||
return &transformDescriptionMatcher{newDesc, m}
|
||||
}
|
||||
|
||||
type transformDescriptionMatcher struct {
|
||||
desc string
|
||||
wrappedMatcher Matcher
|
||||
}
|
||||
|
||||
func (m *transformDescriptionMatcher) Description() string {
|
||||
return m.desc
|
||||
}
|
||||
|
||||
func (m *transformDescriptionMatcher) Matches(c interface{}) error {
|
||||
return m.wrappedMatcher.Matches(c)
|
||||
}
|
@ -0,0 +1,94 @@
|
||||
package assertions
|
||||
|
||||
const ( // equality
|
||||
shouldHaveBeenEqual = "Expected: '%v'\nActual: '%v'\n(Should be equal)"
|
||||
shouldNotHaveBeenEqual = "Expected '%v'\nto NOT equal '%v'\n(but it did)!"
|
||||
shouldHaveBeenEqualTypeMismatch = "Expected: '%v' (%T)\nActual: '%v' (%T)\n(Should be equal, type mismatch)"
|
||||
shouldHaveBeenAlmostEqual = "Expected '%v' to almost equal '%v' (but it didn't)!"
|
||||
shouldHaveNotBeenAlmostEqual = "Expected '%v' to NOT almost equal '%v' (but it did)!"
|
||||
shouldHaveResembled = "Expected: '%#v'\nActual: '%#v'\n(Should resemble)!"
|
||||
shouldHaveResembledTypeMismatch = "Expected: '%#v' (%T)\nActual: '%#v' (%T)\n(Should resemble, type mismatch)"
|
||||
shouldNotHaveResembled = "Expected '%#v'\nto NOT resemble '%#v'\n(but it did)!"
|
||||
shouldBePointers = "Both arguments should be pointers "
|
||||
shouldHaveBeenNonNilPointer = shouldBePointers + "(the %s was %s)!"
|
||||
shouldHavePointedTo = "Expected '%+v' (address: '%v') and '%+v' (address: '%v') to be the same address (but their weren't)!"
|
||||
shouldNotHavePointedTo = "Expected '%+v' and '%+v' to be different references (but they matched: '%v')!"
|
||||
shouldHaveBeenNil = "Expected: nil\nActual: '%v'"
|
||||
shouldNotHaveBeenNil = "Expected '%+v' to NOT be nil (but it was)!"
|
||||
shouldHaveBeenTrue = "Expected: true\nActual: %v"
|
||||
shouldHaveBeenFalse = "Expected: false\nActual: %v"
|
||||
shouldHaveBeenZeroValue = "'%+v' should have been the zero value" //"Expected: (zero value)\nActual: %v"
|
||||
)
|
||||
|
||||
const ( // quantity comparisons
|
||||
shouldHaveBeenGreater = "Expected '%v' to be greater than '%v' (but it wasn't)!"
|
||||
shouldHaveBeenGreaterOrEqual = "Expected '%v' to be greater than or equal to '%v' (but it wasn't)!"
|
||||
shouldHaveBeenLess = "Expected '%v' to be less than '%v' (but it wasn't)!"
|
||||
shouldHaveBeenLessOrEqual = "Expected '%v' to be less than or equal to '%v' (but it wasn't)!"
|
||||
shouldHaveBeenBetween = "Expected '%v' to be between '%v' and '%v' (but it wasn't)!"
|
||||
shouldNotHaveBeenBetween = "Expected '%v' NOT to be between '%v' and '%v' (but it was)!"
|
||||
shouldHaveDifferentUpperAndLower = "The lower and upper bounds must be different values (they were both '%v')."
|
||||
shouldHaveBeenBetweenOrEqual = "Expected '%v' to be between '%v' and '%v' or equal to one of them (but it wasn't)!"
|
||||
shouldNotHaveBeenBetweenOrEqual = "Expected '%v' NOT to be between '%v' and '%v' or equal to one of them (but it was)!"
|
||||
)
|
||||
|
||||
const ( // collections
|
||||
shouldHaveContained = "Expected the container (%v) to contain: '%v' (but it didn't)!"
|
||||
shouldNotHaveContained = "Expected the container (%v) NOT to contain: '%v' (but it did)!"
|
||||
shouldHaveContainedKey = "Expected the %v to contain the key: %v (but it didn't)!"
|
||||
shouldNotHaveContainedKey = "Expected the %v NOT to contain the key: %v (but it did)!"
|
||||
shouldHaveBeenIn = "Expected '%v' to be in the container (%v), but it wasn't!"
|
||||
shouldNotHaveBeenIn = "Expected '%v' NOT to be in the container (%v), but it was!"
|
||||
shouldHaveBeenAValidCollection = "You must provide a valid container (was %v)!"
|
||||
shouldHaveBeenAValidMap = "You must provide a valid map type (was %v)!"
|
||||
shouldHaveBeenEmpty = "Expected %+v to be empty (but it wasn't)!"
|
||||
shouldNotHaveBeenEmpty = "Expected %+v to NOT be empty (but it was)!"
|
||||
shouldHaveBeenAValidInteger = "You must provide a valid integer (was %v)!"
|
||||
shouldHaveBeenAValidLength = "You must provide a valid positive integer (was %v)!"
|
||||
shouldHaveHadLength = "Expected %+v to have length equal to '%v', but it wasn't!"
|
||||
)
|
||||
|
||||
const ( // strings
|
||||
shouldHaveStartedWith = "Expected '%v'\nto start with '%v'\n(but it didn't)!"
|
||||
shouldNotHaveStartedWith = "Expected '%v'\nNOT to start with '%v'\n(but it did)!"
|
||||
shouldHaveEndedWith = "Expected '%v'\nto end with '%v'\n(but it didn't)!"
|
||||
shouldNotHaveEndedWith = "Expected '%v'\nNOT to end with '%v'\n(but it did)!"
|
||||
shouldAllBeStrings = "All arguments to this assertion must be strings (you provided: %v)."
|
||||
shouldBothBeStrings = "Both arguments to this assertion must be strings (you provided %v and %v)."
|
||||
shouldBeString = "The argument to this assertion must be a string (you provided %v)."
|
||||
shouldHaveContainedSubstring = "Expected '%s' to contain substring '%s' (but it didn't)!"
|
||||
shouldNotHaveContainedSubstring = "Expected '%s' NOT to contain substring '%s' (but it did)!"
|
||||
shouldHaveBeenBlank = "Expected '%s' to be blank (but it wasn't)!"
|
||||
shouldNotHaveBeenBlank = "Expected value to NOT be blank (but it was)!"
|
||||
)
|
||||
|
||||
const ( // panics
|
||||
shouldUseVoidNiladicFunction = "You must provide a void, niladic function as the first argument!"
|
||||
shouldHavePanickedWith = "Expected func() to panic with '%v' (but it panicked with '%v')!"
|
||||
shouldHavePanicked = "Expected func() to panic (but it didn't)!"
|
||||
shouldNotHavePanicked = "Expected func() NOT to panic (error: '%+v')!"
|
||||
shouldNotHavePanickedWith = "Expected func() NOT to panic with '%v' (but it did)!"
|
||||
)
|
||||
|
||||
const ( // type checking
|
||||
shouldHaveBeenA = "Expected '%v' to be: '%v' (but was: '%v')!"
|
||||
shouldNotHaveBeenA = "Expected '%v' to NOT be: '%v' (but it was)!"
|
||||
|
||||
shouldHaveImplemented = "Expected: '%v interface support'\nActual: '%v' does not implement the interface!"
|
||||
shouldNotHaveImplemented = "Expected '%v'\nto NOT implement '%v'\n(but it did)!"
|
||||
shouldCompareWithInterfacePointer = "The expected value must be a pointer to an interface type (eg. *fmt.Stringer)"
|
||||
shouldNotBeNilActual = "The actual value was 'nil' and should be a value or a pointer to a value!"
|
||||
)
|
||||
|
||||
const ( // time comparisons
|
||||
shouldUseTimes = "You must provide time instances as arguments to this assertion."
|
||||
shouldUseTimeSlice = "You must provide a slice of time instances as the first argument to this assertion."
|
||||
shouldUseDurationAndTime = "You must provide a duration and a time as arguments to this assertion."
|
||||
shouldHaveHappenedBefore = "Expected '%v' to happen before '%v' (it happened '%v' after)!"
|
||||
shouldHaveHappenedAfter = "Expected '%v' to happen after '%v' (it happened '%v' before)!"
|
||||
shouldHaveHappenedBetween = "Expected '%v' to happen between '%v' and '%v' (it happened '%v' outside threshold)!"
|
||||
shouldNotHaveHappenedOnOrBetween = "Expected '%v' to NOT happen on or between '%v' and '%v' (but it did)!"
|
||||
|
||||
// format params: incorrect-index, previous-index, previous-time, incorrect-index, incorrect-time
|
||||
shouldHaveBeenChronological = "The 'Time' at index [%d] should have happened after the previous one (but it didn't!):\n [%d]: %s\n [%d]: %s (see, it happened before!)"
|
||||
)
|
@ -0,0 +1,115 @@
|
||||
package assertions
|
||||
|
||||
import "fmt"
|
||||
|
||||
// ShouldPanic receives a void, niladic function and expects to recover a panic.
|
||||
func ShouldPanic(actual interface{}, expected ...interface{}) (message string) {
|
||||
if fail := need(0, expected); fail != success {
|
||||
return fail
|
||||
}
|
||||
|
||||
action, _ := actual.(func())
|
||||
|
||||
if action == nil {
|
||||
message = shouldUseVoidNiladicFunction
|
||||
return
|
||||
}
|
||||
|
||||
defer func() {
|
||||
recovered := recover()
|
||||
if recovered == nil {
|
||||
message = shouldHavePanicked
|
||||
} else {
|
||||
message = success
|
||||
}
|
||||
}()
|
||||
action()
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// ShouldNotPanic receives a void, niladic function and expects to execute the function without any panic.
|
||||
func ShouldNotPanic(actual interface{}, expected ...interface{}) (message string) {
|
||||
if fail := need(0, expected); fail != success {
|
||||
return fail
|
||||
}
|
||||
|
||||
action, _ := actual.(func())
|
||||
|
||||
if action == nil {
|
||||
message = shouldUseVoidNiladicFunction
|
||||
return
|
||||
}
|
||||
|
||||
defer func() {
|
||||
recovered := recover()
|
||||
if recovered != nil {
|
||||
message = fmt.Sprintf(shouldNotHavePanicked, recovered)
|
||||
} else {
|
||||
message = success
|
||||
}
|
||||
}()
|
||||
action()
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// ShouldPanicWith receives a void, niladic function and expects to recover a panic with the second argument as the content.
|
||||
func ShouldPanicWith(actual interface{}, expected ...interface{}) (message string) {
|
||||
if fail := need(1, expected); fail != success {
|
||||
return fail
|
||||
}
|
||||
|
||||
action, _ := actual.(func())
|
||||
|
||||
if action == nil {
|
||||
message = shouldUseVoidNiladicFunction
|
||||
return
|
||||
}
|
||||
|
||||
defer func() {
|
||||
recovered := recover()
|
||||
if recovered == nil {
|
||||
message = shouldHavePanicked
|
||||
} else {
|
||||
if equal := ShouldEqual(recovered, expected[0]); equal != success {
|
||||
message = serializer.serialize(expected[0], recovered, fmt.Sprintf(shouldHavePanickedWith, expected[0], recovered))
|
||||
} else {
|
||||
message = success
|
||||
}
|
||||
}
|
||||
}()
|
||||
action()
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// ShouldNotPanicWith receives a void, niladic function and expects to recover a panic whose content differs from the second argument.
|
||||
func ShouldNotPanicWith(actual interface{}, expected ...interface{}) (message string) {
|
||||
if fail := need(1, expected); fail != success {
|
||||
return fail
|
||||
}
|
||||
|
||||
action, _ := actual.(func())
|
||||
|
||||
if action == nil {
|
||||
message = shouldUseVoidNiladicFunction
|
||||
return
|
||||
}
|
||||
|
||||
defer func() {
|
||||
recovered := recover()
|
||||
if recovered == nil {
|
||||
message = success
|
||||
} else {
|
||||
if equal := ShouldEqual(recovered, expected[0]); equal == success {
|
||||
message = fmt.Sprintf(shouldNotHavePanickedWith, expected[0])
|
||||
} else {
|
||||
message = success
|
||||
}
|
||||
}
|
||||
}()
|
||||
action()
|
||||
|
||||
return
|
||||
}
|
@ -0,0 +1,141 @@
|
||||
package assertions
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/smartystreets/assertions/internal/oglematchers"
|
||||
)
|
||||
|
||||
// ShouldBeGreaterThan receives exactly two parameters and ensures that the first is greater than the second.
|
||||
func ShouldBeGreaterThan(actual interface{}, expected ...interface{}) string {
|
||||
if fail := need(1, expected); fail != success {
|
||||
return fail
|
||||
}
|
||||
|
||||
if matchError := oglematchers.GreaterThan(expected[0]).Matches(actual); matchError != nil {
|
||||
return fmt.Sprintf(shouldHaveBeenGreater, actual, expected[0])
|
||||
}
|
||||
return success
|
||||
}
|
||||
|
||||
// ShouldBeGreaterThanOrEqualTo receives exactly two parameters and ensures that the first is greater than or equal to the second.
|
||||
func ShouldBeGreaterThanOrEqualTo(actual interface{}, expected ...interface{}) string {
|
||||
if fail := need(1, expected); fail != success {
|
||||
return fail
|
||||
} else if matchError := oglematchers.GreaterOrEqual(expected[0]).Matches(actual); matchError != nil {
|
||||
return fmt.Sprintf(shouldHaveBeenGreaterOrEqual, actual, expected[0])
|
||||
}
|
||||
return success
|
||||
}
|
||||
|
||||
// ShouldBeLessThan receives exactly two parameters and ensures that the first is less than the second.
|
||||
func ShouldBeLessThan(actual interface{}, expected ...interface{}) string {
|
||||
if fail := need(1, expected); fail != success {
|
||||
return fail
|
||||
} else if matchError := oglematchers.LessThan(expected[0]).Matches(actual); matchError != nil {
|
||||
return fmt.Sprintf(shouldHaveBeenLess, actual, expected[0])
|
||||
}
|
||||
return success
|
||||
}
|
||||
|
||||
// ShouldBeLessThan receives exactly two parameters and ensures that the first is less than or equal to the second.
|
||||
func ShouldBeLessThanOrEqualTo(actual interface{}, expected ...interface{}) string {
|
||||
if fail := need(1, expected); fail != success {
|
||||
return fail
|
||||
} else if matchError := oglematchers.LessOrEqual(expected[0]).Matches(actual); matchError != nil {
|
||||
return fmt.Sprintf(shouldHaveBeenLess, actual, expected[0])
|
||||
}
|
||||
return success
|
||||
}
|
||||
|
||||
// ShouldBeBetween receives exactly three parameters: an actual value, a lower bound, and an upper bound.
|
||||
// It ensures that the actual value is between both bounds (but not equal to either of them).
|
||||
func ShouldBeBetween(actual interface{}, expected ...interface{}) string {
|
||||
if fail := need(2, expected); fail != success {
|
||||
return fail
|
||||
}
|
||||
lower, upper, fail := deriveBounds(expected)
|
||||
|
||||
if fail != success {
|
||||
return fail
|
||||
} else if !isBetween(actual, lower, upper) {
|
||||
return fmt.Sprintf(shouldHaveBeenBetween, actual, lower, upper)
|
||||
}
|
||||
return success
|
||||
}
|
||||
|
||||
// ShouldNotBeBetween receives exactly three parameters: an actual value, a lower bound, and an upper bound.
|
||||
// It ensures that the actual value is NOT between both bounds.
|
||||
func ShouldNotBeBetween(actual interface{}, expected ...interface{}) string {
|
||||
if fail := need(2, expected); fail != success {
|
||||
return fail
|
||||
}
|
||||
lower, upper, fail := deriveBounds(expected)
|
||||
|
||||
if fail != success {
|
||||
return fail
|
||||
} else if isBetween(actual, lower, upper) {
|
||||
return fmt.Sprintf(shouldNotHaveBeenBetween, actual, lower, upper)
|
||||
}
|
||||
return success
|
||||
}
|
||||
func deriveBounds(values []interface{}) (lower interface{}, upper interface{}, fail string) {
|
||||
lower = values[0]
|
||||
upper = values[1]
|
||||
|
||||
if ShouldNotEqual(lower, upper) != success {
|
||||
return nil, nil, fmt.Sprintf(shouldHaveDifferentUpperAndLower, lower)
|
||||
} else if ShouldBeLessThan(lower, upper) != success {
|
||||
lower, upper = upper, lower
|
||||
}
|
||||
return lower, upper, success
|
||||
}
|
||||
func isBetween(value, lower, upper interface{}) bool {
|
||||
if ShouldBeGreaterThan(value, lower) != success {
|
||||
return false
|
||||
} else if ShouldBeLessThan(value, upper) != success {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// ShouldBeBetweenOrEqual receives exactly three parameters: an actual value, a lower bound, and an upper bound.
|
||||
// It ensures that the actual value is between both bounds or equal to one of them.
|
||||
func ShouldBeBetweenOrEqual(actual interface{}, expected ...interface{}) string {
|
||||
if fail := need(2, expected); fail != success {
|
||||
return fail
|
||||
}
|
||||
lower, upper, fail := deriveBounds(expected)
|
||||
|
||||
if fail != success {
|
||||
return fail
|
||||
} else if !isBetweenOrEqual(actual, lower, upper) {
|
||||
return fmt.Sprintf(shouldHaveBeenBetweenOrEqual, actual, lower, upper)
|
||||
}
|
||||
return success
|
||||
}
|
||||
|
||||
// ShouldNotBeBetweenOrEqual receives exactly three parameters: an actual value, a lower bound, and an upper bound.
|
||||
// It ensures that the actual value is nopt between the bounds nor equal to either of them.
|
||||
func ShouldNotBeBetweenOrEqual(actual interface{}, expected ...interface{}) string {
|
||||
if fail := need(2, expected); fail != success {
|
||||
return fail
|
||||
}
|
||||
lower, upper, fail := deriveBounds(expected)
|
||||
|
||||
if fail != success {
|
||||
return fail
|
||||
} else if isBetweenOrEqual(actual, lower, upper) {
|
||||
return fmt.Sprintf(shouldNotHaveBeenBetweenOrEqual, actual, lower, upper)
|
||||
}
|
||||
return success
|
||||
}
|
||||
|
||||
func isBetweenOrEqual(value, lower, upper interface{}) bool {
|
||||
if ShouldBeGreaterThanOrEqualTo(value, lower) != success {
|
||||
return false
|
||||
} else if ShouldBeLessThanOrEqualTo(value, upper) != success {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
@ -0,0 +1,59 @@
|
||||
package assertions
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
"github.com/smartystreets/goconvey/convey/reporting"
|
||||
)
|
||||
|
||||
type Serializer interface {
|
||||
serialize(expected, actual interface{}, message string) string
|
||||
serializeDetailed(expected, actual interface{}, message string) string
|
||||
}
|
||||
|
||||
type failureSerializer struct{}
|
||||
|
||||
func (self *failureSerializer) serializeDetailed(expected, actual interface{}, message string) string {
|
||||
view := self.format(expected, actual, message, "%#v")
|
||||
serialized, err := json.Marshal(view)
|
||||
if err != nil {
|
||||
return message
|
||||
}
|
||||
return string(serialized)
|
||||
}
|
||||
|
||||
func (self *failureSerializer) serialize(expected, actual interface{}, message string) string {
|
||||
view := self.format(expected, actual, message, "%+v")
|
||||
serialized, err := json.Marshal(view)
|
||||
if err != nil {
|
||||
return message
|
||||
}
|
||||
return string(serialized)
|
||||
}
|
||||
|
||||
func (self *failureSerializer) format(expected, actual interface{}, message string, format string) reporting.FailureView {
|
||||
return reporting.FailureView{
|
||||
Message: message,
|
||||
Expected: fmt.Sprintf(format, expected),
|
||||
Actual: fmt.Sprintf(format, actual),
|
||||
}
|
||||
}
|
||||
|
||||
func newSerializer() *failureSerializer {
|
||||
return &failureSerializer{}
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////
|
||||
|
||||
// noopSerializer just gives back the original message. This is useful when we are using
|
||||
// the assertions from a context other than the web UI, that requires the JSON structure
|
||||
// provided by the failureSerializer.
|
||||
type noopSerializer struct{}
|
||||
|
||||
func (self *noopSerializer) serialize(expected, actual interface{}, message string) string {
|
||||
return message
|
||||
}
|
||||
func (self *noopSerializer) serializeDetailed(expected, actual interface{}, message string) string {
|
||||
return message
|
||||
}
|
@ -0,0 +1,227 @@
|
||||
package assertions
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// ShouldStartWith receives exactly 2 string parameters and ensures that the first starts with the second.
|
||||
func ShouldStartWith(actual interface{}, expected ...interface{}) string {
|
||||
if fail := need(1, expected); fail != success {
|
||||
return fail
|
||||
}
|
||||
|
||||
value, valueIsString := actual.(string)
|
||||
prefix, prefixIsString := expected[0].(string)
|
||||
|
||||
if !valueIsString || !prefixIsString {
|
||||
return fmt.Sprintf(shouldBothBeStrings, reflect.TypeOf(actual), reflect.TypeOf(expected[0]))
|
||||
}
|
||||
|
||||
return shouldStartWith(value, prefix)
|
||||
}
|
||||
func shouldStartWith(value, prefix string) string {
|
||||
if !strings.HasPrefix(value, prefix) {
|
||||
shortval := value
|
||||
if len(shortval) > len(prefix) {
|
||||
shortval = shortval[:len(prefix)] + "..."
|
||||
}
|
||||
return serializer.serialize(prefix, shortval, fmt.Sprintf(shouldHaveStartedWith, value, prefix))
|
||||
}
|
||||
return success
|
||||
}
|
||||
|
||||
// ShouldNotStartWith receives exactly 2 string parameters and ensures that the first does not start with the second.
|
||||
func ShouldNotStartWith(actual interface{}, expected ...interface{}) string {
|
||||
if fail := need(1, expected); fail != success {
|
||||
return fail
|
||||
}
|
||||
|
||||
value, valueIsString := actual.(string)
|
||||
prefix, prefixIsString := expected[0].(string)
|
||||
|
||||
if !valueIsString || !prefixIsString {
|
||||
return fmt.Sprintf(shouldBothBeStrings, reflect.TypeOf(actual), reflect.TypeOf(expected[0]))
|
||||
}
|
||||
|
||||
return shouldNotStartWith(value, prefix)
|
||||
}
|
||||
func shouldNotStartWith(value, prefix string) string {
|
||||
if strings.HasPrefix(value, prefix) {
|
||||
if value == "" {
|
||||
value = "<empty>"
|
||||
}
|
||||
if prefix == "" {
|
||||
prefix = "<empty>"
|
||||
}
|
||||
return fmt.Sprintf(shouldNotHaveStartedWith, value, prefix)
|
||||
}
|
||||
return success
|
||||
}
|
||||
|
||||
// ShouldEndWith receives exactly 2 string parameters and ensures that the first ends with the second.
|
||||
func ShouldEndWith(actual interface{}, expected ...interface{}) string {
|
||||
if fail := need(1, expected); fail != success {
|
||||
return fail
|
||||
}
|
||||
|
||||
value, valueIsString := actual.(string)
|
||||
suffix, suffixIsString := expected[0].(string)
|
||||
|
||||
if !valueIsString || !suffixIsString {
|
||||
return fmt.Sprintf(shouldBothBeStrings, reflect.TypeOf(actual), reflect.TypeOf(expected[0]))
|
||||
}
|
||||
|
||||
return shouldEndWith(value, suffix)
|
||||
}
|
||||
func shouldEndWith(value, suffix string) string {
|
||||
if !strings.HasSuffix(value, suffix) {
|
||||
shortval := value
|
||||
if len(shortval) > len(suffix) {
|
||||
shortval = "..." + shortval[len(shortval)-len(suffix):]
|
||||
}
|
||||
return serializer.serialize(suffix, shortval, fmt.Sprintf(shouldHaveEndedWith, value, suffix))
|
||||
}
|
||||
return success
|
||||
}
|
||||
|
||||
// ShouldEndWith receives exactly 2 string parameters and ensures that the first does not end with the second.
|
||||
func ShouldNotEndWith(actual interface{}, expected ...interface{}) string {
|
||||
if fail := need(1, expected); fail != success {
|
||||
return fail
|
||||
}
|
||||
|
||||
value, valueIsString := actual.(string)
|
||||
suffix, suffixIsString := expected[0].(string)
|
||||
|
||||
if !valueIsString || !suffixIsString {
|
||||
return fmt.Sprintf(shouldBothBeStrings, reflect.TypeOf(actual), reflect.TypeOf(expected[0]))
|
||||
}
|
||||
|
||||
return shouldNotEndWith(value, suffix)
|
||||
}
|
||||
func shouldNotEndWith(value, suffix string) string {
|
||||
if strings.HasSuffix(value, suffix) {
|
||||
if value == "" {
|
||||
value = "<empty>"
|
||||
}
|
||||
if suffix == "" {
|
||||
suffix = "<empty>"
|
||||
}
|
||||
return fmt.Sprintf(shouldNotHaveEndedWith, value, suffix)
|
||||
}
|
||||
return success
|
||||
}
|
||||
|
||||
// ShouldContainSubstring receives exactly 2 string parameters and ensures that the first contains the second as a substring.
|
||||
func ShouldContainSubstring(actual interface{}, expected ...interface{}) string {
|
||||
if fail := need(1, expected); fail != success {
|
||||
return fail
|
||||
}
|
||||
|
||||
long, longOk := actual.(string)
|
||||
short, shortOk := expected[0].(string)
|
||||
|
||||
if !longOk || !shortOk {
|
||||
return fmt.Sprintf(shouldBothBeStrings, reflect.TypeOf(actual), reflect.TypeOf(expected[0]))
|
||||
}
|
||||
|
||||
if !strings.Contains(long, short) {
|
||||
return serializer.serialize(expected[0], actual, fmt.Sprintf(shouldHaveContainedSubstring, long, short))
|
||||
}
|
||||
return success
|
||||
}
|
||||
|
||||
// ShouldNotContainSubstring receives exactly 2 string parameters and ensures that the first does NOT contain the second as a substring.
|
||||
func ShouldNotContainSubstring(actual interface{}, expected ...interface{}) string {
|
||||
if fail := need(1, expected); fail != success {
|
||||
return fail
|
||||
}
|
||||
|
||||
long, longOk := actual.(string)
|
||||
short, shortOk := expected[0].(string)
|
||||
|
||||
if !longOk || !shortOk {
|
||||
return fmt.Sprintf(shouldBothBeStrings, reflect.TypeOf(actual), reflect.TypeOf(expected[0]))
|
||||
}
|
||||
|
||||
if strings.Contains(long, short) {
|
||||
return fmt.Sprintf(shouldNotHaveContainedSubstring, long, short)
|
||||
}
|
||||
return success
|
||||
}
|
||||
|
||||
// ShouldBeBlank receives exactly 1 string parameter and ensures that it is equal to "".
|
||||
func ShouldBeBlank(actual interface{}, expected ...interface{}) string {
|
||||
if fail := need(0, expected); fail != success {
|
||||
return fail
|
||||
}
|
||||
value, ok := actual.(string)
|
||||
if !ok {
|
||||
return fmt.Sprintf(shouldBeString, reflect.TypeOf(actual))
|
||||
}
|
||||
if value != "" {
|
||||
return serializer.serialize("", value, fmt.Sprintf(shouldHaveBeenBlank, value))
|
||||
}
|
||||
return success
|
||||
}
|
||||
|
||||
// ShouldNotBeBlank receives exactly 1 string parameter and ensures that it is equal to "".
|
||||
func ShouldNotBeBlank(actual interface{}, expected ...interface{}) string {
|
||||
if fail := need(0, expected); fail != success {
|
||||
return fail
|
||||
}
|
||||
value, ok := actual.(string)
|
||||
if !ok {
|
||||
return fmt.Sprintf(shouldBeString, reflect.TypeOf(actual))
|
||||
}
|
||||
if value == "" {
|
||||
return shouldNotHaveBeenBlank
|
||||
}
|
||||
return success
|
||||
}
|
||||
|
||||
// ShouldEqualWithout receives exactly 3 string parameters and ensures that the first is equal to the second
|
||||
// after removing all instances of the third from the first using strings.Replace(first, third, "", -1).
|
||||
func ShouldEqualWithout(actual interface{}, expected ...interface{}) string {
|
||||
if fail := need(2, expected); fail != success {
|
||||
return fail
|
||||
}
|
||||
actualString, ok1 := actual.(string)
|
||||
expectedString, ok2 := expected[0].(string)
|
||||
replace, ok3 := expected[1].(string)
|
||||
|
||||
if !ok1 || !ok2 || !ok3 {
|
||||
return fmt.Sprintf(shouldAllBeStrings, []reflect.Type{
|
||||
reflect.TypeOf(actual),
|
||||
reflect.TypeOf(expected[0]),
|
||||
reflect.TypeOf(expected[1]),
|
||||
})
|
||||
}
|
||||
|
||||
replaced := strings.Replace(actualString, replace, "", -1)
|
||||
if replaced == expectedString {
|
||||
return ""
|
||||
}
|
||||
|
||||
return fmt.Sprintf("Expected '%s' to equal '%s' but without any '%s' (but it didn't).", actualString, expectedString, replace)
|
||||
}
|
||||
|
||||
// ShouldEqualTrimSpace receives exactly 2 string parameters and ensures that the first is equal to the second
|
||||
// after removing all leading and trailing whitespace using strings.TrimSpace(first).
|
||||
func ShouldEqualTrimSpace(actual interface{}, expected ...interface{}) string {
|
||||
if fail := need(1, expected); fail != success {
|
||||
return fail
|
||||
}
|
||||
|
||||
actualString, valueIsString := actual.(string)
|
||||
_, value2IsString := expected[0].(string)
|
||||
|
||||
if !valueIsString || !value2IsString {
|
||||
return fmt.Sprintf(shouldBothBeStrings, reflect.TypeOf(actual), reflect.TypeOf(expected[0]))
|
||||
}
|
||||
|
||||
actualString = strings.TrimSpace(actualString)
|
||||
return ShouldEqual(actualString, expected[0])
|
||||
}
|
@ -0,0 +1,202 @@
|
||||
package assertions
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
)
|
||||
|
||||
// ShouldHappenBefore receives exactly 2 time.Time arguments and asserts that the first happens before the second.
|
||||
func ShouldHappenBefore(actual interface{}, expected ...interface{}) string {
|
||||
if fail := need(1, expected); fail != success {
|
||||
return fail
|
||||
}
|
||||
actualTime, firstOk := actual.(time.Time)
|
||||
expectedTime, secondOk := expected[0].(time.Time)
|
||||
|
||||
if !firstOk || !secondOk {
|
||||
return shouldUseTimes
|
||||
}
|
||||
|
||||
if !actualTime.Before(expectedTime) {
|
||||
return fmt.Sprintf(shouldHaveHappenedBefore, actualTime, expectedTime, actualTime.Sub(expectedTime))
|
||||
}
|
||||
|
||||
return success
|
||||
}
|
||||
|
||||
// ShouldHappenOnOrBefore receives exactly 2 time.Time arguments and asserts that the first happens on or before the second.
|
||||
func ShouldHappenOnOrBefore(actual interface{}, expected ...interface{}) string {
|
||||
if fail := need(1, expected); fail != success {
|
||||
return fail
|
||||
}
|
||||
actualTime, firstOk := actual.(time.Time)
|
||||
expectedTime, secondOk := expected[0].(time.Time)
|
||||
|
||||
if !firstOk || !secondOk {
|
||||
return shouldUseTimes
|
||||
}
|
||||
|
||||
if actualTime.Equal(expectedTime) {
|
||||
return success
|
||||
}
|
||||
return ShouldHappenBefore(actualTime, expectedTime)
|
||||
}
|
||||
|
||||
// ShouldHappenAfter receives exactly 2 time.Time arguments and asserts that the first happens after the second.
|
||||
func ShouldHappenAfter(actual interface{}, expected ...interface{}) string {
|
||||
if fail := need(1, expected); fail != success {
|
||||
return fail
|
||||
}
|
||||
actualTime, firstOk := actual.(time.Time)
|
||||
expectedTime, secondOk := expected[0].(time.Time)
|
||||
|
||||
if !firstOk || !secondOk {
|
||||
return shouldUseTimes
|
||||
}
|
||||
if !actualTime.After(expectedTime) {
|
||||
return fmt.Sprintf(shouldHaveHappenedAfter, actualTime, expectedTime, expectedTime.Sub(actualTime))
|
||||
}
|
||||
return success
|
||||
}
|
||||
|
||||
// ShouldHappenOnOrAfter receives exactly 2 time.Time arguments and asserts that the first happens on or after the second.
|
||||
func ShouldHappenOnOrAfter(actual interface{}, expected ...interface{}) string {
|
||||
if fail := need(1, expected); fail != success {
|
||||
return fail
|
||||
}
|
||||
actualTime, firstOk := actual.(time.Time)
|
||||
expectedTime, secondOk := expected[0].(time.Time)
|
||||
|
||||
if !firstOk || !secondOk {
|
||||
return shouldUseTimes
|
||||
}
|
||||
if actualTime.Equal(expectedTime) {
|
||||
return success
|
||||
}
|
||||
return ShouldHappenAfter(actualTime, expectedTime)
|
||||
}
|
||||
|
||||
// ShouldHappenBetween receives exactly 3 time.Time arguments and asserts that the first happens between (not on) the second and third.
|
||||
func ShouldHappenBetween(actual interface{}, expected ...interface{}) string {
|
||||
if fail := need(2, expected); fail != success {
|
||||
return fail
|
||||
}
|
||||
actualTime, firstOk := actual.(time.Time)
|
||||
min, secondOk := expected[0].(time.Time)
|
||||
max, thirdOk := expected[1].(time.Time)
|
||||
|
||||
if !firstOk || !secondOk || !thirdOk {
|
||||
return shouldUseTimes
|
||||
}
|
||||
|
||||
if !actualTime.After(min) {
|
||||
return fmt.Sprintf(shouldHaveHappenedBetween, actualTime, min, max, min.Sub(actualTime))
|
||||
}
|
||||
if !actualTime.Before(max) {
|
||||
return fmt.Sprintf(shouldHaveHappenedBetween, actualTime, min, max, actualTime.Sub(max))
|
||||
}
|
||||
return success
|
||||
}
|
||||
|
||||
// ShouldHappenOnOrBetween receives exactly 3 time.Time arguments and asserts that the first happens between or on the second and third.
|
||||
func ShouldHappenOnOrBetween(actual interface{}, expected ...interface{}) string {
|
||||
if fail := need(2, expected); fail != success {
|
||||
return fail
|
||||
}
|
||||
actualTime, firstOk := actual.(time.Time)
|
||||
min, secondOk := expected[0].(time.Time)
|
||||
max, thirdOk := expected[1].(time.Time)
|
||||
|
||||
if !firstOk || !secondOk || !thirdOk {
|
||||
return shouldUseTimes
|
||||
}
|
||||
if actualTime.Equal(min) || actualTime.Equal(max) {
|
||||
return success
|
||||
}
|
||||
return ShouldHappenBetween(actualTime, min, max)
|
||||
}
|
||||
|
||||
// ShouldNotHappenOnOrBetween receives exactly 3 time.Time arguments and asserts that the first
|
||||
// does NOT happen between or on the second or third.
|
||||
func ShouldNotHappenOnOrBetween(actual interface{}, expected ...interface{}) string {
|
||||
if fail := need(2, expected); fail != success {
|
||||
return fail
|
||||
}
|
||||
actualTime, firstOk := actual.(time.Time)
|
||||
min, secondOk := expected[0].(time.Time)
|
||||
max, thirdOk := expected[1].(time.Time)
|
||||
|
||||
if !firstOk || !secondOk || !thirdOk {
|
||||
return shouldUseTimes
|
||||
}
|
||||
if actualTime.Equal(min) || actualTime.Equal(max) {
|
||||
return fmt.Sprintf(shouldNotHaveHappenedOnOrBetween, actualTime, min, max)
|
||||
}
|
||||
if actualTime.After(min) && actualTime.Before(max) {
|
||||
return fmt.Sprintf(shouldNotHaveHappenedOnOrBetween, actualTime, min, max)
|
||||
}
|
||||
return success
|
||||
}
|
||||
|
||||
// ShouldHappenWithin receives a time.Time, a time.Duration, and a time.Time (3 arguments)
|
||||
// and asserts that the first time.Time happens within or on the duration specified relative to
|
||||
// the other time.Time.
|
||||
func ShouldHappenWithin(actual interface{}, expected ...interface{}) string {
|
||||
if fail := need(2, expected); fail != success {
|
||||
return fail
|
||||
}
|
||||
actualTime, firstOk := actual.(time.Time)
|
||||
tolerance, secondOk := expected[0].(time.Duration)
|
||||
threshold, thirdOk := expected[1].(time.Time)
|
||||
|
||||
if !firstOk || !secondOk || !thirdOk {
|
||||
return shouldUseDurationAndTime
|
||||
}
|
||||
|
||||
min := threshold.Add(-tolerance)
|
||||
max := threshold.Add(tolerance)
|
||||
return ShouldHappenOnOrBetween(actualTime, min, max)
|
||||
}
|
||||
|
||||
// ShouldNotHappenWithin receives a time.Time, a time.Duration, and a time.Time (3 arguments)
|
||||
// and asserts that the first time.Time does NOT happen within or on the duration specified relative to
|
||||
// the other time.Time.
|
||||
func ShouldNotHappenWithin(actual interface{}, expected ...interface{}) string {
|
||||
if fail := need(2, expected); fail != success {
|
||||
return fail
|
||||
}
|
||||
actualTime, firstOk := actual.(time.Time)
|
||||
tolerance, secondOk := expected[0].(time.Duration)
|
||||
threshold, thirdOk := expected[1].(time.Time)
|
||||
|
||||
if !firstOk || !secondOk || !thirdOk {
|
||||
return shouldUseDurationAndTime
|
||||
}
|
||||
|
||||
min := threshold.Add(-tolerance)
|
||||
max := threshold.Add(tolerance)
|
||||
return ShouldNotHappenOnOrBetween(actualTime, min, max)
|
||||
}
|
||||
|
||||
// ShouldBeChronological receives a []time.Time slice and asserts that the are
|
||||
// in chronological order starting with the first time.Time as the earliest.
|
||||
func ShouldBeChronological(actual interface{}, expected ...interface{}) string {
|
||||
if fail := need(0, expected); fail != success {
|
||||
return fail
|
||||
}
|
||||
|
||||
times, ok := actual.([]time.Time)
|
||||
if !ok {
|
||||
return shouldUseTimeSlice
|
||||
}
|
||||
|
||||
var previous time.Time
|
||||
for i, current := range times {
|
||||
if i > 0 && current.Before(previous) {
|
||||
return fmt.Sprintf(shouldHaveBeenChronological,
|
||||
i, i-1, previous.String(), i, current.String())
|
||||
}
|
||||
previous = current
|
||||
}
|
||||
return ""
|
||||
}
|
@ -0,0 +1,112 @@
|
||||
package assertions
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
)
|
||||
|
||||
// ShouldHaveSameTypeAs receives exactly two parameters and compares their underlying types for equality.
|
||||
func ShouldHaveSameTypeAs(actual interface{}, expected ...interface{}) string {
|
||||
if fail := need(1, expected); fail != success {
|
||||
return fail
|
||||
}
|
||||
|
||||
first := reflect.TypeOf(actual)
|
||||
second := reflect.TypeOf(expected[0])
|
||||
|
||||
if equal := ShouldEqual(first, second); equal != success {
|
||||
return serializer.serialize(second, first, fmt.Sprintf(shouldHaveBeenA, actual, second, first))
|
||||
}
|
||||
return success
|
||||
}
|
||||
|
||||
// ShouldNotHaveSameTypeAs receives exactly two parameters and compares their underlying types for inequality.
|
||||
func ShouldNotHaveSameTypeAs(actual interface{}, expected ...interface{}) string {
|
||||
if fail := need(1, expected); fail != success {
|
||||
return fail
|
||||
}
|
||||
|
||||
first := reflect.TypeOf(actual)
|
||||
second := reflect.TypeOf(expected[0])
|
||||
|
||||
if equal := ShouldEqual(first, second); equal == success {
|
||||
return fmt.Sprintf(shouldNotHaveBeenA, actual, second)
|
||||
}
|
||||
return success
|
||||
}
|
||||
|
||||
// ShouldImplement receives exactly two parameters and ensures
|
||||
// that the first implements the interface type of the second.
|
||||
func ShouldImplement(actual interface{}, expectedList ...interface{}) string {
|
||||
if fail := need(1, expectedList); fail != success {
|
||||
return fail
|
||||
}
|
||||
|
||||
expected := expectedList[0]
|
||||
if fail := ShouldBeNil(expected); fail != success {
|
||||
return shouldCompareWithInterfacePointer
|
||||
}
|
||||
|
||||
if fail := ShouldNotBeNil(actual); fail != success {
|
||||
return shouldNotBeNilActual
|
||||
}
|
||||
|
||||
var actualType reflect.Type
|
||||
if reflect.TypeOf(actual).Kind() != reflect.Ptr {
|
||||
actualType = reflect.PtrTo(reflect.TypeOf(actual))
|
||||
} else {
|
||||
actualType = reflect.TypeOf(actual)
|
||||
}
|
||||
|
||||
expectedType := reflect.TypeOf(expected)
|
||||
if fail := ShouldNotBeNil(expectedType); fail != success {
|
||||
return shouldCompareWithInterfacePointer
|
||||
}
|
||||
|
||||
expectedInterface := expectedType.Elem()
|
||||
|
||||
if actualType == nil {
|
||||
return fmt.Sprintf(shouldHaveImplemented, expectedInterface, actual)
|
||||
}
|
||||
|
||||
if !actualType.Implements(expectedInterface) {
|
||||
return fmt.Sprintf(shouldHaveImplemented, expectedInterface, actualType)
|
||||
}
|
||||
return success
|
||||
}
|
||||
|
||||
// ShouldNotImplement receives exactly two parameters and ensures
|
||||
// that the first does NOT implement the interface type of the second.
|
||||
func ShouldNotImplement(actual interface{}, expectedList ...interface{}) string {
|
||||
if fail := need(1, expectedList); fail != success {
|
||||
return fail
|
||||
}
|
||||
|
||||
expected := expectedList[0]
|
||||
if fail := ShouldBeNil(expected); fail != success {
|
||||
return shouldCompareWithInterfacePointer
|
||||
}
|
||||
|
||||
if fail := ShouldNotBeNil(actual); fail != success {
|
||||
return shouldNotBeNilActual
|
||||
}
|
||||
|
||||
var actualType reflect.Type
|
||||
if reflect.TypeOf(actual).Kind() != reflect.Ptr {
|
||||
actualType = reflect.PtrTo(reflect.TypeOf(actual))
|
||||
} else {
|
||||
actualType = reflect.TypeOf(actual)
|
||||
}
|
||||
|
||||
expectedType := reflect.TypeOf(expected)
|
||||
if fail := ShouldNotBeNil(expectedType); fail != success {
|
||||
return shouldCompareWithInterfacePointer
|
||||
}
|
||||
|
||||
expectedInterface := expectedType.Elem()
|
||||
|
||||
if actualType.Implements(expectedInterface) {
|
||||
return fmt.Sprintf(shouldNotHaveImplemented, actualType, expectedInterface)
|
||||
}
|
||||
return success
|
||||
}
|
@ -0,0 +1,23 @@
|
||||
Copyright (c) 2014 SmartyStreets, LLC
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
||||
NOTE: Various optional and subordinate components carry their own licensing
|
||||
requirements and restrictions. Use of those components is subject to the terms
|
||||
and conditions outlined the respective license of each component.
|
@ -0,0 +1,68 @@
|
||||
package convey
|
||||
|
||||
import "github.com/smartystreets/assertions"
|
||||
|
||||
var (
|
||||
ShouldEqual = assertions.ShouldEqual
|
||||
ShouldNotEqual = assertions.ShouldNotEqual
|
||||
ShouldAlmostEqual = assertions.ShouldAlmostEqual
|
||||
ShouldNotAlmostEqual = assertions.ShouldNotAlmostEqual
|
||||
ShouldResemble = assertions.ShouldResemble
|
||||
ShouldNotResemble = assertions.ShouldNotResemble
|
||||
ShouldPointTo = assertions.ShouldPointTo
|
||||
ShouldNotPointTo = assertions.ShouldNotPointTo
|
||||
ShouldBeNil = assertions.ShouldBeNil
|
||||
ShouldNotBeNil = assertions.ShouldNotBeNil
|
||||
ShouldBeTrue = assertions.ShouldBeTrue
|
||||
ShouldBeFalse = assertions.ShouldBeFalse
|
||||
ShouldBeZeroValue = assertions.ShouldBeZeroValue
|
||||
|
||||
ShouldBeGreaterThan = assertions.ShouldBeGreaterThan
|
||||
ShouldBeGreaterThanOrEqualTo = assertions.ShouldBeGreaterThanOrEqualTo
|
||||
ShouldBeLessThan = assertions.ShouldBeLessThan
|
||||
ShouldBeLessThanOrEqualTo = assertions.ShouldBeLessThanOrEqualTo
|
||||
ShouldBeBetween = assertions.ShouldBeBetween
|
||||
ShouldNotBeBetween = assertions.ShouldNotBeBetween
|
||||
ShouldBeBetweenOrEqual = assertions.ShouldBeBetweenOrEqual
|
||||
ShouldNotBeBetweenOrEqual = assertions.ShouldNotBeBetweenOrEqual
|
||||
|
||||
ShouldContain = assertions.ShouldContain
|
||||
ShouldNotContain = assertions.ShouldNotContain
|
||||
ShouldContainKey = assertions.ShouldContainKey
|
||||
ShouldNotContainKey = assertions.ShouldNotContainKey
|
||||
ShouldBeIn = assertions.ShouldBeIn
|
||||
ShouldNotBeIn = assertions.ShouldNotBeIn
|
||||
ShouldBeEmpty = assertions.ShouldBeEmpty
|
||||
ShouldNotBeEmpty = assertions.ShouldNotBeEmpty
|
||||
ShouldHaveLength = assertions.ShouldHaveLength
|
||||
|
||||
ShouldStartWith = assertions.ShouldStartWith
|
||||
ShouldNotStartWith = assertions.ShouldNotStartWith
|
||||
ShouldEndWith = assertions.ShouldEndWith
|
||||
ShouldNotEndWith = assertions.ShouldNotEndWith
|
||||
ShouldBeBlank = assertions.ShouldBeBlank
|
||||
ShouldNotBeBlank = assertions.ShouldNotBeBlank
|
||||
ShouldContainSubstring = assertions.ShouldContainSubstring
|
||||
ShouldNotContainSubstring = assertions.ShouldNotContainSubstring
|
||||
|
||||
ShouldPanic = assertions.ShouldPanic
|
||||
ShouldNotPanic = assertions.ShouldNotPanic
|
||||
ShouldPanicWith = assertions.ShouldPanicWith
|
||||
ShouldNotPanicWith = assertions.ShouldNotPanicWith
|
||||
|
||||
ShouldHaveSameTypeAs = assertions.ShouldHaveSameTypeAs
|
||||
ShouldNotHaveSameTypeAs = assertions.ShouldNotHaveSameTypeAs
|
||||
ShouldImplement = assertions.ShouldImplement
|
||||
ShouldNotImplement = assertions.ShouldNotImplement
|
||||
|
||||
ShouldHappenBefore = assertions.ShouldHappenBefore
|
||||
ShouldHappenOnOrBefore = assertions.ShouldHappenOnOrBefore
|
||||
ShouldHappenAfter = assertions.ShouldHappenAfter
|
||||
ShouldHappenOnOrAfter = assertions.ShouldHappenOnOrAfter
|
||||
ShouldHappenBetween = assertions.ShouldHappenBetween
|
||||
ShouldHappenOnOrBetween = assertions.ShouldHappenOnOrBetween
|
||||
ShouldNotHappenOnOrBetween = assertions.ShouldNotHappenOnOrBetween
|
||||
ShouldHappenWithin = assertions.ShouldHappenWithin
|
||||
ShouldNotHappenWithin = assertions.ShouldNotHappenWithin
|
||||
ShouldBeChronological = assertions.ShouldBeChronological
|
||||
)
|
@ -0,0 +1,272 @@
|
||||
package convey
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/jtolds/gls"
|
||||
"github.com/smartystreets/goconvey/convey/reporting"
|
||||
)
|
||||
|
||||
type conveyErr struct {
|
||||
fmt string
|
||||
params []interface{}
|
||||
}
|
||||
|
||||
func (e *conveyErr) Error() string {
|
||||
return fmt.Sprintf(e.fmt, e.params...)
|
||||
}
|
||||
|
||||
func conveyPanic(fmt string, params ...interface{}) {
|
||||
panic(&conveyErr{fmt, params})
|
||||
}
|
||||
|
||||
const (
|
||||
missingGoTest = `Top-level calls to Convey(...) need a reference to the *testing.T.
|
||||
Hint: Convey("description here", t, func() { /* notice that the second argument was the *testing.T (t)! */ }) `
|
||||
extraGoTest = `Only the top-level call to Convey(...) needs a reference to the *testing.T.`
|
||||
noStackContext = "Convey operation made without context on goroutine stack.\n" +
|
||||
"Hint: Perhaps you meant to use `Convey(..., func(c C){...})` ?"
|
||||
differentConveySituations = "Different set of Convey statements on subsequent pass!\nDid not expect %#v."
|
||||
multipleIdenticalConvey = "Multiple convey suites with identical names: %#v"
|
||||
)
|
||||
|
||||
const (
|
||||
failureHalt = "___FAILURE_HALT___"
|
||||
|
||||
nodeKey = "node"
|
||||
)
|
||||
|
||||
///////////////////////////////// Stack Context /////////////////////////////////
|
||||
|
||||
func getCurrentContext() *context {
|
||||
ctx, ok := ctxMgr.GetValue(nodeKey)
|
||||
if ok {
|
||||
return ctx.(*context)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func mustGetCurrentContext() *context {
|
||||
ctx := getCurrentContext()
|
||||
if ctx == nil {
|
||||
conveyPanic(noStackContext)
|
||||
}
|
||||
return ctx
|
||||
}
|
||||
|
||||
//////////////////////////////////// Context ////////////////////////////////////
|
||||
|
||||
// context magically handles all coordination of Convey's and So assertions.
|
||||
//
|
||||
// It is tracked on the stack as goroutine-local-storage with the gls package,
|
||||
// or explicitly if the user decides to call convey like:
|
||||
//
|
||||
// Convey(..., func(c C) {
|
||||
// c.So(...)
|
||||
// })
|
||||
//
|
||||
// This implements the `C` interface.
|
||||
type context struct {
|
||||
reporter reporting.Reporter
|
||||
|
||||
children map[string]*context
|
||||
|
||||
resets []func()
|
||||
|
||||
executedOnce bool
|
||||
expectChildRun *bool
|
||||
complete bool
|
||||
|
||||
focus bool
|
||||
failureMode FailureMode
|
||||
}
|
||||
|
||||
// rootConvey is the main entry point to a test suite. This is called when
|
||||
// there's no context in the stack already, and items must contain a `t` object,
|
||||
// or this panics.
|
||||
func rootConvey(items ...interface{}) {
|
||||
entry := discover(items)
|
||||
|
||||
if entry.Test == nil {
|
||||
conveyPanic(missingGoTest)
|
||||
}
|
||||
|
||||
expectChildRun := true
|
||||
ctx := &context{
|
||||
reporter: buildReporter(),
|
||||
|
||||
children: make(map[string]*context),
|
||||
|
||||
expectChildRun: &expectChildRun,
|
||||
|
||||
focus: entry.Focus,
|
||||
failureMode: defaultFailureMode.combine(entry.FailMode),
|
||||
}
|
||||
ctxMgr.SetValues(gls.Values{nodeKey: ctx}, func() {
|
||||
ctx.reporter.BeginStory(reporting.NewStoryReport(entry.Test))
|
||||
defer ctx.reporter.EndStory()
|
||||
|
||||
for ctx.shouldVisit() {
|
||||
ctx.conveyInner(entry.Situation, entry.Func)
|
||||
expectChildRun = true
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
//////////////////////////////////// Methods ////////////////////////////////////
|
||||
|
||||
func (ctx *context) SkipConvey(items ...interface{}) {
|
||||
ctx.Convey(items, skipConvey)
|
||||
}
|
||||
|
||||
func (ctx *context) FocusConvey(items ...interface{}) {
|
||||
ctx.Convey(items, focusConvey)
|
||||
}
|
||||
|
||||
func (ctx *context) Convey(items ...interface{}) {
|
||||
entry := discover(items)
|
||||
|
||||
// we're a branch, or leaf (on the wind)
|
||||
if entry.Test != nil {
|
||||
conveyPanic(extraGoTest)
|
||||
}
|
||||
if ctx.focus && !entry.Focus {
|
||||
return
|
||||
}
|
||||
|
||||
var inner_ctx *context
|
||||
if ctx.executedOnce {
|
||||
var ok bool
|
||||
inner_ctx, ok = ctx.children[entry.Situation]
|
||||
if !ok {
|
||||
conveyPanic(differentConveySituations, entry.Situation)
|
||||
}
|
||||
} else {
|
||||
if _, ok := ctx.children[entry.Situation]; ok {
|
||||
conveyPanic(multipleIdenticalConvey, entry.Situation)
|
||||
}
|
||||
inner_ctx = &context{
|
||||
reporter: ctx.reporter,
|
||||
|
||||
children: make(map[string]*context),
|
||||
|
||||
expectChildRun: ctx.expectChildRun,
|
||||
|
||||
focus: entry.Focus,
|
||||
failureMode: ctx.failureMode.combine(entry.FailMode),
|
||||
}
|
||||
ctx.children[entry.Situation] = inner_ctx
|
||||
}
|
||||
|
||||
if inner_ctx.shouldVisit() {
|
||||
ctxMgr.SetValues(gls.Values{nodeKey: inner_ctx}, func() {
|
||||
inner_ctx.conveyInner(entry.Situation, entry.Func)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func (ctx *context) SkipSo(stuff ...interface{}) {
|
||||
ctx.assertionReport(reporting.NewSkipReport())
|
||||
}
|
||||
|
||||
func (ctx *context) So(actual interface{}, assert assertion, expected ...interface{}) {
|
||||
if result := assert(actual, expected...); result == assertionSuccess {
|
||||
ctx.assertionReport(reporting.NewSuccessReport())
|
||||
} else {
|
||||
ctx.assertionReport(reporting.NewFailureReport(result))
|
||||
}
|
||||
}
|
||||
|
||||
func (ctx *context) Reset(action func()) {
|
||||
/* TODO: Failure mode configuration */
|
||||
ctx.resets = append(ctx.resets, action)
|
||||
}
|
||||
|
||||
func (ctx *context) Print(items ...interface{}) (int, error) {
|
||||
fmt.Fprint(ctx.reporter, items...)
|
||||
return fmt.Print(items...)
|
||||
}
|
||||
|
||||
func (ctx *context) Println(items ...interface{}) (int, error) {
|
||||
fmt.Fprintln(ctx.reporter, items...)
|
||||
return fmt.Println(items...)
|
||||
}
|
||||
|
||||
func (ctx *context) Printf(format string, items ...interface{}) (int, error) {
|
||||
fmt.Fprintf(ctx.reporter, format, items...)
|
||||
return fmt.Printf(format, items...)
|
||||
}
|
||||
|
||||
//////////////////////////////////// Private ////////////////////////////////////
|
||||
|
||||
// shouldVisit returns true iff we should traverse down into a Convey. Note
|
||||
// that just because we don't traverse a Convey this time, doesn't mean that
|
||||
// we may not traverse it on a subsequent pass.
|
||||
func (c *context) shouldVisit() bool {
|
||||
return !c.complete && *c.expectChildRun
|
||||
}
|
||||
|
||||
// conveyInner is the function which actually executes the user's anonymous test
|
||||
// function body. At this point, Convey or RootConvey has decided that this
|
||||
// function should actually run.
|
||||
func (ctx *context) conveyInner(situation string, f func(C)) {
|
||||
// Record/Reset state for next time.
|
||||
defer func() {
|
||||
ctx.executedOnce = true
|
||||
|
||||
// This is only needed at the leaves, but there's no harm in also setting it
|
||||
// when returning from branch Convey's
|
||||
*ctx.expectChildRun = false
|
||||
}()
|
||||
|
||||
// Set up+tear down our scope for the reporter
|
||||
ctx.reporter.Enter(reporting.NewScopeReport(situation))
|
||||
defer ctx.reporter.Exit()
|
||||
|
||||
// Recover from any panics in f, and assign the `complete` status for this
|
||||
// node of the tree.
|
||||
defer func() {
|
||||
ctx.complete = true
|
||||
if problem := recover(); problem != nil {
|
||||
if problem, ok := problem.(*conveyErr); ok {
|
||||
panic(problem)
|
||||
}
|
||||
if problem != failureHalt {
|
||||
ctx.reporter.Report(reporting.NewErrorReport(problem))
|
||||
}
|
||||
} else {
|
||||
for _, child := range ctx.children {
|
||||
if !child.complete {
|
||||
ctx.complete = false
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
// Resets are registered as the `f` function executes, so nil them here.
|
||||
// All resets are run in registration order (FIFO).
|
||||
ctx.resets = []func(){}
|
||||
defer func() {
|
||||
for _, r := range ctx.resets {
|
||||
// panics handled by the previous defer
|
||||
r()
|
||||
}
|
||||
}()
|
||||
|
||||
if f == nil {
|
||||
// if f is nil, this was either a Convey(..., nil), or a SkipConvey
|
||||
ctx.reporter.Report(reporting.NewSkipReport())
|
||||
} else {
|
||||
f(ctx)
|
||||
}
|
||||
}
|
||||
|
||||
// assertionReport is a helper for So and SkipSo which makes the report and
|
||||
// then possibly panics, depending on the current context's failureMode.
|
||||
func (ctx *context) assertionReport(r *reporting.AssertionResult) {
|
||||
ctx.reporter.Report(r)
|
||||
if r.Failure != "" && ctx.failureMode == FailureHalts {
|
||||
panic(failureHalt)
|
||||
}
|
||||
}
|
@ -0,0 +1,4 @@
|
||||
#ignore
|
||||
-timeout=1s
|
||||
#-covermode=count
|
||||
#-coverpkg=github.com/smartystreets/goconvey/convey,github.com/smartystreets/goconvey/convey/gotest,github.com/smartystreets/goconvey/convey/reporting
|
@ -0,0 +1,103 @@
|
||||
package convey
|
||||
|
||||
type actionSpecifier uint8
|
||||
|
||||
const (
|
||||
noSpecifier actionSpecifier = iota
|
||||
skipConvey
|
||||
focusConvey
|
||||
)
|
||||
|
||||
type suite struct {
|
||||
Situation string
|
||||
Test t
|
||||
Focus bool
|
||||
Func func(C) // nil means skipped
|
||||
FailMode FailureMode
|
||||
}
|
||||
|
||||
func newSuite(situation string, failureMode FailureMode, f func(C), test t, specifier actionSpecifier) *suite {
|
||||
ret := &suite{
|
||||
Situation: situation,
|
||||
Test: test,
|
||||
Func: f,
|
||||
FailMode: failureMode,
|
||||
}
|
||||
switch specifier {
|
||||
case skipConvey:
|
||||
ret.Func = nil
|
||||
case focusConvey:
|
||||
ret.Focus = true
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
func discover(items []interface{}) *suite {
|
||||
name, items := parseName(items)
|
||||
test, items := parseGoTest(items)
|
||||
failure, items := parseFailureMode(items)
|
||||
action, items := parseAction(items)
|
||||
specifier, items := parseSpecifier(items)
|
||||
|
||||
if len(items) != 0 {
|
||||
conveyPanic(parseError)
|
||||
}
|
||||
|
||||
return newSuite(name, failure, action, test, specifier)
|
||||
}
|
||||
func item(items []interface{}) interface{} {
|
||||
if len(items) == 0 {
|
||||
conveyPanic(parseError)
|
||||
}
|
||||
return items[0]
|
||||
}
|
||||
func parseName(items []interface{}) (string, []interface{}) {
|
||||
if name, parsed := item(items).(string); parsed {
|
||||
return name, items[1:]
|
||||
}
|
||||
conveyPanic(parseError)
|
||||
panic("never get here")
|
||||
}
|
||||
func parseGoTest(items []interface{}) (t, []interface{}) {
|
||||
if test, parsed := item(items).(t); parsed {
|
||||
return test, items[1:]
|
||||
}
|
||||
return nil, items
|
||||
}
|
||||
func parseFailureMode(items []interface{}) (FailureMode, []interface{}) {
|
||||
if mode, parsed := item(items).(FailureMode); parsed {
|
||||
return mode, items[1:]
|
||||
}
|
||||
return FailureInherits, items
|
||||
}
|
||||
func parseAction(items []interface{}) (func(C), []interface{}) {
|
||||
switch x := item(items).(type) {
|
||||
case nil:
|
||||
return nil, items[1:]
|
||||
case func(C):
|
||||
return x, items[1:]
|
||||
case func():
|
||||
return func(C) { x() }, items[1:]
|
||||
}
|
||||
conveyPanic(parseError)
|
||||
panic("never get here")
|
||||
}
|
||||
func parseSpecifier(items []interface{}) (actionSpecifier, []interface{}) {
|
||||
if len(items) == 0 {
|
||||
return noSpecifier, items
|
||||
}
|
||||
if spec, ok := items[0].(actionSpecifier); ok {
|
||||
return spec, items[1:]
|
||||
}
|
||||
conveyPanic(parseError)
|
||||
panic("never get here")
|
||||
}
|
||||
|
||||
// This interface allows us to pass the *testing.T struct
|
||||
// throughout the internals of this package without ever
|
||||
// having to import the "testing" package.
|
||||
type t interface {
|
||||
Fail()
|
||||
}
|
||||
|
||||
const parseError = "You must provide a name (string), then a *testing.T (if in outermost scope), an optional FailureMode, and then an action (func())."
|
@ -0,0 +1,218 @@
|
||||
// Package convey contains all of the public-facing entry points to this project.
|
||||
// This means that it should never be required of the user to import any other
|
||||
// packages from this project as they serve internal purposes.
|
||||
package convey
|
||||
|
||||
import "github.com/smartystreets/goconvey/convey/reporting"
|
||||
|
||||
////////////////////////////////// suite //////////////////////////////////
|
||||
|
||||
// C is the Convey context which you can optionally obtain in your action
|
||||
// by calling Convey like:
|
||||
//
|
||||
// Convey(..., func(c C) {
|
||||
// ...
|
||||
// })
|
||||
//
|
||||
// See the documentation on Convey for more details.
|
||||
//
|
||||
// All methods in this context behave identically to the global functions of the
|
||||
// same name in this package.
|
||||
type C interface {
|
||||
Convey(items ...interface{})
|
||||
SkipConvey(items ...interface{})
|
||||
FocusConvey(items ...interface{})
|
||||
|
||||
So(actual interface{}, assert assertion, expected ...interface{})
|
||||
SkipSo(stuff ...interface{})
|
||||
|
||||
Reset(action func())
|
||||
|
||||
Println(items ...interface{}) (int, error)
|
||||
Print(items ...interface{}) (int, error)
|
||||
Printf(format string, items ...interface{}) (int, error)
|
||||
}
|
||||
|
||||
// Convey is the method intended for use when declaring the scopes of
|
||||
// a specification. Each scope has a description and a func() which may contain
|
||||
// other calls to Convey(), Reset() or Should-style assertions. Convey calls can
|
||||
// be nested as far as you see fit.
|
||||
//
|
||||
// IMPORTANT NOTE: The top-level Convey() within a Test method
|
||||
// must conform to the following signature:
|
||||
//
|
||||
// Convey(description string, t *testing.T, action func())
|
||||
//
|
||||
// All other calls should look like this (no need to pass in *testing.T):
|
||||
//
|
||||
// Convey(description string, action func())
|
||||
//
|
||||
// Don't worry, goconvey will panic if you get it wrong so you can fix it.
|
||||
//
|
||||
// Additionally, you may explicitly obtain access to the Convey context by doing:
|
||||
//
|
||||
// Convey(description string, action func(c C))
|
||||
//
|
||||
// You may need to do this if you want to pass the context through to a
|
||||
// goroutine, or to close over the context in a handler to a library which
|
||||
// calls your handler in a goroutine (httptest comes to mind).
|
||||
//
|
||||
// All Convey()-blocks also accept an optional parameter of FailureMode which sets
|
||||
// how goconvey should treat failures for So()-assertions in the block and
|
||||
// nested blocks. See the constants in this file for the available options.
|
||||
//
|
||||
// By default it will inherit from its parent block and the top-level blocks
|
||||
// default to the FailureHalts setting.
|
||||
//
|
||||
// This parameter is inserted before the block itself:
|
||||
//
|
||||
// Convey(description string, t *testing.T, mode FailureMode, action func())
|
||||
// Convey(description string, mode FailureMode, action func())
|
||||
//
|
||||
// See the examples package for, well, examples.
|
||||
func Convey(items ...interface{}) {
|
||||
if ctx := getCurrentContext(); ctx == nil {
|
||||
rootConvey(items...)
|
||||
} else {
|
||||
ctx.Convey(items...)
|
||||
}
|
||||
}
|
||||
|
||||
// SkipConvey is analagous to Convey except that the scope is not executed
|
||||
// (which means that child scopes defined within this scope are not run either).
|
||||
// The reporter will be notified that this step was skipped.
|
||||
func SkipConvey(items ...interface{}) {
|
||||
Convey(append(items, skipConvey)...)
|
||||
}
|
||||
|
||||
// FocusConvey is has the inverse effect of SkipConvey. If the top-level
|
||||
// Convey is changed to `FocusConvey`, only nested scopes that are defined
|
||||
// with FocusConvey will be run. The rest will be ignored completely. This
|
||||
// is handy when debugging a large suite that runs a misbehaving function
|
||||
// repeatedly as you can disable all but one of that function
|
||||
// without swaths of `SkipConvey` calls, just a targeted chain of calls
|
||||
// to FocusConvey.
|
||||
func FocusConvey(items ...interface{}) {
|
||||
Convey(append(items, focusConvey)...)
|
||||
}
|
||||
|
||||
// Reset registers a cleanup function to be run after each Convey()
|
||||
// in the same scope. See the examples package for a simple use case.
|
||||
func Reset(action func()) {
|
||||
mustGetCurrentContext().Reset(action)
|
||||
}
|
||||
|
||||
/////////////////////////////////// Assertions ///////////////////////////////////
|
||||
|
||||
// assertion is an alias for a function with a signature that the convey.So()
|
||||
// method can handle. Any future or custom assertions should conform to this
|
||||
// method signature. The return value should be an empty string if the assertion
|
||||
// passes and a well-formed failure message if not.
|
||||
type assertion func(actual interface{}, expected ...interface{}) string
|
||||
|
||||
const assertionSuccess = ""
|
||||
|
||||
// So is the means by which assertions are made against the system under test.
|
||||
// The majority of exported names in the assertions package begin with the word
|
||||
// 'Should' and describe how the first argument (actual) should compare with any
|
||||
// of the final (expected) arguments. How many final arguments are accepted
|
||||
// depends on the particular assertion that is passed in as the assert argument.
|
||||
// See the examples package for use cases and the assertions package for
|
||||
// documentation on specific assertion methods. A failing assertion will
|
||||
// cause t.Fail() to be invoked--you should never call this method (or other
|
||||
// failure-inducing methods) in your test code. Leave that to GoConvey.
|
||||
func So(actual interface{}, assert assertion, expected ...interface{}) {
|
||||
mustGetCurrentContext().So(actual, assert, expected...)
|
||||
}
|
||||
|
||||
// SkipSo is analagous to So except that the assertion that would have been passed
|
||||
// to So is not executed and the reporter is notified that the assertion was skipped.
|
||||
func SkipSo(stuff ...interface{}) {
|
||||
mustGetCurrentContext().SkipSo()
|
||||
}
|
||||
|
||||
// FailureMode is a type which determines how the So() blocks should fail
|
||||
// if their assertion fails. See constants further down for acceptable values
|
||||
type FailureMode string
|
||||
|
||||
const (
|
||||
|
||||
// FailureContinues is a failure mode which prevents failing
|
||||
// So()-assertions from halting Convey-block execution, instead
|
||||
// allowing the test to continue past failing So()-assertions.
|
||||
FailureContinues FailureMode = "continue"
|
||||
|
||||
// FailureHalts is the default setting for a top-level Convey()-block
|
||||
// and will cause all failing So()-assertions to halt further execution
|
||||
// in that test-arm and continue on to the next arm.
|
||||
FailureHalts FailureMode = "halt"
|
||||
|
||||
// FailureInherits is the default setting for failure-mode, it will
|
||||
// default to the failure-mode of the parent block. You should never
|
||||
// need to specify this mode in your tests..
|
||||
FailureInherits FailureMode = "inherits"
|
||||
)
|
||||
|
||||
func (f FailureMode) combine(other FailureMode) FailureMode {
|
||||
if other == FailureInherits {
|
||||
return f
|
||||
}
|
||||
return other
|
||||
}
|
||||
|
||||
var defaultFailureMode FailureMode = FailureHalts
|
||||
|
||||
// SetDefaultFailureMode allows you to specify the default failure mode
|
||||
// for all Convey blocks. It is meant to be used in an init function to
|
||||
// allow the default mode to be changdd across all tests for an entire packgae
|
||||
// but it can be used anywhere.
|
||||
func SetDefaultFailureMode(mode FailureMode) {
|
||||
if mode == FailureContinues || mode == FailureHalts {
|
||||
defaultFailureMode = mode
|
||||
} else {
|
||||
panic("You may only use the constants named 'FailureContinues' and 'FailureHalts' as default failure modes.")
|
||||
}
|
||||
}
|
||||
|
||||
//////////////////////////////////// Print functions ////////////////////////////////////
|
||||
|
||||
// Print is analogous to fmt.Print (and it even calls fmt.Print). It ensures that
|
||||
// output is aligned with the corresponding scopes in the web UI.
|
||||
func Print(items ...interface{}) (written int, err error) {
|
||||
return mustGetCurrentContext().Print(items...)
|
||||
}
|
||||
|
||||
// Print is analogous to fmt.Println (and it even calls fmt.Println). It ensures that
|
||||
// output is aligned with the corresponding scopes in the web UI.
|
||||
func Println(items ...interface{}) (written int, err error) {
|
||||
return mustGetCurrentContext().Println(items...)
|
||||
}
|
||||
|
||||
// Print is analogous to fmt.Printf (and it even calls fmt.Printf). It ensures that
|
||||
// output is aligned with the corresponding scopes in the web UI.
|
||||
func Printf(format string, items ...interface{}) (written int, err error) {
|
||||
return mustGetCurrentContext().Printf(format, items...)
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// SuppressConsoleStatistics prevents automatic printing of console statistics.
|
||||
// Calling PrintConsoleStatistics explicitly will force printing of statistics.
|
||||
func SuppressConsoleStatistics() {
|
||||
reporting.SuppressConsoleStatistics()
|
||||
}
|
||||
|
||||
// ConsoleStatistics may be called at any time to print assertion statistics.
|
||||
// Generally, the best place to do this would be in a TestMain function,
|
||||
// after all tests have been run. Something like this:
|
||||
//
|
||||
// func TestMain(m *testing.M) {
|
||||
// convey.SuppressConsoleStatistics()
|
||||
// result := m.Run()
|
||||
// convey.PrintConsoleStatistics()
|
||||
// os.Exit(result)
|
||||
// }
|
||||
//
|
||||
func PrintConsoleStatistics() {
|
||||
reporting.PrintConsoleStatistics()
|
||||
}
|
@ -0,0 +1,37 @@
|
||||
// Package gotest contains internal functionality. Although this package
|
||||
// contains one or more exported names it is not intended for public
|
||||
// consumption. See the examples package for how to use this project.
|
||||
package gotest
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"runtime"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func FormatExternalFileAndLine() string {
|
||||
file, line, _ := ResolveExternalCaller()
|
||||
if line == -1 {
|
||||
return "<unknown caller!>" // panic?
|
||||
}
|
||||
return fmt.Sprintf("%s:%d", file, line)
|
||||
}
|
||||
|
||||
func ResolveExternalCaller() (file string, line int, name string) {
|
||||
var caller_id uintptr
|
||||
callers := runtime.Callers(0, callStack)
|
||||
|
||||
for x := 0; x < callers; x++ {
|
||||
caller_id, file, line, _ = runtime.Caller(x)
|
||||
if strings.HasSuffix(file, "_test.go") || strings.HasSuffix(file, "_tests.go") {
|
||||
name = runtime.FuncForPC(caller_id).Name()
|
||||
return
|
||||
}
|
||||
}
|
||||
file, line, name = "<unkown file>", -1, "<unknown name>"
|
||||
return // panic?
|
||||
}
|
||||
|
||||
const maxStackDepth = 100 // This had better be enough...
|
||||
|
||||
var callStack []uintptr = make([]uintptr, maxStackDepth, maxStackDepth)
|
@ -0,0 +1,76 @@
|
||||
package convey
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"os"
|
||||
|
||||
"github.com/jtolds/gls"
|
||||
"github.com/smartystreets/assertions"
|
||||
"github.com/smartystreets/goconvey/convey/reporting"
|
||||
)
|
||||
|
||||
func init() {
|
||||
assertions.GoConveyMode(true)
|
||||
|
||||
declareFlags()
|
||||
|
||||
ctxMgr = gls.NewContextManager()
|
||||
}
|
||||
|
||||
func declareFlags() {
|
||||
flag.BoolVar(&json, "json", false, "When true, emits results in JSON blocks. Default: 'false'")
|
||||
flag.BoolVar(&silent, "silent", false, "When true, all output from GoConvey is suppressed.")
|
||||
flag.BoolVar(&story, "story", false, "When true, emits story output, otherwise emits dot output. When not provided, this flag mirros the value of the '-test.v' flag")
|
||||
|
||||
if noStoryFlagProvided() {
|
||||
story = verboseEnabled
|
||||
}
|
||||
|
||||
// FYI: flag.Parse() is called from the testing package.
|
||||
}
|
||||
|
||||
func noStoryFlagProvided() bool {
|
||||
return !story && !storyDisabled
|
||||
}
|
||||
|
||||
func buildReporter() reporting.Reporter {
|
||||
switch {
|
||||
case testReporter != nil:
|
||||
return testReporter
|
||||
case json:
|
||||
return reporting.BuildJsonReporter()
|
||||
case silent:
|
||||
return reporting.BuildSilentReporter()
|
||||
case story:
|
||||
return reporting.BuildStoryReporter()
|
||||
default:
|
||||
return reporting.BuildDotReporter()
|
||||
}
|
||||
}
|
||||
|
||||
var (
|
||||
ctxMgr *gls.ContextManager
|
||||
|
||||
// only set by internal tests
|
||||
testReporter reporting.Reporter
|
||||
)
|
||||
|
||||
var (
|
||||
json bool
|
||||
silent bool
|
||||
story bool
|
||||
|
||||
verboseEnabled = flagFound("-test.v=true")
|
||||
storyDisabled = flagFound("-story=false")
|
||||
)
|
||||
|
||||
// flagFound parses the command line args manually for flags defined in other
|
||||
// packages. Like the '-v' flag from the "testing" package, for instance.
|
||||
func flagFound(flagValue string) bool {
|
||||
for _, arg := range os.Args {
|
||||
if arg == flagValue {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
@ -0,0 +1,15 @@
|
||||
package convey
|
||||
|
||||
import (
|
||||
"github.com/smartystreets/goconvey/convey/reporting"
|
||||
)
|
||||
|
||||
type nilReporter struct{}
|
||||
|
||||
func (self *nilReporter) BeginStory(story *reporting.StoryReport) {}
|
||||
func (self *nilReporter) Enter(scope *reporting.ScopeReport) {}
|
||||
func (self *nilReporter) Report(report *reporting.AssertionResult) {}
|
||||
func (self *nilReporter) Exit() {}
|
||||
func (self *nilReporter) EndStory() {}
|
||||
func (self *nilReporter) Write(p []byte) (int, error) { return len(p), nil }
|
||||
func newNilReporter() *nilReporter { return &nilReporter{} }
|
@ -0,0 +1,16 @@
|
||||
package reporting
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
)
|
||||
|
||||
type console struct{}
|
||||
|
||||
func (self *console) Write(p []byte) (n int, err error) {
|
||||
return fmt.Print(string(p))
|
||||
}
|
||||
|
||||
func NewConsole() io.Writer {
|
||||
return new(console)
|
||||
}
|
@ -0,0 +1,5 @@
|
||||
// Package reporting contains internal functionality related
|
||||
// to console reporting and output. Although this package has
|
||||
// exported names is not intended for public consumption. See the
|
||||
// examples package for how to use this project.
|
||||
package reporting
|
@ -0,0 +1,40 @@
|
||||
package reporting
|
||||
|
||||
import "fmt"
|
||||
|
||||
type dot struct{ out *Printer }
|
||||
|
||||
func (self *dot) BeginStory(story *StoryReport) {}
|
||||
|
||||
func (self *dot) Enter(scope *ScopeReport) {}
|
||||
|
||||
func (self *dot) Report(report *AssertionResult) {
|
||||
if report.Error != nil {
|
||||
fmt.Print(redColor)
|
||||
self.out.Insert(dotError)
|
||||
} else if report.Failure != "" {
|
||||
fmt.Print(yellowColor)
|
||||
self.out.Insert(dotFailure)
|
||||
} else if report.Skipped {
|
||||
fmt.Print(yellowColor)
|
||||
self.out.Insert(dotSkip)
|
||||
} else {
|
||||
fmt.Print(greenColor)
|
||||
self.out.Insert(dotSuccess)
|
||||
}
|
||||
fmt.Print(resetColor)
|
||||
}
|
||||
|
||||
func (self *dot) Exit() {}
|
||||
|
||||
func (self *dot) EndStory() {}
|
||||
|
||||
func (self *dot) Write(content []byte) (written int, err error) {
|
||||
return len(content), nil // no-op
|
||||
}
|
||||
|
||||
func NewDotReporter(out *Printer) *dot {
|
||||
self := new(dot)
|
||||
self.out = out
|
||||
return self
|
||||
}
|
@ -0,0 +1,33 @@
|
||||
package reporting
|
||||
|
||||
type gotestReporter struct{ test T }
|
||||
|
||||
func (self *gotestReporter) BeginStory(story *StoryReport) {
|
||||
self.test = story.Test
|
||||
}
|
||||
|
||||
func (self *gotestReporter) Enter(scope *ScopeReport) {}
|
||||
|
||||
func (self *gotestReporter) Report(r *AssertionResult) {
|
||||
if !passed(r) {
|
||||
self.test.Fail()
|
||||
}
|
||||
}
|
||||
|
||||
func (self *gotestReporter) Exit() {}
|
||||
|
||||
func (self *gotestReporter) EndStory() {
|
||||
self.test = nil
|
||||
}
|
||||
|
||||
func (self *gotestReporter) Write(content []byte) (written int, err error) {
|
||||
return len(content), nil // no-op
|
||||
}
|
||||
|
||||
func NewGoTestReporter() *gotestReporter {
|
||||
return new(gotestReporter)
|
||||
}
|
||||
|
||||
func passed(r *AssertionResult) bool {
|
||||
return r.Error == nil && r.Failure == ""
|
||||
}
|
@ -0,0 +1,97 @@
|
||||
package reporting
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"runtime"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func init() {
|
||||
if !isXterm() {
|
||||
monochrome()
|
||||
}
|
||||
|
||||
if runtime.GOOS == "windows" {
|
||||
success, failure, error_ = dotSuccess, dotFailure, dotError
|
||||
}
|
||||
}
|
||||
|
||||
func BuildJsonReporter() Reporter {
|
||||
out := NewPrinter(NewConsole())
|
||||
return NewReporters(
|
||||
NewGoTestReporter(),
|
||||
NewJsonReporter(out))
|
||||
}
|
||||
func BuildDotReporter() Reporter {
|
||||
out := NewPrinter(NewConsole())
|
||||
return NewReporters(
|
||||
NewGoTestReporter(),
|
||||
NewDotReporter(out),
|
||||
NewProblemReporter(out),
|
||||
consoleStatistics)
|
||||
}
|
||||
func BuildStoryReporter() Reporter {
|
||||
out := NewPrinter(NewConsole())
|
||||
return NewReporters(
|
||||
NewGoTestReporter(),
|
||||
NewStoryReporter(out),
|
||||
NewProblemReporter(out),
|
||||
consoleStatistics)
|
||||
}
|
||||
func BuildSilentReporter() Reporter {
|
||||
out := NewPrinter(NewConsole())
|
||||
return NewReporters(
|
||||
NewGoTestReporter(),
|
||||
NewProblemReporter(out))
|
||||
}
|
||||
|
||||
var (
|
||||
newline = "\n"
|
||||
success = "✔"
|
||||
failure = "✘"
|
||||
error_ = "🔥"
|
||||
skip = "⚠"
|
||||
dotSuccess = "."
|
||||
dotFailure = "x"
|
||||
dotError = "E"
|
||||
dotSkip = "S"
|
||||
errorTemplate = "* %s \nLine %d: - %v \n%s\n"
|
||||
failureTemplate = "* %s \nLine %d:\n%s\n"
|
||||
)
|
||||
|
||||
var (
|
||||
greenColor = "\033[32m"
|
||||
yellowColor = "\033[33m"
|
||||
redColor = "\033[31m"
|
||||
resetColor = "\033[0m"
|
||||
)
|
||||
|
||||
var consoleStatistics = NewStatisticsReporter(NewPrinter(NewConsole()))
|
||||
|
||||
func SuppressConsoleStatistics() { consoleStatistics.Suppress() }
|
||||
func PrintConsoleStatistics() { consoleStatistics.PrintSummary() }
|
||||
|
||||
// QuiteMode disables all console output symbols. This is only meant to be used
|
||||
// for tests that are internal to goconvey where the output is distracting or
|
||||
// otherwise not needed in the test output.
|
||||
func QuietMode() {
|
||||
success, failure, error_, skip, dotSuccess, dotFailure, dotError, dotSkip = "", "", "", "", "", "", "", ""
|
||||
}
|
||||
|
||||
func monochrome() {
|
||||
greenColor, yellowColor, redColor, resetColor = "", "", "", ""
|
||||
}
|
||||
|
||||
func isXterm() bool {
|
||||
env := fmt.Sprintf("%v", os.Environ())
|
||||
return strings.Contains(env, " TERM=isXterm") ||
|
||||
strings.Contains(env, " TERM=xterm")
|
||||
}
|
||||
|
||||
// This interface allows us to pass the *testing.T struct
|
||||
// throughout the internals of this tool without ever
|
||||
// having to import the "testing" package.
|
||||
type T interface {
|
||||
Fail()
|
||||
}
|
@ -0,0 +1,88 @@
|
||||
// TODO: under unit test
|
||||
|
||||
package reporting
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type JsonReporter struct {
|
||||
out *Printer
|
||||
currentKey []string
|
||||
current *ScopeResult
|
||||
index map[string]*ScopeResult
|
||||
scopes []*ScopeResult
|
||||
}
|
||||
|
||||
func (self *JsonReporter) depth() int { return len(self.currentKey) }
|
||||
|
||||
func (self *JsonReporter) BeginStory(story *StoryReport) {}
|
||||
|
||||
func (self *JsonReporter) Enter(scope *ScopeReport) {
|
||||
self.currentKey = append(self.currentKey, scope.Title)
|
||||
ID := strings.Join(self.currentKey, "|")
|
||||
if _, found := self.index[ID]; !found {
|
||||
next := newScopeResult(scope.Title, self.depth(), scope.File, scope.Line)
|
||||
self.scopes = append(self.scopes, next)
|
||||
self.index[ID] = next
|
||||
}
|
||||
self.current = self.index[ID]
|
||||
}
|
||||
|
||||
func (self *JsonReporter) Report(report *AssertionResult) {
|
||||
self.current.Assertions = append(self.current.Assertions, report)
|
||||
}
|
||||
|
||||
func (self *JsonReporter) Exit() {
|
||||
self.currentKey = self.currentKey[:len(self.currentKey)-1]
|
||||
}
|
||||
|
||||
func (self *JsonReporter) EndStory() {
|
||||
self.report()
|
||||
self.reset()
|
||||
}
|
||||
func (self *JsonReporter) report() {
|
||||
scopes := []string{}
|
||||
for _, scope := range self.scopes {
|
||||
serialized, err := json.Marshal(scope)
|
||||
if err != nil {
|
||||
self.out.Println(jsonMarshalFailure)
|
||||
panic(err)
|
||||
}
|
||||
var buffer bytes.Buffer
|
||||
json.Indent(&buffer, serialized, "", " ")
|
||||
scopes = append(scopes, buffer.String())
|
||||
}
|
||||
self.out.Print(fmt.Sprintf("%s\n%s,\n%s\n", OpenJson, strings.Join(scopes, ","), CloseJson))
|
||||
}
|
||||
func (self *JsonReporter) reset() {
|
||||
self.scopes = []*ScopeResult{}
|
||||
self.index = map[string]*ScopeResult{}
|
||||
self.currentKey = nil
|
||||
}
|
||||
|
||||
func (self *JsonReporter) Write(content []byte) (written int, err error) {
|
||||
self.current.Output += string(content)
|
||||
return len(content), nil
|
||||
}
|
||||
|
||||
func NewJsonReporter(out *Printer) *JsonReporter {
|
||||
self := new(JsonReporter)
|
||||
self.out = out
|
||||
self.reset()
|
||||
return self
|
||||
}
|
||||
|
||||
const OpenJson = ">->->OPEN-JSON->->->" // "⌦"
|
||||
const CloseJson = "<-<-<-CLOSE-JSON<-<-<" // "⌫"
|
||||
const jsonMarshalFailure = `
|
||||
|
||||
GOCONVEY_JSON_MARSHALL_FAILURE: There was an error when attempting to convert test results to JSON.
|
||||
Please file a bug report and reference the code that caused this failure if possible.
|
||||
|
||||
Here's the panic:
|
||||
|
||||
`
|
@ -0,0 +1,57 @@
|
||||
package reporting
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type Printer struct {
|
||||
out io.Writer
|
||||
prefix string
|
||||
}
|
||||
|
||||
func (self *Printer) Println(message string, values ...interface{}) {
|
||||
formatted := self.format(message, values...) + newline
|
||||
self.out.Write([]byte(formatted))
|
||||
}
|
||||
|
||||
func (self *Printer) Print(message string, values ...interface{}) {
|
||||
formatted := self.format(message, values...)
|
||||
self.out.Write([]byte(formatted))
|
||||
}
|
||||
|
||||
func (self *Printer) Insert(text string) {
|
||||
self.out.Write([]byte(text))
|
||||
}
|
||||
|
||||
func (self *Printer) format(message string, values ...interface{}) string {
|
||||
var formatted string
|
||||
if len(values) == 0 {
|
||||
formatted = self.prefix + message
|
||||
} else {
|
||||
formatted = self.prefix + fmt.Sprintf(message, values...)
|
||||
}
|
||||
indented := strings.Replace(formatted, newline, newline+self.prefix, -1)
|
||||
return strings.TrimRight(indented, space)
|
||||
}
|
||||
|
||||
func (self *Printer) Indent() {
|
||||
self.prefix += pad
|
||||
}
|
||||
|
||||
func (self *Printer) Dedent() {
|
||||
if len(self.prefix) >= padLength {
|
||||
self.prefix = self.prefix[:len(self.prefix)-padLength]
|
||||
}
|
||||
}
|
||||
|
||||
func NewPrinter(out io.Writer) *Printer {
|
||||
self := new(Printer)
|
||||
self.out = out
|
||||
return self
|
||||
}
|
||||
|
||||
const space = " "
|
||||
const pad = space + space
|
||||
const padLength = len(pad)
|
@ -0,0 +1,68 @@
|
||||
package reporting
|
||||
|
||||
import "fmt"
|
||||
|
||||
type problem struct {
|
||||
out *Printer
|
||||
errors []*AssertionResult
|
||||
failures []*AssertionResult
|
||||
}
|
||||
|
||||
func (self *problem) BeginStory(story *StoryReport) {}
|
||||
|
||||
func (self *problem) Enter(scope *ScopeReport) {}
|
||||
|
||||
func (self *problem) Report(report *AssertionResult) {
|
||||
if report.Error != nil {
|
||||
self.errors = append(self.errors, report)
|
||||
} else if report.Failure != "" {
|
||||
self.failures = append(self.failures, report)
|
||||
}
|
||||
}
|
||||
|
||||
func (self *problem) Exit() {}
|
||||
|
||||
func (self *problem) EndStory() {
|
||||
self.show(self.showErrors, redColor)
|
||||
self.show(self.showFailures, yellowColor)
|
||||
self.prepareForNextStory()
|
||||
}
|
||||
func (self *problem) show(display func(), color string) {
|
||||
fmt.Print(color)
|
||||
display()
|
||||
fmt.Print(resetColor)
|
||||
self.out.Dedent()
|
||||
}
|
||||
func (self *problem) showErrors() {
|
||||
for i, e := range self.errors {
|
||||
if i == 0 {
|
||||
self.out.Println("\nErrors:\n")
|
||||
self.out.Indent()
|
||||
}
|
||||
self.out.Println(errorTemplate, e.File, e.Line, e.Error, e.StackTrace)
|
||||
}
|
||||
}
|
||||
func (self *problem) showFailures() {
|
||||
for i, f := range self.failures {
|
||||
if i == 0 {
|
||||
self.out.Println("\nFailures:\n")
|
||||
self.out.Indent()
|
||||
}
|
||||
self.out.Println(failureTemplate, f.File, f.Line, f.Failure)
|
||||
}
|
||||
}
|
||||
|
||||
func (self *problem) Write(content []byte) (written int, err error) {
|
||||
return len(content), nil // no-op
|
||||
}
|
||||
|
||||
func NewProblemReporter(out *Printer) *problem {
|
||||
self := new(problem)
|
||||
self.out = out
|
||||
self.prepareForNextStory()
|
||||
return self
|
||||
}
|
||||
func (self *problem) prepareForNextStory() {
|
||||
self.errors = []*AssertionResult{}
|
||||
self.failures = []*AssertionResult{}
|
||||
}
|
@ -0,0 +1,39 @@
|
||||
package reporting
|
||||
|
||||
import "io"
|
||||
|
||||
type Reporter interface {
|
||||
BeginStory(story *StoryReport)
|
||||
Enter(scope *ScopeReport)
|
||||
Report(r *AssertionResult)
|
||||
Exit()
|
||||
EndStory()
|
||||
io.Writer
|
||||
}
|
||||
|
||||
type reporters struct{ collection []Reporter }
|
||||
|
||||
func (self *reporters) BeginStory(s *StoryReport) { self.foreach(func(r Reporter) { r.BeginStory(s) }) }
|
||||
func (self *reporters) Enter(s *ScopeReport) { self.foreach(func(r Reporter) { r.Enter(s) }) }
|
||||
func (self *reporters) Report(a *AssertionResult) { self.foreach(func(r Reporter) { r.Report(a) }) }
|
||||
func (self *reporters) Exit() { self.foreach(func(r Reporter) { r.Exit() }) }
|
||||
func (self *reporters) EndStory() { self.foreach(func(r Reporter) { r.EndStory() }) }
|
||||
|
||||
func (self *reporters) Write(contents []byte) (written int, err error) {
|
||||
self.foreach(func(r Reporter) {
|
||||
written, err = r.Write(contents)
|
||||
})
|
||||
return written, err
|
||||
}
|
||||
|
||||
func (self *reporters) foreach(action func(Reporter)) {
|
||||
for _, r := range self.collection {
|
||||
action(r)
|
||||
}
|
||||
}
|
||||
|
||||
func NewReporters(collection ...Reporter) *reporters {
|
||||
self := new(reporters)
|
||||
self.collection = collection
|
||||
return self
|
||||
}
|
@ -0,0 +1,2 @@
|
||||
#ignore
|
||||
-timeout=1s
|
@ -0,0 +1,177 @@
|
||||
package reporting
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"runtime"
|
||||
"strings"
|
||||
|
||||
"github.com/smartystreets/goconvey/convey/gotest"
|
||||
)
|
||||
|
||||
////////////////// ScopeReport ////////////////////
|
||||
|
||||
type ScopeReport struct {
|
||||
Title string
|
||||
File string
|
||||
Line int
|
||||
}
|
||||
|
||||
func NewScopeReport(title string) *ScopeReport {
|
||||
file, line, _ := gotest.ResolveExternalCaller()
|
||||
self := new(ScopeReport)
|
||||
self.Title = title
|
||||
self.File = file
|
||||
self.Line = line
|
||||
return self
|
||||
}
|
||||
|
||||
////////////////// ScopeResult ////////////////////
|
||||
|
||||
type ScopeResult struct {
|
||||
Title string
|
||||
File string
|
||||
Line int
|
||||
Depth int
|
||||
Assertions []*AssertionResult
|
||||
Output string
|
||||
}
|
||||
|
||||
func newScopeResult(title string, depth int, file string, line int) *ScopeResult {
|
||||
self := new(ScopeResult)
|
||||
self.Title = title
|
||||
self.Depth = depth
|
||||
self.File = file
|
||||
self.Line = line
|
||||
self.Assertions = []*AssertionResult{}
|
||||
return self
|
||||
}
|
||||
|
||||
/////////////////// StoryReport /////////////////////
|
||||
|
||||
type StoryReport struct {
|
||||
Test T
|
||||
Name string
|
||||
File string
|
||||
Line int
|
||||
}
|
||||
|
||||
func NewStoryReport(test T) *StoryReport {
|
||||
file, line, name := gotest.ResolveExternalCaller()
|
||||
name = removePackagePath(name)
|
||||
self := new(StoryReport)
|
||||
self.Test = test
|
||||
self.Name = name
|
||||
self.File = file
|
||||
self.Line = line
|
||||
return self
|
||||
}
|
||||
|
||||
// name comes in looking like "github.com/smartystreets/goconvey/examples.TestName".
|
||||
// We only want the stuff after the last '.', which is the name of the test function.
|
||||
func removePackagePath(name string) string {
|
||||
parts := strings.Split(name, ".")
|
||||
return parts[len(parts)-1]
|
||||
}
|
||||
|
||||
/////////////////// FailureView ////////////////////////
|
||||
|
||||
type FailureView struct {
|
||||
Message string
|
||||
Expected string
|
||||
Actual string
|
||||
}
|
||||
|
||||
////////////////////AssertionResult //////////////////////
|
||||
|
||||
type AssertionResult struct {
|
||||
File string
|
||||
Line int
|
||||
Expected string
|
||||
Actual string
|
||||
Failure string
|
||||
Error interface{}
|
||||
StackTrace string
|
||||
Skipped bool
|
||||
}
|
||||
|
||||
func NewFailureReport(failure string) *AssertionResult {
|
||||
report := new(AssertionResult)
|
||||
report.File, report.Line = caller()
|
||||
report.StackTrace = stackTrace()
|
||||
parseFailure(failure, report)
|
||||
return report
|
||||
}
|
||||
func parseFailure(failure string, report *AssertionResult) {
|
||||
view := new(FailureView)
|
||||
err := json.Unmarshal([]byte(failure), view)
|
||||
if err == nil {
|
||||
report.Failure = view.Message
|
||||
report.Expected = view.Expected
|
||||
report.Actual = view.Actual
|
||||
} else {
|
||||
report.Failure = failure
|
||||
}
|
||||
}
|
||||
func NewErrorReport(err interface{}) *AssertionResult {
|
||||
report := new(AssertionResult)
|
||||
report.File, report.Line = caller()
|
||||
report.StackTrace = fullStackTrace()
|
||||
report.Error = fmt.Sprintf("%v", err)
|
||||
return report
|
||||
}
|
||||
func NewSuccessReport() *AssertionResult {
|
||||
return new(AssertionResult)
|
||||
}
|
||||
func NewSkipReport() *AssertionResult {
|
||||
report := new(AssertionResult)
|
||||
report.File, report.Line = caller()
|
||||
report.StackTrace = fullStackTrace()
|
||||
report.Skipped = true
|
||||
return report
|
||||
}
|
||||
|
||||
func caller() (file string, line int) {
|
||||
file, line, _ = gotest.ResolveExternalCaller()
|
||||
return
|
||||
}
|
||||
|
||||
func stackTrace() string {
|
||||
buffer := make([]byte, 1024*64)
|
||||
n := runtime.Stack(buffer, false)
|
||||
return removeInternalEntries(string(buffer[:n]))
|
||||
}
|
||||
func fullStackTrace() string {
|
||||
buffer := make([]byte, 1024*64)
|
||||
n := runtime.Stack(buffer, true)
|
||||
return removeInternalEntries(string(buffer[:n]))
|
||||
}
|
||||
func removeInternalEntries(stack string) string {
|
||||
lines := strings.Split(stack, newline)
|
||||
filtered := []string{}
|
||||
for _, line := range lines {
|
||||
if !isExternal(line) {
|
||||
filtered = append(filtered, line)
|
||||
}
|
||||
}
|
||||
return strings.Join(filtered, newline)
|
||||
}
|
||||
func isExternal(line string) bool {
|
||||
for _, p := range internalPackages {
|
||||
if strings.Contains(line, p) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// NOTE: any new packages that host goconvey packages will need to be added here!
|
||||
// An alternative is to scan the goconvey directory and then exclude stuff like
|
||||
// the examples package but that's nasty too.
|
||||
var internalPackages = []string{
|
||||
"goconvey/assertions",
|
||||
"goconvey/convey",
|
||||
"goconvey/execution",
|
||||
"goconvey/gotest",
|
||||
"goconvey/reporting",
|
||||
}
|
@ -0,0 +1,89 @@
|
||||
package reporting
|
||||
|
||||
import "fmt"
|
||||
|
||||
func (self *statistics) BeginStory(story *StoryReport) {}
|
||||
|
||||
func (self *statistics) Enter(scope *ScopeReport) {}
|
||||
|
||||
func (self *statistics) Report(report *AssertionResult) {
|
||||
if !self.failing && report.Failure != "" {
|
||||
self.failing = true
|
||||
}
|
||||
if !self.erroring && report.Error != nil {
|
||||
self.erroring = true
|
||||
}
|
||||
if report.Skipped {
|
||||
self.skipped += 1
|
||||
} else {
|
||||
self.total++
|
||||
}
|
||||
}
|
||||
|
||||
func (self *statistics) Exit() {}
|
||||
|
||||
func (self *statistics) EndStory() {
|
||||
if !self.suppressed {
|
||||
self.PrintSummary()
|
||||
}
|
||||
}
|
||||
|
||||
func (self *statistics) Suppress() {
|
||||
self.suppressed = true
|
||||
}
|
||||
|
||||
func (self *statistics) PrintSummary() {
|
||||
self.reportAssertions()
|
||||
self.reportSkippedSections()
|
||||
self.completeReport()
|
||||
}
|
||||
func (self *statistics) reportAssertions() {
|
||||
self.decideColor()
|
||||
self.out.Print("\n%d total %s", self.total, plural("assertion", self.total))
|
||||
}
|
||||
func (self *statistics) decideColor() {
|
||||
if self.failing && !self.erroring {
|
||||
fmt.Print(yellowColor)
|
||||
} else if self.erroring {
|
||||
fmt.Print(redColor)
|
||||
} else {
|
||||
fmt.Print(greenColor)
|
||||
}
|
||||
}
|
||||
func (self *statistics) reportSkippedSections() {
|
||||
if self.skipped > 0 {
|
||||
fmt.Print(yellowColor)
|
||||
self.out.Print(" (one or more sections skipped)")
|
||||
}
|
||||
}
|
||||
func (self *statistics) completeReport() {
|
||||
fmt.Print(resetColor)
|
||||
self.out.Print("\n")
|
||||
self.out.Print("\n")
|
||||
}
|
||||
|
||||
func (self *statistics) Write(content []byte) (written int, err error) {
|
||||
return len(content), nil // no-op
|
||||
}
|
||||
|
||||
func NewStatisticsReporter(out *Printer) *statistics {
|
||||
self := statistics{}
|
||||
self.out = out
|
||||
return &self
|
||||
}
|
||||
|
||||
type statistics struct {
|
||||
out *Printer
|
||||
total int
|
||||
failing bool
|
||||
erroring bool
|
||||
skipped int
|
||||
suppressed bool
|
||||
}
|
||||
|
||||
func plural(word string, count int) string {
|
||||
if count == 1 {
|
||||
return word
|
||||
}
|
||||
return word + "s"
|
||||
}
|
@ -0,0 +1,73 @@
|
||||
// TODO: in order for this reporter to be completely honest
|
||||
// we need to retrofit to be more like the json reporter such that:
|
||||
// 1. it maintains ScopeResult collections, which count assertions
|
||||
// 2. it reports only after EndStory(), so that all tick marks
|
||||
// are placed near the appropriate title.
|
||||
// 3. Under unit test
|
||||
|
||||
package reporting
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type story struct {
|
||||
out *Printer
|
||||
titlesById map[string]string
|
||||
currentKey []string
|
||||
}
|
||||
|
||||
func (self *story) BeginStory(story *StoryReport) {}
|
||||
|
||||
func (self *story) Enter(scope *ScopeReport) {
|
||||
self.out.Indent()
|
||||
|
||||
self.currentKey = append(self.currentKey, scope.Title)
|
||||
ID := strings.Join(self.currentKey, "|")
|
||||
|
||||
if _, found := self.titlesById[ID]; !found {
|
||||
self.out.Println("")
|
||||
self.out.Print(scope.Title)
|
||||
self.out.Insert(" ")
|
||||
self.titlesById[ID] = scope.Title
|
||||
}
|
||||
}
|
||||
|
||||
func (self *story) Report(report *AssertionResult) {
|
||||
if report.Error != nil {
|
||||
fmt.Print(redColor)
|
||||
self.out.Insert(error_)
|
||||
} else if report.Failure != "" {
|
||||
fmt.Print(yellowColor)
|
||||
self.out.Insert(failure)
|
||||
} else if report.Skipped {
|
||||
fmt.Print(yellowColor)
|
||||
self.out.Insert(skip)
|
||||
} else {
|
||||
fmt.Print(greenColor)
|
||||
self.out.Insert(success)
|
||||
}
|
||||
fmt.Print(resetColor)
|
||||
}
|
||||
|
||||
func (self *story) Exit() {
|
||||
self.out.Dedent()
|
||||
self.currentKey = self.currentKey[:len(self.currentKey)-1]
|
||||
}
|
||||
|
||||
func (self *story) EndStory() {
|
||||
self.titlesById = make(map[string]string)
|
||||
self.out.Println("\n")
|
||||
}
|
||||
|
||||
func (self *story) Write(content []byte) (written int, err error) {
|
||||
return len(content), nil // no-op
|
||||
}
|
||||
|
||||
func NewStoryReporter(out *Printer) *story {
|
||||
self := new(story)
|
||||
self.out = out
|
||||
self.titlesById = make(map[string]string)
|
||||
return self
|
||||
}
|
Loading…
Reference in new issue