// 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/memory/ptr_util.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::Sequence;
using ::testing::SetArgPointee;
using ::testing::StrictMock;

namespace content {
namespace {

// Struct for SourceStream states verification.
struct SourceStreamTestData {
  SourceStreamTestData(int64_t offset, int64_t bytes_written, bool finished)
      : offset(offset), bytes_written(bytes_written), finished(finished) {}
  int64_t offset;
  int64_t bytes_written;
  bool finished;
};

int64_t GetBuffersLength(const char** buffers, size_t num_buffer) {
  int64_t result = 0;
  for (size_t i = 0; i < num_buffer; ++i)
    result += static_cast<int64_t>(strlen(buffers[i]));
  return result;
}

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_METHOD3(DestinationUpdate, void(
      int64_t, int64_t, const std::vector<DownloadItem::ReceivedSlice>&));
  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 kTestData4[];
  static const char kTestData5[];
  static const char* kTestData6[];
  static const char* kTestData7[];
  static const char* kTestData8[];
  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),
        additional_streams_(
            std::vector<StrictMock<MockByteStreamReader>*>{nullptr, nullptr}),
        bytes_(-1),
        bytes_per_sec_(-1) {}

  ~DownloadFileTest() override {}

  void SetUpdateDownloadInfo(
      int64_t bytes, int64_t bytes_per_sec,
      const std::vector<DownloadItem::ReceivedSlice>& received_slices) {
    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) {
    return CreateDownloadFile(offset, 0, calculate_hash,
                              DownloadItem::ReceivedSlices());
  }

  bool CreateDownloadFile(int offset,
                          int length,
                          bool calculate_hash,
                          const DownloadItem::ReceivedSlices& received_slices) {
    // 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());
    save_info->offset = offset;
    save_info->length = length;

    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),
        DownloadFile::CancelRequestCallback(), received_slices, true);
    loop_runner.Run();

    ::testing::Mock::VerifyAndClearExpectations(input_stream_);
    return result == DOWNLOAD_INTERRUPT_REASON_NONE;
  }

  void DestroyDownloadFile(int offset, bool compare_disk_data = true) {
    EXPECT_FALSE(download_file_->InProgress());

    // Make sure the data has been properly written to disk.
    if (compare_disk_data) {
      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 append data or write from |offset| to the file.
  // Don't actually trigger the callback or do verifications.
  void SetupDataAppend(const char** data_chunks,
                       size_t num_chunks,
                       MockByteStreamReader* stream_reader,
                       ::testing::Sequence s,
                       int64_t offset = -1) {
    DCHECK(stream_reader);
    size_t current_pos = static_cast<size_t>(offset);
    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(*stream_reader, Read(_, _))
          .InSequence(s)
          .WillOnce(DoAll(SetArgPointee<0>(data), SetArgPointee<1>(length),
                          Return(ByteStreamReader::STREAM_HAS_DATA)))
          .RetiresOnSaturation();

      if (offset < 0) {
        // Append data.
        expected_data_ += source_data;
        continue;
      }

      // Write from offset. May fill holes with '\0'.
      size_t new_len = current_pos + length;
      if (new_len > expected_data_.size())
        expected_data_.append(new_len - expected_data_.size(), '\0');
      expected_data_.replace(current_pos, length, source_data);
      current_pos += length;
    }
  }

  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, input_stream_, s1);
    EXPECT_CALL(*input_stream_, Read(_, _))
        .InSequence(s1)
        .WillOnce(Return(ByteStreamReader::STREAM_EMPTY))
        .RetiresOnSaturation();
    sink_callback_.Run();
    VerifyStreamAndSize();
  }

  void SetupFinishStream(DownloadInterruptReason interrupt_reason,
                         MockByteStreamReader* stream_reader,
                         ::testing::Sequence s) {
    EXPECT_CALL(*stream_reader, Read(_, _))
        .InSequence(s)
        .WillOnce(Return(ByteStreamReader::STREAM_COMPLETE))
        .RetiresOnSaturation();
    EXPECT_CALL(*stream_reader, GetStatus())
        .InSequence(s)
        .WillOnce(Return(interrupt_reason))
        .RetiresOnSaturation();
    EXPECT_CALL(*stream_reader, RegisterCallback(_)).RetiresOnSaturation();
  }

  void FinishStream(DownloadInterruptReason interrupt_reason,
                    bool check_observer,
                    const std::string& expected_hash) {
    ::testing::Sequence s1;
    SetupFinishStream(interrupt_reason, input_stream_, 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;
  }

  // Prepare a byte stream to write to the file sink.
  void PrepareStream(StrictMock<MockByteStreamReader>** stream,
                     int64_t offset,
                     bool create_stream,
                     bool will_finish,
                     const char** buffers,
                     size_t num_buffer) {
    if (create_stream)
      *stream = new StrictMock<MockByteStreamReader>();

    // Expectation on MockByteStreamReader for MultipleStreams tests:
    // 1. RegisterCallback: Must called twice. One to set the callback, the
    // other to release the stream.
    // 2. Read: If filled with N buffer, called (N+1) times, where the last Read
    // call doesn't read any data but returns STRAM_COMPLETE.
    // The stream may terminate in the middle and less Read calls are expected.
    // 3. GetStatus: Only called if the stream is completed and last Read call
    // returns STREAM_COMPLETE.
    Sequence seq;
    SetupDataAppend(buffers, num_buffer, *stream, seq, offset);
    if (will_finish)
      SetupFinishStream(DOWNLOAD_INTERRUPT_REASON_NONE, *stream, seq);
  }

  void VerifySourceStreamsStates(const SourceStreamTestData& data) {
    DCHECK(download_file_->source_streams_.find(data.offset) !=
           download_file_->source_streams_.end());
    DownloadFileImpl::SourceStream* stream =
        download_file_->source_streams_[data.offset].get();
    DCHECK(stream);
    EXPECT_EQ(data.offset, stream->offset());
    EXPECT_EQ(data.bytes_written, stream->bytes_written());
    EXPECT_EQ(data.finished, stream->is_finished());
  }

  int64_t TotalBytesReceived() const {
    DCHECK(download_file_);
    return download_file_->TotalBytesReceived();
  }

  std::unique_ptr<StrictMock<MockDownloadDestinationObserver>> observer_;
  base::WeakPtrFactory<DownloadDestinationObserver> observer_factory_;

  // DownloadFile instance we are testing.
  std::unique_ptr<DownloadFileImpl> 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_;

  // Additional streams to test multiple stream write.
  std::vector<StrictMock<MockByteStreamReader>*> additional_streams_;

  // 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::kTestData4[] = "abcdefg";
const char DownloadFileTest::kTestData5[] = "01234";
const char* DownloadFileTest::kTestData6[] = {kTestData1, kTestData2};
const char* DownloadFileTest::kTestData7[] = {kTestData4, kTestData5};
const char* DownloadFileTest::kTestData8[] = {kTestData1, kTestData2,
                                              kTestData4, kTestData5};

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, input_stream_, s1);
  SetupFinishStream(DOWNLOAD_INTERRUPT_REASON_NONE, input_stream_, 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, input_stream_, s1);
  SetupFinishStream(DOWNLOAD_INTERRUPT_REASON_NETWORK_DISCONNECTED,
                    input_stream_, 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);
}

