root/content/browser/download/download_browsertest.cc

/* [<][>][^][v][top][bottom][index][help] */

DEFINITIONS

This source file includes following definitions.
  1. ManagerGoingDown
  2. DownloadManagerForShell
  3. owner_
  4. RenameAndUniquify
  5. RenameAndAnnotate
  6. RenameCallbackWrapper
  7. waiting_
  8. CreateFile
  9. AddRenameCallback
  10. GetAllRenameCallbacks
  11. WaitForSomeCallback
  12. Initialize
  13. GetNumberActiveFiles
  14. GetNumberActiveFilesFromFileThread
  15. CreateFile
  16. ShouldOpenDownload
  17. SetDelayedOpen
  18. GetDelayedCallbacks
  19. CompareToExpectedRecord
  20. OnDownloadUpdated
  21. OnDownloadDestroyed
  22. RemoveObserver
  23. waiting_
  24. ManagerGoingDown
  25. OnDownloadCreated
  26. WaitForFinished
  27. DataReceivedFilter
  28. DownloadCompleteFilter
  29. InitialSizeFilter
  30. GetSafeBufferChunk
  31. SetUpOnMainThread
  32. GetDownloadManagerDelegate
  33. CreateWaiter
  34. CreateInProgressWaiter
  35. CreateInterruptedWaiter
  36. SetupEnsureNoPendingDownloads
  37. EnsureNoPendingDownloads
  38. DownloadAndWait
  39. VerifyFile
  40. StartDownloadAndReturnItem
  41. WaitForData
  42. ReleaseRSTAndConfirmInterruptForResume
  43. ConfirmFileStatusForResume
  44. EnsureNoPendingDownloadJobsOnIO
  45. IN_PROC_BROWSER_TEST_F
  46. IN_PROC_BROWSER_TEST_F
  47. IN_PROC_BROWSER_TEST_F
  48. IN_PROC_BROWSER_TEST_F
  49. IN_PROC_BROWSER_TEST_F
  50. IN_PROC_BROWSER_TEST_F
  51. IN_PROC_BROWSER_TEST_F
  52. IN_PROC_BROWSER_TEST_F
  53. IN_PROC_BROWSER_TEST_F
  54. IN_PROC_BROWSER_TEST_F
  55. IN_PROC_BROWSER_TEST_F
  56. IN_PROC_BROWSER_TEST_F
  57. IN_PROC_BROWSER_TEST_F
  58. IN_PROC_BROWSER_TEST_F
  59. IN_PROC_BROWSER_TEST_F
  60. IN_PROC_BROWSER_TEST_F
  61. IN_PROC_BROWSER_TEST_F
  62. IN_PROC_BROWSER_TEST_F
  63. IN_PROC_BROWSER_TEST_F
  64. IN_PROC_BROWSER_TEST_F

// 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.

// This file contains download browser tests that are known to be runnable
// in a pure content context.  Over time tests should be migrated here.

#include "base/command_line.h"
#include "base/file_util.h"
#include "base/files/file_path.h"
#include "base/files/scoped_temp_dir.h"
#include "base/strings/stringprintf.h"
#include "base/strings/utf_string_conversions.h"
#include "base/threading/platform_thread.h"
#include "base/time/time.h"
#include "content/browser/byte_stream.h"
#include "content/browser/download/download_file_factory.h"
#include "content/browser/download/download_file_impl.h"
#include "content/browser/download/download_item_impl.h"
#include "content/browser/download/download_manager_impl.h"
#include "content/browser/download/download_resource_handler.h"
#include "content/browser/plugin_service_impl.h"
#include "content/browser/web_contents/web_contents_impl.h"
#include "content/public/browser/power_save_blocker.h"
#include "content/public/common/content_switches.h"
#include "content/public/common/webplugininfo.h"
#include "content/public/test/browser_test_utils.h"
#include "content/public/test/content_browser_test.h"
#include "content/public/test/content_browser_test_utils.h"
#include "content/public/test/download_test_observer.h"
#include "content/public/test/test_file_error_injector.h"
#include "content/public/test/test_utils.h"
#include "content/shell/browser/shell.h"
#include "content/shell/browser/shell_browser_context.h"
#include "content/shell/browser/shell_download_manager_delegate.h"
#include "content/shell/browser/shell_network_delegate.h"
#include "content/test/net/url_request_mock_http_job.h"
#include "content/test/net/url_request_slow_download_job.h"
#include "net/test/spawned_test_server/spawned_test_server.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "url/gurl.h"

using ::testing::_;
using ::testing::AllOf;
using ::testing::Field;
using ::testing::InSequence;
using ::testing::Property;
using ::testing::Return;
using ::testing::StrictMock;

namespace content {

namespace {

class MockDownloadItemObserver : public DownloadItem::Observer {
 public:
  MockDownloadItemObserver() {}
  virtual ~MockDownloadItemObserver() {}

  MOCK_METHOD1(OnDownloadUpdated, void(DownloadItem*));
  MOCK_METHOD1(OnDownloadOpened, void(DownloadItem*));
  MOCK_METHOD1(OnDownloadRemoved, void(DownloadItem*));
  MOCK_METHOD1(OnDownloadDestroyed, void(DownloadItem*));
};

class MockDownloadManagerObserver : public DownloadManager::Observer {
 public:
  MockDownloadManagerObserver(DownloadManager* manager) {
    manager_ = manager;
    manager->AddObserver(this);
  }
  virtual ~MockDownloadManagerObserver() {
    if (manager_)
      manager_->RemoveObserver(this);
  }

  MOCK_METHOD2(OnDownloadCreated, void(DownloadManager*, DownloadItem*));
  MOCK_METHOD1(ModelChanged, void(DownloadManager*));
  void ManagerGoingDown(DownloadManager* manager) {
    DCHECK_EQ(manager_, manager);
    MockManagerGoingDown(manager);

    manager_->RemoveObserver(this);
    manager_ = NULL;
  }

  MOCK_METHOD1(MockManagerGoingDown, void(DownloadManager*));
 private:
  DownloadManager* manager_;
};

class DownloadFileWithDelayFactory;

static DownloadManagerImpl* DownloadManagerForShell(Shell* shell) {
  // We're in a content_browsertest; we know that the DownloadManager
  // is a DownloadManagerImpl.
  return static_cast<DownloadManagerImpl*>(
      BrowserContext::GetDownloadManager(
          shell->web_contents()->GetBrowserContext()));
}

class DownloadFileWithDelay : public DownloadFileImpl {
 public:
  DownloadFileWithDelay(
      scoped_ptr<DownloadSaveInfo> save_info,
      const base::FilePath& default_download_directory,
      const GURL& url,
      const GURL& referrer_url,
      bool calculate_hash,
      scoped_ptr<ByteStreamReader> stream,
      const net::BoundNetLog& bound_net_log,
      scoped_ptr<PowerSaveBlocker> power_save_blocker,
      base::WeakPtr<DownloadDestinationObserver> observer,
      base::WeakPtr<DownloadFileWithDelayFactory> owner);

  virtual ~DownloadFileWithDelay();

  // Wraps DownloadFileImpl::Rename* and intercepts the return callback,
  // storing it in the factory that produced this object for later
  // retrieval.
  virtual void RenameAndUniquify(
      const base::FilePath& full_path,
      const RenameCompletionCallback& callback) OVERRIDE;
  virtual void RenameAndAnnotate(
      const base::FilePath& full_path,
      const RenameCompletionCallback& callback) OVERRIDE;

 private:
  static void RenameCallbackWrapper(
      const base::WeakPtr<DownloadFileWithDelayFactory>& factory,
      const RenameCompletionCallback& original_callback,
      DownloadInterruptReason reason,
      const base::FilePath& path);

  // This variable may only be read on the FILE thread, and may only be
  // indirected through (e.g. methods on DownloadFileWithDelayFactory called)
  // on the UI thread.  This is because after construction,
  // DownloadFileWithDelay lives on the file thread, but
  // DownloadFileWithDelayFactory is purely a UI thread object.
  base::WeakPtr<DownloadFileWithDelayFactory> owner_;

  DISALLOW_COPY_AND_ASSIGN(DownloadFileWithDelay);
};

// All routines on this class must be called on the UI thread.
class DownloadFileWithDelayFactory : public DownloadFileFactory {
 public:
  DownloadFileWithDelayFactory();
  virtual ~DownloadFileWithDelayFactory();

  // DownloadFileFactory interface.
  virtual DownloadFile* CreateFile(
      scoped_ptr<DownloadSaveInfo> save_info,
      const base::FilePath& default_download_directory,
      const GURL& url,
      const GURL& referrer_url,
      bool calculate_hash,
      scoped_ptr<ByteStreamReader> stream,
      const net::BoundNetLog& bound_net_log,
      base::WeakPtr<DownloadDestinationObserver> observer) OVERRIDE;

