ComposerIntegrationTest.php 7.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220
  1. <?php
  2. namespace Drupal\Tests;
  3. /**
  4. * Tests Composer integration.
  5. *
  6. * @group Composer
  7. */
  8. class ComposerIntegrationTest extends UnitTestCase {
  9. /**
  10. * Gets human-readable JSON error messages.
  11. *
  12. * @return string[]
  13. * Keys are JSON_ERROR_* constants.
  14. */
  15. protected function getErrorMessages() {
  16. $messages = [
  17. 0 => 'No errors found',
  18. JSON_ERROR_DEPTH => 'The maximum stack depth has been exceeded',
  19. JSON_ERROR_STATE_MISMATCH => 'Invalid or malformed JSON',
  20. JSON_ERROR_CTRL_CHAR => 'Control character error, possibly incorrectly encoded',
  21. JSON_ERROR_SYNTAX => 'Syntax error',
  22. JSON_ERROR_UTF8 => 'Malformed UTF-8 characters, possibly incorrectly encoded',
  23. ];
  24. if (version_compare(phpversion(), '5.5.0', '>=')) {
  25. $messages[JSON_ERROR_RECURSION] = 'One or more recursive references in the value to be encoded';
  26. $messages[JSON_ERROR_INF_OR_NAN] = 'One or more NAN or INF values in the value to be encoded';
  27. $messages[JSON_ERROR_UNSUPPORTED_TYPE] = 'A value of a type that cannot be encoded was given';
  28. }
  29. return $messages;
  30. }
  31. /**
  32. * Gets the paths to the folders that contain the Composer integration.
  33. *
  34. * @return string[]
  35. * The paths.
  36. */
  37. protected function getPaths() {
  38. return [
  39. $this->root,
  40. $this->root . '/core',
  41. $this->root . '/core/lib/Drupal/Component/Annotation',
  42. $this->root . '/core/lib/Drupal/Component/Assertion',
  43. $this->root . '/core/lib/Drupal/Component/Bridge',
  44. $this->root . '/core/lib/Drupal/Component/ClassFinder',
  45. $this->root . '/core/lib/Drupal/Component/Datetime',
  46. $this->root . '/core/lib/Drupal/Component/DependencyInjection',
  47. $this->root . '/core/lib/Drupal/Component/Diff',
  48. $this->root . '/core/lib/Drupal/Component/Discovery',
  49. $this->root . '/core/lib/Drupal/Component/EventDispatcher',
  50. $this->root . '/core/lib/Drupal/Component/FileCache',
  51. $this->root . '/core/lib/Drupal/Component/FileSystem',
  52. $this->root . '/core/lib/Drupal/Component/Gettext',
  53. $this->root . '/core/lib/Drupal/Component/Graph',
  54. $this->root . '/core/lib/Drupal/Component/HttpFoundation',
  55. $this->root . '/core/lib/Drupal/Component/PhpStorage',
  56. $this->root . '/core/lib/Drupal/Component/Plugin',
  57. $this->root . '/core/lib/Drupal/Component/ProxyBuilder',
  58. $this->root . '/core/lib/Drupal/Component/Render',
  59. $this->root . '/core/lib/Drupal/Component/Serialization',
  60. $this->root . '/core/lib/Drupal/Component/Transliteration',
  61. $this->root . '/core/lib/Drupal/Component/Utility',
  62. $this->root . '/core/lib/Drupal/Component/Uuid',
  63. ];
  64. }
  65. /**
  66. * Tests composer.json.
  67. */
  68. public function testComposerJson() {
  69. foreach ($this->getPaths() as $path) {
  70. $json = file_get_contents($path . '/composer.json');
  71. $result = json_decode($json);
  72. $this->assertNotNull($result, $this->getErrorMessages()[json_last_error()]);
  73. }
  74. }
  75. /**
  76. * Tests composer.lock content-hash.
  77. */
  78. public function testComposerLockHash() {
  79. $content_hash = self::getContentHash(file_get_contents($this->root . '/composer.json'));
  80. $lock = json_decode(file_get_contents($this->root . '/composer.lock'), TRUE);
  81. $this->assertSame($content_hash, $lock['content-hash']);
  82. }
  83. /**
  84. * Tests composer.json versions.
  85. *
  86. * @param string $path
  87. * Path to a composer.json to test.
  88. *
  89. * @dataProvider providerTestComposerJson
  90. */
  91. public function testComposerTilde($path) {
  92. $content = json_decode(file_get_contents($path), TRUE);
  93. $composer_keys = array_intersect(['require', 'require-dev'], array_keys($content));
  94. if (empty($composer_keys)) {
  95. $this->markTestSkipped("$path has no keys to test");
  96. }
  97. foreach ($composer_keys as $composer_key) {
  98. foreach ($content[$composer_key] as $dependency => $version) {
  99. // We allow tildes if the dependency is a Symfony component.
  100. // @see https://www.drupal.org/node/2887000
  101. if (strpos($dependency, 'symfony/') === 0) {
  102. continue;
  103. }
  104. $this->assertFalse(strpos($version, '~'), "Dependency $dependency in $path contains a tilde, use a caret.");
  105. }
  106. }
  107. }
  108. /**
  109. * Data provider for all the composer.json provided by Drupal core.
  110. *
  111. * @return array
  112. */
  113. public function providerTestComposerJson() {
  114. $root = realpath(__DIR__ . '/../../../../');
  115. $tests = [[$root . '/composer.json']];
  116. $directory = new \RecursiveDirectoryIterator($root . '/core');
  117. $iterator = new \RecursiveIteratorIterator($directory);
  118. /** @var \SplFileInfo $file */
  119. foreach ($iterator as $file) {
  120. if ($file->getFilename() === 'composer.json' && strpos($file->getPath(), 'core/modules/system/tests/fixtures/HtaccessTest') === FALSE) {
  121. $tests[] = [$file->getRealPath()];
  122. }
  123. }
  124. return $tests;
  125. }
  126. /**
  127. * Tests core's composer.json replace section.
  128. *
  129. * Verify that all core modules are also listed in the 'replace' section of
  130. * core's composer.json.
  131. */
  132. public function testAllModulesReplaced() {
  133. // Assemble a path to core modules.
  134. $module_path = $this->root . '/core/modules';
  135. // Grab the 'replace' section of the core composer.json file.
  136. $json = json_decode(file_get_contents($this->root . '/core/composer.json'));
  137. $composer_replace_packages = (array) $json->replace;
  138. // Get a list of all the files in the module path.
  139. $folders = scandir($module_path);
  140. // Make sure we only deal with directories that aren't . or ..
  141. $module_names = [];
  142. $discard = ['.', '..'];
  143. foreach ($folders as $file_name) {
  144. if ((!in_array($file_name, $discard)) && is_dir($module_path . '/' . $file_name)) {
  145. $module_names[] = $file_name;
  146. }
  147. }
  148. // Assert that each core module has a corresponding 'replace' in
  149. // composer.json.
  150. foreach ($module_names as $module_name) {
  151. $this->assertArrayHasKey(
  152. 'drupal/' . $module_name,
  153. $composer_replace_packages,
  154. 'Unable to find ' . $module_name . ' in replace list of composer.json'
  155. );
  156. }
  157. }
  158. // @codingStandardsIgnoreStart
  159. /**
  160. * The following method is copied from \Composer\Package\Locker.
  161. *
  162. * @see https://github.com/composer/composer
  163. */
  164. /**
  165. * Returns the md5 hash of the sorted content of the composer file.
  166. *
  167. * @param string $composerFileContents The contents of the composer file.
  168. *
  169. * @return string
  170. */
  171. protected static function getContentHash($composerFileContents)
  172. {
  173. $content = json_decode($composerFileContents, true);
  174. $relevantKeys = array(
  175. 'name',
  176. 'version',
  177. 'require',
  178. 'require-dev',
  179. 'conflict',
  180. 'replace',
  181. 'provide',
  182. 'minimum-stability',
  183. 'prefer-stable',
  184. 'repositories',
  185. 'extra',
  186. );
  187. $relevantContent = array();
  188. foreach (array_intersect($relevantKeys, array_keys($content)) as $key) {
  189. $relevantContent[$key] = $content[$key];
  190. }
  191. if (isset($content['config']['platform'])) {
  192. $relevantContent['config']['platform'] = $content['config']['platform'];
  193. }
  194. ksort($relevantContent);
  195. return md5(json_encode($relevantContent));
  196. }
  197. // @codingStandardsIgnoreEnd
  198. }