[Bridge\PhpUnit] Handle deprecations triggered in separate processes

This commit is contained in:
Paul Mitchum 2017-06-02 12:44:10 -07:00 committed by Nicolas Grekas
parent 788eb4f0bb
commit ff379efb59
4 changed files with 87 additions and 15 deletions

View File

@ -238,6 +238,20 @@ class DeprecationErrorHandler
} }
} }
public static function collectDeprecations($outputFile)
{
$deprecations = array();
$previousErrorHandler = set_error_handler(function ($type, $msg, $file, $line, $context = array()) use (&$deprecations, &$previousErrorHandler) {
if (E_USER_DEPRECATED !== $type && E_DEPRECATED !== $type) {
return $previousErrorHandler ? $previousErrorHandler($type, $msg, $file, $line, $context) : false;
}
$deprecations[] = array(error_reporting(), $msg);
});
register_shutdown_function(function () use ($outputFile, &$deprecations) {
file_put_contents($outputFile, serialize($deprecations));
});
}
private static function hasColorSupport() private static function hasColorSupport()
{ {
if ('\\' === DIRECTORY_SEPARATOR) { if ('\\' === DIRECTORY_SEPARATOR) {

View File

@ -39,6 +39,7 @@ class SymfonyTestsListenerTrait
private $testsWithWarnings; private $testsWithWarnings;
private $reportUselessTests; private $reportUselessTests;
private $error; private $error;
private $runsInSeparateProcess = false;
/** /**
* @param array $mockedNamespaces List of namespaces, indexed by mocked features (time-sensitive or dns-sensitive) * @param array $mockedNamespaces List of namespaces, indexed by mocked features (time-sensitive or dns-sensitive)
@ -183,6 +184,12 @@ class SymfonyTestsListenerTrait
$this->reportUselessTests = $test->getTestResultObject()->isStrictAboutTestsThatDoNotTestAnything(); $this->reportUselessTests = $test->getTestResultObject()->isStrictAboutTestsThatDoNotTestAnything();
} }
// This event is triggered before the test is re-run in isolation
if ($this->willBeIsolated($test)) {
$this->runsInSeparateProcess = tempnam(sys_get_temp_dir(), 'deprec');
putenv('SYMFONY_DEPRECATIONS_SERIALIZE='.$this->runsInSeparateProcess);
}
if (class_exists('PHPUnit_Util_Blacklist', false)) { if (class_exists('PHPUnit_Util_Blacklist', false)) {
$Test = 'PHPUnit_Util_Test'; $Test = 'PHPUnit_Util_Test';
$AssertionFailedError = 'PHPUnit_Framework_AssertionFailedError'; $AssertionFailedError = 'PHPUnit_Framework_AssertionFailedError';
@ -192,12 +199,14 @@ class SymfonyTestsListenerTrait
} }
$groups = $Test::getGroups(get_class($test), $test->getName(false)); $groups = $Test::getGroups(get_class($test), $test->getName(false));
if (in_array('time-sensitive', $groups, true)) { if (!$this->runsInSeparateProcess) {
ClockMock::register(get_class($test)); if (in_array('time-sensitive', $groups, true)) {
ClockMock::withClockMock(true); ClockMock::register(get_class($test));
} ClockMock::withClockMock(true);
if (in_array('dns-sensitive', $groups, true)) { }
DnsMock::register(get_class($test)); if (in_array('dns-sensitive', $groups, true)) {
DnsMock::register(get_class($test));
}
} }
$annotations = $Test::parseTestMethodAnnotations(get_class($test), $test->getName(false)); $annotations = $Test::parseTestMethodAnnotations(get_class($test), $test->getName(false));
@ -245,17 +254,22 @@ class SymfonyTestsListenerTrait
$this->reportUselessTests = null; $this->reportUselessTests = null;
} }
$errored = false; if ($errored = null !== $this->error) {
$test->getTestResultObject()->addError($test, $this->error, 0);
if (null !== $this->error) {
if ($BaseTestRunner::STATUS_PASSED === $test->getStatus()) {
$test->getTestResultObject()->addError($test, $this->error, 0);
$errored = true;
}
$this->error = null; $this->error = null;
} }
if ($this->runsInSeparateProcess) {
foreach (unserialize(file_get_contents($this->runsInSeparateProcess)) as $deprecation) {
if ($deprecation[0]) {
trigger_error($deprecation[1], E_USER_DEPRECATED);
} else {
@trigger_error($deprecation[1], E_USER_DEPRECATED);
}
}
$this->runsInSeparateProcess = false;
}
if ($this->expectedDeprecations) { if ($this->expectedDeprecations) {
if (!in_array($test->getStatus(), array($BaseTestRunner::STATUS_SKIPPED, $BaseTestRunner::STATUS_INCOMPLETE), true)) { if (!in_array($test->getStatus(), array($BaseTestRunner::STATUS_SKIPPED, $BaseTestRunner::STATUS_INCOMPLETE), true)) {
$test->addToAssertionCount(count($this->expectedDeprecations)); $test->addToAssertionCount(count($this->expectedDeprecations));
@ -277,7 +291,7 @@ class SymfonyTestsListenerTrait
$this->expectedDeprecations = $this->gatheredDeprecations = array(); $this->expectedDeprecations = $this->gatheredDeprecations = array();
$this->previousErrorHandler = null; $this->previousErrorHandler = null;
} }
if (-2 < $this->state && ($test instanceof \PHPUnit_Framework_TestCase || $test instanceof TestCase)) { if (!$this->runsInSeparateProcess && -2 < $this->state && ($test instanceof \PHPUnit_Framework_TestCase || $test instanceof TestCase)) {
if (in_array('time-sensitive', $groups, true)) { if (in_array('time-sensitive', $groups, true)) {
ClockMock::withClockMock(false); ClockMock::withClockMock(false);
} }
@ -315,4 +329,21 @@ class SymfonyTestsListenerTrait
} }
$this->gatheredDeprecations[] = $msg; $this->gatheredDeprecations[] = $msg;
} }
/**
* @param Test $test
*
* @return bool
*/
private function willBeIsolated($test)
{
if ($test->isInIsolation()) {
return false;
}
$r = new \ReflectionProperty($test, 'runTestInSeparateProcess');
$r->setAccessible(true);
return $r->getValue($test);
}
} }

View File

@ -0,0 +1,23 @@
<?php
namespace Symfony\Bridge\PhpUnit\Tests;
use PHPUnit\Framework\TestCase;
/**
* Don't remove this test case, it tests the legacy group.
*
* @group legacy
*
* @runTestsInSeparateProcesses
*/
class ProcessIsolationTest extends TestCase
{
/**
* @expectedDeprecation Test abc
*/
public function testIsolation()
{
@trigger_error('Test abc', E_USER_DEPRECATED);
}
}

View File

@ -14,6 +14,10 @@ use Symfony\Bridge\PhpUnit\DeprecationErrorHandler;
// Detect if we're loaded by an actual run of phpunit // Detect if we're loaded by an actual run of phpunit
if (!defined('PHPUNIT_COMPOSER_INSTALL') && !class_exists('PHPUnit_TextUI_Command', false) && !class_exists('PHPUnit\TextUI\Command', false)) { if (!defined('PHPUNIT_COMPOSER_INSTALL') && !class_exists('PHPUnit_TextUI_Command', false) && !class_exists('PHPUnit\TextUI\Command', false)) {
if ($ser = getenv('SYMFONY_DEPRECATIONS_SERIALIZE')) {
DeprecationErrorHandler::collectDeprecations($ser);
}
return; return;
} }