wiki.techinc.nl/tests/phpunit/unit/includes/libs/MemoizedCallableTest.php
Umherirrender da63db52af libs: Add missing documentation to class properties
Add doc-typehints to class properties found by the PropertyDocumentation
sniff to improve the documentation.

Once the sniff is enabled it avoids that new code is missing type
declarations. This is focused on documentation and does not change code.

Change-Id: I46f46f1855ca32c89a276b06f4e2051ff541886e
2024-09-13 17:24:12 +00:00

157 lines
4.2 KiB
PHP

<?php
namespace Wikimedia\Tests;
use InvalidArgumentException;
use MediaWikiCoversValidator;
use MemoizedCallable;
use PHPUnit\Framework\TestCase;
use stdClass;
use Wikimedia\TestingAccessWrapper;
/**
* @covers \MemoizedCallable
*/
class MemoizedCallableTest extends TestCase {
use MediaWikiCoversValidator;
/**
* The memoized callable should relate inputs to outputs in the same
* way as the original underlying callable.
*/
public function testReturnValuePassedThrough() {
$mock = $this->getMockBuilder( stdClass::class )
->addMethods( [ 'reverse' ] )->getMock();
$mock->method( 'reverse' )
->willReturnCallback( 'strrev' );
$memoized = new MemoizedCallable( [ $mock, 'reverse' ] );
$this->assertEquals( 'flow', $memoized->invoke( 'wolf' ) );
}
/**
* Consecutive calls to the memoized callable with the same arguments
* should result in just one invocation of the underlying callable.
*
* @requires extension apcu
*/
public function testCallableMemoized() {
$observer = $this->getMockBuilder( stdClass::class )
->addMethods( [ 'computeSomething' ] )->getMock();
$observer->expects( $this->once() )
->method( 'computeSomething' )
->willReturn( 'ok' );
$memoized = new ArrayBackedMemoizedCallable( [ $observer, 'computeSomething' ] );
// First invocation -- delegates to $observer->computeSomething()
$this->assertEquals( 'ok', $memoized->invoke() );
// Second invocation -- returns memoized result
$this->assertEquals( 'ok', $memoized->invoke() );
}
public function testInvokeVariadic() {
$memoized = new MemoizedCallable( 'sprintf' );
$this->assertEquals(
$memoized->invokeArgs( [ 'this is %s', 'correct' ] ),
$memoized->invoke( 'this is %s', 'correct' )
);
}
public function testShortcutMethod() {
$this->assertEquals(
'this is correct',
MemoizedCallable::call( 'sprintf', [ 'this is %s', 'correct' ] )
);
}
/**
* Outlier TTL values should be coerced to range 1 - 86400.
*/
public function testTTLMaxMin() {
$memoized = TestingAccessWrapper::newFromObject( new MemoizedCallable( 'abs', 100000 ) );
$this->assertEquals( 86400, $memoized->ttl );
$memoized = TestingAccessWrapper::newFromObject( new MemoizedCallable( 'abs', -10 ) );
$this->assertSame( 1, $memoized->ttl );
}
public static function makeA() {
return 'a';
}
public static function makeB() {
return 'b';
}
public static function makeRand() {
return rand();
}
/**
* Closure names should be distinct.
*/
public function testMemoizedClosure() {
$a = new MemoizedCallable( [ self::class, 'makeA' ] );
$b = new MemoizedCallable( [ self::class, 'makeB' ] );
$this->assertEquals( 'a', $a->invokeArgs() );
$this->assertEquals( 'b', $b->invokeArgs() );
$a = TestingAccessWrapper::newFromObject( $a );
$b = TestingAccessWrapper::newFromObject( $b );
$this->assertNotEquals(
$a->callableName,
$b->callableName
);
$c = new ArrayBackedMemoizedCallable( [ self::class, 'makeRand' ] );
$this->assertEquals( $c->invokeArgs(), $c->invokeArgs(), 'memoized random' );
}
public function testNonScalarArguments() {
$memoized = new MemoizedCallable( 'gettype' );
$this->expectExceptionMessage( "non-scalar argument" );
$this->expectException( InvalidArgumentException::class );
$memoized->invoke( (object)[] );
}
public function testUnnamedCallable() {
$this->expectExceptionMessage( 'Cannot memoize unnamed closure' );
$this->expectException( InvalidArgumentException::class );
$memoized = new MemoizedCallable( static function () {
return 'a';
} );
}
public function testNotCallable() {
$this->expectExceptionMessage( "must be an instance of callable" );
$this->expectException( InvalidArgumentException::class );
$memoized = new MemoizedCallable( 14 );
}
}
/**
* A MemoizedCallable subclass that stores function return values
* in an instance property rather than APC or APCu.
*/
class ArrayBackedMemoizedCallable extends MemoizedCallable {
/** @var array */
private $cache = [];
protected function fetchResult( $key, &$success ) {
if ( array_key_exists( $key, $this->cache ) ) {
$success = true;
return $this->cache[$key];
}
$success = false;
return false;
}
protected function storeResult( $key, $result ) {
$this->cache[$key] = $result;
}
}