  void AddRenameCallback(base::Closure callback);
  void GetAllRenameCallbacks(std::vector<base::Closure>* results);

  // Do not return until GetAllRenameCallbacks() will return a non-empty list.
  void WaitForSomeCallback();

 private:
  base::WeakPtrFactory<DownloadFileWithDelayFactory> weak_ptr_factory_;
  std::vector<base::Closure> rename_callbacks_;
  bool waiting_;

  DISALLOW_COPY_AND_ASSIGN(DownloadFileWithDelayFactory);
};

DownloadFileWithDelay::DownloadFileWithDelay(
    scoped_ptr<DownloadSaveInfo> save_info,
    const base::FilePath& default_download_directory,
    const GURL& url,
    const GURL& referrer_url,
    bool calculate_hash,
    scoped_ptr<ByteStreamReader> stream,
    const net::BoundNetLog& bound_net_log,
    scoped_ptr<PowerSaveBlocker> power_save_blocker,
    base::WeakPtr<DownloadDestinationObserver> observer,
    base::WeakPtr<DownloadFileWithDelayFactory> owner)
    : DownloadFileImpl(
        save_info.Pass(), default_download_directory, url, referrer_url,
        calculate_hash, stream.Pass(), bound_net_log,
        power_save_blocker.Pass(), observer),
      owner_(owner) {}

DownloadFileWithDelay::~DownloadFileWithDelay() {}

void DownloadFileWithDelay::RenameAndUniquify(
    const base::FilePath& full_path,
    const RenameCompletionCallback& callback) {
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
  DownloadFileImpl::RenameAndUniquify(
      full_path, base::Bind(DownloadFileWithDelay::RenameCallbackWrapper,
                            owner_, callback));
}

void DownloadFileWithDelay::RenameAndAnnotate(
    const base::FilePath& full_path, const RenameCompletionCallback& callback) {
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
  DownloadFileImpl::RenameAndAnnotate(
      full_path, base::Bind(DownloadFileWithDelay::RenameCallbackWrapper,
                            owner_, callback));
}

// static
void DownloadFileWithDelay::RenameCallbackWrapper(
    const base::WeakPtr<DownloadFileWithDelayFactory>& factory,
    const RenameCompletionCallback& original_callback,
    DownloadInterruptReason reason,
    const base::FilePath& path) {
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
  if (!factory)
    return;
  factory->AddRenameCallback(base::Bind(original_callback, reason, path));
}

DownloadFileWithDelayFactory::DownloadFileWithDelayFactory()
    : weak_ptr_factory_(this),
      waiting_(false) {}
DownloadFileWithDelayFactory::~DownloadFileWithDelayFactory() {}

DownloadFile* DownloadFileWithDelayFactory::CreateFile(
    scoped_ptr<DownloadSaveInfo> save_info,
    const base::FilePath& default_download_directory,
    const GURL& url,
    const GURL& referrer_url,
    bool calculate_hash,
    scoped_ptr<ByteStreamReader> stream,
    const net::BoundNetLog& bound_net_log,
    base::WeakPtr<DownloadDestinationObserver> observer) {
  scoped_ptr<PowerSaveBlocker> psb(
      PowerSaveBlocker::Create(
          PowerSaveBlocker::kPowerSaveBlockPreventAppSuspension,
          "Download in progress"));
  return new DownloadFileWithDelay(
      save_info.Pass(), default_download_directory, url, referrer_url,
      calculate_hash, stream.Pass(), bound_net_log,
      psb.Pass(), observer, weak_ptr_factory_.GetWeakPtr());
}

void DownloadFileWithDelayFactory::AddRenameCallback(base::Closure callback) {
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
  rename_callbacks_.push_back(callback);
  if (waiting_)
    base::MessageLoopForUI::current()->Quit();
}

void DownloadFileWithDelayFactory::GetAllRenameCallbacks(
    std::vector<base::Closure>* results) {
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
  results->swap(rename_callbacks_);
}

void DownloadFileWithDelayFactory::WaitForSomeCallback() {
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));

  if (rename_callbacks_.empty()) {
    waiting_ = true;
    RunMessageLoop();
    waiting_ = false;
  }
}

class CountingDownloadFile : public DownloadFileImpl {
 public:
  CountingDownloadFile(
    scoped_ptr<DownloadSaveInfo> save_info,
    const base::FilePath& default_downloads_directory,
    const GURL& url,
    const GURL& referrer_url,
    bool calculate_hash,
    scoped_ptr<ByteStreamReader> stream,
    const net::BoundNetLog& bound_net_log,
    scoped_ptr<PowerSaveBlocker> power_save_blocker,
    base::WeakPtr<DownloadDestinationObserver> observer)
      : DownloadFileImpl(save_info.Pass(), default_downloads_directory,
                         url, referrer_url, calculate_hash,
                         stream.Pass(), bound_net_log,
                         power_save_blocker.Pass(), observer) {}

  virtual ~CountingDownloadFile() {
    DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
    active_files_--;
  }

  virtual void Initialize(const InitializeCallback& callback) OVERRIDE {
    DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
    active_files_++;
    return DownloadFileImpl::Initialize(callback);
  }

  static void GetNumberActiveFiles(int* result) {
    DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
    *result = active_files_;
  }

  // Can be called on any thread, and will block (running message loop)
  // until data is returned.
  static int GetNumberActiveFilesFromFileThread() {
    int result = -1;
    BrowserThread::PostTaskAndReply(
        BrowserThread::FILE,
        FROM_HERE,
        base::Bind(&CountingDownloadFile::GetNumberActiveFiles, &result),
        base::MessageLoop::current()->QuitClosure());
    base::MessageLoop::current()->Run();
    DCHECK_NE(-1, result);
    return result;
  }

 private:
  static int active_files_;
};

int CountingDownloadFile::active_files_ = 0;

class CountingDownloadFileFactory : public DownloadFileFactory {
 public:
  CountingDownloadFileFactory() {}
  virtual ~CountingDownloadFileFactory() {}

  // DownloadFileFactory interface.
  virtual DownloadFile* CreateFile(
    scoped_ptr<DownloadSaveInfo> save_info,
    const base::FilePath& default_downloads_directory,
    const GURL& url,
    const GURL& referrer_url,
    bool calculate_hash,
    scoped_ptr<ByteStreamReader> stream,
    const net::BoundNetLog& bound_net_log,
    base::WeakPtr<DownloadDestinationObserver> observer) OVERRIDE {
    scoped_ptr<PowerSaveBlocker> psb(
        PowerSaveBlocker::Create(
            PowerSaveBlocker::kPowerSaveBlockPreventAppSuspension,
            "Download in progress"));
    return new CountingDownloadFile(
        save_info.Pass(), default_downloads_directory, url, referrer_url,
        calculate_hash, stream.Pass(), bound_net_log,
        psb.Pass(), observer);
  }
};

class TestShellDownloadManagerDelegate : public ShellDownloadManagerDelegate {
 public:
  TestShellDownloadManagerDelegate()
      : delay_download_open_(false) {}
  virtual ~TestShellDownloadManagerDelegate() {}

  virtual bool ShouldOpenDownload(
      DownloadItem* item,
      const DownloadOpenDelayedCallback& callback) OVERRIDE {
    if (delay_download_open_) {
      delayed_callbacks_.push_back(callback);
      return false;
    }
    return true;
  }

  void SetDelayedOpen(bool delay) {
    delay_download_open_ = delay;
  }

  void GetDelayedCallbacks(
      std::vector<DownloadOpenDelayedCallback>* callbacks) {
    callbacks->swap(delayed_callbacks_);
  }
 private:
  bool delay_download_open_;
  std::vector<DownloadOpenDelayedCallback> delayed_callbacks_;
};

// Record all state transitions and byte counts on the observed download.
class RecordingDownloadObserver : DownloadItem::Observer {
 public:
  struct RecordStruct {
    DownloadItem::DownloadState state;
    int bytes_received;
  };

  typedef std::vector<RecordStruct> RecordVector;

  RecordingDownloadObserver(DownloadItem* download)
      : download_(download) {
    last_state_.state = download->GetState();
    last_state_.bytes_received = download->GetReceivedBytes();
    download_->AddObserver(this);
  }

  virtual ~RecordingDownloadObserver() {
    RemoveObserver();
  }

  void CompareToExpectedRecord(const RecordStruct expected[], size_t size) {
    EXPECT_EQ(size, record_.size());
    int min = size > record_.size() ? record_.size() : size;
    for (int i = 0; i < min; ++i) {
      EXPECT_EQ(expected[i].state, record_[i].state) << "Iteration " << i;
      EXPECT_EQ(expected[i].bytes_received, record_[i].bytes_received)
          << "Iteration " << i;
    }
  }

