99
1010namespace ZendTest \Diactoros \Response ;
1111
12- use PHPUnit_Framework_TestCase as TestCase ;
13- use Psr \Http \Message \ResponseInterface ;
14- use Psr \Http \Message \StreamInterface ;
12+ use Prophecy \Argument ;
1513use Zend \Diactoros \CallbackStream ;
1614use Zend \Diactoros \Response ;
1715use Zend \Diactoros \Response \SapiStreamEmitter ;
@@ -43,6 +41,7 @@ public function testDoesNotInjectContentLengthHeaderIfStreamSizeIsUnknown()
4341 $ stream = $ this ->prophesize ('Psr\Http\Message\StreamInterface ' );
4442 $ stream ->__toString ()->willReturn ('Content! ' );
4543 $ stream ->isSeekable ()->willReturn (false );
44+ $ stream ->isReadable ()->willReturn (false );
4645 $ stream ->eof ()->willReturn (true );
4746 $ stream ->rewind ()->willReturn (true );
4847 $ stream ->getSize ()->willReturn (null );
@@ -58,6 +57,218 @@ public function testDoesNotInjectContentLengthHeaderIfStreamSizeIsUnknown()
5857 }
5958 }
6059
60+ public function emitBodyProvider ()
61+ {
62+ return [
63+ [true , '01234567890123456789 ' , 10 , 2 ],
64+ [true , '012345678901234567890123 ' , 10 , 3 ],
65+ [false , '01234567890123456789 ' , 10 , 2 ],
66+ [false , '012345678901234567890123 ' , 10 , 3 ],
67+ ];
68+ }
69+
70+ /**
71+ * @dataProvider emitBodyProvider
72+ */
73+ public function testEmitBody ($ seekable , $ contents , $ maxBufferLength , $ expectedReads = 0 )
74+ {
75+ $ position = 0 ;
76+
77+ $ stream = $ this ->prophesize ('Psr\Http\Message\StreamInterface ' );
78+ $ stream ->getSize ()->willReturn (strlen ($ contents ));
79+ $ stream ->isSeekable ()->willReturn ($ seekable );
80+ $ stream ->isReadable ()->willReturn (true );
81+ $ stream ->__toString ()->willReturn ($ contents );
82+ $ stream ->getContents ()->willReturn ($ contents );
83+ $ stream ->rewind ()->willReturn (true );
84+
85+ $ stream ->eof ()->will (function () use (& $ contents , & $ position ) {
86+ return ! isset ($ contents [$ position ]);
87+ });
88+
89+ $ stream ->read (Argument::type ('integer ' ))->will (function ($ args ) use (& $ contents , & $ position ) {
90+ $ data = substr ($ contents , $ position , $ args [0 ]);
91+ $ position += strlen ($ data );
92+
93+ return $ data ;
94+ });
95+
96+ $ response = (new Response ())
97+ ->withStatus (200 )
98+ ->withBody ($ stream ->reveal ());
99+
100+ ob_start ();
101+ $ this ->emitter ->emit ($ response , $ maxBufferLength );
102+ $ stream ->rewind ()->shouldBeCalledTimes ($ seekable ? 1 : 0 );
103+ $ stream ->read (Argument::type ('integer ' ))->shouldBeCalledTimes ($ expectedReads );
104+ $ this ->assertEquals ($ contents , ob_get_clean ());
105+ }
106+
107+ public function emitMemoryUsageProvider ()
108+ {
109+ return [
110+ [true , 512 , 1000 , 20 , null ],
111+ [true , 8192 , 1000 , 20 , null ],
112+ [false , 512 , 1000 , 20 , null ],
113+ [false , 8192 , 1000 , 20 , null ],
114+ [true , 512 , 1000 , 20 , [25 , 75 ]],
115+ [true , 8192 , 1000 , 20 , [25 , 75 ]],
116+ [true , 512 , 1000 , 20 , [250 , 750 ]],
117+ [true , 8192 , 1000 , 20 , [250 , 750 ]],
118+ [false , 512 , 1000 , 20 , [25 , 75 ]],
119+ [false , 8192 , 1000 , 20 , [25 , 75 ]],
120+ [false , 512 , 1000 , 20 , [250 , 750 ]],
121+ [false , 8192 , 1000 , 20 , [250 , 750 ]],
122+ ];
123+ }
124+
125+ /**
126+ * @dataProvider emitMemoryUsageProvider
127+ */
128+ public function testEmitMemoryUsage ($ seekable , $ maxBufferLength , $ sizeBlocks , $ maxAllowedBlocks , $ rangeBlocks )
129+ {
130+ $ sizeBytes = $ maxBufferLength * $ sizeBlocks ;
131+ $ maxAllowedMemoryUsage = $ maxBufferLength * $ maxAllowedBlocks ;
132+ $ peakBufferLength = 0 ;
133+ $ peakMemoryUsage = 0 ;
134+
135+ $ position = 0 ;
136+
137+ if ($ rangeBlocks ) {
138+ $ first = $ maxBufferLength * $ rangeBlocks [0 ];
139+ $ last = $ maxBufferLength * $ rangeBlocks [1 ];
140+ $ position = $ first ;
141+ }
142+
143+ $ closureTrackMemoryUsage = function () use (& $ peakMemoryUsage ) {
144+ $ peakMemoryUsage = max ($ peakMemoryUsage , memory_get_usage ());
145+ };
146+
147+ $ closureFullContents = function () use (& $ sizeBytes ) {
148+ return str_repeat ('0 ' , $ sizeBytes );
149+ };
150+
151+ $ stream = $ this ->prophesize ('Psr\Http\Message\StreamInterface ' );
152+ $ stream ->getSize ()->willReturn ($ sizeBytes );
153+ $ stream ->isSeekable ()->willReturn ($ seekable );
154+ $ stream ->isReadable ()->willReturn (true );
155+ $ stream ->__toString ()->will ($ closureFullContents );
156+ $ stream ->getContents ()->will ($ closureFullContents );
157+ $ stream ->rewind ()->willReturn (true );
158+
159+ $ stream ->seek (Argument::type ('integer ' ), Argument::any ())->will (function ($ args ) use (& $ position ) {
160+ $ position = $ args [0 ];
161+ return true ;
162+ });
163+
164+ $ stream ->eof ()->will (function () use (& $ sizeBytes , & $ position ) {
165+ return ($ position >= $ sizeBytes );
166+ });
167+
168+ $ stream ->tell ()->will (function () use (& $ position ) {
169+ return $ position ;
170+ });
171+
172+ $ stream ->read (Argument::type ('integer ' ))->will (function ($ args ) use (& $ position , & $ peakBufferLength ) {
173+ if ($ args [0 ] > $ peakBufferLength ) {
174+ $ peakBufferLength = $ args [0 ];
175+ }
176+
177+ $ position += $ args [0 ];
178+
179+ return str_repeat ('0 ' , $ args [0 ]);
180+ });
181+
182+ $ response = (new Response ())
183+ ->withStatus (200 )
184+ ->withBody ($ stream ->reveal ());
185+
186+
187+ if ($ rangeBlocks ) {
188+ $ response = $ response ->withHeader ('Content-Range ' , 'bytes ' . $ first . '- ' . $ last . '/* ' );
189+ }
190+
191+ ob_start (
192+ function () use (& $ closureTrackMemoryUsage ) {
193+ $ closureTrackMemoryUsage ();
194+
195+ return '' ;
196+ },
197+ $ maxBufferLength
198+ );
199+
200+ gc_collect_cycles ();
201+
202+ $ this ->emitter ->emit ($ response , $ maxBufferLength );
203+
204+ ob_end_flush ();
205+
206+ gc_collect_cycles ();
207+
208+ $ localMemoryUsage = memory_get_usage ();
209+
210+ $ this ->assertLessThanOrEqual ($ maxBufferLength , $ peakBufferLength );
211+ $ this ->assertLessThanOrEqual ($ maxAllowedMemoryUsage , $ peakMemoryUsage - $ localMemoryUsage );
212+ }
213+
214+ public function emitBodyRangeProvider ()
215+ {
216+ return [
217+ [true , '01234567890123456789 ' , ['bytes ' , 10 , 20 , '* ' ], 10 , 1 ],
218+ [true , '012345678901234567890123 ' , ['bytes ' , 10 , 40 , '* ' ], 10 , 2 ],
219+ [false , '01234567890123456789 ' , ['bytes ' , 11 , 20 , '* ' ], 10 , 1 ],
220+ [false , '012345678901234567890123 ' , ['bytes ' , 11 , 40 , '* ' ], 10 , 2 ],
221+ ];
222+ }
223+
224+ /**
225+ * @dataProvider emitBodyRangeProvider
226+ */
227+ public function testEmitBodyRange ($ seekable , $ contents , $ range , $ maxBufferLength , $ expectedReads = 0 )
228+ {
229+ list ($ unit , $ first , $ last , $ length ) = $ range ;
230+
231+ $ position = $ first ;
232+
233+ $ stream = $ this ->prophesize ('Psr\Http\Message\StreamInterface ' );
234+ $ stream ->getSize ()->willReturn (strlen ($ contents ));
235+ $ stream ->isSeekable ()->willReturn ($ seekable );
236+ $ stream ->isReadable ()->willReturn (true );
237+ $ stream ->__toString ()->willReturn ($ contents );
238+ $ stream ->getContents ()->willReturn ($ contents );
239+ $ stream ->rewind ()->willReturn (true );
240+
241+ $ stream ->seek (Argument::type ('integer ' ), Argument::any ())->will (function ($ args ) use (& $ position ) {
242+ $ position = $ args [0 ];
243+ return true ;
244+ });
245+
246+ $ stream ->eof ()->will (function () use (& $ contents , & $ position ) {
247+ return ! isset ($ contents [$ position ]);
248+ });
249+
250+ $ stream ->tell ()->will (function () use (& $ position ) {
251+ return $ position ;
252+ });
253+
254+ $ stream ->read (Argument::type ('integer ' ))->will (function ($ args ) use (& $ contents , & $ position ) {
255+ $ data = substr ($ contents , $ position , $ args [0 ]);
256+ $ position += strlen ($ data );
257+ return $ data ;
258+ });
259+
260+ $ response = (new Response ())
261+ ->withStatus (200 )
262+ ->withHeader ('Content-Range ' , "$ unit $ first- $ last/ $ length " )
263+ ->withBody ($ stream ->reveal ());
264+
265+ ob_start ();
266+ $ this ->emitter ->emit ($ response , $ maxBufferLength );
267+ $ stream ->seek (Argument::type ('integer ' ), Argument::any ())->shouldBeCalledTimes ($ seekable ? 1 : 0 );
268+ $ stream ->read (Argument::type ('integer ' ))->shouldBeCalledTimes ($ expectedReads );
269+ $ this ->assertEquals (substr ($ contents , $ first , $ last - $ first + 1 ), ob_get_clean ());
270+ }
271+
61272 public function contentRangeProvider ()
62273 {
63274 return [
0 commit comments