Maybe fix it?

This commit is contained in:
Damian Mooyman 2018-06-19 16:16:50 +12:00 committed by Maxime Rainville
parent a116b5dad8
commit 6da72d686f
5 changed files with 113 additions and 67 deletions

View File

@ -19,6 +19,13 @@ class FixtureTestState implements TestState
*/ */
private $fixtureFactories = []; private $fixtureFactories = [];
/**
* Set if fixtures have been loaded
*
* @var bool
*/
protected $loaded = [];
/** /**
* Called on setup * Called on setup
* *
@ -29,6 +36,8 @@ class FixtureTestState implements TestState
if (!$this->testNeedsDB($test)) { if (!$this->testNeedsDB($test)) {
return; return;
} }
// Ensure DB is built
$tmpDB = $test::tempDB(); $tmpDB = $test::tempDB();
if (!$tmpDB->isUsed()) { if (!$tmpDB->isUsed()) {
// Build base db // Build base db
@ -40,10 +49,11 @@ class FixtureTestState implements TestState
$tmpDB->resetDBSchema($extraObjects); $tmpDB->resetDBSchema($extraObjects);
} }
} }
DataObject::singleton()->flushCache(); DataObject::singleton()->flushCache();
// Ensure DB is built and populated // Ensure DB is built and populated
if (!$tmpDB->hasStarted()) { if (!$this->getIsLoaded(get_class($test))) {
foreach ($test->getRequireDefaultRecordsFrom() as $className) { foreach ($test->getRequireDefaultRecordsFrom() as $className) {
$instance = singleton($className); $instance = singleton($className);
if (method_exists($instance, 'requireDefaultRecords')) { if (method_exists($instance, 'requireDefaultRecords')) {
@ -155,6 +165,8 @@ class FixtureTestState implements TestState
foreach ($paths as $fixtureFile) { foreach ($paths as $fixtureFile) {
$this->loadFixture($fixtureFile, $test); $this->loadFixture($fixtureFile, $test);
} }
// Flag as loaded
$this->loaded[get_class($test)] = true;
} }
/** /**
@ -254,5 +266,17 @@ class FixtureTestState implements TestState
protected function resetFixtureFactory($class) protected function resetFixtureFactory($class)
{ {
$this->fixtureFactories[strtolower($class)] = Injector::inst()->create(FixtureFactory::class); $this->fixtureFactories[strtolower($class)] = Injector::inst()->create(FixtureFactory::class);
$this->loaded[$class] = false;
}
/**
* Check if fixtures need to be loaded for this class
*
* @param string $class Name of test to check
* @return bool
*/
protected function getIsLoaded($class)
{
return !empty($this->loaded[$class]);
} }
} }

View File

@ -668,6 +668,20 @@ abstract class Database
*/ */
abstract public function transactionEnd($chain = false); abstract public function transactionEnd($chain = false);
/**
* Return depth of current transaction
*
* @return int Nesting level, or 0 if not in a transaction
*/
public function transactionDepth()
{
// Placeholder error for transactional DBs that don't expose depth
if ($this->supportsTransactions()) {
user_error(get_class($this) . " does not support transactionDepth", E_USER_WARNING);
}
return 0;
}
/** /**
* Determines if the used database supports application-level locks, * Determines if the used database supports application-level locks,
* which is different from table- or row-level locking. * which is different from table- or row-level locking.

View File

@ -348,6 +348,11 @@ class MySQLDatabase extends Database
return true; return true;
} }
public function transactionDepth()
{
return $this->transactionNesting;
}
public function transactionEnd($chain = false) public function transactionEnd($chain = false)
{ {
// Fail if transaction isn't available // Fail if transaction isn't available
@ -365,7 +370,7 @@ class MySQLDatabase extends Database
/** /**
* In error condition, set transactionNesting to zero * In error condition, set transactionNesting to zero
*/ */
protected function discardTransactions() protected function resetTransactionNesting()
{ {
$this->transactionNesting = 0; $this->transactionNesting = 0;
} }
@ -394,7 +399,7 @@ class MySQLDatabase extends Database
// on why we need to be over-eager // on why we need to be over-eager
$isDDL = $this->getConnector()->isQueryDDL($sql); $isDDL = $this->getConnector()->isQueryDDL($sql);
if ($isDDL) { if ($isDDL) {
$this->discardTransactions(); $this->resetTransactionNesting();
} }
} }

View File

