Skip to content

Commit d60de15

Browse files
authored
Merge branch 'develop' into MQE-942
2 parents 5b6185c + 846f9c6 commit d60de15

File tree

5 files changed

+192
-47
lines changed

5 files changed

+192
-47
lines changed

docs/commands/mftf.md

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -430,8 +430,12 @@ The example parameters are taken from the `etc/config/.env.example` file.
430430

431431
### `static-checks`
432432

433-
Runs all or specific MFTF static-checks on the test codebase that MFTF is currently attached to.
434-
If no script name argument is specified, all existing static check scripts will run.
433+
Runs all or specific MFTF static-checks on the test codebase that MFTF is currently attached to.
434+
Behavior for determining what tests to run is as follows:
435+
436+
* If test names are specified, only those tests are run.
437+
* If no test names are specified, tests are run according to `staticRuleset.json`.
438+
* If no `staticRuleset.json` is found, all tests are run.
435439

436440
#### Usage
437441

@@ -489,6 +493,25 @@ vendor/bin/mftf static-checks testDependencies actionGroupArguments
489493
|`actionGroupArguments` | Checks that action groups do not have unused arguments.|
490494
|`deprecatedEntityUsage`| Checks that deprecated test entities are not being referenced.|
491495
496+
#### Defining ruleset
497+
498+
The `static-checks` command will look for a `staticRuleset.json` file under either:
499+
500+
* `dev/tests/acceptance/staticRuleset.json`, if embedded with Magento2
501+
* `dev/staticRuleset.json`, if standalone
502+
503+
This file works as the default configuration to easily allow for the integration of `static-checks` in a CI environment.
504+
Currently, the ruleset only defines the tests to run. Here is an example of the expected format:
505+
506+
```json
507+
{
508+
"tests": [
509+
"actionGroupArguments",
510+
"anotherTest"
511+
]
512+
}
513+
```
514+
492515
### `upgrade:tests`
493516

494517
When the path argument is specified, this `upgrade` command applies all the major version MFTF upgrade scripts to a `Test Module` in the given path.

src/Magento/FunctionalTestingFramework/Console/StaticChecksCommand.php

Lines changed: 57 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,13 @@
2222

2323
class StaticChecksCommand extends Command
2424
{
25+
/**
26+
* Associative array containing static ruleset properties.
27+
*
28+
* @var array
29+
*/
30+
private $ruleSet;
31+
2532
/**
2633
* Pool of all existing static check objects
2734
*
@@ -132,26 +139,15 @@ private function validateInput(InputInterface $input)
132139
{
133140
$this->staticCheckObjects = [];
134141
$requiredChecksNames = $input->getArgument('names');
135-
$invalidCheckNames = [];
136-
// Found user required static check script(s) to run,
137-
// If no static check name is supplied, run all static check scripts
142+
// Build list of static check names to run.
143+
if (empty($requiredChecksNames)) {
144+
$this->parseRulesetJson();
145+
$requiredChecksNames = $this->ruleSet['tests'] ?? null;
146+
}
138147
if (empty($requiredChecksNames)) {
139148
$this->staticCheckObjects = $this->allStaticCheckObjects;
140149
} else {
141-
for ($index = 0; $index < count($requiredChecksNames); $index++) {
142-
if (in_array($requiredChecksNames[$index], array_keys($this->allStaticCheckObjects))) {
143-
$this->staticCheckObjects[$requiredChecksNames[$index]] =
144-
$this->allStaticCheckObjects[$requiredChecksNames[$index]];
145-
} else {
146-
$invalidCheckNames[] = $requiredChecksNames[$index];
147-
}
148-
}
149-
}
150-
151-
if (!empty($invalidCheckNames)) {
152-
throw new InvalidArgumentException(
153-
'Invalid static check script(s): ' . implode(', ', $invalidCheckNames) . '.'
154-
);
150+
$this->validateTestNames($requiredChecksNames);
155151
}
156152

157153
if ($input->getOption('path')) {
@@ -164,4 +160,48 @@ private function validateInput(InputInterface $input)
164160
);
165161
}
166162
}
163+
164+
/**
165+
* Validates that all passed in static-check names match an existing static check
166+
* @param string[] $requiredChecksNames
167+
* @return void
168+
*/
169+
private function validateTestNames($requiredChecksNames)
170+
{
171+
$invalidCheckNames = [];
172+
for ($index = 0; $index < count($requiredChecksNames); $index++) {
173+
if (in_array($requiredChecksNames[$index], array_keys($this->allStaticCheckObjects))) {
174+
$this->staticCheckObjects[$requiredChecksNames[$index]] =
175+
$this->allStaticCheckObjects[$requiredChecksNames[$index]];
176+
} else {
177+
$invalidCheckNames[] = $requiredChecksNames[$index];
178+
}
179+
}
180+
181+
if (!empty($invalidCheckNames)) {
182+
throw new InvalidArgumentException(
183+
'Invalid static check script(s): ' . implode(', ', $invalidCheckNames) . '.'
184+
);
185+
}
186+
}
187+
188+
/**
189+
* Parses and sets local ruleSet. If not found, simply returns and lets script continue.
190+
* @return void;
191+
*/
192+
private function parseRulesetJson()
193+
{
194+
$pathAddition = "/dev/tests/acceptance/";
195+
// MFTF is both NOT attached and no MAGENTO_BP defined in .env
196+
if (MAGENTO_BP === FW_BP) {
197+
$pathAddition = "/dev/";
198+
}
199+
$pathToRuleset = MAGENTO_BP . $pathAddition . "staticRuleset.json";
200+
if (!file_exists($pathToRuleset)) {
201+
$this->ioStyle->text("No ruleset under $pathToRuleset" . PHP_EOL);
202+
return;
203+
}
204+
$this->ioStyle->text("Using ruleset under $pathToRuleset" . PHP_EOL);
205+
$this->ruleSet = json_decode(file_get_contents($pathToRuleset), true);
206+
}
167207
}

