summaryrefslogtreecommitdiff
path: root/tests/phpunit/includes/media
diff options
context:
space:
mode:
Diffstat (limited to 'tests/phpunit/includes/media')
-rw-r--r--tests/phpunit/includes/media/BitmapMetadataHandlerTest.php125
-rw-r--r--tests/phpunit/includes/media/BitmapScalingTest.php136
-rw-r--r--tests/phpunit/includes/media/ExifBitmapTest.php122
-rw-r--r--tests/phpunit/includes/media/ExifRotationTest.php249
-rw-r--r--tests/phpunit/includes/media/ExifTest.php51
-rw-r--r--tests/phpunit/includes/media/FormatMetadataTest.php29
-rw-r--r--tests/phpunit/includes/media/GIFMetadataExtractorTest.php95
-rw-r--r--tests/phpunit/includes/media/GIFTest.php85
-rw-r--r--tests/phpunit/includes/media/IPTCTest.php55
-rw-r--r--tests/phpunit/includes/media/JpegMetadataExtractorTest.php94
-rw-r--r--tests/phpunit/includes/media/JpegTest.php29
-rw-r--r--tests/phpunit/includes/media/PNGMetadataExtractorTest.php141
-rw-r--r--tests/phpunit/includes/media/PNGTest.php88
-rw-r--r--tests/phpunit/includes/media/SVGMetadataExtractorTest.php88
-rw-r--r--tests/phpunit/includes/media/TiffTest.php36
-rw-r--r--tests/phpunit/includes/media/XMPTest.php154
16 files changed, 1577 insertions, 0 deletions
diff --git a/tests/phpunit/includes/media/BitmapMetadataHandlerTest.php b/tests/phpunit/includes/media/BitmapMetadataHandlerTest.php
new file mode 100644
index 00000000..a0d5cd86
--- /dev/null
+++ b/tests/phpunit/includes/media/BitmapMetadataHandlerTest.php
@@ -0,0 +1,125 @@
+<?php
+class BitmapMetadataHandlerTest extends MediaWikiTestCase {
+
+ public function setUp() {
+ $this->filePath = dirname( __FILE__ ) . '/../../data/media/';
+ }
+
+ /**
+ * Test if having conflicting metadata values from different
+ * types of metadata, that the right one takes precedence.
+ *
+ * Basically the file has IPTC and XMP metadata, the
+ * IPTC should override the XMP, except for the multilingual
+ * translation (to en) where XMP should win.
+ */
+ public function testMultilingualCascade() {
+ global $wgShowEXIF;
+ if ( !$wgShowEXIF ) {
+ $this->markTestIncomplete( "This test needs the exif extension." );
+ }
+
+ $meta = BitmapMetadataHandler::Jpeg( $this->filePath .
+ '/Xmp-exif-multilingual_test.jpg' );
+
+ $expected = array(
+ 'x-default' => 'right(iptc)',
+ 'en' => 'right translation',
+ '_type' => 'lang'
+ );
+
+ $this->assertArrayHasKey( 'ImageDescription', $meta,
+ 'Did not extract any ImageDescription info?!' );
+
+ $this->assertEquals( $expected, $meta['ImageDescription'] );
+ }
+
+ /**
+ * Test for jpeg comments are being handled by
+ * BitmapMetadataHandler correctly.
+ *
+ * There's more extensive tests of comment extraction in
+ * JpegMetadataExtractorTests.php
+ */
+ public function testJpegComment() {
+ $meta = BitmapMetadataHandler::Jpeg( $this->filePath .
+ 'jpeg-comment-utf.jpg' );
+
+ $this->assertEquals( 'UTF-8 JPEG Comment — ¼',
+ $meta['JPEGFileComment'][0] );
+ }
+
+ public function testIPTCDates() {
+ $meta = BitmapMetadataHandler::Jpeg( $this->filePath .
+ 'iptc-timetest.jpg' );
+
+ $this->assertEquals( '2020:07:14 01:36:05', $meta['DateTimeDigitized'] );
+ $this->assertEquals( '1997:03:02 00:01:02', $meta['DateTimeOriginal'] );
+ }
+ /* File has an invalid time (+ one valid but really weird time)
+ * that shouldn't be included
+ */
+ public function testIPTCDatesInvalid() {
+ $meta = BitmapMetadataHandler::Jpeg( $this->filePath .
+ 'iptc-timetest-invalid.jpg' );
+
+ $this->assertEquals( '1845:03:02 00:01:02', $meta['DateTimeOriginal'] );
+ $this->assertFalse( isset( $meta['DateTimeDigitized'] ) );
+ }
+
+ /**
+ * XMP data should take priority over iptc data
+ * when hash has been updated, but not when
+ * the hash is wrong.
+ */
+ public function testMerging() {
+ $merger = new BitmapMetadataHandler();
+ $merger->addMetadata( array( 'foo' => 'xmp' ), 'xmp-general' );
+ $merger->addMetadata( array( 'bar' => 'xmp' ), 'xmp-general' );
+ $merger->addMetadata( array( 'baz' => 'xmp' ), 'xmp-general' );
+ $merger->addMetadata( array( 'fred' => 'xmp' ), 'xmp-general' );
+ $merger->addMetadata( array( 'foo' => 'iptc (hash)' ), 'iptc-good-hash' );
+ $merger->addMetadata( array( 'bar' => 'iptc (bad hash)' ), 'iptc-bad-hash' );
+ $merger->addMetadata( array( 'baz' => 'iptc (bad hash)' ), 'iptc-bad-hash' );
+ $merger->addMetadata( array( 'fred' => 'iptc (no hash)' ), 'iptc-no-hash' );
+ $merger->addMetadata( array( 'baz' => 'exif' ), 'exif' );
+
+ $actual = $merger->getMetadataArray();
+ $expected = array(
+ 'foo' => 'xmp',
+ 'bar' => 'iptc (bad hash)',
+ 'baz' => 'exif',
+ 'fred' => 'xmp',
+ );
+ $this->assertEquals( $expected, $actual );
+ }
+
+ public function testPNGXMP() {
+ $handler = new BitmapMetadataHandler();
+ $result = $handler->png( $this->filePath . 'xmp.png' );
+ $expected = array (
+ 'frameCount' => 0,
+ 'loopCount' => 1,
+ 'duration' => 0,
+ 'bitDepth' => 1,
+ 'colorType' => 'index-coloured',
+ 'metadata' => array (
+ 'SerialNumber' => '123456789',
+ '_MW_PNG_VERSION' => 1,
+ ),
+ );
+ $this->assertEquals( $expected, $result );
+ }
+ public function testPNGNative() {
+ $handler = new BitmapMetadataHandler();
+ $result = $handler->png( $this->filePath . 'Png-native-test.png' );
+ $expected = 'http://example.com/url';
+ $this->assertEquals( $expected, $result['metadata']['Identifier']['x-default'] );
+ }
+ public function testTiffByteOrder() {
+ $handler = new BitmapMetadataHandler();
+ $res = $handler->getTiffByteOrder( $this->filePath . 'test.tiff' );
+ $this->assertEquals( 'LE', $res );
+ }
+
+}
diff --git a/tests/phpunit/includes/media/BitmapScalingTest.php b/tests/phpunit/includes/media/BitmapScalingTest.php
new file mode 100644
index 00000000..5bcd3232
--- /dev/null
+++ b/tests/phpunit/includes/media/BitmapScalingTest.php
@@ -0,0 +1,136 @@
+<?php
+
+class BitmapScalingTest extends MediaWikiTestCase {
+
+ function setUp() {
+ global $wgMaxImageArea;
+ $this->oldMaxImageArea = $wgMaxImageArea;
+ $wgMaxImageArea = 1.25e7; // 3500x3500
+ }
+ function tearDown() {
+ global $wgMaxImageArea;
+ $wgMaxImageArea = $this->oldMaxImageArea;
+ }
+ /**
+ * @dataProvider provideNormaliseParams
+ */
+ function testNormaliseParams( $fileDimensions, $expectedParams, $params, $msg ) {
+ $file = new FakeDimensionFile( $fileDimensions );
+ $handler = new BitmapHandler;
+ $valid = $handler->normaliseParams( $file, $params );
+ $this->assertTrue( $valid );
+ $this->assertEquals( $expectedParams, $params, $msg );
+ }
+
+ function provideNormaliseParams() {
+ return array(
+ /* Regular resize operations */
+ array(
+ array( 1024, 768 ),
+ array(
+ 'width' => 512, 'height' => 384,
+ 'physicalWidth' => 512, 'physicalHeight' => 384,
+ 'page' => 1,
+ ),
+ array( 'width' => 512 ),
+ 'Resizing with width set',
+ ),
+ array(
+ array( 1024, 768 ),
+ array(
+ 'width' => 512, 'height' => 384,
+ 'physicalWidth' => 512, 'physicalHeight' => 384,
+ 'page' => 1,
+ ),
+ array( 'width' => 512, 'height' => 768 ),
+ 'Resizing with height set too high',
+ ),
+ array(
+ array( 1024, 768 ),
+ array(
+ 'width' => 512, 'height' => 384,
+ 'physicalWidth' => 512, 'physicalHeight' => 384,
+ 'page' => 1,
+ ),
+ array( 'width' => 1024, 'height' => 384 ),
+ 'Resizing with height set',
+ ),
+
+ /* Very tall images */
+ array(
+ array( 1000, 100 ),
+ array(
+ 'width' => 5, 'height' => 1,
+ 'physicalWidth' => 5, 'physicalHeight' => 1,
+ 'page' => 1,
+ ),
+ array( 'width' => 5 ),
+ 'Very wide image',
+ ),
+
+ array(
+ array( 100, 1000 ),
+ array(
+ 'width' => 1, 'height' => 10,
+ 'physicalWidth' => 1, 'physicalHeight' => 10,
+ 'page' => 1,
+ ),
+ array( 'width' => 1 ),
+ 'Very high image',
+ ),
+ array(
+ array( 100, 1000 ),
+ array(
+ 'width' => 1, 'height' => 5,
+ 'physicalWidth' => 1, 'physicalHeight' => 10,
+ 'page' => 1,
+ ),
+ array( 'width' => 10, 'height' => 5 ),
+ 'Very high image with height set',
+ ),
+ /* Max image area */
+ array(
+ array( 4000, 4000 ),
+ array(
+ 'width' => 5000, 'height' => 5000,
+ 'physicalWidth' => 4000, 'physicalHeight' => 4000,
+ 'page' => 1,
+ ),
+ array( 'width' => 5000 ),
+ 'Bigger than max image size but doesn\'t need scaling',
+ ),
+ );
+ }
+ function testTooBigImage() {
+ $file = new FakeDimensionFile( array( 4000, 4000 ) );
+ $handler = new BitmapHandler;
+ $params = array( 'width' => '3700' ); // Still bigger than max size.
+ $this->assertFalse( $handler->normaliseParams( $file, $params ) );
+ }
+ function testTooBigMustRenderImage() {
+ $file = new FakeDimensionFile( array( 4000, 4000 ) );
+ $file->mustRender = true;
+ $handler = new BitmapHandler;
+ $params = array( 'width' => '5000' ); // Still bigger than max size.
+ $this->assertFalse( $handler->normaliseParams( $file, $params ) );
+ }
+}
+
+class FakeDimensionFile extends File {
+ public $mustRender = false;
+
+ public function __construct( $dimensions ) {
+ parent::__construct( Title::makeTitle( NS_FILE, 'Test' ), null );
+
+ $this->dimensions = $dimensions;
+ }
+ public function getWidth( $page = 1 ) {
+ return $this->dimensions[0];
+ }
+ public function getHeight( $page = 1 ) {
+ return $this->dimensions[1];
+ }
+ public function mustRender() {
+ return $this->mustRender;
+ }
+}
diff --git a/tests/phpunit/includes/media/ExifBitmapTest.php b/tests/phpunit/includes/media/ExifBitmapTest.php
new file mode 100644
index 00000000..4282d3c8
--- /dev/null
+++ b/tests/phpunit/includes/media/ExifBitmapTest.php
@@ -0,0 +1,122 @@
+<?php
+class ExifBitmapTest extends MediaWikiTestCase {
+
+ public function setUp() {
+ global $wgShowEXIF;
+ $this->showExif = $wgShowEXIF;
+ $wgShowEXIF = true;
+ $this->handler = new ExifBitmapHandler;
+ if ( !wfDl( 'exif' ) ) {
+ $this->markTestSkipped( "This test needs the exif extension." );
+ }
+ }
+
+ public function tearDown() {
+ global $wgShowEXIF;
+ $wgShowEXIF = $this->showExif;
+ }
+
+ public function testIsOldBroken() {
+ if ( !wfDl( 'exif' ) ) {
+ $this->markTestIncomplete( "This test needs the exif extension." );
+ }
+ $res = $this->handler->isMetadataValid( null, ExifBitmapHandler::OLD_BROKEN_FILE );
+ $this->assertEquals( ExifBitmapHandler::METADATA_COMPATIBLE, $res );
+ }
+ public function testIsBrokenFile() {
+ global $wgShowEXIF;
+ if ( !$wgShowEXIF ) {
+ $this->markTestIncomplete( "This test needs the exif extension." );
+ }
+ $res = $this->handler->isMetadataValid( null, ExifBitmapHandler::BROKEN_FILE );
+ $this->assertEquals( ExifBitmapHandler::METADATA_GOOD, $res );
+ }
+ public function testIsInvalid() {
+ global $wgShowEXIF;
+ if ( !$wgShowEXIF ) {
+ $this->markTestIncomplete( "This test needs the exif extension." );
+ }
+ $res = $this->handler->isMetadataValid( null, 'Something Invalid Here.' );
+ $this->assertEquals( ExifBitmapHandler::METADATA_BAD, $res );
+ }
+ public function testGoodMetadata() {
+ global $wgShowEXIF;
+ if ( !$wgShowEXIF ) {
+ $this->markTestIncomplete( "This test needs the exif extension." );
+ }
+ $meta = 'a:16:{s:10:"ImageWidth";i:20;s:11:"ImageLength";i:20;s:13:"BitsPerSample";a:3:{i:0;i:8;i:1;i:8;i:2;i:8;}s:11:"Compression";i:5;s:25:"PhotometricInterpretation";i:2;s:16:"ImageDescription";s:17:"Created with GIMP";s:12:"StripOffsets";i:8;s:11:"Orientation";i:1;s:15:"SamplesPerPixel";i:3;s:12:"RowsPerStrip";i:64;s:15:"StripByteCounts";i:238;s:11:"XResolution";s:19:"1207959552/16777216";s:11:"YResolution";s:19:"1207959552/16777216";s:19:"PlanarConfiguration";i:1;s:14:"ResolutionUnit";i:2;s:22:"MEDIAWIKI_EXIF_VERSION";i:2;}';
+ $res = $this->handler->isMetadataValid( null, $meta );
+ $this->assertEquals( ExifBitmapHandler::METADATA_GOOD, $res );
+ }
+ public function testIsOldGood() {
+ global $wgShowEXIF;
+ if ( !$wgShowEXIF ) {
+ $this->markTestIncomplete( "This test needs the exif extension." );
+ }
+ $meta = 'a:16:{s:10:"ImageWidth";i:20;s:11:"ImageLength";i:20;s:13:"BitsPerSample";a:3:{i:0;i:8;i:1;i:8;i:2;i:8;}s:11:"Compression";i:5;s:25:"PhotometricInterpretation";i:2;s:16:"ImageDescription";s:17:"Created with GIMP";s:12:"StripOffsets";i:8;s:11:"Orientation";i:1;s:15:"SamplesPerPixel";i:3;s:12:"RowsPerStrip";i:64;s:15:"StripByteCounts";i:238;s:11:"XResolution";s:19:"1207959552/16777216";s:11:"YResolution";s:19:"1207959552/16777216";s:19:"PlanarConfiguration";i:1;s:14:"ResolutionUnit";i:2;s:22:"MEDIAWIKI_EXIF_VERSION";i:1;}';
+ $res = $this->handler->isMetadataValid( null, $meta );
+ $this->assertEquals( ExifBitmapHandler::METADATA_COMPATIBLE, $res );
+ }
+ // Handle metadata from paged tiff handler (gotten via instant commons)
+ // gracefully.
+ public function testPagedTiffHandledGracefully() {
+ global $wgShowEXIF;
+ if ( !$wgShowEXIF ) {
+ $this->markTestIncomplete( "This test needs the exif extension." );
+ }
+ $meta = 'a:6:{s:9:"page_data";a:1:{i:1;a:5:{s:5:"width";i:643;s:6:"height";i:448;s:5:"alpha";s:4:"true";s:4:"page";i:1;s:6:"pixels";i:288064;}}s:10:"page_count";i:1;s:10:"first_page";i:1;s:9:"last_page";i:1;s:4:"exif";a:9:{s:10:"ImageWidth";i:643;s:11:"ImageLength";i:448;s:11:"Compression";i:5;s:25:"PhotometricInterpretation";i:2;s:11:"Orientation";i:1;s:15:"SamplesPerPixel";i:4;s:12:"RowsPerStrip";i:50;s:19:"PlanarConfiguration";i:1;s:22:"MEDIAWIKI_EXIF_VERSION";i:1;}s:21:"TIFF_METADATA_VERSION";s:3:"1.4";}';
+ $res = $this->handler->isMetadataValid( null, $meta );
+ $this->assertEquals( ExifBitmapHandler::METADATA_BAD, $res );
+ }
+
+ function testConvertMetadataLatest() {
+ $metadata = array(
+ 'foo' => array( 'First', 'Second', '_type' => 'ol' ),
+ 'MEDIAWIKI_EXIF_VERSION' => 2
+ );
+ $res = $this->handler->convertMetadataVersion( $metadata, 2 );
+ $this->assertEquals( $metadata, $res );
+ }
+ function testConvertMetadataToOld() {
+ $metadata = array(
+ 'foo' => array( 'First', 'Second', '_type' => 'ol' ),
+ 'bar' => array( 'First', 'Second', '_type' => 'ul' ),
+ 'baz' => array( 'First', 'Second' ),
+ 'fred' => 'Single',
+ 'MEDIAWIKI_EXIF_VERSION' => 2,
+ );
+ $expected = array(
+ 'foo' => "\n#First\n#Second",
+ 'bar' => "\n*First\n*Second",
+ 'baz' => "\n*First\n*Second",
+ 'fred' => 'Single',
+ 'MEDIAWIKI_EXIF_VERSION' => 1,
+ );
+ $res = $this->handler->convertMetadataVersion( $metadata, 1 );
+ $this->assertEquals( $expected, $res );
+ }
+ function testConvertMetadataSoftware() {
+ $metadata = array(
+ 'Software' => array( array('GIMP', '1.1' ) ),
+ 'MEDIAWIKI_EXIF_VERSION' => 2,
+ );
+ $expected = array(
+ 'Software' => 'GIMP (Version 1.1)',
+ 'MEDIAWIKI_EXIF_VERSION' => 1,
+ );
+ $res = $this->handler->convertMetadataVersion( $metadata, 1 );
+ $this->assertEquals( $expected, $res );
+ }
+ function testConvertMetadataSoftwareNormal() {
+ $metadata = array(
+ 'Software' => array( "GIMP 1.2", "vim" ),
+ 'MEDIAWIKI_EXIF_VERSION' => 2,
+ );
+ $expected = array(
+ 'Software' => "\n*GIMP 1.2\n*vim",
+ 'MEDIAWIKI_EXIF_VERSION' => 1,
+ );
+ $res = $this->handler->convertMetadataVersion( $metadata, 1 );
+ $this->assertEquals( $expected, $res );
+ }
+}
diff --git a/tests/phpunit/includes/media/ExifRotationTest.php b/tests/phpunit/includes/media/ExifRotationTest.php
new file mode 100644
index 00000000..639091d0
--- /dev/null
+++ b/tests/phpunit/includes/media/ExifRotationTest.php
@@ -0,0 +1,249 @@
+<?php
+
+/**
+ * Tests related to auto rotation
+ */
+class ExifRotationTest extends MediaWikiTestCase {
+
+ function setUp() {
+ parent::setUp();
+ $this->filePath = dirname( __FILE__ ) . '/../../data/media/';
+ $this->handler = new BitmapHandler();
+ $this->repo = new FSRepo(array(
+ 'name' => 'temp',
+ 'directory' => wfTempDir() . '/exif-test-' . time() . '-' . mt_rand(),
+ 'url' => 'http://localhost/thumbtest'
+ ));
+ if ( !wfDl( 'exif' ) ) {
+ $this->markTestSkipped( "This test needs the exif extension." );
+ }
+ global $wgShowEXIF;
+ $this->show = $wgShowEXIF;
+ $wgShowEXIF = true;
+
+ global $wgEnableAutoRotation;
+ $this->oldAuto = $wgEnableAutoRotation;
+ $wgEnableAutoRotation = true;
+ }
+ public function tearDown() {
+ global $wgShowEXIF, $wgEnableAutoRotation;
+ $wgShowEXIF = $this->show;
+ $wgEnableAutoRotation = $this->oldAuto;
+ }
+
+ /**
+ *
+ * @dataProvider providerFiles
+ */
+ function testMetadata( $name, $type, $info ) {
+ if ( !BitmapHandler::canRotate() ) {
+ $this->markTestSkipped( "This test needs a rasterizer that can auto-rotate." );
+ }
+ $file = UnregisteredLocalFile::newFromPath( $this->filePath . $name, $type );
+ $this->assertEquals( $info['width'], $file->getWidth(), "$name: width check" );
+ $this->assertEquals( $info['height'], $file->getHeight(), "$name: height check" );
+ }
+
+ /**
+ *
+ * @dataProvider providerFiles
+ */
+ function testRotationRendering( $name, $type, $info, $thumbs ) {
+ if ( !BitmapHandler::canRotate() ) {
+ $this->markTestSkipped( "This test needs a rasterizer that can auto-rotate." );
+ }
+ foreach( $thumbs as $size => $out ) {
+ if( preg_match('/^(\d+)px$/', $size, $matches ) ) {
+ $params = array(
+ 'width' => $matches[1],
+ );
+ } elseif ( preg_match( '/^(\d+)x(\d+)px$/', $size, $matches ) ) {
+ $params = array(
+ 'width' => $matches[1],
+ 'height' => $matches[2]
+ );
+ } else {
+ throw new MWException('bogus test data format ' . $size);
+ }
+
+ $file = $this->localFile( $name, $type );
+ $thumb = $file->transform( $params, File::RENDER_NOW );
+
+ $this->assertEquals( $out[0], $thumb->getWidth(), "$name: thumb reported width check for $size" );
+ $this->assertEquals( $out[1], $thumb->getHeight(), "$name: thumb reported height check for $size" );
+
+ $gis = getimagesize( $thumb->getPath() );
+ if ($out[0] > $info['width']) {
+ // Physical image won't be scaled bigger than the original.
+ $this->assertEquals( $info['width'], $gis[0], "$name: thumb actual width check for $size");
+ $this->assertEquals( $info['height'], $gis[1], "$name: thumb actual height check for $size");
+ } else {
+ $this->assertEquals( $out[0], $gis[0], "$name: thumb actual width check for $size");
+ $this->assertEquals( $out[1], $gis[1], "$name: thumb actual height check for $size");
+ }
+ }
+ }
+
+ private function localFile( $name, $type ) {
+ return new UnregisteredLocalFile( false, $this->repo, $this->filePath . $name, $type );
+ }
+
+ function providerFiles() {
+ return array(
+ array(
+ 'landscape-plain.jpg',
+ 'image/jpeg',
+ array(
+ 'width' => 1024,
+ 'height' => 768,
+ ),
+ array(
+ '800x600px' => array( 800, 600 ),
+ '9999x800px' => array( 1067, 800 ),
+ '800px' => array( 800, 600 ),
+ '600px' => array( 600, 450 ),
+ )
+ ),
+ array(
+ 'portrait-rotated.jpg',
+ 'image/jpeg',
+ array(
+ 'width' => 768, // as rotated
+ 'height' => 1024, // as rotated
+ ),
+ array(
+ '800x600px' => array( 450, 600 ),
+ '9999x800px' => array( 600, 800 ),
+ '800px' => array( 800, 1067 ),
+ '600px' => array( 600, 800 ),
+ )
+ )
+ );
+ }
+
+ /**
+ * Same as before, but with auto-rotation disabled.
+ * @dataProvider providerFilesNoAutoRotate
+ */
+ function testMetadataNoAutoRotate( $name, $type, $info ) {
+ global $wgEnableAutoRotation;
+ $wgEnableAutoRotation = false;
+
+ $file = UnregisteredLocalFile::newFromPath( $this->filePath . $name, $type );
+ $this->assertEquals( $info['width'], $file->getWidth(), "$name: width check" );
+ $this->assertEquals( $info['height'], $file->getHeight(), "$name: height check" );
+
+ $wgEnableAutoRotation = true;
+ }
+
+ /**
+ *
+ * @dataProvider providerFilesNoAutoRotate
+ */
+ function testRotationRenderingNoAutoRotate( $name, $type, $info, $thumbs ) {
+ global $wgEnableAutoRotation;
+ $wgEnableAutoRotation = false;
+
+ foreach( $thumbs as $size => $out ) {
+ if( preg_match('/^(\d+)px$/', $size, $matches ) ) {
+ $params = array(
+ 'width' => $matches[1],
+ );
+ } elseif ( preg_match( '/^(\d+)x(\d+)px$/', $size, $matches ) ) {
+ $params = array(
+ 'width' => $matches[1],
+ 'height' => $matches[2]
+ );
+ } else {
+ throw new MWException('bogus test data format ' . $size);
+ }
+
+ $file = $this->localFile( $name, $type );
+ $thumb = $file->transform( $params, File::RENDER_NOW );
+
+ $this->assertEquals( $out[0], $thumb->getWidth(), "$name: thumb reported width check for $size" );
+ $this->assertEquals( $out[1], $thumb->getHeight(), "$name: thumb reported height check for $size" );
+
+ $gis = getimagesize( $thumb->getPath() );
+ if ($out[0] > $info['width']) {
+ // Physical image won't be scaled bigger than the original.
+ $this->assertEquals( $info['width'], $gis[0], "$name: thumb actual width check for $size");
+ $this->assertEquals( $info['height'], $gis[1], "$name: thumb actual height check for $size");
+ } else {
+ $this->assertEquals( $out[0], $gis[0], "$name: thumb actual width check for $size");
+ $this->assertEquals( $out[1], $gis[1], "$name: thumb actual height check for $size");
+ }
+ }
+ $wgEnableAutoRotation = true;
+ }
+
+ function providerFilesNoAutoRotate() {
+ return array(
+ array(
+ 'landscape-plain.jpg',
+ 'image/jpeg',
+ array(
+ 'width' => 1024,
+ 'height' => 768,
+ ),
+ array(
+ '800x600px' => array( 800, 600 ),
+ '9999x800px' => array( 1067, 800 ),
+ '800px' => array( 800, 600 ),
+ '600px' => array( 600, 450 ),
+ )
+ ),
+ array(
+ 'portrait-rotated.jpg',
+ 'image/jpeg',
+ array(
+ 'width' => 1024, // since not rotated
+ 'height' => 768, // since not rotated
+ ),
+ array(
+ '800x600px' => array( 800, 600 ),
+ '9999x800px' => array( 1067, 800 ),
+ '800px' => array( 800, 600 ),
+ '600px' => array( 600, 450 ),
+ )
+ )
+ );
+ }
+
+
+ const TEST_WIDTH = 100;
+ const TEST_HEIGHT = 200;
+
+ /**
+ * @dataProvider provideBitmapExtractPreRotationDimensions
+ */
+ function testBitmapExtractPreRotationDimensions( $rotation, $expected ) {
+ $result = $this->handler->extractPreRotationDimensions( array(
+ 'physicalWidth' => self::TEST_WIDTH,
+ 'physicalHeight' => self::TEST_HEIGHT,
+ ), $rotation );
+ $this->assertEquals( $expected, $result );
+ }
+
+ function provideBitmapExtractPreRotationDimensions() {
+ return array(
+ array(
+ 0,
+ array( self::TEST_WIDTH, self::TEST_HEIGHT )
+ ),
+ array(
+ 90,
+ array( self::TEST_HEIGHT, self::TEST_WIDTH )
+ ),
+ array(
+ 180,
+ array( self::TEST_WIDTH, self::TEST_HEIGHT )
+ ),
+ array(
+ 270,
+ array( self::TEST_HEIGHT, self::TEST_WIDTH )
+ ),
+ );
+ }
+}
+
diff --git a/tests/phpunit/includes/media/ExifTest.php b/tests/phpunit/includes/media/ExifTest.php
new file mode 100644
index 00000000..9b490e92
--- /dev/null
+++ b/tests/phpunit/includes/media/ExifTest.php
@@ -0,0 +1,51 @@
+<?php
+class ExifTest extends MediaWikiTestCase {
+
+ public function setUp() {
+ $this->mediaPath = dirname( __FILE__ ) . '/../../data/media/';
+
+ global $wgShowEXIF;
+ $this->showExif = $wgShowEXIF;
+ $wgShowEXIF = true;
+ }
+ public function tearDown() {
+ global $wgShowEXIF;
+ $wgShowEXIF = $this->showExif;
+ }
+
+ public function testGPSExtraction() {
+ if ( !wfDl( 'exif' ) ) {
+ $this->markTestIncomplete( "This test needs the exif extension." );
+ }
+
+ $filename = $this->mediaPath . 'exif-gps.jpg';
+ $seg = JpegMetadataExtractor::segmentSplitter( $filename );
+ $exif = new Exif( $filename, $seg['byteOrder'] );
+ $data = $exif->getFilteredData();
+ $expected = array(
+ 'GPSLatitude' => 88.5180555556,
+ 'GPSLongitude' => -21.12357,
+ 'GPSAltitude' => -200,
+ 'GPSDOP' => '5/1',
+ 'GPSVersionID' => '2.2.0.0',
+ );
+ $this->assertEquals( $expected, $data, '', 0.0000000001 );
+ }
+ public function testUnicodeUserComment() {
+ if ( !wfDl( 'exif' ) ) {
+ $this->markTestIncomplete( "This test needs the exif extension." );
+ }
+
+ $filename = $this->mediaPath . 'exif-user-comment.jpg';
+ $seg = JpegMetadataExtractor::segmentSplitter( $filename );
+ $exif = new Exif( $filename, $seg['byteOrder'] );
+ $data = $exif->getFilteredData();
+
+ $expected = array(
+ 'UserComment' => 'test⁔comment'
+ );
+ $this->assertEquals( $expected, $data );
+ }
+
+
+}
diff --git a/tests/phpunit/includes/media/FormatMetadataTest.php b/tests/phpunit/includes/media/FormatMetadataTest.php
new file mode 100644
index 00000000..db36dea3
--- /dev/null
+++ b/tests/phpunit/includes/media/FormatMetadataTest.php
@@ -0,0 +1,29 @@
+<?php
+class FormatMetadataTest extends MediaWikiTestCase {
+ public function testInvalidDate() {
+ global $wgShowEXIF;
+ if ( !$wgShowEXIF ) {
+ $this->markTestIncomplete( "This test needs the exif extension." );
+ }
+
+ $file = UnregisteredLocalFile::newFromPath( dirname( __FILE__ ) .
+ '/../../data/media/broken_exif_date.jpg', 'image/jpeg' );
+
+ // Throws an error if bug hit
+ $meta = $file->formatMetadata();
+ $this->assertNotEquals( false, $meta, 'Valid metadata extracted' );
+
+ // Find date exif entry
+ $this->assertArrayHasKey( 'visible', $meta );
+ $dateIndex = null;
+ foreach ( $meta['visible'] as $i => $data ) {
+ if ( $data['id'] == 'exif-datetimeoriginal' ) {
+ $dateIndex = $i;
+ }
+ }
+ $this->assertNotNull( $dateIndex, 'Date entry exists in metadata' );
+ $this->assertEquals( '0000:01:00 00:02:27',
+ $meta['visible'][$dateIndex]['value'],
+ 'File with invalid date metadata (bug 29471)' );
+ }
+} \ No newline at end of file
diff --git a/tests/phpunit/includes/media/GIFMetadataExtractorTest.php b/tests/phpunit/includes/media/GIFMetadataExtractorTest.php
new file mode 100644
index 00000000..59b30441
--- /dev/null
+++ b/tests/phpunit/includes/media/GIFMetadataExtractorTest.php
@@ -0,0 +1,95 @@
+<?php
+class GIFMetadataExtractorTest extends MediaWikiTestCase {
+
+ public function setUp() {
+ $this->mediaPath = dirname( __FILE__ ) . '/../../data/media/';
+ }
+ /**
+ * Put in a file, and see if the metadata coming out is as expected.
+ * @param $filename String
+ * @param $expected Array The extracted metadata.
+ * @dataProvider dataGetMetadata
+ */
+ public function testGetMetadata( $filename, $expected ) {
+ $actual = GIFMetadataExtractor::getMetadata( $this->mediaPath . $filename );
+ $this->assertEquals( $expected, $actual );
+ }
+ public function dataGetMetadata() {
+
+ $xmpNugget = <<<EOF
+<?xpacket begin='' id='W5M0MpCehiHzreSzNTczkc9d'?>
+<x:xmpmeta xmlns:x='adobe:ns:meta/' x:xmptk='Image::ExifTool 7.30'>
+<rdf:RDF xmlns:rdf='http://www.w3.org/1999/02/22-rdf-syntax-ns#'>
+
+ <rdf:Description rdf:about=''
+ xmlns:Iptc4xmpCore='http://iptc.org/std/Iptc4xmpCore/1.0/xmlns/'>
+ <Iptc4xmpCore:Location>The interwebs</Iptc4xmpCore:Location>
+ </rdf:Description>
+
+ <rdf:Description rdf:about=''
+ xmlns:tiff='http://ns.adobe.com/tiff/1.0/'>
+ <tiff:Artist>Bawolff</tiff:Artist>
+ <tiff:ImageDescription>
+ <rdf:Alt>
+ <rdf:li xml:lang='x-default'>A file to test GIF</rdf:li>
+ </rdf:Alt>
+ </tiff:ImageDescription>
+ </rdf:Description>
+</rdf:RDF>
+</x:xmpmeta>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+<?xpacket end='w'?>
+EOF;
+
+ return array(
+ array( 'nonanimated.gif', array(
+ 'comment' => array( 'GIF test file ⁕ Created with GIMP' ),
+ 'duration' => 0.1,
+ 'frameCount' => 1,
+ 'looped' => false,
+ 'xmp' => '',
+ )
+ ),
+ array( 'animated.gif', array(
+ 'comment' => array( 'GIF test file . Created with GIMP' ),
+ 'duration' => 2.4,
+ 'frameCount' => 4,
+ 'looped' => true,
+ 'xmp' => '',
+ )
+ ),
+
+ array( 'animated-xmp.gif', array(
+ 'xmp' => $xmpNugget,
+ 'duration' => 2.4,
+ 'frameCount' => 4,
+ 'looped' => true,
+ 'comment' => array( 'GIƒ·test·file' ),
+ )
+ ),
+ );
+ }
+}
diff --git a/tests/phpunit/includes/media/GIFTest.php b/tests/phpunit/includes/media/GIFTest.php
new file mode 100644
index 00000000..42c25ca5
--- /dev/null
+++ b/tests/phpunit/includes/media/GIFTest.php
@@ -0,0 +1,85 @@
+<?php
+class GIFHandlerTest extends MediaWikiTestCase {
+
+ public function setUp() {
+ $this->filePath = dirname( __FILE__ ) . '/../../data/media/';
+ $this->handler = new GIFHandler();
+ }
+
+ public function testInvalidFile() {
+ $res = $this->handler->getMetadata( null, $this->filePath . 'README' );
+ $this->assertEquals( GIFHandler::BROKEN_FILE, $res );
+ }
+ /**
+ * @param $filename String basename of the file to check
+ * @param $expected boolean Expected result.
+ * @dataProvider dataIsAnimated
+ */
+ public function testIsAnimanted( $filename, $expected ) {
+ $file = UnregisteredLocalFile::newFromPath( $this->filePath . $filename,
+ 'image/gif' );
+ $actual = $this->handler->isAnimatedImage( $file );
+ $this->assertEquals( $expected, $actual );
+ }
+ public function dataIsAnimated() {
+ return array(
+ array( 'animated.gif', true ),
+ array( 'nonanimated.gif', false ),
+ );
+ }
+
+ /**
+ * @param $filename String
+ * @param $expected Integer Total image area
+ * @dataProvider dataGetImageArea
+ */
+ public function testGetImageArea( $filename, $expected ) {
+ $file = UnregisteredLocalFile::newFromPath( $this->filePath . $filename,
+ 'image/gif' );
+ $actual = $this->handler->getImageArea( $file, $file->getWidth(), $file->getHeight() );
+ $this->assertEquals( $expected, $actual );
+ }
+ public function dataGetImageArea() {
+ return array(
+ array( 'animated.gif', 5400 ),
+ array( 'nonanimated.gif', 1350 ),
+ );
+ }
+
+ /**
+ * @param $metadata String Serialized metadata
+ * @param $expected Integer One of the class constants of GIFHandler
+ * @dataProvider dataIsMetadataValid
+ */
+ public function testIsMetadataValid( $metadata, $expected ) {
+ $actual = $this->handler->isMetadataValid( null, $metadata );
+ $this->assertEquals( $expected, $actual );
+ }
+ public function dataIsMetadataValid() {
+ return array(
+ array( GIFHandler::BROKEN_FILE, GIFHandler::METADATA_GOOD ),
+ array( '', GIFHandler::METADATA_BAD ),
+ array( null, GIFHandler::METADATA_BAD ),
+ array( 'Something invalid!', GIFHandler::METADATA_BAD ),
+ array( 'a:4:{s:10:"frameCount";i:1;s:6:"looped";b:0;s:8:"duration";d:0.1000000000000000055511151231257827021181583404541015625;s:8:"metadata";a:2:{s:14:"GIFFileComment";a:1:{i:0;s:35:"GIF test file ⁕ Created with GIMP";}s:15:"_MW_GIF_VERSION";i:1;}}', GIFHandler::METADATA_GOOD ),
+ );
+ }
+
+ /**
+ * @param $filename String
+ * @param $expected String Serialized array
+ * @dataProvider dataGetMetadata
+ */
+ public function testGetMetadata( $filename, $expected ) {
+ $file = UnregisteredLocalFile::newFromPath( $this->filePath . $filename,
+ 'image/gif' );
+ $actual = $this->handler->getMetadata( $file, $this->filePath . $filename );
+ $this->assertEquals( unserialize( $expected ), unserialize( $actual ) );
+ }
+ public function dataGetMetadata() {
+ return array(
+ array( 'nonanimated.gif', 'a:4:{s:10:"frameCount";i:1;s:6:"looped";b:0;s:8:"duration";d:0.1000000000000000055511151231257827021181583404541015625;s:8:"metadata";a:2:{s:14:"GIFFileComment";a:1:{i:0;s:35:"GIF test file ⁕ Created with GIMP";}s:15:"_MW_GIF_VERSION";i:1;}}' ),
+ array( 'animated-xmp.gif', 'a:4:{s:10:"frameCount";i:4;s:6:"looped";b:1;s:8:"duration";d:2.399999999999999911182158029987476766109466552734375;s:8:"metadata";a:5:{s:6:"Artist";s:7:"Bawolff";s:16:"ImageDescription";a:2:{s:9:"x-default";s:18:"A file to test GIF";s:5:"_type";s:4:"lang";}s:15:"SublocationDest";s:13:"The interwebs";s:14:"GIFFileComment";a:1:{i:0;s:16:"GIƒ·test·file";}s:15:"_MW_GIF_VERSION";i:1;}}' ),
+ );
+ }
+}
diff --git a/tests/phpunit/includes/media/IPTCTest.php b/tests/phpunit/includes/media/IPTCTest.php
new file mode 100644
index 00000000..ec6deeb8
--- /dev/null
+++ b/tests/phpunit/includes/media/IPTCTest.php
@@ -0,0 +1,55 @@
+<?php
+class IPTCTest extends MediaWikiTestCase {
+ public function testRecognizeUtf8() {
+ // utf-8 is the only one used in practise.
+ $res = IPTC::getCharset( "\x1b%G" );
+ $this->assertEquals( 'UTF-8', $res );
+ }
+
+ public function testIPTCParseNoCharset88591() {
+ // basically IPTC for keyword with value of 0xBC which is 1/4 in iso-8859-1
+ // This data doesn't specify a charset. We're supposed to guess
+ // (which basically means utf-8 if valid, windows 1252 (iso 8859-1) if not)
+ $iptcData = "Photoshop 3.0\08BIM\4\4\0\0\0\0\0\x06\x1c\x02\x19\x00\x01\xBC";
+ $res = IPTC::Parse( $iptcData );
+ $this->assertEquals( array( '¼' ), $res['Keywords'] );
+ }
+ /* This one contains a sequence that's valid iso 8859-1 but not valid utf8 */
+ /* \xC3 = Ã, \xB8 = ¸ */
+ public function testIPTCParseNoCharset88591b() {
+ $iptcData = "Photoshop 3.0\08BIM\4\4\0\0\0\0\0\x09\x1c\x02\x19\x00\x04\xC3\xC3\xC3\xB8";
+ $res = IPTC::Parse( $iptcData );
+ $this->assertEquals( array( 'ÃÃø' ), $res['Keywords'] );
+ }
+ /* Same as testIPTCParseNoCharset88591b, but forcing the charset to utf-8.
+ * What should happen is the first "\xC3\xC3" should be dropped as invalid,
+ * leaving \xC3\xB8, which is ø
+ */
+ public function testIPTCParseForcedUTFButInvalid() {
+ $iptcData = "Photoshop 3.0\08BIM\4\4\0\0\0\0\0\x11\x1c\x02\x19\x00\x04\xC3\xC3\xC3\xB8"
+ . "\x1c\x01\x5A\x00\x03\x1B\x25\x47";
+ $res = IPTC::Parse( $iptcData );
+ $this->assertEquals( array( 'ø' ), $res['Keywords'] );
+ }
+ public function testIPTCParseNoCharsetUTF8() {
+ $iptcData = "Photoshop 3.0\08BIM\4\4\0\0\0\0\0\x07\x1c\x02\x19\x00\x02¼";
+ $res = IPTC::Parse( $iptcData );
+ $this->assertEquals( array( '¼' ), $res['Keywords'] );
+ }
+ // Testing something that has 2 values for keyword
+ public function testIPTCParseMulti() {
+ $iptcData = /* identifier */ "Photoshop 3.0\08BIM\4\4"
+ /* length */ . "\0\0\0\0\0\x0D"
+ . "\x1c\x02\x19" . "\x00\x01" . "\xBC"
+ . "\x1c\x02\x19" . "\x00\x02" . "\xBC\xBD";
+ $res = IPTC::Parse( $iptcData );
+ $this->assertEquals( array( '¼', '¼½' ), $res['Keywords'] );
+ }
+ public function testIPTCParseUTF8() {
+ // This has the magic "\x1c\x01\x5A\x00\x03\x1B\x25\x47" which marks content as UTF8.
+ $iptcData = "Photoshop 3.0\08BIM\4\4\0\0\0\0\0\x0F\x1c\x02\x19\x00\x02¼\x1c\x01\x5A\x00\x03\x1B\x25\x47";
+ $res = IPTC::Parse( $iptcData );
+ $this->assertEquals( array( '¼' ), $res['Keywords'] );
+ }
+
+}
diff --git a/tests/phpunit/includes/media/JpegMetadataExtractorTest.php b/tests/phpunit/includes/media/JpegMetadataExtractorTest.php
new file mode 100644
index 00000000..61fc9c81
--- /dev/null
+++ b/tests/phpunit/includes/media/JpegMetadataExtractorTest.php
@@ -0,0 +1,94 @@
+<?php
+/*
+ * @todo Could use a test of extended XMP segments. Hard to find programs that
+ * create example files, and creating my own in vim propbably wouldn't
+ * serve as a very good "test". (Adobe photoshop probably creates such files
+ * but it costs money). The implementation of it currently in MediaWiki is based
+ * solely on reading the standard, without any real world test files.
+ */
+class JpegMetadataExtractorTest extends MediaWikiTestCase {
+
+ public function setUp() {
+ $this->filePath = dirname( __FILE__ ) . '/../../data/media/';
+ }
+
+ /**
+ * We also use this test to test padding bytes don't
+ * screw stuff up
+ *
+ * @param $file filename
+ *
+ * @dataProvider dataUtf8Comment
+ */
+ public function testUtf8Comment( $file ) {
+ $res = JpegMetadataExtractor::segmentSplitter( $this->filePath . $file );
+ $this->assertEquals( array( 'UTF-8 JPEG Comment — ¼' ), $res['COM'] );
+ }
+ public function dataUtf8Comment() {
+ return array(
+ array( 'jpeg-comment-utf.jpg' ),
+ array( 'jpeg-padding-even.jpg' ),
+ array( 'jpeg-padding-odd.jpg' ),
+ );
+ }
+ /** The file is iso-8859-1, but it should get auto converted */
+ public function testIso88591Comment() {
+ $res = JpegMetadataExtractor::segmentSplitter( $this->filePath . 'jpeg-comment-iso8859-1.jpg' );
+ $this->assertEquals( array( 'ISO-8859-1 JPEG Comment - ¼' ), $res['COM'] );
+ }
+ /** Comment values that are non-textual (random binary junk) should not be shown.
+ * The example test file has a comment with a 0x5 byte in it which is a control character
+ * and considered binary junk for our purposes.
+ */
+ public function testBinaryCommentStripped() {
+ $res = JpegMetadataExtractor::segmentSplitter( $this->filePath . 'jpeg-comment-binary.jpg' );
+ $this->assertEmpty( $res['COM'] );
+ }
+ /* Very rarely a file can have multiple comments.
+ * Order of comments is based on order inside the file.
+ */
+ public function testMultipleComment() {
+ $res = JpegMetadataExtractor::segmentSplitter( $this->filePath . 'jpeg-comment-multiple.jpg' );
+ $this->assertEquals( array( 'foo', 'bar' ), $res['COM'] );
+ }
+ public function testXMPExtraction() {
+ $res = JpegMetadataExtractor::segmentSplitter( $this->filePath . 'jpeg-xmp-psir.jpg' );
+ $expected = file_get_contents( $this->filePath . 'jpeg-xmp-psir.xmp' );
+ $this->assertEquals( $expected, $res['XMP'] );
+ }
+ public function testPSIRExtraction() {
+ $res = JpegMetadataExtractor::segmentSplitter( $this->filePath . 'jpeg-xmp-psir.jpg' );
+ $expected = '50686f746f73686f7020332e30003842494d04040000000000181c02190004746573741c02190003666f6f1c020000020004';
+ $this->assertEquals( $expected, bin2hex( $res['PSIR'] ) );
+ }
+ public function testXMPExtractionAltAppId() {
+ $res = JpegMetadataExtractor::segmentSplitter( $this->filePath . 'jpeg-xmp-alt.jpg' );
+ $expected = file_get_contents( $this->filePath . 'jpeg-xmp-psir.xmp' );
+ $this->assertEquals( $expected, $res['XMP'] );
+ }
+
+
+ public function testIPTCHashComparisionNoHash() {
+ $segments = JpegMetadataExtractor::segmentSplitter( $this->filePath . 'jpeg-xmp-psir.jpg' );
+ $res = JpegMetadataExtractor::doPSIR( $segments['PSIR'] );
+
+ $this->assertEquals( 'iptc-no-hash', $res );
+ }
+ public function testIPTCHashComparisionBadHash() {
+ $segments = JpegMetadataExtractor::segmentSplitter( $this->filePath . 'jpeg-iptc-bad-hash.jpg' );
+ $res = JpegMetadataExtractor::doPSIR( $segments['PSIR'] );
+
+ $this->assertEquals( 'iptc-bad-hash', $res );
+ }
+ public function testIPTCHashComparisionGoodHash() {
+ $segments = JpegMetadataExtractor::segmentSplitter( $this->filePath . 'jpeg-iptc-good-hash.jpg' );
+ $res = JpegMetadataExtractor::doPSIR( $segments['PSIR'] );
+
+ $this->assertEquals( 'iptc-good-hash', $res );
+ }
+ public function testExifByteOrder() {
+ $res = JpegMetadataExtractor::segmentSplitter( $this->filePath . 'exif-user-comment.jpg' );
+ $expected = 'BE';
+ $this->assertEquals( $expected, $res['byteOrder'] );
+ }
+}
diff --git a/tests/phpunit/includes/media/JpegTest.php b/tests/phpunit/includes/media/JpegTest.php
new file mode 100644
index 00000000..713a3410
--- /dev/null
+++ b/tests/phpunit/includes/media/JpegTest.php
@@ -0,0 +1,29 @@
+<?php
+class JpegTest extends MediaWikiTestCase {
+
+ public function setUp() {
+ $this->filePath = dirname( __FILE__ ) . '/../../data/media/';
+ }
+
+ public function testInvalidFile() {
+ global $wgShowEXIF;
+ if ( !$wgShowEXIF ) {
+ $this->markTestIncomplete( "This test needs the exif extension." );
+ }
+ $jpeg = new JpegHandler;
+ $res = $jpeg->getMetadata( null, $this->filePath . 'README' );
+ $this->assertEquals( ExifBitmapHandler::BROKEN_FILE, $res );
+ }
+ public function testJpegMetadataExtraction() {
+ global $wgShowEXIF;
+ if ( !$wgShowEXIF ) {
+ $this->markTestIncomplete( "This test needs the exif extension." );
+ }
+ $h = new JpegHandler;
+ $res = $h->getMetadata( null, $this->filePath . 'test.jpg' );
+ $expected = 'a:7:{s:16:"ImageDescription";s:9:"Test file";s:11:"XResolution";s:4:"72/1";s:11:"YResolution";s:4:"72/1";s:14:"ResolutionUnit";i:2;s:16:"YCbCrPositioning";i:1;s:15:"JPEGFileComment";a:1:{i:0;s:17:"Created with GIMP";}s:22:"MEDIAWIKI_EXIF_VERSION";i:2;}';
+
+ // Unserialize in case serialization format ever changes.
+ $this->assertEquals( unserialize( $expected ), unserialize( $res ) );
+ }
+}
diff --git a/tests/phpunit/includes/media/PNGMetadataExtractorTest.php b/tests/phpunit/includes/media/PNGMetadataExtractorTest.php
new file mode 100644
index 00000000..9f702c50
--- /dev/null
+++ b/tests/phpunit/includes/media/PNGMetadataExtractorTest.php
@@ -0,0 +1,141 @@
+<?php
+class PNGMetadataExtractorTest extends MediaWikiTestCase {
+
+ function setUp() {
+ $this->filePath = dirname( __FILE__ ) . '/../../data/media/';
+ }
+ /**
+ * Tests zTXt tag (compressed textual metadata)
+ */
+ function testPngNativetZtxt() {
+ $meta = PNGMetadataExtractor::getMetadata( $this->filePath .
+ 'Png-native-test.png' );
+ $expected = "foo bar baz foo foo foo foof foo foo foo foo";
+ $this->assertArrayHasKey( 'text', $meta );
+ $meta = $meta['text'];
+ $this->assertArrayHasKey( 'Make', $meta );
+ $this->assertArrayHasKey( 'x-default', $meta['Make'] );
+
+ $this->assertEquals( $expected, $meta['Make']['x-default'] );
+ }
+
+ /**
+ * Test tEXt tag (Uncompressed textual metadata)
+ */
+ function testPngNativeText() {
+ $meta = PNGMetadataExtractor::getMetadata( $this->filePath .
+ 'Png-native-test.png' );
+ $expected = "Some long image desc";
+ $this->assertArrayHasKey( 'text', $meta );
+ $meta = $meta['text'];
+ $this->assertArrayHasKey( 'ImageDescription', $meta );
+ $this->assertArrayHasKey( 'x-default', $meta['ImageDescription'] );
+ $this->assertArrayHasKey( '_type', $meta['ImageDescription'] );
+
+ $this->assertEquals( $expected, $meta['ImageDescription']['x-default'] );
+ }
+
+ /**
+ * tEXt tags must be encoded iso-8859-1 (vs iTXt which are utf-8)
+ * Make sure non-ascii characters get converted properly
+ */
+ function testPngNativeTextNonAscii() {
+ $meta = PNGMetadataExtractor::getMetadata( $this->filePath .
+ 'Png-native-test.png' );
+
+ // Note the Copyright symbol here is a utf-8 one
+ // (aka \xC2\xA9) where in the file its iso-8859-1
+ // encoded as just \xA9.
+ $expected = "© 2010 Bawolff";
+
+
+ $this->assertArrayHasKey( 'text', $meta );
+ $meta = $meta['text'];
+ $this->assertArrayHasKey( 'Copyright', $meta );
+ $this->assertArrayHasKey( 'x-default', $meta['Copyright'] );
+
+ $this->assertEquals( $expected, $meta['Copyright']['x-default'] );
+ }
+
+ /**
+ * Test extraction of pHYs tags, which can tell what the
+ * actual resolution of the image is (aka in dots per meter).
+ function testPngPhysTag () {
+ $meta = PNGMetadataExtractor::getMetadata( $this->filePath .
+ 'Png-native-test.png' );
+
+ $this->assertArrayHasKey( 'text', $meta );
+ $meta = $meta['text'];
+
+ $this->assertEquals( '2835/100', $meta['XResolution'] );
+ $this->assertEquals( '2835/100', $meta['YResolution'] );
+ $this->assertEquals( 3, $meta['ResolutionUnit'] ); // 3 = cm
+ }
+
+ /**
+ * Given a normal static PNG, check the animation metadata returned.
+ */
+ function testStaticPngAnimationMetadata() {
+ $meta = PNGMetadataExtractor::getMetadata( $this->filePath .
+ 'Png-native-test.png' );
+
+ $this->assertEquals( 0, $meta['frameCount'] );
+ $this->assertEquals( 1, $meta['loopCount'] );
+ $this->assertEquals( 0, $meta['duration'] );
+ }
+
+ /**
+ * Given an animated APNG image file
+ * check it gets animated metadata right.
+ */
+ function testApngAnimationMetadata() {
+ $meta = PNGMetadataExtractor::getMetadata( $this->filePath .
+ 'Animated_PNG_example_bouncing_beach_ball.png' );
+
+ $this->assertEquals( 20, $meta['frameCount'] );
+ // Note loop count of 0 = infinity
+ $this->assertEquals( 0, $meta['loopCount'] );
+ $this->assertEquals( 1.5, $meta['duration'], '', 0.00001 );
+ }
+
+ function testPngBitDepth8() {
+ $meta = PNGMetadataExtractor::getMetadata( $this->filePath .
+ 'Png-native-test.png' );
+
+ $this->assertEquals( 8, $meta['bitDepth'] );
+ }
+ function testPngBitDepth1() {
+ $meta = PNGMetadataExtractor::getMetadata( $this->filePath .
+ '1bit-png.png' );
+ $this->assertEquals( 1, $meta['bitDepth'] );
+ }
+
+
+ function testPngIndexColour() {
+ $meta = PNGMetadataExtractor::getMetadata( $this->filePath .
+ 'Png-native-test.png' );
+
+ $this->assertEquals( 'index-coloured', $meta['colorType'] );
+ }
+ function testPngRgbColour() {
+ $meta = PNGMetadataExtractor::getMetadata( $this->filePath .
+ 'rgb-png.png' );
+ $this->assertEquals( 'truecolour-alpha', $meta['colorType'] );
+ }
+ function testPngRgbNoAlphaColour() {
+ $meta = PNGMetadataExtractor::getMetadata( $this->filePath .
+ 'rgb-na-png.png' );
+ $this->assertEquals( 'truecolour', $meta['colorType'] );
+ }
+ function testPngGreyscaleColour() {
+ $meta = PNGMetadataExtractor::getMetadata( $this->filePath .
+ 'greyscale-png.png' );
+ $this->assertEquals( 'greyscale-alpha', $meta['colorType'] );
+ }
+ function testPngGreyscaleNoAlphaColour() {
+ $meta = PNGMetadataExtractor::getMetadata( $this->filePath .
+ 'greyscale-na-png.png' );
+ $this->assertEquals( 'greyscale', $meta['colorType'] );
+ }
+
+}
diff --git a/tests/phpunit/includes/media/PNGTest.php b/tests/phpunit/includes/media/PNGTest.php
new file mode 100644
index 00000000..b782918c
--- /dev/null
+++ b/tests/phpunit/includes/media/PNGTest.php
@@ -0,0 +1,88 @@
+<?php
+class PNGHandlerTest extends MediaWikiTestCase {
+
+ public function setUp() {
+ $this->filePath = dirname( __FILE__ ) . '/../../data/media/';
+ $this->handler = new PNGHandler();
+ }
+
+ public function testInvalidFile() {
+ $res = $this->handler->getMetadata( null, $this->filePath . 'README' );
+ $this->assertEquals( PNGHandler::BROKEN_FILE, $res );
+ }
+ /**
+ * @param $filename String basename of the file to check
+ * @param $expected boolean Expected result.
+ * @dataProvider dataIsAnimated
+ */
+ public function testIsAnimanted( $filename, $expected ) {
+ $file = UnregisteredLocalFile::newFromPath( $this->filePath . $filename,
+ 'image/png' );
+ $actual = $this->handler->isAnimatedImage( $file );
+ $this->assertEquals( $expected, $actual );
+ }
+ public function dataIsAnimated() {
+ return array(
+ array( 'Animated_PNG_example_bouncing_beach_ball.png', true ),
+ array( '1bit-png.png', false ),
+ );
+ }
+
+ /**
+ * @param $filename String
+ * @param $expected Integer Total image area
+ * @dataProvider dataGetImageArea
+ */
+ public function testGetImageArea( $filename, $expected ) {
+ $file = UnregisteredLocalFile::newFromPath( $this->filePath . $filename,
+ 'image/png' );
+ $actual = $this->handler->getImageArea( $file, $file->getWidth(), $file->getHeight() );
+ $this->assertEquals( $expected, $actual );
+ }
+ public function dataGetImageArea() {
+ return array(
+ array( '1bit-png.png', 2500 ),
+ array( 'greyscale-png.png', 2500 ),
+ array( 'Png-native-test.png', 126000 ),
+ array( 'Animated_PNG_example_bouncing_beach_ball.png', 10000 ),
+ );
+ }
+
+ /**
+ * @param $metadata String Serialized metadata
+ * @param $expected Integer One of the class constants of PNGHandler
+ * @dataProvider dataIsMetadataValid
+ */
+ public function testIsMetadataValid( $metadata, $expected ) {
+ $actual = $this->handler->isMetadataValid( null, $metadata );
+ $this->assertEquals( $expected, $actual );
+ }
+ public function dataIsMetadataValid() {
+ return array(
+ array( PNGHandler::BROKEN_FILE, PNGHandler::METADATA_GOOD ),
+ array( '', PNGHandler::METADATA_BAD ),
+ array( null, PNGHandler::METADATA_BAD ),
+ array( 'Something invalid!', PNGHandler::METADATA_BAD ),
+ array( 'a:6:{s:10:"frameCount";i:0;s:9:"loopCount";i:1;s:8:"duration";d:0;s:8:"bitDepth";i:8;s:9:"colorType";s:10:"truecolour";s:8:"metadata";a:1:{s:15:"_MW_PNG_VERSION";i:1;}}', PNGHandler::METADATA_GOOD ),
+ );
+ }
+
+ /**
+ * @param $filename String
+ * @param $expected String Serialized array
+ * @dataProvider dataGetMetadata
+ */
+ public function testGetMetadata( $filename, $expected ) {
+ $file = UnregisteredLocalFile::newFromPath( $this->filePath . $filename,
+ 'image/png' );
+ $actual = $this->handler->getMetadata( $file, $this->filePath . $filename );
+// $this->assertEquals( unserialize( $expected ), unserialize( $actual ) );
+ $this->assertEquals( ( $expected ), ( $actual ) );
+ }
+ public function dataGetMetadata() {
+ return array(
+ array( 'rgb-na-png.png', 'a:6:{s:10:"frameCount";i:0;s:9:"loopCount";i:1;s:8:"duration";d:0;s:8:"bitDepth";i:8;s:9:"colorType";s:10:"truecolour";s:8:"metadata";a:1:{s:15:"_MW_PNG_VERSION";i:1;}}' ),
+ array( 'xmp.png', 'a:6:{s:10:"frameCount";i:0;s:9:"loopCount";i:1;s:8:"duration";d:0;s:8:"bitDepth";i:1;s:9:"colorType";s:14:"index-coloured";s:8:"metadata";a:2:{s:12:"SerialNumber";s:9:"123456789";s:15:"_MW_PNG_VERSION";i:1;}}' ),
+ );
+ }
+}
diff --git a/tests/phpunit/includes/media/SVGMetadataExtractorTest.php b/tests/phpunit/includes/media/SVGMetadataExtractorTest.php
new file mode 100644
index 00000000..c2c81b98
--- /dev/null
+++ b/tests/phpunit/includes/media/SVGMetadataExtractorTest.php
@@ -0,0 +1,88 @@
+<?php
+
+class SVGMetadataExtractorTest extends MediaWikiTestCase {
+
+ function setUp() {
+ AutoLoader::loadClass( 'SVGMetadataExtractorTest' );
+ }
+
+ /**
+ * @dataProvider providerSvgFiles
+ */
+ function testGetMetadata( $infile, $expected ) {
+ $this->assertMetadata( $infile, $expected );
+ }
+
+ /**
+ * @dataProvider providerSvgFilesWithXMLMetadata
+ */
+ function testGetXMLMetadata( $infile, $expected ) {
+ $r = new XMLReader();
+ if( !method_exists( $r, 'readInnerXML' ) ) {
+ $this->markTestSkipped( 'XMLReader::readInnerXML() does not exist (libxml >2.6.20 needed).' );
+ return;
+ }
+ $this->assertMetadata( $infile, $expected );
+ }
+
+ function assertMetadata( $infile, $expected ) {
+ try {
+ $data = SVGMetadataExtractor::getMetadata( $infile );
+ $this->assertEquals( $expected, $data, 'SVG metadata extraction test' );
+ } catch ( MWException $e ) {
+ if ( $expected === false ) {
+ $this->assertTrue( true, 'SVG metadata extracted test (expected failure)' );
+ } else {
+ throw $e;
+ }
+ }
+ }
+
+ function providerSvgFiles() {
+ $base = dirname( __FILE__ ) . '/../../data/media';
+ return array(
+ array(
+ "$base/Wikimedia-logo.svg",
+ array(
+ 'width' => 1024,
+ 'height' => 1024
+ )
+ ),
+ array(
+ "$base/QA_icon.svg",
+ array(
+ 'width' => 60,
+ 'height' => 60
+ )
+ ),
+ array(
+ "$base/Gtk-media-play-ltr.svg",
+ array(
+ 'width' => 60,
+ 'height' => 60
+ )
+ ),
+ );
+ }
+
+ function providerSvgFilesWithXMLMetadata() {
+ $base = dirname( __FILE__ ) . '/../../data/media';
+ return array(
+ array(
+ "$base/US_states_by_total_state_tax_revenue.svg",
+ array(
+ 'height' => 593,
+ 'metadata' =>
+ '<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
+ <ns4:Work xmlns:ns4="http://creativecommons.org/ns#" rdf:about="">
+ <ns5:format xmlns:ns5="http://purl.org/dc/elements/1.1/">image/svg+xml</ns5:format>
+ <ns5:type xmlns:ns5="http://purl.org/dc/elements/1.1/" rdf:resource="http://purl.org/dc/dcmitype/StillImage"/>
+ </ns4:Work>
+ </rdf:RDF>',
+ 'width' => 959
+ )
+ ),
+ );
+ }
+}
+
diff --git a/tests/phpunit/includes/media/TiffTest.php b/tests/phpunit/includes/media/TiffTest.php
new file mode 100644
index 00000000..0a7e8e8c
--- /dev/null
+++ b/tests/phpunit/includes/media/TiffTest.php
@@ -0,0 +1,36 @@
+<?php
+class TiffTest extends MediaWikiTestCase {
+
+ public function setUp() {
+ global $wgShowEXIF;
+ $this->showExif = $wgShowEXIF;
+ $wgShowEXIF = true;
+ $this->filePath = dirname( __FILE__ ) . '/../../data/media/';
+ $this->handler = new TiffHandler;
+ }
+
+ public function tearDown() {
+ global $wgShowEXIF;
+ $wgShowEXIF = $this->showExif;
+ }
+
+ public function testInvalidFile() {
+ global $wgShowEXIF;
+ if ( !$wgShowEXIF ) {
+ $this->markTestIncomplete( "This test needs the exif extension." );
+ }
+ $res = $this->handler->getMetadata( null, $this->filePath . 'README' );
+ $this->assertEquals( ExifBitmapHandler::BROKEN_FILE, $res );
+ }
+ public function testTiffMetadataExtraction() {
+ global $wgShowEXIF;
+ if ( !$wgShowEXIF ) {
+ $this->markTestIncomplete( "This test needs the exif extension." );
+ }
+ $res = $this->handler->getMetadata( null, $this->filePath . 'test.tiff' );
+ $expected = 'a:16:{s:10:"ImageWidth";i:20;s:11:"ImageLength";i:20;s:13:"BitsPerSample";a:3:{i:0;i:8;i:1;i:8;i:2;i:8;}s:11:"Compression";i:5;s:25:"PhotometricInterpretation";i:2;s:16:"ImageDescription";s:17:"Created with GIMP";s:12:"StripOffsets";i:8;s:11:"Orientation";i:1;s:15:"SamplesPerPixel";i:3;s:12:"RowsPerStrip";i:64;s:15:"StripByteCounts";i:238;s:11:"XResolution";s:19:"1207959552/16777216";s:11:"YResolution";s:19:"1207959552/16777216";s:19:"PlanarConfiguration";i:1;s:14:"ResolutionUnit";i:2;s:22:"MEDIAWIKI_EXIF_VERSION";i:2;}';
+ // Re-unserialize in case there are subtle differences between how versions
+ // of php serialize stuff.
+ $this->assertEquals( unserialize( $expected ), unserialize( $res ) );
+ }
+}
diff --git a/tests/phpunit/includes/media/XMPTest.php b/tests/phpunit/includes/media/XMPTest.php
new file mode 100644
index 00000000..d1ec4767
--- /dev/null
+++ b/tests/phpunit/includes/media/XMPTest.php
@@ -0,0 +1,154 @@
+<?php
+class XMPTest extends MediaWikiTestCase {
+
+ /**
+ * Put XMP in, compare what comes out...
+ *
+ * @param $xmp String the actual xml data.
+ * @param $expected Array expected result of parsing the xmp.
+ * @param $info String Short sentence on what's being tested.
+ *
+ * @dataProvider dataXMPParse
+ */
+ public function testXMPParse( $xmp, $expected, $info ) {
+ if ( !function_exists( 'xml_parser_create_ns' ) ) {
+ $this->markIncomplete( 'Requires libxml to do XMP parsing' );
+ }
+ if ( !is_string( $xmp ) || !is_array( $expected ) ) {
+ throw new Exception( "Invalid data provided to " . __METHOD__ );
+ }
+ $reader = new XMPReader;
+ $reader->parse( $xmp );
+ $this->assertEquals( $expected, $reader->getResults(), $info );
+ }
+
+ public function dataXMPParse() {
+ $xmpPath = dirname( __FILE__ ) . '/../../data/xmp/' ;
+ $data = array();
+
+ // $xmpFiles format: array of arrays with first arg file base name,
+ // with the actual file having .xmp on the end for the xmp
+ // and .result.php on the end for a php file containing the result
+ // array. Second argument is some info on what's being tested.
+ $xmpFiles = array(
+ array( '1', 'parseType=Resource test' ),
+ array( '2', 'Structure with mixed attribute and element props' ),
+ array( '3', 'Extra qualifiers (that should be ignored)' ),
+ array( '3-invalid', 'Test ignoring qualifiers that look like normal props' ),
+ array( '4', 'Flash as qualifier' ),
+ array( '5', 'Flash as qualifier 2' ),
+ array( '6', 'Multiple rdf:Description' ),
+ array( '7', 'Generic test of several property types' ),
+ array( 'flash', 'Test of Flash property' ),
+ array( 'invalid-child-not-struct', 'Test child props not in struct or ignored' ),
+ array( 'no-recognized-props', 'Test namespace and no recognized props' ),
+ array( 'no-namespace', 'Test non-namespaced attributes are ignored' ),
+ array( 'bag-for-seq', "Allow bag's instead of seq's. (bug 27105)" ),
+ array( 'utf16BE', 'UTF-16BE encoding' ),
+ array( 'utf16LE', 'UTF-16LE encoding' ),
+ array( 'utf32BE', 'UTF-32BE encoding' ),
+ array( 'utf32LE', 'UTF-32LE encoding' ),
+ array( 'xmpExt', 'Extended XMP missing second part' ),
+ );
+ foreach( $xmpFiles as $file ) {
+ $xmp = file_get_contents( $xmpPath . $file[0] . '.xmp' );
+ // I'm not sure if this is the best way to handle getting the
+ // result array, but it seems kind of big to put directly in the test
+ // file.
+ $result = null;
+ include( $xmpPath . $file[0] . '.result.php' );
+ $data[] = array( $xmp, $result, '[' . $file[0] . '.xmp] ' . $file[1] );
+ }
+ return $data;
+ }
+
+ /** Test ExtendedXMP block support. (Used when the XMP has to be split
+ * over multiple jpeg segments, due to 64k size limit on jpeg segments.
+ *
+ * @todo This is based on what the standard says. Need to find a real
+ * world example file to double check the support for this is right.
+ */
+ function testExtendedXMP() {
+ $xmpPath = dirname( __FILE__ ) . '/../../data/xmp/';
+ $standardXMP = file_get_contents( $xmpPath . 'xmpExt.xmp' );
+ $extendedXMP = file_get_contents( $xmpPath . 'xmpExt2.xmp' );
+
+ $md5sum = '28C74E0AC2D796886759006FBE2E57B7'; // of xmpExt2.xmp
+ $length = pack( 'N', strlen( $extendedXMP ) );
+ $offset = pack( 'N', 0 );
+ $extendedPacket = $md5sum . $length . $offset . $extendedXMP;
+
+ $reader = new XMPReader();
+ $reader->parse( $standardXMP );
+ $reader->parseExtended( $extendedPacket );
+ $actual = $reader->getResults();
+
+ $expected = array( 'xmp-exif' =>
+ array(
+ 'DigitalZoomRatio' => '0/10',
+ 'Flash' => 9,
+ 'FNumber' => '2/10',
+ )
+ );
+
+ $this->assertEquals( $expected, $actual );
+ }
+
+ /**
+ * This test has an extended XMP block with a wrong guid (md5sum)
+ * and thus should only return the StandardXMP, not the ExtendedXMP.
+ */
+ function testExtendedXMPWithWrongGUID() {
+ $xmpPath = dirname( __FILE__ ) . '/../../data/xmp/';
+ $standardXMP = file_get_contents( $xmpPath . 'xmpExt.xmp' );
+ $extendedXMP = file_get_contents( $xmpPath . 'xmpExt2.xmp' );
+
+ $md5sum = '28C74E0AC2D796886759006FBE2E57B9'; // Note last digit.
+ $length = pack( 'N', strlen( $extendedXMP ) );
+ $offset = pack( 'N', 0 );
+ $extendedPacket = $md5sum . $length . $offset . $extendedXMP;
+
+ $reader = new XMPReader();
+ $reader->parse( $standardXMP );
+ $reader->parseExtended( $extendedPacket );
+ $actual = $reader->getResults();
+
+ $expected = array( 'xmp-exif' =>
+ array(
+ 'DigitalZoomRatio' => '0/10',
+ 'Flash' => 9,
+ )
+ );
+
+ $this->assertEquals( $expected, $actual );
+ }
+ /**
+ * Have a high offset to simulate a missing packet,
+ * which should cause it to ignore the ExtendedXMP packet.
+ */
+ function testExtendedXMPMissingPacket() {
+ $xmpPath = dirname( __FILE__ ) . '/../../data/xmp/';
+ $standardXMP = file_get_contents( $xmpPath . 'xmpExt.xmp' );
+ $extendedXMP = file_get_contents( $xmpPath . 'xmpExt2.xmp' );
+
+ $md5sum = '28C74E0AC2D796886759006FBE2E57B7'; // of xmpExt2.xmp
+ $length = pack( 'N', strlen( $extendedXMP ) );
+ $offset = pack( 'N', 2048 );
+ $extendedPacket = $md5sum . $length . $offset . $extendedXMP;
+
+ $reader = new XMPReader();
+ $reader->parse( $standardXMP );
+ $reader->parseExtended( $extendedPacket );
+ $actual = $reader->getResults();
+
+ $expected = array( 'xmp-exif' =>
+ array(
+ 'DigitalZoomRatio' => '0/10',
+ 'Flash' => 9,
+ )
+ );
+
+ $this->assertEquals( $expected, $actual );
+ }
+
+}