Index: includes/mock-image-editor.php
===================================================================
--- includes/mock-image-editor.php	(revision 0)
+++ includes/mock-image-editor.php	(working copy)
@@ -0,0 +1,42 @@
+<?php
+
+if (class_exists( 'WP_Image_Editor' ) ) :
+
+	class WP_Image_Editor_Mock extends WP_Image_Editor {
+
+		public static $load_return = true;
+		public static $test_return = true;
+
+		protected function load() {
+			return self::$load_return;
+		}
+		public static function test() {
+			return self::$test_return;
+		}
+		public static function supports_mime_type( $mime_type) {
+			return true;
+		}
+		public function resize( $max_w, $max_h, $crop = false ) {
+
+		}
+		public function multi_resize( $sizes ) {
+
+		}
+		public function crop( $src_x, $src_y, $src_w, $src_h, $dst_w = null, $dst_h = null, $src_abs = false ) {
+
+		}
+		public function rotate( $angle ) {
+
+		}
+		public function flip( $horz, $vert ) {
+
+		}
+		public function save( $destfilename = null, $mime_type = null ) {
+
+		}
+		public function stream( $mime_type = null ) {
+
+		}
+	}
+
+endif;
\ No newline at end of file
Index: tests/functions/deprecated.php
===================================================================
--- tests/functions/deprecated.php	(revision 0)
+++ tests/functions/deprecated.php	(working copy)
@@ -0,0 +1,173 @@
+<?php
+
+/**
+ * Test cases for deprecated functions, arguments, and files
+ *
+ * @package    WordPress
+ * @subpackage Unit Tests
+ * @since      3.5
+ * @group      deprecated
+ */
+class Test_Functions_Deprecated extends WP_UnitTestCase {
+
+	/**
+	 * List of functions that have been passed through _deprecated_function()
+	 * @var string[]
+	 */
+	protected $_deprecated_functions = array();
+
+	/**
+	 * List of arguments that have been passed through _deprecated_argument()
+	 * @var string[]
+	 */
+	protected $_deprecated_arguments = array();
+
+	/**
+	 * List of files that have been passed through _deprecated_file()
+	 * @var string[]
+	 */
+	protected $_deprecated_files = array();
+	
+	/**
+	 * Set up the test fixture
+	 * @return void
+	 */
+	public function setUp() {
+		parent::setUp();
+		$this->_deprecated_functions = array();
+		$this->_deprecated_arguments = array();
+		$this->_deprecated_files = array();
+		add_action( 'deprecated_function_run' , array( $this, 'deprecated_function' ), 10, 3 );
+		add_action( 'deprecated_function_trigger_error', '__return_false' );
+		add_action( 'deprecated_argument_run' , array( $this, 'deprecated_argument' ), 10, 3 );
+		add_action( 'deprecated_argument_trigger_error', '__return_false' );
+		add_action( 'deprecated_file_included' , array( $this, 'deprecated_file' ), 10, 4 );
+		add_action( 'deprecated_file_trigger_error', '__return_false' );
+	}
+
+	/**
+	 * Tear down the test fixture
+	 * @return void
+	 */
+	public function teardown() {
+		remove_action( 'deprecated_function_run' , array( $this, 'deprecated_function' ), 10, 3 );
+		remove_action( 'deprecated_function_trigger_error', '__return_false' );
+		remove_action( 'deprecated_argument_run' , array( $this, 'deprecated_argument' ), 10, 3 );
+		remove_action( 'deprecated_argument_trigger_error', '__return_false' );
+		remove_action( 'deprecated_file_included' , array( $this, 'deprecated_argument' ), 10, 4 );
+		remove_action( 'deprecated_file_trigger_error', '__return_false' );
+		parent::tearDown();
+	}
+
+	/**
+	 * Catch functions that have passed through _deprecated_function
+	 * @param string $function
+	 * @param string $replacement
+	 * @param float $version
+	 * @return void
+	 */
+	public function deprecated_function( $function, $replacement, $version ) {
+		$this->_deprecated_functions[] = array(
+			'function'    => $function,
+			'replacement' => $replacement,
+			'version'     => $version
+		);
+	}
+
+	/**
+	 * Catch arguments that have passed through _deprecated_argument
+	 * @param string $argument
+	 * @param string $message
+	 * @param float $version
+	 * @return void
+	 */
+	public function deprecated_argument( $argument, $message, $version ) {
+		$this->_deprecated_arguments[] = array(
+			'argument' => $argument,
+			'message'  => $message,
+			'version'  => $version
+		);
+	}
+
+	/**
+	 * Catch arguments that have passed through _deprecated_argument
+	 * @param string $argument
+	 * @param string $message
+	 * @param float $version
+	 * @return void
+	 */
+	public function deprecated_file( $file, $version, $replacement, $message ) {
+		$this->_deprecated_files[] = array(
+			'file'        => $file,
+			'version'     => $version,
+			'replacement' => $replacement,
+			'message'     => $message
+		);
+	}
+	
+	/**
+	 * Check if something was deprecated
+	 * @param string $type argument|function|file
+	 * @param string $name
+	 * @return array|false
+	 */
+	protected function was_deprecated( $type, $name ) {
+		switch ( $type ) {
+			case 'argument' :
+				$search = $this->_deprecated_arguments;
+				$key    = 'argument';
+				break;
+			case 'function' :
+				$search = $this->_deprecated_functions;
+				$key    = 'function';
+				break;
+			default :
+				$search = $this->_deprecated_files;
+				$key    = 'file';
+		}
+		foreach ( $search as $v ) {
+			if ( $name == $v[$key] ) {
+				return $v;
+			}
+		}
+		return false;
+	}
+	
+	/**
+	 * Test that wp_save_image_file has a deprecated argument when passed a GD resource
+	 * @ticket 6821
+	 */
+	public function test_wp_save_image_file_deprecated_with_gd_resource() {
+
+		// Call wp_save_image_file
+		include_once( ABSPATH . 'wp-admin/includes/image-edit.php' );
+		$file = wp_tempnam();
+		$img = imagecreatefromjpeg( DIR_TESTDATA . '/images/canola.jpg' );
+		wp_save_image_file( $file, $img, 'image/jpeg', 1 );
+		imagedestroy( $img );
+		@unlink($file);
+
+		// Check if the arg was deprecated
+		$check = $this->was_deprecated( 'argument', 'wp_save_image_file' );
+		$this->assertNotEmpty( $check );		
+	}
+	
+	/**
+	 * Test that wp_save_image_file doesn't have a deprecated argument when passed a WP_Image_Editor
+	 * @ticket 6821
+	 */
+	public function test_wp_save_image_file_not_deprecated_with_wp_image_editor() {
+
+		// Call wp_save_image_file
+		include_once( ABSPATH . 'wp-admin/includes/image-edit.php' );
+		$file = wp_tempnam();
+		$img = WP_Image_Editor::get_instance( DIR_TESTDATA . '/images/canola.jpg' );
+		wp_save_image_file( $file, $img, 'image/jpeg', 1 );
+		unset( $img );
+		@unlink($file);
+
+		// Check if the arg was deprecated
+		$check = $this->was_deprecated( 'argument', 'wp_save_image_file' );
+		$this->assertFalse( $check );
+	}
+}
Index: tests/image/editor.php
===================================================================
--- tests/image/editor.php	(revision 0)
+++ tests/image/editor.php	(working copy)
@@ -0,0 +1,275 @@
+<?php
+
+/**
+ * Test the WP_Image_Editor base class
+ * @group image
+ * @group media
+ */
+class Tests_Image_Editor extends WP_UnitTestCase {
+
+	/**
+	 * Image editor
+	 * @var WP_Image_Editor
+	 */
+	protected $editor = null;
+	
+	/**
+	 * Setup test fixture
+	 */
+	public function setup() {
+		if ( !class_exists( 'WP_Image_Editor' ) )
+			$this->markTestSkipped();
+
+		// Include our custom mock
+		include_once( DIR_TESTDATA . '/../includes/mock-image-editor.php' );
+
+		// Mock up an abstract image editor based on WP_Image_Editor
+		// note: this *HAS* to start with 'WP_Image_Editor_'
+		$className = 'WP_Image_Editor_' . substr( md5( uniqid() ), -12 );
+		$this->editor = $this->getMockForAbstractClass( 'WP_Image_Editor', array(
+			'get_size',
+			'get_suffix'
+		), $className, false );
+		
+		// Override the filters to set our own image editor
+		add_filter( 'image_editor_class', array( $this, 'image_editor_class' ) );	
+		add_filter( 'wp_editors', array( $this, 'wp_editors' ) );
+		
+		// Un-cache the chosen image implementation
+		$this->_uncache_implementation();
+	}
+
+	/**
+	 * Tear down test fixture
+	 */
+	public function tearDown() {
+		remove_filter( 'image_editor_class', array( $this, 'image_editor_class' ) );
+		remove_filter( 'wp_editors', array( $this, 'wp_editors' ) );
+	}
+
+	/**
+	 * Unset the static implementation cache
+	 */
+	protected function _uncache_implementation() {
+		$class = new ReflectionClass( 'WP_Image_Editor' );
+		$var = $class->getProperty( 'implementation' );
+		$var->setAccessible( true );
+		$var->setValue( $class, null );
+	}
+	
+	/**
+	 * Override the wp_editors filter
+	 * @return array
+	 */
+	public function wp_editors() {		
+		return array( preg_replace('/^WP_Image_Editor_/', '', get_class( $this->editor ) ) );
+	}
+
+	/**
+	 * Override the image_editor_class filter
+	 * @return mixed
+	 */
+	public function image_editor_class() {
+		return get_class( $this->editor );
+	}
+	
+	/**
+	 * Test get_instance where load returns true
+	 * @ticket 6821
+	 */
+	public function test_get_instance_load_returns_true() {
+
+		// Swap out the PHPUnit mock with our custom mock
+		$func = create_function( '', 'return "WP_Image_Editor_Mock";');
+		remove_filter( 'image_editor_class', array( $this, 'image_editor_class' ) );
+		add_filter( 'image_editor_class', $func );
+
+		// Set load() to return true
+		WP_Image_Editor_Mock::$load_return = true;
+
+		// Load an image
+		$editor = WP_Image_Editor::get_instance( DIR_TESTDATA . '/images/canola.jpg' );
+
+		// Everything should work
+		$this->assertInstanceOf( 'WP_Image_Editor_Mock', $editor );
+
+		// Remove our custom Mock
+		remove_filter( 'image_editor_class', $func );
+	}
+
+	/**
+	 * Test get_instance where load returns false
+	 * @ticket 6821
+	 */
+	public function test_get_instance_load_returns_false() {
+
+		// Swap out the PHPUnit mock with our custom mock
+		$func = create_function( '', 'return "WP_Image_Editor_Mock";');
+		remove_filter( 'image_editor_class', array( $this, 'image_editor_class' ) );
+		add_filter( 'image_editor_class', $func );
+
+		// Set load() to return true
+		WP_Image_Editor_Mock::$load_return = new WP_Error();
+
+		// Load an image
+		$editor = WP_Image_Editor::get_instance( DIR_TESTDATA . '/images/canola.jpg' );
+
+		// Everything should work
+		$this->assertInstanceOf( 'WP_Error', $editor );
+
+		// Remove our custom Mock
+		remove_filter( 'image_editor_class', $func );
+	}
+	
+	/**
+	 * Test the "test" method
+	 * @ticket 6821
+	 */
+	public function test_test_returns_true() {
+
+		// $editor::test() returns true
+		$this->editor->staticExpects( $this->once() )
+				     ->method( 'test' )
+				     ->will( $this->returnValue( true ) );
+
+		// Load an image
+		$editor = WP_Image_Editor::get_instance( DIR_TESTDATA . '/images/canola.jpg' );
+
+		// Everything should work
+		$this->assertInstanceOf( get_class( $this->editor ), $editor );	
+	}
+	
+	/**
+	 * Test the "test" method returns false and the fallback editor is chosen
+	 * @ticket 6821
+	 */
+	public function test_test_returns_false() {
+
+		// $editor::test() returns true
+		$this->editor->staticExpects( $this->once() )
+				     ->method( 'test' )
+				     ->will( $this->returnValue( false ) );
+
+		// Set a fallback editor
+		$className = preg_replace('/^WP_Image_Editor_/', '', get_class( $this->editor ) );
+		$func = create_function( '', "return array('$className', 'Mock');" );
+		remove_filter( 'wp_editors', array( $this, 'wp_editors' ) );
+		remove_filter( 'image_editor_class', array( $this, 'image_editor_class' ) );
+		add_filter( 'wp_editors', $func );
+
+		// Load an image
+		WP_Image_Editor_Mock::$load_return = true;
+		$editor = WP_Image_Editor::get_instance( DIR_TESTDATA . '/images/canola.jpg' );
+
+		// Everything should work
+		$this->assertInstanceOf( 'WP_Image_Editor_Mock', $editor );	
+
+		// Unhook
+		remove_filter( 'image_editor_class', '__return_null' );
+		remove_filter( 'wp_editors', $func );
+	}
+
+	/**
+	 * Test test_quality
+	 * @ticket 6821
+	 */
+	public function test_set_quality() {
+
+		// Get an editor
+		$editor = WP_Image_Editor::get_instance( DIR_TESTDATA . '/images/canola.jpg' );
+
+		// Make quality readable
+		$property = new ReflectionProperty( $editor, 'quality' );
+		$property->setAccessible( true );
+		
+		// Ensure set_quality works
+		$this->assertTrue( $editor->set_quality( 75 ) );
+		$this->assertEquals( 75, $property->getValue( $editor ) );
+
+		// Ensure the quality filter works	
+		$func = create_function( '', "return 100;");
+		add_filter( 'wp_editor_set_quality', $func );
+		$this->assertTrue( $editor->set_quality( 75 ) );
+		$this->assertEquals( 100, $property->getValue( $editor ) );
+
+		// Clean up
+		remove_filter( 'wp_editor_set_quality', $func );
+	}
+
+	/**
+	 * Test generate_filename
+	 * @ticket 6821
+	 */
+	public function test_generate_filename() {
+		
+		// Get an editor
+		$editor = WP_Image_Editor::get_instance( DIR_TESTDATA . '/images/canola.jpg' );
+		$property = new ReflectionProperty( $editor, 'size' );
+		$property->setAccessible( true );
+		$property->setValue( $editor, array(
+			'height' => 50,
+			'width'  => 100
+		));
+
+		// Test with no parameters
+		$this->assertEquals( 'canola-100x50.jpg', basename( $editor->generate_filename() ) );
+
+		// Test with a suffix only
+		$this->assertEquals( 'canola-new.jpg', basename( $editor->generate_filename( 'new' ) ) );
+
+		// Test with a destination dir only
+		$this->assertEquals(trailingslashit( realpath( get_temp_dir() ) ), trailingslashit( realpath( dirname( $editor->generate_filename( null, get_temp_dir() ) ) ) ) );
+
+		// Test with a suffix only
+		$this->assertEquals( 'canola-100x50.png', basename( $editor->generate_filename( null, null, 'png' ) ) );
+	
+		// Combo!
+		$this->assertEquals( trailingslashit( realpath( get_temp_dir() ) ) . 'canola-new.png', $editor->generate_filename( 'new', realpath( get_temp_dir() ), 'png' ) );
+	}
+
+	/**
+	 * Test get_size
+	 * @ticket 6821
+	 */
+	public function test_get_size() {
+
+		$editor = WP_Image_Editor::get_instance( DIR_TESTDATA . '/images/canola.jpg' );
+		
+		// Size should be false by default
+		$this->assertNull( $editor->get_size() );
+		
+		// Set a size
+		$size = array(
+			'height' => 50,
+			'width'  => 100
+		);
+		$property = new ReflectionProperty( $editor, 'size' );
+		$property->setAccessible( true );
+		$property->setValue( $editor, $size );
+
+		$this->assertEquals( $size, $editor->get_size() );
+	}
+	
+	/**
+	 * Test get_suffix
+	 * @ticket 6821
+	 */
+	public function test_get_suffix() {
+
+		$editor = WP_Image_Editor::get_instance( DIR_TESTDATA . '/images/canola.jpg' );
+		
+		// Size should be false by default
+		$this->assertFalse( $editor->get_suffix() );
+
+		// Set a size
+		$size = array(
+			'height' => 50,
+			'width'  => 100
+		);
+		$property = new ReflectionProperty( $editor, 'size' );
+		$property->setAccessible( true );
+		$property->setValue( $editor, $size );
+
+		$this->assertEquals( '100x50', $editor->get_suffix() );
+	}
+}
Index: tests/image/resize.php
===================================================================
--- tests/image/resize.php	(revision 1047)
+++ tests/image/resize.php	(working copy)
@@ -123,4 +123,39 @@
 		unlink($image);
 	}
 
