annotate core/modules/migrate/tests/src/Kernel/QueryBatchTest.php @ 19:fa3358dc1485 tip

Add ndrum files
author Chris Cannam
date Wed, 28 Aug 2019 13:14:47 +0100
parents 7a779792577d
children
rev   line source
Chris@0 1 <?php
Chris@0 2
Chris@0 3 namespace Drupal\Tests\migrate\Kernel;
Chris@0 4
Chris@0 5 use Drupal\KernelTests\KernelTestBase;
Chris@0 6 use Drupal\migrate\MigrateException;
Chris@0 7 use Drupal\migrate\Plugin\MigrateIdMapInterface;
Chris@0 8 use Drupal\migrate\Plugin\MigrationInterface;
Chris@0 9 use Drupal\Core\Database\Driver\sqlite\Connection;
Chris@0 10
Chris@0 11 /**
Chris@0 12 * Tests query batching.
Chris@0 13 *
Chris@0 14 * @covers \Drupal\migrate_query_batch_test\Plugin\migrate\source\QueryBatchTest
Chris@0 15 * @group migrate
Chris@0 16 */
Chris@0 17 class QueryBatchTest extends KernelTestBase {
Chris@0 18
Chris@0 19 /**
Chris@0 20 * The mocked migration.
Chris@0 21 *
Chris@12 22 * @var \Drupal\migrate\Plugin\MigrationInterface|\Prophecy\Prophecy\ObjectProphecy
Chris@0 23 */
Chris@0 24 protected $migration;
Chris@0 25
Chris@0 26 /**
Chris@0 27 * {@inheritdoc}
Chris@0 28 */
Chris@0 29 public static $modules = [
Chris@0 30 'migrate',
Chris@0 31 'migrate_query_batch_test',
Chris@0 32 ];
Chris@0 33
Chris@0 34 /**
Chris@0 35 * {@inheritdoc}
Chris@0 36 */
Chris@0 37 protected function setUp() {
Chris@0 38 parent::setUp();
Chris@0 39
Chris@0 40 // Create a mock migration. This will be injected into the source plugin
Chris@0 41 // under test.
Chris@0 42 $this->migration = $this->prophesize(MigrationInterface::class);
Chris@0 43
Chris@0 44 $this->migration->id()->willReturn(
Chris@0 45 $this->randomMachineName(16)
Chris@0 46 );
Chris@0 47 // Prophesize a useless ID map plugin and an empty set of destination IDs.
Chris@0 48 // Calling code can override these prophecies later and set up different
Chris@0 49 // behaviors.
Chris@0 50 $this->migration->getIdMap()->willReturn(
Chris@0 51 $this->prophesize(MigrateIdMapInterface::class)->reveal()
Chris@0 52 );
Chris@0 53 $this->migration->getDestinationIds()->willReturn([]);
Chris@0 54 }
Chris@0 55
Chris@0 56 /**
Chris@0 57 * Tests a negative batch size throws an exception.
Chris@0 58 */
Chris@0 59 public function testBatchSizeNegative() {
Chris@0 60 $this->setExpectedException(MigrateException::class, 'batch_size must be greater than or equal to zero');
Chris@0 61 $plugin = $this->getPlugin(['batch_size' => -1]);
Chris@0 62 $plugin->next();
Chris@0 63 }
Chris@0 64
Chris@0 65 /**
Chris@0 66 * Tests a non integer batch size throws an exception.
Chris@0 67 */
Chris@0 68 public function testBatchSizeNonInteger() {
Chris@0 69 $this->setExpectedException(MigrateException::class, 'batch_size must be greater than or equal to zero');
Chris@0 70 $plugin = $this->getPlugin(['batch_size' => '1']);
Chris@0 71 $plugin->next();
Chris@0 72 }
Chris@0 73
Chris@0 74 /**
Chris@0 75 * {@inheritdoc}
Chris@0 76 */
Chris@0 77 public function queryDataProvider() {
Chris@0 78 // Define the parameters for building the data array. The first element is
Chris@0 79 // the number of source data rows, the second is the batch size to set on
Chris@0 80 // the plugin configuration.
Chris@0 81 $test_parameters = [
Chris@0 82 // Test when batch size is 0.
Chris@0 83 [200, 0],
Chris@0 84 // Test when rows mod batch size is 0.
Chris@0 85 [200, 20],
Chris@0 86 // Test when rows mod batch size is > 0.
Chris@0 87 [200, 30],
Chris@0 88 // Test when batch size = row count.
Chris@0 89 [200, 200],
Chris@0 90 // Test when batch size > row count.
Chris@0 91 [200, 300],
Chris@0 92 ];
Chris@0 93
Chris@0 94 // Build the data provider array. The provider array consists of the source
Chris@0 95 // data rows, the expected result data, the expected count, the plugin
Chris@0 96 // configuration, the expected batch size and the expected batch count.
Chris@0 97 $table = 'query_batch_test';
Chris@0 98 $tests = [];
Chris@0 99 $data_set = 0;
Chris@0 100 foreach ($test_parameters as $data) {
Chris@0 101 list($num_rows, $batch_size) = $data;
Chris@0 102 for ($i = 0; $i < $num_rows; $i++) {
Chris@0 103 $tests[$data_set]['source_data'][$table][] = [
Chris@0 104 'id' => $i,
Chris@0 105 'data' => $this->randomString(),
Chris@0 106 ];
Chris@0 107 }
Chris@0 108 $tests[$data_set]['expected_data'] = $tests[$data_set]['source_data'][$table];
Chris@0 109 $tests[$data_set][2] = $num_rows;
Chris@0 110 // Plugin configuration array.
Chris@0 111 $tests[$data_set][3] = ['batch_size' => $batch_size];
Chris@0 112 // Expected batch size.
Chris@0 113 $tests[$data_set][4] = $batch_size;
Chris@0 114 // Expected batch count is 0 unless a batch size is set.
Chris@0 115 $expected_batch_count = 0;
Chris@0 116 if ($batch_size > 0) {
Chris@0 117 $expected_batch_count = (int) ($num_rows / $batch_size);
Chris@0 118 if ($num_rows % $batch_size) {
Chris@0 119 // If there is a remainder an extra batch is needed to get the
Chris@0 120 // remaining rows.
Chris@0 121 $expected_batch_count++;
Chris@0 122 }
Chris@0 123 }
Chris@0 124 $tests[$data_set][5] = $expected_batch_count;
Chris@0 125 $data_set++;
Chris@0 126 }
Chris@0 127 return $tests;
Chris@0 128 }
Chris@0 129
Chris@0 130 /**
Chris@0 131 * Tests query batch size.
Chris@0 132 *
Chris@0 133 * @param array $source_data
Chris@0 134 * The source data, keyed by table name. Each table is an array containing
Chris@0 135 * the rows in that table.
Chris@0 136 * @param array $expected_data
Chris@0 137 * The result rows the plugin is expected to return.
Chris@0 138 * @param int $num_rows
Chris@0 139 * How many rows the source plugin is expected to return.
Chris@0 140 * @param array $configuration
Chris@0 141 * Configuration for the source plugin specifying the batch size.
Chris@0 142 * @param int $expected_batch_size
Chris@0 143 * The expected batch size, will be set to zero for invalid batch sizes.
Chris@0 144 * @param int $expected_batch_count
Chris@0 145 * The total number of batches.
Chris@0 146 *
Chris@0 147 * @dataProvider queryDataProvider
Chris@0 148 */
Chris@0 149 public function testQueryBatch($source_data, $expected_data, $num_rows, $configuration, $expected_batch_size, $expected_batch_count) {
Chris@0 150 $plugin = $this->getPlugin($configuration);
Chris@0 151
Chris@0 152 // Since we don't yet inject the database connection, we need to use a
Chris@0 153 // reflection hack to set it in the plugin instance.
Chris@0 154 $reflector = new \ReflectionObject($plugin);
Chris@0 155 $property = $reflector->getProperty('database');
Chris@0 156 $property->setAccessible(TRUE);
Chris@0 157
Chris@0 158 $connection = $this->getDatabase($source_data);
Chris@0 159 $property->setValue($plugin, $connection);
Chris@0 160
Chris@0 161 // Test the results.
Chris@0 162 $i = 0;
Chris@0 163 /** @var \Drupal\migrate\Row $row */
Chris@0 164 foreach ($plugin as $row) {
Chris@0 165
Chris@0 166 $expected = $expected_data[$i++];
Chris@0 167 $actual = $row->getSource();
Chris@0 168
Chris@0 169 foreach ($expected as $key => $value) {
Chris@0 170 $this->assertArrayHasKey($key, $actual);
Chris@0 171 $this->assertSame((string) $value, (string) $actual[$key]);
Chris@0 172 }
Chris@0 173 }
Chris@0 174
Chris@0 175 // Test that all rows were retrieved.
Chris@0 176 self::assertSame($num_rows, $i);
Chris@0 177
Chris@0 178 // Test the batch size.
Chris@0 179 if (is_null($expected_batch_size)) {
Chris@0 180 $expected_batch_size = $configuration['batch_size'];
Chris@0 181 }
Chris@0 182 $property = $reflector->getProperty('batchSize');
Chris@0 183 $property->setAccessible(TRUE);
Chris@0 184 self::assertSame($expected_batch_size, $property->getValue($plugin));
Chris@0 185
Chris@0 186 // Test the batch count.
Chris@0 187 if (is_null($expected_batch_count)) {
Chris@0 188 $expected_batch_count = intdiv($num_rows, $expected_batch_size);
Chris@0 189 if ($num_rows % $configuration['batch_size']) {
Chris@0 190 $expected_batch_count++;
Chris@0 191 }
Chris@0 192 }
Chris@0 193 $property = $reflector->getProperty('batch');
Chris@0 194 $property->setAccessible(TRUE);
Chris@0 195 self::assertSame($expected_batch_count, $property->getValue($plugin));
Chris@0 196 }
Chris@0 197
Chris@0 198 /**
Chris@0 199 * Instantiates the source plugin under test.
Chris@0 200 *
Chris@0 201 * @param array $configuration
Chris@0 202 * The source plugin configuration.
Chris@0 203 *
Chris@0 204 * @return \Drupal\migrate\Plugin\MigrateSourceInterface|object
Chris@0 205 * The fully configured source plugin.
Chris@0 206 */
Chris@0 207 protected function getPlugin($configuration) {
Chris@0 208 /** @var \Drupal\migrate\Plugin\MigratePluginManager $plugin_manager */
Chris@0 209 $plugin_manager = $this->container->get('plugin.manager.migrate.source');
Chris@0 210 $plugin = $plugin_manager->createInstance('query_batch_test', $configuration, $this->migration->reveal());
Chris@0 211
Chris@0 212 $this->migration
Chris@0 213 ->getSourcePlugin()
Chris@0 214 ->willReturn($plugin);
Chris@0 215 return $plugin;
Chris@0 216 }
Chris@0 217
Chris@0 218 /**
Chris@0 219 * Builds an in-memory SQLite database from a set of source data.
Chris@0 220 *
Chris@0 221 * @param array $source_data
Chris@0 222 * The source data, keyed by table name. Each table is an array containing
Chris@0 223 * the rows in that table.
Chris@0 224 *
Chris@0 225 * @return \Drupal\Core\Database\Driver\sqlite\Connection
Chris@0 226 * The SQLite database connection.
Chris@0 227 */
Chris@0 228 protected function getDatabase(array $source_data) {
Chris@0 229 // Create an in-memory SQLite database. Plugins can interact with it like
Chris@0 230 // any other database, and it will cease to exist when the connection is
Chris@0 231 // closed.
Chris@0 232 $connection_options = ['database' => ':memory:'];
Chris@0 233 $pdo = Connection::open($connection_options);
Chris@0 234 $connection = new Connection($pdo, $connection_options);
Chris@0 235
Chris@0 236 // Create the tables and fill them with data.
Chris@0 237 foreach ($source_data as $table => $rows) {
Chris@0 238 // Use the biggest row to build the table schema.
Chris@0 239 $counts = array_map('count', $rows);
Chris@0 240 asort($counts);
Chris@0 241 end($counts);
Chris@0 242 $pilot = $rows[key($counts)];
Chris@0 243
Chris@0 244 $connection->schema()
Chris@0 245 ->createTable($table, [
Chris@0 246 // SQLite uses loose affinity typing, so it's OK for every field to
Chris@0 247 // be a text field.
Chris@0 248 'fields' => array_map(function () {
Chris@0 249 return ['type' => 'text'];
Chris@0 250 }, $pilot),
Chris@0 251 ]);
Chris@0 252
Chris@0 253 $fields = array_keys($pilot);
Chris@0 254 $insert = $connection->insert($table)->fields($fields);
Chris@0 255 array_walk($rows, [$insert, 'values']);
Chris@0 256 $insert->execute();
Chris@0 257 }
Chris@0 258 return $connection;
Chris@0 259 }
Chris@0 260
Chris@0 261 }