Skip to content

Commit

Permalink
Merge pull request #522 from vierge-noire/master
Browse files Browse the repository at this point in the history
Add Migrator::runMany()
  • Loading branch information
othercorey authored Oct 30, 2021
2 parents c324722 + 556de60 commit 00d039d
Show file tree
Hide file tree
Showing 10 changed files with 188 additions and 122 deletions.
9 changes: 9 additions & 0 deletions docs/en/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -860,6 +860,15 @@ beginning::
// Run the Documents migrations on the test_docs connection.
$migrator->run(['plugin' => 'Documents', 'connection' => 'test_docs']);


If you need to run multiple sets of migrations, those can be run as follows::

// Run migrations for plugin Contacts on the ``test`` connection, and Documents on the ``test_docs`` connection
$migrator->runMany([
['plugin' => 'Contacts'],
['plugin' => 'Documents', 'connection' => 'test_docs']
]);

If you need to see additional debugging output from migrations are being run,
you can enable a ``debug`` level logger.

Expand Down
44 changes: 44 additions & 0 deletions docs/fr/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -854,6 +854,50 @@ migrations.
Vous pouvez aussi utiliser les options ``--source``, ``--connection`` et
``--plugin`` comme pour la commande ``migrate``.


Utiliser Migrations dans les Tests
==================================

Si votre application fait usage des migrations, vous pouvez ré-utiliser
celles-ci afin de maintenir le schéma de votre base de données de test. Dans
le fichier ``tests/bootstrap.php``, vous pouvez utiliser la
classe ``Migrator`` pour construire le schéma avant que vos tests ne soient lancés.
La classe ``Migrator`` réutilisera le schéma existant si il correspond à vos migrations.
Si vos migrations ont évolué depuis le dernier lancement de vos tests, toutes les
tables des connections de test concernées seront effacées et les migrations seront relancées
afin d'actualiser le schéma::

// dans tests/bootstrap.php
use Migrations\TestSuite\Migrator;

$migrator = new Migrator();

// Simple setup sans plugins
$migrator->run();

// Setup sur une base de données autre que 'test'
$migrator->run(['connection' => 'test_other']);

// Setup pour un plugin
$migrator->run(['plugin' => 'Contacts']);

// Lancer les migrations du plugin Documents sur la connection test_docs.
$migrator->run(['plugin' => 'Documents', 'connection' => 'test_docs']);


Si vos migrations se trouvent à différents endroits, celles-ci doivent être executées ainsi::

// Migrations du plugin Contacts sur la connection ``test``, et du plugin Documents sur la connection ``test_docs``
$migrator->runMany([
['plugin' => 'Contacts'],
['plugin' => 'Documents', 'connection' => 'test_docs']
]);

Les informations relatives au status des migrations de test sont rapportées dans les logs de l'application.

.. versionadded: 3.2.0
Migrator was added to complement the new fixtures in CakePHP 4.3.0.
Utiliser Migrations dans les Plugins
====================================

Expand Down
3 changes: 1 addition & 2 deletions phpcs.xml.dist
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,7 @@
<exclude-pattern>*/tests/comparisons/*</exclude-pattern>
<exclude-pattern>*/test_app/config/*</exclude-pattern>
<exclude-pattern>*/TestBlog/config/*</exclude-pattern>
<exclude-pattern>*/BarPlugin/config/*</exclude-pattern>
<exclude-pattern>*/FooPlugin/config/*</exclude-pattern>
<exclude-pattern>*/Migrator/config/*</exclude-pattern>

<!-- Necessary for class aliases used for backwards compat -->
<rule ref="PSR1.Files.SideEffects.FoundWithSymbols">
Expand Down
4 changes: 4 additions & 0 deletions phpunit.xml.dist
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@
<testsuites>
<testsuite name="migrations">
<directory>tests/TestCase</directory>
<exclude>tests/TestCase/TestSuite</exclude>
</testsuite>
<testsuite name="migrator">
<directory>tests/TestCase/TestSuite</directory>
</testsuite>
</testsuites>

Expand Down
92 changes: 77 additions & 15 deletions src/TestSuite/Migrator.php
Original file line number Diff line number Diff line change
Expand Up @@ -35,43 +35,86 @@ public function __construct()
}

/**
* Runs migrations.
* Runs one set of migrations.
* This is useful if all your migrations are located in config/Migrations,
* or in a single directory, or in a single plugin.
*
* For options, {@see \Migrations\Migrations::migrate()}.
*
* @param array $options Migrate options
* @param array<string, mixed> $options Migrate options. Connection defaults to `test`.
* @param bool $truncateTables Truncate all tables after running migrations. Defaults to true.
* @return void
*/
public function run(
array $options = [],
bool $truncateTables = true
): void {
$this->runMany([$options], $truncateTables);
}

