diff options
Diffstat (limited to 'chromium/content/browser/download/download_file_unittest.cc')
-rw-r--r-- | chromium/content/browser/download/download_file_unittest.cc | 606 |
1 files changed, 606 insertions, 0 deletions
diff --git a/chromium/content/browser/download/download_file_unittest.cc b/chromium/content/browser/download/download_file_unittest.cc new file mode 100644 index 00000000000..49e418c85bc --- /dev/null +++ b/chromium/content/browser/download/download_file_unittest.cc @@ -0,0 +1,606 @@ +// 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 "base/file_util.h" +#include "base/message_loop/message_loop.h" +#include "base/strings/string_number_conversions.h" +#include "base/test/test_file_util.h" +#include "content/browser/browser_thread_impl.h" +#include "content/browser/byte_stream.h" +#include "content/browser/download/download_create_info.h" +#include "content/browser/download/download_file_impl.h" +#include "content/browser/download/download_request_handle.h" +#include "content/public/browser/download_destination_observer.h" +#include "content/public/browser/download_interrupt_reasons.h" +#include "content/public/browser/download_manager.h" +#include "content/public/browser/power_save_blocker.h" +#include "content/public/test/mock_download_manager.h" +#include "net/base/file_stream.h" +#include "net/base/mock_file_stream.h" +#include "net/base/net_errors.h" +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" + +using ::testing::_; +using ::testing::AnyNumber; +using ::testing::DoAll; +using ::testing::InSequence; +using ::testing::Return; +using ::testing::SetArgPointee; +using ::testing::StrictMock; + +namespace content { +namespace { + +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, int64, const std::string&)); + MOCK_METHOD1(DestinationError, void(DownloadInterruptReason)); + MOCK_METHOD1(DestinationCompleted, void(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_METHOD3(CurrentUpdateStatus, + void(int64, int64, const std::string&)); +}; + +MATCHER(IsNullCallback, "") { return (arg.is_null()); } + +} // namespace + +class DownloadFileTest : public testing::Test { + public: + + static const char* kTestData1; + static const char* kTestData2; + static const char* kTestData3; + static const char* kDataHash; + static const uint32 kDummyDownloadId; + static const int kDummyChildId; + static const int kDummyRequestId; + + DownloadFileTest() : + observer_(new StrictMock<MockDownloadDestinationObserver>), + observer_factory_(observer_.get()), + input_stream_(NULL), + bytes_(-1), + bytes_per_sec_(-1), + hash_state_("xyzzy"), + ui_thread_(BrowserThread::UI, &loop_), + file_thread_(BrowserThread::FILE, &loop_) { + } + + virtual ~DownloadFileTest() { + } + + void SetUpdateDownloadInfo(int64 bytes, int64 bytes_per_sec, + const std::string& hash_state) { + bytes_ = bytes; + bytes_per_sec_ = bytes_per_sec; + hash_state_ = hash_state; + } + + void ConfirmUpdateDownloadInfo() { + observer_->CurrentUpdateStatus(bytes_, bytes_per_sec_, hash_state_); + } + + virtual void SetUp() { + EXPECT_CALL(*(observer_.get()), DestinationUpdate(_, _, _)) + .Times(AnyNumber()) + .WillRepeatedly(Invoke(this, &DownloadFileTest::SetUpdateDownloadInfo)); + } + + // Mock calls to this function are forwarded here. + void RegisterCallback(base::Closure sink_callback) { + sink_callback_ = sink_callback; + } + + void SetInterruptReasonCallback(bool* was_called, + DownloadInterruptReason* reason_p, + DownloadInterruptReason reason) { + *was_called = true; + *reason_p = reason; + } + + virtual bool CreateDownloadFile(int offset, bool calculate_hash) { + // There can be only one. + DCHECK(!download_file_.get()); + + input_stream_ = new StrictMock<MockByteStreamReader>(); + + // TODO: Need to actually create a function that'll set the variables + // based on the inputs from the callback. + EXPECT_CALL(*input_stream_, RegisterCallback(_)) + .WillOnce(Invoke(this, &DownloadFileTest::RegisterCallback)) + .RetiresOnSaturation(); + + scoped_ptr<DownloadSaveInfo> save_info(new DownloadSaveInfo()); + download_file_.reset( + new DownloadFileImpl(save_info.Pass(), + base::FilePath(), + GURL(), // Source + GURL(), // Referrer + calculate_hash, + scoped_ptr<ByteStreamReader>(input_stream_), + net::BoundNetLog(), + scoped_ptr<PowerSaveBlocker>().Pass(), + observer_factory_.GetWeakPtr())); + download_file_->SetClientGuid( + "12345678-ABCD-1234-DCBA-123456789ABC"); + + EXPECT_CALL(*input_stream_, Read(_, _)) + .WillOnce(Return(ByteStreamReader::STREAM_EMPTY)) + .RetiresOnSaturation(); + + base::WeakPtrFactory<DownloadFileTest> weak_ptr_factory(this); + bool called = false; + DownloadInterruptReason result; + download_file_->Initialize(base::Bind( + &DownloadFileTest::SetInterruptReasonCallback, + weak_ptr_factory.GetWeakPtr(), &called, &result)); + loop_.RunUntilIdle(); + EXPECT_TRUE(called); + + ::testing::Mock::VerifyAndClearExpectations(input_stream_); + return result == DOWNLOAD_INTERRUPT_REASON_NONE; + } + + virtual void DestroyDownloadFile(int offset) { + EXPECT_FALSE(download_file_->InProgress()); + + // Make sure the data has been properly written to disk. + std::string disk_data; + EXPECT_TRUE(file_util::ReadFileToString(download_file_->FullPath(), + &disk_data)); + EXPECT_EQ(expected_data_, disk_data); + + // Make sure the Browser and File threads outlive the DownloadFile + // to satisfy thread checks inside it. + download_file_.reset(); + } + + // Setup the stream to do be a data append; don't actually trigger + // the callback or do verifications. + void SetupDataAppend(const char **data_chunks, size_t num_chunks, + ::testing::Sequence s) { + DCHECK(input_stream_); + for (size_t i = 0; i < num_chunks; i++) { + const char *source_data = data_chunks[i]; + size_t length = strlen(source_data); + scoped_refptr<net::IOBuffer> data = new net::IOBuffer(length); + memcpy(data->data(), source_data, length); + EXPECT_CALL(*input_stream_, Read(_, _)) + .InSequence(s) + .WillOnce(DoAll(SetArgPointee<0>(data), + SetArgPointee<1>(length), + Return(ByteStreamReader::STREAM_HAS_DATA))) + .RetiresOnSaturation(); + expected_data_ += source_data; + } + } + + void VerifyStreamAndSize() { + ::testing::Mock::VerifyAndClearExpectations(input_stream_); + int64 size; + EXPECT_TRUE(file_util::GetFileSize(download_file_->FullPath(), &size)); + EXPECT_EQ(expected_data_.size(), static_cast<size_t>(size)); + } + + // TODO(rdsmith): Manage full percentage issues properly. + void AppendDataToFile(const char **data_chunks, size_t num_chunks) { + ::testing::Sequence s1; + SetupDataAppend(data_chunks, num_chunks, s1); + EXPECT_CALL(*input_stream_, Read(_, _)) + .InSequence(s1) + .WillOnce(Return(ByteStreamReader::STREAM_EMPTY)) + .RetiresOnSaturation(); + sink_callback_.Run(); + VerifyStreamAndSize(); + } + + void SetupFinishStream(DownloadInterruptReason interrupt_reason, + ::testing::Sequence s) { + EXPECT_CALL(*input_stream_, Read(_, _)) + .InSequence(s) + .WillOnce(Return(ByteStreamReader::STREAM_COMPLETE)) + .RetiresOnSaturation(); + EXPECT_CALL(*input_stream_, GetStatus()) + .InSequence(s) + .WillOnce(Return(interrupt_reason)) + .RetiresOnSaturation(); + EXPECT_CALL(*input_stream_, RegisterCallback(_)) + .RetiresOnSaturation(); + } + + void FinishStream(DownloadInterruptReason interrupt_reason, + bool check_observer) { + ::testing::Sequence s1; + SetupFinishStream(interrupt_reason, s1); + sink_callback_.Run(); + VerifyStreamAndSize(); + if (check_observer) { + EXPECT_CALL(*(observer_.get()), DestinationCompleted(_)); + loop_.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) { + base::WeakPtrFactory<DownloadFileTest> weak_ptr_factory(this); + DownloadInterruptReason result_reason(DOWNLOAD_INTERRUPT_REASON_NONE); + bool callback_was_called(false); + base::FilePath result_path; + + download_file_->RenameAndUniquify( + full_path, base::Bind(&DownloadFileTest::SetRenameResult, + weak_ptr_factory.GetWeakPtr(), + &callback_was_called, + &result_reason, result_path_p)); + loop_.RunUntilIdle(); + + EXPECT_TRUE(callback_was_called); + return result_reason; + } + + DownloadInterruptReason RenameAndAnnotate( + const base::FilePath& full_path, + base::FilePath* result_path_p) { + base::WeakPtrFactory<DownloadFileTest> weak_ptr_factory(this); + DownloadInterruptReason result_reason(DOWNLOAD_INTERRUPT_REASON_NONE); + bool callback_was_called(false); + base::FilePath result_path; + + download_file_->RenameAndAnnotate( + full_path, base::Bind(&DownloadFileTest::SetRenameResult, + weak_ptr_factory.GetWeakPtr(), + &callback_was_called, + &result_reason, result_path_p)); + loop_.RunUntilIdle(); + + EXPECT_TRUE(callback_was_called); + return result_reason; + } + + protected: + scoped_ptr<StrictMock<MockDownloadDestinationObserver> > observer_; + base::WeakPtrFactory<DownloadDestinationObserver> observer_factory_; + + // DownloadFile instance we are testing. + scoped_ptr<DownloadFile> download_file_; + + // Stream for sending data into the download file. + // Owned by download_file_; will be alive for lifetime of download_file_. + StrictMock<MockByteStreamReader>* input_stream_; + + // Sink callback data for stream. + base::Closure sink_callback_; + + // Latest update sent to the observer. + int64 bytes_; + int64 bytes_per_sec_; + std::string hash_state_; + + base::MessageLoop loop_; + + private: + void SetRenameResult(bool* called_p, + DownloadInterruptReason* reason_p, + base::FilePath* result_path_p, + DownloadInterruptReason reason, + const base::FilePath& result_path) { + if (called_p) + *called_p = true; + if (reason_p) + *reason_p = reason; + if (result_path_p) + *result_path_p = result_path; + } + + // UI thread. + BrowserThreadImpl ui_thread_; + // File thread to satisfy debug checks in DownloadFile. + BrowserThreadImpl file_thread_; + + // Keep track of what data should be saved to the disk file. + std::string expected_data_; +}; + +const char* DownloadFileTest::kTestData1 = + "Let's write some data to the file!\n"; +const char* DownloadFileTest::kTestData2 = "Writing more data.\n"; +const char* DownloadFileTest::kTestData3 = "Final line."; +const char* DownloadFileTest::kDataHash = + "CBF68BF10F8003DB86B31343AFAC8C7175BD03FB5FC905650F8C80AF087443A8"; + +const uint32 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_F(DownloadFileTest, 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 path_5(initial_path.InsertBeforeExtensionASCII("_5")); + base::FilePath output_path; + + // Rename the file before downloading any data. + EXPECT_EQ(DOWNLOAD_INTERRUPT_REASON_NONE, + RenameAndUniquify(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, + RenameAndUniquify(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, + RenameAndUniquify(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)); + + // Should not be able to get the hash until the file is closed. + std::string hash; + EXPECT_FALSE(download_file_->GetHash(&hash)); + FinishStream(DOWNLOAD_INTERRUPT_REASON_NONE, true); + loop_.RunUntilIdle(); + + // Rename the file after downloading all the data and closing the file. + EXPECT_EQ(DOWNLOAD_INTERRUPT_REASON_NONE, + RenameAndUniquify(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)); + + // Check the hash. + EXPECT_TRUE(download_file_->GetHash(&hash)); + EXPECT_EQ(kDataHash, base::HexEncode(hash.data(), hash.size())); + + // Check that a rename with overwrite to an existing file succeeds. + std::string file_contents; + ASSERT_FALSE(base::PathExists(path_5)); + static const char file_data[] = "xyzzy"; + ASSERT_EQ(static_cast<int>(sizeof(file_data) - 1), + file_util::WriteFile(path_5, file_data, sizeof(file_data) - 1)); + ASSERT_TRUE(base::PathExists(path_5)); + EXPECT_TRUE(file_util::ReadFileToString(path_5, &file_contents)); + EXPECT_EQ(std::string(file_data), file_contents); + + EXPECT_EQ(DOWNLOAD_INTERRUPT_REASON_NONE, + RenameAndAnnotate(path_5, &output_path)); + EXPECT_EQ(path_5, output_path); + + file_contents = ""; + EXPECT_TRUE(file_util::ReadFileToString(path_5, &file_contents)); + EXPECT_NE(std::string(file_data), file_contents); + + DestroyDownloadFile(0); +} + +// Test to make sure the rename uniquifies if we aren't overwriting +// and there's a file where we're aiming. +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)), + file_util::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); + loop_.RunUntilIdle(); + DestroyDownloadFile(0); +} + +// Test to make sure we get the proper error on failure. +TEST_F(DownloadFileTest, RenameError) { + ASSERT_TRUE(CreateDownloadFile(0, true)); + base::FilePath initial_path(download_file_->FullPath()); + + // Create a subdirectory. + base::FilePath tempdir( + initial_path.DirName().Append(FILE_PATH_LITERAL("tempdir"))); + ASSERT_TRUE(file_util::CreateDirectory(tempdir)); + base::FilePath target_path(tempdir.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. + { + file_util::PermissionRestorer restorer(tempdir); + ASSERT_TRUE(file_util::MakeFileUnwritable(tempdir)); + + // Expect nulling out of further processing. + EXPECT_CALL(*input_stream_, RegisterCallback(IsNullCallback())); + EXPECT_EQ(DOWNLOAD_INTERRUPT_REASON_FILE_ACCESS_DENIED, + RenameAndAnnotate(target_path, NULL)); + EXPECT_FALSE(base::PathExists(target_path_suffixed)); + } + + FinishStream(DOWNLOAD_INTERRUPT_REASON_NONE, true); + loop_.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. + EXPECT_CALL(*(observer_.get()), DestinationCompleted(_)); + FinishStream(DOWNLOAD_INTERRUPT_REASON_NONE, false); + loop_.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()), + DestinationError( + 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(0, _, _)); + + FinishStream(DOWNLOAD_INTERRUPT_REASON_NETWORK_DISCONNECTED, false); + + loop_.RunUntilIdle(); + + DestroyDownloadFile(0); +} + +TEST_F(DownloadFileTest, StreamNonEmptySuccess) { + ASSERT_TRUE(CreateDownloadFile(0, true)); + base::FilePath initial_path(download_file_->FullPath()); + EXPECT_TRUE(base::PathExists(initial_path)); + + const char* chunks1[] = { kTestData1, kTestData2 }; + ::testing::Sequence s1; + SetupDataAppend(chunks1, 2, s1); + SetupFinishStream(DOWNLOAD_INTERRUPT_REASON_NONE, s1); + EXPECT_CALL(*(observer_.get()), DestinationCompleted(_)); + sink_callback_.Run(); + VerifyStreamAndSize(); + loop_.RunUntilIdle(); + DestroyDownloadFile(0); +} + +TEST_F(DownloadFileTest, StreamNonEmptyError) { + ASSERT_TRUE(CreateDownloadFile(0, true)); + base::FilePath initial_path(download_file_->FullPath()); + EXPECT_TRUE(base::PathExists(initial_path)); + + const char* chunks1[] = { kTestData1, kTestData2 }; + ::testing::Sequence s1; + SetupDataAppend(chunks1, 2, s1); + SetupFinishStream(DOWNLOAD_INTERRUPT_REASON_NETWORK_DISCONNECTED, s1); + + EXPECT_CALL(*(observer_.get()), + DestinationError( + 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(); + loop_.RunUntilIdle(); + VerifyStreamAndSize(); + DestroyDownloadFile(0); +} + +// Send some data, wait 3/4s of a second, run the message loop, and +// confirm the values the observer received are correct. +TEST_F(DownloadFileTest, ConfirmUpdate) { + CreateDownloadFile(0, true); + + const char* chunks1[] = { kTestData1, kTestData2 }; + AppendDataToFile(chunks1, 2); + + // Run the message loops for 750ms and check for results. + loop_.PostDelayedTask(FROM_HERE, + base::MessageLoop::QuitClosure(), + base::TimeDelta::FromMilliseconds(750)); + loop_.Run(); + + EXPECT_EQ(static_cast<int64>(strlen(kTestData1) + strlen(kTestData2)), + bytes_); + EXPECT_EQ(download_file_->GetHashState(), hash_state_); + + FinishStream(DOWNLOAD_INTERRUPT_REASON_NONE, true); + DestroyDownloadFile(0); +} + +} // namespace content |