diff --git a/composer.json b/composer.json index b33567303..e7ee72ff1 100755 --- a/composer.json +++ b/composer.json @@ -13,6 +13,7 @@ "ext-curl": "*", "allure-framework/allure-codeception": "~1.3.0", "codeception/codeception": "~2.3.4 || ~2.4.0 ", + "composer/composer": "^1.6", "consolidation/robo": "^1.0.0", "csharpru/vault-php": "~3.5.3", "csharpru/vault-php-guzzle6-transport": "^2.0", diff --git a/composer.lock b/composer.lock index dd7d6ad3f..28cbd1d4e 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "01cbd9e237e76de7070a3c0de4ee8b9f", + "content-hash": "79e0a6006597df1c5511869876dd7777", "packages": [ { "name": "allure-framework/allure-codeception", @@ -250,7 +250,7 @@ { "name": "Tobias Nyholm", "email": "tobias.nyholm@gmail.com", - "homepage": "https://github.com/Nyholm" + "homepage": "https://github.com/nyholm" } ], "description": "Library of all the php-cache adapters", @@ -388,6 +388,308 @@ "description": "Flexible Stub wrapper for PHPUnit's Mock Builder", "time": "2018-05-17T09:31:08+00:00" }, + { + "name": "composer/ca-bundle", + "version": "1.2.4", + "source": { + "type": "git", + "url": "https://github.com/composer/ca-bundle.git", + "reference": "10bb96592168a0f8e8f6dcde3532d9fa50b0b527" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/composer/ca-bundle/zipball/10bb96592168a0f8e8f6dcde3532d9fa50b0b527", + "reference": "10bb96592168a0f8e8f6dcde3532d9fa50b0b527", + "shasum": "" + }, + "require": { + "ext-openssl": "*", + "ext-pcre": "*", + "php": "^5.3.2 || ^7.0 || ^8.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.8.35 || ^5.7 || 6.5 - 8", + "psr/log": "^1.0", + "symfony/process": "^2.5 || ^3.0 || ^4.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Composer\\CaBundle\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "http://seld.be" + } + ], + "description": "Lets you find a path to the system CA bundle, and includes a fallback to the Mozilla CA bundle.", + "keywords": [ + "cabundle", + "cacert", + "certificate", + "ssl", + "tls" + ], + "time": "2019-08-30T08:44:50+00:00" + }, + { + "name": "composer/composer", + "version": "1.9.0", + "source": { + "type": "git", + "url": "https://github.com/composer/composer.git", + "reference": "314aa57fdcfc942065996f59fb73a8b3f74f3fa5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/composer/composer/zipball/314aa57fdcfc942065996f59fb73a8b3f74f3fa5", + "reference": "314aa57fdcfc942065996f59fb73a8b3f74f3fa5", + "shasum": "" + }, + "require": { + "composer/ca-bundle": "^1.0", + "composer/semver": "^1.0", + "composer/spdx-licenses": "^1.2", + "composer/xdebug-handler": "^1.1", + "justinrainbow/json-schema": "^3.0 || ^4.0 || ^5.0", + "php": "^5.3.2 || ^7.0", + "psr/log": "^1.0", + "seld/jsonlint": "^1.4", + "seld/phar-utils": "^1.0", + "symfony/console": "^2.7 || ^3.0 || ^4.0", + "symfony/filesystem": "^2.7 || ^3.0 || ^4.0", + "symfony/finder": "^2.7 || ^3.0 || ^4.0", + "symfony/process": "^2.7 || ^3.0 || ^4.0" + }, + "conflict": { + "symfony/console": "2.8.38" + }, + "require-dev": { + "phpunit/phpunit": "^4.8.35 || ^5.7", + "phpunit/phpunit-mock-objects": "^2.3 || ^3.0" + }, + "suggest": { + "ext-openssl": "Enabling the openssl extension allows you to access https URLs for repositories and packages", + "ext-zip": "Enabling the zip extension allows you to unzip archives", + "ext-zlib": "Allow gzip compression of HTTP requests" + }, + "bin": [ + "bin/composer" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.9-dev" + } + }, + "autoload": { + "psr-4": { + "Composer\\": "src/Composer" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nils Adermann", + "email": "naderman@naderman.de", + "homepage": "http://www.naderman.de" + }, + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "http://seld.be" + } + ], + "description": "Composer helps you declare, manage and install dependencies of PHP projects. It ensures you have the right stack everywhere.", + "homepage": "https://getcomposer.org/", + "keywords": [ + "autoload", + "dependency", + "package" + ], + "time": "2019-08-02T18:55:33+00:00" + }, + { + "name": "composer/semver", + "version": "1.5.0", + "source": { + "type": "git", + "url": "https://github.com/composer/semver.git", + "reference": "46d9139568ccb8d9e7cdd4539cab7347568a5e2e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/composer/semver/zipball/46d9139568ccb8d9e7cdd4539cab7347568a5e2e", + "reference": "46d9139568ccb8d9e7cdd4539cab7347568a5e2e", + "shasum": "" + }, + "require": { + "php": "^5.3.2 || ^7.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.5 || ^5.0.5", + "phpunit/phpunit-mock-objects": "2.3.0 || ^3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Composer\\Semver\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nils Adermann", + "email": "naderman@naderman.de", + "homepage": "http://www.naderman.de" + }, + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "http://seld.be" + }, + { + "name": "Rob Bast", + "email": "rob.bast@gmail.com", + "homepage": "http://robbast.nl" + } + ], + "description": "Semver library that offers utilities, version constraint parsing and validation.", + "keywords": [ + "semantic", + "semver", + "validation", + "versioning" + ], + "time": "2019-03-19T17:25:45+00:00" + }, + { + "name": "composer/spdx-licenses", + "version": "1.5.2", + "source": { + "type": "git", + "url": "https://github.com/composer/spdx-licenses.git", + "reference": "7ac1e6aec371357df067f8a688c3d6974df68fa5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/composer/spdx-licenses/zipball/7ac1e6aec371357df067f8a688c3d6974df68fa5", + "reference": "7ac1e6aec371357df067f8a688c3d6974df68fa5", + "shasum": "" + }, + "require": { + "php": "^5.3.2 || ^7.0 || ^8.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.8.35 || ^5.7 || 6.5 - 7" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Composer\\Spdx\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nils Adermann", + "email": "naderman@naderman.de", + "homepage": "http://www.naderman.de" + }, + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "http://seld.be" + }, + { + "name": "Rob Bast", + "email": "rob.bast@gmail.com", + "homepage": "http://robbast.nl" + } + ], + "description": "SPDX licenses list and validation library.", + "keywords": [ + "license", + "spdx", + "validator" + ], + "time": "2019-07-29T10:31:59+00:00" + }, + { + "name": "composer/xdebug-handler", + "version": "1.3.3", + "source": { + "type": "git", + "url": "https://github.com/composer/xdebug-handler.git", + "reference": "46867cbf8ca9fb8d60c506895449eb799db1184f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/composer/xdebug-handler/zipball/46867cbf8ca9fb8d60c506895449eb799db1184f", + "reference": "46867cbf8ca9fb8d60c506895449eb799db1184f", + "shasum": "" + }, + "require": { + "php": "^5.3.2 || ^7.0", + "psr/log": "^1.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.5" + }, + "type": "library", + "autoload": { + "psr-4": { + "Composer\\XdebugHandler\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "John Stevenson", + "email": "john-stevenson@blueyonder.co.uk" + } + ], + "description": "Restarts a process without xdebug.", + "keywords": [ + "Xdebug", + "performance" + ], + "time": "2019-05-27T17:52:04+00:00" + }, { "name": "consolidation/annotated-command", "version": "2.9.1", @@ -1859,6 +2161,72 @@ ], "time": "2019-04-17T08:12:16+00:00" }, + { + "name": "justinrainbow/json-schema", + "version": "5.2.8", + "source": { + "type": "git", + "url": "https://github.com/justinrainbow/json-schema.git", + "reference": "dcb6e1006bb5fd1e392b4daa68932880f37550d4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/justinrainbow/json-schema/zipball/dcb6e1006bb5fd1e392b4daa68932880f37550d4", + "reference": "dcb6e1006bb5fd1e392b4daa68932880f37550d4", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "~2.2.20", + "json-schema/json-schema-test-suite": "1.2.0", + "phpunit/phpunit": "^4.8.35" + }, + "bin": [ + "bin/validate-json" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "JsonSchema\\": "src/JsonSchema/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Bruno Prieto Reis", + "email": "bruno.p.reis@gmail.com" + }, + { + "name": "Justin Rainbow", + "email": "justin.rainbow@gmail.com" + }, + { + "name": "Igor Wiedler", + "email": "igor@wiedler.ch" + }, + { + "name": "Robert Schönthal", + "email": "seroscho@googlemail.com" + } + ], + "description": "A library to validate a json schema.", + "homepage": "https://github.com/justinrainbow/json-schema", + "keywords": [ + "json", + "schema" + ], + "time": "2019-01-14T23:55:14+00:00" + }, { "name": "league/container", "version": "2.4.1", @@ -3960,6 +4328,99 @@ "homepage": "https://github.com/sebastianbergmann/version", "time": "2016-10-03T07:35:21+00:00" }, + { + "name": "seld/jsonlint", + "version": "1.7.1", + "source": { + "type": "git", + "url": "https://github.com/Seldaek/jsonlint.git", + "reference": "d15f59a67ff805a44c50ea0516d2341740f81a38" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Seldaek/jsonlint/zipball/d15f59a67ff805a44c50ea0516d2341740f81a38", + "reference": "d15f59a67ff805a44c50ea0516d2341740f81a38", + "shasum": "" + }, + "require": { + "php": "^5.3 || ^7.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.0" + }, + "bin": [ + "bin/jsonlint" + ], + "type": "library", + "autoload": { + "psr-4": { + "Seld\\JsonLint\\": "src/Seld/JsonLint/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "http://seld.be" + } + ], + "description": "JSON Linter", + "keywords": [ + "json", + "linter", + "parser", + "validator" + ], + "time": "2018-01-24T12:46:19+00:00" + }, + { + "name": "seld/phar-utils", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/Seldaek/phar-utils.git", + "reference": "7009b5139491975ef6486545a39f3e6dad5ac30a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Seldaek/phar-utils/zipball/7009b5139491975ef6486545a39f3e6dad5ac30a", + "reference": "7009b5139491975ef6486545a39f3e6dad5ac30a", + "shasum": "" + }, + "require": { + "php": ">=5.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Seld\\PharUtils\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be" + } + ], + "description": "PHAR file format utilities, for when PHP phars you up", + "keywords": [ + "phra" + ], + "time": "2015-10-13T18:44:15+00:00" + }, { "name": "symfony/browser-kit", "version": "v3.4.18", diff --git a/dev/tests/unit/Magento/FunctionalTestFramework/Suite/Handlers/SuiteObjectHandlerTest.php b/dev/tests/unit/Magento/FunctionalTestFramework/Suite/Handlers/SuiteObjectHandlerTest.php index 99125a9e3..5bf29507c 100644 --- a/dev/tests/unit/Magento/FunctionalTestFramework/Suite/Handlers/SuiteObjectHandlerTest.php +++ b/dev/tests/unit/Magento/FunctionalTestFramework/Suite/Handlers/SuiteObjectHandlerTest.php @@ -15,9 +15,16 @@ use Magento\FunctionalTestingFramework\Util\MagentoTestCase; use tests\unit\Util\SuiteDataArrayBuilder; use tests\unit\Util\TestDataArrayBuilder; +use tests\unit\Util\MockModuleResolverBuilder; class SuiteObjectHandlerTest extends MagentoTestCase { + public function setUp() + { + $resolverMock = new MockModuleResolverBuilder(); + $resolverMock->setup(); + } + /** * Tests basic parsing and accesors of suite object and suite object supporting classes */ diff --git a/dev/tests/unit/Magento/FunctionalTestFramework/Suite/SuiteGeneratorTest.php b/dev/tests/unit/Magento/FunctionalTestFramework/Suite/SuiteGeneratorTest.php index eb6298afc..29158b0f5 100644 --- a/dev/tests/unit/Magento/FunctionalTestFramework/Suite/SuiteGeneratorTest.php +++ b/dev/tests/unit/Magento/FunctionalTestFramework/Suite/SuiteGeneratorTest.php @@ -20,10 +20,10 @@ use tests\unit\Util\SuiteDataArrayBuilder; use tests\unit\Util\TestDataArrayBuilder; use tests\unit\Util\TestLoggingUtil; +use tests\unit\Util\MockModuleResolverBuilder; class SuiteGeneratorTest extends MagentoTestCase { - /** * Setup entry append and clear for Suite Generator */ @@ -42,6 +42,8 @@ public static function setUpBeforeClass() public function setUp() { TestLoggingUtil::getInstance()->setMockLoggingUtil(); + $resolverMock = new MockModuleResolverBuilder(); + $resolverMock->setup(); } /** diff --git a/dev/tests/unit/Magento/FunctionalTestFramework/Test/Handlers/TestObjectHandlerTest.php b/dev/tests/unit/Magento/FunctionalTestFramework/Test/Handlers/TestObjectHandlerTest.php index a94c41ae4..dd0dddc1c 100644 --- a/dev/tests/unit/Magento/FunctionalTestFramework/Test/Handlers/TestObjectHandlerTest.php +++ b/dev/tests/unit/Magento/FunctionalTestFramework/Test/Handlers/TestObjectHandlerTest.php @@ -8,7 +8,6 @@ use AspectMock\Test as AspectMock; -use Go\Aop\Aspect; use Magento\FunctionalTestingFramework\ObjectManager; use Magento\FunctionalTestingFramework\ObjectManagerFactory; use Magento\FunctionalTestingFramework\Test\Handlers\TestObjectHandler; @@ -16,10 +15,10 @@ use Magento\FunctionalTestingFramework\Test\Objects\TestHookObject; use Magento\FunctionalTestingFramework\Test\Objects\TestObject; use Magento\FunctionalTestingFramework\Test\Parsers\TestDataParser; -use Magento\FunctionalTestingFramework\Test\Util\ActionObjectExtractor; use Magento\FunctionalTestingFramework\Test\Util\TestObjectExtractor; use Magento\FunctionalTestingFramework\Util\MagentoTestCase; use tests\unit\Util\TestDataArrayBuilder; +use tests\unit\Util\MockModuleResolverBuilder; class TestObjectHandlerTest extends MagentoTestCase { @@ -40,10 +39,13 @@ public function testGetTestObject() ->withTestActions() ->build(); + $resolverMock = new MockModuleResolverBuilder(); + $resolverMock->setup(); $this->setMockParserOutput(['tests' => $mockData]); // run object handler method $toh = TestObjectHandler::getInstance(); + $mockConfig = AspectMock::double(TestObjectHandler::class, ['initTestData' => false]); $actualTestObject = $toh->getObject($testDataArrayBuilder->testName); // perform asserts @@ -130,6 +132,8 @@ public function testGetTestsByGroup() ->withTestActions() ->build(); + $resolverMock = new MockModuleResolverBuilder(); + $resolverMock->setup(); $this->setMockParserOutput(['tests' => array_merge($includeTest, $excludeTest)]); // execute test method @@ -150,16 +154,20 @@ public function testGetTestsByGroup() public function testGetTestWithModuleName() { // set up Test Data - $moduleExpected = "SomeTestModule"; + $moduleExpected = "SomeModuleName"; + $moduleExpectedTest = $moduleExpected . "Test"; $filepath = DIRECTORY_SEPARATOR . - "user" . + "user" . DIRECTORY_SEPARATOR . "magento2ce" . DIRECTORY_SEPARATOR . "dev" . DIRECTORY_SEPARATOR . "tests" . DIRECTORY_SEPARATOR . "acceptance" . DIRECTORY_SEPARATOR . "tests" . DIRECTORY_SEPARATOR . - $moduleExpected . DIRECTORY_SEPARATOR . - "Tests" . DIRECTORY_SEPARATOR . + "functional" . DIRECTORY_SEPARATOR . + "Vendor" . DIRECTORY_SEPARATOR . + $moduleExpectedTest; + $file = $filepath . DIRECTORY_SEPARATOR . + "Test" . DIRECTORY_SEPARATOR . "text.xml"; // set up mock data $testDataArrayBuilder = new TestDataArrayBuilder(); @@ -169,8 +177,12 @@ public function testGetTestWithModuleName() ->withAfterHook() ->withBeforeHook() ->withTestActions() - ->withFileName($filepath) + ->withFileName($file) ->build(); + + $resolverMock = new MockModuleResolverBuilder(); + $resolverMock->setup(['Vendor_' . $moduleExpected => $filepath]); + $this->setMockParserOutput(['tests' => $mockData]); // Execute Test Method $toh = TestObjectHandler::getInstance(); @@ -197,6 +209,8 @@ public function testGetTestObjectWithInvalidExtends() ->withBeforeHook() ->withTestActions() ->build(); + $resolverMock = new MockModuleResolverBuilder(); + $resolverMock->setup(); $this->setMockParserOutput(['tests' => $testOne]); $toh = TestObjectHandler::getInstance(); @@ -233,6 +247,8 @@ public function testGetAllTestObjectsWithInvalidExtends() ->withTestActions() ->build(); + $resolverMock = new MockModuleResolverBuilder(); + $resolverMock->setup(); $this->setMockParserOutput(['tests' => array_merge($testOne, $testTwo)]); $toh = TestObjectHandler::getInstance(); @@ -260,4 +276,14 @@ private function setMockParserOutput($data) ->make(); // bypass the private constructor AspectMock::double(ObjectManagerFactory::class, ['getObjectManager' => $instance]); } + + /** + * After method functionality + * + * @return void + */ + public function tearDown() + { + AspectMock::clean(); + } } diff --git a/dev/tests/unit/Magento/FunctionalTestFramework/Test/Util/ObjectExtensionUtilTest.php b/dev/tests/unit/Magento/FunctionalTestFramework/Test/Util/ObjectExtensionUtilTest.php index ce184e564..d86185b83 100644 --- a/dev/tests/unit/Magento/FunctionalTestFramework/Test/Util/ObjectExtensionUtilTest.php +++ b/dev/tests/unit/Magento/FunctionalTestFramework/Test/Util/ObjectExtensionUtilTest.php @@ -20,6 +20,7 @@ use PHPUnit\Framework\TestCase; use tests\unit\Util\TestDataArrayBuilder; use tests\unit\Util\TestLoggingUtil; +use tests\unit\Util\MockModuleResolverBuilder; class ObjectExtensionUtilTest extends TestCase { @@ -30,6 +31,8 @@ class ObjectExtensionUtilTest extends TestCase public function setUp() { TestLoggingUtil::getInstance()->setMockLoggingUtil(); + $resolverMock = new MockModuleResolverBuilder(); + $resolverMock->setup(); } /** diff --git a/dev/tests/unit/Magento/FunctionalTestFramework/Util/ModulePathExtractorTest.php b/dev/tests/unit/Magento/FunctionalTestFramework/Util/ModulePathExtractorTest.php index 5efa6384b..05bc99b89 100644 --- a/dev/tests/unit/Magento/FunctionalTestFramework/Util/ModulePathExtractorTest.php +++ b/dev/tests/unit/Magento/FunctionalTestFramework/Util/ModulePathExtractorTest.php @@ -7,103 +7,128 @@ namespace tests\unit\Magento\FunctionalTestFramework\Test\Util; use Magento\FunctionalTestingFramework\Util\ModulePathExtractor; -use PHPUnit\Framework\TestCase; +use Magento\FunctionalTestingFramework\Util\MagentoTestCase; +use tests\unit\Util\MockModuleResolverBuilder; -class ModulePathExtractorTest extends TestCase +class ModulePathExtractorTest extends MagentoTestCase { - const EXTENSION_PATH = "app" - . DIRECTORY_SEPARATOR - . "code" - . DIRECTORY_SEPARATOR - . "TestExtension" - . DIRECTORY_SEPARATOR - . "[Analytics]" - . DIRECTORY_SEPARATOR - . "Test" - . DIRECTORY_SEPARATOR - . "Mftf" - . DIRECTORY_SEPARATOR - . "Test" - . DIRECTORY_SEPARATOR - . "SomeText.xml"; - - const MAGENTO_PATH = "dev" - . DIRECTORY_SEPARATOR - . "tests" - . DIRECTORY_SEPARATOR - . "acceptance" - . DIRECTORY_SEPARATOR - . "tests" - . DIRECTORY_SEPARATOR - . "functional" - . DIRECTORY_SEPARATOR - . "Magento" - . DIRECTORY_SEPARATOR - . "FunctionalTest" - . DIRECTORY_SEPARATOR - . "[Analytics]" - . DIRECTORY_SEPARATOR - . "Test" - . DIRECTORY_SEPARATOR - . "SomeText.xml"; + /** + * Mock test module paths + * + * @var array + */ + private $mockTestModulePaths = [ + 'Magento_ModuleA' => '/base/path/app/code/Magento/ModuleA/Test/Mftf', + 'VendorB_ModuleB' => '/base/path/app/code/VendorB/ModuleB/Test/Mftf', + 'Magento_ModuleC' => '/base/path/dev/tests/acceptance/tests/functional/Magento/ModuleCTest', + 'VendorD_ModuleD' => '/base/path/dev/tests/acceptance/tests/functional/VendorD/ModuleDTest', + 'SomeModuleE' => '/base/path/dev/tests/acceptance/tests/functional/FunctionalTest/SomeModuleE', + 'Magento_ModuleF' => '/base/path/vendor/magento/module-modulef/Test/Mftf', + 'VendorG_ModuleG' => '/base/path/vendor/vendorg/module-moduleg-test', + ]; + + /** + * Validate module for app/code path + * + * @throws \Exception + */ + public function testGetModuleAppCode() + { + $mockPath = '/base/path/app/code/Magento/ModuleA/Test/Mftf/Test/SomeTest.xml'; + + $resolverMock = new MockModuleResolverBuilder(); + $resolverMock->setup($this->mockTestModulePaths); + $extractor = new ModulePathExtractor(); + $this->assertEquals('ModuleA', $extractor->extractModuleName($mockPath)); + } /** - * Validate correct module is returned for dev/tests path + * Validate vendor for app/code path + * * @throws \Exception */ - public function testGetMagentoModule() + public function testGetVendorAppCode() { - $modulePathExtractor = new ModulePathExtractor(); - $this->assertEquals( - '[Analytics]', - $modulePathExtractor->extractModuleName( - self::MAGENTO_PATH - ) - ); + $mockPath = '/base/path/app/code/VendorB/ModuleB/Test/Mftf/Test/SomeTest.xml'; + + $resolverMock = new MockModuleResolverBuilder(); + $resolverMock->setup($this->mockTestModulePaths); + $extractor = new ModulePathExtractor(); + $this->assertEquals('VendorB', $extractor->getExtensionPath($mockPath)); } /** - * Validate correct module is returned for extension path + * Validate module for dev/tests path + * * @throws \Exception */ - public function testGetExtensionModule() + public function testGetModuleDevTests() { - $modulePathExtractor = new ModulePathExtractor(); - $this->assertEquals( - '[Analytics]', - $modulePathExtractor->extractModuleName( - self::EXTENSION_PATH - ) - ); + $mockPath = '/base/path/dev/tests/acceptance/tests/functional/Magento/ModuleCTest/Test/SomeTest.xml'; + + $resolverMock = new MockModuleResolverBuilder(); + $resolverMock->setup($this->mockTestModulePaths); + $extractor = new ModulePathExtractor(); + $this->assertEquals('ModuleC', $extractor->extractModuleName($mockPath)); } /** - * Validate Magento is returned for dev/tests/acceptance + * Validate vendor for dev/tests path + * * @throws \Exception */ - public function testMagentoModulePath() + public function testGetVendorDevTests() { - $modulePathExtractor = new ModulePathExtractor(); - $this->assertEquals( - 'Magento', - $modulePathExtractor->getExtensionPath( - self::MAGENTO_PATH - ) - ); + $mockPath = '/base/path/dev/tests/acceptance/tests/functional/VendorD/ModuleDTest/Test/SomeTest.xml'; + + $resolverMock = new MockModuleResolverBuilder(); + $resolverMock->setup($this->mockTestModulePaths); + $extractor = new ModulePathExtractor(); + $this->assertEquals('VendorD', $extractor->getExtensionPath($mockPath)); } /** - * Validate correct extension path is returned + * Validate module with no _ + * * @throws \Exception */ - public function testExtensionModulePath() + public function testGetModule() { - $modulePathExtractor = new ModulePathExtractor(); - $this->assertEquals( - 'TestExtension', - $modulePathExtractor->getExtensionPath( - self::EXTENSION_PATH - ) - ); + $mockPath = '/base/path/dev/tests/acceptance/tests/functional/FunctionalTest/SomeModuleE/Test/SomeTest.xml'; + + $resolverMock = new MockModuleResolverBuilder(); + $resolverMock->setup($this->mockTestModulePaths); + $extractor = new ModulePathExtractor(); + $this->assertEquals('NO MODULE DETECTED', $extractor->extractModuleName($mockPath)); + } + + /** + * Validate module for vendor/tests path + * + * @throws \Exception + */ + public function testGetModuleVendorDir() + { + $mockPath = '/base/path/vendor/magento/module-modulef/Test/Mftf/Test/SomeTest.xml'; + + $resolverMock = new MockModuleResolverBuilder(); + $resolverMock->setup($this->mockTestModulePaths); + $extractor = new ModulePathExtractor(); + $this->assertEquals('ModuleF', $extractor->extractModuleName($mockPath)); + } + + /** + * Validate vendor for vendor path + * + * @throws \Exception + */ + public function testGetVendorVendorDir() + { + $mockPath = '/base/path/vendor/vendorg/module-moduleg-test/Test/SomeTest.xml'; + + $resolverMock = new MockModuleResolverBuilder(); + $resolverMock->setup($this->mockTestModulePaths); + $extractor = new ModulePathExtractor(); + $this->assertEquals('VendorG', $extractor->getExtensionPath($mockPath)); } } diff --git a/dev/tests/unit/Magento/FunctionalTestFramework/Util/ModuleResolverTest.php b/dev/tests/unit/Magento/FunctionalTestFramework/Util/ModuleResolverTest.php index 248704adc..1d4b77740 100644 --- a/dev/tests/unit/Magento/FunctionalTestFramework/Util/ModuleResolverTest.php +++ b/dev/tests/unit/Magento/FunctionalTestFramework/Util/ModuleResolverTest.php @@ -56,21 +56,29 @@ public function testGetModulePathsAlreadySet() */ public function testGetModulePathsAggregate() { + $this->mockForceGenerate(false); $this->setMockResolverClass( false, null, null, null, - ["Magento_example" => "example" . DIRECTORY_SEPARATOR . "paths"] + [ + 'some' . DIRECTORY_SEPARATOR . 'path' . DIRECTORY_SEPARATOR . 'example' => ['example'], + 'other' . DIRECTORY_SEPARATOR . 'path' . DIRECTORY_SEPARATOR . 'sample' => ['sample'], + ], + null, + [ + 'Magento_example' => 'some' . DIRECTORY_SEPARATOR . 'path' . DIRECTORY_SEPARATOR . 'example', + 'Magento_sample' => 'other' . DIRECTORY_SEPARATOR . 'path' . DIRECTORY_SEPARATOR . 'sample', + ] ); $resolver = ModuleResolver::getInstance(); - $this->setMockResolverProperties($resolver, null, [0 => "Magento_example"]); + $this->setMockResolverProperties($resolver, null, [0 => 'Magento_example', 1 => 'Magento_sample']); $this->assertEquals( [ - "example" . DIRECTORY_SEPARATOR . "paths", - "example" . DIRECTORY_SEPARATOR . "paths", - "example" . DIRECTORY_SEPARATOR . "paths" + 'some' . DIRECTORY_SEPARATOR . 'path' . DIRECTORY_SEPARATOR . 'example', + 'other' . DIRECTORY_SEPARATOR . 'path' . DIRECTORY_SEPARATOR . 'sample' ], $resolver->getModulesPath() ); @@ -82,22 +90,36 @@ public function testGetModulePathsAggregate() */ public function testGetModulePathsLocations() { + // clear test object handler value to inject parsed content + $property = new \ReflectionProperty(ModuleResolver::class, 'instance'); + $property->setAccessible(true); + $property->setValue(null); + $this->mockForceGenerate(false); $mockResolver = $this->setMockResolverClass( true, - [0 => "example"], + [], + null, null, + [], + [], + [], + [], + [], + [], + [], null, - ["example" => "example" . DIRECTORY_SEPARATOR . "paths"] + function ($arg) { + return $arg; + }, + function ($arg) { + return $arg; + } ); $resolver = ModuleResolver::getInstance(); $this->setMockResolverProperties($resolver, null, null); $this->assertEquals( - [ - "example" . DIRECTORY_SEPARATOR . "paths", - "example" . DIRECTORY_SEPARATOR . "paths", - "example" . DIRECTORY_SEPARATOR . "paths" - ], + [], $resolver->getModulesPath() ); @@ -119,6 +141,20 @@ public function testGetModulePathsLocations() 'Test' . DIRECTORY_SEPARATOR .'Mftf' ] ); + $mockResolver->verifyInvoked( + 'globRelevantPaths', + [ + $magentoBaseCodePath + . DIRECTORY_SEPARATOR . "dev" + . DIRECTORY_SEPARATOR . "tests" + . DIRECTORY_SEPARATOR . "acceptance" + . DIRECTORY_SEPARATOR . "tests" + . DIRECTORY_SEPARATOR . "functional" + . DIRECTORY_SEPARATOR . "Magento" + . DIRECTORY_SEPARATOR . "FunctionalTest" + , '' + ] + ); } /** @@ -127,14 +163,21 @@ public function testGetModulePathsLocations() */ public function testGetCustomModulePath() { - $this->setMockResolverClass(false, ["Magento_TestModule"], null, null, [], ['otherPath']); + $this->setMockResolverClass( + false, + null, + null, + null, + [], + ['Magento_Module' => 'otherPath'] + ); $resolver = ModuleResolver::getInstance(); $this->setMockResolverProperties($resolver, null, null, null); $this->assertEquals(['otherPath'], $resolver->getModulesPath()); TestLoggingUtil::getInstance()->validateMockLogStatement( 'info', 'including custom module', - ['module' => 'otherPath'] + ['Magento_Module' => 'otherPath'] ); } @@ -149,22 +192,36 @@ public function testGetModulePathsBlacklist() null, null, null, - function ($arg1, $arg2) { - if ($arg2 === "") { - $mockValue = ["somePath" => "somePath"]; - } else { - $mockValue = ["lastPath" => "lastPath"]; - } - return $mockValue; + [], + [], + [], + [], + [], + [], + [], + [ + "vendor" => "vendor", + "appCode" => "appCode", + "devTests" => "devTests", + "thisPath" => "thisPath" + ], + function ($arg) { + return $arg; + }, + function ($arg) { + return $arg; } ); $resolver = ModuleResolver::getInstance(); - $this->setMockResolverProperties($resolver, null, null, ["somePath"]); - $this->assertEquals(["lastPath", "lastPath"], $resolver->getModulesPath()); + $this->setMockResolverProperties($resolver, null, null, ["devTests" => "devTests"]); + $this->assertEquals( + ["vendor", "appCode", "thisPath"], + $resolver->getModulesPath() + ); TestLoggingUtil::getInstance()->validateMockLogStatement( 'info', 'excluding module', - ['module' => 'somePath'] + ['module' => 'devTests'] ); } @@ -252,6 +309,14 @@ public function testGetAdminTokenWithBadResponse() * @param string[] $mockGlob * @param string[] $mockRelativePaths * @param string[] $mockCustomModules + * @param string[] $mockGetRegisteredModuleList + * @param string[] $mockAggregateTestModulePathsFromComposerJson + * @param string[] $mockAggregateTestModulePathsFromComposerInstaller + * @param string[] $mockGetComposerJsonTestModulePaths + * @param string[] $mockGetComposerInstalledTestModulePaths + * @param string[] $mockAggregateTestModulePaths + * @param string[] $mockNormalizeModuleNames + * @param string[] $mockFlipAndFilterModulePathsArray * @throws \Exception * @return Verifier ModuleResolver double */ @@ -261,7 +326,15 @@ private function setMockResolverClass( $mockCustomMethods = null, $mockGlob = null, $mockRelativePaths = null, - $mockCustomModules = null + $mockCustomModules = null, + $mockGetRegisteredModuleList = null, + $mockAggregateTestModulePathsFromComposerJson = [], + $mockAggregateTestModulePathsFromComposerInstaller = [], + $mockGetComposerJsonTestModulePaths = [], + $mockGetComposerInstalledTestModulePaths = [], + $mockAggregateTestModulePaths = null, + $mockNormalizeModuleNames = null, + $mockFlipAndFilterModulePathsArray = null ) { $property = new \ReflectionProperty(ModuleResolver::class, 'instance'); $property->setAccessible(true); @@ -286,7 +359,23 @@ private function setMockResolverClass( if (isset($mockCustomModules)) { $mockMethods['getCustomModulePaths'] = $mockCustomModules; } -// $mockMethods['printMagentoVersionInfo'] = null; + if (isset($mockGetRegisteredModuleList)) { + $mockMethods['getRegisteredModuleList'] = $mockGetRegisteredModuleList; + } + $mockMethods['aggregateTestModulePathsFromComposerJson'] = $mockAggregateTestModulePathsFromComposerJson ?? []; + $mockMethods['aggregateTestModulePathsFromComposerInstaller'] = + $mockAggregateTestModulePathsFromComposerInstaller ?? []; + $mockMethods['getComposerJsonTestModulePaths'] = $mockGetComposerJsonTestModulePaths ?? []; + $mockMethods['getComposerInstalledTestModulePaths'] = $mockGetComposerInstalledTestModulePaths ?? []; + if (isset($mockAggregateTestModulePaths)) { + $mockMethods['aggregateTestModulePaths'] = $mockAggregateTestModulePaths; + } + if (isset($mockNormalizeModuleNames)) { + $mockMethods['normalizeModuleNames'] = $mockNormalizeModuleNames; + } + if (isset($mockFlipAndFilterModulePathsArray)) { + $mockMethods['flipAndFilterModulePathsArray'] = $mockFlipAndFilterModulePathsArray; + } $mockResolver = AspectMock::double( ModuleResolver::class, diff --git a/dev/tests/unit/Util/MockModuleResolverBuilder.php b/dev/tests/unit/Util/MockModuleResolverBuilder.php new file mode 100644 index 000000000..0e1b6fc31 --- /dev/null +++ b/dev/tests/unit/Util/MockModuleResolverBuilder.php @@ -0,0 +1,61 @@ + '/base/path/some/other/path/Magento/Module']; + + /** + * Mock ModuleResolver builder + * + * @param array $paths + * @return void + * @throws \Exception + */ + public function setup($paths = null) + { + if (empty($paths)) { + $paths = $this->defaultPaths; + } + + $mockConfig = AspectMock::double(MftfApplicationConfig::class, ['forceGenerateEnabled' => false]); + $instance = AspectMock::double(ObjectManager::class, ['create' => $mockConfig->make(), 'get' => null])->make(); + AspectMock::double(ObjectManagerFactory::class, ['getObjectManager' => $instance]); + + $property = new \ReflectionProperty(ModuleResolver::class, 'instance'); + $property->setAccessible(true); + $property->setValue(null); + + $mockResolver = AspectMock::double( + ModuleResolver::class, + [ + 'getAdminToken' => false, + 'globRelevantPaths' => [], + 'getEnabledModules' => [] + ] + ); + $instance = AspectMock::double(ObjectManager::class, ['create' => $mockResolver->make(), 'get' => null]) + ->make(); + AspectMock::double(ObjectManagerFactory::class, ['getObjectManager' => $instance]); + + $resolver = ModuleResolver::getInstance(); + $property = new \ReflectionProperty(ModuleResolver::class, 'enabledModuleNameAndPaths'); + $property->setAccessible(true); + $property->setValue($resolver, $paths); + } +} diff --git a/dev/tests/unit/Util/TestDataArrayBuilder.php b/dev/tests/unit/Util/TestDataArrayBuilder.php index aeeeae850..eb192e040 100644 --- a/dev/tests/unit/Util/TestDataArrayBuilder.php +++ b/dev/tests/unit/Util/TestDataArrayBuilder.php @@ -225,7 +225,7 @@ public function withFileName($filename = null) */ public function withTestReference($reference = null) { - if ($reference != null) { + if ($reference !== null) { $this->testReference = $reference; } diff --git a/dev/tests/verification/TestModule/Page/SamplePage.xml b/dev/tests/verification/TestModule/Page/SamplePage.xml index bf8f99615..59efded6e 100644 --- a/dev/tests/verification/TestModule/Page/SamplePage.xml +++ b/dev/tests/verification/TestModule/Page/SamplePage.xml @@ -8,25 +8,25 @@ - +
- +
- +
- +
- +
- +
- +
diff --git a/etc/config/.env.example b/etc/config/.env.example index f4aa90e51..f25cb3de7 100644 --- a/etc/config/.env.example +++ b/etc/config/.env.example @@ -43,7 +43,7 @@ BROWSER=chrome #DEFAULT_TIMEZONE=America/Los_Angeles #*** These properties impact the modules loaded into MFTF, you can point to your own full path, or a custom set of modules located with the core set -MODULE_WHITELIST=Magento_Framework,Magento_ConfigurableProductWishlist,Magento_ConfigurableProductCatalogSearch +MODULE_WHITELIST=Magento_Framework,ConfigurableProductWishlist,ConfigurableProductCatalogSearch #CUSTOM_MODULE_PATHS= #*** Bool property which allows the user to toggle debug output during test execution diff --git a/src/Magento/FunctionalTestingFramework/Composer/AbstractComposer.php b/src/Magento/FunctionalTestingFramework/Composer/AbstractComposer.php new file mode 100644 index 000000000..98f72d0c1 --- /dev/null +++ b/src/Magento/FunctionalTestingFramework/Composer/AbstractComposer.php @@ -0,0 +1,86 @@ +[^,\s]+_[^,\s]+)/'; + + /**#@+ + * Composer package array keys + */ + const PACKAGE_NAME = 'name'; + const PACKAGE_TYPE = 'type'; + const PACKAGE_VERSION = 'version'; + const PACKAGE_DESCRIPTION = 'description'; + const PACKAGE_INSTALLEDPATH = 'installedPath'; + const PACKAGE_REQUIRES = 'requires'; + const PACKAGE_DEVREQUIRES = 'devRequires'; + const PACKAGE_SUGGESTS = 'suggests'; + const PACKAGE_SUGGESTED_MAGENTO_MODULES = 'suggestedMagentoModules'; + /**#@-*/ + + /** + * @var \Composer\Composer + */ + protected $composer; + + /** + * @param string $composerFile + */ + public function __construct($composerFile) + { + $this->composer = \Composer\Factory::create(new BufferIO(), $composerFile); + } + + /** + * Get composer + * + * @return \Composer\Composer + */ + protected function getComposer() + { + return $this->composer; + } + + /** + * Parse input array and return all suggested magento module names, i.e. an example "suggest" in composer.json + * + * "suggest": { + * "magento/module-backend": "type: magento2-module, name: Magento_Backend, version: ~100.0.0", + * "magento/module-store": "type: magento2-module, name: Magento_Store, version: ~100.0.0" + * } + * + * @param array $suggests + * @return array + */ + protected function parseSuggestsForMagentoModuleNames($suggests) + { + $magentoModuleNames = []; + foreach ($suggests as $suggest) { + // Expecting pattern - type: magento2-module, name: Magento_Store, version: ~100.0.0 + preg_match(self::MODULE_NAME_IN_SUGGEST_REGEX, $suggest, $match); + if (isset($match[self::MODULE_NAME_IN_SUGGEST_REGEX_INDEX])) { + $magentoModuleNames[] = $match[self::MODULE_NAME_IN_SUGGEST_REGEX_INDEX]; + } + } + + return array_unique($magentoModuleNames); + } +} diff --git a/src/Magento/FunctionalTestingFramework/Composer/ComposerInstall.php b/src/Magento/FunctionalTestingFramework/Composer/ComposerInstall.php new file mode 100644 index 000000000..f10c0b418 --- /dev/null +++ b/src/Magento/FunctionalTestingFramework/Composer/ComposerInstall.php @@ -0,0 +1,102 @@ +isInstalledPackageOfType($packageName, self::TEST_MODULE_PACKAGE_TYPE); + } + + /** + * Determines if package is a magento package + * + * @param string $packageName + * @return boolean + */ + public function isMagentoPackage($packageName) + { + return $this->isInstalledPackageOfType($packageName, self::MAGENTO_MODULE_PACKAGE_TYPE); + } + + /** + * Determines if an installed package is of a certain type + * + * @param string $packageName + * @param string $packageType + * @return boolean + */ + public function isInstalledPackageOfType($packageName, $packageType) + { + /** @var CompletePackageInterface $package */ + foreach ($this->getLocker()->getLockedRepository()->getPackages() as $package) { + if (($package->getName() == $packageName) && ($package->getType() == $packageType)) { + return true; + } + } + return false; + } + + /** + * Collect all installed mftf test packages from composer lock + * + * @return array + */ + public function getInstalledTestPackages() + { + $packages = []; + /** @var CompletePackageInterface $package */ + foreach ($this->getLocker()->getLockedRepository()->getPackages() as $package) { + if ($package->getType() == self::TEST_MODULE_PACKAGE_TYPE) { + $packages[$package->getName()] = [ + self::PACKAGE_NAME => $package->getName(), + self::PACKAGE_TYPE => $package->getType(), + self::PACKAGE_VERSION => $package->getPrettyVersion(), + self::PACKAGE_DESCRIPTION => $package->getDescription(), + self::PACKAGE_SUGGESTS => $package->getSuggests(), + self::PACKAGE_REQUIRES => $package->getRequires(), + self::PACKAGE_DEVREQUIRES => $package->getDevRequires(), + self::PACKAGE_SUGGESTED_MAGENTO_MODULES => $this->parseSuggestsForMagentoModuleNames( + $package->getSuggests() + ), + self::PACKAGE_INSTALLEDPATH => $this->getComposer()->getInstallationManager() + ->getInstallPath($package) + ]; + } + } + return $packages; + } + + /** + * Load locker + * + * @return \Composer\Package\Locker + */ + private function getLocker() + { + if (!$this->locker) { + $this->locker = $this->getComposer()->getLocker(); + } + return $this->locker; + } +} diff --git a/src/Magento/FunctionalTestingFramework/Composer/ComposerPackage.php b/src/Magento/FunctionalTestingFramework/Composer/ComposerPackage.php new file mode 100644 index 000000000..0a6bfdca2 --- /dev/null +++ b/src/Magento/FunctionalTestingFramework/Composer/ComposerPackage.php @@ -0,0 +1,161 @@ +getRootPackage(); + return $package->getPrettyName(); + } + + /** + * Retrieve package type from composer json + * + * @return string + */ + public function getType() + { + /** @var \Composer\Package\CompletePackage $package */ + $package = $this->getRootPackage(); + return $package->getType(); + } + + /** + * Retrieve package version from composer json + * + * @return string + */ + public function getVersion() + { + /** @var \Composer\Package\CompletePackage $package */ + $package = $this->getRootPackage(); + return $package->getPrettyVersion(); + } + + /** + * Retrieve package description from composer json + * + * @return string + */ + public function getDescription() + { + /** @var \Composer\Package\CompletePackage $package */ + $package = $this->getRootPackage(); + return $package->getDescription(); + } + + /** + * Retrieve package require from composer json + * + * @return array + */ + public function getRequires() + { + /** @var \Composer\Package\CompletePackage $package */ + $package = $this->getRootPackage(); + return $package->getRequires(); + } + + /** + * Retrieve package dev require from composer json + * + * @return array + */ + public function getDevRequires() + { + /** @var \Composer\Package\CompletePackage $package */ + $package = $this->getRootPackage(); + return $package->getDevRequires(); + } + + /** + * Retrieve package suggest from composer json + * + * @return array + */ + public function getSuggests() + { + /** @var \Composer\Package\CompletePackage $package */ + $package = $this->getRootPackage(); + return $package->getSuggests(); + } + + /** + * Retrieve magento module names in package's suggest + * + * @return array + */ + public function getSuggestedMagentoModules() + { + return $this->parseSuggestsForMagentoModuleNames($this->getSuggests()); + } + + /** + * Determines if package is a mftf test package + * + * @return boolean + */ + public function isMftfTestPackage() + { + return ($this->getType() == self::TEST_MODULE_PACKAGE_TYPE) ? true : false; + } + + /** + * Retrieve packages require for given package name and version + * + * @param string $name + * @param string $version + * @return array + */ + public function getRequiresForPackage($name, $version) + { + /** @var \Composer\Package\CompletePackage $package */ + $package = $this->getComposer()->getRepositoryManager()->findPackage($name, $version); + return $package->getRequires(); + } + + /** + * Check if a package is required in composer json + * + * @param string $packageName + * @return boolean + */ + public function isPackageRequiredInComposerJson($packageName) + { + return (in_array($packageName, array_keys($this->getRequires())) + || in_array($packageName, array_keys($this->getDevRequires())) + ); + } + + /** + * Get root package + * + * @return \Composer\Package\RootPackageInterface + */ + public function getRootPackage() + { + if (!$this->rootPackage) { + $this->rootPackage = $this->getComposer()->getPackage(); + } + return $this->rootPackage; + } +} diff --git a/src/Magento/FunctionalTestingFramework/Page/Config/Dom.php b/src/Magento/FunctionalTestingFramework/Page/Config/Dom.php index bb6d08f58..989209461 100644 --- a/src/Magento/FunctionalTestingFramework/Page/Config/Dom.php +++ b/src/Magento/FunctionalTestingFramework/Page/Config/Dom.php @@ -87,9 +87,9 @@ public function initDom($xml, $filename = null) ); $pageNodes = $dom->getElementsByTagName('page'); $currentModule = - $this->modulePathExtractor->extractModuleName($filename) . - '_' . - $this->modulePathExtractor->getExtensionPath($filename); + $this->modulePathExtractor->getExtensionPath($filename) + . '_' + . $this->modulePathExtractor->extractModuleName($filename); foreach ($pageNodes as $pageNode) { $pageModule = $pageNode->getAttribute("module"); $pageName = $pageNode->getAttribute("name"); diff --git a/src/Magento/FunctionalTestingFramework/Util/ComposerModuleResolver.php b/src/Magento/FunctionalTestingFramework/Util/ComposerModuleResolver.php new file mode 100644 index 000000000..5152989e7 --- /dev/null +++ b/src/Magento/FunctionalTestingFramework/Util/ComposerModuleResolver.php @@ -0,0 +1,179 @@ +installedTestModules) { + return $this->installedTestModules; + } + + if (!file_exists($rootComposerFile) || basename($rootComposerFile, '.json') != 'composer') { + throw new TestFrameworkException("Invalid root composer json file: {$rootComposerFile}"); + } + + $this->installedTestModules = []; + $composer = new ComposerInstall($rootComposerFile); + + foreach ($composer->getInstalledTestPackages() as $packageName => $packageData) { + $suggestedModuleNames = $packageData[ComposerInstall::PACKAGE_SUGGESTED_MAGENTO_MODULES]; + $path = $packageData[ComposerInstall::PACKAGE_INSTALLEDPATH]; + $this->installedTestModules[$path] = $suggestedModuleNames; + } + return $this->installedTestModules; + } + + /** + * Get code paths by searching test module composer json file from input directories + * + * @param array $directories + * @return array + * @throws TestFrameworkException + */ + public function getTestModulesFromPaths($directories) + { + if (null !== $this->searchedTestModules) { + return $this->searchedTestModules; + } + + $this->searchedTestModules = []; + foreach ($directories as $directory) { + $this->searchedTestModules = array_merge_recursive( + $this->searchedTestModules, + $this->getTestModules($directory) + ); + } + return $this->searchedTestModules; + } + + /** + * Get code paths by searching test module composer json file from input directory + * + * @param string $directory + * @return array + * @throws TestFrameworkException + */ + private function getTestModules($directory) + { + $normalizedDir = realpath($directory); + if (!is_dir($normalizedDir)) { + throw new TestFrameworkException("Invalid directory: {$directory}"); + } + + // Find all composer json files under directory + $modules = []; + $fileList = $this->findComposerJsonFilesAtDepth($normalizedDir, 2); + foreach ($fileList as $file) { + // Parse composer json for test module name and path information + $composerInfo = new ComposerPackage($file); + if ($composerInfo->isMftfTestPackage()) { + $modulePath = str_replace( + DIRECTORY_SEPARATOR . 'composer.json', + '', + $file + ); + $suggestedMagentoModuleNames = $composerInfo->getSuggestedMagentoModules(); + if (array_key_exists($modulePath, $modules)) { + $modules[$modulePath] = array_merge($modules[$modulePath], $suggestedMagentoModuleNames); + } else { + $modules[$modulePath] = $suggestedMagentoModuleNames; + } + } + } + return $modules; + } + + /** + * Find absolute paths of all composer json files in a given directory + * + * @param string $directory + * @return array + */ + private function findAllComposerJsonFiles($directory) + { + $directory = realpath($directory); + $jsonPattern = DIRECTORY_SEPARATOR . "composer.json"; + $subDirectoryPattern = DIRECTORY_SEPARATOR . "*"; + + $jsonFileList = glob($directory . $jsonPattern); + if ($jsonFileList !== false && !empty($jsonFileList)) { + return $jsonFileList; + } else { + $jsonFileList = []; + foreach (glob($directory . $subDirectoryPattern, GLOB_ONLYDIR) as $dir) { + $jsonFileList = array_merge_recursive($jsonFileList, self::findAllComposerJsonFiles($dir)); + } + return $jsonFileList; + } + } + + /** + * Find absolute paths of all composer json files in a given directory at certain depths + * + * @param string $directory + * @param integer $depth + * @return array + */ + private function findComposerJsonFilesAtDepth($directory, $depth) + { + $directory = realpath($directory); + $jsonPattern = DIRECTORY_SEPARATOR . "composer.json"; + $subDirectoryPattern = DIRECTORY_SEPARATOR . "*"; + + $jsonFileList = []; + if ($depth > 0) { + foreach (glob($directory . $subDirectoryPattern, GLOB_ONLYDIR) as $dir) { + $jsonFileList = array_merge_recursive( + $jsonFileList, + self::findComposerJsonFilesAtDepth($dir, $depth-1) + ); + } + } elseif ($depth == 0) { + $jsonFileList = glob($directory . $jsonPattern); + if ($jsonFileList === false) { + $jsonFileList = []; + } + } + return $jsonFileList; + } +} diff --git a/src/Magento/FunctionalTestingFramework/Util/ModulePathExtractor.php b/src/Magento/FunctionalTestingFramework/Util/ModulePathExtractor.php index 33e559ea8..80b556f9a 100644 --- a/src/Magento/FunctionalTestingFramework/Util/ModulePathExtractor.php +++ b/src/Magento/FunctionalTestingFramework/Util/ModulePathExtractor.php @@ -11,41 +11,89 @@ */ class ModulePathExtractor { - const MAGENTO = 'Magento'; + const SPLIT_DELIMITER = '_'; + + /** + * Test module paths + * + * @var array + */ + private $testModulePaths = []; + + /** + * ModulePathExtractor constructor + */ + public function __construct() + { + $verbosePath = true; + if (empty($this->testModulePaths)) { + $this->testModulePaths = ModuleResolver::getInstance()->getModulesPath($verbosePath); + } + } /** * Extracts module name from the path given + * * @param string $path * @return string */ public function extractModuleName($path) { - if (empty($path)) { - return "NO MODULE DETECTED"; - } - $paths = explode(DIRECTORY_SEPARATOR, $path); - if (count($paths) < 3) { + $key = $this->extractKeyByPath($path); + if (empty($key)) { return "NO MODULE DETECTED"; - } elseif ($paths[count($paths)-3] == "Mftf") { - // app/code/Magento/[Analytics]/Test/Mftf/Test/SomeText.xml - return $paths[count($paths)-5]; } - // dev/tests/acceptance/tests/functional/Magento/FunctionalTest/[Analytics]/Test/SomeText.xml - return $paths[count($paths)-3]; + $parts = $this->splitKeyForParts($key); + return isset($parts[1]) ? $parts[1] : "NO MODULE DETECTED"; } /** - * Extracts the extension form the path, Magento for dev/tests/acceptance, [name] before module otherwise + * Extracts vendor name for module from the path given + * * @param string $path * @return string */ public function getExtensionPath($path) { - $paths = explode(DIRECTORY_SEPARATOR, $path); - if ($paths[count($paths)-3] == "Mftf") { - // app/code/[Magento]/Analytics/Test/Mftf/Test/SomeText.xml - return $paths[count($paths)-6]; + $key = $this->extractKeyByPath($path); + if (empty($key)) { + return "NO VENDOR DETECTED"; + } + $parts = $this->splitKeyForParts($key); + return isset($parts[0]) ? $parts[0] : "NO VENDOR DETECTED"; + } + + /** + * Split key by SPLIT_DELIMITER and return parts array + * + * @param string $key + * @return array + */ + private function splitKeyForParts($key) + { + $parts = explode(self::SPLIT_DELIMITER, $key); + return count($parts) == 2 ? $parts : []; + } + + /** + * Extract module name key by path + * + * @param string $path + * @return string + */ + private function extractKeyByPath($path) + { + $shortenedPath = dirname(dirname($path)); + // Ignore this path if we cannot go to parent directory two levels up + if (empty($shortenedPath) || $shortenedPath === '.') { + return ''; + } + + foreach ($this->testModulePaths as $key => $value) { + if ($value == $shortenedPath) { + return $key; + } } - return self::MAGENTO; + return ''; } } diff --git a/src/Magento/FunctionalTestingFramework/Util/ModuleResolver.php b/src/Magento/FunctionalTestingFramework/Util/ModuleResolver.php index 16e6037fa..c27496dd5 100644 --- a/src/Magento/FunctionalTestingFramework/Util/ModuleResolver.php +++ b/src/Magento/FunctionalTestingFramework/Util/ModuleResolver.php @@ -15,6 +15,7 @@ * Class ModuleResolver, resolve module path based on enabled modules of target Magento instance. * * @api + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ class ModuleResolver { @@ -38,6 +39,25 @@ class ModuleResolver */ const REGISTRAR_CLASS = "\Magento\Framework\Component\ComponentRegistrar"; + const TEST_MFTF_PATTERN = 'Test' . DIRECTORY_SEPARATOR . 'Mftf'; + const VENDOR = 'vendor'; + const APP_CODE = 'app' . DIRECTORY_SEPARATOR . "code"; + const DEV_TESTS = 'dev' + . DIRECTORY_SEPARATOR + . 'tests' + . DIRECTORY_SEPARATOR + . 'acceptance' + . DIRECTORY_SEPARATOR + . 'tests' + . DIRECTORY_SEPARATOR + . 'functional'; + const DEPRECATED_DEV_TESTS = DIRECTORY_SEPARATOR + . self:: DEV_TESTS + . DIRECTORY_SEPARATOR + . "Magento" + . DIRECTORY_SEPARATOR + . "FunctionalTest"; + /** * Enabled modules. * @@ -52,6 +72,13 @@ class ModuleResolver */ protected $enabledModulePaths = null; + /** + * Name and path for enabled modules + * + * @var array|null + */ + protected $enabledModuleNameAndPaths = null; + /** * Configuration instance. * @@ -110,6 +137,27 @@ class ModuleResolver 'SampleTests', 'SampleTemplates' ]; + /** + * Registered module list in magento system under test + * + * @var array + */ + private $registeredModuleList = []; + + /** + * Composer json based test module paths + * + * @var array + */ + private $composerJsonModulePaths = null; + + /** + * Composer installed test module paths + * + * @var array + */ + private $composerInstalledModulePaths = null; + /** * Get ModuleResolver instance. * @@ -138,6 +186,7 @@ private function __construct() * Return an array of enabled modules of target Magento instance. * * @return array + * @throws TestFrameworkException */ public function getEnabledModules() { @@ -179,50 +228,84 @@ public function getEnabledModules() return $this->enabledModules; } - /** - * Return an array of module whitelist that not exist in target Magento instance. - * - * @return array - */ - protected function getModuleWhitelist() - { - $moduleWhitelist = getenv(self::MODULE_WHITELIST); - - if (empty($moduleWhitelist)) { - return []; - } - return array_map('trim', explode(',', $moduleWhitelist)); - } - /** * Return the modules path based on which modules are enabled in the target Magento instance. * + * @param boolean $verbosePath * @return array */ - public function getModulesPath() + public function getModulesPath($verbosePath = false) { - if (isset($this->enabledModulePaths)) { + if (isset($this->enabledModulePaths) && !$verbosePath) { return $this->enabledModulePaths; } + if (isset($this->enabledModuleNameAndPaths) && $verbosePath) { + return $this->enabledModuleNameAndPaths; + } + + // Find test modules paths by searching patterns (Test/Mftf, etc) $allModulePaths = $this->aggregateTestModulePaths(); + // Find test modules paths by searching test composer.json files + $composerBasedModulePaths = $this->aggregateTestModulePathsFromComposerJson(); + + // Find test modules paths by querying composer installed packages + $composerBasedModulePaths = array_merge( + $composerBasedModulePaths, + $this->aggregateTestModulePathsFromComposerInstaller() + ); + + // Merge test module paths altogether + $allModulePaths = $this->mergeModulePaths($allModulePaths, $composerBasedModulePaths); + + // Normalize module names if we get registered module names from Magento system + $allModulePaths = $this->normalizeModuleNames($allModulePaths); + if (MftfApplicationConfig::getConfig()->forceGenerateEnabled()) { + $allModulePaths = $this->flipAndFilterModulePathsArray($allModulePaths); $this->enabledModulePaths = $this->applyCustomModuleMethods($allModulePaths); return $this->enabledModulePaths; } $enabledModules = array_merge($this->getEnabledModules(), $this->getModuleWhitelist()); - $enabledDirectoryPaths = $this->getEnabledDirectoryPaths($enabledModules, $allModulePaths); - + $enabledDirectoryPaths = $this->flipAndFilterModulePathsArray($allModulePaths, $enabledModules); $this->enabledModulePaths = $this->applyCustomModuleMethods($enabledDirectoryPaths); + return $this->enabledModulePaths; } + /** + * Sort files according module sequence. + * + * @param array $files + * @return array + */ + public function sortFilesByModuleSequence(array $files) + { + return $this->sequenceSorter->sort($files); + } + + /** + * Return an array of module whitelist that not exist in target Magento instance. + * + * @return array + */ + protected function getModuleWhitelist() + { + $moduleWhitelist = getenv(self::MODULE_WHITELIST); + + if (empty($moduleWhitelist)) { + return []; + } + return array_map('trim', explode(',', $moduleWhitelist)); + } + /** * Retrieves all module directories which might contain pertinent test code. * * @return array + * @throws TestFrameworkException */ private function aggregateTestModulePaths() { @@ -235,13 +318,14 @@ private function aggregateTestModulePaths() $modulePath = defined('TESTS_MODULE_PATH') ? TESTS_MODULE_PATH : TESTS_BP; $modulePath = rtrim($modulePath, DIRECTORY_SEPARATOR); - $vendorCodePath = DIRECTORY_SEPARATOR . "vendor"; - $appCodePath = DIRECTORY_SEPARATOR . "app" . DIRECTORY_SEPARATOR . "code"; + $vendorCodePath = DIRECTORY_SEPARATOR . self::VENDOR; + $appCodePath = DIRECTORY_SEPARATOR . self::APP_CODE; $codePathsToPattern = [ $modulePath => '', - $magentoBaseCodePath . $vendorCodePath => 'Test' . DIRECTORY_SEPARATOR . 'Mftf', - $magentoBaseCodePath . $appCodePath => 'Test' . DIRECTORY_SEPARATOR . 'Mftf' + $magentoBaseCodePath . $vendorCodePath => self::TEST_MFTF_PATTERN, + $magentoBaseCodePath . $appCodePath => self::TEST_MFTF_PATTERN, + $magentoBaseCodePath . self::DEPRECATED_DEV_TESTS => '' ]; foreach ($codePathsToPattern as $codePath => $pattern) { @@ -269,8 +353,6 @@ private function globRelevantPaths($testPath, $pattern) $relevantPaths = $this->globRelevantWrapper($testPath, $pattern); } - $allComponents = $this->getRegisteredModuleList(); - foreach ($relevantPaths as $codePath) { // Reduce magento/app/code/Magento/AdminGws/ to magento/app/code/Magento/AdminGws to read symlink // Symlinks must be resolved otherwise they will not match Magento's filepath to the module @@ -278,9 +360,8 @@ private function globRelevantPaths($testPath, $pattern) if (is_link($potentialSymlink)) { $codePath = realpath($potentialSymlink) . DIRECTORY_SEPARATOR . $pattern; } - - $mainModName = array_search($codePath, $allComponents) ?: basename(str_replace($pattern, '', $codePath)); - $modulePaths[$mainModName][] = $codePath; + $mainModName = basename(str_replace($pattern, '', $codePath)); + $modulePaths[$codePath] = [$mainModName]; if (MftfApplicationConfig::getConfig()->verboseEnabled()) { LoggingUtil::getInstance()->getLogger(ModuleResolver::class)->debug( @@ -290,6 +371,21 @@ private function globRelevantPaths($testPath, $pattern) } } + if (strpos($testPath, self::DEPRECATED_DEV_TESTS) !== false && !empty($modulePaths)) { + $deprecatedPath = ltrim(self::DEPRECATED_DEV_TESTS, DIRECTORY_SEPARATOR); + $suggestedPath = self::DEV_TESTS . DIRECTORY_SEPARATOR . 'Magento'; + $message = "DEPRECATION: Found MFTF test modules in the deprecated path: $deprecatedPath." + . " Move these test modules to $suggestedPath."; + + if (MftfApplicationConfig::getConfig()->verboseEnabled()) { + LoggingUtil::getInstance()->getLogger(ModuleResolver::class)->warning($message); + } + // Suppress print during unit testing + if (MftfApplicationConfig::getConfig()->getPhase() !== MftfApplicationConfig::UNIT_TEST_PHASE) { + print ("\n$message\n\n"); + } + } + return $modulePaths; } @@ -314,46 +410,192 @@ private static function globRelevantWrapper($testPath, $pattern) } /** - * Takes a multidimensional array of module paths and flattens to return a one dimensional array of test paths + * Aggregate all code paths with test module composer json files * - * @param array $modulePaths * @return array */ - private function flattenAllModulePaths($modulePaths) + private function aggregateTestModulePathsFromComposerJson() { - $it = new \RecursiveIteratorIterator(new \RecursiveArrayIterator($modulePaths)); - $resultArray = []; + // Define the module paths + $magentoBaseCodePath = MAGENTO_BP; - foreach ($it as $value) { - $resultArray[] = $value; + // Define the module paths from default TESTS_MODULE_PATH + $modulePath = defined('TESTS_MODULE_PATH') ? TESTS_MODULE_PATH : TESTS_BP; + $modulePath = rtrim($modulePath, DIRECTORY_SEPARATOR); + + $searchCodePaths = [ + $magentoBaseCodePath . DIRECTORY_SEPARATOR . self::DEV_TESTS, + ]; + + // Add TESTS_MODULE_PATH if it's not included + if (array_search($modulePath, $searchCodePaths) === false) { + $searchCodePaths[] = $modulePath; } - return $resultArray; + return $this->getComposerJsonTestModulePaths($searchCodePaths); } /** - * Runs through enabled modules and maps them known module paths by name. - * @param array $enabledModules - * @param array $allModulePaths + * Retrieve all module code paths that have test module composer json files + * + * @param array $codePaths * @return array */ - private function getEnabledDirectoryPaths($enabledModules, $allModulePaths) + private function getComposerJsonTestModulePaths($codePaths) { - $enabledDirectoryPaths = []; - foreach ($enabledModules as $magentoModuleName) { - if (!isset($this->knownDirectories[$magentoModuleName]) && !isset($allModulePaths[$magentoModuleName])) { - continue; - } elseif (isset($this->knownDirectories[$magentoModuleName]) - && !isset($allModulePaths[$magentoModuleName])) { - LoggingUtil::getInstance()->getLogger(ModuleResolver::class)->warn( - "Known directory could not match to an existing path.", - ['knownDirectory' => $magentoModuleName] - ); + if (null !== $this->composerJsonModulePaths) { + return $this->composerJsonModulePaths; + } + try { + $this->composerJsonModulePaths = []; + $resolver = new ComposerModuleResolver(); + $this->composerJsonModulePaths = $resolver->getTestModulesFromPaths($codePaths); + } catch (TestFrameworkException $e) { + } + + return $this->composerJsonModulePaths; + } + + /** + * Aggregate all code paths with composer installed test modules + * + * @return array + */ + private function aggregateTestModulePathsFromComposerInstaller() + { + // Define the module paths + $magentoBaseCodePath = MAGENTO_BP; + $composerFile = $magentoBaseCodePath . DIRECTORY_SEPARATOR . 'composer.json'; + + return $this->getComposerInstalledTestModulePaths($composerFile); + } + + /** + * Retrieve composer installed test module code paths + * + * @params string $composerFile + * @return array + */ + private function getComposerInstalledTestModulePaths($composerFile) + { + if (null !== $this->composerInstalledModulePaths) { + return $this->composerInstalledModulePaths; + } + try { + $this->composerInstalledModulePaths = []; + $resolver = new ComposerModuleResolver(); + $this->composerInstalledModulePaths = $resolver->getComposerInstalledTestModules($composerFile); + } catch (TestFrameworkException $e) { + } + + return $this->composerInstalledModulePaths; + } + + /** + * Flip and filter module code paths + * + * @param array $objectArray + * @param array $filterArray + * @return array + * @SuppressWarnings(PHPMD.CyclomaticComplexity) + */ + private function flipAndFilterModulePathsArray($objectArray, $filterArray = null) + { + $flippedArray = []; + foreach ($objectArray as $path => $modules) { + // One path maps to one module + if (count($modules) == 1) { + if (!is_array($filterArray) + || (is_array($filterArray) && in_array($modules[0], $filterArray)) + || isset($this->knownDirectories[$modules[0]])) { + if (strpos($modules[0], '_') === false) { + $modules[0] = $this->findVendorNameFromPath($path) . '_' . $modules[0]; + } + $flippedArray[$modules[0]] = $path; + } } else { - $enabledDirectoryPaths[$magentoModuleName] = $allModulePaths[$magentoModuleName]; + // One path maps to multiple modules + if (!is_array($filterArray)) { + $flippedArray[$this->findVendorAndModuleNameFromPath($path)] = $path; + } else { + $skip = false; + foreach ($modules as $module) { + if (!in_array($module, $filterArray)) { + $skip = true; + break; + } + } + if (!$skip) { + $flippedArray[$this->findVendorAndModuleNameFromPath($path)] = $path; + } + } } } - return $enabledDirectoryPaths; + return $flippedArray; + } + + /** + * Merge code paths + * + * @param array $oneToOneArray + * @param array $oneToManyArray + * @return array + */ + private function mergeModulePaths($oneToOneArray, $oneToManyArray) + { + $mergedArray = $oneToOneArray; + foreach ($oneToManyArray as $path => $modules) { + // Do nothing when array_key_exists + if (!array_key_exists($path, $oneToOneArray)) { + $mergedArray[$path] = $modules; + } + } + return $mergedArray; + } + + /** + * Normalize module name if registered module list is available + * + * @param array $codePaths + * + * @return array + */ + private function normalizeModuleNames($codePaths) + { + $allComponents = $this->getRegisteredModuleList(); + if (empty($allComponents)) { + return $codePaths; + } + + $normalizedCodePaths = []; + foreach ($codePaths as $path => $moduleNames) { + $mainModName = array_search($path, $allComponents); + if ($mainModName) { + $normalizedCodePaths[$path] = [$mainModName]; + } else { + $normalizedCodePaths[$path] = $moduleNames; + } + } + + return $normalizedCodePaths; + } + + /** + * Takes a multidimensional array of module paths and flattens to return a one dimensional array of test paths + * + * @param array $modulePaths + * @return array + */ + private function flattenAllModulePaths($modulePaths) + { + $it = new \RecursiveIteratorIterator(new \RecursiveArrayIterator($modulePaths)); + $resultArray = []; + + foreach ($it as $value) { + $resultArray[] = $value; + } + + return $resultArray; } /** @@ -447,17 +689,6 @@ protected function getAdminToken() return json_decode($response); } - /** - * Sort files according module sequence. - * - * @param array $files - * @return array - */ - public function sortFilesByModuleSequence(array $files) - { - return $this->sequenceSorter->sort($files); - } - /** * A wrapping method for any custom logic which needs to be applied to the module list * @@ -469,13 +700,16 @@ protected function applyCustomModuleMethods($modulesPath) $modulePathsResult = $this->removeBlacklistModules($modulesPath); $customModulePaths = $this->getCustomModulePaths(); - array_map(function ($value) { + array_map(function ($key, $value) { LoggingUtil::getInstance()->getLogger(ModuleResolver::class)->info( "including custom module", - ['module' => $value] + [$key => $value] ); - }, $customModulePaths); + }, array_keys($customModulePaths), $customModulePaths); + if (!isset($this->enabledModuleNameAndPaths)) { + $this->enabledModuleNameAndPaths = array_merge($modulePathsResult, $customModulePaths); + } return $this->flattenAllModulePaths(array_merge($modulePathsResult, $customModulePaths)); } @@ -489,6 +723,7 @@ private function removeBlacklistModules($modulePaths) { $modulePathsResult = $modulePaths; foreach ($modulePathsResult as $moduleName => $modulePath) { + // Remove module if it is in blacklist if (in_array($moduleName, $this->getModuleBlacklist())) { unset($modulePathsResult[$moduleName]); LoggingUtil::getInstance()->getLogger(ModuleResolver::class)->info( @@ -508,13 +743,18 @@ private function removeBlacklistModules($modulePaths) */ private function getCustomModulePaths() { - $customModulePaths = getenv(self::CUSTOM_MODULE_PATHS); + $customModulePaths = []; + $paths = getenv(self::CUSTOM_MODULE_PATHS); - if (!$customModulePaths) { - return []; + if (!$paths) { + return $customModulePaths; + } + + foreach (explode(',', $paths) as $path) { + $customModulePaths = [$this->findVendorAndModuleNameFromPath(trim($path)) => $path]; } - return array_map('trim', explode(',', $customModulePaths)); + return $customModulePaths; } /** @@ -534,6 +774,10 @@ private function getModuleBlacklist() */ private function getRegisteredModuleList() { + if (!empty($this->registeredModuleList)) { + return $this->registeredModuleList; + } + if (array_key_exists('MAGENTO_BP', $_ENV)) { $autoloadPath = realpath(MAGENTO_BP . "/app/autoload.php"); if ($autoloadPath) { @@ -556,7 +800,7 @@ private function getRegisteredModuleList() array_walk($allComponents, function (&$value) { // Magento stores component paths with unix DIRECTORY_SEPARATOR, need to stay uniform and convert $value = realpath($value); - $value .= '/Test/Mftf'; + $value .= DIRECTORY_SEPARATOR . self::TEST_MFTF_PATTERN; }); return $allComponents; } catch (TestFrameworkException $e) { @@ -575,4 +819,43 @@ private function getBackendUrl() { return getenv('MAGENTO_BACKEND_BASE_URL') ?: getenv('MAGENTO_BASE_URL'); } + + /** + * Find vendor and module name from path + * + * @param string $path + * @return string + */ + private function findVendorAndModuleNameFromPath($path) + { + $path = str_replace(DIRECTORY_SEPARATOR . self::TEST_MFTF_PATTERN, '', $path); + return $this->findVendorNameFromPath($path) . '_' . basename($path); + } + + /** + * Find vendor name from path + * + * @param string $path + * @return string + */ + private function findVendorNameFromPath($path) + { + $possibleVendorName = 'UnknownVendor'; + $dirPaths = [ + self::VENDOR, + self::APP_CODE, + self::DEV_TESTS + ]; + + foreach ($dirPaths as $dirPath) { + $regex = "~.+\\/" . $dirPath . "\/(?<" . self::VENDOR . ">[^\/]+)\/.+~"; + $match = []; + preg_match($regex, $path, $match); + if (isset($match[self::VENDOR])) { + $possibleVendorName = ucfirst($match[self::VENDOR]); + return $possibleVendorName; + } + } + return $possibleVendorName; + } }