@@ -3,11 +3,16 @@ package controller
33import (
44 "bufio"
55 "bytes"
6+ "context"
67 "io"
8+ "sync/atomic"
79 "testing"
810 "time"
911
1012 "github.com/stretchr/testify/assert"
13+ "k8s.io/client-go/kubernetes"
14+
15+ "github.com/kubeshop/testkube/cmd/testworkflow-init/instructions"
1116)
1217
1318func Test_ReadTimestamp_UTC_Initial (t * testing.T ) {
@@ -65,3 +70,145 @@ func Test_ReadTimestamp_NonUTC_Recurring(t *testing.T) {
6570 assert .Equal (t , []byte (message ), rest )
6671 assert .Equal (t , time .Date (2024 , 6 , 7 , 12 , 41 , 49 , 37275300 , time .UTC ), reader .ts )
6772}
73+
74+ type blockingReader struct {
75+ ctx context.Context
76+ }
77+
78+ func (r * blockingReader ) Read (p []byte ) (int , error ) {
79+ <- r .ctx .Done ()
80+ return 0 , r .ctx .Err ()
81+ }
82+
83+ func TestWatchContainerLogsIdleTimeoutCancelsWhenDone (t * testing.T ) {
84+ t .Parallel ()
85+
86+ ctx , cancel := context .WithCancel (context .Background ())
87+ defer cancel ()
88+
89+ idleTimeout := 50 * time .Millisecond
90+ ch := watchContainerLogsWithStream (
91+ ctx ,
92+ func (ctx context.Context , _ kubernetes.Interface , _ , _ , _ string , _ func () bool , _ * time.Time ) (io.Reader , error ) {
93+ return & blockingReader {ctx : ctx }, nil
94+ },
95+ nil ,
96+ "default" ,
97+ "pod" ,
98+ "container" ,
99+ 1 ,
100+ func () bool { return true },
101+ func (* instructions.Instruction ) bool { return false },
102+ idleTimeout ,
103+ )
104+
105+ deadline := time .NewTimer (500 * time .Millisecond )
106+ defer deadline .Stop ()
107+
108+ var gotErr bool
109+ for {
110+ select {
111+ case msg , ok := <- ch :
112+ if ! ok {
113+ assert .True (t , gotErr , "expected idle timeout error before channel close" )
114+ return
115+ }
116+ if msg .Error != nil {
117+ gotErr = true
118+ assert .Contains (t , msg .Error .Error (), "idle timeout" )
119+ }
120+ case <- deadline .C :
121+ t .Fatal ("timed out waiting for idle timeout to close the channel" )
122+ }
123+ }
124+ }
125+
126+ func TestWatchContainerLogsReopensOnEOF (t * testing.T ) {
127+ t .Parallel ()
128+
129+ ctx , cancel := context .WithCancel (context .Background ())
130+ defer cancel ()
131+
132+ var calls int32
133+ line := "2024-06-07T12:41:49.037275300Z hello\n "
134+ ch := watchContainerLogsWithStream (
135+ ctx ,
136+ func (_ context.Context , _ kubernetes.Interface , _ , _ , _ string , _ func () bool , _ * time.Time ) (io.Reader , error ) {
137+ call := atomic .AddInt32 (& calls , 1 )
138+ if call == 1 {
139+ return bytes .NewBufferString (line ), nil
140+ }
141+ return bytes .NewBuffer (nil ), nil
142+ },
143+ nil ,
144+ "default" ,
145+ "pod" ,
146+ "container" ,
147+ 5 ,
148+ func () bool { return false },
149+ func (* instructions.Instruction ) bool { return false },
150+ 500 * time .Millisecond ,
151+ )
152+
153+ deadline := time .NewTimer (2 * time .Second )
154+ defer deadline .Stop ()
155+
156+ var gotLog bool
157+ for {
158+ select {
159+ case msg , ok := <- ch :
160+ if ! ok {
161+ assert .True (t , gotLog , "expected at least one log message before channel close" )
162+ assert .GreaterOrEqual (t , atomic .LoadInt32 (& calls ), int32 (2 ))
163+ return
164+ }
165+ if msg .Error != nil {
166+ t .Fatalf ("unexpected error from logs channel: %v" , msg .Error )
167+ }
168+ if bytes .Contains (msg .Value .Log , []byte ("hello" )) {
169+ gotLog = true
170+ }
171+ case <- deadline .C :
172+ t .Fatal ("timed out waiting for log stream to close" )
173+ }
174+ }
175+ }
176+
177+ func TestWatchContainerLogsDoneWithNoLogsCloses (t * testing.T ) {
178+ t .Parallel ()
179+
180+ ctx , cancel := context .WithCancel (context .Background ())
181+ defer cancel ()
182+
183+ ch := watchContainerLogsWithStream (
184+ ctx ,
185+ func (_ context.Context , _ kubernetes.Interface , _ , _ , _ string , _ func () bool , _ * time.Time ) (io.Reader , error ) {
186+ return bytes .NewBuffer (nil ), nil
187+ },
188+ nil ,
189+ "default" ,
190+ "pod" ,
191+ "container" ,
192+ 1 ,
193+ func () bool { return true },
194+ func (* instructions.Instruction ) bool { return false },
195+ 500 * time .Millisecond ,
196+ )
197+
198+ deadline := time .NewTimer (500 * time .Millisecond )
199+ defer deadline .Stop ()
200+
201+ for {
202+ select {
203+ case msg , ok := <- ch :
204+ if ! ok {
205+ return
206+ }
207+ if msg .Error != nil {
208+ t .Fatalf ("unexpected error from logs channel: %v" , msg .Error )
209+ }
210+ case <- deadline .C :
211+ t .Fatal ("timed out waiting for log stream to close" )
212+ }
213+ }
214+ }
0 commit comments