@ -23,11 +23,6 @@ class TempDatabase
*/ */
protected $name = null; protected $name = null;
/**
* @var bool If a transaction has been started
*/
protected $hasStarted = false;
/** /**
* Create a new temp database * Create a new temp database
* *
@ -73,14 +68,6 @@ class TempDatabase
return $this->isDBTemp($selected); return $this->isDBTemp($selected);
} }
/**
* @return bool
*/
public function hasStarted()
{
return $this->hasStarted;
}
/** /**
* @return bool * @return bool
*/ */
@ -94,7 +81,6 @@ class TempDatabase
*/ */
public function startTransaction() public function startTransaction()
{ {
$this->hasStarted = true;
if (static::getConn()->supportsTransactions()) { if (static::getConn()->supportsTransactions()) {
static::getConn()->transactionStart(); static::getConn()->transactionStart();
} }
@ -110,18 +96,21 @@ class TempDatabase
*/ */
public function rollbackTransaction() public function rollbackTransaction()
{ {
$success = $this->hasStarted() && static::getConn()->supportsTransactions(); // Ensure a rollback can be performed
if ($success) { $success = static::getConn()->supportsTransactions()
try { && static::getConn()->transactionDepth();
// Explicit false = gnostic error from transactionRollback if (!$success) {
if (static::getConn()->transactionRollback() === false) { return false;
$success = false; }
} try {
} catch (DatabaseException $ex) { // Explicit false = gnostic error from transactionRollback
$success = false; if (static::getConn()->transactionRollback() === false) {
} return false;
}
return true;
} catch (DatabaseException $ex) {
return false;
} }
return $success;
} }
/** /**
@ -129,13 +118,14 @@ class TempDatabase
*/ */
public function kill() public function kill()
{ {
$this->hasStarted = false; // Nothing to kill
// Delete our temporary database
if (!$this->isUsed()) { if (!$this->isUsed()) {
return; return;
} }
// Rollback any transactions (note: Success ignored)
$this->rollbackTransaction();
// Check the database actually exists // Check the database actually exists
$dbConn = $this->getConn(); $dbConn = $this->getConn();
$dbName = $dbConn->getSelectedDatabase(); $dbName = $dbConn->getSelectedDatabase();
@ -160,7 +150,6 @@ class TempDatabase
*/ */
public function clearAllData() public function clearAllData()
{ {
$this->hasStarted = false;
if (!$this->isUsed()) { if (!$this->isUsed()) {
return; return;
} }
@ -218,44 +207,12 @@ class TempDatabase
} }
/** /**
* Clear all temp DBs on this connection * Rebuild all database tables
* *
* Note: This will output results to stdout unless suppressOutput * @param array $extraDataObjects
* is set on the current db schema
*/ */
public function deleteAll() protected function rebuildTables($extraDataObjects = [])
{ {
$schema = $this->getConn()->getSchemaManager();
foreach ($schema->databaseList() as $dbName) {
if ($this->isDBTemp($dbName)) {
$schema->dropDatabase($dbName);
$schema->alterationMessage("Dropped database \"$dbName\"", 'deleted');
flush();
}
}
}
/**
* Reset the testing database's schema.
*
* @param array $extraDataObjects List of extra dataobjects to build
*/
public function resetDBSchema(array $extraDataObjects = [])
{
// pgsql doesn't allow schema updates inside transactions
// so we need to rollback any transactions before commencing a schema reset
if ($this->hasStarted()) {
// Sometimes transactions fail, rebuild
$success = $this->rollbackTransaction();
if (!$success) {
$this->kill();
$this->build();
}
}
if (!$this->isUsed()) {
return;
}
DataObject::reset(); DataObject::reset();
// clear singletons, they're caching old extension info which is used in DatabaseAdmin->doBuild() // clear singletons, they're caching old extension info which is used in DatabaseAdmin->doBuild()
@ -291,4 +248,45 @@ class TempDatabase
ClassInfo::reset_db_cache(); ClassInfo::reset_db_cache();
DataObject::singleton()->flushCache(); DataObject::singleton()->flushCache();
} }
/**
* Clear all temp DBs on this connection
*
* Note: This will output results to stdout unless suppressOutput
* is set on the current db schema
*/
public function deleteAll()
{
$schema = $this->getConn()->getSchemaManager();
foreach ($schema->databaseList() as $dbName) {
if ($this->isDBTemp($dbName)) {
$schema->dropDatabase($dbName);
$schema->alterationMessage("Dropped database \"$dbName\"", 'deleted');
flush();
}
}
}
/**
* Reset the testing database's schema.
*
* @param array $extraDataObjects List of extra dataobjects to build
*/
public function resetDBSchema(array $extraDataObjects = [])
{
// Skip if no DB
if (!$this->isUsed()) {
return;
}
try {
$this->rebuildTables($extraDataObjects);
} catch (DatabaseException $ex) {
// In case of error during build force a hard reset
// e.g. pgsql doesn't allow schema updates inside transactions
$this->kill();
$this->build();
$this->rebuildTables($extraDataObjects);
}
}
} }

View File

@ -20,6 +20,11 @@ class GroupTest extends FunctionalTest
TestMember::class TestMember::class
]; ];
protected function setUp()
{
parent::setUp();
}
public function testGroupCodeDefaultsToTitle() public function testGroupCodeDefaultsToTitle()
{ {
$g1 = new Group(); $g1 = new Group();