// Tests for concurrent streams handling, used for parallel download.
//
// Activate both streams at the same time.
TEST_F(DownloadFileTest, MutipleStreamsWrite) {
  int64_t stream_0_length = GetBuffersLength(kTestData6, 2);
  int64_t stream_1_length = GetBuffersLength(kTestData7, 2);

  ASSERT_TRUE(CreateDownloadFile(0, stream_0_length, true,
                                 DownloadItem::ReceivedSlices()));

  PrepareStream(&input_stream_, 0, false, true, kTestData6, 2);
  PrepareStream(&additional_streams_[0], stream_0_length, true, true,
                kTestData7, 2);

  EXPECT_CALL(*additional_streams_[0], RegisterCallback(_))
      .RetiresOnSaturation();
  EXPECT_CALL(*(observer_.get()), MockDestinationCompleted(_, _));

  // Activate the streams.
  download_file_->AddByteStream(
      std::unique_ptr<MockByteStreamReader>(additional_streams_[0]),
      stream_0_length, DownloadSaveInfo::kLengthFullContent);
  sink_callback_.Run();
  base::RunLoop().RunUntilIdle();

  SourceStreamTestData stream_data_0(0, stream_0_length, true);
  SourceStreamTestData stream_data_1(stream_0_length, stream_1_length, true);
  VerifySourceStreamsStates(stream_data_0);
  VerifySourceStreamsStates(stream_data_1);
  EXPECT_EQ(stream_0_length + stream_1_length, TotalBytesReceived());

  DestroyDownloadFile(0);
}

