diff --git a/assert.go b/assert.go index 7d5f90e..791b600 100644 --- a/assert.go +++ b/assert.go @@ -2,28 +2,32 @@ package assert import ( "reflect" - "runtime" - "testing" + + "github.com/google/go-cmp/cmp" ) // Assert is a helper for tests -type Assert func(bool, ...interface{}) +type Assert struct { + t T +} + +func (a Assert) f(ok bool, msg []interface{}, format string, extra ...interface{}) { + if !ok { + if format != `` { + msg = prepMsg(msg, format, extra...) + } + if msg == nil { + msg = []interface{}{`Assertion failed`} + } + msg = append(append([]interface{}{shell(1) + shell(97) + shell(41) + `FAIL!` + shell(0) + shell(1)}, msg...), shell(0), "\n") + a.t.Helper() + a.t.Error(msg...) + } +} // New returns a new Assert -func New(t *testing.T) Assert { - a := func(ok bool, msg ...interface{}) { - if !ok { - if msg == nil { - msg = []interface{}{`Assertion failed`} - } - msg = append(append([]interface{}{shell(1) + shell(97) + shell(41) + `FAIL!` + shell(0) + shell(1)}, msg...), shell(0), "\n") - t.Helper() - t.Error(msg...) - } - } - - f := runtime.FuncForPC(reflect.ValueOf(a).Pointer()) - ts[f] = t +func New(t T) Assert { + a := Assert{t} return a } @@ -34,16 +38,14 @@ func New(t *testing.T) Assert { // True asserts the given value is true func (a Assert) True(actual bool, msg ...interface{}) { - t(a).Helper() - msg = prepMsg(msg, `Should be true, but it isn't`) - a(actual, msg...) + a.t.Helper() + a.f(actual, msg, `Should be true, but it isn't`) } -// False sserts the given value is false +// False asserts the given value is false func (a Assert) False(actual bool, msg ...interface{}) { - t(a).Helper() - msg = prepMsg(msg, `Should be false, but it isn't`) - a(!actual, msg...) + a.t.Helper() + a.f(!actual, msg, `Should be false, but it isn't`) } ///// Nil ///// @@ -64,87 +66,76 @@ func isNil(val interface{}) bool { // Nil asserts the given value is nil func (a Assert) Nil(actual interface{}, msg ...interface{}) { - t(a).Helper() - msg = prepMsg(msg, `Should be nil, but got %#v`, actual) - - a(isNil(actual), msg...) + a.t.Helper() + a.f(isNil(actual), msg, `Should be nil, but got %#v`, actual) } -// NotNil sserts the given value is not nil +// NotNil asserts the given value is not nil func (a Assert) NotNil(actual interface{}, msg ...interface{}) { - t(a).Helper() - msg = prepMsg(msg, `Should not be nil, but it is`) - - a(!isNil(actual), msg...) + a.t.Helper() + a.f(!isNil(actual), msg, `Should not be nil, but it is`) } ///// Errors ///// // Error asserts the given error is not nil func (a Assert) Error(actual error, msg ...interface{}) { - t(a).Helper() - msg = prepMsg(msg, `Expected an error, but got nil`) - a(actual != nil, msg...) + a.t.Helper() + a.f(actual != nil, msg, `Expected an error, but got nil`) } -// NoError asserts the given error is not nil +// NoError asserts the given error is nil func (a Assert) NoError(actual error, msg ...interface{}) { - t(a).Helper() - msg = prepMsg(msg, `Expected no error, but got %#v`, actual) - a(actual == nil, msg...) + a.t.Helper() + a.f(actual == nil, msg, `Expected no error, but got %#v`, actual) } ///// Comparisons ///// // Eq asserts the given values match func (a Assert) Eq(expected, actual interface{}, msg ...interface{}) { - t(a).Helper() + a.t.Helper() if reflect.TypeOf(expected) != reflect.TypeOf(actual) { - msg = prepMsg(msg, `Expected %T(%#v), but got %T(%#v)`, expected, expected, actual, actual) - a(false, msg...) + a.f(false, msg, `Expected %T(%#v), but got %T(%#v)`, expected, expected, actual, actual) return } - msg = prepMsg(msg, `Expected %#v, but got %#v`, expected, actual) - a(expected == actual, msg...) + a.f(expected == actual, msg, `Expected %#v, but got %#v`, expected, actual) } // Ne asserts the given values don't match func (a Assert) Ne(expected, actual interface{}, msg ...interface{}) { - t(a).Helper() - msg = prepMsg(msg, `Should not be %#v, but it is`, expected) - a(expected != actual, msg...) + a.t.Helper() + a.f(expected != actual, msg, `Should not be %#v, but it is`, expected) } ///// Lists ///// // Contains asserts the expected value is in the given list func (a Assert) Contains(expected, list interface{}, msg ...interface{}) { - t(a).Helper() + a.t.Helper() rlist := reflect.ValueOf(list) - a(rlist.Kind() == reflect.Slice || rlist.Kind() == reflect.Array, `Can only call assert.Contains on a slice or array`) + a.f(rlist.Kind() == reflect.Slice || rlist.Kind() == reflect.Array, nil, `Can only call assert.Contains on a slice or array`) for i := 0; i < rlist.Len(); i++ { if rlist.Index(i).Interface() == expected { return } } - msg = prepMsg(msg, `Expected %#v to be in %#v, but it isn't`, expected, list) - a(false, msg...) + a.f(false, msg, `Expected %#v to be in %#v, but it isn't`, expected, list) } // SameElements asserts the values have the same elements. It ignores the order of the elements func (a Assert) SameElements(expected, actual interface{}, msg ...interface{}) { - t(a).Helper() + a.t.Helper() rexpected, ractual := reflect.ValueOf(expected), reflect.ValueOf(actual) - a(rexpected.Kind() == reflect.Slice || rexpected.Kind() == reflect.Array, `Can only call assert.SameElements on a slice or array`) - a(ractual.Kind() == reflect.Slice || ractual.Kind() == reflect.Array, `Can only call assert.SameElements on a slice or array`) + a.f(rexpected.Kind() == reflect.Slice || rexpected.Kind() == reflect.Array, nil, `Can only call assert.SameElements on a slice or array`) + a.f(ractual.Kind() == reflect.Slice || ractual.Kind() == reflect.Array, nil, `Can only call assert.SameElements on a slice or array`) - msg = prepMsg(msg, `Expected elements of %#v to match %#v, but they don't`, expected, actual) if rexpected.Len() != ractual.Len() { - a(false, msg...) + a.f(false, msg, `Expected elements of %#v to match %#v, but they don't`, expected, actual) return } @@ -162,5 +153,24 @@ func (a Assert) SameElements(expected, actual interface{}, msg ...interface{}) { return } - a(false, msg...) + a.f(false, msg, ``) +} + +// Cmp assert wrapper for go-cmp +func (a Assert) Cmp(expected, actual interface{}, opts ...cmp.Option) { + a.t.Helper() + diff := cmp.Diff(expected, actual, opts...) + if diff == `` { + return + } + + a.f(false, nil, "\n"+diff) +} + +// NCmp assert wrapper for go-cmp but fails when !Equal +func (a Assert) NCmp(expected, actual interface{}, opts ...cmp.Option) { + a.t.Helper() + + ok := cmp.Equal(expected, actual, opts...) + a.f(!ok, nil, `Should not be %#v, but it is`, expected) } diff --git a/assert_test.go b/assert_test.go new file mode 100644 index 0000000..0b31a12 --- /dev/null +++ b/assert_test.go @@ -0,0 +1,194 @@ +package assert + +import ( + "testing" + "time" +) + +// ----------------------------------------- +// Slices +// ----------------------------------------- + +func TestSameElementsSlices(t *testing.T) { + assert := New(t) + ft, fa := newFakeT() + + a, b := []int{1, 2}, []int{1, 2} + fa.SameElements(a, a) + assert.False(ft.GotError()) + + fa.SameElements(a, b) + assert.False(ft.GotError()) + + b = []int{2, 1} + fa.SameElements(b, a) // SameElements ignores order + assert.False(ft.GotError()) + + b = []int{1, 2, 3} + fa.SameElements(b, a) + assert.True(ft.GotError()) + + b = []int{2, 3} + fa.SameElements(b, a) + assert.True(ft.GotError()) +} + +func TestCmpSlices(t *testing.T) { + assert := New(t) + ft, fa := newFakeT() + + a, b := []int{1, 2}, []int{1, 2} + fa.Cmp(a, a) + assert.False(ft.GotError()) + + fa.Cmp(a, b) + assert.False(ft.GotError()) + + b = []int{2, 1} + fa.Cmp(a, b) // Cmp does not accept different order + assert.True(ft.GotError()) + + b = []int{1, 2, 3} + fa.Cmp(a, b) + assert.True(ft.GotError()) + + b = []int{2, 3} + fa.Cmp(a, b) + assert.True(ft.GotError()) +} + +// ----------------------------------------- +// Maps +// ----------------------------------------- + +func TestCmpMaps(t *testing.T) { + assert := New(t) + ft, fa := newFakeT() + + a, b := map[int]int{1: 2, 3: 4}, map[int]int{1: 2, 3: 4} + fa.Cmp(a, a) + assert.False(ft.GotError()) + + fa.Cmp(a, b) + assert.False(ft.GotError()) + + b = map[int]int{1: 2, 3: 5} + fa.Cmp(a, b) + assert.True(ft.GotError()) + + b = map[int]int{1: 2, 3: 4, 5: 6} + fa.Cmp(a, b) + assert.True(ft.GotError()) + + b = map[int]int{1: 2} + fa.Cmp(a, b) + assert.True(ft.GotError()) +} + +// ----------------------------------------- +// Pointers +// ----------------------------------------- + +func TestEqPointers(t *testing.T) { + assert := New(t) + ft, fa := newFakeT() + + var a, b int + fa.Eq(&a, &a) + assert.False(ft.GotError()) + + fa.Eq(&a, &b) + assert.True(ft.GotError()) + + fa.Eq(&b, &a) + assert.True(ft.GotError()) +} + +func TestCmpPointers(t *testing.T) { + assert := New(t) + ft, fa := newFakeT() + + type B struct { + Value int + } + type A struct { + B *B + } + + fa.Cmp(A{B: &B{1}}, A{&B{1}}) + assert.False(ft.GotError()) + + fa.Cmp(A{B: &B{1}}, A{&B{2}}) + assert.True(ft.GotError()) + + fa.Cmp(A{B: &B{1}}, A{nil}) + assert.True(ft.GotError()) +} + +// ----------------------------------------- +// Timezones +// ----------------------------------------- + +func mustLoadLocation(zone string) *time.Location { + loc, err := time.LoadLocation(zone) + if err != nil { + panic(err) + } + + return loc +} + +var locAmsterdam = mustLoadLocation(`Europe/Amsterdam`) +var locTokyo = mustLoadLocation(`Asia/Tokyo`) + +func TestEqTimezones(t *testing.T) { + assert := New(t) + ft, fa := newFakeT() + + ti := time.Now() + ti, ti2 := ti.In(locAmsterdam), ti.In(locTokyo) + + fa.Eq(ti, ti2) + assert.True(ft.GotError()) +} + +func TestCmpTimezones(t *testing.T) { + assert := New(t) + ft, fa := newFakeT() + + type A struct { + Time time.Time + } + + ti := time.Now() + ti2 := ti.In(time.UTC) + fa.Cmp(&A{ti}, &A{ti2}) + assert.False(ft.GotError()) + + ti2 = ti.In(locAmsterdam) + fa.Cmp(&A{ti}, &A{ti2}) + assert.False(ft.GotError()) + + ti = ti.In(locTokyo) + fa.Cmp(&A{ti}, &A{ti2}) + assert.False(ft.GotError()) + + ti2 = ti2.Add(time.Second) + fa.Cmp(&A{ti}, &A{ti2}) + assert.True(ft.GotError()) +} + +func TestNCmp(t *testing.T) { + assert := New(t) + ft, fa := newFakeT() + + type A struct { + S string + } + + fa.NCmp(A{"not"}, A{"equal"}) + assert.False(ft.GotError()) + + fa.NCmp(A{"equal"}, A{"equal"}) + assert.True(ft.GotError()) +} diff --git a/faket_test.go b/faket_test.go new file mode 100644 index 0000000..1a494e0 --- /dev/null +++ b/faket_test.go @@ -0,0 +1,23 @@ +package assert + +type fakeT struct { + gotError bool +} + +func newFakeT() (*fakeT, Assert) { + var ft fakeT + return &ft, New(&ft) +} + +func (t *fakeT) Error(_ ...interface{}) { + t.gotError = true +} + +func (_ *fakeT) Helper() {} + +func (t *fakeT) GotError() bool { + r := t.gotError + t.gotError = false + + return r +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..55a7857 --- /dev/null +++ b/go.mod @@ -0,0 +1,5 @@ +module git.fuyu.moe/Fuyu/assert + +go 1.14 + +require github.com/google/go-cmp v0.4.0 diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..4430646 --- /dev/null +++ b/go.sum @@ -0,0 +1,3 @@ +github.com/google/go-cmp v0.4.0 h1:xsAVV57WRhGj6kEIi8ReJzQlHHqcBYCElAvkovg3B/4= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff --git a/helper.go b/helper.go index 67cc4a9..d8d5e63 100644 --- a/helper.go +++ b/helper.go @@ -2,10 +2,7 @@ package assert import ( "fmt" - "reflect" - "runtime" "strconv" - "testing" ) func prepMsg(msg []interface{}, format string, args ...interface{}) []interface{} { @@ -15,10 +12,3 @@ func prepMsg(msg []interface{}, format string, args ...interface{}) []interface{ func shell(i int) string { return "\x1B[" + strconv.Itoa(i) + "m" } - -var ts = map[*runtime.Func]*testing.T{} - -func t(a Assert) *testing.T { - f := runtime.FuncForPC(reflect.ValueOf(a).Pointer()) - return ts[f] -} diff --git a/type.go b/type.go new file mode 100644 index 0000000..1c9a1fd --- /dev/null +++ b/type.go @@ -0,0 +1,7 @@ +package assert + +// T is an interface of what we use from testing.T +type T interface { + Error(...interface{}) + Helper() +}