Skip to content

Commit 2f1c63d

Browse files
authored
Merge branch 'develop' into MQE-1919
2 parents 38cde07 + 53b055d commit 2f1c63d

File tree

5 files changed

+287
-51
lines changed

5 files changed

+287
-51
lines changed

docs/commands/mftf.md

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -491,11 +491,18 @@ To run specific static check scripts
491491
```bash
492492
vendor/bin/mftf static-checks testDependencies
493493
```
494+
```bash
495+
vendor/bin/mftf static-checks actionGroupArguments
496+
```
497+
```bash
498+
vendor/bin/mftf static-checks testDependencies actionGroupArguments
499+
```
494500

495501
#### Existing static checks
496502

497503
* Test Dependency: Checks that test dependencies do not violate Magento module's composer dependencies.
498-
504+
* Action Group Unused Arguments: Checks that action groups do not have unused arguments.
505+
499506
### `upgrade:tests`
500507

501508
Applies all the MFTF major version upgrade scripts to test components in the given path (`test.xml`, `data.xml`, etc).
Lines changed: 211 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,211 @@
1+
<?php
2+
/**
3+
* Copyright © Magento, Inc. All rights reserved.
4+
* See COPYING.txt for license details.
5+
*/
6+
7+
namespace Magento\FunctionalTestingFramework\StaticCheck;
8+
9+
use Magento\FunctionalTestingFramework\Config\MftfApplicationConfig;
10+
use Magento\FunctionalTestingFramework\Exceptions\XmlException;
11+
use Magento\FunctionalTestingFramework\Test\Handlers\ActionGroupObjectHandler;
12+
use Magento\FunctionalTestingFramework\Test\Objects\ActionGroupObject;
13+
use Symfony\Component\Console\Input\InputInterface;
14+
use Magento\FunctionalTestingFramework\Util\ModuleResolver;
15+
use Symfony\Component\Finder\Finder;
16+
use Exception;
17+
18+
/**
19+
* Class ActionGroupArgumentsCheck
20+
* @package Magento\FunctionalTestingFramework\StaticCheck
21+
*/
22+
class ActionGroupArgumentsCheck implements StaticCheckInterface
23+
{
24+
25+
const ACTIONGROUP_XML_REGEX_PATTERN = '/<actionGroup\sname=(?: (?!<\/actionGroup>).)*/mxs';
26+
const ACTIONGROUP_ARGUMENT_REGEX_PATTERN = '/<argument[^\/>]*name="([^"\']*)/mxs';
27+
const ACTIONGROUP_NAME_REGEX_PATTERN = '/<actionGroup name=["\']([^\'"]*)/';
28+
29+
const ERROR_LOG_FILENAME = 'mftf-arguments-checks';
30+
const ERROR_LOG_MESSAGE = 'MFTF Action Group Unused Arguments Check';
31+
32+
/**
33+
* Array containing all errors found after running the execute() function.
34+
* @var array
35+
*/
36+
private $errors = [];
37+
38+
/**
39+
* String representing the output summary found after running the execute() function.
40+
* @var string
41+
*/
42+
private $output;
43+
44+
/**
45+
* Checks unused arguments in action groups and prints out error to file.
46+
*
47+
* @param InputInterface $input
48+
* @return void
49+
* @throws Exception
50+
*/
51+
public function execute(InputInterface $input)
52+
{
53+
MftfApplicationConfig::create(
54+
true,
55+
MftfApplicationConfig::UNIT_TEST_PHASE,
56+
false,
57+
MftfApplicationConfig::LEVEL_NONE,
58+
true
59+
);
60+
61+
$allModules = ModuleResolver::getInstance()->getModulesPath();
62+
63+
$actionGroupXmlFiles = StaticCheckHelper::buildFileList(
64+
$allModules,
65+
DIRECTORY_SEPARATOR . 'ActionGroup' . DIRECTORY_SEPARATOR
66+
);
67+
68+
$this->errors = $this->findErrorsInFileSet($actionGroupXmlFiles);
69+
70+
$this->output = StaticCheckHelper::printErrorsToFile(
71+
$this->errors,
72+
self::ERROR_LOG_FILENAME,
73+
self::ERROR_LOG_MESSAGE
74+
);
75+
}
76+
77+
/**
78+
* Return array containing all errors found after running the execute() function.
79+
* @return array
80+
*/
81+
public function getErrors()
82+
{
83+
return $this->errors;
84+
}
85+
86+
/**
87+
* Return string of a short human readable result of the check. For example: "No unused arguments found."
88+
* @return string
89+
*/
90+
public function getOutput()
91+
{
92+
return $this->output;
93+
}
94+
95+
/**
96+
* Finds all unused arguments in given set of actionGroup files
97+
* @param Finder $files
98+
* @return array $testErrors
99+
*/
100+
private function findErrorsInFileSet($files)
101+
{
102+
$actionGroupErrors = [];
103+
foreach ($files as $filePath) {
104+
$contents = file_get_contents($filePath);
105+
preg_match_all(self::ACTIONGROUP_XML_REGEX_PATTERN, $contents, $actionGroups);
106+
$actionGroupToArguments = $this->buildUnusedArgumentList($actionGroups[0]);
107+
$actionGroupErrors += $this->setErrorOutput($actionGroupToArguments, $filePath);
108+
}
109+
return $actionGroupErrors;
110+
}
111+
112+
/**
113+
* Builds array of action group => unused arguments
114+
* @param array $actionGroups
115+
* @return array $actionGroupToArguments
116+
*/
117+
private function buildUnusedArgumentList($actionGroups)
118+
{
119+
$actionGroupToArguments = [];
120+
121+
foreach ($actionGroups as $actionGroupXml) {
122+
preg_match(self::ACTIONGROUP_NAME_REGEX_PATTERN, $actionGroupXml, $actionGroupName);
123+
$unusedArguments = $this->findUnusedArguments($actionGroupXml);
124+
if (!empty($unusedArguments)) {
125+
$actionGroupToArguments[$actionGroupName[1]] = $unusedArguments;
126+
}
127+
}
128+
return $actionGroupToArguments;
129+
}
130+
131+
/**
132+
* Returns unused arguments in an action group
133+
* @param string $actionGroupXml
134+
* @return array
135+
*/
136+
private function findUnusedArguments($actionGroupXml)
137+
{
138+
$unusedArguments = [];
139+
140+
preg_match_all(self::ACTIONGROUP_ARGUMENT_REGEX_PATTERN, $actionGroupXml, $arguments);
141+
preg_match(self::ACTIONGROUP_NAME_REGEX_PATTERN, $actionGroupXml, $actionGroupName);
142+
try {
143+
$actionGroup = ActionGroupObjectHandler::getInstance()->getObject($actionGroupName[1]);
144+
} catch (XmlException $e) {
145+
}
146+
foreach ($arguments[1] as $argument) {
147+
//pattern to match all argument references
148+
$patterns = [
149+
'(\{{2}' . $argument . '(\.[a-zA-Z0-9_\[\]\(\).,\'\/ ]+)?}{2})',
150+
'([(,\s\'$$]' . $argument . '(\.[a-zA-Z0-9_$\[\]]+)?[),\s\'])'
151+
];
152+
// matches entity references
153+
if (preg_match($patterns[0], $actionGroupXml)) {
154+
continue;
155+
}
156+
//matches parametrized references
157+
if (preg_match($patterns[1], $actionGroupXml)) {
158+
continue;
159+
}
160+
//for extending action groups, exclude arguments that are also defined in parent action group
161+
if ($this->isParentActionGroupArgument($argument, $actionGroup)) {
162+
continue;
163+
}
164+
$unusedArguments[] = $argument;
165+
}
166+
return $unusedArguments;
167+
}
168+
169+
/**
170+
* Checks if the argument is also defined in the parent for extending action groups.
171+
* @param string $argument
172+
* @param ActionGroupObject $actionGroup
173+
* @return boolean
174+
*/
175+
private function isParentActionGroupArgument($argument, $actionGroup)
176+
{
177+
$parentActionGroupName = $actionGroup->getParentName();
178+
if ($parentActionGroupName !== null) {
179+
$parentActionGroup = ActionGroupObjectHandler::getInstance()->getObject($parentActionGroupName);
180+
$parentArguments = $parentActionGroup->getArguments();
181+
foreach ($parentArguments as $parentArgument) {
182+
if ($argument === $parentArgument->getName()) {
183+
return true;
184+
}
185+
}
186+
}
187+
return false;
188+
}
189+
190+
/**
191+
* Builds and returns error output for violating references
192+
*
193+
* @param array $actionGroupToArguments
194+
* @param string $path
195+
* @return mixed
196+
*/
197+
private function setErrorOutput($actionGroupToArguments, $path)
198+
{
199+
$actionGroupErrors = [];
200+
if (!empty($actionGroupToArguments)) {
201+
// Build error output
202+
$errorOutput = "\nFile \"{$path->getRealPath()}\"";
203+
$errorOutput .= "\ncontains action group(s) with unused arguments.\n\t\t";
204+
foreach ($actionGroupToArguments as $actionGroup => $arguments) {
205+
$errorOutput .= "\n\t {$actionGroup} has unused argument(s): " . implode(", ", $arguments);
206+
}
207+
$actionGroupErrors[$path->getRealPath()][] = $errorOutput;
208+
}
209+
return $actionGroupErrors;
210+
}
211+
}
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
<?php
2+
/**
3+
* Copyright © Magento, Inc. All rights reserved.
4+
* See COPYING.txt for license details.
5+
*/
6+
namespace Magento\FunctionalTestingFramework\StaticCheck;
7+
8+
use Symfony\Component\Finder\Finder;
9+
10+
class StaticCheckHelper
11+
{
12+
/**
13+
* Prints out given errors to file, and returns summary result string
14+
* @param array $errors
15+
* @param string $filename
16+
* @param string $message
17+
* @return string
18+
*/
19+
public static function printErrorsToFile($errors, $filename, $message)
20+
{
21+
if (empty($errors)) {
22+
return $message . ": No errors found.";
23+
}
24+
25+
$outputPath = getcwd() . DIRECTORY_SEPARATOR . $filename . ".txt";
26+
$fileResource = fopen($outputPath, 'w');
27+
28+
foreach ($errors as $test => $error) {
29+
fwrite($fileResource, $error[0] . PHP_EOL);
30+
}
31+
32+
fclose($fileResource);
33+
$errorCount = count($errors);
34+
$output = $message . ": Errors found across {$errorCount} file(s). Error details output to {$outputPath}";
35+
36+
return $output;
37+
}
38+
39+
/**
40+
* Builds list of all XML files in given modulePaths + path given
41+
* @param array $modulePaths
42+
* @param string $path
43+
* @return Finder
44+
*/
45+
public static function buildFileList($modulePaths, $path)
46+
{
47+
$finder = new Finder();
48+
foreach ($modulePaths as $modulePath) {
49+
if (!realpath($modulePath . $path)) {
50+
continue;
51+
}
52+
$finder->files()->in($modulePath . $path)->name("*.xml");
53+
}
54+
return $finder->files();
55+
}
56+
}

