github.com/haraldrudell/parl@v0.4.176/perrors/new-errorf_test.go (about) 1 /* 2 © 2020–present Harald Rudell <harald.rudell@gmail.com> (https://haraldrudell.github.io/haraldrudell/) 3 ISC License 4 */ 5 6 package perrors 7 8 import ( 9 "errors" 10 "runtime" 11 "strings" 12 "testing" 13 14 "github.com/haraldrudell/parl/perrors/errorglue" 15 "github.com/haraldrudell/parl/pruntime" 16 ) 17 18 func TestStack(t *testing.T) { 19 var errMsg = "error message" 20 var err0 = errors.New(errMsg) 21 var expectedStackDepth = 1 22 type testNo int 23 const ( 24 addStack testNo = iota 25 hasAlready 26 isNil 27 stackn 28 LessThan 29 ) 30 var testNames = map[testNo]string{ 31 addStack: "addStack", 32 hasAlready: "hasAlready", 33 isNil: "isNil", 34 stackn: "Stackn", 35 } 36 var fileExp, failure = expectedFile() 37 if failure != "" { 38 t.Fatal(failure) 39 } 40 41 // var actualInt int 42 var err, err1, errorNil error 43 var stack pruntime.Stack 44 var frames []pruntime.Frame 45 var codeLocation *pruntime.CodeLocation 46 47 for i := testNo(0); i < LessThan; i++ { 48 var name = testNames[i] 49 switch i { 50 case addStack: 51 // add stack to existing non-stack error 52 err1 = invokeStack(err0) 53 err = err1 54 case hasAlready: 55 // do not add stack if error already has it 56 err = invokeStack(err1) 57 // Stack invoked on an error with stack should return same error 58 if err != err1 { 59 t.Error("FAIL Stack added stack trace although it was already there") 60 } 61 continue 62 case isNil: 63 // Stack invoked with nil should return nil 64 err = Stack(errorNil) 65 if err != nil { 66 t.Error("FAIL Stack nil is not nil") 67 } 68 continue 69 case stackn: 70 // Stackn 71 err = invokeStackn(err0) 72 } 73 74 // err should not be nil 75 if err == nil { 76 t.Errorf("FAIL test %s err is nil", name) 77 } 78 79 // err.Error() should match 80 if messageAct := err.Error(); messageAct != errMsg { 81 t.Errorf("FAIL %s error message wrong: %q expected: %q", name, messageAct, errMsg) 82 } 83 84 stack = errorglue.GetStackTrace(err) 85 frames = stack.Frames() 86 87 // stack: ID: 35 status: ‘running’ 88 // github.com/haraldrudell/parl/perrors.stackGoroutine({0x104d62f88?, 0x140001022e0?}, 0x14000120820?, 0x140001022f0) 89 // /opt/sw/parl/perrors/new-errorf_test.go:213 90 // Parent-ID: 34 go: github.com/haraldrudell/parl/perrors.invokeStack 91 // /opt/sw/parl/perrors/new-errorf_test.go:203 92 t.Logf("stack: %s", stack) 93 94 // number of frames should match 95 if actualInt := len(frames); actualInt != expectedStackDepth { 96 t.Errorf("FAIL %s stack depth: %d expected: %d", name, actualInt, expectedStackDepth) 97 } 98 99 codeLocation = frames[0].Loc() 100 101 // error116.CodeLocation{ 102 // File:"/opt/sw/privates/parl/error116/new-errorf_test.go", 103 // Line:48, 104 // FuncName:"github.com/haraldrudell/parl/error116.TestStack.func2" 105 // } 106 t.Logf("codeLocation %s", codeLocation) 107 108 // filename should match 109 if !strings.HasSuffix(codeLocation.File, fileExp) { 110 t.Errorf("FAIL %s top stack frame not from this file: %q should end: %q", name, codeLocation.File, fileExp) 111 } 112 } 113 } 114 115 func TestNew(t *testing.T) { 116 var errPrefix = "StackNew from " 117 // var errContains = "" 118 var errMsg, noMsg = "error message", "" 119 var expectedStackDepth = 1 120 var fileExp, failure = expectedFile() 121 if failure != "" { 122 t.Fatal(failure) 123 } 124 var names = []string{"setMessage", "defaultMessage"} 125 126 var err error 127 var stack pruntime.Stack 128 var frames []pruntime.Frame 129 var codeLocation *pruntime.CodeLocation 130 var messageAct string 131 132 for i, message := range []string{errMsg, noMsg} { 133 var name = names[i] 134 135 // invoke [perrors.New] 136 err = invokeNew(message) 137 138 // err should not be nil 139 if err == nil { 140 t.Errorf("FAIL test %s err is nil", name) 141 } 142 143 messageAct = err.Error() 144 if i == 0 { 145 // err.Error() should match 146 if messageAct != errMsg { 147 t.Errorf("FAIL %s error message wrong: %q expected: %q", name, messageAct, errMsg) 148 } 149 // New add proper default message 150 } else if !strings.HasPrefix(messageAct, errPrefix) { 151 t.Errorf("FAIL %s error message wrong: %q expected prefix: %q", name, messageAct, errPrefix) 152 } 153 154 stack = errorglue.GetStackTrace(err) 155 frames = stack.Frames() 156 157 // stack: ID: 35 status: ‘running’ 158 // github.com/haraldrudell/parl/perrors.newGoroutine({0x1045a2f0e?, 0x10450a934?}, 0x140001209c0?, 0x140001022f0) 159 // /opt/sw/parl/perrors/new-errorf_test.go:241 160 // Parent-ID: 34 go: github.com/haraldrudell/parl/perrors.invokeNew 161 // /opt/sw/parl/perrors/new-errorf_test.go:231 162 t.Logf("stack: %s", stack) 163 164 // number of frames should match 165 if actualInt := len(frames); actualInt != expectedStackDepth { 166 t.Errorf("FAIL %s stack depth: %d expected: %d", name, actualInt, expectedStackDepth) 167 } 168 169 codeLocation = frames[0].Loc() 170 171 // codeLocation github.com/haraldrudell/parl/perrors.newGoroutine 172 // /opt/sw/parl/perrors/new-errorf_test.go:241 173 t.Logf("codeLocation %s", codeLocation) 174 175 // filename should match 176 if !strings.HasSuffix(codeLocation.File, fileExp) { 177 t.Errorf("FAIL %s top stack frame not from this file: %q should end: %q", name, codeLocation.File, fileExp) 178 } 179 } 180 } 181 182 // expectedFile obtains the file numer for this file 183 func expectedFile() (file, failure string) { 184 // 0 is the caller of Caller 185 // pc unitptr to get function name 186 // file "/opt/sw/privates/parl/error116/stackslice_test.go" 187 // line int 188 if pc, file0, line, ok := runtime.Caller(0); !ok { 189 failure = "runtime.Caller failed" 190 } else { 191 _ = pc 192 _ = line 193 file = file0 194 } 195 return 196 } 197 198 // invokeStack invokes [perrors.Stack] in a goroutine 199 func invokeStack(err error) (errStack error) { 200 var ch = make(chan struct{}) 201 go stackGoroutine(err, ch, &errStack) 202 <-ch 203 204 return 205 } 206 207 // stackGoroutine is a goroutine invoking [perrors.Stack] 208 func stackGoroutine(err error, ch chan struct{}, errp *error) { 209 defer close(ch) 210 211 *errp = Stack(err) 212 } 213 214 // invokeStack invokes [perrors.Stack] in a goroutine 215 func invokeStackn(err error) (errStack error) { 216 var ch = make(chan struct{}) 217 go stacknGoroutine(err, ch, &errStack) 218 <-ch 219 220 return 221 } 222 223 // stackGoroutine is a goroutine invoking [perrors.Stack] 224 func stacknGoroutine(err error, ch chan struct{}, errp *error) { 225 defer close(ch) 226 227 *errp = Stackn(err, 0) 228 } 229 230 // invokeNew invokes [perrors.New] in a goroutine 231 func invokeNew(message string) (errStack error) { 232 var ch = make(chan struct{}) 233 go newGoroutine(message, ch, &errStack) 234 <-ch 235 236 return 237 } 238 239 // stackGoroutine is a goroutine invoking [perrors.Stack] 240 func newGoroutine(message string, ch chan struct{}, errp *error) { 241 defer close(ch) 242 243 *errp = New(message) 244 }