Chromium Code Reviews| Index: content/browser/renderer_host/render_process_host_impl_unittest.cc |
| diff --git a/content/browser/renderer_host/render_process_host_impl_unittest.cc b/content/browser/renderer_host/render_process_host_impl_unittest.cc |
| new file mode 100644 |
| index 0000000000000000000000000000000000000000..5e22b71c5dce538a7ce941596974c3921054e83d |
| --- /dev/null |
| +++ b/content/browser/renderer_host/render_process_host_impl_unittest.cc |
| @@ -0,0 +1,257 @@ |
| +// Copyright 2013 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 "content/browser/renderer_host/render_process_host_impl.h" |
| + |
| +#include "base/command_line.h" |
| +#include "base/process_util.h" |
| +#include "base/run_loop.h" |
| +#include "content/browser/child_process_launcher.h" |
| +#include "content/browser/loader/resource_dispatcher_host_impl.h" |
| +#include "content/browser/site_instance_impl.h" |
| +#include "content/browser/storage_partition_impl.h" |
| +#include "content/browser/webui/web_ui_controller_factory_registry.h" |
| +#include "content/common/child_process_messages.h" |
| +#include "content/common/view_messages.h" |
| +#include "content/public/browser/render_process_host_factory.h" |
| +#include "content/public/common/sandboxed_process_launcher_delegate.h" |
| +#include "content/public/test/test_browser_context.h" |
| +#include "content/public/test/test_browser_thread.h" |
| +#include "content/test/test_content_browser_client.h" |
| +#include "content/test/test_render_view_host_factory.h" |
| +#include "content/test/test_web_contents.h" |
| +#include "ipc/ipc_switches.h" |
| +#include "ipc/ipc_test_sink.h" |
| +#include "net/url_request/url_request_test_util.h" |
| +#include "testing/gtest/include/gtest/gtest.h" |
| + |
| +namespace content { |
| +namespace { |
| + |
| +class FakeChildProcessLauncher; |
| + |
| +struct LaunchedChildProcess { |
| + // Becomes NULL after the FakeChildProcessLauncher is destroyed. |
| + FakeChildProcessLauncher* launcher_; |
| + // Collects messages sent to the child process. |
| + IPC::TestSink messages_; |
| +}; |
| + |
| +class FakeChildProcessLauncher : public ChildProcessLauncher { |
| + public: |
| + FakeChildProcessLauncher(LaunchedChildProcess* info, |
| + const IPC::ChannelHandle& channel, |
| + Client* client) |
| + : info_(info), |
| + client_(client), |
| + starting_(true), |
| + termination_status_(base::TERMINATION_STATUS_NORMAL_TERMINATION), |
| + channel_(new IPC::ChannelProxy( |
| + channel, |
| + IPC::Channel::MODE_CLIENT, |
| + &info->messages_, |
| + BrowserThread::GetMessageLoopProxyForThread(BrowserThread::IO))), |
| + weak_this_factory_(this) { |
| + info_->launcher_ = this; |
| + |
| + // Automatically start the child process so the RenderProcessHostImpl sends |
| + // messages to it. To mimic real child processes, don't start instantly. |
| + base::MessageLoopProxy::current()->PostTask( |
| + FROM_HERE, |
| + base::Bind(&FakeChildProcessLauncher::Start, |
| + weak_this_factory_.GetWeakPtr())); |
| + } |
| + |
| + virtual ~FakeChildProcessLauncher() { info_->launcher_ = NULL; } |
| + |
| + void Send(IPC::Message* msg) { |
| + EXPECT_TRUE(channel_->Send(msg)); |
| + } |
| + |
| + // Implementation of ChildProcessLauncher. |
| + virtual bool IsStarting() OVERRIDE { return starting_; } |
| + virtual base::ProcessHandle GetHandle() OVERRIDE { |
| + return base::Process::Current().handle(); |
| + } |
| + virtual base::TerminationStatus GetChildTerminationStatus(bool known_dead, |
| + int* exit_code) |
| + OVERRIDE { |
| + return termination_status_; |
| + } |
| + virtual void SetProcessBackgrounded(bool background) OVERRIDE {} |
| + virtual void SetTerminateChildOnShutdown(bool terminate_on_shutdown) |
| + OVERRIDE {} |
| + |
| + private: |
| + void Start() { |
| + starting_ = false; |
| + client_->OnProcessLaunched(); |
| + } |
| + |
| + LaunchedChildProcess* info_; |
| + Client* client_; |
| + bool starting_; |
| + base::TerminationStatus termination_status_; |
| + scoped_ptr<IPC::ChannelProxy> channel_; |
| + base::WeakPtrFactory<FakeChildProcessLauncher> weak_this_factory_; |
| +}; |
| + |
| +scoped_ptr<ChildProcessLauncher> NewFakeChildProcessLauncher( |
| + ScopedVector<LaunchedChildProcess>* child_processes, |
| +#if defined(OS_WIN) |
| + SandboxedProcessLauncherDelegate* delegate, |
| +#elif defined(OS_POSIX) |
| + bool use_zygote, |
| + const base::EnvironmentVector& environ, |
| + int ipcfd, |
| +#endif |
| + CommandLine* cmd_line, |
| + int child_process_id, |
| + ChildProcessLauncher::Client* client) { |
| +#if defined(OS_WIN) |
| + delete delegate; |
| +#endif |
| + scoped_ptr<CommandLine> cmd_line_deleter(cmd_line); |
| + child_processes->push_back(new LaunchedChildProcess()); |
| + std::string channel_id = |
| + cmd_line->GetSwitchValueASCII(switches::kProcessChannelID); |
| + return scoped_ptr<ChildProcessLauncher>(new FakeChildProcessLauncher( |
| + child_processes->back(), |
| + IPC::ChannelHandle(channel_id |
| +#if defined(OS_POSIX) |
| + , |
| + base::FileDescriptor(ipcfd, false) |
| +#endif |
| + ), |
| + client)); |
| +} |
| + |
| +class RPHIBrowserContext : public TestBrowserContext { |
| + private: |
| + virtual net::URLRequestContextGetter* GetRequestContextForRenderProcess( |
| + int renderer_child_id) OVERRIDE { |
| + return GetRequestContext(); |
| + } |
| +}; |
| + |
| +class RPHIBrowserClient : public TestContentBrowserClient { |
| + public: |
| + RPHIBrowserClient() |
| + : request_context_getter_(new net::TestURLRequestContextGetter( |
| + BrowserThread::GetMessageLoopProxyForThread(BrowserThread::IO))) {} |
| + |
| + virtual net::URLRequestContextGetter* CreateRequestContext( |
| + BrowserContext* browser_context, |
| + ProtocolHandlerMap* protocol_handlers) OVERRIDE { |
| + return request_context_getter_.get(); |
| + } |
| + |
| + private: |
| + scoped_refptr<net::TestURLRequestContextGetter> request_context_getter_; |
| +}; |
| + |
| +class FakeChildProcessRPHFactory : public RenderProcessHostFactory { |
| + public: |
| + FakeChildProcessRPHFactory( |
| + ScopedVector<LaunchedChildProcess>* child_processes, |
| + SiteInstance* site_instance) |
| + : child_processes_(child_processes), site_instance_(site_instance) { |
| + SiteInstanceImpl::set_render_process_host_factory(this); |
| + } |
| + virtual ~FakeChildProcessRPHFactory() { |
| + SiteInstanceImpl::set_render_process_host_factory(NULL); |
| + } |
| + |
| + virtual RenderProcessHost* CreateRenderProcessHost( |
| + BrowserContext* browser_context) const OVERRIDE { |
| + RenderProcessHostImpl* host = |
| + new RenderProcessHostImpl(browser_context, |
| + static_cast<StoragePartitionImpl*>( |
| + TestBrowserContext::GetStoragePartition( |
| + browser_context, site_instance_)), |
| + false, |
| + false); |
| + host->SetChildProcessLauncherFactory( |
| + base::Bind(&NewFakeChildProcessLauncher, child_processes_)); |
| + return host; |
| + } |
| + |
| + private: |
| + ScopedVector<LaunchedChildProcess>* child_processes_; |
| + SiteInstance* site_instance_; |
|
Charlie Reis
2013/06/05 19:07:10
It's not clear why this needs a SiteInstance, sinc
Jeffrey Yasskin
2013/06/05 21:23:27
Sure, done.
Another possible refactoring would be
Charlie Reis
2013/06/05 21:54:09
Good point! That seems like a better solution, so
Jeffrey Yasskin
2013/06/05 22:53:20
I've put that in https://codereview.chromium.org/1
|
| +}; |
| + |
| +class RenderProcessHostImplTest : public testing::Test { |
| + public: |
| + RenderProcessHostImplTest() |
| + : ui_thread_(BrowserThread::UI, &message_loop_), |
| + webkit_thread_(BrowserThread::WEBKIT_DEPRECATED, &message_loop_), |
| + file_user_blocking_thread_(BrowserThread::FILE_USER_BLOCKING, |
| + &message_loop_), |
| + io_thread_(BrowserThread::IO, &message_loop_), |
| + old_browser_client_(SetBrowserClientForTesting(&test_browser_client_)) { |
| + } |
| + |
| + virtual ~RenderProcessHostImplTest() { |
| + base::RunLoop().RunUntilIdle(); |
| + SetBrowserClientForTesting(old_browser_client_); |
| + } |
| + |
| + protected: |
| + ScopedVector<LaunchedChildProcess> child_processes_; |
| + base::MessageLoopForIO message_loop_; |
| + TestBrowserThread ui_thread_; |
| + TestBrowserThread webkit_thread_; |
| + TestBrowserThread file_user_blocking_thread_; |
| + TestBrowserThread io_thread_; |
| + |
| + RPHIBrowserContext browser_context_; |
| + RPHIBrowserClient test_browser_client_; |
| + ContentBrowserClient* old_browser_client_; |
| + ResourceDispatcherHostImpl resource_dispatcher_host_; |
| + |
| + DISALLOW_COPY_AND_ASSIGN(RenderProcessHostImplTest); |
| +}; |
| + |
| +// http://crbug.com/87176 |
| +TEST_F(RenderProcessHostImplTest, |
| + RejectShutdownFromChildProcessWithOneActiveView) { |
| + const GURL kUrl("http://example.com"); |
| + |
| + scoped_refptr<SiteInstance> site( |
| + SiteInstance::CreateForURL(&browser_context_, kUrl)); |
| + FakeChildProcessRPHFactory rph_factory(&child_processes_, site.get()); |
| + |
| + WebContents::CreateParams params(&browser_context_, site.get()); |
| + scoped_ptr<WebContentsImpl> tab( |
| + WebContentsImpl::CreateWithOpener(params, NULL)); |
| + tab->GetController().LoadURL(kUrl, Referrer(), PAGE_TRANSITION_TYPED, ""); |
|
Charlie Reis
2013/06/05 19:07:10
How does example.com commit? Am I missing a part
Jeffrey Yasskin
2013/06/05 21:23:27
No, AFAIK, there's no hard-coded response for it,
Charlie Reis
2013/06/05 21:54:09
Well, we usually use a TestWebContents and call Te
Jeffrey Yasskin
2013/06/05 22:53:20
I started this test with a TestWebContents, but Te
|
| + base::RunLoop().RunUntilIdle(); |
| + ASSERT_EQ(1U, child_processes_.size()); |
| + |
| + child_processes_[0]->messages_.ClearMessages(); |
| + |
| + // Double-check that the loading tab isn't pending. |
| + EXPECT_EQ(NULL, |
|
Charlie Reis
2013/06/05 19:07:10
Any reason not to use EXPECT_FALSE? That seems to
Jeffrey Yasskin
2013/06/05 21:23:27
No particular reason. Done.
|
| + tab->GetRenderManagerForTesting()->pending_render_view_host()); |
| + EXPECT_EQ(1, |
| + static_cast<RenderProcessHostImpl*>( |
| + tab->GetSiteInstance()->GetProcess())->GetActiveViewCount()); |
| + |
| + // Sometimes the renderer process's ShutdownRequest (corresponding to the |
| + // ViewMsg_WasSwappedOut from a previous navigation) doesn't arrive until |
|
Charlie Reis
2013/06/05 19:07:10
Interesting. I like that we don't have to simulat
|
| + // after the browser process decides to re-use the renderer for a new purpose. |
| + // This test makes sure the browser doesn't let the renderer die in that case. |
| + child_processes_[0]->launcher_ |
| + ->Send(new ChildProcessHostMsg_ShutdownRequest()); |
|
Charlie Reis
2013/06/05 19:07:10
nit: -> on previous line
Jeffrey Yasskin
2013/06/05 21:23:27
Done. Since clang-format puts the -> at the beginn
Charlie Reis
2013/06/05 21:54:09
Thanks. Not sure if there's an official rule on t
|
| + base::RunLoop().RunUntilIdle(); |
| + EXPECT_EQ(NULL, |
|
Charlie Reis
2013/06/05 19:07:10
EXPECT_FALSE?
Jeffrey Yasskin
2013/06/05 21:23:27
Done.
|
| + child_processes_[0]->messages_.GetFirstMessageMatching( |
| + ChildProcessMsg_Shutdown::ID)) |
| + << "Host sent a message confirming that the renderer process should shut " |
| + << "down even though it was still being used."; |
| +} |
| + |
| +} // namespace |
| +} // namespace content |