blob: 9f1a11aa79d6ac80cd1963f42db4897b52bfa387 [file] [log] [blame] [edit]
// Copyright 2014 Canonical Ltd.
// Licensed under the LGPLv3, see LICENCE file for details.
package errors_test
import (
stderrors "errors"
"fmt"
"io"
"os"
"path/filepath"
"runtime"
"strings"
gc "gopkg.in/check.v1"
"github.com/juju/errors"
)
type functionSuite struct {
}
var _ = gc.Suite(&functionSuite{})
func (*functionSuite) TestNew(c *gc.C) {
err := errors.New("testing")
loc := errorLocationValue(c)
c.Assert(err.Error(), gc.Equals, "testing")
c.Assert(errors.Cause(err), gc.Equals, err)
c.Assert(errors.Details(err), Contains, loc)
}
func (*functionSuite) TestErrorf(c *gc.C) {
err := errors.Errorf("testing %d", 42)
loc := errorLocationValue(c)
c.Assert(err.Error(), gc.Equals, "testing 42")
c.Assert(errors.Cause(err), gc.Equals, err)
c.Assert(errors.Details(err), Contains, loc)
}
func (*functionSuite) TestTrace(c *gc.C) {
first := errors.New("first")
err := errors.Trace(first)
loc := errorLocationValue(c)
c.Assert(err.Error(), gc.Equals, "first")
c.Assert(errors.Is(err, first), gc.Equals, true)
c.Assert(errors.Details(err), Contains, loc)
c.Assert(errors.Trace(nil), gc.IsNil)
}
func (*functionSuite) TestAnnotate(c *gc.C) {
first := errors.New("first")
err := errors.Annotate(first, "annotation")
loc := errorLocationValue(c)
c.Assert(err.Error(), gc.Equals, "annotation: first")
c.Assert(errors.Cause(err), gc.Equals, first)
c.Assert(errors.Details(err), Contains, loc)
c.Assert(errors.Annotate(nil, "annotate"), gc.IsNil)
}
func (*functionSuite) TestAnnotatef(c *gc.C) {
first := errors.New("first")
err := errors.Annotatef(first, "annotation %d", 2) //err annotatefTest
loc := errorLocationValue(c)
c.Assert(err.Error(), gc.Equals, "annotation 2: first")
c.Assert(errors.Cause(err), gc.Equals, first)
c.Assert(errors.Details(err), Contains, loc)
c.Assert(errors.Annotatef(nil, "annotate"), gc.IsNil)
}
func (*functionSuite) TestDeferredAnnotatef(c *gc.C) {
// NOTE: this test fails with gccgo
if runtime.Compiler == "gccgo" {
c.Skip("gccgo can't determine the location")
}
first := errors.New("first")
test := func() (err error) {
defer errors.DeferredAnnotatef(&err, "deferred %s", "annotate")
return first
}
err := test()
c.Assert(err.Error(), gc.Equals, "deferred annotate: first")
c.Assert(errors.Cause(err), gc.Equals, first)
err = nil
errors.DeferredAnnotatef(&err, "deferred %s", "annotate")
c.Assert(err, gc.IsNil)
}
func (*functionSuite) TestWrap(c *gc.C) {
first := errors.New("first")
firstLoc := errorLocationValue(c)
detailed := errors.New("detailed")
err := errors.Wrap(first, detailed)
secondLoc := errorLocationValue(c)
c.Assert(err.Error(), gc.Equals, "detailed")
c.Assert(errors.Cause(err), gc.Equals, detailed)
c.Assert(errors.Details(err), Contains, firstLoc)
c.Assert(errors.Details(err), Contains, secondLoc)
}
func (*functionSuite) TestWrapOfNil(c *gc.C) {
detailed := errors.New("detailed")
err := errors.Wrap(nil, detailed)
loc := errorLocationValue(c)
c.Assert(err.Error(), gc.Equals, "detailed")
c.Assert(errors.Cause(err), gc.Equals, detailed)
c.Assert(errors.Details(err), Contains, loc)
}
func (*functionSuite) TestWrapf(c *gc.C) {
first := errors.New("first")
firstLoc := errorLocationValue(c)
detailed := errors.New("detailed")
err := errors.Wrapf(first, detailed, "value %d", 42)
secondLoc := errorLocationValue(c)
c.Assert(err.Error(), gc.Equals, "value 42: detailed")
c.Assert(errors.Cause(err), gc.Equals, detailed)
c.Assert(errors.Details(err), Contains, firstLoc)
c.Assert(errors.Details(err), Contains, secondLoc)
}
func (*functionSuite) TestWrapfOfNil(c *gc.C) {
detailed := errors.New("detailed")
err := errors.Wrapf(nil, detailed, "value %d", 42)
loc := errorLocationValue(c)
c.Assert(err.Error(), gc.Equals, "value 42: detailed")
c.Assert(errors.Cause(err), gc.Equals, detailed)
c.Assert(errors.Details(err), Contains, loc)
}
func (*functionSuite) TestMask(c *gc.C) {
first := errors.New("first")
err := errors.Mask(first)
loc := errorLocationValue(c)
c.Assert(err.Error(), gc.Equals, "first")
c.Assert(errors.Cause(err), gc.Equals, err)
c.Assert(errors.Details(err), Contains, loc)
c.Assert(errors.Mask(nil), gc.IsNil)
}
func (*functionSuite) TestMaskf(c *gc.C) {
first := errors.New("first")
err := errors.Maskf(first, "masked %d", 42)
loc := errorLocationValue(c)
c.Assert(err.Error(), gc.Equals, "masked 42: first")
c.Assert(errors.Cause(err), gc.Equals, err)
c.Assert(errors.Details(err), Contains, loc)
c.Assert(errors.Maskf(nil, "mask"), gc.IsNil)
}
func (*functionSuite) TestCause(c *gc.C) {
c.Assert(errors.Cause(nil), gc.IsNil)
c.Assert(errors.Cause(someErr), gc.Equals, someErr)
fmtErr := fmt.Errorf("simple")
c.Assert(errors.Cause(fmtErr), gc.Equals, fmtErr)
err := errors.Wrap(someErr, fmtErr)
c.Assert(errors.Cause(err), gc.Equals, fmtErr)
err = errors.Annotate(err, "annotated")
c.Assert(errors.Cause(err), gc.Equals, fmtErr)
err = errors.Maskf(err, "masked")
c.Assert(errors.Cause(err), gc.Equals, err)
// Look for a file that we know isn't there.
dir := c.MkDir()
_, err = os.Stat(filepath.Join(dir, "not-there"))
c.Assert(os.IsNotExist(err), gc.Equals, true)
err = errors.Annotatef(err, "wrap it")
// Now the error itself isn't a 'IsNotExist'.
c.Assert(os.IsNotExist(err), gc.Equals, false)
// However if we use the Check method, it is.
c.Assert(os.IsNotExist(errors.Cause(err)), gc.Equals, true)
}
type tracer interface {
StackTrace() []string
}
func (*functionSuite) TestErrorStack(c *gc.C) {
for i, test := range []struct {
message string
generator func(*gc.C, io.Writer) error
tracer bool
}{{
message: "nil",
generator: func(_ *gc.C, _ io.Writer) error {
return nil
},
}, {
message: "raw error",
generator: func(c *gc.C, expected io.Writer) error {
fmt.Fprint(expected, "raw")
return fmt.Errorf("raw")
},
}, {
message: "single error stack",
generator: func(c *gc.C, expected io.Writer) error {
err := errors.New("first error")
fmt.Fprintf(expected, "%s: first error", errorLocationValue(c))
return err
},
tracer: true,
}, {
message: "annotated error",
generator: func(c *gc.C, expected io.Writer) error {
err := errors.New("first error")
fmt.Fprintf(expected, "%s: first error\n", errorLocationValue(c))
err = errors.Annotate(err, "annotation")
fmt.Fprintf(expected, "%s: annotation", errorLocationValue(c))
return err
},
tracer: true,
}, {
message: "wrapped error",
generator: func(c *gc.C, expected io.Writer) error {
err := errors.New("first error")
fmt.Fprintf(expected, "%s: first error\n", errorLocationValue(c))
err = errors.Wrap(err, newError("detailed error"))
fmt.Fprintf(expected, "%s: detailed error", errorLocationValue(c))
return err
},
tracer: true,
}, {
message: "annotated wrapped error",
generator: func(c *gc.C, expected io.Writer) error {
err := errors.Errorf("first error")
fmt.Fprintf(expected, "%s: first error\n", errorLocationValue(c))
err = errors.Wrap(err, fmt.Errorf("detailed error"))
fmt.Fprintf(expected, "%s: detailed error\n", errorLocationValue(c))
err = errors.Annotatef(err, "annotated")
fmt.Fprintf(expected, "%s: annotated", errorLocationValue(c))
return err
},
tracer: true,
}, {
message: "traced, and annotated",
generator: func(c *gc.C, expected io.Writer) error {
err := errors.New("first error")
fmt.Fprintf(expected, "%s: first error\n", errorLocationValue(c))
err = errors.Trace(err)
fmt.Fprintf(expected, "%s: \n", errorLocationValue(c))
err = errors.Annotate(err, "some context")
fmt.Fprintf(expected, "%s: some context\n", errorLocationValue(c))
err = errors.Trace(err)
fmt.Fprintf(expected, "%s: \n", errorLocationValue(c))
err = errors.Annotate(err, "more context")
fmt.Fprintf(expected, "%s: more context\n", errorLocationValue(c))
err = errors.Trace(err)
fmt.Fprintf(expected, "%s: ", errorLocationValue(c))
return err
},
tracer: true,
}, {
message: "uncomparable, wrapped with a value error",
generator: func(c *gc.C, expected io.Writer) error {
err := newNonComparableError("first error")
fmt.Fprintln(expected, "first error")
err = errors.Trace(err)
fmt.Fprintf(expected, "%s: \n", errorLocationValue(c))
err = errors.Wrap(err, newError("value error"))
fmt.Fprintf(expected, "%s: value error\n", errorLocationValue(c))
err = errors.Maskf(err, "masked")
fmt.Fprintf(expected, "%s: masked\n", errorLocationValue(c))
err = errors.Annotate(err, "more context")
fmt.Fprintf(expected, "%s: more context\n", errorLocationValue(c))
err = errors.Trace(err)
fmt.Fprintf(expected, "%s: ", errorLocationValue(c))
return err
},
tracer: true,
}} {
c.Logf("%v: %s", i, test.message)
expected := strings.Builder{}
err := test.generator(c, &expected)
stack := errors.ErrorStack(err)
ok := c.Check(stack, gc.Equals, expected.String())
if !ok {
c.Logf("%#v", err)
}
tracer, ok := err.(tracer)
c.Check(ok, gc.Equals, test.tracer)
if ok {
stackTrace := tracer.StackTrace()
c.Check(stackTrace, gc.DeepEquals, strings.Split(stack, "\n"))
}
}
}
func (*functionSuite) TestFormat(c *gc.C) {
formatErrorExpected := &strings.Builder{}
err := errors.New("TestFormat")
fmt.Fprintf(formatErrorExpected, "%s: TestFormat\n", errorLocationValue(c))
err = errors.Mask(err)
fmt.Fprintf(formatErrorExpected, "%s: ", errorLocationValue(c))
for i, test := range []struct {
format string
expect string
}{{
format: "%s",
expect: "TestFormat",
}, {
format: "%v",
expect: "TestFormat",
}, {
format: "%q",
expect: `"TestFormat"`,
}, {
format: "%A",
expect: `%!A(*errors.Err=TestFormat)`,
}, {
format: "%+v",
expect: formatErrorExpected.String(),
}} {
c.Logf("test %d: %q", i, test.format)
s := fmt.Sprintf(test.format, err)
c.Check(s, gc.Equals, test.expect)
}
}
type basicError struct {
Reason string
}
func (b *basicError) Error() string {
return b.Reason
}
func (*functionSuite) TestAs(c *gc.C) {
baseError := &basicError{"I'm an error"}
testErrors := []error{
errors.Trace(baseError),
errors.Annotate(baseError, "annotation"),
errors.Wrap(baseError, errors.New("wrapper")),
errors.Mask(baseError),
}
for _, err := range testErrors {
bError := &basicError{}
val := errors.As(err, &bError)
c.Check(val, gc.Equals, true)
c.Check(bError.Reason, gc.Equals, "I'm an error")
}
}
func (*functionSuite) TestIs(c *gc.C) {
baseError := &basicError{"I'm an error"}
testErrors := []error{
errors.Trace(baseError),
errors.Annotate(baseError, "annotation"),
errors.Wrap(baseError, errors.New("wrapper")),
errors.Mask(baseError),
}
for _, err := range testErrors {
val := errors.Is(err, baseError)
c.Check(val, gc.Equals, true)
}
}
func (*functionSuite) TestSetLocationWithNilError(c *gc.C) {
c.Assert(errors.SetLocation(nil, 1), gc.IsNil)
}
func (*functionSuite) TestSetLocation(c *gc.C) {
err := errors.New("test")
err = errors.SetLocation(err, 1)
stack := fmt.Sprintf("%s: test", errorLocationValue(c))
_, implements := err.(errors.Locationer)
c.Assert(implements, gc.Equals, true)
c.Check(errors.ErrorStack(err), gc.Equals, stack)
}
func (*functionSuite) TestHideErrorStillReturnsErrorString(c *gc.C) {
err := stderrors.New("This is a simple error")
err = errors.Hide(err)
c.Assert(err.Error(), gc.Equals, "This is a simple error")
}
func (*functionSuite) TestQuietWrappedErrorStillSatisfied(c *gc.C) {
simpleTestError := errors.ConstError("I am a teapot")
err := fmt.Errorf("fill me up%w", errors.Hide(simpleTestError))
c.Assert(err.Error(), gc.Equals, "fill me up")
c.Assert(errors.Is(err, simpleTestError), gc.Equals, true)
}
type ComplexErrorMessage interface {
error
ComplexMessage() string
}
type complexError struct {
Message string
}
func (c *complexError) Error() string {
return c.Message
}
func (c *complexError) ComplexMessage() string {
return c.Message
}
type complexErrorOther struct {
Message string
}
func (c *complexErrorOther) As(e any) bool {
if ce, ok := e.(**complexError); ok {
*ce = &complexError{
Message: c.Message,
}
return true
}
return false
}
func (c *complexErrorOther) Error() string {
return c.Message
}
func (c *complexErrorOther) ComplexMessage() string {
return c.Message
}
func (*functionSuite) TestHasType(c *gc.C) {
complexErr := &complexError{Message: "complex error message"}
wrapped1 := fmt.Errorf("wrapping1: %w", complexErr)
wrapped2 := fmt.Errorf("wrapping2: %w", wrapped1)
c.Assert(errors.HasType[*complexError](complexErr), gc.Equals, true)
c.Assert(errors.HasType[*complexError](wrapped1), gc.Equals, true)
c.Assert(errors.HasType[*complexError](wrapped2), gc.Equals, true)
c.Assert(errors.HasType[ComplexErrorMessage](wrapped2), gc.Equals, true)
c.Assert(errors.HasType[*complexErrorOther](wrapped2), gc.Equals, false)
c.Assert(errors.HasType[*complexErrorOther](nil), gc.Equals, false)
complexErrOther := &complexErrorOther{Message: "another complex error"}
c.Assert(errors.HasType[*complexError](complexErrOther), gc.Equals, true)
wrapped2 = fmt.Errorf("wrapping1: %w", complexErrOther)
c.Assert(errors.HasType[*complexError](wrapped2), gc.Equals, true)
}
func (*functionSuite) TestAsType(c *gc.C) {
complexErr := &complexError{Message: "complex error message"}
wrapped1 := fmt.Errorf("wrapping1: %w", complexErr)
wrapped2 := fmt.Errorf("wrapping2: %w", wrapped1)
ce, ok := errors.AsType[*complexError](complexErr)
c.Assert(ok, gc.Equals, true)
c.Assert(ce.Message, gc.Equals, complexErr.Message)
ce, ok = errors.AsType[*complexError](wrapped1)
c.Assert(ok, gc.Equals, true)
c.Assert(ce.Message, gc.Equals, complexErr.Message)
ce, ok = errors.AsType[*complexError](wrapped2)
c.Assert(ok, gc.Equals, true)
c.Assert(ce.Message, gc.Equals, complexErr.Message)
cem, ok := errors.AsType[ComplexErrorMessage](wrapped2)
c.Assert(ok, gc.Equals, true)
c.Assert(cem.ComplexMessage(), gc.Equals, complexErr.Message)
ceo, ok := errors.AsType[*complexErrorOther](wrapped2)
c.Assert(ok, gc.Equals, false)
c.Assert(ceo, gc.Equals, (*complexErrorOther)(nil))
ceo, ok = errors.AsType[*complexErrorOther](nil)
c.Assert(ok, gc.Equals, false)
c.Assert(ceo, gc.Equals, (*complexErrorOther)(nil))
complexErrOther := &complexErrorOther{Message: "another complex error"}
ce, ok = errors.AsType[*complexError](complexErrOther)
c.Assert(ok, gc.Equals, true)
c.Assert(ce.Message, gc.Equals, complexErrOther.Message)
wrapped2 = fmt.Errorf("wrapping1: %w", complexErrOther)
ce, ok = errors.AsType[*complexError](wrapped2)
c.Assert(ok, gc.Equals, true)
c.Assert(ce.Message, gc.Equals, complexErrOther.Message)
}
func ExampleHide() {
myConstError := errors.ConstError("I don't want to be fmt printed")
err := fmt.Errorf("don't show this error%w", errors.Hide(myConstError))
fmt.Println(err)
fmt.Println(stderrors.Is(err, myConstError))
// Output:
// don't show this error
// true
}
type MyError struct {
Message string
}
func (m *MyError) Error() string {
return m.Message
}
func ExampleHasType() {
myErr := &MyError{Message: "these are not the droids you're looking for"}
err := fmt.Errorf("wrapped: %w", myErr)
is := errors.HasType[*MyError](err)
fmt.Println(is)
// Output:
// true
}
func ExampleAsType() {
myErr := &MyError{Message: "these are not the droids you're looking for"}
err := fmt.Errorf("wrapped: %w", myErr)
myErr, is := errors.AsType[*MyError](err)
fmt.Println(is)
fmt.Println(myErr.Message)
// Output:
// true
// these are not the droids you're looking for
}