Index: chrome/browser/download/download_browsertest.cc |
diff --git a/chrome/browser/download/download_browsertest.cc b/chrome/browser/download/download_browsertest.cc |
index 2f9700c83d79af482bc19b27fddcbf3416248131..65c1164a9b9e6b3b246d8e714bb3a8b4b12d327b 100644 |
--- a/chrome/browser/download/download_browsertest.cc |
+++ b/chrome/browser/download/download_browsertest.cc |
@@ -6,6 +6,7 @@ |
#include "base/bind.h" |
#include "base/bind_helpers.h" |
+#include "base/command_line.h" |
#include "base/file_util.h" |
#include "base/files/file_path.h" |
#include "base/files/scoped_temp_dir.h" |
@@ -68,6 +69,7 @@ |
#include "content/public/browser/render_view_host.h" |
#include "content/public/browser/resource_context.h" |
#include "content/public/browser/web_contents.h" |
+#include "content/public/common/content_switches.h" |
#include "content/public/common/context_menu_params.h" |
#include "content/public/common/page_transition_types.h" |
#include "content/public/test/browser_test_utils.h" |
@@ -185,6 +187,64 @@ class PercentWaiter : public content::DownloadItem::Observer { |
DISALLOW_COPY_AND_ASSIGN(PercentWaiter); |
}; |
+// DownloadTestObserver subclass that observes one download until it transitions |
+// from a non-resumable state to a resumable state a specified number of |
+// times. Note that this observer can only observe a single download. |
+class DownloadTestObserverResumable : public content::DownloadTestObserver { |
+ public: |
+ // Construct a new observer. |transition_count| is the number of times the |
+ // download should transition from a non-resumable state to a resumable state. |
+ DownloadTestObserverResumable(DownloadManager* download_manager, |
+ size_t transition_count) |
+ : DownloadTestObserver(download_manager, 1, |
+ ON_DANGEROUS_DOWNLOAD_FAIL), |
+ was_previously_resumable_(false), |
+ transitions_left_(transition_count) { |
+ Init(); |
+ } |
+ virtual ~DownloadTestObserverResumable() {} |
+ |
+ private: |
+ virtual bool IsDownloadInFinalState(DownloadItem* download) OVERRIDE { |
+ bool is_resumable_now = download->CanResume(); |
+ if (!was_previously_resumable_ && is_resumable_now) |
+ --transitions_left_; |
+ was_previously_resumable_ = is_resumable_now; |
+ return transitions_left_ == 0; |
+ } |
+ |
+ bool was_previously_resumable_; |
+ size_t transitions_left_; |
+ |
+ DISALLOW_COPY_AND_ASSIGN(DownloadTestObserverResumable); |
+}; |
+ |
+// DownloadTestObserver subclass that observes a download until it transitions |
+// from IN_PROGRESS to another state, but only after StartObserving() is called. |
+class DownloadTestObserverNotInProgress : public content::DownloadTestObserver { |
+ public: |
+ DownloadTestObserverNotInProgress(DownloadManager* download_manager, |
+ size_t count) |
+ : DownloadTestObserver(download_manager, count, |
+ ON_DANGEROUS_DOWNLOAD_FAIL), |
+ started_observing_(false) { |
+ Init(); |
+ } |
+ virtual ~DownloadTestObserverNotInProgress() {} |
+ |
+ void StartObserving() { |
+ started_observing_ = true; |
+ } |
+ |
+ private: |
+ virtual bool IsDownloadInFinalState(DownloadItem* download) OVERRIDE { |
+ return started_observing_ && |
+ download->GetState() != DownloadItem::IN_PROGRESS; |
+ } |
+ |
+ bool started_observing_; |
+}; |
+ |
// IDs and paths of CRX files used in tests. |
const char kGoodCrxId[] = "ldnnhddmnhbkjipkidpdiheffobcpfmf"; |
const base::FilePath kGoodCrxPath(FILE_PATH_LITERAL("extensions/good.crx")); |
@@ -1005,6 +1065,49 @@ class DownloadTest : public InProcessBrowserTest { |
browser()->tab_strip_model()->GetActiveWebContents())); |
} |
+ // This method: |
+ // * Starts a mock download by navigating browser() to a URLRequestMockHTTPJob |
+ // mock URL. |
+ // * Injects |error| on the first write using |error_injector|. |
+ // * Waits for the download to be interrupted. |
+ // * Clears the errors on |error_injector|. |
+ // * Returns the resulting interrupted download. |
+ DownloadItem* StartMockDownloadAndInjectError( |
+ content::TestFileErrorInjector* error_injector, |
+ content::DownloadInterruptReason error) { |
+ base::FilePath file_path(FILE_PATH_LITERAL("download-test1.lib")); |
+ GURL url = URLRequestMockHTTPJob::GetMockUrl(file_path); |
+ |
+ content::TestFileErrorInjector::FileErrorInfo error_info; |
+ error_info.url = url.spec(); |
+ error_info.code = content::TestFileErrorInjector::FILE_OPERATION_WRITE; |
+ error_info.operation_instance = 0; |
+ error_info.error = error; |
+ error_injector->ClearErrors(); |
+ error_injector->AddError(error_info); |
+ error_injector->InjectErrors(); |
+ |
+ scoped_ptr<content::DownloadTestObserver> observer( |
+ new DownloadTestObserverResumable( |
+ DownloadManagerForBrowser(browser()), 1)); |
+ ui_test_utils::NavigateToURL(browser(), url); |
+ observer->WaitForFinished(); |
+ |
+ content::DownloadManager::DownloadVector downloads; |
+ DownloadManagerForBrowser(browser())->GetAllDownloads(&downloads); |
+ EXPECT_EQ(1u, downloads.size()); |
+ |
+ if (downloads.size() != 1) |
+ return NULL; |
+ |
+ error_injector->ClearErrors(); |
+ error_injector->InjectErrors(); |
+ DownloadItem* download = downloads[0]; |
+ EXPECT_EQ(DownloadItem::INTERRUPTED, download->GetState()); |
+ EXPECT_EQ(error, download->GetLastReason()); |
+ return download; |
+ } |
+ |
private: |
static void EnsureNoPendingDownloadJobsOnIO(bool* result) { |
if (URLRequestSlowDownloadJob::NumberOutstandingRequests()) |
@@ -2971,3 +3074,156 @@ IN_PROC_BROWSER_TEST_F(DownloadTest, DownloadPrefs_SaveFilePath) { |
EXPECT_EQ(dir.AppendASCII("on").value(), on_prefs->SaveFilePath().value()); |
EXPECT_EQ(dir.AppendASCII("off").value(), off_prefs->SaveFilePath().value()); |
} |
+ |
+// A download that is interrupted due to a file error should be able to be |
+// resumed. |
+IN_PROC_BROWSER_TEST_F(DownloadTest, Resumption_NoPrompt) { |
+ CommandLine::ForCurrentProcess()->AppendSwitch( |
+ switches::kEnableDownloadResumption); |
+ scoped_refptr<content::TestFileErrorInjector> error_injector( |
+ content::TestFileErrorInjector::Create( |
+ DownloadManagerForBrowser(browser()))); |
+ scoped_ptr<content::DownloadTestObserver> completion_observer( |
+ CreateWaiter(browser(), 1)); |
+ EnableFileChooser(true); |
+ |
+ DownloadItem* download = StartMockDownloadAndInjectError( |
+ error_injector, |
+ content::DOWNLOAD_INTERRUPT_REASON_FILE_FAILED); |
+ ASSERT_TRUE(download); |
+ |
+ download->Resume(); |
+ completion_observer->WaitForFinished(); |
+ |
+ EXPECT_EQ( |
+ 1u, completion_observer->NumDownloadsSeenInState(DownloadItem::COMPLETE)); |
+ EXPECT_FALSE(DidShowFileChooser()); |
+} |
+ |
+// A download that's interrupted due to a reason that indicates that the target |
+// path is invalid or unusable should cause a prompt to be displayed on |
+// resumption. |
+IN_PROC_BROWSER_TEST_F(DownloadTest, Resumption_WithPrompt) { |
+ CommandLine::ForCurrentProcess()->AppendSwitch( |
+ switches::kEnableDownloadResumption); |
+ scoped_refptr<content::TestFileErrorInjector> error_injector( |
+ content::TestFileErrorInjector::Create( |
+ DownloadManagerForBrowser(browser()))); |
+ scoped_ptr<content::DownloadTestObserver> completion_observer( |
+ CreateWaiter(browser(), 1)); |
+ EnableFileChooser(true); |
+ |
+ DownloadItem* download = StartMockDownloadAndInjectError( |
+ error_injector, |
+ content::DOWNLOAD_INTERRUPT_REASON_FILE_NO_SPACE); |
+ ASSERT_TRUE(download); |
+ |
+ download->Resume(); |
+ completion_observer->WaitForFinished(); |
+ |
+ EXPECT_EQ( |
+ 1u, completion_observer->NumDownloadsSeenInState(DownloadItem::COMPLETE)); |
+ EXPECT_TRUE(DidShowFileChooser()); |
+} |
+ |
+// The user shouldn't be prompted on a resumed download unless a prompt is |
+// necessary due to the interrupt reason. |
+IN_PROC_BROWSER_TEST_F(DownloadTest, Resumption_WithPromptAlways) { |
+ CommandLine::ForCurrentProcess()->AppendSwitch( |
+ switches::kEnableDownloadResumption); |
+ browser()->profile()->GetPrefs()->SetBoolean( |
+ prefs::kPromptForDownload, true); |
+ scoped_refptr<content::TestFileErrorInjector> error_injector( |
+ content::TestFileErrorInjector::Create( |
+ DownloadManagerForBrowser(browser()))); |
+ scoped_ptr<content::DownloadTestObserver> completion_observer( |
+ CreateWaiter(browser(), 1)); |
+ EnableFileChooser(true); |
+ |
+ DownloadItem* download = StartMockDownloadAndInjectError( |
+ error_injector, |
+ content::DOWNLOAD_INTERRUPT_REASON_FILE_FAILED); |
+ ASSERT_TRUE(download); |
+ |
+ // Prompts the user initially because of the kPromptForDownload preference. |
+ EXPECT_TRUE(DidShowFileChooser()); |
+ |
+ download->Resume(); |
+ completion_observer->WaitForFinished(); |
+ |
+ EXPECT_EQ( |
+ 1u, completion_observer->NumDownloadsSeenInState(DownloadItem::COMPLETE)); |
+ // Shouldn't prompt for resumption. |
+ EXPECT_FALSE(DidShowFileChooser()); |
+} |
+ |
+// A download that is interrupted due to a transient error should be resumed |
+// automatically. |
+IN_PROC_BROWSER_TEST_F(DownloadTest, Resumption_Automatic) { |
+ CommandLine::ForCurrentProcess()->AppendSwitch( |
+ switches::kEnableDownloadResumption); |
+ scoped_refptr<content::TestFileErrorInjector> error_injector( |
+ content::TestFileErrorInjector::Create( |
+ DownloadManagerForBrowser(browser()))); |
+ |
+ DownloadItem* download = StartMockDownloadAndInjectError( |
+ error_injector, |
+ content::DOWNLOAD_INTERRUPT_REASON_FILE_TRANSIENT_ERROR); |
+ ASSERT_TRUE(download); |
+ |
+ // The number of times this the download is resumed automatically is defined |
+ // in DownloadItemImpl::kMaxAutoResumeAttempts. The number of DownloadFiles |
+ // created should be that number + 1 (for the original download request). We |
+ // only care that it is greater than 1. |
+ EXPECT_GT(1u, error_injector->TotalFileCount()); |
+} |
+ |
+// An interrupting download should be resumable multiple times. |
+IN_PROC_BROWSER_TEST_F(DownloadTest, Resumption_MultipleAttempts) { |
+ CommandLine::ForCurrentProcess()->AppendSwitch( |
+ switches::kEnableDownloadResumption); |
+ scoped_refptr<content::TestFileErrorInjector> error_injector( |
+ content::TestFileErrorInjector::Create( |
+ DownloadManagerForBrowser(browser()))); |
+ scoped_ptr<DownloadTestObserverNotInProgress> completion_observer( |
+ new DownloadTestObserverNotInProgress( |
+ DownloadManagerForBrowser(browser()), 1)); |
+ // Wait for two transitions to a resumable state |
+ scoped_ptr<content::DownloadTestObserver> resumable_observer( |
+ new DownloadTestObserverResumable( |
+ DownloadManagerForBrowser(browser()), 2)); |
+ |
+ EnableFileChooser(true); |
+ DownloadItem* download = StartMockDownloadAndInjectError( |
+ error_injector, |
+ content::DOWNLOAD_INTERRUPT_REASON_FILE_FAILED); |
+ ASSERT_TRUE(download); |
+ |
+ content::TestFileErrorInjector::FileErrorInfo error_info; |
+ error_info.url = download->GetOriginalUrl().spec(); |
+ error_info.code = content::TestFileErrorInjector::FILE_OPERATION_WRITE; |
+ error_info.operation_instance = 0; |
+ error_info.error = content::DOWNLOAD_INTERRUPT_REASON_FILE_FAILED; |
+ error_injector->AddError(error_info); |
+ error_injector->InjectErrors(); |
+ |
+ // Resuming should cause the download to be interrupted again due to the |
+ // errors we are injecting. |
+ download->Resume(); |
+ resumable_observer->WaitForFinished(); |
+ ASSERT_EQ(DownloadItem::INTERRUPTED, download->GetState()); |
+ ASSERT_EQ(content::DOWNLOAD_INTERRUPT_REASON_FILE_FAILED, |
+ download->GetLastReason()); |
+ |
+ error_injector->ClearErrors(); |
+ error_injector->InjectErrors(); |
+ |
+ // No errors this time. The download should complete successfully. |
+ EXPECT_FALSE(completion_observer->IsFinished()); |
+ completion_observer->StartObserving(); |
+ download->Resume(); |
+ completion_observer->WaitForFinished(); |
+ EXPECT_EQ(DownloadItem::COMPLETE, download->GetState()); |
+ |
+ EXPECT_FALSE(DidShowFileChooser()); |
+} |