cef/tests/ceftests/thread_unittest.cc
2024-01-20 12:06:28 -05:00

444 lines
13 KiB
C++

// 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_callback.h"
#include "include/base/cef_callback_helpers.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() = default;
virtual ~ThreadTest() = default;
// 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(
base::OnceClosure test_task,
CefRefPtr<CefTaskRunner> callback_task_runner,
base::OnceClosure callback) {
EXPECT_TRUE(thread_.get());
thread_task_runner_->PostTask(CefCreateClosureTask(base::BindOnce(
&ThreadTest::ExecuteOnTestThread, this, std::move(test_task),
callback_task_runner, std::move(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(base::OnceClosure test_task,
CefRefPtr<CefTaskRunner> callback_task_runner,
base::OnceClosure callback) {
AssertTestThread();
std::move(test_task).Run();
callback_task_runner->PostTask(CefCreateClosureTask(std::move(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,
base::OnceClosure task_callback,
base::OnceClosure done_callback)
: expected_task_count_(expected_task_count),
task_callback_(std::move(task_callback)),
done_callback_(std::move(done_callback)) {}
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::BindOnce(&SimpleThreadTest::Task, this), owner_task_runner(),
base::BindOnce(&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()) {
std::move(task_callback_).Run();
}
}
void Done() {
AssertOwnerThread();
if (++got_done_count_ == expected_task_count_ &&
!done_callback_.is_null()) {
std::move(done_callback_).Run();
}
}
const size_t expected_task_count_;
base::OnceClosure task_callback_;
base::OnceClosure done_callback_;
size_t got_task_count_ = 0U;
size_t got_done_count_ = 0U;
DISALLOW_COPY_AND_ASSIGN(SimpleThreadTest);
};
// Test creation/execution of threads in the browser process.
const char kBrowserThreadTestHtml[] = "https://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::BindOnce(&BrowserThreadTestHandler::RunThreadTestOnOwnerThread,
this));
return;
}
EXPECT_FALSE(thread_test_.get());
thread_test_ = new SimpleThreadTest(
3, base::DoNothing(),
base::BindOnce(&BrowserThreadTestHandler::DoneOnOwnerThread, this));
thread_test_->RunTest();
}
void DoneOnOwnerThread() {
// Let the call stack unwind before destroying |thread_test_|.
CefPostTask(owner_thread_id_,
base::BindOnce(
&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::BindOnce(&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) {
// Use a FILE thread that will run tasks relatively quickly.
CefRefPtr<BrowserThreadTestHandler> handler =
new BrowserThreadTestHandler(TID_FILE_USER_VISIBLE);
handler->ExecuteTest();
ReleaseAndWaitForDestructor(handler);
}
namespace {
// Test creation/execution of threads in the render process.
const char kRenderThreadTestHtml[] = "https://test.com/renderthread.html";
const char kRenderThreadTestMsg[] = "ThreadTest.RenderThreadTest";
// Browser side.
class RenderThreadTestHandler : public TestHandler {
public:
RenderThreadTestHandler() = default;
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() = default;
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::DoNothing(),
base::BindOnce(&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::BindOnce(&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);
}