 private:
  virtual void OnDownloadUpdated(DownloadItem* download) OVERRIDE {
    DCHECK_EQ(download_, download);
    DownloadItem::DownloadState state = download->GetState();
    int bytes = download->GetReceivedBytes();
    if (last_state_.state != state || last_state_.bytes_received > bytes) {
      last_state_.state = state;
      last_state_.bytes_received = bytes;
      record_.push_back(last_state_);
    }
  }

  virtual void OnDownloadDestroyed(DownloadItem* download) OVERRIDE {
    DCHECK_EQ(download_, download);
    RemoveObserver();
  }

  void RemoveObserver() {
    if (download_) {
      download_->RemoveObserver(this);
      download_ = NULL;
    }
  }

  DownloadItem* download_;
  RecordStruct last_state_;
  RecordVector record_;
};

// Get the next created download.
class DownloadCreateObserver : DownloadManager::Observer {
 public:
  DownloadCreateObserver(DownloadManager* manager)
      : manager_(manager),
        item_(NULL),
        waiting_(false) {
    manager_->AddObserver(this);
  }

  virtual ~DownloadCreateObserver() {
    if (manager_)
      manager_->RemoveObserver(this);
    manager_ = NULL;
  }

  virtual void ManagerGoingDown(DownloadManager* manager) OVERRIDE {
    DCHECK_EQ(manager_, manager);
    manager_->RemoveObserver(this);
    manager_ = NULL;
  }

  virtual void OnDownloadCreated(DownloadManager* manager,
                                 DownloadItem* download) OVERRIDE {
    if (!item_)
      item_ = download;

    if (waiting_)
      base::MessageLoopForUI::current()->Quit();
  }

  DownloadItem* WaitForFinished() {
    DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
    if (!item_) {
      waiting_ = true;
      RunMessageLoop();
      waiting_ = false;
    }
    return item_;
  }

 private:
  DownloadManager* manager_;
  DownloadItem* item_;
  bool waiting_;
};


// Filter for waiting for a certain number of bytes.
bool DataReceivedFilter(int number_of_bytes, DownloadItem* download) {
  return download->GetReceivedBytes() >= number_of_bytes;
}

// Filter for download completion.
bool DownloadCompleteFilter(DownloadItem* download) {
  return download->GetState() == DownloadItem::COMPLETE;
}

// Filter for saving the size of the download when the first IN_PROGRESS
// is hit.
bool InitialSizeFilter(int* download_size, DownloadItem* download) {
  if (download->GetState() != DownloadItem::IN_PROGRESS)
    return false;

  *download_size = download->GetReceivedBytes();
  return true;
}

}  // namespace

class DownloadContentTest : public ContentBrowserTest {
 protected:
  // An initial send from a website of at least this size will not be
  // help up by buffering in the underlying downloads ByteStream data
  // transfer.  This is important because on resumption tests we wait
  // until we've gotten the data we expect before allowing the test server
  // to send its reset, to get around hard close semantics on the Windows
  // socket layer implementation.
  int GetSafeBufferChunk() const {
    return (DownloadResourceHandler::kDownloadByteStreamSize /
       ByteStreamWriter::kFractionBufferBeforeSending) + 1;
  }

  virtual void SetUpOnMainThread() OVERRIDE {
    ASSERT_TRUE(downloads_directory_.CreateUniqueTempDir());

    test_delegate_.reset(new TestShellDownloadManagerDelegate());
    test_delegate_->SetDownloadBehaviorForTesting(downloads_directory_.path());
    DownloadManager* manager = DownloadManagerForShell(shell());
    manager->GetDelegate()->Shutdown();
    manager->SetDelegate(test_delegate_.get());
    test_delegate_->SetDownloadManager(manager);

    BrowserThread::PostTask(
        BrowserThread::IO, FROM_HERE,
        base::Bind(&URLRequestSlowDownloadJob::AddUrlHandler));
    base::FilePath mock_base(GetTestFilePath("download", ""));
    BrowserThread::PostTask(
        BrowserThread::IO, FROM_HERE,
        base::Bind(&URLRequestMockHTTPJob::AddUrlHandler, mock_base));
  }

  TestShellDownloadManagerDelegate* GetDownloadManagerDelegate() {
    return test_delegate_.get();
  }

  // Create a DownloadTestObserverTerminal that will wait for the
  // specified number of downloads to finish.
  DownloadTestObserver* CreateWaiter(
      Shell* shell, int num_downloads) {
    DownloadManager* download_manager = DownloadManagerForShell(shell);
    return new DownloadTestObserverTerminal(download_manager, num_downloads,
        DownloadTestObserver::ON_DANGEROUS_DOWNLOAD_FAIL);
  }

  // Create a DownloadTestObserverInProgress that will wait for the
  // specified number of downloads to start.
  DownloadCreateObserver* CreateInProgressWaiter(
      Shell* shell, int num_downloads) {
    DownloadManager* download_manager = DownloadManagerForShell(shell);
    return new DownloadCreateObserver(download_manager);
  }

  DownloadTestObserver* CreateInterruptedWaiter(
      Shell* shell, int num_downloads) {
    DownloadManager* download_manager = DownloadManagerForShell(shell);
    return new DownloadTestObserverInterrupted(download_manager, num_downloads,
        DownloadTestObserver::ON_DANGEROUS_DOWNLOAD_FAIL);
  }

  // Note: Cannot be used with other alternative DownloadFileFactorys
  void SetupEnsureNoPendingDownloads() {
    DownloadManagerForShell(shell())->SetDownloadFileFactoryForTesting(
        scoped_ptr<DownloadFileFactory>(
            new CountingDownloadFileFactory()).Pass());
  }

  bool EnsureNoPendingDownloads() {
    bool result = true;
    BrowserThread::PostTask(
        BrowserThread::IO, FROM_HERE,
        base::Bind(&EnsureNoPendingDownloadJobsOnIO, &result));
    base::MessageLoop::current()->Run();
    return result &&
           (CountingDownloadFile::GetNumberActiveFilesFromFileThread() == 0);
  }

  void DownloadAndWait(Shell* shell, const GURL& url,
                       DownloadItem::DownloadState expected_terminal_state) {
    scoped_ptr<DownloadTestObserver> observer(CreateWaiter(shell, 1));
    NavigateToURL(shell, url);
    observer->WaitForFinished();
    EXPECT_EQ(1u, observer->NumDownloadsSeenInState(expected_terminal_state));
  }

  // Checks that |path| is has |file_size| bytes, and matches the |value|
  // string.
  bool VerifyFile(const base::FilePath& path,
                  const std::string& value,
                  const int64 file_size) {
    std::string file_contents;

    bool read = base::ReadFileToString(path, &file_contents);
    EXPECT_TRUE(read) << "Failed reading file: " << path.value() << std::endl;
    if (!read)
      return false;  // Couldn't read the file.

    // Note: we don't handle really large files (more than size_t can hold)
    // so we will fail in that case.
    size_t expected_size = static_cast<size_t>(file_size);

    // Check the size.
    EXPECT_EQ(expected_size, file_contents.size());
    if (expected_size != file_contents.size())
      return false;

    // Check the contents.
    EXPECT_EQ(value, file_contents);
    if (memcmp(file_contents.c_str(), value.c_str(), expected_size) != 0)
      return false;

    return true;
  }

  // Start a download and return the item.
  DownloadItem* StartDownloadAndReturnItem(GURL url) {
    scoped_ptr<DownloadCreateObserver> observer(
        CreateInProgressWaiter(shell(), 1));
    NavigateToURL(shell(), url);
    observer->WaitForFinished();
    std::vector<DownloadItem*> downloads;
    DownloadManagerForShell(shell())->GetAllDownloads(&downloads);
    EXPECT_EQ(1u, downloads.size());
    if (1u != downloads.size())
      return NULL;
    return downloads[0];
  }

  // Wait for data
  void WaitForData(DownloadItem* download, int size) {
    DownloadUpdatedObserver data_observer(
        download, base::Bind(&DataReceivedFilter, size));
    data_observer.WaitForEvent();
    ASSERT_EQ(size, download->GetReceivedBytes());
    ASSERT_EQ(DownloadItem::IN_PROGRESS, download->GetState());
  }

  // Tell the test server to release a pending RST and confirm
  // that the interrupt is received properly (for download resumption
  // testing).
  void ReleaseRSTAndConfirmInterruptForResume(DownloadItem* download) {
    scoped_ptr<DownloadTestObserver> rst_observer(
        CreateInterruptedWaiter(shell(), 1));
    NavigateToURL(shell(), test_server()->GetURL("download-finish"));
    rst_observer->WaitForFinished();
    EXPECT_EQ(DownloadItem::INTERRUPTED, download->GetState());
  }

