wiki.techinc.nl/tests/phpunit/mocks/TestLocalisationCache.php
thiemowmde 8334188972 Fix TestLocalisationCache being way to small
I dug into a particular profile via KCachegrind (specifically the
profile for DirectParsoidClientTest in VisualEditor) and found this
surprising bottleneck. Let's say a test uses a data provider to test
a particular piece of code with 5 different languages. And then
another piece of code with 5 languages. And so on. Because the cache
can only hold 4 languages there will never be a successfull cache
hit.

The items in the cache are something like 1 or 2 megabytes each. It
should be fine to hold 16 of them in memory. Possibly even more. The
sweet spot in my example was 10. This is when the runtime of
DirectParsoidClientTest gets suddenly cut in half.

Bug: T225730
Change-Id: Ie04f338ee8a59f08be50947d794626ed52d793bc
2023-07-07 20:15:03 +00:00

67 lines
2.2 KiB
PHP

<?php
use MediaWiki\MainConfigNames;
use MediaWiki\MediaWikiServices;
use Wikimedia\TestingAccessWrapper;
/**
* A test-only LocalisationCache that caches all data in memory for test speed.
*/
class TestLocalisationCache extends LocalisationCache {
/**
* A cache of the parsed data for tests. Services are reset between every test, which forces
* localization to be recached between every test, which is unreasonably slow. As an
* optimization, we cache our data in a static member for tests.
*
* @var array[]
*/
private static $testingCache = [];
private const PROPERTY_NAMES = [ 'data', 'sourceLanguage' ];
/** @var self */
private $selfAccess;
public function __construct() {
parent::__construct( ...func_get_args() );
$this->selfAccess = TestingAccessWrapper::newFromObject( $this );
}
public function recache( $code ) {
$hookContainer = MediaWikiServices::getInstance()->getHookContainer();
// Test run performance is killed if we have to regenerate l10n for every test
$cacheKey = sha1( json_encode( [
$code,
$this->selfAccess->options->get( MainConfigNames::ExtensionMessagesFiles ),
$this->selfAccess->options->get( MainConfigNames::MessagesDirs ),
$hookContainer->getHandlerDescriptions( 'LocalisationCacheRecacheFallback' ),
$hookContainer->getHandlerDescriptions( 'LocalisationCacheRecache' ),
] ) );
if ( isset( self::$testingCache[$cacheKey] ) ) {
foreach ( self::PROPERTY_NAMES as $prop ) {
$this->$prop[$code] = self::$testingCache[$cacheKey][$prop];
}
$loadedItems = $this->selfAccess->loadedItems;
foreach ( self::$testingCache[$cacheKey]['data'] as $key => $item ) {
$loadedItems[$code][$key] = true;
}
$this->selfAccess->loadedItems = $loadedItems;
return;
}
parent::recache( $code );
// Limit the cache size (entries are approx. 1 MB each) but not too much. Critical for tests
// that use e.g. 5 different languages, and then the same 5 languages again, and again, …
if ( count( self::$testingCache ) > 16 ) {
array_pop( self::$testingCache );
}
$cache = [];
foreach ( self::PROPERTY_NAMES as $prop ) {
$cache[$prop] = $this->$prop[$code];
}
// Put the new one in front
self::$testingCache = array_merge( [ $cacheKey => $cache ], self::$testingCache );
}
}