? .DS_Store
? .buildpath
? .cache
? .git
? .project
? .settings
? INSTALL.sqlite.txt
? file_334303_11.patch
? junk
? logs
? includes/dummy_stream_wrapper.inc
? sites/.DS_Store
? sites/all/modules
? sites/default/.DS_Store
? sites/default/files
? sites/default/settings.php
? sites/default/test
Index: includes/file.inc
===================================================================
RCS file: /cvs/drupal/drupal/includes/file.inc,v
retrieving revision 1.151
diff -u -p -r1.151 file.inc
--- includes/file.inc	6 Jan 2009 12:00:40 -0000	1.151
+++ includes/file.inc	8 Jan 2009 04:41:22 -0000
@@ -361,18 +361,21 @@ function file_save($file) {
  *   replace the file or rename the file based on the $replace parameter.
  * - Adds the new file to the files database. If the source file is a
  *   temporary file, the resulting file will also be a temporary file.
- *   @see file_save_upload about temporary files.
+ *   @see file_save_upload() for details on temporary files.
  *
  * @param $source
  *   A file object.
  * @param $destination
- *   A string containing the directory $source should be copied to. If this
- *   value is omitted, Drupal's 'files' directory will be used.
+ *   A string containing the destination that $source should be copied to. This
+ *   can be a complete file path, a directory path or, if this value is omitted,
+ *   Drupal's 'files' directory will be used.
  * @param $replace
  *   Replace behavior when the destination file already exists:
- *   - FILE_EXISTS_REPLACE - Replace the existing file.
+ *   - FILE_EXISTS_REPLACE - Replace the existing file. If a managed file with
+ *       the destination name exists then its database entry will be updated. If
+ *       no database entry is found then a new one will be created.
  *   - FILE_EXISTS_RENAME - Append _{incrementing number} until the filename is
- *                          unique.
+ *       unique.
  *   - FILE_EXISTS_ERROR - Do nothing and return FALSE.
  * @return
  *   File object if the copy is successful, or FALSE in the event of an error.
@@ -385,14 +388,30 @@ function file_copy($source, $destination
 
   if ($filepath = file_unmanaged_copy($source->filepath, $destination, $replace)) {
     $file = clone $source;
-    $file->fid      = NULL;
-    $file->filename = basename($filepath);
+    $file->fid = NULL;
     $file->filepath = $filepath;
-    if ($file = file_save($file)) {
-      // Inform modules that the file has been copied.
-      module_invoke_all('file_copy', $file, $source);
-      return $file;
+    $file->filename = basename($filepath);
+    // If we are replacing an existing file re-use its database record.
+    if ($replace == FILE_EXISTS_REPLACE) {
+      $existing_files = file_load_multiple(array(), array('filepath' => $filepath));
+      if (count($existing_files)) {
+        $existing = reset($existing_files);
+        $file->fid = $existing->fid;
+        $file->filename = $existing->filename;
+      }
     }
+    // If we are renaming around an existing file (rather than a directory),
+    // use its basename for the filename.
+    else if ($replace == FILE_EXISTS_RENAME && is_file(file_create_path($destination))) {
+      $file->filename = basename($destination);
+    }
+
+    $file = file_save($file);
+
+    // Inform modules that the file has been copied.
+    module_invoke_all('file_copy', $file, $source);
+
+    return $file;
   }
   return FALSE;
 }
@@ -412,13 +431,14 @@ function file_copy($source, $destination
  * @param $source
  *   A string specifying the file location of the original file.
  * @param $destination
- *   A string containing the directory $source should be copied to. If this
- *   value is omitted, Drupal's 'files' directory will be used.
+ *   A string containing the destination that $source should be copied to. This
+ *   can be a complete file path, a directory path or, if this value is omitted,
+ *   Drupal's 'files' directory will be used.
  * @param $replace
  *   Replace behavior when the destination file already exists:
  *   - FILE_EXISTS_REPLACE - Replace the existing file.
  *   - FILE_EXISTS_RENAME - Append _{incrementing number} until the filename is
- *                          unique.
+ *       unique.
  *   - FILE_EXISTS_ERROR - Do nothing and return FALSE.
  * @return
  *   The path to the new file, or FALSE in the event of an error.
@@ -482,7 +502,7 @@ function file_unmanaged_copy($source, $d
  *   Replace behavior when the destination file already exists.
  *   - FILE_EXISTS_REPLACE - Replace the existing file.
  *   - FILE_EXISTS_RENAME - Append _{incrementing number} until the filename is
- *                          unique.
+ *       unique.
  *   - FILE_EXISTS_ERROR - Do nothing and return FALSE.
  * @return
  *   The destination file path or FALSE if the file already exists and
@@ -523,13 +543,18 @@ function file_destination($destination, 
  * @param $source
  *   A file object.
  * @param $destination
- *   A string containing the directory $source should be copied to. If this
- *   value is omitted, Drupal's 'files' directory will be used.
+ *   A string containing the destination that $source should be moved to. This
+ *   can be a complete file path, a directory path or, if this value is omitted,
+ *   Drupal's 'files' directory will be used.
  * @param $replace
  *   Replace behavior when the destination file already exists:
- *   - FILE_EXISTS_REPLACE - Replace the existing file.
+ *   - FILE_EXISTS_REPLACE - Replace the existing file. If a managed file with
+ *       the destination name exists then its database entry will be updated and
+ *       file_delete() called on the source file after hook_file_move is called.
+ *       If no database entry is found then the source files record will be
+ *       updated.
  *   - FILE_EXISTS_RENAME - Append _{incrementing number} until the filename is
- *                          unique.
+ *       unique.
  *   - FILE_EXISTS_ERROR - Do nothing and return FALSE.
  * @return
  *   Resulting file object for success, or FALSE in the event of an error.
@@ -541,15 +566,36 @@ function file_move($source, $destination
   $source = (object)$source;
 
   if ($filepath = file_unmanaged_move($source->filepath, $destination, $replace)) {
+    $delete_source = FALSE;
+
     $file = clone $source;
-    $file->filename = basename($filepath);
     $file->filepath = $filepath;
-    if ($file = file_save($file)) {
-      // Inform modules that the file has been moved.
-      module_invoke_all('file_move', $file, $source);
-      return $file;
+    // If we are replacing an existing file re-use its database record.
+    if ($replace == FILE_EXISTS_REPLACE) {
+      $existing_files = file_load_multiple(array(), array('filepath' => $filepath));
+      if (count($existing_files)) {
+        $existing = reset($existing_files);
+        $delete_source = TRUE;
+        $file->fid = $existing->fid;
+      }
     }
-    drupal_set_message(t('The removal of the original file %file has failed.', array('%file' => $source->filepath)), 'error');
+    // If we are renaming around an existing file (rather than a directory),
+    // use its basename for the filename.
+    else if ($replace == FILE_EXISTS_RENAME && is_file(file_create_path($destination))) {
+      $file->filename = basename($destination);
+    }
+
+    $file = file_save($file);
+
+    // Inform modules that the file has been moved.
+    module_invoke_all('file_move', $file, $source);
+
+    if ($delete_source) {
+      // Try a soft delete to remove original if it's not in use elsewhere.
+      file_delete($source);
+    }
+
+    return $file;
   }
   return FALSE;
 }
@@ -561,13 +607,14 @@ function file_move($source, $destination
  * @param $source
  *   A string specifying the file location of the original file.
  * @param $destination
- *   A string containing the directory $source should be copied to. If this
- *   value is omitted, Drupal's 'files' directory will be used.
+ *   A string containing the destination that $source should be moved to. This
+ *   can be a complete file path, a directory name or, if this value is omitted,
+ *   Drupal's 'files' directory will be used.
  * @param $replace
  *   Replace behavior when the destination file already exists:
  *   - FILE_EXISTS_REPLACE - Replace the existing file.
  *   - FILE_EXISTS_RENAME - Append _{incrementing number} until the filename is
- *                          unique.
+ *       unique.
  *   - FILE_EXISTS_ERROR - Do nothing and return FALSE.
  * @return
  *   The filepath of the moved file, or FALSE in the event of an error.
@@ -875,6 +922,11 @@ function file_save_upload($source, $vali
 
   $file->source = $source;
   $file->destination = file_destination(file_create_path($destination . '/' . $file->filename), $replace);
+  // If file_destination() returns FALSE then $replace == FILE_EXISTS_ERROR and
+  // there's an existing file so we need to bail.
+  if ($file->destination === FALSE) {
+    return FALSE;
+  }
 
   // Add in our check of the the file name length.
   $validators['file_validate_name_length'] = array();
@@ -905,6 +957,15 @@ function file_save_upload($source, $vali
     return FALSE;
   }
 
+  // If we are replacing an existing file re-use its database record.
+  if ($replace == FILE_EXISTS_REPLACE) {
+    $existing_files = file_load_multiple(array(), array('filepath' => $file->filepath));
+    if (count($existing_files)) {
+      $existing = reset($existing_files);
+      $file->fid = $existing->fid;
+    }
+  }
+
   // If we made it this far it's safe to record this file in the database.
   if ($file = file_save($file)) {
     // Add file to the cache.
@@ -1121,9 +1182,11 @@ function file_validate_image_resolution(
  *   files directory.
  * @param $replace
  *   Replace behavior when the destination file already exists:
- *   - FILE_EXISTS_REPLACE - Replace the existing file.
+ *   - FILE_EXISTS_REPLACE - Replace the existing file. If a managed file with
+ *       the destination name exists then its database entry will be updated. If
+ *       no database entry is found then a new one will be created.
  *   - FILE_EXISTS_RENAME - Append _{incrementing number} until the filename is
- *                          unique.
+ *       unique.
  *   - FILE_EXISTS_ERROR - Do nothing and return FALSE.
  * @return
  *   A file object, or FALSE on error.
@@ -1136,11 +1199,27 @@ function file_save_data($data, $destinat
   if ($filepath = file_unmanaged_save_data($data, $destination, $replace)) {
     // Create a file object.
     $file = new stdClass();
+    $file->fid = NULL;
     $file->filepath = $filepath;
-    $file->filename = basename($file->filepath);
+    $file->filename = basename($filepath);
     $file->filemime = file_get_mimetype($file->filepath);
     $file->uid      = $user->uid;
     $file->status  |= FILE_STATUS_PERMANENT;
+    // If we are replacing an existing file re-use its database record.
+    if ($replace == FILE_EXISTS_REPLACE) {
+      $existing_files = file_load_multiple(array(), array('filepath' => $filepath));
+      if (count($existing_files)) {
+        $existing = reset($existing_files);
+        $file->fid = $existing->fid;
+        $file->filename = $existing->filename;
+      }
+    }
+    // If we are renaming around an existing file (rather than a directory),
+    // use its basename for the filename.
+    else if ($replace == FILE_EXISTS_RENAME && is_file(file_create_path($destination))) {
+      $file->filename = basename($destination);
+    }
+
     return file_save($file);
   }
   return FALSE;
Index: modules/simpletest/tests/file.test
===================================================================
RCS file: /cvs/drupal/drupal/modules/simpletest/tests/file.test,v
retrieving revision 1.19
diff -u -p -r1.19 file.test
--- modules/simpletest/tests/file.test	6 Jan 2009 12:00:40 -0000	1.19
+++ modules/simpletest/tests/file.test	8 Jan 2009 04:41:22 -0000
@@ -20,6 +20,51 @@ function file_test_validator($file, $err
  */
 class FileTestCase extends DrupalWebTestCase {
   /**
+   * Check that two files have the same values for all fields other than the
+   * timestamp.
+   *
+   * @param $before
+   *   File object to compare.
+   * @param $after
+   *   File object to compare.
+   */
+  function assertFileUnchanged($before, $after) {
+    $this->assertEqual($before->fid, $after->fid, t('File id is the same.'), 'File unchanged');
+    $this->assertEqual($before->uid, $after->uid, t('File owner is the same.'), 'File unchanged');
+    $this->assertEqual($before->filename, $after->filename, t('File name is the same.'), 'File unchanged');
+    $this->assertEqual($before->filepath, $after->filepath, t('File path is the same.'), 'File unchanged');
+    $this->assertEqual($before->filemime, $after->filemime, t('File MIME type is the same.'), 'File unchanged');
+    $this->assertEqual($before->filesize, $after->filesize, t('File size is the same.'), 'File unchanged');
+    $this->assertEqual($before->status, $after->status, t('File status is the same.'), 'File unchanged');
+  }
+
+  /**
+   * Check that two files are not the same by comparing the fid and filepath.
+   *
+   * @param $a
+   *   File object to compare.
+   * @param $b
+   *   File object to compare.
+   */
+  function assertDifferentFile($a, $b) {
+    $this->assertNotEqual($a->fid, $b->fid, t('Files have different ids.'), 'Different file');
+    $this->assertNotEqual($a->filepath, $b->filepath, t('Files have different paths.'), 'Different file');
+  }
+
+  /**
+   * Check that two files are the same by comparing the fid and filepath.
+   *
+   * @param $a
+   *   File object to compare.
+   * @param $b
+   *   File object to compare.
+   */
+  function assertSameFile($a, $b) {
+    $this->assertEqual($a->fid, $b->fid, t('Files have the same ids %a-fid == %b-fid.', array('%a-fid' => $a->fid, '%b-fid' => $b->fid)), 'Same file');
+    $this->assertEqual($a->filepath, $b->filepath, t('Files have the same path %a-filepath == %b-filepath', array('%a-filepath' => $a->filepath, '%b-filepath' => $b->filepath)), 'Same file');
+  }
+
+  /**
    * Helper function to test the permissions of a file.
    *
    * @param $filepath
@@ -68,16 +113,23 @@ class FileTestCase extends DrupalWebTest
    * @param $filepath
    *   Optional string specifying the file path. If none is provided then a
    *   randomly named file will be created in the site's files directory.
+   * @param $contents
+   *   Optional contents to save into the file. If a NULL value is provided an
+   *   arbitrary string will be used.
    * @return
    *   File object.
    */
-  function createFile($filepath = NULL) {
+  function createFile($filepath = NULL, $contents = NULL) {
     if (is_null($filepath)) {
       $filepath = file_directory_path() . '/' . $this->randomName();
     }
 
-    file_put_contents($filepath, 'File put contents does not seem to appreciate empty strings so lets put in some data.');
-    $this->assertTrue(is_file($filepath), t('The test file exists on the disk.'));
+    if (is_null($contents)) {
+      $contents = "file_put_contents() doesn't seem to appreciate empty strings so lets put in some data.";
+    }
+
+    file_put_contents($filepath, $contents);
+    $this->assertTrue(is_file($filepath), t('The test file exists on the disk.'), 'Create test file');
 
     $file = new stdClass();
     $file->filepath = $filepath;
@@ -87,7 +139,9 @@ class FileTestCase extends DrupalWebTest
     $file->timestamp = REQUEST_TIME;
     $file->filesize = filesize($file->filepath);
     $file->status = 0;
-    $this->assertNotIdentical(drupal_write_record('files', $file), FALSE, t('The file was added to the database.'));
+    // Write the record directly rather than calling file_save() so we don't
+    // invoke the hooks.
+    $this->assertNotIdentical(drupal_write_record('files', $file), FALSE, t('The file was added to the database.'), 'Create test file');
 
     return $file;
   }
@@ -122,6 +176,9 @@ class FileHookTestCase extends FileTestC
       if ($actual_count == $expected_count) {
         $message = t('hook_file_@name was called correctly.', array('@name' => $hook));
       }
+      elseif ($expected_count == 0) {
+        $message = format_plural($actual_count, 'hook_file_@name was not expected to be called but was actually called once.', 'hook_file_@name was not expected to be called but was actually called @count times.', array('@name' => $hook, '@count' => $actual_count));
+      }
       else {
         $message = t('hook_file_@name was expected to be called %expected times but was called %actual times.', array('@name' => $hook, '%expected' => $expected_count, '%actual' => $actual_count));
       }
@@ -387,6 +444,16 @@ class FileUnmanagedSaveDataTest extends 
  * Test the file_save_upload() function.
  */
 class FileSaveUploadTest extends FileHookTestCase {
+  /**
+   * An image file path for uploading.
+   */
+  var $image;
+
+  /**
+   * The largest file id when the test starts.
+   */
+  var $maxFidBefore;
+
   function getInfo() {
     return array(
       'name' => t('File uploading'),
@@ -395,26 +462,36 @@ class FileSaveUploadTest extends FileHoo
     );
   }
 
-  /**
-   * Test the file_save_upload() function.
-   */
-  function testFileSaveUpload() {
-    $max_fid_before = db_query('SELECT MAX(fid) AS fid FROM {files}')->fetchField();
-    $upload_user = $this->drupalCreateUser(array('access content'));
-    $this->drupalLogin($upload_user);
+  function setUp() {
+    parent::setUp();
+    $account = $this->drupalCreateUser(array('access content'));
+    $this->drupalLogin($account);
+
+    $this->image = current($this->drupalGetTestFiles('image'));
+    $this->assertTrue(is_file($this->image->filename), t("The file we're going to upload exists."));
 
-    $image = current($this->drupalGetTestFiles('image'));
-    $this->assertTrue(is_file($image->filename), t("The file we're going to upload exists."));
-    $edit = array('files[file_test_upload]' => realpath($image->filename));
+    $this->maxFidBefore = db_query('SELECT MAX(fid) AS fid FROM {files}')->fetchField();
+
+    // Upload with replace to gurantee there's something there.
+    $edit = array(
+      'file_test_replace' => FILE_EXISTS_REPLACE,
+      'files[file_test_upload]' => realpath($this->image->filename)
+    );
     $this->drupalPost('file-test/upload', $edit, t('Submit'));
     $this->assertResponse(200, t('Received a 200 response for posted test file.'));
+    $this->assertRaw(t('You WIN!'), t('Found the successs message.'));
+    $this->assertFileHookCalled('validate');
+    $this->assertFileHookCalled('insert');
+    $this->assertFileHookCalled('update', 0);
+    file_test_reset();
+  }
 
-    // We can't easily check that the hooks were called but since
-    // file_save_upload() calles file_save() we can rely on file_save()'s
-    // test to catch problems invoking the hooks.
-
+  /**
+   * Test the file_save_upload() function.
+   */
+  function testNormal() {
     $max_fid_after = db_result(db_query('SELECT MAX(fid) AS fid FROM {files}'));
-    $this->assertTrue($max_fid_after > $max_fid_before, t('A new file was created.'));
+    $this->assertTrue($max_fid_after > $this->maxFidBefore, t('A new file was created.'));
     $file1 = file_load($max_fid_after);
     $this->assertTrue($file1, t('Loaded the file.'));
 
@@ -423,6 +500,8 @@ class FileSaveUploadTest extends FileHoo
     $image2 = current($this->drupalGetTestFiles('image'));
     $edit = array('files[file_test_upload]' => realpath($image2->filename));
     $this->drupalPost('file-test/upload', $edit, t('Submit'));
+    $this->assertResponse(200, t('Received a 200 response for posted test file.'));
+    $this->assertRaw(t('You WIN!'));
     $max_fid_after = db_query('SELECT MAX(fid) AS fid FROM {files}')->fetchField();
 
     $file2 = file_load($max_fid_after);
@@ -433,6 +512,55 @@ class FileSaveUploadTest extends FileHoo
     $this->assertTrue(isset($files[$file1->fid]), t('File was loaded successfully'));
     $this->assertTrue(isset($files[$file2->fid]), t('File was loaded successfully'));
   }
+
+
+  /**
+   * Test renaming when uploading over a file that already exists.
+   */
+  function testExistingRename() {
+    $edit = array(
+      'file_test_replace' => FILE_EXISTS_RENAME,
+      'files[file_test_upload]' => realpath($this->image->filename)
+    );
+    $this->drupalPost('file-test/upload', $edit, t('Submit'));
+    $this->assertResponse(200, t('Received a 200 response for posted test file.'));
+    $this->assertRaw(t('You WIN!'), t('Found the successs message.'));
+    $this->assertFileHookCalled('validate', 1);
+    $this->assertFileHookCalled('insert', 1);
+    $this->assertFileHookCalled('update', 0);
+  }
+
+  /**
+   * Test replacement when uploading over a file that already exists.
+   */
+  function testExistingReplace() {
+    $edit = array(
+      'file_test_replace' => FILE_EXISTS_REPLACE,
+      'files[file_test_upload]' => realpath($this->image->filename)
+    );
+    $this->drupalPost('file-test/upload', $edit, t('Submit'));
+    $this->assertResponse(200, t('Received a 200 response for posted test file.'));
+    $this->assertRaw(t('You WIN!'), t('Found the successs message.'));
+    $this->assertFileHookCalled('validate', 1);
+    $this->assertFileHookCalled('insert', 0);
+    $this->assertFileHookCalled('update', 1);
+  }
+
+  /**
+   * Test for failure when uploading over a file that already exists.
+   */
+  function testExistingError() {
+    $edit = array(
+      'file_test_replace' => FILE_EXISTS_ERROR,
+      'files[file_test_upload]' => realpath($this->image->filename)
+    );
+    $this->drupalPost('file-test/upload', $edit, t('Submit'));
+    $this->assertResponse(200, t('Received a 200 response for posted test file.'));
+    $this->assertRaw(t('Epic upload FAIL!'), t('Found the failure message.'));
+    $this->assertFileHookCalled('validate', 0);
+    $this->assertFileHookCalled('insert', 0);
+    $this->assertFileHookCalled('update', 0);
+  }
 }
 
 /**
@@ -882,19 +1010,122 @@ class FileMoveTest extends FileHookTestC
    * Move a normal file.
    */
   function testNormal() {
-    $file = $this->createFile();
+    $contents = $this->randomName(10);
+    $source = $this->createFile(NULL, $contents);
     $desired_filepath = file_directory_path() . '/' . $this->randomName();
 
-    $file = file_move(clone $file, $desired_filepath, FILE_EXISTS_ERROR);
-    $this->assertTrue($file, t("File moved sucessfully."));
+    $result = file_move(clone $source, $desired_filepath, FILE_EXISTS_ERROR);
+
+    // Look at the results.
+    $this->assertTrue($result, t('File moved sucessfully.'));
+    $this->assertFalse(file_exists($source->filepath));
+    $this->assertEqual($contents, file_get_contents($result->filepath), t('Contents of file correctly written.'));
     $this->assertFileHookCalled('move');
     $this->assertFileHookCalled('update');
-    $this->assertEqual($file->fid, $file->fid, t("File id $file->fid is unchanged after move."));
+    $this->assertEqual($source->fid, $result->fid, t("Source file id's' %fid is unchanged after move.", array('%fid' => $source->fid)));
 
-    $loaded_file = file_load($file->fid);
-    $this->assertTrue($loaded_file, t("File can be loaded from the database."));
-    $this->assertEqual($file->filename, $loaded_file->filename, t("File name was updated correctly in the database."));
-    $this->assertEqual($file->filepath, $loaded_file->filepath, t("File path was updated correctly in the database."));
+    $loaded_file = file_load($result->fid, TRUE);
+    $this->assertTrue($loaded_file, t('File can be loaded from the database.'));
+    $this->assertFileUnchanged($result, $loaded_file);
+  }
+
+  /**
+   * Test renaming when moveing onto a file that already exists.
+   */
+  function testExistingRename() {
+    $contents = $this->randomName(10);
+    $source = $this->createFile(NULL, $contents);
+    $target = $this->createFile();
+    $this->assertDifferentFile($source, $target);
+
+    $result = file_move(clone $source, $target->filepath, FILE_EXISTS_RENAME);
+
+    // Look at the results.
+    $this->assertTrue($result, t('File moved sucessfully.'));
+    $this->assertFalse(file_exists($source->filepath));
+    $this->assertEqual($contents, file_get_contents($result->filepath), t('Contents of file correctly written.'));
+    $this->assertFileHookCalled('move');
+    $this->assertFileHookCalled('update', 1);
+    $this->assertFileHookCalled('insert', 0);
+
+    // Compare the returned value to what made it into the database.
+    $this->assertFileUnchanged($result, file_load($result->fid, TRUE));
+    // The target file should not have been altered.
+    $this->assertFileUnchanged($target, file_load($target->fid, TRUE));
+    // Make sure we end up with two distinct files afterwards.
+    $this->assertDifferentFile($target, $result);
+
+    // Compare the source and results.
+    $loaded_source = file_load($source->fid, TRUE);
+    $this->assertEqual($loaded_source->fid, $result->fid, t("Returned file's id matches the source."));
+    $this->assertNotEqual($loaded_source->filepath, $source->filepath, t("Returned file path has changed from the original."));
+  }
+
+  /**
+   * Test replacement when moving onto a file that already exists.
+   */
+  function testExistingReplace() {
+    $contents = $this->randomName(10);
+    $source = $this->createFile(NULL, $contents);
+    $target = $this->createFile();
+    $this->assertDifferentFile($source, $target);
+
+    $result = file_move(clone $source, $target->filepath, FILE_EXISTS_REPLACE);
+
+    // Look at the results
+    $this->assertEqual($contents, file_get_contents($result->filepath), t('Contents of file were overwritten.'));
+    $this->assertFalse(file_exists($source->filepath));
+    $this->assertTrue($result, t('File moved sucessfully.'));
+    $this->assertFileHookCalled('move');
+    $this->assertFileHookCalled('update');
+    $this->assertFileHookCalled('delete');
+    $this->assertFileHookCalled('insert', 0);
+
+    $loaded_result = file_load($result->fid, TRUE);
+    // Check that the changes were actually saved to the database.
+    $this->assertFileUnchanged($result, $loaded_result);
+    // Check that target was re-used.
+    $this->assertSameFile($target, $loaded_result);
+    // Source and result should be totally different.
+    $this->assertDifferentFile($source, $loaded_result);
+  }
+
+  /**
+   * Test replacement when moving onto itself.
+   */
+  function testExistingReplaceSelf() {
+    $contents = $this->randomName(10);
+    $source = $this->createFile(NULL, $contents);
+
+    $result = file_move(clone $source, $source->filepath, FILE_EXISTS_REPLACE);
+    $this->assertFalse($result, t('File move failed.'));
+    $this->assertEqual($contents, file_get_contents($source->filepath), t('Contents of file were not altered.'));
+    $this->assertFileHookCalled('move', 0);
+    $this->assertFileHookCalled('insert', 0);
+    $this->assertFileHookCalled('update', 0);
+    $this->assertFileHookCalled('delete', 0);
+    $this->assertFileUnchanged($source, file_load($source->fid, TRUE));
+  }
+
+  /**
+   * Test that moving onto an existing file fails when FILE_EXISTS_ERROR is
+   * specified.
+   */
+  function testExistingError() {
+    $contents = $this->randomName(10);
+    $source = $this->createFile();
+    $target = $this->createFile(NULL, $contents);
+    $this->assertDifferentFile($source, $target);
+
+    $result = file_move(clone $source, $target->filepath, FILE_EXISTS_ERROR);
+    $this->assertFalse($result, t('File move failed.'));
+    $this->assertTrue(file_exists($source->filepath));
+    $this->assertEqual($contents, file_get_contents($target->filepath), t('Contents of file were not altered.'));
+    $this->assertFileHookCalled('move', 0);
+    $this->assertFileHookCalled('insert', 0);
+    $this->assertFileHookCalled('update', 0);
+    $this->assertFileUnchanged($source, file_load($source->fid, TRUE));
+    $this->assertFileUnchanged($target, file_load($target->fid, TRUE));
   }
 }
 
@@ -912,27 +1143,113 @@ class FileCopyTest extends FileHookTestC
   }
 
   /**
-   * Test copying a normal file.
+   * Test file copying in the normal, base case.
    */
   function testNormal() {
-    $source_file = $this->createFile();
+    $contents = $this->randomName(10);
+    $source = $this->createFile(NULL, $contents);
     $desired_filepath = file_directory_path() . '/' . $this->randomName();
 
-    $file = file_copy(clone $source_file, $desired_filepath, FILE_EXISTS_ERROR);
-    $this->assertTrue($file, t("File copied sucessfully."));
+    $result = file_copy(clone $source, $desired_filepath, FILE_EXISTS_ERROR);
+
+    // Look at the results.
+    $this->assertTrue($result, t('File copied sucessfully.'));
+    $this->assertEqual($contents, file_get_contents($result->filepath), t('Contents of file were copied correctly.'));
     $this->assertFileHookCalled('copy');
     $this->assertFileHookCalled('insert');
-    $this->assertNotEqual($source_file->fid, $file->fid, t("A new file id was created."));
-    $this->assertNotEqual($source_file->filepath, $file->filepath, t("A new filepath was created."));
-    $this->assertEqual($file->filepath, $desired_filepath, t('The copied file object has the desired filepath.'));
-    $this->assertTrue(file_exists($source_file->filepath), t('The original file still exists.'));
-    $this->assertTrue(file_exists($file->filepath), t('The copied file exists.'));
+    $this->assertDifferentFile($source, $result);
+    $this->assertEqual($result->filepath, $desired_filepath, t('The copied file object has the desired filepath.'));
+    $this->assertTrue(file_exists($source->filepath), t('The original file still exists.'));
+    $this->assertTrue(file_exists($result->filepath), t('The copied file exists.'));
 
     // Check that the changes were actually saved to the database.
-    $loaded_file = file_load($file->fid);
-    $this->assertTrue($loaded_file, t("File can be loaded from the database."));
-    $this->assertEqual($file->filename, $loaded_file->filename, t("File name was updated correctly in the database."));
-    $this->assertEqual($file->filepath, $loaded_file->filepath, t("File path was updated correctly in the database."));
+    $this->assertFileUnchanged($result, file_load($result->fid, TRUE));
+  }
+
+  /**
+   * Test renaming when copying over a file that already exists.
+   */
+  function testExistingRename() {
+    $contents = $this->randomName(10);
+    $source = $this->createFile(NULL, $contents);
+    $target = $this->createFile();
+    $this->assertDifferentFile($source, $target);
+
+    $result = file_copy(clone $source, $target->filepath, FILE_EXISTS_RENAME);
+
+    // Look at the results.
+    $this->assertTrue($result, t('File copied sucessfully.'));
+    $this->assertEqual($contents, file_get_contents($result->filepath), t('Contents of file were copied correctly.'));
+    $this->assertNotEqual($result->filepath, $source->filepath, t('Returned file path has changed from the original.'));
+    $this->assertFileHookCalled('copy');
+    $this->assertFileHookCalled('insert');
+    $this->assertFileHookCalled('update', 0);
+
+    $loaded_source = file_load($source->fid, TRUE);
+    $loaded_target = file_load($target->fid, TRUE);
+    $loaded_result = file_load($result->fid, TRUE);
+    // Verify that the source file wasn't changed.
+    $this->assertFileUnchanged($source, $loaded_source);
+    // Verify that what was returned is what's in the database.
+    $this->assertFileUnchanged($result, $loaded_result);
+    // Make sure we end up with three distinct files afterwards.
+    $this->assertDifferentFile($loaded_source, $loaded_target);
+    $this->assertDifferentFile($loaded_target, $loaded_result);
+    $this->assertDifferentFile($loaded_source, $loaded_result);
+  }
+
+  /**
+   * Test replacement when copying over a file that already exists.
+   */
+  function testExistingReplace() {
+    $contents = $this->randomName(10);
+    $source = $this->createFile(NULL, $contents);
+    $target = $this->createFile();
+    $this->assertDifferentFile($source, $target);
+
+    $result = file_copy(clone $source, $target->filepath, FILE_EXISTS_REPLACE);
+
+    // Look at the results.
+    $this->assertTrue($result, t('File copied sucessfully.'));
+    $this->assertEqual($contents, file_get_contents($result->filepath), t('Contents of file were overwritten.'));
+    $this->assertFileHookCalled('copy');
+    $this->assertFileHookCalled('insert', 0);
+    $this->assertFileHookCalled('update');
+    $this->assertDifferentFile($source, $result);
+
+    // Now examine what actually made it into the database.
+    $loaded_source = file_load($source->fid, TRUE);
+    $loaded_target = file_load($target->fid, TRUE);
+    $loaded_result = file_load($result->fid, TRUE);
+
+    // Verify that the source file wasn't changed.
+    $this->assertFileUnchanged($source, $loaded_source);
+    // Verify that what was returned is what's in the database.
+    $this->assertFileUnchanged($result, $loaded_result);
+    // Target file was reused for the result.
+    $this->assertFileUnchanged($loaded_target, $loaded_result);
+  }
+
+  /**
+   * Test that copying over an existing file fails when FILE_EXISTS_ERROR is
+   * specified.
+   */
+  function testExistingError() {
+    $contents = $this->randomName(10);
+    $source = $this->createFile();
+    $target = $this->createFile(NULL, $contents);
+    $this->assertDifferentFile($source, $target);
+
+    $result = file_copy(clone $source, $target->filepath, FILE_EXISTS_ERROR);
+
+    // Look at the results.
+    $this->assertFalse($result, t('File copy failed.'));
+    $this->assertEqual($contents, file_get_contents($target->filepath), t('Contents of file were not altered.'));
+    $this->assertFileHookCalled('copy', 0);
+    $this->assertFileHookCalled('insert', 0);
+    $this->assertFileHookCalled('update', 0);
+    $this->assertFileUnchanged($source, file_load($source->fid, TRUE));
+    $this->assertFileUnchanged($target, file_load($target->fid, TRUE));
   }
 }
 
@@ -1135,33 +1452,117 @@ class FileSaveDataTest extends FileHookT
   }
 
   /**
-   * Test the file_save_data() function.
+   * Test the file_save_data() function when no filename is provided.
    */
-  function testFileSaveData() {
+  function testWithoutFilename() {
     $contents = $this->randomName(8);
 
-    // No filename.
-    $file = file_save_data($contents);
-    $this->assertTrue($file, t("Unnamed file saved correctly."));
-    $this->assertEqual(file_directory_path(), dirname($file->filepath), t("File was placed in Drupal's files directory."));
-    $this->assertEqual($contents, file_get_contents(realpath($file->filepath)), t("Contents of the file are correct."));
-    $this->assertEqual($file->filemime, 'application/octet-stream', t("A MIME type was set."));
-    $this->assertEqual($file->status, FILE_STATUS_PERMANENT, t("The file's status was set to permanent."));
-
-    // Try loading the file.
-    $loaded_file = file_load($file->fid);
-    $this->assertTrue($loaded_file, t("File loaded from database."));
+    $result = file_save_data($contents);
 
-    // Provide a filename.
-    $file = file_save_data($contents, 'asdf.txt', FILE_EXISTS_REPLACE);
-    $this->assertTrue($file, t("Unnamed file saved correctly."));
-    $this->assertEqual(file_directory_path(), dirname($file->filepath), t("File was placed in Drupal's files directory."));
-    $this->assertEqual('asdf.txt', basename($file->filepath), t("File was named correctly."));
-    $this->assertEqual($contents, file_get_contents(realpath($file->filepath)), t("Contents of the file are correct."));
+    $this->assertTrue($result, t('Unnamed file saved correctly.'));
+    $this->assertEqual(file_directory_path(), dirname($result->filepath), t("File was placed in Drupal's files directory."));
+    $this->assertEqual($result->filename, basename($result->filepath), t("Filename was set to the file's basename."));
+    $this->assertEqual($contents, file_get_contents($result->filepath), t('Contents of the file are correct.'));
+    $this->assertEqual($result->filemime, 'application/octet-stream', t('A MIME type was set.'));
+    $this->assertEqual($result->status, FILE_STATUS_PERMANENT, t("The file's status was set to permanent."));
+    $this->assertFileHookCalled('insert');
+    $this->assertFileHookCalled('update', 0);
+    $this->assertFileHookCalled('delete', 0);
+
+    // Verify that what was returned is what's in the database.
+    $this->assertFileUnchanged($result, file_load($result->fid, TRUE));
+  }
+
+  /**
+   * Test the file_save_data() function when a filename is provided.
+   */
+  function testWithFilename() {
+    $contents = $this->randomName(8);
+
+    $result = file_save_data($contents, 'asdf.txt');
+
+    $this->assertTrue($result, t('Unnamed file saved correctly.'));
+    $this->assertEqual(file_directory_path(), dirname($result->filepath), t("File was placed in Drupal's files directory."));
+    $this->assertEqual('asdf.txt', basename($result->filepath), t('File was named correctly.'));
+    $this->assertEqual($contents, file_get_contents($result->filepath), t('Contents of the file are correct.'));
+    $this->assertEqual($result->filemime, 'text/plain', t('A MIME type was set.'));
+    $this->assertEqual($result->status, FILE_STATUS_PERMANENT, t("The file's status was set to permanent."));
+    $this->assertFileHookCalled('insert');
+    $this->assertFileHookCalled('update', 0);
+    $this->assertFileHookCalled('delete', 0);
+
+    // Verify that what was returned is what's in the database.
+    $this->assertFileUnchanged($result, file_load($result->fid, TRUE));
+  }
+
+  /**
+   * Test file_save_data() when renaming around an existing file.
+   */
+  function testExistingRename() {
+    $existing = $this->createFile();
+    $contents = $this->randomName(8);
+
+    $result = file_save_data($contents, $existing->filepath, FILE_EXISTS_RENAME);
+
+    $this->assertTrue($result, t("File saved sucessfully."));
+    $this->assertEqual(file_directory_path(), dirname($result->filepath), t("File was placed in Drupal's files directory."));
+    $this->assertEqual($result->filename, $existing->filename, t("Filename was set to the basename of the source."));
+    $this->assertEqual($contents, file_get_contents($result->filepath), t("Contents of the file are correct."));
+    $this->assertEqual($result->filemime, 'application/octet-stream', t("A MIME type was set."));
+    $this->assertEqual($result->status, FILE_STATUS_PERMANENT, t("The file's status was set to permanent."));
+    $this->assertFileHookCalled('insert');
+
+    // Ensure that the existing file wasn't ovewritten.
+    $this->assertDifferentFile($existing, $result);
+    $this->assertFileUnchanged($existing, file_load($existing->fid, TRUE));
+
+    // Verify that was returned is what's in the database.
+    $this->assertFileUnchanged($result, file_load($result->fid, TRUE));
+  }
+
+  /**
+   * Test file_save_data() when replacing an existing file.
+   */
+  function testExistingReplace() {
+    $existing = $this->createFile();
+    $contents = $this->randomName(8);
+
+    $result = file_save_data($contents, $existing->filepath, FILE_EXISTS_REPLACE);
+
+    $this->assertTrue($result, t('File saved sucessfully.'));
+    $this->assertEqual(file_directory_path(), dirname($result->filepath), t("File was placed in Drupal's files directory."));
+    $this->assertEqual($result->filename, $existing->filename, t('Filename was set to the basename of the source.'));
+    $this->assertEqual($contents, file_get_contents($result->filepath), t('Contents of the file are correct.'));
+    $this->assertEqual($result->filemime, 'application/octet-stream', t('A MIME type was set.'));
+    $this->assertEqual($result->status, FILE_STATUS_PERMANENT, t("The file's status was set to permanent."));
+    $this->assertFileHookCalled('update');
+    $this->assertFileHookCalled('insert', 0);
+    $this->assertFileHookCalled('delete', 0);
+
+    // Verify that the existing file was re-used.
+    $this->assertSameFile($existing, $result);
+
+    // Verify that what was returned is what's in the database.
+    $this->assertFileUnchanged($result, file_load($result->fid, TRUE));
+  }
+
+  /**
+   * Test that file_save_data() fails overwriting an existing file.
+   */
+  function testExistingError() {
+    $contents = $this->randomName(8);
+    $existing = $this->createFile(NULL, $contents);
 
     // Check the overwrite error.
-    $file = file_save_data($contents, 'asdf.txt', FILE_EXISTS_ERROR);
-    $this->assertFalse($file, t("Overwriting a file fails when FILE_EXISTS_ERROR is specified."));
+    $result = file_save_data('asdf', $existing->filepath, FILE_EXISTS_ERROR);
+    $this->assertFalse($result, t('Overwriting a file fails when FILE_EXISTS_ERROR is specified.'));
+    $this->assertEqual($contents, file_get_contents($existing->filepath), t('Contents of existing file were unchagned.'));
+    $this->assertFileHookCalled('insert', 0);
+    $this->assertFileHookCalled('update', 0);
+    $this->assertFileHookCalled('delete', 0);
+
+    // Ensure that the existing file wasn't overwritten.
+    $this->assertFileUnchanged($existing, file_load($existing->fid, TRUE));
   }
 }
 
Index: modules/simpletest/tests/file_test.module
===================================================================
RCS file: /cvs/drupal/drupal/modules/simpletest/tests/file_test.module,v
retrieving revision 1.6
diff -u -p -r1.6 file_test.module
--- modules/simpletest/tests/file_test.module	31 Dec 2008 11:08:47 -0000	1.6
+++ modules/simpletest/tests/file_test.module	8 Jan 2009 04:41:22 -0000
@@ -32,6 +32,16 @@ function _file_test_form(&$form_state) {
     '#type' => 'file',
     '#title' => t('Upload an image'),
   );
+  $form['file_test_replace'] = array(
+    '#type' => 'select',
+    '#title' => t('Replace existing image'),
+    '#options' => array(
+      FILE_EXISTS_RENAME => t('Appends number until name is unique'),
+      FILE_EXISTS_REPLACE => t('Replace the existing file'),
+      FILE_EXISTS_ERROR => t('Fail with an error'),
+    ),
+    '#default_value' => FILE_EXISTS_RENAME,
+  );
   $form['submit'] = array(
     '#type' => 'submit',
     '#value' => t('Submit'),
@@ -44,10 +54,11 @@ function _file_test_form(&$form_state) {
  */
 function _file_test_form_submit(&$form, &$form_state) {
   // Validate the uploaded picture.
-  $file = file_save_upload('file_test_upload', array('file_validate_is_image' => array()));
+  $file = file_save_upload('file_test_upload', array('file_validate_is_image' => array()), FALSE, $form_state['values']['file_test_replace']);
   if ($file) {
     $form_state['values']['file_test_upload'] = $file;
     drupal_set_message(t('File @filepath was uploaded.', array('@filepath' => $file->filepath)));
+    drupal_set_message(t('You WIN!'));
   }
   else {
     drupal_set_message(t('Epic upload FAIL!'), 'error');