+	/**
+	 * Try resizing a non-existent image
+	 * @ticket 6821
+	 */
+	public function test_resize_non_existent_image() {
+		$classes = array('WP_Image_Editor_GD', 'WP_Image_Editor_Imagick');
+		foreach ( $classes as $class ) {
+			if ( !$class::test() ) {
+				continue;
+			}
+			$filter = create_function( '', "return $class;" );
+			add_filter( 'image_editor_class', $filter );
+			$image = image_resize( DIR_TESTDATA.'/images/test-non-existent-image.jpg', 25, 25 );
+			$this->assertInstanceOf( 'WP_Error', $image );
+			$this->assertEquals( 'error_loading_image', $image->get_error_code() );
+		}
+	}
+	
+	/**
+	 * Try resizing a php file (bad image)
+	 * @ticket 6821
+	 */
+	public function test_resize_bad_image() {
+		$classes = array('WP_Image_Editor_GD', 'WP_Image_Editor_Imagick');
+		foreach ( $classes as $class ) {
+			if ( !$class::test() ) {
+				continue;
+			}
+			$filter = create_function( '', "return $class;" );
+			add_filter( 'image_editor_class', $filter );
+			$image = image_resize( DIR_TESTDATA.'/export/crazy-cdata.xml', 25, 25 );
+			$this->assertInstanceOf( 'WP_Error', $image );
+			$this->assertEquals( 'invalid_image', $image->get_error_code() );
+		}
+	}
 }
