Skip to content

Commit f209857

Browse files
committed
MQE-838: requiredEntity in data file will be overwritten by requiredEntity merged in from another data file
- add new Dom and Filesystem classes with mergeable path entry - update array parsing to use new structure - add new verification test
1 parent 523c639 commit f209857

File tree

11 files changed

+309
-47
lines changed

11 files changed

+309
-47
lines changed

dev/tests/verification/TestModule/Data/ParameterArrayData.xml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,4 +13,9 @@
1313
<data key="uniqueNamePre" unique="prefix">prename</data>
1414
<data key="uniqueNamePost" unique="suffix">postname</data>
1515
</entity>
16+
<entity name="testEntity" type="sample">
17+
<data key="sampleField">value1</data>
18+
<requiredEntity type="test">originalValue</requiredEntity>
19+
<requiredEntity type="test3">originalValue3</requiredEntity>
20+
</entity>
1621
</entities>

dev/tests/verification/TestModule/Data/ReplacementData.xml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,4 +22,8 @@
2222
<data key="firstname" unique="prefix">John</data>
2323
<data key="lastname" unique="suffix">Doe</data>
2424
</entity>
25+
<entity name="testEntity" type="sample">
26+
<data key="sampleField2">moreData</data>
27+
<requiredEntity type="test2">originalValue2</requiredEntity>
28+
</entity>
2529
</entities>

dev/tests/verification/Tests/MergedGenerationTest.php

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
*/
66
namespace tests\verification\Tests;
77

8+
use Magento\FunctionalTestingFramework\DataGenerator\Handlers\DataObjectHandler;
89
use tests\util\MftfTestCase;
910

1011
class MergedGenerationTest extends MftfTestCase
@@ -30,4 +31,13 @@ public function testMergedReferences()
3031
{
3132
$this->generateAndCompareTest('MergedReferencesTest');
3233
}
34+
35+
/**
36+
* Tests the merging of requiredEntity elements in Data, MQE-838
37+
*/
38+
public function testParsedArray()
39+
{
40+
$entity = DataObjectHandler::getInstance()->getObject('testEntity');
41+
$this->assertCount(3, $entity->getLinkedEntities());
42+
}
3343
}

etc/di.xml

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -146,15 +146,19 @@
146146
<argument name="schemaPath" xsi:type="string">Magento/FunctionalTestingFramework/DataGenerator/etc/dataProfileSchema.xsd</argument>
147147
</arguments>
148148
</virtualType>
149-
<virtualType name="Magento\FunctionalTestingFramework\Config\Reader\DataProfile" type="Magento\FunctionalTestingFramework\Config\Reader\Filesystem">
149+
<virtualType name="Magento\FunctionalTestingFramework\Config\Reader\DataProfile" type="Magento\FunctionalTestingFramework\DataGenerator\Config\Reader\Filesystem">
150150
<arguments>
151151
<argument name="fileResolver" xsi:type="object">Magento\FunctionalTestingFramework\Config\FileResolver\Module</argument>
152152
<argument name="converter" xsi:type="object">Magento\FunctionalTestingFramework\Config\Converter</argument>
153+
<argument name="domDocumentClass" xsi:type="string">Magento\FunctionalTestingFramework\DataGenerator\Config\Dom</argument>
153154
<argument name="schemaLocator" xsi:type="object">Magento\FunctionalTestingFramework\Config\SchemaLocator\DataProfile</argument>
154155
<argument name="idAttributes" xsi:type="array">
155156
<item name="/entities/entity" xsi:type="string">name</item>
156157
<item name="/entities/entity/(data|array)" xsi:type="string">key</item>
157-
<item name="/entities/entity/requiredEntity" xsi:type="string">name</item>
158+
</argument>
159+
<argument name="mergeablePaths" xsi:type="array">
160+
<item name="/entities/entity/requiredEntity" xsi:type="string"/>
161+
<item name="/entities/entity/array" xsi:type="string"/>
158162
</argument>
159163
<argument name="fileName" xsi:type="string">*Data.xml</argument>
160164
<argument name="defaultScope" xsi:type="string">Data</argument>

src/Magento/FunctionalTestingFramework/Config/Dom.php

