Updated Drupal to 8.6. This goes with the following updates because it's possible...
[yaffs-website] / web / core / tests / Drupal / Tests / ComposerIntegrationTest.php
1 <?php
2
3 namespace Drupal\Tests;
4
5 use Composer\Semver\Semver;
6
7 /**
8  * Tests Composer integration.
9  *
10  * @group Composer
11  */
12 class ComposerIntegrationTest extends UnitTestCase {
13
14   /**
15    * The minimum PHP version supported by Drupal.
16    *
17    * @see https://www.drupal.org/docs/8/system-requirements/web-server
18    *
19    * @todo Remove as part of https://www.drupal.org/node/2908079
20    */
21   const MIN_PHP_VERSION = '5.5.9';
22
23   /**
24    * Gets human-readable JSON error messages.
25    *
26    * @return string[]
27    *   Keys are JSON_ERROR_* constants.
28    */
29   protected function getErrorMessages() {
30     $messages = [
31       0 => 'No errors found',
32       JSON_ERROR_DEPTH => 'The maximum stack depth has been exceeded',
33       JSON_ERROR_STATE_MISMATCH => 'Invalid or malformed JSON',
34       JSON_ERROR_CTRL_CHAR => 'Control character error, possibly incorrectly encoded',
35       JSON_ERROR_SYNTAX => 'Syntax error',
36       JSON_ERROR_UTF8 => 'Malformed UTF-8 characters, possibly incorrectly encoded',
37     ];
38
39     if (version_compare(phpversion(), '5.5.0', '>=')) {
40       $messages[JSON_ERROR_RECURSION] = 'One or more recursive references in the value to be encoded';
41       $messages[JSON_ERROR_INF_OR_NAN] = 'One or more NAN or INF values in the value to be encoded';
42       $messages[JSON_ERROR_UNSUPPORTED_TYPE] = 'A value of a type that cannot be encoded was given';
43     }
44
45     return $messages;
46   }
47
48   /**
49    * Gets the paths to the folders that contain the Composer integration.
50    *
51    * @return string[]
52    *   The paths.
53    */
54   protected function getPaths() {
55     return [
56       $this->root,
57       $this->root . '/core',
58       $this->root . '/core/lib/Drupal/Component/Annotation',
59       $this->root . '/core/lib/Drupal/Component/Assertion',
60       $this->root . '/core/lib/Drupal/Component/Bridge',
61       $this->root . '/core/lib/Drupal/Component/ClassFinder',
62       $this->root . '/core/lib/Drupal/Component/Datetime',
63       $this->root . '/core/lib/Drupal/Component/DependencyInjection',
64       $this->root . '/core/lib/Drupal/Component/Diff',
65       $this->root . '/core/lib/Drupal/Component/Discovery',
66       $this->root . '/core/lib/Drupal/Component/EventDispatcher',
67       $this->root . '/core/lib/Drupal/Component/FileCache',
68       $this->root . '/core/lib/Drupal/Component/FileSystem',
69       $this->root . '/core/lib/Drupal/Component/Gettext',
70       $this->root . '/core/lib/Drupal/Component/Graph',
71       $this->root . '/core/lib/Drupal/Component/HttpFoundation',
72       $this->root . '/core/lib/Drupal/Component/PhpStorage',
73       $this->root . '/core/lib/Drupal/Component/Plugin',
74       $this->root . '/core/lib/Drupal/Component/ProxyBuilder',
75       $this->root . '/core/lib/Drupal/Component/Render',
76       $this->root . '/core/lib/Drupal/Component/Serialization',
77       $this->root . '/core/lib/Drupal/Component/Transliteration',
78       $this->root . '/core/lib/Drupal/Component/Utility',
79       $this->root . '/core/lib/Drupal/Component/Uuid',
80     ];
81   }
82
83   /**
84    * Tests composer.json.
85    */
86   public function testComposerJson() {
87     foreach ($this->getPaths() as $path) {
88       $json = file_get_contents($path . '/composer.json');
89       $result = json_decode($json);
90       $this->assertNotNull($result, $this->getErrorMessages()[json_last_error()]);
91     }
92   }
93
94   /**
95    * Tests composer.lock content-hash.
96    */
97   public function testComposerLockHash() {
98     $content_hash = self::getContentHash(file_get_contents($this->root . '/composer.json'));
99     $lock = json_decode(file_get_contents($this->root . '/composer.lock'), TRUE);
100     $this->assertSame($content_hash, $lock['content-hash']);
101   }
102
103   /**
104    * Tests composer.json versions.
105    *
106    * @param string $path
107    *   Path to a composer.json to test.
108    *
109    * @dataProvider providerTestComposerJson
110    */
111   public function testComposerTilde($path) {
112     $content = json_decode(file_get_contents($path), TRUE);
113     $composer_keys = array_intersect(['require', 'require-dev'], array_keys($content));
114     if (empty($composer_keys)) {
115       $this->markTestSkipped("$path has no keys to test");
116     }
117     foreach ($composer_keys as $composer_key) {
118       foreach ($content[$composer_key] as $dependency => $version) {
119         // We allow tildes if the dependency is a Symfony component.
120         // @see https://www.drupal.org/node/2887000
121         if (strpos($dependency, 'symfony/') === 0) {
122           continue;
123         }
124         $this->assertFalse(strpos($version, '~'), "Dependency $dependency in $path contains a tilde, use a caret.");
125       }
126     }
127   }
128
129   /**
130    * Data provider for all the composer.json provided by Drupal core.
131    *
132    * @return array
133    */
134   public function providerTestComposerJson() {
135     $root = realpath(__DIR__ . '/../../../../');
136     $tests = [[$root . '/composer.json']];
137     $directory = new \RecursiveDirectoryIterator($root . '/core');
138     $iterator = new \RecursiveIteratorIterator($directory);
139     /** @var \SplFileInfo $file */
140     foreach ($iterator as $file) {
141       if ($file->getFilename() === 'composer.json' && strpos($file->getPath(), 'core/modules/system/tests/fixtures/HtaccessTest') === FALSE) {
142         $tests[] = [$file->getRealPath()];
143       }
144     }
145     return $tests;
146   }
147
148   /**
149    * Tests core's composer.json replace section.
150    *
151    * Verify that all core modules are also listed in the 'replace' section of
152    * core's composer.json.
153    */
154   public function testAllModulesReplaced() {
155     // Assemble a path to core modules.
156     $module_path = $this->root . '/core/modules';
157
158     // Grab the 'replace' section of the core composer.json file.
159     $json = json_decode(file_get_contents($this->root . '/core/composer.json'));
160     $composer_replace_packages = (array) $json->replace;
161
162     // Get a list of all the files in the module path.
163     $folders = scandir($module_path);
164
165     // Make sure we only deal with directories that aren't . or ..
166     $module_names = [];
167     $discard = ['.', '..'];
168     foreach ($folders as $file_name) {
169       if ((!in_array($file_name, $discard)) && is_dir($module_path . '/' . $file_name)) {
170         $module_names[] = $file_name;
171       }
172     }
173
174     // Assert that each core module has a corresponding 'replace' in
175     // composer.json.
176     foreach ($module_names as $module_name) {
177       $this->assertArrayHasKey(
178         'drupal/' . $module_name,
179         $composer_replace_packages,
180         'Unable to find ' . $module_name . ' in replace list of composer.json'
181       );
182     }
183   }
184
185   /**
186    * Tests package requirements for the minimum supported PHP version by Drupal.
187    *
188    * @todo This can be removed when DrupalCI supports dependency regression
189    *   testing in https://www.drupal.org/node/2874198
190    */
191   public function testMinPHPVersion() {
192     // Check for lockfile in the application root. If the lockfile does not
193     // exist, then skip this test.
194     $lockfile = $this->root . '/composer.lock';
195     if (!file_exists($lockfile)) {
196       $this->markTestSkipped('/composer.lock is not available.');
197     }
198
199     $lock = json_decode(file_get_contents($lockfile), TRUE);
200
201     // Check the PHP version for each installed non-development  package. The
202     // testing infrastructure uses the uses the development packages, and may
203     // update them for particular environment configurations. In particular,
204     // PHP 7.2+ require an updated version of phpunit, which is incompatible
205     // with Drupal's minimum PHP requirement.
206     foreach ($lock['packages'] as $package) {
207       if (isset($package['require']['php'])) {
208         $this->assertTrue(Semver::satisfies(static::MIN_PHP_VERSION, $package['require']['php']), $package['name'] . ' has a PHP dependency requirement of "' . $package['require']['php'] . '"');
209       }
210     }
211   }
212
213   // @codingStandardsIgnoreStart
214   /**
215    * The following method is copied from \Composer\Package\Locker.
216    *
217    * @see https://github.com/composer/composer
218    */
219   /**
220    * Returns the md5 hash of the sorted content of the composer file.
221    *
222    * @param string $composerFileContents The contents of the composer file.
223    *
224    * @return string
225    */
226   protected static function getContentHash($composerFileContents)
227   {
228     $content = json_decode($composerFileContents, true);
229
230     $relevantKeys = array(
231       'name',
232       'version',
233       'require',
234       'require-dev',
235       'conflict',
236       'replace',
237       'provide',
238       'minimum-stability',
239       'prefer-stable',
240       'repositories',
241       'extra',
242     );
243
244     $relevantContent = array();
245
246     foreach (array_intersect($relevantKeys, array_keys($content)) as $key) {
247       $relevantContent[$key] = $content[$key];
248     }
249     if (isset($content['config']['platform'])) {
250       $relevantContent['config']['platform'] = $content['config']['platform'];
251     }
252
253     ksort($relevantContent);
254
255     return md5(json_encode($relevantContent));
256   }
257   // @codingStandardsIgnoreEnd
258
259 }