diff options
author | initial.commit <initial.commit@0039d316-1c4b-4281-b951-d872f2087c98> | 2008-07-26 21:49:38 +0000 |
---|---|---|
committer | initial.commit <initial.commit@0039d316-1c4b-4281-b951-d872f2087c98> | 2008-07-26 21:49:38 +0000 |
commit | d7cae12696b96500c05dd2d430f6238922c20c96 (patch) | |
tree | ecff27b367735535b2a66477f8cd89d3c462a6c0 /base/message_loop_unittest.cc | |
parent | ee2815e28d408216cf94e874825b6bcf76c69083 (diff) | |
download | chromium_src-d7cae12696b96500c05dd2d430f6238922c20c96.zip chromium_src-d7cae12696b96500c05dd2d430f6238922c20c96.tar.gz chromium_src-d7cae12696b96500c05dd2d430f6238922c20c96.tar.bz2 |
Add base to the repository.
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@8 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'base/message_loop_unittest.cc')
-rw-r--r-- | base/message_loop_unittest.cc | 827 |
1 files changed, 827 insertions, 0 deletions
diff --git a/base/message_loop_unittest.cc b/base/message_loop_unittest.cc new file mode 100644 index 0000000..374a618 --- /dev/null +++ b/base/message_loop_unittest.cc @@ -0,0 +1,827 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "base/logging.h" +#include "base/message_loop.h" +#include "base/scoped_handle.h" +#include "base/thread.h" +#include "base/ref_counted.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace { + +class MessageLoopTest : public testing::Test { + public: + virtual void SetUp() { + enable_recursive_task_ = MessageLoop::current()->NestableTasksAllowed(); + } + virtual void TearDown() { + MessageLoop::current()->SetNestableTasksAllowed(enable_recursive_task_); + } + private: + bool enable_recursive_task_; +}; + +class Foo : public base::RefCounted<Foo> { + public: + Foo() : test_count_(0) { + } + + void Test0() { + ++test_count_; + } + + void Test1ConstRef(const std::string& a) { + ++test_count_; + result_.append(a); + } + + void Test1Ptr(std::string* a) { + ++test_count_; + result_.append(*a); + } + + void Test1Int(int a) { + test_count_ += a; + } + + void Test2Ptr(std::string* a, std::string* b) { + ++test_count_; + result_.append(*a); + result_.append(*b); + } + + void Test2Mixed(const std::string& a, std::string* b) { + ++test_count_; + result_.append(a); + result_.append(*b); + } + + int test_count() const { return test_count_; } + const std::string& result() const { return result_; } + + private: + int test_count_; + std::string result_; +}; + +class QuitMsgLoop : public base::RefCounted<QuitMsgLoop> { + public: + void QuitNow() { + MessageLoop::current()->Quit(); + } +}; + +} // namespace + +TEST(MessageLoopTest, PostTask) { + // Add tests to message loop + scoped_refptr<Foo> foo = new Foo(); + std::string a("a"), b("b"), c("c"), d("d"); + MessageLoop::current()->PostTask(FROM_HERE, NewRunnableMethod( + foo.get(), &Foo::Test0)); + MessageLoop::current()->PostTask(FROM_HERE, NewRunnableMethod( + foo.get(), &Foo::Test1ConstRef, a)); + MessageLoop::current()->PostTask(FROM_HERE, NewRunnableMethod( + foo.get(), &Foo::Test1Ptr, &b)); + MessageLoop::current()->PostTask(FROM_HERE, NewRunnableMethod( + foo.get(), &Foo::Test1Int, 100)); + MessageLoop::current()->PostTask(FROM_HERE, NewRunnableMethod( + foo.get(), &Foo::Test2Ptr, &a, &c)); + MessageLoop::current()->PostTask(FROM_HERE, NewRunnableMethod( + foo.get(), &Foo::Test2Mixed, a, &d)); + + // After all tests, post a message that will shut down the message loop + scoped_refptr<QuitMsgLoop> quit = new QuitMsgLoop(); + MessageLoop::current()->PostTask(FROM_HERE, NewRunnableMethod( + quit.get(), &QuitMsgLoop::QuitNow)); + + // Now kick things off + MessageLoop::current()->Run(); + + EXPECT_EQ(foo->test_count(), 105); + EXPECT_EQ(foo->result(), "abacad"); +} + +TEST(MessageLoopTest, InvokeLater_SEH) { + // Add tests to message loop + scoped_refptr<Foo> foo = new Foo(); + std::string a("a"), b("b"), c("c"), d("d"); + MessageLoop::current()->PostTask(FROM_HERE, NewRunnableMethod( + foo.get(), &Foo::Test0)); + MessageLoop::current()->PostTask(FROM_HERE, NewRunnableMethod( + foo.get(), &Foo::Test1ConstRef, a)); + MessageLoop::current()->PostTask(FROM_HERE, NewRunnableMethod( + foo.get(), &Foo::Test1Ptr, &b)); + MessageLoop::current()->PostTask(FROM_HERE, NewRunnableMethod( + foo.get(), &Foo::Test1Int, 100)); + MessageLoop::current()->PostTask(FROM_HERE, NewRunnableMethod( + foo.get(), &Foo::Test2Ptr, &a, &c)); + MessageLoop::current()->PostTask(FROM_HERE, NewRunnableMethod( + foo.get(), &Foo::Test2Mixed, a, &d)); + + // After all tests, post a message that will shut down the message loop + scoped_refptr<QuitMsgLoop> quit = new QuitMsgLoop(); + MessageLoop::current()->PostTask(FROM_HERE, NewRunnableMethod( + quit.get(), &QuitMsgLoop::QuitNow)); + + // Now kick things off with the SEH block active. + MessageLoop::current()->set_exception_restoration(true); + MessageLoop::current()->Run(); + MessageLoop::current()->set_exception_restoration(false); + + EXPECT_EQ(foo->test_count(), 105); + EXPECT_EQ(foo->result(), "abacad"); +} + +namespace { + +class NestingTest : public Task { + public: + explicit NestingTest(int* depth) : depth_(depth) { + } + void Run() { + if (*depth_ > 0) { + *depth_ -= 1; + MessageLoop::current()->PostTask(FROM_HERE, new NestingTest(depth_)); + + MessageLoop::current()->SetNestableTasksAllowed(true); + MessageLoop::current()->Run(); + } + MessageLoop::current()->Quit(); + } + private: + int* depth_; +}; + +LONG WINAPI BadExceptionHandler(EXCEPTION_POINTERS *ex_info) { + ADD_FAILURE() << "bad exception handler"; + ::ExitProcess(ex_info->ExceptionRecord->ExceptionCode); + return EXCEPTION_EXECUTE_HANDLER; +} + +// This task throws an SEH exception: initially write to an invalid address. +// If the right SEH filter is installed, it will fix the error. +class CrasherTask : public Task { + public: + // Ctor. If trash_SEH_handler is true, the task will override the unhandled + // exception handler with one sure to crash this test. + explicit CrasherTask(bool trash_SEH_handler) + : trash_SEH_handler_(trash_SEH_handler) { + } + void Run() { + Sleep(1); + if (trash_SEH_handler_) + ::SetUnhandledExceptionFilter(&BadExceptionHandler); + // Generate a SEH fault. We do it in asm to make sure we know how to undo + // the damage. + __asm { + mov eax, dword ptr [CrasherTask::bad_array_] + mov byte ptr [eax], 66 + } + MessageLoop::current()->Quit(); + } + // Points the bad array to a valid memory location. + static void FixError() { + bad_array_ = &valid_store_; + } + + private: + bool trash_SEH_handler_; + static volatile char* bad_array_; + static char valid_store_; +}; + +volatile char* CrasherTask::bad_array_ = 0; +char CrasherTask::valid_store_ = 0; + +// This SEH filter fixes the problem and retries execution. Fixing requires +// that the last instruction: mov eax, [CrasherTask::bad_array_] to be retried +// so we move the instruction pointer 5 bytes back. +LONG WINAPI HandleCrasherTaskException(EXCEPTION_POINTERS *ex_info) { + if (ex_info->ExceptionRecord->ExceptionCode != EXCEPTION_ACCESS_VIOLATION) + return EXCEPTION_EXECUTE_HANDLER; + + CrasherTask::FixError(); + ex_info->ContextRecord->Eip -= 5; + return EXCEPTION_CONTINUE_EXECUTION; +} + +} // namespace + + +TEST(MessageLoopTest, Crasher) { + if (::IsDebuggerPresent()) + return; + + LPTOP_LEVEL_EXCEPTION_FILTER old_SEH_filter = + ::SetUnhandledExceptionFilter(&HandleCrasherTaskException); + + MessageLoop::current()->PostTask(FROM_HERE, new CrasherTask(false)); + MessageLoop::current()->set_exception_restoration(true); + MessageLoop::current()->Run(); + MessageLoop::current()->set_exception_restoration(false); + + ::SetUnhandledExceptionFilter(old_SEH_filter); +} + + +TEST(MessageLoopTest, CrasherNasty) { + if (::IsDebuggerPresent()) + return; + + LPTOP_LEVEL_EXCEPTION_FILTER old_SEH_filter = + ::SetUnhandledExceptionFilter(&HandleCrasherTaskException); + + MessageLoop::current()->PostTask(FROM_HERE, new CrasherTask(true)); + MessageLoop::current()->set_exception_restoration(true); + MessageLoop::current()->Run(); + MessageLoop::current()->set_exception_restoration(false); + + ::SetUnhandledExceptionFilter(old_SEH_filter); +} + +TEST(MessageLoopTest, Nesting) { + int depth = 100; + MessageLoop::current()->PostTask(FROM_HERE, new NestingTest(&depth)); + MessageLoop::current()->Run(); + EXPECT_EQ(depth, 0); +} + +namespace { + +const wchar_t* const kMessageBoxTitle = L"MessageLoop Unit Test"; + +enum TaskType { + MESSAGEBOX, + ENDDIALOG, + RECURSIVE, + TIMEDMESSAGELOOP, + QUITMESSAGELOOP, + ORDERERD, + PUMPS, +}; + +// Saves the order in which the tasks executed. +struct TaskItem { + TaskItem(TaskType t, int c, bool s) + : type(t), + cookie(c), + start(s) { + } + + TaskType type; + int cookie; + bool start; + + bool operator == (const TaskItem& other) const { + return type == other.type && cookie == other.cookie && start == other.start; + } +}; + +typedef std::vector<TaskItem> TaskList; + +std::ostream& operator <<(std::ostream& os, TaskType type) { + switch (type) { + case MESSAGEBOX: os << "MESSAGEBOX"; break; + case ENDDIALOG: os << "ENDDIALOG"; break; + case RECURSIVE: os << "RECURSIVE"; break; + case TIMEDMESSAGELOOP: os << "TIMEDMESSAGELOOP"; break; + case QUITMESSAGELOOP: os << "QUITMESSAGELOOP"; break; + case ORDERERD: os << "ORDERERD"; break; + case PUMPS: os << "PUMPS"; break; + default: + NOTREACHED(); + os << "Unknown TaskType"; + break; + } + return os; +} + +std::ostream& operator <<(std::ostream& os, const TaskItem& item) { + if (item.start) + return os << item.type << " " << item.cookie << " starts"; + else + return os << item.type << " " << item.cookie << " ends"; +} + +// Saves the order the tasks ran. +class OrderedTasks : public Task { + public: + OrderedTasks(TaskList* order, int cookie) + : order_(order), + type_(ORDERERD), + cookie_(cookie) { + } + OrderedTasks(TaskList* order, TaskType type, int cookie) + : order_(order), + type_(type), + cookie_(cookie) { + } + + void RunStart() { + TaskItem item(type_, cookie_, true); + DLOG(INFO) << item; + order_->push_back(item); + } + void RunEnd() { + TaskItem item(type_, cookie_, false); + DLOG(INFO) << item; + order_->push_back(item); + } + + virtual void Run() { + RunStart(); + RunEnd(); + } + + protected: + TaskList* order() const { + return order_; + } + + int cookie() const { + return cookie_; + } + + private: + TaskList* order_; + TaskType type_; + int cookie_; +}; + +// MessageLoop implicitly start a "modal message loop". Modal dialog boxes, +// common controls (like OpenFile) and StartDoc printing function can cause +// implicit message loops. +class MessageBoxTask : public OrderedTasks { + public: + MessageBoxTask(TaskList* order, int cookie, bool is_reentrant) + : OrderedTasks(order, MESSAGEBOX, cookie), + is_reentrant_(is_reentrant) { + } + + virtual void Run() { + RunStart(); + if (is_reentrant_) + MessageLoop::current()->SetNestableTasksAllowed(true); + MessageBox(NULL, L"Please wait...", kMessageBoxTitle, MB_OK); + RunEnd(); + } + + private: + bool is_reentrant_; +}; + +// Will end the MessageBox. +class EndDialogTask : public OrderedTasks { + public: + EndDialogTask(TaskList* order, int cookie) + : OrderedTasks(order, ENDDIALOG, cookie) { + } + + virtual void Run() { + RunStart(); + HWND window = GetActiveWindow(); + if (window != NULL) { + EXPECT_NE(EndDialog(window, IDCONTINUE), 0); + // Cheap way to signal that the window wasn't found if RunEnd() isn't + // called. + RunEnd(); + } + } +}; + +class RecursiveTask : public OrderedTasks { + public: + RecursiveTask(int depth, TaskList* order, int cookie, bool is_reentrant) + : OrderedTasks(order, RECURSIVE, cookie), + depth_(depth), + is_reentrant_(is_reentrant) { + } + + virtual void Run() { + RunStart(); + if (depth_ > 0) { + if (is_reentrant_) + MessageLoop::current()->SetNestableTasksAllowed(true); + MessageLoop::current()->PostTask(FROM_HERE, + new RecursiveTask(depth_ - 1, order(), cookie(), is_reentrant_)); + } + RunEnd(); + } + + private: + int depth_; + bool is_reentrant_; +}; + +class QuitTask : public OrderedTasks { + public: + QuitTask(TaskList* order, int cookie) + : OrderedTasks(order, QUITMESSAGELOOP, cookie) { + } + + virtual void Run() { + RunStart(); + MessageLoop::current()->Quit(); + RunEnd(); + } +}; + +class Recursive2Tasks : public Task { + public: + Recursive2Tasks(MessageLoop* target, + HANDLE event, + bool expect_window, + TaskList* order, + bool is_reentrant) + : target_(target), + event_(event), + expect_window_(expect_window), + order_(order), + is_reentrant_(is_reentrant) { + } + + virtual void Run() { + target_->PostTask(FROM_HERE, + new RecursiveTask(2, order_, 1, is_reentrant_)); + target_->PostTask(FROM_HERE, + new MessageBoxTask(order_, 2, is_reentrant_)); + target_->PostTask(FROM_HERE, + new RecursiveTask(2, order_, 3, is_reentrant_)); + // The trick here is that for recursive task processing, this task will be + // ran _inside_ the MessageBox message loop, dismissing the MessageBox + // without a chance. + // For non-recursive task processing, this will be executed _after_ the + // MessageBox will have been dismissed by the code below, where + // expect_window_ is true. + target_->PostTask(FROM_HERE, new EndDialogTask(order_, 4)); + target_->PostTask(FROM_HERE, new QuitTask(order_, 5)); + + // Enforce that every tasks are sent before starting to run the main thread + // message loop. + ASSERT_TRUE(SetEvent(event_)); + + // Poll for the MessageBox. Don't do this at home! At the speed we do it, + // you will never realize one MessageBox was shown. + for (; expect_window_;) { + HWND window = FindWindow(L"#32770", kMessageBoxTitle); + if (window) { + // Dismiss it. + for (;;) { + HWND button = FindWindowEx(window, NULL, L"Button", NULL); + if (button != NULL) { + EXPECT_TRUE(0 == SendMessage(button, WM_LBUTTONDOWN, 0, 0)); + EXPECT_TRUE(0 == SendMessage(button, WM_LBUTTONUP, 0, 0)); + break; + } + } + break; + } + } + } + + private: + MessageLoop* target_; + HANDLE event_; + TaskList* order_; + bool expect_window_; + bool is_reentrant_; +}; + +} // namespace + +TEST(MessageLoop, RecursiveDenial1) { + EXPECT_TRUE(MessageLoop::current()->NestableTasksAllowed()); + TaskList order; + MessageLoop::current()->PostTask(FROM_HERE, + new RecursiveTask(2, &order, 1, false)); + MessageLoop::current()->PostTask(FROM_HERE, + new RecursiveTask(2, &order, 2, false)); + MessageLoop::current()->PostTask(FROM_HERE, new QuitTask(&order, 3)); + + MessageLoop::current()->Run(); + + // FIFO order. + ASSERT_EQ(order.size(), 14); + EXPECT_EQ(order[ 0], TaskItem(RECURSIVE, 1, true)); + EXPECT_EQ(order[ 1], TaskItem(RECURSIVE, 1, false)); + EXPECT_EQ(order[ 2], TaskItem(RECURSIVE, 2, true)); + EXPECT_EQ(order[ 3], TaskItem(RECURSIVE, 2, false)); + EXPECT_EQ(order[ 4], TaskItem(QUITMESSAGELOOP, 3, true)); + EXPECT_EQ(order[ 5], TaskItem(QUITMESSAGELOOP, 3, false)); + EXPECT_EQ(order[ 6], TaskItem(RECURSIVE, 1, true)); + EXPECT_EQ(order[ 7], TaskItem(RECURSIVE, 1, false)); + EXPECT_EQ(order[ 8], TaskItem(RECURSIVE, 2, true)); + EXPECT_EQ(order[ 9], TaskItem(RECURSIVE, 2, false)); + EXPECT_EQ(order[10], TaskItem(RECURSIVE, 1, true)); + EXPECT_EQ(order[11], TaskItem(RECURSIVE, 1, false)); + EXPECT_EQ(order[12], TaskItem(RECURSIVE, 2, true)); + EXPECT_EQ(order[13], TaskItem(RECURSIVE, 2, false)); +} + + +TEST(MessageLoop, RecursiveSupport1) { + TaskList order; + MessageLoop::current()->PostTask(FROM_HERE, + new RecursiveTask(2, &order, 1, true)); + MessageLoop::current()->PostTask(FROM_HERE, + new RecursiveTask(2, &order, 2, true)); + MessageLoop::current()->PostTask(FROM_HERE, + new QuitTask(&order, 3)); + + MessageLoop::current()->Run(); + + // FIFO order. + ASSERT_EQ(order.size(), 14); + EXPECT_EQ(order[ 0], TaskItem(RECURSIVE, 1, true)); + EXPECT_EQ(order[ 1], TaskItem(RECURSIVE, 1, false)); + EXPECT_EQ(order[ 2], TaskItem(RECURSIVE, 2, true)); + EXPECT_EQ(order[ 3], TaskItem(RECURSIVE, 2, false)); + EXPECT_EQ(order[ 4], TaskItem(QUITMESSAGELOOP, 3, true)); + EXPECT_EQ(order[ 5], TaskItem(QUITMESSAGELOOP, 3, false)); + EXPECT_EQ(order[ 6], TaskItem(RECURSIVE, 1, true)); + EXPECT_EQ(order[ 7], TaskItem(RECURSIVE, 1, false)); + EXPECT_EQ(order[ 8], TaskItem(RECURSIVE, 2, true)); + EXPECT_EQ(order[ 9], TaskItem(RECURSIVE, 2, false)); + EXPECT_EQ(order[10], TaskItem(RECURSIVE, 1, true)); + EXPECT_EQ(order[11], TaskItem(RECURSIVE, 1, false)); + EXPECT_EQ(order[12], TaskItem(RECURSIVE, 2, true)); + EXPECT_EQ(order[13], TaskItem(RECURSIVE, 2, false)); +} + +// A side effect of this test is the generation a beep. Sorry. +TEST(MessageLoop, RecursiveDenial2) { + Thread worker("RecursiveDenial2_worker"); + ASSERT_EQ(true, worker.Start()); + TaskList order; + ScopedHandle event(CreateEvent(NULL, FALSE, FALSE, NULL)); + worker.message_loop()->PostTask(FROM_HERE, + new Recursive2Tasks(MessageLoop::current(), + event, + true, + &order, + false)); + // Let the other thread execute. + WaitForSingleObject(event, INFINITE); + MessageLoop::current()->Run(); + + ASSERT_EQ(order.size(), 17); + EXPECT_EQ(order[ 0], TaskItem(RECURSIVE, 1, true)); + EXPECT_EQ(order[ 1], TaskItem(RECURSIVE, 1, false)); + EXPECT_EQ(order[ 2], TaskItem(MESSAGEBOX, 2, true)); + EXPECT_EQ(order[ 3], TaskItem(MESSAGEBOX, 2, false)); + EXPECT_EQ(order[ 4], TaskItem(RECURSIVE, 3, true)); + EXPECT_EQ(order[ 5], TaskItem(RECURSIVE, 3, false)); + // When EndDialogTask is processed, the window is already dismissed, hence no + // "end" entry. + EXPECT_EQ(order[ 6], TaskItem(ENDDIALOG, 4, true)); + EXPECT_EQ(order[ 7], TaskItem(QUITMESSAGELOOP, 5, true)); + EXPECT_EQ(order[ 8], TaskItem(QUITMESSAGELOOP, 5, false)); + EXPECT_EQ(order[ 9], TaskItem(RECURSIVE, 1, true)); + EXPECT_EQ(order[10], TaskItem(RECURSIVE, 1, false)); + EXPECT_EQ(order[11], TaskItem(RECURSIVE, 3, true)); + EXPECT_EQ(order[12], TaskItem(RECURSIVE, 3, false)); + EXPECT_EQ(order[13], TaskItem(RECURSIVE, 1, true)); + EXPECT_EQ(order[14], TaskItem(RECURSIVE, 1, false)); + EXPECT_EQ(order[15], TaskItem(RECURSIVE, 3, true)); + EXPECT_EQ(order[16], TaskItem(RECURSIVE, 3, false)); +} + +// A side effect of this test is the generation a beep. Sorry. +TEST(MessageLoop, RecursiveSupport2) { + Thread worker("RecursiveSupport2_worker"); + ASSERT_EQ(true, worker.Start()); + TaskList order; + ScopedHandle event(CreateEvent(NULL, FALSE, FALSE, NULL)); + worker.message_loop()->PostTask(FROM_HERE, + new Recursive2Tasks(MessageLoop::current(), + event, + false, + &order, + true)); + // Let the other thread execute. + WaitForSingleObject(event, INFINITE); + MessageLoop::current()->Run(); + + ASSERT_EQ(order.size(), 18); + EXPECT_EQ(order[ 0], TaskItem(RECURSIVE, 1, true)); + EXPECT_EQ(order[ 1], TaskItem(RECURSIVE, 1, false)); + EXPECT_EQ(order[ 2], TaskItem(MESSAGEBOX, 2, true)); + // Note that this executes in the MessageBox modal loop. + EXPECT_EQ(order[ 3], TaskItem(RECURSIVE, 3, true)); + EXPECT_EQ(order[ 4], TaskItem(RECURSIVE, 3, false)); + EXPECT_EQ(order[ 5], TaskItem(ENDDIALOG, 4, true)); + EXPECT_EQ(order[ 6], TaskItem(ENDDIALOG, 4, false)); + EXPECT_EQ(order[ 7], TaskItem(MESSAGEBOX, 2, false)); + /* The order can subtly change here. The reason is that when RecursiveTask(1) + is called in the main thread, if it is faster than getting to the + PostTask(FROM_HERE, QuitTask) execution, the order of task execution can + change. We don't care anyway that the order isn't correct. + EXPECT_EQ(order[ 8], TaskItem(QUITMESSAGELOOP, 5, true)); + EXPECT_EQ(order[ 9], TaskItem(QUITMESSAGELOOP, 5, false)); + EXPECT_EQ(order[10], TaskItem(RECURSIVE, 1, true)); + EXPECT_EQ(order[11], TaskItem(RECURSIVE, 1, false)); + */ + EXPECT_EQ(order[12], TaskItem(RECURSIVE, 3, true)); + EXPECT_EQ(order[13], TaskItem(RECURSIVE, 3, false)); + EXPECT_EQ(order[14], TaskItem(RECURSIVE, 1, true)); + EXPECT_EQ(order[15], TaskItem(RECURSIVE, 1, false)); + EXPECT_EQ(order[16], TaskItem(RECURSIVE, 3, true)); + EXPECT_EQ(order[17], TaskItem(RECURSIVE, 3, false)); +} + +class TaskThatPumps : public OrderedTasks { + public: + TaskThatPumps(TaskList* order, int cookie) + : OrderedTasks(order, PUMPS, cookie) { + } + + virtual void Run() { + RunStart(); + bool old_state = MessageLoop::current()->NestableTasksAllowed(); + MessageLoop::current()->Quit(); + MessageLoop::current()->SetNestableTasksAllowed(true); + MessageLoop::current()->Run(); + MessageLoop::current()->SetNestableTasksAllowed(old_state); + RunEnd(); + } + + private: +}; + + +// Tests that non nestable tasks run in FIFO if there are no nested loops. +TEST(MessageLoop, NonNestableWithNoNesting) { + TaskList order; + + Task* task = new OrderedTasks(&order, 1); + task->set_nestable(false); + MessageLoop::current()->PostTask(FROM_HERE, task); + MessageLoop::current()->PostTask(FROM_HERE, new OrderedTasks(&order, 2)); + MessageLoop::current()->PostTask(FROM_HERE, new QuitTask(&order, 3)); + MessageLoop::current()->Run(); + + // FIFO order. + ASSERT_EQ(order.size(), 6); + EXPECT_EQ(order[ 0], TaskItem(ORDERERD, 1, true)); + EXPECT_EQ(order[ 1], TaskItem(ORDERERD, 1, false)); + EXPECT_EQ(order[ 2], TaskItem(ORDERERD, 2, true)); + EXPECT_EQ(order[ 3], TaskItem(ORDERERD, 2, false)); + EXPECT_EQ(order[ 4], TaskItem(QUITMESSAGELOOP, 3, true)); + EXPECT_EQ(order[ 5], TaskItem(QUITMESSAGELOOP, 3, false)); +} + +// Tests that non nestable tasks don't run when there's code in the call stack. +TEST(MessageLoop, NonNestableInNestedLoop) { + TaskList order; + + MessageLoop::current()->PostTask(FROM_HERE, + new TaskThatPumps(&order, 1)); + Task* task = new OrderedTasks(&order, 2); + task->set_nestable(false); + MessageLoop::current()->PostTask(FROM_HERE, task); + MessageLoop::current()->PostTask(FROM_HERE, new OrderedTasks(&order, 3)); + MessageLoop::current()->PostTask(FROM_HERE, new QuitTask(&order, 4)); + Task* non_nestable_quit = new QuitTask(&order, 5); + non_nestable_quit->set_nestable(false); + MessageLoop::current()->PostTask(FROM_HERE, non_nestable_quit); + + + MessageLoop::current()->Run(); + + // FIFO order. + ASSERT_EQ(order.size(), 10); + EXPECT_EQ(order[ 0], TaskItem(PUMPS, 1, true)); + EXPECT_EQ(order[ 1], TaskItem(ORDERERD, 3, true)); + EXPECT_EQ(order[ 2], TaskItem(ORDERERD, 3, false)); + EXPECT_EQ(order[ 3], TaskItem(QUITMESSAGELOOP, 4, true)); + EXPECT_EQ(order[ 4], TaskItem(QUITMESSAGELOOP, 4, false)); + EXPECT_EQ(order[ 5], TaskItem(PUMPS, 1, false)); + EXPECT_EQ(order[ 6], TaskItem(ORDERERD, 2, true)); + EXPECT_EQ(order[ 7], TaskItem(ORDERERD, 2, false)); + EXPECT_EQ(order[ 8], TaskItem(QUITMESSAGELOOP, 5, true)); + EXPECT_EQ(order[ 9], TaskItem(QUITMESSAGELOOP, 5, false)); +} + + +namespace { + +class AutoresetWatcher : public MessageLoop::Watcher { + public: + AutoresetWatcher(HANDLE signal, MessageLoop* message_loop) + : signal_(signal), message_loop_(message_loop) {} + virtual void OnObjectSignaled(HANDLE object); + private: + HANDLE signal_; + MessageLoop* message_loop_; +}; + +void AutoresetWatcher::OnObjectSignaled(HANDLE object) { + message_loop_->WatchObject(object, NULL); + ASSERT_TRUE(SetEvent(signal_)); +} + +class AutoresetTask : public Task { + public: + AutoresetTask(HANDLE object, MessageLoop::Watcher* watcher) + : object_(object), watcher_(watcher) {} + virtual void Run() { + MessageLoop::current()->WatchObject(object_, watcher_); + } + + private: + HANDLE object_; + MessageLoop::Watcher* watcher_; +}; + +} // namespace + +TEST(MessageLoop, AutoresetEvents) { + SECURITY_ATTRIBUTES attributes; + attributes.nLength = sizeof(attributes); + attributes.bInheritHandle = false; + attributes.lpSecurityDescriptor = NULL; + + // Init an autoreset and a manual reset events. + HANDLE autoreset = CreateEvent(&attributes, FALSE, FALSE, NULL); + HANDLE callback_called = CreateEvent(&attributes, TRUE, FALSE, NULL); + ASSERT_TRUE(NULL != autoreset); + ASSERT_TRUE(NULL != callback_called); + + Thread thread("Autoreset test"); + ASSERT_TRUE(thread.Start()); + + MessageLoop* message_loop = thread.message_loop(); + ASSERT_TRUE(NULL != message_loop); + + AutoresetWatcher watcher(callback_called, message_loop); + AutoresetTask* task = new AutoresetTask(autoreset, &watcher); + message_loop->PostTask(FROM_HERE, task); + Sleep(100); // Make sure the thread runs and sleeps for lack of work. + + ASSERT_TRUE(SetEvent(autoreset)); + + DWORD result = WaitForSingleObject(callback_called, 1000); + EXPECT_EQ(WAIT_OBJECT_0, result); + + thread.Stop(); +} + +namespace { + +class DispatcherImpl : public MessageLoop::Dispatcher { + public: + DispatcherImpl() : dispatch_count_(0) {} + + virtual bool Dispatch(const MSG& msg) { + ::TranslateMessage(&msg); + ::DispatchMessage(&msg); + return (++dispatch_count_ != 2); + } + + int dispatch_count_; +}; + +} // namespace + +TEST(MessageLoop, Dispatcher) { + class MyTask : public Task { + public: + virtual void Run() { + PostMessage(NULL, WM_LBUTTONDOWN, 0, 0); + PostMessage(NULL, WM_LBUTTONUP, 'A', 0); + } + }; + Task* task = new MyTask(); + MessageLoop::current()->PostDelayedTask(FROM_HERE, task, 100); + DispatcherImpl dispatcher; + MessageLoop::current()->Run(&dispatcher); + ASSERT_EQ(2, dispatcher.dispatch_count_); +} |