Skip to content

Commit

Permalink
Initial work on refactoring to use SebastianBergmann\GlobalState for
Browse files Browse the repository at this point in the history
snapshotting global state.

An expected value in Framework_TestCaseTest::testStaticAttributesBackupPost() was changed to make this test pass after refactoring. While I really hate doing that it appears that the previous expected value was wrong, thus hiding a bug in the previous implementation.
  • Loading branch information
sebastianbergmann committed Sep 3, 2014
1 parent a1e1dd6 commit 59ce590
Show file tree
Hide file tree
Showing 4 changed files with 82 additions and 204 deletions.
1 change: 1 addition & 0 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
"sebastian/diff": "~1.1",
"sebastian/environment": "~1.0",
"sebastian/exporter": "~1.0",
"sebastian/global-state": "1.0.*@dev",
"sebastian/version": "~1.0",
"ext-dom": "*",
"ext-json": "*",
Expand Down
117 changes: 80 additions & 37 deletions src/Framework/TestCase.php
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,10 @@
* @since File available since Release 2.0.0
*/

use SebastianBergmann\GlobalState\Snapshot;
use SebastianBergmann\GlobalState\Restorer;
use SebastianBergmann\GlobalState\Blacklist;

/**
* A TestCase defines the fixture to run multiple tests.
*
Expand Down Expand Up @@ -287,6 +291,11 @@ abstract class PHPUnit_Framework_TestCase extends PHPUnit_Framework_Assert imple
*/
private $outputBufferingLevel;

/**
* @var SebastianBergmann\GlobalState\Snapshot
*/
private $snapshot;

/**
* Constructs a test case with the given name.
*
Expand Down Expand Up @@ -724,29 +733,9 @@ public function runBare()
{
$this->numAssertions = 0;

// Backup the $GLOBALS array and static attributes.
if ($this->runTestInSeparateProcess !== true &&
$this->inIsolation !== true) {
if ($this->backupGlobals === null ||
$this->backupGlobals === true) {
PHPUnit_Util_GlobalState::backupGlobals(
$this->backupGlobalsBlacklist
);
}

if ($this->backupStaticAttributes === true) {
PHPUnit_Util_GlobalState::backupStaticAttributes(
$this->backupStaticAttributesBlacklist
);
}
}

$this->snapshotGlobalState();
$this->startOutputBuffering();

// Clean up stat cache.
clearstatcache();

// Backup the cwd
$currentWorkingDirectory = getcwd();

$hookMethods = PHPUnit_Util_Test::getHookMethods(get_class($this));
Expand Down Expand Up @@ -813,28 +802,13 @@ public function runBare()

$this->stopOutputBuffering();

// Clean up stat cache.
clearstatcache();

// Restore the cwd if it was changed by the test
if ($currentWorkingDirectory != getcwd()) {
chdir($currentWorkingDirectory);
}

// Restore the $GLOBALS array and static attributes.
if ($this->runTestInSeparateProcess !== true &&
$this->inIsolation !== true) {
if ($this->backupGlobals === null ||
$this->backupGlobals === true) {
PHPUnit_Util_GlobalState::restoreGlobals(
$this->backupGlobalsBlacklist
);
}

if ($this->backupStaticAttributes === true) {
PHPUnit_Util_GlobalState::restoreStaticAttributes();
}
}
$this->restoreGlobalState();

// Clean up INI settings.
foreach ($this->iniSettings as $varName => $oldValue) {
Expand Down Expand Up @@ -1956,4 +1930,73 @@ private function stopOutputBuffering()
$this->outputBufferingActive = false;
$this->outputBufferingLevel = ob_get_level();
}

private function snapshotGlobalState()
{
if ($this->runTestInSeparateProcess || $this->inIsolation) {
return;
}

$backupGlobals = $this->backupGlobals === null || $this->backupGlobals === true;

if ($backupGlobals || $this->backupStaticAttributes) {
$blacklist = new Blacklist;

if ($backupGlobals) {
foreach ($this->backupGlobalsBlacklist as $globalVariable) {
$blacklist->addGlobalVariable($globalVariable);
}
}

if ($this->backupStaticAttributes && !defined('PHPUNIT_TESTSUITE')) {
$blacklist->addClassNamePrefix('PHPUnit');
$blacklist->addClassNamePrefix('File_Iterator');
$blacklist->addClassNamePrefix('PHP_CodeCoverage');
$blacklist->addClassNamePrefix('PHP_Invoker');
$blacklist->addClassNamePrefix('PHP_Timer');
$blacklist->addClassNamePrefix('PHP_Token');
$blacklist->addClassNamePrefix('Symfony');
$blacklist->addClassNamePrefix('Text_Template');
$blacklist->addClassNamePrefix('Doctrine\Instantiator');

foreach ($this->backupStaticAttributesBlacklist as $class => $attributes) {
foreach ($attributes as $attribute) {
$blacklist->addStaticAttribute($class, $attribute);
}
}
}

$this->snapshot = new Snapshot(
$blacklist,
$backupGlobals,
$this->backupStaticAttributes,
false,
false,
false,
false,
false,
false,
false
);
}
}

private function restoreGlobalState()
{
if (!$this->snapshot instanceof Snapshot) {
return;
}

$restorer = new Restorer;

if ($this->backupGlobals === null || $this->backupGlobals === true) {
$restorer->restoreGlobalVariables($this->snapshot);
}

if ($this->backupStaticAttributes) {
$restorer->restoreStaticAttributes($this->snapshot);
}

$this->snapshot = null;
}
}
166 changes: 0 additions & 166 deletions src/Util/GlobalState.php
Original file line number Diff line number Diff line change
Expand Up @@ -56,16 +56,6 @@
*/
class PHPUnit_Util_GlobalState
{
/**
* @var array
*/
protected static $globals = array();

/**
* @var array
*/
protected static $staticAttributes = array();

/**
* @var array
*/
Expand All @@ -91,97 +81,6 @@ class PHPUnit_Util_GlobalState
'HTTP_POST_FILES'
);

