1
16
17 package funcr
18
19 import (
20 "encoding/json"
21 "fmt"
22 "path/filepath"
23 "reflect"
24 "runtime"
25 "testing"
26
27 "github.com/go-logr/logr"
28 )
29
30
31 type substr string
32
33 func ptrint(i int) *int {
34 return &i
35 }
36 func ptrstr(s string) *string {
37 return &s
38 }
39
40
41 type point struct{ x, y int }
42
43 func (p point) MarshalText() ([]byte, error) {
44 return []byte(fmt.Sprintf("(%d, %d)", p.x, p.y)), nil
45 }
46
47
48 type pointErr struct{ x, y int }
49
50 func (p pointErr) MarshalText() ([]byte, error) {
51 return nil, fmt.Errorf("uh oh: %d, %d", p.x, p.y)
52 }
53
54
55 type Tmarshaler struct{ val string }
56
57 func (t Tmarshaler) MarshalLog() any {
58 return struct{ Inner string }{"I am a logr.Marshaler"}
59 }
60
61 func (t Tmarshaler) String() string {
62 return "String(): you should not see this"
63 }
64
65 func (t Tmarshaler) Error() string {
66 return "Error(): you should not see this"
67 }
68
69
70 type Tmarshalerpanic struct{ val string }
71
72 func (t Tmarshalerpanic) MarshalLog() any {
73 panic("Tmarshalerpanic")
74 }
75
76
77 type Tstringer struct{ val string }
78
79 func (t Tstringer) String() string {
80 return "I am a fmt.Stringer"
81 }
82
83 func (t Tstringer) Error() string {
84 return "Error(): you should not see this"
85 }
86
87
88 type Tstringerpanic struct{ val string }
89
90 func (t Tstringerpanic) String() string {
91 panic("Tstringerpanic")
92 }
93
94
95 type Terror struct{ val string }
96
97 func (t Terror) Error() string {
98 return "I am an error"
99 }
100
101
102 type Terrorpanic struct{ val string }
103
104 func (t Terrorpanic) Error() string {
105 panic("Terrorpanic")
106 }
107
108 type TjsontagsString struct {
109 String0 string `json:"-"`
110 String1 string `json:"string1"`
111 String2 string `json:"-"`
112 String3 string `json:"-,"`
113 String4 string `json:"string4,omitempty"`
114 String5 string `json:","`
115 String6 string `json:",omitempty"`
116 }
117
118 type TjsontagsBool struct {
119 Bool0 bool `json:"-"`
120 Bool1 bool `json:"bool1"`
121 Bool2 bool `json:"-"`
122 Bool3 bool `json:"-,"`
123 Bool4 bool `json:"bool4,omitempty"`
124 Bool5 bool `json:","`
125 Bool6 bool `json:",omitempty"`
126 }
127
128 type TjsontagsInt struct {
129 Int0 int `json:"-"`
130 Int1 int `json:"int1"`
131 Int2 int `json:"-"`
132 Int3 int `json:"-,"`
133 Int4 int `json:"int4,omitempty"`
134 Int5 int `json:","`
135 Int6 int `json:",omitempty"`
136 }
137
138 type TjsontagsUint struct {
139 Uint0 int `json:"-"`
140 Uint1 uint `json:"uint1"`
141 Uint2 uint `json:"-"`
142 Uint3 uint `json:"-,"`
143 Uint4 uint `json:"uint4,omitempty"`
144 Uint5 uint `json:","`
145 Uint6 uint `json:",omitempty"`
146 }
147
148 type TjsontagsFloat struct {
149 Float0 float64 `json:"-"`
150 Float1 float64 `json:"float1"`
151 Float2 float64 `json:"-"`
152 Float3 float64 `json:"-,"`
153 Float4 float64 `json:"float4,omitempty"`
154 Float5 float64 `json:","`
155 Float6 float64 `json:",omitempty"`
156 }
157
158 type TjsontagsComplex struct {
159 Complex0 complex128 `json:"-"`
160 Complex1 complex128 `json:"complex1"`
161 Complex2 complex128 `json:"-"`
162 Complex3 complex128 `json:"-,"`
163 Complex4 complex128 `json:"complex4,omitempty"`
164 Complex5 complex128 `json:","`
165 Complex6 complex128 `json:",omitempty"`
166 }
167
168 type TjsontagsPtr struct {
169 Ptr0 *string `json:"-"`
170 Ptr1 *string `json:"ptr1"`
171 Ptr2 *string `json:"-"`
172 Ptr3 *string `json:"-,"`
173 Ptr4 *string `json:"ptr4,omitempty"`
174 Ptr5 *string `json:","`
175 Ptr6 *string `json:",omitempty"`
176 }
177
178 type TjsontagsArray struct {
179 Array0 [2]string `json:"-"`
180 Array1 [2]string `json:"array1"`
181 Array2 [2]string `json:"-"`
182 Array3 [2]string `json:"-,"`
183 Array4 [2]string `json:"array4,omitempty"`
184 Array5 [2]string `json:","`
185 Array6 [2]string `json:",omitempty"`
186 }
187
188 type TjsontagsSlice struct {
189 Slice0 []string `json:"-"`
190 Slice1 []string `json:"slice1"`
191 Slice2 []string `json:"-"`
192 Slice3 []string `json:"-,"`
193 Slice4 []string `json:"slice4,omitempty"`
194 Slice5 []string `json:","`
195 Slice6 []string `json:",omitempty"`
196 }
197
198 type TjsontagsMap struct {
199 Map0 map[string]string `json:"-"`
200 Map1 map[string]string `json:"map1"`
201 Map2 map[string]string `json:"-"`
202 Map3 map[string]string `json:"-,"`
203 Map4 map[string]string `json:"map4,omitempty"`
204 Map5 map[string]string `json:","`
205 Map6 map[string]string `json:",omitempty"`
206 }
207
208 type Tinnerstruct struct {
209 Inner string
210 }
211 type Tinnerint int
212 type Tinnermap map[string]string
213 type Tinnerslice []string
214
215 type Tembedstruct struct {
216 Tinnerstruct
217 Outer string
218 }
219
220 type Tembednonstruct struct {
221 Tinnerint
222 Tinnermap
223 Tinnerslice
224 }
225
226 type Tinner1 Tinnerstruct
227 type Tinner2 Tinnerstruct
228 type Tinner3 Tinnerstruct
229 type Tinner4 Tinnerstruct
230 type Tinner5 Tinnerstruct
231 type Tinner6 Tinnerstruct
232
233 type Tembedjsontags struct {
234 Outer string
235 Tinner1 `json:"inner1"`
236 Tinner2 `json:"-"`
237 Tinner3 `json:"-,"`
238 Tinner4 `json:"inner4,omitempty"`
239 Tinner5 `json:","`
240 Tinner6 `json:"inner6,omitempty"`
241 }
242
243 type Trawjson struct {
244 Message json.RawMessage `json:"message"`
245 }
246
247 func TestPretty(t *testing.T) {
248
249 newStr := func(s string) *string {
250 return &s
251 }
252
253 cases := []struct {
254 val any
255 exp string
256 }{{
257 val: "strval",
258 }, {
259 val: "strval\nwith\t\"escapes\"",
260 }, {
261 val: substr("substrval"),
262 }, {
263 val: substr("substrval\nwith\t\"escapes\""),
264 }, {
265 val: true,
266 }, {
267 val: false,
268 }, {
269 val: int(93),
270 }, {
271 val: int8(93),
272 }, {
273 val: int16(93),
274 }, {
275 val: int32(93),
276 }, {
277 val: int64(93),
278 }, {
279 val: int(-93),
280 }, {
281 val: int8(-93),
282 }, {
283 val: int16(-93),
284 }, {
285 val: int32(-93),
286 }, {
287 val: int64(-93),
288 }, {
289 val: uint(93),
290 }, {
291 val: uint8(93),
292 }, {
293 val: uint16(93),
294 }, {
295 val: uint32(93),
296 }, {
297 val: uint64(93),
298 }, {
299 val: uintptr(93),
300 }, {
301 val: float32(93.76),
302 }, {
303 val: float64(93.76),
304 }, {
305 val: complex64(93i),
306 exp: `"(0+93i)"`,
307 }, {
308 val: complex128(93i),
309 exp: `"(0+93i)"`,
310 }, {
311 val: ptrint(93),
312 }, {
313 val: ptrstr("pstrval"),
314 }, {
315 val: []int{},
316 }, {
317 val: []int(nil),
318 exp: `[]`,
319 }, {
320 val: []int{9, 3, 7, 6},
321 }, {
322 val: []string{"str", "with\tescape"},
323 }, {
324 val: []substr{"substr", "with\tescape"},
325 }, {
326 val: [4]int{9, 3, 7, 6},
327 }, {
328 val: [2]string{"str", "with\tescape"},
329 }, {
330 val: [2]substr{"substr", "with\tescape"},
331 }, {
332 val: struct {
333 Int int
334 notExported string
335 String string
336 }{
337 93, "you should not see this", "seventy-six",
338 },
339 }, {
340 val: map[string]int{},
341 }, {
342 val: map[string]int(nil),
343 exp: `{}`,
344 }, {
345 val: map[string]int{
346 "nine": 3,
347 },
348 }, {
349 val: map[string]int{
350 "with\tescape": 76,
351 },
352 }, {
353 val: map[substr]int{
354 "nine": 3,
355 },
356 }, {
357 val: map[substr]int{
358 "with\tescape": 76,
359 },
360 }, {
361 val: map[int]int{
362 9: 3,
363 },
364 }, {
365 val: map[float64]int{
366 9.5: 3,
367 },
368 exp: `{"9.5":3}`,
369 }, {
370 val: map[point]int{
371 {x: 1, y: 2}: 3,
372 },
373 }, {
374 val: map[pointErr]int{
375 {x: 1, y: 2}: 3,
376 },
377 exp: `{"<error-MarshalText: uh oh: 1, 2>":3}`,
378 }, {
379 val: struct {
380 X int `json:"x"`
381 Y int `json:"y"`
382 }{
383 93, 76,
384 },
385 }, {
386 val: struct {
387 X []int
388 Y map[int]int
389 Z struct{ P, Q int }
390 }{
391 []int{9, 3, 7, 6},
392 map[int]int{9: 3},
393 struct{ P, Q int }{9, 3},
394 },
395 }, {
396 val: []struct{ X, Y string }{
397 {"nine", "three"},
398 {"seven", "six"},
399 {"with\t", "\tescapes"},
400 },
401 }, {
402 val: struct {
403 A *int
404 B *int
405 C any
406 D any
407 }{
408 B: ptrint(1),
409 D: any(2),
410 },
411 }, {
412 val: Tmarshaler{"foobar"},
413 exp: `{"Inner":"I am a logr.Marshaler"}`,
414 }, {
415 val: &Tmarshaler{"foobar"},
416 exp: `{"Inner":"I am a logr.Marshaler"}`,
417 }, {
418 val: (*Tmarshaler)(nil),
419 exp: `"<panic: value method github.com/go-logr/logr/funcr.Tmarshaler.MarshalLog called using nil *Tmarshaler pointer>"`,
420 }, {
421 val: Tmarshalerpanic{"foobar"},
422 exp: `"<panic: Tmarshalerpanic>"`,
423 }, {
424 val: Tstringer{"foobar"},
425 exp: `"I am a fmt.Stringer"`,
426 }, {
427 val: &Tstringer{"foobar"},
428 exp: `"I am a fmt.Stringer"`,
429 }, {
430 val: (*Tstringer)(nil),
431 exp: `"<panic: value method github.com/go-logr/logr/funcr.Tstringer.String called using nil *Tstringer pointer>"`,
432 }, {
433 val: Tstringerpanic{"foobar"},
434 exp: `"<panic: Tstringerpanic>"`,
435 }, {
436 val: Terror{"foobar"},
437 exp: `"I am an error"`,
438 }, {
439 val: &Terror{"foobar"},
440 exp: `"I am an error"`,
441 }, {
442 val: (*Terror)(nil),
443 exp: `"<panic: value method github.com/go-logr/logr/funcr.Terror.Error called using nil *Terror pointer>"`,
444 }, {
445 val: Terrorpanic{"foobar"},
446 exp: `"<panic: Terrorpanic>"`,
447 }, {
448 val: TjsontagsString{
449 String1: "v1",
450 String2: "v2",
451 String3: "v3",
452 String4: "v4",
453 String5: "v5",
454 String6: "v6",
455 },
456 }, {
457 val: TjsontagsString{},
458 }, {
459 val: TjsontagsBool{
460 Bool1: true,
461 Bool2: true,
462 Bool3: true,
463 Bool4: true,
464 Bool5: true,
465 Bool6: true,
466 },
467 }, {
468 val: TjsontagsBool{},
469 }, {
470 val: TjsontagsInt{
471 Int1: 1,
472 Int2: 2,
473 Int3: 3,
474 Int4: 4,
475 Int5: 5,
476 Int6: 6,
477 },
478 }, {
479 val: TjsontagsInt{},
480 }, {
481 val: TjsontagsUint{
482 Uint1: 1,
483 Uint2: 2,
484 Uint3: 3,
485 Uint4: 4,
486 Uint5: 5,
487 Uint6: 6,
488 },
489 }, {
490 val: TjsontagsUint{},
491 }, {
492 val: TjsontagsFloat{
493 Float1: 1.1,
494 Float2: 2.2,
495 Float3: 3.3,
496 Float4: 4.4,
497 Float5: 5.5,
498 Float6: 6.6,
499 },
500 }, {
501 val: TjsontagsFloat{},
502 }, {
503 val: TjsontagsComplex{
504 Complex1: 1i,
505 Complex2: 2i,
506 Complex3: 3i,
507 Complex4: 4i,
508 Complex5: 5i,
509 Complex6: 6i,
510 },
511 exp: `{"complex1":"(0+1i)","-":"(0+3i)","complex4":"(0+4i)","Complex5":"(0+5i)","Complex6":"(0+6i)"}`,
512 }, {
513 val: TjsontagsComplex{},
514 exp: `{"complex1":"(0+0i)","-":"(0+0i)","Complex5":"(0+0i)"}`,
515 }, {
516 val: TjsontagsPtr{
517 Ptr1: newStr("1"),
518 Ptr2: newStr("2"),
519 Ptr3: newStr("3"),
520 Ptr4: newStr("4"),
521 Ptr5: newStr("5"),
522 Ptr6: newStr("6"),
523 },
524 }, {
525 val: TjsontagsPtr{},
526 }, {
527 val: TjsontagsArray{
528 Array1: [2]string{"v1", "v1"},
529 Array2: [2]string{"v2", "v2"},
530 Array3: [2]string{"v3", "v3"},
531 Array4: [2]string{"v4", "v4"},
532 Array5: [2]string{"v5", "v5"},
533 Array6: [2]string{"v6", "v6"},
534 },
535 }, {
536 val: TjsontagsArray{},
537 }, {
538 val: TjsontagsSlice{
539 Slice1: []string{"v1", "v1"},
540 Slice2: []string{"v2", "v2"},
541 Slice3: []string{"v3", "v3"},
542 Slice4: []string{"v4", "v4"},
543 Slice5: []string{"v5", "v5"},
544 Slice6: []string{"v6", "v6"},
545 },
546 }, {
547 val: TjsontagsSlice{},
548 exp: `{"slice1":[],"-":[],"Slice5":[]}`,
549 }, {
550 val: TjsontagsMap{
551 Map1: map[string]string{"k1": "v1"},
552 Map2: map[string]string{"k2": "v2"},
553 Map3: map[string]string{"k3": "v3"},
554 Map4: map[string]string{"k4": "v4"},
555 Map5: map[string]string{"k5": "v5"},
556 Map6: map[string]string{"k6": "v6"},
557 },
558 }, {
559 val: TjsontagsMap{},
560 exp: `{"map1":{},"-":{},"Map5":{}}`,
561 }, {
562 val: Tembedstruct{},
563 }, {
564 val: Tembednonstruct{},
565 exp: `{"Tinnerint":0,"Tinnermap":{},"Tinnerslice":[]}`,
566 }, {
567 val: Tembedjsontags{},
568 }, {
569 val: PseudoStruct(makeKV("f1", 1, "f2", true, "f3", []int{})),
570 exp: `{"f1":1,"f2":true,"f3":[]}`,
571 }, {
572 val: map[TjsontagsString]int{
573 {String1: `"quoted"`, String4: `unquoted`}: 1,
574 },
575 exp: `{"{\"string1\":\"\\\"quoted\\\"\",\"-\":\"\",\"string4\":\"unquoted\",\"String5\":\"\"}":1}`,
576 }, {
577 val: map[TjsontagsInt]int{
578 {Int1: 1, Int2: 2}: 3,
579 },
580 exp: `{"{\"int1\":1,\"-\":0,\"Int5\":0}":3}`,
581 }, {
582 val: map[[2]struct{ S string }]int{
583 {{S: `"quoted"`}, {S: "unquoted"}}: 1,
584 },
585 exp: `{"[{\"S\":\"\\\"quoted\\\"\"},{\"S\":\"unquoted\"}]":1}`,
586 }, {
587 val: TjsontagsComplex{},
588 exp: `{"complex1":"(0+0i)","-":"(0+0i)","Complex5":"(0+0i)"}`,
589 }, {
590 val: TjsontagsPtr{
591 Ptr1: newStr("1"),
592 Ptr2: newStr("2"),
593 Ptr3: newStr("3"),
594 Ptr4: newStr("4"),
595 Ptr5: newStr("5"),
596 Ptr6: newStr("6"),
597 },
598 }, {
599 val: TjsontagsPtr{},
600 }, {
601 val: TjsontagsArray{
602 Array1: [2]string{"v1", "v1"},
603 Array2: [2]string{"v2", "v2"},
604 Array3: [2]string{"v3", "v3"},
605 Array4: [2]string{"v4", "v4"},
606 Array5: [2]string{"v5", "v5"},
607 Array6: [2]string{"v6", "v6"},
608 },
609 }, {
610 val: TjsontagsArray{},
611 }, {
612 val: TjsontagsSlice{
613 Slice1: []string{"v1", "v1"},
614 Slice2: []string{"v2", "v2"},
615 Slice3: []string{"v3", "v3"},
616 Slice4: []string{"v4", "v4"},
617 Slice5: []string{"v5", "v5"},
618 Slice6: []string{"v6", "v6"},
619 },
620 }, {
621 val: TjsontagsSlice{},
622 exp: `{"slice1":[],"-":[],"Slice5":[]}`,
623 }, {
624 val: TjsontagsMap{
625 Map1: map[string]string{"k1": "v1"},
626 Map2: map[string]string{"k2": "v2"},
627 Map3: map[string]string{"k3": "v3"},
628 Map4: map[string]string{"k4": "v4"},
629 Map5: map[string]string{"k5": "v5"},
630 Map6: map[string]string{"k6": "v6"},
631 },
632 }, {
633 val: TjsontagsMap{},
634 exp: `{"map1":{},"-":{},"Map5":{}}`,
635 }, {
636 val: Tembedstruct{},
637 }, {
638 val: Tembednonstruct{},
639 exp: `{"Tinnerint":0,"Tinnermap":{},"Tinnerslice":[]}`,
640 }, {
641 val: Tembedjsontags{},
642 }, {
643 val: PseudoStruct(makeKV("f1", 1, "f2", true, "f3", []int{})),
644 exp: `{"f1":1,"f2":true,"f3":[]}`,
645 }, {
646 val: map[TjsontagsString]int{
647 {String1: `"quoted"`, String4: `unquoted`}: 1,
648 },
649 exp: `{"{\"string1\":\"\\\"quoted\\\"\",\"-\":\"\",\"string4\":\"unquoted\",\"String5\":\"\"}":1}`,
650 }, {
651 val: map[TjsontagsInt]int{
652 {Int1: 1, Int2: 2}: 3,
653 },
654 exp: `{"{\"int1\":1,\"-\":0,\"Int5\":0}":3}`,
655 }, {
656 val: map[[2]struct{ S string }]int{
657 {{S: `"quoted"`}, {S: "unquoted"}}: 1,
658 },
659 exp: `{"[{\"S\":\"\\\"quoted\\\"\"},{\"S\":\"unquoted\"}]":1}`,
660 }}
661
662 f := NewFormatterJSON(Options{})
663 for i, tc := range cases {
664 ours := f.pretty(tc.val)
665 want := ""
666 if tc.exp != "" {
667 want = tc.exp
668 } else {
669 jb, err := json.Marshal(tc.val)
670 if err != nil {
671 t.Fatalf("[%d]: unexpected error: %v\ngot: %q", i, err, ours)
672 }
673 want = string(jb)
674 }
675 if ours != want {
676 t.Errorf("[%d]:\n\texpected %q\n\tgot %q", i, want, ours)
677 }
678 }
679 }
680
681 func makeKV(args ...any) []any {
682 return args
683 }
684
685 func TestRender(t *testing.T) {
686
687 raw := &Trawjson{}
688 marshal := &TjsontagsInt{}
689 var err error
690 raw.Message, err = json.Marshal(marshal)
691 if err != nil {
692 t.Fatalf("json.Marshal error: %v", err)
693 }
694
695 testCases := []struct {
696 name string
697 builtins []any
698 values []any
699 args []any
700 expectKV string
701 expectJSON string
702 }{{
703 name: "nil",
704 expectKV: "",
705 expectJSON: "{}",
706 }, {
707 name: "empty",
708 builtins: []any{},
709 values: []any{},
710 args: []any{},
711 expectKV: "",
712 expectJSON: "{}",
713 }, {
714 name: "primitives",
715 builtins: makeKV("int1", 1, "int2", 2),
716 values: makeKV("str1", "ABC", "str2", "DEF"),
717 args: makeKV("bool1", true, "bool2", false),
718 expectKV: `"int1"=1 "int2"=2 "str1"="ABC" "str2"="DEF" "bool1"=true "bool2"=false`,
719 expectJSON: `{"int1":1,"int2":2,"str1":"ABC","str2":"DEF","bool1":true,"bool2":false}`,
720 }, {
721 name: "pseudo structs",
722 builtins: makeKV("int", PseudoStruct(makeKV("intsub", 1))),
723 values: makeKV("str", PseudoStruct(makeKV("strsub", "2"))),
724 args: makeKV("bool", PseudoStruct(makeKV("boolsub", true))),
725 expectKV: `"int"={"intsub"=1} "str"={"strsub"="2"} "bool"={"boolsub"=true}`,
726 expectJSON: `{"int":{"intsub":1},"str":{"strsub":"2"},"bool":{"boolsub":true}}`,
727 }, {
728 name: "escapes",
729 builtins: makeKV("\"1\"", 1),
730 values: makeKV("\tstr", "ABC"),
731 args: makeKV("bool\n", true),
732 expectKV: `""1""=1 "\tstr"="ABC" "bool\n"=true`,
733 expectJSON: `{""1"":1,"\tstr":"ABC","bool\n":true}`,
734 }, {
735 name: "missing value",
736 builtins: makeKV("builtin"),
737 values: makeKV("value"),
738 args: makeKV("arg"),
739 expectKV: `"builtin"="<no-value>" "value"="<no-value>" "arg"="<no-value>"`,
740 expectJSON: `{"builtin":"<no-value>","value":"<no-value>","arg":"<no-value>"}`,
741 }, {
742 name: "non-string key int",
743 builtins: makeKV(123, "val"),
744 values: makeKV(456, "val"),
745 args: makeKV(789, "val"),
746 expectKV: `"<non-string-key: 123>"="val" "<non-string-key: 456>"="val" "<non-string-key: 789>"="val"`,
747 expectJSON: `{"<non-string-key: 123>":"val","<non-string-key: 456>":"val","<non-string-key: 789>":"val"}`,
748 }, {
749 name: "non-string key struct",
750 builtins: makeKV(struct {
751 F1 string
752 F2 int
753 }{"builtin", 123}, "val"),
754 values: makeKV(struct {
755 F1 string
756 F2 int
757 }{"value", 456}, "val"),
758 args: makeKV(struct {
759 F1 string
760 F2 int
761 }{"arg", 789}, "val"),
762 expectKV: `"<non-string-key: {"F1"="builtin" >"="val" "<non-string-key: {\"F1\"=\"value\" \"F>"="val" "<non-string-key: {\"F1\"=\"arg\" \"F2\">"="val"`,
763 expectJSON: `{"<non-string-key: {"F1":"builtin",>":"val","<non-string-key: {\"F1\":\"value\",\"F>":"val","<non-string-key: {\"F1\":\"arg\",\"F2\">":"val"}`,
764 }, {
765 name: "json rendering with json.RawMessage",
766 args: makeKV("key", raw),
767 expectKV: `"key"={"message"=[123 34 105 110 116 49 34 58 48 44 34 45 34 58 48 44 34 73 110 116 53 34 58 48 125]}`,
768 expectJSON: `{"key":{"message":{"int1":0,"-":0,"Int5":0}}}`,
769 }, {
770 name: "byte array not json.RawMessage",
771 args: makeKV("key", []byte{1, 2, 3, 4}),
772 expectKV: `"key"=[1 2 3 4]`,
773 expectJSON: `{"key":[1,2,3,4]}`,
774 }, {
775 name: "json rendering with empty json.RawMessage",
776 args: makeKV("key", &Trawjson{}),
777 expectKV: `"key"={"message"=[]}`,
778 expectJSON: `{"key":{"message":null}}`,
779 }}
780
781 for _, tc := range testCases {
782 t.Run(tc.name, func(t *testing.T) {
783 test := func(t *testing.T, formatter Formatter, expect string) {
784 formatter.AddValues(tc.values)
785 r := formatter.render(tc.builtins, tc.args)
786 if r != expect {
787 t.Errorf("wrong output:\nexpected %q\n got %q", expect, r)
788 }
789 }
790 t.Run("KV", func(t *testing.T) {
791 test(t, NewFormatter(Options{}), tc.expectKV)
792 })
793 t.Run("JSON", func(t *testing.T) {
794 test(t, NewFormatterJSON(Options{}), tc.expectJSON)
795 })
796 })
797 }
798 }
799
800 func TestSanitize(t *testing.T) {
801 testCases := []struct {
802 name string
803 kv []any
804 expect []any
805 }{{
806 name: "empty",
807 kv: []any{},
808 expect: []any{},
809 }, {
810 name: "already sane",
811 kv: makeKV("int", 1, "str", "ABC", "bool", true),
812 expect: makeKV("int", 1, "str", "ABC", "bool", true),
813 }, {
814 name: "missing value",
815 kv: makeKV("key"),
816 expect: makeKV("key", "<no-value>"),
817 }, {
818 name: "non-string key int",
819 kv: makeKV(123, "val"),
820 expect: makeKV("<non-string-key: 123>", "val"),
821 }, {
822 name: "non-string key struct",
823 kv: makeKV(struct {
824 F1 string
825 F2 int
826 }{"f1", 8675309}, "val"),
827 expect: makeKV(`<non-string-key: {"F1":"f1","F2":>`, "val"),
828 }}
829
830 f := NewFormatterJSON(Options{})
831 for _, tc := range testCases {
832 t.Run(tc.name, func(t *testing.T) {
833 r := f.sanitize(tc.kv)
834 if !reflect.DeepEqual(r, tc.expect) {
835 t.Errorf("wrong output:\nexpected %q\n got %q", tc.expect, r)
836 }
837 })
838 }
839 }
840
841 func TestEnabled(t *testing.T) {
842 t.Run("default V", func(t *testing.T) {
843 log := newSink(func(_, _ string) {}, NewFormatter(Options{}))
844 if !log.Enabled(0) {
845 t.Errorf("expected true")
846 }
847 if log.Enabled(1) {
848 t.Errorf("expected false")
849 }
850 })
851 t.Run("V=9", func(t *testing.T) {
852 log := newSink(func(_, _ string) {}, NewFormatter(Options{Verbosity: 9}))
853 if !log.Enabled(8) {
854 t.Errorf("expected true")
855 }
856 if !log.Enabled(9) {
857 t.Errorf("expected true")
858 }
859 if log.Enabled(10) {
860 t.Errorf("expected false")
861 }
862 })
863 }
864
865 type capture struct {
866 log string
867 }
868
869 func (c *capture) Func(prefix, args string) {
870 space := " "
871 if len(prefix) == 0 {
872 space = ""
873 }
874 c.log = prefix + space + args
875 }
876
877 func TestInfo(t *testing.T) {
878 testCases := []struct {
879 name string
880 args []any
881 expectKV string
882 expectJSON string
883 }{{
884 name: "just msg",
885 args: makeKV(),
886 expectKV: `"level"=0 "msg"="msg"`,
887 expectJSON: `{"logger":"","level":0,"msg":"msg"}`,
888 }, {
889 name: "primitives",
890 args: makeKV("int", 1, "str", "ABC", "bool", true),
891 expectKV: `"level"=0 "msg"="msg" "int"=1 "str"="ABC" "bool"=true`,
892 expectJSON: `{"logger":"","level":0,"msg":"msg","int":1,"str":"ABC","bool":true}`,
893 }}
894
895 for _, tc := range testCases {
896 t.Run("KV: "+tc.name, func(t *testing.T) {
897 capt := &capture{}
898 sink := newSink(capt.Func, NewFormatter(Options{}))
899 sink.Info(0, "msg", tc.args...)
900 if capt.log != tc.expectKV {
901 t.Errorf("\nexpected %q\n got %q", tc.expectKV, capt.log)
902 }
903 })
904 t.Run("JSON: "+tc.name, func(t *testing.T) {
905 capt := &capture{}
906 sink := newSink(capt.Func, NewFormatterJSON(Options{}))
907 sink.Info(0, "msg", tc.args...)
908 if capt.log != tc.expectJSON {
909 t.Errorf("\nexpected %q\n got %q", tc.expectJSON, capt.log)
910 }
911 })
912 }
913 }
914
915 func TestInfoWithCaller(t *testing.T) {
916 t.Run("KV: LogCaller=All", func(t *testing.T) {
917 capt := &capture{}
918 sink := newSink(capt.Func, NewFormatter(Options{LogCaller: All}))
919 sink.Info(0, "msg")
920 _, file, line, _ := runtime.Caller(0)
921 expect := fmt.Sprintf(`"caller"={"file"=%q "line"=%d} "level"=0 "msg"="msg"`, filepath.Base(file), line-1)
922 if capt.log != expect {
923 t.Errorf("\nexpected %q\n got %q", expect, capt.log)
924 }
925 sink.Error(fmt.Errorf("error"), "msg")
926 _, file, line, _ = runtime.Caller(0)
927 expect = fmt.Sprintf(`"caller"={"file"=%q "line"=%d} "msg"="msg" "error"="error"`, filepath.Base(file), line-1)
928 if capt.log != expect {
929 t.Errorf("\nexpected %q\n got %q", expect, capt.log)
930 }
931 })
932 t.Run("JSON: LogCaller=All", func(t *testing.T) {
933 capt := &capture{}
934 sink := newSink(capt.Func, NewFormatterJSON(Options{LogCaller: All}))
935 sink.Info(0, "msg")
936 _, file, line, _ := runtime.Caller(0)
937 expect := fmt.Sprintf(`{"logger":"","caller":{"file":%q,"line":%d},"level":0,"msg":"msg"}`, filepath.Base(file), line-1)
938 if capt.log != expect {
939 t.Errorf("\nexpected %q\n got %q", expect, capt.log)
940 }
941 sink.Error(fmt.Errorf("error"), "msg")
942 _, file, line, _ = runtime.Caller(0)
943 expect = fmt.Sprintf(`{"logger":"","caller":{"file":%q,"line":%d},"msg":"msg","error":"error"}`, filepath.Base(file), line-1)
944 if capt.log != expect {
945 t.Errorf("\nexpected %q\n got %q", expect, capt.log)
946 }
947 })
948 t.Run("KV: LogCaller=All, LogCallerFunc=true", func(t *testing.T) {
949 thisFunc := "github.com/go-logr/logr/funcr.TestInfoWithCaller.func3"
950 capt := &capture{}
951 sink := newSink(capt.Func, NewFormatter(Options{LogCaller: All, LogCallerFunc: true}))
952 sink.Info(0, "msg")
953 _, file, line, _ := runtime.Caller(0)
954 expect := fmt.Sprintf(`"caller"={"file"=%q "line"=%d "function"=%q} "level"=0 "msg"="msg"`, filepath.Base(file), line-1, thisFunc)
955 if capt.log != expect {
956 t.Errorf("\nexpected %q\n got %q", expect, capt.log)
957 }
958 sink.Error(fmt.Errorf("error"), "msg")
959 _, file, line, _ = runtime.Caller(0)
960 expect = fmt.Sprintf(`"caller"={"file"=%q "line"=%d "function"=%q} "msg"="msg" "error"="error"`, filepath.Base(file), line-1, thisFunc)
961 if capt.log != expect {
962 t.Errorf("\nexpected %q\n got %q", expect, capt.log)
963 }
964 })
965 t.Run("JSON: LogCaller=All, LogCallerFunc=true", func(t *testing.T) {
966 thisFunc := "github.com/go-logr/logr/funcr.TestInfoWithCaller.func4"
967 capt := &capture{}
968 sink := newSink(capt.Func, NewFormatterJSON(Options{LogCaller: All, LogCallerFunc: true}))
969 sink.Info(0, "msg")
970 _, file, line, _ := runtime.Caller(0)
971 expect := fmt.Sprintf(`{"logger":"","caller":{"file":%q,"line":%d,"function":%q},"level":0,"msg":"msg"}`, filepath.Base(file), line-1, thisFunc)
972 if capt.log != expect {
973 t.Errorf("\nexpected %q\n got %q", expect, capt.log)
974 }
975 sink.Error(fmt.Errorf("error"), "msg")
976 _, file, line, _ = runtime.Caller(0)
977 expect = fmt.Sprintf(`{"logger":"","caller":{"file":%q,"line":%d,"function":%q},"msg":"msg","error":"error"}`, filepath.Base(file), line-1, thisFunc)
978 if capt.log != expect {
979 t.Errorf("\nexpected %q\n got %q", expect, capt.log)
980 }
981 })
982 t.Run("LogCaller=Info", func(t *testing.T) {
983 capt := &capture{}
984 sink := newSink(capt.Func, NewFormatter(Options{LogCaller: Info}))
985 sink.Info(0, "msg")
986 _, file, line, _ := runtime.Caller(0)
987 expect := fmt.Sprintf(`"caller"={"file"=%q "line"=%d} "level"=0 "msg"="msg"`, filepath.Base(file), line-1)
988 if capt.log != expect {
989 t.Errorf("\nexpected %q\n got %q", expect, capt.log)
990 }
991 sink.Error(fmt.Errorf("error"), "msg")
992 expect = `"msg"="msg" "error"="error"`
993 if capt.log != expect {
994 t.Errorf("\nexpected %q\n got %q", expect, capt.log)
995 }
996 })
997 t.Run("LogCaller=Error", func(t *testing.T) {
998 capt := &capture{}
999 sink := newSink(capt.Func, NewFormatter(Options{LogCaller: Error}))
1000 sink.Info(0, "msg")
1001 expect := `"level"=0 "msg"="msg"`
1002 if capt.log != expect {
1003 t.Errorf("\nexpected %q\n got %q", expect, capt.log)
1004 }
1005 sink.Error(fmt.Errorf("error"), "msg")
1006 _, file, line, _ := runtime.Caller(0)
1007 expect = fmt.Sprintf(`"caller"={"file"=%q "line"=%d} "msg"="msg" "error"="error"`, filepath.Base(file), line-1)
1008 if capt.log != expect {
1009 t.Errorf("\nexpected %q\n got %q", expect, capt.log)
1010 }
1011 })
1012 t.Run("LogCaller=None", func(t *testing.T) {
1013 capt := &capture{}
1014 sink := newSink(capt.Func, NewFormatter(Options{LogCaller: None}))
1015 sink.Info(0, "msg")
1016 expect := `"level"=0 "msg"="msg"`
1017 if capt.log != expect {
1018 t.Errorf("\nexpected %q\n got %q", expect, capt.log)
1019 }
1020 sink.Error(fmt.Errorf("error"), "msg")
1021 expect = `"msg"="msg" "error"="error"`
1022 if capt.log != expect {
1023 t.Errorf("\nexpected %q\n got %q", expect, capt.log)
1024 }
1025 })
1026 }
1027
1028 func TestError(t *testing.T) {
1029 testCases := []struct {
1030 name string
1031 args []any
1032 expectKV string
1033 expectJSON string
1034 }{{
1035 name: "just msg",
1036 args: makeKV(),
1037 expectKV: `"msg"="msg" "error"="err"`,
1038 expectJSON: `{"logger":"","msg":"msg","error":"err"}`,
1039 }, {
1040 name: "primitives",
1041 args: makeKV("int", 1, "str", "ABC", "bool", true),
1042 expectKV: `"msg"="msg" "error"="err" "int"=1 "str"="ABC" "bool"=true`,
1043 expectJSON: `{"logger":"","msg":"msg","error":"err","int":1,"str":"ABC","bool":true}`,
1044 }}
1045
1046 for _, tc := range testCases {
1047 t.Run("KV: "+tc.name, func(t *testing.T) {
1048 capt := &capture{}
1049 sink := newSink(capt.Func, NewFormatter(Options{}))
1050 sink.Error(fmt.Errorf("err"), "msg", tc.args...)
1051 if capt.log != tc.expectKV {
1052 t.Errorf("\nexpected %q\n got %q", tc.expectKV, capt.log)
1053 }
1054 })
1055 t.Run("JSON: "+tc.name, func(t *testing.T) {
1056 capt := &capture{}
1057 sink := newSink(capt.Func, NewFormatterJSON(Options{}))
1058 sink.Error(fmt.Errorf("err"), "msg", tc.args...)
1059 if capt.log != tc.expectJSON {
1060 t.Errorf("\nexpected %q\n got %q", tc.expectJSON, capt.log)
1061 }
1062 })
1063 }
1064 }
1065
1066 func TestErrorWithCaller(t *testing.T) {
1067 t.Run("KV: LogCaller=All", func(t *testing.T) {
1068 capt := &capture{}
1069 sink := newSink(capt.Func, NewFormatter(Options{LogCaller: All}))
1070 sink.Error(fmt.Errorf("err"), "msg")
1071 _, file, line, _ := runtime.Caller(0)
1072 expect := fmt.Sprintf(`"caller"={"file"=%q "line"=%d} "msg"="msg" "error"="err"`, filepath.Base(file), line-1)
1073 if capt.log != expect {
1074 t.Errorf("\nexpected %q\n got %q", expect, capt.log)
1075 }
1076 })
1077 t.Run("JSON: LogCaller=All", func(t *testing.T) {
1078 capt := &capture{}
1079 sink := newSink(capt.Func, NewFormatterJSON(Options{LogCaller: All}))
1080 sink.Error(fmt.Errorf("err"), "msg")
1081 _, file, line, _ := runtime.Caller(0)
1082 expect := fmt.Sprintf(`{"logger":"","caller":{"file":%q,"line":%d},"msg":"msg","error":"err"}`, filepath.Base(file), line-1)
1083 if capt.log != expect {
1084 t.Errorf("\nexpected %q\n got %q", expect, capt.log)
1085 }
1086 })
1087 t.Run("LogCaller=Error", func(t *testing.T) {
1088 capt := &capture{}
1089 sink := newSink(capt.Func, NewFormatter(Options{LogCaller: Error}))
1090 sink.Error(fmt.Errorf("err"), "msg")
1091 _, file, line, _ := runtime.Caller(0)
1092 expect := fmt.Sprintf(`"caller"={"file"=%q "line"=%d} "msg"="msg" "error"="err"`, filepath.Base(file), line-1)
1093 if capt.log != expect {
1094 t.Errorf("\nexpected %q\n got %q", expect, capt.log)
1095 }
1096 })
1097 t.Run("LogCaller=Info", func(t *testing.T) {
1098 capt := &capture{}
1099 sink := newSink(capt.Func, NewFormatter(Options{LogCaller: Info}))
1100 sink.Error(fmt.Errorf("err"), "msg")
1101 expect := `"msg"="msg" "error"="err"`
1102 if capt.log != expect {
1103 t.Errorf("\nexpected %q\n got %q", expect, capt.log)
1104 }
1105 })
1106 t.Run("LogCaller=None", func(t *testing.T) {
1107 capt := &capture{}
1108 sink := newSink(capt.Func, NewFormatter(Options{LogCaller: None}))
1109 sink.Error(fmt.Errorf("err"), "msg")
1110 expect := `"msg"="msg" "error"="err"`
1111 if capt.log != expect {
1112 t.Errorf("\nexpected %q\n got %q", expect, capt.log)
1113 }
1114 })
1115 }
1116
1117 func TestInfoWithName(t *testing.T) {
1118 testCases := []struct {
1119 name string
1120 names []string
1121 args []any
1122 expectKV string
1123 expectJSON string
1124 }{{
1125 name: "one",
1126 names: []string{"pfx1"},
1127 args: makeKV("k", "v"),
1128 expectKV: `pfx1 "level"=0 "msg"="msg" "k"="v"`,
1129 expectJSON: `{"logger":"pfx1","level":0,"msg":"msg","k":"v"}`,
1130 }, {
1131 name: "two",
1132 names: []string{"pfx1", "pfx2"},
1133 args: makeKV("k", "v"),
1134 expectKV: `pfx1/pfx2 "level"=0 "msg"="msg" "k"="v"`,
1135 expectJSON: `{"logger":"pfx1/pfx2","level":0,"msg":"msg","k":"v"}`,
1136 }}
1137
1138 for _, tc := range testCases {
1139 t.Run("KV: "+tc.name, func(t *testing.T) {
1140 capt := &capture{}
1141 sink := newSink(capt.Func, NewFormatter(Options{}))
1142 for _, n := range tc.names {
1143 sink = sink.WithName(n)
1144 }
1145 sink.Info(0, "msg", tc.args...)
1146 if capt.log != tc.expectKV {
1147 t.Errorf("\nexpected %q\n got %q", tc.expectKV, capt.log)
1148 }
1149 })
1150 t.Run("JSON: "+tc.name, func(t *testing.T) {
1151 capt := &capture{}
1152 sink := newSink(capt.Func, NewFormatterJSON(Options{}))
1153 for _, n := range tc.names {
1154 sink = sink.WithName(n)
1155 }
1156 sink.Info(0, "msg", tc.args...)
1157 if capt.log != tc.expectJSON {
1158 t.Errorf("\nexpected %q\n got %q", tc.expectJSON, capt.log)
1159 }
1160 })
1161 }
1162 }
1163
1164 func TestErrorWithName(t *testing.T) {
1165 testCases := []struct {
1166 name string
1167 names []string
1168 args []any
1169 expectKV string
1170 expectJSON string
1171 }{{
1172 name: "one",
1173 names: []string{"pfx1"},
1174 args: makeKV("k", "v"),
1175 expectKV: `pfx1 "msg"="msg" "error"="err" "k"="v"`,
1176 expectJSON: `{"logger":"pfx1","msg":"msg","error":"err","k":"v"}`,
1177 }, {
1178 name: "two",
1179 names: []string{"pfx1", "pfx2"},
1180 args: makeKV("k", "v"),
1181 expectKV: `pfx1/pfx2 "msg"="msg" "error"="err" "k"="v"`,
1182 expectJSON: `{"logger":"pfx1/pfx2","msg":"msg","error":"err","k":"v"}`,
1183 }}
1184
1185 for _, tc := range testCases {
1186 t.Run("KV: "+tc.name, func(t *testing.T) {
1187 capt := &capture{}
1188 sink := newSink(capt.Func, NewFormatter(Options{}))
1189 for _, n := range tc.names {
1190 sink = sink.WithName(n)
1191 }
1192 sink.Error(fmt.Errorf("err"), "msg", tc.args...)
1193 if capt.log != tc.expectKV {
1194 t.Errorf("\nexpected %q\n got %q", tc.expectKV, capt.log)
1195 }
1196 })
1197 t.Run("JSON: "+tc.name, func(t *testing.T) {
1198 capt := &capture{}
1199 sink := newSink(capt.Func, NewFormatterJSON(Options{}))
1200 for _, n := range tc.names {
1201 sink = sink.WithName(n)
1202 }
1203 sink.Error(fmt.Errorf("err"), "msg", tc.args...)
1204 if capt.log != tc.expectJSON {
1205 t.Errorf("\nexpected %q\n got %q", tc.expectJSON, capt.log)
1206 }
1207 })
1208 }
1209 }
1210
1211 func TestInfoWithValues(t *testing.T) {
1212 testCases := []struct {
1213 name string
1214 values []any
1215 args []any
1216 expectKV string
1217 expectJSON string
1218 }{{
1219 name: "zero",
1220 values: makeKV(),
1221 args: makeKV("k", "v"),
1222 expectKV: `"level"=0 "msg"="msg" "k"="v"`,
1223 expectJSON: `{"logger":"","level":0,"msg":"msg","k":"v"}`,
1224 }, {
1225 name: "one",
1226 values: makeKV("one", 1),
1227 args: makeKV("k", "v"),
1228 expectKV: `"level"=0 "msg"="msg" "one"=1 "k"="v"`,
1229 expectJSON: `{"logger":"","level":0,"msg":"msg","one":1,"k":"v"}`,
1230 }, {
1231 name: "two",
1232 values: makeKV("one", 1, "two", 2),
1233 args: makeKV("k", "v"),
1234 expectKV: `"level"=0 "msg"="msg" "one"=1 "two"=2 "k"="v"`,
1235 expectJSON: `{"logger":"","level":0,"msg":"msg","one":1,"two":2,"k":"v"}`,
1236 }, {
1237 name: "dangling",
1238 values: makeKV("dangling"),
1239 args: makeKV("k", "v"),
1240 expectKV: `"level"=0 "msg"="msg" "dangling"="<no-value>" "k"="v"`,
1241 expectJSON: `{"logger":"","level":0,"msg":"msg","dangling":"<no-value>","k":"v"}`,
1242 }}
1243
1244 for _, tc := range testCases {
1245 t.Run("KV: "+tc.name, func(t *testing.T) {
1246 capt := &capture{}
1247 sink := newSink(capt.Func, NewFormatter(Options{}))
1248 sink = sink.WithValues(tc.values...)
1249 sink.Info(0, "msg", tc.args...)
1250 if capt.log != tc.expectKV {
1251 t.Errorf("\nexpected %q\n got %q", tc.expectKV, capt.log)
1252 }
1253 })
1254 t.Run("JSON: "+tc.name, func(t *testing.T) {
1255 capt := &capture{}
1256 sink := newSink(capt.Func, NewFormatterJSON(Options{}))
1257 sink = sink.WithValues(tc.values...)
1258 sink.Info(0, "msg", tc.args...)
1259 if capt.log != tc.expectJSON {
1260 t.Errorf("\nexpected %q\n got %q", tc.expectJSON, capt.log)
1261 }
1262 })
1263 }
1264 }
1265
1266 func TestErrorWithValues(t *testing.T) {
1267 testCases := []struct {
1268 name string
1269 values []any
1270 args []any
1271 expectKV string
1272 expectJSON string
1273 }{{
1274 name: "zero",
1275 values: makeKV(),
1276 args: makeKV("k", "v"),
1277 expectKV: `"msg"="msg" "error"="err" "k"="v"`,
1278 expectJSON: `{"logger":"","msg":"msg","error":"err","k":"v"}`,
1279 }, {
1280 name: "one",
1281 values: makeKV("one", 1),
1282 args: makeKV("k", "v"),
1283 expectKV: `"msg"="msg" "error"="err" "one"=1 "k"="v"`,
1284 expectJSON: `{"logger":"","msg":"msg","error":"err","one":1,"k":"v"}`,
1285 }, {
1286 name: "two",
1287 values: makeKV("one", 1, "two", 2),
1288 args: makeKV("k", "v"),
1289 expectKV: `"msg"="msg" "error"="err" "one"=1 "two"=2 "k"="v"`,
1290 expectJSON: `{"logger":"","msg":"msg","error":"err","one":1,"two":2,"k":"v"}`,
1291 }, {
1292 name: "dangling",
1293 values: makeKV("dangling"),
1294 args: makeKV("k", "v"),
1295 expectKV: `"msg"="msg" "error"="err" "dangling"="<no-value>" "k"="v"`,
1296 expectJSON: `{"logger":"","msg":"msg","error":"err","dangling":"<no-value>","k":"v"}`,
1297 }}
1298
1299 for _, tc := range testCases {
1300 t.Run("KV: "+tc.name, func(t *testing.T) {
1301 capt := &capture{}
1302 sink := newSink(capt.Func, NewFormatter(Options{}))
1303 sink = sink.WithValues(tc.values...)
1304 sink.Error(fmt.Errorf("err"), "msg", tc.args...)
1305 if capt.log != tc.expectKV {
1306 t.Errorf("\nexpected %q\n got %q", tc.expectKV, capt.log)
1307 }
1308 })
1309 t.Run("JSON: "+tc.name, func(t *testing.T) {
1310 capt := &capture{}
1311 sink := newSink(capt.Func, NewFormatterJSON(Options{}))
1312 sink = sink.WithValues(tc.values...)
1313 sink.Error(fmt.Errorf("err"), "msg", tc.args...)
1314 if capt.log != tc.expectJSON {
1315 t.Errorf("\nexpected %q\n got %q", tc.expectJSON, capt.log)
1316 }
1317 })
1318 }
1319 }
1320
1321 func TestInfoWithCallDepth(t *testing.T) {
1322 t.Run("one", func(t *testing.T) {
1323 capt := &capture{}
1324 sink := newSink(capt.Func, NewFormatter(Options{LogCaller: All}))
1325 dSink, _ := sink.(logr.CallDepthLogSink)
1326 sink = dSink.WithCallDepth(1)
1327 sink.Info(0, "msg")
1328 _, file, line, _ := runtime.Caller(1)
1329 expect := fmt.Sprintf(`"caller"={"file"=%q "line"=%d} "level"=0 "msg"="msg"`, filepath.Base(file), line)
1330 if capt.log != expect {
1331 t.Errorf("\nexpected %q\n got %q", expect, capt.log)
1332 }
1333 })
1334 }
1335
1336 func TestErrorWithCallDepth(t *testing.T) {
1337 t.Run("one", func(t *testing.T) {
1338 capt := &capture{}
1339 sink := newSink(capt.Func, NewFormatter(Options{LogCaller: All}))
1340 dSink, _ := sink.(logr.CallDepthLogSink)
1341 sink = dSink.WithCallDepth(1)
1342 sink.Error(fmt.Errorf("err"), "msg")
1343 _, file, line, _ := runtime.Caller(1)
1344 expect := fmt.Sprintf(`"caller"={"file"=%q "line"=%d} "msg"="msg" "error"="err"`, filepath.Base(file), line)
1345 if capt.log != expect {
1346 t.Errorf("\nexpected %q\n got %q", expect, capt.log)
1347 }
1348 })
1349 }
1350
1351 func TestOptionsTimestampFormat(t *testing.T) {
1352 capt := &capture{}
1353
1354
1355 sink := newSink(capt.Func, NewFormatter(Options{LogTimestamp: true, TimestampFormat: "TIMESTAMP"}))
1356 dSink, _ := sink.(logr.CallDepthLogSink)
1357 sink = dSink.WithCallDepth(1)
1358 sink.Info(0, "msg")
1359 expect := `"ts"="TIMESTAMP" "level"=0 "msg"="msg"`
1360 if capt.log != expect {
1361 t.Errorf("\nexpected %q\n got %q", expect, capt.log)
1362 }
1363 }
1364
1365 func TestOptionsLogInfoLevel(t *testing.T) {
1366 testCases := []struct {
1367 name string
1368 level *string
1369 expect string
1370 }{
1371 {
1372 name: "custom key",
1373 level: ptrstr("info_level"),
1374 expect: `"info_level"=0 "msg"="msg"`,
1375 },
1376 {
1377 name: "no level",
1378 level: ptrstr(""),
1379 expect: `"msg"="msg"`,
1380 },
1381 {
1382 name: "default",
1383 level: nil,
1384 expect: `"level"=0 "msg"="msg"`,
1385 },
1386 }
1387
1388 for _, tc := range testCases {
1389 t.Run("Run: "+tc.name, func(t *testing.T) {
1390 capt := &capture{}
1391 sink := newSink(capt.Func, NewFormatter(Options{LogInfoLevel: tc.level}))
1392 dSink, _ := sink.(logr.CallDepthLogSink)
1393 sink = dSink.WithCallDepth(1)
1394 sink.Info(0, "msg")
1395 if capt.log != tc.expect {
1396 t.Errorf("\nexpected %q\n got %q", tc.expect, capt.log)
1397 }
1398 })
1399 }
1400 }
1401
View as plain text