forked from magento/magento2-functional-testing-framework
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathSuiteGenerator.php
366 lines (326 loc) · 13.4 KB
/
SuiteGenerator.php
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
<?php
/**
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
namespace Magento\FunctionalTestingFramework\Suite;
use Magento\FunctionalTestingFramework\Exceptions\TestFrameworkException;
use Magento\FunctionalTestingFramework\Exceptions\TestReferenceException;
use Magento\FunctionalTestingFramework\Exceptions\XmlException;
use Magento\FunctionalTestingFramework\Suite\Generators\GroupClassGenerator;
use Magento\FunctionalTestingFramework\Suite\Handlers\SuiteObjectHandler;
use Magento\FunctionalTestingFramework\Suite\Objects\SuiteObject;
use Magento\FunctionalTestingFramework\Test\Handlers\TestObjectHandler;
use Magento\FunctionalTestingFramework\Util\Filesystem\DirSetupUtil;
use Magento\FunctionalTestingFramework\Util\Logger\LoggingUtil;
use Magento\FunctionalTestingFramework\Util\Manifest\BaseTestManifest;
use Magento\FunctionalTestingFramework\Util\Path\FilePathFormatter;
use Magento\FunctionalTestingFramework\Util\TestGenerator;
use Symfony\Component\Yaml\Yaml;
/**
* Class SuiteGenerator
* @SuppressWarnings(PHPMD.CouplingBetweenObjects)
*/
class SuiteGenerator
{
const YAML_CODECEPTION_DIST_FILENAME = 'codeception.dist.yml';
const YAML_CODECEPTION_CONFIG_FILENAME = 'codeception.yml';
const YAML_GROUPS_TAG = 'groups';
const YAML_EXTENSIONS_TAG = 'extensions';
const YAML_ENABLED_TAG = 'enabled';
const YAML_COPYRIGHT_TEXT =
"# Copyright © Magento, Inc. All rights reserved.\n# See COPYING.txt for license details.\n";
/**
* Singelton Variable Instance.
*
* @var SuiteGenerator
*/
private static $instance;
/**
* Group Class Generator initialized in constructor.
*
* @var GroupClassGenerator
*/
private $groupClassGenerator;
/**
* Avoids instantiation of LoggingUtil by new.
* @return void
*/
private function __construct()
{
$this->groupClassGenerator = new GroupClassGenerator();
}
/**
* Avoids instantiation of SuiteGenerator by clone.
* @return void
*/
private function __clone()
{
}
/**
* Singleton method which is used to retrieve the instance of the suite generator.
*
* @return SuiteGenerator
*/
public static function getInstance(): SuiteGenerator
{
if (!self::$instance) {
// clear any previous configurations before any generation occurs.
self::clearPreviousGroupPreconditions();
self::clearPreviousSessionConfigEntries();
self::$instance = new SuiteGenerator();
}
return self::$instance;
}
/**
* Function which takes all suite configurations and generates to appropriate directory, updating yml configuration
* as needed. Returns an array of all tests generated keyed by test name.
*
* @param BaseTestManifest $testManifest
* @return void
* @throws \Exception
*/
public function generateAllSuites($testManifest)
{
$suites = $testManifest->getSuiteConfig();
foreach ($suites as $suiteName => $suiteContent) {
$firstElement = array_values($suiteContent)[0];
// if the first element is a string we know that we simply have an array of tests
if (is_string($firstElement)) {
$this->generateSuiteFromTest($suiteName, $suiteContent);
}
// if our first element is an array we know that we have split the suites
if (is_array($firstElement)) {
$this->generateSplitSuiteFromTest($suiteName, $suiteContent);
}
}
}
/**
* Function which takes a suite name and generates corresponding dir, test files, group class, and updates
* yml configuration for group run.
*
* @param string $suiteName
* @return void
* @throws \Exception
*/
public function generateSuite($suiteName)
{
/**@var SuiteObject $suite **/
$this->generateSuiteFromTest($suiteName, []);
}
/**
* Function which takes a suite name and a set of test names. The function then generates all relevant supporting
* files and classes for the suite. The function takes an optional argument for suites which are split by a parallel
* run so that any pre/post conditions can be duplicated.
*
* @param string $suiteName
* @param array $tests
* @param string $originalSuiteName
* @return void
* @throws TestReferenceException
* @throws XmlException
* @throws TestFrameworkException
*/
private function generateSuiteFromTest($suiteName, $tests = [], $originalSuiteName = null)
{
$relativePath = TestGenerator::GENERATED_DIR . DIRECTORY_SEPARATOR . $suiteName;
$fullPath = FilePathFormatter::format(TESTS_MODULE_PATH) . $relativePath . DIRECTORY_SEPARATOR;
DirSetupUtil::createGroupDir($fullPath);
$relevantTests = [];
if (!empty($tests)) {
$this->validateTestsReferencedInSuite($suiteName, $tests, $originalSuiteName);
foreach ($tests as $testName) {
$relevantTests[$testName] = TestObjectHandler::getInstance()->getObject($testName);
}
} else {
$relevantTests = SuiteObjectHandler::getInstance()->getObject($suiteName)->getTests();
}
$this->generateRelevantGroupTests($suiteName, $relevantTests);
$groupNamespace = $this->generateGroupFile($suiteName, $relevantTests, $originalSuiteName);
$this->appendEntriesToConfig($suiteName, $fullPath, $groupNamespace);
LoggingUtil::getInstance()->getLogger(SuiteGenerator::class)->info(
"suite generated",
['suite' => $suiteName, 'relative_path' => $relativePath]
);
}
/**
* Function which validates tests passed in as custom configuration against the configuration defined by the user to
* prevent possible invalid test configurations from executing.
*
* @param string $suiteName
* @param array $testsReferenced
* @param string $originalSuiteName
* @return void
* @throws TestReferenceException
* @throws XmlException
*/
private function validateTestsReferencedInSuite($suiteName, $testsReferenced, $originalSuiteName)
{
$suiteRef = $originalSuiteName ?? $suiteName;
$possibleTestRef = SuiteObjectHandler::getInstance()->getObject($suiteRef)->getTests();
$errorMsg = "Cannot reference tests which are not declared as part of suite";
$invalidTestRef = array_diff($testsReferenced, array_keys($possibleTestRef));
if (!empty($invalidTestRef)) {
$testList = implode("\", \"", $invalidTestRef);
$fullError = $errorMsg . " (Suite: \"{$suiteRef}\" Tests: \"{$testList}\")";
throw new TestReferenceException($fullError, ['suite' => $suiteRef, 'test' => $invalidTestRef]);
}
}
/**
* Function for generating split groups of tests (following a parallel execution). Takes a paralle suite config
* and generates applicable suites.
*
* @param string $suiteName
* @param array $suiteContent
* @return void
* @throws \Exception
*/
private function generateSplitSuiteFromTest($suiteName, $suiteContent)
{
foreach ($suiteContent as $suiteSplitName => $tests) {
$this->generateSuiteFromTest($suiteSplitName, $tests, $suiteName);
}
}
/**
* Function which takes a suite name, array of tests, and an original suite name. The function takes these args
* and generates a group file which captures suite level preconditions.
*
* @param string $suiteName
* @param array $tests
* @param string $originalSuiteName
* @return null|string
* @throws XmlException
* @throws TestReferenceException
*/
private function generateGroupFile($suiteName, $tests, $originalSuiteName)
{
// if there's an original suite name we know that this test came from a split group.
if ($originalSuiteName) {
// create the new suite object
/** @var SuiteObject $originalSuite */
$originalSuite = SuiteObjectHandler::getInstance()->getObject($originalSuiteName);
$suiteObject = new SuiteObject(
$suiteName,
$tests,
[],
$originalSuite->getHooks()
);
} else {
$suiteObject = SuiteObjectHandler::getInstance()->getObject($suiteName);
// we have to handle the case when there is a custom configuration for an existing suite.
if (count($suiteObject->getTests()) != count($tests)) {
return $this->generateGroupFile($suiteName, $tests, $suiteName);
}
}
if (!$suiteObject->requiresGroupFile()) {
// if we do not require a group file we don't need a namespace
return null;
}
// if the suite requires a group file, generate it and set the namespace
return $this->groupClassGenerator->generateGroupClass($suiteObject);
}
/**
* Function which accepts a suite name and suite path and appends a new group entry to the codeception.yml.dist
* file in order to register the set of tests as a new group. Also appends group object location if required
* by suite.
*
* @param string $suiteName
* @param string $suitePath
* @param string $groupNamespace
* @return void
*/
private function appendEntriesToConfig($suiteName, $suitePath, $groupNamespace)
{
$relativeSuitePath = substr($suitePath, strlen(TESTS_BP));
$relativeSuitePath = ltrim($relativeSuitePath, DIRECTORY_SEPARATOR);
$ymlArray = self::getYamlFileContents();
if (!array_key_exists(self::YAML_GROUPS_TAG, $ymlArray)) {
$ymlArray[self::YAML_GROUPS_TAG]= [];
}
if ($groupNamespace) {
$ymlArray[self::YAML_EXTENSIONS_TAG][self::YAML_ENABLED_TAG][] = $groupNamespace;
}
$ymlArray[self::YAML_GROUPS_TAG][$suiteName] = [$relativeSuitePath];
$ymlText = self::YAML_COPYRIGHT_TEXT . Yaml::dump($ymlArray, 10);
file_put_contents(self::getYamlConfigFilePath() . self::YAML_CODECEPTION_CONFIG_FILENAME, $ymlText);
}
/**
* Function which takes the current config.yml array and clears any previous configuration for suite group object
* files.
*
* @return void
*/
private static function clearPreviousSessionConfigEntries()
{
$ymlArray = self::getYamlFileContents();
$newYmlArray = $ymlArray;
// if the yaml entries haven't already been cleared
if (array_key_exists(self::YAML_EXTENSIONS_TAG, $ymlArray)) {
foreach ($ymlArray[self::YAML_EXTENSIONS_TAG][self::YAML_ENABLED_TAG] as $key => $entry) {
if (preg_match('/(Group\\\\.*)/', $entry)) {
unset($newYmlArray[self::YAML_EXTENSIONS_TAG][self::YAML_ENABLED_TAG][$key]);
}
}
// needed for proper yml file generation based on indices
$newYmlArray[self::YAML_EXTENSIONS_TAG][self::YAML_ENABLED_TAG] =
array_values($newYmlArray[self::YAML_EXTENSIONS_TAG][self::YAML_ENABLED_TAG]);
}
if (array_key_exists(self::YAML_GROUPS_TAG, $newYmlArray)) {
unset($newYmlArray[self::YAML_GROUPS_TAG]);
}
$ymlText = self::YAML_COPYRIGHT_TEXT . Yaml::dump($newYmlArray, 10);
file_put_contents(self::getYamlConfigFilePath() . self::YAML_CODECEPTION_CONFIG_FILENAME, $ymlText);
}
/**
* Function which takes a string which is the desired output directory (under _generated) and an array of tests
* relevant to the suite to be generated. The function takes this information and creates a new instance of the test
* generator which is then called to create all the test files for the suite.
*
* @param string $path
* @param array $tests
* @return void
* @throws TestReferenceException
*/
private function generateRelevantGroupTests($path, $tests)
{
$testGenerator = TestGenerator::getInstance($path, $tests);
$testGenerator->createAllTestFiles(null, []);
}
/**
* Function which on first execution deletes all generate php in the MFTF Group directory
*
* @return void
*/
private static function clearPreviousGroupPreconditions()
{
$groupFilePath = GroupClassGenerator::getGroupDirPath();
array_map('unlink', glob("$groupFilePath*.php"));
}
/**
* Function to return contents of codeception.yml file for config changes.
*
* @return array
*/
private static function getYamlFileContents()
{
$configYmlFile = self::getYamlConfigFilePath() . self::YAML_CODECEPTION_CONFIG_FILENAME;
$defaultConfigYmlFile = self::getYamlConfigFilePath() . self::YAML_CODECEPTION_DIST_FILENAME;
$ymlContents = null;
if (file_exists($configYmlFile)) {
$ymlContents = file_get_contents($configYmlFile);
} else {
$ymlContents = file_get_contents($defaultConfigYmlFile);
}
return Yaml::parse($ymlContents) ?? [];
}
/**
* Static getter for the Config yml filepath (as path cannot be stored in a const)
*
* @return string
* @throws TestFrameworkException
*/
private static function getYamlConfigFilePath()
{
return FilePathFormatter::format(TESTS_BP);
}
}