@@ -19,13 +19,17 @@ package record
19
19
import (
20
20
"context"
21
21
"encoding/json"
22
+ stderrors "errors"
22
23
"fmt"
23
24
"net/http"
24
25
"strconv"
25
26
"sync"
26
27
"testing"
27
28
"time"
28
29
30
+ "github.com/stretchr/testify/assert"
31
+ "go.uber.org/goleak"
32
+
29
33
v1 "k8s.io/api/core/v1"
30
34
"k8s.io/apimachinery/pkg/api/errors"
31
35
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@@ -34,6 +38,8 @@ import (
34
38
"k8s.io/client-go/kubernetes/scheme"
35
39
restclient "k8s.io/client-go/rest"
36
40
ref "k8s.io/client-go/tools/reference"
41
+ "k8s.io/klog/v2"
42
+ "k8s.io/klog/v2/ktesting"
37
43
"k8s.io/utils/clock"
38
44
testclocks "k8s.io/utils/clock/testing"
39
45
)
@@ -104,13 +110,38 @@ func OnPatchFactory(testCache map[string]*v1.Event, patchEvent chan<- *v1.Event)
104
110
}
105
111
}
106
112
113
+ // newBroadcasterForTests creates a new broadcaster which produces per-test log
114
+ // output if StartStructuredLogging is used. Will be shut down automatically
115
+ // after the test.
116
+ func newBroadcasterForTests (tb testing.TB ) EventBroadcaster {
117
+ _ , ctx := ktesting .NewTestContext (tb )
118
+ caster := NewBroadcaster (WithSleepDuration (0 ), WithContext (ctx ))
119
+ tb .Cleanup (caster .Shutdown )
120
+ return caster
121
+ }
122
+
123
+ func TestBroadcasterShutdown (t * testing.T ) {
124
+ _ , ctx := ktesting .NewTestContext (t )
125
+ ctx , cancel := context .WithCancelCause (ctx )
126
+
127
+ // Start a broadcaster with background activity.
128
+ caster := NewBroadcaster (WithContext (ctx ))
129
+ caster .StartStructuredLogging (0 )
130
+
131
+ // Stop it.
132
+ cancel (stderrors .New ("time to stop" ))
133
+
134
+ // Ensure that the broadcaster goroutine is not left running.
135
+ goleak .VerifyNone (t )
136
+ }
137
+
107
138
func TestNonRacyShutdown (t * testing.T ) {
108
139
// Attempt to simulate previously racy conditions, and ensure that no race
109
140
// occurs: Nominally, calling "Eventf" *followed by* shutdown from the same
110
141
// thread should be a safe operation, but it's not if we launch recorder.Action
111
142
// in a goroutine.
112
143
113
- caster := NewBroadcasterForTests ( 0 )
144
+ caster := newBroadcasterForTests ( t )
114
145
clock := testclocks .NewFakeClock (time .Now ())
115
146
recorder := recorderWithFakeClock (t , v1.EventSource {Component : "eventTest" }, caster , clock )
116
147
@@ -151,14 +182,15 @@ func TestEventf(t *testing.T) {
151
182
t .Fatal (err )
152
183
}
153
184
table := []struct {
154
- obj k8sruntime.Object
155
- eventtype string
156
- reason string
157
- messageFmt string
158
- elements []interface {}
159
- expect * v1.Event
160
- expectLog string
161
- expectUpdate bool
185
+ obj k8sruntime.Object
186
+ eventtype string
187
+ reason string
188
+ messageFmt string
189
+ elements []interface {}
190
+ expect * v1.Event
191
+ expectLog string
192
+ expectStructuredLog string
193
+ expectUpdate bool
162
194
}{
163
195
{
164
196
obj : testRef ,
@@ -186,7 +218,9 @@ func TestEventf(t *testing.T) {
186
218
Count : 1 ,
187
219
Type : v1 .EventTypeNormal ,
188
220
},
189
- expectLog : `Event(v1.ObjectReference{Kind:"Pod", Namespace:"baz", Name:"foo", UID:"bar", APIVersion:"v1", ResourceVersion:"", FieldPath:"spec.containers[2]"}): type: 'Normal' reason: 'Started' some verbose message: 1` ,
221
+ expectLog : `Event(v1.ObjectReference{Kind:"Pod", Namespace:"baz", Name:"foo", UID:"bar", APIVersion:"v1", ResourceVersion:"", FieldPath:"spec.containers[2]"}): type: 'Normal' reason: 'Started' some verbose message: 1` ,
222
+ expectStructuredLog : `INFO Event occurred object="baz/foo" fieldPath="spec.containers[2]" kind="Pod" apiVersion="v1" type="Normal" reason="Started" message="some verbose message: 1"
223
+ ` ,
190
224
expectUpdate : false ,
191
225
},
192
226
{
@@ -214,7 +248,9 @@ func TestEventf(t *testing.T) {
214
248
Count : 1 ,
215
249
Type : v1 .EventTypeNormal ,
216
250
},
217
- expectLog : `Event(v1.ObjectReference{Kind:"Pod", Namespace:"baz", Name:"foo", UID:"bar", APIVersion:"v1", ResourceVersion:"", FieldPath:""}): type: 'Normal' reason: 'Killed' some other verbose message: 1` ,
251
+ expectLog : `Event(v1.ObjectReference{Kind:"Pod", Namespace:"baz", Name:"foo", UID:"bar", APIVersion:"v1", ResourceVersion:"", FieldPath:""}): type: 'Normal' reason: 'Killed' some other verbose message: 1` ,
252
+ expectStructuredLog : `INFO Event occurred object="baz/foo" fieldPath="" kind="Pod" apiVersion="v1" type="Normal" reason="Killed" message="some other verbose message: 1"
253
+ ` ,
218
254
expectUpdate : false ,
219
255
},
220
256
{
@@ -243,7 +279,9 @@ func TestEventf(t *testing.T) {
243
279
Count : 2 ,
244
280
Type : v1 .EventTypeNormal ,
245
281
},
246
- expectLog : `Event(v1.ObjectReference{Kind:"Pod", Namespace:"baz", Name:"foo", UID:"bar", APIVersion:"v1", ResourceVersion:"", FieldPath:"spec.containers[2]"}): type: 'Normal' reason: 'Started' some verbose message: 1` ,
282
+ expectLog : `Event(v1.ObjectReference{Kind:"Pod", Namespace:"baz", Name:"foo", UID:"bar", APIVersion:"v1", ResourceVersion:"", FieldPath:"spec.containers[2]"}): type: 'Normal' reason: 'Started' some verbose message: 1` ,
283
+ expectStructuredLog : `INFO Event occurred object="baz/foo" fieldPath="spec.containers[2]" kind="Pod" apiVersion="v1" type="Normal" reason="Started" message="some verbose message: 1"
284
+ ` ,
247
285
expectUpdate : true ,
248
286
},
249
287
{
@@ -272,7 +310,9 @@ func TestEventf(t *testing.T) {
272
310
Count : 1 ,
273
311
Type : v1 .EventTypeNormal ,
274
312
},
275
- expectLog : `Event(v1.ObjectReference{Kind:"Pod", Namespace:"baz", Name:"foo", UID:"differentUid", APIVersion:"v1", ResourceVersion:"", FieldPath:"spec.containers[3]"}): type: 'Normal' reason: 'Started' some verbose message: 1` ,
313
+ expectLog : `Event(v1.ObjectReference{Kind:"Pod", Namespace:"baz", Name:"foo", UID:"differentUid", APIVersion:"v1", ResourceVersion:"", FieldPath:"spec.containers[3]"}): type: 'Normal' reason: 'Started' some verbose message: 1` ,
314
+ expectStructuredLog : `INFO Event occurred object="baz/foo" fieldPath="spec.containers[3]" kind="Pod" apiVersion="v1" type="Normal" reason="Started" message="some verbose message: 1"
315
+ ` ,
276
316
expectUpdate : false ,
277
317
},
278
318
{
@@ -301,7 +341,9 @@ func TestEventf(t *testing.T) {
301
341
Count : 3 ,
302
342
Type : v1 .EventTypeNormal ,
303
343
},
304
- expectLog : `Event(v1.ObjectReference{Kind:"Pod", Namespace:"baz", Name:"foo", UID:"bar", APIVersion:"v1", ResourceVersion:"", FieldPath:"spec.containers[2]"}): type: 'Normal' reason: 'Started' some verbose message: 1` ,
344
+ expectLog : `Event(v1.ObjectReference{Kind:"Pod", Namespace:"baz", Name:"foo", UID:"bar", APIVersion:"v1", ResourceVersion:"", FieldPath:"spec.containers[2]"}): type: 'Normal' reason: 'Started' some verbose message: 1` ,
345
+ expectStructuredLog : `INFO Event occurred object="baz/foo" fieldPath="spec.containers[2]" kind="Pod" apiVersion="v1" type="Normal" reason="Started" message="some verbose message: 1"
346
+ ` ,
305
347
expectUpdate : true ,
306
348
},
307
349
{
@@ -330,7 +372,9 @@ func TestEventf(t *testing.T) {
330
372
Count : 1 ,
331
373
Type : v1 .EventTypeNormal ,
332
374
},
333
- expectLog : `Event(v1.ObjectReference{Kind:"Pod", Namespace:"baz", Name:"foo", UID:"differentUid", APIVersion:"v1", ResourceVersion:"", FieldPath:"spec.containers[3]"}): type: 'Normal' reason: 'Stopped' some verbose message: 1` ,
375
+ expectLog : `Event(v1.ObjectReference{Kind:"Pod", Namespace:"baz", Name:"foo", UID:"differentUid", APIVersion:"v1", ResourceVersion:"", FieldPath:"spec.containers[3]"}): type: 'Normal' reason: 'Stopped' some verbose message: 1` ,
376
+ expectStructuredLog : `INFO Event occurred object="baz/foo" fieldPath="spec.containers[3]" kind="Pod" apiVersion="v1" type="Normal" reason="Stopped" message="some verbose message: 1"
377
+ ` ,
334
378
expectUpdate : false ,
335
379
},
336
380
{
@@ -359,7 +403,9 @@ func TestEventf(t *testing.T) {
359
403
Count : 2 ,
360
404
Type : v1 .EventTypeNormal ,
361
405
},
362
- expectLog : `Event(v1.ObjectReference{Kind:"Pod", Namespace:"baz", Name:"foo", UID:"differentUid", APIVersion:"v1", ResourceVersion:"", FieldPath:"spec.containers[3]"}): type: 'Normal' reason: 'Stopped' some verbose message: 1` ,
406
+ expectLog : `Event(v1.ObjectReference{Kind:"Pod", Namespace:"baz", Name:"foo", UID:"differentUid", APIVersion:"v1", ResourceVersion:"", FieldPath:"spec.containers[3]"}): type: 'Normal' reason: 'Stopped' some verbose message: 1` ,
407
+ expectStructuredLog : `INFO Event occurred object="baz/foo" fieldPath="spec.containers[3]" kind="Pod" apiVersion="v1" type="Normal" reason="Stopped" message="some verbose message: 1"
408
+ ` ,
363
409
expectUpdate : true ,
364
410
},
365
411
}
@@ -377,23 +423,36 @@ func TestEventf(t *testing.T) {
377
423
},
378
424
OnPatch : OnPatchFactory (testCache , patchEvent ),
379
425
}
380
- eventBroadcaster := NewBroadcasterForTests (0 )
426
+ logger := ktesting .NewLogger (t , ktesting .NewConfig (ktesting .BufferLogs (true )))
427
+ logSink := logger .GetSink ().(ktesting.Underlier )
428
+ ctx := klog .NewContext (context .Background (), logger )
429
+ eventBroadcaster := NewBroadcaster (WithSleepDuration (0 ), WithContext (ctx ))
430
+ defer eventBroadcaster .Shutdown ()
381
431
sinkWatcher := eventBroadcaster .StartRecordingToSink (& testEvents )
382
432
383
433
clock := testclocks .NewFakeClock (time .Now ())
384
434
recorder := recorderWithFakeClock (t , v1.EventSource {Component : "eventTest" }, eventBroadcaster , clock )
385
435
for index , item := range table {
386
436
clock .Step (1 * time .Second )
437
+ //nolint:logcheck // Intentionally testing StartLogging here.
387
438
logWatcher := eventBroadcaster .StartLogging (func (formatter string , args ... interface {}) {
388
439
if e , a := item .expectLog , fmt .Sprintf (formatter , args ... ); e != a {
389
440
t .Errorf ("Expected '%v', got '%v'" , e , a )
390
441
}
391
442
logCalled <- struct {}{}
392
443
})
444
+ oldEnd := len (logSink .GetBuffer ().String ())
445
+ structuredLogWatcher := eventBroadcaster .StartStructuredLogging (0 )
393
446
recorder .Eventf (item .obj , item .eventtype , item .reason , item .messageFmt , item .elements ... )
394
447
395
448
<- logCalled
396
449
450
+ // We don't get notified by the structured test logger directly.
451
+ // Instead, we periodically check what new output it has produced.
452
+ assert .EventuallyWithT (t , func (t * assert.CollectT ) {
453
+ assert .Equal (t , item .expectStructuredLog , logSink .GetBuffer ().String ()[oldEnd :], "new structured log output" )
454
+ }, time .Minute , time .Millisecond )
455
+
397
456
// validate event
398
457
if item .expectUpdate {
399
458
actualEvent := <- patchEvent
@@ -403,6 +462,7 @@ func TestEventf(t *testing.T) {
403
462
validateEvent (strconv .Itoa (index ), actualEvent , item .expect , t )
404
463
}
405
464
logWatcher .Stop ()
465
+ structuredLogWatcher .Stop ()
406
466
}
407
467
sinkWatcher .Stop ()
408
468
}
@@ -561,8 +621,9 @@ func TestLotsOfEvents(t *testing.T) {
561
621
},
562
622
}
563
623
564
- eventBroadcaster := NewBroadcasterForTests ( 0 )
624
+ eventBroadcaster := newBroadcasterForTests ( t )
565
625
sinkWatcher := eventBroadcaster .StartRecordingToSink (& testEvents )
626
+ //nolint:logcheck // Intentionally using StartLogging here to get notified.
566
627
logWatcher := eventBroadcaster .StartLogging (func (formatter string , args ... interface {}) {
567
628
loggerCalled <- struct {}{}
568
629
})
@@ -658,7 +719,7 @@ func TestEventfNoNamespace(t *testing.T) {
658
719
},
659
720
OnPatch : OnPatchFactory (testCache , patchEvent ),
660
721
}
661
- eventBroadcaster := NewBroadcasterForTests ( 0 )
722
+ eventBroadcaster := newBroadcasterForTests ( t )
662
723
sinkWatcher := eventBroadcaster .StartRecordingToSink (& testEvents )
663
724
664
725
clock := testclocks .NewFakeClock (time .Now ())
@@ -953,7 +1014,7 @@ func TestMultiSinkCache(t *testing.T) {
953
1014
OnPatch : OnPatchFactory (testCache2 , patchEvent2 ),
954
1015
}
955
1016
956
- eventBroadcaster := NewBroadcasterForTests ( 0 )
1017
+ eventBroadcaster := newBroadcasterForTests ( t )
957
1018
clock := testclocks .NewFakeClock (time .Now ())
958
1019
recorder := recorderWithFakeClock (t , v1.EventSource {Component : "eventTest" }, eventBroadcaster , clock )
959
1020
@@ -971,6 +1032,9 @@ func TestMultiSinkCache(t *testing.T) {
971
1032
validateEvent (strconv .Itoa (index ), actualEvent , item .expect , t )
972
1033
}
973
1034
}
1035
+ // Stop before creating more events, otherwise the On* callbacks above
1036
+ // get stuck writing to the channel that we don't read from anymore.
1037
+ sinkWatcher .Stop ()
974
1038
975
1039
// Another StartRecordingToSink call should start to record events with new clean cache.
976
1040
sinkWatcher2 := eventBroadcaster .StartRecordingToSink (& testEvents2 )
@@ -988,6 +1052,5 @@ func TestMultiSinkCache(t *testing.T) {
988
1052
}
989
1053
}
990
1054
991
- sinkWatcher .Stop ()
992
1055
sinkWatcher2 .Stop ()
993
1056
}
0 commit comments