Skip to content

Instantly share code, notes, and snippets.

@shmaltorhbooks
Last active December 9, 2023 16:30
Show Gist options
  • Save shmaltorhbooks/48966ae2052e72b8e85c717c05b9071b to your computer and use it in GitHub Desktop.
Save shmaltorhbooks/48966ae2052e72b8e85c717c05b9071b to your computer and use it in GitHub Desktop.
paratest with multiple db instances
alias paratest='php bin/console test:init:database:pool -e=test 10 && vendor/bin/paratest -f -p 10 —max-batch-size 5 —coverage-html=build/coverage'
abstract class AbstractFunctionalTestCase extends AbstractTestCase
{
/**
* @var ContainerInterface
*/
protected $container;
/**
* @var Client
*/
protected $client;
/**
* @var EntityManager
*/
protected $em;
public function setUp()
{
parent::setUp();
$this->client = static::createClient();
$this->container = $this->client->getContainer();
$this->em = $this->container->get('doctrine.orm.entity_manager');
$this->container->get('database_pool')->loadDatabaseFromDump();
}
}
parameters:
dbname: "@=service('database_pool').get()"
services:
database_pool:
class: AppBundle\Test\DatabasePool
arguments:
- "@service_container"
- "%database_name%"
- "%database_host%"
- "%database_port%"
- "%database_user%"
- "%database_password%"
doctrine.dbal.default_connection:
class: Doctrine\DBAL\Portability\Connection
factory: [Doctrine\DBAL\DriverManager, getConnection]
arguments:
- driver: pdo_mysql
host: "%database_host%"
port: "%database_port%"
dbname: "@=service('database_pool').get()"
user: "%database_user%"
password: "%database_password%"
<?php
namespace AppBundle\Test;
use AppKernel;
use Doctrine\Common\DataFixtures\Executor\ORMExecutor;
use Doctrine\Common\DataFixtures\Loader;
use Doctrine\Common\DataFixtures\Purger\ORMPurger;
use Symfony\Bundle\FrameworkBundle\Console\Application;
use Symfony\Component\Console\Input\ArrayInput;
use Symfony\Component\Console\Output\BufferedOutput;
use Symfony\Component\DependencyInjection\ContainerInterface;
class DatabasePool
{
const DB_DUMP_FILE = 'db-dump.sql';
/**
* @var ContainerInterface
*/
protected $container;
/**
* @var string
*/
protected $databaseName;
/**
* @var string
*/
protected $databaseHost;
/**
* @var int
*/
protected $databasePort;
/**
* @var string
*/
protected $databaseUser;
/**
* @var string
*/
protected $databasePassword;
/**
* @var string
*/
protected $createSchemaQueries;
protected static $pdo;
public function __construct(ContainerInterface $container, $databaseName, $databaseHost, $databasePort, $databaseUser, $databasePassword)
{
$this->container = $container;
$this->databaseName = $databaseName;
$this->databaseHost = $databaseHost;
$this->databasePort = $databasePort;
$this->databaseUser = $databaseUser;
$this->databasePassword = $databasePassword;
if (is_null(self::$pdo)) {
self::$pdo = new \PDO("mysql:host=$this->databaseHost;port=$this->databasePort", $this->databaseUser, $this->databasePassword);
self::$pdo->setAttribute(\PDO::ATTR_EMULATE_PREPARES, 1);
}
}
public function get()
{
$pooledDatabases = $this->getPool();
$dbName = $this->databaseName;
if (getenv('TEST_TOKEN') !== false) {
$token = getenv('TEST_TOKEN');
if ($token > 0) {
$dbName = $this->getDatabasePrefix() . getenv('TEST_TOKEN');
}
}
if (count($pooledDatabases) == 0) {
// throw new \RuntimeException("Database pool is empty");
} else {
if (in_array($dbName, $pooledDatabases)) {
return $dbName;
}
throw new \RuntimeException("Test database '$dbName' not found");
}
}
public function fillPool($size)
{
if ($size <= 0) {
throw new \UnexpectedValueException('Pool size must be greater than 0');
}
foreach ($this->getPool() as $pooledDatabase) {
self::$pdo->exec("DROP DATABASE {$pooledDatabase}");
}
$databaseName = $this->getDatabasePrefix();
$this->createDatabase($databaseName);
$this->createSchema();
$this->loadFixtures();
$this->dumpDatabase();
for ($i = 1; $i <= $size; $i++) {
$databaseName = $this->getDatabasePrefix() . $i;
$this->createDatabase($databaseName);
}
}
private function dumpDatabase()
{
$command = "mysqldump --opt --lock-tables=false --port=$this->databasePort --host=$this->databaseHost --user=$this->databaseUser --password=$this->databasePassword";
$command .= sprintf(' %s > %s', $this->databaseName, $this->getDbDumpFilename($this->container));
$command .= ' 2>/dev/null';
exec($command);
}
public function loadDatabaseFromDump()
{
$command = "mysql --port=$this->databasePort --host=$this->databaseHost --user=$this->databaseUser --password=$this->databasePassword";
$command .= sprintf(' %s < %s', $this->get(), $this->getDbDumpFilename($this->container));
$command .= ' 2>/dev/null';
exec($command);
}
/**
* @return array
*/
protected function getPool()
{
$sql = "
SELECT `schema_name`
FROM information_schema.schemata
WHERE `schema_name` LIKE '{$this->getDatabasePrefix()}%'
ORDER BY `schema_name` DESC
";
return self::$pdo->query($sql)->fetchAll(\PDO::FETCH_COLUMN);
}
protected function createDatabase($name)
{
self::$pdo->query("CREATE DATABASE IF NOT EXISTS $name");
self::$pdo->query("USE $name");
}
protected function executeCommand($commandName, $parameters = [])
{
$kernel = new AppKernel('test', true);
$application = new Application($kernel);
$application->setAutoExit(false);
$input = new ArrayInput(array_merge(['command' => $commandName], $parameters));
$output = new BufferedOutput();
$application->run($input, $output);
return $output->fetch();
}
protected function getDatabasePrefix()
{
return $this->databaseName;
}
protected function createSchema()
{
$query = $this->executeCommand('doctrine:schema:create', ['--dump-sql' => true]);
self::$pdo->query($query);
}
protected function loadFixtures()
{
$fixturesLoader = new Loader();
$fixturesLoader->loadFromDirectory($this->container->getParameter('kernel.root_dir') . '/../tests/Fixtures');
foreach ($fixturesLoader->getFixtures() as $fixture) {
if (method_exists($fixture, 'setContainer')) {
$fixture->setContainer($this->container);
}
}
$em = $this->container->get('doctrine.orm.entity_manager');
$purger = new ORMPurger($em);
$purger->setPurgeMode(ORMPurger::PURGE_MODE_DELETE);
$executor = new ORMExecutor($em, $purger);
$executor->execute($fixturesLoader->getFixtures());
}
public function closeConnection()
{
self::$pdo = null;
}
public static function getDbDumpFilename(ContainerInterface $container)
{
return $container->getParameter('kernel.cache_dir') . '/' . self::DB_DUMP_FILE;
}
}
<?php
namespace AppBundle\Command\Test;
use AppBundle\Test\DatabasePool;
use Symfony\Bundle\FrameworkBundle\Command\ContainerAwareCommand;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
class InitDatabasePoolCommand extends ContainerAwareCommand
{
protected function configure()
{
$this
->setName('test:init:database:pool')
->addArgument('size', InputArgument::OPTIONAL, 'amount db in pool', 10)
->setDescription('Create poll with testing databases');
}
protected function execute(InputInterface $input, OutputInterface $output)
{
$container = $this->getContainer();
$pool = new DatabasePool(
$container,
$container->getParameter('database_name'),
$container->getParameter('database_host'),
$container->getParameter('database_port'),
$container->getParameter('database_user'),
$container->getParameter('database_password')
);
$pool->fillPool($input->getArgument('size'));
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment