diff options
Diffstat (limited to 'base/message_loop_proxy_unittest.cc')
-rw-r--r-- | base/message_loop_proxy_unittest.cc | 263 |
1 files changed, 263 insertions, 0 deletions
diff --git a/base/message_loop_proxy_unittest.cc b/base/message_loop_proxy_unittest.cc new file mode 100644 index 0000000..aeb2f0c --- /dev/null +++ b/base/message_loop_proxy_unittest.cc @@ -0,0 +1,263 @@ +// Copyright (c) 2011 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 "base/message_loop_proxy.h" + +#include "base/atomic_sequence_num.h" +#include "base/bind.h" +#include "base/memory/ref_counted.h" +#include "base/memory/scoped_ptr.h" +#include "base/message_loop.h" +#include "base/synchronization/waitable_event.h" +#include "base/threading/thread.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace base { + +namespace { + +class MessageLoopProxyTest : public testing::Test { + public: + MessageLoopProxyTest() + : current_loop_(new MessageLoop()), + task_thread_("task_thread"), + thread_sync_(true, false) { + } + + void DeleteCurrentMessageLoop() { + current_loop_.reset(); + } + + protected: + virtual void SetUp() { + // Use SetUp() instead of the constructor to avoid posting a task to a + // partialy constructed object. + task_thread_.Start(); + + // Allow us to pause the |task_thread_|'s MessageLoop. + task_thread_.message_loop()->PostTask( + FROM_HERE, + Bind(&MessageLoopProxyTest::BlockTaskThreadHelper, Unretained(this))); + } + + virtual void TearDown() { + // Make sure the |task_thread_| is not blocked, and stop the thread + // fully before destuction because its tasks may still depend on the + // |thread_sync_| event. + thread_sync_.Signal(); + task_thread_.Stop(); + DeleteCurrentMessageLoop(); + } + + // Make LoopRecorder threadsafe so that there is defined behavior even if a + // threading mistake sneaks into the PostTaskAndReplyRelay implementation. + class LoopRecorder : public RefCountedThreadSafe<LoopRecorder> { + public: + LoopRecorder(MessageLoop** run_on, MessageLoop** deleted_on, + int* destruct_order) + : run_on_(run_on), + deleted_on_(deleted_on), + destruct_order_(destruct_order) { + } + + void RecordRun() { + *run_on_ = MessageLoop::current(); + } + + private: + friend class RefCountedThreadSafe<LoopRecorder>; + ~LoopRecorder() { + *deleted_on_ = MessageLoop::current(); + *destruct_order_ = g_order.GetNext(); + } + + MessageLoop** run_on_; + MessageLoop** deleted_on_; + int* destruct_order_; + }; + + static void RecordLoop(scoped_refptr<LoopRecorder> recorder) { + recorder->RecordRun(); + } + + static void RecordLoopAndQuit(scoped_refptr<LoopRecorder> recorder) { + recorder->RecordRun(); + MessageLoop::current()->Quit(); + } + + void UnblockTaskThread() { + thread_sync_.Signal(); + } + + void BlockTaskThreadHelper() { + thread_sync_.Wait(); + } + + static AtomicSequenceNumber g_order; + + scoped_ptr<MessageLoop> current_loop_; + Thread task_thread_; + + private: + base::WaitableEvent thread_sync_; +}; + +AtomicSequenceNumber MessageLoopProxyTest::g_order(LINKER_INITIALIZED); + +TEST_F(MessageLoopProxyTest, PostTaskAndReply_Basic) { + MessageLoop* task_run_on = NULL; + MessageLoop* task_deleted_on = NULL; + int task_delete_order = -1; + MessageLoop* reply_run_on = NULL; + MessageLoop* reply_deleted_on = NULL; + int reply_delete_order = -1; + + scoped_refptr<LoopRecorder> task_recoder = + new LoopRecorder(&task_run_on, &task_deleted_on, &task_delete_order); + scoped_refptr<LoopRecorder> reply_recoder = + new LoopRecorder(&reply_run_on, &reply_deleted_on, &reply_delete_order); + + ASSERT_TRUE(task_thread_.message_loop_proxy()->PostTaskAndReply( + FROM_HERE, + Bind(&RecordLoop, task_recoder), + Bind(&RecordLoopAndQuit, reply_recoder))); + + // Die if base::Bind doesn't retain a reference to the recorders. + task_recoder = NULL; + reply_recoder = NULL; + ASSERT_FALSE(task_deleted_on); + ASSERT_FALSE(reply_deleted_on); + + UnblockTaskThread(); + current_loop_->Run(); + + EXPECT_EQ(task_thread_.message_loop(), task_run_on); + EXPECT_EQ(current_loop_.get(), task_deleted_on); + EXPECT_EQ(current_loop_.get(), reply_run_on); + EXPECT_EQ(current_loop_.get(), reply_deleted_on); + EXPECT_LT(task_delete_order, reply_delete_order); +} + +TEST_F(MessageLoopProxyTest, PostTaskAndReplyOnDeletedThreadDoesNotLeak) { + MessageLoop* task_run_on = NULL; + MessageLoop* task_deleted_on = NULL; + int task_delete_order = -1; + MessageLoop* reply_run_on = NULL; + MessageLoop* reply_deleted_on = NULL; + int reply_delete_order = -1; + + scoped_refptr<LoopRecorder> task_recoder = + new LoopRecorder(&task_run_on, &task_deleted_on, &task_delete_order); + scoped_refptr<LoopRecorder> reply_recoder = + new LoopRecorder(&reply_run_on, &reply_deleted_on, &reply_delete_order); + + // Grab a MessageLoopProxy to a dead MessageLoop. + scoped_refptr<MessageLoopProxy> task_loop_proxy = + task_thread_.message_loop_proxy(); + UnblockTaskThread(); + task_thread_.Stop(); + + ASSERT_FALSE(task_loop_proxy->PostTaskAndReply( + FROM_HERE, + Bind(&RecordLoop, task_recoder), + Bind(&RecordLoopAndQuit, reply_recoder))); + + // The relay should have properly deleted its resources leaving us as the only + // reference. + EXPECT_EQ(task_delete_order, reply_delete_order); + ASSERT_TRUE(task_recoder->HasOneRef()); + ASSERT_TRUE(reply_recoder->HasOneRef()); + + // Nothing should have run though. + EXPECT_FALSE(task_run_on); + EXPECT_FALSE(reply_run_on); +} + +TEST_F(MessageLoopProxyTest, PostTaskAndReply_SameLoop) { + MessageLoop* task_run_on = NULL; + MessageLoop* task_deleted_on = NULL; + int task_delete_order = -1; + MessageLoop* reply_run_on = NULL; + MessageLoop* reply_deleted_on = NULL; + int reply_delete_order = -1; + + scoped_refptr<LoopRecorder> task_recoder = + new LoopRecorder(&task_run_on, &task_deleted_on, &task_delete_order); + scoped_refptr<LoopRecorder> reply_recoder = + new LoopRecorder(&reply_run_on, &reply_deleted_on, &reply_delete_order); + + // Enqueue the relay. + ASSERT_TRUE(current_loop_->message_loop_proxy()->PostTaskAndReply( + FROM_HERE, + Bind(&RecordLoop, task_recoder), + Bind(&RecordLoopAndQuit, reply_recoder))); + + // Die if base::Bind doesn't retain a reference to the recorders. + task_recoder = NULL; + reply_recoder = NULL; + ASSERT_FALSE(task_deleted_on); + ASSERT_FALSE(reply_deleted_on); + + current_loop_->Run(); + + EXPECT_EQ(current_loop_.get(), task_run_on); + EXPECT_EQ(current_loop_.get(), task_deleted_on); + EXPECT_EQ(current_loop_.get(), reply_run_on); + EXPECT_EQ(current_loop_.get(), reply_deleted_on); + EXPECT_LT(task_delete_order, reply_delete_order); +} + +TEST_F(MessageLoopProxyTest, PostTaskAndReply_DeadReplyLoopDoesNotDelete) { + MessageLoop* task_run_on = NULL; + MessageLoop* task_deleted_on = NULL; + int task_delete_order = -1; + MessageLoop* reply_run_on = NULL; + MessageLoop* reply_deleted_on = NULL; + int reply_delete_order = -1; + + scoped_refptr<LoopRecorder> task_recoder = + new LoopRecorder(&task_run_on, &task_deleted_on, &task_delete_order); + scoped_refptr<LoopRecorder> reply_recoder = + new LoopRecorder(&reply_run_on, &reply_deleted_on, &reply_delete_order); + + // Enqueue the relay. + task_thread_.message_loop_proxy()->PostTaskAndReply( + FROM_HERE, + Bind(&RecordLoop, task_recoder), + Bind(&RecordLoopAndQuit, reply_recoder)); + + // Die if base::Bind doesn't retain a reference to the recorders. + task_recoder = NULL; + reply_recoder = NULL; + ASSERT_FALSE(task_deleted_on); + ASSERT_FALSE(reply_deleted_on); + + UnblockTaskThread(); + + // Mercilessly whack the current loop before |reply| gets to run. + current_loop_.reset(); + + // This should ensure the relay has been run. We need to record the + // MessageLoop pointer before stopping the thread because Thread::Stop() will + // NULL out its own pointer. + MessageLoop* task_loop = task_thread_.message_loop(); + task_thread_.Stop(); + + EXPECT_EQ(task_loop, task_run_on); + ASSERT_FALSE(task_deleted_on); + EXPECT_FALSE(reply_run_on); + ASSERT_FALSE(reply_deleted_on); + EXPECT_EQ(task_delete_order, reply_delete_order); + + // The PostTaskAndReplyRelay is leaked here. Even if we had a reference to + // it, we cannot just delete it because PostTaskAndReplyRelay's destructor + // checks that MessageLoop::current() is the the same as when the + // PostTaskAndReplyRelay object was constructed. However, this loop must have + // aleady been deleted in order to perform this test. See + // http://crbug.com/86301. +} + +} // namespace + +} // namespace base |