@@ -109,13 +109,17 @@ interface ConsoleMock extends Console {
109109
110110test ( "getStreamSink()" , async ( ) => {
111111 let buffer : string = "" ;
112+ let closed = false ;
112113 const decoder = new TextDecoder ( ) ;
113114 const sink = getStreamSink (
114115 new WritableStream ( {
115116 write ( chunk : Uint8Array ) {
116117 buffer += decoder . decode ( chunk ) ;
117118 return Promise . resolve ( ) ;
118119 } ,
120+ close ( ) {
121+ closed = true ;
122+ } ,
119123 } ) ,
120124 ) ;
121125 sink ( trace ) ;
@@ -136,6 +140,40 @@ test("getStreamSink()", async () => {
1361402023-11-14 22:13:20.000 +00:00 [FTL] my-app·junk: Hello, 123 & 456!
137141` ,
138142 ) ;
143+ assert . strictEqual ( closed , true ) ;
144+ } ) ;
145+
146+ test ( "getStreamSink() with closeStream: false" , async ( ) => {
147+ // Arrange
148+ let buffer = "" ;
149+ let closed = false ;
150+ const decoder = new TextDecoder ( ) ;
151+ const stream = new WritableStream ( {
152+ write ( chunk : Uint8Array ) {
153+ buffer += decoder . decode ( chunk ) ;
154+ } ,
155+ close ( ) {
156+ closed = true ;
157+ } ,
158+ } ) ;
159+ const sink = getStreamSink ( stream , { closeStream : false } ) ;
160+
161+ // Act
162+ sink ( info ) ;
163+ await sink [ Symbol . asyncDispose ] ( ) ;
164+
165+ // Assert
166+ assert . strictEqual (
167+ buffer ,
168+ "2023-11-14 22:13:20.000 +00:00 [INF] my-app·junk: " +
169+ "Hello, 123 & 456!\n" ,
170+ ) ;
171+ assert . strictEqual ( closed , false ) ;
172+ const writer = stream . getWriter ( ) ;
173+ await writer . write ( new TextEncoder ( ) . encode ( "after disposal" ) ) ;
174+ await writer . close ( ) ;
175+ writer . releaseLock ( ) ;
176+ assert . ok ( buffer . endsWith ( "after disposal" ) ) ;
139177} ) ;
140178
141179test ( "getStreamSink() with nonBlocking - simple boolean" , async ( ) => {
@@ -300,6 +338,90 @@ test("getStreamSink() with nonBlocking - flush on dispose", async () => {
300338 ) ;
301339} ) ;
302340
341+ test (
342+ "getStreamSink() with nonBlocking and closeStream: false" ,
343+ async ( ) => {
344+ // Arrange
345+ let buffer = "" ;
346+ let closed = false ;
347+ const decoder = new TextDecoder ( ) ;
348+ const stream = new WritableStream ( {
349+ write ( chunk : Uint8Array ) {
350+ buffer += decoder . decode ( chunk ) ;
351+ } ,
352+ close ( ) {
353+ closed = true ;
354+ } ,
355+ } ) ;
356+ const sink = getStreamSink ( stream , {
357+ closeStream : false ,
358+ nonBlocking : {
359+ bufferSize : 100 ,
360+ flushInterval : 5000 ,
361+ } ,
362+ } ) ;
363+
364+ // Act
365+ sink ( info ) ;
366+ await sink [ Symbol . asyncDispose ] ( ) ;
367+
368+ // Assert
369+ assert . strictEqual (
370+ buffer ,
371+ "2023-11-14 22:13:20.000 +00:00 [INF] my-app·junk: " +
372+ "Hello, 123 & 456!\n" ,
373+ ) ;
374+ assert . strictEqual ( closed , false ) ;
375+ const writer = stream . getWriter ( ) ;
376+ await writer . close ( ) ;
377+ writer . releaseLock ( ) ;
378+ } ,
379+ ) ;
380+
381+ test (
382+ "getStreamSink() with nonBlocking waits for an active flush on dispose" ,
383+ async ( ) => {
384+ // Arrange
385+ let markWriteStarted : ( ) => void = ( ) => { } ;
386+ let finishWrite : ( ) => void = ( ) => { } ;
387+ const writeStarted = new Promise < void > ( ( resolve ) => {
388+ markWriteStarted = resolve ;
389+ } ) ;
390+ const writeCanFinish = new Promise < void > ( ( resolve ) => {
391+ finishWrite = resolve ;
392+ } ) ;
393+ const stream = new WritableStream ( {
394+ async write ( ) {
395+ markWriteStarted ( ) ;
396+ await writeCanFinish ;
397+ } ,
398+ } ) ;
399+ const sink = getStreamSink ( stream , {
400+ closeStream : false ,
401+ nonBlocking : { bufferSize : 1 } ,
402+ } ) ;
403+
404+ // Act
405+ sink ( info ) ;
406+ await beforeDeadline ( writeStarted , "The buffered write did not start" ) ;
407+ let disposed = false ;
408+ const disposePromise = Promise . resolve ( sink [ Symbol . asyncDispose ] ( ) ) . then (
409+ ( ) => {
410+ disposed = true ;
411+ } ,
412+ ) ;
413+ await delay ( 0 ) ;
414+
415+ // Assert
416+ assert . strictEqual ( disposed , false ) ;
417+ finishWrite ( ) ;
418+ await beforeDeadline ( disposePromise , "The stream sink was not disposed" ) ;
419+ const writer = stream . getWriter ( ) ;
420+ await writer . close ( ) ;
421+ writer . releaseLock ( ) ;
422+ } ,
423+ ) ;
424+
303425test ( "getStreamSink() with nonBlocking - buffer overflow protection" , async ( ) => {
304426 let buffer : string = "" ;
305427 const decoder = new TextDecoder ( ) ;
@@ -3292,3 +3414,22 @@ function recordWithLevel(level: LogLevel): LogRecord {
32923414 properties : { } ,
32933415 } ;
32943416}
3417+
3418+ async function beforeDeadline < T > (
3419+ promise : Promise < T > ,
3420+ message : string ,
3421+ ) : Promise < T > {
3422+ let timeoutId : ReturnType < typeof globalThis . setTimeout > | undefined ;
3423+ try {
3424+ return await Promise . race ( [
3425+ promise ,
3426+ new Promise < never > ( ( _ , reject ) => {
3427+ timeoutId = globalThis . setTimeout ( ( ) => {
3428+ reject ( new Error ( message ) ) ;
3429+ } , 1000 ) ;
3430+ } ) ,
3431+ ] ) ;
3432+ } finally {
3433+ if ( timeoutId !== undefined ) globalThis . clearTimeout ( timeoutId ) ;
3434+ }
3435+ }
0 commit comments