Lines changed: 49 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -113,8 +113,6 @@ public function merge($xml)
113113
* @param \DOMElement $node
114114
* @param string $parentPath path to parent node
115115
* @return void
116-
* @SuppressWarnings(PHPMD.CyclomaticComplexity)
117-
* @SuppressWarnings(PHPMD.NPathComplexity)
118116
*/
119117
protected function mergeNode(\DOMElement $node, $parentPath)
120118
{
@@ -124,39 +122,7 @@ protected function mergeNode(\DOMElement $node, $parentPath)
124122

125123
/* Update matched node attributes and value */
126124
if ($matchedNode) {
127-
128-
//different node type
129-
if ($this->typeAttributeName
130-
&& $node->hasAttribute($this->typeAttributeName)
131-
&& $matchedNode->hasAttribute($this->typeAttributeName)
132-
&& $node->getAttribute($this->typeAttributeName)
133-
!== $matchedNode->getAttribute($this->typeAttributeName)
134-
) {
135-
$this->replaceNodeValue($parentPath, $node, $matchedNode);
136-
return;
137-
}
138-
139-
$this->mergeAttributes($matchedNode, $node);
140-
if ($node->nodeValue === '' && $matchedNode->nodeValue !== '' && $matchedNode->childNodes->length === 1) {
141-
$this->replaceNodeValue($parentPath, $node, $matchedNode);
142-
}
143-
if (!$node->hasChildNodes()) {
144-
return;
145-
}
146-
/* override node value */
147-
if ($this->isTextNode($node)) {
148-
/* skip the case when the matched node has children, otherwise they get overridden */
149-
if (!$matchedNode->hasChildNodes() || $this->isTextNode($matchedNode)) {
150-
$matchedNode->nodeValue = $node->childNodes->item(0)->nodeValue;
151-
}
152-
} else {
153-
/* recursive merge for all child nodes */
154-
foreach ($node->childNodes as $childNode) {
155-
if ($childNode instanceof \DOMElement) {
156-
$this->mergeNode($childNode, $path);
157-
}
158-
}
159-
}
125+
$this->mergeMatchingNode($node, $parentPath, $matchedNode, $path);
160126
} else {
161127
/* Add node as is to the document under the same parent element */
162128
$parentMatchedNode = $this->getMatchedNode($parentPath);
@@ -165,6 +131,53 @@ protected function mergeNode(\DOMElement $node, $parentPath)
165131
}
166132
}
167133

134+
/**
135+
* Function to process matching node merges. Broken into shared logic for extending classes.
136+
*
137+
* @param \DomElement $node
138+
* @param string $parentPath
139+
* @param |DomElement $matchedNode
140+
* @param string $path
141+
* @return void
142+
* @SuppressWarnings(PHPMD.CyclomaticComplexity)
143+
* @SuppressWarnings(PHPMD.NPathComplexity)
144+
*/
145+
protected function mergeMatchingNode(\DomElement $node, $parentPath, $matchedNode, $path)
146+
{
147+
//different node type
148+
if ($this->typeAttributeName
149+
&& $node->hasAttribute($this->typeAttributeName)
150+
&& $matchedNode->hasAttribute($this->typeAttributeName)
151+
&& $node->getAttribute($this->typeAttributeName)
152+
!== $matchedNode->getAttribute($this->typeAttributeName)
153+
) {
154+
$this->replaceNodeValue($parentPath, $node, $matchedNode);
155+
return;
156+
}
157+
158+
$this->mergeAttributes($matchedNode, $node);
159+
if ($node->nodeValue === '' && $matchedNode->nodeValue !== '' && $matchedNode->childNodes->length === 1) {
160+
$this->replaceNodeValue($parentPath, $node, $matchedNode);
161+
}
162+
if (!$node->hasChildNodes()) {
163+
return;
164+
}
165+
/* override node value */
166+
if ($this->isTextNode($node)) {
167+
/* skip the case when the matched node has children, otherwise they get overridden */
168+
if (!$matchedNode->hasChildNodes() || $this->isTextNode($matchedNode)) {
169+
$matchedNode->nodeValue = $node->childNodes->item(0)->nodeValue;
170+
}
171+
} else {
172+
/* recursive merge for all child nodes */
173+
foreach ($node->childNodes as $childNode) {
174+
if ($childNode instanceof \DOMElement) {
175+
$this->mergeNode($childNode, $path);
176+
}
177+
}
178+
}
179+
}
180+
168181
/**
169182
* Replace node value.
170183
*
@@ -174,7 +187,7 @@ protected function mergeNode(\DOMElement $node, $parentPath)
174187
*
175188
* @return void
176189
*/
177-
private function replaceNodeValue($parentPath, \DOMElement $node, \DOMElement $matchedNode)
190+
protected function replaceNodeValue($parentPath, \DOMElement $node, \DOMElement $matchedNode)
178191
{
179192
$parentMatchedNode = $this->getMatchedNode($parentPath);
180193
$newNode = $this->dom->importNode($node, true);

src/Magento/FunctionalTestingFramework/Config/Dom/NodeMergingConfig.php

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,4 +50,14 @@ public function getIdAttribute($nodeXpath)
5050
}
5151
return null;
5252
}
53+
54+
/**
55+
* Getter to return nodePathMatcher for convenience
56+
*
57+
* @return NodePathMatcher
58+
*/
59+
public function getNodePathMatcher()
60+
{
61+
return $this->nodePathMatcher;
62+
}
5363
}

