massert: add All, None, Any, AnyOne, and flesh out tests

This commit is contained in:
Brian Picciano 2018-07-07 17:43:14 +00:00
parent 1b26e2958a
commit c297e09446
2 changed files with 229 additions and 65 deletions

View File

@ -26,6 +26,23 @@ func fmtBlock(str string) string {
return "\n\t" + strings.Replace(str, "\n", "\n\t", -1) + "\n"
}
func fmtMultiDescr(prefix string, aa ...Assertion) string {
if len(aa) == 0 {
return prefix + "()"
} else if len(aa) == 1 {
return prefix + "(" + fmtBlock(aa[0].Description()) + ")"
}
buf := new(bytes.Buffer)
fmt.Fprintf(buf, "%s(\n", prefix)
for _, a := range aa {
descrStr := "\t" + strings.Replace(a.Description(), "\n", "\n\t", -1)
fmt.Fprintf(buf, "%s,\n", descrStr)
}
fmt.Fprintf(buf, ")")
return buf.String()
}
func fmtStack(frames []runtime.Frame) string {
buf := new(bytes.Buffer)
tw := tabwriter.NewWriter(buf, 0, 4, 2, ' ', 0)
@ -41,6 +58,7 @@ func fmtStack(frames []runtime.Frame) string {
func (ae AssertErr) Error() string {
buf := new(bytes.Buffer)
fmt.Fprintf(buf, "\n")
fmt.Fprintf(buf, "Assertion: %s\n", fmtBlock(ae.Assertion.Description()))
fmt.Fprintf(buf, "Error: %s\n", fmtBlock(ae.Err.Error()))
fmt.Fprintf(buf, "Stack: %s\n", fmtBlock(fmtStack(ae.Assertion.Stack())))
@ -93,6 +111,8 @@ func newAssertion(assertFn func() error, descr string, skip int) Assertion {
err := assertFn()
if err == nil {
return nil
} else if ae, ok := err.(AssertErr); ok {
return ae
}
return AssertErr{
Err: err,
@ -114,31 +134,6 @@ func (a *assertion) Stack() []runtime.Frame {
return a.stack
}
// Assertions represents a set of Assertions which can be tested all at once.
type Assertions []Assertion
// New returns an empty set of Assertions which can be Add'd to.
func New() Assertions {
return make(Assertions, 0, 8)
}
// Add adds the given Assertion to the set.
func (aa *Assertions) Add(a Assertion) {
(*aa) = append(*aa, a)
}
// Assert performs the Assert method of each of the set's Assertions
// sequentially, stopping at the first error and generating a new one which
// includes the Assertion's string and stack information.
func (aa Assertions) Assert() error {
for _, a := range aa {
if err := a.Assert(); err != nil {
return err
}
}
return nil
}
////////////////////////////////////////////////////////////////////////////////
// Assertion wrappers
@ -168,7 +163,7 @@ func (dw descrWrap) Description() string {
return dw.descr
}
// Comment prepends a formatted string to the given Assertions string
// Comment prepends a formatted string to the given Assertion's string
// description.
func Comment(a Assertion, msg string, args ...interface{}) Assertion {
msg = strings.TrimSpace(msg)
@ -177,28 +172,86 @@ func Comment(a Assertion, msg string, args ...interface{}) Assertion {
return wrap{descrWrap{Assertion: a, descr: descr}}
}
type not struct {
Assertion
// Not negates an Assertion, so that it fails if the given Assertion does not,
// and vice-versa.
func Not(a Assertion) Assertion {
fn := func() error {
if err := a.Assert(); err == nil {
return errors.New("assertion should have failed")
}
return nil
}
return newAssertion(fn, fmtMultiDescr("Not", a), 0)
}
func (n not) Assert() error {
if err := n.Assertion.Assert(); err == nil {
return AssertErr{
Err: errors.New("assertion should have failed"),
Assertion: n,
// Any asserts that at least one of the given Assertions succeeds.
func Any(aa ...Assertion) Assertion {
fn := func() error {
for _, a := range aa {
if err := a.Assert(); err == nil {
return nil
}
}
return errors.New("no assertions succeeded")
}
return newAssertion(fn, fmtMultiDescr("Any", aa...), 0)
}
// AnyOne asserts that exactly one of the given Assertions succeeds.
func AnyOne(aa ...Assertion) Assertion {
fn := func() error {
any := -1
for i, a := range aa {
if err := a.Assert(); err == nil {
if any >= 0 {
return fmt.Errorf("assertions indices %d and %d both succeeded", any, i)
}
any = i
}
}
if any == -1 {
return errors.New("no assertions succeeded")
}
return nil
}
return newAssertion(fn, fmtMultiDescr("AnyOne", aa...), 0)
}
// All asserts that at all of the given Assertions succeed. Its Assert method
// will return the error of whichever Assertion failed.
func All(aa ...Assertion) Assertion {
fn := func() error {
for _, a := range aa {
if err := a.Assert(); err != nil {
// newAssertion will pass this error through, so that its
// description and callstack is what gets displayed as the
// error. This isn't totally consistent with Any's behavior, but
// it's fine.
return err
}
}
return nil
}
func (n not) Description() string {
return "not(" + fmtBlock(n.Assertion.Description()) + ")"
return newAssertion(fn, fmtMultiDescr("All", aa...), 0)
}
// Not negates an Assertion, so that it fails if the given Assertion does not,
// and vice-versa.
func Not(a Assertion) Assertion {
return not{Assertion: a}
// None asserts that all of the given Assertions fail.
//
// NOTE this is functionally equivalent to doing `Not(Any(aa...))`, but the
// error returned is more helpful.
func None(aa ...Assertion) Assertion {
fn := func() error {
for _, a := range aa {
if err := a.Assert(); err == nil {
return AssertErr{
Err: errors.New("assertion should not have succeeded"),
Assertion: a,
}
}
}
return nil
}
return newAssertion(fn, fmtMultiDescr("None", aa...), 0)
}
////////////////////////////////////////////////////////////////////////////////

View File

@ -1,24 +1,135 @@
package massert
import . "testing"
import (
"errors"
. "testing"
)
func TestAssertions(t *T) {
a := Equal(1, 1)
b := Equal(2, 2)
if err := (Assertions{a, b}).Assert(); err != nil {
t.Fatalf("first Assertions shouldn't return error, returned: %s", err)
func succeed() Assertion {
return newAssertion(func() error { return nil }, "Succeed", 0)
}
c := Comment(Equal(3, 3), "this part would succeed")
c = Comment(Not(c), "but it's being wrapped in a not, so it then won't")
func fail() Assertion {
return newAssertion(func() error { return errors.New("failure") }, "Fail", 0)
}
aa := New()
aa.Add(a)
aa.Add(b)
aa.Add(c)
err := aa.Assert()
if err == nil {
t.Fatalf("second Assertions should have returned an error, returned nil")
func TestNot(t *T) {
if err := Not(succeed()).Assert(); err == nil {
t.Fatal("Not(succeed()) should have failed")
}
if err := Not(fail()).Assert(); err != nil {
t.Fatal(err)
}
}
func TestAny(t *T) {
if err := Any().Assert(); err == nil {
t.Fatal("empty Any should fail")
}
if err := Any(succeed(), succeed()).Assert(); err != nil {
t.Fatal(err)
}
if err := Any(succeed(), fail()).Assert(); err != nil {
t.Fatal(err)
}
if err := Any(fail(), fail()).Assert(); err == nil {
t.Fatal("Any should have failed with all inner fail Assertions")
}
}
func TestAnyOne(t *T) {
if err := AnyOne().Assert(); err == nil {
t.Fatal("empty AnyOne should fail")
}
if err := AnyOne(succeed(), succeed()).Assert(); err == nil {
t.Fatal("AnyOne with two succeeds should fail")
}
if err := AnyOne(succeed(), fail()).Assert(); err != nil {
t.Fatal(err)
}
if err := AnyOne(fail(), fail()).Assert(); err == nil {
t.Fatal("AnyOne should have failed with all inner fail Assertions")
}
}
func TestAll(t *T) {
if err := All().Assert(); err != nil {
t.Fatal(err)
}
if err := All(succeed(), succeed()).Assert(); err != nil {
t.Fatal(err)
}
if err := All(succeed(), fail()).Assert(); err == nil {
t.Fatal("All should have failed with one inner fail Assertion")
}
if err := All(fail(), fail()).Assert(); err == nil {
t.Fatal("All should have failed with all inner fail Assertions")
}
}
func TestNone(t *T) {
if err := None().Assert(); err != nil {
t.Fatal(err)
}
if err := None(succeed(), succeed()).Assert(); err == nil {
t.Fatal("None should have failed with all inner succeed Assertions")
}
if err := None(succeed(), fail()).Assert(); err == nil {
t.Fatal("None should have failed with one inner succeed Assertion")
}
if err := None(fail(), fail()).Assert(); err != nil {
t.Fatal(err)
}
}
// TODO pointers, structs, slices, maps, nils
func TestEqual(t *T) {
if err := All(
Equal(1, 1),
Equal(1, int64(1)),
Equal(1, uint64(1)),
Equal("foo", "foo"),
).Assert(); err != nil {
t.Fatal(err)
}
if err := None(
Equal(1, 2),
Equal(1, int64(2)),
Equal(1, uint64(2)),
Equal("foo", "bar"),
).Assert(); err != nil {
t.Fatal(err)
}
}
func TestExactly(t *T) {
if err := All(
Exactly(1, 1),
Exactly("foo", "foo"),
).Assert(); err != nil {
t.Fatal(err)
}
if err := None(
Exactly(1, 2),
Exactly(1, int64(1)),
Exactly(1, uint64(1)),
Exactly("foo", "bar"),
).Assert(); err != nil {
t.Fatal(err)
}
t.Logf("got expected second Assertions error:\n%s", err)
}