From b8ac52c51c033014f371e35442afb37ae573c1d4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mat=C4=9Bj=20Such=C3=A1nek?= Date: Wed, 10 Feb 2021 14:13:03 +0100 Subject: [PATCH] Use independent stats for emergency disable Bug: T264629 Change-Id: I64b611243b6a4c136b82b09f2ccf588d1c3e3426 --- includes/AbuseFilterServices.php | 7 + includes/EmergencyCache.php | 140 ++++++++++++++++++ includes/FilterRunner.php | 26 ++++ includes/FilterRunnerFactory.php | 6 + includes/FilterStore.php | 23 ++- includes/ServiceWiring.php | 13 +- includes/Watcher/EmergencyWatcher.php | 34 +++-- tests/phpunit/unit/EmergencyCacheTest.php | 107 +++++++++++++ .../phpunit/unit/FilterRunnerFactoryTest.php | 2 + tests/phpunit/unit/FilterRunnerTest.php | 2 + tests/phpunit/unit/FilterStoreTest.php | 4 +- .../unit/Watcher/EmergencyWatcherTest.php | 69 ++++----- 12 files changed, 369 insertions(+), 64 deletions(-) create mode 100644 includes/EmergencyCache.php create mode 100644 tests/phpunit/unit/EmergencyCacheTest.php diff --git a/includes/AbuseFilterServices.php b/includes/AbuseFilterServices.php index 043fc5a61..54fd752cb 100644 --- a/includes/AbuseFilterServices.php +++ b/includes/AbuseFilterServices.php @@ -99,6 +99,13 @@ class AbuseFilterServices { return MediaWikiServices::getInstance()->getService( FilterLookup::SERVICE_NAME ); } + /** + * @return EmergencyCache + */ + public static function getEmergencyCache() : EmergencyCache { + return MediaWikiServices::getInstance()->getService( EmergencyCache::SERVICE_NAME ); + } + /** * @return EmergencyWatcher */ diff --git a/includes/EmergencyCache.php b/includes/EmergencyCache.php new file mode 100644 index 000000000..3811a68c7 --- /dev/null +++ b/includes/EmergencyCache.php @@ -0,0 +1,140 @@ +stash = $stash; + $this->ttlPerGroup = $ttlPerGroup; + } + + /** + * Get recently modified filters in the group. Thanks to this, performance can be improved, + * because only a small subset of filters will need an update. + * + * @param string $group + * @return int[] + */ + public function getFiltersToCheckInGroup( string $group ) : array { + $filterToExpiry = $this->stash->get( $this->createGroupKey( $group ) ); + if ( $filterToExpiry === false ) { + return []; + } + $time = (int)round( $this->stash->getCurrentTime() ); + return array_keys( array_filter( + $filterToExpiry, + static function ( $exp ) use ( $time ) { + return $exp > $time; + } + ) ); + } + + /** + * Create a new entry in cache for a filter and update the entry for the group. + * This method is usually called after the filter has been updated. + * + * @param int $filter + * @param string $group + * @return bool + */ + public function setNewForFilter( int $filter, string $group ) : bool { + $ttl = $this->ttlPerGroup[$group] ?? $this->ttlPerGroup['default']; + $expiry = (int)round( $this->stash->getCurrentTime() + $ttl ); + $this->stash->merge( + $this->createGroupKey( $group ), + function ( $cache, $key, $value ) use ( $filter, $expiry ) { + if ( $value === false ) { + $value = []; + } + // note that some filters may have already had their keys expired + // we are currently filtering them out in getFiltersToCheckInGroup + // but if necessary, it can be done here + $value[$filter] = $expiry; + return $value; + }, + $expiry + ); + return $this->stash->set( + $this->createFilterKey( $filter ), + [ 'total' => 0, 'matches' => 0, 'expiry' => $expiry ], + $expiry + ); + } + + /** + * Increase the filter's 'total' value by one and possibly also the 'matched' value. + * + * @param int $filter + * @param bool $matched Whether the filter matched the action + * @return bool + */ + public function incrementForFilter( int $filter, bool $matched ) : bool { + return $this->stash->merge( + $this->createFilterKey( $filter ), + static function ( $cache, $key, $value, &$expiry ) use ( $matched ) { + if ( $value === false ) { + return false; + } + $value['total']++; + if ( $matched ) { + $value['matches']++; + } + // enforce the prior TTL + $expiry = $value['expiry']; + return $value; + } + ); + } + + /** + * Get the cache entry for the filter. Returns false when the key has already expired. + * Otherwise it returns the entry formatted as [ 'total' => number of actions, + * 'matches' => number of hits ] (since the last filter modification). + * + * @param int $filter + * @return array|false + */ + public function getForFilter( int $filter ) { + $value = $this->stash->get( $this->createFilterKey( $filter ) ); + if ( $value !== false ) { + unset( $value['expiry'] ); + } + return $value; + } + + /** + * @param string $group + * @return string + */ + private function createGroupKey( string $group ) : string { + return $this->stash->makeKey( 'abusefilter', 'emergency', 'group', $group ); + } + + /** + * @param int $filter + * @return string + */ + private function createFilterKey( int $filter ) : string { + return $this->stash->makeKey( 'abusefilter', 'emergency', 'filter', $filter ); + } + +} diff --git a/includes/FilterRunner.php b/includes/FilterRunner.php index c49e2ec91..210d87a29 100644 --- a/includes/FilterRunner.php +++ b/includes/FilterRunner.php @@ -3,6 +3,7 @@ namespace MediaWiki\Extension\AbuseFilter; use BadMethodCallException; +use DeferredUpdates; use IBufferingStatsdDataFactory; use InvalidArgumentException; use MediaWiki\Config\ServiceOptions; @@ -51,6 +52,8 @@ class FilterRunner { private $consExecutorFactory; /** @var AbuseLoggerFactory */ private $abuseLoggerFactory; + /** @var EmergencyCache */ + private $emergencyCache; /** @var Watcher[] */ private $watchers; /** @var EditStashCache */ @@ -103,6 +106,7 @@ class FilterRunner { * @param AbuseLoggerFactory $abuseLoggerFactory * @param VariablesManager $varManager * @param VariableGeneratorFactory $varGeneratorFactory + * @param EmergencyCache $emergencyCache * @param Watcher[] $watchers * @param EditStashCache $stashCache * @param LoggerInterface $logger @@ -124,6 +128,7 @@ class FilterRunner { AbuseLoggerFactory $abuseLoggerFactory, VariablesManager $varManager, VariableGeneratorFactory $varGeneratorFactory, + EmergencyCache $emergencyCache, array $watchers, EditStashCache $stashCache, LoggerInterface $logger, @@ -143,6 +148,7 @@ class FilterRunner { $this->abuseLoggerFactory = $abuseLoggerFactory; $this->varManager = $varManager; $this->varGeneratorFactory = $varGeneratorFactory; + $this->emergencyCache = $emergencyCache; $this->watchers = $watchers; $this->stashCache = $stashCache; $this->logger = $logger; @@ -226,6 +232,14 @@ class FilterRunner { } $this->profileExecution( $runnerData ); + // hack until DI for DeferredUpdates is possible (T265749) + if ( defined( 'MW_PHPUNIT_TEST' ) ) { + $this->updateEmergencyCache( $runnerData->getMatchesMap() ); + } else { + DeferredUpdates::addCallableUpdate( function () use ( $runnerData ) { + $this->updateEmergencyCache( $runnerData->getMatchesMap() ); + } ); + } // Tag the action if the condition limit was hit if ( $runnerData->getTotalConditions() > $this->options->get( 'AbuseFilterConditionLimit' ) ) { @@ -395,6 +409,18 @@ class FilterRunner { ); } + /** + * @param bool[] $matches + */ + protected function updateEmergencyCache( array $matches ) : void { + $filters = $this->emergencyCache->getFiltersToCheckInGroup( $this->group ); + foreach ( $filters as $filter ) { + if ( array_key_exists( "$filter", $matches ) ) { + $this->emergencyCache->incrementForFilter( $filter, $matches["$filter"] ); + } + } + } + /** * @return array */ diff --git a/includes/FilterRunnerFactory.php b/includes/FilterRunnerFactory.php index c4cd5104d..ad7843862 100644 --- a/includes/FilterRunnerFactory.php +++ b/includes/FilterRunnerFactory.php @@ -40,6 +40,8 @@ class FilterRunnerFactory { private $varManager; /** @var VariableGeneratorFactory */ private $varGeneratorFactory; + /** @var EmergencyCache */ + private $emergencyCache; /** @var UpdateHitCountWatcher */ private $updateHitCountWatcher; /** @var EmergencyWatcher */ @@ -65,6 +67,7 @@ class FilterRunnerFactory { * @param AbuseLoggerFactory $abuseLoggerFactory * @param VariablesManager $varManager * @param VariableGeneratorFactory $varGeneratorFactory + * @param EmergencyCache $emergencyCache * @param UpdateHitCountWatcher $updateHitCountWatcher * @param EmergencyWatcher $emergencyWatcher * @param BagOStuff $localCache @@ -83,6 +86,7 @@ class FilterRunnerFactory { AbuseLoggerFactory $abuseLoggerFactory, VariablesManager $varManager, VariableGeneratorFactory $varGeneratorFactory, + EmergencyCache $emergencyCache, UpdateHitCountWatcher $updateHitCountWatcher, EmergencyWatcher $emergencyWatcher, BagOStuff $localCache, @@ -100,6 +104,7 @@ class FilterRunnerFactory { $this->abuseLoggerFactory = $abuseLoggerFactory; $this->varManager = $varManager; $this->varGeneratorFactory = $varGeneratorFactory; + $this->emergencyCache = $emergencyCache; $this->updateHitCountWatcher = $updateHitCountWatcher; $this->emergencyWatcher = $emergencyWatcher; $this->localCache = $localCache; @@ -134,6 +139,7 @@ class FilterRunnerFactory { $this->abuseLoggerFactory, $this->varManager, $this->varGeneratorFactory, + $this->emergencyCache, $watchers, new EditStashCache( $this->localCache, diff --git a/includes/FilterStore.php b/includes/FilterStore.php index 0c0133a05..e098abe19 100644 --- a/includes/FilterStore.php +++ b/includes/FilterStore.php @@ -39,6 +39,9 @@ class FilterStore { /** @var FilterCompare */ private $filterCompare; + /** @var EmergencyCache */ + private $emergencyCache; + /** * @param ConsequencesRegistry $consequencesRegistry * @param ILoadBalancer $loadBalancer @@ -47,6 +50,7 @@ class FilterStore { * @param ChangeTagsManager $tagsManager * @param FilterValidator $filterValidator * @param FilterCompare $filterCompare + * @param EmergencyCache $emergencyCache */ public function __construct( ConsequencesRegistry $consequencesRegistry, @@ -55,7 +59,8 @@ class FilterStore { FilterLookup $filterLookup, ChangeTagsManager $tagsManager, FilterValidator $filterValidator, - FilterCompare $filterCompare + FilterCompare $filterCompare, + EmergencyCache $emergencyCache ) { $this->consequencesRegistry = $consequencesRegistry; $this->loadBalancer = $loadBalancer; @@ -64,6 +69,7 @@ class FilterStore { $this->tagsManager = $tagsManager; $this->filterValidator = $filterValidator; $this->filterCompare = $filterCompare; + $this->emergencyCache = $emergencyCache; } /** @@ -213,13 +219,15 @@ class FilterStore { $dbw->endAtomic( __METHOD__ ); + // TODO: isset() shouldn't be necessary, + // invalid values should also be discarded upstream + $group = 'default'; + if ( isset( $newRow['af_group'] ) && $newRow['af_group'] !== '' ) { + $group = $newRow['af_group']; + } + // Invalidate cache if this was a global rule if ( $wasGlobal || $newRow['af_global'] ) { - $group = 'default'; - if ( isset( $newRow['af_group'] ) && $newRow['af_group'] !== '' ) { - $group = $newRow['af_group']; - } - $this->filterLookup->purgeGroupWANCache( $group ); } @@ -241,6 +249,9 @@ class FilterStore { } $this->filterProfiler->resetFilterProfile( $newID ); + if ( $newRow['af_enabled'] ) { + $this->emergencyCache->setNewForFilter( $newID, $group ); + } return [ $newID, $historyID ]; } diff --git a/includes/ServiceWiring.php b/includes/ServiceWiring.php index 4264ccc25..7faa7b3e1 100644 --- a/includes/ServiceWiring.php +++ b/includes/ServiceWiring.php @@ -18,6 +18,7 @@ use MediaWiki\Extension\AbuseFilter\Consequences\ConsequencesRegistry; use MediaWiki\Extension\AbuseFilter\EchoNotifier; use MediaWiki\Extension\AbuseFilter\EditBox\EditBoxBuilderFactory; use MediaWiki\Extension\AbuseFilter\EditRevUpdater; +use MediaWiki\Extension\AbuseFilter\EmergencyCache; use MediaWiki\Extension\AbuseFilter\FilterCompare; use MediaWiki\Extension\AbuseFilter\FilterImporter; use MediaWiki\Extension\AbuseFilter\FilterLookup; @@ -127,9 +128,15 @@ return [ $services->get( CentralDBManager::SERVICE_NAME ) ); }, + EmergencyCache::SERVICE_NAME => function ( MediaWikiServices $services ): EmergencyCache { + return new EmergencyCache( + $services->getMainObjectStash(), + $services->getMainConfig()->get( 'AbuseFilterEmergencyDisableAge' ) + ); + }, EmergencyWatcher::SERVICE_NAME => function ( MediaWikiServices $services ): EmergencyWatcher { return new EmergencyWatcher( - $services->getService( FilterProfiler::SERVICE_NAME ), + $services->getService( EmergencyCache::SERVICE_NAME ), $services->getDBLoadBalancer(), $services->getService( FilterLookup::SERVICE_NAME ), $services->getService( EchoNotifier::SERVICE_NAME ), @@ -176,7 +183,8 @@ return [ $services->get( FilterLookup::SERVICE_NAME ), $services->get( ChangeTagsManager::SERVICE_NAME ), $services->get( FilterValidator::SERVICE_NAME ), - $services->get( FilterCompare::SERVICE_NAME ) + $services->get( FilterCompare::SERVICE_NAME ), + $services->get( EmergencyCache::SERVICE_NAME ) ); }, ConsequencesFactory::SERVICE_NAME => function ( MediaWikiServices $services ): ConsequencesFactory { @@ -277,6 +285,7 @@ return [ $services->get( AbuseLoggerFactory::SERVICE_NAME ), $services->get( VariablesManager::SERVICE_NAME ), $services->get( VariableGeneratorFactory::SERVICE_NAME ), + $services->get( EmergencyCache::SERVICE_NAME ), $services->get( UpdateHitCountWatcher::SERVICE_NAME ), $services->get( EmergencyWatcher::SERVICE_NAME ), ObjectCache::getLocalClusterInstance(), diff --git a/includes/Watcher/EmergencyWatcher.php b/includes/Watcher/EmergencyWatcher.php index 526b38643..8c6ccd3d4 100644 --- a/includes/Watcher/EmergencyWatcher.php +++ b/includes/Watcher/EmergencyWatcher.php @@ -7,8 +7,8 @@ use DeferredUpdates; use InvalidArgumentException; use MediaWiki\Config\ServiceOptions; use MediaWiki\Extension\AbuseFilter\EchoNotifier; +use MediaWiki\Extension\AbuseFilter\EmergencyCache; use MediaWiki\Extension\AbuseFilter\FilterLookup; -use MediaWiki\Extension\AbuseFilter\FilterProfiler; use Wikimedia\Rdbms\IDatabase; use Wikimedia\Rdbms\ILoadBalancer; @@ -27,8 +27,8 @@ class EmergencyWatcher implements Watcher { 'AbuseFilterEmergencyDisableThreshold', ]; - /** @var FilterProfiler */ - private $profiler; + /** @var EmergencyCache */ + private $cache; /** @var ILoadBalancer */ private $loadBalancer; @@ -43,21 +43,21 @@ class EmergencyWatcher implements Watcher { private $options; /** - * @param FilterProfiler $profiler + * @param EmergencyCache $cache * @param ILoadBalancer $loadBalancer * @param FilterLookup $filterLookup * @param EchoNotifier $notifier * @param ServiceOptions $options */ public function __construct( - FilterProfiler $profiler, + EmergencyCache $cache, ILoadBalancer $loadBalancer, FilterLookup $filterLookup, EchoNotifier $notifier, ServiceOptions $options ) { $options->assertRequiredOptions( self::CONSTRUCTOR_OPTIONS ); - $this->profiler = $profiler; + $this->cache = $cache; $this->loadBalancer = $loadBalancer; $this->filterLookup = $filterLookup; $this->notifier = $notifier; @@ -73,12 +73,11 @@ class EmergencyWatcher implements Watcher { * @return int[] Array of filters to be throttled */ public function getFiltersToThrottle( array $filters, string $group ) : array { - $groupProfile = $this->profiler->getGroupProfile( $group ); - - // @ToDo this is an amount between 1 and AbuseFilterProfileActionsCap, which means that the - // reliability of this number may strongly vary. We should instead use a fixed one. - $totalActions = $groupProfile['total']; - if ( $totalActions === 0 ) { + $filters = array_intersect( + $filters, + $this->cache->getFiltersToCheckInGroup( $group ) + ); + if ( $filters === [] ) { return []; } @@ -91,6 +90,8 @@ class EmergencyWatcher implements Watcher { $throttleFilters = []; foreach ( $filters as $filter ) { $filterObj = $this->filterLookup->getFilter( $filter, false ); + // TODO: consider removing the filter from the group key + // after throttling if ( $filterObj->isThrottled() ) { continue; } @@ -99,13 +100,20 @@ class EmergencyWatcher implements Watcher { $exemptTime = $filterAge + $maxAge; // Optimize for the common case when filters are well-established + // This check somewhat duplicates the role of cache entry's TTL + // and could as well be removed if ( $exemptTime <= $time ) { continue; } // TODO: this value might be stale, there is no guarantee the match // has actually been recorded now - $matchCount = $this->profiler->getFilterProfile( $filter )['matches']; + $cacheValue = $this->cache->getForFilter( $filter ); + if ( $cacheValue === false ) { + continue; + } + + [ 'total' => $totalActions, 'matches' => $matchCount ] = $cacheValue; if ( $matchCount > $hitCountLimit && ( $matchCount / $totalActions ) > $threshold ) { // More than AbuseFilterEmergencyDisableCount matches, constituting more than diff --git a/tests/phpunit/unit/EmergencyCacheTest.php b/tests/phpunit/unit/EmergencyCacheTest.php new file mode 100644 index 000000000..5f95b1746 --- /dev/null +++ b/tests/phpunit/unit/EmergencyCacheTest.php @@ -0,0 +1,107 @@ + 86400 ] ); + $this->assertSame( [], $cache->getFiltersToCheckInGroup( 'default' ) ); + $this->assertFalse( $cache->getForFilter( 1 ) ); + } + + /** + * @covers ::setNewForFilter + * @covers ::getFiltersToCheckInGroup + * @covers ::getForFilter + * @covers ::createGroupKey + * @covers ::createFilterKey + */ + public function testSetNewForFilter() { + $time = microtime( true ); + $stash = new HashBagOStuff(); + $stash->setMockTime( $time ); + $cache = new EmergencyCache( $stash, [ 'default' => 86400, 'other' => 3600 ] ); + $cache->setNewForFilter( 2, 'other' ); + $this->assertSame( + [ 'total' => 0, 'matches' => 0 ], + $cache->getForFilter( 2 ) + ); + $this->assertSame( + [ 2 ], + $cache->getFiltersToCheckInGroup( 'other' ) + ); + $this->assertSame( + [], + $cache->getFiltersToCheckInGroup( 'default' ) + ); + + $time += 3599; + $this->assertNotFalse( $cache->getForFilter( 2 ) ); + $time += 2; + $this->assertFalse( $cache->getForFilter( 2 ) ); + $this->assertSame( [], $cache->getFiltersToCheckInGroup( 'other' ) ); + } + + /** + * @covers ::incrementForFilter + * @covers ::getForFilter + * @covers ::setNewForFilter + */ + public function testIncrementForFilter() { + $time = microtime( true ); + $stash = new HashBagOStuff(); + $stash->setMockTime( $time ); + $cache = new EmergencyCache( $stash, [ 'default' => 86400 ] ); + $cache->setNewForFilter( 1, 'default' ); + $cache->incrementForFilter( 1, false ); + $this->assertSame( + [ 'total' => 1, 'matches' => 0 ], + $cache->getForFilter( 1 ) + ); + $cache->incrementForFilter( 1, true ); + $this->assertSame( + [ 'total' => 2, 'matches' => 1 ], + $cache->getForFilter( 1 ) + ); + + $time += 86401; + $cache->incrementForFilter( 1, true ); + $this->assertFalse( $cache->getForFilter( 1 ) ); + } + + /** + * @covers ::getFiltersToCheckInGroup + */ + public function testGetFiltersToCheckInGroup() { + $time = microtime( true ); + $stash = new HashBagOStuff(); + $stash->setMockTime( $time ); + $cache = new EmergencyCache( $stash, [ 'default' => 3600 ] ); + $cache->setNewForFilter( 1, 'default' ); + $time += 1000; + $cache->setNewForFilter( 2, 'default' ); + $this->assertArrayEquals( + [ 1, 2 ], + $cache->getFiltersToCheckInGroup( 'default' ) + ); + $time += 2601; + $this->assertArrayEquals( + [ 2 ], + $cache->getFiltersToCheckInGroup( 'default' ) + ); + } + +} diff --git a/tests/phpunit/unit/FilterRunnerFactoryTest.php b/tests/phpunit/unit/FilterRunnerFactoryTest.php index de86ac36c..dfe53a823 100644 --- a/tests/phpunit/unit/FilterRunnerFactoryTest.php +++ b/tests/phpunit/unit/FilterRunnerFactoryTest.php @@ -8,6 +8,7 @@ use MediaWiki\Config\ServiceOptions; use MediaWiki\Extension\AbuseFilter\AbuseLoggerFactory; use MediaWiki\Extension\AbuseFilter\ChangeTags\ChangeTagger; use MediaWiki\Extension\AbuseFilter\Consequences\ConsequencesExecutorFactory; +use MediaWiki\Extension\AbuseFilter\EmergencyCache; use MediaWiki\Extension\AbuseFilter\FilterLookup; use MediaWiki\Extension\AbuseFilter\FilterProfiler; use MediaWiki\Extension\AbuseFilter\FilterRunner; @@ -54,6 +55,7 @@ class FilterRunnerFactoryTest extends MediaWikiUnitTestCase { $this->createMock( AbuseLoggerFactory::class ), $this->createMock( VariablesManager::class ), $this->createMock( VariableGeneratorFactory::class ), + $this->createMock( EmergencyCache::class ), $this->createMock( UpdateHitCountWatcher::class ), $this->createMock( EmergencyWatcher::class ), new HashBagOStuff(), diff --git a/tests/phpunit/unit/FilterRunnerTest.php b/tests/phpunit/unit/FilterRunnerTest.php index 2324744d6..ece8c50dc 100644 --- a/tests/phpunit/unit/FilterRunnerTest.php +++ b/tests/phpunit/unit/FilterRunnerTest.php @@ -9,6 +9,7 @@ use MediaWiki\Extension\AbuseFilter\AbuseLoggerFactory; use MediaWiki\Extension\AbuseFilter\ChangeTags\ChangeTagger; use MediaWiki\Extension\AbuseFilter\Consequences\ConsequencesExecutorFactory; use MediaWiki\Extension\AbuseFilter\EditStashCache; +use MediaWiki\Extension\AbuseFilter\EmergencyCache; use MediaWiki\Extension\AbuseFilter\FilterLookup; use MediaWiki\Extension\AbuseFilter\FilterProfiler; use MediaWiki\Extension\AbuseFilter\FilterRunner; @@ -67,6 +68,7 @@ class FilterRunnerTest extends MediaWikiUnitTestCase { $this->createMock( AbuseLoggerFactory::class ), $this->createMock( VariablesManager::class ), $this->createMock( VariableGeneratorFactory::class ), + $this->createMock( EmergencyCache::class ), [], $cache, new NullLogger(), diff --git a/tests/phpunit/unit/FilterStoreTest.php b/tests/phpunit/unit/FilterStoreTest.php index 67e7d20bc..c8f8d1aa2 100644 --- a/tests/phpunit/unit/FilterStoreTest.php +++ b/tests/phpunit/unit/FilterStoreTest.php @@ -4,6 +4,7 @@ namespace MediaWiki\Extension\AbuseFilter\Tests\Unit; use MediaWiki\Extension\AbuseFilter\ChangeTags\ChangeTagsManager; use MediaWiki\Extension\AbuseFilter\Consequences\ConsequencesRegistry; +use MediaWiki\Extension\AbuseFilter\EmergencyCache; use MediaWiki\Extension\AbuseFilter\FilterCompare; use MediaWiki\Extension\AbuseFilter\FilterLookup; use MediaWiki\Extension\AbuseFilter\FilterProfiler; @@ -32,7 +33,8 @@ class FilterStoreTest extends MediaWikiUnitTestCase { $this->createMock( FilterLookup::class ), $this->createMock( ChangeTagsManager::class ), $this->createMock( FilterValidator::class ), - $this->createMock( FilterCompare::class ) + $this->createMock( FilterCompare::class ), + $this->createMock( EmergencyCache::class ) ) ); } diff --git a/tests/phpunit/unit/Watcher/EmergencyWatcherTest.php b/tests/phpunit/unit/Watcher/EmergencyWatcherTest.php index ca1d7e0e3..030481d24 100644 --- a/tests/phpunit/unit/Watcher/EmergencyWatcherTest.php +++ b/tests/phpunit/unit/Watcher/EmergencyWatcherTest.php @@ -4,9 +4,9 @@ namespace MediaWiki\Extension\AbuseFilter\Tests\Unit\Watcher; use MediaWiki\Config\ServiceOptions; use MediaWiki\Extension\AbuseFilter\EchoNotifier; +use MediaWiki\Extension\AbuseFilter\EmergencyCache; use MediaWiki\Extension\AbuseFilter\Filter\ExistingFilter; use MediaWiki\Extension\AbuseFilter\FilterLookup; -use MediaWiki\Extension\AbuseFilter\FilterProfiler; use MediaWiki\Extension\AbuseFilter\Watcher\EmergencyWatcher; use MediaWikiUnitTestCase; use MWTimestamp; @@ -36,15 +36,15 @@ class EmergencyWatcherTest extends MediaWikiUnitTestCase { ); } - private function getFilterProfiler( array $profilerData ) : FilterProfiler { - $profiler = $this->createMock( FilterProfiler::class ); - $profiler->method( 'getGroupProfile' ) - ->willReturnCallback( function ( $group ) use ( $profilerData ) { - return [ 'total' => $profilerData['total'][$group] ]; - } ); - $profiler->method( 'getFilterProfile' ) - ->willReturn( [ 'matches' => $profilerData['matches'] ] ); - return $profiler; + private function getEmergencyCache( array $cacheData, string $group ) : EmergencyCache { + $cache = $this->createMock( EmergencyCache::class ); + $cache->method( 'getForFilter' ) + ->with( 1 ) + ->willReturn( $cacheData ); + $cache->method( 'getFiltersToCheckInGroup' ) + ->with( $group ) + ->willReturn( [ 1 ] ); + return $cache; } private function getFilterLookup( array $filterData ) : FilterLookup { @@ -67,10 +67,8 @@ class EmergencyWatcherTest extends MediaWikiUnitTestCase { /* filterData */ [ 'timestamp' => '20201016000000' ], - /* profilerData */ [ - 'total' => [ - 'default' => 100, - ], + /* cacheData */ [ + 'total' => 100, 'matches' => 10 ], /* willThrottle */ true @@ -80,11 +78,8 @@ class EmergencyWatcherTest extends MediaWikiUnitTestCase { /* filterData */ [ 'timestamp' => '20201016000000' ], - /* profilerData */ [ - 'total' => [ - 'default' => 200, - 'other' => 100 - ], + /* cacheData */ [ + 'total' => 100, 'matches' => 5 ], /* willThrottle */ true, @@ -96,10 +91,8 @@ class EmergencyWatcherTest extends MediaWikiUnitTestCase { 'timestamp' => '20201016000000', 'throttled' => true, ], - /* profilerData */ [ - 'total' => [ - 'default' => 100, - ], + /* cacheData */ [ + 'total' => 100, 'matches' => 10 ], /* willThrottle */ false @@ -109,10 +102,8 @@ class EmergencyWatcherTest extends MediaWikiUnitTestCase { /* filterData */ [ 'timestamp' => '20201016000000' ], - /* profilerData */ [ - 'total' => [ - 'default' => 5, - ], + /* cacheData */ [ + 'total' => 5, 'matches' => 2 ], /* willThrottle */ false @@ -122,10 +113,8 @@ class EmergencyWatcherTest extends MediaWikiUnitTestCase { /* filterData */ [ 'timestamp' => '20201016000000' ], - /* profilerData */ [ - 'total' => [ - 'default' => 100, - ], + /* cacheData */ [ + 'total' => 100, 'matches' => 5 ], /* willThrottle */ false @@ -135,10 +124,8 @@ class EmergencyWatcherTest extends MediaWikiUnitTestCase { /* filterData */ [ 'timestamp' => '20201016000000' ], - /* profilerData */ [ - 'total' => [ - 'default' => 1000, - ], + /* cacheData */ [ + 'total' => 1000, 'matches' => 100 ], /* willThrottle */ false @@ -148,10 +135,8 @@ class EmergencyWatcherTest extends MediaWikiUnitTestCase { /* filterData */ [ 'timestamp' => '20201016000000' ], - /* profilerData */ [ - 'total' => [ - 'default' => 0, - ], + /* cacheData */ [ + 'total' => 0, 'matches' => 0 ], /* willThrottle */ false @@ -167,13 +152,13 @@ class EmergencyWatcherTest extends MediaWikiUnitTestCase { public function testGetFiltersToThrottle( string $timestamp, array $filterData, - array $profilerData, + array $cacheData, bool $willThrottle, string $group = 'default' ) { MWTimestamp::setFakeTime( $timestamp ); $watcher = new EmergencyWatcher( - $this->getFilterProfiler( $profilerData ), + $this->getEmergencyCache( $cacheData, $group ), $this->createMock( ILoadBalancer::class ), $this->getFilterLookup( $filterData ), $this->createMock( EchoNotifier::class ), @@ -194,7 +179,7 @@ class EmergencyWatcherTest extends MediaWikiUnitTestCase { */ public function testConstruct() { $watcher = new EmergencyWatcher( - $this->createMock( FilterProfiler::class ), + $this->createMock( EmergencyCache::class ), $this->createMock( ILoadBalancer::class ), $this->createMock( FilterLookup::class ), $this->createMock( EchoNotifier::class ),