Skip to content
Snippets Groups Projects
Commit c5a246bf authored by Kunal Sachdev's avatar Kunal Sachdev Committed by Ted Bowman
Browse files

Issue #3252299 by kunal.sachdev, phenaproxima, tedbow, Wim Leers: Reliably...

Issue #3252299 by kunal.sachdev, phenaproxima, tedbow, Wim Leers: Reliably support cweagans/composer-patches in Package Manager & Automatic Updates: validate stage
parent 8171813d
No related branches found
No related tags found
1 merge request!666Issue #3252299: Reliably support cweagans/composer-patches in Package Manager & Automatic Updates: validate stage + test coverage to verify patches can be applied
Showing
with 447 additions and 73 deletions
......@@ -33,7 +33,7 @@ function package_manager_help($route_name, RouteMatchInterface $route_match) {
$output .= '<ul>';
$output .= ' <li>' . t('It does not support Drupal multi-site installations.') . '</li>';
$output .= ' <li>' . t('It does not support symlinks. If you have any, see <a href="#package-manager-faq-composer-not-found">What if it says I have symlinks in my codebase?</a>.') . '</li>';
$output .= ' <li>' . t('It only allows supported composer plugins. If you have any, see <a href="#package-manager-faq-unsupported-composer-plugin">What if it says I have unsupported composer plugins in my codebase?</a>.') . '</li>';
$output .= ' <li>' . t('It only allows supported Composer plugins. If you have any, see <a href="#package-manager-faq-unsupported-composer-plugin">What if it says I have unsupported Composer plugins in my codebase?</a>.') . '</li>';
$output .= ' <li>' . t('It does not automatically perform version control operations, e.g., with Git. Site administrators are responsible for committing updates.') . '</li>';
$output .= ' <li>' . t('It can only maintain one copy of the site at any given time. If a copy of the site already exists, another one cannot be created until the existing copy is destroyed.') . '</li>';
$output .= ' <li>' . t('It associates the temporary copy of the site with the user or session that originally created it, and only that user or session can make changes to it.') . '</li>';
......@@ -81,11 +81,22 @@ function package_manager_help($route_name, RouteMatchInterface $route_match) {
$output .= '<p>' . t('The new configuration will take effect on the next Composer install or update event. Do this to apply it immediately:') . '</p>';
$output .= '<pre><code>composer install</code></pre>';
$output .= '<h4 id="package-manager-faq-unsupported-composer-plugin">' . t('What if it says I have unsupported composer plugins in my codebase?') . '</h4>';
$output .= '<p>' . t('A fresh Drupal installation only uses supported composer plugins, but some modules or themes may depend on additional composer plugins. Please <a href=":new-issue">create a new issue</a> when you encounter this.', [
$output .= '<h4 id="package-manager-faq-unsupported-composer-plugin">' . t('What if it says I have unsupported Composer plugins in my codebase?') . '</h4>';
$output .= '<p>' . t('A fresh Drupal installation only uses supported Composer plugins, but some modules or themes may depend on additional Composer plugins. Please <a href=":new-issue">create a new issue</a> when you encounter this.', [
':new-issue' => 'https://www.drupal.org/node/add/project-issue/automatic_updates',
]) . '</p>';
$output .= '<p>' . t('It is possible to <em>trust</em> additional composer plugins, but this requires significant expertise: understanding the code of that composer plugin, what the effects on the file system are and how it affects the Package Manager module. Some composer plugins could result in a broken site!') . '</p>';
$output .= '<p>' . t('It is possible to <em>trust</em> additional Composer plugins, but this requires significant expertise: understanding the code of that Composer plugin, what the effects on the file system are and how it affects the Package Manager module. Some Composer plugins could result in a broken site!') . '</p>';
$output .= '<h4 id="package-manager-faq-composer-patches-installed-or-removed">' . t('What if it says <code>cweagans/composer-patches</code> cannot be installed/removed?') . '</h4>';
$output .= '<p>' . t('Installation or removal of <code>cweagans/composer-patches</code> via Package Manager is not support it. You can install or remove it manually by running Composer commands in your site root.') . '</p>';
$output .= '<p>' . t('To install it:') . '</p>';
$output .= '<pre><code>composer require cweagans/composer-patches</code></pre>';
$output .= '<p>' . t('To remove it:') . '</p>';
$output .= '<pre><code>composer remove cweagans/composer-patches</code></pre>';
$output .= '<h4 id="package-manager-faq-composer-patches-not-a-root-dependency">' . t('What if it says <code>cweagans/composer-patches</code> must be a root dependency?') . '</h4>';
$output .= '<p>' . t('If <code>cweagans/composer-patches</code> is installed, it must be defined as a dependency of the main project (i.e., it must be listed in the <code>require</code> or <code>require-dev</code> section of <code>composer.json</code>). You can run the following command in your site root to add it as a dependency of the main project:') . '</p>';
$output .= "<pre><code>composer require cweagans/composer-patches</code></pre>";
$output .= '<h5>' . t('Custom code') . '</h5>';
$output .= '<p>' . t('Symlinks are seldom truly necessary and should be avoided in your own code. No solution currently exists to get around them--they must be removed in order to use Automatic Updates.') . '</p>';
......
......@@ -240,6 +240,8 @@ services:
- { name: event_subscriber }
package_manager.validator.patches:
class: Drupal\package_manager\Validator\ComposerPatchesValidator
arguments:
- '@module_handler'
tags:
- { name: event_subscriber }
package_manager.validator.supported_releases:
......
......@@ -4,7 +4,11 @@ declare(strict_types = 1);
namespace Drupal\package_manager\Validator;
use Drupal\Core\Extension\ModuleHandlerInterface;
use Drupal\Core\StringTranslation\StringTranslationTrait;
use Drupal\Core\StringTranslation\TranslatableMarkup;
use Drupal\Core\Url;
use Drupal\package_manager\ComposerUtility;
use Drupal\package_manager\Event\PreApplyEvent;
use Drupal\package_manager\Event\PreCreateEvent;
use Drupal\package_manager\Event\PreOperationStageEvent;
......@@ -13,30 +17,152 @@ use Symfony\Component\EventDispatcher\EventSubscriberInterface;
/**
* Validates the configuration of the cweagans/composer-patches plugin.
*
* To ensure that applied patches remain consistent between the active and
* stage directories, the following rules are enforced if the patcher is
* installed:
* - It must be installed in both places, or in neither of them. It can't, for
* example, be installed in the active directory but not the stage directory
* (or vice-versa).
* - It must be one of the project's direct runtime or dev dependencies.
* - It cannot be installed or removed by Package Manager. In other words, it
* must be added to the project at the command line by someone technical
* enough to install and configure it properly.
*
* @internal
* This is an internal part of Package Manager and may be changed or removed
* at any time without warning. External code should not interact with this
* class.
*/
class ComposerPatchesValidator implements EventSubscriberInterface {
final class ComposerPatchesValidator implements EventSubscriberInterface {
use StringTranslationTrait;
/**
* {@inheritdoc}
* The name of the plugin being analyzed.
*
* @var string
*/
private const PLUGIN_NAME = 'cweagans/composer-patches';
/**
* The module handler service.
*
* @var \Drupal\Core\Extension\ModuleHandlerInterface
*/
private ModuleHandlerInterface $moduleHandler;
/**
* Constructs a ComposerPatchesValidator object.
*
* @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
* The module handler service.
*/
public function __construct(ModuleHandlerInterface $module_handler) {
$this->moduleHandler = $module_handler;
}
/**
* Validates the status of the patcher plugin.
*
* @param \Drupal\package_manager\Event\PreOperationStageEvent $event
* The event object.
*/
public function validateStagePreOperation(PreOperationStageEvent $event): void {
public function validatePatcher(PreOperationStageEvent $event): void {
$messages = [];
$stage = $event->getStage();
$composer = $stage->getActiveComposer();
if (array_key_exists('cweagans/composer-patches', $composer->getInstalledPackages())) {
$composer = $composer->getComposer();
$extra = $composer->getPackage()->getExtra();
if (empty($extra['composer-exit-on-patch-failure'])) {
$event->addError([
$this->t('The <code>cweagans/composer-patches</code> plugin is installed, but the <code>composer-exit-on-patch-failure</code> key is not set to <code>true</code> in the <code>extra</code> section of @file.', [
'@file' => $composer->getConfig()->getConfigSource()->getName(),
]),
]);
[$plugin_installed_in_active, $is_active_root_requirement, $active_configuration_ok] = $this->computePatcherStatus($stage->getActiveComposer());
try {
[$plugin_installed_in_stage, $is_stage_root_requirement, $stage_configuration_ok] = $this->computePatcherStatus($stage->getStageComposer());
$has_staged_update = TRUE;
}
catch (\LogicException $e) {
// No staged update exists.
$has_staged_update = FALSE;
}
// If there's a staged update and the patcher has been installed or removed
// in the stage directory, that's a problem.
if ($has_staged_update && $plugin_installed_in_active !== $plugin_installed_in_stage) {
if ($plugin_installed_in_stage) {
$message = $this->t('It cannot be installed by Package Manager.');
}
else {
$message = $this->t('It cannot be removed by Package Manager.');
}
$messages[] = $this->createErrorMessage($message, 'package-manager-faq-composer-patches-installed-or-removed');
}
// If the patcher is not listed in the runtime or dev dependencies, that's
// an error as well.
if (($plugin_installed_in_active && !$is_active_root_requirement) || ($has_staged_update && $plugin_installed_in_stage && !$is_stage_root_requirement)) {
$messages[] = $this->createErrorMessage($this->t('It must be a root dependency.'), 'package-manager-faq-composer-patches-not-a-root-dependency');
}
// If the plugin is misconfigured in either the active or stage directories,
// flag an error.
if (($plugin_installed_in_active && !$active_configuration_ok) || ($has_staged_update && $plugin_installed_in_stage && !$stage_configuration_ok)) {
$messages[] = $this->t('The <code>composer-exit-on-patch-failure</code> key is not set to <code>true</code> in the <code>extra</code> section of <code>composer.json</code>.');
}
if ($messages) {
$summary = $this->t("Problems detected related to the Composer plugin <code>@plugin</code>.", [
'@plugin' => static::PLUGIN_NAME,
]);
$event->addError($messages, $summary);
}
}
/**
* Appends a link to online help to an error message.
*
* @param \Drupal\Core\StringTranslation\TranslatableMarkup $message
* The error message.
* @param string $fragment
* The fragment of the online help to link to.
*
* @return \Drupal\Core\StringTranslation\TranslatableMarkup
* The final, translated error message.
*/
private function createErrorMessage(TranslatableMarkup $message, string $fragment): TranslatableMarkup {
if ($this->moduleHandler->moduleExists('help')) {
$url = Url::fromRoute('help.page', ['name' => 'package_manager'])
->setOption('fragment', $fragment)
->toString();
return $this->t('@message See <a href=":url">the help page</a> for information on how to resolve the problem.', [
'@message' => $message,
':url' => $url,
]);
}
return $message;
}
/**
* Computes the status of the patcher plugin in a particular directory.
*
* @param \Drupal\package_manager\ComposerUtility $composer
* A Composer utility for a specific directory.
*
* @return bool[]
* An indexed array containing three booleans, in order:
* - Whether the patcher plugin is installed.
* - Whether the patcher plugin is a root requirement in composer.json (in
* either the runtime or dev dependencies).
* - Whether the `composer-exit-on-patch-failure` flag is set in the `extra`
* section of composer.json.
*/
private function computePatcherStatus(ComposerUtility $composer): array {
$is_installed = array_key_exists(static::PLUGIN_NAME, $composer->getInstalledPackages());
$root_package = $composer->getComposer()->getPackage();
$is_root_requirement = array_key_exists(static::PLUGIN_NAME, $root_package->getRequires()) || array_key_exists(static::PLUGIN_NAME, $root_package->getDevRequires());
$extra = $root_package->getExtra();
$exit_on_failure = !empty($extra['composer-exit-on-patch-failure']);
return [$is_installed, $is_root_requirement, $exit_on_failure];
}
/**
......@@ -44,9 +170,9 @@ class ComposerPatchesValidator implements EventSubscriberInterface {
*/
public static function getSubscribedEvents(): array {
return [
PreCreateEvent::class => 'validateStagePreOperation',
PreApplyEvent::class => 'validateStagePreOperation',
StatusCheckEvent::class => 'validateStagePreOperation',
PreCreateEvent::class => 'validatePatcher',
PreApplyEvent::class => 'validatePatcher',
StatusCheckEvent::class => 'validatePatcher',
];
}
......
......@@ -45,6 +45,14 @@
"options": {
"symlink": false
}
},
"cweagans/composer-patches": {
"type": "path",
"version": "24.12.1999",
"url": "../path_repos/cweagans--composer-patches",
"options": {
"symlink": false
}
}
},
"minimum-stability": "stable",
......
{
"name": "cweagans/composer-patches",
"type": "composer-plugin",
"extra": {
"class": "\\cweagans\\Fake\\ComposerPatches"
},
"require": {
"composer-plugin-api": "^1.0 || ^2.0"
},
"autoload": {
"psr-4": {"cweagans\\Fake\\": "src"}
}
}
<?php
namespace cweagans\Fake;
use Composer\Composer;
use Composer\IO\IOInterface;
use Composer\Plugin\PluginInterface;
/**
* Dummy composer plugin implementation.
*/
class ComposerPatches implements PluginInterface {
/**
* {@inheritdoc}
*/
public function activate(Composer $composer, IOInterface $io) {}
/**
* {@inheritdoc}
*/
public function deactivate(Composer $composer, IOInterface $io) {}
/**
* {@inheritdoc}
*/
public function uninstall(Composer $composer, IOInterface $io) {}
}
......@@ -4,10 +4,12 @@ declare(strict_types = 1);
namespace Drupal\Tests\package_manager\Kernel;
use Drupal\Core\Url;
use Drupal\fixture_manipulator\ActiveFixtureManipulator;
use Drupal\package_manager\Event\PreApplyEvent;
use Drupal\package_manager\Event\PreCreateEvent;
use Drupal\package_manager\Event\StatusCheckEvent;
use Drupal\package_manager\ValidationResult;
use Symfony\Component\Process\Process;
/**
* @covers \Drupal\package_manager\Validator\ComposerPatchesValidator
......@@ -16,63 +18,227 @@ use Drupal\package_manager\ValidationResult;
*/
class ComposerPatchesValidatorTest extends PackageManagerKernelTestBase {
/**
* Data provider for testErrorDuringPreCreate().
*
* @return mixed[][]
* The test cases.
*/
public function providerPatcherConfiguration(): array {
return [
'exit-on-patch-failure missing' => [
FALSE,
[
ValidationResult::createError([
t('The <code>composer-exit-on-patch-failure</code> key is not set to <code>true</code> in the <code>extra</code> section of <code>composer.json</code>.'),
], t('Problems detected related to the Composer plugin <code>cweagans/composer-patches</code>.')),
],
],
'exit-on-patch-failure set' => [
TRUE,
[],
],
];
}
/**
* Tests that the patcher configuration is validated during pre-create.
*
* @param bool $extra_key_set
* Whether to set key in extra part of root package.
* @param \Drupal\package_manager\ValidationResult[] $expected_results
* The expected validation results.
*
* @dataProvider providerPatcherConfiguration()
*/
public function testError(): void {
// Simulate an active directory where the patcher is installed, but there's
// no composer-exit-on-patch-failure flag.
$dir = $this->container->get('package_manager.path_locator')
->getProjectRoot();
$this->installPatcherInActive($dir);
// Because ComposerUtility reads composer.json and passes it to the Composer
// factory as an array, Composer will assume that the configuration is
// coming from a config.json file, even if one doesn't exist.
$error = ValidationResult::createError([
t('The <code>cweagans/composer-patches</code> plugin is installed, but the <code>composer-exit-on-patch-failure</code> key is not set to <code>true</code> in the <code>extra</code> section of @dir/composer.json.', [
'@dir' => realpath($dir),
]),
]);
$this->assertStatusCheckResults([$error]);
$this->assertResults([$error], PreCreateEvent::class);
public function testPatcherConfiguration(bool $extra_key_set, array $expected_results): void {
$this->addPatcherToAllowedPlugins();
$this->setRootRequires();
if ($extra_key_set) {
$this->setRootExtra();
}
$this->assertStatusCheckResults($expected_results);
$this->assertResults($expected_results, PreCreateEvent::class);
}
/**
* Tests that the patcher configuration is validated during pre-apply.
* Data provider for testErrorDuringPreApply() and testHelpLink().
*
* @return mixed[][]
* The test cases.
*/
public function testErrorDuringPreApply(): void {
// Simulate an active directory where the patcher is installed, but there's
// no composer-exit-on-patch-failure flag.
$dir = $this->container->get('package_manager.path_locator')
->getProjectRoot();
$this->addEventTestListener(function () use ($dir): void {
$this->installPatcherInActive($dir);
});
// Because ComposerUtility reads composer.json and passes it to the Composer
// factory as an array, Composer will assume that the configuration is
// coming from a config.json file, even if one doesn't exist.
$error = ValidationResult::createError([
"The <code>cweagans/composer-patches</code> plugin is installed, but the <code>composer-exit-on-patch-failure</code> key is not set to <code>true</code> in the <code>extra</code> section of $dir/composer.json.",
]);
$this->assertResults([$error], PreApplyEvent::class);
public function providerErrorDuringPreApply(): array {
$summary = t('Problems detected related to the Composer plugin <code>cweagans/composer-patches</code>.');
return [
'composer-patches present in stage, but not present in active' => [
FALSE,
TRUE,
[
ValidationResult::createError([
t('It cannot be installed by Package Manager.'),
t('It must be a root dependency.'),
t('The <code>composer-exit-on-patch-failure</code> key is not set to <code>true</code> in the <code>extra</code> section of <code>composer.json</code>.'),
], $summary),
],
[
'package-manager-faq-composer-patches-installed-or-removed',
'package-manager-faq-composer-patches-not-a-root-dependency',
NULL,
],
],
'composer-patches removed in stage, but present in active' => [
TRUE,
FALSE,
[
ValidationResult::createError([
t('It cannot be removed by Package Manager.'),
], $summary),
],
[
'package-manager-faq-composer-patches-installed-or-removed',
],
],
'composer-patches present in stage and active' => [
TRUE,
TRUE,
[],
[],
],
'composer-patches not present in stage and active' => [
FALSE,
FALSE,
[],
[],
],
];
}
/**
* Simulates that the patcher is installed in the active directory.
* Tests the patcher's presence and configuration are validated on pre-apply.
*
* @param string $dir
* The active directory.
* @param bool $in_active
* Whether patcher is installed in active.
* @param bool $in_stage
* Whether patcher is installed in stage.
* @param \Drupal\package_manager\ValidationResult[] $expected_results
* The expected validation results.
*
* @dataProvider providerErrorDuringPreApply
*/
private function installPatcherInActive(string $dir): void {
public function testErrorDuringPreApply(bool $in_active, bool $in_stage, array $expected_results): void {
if ($in_active) {
// Add patcher as a root dependency and set
// `composer-exit-on-patch-failure` to true.
$this->addPatcherToAllowedPlugins();
$this->setRootRequires();
$this->setRootExtra();
}
if ($in_stage && !$in_active) {
// Simulate a stage directory where the patcher is installed.
$this->getStageFixtureManipulator()
->addPackage([
'name' => 'cweagans/composer-patches',
'version' => '24.12.1999',
'type' => 'composer-plugin',
]);
}
if (!$in_stage && $in_active) {
$this->getStageFixtureManipulator()
->removePackage('cweagans/composer-patches');
}
$stage = $this->createStage();
$stage->create();
$stage_dir = $stage->getStageDirectory();
$stage->require(['drupal/core:9.8.1']);
$event = new StatusCheckEvent($stage, []);
$this->container->get('event_dispatcher')->dispatch($event);
$this->assertValidationResultsEqual($expected_results, $event->getResults(), NULL, $stage_dir);
try {
$stage->apply();
// If we didn't get an exception, ensure we didn't expect any errors
$this->assertSame([], $expected_results);
}
catch (TestStageValidationException $e) {
$this->assertNotEmpty($expected_results);
$this->assertValidationResultsEqual($expected_results, $e->getResults(), NULL, $stage_dir);
}
}
/**
* Tests that validation errors can carry links to help.
*
* @param bool $in_active
* Whether patcher is installed in active.
* @param bool $in_stage
* Whether patcher is installed in stage.
* @param \Drupal\package_manager\ValidationResult[] $expected_results
* The expected validation results.
* @param string[] $help_page_sections
* An associative array of fragments (anchors) in the online help. The keys
* should be the numeric indices of the validation result messages which
* should link to those fragments.
*
* @dataProvider providerErrorDuringPreApply
*/
public function testErrorDuringPreApplyWithHelp(bool $in_active, bool $in_stage, array $expected_results, array $help_page_sections): void {
$this->enableModules(['help']);
foreach ($expected_results as $result_index => $result) {
$messages = $result->getMessages();
foreach ($messages as $message_index => $message) {
if ($help_page_sections[$message_index]) {
// Get the link to the online documentation for the error message.
$url = Url::fromRoute('help.page', ['name' => 'package_manager'])
->setOption('fragment', $help_page_sections[$message_index])
->toString();
// Reformat the provided results so that they all have the link to the
// online documentation appended to them.
$messages[$message_index] = $message . ' See <a href="' . $url . '">the help page</a> for information on how to resolve the problem.';
}
}
$expected_results[$result_index] = ValidationResult::createError($messages, $result->getSummary());
}
$this->testErrorDuringPreApply($in_active, $in_stage, $expected_results);
}
/**
* Add the installed patcher to allowed plugins.
*/
private function addPatcherToAllowedPlugins(): void {
(new ActiveFixtureManipulator())
->addPackage([
'name' => 'cweagans/composer-patches',
'version' => '1.0.0',
'type' => 'composer-plugin',
])->commitChanges();
->addConfig([
'allow-plugins' => [
'cweagans/composer-patches' => TRUE,
],
])
->commitChanges();
}
/**
* Sets the cweagans/composer-patches as required package for root package.
*/
private function setRootRequires(): void {
$process = new Process(
['composer', 'require', "cweagans/composer-patches:@dev"],
$this->container->get('package_manager.path_locator')->getProjectRoot()
);
$process->mustRun();
}
/**
* Sets the composer-exit-on-patch-failure key in extra part of root package.
*/
private function setRootExtra(): void {
$process = new Process(
['composer', 'config', 'extra.composer-exit-on-patch-failure', 'true'],
$this->container->get('package_manager.path_locator')->getProjectRoot()
);
$process->mustRun();
}
}
......@@ -245,6 +245,12 @@ abstract class PackageManagerKernelTestBase extends KernelTestBase {
$this->assertTrue(mkdir($active_dir));
static::copyFixtureFilesTo($source_dir, $active_dir);
// Make sure that the path repositories exist in the test project too.
(new Filesystem())->mirror(__DIR__ . '/../../fixtures/path_repos', $root . DIRECTORY_SEPARATOR . 'path_repos', NULL, [
'override' => TRUE,
'delete' => FALSE,
]);
// Removing 'vfs://root/' from site path set in
// \Drupal\KernelTests\KernelTestBase::setUpFilesystem as we don't use vfs.
$test_site_path = str_replace('vfs://root/', '', $this->siteDirectory);
......
......@@ -28,14 +28,16 @@ trait ValidationTestTrait {
* The actual validation results.
* @param \Drupal\package_manager\PathLocator|null $path_locator
* (optional) The path locator (when this trait is used in unit tests).
* @param string|null $stage_dir
* (optional) The stage directory.
*/
protected function assertValidationResultsEqual(array $expected_results, array $actual_results, ?PathLocator $path_locator = NULL): void {
protected function assertValidationResultsEqual(array $expected_results, array $actual_results, ?PathLocator $path_locator = NULL, ?string $stage_dir = NULL): void {
if ($path_locator) {
assert(is_a(get_called_class(), UnitTestCase::class, TRUE));
}
$expected_results = array_map(
function (array $result) use ($path_locator): array {
$result['messages'] = $this->resolvePlaceholdersInArrayValuesWithRealPaths($result['messages'], $path_locator);
function (array $result) use ($path_locator, $stage_dir): array {
$result['messages'] = $this->resolvePlaceholdersInArrayValuesWithRealPaths($result['messages'], $path_locator, $stage_dir);
return $result;
},
$this->getValidationResultsAsArray($expected_results)
......@@ -54,19 +56,30 @@ trait ValidationTestTrait {
* <STAGE_ROOT_PARENT>.
* @param \Drupal\package_manager\PathLocator|null $path_locator
* (optional) The path locator (when this trait is used in unit tests).
* @param string|null $stage_dir
* (optional) The stage directory.
*
* @return array
* The same array, with unchanged keys, and with the placeholders resolved.
*/
protected function resolvePlaceholdersInArrayValuesWithRealPaths(array $subject, ?PathLocator $path_locator = NULL): array {
protected function resolvePlaceholdersInArrayValuesWithRealPaths(array $subject, ?PathLocator $path_locator = NULL, ?string $stage_dir = NULL): array {
if (!$path_locator) {
$path_locator = $this->container->get('package_manager.path_locator');
}
return str_replace(
$subject = str_replace(
['<PROJECT_ROOT>', '<VENDOR_DIR>', '<STAGE_ROOT>', '<STAGE_ROOT_PARENT>'],
[$path_locator->getProjectRoot(), $path_locator->getVendorDirectory(), $path_locator->getStagingRoot(), dirname($path_locator->getStagingRoot())],
$subject
);
if ($stage_dir) {
$subject = str_replace(['<STAGE_DIR>'], [$stage_dir], $subject);
}
foreach ($subject as $message) {
if (str_contains($message, '<STAGE_DIR>')) {
throw new \LogicException("No stage directory passed to replace '<STAGE_DIR>' in message '$message'");
}
}
return $subject;
}
/**
......
......@@ -42,7 +42,7 @@ class ScaffoldFilePermissionsValidatorTest extends AutomaticUpdatesKernelTestBas
/**
* {@inheritdoc}
*/
protected function assertValidationResultsEqual(array $expected_results, array $actual_results, ?PathLocator $path_locator = NULL): void {
protected function assertValidationResultsEqual(array $expected_results, array $actual_results, ?PathLocator $path_locator = NULL, ?string $stage_dir = NULL): void {
$map = function (string $path): string {
return $this->activeDir . '/' . $path;
};
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment