Chromium Code Reviews| Index: content/browser/download/download_file_unittest.cc |
| diff --git a/content/browser/download/download_file_unittest.cc b/content/browser/download/download_file_unittest.cc |
| index 1127908c26057d7f0a28a565e7ad733101ac06bd..4ae173d66c5c759a7c1e3bfc0ddb511fe0b38bb9 100644 |
| --- a/content/browser/download/download_file_unittest.cc |
| +++ b/content/browser/download/download_file_unittest.cc |
| @@ -3,7 +3,9 @@ |
| // found in the LICENSE file. |
| #include "base/file_util.h" |
| +#include "base/files/file.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" |
| @@ -59,6 +61,48 @@ class MockDownloadDestinationObserver : public DownloadDestinationObserver { |
| 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<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) |
| + : 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 { |
| @@ -104,15 +148,15 @@ class DownloadFileTest : public testing::Test { |
| } |
| // Mock calls to this function are forwarded here. |
| - void RegisterCallback(base::Closure sink_callback) { |
| + void RegisterCallback(const base::Closure& sink_callback) { |
| sink_callback_ = sink_callback; |
| } |
| - void SetInterruptReasonCallback(bool* was_called, |
| + void SetInterruptReasonCallback(const base::Closure& closure, |
| DownloadInterruptReason* reason_p, |
| DownloadInterruptReason reason) { |
| - *was_called = true; |
| *reason_p = reason; |
| + closure.Run(); |
| } |
| bool CreateDownloadFile(int offset, bool calculate_hash) { |
| @@ -128,30 +172,29 @@ class DownloadFileTest : public testing::Test { |
| .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(), |
| - observer_factory_.GetWeakPtr())); |
| - download_file_->SetClientGuid( |
| - "12345678-ABCD-1234-DCBA-123456789ABC"); |
| + scoped_ptr<TestDownloadFileImpl> download_file_impl( |
| + new TestDownloadFileImpl(save_info.Pass(), |
| + base::FilePath(), |
| + GURL(), // Source |
| + GURL(), // Referrer |
| + calculate_hash, |
| + scoped_ptr<ByteStreamReader>(input_stream_), |
| + net::BoundNetLog(), |
| + observer_factory_.GetWeakPtr())); |
| + download_file_impl->SetClientGuid("12345678-ABCD-1234-DCBA-123456789ABC"); |
| + download_file_ = download_file_impl.PassAs<DownloadFile>(); |
| EXPECT_CALL(*input_stream_, Read(_, _)) |
| .WillOnce(Return(ByteStreamReader::STREAM_EMPTY)) |
| .RetiresOnSaturation(); |
| base::WeakPtrFactory<DownloadFileTest> weak_ptr_factory(this); |
| - bool called = false; |
| - DownloadInterruptReason result; |
| + DownloadInterruptReason result = DOWNLOAD_INTERRUPT_REASON_NONE; |
| + base::RunLoop loop_runner; |
| download_file_->Initialize(base::Bind( |
| &DownloadFileTest::SetInterruptReasonCallback, |
| - weak_ptr_factory.GetWeakPtr(), &called, &result)); |
| - loop_.RunUntilIdle(); |
| - EXPECT_TRUE(called); |
| + weak_ptr_factory.GetWeakPtr(), loop_runner.QuitClosure(), &result)); |
| + loop_runner.Run(); |
| ::testing::Mock::VerifyAndClearExpectations(input_stream_); |
| return result == DOWNLOAD_INTERRUPT_REASON_NONE; |
| @@ -243,42 +286,36 @@ class DownloadFileTest : public testing::Test { |
| 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; |
| + return InvokeRenameMethodAndWaitForCallback( |
| + &DownloadFile::RenameAndUniquify, full_path, result_path_p); |
| } |
| DownloadInterruptReason RenameAndAnnotate( |
| const base::FilePath& full_path, |
| base::FilePath* result_path_p) { |
| - base::WeakPtrFactory<DownloadFileTest> weak_ptr_factory(this); |
| + return InvokeRenameMethodAndWaitForCallback( |
| + &DownloadFile::RenameAndAnnotate, full_path, result_path_p); |
| + } |
| + |
| + protected: |
| + DownloadInterruptReason InvokeRenameMethodAndWaitForCallback( |
| + DownloadFileRenameMethodType method, |
| + const base::FilePath& full_path, |
| + base::FilePath* result_path_p) { |
| 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); |
| + 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; |
| } |
| - protected: |
| scoped_ptr<StrictMock<MockDownloadDestinationObserver> > observer_; |
| base::WeakPtrFactory<DownloadDestinationObserver> observer_factory_; |
| @@ -300,17 +337,16 @@ class DownloadFileTest : public testing::Test { |
| base::MessageLoop loop_; |
| private: |
| - void SetRenameResult(bool* called_p, |
| + void SetRenameResult(const base::Closure& closure, |
| 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; |
| + closure.Run(); |
| } |
| // UI thread. |
| @@ -322,6 +358,23 @@ class DownloadFileTest : public testing::Test { |
| std::string expected_data_; |
| }; |
| +class DownloadFileTestWithRename |
| + : public DownloadFileTest, |
| + public ::testing::WithParamInterface<DownloadFileRenameMethodType> { |
| + protected: |
| + DownloadInterruptReason InvokeSelectedRenameMethod( |
| + const base::FilePath& full_path, |
| + base::FilePath* result_path_p) { |
| + return InvokeRenameMethodAndWaitForCallback( |
| + GetParam(), full_path, result_path_p); |
| + } |
| +}; |
| + |
| +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"; |
| @@ -335,7 +388,7 @@ 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) { |
| +TEST_P(DownloadFileTestWithRename, RenameFileFinal) { |
| ASSERT_TRUE(CreateDownloadFile(0, true)); |
| base::FilePath initial_path(download_file_->FullPath()); |
| EXPECT_TRUE(base::PathExists(initial_path)); |
| @@ -343,12 +396,11 @@ TEST_F(DownloadFileTest, RenameFileFinal) { |
| 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)); |
| + InvokeSelectedRenameMethod(path_1, &output_path)); |
| base::FilePath renamed_path = download_file_->FullPath(); |
| EXPECT_EQ(path_1, renamed_path); |
| EXPECT_EQ(path_1, output_path); |
| @@ -363,7 +415,7 @@ TEST_F(DownloadFileTest, RenameFileFinal) { |
| // Rename the file after downloading some data. |
| EXPECT_EQ(DOWNLOAD_INTERRUPT_REASON_NONE, |
| - RenameAndUniquify(path_2, &output_path)); |
| + InvokeSelectedRenameMethod(path_2, &output_path)); |
| renamed_path = download_file_->FullPath(); |
| EXPECT_EQ(path_2, renamed_path); |
| EXPECT_EQ(path_2, output_path); |
| @@ -377,7 +429,7 @@ TEST_F(DownloadFileTest, RenameFileFinal) { |
| // Rename the file after downloading all the data. |
| EXPECT_EQ(DOWNLOAD_INTERRUPT_REASON_NONE, |
| - RenameAndUniquify(path_3, &output_path)); |
| + InvokeSelectedRenameMethod(path_3, &output_path)); |
| renamed_path = download_file_->FullPath(); |
| EXPECT_EQ(path_3, renamed_path); |
| EXPECT_EQ(path_3, output_path); |
| @@ -394,7 +446,7 @@ TEST_F(DownloadFileTest, RenameFileFinal) { |
| // Rename the file after downloading all the data and closing the file. |
| EXPECT_EQ(DOWNLOAD_INTERRUPT_REASON_NONE, |
| - RenameAndUniquify(path_4, &output_path)); |
| + InvokeSelectedRenameMethod(path_4, &output_path)); |
| renamed_path = download_file_->FullPath(); |
| EXPECT_EQ(path_4, renamed_path); |
| EXPECT_EQ(path_4, output_path); |
| @@ -407,24 +459,33 @@ TEST_F(DownloadFileTest, RenameFileFinal) { |
| 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)); |
| + DestroyDownloadFile(0); |
| +} |
| + |
| +// Test to make sure the rename overwrites when requested. |
| +TEST_F(DownloadFileTest, RenameOverwrites) { |
| + ASSERT_TRUE(CreateDownloadFile(0, true)); |
| + base::FilePath initial_path(download_file_->FullPath()); |
| + EXPECT_TRUE(base::PathExists(initial_path)); |
| + base::FilePath path_1(initial_path.InsertBeforeExtensionASCII("_1")); |
| + |
| + ASSERT_FALSE(base::PathExists(path_1)); |
| static const char file_data[] = "xyzzy"; |
| - ASSERT_EQ(static_cast<int>(sizeof(file_data) - 1), |
| - base::WriteFile(path_5, file_data, sizeof(file_data) - 1)); |
| - ASSERT_TRUE(base::PathExists(path_5)); |
| - EXPECT_TRUE(base::ReadFileToString(path_5, &file_contents)); |
| - EXPECT_EQ(std::string(file_data), file_contents); |
| + ASSERT_EQ(static_cast<int>(sizeof(file_data)), |
| + base::WriteFile(path_1, file_data, sizeof(file_data))); |
| + ASSERT_TRUE(base::PathExists(path_1)); |
| + base::FilePath new_path; |
| EXPECT_EQ(DOWNLOAD_INTERRUPT_REASON_NONE, |
| - RenameAndAnnotate(path_5, &output_path)); |
| - EXPECT_EQ(path_5, output_path); |
| + RenameAndAnnotate(path_1, &new_path)); |
| + EXPECT_EQ(path_1.value(), new_path.value()); |
| - file_contents = ""; |
| - EXPECT_TRUE(base::ReadFileToString(path_5, &file_contents)); |
| + 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); |
| } |
| @@ -451,16 +512,35 @@ TEST_F(DownloadFileTest, RenameUniquifies) { |
| 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_F(DownloadFileTest, RenameError) { |
| +TEST_P(DownloadFileTestWithRename, 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(base::CreateDirectory(tempdir)); |
| - base::FilePath target_path(tempdir.Append(initial_path.BaseName())); |
| + 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( |
| @@ -470,13 +550,13 @@ TEST_F(DownloadFileTest, RenameError) { |
| // Make the directory unwritable and try to rename within it. |
| { |
| - file_util::PermissionRestorer restorer(tempdir); |
| - ASSERT_TRUE(file_util::MakeFileUnwritable(tempdir)); |
| + file_util::PermissionRestorer restorer(target_dir); |
| + ASSERT_TRUE(file_util::MakeFileUnwritable(target_dir)); |
| // Expect nulling out of further processing. |
| EXPECT_CALL(*input_stream_, RegisterCallback(IsNullCallback())); |
| EXPECT_EQ(DOWNLOAD_INTERRUPT_REASON_FILE_ACCESS_DENIED, |
| - RenameAndAnnotate(target_path, NULL)); |
| + InvokeSelectedRenameMethod(target_path, NULL)); |
| EXPECT_FALSE(base::PathExists(target_path_suffixed)); |
| } |
| @@ -485,6 +565,87 @@ TEST_F(DownloadFileTest, RenameError) { |
| 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() |
| +// inbetween the retry tasks to stagger them and then allow the rename to |
|
Randy Smith (Not in Mondays)
2014/06/12 22:10:30
nit: in between.
asanka
2014/06/17 21:40:57
Done.
|
| +// succeed. |
|
Randy Smith (Not in Mondays)
2014/06/12 22:10:30
Suggestion: I had a hard time groking the threadin
asanka
2014/06/17 21:40:57
Added comments as suggested. Hopefully it's more r
|
| +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; |
| + base::RunLoop succeeding_run; |
| + { |
| +#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. |
| + file_util::PermissionRestorer restore_permissions_for(target_dir); |
| + ASSERT_TRUE(file_util::MakeFileUnwritable(target_dir)); |
| +#endif |
| + |
| + base::RunLoop first_failing_run; |
| + base::MessageLoop::current()->PostTask(FROM_HERE, |
| + first_failing_run.QuitClosure()); |
| + ((*download_file_).*GetParam())(target_path, |
| + base::Bind(&TestRenameCompletionCallback, |
| + succeeding_run.QuitClosure(), |
| + &did_run_callback)); |
| + EXPECT_FALSE(did_run_callback); |
| + |
| + // This should quit because the QuitClosure() from first_failing_run is run. |
| + // The QuitClosure() from succeeding_run shouldn't be run since |
| + // download_file_ is still retrying the request. |
| + first_failing_run.Run(); |
| + EXPECT_FALSE(did_run_callback); |
| + |
| + // Running another loop should have the same effect 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 since the |
| + // target directory is now writeable. |
| + 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)); |