@@ -110,10 +110,9 @@ func TestReceivingMediaLiveInput(t *testing.T) {
110110 wg .Done ()
111111 }()
112112 wg .Wait ()
113- time .Sleep (50 * time .Millisecond ) // Need to finish the asynchronous writing of metadata
114- // Check that first media segment has been written
113+ // Check that first media segment has been written (poll: the write is asynchronous)
115114 for _ , trd := range testTrackData {
116- assert .True (t , testFileExists (filepath .Join (dstDir , trd .trName , fmt .Sprintf ("%d%s" , trd .minNr - c .startNr , trd .ext ))),
115+ assert .True (t , waitForFile (filepath .Join (dstDir , trd .trName , fmt .Sprintf ("%d%s" , trd .minNr - c .startNr , trd .ext ))),
117116 fmt .Sprintf ("%s/%d%s should exist" , trd .trName , trd .minNr - c .startNr , trd .ext ))
118117 }
119118
@@ -129,13 +128,13 @@ func TestReceivingMediaLiveInput(t *testing.T) {
129128 wg .Done ()
130129 }()
131130 wg .Wait ()
132- time .Sleep (50 * time .Millisecond ) // Need to finish the asynchronous writing
133- assert .True (t , ! testFileExists (filepath .Join (dstDir , "content_info.json" )), "content_info.json should not exist yet" )
134- // Check that the second media segment has been written
131+ // Check that the second media segment has been written (poll: the write is asynchronous)
135132 for _ , trd := range testTrackData {
136- assert .True (t , testFileExists (filepath .Join (dstDir , trd .trName , fmt .Sprintf ("%d%s" , trd .minNr + 1 - c .startNr , trd .ext ))),
133+ assert .True (t , waitForFile (filepath .Join (dstDir , trd .trName , fmt .Sprintf ("%d%s" , trd .minNr + 1 - c .startNr , trd .ext ))),
137134 fmt .Sprintf ("%s/%d%s should exist" , trd .trName , trd .minNr + 1 - c .startNr , trd .ext ))
138135 }
136+ // content_info.json is only written once two segments share a duration, so not yet
137+ assert .False (t , testFileExists (filepath .Join (dstDir , "content_info.json" )), "content_info.json should not exist yet" )
139138 // Send the third media segments. Since the duration of segment 2 and 3 are the same, manifest and content_info
140139 // should have been written unless shifted.
141140 wg = sync.WaitGroup {}
@@ -149,10 +148,11 @@ func TestReceivingMediaLiveInput(t *testing.T) {
149148 wg .Done ()
150149 }()
151150 wg .Wait ()
152- time .Sleep (50 * time .Millisecond ) // Need to finish the asynchronous writing of metadata
151+ // Poll for the manifest (written asynchronously); the SegmentTimeline-number MPD must not
152+ // appear until a later segment triggers it.
153+ assert .True (t , waitForFile (filepath .Join (dstDir , "manifest.mpd" )), "manifest.mpd should exist" )
153154 ch , ok := receiver .channelMgr .GetChannel (chName )
154155 assert .True (t , ok , "channel should exist" )
155- assert .True (t , testFileExists (filepath .Join (dstDir , "manifest.mpd" )), "manifest.mpd should exist" )
156156 require .False (t , testFileExists (filepath .Join (dstDir , timelineNrMPD )), "manifest_time_nrß.mpd should not exist" )
157157
158158 var videoTrackData trTestData
@@ -161,15 +161,19 @@ func TestReceivingMediaLiveInput(t *testing.T) {
161161 videoTrackData = trd
162162 }
163163 }
164- if ! ch .isShifted () {
165- firstSeqNr , ok := ch .segTimesGen .getBufferFirstSeqNr ("video" )
166- assert .True (t , ok , "segment data buffer should have items" )
167- assert .Equal (t , firstSeqNr , uint32 (videoTrackData .minNr + 1 - c .startNr ),
168- "first sequence number in segment data buffer should be the second segment" )
169- } else {
170- nrItems := ch .segTimesGen .getBufferNrItems ("video" )
171- assert .Equal (t , 0 , int (nrItems ), "shifted segment data buffer should be empty" )
172- }
164+ // The segment-data buffer is updated asynchronously as segments are processed, so poll it
165+ // to its expected state rather than reading it at a single fixed moment.
166+ require .EventuallyWithT (t , func (ct * assert.CollectT ) {
167+ if ! ch .isShifted () {
168+ firstSeqNr , ok := ch .segTimesGen .getBufferFirstSeqNr ("video" )
169+ assert .True (ct , ok , "segment data buffer should have items" )
170+ assert .Equal (ct , firstSeqNr , uint32 (videoTrackData .minNr + 1 - c .startNr ),
171+ "first sequence number in segment data buffer should be the second segment" )
172+ } else {
173+ nrItems := ch .segTimesGen .getBufferNrItems ("video" )
174+ assert .Equal (ct , 0 , int (nrItems ), "shifted segment data buffer should be empty" )
175+ }
176+ }, asyncWriteTimeout , asyncWritePollTick )
173177
174178 // Send the fourth media segments.
175179 // If shifted, there should be segments in the SegmentTimel line MPD, if not, just one segment.
@@ -183,29 +187,23 @@ func TestReceivingMediaLiveInput(t *testing.T) {
183187 wg .Done ()
184188 }()
185189 wg .Wait ()
186- time . Sleep ( 50 * time . Millisecond ) // Need to finish the asynchronous writing of metadata
187- assert . True ( t , testFileExists ( filepath . Join ( dstDir , timelineNrMPD )), "manifest_timeline_nr.mpd should now exist" )
188- data , err := os . ReadFile ( filepath . Join ( dstDir , timelineNrMPD ))
189- require . NoError ( t , err )
190- manifest , err := mpd . MPDFromBytes ( data )
191- require . NoError (t , err )
192- assert . Equal ( t , 1 , len ( manifest . Periods ), "there should be 1 period" )
193- assert . Equal ( t , 2 , len ( manifest . Periods [ 0 ]. AdaptationSets ), "there should be 2 adaptation sets" )
190+ // Poll until the SegmentTimeline-number MPD has been written to the expected state.
191+ expStartNr , expNrSegments := 896605655 , 3
192+ if ch . isShifted () { // Just one segment
193+ expStartNr , expNrSegments = 896605657 , 1
194+ }
195+ manifest := waitForMPD (t , filepath . Join ( dstDir , timelineNrMPD ), func ( m * mpd. MPD ) bool {
196+ return mpdReady ( m , 2 , expStartNr )
197+ } )
194198 for _ , as := range manifest .Periods [0 ].AdaptationSets {
195199 stl := as .SegmentTemplate
196200 assert .NotNil (t , stl , "segment template should exist" )
197201 nrSegments := 0
198202 for _ , s := range stl .SegmentTimeline .S {
199203 nrSegments += int (s .R ) + 1
200204 }
201- if ch .isShifted () { // Just one segment
202- require .Equal (t , 896605657 , int (* stl .StartNumber ), "start number should be 896605657" )
203- require .Equal (t , 1 , nrSegments , "number of segments should be 1" )
204- } else {
205- require .Equal (t , 896605655 , int (* stl .StartNumber ), "start number should be 896605655" )
206- require .Equal (t , 3 , nrSegments , "number of segments should be 3" )
207-
208- }
205+ assert .Equal (t , expStartNr , int (* stl .StartNumber ), "start number" )
206+ assert .Equal (t , expNrSegments , nrSegments , "number of segments" )
209207 }
210208
211209 // Check that the manifest fetched from the file server is the same as the one in the storage
@@ -344,7 +342,7 @@ func TestReceivingNonIdealInput(t *testing.T) {
344342 t .Fatalf ("Unknown content type %s" , contentType )
345343 }
346344 assert .Equal (t , 0 , int (ch .startTime ), "startTime should be zero" )
347- assert .True (t , testFileExists (filepath .Join (dstDir , trd .trName , fmt .Sprintf ("%d%s" , 8090 , trd .ext ))))
345+ assert .True (t , waitForFile (filepath .Join (dstDir , trd .trName , fmt .Sprintf ("%d%s" , 8090 , trd .ext ))))
348346 }
349347
350348 // Send second media segments and check that they have been received
@@ -359,7 +357,6 @@ func TestReceivingNonIdealInput(t *testing.T) {
359357 }()
360358 wg .Wait ()
361359 nextNr ++
362- time .Sleep (50 * time .Millisecond ) // Need to finish the asynchronous writing of metadata
363360 // Check that second segment has been written to nr 8091, except for second video track which has been updated
364361 // It is updated because data from the first video track is used to determine the shift.
365362 seqNr := 8091
@@ -368,16 +365,12 @@ func TestReceivingNonIdealInput(t *testing.T) {
368365 seqNr = 449002889
369366 }
370367 fName := fmt .Sprintf ("%d%s" , seqNr , trd .ext )
371- assert .True (t , testFileExists (filepath .Join (dstDir , trd .trName , fName )), fmt .Sprintf ("%s should exist" , fName ))
368+ assert .True (t , waitForFile (filepath .Join (dstDir , trd .trName , fName )), fmt .Sprintf ("%s should exist" , fName ))
372369 }
373- // Check that metadata has been written (two segments with same duration)
374- assert .True (t , testFileExists (filepath .Join (dstDir , "manifest.mpd" )), "manifest.mpd should exist" )
375- data , err := os .ReadFile (filepath .Join (dstDir , "manifest.mpd" ))
376- require .NoError (t , err )
377- manifest , err := mpd .MPDFromBytes (data )
378- require .NoError (t , err )
379- assert .Equal (t , 1 , len (manifest .Periods ), "there should be 1 period" )
380- assert .Equal (t , 4 , len (manifest .Periods [0 ].AdaptationSets ), "there should be 4 adaptation sets" )
370+ // Check that metadata has been written (two segments with same duration); poll for the async write
371+ manifest := waitForMPD (t , filepath .Join (dstDir , "manifest.mpd" ), func (m * mpd.MPD ) bool {
372+ return mpdReady (m , 4 , - 1 )
373+ })
381374 for _ , as := range manifest .Periods [0 ].AdaptationSets {
382375 switch as .ContentType {
383376 case "video" :
@@ -417,40 +410,14 @@ func TestReceivingNonIdealInput(t *testing.T) {
417410 }()
418411 wg .Wait ()
419412 nextNr ++
420- time .Sleep (50 * time .Millisecond ) // Need to finish the asynchronous writing of metadata
421- assert .True (t , testFileExists (filepath .Join (dstDir , timelineNrMPD )), "manifest_timeline_nr.mpd should now exist" )
422- data , err = os .ReadFile (filepath .Join (dstDir , timelineNrMPD ))
423- require .NoError (t , err )
424- manifest , err = mpd .MPDFromBytes (data )
425- require .NoError (t , err )
426- assert .Equal (t , 1 , len (manifest .Periods ), "there should be 1 period" )
427- assert .Equal (t , 4 , len (manifest .Periods [0 ].AdaptationSets ), "there should be 4 adaptation sets" )
413+ // Poll until the SegmentTimeline-number MPD has been written (startNumber settles to 449002890).
414+ manifest = waitForMPD (t , filepath .Join (dstDir , timelineNrMPD ), func (m * mpd.MPD ) bool {
415+ return mpdReady (m , 4 , 449002890 )
416+ })
428417 for _ , as := range manifest .Periods [0 ].AdaptationSets {
429418 stl := as .SegmentTemplate
430419 assert .NotNil (t , stl , "segment template should exist" )
431- assert .True (t , testFileExists (filepath .Join (dstDir , timelineNrMPD )), "manifest_timeline_nr.mpd should now exist" )
432- data , err = os .ReadFile (filepath .Join (dstDir , timelineNrMPD ))
433- require .NoError (t , err )
434- manifest , err = mpd .MPDFromBytes (data )
435- require .NoError (t , err )
436- assert .Equal (t , 1 , len (manifest .Periods ), "there should be 1 period" )
437- assert .Equal (t , 4 , len (manifest .Periods [0 ].AdaptationSets ), "there should be 4 adaptation sets" )
438- for _ , as := range manifest .Periods [0 ].AdaptationSets {
439- stl := as .SegmentTemplate
440- assert .NotNil (t , stl , "segment template should exist" )
441- assert .Equal (t , 449002890 , int (* stl .StartNumber ))
442- data , err = os .ReadFile (filepath .Join (dstDir , timelineNrMPD ))
443- require .NoError (t , err )
444- manifest , err = mpd .MPDFromBytes (data )
445- require .NoError (t , err )
446- assert .Equal (t , 1 , len (manifest .Periods ), "there should be 1 period" )
447- assert .Equal (t , 4 , len (manifest .Periods [0 ].AdaptationSets ), "there should be 4 adaptation sets" )
448- for _ , as := range manifest .Periods [0 ].AdaptationSets {
449- stl := as .SegmentTemplate
450- assert .NotNil (t , stl , "segment template should exist" )
451- assert .Equal (t , 449002890 , int (* stl .StartNumber ), "start number should be 449002890" )
452- }
453- }
420+ assert .Equal (t , 449002890 , int (* stl .StartNumber ), "start number should be 449002890" )
454421 }
455422
456423 // Next, let us have a jump in the sequence numbers, so segmentTimes should have one new number.
@@ -467,14 +434,10 @@ func TestReceivingNonIdealInput(t *testing.T) {
467434 }()
468435 wg .Wait ()
469436 nextNr ++
470- time .Sleep (50 * time .Millisecond ) // Need to finish the asynchronous writing of metadata
471- require .NoError (t , err )
472- assert .True (t , testFileExists (filepath .Join (dstDir , timelineNrMPD )))
473- data , err = os .ReadFile (filepath .Join (dstDir , timelineNrMPD ))
474- require .NoError (t , err )
475- manifest , err = mpd .MPDFromBytes (data )
476- require .NoError (t , err )
477- assert .Equal (t , 4 , len (manifest .Periods [0 ].AdaptationSets ), "there should be 4 adaptation sets" )
437+ // Poll until the SegmentTimeline-number MPD has been re-written (startNumber settles to 449002892).
438+ manifest = waitForMPD (t , filepath .Join (dstDir , timelineNrMPD ), func (m * mpd.MPD ) bool {
439+ return mpdReady (m , 4 , 449002892 )
440+ })
478441 for _ , as := range manifest .Periods [0 ].AdaptationSets {
479442 stl := as .SegmentTemplate
480443 assert .NotNil (t , stl , "segment template should exist" )
@@ -581,6 +544,78 @@ func testFileExists(filePath string) bool {
581544 return ! info .IsDir ()
582545}
583546
547+ // The receiver writes segments and manifests asynchronously after the upload request returns, so
548+ // tests must wait for those writes before asserting on them. The helpers below poll for that
549+ // instead of using a fixed sleep, which flaked on the slower Windows CI runner.
550+ //
551+ // TODO: once net/http works under testing/synctest (golang/go#76608, expected ~Go 1.27), replace
552+ // the polling with a synctest bubble and synctest.Wait() for deterministic, instant coordination.
553+
554+ // Poll budget and cadence for those async writes. The tick comfortably exceeds the time to read and
555+ // parse one of these small manifests; testify's EventuallyWithT runs the condition serially (it
556+ // waits for one call to return before scheduling the next tick), so the tick never overlaps a parse.
557+ const (
558+ asyncWriteTimeout = 10 * time .Second
559+ asyncWritePollTick = 50 * time .Millisecond
560+ )
561+
562+ // waitForFile polls (up to asyncWriteTimeout) for filePath to exist with content, returning true once it does.
563+ func waitForFile (filePath string ) bool {
564+ deadline := time .Now ().Add (asyncWriteTimeout )
565+ for {
566+ if info , err := os .Stat (filePath ); err == nil && ! info .IsDir () && info .Size () > 0 {
567+ return true
568+ }
569+ if time .Now ().After (deadline ) {
570+ return false
571+ }
572+ time .Sleep (asyncWritePollTick )
573+ }
574+ }
575+
576+ // mpdReady reports whether m has exactly nrAS adaptation sets in a single period and, when startNr
577+ // is >= 0, that every adaptation set's SegmentTemplate carries startNr as its StartNumber. It is the
578+ // "settled state" predicate used to wait past an in-progress or stale (re)write.
579+ func mpdReady (m * mpd.MPD , nrAS , startNr int ) bool {
580+ if len (m .Periods ) != 1 || len (m .Periods [0 ].AdaptationSets ) != nrAS {
581+ return false
582+ }
583+ if startNr < 0 {
584+ return true
585+ }
586+ for _ , as := range m .Periods [0 ].AdaptationSets {
587+ stl := as .SegmentTemplate
588+ if stl == nil || stl .StartNumber == nil || int (* stl .StartNumber ) != startNr {
589+ return false
590+ }
591+ }
592+ return true
593+ }
594+
595+ // waitForMPD polls (up to asyncWriteTimeout) until filePath parses into an MPD satisfying cond, then returns it,
596+ // so callers can run their detailed assertions on a settled manifest. cond lets a caller wait for a
597+ // specific state (e.g. an updated startNumber), not just the file's first appearance — a plain
598+ // existence check would read stale content on a re-write.
599+ func waitForMPD (t * testing.T , filePath string , cond func (* mpd.MPD ) bool ) * mpd.MPD {
600+ t .Helper ()
601+ var manifest * mpd.MPD
602+ require .EventuallyWithT (t , func (c * assert.CollectT ) {
603+ data , err := os .ReadFile (filePath )
604+ if ! assert .NoError (c , err ) {
605+ return
606+ }
607+ m , err := mpd .MPDFromBytes (data )
608+ if ! assert .NoError (c , err ) {
609+ return
610+ }
611+ if ! assert .True (c , cond (m ), "manifest %s is not yet in the expected state" , filePath ) {
612+ return
613+ }
614+ manifest = m
615+ }, asyncWriteTimeout , asyncWritePollTick )
616+ return manifest
617+ }
618+
584619func addOrigInitSegments (srcDir , chName , tmpDir string , ttd []trTestData ) error {
585620 for _ , trd := range ttd {
586621 srcInitPath := filepath .Join (srcDir , chName , trd .trName , "init_org" + trd .ext )
0 commit comments