diff --git a/src/Magento/FunctionalTestingFramework/Util/TestGenerator.php b/src/Magento/FunctionalTestingFramework/Util/TestGenerator.php
index 09c353029..66c11975c 100644
--- a/src/Magento/FunctionalTestingFramework/Util/TestGenerator.php
+++ b/src/Magento/FunctionalTestingFramework/Util/TestGenerator.php
@@ -40,9 +40,11 @@ class TestGenerator
const REQUIRED_ENTITY_REFERENCE = 'createDataKey';
const GENERATED_DIR = '_generated';
const DEFAULT_DIR = 'default';
+
const TEST_SCOPE = 'test';
const HOOK_SCOPE = 'hook';
const SUITE_SCOPE = 'suite';
+
const PRESSKEY_ARRAY_ANCHOR_KEY = '987654321098765432109876543210';
const PERSISTED_OBJECT_NOTATION_REGEX = '/\${1,2}[\w.\[\]]+\${1,2}/';
const NO_STEPKEY_ACTIONS = [
@@ -53,7 +55,11 @@ class TestGenerator
'generateDate',
'field'
];
+ const RULE_ERROR = 'On step with stepKey "%s", only one of the attributes: "%s" can be use for action "%s"';
+
const STEP_KEY_ANNOTATION = " // stepKey: %s";
+ const ARRAY_WRAP_OPEN = '[';
+ const ARRAY_WRAP_CLOSE = ']';
/**
* Actor name for AcceptanceTest
@@ -105,7 +111,7 @@ class TestGenerator
private $currentGenerationScope = TestGenerator::TEST_SCOPE;
/**
- * TestGenerator constructor.
+ * Private constructor for Factory
*
* @param string $exportDir
* @param array $tests
@@ -114,13 +120,11 @@ class TestGenerator
*/
private function __construct($exportDir, $tests, $debug = false)
{
- // private constructor for factory
$this->exportDirName = $exportDir ?? self::DEFAULT_DIR;
- $exportDir = $exportDir ?? self::DEFAULT_DIR;
$this->exportDirectory = FilePathFormatter::format(TESTS_MODULE_PATH)
. self::GENERATED_DIR
. DIRECTORY_SEPARATOR
- . $exportDir;
+ . $this->exportDirName;
$this->tests = $tests;
$this->consoleOutput = new \Symfony\Component\Console\Output\ConsoleOutput();
$this->debug = $debug;
@@ -185,13 +189,13 @@ private function loadAllTestObjects($testsToIgnore)
* @return void
* @throws \Exception
*/
- private function createCestFile($testPhp, $filename)
+ private function createCestFile(string $testPhp, string $filename)
{
$exportFilePath = $this->exportDirectory . DIRECTORY_SEPARATOR . $filename . ".php";
$file = fopen($exportFilePath, 'w');
if (!$file) {
- throw new \Exception("Could not open the file.");
+ throw new \Exception(sprintf('Could not open test file: "%s"', $exportFilePath));
}
fwrite($file, $testPhp);
@@ -628,9 +632,9 @@ public function generateStepsPhp($actionObjects, $generationScope = TestGenerato
// validate the param array is in the correct format
$this->validateParameterArray($customActionAttributes['parameterArray']);
- $parameterArray = "[";
- $parameterArray .= $this->addUniquenessToParamArray($customActionAttributes['parameterArray']);
- $parameterArray .= "]";
+ $parameterArray = $this->wrapParameterArray(
+ $this->addUniquenessToParamArray($customActionAttributes['parameterArray'])
+ );
}
if (isset($customActionAttributes['requiredAction'])) {
@@ -748,13 +752,8 @@ public function generateStepsPhp($actionObjects, $generationScope = TestGenerato
if (!empty($requiredEntityKeys)) {
$requiredEntityKeysArray = '"' . implode('", "', $requiredEntityKeys) . '"';
}
- //Determine Scope
- $scope = PersistedObjectHandler::TEST_SCOPE;
- if ($generationScope == TestGenerator::HOOK_SCOPE) {
- $scope = PersistedObjectHandler::HOOK_SCOPE;
- } elseif ($generationScope == TestGenerator::SUITE_SCOPE) {
- $scope = PersistedObjectHandler::SUITE_SCOPE;
- }
+
+ $scope = $this->getObjectScope($generationScope);
$createEntityFunctionCall = "\t\t\${$actor}->createEntity(";
$createEntityFunctionCall .= "\"{$stepKey}\",";
@@ -782,13 +781,7 @@ public function generateStepsPhp($actionObjects, $generationScope = TestGenerato
$actionGroup = $actionObject->getCustomActionAttributes()['actionGroup'] ?? null;
$key .= $actionGroup;
- //Determine Scope
- $scope = PersistedObjectHandler::TEST_SCOPE;
- if ($generationScope == TestGenerator::HOOK_SCOPE) {
- $scope = PersistedObjectHandler::HOOK_SCOPE;
- } elseif ($generationScope == TestGenerator::SUITE_SCOPE) {
- $scope = PersistedObjectHandler::SUITE_SCOPE;
- }
+ $scope = $this->getObjectScope($generationScope);
$deleteEntityFunctionCall = "\t\t\${$actor}->deleteEntity(";
$deleteEntityFunctionCall .= "\"{$key}\",";
@@ -831,12 +824,7 @@ public function generateStepsPhp($actionObjects, $generationScope = TestGenerato
$requiredEntityKeysArray = '"' . implode('", "', $requiredEntityKeys) . '"';
}
- $scope = PersistedObjectHandler::TEST_SCOPE;
- if ($generationScope == TestGenerator::HOOK_SCOPE) {
- $scope = PersistedObjectHandler::HOOK_SCOPE;
- } elseif ($generationScope == TestGenerator::SUITE_SCOPE) {
- $scope = PersistedObjectHandler::SUITE_SCOPE;
- }
+ $scope = $this->getObjectScope($generationScope);
$updateEntityFunctionCall = "\t\t\${$actor}->updateEntity(";
$updateEntityFunctionCall .= "\"{$key}\",";
@@ -870,13 +858,7 @@ public function generateStepsPhp($actionObjects, $generationScope = TestGenerato
$requiredEntityKeysArray = '"' . implode('", "', $requiredEntityKeys) . '"';
}
- //Determine Scope
- $scope = PersistedObjectHandler::TEST_SCOPE;
- if ($generationScope == TestGenerator::HOOK_SCOPE) {
- $scope = PersistedObjectHandler::HOOK_SCOPE;
- } elseif ($generationScope == TestGenerator::SUITE_SCOPE) {
- $scope = PersistedObjectHandler::SUITE_SCOPE;
- }
+ $scope = $this->getObjectScope($generationScope);
//Create Function
$getEntityFunctionCall = "\t\t\${$actor}->getEntity(";
@@ -1303,7 +1285,7 @@ public function generateStepsPhp($actionObjects, $generationScope = TestGenerato
break;
case "comment":
$input = $input === null ? strtr($value, ['$' => '\$', '{' => '\{', '}' => '\}']) : $input;
- // Combining userInput from native XML comment and action to fall-through 'default' case
+ // Combining userInput from native XML comment and action to fall-through 'default' case
default:
$testSteps .= $this->wrapFunctionCall(
$actor,
@@ -1380,9 +1362,9 @@ private function trimVariableIfNeeded($input)
preg_match('/"{\$[a-z][a-zA-Z\d]+}"/', $input, $match);
if (isset($match[0])) {
return trim($input, '{}"');
- } else {
- return $input;
}
+
+ return $input;
}
/**
@@ -1403,7 +1385,7 @@ private function replaceMatchesIntoArg($matches, &$outputArg)
$variable = $this->stripAndSplitReference($match, $delimiter);
if (count($variable) != 2) {
throw new \Exception(
- "Invalid Persisted Entity Reference: {$match}.
+ "Invalid Persisted Entity Reference: {$match}.
Test persisted entity references must follow {$delimiter}entityStepKey.field{$delimiter} format."
);
}
@@ -1480,7 +1462,7 @@ private function resolveStepKeyReferences($input, $actionGroupOrigin, $matchAll
// only replace when whole word matches exactly
// e.g. testVar => $testVar but not $testVar2
if (strpos($output, $stepKeyVarRef) !== false) {
- $output = preg_replace('/\B\\' .$stepKeyVarRef. '\b/', $stepKeyVarRef . $testInvocationKey, $output);
+ $output = preg_replace('/\B\\' . $stepKeyVarRef . '\b/', $stepKeyVarRef . $testInvocationKey, $output);
}
if (strpos($output, $persistedVarRef) !== false) {
@@ -1515,8 +1497,8 @@ private function wrapFunctionArgsWithQuotes($functionRegex, $input)
foreach ($allArguments as $argument) {
$argument = trim($argument);
- if ($argument[0] == "[") {
- $replacement = "[" . $this->addUniquenessToParamArray($argument) . "]";
+ if ($argument[0] == self::ARRAY_WRAP_OPEN) {
+ $replacement = $this->wrapParameterArray($this->addUniquenessToParamArray($argument));
} elseif (is_numeric($argument)) {
$replacement = $argument;
} else {
@@ -1685,8 +1667,9 @@ private function processPressKey($input)
preg_match_all('/[\[][^\]]*?[\]]/', $input, $paramInput);
if (!empty($paramInput)) {
foreach ($paramInput[0] as $param) {
- $arrayResult[self::PRESSKEY_ARRAY_ANCHOR_KEY . $count] =
- '[' . trim($this->addUniquenessToParamArray($param)) . ']';
+ $arrayResult[self::PRESSKEY_ARRAY_ANCHOR_KEY . $count] = $this->wrapParameterArray(
+ trim($this->addUniquenessToParamArray($param))
+ );
$input = str_replace($param, self::PRESSKEY_ARRAY_ANCHOR_KEY . $count, $input);
$count++;
}
@@ -1780,13 +1763,8 @@ private function stripWrappedQuotes($input)
if (empty($input)) {
return '';
}
- if (substr($input, 0, 1) === '"') {
- $input = substr($input, 1);
- }
- if (substr($input, -1, 1) === '"') {
- $input = substr($input, 0, -1);
- }
- return $input;
+
+ return trim($input, '"');
}
/**
@@ -1800,15 +1778,12 @@ private function addDollarSign($input)
return sprintf("$%s", ltrim($this->stripQuotes($input), '$'));
}
- // @codingStandardsIgnoreStart
-
/**
* Wrap parameters into a function call.
*
- * @param string $actor
+ * @param string $actor
* @param actionObject $action
- * @param string $scope
- * @param array ...$args
+ * @param array ...$args
* @return string
* @throws \Exception
*/
@@ -1821,7 +1796,7 @@ private function wrapFunctionCall($actor, $action, ...$args)
continue;
}
if ($args[$i] === "") {
- $args[$i] = '"' . $args[$i] . '"';
+ $args[$i] = '""';
}
}
if (!is_array($args)) {
@@ -1829,7 +1804,7 @@ private function wrapFunctionCall($actor, $action, ...$args)
}
$args = $this->resolveAllRuntimeReferences($args);
$args = $this->resolveTestVariable($args, $action->getActionOrigin());
- $output .= implode(", ", array_filter($args, function($value) { return $value !== null; })) . ");";
+ $output .= implode(", ", array_filter($args, $this->filterNullCallback())) . ");";
return $output;
}
@@ -1839,8 +1814,7 @@ private function wrapFunctionCall($actor, $action, ...$args)
* @param string $returnVariable
* @param string $actor
* @param string $action
- * @param string $scope
- * @param array ...$args
+ * @param array ...$args
* @return string
* @throws \Exception
*/
@@ -1853,7 +1827,7 @@ private function wrapFunctionCallWithReturnValue($returnVariable, $actor, $actio
continue;
}
if ($args[$i] === "") {
- $args[$i] = '"' . $args[$i] . '"';
+ $args[$i] = '""';
}
}
if (!is_array($args)) {
@@ -1861,14 +1835,25 @@ private function wrapFunctionCallWithReturnValue($returnVariable, $actor, $actio
}
$args = $this->resolveAllRuntimeReferences($args);
$args = $this->resolveTestVariable($args, $action->getActionOrigin());
- $output .= implode(", ", array_filter($args, function($value) { return $value !== null; })) . ");";
+ $output .= implode(", ", array_filter($args, $this->filterNullCallback())) . ");";
return $output;
}
- // @codingStandardsIgnoreEnd
+ /**
+ * Closure returned is used as a callable for array_filter to remove null values from array
+ *
+ * @return callable
+ */
+ private function filterNullCallback()
+ {
+ return function ($value) {
+ return $value !== null;
+ };
+ }
/**
* Resolves {{_ENV.variable}} into getenv("variable") for test-runtime ENV referencing.
+ *
* @param array $args
* @param string $regex
* @param string $func
@@ -1928,43 +1913,79 @@ private function resolveAllRuntimeReferences($args)
*/
private function validateParameterArray($paramArray)
{
- if (substr($paramArray, 0, 1) != "[" || substr($paramArray, strlen($paramArray) - 1, 1) != "]") {
- throw new TestReferenceException("parameterArray must begin with `[` and end with `]");
+ if (!$this->isWrappedArray($paramArray)) {
+ throw new TestReferenceException(sprintf(
+ "parameterArray must begin with `%s` and end with `%s`",
+ self::ARRAY_WRAP_OPEN,
+ self::ARRAY_WRAP_CLOSE
+ ));
}
}
+ /**
+ * Verifies whether we have correctly wrapped array syntax
+ *
+ * @param string $paramArray
+ * @return boolean
+ */
+ private function isWrappedArray(string $paramArray)
+ {
+ return 0 === strpos($paramArray, self::ARRAY_WRAP_OPEN)
+ && substr($paramArray, -1) === self::ARRAY_WRAP_CLOSE;
+ }
+
/**
* Resolve value based on type.
*
- * @param string $value
- * @param string $type
- * @return string
+ * @param string|null $value
+ * @param string|null $type
+ * @return string|null
* @throws TestReferenceException
- * @SuppressWarnings(PHPMD.CyclomaticComplexity)
*/
- private function resolveValueByType($value, $type)
+ private function resolveValueByType($value = null, $type = null)
{
- //TODO: Refactor to deal with PHPMD.CyclomaticComplexity, and remove @SuppressWarnings
if (null === $value) {
return null;
}
+
if (null === $type) {
$type = 'const';
}
- if ($type == "string") {
- return $this->addUniquenessFunctionCall($value);
- } elseif ($type == "bool") {
- return $this->toBoolean($value) ? "true" : "false";
- } elseif ($type == "int" || $type == "float") {
- return $this->toNumber($value);
- } elseif ($type == "array") {
- $this->validateParameterArray($value);
- return "[" . $this->addUniquenessToParamArray($value) . "]";
- } elseif ($type == "variable") {
- return $this->addDollarSign($value);
- } else {
- return $value;
+
+ switch ($type) {
+ case 'string':
+ return $this->addUniquenessFunctionCall($value);
+ case 'bool':
+ return $this->toBoolean($value) ? "true" : "false";
+ case 'int':
+ case 'float':
+ return $this->toNumber($value);
+ case 'array':
+ $this->validateParameterArray($value);
+ return $this->wrapParameterArray($this->addUniquenessToParamArray($value));
+ case 'variable':
+ return $this->addDollarSign($value);
}
+
+ return $value;
+ }
+
+ /**
+ * Determines correct scope based on parameter
+ *
+ * @param string $generationScope
+ * @return string
+ */
+ private function getObjectScope(string $generationScope): string
+ {
+ switch ($generationScope) {
+ case TestGenerator::SUITE_SCOPE:
+ return PersistedObjectHandler::SUITE_SCOPE;
+ case TestGenerator::HOOK_SCOPE:
+ return PersistedObjectHandler::HOOK_SCOPE;
+ }
+
+ return PersistedObjectHandler::TEST_SCOPE;
}
/**
@@ -1987,11 +2008,11 @@ private function toBoolean($inStr)
private function toNumber($inStr)
{
$outStr = $this->stripQuotes($inStr);
- if (strpos($outStr, localeconv()['decimal_point']) === false) {
- return intval($outStr);
- } else {
+ if ($this->hasDecimalPoint($outStr)) {
return floatval($outStr);
}
+
+ return intval($outStr);
}
/**
@@ -2078,9 +2099,29 @@ private function printRuleErrorToConsole($key, $tagName, $attributes)
if (empty($tagName) || empty($attributes)) {
return;
}
- $message = 'On step with stepKey "' . $key . '", only one of the attributes: "';
- $message .= implode('", "', $attributes);
- $message .= '" can be use for action "' . $tagName . "\".\n";
- print $message;
+
+ printf(self::RULE_ERROR, $key, implode('", "', $attributes), $tagName);
+ }
+
+ /**
+ * Wraps parameters array with opening and closing symbol.
+ *
+ * @param string $value
+ * @return string
+ */
+ private function wrapParameterArray(string $value): string
+ {
+ return sprintf('%s%s%s', self::ARRAY_WRAP_OPEN, $value, self::ARRAY_WRAP_CLOSE);
+ }
+
+ /**
+ * Determines whether string provided contains decimal point characteristic for current locale
+ *
+ * @param string $outStr
+ * @return boolean
+ */
+ private function hasDecimalPoint(string $outStr)
+ {
+ return strpos($outStr, localeconv()['decimal_point']) === false;
}
}