Newer
Older

Adam G-H
committed
<?php
namespace Drupal\Tests\package_manager\Kernel;

Adam G-H
committed
use Drupal\Core\DependencyInjection\ContainerBuilder;

Adam G-H
committed
use Drupal\KernelTests\KernelTestBase;
use Drupal\package_manager\Event\StageEvent;
use Drupal\package_manager\Validator\DiskSpaceValidator;
use Drupal\package_manager\Exception\StageException;
use Drupal\package_manager\Exception\StageValidationException;
use Drupal\package_manager\PathLocator;

Adam G-H
committed
use Drupal\package_manager\Stage;
use Drupal\Tests\package_manager\Traits\ValidationTestTrait;
use org\bovigo\vfs\vfsStream;

Adam G-H
committed
/**
* Base class for kernel tests of Package Manager's functionality.
*/
abstract class PackageManagerKernelTestBase extends KernelTestBase {
use ValidationTestTrait;
/**
* {@inheritdoc}
*/
protected static $modules = [
'package_manager',
'package_manager_bypass',
];
/**
* {@inheritdoc}
*/
protected function setUp(): void {
parent::setUp();
$this->installConfig('package_manager');
}

Adam G-H
committed
/**
* {@inheritdoc}
*/
public function register(ContainerBuilder $container) {
parent::register($container);
$this->disableValidators($container);
}
/**
* Disables any validators that will interfere with this test.
*/
protected function disableValidators(ContainerBuilder $container): void {
// Disable the filesystem permissions validator, since we cannot guarantee
// that the current code base will be writable in all testing situations.
// We test this validator functionally in Automatic Updates' build tests,
// since those do give us control over the filesystem permissions.
// @see \Drupal\Tests\automatic_updates\Build\CoreUpdateTest::assertReadOnlyFileSystemError()
// @see \Drupal\Tests\package_manager\Kernel\WritableFileSystemValidatorTest
$container->removeDefinition('package_manager.validator.file_system');
}

Adam G-H
committed
/**
* Creates a stage object for testing purposes.

Adam G-H
committed
*
* @return \Drupal\Tests\package_manager\Kernel\TestStage
* A stage object, with test-only modifications.

Adam G-H
committed
*/
protected function createStage(): TestStage {
return new TestStage(

Adam G-H
committed
$this->container->get('package_manager.path_locator'),
$this->container->get('package_manager.beginner'),
$this->container->get('package_manager.stager'),
$this->container->get('package_manager.committer'),

Ted Bowman
committed
$this->container->get('file_system'),

Adam G-H
committed
$this->container->get('event_dispatcher'),

Ted Bowman
committed
$this->container->get('tempstore.shared')

Adam G-H
committed
);
}
/**
* Asserts validation results are returned from a stage life cycle event.
*
* @param \Drupal\package_manager\ValidationResult[] $expected_results
* The expected validation results.
* @param string|null $event_class
* (optional) The class of the event which should return the results. Must
* be passed if $expected_results is not empty.
*/
protected function assertResults(array $expected_results, string $event_class = NULL): void {
$stage = $this->createStage();

Adam G-H
committed
try {
$stage->create();
$stage->require(['drupal/core:9.8.1']);
$stage->apply();
$stage->destroy();
// If we did not get an exception, ensure we didn't expect any results.
$this->assertEmpty($expected_results);

Adam G-H
committed
}
catch (StageValidationException $e) {
$this->assertNotEmpty($expected_results);

Adam G-H
committed
$this->assertValidationResultsEqual($expected_results, $e->getResults());
// TestStage::dispatch() attaches the event object to the exception so
// that we can analyze it.
$this->assertNotEmpty($event_class);

Adam G-H
committed
$this->assertInstanceOf($event_class, $e->event);
}
}

Ted Bowman
committed
/**
* Marks all pending post-update functions as completed.
*
* Since kernel tests don't normally install modules and register their
* updates, this method makes sure that we are testing from a clean, fully
* up-to-date state.
*/
protected function registerPostUpdateFunctions(): void {
$updates = $this->container->get('update.post_update_registry')
->getPendingUpdateFunctions();
$this->container->get('keyvalue')
->get('post_update')
->set('existing_updates', $updates);
}
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
/**
* Creates a test project in a virtual file system.
*
* This will create two directories at the root of the virtual file system:
* 'active', which is the active directory containing a fake Drupal code base,
* and 'stage', which is the root directory used to stage changes. The path
* locator service will also be mocked so that it points to the test project.
*/
protected function createTestProject(): void {
$tree = [
'active' => [
'composer.json' => '{}',
'composer.lock' => '{}',
'.git' => [
'ignore.txt' => 'This file should never be staged.',
],
'.gitignore' => 'This file should be staged.',
'private' => [
'ignore.txt' => 'This file should never be staged.',
],
'modules' => [
'example' => [
'example.info.yml' => 'This file should be staged.',
'.git' => [
'ignore.txt' => 'This file should never be staged.',
],
],
],
'sites' => [
'default' => [
'services.yml' => <<<END
# This file should never be staged.
must_not_be: 'empty'
END,
'settings.local.php' => <<<END
<?php
/**
* @file
* This file should never be staged.
*/
END,
'settings.php' => <<<END
<?php
/**
* @file
* This file should never be staged.
*/
END,
'stage.txt' => 'This file should be staged.',
],
'example.com' => [
'files' => [
'ignore.txt' => 'This file should never be staged.',
],
'db.sqlite' => 'This file should never be staged.',
'db.sqlite-shm' => 'This file should never be staged.',
'db.sqlite-wal' => 'This file should never be staged.',
'services.yml' => <<<END
# This file should never be staged.
key: "value"
END,
'settings.local.php' => <<<END
<?php
/**
* @file
* This file should never be staged.
*/
END,
'settings.php' => <<<END
<?php
/**
* @file
* This file should never be staged.
*/
END,
],
'simpletest' => [
'ignore.txt' => 'This file should never be staged.',
],
],
'vendor' => [
'.htaccess' => '# This file should never be staged.',
'web.config' => 'This file should never be staged.',
],
],
'stage' => [],
];
$root = vfsStream::create($tree, $this->vfsRoot)->url();
$active_dir = "$root/active";
TestStage::$stagingRoot = "$root/stage";
$path_locator = $this->prophesize(PathLocator::class);
$path_locator->getProjectRoot()->willReturn($active_dir);
$path_locator->getWebRoot()->willReturn('');
$path_locator->getVendorDirectory()->willReturn("$active_dir/vendor");
// We won't need the prophet anymore.
$path_locator = $path_locator->reveal();
$this->container->set('package_manager.path_locator', $path_locator);
// Since the path locator now points to a virtual file system, we need to
// replace the disk space validator with a test-only version that bypasses
// system calls, like disk_free_space() and stat(), which aren't supported
// by vfsStream.
$validator = new TestDiskSpaceValidator(
$this->container->get('package_manager.path_locator'),
$this->container->get('string_translation')
);
// By default, the validator should report that the root, vendor, and
// temporary directories have basically infinite free space.
$validator->freeSpace = [
$path_locator->getProjectRoot() => PHP_INT_MAX,
$path_locator->getVendorDirectory() => PHP_INT_MAX,
$validator->temporaryDirectory() => PHP_INT_MAX,
];
$this->container->set('package_manager.validator.disk_space', $validator);
}

Adam G-H
committed
}
/**
* Defines a stage specifically for testing purposes.
*/
class TestStage extends Stage {
/**
* The directory where staging areas will be created.
*
* @var string
*/
public static $stagingRoot;
/**
* {@inheritdoc}
*/
protected static function getStagingRoot(): string {
return static::$stagingRoot ?: parent::getStagingRoot();
}

Adam G-H
committed
/**
* {@inheritdoc}
*/
protected function dispatch(StageEvent $event): void {
try {
parent::dispatch($event);
}
catch (StageException $e) {
// Attach the event object to the exception so that test code can verify
// that the exception was thrown when a specific event was dispatched.
$e->event = $event;
throw $e;
}
}
}
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
/**
* A test version of the disk space validator to bypass system-level functions.
*/
class TestDiskSpaceValidator extends DiskSpaceValidator {
/**
* Whether the root and vendor directories are on the same logical disk.
*
* @var bool
*/
public $sharedDisk = TRUE;
/**
* The amount of free space, keyed by path.
*
* @var float[]
*/
public $freeSpace = [];
/**
* {@inheritdoc}
*/
protected function stat(string $path): array {
return [
'dev' => $this->sharedDisk ? 'disk' : uniqid(),
];
}
/**
* {@inheritdoc}
*/
protected function freeSpace(string $path): float {
return $this->freeSpace[$path];
}
/**
* {@inheritdoc}
*/
public function temporaryDirectory(): string {
return 'temp';
}
}