| <?php |
| |
| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| */ |
| |
| declare(strict_types=1); |
| |
| namespace Test\Thrift\Unit\Lib\Server; |
| |
| use phpmock\phpunit\PHPMock; |
| use PHPUnit\Framework\TestCase; |
| use Test\Thrift\Unit\Lib\ReflectionHelper; |
| use Thrift\Exception\TException; |
| use Thrift\Exception\TTransportException; |
| use Thrift\Factory\TProtocolFactory; |
| use Thrift\Factory\TTransportFactoryInterface; |
| use Thrift\Server\TForkingServer; |
| use Thrift\Server\TServerTransport; |
| use Thrift\Transport\TTransport; |
| |
| class TForkingServerTest extends TestCase |
| { |
| use PHPMock; |
| use ReflectionHelper; |
| |
| private const NO_CONNECTION = 'no connection'; |
| |
| private function createServer( |
| $processor = null, |
| $transport = null, |
| $inputTransportFactory = null, |
| $outputTransportFactory = null, |
| $inputProtocolFactory = null, |
| $outputProtocolFactory = null |
| ): TForkingServer { |
| return new TForkingServer( |
| $processor ?? new \stdClass(), |
| $transport ?? $this->createStub(TServerTransport::class), |
| $inputTransportFactory ?? $this->createStub(TTransportFactoryInterface::class), |
| $outputTransportFactory ?? $this->createStub(TTransportFactoryInterface::class), |
| $inputProtocolFactory ?? $this->createStub(TProtocolFactory::class), |
| $outputProtocolFactory ?? $this->createStub(TProtocolFactory::class) |
| ); |
| } |
| |
| public function testStopClosesTransportAndSetsFlag() |
| { |
| $transport = $this->createMock(TServerTransport::class); |
| $transport->expects($this->once())->method('close'); |
| |
| $server = $this->createServer(null, $transport); |
| $server->stop(); |
| |
| $this->assertTrue($this->getPropertyValue($server, 'stop')); |
| } |
| |
| public function testChildrenArrayInitiallyEmpty() |
| { |
| $server = $this->createServer(); |
| $this->assertEmpty($this->getPropertyValue($server, 'children')); |
| } |
| |
| public function testConstructorStoresCollaborators() |
| { |
| $processor = new \stdClass(); |
| $transport = $this->createStub(TServerTransport::class); |
| $inputTransportFactory = $this->createStub(TTransportFactoryInterface::class); |
| $outputTransportFactory = $this->createStub(TTransportFactoryInterface::class); |
| $inputProtocolFactory = $this->createStub(TProtocolFactory::class); |
| $outputProtocolFactory = $this->createStub(TProtocolFactory::class); |
| |
| $server = $this->createServer( |
| $processor, |
| $transport, |
| $inputTransportFactory, |
| $outputTransportFactory, |
| $inputProtocolFactory, |
| $outputProtocolFactory |
| ); |
| |
| $this->assertSame($processor, $this->getPropertyValue($server, 'processor')); |
| $this->assertSame($transport, $this->getPropertyValue($server, 'transport')); |
| $this->assertSame($inputTransportFactory, $this->getPropertyValue($server, 'inputTransportFactory')); |
| $this->assertSame($outputTransportFactory, $this->getPropertyValue($server, 'outputTransportFactory')); |
| $this->assertSame($inputProtocolFactory, $this->getPropertyValue($server, 'inputProtocolFactory')); |
| $this->assertSame($outputProtocolFactory, $this->getPropertyValue($server, 'outputProtocolFactory')); |
| } |
| |
| public function testServeListensAndLoopsUntilStopped() |
| { |
| $serverTransport = $this->createMock(TServerTransport::class); |
| $serverTransport->expects($this->once())->method('listen'); |
| |
| $server = $this->createServer(null, $serverTransport); |
| |
| // accept throws TTransportException (no connection), then on second call we stop |
| $callCount = 0; |
| $serverTransport->method('accept')->willReturnCallback( |
| function () use ($server, &$callCount) { |
| $callCount++; |
| if ($callCount >= 2) { |
| $this->setPropertyValue($server, 'stop', true); |
| } |
| throw new TTransportException(self::NO_CONNECTION); |
| } |
| ); |
| |
| $this->getFunctionMock('Thrift\Server', 'pcntl_waitpid') |
| ->expects($this->any()) |
| ->willReturn(0); |
| |
| $server->serve(); |
| |
| $this->assertGreaterThanOrEqual(2, $callCount); |
| } |
| |
| public function testServeForksAndHandlesParent() |
| { |
| $serverTransport = $this->createMock(TServerTransport::class); |
| $serverTransport->expects($this->once())->method('listen'); |
| |
| $clientTransport = $this->createStub(TTransport::class); |
| $server = $this->createServer(null, $serverTransport); |
| |
| $callCount = 0; |
| $serverTransport->method('accept')->willReturnCallback( |
| function () use ($server, $clientTransport, &$callCount) { |
| $callCount++; |
| if ($callCount === 1) { |
| return $clientTransport; |
| } |
| $this->setPropertyValue($server, 'stop', true); |
| throw new TTransportException(self::NO_CONNECTION); |
| } |
| ); |
| |
| $this->getFunctionMock('Thrift\Server', 'pcntl_fork') |
| ->expects($this->once()) |
| ->willReturn(12345); |
| |
| $this->getFunctionMock('Thrift\Server', 'pcntl_waitpid') |
| ->expects($this->any()) |
| ->willReturn(0); |
| |
| $server->serve(); |
| |
| $children = $this->getPropertyValue($server, 'children'); |
| $this->assertArrayHasKey(12345, $children); |
| $this->assertSame($clientTransport, $children[12345]); |
| } |
| |
| public function testServeThrowsTExceptionOnForkFailure() |
| { |
| $this->expectException(TException::class); |
| $this->expectExceptionMessage('Failed to fork'); |
| |
| $serverTransport = $this->createMock(TServerTransport::class); |
| $clientTransport = $this->createStub(TTransport::class); |
| |
| $server = $this->createServer(null, $serverTransport); |
| |
| $serverTransport->method('accept')->willReturn($clientTransport); |
| |
| $this->getFunctionMock('Thrift\Server', 'pcntl_fork') |
| ->expects($this->once()) |
| ->willReturn(-1); |
| |
| $this->getFunctionMock('Thrift\Server', 'pcntl_waitpid') |
| ->expects($this->any()) |
| ->willReturn(0); |
| |
| $server->serve(); |
| } |
| |
| public function testServeIgnoresTTransportException() |
| { |
| $serverTransport = $this->createMock(TServerTransport::class); |
| $serverTransport->expects($this->once())->method('listen'); |
| |
| $server = $this->createServer(null, $serverTransport); |
| |
| $callCount = 0; |
| $serverTransport->method('accept')->willReturnCallback( |
| function () use ($server, &$callCount) { |
| $callCount++; |
| if ($callCount === 1) { |
| throw new TTransportException('Connection reset'); |
| } |
| $this->setPropertyValue($server, 'stop', true); |
| throw new TTransportException(self::NO_CONNECTION); |
| } |
| ); |
| |
| $this->getFunctionMock('Thrift\Server', 'pcntl_waitpid') |
| ->expects($this->any()) |
| ->willReturn(0); |
| |
| $server->serve(); |
| |
| $this->assertGreaterThanOrEqual(2, $callCount); |
| } |
| |
| public function testCollectChildrenRemovesFinishedAndClosesTransport() |
| { |
| $server = $this->createServer(); |
| |
| $transport1 = $this->createMock(TTransport::class); |
| $transport1->expects($this->once())->method('close'); |
| |
| $transport2 = $this->createMock(TTransport::class); |
| $transport2->expects($this->never())->method('close'); |
| |
| $this->setPropertyValue($server, 'children', [ |
| 111 => $transport1, |
| 222 => $transport2, |
| ]); |
| |
| $this->getFunctionMock('Thrift\Server', 'pcntl_waitpid') |
| ->expects($this->exactly(2)) |
| ->willReturnCallback(function ($pid) { |
| return ($pid === 111) ? 111 : 0; |
| }); |
| |
| $method = $this->getAccessibleMethod($server, 'collectChildren'); |
| $method->invoke($server); |
| |
| $children = $this->getPropertyValue($server, 'children'); |
| $this->assertArrayNotHasKey(111, $children); |
| $this->assertArrayHasKey(222, $children); |
| } |
| |
| public function testCollectChildrenHandlesNullTransport() |
| { |
| $server = $this->createServer(); |
| |
| $this->setPropertyValue($server, 'children', [ |
| 333 => null, |
| ]); |
| |
| $this->getFunctionMock('Thrift\Server', 'pcntl_waitpid') |
| ->expects($this->once()) |
| ->willReturn(333); |
| |
| $method = $this->getAccessibleMethod($server, 'collectChildren'); |
| $method->invoke($server); |
| |
| $children = $this->getPropertyValue($server, 'children'); |
| $this->assertEmpty($children); |
| } |
| |
| public function testHandleParentStoresChildPid() |
| { |
| $server = $this->createServer(); |
| $transport = $this->createStub(TTransport::class); |
| |
| $method = $this->getAccessibleMethod($server, 'handleParent'); |
| $method->invoke($server, $transport, 42); |
| |
| $children = $this->getPropertyValue($server, 'children'); |
| $this->assertArrayHasKey(42, $children); |
| $this->assertSame($transport, $children[42]); |
| } |
| } |