Issue #3063887 by mondrake, alexpott, longwave, catch, xjm: Support PHPUnit 8 in Drupal 9, drop support for PHPUnit 7

merge-requests/2419/head
catch 2020-02-14 12:21:47 +00:00
parent da21d14ac5
commit 5d457e37c5
17 changed files with 191 additions and 68 deletions

View File

@ -22,7 +22,7 @@
"composer/composer": "^1.9.1",
"drupal/coder": "^8.3.7",
"mikey179/vfsstream": "^1.6.8",
"phpunit/phpunit": "^7",
"phpunit/phpunit": "^8.4.1",
"phpspec/prophecy": "^1.7",
"symfony/css-selector": "^4.4",
"symfony/phpunit-bridge": "^4.4",

142
composer.lock generated
View File

@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "851ca5677985628c73d2d91f044760ce",
"content-hash": "d8477fb085432c15724c090b7d20f6d2",
"packages": [
{
"name": "asm89/stack-cors",
@ -4985,40 +4985,40 @@
},
{
"name": "phpunit/php-code-coverage",
"version": "6.1.4",
"version": "7.0.10",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/php-code-coverage.git",
"reference": "807e6013b00af69b6c5d9ceb4282d0393dbb9d8d"
"reference": "f1884187926fbb755a9aaf0b3836ad3165b478bf"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/807e6013b00af69b6c5d9ceb4282d0393dbb9d8d",
"reference": "807e6013b00af69b6c5d9ceb4282d0393dbb9d8d",
"url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/f1884187926fbb755a9aaf0b3836ad3165b478bf",
"reference": "f1884187926fbb755a9aaf0b3836ad3165b478bf",
"shasum": ""
},
"require": {
"ext-dom": "*",
"ext-xmlwriter": "*",
"php": "^7.1",
"phpunit/php-file-iterator": "^2.0",
"php": "^7.2",
"phpunit/php-file-iterator": "^2.0.2",
"phpunit/php-text-template": "^1.2.1",
"phpunit/php-token-stream": "^3.0",
"phpunit/php-token-stream": "^3.1.1",
"sebastian/code-unit-reverse-lookup": "^1.0.1",
"sebastian/environment": "^3.1 || ^4.0",
"sebastian/environment": "^4.2.2",
"sebastian/version": "^2.0.1",
"theseer/tokenizer": "^1.1"
"theseer/tokenizer": "^1.1.3"
},
"require-dev": {
"phpunit/phpunit": "^7.0"
"phpunit/phpunit": "^8.2.2"
},
"suggest": {
"ext-xdebug": "^2.6.0"
"ext-xdebug": "^2.7.2"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "6.1-dev"
"dev-master": "7.0-dev"
}
},
"autoload": {
@ -5044,7 +5044,7 @@
"testing",
"xunit"
],
"time": "2018-10-31T16:06:48+00:00"
"time": "2019-11-20T13:55:58+00:00"
},
{
"name": "phpunit/php-file-iterator",
@ -5237,53 +5237,52 @@
},
{
"name": "phpunit/phpunit",
"version": "7.5.20",
"version": "8.5.2",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/phpunit.git",
"reference": "9467db479d1b0487c99733bb1e7944d32deded2c"
"reference": "018b6ac3c8ab20916db85fa91bf6465acb64d1e0"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/9467db479d1b0487c99733bb1e7944d32deded2c",
"reference": "9467db479d1b0487c99733bb1e7944d32deded2c",
"url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/018b6ac3c8ab20916db85fa91bf6465acb64d1e0",
"reference": "018b6ac3c8ab20916db85fa91bf6465acb64d1e0",
"shasum": ""
},
"require": {
"doctrine/instantiator": "^1.1",
"doctrine/instantiator": "^1.2.0",
"ext-dom": "*",
"ext-json": "*",
"ext-libxml": "*",
"ext-mbstring": "*",
"ext-xml": "*",
"myclabs/deep-copy": "^1.7",
"phar-io/manifest": "^1.0.2",
"phar-io/version": "^2.0",
"php": "^7.1",
"phpspec/prophecy": "^1.7",
"phpunit/php-code-coverage": "^6.0.7",
"phpunit/php-file-iterator": "^2.0.1",
"ext-xmlwriter": "*",
"myclabs/deep-copy": "^1.9.1",
"phar-io/manifest": "^1.0.3",
"phar-io/version": "^2.0.1",
"php": "^7.2",
"phpspec/prophecy": "^1.8.1",
"phpunit/php-code-coverage": "^7.0.7",
"phpunit/php-file-iterator": "^2.0.2",
"phpunit/php-text-template": "^1.2.1",
"phpunit/php-timer": "^2.1",
"sebastian/comparator": "^3.0",
"sebastian/diff": "^3.0",
"sebastian/environment": "^4.0",
"sebastian/exporter": "^3.1",
"sebastian/global-state": "^2.0",
"phpunit/php-timer": "^2.1.2",
"sebastian/comparator": "^3.0.2",
"sebastian/diff": "^3.0.2",
"sebastian/environment": "^4.2.2",
"sebastian/exporter": "^3.1.1",
"sebastian/global-state": "^3.0.0",
"sebastian/object-enumerator": "^3.0.3",
"sebastian/resource-operations": "^2.0",
"sebastian/resource-operations": "^2.0.1",
"sebastian/type": "^1.1.3",
"sebastian/version": "^2.0.1"
},
"conflict": {
"phpunit/phpunit-mock-objects": "*"
},
"require-dev": {
"ext-pdo": "*"
},
"suggest": {
"ext-soap": "*",
"ext-xdebug": "*",
"phpunit/php-invoker": "^2.0"
"phpunit/php-invoker": "^2.0.0"
},
"bin": [
"phpunit"
@ -5291,7 +5290,7 @@
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "7.5-dev"
"dev-master": "8.5-dev"
}
},
"autoload": {
@ -5317,7 +5316,7 @@
"testing",
"xunit"
],
"time": "2020-01-08T08:45:45+00:00"
"time": "2020-01-08T08:49:49+00:00"
},
{
"name": "sebastian/code-unit-reverse-lookup",
@ -5606,23 +5605,26 @@
},
{
"name": "sebastian/global-state",
"version": "2.0.0",
"version": "3.0.0",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/global-state.git",
"reference": "e8ba02eed7bbbb9e59e43dedd3dddeff4a56b0c4"
"reference": "edf8a461cf1d4005f19fb0b6b8b95a9f7fa0adc4"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/e8ba02eed7bbbb9e59e43dedd3dddeff4a56b0c4",
"reference": "e8ba02eed7bbbb9e59e43dedd3dddeff4a56b0c4",
"url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/edf8a461cf1d4005f19fb0b6b8b95a9f7fa0adc4",
"reference": "edf8a461cf1d4005f19fb0b6b8b95a9f7fa0adc4",
"shasum": ""
},
"require": {
"php": "^7.0"
"php": "^7.2",
"sebastian/object-reflector": "^1.1.1",
"sebastian/recursion-context": "^3.0"
},
"require-dev": {
"phpunit/phpunit": "^6.0"
"ext-dom": "*",
"phpunit/phpunit": "^8.0"
},
"suggest": {
"ext-uopz": "*"
@ -5630,7 +5632,7 @@
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "2.0-dev"
"dev-master": "3.0-dev"
}
},
"autoload": {
@ -5653,7 +5655,7 @@
"keywords": [
"global state"
],
"time": "2017-04-27T15:39:26+00:00"
"time": "2019-02-01T05:30:01+00:00"
},
{
"name": "sebastian/object-enumerator",
@ -5842,6 +5844,52 @@
"homepage": "https://www.github.com/sebastianbergmann/resource-operations",
"time": "2018-10-04T04:07:39+00:00"
},
{
"name": "sebastian/type",
"version": "1.1.3",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/type.git",
"reference": "3aaaa15fa71d27650d62a948be022fe3b48541a3"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/type/zipball/3aaaa15fa71d27650d62a948be022fe3b48541a3",
"reference": "3aaaa15fa71d27650d62a948be022fe3b48541a3",
"shasum": ""
},
"require": {
"php": "^7.2"
},
"require-dev": {
"phpunit/phpunit": "^8.2"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.1-dev"
}
},
"autoload": {
"classmap": [
"src/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"BSD-3-Clause"
],
"authors": [
{
"name": "Sebastian Bergmann",
"email": "sebastian@phpunit.de",
"role": "lead"
}
],
"description": "Collection of value objects that represent the types of the PHP type system",
"homepage": "https://github.com/sebastianbergmann/type",
"time": "2019-07-02T08:10:15+00:00"
},
{
"name": "sebastian/version",
"version": "2.0.1",

View File

@ -16,7 +16,7 @@
"justinrainbow/json-schema": "^5.2",
"mikey179/vfsstream": "^1.6.8",
"phpspec/prophecy": "^1.7",
"phpunit/phpunit": "^7",
"phpunit/phpunit": "^8.4.1",
"symfony/browser-kit": "^4.4",
"symfony/css-selector": "^4.4",
"symfony/debug": "^4.4",

View File

@ -30,22 +30,23 @@
"phpdocumentor/reflection-docblock": "4.3.4",
"phpdocumentor/type-resolver": "1.0.1",
"phpspec/prophecy": "v1.10.2",
"phpunit/php-code-coverage": "6.1.4",
"phpunit/php-code-coverage": "7.0.10",
"phpunit/php-file-iterator": "2.0.2",
"phpunit/php-text-template": "1.2.1",
"phpunit/php-timer": "2.1.2",
"phpunit/php-token-stream": "3.1.1",
"phpunit/phpunit": "7.5.20",
"phpunit/phpunit": "8.5.2",
"sebastian/code-unit-reverse-lookup": "1.0.1",
"sebastian/comparator": "3.0.2",
"sebastian/diff": "3.0.2",
"sebastian/environment": "4.2.3",
"sebastian/exporter": "3.1.2",
"sebastian/global-state": "2.0.0",
"sebastian/global-state": "3.0.0",
"sebastian/object-enumerator": "3.0.3",
"sebastian/object-reflector": "1.1.1",
"sebastian/recursion-context": "3.0.0",
"sebastian/resource-operations": "2.0.1",
"sebastian/type": "1.1.3",
"sebastian/version": "2.0.1",
"seld/jsonlint": "1.7.2",
"seld/phar-utils": "1.0.2",

View File

@ -180,7 +180,7 @@ class Connection extends DatabaseConnection {
$count = $this->query('SELECT COUNT(*) FROM ' . $prefix . '.sqlite_master WHERE type = :type AND name NOT LIKE :pattern', [':type' => 'table', ':pattern' => 'sqlite_%'])->fetchField();
// We can prune the database file if it doesn't have any tables.
if ($count == 0) {
if ($count == 0 && file_exists($this->connectionOptions['database'] . '-' . $prefix)) {
// Detaching the database fails at this point, but no other queries
// are executed after the connection is destructed so we can simply
// remove the database file.

View File

@ -7,6 +7,7 @@ use Drupal\Component\Annotation\Reflection\MockFileFinder;
use Drupal\Component\Utility\NestedArray;
use Drupal\Core\Extension\ExtensionDiscovery;
use Drupal\Core\Test\Exception\MissingGroupException;
use Drupal\TestTools\PhpUnitCompatibility\PhpUnit8\ClassWriter;
use PHPUnit\Util\Test;
/**
@ -116,6 +117,10 @@ class TestDiscovery {
$this->classLoader->addPsr4($prefix, $paths);
}
$loader = require __DIR__ . '/../../../../../autoload.php';
// Ensure we have a valid TestCase class.
ClassWriter::mutateTestBase($loader);
return $this->testNamespaces;
}

View File

@ -9,7 +9,8 @@
beStrictAboutTestsThatDoNotTestAnything="true"
beStrictAboutOutputDuringTests="true"
beStrictAboutChangesToGlobalState="true"
printerClass="\Drupal\Tests\Listeners\HtmlOutputPrinter">
printerClass="\Drupal\Tests\Listeners\HtmlOutputPrinter"
cacheResult="false">
<php>
<!-- Set error reporting to E_ALL. -->
<ini name="error_reporting" value="32767"/>

View File

@ -18,6 +18,7 @@ use Drupal\Core\Test\RunTests\TestFileParser;
use Drupal\Core\Test\TestDatabase;
use Drupal\Core\Test\TestRunnerKernel;
use Drupal\Core\Test\TestDiscovery;
use Drupal\TestTools\PhpUnitCompatibility\PhpUnit8\ClassWriter;
use PHPUnit\Framework\TestCase;
use PHPUnit\Runner\Version;
use Symfony\Component\Console\Output\ConsoleOutput;
@ -504,6 +505,7 @@ function simpletest_script_init() {
$autoloader = require_once __DIR__ . '/../../autoload.php';
// The PHPUnit compatibility layer needs to be available to autoload tests.
$autoloader->add('Drupal\\TestTools', __DIR__ . '/../tests');
ClassWriter::mutateTestBase($autoloader);
// Get URL from arguments.
if (!empty($args['url'])) {

View File

@ -0,0 +1,54 @@
<?php
namespace Drupal\TestTools\PhpUnitCompatibility\PhpUnit8;
/**
* Helper class to rewrite PHPUnit's TestCase class.
*
* This class contains static methods only and is not meant to be instantiated.
*
* @internal
* This should only be called by test running code. Drupal 9 will provide best
* effort to maintain this class for the Drupal 9 cycle. However if changes to
* PHP or PHPUnit make this impossible then support will be removed.
*/
final class ClassWriter {
/**
* This class should not be instantiated.
*/
private function __construct() {
}
/**
* Mutates the TestCase class from PHPUnit to make it compatible with Drupal.
*
* @param object $autoloader
* The autoloader.
*
* @throws \ReflectionException
*/
public static function mutateTestBase($autoloader) {
// If the class exists already there is nothing we can do. Hopefully this
// is happening because this has been called already. The call from
// \Drupal\Core\Test\TestDiscovery::registerTestNamespaces() necessitates
// this protection.
if (class_exists('PHPUnit\Framework\TestCase', FALSE)) {
return;
}
// Inspired by Symfony's simple-phpunit remove typehints from TestCase.
$reflector = new \ReflectionClass($autoloader);
$vendor_dir = dirname(dirname($reflector->getFileName()));
// Mutate TestCase code to make it compatible with Drupal 8 and 9 tests.
$alteredCode = file_get_contents($alteredFile = $vendor_dir . '/phpunit/phpunit/src/Framework/TestCase.php');
$alteredCode = preg_replace('/^ ((?:protected|public)(?: static)? function \w+\(\)): void/m', ' $1', $alteredCode);
$alteredCode = str_replace("__DIR__ . '/../Util/", "'$vendor_dir/phpunit/phpunit/src/Util/", $alteredCode);
$filename = __DIR__ . '/../../../../../../sites/simpletest/TestCase.php';
// Only write when necessary.
if (!file_exists($filename) || md5_file($filename) !== md5($alteredCode)) {
file_put_contents($filename, $alteredCode);
}
include $filename;
}
}

View File

@ -479,7 +479,7 @@ class ContentEntityBaseUnitTest extends UnitTestCase {
*/
public function testLabel() {
$this->expectDeprecation('Entity type ' . $this->entityTypeId . ' defines a label callback. Support for that is deprecated in drupal:8.0.0 and will be removed in drupal:9.0.0. Override the EntityInterface::label() method instead. See https://www.drupal.org/node/3050794');
$this->addExpectedDeprecationMessage('Entity type ' . $this->entityTypeId . ' defines a label callback. Support for that is deprecated in drupal:8.0.0 and will be removed in drupal:9.0.0. Override the EntityInterface::label() method instead. See https://www.drupal.org/node/3050794');
// Make a mock with one method that we use as the entity's label callback.
// We check that it is called, and that the entity's label is the callback's

View File

@ -173,7 +173,7 @@ class EntityUnitTest extends UnitTestCase {
*/
public function testLabel() {
$this->expectDeprecation('Entity type ' . $this->entityTypeId . ' defines a label callback. Support for that is deprecated in drupal:8.0.0 and will be removed in drupal:9.0.0. Override the EntityInterface::label() method instead. See https://www.drupal.org/node/3050794');
$this->addExpectedDeprecationMessage('Entity type ' . $this->entityTypeId . ' defines a label callback. Support for that is deprecated in drupal:8.0.0 and will be removed in drupal:9.0.0. Override the EntityInterface::label() method instead. See https://www.drupal.org/node/3050794');
// Make a mock with one method that we use as the entity's uri_callback. We
// check that it is called, and that the entity's label is the callback's

View File

@ -5,7 +5,7 @@ namespace Drupal\Tests\Core\Plugin;
use Drupal\Component\Plugin\ConfigurableInterface;
use Drupal\Component\Plugin\PluginBase;
use Drupal\Core\Plugin\DefaultSingleLazyPluginCollection;
use PHPUnit\Framework\MockObject\Matcher\InvokedRecorder;
use PHPUnit\Framework\MockObject\Rule\InvocationOrder;
/**
* @coversDefaultClass \Drupal\Core\Plugin\DefaultSingleLazyPluginCollection
@ -16,7 +16,7 @@ class DefaultSingleLazyPluginCollectionTest extends LazyPluginCollectionTestBase
/**
* {@inheritdoc}
*/
protected function setupPluginCollection(InvokedRecorder $create_count = NULL) {
protected function setupPluginCollection(InvocationOrder $create_count = NULL) {
$definitions = $this->getPluginDefinitions();
$this->pluginInstances['apple'] = new ConfigurablePlugin(['id' => 'apple', 'key' => 'value'], 'apple', $definitions['apple']);
$this->pluginInstances['banana'] = new ConfigurablePlugin(['id' => 'banana', 'key' => 'other_value'], 'banana', $definitions['banana']);

View File

@ -4,7 +4,7 @@ namespace Drupal\Tests\Core\Plugin;
use Drupal\Core\Plugin\DefaultLazyPluginCollection;
use Drupal\Tests\UnitTestCase;
use PHPUnit\Framework\MockObject\Matcher\InvokedRecorder;
use PHPUnit\Framework\MockObject\Rule\InvocationOrder;
/**
* Provides a base class for plugin collection tests.
@ -54,12 +54,12 @@ abstract class LazyPluginCollectionTestBase extends UnitTestCase {
/**
* Sets up the default plugin collection.
*
* @param \PHPUnit\Framework\MockObject\Matcher\InvokedRecorder|null $create_count
* @param \PHPUnit\Framework\MockObject\Rule\InvocationOrder|null $create_count
* (optional) The number of times that createInstance() is expected to be
* called. For example, $this->any(), $this->once(), $this->exactly(6).
* Defaults to $this->never().
*/
protected function setupPluginCollection(InvokedRecorder $create_count = NULL) {
protected function setupPluginCollection(InvocationOrder $create_count = NULL) {
$this->pluginInstances = [];
$map = [];
foreach ($this->getPluginDefinitions() as $plugin_id => $definition) {

View File

@ -14,21 +14,21 @@ class ExpectDeprecationTest extends UnitTestCase {
use ExpectDeprecationTrait;
/**
* @covers ::expectDeprecation
* @covers ::addExpectedDeprecationMessage
*/
public function testExpectDeprecation() {
$this->expectDeprecation('Test deprecation');
$this->addExpectedDeprecationMessage('Test deprecation');
@trigger_error('Test deprecation', E_USER_DEPRECATED);
}
/**
* @covers ::expectDeprecation
* @covers ::addExpectedDeprecationMessage
* @runInSeparateProcess
* @preserveGlobalState disabled
*/
public function testExpectDeprecationInIsolation() {
$this->expectDeprecation('Test isolated deprecation');
$this->expectDeprecation('Test isolated deprecation2');
$this->addExpectedDeprecationMessage('Test isolated deprecation');
$this->addExpectedDeprecationMessage('Test isolated deprecation2');
@trigger_error('Test isolated deprecation', E_USER_DEPRECATED);
@trigger_error('Test isolated deprecation2', E_USER_DEPRECATED);
}

View File

@ -185,6 +185,15 @@ trait DeprecationListenerTrait {
// testing using \Symfony\Component\ErrorHandler\DebugClassLoader.
'The "Twig\Environment::getTemplateClass()" method is considered internal. It may change without further notice. You should not extend it from "Drupal\Core\Template\TwigEnvironment".',
'"Symfony\Component\DomCrawler\Crawler::text()" will normalize whitespaces by default in Symfony 5.0, set the second "$normalizeWhitespace" argument to false to retrieve the non-normalized version of the text.',
// PHPUnit 8.
"The \"Drupal\Tests\Listeners\AfterSymfonyListener\" class implements \"PHPUnit\Framework\TestListener\" that is deprecated Use the `TestHook` interfaces instead.",
"The \"Drupal\Tests\Listeners\AfterSymfonyListener\" class uses \"PHPUnit\Framework\TestListenerDefaultImplementation\" that is deprecated The `TestListener` interface is deprecated.",
"The \"PHPUnit\TextUI\ResultPrinter\" class is considered internal This class is not covered by the backward compatibility promise for PHPUnit. It may change without further notice. You should not use it from \"Drupal\Tests\Listeners\HtmlOutputPrinter\".",
"The \"Drupal\Tests\Listeners\DrupalListener\" class implements \"PHPUnit\Framework\TestListener\" that is deprecated Use the `TestHook` interfaces instead.",
"The \"Drupal\Tests\Listeners\DrupalListener\" class uses \"PHPUnit\Framework\TestListenerDefaultImplementation\" that is deprecated The `TestListener` interface is deprecated.",
"The \"PHPUnit\Framework\TestSuite\" class is considered internal This class is not covered by the backward compatibility promise for PHPUnit. It may change without further notice. You should not use it from \"Drupal\Tests\TestSuites\TestSuiteBase\".",
"The \"PHPUnit\Framework\TestCase::__construct()\" method is considered internal This method is not covered by the backward compatibility promise for PHPUnit. It may change without further notice. You should not extend it from \"Drupal\Tests\BrowserTestBase\".",
"The \"PHPUnit\Framework\TestCase::__construct()\" method is considered internal This method is not covered by the backward compatibility promise for PHPUnit. It may change without further notice. You should not extend it from \"Drupal\FunctionalTests\Update\UpdatePathTestBase\".",
];
}

View File

@ -25,7 +25,7 @@ trait ExpectDeprecationTrait {
* @param string $message
* The expected deprecation message.
*/
protected function expectDeprecation($message) {
protected function addExpectedDeprecationMessage($message) {
$this->expectedDeprecations([$message]);
}

View File

@ -8,6 +8,7 @@
*/
use Drupal\Component\Assertion\Handle;
use Drupal\TestTools\PhpUnitCompatibility\PhpUnit8\ClassWriter;
/**
* Finds all valid extension directories recursively within a given directory.
@ -150,7 +151,9 @@ function drupal_phpunit_populate_class_loader() {
}
// Do class loader population.
drupal_phpunit_populate_class_loader();
$loader = drupal_phpunit_populate_class_loader();
ClassWriter::mutateTestBase($loader);
// Set sane locale settings, to ensure consistent string, dates, times and
// numbers handling.