...

Source file src/github.com/go-logr/logr/funcr/funcr_test.go

Documentation: github.com/go-logr/logr/funcr

     1  /*
     2  Copyright 2021 The logr Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    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  // Will be handled via reflection instead of type assertions.
    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  // point implements encoding.TextMarshaler and can be used as a map key.
    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  // pointErr implements encoding.TextMarshaler but returns an error.
    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  // Logging this should result in the MarshalLog() value.
    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  // Logging this should result in a panic.
    70  type Tmarshalerpanic struct{ val string }
    71  
    72  func (t Tmarshalerpanic) MarshalLog() any {
    73  	panic("Tmarshalerpanic")
    74  }
    75  
    76  // Logging this should result in the String() value.
    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  // Logging this should result in a panic.
    88  type Tstringerpanic struct{ val string }
    89  
    90  func (t Tstringerpanic) String() string {
    91  	panic("Tstringerpanic")
    92  }
    93  
    94  // Logging this should result in the Error() value.
    95  type Terror struct{ val string }
    96  
    97  func (t Terror) Error() string {
    98  	return "I am an error"
    99  }
   100  
   101  // Logging this should result in a panic.
   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:"-"`                 // first field ignored
   110  	String1 string `json:"string1"`           // renamed
   111  	String2 string `json:"-"`                 // ignored
   112  	String3 string `json:"-,"`                // named "-"
   113  	String4 string `json:"string4,omitempty"` // renamed, ignore if empty
   114  	String5 string `json:","`                 // no-op
   115  	String6 string `json:",omitempty"`        // ignore if empty
   116  }
   117  
   118  type TjsontagsBool struct {
   119  	Bool0 bool `json:"-"`               // first field ignored
   120  	Bool1 bool `json:"bool1"`           // renamed
   121  	Bool2 bool `json:"-"`               // ignored
   122  	Bool3 bool `json:"-,"`              // named "-"
   123  	Bool4 bool `json:"bool4,omitempty"` // renamed, ignore if empty
   124  	Bool5 bool `json:","`               // no-op
   125  	Bool6 bool `json:",omitempty"`      // ignore if empty
   126  }
   127  
   128  type TjsontagsInt struct {
   129  	Int0 int `json:"-"`              // first field ignored
   130  	Int1 int `json:"int1"`           // renamed
   131  	Int2 int `json:"-"`              // ignored
   132  	Int3 int `json:"-,"`             // named "-"
   133  	Int4 int `json:"int4,omitempty"` // renamed, ignore if empty
   134  	Int5 int `json:","`              // no-op
   135  	Int6 int `json:",omitempty"`     // ignore if empty
   136  }
   137  
   138  type TjsontagsUint struct {
   139  	Uint0 int  `json:"-"`               // first field ignored
   140  	Uint1 uint `json:"uint1"`           // renamed
   141  	Uint2 uint `json:"-"`               // ignored
   142  	Uint3 uint `json:"-,"`              // named "-"
   143  	Uint4 uint `json:"uint4,omitempty"` // renamed, ignore if empty
   144  	Uint5 uint `json:","`               // no-op
   145  	Uint6 uint `json:",omitempty"`      // ignore if empty
   146  }
   147  
   148  type TjsontagsFloat struct {
   149  	Float0 float64 `json:"-"`                // first field ignored
   150  	Float1 float64 `json:"float1"`           // renamed
   151  	Float2 float64 `json:"-"`                // ignored
   152  	Float3 float64 `json:"-,"`               // named "-"
   153  	Float4 float64 `json:"float4,omitempty"` // renamed, ignore if empty
   154  	Float5 float64 `json:","`                // no-op
   155  	Float6 float64 `json:",omitempty"`       // ignore if empty
   156  }
   157  
   158  type TjsontagsComplex struct {
   159  	Complex0 complex128 `json:"-"`                  // first field ignored
   160  	Complex1 complex128 `json:"complex1"`           // renamed
   161  	Complex2 complex128 `json:"-"`                  // ignored
   162  	Complex3 complex128 `json:"-,"`                 // named "-"
   163  	Complex4 complex128 `json:"complex4,omitempty"` // renamed, ignore if empty
   164  	Complex5 complex128 `json:","`                  // no-op
   165  	Complex6 complex128 `json:",omitempty"`         // ignore if empty
   166  }
   167  
   168  type TjsontagsPtr struct {
   169  	Ptr0 *string `json:"-"`              // first field ignored
   170  	Ptr1 *string `json:"ptr1"`           // renamed
   171  	Ptr2 *string `json:"-"`              // ignored
   172  	Ptr3 *string `json:"-,"`             // named "-"
   173  	Ptr4 *string `json:"ptr4,omitempty"` // renamed, ignore if empty
   174  	Ptr5 *string `json:","`              // no-op
   175  	Ptr6 *string `json:",omitempty"`     // ignore if empty
   176  }
   177  
   178  type TjsontagsArray struct {
   179  	Array0 [2]string `json:"-"`                // first field ignored
   180  	Array1 [2]string `json:"array1"`           // renamed
   181  	Array2 [2]string `json:"-"`                // ignored
   182  	Array3 [2]string `json:"-,"`               // named "-"
   183  	Array4 [2]string `json:"array4,omitempty"` // renamed, ignore if empty
   184  	Array5 [2]string `json:","`                // no-op
   185  	Array6 [2]string `json:",omitempty"`       // ignore if empty
   186  }
   187  
   188  type TjsontagsSlice struct {
   189  	Slice0 []string `json:"-"`                // first field ignored
   190  	Slice1 []string `json:"slice1"`           // renamed
   191  	Slice2 []string `json:"-"`                // ignored
   192  	Slice3 []string `json:"-,"`               // named "-"
   193  	Slice4 []string `json:"slice4,omitempty"` // renamed, ignore if empty
   194  	Slice5 []string `json:","`                // no-op
   195  	Slice6 []string `json:",omitempty"`       // ignore if empty
   196  }
   197  
   198  type TjsontagsMap struct {
   199  	Map0 map[string]string `json:"-"`              // first field ignored
   200  	Map1 map[string]string `json:"map1"`           // renamed
   201  	Map2 map[string]string `json:"-"`              // ignored
   202  	Map3 map[string]string `json:"-,"`             // named "-"
   203  	Map4 map[string]string `json:"map4,omitempty"` // renamed, ignore if empty
   204  	Map5 map[string]string `json:","`              // no-op
   205  	Map6 map[string]string `json:",omitempty"`     // ignore if empty
   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  	// used below
   249  	newStr := func(s string) *string {
   250  		return &s
   251  	}
   252  
   253  	cases := []struct {
   254  		val any
   255  		exp string // used in cases where JSON can't handle it
   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  	// used below
   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),     // will not be escaped, but should never happen
   730  		values:     makeKV("\tstr", "ABC"), // escaped
   731  		args:       makeKV("bool\n", true), // escaped
   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"), // should never happen
   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 { // will not be escaped, but should never happen
   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  	//  This timestamp format contains none of the characters that are
  1354  	//  considered placeholders, so will produce a constant result.
  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