diff --git a/src/Capability/BaseResolverProvider.php b/src/Capability/BaseResolverProvider.php
new file mode 100644
index 00000000..cf8c539e
--- /dev/null
+++ b/src/Capability/BaseResolverProvider.php
@@ -0,0 +1,46 @@
+composer = $args['composer'];
+ $this->io = $args['io'];
+ $this->plugin = $args['plugin'];
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ abstract public function getResolvers();
+}
diff --git a/src/Capability/CoreResolverProvider.php b/src/Capability/CoreResolverProvider.php
new file mode 100644
index 00000000..c6ce3aa2
--- /dev/null
+++ b/src/Capability/CoreResolverProvider.php
@@ -0,0 +1,22 @@
+composer, $this->io),
+ new PatchesFile($this->composer, $this->io),
+ new DependencyPatches($this->composer, $this->io),
+ ];
+ }
+}
diff --git a/src/Capability/ResolverProvider.php b/src/Capability/ResolverProvider.php
new file mode 100644
index 00000000..189e24be
--- /dev/null
+++ b/src/Capability/ResolverProvider.php
@@ -0,0 +1,22 @@
+executor = new ProcessExecutor($this->io);
$this->patches = array();
$this->installedPatches = array();
+ $this->patchesResolved = false;
+ $this->patchCollection = new PatchCollection();
$this->configuration = [
'exit-on-patch-failure' => [
@@ -84,9 +102,9 @@ public function activate(Composer $composer, IOInterface $io)
'type' => 'bool',
'default' => false,
],
- 'disable-patching-from-dependencies' => [
- 'type' => 'bool',
- 'default' => false,
+ 'disable-resolvers' => [
+ 'type' => 'list',
+ 'default' => [],
],
'disable-patch-reports' => [
'type' => 'bool',
@@ -110,10 +128,10 @@ public function activate(Composer $composer, IOInterface $io)
public static function getSubscribedEvents()
{
return array(
- ScriptEvents::PRE_INSTALL_CMD => array('checkPatches'),
- ScriptEvents::PRE_UPDATE_CMD => array('checkPatches'),
- PackageEvents::PRE_PACKAGE_INSTALL => array('gatherPatches'),
- PackageEvents::PRE_PACKAGE_UPDATE => array('gatherPatches'),
+// ScriptEvents::PRE_INSTALL_CMD => array('checkPatches'),
+// ScriptEvents::PRE_UPDATE_CMD => array('checkPatches'),
+ PackageEvents::PRE_PACKAGE_INSTALL => array('resolvePatches'),
+ PackageEvents::PRE_PACKAGE_UPDATE => array('resolvePatches'),
// The following is a higher weight for compatibility with
// https://github.com/AydinHassan/magento-core-composer-installer and
// more generally for compatibility with any Composer plugin which
@@ -126,6 +144,87 @@ public static function getSubscribedEvents()
);
}
+ /**
+ * Return a list of plugin capabilities.
+ *
+ * @return array
+ */
+ public function getCapabilities()
+ {
+ return [
+ 'cweagans\Composer\Capability\ResolverProvider' => 'cweagans\Composer\Capability\CoreResolverProvider',
+ ];
+ }
+
+ /**
+ * Gather a list of all patch resolvers from all enabled Composer plugins.
+ *
+ * @return ResolverBase[]
+ * A list of PatchResolvers to be run.
+ */
+ public function getPatchResolvers()
+ {
+ $resolvers = [];
+ $plugin_manager = $this->composer->getPluginManager();
+ foreach ($plugin_manager->getPluginCapabilities(
+ 'cweagans\Composer\Capability\ResolverProvider',
+ ['composer' => $this->composer, 'io' => $this->io]
+ ) as $capability) {
+ /** @var ResolverProvider $capability */
+ $newResolvers = $capability->getResolvers();
+ if (!is_array($newResolvers)) {
+ throw new \UnexpectedValueException(
+ 'Plugin capability ' . get_class($capability) . ' failed to return an array from getResolvers().'
+ );
+ }
+ foreach ($newResolvers as $resolver) {
+ if (!$resolver instanceof ResolverBase) {
+ throw new \UnexpectedValueException(
+ 'Plugin capability ' . get_class($capability) . ' returned an invalid value.'
+ );
+ }
+ }
+ $resolvers = array_merge($resolvers, $newResolvers);
+ }
+
+ return $resolvers;
+ }
+
+ /**
+ * Gather patches that need to be applied to the current set of packages.
+ *
+ * Note that this work is done unconditionally if this plugin is enabled,
+ * even if patching is disabled in any way. The point where patches are applied
+ * is where the work will be skipped. It's done this way to ensure that
+ * patching can be disabled temporarily in a way that doesn't affect the
+ * contents of composer.lock.
+ *
+ * @param PackageEvent $event
+ * The PackageEvent passed by Composer
+ */
+ public function resolvePatches(PackageEvent $event)
+ {
+ // No need to resolve patches more than once.
+ if ($this->patchesResolved) {
+ return;
+ }
+
+ // Let each resolver discover patches and add them to the PatchCollection.
+ /** @var ResolverInterface $resolver */
+ foreach ($this->getPatchResolvers() as $resolver) {
+ if (!in_array(get_class($resolver), $this->getConfig('disable-resolvers'))) {
+ $resolver->resolve($this->patchCollection, $event);
+ } else {
+ if ($this->io->isVerbose()) {
+ $this->io->write(' - Skipping resolver ' . get_class($resolver) . '');
+ }
+ }
+ }
+
+ // Make sure we only do this once.
+ $this->patchesResolved = true;
+ }
+
/**
* Before running composer install,
* @param Event $event
@@ -186,163 +285,6 @@ public function checkPatches(Event $event)
}
}
- /**
- * Gather patches from dependencies and store them for later use.
- *
- * @param PackageEvent $event
- */
- public function gatherPatches(PackageEvent $event)
- {
- // If we've already done this, then don't do it again.
- if (isset($this->patches['_patchesGathered'])) {
- $this->io->write('Patches already gathered. Skipping', true, IOInterface::VERBOSE);
- return;
- } // If patching has been disabled, bail out here.
- elseif (!$this->isPatchingEnabled()) {
- $this->io->write('Patching is disabled. Skipping.', true, IOInterface::VERBOSE);
- return;
- }
-
- $this->patches = $this->grabPatches();
- if (empty($this->patches)) {
- $this->io->write('No patches supplied.');
- }
-
- $extra = $this->composer->getPackage()->getExtra();
- $patches_ignore = isset($extra['patches-ignore']) ? $extra['patches-ignore'] : array();
-
- // Now add all the patches from dependencies that will be installed.
- if (!$this->getConfig('disable-patching-from-dependencies')) {
- $operations = $event->getOperations();
- $this->io->write('Gathering patches for dependencies. This might take a minute.');
- foreach ($operations as $operation) {
- if ($operation->getJobType() == 'install' || $operation->getJobType() == 'update') {
- $package = $this->getPackageFromOperation($operation);
- $extra = $package->getExtra();
- if (isset($extra['patches'])) {
- if (isset($patches_ignore[$package->getName()])) {
- foreach ($patches_ignore[$package->getName()] as $package_name => $patches) {
- if (isset($extra['patches'][$package_name])) {
- $extra['patches'][$package_name] = array_diff(
- $extra['patches'][$package_name],
- $patches
- );
- }
- }
- }
- $this->patches = Util::arrayMergeRecursiveDistinct($this->patches, $extra['patches']);
- }
- // Unset installed patches for this package
- if (isset($this->installedPatches[$package->getName()])) {
- unset($this->installedPatches[$package->getName()]);
- }
- }
- }
- }
-
- // Merge installed patches from dependencies that did not receive an update.
- foreach ($this->installedPatches as $patches) {
-// $this->patches = Util::arrayMergeRecursiveDistinct($this->patches, $patches);
- }
-
- // If we're in verbose mode, list the projects we're going to patch.
- if ($this->io->isVerbose()) {
- foreach ($this->patches as $package => $patches) {
- $number = count($patches);
- $this->io->write('Found ' . $number . ' patches for ' . $package . '.');
- }
- }
-
- // Make sure we don't gather patches again. Extra keys in $this->patches
- // won't hurt anything, so we'll just stash it there.
- $this->patches['_patchesGathered'] = true;
- }
-
- /**
- * Get the patches from root composer or external file
- * @return Patches
- * @throws \Exception
- */
- public function grabPatches()
- {
- // First, try to get the patches from the root composer.json.
- $patches = [];
- $extra = $this->composer->getPackage()->getExtra();
- if (isset($extra['patches'])) {
- $this->io->write('Gathering patches for root package.');
- $patches = $extra['patches'];
- } // If it's not specified there, look for a patches-file definition.
- elseif ($this->getConfig('patches-file') != '') {
- $this->io->write('Gathering patches from patch file.');
- $patches = file_get_contents($this->getConfig('patches-file'));
- $patches = json_decode($patches, true);
- $error = json_last_error();
- if ($error != 0) {
- switch ($error) {
- case JSON_ERROR_DEPTH:
- $msg = ' - Maximum stack depth exceeded';
- break;
- case JSON_ERROR_STATE_MISMATCH:
- $msg = ' - Underflow or the modes mismatch';
- break;
- case JSON_ERROR_CTRL_CHAR:
- $msg = ' - Unexpected control character found';
- break;
- case JSON_ERROR_SYNTAX:
- $msg = ' - Syntax error, malformed JSON';
- break;
- case JSON_ERROR_UTF8:
- $msg = ' - Malformed UTF-8 characters, possibly incorrectly encoded';
- break;
- default:
- $msg = ' - Unknown error';
- break;
- }
- throw new \Exception('There was an error in the supplied patches file:' . $msg);
- }
- if (isset($patches['patches'])) {
- $patches = $patches['patches'];
- } elseif (!$patches) {
- throw new \Exception('There was an error in the supplied patch file');
- }
- } else {
- return array();
- }
-
- // Now that we have a populated $patches list, populate patch objects for everything.
- foreach ($patches as $package => $patch_defs) {
- if (isset($patch_defs[0]) && is_array($patch_defs[0])) {
- $this->io->write("Using expanded definition format for package {$package}");
-
- foreach ($patch_defs as $index => $def) {
- $patch = new Patch();
- $patch->package = $package;
- $patch->url = $def["url"];
- $patch->description = $def["description"];
-
- $patches[$package][$index] = $patch;
- }
- } else {
- $this->io->write("Using compact definition format for package {$package}");
-
- $temporary_patch_list = [];
-
- foreach ($patch_defs as $description => $url) {
- $patch = new Patch();
- $patch->package = $package;
- $patch->url = $url;
- $patch->description = $description;
-
- $temporary_patch_list[] = $patch;
- }
-
- $patches[$package] = $temporary_patch_list;
- }
- }
-
- return $patches;
- }
-
/**
* @param PackageEvent $event
* @throws \Exception
@@ -355,7 +297,7 @@ public function postInstall(PackageEvent $event)
$package = $this->getPackageFromOperation($operation);
$package_name = $package->getName();
- if (!isset($this->patches[$package_name])) {
+ if (empty($this->patchCollection->getPatchesForPackage($package_name))) {
if ($this->io->isVerbose()) {
$this->io->write('No patches found for ' . $package_name . '.');
}
@@ -376,7 +318,7 @@ public function postInstall(PackageEvent $event)
$extra = $localPackage->getExtra();
$extra['patches_applied'] = array();
- foreach ($this->patches[$package_name] as $index => $patch) {
+ foreach ($this->patchCollection->getPatchesForPackage($package_name) as $patch) {
/** @var Patch $patch */
$this->io->write(' ' . $patch->url . ' (' . $patch->description . ')');
try {
@@ -404,7 +346,7 @@ public function postInstall(PackageEvent $event)
// $localPackage->setExtra($extra);
$this->io->write('');
- $this->writePatchReport($this->patches[$package_name], $install_path);
+ $this->writePatchReport($this->patchCollection->getPatchesForPackage($package_name), $install_path);
}
/**
diff --git a/src/Resolvers/DependencyPatches.php b/src/Resolvers/DependencyPatches.php
new file mode 100644
index 00000000..9ee63496
--- /dev/null
+++ b/src/Resolvers/DependencyPatches.php
@@ -0,0 +1,68 @@
+io->write(' - Gathering patches from dependencies.');
+
+ $operations = $event->getOperations();
+ foreach ($operations as $operation) {
+ if ($operation->getJobType() == 'install' || $operation->getJobType() == 'update') {
+ // @TODO handle exception.
+ $package = $this->getPackageFromOperation($operation);
+ /** @var PackageInterface $extra */
+ $extra = $package->getExtra();
+ if (isset($extra['patches'])) {
+ $patches = $this->findPatchesInJson($extra['patches']);
+ foreach ($patches as $package => $patch_list) {
+ foreach ($patch_list as $patch) {
+ $collection->addPatch($patch);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Get a Package object from an OperationInterface object.
+ *
+ * @param OperationInterface $operation
+ * @return PackageInterface
+ * @throws \Exception
+ *
+ * @todo Will this method ever get something other than an InstallOperation or UpdateOperation?
+ */
+ protected function getPackageFromOperation(OperationInterface $operation)
+ {
+ if ($operation instanceof InstallOperation) {
+ $package = $operation->getPackage();
+ } elseif ($operation instanceof UpdateOperation) {
+ $package = $operation->getTargetPackage();
+ } else {
+ throw new \Exception('Unknown operation: ' . get_class($operation));
+ }
+
+ return $package;
+ }
+}
diff --git a/src/Resolvers/PatchesFile.php b/src/Resolvers/PatchesFile.php
new file mode 100644
index 00000000..4e4de17d
--- /dev/null
+++ b/src/Resolvers/PatchesFile.php
@@ -0,0 +1,72 @@
+io->write(' - Gathering patches from patches file.');
+
+ $extra = $this->composer->getPackage()->getExtra();
+ $valid_patches_file = array_key_exists('patches-file', $extra) &&
+ file_exists(realpath($extra['patches-file'])) &&
+ is_readable(realpath($extra['patches-file']));
+
+ // If we don't have a valid patches file, exit early.
+ if (!$valid_patches_file) {
+ return;
+ }
+
+ $patches_file = $this->readPatchesFile($extra['patches-file']);
+
+ foreach ($this->findPatchesInJson($patches_file) as $package => $patches) {
+ foreach ($patches as $patch) {
+ /** @var Patch $patch */
+ $collection->addPatch($patch);
+ }
+ }
+ }
+
+ /**
+ * Read a patches file.
+ *
+ * @param $patches_file
+ * A URI to a file. Can be anything accepted by file_get_contents().
+ * @return array
+ * A list of patches.
+ * @throws \InvalidArgumentException
+ */
+ protected function readPatchesFile($patches_file)
+ {
+ $patches = file_get_contents($patches_file);
+ $patches = json_decode($patches, true);
+
+ // First, check for JSON syntax issues.
+ $json_error = json_last_error_msg();
+ if ($json_error != "No error") {
+ throw new \InvalidArgumentException($json_error);
+ }
+
+ // Next, make sure there is a patches key in the file.
+ if (!array_key_exists('patches', $patches)) {
+ throw new \InvalidArgumentException('No patches found.');
+ }
+
+ // If nothing is wrong at this point, we can return the list of patches.
+ return $patches['patches'];
+ }
+}
diff --git a/src/Resolvers/ResolverBase.php b/src/Resolvers/ResolverBase.php
new file mode 100644
index 00000000..8462cb00
--- /dev/null
+++ b/src/Resolvers/ResolverBase.php
@@ -0,0 +1,93 @@
+composer = $composer;
+ $this->io = $io;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ abstract public function resolve(PatchCollection $collection, PackageEvent $event);
+
+ /**
+ * Handles the different patch definition formats and returns a list of Patches.
+ *
+ * @param array $patches
+ * An array of patch defs from composer.json or a patches file.
+ *
+ * @return array $patches
+ * An array of Patch objects grouped by package name.
+ */
+ public function findPatchesInJson($patches)
+ {
+ // Given an array of patch data (pulled directly from the root composer.json
+ // or a patches file), figure out what patch format each package is using and
+ // marshall everything into Patch objects.
+ foreach ($patches as $package => $patch_defs) {
+ if (isset($patch_defs[0]) && is_array($patch_defs[0])) {
+ $this->io->write("Using expanded definition format for package {$package}");
+
+ foreach ($patch_defs as $index => $def) {
+ $patch = new Patch();
+ $patch->package = $package;
+ $patch->url = $def["url"];
+ $patch->description = $def["description"];
+
+ $patches[$package][$index] = $patch;
+ }
+ } else {
+ $this->io->write("Using compact definition format for package {$package}");
+
+ $temporary_patch_list = [];
+
+ foreach ($patch_defs as $description => $url) {
+ $patch = new Patch();
+ $patch->package = $package;
+ $patch->url = $url;
+ $patch->description = $description;
+
+ $temporary_patch_list[] = $patch;
+ }
+
+ $patches[$package] = $temporary_patch_list;
+ }
+ }
+
+ return $patches;
+ }
+}
diff --git a/src/Resolvers/ResolverInterface.php b/src/Resolvers/ResolverInterface.php
new file mode 100644
index 00000000..99d73cef
--- /dev/null
+++ b/src/Resolvers/ResolverInterface.php
@@ -0,0 +1,42 @@
+io->write(' - Gathering patches from root package');
+
+ $extra = $this->composer->getPackage()->getExtra();
+
+ if (!isset($extra['patches'])) {
+ return;
+ }
+
+ foreach ($this->findPatchesInJson($extra['patches']) as $package => $patches) {
+ foreach ($patches as $patch) {
+ /** @var Patch $patch */
+ $collection->addPatch($patch);
+ }
+ }
+ }
+}
diff --git a/tests/_data/dep-test-package/composer.json b/tests/_data/dep-test-package/composer.json
new file mode 100644
index 00000000..c1ad968a
--- /dev/null
+++ b/tests/_data/dep-test-package/composer.json
@@ -0,0 +1,16 @@
+{
+ "name": "cweagans/dep-test-package",
+ "description": "Project for use in cweagans/composer-patches acceptance tests.",
+ "type": "project",
+ "license": "BSD-2-Clause",
+ "require": {
+ "drupal/drupal": "8.2.0"
+ },
+ "extra": {
+ "patches": {
+ "drupal/drupal": {
+ "Add a startup config for the PHP web server": "https://www.drupal.org/files/issues/add_a_startup-1543858-58.patch"
+ }
+ }
+ }
+}
diff --git a/tests/_support/Helper/Acceptance.php b/tests/_support/Helper/Acceptance.php
index 2c811994..589f4ee0 100644
--- a/tests/_support/Helper/Acceptance.php
+++ b/tests/_support/Helper/Acceptance.php
@@ -20,8 +20,8 @@ public function _beforeSuite($settings = [])
$this->_afterSuite();
}
$filesystem->mkdir($this->_getPluginDir());
- $filesystem->mirror($this->_getProjectRoot() . '/src', $this->_getPluginDir() . '/src');
- $filesystem->copy($this->_getProjectRoot() . '/composer.json', $this->_getPluginDir() . '/composer.json');
+ $filesystem->symlink($this->_getProjectRoot() . '/src', $this->_getPluginDir() . '/src');
+ $filesystem->symlink($this->_getProjectRoot() . '/composer.json', $this->_getPluginDir() . '/composer.json');
}
public function _afterSuite()
diff --git a/tests/acceptance/ApplyPatchFromDependencyCept.php b/tests/acceptance/ApplyPatchFromDependencyCept.php
new file mode 100644
index 00000000..bda7cfb4
--- /dev/null
+++ b/tests/acceptance/ApplyPatchFromDependencyCept.php
@@ -0,0 +1,6 @@
+wantTo('apply a patch defined in a dependency');
+$I->amInPath(realpath(__DIR__ . '/fixtures/apply-patch-from-dependency'));
+$I->runShellCommand('composer install');
+$I->canSeeFileFound('./vendor/drupal/drupal/.ht.router.php');
diff --git a/tests/acceptance/DontApplyPatchFromDependencyCept.php b/tests/acceptance/DontApplyPatchFromDependencyCept.php
new file mode 100644
index 00000000..0c9278da
--- /dev/null
+++ b/tests/acceptance/DontApplyPatchFromDependencyCept.php
@@ -0,0 +1,6 @@
+wantTo('dont apply a patch defined in a dependency if the dependency patch resolver is disabled');
+$I->amInPath(realpath(__DIR__ . '/fixtures/dont-apply-patch-from-dependency'));
+$I->runShellCommand('composer install');
+$I->cantSeeFileFound('./vendor/drupal/drupal/.ht.router.php');
diff --git a/tests/acceptance/fixtures/apply-patch-from-dependency/composer.json b/tests/acceptance/fixtures/apply-patch-from-dependency/composer.json
new file mode 100644
index 00000000..cf161c75
--- /dev/null
+++ b/tests/acceptance/fixtures/apply-patch-from-dependency/composer.json
@@ -0,0 +1,20 @@
+{
+ "name": "cweagans/composer-patches-test-project",
+ "description": "Project for use in cweagans/composer-patches acceptance tests.",
+ "type": "project",
+ "license": "BSD-2-Clause",
+ "repositories": [
+ {
+ "type": "path",
+ "url": "../composer-patches"
+ },
+ {
+ "type": "path",
+ "url": "../../../_data/dep-test-package"
+ }
+ ],
+ "require": {
+ "cweagans/composer-patches": "*@dev",
+ "cweagans/dep-test-package": "*@dev"
+ }
+}
diff --git a/tests/acceptance/fixtures/dont-apply-patch-from-dependency/composer.json b/tests/acceptance/fixtures/dont-apply-patch-from-dependency/composer.json
new file mode 100644
index 00000000..5cf8ff4c
--- /dev/null
+++ b/tests/acceptance/fixtures/dont-apply-patch-from-dependency/composer.json
@@ -0,0 +1,25 @@
+{
+ "name": "cweagans/composer-patches-test-project",
+ "description": "Project for use in cweagans/composer-patches acceptance tests.",
+ "type": "project",
+ "license": "BSD-2-Clause",
+ "repositories": [
+ {
+ "type": "path",
+ "url": "../composer-patches"
+ },
+ {
+ "type": "path",
+ "url": "../../../_data/dep-test-package"
+ }
+ ],
+ "require": {
+ "cweagans/composer-patches": "*@dev",
+ "cweagans/dep-test-package": "*@dev"
+ },
+ "extra": {
+ "composer-patches": {
+ "disable-resolvers": ["cweagans\\Composer\\Resolvers\\DependencyPatches"]
+ }
+ }
+}
diff --git a/tests/acceptance/fixtures/dont-apply-patch-from-dependency/composer.phar b/tests/acceptance/fixtures/dont-apply-patch-from-dependency/composer.phar
new file mode 100644
index 00000000..4055d874
Binary files /dev/null and b/tests/acceptance/fixtures/dont-apply-patch-from-dependency/composer.phar differ
diff --git a/tests/unit/CoreResolverProviderTest.php b/tests/unit/CoreResolverProviderTest.php
new file mode 100644
index 00000000..3ad9a328
--- /dev/null
+++ b/tests/unit/CoreResolverProviderTest.php
@@ -0,0 +1,26 @@
+ Stub::make(\Composer\Composer::class),
+ 'io' => new \Composer\IO\NullIO(),
+ 'plugin' => Stub::makeEmpty(\Composer\Plugin\PluginInterface::class),
+ ]);
+
+ $resolvers = $resolverProvider->getResolvers();
+
+ $this->assertCount(3, $resolvers);
+ $this->assertInstanceOf(\cweagans\Composer\Resolvers\RootComposer::class, $resolvers[0]);
+ $this->assertInstanceOf(\cweagans\Composer\Resolvers\PatchesFile::class, $resolvers[1]);
+ $this->assertInstanceOf(\cweagans\Composer\Resolvers\DependencyPatches::class, $resolvers[2]);
+ }
+}
diff --git a/tests/unit/DependencyPatchesResolverTest.php b/tests/unit/DependencyPatchesResolverTest.php
new file mode 100644
index 00000000..13b122ca
--- /dev/null
+++ b/tests/unit/DependencyPatchesResolverTest.php
@@ -0,0 +1,42 @@
+setExtra(['patches' => []]);
+ $composer = new Composer();
+ $composer->setPackage($root_package);
+ $io = new NullIO();
+ $event = Stub::make(PackageEvent::class, [
+ 'getOperations' => function () {
+ return [];
+ },
+ ]);
+
+ // Empty patch list.
+ $resolver = new DependencyPatches($composer, $io);
+ $resolver->resolve($patch_collection, $event);
+ $this->assertCount(0, $patch_collection->getPatchesForPackage('test/package'));
+
+ // @TODO: Add operations to the event and test that the resolver finds patches appropriately.
+ }
+}
diff --git a/tests/unit/PatchesFileResolverTest.php b/tests/unit/PatchesFileResolverTest.php
new file mode 100644
index 00000000..83d77e2d
--- /dev/null
+++ b/tests/unit/PatchesFileResolverTest.php
@@ -0,0 +1,63 @@
+setExtra([
+ 'patches-file' => __DIR__ . '/../_data/dummyPatches.json',
+ ]);
+ $composer->setPackage($package);
+ $io = new NullIO();
+ $event = Stub::make(PackageEvent::class, []);
+
+ $collection = new PatchCollection();
+ $resolver = new PatchesFile($composer, $io);
+ $resolver->resolve($collection, $event);
+ $this->assertCount(2, $collection->getPatchesForPackage('test/package'));
+ $this->assertCount(2, $collection->getPatchesForPackage('test/package2'));
+
+ // Empty patches.
+ try {
+ $package = new RootPackage('test/package', '1.0.0.0', '1.0.0');
+ $package->setExtra([
+ 'patches-file' => __DIR__ . '/../_data/dummyPatchesEmpty.json',
+ ]);
+ $composer->setPackage($package);
+ $collection = new PatchCollection();
+ $resolver = new PatchesFile($composer, $io);
+ $resolver->resolve($collection, $event);
+ } catch (\InvalidArgumentException $e) {
+ $this->assertEquals('No patches found.', $e->getMessage());
+ }
+
+ // Invalid JSON.
+ try {
+ $package = new RootPackage('test/package', '1.0.0.0', '1.0.0');
+ $package->setExtra([
+ 'patches-file' => __DIR__ . '/../_data/dummyPatchesInvalid.json',
+ ]);
+ $composer->setPackage($package);
+ $collection = new PatchCollection();
+ $resolver = new PatchesFile($composer, $io);
+ $resolver->resolve($collection, $event);
+ } catch (\InvalidArgumentException $e) {
+ $this->assertEquals('Syntax error', $e->getMessage());
+ }
+ }
+}
diff --git a/tests/unit/RootComposerResolverTest.php b/tests/unit/RootComposerResolverTest.php
new file mode 100644
index 00000000..14b32991
--- /dev/null
+++ b/tests/unit/RootComposerResolverTest.php
@@ -0,0 +1,52 @@
+setExtra(['patches' => []]);
+ $composer = new Composer();
+ $composer->setPackage($root_package);
+ $io = new NullIO();
+ $event = Stub::make(PackageEvent::class, []);
+
+ // Empty patch list.
+ $resolver = new RootComposer($composer, $io);
+ $resolver->resolve($patch_collection, $event);
+ $this->assertCount(0, $patch_collection->getPatchesForPackage('test/package'));
+
+ // One patch.
+ $patch = new \stdClass();
+ $patch->url = 'http://drupal.org';
+ $patch->description = 'Test patch';
+ $root_package->setExtra([
+ 'patches' => [
+ 'test/package' => [
+ 0 => $patch,
+ ]
+ ]
+ ]);
+
+ $composer->setPackage($root_package);
+ $resolver = new RootComposer($composer, $io);
+ $resolver->resolve($patch_collection, $event);
+ $this->assertCount(1, $patch_collection->getPatchesForPackage('test/package'));
+ }
+}