/**
* Runs multiple sets of migrations.
* This is useful if your migrations are located in multiple sources, plugins or connections.
*
* For options, {@see \Migrations\Migrations::migrate()}.
*
* Example:
*
* $this->runMany([
* ['connection' => 'some-connection', 'source' => 'some/directory'],
* ['plugin' => 'PluginA']
* ]);
*
* @param array<array<string, mixed>> $options Array of option arrays.
* @param bool $truncateTables Truncate all tables after running migrations. Defaults to true.
* @return void
*/
public function runMany(
array $options = [],
bool $truncateTables = true
): void {
// Don't recreate schema if we are in a phpunit separate process test.
if (isset($GLOBALS['__PHPUNIT_BOOTSTRAP'])) {
return;
}

$options += ['connection' => 'test'];
$migrations = new Migrations();

if ($this->shouldDropTables($migrations, $options)) {
$dropTables = $this->getNonPhinxTables($options['connection']);
if (count($dropTables)) {
$this->helper->dropTables($options['connection'], $dropTables);
// Detect all connections involved, and mark those with changed status.
$connectionsToDrop = [];
$connectionsList = [];
foreach ($options as $i => $migrationSet) {
$migrationSet += ['connection' => 'test'];
$options[$i] = $migrationSet;
$connectionName = $migrationSet['connection'];
if (!in_array($connectionName, $connectionsList)) {
$connectionsList[] = $connectionName;
}
$phinxTables = $this->getPhinxTables($options['connection']);
if (count($phinxTables)) {
$this->helper->truncateTables($options['connection'], $phinxTables);

$migrations = new Migrations();
if (!in_array($connectionName, $connectionsToDrop) && $this->shouldDropTables($migrations, $migrationSet)) {
$connectionsToDrop[] = $connectionName;
}
}

if (!$migrations->migrate($options)) {
throw new RuntimeException(sprintf('Unable to migrate fixtures for `%s`.', $options['connection']));
foreach ($connectionsToDrop as $connectionName) {
$this->dropTables($connectionName);
}

// Run all sets of migrations
foreach ($options as $migrationSet) {
$migrations = new Migrations();

if (!$migrations->migrate($migrationSet)) {
throw new RuntimeException(
sprintf('Unable to migrate fixtures for `%s`.', $migrationSet['connection'])
);
}
}

// Truncate all connections if required in parameters
if ($truncateTables) {
$this->truncate($options['connection']);
foreach ($connectionsList as $connectionName) {
$this->truncate($connectionName);
}
}
}

Expand Down Expand Up @@ -129,6 +172,25 @@ protected function shouldDropTables(Migrations $migrations, array $options): boo
return false;
}

/**
* Drops the regular tables of the provided connection
* and truncates the phinx tables.
*
* @param string $connection Connection on which tables are dropped.
* @return void
*/
protected function dropTables(string $connection): void
{
$dropTables = $this->getNonPhinxTables($connection);
if (count($dropTables)) {
$this->helper->dropTables($connection, $dropTables);
}
$phinxTables = $this->getPhinxTables($connection);
if (count($phinxTables)) {
$this->helper->truncateTables($connection, $phinxTables);
}
}

/**
* Get the list of tables that are phinxlog
*
Expand Down
89 changes: 38 additions & 51 deletions tests/TestCase/TestSuite/MigratorTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,8 @@
*/
namespace Migrations\Test\TestCase\TestSuite;

use Cake\Cache\Cache;
use Cake\Database\Connection;
use Cake\Database\Driver\Sqlite;
use Cake\Datasource\ConnectionManager;
use Cake\TestSuite\ConnectionHelper;
use Cake\TestSuite\TestCase;
use Migrations\TestSuite\Migrator;

Expand All @@ -31,38 +29,25 @@ public function setUp(): void
$this->restore = $GLOBALS['__PHPUNIT_BOOTSTRAP'];
unset($GLOBALS['__PHPUNIT_BOOTSTRAP']);

$this->dropDatabase = tempnam(TMP, 'migrator_test_');
Cache::clear('_cake_model_');
(new ConnectionHelper())->dropTables('test');
}

public function tearDown(): void
{
parent::tearDown();
$GLOBALS['__PHPUNIT_BOOTSTRAP'] = $this->restore;

ConnectionManager::drop('test_migrator');
if (file_exists($this->dropDatabase)) {
unlink($this->dropDatabase);
}
}

public function testMigrateDropTruncate(): void
{
$this->skipIf(!extension_loaded('pdo_sqlite'), 'Skipping as SQLite extension is missing');
ConnectionManager::setConfig('test_migrator', [
'className' => Connection::class,
'driver' => Sqlite::class,
'database' => $this->dropDatabase,
]);

$migrator = new Migrator();
$migrator->run(['connection' => 'test_migrator', 'source' => 'Migrator']);
$migrator->run(['plugin' => 'Migrator']);

$connection = ConnectionManager::get('test_migrator');
$connection = ConnectionManager::get('test');
$tables = $connection->getSchemaCollection()->listTables();
$this->assertContains('migrator', $tables);

$migrator->run(['connection' => 'test_migrator', 'source' => 'Migrator']);
$migrator->run(['plugin' => 'Migrator',]);

$tables = $connection->getSchemaCollection()->listTables();
$this->assertContains('migrator', $tables);
Expand All @@ -72,55 +57,57 @@ public function testMigrateDropTruncate(): void

public function testMigrateDropNoTruncate(): void
{
$this->skipIf(!extension_loaded('pdo_sqlite'), 'Skipping as SQLite extension is missing');
ConnectionManager::setConfig('test_migrator', [
'className' => Connection::class,
'driver' => Sqlite::class,
'database' => $this->dropDatabase,
]);

$migrator = new Migrator();
$migrator->run(['connection' => 'test_migrator', 'source' => 'Migrator'], false);
$migrator->run(['plugin' => 'Migrator'], false);

$connection = ConnectionManager::get('test_migrator');
$connection = ConnectionManager::get('test');
$tables = $connection->getSchemaCollection()->listTables();

$this->assertContains('migrator', $tables);
$this->assertCount(1, $connection->query('SELECT * FROM migrator')->fetchAll());
}

/**
* @depends testMigrateDropNoTruncate
*/
public function testTruncateAfterMigrations(): void
public function testRunManyDropTruncate(): void
{
$this->testMigrateDropNoTruncate();

$migrator = new Migrator();
$migrator->truncate('test_migrator');
$migrator->runMany([
['plugin' => 'Migrator',],
['plugin' => 'Migrator', 'source' => 'Migrations2',],
]);

$connection = ConnectionManager::get('test_migrator');
$connection = ConnectionManager::get('test');
$tables = $connection->getSchemaCollection()->listTables();
$this->assertContains('migrator', $tables);
$this->assertCount(0, $connection->query('SELECT * FROM migrator')->fetchAll());
$this->assertCount(2, $connection->query('SELECT * FROM migrator_phinxlog')->fetchAll());
}

public function testTruncateExternalTables(): void
public function testRunManyDropNoTruncate(): void
{
$this->skipIf(!extension_loaded('pdo_sqlite'), 'Skipping as SQLite extension is missing');
ConnectionManager::setConfig('test_migrator', [
'className' => Connection::class,
'driver' => Sqlite::class,
'database' => $this->dropDatabase,
]);
$connection = ConnectionManager::get('test_migrator');
$connection->execute('CREATE TABLE external_table (colname TEXT NOT NULL);');
$migrator = new Migrator();
$migrator->runMany([
['plugin' => 'Migrator',],
['plugin' => 'Migrator', 'source' => 'Migrations2',],
], false);

$connection = ConnectionManager::get('test');
$tables = $connection->getSchemaCollection()->listTables();
$this->assertContains('external_table', $tables);
$connection->execute('INSERT INTO external_table (colname) VALUES ("test");');
$this->assertCount(1, $connection->query('SELECT * FROM external_table')->fetchAll());
$this->assertContains('migrator', $tables);
$this->assertCount(2, $connection->query('SELECT * FROM migrator')->fetchAll());
$this->assertCount(2, $connection->query('SELECT * FROM migrator_phinxlog')->fetchAll());
}

/**
* @depends testMigrateDropNoTruncate
*/
public function testTruncateAfterMigrations(): void
{
$this->testMigrateDropNoTruncate();

$migrator = new Migrator();
$migrator->truncate('test_migrator');
$migrator->truncate('test');

$this->assertCount(0, $connection->query('SELECT * FROM external_table')->fetchAll());
$connection = ConnectionManager::get('test');
$this->assertCount(0, $connection->query('SELECT * FROM migrator')->fetchAll());
}
}

This file was deleted.

Loading

0 comments on commit 00d039d

Please sign in to comment.