src/Magento/FunctionalTestingFramework/StaticCheck/StaticChecksList.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ public function __construct(array $checks = [])
2929
{
3030
$this->checks = [
3131
'testDependencies' => new TestDependencyCheck(),
32+
'actionGroupArguments' => new ActionGroupArgumentsCheck(),
3233
] + $checks;
3334
}
3435

src/Magento/FunctionalTestingFramework/StaticCheck/TestDependencyCheck.php

Lines changed: 11 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,9 @@ class TestDependencyCheck implements StaticCheckInterface
3232
const ACTIONGROUP_REGEX_PATTERN = '/ref=["\']([^\'"]*)/';
3333
const ACTIONGROUP_ARGUMENT_REGEX_PATTERN = '/<argument[^\/>]*name="([^"\']*)/';
3434

35+
const ERROR_LOG_FILENAME = 'mftf-dependency-checks';
36+
const ERROR_LOG_MESSAGE = 'MFTF File Dependency Check';
37+
3538
/**
3639
* Array of FullModuleName => [dependencies]
3740
* @var array
@@ -113,17 +116,21 @@ public function execute(InputInterface $input)
113116
DIRECTORY_SEPARATOR . 'Data' . DIRECTORY_SEPARATOR,
114117
];
115118
// These files can contain references to other modules.
116-
$testXmlFiles = $this->buildFileList($allModules, $filePaths[0]);
117-
$actionGroupXmlFiles = $this->buildFileList($allModules, $filePaths[1]);
118-
$dataXmlFiles= $this->buildFileList($allModules, $filePaths[2]);
119+
$testXmlFiles = StaticCheckHelper::buildFileList($allModules, $filePaths[0]);
120+
$actionGroupXmlFiles = StaticCheckHelper::buildFileList($allModules, $filePaths[1]);
121+
$dataXmlFiles= StaticCheckHelper::buildFileList($allModules, $filePaths[2]);
119122

120123
$this->errors = [];
121124
$this->errors += $this->findErrorsInFileSet($testXmlFiles);
122125
$this->errors += $this->findErrorsInFileSet($actionGroupXmlFiles);
123126
$this->errors += $this->findErrorsInFileSet($dataXmlFiles);
124127

125128
// hold on to the output and print any errors to a file
126-
$this->output = $this->printErrorsToFile();
129+
$this->output = StaticCheckHelper::printErrorsToFile(
130+
$this->errors,
131+
self::ERROR_LOG_FILENAME,
132+
self::ERROR_LOG_MESSAGE
133+
);
127134
}
128135

129136
/**
@@ -413,24 +420,6 @@ private function getModuleDependenciesFromReferences($array)
413420
return $filenames;
414421
}
415422

416-
/**
417-
* Builds list of all XML files in given modulePaths + path given
418-
* @param string $modulePaths
419-
* @param string $path
420-
* @return Finder
421-
*/
422-
private function buildFileList($modulePaths, $path)
423-
{
424-
$finder = new Finder();
425-
foreach ($modulePaths as $modulePath) {
426-
if (!realpath($modulePath . $path)) {
427-
continue;
428-
}
429-
$finder->files()->in($modulePath . $path)->name("*.xml");
430-
}
431-
return $finder->files();
432-
}
433-
434423
/**
435424
* Attempts to find any MFTF entity by its name. Returns null if none are found.
436425
* @param string $name
@@ -461,32 +450,4 @@ private function findEntity($name)
461450
}
462451
return null;
463452
}
464-
465-
/**
466-
* Prints out given errors to file, and returns summary result string
467-
* @return string
468-
*/
469-
private function printErrorsToFile()
470-
{
471-
$errors = $this->getErrors();
472-
473-
if (empty($errors)) {
474-
return "No Dependency errors found.";
475-
}
476-
477-
$outputPath = getcwd() . DIRECTORY_SEPARATOR . "mftf-dependency-checks.txt";
478-
$fileResource = fopen($outputPath, 'w');
479-
$header = "MFTF File Dependency Check:\n";
480-
fwrite($fileResource, $header);
481-
482-
foreach ($errors as $test => $error) {
483-
fwrite($fileResource, $error[0] . PHP_EOL);
484-
}
485-
486-
fclose($fileResource);
487-
$errorCount = count($errors);
488-
$output = "Dependency errors found across {$errorCount} file(s). Error details output to {$outputPath}";
489-
490-
return $output;
491-
}
492453
}

0 commit comments

Comments
 (0)