public static function backupGlobals(array $blacklist)
{
self::$globals = array();
$superGlobalArrays = self::getSuperGlobalArrays();

foreach ($superGlobalArrays as $superGlobalArray) {
if (!in_array($superGlobalArray, $blacklist)) {
self::backupSuperGlobalArray($superGlobalArray);
}
}

foreach (array_keys($GLOBALS) as $key) {
if ($key != 'GLOBALS' &&
!in_array($key, $superGlobalArrays) &&
!in_array($key, $blacklist) &&
!$GLOBALS[$key] instanceof Closure) {
self::$globals['GLOBALS'][$key] = serialize($GLOBALS[$key]);
}
}
}

public static function restoreGlobals(array $blacklist)
{
if (ini_get('register_long_arrays') == '1') {
$superGlobalArrays = array_merge(
self::$superGlobalArrays, self::$superGlobalArraysLong
);
} else {
$superGlobalArrays = self::$superGlobalArrays;
}

foreach ($superGlobalArrays as $superGlobalArray) {
if (!in_array($superGlobalArray, $blacklist)) {
self::restoreSuperGlobalArray($superGlobalArray);
}
}

foreach (array_keys($GLOBALS) as $key) {
if ($key != 'GLOBALS' &&
!in_array($key, $superGlobalArrays) &&
!in_array($key, $blacklist)) {
if (isset(self::$globals['GLOBALS'][$key])) {
$GLOBALS[$key] = unserialize(
self::$globals['GLOBALS'][$key]
);
} else {
unset($GLOBALS[$key]);
}
}
}

self::$globals = array();
}

protected static function backupSuperGlobalArray($superGlobalArray)
{
self::$globals[$superGlobalArray] = array();

if (isset($GLOBALS[$superGlobalArray]) &&
is_array($GLOBALS[$superGlobalArray])) {
foreach ($GLOBALS[$superGlobalArray] as $key => $value) {
self::$globals[$superGlobalArray][$key] = serialize($value);
}
}
}

