Instead of always generating thumbnails based on the original, this adds the ability to generate thumbnails based on references buckets. The buckets themselves have their generation chained following the same process (smaller bucket generated based on bigger bucket). In situations where no suitable bucket is found, the original is used, like it used to. This is entirely optional, as most non-WMF wikis would probably prefer to keep generating all thumbnails based on originals. The quality implications have been verified through a survey aimed at Commons users and people actually preferred the chained version. Presumably due to the multiple passes of sharpening which maintained visual details better for small thumbnail sizes. Change-Id: I285d56b2024c81365247338f85c1e0aa708cb21e Mingle: https://wikimedia.mingle.thoughtworks.com/projects/multimedia/cards/600 Bug: 67525
348 lines
12 KiB
PHP
348 lines
12 KiB
PHP
<?php
|
|
|
|
class FileRepoFileTest extends MediaWikiMediaTestCase {
|
|
/**
|
|
* @dataProvider getThumbnailBucketProvider
|
|
* @covers File::getThumbnailBucket
|
|
*/
|
|
public function testGetThumbnailBucket( $data ) {
|
|
$this->setMwGlobals( 'wgThumbnailBuckets', $data['buckets'] );
|
|
$this->setMwGlobals( 'wgThumbnailMinimumBucketDistance', $data['minimumBucketDistance'] );
|
|
|
|
$fileMock = $this->getMockBuilder( 'File' )
|
|
->setConstructorArgs( array( 'fileMock', false ) )
|
|
->setMethods( array( 'getWidth' ) )
|
|
->getMockForAbstractClass();
|
|
|
|
$fileMock->expects( $this->any() )->method( 'getWidth' )->will(
|
|
$this->returnValue( $data['width'] ) );
|
|
|
|
$this->assertEquals(
|
|
$data['expectedBucket'],
|
|
$fileMock->getThumbnailBucket( $data['requestedWidth'] ),
|
|
$data['message'] );
|
|
}
|
|
|
|
public function getThumbnailBucketProvider() {
|
|
$defaultBuckets = array( 256, 512, 1024, 2048, 4096 );
|
|
|
|
return array(
|
|
array( array(
|
|
'buckets' => $defaultBuckets,
|
|
'minimumBucketDistance' => 0,
|
|
'width' => 3000,
|
|
'requestedWidth' => 120,
|
|
'expectedBucket' => 256,
|
|
'message' => 'Picking bucket bigger than requested size'
|
|
) ),
|
|
array( array(
|
|
'buckets' => $defaultBuckets,
|
|
'minimumBucketDistance' => 0,
|
|
'width' => 3000,
|
|
'requestedWidth' => 300,
|
|
'expectedBucket' => 512,
|
|
'message' => 'Picking bucket bigger than requested size'
|
|
) ),
|
|
array( array(
|
|
'buckets' => $defaultBuckets,
|
|
'minimumBucketDistance' => 0,
|
|
'width' => 3000,
|
|
'requestedWidth' => 1024,
|
|
'expectedBucket' => 2048,
|
|
'message' => 'Picking bucket bigger than requested size'
|
|
) ),
|
|
array( array(
|
|
'buckets' => $defaultBuckets,
|
|
'minimumBucketDistance' => 0,
|
|
'width' => 3000,
|
|
'requestedWidth' => 2048,
|
|
'expectedBucket' => false,
|
|
'message' => 'Picking no bucket because none is bigger than the requested size'
|
|
) ),
|
|
array( array(
|
|
'buckets' => $defaultBuckets,
|
|
'minimumBucketDistance' => 0,
|
|
'width' => 3000,
|
|
'requestedWidth' => 3500,
|
|
'expectedBucket' => false,
|
|
'message' => 'Picking no bucket because requested size is bigger than original'
|
|
) ),
|
|
array( array(
|
|
'buckets' => array( 1024 ),
|
|
'minimumBucketDistance' => 0,
|
|
'width' => 3000,
|
|
'requestedWidth' => 1024,
|
|
'expectedBucket' => false,
|
|
'message' => 'Picking no bucket because requested size equals biggest bucket'
|
|
) ),
|
|
array( array(
|
|
'buckets' => null,
|
|
'minimumBucketDistance' => 0,
|
|
'width' => 3000,
|
|
'requestedWidth' => 1024,
|
|
'expectedBucket' => false,
|
|
'message' => 'Picking no bucket because no buckets have been specified'
|
|
) ),
|
|
array( array(
|
|
'buckets' => array( 256, 512 ),
|
|
'minimumBucketDistance' => 10,
|
|
'width' => 3000,
|
|
'requestedWidth' => 245,
|
|
'expectedBucket' => 256,
|
|
'message' => 'Requested width is distant enough from next bucket for it to be picked'
|
|
) ),
|
|
array( array(
|
|
'buckets' => array( 256, 512 ),
|
|
'minimumBucketDistance' => 10,
|
|
'width' => 3000,
|
|
'requestedWidth' => 246,
|
|
'expectedBucket' => 512,
|
|
'message' => 'Requested width is too close to next bucket, picking next one'
|
|
) ),
|
|
);
|
|
}
|
|
|
|
/**
|
|
* @dataProvider getThumbnailSourceProvider
|
|
* @covers File::getThumbnailSource
|
|
*/
|
|
public function testGetThumbnailSource( $data ) {
|
|
$backendMock = $this->getMockBuilder( 'FSFileBackend' )
|
|
->setConstructorArgs( array( array( 'name' => 'backendMock', 'wikiId' => wfWikiId() ) ) )
|
|
->getMock();
|
|
|
|
$repoMock = $this->getMockBuilder( 'FileRepo' )
|
|
->setConstructorArgs( array( array( 'name' => 'repoMock', 'backend' => $backendMock ) ) )
|
|
->setMethods( array( 'fileExists', 'getLocalReference' ) )
|
|
->getMock();
|
|
|
|
$fsFile = new FSFile( 'fsFilePath' );
|
|
|
|
$repoMock->expects( $this->any() )->method( 'fileExists' )->will(
|
|
$this->returnValue( true ) );
|
|
|
|
$repoMock->expects( $this->any() )->method( 'getLocalReference' )->will(
|
|
$this->returnValue( $fsFile ) );
|
|
|
|
$handlerMock = $this->getMock( 'BitmapHandler', array( 'supportsBucketing' ) );
|
|
$handlerMock->expects( $this->any() )->method( 'supportsBucketing' )->will(
|
|
$this->returnValue( $data['supportsBucketing'] ) );
|
|
|
|
$fileMock = $this->getMockBuilder( 'File' )
|
|
->setConstructorArgs( array( 'fileMock', $repoMock ) )
|
|
->setMethods( array( 'getThumbnailBucket', 'getLocalRefPath', 'getHandler' ) )
|
|
->getMockForAbstractClass();
|
|
|
|
$fileMock->expects( $this->any() )->method( 'getThumbnailBucket' )->will(
|
|
$this->returnValue( $data['thumbnailBucket'] ) );
|
|
|
|
$fileMock->expects( $this->any() )->method( 'getLocalRefPath' )->will(
|
|
$this->returnValue( 'localRefPath' ) );
|
|
|
|
$fileMock->expects( $this->any() )->method( 'getHandler' )->will(
|
|
$this->returnValue( $handlerMock ) );
|
|
|
|
$reflection = new ReflectionClass( $fileMock );
|
|
$reflection_property = $reflection->getProperty( 'handler' );
|
|
$reflection_property->setAccessible( true );
|
|
$reflection_property->setValue( $fileMock, $handlerMock );
|
|
|
|
if ( !is_null( $data['tmpBucketedThumbCache'] ) ) {
|
|
$reflection_property = $reflection->getProperty( 'tmpBucketedThumbCache' );
|
|
$reflection_property->setAccessible( true );
|
|
$reflection_property->setValue( $fileMock, $data['tmpBucketedThumbCache'] );
|
|
}
|
|
|
|
$result = $fileMock->getThumbnailSource(
|
|
array( 'physicalWidth' => $data['physicalWidth'] ) );
|
|
|
|
$this->assertEquals( $data['expectedPath'], $result['path'], $data['message'] );
|
|
}
|
|
|
|
public function getThumbnailSourceProvider() {
|
|
return array(
|
|
array( array(
|
|
'supportsBucketing' => true,
|
|
'tmpBucketedThumbCache' => null,
|
|
'thumbnailBucket' => 1024,
|
|
'physicalWidth' => 2048,
|
|
'expectedPath' => 'fsFilePath',
|
|
'message' => 'Path downloaded from storage'
|
|
) ),
|
|
array( array(
|
|
'supportsBucketing' => true,
|
|
'tmpBucketedThumbCache' => array( 1024 => '/tmp/shouldnotexist' + rand() ),
|
|
'thumbnailBucket' => 1024,
|
|
'physicalWidth' => 2048,
|
|
'expectedPath' => 'fsFilePath',
|
|
'message' => 'Path downloaded from storage because temp file is missing'
|
|
) ),
|
|
array( array(
|
|
'supportsBucketing' => true,
|
|
'tmpBucketedThumbCache' => array( 1024 => '/tmp' ),
|
|
'thumbnailBucket' => 1024,
|
|
'physicalWidth' => 2048,
|
|
'expectedPath' => '/tmp',
|
|
'message' => 'Temporary path because temp file was found'
|
|
) ),
|
|
array( array(
|
|
'supportsBucketing' => false,
|
|
'tmpBucketedThumbCache' => null,
|
|
'thumbnailBucket' => 1024,
|
|
'physicalWidth' => 2048,
|
|
'expectedPath' => 'localRefPath',
|
|
'message' => 'Original file path because bucketing is unsupported by handler'
|
|
) ),
|
|
array( array(
|
|
'supportsBucketing' => true,
|
|
'tmpBucketedThumbCache' => null,
|
|
'thumbnailBucket' => false,
|
|
'physicalWidth' => 2048,
|
|
'expectedPath' => 'localRefPath',
|
|
'message' => 'Original file path because no width provided'
|
|
) ),
|
|
);
|
|
}
|
|
|
|
/**
|
|
* @dataProvider generateBucketsIfNeededProvider
|
|
* @covers File::generateBucketsIfNeeded
|
|
*/
|
|
public function testGenerateBucketsIfNeeded( $data ) {
|
|
$this->setMwGlobals( 'wgThumbnailBuckets', $data['buckets'] );
|
|
|
|
$backendMock = $this->getMockBuilder( 'FSFileBackend' )
|
|
->setConstructorArgs( array( array( 'name' => 'backendMock', 'wikiId' => wfWikiId() ) ) )
|
|
->getMock();
|
|
|
|
$repoMock = $this->getMockBuilder( 'FileRepo' )
|
|
->setConstructorArgs( array( array( 'name' => 'repoMock', 'backend' => $backendMock ) ) )
|
|
->setMethods( array( 'fileExists', 'getLocalReference' ) )
|
|
->getMock();
|
|
|
|
$fileMock = $this->getMockBuilder( 'File' )
|
|
->setConstructorArgs( array( 'fileMock', $repoMock ) )
|
|
->setMethods( array( 'getWidth', 'getBucketThumbPath', 'makeTransformTmpFile', 'generateAndSaveThumb', 'getHandler' ) )
|
|
->getMockForAbstractClass();
|
|
|
|
$handlerMock = $this->getMock( 'JpegHandler', array( 'supportsBucketing' ) );
|
|
$handlerMock->expects( $this->any() )->method( 'supportsBucketing' )->will(
|
|
$this->returnValue( true ) );
|
|
|
|
$fileMock->expects( $this->any() )->method( 'getHandler' )->will(
|
|
$this->returnValue( $handlerMock ) );
|
|
|
|
$reflectionMethod = new ReflectionMethod( 'File', 'generateBucketsIfNeeded' );
|
|
$reflectionMethod->setAccessible( true );
|
|
|
|
$fileMock->expects( $this->any() )
|
|
->method( 'getWidth' )
|
|
->will( $this->returnValue( $data['width'] ) );
|
|
|
|
$fileMock->expects( $data['expectedGetBucketThumbPathCalls'] )
|
|
->method( 'getBucketThumbPath' );
|
|
|
|
$repoMock->expects( $data['expectedFileExistsCalls'] )
|
|
->method( 'fileExists' )
|
|
->will( $this->returnValue( $data['fileExistsReturn'] ) );
|
|
|
|
$fileMock->expects( $data['expectedMakeTransformTmpFile'] )
|
|
->method( 'makeTransformTmpFile' )
|
|
->will( $this->returnValue( $data['makeTransformTmpFileReturn'] ) );
|
|
|
|
$fileMock->expects( $data['expectedGenerateAndSaveThumb'] )
|
|
->method( 'generateAndSaveThumb' )
|
|
->will( $this->returnValue( $data['generateAndSaveThumbReturn'] ) );
|
|
|
|
$this->assertEquals( $data['expectedResult'],
|
|
$reflectionMethod->invoke(
|
|
$fileMock,
|
|
array(
|
|
'physicalWidth' => $data['physicalWidth'],
|
|
'physicalHeight' => $data['physicalHeight'] )
|
|
),
|
|
$data['message'] );
|
|
}
|
|
|
|
public function generateBucketsIfNeededProvider() {
|
|
$defaultBuckets = array( 256, 512, 1024, 2048, 4096 );
|
|
|
|
return array(
|
|
array( array(
|
|
'buckets' => $defaultBuckets,
|
|
'width' => 256,
|
|
'physicalWidth' => 256,
|
|
'physicalHeight' => 100,
|
|
'expectedGetBucketThumbPathCalls' => $this->never(),
|
|
'expectedFileExistsCalls' => $this->never(),
|
|
'fileExistsReturn' => null,
|
|
'expectedMakeTransformTmpFile' => $this->never(),
|
|
'makeTransformTmpFileReturn' => false,
|
|
'expectedGenerateAndSaveThumb' => $this->never(),
|
|
'generateAndSaveThumbReturn' => false,
|
|
'expectedResult' => false,
|
|
'message' => 'No bucket found, nothing to generate'
|
|
) ),
|
|
array( array(
|
|
'buckets' => $defaultBuckets,
|
|
'width' => 5000,
|
|
'physicalWidth' => 300,
|
|
'physicalHeight' => 200,
|
|
'expectedGetBucketThumbPathCalls' => $this->once(),
|
|
'expectedFileExistsCalls' => $this->once(),
|
|
'fileExistsReturn' => true,
|
|
'expectedMakeTransformTmpFile' => $this->never(),
|
|
'makeTransformTmpFileReturn' => false,
|
|
'expectedGenerateAndSaveThumb' => $this->never(),
|
|
'generateAndSaveThumbReturn' => false,
|
|
'expectedResult' => false,
|
|
'message' => 'File already exists, no reason to generate buckets'
|
|
) ),
|
|
array( array(
|
|
'buckets' => $defaultBuckets,
|
|
'width' => 5000,
|
|
'physicalWidth' => 300,
|
|
'physicalHeight' => 200,
|
|
'expectedGetBucketThumbPathCalls' => $this->once(),
|
|
'expectedFileExistsCalls' => $this->once(),
|
|
'fileExistsReturn' => false,
|
|
'expectedMakeTransformTmpFile' => $this->once(),
|
|
'makeTransformTmpFileReturn' => false,
|
|
'expectedGenerateAndSaveThumb' => $this->never(),
|
|
'generateAndSaveThumbReturn' => false,
|
|
'expectedResult' => false,
|
|
'message' => 'Cannot generate temp file for bucket'
|
|
) ),
|
|
array( array(
|
|
'buckets' => $defaultBuckets,
|
|
'width' => 5000,
|
|
'physicalWidth' => 300,
|
|
'physicalHeight' => 200,
|
|
'expectedGetBucketThumbPathCalls' => $this->once(),
|
|
'expectedFileExistsCalls' => $this->once(),
|
|
'fileExistsReturn' => false,
|
|
'expectedMakeTransformTmpFile' => $this->once(),
|
|
'makeTransformTmpFileReturn' => new TempFSFile( '/tmp/foo' ),
|
|
'expectedGenerateAndSaveThumb' => $this->once(),
|
|
'generateAndSaveThumbReturn' => false,
|
|
'expectedResult' => false,
|
|
'message' => 'Bucket image could not be generated'
|
|
) ),
|
|
array( array(
|
|
'buckets' => $defaultBuckets,
|
|
'width' => 5000,
|
|
'physicalWidth' => 300,
|
|
'physicalHeight' => 200,
|
|
'expectedGetBucketThumbPathCalls' => $this->once(),
|
|
'expectedFileExistsCalls' => $this->once(),
|
|
'fileExistsReturn' => false,
|
|
'expectedMakeTransformTmpFile' => $this->once(),
|
|
'makeTransformTmpFileReturn' => new TempFSFile( '/tmp/foo' ),
|
|
'expectedGenerateAndSaveThumb' => $this->once(),
|
|
'generateAndSaveThumbReturn' => new ThumbnailImage( false, 'bar', false, false ),
|
|
'expectedResult' => true,
|
|
'message' => 'Bucket image could not be generated'
|
|
) ),
|
|
);
|
|
}
|
|
}
|