text: // Copyright (c) 2012 The Chromium Authors. All rights reserved.
  // Use of this source code is governed by a BSD-style license that can be
  // found in the LICENSE file.
  
  #include <stddef.h>
  #include <stdint.h>
  
  #include <utility>
  #include <vector>
  
  #include "base/files/file.h"
  #include "base/files/file_util.h"
  #include "base/location.h"
  #include "base/run_loop.h"
  #include "base/single_thread_task_runner.h"
  #include "base/strings/string_number_conversions.h"
  #include "base/test/test_file_util.h"
  #include "base/threading/thread_task_runner_handle.h"
  #include "build/build_config.h"
  #include "content/browser/byte_stream.h"
  #include "content/browser/download/download_create_info.h"
  #include "content/browser/download/download_destination_observer.h"
  #include "content/browser/download/download_file_impl.h"
  #include "content/browser/download/download_request_handle.h"
  #include "content/public/browser/download_interrupt_reasons.h"
  #include "content/public/browser/download_manager.h"
  #include "content/public/test/mock_download_manager.h"
  #include "content/public/test/test_browser_thread_bundle.h"
  #include "net/base/file_stream.h"
  #include "net/base/mock_file_stream.h"
  #include "net/base/net_errors.h"
  #include "net/log/net_log_with_source.h"
  #include "testing/gmock/include/gmock/gmock.h"
  #include "testing/gtest/include/gtest/gtest.h"
  
  using ::testing::_;
  using ::testing::AnyNumber;
  using ::testing::DoAll;
  using ::testing::InSequence;
  using ::testing::Return;
  using ::testing::SetArgPointee;
  using ::testing::StrictMock;
  
  namespace content {
  namespace {
  
  std::string GetHexEncodedHashValue(crypto::SecureHash* hash_state) {
    if (!hash_state)
      return std::string();
    std::vector<char> hash_value(hash_state->GetHashLength());
    hash_state->Finish(&hash_value.front(), hash_value.size());
    return base::HexEncode(&hash_value.front(), hash_value.size());
  }
  
  class MockByteStreamReader : public ByteStreamReader {
   public:
    MockByteStreamReader() {}
    ~MockByteStreamReader() {}
  
    // ByteStream functions
    MOCK_METHOD2(Read, ByteStreamReader::StreamState(
        scoped_refptr<net::IOBuffer>*, size_t*));
    MOCK_CONST_METHOD0(GetStatus, int());
    MOCK_METHOD1(RegisterCallback, void(const base::Closure&));
  };
  
  class MockDownloadDestinationObserver : public DownloadDestinationObserver {
   public:
    MOCK_METHOD2(DestinationUpdate, void(int64_t, int64_t));
    void DestinationError(
        DownloadInterruptReason reason,
        int64_t bytes_so_far,
        std::unique_ptr<crypto::SecureHash> hash_state) override {
      MockDestinationError(
          reason, bytes_so_far, GetHexEncodedHashValue(hash_state.get()));
    }
    void DestinationCompleted(
        int64_t total_bytes,
        std::unique_ptr<crypto::SecureHash> hash_state) override {
      MockDestinationCompleted(total_bytes,
                               GetHexEncodedHashValue(hash_state.get()));
    }
  
    MOCK_METHOD3(MockDestinationError,
                 void(DownloadInterruptReason, int64_t, const std::string&));
    MOCK_METHOD2(MockDestinationCompleted, void(int64_t, const std::string&));
  
    // Doesn't override any methods in the base class.  Used to make sure
    // that the last DestinationUpdate before a Destination{Completed,Error}
    // had the right values.
    MOCK_METHOD2(CurrentUpdateStatus, void(int64_t, int64_t));
  };
  
  MATCHER(IsNullCallback, "") { return (arg.is_null()); }
  
  enum DownloadFileRenameMethodType { RENAME_AND_UNIQUIFY, RENAME_AND_ANNOTATE };
  
  // This is a test DownloadFileImpl that has no retry delay and, on Posix,
  // retries renames failed due to ACCESS_DENIED.
  class TestDownloadFileImpl : public DownloadFileImpl {
   public:
    TestDownloadFileImpl(std::unique_ptr<DownloadSaveInfo> save_info,
                         const base::FilePath& default_downloads_directory,
                         std::unique_ptr<ByteStreamReader> stream,
                         const net::NetLogWithSource& net_log,
                         base::WeakPtr<DownloadDestinationObserver> observer)
        : DownloadFileImpl(std::move(save_info),
                           default_downloads_directory,
                           std::move(stream),
                           net_log,
                           observer) {}
  
   protected:
    base::TimeDelta GetRetryDelayForFailedRename(int attempt_count) override {
      return base::TimeDelta::FromMilliseconds(0);
    }
  
  #if !defined(OS_WIN)
    // On Posix, we don't encounter transient errors during renames, except
    // possibly EAGAIN, which is difficult to replicate reliably. So we resort to
    // simulating a transient error using ACCESS_DENIED instead.
    bool ShouldRetryFailedRename(DownloadInterruptReason reason) override {
      return reason == DOWNLOAD_INTERRUPT_REASON_FILE_ACCESS_DENIED;
    }
  #endif
  };
  
  }  // namespace
  
  class DownloadFileTest : public testing::Test {
   public:
    static const char kTestData1[];
    static const char kTestData2[];
    static const char kTestData3[];
    static const char kDataHash[];
    static const char kEmptyHash[];
    static const uint32_t kDummyDownloadId;
    static const int kDummyChildId;
    static const int kDummyRequestId;
  
    DownloadFileTest()
        : observer_(new StrictMock<MockDownloadDestinationObserver>),
          observer_factory_(observer_.get()),
          input_stream_(NULL),
          bytes_(-1),
          bytes_per_sec_(-1) {}
  
    ~DownloadFileTest() override {}
  
    void SetUpdateDownloadInfo(int64_t bytes, int64_t bytes_per_sec) {
      bytes_ = bytes;
      bytes_per_sec_ = bytes_per_sec;
    }
  
    void ConfirmUpdateDownloadInfo() {
      observer_->CurrentUpdateStatus(bytes_, bytes_per_sec_);
    }
  
    void SetUp() override {
      EXPECT_CALL(*(observer_.get()), DestinationUpdate(_, _))
          .Times(AnyNumber())
          .WillRepeatedly(Invoke(this, &DownloadFileTest::SetUpdateDownloadInfo));
    }
  
    // Mock calls to this function are forwarded here.
    void RegisterCallback(const base::Closure& sink_callback) {
      sink_callback_ = sink_callback;
    }
  
    void SetInterruptReasonCallback(const base::Closure& closure,
                                    DownloadInterruptReason* reason_p,
                                    DownloadInterruptReason reason) {
      *reason_p = reason;
      closure.Run();
    }
  
    bool CreateDownloadFile(int offset, bool calculate_hash) {
      // There can be only one.
      DCHECK(!download_file_.get());
  
      input_stream_ = new StrictMock<MockByteStreamReader>();
  
      // TODO: Need to actually create a function that'll set the variables
      // based on the inputs from the callback.
      EXPECT_CALL(*input_stream_, RegisterCallback(_))
          .WillOnce(Invoke(this, &DownloadFileTest::RegisterCallback))
          .RetiresOnSaturation();
  
      std::unique_ptr<DownloadSaveInfo> save_info(new DownloadSaveInfo());
      download_file_.reset(new TestDownloadFileImpl(
          std::move(save_info), base::FilePath(),
          std::unique_ptr<ByteStreamReader>(input_stream_),
          net::NetLogWithSource(), observer_factory_.GetWeakPtr()));
  
      EXPECT_CALL(*input_stream_, Read(_, _))
          .WillOnce(Return(ByteStreamReader::STREAM_EMPTY))
          .RetiresOnSaturation();
  
      base::WeakPtrFactory<DownloadFileTest> weak_ptr_factory(this);
      DownloadInterruptReason result = DOWNLOAD_INTERRUPT_REASON_NONE;
      base::RunLoop loop_runner;
      download_file_->Initialize(base::Bind(
          &DownloadFileTest::SetInterruptReasonCallback,
          weak_ptr_factory.GetWeakPtr(), loop_runner.QuitClosure(), &result));
      loop_runner.Run();
  
      ::testing::Mock::VerifyAndClearExpectations(input_stream_);
      return result == DOWNLOAD_INTERRUPT_REASON_NONE;
    }
  
    void DestroyDownloadFile(int offset) {
      EXPECT_FALSE(download_file_->InProgress());
  
      // Make sure the data has been properly written to disk.
      std::string disk_data;
      EXPECT_TRUE(base::ReadFileToString(download_file_->FullPath(), &disk_data));
      EXPECT_EQ(expected_data_, disk_data);
  
      // Make sure the Browser and File threads outlive the DownloadFile
      // to satisfy thread checks inside it.
      download_file_.reset();
    }
  
    // Setup the stream to do be a data append; don't actually trigger
    // the callback or do verifications.
    void SetupDataAppend(const char **data_chunks, size_t num_chunks,
                         ::testing::Sequence s) {
      DCHECK(input_stream_);
      for (size_t i = 0; i < num_chunks; i++) {
        const char *source_data = data_chunks[i];
        size_t length = strlen(source_data);
        scoped_refptr<net::IOBuffer> data = new net::IOBuffer(length);
        memcpy(data->data(), source_data, length);
        EXPECT_CALL(*input_stream_, Read(_, _))
            .InSequence(s)
            .WillOnce(DoAll(SetArgPointee<0>(data),
                            SetArgPointee<1>(length),
                            Return(ByteStreamReader::STREAM_HAS_DATA)))
            .RetiresOnSaturation();
        expected_data_ += source_data;
      }
    }
  
    void VerifyStreamAndSize() {
      ::testing::Mock::VerifyAndClearExpectations(input_stream_);
      int64_t size;
      EXPECT_TRUE(base::GetFileSize(download_file_->FullPath(), &size));
      EXPECT_EQ(expected_data_.size(), static_cast<size_t>(size));
    }
  
    // TODO(rdsmith): Manage full percentage issues properly.
    void AppendDataToFile(const char **data_chunks, size_t num_chunks) {
      ::testing::Sequence s1;
      SetupDataAppend(data_chunks, num_chunks, s1);
      EXPECT_CALL(*input_stream_, Read(_, _))
          .InSequence(s1)
          .WillOnce(Return(ByteStreamReader::STREAM_EMPTY))
          .RetiresOnSaturation();
      sink_callback_.Run();
      VerifyStreamAndSize();
    }
  
    void SetupFinishStream(DownloadInterruptReason interrupt_reason,
                         ::testing::Sequence s) {
      EXPECT_CALL(*input_stream_, Read(_, _))
          .InSequence(s)
          .WillOnce(Return(ByteStreamReader::STREAM_COMPLETE))
          .RetiresOnSaturation();
      EXPECT_CALL(*input_stream_, GetStatus())
          .InSequence(s)
          .WillOnce(Return(interrupt_reason))
          .RetiresOnSaturation();
      EXPECT_CALL(*input_stream_, RegisterCallback(_))
          .RetiresOnSaturation();
    }
  
    void FinishStream(DownloadInterruptReason interrupt_reason,
                      bool check_observer,
                      const std::string& expected_hash) {
      ::testing::Sequence s1;
      SetupFinishStream(interrupt_reason, s1);
      sink_callback_.Run();
      VerifyStreamAndSize();
      if (check_observer) {
        EXPECT_CALL(*(observer_.get()),
                    MockDestinationCompleted(_, expected_hash));
        base::RunLoop().RunUntilIdle();
        ::testing::Mock::VerifyAndClearExpectations(observer_.get());
        EXPECT_CALL(*(observer_.get()), DestinationUpdate(_, _))
            .Times(AnyNumber())
            .WillRepeatedly(
                Invoke(this, &DownloadFileTest::SetUpdateDownloadInfo));
      }
    }
  
    DownloadInterruptReason RenameAndUniquify(
        const base::FilePath& full_path,
        base::FilePath* result_path_p) {
      return InvokeRenameMethodAndWaitForCallback(
          RENAME_AND_UNIQUIFY, full_path, result_path_p);
    }
  
    DownloadInterruptReason RenameAndAnnotate(
        const base::FilePath& full_path,
        base::FilePath* result_path_p) {
      return InvokeRenameMethodAndWaitForCallback(
          RENAME_AND_ANNOTATE, full_path, result_path_p);
    }
  
    void ExpectPermissionError(DownloadInterruptReason err) {
      EXPECT_TRUE(err == DOWNLOAD_INTERRUPT_REASON_FILE_TRANSIENT_ERROR ||
                  err == DOWNLOAD_INTERRUPT_REASON_FILE_ACCESS_DENIED)
          << "Interrupt reason = " << err;
    }
  
   protected:
    void InvokeRenameMethod(
        DownloadFileRenameMethodType method,
        const base::FilePath& full_path,
        const DownloadFile::RenameCompletionCallback& completion_callback) {
      switch (method) {
        case RENAME_AND_UNIQUIFY:
          download_file_->RenameAndUniquify(full_path, completion_callback);
          break;
  
        case RENAME_AND_ANNOTATE:
          download_file_->RenameAndAnnotate(
              full_path,
              "12345678-ABCD-1234-DCBA-123456789ABC",
              GURL(),
              GURL(),
              completion_callback);
          break;
      }
    }
  
    DownloadInterruptReason InvokeRenameMethodAndWaitForCallback(
        DownloadFileRenameMethodType method,
        const base::FilePath& full_path,
        base::FilePath* result_path_p) {
      DownloadInterruptReason result_reason(DOWNLOAD_INTERRUPT_REASON_NONE);
      base::FilePath result_path;
      base::RunLoop loop_runner;
      DownloadFile::RenameCompletionCallback completion_callback =
          base::Bind(&DownloadFileTest::SetRenameResult,
                     base::Unretained(this),
                     loop_runner.QuitClosure(),
                     &result_reason,
                     result_path_p);
      InvokeRenameMethod(method, full_path, completion_callback);
      loop_runner.Run();
      return result_reason;
    }
  
    std::unique_ptr<StrictMock<MockDownloadDestinationObserver>> observer_;
    base::WeakPtrFactory<DownloadDestinationObserver> observer_factory_;
  
    // DownloadFile instance we are testing.
    std::unique_ptr<DownloadFile> download_file_;
  
    // Stream for sending data into the download file.
    // Owned by download_file_; will be alive for lifetime of download_file_.
    StrictMock<MockByteStreamReader>* input_stream_;
  
    // Sink callback data for stream.
    base::Closure sink_callback_;
  
    // Latest update sent to the observer.
    int64_t bytes_;
    int64_t bytes_per_sec_;
  
   private:
    void SetRenameResult(const base::Closure& closure,
                         DownloadInterruptReason* reason_p,
                         base::FilePath* result_path_p,
                         DownloadInterruptReason reason,
                         const base::FilePath& result_path) {
      if (reason_p)
        *reason_p = reason;
      if (result_path_p)
        *result_path_p = result_path;
      closure.Run();
    }
  
    TestBrowserThreadBundle thread_bundle_;
  
    // Keep track of what data should be saved to the disk file.
    std::string expected_data_;
  };
  
  // DownloadFile::RenameAndAnnotate and DownloadFile::RenameAndUniquify have a
  // considerable amount of functional overlap. In order to re-use test logic, we
  // are going to introduce this value parameterized test fixture. It will take a
  // DownloadFileRenameMethodType value which can be either of the two rename
  // methods.
  class DownloadFileTestWithRename
      : public DownloadFileTest,
        public ::testing::WithParamInterface<DownloadFileRenameMethodType> {
   protected:
    DownloadInterruptReason InvokeSelectedRenameMethod(
        const base::FilePath& full_path,
        base::FilePath* result_path_p) {
      return InvokeRenameMethodAndWaitForCallback(
          GetParam(), full_path, result_path_p);
    }
  };
  
  // And now instantiate all DownloadFileTestWithRename tests using both
  // DownloadFile rename methods. Each test of the form
  // DownloadFileTestWithRename.<FooTest> will be instantiated once with
  // RenameAndAnnotate as the value parameter and once with RenameAndUniquify as
  // the value parameter.
  INSTANTIATE_TEST_CASE_P(DownloadFile,
                          DownloadFileTestWithRename,
                          ::testing::Values(RENAME_AND_ANNOTATE,
                                            RENAME_AND_UNIQUIFY));
  
  const char DownloadFileTest::kTestData1[] =
      "Let's write some data to the file!\n";
  const char DownloadFileTest::kTestData2[] = "Writing more data.\n";
  const char DownloadFileTest::kTestData3[] = "Final line.";
  const char DownloadFileTest::kDataHash[] =
      "CBF68BF10F8003DB86B31343AFAC8C7175BD03FB5FC905650F8C80AF087443A8";
  const char DownloadFileTest::kEmptyHash[] =
      "E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855";
  
  const uint32_t DownloadFileTest::kDummyDownloadId = 23;
  const int DownloadFileTest::kDummyChildId = 3;
  const int DownloadFileTest::kDummyRequestId = 67;
  
  // Rename the file before any data is downloaded, after some has, after it all
  // has, and after it's closed.
  TEST_P(DownloadFileTestWithRename, RenameFileFinal) {
    ASSERT_TRUE(CreateDownloadFile(0, true));
    base::FilePath initial_path(download_file_->FullPath());
    EXPECT_TRUE(base::PathExists(initial_path));
    base::FilePath path_1(initial_path.InsertBeforeExtensionASCII("_1"));
    base::FilePath path_2(initial_path.InsertBeforeExtensionASCII("_2"));
    base::FilePath path_3(initial_path.InsertBeforeExtensionASCII("_3"));
    base::FilePath path_4(initial_path.InsertBeforeExtensionASCII("_4"));
    base::FilePath output_path;
  
    // Rename the file before downloading any data.
    EXPECT_EQ(DOWNLOAD_INTERRUPT_REASON_NONE,
              InvokeSelectedRenameMethod(path_1, &output_path));
    base::FilePath renamed_path = download_file_->FullPath();
    EXPECT_EQ(path_1, renamed_path);
    EXPECT_EQ(path_1, output_path);
  
    // Check the files.
    EXPECT_FALSE(base::PathExists(initial_path));
    EXPECT_TRUE(base::PathExists(path_1));
  
    // Download the data.
    const char* chunks1[] = { kTestData1, kTestData2 };
    AppendDataToFile(chunks1, 2);
  
    // Rename the file after downloading some data.
    EXPECT_EQ(DOWNLOAD_INTERRUPT_REASON_NONE,
              InvokeSelectedRenameMethod(path_2, &output_path));
    renamed_path = download_file_->FullPath();
    EXPECT_EQ(path_2, renamed_path);
    EXPECT_EQ(path_2, output_path);
  
    // Check the files.
    EXPECT_FALSE(base::PathExists(path_1));
    EXPECT_TRUE(base::PathExists(path_2));
  
    const char* chunks2[] = { kTestData3 };
    AppendDataToFile(chunks2, 1);
  
    // Rename the file after downloading all the data.
    EXPECT_EQ(DOWNLOAD_INTERRUPT_REASON_NONE,
              InvokeSelectedRenameMethod(path_3, &output_path));
    renamed_path = download_file_->FullPath();
    EXPECT_EQ(path_3, renamed_path);
    EXPECT_EQ(path_3, output_path);
  
    // Check the files.
    EXPECT_FALSE(base::PathExists(path_2));
    EXPECT_TRUE(base::PathExists(path_3));
  
    FinishStream(DOWNLOAD_INTERRUPT_REASON_NONE, true, kDataHash);
    base::RunLoop().RunUntilIdle();
  
    // Rename the file after downloading all the data and closing the file.
    EXPECT_EQ(DOWNLOAD_INTERRUPT_REASON_NONE,
              InvokeSelectedRenameMethod(path_4, &output_path));
    renamed_path = download_file_->FullPath();
    EXPECT_EQ(path_4, renamed_path);
    EXPECT_EQ(path_4, output_path);
  
    // Check the files.
    EXPECT_FALSE(base::PathExists(path_3));
    EXPECT_TRUE(base::PathExists(path_4));
  
    DestroyDownloadFile(0);
  }
  
  // Test to make sure the rename overwrites when requested. This is separate from
  // the above test because it only applies to RenameAndAnnotate().
  // RenameAndUniquify() doesn't overwrite by design.
  TEST_F(DownloadFileTest, RenameOverwrites) {
    ASSERT_TRUE(CreateDownloadFile(0, true));
    base::FilePath initial_path(download_file_->FullPath());
    EXPECT_TRUE(base::PathExists(initial_path));
    base::FilePath path_1(initial_path.InsertBeforeExtensionASCII("_1"));
  
    ASSERT_FALSE(base::PathExists(path_1));
    static const char file_data[] = "xyzzy";
    ASSERT_EQ(static_cast<int>(sizeof(file_data)),
              base::WriteFile(path_1, file_data, sizeof(file_data)));
    ASSERT_TRUE(base::PathExists(path_1));
  
    base::FilePath new_path;
    EXPECT_EQ(DOWNLOAD_INTERRUPT_REASON_NONE,
              RenameAndAnnotate(path_1, &new_path));
    EXPECT_EQ(path_1.value(), new_path.value());
  
    std::string file_contents;
    ASSERT_TRUE(base::ReadFileToString(new_path, &file_contents));
    EXPECT_NE(std::string(file_data), file_contents);
  
    FinishStream(DOWNLOAD_INTERRUPT_REASON_NONE, true, kEmptyHash);
    base::RunLoop().RunUntilIdle();
    DestroyDownloadFile(0);
  }
  
  // Test to make sure the rename uniquifies if we aren't overwriting
  // and there's a file where we're aiming. As above, not a
  // DownloadFileTestWithRename test because this only applies to
  // RenameAndUniquify().
  TEST_F(DownloadFileTest, RenameUniquifies) {
    ASSERT_TRUE(CreateDownloadFile(0, true));
    base::FilePath initial_path(download_file_->FullPath());
    EXPECT_TRUE(base::PathExists(initial_path));
    base::FilePath path_1(initial_path.InsertBeforeExtensionASCII("_1"));
    base::FilePath path_1_suffixed(path_1.InsertBeforeExtensionASCII(" (1)"));
  
    ASSERT_FALSE(base::PathExists(path_1));
    static const char file_data[] = "xyzzy";
    ASSERT_EQ(static_cast<int>(sizeof(file_data)),
              base::WriteFile(path_1, file_data, sizeof(file_data)));
    ASSERT_TRUE(base::PathExists(path_1));
  
    EXPECT_EQ(DOWNLOAD_INTERRUPT_REASON_NONE, RenameAndUniquify(path_1, NULL));
    EXPECT_TRUE(base::PathExists(path_1_suffixed));
  
    FinishStream(DOWNLOAD_INTERRUPT_REASON_NONE, true, kEmptyHash);
    base::RunLoop().RunUntilIdle();
    DestroyDownloadFile(0);
  }
  
  // Test that RenameAndUniquify doesn't try to uniquify in the case where the
  // target filename is the same as the current filename.
  TEST_F(DownloadFileTest, RenameRecognizesSelfConflict) {
    ASSERT_TRUE(CreateDownloadFile(0, true));
    base::FilePath initial_path(download_file_->FullPath());
    EXPECT_TRUE(base::PathExists(initial_path));
  
    base::FilePath new_path;
    EXPECT_EQ(DOWNLOAD_INTERRUPT_REASON_NONE,
              RenameAndUniquify(initial_path, &new_path));
    EXPECT_TRUE(base::PathExists(initial_path));
  
    FinishStream(DOWNLOAD_INTERRUPT_REASON_NONE, true, kEmptyHash);
    base::RunLoop().RunUntilIdle();
    DestroyDownloadFile(0);
    EXPECT_EQ(initial_path.value(), new_path.value());
  }
  
  // Test to make sure we get the proper error on failure.
  TEST_P(DownloadFileTestWithRename, RenameError) {
    ASSERT_TRUE(CreateDownloadFile(0, true));
    base::FilePath initial_path(download_file_->FullPath());
  
    // Create a subdirectory.
    base::FilePath target_dir(
        initial_path.DirName().Append(FILE_PATH_LITERAL("TargetDir")));
    ASSERT_FALSE(base::DirectoryExists(target_dir));
    ASSERT_TRUE(base::CreateDirectory(target_dir));
    base::FilePath target_path(target_dir.Append(initial_path.BaseName()));
  
    // Targets
    base::FilePath target_path_suffixed(
        target_path.InsertBeforeExtensionASCII(" (1)"));
    ASSERT_FALSE(base::PathExists(target_path));
    ASSERT_FALSE(base::PathExists(target_path_suffixed));
  
    // Make the directory unwritable and try to rename within it.
    {
      base::FilePermissionRestorer restorer(target_dir);
      ASSERT_TRUE(base::MakeFileUnwritable(target_dir));
  
      // Expect nulling out of further processing.
      EXPECT_CALL(*input_stream_, RegisterCallback(IsNullCallback()));
      ExpectPermissionError(InvokeSelectedRenameMethod(target_path, NULL));
      EXPECT_FALSE(base::PathExists(target_path_suffixed));
    }
  
    FinishStream(DOWNLOAD_INTERRUPT_REASON_NONE, true, kEmptyHash);
    base::RunLoop().RunUntilIdle();
    DestroyDownloadFile(0);
  }
  
  namespace {
  
  void TestRenameCompletionCallback(const base::Closure& closure,
                                    bool* did_run_callback,
                                    DownloadInterruptReason interrupt_reason,
                                    const base::FilePath& new_path) {
    EXPECT_EQ(DOWNLOAD_INTERRUPT_REASON_NONE, interrupt_reason);
    *did_run_callback = true;
    closure.Run();
  }
  
  }  // namespace
  
  // Test that the retry logic works. This test assumes that DownloadFileImpl will
  // post tasks to the current message loop (acting as the FILE thread)
  // asynchronously to retry the renames. We will stuff RunLoop::QuitClosures()
  // in between the retry tasks to stagger them and then allow the rename to
  // succeed.
  //
  // Note that there is only one queue of tasks to run, and that is in the tests'
  // base::MessageLoop::current(). Each RunLoop processes that queue until it sees
  // a QuitClosure() targeted at itself, at which point it stops processing.
  TEST_P(DownloadFileTestWithRename, RenameWithErrorRetry) {
    ASSERT_TRUE(CreateDownloadFile(0, true));
    base::FilePath initial_path(download_file_->FullPath());
  
    // Create a subdirectory.
    base::FilePath target_dir(
        initial_path.DirName().Append(FILE_PATH_LITERAL("TargetDir")));
    ASSERT_FALSE(base::DirectoryExists(target_dir));
    ASSERT_TRUE(base::CreateDirectory(target_dir));
    base::FilePath target_path(target_dir.Append(initial_path.BaseName()));
  
    bool did_run_callback = false;
  
    // Each RunLoop can be used the run the MessageLoop until the corresponding
    // QuitClosure() is run. This one is used to produce the QuitClosure() that
    // will be run when the entire rename operation is complete.
    base::RunLoop succeeding_run;
    {
      // (Scope for the base::File or base::FilePermissionRestorer below.)
  #if defined(OS_WIN)
      // On Windows we test with an actual transient error, a sharing violation.
      // The rename will fail because we are holding the file open for READ. On
      // Posix this doesn't cause a failure.
      base::File locked_file(initial_path,
                             base::File::FLAG_OPEN | base::File::FLAG_READ);
      ASSERT_TRUE(locked_file.IsValid());
  #else
      // Simulate a transient failure by revoking write permission for target_dir.
      // The TestDownloadFileImpl class treats this error as transient even though
      // DownloadFileImpl itself doesn't.
      base::FilePermissionRestorer restore_permissions_for(target_dir);
      ASSERT_TRUE(base::MakeFileUnwritable(target_dir));
  #endif
  
      // The Rename() should fail here and enqueue a retry task without invoking
      // the completion callback.
      InvokeRenameMethod(GetParam(),
                         target_path,
                         base::Bind(&TestRenameCompletionCallback,
                                    succeeding_run.QuitClosure(),
                                    &did_run_callback));
      EXPECT_FALSE(did_run_callback);
  
      base::RunLoop first_failing_run;
      // Queue the QuitClosure() on the MessageLoop now. Any tasks queued by the
      // Rename() will be in front of the QuitClosure(). Running the message loop
      // now causes the just the first retry task to be run. The rename still
      // fails, so another retry task would get queued behind the QuitClosure().
      base::ThreadTaskRunnerHandle::Get()->PostTask(
          FROM_HERE, first_failing_run.QuitClosure());
      first_failing_run.Run();
      EXPECT_FALSE(did_run_callback);
  
      // Running another loop should have the same effect as the above as long as
      // kMaxRenameRetries is greater than 2.
      base::RunLoop second_failing_run;
      base::ThreadTaskRunnerHandle::Get()->PostTask(
          FROM_HERE, second_failing_run.QuitClosure());
      second_failing_run.Run();
      EXPECT_FALSE(did_run_callback);
    }
  
    // This time the QuitClosure from succeeding_run should get executed.
    succeeding_run.Run();
    EXPECT_TRUE(did_run_callback);
  
    FinishStream(DOWNLOAD_INTERRUPT_REASON_NONE, true, kEmptyHash);
    base::RunLoop().RunUntilIdle();
    DestroyDownloadFile(0);
  }
  
  // Various tests of the StreamActive method.
  TEST_F(DownloadFileTest, StreamEmptySuccess) {
    ASSERT_TRUE(CreateDownloadFile(0, true));
    base::FilePath initial_path(download_file_->FullPath());
    EXPECT_TRUE(base::PathExists(initial_path));
  
    // Test that calling the sink_callback_ on an empty stream shouldn't
    // do anything.
    AppendDataToFile(NULL, 0);
  
    // Finish the download this way and make sure we see it on the observer.
    FinishStream(DOWNLOAD_INTERRUPT_REASON_NONE, true, kEmptyHash);
    base::RunLoop().RunUntilIdle();
  
    DestroyDownloadFile(0);
  }
  
  TEST_F(DownloadFileTest, StreamEmptyError) {
    ASSERT_TRUE(CreateDownloadFile(0, true));
    base::FilePath initial_path(download_file_->FullPath());
    EXPECT_TRUE(base::PathExists(initial_path));
  
    // Finish the download in error and make sure we see it on the
    // observer.
    EXPECT_CALL(
        *(observer_.get()),
        MockDestinationError(
            DOWNLOAD_INTERRUPT_REASON_NETWORK_DISCONNECTED, 0, kEmptyHash))
        .WillOnce(InvokeWithoutArgs(
            this, &DownloadFileTest::ConfirmUpdateDownloadInfo));
  
    // If this next EXPECT_CALL fails flakily, it's probably a real failure.
    // We'll be getting a stream of UpdateDownload calls from the timer, and
    // the last one may have the correct information even if the failure
    // doesn't produce an update, as the timer update may have triggered at the
    // same time.
    EXPECT_CALL(*(observer_.get()), CurrentUpdateStatus(0, _));
  
    FinishStream(
        DOWNLOAD_INTERRUPT_REASON_NETWORK_DISCONNECTED, false, kEmptyHash);
  
    base::RunLoop().RunUntilIdle();
  
    DestroyDownloadFile(0);
  }
  
  TEST_F(DownloadFileTest, StreamNonEmptySuccess) {
    ASSERT_TRUE(CreateDownloadFile(0, true));
    base::FilePath initial_path(download_file_->FullPath());
    EXPECT_TRUE(base::PathExists(initial_path));
  
    const char* chunks1[] = { kTestData1, kTestData2 };
    ::testing::Sequence s1;
    SetupDataAppend(chunks1, 2, s1);
    SetupFinishStream(DOWNLOAD_INTERRUPT_REASON_NONE, s1);
    EXPECT_CALL(*(observer_.get()), MockDestinationCompleted(_, _));
    sink_callback_.Run();
    VerifyStreamAndSize();
    base::RunLoop().RunUntilIdle();
    DestroyDownloadFile(0);
  }
  
  TEST_F(DownloadFileTest, StreamNonEmptyError) {
    ASSERT_TRUE(CreateDownloadFile(0, true));
    base::FilePath initial_path(download_file_->FullPath());
    EXPECT_TRUE(base::PathExists(initial_path));
  
    const char* chunks1[] = { kTestData1, kTestData2 };
    ::testing::Sequence s1;
    SetupDataAppend(chunks1, 2, s1);
    SetupFinishStream(DOWNLOAD_INTERRUPT_REASON_NETWORK_DISCONNECTED, s1);
  
    EXPECT_CALL(*(observer_.get()),
                MockDestinationError(
                    DOWNLOAD_INTERRUPT_REASON_NETWORK_DISCONNECTED, _, _))
        .WillOnce(InvokeWithoutArgs(
            this, &DownloadFileTest::ConfirmUpdateDownloadInfo));
  
    // If this next EXPECT_CALL fails flakily, it's probably a real failure.
    // We'll be getting a stream of UpdateDownload calls from the timer, and
    // the last one may have the correct information even if the failure
    // doesn't produce an update, as the timer update may have triggered at the
    // same time.
    EXPECT_CALL(*(observer_.get()),
                CurrentUpdateStatus(strlen(kTestData1) + strlen(kTestData2), _));
  
    sink_callback_.Run();
    base::RunLoop().RunUntilIdle();
    VerifyStreamAndSize();
    DestroyDownloadFile(0);
  }
  
  }  // namespace content
  