mediawiki-extensions-PageIm.../tests/phpunit/LinksUpdateHookHandlerTest.php
Thiemo Kreuz 71b5c4db22 Relax score calculation in LinksUpdateHookHandler to use floats
This allows to use floats in the $wgPageImagesScores configuration.
Before, decimal places have just been cut off with no warning. I find
this pretty unexpected. When I see the terminology "score" being used,
I always think of float values. I checked all the code that consumes
these scores (it is all internal to LinksUpdateHookHandler), and it's
all fine with floats. I don't see a reason to forcefully cut decimal
places off.

Bug: T212013
Change-Id: I0f1f0ea0865f07b3e58a2fc142dcd838eb687c97
2019-01-10 09:31:20 +01:00

373 lines
11 KiB
PHP

<?php
namespace PageImages\Tests\Hooks;
use LinksUpdate;
use PageImages;
use PageImages\Hooks\LinksUpdateHookHandler;
use ParserOutput;
use MediaWikiTestCase;
use RepoGroup;
use Title;
use Wikimedia\TestingAccessWrapper;
/**
* @covers \PageImages\Hooks\LinksUpdateHookHandler
*
* @group PageImages
*
* @license WTFPL
* @author Thiemo Kreuz
*/
class LinksUpdateHookHandlerTest extends MediaWikiTestCase {
public function tearDown() {
// remove mock added in testGetMetadata()
RepoGroup::destroySingleton();
parent::tearDown();
}
public function setUp() {
parent::setUp();
// Force LinksUpdateHookHandler::getPageImageCanditates to look at all
// sections.
$this->setMwGlobals( 'wgPageImagesLeadSectionOnly', false );
}
/**
* @param array[] $images
* @param array[]|bool $leadImages
*
* @return LinksUpdate
*/
private function getLinksUpdate( array $images, $leadImages = false ) {
$parserOutput = new ParserOutput();
$parserOutput->setExtensionData( 'pageImages', $images );
$parserOutputLead = new ParserOutput();
$parserOutputLead->setExtensionData( 'pageImages', $leadImages ?: $images );
$rev = $this->getMockBuilder( 'Revision' )
->disableOriginalConstructor()
->getMock();
$content = $this->getMockBuilder( 'AbstractContent' )
->disableOriginalConstructor()
->getMock();
$sectionContent = $this->getMockBuilder( 'AbstractContent' )
->disableOriginalConstructor()
->getMock();
$linksUpdate = $this->getMockBuilder( 'LinksUpdate' )
->disableOriginalConstructor()
->getMock();
$linksUpdate->expects( $this->any() )
->method( 'getTitle' )
->will( $this->returnValue( new Title( 'LinksUpdateHandlerTest' ) ) );
$linksUpdate->expects( $this->any() )
->method( 'getParserOutput' )
->will( $this->returnValue( $parserOutput ) );
$linksUpdate->expects( $this->any() )
->method( 'getRevision' )
->will( $this->returnValue( $rev ) );
$rev->expects( $this->any() )
->method( 'getContent' )
->will( $this->returnValue( $content ) );
$content->expects( $this->any() )
->method( 'getSection' )
->will( $this->returnValue( $sectionContent ) );
$sectionContent->expects( $this->any() )
->method( 'getParserOutput' )
->will( $this->returnValue( $parserOutputLead ) );
return $linksUpdate;
}
/**
* Required to make wfFindFile in LinksUpdateHookHandler::getScore return something.
* @return RepoGroup
*/
private function getRepoGroup() {
$file = $this->getMockBuilder( 'File' )
->disableOriginalConstructor()
->getMock();
// ugly hack to avoid all the unmockable crap in FormatMetadata
$file->expects( $this->any() )
->method( 'isDeleted' )
->will( $this->returnValue( true ) );
$repoGroup = $this->getMockBuilder( 'RepoGroup' )
->disableOriginalConstructor()
->getMock();
$repoGroup->expects( $this->any() )
->method( 'findFile' )
->will( $this->returnValue( $file ) );
return $repoGroup;
}
/**
* @dataProvider provideDoLinksUpdate
* @covers \PageImages\Hooks\LinksUpdateHookHandler::doLinksUpdate
*/
public function testDoLinksUpdate(
array $images,
$expectedFreeFileName,
$expectedNonFreeFileName
) {
$linksUpdate = $this->getLinksUpdate( $images );
$mock = TestingAccessWrapper::newFromObject(
$this->getMockBuilder( LinksUpdateHookHandler::class )
->setMethods( [ 'getScore', 'isImageFree' ] )
->getMock()
);
$scoreMap = [];
$isFreeMap = [];
$counter = 0;
foreach ( $images as $image ) {
array_push( $scoreMap, [ $image, $counter++, $image['score'] ] );
array_push( $isFreeMap, [ $image['filename'], $image['isFree'] ] );
}
$mock->expects( $this->any() )
->method( 'getScore' )
->will( $this->returnValueMap( $scoreMap ) );
$mock->expects( $this->any() )
->method( 'isImageFree' )
->will( $this->returnValueMap( $isFreeMap ) );
$mock->doLinksUpdate( $linksUpdate );
$this->assertTrue( property_exists( $linksUpdate, 'mProperties' ), 'precondition' );
if ( is_null( $expectedFreeFileName ) ) {
$this->assertArrayNotHasKey( PageImages::PROP_NAME_FREE, $linksUpdate->mProperties );
} else {
$this->assertSame( $expectedFreeFileName,
$linksUpdate->mProperties[PageImages::PROP_NAME_FREE] );
}
if ( is_null( $expectedNonFreeFileName ) ) {
$this->assertArrayNotHasKey( PageImages::PROP_NAME, $linksUpdate->mProperties );
} else {
$this->assertSame( $expectedNonFreeFileName, $linksUpdate->mProperties[PageImages::PROP_NAME] );
}
}
public function provideDoLinksUpdate() {
return [
// both images are non-free
[
[
[ 'filename' => 'A.jpg', 'score' => 100, 'isFree' => false ],
[ 'filename' => 'B.jpg', 'score' => 90, 'isFree' => false ],
],
null,
'A.jpg'
],
// both images are free
[
[
[ 'filename' => 'A.jpg', 'score' => 100, 'isFree' => true ],
[ 'filename' => 'B.jpg', 'score' => 90, 'isFree' => true ],
],
'A.jpg',
null
],
// one free (with a higher score), one non-free image
[
[
[ 'filename' => 'A.jpg', 'score' => 100, 'isFree' => true ],
[ 'filename' => 'B.jpg', 'score' => 90, 'isFree' => false ],
],
'A.jpg',
null
],
// one non-free (with a higher score), one free image
[
[
[ 'filename' => 'A.jpg', 'score' => 100, 'isFree' => false ],
[ 'filename' => 'B.jpg', 'score' => 90, 'isFree' => true ],
],
'B.jpg',
'A.jpg'
]
];
}
/**
* @covers \PageImages\Hooks\LinksUpdateHookHandler::getPageImageCandidates
*/
public function testGetPageImageCandidates() {
$candidates = [
[ 'filename' => 'A.jpg', 'score' => 100, 'isFree' => false ],
[ 'filename' => 'B.jpg', 'score' => 90, 'isFree' => false ],
];
$linksUpdate = $this->getLinksUpdate( $candidates, array_slice( $candidates, 0, 1 ) );
// should get without lead.
$handler = new LinksUpdateHookHandler();
$this->setMwGlobals( 'wgPageImagesLeadSectionOnly', false );
$images = $handler->getPageImageCandidates( $linksUpdate );
$this->assertCount( 2, $images, 'All images are returned.' );
$this->setMwGlobals( 'wgPageImagesLeadSectionOnly', true );
$images = $handler->getPageImageCandidates( $linksUpdate );
$this->assertCount( 1, $images, 'Only lead images are returned.' );
}
/**
* @dataProvider provideGetScore
*/
public function testGetScore( $image, $scoreFromTable, $position, $expected ) {
$mock = TestingAccessWrapper::newFromObject(
$this->getMockBuilder( LinksUpdateHookHandler::class )
->setMethods( [ 'scoreFromTable', 'getMetadata', 'getRatio', 'getBlacklist' ] )
->getMock()
);
$mock->expects( $this->any() )
->method( 'scoreFromTable' )
->will( $this->returnValue( $scoreFromTable ) );
$mock->expects( $this->any() )
->method( 'getRatio' )
->will( $this->returnValue( 0 ) );
$mock->expects( $this->any() )
->method( 'getBlacklist' )
->will( $this->returnValue( [ 'blacklisted.jpg' => 1 ] ) );
$score = $mock->getScore( $image, $position );
$this->assertEquals( $expected, $score );
}
public function provideGetScore() {
return [
[
[ 'filename' => 'A.jpg', 'handler' => [ 'width' => 100 ] ],
100,
0,
// width score + ratio score + position score
100 + 100 + 8
],
[
[ 'filename' => 'A.jpg', 'fullwidth' => 100 ],
50,
1,
// width score + ratio score + position score
106
],
[
[ 'filename' => 'A.jpg', 'fullwidth' => 100 ],
50,
2,
// width score + ratio score + position score
104
],
[
[ 'filename' => 'A.jpg', 'fullwidth' => 100 ],
50,
3,
// width score + ratio score + position score
103
],
[
[ 'filename' => 'blacklisted.jpg', 'fullwidth' => 100 ],
50,
3,
// blacklist score
- 1000
],
];
}
/**
* @dataProvider provideScoreFromTable
* @covers \PageImages\Hooks\LinksUpdateHookHandler::scoreFromTable
*/
public function testScoreFromTable( array $scores, $value, $expected ) {
$handlerWrapper = TestingAccessWrapper::newFromObject( new LinksUpdateHookHandler );
$score = $handlerWrapper->scoreFromTable( $value, $scores );
$this->assertEquals( $expected, $score );
}
public function provideScoreFromTable() {
global $wgPageImagesScores;
return [
'no match' => [ [], 100, 0 ],
'float' => [ [ 0.5 ], 0, 0.5 ],
'always min when below range' => [ [ 200 => 2, 800 => 1 ], 0, 2 ],
'always max when above range' => [ [ 200 => 2, 800 => 1 ], 1000, 1 ],
'always min when below range (reversed)' => [ [ 800 => 1, 200 => 2 ], 0, 2 ],
'always max when above range (reversed)' => [ [ 800 => 1, 200 => 2 ], 1000, 1 ],
'min match' => [ [ 200 => 2, 400 => 3, 800 => 1 ], 200, 2 ],
'above min' => [ [ 200 => 2, 400 => 3, 800 => 1 ], 201, 3 ],
'second last match' => [ [ 200 => 2, 400 => 3, 800 => 1 ], 400, 3 ],
'above second last' => [ [ 200 => 2, 400 => 3, 800 => 1 ], 401, 1 ],
// These test cases use the default values from extension.json
[ $wgPageImagesScores['width'], 100, -100 ],
[ $wgPageImagesScores['width'], 119, -100 ],
[ $wgPageImagesScores['width'], 300, 10 ],
[ $wgPageImagesScores['width'], 400, 10 ],
[ $wgPageImagesScores['width'], 500, 5 ],
[ $wgPageImagesScores['width'], 600, 5 ],
[ $wgPageImagesScores['width'], 601, 0 ],
[ $wgPageImagesScores['width'], 999, 0 ],
[ $wgPageImagesScores['galleryImageWidth'], 99, -100 ],
[ $wgPageImagesScores['galleryImageWidth'], 100, 0 ],
[ $wgPageImagesScores['galleryImageWidth'], 500, 0 ],
[ $wgPageImagesScores['ratio'], 1, -100 ],
[ $wgPageImagesScores['ratio'], 3, -100 ],
[ $wgPageImagesScores['ratio'], 4, 0 ],
[ $wgPageImagesScores['ratio'], 5, 0 ],
[ $wgPageImagesScores['ratio'], 10, 5 ],
[ $wgPageImagesScores['ratio'], 20, 5 ],
[ $wgPageImagesScores['ratio'], 25, 0 ],
[ $wgPageImagesScores['ratio'], 30, 0 ],
[ $wgPageImagesScores['ratio'], 31, -100 ],
[ $wgPageImagesScores['ratio'], 40, -100 ],
'T212013' => [ $wgPageImagesScores['width'], 0, -100 ],
];
}
/**
* @dataProvider provideIsFreeImage
* @covers \PageImages\Hooks\LinksUpdateHookHandler::isImageFree
*/
public function testIsFreeImage( $fileName, $metadata, $expected ) {
RepoGroup::setSingleton( $this->getRepoGroup() );
$mock = TestingAccessWrapper::newFromObject(
$this->getMockBuilder( LinksUpdateHookHandler::class )
->setMethods( [ 'fetchFileMetadata' ] )
->getMock()
);
$mock->expects( $this->any() )
->method( 'fetchFileMetadata' )
->will( $this->returnValue( $metadata ) );
$this->assertEquals( $expected, $mock->isImageFree( $fileName ) );
}
public function provideIsFreeImage() {
return [
[ 'A.jpg', [], true ],
[ 'A.jpg', [ 'NonFree' => [ 'value' => '0' ] ], true ],
[ 'A.jpg', [ 'NonFree' => [ 'value' => 0 ] ], true ],
[ 'A.jpg', [ 'NonFree' => [ 'value' => false ] ], true ],
[ 'A.jpg', [ 'NonFree' => [ 'value' => 'something' ] ], false ],
[ 'A.jpg', [ 'something' => [ 'value' => 'something' ] ], true ],
];
}
}