Skip to content

Commit

Permalink
Add command to ping the database
Browse files Browse the repository at this point in the history
  • Loading branch information
mcfedr committed Oct 21, 2019
1 parent 8012286 commit 007119d
Show file tree
Hide file tree
Showing 3 changed files with 231 additions and 0 deletions.
82 changes: 82 additions & 0 deletions lib/Doctrine/DBAL/Tools/Console/Command/PingCommand.php
@@ -0,0 +1,82 @@
<?php

namespace Doctrine\DBAL\Tools\Console\Command;

use Doctrine\DBAL\Connection;
use RuntimeException;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Throwable;
use function is_numeric;
use function sleep;
use function sprintf;

class PingCommand extends Command
{
protected function configure()
{
$this
->setName('dbal:ping')
->setDescription('Check db is available')
->addOption('limit', null, InputOption::VALUE_REQUIRED, 'Max number of pings to try', '1')
->addOption('sleep', null, InputOption::VALUE_REQUIRED, 'Length of time (seconds) to sleep between pings', '1')
->setHelp(<<<EOT
Connects to the database to check if it is accessible.
The exit code will be non-zero when the connection fails.
EOT
);
}

protected function execute(InputInterface $input, OutputInterface $output) : int
{
$limit = $input->getOption('limit');
if (! is_numeric($limit) || $limit < 0) {
throw new RuntimeException('Option "limit" must contain a positive integer value');
}
$sleep = $input->getOption('sleep');
if (! is_numeric($sleep) || $sleep < 0) {
throw new RuntimeException('Option "sleep" must contain a positive integer value');
}

return $this->waitForPing($this->getHelper('db')->getConnection(), (int) $limit, (int) $sleep, $output);
}

/**
* @return int > 0 for error
*/
private function waitForPing(Connection $conn, int $limit, int $sleep, OutputInterface $output) : int
{
while (true) {
$last = $this->ping($conn, $output);
if ($last === 0 || --$limit <= 0) {
break;
}
sleep($sleep);
}

return $last;
}

/**
* @return int > 0 for error
*/
private function ping(Connection $conn, OutputInterface $output) : int
{
try {
if ($conn->ping()) {
return 0;
}

$output->writeln('Ping failed');

return 1;
} catch (Throwable $e) {
$output->writeln(sprintf('Ping failed: <error>%s</error>', $e->getMessage()));

return 2;
}
}
}
2 changes: 2 additions & 0 deletions lib/Doctrine/DBAL/Tools/Console/ConsoleRunner.php
Expand Up @@ -4,6 +4,7 @@

use Doctrine\DBAL\Connection;
use Doctrine\DBAL\Tools\Console\Command\ImportCommand;
use Doctrine\DBAL\Tools\Console\Command\PingCommand;
use Doctrine\DBAL\Tools\Console\Command\ReservedWordsCommand;
use Doctrine\DBAL\Tools\Console\Command\RunSqlCommand;
use Doctrine\DBAL\Tools\Console\Helper\ConnectionHelper;
Expand Down Expand Up @@ -58,6 +59,7 @@ public static function addCommands(Application $cli)
new RunSqlCommand(),
new ImportCommand(),
new ReservedWordsCommand(),
new PingCommand(),
]);
}

Expand Down
147 changes: 147 additions & 0 deletions tests/Doctrine/Tests/DBAL/Tools/Console/Command/PingCommandTest.php
@@ -0,0 +1,147 @@
<?php

namespace Doctrine\Tests\DBAL\Tools\Console\Command;

use Doctrine\DBAL\Connection;
use Doctrine\DBAL\Tools\Console\Command\PingCommand;
use Doctrine\DBAL\Tools\Console\ConsoleRunner;
use PDOException;
use PHPUnit\Framework\TestCase;
use RuntimeException;
use Symfony\Component\Console\Application;
use Symfony\Component\Console\Tester\CommandTester;

class PingCommandTest extends TestCase
{
/** @var CommandTester */
private $commandTester;
/** @var PingCommand */
private $command;

/** @var Connection */
private $connectionMock;

protected function setUp() : void
{
$application = new Application();
$application->add(new PingCommand());

$this->command = $application->find('dbal:ping');
$this->commandTester = new CommandTester($this->command);

$this->connectionMock = $this->createMock(Connection::class);

$helperSet = ConsoleRunner::createHelperSet($this->connectionMock);
$this->command->setHelperSet($helperSet);
}

public function testConnectionWorking() : void
{
$this->connectionMock
->expects($this->once())
->method('ping')
->willReturn(true);

$this->commandTester->execute([]);

self::assertSame(0, $this->commandTester->getStatusCode());
}

public function testConnectionNotWorking() : void
{
$this->connectionMock
->expects($this->once())
->method('ping')
->willReturn(false);

$this->commandTester->execute([]);

self::assertSame(1, $this->commandTester->getStatusCode());
self::assertSame("Ping failed\n", $this->commandTester->getDisplay());
}

public function testConnectionErrors() : void
{
$this->connectionMock
->expects($this->once())
->method('ping')
->willThrowException(new PDOException('Connection failed'));

$this->commandTester->execute([]);

self::assertSame(2, $this->commandTester->getStatusCode());
self::assertSame("Ping failed: Connection failed\n", $this->commandTester->getDisplay());
}

public function testConnectionNotWorkingLoop() : void
{
$this->connectionMock
->expects($this->exactly(3))
->method('ping')
->willReturn(false);

$this->commandTester->execute([
'--limit' => '3',
'--sleep' => '0',
]);

self::assertSame(1, $this->commandTester->getStatusCode());
self::assertSame("Ping failed\nPing failed\nPing failed\n", $this->commandTester->getDisplay());
}

public function testConnectionStartsWorking() : void
{
$this->connectionMock
->expects($this->exactly(3))
->method('ping')
->willReturnOnConsecutiveCalls(false, false, true);

$this->commandTester->execute([
'--limit' => '5',
'--sleep' => '0',
]);

self::assertSame(0, $this->commandTester->getStatusCode());
self::assertSame("Ping failed\nPing failed\n", $this->commandTester->getDisplay());
}

public function testInvalidLimit() : void
{
$this->expectException(RuntimeException::class);
$this->expectExceptionMessage('Option "limit" must contain a positive integer value');

$this->commandTester->execute(['--limit' => '-1']);

self::assertSame(1, $this->commandTester->getStatusCode());
}

public function testInvalidLimitNum() : void
{
$this->expectException(RuntimeException::class);
$this->expectExceptionMessage('Option "limit" must contain a positive integer value');

$this->commandTester->execute(['--limit' => 'foo']);

self::assertSame(1, $this->commandTester->getStatusCode());
}

public function testInvalidSleep() : void
{
$this->expectException(RuntimeException::class);
$this->expectExceptionMessage('Option "sleep" must contain a positive integer value');

$this->commandTester->execute(['--sleep' => '-1']);

self::assertSame(1, $this->commandTester->getStatusCode());
}

public function testInvalidSleepNum() : void
{
$this->expectException(RuntimeException::class);
$this->expectExceptionMessage('Option "sleep" must contain a positive integer value');

$this->commandTester->execute(['--sleep' => 'foo']);

self::assertSame(1, $this->commandTester->getStatusCode());
}
}

0 comments on commit 007119d

Please sign in to comment.