Commit 75dcf9af authored by catch's avatar catch

Issue #2870194 by alexpott, Mile23, jibran, larowlan, tim.plunkett: Ensure...

Issue #2870194 by alexpott, Mile23, jibran, larowlan, tim.plunkett: Ensure that process-isolated tests can use Symfony's PHPunit bridge to catch usages of deprecated code
parent b83782a7
......@@ -2801,7 +2801,7 @@
"version": "8.2.12",
"source": {
"type": "git",
"url": "https://github.com/klausi/coder.git",
"url": "https://git.drupal.org/project/coder.git",
"reference": "984c54a7b1e8f27ff1c32348df69712afd86b17f"
},
"dist": {
......@@ -4194,23 +4194,23 @@
},
{
"name": "symfony/phpunit-bridge",
"version": "v3.2.8",
"version": "v3.4.0-BETA2",
"source": {
"type": "git",
"url": "https://github.com/symfony/phpunit-bridge.git",
"reference": "00916603c524b8048906de460b7ea0dfa1651281"
"reference": "9128796e0cc46f17973e7e9345eb34696aafe00b"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/phpunit-bridge/zipball/00916603c524b8048906de460b7ea0dfa1651281",
"reference": "00916603c524b8048906de460b7ea0dfa1651281",
"url": "https://api.github.com/repos/symfony/phpunit-bridge/zipball/9128796e0cc46f17973e7e9345eb34696aafe00b",
"reference": "9128796e0cc46f17973e7e9345eb34696aafe00b",
"shasum": ""
},
"require": {
"php": ">=5.3.3"
},
"conflict": {
"phpunit/phpunit": ">=6.0"
"phpunit/phpunit": "<4.8.35|<5.4.3,>=5.0"
},
"suggest": {
"ext-zip": "Zip support is required when using bin/simple-phpunit",
......@@ -4222,7 +4222,7 @@
"type": "symfony-bridge",
"extra": {
"branch-alias": {
"dev-master": "3.2-dev"
"dev-master": "3.4-dev"
}
},
"autoload": {
......@@ -4252,13 +4252,14 @@
],
"description": "Symfony PHPUnit Bridge",
"homepage": "https://symfony.com",
"time": "2017-04-12T14:13:17+00:00"
"time": "2017-10-28T16:49:05+00:00"
}
],
"aliases": [],
"minimum-stability": "dev",
"stability-flags": {
"behat/mink": 20
"behat/mink": 20,
"symfony/phpunit-bridge": 10
},
"prefer-stable": true,
"prefer-lowest": false,
......
......@@ -47,7 +47,7 @@
"phpunit/phpunit": ">=4.8.35 <5",
"phpspec/prophecy": "^1.4",
"symfony/css-selector": "~3.2.8",
"symfony/phpunit-bridge": "~3.2.8"
"symfony/phpunit-bridge": "^3.4.0@beta"
},
"replace": {
"drupal/action": "self.version",
......
......@@ -83,6 +83,19 @@ function _drupal_error_handler_real($error_level, $message, $filename, $line, $c
'@backtrace_string' => (new \Exception())->getTraceAsString(),
], $recoverable || $to_string);
}
// If the site is a test site then fail for user deprecations so they can be
// caught by the deprecation error handler.
elseif (DRUPAL_TEST_IN_CHILD_SITE && $error_level === E_USER_DEPRECATED) {
$backtrace = debug_backtrace();
$caller = Error::getLastCaller($backtrace);
_drupal_error_header(
Markup::create(Xss::filterAdmin($message)),
'User deprecated function',
$caller['function'],
$caller['file'],
$caller['line']
);
}
}
/**
......@@ -136,25 +149,7 @@ function _drupal_log_error($error, $fatal = FALSE) {
// When running inside the testing framework, we relay the errors
// to the tested site by the way of HTTP headers.
if (DRUPAL_TEST_IN_CHILD_SITE && !headers_sent() && (!defined('SIMPLETEST_COLLECT_ERRORS') || SIMPLETEST_COLLECT_ERRORS)) {
// $number does not use drupal_static as it should not be reset
// as it uniquely identifies each PHP error.
static $number = 0;
$assertion = [
$error['@message'],
$error['%type'],
[
'function' => $error['%function'],
'file' => $error['%file'],
'line' => $error['%line'],
],
];
// For non-fatal errors (e.g. PHP notices) _drupal_log_error can be called
// multiple times per request. In that case the response is typically
// generated outside of the error handler, e.g., in a controller. As a
// result it is not possible to use a Response object here but instead the
// headers need to be emitted directly.
header('X-Drupal-Assertion-' . $number . ': ' . rawurlencode(serialize($assertion)));
$number++;
_drupal_error_header($error['@message'], $error['%type'], $error['%function'], $error['%file'], $error['%line']);
}
$response = new Response();
......@@ -325,3 +320,39 @@ function _drupal_get_error_level() {
// request on a public site, so use the non-verbose default value.
return $error_level ?: ERROR_REPORTING_DISPLAY_ALL;
}
/**
* Adds error information to headers so that tests can access it.
*
* @param $message
* The error message.
* @param $type
* The type of error.
* @param $function
* The function that emitted the error.
* @param $file
* The file that emitted the error.
* @param $line
* The line number in file that emitted the error.
*/
function _drupal_error_header($message, $type, $function, $file, $line) {
// $number does not use drupal_static as it should not be reset
// as it uniquely identifies each PHP error.
static $number = 0;
$assertion = [
$message,
$type,
[
'function' => $function,
'file' => $file,
'line' => $line,
],
];
// For non-fatal errors (e.g. PHP notices) _drupal_log_error can be called
// multiple times per request. In that case the response is typically
// generated outside of the error handler, e.g., in a controller. As a
// result it is not possible to use a Response object here but instead the
// headers need to be emitted directly.
header('X-Drupal-Assertion-' . $number . ': ' . rawurlencode(serialize($assertion)));
$number++;
}
......@@ -41,7 +41,15 @@ public function __invoke() {
// the header.
$parameters = unserialize(urldecode($header_value));
if (count($parameters) === 3) {
throw new \Exception($parameters[1] . ': ' . $parameters[0] . "\n" . Error::formatBacktrace([$parameters[2]]));
if ($parameters[1] === 'User deprecated function') {
// Fire the same deprecation message to allow it to be
// collected by
// \Symfony\Bridge\PhpUnit\DeprecationErrorHandler::collectDeprecations().
@trigger_error((string) $parameters[0], E_USER_DEPRECATED);
}
else {
throw new \Exception($parameters[1] . ': ' . $parameters[0] . "\n" . Error::formatBacktrace([$parameters[2]]));
}
}
else {
throw new \Exception('Error thrown with the wrong amount of parameters.');
......
......@@ -2,7 +2,7 @@
namespace Drupal\basic_auth\Tests;
@trigger_error(__FILE__ . ' is deprecated in Drupal 8.3.0 and will be removed before Drupal 9.0.0. Use \Drupal\Tests\basic_auth\Traits\BasicAuthTestTrait instead. See https://www.drupal.org/node/2862800.', E_USER_DEPRECATED);
@trigger_error(__NAMESPACE__ . '\BasicAuthTestTrait is deprecated in Drupal 8.3.0 and will be removed before Drupal 9.0.0. Use \Drupal\Tests\basic_auth\Traits\BasicAuthTestTrait instead. See https://www.drupal.org/node/2862800.', E_USER_DEPRECATED);
/**
* Provides common functionality for Basic Authentication test classes.
......
......@@ -2,8 +2,7 @@
namespace Drupal\migrate\Plugin\migrate\process;
@trigger_error('The ' . __NAMESPACE__ . '\Iterator is deprecated in
Drupal 8.4.x and will be removed before Drupal 9.0.0. Instead, use ' . __NAMESPACE__ . '\SubProcess', E_USER_DEPRECATED);
@trigger_error('The ' . __NAMESPACE__ . '\Iterator is deprecated in Drupal 8.4.x and will be removed before Drupal 9.0.0. Instead, use ' . __NAMESPACE__ . '\SubProcess', E_USER_DEPRECATED);
/**
* Iterates and processes an associative array.
......
......@@ -2,8 +2,7 @@
namespace Drupal\migrate\Plugin\migrate\process;
@trigger_error('The ' . __NAMESPACE__ . '\Migration is deprecated in
Drupal 8.4.0 and will be removed before Drupal 9.0.0. Instead, use ' . __NAMESPACE__ . '\MigrationLookup', E_USER_DEPRECATED);
@trigger_error('The ' . __NAMESPACE__ . '\Migration is deprecated in Drupal 8.4.0 and will be removed before Drupal 9.0.0. Instead, use ' . __NAMESPACE__ . '\MigrationLookup', E_USER_DEPRECATED);
/**
* Calculates the value of a property based on a previous migration.
......
......@@ -8,6 +8,7 @@
* Tests that migrations exist in the migration_templates directory.
*
* @group migrate
* @group legacy
*/
class MigrationDirectoryTest extends MigrateDrupalTestBase {
......@@ -18,6 +19,8 @@ class MigrationDirectoryTest extends MigrateDrupalTestBase {
/**
* Tests that migrations in the migration_templates directory are created.
*
* @expectedDeprecationMessage Use of the /migration_templates directory to store migration configuration files is deprecated in Drupal 8.1.0 and will be removed before Drupal 9.0.0.
*/
public function testMigrationDirectory() {
/** @var \Drupal\migrate\Plugin\MigrationPluginManager $plugin_manager */
......
......@@ -483,10 +483,16 @@ public function testGet() {
// GET requests - depending on web server configuration. This would usually
// be 'Transfer-Encoding: chunked'.
$ignored_headers = ['Date', 'Content-Length', 'X-Drupal-Cache', 'X-Drupal-Dynamic-Cache', 'Transfer-Encoding'];
foreach ($ignored_headers as $ignored_header) {
unset($head_headers[$ignored_header]);
unset($get_headers[$ignored_header]);
}
$header_cleaner = function ($headers) use ($ignored_headers) {
foreach ($headers as $header => $value) {
if (strpos($header, 'X-Drupal-Assertion-') === 0 || in_array($header, $ignored_headers)) {
unset($headers[$header]);
}
}
return $headers;
};
$get_headers = $header_cleaner($get_headers);
$head_headers = $header_cleaner($head_headers);
$this->assertSame($get_headers, $head_headers);
// BC: serialization_update_8302().
......
......@@ -18,6 +18,7 @@
use Drupal\system\Tests\Cache\AssertPageCacheContextsAndTagsTrait;
use Drupal\Tests\EntityViewTrait;
use Drupal\Tests\block\Traits\BlockCreationTrait as BaseBlockCreationTrait;
use Drupal\Tests\Listeners\DeprecationListener;
use Drupal\Tests\node\Traits\ContentTypeCreationTrait;
use Drupal\Tests\node\Traits\NodeCreationTrait;
use Drupal\Tests\Traits\Core\CronRunTrait;
......@@ -690,9 +691,21 @@ protected function curlHeaderCallback($curlHandler, $header) {
// generated by _drupal_log_error() in the exact form required
// by \Drupal\simpletest\WebTestBase::error().
if (preg_match('/^X-Drupal-Assertion-[0-9]+: (.*)$/', $header, $matches)) {
// Call \Drupal\simpletest\WebTestBase::error() with the parameters from
// the header.
call_user_func_array([&$this, 'error'], unserialize(urldecode($matches[1])));
$parameters = unserialize(urldecode($matches[1]));
// Handle deprecation notices triggered by system under test.
if ($parameters[1] === 'User deprecated function') {
if (getenv('SYMFONY_DEPRECATIONS_HELPER') !== 'disabled') {
$message = (string) $parameters[0];
if (!in_array($message, DeprecationListener::getSkippedDeprecations())) {
call_user_func_array([&$this, 'error'], $parameters);
}
}
}
else {
// Call \Drupal\simpletest\WebTestBase::error() with the parameters from
// the header.
call_user_func_array([&$this, 'error'], $parameters);
}
}
// Save cookies.
......
<?php
/**
* @file
* Contains functions for testing calling deprecated functions in tests.
*/
/**
* A deprecated function.
*
* @return string
* A known return value of 'known_return_value'.
*
* @deprecated in Drupal 8.4.x. Might be removed before Drupal 9.0.0. This is
* the deprecation message for deprecated_test_function().
*/
function deprecation_test_function() {
@trigger_error('This is the deprecation message for deprecation_test_function().', E_USER_DEPRECATED);
return 'known_return_value';
}
deprecation_test.route:
path: '/this-calls-a-deprecated-method'
defaults:
_controller: \Drupal\deprecation_test\DeprecatedController::deprecatedMethod
requirements:
_access: 'TRUE'
<?php
namespace Drupal\deprecation_test;
/**
* Defines a controller that calls a deprecated method.
*/
class DeprecatedController {
/**
* Controller callback.
*
* @return array
* Render array.
*/
public function deprecatedMethod() {
return [
'#markup' => deprecation_test_function(),
];
}
}
......@@ -29,6 +29,8 @@
<env name="SIMPLETEST_DB" value=""/>
<!-- Example BROWSERTEST_OUTPUT_DIRECTORY value: /path/to/webroot/sites/simpletest/browser_output -->
<env name="BROWSERTEST_OUTPUT_DIRECTORY" value=""/>
<!-- To disable deprecation testing uncomment the next line. -->
<!-- <env name="SYMFONY_DEPRECATIONS_HELPER" value="disabled"/> -->
</php>
<testsuites>
<testsuite name="unit">
......@@ -45,6 +47,10 @@
</testsuite>
</testsuites>
<listeners>
<listener class="\Drupal\Tests\Listeners\DeprecationListener">
</listener>
<!-- The Symfony deprecation listener has to come after the Drupal
deprecation listener -->
<listener class="Symfony\Bridge\PhpUnit\SymfonyTestsListener">
</listener>
<listener class="\Drupal\Tests\Listeners\DrupalStandardsListener">
......
......@@ -305,6 +305,10 @@ function simpletest_script_help() {
--non-html Removes escaping from output. Useful for reading results on the
CLI.
--suppress-deprecations
Stops tests from failing if deprecation errors are triggered.
<test1>[,<test2>[,<test3> ...]]
One or more tests to be run. By default, these are interpreted
......@@ -369,6 +373,7 @@ function simpletest_script_parse_args() {
'test_names' => array(),
'repeat' => 1,
'die-on-fail' => FALSE,
'suppress-deprecations' => FALSE,
'browser' => FALSE,
// Used internally.
'test-id' => 0,
......@@ -784,6 +789,12 @@ function simpletest_script_run_one_test($test_id, $test_class) {
$methods = array();
}
$test = new $class_name($test_id);
if ($args['suppress-deprecations']) {
putenv('SYMFONY_DEPRECATIONS_HELPER=disabled');
}
else {
putenv('SYMFONY_DEPRECATIONS_HELPER=strict');
}
if (is_subclass_of($test_class, TestCase::class)) {
$status = simpletest_script_run_phpunit($test_id, $test_class);
}
......@@ -834,7 +845,7 @@ function simpletest_script_command($test_id, $test_class) {
}
$command .= ' --php ' . escapeshellarg($php);
$command .= " --test-id $test_id";
foreach (array('verbose', 'keep-results', 'color', 'die-on-fail') as $arg) {
foreach (array('verbose', 'keep-results', 'color', 'die-on-fail', 'suppress-deprecations') as $arg) {
if ($args[$arg]) {
$command .= ' --' . $arg;
}
......
<?php
namespace Drupal\FunctionalTests\Core\Test;
use Drupal\Core\Url;
use Drupal\Tests\BrowserTestBase;
/**
* Tests Drupal's integration with Symfony PHPUnit Bridge.
*
* @group Test
* @group legacy
*/
class PhpUnitBridgeTest extends BrowserTestBase {
protected static $modules = ['deprecation_test'];
/**
* @expectedDeprecation This is the deprecation message for deprecation_test_function().
*/
public function testSilencedError() {
$this->assertEquals('known_return_value', deprecation_test_function());
}
/**
* @expectedDeprecation This is the deprecation message for deprecation_test_function().
*/
public function testErrorOnSiteUnderTest() {
$this->drupalGet(Url::fromRoute('deprecation_test.route'));
}
}
<?php
namespace Drupal\KernelTests\Core\Test;
use Drupal\KernelTests\KernelTestBase;
use Drupal\deprecation_test\Deprecation\FixtureDeprecatedClass;
/**
* Test how kernel tests interact with deprecation errors.
*
* @group Test
* @group legacy
*/
class PhpUnitBridgeTest extends KernelTestBase {
public static $modules = ['deprecation_test'];
/**
* @expectedDeprecation Drupal\deprecation_test\Deprecation\FixtureDeprecatedClass is deprecated.
*/
public function testDeprecatedClass() {
$deprecated = new FixtureDeprecatedClass();
$this->assertEquals('test', $deprecated->testFunction());
}
/**
* @expectedDeprecation This is the deprecation message for deprecation_test_function().
*/
public function testDeprecatedFunction() {
$this->assertEquals('known_return_value', \deprecation_test_function());
}
}
<?php
namespace Drupal\Tests\Core\Test;
use Drupal\Tests\UnitTestCase;
use Drupal\deprecation_test\Deprecation\FixtureDeprecatedClass;
/**
* Test how unit tests interact with deprecation errors in process isolation.
*
* @runTestsInSeparateProcesses
* @preserveGlobalState disabled
*
* @group Test
* @group legacy
*/
class PhpUnitBridgeIsolatedTest extends UnitTestCase {
/**
* @expectedDeprecation Drupal\deprecation_test\Deprecation\FixtureDeprecatedClass is deprecated.
*/
public function testDeprecatedClass() {
$deprecated = new FixtureDeprecatedClass();
$this->assertEquals('test', $deprecated->testFunction());
}
}
<?php
namespace Drupal\Tests\Core\Test;
use Drupal\Tests\UnitTestCase;
use Drupal\deprecation_test\Deprecation\FixtureDeprecatedClass;
/**
* Test how unit tests interact with deprecation errors.
*
* @group Test
* @group legacy
*/
class PhpUnitBridgeTest extends UnitTestCase {
/**
* @expectedDeprecation Drupal\deprecation_test\Deprecation\FixtureDeprecatedClass is deprecated.
*/
public function testDeprecatedClass() {
$deprecated = new FixtureDeprecatedClass();
$this->assertEquals('test', $deprecated->testFunction());
}
public function testDeprecatedFunction() {
$this->markTestIncomplete('Modules are not loaded for unit tests, so deprecated_test_function() will not be available.');
$this->assertEquals('known_return_value', \deprecation_test_function());
}
}
This diff is collapsed.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment