-
-
Save asanikovich/8fb95c48793ed1949cae0586ba831592 to your computer and use it in GitHub Desktop.
paratest with multiple db instances
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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' |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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(); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
parameters: | |
dbname: "@=service('database_pool').get()" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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%" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?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; | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?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
https://locastic.com/blog/speed-up-database-refreshing-in-phpunit-tests