blob: 029b5484c9a096dfa4e3dca060218ceab31a9eaf [file] [log] [blame]
// Copyright 2016 The Upspin Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// +build !debug
package errors
import (
"io"
"os"
"os/exec"
"testing"
"upspin.io/upspin"
)
func TestDebug(t *testing.T) {
// Test with -tags debug to run the tests in debug_test.go
cmd := exec.Command("go", "test", "-tags", "debug")
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
if err := cmd.Run(); err != nil {
t.Fatalf("external go test failed: %v", err)
}
}
func TestMarshal(t *testing.T) {
path := upspin.PathName("jane@doe.com/file")
user := upspin.UserName("joe@blow.com")
// Single error. No user is set, so we will have a zero-length field inside.
e1 := E(Op("Get"), path, IO, "network unreachable")
// Nested error.
e2 := E(Op("Read"), path, user, Other, e1)
b := MarshalError(e2)
e3 := UnmarshalError(b)
in := e2.(*Error)
out := e3.(*Error)
// Compare elementwise.
if in.Path != out.Path {
t.Errorf("expected Path %q; got %q", in.Path, out.Path)
}
if in.User != out.User {
t.Errorf("expected User %q; got %q", in.User, out.User)
}
if in.Op != out.Op {
t.Errorf("expected Op %q; got %q", in.Op, out.Op)
}
if in.Kind != out.Kind {
t.Errorf("expected kind %d; got %d", in.Kind, out.Kind)
}
// Note that error will have lost type information, so just check its Error string.
if in.Err.Error() != out.Err.Error() {
t.Errorf("expected Err %q; got %q", in.Err, out.Err)
}
}
func TestSeparator(t *testing.T) {
defer func(prev string) {
Separator = prev
}(Separator)
Separator = ":: "
// Same pattern as above.
path := upspin.PathName("jane@doe.com/file")
user := upspin.UserName("joe@blow.com")
// Single error. No user is set, so we will have a zero-length field inside.
e1 := E(Op("Get"), path, IO, "network unreachable")
// Nested error.
e2 := E(Op("Read"), path, user, Other, e1)
want := "Read: jane@doe.com/file, user joe@blow.com: I/O error:: Get: network unreachable"
if errorAsString(e2) != want {
t.Errorf("expected %q; got %q", want, e2)
}
}
func TestDoesNotChangePreviousError(t *testing.T) {
err := E(Permission)
err2 := E(Op("I will NOT modify err"), err)
expected := "I will NOT modify err: permission denied"
if errorAsString(err2) != expected {
t.Fatalf("Expected %q, got %q", expected, err2)
}
kind := err.(*Error).Kind
if kind != Permission {
t.Fatalf("Expected kind %v, got %v", Permission, kind)
}
}
func TestNoArgs(t *testing.T) {
defer func() {
err := recover()
if err == nil {
t.Fatal("E() did not panic")
}
}()
_ = E()
}
type matchTest struct {
err1, err2 error
matched bool
}
const (
path1 = upspin.PathName("john@doe.io/x")
path2 = upspin.PathName("john@doe.io/y")
john = upspin.UserName("john@doe.io")
jane = upspin.UserName("jane@doe.io")
)
const (
op = Op("Op")
op1 = Op("Op1")
op2 = Op("Op2")
)
var matchTests = []matchTest{
// Errors not of type *Error fail outright.
{nil, nil, false},
{io.EOF, io.EOF, false},
{E(io.EOF), io.EOF, false},
{io.EOF, E(io.EOF), false},
// Success. We can drop fields from the first argument and still match.
{E(io.EOF), E(io.EOF), true},
{E(op, Invalid, io.EOF, jane, path1), E(op, Invalid, io.EOF, jane, path1), true},
{E(op, Invalid, io.EOF, jane), E(op, Invalid, io.EOF, jane, path1), true},
{E(op, Invalid, io.EOF), E(op, Invalid, io.EOF, jane, path1), true},
{E(op, Invalid), E(op, Invalid, io.EOF, jane, path1), true},
{E(op), E(op, Invalid, io.EOF, jane, path1), true},
// Failure.
{E(io.EOF), E(io.ErrClosedPipe), false},
{E(op1), E(op2), false},
{E(Invalid), E(Permission), false},
{E(jane), E(john), false},
{E(path1), E(path2), false},
{E(op, Invalid, io.EOF, jane, path1), E(op, Invalid, io.EOF, john, path1), false},
{E(path1, Str("something")), E(path1), false}, // Test nil error on rhs.
// Nested *Errors.
{E(op1, E(path1)), E(op1, john, E(op2, jane, path1)), true},
{E(op1, path1), E(op1, john, E(op2, jane, path1)), false},
{E(op1, E(path1)), E(op1, john, Str(E(op2, jane, path1).Error())), false},
}
func TestMatch(t *testing.T) {
for _, test := range matchTests {
matched := Match(test.err1, test.err2)
if matched != test.matched {
t.Errorf("Match(%q, %q)=%t; want %t", test.err1, test.err2, matched, test.matched)
}
}
}
type kindTest struct {
err error
kind Kind
want bool
}
var kindTests = []kindTest{
// Non-Error errors.
{nil, NotExist, false},
{Str("not an *Error"), NotExist, false},
// Basic comparisons.
{E(NotExist), NotExist, true},
{E(Exist), NotExist, false},
{E("no kind"), NotExist, false},
{E("no kind"), Other, false},
// Nested *Error values.
{E("Nesting", E(NotExist)), NotExist, true},
{E("Nesting", E(Exist)), NotExist, false},
{E("Nesting", E("no kind")), NotExist, false},
{E("Nesting", E("no kind")), Other, false},
}
func TestKind(t *testing.T) {
for _, test := range kindTests {
got := Is(test.kind, test.err)
if got != test.want {
t.Errorf("Is(%q, %q)=%t; want %t", test.kind, test.err, got, test.want)
}
}
}
// errorAsString returns the string form of the provided error value.
// If the given string is an *Error, the stack information is removed
// before the value is stringified.
func errorAsString(err error) string {
if e, ok := err.(*Error); ok {
e2 := *e
e2.stack = stack{}
return e2.Error()
}
return err.Error()
}
// Simple test for issue 398.
func TestIssue398(t *testing.T) {
e := E("a@b.com", "c@d.com", "e@f.com/", "g@h.com/").(*Error)
// First should win.
if e.User != "a@b.com" {
t.Errorf("wrong user: got %q; want %q", e.User, "a@b.com")
}
if e.Path != "e@f.com/" {
t.Errorf("wrong path: got %q; want %q", e.Path, "e@f.com/")
}
}