-
Notifications
You must be signed in to change notification settings - Fork 4
Expand file tree
/
Copy patherrors_test.go
More file actions
528 lines (435 loc) · 13.8 KB
/
errors_test.go
File metadata and controls
528 lines (435 loc) · 13.8 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
package goerr_test
import (
"encoding/json"
"errors"
"fmt"
"log/slog"
"strings"
"testing"
"github.com/m-mizutani/goerr/v2"
)
func TestJoin(t *testing.T) {
err1 := fmt.Errorf("first error")
err2 := fmt.Errorf("second error")
errs := goerr.Join(err1, err2)
if errs == nil {
t.Error("Join should return non-nil for valid errors")
}
if errs.Len() != 2 {
t.Errorf("Expected 2 errors, got %d", errs.Len())
}
// Test with nil errors
errs = goerr.Join(nil, nil)
if errs != nil {
t.Error("Join should return nil for all nil errors")
}
}
func TestAppend(t *testing.T) {
var errs *goerr.Errors
err1 := fmt.Errorf("first error")
err2 := fmt.Errorf("second error")
// Test nil base
errs = goerr.Append(errs, err1)
if errs == nil {
t.Error("Append should create new Errors for nil base")
}
if errs.Len() != 1 {
t.Errorf("Expected 1 error, got %d", errs.Len())
}
// Test append to existing
errs = goerr.Append(errs, err2)
if errs.Len() != 2 {
t.Errorf("Expected 2 errors, got %d", errs.Len())
}
}
func TestAsErrors(t *testing.T) {
err1 := fmt.Errorf("standard error")
errs := goerr.Join(err1)
// Test extracting Errors
extracted := goerr.AsErrors(errs)
if extracted == nil {
t.Error("AsErrors should extract Errors successfully")
}
// Test with non-Errors type
extracted = goerr.AsErrors(err1)
if extracted != nil {
t.Error("AsErrors should return nil for non-Errors type")
}
}
func TestErrorsErrorMethod(t *testing.T) {
err1 := fmt.Errorf("first error")
err2 := fmt.Errorf("second error")
// Test single error
errs := goerr.Join(err1)
if errs.Error() != "first error" {
t.Errorf("Expected 'first error', got '%s'", errs.Error())
}
// Test multiple errors
errs = goerr.Join(err1, err2)
expected := "first error\nsecond error"
if errs.Error() != expected {
t.Errorf("Expected '%s', got '%s'", expected, errs.Error())
}
// Test nil Errors
var nilErrs *goerr.Errors
if nilErrs.Error() != "" {
t.Error("Nil Errors should return empty string")
}
}
func TestErrorsUnwrap(t *testing.T) {
err1 := fmt.Errorf("first error")
err2 := fmt.Errorf("second error")
errs := goerr.Join(err1, err2)
unwrapped := errs.Unwrap()
if len(unwrapped) != 2 {
t.Errorf("Expected 2 unwrapped errors, got %d", len(unwrapped))
}
if unwrapped[0].Error() != "first error" {
t.Errorf("Expected 'first error', got '%s'", unwrapped[0].Error())
}
if unwrapped[1].Error() != "second error" {
t.Errorf("Expected 'second error', got '%s'", unwrapped[1].Error())
}
}
func TestErrorsIs(t *testing.T) {
err1 := fmt.Errorf("first error")
err2 := fmt.Errorf("second error")
err3 := fmt.Errorf("third error")
errs := goerr.Join(err1, err2)
if !errors.Is(errs, err1) {
t.Error("errors.Is should find err1 in Errors")
}
if !errors.Is(errs, err2) {
t.Error("errors.Is should find err2 in Errors")
}
if errors.Is(errs, err3) {
t.Error("errors.Is should not find err3 in Errors")
}
}
func TestErrorsAs(t *testing.T) {
customErr := &CustomError{msg: "custom error"}
standardErr := fmt.Errorf("standard error")
errs := goerr.Join(customErr, standardErr)
var target *CustomError
if !errors.As(errs, &target) {
t.Error("errors.As should find CustomError in Errors")
}
if target.msg != "custom error" {
t.Errorf("Expected 'custom error', got '%s'", target.msg)
}
}
func TestErrorsConvenienceMethods(t *testing.T) {
var nilErrs *goerr.Errors
// Test nil safety
if !nilErrs.IsEmpty() {
t.Error("Nil Errors should be empty")
}
if nilErrs.Len() != 0 {
t.Error("Nil Errors should have length 0")
}
if nilErrs.ErrorOrNil() != nil {
t.Error("Nil Errors should return nil from ErrorOrNil")
}
// Test with actual errors
err1 := fmt.Errorf("test error")
errs := goerr.Join(err1)
if errs.IsEmpty() {
t.Error("Non-empty Errors should not be empty")
}
if errs.Len() != 1 {
t.Errorf("Expected length 1, got %d", errs.Len())
}
if errs.ErrorOrNil() == nil {
t.Error("Non-empty Errors should return self from ErrorOrNil")
}
}
func TestErrorsMarshalJSON(t *testing.T) {
err1 := fmt.Errorf("first error")
err2 := fmt.Errorf("second error")
errs := goerr.Join(err1, err2)
data, err := json.Marshal(errs)
if err != nil {
t.Fatalf("Failed to marshal Errors: %v", err)
}
var result map[string]any
if err := json.Unmarshal(data, &result); err != nil {
t.Fatalf("Failed to unmarshal JSON: %v", err)
}
errorsArray, ok := result["errors"].([]any)
if !ok {
t.Error("Expected 'errors' field to be array")
}
if len(errorsArray) != 2 {
t.Errorf("Expected 2 errors in JSON, got %d", len(errorsArray))
}
}
func TestErrorsMarshalJSONWithFailingMarshaler(t *testing.T) {
// Create an error that implements json.Marshaler but fails
failingErr := &FailingMarshaler{msg: "failing error"}
normalErr := fmt.Errorf("normal error")
errs := goerr.Join(failingErr, normalErr)
data, err := json.Marshal(errs)
if err != nil {
t.Fatalf("Failed to marshal Errors: %v", err)
}
var result map[string]any
if err := json.Unmarshal(data, &result); err != nil {
t.Fatalf("Failed to unmarshal JSON: %v", err)
}
errorsArray, ok := result["errors"].([]any)
if !ok {
t.Error("Expected 'errors' field to be array")
}
if len(errorsArray) != 2 {
t.Errorf("Expected 2 errors in JSON, got %d", len(errorsArray))
}
// First error should fallback to string representation
if errorsArray[0] != "failing error" {
t.Errorf("Expected failing marshaler to fallback to string, got %v", errorsArray[0])
}
}
func TestErrorsLogValue(t *testing.T) {
err1 := fmt.Errorf("first error")
err2 := fmt.Errorf("second error")
errs := goerr.Join(err1, err2)
// Test that LogValue returns a group
logValue := errs.LogValue()
if logValue.Kind() != slog.KindGroup {
t.Error("LogValue should return a group")
}
// Verify the structure of the LogValue directly
groupAttrs := logValue.Group()
// Verify we have the expected attributes: count and errors
var countAttr, errorsGroupAttr *slog.Attr
for i, attr := range groupAttrs {
switch attr.Key {
case "count":
countAttr = &groupAttrs[i]
case "errors":
errorsGroupAttr = &groupAttrs[i]
}
}
// Verify count attribute
if countAttr == nil {
t.Error("Missing 'count' attribute in errors group")
} else {
count := countAttr.Value.Any()
if count != int64(2) {
t.Errorf("Expected count 2, got %v (type: %T)", count, count)
}
}
// Verify errors group attribute
if errorsGroupAttr == nil {
t.Error("Missing 'errors' group attribute")
} else if errorsGroupAttr.Value.Kind() != slog.KindGroup {
t.Error("Errors group should be a group")
} else {
// Verify the individual error entries have proper key-value structure
errorGroupAttrs := errorsGroupAttr.Value.Group()
// Should have 2 attributes: "0" and "1" (the keys), each with their error message values
if len(errorGroupAttrs) != 2 {
t.Errorf("Expected 2 attributes in errors group (key-value pairs), got %d", len(errorGroupAttrs))
// Debug: print what we got
for i, attr := range errorGroupAttrs {
t.Logf("Attr[%d]: Key='%s', Value=%v", i, attr.Key, attr.Value.Any())
}
} else {
// Verify first error (key "0")
if errorGroupAttrs[0].Key != "0" {
t.Errorf("Expected first key to be '0', got '%s'", errorGroupAttrs[0].Key)
}
if errorGroupAttrs[0].Value.Any() != "first error" {
t.Errorf("Expected first error value to be 'first error', got %v", errorGroupAttrs[0].Value.Any())
}
// Verify second error (key "1")
if errorGroupAttrs[1].Key != "1" {
t.Errorf("Expected second key to be '1', got '%s'", errorGroupAttrs[1].Key)
}
if errorGroupAttrs[1].Value.Any() != "second error" {
t.Errorf("Expected second error value to be 'second error', got %v", errorGroupAttrs[1].Value.Any())
}
}
}
// Test that the LogValue is properly structured for the slog.Group fix
// This test verifies that we're using proper key-value pairs in slog.Group
// which would have been broken before the fix
// Verify that errors group contains key-value pairs as expected
if errorsGroupAttr != nil && errorsGroupAttr.Value.Kind() == slog.KindGroup {
errorGroupAttrs := errorsGroupAttr.Value.Group()
// This is the critical test: the slog.Group should have proper key-value pairs
// Before the fix, this would have been malformed
for i, attr := range errorGroupAttrs {
// Each attribute should have a string key (the error index)
expectedKey := fmt.Sprintf("%d", i)
if attr.Key != expectedKey {
t.Errorf("Error group attribute %d should have key '%s', got '%s'", i, expectedKey, attr.Key)
}
// Each value should be a string (the error message)
if attr.Value.Kind() != slog.KindString {
t.Errorf("Error group attribute %d should have string value, got %v", i, attr.Value.Kind())
}
}
}
}
func TestErrorsLogValueWithGoerr(t *testing.T) {
// Test with goerr.Error instances to ensure LogValue is processed correctly
tag := goerr.NewTag("test-tag")
err1 := goerr.New("first goerr error", goerr.Tag(tag), goerr.Value("key1", "value1"))
err2 := goerr.New("second goerr error", goerr.Value("key2", "value2"))
errs := goerr.Join(err1, err2)
logValue := errs.LogValue()
if logValue.Kind() != slog.KindGroup {
t.Error("LogValue should return a group")
}
groupAttrs := logValue.Group()
// Find the errors group
var errorsGroupAttr *slog.Attr
for i, attr := range groupAttrs {
if attr.Key == "errors" {
errorsGroupAttr = &groupAttrs[i]
break
}
}
if errorsGroupAttr == nil {
t.Fatal("Missing 'errors' group attribute")
}
// Verify the errors group contains LogValue from goerr.Error instances
if errorsGroupAttr.Value.Kind() != slog.KindGroup {
t.Error("Errors group should be a group")
} else {
errorGroupAttrs := errorsGroupAttr.Value.Group()
// Should have 2 entries for the 2 errors
if len(errorGroupAttrs) != 2 {
t.Errorf("Expected 2 error entries, got %d", len(errorGroupAttrs))
}
// First error should be processed through LogValue (returns a group)
if len(errorGroupAttrs) >= 1 {
if errorGroupAttrs[0].Key != "0" {
t.Errorf("First error key should be '0', got '%s'", errorGroupAttrs[0].Key)
}
// Since err1 implements LogValuer, it should return a group
if errorGroupAttrs[0].Value.Kind() != slog.KindGroup {
t.Error("First error should be processed through LogValue and return a group")
}
}
// Second error should also be processed through LogValue
if len(errorGroupAttrs) >= 2 {
if errorGroupAttrs[1].Key != "1" {
t.Errorf("Second error key should be '1', got '%s'", errorGroupAttrs[1].Key)
}
// Since err2 implements LogValuer, it should return a group
if errorGroupAttrs[1].Value.Kind() != slog.KindGroup {
t.Error("Second error should be processed through LogValue and return a group")
}
}
}
}
func TestErrorsFormat(t *testing.T) {
err1 := fmt.Errorf("first error")
err2 := fmt.Errorf("second error")
errs := goerr.Join(err1, err2)
// Test %v format
result := fmt.Sprintf("%v", errs)
expected := "first error\nsecond error"
if result != expected {
t.Errorf("Expected '%s' in format, got '%s'", expected, result)
}
// Test %+v format
detailedResult := fmt.Sprintf("%+v", errs)
if !strings.Contains(detailedResult, "Errors (2)") {
t.Errorf("Expected 'Errors (2)' in detailed format, got '%s'", detailedResult)
}
}
func TestErrorsFlatten(t *testing.T) {
err1 := fmt.Errorf("error 1")
err2 := fmt.Errorf("error 2")
err3 := fmt.Errorf("error 3")
// Create nested Errors
inner := goerr.Join(err1, err2)
outer := goerr.Append(nil, inner, err3)
// Should flatten to 3 individual errors
if outer.Len() != 3 {
t.Errorf("Expected 3 flattened errors, got %d", outer.Len())
}
unwrapped := outer.Unwrap()
if len(unwrapped) != 3 {
t.Errorf("Expected 3 unwrapped errors, got %d", len(unwrapped))
}
}
func TestErrorsHasTag(t *testing.T) {
tag1 := goerr.NewTag("tag1")
tag2 := goerr.NewTag("tag2")
tag3 := goerr.NewTag("tag3")
// Create errors with tags
err1 := goerr.New("error 1", goerr.Tag(tag1))
err2 := goerr.New("error 2", goerr.Tag(tag2))
err3 := fmt.Errorf("error 3") // no tag
// Test Errors with tagged errors
errs := goerr.Join(err1, err2, err3)
if !errs.HasTag(tag1) {
t.Error("Errors should have tag1 from err1")
}
if !errs.HasTag(tag2) {
t.Error("Errors should have tag2 from err2")
}
if errs.HasTag(tag3) {
t.Error("Errors should not have tag3")
}
// Test with nil Errors
var nilErrs *goerr.Errors
if nilErrs.HasTag(tag1) {
t.Error("Nil Errors should not have any tags")
}
// Test with empty Errors
emptyErrs := goerr.Join()
if emptyErrs != nil && emptyErrs.HasTag(tag1) {
t.Error("Empty Errors should not have any tags")
}
// Test goerr.HasTag function with Errors
if !goerr.HasTag(errs, tag1) {
t.Error("goerr.HasTag should find tag1 in Errors")
}
if !goerr.HasTag(errs, tag2) {
t.Error("goerr.HasTag should find tag2 in Errors")
}
if goerr.HasTag(errs, tag3) {
t.Error("goerr.HasTag should not find tag3 in Errors")
}
}
func TestErrorsHasTagNested(t *testing.T) {
tag1 := goerr.NewTag("nested-tag")
tag2 := goerr.NewTag("outer-tag")
// Create nested structure
innerErr := goerr.New("inner error", goerr.Tag(tag1))
innerErrs := goerr.Join(innerErr)
outerErr := goerr.New("outer error", goerr.Tag(tag2))
allErrs := goerr.Join(innerErrs, outerErr)
// Should find tags from nested Errors
if !allErrs.HasTag(tag1) {
t.Error("Should find tag from nested Errors")
}
if !allErrs.HasTag(tag2) {
t.Error("Should find tag from direct error")
}
unknownTag := goerr.NewTag("unknown")
if allErrs.HasTag(unknownTag) {
t.Error("Should not find unknown tag")
}
}
// CustomError for testing errors.As
type CustomError struct {
msg string
}
func (e *CustomError) Error() string {
return e.msg
}
// FailingMarshaler for testing JSON marshaling error handling
type FailingMarshaler struct {
msg string
}
func (e *FailingMarshaler) Error() string {
return e.msg
}
func (e *FailingMarshaler) MarshalJSON() ([]byte, error) {
return nil, fmt.Errorf("marshaling failed")
}