diff --git a/dev/tests/_bootstrap.php b/dev/tests/_bootstrap.php index 981da5b8b..655e33910 100644 --- a/dev/tests/_bootstrap.php +++ b/dev/tests/_bootstrap.php @@ -16,8 +16,12 @@ 'includePaths' => [PROJECT_ROOT . '/src'] ]); -// force php to generate -$GLOBALS['FORCE_PHP_GENERATE'] = true; +// set mftf appplication context +\Magento\FunctionalTestingFramework\Config\MftfApplicationConfig::create( + true, + \Magento\FunctionalTestingFramework\Config\MftfApplicationConfig::GENERATION_PHASE, + true +); // Load needed framework env params $TEST_ENVS = [ diff --git a/dev/tests/unit/Magento/FunctionalTestFramework/Test/Util/ActionObjectExtractorTest.php b/dev/tests/unit/Magento/FunctionalTestFramework/Test/Util/ActionObjectExtractorTest.php new file mode 100644 index 000000000..873e622f7 --- /dev/null +++ b/dev/tests/unit/Magento/FunctionalTestFramework/Test/Util/ActionObjectExtractorTest.php @@ -0,0 +1,106 @@ +testActionObjectExtractor = new ActionObjectExtractor(); + } + + /** + * Tests basic action object extraction with a valid parser array. + */ + public function testBasicActionObjectExtration() + { + $actionObjects = $this->testActionObjectExtractor->extractActions($this->createBasicActionObjectArray()); + $this->assertCount(1, $actionObjects); + + /** @var ActionObject $firstElement */ + $firstElement = array_values($actionObjects)[0]; + $this->assertEquals('testAction1', $firstElement->getStepKey()); + $this->assertCount(1, $firstElement->getCustomActionAttributes()); + } + + /** + * Tests an invalid merge order reference (i.e. a step referencing itself). + */ + public function testInvalidMergeOrderReference() + { + $invalidArray = $this->createBasicActionObjectArray('invalidTestAction1', 'invalidTestAction1'); + + $this->expectException('\Magento\FunctionalTestingFramework\Exceptions\TestReferenceException'); + $expectedExceptionMessage = "Invalid ordering configuration in test TestWithSelfReferencingStepKey with step" . + " key(s):\n\tinvalidTestAction1\n"; + $this->expectExceptionMessage($expectedExceptionMessage); + + $this->testActionObjectExtractor->extractActions($invalidArray, 'TestWithSelfReferencingStepKey'); + } + + /** + * Validates a warning is printed to the console when multiple actions reference the same actions for merging. + */ + public function testAmbiguousMergeOrderRefernece() + { + $ambiguousArray = $this->createBasicActionObjectArray('testAction1'); + $ambiguousArray = array_merge( + $ambiguousArray, + $this->createBasicActionObjectArray('testAction2', 'testAction1') + ); + + $ambiguousArray = array_merge( + $ambiguousArray, + $this->createBasicActionObjectArray('testAction3', null, 'testAction1') + ); + + $outputString = "multiple actions referencing step key testAction1 in test AmbiguousRefTest:\n" . + "\ttestAction2\n" . + "\ttestAction3\n"; + + $this->expectOutputString($outputString); + $this->testActionObjectExtractor->extractActions($ambiguousArray, 'AmbiguousRefTest'); + } + + /** + * Utility function to return mock parser output for testing extraction into ActionObjects. + * + * @param string $stepKey + * @param string $before + * @param string $after + * @return array + */ + private function createBasicActionObjectArray($stepKey = 'testAction1', $before = null, $after = null) + { + $baseArray = [ + $stepKey => [ + "nodeName" => "sampleAction", + "stepKey" => $stepKey, + "someAttribute" => "someAttributeValue" + ] + ]; + + if ($before) { + $baseArray[$stepKey] = array_merge($baseArray[$stepKey], ['before' => $before]); + } + + if ($after) { + $baseArray[$stepKey] = array_merge($baseArray[$stepKey], ['after' => $after]); + } + + return $baseArray; + } +} diff --git a/src/Magento/FunctionalTestingFramework/Config/MftfApplicationConfig.php b/src/Magento/FunctionalTestingFramework/Config/MftfApplicationConfig.php new file mode 100644 index 000000000..4d4500b1e --- /dev/null +++ b/src/Magento/FunctionalTestingFramework/Config/MftfApplicationConfig.php @@ -0,0 +1,127 @@ +forceGenerate = $forceGenerate; + + if (!in_array($phase, self::MFTF_PHASES)) { + throw new TestFrameworkException("{$phase} is not an mftf phase"); + } + + $this->phase = $phase; + $this->verboseEnabled = $verboseEnabled; + } + + /** + * Creates an instance of the configuration instance for reference once application has started. This function + * returns void and is only run once during the lifetime of the application. + * + * @param bool $forceGenerate + * @param string $phase + * @param bool $verboseEnabled + * @return void + */ + public static function create($forceGenerate, $phase, $verboseEnabled) + { + if (self::$MFTF_APPLICATION_CONTEXT == null) { + self::$MFTF_APPLICATION_CONTEXT = new MftfApplicationConfig($forceGenerate, $phase, $verboseEnabled); + } + } + + /** + * This function returns an instance of the MftfApplicationConfig which is created once the application starts. + * + * @return MftfApplicationConfig + */ + public static function getConfig() + { + // TODO explicitly set this with AcceptanceTester or MagentoWebDriver + // during execution we cannot guarantee the use of the robofile so we return the default application config, + // we don't want to set the application context in case the user explicitly does so at a later time. + if (self::$MFTF_APPLICATION_CONTEXT == null) { + return new MftfApplicationConfig(); + } + + return self::$MFTF_APPLICATION_CONTEXT; + } + + /** + * Returns a booelan indiciating whether or not the user has indicated a forced generation. + * + * @return bool + */ + public function forceGenerateEnabled() + { + return $this->forceGenerate; + } + + /** + * Returns a boolean indicating whether the user has indicated a verbose run, which will cause all applicable + * text to print to the console. + * + * @return bool + */ + public function verboseEnabled() + { + return $this->verboseEnabled ?? getenv('MFTF_DEBUG'); + } + + /** + * Returns a string which indicates the phase of mftf execution. + * + * @return string + */ + public function getPhase() + { + return $this->phase; + } +} diff --git a/src/Magento/FunctionalTestingFramework/Test/Objects/ActionObject.php b/src/Magento/FunctionalTestingFramework/Test/Objects/ActionObject.php index 9a55755cc..ad062dbed 100644 --- a/src/Magento/FunctionalTestingFramework/Test/Objects/ActionObject.php +++ b/src/Magento/FunctionalTestingFramework/Test/Objects/ActionObject.php @@ -5,6 +5,7 @@ */ namespace Magento\FunctionalTestingFramework\Test\Objects; +use Magento\FunctionalTestingFramework\Config\MftfApplicationConfig; use Magento\FunctionalTestingFramework\DataGenerator\Handlers\DataObjectHandler; use Magento\FunctionalTestingFramework\DataGenerator\Objects\EntityDataObject; use Magento\FunctionalTestingFramework\Exceptions\XmlException; @@ -264,7 +265,8 @@ public function trimAssertionAttributes() $oldAttributes = array_intersect($actionAttributeKeys, ActionObject::OLD_ASSERTION_ATTRIBUTES); if (!empty($oldAttributes)) { // @codingStandardsIgnoreStart - if ($GLOBALS['GENERATE_TESTS'] ?? false == true) { + $appConfig = MftfApplicationConfig::getConfig(); + if ($appConfig->getPhase() == MftfApplicationConfig::GENERATION_PHASE && $appConfig->verboseEnabled()) { echo("WARNING: Use of one line Assertion actions will be deprecated in MFTF 3.0.0, please use nested syntax (Action: {$this->type} StepKey: {$this->stepKey})" . PHP_EOL); } // @codingStandardsIgnoreEnd diff --git a/src/Magento/FunctionalTestingFramework/Test/Util/ActionObjectExtractor.php b/src/Magento/FunctionalTestingFramework/Test/Util/ActionObjectExtractor.php index eb05c7e89..a3ce58a5e 100644 --- a/src/Magento/FunctionalTestingFramework/Test/Util/ActionObjectExtractor.php +++ b/src/Magento/FunctionalTestingFramework/Test/Util/ActionObjectExtractor.php @@ -6,6 +6,9 @@ namespace Magento\FunctionalTestingFramework\Test\Util; +use Magento\FunctionalTestingFramework\Config\MftfApplicationConfig; +use Magento\FunctionalTestingFramework\Exceptions\TestFrameworkException; +use Magento\FunctionalTestingFramework\Exceptions\TestReferenceException; use Magento\FunctionalTestingFramework\Exceptions\XmlException; use Magento\FunctionalTestingFramework\Test\Objects\ActionObject; @@ -42,12 +45,14 @@ public function __construct() * irrelevant tags and returned as an array of ActionObjects. * * @param array $testActions + * @param string $testName * @return array * @throws XmlException */ - public function extractActions($testActions) + public function extractActions($testActions, $testName = null) { $actions = []; + $stepKeyRefs = []; foreach ($testActions as $actionName => $actionData) { $stepKey = $actionData[self::TEST_STEP_MERGE_KEY]; @@ -61,8 +66,6 @@ public function extractActions($testActions) self::TEST_STEP_MERGE_KEY, self::NODE_NAME ); - $linkedAction = null; - $order = null; // Flatten AssertSorted "array" element to parameterArray if (isset($actionData["array"])) { @@ -73,22 +76,14 @@ public function extractActions($testActions) $actionAttributes = $this->processActionGroupArgs($actionAttributes); } - if (array_key_exists(self::TEST_ACTION_BEFORE, $actionData) - and array_key_exists(self::TEST_ACTION_AFTER, $actionData)) { - throw new XmlException(sprintf(self::BEFORE_AFTER_ERROR_MSG, $actionName)); - } - - if (array_key_exists(self::TEST_ACTION_BEFORE, $actionData)) { - $linkedAction = $actionData[self::TEST_ACTION_BEFORE]; - $order = self::TEST_ACTION_BEFORE; - } elseif (array_key_exists(self::TEST_ACTION_AFTER, $actionData)) { - $linkedAction = $actionData[self::TEST_ACTION_AFTER]; - $order = self::TEST_ACTION_AFTER; - } - + $linkedAction = $this->processLinkedActions($actionName, $actionData); $actions = $this->extractFieldActions($actionData, $actions); $actionAttributes = $this->extractFieldReferences($actionData, $actionAttributes); + if ($linkedAction['stepKey'] != null) { + $stepKeyRefs[$linkedAction['stepKey']][] = $stepKey; + } + // TODO this is to be implemented later. Currently the schema does not use or need return var. /*if (array_key_exists(ActionGroupObjectHandler::TEST_ACTION_RETURN_VARIABLE, $actionData)) { $returnVariable = $actionData[ActionGroupObjectHandler::TEST_ACTION_RETURN_VARIABLE]; @@ -98,14 +93,44 @@ public function extractActions($testActions) $stepKey, $actionData[self::NODE_NAME], $actionAttributes, - $linkedAction, - $order + $linkedAction['stepKey'], + $linkedAction['order'] ); } + $this->auditMergeSteps($stepKeyRefs, $testName); + return $actions; } + /** + * Function which processes any actions which have an explicit reference to an additional step for merging purposes. + * Returns an array with keys corresponding to the linked action's stepKey and order. + * + * @param string $actionName + * @param array $actionData + * @return array + * @throws XmlException + */ + private function processLinkedActions($actionName, $actionData) + { + $linkedAction =['stepKey' => null, 'order' => null]; + if (array_key_exists(self::TEST_ACTION_BEFORE, $actionData) + and array_key_exists(self::TEST_ACTION_AFTER, $actionData)) { + throw new XmlException(sprintf(self::BEFORE_AFTER_ERROR_MSG, $actionName)); + } + + if (array_key_exists(self::TEST_ACTION_BEFORE, $actionData)) { + $linkedAction['stepKey'] = $actionData[self::TEST_ACTION_BEFORE]; + $linkedAction['order'] = self::TEST_ACTION_BEFORE; + } elseif (array_key_exists(self::TEST_ACTION_AFTER, $actionData)) { + $linkedAction['stepKey'] = $actionData[self::TEST_ACTION_AFTER]; + $linkedAction['order'] = self::TEST_ACTION_AFTER; + } + + return $linkedAction; + } + /** * Takes the action group reference and parses out arguments as an array that can be passed to override defaults * defined in the action group xml. @@ -194,4 +219,50 @@ private function extractFieldReferences($actionData, $actionAttributes) return $attributes; } + + /** + * Function which validates stepKey references within mergeable actions + * + * @param array $stepKeyRefs + * @param string $testName + * @return void + * @throws TestReferenceException + */ + private function auditMergeSteps($stepKeyRefs, $testName) + { + if (empty($stepKeyRefs)) { + return; + } + + // check for step keys which are referencing themselves as before/after + $invalidStepRef = array_filter($stepKeyRefs, function ($value, $key) { + return in_array($key, $value); + }, ARRAY_FILTER_USE_BOTH); + + if (!empty($invalidStepRef)) { + $errorMsg = "Invalid ordering configuration in test {$testName} with step key(s):\n"; + array_walk($invalidStepRef, function ($value, $key) use (&$errorMsg) { + $errorMsg.="\t{$key}\n"; + }); + + throw new TestReferenceException($errorMsg); + } + + // check for ambiguous references to step keys (multiple refs across test merges). + $atRiskStepRef = array_filter($stepKeyRefs, function ($value) { + return count($value) > 1; + }); + + $multipleActionsError = ""; + foreach ($atRiskStepRef as $stepKey => $stepRefs) { + $multipleActionsError.= "multiple actions referencing step key {$stepKey} in test {$testName}:\n"; + array_walk($stepRefs, function ($value) use (&$multipleActionsError) { + $multipleActionsError.= "\t{$value}\n"; + }); + } + + if (MftfApplicationConfig::getConfig()->verboseEnabled()) { + print $multipleActionsError; + } + } } diff --git a/src/Magento/FunctionalTestingFramework/Test/Util/TestObjectExtractor.php b/src/Magento/FunctionalTestingFramework/Test/Util/TestObjectExtractor.php index 48b7d5552..be4e3c97c 100644 --- a/src/Magento/FunctionalTestingFramework/Test/Util/TestObjectExtractor.php +++ b/src/Magento/FunctionalTestingFramework/Test/Util/TestObjectExtractor.php @@ -6,7 +6,9 @@ namespace Magento\FunctionalTestingFramework\Test\Util; +use Magento\FunctionalTestingFramework\Exceptions\TestFrameworkException; use Magento\FunctionalTestingFramework\Exceptions\XmlException; +use Magento\FunctionalTestingFramework\Test\Objects\ActionObject; use Magento\FunctionalTestingFramework\Test\Objects\TestObject; use Magento\FunctionalTestingFramework\Util\Validation\NameValidationUtil; @@ -109,7 +111,7 @@ public function extractTestData($testData) try { return new TestObject( $testData[self::NAME], - $this->actionObjectExtractor->extractActions($testActions), + $this->actionObjectExtractor->extractActions($testActions, $testData[self::NAME]), $testAnnotations, $testHooks, $filename diff --git a/src/Magento/FunctionalTestingFramework/Util/ConfigSanitizerUtil.php b/src/Magento/FunctionalTestingFramework/Util/ConfigSanitizerUtil.php index c6244ca82..802db4127 100644 --- a/src/Magento/FunctionalTestingFramework/Util/ConfigSanitizerUtil.php +++ b/src/Magento/FunctionalTestingFramework/Util/ConfigSanitizerUtil.php @@ -6,6 +6,8 @@ namespace Magento\FunctionalTestingFramework\Util; +use Magento\FunctionalTestingFramework\Config\MftfApplicationConfig; + /** * Class ConfigSanitizerUtil */ @@ -86,8 +88,7 @@ private static function validateConfigBasedVars($config) */ public static function sanitizeUrl($url) { - $forceGenerate = $GLOBALS['FORCE_PHP_GENERATE'] ?? false; - if (strlen($url) == 0 && $forceGenerate === false) { + if (strlen($url) == 0 && !MftfApplicationConfig::getConfig()->forceGenerateEnabled()) { trigger_error("MAGENTO_BASE_URL must be defined in .env", E_USER_ERROR); } diff --git a/src/Magento/FunctionalTestingFramework/Util/ModuleResolver.php b/src/Magento/FunctionalTestingFramework/Util/ModuleResolver.php index 324e206ce..0fdf37f75 100644 --- a/src/Magento/FunctionalTestingFramework/Util/ModuleResolver.php +++ b/src/Magento/FunctionalTestingFramework/Util/ModuleResolver.php @@ -6,6 +6,8 @@ namespace Magento\FunctionalTestingFramework\Util; +use Magento\FunctionalTestingFramework\Config\MftfApplicationConfig; + /** * Class ModuleResolver, resolve module path based on enabled modules of target Magento instance. * @@ -126,13 +128,11 @@ private function __construct() */ public function getEnabledModules() { - $testGenerationPhase = $GLOBALS['GENERATE_TESTS'] ?? false; - if (isset($this->enabledModules)) { return $this->enabledModules; } - if ($testGenerationPhase) { + if (MftfApplicationConfig::getConfig()->getPhase() == MftfApplicationConfig::GENERATION_PHASE) { $this->printMagentoVersionInfo(); } @@ -190,9 +190,7 @@ public function getModulesPath() } $enabledModules = $this->getEnabledModules(); - $forceGeneration = $GLOBALS['FORCE_PHP_GENERATE'] ?? false; - - if (empty($enabledModules) && !$forceGeneration) { + if (empty($enabledModules) && !MftfApplicationConfig::getConfig()->forceGenerateEnabled()) { trigger_error( "Could not retrieve enabled modules from provided 'MAGENTO_BASE_URL'," . "please make sure Magento is available at this url", @@ -321,12 +319,13 @@ private function getEnabledDirectoryPaths($enabledModules, $allModulePaths) */ private function printMagentoVersionInfo() { - $forceGeneration = $GLOBALS['FORCE_PHP_GENERATE'] ?? false; - if ($forceGeneration) { + + if (MftfApplicationConfig::getConfig()->forceGenerateEnabled()) { return; } $url = ConfigSanitizerUtil::sanitizeUrl(getenv('MAGENTO_BASE_URL')) . $this->versionUrl; - print "Fetching version information from {$url}"; + print "Fetching version information from {$url}\n"; + $ch = curl_init($url); curl_setopt($ch, CURLOPT_CUSTOMREQUEST, "GET"); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); @@ -336,7 +335,9 @@ private function printMagentoVersionInfo() $response = "No version information available."; } - print "\nVersion Information: {$response}\n"; + if (MftfApplicationConfig::getConfig()->verboseEnabled()) { + print "\nVersion Information: {$response}\n"; + } } /**