ComposerIntegrationTest.php 8.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223
  1. <?php
  2. namespace Drupal\Tests;
  3. use Drupal\Tests\Composer\ComposerIntegrationTrait;
  4. /**
  5. * Tests Composer integration.
  6. *
  7. * @group Composer
  8. */
  9. class ComposerIntegrationTest extends UnitTestCase {
  10. use ComposerIntegrationTrait;
  11. /**
  12. * Tests composer.lock content-hash.
  13. */
  14. public function testComposerLockHash() {
  15. $content_hash = self::getContentHash(file_get_contents($this->root . '/composer.json'));
  16. $lock = json_decode(file_get_contents($this->root . '/composer.lock'), TRUE);
  17. $this->assertSame($content_hash, $lock['content-hash']);
  18. }
  19. /**
  20. * Tests composer.json versions.
  21. *
  22. * @param string $path
  23. * Path to a composer.json to test.
  24. *
  25. * @dataProvider providerTestComposerJson
  26. */
  27. public function testComposerTilde($path) {
  28. $content = json_decode(file_get_contents($path), TRUE);
  29. $composer_keys = array_intersect(['require', 'require-dev'], array_keys($content));
  30. if (empty($composer_keys)) {
  31. $this->markTestSkipped("$path has no keys to test");
  32. }
  33. foreach ($composer_keys as $composer_key) {
  34. foreach ($content[$composer_key] as $dependency => $version) {
  35. // We allow tildes if the dependency is a Symfony component.
  36. // @see https://www.drupal.org/node/2887000
  37. if (strpos($dependency, 'symfony/') === 0) {
  38. continue;
  39. }
  40. $this->assertStringNotContainsString('~', $version, "Dependency $dependency in $path contains a tilde, use a caret.");
  41. }
  42. }
  43. }
  44. /**
  45. * Data provider for all the composer.json provided by Drupal core.
  46. *
  47. * @return array
  48. */
  49. public function providerTestComposerJson() {
  50. $data = [];
  51. $composer_json_finder = $this->getComposerJsonFinder(realpath(__DIR__ . '/../../../../'));
  52. foreach ($composer_json_finder->getIterator() as $composer_json) {
  53. $data[] = [$composer_json->getPathname()];
  54. }
  55. return $data;
  56. }
  57. /**
  58. * Tests core's composer.json replace section.
  59. *
  60. * Verify that all core modules are also listed in the 'replace' section of
  61. * core's composer.json.
  62. */
  63. public function testAllModulesReplaced() {
  64. // Assemble a path to core modules.
  65. $module_path = $this->root . '/core/modules';
  66. // Grab the 'replace' section of the core composer.json file.
  67. $json = json_decode(file_get_contents($this->root . '/core/composer.json'));
  68. $composer_replace_packages = (array) $json->replace;
  69. // Get a list of all the files in the module path.
  70. $folders = scandir($module_path);
  71. // Make sure we only deal with directories that aren't . or ..
  72. $module_names = [];
  73. $discard = ['.', '..'];
  74. foreach ($folders as $file_name) {
  75. if ((!in_array($file_name, $discard)) && is_dir($module_path . '/' . $file_name)) {
  76. $module_names[] = $file_name;
  77. }
  78. }
  79. // Assert that each core module has a corresponding 'replace' in
  80. // composer.json.
  81. foreach ($module_names as $module_name) {
  82. $this->assertArrayHasKey(
  83. 'drupal/' . $module_name,
  84. $composer_replace_packages,
  85. 'Unable to find ' . $module_name . ' in replace list of composer.json'
  86. );
  87. }
  88. }
  89. /**
  90. * Data provider for the scaffold files test for Drupal core.
  91. *
  92. * @return array
  93. */
  94. public function providerTestExpectedScaffoldFiles() {
  95. return [
  96. ['.editorconfig', 'assets/scaffold/files/editorconfig', '[project-root]'],
  97. ['.gitattributes', 'assets/scaffold/files/gitattributes', '[project-root]'],
  98. ['.csslintrc', 'assets/scaffold/files/csslintrc'],
  99. ['.eslintignore', 'assets/scaffold/files/eslintignore'],
  100. ['.eslintrc.json', 'assets/scaffold/files/eslintrc.json'],
  101. ['.ht.router.php', 'assets/scaffold/files/ht.router.php'],
  102. ['.htaccess', 'assets/scaffold/files/htaccess'],
  103. ['example.gitignore', 'assets/scaffold/files/example.gitignore'],
  104. ['index.php', 'assets/scaffold/files/index.php'],
  105. ['INSTALL.txt', 'assets/scaffold/files/drupal.INSTALL.txt'],
  106. ['README.txt', 'assets/scaffold/files/drupal.README.txt'],
  107. ['robots.txt', 'assets/scaffold/files/robots.txt'],
  108. ['update.php', 'assets/scaffold/files/update.php'],
  109. ['web.config', 'assets/scaffold/files/web.config'],
  110. ['sites/README.txt', 'assets/scaffold/files/sites.README.txt'],
  111. ['sites/development.services.yml', 'assets/scaffold/files/development.services.yml'],
  112. ['sites/example.settings.local.php', 'assets/scaffold/files/example.settings.local.php'],
  113. ['sites/example.sites.php', 'assets/scaffold/files/example.sites.php'],
  114. ['sites/default/default.services.yml', 'assets/scaffold/files/default.services.yml'],
  115. ['sites/default/default.settings.php', 'assets/scaffold/files/default.settings.php'],
  116. ['modules/README.txt', 'assets/scaffold/files/modules.README.txt'],
  117. ['profiles/README.txt', 'assets/scaffold/files/profiles.README.txt'],
  118. ['themes/README.txt', 'assets/scaffold/files/themes.README.txt'],
  119. ];
  120. }
  121. /**
  122. * Tests core's composer.json extra drupal-scaffold file-mappings section.
  123. *
  124. * Verify that every file listed in file-mappings exists in its destination
  125. * path (mapping key) and also at its source path (mapping value), and that
  126. * both of these files have exactly the same content.
  127. *
  128. * In Drupal 9, the files at the destination path will be removed. For the
  129. * remainder of the Drupal 8 development cycle, these files will remain in
  130. * order to maintain backwards compatibility with sites based on the template
  131. * project drupal-composer/drupal-project.
  132. *
  133. * See https://www.drupal.org/project/drupal/issues/3075954
  134. *
  135. * @param string $destRelPath
  136. * Path to scaffold file destination location
  137. * @param string $sourceRelPath
  138. * Path to scaffold file source location
  139. * @param string $expectedDestination
  140. * Named location to the destination path of the scaffold file
  141. *
  142. * @dataProvider providerTestExpectedScaffoldFiles
  143. */
  144. public function testExpectedScaffoldFiles($destRelPath, $sourceRelPath, $expectedDestination = '[web-root]') {
  145. // Grab the 'file-mapping' section of the core composer.json file.
  146. $json = json_decode(file_get_contents($this->root . '/core/composer.json'));
  147. $scaffold_file_mapping = (array) $json->extra->{'drupal-scaffold'}->{'file-mapping'};
  148. // Assert that the 'file-mapping' section has the expected entry.
  149. $this->assertArrayHasKey("$expectedDestination/$destRelPath", $scaffold_file_mapping);
  150. $this->assertEquals($sourceRelPath, $scaffold_file_mapping["$expectedDestination/$destRelPath"]);
  151. // Assert that the source file exists.
  152. $this->assertFileExists($this->root . '/core/' . $sourceRelPath);
  153. // Assert that the destination file exists and has the same contents as
  154. // the source file. Note that in Drupal 9, the destination file will be
  155. // removed.
  156. $this->assertFileExists($this->root . '/' . $destRelPath);
  157. $this->assertFileEquals($this->root . '/core/' . $sourceRelPath, $this->root . '/' . $destRelPath, 'Scaffold source and destination files must have the same contents.');
  158. }
  159. // @codingStandardsIgnoreStart
  160. /**
  161. * The following method is copied from \Composer\Package\Locker.
  162. *
  163. * @see https://github.com/composer/composer
  164. */
  165. /**
  166. * Returns the md5 hash of the sorted content of the composer file.
  167. *
  168. * @param string $composerFileContents The contents of the composer file.
  169. *
  170. * @return string
  171. */
  172. protected static function getContentHash($composerFileContents)
  173. {
  174. $content = json_decode($composerFileContents, true);
  175. $relevantKeys = array(
  176. 'name',
  177. 'version',
  178. 'require',
  179. 'require-dev',
  180. 'conflict',
  181. 'replace',
  182. 'provide',
  183. 'minimum-stability',
  184. 'prefer-stable',
  185. 'repositories',
  186. 'extra',
  187. );
  188. $relevantContent = array();
  189. foreach (array_intersect($relevantKeys, array_keys($content)) as $key) {
  190. $relevantContent[$key] = $content[$key];
  191. }
  192. if (isset($content['config']['platform'])) {
  193. $relevantContent['config']['platform'] = $content['config']['platform'];
  194. }
  195. ksort($relevantContent);
  196. return md5(json_encode($relevantContent));
  197. }
  198. // @codingStandardsIgnoreEnd
  199. }