diff --git a/composer.json b/composer.json
index f99913123ef4db8148e2a1f1c96f4d1b6b1f5136..569097373ef9a185c8d982f8e34718df313bc376 100644
--- a/composer.json
+++ b/composer.json
@@ -71,11 +71,15 @@
         }
     },
     "scripts": {
-        "pre-install-cmd": "Drupal\\Core\\Composer\\Composer::ensureComposerVersion",
-        "pre-update-cmd": "Drupal\\Core\\Composer\\Composer::ensureComposerVersion",
+        "pre-install-cmd": "Drupal\\Composer\\Composer::ensureComposerVersion",
+        "pre-update-cmd": "Drupal\\Composer\\Composer::ensureComposerVersion",
         "pre-autoload-dump": "Drupal\\Core\\Composer\\Composer::preAutoloadDump",
         "drupal-phpunit-upgrade-check": "Drupal\\Core\\Composer\\Composer::upgradePHPUnit",
         "drupal-phpunit-upgrade": "@composer update phpunit/phpunit symfony/phpunit-bridge phpspec/prophecy symfony/yaml --with-dependencies --no-progress",
+        "post-update-cmd": [
+            "Drupal\\Composer\\Composer::generateMetapackages",
+            "Drupal\\Composer\\Composer::ensureBehatDriverVersions"
+        ],
         "phpcs": "phpcs --standard=core/phpcs.xml.dist --runtime-set installed_paths $($COMPOSER_BINARY config vendor-dir)/drupal/coder/coder_sniffer --",
         "phpcbf": "phpcbf --standard=core/phpcs.xml.dist --runtime-set installed_paths $($COMPOSER_BINARY config vendor-dir)/drupal/coder/coder_sniffer --"
     },
diff --git a/composer.lock b/composer.lock
index e51f6c0bcc8e49bd5a1a9ac4c3632e09f23e7524..9b2b43dfe3195c84a7377be279ffe2a3f3677e3d 100644
--- a/composer.lock
+++ b/composer.lock
@@ -947,14 +947,14 @@
             "authors": [
                 {
                     "name": "Nicholas Humfrey",
-                    "role": "Developer",
                     "email": "njh@aelius.com",
-                    "homepage": "http://www.aelius.com/njh/"
+                    "homepage": "http://www.aelius.com/njh/",
+                    "role": "Developer"
                 },
                 {
                     "name": "Alexey Zakhlestin",
-                    "role": "Developer",
-                    "email": "indeyets@gmail.com"
+                    "email": "indeyets@gmail.com",
+                    "role": "Developer"
                 }
             ],
             "description": "EasyRdf is a PHP library designed to make it easy to consume and produce RDF.",
@@ -4025,16 +4025,16 @@
         },
         {
             "name": "behat/mink-selenium2-driver",
-            "version": "dev-master",
+            "version": "1.3.x-dev",
             "source": {
                 "type": "git",
                 "url": "https://github.com/minkphp/MinkSelenium2Driver.git",
-                "reference": "8684ee4e634db7abda9039ea53545f86fc1e105a"
+                "reference": "0a09c4341621fca937a726827611b20ce3e2c259"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/minkphp/MinkSelenium2Driver/zipball/8684ee4e634db7abda9039ea53545f86fc1e105a",
-                "reference": "8684ee4e634db7abda9039ea53545f86fc1e105a",
+                "url": "https://api.github.com/repos/minkphp/MinkSelenium2Driver/zipball/0a09c4341621fca937a726827611b20ce3e2c259",
+                "reference": "0a09c4341621fca937a726827611b20ce3e2c259",
                 "shasum": ""
             },
             "require": {
@@ -4061,15 +4061,15 @@
                 "MIT"
             ],
             "authors": [
-                {
-                    "name": "Konstantin Kudryashov",
-                    "email": "ever.zet@gmail.com",
-                    "homepage": "http://everzet.com"
-                },
                 {
                     "name": "Pete Otaqui",
                     "email": "pete@otaqui.com",
                     "homepage": "https://github.com/pete-otaqui"
+                },
+                {
+                    "name": "Konstantin Kudryashov",
+                    "email": "ever.zet@gmail.com",
+                    "homepage": "http://everzet.com"
                 }
             ],
             "description": "Selenium2 (WebDriver) driver for Mink framework",
@@ -4082,7 +4082,7 @@
                 "testing",
                 "webdriver"
             ],
