diff --git a/docs/commands/mftf.md b/docs/commands/mftf.md index cf1260b6a..b2c36dae4 100644 --- a/docs/commands/mftf.md +++ b/docs/commands/mftf.md @@ -491,11 +491,18 @@ To run specific static check scripts ```bash vendor/bin/mftf static-checks testDependencies ``` +```bash +vendor/bin/mftf static-checks actionGroupArguments +``` +```bash +vendor/bin/mftf static-checks testDependencies actionGroupArguments +``` #### Existing static checks * Test Dependency: Checks that test dependencies do not violate Magento module's composer dependencies. - +* Action Group Unused Arguments: Checks that action groups do not have unused arguments. + ### `upgrade:tests` Applies all the MFTF major version upgrade scripts to test components in the given path (`test.xml`, `data.xml`, etc). diff --git a/src/Magento/FunctionalTestingFramework/StaticCheck/ActionGroupArgumentsCheck.php b/src/Magento/FunctionalTestingFramework/StaticCheck/ActionGroupArgumentsCheck.php new file mode 100644 index 000000000..32c7661fe --- /dev/null +++ b/src/Magento/FunctionalTestingFramework/StaticCheck/ActionGroupArgumentsCheck.php @@ -0,0 +1,211 @@ +).)*/mxs'; + const ACTIONGROUP_ARGUMENT_REGEX_PATTERN = '/]*name="([^"\']*)/mxs'; + const ACTIONGROUP_NAME_REGEX_PATTERN = '/getModulesPath(); + + $actionGroupXmlFiles = StaticCheckHelper::buildFileList( + $allModules, + DIRECTORY_SEPARATOR . 'ActionGroup' . DIRECTORY_SEPARATOR + ); + + $this->errors = $this->findErrorsInFileSet($actionGroupXmlFiles); + + $this->output = StaticCheckHelper::printErrorsToFile( + $this->errors, + self::ERROR_LOG_FILENAME, + 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 unused arguments found." + * @return string + */ + public function getOutput() + { + return $this->output; + } + + /** + * Finds all unused arguments in given set of actionGroup files + * @param Finder $files + * @return array $testErrors + */ + private function findErrorsInFileSet($files) + { + $actionGroupErrors = []; + foreach ($files as $filePath) { + $contents = file_get_contents($filePath); + preg_match_all(self::ACTIONGROUP_XML_REGEX_PATTERN, $contents, $actionGroups); + $actionGroupToArguments = $this->buildUnusedArgumentList($actionGroups[0]); + $actionGroupErrors += $this->setErrorOutput($actionGroupToArguments, $filePath); + } + return $actionGroupErrors; + } + + /** + * Builds array of action group => unused arguments + * @param array $actionGroups + * @return array $actionGroupToArguments + */ + private function buildUnusedArgumentList($actionGroups) + { + $actionGroupToArguments = []; + + foreach ($actionGroups as $actionGroupXml) { + preg_match(self::ACTIONGROUP_NAME_REGEX_PATTERN, $actionGroupXml, $actionGroupName); + $unusedArguments = $this->findUnusedArguments($actionGroupXml); + if (!empty($unusedArguments)) { + $actionGroupToArguments[$actionGroupName[1]] = $unusedArguments; + } + } + return $actionGroupToArguments; + } + + /** + * Returns unused arguments in an action group + * @param string $actionGroupXml + * @return array + */ + private function findUnusedArguments($actionGroupXml) + { + $unusedArguments = []; + + preg_match_all(self::ACTIONGROUP_ARGUMENT_REGEX_PATTERN, $actionGroupXml, $arguments); + preg_match(self::ACTIONGROUP_NAME_REGEX_PATTERN, $actionGroupXml, $actionGroupName); + try { + $actionGroup = ActionGroupObjectHandler::getInstance()->getObject($actionGroupName[1]); + } catch (XmlException $e) { + } + foreach ($arguments[1] as $argument) { + //pattern to match all argument references + $patterns = [ + '(\{{2}' . $argument . '(\.[a-zA-Z0-9_\[\]\(\).,\'\/ ]+)?}{2})', + '([(,\s\'$$]' . $argument . '(\.[a-zA-Z0-9_$\[\]]+)?[),\s\'])' + ]; + // matches entity references + if (preg_match($patterns[0], $actionGroupXml)) { + continue; + } + //matches parametrized references + if (preg_match($patterns[1], $actionGroupXml)) { + continue; + } + //for extending action groups, exclude arguments that are also defined in parent action group + if ($this->isParentActionGroupArgument($argument, $actionGroup)) { + continue; + } + $unusedArguments[] = $argument; + } + return $unusedArguments; + } + + /** + * Checks if the argument is also defined in the parent for extending action groups. + * @param string $argument + * @param ActionGroupObject $actionGroup + * @return boolean + */ + private function isParentActionGroupArgument($argument, $actionGroup) + { + $parentActionGroupName = $actionGroup->getParentName(); + if ($parentActionGroupName !== null) { + $parentActionGroup = ActionGroupObjectHandler::getInstance()->getObject($parentActionGroupName); + $parentArguments = $parentActionGroup->getArguments(); + foreach ($parentArguments as $parentArgument) { + if ($argument === $parentArgument->getName()) { + return true; + } + } + } + return false; + } + + /** + * Builds and returns error output for violating references + * + * @param array $actionGroupToArguments + * @param string $path + * @return mixed + */ + private function setErrorOutput($actionGroupToArguments, $path) + { + $actionGroupErrors = []; + if (!empty($actionGroupToArguments)) { + // Build error output + $errorOutput = "\nFile \"{$path->getRealPath()}\""; + $errorOutput .= "\ncontains action group(s) with unused arguments.\n\t\t"; + foreach ($actionGroupToArguments as $actionGroup => $arguments) { + $errorOutput .= "\n\t {$actionGroup} has unused argument(s): " . implode(", ", $arguments); + } + $actionGroupErrors[$path->getRealPath()][] = $errorOutput; + } + return $actionGroupErrors; + } +} diff --git a/src/Magento/FunctionalTestingFramework/StaticCheck/StaticCheckHelper.php b/src/Magento/FunctionalTestingFramework/StaticCheck/StaticCheckHelper.php new file mode 100644 index 000000000..f534544fc --- /dev/null +++ b/src/Magento/FunctionalTestingFramework/StaticCheck/StaticCheckHelper.php @@ -0,0 +1,56 @@ + $error) { + fwrite($fileResource, $error[0] . PHP_EOL); + } + + fclose($fileResource); + $errorCount = count($errors); + $output = $message . ": Errors found across {$errorCount} file(s). Error details output to {$outputPath}"; + + return $output; + } + + /** + * Builds list of all XML files in given modulePaths + path given + * @param array $modulePaths + * @param string $path + * @return Finder + */ + public static function buildFileList($modulePaths, $path) + { + $finder = new Finder(); + foreach ($modulePaths as $modulePath) { + if (!realpath($modulePath . $path)) { + continue; + } + $finder->files()->in($modulePath . $path)->name("*.xml"); + } + return $finder->files(); + } +} diff --git a/src/Magento/FunctionalTestingFramework/StaticCheck/StaticChecksList.php b/src/Magento/FunctionalTestingFramework/StaticCheck/StaticChecksList.php index f3cf20739..ffa63389d 100644 --- a/src/Magento/FunctionalTestingFramework/StaticCheck/StaticChecksList.php +++ b/src/Magento/FunctionalTestingFramework/StaticCheck/StaticChecksList.php @@ -29,6 +29,7 @@ public function __construct(array $checks = []) { $this->checks = [ 'testDependencies' => new TestDependencyCheck(), + 'actionGroupArguments' => new ActionGroupArgumentsCheck(), ] + $checks; } diff --git a/src/Magento/FunctionalTestingFramework/StaticCheck/TestDependencyCheck.php b/src/Magento/FunctionalTestingFramework/StaticCheck/TestDependencyCheck.php index 19725dc18..3c3ed6a37 100644 --- a/src/Magento/FunctionalTestingFramework/StaticCheck/TestDependencyCheck.php +++ b/src/Magento/FunctionalTestingFramework/StaticCheck/TestDependencyCheck.php @@ -32,6 +32,9 @@ class TestDependencyCheck implements StaticCheckInterface const ACTIONGROUP_REGEX_PATTERN = '/ref=["\']([^\'"]*)/'; const ACTIONGROUP_ARGUMENT_REGEX_PATTERN = '/]*name="([^"\']*)/'; + const ERROR_LOG_FILENAME = 'mftf-dependency-checks'; + const ERROR_LOG_MESSAGE = 'MFTF File Dependency Check'; + /** * Array of FullModuleName => [dependencies] * @var array @@ -113,9 +116,9 @@ public function execute(InputInterface $input) DIRECTORY_SEPARATOR . 'Data' . DIRECTORY_SEPARATOR, ]; // These files can contain references to other modules. - $testXmlFiles = $this->buildFileList($allModules, $filePaths[0]); - $actionGroupXmlFiles = $this->buildFileList($allModules, $filePaths[1]); - $dataXmlFiles= $this->buildFileList($allModules, $filePaths[2]); + $testXmlFiles = StaticCheckHelper::buildFileList($allModules, $filePaths[0]); + $actionGroupXmlFiles = StaticCheckHelper::buildFileList($allModules, $filePaths[1]); + $dataXmlFiles= StaticCheckHelper::buildFileList($allModules, $filePaths[2]); $this->errors = []; $this->errors += $this->findErrorsInFileSet($testXmlFiles); @@ -123,7 +126,11 @@ public function execute(InputInterface $input) $this->errors += $this->findErrorsInFileSet($dataXmlFiles); // hold on to the output and print any errors to a file - $this->output = $this->printErrorsToFile(); + $this->output = StaticCheckHelper::printErrorsToFile( + $this->errors, + self::ERROR_LOG_FILENAME, + self::ERROR_LOG_MESSAGE + ); } /** @@ -413,24 +420,6 @@ private function getModuleDependenciesFromReferences($array) return $filenames; } - /** - * Builds list of all XML files in given modulePaths + path given - * @param string $modulePaths - * @param string $path - * @return Finder - */ - private function buildFileList($modulePaths, $path) - { - $finder = new Finder(); - foreach ($modulePaths as $modulePath) { - if (!realpath($modulePath . $path)) { - continue; - } - $finder->files()->in($modulePath . $path)->name("*.xml"); - } - return $finder->files(); - } - /** * Attempts to find any MFTF entity by its name. Returns null if none are found. * @param string $name @@ -461,32 +450,4 @@ private function findEntity($name) } return null; } - - /** - * Prints out given errors to file, and returns summary result string - * @return string - */ - private function printErrorsToFile() - { - $errors = $this->getErrors(); - - if (empty($errors)) { - return "No Dependency errors found."; - } - - $outputPath = getcwd() . DIRECTORY_SEPARATOR . "mftf-dependency-checks.txt"; - $fileResource = fopen($outputPath, 'w'); - $header = "MFTF File Dependency Check:\n"; - fwrite($fileResource, $header); - - foreach ($errors as $test => $error) { - fwrite($fileResource, $error[0] . PHP_EOL); - } - - fclose($fileResource); - $errorCount = count($errors); - $output = "Dependency errors found across {$errorCount} file(s). Error details output to {$outputPath}"; - - return $output; - } }