src/Magento/FunctionalTestingFramework/StaticCheck/ActionGroupArgumentsCheck.php

Lines changed: 44 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -12,17 +12,16 @@
1212
use Symfony\Component\Finder\Finder;
1313
use Exception;
1414
use Magento\FunctionalTestingFramework\Util\Script\ScriptUtil;
15+
use Symfony\Component\Finder\SplFileInfo;
16+
use DOMElement;
1517

1618
/**
1719
* Class ActionGroupArgumentsCheck
1820
* @package Magento\FunctionalTestingFramework\StaticCheck
1921
*/
2022
class ActionGroupArgumentsCheck implements StaticCheckInterface
2123
{
22-
const ACTIONGROUP_XML_REGEX_PATTERN = '/<actionGroup\sname=(?: (?!<\/actionGroup>).)*/mxs';
23-
const ACTIONGROUP_ARGUMENT_REGEX_PATTERN = '/<argument[^\/>]*name="([^"\']*)/mxs';
2424
const ACTIONGROUP_NAME_REGEX_PATTERN = '/<actionGroup name=["\']([^\'"]*)/';
25-
2625
const ERROR_LOG_FILENAME = 'mftf-arguments-checks';
2726
const ERROR_LOG_MESSAGE = 'MFTF Action Group Unused Arguments Check';
2827

@@ -97,45 +96,62 @@ public function getOutput()
9796
private function findErrorsInFileSet($files)
9897
{
9998
$actionGroupErrors = [];
99+
/** @var SplFileInfo $filePath */
100100
foreach ($files as $filePath) {
101-
$contents = file_get_contents($filePath);
102-
preg_match_all(self::ACTIONGROUP_XML_REGEX_PATTERN, $contents, $actionGroups);
103-
$actionGroupToArguments = $this->buildUnusedArgumentList($actionGroups[0]);
104-
$actionGroupErrors += $this->setErrorOutput($actionGroupToArguments, $filePath);
101+
$actionGroupToArguments = [];
102+
$contents = $filePath->getContents();
103+
/** @var DOMElement $actionGroup */
104+
$actionGroup = $this->getActionGroupDomElement($contents);
105+
$arguments = $this->extractActionGroupArguments($actionGroup);
106+
$unusedArguments = $this->findUnusedArguments($arguments, $contents);
107+
if (!empty($unusedArguments)) {
108+
$actionGroupToArguments[$actionGroup->getAttribute('name')] = $unusedArguments;
109+
$actionGroupErrors += $this->setErrorOutput($actionGroupToArguments, $filePath);
110+
}
105111
}
106112
return $actionGroupErrors;
107113
}
108114

109115
/**
110-
* Builds array of action group => unused arguments
111-
* @param array $actionGroups
112-
* @return array $actionGroupToArguments
116+
* Extract actionGroup DomElement from xml file
117+
* @param string $contents
118+
* @return \DOMElement
113119
*/
114-
private function buildUnusedArgumentList($actionGroups)
120+
public function getActionGroupDomElement($contents)
115121
{
116-
$actionGroupToArguments = [];
122+
$domDocument = new \DOMDocument();
123+
$domDocument->loadXML($contents);
124+
return $domDocument->getElementsByTagName('actionGroup')[0];
125+
}
117126

118-
foreach ($actionGroups as $actionGroupXml) {
119-
preg_match(self::ACTIONGROUP_NAME_REGEX_PATTERN, $actionGroupXml, $actionGroupName);
120-
$unusedArguments = $this->findUnusedArguments($actionGroupXml);
121-
if (!empty($unusedArguments)) {
122-
$actionGroupToArguments[$actionGroupName[1]] = $unusedArguments;
127+
/**
128+
* Get list of action group arguments declared in an action group
129+
* @param \DOMElement $actionGroup
130+
* @return array $arguments
131+
*/
132+
public function extractActionGroupArguments($actionGroup)
133+
{
134+
$arguments = [];
135+
$argumentsNodes = $actionGroup->getElementsByTagName('arguments');
136+
if ($argumentsNodes->length > 0) {
137+
$argumentNodes = $argumentsNodes[0]->getElementsByTagName('argument');
138+
foreach ($argumentNodes as $argumentNode) {
139+
$arguments[] = $argumentNode->getAttribute('name');
123140
}
124141
}
125-
return $actionGroupToArguments;
142+
return $arguments;
126143
}
127144

128145
/**
129146
* Returns unused arguments in an action group
130-
* @param string $actionGroupXml
147+
* @param array $arguments
148+
* @param string $contents
131149
* @return array
132150
*/
133-
private function findUnusedArguments($actionGroupXml)
151+
public function findUnusedArguments($arguments, $contents)
134152
{
135153
$unusedArguments = [];
136-
137-
preg_match_all(self::ACTIONGROUP_ARGUMENT_REGEX_PATTERN, $actionGroupXml, $arguments);
138-
preg_match(self::ACTIONGROUP_NAME_REGEX_PATTERN, $actionGroupXml, $actionGroupName);
154+
preg_match(self::ACTIONGROUP_NAME_REGEX_PATTERN, $contents, $actionGroupName);
139155
$validActionGroup = false;
140156
try {
141157
$actionGroup = ActionGroupObjectHandler::getInstance()->getObject($actionGroupName[1]);
@@ -149,18 +165,18 @@ private function findUnusedArguments($actionGroupXml)
149165
return $unusedArguments;
150166
}
151167

152-
foreach ($arguments[1] as $argument) {
168+
foreach ($arguments as $argument) {
153169
//pattern to match all argument references
154170
$patterns = [
155171
'(\{{2}' . $argument . '(\.[a-zA-Z0-9_\[\]\(\).,\'\/ ]+)?}{2})',
156172
'([(,\s\'$$]' . $argument . '(\.[a-zA-Z0-9_$\[\]]+)?[),\s\'])'
157173
];
158174
// matches entity references
159-
if (preg_match($patterns[0], $actionGroupXml)) {
175+
if (preg_match($patterns[0], $contents)) {
160176
continue;
161177
}
162178
//matches parametrized references
163-
if (preg_match($patterns[1], $actionGroupXml)) {
179+
if (preg_match($patterns[1], $contents)) {
164180
continue;
165181
}
166182
//for extending action groups, exclude arguments that are also defined in parent action group
@@ -196,8 +212,8 @@ private function isParentActionGroupArgument($argument, $actionGroup)
196212
/**
197213
* Builds and returns error output for violating references
198214
*
199-
* @param array $actionGroupToArguments
200-
* @param string $path
215+
* @param array $actionGroupToArguments
216+
* @param SplFileInfo $path
201217
* @return mixed
202218
*/
203219
private function setErrorOutput($actionGroupToArguments, $path)
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
<?php
2+
/**
3+
* Copyright © Magento, Inc. All rights reserved.
4+
* See COPYING.txt for license details.
5+
*/
6+
7+
namespace Magento\FunctionalTestingFramework\Upgrade;
8+
9+
use Magento\FunctionalTestingFramework\StaticCheck\ActionGroupArgumentsCheck;
10+
use Magento\FunctionalTestingFramework\Util\Script\ScriptUtil;
11+
use Symfony\Component\Console\Input\InputInterface;
12+
use Symfony\Component\Console\Output\OutputInterface;
13+
use Symfony\Component\Filesystem\Filesystem;
14+
use DOMElement;
15+
16+
/**
17+
* Class RenameMetadataFiles
18+
* @package Magento\FunctionalTestingFramework\Upgrade
19+
*/
20+
class RemoveUnusedArguments implements UpgradeInterface
21+
{
22+
const ARGUMENTS_BLOCK_REGEX_PATTERN = "/\s*<arguments.*\/arguments>/s";
23+
24+
/**
25+
* Updates all actionGroup xml files
26+
*
27+
* @param InputInterface $input
28+
* @param OutputInterface $output
29+
* @return string
30+
*/
31+
public function execute(InputInterface $input, OutputInterface $output)
32+
{
33+
$scriptUtil = new ScriptUtil();
34+
$testPaths[] = $input->getArgument('path');
35+
if (empty($testPaths[0])) {
36+
$testPaths = $scriptUtil->getAllModulePaths();
37+
}
38+
$xmlFiles = $scriptUtil->getModuleXmlFilesByScope($testPaths, 'ActionGroup');
39+
$actionGroupsUpdated = 0;
40+
$fileSystem = new Filesystem();
41+
foreach ($xmlFiles as $file) {
42+
$contents = $file->getContents();
43+
$argumentsCheck = new ActionGroupArgumentsCheck();
44+
/** @var DOMElement $actionGroup */
45+
$actionGroup = $argumentsCheck->getActionGroupDomElement($contents);
46+
$allArguments = $argumentsCheck->extractActionGroupArguments($actionGroup);
47+
$unusedArguments = $argumentsCheck->findUnusedArguments($allArguments, $contents);
48+
if (empty($unusedArguments)) {
49+
continue;
50+
}
51+
//Remove <arguments> block if all arguments are unused
52+
if (empty(array_diff($allArguments, $unusedArguments))) {
53+
$contents = preg_replace(self::ARGUMENTS_BLOCK_REGEX_PATTERN, '', $contents);
54+
} else {
55+
foreach ($unusedArguments as $argument) {
56+
$argumentRegexPattern = "/\s*<argument.*name\s*=\s*\"".$argument."\".*\/>/";
57+
$contents = preg_replace($argumentRegexPattern, '', $contents);
58+
}
59+
}
60+
$fileSystem->dumpFile($file->getRealPath(), $contents);
61+
$actionGroupsUpdated++;
62+
}
63+
return "Removed unused action group arguments from {$actionGroupsUpdated} file(s).";
64+
}
65+
}

src/Magento/FunctionalTestingFramework/Upgrade/UpgradeScriptList.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ class UpgradeScriptList implements UpgradeScriptListInterface
2828
public function __construct(array $scripts = [])
2929
{
3030
$this->scripts = [
31+
'removeUnusedArguments' => new RemoveUnusedArguments(),
3132
'upgradeTestSchema' => new UpdateTestSchemaPaths(),
3233
'upgradeAssertionSchema' => new UpdateAssertionSchema(),
3334
'renameMetadataFiles' => new RenameMetadataFiles(),

0 commit comments

Comments
 (0)