1package errors 2 3import ( 4 "fmt" 5 "runtime" 6 "testing" 7) 8 9var initpc = caller() 10 11type X struct{} 12 13// val returns a Frame pointing to itself. 14func (x X) val() Frame { 15 return caller() 16} 17 18// ptr returns a Frame pointing to itself. 19func (x *X) ptr() Frame { 20 return caller() 21} 22 23func TestFrameFormat(t *testing.T) { 24 var tests = []struct { 25 Frame 26 format string 27 want string 28 }{{ 29 initpc, 30 "%s", 31 "stack_test.go", 32 }, { 33 initpc, 34 "%+s", 35 "github.com/pkg/errors.init\n" + 36 "\t.+/github.com/pkg/errors/stack_test.go", 37 }, { 38 0, 39 "%s", 40 "unknown", 41 }, { 42 0, 43 "%+s", 44 "unknown", 45 }, { 46 initpc, 47 "%d", 48 "9", 49 }, { 50 0, 51 "%d", 52 "0", 53 }, { 54 initpc, 55 "%n", 56 "init", 57 }, { 58 func() Frame { 59 var x X 60 return x.ptr() 61 }(), 62 "%n", 63 `\(\*X\).ptr`, 64 }, { 65 func() Frame { 66 var x X 67 return x.val() 68 }(), 69 "%n", 70 "X.val", 71 }, { 72 0, 73 "%n", 74 "", 75 }, { 76 initpc, 77 "%v", 78 "stack_test.go:9", 79 }, { 80 initpc, 81 "%+v", 82 "github.com/pkg/errors.init\n" + 83 "\t.+/github.com/pkg/errors/stack_test.go:9", 84 }, { 85 0, 86 "%v", 87 "unknown:0", 88 }} 89 90 for i, tt := range tests { 91 testFormatRegexp(t, i, tt.Frame, tt.format, tt.want) 92 } 93} 94 95func TestFuncname(t *testing.T) { 96 tests := []struct { 97 name, want string 98 }{ 99 {"", ""}, 100 {"runtime.main", "main"}, 101 {"github.com/pkg/errors.funcname", "funcname"}, 102 {"funcname", "funcname"}, 103 {"io.copyBuffer", "copyBuffer"}, 104 {"main.(*R).Write", "(*R).Write"}, 105 } 106 107 for _, tt := range tests { 108 got := funcname(tt.name) 109 want := tt.want 110 if got != want { 111 t.Errorf("funcname(%q): want: %q, got %q", tt.name, want, got) 112 } 113 } 114} 115 116func TestStackTrace(t *testing.T) { 117 tests := []struct { 118 err error 119 want []string 120 }{{ 121 New("ooh"), []string{ 122 "github.com/pkg/errors.TestStackTrace\n" + 123 "\t.+/github.com/pkg/errors/stack_test.go:121", 124 }, 125 }, { 126 Wrap(New("ooh"), "ahh"), []string{ 127 "github.com/pkg/errors.TestStackTrace\n" + 128 "\t.+/github.com/pkg/errors/stack_test.go:126", // this is the stack of Wrap, not New 129 }, 130 }, { 131 Cause(Wrap(New("ooh"), "ahh")), []string{ 132 "github.com/pkg/errors.TestStackTrace\n" + 133 "\t.+/github.com/pkg/errors/stack_test.go:131", // this is the stack of New 134 }, 135 }, { 136 func() error { return New("ooh") }(), []string{ 137 `github.com/pkg/errors.TestStackTrace.func1` + 138 "\n\t.+/github.com/pkg/errors/stack_test.go:136", // this is the stack of New 139 "github.com/pkg/errors.TestStackTrace\n" + 140 "\t.+/github.com/pkg/errors/stack_test.go:136", // this is the stack of New's caller 141 }, 142 }, { 143 Cause(func() error { 144 return func() error { 145 return Errorf("hello %s", fmt.Sprintf("world: %s", "ooh")) 146 }() 147 }()), []string{ 148 `github.com/pkg/errors.TestStackTrace.func2.1` + 149 "\n\t.+/github.com/pkg/errors/stack_test.go:145", // this is the stack of Errorf 150 `github.com/pkg/errors.TestStackTrace.func2` + 151 "\n\t.+/github.com/pkg/errors/stack_test.go:146", // this is the stack of Errorf's caller 152 "github.com/pkg/errors.TestStackTrace\n" + 153 "\t.+/github.com/pkg/errors/stack_test.go:147", // this is the stack of Errorf's caller's caller 154 }, 155 }} 156 for i, tt := range tests { 157 x, ok := tt.err.(interface { 158 StackTrace() StackTrace 159 }) 160 if !ok { 161 t.Errorf("expected %#v to implement StackTrace() StackTrace", tt.err) 162 continue 163 } 164 st := x.StackTrace() 165 for j, want := range tt.want { 166 testFormatRegexp(t, i, st[j], "%+v", want) 167 } 168 } 169} 170 171func stackTrace() StackTrace { 172 const depth = 8 173 var pcs [depth]uintptr 174 n := runtime.Callers(1, pcs[:]) 175 var st stack = pcs[0:n] 176 return st.StackTrace() 177} 178 179func TestStackTraceFormat(t *testing.T) { 180 tests := []struct { 181 StackTrace 182 format string 183 want string 184 }{{ 185 nil, 186 "%s", 187 `\[\]`, 188 }, { 189 nil, 190 "%v", 191 `\[\]`, 192 }, { 193 nil, 194 "%+v", 195 "", 196 }, { 197 nil, 198 "%#v", 199 `\[\]errors.Frame\(nil\)`, 200 }, { 201 make(StackTrace, 0), 202 "%s", 203 `\[\]`, 204 }, { 205 make(StackTrace, 0), 206 "%v", 207 `\[\]`, 208 }, { 209 make(StackTrace, 0), 210 "%+v", 211 "", 212 }, { 213 make(StackTrace, 0), 214 "%#v", 215 `\[\]errors.Frame{}`, 216 }, { 217 stackTrace()[:2], 218 "%s", 219 `\[stack_test.go stack_test.go\]`, 220 }, { 221 stackTrace()[:2], 222 "%v", 223 `\[stack_test.go:174 stack_test.go:221\]`, 224 }, { 225 stackTrace()[:2], 226 "%+v", 227 "\n" + 228 "github.com/pkg/errors.stackTrace\n" + 229 "\t.+/github.com/pkg/errors/stack_test.go:174\n" + 230 "github.com/pkg/errors.TestStackTraceFormat\n" + 231 "\t.+/github.com/pkg/errors/stack_test.go:225", 232 }, { 233 stackTrace()[:2], 234 "%#v", 235 `\[\]errors.Frame{stack_test.go:174, stack_test.go:233}`, 236 }} 237 238 for i, tt := range tests { 239 testFormatRegexp(t, i, tt.StackTrace, tt.format, tt.want) 240 } 241} 242 243// a version of runtime.Caller that returns a Frame, not a uintptr. 244func caller() Frame { 245 var pcs [3]uintptr 246 n := runtime.Callers(2, pcs[:]) 247 frames := runtime.CallersFrames(pcs[:n]) 248 frame, _ := frames.Next() 249 return Frame(frame.PC) 250} 251