Big challenge: Selenium causes the ObjectManager to fail

It might happen (in my case it did) that running Selenium tests will cause your ObjectManager to fail. After (!) the first Selenium test run, the ObjectManager or some other instance that uses the $GLOBALS[‘TYPO3_DB’] connection might fail like this:

Bildschirmfoto vom 2014-10-20 21:43:31I simply wrapped my ObjectManager call into a try-catch-block to get some more details about the issue. This is the stacktrace (you might face something similar):

#0 [internal function]: PHPUnit_Util_ErrorHandler::handleError(2, 'mysql_real_esca...', '/opt/typo3_src-...', 774, Array)
#1 [...]t3lib/class.t3lib_db.php(774): mysql_real_escape_string('Tx_Extbase_MVC_...', 0)
#2 [...]t3lib/cache/backend/class.t3lib_cache_backend_dbbackend.php(205): t3lib_DB->fullQuoteStr('Tx_Extbase_MVC_...', 'tx_extbase_cach...')
#3 [...]t3lib/cache/frontend/class.t3lib_cache_frontend_abstractfrontend.php(103): t3lib_cache_backend_DbBackend->has('Tx_Extbase_MVC_...')
#4 [...]typo3/sysext/extbase/Classes/Object/Container/ClassInfoCache.php(59): t3lib_cache_frontend_AbstractFrontend->has('Tx_Extbase_MVC_...')
#5 [...]typo3/sysext/extbase/Classes/Object/Container/Container.php(289): Tx_Extbase_Object_Container_ClassInfoCache->has('Tx_Extbase_MVC_...')
#6 [...]typo3/sysext/extbase/Classes/Object/Container/Container.php(127): Tx_Extbase_Object_Container_Container->getClassInfo('Tx_Extbase_MVC_...')
#7 [...]typo3/sysext/extbase/Classes/Object/Container/Container.php(95): Tx_Extbase_Object_Container_Container->getInstanceInternal('Tx_Extbase_MVC_...', Array)
#8 [...]typo3/sysext/extbase/Classes/Object/ObjectManager.php(83): Tx_Extbase_Object_Container_Container->getInstance('Tx_Extbase_MVC_...', Array)
#9 [...]typo3/sysext/extbase/Classes/MVC/Controller/AbstractController.php(167): Tx_Extbase_Object_ObjectManager->create('Tx_Extbase_MVC_...')
#10 [...]typo3conf/ext/csevents/Tests/Unit/Controller/AppointmentControllerTest.php(51): Tx_Extbase_MVC_Controller_AbstractController->injectObjectManager(Object(Tx_Extbase_Object_ObjectManager))
#11 [...]typo3conf/ext/phpunit/Composer/vendor/phpunit/phpunit/PHPUnit/Framework/TestCase.php(828): Tx_Csevents_Tests_Unit_Controller_AppointmentControllerTest->setUp()
#12 [...]typo3/sysext/extbase/Tests/Unit/BaseTestCase.php(62): PHPUnit_Framework_TestCase->runBare()
#13 [...]typo3conf/ext/phpunit/Composer/vendor/phpunit/phpunit/PHPUnit/Framework/TestResult.php(648): Tx_Extbase_Tests_Unit_BaseTestCase->runBare()
#14 [...]typo3conf/ext/phpunit/Composer/vendor/phpunit/phpunit/PHPUnit/Framework/TestCase.php(776): PHPUnit_Framework_TestResult->run(Object(Tx_Csevents_Tests_Unit_Controller_AppointmentControllerTest))
#15 [...]typo3conf/ext/phpunit/Composer/vendor/phpunit/phpunit/PHPUnit/Framework/TestSuite.php(775): PHPUnit_Framework_TestCase->run(Object(PHPUnit_Framework_TestResult))
#16 [...]typo3conf/ext/phpunit/Composer/vendor/phpunit/phpunit/PHPUnit/Framework/TestSuite.php(745): PHPUnit_Framework_TestSuite->runTest(Object(Tx_Csevents_Tests_Unit_Controller_AppointmentControllerTest), Object(PHPUnit_Framework_TestResult))
#17 [...]typo3conf/ext/phpunit/Composer/vendor/phpunit/phpunit/PHPUnit/Framework/TestSuite.php(705): PHPUnit_Framework_TestSuite->run(Object(PHPUnit_Framework_TestResult), false, Array, Array, false)
#18 [...]typo3conf/ext/phpunit/Classes/BackEnd/Module.php(831): PHPUnit_Framework_TestSuite->run(Object(PHPUnit_Framework_TestResult))
#19 [...]typo3conf/ext/phpunit/Classes/BackEnd/Module.php(593): Tx_Phpunit_BackEnd_Module->runAllTests(Object(PHPUnit_Framework_TestSuite), Object(PHPUnit_Framework_TestResult))
#20 [...]typo3conf/ext/phpunit/Classes/BackEnd/Module.php(288): Tx_Phpunit_BackEnd_Module->renderRunningTest()
#21 [...]typo3conf/ext/phpunit/Classes/BackEnd/Module.php(206): Tx_Phpunit_BackEnd_Module->renderRunTests()
#22 [...]typo3conf/ext/phpunit/Classes/BackEnd/index.php(86): Tx_Phpunit_BackEnd_Module->main()
#23 [...]typo3/mod.php(51): require('/home/.sdb/var/...')
#24 {main}

