2018-01-28 14:56:30 +00:00
|
|
|
// Copyright 2014 Canonical Ltd.
|
|
|
|
// Licensed under the LGPLv3, see LICENCE file for details.
|
|
|
|
|
|
|
|
package errors_test
|
|
|
|
|
|
|
|
import (
|
|
|
|
"fmt"
|
|
|
|
"os"
|
|
|
|
"path/filepath"
|
|
|
|
"runtime"
|
|
|
|
"strings"
|
|
|
|
|
|
|
|
jc "github.com/juju/testing/checkers"
|
|
|
|
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") //err newTest
|
|
|
|
c.Assert(err.Error(), gc.Equals, "testing")
|
|
|
|
c.Assert(errors.Cause(err), gc.Equals, err)
|
|
|
|
c.Assert(errors.Details(err), jc.Contains, tagToLocation["newTest"].String())
|
|
|
|
}
|
|
|
|
|
|
|
|
func (*functionSuite) TestErrorf(c *gc.C) {
|
|
|
|
err := errors.Errorf("testing %d", 42) //err errorfTest
|
|
|
|
c.Assert(err.Error(), gc.Equals, "testing 42")
|
|
|
|
c.Assert(errors.Cause(err), gc.Equals, err)
|
|
|
|
c.Assert(errors.Details(err), jc.Contains, tagToLocation["errorfTest"].String())
|
|
|
|
}
|
|
|
|
|
|
|
|
func (*functionSuite) TestTrace(c *gc.C) {
|
|
|
|
first := errors.New("first")
|
|
|
|
err := errors.Trace(first) //err traceTest
|
|
|
|
c.Assert(err.Error(), gc.Equals, "first")
|
|
|
|
c.Assert(errors.Cause(err), gc.Equals, first)
|
|
|
|
c.Assert(errors.Details(err), jc.Contains, tagToLocation["traceTest"].String())
|
|
|
|
|
|
|
|
c.Assert(errors.Trace(nil), gc.IsNil)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (*functionSuite) TestAnnotate(c *gc.C) {
|
|
|
|
first := errors.New("first")
|
|
|
|
err := errors.Annotate(first, "annotation") //err annotateTest
|
|
|
|
c.Assert(err.Error(), gc.Equals, "annotation: first")
|
|
|
|
c.Assert(errors.Cause(err), gc.Equals, first)
|
|
|
|
c.Assert(errors.Details(err), jc.Contains, tagToLocation["annotateTest"].String())
|
|
|
|
|
|
|
|
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
|
|
|
|
c.Assert(err.Error(), gc.Equals, "annotation 2: first")
|
|
|
|
c.Assert(errors.Cause(err), gc.Equals, first)
|
|
|
|
c.Assert(errors.Details(err), jc.Contains, tagToLocation["annotatefTest"].String())
|
|
|
|
|
|
|
|
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 deferredAnnotate
|
|
|
|
}
|
|
|
|
err := test()
|
|
|
|
c.Assert(err.Error(), gc.Equals, "deferred annotate: first")
|
|
|
|
c.Assert(errors.Cause(err), gc.Equals, first)
|
|
|
|
c.Assert(errors.Details(err), jc.Contains, tagToLocation["deferredAnnotate"].String())
|
|
|
|
|
|
|
|
err = nil
|
|
|
|
errors.DeferredAnnotatef(&err, "deferred %s", "annotate")
|
|
|
|
c.Assert(err, gc.IsNil)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (*functionSuite) TestWrap(c *gc.C) {
|
|
|
|
first := errors.New("first") //err wrapFirst
|
|
|
|
detailed := errors.New("detailed")
|
|
|
|
err := errors.Wrap(first, detailed) //err wrapTest
|
|
|
|
c.Assert(err.Error(), gc.Equals, "detailed")
|
|
|
|
c.Assert(errors.Cause(err), gc.Equals, detailed)
|
|
|
|
c.Assert(errors.Details(err), jc.Contains, tagToLocation["wrapFirst"].String())
|
|
|
|
c.Assert(errors.Details(err), jc.Contains, tagToLocation["wrapTest"].String())
|
|
|
|
}
|
|
|
|
|
|
|
|
func (*functionSuite) TestWrapOfNil(c *gc.C) {
|
|
|
|
detailed := errors.New("detailed")
|
|
|
|
err := errors.Wrap(nil, detailed) //err nilWrapTest
|
|
|
|
c.Assert(err.Error(), gc.Equals, "detailed")
|
|
|
|
c.Assert(errors.Cause(err), gc.Equals, detailed)
|
|
|
|
c.Assert(errors.Details(err), jc.Contains, tagToLocation["nilWrapTest"].String())
|
|
|
|
}
|
|
|
|
|
|
|
|
func (*functionSuite) TestWrapf(c *gc.C) {
|
|
|
|
first := errors.New("first") //err wrapfFirst
|
|
|
|
detailed := errors.New("detailed")
|
|
|
|
err := errors.Wrapf(first, detailed, "value %d", 42) //err wrapfTest
|
|
|
|
c.Assert(err.Error(), gc.Equals, "value 42: detailed")
|
|
|
|
c.Assert(errors.Cause(err), gc.Equals, detailed)
|
|
|
|
c.Assert(errors.Details(err), jc.Contains, tagToLocation["wrapfFirst"].String())
|
|
|
|
c.Assert(errors.Details(err), jc.Contains, tagToLocation["wrapfTest"].String())
|
|
|
|
}
|
|
|
|
|
|
|
|
func (*functionSuite) TestWrapfOfNil(c *gc.C) {
|
|
|
|
detailed := errors.New("detailed")
|
|
|
|
err := errors.Wrapf(nil, detailed, "value %d", 42) //err nilWrapfTest
|
|
|
|
c.Assert(err.Error(), gc.Equals, "value 42: detailed")
|
|
|
|
c.Assert(errors.Cause(err), gc.Equals, detailed)
|
|
|
|
c.Assert(errors.Details(err), jc.Contains, tagToLocation["nilWrapfTest"].String())
|
|
|
|
}
|
|
|
|
|
|
|
|
func (*functionSuite) TestMask(c *gc.C) {
|
|
|
|
first := errors.New("first")
|
|
|
|
err := errors.Mask(first) //err maskTest
|
|
|
|
c.Assert(err.Error(), gc.Equals, "first")
|
|
|
|
c.Assert(errors.Cause(err), gc.Equals, err)
|
|
|
|
c.Assert(errors.Details(err), jc.Contains, tagToLocation["maskTest"].String())
|
|
|
|
|
|
|
|
c.Assert(errors.Mask(nil), gc.IsNil)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (*functionSuite) TestMaskf(c *gc.C) {
|
|
|
|
first := errors.New("first")
|
|
|
|
err := errors.Maskf(first, "masked %d", 42) //err maskfTest
|
|
|
|
c.Assert(err.Error(), gc.Equals, "masked 42: first")
|
|
|
|
c.Assert(errors.Cause(err), gc.Equals, err)
|
|
|
|
c.Assert(errors.Details(err), jc.Contains, tagToLocation["maskfTest"].String())
|
|
|
|
|
|
|
|
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, "maksed")
|
|
|
|
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), jc.IsTrue)
|
|
|
|
|
|
|
|
err = errors.Annotatef(err, "wrap it")
|
|
|
|
// Now the error itself isn't a 'IsNotExist'.
|
|
|
|
c.Assert(os.IsNotExist(err), jc.IsFalse)
|
|
|
|
// However if we use the Check method, it is.
|
|
|
|
c.Assert(os.IsNotExist(errors.Cause(err)), jc.IsTrue)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *functionSuite) TestDetails(c *gc.C) {
|
|
|
|
if runtime.Compiler == "gccgo" {
|
|
|
|
c.Skip("gccgo can't determine the location")
|
|
|
|
}
|
|
|
|
c.Assert(errors.Details(nil), gc.Equals, "[]")
|
|
|
|
|
|
|
|
otherErr := fmt.Errorf("other")
|
|
|
|
checkDetails(c, otherErr, "[{other}]")
|
|
|
|
|
|
|
|
err0 := newEmbed("foo") //err TestStack#0
|
|
|
|
checkDetails(c, err0, "[{$TestStack#0$: foo}]")
|
|
|
|
|
|
|
|
err1 := errors.Annotate(err0, "bar") //err TestStack#1
|
|
|
|
checkDetails(c, err1, "[{$TestStack#1$: bar} {$TestStack#0$: foo}]")
|
|
|
|
|
|
|
|
err2 := errors.Trace(err1) //err TestStack#2
|
|
|
|
checkDetails(c, err2, "[{$TestStack#2$: } {$TestStack#1$: bar} {$TestStack#0$: foo}]")
|
|
|
|
}
|
|
|
|
|
|
|
|
type tracer interface {
|
|
|
|
StackTrace() []string
|
|
|
|
}
|
|
|
|
|
|
|
|
func (*functionSuite) TestErrorStack(c *gc.C) {
|
|
|
|
for i, test := range []struct {
|
|
|
|
message string
|
|
|
|
generator func() error
|
|
|
|
expected string
|
|
|
|
tracer bool
|
2019-04-21 17:58:15 +00:00
|
|
|
}{{
|
|
|
|
message: "nil",
|
|
|
|
generator: func() error {
|
|
|
|
return nil
|
2018-01-28 14:56:30 +00:00
|
|
|
},
|
2019-04-21 17:58:15 +00:00
|
|
|
}, {
|
|
|
|
message: "raw error",
|
|
|
|
generator: func() error {
|
|
|
|
return fmt.Errorf("raw")
|
|
|
|
},
|
|
|
|
expected: "raw",
|
|
|
|
}, {
|
|
|
|
message: "single error stack",
|
|
|
|
generator: func() error {
|
|
|
|
return errors.New("first error") //err single
|
|
|
|
},
|
|
|
|
expected: "$single$: first error",
|
|
|
|
tracer: true,
|
|
|
|
}, {
|
|
|
|
message: "annotated error",
|
|
|
|
generator: func() error {
|
|
|
|
err := errors.New("first error") //err annotated-0
|
|
|
|
return errors.Annotate(err, "annotation") //err annotated-1
|
|
|
|
},
|
|
|
|
expected: "" +
|
|
|
|
"$annotated-0$: first error\n" +
|
|
|
|
"$annotated-1$: annotation",
|
|
|
|
tracer: true,
|
|
|
|
}, {
|
|
|
|
message: "wrapped error",
|
|
|
|
generator: func() error {
|
|
|
|
err := errors.New("first error") //err wrapped-0
|
|
|
|
return errors.Wrap(err, newError("detailed error")) //err wrapped-1
|
|
|
|
},
|
|
|
|
expected: "" +
|
|
|
|
"$wrapped-0$: first error\n" +
|
|
|
|
"$wrapped-1$: detailed error",
|
|
|
|
tracer: true,
|
|
|
|
}, {
|
|
|
|
message: "annotated wrapped error",
|
|
|
|
generator: func() error {
|
|
|
|
err := errors.Errorf("first error") //err ann-wrap-0
|
|
|
|
err = errors.Wrap(err, fmt.Errorf("detailed error")) //err ann-wrap-1
|
|
|
|
return errors.Annotatef(err, "annotated") //err ann-wrap-2
|
|
|
|
},
|
|
|
|
expected: "" +
|
|
|
|
"$ann-wrap-0$: first error\n" +
|
|
|
|
"$ann-wrap-1$: detailed error\n" +
|
|
|
|
"$ann-wrap-2$: annotated",
|
|
|
|
tracer: true,
|
|
|
|
}, {
|
|
|
|
message: "traced, and annotated",
|
|
|
|
generator: func() error {
|
|
|
|
err := errors.New("first error") //err stack-0
|
|
|
|
err = errors.Trace(err) //err stack-1
|
|
|
|
err = errors.Annotate(err, "some context") //err stack-2
|
|
|
|
err = errors.Trace(err) //err stack-3
|
|
|
|
err = errors.Annotate(err, "more context") //err stack-4
|
|
|
|
return errors.Trace(err) //err stack-5
|
|
|
|
},
|
|
|
|
expected: "" +
|
|
|
|
"$stack-0$: first error\n" +
|
|
|
|
"$stack-1$: \n" +
|
|
|
|
"$stack-2$: some context\n" +
|
|
|
|
"$stack-3$: \n" +
|
|
|
|
"$stack-4$: more context\n" +
|
|
|
|
"$stack-5$: ",
|
|
|
|
tracer: true,
|
|
|
|
}, {
|
|
|
|
message: "uncomparable, wrapped with a value error",
|
|
|
|
generator: func() error {
|
|
|
|
err := newNonComparableError("first error") //err mixed-0
|
|
|
|
err = errors.Trace(err) //err mixed-1
|
|
|
|
err = errors.Wrap(err, newError("value error")) //err mixed-2
|
|
|
|
err = errors.Maskf(err, "masked") //err mixed-3
|
|
|
|
err = errors.Annotate(err, "more context") //err mixed-4
|
|
|
|
return errors.Trace(err) //err mixed-5
|
|
|
|
},
|
|
|
|
expected: "" +
|
|
|
|
"first error\n" +
|
|
|
|
"$mixed-1$: \n" +
|
|
|
|
"$mixed-2$: value error\n" +
|
|
|
|
"$mixed-3$: masked\n" +
|
|
|
|
"$mixed-4$: more context\n" +
|
|
|
|
"$mixed-5$: ",
|
|
|
|
tracer: true,
|
|
|
|
}} {
|
2018-01-28 14:56:30 +00:00
|
|
|
c.Logf("%v: %s", i, test.message)
|
|
|
|
err := test.generator()
|
|
|
|
expected := replaceLocations(test.expected)
|
|
|
|
stack := errors.ErrorStack(err)
|
|
|
|
ok := c.Check(stack, gc.Equals, expected)
|
|
|
|
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"))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2019-04-21 17:58:15 +00:00
|
|
|
|
|
|
|
func (*functionSuite) TestFormat(c *gc.C) {
|
|
|
|
err := errors.New("TestFormat") //err testformat
|
|
|
|
err = errors.Mask(err) //err testformat-wrap
|
|
|
|
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: "" +
|
|
|
|
"$testformat$: TestFormat\n" +
|
|
|
|
"$testformat-wrap$: ",
|
|
|
|
}} {
|
|
|
|
c.Logf("test %d: %q", i, test.format)
|
|
|
|
s := fmt.Sprintf(test.format, err)
|
|
|
|
expect := replaceLocations(test.expect)
|
|
|
|
c.Check(s, gc.Equals, expect)
|
|
|
|
}
|
|
|
|
}
|