| // Copyright 2016 The Chromium Embedded Framework 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 "include/base/cef_bind.h" |
| #include "include/cef_task.h" |
| #include "include/cef_thread.h" |
| #include "include/wrapper/cef_closure_task.h" |
| #include "tests/ceftests/test_handler.h" |
| #include "tests/gtest/include/gtest/gtest.h" |
| #include "tests/shared/browser/client_app_browser.h" |
| #include "tests/shared/renderer/client_app_renderer.h" |
| |
| using client::ClientAppBrowser; |
| using client::ClientAppRenderer; |
| |
| namespace { |
| |
| // Base class for creating and testing threads. |
| class ThreadTest : public base::RefCountedThreadSafe<ThreadTest> { |
| public: |
| ThreadTest() {} |
| virtual ~ThreadTest() {} |
| |
| // Create the test thread. Should only be called one time. |
| void CreateTestThread() { |
| EXPECT_TRUE(!thread_.get()); |
| |
| owner_task_runner_ = CefTaskRunner::GetForCurrentThread(); |
| EXPECT_TRUE(owner_task_runner_.get()); |
| EXPECT_TRUE(owner_task_runner_->BelongsToCurrentThread()); |
| |
| thread_ = CefThread::CreateThread("test_thread"); |
| EXPECT_TRUE(thread_.get()); |
| EXPECT_TRUE(thread_->IsRunning()); |
| |
| thread_id_ = thread_->GetPlatformThreadId(); |
| EXPECT_NE(thread_id_, kInvalidPlatformThreadId); |
| |
| thread_task_runner_ = thread_->GetTaskRunner(); |
| EXPECT_TRUE(thread_task_runner_.get()); |
| |
| AssertOwnerThread(); |
| } |
| |
| // Destroy the test thread. Should only be called one time. |
| void DestroyTestThread() { |
| EXPECT_TRUE(thread_.get()); |
| AssertOwnerThread(); |
| |
| EXPECT_TRUE(thread_->IsRunning()); |
| thread_->Stop(); |
| EXPECT_FALSE(thread_->IsRunning()); |
| |
| AssertOwnerThread(); |
| |
| thread_ = nullptr; |
| } |
| |
| // Execute |test_task| on the test thread. After execution |callback| will be |
| // posted to |callback_task_runner|. |
| void PostOnTestThreadAndCallback( |
| const base::Closure& test_task, |
| CefRefPtr<CefTaskRunner> callback_task_runner, |
| const base::Closure& callback) { |
| EXPECT_TRUE(thread_.get()); |
| thread_task_runner_->PostTask(CefCreateClosureTask( |
| base::Bind(&ThreadTest::ExecuteOnTestThread, this, test_task, |
| callback_task_runner, callback))); |
| } |
| |
| CefRefPtr<CefTaskRunner> owner_task_runner() const { |
| return owner_task_runner_; |
| } |
| CefRefPtr<CefTaskRunner> thread_task_runner() const { |
| return thread_task_runner_; |
| } |
| |
| // Assert that we're running on the owner thread. |
| void AssertOwnerThread() { |
| EXPECT_TRUE(owner_task_runner_->BelongsToCurrentThread()); |
| EXPECT_FALSE(thread_task_runner_->BelongsToCurrentThread()); |
| EXPECT_TRUE(thread_task_runner_->IsSame(thread_->GetTaskRunner())); |
| EXPECT_EQ(thread_id_, thread_->GetPlatformThreadId()); |
| } |
| |
| // Assert that we're running on the test thread. |
| void AssertTestThread() { |
| EXPECT_FALSE(owner_task_runner_->BelongsToCurrentThread()); |
| EXPECT_TRUE(thread_task_runner_->BelongsToCurrentThread()); |
| EXPECT_TRUE(thread_task_runner_->IsSame(thread_->GetTaskRunner())); |
| EXPECT_EQ(thread_id_, thread_->GetPlatformThreadId()); |
| } |
| |
| private: |
| // Helper for PostOnTestThreadAndCallback(). |
| void ExecuteOnTestThread(const base::Closure& test_task, |
| CefRefPtr<CefTaskRunner> callback_task_runner, |
| const base::Closure& callback) { |
| AssertTestThread(); |
| |
| test_task.Run(); |
| |
| callback_task_runner->PostTask(CefCreateClosureTask(callback)); |
| } |
| |
| CefRefPtr<CefTaskRunner> owner_task_runner_; |
| |
| CefRefPtr<CefThread> thread_; |
| cef_platform_thread_id_t thread_id_; |
| CefRefPtr<CefTaskRunner> thread_task_runner_; |
| |
| DISALLOW_COPY_AND_ASSIGN(ThreadTest); |
| }; |
| |
| } // namespace |
| |
| // Test thread creation and destruction without any task execution. |
| TEST(ThreadTest, Create) { |
| scoped_refptr<ThreadTest> thread_test = new ThreadTest(); |
| thread_test->CreateTestThread(); |
| thread_test->DestroyTestThread(); |
| thread_test = nullptr; |
| } |
| |
| namespace { |
| |
| // Simple implementation of ThreadTest that creates a thread, executes tasks |
| // on the thread, then destroys the thread after all tasks have completed. |
| class SimpleThreadTest : public ThreadTest { |
| public: |
| SimpleThreadTest(size_t expected_task_count, |
| const base::Closure& task_callback, |
| const base::Closure& done_callback) |
| : expected_task_count_(expected_task_count), |
| task_callback_(task_callback), |
| done_callback_(done_callback), |
| got_task_count_(0U), |
| got_done_count_(0U) {} |
| |
| void RunTest() { |
| // Create the test thread. |
| CreateTestThread(); |
| |
| for (size_t i = 0U; i < expected_task_count_; ++i) { |
| // Execute Task() on the test thread and then call Done() on this thread. |
| PostOnTestThreadAndCallback(base::Bind(&SimpleThreadTest::Task, this), |
| owner_task_runner(), |
| base::Bind(&SimpleThreadTest::Done, this)); |
| } |
| } |
| |
| void DestroyTest() { |
| EXPECT_EQ(expected_task_count_, got_task_count_); |
| EXPECT_EQ(expected_task_count_, got_done_count_); |
| |
| // Destroy the test thread. |
| DestroyTestThread(); |
| } |
| |
| private: |
| void Task() { |
| AssertTestThread(); |
| got_task_count_++; |
| if (!task_callback_.is_null()) |
| task_callback_.Run(); |
| } |
| |
| void Done() { |
| AssertOwnerThread(); |
| if (++got_done_count_ == expected_task_count_ && !done_callback_.is_null()) |
| done_callback_.Run(); |
| } |
| |
| const size_t expected_task_count_; |
| base::Closure task_callback_; |
| base::Closure done_callback_; |
| |
| size_t got_task_count_; |
| size_t got_done_count_; |
| |
| DISALLOW_COPY_AND_ASSIGN(SimpleThreadTest); |
| }; |
| |
| // Test creation/execution of threads in the browser process. |
| |
| const char kBrowserThreadTestHtml[] = "http://test.com/browserthread.html"; |
| |
| // Browser side. |
| class BrowserThreadTestHandler : public TestHandler { |
| public: |
| explicit BrowserThreadTestHandler(CefThreadId owner_thread_id) |
| : owner_thread_id_(owner_thread_id) {} |
| |
| void RunTest() override { |
| AddResource(kBrowserThreadTestHtml, "<html><body>Test</body></html>", |
| "text/html"); |
| |
| CreateBrowser(kBrowserThreadTestHtml); |
| |
| // Time out the test after a reasonable period of time. |
| SetTestTimeout(); |
| } |
| |
| void RunThreadTestOnOwnerThread() { |
| if (!CefCurrentlyOn(owner_thread_id_)) { |
| // Run the test on the desired owner thread. |
| CefPostTask( |
| owner_thread_id_, |
| base::Bind(&BrowserThreadTestHandler::RunThreadTestOnOwnerThread, |
| this)); |
| return; |
| } |
| |
| EXPECT_FALSE(thread_test_.get()); |
| thread_test_ = new SimpleThreadTest( |
| 3, base::Closure(), |
| base::Bind(&BrowserThreadTestHandler::DoneOnOwnerThread, this)); |
| thread_test_->RunTest(); |
| } |
| |
| void DoneOnOwnerThread() { |
| // Let the call stack unwind before destroying |thread_test_|. |
| CefPostTask( |
| owner_thread_id_, |
| base::Bind(&BrowserThreadTestHandler::DestroyTestOnOwnerThread, this)); |
| } |
| |
| void DestroyTestOnOwnerThread() { |
| EXPECT_TRUE(CefCurrentlyOn(owner_thread_id_)); |
| |
| EXPECT_TRUE(thread_test_.get()); |
| if (thread_test_) { |
| thread_test_->DestroyTest(); |
| thread_test_ = nullptr; |
| } |
| |
| got_test_done_.yes(); |
| |
| // Call DestroyTest() on the UI thread. |
| CefPostTask(TID_UI, |
| base::Bind(&BrowserThreadTestHandler::DestroyTest, this)); |
| } |
| |
| void OnLoadingStateChange(CefRefPtr<CefBrowser> browser, |
| bool isLoading, |
| bool canGoBack, |
| bool canGoForward) override { |
| if (!isLoading) |
| RunThreadTestOnOwnerThread(); |
| } |
| |
| private: |
| void DestroyTest() override { |
| EXPECT_FALSE(thread_test_.get()); |
| EXPECT_TRUE(got_test_done_); |
| |
| TestHandler::DestroyTest(); |
| } |
| |
| const CefThreadId owner_thread_id_; |
| |
| scoped_refptr<SimpleThreadTest> thread_test_; |
| TrackCallback got_test_done_; |
| |
| IMPLEMENT_REFCOUNTING(BrowserThreadTestHandler); |
| DISALLOW_COPY_AND_ASSIGN(BrowserThreadTestHandler); |
| }; |
| |
| } // namespace |
| |
| // Test creation of new threads from the browser UI thread. |
| TEST(ThreadTest, CreateFromBrowserUIThread) { |
| CefRefPtr<BrowserThreadTestHandler> handler = |
| new BrowserThreadTestHandler(TID_UI); |
| handler->ExecuteTest(); |
| ReleaseAndWaitForDestructor(handler); |
| } |
| |
| // Test creation of new threads from the browser IO thread. |
| TEST(ThreadTest, CreateFromBrowserIOThread) { |
| CefRefPtr<BrowserThreadTestHandler> handler = |
| new BrowserThreadTestHandler(TID_IO); |
| handler->ExecuteTest(); |
| ReleaseAndWaitForDestructor(handler); |
| } |
| |
| // Test creation of new threads from the browser FILE thread. |
| TEST(ThreadTest, CreateFromBrowserFILEThread) { |
| CefRefPtr<BrowserThreadTestHandler> handler = |
| new BrowserThreadTestHandler(TID_FILE); |
| handler->ExecuteTest(); |
| ReleaseAndWaitForDestructor(handler); |
| } |
| |
| namespace { |
| |
| // Test creation/execution of threads in the render process. |
| |
| const char kRenderThreadTestHtml[] = "http://test.com/renderthread.html"; |
| const char kRenderThreadTestMsg[] = "ThreadTest.RenderThreadTest"; |
| |
| // Browser side. |
| class RenderThreadTestHandler : public TestHandler { |
| public: |
| RenderThreadTestHandler() {} |
| |
| void RunTest() override { |
| AddResource(kRenderThreadTestHtml, "<html><body>Test</body></html>", |
| "text/html"); |
| |
| CreateBrowser(kRenderThreadTestHtml); |
| |
| // Time out the test after a reasonable period of time. |
| SetTestTimeout(); |
| } |
| |
| void OnLoadingStateChange(CefRefPtr<CefBrowser> browser, |
| bool isLoading, |
| bool canGoBack, |
| bool canGoForward) override { |
| if (!isLoading) { |
| // Return the test in the render process. |
| CefRefPtr<CefProcessMessage> msg = |
| CefProcessMessage::Create(kRenderThreadTestMsg); |
| browser->GetMainFrame()->SendProcessMessage(PID_RENDERER, msg); |
| } |
| } |
| |
| bool OnProcessMessageReceived(CefRefPtr<CefBrowser> browser, |
| CefRefPtr<CefFrame> frame, |
| CefProcessId source_process, |
| CefRefPtr<CefProcessMessage> message) override { |
| EXPECT_TRUE(browser.get()); |
| EXPECT_TRUE(frame.get()); |
| EXPECT_EQ(PID_RENDERER, source_process); |
| EXPECT_TRUE(message.get()); |
| EXPECT_TRUE(message->IsReadOnly()); |
| |
| const std::string& message_name = message->GetName(); |
| EXPECT_STREQ(kRenderThreadTestMsg, message_name.c_str()); |
| |
| got_message_.yes(); |
| |
| if (message->GetArgumentList()->GetBool(0)) |
| got_success_.yes(); |
| |
| // Test is complete. |
| DestroyTest(); |
| |
| return true; |
| } |
| |
| protected: |
| void DestroyTest() override { |
| EXPECT_TRUE(got_message_); |
| EXPECT_TRUE(got_success_); |
| |
| TestHandler::DestroyTest(); |
| } |
| |
| TrackCallback got_message_; |
| TrackCallback got_success_; |
| |
| IMPLEMENT_REFCOUNTING(RenderThreadTestHandler); |
| DISALLOW_COPY_AND_ASSIGN(RenderThreadTestHandler); |
| }; |
| |
| // Renderer side. |
| class RenderThreadRendererTest : public ClientAppRenderer::Delegate { |
| public: |
| RenderThreadRendererTest() {} |
| |
| bool OnProcessMessageReceived(CefRefPtr<ClientAppRenderer> app, |
| CefRefPtr<CefBrowser> browser, |
| CefRefPtr<CefFrame> frame, |
| CefProcessId source_process, |
| CefRefPtr<CefProcessMessage> message) override { |
| if (message->GetName().ToString() == kRenderThreadTestMsg) { |
| browser_ = browser; |
| EXPECT_FALSE(thread_test_.get()); |
| thread_test_ = new SimpleThreadTest( |
| 3, base::Closure(), |
| base::Bind(&RenderThreadRendererTest::Done, this)); |
| thread_test_->RunTest(); |
| return true; |
| } |
| |
| // Message not handled. |
| return false; |
| } |
| |
| private: |
| void Done() { |
| // Let the call stack unwind before destroying |thread_test_|. |
| CefPostTask(TID_RENDERER, |
| base::Bind(&RenderThreadRendererTest::DestroyTest, this)); |
| } |
| |
| void DestroyTest() { |
| EXPECT_TRUE(thread_test_.get()); |
| if (thread_test_) { |
| thread_test_->DestroyTest(); |
| thread_test_ = nullptr; |
| } |
| |
| // Check if the test has failed. |
| bool result = !TestFailed(); |
| |
| // Return the result to the browser process. |
| CefRefPtr<CefProcessMessage> return_msg = |
| CefProcessMessage::Create(kRenderThreadTestMsg); |
| EXPECT_TRUE(return_msg->GetArgumentList()->SetBool(0, result)); |
| browser_->GetMainFrame()->SendProcessMessage(PID_BROWSER, return_msg); |
| |
| browser_ = nullptr; |
| } |
| |
| CefRefPtr<CefBrowser> browser_; |
| scoped_refptr<SimpleThreadTest> thread_test_; |
| |
| IMPLEMENT_REFCOUNTING(RenderThreadRendererTest); |
| DISALLOW_COPY_AND_ASSIGN(RenderThreadRendererTest); |
| }; |
| |
| } // namespace |
| |
| TEST(ThreadTest, CreateFromRenderThread) { |
| CefRefPtr<RenderThreadTestHandler> handler = new RenderThreadTestHandler(); |
| handler->ExecuteTest(); |
| ReleaseAndWaitForDestructor(handler); |
| } |
| |
| // Entry point for creating request handler renderer test objects. |
| // Called from client_app_delegates.cc. |
| void CreateThreadRendererTests(ClientAppRenderer::DelegateSet& delegates) { |
| delegates.insert(new RenderThreadRendererTest); |
| } |