github.com/haraldrudell/parl@v0.4.176/perrors/errorglue/error-stack_test.go (about) 1 /* 2 © 2018–present Harald Rudell <harald.rudell@gmail.com> (https://haraldrudell.github.io/haraldrudell/) 3 ISC License 4 */ 5 6 package errorglue 7 8 import ( 9 "errors" 10 "reflect" 11 "slices" 12 "strings" 13 "testing" 14 15 "github.com/haraldrudell/parl/pruntime" 16 ) 17 18 func TestErrorStack(t *testing.T) { 19 //t.Errorf("logging on") 20 // stack to examine 21 var stackExp = pruntime.NewStack(0) 22 // at-string preceding code locations “ at ” 23 var atString = "\x20at\x20" 24 // encapsulated error “message” 25 var error0 = errors.New("message") 26 // an invalid ChainString format code 27 var badFormat = func() (badFormat CSFormat) { 28 var i = -1 29 badFormat = CSFormat(i) 30 return 31 }() 32 // expected [ShortSuffix] output 33 var shortSuffixExp = strings.TrimPrefix(stackExp.Frames()[0].Loc().Short(), atString) 34 // ordered list of formats 35 var formats = []CSFormat{ 36 DefaultFormat, ShortFormat, LongFormat, ShortSuffix, LongSuffix, 37 badFormat, 38 } 39 // map from format to expected value 40 var formatExp map[CSFormat]string 41 42 var ok bool 43 var err error 44 var stackAct pruntime.Stack 45 var chainStringAct, chainStringExp string 46 var _ *errorStack 47 48 // ChainString() StackTrace() 49 // - delegated: Format() Unwrap() Error() 50 var eStackAct *errorStack 51 52 err = NewErrorStack(error0, stackExp) 53 54 // NewErrorStack() should return runtime type errorStack 55 if eStackAct, ok = err.(*errorStack); !ok { 56 t.Fatalf("FAIL NewErrorStack not errorStack") 57 } 58 59 // StackTrace() should return the slice 60 stackAct = eStackAct.StackTrace() 61 if !slices.Equal(stackAct.Frames(), stackExp.Frames()) { 62 t.Errorf("StackTrace bad\n%v exp\n%v", stackAct, stackExp) 63 } 64 65 // ChainString() should return correct string 66 formatExp = map[CSFormat]string{ 67 DefaultFormat: eStackAct.Error(), 68 ShortFormat: eStackAct.Error() + atString + stackExp.Frames()[0].Loc().Short(), 69 LongFormat: eStackAct.Error() + " [" + reflect.TypeOf(eStackAct).String() + "]" + "\n" + stackExp.String(), 70 ShortSuffix: shortSuffixExp, 71 LongSuffix: stackExp.String(), 72 badFormat: "", 73 } 74 for _, csFormat := range formats { 75 if chainStringExp, ok = formatExp[csFormat]; !ok { 76 t.Errorf("CORRUPT no formatMap entry for format %s", csFormat) 77 } 78 79 // DefaultFormat: message 80 // ShortFormat: message at errorglue.TestErrorStack()-error-stack_test.go:21 81 // LongFormat: message [*errorglue.errorStack]ID: 34 IsMain: false status: running 82 // github.com/haraldrudell/parl/perrors/errorglue.TestErrorStack(0x14000120820) 83 // error-stack_test.go:21 84 // testing.tRunner(0x14000120820, 0x102b3a100) 85 // testing.go:1595 86 // cre: testing.(*T).Run-testing.go:1648 in goroutine 1 1 87 // ShortSuffix: errorglue.TestErrorStack()-error-stack_test.go:21 88 // LongSuffix: ID: 34 IsMain: false status: running 89 // github.com/haraldrudell/parl/perrors/errorglue.TestErrorStack(0x14000120820) 90 // error-stack_test.go:21 91 // testing.tRunner(0x14000120820, 0x102b3a100) 92 // testing.go:1595 93 // cre: testing.(*T).Run-testing.go:1648 in goroutine 1 1 94 // ?255: 95 t.Logf("%s: %s", csFormat, chainStringExp) 96 97 chainStringAct = eStackAct.ChainString(csFormat) 98 if chainStringAct != chainStringExp { 99 t.Errorf("FAIL ChainString %s:\n%q exp\n%q", 100 csFormat, 101 chainStringAct, chainStringExp, 102 ) 103 } 104 } 105 } 106 107 // errorStack.ChainString(ShortSuffix), and [perrors.Short], should return 108 // panic location and not error creation location 109 func TestErrorStackPanicLine(t *testing.T) { 110 var atString = "\x20at\x20" 111 112 var suffixExp string 113 // errContainingPanicAct is created in recovery from a panic() invocation 114 var errContainingPanicAct error 115 var suffixAct string 116 // stackAct is taken on the same line as a errContainingPanic panic() invocation 117 var stackAct pruntime.Stack 118 var noErrorBase error 119 120 // ChainString() StackTrace() 121 // - delegated: Format() Unwrap() Error() 122 var eStack *errorStack 123 124 // get actuals 125 stackAct, errContainingPanicAct = getErrorStackPanic(noErrorBase) 126 if errContainingPanicAct == nil { 127 panic(errors.New("errorRecovered == nil")) 128 } else if stackAct == nil { 129 panic(errors.New("stackSlice == nil")) 130 } 131 // errContainingPanicAct runtime type should be errorStack 132 eStack = errContainingPanicAct.(*errorStack) 133 134 // stackSlice: "errorglue.getErrorStackPanic()-error-stack_test.go:202" 135 t.Logf("stackSlice: %s", stackAct.Frames()[0].Loc().Short()) 136 137 // ShortSuffix should match 138 suffixExp = strings.TrimPrefix(stackAct.Frames()[0].Loc().Short(), atString) 139 suffixAct = eStack.ChainString(ShortSuffix) 140 if suffixAct != suffixExp { 141 t.Errorf("FAIL ChainString panic:\n%q exp\n%q", suffixAct, suffixExp) 142 } 143 } 144 145 // ShortFormat and ShortSuffix should panic location for non-stack recover-value 146 func TestErrorStackPanicWithStack(t *testing.T) { 147 //t.Errorf("logging on") 148 var suffixExp string 149 var atString = "\x20at\x20" 150 var error0 = NewErrorStack(errors.New("message"), pruntime.NewStack(0)) 151 152 var errorRecovered error 153 var suffixAct string 154 var slice pruntime.Stack 155 var stacks []pruntime.Stack 156 157 // ChainString() StackTrace() 158 var eStack *errorStack 159 160 slice, errorRecovered = getErrorStackPanic(error0) 161 if errorRecovered == nil { 162 panic(errors.New("errorRecovered == nil")) 163 } else if slice == nil { 164 panic(errors.New("stackSlice == nil")) 165 } 166 167 // errorRecovered should have two stacks 168 // - oldest first 169 stacks = GetStacks(errorRecovered) 170 if len(stacks) != 2 { 171 panic(errors.New("stacks not 2")) 172 } 173 eStack = errorRecovered.(*errorStack) 174 175 // oldest comes from error0 176 // newest comes from getErrorStackPanic 177 // slice is the panic location that should be found 178 // - extracted from newest 179 // - because oldest does not have a panic 180 181 // oldest: " at errorglue.TestErrorStackPanicWithStack()-error-stack_test.go:117" 182 t.Logf("errorRecovered oldest: %q", stacks[0].Frames()[0].Loc().Short()) 183 // newest: " at errorglue.getErrorStackPanic.func1()-error-stack_test.go:156" 184 t.Logf("errorRecovered newest: %q", stacks[1].Frames()[0].Loc().Short()) 185 // stackSlice: " at errorglue.getErrorStackPanic()-error-stack_test.go:166" 186 t.Logf("slice: %q", slice.Frames()[0].Loc().Short()) 187 188 // get expected value from verifying slice 189 suffixExp = strings.TrimPrefix(slice.Frames()[0].Loc().Short(), atString) 190 191 suffixAct = eStack.ChainString(ShortSuffix) 192 if suffixAct != suffixExp { 193 t.Errorf("ChainString panic:\n%q exp\n%q", suffixAct, suffixExp) 194 } 195 } 196 197 // getErrorStackPanic returns a stack from the same line as a panic in err 198 // - error0 is an optional error used as panic argument 199 func getErrorStackPanic(error0 error) (stack pruntime.Stack, err error) { 200 defer getErrorStackPanicRecover(&err) 201 202 if error0 == nil { 203 error0 = errors.New("recover") 204 } 205 // NewStack and panic on same line 206 for stack = pruntime.NewStack(0); ; panic(error0) { 207 } 208 } 209 210 // getErrorStackPanicRecover is deeferred recover function for getErrorStackPanic 211 func getErrorStackPanicRecover(errp *error) { 212 var stack = pruntime.NewStack(0) 213 var e = recover().(error) 214 *errp = NewErrorStack(e, stack) 215 }