  // Confirm file status expected for the given location in a stream
  // provided by the resume test server.
  void ConfirmFileStatusForResume(
      DownloadItem* download, bool file_exists,
      int received_bytes, int total_bytes,
      const base::FilePath& expected_filename) {
    // expected_filename is only known if the file exists.
    ASSERT_EQ(file_exists, !expected_filename.empty());
    EXPECT_EQ(received_bytes, download->GetReceivedBytes());
    EXPECT_EQ(total_bytes, download->GetTotalBytes());
    EXPECT_EQ(expected_filename.value(),
              download->GetFullPath().BaseName().value());
    EXPECT_EQ(file_exists,
              (!download->GetFullPath().empty() &&
               base::PathExists(download->GetFullPath())));

    if (file_exists) {
      std::string file_contents;
      EXPECT_TRUE(base::ReadFileToString(
          download->GetFullPath(), &file_contents));

      ASSERT_EQ(static_cast<size_t>(received_bytes), file_contents.size());
      for (int i = 0; i < received_bytes; ++i) {
        EXPECT_EQ(static_cast<char>((i * 2 + 15) % 256), file_contents[i])
            << "File contents diverged at position " << i
            << " for " << expected_filename.value();

        if (static_cast<char>((i * 2 + 15) % 256) != file_contents[i])
          return;
      }
    }
  }

 private:
  static void EnsureNoPendingDownloadJobsOnIO(bool* result) {
    if (URLRequestSlowDownloadJob::NumberOutstandingRequests())
      *result = false;
    BrowserThread::PostTask(
        BrowserThread::UI, FROM_HERE, base::MessageLoop::QuitClosure());
  }

  // Location of the downloads directory for these tests
  base::ScopedTempDir downloads_directory_;
  scoped_ptr<TestShellDownloadManagerDelegate> test_delegate_;
};

IN_PROC_BROWSER_TEST_F(DownloadContentTest, DownloadCancelled) {
  SetupEnsureNoPendingDownloads();

  // Create a download, wait until it's started, and confirm
  // we're in the expected state.
  scoped_ptr<DownloadCreateObserver> observer(
      CreateInProgressWaiter(shell(), 1));
  NavigateToURL(shell(), GURL(URLRequestSlowDownloadJob::kUnknownSizeUrl));
  observer->WaitForFinished();

  std::vector<DownloadItem*> downloads;
  DownloadManagerForShell(shell())->GetAllDownloads(&downloads);
  ASSERT_EQ(1u, downloads.size());
  ASSERT_EQ(DownloadItem::IN_PROGRESS, downloads[0]->GetState());

  // Cancel the download and wait for download system quiesce.
  downloads[0]->Cancel(true);
  scoped_refptr<DownloadTestFlushObserver> flush_observer(
      new DownloadTestFlushObserver(DownloadManagerForShell(shell())));
  flush_observer->WaitForFlush();

  // Get the important info from other threads and check it.
  EXPECT_TRUE(EnsureNoPendingDownloads());
}

// Check that downloading multiple (in this case, 2) files does not result in
// corrupted files.
IN_PROC_BROWSER_TEST_F(DownloadContentTest, MultiDownload) {
  SetupEnsureNoPendingDownloads();

  // Create a download, wait until it's started, and confirm
  // we're in the expected state.
  scoped_ptr<DownloadCreateObserver> observer1(
      CreateInProgressWaiter(shell(), 1));
  NavigateToURL(shell(), GURL(URLRequestSlowDownloadJob::kUnknownSizeUrl));
  observer1->WaitForFinished();

  std::vector<DownloadItem*> downloads;
  DownloadManagerForShell(shell())->GetAllDownloads(&downloads);
  ASSERT_EQ(1u, downloads.size());
  ASSERT_EQ(DownloadItem::IN_PROGRESS, downloads[0]->GetState());
  DownloadItem* download1 = downloads[0];  // The only download.

  // Start the second download and wait until it's done.
  base::FilePath file(FILE_PATH_LITERAL("download-test.lib"));
  GURL url(URLRequestMockHTTPJob::GetMockUrl(file));
  // Download the file and wait.
  DownloadAndWait(shell(), url, DownloadItem::COMPLETE);

  // Should now have 2 items on the manager.
  downloads.clear();
  DownloadManagerForShell(shell())->GetAllDownloads(&downloads);
  ASSERT_EQ(2u, downloads.size());
  // We don't know the order of the downloads.
  DownloadItem* download2 = downloads[(download1 == downloads[0]) ? 1 : 0];

  ASSERT_EQ(DownloadItem::IN_PROGRESS, download1->GetState());
  ASSERT_EQ(DownloadItem::COMPLETE, download2->GetState());

  // Allow the first request to finish.
  scoped_ptr<DownloadTestObserver> observer2(CreateWaiter(shell(), 1));
  NavigateToURL(shell(), GURL(URLRequestSlowDownloadJob::kFinishDownloadUrl));
  observer2->WaitForFinished();  // Wait for the third request.
  EXPECT_EQ(1u, observer2->NumDownloadsSeenInState(DownloadItem::COMPLETE));

  // Get the important info from other threads and check it.
  EXPECT_TRUE(EnsureNoPendingDownloads());

  // The |DownloadItem|s should now be done and have the final file names.
  // Verify that the files have the expected data and size.
  // |file1| should be full of '*'s, and |file2| should be the same as the
  // source file.
  base::FilePath file1(download1->GetTargetFilePath());
  size_t file_size1 = URLRequestSlowDownloadJob::kFirstDownloadSize +
                      URLRequestSlowDownloadJob::kSecondDownloadSize;
  std::string expected_contents(file_size1, '*');
  ASSERT_TRUE(VerifyFile(file1, expected_contents, file_size1));

  base::FilePath file2(download2->GetTargetFilePath());
  ASSERT_TRUE(base::ContentsEqual(
      file2, GetTestFilePath("download", "download-test.lib")));
}

#if defined(ENABLE_PLUGINS)
// Content served with a MIME type of application/octet-stream should be
// downloaded even when a plugin can be found that handles the file type.
IN_PROC_BROWSER_TEST_F(DownloadContentTest, DownloadOctetStream) {
  const base::FilePath::CharType kTestFilePath[] =
      FILE_PATH_LITERAL("octet-stream.abc");
  const char kTestPluginName[] = "TestPlugin";
  const char kTestMimeType[] = "application/x-test-mime-type";
  const char kTestFileType[] = "abc";

  WebPluginInfo plugin_info;
  plugin_info.name = base::ASCIIToUTF16(kTestPluginName);
  plugin_info.mime_types.push_back(
      WebPluginMimeType(kTestMimeType, kTestFileType, ""));
  PluginServiceImpl::GetInstance()->RegisterInternalPlugin(plugin_info, false);

  // The following is served with a Content-Type of application/octet-stream.
  GURL url(URLRequestMockHTTPJob::GetMockUrl(base::FilePath(kTestFilePath)));
  DownloadAndWait(shell(), url, DownloadItem::COMPLETE);
}
#endif

// Try to cancel just before we release the download file, by delaying final
// rename callback.
IN_PROC_BROWSER_TEST_F(DownloadContentTest, CancelAtFinalRename) {
  // Setup new factory.
  DownloadFileWithDelayFactory* file_factory =
      new DownloadFileWithDelayFactory();
  DownloadManagerImpl* download_manager(DownloadManagerForShell(shell()));
  download_manager->SetDownloadFileFactoryForTesting(
      scoped_ptr<DownloadFileFactory>(file_factory).Pass());

  // Create a download
  base::FilePath file(FILE_PATH_LITERAL("download-test.lib"));
  NavigateToURL(shell(), URLRequestMockHTTPJob::GetMockUrl(file));

  // Wait until the first (intermediate file) rename and execute the callback.
  file_factory->WaitForSomeCallback();
  std::vector<base::Closure> callbacks;
  file_factory->GetAllRenameCallbacks(&callbacks);
  ASSERT_EQ(1u, callbacks.size());
  callbacks[0].Run();
  callbacks.clear();

  // Wait until the second (final) rename callback is posted.
  file_factory->WaitForSomeCallback();
  file_factory->GetAllRenameCallbacks(&callbacks);
  ASSERT_EQ(1u, callbacks.size());

  // Cancel it.
  std::vector<DownloadItem*> items;
  download_manager->GetAllDownloads(&items);
  ASSERT_EQ(1u, items.size());
  items[0]->Cancel(true);
  RunAllPendingInMessageLoop();

  // Check state.
  EXPECT_EQ(DownloadItem::CANCELLED, items[0]->GetState());

  // Run final rename callback.
  callbacks[0].Run();
  callbacks.clear();

  // Check state.
  EXPECT_EQ(DownloadItem::CANCELLED, items[0]->GetState());
}

