blob: 7b47440285032fded158f547722df36ecf7bb236 [file] [log] [blame]
// Copyright 2018 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package fmt_test
import (
"io"
"os"
"testing"
"golang.org/x/exp/errors"
"golang.org/x/exp/errors/fmt"
)
func TestErrorFormatter(t *testing.T) {
var (
simple = &wrapped{"simple", nil}
elephant = &wrapped{
"can't adumbrate elephant",
detailed{},
}
transition = &wrapped2{"elephant still on strike", detailed{}}
nonascii = &wrapped{"café", nil}
newline = &wrapped{"msg with\nnewline",
&wrapped{"and another\none", nil}}
fallback = &wrapped{"fallback", os.ErrNotExist}
oldAndNew = &wrapped{"new style", formatError("old style")}
)
testCases := []struct {
err error
fmt string
want string
}{{
err: simple,
fmt: "%s",
want: "simple",
}, {
err: elephant,
fmt: "%s",
want: "can't adumbrate elephant: out of peanuts",
}, {
err: simple,
fmt: "%+v",
want: "simple\n somefile.go:123",
}, {
err: elephant,
fmt: "%+v",
want: `can't adumbrate elephant
somefile.go:123
--- out of peanuts
the elephant is on strike
and the 12 monkeys
are laughing`,
}, {
err: transition,
fmt: "%+v",
want: `elephant still on strike
somefile.go:123
--- out of peanuts
the elephant is on strike
and the 12 monkeys
are laughing`,
}, {
err: simple,
fmt: "%#v",
want: "&fmt_test.wrapped{msg:\"simple\", err:error(nil)}",
}, {
err: fmtTwice("Hello World!"),
fmt: "%#v",
want: "2 times Hello World!",
}, {
err: fallback,
fmt: "%s",
want: "fallback: file does not exist",
}, {
err: fallback,
fmt: "%+v",
want: "fallback\n somefile.go:123\n--- file does not exist",
}, {
err: oldAndNew,
fmt: "%v",
want: "new style: old style",
}, {
err: oldAndNew,
fmt: "%q",
want: `"new style: old style"`,
}, {
err: oldAndNew,
fmt: "%+v",
// Note the extra indentation.
want: "new style\n somefile.go:123\n--- old style\n otherfile.go:456",
}, {
err: simple,
fmt: "%-12s",
want: "simple ",
}, {
// Don't use formatting flags for detailed view.
err: simple,
fmt: "%+12v",
want: "simple\n somefile.go:123",
}, {
err: elephant,
fmt: "%+50s",
want: " can't adumbrate elephant: out of peanuts",
}, {
err: nonascii,
fmt: "%q",
want: `"café"`,
}, {
err: nonascii,
fmt: "%+q",
want: `"caf\u00e9"`,
}, {
err: simple,
fmt: "% x",
want: "73 69 6d 70 6c 65",
}, {
err: newline,
fmt: "%s",
want: "msg with\nnewline: and another\none",
}, {
err: nil,
fmt: "%+v",
want: "<nil>",
}, {
err: (*wrapped)(nil),
fmt: "%+v",
want: "<nil>",
}, {
err: simple,
fmt: "%T",
want: "*fmt_test.wrapped",
}, {
err: simple,
fmt: "%🤪",
want: "%!🤪(*fmt_test.wrapped=&{simple <nil>})",
}, {
err: formatError("use fmt.Formatter"),
fmt: "%#v",
want: "use fmt.Formatter",
}, {
err: wrapped{"using errors.Formatter",
formatError("use fmt.Formatter")},
fmt: "%#v",
want: "fmt_test.wrapped{msg:\"using errors.Formatter\", err:\"use fmt.Formatter\"}",
}, {
err: fmtTwice("%s %s", "ok", panicValue{}),
fmt: "%s",
want: "ok %!s(PANIC=panic)/ok %!s(PANIC=panic)",
}, {
err: fmtTwice("%o %s", panicValue{}, "ok"),
fmt: "%s",
want: "{} ok/{} ok",
}}
for i, tc := range testCases {
t.Run(fmt.Sprintf("%d/%s", i, tc.fmt), func(t *testing.T) {
got := fmt.Sprintf(tc.fmt, tc.err)
if got != tc.want {
t.Errorf("\n got: %q\nwant: %q", got, tc.want)
}
})
}
}
var _ errors.Formatter = wrapped{}
type wrapped struct {
msg string
err error
}
func (e wrapped) Error() string { return "should call Format" }
func (e wrapped) Format(p errors.Printer) (next error) {
p.Print(e.msg)
p.Detail()
p.Print("somefile.go:123")
return e.err
}
type wrapped2 struct {
msg string
err error
}
func (e wrapped2) Error() string { return "should call Format" }
func (e wrapped2) FormatError(p errors.Printer) (next error) {
p.Print(e.msg)
p.Detail()
p.Print("somefile.go:123")
return e.err
}
var _ errors.Formatter = detailed{}
type detailed struct{}
func (e detailed) Error() string { return fmt.Sprint(e) }
func (detailed) Format(p errors.Printer) (next error) {
p.Printf("out of %s", "peanuts")
p.Detail()
p.Print("the elephant is on strike\n")
p.Printf("and the %d monkeys\nare laughing", 12)
return nil
}
// formatError is an error implementing Format instead of errors.Formatter.
// The implementation mimics the implementation of github.com/pkg/errors,
// including that
type formatError string
func (e formatError) Error() string { return string(e) }
func (e formatError) Format(s fmt.State, verb rune) {
// Body based on pkg/errors/errors.go
switch verb {
case 'v':
if s.Flag('+') {
io.WriteString(s, string(e))
fmt.Fprintf(s, "\n%s", "otherfile.go:456")
return
}
fallthrough
case 's':
io.WriteString(s, string(e))
case 'q':
fmt.Fprintf(s, "%q", string(e))
}
}
func (e formatError) GoString() string {
panic("should never be called")
}
type fmtTwiceErr struct {
format string
args []interface{}
}
func fmtTwice(format string, a ...interface{}) error {
return fmtTwiceErr{format, a}
}
func (e fmtTwiceErr) Error() string { return fmt.Sprint(e) }
func (e fmtTwiceErr) Format(p errors.Printer) (next error) {
p.Printf(e.format, e.args...)
p.Print("/")
p.Printf(e.format, e.args...)
return nil
}
func (e fmtTwiceErr) GoString() string {
return "2 times " + fmt.Sprintf(e.format, e.args...)
}
type panicValue struct{}
func (panicValue) String() string { panic("panic") }