Skip to content

Commit e2c209d

Browse files
Implement the --strict-global-state command-line option and the beStrictAboutChangesToGlobalState configuration setting
1 parent 819bcc6 commit e2c209d

12 files changed

+178
-44
lines changed

ChangeLog-4.6.md

+1
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
## PHPUnit 4.6.0
44

5+
* Added the `--strict-global-state` command-line option and the `beStrictAboutChangesToGlobalState` configuration setting for enabling a check that global variabes, super-global variables, and static attributes in user-defined classes are not modified during a test
56
* Merged [#1527](https://github.com/sebastianbergmann/phpunit/issues/1527) and [#1529](https://github.com/sebastianbergmann/phpunit/issues/1529): Allow to define options for displaying colors
67
* Merged [#1528](https://github.com/sebastianbergmann/phpunit/issues/1528): Improve message when `PHPUnit_Framework_Constraint_Count` is used with logical operators
78
* Merged [#1537](https://github.com/sebastianbergmann/phpunit/issues/1537): Fix problem of `--stderr` with `--tap` and `--testdox`

phpunit.xsd

+1
Original file line numberDiff line numberDiff line change
@@ -208,6 +208,7 @@
208208
<xs:attribute name="beStrictAboutOutputDuringTests" type="xs:boolean" default="false"/>
209209
<xs:attribute name="beStrictAboutTestSize" type="xs:boolean" default="false"/>
210210
<xs:attribute name="beStrictAboutTodoAnnotatedTests" type="xs:boolean" default="false"/>
211+
<xs:attribute name="beStrictAboutChangesToGlobalState" type="xs:boolean" default="false"/>
211212
<xs:attribute name="checkForUnintentionallyCoveredCode" type="xs:boolean" default="false"/>
212213
<xs:attribute name="strict" type="xs:boolean" default="false"/>
213214
<xs:attribute name="testSuiteLoaderClass" type="xs:string" default="PHPUnit_Runner_StandardTestSuiteLoader"/>

src/Framework/TestCase.php

+129-44
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
use SebastianBergmann\GlobalState\Snapshot;
1212
use SebastianBergmann\GlobalState\Restorer;
1313
use SebastianBergmann\GlobalState\Blacklist;
14+
use SebastianBergmann\Diff\Differ;
1415
use SebastianBergmann\Exporter\Exporter;
1516
use Prophecy\Exception\Prediction\PredictionException;
1617
use Prophecy\Prophet;
@@ -264,6 +265,11 @@ abstract class PHPUnit_Framework_TestCase extends PHPUnit_Framework_Assert imple
264265
*/
265266
private $prophet;
266267

268+
/**
269+
* @var boolean
270+
*/
271+
private $disallowChangesToGlobalState = false;
272+
267273
/**
268274
* Constructs a test case with the given name.
269275
*
@@ -1010,6 +1016,15 @@ public function setDependencyInput(array $dependencyInput)
10101016
$this->dependencyInput = $dependencyInput;
10111017
}
10121018

1019+
/**
1020+
* @param boolean $disallowChangesToGlobalState
1021+
* @since Method available since Release 4.6.0
1022+
*/
1023+
public function setDisallowChangesToGlobalState($disallowChangesToGlobalState)
1024+
{
1025+
$this->disallowChangesToGlobalState = $disallowChangesToGlobalState;
1026+
}
1027+
10131028
/**
10141029
* Calling this method in setUp() has no effect!
10151030
*
@@ -1908,52 +1923,14 @@ private function stopOutputBuffering()
19081923

19091924
private function snapshotGlobalState()
19101925
{
1911-
if ($this->runTestInSeparateProcess || $this->inIsolation) {
1912-
return;
1913-
}
1914-
19151926
$backupGlobals = $this->backupGlobals === null || $this->backupGlobals === true;
19161927

1917-
if ($backupGlobals || $this->backupStaticAttributes) {
1918-
$blacklist = new Blacklist;
1919-
1920-
if ($backupGlobals) {
1921-
foreach ($this->backupGlobalsBlacklist as $globalVariable) {
1922-
$blacklist->addGlobalVariable($globalVariable);
1923-
}
1924-
}
1925-
1926-
if ($this->backupStaticAttributes && !defined('PHPUNIT_TESTSUITE')) {
1927-
$blacklist->addClassNamePrefix('PHPUnit');
1928-
$blacklist->addClassNamePrefix('File_Iterator');
1929-
$blacklist->addClassNamePrefix('PHP_CodeCoverage');
1930-
$blacklist->addClassNamePrefix('PHP_Invoker');
1931-
$blacklist->addClassNamePrefix('PHP_Timer');
1932-
$blacklist->addClassNamePrefix('PHP_Token');
1933-
$blacklist->addClassNamePrefix('Symfony');
1934-
$blacklist->addClassNamePrefix('Text_Template');
1935-
$blacklist->addClassNamePrefix('Doctrine\Instantiator');
1936-
1937-
foreach ($this->backupStaticAttributesBlacklist as $class => $attributes) {
1938-
foreach ($attributes as $attribute) {
1939-
$blacklist->addStaticAttribute($class, $attribute);
1940-
}
1941-
}
1942-
}
1943-
1944-
$this->snapshot = new Snapshot(
1945-
$blacklist,
1946-
$backupGlobals,
1947-
$this->backupStaticAttributes,
1948-
false,
1949-
false,
1950-
false,
1951-
false,
1952-
false,
1953-
false,
1954-
false
1955-
);
1928+
if ($this->runTestInSeparateProcess || $this->inIsolation ||
1929+
(!$backupGlobals && !$this->backupStaticAttributes)) {
1930+
return;
19561931
}
1932+
1933+
$this->snapshot = $this->createGlobalStateSnapshot($backupGlobals);
19571934
}
19581935

19591936
private function restoreGlobalState()
@@ -1962,9 +1939,18 @@ private function restoreGlobalState()
19621939
return;
19631940
}
19641941

1942+
$backupGlobals = $this->backupGlobals === null || $this->backupGlobals === true;
1943+
1944+
if ($this->disallowChangesToGlobalState) {
1945+
$this->compareGlobalStateSnapshots(
1946+
$this->snapshot,
1947+
$this->createGlobalStateSnapshot($backupGlobals)
1948+
);
1949+
}
1950+
19651951
$restorer = new Restorer;
19661952

1967-
if ($this->backupGlobals === null || $this->backupGlobals === true) {
1953+
if ($backupGlobals) {
19681954
$restorer->restoreGlobalVariables($this->snapshot);
19691955
}
19701956

@@ -1975,6 +1961,105 @@ private function restoreGlobalState()
19751961
$this->snapshot = null;
19761962
}
19771963

1964+
/**
1965+
* @param boolean $backupGlobals
1966+
* @return Snapshot
1967+
*/
1968+
private function createGlobalStateSnapshot($backupGlobals)
1969+
{
1970+
$blacklist = new Blacklist;
1971+
1972+
foreach ($this->backupGlobalsBlacklist as $globalVariable) {
1973+
$blacklist->addGlobalVariable($globalVariable);
1974+
}
1975+
1976+
if (!defined('PHPUNIT_TESTSUITE')) {
1977+
$blacklist->addClassNamePrefix('PHPUnit');
1978+
$blacklist->addClassNamePrefix('File_Iterator');
1979+
$blacklist->addClassNamePrefix('PHP_CodeCoverage');
1980+
$blacklist->addClassNamePrefix('PHP_Invoker');
1981+
$blacklist->addClassNamePrefix('PHP_Timer');
1982+
$blacklist->addClassNamePrefix('PHP_Token');
1983+
$blacklist->addClassNamePrefix('Symfony');
1984+
$blacklist->addClassNamePrefix('Text_Template');
1985+
$blacklist->addClassNamePrefix('Doctrine\Instantiator');
1986+
1987+
foreach ($this->backupStaticAttributesBlacklist as $class => $attributes) {
1988+
foreach ($attributes as $attribute) {
1989+
$blacklist->addStaticAttribute($class, $attribute);
1990+
}
1991+
}
1992+
}
1993+
1994+
return new Snapshot(
1995+
$blacklist,
1996+
$backupGlobals,
1997+
$this->backupStaticAttributes,
1998+
false,
1999+
false,
2000+
false,
2001+
false,
2002+
false,
2003+
false,
2004+
false
2005+
);
2006+
}
2007+
2008+
/**
2009+
* @param Snapshot $before
2010+
* @param Snapshot $after
2011+
* @throws PHPUnit_Framework_RiskyTestError
2012+
*/
2013+
private function compareGlobalStateSnapshots(Snapshot $before, Snapshot $after)
2014+
{
2015+
$backupGlobals = $this->backupGlobals === null || $this->backupGlobals === true;
2016+
2017+
if ($backupGlobals) {
2018+
$this->compareGlobalStateSnapshotPart(
2019+
$before->globalVariables(),
2020+
$after->globalVariables(),
2021+
"--- Global variables before the test\n+++ Global variables after the test\n"
2022+
);
2023+
2024+
$this->compareGlobalStateSnapshotPart(
2025+
$before->superGlobalVariables(),
2026+
$after->superGlobalVariables(),
2027+
"--- Super-global variables before the test\n+++ Super-global variables after the test\n"
2028+
);
2029+
}
2030+
2031+
if ($this->backupStaticAttributes) {
2032+
$this->compareGlobalStateSnapshotPart(
2033+
$before->staticAttributes(),
2034+
$after->staticAttributes(),
2035+
"--- Static attributes before the test\n+++ Static attributes after the test\n"
2036+
);
2037+
}
2038+
}
2039+
2040+
/**
2041+
* @param array $before
2042+
* @param array $after
2043+
* @param string $header
2044+
* @throws PHPUnit_Framework_RiskyTestError
2045+
*/
2046+
private function compareGlobalStateSnapshotPart(array $before, array $after, $header)
2047+
{
2048+
if ($before != $after) {
2049+
$differ = new Differ($header);
2050+
$exporter = new Exporter;
2051+
2052+
$diff = $differ->diff(
2053+
$exporter->export($before),
2054+
$exporter->export($after)
2055+
);
2056+
2057+
throw new PHPUnit_Framework_RiskyTestError(
2058+
$diff
2059+
);
2060+
}
2061+
}
2062+
19782063
/**
19792064
* @return Prophecy\Prophet
19802065
* @since Method available since Release 4.5.0

src/Framework/TestSuite.php

+17
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,11 @@ class PHPUnit_Framework_TestSuite implements PHPUnit_Framework_Test, PHPUnit_Fra
6060
*/
6161
protected $backupStaticAttributes = null;
6262

63+
/**
64+
* @var boolean
65+
*/
66+
private $disallowChangesToGlobalState = null;
67+
6368
/**
6469
* @var boolean
6570
*/
@@ -708,6 +713,7 @@ class_exists($this->name, false) &&
708713

709714
if ($test instanceof PHPUnit_Framework_TestCase ||
710715
$test instanceof PHPUnit_Framework_TestSuite) {
716+
$test->setDisallowChangesToGlobalState($this->disallowChangesToGlobalState);
711717
$test->setBackupGlobals($this->backupGlobals);
712718
$test->setBackupStaticAttributes($this->backupStaticAttributes);
713719
$test->setRunTestInSeparateProcess($this->runTestInSeparateProcess);
@@ -905,6 +911,17 @@ protected static function incompleteTest($class, $methodName, $message)
905911
return new PHPUnit_Framework_IncompleteTestCase($class, $methodName, $message);
906912
}
907913

914+
/**
915+
* @param boolean $disallowChangesToGlobalState
916+
* @since Method available since Release 4.6.0
917+
*/
918+
public function setDisallowChangesToGlobalState($disallowChangesToGlobalState)
919+
{
920+
if (is_null($this->disallowChangesToGlobalState) && is_bool($disallowChangesToGlobalState)) {
921+
$this->disallowChangesToGlobalState = $disallowChangesToGlobalState;
922+
}
923+
}
924+
908925
/**
909926
* @param boolean $backupGlobals
910927
* @since Method available since Release 3.3.0

src/TextUI/Command.php

+7
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@ class PHPUnit_TextUI_Command
7575
'disallow-test-output' => null,
7676
'enforce-time-limit' => null,
7777
'disallow-todo-tests' => null,
78+
'strict-global-state' => null,
7879
'strict' => null,
7980
'tap' => null,
8081
'testdox' => null,
@@ -491,6 +492,11 @@ protected function handleArguments(array $argv)
491492
}
492493
break;
493494

495+
case '--strict-global-state': {
496+
$this->arguments['disallowChangesToGlobalState'] = true;
497+
}
498+
break;
499+
494500
case '--disallow-test-output': {
495501
$this->arguments['disallowTestOutput'] = true;
496502
}
@@ -904,6 +910,7 @@ protected function showHelp()
904910
905911
--report-useless-tests Be strict about tests that do not test anything.
906912
--strict-coverage Be strict about unintentionally covered code.
913+
--strict-global-state Be strict about changes to global state
907914
--disallow-test-output Be strict about output during tests.
908915
--enforce-time-limit Enforce time limit based on test size.
909916
--disallow-todo-tests Disallow @todo-annotated tests.

src/TextUI/TestRunner.php

+10
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,10 @@ public function doRun(PHPUnit_Framework_Test $suite, array $arguments = array())
166166
$suite->setBackupStaticAttributes(true);
167167
}
168168

169+
if ($arguments['disallowChangesToGlobalState'] === true) {
170+
$suite->setDisallowChangesToGlobalState(true);
171+
}
172+
169173
if (is_integer($arguments['repeat'])) {
170174
$test = new PHPUnit_Extensions_RepeatedTest(
171175
$suite,
@@ -590,6 +594,11 @@ protected function handleConfiguration(array &$arguments)
590594
$arguments['backupStaticAttributes'] = $phpunitConfiguration['backupStaticAttributes'];
591595
}
592596

597+
if (isset($phpunitConfiguration['disallowChangesToGlobalState']) &&
598+
!isset($arguments['disallowChangesToGlobalState'])) {
599+
$arguments['disallowChangesToGlobalState'] = $phpunitConfiguration['disallowChangesToGlobalState'];
600+
}
601+
593602
if (isset($phpunitConfiguration['bootstrap']) &&
594603
!isset($arguments['bootstrap'])) {
595604
$arguments['bootstrap'] = $phpunitConfiguration['bootstrap'];
@@ -895,6 +904,7 @@ protected function handleConfiguration(array &$arguments)
895904
$arguments['processUncoveredFilesFromWhitelist'] = isset($arguments['processUncoveredFilesFromWhitelist']) ? $arguments['processUncoveredFilesFromWhitelist'] : false;
896905
$arguments['backupGlobals'] = isset($arguments['backupGlobals']) ? $arguments['backupGlobals'] : null;
897906
$arguments['backupStaticAttributes'] = isset($arguments['backupStaticAttributes']) ? $arguments['backupStaticAttributes'] : null;
907+
$arguments['disallowChangesToGlobalState'] = isset($arguments['disallowChangesToGlobalState']) ? $arguments['disallowChangesToGlobalState'] : null;
898908
$arguments['cacheTokens'] = isset($arguments['cacheTokens']) ? $arguments['cacheTokens'] : false;
899909
$arguments['columns'] = isset($arguments['columns']) ? $arguments['columns'] : 80;
900910
$arguments['colors'] = isset($arguments['colors']) ? $arguments['colors'] : PHPUnit_TextUI_ResultPrinter::COLOR_DEFAULT;

src/Util/Configuration.php

+8
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@
4343
* beStrictAboutTestSize="false"
4444
* beStrictAboutTodoAnnotatedTests="false"
4545
* checkForUnintentionallyCoveredCode="false"
46+
* disallowChangesToGlobalState="false"
4647
* verbose="false">
4748
* <testsuites>
4849
* <testsuite name="My Test Suite">
@@ -739,6 +740,13 @@ public function getPHPUnitConfiguration()
739740
);
740741
}
741742

743+
if ($root->hasAttribute('beStrictAboutChangesToGlobalState')) {
744+
$result['disallowChangesToGlobalState'] = $this->getBoolean(
745+
(string) $root->getAttribute('beStrictAboutChangesToGlobalState'),
746+
false
747+
);
748+
}
749+
742750
if ($root->hasAttribute('beStrictAboutTestSize')) {
743751
$result['enforceTimeLimit'] = $this->getBoolean(
744752
(string) $root->getAttribute('beStrictAboutTestSize'),

tests/TextUI/help.phpt

+1
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ Test Execution Options:
4545

4646
--report-useless-tests Be strict about tests that do not test anything.
4747
--strict-coverage Be strict about unintentionally covered code.
48+
--strict-global-state Be strict about changes to global state
4849
--disallow-test-output Be strict about output during tests.
4950
--enforce-time-limit Enforce time limit based on test size.
5051
--disallow-todo-tests Disallow @todo-annotated tests.

tests/TextUI/help2.phpt

+1
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ Test Execution Options:
4646

4747
--report-useless-tests Be strict about tests that do not test anything.
4848
--strict-coverage Be strict about unintentionally covered code.
49+
--strict-global-state Be strict about changes to global state
4950
--disallow-test-output Be strict about output during tests.
5051
--enforce-time-limit Enforce time limit based on test size.
5152
--disallow-todo-tests Disallow @todo-annotated tests.

tests/Util/ConfigurationTest.php

+1
Original file line numberDiff line numberDiff line change
@@ -353,6 +353,7 @@ public function testPHPUnitConfigurationIsReadCorrectly()
353353
array(
354354
'backupGlobals' => true,
355355
'backupStaticAttributes' => false,
356+
'disallowChangesToGlobalState' => false,
356357
'bootstrap' => '/path/to/bootstrap.php',
357358
'cacheTokens' => false,
358359
'columns' => 80,

tests/_files/configuration.xml

+1
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
beStrictAboutTestSize="false"
2424
beStrictAboutTodoAnnotatedTests="false"
2525
checkForUnintentionallyCoveredCode="false"
26+
beStrictAboutChangesToGlobalState="false"
2627
verbose="false">
2728
<testsuites>
2829
<testsuite name="My Test Suite">

tests/_files/configuration_xinclude.xml

+1
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
beStrictAboutTestSize="false"
2424
beStrictAboutTodoAnnotatedTests="false"
2525
checkForUnintentionallyCoveredCode="false"
26+
beStrictAboutChangesToGlobalState="false"
2627
verbose="false">
2728
<xi:include xmlns:xi="http://www.w3.org/2001/XInclude"
2829
href="configuration.xml"

0 commit comments

Comments
 (0)