// Try to cancel just after we release the download file, by delaying
// in ShouldOpenDownload.
IN_PROC_BROWSER_TEST_F(DownloadContentTest, CancelAtRelease) {
  DownloadManagerImpl* download_manager(DownloadManagerForShell(shell()));

  // Mark delegate for delayed open.
  GetDownloadManagerDelegate()->SetDelayedOpen(true);

  // Setup new factory.
  DownloadFileWithDelayFactory* file_factory =
      new DownloadFileWithDelayFactory();
  download_manager->SetDownloadFileFactoryForTesting(
      scoped_ptr<DownloadFileFactory>(file_factory).Pass());

  // Create a download
  base::FilePath file(FILE_PATH_LITERAL("download-test.lib"));
  NavigateToURL(shell(), URLRequestMockHTTPJob::GetMockUrl(file));

  // Wait until the first (intermediate file) rename and execute the callback.
  file_factory->WaitForSomeCallback();
  std::vector<base::Closure> callbacks;
  file_factory->GetAllRenameCallbacks(&callbacks);
  ASSERT_EQ(1u, callbacks.size());
  callbacks[0].Run();
  callbacks.clear();

  // Wait until the second (final) rename callback is posted.
  file_factory->WaitForSomeCallback();
  file_factory->GetAllRenameCallbacks(&callbacks);
  ASSERT_EQ(1u, callbacks.size());

  // Call it.
  callbacks[0].Run();
  callbacks.clear();

  // Confirm download still IN_PROGRESS (internal state COMPLETING).
  std::vector<DownloadItem*> items;
  download_manager->GetAllDownloads(&items);
  EXPECT_EQ(DownloadItem::IN_PROGRESS, items[0]->GetState());

  // Cancel the download; confirm cancel fails.
  ASSERT_EQ(1u, items.size());
  items[0]->Cancel(true);
  EXPECT_EQ(DownloadItem::IN_PROGRESS, items[0]->GetState());

  // Need to complete open test.
  std::vector<DownloadOpenDelayedCallback> delayed_callbacks;
  GetDownloadManagerDelegate()->GetDelayedCallbacks(
      &delayed_callbacks);
  ASSERT_EQ(1u, delayed_callbacks.size());
  delayed_callbacks[0].Run(true);

  // *Now* the download should be complete.
  EXPECT_EQ(DownloadItem::COMPLETE, items[0]->GetState());
}

// Try to shutdown with a download in progress to make sure shutdown path
// works properly.
IN_PROC_BROWSER_TEST_F(DownloadContentTest, ShutdownInProgress) {
  // Create a download that won't complete.
  scoped_ptr<DownloadCreateObserver> observer(
      CreateInProgressWaiter(shell(), 1));
  NavigateToURL(shell(), GURL(URLRequestSlowDownloadJob::kUnknownSizeUrl));
  observer->WaitForFinished();

  // Get the item.
  std::vector<DownloadItem*> items;
  DownloadManagerForShell(shell())->GetAllDownloads(&items);
  ASSERT_EQ(1u, items.size());
  EXPECT_EQ(DownloadItem::IN_PROGRESS, items[0]->GetState());

  // Shutdown the download manager and make sure we get the right
  // notifications in the right order.
  StrictMock<MockDownloadItemObserver> item_observer;
  items[0]->AddObserver(&item_observer);
  MockDownloadManagerObserver manager_observer(
      DownloadManagerForShell(shell()));
  // Don't care about ModelChanged() events.
  EXPECT_CALL(manager_observer, ModelChanged(_))
      .WillRepeatedly(Return());
  {
    InSequence notifications;

    EXPECT_CALL(manager_observer, MockManagerGoingDown(
        DownloadManagerForShell(shell())))
        .WillOnce(Return());
    EXPECT_CALL(item_observer, OnDownloadUpdated(
        AllOf(items[0],
              Property(&DownloadItem::GetState, DownloadItem::CANCELLED))))
        .WillOnce(Return());
    EXPECT_CALL(item_observer, OnDownloadDestroyed(items[0]))
        .WillOnce(Return());
  }

  // See http://crbug.com/324525.  If we have a refcount release/post task
  // race, the second post will stall the IO thread long enough so that we'll
  // lose the race and crash.  The first stall is just to give the UI thread
  // a chance to get the second stall onto the IO thread queue after the cancel
  // message created by Shutdown and before the notification callback
  // created by the IO thread in canceling the request.
  BrowserThread::PostTask(BrowserThread::IO, FROM_HERE,
                          base::Bind(&base::PlatformThread::Sleep,
                                     base::TimeDelta::FromMilliseconds(25)));
  DownloadManagerForShell(shell())->Shutdown();
  BrowserThread::PostTask(BrowserThread::IO, FROM_HERE,
                          base::Bind(&base::PlatformThread::Sleep,
                                     base::TimeDelta::FromMilliseconds(25)));
  items.clear();
}

// Try to shutdown just after we release the download file, by delaying
// release.
IN_PROC_BROWSER_TEST_F(DownloadContentTest, ShutdownAtRelease) {
  DownloadManagerImpl* download_manager(DownloadManagerForShell(shell()));

  // Mark delegate for delayed open.
  GetDownloadManagerDelegate()->SetDelayedOpen(true);

  // Setup new factory.
  DownloadFileWithDelayFactory* file_factory =
      new DownloadFileWithDelayFactory();
  download_manager->SetDownloadFileFactoryForTesting(
      scoped_ptr<DownloadFileFactory>(file_factory).Pass());

  // Create a download
  base::FilePath file(FILE_PATH_LITERAL("download-test.lib"));
  NavigateToURL(shell(), URLRequestMockHTTPJob::GetMockUrl(file));

  // Wait until the first (intermediate file) rename and execute the callback.
  file_factory->WaitForSomeCallback();
  std::vector<base::Closure> callbacks;
  file_factory->GetAllRenameCallbacks(&callbacks);
  ASSERT_EQ(1u, callbacks.size());
  callbacks[0].Run();
  callbacks.clear();

  // Wait until the second (final) rename callback is posted.
  file_factory->WaitForSomeCallback();
  file_factory->GetAllRenameCallbacks(&callbacks);
  ASSERT_EQ(1u, callbacks.size());

  // Call it.
  callbacks[0].Run();
  callbacks.clear();

  // Confirm download isn't complete yet.
  std::vector<DownloadItem*> items;
  DownloadManagerForShell(shell())->GetAllDownloads(&items);
  EXPECT_EQ(DownloadItem::IN_PROGRESS, items[0]->GetState());

  // Cancel the download; confirm cancel fails anyway.
  ASSERT_EQ(1u, items.size());
  items[0]->Cancel(true);
  EXPECT_EQ(DownloadItem::IN_PROGRESS, items[0]->GetState());
  RunAllPendingInMessageLoop();
  EXPECT_EQ(DownloadItem::IN_PROGRESS, items[0]->GetState());

  MockDownloadItemObserver observer;
  items[0]->AddObserver(&observer);
  EXPECT_CALL(observer, OnDownloadDestroyed(items[0]));

  // Shutdown the download manager.  Mostly this is confirming a lack of
  // crashes.
  DownloadManagerForShell(shell())->Shutdown();
}

IN_PROC_BROWSER_TEST_F(DownloadContentTest, ResumeInterruptedDownload) {
  CommandLine::ForCurrentProcess()->AppendSwitch(
      switches::kEnableDownloadResumption);
  ASSERT_TRUE(test_server()->Start());

  GURL url = test_server()->GetURL(
      base::StringPrintf("rangereset?size=%d&rst_boundary=%d",
                   GetSafeBufferChunk() * 3, GetSafeBufferChunk()));

  MockDownloadManagerObserver dm_observer(DownloadManagerForShell(shell()));
  EXPECT_CALL(dm_observer, OnDownloadCreated(_,_)).Times(1);

  DownloadItem* download(StartDownloadAndReturnItem(url));
  WaitForData(download, GetSafeBufferChunk());
  ::testing::Mock::VerifyAndClearExpectations(&dm_observer);

  // Confirm resumption while in progress doesn't do anything.
  download->Resume();
  ASSERT_EQ(GetSafeBufferChunk(), download->GetReceivedBytes());
  ASSERT_EQ(DownloadItem::IN_PROGRESS, download->GetState());

  // Tell the server to send the RST and confirm the interrupt happens.
  ReleaseRSTAndConfirmInterruptForResume(download);
  ConfirmFileStatusForResume(
      download, true, GetSafeBufferChunk(), GetSafeBufferChunk() * 3,
      base::FilePath(FILE_PATH_LITERAL("rangereset.crdownload")));

  // Resume, confirming received bytes on resumption is correct.
  // Make sure no creation calls are included.
  EXPECT_CALL(dm_observer, OnDownloadCreated(_,_)).Times(0);
  int initial_size = 0;
  DownloadUpdatedObserver initial_size_observer(
      download, base::Bind(&InitialSizeFilter, &initial_size));
  download->Resume();
  initial_size_observer.WaitForEvent();
  EXPECT_EQ(GetSafeBufferChunk(), initial_size);
  ::testing::Mock::VerifyAndClearExpectations(&dm_observer);

  // and wait for expected data.
  WaitForData(download, GetSafeBufferChunk() * 2);

  // Tell the server to send the RST and confirm the interrupt happens.
  ReleaseRSTAndConfirmInterruptForResume(download);
  ConfirmFileStatusForResume(
      download, true, GetSafeBufferChunk() * 2, GetSafeBufferChunk() * 3,
      base::FilePath(FILE_PATH_LITERAL("rangereset.crdownload")));

  // Resume and wait for completion.
  DownloadUpdatedObserver completion_observer(
      download, base::Bind(DownloadCompleteFilter));
  download->Resume();
  completion_observer.WaitForEvent();

  ConfirmFileStatusForResume(
      download, true, GetSafeBufferChunk() * 3, GetSafeBufferChunk() * 3,
      base::FilePath(FILE_PATH_LITERAL("rangereset")));

  // Confirm resumption while complete doesn't do anything.
  download->Resume();
  ASSERT_EQ(GetSafeBufferChunk() * 3, download->GetReceivedBytes());
  ASSERT_EQ(DownloadItem::COMPLETE, download->GetState());
  RunAllPendingInMessageLoop();
  ASSERT_EQ(GetSafeBufferChunk() * 3, download->GetReceivedBytes());
  ASSERT_EQ(DownloadItem::COMPLETE, download->GetState());
}

// Confirm restart fallback happens if a range request is bounced.
IN_PROC_BROWSER_TEST_F(DownloadContentTest, ResumeInterruptedDownloadNoRange) {
  CommandLine::ForCurrentProcess()->AppendSwitch(
      switches::kEnableDownloadResumption);
  ASSERT_TRUE(test_server()->Start());

  // Auto-restart if server doesn't handle ranges.
  GURL url = test_server()->GetURL(
      base::StringPrintf(
          // First download hits an RST, rest don't, no ranges.
          "rangereset?size=%d&rst_boundary=%d&"
          "token=NoRange&rst_limit=1&bounce_range",
          GetSafeBufferChunk() * 3, GetSafeBufferChunk()));

  // Start the download and wait for first data chunk.
  DownloadItem* download(StartDownloadAndReturnItem(url));
  WaitForData(download, GetSafeBufferChunk());

  RecordingDownloadObserver recorder(download);

  ReleaseRSTAndConfirmInterruptForResume(download);
  ConfirmFileStatusForResume(
      download, true, GetSafeBufferChunk(), GetSafeBufferChunk() * 3,
      base::FilePath(FILE_PATH_LITERAL("rangereset.crdownload")));

  DownloadUpdatedObserver completion_observer(
      download, base::Bind(DownloadCompleteFilter));
  download->Resume();
  completion_observer.WaitForEvent();

  ConfirmFileStatusForResume(
      download, true, GetSafeBufferChunk() * 3, GetSafeBufferChunk() * 3,
      base::FilePath(FILE_PATH_LITERAL("rangereset")));

  static const RecordingDownloadObserver::RecordStruct expected_record[] = {
    // Result of RST
    {DownloadItem::INTERRUPTED, GetSafeBufferChunk()},
    // Starting continuation
    {DownloadItem::IN_PROGRESS, GetSafeBufferChunk()},
    // Notification of receiving whole file.
    {DownloadItem::IN_PROGRESS, 0},
    // Completion.
    {DownloadItem::COMPLETE, GetSafeBufferChunk() * 3},
  };

  recorder.CompareToExpectedRecord(expected_record, arraysize(expected_record));
}

// Confirm restart fallback happens if a precondition is failed.
IN_PROC_BROWSER_TEST_F(DownloadContentTest,
                       ResumeInterruptedDownloadBadPrecondition) {
  CommandLine::ForCurrentProcess()->AppendSwitch(
      switches::kEnableDownloadResumption);
  ASSERT_TRUE(test_server()->Start());

  GURL url = test_server()->GetURL(base::StringPrintf(
      // First download hits an RST, rest don't, precondition fail.
      "rangereset?size=%d&rst_boundary=%d&"
      "token=BadPrecondition&rst_limit=1&fail_precondition=2",
      GetSafeBufferChunk() * 3,
      GetSafeBufferChunk()));

  // Start the download and wait for first data chunk.
  DownloadItem* download(StartDownloadAndReturnItem(url));
  WaitForData(download, GetSafeBufferChunk());

  RecordingDownloadObserver recorder(download);

  ReleaseRSTAndConfirmInterruptForResume(download);
  ConfirmFileStatusForResume(
      download, true, GetSafeBufferChunk(), GetSafeBufferChunk() * 3,
      base::FilePath(FILE_PATH_LITERAL("rangereset.crdownload")));
  EXPECT_EQ("BadPrecondition2", download->GetETag());

  DownloadUpdatedObserver completion_observer(
      download, base::Bind(DownloadCompleteFilter));
  download->Resume();
  completion_observer.WaitForEvent();

  ConfirmFileStatusForResume(
      download, true, GetSafeBufferChunk() * 3, GetSafeBufferChunk() * 3,
      base::FilePath(FILE_PATH_LITERAL("rangereset")));
  EXPECT_EQ("BadPrecondition0", download->GetETag());

  static const RecordingDownloadObserver::RecordStruct expected_record[] = {
    // Result of RST
    {DownloadItem::INTERRUPTED, GetSafeBufferChunk()},
    // Starting continuation
    {DownloadItem::IN_PROGRESS, GetSafeBufferChunk()},
    // Server precondition fail.
    {DownloadItem::INTERRUPTED, 0},
    // Notification of successful restart.
    {DownloadItem::IN_PROGRESS, 0},
    // Completion.
    {DownloadItem::COMPLETE, GetSafeBufferChunk() * 3},
  };

  recorder.CompareToExpectedRecord(expected_record, arraysize(expected_record));
}

// Confirm we don't try to resume if we don't have a verifier.
IN_PROC_BROWSER_TEST_F(DownloadContentTest,
                       ResumeInterruptedDownloadNoVerifiers) {
  CommandLine::ForCurrentProcess()->AppendSwitch(
      switches::kEnableDownloadResumption);
  ASSERT_TRUE(test_server()->Start());

  GURL url = test_server()->GetURL(
      base::StringPrintf(
          // First download hits an RST, rest don't, no verifiers.
          "rangereset?size=%d&rst_boundary=%d&"
          "token=NoRange&rst_limit=1&no_verifiers",
          GetSafeBufferChunk() * 3, GetSafeBufferChunk()));

  // Start the download and wait for first data chunk.
  DownloadItem* download(StartDownloadAndReturnItem(url));
  WaitForData(download, GetSafeBufferChunk());

  RecordingDownloadObserver recorder(download);

  ReleaseRSTAndConfirmInterruptForResume(download);
  ConfirmFileStatusForResume(
      download, false, GetSafeBufferChunk(), GetSafeBufferChunk() * 3,
      base::FilePath());

  DownloadUpdatedObserver completion_observer(
      download, base::Bind(DownloadCompleteFilter));
  download->Resume();
  completion_observer.WaitForEvent();

  ConfirmFileStatusForResume(
      download, true, GetSafeBufferChunk() * 3, GetSafeBufferChunk() * 3,
      base::FilePath(FILE_PATH_LITERAL("rangereset")));

  static const RecordingDownloadObserver::RecordStruct expected_record[] = {
    // Result of RST
    {DownloadItem::INTERRUPTED, GetSafeBufferChunk()},
    // Restart for lack of verifiers
    {DownloadItem::IN_PROGRESS, 0},
    // Completion.
    {DownloadItem::COMPLETE, GetSafeBufferChunk() * 3},
  };

  recorder.CompareToExpectedRecord(expected_record, arraysize(expected_record));
}

IN_PROC_BROWSER_TEST_F(DownloadContentTest, ResumeWithDeletedFile) {
  CommandLine::ForCurrentProcess()->AppendSwitch(
      switches::kEnableDownloadResumption);
  ASSERT_TRUE(test_server()->Start());

  GURL url = test_server()->GetURL(
      base::StringPrintf(
          // First download hits an RST, rest don't
          "rangereset?size=%d&rst_boundary=%d&"
          "token=NoRange&rst_limit=1",
          GetSafeBufferChunk() * 3, GetSafeBufferChunk()));

  // Start the download and wait for first data chunk.
  DownloadItem* download(StartDownloadAndReturnItem(url));
  WaitForData(download, GetSafeBufferChunk());

  RecordingDownloadObserver recorder(download);

  ReleaseRSTAndConfirmInterruptForResume(download);
  ConfirmFileStatusForResume(
      download, true, GetSafeBufferChunk(), GetSafeBufferChunk() * 3,
      base::FilePath(FILE_PATH_LITERAL("rangereset.crdownload")));

  // Delete the intermediate file.
  base::DeleteFile(download->GetFullPath(), false);

  DownloadUpdatedObserver completion_observer(
      download, base::Bind(DownloadCompleteFilter));
  download->Resume();
  completion_observer.WaitForEvent();

  ConfirmFileStatusForResume(
      download, true, GetSafeBufferChunk() * 3, GetSafeBufferChunk() * 3,
      base::FilePath(FILE_PATH_LITERAL("rangereset")));

  static const RecordingDownloadObserver::RecordStruct expected_record[] = {
    // Result of RST
    {DownloadItem::INTERRUPTED, GetSafeBufferChunk()},
    // Starting continuation
    {DownloadItem::IN_PROGRESS, GetSafeBufferChunk()},
    // Error because file isn't there.
    {DownloadItem::INTERRUPTED, 0},
    // Restart.
    {DownloadItem::IN_PROGRESS, 0},
    // Completion.
    {DownloadItem::COMPLETE, GetSafeBufferChunk() * 3},
  };

  recorder.CompareToExpectedRecord(expected_record, arraysize(expected_record));
}

