github.com/ActiveState/cli@v0.0.0-20240508170324-6801f60cd051/internal/errs/errs_test.go (about) 1 package errs_test 2 3 import ( 4 "errors" 5 "os" 6 "os/exec" 7 "path/filepath" 8 "reflect" 9 "strings" 10 "testing" 11 12 "github.com/ActiveState/cli/internal/errs" 13 "github.com/ActiveState/cli/internal/locale" 14 "github.com/ActiveState/cli/internal/rtutils" 15 "github.com/stretchr/testify/assert" 16 ) 17 18 func TestErrs(t *testing.T) { 19 tests := []struct { 20 name string 21 err error 22 wantMessage string 23 wantJoinMessage string 24 }{ 25 { 26 "Creates error", 27 errs.New("hello %s", "world"), 28 "hello world", 29 "hello world", 30 }, 31 { 32 "Creates wrapped error", 33 errs.Wrap(errors.New("Wrapped"), "Wrapper %s", "error"), 34 "Wrapper error", 35 "Wrapper error: Wrapped", 36 }, 37 } 38 for _, tt := range tests { 39 t.Run(tt.name, func(t *testing.T) { 40 err := tt.err 41 if err != nil && err.Error() != tt.wantMessage { 42 t.Errorf("New() error message = %s, wantMessage %s", err.Error(), tt.wantMessage) 43 } 44 ee, ok := err.(errs.Errorable) 45 if !ok { 46 t.Fatalf("Error should be of type errs.Error") 47 } 48 if ee.Stack() == nil { 49 t.Fatalf("Stacktrace was not created") 50 } 51 for i, frame := range ee.Stack().Frames { 52 curFile := rtutils.CurrentFile() 53 if strings.Contains(frame.Path, filepath.Dir(curFile)) && frame.Path != curFile { 54 t.Fatalf("Stack should not contain reference to errs package.\nFound: %s at frame %d. Full stack:\n%s", frame.Path, i, ee.Stack().String()) 55 } 56 } 57 if joinmessage := errs.JoinMessage(err); joinmessage != tt.wantJoinMessage { 58 t.Errorf("JoinMessage did not match, want: '%s', got: '%s'", tt.wantJoinMessage, joinmessage) 59 } 60 }) 61 } 62 } 63 64 type standardError struct{ error } 65 66 func TestMatches(t *testing.T) { 67 type args struct { 68 err error 69 target interface{} 70 } 71 tests := []struct { 72 name string 73 args args 74 want bool 75 }{ 76 { 77 "Simple match", 78 args{ 79 &standardError{errors.New("error")}, 80 &standardError{}, 81 }, 82 true, 83 }, 84 { 85 "Simple miss-match", 86 args{ 87 errors.New("error"), 88 &standardError{}, 89 }, 90 false, 91 }, 92 { 93 "Wrapped match", 94 args{ 95 errs.Wrap(&standardError{errors.New("error")}, "Wrapped"), 96 &standardError{}, 97 }, 98 true, 99 }, 100 { 101 "exec.ExitError", // this one has proved troublesome 102 args{ 103 &exec.ExitError{&os.ProcessState{}, []byte("")}, 104 &exec.ExitError{}, 105 }, 106 true, 107 }, 108 { 109 "wrapped exec.ExitError", 110 args{ 111 errs.Wrap(&exec.ExitError{&os.ProcessState{}, []byte("")}, "wrapped"), 112 &exec.ExitError{}, 113 }, 114 true, 115 }, 116 { 117 "combined errors 1", 118 args{ 119 errs.Pack(&exec.ExitError{&os.ProcessState{}, []byte("")}, errs.New("Random")), 120 &exec.ExitError{}, 121 }, 122 true, 123 }, 124 { 125 "combined errors 2 - inverted", 126 args{ 127 errs.Pack(errs.New("Random"), &exec.ExitError{&os.ProcessState{}, []byte("")}), 128 &exec.ExitError{}, 129 }, 130 true, 131 }, 132 } 133 for _, tt := range tests { 134 t.Run(tt.name, func(t *testing.T) { 135 if got := errs.Matches(tt.args.err, tt.args.target); got != tt.want { 136 t.Errorf("Matches() = %v, want %v", got, tt.want) 137 } 138 }) 139 } 140 } 141 142 func TestAddTips(t *testing.T) { 143 type args struct { 144 err error 145 tips []string 146 } 147 tests := []struct { 148 name string 149 args args 150 wantErrorMsgs []string 151 wantTips []string 152 }{ 153 { 154 "Simple", 155 args{ 156 errs.New("error"), 157 []string{"tip1", "tip2"}, 158 }, 159 []string{"error"}, 160 []string{"tip1", "tip2"}, 161 }, 162 { 163 "Localized", 164 args{ 165 locale.NewError("error"), 166 []string{"tip1", "tip2"}, 167 }, 168 []string{"error"}, 169 []string{"tip1", "tip2"}, 170 }, 171 { 172 "Multi error", 173 args{ 174 errs.Pack(errs.New("error1"), errs.New("error2")), 175 []string{"tip1", "tip2"}, 176 }, 177 []string{"error1", "error2"}, 178 []string{"tip1", "tip2"}, 179 }, 180 { 181 "Multi error with locale", 182 args{ 183 errs.Pack(locale.NewError("error1"), locale.NewError("error2")), 184 []string{"tip1", "tip2"}, 185 }, 186 []string{"error1", "error2"}, 187 []string{"tip1", "tip2"}, 188 }, 189 } 190 for _, tt := range tests { 191 t.Run(tt.name, func(t *testing.T) { 192 err := errs.AddTips(tt.args.err, tt.args.tips...) 193 gotTips := []string{} 194 msgs := []string{} 195 errors := errs.Unpack(err) 196 for _, err := range errors { 197 _, isMultiError := err.(*errs.PackedErrors) 198 if !isMultiError && err.Error() != errs.TipMessage { 199 msgs = append(msgs, err.Error()) 200 } 201 202 // Check via direct type cast or via direct `.As()` method because otherwise the unwrapper of 203 // errors.As will go down paths we're not interested in. 204 var errTips errs.ErrorTips 205 if x, ok := err.(interface{ As(interface{}) bool }); ok && x.As(&errTips) { 206 gotTips = append(gotTips, errTips.ErrorTips()...) 207 } else if x, ok := err.(errs.ErrorTips); ok { 208 gotTips = append(gotTips, x.ErrorTips()...) 209 } 210 } 211 if !reflect.DeepEqual(gotTips, tt.wantTips) { 212 t.Errorf("AddTips() = %v, want %v", gotTips, tt.wantTips) 213 } 214 if !reflect.DeepEqual(msgs, tt.wantErrorMsgs) { 215 t.Errorf("Error Msgs = %v, want %v", msgs, tt.wantErrorMsgs) 216 } 217 }) 218 } 219 } 220 221 func TestUnpack(t *testing.T) { 222 tests := []struct { 223 name string 224 err error 225 want []string 226 }{ 227 { 228 "Single", 229 errs.New("error1"), 230 []string{"error1"}, 231 }, 232 { 233 "Wrapped", 234 errs.Wrap(errs.New("error1"), "error2"), 235 []string{"error2", "error1"}, 236 }, 237 { 238 "Stacked", 239 errs.Pack(errs.New("error1"), errs.New("error2"), errs.New("error3")), 240 []string{"error1", "error2", "error3"}, 241 }, 242 { 243 "Stacked and Wrapped", 244 errs.Pack(errs.New("error1"), errs.Wrap(errs.New("error2"), "error2-wrap"), errs.New("error3")), 245 []string{"error1", "error2-wrap", "error2", "error3"}, 246 }, 247 { 248 "Stacked, Wrapped and Stacked", 249 errs.Pack( 250 errs.New("error1"), 251 errs.Wrap( 252 errs.Pack(errs.New("error2a"), errs.New("error2b")), 253 "error2-wrap", 254 ), 255 errs.New("error3")), 256 []string{"error1", "error2-wrap", "error2a", "error2b", "error3"}, 257 }, 258 } 259 for _, tt := range tests { 260 t.Run(tt.name, func(t *testing.T) { 261 errors := errs.Unpack(tt.err) 262 errorMsgs := []string{} 263 for _, err := range errors { 264 errorMsgs = append(errorMsgs, err.Error()) 265 } 266 assert.Equalf(t, tt.want, errorMsgs, "Unpack(%v)", tt.err) 267 }) 268 } 269 } 270 271 func TestJoinMessage(t *testing.T) { 272 tests := []struct { 273 name string 274 err error 275 want string 276 }{ 277 { 278 "Single", 279 errs.New("error1"), 280 "error1", 281 }, 282 { 283 "Wrapped", 284 errs.Wrap(errs.New("error1"), "error2"), 285 "error2: error1", 286 }, 287 { 288 "Stacked", 289 errs.Pack(errs.New("error1"), errs.New("error2"), errs.New("error3")), 290 "- error1\n- error2\n- error3", 291 }, 292 { 293 "Stacked and Wrapped", 294 errs.Pack( 295 errs.New("error1"), 296 errs.Wrap(errs.New("error2"), "error2-wrap"), 297 errs.New("error3"), 298 ), 299 "- error1\n- error2-wrap: error2\n- error3", 300 }, 301 { 302 "Stacked, Wrapped and Stacked", 303 errs.Pack( 304 errs.New("error1"), 305 errs.Wrap( 306 errs.Pack(errs.New("error2a"), errs.New("error2b")), 307 "error2-wrap", 308 ), 309 errs.New("error3")), 310 "- error1\n- error2-wrap:\n - error2a\n - error2b\n- error3", 311 }, 312 } 313 for _, tt := range tests { 314 t.Run(tt.name, func(t *testing.T) { 315 msg := errs.JoinMessage(tt.err) 316 assert.Equalf(t, tt.want, msg, "JoinMessage(%v)", tt.err) 317 }) 318 } 319 }