Index: chrome/browser/profiles/profile_browsertest.cc |
diff --git a/chrome/browser/profiles/profile_browsertest.cc b/chrome/browser/profiles/profile_browsertest.cc |
index 46623e783b4dc19b8a3dbd2c7201abcc37c4d226..0166e9741e86873f549bd46fe63fc8717312e31c 100644 |
--- a/chrome/browser/profiles/profile_browsertest.cc |
+++ b/chrome/browser/profiles/profile_browsertest.cc |
@@ -8,6 +8,10 @@ |
#include "base/files/file_util.h" |
#include "base/files/scoped_temp_dir.h" |
#include "base/json/json_reader.h" |
+#include "base/logging.h" |
+#include "base/macros.h" |
+#include "base/memory/ref_counted.h" |
+#include "base/memory/scoped_ptr.h" |
#include "base/prefs/pref_service.h" |
#include "base/sequenced_task_runner.h" |
#include "base/synchronization/waitable_event.h" |
@@ -15,18 +19,30 @@ |
#include "base/version.h" |
#include "chrome/browser/browser_process.h" |
#include "chrome/browser/chrome_notification_types.h" |
+#include "chrome/browser/net/url_request_mock_util.h" |
#include "chrome/browser/profiles/chrome_version_service.h" |
#include "chrome/browser/profiles/profile_impl.h" |
#include "chrome/browser/profiles/profile_manager.h" |
#include "chrome/browser/profiles/startup_task_runner_service.h" |
#include "chrome/browser/profiles/startup_task_runner_service_factory.h" |
+#include "chrome/browser/ui/browser.h" |
+#include "chrome/browser/ui/tabs/tab_strip_model.h" |
#include "chrome/common/chrome_constants.h" |
#include "chrome/common/chrome_version_info.h" |
#include "chrome/common/pref_names.h" |
#include "chrome/test/base/in_process_browser_test.h" |
+#include "chrome/test/base/ui_test_utils.h" |
+#include "content/public/browser/browser_thread.h" |
#include "content/public/test/test_utils.h" |
+#include "net/base/net_errors.h" |
+#include "net/test/url_request/url_request_failed_job.h" |
+#include "net/url_request/url_fetcher.h" |
+#include "net/url_request/url_fetcher_delegate.h" |
+#include "net/url_request/url_request_context_getter.h" |
+#include "net/url_request/url_request_status.h" |
#include "testing/gmock/include/gmock/gmock.h" |
#include "testing/gtest/include/gtest/gtest.h" |
+#include "url/gurl.h" |
#if defined(OS_CHROMEOS) |
#include "chrome/browser/chromeos/profiles/profile_helper.h" |
@@ -35,6 +51,49 @@ |
namespace { |
+// Simple URLFetcherDelegate with an expected final status and the ability to |
+// wait until a request completes. It's not considered a failure for the request |
+// to never complete. |
davidben
2015/05/14 17:09:27
This comment is a little confusing since the metho
mmenke
2015/05/14 19:23:52
Done - I think the last sentence is a bit unexpect
|
+class TestURLFetcherDelegate : public net::URLFetcherDelegate { |
+ public: |
+ // Creating the TestURLFetcherDelegate automatically creates and starts a |
+ // URLFetcher. |
+ TestURLFetcherDelegate( |
+ scoped_refptr<net::URLRequestContextGetter> context_getter, |
+ const GURL& url, |
+ net::URLRequestStatus expected_request_status) |
+ : expected_request_status_(expected_request_status), |
+ is_complete_(false), |
+ fetcher_(net::URLFetcher::Create(url, net::URLFetcher::GET, this)) { |
+ fetcher_->SetRequestContext(context_getter.get()); |
+ fetcher_->Start(); |
+ } |
+ |
+ ~TestURLFetcherDelegate() override {} |
+ |
+ void OnURLFetchComplete(const net::URLFetcher* source) override { |
+ EXPECT_EQ(expected_request_status_.status(), source->GetStatus().status()); |
+ EXPECT_EQ(expected_request_status_.error(), source->GetStatus().error()); |
+ |
+ run_loop_.Quit(); |
+ } |
+ |
+ void WaitForCompletion() { |
+ run_loop_.Run(); |
+ } |
+ |
+ bool is_complete() const { return is_complete_; } |
+ |
+ private: |
+ const net::URLRequestStatus expected_request_status_; |
+ base::RunLoop run_loop_; |
+ |
+ bool is_complete_; |
+ scoped_ptr<net::URLFetcher> fetcher_; |
+ |
+ DISALLOW_COPY_AND_ASSIGN(TestURLFetcherDelegate); |
+}; |
+ |
class MockProfileDelegate : public Profile::Delegate { |
public: |
MOCK_METHOD1(OnPrefsLoaded, void(Profile*)); |
@@ -100,6 +159,20 @@ class ProfileBrowserTest : public InProcessBrowserTest { |
#endif |
} |
+ // content::BrowserTestBase implementation: |
+ |
+ void SetUpOnMainThread() override { |
+ content::BrowserThread::PostTask( |
+ content::BrowserThread::IO, FROM_HERE, |
+ base::Bind(&chrome_browser_net::SetUrlRequestMocksEnabled, true)); |
+ } |
+ |
+ void TearDownOnMainThread() override { |
+ content::BrowserThread::PostTask( |
+ content::BrowserThread::IO, FROM_HERE, |
+ base::Bind(&chrome_browser_net::SetUrlRequestMocksEnabled, false)); |
+ } |
+ |
scoped_ptr<Profile> CreateProfile( |
const base::FilePath& path, |
Profile::Delegate* delegate, |
@@ -119,7 +192,83 @@ class ProfileBrowserTest : public InProcessBrowserTest { |
SpinThreads(); |
} |
+ // Starts a test where a URLFetcher is active during profile shutdown. The |
+ // test completes during teardown of the test fixture. The request should be |
+ // canceled by |context_getter| during profile shutdown, before the |
+ // URLRequestContext is destroyed. If that doesn't happen, the Context's |
+ // will still have oustanding requests during its destruction, and will |
+ // trigger a CHECK failure. |
+ void StartActiveFetcherDuringProfileShutdownTest( |
+ scoped_refptr<net::URLRequestContextGetter> context_getter) { |
+ // This method should only be called once per test. |
+ DCHECK(!url_fetcher_delegate_); |
+ |
+ // Start a hanging request. This request may or may not completed before |
+ // the end of the request. |
+ url_fetcher_delegate_.reset(new TestURLFetcherDelegate( |
+ context_getter.get(), |
+ net::URLRequestFailedJob::GetMockHttpUrl(net::ERR_IO_PENDING), |
+ net::URLRequestStatus(net::URLRequestStatus::CANCELED, |
+ net::ERR_CONTEXT_SHUT_DOWN))); |
+ |
+ // Start a second mock request that just fails, and wait for it to complete. |
+ // This ensures the first request has reached the network stack. |
+ TestURLFetcherDelegate url_fetcher_delegate2( |
+ context_getter.get(), |
+ net::URLRequestFailedJob::GetMockHttpUrl(net::ERR_FAILED), |
+ net::URLRequestStatus(net::URLRequestStatus::FAILED, |
+ net::ERR_FAILED)); |
+ url_fetcher_delegate2.WaitForCompletion(); |
davidben
2015/05/14 17:09:27
[Just to confirm, without this line, the test stil
mmenke
2015/05/14 19:23:52
Discussed this offline, clarified comment.
|
+ |
+ // The first request should still be hung. |
+ EXPECT_FALSE(url_fetcher_delegate_->is_complete()); |
+ } |
+ |
+ // Runs a test where an incognito profile's URLFetcher is active during |
+ // teardown of the profile, and makes sure the request fails as expected. |
+ // Also tries issuing a request after the incognito profile has been |
+ // destroyed. |
+ static void RunURLFetcherActiveDuringIncognitoTeardownTest( |
+ Browser* incognito_browser, |
+ scoped_refptr<net::URLRequestContextGetter> context_getter) { |
+ // Start a hanging request. |
+ TestURLFetcherDelegate url_fetcher_delegate1( |
+ context_getter.get(), |
+ net::URLRequestFailedJob::GetMockHttpUrl(net::ERR_IO_PENDING), |
+ net::URLRequestStatus(net::URLRequestStatus::CANCELED, |
+ net::ERR_CONTEXT_SHUT_DOWN)); |
+ |
+ // Start a second mock request that just fails, and wait for it to complete. |
+ // This ensures the first request has reached the network stack. |
+ TestURLFetcherDelegate url_fetcher_delegate2( |
+ context_getter.get(), |
+ net::URLRequestFailedJob::GetMockHttpUrl(net::ERR_FAILED), |
+ net::URLRequestStatus(net::URLRequestStatus::FAILED, |
+ net::ERR_FAILED)); |
+ url_fetcher_delegate2.WaitForCompletion(); |
+ |
+ // The first request should still be hung. |
+ EXPECT_FALSE(url_fetcher_delegate1.is_complete()); |
+ |
+ // Close all incognito tabs, starting profile shutdown. |
+ incognito_browser->tab_strip_model()->CloseAllTabs(); |
+ |
+ // The request should have been canceled when the Profile shut down. |
+ url_fetcher_delegate1.WaitForCompletion(); |
+ |
+ // Requests issued after Profile shutdown should fail in a similar manner. |
+ TestURLFetcherDelegate url_fetcher_delegate3( |
+ context_getter.get(), |
+ net::URLRequestFailedJob::GetMockHttpUrl(net::ERR_IO_PENDING), |
+ net::URLRequestStatus(net::URLRequestStatus::CANCELED, |
+ net::ERR_CONTEXT_SHUT_DOWN)); |
+ url_fetcher_delegate3.WaitForCompletion(); |
+ } |
+ |
scoped_refptr<base::SequencedTaskRunner> profile_io_task_runner_; |
+ |
+ // URLFetcherDelegate that outlives the Profile, to test shutdown. |
+ scoped_ptr<TestURLFetcherDelegate> url_fetcher_delegate_; |
}; |
// Test OnProfileCreate is called with is_new_profile set to true when |
@@ -409,3 +558,46 @@ IN_PROC_BROWSER_TEST_F(ProfileBrowserTest, |
} |
#endif // defined(USE_X11) || defined(OS_WIN) || defined(USE_OZONE) |
+ |
+// The following tests make sure that it's safe to shut down while one of the |
+// Profile's URLRequestContextGetters is in use by a URLFetcher. |
+ |
+IN_PROC_BROWSER_TEST_F(ProfileBrowserTest, |
+ URLFetcherUsingMainContextDuringShutdown) { |
+ StartActiveFetcherDuringProfileShutdownTest( |
+ browser()->profile()->GetRequestContext()); |
+} |
+ |
+IN_PROC_BROWSER_TEST_F(ProfileBrowserTest, |
+ URLFetcherUsingMediaContextDuringShutdown) { |
+ StartActiveFetcherDuringProfileShutdownTest( |
+ browser()->profile()->GetMediaRequestContext()); |
+} |
+ |
+IN_PROC_BROWSER_TEST_F(ProfileBrowserTest, |
+ URLFetcherUsingExtensionContextDuringShutdown) { |
+ StartActiveFetcherDuringProfileShutdownTest( |
+ browser()->profile()->GetRequestContextForExtensions()); |
+} |
+ |
+// The following tests make sure that it's safe to destroy an incognito profile |
+// while one of the its URLRequestContextGetters is in use by a URLFetcher. |
+ |
+IN_PROC_BROWSER_TEST_F(ProfileBrowserTest, |
+ URLFetcherUsingMainContextDuringIncognitoTeardown) { |
+ Browser* incognito_browser = |
+ ui_test_utils::OpenURLOffTheRecord(browser()->profile(), |
+ GURL("about:blank")); |
+ RunURLFetcherActiveDuringIncognitoTeardownTest( |
+ incognito_browser, incognito_browser->profile()->GetRequestContext()); |
+} |
+ |
+IN_PROC_BROWSER_TEST_F(ProfileBrowserTest, |
+ URLFetcherUsingExtensionContextDuringIncognitoTeardown) { |
+ Browser* incognito_browser = |
+ ui_test_utils::OpenURLOffTheRecord(browser()->profile(), |
+ GURL("about:blank")); |
+ RunURLFetcherActiveDuringIncognitoTeardownTest( |
+ incognito_browser, |
+ incognito_browser->profile()->GetRequestContextForExtensions()); |
+} |