blob: 6dc427b9a51a027e1dc6ef8b9ccac02b03512235 [file] [log] [blame]
// Copyright 2015 The Vanadium 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 verror_test
import (
"errors"
"fmt"
"runtime"
"strings"
"testing"
"v.io/v23/i18n"
"v.io/v23/verror"
"v.io/v23/vtrace"
_ "v.io/x/ref/runtime/factories/roaming"
"v.io/x/ref/test"
)
var (
// Some error IDActions.
idActionA = verror.IDAction{
ID: "A",
Action: verror.NoRetry,
}
idActionB = verror.IDAction{
ID: "B",
Action: verror.RetryBackoff,
}
idActionC = verror.IDAction{
ID: "C",
Action: verror.NoRetry,
}
// Some languages
en = i18n.LangID("en")
fr = i18n.LangID("fr")
de = i18n.LangID("de")
)
var (
aEN0 error
aEN1 error
aFR0 error
aFR1 error
aDE0 error
aDE1 error
bEN0 error
bEN1 error
bFR0 error
bFR1 error
bDE0 error
bDE1 error
uEN0 error
uEN1 error
uFR0 error
uFR1 error
uDE0 error
uDE1 error
nEN0 error
nEN1 error
nFR0 error
nFR1 error
nDE0 error
nDE1 error
gEN error
gFR error
gDE error
v2EN error
v2FR0 error
v2FR1 error
v2DE error
)
// This function comes first because it has line numbers embedded in it, and putting it first
// reduces the chances that its line numbers will change.
func TestSubordinateErrors(t *testing.T) {
p := verror.ExplicitNew(idActionA, en, "server", "aEN0", 0)
p1 := verror.AddSubErrs(p, nil, verror.SubErr{
Name: "a=1",
Err: aEN1,
Options: verror.Print,
}, verror.SubErr{
Name: "a=2",
Err: aFR0,
Options: verror.Print,
})
r1 := "server aEN0 error A 0 [a=1: server aEN1 error A 1 2], [a=2: server aFR0 erreur A 0]"
if got, want := p1.Error(), r1; got != want {
t.Errorf("got %q, want %q", got, want)
}
p2 := verror.AddSubErrs(p, nil, verror.SubErr{
Name: "go_err=1",
Err: fmt.Errorf("Oh"),
Options: verror.Print,
})
r2 := "server aEN0 error A 0 [go_err=1: verror.test unknown error Oh]"
if got, want := p2.Error(), r2; got != want {
t.Errorf("got %q, want %q", got, want)
}
p2str := verror.DebugString(p2)
if !strings.Contains(p2str, r2) {
t.Errorf("debug string missing error message: %q, %q", p2str, r2)
}
if !strings.Contains(p2str, "verror_test.go:85") {
t.Errorf("debug string missing correct line #: %s", p2str)
}
// When Go1.4 generates stack traces, it reports the last line of the
// expression. Go1.5 reports the first line. We check for both so the
// test passes on either version.
if !strings.Contains(p2str, "verror_test.go:99") && !strings.Contains(p2str, "verror_test.go:103") {
t.Errorf("debug string missing correct line #: %s", p2str)
}
p3 := verror.AddSubErrs(p, nil, verror.SubErr{
Name: "go_err=2",
Err: fmt.Errorf("Oh"),
Options: 0,
})
r3 := "server aEN0 error A 0 "
if got, want := p3.Error(), r3; got != want {
t.Errorf("got %q, want %q", got, want)
}
}
// This function comes first because it has line numbers embedded in it, and putting it first
// reduces the chances that its line numbers will change.
func TestChained(t *testing.T) {
first := verror.IDAction{
ID: "first",
Action: verror.NoRetry,
}
second := verror.IDAction{
ID: "second",
Action: verror.NoRetry,
}
third := verror.IDAction{
ID: "third",
Action: verror.NoRetry,
}
cat := i18n.Cat()
cat.Set(en, i18n.MsgID(first.ID), "first {_}")
cat.Set(en, i18n.MsgID(second.ID), "second {3}")
cat.Set(en, i18n.MsgID(third.ID), "{3}")
l1 := verror.ExplicitNew(third, en, "", "", "third")
if got, want := len(verror.Stack(l1)), 3; got != want {
t.Fatalf("got %v, want %v", got, want)
}
l2 := verror.ExplicitNew(second, en, "", "", l1)
if got, want := len(verror.Stack(l2)), 7; got != want {
t.Fatalf("got %v, want %v", got, want)
}
l3 := verror.ExplicitNew(first, en, "", "", "not first", l2, l1)
if got, want := len(verror.Stack(l3)), 11; got != want {
t.Fatalf("got %v, want %v", got, want)
}
lines := strings.Split(verror.Stack(l3).String(), "\n")
if got, want := lines[0], "verror_test.go:160"; !strings.Contains(got, want) {
t.Fatalf("%q, doesn't contain %q", got, want)
}
if got, want := lines[4], "verror_test.go:156"; !strings.Contains(got, want) {
t.Fatalf("%q, doesn't contain %q", got, want)
}
if got, want := lines[8], "verror_test.go:152"; !strings.Contains(got, want) {
t.Fatalf("%q, doesn't contain %q", got, want)
}
for _, i := range []int{3, 7} {
if got, want := lines[i], "----- chained verror -----"; got != want {
t.Fatalf("got %v, want %v", got, want)
}
}
l1s := l1.Error()
l2s := l2.Error()
if got, want := l3.Error(), "first not first "+l2s+" "+l1s; got != want {
t.Fatalf("got %v, want %v", got, want)
}
}
func init() {
rootCtx, shutdown := test.V23Init()
defer shutdown()
def := i18n.WithLangID(rootCtx, en)
def = verror.WithComponentName(def, "verror.test")
verror.SetDefaultContext(def)
cat := i18n.Cat()
// Set messages for English and French. Do not set messages for
// German, to test the case where the messages are not present.
cat.Set(en, i18n.MsgID(idActionA.ID), "{1} {2} error A {_}")
cat.Set(fr, i18n.MsgID(idActionA.ID), "{1} {2} erreur A {_}")
cat.Set(en, i18n.MsgID(idActionB.ID), "{1} {2} problem B {_}")
cat.Set(fr, i18n.MsgID(idActionB.ID), "{1} {2} problème B {_}")
// Set English and French messages for Unknown and NoExist
// to ones the test can predict.
// Delete any German messages that may be present.
cat.Set(en, i18n.MsgID(verror.ErrUnknown.ID), "{1} {2} unknown error {_}")
cat.Set(fr, i18n.MsgID(verror.ErrUnknown.ID), "{1} {2} erreur inconnu {_}")
cat.Set(de, i18n.MsgID(verror.ErrUnknown.ID), "")
cat.Set(en, i18n.MsgID(verror.ErrNoExist.ID), "{1} {2} not found {_}")
cat.Set(fr, i18n.MsgID(verror.ErrNoExist.ID), "{1} {2} pas trouvé {_}")
cat.Set(de, i18n.MsgID(verror.ErrNoExist.ID), "")
// Set up a context that advertises French, on a server called FooServer,
// running an operation called aFR0.
ctx := i18n.WithLangID(rootCtx, fr)
ctx = verror.WithComponentName(ctx, "FooServer")
ctx, _ = vtrace.WithNewSpan(ctx, "aFR1")
// A first IDAction in various languages.
aEN0 = verror.ExplicitNew(idActionA, en, "server", "aEN0", 0)
aEN1 = verror.ExplicitNew(idActionA, en, "server", "aEN1", 1, 2)
aFR0 = verror.ExplicitNew(idActionA, fr, "server", "aFR0", 0)
aFR1 = verror.New(idActionA, ctx, 1, 2)
aDE0 = verror.ExplicitNew(idActionA, de, "server", "aDE0", 0)
aDE1 = verror.ExplicitNew(idActionA, de, "server", "aDE1", 1, 2)
// A second IDAction in various languages.
bEN0 = verror.ExplicitNew(idActionB, en, "server", "bEN0", 0)
bEN1 = verror.ExplicitNew(idActionB, en, "server", "bEN1", 1, 2)
bFR0 = verror.ExplicitNew(idActionB, fr, "server", "bFR0", 0)
bFR1 = verror.ExplicitNew(idActionB, fr, "server", "bFR1", 1, 2)
bDE0 = verror.ExplicitNew(idActionB, de, "server", "bDE0", 0)
bDE1 = verror.ExplicitNew(idActionB, de, "server", "bDE1", 1, 2)
// The Unknown error in various languages.
uEN0 = verror.ExplicitNew(verror.ErrUnknown, en, "server", "uEN0", 0)
uEN1 = verror.ExplicitNew(verror.ErrUnknown, en, "server", "uEN1", 1, 2)
uFR0 = verror.ExplicitNew(verror.ErrUnknown, fr, "server", "uFR0", 0)
uFR1 = verror.ExplicitNew(verror.ErrUnknown, fr, "server", "uFR1", 1, 2)
uDE0 = verror.ExplicitNew(verror.ErrUnknown, de, "server", "uDE0", 0)
uDE1 = verror.ExplicitNew(verror.ErrUnknown, de, "server", "uDE1", 1, 2)
// The NoExist error in various languages.
nEN0 = verror.ExplicitNew(verror.ErrNoExist, en, "server", "nEN0", 0)
nEN1 = verror.ExplicitNew(verror.ErrNoExist, en, "server", "nEN1", 1, 2)
nFR0 = verror.ExplicitNew(verror.ErrNoExist, fr, "server", "nFR0", 0)
nFR1 = verror.ExplicitNew(verror.ErrNoExist, fr, "server", "nFR1", 1, 2)
nDE0 = verror.ExplicitNew(verror.ErrNoExist, de, "server", "nDE0", 0)
nDE1 = verror.ExplicitNew(verror.ErrNoExist, de, "server", "nDE1", 1, 2)
// Errors derived from Go errors.
gerr := errors.New("Go error")
gEN = verror.ExplicitConvert(verror.ErrUnknown, en, "server", "op", gerr)
gFR = verror.ExplicitConvert(verror.ErrUnknown, fr, "server", "op", gerr)
gDE = verror.ExplicitConvert(verror.ErrUnknown, de, "server", "op", gerr)
// Errors derived from other verror errors.
// eEN1 has an English message.
v2EN = verror.ExplicitConvert(verror.ErrUnknown, en, "", "", aEN1) // still in English.
v2FR0 = verror.ExplicitConvert(verror.ErrUnknown, fr, "", "", aEN1) // converted to French, with original server and op.
v2FR1 = verror.Convert(verror.ErrUnknown, ctx, aEN1) // converted to French, but still with param[1]==aEN1.
v2DE = verror.ExplicitConvert(verror.ErrUnknown, de, "other", "op", aEN1) // left as English, since we lack German.
}
func TestDefaultValues(t *testing.T) {
if got, want := verror.ErrorID(nil), verror.ID(""); got != want {
t.Errorf("got: %v, want: %v", got, want)
}
if got, want := verror.Action(nil), verror.NoRetry; got != want {
t.Errorf("got: %v, want: %v", got, want)
}
if got, want := verror.ErrorID(verror.E{}), verror.ID(""); got != want {
t.Errorf("got: %v, want: %v", got, want)
}
if got, want := verror.Action(verror.E{}), verror.NoRetry; got != want {
t.Errorf("got: %v, want: %v", got, want)
}
unknown := verror.ExplicitNew(verror.ErrUnknown, i18n.NoLangID, "", "")
if got, want := verror.ErrorID(unknown), verror.ErrUnknown.ID; got != want {
t.Errorf("got: %v, want: %v", got, want)
}
if got, want := verror.Action(unknown), verror.NoRetry; got != want {
t.Errorf("got: %v, want: %v", got, want)
}
}
func TestBasic(t *testing.T) {
var tests = []struct {
err error
idAction verror.IDAction
msg string
}{
{aEN0, idActionA, "server aEN0 error A 0"},
{aEN1, idActionA, "server aEN1 error A 1 2"},
{aFR0, idActionA, "server aFR0 erreur A 0"},
{aFR1, idActionA, "FooServer aFR1 erreur A 1 2"},
{aDE0, idActionA, "A: server aDE0 0"},
{aDE1, idActionA, "A: server aDE1 1 2"},
{bEN0, idActionB, "server bEN0 problem B 0"},
{bEN1, idActionB, "server bEN1 problem B 1 2"},
{bFR0, idActionB, "server bFR0 problème B 0"},
{bFR1, idActionB, "server bFR1 problème B 1 2"},
{bDE0, idActionB, "B: server bDE0 0"},
{bDE1, idActionB, "B: server bDE1 1 2"},
{nEN0, verror.ErrNoExist, "server nEN0 not found 0"},
{nEN1, verror.ErrNoExist, "server nEN1 not found 1 2"},
{nFR0, verror.ErrNoExist, "server nFR0 pas trouvé 0"},
{nFR1, verror.ErrNoExist, "server nFR1 pas trouvé 1 2"},
{nDE0, verror.ErrNoExist, "v.io/v23/verror.NoExist: server nDE0 0"},
{nDE1, verror.ErrNoExist, "v.io/v23/verror.NoExist: server nDE1 1 2"},
{gEN, verror.ErrUnknown, "server op unknown error Go error"},
{gFR, verror.ErrUnknown, "server op erreur inconnu Go error"},
{gDE, verror.ErrUnknown, "v.io/v23/verror.Unknown: server op Go error"},
{v2EN, idActionA, "server aEN1 error A 1 2"},
{v2FR0, idActionA, "server aEN1 erreur A 1 2"},
{v2FR1, idActionA, "server aEN1 erreur A 1 2"},
{v2DE, idActionA, "server aEN1 error A 1 2"},
}
for i, test := range tests {
if got, want := verror.ErrorID(test.err), test.idAction.ID; got != want {
t.Errorf("%d: ErrorID(%#v); got %v, want %v", i, test.err, got, want)
}
if got, nowant := verror.ErrorID(test.err), idActionC.ID; got == nowant {
t.Errorf("%d: ErrorID(%#v); got %v, nowant %v", i, test.err, got, nowant)
}
if got, want := verror.Action(test.err), test.idAction.Action; got != want {
t.Errorf("%d: Action(%#v); got %v, want %v", i, test.err, got, want)
}
if got, want := test.err.Error(), test.msg; got != want {
t.Errorf("%d: %#v.Error(); got %q, want %q", i, test.err, got, want)
}
stack := verror.Stack(test.err)
if stack == nil {
t.Errorf("Stack(%q) got nil, want non-nil", verror.ErrorID(test.err))
} else if len(stack) < 1 || 10 < len(stack) {
t.Errorf("len(Stack(%q)) got %d, want between 1 and 10", verror.ErrorID(test.err), len(stack))
} else {
fnc := runtime.FuncForPC(stack[0])
if !strings.Contains(fnc.Name(), "verror_test.init") {
t.Errorf("Func.Name(Stack(%q)[0]) got %q, want \"verror.init\"",
verror.ErrorID(test.err), fnc.Name())
}
}
}
}
func tester() (error, error) {
l1 := verror.ExplicitNew(idActionA, en, "server", "aEN0", 0)
return l1, verror.ExplicitNew(idActionA, en, "server", "aEN0", 1)
}
func TestStack(t *testing.T) {
l1, l2 := tester()
stack1 := verror.Stack(l1).String()
stack2 := verror.Stack(l2).String()
if stack1 == stack2 {
t.Errorf("expected %q and %q to differ", stack1, stack2)
}
for _, stack := range []string{stack1, stack2} {
if got := strings.Count(stack, "\n"); got < 1 || 10 < got {
t.Errorf("got %d, want between 1 and 10", got)
}
if !strings.Contains(stack, "verror_test.tester") {
t.Errorf("got %q, doesn't contain 'verror_test.tester", stack)
}
}
}