IN_PROC_BROWSER_TEST_F(DownloadContentTest, ResumeWithFileInitError) {
  CommandLine::ForCurrentProcess()->AppendSwitch(
      switches::kEnableDownloadResumption);
  base::FilePath file(FILE_PATH_LITERAL("download-test.lib"));
  GURL url(URLRequestMockHTTPJob::GetMockUrl(file));

  // Setup the error injector.
  scoped_refptr<TestFileErrorInjector> injector(
      TestFileErrorInjector::Create(DownloadManagerForShell(shell())));

  TestFileErrorInjector::FileErrorInfo err = {
    url.spec(),
    TestFileErrorInjector::FILE_OPERATION_INITIALIZE,
    0,
    DOWNLOAD_INTERRUPT_REASON_FILE_NO_SPACE
  };
  injector->AddError(err);
  injector->InjectErrors();

  // Start and watch for interrupt.
  scoped_ptr<DownloadTestObserver> int_observer(
      CreateInterruptedWaiter(shell(), 1));
  DownloadItem* download(StartDownloadAndReturnItem(url));
  int_observer->WaitForFinished();
  ASSERT_EQ(DownloadItem::INTERRUPTED, download->GetState());
  EXPECT_EQ(DOWNLOAD_INTERRUPT_REASON_FILE_NO_SPACE,
            download->GetLastReason());
  EXPECT_EQ(0, download->GetReceivedBytes());
  EXPECT_TRUE(download->GetFullPath().empty());
  EXPECT_TRUE(download->GetTargetFilePath().empty());

  // We need to make sure that any cross-thread downloads communication has
  // quiesced before clearing and injecting the new errors, as the
  // InjectErrors() routine alters the currently in use download file
  // factory, which is a file thread object.
  RunAllPendingInMessageLoop(BrowserThread::FILE);
  RunAllPendingInMessageLoop();

  // Clear the old errors list.
  injector->ClearErrors();
  injector->InjectErrors();

  // Resume and watch completion.
  DownloadUpdatedObserver completion_observer(
      download, base::Bind(DownloadCompleteFilter));
  download->Resume();
  completion_observer.WaitForEvent();
  EXPECT_EQ(download->GetState(), DownloadItem::COMPLETE);
}

IN_PROC_BROWSER_TEST_F(DownloadContentTest,
                       ResumeWithFileIntermediateRenameError) {
  CommandLine::ForCurrentProcess()->AppendSwitch(
      switches::kEnableDownloadResumption);
  base::FilePath file(FILE_PATH_LITERAL("download-test.lib"));
  GURL url(URLRequestMockHTTPJob::GetMockUrl(file));

  // Setup the error injector.
  scoped_refptr<TestFileErrorInjector> injector(
      TestFileErrorInjector::Create(DownloadManagerForShell(shell())));

  TestFileErrorInjector::FileErrorInfo err = {
    url.spec(),
    TestFileErrorInjector::FILE_OPERATION_RENAME_UNIQUIFY,
    0,
    DOWNLOAD_INTERRUPT_REASON_FILE_NO_SPACE
  };
  injector->AddError(err);
  injector->InjectErrors();

  // Start and watch for interrupt.
  scoped_ptr<DownloadTestObserver> int_observer(
      CreateInterruptedWaiter(shell(), 1));
  DownloadItem* download(StartDownloadAndReturnItem(url));
  int_observer->WaitForFinished();
  ASSERT_EQ(DownloadItem::INTERRUPTED, download->GetState());
  EXPECT_EQ(DOWNLOAD_INTERRUPT_REASON_FILE_NO_SPACE,
            download->GetLastReason());
  EXPECT_TRUE(download->GetFullPath().empty());
  // Target path will have been set after file name determination. GetFullPath()
  // being empty is sufficient to signal that filename determination needs to be
  // redone.
  EXPECT_FALSE(download->GetTargetFilePath().empty());

  // We need to make sure that any cross-thread downloads communication has
  // quiesced before clearing and injecting the new errors, as the
  // InjectErrors() routine alters the currently in use download file
  // factory, which is a file thread object.
  RunAllPendingInMessageLoop(BrowserThread::FILE);
  RunAllPendingInMessageLoop();

  // Clear the old errors list.
  injector->ClearErrors();
  injector->InjectErrors();

  // Resume and watch completion.
  DownloadUpdatedObserver completion_observer(
      download, base::Bind(DownloadCompleteFilter));
  download->Resume();
  completion_observer.WaitForEvent();
  EXPECT_EQ(download->GetState(), DownloadItem::COMPLETE);
}

IN_PROC_BROWSER_TEST_F(DownloadContentTest, ResumeWithFileFinalRenameError) {
  CommandLine::ForCurrentProcess()->AppendSwitch(
      switches::kEnableDownloadResumption);
  base::FilePath file(FILE_PATH_LITERAL("download-test.lib"));
  GURL url(URLRequestMockHTTPJob::GetMockUrl(file));

  // Setup the error injector.
  scoped_refptr<TestFileErrorInjector> injector(
      TestFileErrorInjector::Create(DownloadManagerForShell(shell())));

  DownloadManagerForShell(shell())->RemoveAllDownloads();
  TestFileErrorInjector::FileErrorInfo err = {
    url.spec(),
    TestFileErrorInjector::FILE_OPERATION_RENAME_ANNOTATE,
    0,
    DOWNLOAD_INTERRUPT_REASON_FILE_NO_SPACE
  };
  injector->AddError(err);
  injector->InjectErrors();

  // Start and watch for interrupt.
  scoped_ptr<DownloadTestObserver> int_observer(
      CreateInterruptedWaiter(shell(), 1));
  DownloadItem* download(StartDownloadAndReturnItem(url));
  int_observer->WaitForFinished();
  ASSERT_EQ(DownloadItem::INTERRUPTED, download->GetState());
  EXPECT_EQ(DOWNLOAD_INTERRUPT_REASON_FILE_NO_SPACE,
            download->GetLastReason());
  EXPECT_TRUE(download->GetFullPath().empty());
  // Target path should still be intact.
  EXPECT_FALSE(download->GetTargetFilePath().empty());

  // We need to make sure that any cross-thread downloads communication has
  // quiesced before clearing and injecting the new errors, as the
  // InjectErrors() routine alters the currently in use download file
  // factory, which is a file thread object.
  RunAllPendingInMessageLoop(BrowserThread::FILE);
  RunAllPendingInMessageLoop();

  // Clear the old errors list.
  injector->ClearErrors();
  injector->InjectErrors();

  // Resume and watch completion.
  DownloadUpdatedObserver completion_observer(
      download, base::Bind(DownloadCompleteFilter));
  download->Resume();
  completion_observer.WaitForEvent();
  EXPECT_EQ(download->GetState(), DownloadItem::COMPLETE);
}

// An interrupted download should remove the intermediate file when it is
// cancelled.
IN_PROC_BROWSER_TEST_F(DownloadContentTest, CancelInterruptedDownload) {
  CommandLine::ForCurrentProcess()->AppendSwitch(
      switches::kEnableDownloadResumption);
  ASSERT_TRUE(test_server()->Start());

  GURL url1 = test_server()->GetURL(
      base::StringPrintf("rangereset?size=%d&rst_boundary=%d",
                         GetSafeBufferChunk() * 3, GetSafeBufferChunk()));

  DownloadItem* download(StartDownloadAndReturnItem(url1));
  WaitForData(download, GetSafeBufferChunk());

  ReleaseRSTAndConfirmInterruptForResume(download);
  ConfirmFileStatusForResume(
      download, true, GetSafeBufferChunk(), GetSafeBufferChunk() * 3,
      base::FilePath(FILE_PATH_LITERAL("rangereset.crdownload")));

  base::FilePath intermediate_path(download->GetFullPath());
  ASSERT_FALSE(intermediate_path.empty());
  EXPECT_TRUE(base::PathExists(intermediate_path));

  download->Cancel(true /* user_cancel */);
  RunAllPendingInMessageLoop(BrowserThread::FILE);
  RunAllPendingInMessageLoop();

  // The intermediate file should now be gone.
  EXPECT_FALSE(base::PathExists(intermediate_path));
  EXPECT_TRUE(download->GetFullPath().empty());
}

