wiki.techinc.nl/tests/phpunit/unit/includes/libs/objectcache/ReplicatedBagOStuffTest.php
Aaron Schulz 8f6d05e62c objectcache: add watchErrors() to BagOStuff/WANObjectCache
The new style of checking for the last error during a section of
calls is more robust since it allows nesting of callers. Typically,
an external caller will want to watch a section of code that will
involve zero or more internally watched sections. Errors that are
seen internally (leading to a failing response) should also be
visible externally.

Replace internal BagOStuff clearLastError() calls.

Replace WANObjectCache clearLastError() calls. Such a class should not
clear the error codes since the class is effectively "internal". Callers
that are more meaningfully "external" might want to check the errors.

Cleanup "last" error handling for proxy backends.

Change-Id: I281817a85602967c0ec2bdd23a5d8be101680b64
2021-11-10 00:38:27 +00:00

178 lines
5.9 KiB
PHP

<?php
use Wikimedia\LightweightObjectStore\StorageAwareness;
use Wikimedia\TestingAccessWrapper;
class ReplicatedBagOStuffTest extends \MediaWikiUnitTestCase {
/** @var HashBagOStuff */
private $writeCache;
/** @var HashBagOStuff */
private $readCache;
/** @var ReplicatedBagOStuff */
private $cache;
protected function setUp(): void {
parent::setUp();
$this->writeCache = new HashBagOStuff();
$this->readCache = new HashBagOStuff();
$this->cache = new ReplicatedBagOStuff( [
'keyspace' => 'repl_local',
'writeFactory' => $this->writeCache,
'readFactory' => $this->readCache,
] );
}
/**
* @covers ReplicatedBagOStuff::set
*/
public function testSet() {
$key = $this->cache->makeKey( 'a', 'key' );
$value = 'a value';
$this->cache->set( $key, $value );
$this->assertSame( $value, $this->writeCache->get( $key ), 'Written' );
$this->assertFalse( $this->readCache->get( $key ), 'Async replication' );
}
/**
* @covers ReplicatedBagOStuff::get
*/
public function testGet() {
$key = $this->cache->makeKey( 'a', 'key' );
$write = 'new value';
$this->writeCache->set( $key, $write );
$read = 'old value';
$this->readCache->set( $key, $read );
$this->assertSame( $read, $this->cache->get( $key ), 'Async replication' );
}
/**
* @covers ReplicatedBagOStuff::get
*/
public function testGetAbsent() {
$key = $this->cache->makeKey( 'a', 'key' );
$value = 'a value';
$this->writeCache->set( $key, $value );
$this->assertFalse( $this->cache->get( $key ), 'Async replication' );
}
/**
* @covers ReplicatedBagOStuff::setMulti
* @covers ReplicatedBagOStuff::getMulti
*/
public function testGetSetMulti() {
$keyA = $this->cache->makeKey( 'key', 'a' );
$keyB = $this->cache->makeKey( 'key', 'b' );
$valueAOld = 'one old value';
$valueBOld = 'another old value';
$valueANew = 'one new value';
$valueBNew = 'another new value';
$this->writeCache->setMulti( [ $keyA => $valueANew, $keyB => $valueBNew ] );
$this->readCache->setMulti( [ $keyA => $valueAOld, $keyB => $valueBOld ] );
$this->assertEquals(
[ $keyA => $valueAOld, $keyB => $valueBOld ],
$this->cache->getMulti( [ $keyA, $keyB ] ),
'Async replication'
);
}
/**
* @covers ReplicatedBagOStuff::get
* @covers ReplicatedBagOStuff::set
*/
public function testGetSetRaw() {
$key = 'a:key';
$value = 'a value';
$this->cache->set( $key, $value );
// Write to master.
$this->assertEquals( $value, $this->writeCache->get( $key ) );
// Don't write to replica. Replication is deferred to backend.
$this->assertFalse( $this->readCache->get( $key ) );
}
/**
* @covers ReplicatedBagOStuff::watchErrors()
* @covers ReplicatedBagOStuff::getLastError()
* @covers ReplicatedBagOStuff::setLastError()
*/
public function testErrorHandling() {
$wCache = $this->createPartialMock( HashBagOStuff::class, [ 'set' ] );
$wCacheWrapper = TestingAccessWrapper::newFromObject( $wCache );
$wCacheNextError = StorageAwareness::ERR_NONE;
$wCache->method( 'set' )
->willReturnCallback( static function () use ( $wCacheWrapper, &$wCacheNextError ) {
if ( $wCacheNextError !== StorageAwareness::ERR_NONE ) {
$wCacheWrapper->setLastError( $wCacheNextError );
return false;
}
return true;
} );
$rCache = $this->createPartialMock( HashBagOStuff::class, [ 'get' ] );
$rCacheWrapper = TestingAccessWrapper::newFromObject( $rCache );
$rCacheNextError = StorageAwareness::ERR_NONE;
$rCache->method( 'get' )
->willReturnCallback( static function () use ( $rCacheWrapper, &$rCacheNextError ) {
if ( $rCacheNextError !== StorageAwareness::ERR_NONE ) {
$rCacheWrapper->setLastError( $rCacheNextError );
}
return false;
} );
$cache = new ReplicatedBagOStuff( [
'keyspace' => 'repl_local',
'writeFactory' => $wCache,
'readFactory' => $rCache,
] );
$cacheWrapper = TestingAccessWrapper::newFromObject( $cache );
$key1 = 'a:key';
$wp1 = $cache->watchErrors();
$cache->get( $key1 );
$this->assertSame( StorageAwareness::ERR_NONE, $rCache->getLastError() );
$this->assertSame( StorageAwareness::ERR_NONE, $cache->getLastError() );
$this->assertSame( StorageAwareness::ERR_NONE, $cache->getLastError( $wp1 ) );
$cache->set( $key1, 'value', 3600 );
$this->assertSame( StorageAwareness::ERR_NONE, $wCache->getLastError() );
$this->assertSame( StorageAwareness::ERR_NONE, $cache->getLastError() );
$this->assertSame( StorageAwareness::ERR_NONE, $cache->getLastError( $wp1 ) );
// Use a different key to avoid the "sessionConsistencyWindow" configuration
$key2 = 'b:key';
$wCacheNextError = StorageAwareness::ERR_NO_RESPONSE;
$rCacheNextError = StorageAwareness::ERR_UNREACHABLE;
$cache->get( $key2 );
$this->assertSame( $rCacheNextError, $rCache->getLastError() );
$this->assertSame( $rCacheNextError, $cache->getLastError() );
$wp2 = $cache->watchErrors();
$cache->set( $key2, 'value', 3600 );
$wp3 = $cache->watchErrors();
$this->assertSame( $wCacheNextError, $wCache->getLastError() );
$this->assertSame( $wCacheNextError, $cache->getLastError() );
$this->assertSame( $wCacheNextError, $cache->getLastError( $wp1 ) );
$this->assertSame( $wCacheNextError, $cache->getLastError( $wp2 ) );
$this->assertSame( StorageAwareness::ERR_NONE, $cache->getLastError( $wp3 ) );
$cacheWrapper->setLastError( StorageAwareness::ERR_UNEXPECTED );
$wp4 = $cache->watchErrors();
$this->assertSame( StorageAwareness::ERR_UNEXPECTED, $cache->getLastError() );
$this->assertSame( StorageAwareness::ERR_UNEXPECTED, $cache->getLastError( $wp1 ) );
$this->assertSame( StorageAwareness::ERR_UNEXPECTED, $cache->getLastError( $wp2 ) );
$this->assertSame( StorageAwareness::ERR_UNEXPECTED, $cache->getLastError( $wp3 ) );
$this->assertSame( StorageAwareness::ERR_NONE, $cache->getLastError( $wp4 ) );
$this->assertSame( $wCacheNextError, $wCache->getLastError() );
$this->assertSame( $rCacheNextError, $rCache->getLastError() );
}
}