src/Magento/FunctionalTestingFramework/Config/Dom/NodePathMatcher.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ public function match($pathPattern, $xpathSubject)
3030
* @param string $xpath
3131
* @return string
3232
*/
33-
protected function simplifyXpath($xpath)
33+
public function simplifyXpath($xpath)
3434
{
3535
$result = $xpath;
3636
$result = preg_replace('/\[@[^\]]+?\]/', '', $result);
Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
<?php
2+
/**
3+
* Copyright © Magento, Inc. All rights reserved.
4+
* See COPYING.txt for license details.
5+
*/
6+
namespace Magento\FunctionalTestingFramework\DataGenerator\Config;
7+
8+
use Magento\FunctionalTestingFramework\Config\Dom\NodeMergingConfig;
9+
use Magento\FunctionalTestingFramework\Config\Dom\NodePathMatcher;
10+
11+
/**
12+
* Magento configuration XML DOM utility
13+
*/
14+
class Dom extends \Magento\FunctionalTestingFramework\Config\Dom
15+
{
16+
17+
/**
18+
* Array of non keyed mergeable paths
19+
*
20+
* @var array
21+
*/
22+
private $mergeablePaths;
23+
24+
/**
25+
* Build DOM with initial XML contents and specifying identifier attributes for merging. Overridden to include new
26+
* mergeablePaths argument which can be matched for non keyed mergeable xml elements.
27+
*
28+
* Format of $idAttributes: array('/xpath/to/some/node' => 'id_attribute_name')
29+
* The path to ID attribute name should not include any attribute notations or modifiers -- only node names
30+
*
31+
* @param string $xml
32+
* @param array $idAttributes
33+
* @param array $mergeablePaths
34+
* @param string $typeAttributeName
35+
* @param string $schemaFile
36+
* @param string $errorFormat
37+
*/
38+
public function __construct(
39+
$xml,
40+
array $idAttributes = [],
41+
array $mergeablePaths = [],
42+
$typeAttributeName = null,
43+
$schemaFile = null,
44+
$errorFormat = self::ERROR_FORMAT_DEFAULT
45+
) {
46+
$this->schemaFile = $schemaFile;
47+
$this->nodeMergingConfig = new NodeMergingConfig(new NodePathMatcher(), $idAttributes);
48+
$this->mergeablePaths = $mergeablePaths;
49+
$this->typeAttributeName = $typeAttributeName;
50+
$this->errorFormat = $errorFormat;
51+
$this->dom = $this->initDom($xml);
52+
$this->rootNamespace = $this->dom->lookupNamespaceUri($this->dom->namespaceURI);
53+
}
54+
55+
/**
56+
* Recursive merging of the \DOMElement into the original document. Overridden to include a call to
57+
*
58+
* Algorithm:
59+
* 1. Find the same node in original document
60+
* 2. Extend and override original document node attributes and scalar value if found
61+
* 3. Append new node if original document doesn't have the same node
62+
*
63+
* @param \DOMElement $node
64+
* @param string $parentPath path to parent node
65+
* @return void
66+
*/
67+
public function mergeNode(\DOMElement $node, $parentPath)
68+
{
69+
$path = $this->getNodePathByParent($node, $parentPath);
70+
$isMergeablePath = $this->validateIsPathMergeable($path);
71+
72+
$matchedNode = $this->getMatchedNode($path, $isMergeablePath);
73+
74+
/* Update matched node attributes and value */
75+
if ($matchedNode && !$isMergeablePath) {
76+
//different node type
77+
$this->mergeMatchingNode($node, $parentPath, $matchedNode, $path);
78+
} else {
79+
/* Add node as is to the document under the same parent element */
80+
$parentMatchedNode = $this->getMatchedNode($parentPath);
81+
$newNode = $this->dom->importNode($node, true);
82+
$parentMatchedNode->appendChild($newNode);
83+
}
84+
}
85+
86+
/**
87+
* Getter for node by path, overridden to include validation flag for mergeable entries
88+
* An exception is possible if original document contains multiple nodes for identifier
89+
*
90+
* @param string $nodePath
91+
* @oaram boolean $isMergeablePath
92+
* @throws \Exception
93+
* @return \DOMElement|null
94+
*/
95+
public function getMatchedNode($nodePath, $isMergeablePath = false)
96+
{
97+
$xPath = new \DOMXPath($this->dom);
98+
if ($this->rootNamespace) {
99+
$xPath->registerNamespace(self::ROOT_NAMESPACE_PREFIX, $this->rootNamespace);
100+
}
101+
$matchedNodes = $xPath->query($nodePath);
102+
$node = null;
103+
104+
if ($matchedNodes->length > 1 && !$isMergeablePath) {
105+
throw new \Exception("More than one node matching the query: {$nodePath}");
106+
} elseif ($matchedNodes->length == 1) {
107+
$node = $matchedNodes->item(0);
108+
}
109+
return $node;
110+
}
111+
112+
/**
113+
* Function which simplifies and xpath match in dom and compares with listed known mergeable paths
114+
*
115+
* @param string $path
116+
* @return boolean
117+
*/
118+
private function validateIsPathMergeable($path)
119+
{
120+
$simplifiedPath = $this->nodeMergingConfig->getNodePathMatcher()->simplifyXpath($path);
121+
return array_key_exists($simplifiedPath, $this->mergeablePaths);
122+
}
123+
}

0 commit comments

Comments
 (0)