@@ -81,6 +81,8 @@ public function testEmitBody($seekable, $contents, $maxBufferLength, $expectedRe
8181 $ stream ->getSize ()->willReturn (strlen ($ contents ));
8282 $ stream ->isSeekable ()->willReturn ($ seekable );
8383 $ stream ->isReadable ()->willReturn (true );
84+ $ stream ->__toString ()->willReturn ($ contents );
85+ $ stream ->getContents ()->willReturn ($ contents );
8486 $ stream ->rewind ()->willReturn (true );
8587
8688 $ stream ->eof ()->will (function () use (&$ contents , &$ position ) {
@@ -104,6 +106,109 @@ public function testEmitBody($seekable, $contents, $maxBufferLength, $expectedRe
104106 $ this ->assertEquals ($ contents , ob_get_clean ());
105107 }
106108
109+ public function emitMemoryUsageProvider ()
110+ {
111+ return [
112+ [true , 512 , 1000 , 20 , null ],
113+ [true , 8192 , 1000 , 20 , null ],
114+ [false , 512 , 1000 , 20 , null ],
115+ [false , 8192 , 1000 , 20 , null ],
116+ [true , 512 , 1000 , 20 , [25 , 75 ]],
117+ [true , 8192 , 1000 , 20 , [25 , 75 ]],
118+ [true , 512 , 1000 , 20 , [250 , 750 ]],
119+ [true , 8192 , 1000 , 20 , [250 , 750 ]],
120+ [false , 512 , 1000 , 20 , [25 , 75 ]],
121+ [false , 8192 , 1000 , 20 , [25 , 75 ]],
122+ [false , 512 , 1000 , 20 , [250 , 750 ]],
123+ [false , 8192 , 1000 , 20 , [250 , 750 ]],
124+ ];
125+ }
126+
127+ /**
128+ * @dataProvider emitMemoryUsageProvider
129+ */
130+ public function testEmitMemoryUsage ($ seekable , $ maxBufferLength , $ sizeBlocks , $ maxAllowedBlocks , $ rangeBlocks )
131+ {
132+ $ sizeBytes = $ maxBufferLength * $ sizeBlocks ;
133+ $ maxAllowedMemoryUsage = $ maxBufferLength * $ maxAllowedBlocks ;
134+ $ peakBufferLength = 0 ;
135+ $ peakMemoryUsage = 0 ;
136+
137+ if ($ rangeBlocks ) {
138+ $ first = $ maxBufferLength * $ rangeBlocks [0 ];
139+ $ last = $ maxBufferLength * $ rangeBlocks [1 ];
140+ $ position = $ first ;
141+ } else {
142+ $ position = 0 ;
143+ }
144+
145+ $ closureFullContents = function () use (&$ sizeBytes ) {
146+ return str_repeat ('0 ' , $ sizeBytes );
147+ };
148+
149+ $ stream = $ this ->prophesize ('Psr\Http\Message\StreamInterface ' );
150+ $ stream ->getSize ()->willReturn ($ sizeBytes );
151+ $ stream ->isSeekable ()->willReturn ($ seekable );
152+ $ stream ->isReadable ()->willReturn (true );
153+ $ stream ->__toString ()->will ($ closureFullContents );
154+ $ stream ->getContents ()->willReturn ($ closureFullContents );
155+ $ stream ->rewind ()->willReturn (true );
156+
157+ $ stream ->seek (Argument::type ('integer ' ), Argument::any ())->will (function ($ args ) use (&$ position ) {
158+ $ position = $ args [0 ];
159+ return true ;
160+ });
161+
162+ $ stream ->eof ()->will (function () use (&$ sizeBytes , &$ position ) {
163+ return ($ position >= $ sizeBytes );
164+ });
165+
166+ $ stream ->read (Argument::type ('integer ' ))->will (function ($ args ) use (&$ position , &$ peakBufferLength ) {
167+ if ($ args [0 ] > $ peakBufferLength ) {
168+ $ peakBufferLength = $ args [0 ];
169+ }
170+
171+ $ position += $ args [0 ];
172+ return str_repeat ('0 ' , $ args [0 ]);
173+ });
174+
175+ $ response = (new Response ())
176+ ->withStatus (200 )
177+ ->withBody ($ stream ->reveal ());
178+
179+
180+ if ($ rangeBlocks ) {
181+ $ response ->withHeader ('Content-Range ' , "bytes $ first- $ last/* " );
182+ }
183+
184+ ob_start (function ($ output ) {
185+ return "" ;
186+ }, $ maxBufferLength );
187+
188+ $ closureTrackMemoryUsage = function () use (&$ peakMemoryUsage ) {
189+ $ memoryUsage = memory_get_usage ();
190+
191+ if ($ memoryUsage > $ peakMemoryUsage ) {
192+ $ peakMemoryUsage = $ memoryUsage ;
193+ }
194+ };
195+
196+ register_tick_function ($ closureTrackMemoryUsage );
197+
198+ declare (ticks = 1 ) {
199+ $ this ->emitter ->emit ($ response , $ maxBufferLength );
200+ }
201+
202+ unregister_tick_function ($ closureTrackMemoryUsage );
203+
204+ ob_end_flush ();
205+
206+ $ localMemoryUsage = memory_get_usage ();
207+
208+ $ this ->assertLessThanOrEqual ($ maxBufferLength , $ peakBufferLength );
209+ $ this ->assertLessThanOrEqual ($ maxAllowedMemoryUsage , ($ peakMemoryUsage - $ localMemoryUsage ));
210+ }
211+
107212 public function emitBodyRangeProvider ()
108213 {
109214 return [
@@ -127,7 +232,11 @@ public function testEmitBodyRange($seekable, $contents, $range, $maxBufferLength
127232 $ stream ->getSize ()->willReturn (strlen ($ contents ));
128233 $ stream ->isSeekable ()->willReturn ($ seekable );
129234 $ stream ->isReadable ()->willReturn (true );
130- $ stream ->seek (Argument::type ('integer ' ))->will (function ($ args ) use (&$ position ) {
235+ $ stream ->__toString ()->willReturn ($ contents );
236+ $ stream ->getContents ()->willReturn ($ contents );
237+ $ stream ->rewind ()->willReturn (true );
238+
239+ $ stream ->seek (Argument::type ('integer ' ), Argument::any ())->will (function ($ args ) use (&$ position ) {
131240 $ position = $ args [0 ];
132241 return true ;
133242 });
@@ -136,6 +245,10 @@ public function testEmitBodyRange($seekable, $contents, $range, $maxBufferLength
136245 return ! isset ($ contents [$ position ]);
137246 });
138247
248+ $ stream ->tell ()->will (function () use (&$ position ) {
249+ return $ position ;
250+ });
251+
139252 $ stream ->read (Argument::type ('integer ' ))->will (function ($ args ) use (&$ contents , &$ position ) {
140253 $ data = substr ($ contents , $ position , $ args [0 ]);
141254 $ position += strlen ($ data );
@@ -149,7 +262,7 @@ public function testEmitBodyRange($seekable, $contents, $range, $maxBufferLength
149262
150263 ob_start ();
151264 $ this ->emitter ->emit ($ response , $ maxBufferLength );
152- $ stream ->seek (Argument::type ('integer ' ))->shouldBeCalledTimes ($ seekable ? 1 : 0 );
265+ $ stream ->seek (Argument::type ('integer ' ), Argument:: any () )->shouldBeCalledTimes ($ seekable ? 1 : 0 );
153266 $ stream ->read (Argument::type ('integer ' ))->shouldBeCalledTimes ($ expectedReads );
154267 $ this ->assertEquals (substr ($ contents , $ first , $ last - $ first + 1 ), ob_get_clean ());
155268 }
0 commit comments