github.com/benz9527/xboot@v0.0.0-20240504061247-c23f15593274/lib/infra/err_stack_test.go (about) 1 package infra 2 3 import ( 4 "encoding/json" 5 "errors" 6 "fmt" 7 "regexp" 8 "runtime" 9 "testing" 10 11 "github.com/stretchr/testify/require" 12 "go.uber.org/zap" 13 ) 14 15 var initPC = caller() 16 17 func caller() frame { 18 var PCs [3]uintptr 19 n := runtime.Callers(2, PCs[:]) 20 frames := runtime.CallersFrames(PCs[:n]) 21 fr, _ := frames.Next() 22 return frame(fr.PC) 23 } 24 25 func TestFrameFormat(t *testing.T) { 26 testcases := []struct { 27 frame 28 format string 29 expectedExpr string 30 }{ 31 { 32 initPC, 33 "%s", 34 "^err_stack_test.go$", 35 }, 36 { 37 initPC, 38 "%+s", 39 "^github.com/benz9527/xboot/lib/infra.init\\n\\t.*xboot/lib/infra/err_stack_test.go$", 40 }, 41 { 42 initPC, 43 "%n", 44 "^init$", 45 }, 46 { 47 initPC, 48 "%d", 49 "\\d+", 50 }, 51 { 52 initPC, 53 "%v", 54 "^err_stack_test.go:\\d+$", 55 }, 56 { 57 initPC, 58 "%+v", 59 "^github.com/benz9527/xboot/lib/infra.init\\n\\t.*xboot/lib/infra/err_stack_test.go:\\d+$", 60 }, 61 { 62 frame(0), 63 "%s", 64 "unknownFile", 65 }, 66 { 67 frame(0), 68 "%n", 69 "unknownFunc", 70 }, 71 { 72 frame(0), 73 "%d", 74 "0", 75 }, 76 } 77 78 for _, tc := range testcases { 79 expr := regexp.MustCompile(tc.expectedExpr) 80 require.NotNil(t, expr) 81 frameRes := fmt.Sprintf(tc.format, tc.frame) 82 require.True(t, expr.MatchString(frameRes)) 83 } 84 } 85 86 func TestFrameMarshalText(t *testing.T) { 87 testcases := []struct { 88 frame 89 expectedExpr string 90 }{ 91 { 92 initPC, 93 "^github.com/benz9527/xboot/lib/infra.init .*xboot/lib/infra/err_stack_test.go:\\d+$", 94 }, 95 { 96 frame(0), 97 "unknownFrame", 98 }, 99 } 100 for _, tc := range testcases { 101 _bytes, err := tc.frame.MarshalText() 102 require.NoError(t, err) 103 require.Greater(t, len(_bytes), 0) 104 expr := regexp.MustCompile(tc.expectedExpr) 105 require.True(t, expr.MatchString(string(_bytes))) 106 } 107 } 108 109 func TestFrameMarshalJSON(t *testing.T) { 110 testcases := []struct { 111 frame 112 expectedExpr string 113 }{ 114 { 115 initPC, 116 "^{\"func\":\"github\\.com/benz9527/xboot/lib/infra\\.init\",\"fileAndLine\":\".*xboot/lib/infra/err_stack_test\\.go:\\d+\"}$", 117 }, 118 { 119 frame(0), 120 "{\"frame\":\"unknownFrame\"}", 121 }, 122 } 123 for _, tc := range testcases { 124 _bytes, err := json.Marshal(tc.frame) 125 require.NoError(t, err) 126 require.Greater(t, len(_bytes), 0) 127 expr := regexp.MustCompile(tc.expectedExpr) 128 require.NotNil(t, expr) 129 require.True(t, expr.MatchString(string(_bytes))) 130 } 131 } 132 133 func TestStackTraceFormat(t *testing.T) { 134 testcases := []struct { 135 stackTrace 136 format string 137 expectedExpr string 138 }{ 139 { 140 []frame{}, 141 "%s", 142 "\\[\\]", 143 }, 144 { 145 nil, 146 "%s", 147 "\\[\\]", 148 }, 149 { 150 nil, 151 "%+v", 152 "nil", 153 }, 154 { 155 nil, 156 "%#v", 157 "infra.stackTrace\\(nil\\)", 158 }, 159 { 160 testStackTrace()[:4], 161 "%v", 162 "\\[(.*\\.(go|s):\\d+ ?)+\\]", 163 }, 164 { 165 testStackTrace()[:4], 166 "%#v", 167 "infra.stackTrace\\(\\[(.*\\.(go|s):\\d+,?)+\\]\\)", 168 }, 169 { 170 testStackTrace()[:2], 171 "%+v", 172 `(github\.com/benz9527/xboot/lib/infra\..*\n\t.*\.go:\d+\n?){2}`, 173 }, 174 { 175 testStackTrace()[:2], 176 "%+s", 177 `(github\.com/benz9527/xboot/lib/infra\..*\n\t.*\.go\n?){2}`, 178 }, 179 } 180 for _, tc := range testcases { 181 expr := regexp.MustCompile(tc.expectedExpr) 182 require.NotNil(t, expr) 183 st := fmt.Sprintf(tc.format, tc.stackTrace) 184 require.True(t, expr.MatchString(st)) 185 } 186 } 187 188 func testStackTrace() stackTrace { 189 pcs := make([]uintptr, 8) 190 n := runtime.Callers(1, pcs[:]) 191 var st stack = pcs[0:n] 192 return st.StackTrace() 193 } 194 195 func TestStackTraceMarshalText(t *testing.T) { 196 testcases := []struct { 197 stackTrace 198 expectedExpr string 199 }{ 200 { 201 []frame{}, 202 "", 203 }, 204 { 205 nil, 206 "", 207 }, 208 { 209 testStackTrace()[:2], 210 `(github\.com/benz9527/xboot/lib/infra\..* .*\.go:\d+\n?){2}`, 211 }, 212 } 213 for _, tc := range testcases { 214 expr := regexp.MustCompile(tc.expectedExpr) 215 require.NotNil(t, expr) 216 st, err := tc.stackTrace.MarshalText() 217 require.NoError(t, err) 218 require.GreaterOrEqual(t, len(st), 0) 219 require.True(t, expr.MatchString(string(st))) 220 } 221 } 222 223 func TestStackTraceMarshalJSON(t *testing.T) { 224 testcases := []struct { 225 stackTrace 226 expectedExpr string 227 }{ 228 { 229 []frame{}, 230 "\\[\\]", 231 }, 232 { 233 nil, 234 "\\[\\]", 235 }, 236 { 237 testStackTrace()[:2], 238 "\\[({\"func\":\"github\\.com/benz9527/xboot/lib/infra\\..*\",\"fileAndLine\":\".*/lib/infra/.*\\.go:\\d+\"},?)+\\]", 239 }, 240 { 241 testStackTrace()[:2], 242 "\\[[^]]+\\]", 243 }, 244 } 245 for _, tc := range testcases { 246 expr := regexp.MustCompile(tc.expectedExpr) 247 require.NotNil(t, expr) 248 st, err := json.Marshal(tc.stackTrace) 249 require.NoError(t, err) 250 require.GreaterOrEqual(t, len(st), 0) 251 require.True(t, expr.MatchString(string(st))) 252 } 253 } 254 255 func TestStackFormat(t *testing.T) { 256 c1 := getCallers(32) 257 testcases := []struct { 258 stack 259 format string 260 expectedExpr string 261 }{ 262 { 263 []uintptr{}, 264 "%+v", 265 "stack:\nnil", 266 }, 267 { 268 nil, 269 "%+v", 270 "stack:\nnil", 271 }, 272 { 273 getCallers(32), 274 "%+v", 275 `stack:\n(.*\..*\n\t.*\.(go|s):\d+)+`, 276 }, 277 { 278 c1, 279 "%+v", 280 `stack:\n(.*\..*\n\t.*\.(go|s):\d+)+`, 281 }, 282 { 283 nil, 284 "%#v", 285 "infra\\.stack\\(infra\\.stackTrace\\(nil\\)\\)", 286 }, 287 { 288 getCallers(32), 289 "%#v", 290 `infra\.stack\(infra\.stackTrace\(\[(.*\.(go|s):\d+,?)+\]\)\)`, 291 }, 292 { 293 c1, 294 "%#v", 295 `infra\.stack\(infra\.stackTrace\(\[(.*\.(go|s):\d+,?)+\]\)\)`, 296 }, 297 } 298 for _, tc := range testcases { 299 expr := regexp.MustCompile(tc.expectedExpr) 300 require.NotNil(t, expr) 301 s := fmt.Sprintf(tc.format, tc.stack) 302 require.True(t, expr.MatchString(s)) 303 } 304 } 305 306 func TestErrorStackMarshalJSON(t *testing.T) { 307 _errors := []error{ 308 fmt.Errorf("test"), 309 fmt.Errorf("test2"), 310 } 311 es := AppendErrorStack(nil, _errors...) 312 _bytes, err := json.Marshal(es) 313 require.NoError(t, err) 314 t.Log(string(_bytes)) 315 316 t.Logf("%v\n", es) 317 t.Logf("%s\n", es) 318 t.Logf("%+v\n", es) 319 t.Logf("%#v\n", es) 320 321 t.Log("=== 1 ===") 322 es = AppendErrorStack(es, errors.New("test3")) 323 _bytes, err = json.Marshal(es) 324 require.NoError(t, err) 325 t.Log(string(_bytes)) 326 327 t.Logf("%v\n", es) 328 t.Logf("%s\n", es) 329 t.Logf("%+v\n", es) 330 t.Logf("%#v\n", es) 331 332 t.Log("=== 2 ===") 333 es = &errorStack{} 334 _bytes, err = json.Marshal(es) 335 require.NoError(t, err) 336 t.Log(string(_bytes)) 337 338 t.Logf("%v\n", es) 339 t.Logf("%s\n", es) 340 t.Logf("%+v\n", es) 341 t.Logf("%#v\n", es) 342 343 t.Log("=== 3 ===") 344 es = AppendErrorStack(es, errors.New("test4")) 345 _bytes, err = json.Marshal(es) 346 require.NoError(t, err) 347 t.Log(string(_bytes)) 348 349 t.Logf("%v\n", es) 350 t.Logf("%s\n", es) 351 t.Logf("%+v\n", es) 352 t.Logf("%#v\n", es) 353 354 t.Log("=== 4 ===") 355 es = AppendErrorStack(es, errors.New("test5")) 356 _bytes, err = json.Marshal(es) 357 require.NoError(t, err) 358 t.Log(string(_bytes)) 359 360 t.Logf("%v\n", es) 361 t.Logf("%s\n", es) 362 t.Logf("%+v\n", es) 363 t.Logf("%#v\n", es) 364 365 t.Log("=== 5 ===") 366 es = &errorStack{} 367 es = WrapErrorStackWithMessage(es, "test6") 368 _bytes, err = json.Marshal(es) 369 require.NoError(t, err) 370 t.Log(string(_bytes)) 371 372 t.Logf("%v\n", es) 373 t.Logf("%s\n", es) 374 t.Logf("%+v\n", es) 375 t.Logf("%#v\n", es) 376 377 t.Log("=== 6 ===") 378 es = WrapErrorStackWithMessage(nil, "test7") 379 _bytes, err = json.Marshal(es) 380 require.NoError(t, err) 381 t.Log(string(_bytes)) 382 383 t.Logf("%v\n", es) 384 t.Logf("%s\n", es) 385 t.Logf("%+v\n", es) 386 t.Logf("%#v\n", es) 387 388 t.Log("=== 7 ===") 389 es = AppendErrorStack(nil, errors.New("test8")) 390 _bytes, err = json.Marshal(es) 391 require.NoError(t, err) 392 t.Log(string(_bytes)) 393 394 t.Logf("%v\n", es) 395 t.Logf("%s\n", es) 396 t.Logf("%+v\n", es) 397 t.Logf("%#v\n", es) 398 399 l, err := zap.NewDevelopment() 400 require.NoError(t, err) 401 402 var _es *errorStack = nil 403 l.Error("test zap nil err", zap.Inline(_es)) 404 405 es = WrapErrorStackWithMessage(nil, "") 406 require.Nil(t, es) 407 408 es = AppendErrorStack(nil) 409 require.Nil(t, es) 410 411 es = WrapErrorStack(nil) 412 require.Nil(t, es) 413 414 es = WrapErrorStack(errors.New("test9")) 415 require.NotNil(t, es) 416 417 es = AppendErrorStack(es, errors.New("test10")) 418 require.NotNil(t, es) 419 420 es = WrapErrorStackWithMessage(es, "test11") 421 require.NotNil(t, es) 422 423 es = AppendErrorStack(errors.New("test12"), errors.New("test13")) 424 require.NotNil(t, es) 425 l.Info("test zap dev logger", zap.Inline(es.(*errorStack))) 426 427 es = WrapErrorStackWithMessage(errors.New("test14"), "test15") 428 require.NotNil(t, es) 429 430 es = WrapErrorStack(es) 431 require.NotNil(t, es) 432 433 es = NewErrorStack("") 434 require.Nil(t, es) 435 436 es = NewErrorStack("test16") 437 require.NotNil(t, es) 438 } 439 440 type testStrError string 441 442 func (e testStrError) Error() string { 443 return string(e) 444 } 445 446 func TestErrorStack_StrError(t *testing.T) { 447 const ( 448 stErr1 = testStrError("test1-error") 449 stErr2 = testStrError("test2-error") 450 stErr3 = testStrError("test3-error") 451 ) 452 es := WrapErrorStack(stErr1) 453 require.True(t, errors.Is(es, stErr1)) 454 455 _bytes, err := json.Marshal(es) 456 require.NoError(t, err) 457 t.Log(string(_bytes)) 458 459 t.Logf("%v\n", es) 460 t.Logf("%s\n", es) 461 t.Logf("%+v\n", es) 462 t.Logf("%#v\n", es) 463 464 merr := AppendErrorStack(es, stErr2, stErr3) 465 require.True(t, errors.Is(merr, stErr2)) 466 }