1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39: 40: 41: 42: 43: 44: 45: 46: 47: 48: 49: 50: 51: 52: 53: 54: 55: 56: 57: 58: 59: 60: 61: 62: 63: 64: 65: 66: 67: 68: 69: 70: 71: 72: 73: 74: 75: 76: 77: 78: 79: 80: 81: 82: 83: 84: 85: 86: 87: 88: 89: 90: 91: 92: 93: 94: 95: 96: 97: 98: 99: 100: 101: 102: 103: 104: 105: 106: 107: 108: 109: 110: 111: 112: 113: 114: 115: 116: 117: 118: 119: 120: 121: 122: 123: 124: 125: 126: 127: 128: 129: 130: 131: 132: 133: 134: 135: 136: 137: 138: 139: 140: 141: 142: 143: 144: 145: 146: 147: 148: 149: 150: 151: 152: 153: 154: 155: 156: 157: 158: 159: 160: 161: 162: 163: 164: 165: 166: 167: 168: 169: 170: 171: 172: 173: 174: 175: 176: 177: 178: 179: 180: 181: 182: 183: 184: 185: 186: 187: 188: 189: 190: 191: 192: 193: 194: 195: 196: 197: 198: 199: 200: 201: 202: 203: 204: 205: 206: 207: 208: 209: 210: 211: 212: 213: 214: 215: 216: 217: 218: 219: 220: 221: 222: 223: 224: 225: 226: 227: 228: 229: 230: 231: 232: 233: 234: 235: 236: 237: 238: 239: 240: 241: 242: 243: 244: 245: 246: 247: 248: 249: 250: 251: 252: 253: 254: 255: 256: 257: 258: 259: 260: 261: 262: 263: 264: 265: 266: 267: 268: 269: 270: 271: 272: 273: 274: 275: 276: 277: 278: 279: 280: 281: 282: 283: 284: 285: 286: 287: 288: 289: 290: 291: 292: 293: 294: 295: 296: 297: 298: 299: 300: 301: 302: 303: 304: 305: 306: 307: 308: 309: 310: 311: 312: 313: 314: 315: 316: 317: 318: 319: 320: 321: 322: 323: 324: 325: 326: 327: 328: 329: 330: 331: 332: 333: 334: 335: 336: 337: 338: 339: 340: 341: 342: 343: 344: 345: 346: 347: 348: 349: 350: 351: 352: 353: 354: 355: 356: 357: 358: 359: 360: 361: 362: 363: 364: 365: 366: 367: 368: 369: 370: 371: 372: 373: 374: 375: 376: 377: 378: 379: 380: 381: 382: 383: 384: 385: 386: 387: 388: 389: 390: 391: 392: 393: 394: 395: 396: 397: 398: 399: 400: 401: 402: 403: 404: 405: 406: 407: 408: 409: 410: 411: 412: 413: 414: 415: 416: 417: 418: 419: 420: 421: 422: 423: 424: 425: 426: 427: 428: 429: 430: 431: 432: 433: 434: 435: 436: 437: 438: 439: 440: 441: 442: 443: 444: 445: 446: 447: 448: 449: 450: 451: 452: 453: 454: 455: 456: 457: 458: 459: 460: 461: 462: 463: 464: 465: 466: 467: 468: 469: 470: 471: 472: 473: 474: 475: 476: 477: 478: 479: 480: 481: 482: 483: 484: 485: 486: 487: 488: 489: 490: 491: 492: 493: 494: 495: 496: 497: 498: 499: 500: 501: 502: 503: 504: 505: 506: 507: 508: 509: 510: 511: 512: 513: 514: 515: 516: 517: 518: 519: 520: 521: 522: 523: 524: 525: 526: 527: 528: 529: 530: 531: 532: 533: 534: 535: 536: 537: 538: 539: 540: 541: 542: 543: 544: 545: 546: 547: 548: 549: 550: 551: 552: 553: 554: 555: 556: 557: 558: 559: 560: 561: 562: 563: 564: 565: 566: 567: 568: 569: 570: 571: 572: 573: 574: 575: 576: 577: 578: 579: 580: 581: 582: 583: 584: 585: 586: 587: 588: 589: 590: 591: 592: 593: 594: 595: 596: 597: 598: 599: 600: 601: 602: 603: 604: 605: 606: 607: 608: 609: 610: 611: 612: 613: 614: 615: 616: 617: 618: 619: 620: 621: 622: 623: 624: 625: 626: 627: 628: 629: 630: 631: 632: 633: 634: 635: 636: 637: 638: 639: 640: 641: 642: 643: 644: 645: 646: 647: 648: 649: 650: 651: 652: 653: 654: 655: 656: 657: 658: 659: 660: 661: 662: 663: 664: 665: 666: 667: 668: 669: 670: 671: 672: 673: 674: 675: 676: 677: 678: 679: 680: 681: 682: 683: 684:
<?php
namespace GuzzleHttp\Test\Handler;
use GuzzleHttp\Exception\ConnectException;
use GuzzleHttp\Handler\StreamHandler;
use GuzzleHttp\Psr7;
use GuzzleHttp\Psr7\Request;
use GuzzleHttp\Psr7\Response;
use GuzzleHttp\Psr7\FnStream;
use GuzzleHttp\RequestOptions;
use GuzzleHttp\Tests\Server;
use GuzzleHttp\TransferStats;
use Psr\Http\Message\ResponseInterface;
use PHPUnit\Framework\TestCase;
class StreamHandlerTest extends TestCase
{
private function queueRes()
{
Server::flush();
Server::enqueue([
new Response(200, [
'Foo' => 'Bar',
'Content-Length' => 8,
], 'hi there')
]);
}
public function testReturnsResponseForSuccessfulRequest()
{
$this->queueRes();
$handler = new StreamHandler();
$response = $handler(
new Request('GET', Server::$url, ['Foo' => 'Bar']),
[]
)->wait();
$this->assertEquals(200, $response->getStatusCode());
$this->assertEquals('OK', $response->getReasonPhrase());
$this->assertEquals('Bar', $response->getHeaderLine('Foo'));
$this->assertEquals('8', $response->getHeaderLine('Content-Length'));
$this->assertEquals('hi there', (string) $response->getBody());
$sent = Server::received()[0];
$this->assertEquals('GET', $sent->getMethod());
$this->assertEquals('/', $sent->getUri()->getPath());
$this->assertEquals('127.0.0.1:8126', $sent->getHeaderLine('Host'));
$this->assertEquals('Bar', $sent->getHeaderLine('foo'));
}
public function testAddsErrorToResponse()
{
$handler = new StreamHandler();
$handler(
new Request('GET', 'http://localhost:123'),
['timeout' => 0.01]
)->wait();
}
public function testStreamAttributeKeepsStreamOpen()
{
$this->queueRes();
$handler = new StreamHandler();
$request = new Request(
'PUT',
Server::$url . 'foo?baz=bar',
['Foo' => 'Bar'],
'test'
);
$response = $handler($request, ['stream' => true])->wait();
$this->assertEquals(200, $response->getStatusCode());
$this->assertEquals('OK', $response->getReasonPhrase());
$this->assertEquals('8', $response->getHeaderLine('Content-Length'));
$body = $response->getBody();
$stream = $body->detach();
$this->assertInternalType('resource', $stream);
$this->assertEquals('http', stream_get_meta_data($stream)['wrapper_type']);
$this->assertEquals('hi there', stream_get_contents($stream));
fclose($stream);
$sent = Server::received()[0];
$this->assertEquals('PUT', $sent->getMethod());
$this->assertEquals('http://127.0.0.1:8126/foo?baz=bar', (string) $sent->getUri());
$this->assertEquals('Bar', $sent->getHeaderLine('Foo'));
$this->assertEquals('test', (string) $sent->getBody());
}
public function testDrainsResponseIntoTempStream()
{
$this->queueRes();
$handler = new StreamHandler();
$request = new Request('GET', Server::$url);
$response = $handler($request, [])->wait();
$body = $response->getBody();
$stream = $body->detach();
$this->assertEquals('php://temp', stream_get_meta_data($stream)['uri']);
$this->assertEquals('hi', fread($stream, 2));
fclose($stream);
}
public function testDrainsResponseIntoSaveToBody()
{
$r = fopen('php://temp', 'r+');
$this->queueRes();
$handler = new StreamHandler();
$request = new Request('GET', Server::$url);
$response = $handler($request, ['sink' => $r])->wait();
$body = $response->getBody()->detach();
$this->assertEquals('php://temp', stream_get_meta_data($body)['uri']);
$this->assertEquals('hi', fread($body, 2));
$this->assertEquals(' there', stream_get_contents($r));
fclose($r);
}
public function testDrainsResponseIntoSaveToBodyAtPath()
{
$tmpfname = tempnam('/tmp', 'save_to_path');
$this->queueRes();
$handler = new StreamHandler();
$request = new Request('GET', Server::$url);
$response = $handler($request, ['sink' => $tmpfname])->wait();
$body = $response->getBody();
$this->assertEquals($tmpfname, $body->getMetadata('uri'));
$this->assertEquals('hi', $body->read(2));
$body->close();
unlink($tmpfname);
}
public function testDrainsResponseIntoSaveToBodyAtNonExistentPath()
{
$tmpfname = tempnam('/tmp', 'save_to_path');
unlink($tmpfname);
$this->queueRes();
$handler = new StreamHandler();
$request = new Request('GET', Server::$url);
$response = $handler($request, ['sink' => $tmpfname])->wait();
$body = $response->getBody();
$this->assertEquals($tmpfname, $body->getMetadata('uri'));
$this->assertEquals('hi', $body->read(2));
$body->close();
unlink($tmpfname);
}
public function testDrainsResponseAndReadsOnlyContentLengthBytes()
{
Server::flush();
Server::enqueue([
new Response(200, [
'Foo' => 'Bar',
'Content-Length' => 8,
], 'hi there... This has way too much data!')
]);
$handler = new StreamHandler();
$request = new Request('GET', Server::$url);
$response = $handler($request, [])->wait();
$body = $response->getBody();
$stream = $body->detach();
$this->assertEquals('hi there', stream_get_contents($stream));
fclose($stream);
}
public function testDoesNotDrainWhenHeadRequest()
{
Server::flush();
Server::enqueue([
new Response(200, [
'Foo' => 'Bar',
'Content-Length' => 8,
], '')
]);
$handler = new StreamHandler();
$request = new Request('HEAD', Server::$url);
$response = $handler($request, [])->wait();
$body = $response->getBody();
$stream = $body->detach();
$this->assertEquals('', stream_get_contents($stream));
fclose($stream);
}
public function testAutomaticallyDecompressGzip()
{
Server::flush();
$content = gzencode('test');
Server::enqueue([
new Response(200, [
'Content-Encoding' => 'gzip',
'Content-Length' => strlen($content),
], $content)
]);
$handler = new StreamHandler();
$request = new Request('GET', Server::$url);
$response = $handler($request, ['decode_content' => true])->wait();
$this->assertEquals('test', (string) $response->getBody());
$this->assertFalse($response->hasHeader('content-encoding'));
$this->assertTrue(!$response->hasHeader('content-length') || $response->getHeaderLine('content-length') == $response->getBody()->getSize());
}
public function testReportsOriginalSizeAndContentEncodingAfterDecoding()
{
Server::flush();
$content = gzencode('test');
Server::enqueue([
new Response(200, [
'Content-Encoding' => 'gzip',
'Content-Length' => strlen($content),
], $content)
]);
$handler = new StreamHandler();
$request = new Request('GET', Server::$url);
$response = $handler($request, ['decode_content' => true])->wait();
$this->assertSame(
'gzip',
$response->getHeaderLine('x-encoded-content-encoding')
);
$this->assertSame(
strlen($content),
(int) $response->getHeaderLine('x-encoded-content-length')
);
}
public function testDoesNotForceGzipDecode()
{
Server::flush();
$content = gzencode('test');
Server::enqueue([
new Response(200, [
'Content-Encoding' => 'gzip',
'Content-Length' => strlen($content),
], $content)
]);
$handler = new StreamHandler();
$request = new Request('GET', Server::$url);
$response = $handler($request, ['decode_content' => false])->wait();
$this->assertSame($content, (string) $response->getBody());
$this->assertEquals('gzip', $response->getHeaderLine('content-encoding'));
$this->assertEquals(strlen($content), $response->getHeaderLine('content-length'));
}
public function testProtocolVersion()
{
$this->queueRes();
$handler = new StreamHandler();
$request = new Request('GET', Server::$url, [], null, '1.0');
$handler($request, []);
$this->assertEquals('1.0', Server::received()[0]->getProtocolVersion());
}
protected function getSendResult(array $opts)
{
$this->queueRes();
$handler = new StreamHandler();
$opts['stream'] = true;
$request = new Request('GET', Server::$url);
return $handler($request, $opts)->wait();
}
public function testAddsProxy()
{
$this->getSendResult(['proxy' => '127.0.0.1:8125']);
}
public function testAddsProxyByProtocol()
{
$url = str_replace('http', 'tcp', Server::$url);
$url = rtrim($url, '/');
$res = $this->getSendResult(['proxy' => ['http' => $url]]);
$opts = stream_context_get_options($res->getBody()->detach());
$this->assertEquals($url, $opts['http']['proxy']);
}
public function testAddsProxyButHonorsNoProxy()
{
$url = str_replace('http', 'tcp', Server::$url);
$res = $this->getSendResult(['proxy' => [
'http' => $url,
'no' => ['*']
]]);
$opts = stream_context_get_options($res->getBody()->detach());
$this->assertTrue(empty($opts['http']['proxy']));
}
public function testAddsTimeout()
{
$res = $this->getSendResult(['stream' => true, 'timeout' => 200]);
$opts = stream_context_get_options($res->getBody()->detach());
$this->assertEquals(200, $opts['http']['timeout']);
}
public function testVerifiesVerifyIsValidIfPath()
{
$this->getSendResult(['verify' => '/does/not/exist']);
}
public function testVerifyCanBeDisabled()
{
$this->getSendResult(['verify' => false]);
}
public function testVerifiesCertIfValidPath()
{
$this->getSendResult(['cert' => '/does/not/exist']);
}
public function testVerifyCanBeSetToPath()
{
$path = $path = \GuzzleHttp\default_ca_bundle();
$res = $this->getSendResult(['verify' => $path]);
$opts = stream_context_get_options($res->getBody()->detach());
$this->assertTrue($opts['ssl']['verify_peer']);
$this->assertTrue($opts['ssl']['verify_peer_name']);
$this->assertEquals($path, $opts['ssl']['cafile']);
$this->assertFileExists($opts['ssl']['cafile']);
}
public function testUsesSystemDefaultBundle()
{
$path = $path = \GuzzleHttp\default_ca_bundle();
$res = $this->getSendResult(['verify' => true]);
$opts = stream_context_get_options($res->getBody()->detach());
if (PHP_VERSION_ID < 50600) {
$this->assertEquals($path, $opts['ssl']['cafile']);
}
}
public function testEnsuresVerifyOptionIsValid()
{
$this->getSendResult(['verify' => 10]);
}
public function testCanSetPasswordWhenSettingCert()
{
$path = __FILE__;
$res = $this->getSendResult(['cert' => [$path, 'foo']]);
$opts = stream_context_get_options($res->getBody()->detach());
$this->assertEquals($path, $opts['ssl']['local_cert']);
$this->assertEquals('foo', $opts['ssl']['passphrase']);
}
public function testDebugAttributeWritesToStream()
{
$this->queueRes();
$f = fopen('php://temp', 'w+');
$this->getSendResult(['debug' => $f]);
fseek($f, 0);
$contents = stream_get_contents($f);
$this->assertContains('<GET http://127.0.0.1:8126/> [CONNECT]', $contents);
$this->assertContains('<GET http://127.0.0.1:8126/> [FILE_SIZE_IS]', $contents);
$this->assertContains('<GET http://127.0.0.1:8126/> [PROGRESS]', $contents);
}
public function testDebugAttributeWritesStreamInfoToBuffer()
{
$called = false;
$this->queueRes();
$buffer = fopen('php://temp', 'r+');
$this->getSendResult([
'progress' => function () use (&$called) { $called = true; },
'debug' => $buffer,
]);
fseek($buffer, 0);
$contents = stream_get_contents($buffer);
$this->assertContains('<GET http://127.0.0.1:8126/> [CONNECT]', $contents);
$this->assertContains('<GET http://127.0.0.1:8126/> [FILE_SIZE_IS] message: "Content-Length: 8"', $contents);
$this->assertContains('<GET http://127.0.0.1:8126/> [PROGRESS] bytes_max: "8"', $contents);
$this->assertTrue($called);
}
public function testEmitsProgressInformation()
{
$called = [];
$this->queueRes();
$this->getSendResult([
'progress' => function () use (&$called) {
$called[] = func_get_args();
},
]);
$this->assertNotEmpty($called);
$this->assertEquals(8, $called[0][0]);
$this->assertEquals(0, $called[0][1]);
}
public function testEmitsProgressInformationAndDebugInformation()
{
$called = [];
$this->queueRes();
$buffer = fopen('php://memory', 'w+');
$this->getSendResult([
'debug' => $buffer,
'progress' => function () use (&$called) {
$called[] = func_get_args();
},
]);
$this->assertNotEmpty($called);
$this->assertEquals(8, $called[0][0]);
$this->assertEquals(0, $called[0][1]);
rewind($buffer);
$this->assertNotEmpty(stream_get_contents($buffer));
fclose($buffer);
}
public function testPerformsShallowMergeOfCustomContextOptions()
{
$res = $this->getSendResult([
'stream_context' => [
'http' => [
'request_fulluri' => true,
'method' => 'HEAD',
],
'socket' => [
'bindto' => '127.0.0.1:0',
],
'ssl' => [
'verify_peer' => false,
],
],
]);
$opts = stream_context_get_options($res->getBody()->detach());
$this->assertEquals('HEAD', $opts['http']['method']);
$this->assertTrue($opts['http']['request_fulluri']);
$this->assertEquals('127.0.0.1:0', $opts['socket']['bindto']);
$this->assertFalse($opts['ssl']['verify_peer']);
}
public function testEnsuresThatStreamContextIsAnArray()
{
$this->getSendResult(['stream_context' => 'foo']);
}
public function testDoesNotAddContentTypeByDefault()
{
$this->queueRes();
$handler = new StreamHandler();
$request = new Request('PUT', Server::$url, ['Content-Length' => 3], 'foo');
$handler($request, []);
$req = Server::received()[0];
$this->assertEquals('', $req->getHeaderLine('Content-Type'));
$this->assertEquals(3, $req->getHeaderLine('Content-Length'));
}
public function testAddsContentLengthByDefault()
{
$this->queueRes();
$handler = new StreamHandler();
$request = new Request('PUT', Server::$url, [], 'foo');
$handler($request, []);
$req = Server::received()[0];
$this->assertEquals(3, $req->getHeaderLine('Content-Length'));
}
public function testAddsContentLengthEvenWhenEmpty()
{
$this->queueRes();
$handler = new StreamHandler();
$request = new Request('PUT', Server::$url, [], '');
$handler($request, []);
$req = Server::received()[0];
$this->assertEquals(0, $req->getHeaderLine('Content-Length'));
}
public function testSupports100Continue()
{
Server::flush();
$response = new Response(200, ['Test' => 'Hello', 'Content-Length' => '4'], 'test');
Server::enqueue([$response]);
$request = new Request('PUT', Server::$url, ['Expect' => '100-Continue'], 'test');
$handler = new StreamHandler();
$response = $handler($request, [])->wait();
$this->assertEquals(200, $response->getStatusCode());
$this->assertEquals('Hello', $response->getHeaderLine('Test'));
$this->assertEquals('4', $response->getHeaderLine('Content-Length'));
$this->assertEquals('test', (string) $response->getBody());
}
public function testDoesSleep()
{
$response = new response(200);
Server::enqueue([$response]);
$a = new StreamHandler();
$request = new Request('GET', Server::$url);
$s = microtime(true);
$a($request, ['delay' => 0.1])->wait();
$this->assertGreaterThan(0.0001, microtime(true) - $s);
}
public function testEnsuresOnHeadersIsCallable()
{
$req = new Request('GET', Server::$url);
$handler = new StreamHandler();
$handler($req, ['on_headers' => 'error!']);
}
public function testRejectsPromiseWhenOnHeadersFails()
{
Server::flush();
Server::enqueue([
new Response(200, ['X-Foo' => 'bar'], 'abc 123')
]);
$req = new Request('GET', Server::$url);
$handler = new StreamHandler();
$promise = $handler($req, [
'on_headers' => function () {
throw new \Exception('test');
}
]);
$promise->wait();
}
public function testSuccessfullyCallsOnHeadersBeforeWritingToSink()
{
Server::flush();
Server::enqueue([
new Response(200, ['X-Foo' => 'bar'], 'abc 123')
]);
$req = new Request('GET', Server::$url);
$got = null;
$stream = Psr7\stream_for();
$stream = FnStream::decorate($stream, [
'write' => function ($data) use ($stream, &$got) {
$this->assertNotNull($got);
return $stream->write($data);
}
]);
$handler = new StreamHandler();
$promise = $handler($req, [
'sink' => $stream,
'on_headers' => function (ResponseInterface $res) use (&$got) {
$got = $res;
$this->assertEquals('bar', $res->getHeaderLine('X-Foo'));
}
]);
$response = $promise->wait();
$this->assertEquals(200, $response->getStatusCode());
$this->assertEquals('bar', $response->getHeaderLine('X-Foo'));
$this->assertEquals('abc 123', (string) $response->getBody());
}
public function testInvokesOnStatsOnSuccess()
{
Server::flush();
Server::enqueue([new Psr7\Response(200)]);
$req = new Psr7\Request('GET', Server::$url);
$gotStats = null;
$handler = new StreamHandler();
$promise = $handler($req, [
'on_stats' => function (TransferStats $stats) use (&$gotStats) {
$gotStats = $stats;
}
]);
$response = $promise->wait();
$this->assertEquals(200, $response->getStatusCode());
$this->assertEquals(200, $gotStats->getResponse()->getStatusCode());
$this->assertEquals(
Server::$url,
(string) $gotStats->getEffectiveUri()
);
$this->assertEquals(
Server::$url,
(string) $gotStats->getRequest()->getUri()
);
$this->assertGreaterThan(0, $gotStats->getTransferTime());
}
public function testInvokesOnStatsOnError()
{
$req = new Psr7\Request('GET', 'http://127.0.0.1:123');
$gotStats = null;
$handler = new StreamHandler();
$promise = $handler($req, [
'connect_timeout' => 0.001,
'timeout' => 0.001,
'on_stats' => function (TransferStats $stats) use (&$gotStats) {
$gotStats = $stats;
}
]);
$promise->wait(false);
$this->assertFalse($gotStats->hasResponse());
$this->assertEquals(
'http://127.0.0.1:123',
(string) $gotStats->getEffectiveUri()
);
$this->assertEquals(
'http://127.0.0.1:123',
(string) $gotStats->getRequest()->getUri()
);
$this->assertInternalType('float', $gotStats->getTransferTime());
$this->assertInstanceOf(
ConnectException::class,
$gotStats->getHandlerErrorData()
);
}
public function testStreamIgnoresZeroTimeout()
{
Server::flush();
Server::enqueue([new Psr7\Response(200)]);
$req = new Psr7\Request('GET', Server::$url);
$gotStats = null;
$handler = new StreamHandler();
$promise = $handler($req, [
'connect_timeout' => 10,
'timeout' => 0
]);
$response = $promise->wait();
$this->assertEquals(200, $response->getStatusCode());
}
public function testDrainsResponseAndReadsAllContentWhenContentLengthIsZero()
{
Server::flush();
Server::enqueue([
new Response(200, [
'Foo' => 'Bar',
'Content-Length' => '0',
], 'hi there... This has a lot of data!')
]);
$handler = new StreamHandler();
$request = new Request('GET', Server::$url);
$response = $handler($request, [])->wait();
$body = $response->getBody();
$stream = $body->detach();
$this->assertEquals('hi there... This has a lot of data!', stream_get_contents($stream));
fclose($stream);
}
public function testHonorsReadTimeout()
{
Server::flush();
$handler = new StreamHandler();
$response = $handler(
new Request('GET', Server::$url . 'guzzle-server/read-timeout'),
[
RequestOptions::READ_TIMEOUT => 1,
RequestOptions::STREAM => true,
]
)->wait();
$this->assertEquals(200, $response->getStatusCode());
$this->assertEquals('OK', $response->getReasonPhrase());
$body = $response->getBody()->detach();
$line = fgets($body);
$this->assertEquals("sleeping 60 seconds ...\n", $line);
$line = fgets($body);
$this->assertFalse($line);
$this->assertTrue(stream_get_meta_data($body)['timed_out']);
$this->assertFalse(feof($body));
}
}