2017-01-18 18:05:05 +01:00
|
|
|
<?php
|
2016-09-14 16:27:39 +02:00
|
|
|
|
|
|
|
namespace Amp\Postgres\Test;
|
|
|
|
|
2017-11-06 06:06:17 +01:00
|
|
|
use Amp\Delayed;
|
2017-06-21 05:17:53 +02:00
|
|
|
use Amp\Loop;
|
|
|
|
use Amp\Postgres\CommandResult;
|
2017-08-02 06:01:55 +02:00
|
|
|
use Amp\Postgres\Connection;
|
2017-08-02 06:35:48 +02:00
|
|
|
use Amp\Postgres\Listener;
|
2017-11-06 06:06:17 +01:00
|
|
|
use Amp\Postgres\Pool;
|
2017-12-03 02:35:49 +01:00
|
|
|
use Amp\Postgres\ResultSet;
|
2017-12-03 02:56:40 +01:00
|
|
|
use Amp\Postgres\Transaction;
|
2017-06-21 05:17:53 +02:00
|
|
|
use Amp\Promise;
|
2017-05-26 17:47:44 +02:00
|
|
|
use PHPUnit\Framework\TestCase;
|
2017-06-21 05:17:53 +02:00
|
|
|
use function Amp\call;
|
2016-09-14 16:27:39 +02:00
|
|
|
|
2017-05-26 17:47:44 +02:00
|
|
|
abstract class AbstractPoolTest extends TestCase {
|
2016-09-14 16:27:39 +02:00
|
|
|
/**
|
|
|
|
* @param array $connections
|
|
|
|
*
|
|
|
|
* @return \Amp\Postgres\Pool
|
|
|
|
*/
|
2017-11-06 06:06:17 +01:00
|
|
|
abstract protected function createPool(array $connections): Pool;
|
2016-09-14 16:27:39 +02:00
|
|
|
|
|
|
|
/**
|
2017-08-02 06:01:55 +02:00
|
|
|
* @return \PHPUnit_Framework_MockObject_MockObject|\Amp\Postgres\Connection
|
2016-09-14 16:27:39 +02:00
|
|
|
*/
|
2017-11-06 06:06:17 +01:00
|
|
|
protected function createConnection(): Connection {
|
2017-11-05 22:38:17 +01:00
|
|
|
$mock = $this->createMock(Connection::class);
|
2017-12-10 16:53:52 +01:00
|
|
|
$mock->method('isAlive')->willReturn(true);
|
2017-11-05 22:38:17 +01:00
|
|
|
return $mock;
|
2016-09-14 16:27:39 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @param int $count
|
|
|
|
*
|
|
|
|
* @return \Amp\Postgres\Connection[]|\PHPUnit_Framework_MockObject_MockObject[]
|
|
|
|
*/
|
2018-01-02 23:26:28 +01:00
|
|
|
protected function makeConnectionSet(int $count) {
|
2016-09-14 16:27:39 +02:00
|
|
|
$connections = [];
|
|
|
|
|
|
|
|
for ($i = 0; $i < $count; ++$i) {
|
|
|
|
$connections[] = $this->createConnection();
|
|
|
|
}
|
|
|
|
|
|
|
|
return $connections;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @return array
|
|
|
|
*/
|
|
|
|
public function getMethodsAndResults() {
|
|
|
|
return [
|
2017-12-03 02:35:49 +01:00
|
|
|
[3, 'query', ResultSet::class, ["SELECT * FROM test"]],
|
2017-11-18 04:33:49 +01:00
|
|
|
[2, 'query', CommandResult::class, ["INSERT INTO test VALUES (1, 7)"]],
|
|
|
|
[5, 'listen', Listener::class, ["test"]],
|
2017-12-03 02:35:49 +01:00
|
|
|
[4, 'execute', ResultSet::class, ["SELECT * FROM test WHERE id=\$1 AND time>\$2", [1, time()]]],
|
2017-11-18 04:33:49 +01:00
|
|
|
[4, 'notify', CommandResult::class, ["test", "payload"]],
|
2016-09-14 16:27:39 +02:00
|
|
|
];
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @dataProvider getMethodsAndResults
|
|
|
|
*
|
|
|
|
* @param int $count
|
|
|
|
* @param string $method
|
|
|
|
* @param string $resultClass
|
2017-11-18 04:33:49 +01:00
|
|
|
* @param mixed[] $params
|
2016-09-14 16:27:39 +02:00
|
|
|
*/
|
2017-11-18 04:33:49 +01:00
|
|
|
public function testSingleQuery(int $count, string $method, string $resultClass, array $params = []) {
|
2016-09-14 16:27:39 +02:00
|
|
|
$result = $this->getMockBuilder($resultClass)
|
|
|
|
->disableOriginalConstructor()
|
|
|
|
->getMock();
|
|
|
|
|
|
|
|
$connections = $this->makeConnectionSet($count);
|
|
|
|
|
|
|
|
$connection = $connections[0];
|
|
|
|
$connection->expects($this->once())
|
|
|
|
->method($method)
|
|
|
|
->with(...$params)
|
2017-11-06 06:06:17 +01:00
|
|
|
->will($this->returnValue(new Delayed(10, $result)));
|
2016-09-14 16:27:39 +02:00
|
|
|
|
|
|
|
$pool = $this->createPool($connections);
|
2018-01-02 23:26:28 +01:00
|
|
|
$pool->resetConnections(false);
|
2017-05-16 06:28:37 +02:00
|
|
|
|
2017-08-02 06:35:48 +02:00
|
|
|
Loop::run(function () use ($method, $pool, $params, $result, $resultClass) {
|
2016-09-14 16:27:39 +02:00
|
|
|
$return = yield $pool->{$method}(...$params);
|
2017-08-02 06:35:48 +02:00
|
|
|
$this->assertInstanceOf($resultClass, $return);
|
2017-03-17 16:17:24 +01:00
|
|
|
});
|
2016-09-14 16:27:39 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @dataProvider getMethodsAndResults
|
|
|
|
*
|
|
|
|
* @param int $count
|
|
|
|
* @param string $method
|
|
|
|
* @param string $resultClass
|
2017-11-18 04:33:49 +01:00
|
|
|
* @param mixed[] $params
|
2016-09-14 16:27:39 +02:00
|
|
|
*/
|
2017-11-18 04:33:49 +01:00
|
|
|
public function testConsecutiveQueries(int $count, string $method, string $resultClass, array $params = []) {
|
2016-09-14 16:27:39 +02:00
|
|
|
$rounds = 3;
|
|
|
|
$result = $this->getMockBuilder($resultClass)
|
|
|
|
->disableOriginalConstructor()
|
|
|
|
->getMock();
|
|
|
|
|
|
|
|
$connections = $this->makeConnectionSet($count);
|
|
|
|
|
|
|
|
foreach ($connections as $connection) {
|
|
|
|
$connection->method($method)
|
|
|
|
->with(...$params)
|
2017-11-06 06:06:17 +01:00
|
|
|
->will($this->returnValue(new Delayed(10, $result)));
|
2016-09-14 16:27:39 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
$pool = $this->createPool($connections);
|
2018-01-02 23:26:28 +01:00
|
|
|
$pool->resetConnections(false);
|
2017-05-16 06:28:37 +02:00
|
|
|
|
2017-05-26 17:47:44 +02:00
|
|
|
Loop::run(function () use ($resultClass, $count, $rounds, $pool, $method, $params) {
|
2016-11-15 18:06:21 +01:00
|
|
|
$promises = [];
|
2017-05-16 06:28:37 +02:00
|
|
|
|
2016-09-14 16:27:39 +02:00
|
|
|
for ($i = 0; $i < $count * $rounds; ++$i) {
|
2016-11-15 18:06:21 +01:00
|
|
|
$promises[] = $pool->{$method}(...$params);
|
2016-09-14 16:27:39 +02:00
|
|
|
}
|
2017-05-26 17:47:44 +02:00
|
|
|
|
|
|
|
$results = yield Promise\all($promises);
|
|
|
|
|
|
|
|
foreach ($results as $result) {
|
|
|
|
$this->assertInstanceOf($resultClass, $result);
|
|
|
|
}
|
2017-03-17 16:17:24 +01:00
|
|
|
});
|
2016-09-14 16:27:39 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @return array
|
|
|
|
*/
|
|
|
|
public function getConnectionCounts() {
|
|
|
|
return array_map(function ($count) { return [$count]; }, range(1, 10));
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @dataProvider getConnectionCounts
|
|
|
|
*
|
|
|
|
* @param int $count
|
|
|
|
*/
|
2017-08-01 07:38:12 +02:00
|
|
|
public function testTransaction(int $count) {
|
2016-09-14 16:27:39 +02:00
|
|
|
$connections = $this->makeConnectionSet($count);
|
|
|
|
|
|
|
|
$connection = $connections[0];
|
|
|
|
$result = $this->getMockBuilder(Transaction::class)
|
|
|
|
->disableOriginalConstructor()
|
|
|
|
->getMock();
|
|
|
|
|
|
|
|
$connection->expects($this->once())
|
|
|
|
->method('transaction')
|
|
|
|
->with(Transaction::COMMITTED)
|
2017-11-06 06:06:17 +01:00
|
|
|
->will($this->returnValue(new Delayed(10, $result)));
|
2016-09-14 16:27:39 +02:00
|
|
|
|
|
|
|
$pool = $this->createPool($connections);
|
2018-01-02 23:26:28 +01:00
|
|
|
$pool->resetConnections(false);
|
2017-05-16 06:28:37 +02:00
|
|
|
|
2017-03-17 16:17:24 +01:00
|
|
|
Loop::run(function () use ($pool, $result) {
|
2016-09-14 16:27:39 +02:00
|
|
|
$return = yield $pool->transaction(Transaction::COMMITTED);
|
|
|
|
$this->assertInstanceOf(Transaction::class, $return);
|
|
|
|
yield $return->rollback();
|
2017-03-17 16:17:24 +01:00
|
|
|
});
|
2016-09-14 16:27:39 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @dataProvider getConnectionCounts
|
|
|
|
*
|
|
|
|
* @param int $count
|
|
|
|
*/
|
2017-08-01 07:38:12 +02:00
|
|
|
public function testConsecutiveTransactions(int $count) {
|
2016-09-14 16:27:39 +02:00
|
|
|
$rounds = 3;
|
|
|
|
$result = $this->getMockBuilder(Transaction::class)
|
|
|
|
->disableOriginalConstructor()
|
|
|
|
->getMock();
|
|
|
|
|
|
|
|
$connections = $this->makeConnectionSet($count);
|
|
|
|
|
|
|
|
foreach ($connections as $connection) {
|
|
|
|
$connection->method('transaction')
|
|
|
|
->with(Transaction::COMMITTED)
|
|
|
|
->will($this->returnCallback(function () use ($result) {
|
2017-11-06 06:06:17 +01:00
|
|
|
return new Delayed(10, $result);
|
2016-09-14 16:27:39 +02:00
|
|
|
}));
|
|
|
|
}
|
|
|
|
|
|
|
|
$pool = $this->createPool($connections);
|
2018-01-02 23:26:28 +01:00
|
|
|
$pool->resetConnections(false);
|
2017-05-16 06:28:37 +02:00
|
|
|
|
2017-03-17 16:17:24 +01:00
|
|
|
Loop::run(function () use ($count, $rounds, $pool) {
|
2016-11-15 18:06:21 +01:00
|
|
|
$promises = [];
|
2017-05-26 17:47:44 +02:00
|
|
|
for ($i = 0; $i < $count; ++$i) {
|
2016-11-15 18:06:21 +01:00
|
|
|
$promises[] = $pool->transaction(Transaction::COMMITTED);
|
2016-09-14 16:27:39 +02:00
|
|
|
}
|
2017-04-15 01:32:57 +02:00
|
|
|
|
2017-05-26 17:47:44 +02:00
|
|
|
$results = yield Promise\all(\array_map(function (Promise $promise) {
|
2017-05-16 06:14:02 +02:00
|
|
|
return call(function () use ($promise) {
|
|
|
|
$transaction = yield $promise;
|
2017-05-26 17:47:44 +02:00
|
|
|
$this->assertInstanceOf(Transaction::class, $transaction);
|
|
|
|
return yield $transaction->rollback();
|
2017-04-15 01:32:57 +02:00
|
|
|
});
|
2016-11-15 18:06:21 +01:00
|
|
|
}, $promises));
|
2017-05-26 17:47:44 +02:00
|
|
|
|
|
|
|
foreach ($results as $result) {
|
|
|
|
$this->assertInstanceof(CommandResult::class, $result);
|
|
|
|
}
|
2017-03-17 16:17:24 +01:00
|
|
|
});
|
2016-09-14 16:27:39 +02:00
|
|
|
}
|
2017-08-01 07:38:12 +02:00
|
|
|
|
|
|
|
/**
|
|
|
|
* @dataProvider getConnectionCounts
|
|
|
|
*
|
|
|
|
* @param int $count
|
|
|
|
*/
|
2017-11-23 03:34:08 +01:00
|
|
|
public function testExtractConnection(int $count) {
|
2017-08-01 07:38:12 +02:00
|
|
|
$connections = $this->makeConnectionSet($count);
|
|
|
|
$query = "SELECT * FROM test";
|
|
|
|
|
|
|
|
foreach ($connections as $connection) {
|
|
|
|
$connection->expects($this->once())
|
|
|
|
->method('query')
|
|
|
|
->with($query);
|
|
|
|
}
|
|
|
|
|
|
|
|
$pool = $this->createPool($connections);
|
2018-01-02 23:26:28 +01:00
|
|
|
$pool->resetConnections(false);
|
2017-08-01 07:38:12 +02:00
|
|
|
|
|
|
|
Loop::run(function () use ($pool, $query, $count) {
|
|
|
|
$promises = [];
|
|
|
|
for ($i = 0; $i < $count; ++$i) {
|
2017-11-23 03:34:08 +01:00
|
|
|
$promises[] = $pool->extractConnection();
|
2017-08-01 07:38:12 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
$results = yield Promise\all($promises);
|
|
|
|
|
|
|
|
foreach ($results as $result) {
|
2017-08-02 06:01:55 +02:00
|
|
|
$this->assertInstanceof(Connection::class, $result);
|
2017-08-01 07:38:12 +02:00
|
|
|
$result->query($query);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
2018-01-24 02:44:24 +01:00
|
|
|
|
|
|
|
public function testConnectionClosedWhileInPool() {
|
|
|
|
Loop::run(function () {
|
|
|
|
$connections = $this->makeConnectionSet(1);
|
|
|
|
|
|
|
|
$connection = $this->createMock(Connection::class);
|
|
|
|
$connection->method('isAlive')->willReturnOnConsecutiveCalls(true, true, true, false);
|
|
|
|
|
|
|
|
$count = \array_unshift($connections, $connection);
|
|
|
|
|
|
|
|
foreach ($connections as $connection) {
|
|
|
|
$connection->method('query')
|
|
|
|
->willReturn(new Delayed(10));
|
|
|
|
}
|
|
|
|
|
|
|
|
$pool = $this->createPool($connections);
|
|
|
|
$pool->resetConnections(false);
|
|
|
|
|
|
|
|
// Perform queries to load mock connections into pool.
|
|
|
|
for ($i = 0; $i < $count; ++$i) {
|
|
|
|
yield $pool->query("SELECT 1");
|
|
|
|
}
|
|
|
|
|
|
|
|
$extracted = yield $pool->extractConnection();
|
|
|
|
$this->assertNotSame($connections[0], $extracted);
|
|
|
|
$this->assertSame($connections[1], $extracted);
|
|
|
|
});
|
|
|
|
}
|
2016-09-14 16:27:39 +02:00
|
|
|
}
|