// 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/files/file.h" #include "base/files/file_util.h" #include "base/message_loop/message_loop.h" #include "base/run_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/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*, 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()); } typedef void (DownloadFile::*DownloadFileRenameMethodType)( const base::FilePath&, const DownloadFile::RenameCompletionCallback&); // This is a test DownloadFileImpl that has no retry delay and, on Posix, // retries renames failed due to ACCESS_DENIED. class TestDownloadFileImpl : public DownloadFileImpl { public: TestDownloadFileImpl(scoped_ptr save_info, const base::FilePath& default_downloads_directory, const GURL& url, const GURL& referrer_url, bool calculate_hash, scoped_ptr stream, const net::BoundNetLog& bound_net_log, base::WeakPtr observer) : DownloadFileImpl(save_info.Pass(), default_downloads_directory, url, referrer_url, calculate_hash, stream.Pass(), bound_net_log, observer) {} protected: virtual base::TimeDelta GetRetryDelayForFailedRename( int attempt_count) OVERRIDE { return base::TimeDelta::FromMilliseconds(0); } #if !defined(OS_WIN) // On Posix, we don't encounter transient errors during renames, except // possibly EAGAIN, which is difficult to replicate reliably. So we resort to // simulating a transient error using ACCESS_DENIED instead. virtual bool ShouldRetryFailedRename( DownloadInterruptReason reason) OVERRIDE { return reason == DOWNLOAD_INTERRUPT_REASON_FILE_ACCESS_DENIED; } #endif }; } // namespace class DownloadFileTest : public testing::Test { public: static const char* kTestData1; static const char* kTestData2; static const char* kTestData3; static const char* kDataHash; static const uint32 kDummyDownloadId; static const int kDummyChildId; static const int kDummyRequestId; DownloadFileTest() : observer_(new StrictMock), 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(const base::Closure& sink_callback) { sink_callback_ = sink_callback; } void SetInterruptReasonCallback(const base::Closure& closure, DownloadInterruptReason* reason_p, DownloadInterruptReason reason) { *reason_p = reason; closure.Run(); } bool CreateDownloadFile(int offset, bool calculate_hash) { // There can be only one. DCHECK(!download_file_.get()); input_stream_ = new StrictMock(); // 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 save_info(new DownloadSaveInfo()); scoped_ptr download_file_impl( new TestDownloadFileImpl(save_info.Pass(), base::FilePath(), GURL(), // Source GURL(), // Referrer calculate_hash, scoped_ptr(input_stream_), net::BoundNetLog(), observer_factory_.GetWeakPtr())); download_file_impl->SetClientGuid("12345678-ABCD-1234-DCBA-123456789ABC"); download_file_ = download_file_impl.PassAs(); EXPECT_CALL(*input_stream_, Read(_, _)) .WillOnce(Return(ByteStreamReader::STREAM_EMPTY)) .RetiresOnSaturation(); base::WeakPtrFactory weak_ptr_factory(this); DownloadInterruptReason result = DOWNLOAD_INTERRUPT_REASON_NONE; base::RunLoop loop_runner; download_file_->Initialize(base::Bind( &DownloadFileTest::SetInterruptReasonCallback, weak_ptr_factory.GetWeakPtr(), loop_runner.QuitClosure(), &result)); loop_runner.Run(); ::testing::Mock::VerifyAndClearExpectations(input_stream_); return result == DOWNLOAD_INTERRUPT_REASON_NONE; } void DestroyDownloadFile(int offset) { EXPECT_FALSE(download_file_->InProgress()); // Make sure the data has been properly written to disk. std::string disk_data; EXPECT_TRUE(base::ReadFileToString(download_file_->FullPath(), &disk_data)); EXPECT_EQ(expected_data_, disk_data); // Make sure the Browser and File threads outlive the DownloadFile // to satisfy thread checks inside it. download_file_.reset(); } // Setup the stream to do be a data append; don't actually trigger // the callback or do verifications. void SetupDataAppend(const char **data_chunks, size_t num_chunks, ::testing::Sequence s) { DCHECK(input_stream_); for (size_t i = 0; i < num_chunks; i++) { const char *source_data = data_chunks[i]; size_t length = strlen(source_data); scoped_refptr 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(base::GetFileSize(download_file_->FullPath(), &size)); EXPECT_EQ(expected_data_.size(), static_cast(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) { return InvokeRenameMethodAndWaitForCallback( &DownloadFile::RenameAndUniquify, full_path, result_path_p); } DownloadInterruptReason RenameAndAnnotate( const base::FilePath& full_path, base::FilePath* result_path_p) { return InvokeRenameMethodAndWaitForCallback( &DownloadFile::RenameAndAnnotate, full_path, result_path_p); } void ExpectPermissionError(DownloadInterruptReason err) { EXPECT_TRUE(err == DOWNLOAD_INTERRUPT_REASON_FILE_TRANSIENT_ERROR || err == DOWNLOAD_INTERRUPT_REASON_FILE_ACCESS_DENIED) << "Interrupt reason = " << err; } protected: DownloadInterruptReason InvokeRenameMethodAndWaitForCallback( DownloadFileRenameMethodType method, const base::FilePath& full_path, base::FilePath* result_path_p) { DownloadInterruptReason result_reason(DOWNLOAD_INTERRUPT_REASON_NONE); base::FilePath result_path; base::RunLoop loop_runner; ((*download_file_).*method)(full_path, base::Bind(&DownloadFileTest::SetRenameResult, base::Unretained(this), loop_runner.QuitClosure(), &result_reason, result_path_p)); loop_runner.Run(); return result_reason; } scoped_ptr > observer_; base::WeakPtrFactory observer_factory_; // DownloadFile instance we are testing. scoped_ptr download_file_; // Stream for sending data into the download file. // Owned by download_file_; will be alive for lifetime of download_file_. StrictMock* 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(const base::Closure& closure, DownloadInterruptReason* reason_p, base::FilePath* result_path_p, DownloadInterruptReason reason, const base::FilePath& result_path) { if (reason_p) *reason_p = reason; if (result_path_p) *result_path_p = result_path; closure.Run(); } // 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_; }; // DownloadFile::RenameAndAnnotate and DownloadFile::RenameAndUniquify have a // considerable amount of functional overlap. In order to re-use test logic, we // are going to introduce this value parameterized test fixture. It will take a // DownloadFileRenameMethodType value which can be either of the two rename // methods. class DownloadFileTestWithRename : public DownloadFileTest, public ::testing::WithParamInterface { protected: DownloadInterruptReason InvokeSelectedRenameMethod( const base::FilePath& full_path, base::FilePath* result_path_p) { return InvokeRenameMethodAndWaitForCallback( GetParam(), full_path, result_path_p); } }; // And now instantiate all DownloadFileTestWithRename tests using both // DownloadFile rename methods. Each test of the form // DownloadFileTestWithRename. will be instantiated once with // RenameAndAnnotate as the value parameter and once with RenameAndUniquify as // the value parameter. INSTANTIATE_TEST_CASE_P(DownloadFile, DownloadFileTestWithRename, ::testing::Values(&DownloadFile::RenameAndAnnotate, &DownloadFile::RenameAndUniquify)); 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_P(DownloadFileTestWithRename, RenameFileFinal) { ASSERT_TRUE(CreateDownloadFile(0, true)); base::FilePath initial_path(download_file_->FullPath()); EXPECT_TRUE(base::PathExists(initial_path)); base::FilePath path_1(initial_path.InsertBeforeExtensionASCII("_1")); base::FilePath path_2(initial_path.InsertBeforeExtensionASCII("_2")); base::FilePath path_3(initial_path.InsertBeforeExtensionASCII("_3")); base::FilePath path_4(initial_path.InsertBeforeExtensionASCII("_4")); base::FilePath output_path; // Rename the file before downloading any data. EXPECT_EQ(DOWNLOAD_INTERRUPT_REASON_NONE, InvokeSelectedRenameMethod(path_1, &output_path)); base::FilePath renamed_path = download_file_->FullPath(); EXPECT_EQ(path_1, renamed_path); EXPECT_EQ(path_1, output_path); // Check the files. EXPECT_FALSE(base::PathExists(initial_path)); EXPECT_TRUE(base::PathExists(path_1)); // Download the data. const char* chunks1[] = { kTestData1, kTestData2 }; AppendDataToFile(chunks1, 2); // Rename the file after downloading some data. EXPECT_EQ(DOWNLOAD_INTERRUPT_REASON_NONE, InvokeSelectedRenameMethod(path_2, &output_path)); renamed_path = download_file_->FullPath(); EXPECT_EQ(path_2, renamed_path); EXPECT_EQ(path_2, output_path); // Check the files. EXPECT_FALSE(base::PathExists(path_1)); EXPECT_TRUE(base::PathExists(path_2)); const char* chunks2[] = { kTestData3 }; AppendDataToFile(chunks2, 1); // Rename the file after downloading all the data. EXPECT_EQ(DOWNLOAD_INTERRUPT_REASON_NONE, InvokeSelectedRenameMethod(path_3, &output_path)); renamed_path = download_file_->FullPath(); EXPECT_EQ(path_3, renamed_path); EXPECT_EQ(path_3, output_path); // Check the files. EXPECT_FALSE(base::PathExists(path_2)); EXPECT_TRUE(base::PathExists(path_3)); // 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, InvokeSelectedRenameMethod(path_4, &output_path)); renamed_path = download_file_->FullPath(); EXPECT_EQ(path_4, renamed_path); EXPECT_EQ(path_4, output_path); // Check the files. EXPECT_FALSE(base::PathExists(path_3)); EXPECT_TRUE(base::PathExists(path_4)); // Check the hash. EXPECT_TRUE(download_file_->GetHash(&hash)); EXPECT_EQ(kDataHash, base::HexEncode(hash.data(), hash.size())); DestroyDownloadFile(0); } // Test to make sure the rename overwrites when requested. This is separate from // the above test because it only applies to RenameAndAnnotate(). // RenameAndUniquify() doesn't overwrite by design. TEST_F(DownloadFileTest, RenameOverwrites) { ASSERT_TRUE(CreateDownloadFile(0, true)); base::FilePath initial_path(download_file_->FullPath()); EXPECT_TRUE(base::PathExists(initial_path)); base::FilePath path_1(initial_path.InsertBeforeExtensionASCII("_1")); ASSERT_FALSE(base::PathExists(path_1)); static const char file_data[] = "xyzzy"; ASSERT_EQ(static_cast(sizeof(file_data)), base::WriteFile(path_1, file_data, sizeof(file_data))); ASSERT_TRUE(base::PathExists(path_1)); base::FilePath new_path; EXPECT_EQ(DOWNLOAD_INTERRUPT_REASON_NONE, RenameAndAnnotate(path_1, &new_path)); EXPECT_EQ(path_1.value(), new_path.value()); std::string file_contents; ASSERT_TRUE(base::ReadFileToString(new_path, &file_contents)); EXPECT_NE(std::string(file_data), file_contents); FinishStream(DOWNLOAD_INTERRUPT_REASON_NONE, true); loop_.RunUntilIdle(); DestroyDownloadFile(0); } // Test to make sure the rename uniquifies if we aren't overwriting // and there's a file where we're aiming. As above, not a // DownloadFileTestWithRename test because this only applies to // RenameAndUniquify(). TEST_F(DownloadFileTest, RenameUniquifies) { ASSERT_TRUE(CreateDownloadFile(0, true)); base::FilePath initial_path(download_file_->FullPath()); EXPECT_TRUE(base::PathExists(initial_path)); base::FilePath path_1(initial_path.InsertBeforeExtensionASCII("_1")); base::FilePath path_1_suffixed(path_1.InsertBeforeExtensionASCII(" (1)")); ASSERT_FALSE(base::PathExists(path_1)); static const char file_data[] = "xyzzy"; ASSERT_EQ(static_cast(sizeof(file_data)), base::WriteFile(path_1, file_data, sizeof(file_data))); ASSERT_TRUE(base::PathExists(path_1)); EXPECT_EQ(DOWNLOAD_INTERRUPT_REASON_NONE, RenameAndUniquify(path_1, NULL)); EXPECT_TRUE(base::PathExists(path_1_suffixed)); FinishStream(DOWNLOAD_INTERRUPT_REASON_NONE, true); loop_.RunUntilIdle(); DestroyDownloadFile(0); } // Test that RenameAndUniquify doesn't try to uniquify in the case where the // target filename is the same as the current filename. TEST_F(DownloadFileTest, RenameRecognizesSelfConflict) { ASSERT_TRUE(CreateDownloadFile(0, true)); base::FilePath initial_path(download_file_->FullPath()); EXPECT_TRUE(base::PathExists(initial_path)); base::FilePath new_path; EXPECT_EQ(DOWNLOAD_INTERRUPT_REASON_NONE, RenameAndUniquify(initial_path, &new_path)); EXPECT_TRUE(base::PathExists(initial_path)); FinishStream(DOWNLOAD_INTERRUPT_REASON_NONE, true); loop_.RunUntilIdle(); DestroyDownloadFile(0); EXPECT_EQ(initial_path.value(), new_path.value()); } // Test to make sure we get the proper error on failure. TEST_P(DownloadFileTestWithRename, RenameError) { ASSERT_TRUE(CreateDownloadFile(0, true)); base::FilePath initial_path(download_file_->FullPath()); // Create a subdirectory. base::FilePath target_dir( initial_path.DirName().Append(FILE_PATH_LITERAL("TargetDir"))); ASSERT_FALSE(base::DirectoryExists(target_dir)); ASSERT_TRUE(base::CreateDirectory(target_dir)); base::FilePath target_path(target_dir.Append(initial_path.BaseName())); // Targets base::FilePath target_path_suffixed( target_path.InsertBeforeExtensionASCII(" (1)")); ASSERT_FALSE(base::PathExists(target_path)); ASSERT_FALSE(base::PathExists(target_path_suffixed)); // Make the directory unwritable and try to rename within it. { base::FilePermissionRestorer restorer(target_dir); ASSERT_TRUE(base::MakeFileUnwritable(target_dir)); // Expect nulling out of further processing. EXPECT_CALL(*input_stream_, RegisterCallback(IsNullCallback())); ExpectPermissionError(InvokeSelectedRenameMethod(target_path, NULL)); EXPECT_FALSE(base::PathExists(target_path_suffixed)); } FinishStream(DOWNLOAD_INTERRUPT_REASON_NONE, true); loop_.RunUntilIdle(); DestroyDownloadFile(0); } namespace { void TestRenameCompletionCallback(const base::Closure& closure, bool* did_run_callback, DownloadInterruptReason interrupt_reason, const base::FilePath& new_path) { EXPECT_EQ(DOWNLOAD_INTERRUPT_REASON_NONE, interrupt_reason); *did_run_callback = true; closure.Run(); } } // namespace // Test that the retry logic works. This test assumes that DownloadFileImpl will // post tasks to the current message loop (acting as the FILE thread) // asynchronously to retry the renames. We will stuff RunLoop::QuitClosures() // in between the retry tasks to stagger them and then allow the rename to // succeed. // // Note that there is only one queue of tasks to run, and that is in the tests' // base::MessageLoop::current(). Each RunLoop processes that queue until it sees // a QuitClosure() targeted at itself, at which point it stops processing. TEST_P(DownloadFileTestWithRename, RenameWithErrorRetry) { ASSERT_TRUE(CreateDownloadFile(0, true)); base::FilePath initial_path(download_file_->FullPath()); // Create a subdirectory. base::FilePath target_dir( initial_path.DirName().Append(FILE_PATH_LITERAL("TargetDir"))); ASSERT_FALSE(base::DirectoryExists(target_dir)); ASSERT_TRUE(base::CreateDirectory(target_dir)); base::FilePath target_path(target_dir.Append(initial_path.BaseName())); bool did_run_callback = false; // Each RunLoop can be used the run the MessageLoop until the corresponding // QuitClosure() is run. This one is used to produce the QuitClosure() that // will be run when the entire rename operation is complete. base::RunLoop succeeding_run; { // (Scope for the base::File or base::FilePermissionRestorer below.) #if defined(OS_WIN) // On Windows we test with an actual transient error, a sharing violation. // The rename will fail because we are holding the file open for READ. On // Posix this doesn't cause a failure. base::File locked_file(initial_path, base::File::FLAG_OPEN | base::File::FLAG_READ); ASSERT_TRUE(locked_file.IsValid()); #else // Simulate a transient failure by revoking write permission for target_dir. // The TestDownloadFileImpl class treats this error as transient even though // DownloadFileImpl itself doesn't. base::FilePermissionRestorer restore_permissions_for(target_dir); ASSERT_TRUE(base::MakeFileUnwritable(target_dir)); #endif // The Rename() should fail here and enqueue a retry task without invoking // the completion callback. ((*download_file_).*GetParam())(target_path, base::Bind(&TestRenameCompletionCallback, succeeding_run.QuitClosure(), &did_run_callback)); EXPECT_FALSE(did_run_callback); base::RunLoop first_failing_run; // Queue the QuitClosure() on the MessageLoop now. Any tasks queued by the // Rename() will be in front of the QuitClosure(). Running the message loop // now causes the just the first retry task to be run. The rename still // fails, so another retry task would get queued behind the QuitClosure(). base::MessageLoop::current()->PostTask(FROM_HERE, first_failing_run.QuitClosure()); first_failing_run.Run(); EXPECT_FALSE(did_run_callback); // Running another loop should have the same effect as the above as long as // kMaxRenameRetries is greater than 2. base::RunLoop second_failing_run; base::MessageLoop::current()->PostTask(FROM_HERE, second_failing_run.QuitClosure()); second_failing_run.Run(); EXPECT_FALSE(did_run_callback); } // This time the QuitClosure from succeeding_run should get executed. succeeding_run.Run(); EXPECT_TRUE(did_run_callback); FinishStream(DOWNLOAD_INTERRUPT_REASON_NONE, true); 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(strlen(kTestData1) + strlen(kTestData2)), bytes_); EXPECT_EQ(download_file_->GetHashState(), hash_state_); FinishStream(DOWNLOAD_INTERRUPT_REASON_NONE, true); DestroyDownloadFile(0); } } // namespace content