It’s obvious that t3lib_db does not have any link to the database after a Selenium run (look at the last “0” parameter at #1, line 2). This link is set up by t3lib_db::sql_pconnect(…) and used by calling $GLOBALS[‘TYPO3_DB’]. That’s where this database connection is stored.

The root cause for this failure is inside the PHPUnit_Framework_TestCase class that runs all tests. In some circumstances (I’ll come to the details later on) it backups the $GLOBALS before running a test and restores them back right after the test was executed. It looks like this:

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
            );
        }
    }

// more test preparation (code skipped...)

    $this->testResult = $this->runTest();

// shutting down the test (code skipped...)

    // 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();
        }
    }

// even some more cleanup activities (code skipped...)
}

You see some if-statements steering, if the backup/restore is done or not. In case of my Selenium testcases, the backup/restore was performed, i.e. the following parameters are set:

$this->runTestInSeparateProcess = NULL; // default; = FALSE would be the same
$this->inIsolation = NULL; // default; = FALSE would be the same
$this->backupGlobals = NULL; // default; = TRUE would be the same
$this->backupGlobalsBlacklist = array(); // default

With this setting, the TestCase tries to backup and restore all $GLOBALS values including the $GLOBALS[‘TYPO3_DB’] connection. PHPUnit_Util_GlobalState does this by serialising and deserialising this resource, which in case of the DB-connection does not work. Right after the restore $GLOBALS[‘TYPO3_DB’] is set to 0.

To avoid this, a) someone has to fix the restore-procedure or b) you have to avoid the backup/restore of the DB-connection. Options for b) directly come from the parameters above:

  1. Run the test in a separate process (to be described later on) or…
  2. Set the “backupGlobals” switch to “FALSE” in order to not backup/restore any global variables (which might lead to other problems, if your tests change global values) or…
  3. Exclude the TYPO3_DB variable from being backuped/restored by adding it to the blacklist.

I found a Typo3 Forum post about this issue: http://forum.typo3.org/index.php/t/140961/ It recommends to use solution #3. It works!!!

class Tx_Csevents_Tests_Selenium_BasicSeleniumTestCase extends Tx_Phpunit_Selenium_TestCase {

    // add "TYPO3_DB" to the global backup black list in order to not lose the DB connection
    protected $backupGlobalsBlacklist = array('TYPO3_DB');

    //.... (followed by the rest of this class)
}

That’s it: I already introduced a “BasicSeleniumTestCase” class to handle all Selenium Test cases. Now I use this class to overwrite the $backupGlobalsBlacklist, which is empty by default, using a list that contains “TYPO3_DB”. Now $GLOBALS[‘TYPO3_DB’] is not backuped nor restored any more, thus it’s save from being overwritten by 0 (because resource values are not serializable). Finally fixed! 😀

Leave a Reply