protected static function restoreSuperGlobalArray($superGlobalArray)
{
if (isset($GLOBALS[$superGlobalArray]) &&
is_array($GLOBALS[$superGlobalArray]) &&
isset(self::$globals[$superGlobalArray])) {
$keys = array_keys(
array_merge(
$GLOBALS[$superGlobalArray], self::$globals[$superGlobalArray]
)
);

foreach ($keys as $key) {
if (isset(self::$globals[$superGlobalArray][$key])) {
$GLOBALS[$superGlobalArray][$key] = unserialize(
self::$globals[$superGlobalArray][$key]
);
} else {
unset($GLOBALS[$superGlobalArray][$key]);
}
}
}

self::$globals[$superGlobalArray] = array();
}

public static function getIncludedFilesAsString()
{
return static::processIncludedFilesAsString(get_included_files());
Expand Down Expand Up @@ -302,71 +201,6 @@ protected static function getSuperGlobalArrays()
}
}

public static function backupStaticAttributes(array $blacklist)
{
self::$staticAttributes = array();
$declaredClasses = get_declared_classes();
$declaredClassesNum = count($declaredClasses);

for ($i = $declaredClassesNum - 1; $i >= 0; $i--) {
if (strpos($declaredClasses[$i], 'PHPUnit') !== 0 &&
strpos($declaredClasses[$i], 'File_Iterator') !== 0 &&
strpos($declaredClasses[$i], 'PHP_CodeCoverage') !== 0 &&
strpos($declaredClasses[$i], 'PHP_Invoker') !== 0 &&
strpos($declaredClasses[$i], 'PHP_Timer') !== 0 &&
strpos($declaredClasses[$i], 'PHP_Token_Stream') !== 0 &&
strpos($declaredClasses[$i], 'Symfony') !== 0 &&
strpos($declaredClasses[$i], 'Text_Template') !== 0 &&
strpos($declaredClasses[$i], 'Instantiator') !== 0 &&
strpos($declaredClasses[$i], 'LazyMap') !== 0) {
$class = new ReflectionClass($declaredClasses[$i]);

if ($class->isSubclassOf('PHPUnit_Framework_Test')) {
continue;
}

if (!$class->isUserDefined()) {
break;
}

$backup = array();

foreach ($class->getProperties() as $attribute) {
if ($attribute->isStatic()) {
$name = $attribute->getName();

if (!isset($blacklist[$declaredClasses[$i]]) ||
!in_array($name, $blacklist[$declaredClasses[$i]])) {
$attribute->setAccessible(true);
$value = $attribute->getValue();

if (!$value instanceof Closure) {
$backup[$name] = serialize($value);
}
}
}
}

if (!empty($backup)) {
self::$staticAttributes[$declaredClasses[$i]] = $backup;
}
}
}
}

public static function restoreStaticAttributes()
{
foreach (self::$staticAttributes as $className => $staticAttributes) {
foreach ($staticAttributes as $name => $value) {
$reflector = new ReflectionProperty($className, $name);
$reflector->setAccessible(true);
$reflector->setValue(unserialize($value));
}
}

self::$staticAttributes = array();
}

protected static function exportVariable($variable)
{
if (is_scalar($variable) || is_null($variable) ||
Expand Down
2 changes: 1 addition & 1 deletion tests/Framework/TestCaseTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -375,7 +375,7 @@ public function testStaticAttributesBackupPre()
public function testStaticAttributesBackupPost()
{
$this->assertNotSame($GLOBALS['singleton'], Singleton::getInstance());
$this->assertSame(123, self::$_testStatic);
$this->assertSame(0, self::$_testStatic);
}

public function testIsInIsolationReturnsFalse()
Expand Down

0 comments on commit 59ce590

Please sign in to comment.