Index: tests/image/functions.php
===================================================================
--- tests/image/functions.php	(revision 1047)
+++ tests/image/functions.php	(working copy)
@@ -6,6 +6,26 @@
  * @group upload
  */
 class Tests_Image_Functions extends WP_UnitTestCase {
+
+	/**
+	 * Get the MIME type of a file
+	 * @param string $filename
+	 * @return string
+	 */
+	protected function get_mime_type( $filename ) {
+		$mime_type = ''; 
+		if ( extension_loaded( 'fileinfo' ) ) { 
+			$finfo = new finfo();
+			$mime_type = $finfo->file( $filename, FILEINFO_MIME );
+		} elseif ( function_exists('mime_content_type') ) { 
+			$mime_type = mime_content_type( $filename );
+		}
+		if ( false !== strpos( $mime_type, ';' ) ) {
+			list( $mime_type, $charset ) = explode( ';', $mime_type, 2 );
+		}
+		return $mime_type;
+	}
+	
 	function test_is_image_positive() {
 		// these are all image files recognized by php
 		$files = array(
@@ -72,6 +92,131 @@
 		foreach ($files as $file) {
 			$this->assertFalse( file_is_displayable_image( DIR_TESTDATA.'/images/'.$file ), "file_is_valid_image($file) should return false" );
 		}
+	}	
+	
+	/**
+	 * Test save image file and mime_types
+	 * @ticket 6821
+	 */
+	public function test_wp_save_image_file() {
+		include_once( ABSPATH . 'wp-admin/includes/image-edit.php' );
+
+		// Mime types
+		$mime_types = array(
+			'image/jpeg',
+			'image/gif',
+			'image/png'
+		);
+		
+		// Test each image editor engine
+		$classes = array('WP_Image_Editor_GD', 'WP_Image_Editor_Imagick');
+		foreach ( $classes as $class ) {
+			
+			// If the image editor isn't available, skip it
+			if ( !$class::test() ) {
+				continue;
+			}
+			$filter = create_function( '', "return '$class';" );
+			add_filter( 'image_editor_class', $filter );
+
+			// Call wp_save_image_file
+			$img = WP_Image_Editor::get_instance( DIR_TESTDATA . '/images/canola.jpg' );
+			
+			// Save a file as each mime type, assert it works
+			foreach ( $mime_types as $mime_type ) {
+				$file = wp_tempnam();				
+				$ret = wp_save_image_file( $file, $img, $mime_type, 1 );
+				$this->assertNotEmpty( $ret );
+				$this->assertNotInstanceOf( 'WP_Error', $ret );
+				$this->assertEquals( $mime_type, $this->get_mime_type( $ret['path'] ) );
+				
+				// Clean up
+				@unlink( $file );
+				@unlink( $ret['path'] );
+			}
+
+			// Clean up
+			unset( $img );
+		}
 	}
+	
+	/**
+	 * Test that a passed mime type overrides the extension in the filename
+	 * @ticket 6821
+	 */
+	public function test_mime_overrides_filename() {
 
+		// Test each image editor engine
+		$classes = array('WP_Image_Editor_GD', 'WP_Image_Editor_Imagick');
+		foreach ( $classes as $class ) {
+
+			// If the image editor isn't available, skip it
+			if ( !$class::test() ) {
+				continue;
+			}
+			$filter = create_function( '', "return '$class';" );
+			add_filter( 'image_editor_class', $filter );
+
+			// Save the file
+			$img = WP_Image_Editor::get_instance( DIR_TESTDATA . '/images/canola.jpg' );
+			$mime_type = 'image/gif';
+			$file = wp_tempnam( 'tmp.jpg' );
+			$ret = $img->save( $file, $mime_type );
+			
+			// Make assertions
+			$this->assertNotEmpty( $ret );
+			$this->assertNotInstanceOf( 'WP_Error', $ret );
+			$this->assertEquals( $mime_type, $this->get_mime_type( $ret['path'] ) );
+			
+			// Clean up
+			@unlink( $file );
+			@unlink( $ret['path'] );
+			unset( $img );
+		}
+	}
+
+	/**
+	 * Test that mime types are correctly inferred from file extensions
+	 * @ticket 6821
+	 */
+	public function test_inferred_mime_types() {
+
+		// Mime types
+		$mime_types = array(
+			'jpg'  => 'image/jpeg',
+			'jpeg' => 'image/jpeg',
+			'jpe'  => 'image/jpeg',
+			'gif'  => 'image/gif',
+			'png'  => 'image/png',
+			'unk'  => 'image/jpeg' // Default, unknown
+		);
+
+		// Test each image editor engine
+		$classes = array('WP_Image_Editor_GD', 'WP_Image_Editor_Imagick');
+		foreach ( $classes as $class ) {
+
+			// If the image editor isn't available, skip it
+			if ( !$class::test() ) {
+				continue;
+			}
+			$filter = create_function( '', "return '$class';" );
+			add_filter( 'image_editor_class', $filter );
+			
+			// Save the image as each file extension, check the mime type
+			$img = WP_Image_Editor::get_instance( DIR_TESTDATA . '/images/canola.jpg' );
+			$temp = get_temp_dir();
+			foreach ( $mime_types as $ext => $mime_type ) {
+				$file = wp_unique_filename( $temp, uniqid() . ".$ext" );
+				$ret = $img->save( trailingslashit( $temp ) . $file );
+				$this->assertNotEmpty( $ret );
+				$this->assertNotInstanceOf( 'WP_Error', $ret );
+				$this->assertEquals( $mime_type, $this->get_mime_type( $ret['path'] ) );
+				@unlink( $file );
+				@unlink( $ret['path'] );
+			}
+
+			// Clean up
+			unset( $img );
+		}
+	}
 }
