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.php167
-rw-r--r--tests/phpunit/includes/media/BitmapScalingTest.php140
-rw-r--r--tests/phpunit/includes/media/DjVuTest.php69
-rw-r--r--tests/phpunit/includes/media/ExifBitmapTest.php146
-rw-r--r--tests/phpunit/includes/media/ExifRotationTest.php280
-rw-r--r--tests/phpunit/includes/media/ExifTest.php47
-rw-r--r--tests/phpunit/includes/media/FakeDimensionFile.php31
-rw-r--r--tests/phpunit/includes/media/FormatMetadataTest.php71
-rw-r--r--tests/phpunit/includes/media/GIFMetadataExtractorTest.php111
-rw-r--r--tests/phpunit/includes/media/GIFTest.php142
-rw-r--r--tests/phpunit/includes/media/IPTCTest.php85
-rw-r--r--tests/phpunit/includes/media/JpegMetadataExtractorTest.php111
-rw-r--r--tests/phpunit/includes/media/JpegTest.php54
-rw-r--r--tests/phpunit/includes/media/MediaHandlerTest.php56
-rw-r--r--tests/phpunit/includes/media/MediaWikiMediaTestCase.php86
-rw-r--r--tests/phpunit/includes/media/PNGMetadataExtractorTest.php155
-rw-r--r--tests/phpunit/includes/media/PNGTest.php131
-rw-r--r--tests/phpunit/includes/media/SVGMetadataExtractorTest.php160
-rw-r--r--tests/phpunit/includes/media/SVGTest.php41
-rw-r--r--tests/phpunit/includes/media/TiffTest.php45
-rw-r--r--tests/phpunit/includes/media/XCFTest.php78
-rw-r--r--tests/phpunit/includes/media/XMPTest.php223
-rw-r--r--tests/phpunit/includes/media/XMPValidateTest.php50
23 files changed, 2479 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..c720d7b7
--- /dev/null
+++ b/tests/phpunit/includes/media/BitmapMetadataHandlerTest.php
@@ -0,0 +1,167 @@
+<?php
+
+/**
+ * @group Media
+ */
+class BitmapMetadataHandlerTest extends MediaWikiTestCase {
+
+ protected function setUp() {
+ parent::setUp();
+
+ $this->setMwGlobals( 'wgShowEXIF', false );
+
+ $this->filePath = __DIR__ . '/../../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.
+ * @covers BitmapMetadataHandler::Jpeg
+ */
+ public function testMultilingualCascade() {
+ $this->checkPHPExtension( 'exif' );
+ $this->checkPHPExtension( 'xml' );
+
+ $this->setMwGlobals( 'wgShowEXIF', true );
+
+ $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
+ * @covers BitmapMetadataHandler::Jpeg
+ */
+ public function testJpegComment() {
+ $meta = BitmapMetadataHandler::Jpeg( $this->filePath .
+ 'jpeg-comment-utf.jpg' );
+
+ $this->assertEquals( 'UTF-8 JPEG Comment — ¼',
+ $meta['JPEGFileComment'][0] );
+ }
+
+ /**
+ * Make sure a bad iptc block doesn't stop the other metadata
+ * from being extracted.
+ * @covers BitmapMetadataHandler::Jpeg
+ */
+ public function testBadIPTC() {
+ $meta = BitmapMetadataHandler::Jpeg( $this->filePath .
+ 'iptc-invalid-psir.jpg' );
+ $this->assertEquals( 'Created with GIMP', $meta['JPEGFileComment'][0] );
+ }
+
+ /**
+ * @covers BitmapMetadataHandler::Jpeg
+ */
+ 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
+ * @covers BitmapMetadataHandler::Jpeg
+ */
+ 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.
+ * @covers BitmapMetadataHandler::addMetadata
+ * @covers BitmapMetadataHandler::getMetadataArray
+ */
+ 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 );
+ }
+
+ /**
+ * @covers BitmapMetadataHandler::png
+ */
+ public function testPNGXMP() {
+ if ( !extension_loaded( 'xml' ) ) {
+ $this->markTestSkipped( "This test needs the xml extension." );
+ }
+ $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 );
+ }
+
+ /**
+ * @covers BitmapMetadataHandler::png
+ */
+ 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'] );
+ }
+
+ /**
+ * @covers BitmapMetadataHandler::getTiffByteOrder
+ */
+ 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..1972c969
--- /dev/null
+++ b/tests/phpunit/includes/media/BitmapScalingTest.php
@@ -0,0 +1,140 @@
+<?php
+
+/**
+ * @group Media
+ */
+class BitmapScalingTest extends MediaWikiTestCase {
+
+ protected function setUp() {
+ parent::setUp();
+
+ $this->setMwGlobals( array(
+ 'wgMaxImageArea' => 1.25e7, // 3500x3500
+ 'wgCustomConvertCommand' => 'dummy', // Set so that we don't get client side rendering
+ ) );
+ }
+
+ /**
+ * @dataProvider provideNormaliseParams
+ * @covers BitmapHandler::normaliseParams
+ */
+ public 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 );
+ }
+
+ public static 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',
+ ),
+ );
+ }
+
+ /**
+ * @covers BitmapHandler::doTransform
+ */
+ public function testTooBigImage() {
+ $file = new FakeDimensionFile( array( 4000, 4000 ) );
+ $handler = new BitmapHandler;
+ $params = array( 'width' => '3700' ); // Still bigger than max size.
+ $this->assertEquals( 'TransformParameterError',
+ get_class( $handler->doTransform( $file, 'dummy path', '', $params ) ) );
+ }
+
+ /**
+ * @covers BitmapHandler::doTransform
+ */
+ public function testTooBigMustRenderImage() {
+ $file = new FakeDimensionFile( array( 4000, 4000 ) );
+ $file->mustRender = true;
+ $handler = new BitmapHandler;
+ $params = array( 'width' => '5000' ); // Still bigger than max size.
+ $this->assertEquals( 'TransformParameterError',
+ get_class( $handler->doTransform( $file, 'dummy path', '', $params ) ) );
+ }
+
+ /**
+ * @covers BitmapHandler::getImageArea
+ */
+ public function testImageArea() {
+ $file = new FakeDimensionFile( array( 7, 9 ) );
+ $handler = new BitmapHandler;
+ $this->assertEquals( 63, $handler->getImageArea( $file ) );
+ }
+}
diff --git a/tests/phpunit/includes/media/DjVuTest.php b/tests/phpunit/includes/media/DjVuTest.php
new file mode 100644
index 00000000..c0871f19
--- /dev/null
+++ b/tests/phpunit/includes/media/DjVuTest.php
@@ -0,0 +1,69 @@
+<?php
+/**
+ * @group Media
+ * @covers DjVuHandler
+ */
+class DjVuTest extends MediaWikiMediaTestCase {
+
+ /**
+ * @var DjVuHandler
+ */
+ protected $handler;
+
+ protected function setUp() {
+ parent::setUp();
+
+ //cli tool setup
+ $djvuSupport = new DjVuSupport();
+
+ if ( !$djvuSupport->isEnabled() ) {
+ $this->markTestSkipped(
+ 'This test needs the installation of the ddjvu, djvutoxml and djvudump tools' );
+ }
+
+ $this->handler = new DjVuHandler();
+ }
+
+ public function testGetImageSize() {
+ $this->assertArrayEquals(
+ array( 2480, 3508, 'DjVu', 'width="2480" height="3508"' ),
+ $this->handler->getImageSize( null, $this->filePath . '/LoremIpsum.djvu' ),
+ 'Test file LoremIpsum.djvu should have a size of 2480 * 3508'
+ );
+ }
+
+ public function testInvalidFile() {
+ $this->assertEquals(
+ 'a:1:{s:5:"error";s:25:"Error extracting metadata";}',
+ $this->handler->getMetadata( null, $this->filePath . '/some-nonexistent-file' ),
+ 'Getting metadata for an inexistent file should return false'
+ );
+ }
+
+ public function testPageCount() {
+ $file = $this->dataFile( 'LoremIpsum.djvu', 'image/x.djvu' );
+ $this->assertEquals(
+ 5,
+ $this->handler->pageCount( $file ),
+ 'Test file LoremIpsum.djvu should be detected as containing 5 pages'
+ );
+ }
+
+ public function testGetPageDimensions() {
+ $file = $this->dataFile( 'LoremIpsum.djvu', 'image/x.djvu' );
+ $this->assertArrayEquals(
+ array( 2480, 3508 ),
+ $this->handler->getPageDimensions( $file, 1 ),
+ 'Page 1 of test file LoremIpsum.djvu should have a size of 2480 * 3508'
+ );
+ }
+
+ public function testGetPageText() {
+ $file = $this->dataFile( 'LoremIpsum.djvu', 'image/x.djvu' );
+ $this->assertEquals(
+ "Lorem ipsum \n1 \n",
+ (string)$this->handler->getPageText( $file, 1 ),
+ "Text layer of page 1 of file LoremIpsum.djvu should be 'Lorem ipsum \n1 \n'"
+ );
+ }
+}
diff --git a/tests/phpunit/includes/media/ExifBitmapTest.php b/tests/phpunit/includes/media/ExifBitmapTest.php
new file mode 100644
index 00000000..41330f41
--- /dev/null
+++ b/tests/phpunit/includes/media/ExifBitmapTest.php
@@ -0,0 +1,146 @@
+<?php
+
+/**
+ * @group Media
+ */
+class ExifBitmapTest extends MediaWikiTestCase {
+
+ /**
+ * @var ExifBitmapHandler
+ */
+ protected $handler;
+
+ protected function setUp() {
+ parent::setUp();
+ $this->checkPHPExtension( 'exif' );
+
+ $this->setMwGlobals( 'wgShowEXIF', true );
+
+ $this->handler = new ExifBitmapHandler;
+
+ }
+
+ /**
+ * @covers ExifBitmapHandler::isMetadataValid
+ */
+ public function testIsOldBroken() {
+ $res = $this->handler->isMetadataValid( null, ExifBitmapHandler::OLD_BROKEN_FILE );
+ $this->assertEquals( ExifBitmapHandler::METADATA_COMPATIBLE, $res );
+ }
+
+ /**
+ * @covers ExifBitmapHandler::isMetadataValid
+ */
+ public function testIsBrokenFile() {
+ $res = $this->handler->isMetadataValid( null, ExifBitmapHandler::BROKEN_FILE );
+ $this->assertEquals( ExifBitmapHandler::METADATA_GOOD, $res );
+ }
+
+ /**
+ * @covers ExifBitmapHandler::isMetadataValid
+ */
+ public function testIsInvalid() {
+ $res = $this->handler->isMetadataValid( null, 'Something Invalid Here.' );
+ $this->assertEquals( ExifBitmapHandler::METADATA_BAD, $res );
+ }
+
+ /**
+ * @covers ExifBitmapHandler::isMetadataValid
+ */
+ public function testGoodMetadata() {
+ // @codingStandardsIgnoreStart Ignore Generic.Files.LineLength.TooLong
+ $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;}';
+ // @codingStandardsIgnoreEnd
+ $res = $this->handler->isMetadataValid( null, $meta );
+ $this->assertEquals( ExifBitmapHandler::METADATA_GOOD, $res );
+ }
+
+ /**
+ * @covers ExifBitmapHandler::isMetadataValid
+ */
+ public function testIsOldGood() {
+ // @codingStandardsIgnoreStart Ignore Generic.Files.LineLength.TooLong
+ $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;}';
+ // @codingStandardsIgnoreEnd
+ $res = $this->handler->isMetadataValid( null, $meta );
+ $this->assertEquals( ExifBitmapHandler::METADATA_COMPATIBLE, $res );
+ }
+
+ /**
+ * Handle metadata from paged tiff handler (gotten via instant commons) gracefully.
+ * @covers ExifBitmapHandler::isMetadataValid
+ */
+ public function testPagedTiffHandledGracefully() {
+ // @codingStandardsIgnoreStart Ignore Generic.Files.LineLength.TooLong
+ $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";}';
+ // @codingStandardsIgnoreEnd
+ $res = $this->handler->isMetadataValid( null, $meta );
+ $this->assertEquals( ExifBitmapHandler::METADATA_BAD, $res );
+ }
+
+ /**
+ * @covers ExifBitmapHandler::convertMetadataVersion
+ */
+ public function testConvertMetadataLatest() {
+ $metadata = array(
+ 'foo' => array( 'First', 'Second', '_type' => 'ol' ),
+ 'MEDIAWIKI_EXIF_VERSION' => 2
+ );
+ $res = $this->handler->convertMetadataVersion( $metadata, 2 );
+ $this->assertEquals( $metadata, $res );
+ }
+
+ /**
+ * @covers ExifBitmapHandler::convertMetadataVersion
+ */
+ public 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 );
+ }
+
+ /**
+ * @covers ExifBitmapHandler::convertMetadataVersion
+ */
+ public 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 );
+ }
+
+ /**
+ * @covers ExifBitmapHandler::convertMetadataVersion
+ */
+ public 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..f0bd42a0
--- /dev/null
+++ b/tests/phpunit/includes/media/ExifRotationTest.php
@@ -0,0 +1,280 @@
+<?php
+/**
+ * Tests related to auto rotation.
+ *
+ * @group Media
+ * @group medium
+ *
+ * @todo covers tags
+ */
+class ExifRotationTest extends MediaWikiMediaTestCase {
+
+ protected function setUp() {
+ parent::setUp();
+ $this->checkPHPExtension( 'exif' );
+
+ $this->handler = new BitmapHandler();
+
+ $this->setMwGlobals( array(
+ 'wgShowEXIF' => true,
+ 'wgEnableAutoRotation' => true,
+ ) );
+ }
+
+ /**
+ * Mark this test as creating thumbnail files.
+ */
+ protected function createsThumbnails() {
+ return true;
+ }
+
+ /**
+ * @dataProvider provideFiles
+ */
+ public function testMetadata( $name, $type, $info ) {
+ if ( !$this->handler->canRotate() ) {
+ $this->markTestSkipped( "This test needs a rasterizer that can auto-rotate." );
+ }
+ $file = $this->dataFile( $name, $type );
+ $this->assertEquals( $info['width'], $file->getWidth(), "$name: width check" );
+ $this->assertEquals( $info['height'], $file->getHeight(), "$name: height check" );
+ }
+
+ /**
+ * Same as before, but with auto-rotation set to auto.
+ *
+ * This sets scaler to image magick, which we should detect as
+ * supporting rotation.
+ * @dataProvider provideFiles
+ */
+ public function testMetadataAutoRotate( $name, $type, $info ) {
+ $this->setMwGlobals( 'wgEnableAutoRotation', null );
+ $this->setMwGlobals( 'wgUseImageMagick', true );
+ $this->setMwGlobals( 'wgUseImageResize', true );
+
+ $file = $this->dataFile( $name, $type );
+ $this->assertEquals( $info['width'], $file->getWidth(), "$name: width check" );
+ $this->assertEquals( $info['height'], $file->getHeight(), "$name: height check" );
+ }
+
+ /**
+ *
+ * @dataProvider provideFiles
+ */
+ public function testRotationRendering( $name, $type, $info, $thumbs ) {
+ if ( !$this->handler->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->dataFile( $name, $type );
+ $thumb = $file->transform( $params, File::RENDER_NOW | File::RENDER_FORCE );
+
+ $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->getLocalCopyPath() );
+ 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" );
+ }
+ }
+ }
+
+ public static function provideFiles() {
+ 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 provideFilesNoAutoRotate
+ */
+ public function testMetadataNoAutoRotate( $name, $type, $info ) {
+ $this->setMwGlobals( 'wgEnableAutoRotation', false );
+
+ $file = $this->dataFile( $name, $type );
+ $this->assertEquals( $info['width'], $file->getWidth(), "$name: width check" );
+ $this->assertEquals( $info['height'], $file->getHeight(), "$name: height check" );
+ }
+
+ /**
+ * Same as before, but with auto-rotation set to auto and an image scaler that doesn't support it.
+ * @dataProvider provideFilesNoAutoRotate
+ */
+ public function testMetadataAutoRotateUnsupported( $name, $type, $info ) {
+ $this->setMwGlobals( 'wgEnableAutoRotation', null );
+ $this->setMwGlobals( 'wgUseImageResize', false );
+
+ $file = $this->dataFile( $name, $type );
+ $this->assertEquals( $info['width'], $file->getWidth(), "$name: width check" );
+ $this->assertEquals( $info['height'], $file->getHeight(), "$name: height check" );
+ }
+
+ /**
+ *
+ * @dataProvider provideFilesNoAutoRotate
+ */
+ public function testRotationRenderingNoAutoRotate( $name, $type, $info, $thumbs ) {
+ $this->setMwGlobals( '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->dataFile( $name, $type );
+ $thumb = $file->transform( $params, File::RENDER_NOW | File::RENDER_FORCE );
+
+ $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->getLocalCopyPath() );
+ 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" );
+ }
+ }
+ }
+
+ public static function provideFilesNoAutoRotate() {
+ 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
+ */
+ public function testBitmapExtractPreRotationDimensions( $rotation, $expected ) {
+ $result = $this->handler->extractPreRotationDimensions( array(
+ 'physicalWidth' => self::TEST_WIDTH,
+ 'physicalHeight' => self::TEST_HEIGHT,
+ ), $rotation );
+ $this->assertEquals( $expected, $result );
+ }
+
+ public static 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..f3c05fb1
--- /dev/null
+++ b/tests/phpunit/includes/media/ExifTest.php
@@ -0,0 +1,47 @@
+<?php
+
+/**
+ * @group Media
+ * @covers Exif
+ */
+class ExifTest extends MediaWikiTestCase {
+
+ /** @var string */
+ protected $mediaPath;
+
+ protected function setUp() {
+ parent::setUp();
+ $this->checkPHPExtension( 'exif' );
+
+ $this->mediaPath = __DIR__ . '/../../data/media/';
+
+ $this->setMwGlobals( 'wgShowEXIF', true );
+ }
+
+ public function testGPSExtraction() {
+ $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' => -3.141592653,
+ 'GPSDOP' => '5/1',
+ 'GPSVersionID' => '2.2.0.0',
+ );
+ $this->assertEquals( $expected, $data, '', 0.0000000001 );
+ }
+
+ public function testUnicodeUserComment() {
+ $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/FakeDimensionFile.php b/tests/phpunit/includes/media/FakeDimensionFile.php
new file mode 100644
index 00000000..4b8f213e
--- /dev/null
+++ b/tests/phpunit/includes/media/FakeDimensionFile.php
@@ -0,0 +1,31 @@
+<?php
+
+/**
+ * @group Media
+ */
+class FakeDimensionFile extends File {
+ public $mustRender = false;
+
+ public function __construct( $dimensions ) {
+ parent::__construct( Title::makeTitle( NS_FILE, 'Test' ),
+ new NullRepo( 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;
+ }
+
+ public function getPath() {
+ return '';
+ }
+}
diff --git a/tests/phpunit/includes/media/FormatMetadataTest.php b/tests/phpunit/includes/media/FormatMetadataTest.php
new file mode 100644
index 00000000..002e2cb9
--- /dev/null
+++ b/tests/phpunit/includes/media/FormatMetadataTest.php
@@ -0,0 +1,71 @@
+<?php
+
+/**
+ * @group Media
+ */
+class FormatMetadataTest extends MediaWikiMediaTestCase {
+
+ protected function setUp() {
+ parent::setUp();
+
+ $this->checkPHPExtension( 'exif' );
+ $this->setMwGlobals( 'wgShowEXIF', true );
+ }
+
+ /**
+ * @covers File::formatMetadata
+ */
+ public function testInvalidDate() {
+ $file = $this->dataFile( '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)' );
+ }
+
+ /**
+ * @param string $filename
+ * @param int $expected Total image area
+ * @dataProvider provideFlattenArray
+ * @covers FormatMetadata::flattenArray
+ */
+ public function testFlattenArray( $vals, $type, $noHtml, $ctx, $expected ) {
+ $actual = FormatMetadata::flattenArray( $vals, $type, $noHtml, $ctx );
+ $this->assertEquals( $expected, $actual );
+ }
+
+ public static function provideFlattenArray() {
+ return array(
+ array(
+ array( 1, 2, 3 ), 'ul', false, false,
+ "<ul><li>1</li>\n<li>2</li>\n<li>3</li></ul>",
+ ),
+ array(
+ array( 1, 2, 3 ), 'ol', false, false,
+ "<ol><li>1</li>\n<li>2</li>\n<li>3</li></ol>",
+ ),
+ array(
+ array( 1, 2, 3 ), 'ul', true, false,
+ "\n*1\n*2\n*3",
+ ),
+ array(
+ array( 1, 2, 3 ), 'ol', true, false,
+ "\n#1\n#2\n#3",
+ ),
+ // TODO: more test cases
+ );
+ }
+}
diff --git a/tests/phpunit/includes/media/GIFMetadataExtractorTest.php b/tests/phpunit/includes/media/GIFMetadataExtractorTest.php
new file mode 100644
index 00000000..6aecd8b1
--- /dev/null
+++ b/tests/phpunit/includes/media/GIFMetadataExtractorTest.php
@@ -0,0 +1,111 @@
+<?php
+
+/**
+ * @group Media
+ */
+class GIFMetadataExtractorTest extends MediaWikiTestCase {
+
+ protected function setUp() {
+ parent::setUp();
+
+ $this->mediaPath = __DIR__ . '/../../data/media/';
+ }
+
+ /**
+ * Put in a file, and see if the metadata coming out is as expected.
+ * @param string $filename
+ * @param array $expected The extracted metadata.
+ * @dataProvider provideGetMetadata
+ * @covers GIFMetadataExtractor::getMetadata
+ */
+ public function testGetMetadata( $filename, $expected ) {
+ $actual = GIFMetadataExtractor::getMetadata( $this->mediaPath . $filename );
+ $this->assertEquals( $expected, $actual );
+ }
+
+ public static function provideGetMetadata() {
+
+ $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;
+ $xmpNugget = str_replace( "\r", '', $xmpNugget ); // Windows compat
+
+ 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..87ffd995
--- /dev/null
+++ b/tests/phpunit/includes/media/GIFTest.php
@@ -0,0 +1,142 @@
+<?php
+
+/**
+ * @group Media
+ */
+class GIFHandlerTest extends MediaWikiMediaTestCase {
+
+ /** @var GIFHandler */
+ protected $handler;
+
+ protected function setUp() {
+ parent::setUp();
+
+ $this->handler = new GIFHandler();
+ }
+
+ /**
+ * @covers GIFHandler::getMetadata
+ */
+ public function testInvalidFile() {
+ $res = $this->handler->getMetadata( null, $this->filePath . '/README' );
+ $this->assertEquals( GIFHandler::BROKEN_FILE, $res );
+ }
+
+ /**
+ * @param string $filename Basename of the file to check
+ * @param bool $expected Expected result.
+ * @dataProvider provideIsAnimated
+ * @covers GIFHandler::isAnimatedImage
+ */
+ public function testIsAnimanted( $filename, $expected ) {
+ $file = $this->dataFile( $filename, 'image/gif' );
+ $actual = $this->handler->isAnimatedImage( $file );
+ $this->assertEquals( $expected, $actual );
+ }
+
+ public static function provideIsAnimated() {
+ return array(
+ array( 'animated.gif', true ),
+ array( 'nonanimated.gif', false ),
+ );
+ }
+
+ /**
+ * @param string $filename
+ * @param int $expected Total image area
+ * @dataProvider provideGetImageArea
+ * @covers GIFHandler::getImageArea
+ */
+ public function testGetImageArea( $filename, $expected ) {
+ $file = $this->dataFile( $filename, 'image/gif' );
+ $actual = $this->handler->getImageArea( $file, $file->getWidth(), $file->getHeight() );
+ $this->assertEquals( $expected, $actual );
+ }
+
+ public static function provideGetImageArea() {
+ return array(
+ array( 'animated.gif', 5400 ),
+ array( 'nonanimated.gif', 1350 ),
+ );
+ }
+
+ /**
+ * @param string $metadata Serialized metadata
+ * @param int $expected One of the class constants of GIFHandler
+ * @dataProvider provideIsMetadataValid
+ * @covers GIFHandler::isMetadataValid
+ */
+ public function testIsMetadataValid( $metadata, $expected ) {
+ $actual = $this->handler->isMetadataValid( null, $metadata );
+ $this->assertEquals( $expected, $actual );
+ }
+
+ public static function provideIsMetadataValid() {
+ return array(
+ array( GIFHandler::BROKEN_FILE, GIFHandler::METADATA_GOOD ),
+ array( '', GIFHandler::METADATA_BAD ),
+ array( null, GIFHandler::METADATA_BAD ),
+ array( 'Something invalid!', GIFHandler::METADATA_BAD ),
+ // @codingStandardsIgnoreStart Ignore Generic.Files.LineLength.TooLong
+ 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 ),
+ // @codingStandardsIgnoreEnd
+ );
+ }
+
+ /**
+ * @param string $filename
+ * @param string $expected Serialized array
+ * @dataProvider provideGetMetadata
+ * @covers GIFHandler::getMetadata
+ */
+ public function testGetMetadata( $filename, $expected ) {
+ $file = $this->dataFile( $filename, 'image/gif' );
+ $actual = $this->handler->getMetadata( $file, "$this->filePath/$filename" );
+ $this->assertEquals( unserialize( $expected ), unserialize( $actual ) );
+ }
+
+ public static function provideGetMetadata() {
+ return array(
+ // @codingStandardsIgnoreStart Ignore Generic.Files.LineLength.TooLong
+ 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;}}' ),
+ // @codingStandardsIgnoreEnd
+ );
+ }
+
+ /**
+ * @param string $filename
+ * @param string $expected Serialized array
+ * @dataProvider provideGetIndependentMetaArray
+ * @covers GIFHandler::getCommonMetaArray
+ */
+ public function testGetIndependentMetaArray( $filename, $expected ) {
+ $file = $this->dataFile( $filename, 'image/gif' );
+ $actual = $this->handler->getCommonMetaArray( $file );
+ $this->assertEquals( $expected, $actual );
+ }
+
+ public static function provideGetIndependentMetaArray() {
+ return array(
+ array( 'nonanimated.gif', array(
+ 'GIFFileComment' => array(
+ 'GIF test file ⁕ Created with GIMP',
+ ),
+ ) ),
+ array( 'animated-xmp.gif',
+ array(
+ 'Artist' => 'Bawolff',
+ 'ImageDescription' => array(
+ 'x-default' => 'A file to test GIF',
+ '_type' => 'lang',
+ ),
+ 'SublocationDest' => 'The interwebs',
+ 'GIFFileComment' =>
+ array(
+ 'GIƒ·test·file',
+ ),
+ )
+ ),
+ );
+ }
+}
diff --git a/tests/phpunit/includes/media/IPTCTest.php b/tests/phpunit/includes/media/IPTCTest.php
new file mode 100644
index 00000000..06542cfe
--- /dev/null
+++ b/tests/phpunit/includes/media/IPTCTest.php
@@ -0,0 +1,85 @@
+<?php
+
+/**
+ * @group Media
+ */
+class IPTCTest extends MediaWikiTestCase {
+
+ /**
+ * @covers IPTC::getCharset
+ */
+ public function testRecognizeUtf8() {
+ // utf-8 is the only one used in practise.
+ $res = IPTC::getCharset( "\x1b%G" );
+ $this->assertEquals( 'UTF-8', $res );
+ }
+
+ /**
+ * @covers IPTC::Parse
+ */
+ 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'] );
+ }
+
+ /**
+ * @covers IPTC::Parse
+ */
+ public function testIPTCParseNoCharset88591b() {
+ /* This one contains a sequence that's valid iso 8859-1 but not valid utf8 */
+ /* \xC3 = Ã, \xB8 = ¸ */
+ $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 ø
+ * @covers IPTC::Parse
+ */
+ 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'] );
+ }
+
+ /**
+ * @covers IPTC::Parse
+ */
+ 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
+ * @covers IPTC::Parse
+ */
+ 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'] );
+ }
+
+ /**
+ * @covers IPTC::Parse
+ */
+ 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..7c977d5a
--- /dev/null
+++ b/tests/phpunit/includes/media/JpegMetadataExtractorTest.php
@@ -0,0 +1,111 @@
+<?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.
+ *
+ * @group Media
+ * @covers JpegMetadataExtractor
+ */
+class JpegMetadataExtractorTest extends MediaWikiTestCase {
+
+ protected $filePath;
+
+ protected function setUp() {
+ parent::setUp();
+
+ $this->filePath = __DIR__ . '/../../data/media/';
+ }
+
+ /**
+ * We also use this test to test padding bytes don't
+ * screw stuff up
+ *
+ * @param string $file Filename
+ *
+ * @dataProvider provideUtf8Comment
+ */
+ public function testUtf8Comment( $file ) {
+ $res = JpegMetadataExtractor::segmentSplitter( $this->filePath . $file );
+ $this->assertEquals( array( 'UTF-8 JPEG Comment — ¼' ), $res['COM'] );
+ }
+
+ public static function provideUtf8Comment() {
+ 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 = '50686f746f73686f7020332e30003842494d04040000000'
+ . '000181c02190004746573741c02190003666f6f1c020000020004';
+ $this->assertEquals( $expected, bin2hex( $res['PSIR'][0] ) );
+ }
+
+ 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'][0] );
+
+ $this->assertEquals( 'iptc-no-hash', $res );
+ }
+
+ public function testIPTCHashComparisionBadHash() {
+ $segments = JpegMetadataExtractor::segmentSplitter( $this->filePath . 'jpeg-iptc-bad-hash.jpg' );
+ $res = JpegMetadataExtractor::doPSIR( $segments['PSIR'][0] );
+
+ $this->assertEquals( 'iptc-bad-hash', $res );
+ }
+
+ public function testIPTCHashComparisionGoodHash() {
+ $segments = JpegMetadataExtractor::segmentSplitter( $this->filePath . 'jpeg-iptc-good-hash.jpg' );
+ $res = JpegMetadataExtractor::doPSIR( $segments['PSIR'][0] );
+
+ $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..2436e7d9
--- /dev/null
+++ b/tests/phpunit/includes/media/JpegTest.php
@@ -0,0 +1,54 @@
+<?php
+
+/**
+ * @group Media
+ * @covers JpegHandler
+ */
+class JpegTest extends MediaWikiMediaTestCase {
+
+ protected function setUp() {
+ parent::setUp();
+ $this->checkPHPExtension( 'exif' );
+
+ $this->setMwGlobals( 'wgShowEXIF', true );
+
+ $this->handler = new JpegHandler;
+ }
+
+ public function testInvalidFile() {
+ $file = $this->dataFile( 'README', 'image/jpeg' );
+ $res = $this->handler->getMetadata( $file, $this->filePath . 'README' );
+ $this->assertEquals( ExifBitmapHandler::BROKEN_FILE, $res );
+ }
+
+ public function testJpegMetadataExtraction() {
+ $file = $this->dataFile( 'test.jpg', 'image/jpeg' );
+ $res = $this->handler->getMetadata( $file, $this->filePath . 'test.jpg' );
+ // @codingStandardsIgnoreStart Ignore Generic.Files.LineLength.TooLong
+ $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;}';
+ // @codingStandardsIgnoreEnd
+
+ // Unserialize in case serialization format ever changes.
+ $this->assertEquals( unserialize( $expected ), unserialize( $res ) );
+ }
+
+ /**
+ * @covers JpegHandler::getCommonMetaArray
+ */
+ public function testGetIndependentMetaArray() {
+ $file = $this->dataFile( 'test.jpg', 'image/jpeg' );
+ $res = $this->handler->getCommonMetaArray( $file );
+ $expected = array(
+ 'ImageDescription' => 'Test file',
+ 'XResolution' => '72/1',
+ 'YResolution' => '72/1',
+ 'ResolutionUnit' => 2,
+ 'YCbCrPositioning' => 1,
+ 'JPEGFileComment' => array(
+ 'Created with GIMP',
+ ),
+ );
+
+ $this->assertEquals( $res, $expected );
+ }
+}
diff --git a/tests/phpunit/includes/media/MediaHandlerTest.php b/tests/phpunit/includes/media/MediaHandlerTest.php
new file mode 100644
index 00000000..d8cfcc45
--- /dev/null
+++ b/tests/phpunit/includes/media/MediaHandlerTest.php
@@ -0,0 +1,56 @@
+<?php
+
+/**
+ * @group Media
+ */
+class MediaHandlerTest extends MediaWikiTestCase {
+
+ /**
+ * @covers MediaHandler::fitBoxWidth
+ * @todo split into a dataprovider and test method
+ */
+ public function testFitBoxWidth() {
+ $vals = array(
+ array(
+ 'width' => 50,
+ 'height' => 50,
+ 'tests' => array(
+ 50 => 50,
+ 17 => 17,
+ 18 => 18 ) ),
+ array(
+ 'width' => 366,
+ 'height' => 300,
+ 'tests' => array(
+ 50 => 61,
+ 17 => 21,
+ 18 => 22 ) ),
+ array(
+ 'width' => 300,
+ 'height' => 366,
+ 'tests' => array(
+ 50 => 41,
+ 17 => 14,
+ 18 => 15 ) ),
+ array(
+ 'width' => 100,
+ 'height' => 400,
+ 'tests' => array(
+ 50 => 12,
+ 17 => 4,
+ 18 => 4 ) ) );
+ foreach ( $vals as $row ) {
+ $tests = $row['tests'];
+ $height = $row['height'];
+ $width = $row['width'];
+ foreach ( $tests as $max => $expected ) {
+ $y = round( $expected * $height / $width );
+ $result = MediaHandler::fitBoxWidth( $width, $height, $max );
+ $y2 = round( $result * $height / $width );
+ $this->assertEquals( $expected,
+ $result,
+ "($width, $height, $max) wanted: {$expected}x$y, got: {$result}x$y2" );
+ }
+ }
+ }
+}
diff --git a/tests/phpunit/includes/media/MediaWikiMediaTestCase.php b/tests/phpunit/includes/media/MediaWikiMediaTestCase.php
new file mode 100644
index 00000000..8f28158d
--- /dev/null
+++ b/tests/phpunit/includes/media/MediaWikiMediaTestCase.php
@@ -0,0 +1,86 @@
+<?php
+/**
+ * Specificly for testing Media handlers. Sets up a FSFile backend
+ */
+abstract class MediaWikiMediaTestCase extends MediaWikiTestCase {
+
+ /** @var FSRepo */
+ protected $repo;
+ /** @var FSFileBackend */
+ protected $backend;
+ /** @var string */
+ protected $filePath;
+
+
+ protected function setUp() {
+ parent::setUp();
+
+ $this->filePath = $this->getFilePath();
+ $containers = array( 'data' => $this->filePath );
+ if ( $this->createsThumbnails() ) {
+ // We need a temp directory for the thumbnails
+ // the container is named 'temp-thumb' because it is the
+ // thumb directory for a FSRepo named "temp".
+ $containers['temp-thumb'] = $this->getNewTempDirectory();
+ }
+
+ $this->backend = new FSFileBackend( array(
+ 'name' => 'localtesting',
+ 'wikiId' => wfWikiId(),
+ 'containerPaths' => $containers
+ ) );
+ $this->repo = new FSRepo( $this->getRepoOptions() );
+ }
+
+ /**
+ * @return array Argument for FSRepo constructor
+ */
+ protected function getRepoOptions() {
+ return array(
+ 'name' => 'temp',
+ 'url' => 'http://localhost/thumbtest',
+ 'backend' => $this->backend
+ );
+ }
+
+ /**
+ * The result of this method will set the file path to use,
+ * as well as the protected member $filePath
+ *
+ * @return string Path where files are
+ */
+ protected function getFilePath() {
+ return __DIR__ . '/../../data/media/';
+ }
+
+ /**
+ * Will the test create thumbnails (and thus do we need to set aside
+ * a temporary directory for them?)
+ *
+ * Override this method if your test case creates thumbnails
+ *
+ * @return bool
+ */
+ protected function createsThumbnails() {
+ return false;
+ }
+
+ /**
+ * Utility function: Get a new file object for a file on disk but not actually in db.
+ *
+ * File must be in the path returned by getFilePath()
+ * @param string $name File name
+ * @param string $type MIME type [optional]
+ * @return UnregisteredLocalFile
+ */
+ protected function dataFile( $name, $type = null ) {
+ if ( !$type ) {
+ // Autodetect by file extension for the lazy.
+ $magic = MimeMagic::singleton();
+ $parts = explode( $name, '.' );
+ $type = $magic->guessTypesForExtension( $parts[count( $parts ) - 1] );
+ }
+ return new UnregisteredLocalFile( false, $this->repo,
+ "mwstore://localtesting/data/$name", $type );
+ }
+}
diff --git a/tests/phpunit/includes/media/PNGMetadataExtractorTest.php b/tests/phpunit/includes/media/PNGMetadataExtractorTest.php
new file mode 100644
index 00000000..a9eaa9e7
--- /dev/null
+++ b/tests/phpunit/includes/media/PNGMetadataExtractorTest.php
@@ -0,0 +1,155 @@
+<?php
+
+/**
+ * @group Media
+ * @covers PNGMetadataExtractor
+ */
+class PNGMetadataExtractorTest extends MediaWikiTestCase {
+
+ protected function setUp() {
+ parent::setUp();
+ $this->filePath = __DIR__ . '/../../data/media/';
+ }
+
+ /**
+ * Tests zTXt tag (compressed textual metadata)
+ */
+ public function testPngNativetZtxt() {
+ $this->checkPHPExtension( 'zlib' );
+
+ $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)
+ */
+ public 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
+ */
+ public 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).
+ */
+ /*
+ public 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.
+ */
+ public 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.
+ */
+ public 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 );
+ }
+
+ public function testPngBitDepth8() {
+ $meta = PNGMetadataExtractor::getMetadata( $this->filePath .
+ 'Png-native-test.png' );
+
+ $this->assertEquals( 8, $meta['bitDepth'] );
+ }
+
+ public function testPngBitDepth1() {
+ $meta = PNGMetadataExtractor::getMetadata( $this->filePath .
+ '1bit-png.png' );
+ $this->assertEquals( 1, $meta['bitDepth'] );
+ }
+
+ public function testPngIndexColour() {
+ $meta = PNGMetadataExtractor::getMetadata( $this->filePath .
+ 'Png-native-test.png' );
+
+ $this->assertEquals( 'index-coloured', $meta['colorType'] );
+ }
+
+ public function testPngRgbColour() {
+ $meta = PNGMetadataExtractor::getMetadata( $this->filePath .
+ 'rgb-png.png' );
+ $this->assertEquals( 'truecolour-alpha', $meta['colorType'] );
+ }
+
+ public function testPngRgbNoAlphaColour() {
+ $meta = PNGMetadataExtractor::getMetadata( $this->filePath .
+ 'rgb-na-png.png' );
+ $this->assertEquals( 'truecolour', $meta['colorType'] );
+ }
+
+ public function testPngGreyscaleColour() {
+ $meta = PNGMetadataExtractor::getMetadata( $this->filePath .
+ 'greyscale-png.png' );
+ $this->assertEquals( 'greyscale-alpha', $meta['colorType'] );
+ }
+
+ public 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..36872a75
--- /dev/null
+++ b/tests/phpunit/includes/media/PNGTest.php
@@ -0,0 +1,131 @@
+<?php
+
+/**
+ * @group Media
+ */
+class PNGHandlerTest extends MediaWikiMediaTestCase {
+
+ /** @var PNGHandler */
+ protected $handler;
+
+ protected function setUp() {
+ parent::setUp();
+ $this->handler = new PNGHandler();
+ }
+
+ /**
+ * @covers PNGHandler::getMetadata
+ */
+ public function testInvalidFile() {
+ $res = $this->handler->getMetadata( null, $this->filePath . '/README' );
+ $this->assertEquals( PNGHandler::BROKEN_FILE, $res );
+ }
+
+ /**
+ * @param string $filename Basename of the file to check
+ * @param bool $expected Expected result.
+ * @dataProvider provideIsAnimated
+ * @covers PNGHandler::isAnimatedImage
+ */
+ public function testIsAnimanted( $filename, $expected ) {
+ $file = $this->dataFile( $filename, 'image/png' );
+ $actual = $this->handler->isAnimatedImage( $file );
+ $this->assertEquals( $expected, $actual );
+ }
+
+ public static function provideIsAnimated() {
+ return array(
+ array( 'Animated_PNG_example_bouncing_beach_ball.png', true ),
+ array( '1bit-png.png', false ),
+ );
+ }
+
+ /**
+ * @param string $filename
+ * @param int $expected Total image area
+ * @dataProvider provideGetImageArea
+ * @covers PNGHandler::getImageArea
+ */
+ public function testGetImageArea( $filename, $expected ) {
+ $file = $this->dataFile( $filename, 'image/png' );
+ $actual = $this->handler->getImageArea( $file, $file->getWidth(), $file->getHeight() );
+ $this->assertEquals( $expected, $actual );
+ }
+
+ public static function provideGetImageArea() {
+ 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 string $metadata Serialized metadata
+ * @param int $expected One of the class constants of PNGHandler
+ * @dataProvider provideIsMetadataValid
+ * @covers PNGHandler::isMetadataValid
+ */
+ public function testIsMetadataValid( $metadata, $expected ) {
+ $actual = $this->handler->isMetadataValid( null, $metadata );
+ $this->assertEquals( $expected, $actual );
+ }
+
+ public static function provideIsMetadataValid() {
+ return array(
+ array( PNGHandler::BROKEN_FILE, PNGHandler::METADATA_GOOD ),
+ array( '', PNGHandler::METADATA_BAD ),
+ array( null, PNGHandler::METADATA_BAD ),
+ array( 'Something invalid!', PNGHandler::METADATA_BAD ),
+ // @codingStandardsIgnoreStart Ignore Generic.Files.LineLength.TooLong
+ 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 ),
+ // @codingStandardsIgnoreEnd
+ );
+ }
+
+ /**
+ * @param string $filename
+ * @param string $expected Serialized array
+ * @dataProvider provideGetMetadata
+ * @covers PNGHandler::getMetadata
+ */
+ public function testGetMetadata( $filename, $expected ) {
+ $file = $this->dataFile( $filename, 'image/png' );
+ $actual = $this->handler->getMetadata( $file, "$this->filePath/$filename" );
+// $this->assertEquals( unserialize( $expected ), unserialize( $actual ) );
+ $this->assertEquals( ( $expected ), ( $actual ) );
+ }
+
+ public static function provideGetMetadata() {
+ return array(
+ // @codingStandardsIgnoreStart Ignore Generic.Files.LineLength.TooLong
+ 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;}}' ),
+ // @codingStandardsIgnoreEnd
+ );
+ }
+
+ /**
+ * @param string $filename
+ * @param array $expected Expected standard metadata
+ * @dataProvider provideGetIndependentMetaArray
+ * @covers PNGHandler::getCommonMetaArray
+ */
+ public function testGetIndependentMetaArray( $filename, $expected ) {
+ $file = $this->dataFile( $filename, 'image/png' );
+ $actual = $this->handler->getCommonMetaArray( $file );
+ $this->assertEquals( $expected, $actual );
+ }
+
+ public static function provideGetIndependentMetaArray() {
+ return array(
+ array( 'rgb-na-png.png', array() ),
+ array( 'xmp.png',
+ array(
+ 'SerialNumber' => '123456789',
+ )
+ ),
+ );
+ }
+}
diff --git a/tests/phpunit/includes/media/SVGMetadataExtractorTest.php b/tests/phpunit/includes/media/SVGMetadataExtractorTest.php
new file mode 100644
index 00000000..ab33d1c2
--- /dev/null
+++ b/tests/phpunit/includes/media/SVGMetadataExtractorTest.php
@@ -0,0 +1,160 @@
+<?php
+
+/**
+ * @group Media
+ * @covers SVGMetadataExtractor
+ */
+class SVGMetadataExtractorTest extends MediaWikiTestCase {
+
+ protected function setUp() {
+ parent::setUp();
+ AutoLoader::loadClass( 'SVGMetadataExtractorTest' );
+ }
+
+ /**
+ * @dataProvider provideSvgFiles
+ */
+ public function testGetMetadata( $infile, $expected ) {
+ $this->assertMetadata( $infile, $expected );
+ }
+
+ /**
+ * @dataProvider provideSvgFilesWithXMLMetadata
+ */
+ public 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;
+ }
+ }
+ }
+
+ public static function provideSvgFiles() {
+ $base = __DIR__ . '/../../data/media';
+
+ return array(
+ array(
+ "$base/Wikimedia-logo.svg",
+ array(
+ 'width' => 1024,
+ 'height' => 1024,
+ 'originalWidth' => '1024',
+ 'originalHeight' => '1024',
+ 'translations' => array(),
+ )
+ ),
+ array(
+ "$base/QA_icon.svg",
+ array(
+ 'width' => 60,
+ 'height' => 60,
+ 'originalWidth' => '60',
+ 'originalHeight' => '60',
+ 'translations' => array(),
+ )
+ ),
+ array(
+ "$base/Gtk-media-play-ltr.svg",
+ array(
+ 'width' => 60,
+ 'height' => 60,
+ 'originalWidth' => '60.0000000',
+ 'originalHeight' => '60.0000000',
+ 'translations' => array(),
+ )
+ ),
+ array(
+ "$base/Toll_Texas_1.svg",
+ // This file triggered bug 31719, needs entity expansion in the xmlns checks
+ array(
+ 'width' => 385,
+ 'height' => 385,
+ 'originalWidth' => '385',
+ 'originalHeight' => '385.0004883',
+ 'translations' => array(),
+ )
+ ),
+ array(
+ "$base/Tux.svg",
+ array(
+ 'width' => 512,
+ 'height' => 594,
+ 'originalWidth' => '100%',
+ 'originalHeight' => '100%',
+ 'title' => 'Tux',
+ 'translations' => array(),
+ 'description' => 'For more information see: http://commons.wikimedia.org/wiki/Image:Tux.svg',
+ )
+ ),
+ array(
+ "$base/Speech_bubbles.svg",
+ array(
+ 'width' => 627,
+ 'height' => 461,
+ 'originalWidth' => '17.7cm',
+ 'originalHeight' => '13cm',
+ 'translations' => array(
+ 'de' => SVGReader::LANG_FULL_MATCH,
+ 'fr' => SVGReader::LANG_FULL_MATCH,
+ 'nl' => SVGReader::LANG_FULL_MATCH,
+ 'tlh-ca' => SVGReader::LANG_FULL_MATCH,
+ 'tlh' => SVGReader::LANG_PREFIX_MATCH
+ ),
+ )
+ ),
+ array(
+ "$base/Soccer_ball_animated.svg",
+ array(
+ 'width' => 150,
+ 'height' => 150,
+ 'originalWidth' => '150',
+ 'originalHeight' => '150',
+ 'animated' => true,
+ 'translations' => array()
+ ),
+ ),
+ );
+ }
+
+ public static function provideSvgFilesWithXMLMetadata() {
+ $base = __DIR__ . '/../../data/media';
+ // @codingStandardsIgnoreStart Ignore Generic.Files.LineLength.TooLong
+ $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>';
+ // @codingStandardsIgnoreEnd
+
+ $metadata = str_replace( "\r", '', $metadata ); // Windows compat
+ return array(
+ array(
+ "$base/US_states_by_total_state_tax_revenue.svg",
+ array(
+ 'height' => 593,
+ 'metadata' => $metadata,
+ 'width' => 959,
+ 'originalWidth' => '958.69',
+ 'originalHeight' => '592.78998',
+ 'translations' => array(),
+ )
+ ),
+ );
+ }
+}
diff --git a/tests/phpunit/includes/media/SVGTest.php b/tests/phpunit/includes/media/SVGTest.php
new file mode 100644
index 00000000..8f7a0d69
--- /dev/null
+++ b/tests/phpunit/includes/media/SVGTest.php
@@ -0,0 +1,41 @@
+<?php
+
+/**
+ * @group Media
+ */
+class SvgTest extends MediaWikiMediaTestCase {
+
+ protected function setUp() {
+ parent::setUp();
+
+ $this->filePath = __DIR__ . '/../../data/media/';
+
+ $this->setMwGlobals( 'wgShowEXIF', true );
+
+ $this->handler = new SvgHandler;
+ }
+
+ /**
+ * @param string $filename
+ * @param array $expected The expected independent metadata
+ * @dataProvider providerGetIndependentMetaArray
+ * @covers SvgHandler::getCommonMetaArray
+ */
+ public function testGetIndependentMetaArray( $filename, $expected ) {
+ $file = $this->dataFile( $filename, 'image/svg+xml' );
+ $res = $this->handler->getCommonMetaArray( $file );
+
+ $this->assertEquals( $res, $expected );
+ }
+
+ public static function providerGetIndependentMetaArray() {
+ return array(
+ array( 'Tux.svg', array(
+ 'ObjectName' => 'Tux',
+ 'ImageDescription' =>
+ 'For more information see: http://commons.wikimedia.org/wiki/Image:Tux.svg',
+ ) ),
+ array( 'Wikimedia-logo.svg', array() )
+ );
+ }
+}
diff --git a/tests/phpunit/includes/media/TiffTest.php b/tests/phpunit/includes/media/TiffTest.php
new file mode 100644
index 00000000..d1148202
--- /dev/null
+++ b/tests/phpunit/includes/media/TiffTest.php
@@ -0,0 +1,45 @@
+<?php
+
+/**
+ * @group Media
+ */
+class TiffTest extends MediaWikiTestCase {
+
+ /** @var TiffHandler */
+ protected $handler;
+ /** @var string */
+ protected $filePath;
+
+ protected function setUp() {
+ parent::setUp();
+ $this->checkPHPExtension( 'exif' );
+
+ $this->setMwGlobals( 'wgShowEXIF', true );
+
+ $this->filePath = __DIR__ . '/../../data/media/';
+ $this->handler = new TiffHandler;
+ }
+
+ /**
+ * @covers TiffHandler::getMetadata
+ */
+ public function testInvalidFile() {
+ $res = $this->handler->getMetadata( null, $this->filePath . 'README' );
+ $this->assertEquals( ExifBitmapHandler::BROKEN_FILE, $res );
+ }
+
+ /**
+ * @covers TiffHandler::getMetadata
+ */
+ public function testTiffMetadataExtraction() {
+ $res = $this->handler->getMetadata( null, $this->filePath . 'test.tiff' );
+
+ // @codingStandardsIgnoreStart Ignore Generic.Files.LineLength.TooLong
+ $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;}';
+ // @codingStandardsIgnoreEnd
+
+ // 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/XCFTest.php b/tests/phpunit/includes/media/XCFTest.php
new file mode 100644
index 00000000..5b2de151
--- /dev/null
+++ b/tests/phpunit/includes/media/XCFTest.php
@@ -0,0 +1,78 @@
+<?php
+
+/**
+ * @group Media
+ */
+class XCFHandlerTest extends MediaWikiMediaTestCase {
+
+ /** @var XCFHandler */
+ protected $handler;
+
+ protected function setUp() {
+ parent::setUp();
+ $this->handler = new XCFHandler();
+ }
+
+
+ /**
+ * @param string $filename
+ * @param int $expectedWidth Width
+ * @param int $expectedHeight Height
+ * @dataProvider provideGetImageSize
+ * @covers XCFHandler::getImageSize
+ */
+ public function testGetImageSize( $filename, $expectedWidth, $expectedHeight ) {
+ $file = $this->dataFile( $filename, 'image/x-xcf' );
+ $actual = $this->handler->getImageSize( $file, $file->getLocalRefPath() );
+ $this->assertEquals( $expectedWidth, $actual[0] );
+ $this->assertEquals( $expectedHeight, $actual[1] );
+ }
+
+ public static function provideGetImageSize() {
+ return array(
+ array( '80x60-2layers.xcf', 80, 60 ),
+ array( '80x60-RGB.xcf', 80, 60 ),
+ array( '80x60-Greyscale.xcf', 80, 60 ),
+ );
+ }
+
+ /**
+ * @param string $metadata Serialized metadata
+ * @param int $expected One of the class constants of XCFHandler
+ * @dataProvider provideIsMetadataValid
+ * @covers XCFHandler::isMetadataValid
+ */
+ public function testIsMetadataValid( $metadata, $expected ) {
+ $actual = $this->handler->isMetadataValid( null, $metadata );
+ $this->assertEquals( $expected, $actual );
+ }
+
+ public static function provideIsMetadataValid() {
+ return array(
+ array( '', XCFHandler::METADATA_BAD ),
+ array( serialize( array( 'error' => true ) ), XCFHandler::METADATA_GOOD ),
+ array( false, XCFHandler::METADATA_BAD ),
+ array( serialize( array( 'colorType' => 'greyscale-alpha' ) ), XCFHandler::METADATA_GOOD ),
+ );
+ }
+
+ /**
+ * @param string $filename
+ * @param string $expected Serialized array
+ * @dataProvider provideGetMetadata
+ * @covers XCFHandler::getMetadata
+ */
+ public function testGetMetadata( $filename, $expected ) {
+ $file = $this->dataFile( $filename, 'image/png' );
+ $actual = $this->handler->getMetadata( $file, "$this->filePath/$filename" );
+ $this->assertEquals( $expected, $actual );
+ }
+
+ public static function provideGetMetadata() {
+ return array(
+ array( '80x60-2layers.xcf', 'a:1:{s:9:"colorType";s:16:"truecolour-alpha";}' ),
+ array( '80x60-RGB.xcf', 'a:1:{s:9:"colorType";s:16:"truecolour-alpha";}' ),
+ array( '80x60-Greyscale.xcf', 'a:1:{s:9:"colorType";s:15:"greyscale-alpha";}' ),
+ );
+ }
+}
diff --git a/tests/phpunit/includes/media/XMPTest.php b/tests/phpunit/includes/media/XMPTest.php
new file mode 100644
index 00000000..6758e94c
--- /dev/null
+++ b/tests/phpunit/includes/media/XMPTest.php
@@ -0,0 +1,223 @@
+<?php
+
+/**
+ * @group Media
+ * @covers XMPReader
+ */
+class XMPTest extends MediaWikiTestCase {
+
+ protected function setUp() {
+ parent::setUp();
+ $this->checkPHPExtension( 'exif' ); # Requires libxml to do XMP parsing
+ }
+
+ /**
+ * Put XMP in, compare what comes out...
+ *
+ * @param string $xmp The actual xml data.
+ * @param array $expected Expected result of parsing the xmp.
+ * @param string $info Short sentence on what's being tested.
+ *
+ * @throws Exception
+ * @dataProvider provideXMPParse
+ *
+ * @covers XMPReader::parse
+ */
+ public function testXMPParse( $xmp, $expected, $info ) {
+ 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, 0.0000000001 );
+ }
+
+ public static function provideXMPParse() {
+ $xmpPath = __DIR__ . '/../../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' ),
+ array( 'gps', 'Handling of exif GPS parameters in XMP' ),
+ );
+
+ $xmpFiles[] = array( 'doctype-included', 'XMP includes doctype' );
+
+ 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.
+ *
+ * @covers XMPReader::parseExtended
+ */
+ public function testExtendedXMP() {
+ $xmpPath = __DIR__ . '/../../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.
+ *
+ * @covers XMPReader::parseExtended
+ */
+ public function testExtendedXMPWithWrongGUID() {
+ $xmpPath = __DIR__ . '/../../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.
+ *
+ * @covers XMPReader::parseExtended
+ */
+ public function testExtendedXMPMissingPacket() {
+ $xmpPath = __DIR__ . '/../../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 );
+ }
+
+ /**
+ * Test for multi-section, hostile XML
+ * @covers checkParseSafety
+ */
+ public function testCheckParseSafety() {
+
+ // Test for detection
+ $xmpPath = __DIR__ . '/../../data/xmp/';
+ $file = fopen( $xmpPath . 'doctype-included.xmp', 'rb' );
+ $valid = false;
+ $reader = new XMPReader();
+ do {
+ $chunk = fread( $file, 10 );
+ $valid = $reader->parse( $chunk, feof( $file ) );
+ } while ( !feof( $file ) );
+ $this->assertFalse( $valid, 'Check that doctype is detected in fragmented XML' );
+ $this->assertEquals(
+ array(),
+ $reader->getResults(),
+ 'Check that doctype is detected in fragmented XML'
+ );
+ fclose( $file );
+ unset( $reader );
+
+ // Test for false positives
+ $file = fopen( $xmpPath . 'doctype-not-included.xmp', 'rb' );
+ $valid = false;
+ $reader = new XMPReader();
+ do {
+ $chunk = fread( $file, 10 );
+ $valid = $reader->parse( $chunk, feof( $file ) );
+ } while ( !feof( $file ) );
+ $this->assertTrue(
+ $valid,
+ 'Check for false-positive detecting doctype in fragmented XML'
+ );
+ $this->assertEquals(
+ array(
+ 'xmp-exif' => array(
+ 'DigitalZoomRatio' => '0/10',
+ 'Flash' => '9'
+ )
+ ),
+ $reader->getResults(),
+ 'Check that doctype is detected in fragmented XML'
+ );
+ }
+}
diff --git a/tests/phpunit/includes/media/XMPValidateTest.php b/tests/phpunit/includes/media/XMPValidateTest.php
new file mode 100644
index 00000000..ebec8f6c
--- /dev/null
+++ b/tests/phpunit/includes/media/XMPValidateTest.php
@@ -0,0 +1,50 @@
+<?php
+
+/**
+ * @group Media
+ */
+class XMPValidateTest extends MediaWikiTestCase {
+
+ /**
+ * @dataProvider provideDates
+ * @covers XMPValidate::validateDate
+ */
+ public function testValidateDate( $value, $expected ) {
+ // The method should modify $value.
+ XMPValidate::validateDate( array(), $value, true );
+ $this->assertEquals( $expected, $value );
+ }
+
+ public static function provideDates() {
+ /* For reference valid date formats are:
+ * YYYY
+ * YYYY-MM
+ * YYYY-MM-DD
+ * YYYY-MM-DDThh:mmTZD
+ * YYYY-MM-DDThh:mm:ssTZD
+ * YYYY-MM-DDThh:mm:ss.sTZD
+ * (Time zone is optional)
+ */
+ return array(
+ array( '1992', '1992' ),
+ array( '1992-04', '1992:04' ),
+ array( '1992-02-01', '1992:02:01' ),
+ array( '2011-09-29', '2011:09:29' ),
+ array( '1982-12-15T20:12', '1982:12:15 20:12' ),
+ array( '1982-12-15T20:12Z', '1982:12:15 20:12' ),
+ array( '1982-12-15T20:12+02:30', '1982:12:15 22:42' ),
+ array( '1982-12-15T01:12-02:30', '1982:12:14 22:42' ),
+ array( '1982-12-15T20:12:11', '1982:12:15 20:12:11' ),
+ array( '1982-12-15T20:12:11Z', '1982:12:15 20:12:11' ),
+ array( '1982-12-15T20:12:11+01:10', '1982:12:15 21:22:11' ),
+ array( '2045-12-15T20:12:11', '2045:12:15 20:12:11' ),
+ array( '1867-06-01T15:00:00', '1867:06:01 15:00:00' ),
+ /* some invalid ones */
+ array( '2001--12', null ),
+ array( '2001-5-12', null ),
+ array( '2001-5-12TZ', null ),
+ array( '2001-05-12T15', null ),
+ array( '2001-12T15:13', null ),
+ );
+ }
+}