wiki.techinc.nl/tests/phpunit/includes/libs/rdbms/database/DatabaseTest.php

609 lines
19 KiB
PHP
Raw Normal View History

<?php
use Wikimedia\Rdbms\Database;
use Wikimedia\Rdbms\IDatabase;
use Wikimedia\Rdbms\DatabaseMysqli;
use Wikimedia\Rdbms\LBFactorySingle;
use Wikimedia\Rdbms\TransactionProfiler;
use Wikimedia\TestingAccessWrapper;
use Wikimedia\Rdbms\DatabaseSqlite;
use Wikimedia\Rdbms\DatabasePostgres;
use Wikimedia\Rdbms\DatabaseMssql;
class DatabaseTest extends PHPUnit\Framework\TestCase {
use MediaWikiCoversValidator;
Clean and repair many phpunit tests (+ fix implied configuration) This commit depends on the introduction of MediaWikiTestCase::setMwGlobals in change Iccf6ea81f4. Various tests already set their globals, but forgot to restore them afterwards, or forgot to call the parent setUp, tearDown... Either way they won't have to anymore with setMwGlobals. Consistent use of function characteristics: * protected function setUp * protected function tearDown * public static function (provide..) (Matching the function signature with PHPUnit/Framework/TestCase.php) Replaces: * public function (setUp|tearDown)\( * protected function $1( * \tfunction (setUp|tearDown)\( * \tprotected function $1( * \tfunction (data|provide)\( * \tpublic static function $1\( Also renamed a few "data#", "provider#" and "provides#" functions to "provide#" for consistency. This also removes confusion where the /media tests had a few private methods called dataFile(), which were sometimes expected to be data providers. Fixes: TimestampTest often failed due to a previous test setting a different language (it tests "1 hour ago" so need to make sure it is set to English). MWNamespaceTest became a lot cleaner now that it executes with a known context. Though the now-redundant code that was removed didn't work anyway because wgContentNamespaces isn't keyed by namespace id, it had them was values... FileBackendTest: * Fixed: "PHP Fatal: Using $this when not in object context" HttpTest * Added comment about: "PHP Fatal: Call to protected MWHttpRequest::__construct()" (too much unrelated code to fix in this commit) ExternalStoreTest * Add an assertTrue as well, without it the test is useless because regardless of whether wgExternalStores is true or false it only uses it if it is an array. Change-Id: I9d2b148e57bada64afeb7d5a99bec0e58f8e1561
2012-10-08 10:56:20 +00:00
protected function setUp() {
$this->db = new DatabaseTestHelper( __CLASS__ . '::' . $this->getName() );
}
/**
* @dataProvider provideAddQuotes
* @covers Wikimedia\Rdbms\Database::factory
*/
public function testFactory() {
$m = Database::NEW_UNCONNECTED; // no-connect mode
$p = [ 'host' => 'localhost', 'user' => 'me', 'password' => 'myself', 'dbname' => 'i' ];
$this->assertInstanceOf( DatabaseMysqli::class, Database::factory( 'mysqli', $p, $m ) );
$this->assertInstanceOf( DatabaseMysqli::class, Database::factory( 'MySqli', $p, $m ) );
$this->assertInstanceOf( DatabaseMysqli::class, Database::factory( 'MySQLi', $p, $m ) );
$this->assertInstanceOf( DatabasePostgres::class, Database::factory( 'postgres', $p, $m ) );
$this->assertInstanceOf( DatabasePostgres::class, Database::factory( 'Postgres', $p, $m ) );
$x = $p + [ 'port' => 10000, 'UseWindowsAuth' => false ];
$this->assertInstanceOf( DatabaseMssql::class, Database::factory( 'mssql', $x, $m ) );
$x = $p + [ 'dbFilePath' => 'some/file.sqlite' ];
$this->assertInstanceOf( DatabaseSqlite::class, Database::factory( 'sqlite', $x, $m ) );
$x = $p + [ 'dbDirectory' => 'some/file' ];
$this->assertInstanceOf( DatabaseSqlite::class, Database::factory( 'sqlite', $x, $m ) );
}
public static function provideAddQuotes() {
return [
[ null, 'NULL' ],
[ 1234, "'1234'" ],
[ 1234.5678, "'1234.5678'" ],
[ 'string', "'string'" ],
[ 'string\'s cause trouble', "'string\'s cause trouble'" ],
];
}
/**
* @dataProvider provideAddQuotes
* @covers Wikimedia\Rdbms\Database::addQuotes
*/
public function testAddQuotes( $input, $expected ) {
$this->assertEquals( $expected, $this->db->addQuotes( $input ) );
}
public static function provideTableName() {
// Formatting is mostly ignored since addIdentifierQuotes is abstract.
// For testing of addIdentifierQuotes, see actual Database subclas tests.
return [
'local' => [
'tablename',
'tablename',
'quoted',
],
'local-raw' => [
'tablename',
'tablename',
'raw',
],
'shared' => [
'sharedb.tablename',
'tablename',
'quoted',
[ 'dbname' => 'sharedb', 'schema' => null, 'prefix' => '' ],
],
'shared-raw' => [
'sharedb.tablename',
'tablename',
'raw',
[ 'dbname' => 'sharedb', 'schema' => null, 'prefix' => '' ],
],
'shared-prefix' => [
'sharedb.sh_tablename',
'tablename',
'quoted',
[ 'dbname' => 'sharedb', 'schema' => null, 'prefix' => 'sh_' ],
],
'shared-prefix-raw' => [
'sharedb.sh_tablename',
'tablename',
'raw',
[ 'dbname' => 'sharedb', 'schema' => null, 'prefix' => 'sh_' ],
],
'foreign' => [
'databasename.tablename',
'databasename.tablename',
'quoted',
],
'foreign-raw' => [
'databasename.tablename',
'databasename.tablename',
'raw',
],
];
}
/**
* @dataProvider provideTableName
* @covers Wikimedia\Rdbms\Database::tableName
*/
public function testTableName( $expected, $table, $format, array $alias = null ) {
if ( $alias ) {
$this->db->setTableAliases( [ $table => $alias ] );
}
$this->assertEquals(
$expected,
$this->db->tableName( $table, $format ?: 'quoted' )
);
}
public function provideTableNamesWithIndexClauseOrJOIN() {
return [
'one-element array' => [
[ 'table' ], [], 'table '
],
'comma join' => [
[ 'table1', 'table2' ], [], 'table1,table2 '
],
'real join' => [
[ 'table1', 'table2' ],
[ 'table2' => [ 'LEFT JOIN', 't1_id = t2_id' ] ],
'table1 LEFT JOIN table2 ON ((t1_id = t2_id))'
],
'real join with multiple conditionals' => [
[ 'table1', 'table2' ],
[ 'table2' => [ 'LEFT JOIN', [ 't1_id = t2_id', 't2_x = \'X\'' ] ] ],
'table1 LEFT JOIN table2 ON ((t1_id = t2_id) AND (t2_x = \'X\'))'
],
'join with parenthesized group' => [
[ 'table1', 'n' => [ 'table2', 'table3' ] ],
[
'table3' => [ 'JOIN', 't2_id = t3_id' ],
'n' => [ 'LEFT JOIN', 't1_id = t2_id' ],
],
'table1 LEFT JOIN (table2 JOIN table3 ON ((t2_id = t3_id))) ON ((t1_id = t2_id))'
],
'join with degenerate parenthesized group' => [
[ 'table1', 'n' => [ 't2' => 'table2' ] ],
[
'n' => [ 'LEFT JOIN', 't1_id = t2_id' ],
],
'table1 LEFT JOIN table2 t2 ON ((t1_id = t2_id))'
],
];
}
/**
* @dataProvider provideTableNamesWithIndexClauseOrJOIN
* @covers Wikimedia\Rdbms\Database::tableNamesWithIndexClauseOrJOIN
*/
public function testTableNamesWithIndexClauseOrJOIN( $tables, $join_conds, $expect ) {
$clause = TestingAccessWrapper::newFromObject( $this->db )
->tableNamesWithIndexClauseOrJOIN( $tables, [], [], $join_conds );
$this->assertSame( $expect, $clause );
}
/**
* @covers Wikimedia\Rdbms\Database::onTransactionIdle
* @covers Wikimedia\Rdbms\Database::runOnTransactionIdleCallbacks
*/
public function testTransactionIdle() {
$db = $this->db;
$db->clearFlag( DBO_TRX );
$called = false;
$flagSet = null;
$callback = function () use ( $db, &$flagSet, &$called ) {
$called = true;
$flagSet = $db->getFlag( DBO_TRX );
};
$db->onTransactionIdle( $callback, __METHOD__ );
$this->assertTrue( $called, 'Callback reached' );
$this->assertFalse( $flagSet, 'DBO_TRX off in callback' );
$this->assertFalse( $db->getFlag( DBO_TRX ), 'DBO_TRX still default' );
$flagSet = null;
$called = false;
$db->startAtomic( __METHOD__ );
$db->onTransactionIdle( $callback, __METHOD__ );
$this->assertFalse( $called, 'Callback not reached during TRX' );
$db->endAtomic( __METHOD__ );
$this->assertTrue( $called, 'Callback reached after COMMIT' );
$this->assertFalse( $flagSet, 'DBO_TRX off in callback' );
$this->assertFalse( $db->getFlag( DBO_TRX ), 'DBO_TRX restored to default' );
$db->clearFlag( DBO_TRX );
$db->onTransactionIdle(
function () use ( $db ) {
$db->setFlag( DBO_TRX );
},
__METHOD__
);
$this->assertFalse( $db->getFlag( DBO_TRX ), 'DBO_TRX restored to default' );
}
/**
* @covers Wikimedia\Rdbms\Database::onTransactionIdle
* @covers Wikimedia\Rdbms\Database::runOnTransactionIdleCallbacks
*/
public function testTransactionIdle_TRX() {
$db = $this->getMockDB( [ 'isOpen', 'ping' ] );
$db->method( 'isOpen' )->willReturn( true );
$db->method( 'ping' )->willReturn( true );
$db->setFlag( DBO_TRX );
$lbFactory = LBFactorySingle::newFromConnection( $db );
// Ask for the connection so that LB sets internal state
// about this connection being the master connection
$lb = $lbFactory->getMainLB();
$conn = $lb->openConnection( $lb->getWriterIndex() );
$this->assertSame( $db, $conn, 'Same DB instance' );
$this->assertTrue( $db->getFlag( DBO_TRX ), 'DBO_TRX is set' );
$called = false;
$flagSet = null;
$callback = function () use ( $db, &$flagSet, &$called ) {
$called = true;
$flagSet = $db->getFlag( DBO_TRX );
};
$db->onTransactionIdle( $callback, __METHOD__ );
$this->assertTrue( $called, 'Called when idle if DBO_TRX is set' );
$this->assertFalse( $flagSet, 'DBO_TRX off in callback' );
$this->assertTrue( $db->getFlag( DBO_TRX ), 'DBO_TRX still default' );
$called = false;
$lbFactory->beginMasterChanges( __METHOD__ );
$db->onTransactionIdle( $callback, __METHOD__ );
$this->assertFalse( $called, 'Not called when lb-transaction is active' );
$lbFactory->commitMasterChanges( __METHOD__ );
$this->assertTrue( $called, 'Called when lb-transaction is committed' );
$called = false;
$lbFactory->beginMasterChanges( __METHOD__ );
$db->onTransactionIdle( $callback, __METHOD__ );
$this->assertFalse( $called, 'Not called when lb-transaction is active' );
$lbFactory->rollbackMasterChanges( __METHOD__ );
$this->assertFalse( $called, 'Not called when lb-transaction is rolled back' );
$lbFactory->commitMasterChanges( __METHOD__ );
$this->assertFalse( $called, 'Not called in next round commit' );
}
/**
* @covers Wikimedia\Rdbms\Database::onTransactionPreCommitOrIdle
* @covers Wikimedia\Rdbms\Database::runOnTransactionPreCommitCallbacks
*/
public function testTransactionPreCommitOrIdle() {
$db = $this->getMockDB( [ 'isOpen' ] );
$db->method( 'isOpen' )->willReturn( true );
$db->clearFlag( DBO_TRX );
$this->assertFalse( $db->getFlag( DBO_TRX ), 'DBO_TRX is not set' );
$called = false;
$db->onTransactionPreCommitOrIdle(
function () use ( &$called ) {
$called = true;
},
__METHOD__
);
$this->assertTrue( $called, 'Called when idle' );
$db->begin( __METHOD__ );
$called = false;
$db->onTransactionPreCommitOrIdle(
function () use ( &$called ) {
$called = true;
},
__METHOD__
);
$this->assertFalse( $called, 'Not called when transaction is active' );
$db->commit( __METHOD__ );
$this->assertTrue( $called, 'Called when transaction is committed' );
}
/**
* @covers Wikimedia\Rdbms\Database::onTransactionPreCommitOrIdle
* @covers Wikimedia\Rdbms\Database::runOnTransactionPreCommitCallbacks
*/
public function testTransactionPreCommitOrIdle_TRX() {
$db = $this->getMockDB( [ 'isOpen', 'ping' ] );
$db->method( 'isOpen' )->willReturn( true );
$db->method( 'ping' )->willReturn( true );
$db->setFlag( DBO_TRX );
$lbFactory = LBFactorySingle::newFromConnection( $db );
// Ask for the connection so that LB sets internal state
// about this connection being the master connection
$lb = $lbFactory->getMainLB();
$conn = $lb->openConnection( $lb->getWriterIndex() );
$this->assertSame( $db, $conn, 'Same DB instance' );
$this->assertTrue( $db->getFlag( DBO_TRX ), 'DBO_TRX is set' );
$called = false;
$callback = function () use ( &$called ) {
$called = true;
};
$db->onTransactionPreCommitOrIdle( $callback, __METHOD__ );
$this->assertTrue( $called, 'Called when idle if DBO_TRX is set' );
$called = false;
$lbFactory->beginMasterChanges( __METHOD__ );
$db->onTransactionPreCommitOrIdle( $callback, __METHOD__ );
$this->assertFalse( $called, 'Not called when lb-transaction is active' );
$lbFactory->commitMasterChanges( __METHOD__ );
$this->assertTrue( $called, 'Called when lb-transaction is committed' );
$called = false;
$lbFactory->beginMasterChanges( __METHOD__ );
$db->onTransactionPreCommitOrIdle( $callback, __METHOD__ );
$this->assertFalse( $called, 'Not called when lb-transaction is active' );
$lbFactory->rollbackMasterChanges( __METHOD__ );
$this->assertFalse( $called, 'Not called when lb-transaction is rolled back' );
$lbFactory->commitMasterChanges( __METHOD__ );
$this->assertFalse( $called, 'Not called in next round commit' );
}
/**
* @covers Wikimedia\Rdbms\Database::onTransactionResolution
* @covers Wikimedia\Rdbms\Database::runOnTransactionIdleCallbacks
*/
public function testTransactionResolution() {
$db = $this->db;
$db->clearFlag( DBO_TRX );
$db->begin( __METHOD__ );
$called = false;
$db->onTransactionResolution( function () use ( $db, &$called ) {
$called = true;
$db->setFlag( DBO_TRX );
} );
$db->commit( __METHOD__ );
$this->assertFalse( $db->getFlag( DBO_TRX ), 'DBO_TRX restored to default' );
$this->assertTrue( $called, 'Callback reached' );
$db->clearFlag( DBO_TRX );
$db->begin( __METHOD__ );
$called = false;
$db->onTransactionResolution( function () use ( $db, &$called ) {
$called = true;
$db->setFlag( DBO_TRX );
} );
$db->rollback( __METHOD__, IDatabase::FLUSHING_ALL_PEERS );
$this->assertFalse( $db->getFlag( DBO_TRX ), 'DBO_TRX restored to default' );
$this->assertTrue( $called, 'Callback reached' );
}
/**
* @covers Wikimedia\Rdbms\Database::setTransactionListener
*/
public function testTransactionListener() {
$db = $this->db;
$db->setTransactionListener( 'ping', function () use ( $db, &$called ) {
$called = true;
} );
$called = false;
$db->begin( __METHOD__ );
$db->commit( __METHOD__ );
$this->assertTrue( $called, 'Callback reached' );
$called = false;
$db->begin( __METHOD__ );
$db->commit( __METHOD__ );
$this->assertTrue( $called, 'Callback still reached' );
$called = false;
$db->begin( __METHOD__ );
$db->rollback( __METHOD__ );
$this->assertTrue( $called, 'Callback reached' );
$db->setTransactionListener( 'ping', null );
$called = false;
$db->begin( __METHOD__ );
$db->commit( __METHOD__ );
$this->assertFalse( $called, 'Callback not reached' );
}
/**
* Use this mock instead of DatabaseTestHelper for cases where
* DatabaseTestHelper is too inflexibile due to mocking too much
* or being too restrictive about fname matching (e.g. for tests
* that assert behaviour when the name is a mismatch, we need to
* catch the error here instead of there).
*
* @return Database
*/
private function getMockDB( $methods = [] ) {
static $abstractMethods = [
'fetchAffectedRowCount',
'closeConnection',
'dataSeek',
'doQuery',
'fetchObject', 'fetchRow',
'fieldInfo', 'fieldName',
'getSoftwareLink', 'getServerVersion',
'getType',
'indexInfo',
'insertId',
'lastError', 'lastErrno',
'numFields', 'numRows',
'open',
'strencode',
];
$db = $this->getMockBuilder( Database::class )
->disableOriginalConstructor()
->setMethods( array_values( array_unique( array_merge(
$abstractMethods,
$methods
) ) ) )
->getMock();
$wdb = TestingAccessWrapper::newFromObject( $db );
$wdb->trxProfiler = new TransactionProfiler();
$wdb->connLogger = new \Psr\Log\NullLogger();
$wdb->queryLogger = new \Psr\Log\NullLogger();
return $db;
}
/**
* @covers Wikimedia\Rdbms\Database::flushSnapshot
*/
public function testFlushSnapshot() {
$db = $this->getMockDB( [ 'isOpen' ] );
$db->method( 'isOpen' )->willReturn( true );
$db->flushSnapshot( __METHOD__ ); // ok
$db->flushSnapshot( __METHOD__ ); // ok
$db->setFlag( DBO_TRX, $db::REMEMBER_PRIOR );
$db->query( 'SELECT 1', __METHOD__ );
$this->assertTrue( (bool)$db->trxLevel(), "Transaction started." );
$db->flushSnapshot( __METHOD__ ); // ok
$db->restoreFlags( $db::RESTORE_PRIOR );
$this->assertFalse( (bool)$db->trxLevel(), "Transaction cleared." );
}
/**
* @covers Wikimedia\Rdbms\Database::getScopedLockAndFlush
* @covers Wikimedia\Rdbms\Database::lock
* @covers Wikimedia\Rdbms\Database::unlock
* @covers Wikimedia\Rdbms\Database::lockIsFree
*/
public function testGetScopedLock() {
$db = $this->getMockDB( [ 'isOpen' ] );
$db->method( 'isOpen' )->willReturn( true );
$this->assertEquals( 0, $db->trxLevel() );
$this->assertEquals( true, $db->lockIsFree( 'x', __METHOD__ ) );
$this->assertEquals( true, $db->lock( 'x', __METHOD__ ) );
$this->assertEquals( false, $db->lockIsFree( 'x', __METHOD__ ) );
$this->assertEquals( true, $db->unlock( 'x', __METHOD__ ) );
$this->assertEquals( true, $db->lockIsFree( 'x', __METHOD__ ) );
$this->assertEquals( 0, $db->trxLevel() );
$db->setFlag( DBO_TRX );
$this->assertEquals( true, $db->lockIsFree( 'x', __METHOD__ ) );
$this->assertEquals( true, $db->lock( 'x', __METHOD__ ) );
$this->assertEquals( false, $db->lockIsFree( 'x', __METHOD__ ) );
$this->assertEquals( true, $db->unlock( 'x', __METHOD__ ) );
$this->assertEquals( true, $db->lockIsFree( 'x', __METHOD__ ) );
$db->clearFlag( DBO_TRX );
$this->assertEquals( 0, $db->trxLevel() );
$db->setFlag( DBO_TRX );
try {
$this->badLockingMethodImplicit( $db );
} catch ( RunTimeException $e ) {
$this->assertTrue( $db->trxLevel() > 0, "Transaction not committed." );
}
$db->clearFlag( DBO_TRX );
$db->rollback( __METHOD__, IDatabase::FLUSHING_ALL_PEERS );
$this->assertTrue( $db->lockIsFree( 'meow', __METHOD__ ) );
try {
$this->badLockingMethodExplicit( $db );
} catch ( RunTimeException $e ) {
$this->assertTrue( $db->trxLevel() > 0, "Transaction not committed." );
}
$db->rollback( __METHOD__, IDatabase::FLUSHING_ALL_PEERS );
$this->assertTrue( $db->lockIsFree( 'meow', __METHOD__ ) );
}
private function badLockingMethodImplicit( IDatabase $db ) {
$lock = $db->getScopedLockAndFlush( 'meow', __METHOD__, 1 );
$db->query( "SELECT 1" ); // trigger DBO_TRX
throw new RunTimeException( "Uh oh!" );
}
private function badLockingMethodExplicit( IDatabase $db ) {
$lock = $db->getScopedLockAndFlush( 'meow', __METHOD__, 1 );
$db->begin( __METHOD__ );
throw new RunTimeException( "Uh oh!" );
}
/**
* @covers Wikimedia\Rdbms\Database::getFlag
* @covers Wikimedia\Rdbms\Database::setFlag
* @covers Wikimedia\Rdbms\Database::restoreFlags
*/
public function testFlagSetting() {
$db = $this->db;
$origTrx = $db->getFlag( DBO_TRX );
$origSsl = $db->getFlag( DBO_SSL );
$origTrx
? $db->clearFlag( DBO_TRX, $db::REMEMBER_PRIOR )
: $db->setFlag( DBO_TRX, $db::REMEMBER_PRIOR );
$this->assertEquals( !$origTrx, $db->getFlag( DBO_TRX ) );
$origSsl
? $db->clearFlag( DBO_SSL, $db::REMEMBER_PRIOR )
: $db->setFlag( DBO_SSL, $db::REMEMBER_PRIOR );
$this->assertEquals( !$origSsl, $db->getFlag( DBO_SSL ) );
$db->restoreFlags( $db::RESTORE_INITIAL );
$this->assertEquals( $origTrx, $db->getFlag( DBO_TRX ) );
$this->assertEquals( $origSsl, $db->getFlag( DBO_SSL ) );
$origTrx
? $db->clearFlag( DBO_TRX, $db::REMEMBER_PRIOR )
: $db->setFlag( DBO_TRX, $db::REMEMBER_PRIOR );
$origSsl
? $db->clearFlag( DBO_SSL, $db::REMEMBER_PRIOR )
: $db->setFlag( DBO_SSL, $db::REMEMBER_PRIOR );
$db->restoreFlags();
$this->assertEquals( $origSsl, $db->getFlag( DBO_SSL ) );
$this->assertEquals( !$origTrx, $db->getFlag( DBO_TRX ) );
$db->restoreFlags();
$this->assertEquals( $origSsl, $db->getFlag( DBO_SSL ) );
$this->assertEquals( $origTrx, $db->getFlag( DBO_TRX ) );
}
/**
* @expectedException UnexpectedValueException
* @covers Wikimedia\Rdbms\Database::setFlag
*/
public function testDBOIgnoreSet() {
$db = $this->getMockBuilder( DatabaseMysqli::class )
->disableOriginalConstructor()
->setMethods( null )
->getMock();
$db->setFlag( Database::DBO_IGNORE );
}
/**
* @expectedException UnexpectedValueException
* @covers Wikimedia\Rdbms\Database::clearFlag
*/
public function testDBOIgnoreClear() {
$db = $this->getMockBuilder( DatabaseMysqli::class )
->disableOriginalConstructor()
->setMethods( null )
->getMock();
$db->clearFlag( Database::DBO_IGNORE );
}
/**
* @covers Wikimedia\Rdbms\Database::tablePrefix
* @covers Wikimedia\Rdbms\Database::dbSchema
*/
public function testMutators() {
$old = $this->db->tablePrefix();
$this->assertInternalType( 'string', $old, 'Prefix is string' );
$this->assertEquals( $old, $this->db->tablePrefix(), "Prefix unchanged" );
$this->assertEquals( $old, $this->db->tablePrefix( 'xxx' ) );
$this->assertEquals( 'xxx', $this->db->tablePrefix(), "Prefix set" );
$this->db->tablePrefix( $old );
$this->assertNotEquals( 'xxx', $this->db->tablePrefix() );
$old = $this->db->dbSchema();
$this->assertInternalType( 'string', $old, 'Schema is string' );
$this->assertEquals( $old, $this->db->dbSchema(), "Schema unchanged" );
$this->assertEquals( $old, $this->db->dbSchema( 'xxx' ) );
$this->assertEquals( 'xxx', $this->db->dbSchema(), "Schema set" );
$this->db->dbSchema( $old );
$this->assertNotEquals( 'xxx', $this->db->dbSchema() );
}
}