// 3 streams write to one sink, the second stream has a limited length.
TEST_F(DownloadFileTest, MutipleStreamsLimitedLength) {
  int64_t stream_0_length = GetBuffersLength(kTestData6, 2);

  // The second stream has a limited length and should be partially written
  // to disk. When we prepare the stream, we fill the stream with 2 full buffer.
  int64_t stream_1_length = GetBuffersLength(kTestData7, 2) - 1;

  // The last stream can't have length limit, it's a half open request, e.g
  // "Range:50-".
  int64_t stream_2_length = GetBuffersLength(kTestData6, 2);

  ASSERT_TRUE(CreateDownloadFile(0, stream_0_length, true,
                                 DownloadItem::ReceivedSlices()));

  PrepareStream(&input_stream_, 0, false, true, kTestData6, 2);
  PrepareStream(&additional_streams_[0], stream_0_length, true, false,
                kTestData7, 2);
  PrepareStream(&additional_streams_[1], stream_0_length + stream_1_length,
                true, true, kTestData6, 2);

  EXPECT_CALL(*additional_streams_[0], RegisterCallback(_))
      .Times(2)
      .RetiresOnSaturation();

  EXPECT_CALL(*additional_streams_[1], RegisterCallback(_))
      .RetiresOnSaturation();

  EXPECT_CALL(*(observer_.get()), MockDestinationCompleted(_, _));

  // Activate all the streams.
  download_file_->AddByteStream(
      std::unique_ptr<MockByteStreamReader>(additional_streams_[0]),
      stream_0_length, stream_1_length);
  download_file_->AddByteStream(
      std::unique_ptr<MockByteStreamReader>(additional_streams_[1]),
      stream_0_length + stream_1_length, DownloadSaveInfo::kLengthFullContent);
  sink_callback_.Run();
  base::RunLoop().RunUntilIdle();

  SourceStreamTestData stream_data_0(0, stream_0_length, true);
  SourceStreamTestData stream_data_1(stream_0_length, stream_1_length, true);
  SourceStreamTestData stream_data_2(stream_0_length + stream_1_length,
                                     stream_2_length, true);

  VerifySourceStreamsStates(stream_data_0);
  VerifySourceStreamsStates(stream_data_1);
  VerifySourceStreamsStates(stream_data_2);

  EXPECT_EQ(stream_0_length + stream_1_length + stream_2_length,
            TotalBytesReceived());

  download_file_->Cancel();
  DestroyDownloadFile(0, false);
}

// Activate and deplete one stream, later add the second stream.
TEST_F(DownloadFileTest, MutipleStreamsFirstStreamWriteAllData) {
  int64_t stream_0_length = GetBuffersLength(kTestData8, 4);

  ASSERT_TRUE(CreateDownloadFile(0, DownloadSaveInfo::kLengthFullContent, true,
                                 DownloadItem::ReceivedSlices()));

  PrepareStream(&input_stream_, 0, false, true, kTestData8, 4);

  EXPECT_CALL(*(observer_.get()), MockDestinationCompleted(_, _));

  sink_callback_.Run();
  base::RunLoop().RunUntilIdle();

  // Add another stream, the file is already closed, so nothing should be
  // called.
  EXPECT_FALSE(download_file_->InProgress());

  additional_streams_[0] = new StrictMock<MockByteStreamReader>();
  download_file_->AddByteStream(
      std::unique_ptr<MockByteStreamReader>(additional_streams_[0]),
      stream_0_length - 1, DownloadSaveInfo::kLengthFullContent);
  base::RunLoop().RunUntilIdle();

  SourceStreamTestData stream_data_0(0, stream_0_length, true);
  SourceStreamTestData stream_data_1(stream_0_length - 1, 0, false);
  VerifySourceStreamsStates(stream_data_0);
  VerifySourceStreamsStates(stream_data_1);
  EXPECT_EQ(stream_0_length, TotalBytesReceived());

  DestroyDownloadFile(0);
}

// While one stream is writing, kick off another stream with an offset that has
// been written by the first one.
TEST_F(DownloadFileTest, SecondStreamStartingOffsetAlreadyWritten) {
  int64_t stream_0_length = GetBuffersLength(kTestData6, 2);

  ASSERT_TRUE(CreateDownloadFile(0, stream_0_length, true,
                                 DownloadItem::ReceivedSlices()));

  Sequence seq;
  SetupDataAppend(kTestData6, 2, input_stream_, seq, 0);

  EXPECT_CALL(*input_stream_, Read(_, _))
      .InSequence(seq)
      .WillOnce(Return(ByteStreamReader::STREAM_EMPTY))
      .RetiresOnSaturation();
  sink_callback_.Run();
  base::RunLoop().RunUntilIdle();

  additional_streams_[0] = new StrictMock<MockByteStreamReader>();
  EXPECT_CALL(*additional_streams_[0], RegisterCallback(_))
      .WillRepeatedly(Invoke(this, &DownloadFileTest::RegisterCallback))
      .RetiresOnSaturation();
  EXPECT_CALL(*additional_streams_[0], Read(_, _))
      .WillOnce(Return(ByteStreamReader::STREAM_EMPTY))
      .RetiresOnSaturation();

  download_file_->AddByteStream(
      std::unique_ptr<MockByteStreamReader>(additional_streams_[0]), 0,
      DownloadSaveInfo::kLengthFullContent);

  // The stream should get terminated and reset the callback.
  EXPECT_TRUE(sink_callback_.is_null());
  download_file_->Cancel();
  DestroyDownloadFile(0, false);
}

}  // namespace content
