class CoreUpdateTest in Automatic Updates 8.2
Tests an end-to-end update of Drupal core.
@group automatic_updates
Hierarchy
- class \Drupal\BuildTests\Framework\BuildTestBase extends \PHPUnit\Framework\TestCase uses ExternalCommandRequirementsTrait, PhpunitCompatibilityTrait
- class \Drupal\BuildTests\QuickStart\QuickStartTestBase
- class \Drupal\Tests\automatic_updates\Build\UpdateTestBase uses LocalPackagesTrait, SettingsTrait
- class \Drupal\Tests\automatic_updates\Build\CoreUpdateTest
- class \Drupal\Tests\automatic_updates\Build\UpdateTestBase uses LocalPackagesTrait, SettingsTrait
- class \Drupal\BuildTests\QuickStart\QuickStartTestBase
Expanded class hierarchy of CoreUpdateTest
File
- tests/
src/ Build/ CoreUpdateTest.php, line 10
Namespace
Drupal\Tests\automatic_updates\BuildView source
class CoreUpdateTest extends UpdateTestBase {
/**
* {@inheritdoc}
*/
protected function createTestSite(string $template) : void {
// Build the test site and alter its copy of core so that it thinks it's
// running Drupal 9.8.0, which will never actually exist in the real world.
// Then, prepare a secondary copy of the core code base, masquerading as
// Drupal 9.8.1, which will be the version of core we update to. These two
// versions are referenced in the fake release metadata in our fake release
// metadata (see fixtures/release-history/drupal.0.0.xml).
parent::createTestSite($template);
$this
->setCoreVersion($this
->getWebRoot() . '/core', '9.8.0');
$this
->alterPackage($this
->getWorkspaceDirectory(), $this
->getConfigurationForUpdate('9.8.1'));
// Install Drupal and ensure it's using the fake release metadata to fetch
// information about available updates.
$this
->installQuickStart('minimal');
$this
->setReleaseMetadata([
'drupal' => '9.8.1-security',
]);
$this
->formLogin($this->adminUsername, $this->adminPassword);
$this
->installModules([
'automatic_updates',
'automatic_updates_test',
'update_test',
]);
// Ensure that Drupal thinks we are running 9.8.0, then refresh information
// about available updates.
$this
->assertCoreVersion('9.8.0');
$this
->checkForUpdates();
// Ensure that an update to 9.8.1 is available.
$this
->visit('/admin/modules/automatic-update');
$this
->getMink()
->assertSession()
->pageTextContains('9.8.1');
}
/**
* {@inheritdoc}
*/
protected function tearDown() : void {
if ($this->destroyBuild) {
$this
->deleteCopiedPackages();
}
parent::tearDown();
}
/**
* Modifies a Drupal core code base to set its version.
*
* @param string $dir
* The directory of the Drupal core code base.
* @param string $version
* The version number to set.
*/
private function setCoreVersion(string $dir, string $version) : void {
$this
->alterPackage($dir, [
'version' => $version,
]);
$drupal_php = "{$dir}/lib/Drupal.php";
$this
->assertIsWritable($drupal_php);
$code = file_get_contents($drupal_php);
$code = preg_replace("/const VERSION = '([0-9]+\\.?){3}(-dev)?';/", "const VERSION = '{$version}';", $code);
file_put_contents($drupal_php, $code);
}
/**
* Returns composer.json changes that are needed to update core.
*
* This will clone the following packages into temporary directories:
* - drupal/core
* - drupal/core-recommended
* - drupal/core-project-message
* - drupal/core-composer-scaffold
* The cloned packages will be assigned the given version number, and the test
* site's composer.json will use the clones as path repositories.
*
* @param string $version
* The version of core we will be updating to.
*
* @return array
* The changes to merge into the test site's composer.json.
*/
protected function getConfigurationForUpdate(string $version) : array {
$repositories = [];
// Create a fake version of core with the given version number, and change
// its README so that we can actually be certain that we update to this
// fake version.
$dir = $this
->copyPackage($this
->getWebRoot() . '/core');
$this
->setCoreVersion($dir, $version);
file_put_contents("{$dir}/README.txt", "Placeholder for Drupal core {$version}.");
$repositories['drupal/core'] = $this
->createPathRepository($dir);
$drupal_root = $this
->getDrupalRoot();
// Create a fake version of drupal/core-recommended which itself requires
// the fake version of core we just created.
$dir = $this
->copyPackage("{$drupal_root}/composer/Metapackage/CoreRecommended");
$this
->alterPackage($dir, [
'require' => [
'drupal/core' => $version,
],
'version' => $version,
]);
$repositories['drupal/core-recommended'] = $this
->createPathRepository($dir);
// Create a fake version of drupal/core-project-message.
$dir = $this
->copyPackage("{$drupal_root}/composer/Plugin/ProjectMessage");
$this
->alterPackage($dir, [
'version' => $version,
]);
$repositories['drupal/core-project-message'] = $this
->createPathRepository($dir);
// Create a fake version of drupal/core-composer-scaffold.
$dir = $this
->copyPackage("{$drupal_root}/composer/Plugin/Scaffold");
$this
->alterPackage($dir, [
'version' => $version,
]);
$repositories['drupal/core-composer-scaffold'] = $this
->createPathRepository($dir);
// Create a fake version of drupal/core-vendor-hardening.
$dir = $this
->copyPackage("{$drupal_root}/composer/Plugin/VendorHardening");
$this
->alterPackage($dir, [
'version' => $version,
]);
$repositories['drupal/core-vendor-hardening'] = $this
->createPathRepository($dir);
return [
'repositories' => $repositories,
];
}
/**
* Data provider for end-to-end update tests.
*
* @return array[]
* Sets of arguments to pass to the test method.
*/
public function providerTemplate() : array {
return [
[
'drupal/recommended-project',
],
[
'drupal/legacy-project',
],
];
}
/**
* Tests an end-to-end core update via the API.
*
* @param string $template
* The template project from which to build the test site.
*
* @dataProvider providerTemplate
*/
public function testApi(string $template) : void {
$this
->createTestSite($template);
$mink = $this
->getMink();
$assert_session = $mink
->assertSession();
// Ensure that the update is prevented if the web root and/or vendor
// directories are not writable.
$this
->assertReadOnlyFileSystemError($template, '/automatic-update-test/update/9.8.1');
$mink
->getSession()
->reload();
$assert_session
->pageTextContains('9.8.1');
}
/**
* Tests an end-to-end core update via the UI.
*
* @param string $template
* The template project from which to build the test site.
*
* @dataProvider providerTemplate
*/
public function testUi(string $template) : void {
$this
->createTestSite($template);
$mink = $this
->getMink();
$session = $mink
->getSession();
$page = $session
->getPage();
$assert_session = $mink
->assertSession();
$this
->visit('/admin/modules');
$assert_session
->pageTextContains('There is a security update available for your version of Drupal.');
$page
->clickLink('Update');
// Ensure that the update is prevented if the web root and/or vendor
// directories are not writable.
$this
->assertReadOnlyFileSystemError($template, parse_url($session
->getCurrentUrl(), PHP_URL_PATH));
$session
->reload();
$assert_session
->pageTextNotContains('There is a security update available for your version of Drupal.');
$page
->pressButton('Update');
$this
->waitForBatchJob();
$assert_session
->pageTextContains('Ready to update');
$page
->pressButton('Continue');
$this
->waitForBatchJob();
$assert_session
->pageTextContains('Update complete!');
$assert_session
->pageTextNotContains('There is a security update available for your version of Drupal.');
$this
->assertUpdateSuccessful();
}
/**
* Tests an end-to-end core update via cron.
*
* @param string $template
* The template project from which to build the test site.
*
* @dataProvider providerTemplate
*/
public function testCron(string $template) : void {
$this
->createTestSite($template);
$this
->visit('/admin/reports/status');
$this
->getMink()
->getSession()
->getPage()
->clickLink('Run cron');
$this
->assertUpdateSuccessful();
}
/**
* Asserts that the update is prevented if the filesystem isn't writable.
*
* @param string $template
* The project template used to build the test site. See ::createTestSite()
* for the possible values.
* @param string $url
* A URL where we can see the error message which is raised when parts of
* the file system are not writable. This URL will be visited twice: once
* for the web root, and once for the vendor directory.
*/
private function assertReadOnlyFileSystemError(string $template, string $url) : void {
$directories = [
'Drupal' => rtrim($this
->getWebRoot(), './'),
];
// The location of the vendor directory depends on which project template
// was used to build the test site.
if ($template === 'drupal/recommended-project') {
$directories['vendor'] = $this
->getWorkspaceDirectory() . '/vendor';
}
elseif ($template === 'drupal/legacy-project') {
$directories['vendor'] = $directories['Drupal'] . '/vendor';
}
$assert_session = $this
->getMink()
->assertSession();
foreach ($directories as $type => $path) {
chmod($path, 0555);
$this
->assertDirectoryIsNotWritable($path);
$this
->visit($url);
$assert_session
->pageTextContains("The {$type} directory \"{$path}\" is not writable.");
chmod($path, 0755);
$this
->assertDirectoryIsWritable($path);
}
}
/**
* Asserts that Drupal core was successfully updated.
*/
private function assertUpdateSuccessful() : void {
// The update form should not have any available updates.
// @todo Figure out why this assertion fails when the batch processor
// redirects directly to the update form, instead of update.status, when
// updating via the UI.
$this
->visit('/admin/modules/automatic-update');
$this
->getMink()
->assertSession()
->pageTextContains('No update available');
// The status page should report that we're running Drupal 9.8.1.
$this
->assertCoreVersion('9.8.1');
// The fake placeholder text from ::getConfigurationForUpdate() should be
// present in the README.
$placeholder = file_get_contents($this
->getWebRoot() . '/core/README.txt');
$this
->assertSame('Placeholder for Drupal core 9.8.1.', $placeholder);
}
}
Members
Name![]() |
Modifiers | Type | Description | Overrides |
---|---|---|---|---|
BuildTestBase:: |
private | property | The most recent command process. | |
BuildTestBase:: |
protected | property | Default to destroying build artifacts after a test finishes. | |
BuildTestBase:: |
private static | property | Our native host name, used by PHP when it starts up the server. | |
BuildTestBase:: |
private | property | Port that will be tested. | |
BuildTestBase:: |
private | property | The Mink session manager. | |
BuildTestBase:: |
private | property | A list of ports used by the test. | |
BuildTestBase:: |
private | property | The docroot for the server process. | |
BuildTestBase:: |
private | property | The process that's running the HTTP server. | |
BuildTestBase:: |
private | property | The working directory where this test will manipulate files. | |
BuildTestBase:: |
public | function | Asserts that the last command returned the specified exit code. | |
BuildTestBase:: |
public | function | Assert that text is present in the output of the most recent command. | |
BuildTestBase:: |
public | function | Asserts that the last command ran without error. | |
BuildTestBase:: |
public | function | Helper function to assert that the last visit was a Drupal site. | |
BuildTestBase:: |
public | function | Assert that text is present in the error output of the most recent command. | |
BuildTestBase:: |
protected | function | Checks whether a port is available. | |
BuildTestBase:: |
public | function | Copy the current working codebase into a workspace. | |
BuildTestBase:: |
public | function | Run a command. | |
BuildTestBase:: |
protected | function | Discover an available port number. | |
BuildTestBase:: |
public | function | Get a default Finder object for a Drupal codebase. | |
BuildTestBase:: |
protected | function | Get the root path of this Drupal codebase. | |
BuildTestBase:: |
public | function | Get the Mink instance. | |
BuildTestBase:: |
protected | function | Get the port number for requests. | |
BuildTestBase:: |
protected | function | Get the working directory within the workspace, creating if necessary. | |
BuildTestBase:: |
public | function | Full path to the workspace where this test can build. | |
BuildTestBase:: |
protected | function | Set up the Mink session manager. | |
BuildTestBase:: |
protected | function | Do the work of making a server process. | |
BuildTestBase:: |
protected | function | ||
BuildTestBase:: |
public static | function | ||
BuildTestBase:: |
protected | function | Makes a local test server using PHP's internal HTTP server. | |
BuildTestBase:: |
protected | function | Stop the HTTP server, zero out all necessary variables. | |
CoreUpdateTest:: |
private | function | Asserts that the update is prevented if the filesystem isn't writable. | |
CoreUpdateTest:: |
private | function | Asserts that Drupal core was successfully updated. | |
CoreUpdateTest:: |
protected | function |
Uses our already-installed dependencies to build a test site to update. Overrides UpdateTestBase:: |
|
CoreUpdateTest:: |
protected | function | Returns composer.json changes that are needed to update core. | |
CoreUpdateTest:: |
public | function | Data provider for end-to-end update tests. | |
CoreUpdateTest:: |
private | function | Modifies a Drupal core code base to set its version. | |
CoreUpdateTest:: |
protected | function |
Overrides UpdateTestBase:: |
|
CoreUpdateTest:: |
public | function | Tests an end-to-end core update via the API. | |
CoreUpdateTest:: |
public | function | Tests an end-to-end core update via cron. | |
CoreUpdateTest:: |
public | function | Tests an end-to-end core update via the UI. | |
ExternalCommandRequirementsTrait:: |
private static | property | A list of existing external commands we've already discovered. | |
ExternalCommandRequirementsTrait:: |
private static | function | Checks whether required external commands are available per test class. | |
ExternalCommandRequirementsTrait:: |
private static | function | Checks missing external command requirements. | |
ExternalCommandRequirementsTrait:: |
private static | function | Checks whether required external commands are available per method. | |
ExternalCommandRequirementsTrait:: |
private static | function | Determine if an external command is available. | 3 |
JsonTrait:: |
protected | function | Reads JSON data from a file and returns it as an array. | |
JsonTrait:: |
protected | function | Writes an array of data to a file as JSON. | |
LocalPackagesTrait:: |
private | property | The paths of temporary copies of packages. | |
LocalPackagesTrait:: |
protected | function | Alters a package's composer.json file. | |
LocalPackagesTrait:: |
protected | function | Copies a package's entire directory to another location. Aliased as: traitCopyPackage | |
LocalPackagesTrait:: |
protected | function | Defines a local path repository for a given path. | |
LocalPackagesTrait:: |
protected | function | Deletes all copied packages. | |
LocalPackagesTrait:: |
protected | function | Generates local path repositories for a set of installed packages. | |
LocalPackagesTrait:: |
protected | function | Returns the path of an installed package, relative to composer.json. Aliased as: traitGetPackagePath | |
LocalPackagesTrait:: |
private | function | Reads all package information from a composer.lock file. | |
PhpunitCompatibilityTrait:: |
public | function | Returns a mock object for the specified class using the available method. | |
PhpunitCompatibilityTrait:: |
public | function | Compatibility layer for PHPUnit 6 to support PHPUnit 4 code. | |
QuickStartTestBase:: |
protected | property | Password of the admin account generated during install. | |
QuickStartTestBase:: |
protected | property | User name of the admin account generated during install. | |
SettingsTrait:: |
protected | function | Appends some PHP code to settings.php. | |
SettingsTrait:: |
private | function | Ensures that settings.php is writable. | |
UpdateTestBase:: |
private | property | A secondary server instance, to serve XML metadata about available updates. | |
UpdateTestBase:: |
private | property | The test site's document root, relative to the workspace directory. | |
UpdateTestBase:: |
protected | function | Asserts that a specific version of Drupal core is running. | |
UpdateTestBase:: |
protected | function | Checks for available updates. | |
UpdateTestBase:: |
protected | function | ||
UpdateTestBase:: |
public | function |
Helper that uses Drupal's user/login form to log in. Overrides QuickStartTestBase:: |
|
UpdateTestBase:: |
protected | function | Returns the initial data to write to the test site's composer.json. | |
UpdateTestBase:: |
protected | function | ||
UpdateTestBase:: |
protected | function | Returns the full path to the test site's document root. | |
UpdateTestBase:: |
protected | function | Installs modules in the UI. | |
UpdateTestBase:: |
public | function |
Install a Drupal site using the quick start feature. Overrides QuickStartTestBase:: |
|
UpdateTestBase:: |
protected | function | Prepares the test site to serve an XML feed of available release metadata. | |
UpdateTestBase:: |
public | function |
Visit a URI on the HTTP server. Overrides BuildTestBase:: |
|
UpdateTestBase:: |
protected | function | Waits for an active batch job to finish. |