// 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 "content/browser/download/download_file_manager.h" #include "base/file_path.h" #include "base/file_util.h" #include "base/message_loop.h" #include "base/scoped_temp_dir.h" #include "base/string_number_conversions.h" #include "content/browser/browser_thread_impl.h" #include "content/browser/download/byte_stream.h" #include "content/browser/download/download_create_info.h" #include "content/browser/download/download_interrupt_reasons_impl.h" #include "content/browser/download/download_request_handle.h" #include "content/browser/download/mock_download_file.h" #include "content/public/browser/download_id.h" #include "content/public/test/mock_download_manager.h" #include "net/base/io_buffer.h" #include "net/base/net_errors.h" #include "testing/gmock/include/gmock/gmock.h" #include "testing/gtest/include/gtest/gtest.h" using content::BrowserThread; using content::BrowserThreadImpl; using content::DownloadId; using content::MockDownloadManager; using ::testing::_; using ::testing::AtLeast; using ::testing::Mock; using ::testing::Return; using ::testing::SaveArg; using ::testing::StrictMock; using ::testing::StrEq; namespace { // MockDownloadManager with the addition of a mock callback used for testing. class TestDownloadManager : public MockDownloadManager { public: MOCK_METHOD3(OnDownloadRenamed, void(int download_id, content::DownloadInterruptReason reason, const FilePath& full_path)); private: ~TestDownloadManager() {} }; class MockDownloadFileFactory : public DownloadFileManager::DownloadFileFactory { public: MockDownloadFileFactory() {} virtual ~MockDownloadFileFactory() {} virtual content::DownloadFile* CreateFile( DownloadCreateInfo* info, scoped_ptr stream, content::DownloadManager* download_manager, bool calculate_hash, const net::BoundNetLog& bound_net_log) OVERRIDE; MockDownloadFile* GetExistingFile(const DownloadId& id); private: std::map files_; }; content::DownloadFile* MockDownloadFileFactory::CreateFile( DownloadCreateInfo* info, scoped_ptr stream, content::DownloadManager* download_manager, bool calculate_hash, const net::BoundNetLog& bound_net_log) { DCHECK(files_.end() == files_.find(info->download_id)); MockDownloadFile* created_file = new StrictMock(); files_[info->download_id] = created_file; ON_CALL(*created_file, GetDownloadManager()) .WillByDefault(Return(download_manager)); EXPECT_CALL(*created_file, Initialize()); return created_file; } MockDownloadFile* MockDownloadFileFactory::GetExistingFile( const DownloadId& id) { DCHECK(files_.find(id) != files_.end()); return files_[id]; } class MockDownloadRequestHandle : public DownloadRequestHandle { public: MockDownloadRequestHandle(content::DownloadManager* manager) : manager_(manager) {} virtual content::DownloadManager* GetDownloadManager() const OVERRIDE; private: content::DownloadManager* manager_; }; content::DownloadManager* MockDownloadRequestHandle::GetDownloadManager() const { return manager_; } void NullCallback() { } } // namespace class DownloadFileManagerTest : public testing::Test { public: // State of renamed file. Used with RenameFile(). enum RenameFileState { IN_PROGRESS, COMPLETE }; // Whether to overwrite the target filename in RenameFile(). enum RenameFileOverwrite { OVERWRITE, DONT_OVERWRITE }; 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 int32 kDummyDownloadId; static const int32 kDummyDownloadId2; static const int kDummyChildId; static const int kDummyRequestId; // We need a UI |BrowserThread| in order to destruct |download_manager_|, // which has trait |BrowserThread::DeleteOnUIThread|. Without this, // calling Release() on |download_manager_| won't ever result in its // destructor being called and we get a leak. DownloadFileManagerTest() : last_reason_(content::DOWNLOAD_INTERRUPT_REASON_NONE), ui_thread_(BrowserThread::UI, &loop_), file_thread_(BrowserThread::FILE, &loop_) { } ~DownloadFileManagerTest() { } virtual void SetUp() { download_manager_ = new TestDownloadManager(); request_handle_.reset(new MockDownloadRequestHandle(download_manager_)); download_file_factory_ = new MockDownloadFileFactory; download_file_manager_ = new DownloadFileManager(download_file_factory_); } virtual void TearDown() { // When a DownloadManager's reference count drops to 0, it is not // deleted immediately. Instead, a task is posted to the UI thread's // message loop to delete it. // So, drop the reference count to 0 and run the message loop once // to ensure that all resources are cleaned up before the test exits. download_manager_ = NULL; ui_thread_.message_loop()->RunAllPending(); } void ProcessAllPendingMessages() { loop_.RunAllPending(); } // Clears all gmock expectations for the download file |id| and the manager. void ClearExpectations(DownloadId id) { MockDownloadFile* file = download_file_factory_->GetExistingFile(id); Mock::VerifyAndClearExpectations(file); Mock::VerifyAndClearExpectations(download_manager_); } void OnDownloadFileCreated(content::DownloadInterruptReason reason) { last_reason_ = reason; } // Create a download item on the DFM. // |info| is the information needed to create a new download file. // |id| is the download ID of the new download file. void CreateDownloadFile(scoped_ptr info) { // Mostly null out args; they'll be passed to MockDownloadFileFactory // to be ignored anyway. download_file_manager_->CreateDownloadFile( info.Pass(), scoped_ptr(), download_manager_, true, net::BoundNetLog(), base::Bind(&DownloadFileManagerTest::OnDownloadFileCreated, // The test jig will outlive all download files. base::Unretained(this))); // Anything that isn't DOWNLOAD_INTERRUPT_REASON_NONE. last_reason_ = content::DOWNLOAD_INTERRUPT_REASON_FILE_ACCESS_DENIED; ProcessAllPendingMessages(); EXPECT_EQ(content::DOWNLOAD_INTERRUPT_REASON_NONE, last_reason_); } // Renames the download file. // |id| is the download ID of the download file. // |new_path| is the new file path. // |unique_path| is the actual path that the download file will be // renamed to. If there is an existing file at // |new_path| and |replace| is false, then |new_path| // will be uniquified. // |rename_error| is the error to inject. For no error, // use content::DOWNLOAD_INTERRUPT_REASON_NONE. // |state| whether we are renaming an in-progress download or a // completed download. // |should_overwrite| indicates whether to replace or uniquify the file. void RenameFile(const DownloadId& id, const FilePath& new_path, bool should_overwrite) { MockDownloadFile* file = download_file_factory_->GetExistingFile(id); ASSERT_TRUE(file != NULL); content::DownloadFile::RenameCompletionCallback rename_callback; EXPECT_CALL(*file, Rename(new_path, should_overwrite, _)) .WillOnce(SaveArg<2>(&rename_callback)); content::DownloadFile::RenameCompletionCallback passed_callback( base::Bind(&TestDownloadManager::OnDownloadRenamed, download_manager_, id.local())); download_file_manager_->RenameDownloadFile( id, new_path, should_overwrite, passed_callback); EXPECT_TRUE(rename_callback.Equals(passed_callback)); } void Complete(DownloadId id) { MockDownloadFile* file = download_file_factory_->GetExistingFile(id); ASSERT_TRUE(file != NULL); EXPECT_CALL(*file, AnnotateWithSourceInformation()) .WillOnce(Return()); EXPECT_CALL(*file, Detach()) .WillOnce(Return()); int num_downloads = download_file_manager_->NumberOfActiveDownloads(); EXPECT_LT(0, num_downloads); download_file_manager_->CompleteDownload(id, base::Bind(NullCallback)); EXPECT_EQ(num_downloads - 1, download_file_manager_->NumberOfActiveDownloads()); EXPECT_EQ(NULL, download_file_manager_->GetDownloadFile(id)); } void CleanUp(DownloadId id) { // Expected calls: // DownloadFileManager::CancelDownload // DownloadFile::Cancel // DownloadFileManager::EraseDownload // if no more downloads: // DownloadFileManager::StopUpdateTimer MockDownloadFile* file = download_file_factory_->GetExistingFile(id); ASSERT_TRUE(file != NULL); EXPECT_CALL(*file, Cancel()); download_file_manager_->CancelDownload(id); EXPECT_EQ(NULL, download_file_manager_->GetDownloadFile(id)); } protected: scoped_refptr download_manager_; scoped_ptr request_handle_; MockDownloadFileFactory* download_file_factory_; scoped_refptr download_file_manager_; // Error from creating download file. content::DownloadInterruptReason last_reason_; // Per-download statistics. std::map byte_count_; std::map error_count_; private: MessageLoop loop_; // UI thread. BrowserThreadImpl ui_thread_; // File thread to satisfy debug checks in DownloadFile. BrowserThreadImpl file_thread_; }; const char* DownloadFileManagerTest::kTestData1 = "Let's write some data to the file!\n"; const char* DownloadFileManagerTest::kTestData2 = "Writing more data.\n"; const char* DownloadFileManagerTest::kTestData3 = "Final line."; const char* DownloadFileManagerTest::kTestData4 = "Writing, writing, writing\n"; const char* DownloadFileManagerTest::kTestData5 = "All we do is writing,\n"; const char* DownloadFileManagerTest::kTestData6 = "Rawhide!"; const int32 DownloadFileManagerTest::kDummyDownloadId = 23; const int32 DownloadFileManagerTest::kDummyDownloadId2 = 77; const int DownloadFileManagerTest::kDummyChildId = 3; const int DownloadFileManagerTest::kDummyRequestId = 67; TEST_F(DownloadFileManagerTest, Cancel) { scoped_ptr info(new DownloadCreateInfo); DownloadId dummy_id(download_manager_.get(), kDummyDownloadId); info->download_id = dummy_id; CreateDownloadFile(info.Pass()); CleanUp(dummy_id); } TEST_F(DownloadFileManagerTest, Complete) { scoped_ptr info(new DownloadCreateInfo); DownloadId dummy_id(download_manager_.get(), kDummyDownloadId); info->download_id = dummy_id; CreateDownloadFile(info.Pass()); Complete(dummy_id); } TEST_F(DownloadFileManagerTest, Rename) { scoped_ptr info(new DownloadCreateInfo); DownloadId dummy_id(download_manager_.get(), kDummyDownloadId); info->download_id = dummy_id; ScopedTempDir download_dir; ASSERT_TRUE(download_dir.CreateUniqueTempDir()); CreateDownloadFile(info.Pass()); FilePath foo(download_dir.path().Append(FILE_PATH_LITERAL("foo.txt"))); RenameFile(dummy_id, foo, true); CleanUp(dummy_id); } TEST_F(DownloadFileManagerTest, RenameNoOverwrite) { scoped_ptr info(new DownloadCreateInfo); DownloadId dummy_id(download_manager_.get(), kDummyDownloadId); info->download_id = dummy_id; ScopedTempDir download_dir; ASSERT_TRUE(download_dir.CreateUniqueTempDir()); CreateDownloadFile(info.Pass()); FilePath foo(download_dir.path().Append(FILE_PATH_LITERAL("foo.txt"))); RenameFile(dummy_id, foo, false); CleanUp(dummy_id); } TEST_F(DownloadFileManagerTest, RenameTwice) { scoped_ptr info(new DownloadCreateInfo); DownloadId dummy_id(download_manager_.get(), kDummyDownloadId); info->download_id = dummy_id; ScopedTempDir download_dir; ASSERT_TRUE(download_dir.CreateUniqueTempDir()); CreateDownloadFile(info.Pass()); FilePath crfoo(download_dir.path().Append( FILE_PATH_LITERAL("foo.txt.crdownload"))); RenameFile(dummy_id, crfoo, true); FilePath foo(download_dir.path().Append(FILE_PATH_LITERAL("foo.txt"))); RenameFile(dummy_id, foo, true); CleanUp(dummy_id); } TEST_F(DownloadFileManagerTest, TwoDownloads) { // Same as StartDownload, at first. scoped_ptr info(new DownloadCreateInfo); DownloadId dummy_id(download_manager_.get(), kDummyDownloadId); info->download_id = dummy_id; scoped_ptr info2(new DownloadCreateInfo); DownloadId dummy_id2(download_manager_.get(), kDummyDownloadId2); info2->download_id = dummy_id2; ScopedTempDir download_dir; ASSERT_TRUE(download_dir.CreateUniqueTempDir()); CreateDownloadFile(info.Pass()); CreateDownloadFile(info2.Pass()); FilePath crbar(download_dir.path().Append( FILE_PATH_LITERAL("bar.txt.crdownload"))); RenameFile(dummy_id2, crbar, true); FilePath crfoo(download_dir.path().Append( FILE_PATH_LITERAL("foo.txt.crdownload"))); RenameFile(dummy_id, crfoo, true); FilePath bar(download_dir.path().Append(FILE_PATH_LITERAL("bar.txt"))); RenameFile(dummy_id2, bar, true); CleanUp(dummy_id2); FilePath foo(download_dir.path().Append(FILE_PATH_LITERAL("foo.txt"))); RenameFile(dummy_id, foo, true); CleanUp(dummy_id); } // TODO(ahendrickson) -- A test for download manager shutdown. // Expected call sequence: // OnDownloadManagerShutdown // DownloadFile::GetDownloadManager // DownloadFile::CancelDownloadRequest // DownloadFile::~DownloadFile