Commit d573ee22 by Torkel Ödegaard

Fixed missing godep dependency for unit tests

parent 18ff1569
{
"ImportPath": "github.com/torkelo/grafana-pro",
"GoVersion": "go1.3",
"Packages": [
"./pkg/..."
],
"Deps": [
{
"ImportPath": "github.com/Unknwon/com",
......@@ -34,6 +37,10 @@
"Rev": "5c23849a66f4593e68909bb6c1fa30651b5b0541"
},
{
"ImportPath": "github.com/jtolds/gls",
"Rev": "f1ac7f4f24f50328e6bc838ca4437d1612a0243c"
},
{
"ImportPath": "github.com/macaron-contrib/session",
"Rev": "65b8817c40cb5bdce08673a15fd2a648c2ba0e16"
},
......@@ -42,6 +49,11 @@
"Rev": "d10e2c8f62100097910367dee90a9bd89d426a44"
},
{
"ImportPath": "github.com/smartystreets/goconvey/convey",
"Comment": "1.5.0-356-gfbc0a1c",
"Rev": "fbc0a1c888f9f96263f9a559d1769905245f1123"
},
{
"ImportPath": "golang.org/x/net/context",
"Rev": "972f0c5fbe4ae29e666c3f78c3ed42ae7a448b0a"
},
......
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.
gls
===
Goroutine local storage
### 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
// 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()
}
package gls
import (
"fmt"
"sync"
"testing"
)
func TestContexts(t *testing.T) {
mgr1 := NewContextManager()
mgr2 := NewContextManager()
CheckVal := func(mgr *ContextManager, key, exp_val string) {
val, ok := mgr.GetValue(key)
if len(exp_val) == 0 {
if ok {
t.Fatalf("expected no value for key %s, got %s", key, val)
}
return
}
if !ok {
t.Fatalf("expected value %s for key %s, got no value",
exp_val, key)
}
if exp_val != val {
t.Fatalf("expected value %s for key %s, got %s", exp_val, key,
val)
}
}
Check := func(exp_m1v1, exp_m1v2, exp_m2v1, exp_m2v2 string) {
CheckVal(mgr1, "key1", exp_m1v1)
CheckVal(mgr1, "key2", exp_m1v2)
CheckVal(mgr2, "key1", exp_m2v1)
CheckVal(mgr2, "key2", exp_m2v2)
}
Check("", "", "", "")
mgr2.SetValues(Values{"key1": "val1c"}, func() {
Check("", "", "val1c", "")
mgr1.SetValues(Values{"key1": "val1a"}, func() {
Check("val1a", "", "val1c", "")
mgr1.SetValues(Values{"key2": "val1b"}, func() {
Check("val1a", "val1b", "val1c", "")
var wg sync.WaitGroup
wg.Add(2)
go func() {
defer wg.Done()
Check("", "", "", "")
}()
Go(func() {
defer wg.Done()
Check("val1a", "val1b", "val1c", "")
})
wg.Wait()
})
})
})
}
func ExampleContextManager_SetValues() {
var (
mgr = NewContextManager()
request_id_key = GenSym()
)
MyLog := func() {
if request_id, ok := mgr.GetValue(request_id_key); ok {
fmt.Println("My request id is:", request_id)
} else {
fmt.Println("No request id found")
}
}
mgr.SetValues(Values{request_id_key: "12345"}, func() {
MyLog()
})
MyLog()
// Output: My request id is: 12345
// No request id found
}
func ExampleGo() {
var (
mgr = NewContextManager()
request_id_key = GenSym()
)
MyLog := func() {
if request_id, ok := mgr.GetValue(request_id_key); ok {
fmt.Println("My request id is:", request_id)
} else {
fmt.Println("No request id found")
}
}
mgr.SetValues(Values{request_id_key: "12345"}, func() {
var wg sync.WaitGroup
wg.Add(1)
go func() {
defer wg.Done()
MyLog()
}()
wg.Wait()
wg.Add(1)
Go(func() {
defer wg.Done()
MyLog()
})
wg.Wait()
})
// Output: No request id found
// My request id is: 12345
}
func BenchmarkGetValue(b *testing.B) {
mgr := NewContextManager()
mgr.SetValues(Values{"test_key": "test_val"}, func() {
b.ResetTimer()
for i := 0; i < b.N; i++ {
val, ok := mgr.GetValue("test_key")
if !ok || val != "test_val" {
b.FailNow()
}
}
})
}
func BenchmarkSetValues(b *testing.B) {
mgr := NewContextManager()
for i := 0; i < b.N/2; i++ {
mgr.SetValues(Values{"test_key": "test_val"}, func() {
mgr.SetValues(Values{"test_key2": "test_val2"}, func() {})
})
}
}
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()}
}
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)
}
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
}
package convey
import "github.com/smartystreets/goconvey/convey/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
ShouldBeIn = assertions.ShouldBeIn
ShouldNotBeIn = assertions.ShouldNotBeIn
ShouldBeEmpty = assertions.ShouldBeEmpty
ShouldNotBeEmpty = assertions.ShouldNotBeEmpty
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
)
#ignore
-timeout=1s
-coverpkg=github.com/smartystreets/goconvey/convey/assertions,github.com/smartystreets/goconvey/convey/assertions/oglematchers
\ No newline at end of file
package assertions
import (
"fmt"
"reflect"
"github.com/smartystreets/goconvey/convey/assertions/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])
}
// 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)
}
package assertions
import (
"fmt"
"testing"
"time"
)
func TestShouldContain(t *testing.T) {
fail(t, so([]int{}, ShouldContain), "This assertion requires exactly 1 comparison values (you provided 0).")
fail(t, so([]int{}, ShouldContain, 1, 2, 3), "This assertion requires exactly 1 comparison values (you provided 3).")
fail(t, so(Thing1{}, ShouldContain, 1), "You must provide a valid container (was assertions.Thing1)!")
fail(t, so(nil, ShouldContain, 1), "You must provide a valid container (was <nil>)!")
fail(t, so([]int{1}, ShouldContain, 2), "Expected the container ([]int) to contain: '2' (but it didn't)!")
pass(t, so([]int{1}, ShouldContain, 1))
pass(t, so([]int{1, 2, 3}, ShouldContain, 2))
}
func TestShouldNotContain(t *testing.T) {
fail(t, so([]int{}, ShouldNotContain), "This assertion requires exactly 1 comparison values (you provided 0).")
fail(t, so([]int{}, ShouldNotContain, 1, 2, 3), "This assertion requires exactly 1 comparison values (you provided 3).")
fail(t, so(Thing1{}, ShouldNotContain, 1), "You must provide a valid container (was assertions.Thing1)!")
fail(t, so(nil, ShouldNotContain, 1), "You must provide a valid container (was <nil>)!")
fail(t, so([]int{1}, ShouldNotContain, 1), "Expected the container ([]int) NOT to contain: '1' (but it did)!")
fail(t, so([]int{1, 2, 3}, ShouldNotContain, 2), "Expected the container ([]int) NOT to contain: '2' (but it did)!")
pass(t, so([]int{1}, ShouldNotContain, 2))
}
func TestShouldBeIn(t *testing.T) {
fail(t, so(4, ShouldBeIn), shouldHaveProvidedCollectionMembers)
container := []int{1, 2, 3, 4}
pass(t, so(4, ShouldBeIn, container))
pass(t, so(4, ShouldBeIn, 1, 2, 3, 4))
fail(t, so(4, ShouldBeIn, 1, 2, 3), "Expected '4' to be in the container ([]interface {}, but it wasn't)!")
fail(t, so(4, ShouldBeIn, []int{1, 2, 3}), "Expected '4' to be in the container ([]int, but it wasn't)!")
}
func TestShouldNotBeIn(t *testing.T) {
fail(t, so(4, ShouldNotBeIn), shouldHaveProvidedCollectionMembers)
container := []int{1, 2, 3, 4}
pass(t, so(42, ShouldNotBeIn, container))
pass(t, so(42, ShouldNotBeIn, 1, 2, 3, 4))
fail(t, so(2, ShouldNotBeIn, 1, 2, 3), "Expected '2' NOT to be in the container ([]interface {}, but it was)!")
fail(t, so(2, ShouldNotBeIn, []int{1, 2, 3}), "Expected '2' NOT to be in the container ([]int, but it was)!")
}
func TestShouldBeEmpty(t *testing.T) {
fail(t, so(1, ShouldBeEmpty, 2, 3), "This assertion requires exactly 0 comparison values (you provided 2).")
pass(t, so([]int{}, ShouldBeEmpty)) // empty slice
pass(t, so([]interface{}{}, ShouldBeEmpty)) // empty slice
pass(t, so(map[string]int{}, ShouldBeEmpty)) // empty map
pass(t, so("", ShouldBeEmpty)) // empty string
pass(t, so(&[]int{}, ShouldBeEmpty)) // pointer to empty slice
pass(t, so(&[0]int{}, ShouldBeEmpty)) // pointer to empty array
pass(t, so(nil, ShouldBeEmpty)) // nil
pass(t, so(make(chan string), ShouldBeEmpty)) // empty channel
fail(t, so([]int{1}, ShouldBeEmpty), "Expected [1] to be empty (but it wasn't)!") // non-empty slice
fail(t, so([]interface{}{1}, ShouldBeEmpty), "Expected [1] to be empty (but it wasn't)!") // non-empty slice
fail(t, so(map[string]int{"hi": 0}, ShouldBeEmpty), "Expected map[hi:0] to be empty (but it wasn't)!") // non-empty map
fail(t, so("hi", ShouldBeEmpty), "Expected hi to be empty (but it wasn't)!") // non-empty string
fail(t, so(&[]int{1}, ShouldBeEmpty), "Expected &[1] to be empty (but it wasn't)!") // pointer to non-empty slice
fail(t, so(&[1]int{1}, ShouldBeEmpty), "Expected &[1] to be empty (but it wasn't)!") // pointer to non-empty array
c := make(chan int, 1) // non-empty channel
go func() { c <- 1 }()
time.Sleep(time.Millisecond)
fail(t, so(c, ShouldBeEmpty), fmt.Sprintf("Expected %+v to be empty (but it wasn't)!", c))
}
func TestShouldNotBeEmpty(t *testing.T) {
fail(t, so(1, ShouldNotBeEmpty, 2, 3), "This assertion requires exactly 0 comparison values (you provided 2).")
fail(t, so([]int{}, ShouldNotBeEmpty), "Expected [] to NOT be empty (but it was)!") // empty slice
fail(t, so([]interface{}{}, ShouldNotBeEmpty), "Expected [] to NOT be empty (but it was)!") // empty slice
fail(t, so(map[string]int{}, ShouldNotBeEmpty), "Expected map[] to NOT be empty (but it was)!") // empty map
fail(t, so("", ShouldNotBeEmpty), "Expected to NOT be empty (but it was)!") // empty string
fail(t, so(&[]int{}, ShouldNotBeEmpty), "Expected &[] to NOT be empty (but it was)!") // pointer to empty slice
fail(t, so(&[0]int{}, ShouldNotBeEmpty), "Expected &[] to NOT be empty (but it was)!") // pointer to empty array
fail(t, so(nil, ShouldNotBeEmpty), "Expected <nil> to NOT be empty (but it was)!") // nil
c := make(chan int, 0) // non-empty channel
fail(t, so(c, ShouldNotBeEmpty), fmt.Sprintf("Expected %+v to NOT be empty (but it was)!", c)) // empty channel
pass(t, so([]int{1}, ShouldNotBeEmpty)) // non-empty slice
pass(t, so([]interface{}{1}, ShouldNotBeEmpty)) // non-empty slice
pass(t, so(map[string]int{"hi": 0}, ShouldNotBeEmpty)) // non-empty map
pass(t, so("hi", ShouldNotBeEmpty)) // non-empty string
pass(t, so(&[]int{1}, ShouldNotBeEmpty)) // pointer to non-empty slice
pass(t, so(&[1]int{1}, ShouldNotBeEmpty)) // pointer to non-empty array
c = make(chan int, 1)
go func() { c <- 1 }()
time.Sleep(time.Millisecond)
pass(t, so(c, ShouldNotBeEmpty))
}
// Package assertions contains the implementations for all assertions which
// are referenced in the convey package for use with the So(...) method.
package assertions
// This function is not used by the goconvey library. It's actually a convenience method
// for running assertions on arbitrary arguments outside of any testing context, like for
// application logging. It allows you to perform assertion-like behavior (and get nicely
// formatted messages detailing discrepancies) but without the probram 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) {
serializer = noop
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
////////////////////////////////////////////////////////////////////////////
package assertions
import (
"errors"
"fmt"
"math"
"reflect"
"strings"
"github.com/smartystreets/goconvey/convey/assertions/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 {
message = serializer.serialize(expected, actual, fmt.Sprintf(shouldHaveBeenEqual, expected, actual))
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], actual, expected[0], 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
}
package assertions
import "fmt"
const (
success = ""
needExactValues = "This assertion requires exactly %d comparison values (you provided %d)."
)
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 shouldHaveProvidedCollectionMembers
}
return success
}
package assertions
var (
serializer Serializer = newSerializer()
noop Serializer = new(noopSerializer)
)
package assertions
const ( // equality
shouldHaveBeenEqual = "Expected: '%v'\nActual: '%v'\n(Should be equal)"
shouldNotHaveBeenEqual = "Expected '%v'\nto NOT equal '%v'\n(but it did)!"
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'\nActual: '%#v'\n(Type mismatch: '%T' vs '%T')!"
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)!"
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)!"
shouldHaveProvidedCollectionMembers = "This assertion requires at least 1 comparison value (you provided 0)."
shouldHaveBeenEmpty = "Expected %+v to be empty (but it wasn't)!"
shouldNotHaveBeenEmpty = "Expected %+v to NOT be empty (but it was)!"
)
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)!"
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 didn't)!"
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!)"
)
`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/goconvey/convey/assertions/oglematchers
Documentation
-------------
See [here][reference] for documentation hosted on GoPkgDoc. Alternatively, you
can install the package and then use `go doc`:
go doc github.com/smartystreets/goconvey/convey/assertions/oglematchers
[reference]: http://gopkgdoc.appspot.com/pkg/github.com/smartystreets/goconvey/convey/assertions/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/goconvey/convey/assertions/ogletest
[oglemock]: http://github.com/smartystreets/goconvey/convey/assertions/oglemock
// 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
}
// 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_test
import (
"errors"
. "github.com/smartystreets/goconvey/convey/assertions/oglematchers"
. "github.com/smartystreets/goconvey/convey/assertions/ogletest"
)
////////////////////////////////////////////////////////////////////////
// Helpers
////////////////////////////////////////////////////////////////////////
type allOfFakeMatcher struct {
desc string
err error
}
func (m *allOfFakeMatcher) Matches(c interface{}) error {
return m.err
}
func (m *allOfFakeMatcher) Description() string {
return m.desc
}
type AllOfTest struct {
}
func init() { RegisterTestSuite(&AllOfTest{}) }
////////////////////////////////////////////////////////////////////////
// Tests
////////////////////////////////////////////////////////////////////////
func (t *AllOfTest) DescriptionWithEmptySet() {
m := AllOf()
ExpectEq("is anything", m.Description())
}
func (t *AllOfTest) DescriptionWithOneMatcher() {
m := AllOf(&allOfFakeMatcher{"taco", errors.New("")})
ExpectEq("taco", m.Description())
}
func (t *AllOfTest) DescriptionWithMultipleMatchers() {
m := AllOf(
&allOfFakeMatcher{"taco", errors.New("")},
&allOfFakeMatcher{"burrito", errors.New("")},
&allOfFakeMatcher{"enchilada", errors.New("")})
ExpectEq("taco, and burrito, and enchilada", m.Description())
}
func (t *AllOfTest) EmptySet() {
m := AllOf()
err := m.Matches(17)
ExpectEq(nil, err)
}
func (t *AllOfTest) OneMatcherReturnsFatalErrorAndSomeOthersFail() {
m := AllOf(
&allOfFakeMatcher{"", errors.New("")},
&allOfFakeMatcher{"", NewFatalError("taco")},
&allOfFakeMatcher{"", errors.New("")},
&allOfFakeMatcher{"", nil})
err := m.Matches(17)
ExpectTrue(isFatal(err))
ExpectThat(err, Error(Equals("taco")))
}
func (t *AllOfTest) OneMatcherReturnsNonFatalAndOthersSayTrue() {
m := AllOf(
&allOfFakeMatcher{"", nil},
&allOfFakeMatcher{"", errors.New("taco")},
&allOfFakeMatcher{"", nil})
err := m.Matches(17)
ExpectFalse(isFatal(err))
ExpectThat(err, Error(Equals("taco")))
}
func (t *AllOfTest) AllMatchersSayTrue() {
m := AllOf(
&allOfFakeMatcher{"", nil},
&allOfFakeMatcher{"", nil},
&allOfFakeMatcher{"", nil})
err := m.Matches(17)
ExpectEq(nil, err)
}
// 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
}
// 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 {
if reflect.TypeOf(v).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
}
// 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_test
import (
"errors"
. "github.com/smartystreets/goconvey/convey/assertions/oglematchers"
. "github.com/smartystreets/goconvey/convey/assertions/ogletest"
)
////////////////////////////////////////////////////////////////////////
// Helpers
////////////////////////////////////////////////////////////////////////
type fakeAnyOfMatcher struct {
desc string
err error
}
func (m *fakeAnyOfMatcher) Matches(c interface{}) error {
return m.err
}
func (m *fakeAnyOfMatcher) Description() string {
return m.desc
}
type AnyOfTest struct {
}
func init() { RegisterTestSuite(&AnyOfTest{}) }
////////////////////////////////////////////////////////////////////////
// Tests
////////////////////////////////////////////////////////////////////////
func (t *AnyOfTest) EmptySet() {
matcher := AnyOf()
err := matcher.Matches(0)
ExpectThat(err, Error(Equals("")))
}
func (t *AnyOfTest) OneTrue() {
matcher := AnyOf(
&fakeAnyOfMatcher{"", NewFatalError("foo")},
17,
&fakeAnyOfMatcher{"", errors.New("foo")},
&fakeAnyOfMatcher{"", nil},
&fakeAnyOfMatcher{"", errors.New("foo")},
)
err := matcher.Matches(0)
ExpectEq(nil, err)
}
func (t *AnyOfTest) OneEqual() {
matcher := AnyOf(
&fakeAnyOfMatcher{"", NewFatalError("foo")},
&fakeAnyOfMatcher{"", errors.New("foo")},
13,
"taco",
19,
&fakeAnyOfMatcher{"", errors.New("foo")},
)
err := matcher.Matches("taco")
ExpectEq(nil, err)
}
func (t *AnyOfTest) OneFatal() {
matcher := AnyOf(
&fakeAnyOfMatcher{"", errors.New("foo")},
17,
&fakeAnyOfMatcher{"", NewFatalError("taco")},
&fakeAnyOfMatcher{"", errors.New("foo")},
)
err := matcher.Matches(0)
ExpectThat(err, Error(Equals("taco")))
}
func (t *AnyOfTest) AllFalseAndNotEqual() {
matcher := AnyOf(
&fakeAnyOfMatcher{"", errors.New("foo")},
17,
&fakeAnyOfMatcher{"", errors.New("foo")},
19,
)
err := matcher.Matches(0)
ExpectThat(err, Error(Equals("")))
}
func (t *AnyOfTest) DescriptionForEmptySet() {
matcher := AnyOf()
ExpectEq("or()", matcher.Description())
}
func (t *AnyOfTest) DescriptionForNonEmptySet() {
matcher := AnyOf(
&fakeAnyOfMatcher{"taco", nil},
"burrito",
&fakeAnyOfMatcher{"enchilada", nil},
)
ExpectEq("or(taco, burrito, enchilada)", matcher.Description())
}
// 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_test
import (
. "github.com/smartystreets/goconvey/convey/assertions/oglematchers"
. "github.com/smartystreets/goconvey/convey/assertions/ogletest"
)
////////////////////////////////////////////////////////////////////////
// Helpers
////////////////////////////////////////////////////////////////////////
type AnyTest struct {
}
func init() { RegisterTestSuite(&AnyTest{}) }
////////////////////////////////////////////////////////////////////////
// Tests
////////////////////////////////////////////////////////////////////////
func (t *AnyTest) Description() {
m := Any()
ExpectEq("is anything", m.Description())
}
func (t *AnyTest) Matches() {
var err error
m := Any()
err = m.Matches(nil)
ExpectEq(nil, err)
err = m.Matches(17)
ExpectEq(nil, err)
err = m.Matches("taco")
ExpectEq(nil, err)
}
// 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("")
}
// 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_test
import (
. "github.com/smartystreets/goconvey/convey/assertions/oglematchers"
. "github.com/smartystreets/goconvey/convey/assertions/ogletest"
)
////////////////////////////////////////////////////////////////////////
// Helpers
////////////////////////////////////////////////////////////////////////
type ContainsTest struct{}
func init() { RegisterTestSuite(&ContainsTest{}) }
////////////////////////////////////////////////////////////////////////
// Tests
////////////////////////////////////////////////////////////////////////
func (t *ContainsTest) WrongTypeCandidates() {
m := Contains("")
ExpectEq("contains: ", m.Description())
var err error
// Nil candidate
err = m.Matches(nil)
ExpectTrue(isFatal(err))
ExpectThat(err, Error(HasSubstr("array")))
ExpectThat(err, Error(HasSubstr("slice")))
// String candidate
err = m.Matches("")
ExpectTrue(isFatal(err))
ExpectThat(err, Error(HasSubstr("array")))
ExpectThat(err, Error(HasSubstr("slice")))
// Map candidate
err = m.Matches(make(map[string]string))
ExpectTrue(isFatal(err))
ExpectThat(err, Error(HasSubstr("array")))
ExpectThat(err, Error(HasSubstr("slice")))
}
func (t *ContainsTest) NilArgument() {
m := Contains(nil)
ExpectEq("contains: is nil", m.Description())
var c interface{}
var err error
// Empty array of pointers
c = [...]*int{}
err = m.Matches(c)
ExpectThat(err, Error(Equals("")))
// Empty slice of pointers
c = []*int{}
err = m.Matches(c)
ExpectThat(err, Error(Equals("")))
// Non-empty array of integers
c = [...]int{17, 0, 19}
err = m.Matches(c)
ExpectThat(err, Error(Equals("")))
// Non-empty slice of integers
c = []int{17, 0, 19}
err = m.Matches(c)
ExpectThat(err, Error(Equals("")))
// Non-matching array of pointers
c = [...]*int{new(int), new(int)}
err = m.Matches(c)
ExpectThat(err, Error(Equals("")))
// Non-matching slice of pointers
c = []*int{new(int), new(int)}
err = m.Matches(c)
ExpectThat(err, Error(Equals("")))
// Matching array of pointers
c = [...]*int{new(int), nil, new(int)}
err = m.Matches(c)
ExpectEq(nil, err)
// Matching slice of pointers
c = []*int{new(int), nil, new(int)}
err = m.Matches(c)
ExpectEq(nil, err)
// Non-matching slice of pointers from matching array
someArray := [...]*int{new(int), nil, new(int)}
c = someArray[0:1]
err = m.Matches(c)
ExpectThat(err, Error(Equals("")))
}
func (t *ContainsTest) StringArgument() {
m := Contains("taco")
ExpectEq("contains: taco", m.Description())
var c interface{}
var err error
// Non-matching array of strings
c = [...]string{"burrito", "enchilada"}
err = m.Matches(c)
ExpectThat(err, Error(Equals("")))
// Non-matching slice of strings
c = []string{"burrito", "enchilada"}
err = m.Matches(c)
ExpectThat(err, Error(Equals("")))
// Matching array of strings
c = [...]string{"burrito", "taco", "enchilada"}
err = m.Matches(c)
ExpectEq(nil, err)
// Matching slice of strings
c = []string{"burrito", "taco", "enchilada"}
err = m.Matches(c)
ExpectEq(nil, err)
// Non-matching slice of strings from matching array
someArray := [...]string{"burrito", "taco", "enchilada"}
c = someArray[0:1]
err = m.Matches(c)
ExpectThat(err, Error(Equals("")))
}
func (t *ContainsTest) IntegerArgument() {
m := Contains(int(17))
ExpectEq("contains: 17", m.Description())
var c interface{}
var err error
// Non-matching array of integers
c = [...]int{13, 19}
err = m.Matches(c)
ExpectThat(err, Error(Equals("")))
// Non-matching slice of integers
c = []int{13, 19}
err = m.Matches(c)
ExpectThat(err, Error(Equals("")))
// Matching array of integers
c = [...]int{13, 17, 19}
err = m.Matches(c)
ExpectEq(nil, err)
// Matching slice of integers
c = []int{13, 17, 19}
err = m.Matches(c)
ExpectEq(nil, err)
// Non-matching slice of integers from matching array
someArray := [...]int{13, 17, 19}
c = someArray[0:1]
err = m.Matches(c)
ExpectThat(err, Error(Equals("")))
// Non-matching array of floats
c = [...]float32{13, 17.5, 19}
err = m.Matches(c)
ExpectThat(err, Error(Equals("")))
// Non-matching slice of floats
c = []float32{13, 17.5, 19}
err = m.Matches(c)
ExpectThat(err, Error(Equals("")))
// Matching array of floats
c = [...]float32{13, 17, 19}
err = m.Matches(c)
ExpectEq(nil, err)
// Matching slice of floats
c = []float32{13, 17, 19}
err = m.Matches(c)
ExpectEq(nil, err)
}
func (t *ContainsTest) MatcherArgument() {
m := Contains(HasSubstr("ac"))
ExpectEq("contains: has substring \"ac\"", m.Description())
var c interface{}
var err error
// Non-matching array of strings
c = [...]string{"burrito", "enchilada"}
err = m.Matches(c)
ExpectThat(err, Error(Equals("")))
// Non-matching slice of strings
c = []string{"burrito", "enchilada"}
err = m.Matches(c)
ExpectThat(err, Error(Equals("")))
// Matching array of strings
c = [...]string{"burrito", "taco", "enchilada"}
err = m.Matches(c)
ExpectEq(nil, err)
// Matching slice of strings
c = []string{"burrito", "taco", "enchilada"}
err = m.Matches(c)
ExpectEq(nil, err)
// Non-matching slice of strings from matching array
someArray := [...]string{"burrito", "taco", "enchilada"}
c = someArray[0:1]
err = m.Matches(c)
ExpectThat(err, Error(Equals("")))
}
// 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("")
}
// 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_test
import (
"bytes"
"testing"
. "github.com/smartystreets/goconvey/convey/assertions/oglematchers"
. "github.com/smartystreets/goconvey/convey/assertions/ogletest"
)
////////////////////////////////////////////////////////////////////////
// Helpers
////////////////////////////////////////////////////////////////////////
type DeepEqualsTest struct{}
func init() { RegisterTestSuite(&DeepEqualsTest{}) }
////////////////////////////////////////////////////////////////////////
// Tests
////////////////////////////////////////////////////////////////////////
func (t *DeepEqualsTest) WrongTypeCandidateWithScalarValue() {
var x int = 17
m := DeepEquals(x)
var err error
// Nil candidate.
err = m.Matches(nil)
AssertNe(nil, err)
ExpectTrue(isFatal(err))
ExpectThat(err, Error(HasSubstr("type")))
ExpectThat(err, Error(HasSubstr("<nil>")))
// Int alias candidate.
type intAlias int
err = m.Matches(intAlias(x))
AssertNe(nil, err)
ExpectTrue(isFatal(err))
ExpectThat(err, Error(HasSubstr("type")))
ExpectThat(err, Error(HasSubstr("intAlias")))
// String candidate.
err = m.Matches("taco")
AssertNe(nil, err)
ExpectTrue(isFatal(err))
ExpectThat(err, Error(HasSubstr("type")))
ExpectThat(err, Error(HasSubstr("string")))
// Byte slice candidate.
err = m.Matches([]byte{})
AssertNe(nil, err)
ExpectTrue(isFatal(err))
ExpectThat(err, Error(HasSubstr("type")))
ExpectThat(err, Error(HasSubstr("[]uint8")))
// Other slice candidate.
err = m.Matches([]uint16{})
AssertNe(nil, err)
ExpectTrue(isFatal(err))
ExpectThat(err, Error(HasSubstr("type")))
ExpectThat(err, Error(HasSubstr("[]uint16")))
// Unsigned int candidate.
err = m.Matches(uint(17))
AssertNe(nil, err)
ExpectTrue(isFatal(err))
ExpectThat(err, Error(HasSubstr("type")))
ExpectThat(err, Error(HasSubstr("uint")))
}
func (t *DeepEqualsTest) WrongTypeCandidateWithByteSliceValue() {
x := []byte{}
m := DeepEquals(x)
var err error
// Nil candidate.
err = m.Matches(nil)
AssertNe(nil, err)
ExpectTrue(isFatal(err))
ExpectThat(err, Error(HasSubstr("type")))
ExpectThat(err, Error(HasSubstr("<nil>")))
// String candidate.
err = m.Matches("taco")
AssertNe(nil, err)
ExpectTrue(isFatal(err))
ExpectThat(err, Error(HasSubstr("type")))
ExpectThat(err, Error(HasSubstr("string")))
// Slice candidate with wrong value type.
err = m.Matches([]uint16{})
AssertNe(nil, err)
ExpectTrue(isFatal(err))
ExpectThat(err, Error(HasSubstr("type")))
ExpectThat(err, Error(HasSubstr("[]uint16")))
}
func (t *DeepEqualsTest) WrongTypeCandidateWithOtherSliceValue() {
x := []uint16{}
m := DeepEquals(x)
var err error
// Nil candidate.
err = m.Matches(nil)
AssertNe(nil, err)
ExpectTrue(isFatal(err))
ExpectThat(err, Error(HasSubstr("type")))
ExpectThat(err, Error(HasSubstr("<nil>")))
// String candidate.
err = m.Matches("taco")
AssertNe(nil, err)
ExpectTrue(isFatal(err))
ExpectThat(err, Error(HasSubstr("type")))
ExpectThat(err, Error(HasSubstr("string")))
// Byte slice candidate with wrong value type.
err = m.Matches([]byte{})
AssertNe(nil, err)
ExpectTrue(isFatal(err))
ExpectThat(err, Error(HasSubstr("type")))
ExpectThat(err, Error(HasSubstr("[]uint8")))
// Other slice candidate with wrong value type.
err = m.Matches([]uint32{})
AssertNe(nil, err)
ExpectTrue(isFatal(err))
ExpectThat(err, Error(HasSubstr("type")))
ExpectThat(err, Error(HasSubstr("[]uint32")))
}
func (t *DeepEqualsTest) WrongTypeCandidateWithNilLiteralValue() {
m := DeepEquals(nil)
var err error
// String candidate.
err = m.Matches("taco")
AssertNe(nil, err)
ExpectTrue(isFatal(err))
ExpectThat(err, Error(HasSubstr("type")))
ExpectThat(err, Error(HasSubstr("string")))
// Nil byte slice candidate.
err = m.Matches([]byte(nil))
AssertNe(nil, err)
ExpectTrue(isFatal(err))
ExpectThat(err, Error(HasSubstr("type")))
ExpectThat(err, Error(HasSubstr("[]uint8")))
// Nil other slice candidate.
err = m.Matches([]uint16(nil))
AssertNe(nil, err)
ExpectTrue(isFatal(err))
ExpectThat(err, Error(HasSubstr("type")))
ExpectThat(err, Error(HasSubstr("[]uint16")))
}
func (t *DeepEqualsTest) NilLiteralValue() {
m := DeepEquals(nil)
ExpectEq("deep equals: <nil>", m.Description())
var c interface{}
var err error
// Nil literal candidate.
c = nil
err = m.Matches(c)
ExpectEq(nil, err)
}
func (t *DeepEqualsTest) IntValue() {
m := DeepEquals(int(17))
ExpectEq("deep equals: 17", m.Description())
var c interface{}
var err error
// Matching int.
c = int(17)
err = m.Matches(c)
ExpectEq(nil, err)
// Non-matching int.
c = int(18)
err = m.Matches(c)
ExpectThat(err, Error(Equals("")))
}
func (t *DeepEqualsTest) ByteSliceValue() {
x := []byte{17, 19}
m := DeepEquals(x)
ExpectEq("deep equals: [17 19]", m.Description())
var c []byte
var err error
// Matching.
c = make([]byte, len(x))
AssertEq(len(x), copy(c, x))
err = m.Matches(c)
ExpectEq(nil, err)
// Nil slice.
c = []byte(nil)
err = m.Matches(c)
ExpectThat(err, Error(Equals("which is nil")))
// Prefix.
AssertGt(len(x), 1)
c = make([]byte, len(x)-1)
AssertEq(len(x)-1, copy(c, x))
err = m.Matches(c)
ExpectThat(err, Error(Equals("")))
// Suffix.
c = make([]byte, len(x)+1)
AssertEq(len(x), copy(c, x))
err = m.Matches(c)
ExpectThat(err, Error(Equals("")))
}
func (t *DeepEqualsTest) OtherSliceValue() {
x := []uint16{17, 19}
m := DeepEquals(x)
ExpectEq("deep equals: [17 19]", m.Description())
var c []uint16
var err error
// Matching.
c = make([]uint16, len(x))
AssertEq(len(x), copy(c, x))
err = m.Matches(c)
ExpectEq(nil, err)
// Nil slice.
c = []uint16(nil)
err = m.Matches(c)
ExpectThat(err, Error(Equals("which is nil")))
// Prefix.
AssertGt(len(x), 1)
c = make([]uint16, len(x)-1)
AssertEq(len(x)-1, copy(c, x))
err = m.Matches(c)
ExpectThat(err, Error(Equals("")))
// Suffix.
c = make([]uint16, len(x)+1)
AssertEq(len(x), copy(c, x))
err = m.Matches(c)
ExpectThat(err, Error(Equals("")))
}
func (t *DeepEqualsTest) NilByteSliceValue() {
x := []byte(nil)
m := DeepEquals(x)
ExpectEq("deep equals: <nil slice>", m.Description())
var c []byte
var err error
// Nil slice.
c = []byte(nil)
err = m.Matches(c)
ExpectEq(nil, err)
// Non-nil slice.
c = []byte{}
err = m.Matches(c)
ExpectThat(err, Error(Equals("")))
}
func (t *DeepEqualsTest) NilOtherSliceValue() {
x := []uint16(nil)
m := DeepEquals(x)
ExpectEq("deep equals: <nil slice>", m.Description())
var c []uint16
var err error
// Nil slice.
c = []uint16(nil)
err = m.Matches(c)
ExpectEq(nil, err)
// Non-nil slice.
c = []uint16{}
err = m.Matches(c)
ExpectThat(err, Error(Equals("")))
}
////////////////////////////////////////////////////////////////////////
// Benchmarks
////////////////////////////////////////////////////////////////////////
func benchmarkWithSize(b *testing.B, size int) {
b.StopTimer()
buf := bytes.Repeat([]byte{0x01}, size)
bufCopy := make([]byte, size)
copy(bufCopy, buf)
matcher := DeepEquals(buf)
b.StartTimer()
for i := 0; i < b.N; i++ {
matcher.Matches(bufCopy)
}
b.SetBytes(int64(size))
}
func BenchmarkShortByteSlice(b *testing.B) {
benchmarkWithSize(b, 256)
}
func BenchmarkLongByteSlice(b *testing.B) {
benchmarkWithSize(b, 1<<24)
}
// 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
}
// 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_test
import (
. "github.com/smartystreets/goconvey/convey/assertions/oglematchers"
. "github.com/smartystreets/goconvey/convey/assertions/ogletest"
)
////////////////////////////////////////////////////////////////////////
// Helpers
////////////////////////////////////////////////////////////////////////
type ElementsAreTest struct {
}
func init() { RegisterTestSuite(&ElementsAreTest{}) }
////////////////////////////////////////////////////////////////////////
// Tests
////////////////////////////////////////////////////////////////////////
func (t *ElementsAreTest) EmptySet() {
m := ElementsAre()
ExpectEq("elements are: []", m.Description())
var c []interface{}
var err error
// No candidates.
c = []interface{}{}
err = m.Matches(c)
ExpectEq(nil, err)
// One candidate.
c = []interface{}{17}
err = m.Matches(c)
ExpectThat(err, Error(HasSubstr("length 1")))
}
func (t *ElementsAreTest) OneMatcher() {
m := ElementsAre(LessThan(17))
ExpectEq("elements are: [less than 17]", m.Description())
var c []interface{}
var err error
// No candidates.
c = []interface{}{}
err = m.Matches(c)
ExpectThat(err, Error(HasSubstr("length 0")))
// Matching candidate.
c = []interface{}{16}
err = m.Matches(c)
ExpectEq(nil, err)
// Non-matching candidate.
c = []interface{}{19}
err = m.Matches(c)
ExpectNe(nil, err)
// Two candidates.
c = []interface{}{17, 19}
err = m.Matches(c)
ExpectThat(err, Error(HasSubstr("length 2")))
}
func (t *ElementsAreTest) OneValue() {
m := ElementsAre(17)
ExpectEq("elements are: [17]", m.Description())
var c []interface{}
var err error
// No candidates.
c = []interface{}{}
err = m.Matches(c)
ExpectThat(err, Error(HasSubstr("length 0")))
// Matching int.
c = []interface{}{int(17)}
err = m.Matches(c)
ExpectEq(nil, err)
// Matching float.
c = []interface{}{float32(17)}
err = m.Matches(c)
ExpectEq(nil, err)
// Non-matching candidate.
c = []interface{}{19}
err = m.Matches(c)
ExpectNe(nil, err)
// Two candidates.
c = []interface{}{17, 19}
err = m.Matches(c)
ExpectThat(err, Error(HasSubstr("length 2")))
}
func (t *ElementsAreTest) MultipleElements() {
m := ElementsAre("taco", LessThan(17))
ExpectEq("elements are: [taco, less than 17]", m.Description())
var c []interface{}
var err error
// One candidate.
c = []interface{}{17}
err = m.Matches(c)
ExpectThat(err, Error(HasSubstr("length 1")))
// Both matching.
c = []interface{}{"taco", 16}
err = m.Matches(c)
ExpectEq(nil, err)
// First non-matching.
c = []interface{}{"burrito", 16}
err = m.Matches(c)
ExpectThat(err, Error(Equals("whose element 0 doesn't match")))
// Second non-matching.
c = []interface{}{"taco", 17}
err = m.Matches(c)
ExpectThat(err, Error(Equals("whose element 1 doesn't match")))
// Three candidates.
c = []interface{}{"taco", 17, 19}
err = m.Matches(c)
ExpectThat(err, Error(HasSubstr("length 3")))
}
func (t *ElementsAreTest) ArrayCandidates() {
m := ElementsAre("taco", LessThan(17))
var err error
// One candidate.
err = m.Matches([1]interface{}{"taco"})
ExpectThat(err, Error(HasSubstr("length 1")))
// Both matching.
err = m.Matches([2]interface{}{"taco", 16})
ExpectEq(nil, err)
// First non-matching.
err = m.Matches([2]interface{}{"burrito", 16})
ExpectThat(err, Error(Equals("whose element 0 doesn't match")))
}
func (t *ElementsAreTest) WrongTypeCandidate() {
m := ElementsAre("taco")
var err error
// String candidate.
err = m.Matches("taco")
ExpectTrue(isFatal(err))
ExpectThat(err, Error(HasSubstr("array")))
ExpectThat(err, Error(HasSubstr("slice")))
// Map candidate.
err = m.Matches(map[string]string{})
ExpectTrue(isFatal(err))
ExpectThat(err, Error(HasSubstr("array")))
ExpectThat(err, Error(HasSubstr("slice")))
// Nil candidate.
err = m.Matches(nil)
ExpectTrue(isFatal(err))
ExpectThat(err, Error(HasSubstr("array")))
ExpectThat(err, Error(HasSubstr("slice")))
}
func (t *ElementsAreTest) PropagatesFatality() {
m := ElementsAre(LessThan(17))
ExpectEq("elements are: [less than 17]", m.Description())
var c []interface{}
var err error
// Non-fatal error.
c = []interface{}{19}
err = m.Matches(c)
AssertNe(nil, err)
ExpectFalse(isFatal(err))
// Fatal error.
c = []interface{}{"taco"}
err = m.Matches(c)
AssertNe(nil, err)
ExpectTrue(isFatal(err))
}
This source diff could not be displayed because it is too large. You can view the blob instead.
// 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())
}
// 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_test
import (
"errors"
. "github.com/smartystreets/goconvey/convey/assertions/oglematchers"
. "github.com/smartystreets/goconvey/convey/assertions/ogletest"
)
////////////////////////////////////////////////////////////////////////
// Helpers
////////////////////////////////////////////////////////////////////////
type ErrorTest struct {
matcherCalled bool
suppliedCandidate interface{}
wrappedError error
matcher Matcher
}
func init() { RegisterTestSuite(&ErrorTest{}) }
func (t *ErrorTest) SetUp(i *TestInfo) {
wrapped := &fakeMatcher{
func(c interface{}) error {
t.matcherCalled = true
t.suppliedCandidate = c
return t.wrappedError
},
"is foo",
}
t.matcher = Error(wrapped)
}
func isFatal(err error) bool {
_, isFatal := err.(*FatalError)
return isFatal
}
////////////////////////////////////////////////////////////////////////
// Tests
////////////////////////////////////////////////////////////////////////
func (t *ErrorTest) Description() {
ExpectThat(t.matcher.Description(), Equals("error is foo"))
}
func (t *ErrorTest) CandidateIsNil() {
err := t.matcher.Matches(nil)
ExpectThat(t.matcherCalled, Equals(false))
ExpectThat(err.Error(), Equals("which is not an error"))
ExpectTrue(isFatal(err))
}
func (t *ErrorTest) CandidateIsString() {
err := t.matcher.Matches("taco")
ExpectThat(t.matcherCalled, Equals(false))
ExpectThat(err.Error(), Equals("which is not an error"))
ExpectTrue(isFatal(err))
}
func (t *ErrorTest) CallsWrappedMatcher() {
candidate := errors.New("taco")
t.matcher.Matches(candidate)
ExpectThat(t.matcherCalled, Equals(true))
ExpectThat(t.suppliedCandidate, Equals("taco"))
}
func (t *ErrorTest) ReturnsWrappedMatcherResult() {
t.wrappedError = errors.New("burrito")
err := t.matcher.Matches(errors.New(""))
ExpectThat(err, Equals(t.wrappedError))
}
// 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)
}
// 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)
}
// 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 &hasSubstrMatcher{s}
}
type hasSubstrMatcher struct {
needle string
}
func (m *hasSubstrMatcher) Description() string {
return fmt.Sprintf("has substring \"%s\"", m.needle)
}
func (m *hasSubstrMatcher) Matches(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, m.needle) {
return nil
}
return errors.New("")
}
// 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_test
import (
. "github.com/smartystreets/goconvey/convey/assertions/oglematchers"
. "github.com/smartystreets/goconvey/convey/assertions/ogletest"
)
////////////////////////////////////////////////////////////////////////
// Helpers
////////////////////////////////////////////////////////////////////////
type HasSubstrTest struct {
}
func init() { RegisterTestSuite(&HasSubstrTest{}) }
////////////////////////////////////////////////////////////////////////
// Tests
////////////////////////////////////////////////////////////////////////
func (t *HasSubstrTest) Description() {
matcher := HasSubstr("taco")
ExpectThat(matcher.Description(), Equals("has substring \"taco\""))
}
func (t *HasSubstrTest) CandidateIsNil() {
matcher := HasSubstr("")
err := matcher.Matches(nil)
ExpectThat(err, Error(Equals("which is not a string")))
ExpectTrue(isFatal(err))
}
func (t *HasSubstrTest) CandidateIsInteger() {
matcher := HasSubstr("")
err := matcher.Matches(17)
ExpectThat(err, Error(Equals("which is not a string")))
ExpectTrue(isFatal(err))
}
func (t *HasSubstrTest) CandidateIsByteSlice() {
matcher := HasSubstr("")
err := matcher.Matches([]byte{17})
ExpectThat(err, Error(Equals("which is not a string")))
ExpectTrue(isFatal(err))
}
func (t *HasSubstrTest) CandidateDoesntHaveSubstring() {
matcher := HasSubstr("taco")
err := matcher.Matches("tac")
ExpectThat(err, Error(Equals("")))
ExpectFalse(isFatal(err))
}
func (t *HasSubstrTest) CandidateEqualsArg() {
matcher := HasSubstr("taco")
err := matcher.Matches("taco")
ExpectThat(err, Equals(nil))
}
func (t *HasSubstrTest) CandidateHasProperSubstring() {
matcher := HasSubstr("taco")
err := matcher.Matches("burritos and tacos")
ExpectThat(err, Equals(nil))
}
func (t *HasSubstrTest) EmptyStringIsAlwaysSubString() {
matcher := HasSubstr("")
err := matcher.Matches("asdf")
ExpectThat(err, Equals(nil))
}
// 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("")
}
// 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)
}
// 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))
}
// 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/goconvey/convey/assertions/ogletest and
// github.com/smartystreets/goconvey/convey/assertions/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".
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
}
// 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 supplide 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
}
// 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_test
import (
. "github.com/smartystreets/goconvey/convey/assertions/oglematchers"
. "github.com/smartystreets/goconvey/convey/assertions/ogletest"
)
////////////////////////////////////////////////////////////////////////
// Helpers
////////////////////////////////////////////////////////////////////////
type MatchesRegexpTest struct {
}
func init() { RegisterTestSuite(&MatchesRegexpTest{}) }
////////////////////////////////////////////////////////////////////////
// Tests
////////////////////////////////////////////////////////////////////////
func (t *MatchesRegexpTest) Description() {
m := MatchesRegexp("foo.*bar")
ExpectEq("matches regexp \"foo.*bar\"", m.Description())
}
func (t *MatchesRegexpTest) InvalidRegexp() {
ExpectThat(
func() { MatchesRegexp("(foo") },
Panics(HasSubstr("missing closing )")))
}
func (t *MatchesRegexpTest) CandidateIsNil() {
m := MatchesRegexp("")
err := m.Matches(nil)
ExpectThat(err, Error(Equals("which is not a string or []byte")))
ExpectTrue(isFatal(err))
}
func (t *MatchesRegexpTest) CandidateIsInteger() {
m := MatchesRegexp("")
err := m.Matches(17)
ExpectThat(err, Error(Equals("which is not a string or []byte")))
ExpectTrue(isFatal(err))
}
func (t *MatchesRegexpTest) NonMatchingCandidates() {
m := MatchesRegexp("fo[op]\\s+x")
var err error
err = m.Matches("fon x")
ExpectThat(err, Error(Equals("")))
ExpectFalse(isFatal(err))
err = m.Matches("fopx")
ExpectThat(err, Error(Equals("")))
ExpectFalse(isFatal(err))
err = m.Matches("fop ")
ExpectThat(err, Error(Equals("")))
ExpectFalse(isFatal(err))
}
func (t *MatchesRegexpTest) MatchingCandidates() {
m := MatchesRegexp("fo[op]\\s+x")
var err error
err = m.Matches("foo x")
ExpectEq(nil, err)
err = m.Matches("fop x")
ExpectEq(nil, err)
err = m.Matches("blah blah foo x blah blah")
ExpectEq(nil, err)
}
// 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 &notMatcher{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())
}
// 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_test
import (
"errors"
"testing"
. "github.com/smartystreets/goconvey/convey/assertions/oglematchers"
. "github.com/smartystreets/goconvey/convey/assertions/ogletest"
)
////////////////////////////////////////////////////////////////////////
// Helpers
////////////////////////////////////////////////////////////////////////
type fakeMatcher struct {
matchFunc func(interface{}) error
description string
}
func (m *fakeMatcher) Matches(c interface{}) error {
return m.matchFunc(c)
}
func (m *fakeMatcher) Description() string {
return m.description
}
type NotTest struct {
}
func init() { RegisterTestSuite(&NotTest{}) }
func TestOgletest(t *testing.T) { RunTests(t) }
////////////////////////////////////////////////////////////////////////
// Tests
////////////////////////////////////////////////////////////////////////
func (t *NotTest) CallsWrapped() {
var suppliedCandidate interface{}
matchFunc := func(c interface{}) error {
suppliedCandidate = c
return nil
}
wrapped := &fakeMatcher{matchFunc, ""}
matcher := Not(wrapped)
matcher.Matches(17)
ExpectThat(suppliedCandidate, Equals(17))
}
func (t *NotTest) WrappedReturnsTrue() {
matchFunc := func(c interface{}) error {
return nil
}
wrapped := &fakeMatcher{matchFunc, ""}
matcher := Not(wrapped)
err := matcher.Matches(0)
ExpectThat(err, Error(Equals("")))
}
func (t *NotTest) WrappedReturnsNonFatalError() {
matchFunc := func(c interface{}) error {
return errors.New("taco")
}
wrapped := &fakeMatcher{matchFunc, ""}
matcher := Not(wrapped)
err := matcher.Matches(0)
ExpectEq(nil, err)
}
func (t *NotTest) WrappedReturnsFatalError() {
matchFunc := func(c interface{}) error {
return NewFatalError("taco")
}
wrapped := &fakeMatcher{matchFunc, ""}
matcher := Not(wrapped)
err := matcher.Matches(0)
ExpectThat(err, Error(Equals("taco")))
}
func (t *NotTest) Description() {
wrapped := &fakeMatcher{nil, "taco"}
matcher := Not(wrapped)
ExpectEq("not(taco)", matcher.Description())
}
// 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
}
// 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_test
import (
"errors"
. "github.com/smartystreets/goconvey/convey/assertions/oglematchers"
. "github.com/smartystreets/goconvey/convey/assertions/ogletest"
)
////////////////////////////////////////////////////////////////////////
// Helpers
////////////////////////////////////////////////////////////////////////
type PanicsTest struct {
matcherCalled bool
suppliedCandidate interface{}
wrappedError error
matcher Matcher
}
func init() { RegisterTestSuite(&PanicsTest{}) }
func (t *PanicsTest) SetUp(i *TestInfo) {
wrapped := &fakeMatcher{
func(c interface{}) error {
t.matcherCalled = true
t.suppliedCandidate = c
return t.wrappedError
},
"foo",
}
t.matcher = Panics(wrapped)
}
////////////////////////////////////////////////////////////////////////
// Tests
////////////////////////////////////////////////////////////////////////
func (t *PanicsTest) Description() {
ExpectThat(t.matcher.Description(), Equals("panics with: foo"))
}
func (t *PanicsTest) CandidateIsNil() {
err := t.matcher.Matches(nil)
ExpectThat(err, Error(Equals("which is not a zero-arg function")))
ExpectTrue(isFatal(err))
}
func (t *PanicsTest) CandidateIsString() {
err := t.matcher.Matches("taco")
ExpectThat(err, Error(Equals("which is not a zero-arg function")))
ExpectTrue(isFatal(err))
}
func (t *PanicsTest) CandidateTakesArgs() {
err := t.matcher.Matches(func(i int) string { return "" })
ExpectThat(err, Error(Equals("which is not a zero-arg function")))
ExpectTrue(isFatal(err))
}
func (t *PanicsTest) CallsFunction() {
callCount := 0
t.matcher.Matches(func() string {
callCount++
return ""
})
ExpectThat(callCount, Equals(1))
}
func (t *PanicsTest) FunctionDoesntPanic() {
err := t.matcher.Matches(func() {})
ExpectThat(err, Error(Equals("which didn't panic")))
ExpectFalse(isFatal(err))
}
func (t *PanicsTest) CallsWrappedMatcher() {
expectedErr := 17
t.wrappedError = errors.New("")
t.matcher.Matches(func() { panic(expectedErr) })
ExpectThat(t.suppliedCandidate, Equals(expectedErr))
}
func (t *PanicsTest) WrappedReturnsTrue() {
err := t.matcher.Matches(func() { panic("") })
ExpectEq(nil, err)
}
func (t *PanicsTest) WrappedReturnsFatalErrorWithoutText() {
t.wrappedError = NewFatalError("")
err := t.matcher.Matches(func() { panic(17) })
ExpectThat(err, Error(Equals("which panicked with: 17")))
ExpectFalse(isFatal(err))
}
func (t *PanicsTest) WrappedReturnsFatalErrorWithText() {
t.wrappedError = NewFatalError("which blah")
err := t.matcher.Matches(func() { panic(17) })
ExpectThat(err, Error(Equals("which panicked with: 17, which blah")))
ExpectFalse(isFatal(err))
}
func (t *PanicsTest) WrappedReturnsNonFatalErrorWithoutText() {
t.wrappedError = errors.New("")
err := t.matcher.Matches(func() { panic(17) })
ExpectThat(err, Error(Equals("which panicked with: 17")))
ExpectFalse(isFatal(err))
}
func (t *PanicsTest) WrappedReturnsNonFatalErrorWithText() {
t.wrappedError = errors.New("which blah")
err := t.matcher.Matches(func() { panic(17) })
ExpectThat(err, Error(Equals("which panicked with: 17, which blah")))
ExpectFalse(isFatal(err))
}
// 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())
}
// 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_test
import (
"errors"
"testing"
. "github.com/smartystreets/goconvey/convey/assertions/oglematchers"
. "github.com/smartystreets/goconvey/convey/assertions/ogletest"
)
////////////////////////////////////////////////////////////////////////
// Helpers
////////////////////////////////////////////////////////////////////////
type PointeeTest struct{}
func init() { RegisterTestSuite(&PointeeTest{}) }
func TestPointee(t *testing.T) { RunTests(t) }
////////////////////////////////////////////////////////////////////////
// Tests
////////////////////////////////////////////////////////////////////////
func (t *PointeeTest) Description() {
wrapped := &fakeMatcher{nil, "taco"}
matcher := Pointee(wrapped)
ExpectEq("pointee(taco)", matcher.Description())
}
func (t *PointeeTest) CandidateIsNotAPointer() {
matcher := Pointee(HasSubstr(""))
err := matcher.Matches([]byte{})
ExpectThat(err, Error(Equals("which is not a pointer")))
ExpectTrue(isFatal(err))
}
func (t *PointeeTest) CandidateIsANilLiteral() {
matcher := Pointee(HasSubstr(""))
err := matcher.Matches(nil)
ExpectThat(err, Error(Equals("which is not a pointer")))
ExpectTrue(isFatal(err))
}
func (t *PointeeTest) CandidateIsANilPointer() {
matcher := Pointee(HasSubstr(""))
err := matcher.Matches((*int)(nil))
ExpectThat(err, Error(Equals("")))
ExpectTrue(isFatal(err))
}
func (t *PointeeTest) CallsWrapped() {
var suppliedCandidate interface{}
matchFunc := func(c interface{}) error {
suppliedCandidate = c
return nil
}
wrapped := &fakeMatcher{matchFunc, ""}
matcher := Pointee(wrapped)
someSlice := []byte{}
matcher.Matches(&someSlice)
ExpectThat(suppliedCandidate, IdenticalTo(someSlice))
}
func (t *PointeeTest) WrappedReturnsOkay() {
matchFunc := func(c interface{}) error {
return nil
}
wrapped := &fakeMatcher{matchFunc, ""}
matcher := Pointee(wrapped)
err := matcher.Matches(new(int))
ExpectEq(nil, err)
}
func (t *PointeeTest) WrappedReturnsNonFatalNonEmptyError() {
matchFunc := func(c interface{}) error {
return errors.New("taco")
}
wrapped := &fakeMatcher{matchFunc, ""}
matcher := Pointee(wrapped)
i := 17
err := matcher.Matches(&i)
ExpectFalse(isFatal(err))
ExpectThat(err, Error(Equals("taco")))
}
func (t *PointeeTest) WrappedReturnsNonFatalEmptyError() {
matchFunc := func(c interface{}) error {
return errors.New("")
}
wrapped := &fakeMatcher{matchFunc, ""}
matcher := Pointee(wrapped)
i := 17
err := matcher.Matches(&i)
ExpectFalse(isFatal(err))
ExpectThat(err, Error(HasSubstr("whose pointee")))
ExpectThat(err, Error(HasSubstr("17")))
}
func (t *PointeeTest) WrappedReturnsFatalNonEmptyError() {
matchFunc := func(c interface{}) error {
return NewFatalError("taco")
}
wrapped := &fakeMatcher{matchFunc, ""}
matcher := Pointee(wrapped)
i := 17
err := matcher.Matches(&i)
ExpectTrue(isFatal(err))
ExpectThat(err, Error(Equals("taco")))
}
func (t *PointeeTest) WrappedReturnsFatalEmptyError() {
matchFunc := func(c interface{}) error {
return NewFatalError("")
}
wrapped := &fakeMatcher{matchFunc, ""}
matcher := Pointee(wrapped)
i := 17
err := matcher.Matches(&i)
ExpectTrue(isFatal(err))
ExpectThat(err, Error(HasSubstr("whose pointee")))
ExpectThat(err, Error(HasSubstr("17")))
}
// 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)
}
`oglemock` is a mocking framework for the Go programming language with the
following features:
* An extensive and extensible set of matchers for expressing call
expectations (provided by the [oglematchers][] package).
* Clean, readable output that tells you exactly what you need to know.
* Style and semantics similar to [Google Mock][googlemock] and
[Google JS Test][google-js-test].
* Seamless integration with the [ogletest][] unit testing framework.
It can be integrated into any testing framework (including Go's `testing`
package), but out of the box support is built in to [ogletest][] and that is the
easiest place to use it.
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 `oglemock` and its dependencies, and to
keep them up to date:
go get -u github.com/smartystreets/goconvey/convey/assertions/oglemock
go get -u github.com/smartystreets/goconvey/convey/assertions/oglemock/createmock
Those commands will install the `oglemock` package itself, along with the
`createmock` tool that is used to auto-generate mock types.
Generating and using mock types
-------------------------------
Automatically generating a mock implementation of an interface is easy. If you
want to mock interfaces `Bar` and `Baz` from package `foo`, simply run the
following:
createmock foo Bar Baz
That will print source code that can be saved to a file and used in your tests.
For example, to create a `mock_io` package containing mock implementations of
`io.Reader` and `io.Writer`:
mkdir mock_io
createmock io Reader Writer > mock_io/mock_io.go
The new package will be named `mock_io`, and contain types called `MockReader`
and `MockWriter`, which implement `io.Reader` and `io.Writer` respectively.
For each generated mock type, there is a corresponding function for creating an
instance of that type given a `Controller` object (see below). For example, to
create a mock reader:
```go
someController := [...] // See next section.
someReader := mock_io.NewMockReader(someController, "Mock file reader")
```
The snippet above creates a mock `io.Reader` that reports failures to
`someController`. The reader can subsequently have expectations set up and be
passed to your code under test that uses an `io.Reader`.
Getting ahold of a controller
-----------------------------
[oglemock.Controller][controller-ref] is used to create mock objects, and to set
up and verify expectations for them. You can create one by calling
`NewController` with an `ErrorReporter`, which is the basic type used to
interface between `oglemock` and the testing framework within which it is being
used.
If you are using [ogletest][] you don't need to worry about any of this, since
the `TestInfo` struct provided to your test's `SetUp` function already contains
a working `Controller` that you can use to create mock object, and you can use
the built-in `ExpectCall` function for setting expectations. (See the
[ogletest documentation][ogletest-docs] for more info.) Otherwise, you will need
to implement the simple [ErrorReporter interface][reporter-ref] for your test
environment.
Documentation
-------------
For thorough documentation, including information on how to set up expectations,
see [here][oglemock-docs].
[controller-ref]: http://gopkgdoc.appspot.com/pkg/github.com/smartystreets/goconvey/convey/assertions/oglemock#Controller
[reporter-ref]: http://gopkgdoc.appspot.com/pkg/github.com/smartystreets/goconvey/convey/assertions/oglemock#ErrorReporter
[golang-install]: http://golang.org/doc/install.html
[google-js-test]: http://code.google.com/p/google-js-test/
[googlemock]: http://code.google.com/p/googlemock/
[oglematchers]: https://github.com/smartystreets/goconvey/convey/assertions/oglematchers
[oglemock-docs]: http://gopkgdoc.appspot.com/pkg/github.com/smartystreets/goconvey/convey/assertions/oglemock
[ogletest]: https://github.com/smartystreets/goconvey/convey/assertions/oglematchers
[ogletest-docs]: http://gopkgdoc.appspot.com/pkg/github.com/smartystreets/goconvey/convey/assertions/ogletest
// 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 oglemock
import (
"reflect"
)
// Action represents an action to be taken in response to a call to a mock
// method.
type Action interface {
// Set the signature of the function with which this action is being used.
// This must be called before Invoke is called.
SetSignature(signature reflect.Type) error
// Invoke runs the specified action, given the arguments to the mock method.
// It returns zero or more values that may be treated as the return values of
// the method. If the action doesn't return any values, it may return the nil
// slice.
//
// You must call SetSignature before calling Invoke.
Invoke(methodArgs []interface{}) []interface{}
}
// 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.
// createmock is used to generate source code for mock versions of interfaces
// from installed packages.
package main
import (
"errors"
"flag"
"fmt"
"go/build"
"io/ioutil"
"log"
"os"
"os/exec"
"path"
"regexp"
"text/template"
// Ensure that the generate package, which is used by the generated code, is
// installed by goinstall.
_ "github.com/smartystreets/goconvey/convey/assertions/oglemock/generate"
)
// A template for generated code that is used to print the result.
const tmplStr = `
{{$inputPkg := .InputPkg}}
{{$outputPkg := .OutputPkg}}
package main
import (
{{range $identifier, $import := .Imports}}
{{$identifier}} "{{$import}}"
{{end}}
)
func getTypeForPtr(ptr interface{}) reflect.Type {
return reflect.TypeOf(ptr).Elem()
}
func main() {
// Reduce noise in logging output.
log.SetFlags(0)
interfaces := []reflect.Type{
{{range $typeName := .TypeNames}}
getTypeForPtr((*{{base $inputPkg}}.{{$typeName}})(nil)),
{{end}}
}
err := generate.GenerateMockSource(os.Stdout, "{{$outputPkg}}", interfaces)
if err != nil {
log.Fatalf("Error generating mock source: %v", err)
}
}
`
// A map from import identifier to package to use that identifier for,
// containing elements for each import needed by the generated code.
type importMap map[string]string
type tmplArg struct {
InputPkg string
OutputPkg string
// Imports needed by the generated code.
Imports importMap
// Types to be mocked, relative to their package's name.
TypeNames []string
}
var unknownPackageRegexp = regexp.MustCompile(
`tool\.go:\d+:\d+: cannot find package "([^"]+)"`)
var undefinedInterfaceRegexp = regexp.MustCompile(`tool\.go:\d+: undefined: [\pL_0-9]+\.([\pL_0-9]+)`)
// Does the 'go build' output indicate that a package wasn't found? If so,
// return the name of the package.
func findUnknownPackage(output []byte) *string {
if match := unknownPackageRegexp.FindSubmatch(output); match != nil {
res := string(match[1])
return &res
}
return nil
}
// Does the 'go build' output indicate that an interface wasn't found? If so,
// return the name of the interface.
func findUndefinedInterface(output []byte) *string {
if match := undefinedInterfaceRegexp.FindSubmatch(output); match != nil {
res := string(match[1])
return &res
}
return nil
}
// Split out from main so that deferred calls are executed even in the event of
// an error.
func run() error {
// Reduce noise in logging output.
log.SetFlags(0)
// Check the command-line arguments.
flag.Parse()
cmdLineArgs := flag.Args()
if len(cmdLineArgs) < 2 {
return errors.New("Usage: createmock [package] [interface ...]")
}
// Create a temporary directory inside of $GOPATH to hold generated code.
buildPkg, err := build.Import("github.com/smartystreets/goconvey/convey/assertions/oglemock", "", build.FindOnly)
if err != nil {
return errors.New(fmt.Sprintf("Couldn't find oglemock in $GOPATH: %v", err))
}
tmpDir, err := ioutil.TempDir(buildPkg.SrcRoot, "tmp-createmock-")
if err != nil {
return errors.New(fmt.Sprintf("Creating temp dir: %v", err))
}
defer os.RemoveAll(tmpDir)
// Create a file to hold generated code.
codeFile, err := os.Create(path.Join(tmpDir, "tool.go"))
if err != nil {
return errors.New(fmt.Sprintf("Couldn't create a file to hold code: %v", err))
}
// Create an appropriate path for the built binary.
binaryPath := path.Join(tmpDir, "tool")
// Create an appropriate template argument.
var arg tmplArg
arg.InputPkg = cmdLineArgs[0]
arg.OutputPkg = "mock_" + path.Base(arg.InputPkg)
arg.TypeNames = cmdLineArgs[1:]
arg.Imports = make(importMap)
arg.Imports[path.Base(arg.InputPkg)] = arg.InputPkg
arg.Imports["generate"] = "github.com/smartystreets/goconvey/convey/assertions/oglemock/generate"
arg.Imports["log"] = "log"
arg.Imports["os"] = "os"
arg.Imports["reflect"] = "reflect"
// Execute the template to generate code that will itself generate the mock
// code. Write the code to the temp file.
tmpl := template.Must(
template.New("code").Funcs(
template.FuncMap{
"base": path.Base,
}).Parse(tmplStr))
if err := tmpl.Execute(codeFile, arg); err != nil {
return errors.New(fmt.Sprintf("Error executing template: %v", err))
}
codeFile.Close()
// Attempt to build the code.
cmd := exec.Command("go", "build", "-o", binaryPath)
cmd.Dir = tmpDir
buildOutput, err := cmd.CombinedOutput()
if err != nil {
// Did the compilation fail due to the user-specified package not being found?
if pkg := findUnknownPackage(buildOutput); pkg != nil && *pkg == arg.InputPkg {
return errors.New(fmt.Sprintf("Unknown package: %s", *pkg))
}
// Did the compilation fail due to an unknown interface?
if in := findUndefinedInterface(buildOutput); in != nil {
return errors.New(fmt.Sprintf("Unknown interface: %s", *in))
}
// Otherwise return a generic error.
return errors.New(fmt.Sprintf(
"%s\n\nError building generated code:\n\n"+
" %v\n\nPlease report this oglemock bug.",
buildOutput,
err))
}
// Run the binary.
cmd = exec.Command(binaryPath)
binaryOutput, err := cmd.CombinedOutput()
if err != nil {
return errors.New(fmt.Sprintf(
"%s\n\nError running generated code:\n\n"+
" %v\n\n Please report this oglemock bug.",
binaryOutput,
err))
}
// Copy its output.
_, err = os.Stdout.Write(binaryOutput)
if err != nil {
return errors.New(fmt.Sprintf("Error copying binary output: %v", err))
}
return nil
}
func main() {
if err := run(); err != nil {
fmt.Println(err.Error())
os.Exit(1)
}
}
// 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 oglemock
// ErrorReporter is an interface that wraps methods for reporting errors that
// should cause test failures.
type ErrorReporter interface {
// Report that some failure (e.g. an unsatisfied expectation) occurred. If
// known, fileName and lineNumber should contain information about where it
// occurred. The test may continue if the test framework supports it.
ReportError(fileName string, lineNumber int, err error)
// Like ReportError, but the test should be halted immediately. It is assumed
// that this method does not return.
ReportFatalError(fileName string, lineNumber int, err error)
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment