| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398 |
- <?php
- namespace test;
- use PHPUnit\Framework\TestCase;
- use ReflectionMethod;
- use Workerman\Coroutine;
- use Workerman\Coroutine\Exception\PoolException;
- use Workerman\Coroutine\Pool;
- use Psr\Log\LoggerInterface;
- use ReflectionClass;
- use stdClass;
- use Exception;
- use Workerman\Events\Event;
- use Workerman\Events\Select;
- use Workerman\Timer;
- use Workerman\Worker;
- class PoolTest extends TestCase
- {
- public function testConstructorWithConfig()
- {
- $config = [
- 'min_connections' => 2,
- 'idle_timeout' => 30,
- 'heartbeat_interval' => 10,
- 'wait_timeout' => 5,
- ];
- $pool = new Pool(10, $config);
- $this->assertEquals(10, $this->getPrivateProperty($pool, 'maxConnections'));
- $this->assertEquals(2, $this->getPrivateProperty($pool, 'minConnections'));
- $this->assertEquals(30, $this->getPrivateProperty($pool, 'idleTimeout'));
- $this->assertEquals(10, $this->getPrivateProperty($pool, 'heartbeatInterval'));
- $this->assertEquals(5, $this->getPrivateProperty($pool, 'waitTimeout'));
- }
- public function testSetConnectionCreator()
- {
- $pool = new Pool(5);
- $connectionCreator = function () {
- return new stdClass();
- };
- $pool->setConnectionCreator($connectionCreator);
- $this->assertSame($connectionCreator, $this->getPrivateProperty($pool, 'connectionCreateHandler'));
- }
- public function testSetConnectionCloser()
- {
- $pool = new Pool(5);
- $connectionCloser = function ($conn) {
- // Close connection.
- };
- $pool->setConnectionCloser($connectionCloser);
- $this->assertSame($connectionCloser, $this->getPrivateProperty($pool, 'connectionDestroyHandler'));
- }
- public function testGetConnection()
- {
- $pool = new Pool(5);
- $connectionMock = $this->createMock(stdClass::class);
- // 设置连接创建器
- $pool->setConnectionCreator(function () use ($connectionMock) {
- return $connectionMock;
- });
- $connection = $pool->get();
- $this->assertSame($connectionMock, $connection);
- $this->assertEquals(1, $this->getCurrentConnections($pool));
- // 检查 WeakMap 是否更新
- $connections = $this->getPrivateProperty($pool, 'connections');
- $lastUsedTimes = $this->getPrivateProperty($pool, 'lastUsedTimes');
- $lastHeartbeatTimes = $this->getPrivateProperty($pool, 'lastHeartbeatTimes');
- $this->assertTrue($connections->offsetExists($connection));
- $this->assertTrue($lastUsedTimes->offsetExists($connection));
- $this->assertTrue($lastHeartbeatTimes->offsetExists($connection));
- }
- public function testPutConnection()
- {
- $pool = new Pool(5);
- $connectionMock = $this->createMock(stdClass::class);
- $pool->setConnectionCreator(function () use ($connectionMock) {
- return $connectionMock;
- });
- $connection = $pool->get();
- $pool->put($connection);
- if (Coroutine::isCoroutine()) {
- $channel = $this->getPrivateProperty($pool, 'channel');
- $this->assertEquals(1, $channel->length());
- }
- $this->assertEquals(1, $pool->getConnectionCount());
- }
- public function testPutConnectionDoesNotBelong()
- {
- $this->expectException(PoolException::class);
- $this->expectExceptionMessage('The connection does not belong to the connection pool.');
- $pool = new Pool(5);
- $connection = new stdClass();
- $pool->put($connection);
- }
- public function testCreateConnection()
- {
- $pool = new Pool(5);
- $connectionMock = $this->createMock(stdClass::class);
- $pool->setConnectionCreator(function () use ($connectionMock) {
- return $connectionMock;
- });
- $connection = $pool->createConnection();
- $this->assertSame($connectionMock, $connection);
- // 确保 currentConnections 增加
- $this->assertEquals(1, $this->getCurrentConnections($pool));
- // 检查 WeakMap 是否更新
- $connections = $this->getPrivateProperty($pool, 'connections');
- $lastUsedTimes = $this->getPrivateProperty($pool, 'lastUsedTimes');
- $lastHeartbeatTimes = $this->getPrivateProperty($pool, 'lastHeartbeatTimes');
- $this->assertTrue($connections->offsetExists($connection));
- $this->assertTrue($lastUsedTimes->offsetExists($connection));
- $this->assertTrue($lastHeartbeatTimes->offsetExists($connection));
- }
- public function testCreateMaxConnections()
- {
- if (in_array(Worker::$eventLoopClass, [Select::class, Event::class])) {
- $this->assertTrue(true);
- return;
- }
- $maxConnections = 2;
- $pool = new Pool($maxConnections);
- $pool->setConnectionCreator(function () {
- Timer::sleep(0.01);
- return $this->createMock(stdClass::class);
- });
- $connections = [];
- for ($i = 0; $i < 3; $i++) {
- Coroutine::create(function () use ($pool, &$connections) {
- $connections[] = $pool->get();
- });
- }
- Timer::sleep(0.1);
- $this->assertEquals($maxConnections, $this->getCurrentConnections($pool));
- $lastUsedTimes = $this->getPrivateProperty($pool, 'lastUsedTimes');
- $lastHeartbeatTimes = $this->getPrivateProperty($pool, 'lastHeartbeatTimes');
- $this->assertCount($maxConnections, $lastUsedTimes);
- $this->assertCount($maxConnections, $lastHeartbeatTimes);
- foreach ($connections as $connection) {
- $pool->put($connection);
- }
- }
- public function testCreateConnectionThrowsException()
- {
- $pool = new Pool(5);
- $pool->setConnectionCreator(function () {
- throw new Exception('Failed to create connection');
- });
- $this->expectException(Exception::class);
- $this->expectExceptionMessage('Failed to create connection');
- try {
- $pool->createConnection();
- } finally {
- // 确保 currentConnections 减少
- $this->assertEquals(0, $this->getCurrentConnections($pool));
- }
- }
- public function testCloseConnection()
- {
- $pool = new Pool(5);
- $connection = $this->createMock(ConnectionMock::class);
- // 模拟连接属于连接池
- $connections = $this->getPrivateProperty($pool, 'connections');
- $connections[$connection] = time();
- $connection->expects($this->once())->method('close');
- $pool->setConnectionCloser(function ($conn) {
- $conn->close();
- });
- $pool->closeConnection($connection);
- // 确保 currentConnections 减少
- $this->assertEquals(0, $this->getCurrentConnections($pool));
- // 确保连接从 WeakMap 中移除
- $this->assertFalse($connections->offsetExists($connection));
- }
- public function testCloseConnections()
- {
- $maxConnections = 5;
- $pool = new Pool($maxConnections);
- $pool->setConnectionCreator(function () {
- $connection = $this->createMock(ConnectionMock::class);
- $connection->expects($this->once())->method('close');
- return $connection;
- });
- $pool->setConnectionCloser(function ($conn) {
- $conn->close();
- });
- $connections = [];
- for ($i = 0; $i < $maxConnections; $i++) {
- $connections[] = $pool->get();
- }
- $this->assertEquals(Coroutine::isCoroutine() ? $maxConnections : 1, $this->getCurrentConnections($pool));
- $pool->closeConnections();
- $this->assertEquals(Coroutine::isCoroutine() ? $maxConnections : 0, $this->getCurrentConnections($pool));
- if (!Coroutine::isCoroutine()) {
- return;
- }
- foreach ($connections as $connection) {
- $pool->put($connection);
- }
- $this->assertEquals($maxConnections, $this->getCurrentConnections($pool));
- $pool->closeConnections();
- $this->assertEquals(0, $this->getCurrentConnections($pool));
- $connections = [];
- for ($i = 0; $i < $maxConnections; $i++) {
- $connections[] = $pool->get();
- }
- $this->assertEquals($maxConnections, $this->getCurrentConnections($pool));
- foreach ($connections as $connection) {
- $pool->put($connection);
- }
- $pool->closeConnections();
- unset($connections);
- $this->assertEquals(0, $this->getCurrentConnections($pool));
- }
- public function testCloseConnectionWithExceptionInDestroyHandler()
- {
- $pool = new Pool(5);
- $connection = $this->createMock(stdClass::class);
- // 模拟连接属于连接池
- $connections = $this->getPrivateProperty($pool, 'connections');
- $connections[$connection] = time();
- $exception = new Exception('Error closing connection');
- $pool->setConnectionCloser(function ($conn) use ($exception) {
- throw $exception;
- });
- // 设置日志记录器
- $loggerMock = $this->createMock(LoggerInterface::class);
- $loggerMock->expects($this->once())
- ->method('info')
- ->with($this->stringContains('Error closing connection'));
- $this->setPrivateProperty($pool, 'logger', $loggerMock);
- $pool->closeConnection($connection);
- // 确保 currentConnections 减少
- $this->assertEquals(0, $this->getCurrentConnections($pool));
- // 确保连接从 WeakMap 中移除
- $this->assertFalse($connections->offsetExists($connection));
- }
- public function testHeartbeatChecker()
- {
- $pool = $this->getMockBuilder(Pool::class)
- ->setConstructorArgs([5])
- ->onlyMethods(['closeConnection'])
- ->getMock();
- $connection = $this->createMock(stdClass::class);
- // 设置连接心跳检测器
- $pool->setHeartbeatChecker(function ($conn) {
- // 模拟心跳检测
- });
- // 模拟连接在通道中
- $channel = $this->getPrivateProperty($pool, 'channel');
- $channel->push($connection);
- // 设置连接的上次使用时间和心跳时间
- $connections = $this->getPrivateProperty($pool, 'connections');
- $connections[$connection] = time();
- $lastUsedTimes = $this->getPrivateProperty($pool, 'lastUsedTimes');
- $lastUsedTimes[$connection] = time();
- $lastHeartbeatTimes = $this->getPrivateProperty($pool, 'lastHeartbeatTimes');
- $lastHeartbeatTimes[$connection] = time() - 100; // 超过心跳间隔
- // 调用受保护的 checkConnections 方法
- $reflectedMethod = new ReflectionMethod($pool, 'checkConnections');
- $reflectedMethod->setAccessible(true);
- $reflectedMethod->invoke($pool);
- // 检查心跳时间是否更新
- $lastHeartbeatTimes = $this->getPrivateProperty($pool, 'lastHeartbeatTimes');
- $this->assertGreaterThan(time() - 2, $lastHeartbeatTimes[$connection]);
- }
- public function testConnectionDestroyedWithoutReturn()
- {
- $pool = new Pool(5);
- // 设置连接创建器
- $pool->setConnectionCreator(function () {
- return new stdClass;
- });
- // 获取初始的 currentConnections
- $initialConnections = $this->getCurrentConnections($pool);
- // 从连接池获取一个连接
- $connection = $pool->get();
- // 检查 currentConnections 是否增加
- $this->assertEquals(Coroutine::isCoroutine() ? $initialConnections + 1 : 1, $this->getCurrentConnections($pool));
- // 不归还连接,并销毁连接对象
- unset($connection);
- // 检查 currentConnections 是否减少
- $this->assertEquals(Coroutine::isCoroutine() ? $initialConnections : 1, $this->getCurrentConnections($pool));
- }
- private function getPrivateProperty($object, string $property)
- {
- $reflection = new ReflectionClass($object);
- $prop = $reflection->getProperty($property);
- $prop->setAccessible(true);
- return $prop->getValue($object);
- }
- private function setPrivateProperty($object, string $property, $value)
- {
- $reflection = new ReflectionClass($object);
- $prop = $reflection->getProperty($property);
- $prop->setAccessible(true);
- $prop->setValue($object, $value);
- }
- private function getCurrentConnections($object): int
- {
- return $object->getConnectionCount();
- }
- }
- // 定义 ConnectionMock 类用于测试
- class ConnectionMock
- {
- public function close()
- {
- // 模拟关闭连接
- }
- }
|