1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
|
<?php
declare(strict_types=1);
namespace Drupal\Tests;
use Drupal\Composer\Plugin\VendorHardening\Config;
use Drupal\Tests\Composer\ComposerIntegrationTrait;
use Symfony\Component\Finder\Finder;
/**
* Tests Composer integration.
*
* @group Composer
*/
class ComposerIntegrationTest extends UnitTestCase {
use ComposerIntegrationTrait;
/**
* Tests composer.lock content-hash.
*
* If you have made a change to composer.json, you may need to reconstruct
* composer.lock. Follow the link below for further instructions.
*
* @see https://www.drupal.org/about/core/policies/core-dependencies-policies/managing-composer-updates-for-drupal-core
*/
public function testComposerLockHash() {
$content_hash = self::getContentHash(file_get_contents($this->root . '/composer.json'));
$lock = json_decode(file_get_contents($this->root . '/composer.lock'), TRUE);
$this->assertSame($content_hash, $lock['content-hash']);
// @see \Composer\Repository\PathRepository::initialize()
$core_lock_file_hash = '';
$options = [];
foreach ($lock['packages'] as $package) {
if ($package['name'] === 'drupal/core') {
$core_lock_file_hash = $package['dist']['reference'];
$options = $package['transport-options'] ?? [];
break;
}
}
$core_content_hash = sha1(file_get_contents($this->root . '/core/composer.json') . serialize($options));
$this->assertSame($core_content_hash, $core_lock_file_hash);
}
/**
* Tests composer.json versions.
*
* @param string $path
* Path to a composer.json to test.
*
* @dataProvider providerTestComposerJson
*/
public function testComposerTilde($path) {
if (preg_match('#composer/Metapackage/CoreRecommended/composer.json$#', $path)) {
$this->markTestSkipped("$path has tilde");
}
$content = json_decode(file_get_contents($path), TRUE);
$composer_keys = array_intersect(['require', 'require-dev'], array_keys($content));
if (empty($composer_keys)) {
$this->markTestSkipped("$path has no keys to test");
}
foreach ($composer_keys as $composer_key) {
foreach ($content[$composer_key] as $dependency => $version) {
// We allow tildes if the dependency is a Symfony component.
// @see https://www.drupal.org/node/2887000
if (str_starts_with($dependency, 'symfony/')) {
continue;
}
$this->assertStringNotContainsString('~', $version, "Dependency $dependency in $path contains a tilde, use a caret.");
}
}
}
/**
* Data provider for all the composer.json provided by Drupal core.
*
* @return array
*/
public function providerTestComposerJson() {
$data = [];
$composer_json_finder = $this->getComposerJsonFinder(realpath(__DIR__ . '/../../../../'));
foreach ($composer_json_finder->getIterator() as $composer_json) {
$data[$composer_json->getPathname()] = [$composer_json->getPathname()];
}
return $data;
}
/**
* Tests core's composer.json replace section.
*
* Verify that all core components are also listed in the 'replace' section of
* core's composer.json.
*/
public function testAllCoreComponentsReplaced(): void {
// Assemble a path to core components.
$components_path = $this->root . '/core/lib/Drupal/Component';
// Grab the 'replace' section of the core composer.json file.
$json = json_decode(file_get_contents($this->root . '/core/composer.json'), FALSE);
$composer_replace_packages = (array) $json->replace;
// Get a list of all the composer.json files in the components path.
$components_composer_json_files = [];
$composer_json_finder = new Finder();
$composer_json_finder->name('composer.json')
->in($components_path)
->ignoreUnreadableDirs();
foreach ($composer_json_finder->getIterator() as $composer_json) {
$components_composer_json_files[$composer_json->getPathname()] = [$composer_json->getPathname()];
}
$this->assertNotEmpty($components_composer_json_files);
$this->assertCount(count($composer_replace_packages), $components_composer_json_files);
// Assert that each core components has a corresponding 'replace' in
// composer.json.
foreach ($components_composer_json_files as $components_composer_json_file) {
$json = json_decode(file_get_contents(reset($components_composer_json_file)), FALSE);
$component_name = $json->name;
$this->assertArrayHasKey(
$component_name,
$composer_replace_packages,
'Unable to find ' . $component_name . ' in replace list of composer.json'
);
}
}
/**
* Data provider for the scaffold files test for Drupal core.
*
* @return array
*/
public function providerTestExpectedScaffoldFiles() {
return [
['.editorconfig', 'assets/scaffold/files/editorconfig', '[project-root]'],
['.gitattributes', 'assets/scaffold/files/gitattributes', '[project-root]'],
['.csslintrc', 'assets/scaffold/files/csslintrc'],
['.eslintignore', 'assets/scaffold/files/eslintignore'],
['.eslintrc.json', 'assets/scaffold/files/eslintrc.json'],
['.ht.router.php', 'assets/scaffold/files/ht.router.php'],
['.htaccess', 'assets/scaffold/files/htaccess'],
['example.gitignore', 'assets/scaffold/files/example.gitignore'],
['index.php', 'assets/scaffold/files/index.php'],
['INSTALL.txt', 'assets/scaffold/files/drupal.INSTALL.txt'],
['README.md', 'assets/scaffold/files/drupal.README.md'],
['robots.txt', 'assets/scaffold/files/robots.txt'],
['update.php', 'assets/scaffold/files/update.php'],
['web.config', 'assets/scaffold/files/web.config'],
['sites/README.txt', 'assets/scaffold/files/sites.README.txt'],
['sites/development.services.yml', 'assets/scaffold/files/development.services.yml'],
['sites/example.settings.local.php', 'assets/scaffold/files/example.settings.local.php'],
['sites/example.sites.php', 'assets/scaffold/files/example.sites.php'],
['sites/default/default.services.yml', 'assets/scaffold/files/default.services.yml'],
['sites/default/default.settings.php', 'assets/scaffold/files/default.settings.php'],
['modules/README.txt', 'assets/scaffold/files/modules.README.txt'],
['profiles/README.txt', 'assets/scaffold/files/profiles.README.txt'],
['themes/README.txt', 'assets/scaffold/files/themes.README.txt'],
];
}
/**
* Tests core's composer.json extra drupal-scaffold file-mappings section.
*
* Verify that every file listed in file-mappings exists in its destination
* path (mapping key) and also at its source path (mapping value), and that
* both of these files have exactly the same content.
*
* In Drupal 9, the files at the destination path will be removed. For the
* remainder of the Drupal 8 development cycle, these files will remain in
* order to maintain backwards compatibility with sites based on the template
* project drupal-composer/drupal-project.
*
* See https://www.drupal.org/project/drupal/issues/3075954
*
* @param string $destRelPath
* Path to scaffold file destination location
* @param string $sourceRelPath
* Path to scaffold file source location
* @param string $expectedDestination
* Named location to the destination path of the scaffold file
*
* @dataProvider providerTestExpectedScaffoldFiles
*/
public function testExpectedScaffoldFiles($destRelPath, $sourceRelPath, $expectedDestination = '[web-root]') {
// Grab the 'file-mapping' section of the core composer.json file.
$json = json_decode(file_get_contents($this->root . '/core/composer.json'));
$scaffold_file_mapping = (array) $json->extra->{'drupal-scaffold'}->{'file-mapping'};
// Assert that the 'file-mapping' section has the expected entry.
$this->assertArrayHasKey("$expectedDestination/$destRelPath", $scaffold_file_mapping);
$this->assertEquals($sourceRelPath, $scaffold_file_mapping["$expectedDestination/$destRelPath"]);
// Assert that the source file exists.
$this->assertFileExists($this->root . '/core/' . $sourceRelPath);
// Assert that the destination file exists and has the same contents as
// the source file. Note that in Drupal 9, the destination file will be
// removed.
$this->assertFileExists($this->root . '/' . $destRelPath);
$this->assertFileEquals($this->root . '/core/' . $sourceRelPath, $this->root . '/' . $destRelPath, 'Scaffold source and destination files must have the same contents.');
}
// phpcs:disable
/**
* The following method is copied from \Composer\Package\Locker.
*
* @see https://github.com/composer/composer
*/
/**
* Returns the md5 hash of the sorted content of the composer file.
*
* @param string $composerFileContents The contents of the composer file.
*
* @return string
*/
protected static function getContentHash($composerFileContents)
{
$content = json_decode($composerFileContents, true);
$relevantKeys = array(
'name',
'version',
'require',
'require-dev',
'conflict',
'replace',
'provide',
'minimum-stability',
'prefer-stable',
'repositories',
'extra',
);
$relevantContent = array();
foreach (array_intersect($relevantKeys, array_keys($content)) as $key) {
$relevantContent[$key] = $content[$key];
}
if (isset($content['config']['platform'])) {
$relevantContent['config']['platform'] = $content['config']['platform'];
}
ksort($relevantContent);
return md5(json_encode($relevantContent));
}
// phpcs:enable
/**
* Tests the vendor cleanup utilities do not have obsolete packages listed.
*/
public function testVendorCleanup(): void {
$lock = json_decode(file_get_contents($this->root . '/composer.lock'), TRUE);
$packages = [];
foreach (array_merge($lock['packages'], $lock['packages-dev']) as $package) {
$packages[] = $package['name'];
}
$reflection = new \ReflectionProperty(Config::class, 'defaultConfig');
$config = $reflection->getValue();
foreach (array_keys($config) as $package) {
$this->assertContains(strtolower($package), $packages);
}
}
}
|