1<?php
2
3/**
4 * Hoa
5 *
6 *
7 * @license
8 *
9 * New BSD License
10 *
11 * Copyright © 2007-2017, Hoa community. All rights reserved.
12 *
13 * Redistribution and use in source and binary forms, with or without
14 * modification, are permitted provided that the following conditions are met:
15 *     * Redistributions of source code must retain the above copyright
16 *       notice, this list of conditions and the following disclaimer.
17 *     * Redistributions in binary form must reproduce the above copyright
18 *       notice, this list of conditions and the following disclaimer in the
19 *       documentation and/or other materials provided with the distribution.
20 *     * Neither the name of the Hoa nor the names of its contributors may be
21 *       used to endorse or promote products derived from this software without
22 *       specific prior written permission.
23 *
24 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
25 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
26 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
27 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS AND CONTRIBUTORS BE
28 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
29 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
30 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
31 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
32 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
33 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
34 * POSSIBILITY OF SUCH DAMAGE.
35 */
36
37namespace Hoa\Stream\Test\Unit;
38
39use Hoa\Event;
40use Hoa\Stream as LUT;
41use Hoa\Test;
42
43/**
44 * Class \Hoa\Stream\Test\Unit\Stream.
45 *
46 * Test suite of the stream class.
47 *
48 * @copyright  Copyright © 2007-2017 Hoa community
49 * @license    New BSD License
50 */
51class Stream extends Test\Unit\Suite
52{
53    public function case_interfaces()
54    {
55        $this
56            ->when($result = new SUT(__FILE__))
57            ->then
58                ->object($result)
59                    ->isInstanceOf(LUT\IStream\Stream::class)
60                    ->isInstanceOf(Event\Listenable::class);
61    }
62
63    public function case_constants()
64    {
65        $this
66            ->integer(SUT::NAME)
67                ->isEqualTo(0)
68            ->integer(SUT::HANDLER)
69                ->isEqualTo(1)
70            ->integer(SUT::RESOURCE)
71                ->isEqualTo(2)
72            ->integer(SUT::CONTEXT)
73                ->isEqualTo(3);
74    }
75
76    public function case_construct()
77    {
78        $this
79            ->given($name = __FILE__)
80            ->when($result = new SUT($name))
81            ->then
82                ->string($result->getStreamName())
83                    ->isEqualTo($name)
84                ->boolean($this->invoke($result)->hasBeenDeferred())
85                    ->isFalse()
86                ->let($listener = $this->invoke($result)->getListener())
87                ->object($listener)
88                    ->isInstanceOf(Event\Listener::class)
89                ->boolean($listener->listenerExists('authrequire'))
90                    ->isTrue()
91                ->boolean($listener->listenerExists('authresult'))
92                    ->isTrue()
93                ->boolean($listener->listenerExists('complete'))
94                    ->isTrue()
95                ->boolean($listener->listenerExists('connect'))
96                    ->isTrue()
97                ->boolean($listener->listenerExists('failure'))
98                    ->isTrue()
99                ->boolean($listener->listenerExists('mimetype'))
100                    ->isTrue()
101                ->boolean($listener->listenerExists('progress'))
102                    ->isTrue()
103                ->boolean($listener->listenerExists('redirect'))
104                    ->isTrue()
105                ->boolean($listener->listenerExists('resolve'))
106                    ->isTrue()
107                ->boolean($listener->listenerExists('size'))
108                    ->isTrue()
109                ->boolean(Event::eventExists('hoa://Event/Stream/' . $name))
110                    ->isTrue()
111                ->boolean(Event::eventExists('hoa://Event/Stream/' . $name . ':close-before'))
112                    ->isTrue();
113    }
114
115    public function case_construct_with_a_context()
116    {
117        $this
118            ->given(
119                $name        = __FILE__,
120                $contextName = 'foo',
121                LUT\Context::getInstance($contextName)
122            )
123            ->when($result = new SUT($name, $contextName))
124            ->then
125                ->string($result->getStreamName())
126                    ->isEqualTo($name)
127                ->boolean($this->invoke($result)->hasBeenDeferred())
128                    ->isFalse()
129                ->object($this->invoke($result)->getListener())
130                    ->isInstanceOf(Event\Listener::class);
131    }
132
133    public function case_construct_with_deferred_opening()
134    {
135        $this
136            ->given($name = __FILE__)
137            ->when($result = new SUT($name, null, true))
138            ->then
139                ->boolean($this->invoke($result)->hasBeenDeferred())
140                    ->isTrue()
141                ->boolean($result->isOpened())
142                    ->isFalse()
143                ->variable($result->getStreamName())
144                    ->isNull();
145    }
146
147    public function case_open()
148    {
149        $this
150            ->given(
151                $name   = __FILE__,
152                $stream = new SUT($name, null, true)
153            )
154            ->when($result = $stream->open())
155            ->then
156                ->object($result)
157                    ->isIdenticalTo($stream)
158                ->boolean($this->invoke($result)->hasBeenDeferred())
159                    ->isTrue()
160                ->boolean($result->isOpened())
161                    ->isTrue()
162                ->string($result->getStreamName())
163                    ->isEqualTo($name)
164                ->integer($result->getStreamBufferSize())
165                    ->isEqualTo(SUT::DEFAULT_BUFFER_SIZE);
166    }
167
168    public function case_close()
169    {
170        $this
171            ->given(
172                $name     = __FILE__,
173                $stream   = new SUT($name),
174                $resource = $stream->getStream(),
175                $context  = $stream->getStreamContext()
176            )
177            ->when($result = $stream->close())
178            ->then
179                ->variable($result)
180                    ->isNull()
181                ->boolean($stream->isOpened())
182                    ->isFalse()
183                ->variable(SUT::getStreamHandler($stream))
184                    ->isNull()
185                ->variable($stream->getStreamName())
186                    ->isEqualTo($name)
187                ->variable($stream->getStream())
188                    ->isEqualTo($resource)
189                ->variable($stream->getStreamContext())
190                    ->isEqualTo($context)
191                ->boolean(Event::eventExists('hoa://Event/Stream/' . $name))
192                    ->isFalse()
193                ->boolean(Event::eventExists('hoa://Event/Stream/' . $name . ':close-before'))
194                    ->isFalse();
195    }
196
197    public function case_close_more_than_once()
198    {
199        $this
200            ->given(
201                $name   = __FILE__,
202                $stream = new SUT($name),
203                $close1 = $stream->close()
204            )
205            ->when($result = $stream->close())
206            ->then
207                ->variable($result)
208                    ->isIdenticalTo($close1);
209    }
210
211    public function case_open_close_open()
212    {
213        $this
214            ->given(
215                $name   = __FILE__,
216                $stream = new SUT($name, null, true),
217                $stream->open(),
218                $resource   = $stream->getStream(),
219                $context    = $stream->getStreamContext(),
220                $handler    = SUT::getStreamHandler($stream),
221
222                $this->function->stream_set_write_buffer = 0,
223
224                $stream->setStreamBuffer(42),
225                $stream->close()
226            )
227            ->when($result = $stream->open())
228            ->then
229                ->string($result->getStreamName())
230                    ->isEqualTo($name)
231                ->resource($result->getStream())
232                    ->isNotEqualTo($resource)
233                ->object($handler)
234                    ->isIdenticalTo($result)
235                ->object($this->invoke($stream)->getListener())
236                    ->isInstanceOf(Event\Listener::class)
237                ->boolean(Event::eventExists('hoa://Event/Stream/' . $name))
238                    ->isTrue()
239                ->boolean(Event::eventExists('hoa://Event/Stream/' . $name . ':close-before'))
240                    ->isTrue()
241                ->integer($stream->getStreamBufferSize())
242                    ->isEqualTo(SUT::DEFAULT_BUFFER_SIZE);
243    }
244
245    public function case_close_event_close_before()
246    {
247        $self = $this;
248
249        $this
250            ->given(
251                $name   = 'hoa://Test/Vfs/Foo?type=file',
252                $stream = new SUT($name),
253                Event::getEvent('hoa://Event/Stream/' . $name . ':close-before')->attach(
254                    function (Event\Bucket $bucket) use ($self, &$called) {
255                        $called = true;
256
257                        $self
258                            ->variable($bucket->getData())
259                                ->isNull()
260                            ->boolean($bucket->getSource()->isOpened())
261                                ->isTrue();
262                    }
263                )
264            )
265            ->when($result = $stream->close())
266            ->then
267                ->boolean($called)
268                    ->isTrue();
269    }
270
271    public function case_get_stream_name()
272    {
273        $this
274            ->given(
275                $name   = __FILE__,
276                $stream = new SUT($name)
277            )
278            ->when($result = $stream->getStreamName())
279            ->then
280                ->string($result)
281                    ->isEqualTo($name);
282    }
283
284    public function case_get_stream()
285    {
286        $this
287            ->given(
288                $name   = __FILE__,
289                $stream = new SUT($name)
290            )
291            ->when($result = $stream->getStream())
292            ->then
293                ->resource($result)
294                    ->isStream($name);
295    }
296
297    public function case_get_stream_context()
298    {
299        $this
300            ->given(
301                $name        = __FILE__,
302                $contextName = 'foo',
303                $context     = LUT\Context::getInstance($contextName),
304                $stream      = new SUT($name, $contextName)
305            )
306            ->when($result = $stream->getStreamContext())
307            ->then
308                ->object($result)
309                    ->isIdenticalTo($context);
310    }
311
312    public function case_get_stream_context_with_no_context_given()
313    {
314        $this
315            ->given(
316                $name   = __FILE__,
317                $stream = new SUT($name)
318            )
319            ->when($result = $stream->getStreamContext())
320            ->then
321                ->variable($result)
322                    ->isNull();
323    }
324
325    public function case_get_stream_handler()
326    {
327        $this
328            ->given(
329                $name   = __FILE__,
330                $stream = new SUT($name)
331            )
332            ->when($result = SUT::getStreamHandler($name))
333            ->then
334                ->object($result)
335                    ->isIdenticalTo($result);
336    }
337
338    public function case_get_stream_handler_of_unknown_stream()
339    {
340        $this
341            ->when($result = SUT::getStreamHandler('foo'))
342            ->then
343                ->variable($result)
344                    ->isNull();
345    }
346
347    public function case__set_stream()
348    {
349        $this
350            ->given(
351                $stream    = new SUT(__FILE__),
352                $oldStream = $stream->getStream(),
353                $newStream = fopen('php://memory', 'rb')
354            )
355            ->when($result = $stream->_setStream($newStream))
356            ->then
357                ->resource($result)
358                    ->isIdenticalTo($oldStream)
359                    ->isStream()
360                ->resource($stream->getStream())
361                    ->isStream()
362                    ->isIdenticalTo($newStream);
363    }
364
365    public function case__set_stream_invalid_resource()
366    {
367        $this
368            ->given($stream = new SUT(__FILE__))
369            ->exception(function () use ($stream) {
370                $stream->_setStream(true);
371            })
372                ->isInstanceOf(LUT\Exception::class);
373    }
374
375    public function case__set_stream_unknown_resource()
376    {
377        $this
378            ->given(
379                $stream = new SUT(__FILE__),
380                $oldStream = $stream->getStream(),
381                $newStream = fopen('php://memory', 'rb'),
382                $this->function->is_resource       = false,
383                $this->function->gettype           = 'resource',
384                $this->function->get_resource_type = 'Unknown'
385            )
386            ->when($result = $stream->_setStream($newStream))
387            ->then
388                ->resource($result)
389                    ->isIdenticalTo($oldStream)
390                    ->isStream()
391                ->resource($stream->getStream())
392                    ->isStream()
393                    ->isIdenticalTo($newStream);
394    }
395
396    public function case_is_opened()
397    {
398        $this
399            ->given($stream = new SUT(__FILE__))
400            ->when($result = $stream->isOpened())
401            ->then
402                ->boolean($result)
403                    ->isTrue();
404    }
405
406    public function case_is_not_opened()
407    {
408        $this
409            ->given($stream = new SUT(__FILE__, null, true))
410            ->when($result = $stream->isOpened())
411            ->then
412                ->boolean($result)
413                    ->isFalse()
414
415            ->when(
416                $stream->open(),
417                $result = $stream->isOpened()
418            )
419            ->then
420                ->boolean($result)
421                    ->isTrue();
422    }
423
424    public function case_set_stream_timeout()
425    {
426        $self = $this;
427
428        $this
429            ->given(
430                $stream = new SUT(__FILE__),
431
432                $this->function->stream_set_timeout = function ($_stream, $_seconds, $_microseconds) use ($self, $stream, &$called) {
433                    $called = true;
434
435                    $self
436                        ->resource($_stream)
437                            ->isIdenticalTo($stream->getStream())
438                        ->integer($_seconds)
439                            ->isEqualTo(7)
440                        ->integer($_microseconds)
441                            ->isEqualTo(42);
442
443                    return true;
444                }
445            )
446            ->when($result = $stream->setStreamTimeout(7, 42))
447            ->then
448                ->boolean($result)
449                    ->isTrue()
450                ->boolean($called)
451                    ->isTrue();
452    }
453
454    public function case_has_been_deferred()
455    {
456        $this
457            ->given($stream = new SUT(__FILE__, null, true))
458            ->when($result = $this->invoke($stream)->hasBeenDeferred())
459            ->then
460                ->boolean($result)
461                    ->isTrue();
462    }
463
464    public function case_has_not_been_deferred()
465    {
466        $this
467            ->given($stream = new SUT(__FILE__))
468            ->when($result = $this->invoke($stream)->hasBeenDeferred())
469            ->then
470                ->boolean($result)
471                    ->isFalse();
472    }
473
474    public function case_has_timed_out()
475    {
476        $this
477            ->given(
478                $stream = new SUT(__FILE__),
479                $this->function->stream_get_meta_data = [
480                    'timed_out' => true
481                ]
482            )
483            ->when($result = $stream->hasTimedOut())
484            ->then
485                ->boolean($result)
486                    ->isTrue();
487    }
488
489    public function case_has_not_timed_out()
490    {
491        $this
492            ->given(
493                $stream = new SUT(__FILE__),
494                $this->function->stream_get_meta_data = [
495                    'timed_out' => false
496                ]
497            )
498            ->when($result = $stream->hasTimedOut())
499            ->then
500                ->boolean($result)
501                    ->isFalse();
502    }
503
504    public function case_set_stream_blocking()
505    {
506        $self = $this;
507
508        $this
509            ->given(
510                $stream = new SUT(__FILE__),
511
512                $this->function->stream_set_blocking = function ($_stream, $_mode) use ($self, $stream, &$called) {
513                    $called = true;
514
515                    $self
516                        ->resource($_stream)
517                            ->isIdenticalTo($stream->getStream())
518                        ->integer($_mode)
519                            ->isEqualTo(1);
520
521                    return true;
522                }
523            )
524            ->when($result = $stream->setStreamBlocking(true))
525            ->then
526                ->boolean($result)
527                    ->isTrue()
528                ->boolean($called)
529                    ->isTrue();
530    }
531
532    public function case_get_default_stream_buffer_size()
533    {
534        $self = $this;
535
536        $this
537            ->given($stream = new SUT(__FILE__))
538            ->when($result = $stream->getStreamBufferSize())
539            ->then
540            ->integer($result)
541                    ->isEqualTo(8192);
542    }
543
544    public function case_set_stream_buffer()
545    {
546        $self = $this;
547
548        $this
549            ->given(
550                $stream = new SUT(__FILE__),
551
552                $this->function->stream_set_write_buffer = function ($_stream, $_buffer) use ($self, $stream, &$called) {
553                    $called = true;
554
555                    $self
556                        ->resource($_stream)
557                            ->isIdenticalTo($stream->getStream())
558                        ->integer($_buffer)
559                            ->isEqualTo(42);
560
561                    return 0;
562                }
563            )
564            ->when($result = $stream->setStreamBuffer(42))
565            ->then
566                ->boolean($result)
567                    ->isTrue()
568                ->boolean($called)
569                    ->isTrue()
570                ->integer($stream->getStreamBufferSize())
571                    ->isEqualTo(42);
572    }
573
574    public function case_set_stream_buffer_fail()
575    {
576        $self = $this;
577
578        $this
579            ->given(
580                $stream              = new SUT(__FILE__),
581                $oldStreamBufferSize = $stream->getStreamBufferSize(),
582
583                $this->function->stream_set_write_buffer = function ($_stream, $_buffer) use ($self, $stream, &$called) {
584                    $called = true;
585
586                    $self
587                        ->resource($_stream)
588                            ->isIdenticalTo($stream->getStream())
589                        ->integer($_buffer)
590                            ->isEqualTo(42);
591
592                    return 1;
593                }
594            )
595            ->when($result = $stream->setStreamBuffer(42))
596            ->then
597                ->boolean($result)
598                    ->isFalse()
599                ->boolean($called)
600                    ->isTrue()
601                ->integer($stream->getStreamBufferSize())
602                    ->isEqualTo($oldStreamBufferSize);
603    }
604
605    public function case_disable_stream_buffer()
606    {
607        $self = $this;
608
609        $this
610            ->given(
611                $stream = new SUT(__FILE__),
612
613                $this->function->stream_set_write_buffer = function ($_stream, $_buffer) use ($self, $stream, &$called) {
614                    $called = true;
615
616                    $self
617                        ->resource($_stream)
618                            ->isIdenticalTo($stream->getStream())
619                        ->integer($_buffer)
620                            ->isEqualTo(0);
621
622                    return 0;
623                }
624            )
625            ->when($result = $stream->disableStreamBuffer())
626            ->then
627                ->boolean($result)
628                    ->isTrue()
629                ->boolean($called)
630                    ->isTrue()
631                ->integer($stream->getStreamBufferSize())
632                    ->isEqualTo(0);
633    }
634
635    public function case_get_stream_wrapper_name_with_no_wrapper()
636    {
637        $this
638            ->given($stream = new SUT(__FILE__))
639            ->when($result = $stream->getStreamWrapperName())
640            ->then
641                ->string($result)
642                    ->isEqualTo('file');
643    }
644
645    public function case_get_stream_wrapper_name()
646    {
647        $this
648            ->given($stream = new SUT('hoa://Test/Vfs/Foo?type=file'))
649            ->when($result = $stream->getStreamWrapperName())
650            ->then
651                ->string($result)
652                    ->isEqualTo('hoa');
653    }
654
655    public function case_get_stream_meta_data()
656    {
657        $this
658            ->given($stream = new SUT(__FILE__))
659            ->when($result = $stream->getStreamMetaData())
660            ->then
661                ->array($result)
662                    ->isEqualTo([
663                        'timed_out'    => false,
664                        'blocked'      => true,
665                        'eof'          => false,
666                        'wrapper_type' => 'plainfile',
667                        'stream_type'  => 'STDIO',
668                        'mode'         => 'rb',
669                        'unread_bytes' => 0,
670                        'seekable'     => true,
671                        'uri'          => __FILE__
672                    ]);
673    }
674
675    public function case_is_borrowing()
676    {
677        $this
678            ->given(
679                $streamA1 = new SUT(__FILE__),
680                $streamA2 = new SUT(__FILE__)
681            )
682            ->when($result = $streamA2->isBorrowing())
683            ->then
684                ->boolean($result)
685                    ->isTrue()
686                ->boolean($streamA1->isBorrowing())
687                    ->isFalse();
688    }
689
690    public function case_is_not_borrowing()
691    {
692        $this
693            ->given($stream = new SUT(__FILE__))
694            ->when($result = $stream->isBorrowing())
695            ->then
696                ->boolean($result)
697                    ->isFalse();
698    }
699
700    public function case_shutdown_destructor()
701    {
702        $this
703            ->given(
704                $stream = new \Mock\Hoa\Stream\Test\Unit\SUTWithPublicClose(__FILE__),
705                $this->calling($stream)->_close = function () use (&$called) {
706                    $called = true;
707                }
708            )
709            ->when($result = SUT::_Hoa_Stream())
710            ->then
711                ->boolean($called)
712                    ->isTrue();
713    }
714
715    public function case_destruct_an_opened_stream()
716    {
717        $this
718            ->given(
719                $stream = new \Mock\Hoa\Stream\Test\Unit\SUTWithPublicClose(__FILE__),
720                $this->calling($stream)->_close = function () use (&$called) {
721                    $called = true;
722                }
723            )
724            ->when($result = $stream->__destruct())
725            ->then
726                ->boolean($called)
727                    ->isTrue();
728    }
729
730    public function case_destruct_a_deferred_stream()
731    {
732        $this
733            ->given(
734                $stream = new \Mock\Hoa\Stream\Test\Unit\SUTWithPublicClose(__FILE__, null, true),
735                $this->calling($stream)->_close = function () use (&$called) {
736                    $called = true;
737                }
738            )
739            ->when($result = $stream->__destruct())
740            ->then
741                ->variable($called)
742                    ->isNull();
743    }
744
745    public function case_protocol_reach_id()
746    {
747        $this
748            ->given(
749                $name   = 'hoa://Test/Vfs/Foo?type=file',
750                $stream = new SUT($name)
751            )
752            ->when($result = resolve('hoa://Library/Stream#' . $name))
753            ->then
754                ->object($result)
755                    ->isIdenticalTo($stream);
756    }
757
758    public function case_protocol_reach_unknown_id()
759    {
760        $this
761            ->given($name = 'hoa://Test/Vfs/Foo?type=file')
762            ->when($result = resolve('hoa://Library/Stream#' . $name))
763            ->then
764                ->variable($result)
765                    ->isNull();
766    }
767}
768
769class SUT extends LUT\Stream
770{
771    protected function &_open($streamName, LUT\Context $context = null)
772    {
773        if (null === $context) {
774            $out = fopen($streamName, 'rb');
775        } else {
776            $out = fopen($streamName, 'rb', false, $context->getContext());
777        }
778
779        return $out;
780    }
781
782    protected function _close()
783    {
784        return fclose($this->getStream());
785    }
786}
787
788class SUTWithPublicClose extends SUT
789{
790    public function _close()
791    {
792        return parent::_close();
793    }
794}
795