From 040f948cd46033ba62c512896775778a990b7c5b Mon Sep 17 00:00:00 2001 From: Tymoteusz Motylewski <t.motylewski@gmail.com> Date: Mon, 27 Feb 2017 09:45:03 +0100 Subject: [PATCH] [TASK] Move CSV fixtures handling to FunctionalTestCase Usage of CSV fixtures is now possible for all functional tests. Previously it was available only for DataHandler tests. This patch is backward compatible. Resolves: #80007 Releases: master, 7.6 Change-Id: I6aa69825ac144b8c955b51a61060a822163511ca Reviewed-on: https://review.typo3.org/51838 Tested-by: TYPO3com <no-reply@typo3.com> Reviewed-by: Christian Kuhn <lolli@schwarzbu.ch> Tested-by: Christian Kuhn <lolli@schwarzbu.ch> Reviewed-by: Anja Leichsenring <aleichsenring@ab-softlab.de> Tested-by: Anja Leichsenring <aleichsenring@ab-softlab.de> --- .../Core/Functional/FunctionalTestCase.php | 241 ++++++++++++++++++ .../AbstractDataHandlerActionTestCase.php | 211 +-------------- 2 files changed, 243 insertions(+), 209 deletions(-) diff --git a/components/testing_framework/Classes/Core/Functional/FunctionalTestCase.php b/components/testing_framework/Classes/Core/Functional/FunctionalTestCase.php index 3403815da77f..42700a5e7110 100644 --- a/components/testing_framework/Classes/Core/Functional/FunctionalTestCase.php +++ b/components/testing_framework/Classes/Core/Functional/FunctionalTestCase.php @@ -14,11 +14,13 @@ namespace TYPO3\TestingFramework\Core\Functional; * The TYPO3 project - inspiring people to share! */ +use Doctrine\DBAL\DBALException; use Doctrine\DBAL\Platforms\MySqlPlatform; use Doctrine\DBAL\Platforms\PostgreSqlPlatform; use TYPO3\CMS\Core\Authentication\BackendUserAuthentication; use TYPO3\CMS\Core\Cache\Backend\NullBackend; use TYPO3\CMS\Core\Database\ConnectionPool; +use TYPO3\CMS\Core\Tests\Functional\DataHandling\Framework\DataSet; use TYPO3\CMS\Core\Utility\GeneralUtility; use TYPO3\TestingFramework\Core\BaseTestCase; use TYPO3\TestingFramework\Core\Functional\Framework\Frontend\Response; @@ -361,6 +363,245 @@ abstract class FunctionalTestCase extends BaseTestCase $testbase->importXmlDatabaseFixture($path); } + /** + * Import data from a CSV file to database + * Single file can contain data from multiple tables + * + * @param string $path absolute path to the CSV file containing the data set to load + */ + public function importCSVDataSet($path) + { + $dataSet = DataSet::read($path, true); + + foreach ($dataSet->getTableNames() as $tableName) { + $connection = $this->getConnectionPool()->getConnectionForTable($tableName); + foreach ($dataSet->getElements($tableName) as $element) { + try { + $connection->insert($tableName, $element); + } catch (DBALException $e) { + $this->fail('SQL Error for table "' . $tableName . '": ' . LF . $e->getMessage()); + } + } + Testbase::resetTableSequences($connection, $tableName); + } + } + + /** + * Compare data in database with CSV file + * + * @param string $path absolute path to the CSV file + */ + protected function assertCSVDataSet($path) + { + $fileName = GeneralUtility::getFileAbsFileName($path); + + $dataSet = DataSet::read($fileName); + $failMessages = []; + + foreach ($dataSet->getTableNames() as $tableName) { + $hasUidField = ($dataSet->getIdIndex($tableName) !== null); + $records = $this->getAllRecords($tableName, $hasUidField); + foreach ($dataSet->getElements($tableName) as $assertion) { + $result = $this->assertInRecords($assertion, $records); + if ($result === false) { + if ($hasUidField && empty($records[$assertion['uid']])) { + $failMessages[] = 'Record "' . $tableName . ':' . $assertion['uid'] . '" not found in database'; + continue; + } + $recordIdentifier = $tableName . ($hasUidField ? ':' . $assertion['uid'] : ''); + $additionalInformation = ($hasUidField ? $this->renderRecords($assertion, $records[$assertion['uid']]) : $this->arrayToString($assertion)); + $failMessages[] = 'Assertion in data-set failed for "' . $recordIdentifier . '":' . LF . $additionalInformation; + // Unset failed asserted record + if ($hasUidField) { + unset($records[$assertion['uid']]); + } + } else { + // Unset asserted record + unset($records[$result]); + // Increase assertion counter + $this->assertTrue($result !== false); + } + } + if (!empty($records)) { + foreach ($records as $record) { + $recordIdentifier = $tableName . ':' . $record['uid']; + $emptyAssertion = array_fill_keys($dataSet->getFields($tableName), '[none]'); + $reducedRecord = array_intersect_key($record, $emptyAssertion); + $additionalInformation = ($hasUidField ? $this->renderRecords($emptyAssertion, $reducedRecord) : $this->arrayToString($reducedRecord)); + $failMessages[] = 'Not asserted record found for "' . $recordIdentifier . '":' . LF . $additionalInformation; + } + } + } + + if (!empty($failMessages)) { + $this->fail(implode(LF, $failMessages)); + } + } + + /** + * Check if $expectedRecord is present in $actualRecords array + * and compares if all column values from matches + * + * @param array $expectedRecord + * @param array $actualRecords + * @return bool|int|string false if record is not found or some column value doesn't match + */ + protected function assertInRecords(array $expectedRecord, array $actualRecords) + { + foreach ($actualRecords as $index => $record) { + $differentFields = $this->getDifferentFields($expectedRecord, $record); + + if (empty($differentFields)) { + return $index; + } + } + + return false; + } + + /** + * Fetches all records from a database table + * Helper method for assertCSVDataSet + * + * @param string $tableName + * @param bool $hasUidField + * @return array + */ + protected function getAllRecords($tableName, $hasUidField = false) + { + $queryBuilder = $this->getConnectionPool() + ->getQueryBuilderForTable($tableName); + $queryBuilder->getRestrictions()->removeAll(); + $statement = $queryBuilder + ->select('*') + ->from($tableName) + ->execute(); + + if (!$hasUidField) { + return $statement->fetchAll(); + } + + $allRecords = []; + while ($record = $statement->fetch()) { + $index = $record['uid']; + $allRecords[$index] = $record; + } + + return $allRecords; + } + + /** + * Format array as human readable string. Used to format verbose error messages in assertCSVDataSet + * + * @param array $array + * @return string + */ + protected function arrayToString(array $array) + { + $elements = []; + foreach ($array as $key => $value) { + if (is_array($value)) { + $value = $this->arrayToString($value); + } + $elements[] = "'" . $key . "' => '" . $value . "'"; + } + return 'array(' . PHP_EOL . ' ' . implode(', ' . PHP_EOL . ' ', $elements) . PHP_EOL . ')' . PHP_EOL; + } + + /** + * Format output showing difference between expected and actual db row in a human readable way + * Used to format verbose error messages in assertCSVDataSet + * + * @param array $assertion + * @param array $record + * @return string + */ + protected function renderRecords(array $assertion, array $record) + { + $differentFields = $this->getDifferentFields($assertion, $record); + $columns = [ + 'fields' => ['Fields'], + 'assertion' => ['Assertion'], + 'record' => ['Record'], + ]; + $lines = []; + $linesFromXmlValues = []; + $result = ''; + + foreach ($differentFields as $differentField) { + $columns['fields'][] = $differentField; + $columns['assertion'][] = ($assertion[$differentField] === null ? 'NULL' : $assertion[$differentField]); + $columns['record'][] = ($record[$differentField] === null ? 'NULL' : $record[$differentField]); + } + + foreach ($columns as $columnIndex => $column) { + $columnLength = null; + foreach ($column as $value) { + if (strpos($value, '<?xml') === 0) { + $value = '[see diff]'; + } + $valueLength = strlen($value); + if (empty($columnLength) || $valueLength > $columnLength) { + $columnLength = $valueLength; + } + } + foreach ($column as $valueIndex => $value) { + if (strpos($value, '<?xml') === 0) { + if ($columnIndex === 'assertion') { + try { + $this->assertXmlStringEqualsXmlString((string)$value, (string)$record[$columns['fields'][$valueIndex]]); + } catch (\PHPUnit_Framework_ExpectationFailedException $e) { + $linesFromXmlValues[] = 'Diff for field "' . $columns['fields'][$valueIndex] . '":' . PHP_EOL . $e->getComparisonFailure()->getDiff(); + } + } + $value = '[see diff]'; + } + $lines[$valueIndex][$columnIndex] = str_pad($value, $columnLength, ' '); + } + } + + foreach ($lines as $line) { + $result .= implode('|', $line) . PHP_EOL; + } + + foreach ($linesFromXmlValues as $lineFromXmlValues) { + $result .= PHP_EOL . $lineFromXmlValues . PHP_EOL; + } + + return $result; + } + + /** + * Compares two arrays containing db rows and returns array containing column names which don't match + * It's a helper method used in assertCSVDataSet + * + * @param array $assertion + * @param array $record + * @return array + */ + protected function getDifferentFields(array $assertion, array $record) + { + $differentFields = []; + + foreach ($assertion as $field => $value) { + if (strpos($value, '\\*') === 0) { + continue; + } elseif (strpos($value, '<?xml') === 0) { + try { + $this->assertXmlStringEqualsXmlString((string)$value, (string)$record[$field]); + } catch (\PHPUnit_Framework_ExpectationFailedException $e) { + $differentFields[] = $field; + } + } elseif ($value === null && $record[$field] !== $value) { + $differentFields[] = $field; + } elseif ((string)$record[$field] !== (string)$value) { + $differentFields[] = $field; + } + } + + return $differentFields; + } + /** * @param int $pageId * @param array $typoScriptFiles diff --git a/typo3/sysext/core/Tests/Functional/DataHandling/AbstractDataHandlerActionTestCase.php b/typo3/sysext/core/Tests/Functional/DataHandling/AbstractDataHandlerActionTestCase.php index edcc30c62005..6a4e9ccdce59 100644 --- a/typo3/sysext/core/Tests/Functional/DataHandling/AbstractDataHandlerActionTestCase.php +++ b/typo3/sysext/core/Tests/Functional/DataHandling/AbstractDataHandlerActionTestCase.php @@ -14,11 +14,8 @@ namespace TYPO3\CMS\Core\Tests\Functional\DataHandling; * The TYPO3 project - inspiring people to share! */ -use Doctrine\DBAL\DBALException; use TYPO3\CMS\Core\Database\Connection; -use TYPO3\CMS\Core\Tests\Functional\DataHandling\Framework\DataSet; use TYPO3\CMS\Core\Utility\GeneralUtility; -use TYPO3\TestingFramework\Core\Testbase; /** * Functional test for the DataHandler @@ -111,86 +108,14 @@ abstract class AbstractDataHandlerActionTestCase extends \TYPO3\TestingFramework { $fileName = rtrim($this->scenarioDataSetDirectory, '/') . '/' . $dataSetName . '.csv'; $fileName = GeneralUtility::getFileAbsFileName($fileName); - - $dataSet = DataSet::read($fileName, true); - - foreach ($dataSet->getTableNames() as $tableName) { - $connection = $this->getConnectionPool()->getConnectionForTable($tableName); - foreach ($dataSet->getElements($tableName) as $element) { - try { - $connection->insert($tableName, $element); - } catch (DBALException $e) { - $this->fail('SQL Error for table "' . $tableName . '": ' . LF . $e->getMessage()); - } - } - Testbase::resetTableSequences($connection, $tableName); - } + $this->importCSVDataSet($fileName); } protected function assertAssertionDataSet($dataSetName) { $fileName = rtrim($this->assertionDataSetDirectory, '/') . '/' . $dataSetName . '.csv'; $fileName = GeneralUtility::getFileAbsFileName($fileName); - - $dataSet = DataSet::read($fileName); - $failMessages = []; - - foreach ($dataSet->getTableNames() as $tableName) { - $hasUidField = ($dataSet->getIdIndex($tableName) !== null); - $records = $this->getAllRecords($tableName, $hasUidField); - foreach ($dataSet->getElements($tableName) as $assertion) { - $result = $this->assertInRecords($assertion, $records); - if ($result === false) { - if ($hasUidField && empty($records[$assertion['uid']])) { - $failMessages[] = 'Record "' . $tableName . ':' . $assertion['uid'] . '" not found in database'; - continue; - } - $recordIdentifier = $tableName . ($hasUidField ? ':' . $assertion['uid'] : ''); - $additionalInformation = ($hasUidField ? $this->renderRecords($assertion, $records[$assertion['uid']]) : $this->arrayToString($assertion)); - $failMessages[] = 'Assertion in data-set failed for "' . $recordIdentifier . '":' . LF . $additionalInformation; - // Unset failed asserted record - if ($hasUidField) { - unset($records[$assertion['uid']]); - } - } else { - // Unset asserted record - unset($records[$result]); - // Increase assertion counter - $this->assertTrue($result !== false); - } - } - if (!empty($records)) { - foreach ($records as $record) { - $recordIdentifier = $tableName . ':' . $record['uid']; - $emptyAssertion = array_fill_keys($dataSet->getFields($tableName), '[none]'); - $reducedRecord = array_intersect_key($record, $emptyAssertion); - $additionalInformation = ($hasUidField ? $this->renderRecords($emptyAssertion, $reducedRecord) : $this->arrayToString($reducedRecord)); - $failMessages[] = 'Not asserted record found for "' . $recordIdentifier . '":' . LF . $additionalInformation; - } - } - } - - if (!empty($failMessages)) { - $this->fail(implode(LF, $failMessages)); - } - } - - /** - * @param array $assertion - * @param array $records - * @return bool|int|string - */ - protected function assertInRecords(array $assertion, array $records) - { - foreach ($records as $index => $record) { - $differentFields = $this->getDifferentFields($assertion, $record); - - if (empty($differentFields)) { - return $index; - } - } - - return false; + $this->assertCSVDataSet($fileName); } /** @@ -232,138 +157,6 @@ abstract class AbstractDataHandlerActionTestCase extends \TYPO3\TestingFramework } } - /** - * @param string $tableName - * @param bool $hasUidField - * @return array - */ - protected function getAllRecords($tableName, $hasUidField = false) - { - $queryBuilder = $this->getConnectionPool() - ->getQueryBuilderForTable($tableName); - $queryBuilder->getRestrictions()->removeAll(); - $statement = $queryBuilder - ->select('*') - ->from($tableName) - ->execute(); - - if (!$hasUidField) { - return $statement->fetchAll(); - } - - $allRecords = []; - while ($record = $statement->fetch()) { - $index = $record['uid']; - $allRecords[$index] = $record; - } - - return $allRecords; - } - - /** - * @param array $array - * @return string - */ - protected function arrayToString(array $array) - { - $elements = []; - foreach ($array as $key => $value) { - if (is_array($value)) { - $value = $this->arrayToString($value); - } - $elements[] = "'" . $key . "' => '" . $value . "'"; - } - return 'array(' . PHP_EOL . ' ' . implode(', ' . PHP_EOL . ' ', $elements) . PHP_EOL . ')' . PHP_EOL; - } - - /** - * @param array $assertion - * @param array $record - * @return string - */ - protected function renderRecords(array $assertion, array $record) - { - $differentFields = $this->getDifferentFields($assertion, $record); - $columns = [ - 'fields' => ['Fields'], - 'assertion' => ['Assertion'], - 'record' => ['Record'], - ]; - $lines = []; - $linesFromXmlValues = []; - $result = ''; - - foreach ($differentFields as $differentField) { - $columns['fields'][] = $differentField; - $columns['assertion'][] = ($assertion[$differentField] === null ? 'NULL' : $assertion[$differentField]); - $columns['record'][] = ($record[$differentField] === null ? 'NULL' : $record[$differentField]); - } - - foreach ($columns as $columnIndex => $column) { - $columnLength = null; - foreach ($column as $value) { - if (strpos($value, '<?xml') === 0) { - $value = '[see diff]'; - } - $valueLength = strlen($value); - if (empty($columnLength) || $valueLength > $columnLength) { - $columnLength = $valueLength; - } - } - foreach ($column as $valueIndex => $value) { - if (strpos($value, '<?xml') === 0) { - if ($columnIndex === 'assertion') { - try { - $this->assertXmlStringEqualsXmlString((string)$value, (string)$record[$columns['fields'][$valueIndex]]); - } catch (\PHPUnit_Framework_ExpectationFailedException $e) { - $linesFromXmlValues[] = 'Diff for field "' . $columns['fields'][$valueIndex] . '":' . PHP_EOL . $e->getComparisonFailure()->getDiff(); - } - } - $value = '[see diff]'; - } - $lines[$valueIndex][$columnIndex] = str_pad($value, $columnLength, ' '); - } - } - - foreach ($lines as $line) { - $result .= implode('|', $line) . PHP_EOL; - } - - foreach ($linesFromXmlValues as $lineFromXmlValues) { - $result .= PHP_EOL . $lineFromXmlValues . PHP_EOL; - } - - return $result; - } - - /** - * @param array $assertion - * @param array $record - * @return array - */ - protected function getDifferentFields(array $assertion, array $record) - { - $differentFields = []; - - foreach ($assertion as $field => $value) { - if (strpos($value, '\\*') === 0) { - continue; - } elseif (strpos($value, '<?xml') === 0) { - try { - $this->assertXmlStringEqualsXmlString((string)$value, (string)$record[$field]); - } catch (\PHPUnit_Framework_ExpectationFailedException $e) { - $differentFields[] = $field; - } - } elseif ($value === null && $record[$field] !== $value) { - $differentFields[] = $field; - } elseif ((string)$record[$field] !== (string)$value) { - $differentFields[] = $field; - } - } - - return $differentFields; - } - /** * @return \TYPO3\TestingFramework\Core\Functional\Framework\Constraint\RequestSection\HasRecordConstraint */ -- GitLab