PageRenderTime 43ms CodeModel.GetById 12ms RepoModel.GetById 1ms app.codeStats 0ms

/dev/tests/static/testsuite/Magento/Test/Integrity/Di/CompilerTest.php

https://bitbucket.org/crismablanco/magento
PHP | 387 lines | 327 code | 17 blank | 43 comment | 5 complexity | b148e1f3a059a1539d362f4e191ed201 MD5 | raw file
  1. <?php
  2. /**
  3. * Compiler test. Check compilation of DI definitions and code generation
  4. *
  5. * Copyright © Magento, Inc. All rights reserved.
  6. * See COPYING.txt for license details.
  7. */
  8. namespace Magento\Test\Integrity\Di;
  9. use Magento\Framework\Api\Code\Generator\Mapper;
  10. use Magento\Framework\Api\Code\Generator\SearchResults;
  11. use Magento\Framework\App\Filesystem\DirectoryList;
  12. use Magento\Framework\Component\ComponentRegistrar;
  13. use Magento\Framework\Interception\Code\InterfaceValidator;
  14. use Magento\Framework\ObjectManager\Code\Generator\Converter;
  15. use Magento\Framework\ObjectManager\Code\Generator\Factory;
  16. use Magento\Framework\ObjectManager\Code\Generator\Repository;
  17. use Magento\Framework\Api\Code\Generator\ExtensionAttributesInterfaceGenerator;
  18. use Magento\Framework\Api\Code\Generator\ExtensionAttributesGenerator;
  19. use Magento\Framework\App\Utility\Files;
  20. use Magento\TestFramework\Integrity\PluginValidator;
  21. /**
  22. * @SuppressWarnings(PHPMD.CouplingBetweenObjects)
  23. */
  24. class CompilerTest extends \PHPUnit\Framework\TestCase
  25. {
  26. /**
  27. * @var string
  28. */
  29. protected $_command;
  30. /**
  31. * @var \Magento\Framework\Shell
  32. */
  33. protected $_shell;
  34. /**
  35. * @var string
  36. */
  37. protected $_generationDir;
  38. /**
  39. * @var string
  40. */
  41. protected $_compilationDir;
  42. /**
  43. * @var \Magento\Framework\ObjectManager\Config\Mapper\Dom()
  44. */
  45. protected $_mapper;
  46. /**
  47. * @var \Magento\Framework\Code\Validator
  48. */
  49. protected $_validator;
  50. /**
  51. * Class arguments reader
  52. *
  53. * @var PluginValidator
  54. */
  55. protected $pluginValidator;
  56. protected function setUp()
  57. {
  58. $this->_shell = new \Magento\Framework\Shell(new \Magento\Framework\Shell\CommandRenderer());
  59. $basePath = BP;
  60. $basePath = str_replace('\\', '/', $basePath);
  61. $directoryList = new DirectoryList($basePath);
  62. $this->_generationDir = $directoryList->getPath(DirectoryList::GENERATED_CODE);
  63. $this->_compilationDir = $directoryList->getPath(DirectoryList::GENERATED_METADATA);
  64. $this->_command = 'php ' . $basePath . '/bin/magento setup:di:compile';
  65. $booleanUtils = new \Magento\Framework\Stdlib\BooleanUtils();
  66. $constInterpreter = new \Magento\Framework\Data\Argument\Interpreter\Constant();
  67. $argumentInterpreter = new \Magento\Framework\Data\Argument\Interpreter\Composite(
  68. [
  69. 'boolean' => new \Magento\Framework\Data\Argument\Interpreter\Boolean($booleanUtils),
  70. 'string' => new \Magento\Framework\Data\Argument\Interpreter\BaseStringUtils($booleanUtils),
  71. 'number' => new \Magento\Framework\Data\Argument\Interpreter\Number(),
  72. 'null' => new \Magento\Framework\Data\Argument\Interpreter\NullType(),
  73. 'object' => new \Magento\Framework\Data\Argument\Interpreter\DataObject($booleanUtils),
  74. 'const' => $constInterpreter,
  75. 'init_parameter' => new \Magento\Framework\App\Arguments\ArgumentInterpreter($constInterpreter),
  76. ],
  77. \Magento\Framework\ObjectManager\Config\Reader\Dom::TYPE_ATTRIBUTE
  78. );
  79. // Add interpreters that reference the composite
  80. $argumentInterpreter->addInterpreter(
  81. 'array',
  82. new \Magento\Framework\Data\Argument\Interpreter\ArrayType($argumentInterpreter)
  83. );
  84. $this->_mapper = new \Magento\Framework\ObjectManager\Config\Mapper\Dom(
  85. $argumentInterpreter,
  86. $booleanUtils,
  87. new \Magento\Framework\ObjectManager\Config\Mapper\ArgumentParser()
  88. );
  89. $this->_validator = new \Magento\Framework\Code\Validator();
  90. $this->_validator->add(new \Magento\Framework\Code\Validator\ConstructorIntegrity());
  91. $this->_validator->add(new \Magento\Framework\Code\Validator\TypeDuplication());
  92. $this->_validator->add(new \Magento\Framework\Code\Validator\ArgumentSequence());
  93. $this->_validator->add(new \Magento\Framework\Code\Validator\ConstructorArgumentTypes());
  94. $this->pluginValidator = new PluginValidator(new InterfaceValidator());
  95. }
  96. /**
  97. * Validate DI config file
  98. *
  99. * @param string $file
  100. */
  101. protected function _validateFile($file)
  102. {
  103. $dom = new \DOMDocument();
  104. $dom->load($file);
  105. $data = $this->_mapper->convert($dom);
  106. foreach ($data as $instanceName => $parameters) {
  107. if (!isset($parameters['parameters']) || empty($parameters['parameters'])) {
  108. continue;
  109. }
  110. if (\Magento\Framework\App\Utility\Classes::isVirtual($instanceName)) {
  111. $instanceName = \Magento\Framework\App\Utility\Classes::resolveVirtualType($instanceName);
  112. }
  113. if (!$this->_classExistsAsReal($instanceName)) {
  114. continue;
  115. }
  116. $reflectionClass = new \ReflectionClass($instanceName);
  117. $constructor = $reflectionClass->getConstructor();
  118. if (!$constructor) {
  119. $this->fail('Class ' . $instanceName . ' does not have __constructor');
  120. }
  121. $parameters = $parameters['parameters'];
  122. $classParameters = $constructor->getParameters();
  123. foreach ($classParameters as $classParameter) {
  124. $parameterName = $classParameter->getName();
  125. if (array_key_exists($parameterName, $parameters)) {
  126. unset($parameters[$parameterName]);
  127. }
  128. }
  129. $message = 'Configuration of ' . $instanceName . ' contains data for non-existed parameters: ' . implode(
  130. ', ',
  131. array_keys($parameters)
  132. );
  133. $this->assertEmpty($parameters, $message);
  134. }
  135. }
  136. /**
  137. * Checks if class is a real one or generated Factory
  138. * @param string $instanceName class name
  139. * @throws \PHPUnit\Framework\AssertionFailedError
  140. * @return bool
  141. */
  142. protected function _classExistsAsReal($instanceName)
  143. {
  144. if (class_exists($instanceName)) {
  145. return true;
  146. }
  147. // check for generated factory
  148. if (substr($instanceName, -7) == 'Factory' && class_exists(substr($instanceName, 0, -7))) {
  149. return false;
  150. }
  151. $this->fail('Detected configuration of non existed class: ' . $instanceName);
  152. }
  153. /**
  154. * Get php classes list
  155. *
  156. * @return array
  157. */
  158. protected function _phpClassesDataProvider()
  159. {
  160. $generationPath = str_replace('/', '\\', $this->_generationDir);
  161. $files = Files::init()->getPhpFiles(Files::INCLUDE_APP_CODE | Files::INCLUDE_LIBS);
  162. $patterns = ['/' . preg_quote($generationPath) . '/',];
  163. $replacements = [''];
  164. $componentRegistrar = new ComponentRegistrar();
  165. foreach ($componentRegistrar->getPaths(ComponentRegistrar::MODULE) as $moduleName => $modulePath) {
  166. $patterns[] = '/' . preg_quote(str_replace('/', '\\', $modulePath)) . '/';
  167. $replacements[] = '\\' . str_replace('_', '\\', $moduleName);
  168. }
  169. foreach ($componentRegistrar->getPaths(ComponentRegistrar::LIBRARY) as $libPath) {
  170. $patterns[] = '/' . preg_quote(str_replace('/', '\\', $libPath)) . '/';
  171. $replacements[] = '\\Magento\\Framework';
  172. }
  173. /** Convert file names into class name format */
  174. $classes = [];
  175. foreach ($files as $file) {
  176. $file = str_replace('/', '\\', $file);
  177. $filePath = preg_replace($patterns, $replacements, $file);
  178. $className = substr($filePath, 0, -4);
  179. if (class_exists($className, false)) {
  180. $file = str_replace('\\', DIRECTORY_SEPARATOR, $file);
  181. $classes[$file] = $className;
  182. }
  183. }
  184. /** Build class inheritance hierarchy */
  185. $output = [];
  186. $allowedFiles = array_keys($classes);
  187. foreach ($classes as $class) {
  188. if (!in_array($class, $output)) {
  189. $output = array_merge($output, $this->_buildInheritanceHierarchyTree($class, $allowedFiles));
  190. $output = array_unique($output);
  191. }
  192. }
  193. /** Convert data into data provider format */
  194. $outputClasses = [];
  195. foreach ($output as $className) {
  196. $outputClasses[] = [$className];
  197. }
  198. return $outputClasses;
  199. }
  200. /**
  201. * Build inheritance hierarchy tree
  202. *
  203. * @param string $className
  204. * @param array $allowedFiles
  205. * @return array
  206. */
  207. protected function _buildInheritanceHierarchyTree($className, array $allowedFiles)
  208. {
  209. $output = [];
  210. if (0 !== strpos($className, '\\')) {
  211. $className = '\\' . $className;
  212. }
  213. $class = new \ReflectionClass($className);
  214. $parent = $class->getParentClass();
  215. $file = false;
  216. if ($parent) {
  217. $file = str_replace('\\', DIRECTORY_SEPARATOR, $parent->getFileName());
  218. }
  219. /** Prevent analysis of non Magento classes */
  220. if ($parent && in_array($file, $allowedFiles)) {
  221. $output = array_merge(
  222. $this->_buildInheritanceHierarchyTree($parent->getName(), $allowedFiles),
  223. [$className],
  224. $output
  225. );
  226. } else {
  227. $output[] = $className;
  228. }
  229. return array_unique($output);
  230. }
  231. /**
  232. * Validate class
  233. *
  234. * @param string $className
  235. */
  236. protected function _validateClass($className)
  237. {
  238. try {
  239. $this->_validator->validate($className);
  240. } catch (\Magento\Framework\Exception\ValidatorException $exceptions) {
  241. $this->fail($exceptions->getMessage());
  242. } catch (\ReflectionException $exceptions) {
  243. $this->fail($exceptions->getMessage());
  244. }
  245. }
  246. /**
  247. * Validate DI configuration
  248. */
  249. public function testConfigurationOfInstanceParameters()
  250. {
  251. $invoker = new \Magento\Framework\App\Utility\AggregateInvoker($this);
  252. $invoker(
  253. function ($file) {
  254. $this->_validateFile($file);
  255. },
  256. Files::init()->getDiConfigs(true)
  257. );
  258. }
  259. /**
  260. * Validate constructor integrity
  261. */
  262. public function testConstructorIntegrity()
  263. {
  264. $generatorIo = new \Magento\Framework\Code\Generator\Io(
  265. new \Magento\Framework\Filesystem\Driver\File(),
  266. $this->_generationDir
  267. );
  268. $generator = new \Magento\Framework\Code\Generator(
  269. $generatorIo,
  270. [
  271. Factory::ENTITY_TYPE => \Magento\Framework\ObjectManager\Code\Generator\Factory::class,
  272. Repository::ENTITY_TYPE => \Magento\Framework\ObjectManager\Code\Generator\Repository::class,
  273. Converter::ENTITY_TYPE => \Magento\Framework\ObjectManager\Code\Generator\Converter::class,
  274. Mapper::ENTITY_TYPE => \Magento\Framework\Api\Code\Generator\Mapper::class,
  275. SearchResults::ENTITY_TYPE => \Magento\Framework\Api\Code\Generator\SearchResults::class,
  276. ExtensionAttributesInterfaceGenerator::ENTITY_TYPE =>
  277. \Magento\Framework\Api\Code\Generator\ExtensionAttributesInterfaceGenerator::class,
  278. ExtensionAttributesGenerator::ENTITY_TYPE =>
  279. \Magento\Framework\Api\Code\Generator\ExtensionAttributesGenerator::class
  280. ]
  281. );
  282. $generationAutoloader = new \Magento\Framework\Code\Generator\Autoloader($generator);
  283. spl_autoload_register([$generationAutoloader, 'load']);
  284. $invoker = new \Magento\Framework\App\Utility\AggregateInvoker($this);
  285. $invoker(
  286. function ($className) {
  287. $this->_validateClass($className);
  288. },
  289. $this->_phpClassesDataProvider()
  290. );
  291. spl_autoload_unregister([$generationAutoloader, 'load']);
  292. }
  293. /**
  294. * Test consistency of plugin interfaces
  295. */
  296. public function testPluginInterfaces()
  297. {
  298. $invoker = new \Magento\Framework\App\Utility\AggregateInvoker($this);
  299. $invoker(
  300. function ($plugin, $type) {
  301. $this->validatePlugins($plugin, $type);
  302. },
  303. $this->pluginDataProvider()
  304. );
  305. }
  306. /**
  307. * Validate plugin interface
  308. *
  309. * @param string $plugin
  310. * @param string $type
  311. */
  312. protected function validatePlugins($plugin, $type)
  313. {
  314. try {
  315. $module = \Magento\Framework\App\Utility\Classes::getClassModuleName($type);
  316. if (Files::init()->isModuleExists($module)) {
  317. $this->pluginValidator->validate($plugin, $type);
  318. }
  319. } catch (\Magento\Framework\Exception\ValidatorException $exception) {
  320. $this->fail($exception->getMessage());
  321. }
  322. }
  323. /**
  324. * Get application plugins
  325. *
  326. * @return array
  327. */
  328. protected function pluginDataProvider()
  329. {
  330. $files = Files::init()->getDiConfigs();
  331. $plugins = [];
  332. foreach ($files as $file) {
  333. $dom = new \DOMDocument();
  334. $dom->load($file);
  335. $xpath = new \DOMXPath($dom);
  336. $pluginList = $xpath->query('//config/type/plugin');
  337. foreach ($pluginList as $node) {
  338. /** @var $node \DOMNode */
  339. $type = $node->parentNode->attributes->getNamedItem('name')->nodeValue;
  340. $type = \Magento\Framework\App\Utility\Classes::resolveVirtualType($type);
  341. if ($node->attributes->getNamedItem('type')) {
  342. $plugin = $node->attributes->getNamedItem('type')->nodeValue;
  343. $plugin = \Magento\Framework\App\Utility\Classes::resolveVirtualType($plugin);
  344. $plugins[] = ['plugin' => $plugin, 'intercepted type' => $type];
  345. }
  346. }
  347. }
  348. return $plugins;
  349. }
  350. }