-            "time": "2018-10-10T12:39:06+00:00"
+            "time": "2019-09-02T09:46:54+00:00"
         },
         {
             "name": "composer/ca-bundle",
@@ -4883,18 +4883,18 @@
             "authors": [
                 {
                     "name": "Arne Blankerts",
-                    "role": "Developer",
-                    "email": "arne@blankerts.de"
+                    "email": "arne@blankerts.de",
+                    "role": "Developer"
                 },
                 {
                     "name": "Sebastian Heuer",
-                    "role": "Developer",
-                    "email": "sebastian@phpeople.de"
+                    "email": "sebastian@phpeople.de",
+                    "role": "Developer"
                 },
                 {
                     "name": "Sebastian Bergmann",
-                    "role": "Developer",
-                    "email": "sebastian@phpunit.de"
+                    "email": "sebastian@phpunit.de",
+                    "role": "Developer"
                 }
             ],
             "description": "Library for handling version information and constraints",
@@ -5253,8 +5253,8 @@
             "authors": [
                 {
                     "name": "Sebastian Bergmann",
-                    "role": "lead",
-                    "email": "sebastian@phpunit.de"
+                    "email": "sebastian@phpunit.de",
+                    "role": "lead"
                 }
             ],
             "description": "Simple template engine.",
@@ -6057,8 +6057,8 @@
             "authors": [
                 {
                     "name": "Sebastian Bergmann",
-                    "role": "lead",
-                    "email": "sebastian@phpunit.de"
+                    "email": "sebastian@phpunit.de",
+                    "role": "lead"
                 }
             ],
             "description": "Library that helps with managing the version number of Git-hosted PHP projects",
diff --git a/composer/Composer.php b/composer/Composer.php
new file mode 100644
index 0000000000000000000000000000000000000000..c3c6a39e939c65af7639f3eed28126f3b609b508
--- /dev/null
+++ b/composer/Composer.php
@@ -0,0 +1,81 @@
+<?php
+
+namespace Drupal\Composer;
+
+use Composer\Composer as ComposerApp;
+use Composer\Script\Event;
+use Composer\Semver\Comparator;
+use Drupal\Composer\Generator\PackageGenerator;
+use Drupal\Composer\Generator\Util\DrupalCoreComposer;
+
+/**
+ * Provides static functions for composer script events. See also
+ * core/lib/Drupal/Composer/Composer.php, which contains similar
+ * scripts needed by projects that include drupal/core. Scripts that
+ * are only needed by drupal/drupal go here.
+ *
+ * @see https://getcomposer.org/doc/articles/scripts.md
+ */
+class Composer {
+
+  /**
+   * Update metapackages whenever composer.lock is updated.
+   *
+   * @param \Composer\Script\Event $event
+   */
+  public static function generateMetapackages(Event $event) {
+    $generator = new PackageGenerator();
+    $generator->generate($event->getIO(), getcwd());
+  }
+
+  /**
+   * Ensure that the minimum required version of Composer is running.
+   * Throw an exception if Composer is too old.
+   */
+  public static function ensureComposerVersion() {
+    $composerVersion = method_exists(ComposerApp::class, 'getVersion') ?
+      ComposerApp::getVersion() : ComposerApp::VERSION;
+    if (Comparator::lessThan($composerVersion, '1.9.0')) {
+      throw new \RuntimeException("Drupal core development requires Composer 1.9.0, but Composer $composerVersion is installed. Please run 'composer self-update'.");
+    }
+  }
+
+  /**
+   * Ensure that the right version of behat/mink-selenium2-driver is locked.
+   * Throw an exception if we do not have 1.3.x-dev.
+   *
+   * @todo: Remove this once https://www.drupal.org/node/3078671 is fixed.
+   */
+  public static function ensureBehatDriverVersions() {
+    $drupalCoreComposer = DrupalCoreComposer::createFromPath(getcwd());
+
+    $expectedVersion = '1.3.x-dev';
+    $behatMinkSelenium2DriverInfo = $drupalCoreComposer->packageLockInfo('behat/mink-selenium2-driver', TRUE);
+    if ($behatMinkSelenium2DriverInfo['version'] != $expectedVersion) {
+      $drupalVersion = static::drupalVersionBranch();
+      $message = <<< __EOT__
+Drupal requires behat/mink-selenium2-driver:$expectedVersion in its composer.json
+file, but it is pinned to {$behatMinkSelenium2DriverInfo['version']} in the composer.lock file.
+This sometimes happens when Composer becomes confused. To fix:
+
+1. `git checkout -- composer.lock`, or otherwise reset to a known-good lock file.
+2. `rm -rf vendor`
+3. `composer install`
+4. `COMPOSER_ROOT_VERSION={$drupalVersion} composer update ...` (where ... is
+   the update arguments you wish to run, e.g. --lock).
+__EOT__;
+      throw new \RuntimeException($message);
+    }
+  }
+
+  /**
+   * Return the branch name the current Drupal version is associated with.
+   *
+   * @return string
+   *   A branch name, e.g. 8.9.x or 9.0.x.
+   */
+  public static function drupalVersionBranch() {
+    return preg_replace('#\.[0-9]+-dev#', '.x-dev', \Drupal::VERSION);
+  }
+
+}
diff --git a/composer/Generator/Builder/DrupalCoreRecommendedBuilder.php b/composer/Generator/Builder/DrupalCoreRecommendedBuilder.php
new file mode 100644
index 0000000000000000000000000000000000000000..7964f0610b5fd8802ebddffa658a4d949de2bd02
--- /dev/null
+++ b/composer/Generator/Builder/DrupalCoreRecommendedBuilder.php
@@ -0,0 +1,68 @@
+<?php
+
+namespace Drupal\Composer\Generator\Builder;
+
+/**
+ * Builder to produce metapackage for drupal/core-recommended.
+ */
+class DrupalCoreRecommendedBuilder extends DrupalPackageBuilder {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getPath() {
+    return 'CoreRecommended';
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getPackage() {
+
+    $composer = $this->initialPackageMetadata();
+
+    // Pull up the composer lock data.
+    $composerLockData = $this->drupalCoreInfo->composerLock();
+    if (!isset($composerLockData['packages'])) {
+      return $composer;
+    }
+
+    // Make a list of packages we do not want to put in the 'require' section.
+    $remove_list = ['drupal/core', 'wikimedia/composer-merge-plugin'];
+
+    // Copy the 'packages' section from the Composer lock into our 'require'
+    // section. There is also a 'packages-dev' section, but we do not need
+    // to pin 'require-dev' versions, as 'require-dev' dependencies are never
+    // included from subprojects. Use 'drupal/core-dev-dependencies' to get
+    // Drupal's dev dependencies.
+    foreach ($composerLockData['packages'] as $package) {
+      // If there is no 'source' record, then this is a path repository
+      // or something else that we do not want to include.
+      if (isset($package['source']) && !in_array($package['name'], $remove_list)) {
+        $composer['require'][$package['name']] = $package['version'];
+      }
+    }
+    return $composer;
+  }
+
+  /**
+   * Returns the initial package metadata that describes the metapackage.
+   *
+   * @return array
+   */
+  protected function initialPackageMetadata() {
+    return [
+      "name" => "drupal/core-recommended",
+      "type" => "metapackage",
+      "description" => "Locked core dependencies; require this project INSTEAD OF drupal/core.",
+      "license" => "GPL-2.0-or-later",
+      "conflict" => [
+        "webflo/drupal-core-strict" => "*",
+      ],
+      "require" => [
+        "drupal/core" => "self.version",
+      ],
+    ];
+  }
+
+}
diff --git a/composer/Generator/Builder/DrupalDevDependenciesBuilder.php b/composer/Generator/Builder/DrupalDevDependenciesBuilder.php
new file mode 100644
index 0000000000000000000000000000000000000000..0b68b4a0e46fbc172a7c682084f95defb282259e
--- /dev/null
+++ b/composer/Generator/Builder/DrupalDevDependenciesBuilder.php
@@ -0,0 +1,62 @@
+<?php
+
+namespace Drupal\Composer\Generator\Builder;
+
+/**
+ * Builder to produce metapackage for drupal/dev-dependencies.
+ */
+class DrupalDevDependenciesBuilder extends DrupalPackageBuilder {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getPath() {
+    return 'DevDependencies';
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getPackage() {
+
+    $composer = $this->initialPackageMetadata();
+
+    // Put everything from Drupal's "require-dev" into our "require" section.
+    $composer['require'] = $this->drupalCoreInfo->getRequireDev();
+
+    // If the require-dev is bringing in a dev version of behat/mink, convert
+    // the requirement to a more flexible set of versions.
+    // @todo: remove when https://www.drupal.org/node/3078671 is fixed.
+    if (isset($composer['require']['behat/mink']) && ($composer['require']['behat/mink'] == '1.7.x-dev')) {
+      $composer['require']['behat/mink'] = '1.8.0 | 1.7.1.1 | 1.7.x-dev';
+    }
+
+    // Do the same sort of conversion for behat/mink-selenium2-driver.
+    if (isset($composer['require']['behat/mink-selenium2-driver']) && ($composer['require']['behat/mink-selenium2-driver'] == '1.3.x-dev')) {
+      $composer['require']['behat/mink-selenium2-driver'] = '1.4.0 | 1.3.1.1 | 1.3.x-dev';
+    }
+
+    // Sort our required packages by key.
+    ksort($composer['require']);
+
+    return $composer;
+  }
+
+  /**
+   * Returns the initial package metadata that describes the metapackage.
+   *
+   * @return array
+   */
+  protected function initialPackageMetadata() {
+    return [
+      "name" => "drupal/dev-dependencies",
+      "type" => "metapackage",
+      "description" => "require-dev dependencies from drupal/drupal; use in addition to drupal/core-recommended to run tests from drupal/core.",
+      "license" => "GPL-2.0-or-later",
+      "conflict" => [
+        "webflo/drupal-core-require-dev" => "*",
+      ],
+    ];
+  }
+
+}
diff --git a/composer/Generator/Builder/DrupalPackageBuilder.php b/composer/Generator/Builder/DrupalPackageBuilder.php
new file mode 100644
index 0000000000000000000000000000000000000000..6c0872164794be98781e9501d5b478e0b4559a5a
--- /dev/null
+++ b/composer/Generator/Builder/DrupalPackageBuilder.php
@@ -0,0 +1,30 @@
+<?php
+
+namespace Drupal\Composer\Generator\Builder;
+
+use Drupal\Composer\Generator\BuilderInterface;
+use Drupal\Composer\Generator\Util\DrupalCoreComposer;
+
+/**
+ * Base class that includes helpful utility routine for Drupal builder classes.
+ */
+abstract class DrupalPackageBuilder implements BuilderInterface {
+
+  /**
+   * Information about composer.json, composer.lock etc. in current release.
+   *
+   * @var \Drupal\Composer\Generator\Util\DrupalCoreComposer
+   */
+  protected $drupalCoreInfo;
+
+  /**
+   * DrupalPackageBuilder constructor.
+   *
+   * @param \Drupal\Composer\Generator\Util\DrupalCoreComposer $drupalCoreInfo
+   *   Information about composer.json and composer.lock from current release.
+   */
+  public function __construct(DrupalCoreComposer $drupalCoreInfo) {
+    $this->drupalCoreInfo = $drupalCoreInfo;
+  }
+
+}
diff --git a/composer/Generator/Builder/DrupalPinnedDevDependenciesBuilder.php b/composer/Generator/Builder/DrupalPinnedDevDependenciesBuilder.php
new file mode 100644
index 0000000000000000000000000000000000000000..4a4abb3c1cc880828d369a47a66e6375f74611c0
--- /dev/null
+++ b/composer/Generator/Builder/DrupalPinnedDevDependenciesBuilder.php
@@ -0,0 +1,69 @@
+<?php
+
+namespace Drupal\Composer\Generator\Builder;
+
+/**
+ * Builder to produce metapackage for drupal/pinned-dev-dependencies.
+ */
+class DrupalPinnedDevDependenciesBuilder extends DrupalPackageBuilder {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getPath() {
+    return 'PinnedDevDependencies';
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getPackage() {
+
+    $composer = $this->initialPackageMetadata();
+
+    // Pull the exact versions of the dependencies from the composer.lock
+    // file and use it to build our 'require' section.
+    $composerLockData = $this->drupalCoreInfo->composerLock();
+
+    if (isset($composerLockData['packages-dev'])) {
+
+      foreach ($composerLockData['packages-dev'] as $package) {
+        $composer['require'][$package['name']] = $package['version'];
+
+        // If the require-dev is bringing in a dev version of behat/mink,
+        // convert the requirement to a more flexible set of versions.
+        // @todo: remove when https://www.drupal.org/node/3078671 is fixed.
+        if (($package['name'] == 'behat/mink') && (($package['version'] == 'dev-master') || ($package['version'] == '1.7.x-dev'))) {
+          $composer['require']['behat/mink'] = '1.8.0 | 1.7.1.1 | 1.7.x-dev';
+        }
+
+        // Do the same sort of conversion for behat/mink-selenium2-driver.
+        if (($package['name'] == 'behat/mink-selenium2-driver') && (($package['version'] == 'dev-master') || ($package['version'] == '1.3.x-dev'))) {
+          $composer['require']['behat/mink-selenium2-driver'] = '1.4.0 | 1.3.1.1 | 1.3.x-dev';
+        }
+      }
+    }
+    return $composer;
+  }
+
+  /**
+   * Returns the initial package metadata that describes the metapackage.
+   *
+   * @return array
+   */
+  protected function initialPackageMetadata() {
+    return [
+      "name" => "drupal/pinned-dev-dependencies",
+      "type" => "metapackage",
+      "description" => "Pinned require-dev dependencies from drupal/drupal; use in addition to drupal/core-recommended to run tests from drupal/core.",
+      "license" => "GPL-2.0-or-later",
+      "conflict" => [
+        "webflo/drupal-core-require-dev" => "*",
+      ],
+      "require" => [
+        "drupal/core" => "self.version",
+      ],
+    ];
+  }
+
+}
diff --git a/composer/Generator/BuilderInterface.php b/composer/Generator/BuilderInterface.php
new file mode 100644
index 0000000000000000000000000000000000000000..de4ddf8cc00ba3983a0fb20af10c68697921b317
--- /dev/null
+++ b/composer/Generator/BuilderInterface.php
@@ -0,0 +1,44 @@
+<?php
+
+namespace Drupal\Composer\Generator;
+
+use Drupal\Composer\Generator\Util\DrupalCoreComposer;
+
+/**
+ * Produce the output for a metapackage.
+ *
+ * BuilderInterface provides an interface for builder classes which are
+ * called by the PackageGenerator in order to produce a derived metapackage from
+ * the provided source package.
+ *
+ * See the README.txt file in composer/Metapackage for a description of what
+ * a metapackage is, and an explanation of the metapackages produced by the
+ * generator.
+ */
+interface BuilderInterface {
+
+  /**
+   * BuilderInterface constructor.
+   *
+   * @param \Drupal\Composer\Generator\Util\DrupalCoreComposer $drupalCoreInfo
+   *   Information about the composer.json, composer.lock, and repository path.
+   */
+  public function __construct(DrupalCoreComposer $drupalCoreInfo);
+
+  /**
+   * Return the path to where the metapackage should be written.
+   *
+   * @return string
+   *   Path to the metapackage.
+   */
+  public function getPath();
+
+  /**
+   * Generate the Composer.json data for the current tag or branch.
+   *
+   * @return array
+   *   Composer json data.
+   */
+  public function getPackage();
+
+}
diff --git a/composer/Generator/PackageGenerator.php b/composer/Generator/PackageGenerator.php
new file mode 100644
index 0000000000000000000000000000000000000000..1dacb05654a68ee21d8cf7b888b68390b78514bd
--- /dev/null
+++ b/composer/Generator/PackageGenerator.php
@@ -0,0 +1,127 @@
+<?php
+
+namespace Drupal\Composer\Generator;
+
+use Drupal\Composer\Generator\Builder\DrupalCoreRecommendedBuilder;
+use Drupal\Composer\Generator\Builder\DrupalDevDependenciesBuilder;
+use Drupal\Composer\Generator\Builder\DrupalPinnedDevDependenciesBuilder;
+use Drupal\Composer\Generator\Util\DrupalCoreComposer;
+use Composer\Util\Filesystem;
+use Composer\IO\IOInterface;
+
+/**
+ * Generates metapackages.
+ */
+class PackageGenerator {
+
+  /**
+   * Base directory where generated projects are written.
+   *
+   * @var string
+   */
+  protected $generatedProjectBaseDir;
+
+  /**
+   * PackageGenerator constructor.
+   */
+  public function __construct() {
+    $this->generatedProjectBaseDir = dirname(__DIR__) . '/Metapackage';
+  }
+
+  /**
+   * Generate Drupal's metapackages whenever composer.lock is updated
+   *
+   * @param \Composer\IO\IOInterface $io
+   *   Composer IO object for interacting with the user.
+   * @param string $base_dir
+   *   Directory where drupal/drupal repository is located.
+   */
+  public function generate(IOInterface $io, $base_dir) {
+    // General information from drupal/drupal and drupal/core composer.json
+    // and composer.lock files.
+    $drupalCoreInfo = DrupalCoreComposer::createFromPath($base_dir);
+
+    // Exit early if there is no composer.lock file.
+    if (empty($drupalCoreInfo->composerLock())) {
+      return;
+    }
+
+    // Run all of our available builders.
+    $builders = $this->builders();
+    $changed = FALSE;
+    foreach ($builders as $builder_class) {
+      $builder = new $builder_class($drupalCoreInfo);
+      $changed |= $this->generateMetapackage($io, $builder);
+    }
+
+    // Remind the user not to miss files in a patch.
+    if ($changed) {
+      $io->write("If you make a patch, ensure that the files above are included.");
+    }
+  }
+
+  /**
+   * Returns a list of metapackage builders.
+   *
+   * @return BuilderInterface[]
+   */
+  protected function builders() {
+    return [
+      DrupalCoreRecommendedBuilder::class,
+      DrupalDevDependenciesBuilder::class,
+      DrupalPinnedDevDependenciesBuilder::class,
+    ];
+  }
+
+  /**
+   * Generate one metapackage.
+   *
+   * @param \Composer\IO\IOInterface $io
+   *   Composer IO object for interacting with the user.
+   * @param BuilderInterface $builder
+   *   An object that can build a metapackage.
+   *
+   * @return bool
+   *   TRUE if the generated metapackage is different than what is on disk.
+   */
+  protected function generateMetapackage(IOInterface $io, BuilderInterface $builder) {
+
+    // Load the existing composer.json file for drupal/core-recommended
+    $relative_path = $builder->getPath() . '/composer.json';
+    $composer_json_path = $this->generatedProjectBaseDir . '/' . $relative_path;
+    $original_composer_json = file_exists($composer_json_path) ? file_get_contents($composer_json_path) : '';
+
+    // Get the composer.json file from the builder.
+    $composer_json_data = $builder->getPackage();
+    $updated_composer_json = static::encode($composer_json_data);
+
+    // Exit early if nothing changed.
+    if (trim($original_composer_json, " \t\r\0\x0B") == trim($updated_composer_json, " \t\r\0\x0B")) {
+      return FALSE;
+    }
+
+    // Warn the user that a metapackage file has been updated..
+    $io->write("Updated metapackage file <info>composer/Metapackage/$relative_path</info>.");
+
+    // Write the composer.json file back to disk
+    $fs = new Filesystem();
+    $fs->ensureDirectoryExists(dirname($composer_json_path));
+    file_put_contents($composer_json_path, $updated_composer_json);
+
+    return TRUE;
+  }
+
+  /**
+   * Utility function to encode metapackage json in a consistent way.
+   *
+   * @param array $composer_json_data
+   *   Data to encode into a json string.
+   *
+   * @return string
+   *   Encoded version of provided json data.
+   */
+  public static function encode($composer_json_data) {
+    return json_encode($composer_json_data, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES) . "\n";
+  }
+
+}
diff --git a/composer/Generator/TESTING.txt b/composer/Generator/TESTING.txt
new file mode 100644
index 0000000000000000000000000000000000000000..59ccd78314a6610e8337816262fbe5da7d08154b
--- /dev/null
+++ b/composer/Generator/TESTING.txt
@@ -0,0 +1,18 @@
+HOW-TO: Test this Drupal composer script
+
+In order to test this script, you'll need to get the entire Drupal repo and
+run the tests there.
+
+You'll find the tests under core/tests/Drupal/Tests/Composer/Generator.
+
+You can get the full Drupal repo here:
+https://www.drupal.org/project/drupal/git-instructions
+
+You can find more information about running PHPUnit tests with Drupal here:
+https://www.drupal.org/node/2116263
+
+Each component in the Drupal\Composer\Plugin namespace has its own annotated test
+group. You can use this group to run only the tests for this component. Like
+this:
+
+$ ./vendor/bin/phpunit -c core --group Metapackage
diff --git a/composer/Generator/Util/DrupalCoreComposer.php b/composer/Generator/Util/DrupalCoreComposer.php
new file mode 100644
index 0000000000000000000000000000000000000000..f33e759e20ce0afd105b9977cb4b0b5bfa89328d
--- /dev/null
+++ b/composer/Generator/Util/DrupalCoreComposer.php
@@ -0,0 +1,123 @@
+<?php
+
+namespace Drupal\Composer\Generator\Util;
+
+/**
+ * Utilities for accessing composer.json data from drupal/drupal and drupal/core.
+ *
+ * Some data is stored in the root composer.json file, while others is found
+ * in the core/composer.json file.
+ */
+class DrupalCoreComposer {
+
+  /**
+   * Cached composer.json data.
+   *
+   * @var array
+   */
+  protected $composerJson = [];
+
+  /**
+   * Cached composer.lock data.
+   *
+   * @var array
+   */
+  protected $composerLock = [];
+
+  /**
+   * DrupalCoreComposer constructor.
+   *
+   * @param array $composerJson
+   *   The composer.json data.
+   * @param array $composerLock
+   *   The composer.lock data.
+   */
+  public function __construct(array $composerJson, array $composerLock) {
+    $this->composerJson = $composerJson;
+    $this->composerLock = $composerLock;
+  }
+
+  /**
+   * DrupalCoreComposer factory.
+   *
+   * @param string $repositoryPath
+   *   Path to a directory containing a composer.json and composer.lock files.
+   *
+   * @return static
+   *   New DrupalCoreComposer object containing composer.json and lock data.
+   */
+  public static function createFromPath(string $repositoryPath) {
+    $composerJson = static::loadJsonFromPath("$repositoryPath/composer.json");
+    $composerLock = static::loadJsonFromPath("$repositoryPath/composer.lock");
+    return new self($composerJson, $composerLock);
+  }
+
+  /**
+   * Fetch the composer data from the root drupal/drupal project.
+   *
+   * @return array
+   *   Composer json data
+   */
+  public function rootComposerJson() {
+    return $this->composerJson;
+  }
+
+  /**
+   * Fetch the composer lock data.
+   *
+   * @return array
+   *   Composer lock data
+   */
+  public function composerLock() {
+    return $this->composerLock;
+  }
+
+  /**
+   * Return the "require-dev" section from root or core composer.json file.
+   *
+   * The require-dev constraints moved from core/composer.json (8.7.x and
+   * earlier) to the root composer.json file (8.8.x and later).
+   *
+   * @return array
+   *   The contents of the "require-dev" section.
+   */
+  public function getRequireDev() {
+    $composerJsonData = $this->rootComposerJson();
+    return isset($composerJsonData['require-dev']) ? $composerJsonData['require-dev'] : [];
+  }
+
+  /**
+   * Look up the info for one package in the composer.lock file.
+   *
+   * @param string $packageName
+   *   Name of package to find, e.g. 'behat/mink-selenium2-driver'.
+   * @param bool $dev
+   *   TRUE: consider only 'packages-dev'. Default: consider only 'packages'
+   *
+   * @return array
+   *   Package info from composer.lock.
+   */
+  public function packageLockInfo($packageName, $dev = FALSE) {
+    $packagesSection = $dev ? 'packages-dev' : 'packages';
+    foreach ($this->composerLock[$packagesSection] as $info) {
+      if ($info['name'] == $packageName) {
+        return $info;
+      }
+    }
+    return [];
+  }
+
+  /**
+   * Load json data from the specified path.
+   *
+   * @param string $path
+   *   Relative path to the json file to load.
+   *
+   * @return array
+   *   The contents of the json data for the specified file.
+   */
+  protected static function loadJsonFromPath($path) {
+    return file_exists($path) ? json_decode(file_get_contents($path), TRUE) : [];
+  }
+
+}
diff --git a/composer/Metapackage/CoreRecommended/composer.json b/composer/Metapackage/CoreRecommended/composer.json
new file mode 100644
index 0000000000000000000000000000000000000000..c42c8708a846d78a8c4b59b93eaba4bf94fcc032
--- /dev/null
+++ b/composer/Metapackage/CoreRecommended/composer.json
@@ -0,0 +1,69 @@
+{
+    "name": "drupal/core-recommended",
+    "type": "metapackage",
+    "description": "Locked core dependencies; require this project INSTEAD OF drupal/core.",
+    "license": "GPL-2.0-or-later",
+    "conflict": {
+        "webflo/drupal-core-strict": "*"
+    },
+    "require": {
+        "drupal/core": "self.version",
+        "asm89/stack-cors": "1.2.0",
+        "composer/installers": "v1.7.0",
+        "composer/semver": "1.5.0",
+        "doctrine/annotations": "v1.4.0",
+        "doctrine/cache": "v1.6.2",
+        "doctrine/collections": "v1.4.0",
+        "doctrine/common": "v2.7.3",
+        "doctrine/inflector": "v1.2.0",
+        "doctrine/lexer": "1.0.2",
+        "easyrdf/easyrdf": "0.9.1",
+        "egulias/email-validator": "2.1.11",
+        "guzzlehttp/guzzle": "6.3.3",
+        "guzzlehttp/promises": "v1.3.1",
+        "guzzlehttp/psr7": "1.6.1",
+        "masterminds/html5": "2.7.0",
+        "pear/archive_tar": "1.4.8",
+        "pear/console_getopt": "v1.4.2",
+        "pear/pear-core-minimal": "v1.10.9",
+        "pear/pear_exception": "v1.0.0",
+        "psr/container": "1.0.0",
+        "psr/http-message": "1.0.1",
+        "psr/log": "1.1.0",
+        "ralouphie/getallheaders": "3.0.3",
+        "stack/builder": "v1.0.5",
+        "symfony-cmf/routing": "2.1.0",
+        "symfony/class-loader": "v3.4.32",
+        "symfony/console": "4.4.x-dev",
+        "symfony/debug": "4.4.x-dev",
+        "symfony/dependency-injection": "4.4.x-dev",
+        "symfony/error-handler": "dev-master",
+        "symfony/error-renderer": "4.4.x-dev",
+        "symfony/event-dispatcher": "4.4.x-dev",
+        "symfony/event-dispatcher-contracts": "v1.1.7",
+        "symfony/http-foundation": "4.4.x-dev",
+        "symfony/http-kernel": "4.4.x-dev",
+        "symfony/mime": "v4.3.5",
+        "symfony/polyfill-ctype": "v1.12.0",
+        "symfony/polyfill-iconv": "v1.12.0",
+        "symfony/polyfill-intl-idn": "v1.12.0",
+        "symfony/polyfill-mbstring": "v1.12.0",
+        "symfony/polyfill-php72": "v1.12.0",
+        "symfony/polyfill-php73": "v1.12.0",
+        "symfony/process": "4.4.x-dev",
+        "symfony/psr-http-message-bridge": "v1.2.0",
+        "symfony/routing": "4.4.x-dev",
+        "symfony/serializer": "4.4.x-dev",
+        "symfony/service-contracts": "v1.1.7",
+        "symfony/translation": "4.4.x-dev",
+        "symfony/translation-contracts": "v1.1.7",
+        "symfony/validator": "4.4.x-dev",
+        "symfony/yaml": "4.4.x-dev",
+        "twig/twig": "v1.42.3",
+        "typo3/phar-stream-wrapper": "v3.1.3",
+        "zendframework/zend-diactoros": "1.8.7",
+        "zendframework/zend-escaper": "2.6.1",
+        "zendframework/zend-feed": "2.12.0",
+        "zendframework/zend-stdlib": "3.2.1"
+    }
+}
diff --git a/composer/Metapackage/DevDependencies/composer.json b/composer/Metapackage/DevDependencies/composer.json
new file mode 100644
index 0000000000000000000000000000000000000000..586a4e830924d831b52c7f275f6e642d41ab4be7
--- /dev/null
+++ b/composer/Metapackage/DevDependencies/composer.json
@@ -0,0 +1,30 @@
+{
+    "name": "drupal/dev-dependencies",
+    "type": "metapackage",
+    "description": "require-dev dependencies from drupal/drupal; use in addition to drupal/core-recommended to run tests from drupal/core.",
+    "license": "GPL-2.0-or-later",
+    "conflict": {
+        "webflo/drupal-core-require-dev": "*"
+    },
+    "require": {
+        "behat/mink": "1.8.0 | 1.7.1.1 | 1.7.x-dev",
+        "behat/mink-goutte-driver": "^1.2",
+        "behat/mink-selenium2-driver": "1.4.0 | 1.3.1.1 | 1.3.x-dev",
+        "composer/composer": "^1.8",
+        "drupal/coder": "^8.3.2",
+        "jcalderonzumba/gastonjs": "^1.0.2",
+        "jcalderonzumba/mink-phantomjs-driver": "^0.3.1",
+        "justinrainbow/json-schema": "^5.2",
+        "mikey179/vfsstream": "^1.2",
+        "phpspec/prophecy": "^1.7",
+        "phpunit/phpunit": "^6.5 || ^7",
+        "symfony/browser-kit": "^4.4",
+        "symfony/css-selector": "^4.4",
+        "symfony/debug": "^4.4",
+        "symfony/dom-crawler": "^4.4",
+        "symfony/filesystem": "^4.4",
+        "symfony/finder": "^4.4",
+        "symfony/lock": "^4.4",
+        "symfony/phpunit-bridge": "^4.4"
+    }
+}
diff --git a/composer/Metapackage/PinnedDevDependencies/composer.json b/composer/Metapackage/PinnedDevDependencies/composer.json
new file mode 100644
index 0000000000000000000000000000000000000000..fb040b49f4a215f8f64edb4ff766dd6472e3856d
--- /dev/null
+++ b/composer/Metapackage/PinnedDevDependencies/composer.json
@@ -0,0 +1,65 @@
+{
+    "name": "drupal/pinned-dev-dependencies",
+    "type": "metapackage",
+    "description": "Pinned require-dev dependencies from drupal/drupal; use in addition to drupal/core-recommended to run tests from drupal/core.",
+    "license": "GPL-2.0-or-later",
+    "conflict": {
+        "webflo/drupal-core-require-dev": "*"
+    },
+    "require": {
+        "drupal/core": "self.version",
+        "behat/mink": "1.8.0 | 1.7.1.1 | 1.7.x-dev",
+        "behat/mink-browserkit-driver": "1.3.3",
+        "behat/mink-goutte-driver": "v1.2.1",
+        "behat/mink-selenium2-driver": "1.4.0 | 1.3.1.1 | 1.3.x-dev",
+        "composer/ca-bundle": "1.2.4",
+        "composer/composer": "1.9.0",
+        "composer/spdx-licenses": "1.5.2",
+        "composer/xdebug-handler": "1.3.3",
+        "doctrine/instantiator": "1.0.5",
+        "drupal/coder": "8.3.6",
+        "fabpot/goutte": "v3.2.3",
+        "instaclick/php-webdriver": "1.4.6",
+        "jcalderonzumba/gastonjs": "v1.0.2",
+        "jcalderonzumba/mink-phantomjs-driver": "v0.3.2",
+        "justinrainbow/json-schema": "5.2.8",
+        "mikey179/vfsstream": "v1.6.7",
+        "myclabs/deep-copy": "1.7.0",
+        "phar-io/manifest": "1.0.1",
+        "phar-io/version": "1.0.1",
+        "phpdocumentor/reflection-common": "1.0.1",
+        "phpdocumentor/reflection-docblock": "4.3.2",
+        "phpdocumentor/type-resolver": "0.5.1",
+        "phpspec/prophecy": "1.9.0",
+        "phpunit/php-code-coverage": "5.3.2",
+        "phpunit/php-file-iterator": "1.4.5",
+        "phpunit/php-text-template": "1.2.1",
+        "phpunit/php-timer": "1.0.9",
+        "phpunit/php-token-stream": "2.0.2",
+        "phpunit/phpunit": "6.5.14",
+        "phpunit/phpunit-mock-objects": "5.0.10",
+        "sebastian/code-unit-reverse-lookup": "1.0.1",
+        "sebastian/comparator": "2.1.3",
+        "sebastian/diff": "2.0.1",
+        "sebastian/environment": "3.1.0",
+        "sebastian/exporter": "3.1.2",
+        "sebastian/global-state": "2.0.0",
+        "sebastian/object-enumerator": "3.0.3",
+        "sebastian/object-reflector": "1.1.1",
+        "sebastian/recursion-context": "3.0.0",
+        "sebastian/resource-operations": "1.0.0",
+        "sebastian/version": "2.0.1",
+        "seld/jsonlint": "1.7.1",
+        "seld/phar-utils": "1.0.1",
+        "squizlabs/php_codesniffer": "3.5.0",
+        "symfony/browser-kit": "4.4.x-dev",
+        "symfony/css-selector": "4.4.x-dev",
+        "symfony/dom-crawler": "4.4.x-dev",
+        "symfony/filesystem": "4.4.x-dev",
+        "symfony/finder": "4.4.x-dev",
+        "symfony/lock": "4.4.x-dev",
+        "symfony/phpunit-bridge": "4.4.x-dev",
+        "theseer/tokenizer": "1.1.3",
+        "webmozart/assert": "1.5.0"
+    }
+}
diff --git a/composer/Metapackage/README.txt b/composer/Metapackage/README.txt
new file mode 100644
index 0000000000000000000000000000000000000000..440e89e0a97be23c9ca7d5e5d223588e3eab7524
--- /dev/null
+++ b/composer/Metapackage/README.txt
@@ -0,0 +1,66 @@
+# Drupal Metapackages
+
+A metapackage is a Composer package that contains only a composer.json, and
+has no other content. In other words, the purpose of a metapackage is to
+provide dependencies, not to provide code or data.
+
+
+## Metapackages Provided by Drupal Core
+
+Drupal Core provides three metapackages that serve different purposes.
+
+ - drupal/core-recommended: This project pins to the exact version of each
+   dependency used in drupal/core. It also requires drupal/core, so
+   drupal/core-recommended should be used INSTEAD OF drupal/core. See usage
+   diagram below. This relationship makes it easier for Composer to update
+   a Drupal project.
+
+ - drupal/dev-dependencies: This project provides the same version constraints
+   as Drupal uses for testing. It is useful for projects that either wish to
+   run some of the Drupal tests directly, or for projects that may wish to use
+   the same components that Drupal does for testing.
+
+ - drupal/pinned-dev-dependencies: This project should be used INSTEAD OF
+   drupal/dev-dependencies in instances where a project wishes to pin to the
+   exact version of each testing dependency used in Drupal. This in general
+   should not be necessary.
+
+Note that a project that uses both drupal/core-recommended and
+drupal/pinned-dev-dependencies must update them both at the same time, e.g.:
+
+  composer update drupal/core-recommended drupal/pinned-dev-dependencies --with-updates
+
+Composer may have trouble with the update if one of these projects is listed
+on the command line without the other. Running composer update without any
+parameters should also work, because in this instance every dependency is
+updated.
+
+
+## Metapackage Usage in Template Projects
+
+The relationship between the metapackages drupal/core-recommended and
+drupal/dev-dependencies and the project (subtree split) drupal/core, as
+used in the drupal/recommended-project is shown below:
+
++----------------------------+
+| drupal/recommended-project |
++----------------------------+
+ |
+ +--"require":
+ |    |
+ |    |   +-------------------------+   +-------------+
+ |    +-->| drupal/core-recommended |-->| drupal/core |
+ |        +-------------------------+   +-------------+
+ |
+ +--"require-dev":
+      |
+      |   +-------------------------+
+      +-->| drupal/dev-dependencies |
+          +-------------------------+
+
+If a user does not wish to pin their Drupal project's dependencies to the same
+versions used in drupal/core, then they should replace drupal/core-recommended
+with drupal/core in their "require" section.
+
+If a user does not need the testing dependencies in their Drupal project, then
+they may simply remove drupal/dev-dependencies from the "require-dev" section.
diff --git a/core/lib/Drupal/Core/Composer/Composer.php b/core/lib/Drupal/Core/Composer/Composer.php
index 627972c21c991f0d2d04ff9d854e2e7cda983e24..453a2485f6f3b9de591e7b70a2b81331377fb9a0 100644
--- a/core/lib/Drupal/Core/Composer/Composer.php
+++ b/core/lib/Drupal/Core/Composer/Composer.php
@@ -2,10 +2,8 @@
 
 namespace Drupal\Core\Composer;
 
-use Composer\Composer as ComposerApp;
 use Composer\Installer\PackageEvent;
 use Composer\Script\Event;
-use Composer\Semver\Comparator;
 use Composer\Semver\Constraint\Constraint;
 use Composer\Util\ProcessExecutor;
 use Drupal\Component\FileSecurity\FileSecurity;
@@ -96,20 +94,10 @@ class Composer {
     'zendframework/zend-stdlib' => ['doc'],
   ];
 
-  /**
-   * Ensure that the minimum required version of Composer is running.
-   * Throw an exception if Composer is too old.
-   */
-  public static function ensureComposerVersion() {
-    $composerVersion = method_exists(ComposerApp::class, 'getVersion') ?
-      ComposerApp::getVersion() : ComposerApp::VERSION;
-    if (Comparator::lessThan($composerVersion, '1.9.0')) {
-      throw new \RuntimeException("Drupal core development requires Composer 1.9.0, but Composer $composerVersion is installed. Please run 'composer self-update'.");
-    }
-  }
-
   /**
    * Add vendor classes to Composer's static classmap.
+   *
+   * @param \Composer\Script\Event $event
    */
   public static function preAutoloadDump(Event $event) {
     // Get the configured vendor directory.
diff --git a/core/tests/Drupal/Tests/Composer/ComposerTest.php b/core/tests/Drupal/Tests/Composer/ComposerTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..7728187888e3976163bb14a90aec216d08382e72
--- /dev/null
+++ b/core/tests/Drupal/Tests/Composer/ComposerTest.php
@@ -0,0 +1,47 @@
+<?php
+
+namespace Drupal\Tests\Composer;
+
+use Drupal\Composer\Composer;
+use Drupal\Tests\UnitTestCase;
+
+/**
+ * @coversDefaultClass \Drupal\Composer\Composer
+ * @group Composer
+ */
+class ComposerTest extends UnitTestCase {
+
+  /**
+   * Verify that Composer::ensureComposerVersion() doesn't break.
+   *
+   * @covers::ensureComposerVersion
+   */
+  public function testEnsureComposerVersion() {
+    try {
+      $this->assertNull(Composer::ensureComposerVersion());
+    }
+    catch (\RuntimeException $e) {
+      $this->assertRegExp('/Drupal core development requires Composer 1.9.0, but Composer /', $e->getMessage());
+    }
+  }
+
+  /**
+   * Verify that Composer::ensureBehatDriverVersions() detects a good version.
+   *
+   * @covers::ensureBehatDriverVersions
+   */
+  public function testEnsureBehatDriverVersions() {
+    // First call 'ensureBehatDriverVersions' test directly using Drupal's
+    // composer.lock. It should not fail.
+    chdir($this->root);
+    $this->assertNull(Composer::ensureBehatDriverVersions());
+
+    // Next, call 'ensureBehatDriverVersions' again, this time using a fixture
+    // with a known-bad version number.
+    $this->expectException(\RuntimeException::class);
+    $this->expectExceptionMessageRegExp('#^Drupal requires behat/mink-selenium2-driver:1.3.x-dev#');
+    chdir(__DIR__ . '/fixtures/ensureBehatDriverVersionsFixture');
+    $this->assertNull(Composer::ensureBehatDriverVersions());
+  }
+
+}
diff --git a/core/tests/Drupal/Tests/Composer/Generator/BuilderTest.php b/core/tests/Drupal/Tests/Composer/Generator/BuilderTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..385b6a996f4e1085edbd985b6e14612fa043912b
--- /dev/null
+++ b/core/tests/Drupal/Tests/Composer/Generator/BuilderTest.php
@@ -0,0 +1,98 @@
+<?php
+
+namespace Drupal\Tests\Composer\Generator;
+
+use Drupal\Composer\Generator\Builder\DrupalCoreRecommendedBuilder;
+use Drupal\Composer\Generator\Builder\DrupalDevDependenciesBuilder;
+use Drupal\Composer\Generator\Builder\DrupalPinnedDevDependenciesBuilder;
+use PHPUnit\Framework\TestCase;
+
+/**
+ * Test DrupalCoreRecommendedBuilder
+ *
+ * @group Metapackage
+ */
+class BuilderTest extends TestCase {
+
+  /**
+   * Test data for testBuilder
+   */
+  public function builderTestData() {
+    return [
+      [
+        DrupalCoreRecommendedBuilder::class,
+        [
+          'name' => 'drupal/core-recommended',
+          'type' => 'metapackage',
+          'description' => 'Locked core dependencies; require this project INSTEAD OF drupal/core.',
+          'license' => 'GPL-2.0-or-later',
+          'require' =>
+          [
+            'drupal/core' => 'self.version',
+            'symfony/polyfill-ctype' => 'v1.12.0',
+            'symfony/yaml' => 'v3.4.32',
+          ],
+          'conflict' =>
+          [
+            'webflo/drupal-core-strict' => '*',
+          ],
+        ],
+      ],
+
+      [
+        DrupalDevDependenciesBuilder::class,
+        [
+          'name' => 'drupal/dev-dependencies',
+          'type' => 'metapackage',
+          'description' => 'require-dev dependencies from drupal/drupal; use in addition to drupal/core-recommended to run tests from drupal/core.',
+          'license' => 'GPL-2.0-or-later',
+          'require' =>
+          [
+            'behat/mink' => '1.8.0 | 1.7.1.1 | 1.7.x-dev',
+          ],
+          'conflict' =>
+          [
+            'webflo/drupal-core-require-dev' => '*',
+          ],
+        ],
+      ],
+
+      [
+        DrupalPinnedDevDependenciesBuilder::class,
+        [
+          'name' => 'drupal/pinned-dev-dependencies',
+          'type' => 'metapackage',
+          'description' => 'Pinned require-dev dependencies from drupal/drupal; use in addition to drupal/core-recommended to run tests from drupal/core.',
+          'license' => 'GPL-2.0-or-later',
+          'require' =>
+          [
+            'drupal/core' => 'self.version',
+            'behat/mink' => '1.8.0 | 1.7.1.1 | 1.7.x-dev',
+            'symfony/css-selector' => 'v4.3.5',
+          ],
+          'conflict' =>
+          [
+            'webflo/drupal-core-require-dev' => '*',
+          ],
+        ],
+      ],
+
+    ];
+  }
+
+  /**
+   * Test all of the various kinds of builders.
+   *
+   * @dataProvider builderTestData
+   */
+  public function testBuilder($builderClass, $expected) {
+    $fixtures = new Fixtures();
+    $drupalCoreInfo = $fixtures->drupalCoreComposerFixture();
+
+    $builder = new $builderClass($drupalCoreInfo);
+    $generatedJson = $builder->getPackage();
+
+    $this->assertEquals($expected, $generatedJson);
+  }
+
+}
diff --git a/core/tests/Drupal/Tests/Composer/Generator/Fixtures.php b/core/tests/Drupal/Tests/Composer/Generator/Fixtures.php
new file mode 100644
index 0000000000000000000000000000000000000000..eccac2c12b30ba4bf3583aa12b1b90083f4003ac
--- /dev/null
+++ b/core/tests/Drupal/Tests/Composer/Generator/Fixtures.php
@@ -0,0 +1,109 @@
+<?php
+
+namespace Drupal\Tests\Composer\Generator;
+
+use Drupal\Composer\Generator\Util\DrupalCoreComposer;
+
+/**
+ * Convenience class for creating fixtures.
+ */
+class Fixtures {
+
+  /**
+   * Generate a suitable DrupalCoreComposer fixture for testing.
+   *
+   * @return \Drupal\Composer\Generator\Util\DrupalCoreComposer
+   *   DrupalCoreComposer fixture.
+   */
+  public function drupalCoreComposerFixture() {
+    return new DrupalCoreComposer($this->composerJson(), $this->composerLock());
+  }
+
+  /**
+   * Data for a composer.json fixture.
+   *
+   * @return array
+   *   composer.json fixture data.
+   */
+  protected function composerJson() {
+    return [
+      'name' => 'drupal/project-fixture',
+      'description' => 'A fixture for testing the metapackage generator.',
+      'type' => 'project',
+      'license' => 'GPL-2.0-or-later',
+      'require' =>
+      [
+        'php' => '>=7.0.8',
+        'symfony/yaml' => '~3.4.5',
+      ],
+      'require-dev' =>
+      [
+        'behat/mink' => '1.7.x-dev',
+      ],
+    ];
+  }
+
+  /**
+   * Data for a composer.lock fixture.
+   *
+   * @return array
+   *   composer.lock fixture data.
+   */
+  protected function composerLock() {
+    return [
+      '_readme' =>
+      [
+        'This is a composer.lock fixture. It contains only a subset of a',
+        'typical composer.lock file (just what is needed for testing).',
+      ],
+      'content-hash' => 'da9910627bab73a256b39ceda83d7167',
+      'packages' =>
+      [
+        [
+          'name' => 'symfony/polyfill-ctype',
+          'version' => 'v1.12.0',
+          'source' =>
+          [
+            'type' => 'git',
+            'url' => 'https://github.com/symfony/polyfill-ctype.git',
+            'reference' => '550ebaac289296ce228a706d0867afc34687e3f4',
+          ],
+        ],
+        [
+          'name' => 'symfony/yaml',
+          'version' => 'v3.4.32',
+          'source' =>
+          [
+            'type' => 'git',
+            'url' => 'https://github.com/symfony/yaml.git',
+            'reference' => '768f817446da74a776a31eea335540f9dcb53942',
+          ],
+        ],
+      ],
+      'packages-dev' =>
+      [
+        [
+          'name' => 'behat/mink',
+          'version' => 'dev-master',
+          'source' =>
+          [
+            'type' => 'git',
+            'url' => 'https://github.com/minkphp/Mink.git',
+            'reference' => 'a534fe7dac9525e8e10ca68e737c3d7e5058ec83',
+          ],
+        ],
+        [
+          'name' => 'symfony/css-selector',
+          'version' => 'v4.3.5',
+          'source' =>
+          [
+            'type' => 'git',
+            'url' => 'https://github.com/symfony/css-selector.git',
+            'reference' => 'f4b3ff6a549d9ed28b2b0ecd1781bf67cf220ee9',
+          ],
+        ],
+      ],
+    ];
+  }
+
+}
diff --git a/core/tests/Drupal/Tests/Composer/Generator/MetapackageUpdateTest.php b/core/tests/Drupal/Tests/Composer/Generator/MetapackageUpdateTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..aee83f0c682b123f89594ecbe3ff0747259a8310
--- /dev/null
+++ b/core/tests/Drupal/Tests/Composer/Generator/MetapackageUpdateTest.php
@@ -0,0 +1,85 @@
+<?php
+
+namespace Drupal\Tests\Composer\Generator;
+
+use Drupal\Composer\Generator\Builder\DrupalCoreRecommendedBuilder;
+use Drupal\Composer\Generator\Builder\DrupalDevDependenciesBuilder;
+use Drupal\Composer\Generator\Builder\DrupalPinnedDevDependenciesBuilder;
+use Drupal\Composer\Generator\PackageGenerator;
+use Drupal\Composer\Generator\Util\DrupalCoreComposer;
+
+use PHPUnit\Framework\TestCase;
+
+/**
+ * Test to see if the metapackages are up-to-date with the root composer.lock.
+ *
+ * @group Metapackage
+ */
+class MetapackageUpdateTest extends TestCase {
+
+  /**
+   * Test data for testUpdated
+   */
+  public function updatedTestData() {
+    return [
+      [
+        DrupalCoreRecommendedBuilder::class,
+        'composer/Metapackage/CoreRecommended',
+      ],
+      [
+        DrupalDevDependenciesBuilder::class,
+        'composer/Metapackage/DevDependencies',
+      ],
+      [
+        DrupalPinnedDevDependenciesBuilder::class,
+        'composer/Metapackage/PinnedDevDependencies',
+      ],
+    ];
+  }
+
+  /**
+   * Test to see if the generated metapackages are in sync with composer.lock.
+   *
+   * Note that this is not a test of code correctness, but rather it merely
+   * confirms if the package builder was used on the most recent set of
+   * metapackages.
+   *
+   * See BuilderTest.php for a test that checks for code correctness.
+   *
+   * @param string $builderClass
+   *   The metapackage builder to test.
+   * @param string $path
+   *   The relative path to the metapackage
+   *
+   *  @dataProvider updatedTestData
+   */
+  public function testUpdated($builderClass, $path) {
+    // Create a DrupalCoreComposer for the System Under Test (current repo)
+    $repositoryRoot = dirname(dirname(dirname(dirname(dirname(dirname(__DIR__))))));
+    $drupalCoreInfo = DrupalCoreComposer::createFromPath($repositoryRoot);
+
+    // Rebuild the metapackage for the composer.json / composer.lock of
+    // the current repo.
+    $builder = new $builderClass($drupalCoreInfo);
+    $generatedJson = $builder->getPackage();
+    $generatedJson = PackageGenerator::encode($generatedJson);
+
+    // Also load the most-recently-generated version of the metapackage.
+    $loadedJson = file_get_contents("$repositoryRoot/$path/composer.json");
+
+    // The generated json is the "expected", what we think the loaded
+    // json would contain, if the current patch is generated correctly
+    // (metapackages updated when composer.lock is updated).
+    $version = str_replace('.0-dev', '.x-dev', \Drupal::VERSION);
+    $message = <<< __EOT__
+The rebuilt version of $path does not match what is in the source tree.
+
+To fix, run:
+
+    COMPOSER_ROOT_VERSION=$version composer update --lock
+
+__EOT__;
+    $this->assertEquals($generatedJson, $loadedJson, $message);
+  }
+
+}
diff --git a/core/tests/Drupal/Tests/Composer/fixtures/ensureBehatDriverVersionsFixture/composer.lock b/core/tests/Drupal/Tests/Composer/fixtures/ensureBehatDriverVersionsFixture/composer.lock
new file mode 100644
index 0000000000000000000000000000000000000000..bcf6d8a30393f78dd416a27bec590b48b5c4f29d
--- /dev/null
+++ b/core/tests/Drupal/Tests/Composer/fixtures/ensureBehatDriverVersionsFixture/composer.lock
@@ -0,0 +1,17 @@
+{
+    "_readme": [
+        "This file is a fixture used to test Drupal."
+    ],
+    "content-hash": "da9910627bab73a256b39ceda83d7167",
+    "packages-dev": [
+        {
+            "name": "behat/mink-selenium2-driver",
+            "version": "dev-master",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/minkphp/MinkSelenium2Driver.git",
+                "reference": "0a09c4341621fca937a726827611b20ce3e2c259"
+            }
+        }
+    ]
+}
diff --git a/core/tests/Drupal/Tests/Core/Composer/ComposerTest.php b/core/tests/Drupal/Tests/Core/Composer/ComposerTest.php
deleted file mode 100644
index 906b45d4e525957be9653ca6b8ddbf0cd378f282..0000000000000000000000000000000000000000
--- a/core/tests/Drupal/Tests/Core/Composer/ComposerTest.php
+++ /dev/null
@@ -1,28 +0,0 @@
-<?php
-
-namespace Drupal\Tests\Core\Composer;
-
-use Drupal\Core\Composer\Composer;
-use Drupal\Tests\UnitTestCase;
-
-/**
- * @coversDefaultClass \Drupal\Core\Composer\Composer
- * @group Composer
- */
-class ComposerTest extends UnitTestCase {
-
-  /**
-   * Verify that Composer::ensureComposerVersion() doesn't break.
-   *
-   * @covers::ensureComposerVersion
-   */
-  public function testEnsureComposerVersion() {
-    try {
-      $this->assertNull(Composer::ensureComposerVersion());
-    }
-    catch (\RuntimeException $e) {
-      $this->assertRegExp('/Drupal core development requires Composer 1.9.0, but Composer /', $e->getMessage());
-    }
-  }
-
-}