1
7
8 package ktesting_test
9
10 import (
11 "bytes"
12 "errors"
13 "flag"
14 "fmt"
15 "regexp"
16 "runtime"
17 "sync"
18 "testing"
19 "time"
20
21 "k8s.io/klog/v2"
22 "k8s.io/klog/v2/internal/test/require"
23 "k8s.io/klog/v2/ktesting"
24 )
25
26 var headerRe = regexp.MustCompile(`([IE])[[:digit:]]{4} [[:digit:]]{2}:[[:digit:]]{2}:[[:digit:]]{2}\.[[:digit:]]{6}\] `)
27
28 func TestInfo(t *testing.T) {
29 tests := map[string]struct {
30 text string
31 withValues []interface{}
32 keysAndValues []interface{}
33 names []string
34 err error
35 expectedOutput string
36 }{
37 "should log with values passed to keysAndValues": {
38 text: "test",
39 keysAndValues: []interface{}{"akey", "avalue"},
40 expectedOutput: `Ixxx test akey="avalue"
41 `,
42 },
43 "should support single name": {
44 names: []string{"hello"},
45 text: "test",
46 keysAndValues: []interface{}{"akey", "avalue"},
47 expectedOutput: `Ixxx hello: test akey="avalue"
48 `,
49 },
50 "should support multiple names": {
51 names: []string{"hello", "world"},
52 text: "test",
53 keysAndValues: []interface{}{"akey", "avalue"},
54 expectedOutput: `Ixxx hello/world: test akey="avalue"
55 `,
56 },
57 "should not print duplicate keys with the same value": {
58 text: "test",
59 keysAndValues: []interface{}{"akey", "avalue", "akey", "avalue"},
60 expectedOutput: `Ixxx test akey="avalue" akey="avalue"
61 `,
62 },
63 "should only print the last duplicate key when the values are passed to Info": {
64 text: "test",
65 keysAndValues: []interface{}{"akey", "avalue", "akey", "avalue2"},
66 expectedOutput: `Ixxx test akey="avalue" akey="avalue2"
67 `,
68 },
69 "should only print the duplicate key that is passed to Info if one was passed to the logger": {
70 withValues: []interface{}{"akey", "avalue"},
71 text: "test",
72 keysAndValues: []interface{}{"akey", "avalue"},
73 expectedOutput: `Ixxx test akey="avalue"
74 `,
75 },
76 "should only print the key passed to Info when one is already set on the logger": {
77 withValues: []interface{}{"akey", "avalue"},
78 text: "test",
79 keysAndValues: []interface{}{"akey", "avalue2"},
80 expectedOutput: `Ixxx test akey="avalue2"
81 `,
82 },
83 "should correctly handle odd-numbers of KVs": {
84 text: "test",
85 keysAndValues: []interface{}{"akey", "avalue", "akey2"},
86 expectedOutput: `Ixxx test akey="avalue" akey2="(MISSING)"
87 `,
88 },
89 "should correctly html characters": {
90 text: "test",
91 keysAndValues: []interface{}{"akey", "<&>"},
92 expectedOutput: `Ixxx test akey="<&>"
93 `,
94 },
95 "should correctly handle odd-numbers of KVs in both log values and Info args": {
96 withValues: []interface{}{"basekey1", "basevar1", "basekey2"},
97 text: "test",
98 keysAndValues: []interface{}{"akey", "avalue", "akey2"},
99 expectedOutput: `Ixxx test basekey1="basevar1" basekey2="(MISSING)" akey="avalue" akey2="(MISSING)"
100 `,
101 },
102 "should correctly print regular error types": {
103 text: "test",
104 keysAndValues: []interface{}{"err", errors.New("whoops")},
105 expectedOutput: `Ixxx test err="whoops"
106 `,
107 },
108 "should correctly print regular error types when using logr.Error": {
109 text: "test",
110 err: errors.New("whoops"),
111 expectedOutput: `Exxx test err="whoops"
112 `,
113 },
114 }
115 for n, test := range tests {
116 t.Run(n, func(t *testing.T) {
117 var buffer logToBuf
118 klogr := ktesting.NewLogger(&buffer, ktesting.NewConfig())
119 for _, name := range test.names {
120 klogr = klogr.WithName(name)
121 }
122 klogr = klogr.WithValues(test.withValues...)
123
124 if test.err != nil {
125 klogr.Error(test.err, test.text, test.keysAndValues...)
126 } else {
127 klogr.Info(test.text, test.keysAndValues...)
128 }
129
130 actual := buffer.String()
131 actual = headerRe.ReplaceAllString(actual, `${1}xxx `)
132 if actual != test.expectedOutput {
133 t.Errorf("Expected:\n%sActual:\n%s\n", test.expectedOutput, actual)
134 }
135 })
136 }
137 }
138
139 func TestCallDepth(t *testing.T) {
140 logger := ktesting.NewLogger(t, ktesting.NewConfig())
141 logger.Info("hello world")
142 }
143
144 type logToBuf struct {
145 ktesting.NopTL
146 bytes.Buffer
147 }
148
149 func (l *logToBuf) Helper() {
150 }
151
152 func (l *logToBuf) Log(args ...interface{}) {
153 for i, arg := range args {
154 if i > 0 {
155 l.Write([]byte(" "))
156 }
157 l.Write([]byte(fmt.Sprintf("%s", arg)))
158 }
159 l.Write([]byte("\n"))
160 }
161
162 func TestStop(t *testing.T) {
163
164
165
166
167
168
169
170
171
172
173 state := klog.CaptureState()
174 defer state.Restore()
175 var output bytes.Buffer
176 var fs flag.FlagSet
177 klog.InitFlags(&fs)
178 require.NoError(t, fs.Set("alsologtostderr", "false"))
179 require.NoError(t, fs.Set("logtostderr", "false"))
180 require.NoError(t, fs.Set("stderrthreshold", "FATAL"))
181 require.NoError(t, fs.Set("one_output", "true"))
182 klog.SetOutput(&output)
183
184 var logger klog.Logger
185 var line int
186 var wg1, wg2 sync.WaitGroup
187 wg1.Add(1)
188 wg2.Add(1)
189 t.Run("Sub", func(t *testing.T) {
190 logger, _ = ktesting.NewTestContext(t)
191 go func() {
192 defer wg2.Done()
193
194
195 wg1.Wait()
196
197
198
199 _, _, line, _ = runtime.Caller(0)
200 logger.Info("simple info message")
201 logger.Error(nil, "error message")
202 time.Sleep(15 * time.Second)
203 logger.WithName("me").WithValues("completed", true).Info("complex info message", "anotherValue", 1)
204 }()
205 })
206
207 wg1.Done()
208
209
210 wg2.Wait()
211
212 actual := output.String()
213
214
215 actual = regexp.MustCompile(`(?m)^.* testinglogger_test.go:`).ReplaceAllString(actual, `testinglogger_test.go:`)
216
217
218 actual = regexp.MustCompile(`timeSinceCompletion="\d+s"`).ReplaceAllString(actual, `timeSinceCompletion="<...>s"`)
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239 actual = regexp.MustCompile(`(?m)^\t.*?\n`).ReplaceAllString(actual, ``)
240
241 expected := fmt.Sprintf(`testinglogger_test.go:%d] "simple info message" logger="TestStop/Sub leaked goroutine"
242 testinglogger_test.go:%d] "error message" logger="TestStop/Sub leaked goroutine"
243 testinglogger_test.go:%d] "WARNING: test kept at least one goroutine running after test completion" logger="TestStop/Sub leaked goroutine.me" completed=true timeSinceCompletion="<...>s" callstack=<
244 >
245 testinglogger_test.go:%d] "complex info message" logger="TestStop/Sub leaked goroutine.me" completed=true anotherValue=1
246 `,
247 line+1, line+2, line+4, line+4)
248 if actual != expected {
249 t.Errorf("Output does not match. Expected:\n%s\nActual:\n%s\n", expected, actual)
250 }
251
252 testingLogger, ok := logger.GetSink().(ktesting.Underlier)
253 if !ok {
254 t.Fatal("should have had a ktesting logger")
255 }
256 captured := testingLogger.GetBuffer().String()
257 if captured != "" {
258 t.Errorf("testing logger should not have captured any output, got instead:\n%s", captured)
259 }
260 }
261
View as plain text