diff --git a/CHANGELOG.md b/CHANGELOG.md index fb8550f32..2fe99b021 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,12 @@ Magento Functional Testing Framework Changelog ================================================ +4.7.2 +--------- +### Enhancements +* Fail static test when introduced filename does not equal the MFTF object name + contained within. + 4.7.1 --------- ### Enhancements diff --git a/composer.json b/composer.json index 54bd351a4..1b3df06bb 100755 --- a/composer.json +++ b/composer.json @@ -2,7 +2,7 @@ "name": "magento/magento2-functional-testing-framework", "description": "Magento2 Functional Testing Framework", "type": "library", - "version": "4.7.1", + "version": "4.7.2", "license": "AGPL-3.0", "keywords": ["magento", "automation", "functional", "testing"], "config": { diff --git a/composer.lock b/composer.lock index b9e80d0a2..d259ae8ff 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "2e2f0d12aa6286a374515516107f9f3e", + "content-hash": "05b0c01ac74ec5759e894c033284a3d2", "packages": [ { "name": "allure-framework/allure-codeception", diff --git a/dev/tests/unit/Magento/FunctionalTestFramework/Util/ClassFileNameCheckTest.php b/dev/tests/unit/Magento/FunctionalTestFramework/Util/ClassFileNameCheckTest.php new file mode 100644 index 000000000..4c830ca82 --- /dev/null +++ b/dev/tests/unit/Magento/FunctionalTestFramework/Util/ClassFileNameCheckTest.php @@ -0,0 +1,42 @@ +getAllModulePaths(); + $testXmlFiles = $scriptUtil->getModuleXmlFilesByScope($modulePaths, "Test"); + $classFileNameCheck = new ClassFileNamingCheck(); + $result = $classFileNameCheck->findErrorsInFileSet($testXmlFiles, "test"); + $this->assertMatchesRegularExpression('/does not match with file name/', $result[array_keys($result)[0]][0]); + } + + /** + * This Test checks if the file name is renamed to match the class name if + * mismatch not found in class and file name + */ + public function testClassAndFileMismatchStaticCheckWhenViolationsNotFound() + { + $scriptUtil = new ScriptUtil(); + $modulePaths = $scriptUtil->getAllModulePaths(); + $testXmlFiles = $scriptUtil->getModuleXmlFilesByScope($modulePaths, "Page"); + $classFileNameCheck = new ClassFileNamingCheck(); + $result = $classFileNameCheck->findErrorsInFileSet($testXmlFiles, "page"); + $this->assertEquals(count($result), 0); + } +} diff --git a/src/Magento/FunctionalTestingFramework/Console/StaticChecksCommand.php b/src/Magento/FunctionalTestingFramework/Console/StaticChecksCommand.php index 806508ef8..e8d67e04b 100644 --- a/src/Magento/FunctionalTestingFramework/Console/StaticChecksCommand.php +++ b/src/Magento/FunctionalTestingFramework/Console/StaticChecksCommand.php @@ -117,7 +117,7 @@ protected function execute(InputInterface $input, OutputInterface $output) $staticOutput = $staticCheck->getOutput(); LoggingUtil::getInstance()->getLogger(get_class($staticCheck))->info($staticOutput); - $this->ioStyle->text($staticOutput); + $this->ioStyle->text($staticOutput??""); $this->ioStyle->text('Total execution time is ' . (string)($end - $start) . ' seconds.' . PHP_EOL); } diff --git a/src/Magento/FunctionalTestingFramework/StaticCheck/ClassFileNamingCheck.php b/src/Magento/FunctionalTestingFramework/StaticCheck/ClassFileNamingCheck.php new file mode 100644 index 000000000..2b282a963 --- /dev/null +++ b/src/Magento/FunctionalTestingFramework/StaticCheck/ClassFileNamingCheck.php @@ -0,0 +1,184 @@ +scriptUtil = new ScriptUtil(); + $modulePaths = []; + $path = $input->getOption('path'); + if ($path) { + if (!realpath($path)) { + throw new \InvalidArgumentException('Invalid --path option: ' . $path); + } + $modulePaths[] = realpath($path); + } else { + $modulePaths = $this->scriptUtil->getAllModulePaths(); + } + foreach ($modulePaths as $modulePath) { + if (file_exists($modulePath . DIRECTORY_SEPARATOR . self::ALLOW_LIST_FILENAME)) { + $contents = file_get_contents($modulePath . DIRECTORY_SEPARATOR . self::ALLOW_LIST_FILENAME); + foreach (explode("\n", $contents) as $entity) { + $this->allowFailureEntities[$entity] = true; + } + } + } + $testXmlFiles = $this->scriptUtil->getModuleXmlFilesByScope($modulePaths, "Test"); + $actionGroupXmlFiles = $this->scriptUtil->getModuleXmlFilesByScope($modulePaths, "ActionGroup"); + $pageXmlFiles = $this->scriptUtil->getModuleXmlFilesByScope($modulePaths, "Page"); + $sectionXmlFiles = $this->scriptUtil->getModuleXmlFilesByScope($modulePaths, "Section"); + $suiteXmlFiles = $this->scriptUtil->getModuleXmlFilesByScope($modulePaths, 'Suite'); + $this->errors = []; + $this->errors += $this->findErrorsInFileSet($testXmlFiles, 'test'); + $this->errors += $this->findErrorsInFileSet($actionGroupXmlFiles, 'actionGroup'); + $this->errors += $this->findErrorsInFileSet($pageXmlFiles, 'page'); + $this->errors += $this->findErrorsInFileSet($sectionXmlFiles, 'section'); + $this->errors += $this->findErrorsInFileSet($suiteXmlFiles, 'suite'); + + // hold on to the output and print any errors to a file + $this->output = $this->scriptUtil->printErrorsToFile( + $this->errors, + StaticChecksList::getErrorFilesPath() . DIRECTORY_SEPARATOR . self::ERROR_LOG_FILENAME . '.txt', + self::ERROR_LOG_MESSAGE + ); + if (!empty($this->warnings)) { + $this->output .= "\n " . $this->scriptUtil->printWarningsToFile( + $this->warnings, + StaticChecksList::getErrorFilesPath() . DIRECTORY_SEPARATOR . self::WARNING_LOG_FILENAME . '.txt', + self::ERROR_LOG_MESSAGE + ); + } + } + + /** + * Return array containing all errors found after running the execute() function. + * @return array + */ + public function getErrors() + { + return $this->errors; + } + + /** + * Return string of a short human readable result of the check. For example: "No errors found." + * @return string + */ + public function getOutput() + { + return $this->output; + } + + /** + * Returns Violations if found + * @param SplFileInfo $files + * @param string $fileType + * @return array + */ + public function findErrorsInFileSet($files, $fileType) + { + $errors = []; + /** @var SplFileInfo $filePath */ + + foreach ($files as $filePath) { + $fileNameWithoutExtension = pathinfo($filePath->getFilename(), PATHINFO_FILENAME); + $domDocument = new \DOMDocument(); + $domDocument->load($filePath); + $testResult = $this->getAttributesFromDOMNodeList( + $domDocument->getElementsByTagName($fileType), + ["type" => 'name'] + ); + if ($fileNameWithoutExtension != array_values($testResult[0])[0]) { + $isInAllowList = array_key_exists(array_values($testResult[0])[0], $this->allowFailureEntities); + if ($isInAllowList) { + $errorOutput = ucfirst($fileType). " name does not match with file name + {$filePath->getRealPath()}. ".ucfirst($fileType)." ".array_values($testResult[0])[0]; + $this->warnings[$filePath->getFilename()][] = $errorOutput; + continue; + } + $errorOutput = ucfirst($fileType). " name does not match with file name + {$filePath->getRealPath()}. ".ucfirst($fileType)." ".array_values($testResult[0])[0]; + $errors[$filePath->getFilename()][] = $errorOutput; + } + } + return $errors; + } + + /** + * Return attribute value for each node in DOMNodeList as an array + * + * @param DOMNodeList $nodes + * @param string $attributeName + * @return array + */ + public function getAttributesFromDOMNodeList($nodes, $attributeName) + { + $attributes = []; + foreach ($nodes as $node) { + if (is_string($attributeName)) { + $attributeValue = $node->getAttribute($attributeName); + } else { + $attributeValue = [$node->getAttribute(key($attributeName)) => + $node->getAttribute($attributeName[key($attributeName)])]; + } + if (!empty($attributeValue)) { + $attributes[] = $attributeValue; + } + } + return $attributes; + } +} diff --git a/src/Magento/FunctionalTestingFramework/StaticCheck/StaticChecksList.php b/src/Magento/FunctionalTestingFramework/StaticCheck/StaticChecksList.php index 15afec527..0cb6b4a73 100644 --- a/src/Magento/FunctionalTestingFramework/StaticCheck/StaticChecksList.php +++ b/src/Magento/FunctionalTestingFramework/StaticCheck/StaticChecksList.php @@ -20,6 +20,9 @@ class StaticChecksList implements StaticCheckListInterface const PAUSE_ACTION_USAGE_CHECK_NAME = 'pauseActionUsage'; const CREATED_DATA_FROM_OUTSIDE_ACTIONGROUP = 'createdDataFromOutsideActionGroup'; const UNUSED_ENTITY_CHECK = 'unusedEntityCheck'; + + const CLASS_FILE_NAMING_CHECK = 'classFileNamingCheck'; + const STATIC_RESULTS = 'tests' . DIRECTORY_SEPARATOR .'_output' . DIRECTORY_SEPARATOR . 'static-results'; /** @@ -51,8 +54,10 @@ public function __construct(array $checks = []) 'annotations' => new AnnotationsCheck(), self::PAUSE_ACTION_USAGE_CHECK_NAME => new PauseActionUsageCheck(), self::UNUSED_ENTITY_CHECK => new UnusedEntityCheck(), - self::CREATED_DATA_FROM_OUTSIDE_ACTIONGROUP => new CreatedDataFromOutsideActionGroupCheck(), - ] + $checks; + self::CREATED_DATA_FROM_OUTSIDE_ACTIONGROUP => new CreatedDataFromOutsideActionGroupCheck(), + self::CLASS_FILE_NAMING_CHECK => new ClassFileNamingCheck(), + + ] + $checks; // Static checks error files directory if (null === self::$errorFilesPath) { diff --git a/src/Magento/FunctionalTestingFramework/StaticCheck/TestDependencyCheck.php b/src/Magento/FunctionalTestingFramework/StaticCheck/TestDependencyCheck.php index 486fe8cdb..f7bfdeb72 100644 --- a/src/Magento/FunctionalTestingFramework/StaticCheck/TestDependencyCheck.php +++ b/src/Magento/FunctionalTestingFramework/StaticCheck/TestDependencyCheck.php @@ -179,7 +179,7 @@ public function getErrors(): array */ public function getOutput(): string { - return $this->output; + return $this->output??""; } /**