1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144
|
<?php
use Wikimedia\TestingAccessWrapper;
/**
* PHPUnit tests for MemoizedCallable class.
* @covers MemoizedCallable
*/
class MemoizedCallableTest extends PHPUnit\Framework\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 )
->setMethods( [ 'reverse' ] )->getMock();
$mock->expects( $this->any() )
->method( 'reverse' )
->will( $this->returnCallback( '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 )
->setMethods( [ 'computeSomething' ] )->getMock();
$observer->expects( $this->once() )
->method( 'computeSomething' )
->will( $this->returnValue( '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() );
}
/**
* @covers MemoizedCallable::invoke
*/
public function testInvokeVariadic() {
$memoized = new MemoizedCallable( 'sprintf' );
$this->assertEquals(
$memoized->invokeArgs( [ 'this is %s', 'correct' ] ),
$memoized->invoke( 'this is %s', 'correct' )
);
}
/**
* @covers MemoizedCallable::call
*/
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 );
}
/**
* Closure names should be distinct.
*/
public function testMemoizedClosure() {
$a = new MemoizedCallable( function () {
return 'a';
} );
$b = new MemoizedCallable( function () {
return 'b';
} );
$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( function () {
return rand();
} );
$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 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 {
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;
}
}
|