From 6419eb1a026474d312abfb2efd8d0f4d9a7a4261 Mon Sep 17 00:00:00 2001 From: Patrick Radtke <patrick@cirrusidentity.com> Date: Mon, 24 Apr 2017 11:02:39 -0700 Subject: [PATCH] Reduce state spill over between tests A PHPUnit listener unsets SSP environmental variables and attempts to restore globals. --- lib/SimpleSAML/Configuration.php | 15 +++- lib/SimpleSAML/Utils/ClearableState.php | 17 ++++ tests/Utils/ClearStateTestListener.php | 78 +++++++++++++++++++ tests/config/no-options/config.php | 8 ++ tests/lib/SimpleSAML/ConfigurationTest.php | 10 +++ .../SimpleSAML/Locale/LocalizationTest.php | 5 ++ .../consent/lib/Auth/Process/ConsentTest.php | 3 + .../lib/Auth/Source/Auth_Source_SP_Test.php | 1 + tools/phpunit/phpunit.xml | 4 + 9 files changed, 139 insertions(+), 2 deletions(-) create mode 100644 lib/SimpleSAML/Utils/ClearableState.php create mode 100644 tests/Utils/ClearStateTestListener.php create mode 100644 tests/config/no-options/config.php diff --git a/lib/SimpleSAML/Configuration.php b/lib/SimpleSAML/Configuration.php index 4370473b6..e2a9fdbc1 100644 --- a/lib/SimpleSAML/Configuration.php +++ b/lib/SimpleSAML/Configuration.php @@ -7,7 +7,7 @@ * @author Andreas Aakre Solberg, UNINETT AS. <andreas.solberg@uninett.no> * @package SimpleSAMLphp */ -class SimpleSAML_Configuration +class SimpleSAML_Configuration implements \SimpleSAML\Utils\ClearableState { /** @@ -309,7 +309,6 @@ class SimpleSAML_Configuration } catch (SimpleSAML\Error\ConfigurationError $e) { throw \SimpleSAML\Error\CriticalConfigurationError::fromException($e); } - } throw new \SimpleSAML\Error\CriticalConfigurationError( @@ -1366,4 +1365,16 @@ class SimpleSAML_Configuration return null; } } + + /** + * Clear any configuration information cached. + * Allows for configuration files to be changed and reloaded during a given request. Most useful + * when running phpunit tests and needing to alter config.php between test cases + */ + public static function clearInternalState() + { + self::$configDirs = array(); + self::$instance = array(); + self::$loadedConfigs = array(); + } } diff --git a/lib/SimpleSAML/Utils/ClearableState.php b/lib/SimpleSAML/Utils/ClearableState.php new file mode 100644 index 000000000..b28554c6c --- /dev/null +++ b/lib/SimpleSAML/Utils/ClearableState.php @@ -0,0 +1,17 @@ +<?php + +namespace SimpleSAML\Utils; + +/** + * Indicates an implementation caches state internally and may be cleared. + * + * Primarily designed to allow SSP state to be cleared between unit tests. + * @package SimpleSAML\Utils + */ +interface ClearableState +{ + /** + * Clear any cached internal state. + */ + public static function clearInternalState(); +} diff --git a/tests/Utils/ClearStateTestListener.php b/tests/Utils/ClearStateTestListener.php new file mode 100644 index 000000000..327af5a9e --- /dev/null +++ b/tests/Utils/ClearStateTestListener.php @@ -0,0 +1,78 @@ +<?php +namespace SimpleSAML\Test\Utils; + +use PHPUnit_Framework_Test; +use PHPUnit_Framework_TestSuite; + +/** + * A PHPUnit test listener that attempts to clear global state and cached SSP configuration between test run + */ +class ClearStateTestListener extends \PHPUnit_Framework_BaseTestListener +{ + + /** + * Global state to restore between test runs + * @var array + */ + private static $backups = array(); + + /** + * Class that implement \SimpleSAML\Utils\ClearableState and should have clearInternalState called between tests + * @var array + */ + private static $clearableState = array('SimpleSAML_Configuration'); + + /** + * Variables + * @var array + */ + private static $vars_to_unset = array('SIMPLESAMLPHP_CONFIG_DIR'); + + public function __construct() + { + // Backup any state that is needed as part of processing, so we can restore it later. + // TODO: phpunit's backupGlobals = false, yet we are trying to do a similar thing here. Is that an issue? + if (!self::$backups) { + self::$backups['$_COOKIE'] = $_COOKIE; + self::$backups['$_ENV'] = $_ENV; + self::$backups['$_FILES'] = $_FILES; + self::$backups['$_GET'] = $_GET; + self::$backups['$_POST'] = $_POST; + self::$backups['$_SERVER'] = $_SERVER; + self::$backups['$_SESSION'] = isset($_SESSION) ? $_SESSION : []; + self::$backups['$_REQUEST'] = $_REQUEST; + } + } + + /** + * Clear any state needed prior to a test file running + */ + public function startTestSuite(PHPUnit_Framework_TestSuite $suite) + { + // TODO: decide how to handle tests that want to set suite level settings with setUpBeforeClass() + } + + /** + * Clear any state needed prior to a test case + * @param PHPUnit_Framework_Test $test + */ + public function startTest(PHPUnit_Framework_Test $test) + { + $_COOKIE = self::$backups['$_COOKIE']; + $_ENV = self::$backups['$_ENV']; + $_FILES = self::$backups['$_FILES']; + $_GET = self::$backups['$_GET']; + $_POST = self::$backups['$_POST']; + $_SERVER = self::$backups['$_SERVER']; + $_SESSION = self::$backups['$_SESSION']; + $_REQUEST = self::$backups['$_REQUEST']; + + foreach (self::$clearableState as $var) { + $var::clearInternalState(); + } + + foreach (self::$vars_to_unset as $var) { + putenv($var); + } + } +} diff --git a/tests/config/no-options/config.php b/tests/config/no-options/config.php new file mode 100644 index 000000000..dddd7364c --- /dev/null +++ b/tests/config/no-options/config.php @@ -0,0 +1,8 @@ +<?php +/* A configuration desigend for tests that need no configuration options set yet the SSP requires a config.php to have +been loaded. +*/ +$config = array( + // We need to set at least one key=value pair to avoid validation issues loading the file + 'some_example_option' => 'a', +); \ No newline at end of file diff --git a/tests/lib/SimpleSAML/ConfigurationTest.php b/tests/lib/SimpleSAML/ConfigurationTest.php index 129f658cf..e93e96e2a 100644 --- a/tests/lib/SimpleSAML/ConfigurationTest.php +++ b/tests/lib/SimpleSAML/ConfigurationTest.php @@ -31,6 +31,13 @@ class Test_SimpleSAML_Configuration extends PHPUnit_Framework_TestCase */ public function testCriticalConfigurationError() { + try { + SimpleSAML_Configuration::getInstance(); + $this->fail('Exception expected'); + } catch (\SimpleSAML\Error\CriticalConfigurationError $var) { + // This exception is expected. + } + //TODO: not sure this is correct. First call to getInstance() throw exception and the 2nd works?... $c = SimpleSAML_Configuration::getInstance(); $this->assertNotEmpty($c->toArray()); } @@ -97,6 +104,9 @@ class Test_SimpleSAML_Configuration extends PHPUnit_Framework_TestCase * Test SimpleSAML_Configuration::getBaseURL() */ public function testGetBaseURL() { + + // Need to set a configuration file because the SSP Logger attempts to load it. + putenv('SIMPLESAMLPHP_CONFIG_DIR=tests/config/no-options'); $c = SimpleSAML_Configuration::loadFromArray(array()); $this->assertEquals($c->getBaseURL(), 'simplesaml/'); diff --git a/tests/lib/SimpleSAML/Locale/LocalizationTest.php b/tests/lib/SimpleSAML/Locale/LocalizationTest.php index f94d1c01b..c852d0fce 100644 --- a/tests/lib/SimpleSAML/Locale/LocalizationTest.php +++ b/tests/lib/SimpleSAML/Locale/LocalizationTest.php @@ -6,6 +6,11 @@ use SimpleSAML\Locale\Localization; class LocalizationTest extends \PHPUnit_Framework_TestCase { + protected function setUp() + { + // Localization/Language code attempts to load a cookie, and looks to config.php for a name of the cookie + putenv('SIMPLESAMLPHP_CONFIG_DIR=tests/config/no-options'); + } /** diff --git a/tests/modules/consent/lib/Auth/Process/ConsentTest.php b/tests/modules/consent/lib/Auth/Process/ConsentTest.php index e83659d22..cf63fe675 100644 --- a/tests/modules/consent/lib/Auth/Process/ConsentTest.php +++ b/tests/modules/consent/lib/Auth/Process/ConsentTest.php @@ -37,6 +37,9 @@ class ConsentTest extends \PHPUnit_Framework_TestCase */ public function testCheckDisable() { + // Consent code path attempts to load metadata, which tries to find a config.php + putenv('SIMPLESAMLPHP_CONFIG_DIR=tests/config/no-options'); + // test consent disable regex with match $config = array(); diff --git a/tests/modules/saml/lib/Auth/Source/Auth_Source_SP_Test.php b/tests/modules/saml/lib/Auth/Source/Auth_Source_SP_Test.php index bb3d065db..b9b07c3c9 100644 --- a/tests/modules/saml/lib/Auth/Source/Auth_Source_SP_Test.php +++ b/tests/modules/saml/lib/Auth/Source/Auth_Source_SP_Test.php @@ -125,6 +125,7 @@ class SP_Test extends \PHPUnit_Framework_TestCase ); $this->config = Configuration::loadFromArray(array(), '[ARRAY]', 'simplesaml'); + } diff --git a/tools/phpunit/phpunit.xml b/tools/phpunit/phpunit.xml index d0233b23a..4d455eb10 100644 --- a/tools/phpunit/phpunit.xml +++ b/tools/phpunit/phpunit.xml @@ -31,4 +31,8 @@ <log type="coverage-html" target="build/coverage" title="PHP Coveralls" charset="UTF-8" yui="true" highlight="true" lowUpperBound="35" highLowerBound="70"/> <log type="coverage-clover" target="build/logs/clover.xml"/> </logging> + <listeners> + <listener class="\SimpleSAML\Test\Utils\ClearStateTestListener" file="./tests/Utils/ClearStateTestListener.php"> + </listener> + </listeners> </phpunit> -- GitLab