IN_PROC_BROWSER_TEST_F(DownloadContentTest, RemoveDownload) {
  CommandLine::ForCurrentProcess()->AppendSwitch(
      switches::kEnableDownloadResumption);
  ASSERT_TRUE(test_server()->Start());

  // An interrupted download should remove the intermediate file when it is
  // removed.
  {
    GURL url1 = test_server()->GetURL(
        base::StringPrintf("rangereset?size=%d&rst_boundary=%d",
                           GetSafeBufferChunk() * 3, GetSafeBufferChunk()));

    DownloadItem* download(StartDownloadAndReturnItem(url1));
    WaitForData(download, GetSafeBufferChunk());
    ReleaseRSTAndConfirmInterruptForResume(download);
    ConfirmFileStatusForResume(
        download, true, GetSafeBufferChunk(), GetSafeBufferChunk() * 3,
        base::FilePath(FILE_PATH_LITERAL("rangereset.crdownload")));

    base::FilePath intermediate_path(download->GetFullPath());
    ASSERT_FALSE(intermediate_path.empty());
    EXPECT_TRUE(base::PathExists(intermediate_path));

    download->Remove();
    RunAllPendingInMessageLoop(BrowserThread::FILE);
    RunAllPendingInMessageLoop();

    // The intermediate file should now be gone.
    EXPECT_FALSE(base::PathExists(intermediate_path));
  }

  // A completed download shouldn't delete the downloaded file when it is
  // removed.
  {
    // Start the second download and wait until it's done.
    base::FilePath file2(FILE_PATH_LITERAL("download-test.lib"));
    GURL url2(URLRequestMockHTTPJob::GetMockUrl(file2));
    scoped_ptr<DownloadTestObserver> completion_observer(
        CreateWaiter(shell(), 1));
    DownloadItem* download(StartDownloadAndReturnItem(url2));
    completion_observer->WaitForFinished();

    // The target path should exist.
    base::FilePath target_path(download->GetTargetFilePath());
    EXPECT_TRUE(base::PathExists(target_path));
    download->Remove();
    RunAllPendingInMessageLoop(BrowserThread::FILE);
    RunAllPendingInMessageLoop();

    // The file should still exist.
    EXPECT_TRUE(base::PathExists(target_path));
  }
}

IN_PROC_BROWSER_TEST_F(DownloadContentTest, RemoveResumingDownload) {
  SetupEnsureNoPendingDownloads();
  CommandLine::ForCurrentProcess()->AppendSwitch(
      switches::kEnableDownloadResumption);
  ASSERT_TRUE(test_server()->Start());

  GURL url = test_server()->GetURL(
      base::StringPrintf("rangereset?size=%d&rst_boundary=%d",
                         GetSafeBufferChunk() * 3, GetSafeBufferChunk()));

  MockDownloadManagerObserver dm_observer(DownloadManagerForShell(shell()));
  EXPECT_CALL(dm_observer, OnDownloadCreated(_,_)).Times(1);

  DownloadItem* download(StartDownloadAndReturnItem(url));
  WaitForData(download, GetSafeBufferChunk());
  ::testing::Mock::VerifyAndClearExpectations(&dm_observer);

  // Tell the server to send the RST and confirm the interrupt happens.
  ReleaseRSTAndConfirmInterruptForResume(download);
  ConfirmFileStatusForResume(
      download, true, GetSafeBufferChunk(), GetSafeBufferChunk() * 3,
      base::FilePath(FILE_PATH_LITERAL("rangereset.crdownload")));

  base::FilePath intermediate_path(download->GetFullPath());
  ASSERT_FALSE(intermediate_path.empty());
  EXPECT_TRUE(base::PathExists(intermediate_path));

  // Resume and remove download. We expect only a single OnDownloadCreated()
  // call, and that's for the second download created below.
  EXPECT_CALL(dm_observer, OnDownloadCreated(_,_)).Times(1);
  download->Resume();
  download->Remove();

  // The intermediate file should now be gone.
  RunAllPendingInMessageLoop(BrowserThread::FILE);
  RunAllPendingInMessageLoop();
  EXPECT_FALSE(base::PathExists(intermediate_path));

  // Start the second download and wait until it's done. The test server is
  // single threaded. The response to this download request should follow the
  // response to the previous resumption request.
  GURL url2(test_server()->GetURL("rangereset?size=100&rst_limit=0&token=x"));
  DownloadAndWait(shell(), url2, DownloadItem::COMPLETE);

  EXPECT_TRUE(EnsureNoPendingDownloads());
}

IN_PROC_BROWSER_TEST_F(DownloadContentTest, CancelResumingDownload) {
  SetupEnsureNoPendingDownloads();
  CommandLine::ForCurrentProcess()->AppendSwitch(
      switches::kEnableDownloadResumption);
  ASSERT_TRUE(test_server()->Start());

  GURL url = test_server()->GetURL(
      base::StringPrintf("rangereset?size=%d&rst_boundary=%d",
                         GetSafeBufferChunk() * 3, GetSafeBufferChunk()));

  MockDownloadManagerObserver dm_observer(DownloadManagerForShell(shell()));
  EXPECT_CALL(dm_observer, OnDownloadCreated(_,_)).Times(1);

  DownloadItem* download(StartDownloadAndReturnItem(url));
  WaitForData(download, GetSafeBufferChunk());
  ::testing::Mock::VerifyAndClearExpectations(&dm_observer);

  // Tell the server to send the RST and confirm the interrupt happens.
  ReleaseRSTAndConfirmInterruptForResume(download);
  ConfirmFileStatusForResume(
      download, true, GetSafeBufferChunk(), GetSafeBufferChunk() * 3,
      base::FilePath(FILE_PATH_LITERAL("rangereset.crdownload")));

  base::FilePath intermediate_path(download->GetFullPath());
  ASSERT_FALSE(intermediate_path.empty());
  EXPECT_TRUE(base::PathExists(intermediate_path));

  // Resume and cancel download. We expect only a single OnDownloadCreated()
  // call, and that's for the second download created below.
  EXPECT_CALL(dm_observer, OnDownloadCreated(_,_)).Times(1);
  download->Resume();
  download->Cancel(true);

  // The intermediate file should now be gone.
  RunAllPendingInMessageLoop(BrowserThread::FILE);
  RunAllPendingInMessageLoop();
  EXPECT_FALSE(base::PathExists(intermediate_path));
  EXPECT_TRUE(download->GetFullPath().empty());

  // Start the second download and wait until it's done. The test server is
  // single threaded. The response to this download request should follow the
  // response to the previous resumption request.
  GURL url2(test_server()->GetURL("rangereset?size=100&rst_limit=0&token=x"));
  DownloadAndWait(shell(), url2, DownloadItem::COMPLETE);

  EXPECT_TRUE(EnsureNoPendingDownloads());
}

// Check that the cookie policy is correctly updated when downloading a file
// that redirects cross origin.
IN_PROC_BROWSER_TEST_F(DownloadContentTest, CookiePolicy) {
  ASSERT_TRUE(test_server()->Start());
  net::HostPortPair host_port = test_server()->host_port_pair();
  DCHECK_EQ(host_port.host(), std::string("127.0.0.1"));

  // Block third-party cookies.
  ShellNetworkDelegate::SetAcceptAllCookies(false);

  // |url| redirects to a different origin |download| which tries to set a
  // cookie.
  std::string download(base::StringPrintf(
      "http://localhost:%d/set-cookie?A=B", host_port.port()));
  GURL url(test_server()->GetURL("server-redirect?" + download));

  // Download the file.
  SetupEnsureNoPendingDownloads();
  scoped_ptr<DownloadUrlParameters> dl_params(
      DownloadUrlParameters::FromWebContents(shell()->web_contents(), url));
  scoped_ptr<DownloadTestObserver> observer(CreateWaiter(shell(), 1));
  DownloadManagerForShell(shell())->DownloadUrl(dl_params.Pass());
  observer->WaitForFinished();

  // Get the important info from other threads and check it.
  EXPECT_TRUE(EnsureNoPendingDownloads());

  std::vector<DownloadItem*> downloads;
  DownloadManagerForShell(shell())->GetAllDownloads(&downloads);
  ASSERT_EQ(1u, downloads.size());
  ASSERT_EQ(DownloadItem::COMPLETE, downloads[0]->GetState());

  // Check that the cookies were correctly set.
  EXPECT_EQ("A=B",
            content::GetCookies(shell()->web_contents()->GetBrowserContext(),
                                GURL(download)));
}

}  // namespace content

/* [<][>][^][v][top][bottom][index][help] */