hanayo/vendor/github.com/ansel1/merry/errors_test.go
2019-02-23 13:29:15 +00:00

557 lines
15 KiB
Go

package merry
import (
"errors"
"fmt"
"reflect"
"runtime"
"strconv"
"strings"
"testing"
"github.com/stretchr/testify/assert"
)
func TestNew(t *testing.T) {
_, _, rl, _ := runtime.Caller(0)
err := New("bang")
if HTTPCode(err) != 500 {
t.Errorf("http code should have been 500, was %v", HTTPCode(err))
}
if err.Error() != "bang" {
t.Errorf("error message should have been bang, was %v", err.Error())
}
f, l := Location(err)
if !strings.Contains(f, "errors_test.go") {
t.Errorf("error message should have contained errors_test.go, was %s", f)
}
if l != rl+1 {
t.Errorf("error line should have been %d, was %d", rl+1, 8)
}
}
func TestErrorf(t *testing.T) {
_, _, rl, _ := runtime.Caller(0)
err := Errorf("chitty chitty %v %v", "bang", "bang")
if HTTPCode(err) != 500 {
t.Errorf("http code should have been 500, was %v", HTTPCode(err))
}
if err.Error() != "chitty chitty bang bang" {
t.Errorf("error message should have been chitty chitty bang bang, was %v", err.Error())
}
f, l := Location(err)
if !strings.Contains(f, "errors_test.go") {
t.Errorf("error message should have contained errors_test.go, was %s", f)
}
if l != rl+1 {
t.Errorf("error line should have been %d, was %d", rl+1, 8)
}
}
func TestUserError(t *testing.T) {
_, _, rl, _ := runtime.Caller(0)
err := UserError("bang")
assert.Equal(t, "bang", UserMessage(err))
assert.Empty(t, Message(err))
_, l := Location(err)
assert.Equal(t, rl+1, l)
}
func TestUserErrorf(t *testing.T) {
_, _, rl, _ := runtime.Caller(0)
err := UserErrorf("bang %v", "bang")
assert.Equal(t, "bang bang", UserMessage(err))
assert.Empty(t, Message(err))
_, l := Location(err)
assert.Equal(t, rl+1, l)
}
func TestDetails(t *testing.T) {
var err error = New("bang")
deets := Details(err)
t.Log(deets)
lines := strings.Split(deets, "\n")
if lines[0] != "bang" {
t.Errorf("first line should have been bang: %v", lines[0])
}
if !strings.Contains(deets, Stacktrace(err)) {
t.Error("should have contained the error stacktrace")
}
err = WithUserMessage(err, "stay calm")
deets = Details(err)
t.Log(deets)
assert.Contains(t, deets, "User Message: stay calm")
// Allow nil error
assert.Empty(t, Details(nil))
}
func TestStacktrace(t *testing.T) {
_, _, rl, _ := runtime.Caller(0)
var err error = New("bang")
assert.NotEmpty(t, Stack(err))
s := Stacktrace(err)
t.Log(s)
lines := strings.Split(s, "\n")
assert.NotEmpty(t, lines)
assert.Equal(t, "github.com/ansel1/merry.TestStacktrace", lines[0])
assert.Contains(t, lines[1], fmt.Sprintf("errors_test.go:%d", rl+1))
// Allow nil error
assert.Empty(t, Stacktrace(nil))
}
func TestWrap(t *testing.T) {
err := errors.New("simple")
_, _, rl, _ := runtime.Caller(0)
wrapped := WrapSkipping(err, 0)
f, l := Location(wrapped)
if !strings.Contains(f, "errors_test.go") {
t.Errorf("error message should have contained errors_test.go, was %s", f)
}
if l != rl+1 {
t.Errorf("error line should have been %d, was %d", rl+1, l)
}
rich2 := WrapSkipping(wrapped, 0)
if wrapped != rich2 {
t.Error("rich and rich2 are not the same. Wrap should have been no-op if rich was already a RichError")
}
if !reflect.DeepEqual(Stack(wrapped), Stack(rich2)) {
t.Log(Details(rich2))
t.Error("wrap should have left the stacktrace alone if the original error already had a stack")
}
// wrapping nil -> nil
assert.Nil(t, Wrap(nil))
assert.Nil(t, WrapSkipping(nil, 1))
}
func TestHere(t *testing.T) {
ParseError := New("Parse error")
InvalidCharSet := WithMessage(ParseError, "Invalid charset").WithHTTPCode(400)
InvalidSyntax := ParseError.WithMessage("Syntax error")
if !Is(InvalidCharSet, ParseError) {
t.Error("InvalidCharSet should be a ParseError")
}
_, _, rl, _ := runtime.Caller(0)
pe := Here(ParseError)
_, l := Location(pe)
if l != rl+1 {
t.Errorf("Extend should capture a new stack. Expected %d, got %d", rl+1, l)
}
if !Is(pe, ParseError) {
t.Error("pe should be a ParseError")
}
if Is(pe, InvalidCharSet) {
t.Error("pe should not be an InvalidCharSet")
}
if pe.Error() != "Parse error" {
t.Errorf("child error's message is wrong, expected: Parse error, got %v", pe.Error())
}
icse := Here(InvalidCharSet)
if !Is(icse, ParseError) {
t.Error("icse should be a ParseError")
}
if !Is(icse, InvalidCharSet) {
t.Error("icse should be an InvalidCharSet")
}
if Is(icse, InvalidSyntax) {
t.Error("icse should not be an InvalidSyntax")
}
if icse.Error() != "Invalid charset" {
t.Errorf("child's message is wrong. Expected: Invalid charset, got: %v", icse.Error())
}
if HTTPCode(icse) != 400 {
t.Errorf("child's http code is wrong. Expected 400, got %v", HTTPCode(icse))
}
// nil -> nil
assert.Nil(t, Here(nil))
}
func TestUnwrap(t *testing.T) {
inner := errors.New("bing")
wrapper := WrapSkipping(inner, 0)
if Unwrap(wrapper) != inner {
t.Errorf("unwrapped error should have been the inner err, was %#v", inner)
}
doubleWrap := wrapper.WithMessage("blag")
if Unwrap(doubleWrap) != inner {
t.Errorf("unwrapped should recurse to inner, but got %#v", inner)
}
// nil -> nil
assert.Nil(t, Unwrap(nil))
}
func TestNilValues(t *testing.T) {
// Quirk of go
// http://devs.cloudimmunity.com/gotchas-and-common-mistakes-in-go-golang/index.html#nil_in_nil_in_vals
// an interface value isn't nil unless both the type *and* the value are nil
// make sure we aren't accidentally returning nil values but non-nil types
type e struct{}
var anE *e
type f interface{}
var anF f
if anF != nil {
t.Error("anF should have been nil here, because it doesn't have a concete type yet")
}
anF = anE
if anF == nil {
t.Error("anF should have been not nil here, because it now has a concrete type")
}
if WithMessage(WithHTTPCode(Wrap(nil), 400), "hey") != nil {
t.Error("by using interfaces in all the returns, this should have remained a true nil value")
}
}
func TestIs(t *testing.T) {
ParseError := errors.New("blag")
cp := Here(ParseError)
if !Is(cp, ParseError) {
t.Error("Is(child, parent) should be true")
}
if Is(ParseError, cp) {
t.Error("Is(parent, child) should not be true")
}
if !Is(ParseError, ParseError) {
t.Error("errors are always themselves")
}
if !Is(cp, cp) {
t.Error("should work when comparing rich error to itself")
}
if Is(Here(ParseError), cp) {
t.Error("Is(sibling, sibling) should not be true")
}
err2 := errors.New("blag")
if Is(ParseError, err2) {
t.Error("These should not have been equal")
}
if Is(Here(err2), cp) {
t.Error("these were not copies of the same error")
}
if Is(Here(err2), ParseError) {
t.Error("underlying errors were not equal")
}
nilTests := []struct {
arg1, arg2 error
expect bool
msg string
}{
{nil, New("t"), false, "nil is not any concrete error"},
{New("t"), nil, false, "no concrete error is nil"},
{nil, nil, true, "nil is nil"},
}
for _, tst := range nilTests {
assert.Equal(t, tst.expect, Is(tst.arg1, tst.arg2), tst.msg)
}
}
func TestHTTPCode(t *testing.T) {
basicErr := errors.New("blag")
if c := HTTPCode(basicErr); c != 500 {
t.Errorf("default code should be 500, was %d", c)
}
err := New("blug")
if c := HTTPCode(err); c != 500 {
t.Errorf("default code for rich errors should be 500, was %d", c)
}
errWCode := err.WithHTTPCode(404)
if c := HTTPCode(errWCode); c != 404 {
t.Errorf("the code should be set to 404, was %d", c)
}
if HTTPCode(err) != 500 {
t.Error("original error should not have been modified")
}
// nil -> nil
assert.Nil(t, WithHTTPCode(nil, 404))
assert.Equal(t, 200, HTTPCode(nil), "The code for nil is 200 (ok)")
}
func TestImplicitWrapping(t *testing.T) {
// WithXXX functions will implicitly wrap non-merry errors
// but if they do so, they should skip a frame, so the merry error's stack
// appears to start wherever the WithXXX function was called
_, _, rl, _ := runtime.Caller(0)
tests := []struct {
f func() error
fname string
}{
{fname: "WithHTTPCode", f: func() error { return WithHTTPCode(errors.New("bug"), 404) }},
{fname: "WithUserMessage", f: func() error { return WithUserMessage(errors.New("bug"), "asdf") }},
{fname: "WithUserMessages", f: func() error { return WithUserMessagef(errors.New("bug"), "asdf") }},
{fname: "WithMessage", f: func() error { return WithMessage(errors.New("bug"), "asdf") }},
{fname: "WithMessagef", f: func() error { return WithMessagef(errors.New("bug"), "asdf") }},
{fname: "WithValue", f: func() error { return WithValue(errors.New("bug"), "asdf", "asdf") }},
{fname: "Append", f: func() error { return Append(errors.New("bug"), "asdf") }},
{fname: "Appendf", f: func() error { return Appendf(errors.New("bug"), "asdf") }},
{fname: "Prepend", f: func() error { return Prepend(errors.New("bug"), "asdf") }},
{fname: "Prependf", f: func() error { return Prependf(errors.New("bug"), "asdf") }},
}
for i, test := range tests {
t.Log("Testing ", test.fname)
err := test.f()
f, l := Location(err)
assert.Contains(t, f, "errors_test.go", "error message should have contained errors_test.go")
assert.Equal(t, rl+5+i, l, "error line number was incorrect")
}
}
func TestWithMessage(t *testing.T) {
err1 := New("blug")
err2 := err1.WithMessage("blee")
err3 := err2.WithMessage("red")
assert.EqualError(t, err1, "blug")
assert.EqualError(t, err2, "blee", "should have overridden the underlying message")
assert.EqualError(t, err3, "red")
assert.Equal(t, Stack(err1), Stack(err2), "stack should not have been altered")
// nil -> nil
assert.Nil(t, WithMessage(nil, ""))
}
func TestWithMessagef(t *testing.T) {
err1 := New("blug")
err2 := err1.WithMessagef("super %v", "stew")
err3 := err1.WithMessagef("blue %v", "red")
assert.EqualError(t, err1, "blug")
assert.EqualError(t, err2, "super stew")
assert.EqualError(t, err3, "blue red")
assert.Equal(t, Stack(err1), Stack(err2), "stack should not have been altered")
// nil -> nil
assert.Nil(t, WithMessagef(nil, "", ""))
}
func TestMessage(t *testing.T) {
tests := []error{
errors.New("one"),
WithMessage(errors.New("blue"), "one"),
New("one"),
}
for _, test := range tests {
assert.Equal(t, "one", test.Error())
assert.Equal(t, "one", Message(test))
}
// when verbose is on, Error() changes, but Message() doesn't
defer SetVerboseDefault(false)
SetVerboseDefault(true)
e := New("two")
assert.Equal(t, "two", Message(e))
assert.NotEqual(t, "two", e.Error())
// when error is nil, return ""
assert.Empty(t, Message(nil))
}
func TestWithUserMessage(t *testing.T) {
fault := New("seg fault")
e := WithUserMessage(fault, "a glitch")
assert.Equal(t, "seg fault", e.Error())
assert.Equal(t, "a glitch", UserMessage(e))
e = WithUserMessagef(e, "not a %s deal", "huge")
assert.Equal(t, "not a huge deal", UserMessage(e))
// If user message is set and regular message isn't, set regular message to user message
e = New("").WithUserMessage("a blag")
assert.Equal(t, "a blag", UserMessage(e))
assert.Equal(t, "a blag", e.Error())
}
func TestAppend(t *testing.T) {
blug := New("blug")
err := blug.Append("blog")
assert.Equal(t, err.Error(), "blug: blog")
err = Append(err, "blig")
assert.Equal(t, err.Error(), "blug: blog: blig")
err = blug.Appendf("%s", "blog")
assert.Equal(t, err.Error(), "blug: blog")
err = Appendf(err, "%s", "blig")
assert.Equal(t, err.Error(), "blug: blog: blig")
// nil -> nil
assert.Nil(t, Append(nil, ""))
assert.Nil(t, Appendf(nil, "", ""))
}
func TestPrepend(t *testing.T) {
blug := New("blug")
err := blug.Prepend("blog")
assert.Equal(t, err.Error(), "blog: blug")
err = Prepend(err, "blig")
assert.Equal(t, err.Error(), "blig: blog: blug")
err = blug.Prependf("%s", "blog")
assert.Equal(t, err.Error(), "blog: blug")
err = Prependf(err, "%s", "blig")
assert.Equal(t, err.Error(), "blig: blog: blug")
// nil -> nil
assert.Nil(t, Prepend(nil, ""))
assert.Nil(t, Prependf(nil, "", ""))
}
func TestLocation(t *testing.T) {
// nil -> nil
f, l := Location(nil)
assert.Equal(t, "", f)
assert.Equal(t, 0, l)
}
func TestSourceLine(t *testing.T) {
source := SourceLine(nil)
assert.Equal(t, source, "")
err := New("foo")
source = SourceLine(err)
t.Log(source)
assert.NotEqual(t, source, "")
parts := strings.Split(source, ":")
assert.Equal(t, len(parts), 2)
if !strings.HasSuffix(parts[0], "errors_test.go") {
t.Error("source line should contain file name")
}
if i, e := strconv.Atoi(parts[1]); e != nil {
t.Errorf("not a number: %s", parts[1])
} else if i <= 0 {
t.Errorf("source line must be > 1: %s", parts[1])
}
}
func TestValue(t *testing.T) {
// nil -> nil
assert.Nil(t, WithValue(nil, "", ""))
assert.Nil(t, Value(nil, ""))
}
func TestValues(t *testing.T) {
// nil -> nil
values := Values(nil)
assert.Nil(t, values)
var e error
e = New("bad stuff")
e = WithValue(e, "key1", "val1")
e = WithValue(e, "key2", "val2")
values = Values(e)
assert.NotNil(t, values)
assert.Equal(t, values["key1"], "val1")
assert.Equal(t, values["key2"], "val2")
assert.NotNil(t, values[stack])
// make sure the last value attached is returned
e = WithValue(e, "key3", "val3")
e = WithValue(e, "key3", "val4")
values = Values(e)
assert.Equal(t, values["key3"], "val4")
}
func TestStackCaptureEnabled(t *testing.T) {
// on by default
assert.True(t, StackCaptureEnabled())
SetStackCaptureEnabled(false)
assert.False(t, StackCaptureEnabled())
e := New("yikes")
assert.Empty(t, Stack(e))
// let's just make sure none of the print functions bomb when there's no stack
assert.Empty(t, SourceLine(e))
f, l := Location(e)
assert.Empty(t, f)
assert.Equal(t, 0, l)
assert.Empty(t, Stacktrace(e))
assert.NotPanics(t, func() { Details(e) })
// turn it back on
SetStackCaptureEnabled(true)
assert.True(t, StackCaptureEnabled())
e = New("mommy")
assert.NotEmpty(t, Stack(e))
}
func TestVerboseDefault(t *testing.T) {
defer SetVerboseDefault(false)
// off by default
assert.False(t, VerboseDefault())
SetVerboseDefault(true)
assert.True(t, VerboseDefault())
e := New("yikes")
// test verbose on
assert.Equal(t, Details(e), e.Error())
// test verbose off
SetVerboseDefault(false)
s := e.Error()
assert.Equal(t, Message(e), s)
assert.Equal(t, "yikes", s)
}
func TestMerryErr_Error(t *testing.T) {
origVerbose := verbose
defer func() {
verbose = origVerbose
}()
// test with verbose on
verbose = false
tests := []struct {
desc string
verbose bool
message, userMessage string
expected string
}{
{
desc: "with message",
message: "blue",
expected: "blue",
},
{
desc: "with user message",
userMessage: "red",
expected: "red",
},
}
for _, test := range tests {
t.Log("error message tests: " + test.desc)
verbose = test.verbose
err := New(test.message).WithUserMessage(test.userMessage)
t.Log(err.Error())
assert.Equal(t, test.expected, err.Error())
}
}
func TestMerryErr_Format(t *testing.T) {
e := New("Hi")
assert.Equal(t, fmt.Sprintf("%v", e), e.Error())
assert.Equal(t, fmt.Sprintf("%s", e), e.Error())
assert.Equal(t, fmt.Sprintf("%q", e), fmt.Sprintf("%q", e.Error()))
assert.Equal(t, fmt.Sprintf("%+v", e), Details(e))
}
func BenchmarkNew_withStackCapture(b *testing.B) {
for i := 0; i < b.N; i++ {
New("boom")
}
}
func BenchmarkNew_withoutStackCapture(b *testing.B) {
SetStackCaptureEnabled(false)
for i := 0; i < b.N; i++ {
New("boom")
}
}