// Copyright (c) 2012 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/threading/thread.h" #include <vector> #include "base/bind.h" #include "base/message_loop/message_loop.h" #include "base/third_party/dynamic_annotations/dynamic_annotations.h" #include "testing/gtest/include/gtest/gtest.h" #include "testing/platform_test.h" using base::Thread; typedef PlatformTest ThreadTest; namespace { void ToggleValue(bool* value) { ANNOTATE_BENIGN_RACE(value, "Test-only data race on boolean " "in base/thread_unittest"); *value = !*value; } class SleepInsideInitThread : public Thread { public: SleepInsideInitThread() : Thread("none") { init_called_ = false; ANNOTATE_BENIGN_RACE( this, "Benign test-only data race on vptr - http://crbug.com/98219"); } virtual ~SleepInsideInitThread() { Stop(); } virtual void Init() OVERRIDE { base::PlatformThread::Sleep(base::TimeDelta::FromMilliseconds(500)); init_called_ = true; } bool InitCalled() { return init_called_; } private: bool init_called_; }; enum ThreadEvent { // Thread::Init() was called. THREAD_EVENT_INIT = 0, // The MessageLoop for the thread was deleted. THREAD_EVENT_MESSAGE_LOOP_DESTROYED, // Thread::CleanUp() was called. THREAD_EVENT_CLEANUP, // Keep at end of list. THREAD_NUM_EVENTS }; typedef std::vector<ThreadEvent> EventList; class CaptureToEventList : public Thread { public: // This Thread pushes events into the vector |event_list| to show // the order they occured in. |event_list| must remain valid for the // lifetime of this thread. explicit CaptureToEventList(EventList* event_list) : Thread("none"), event_list_(event_list) { } virtual ~CaptureToEventList() { Stop(); } virtual void Init() OVERRIDE { event_list_->push_back(THREAD_EVENT_INIT); } virtual void CleanUp() OVERRIDE { event_list_->push_back(THREAD_EVENT_CLEANUP); } private: EventList* event_list_; }; // Observer that writes a value into |event_list| when a message loop has been // destroyed. class CapturingDestructionObserver : public base::MessageLoop::DestructionObserver { public: // |event_list| must remain valid throughout the observer's lifetime. explicit CapturingDestructionObserver(EventList* event_list) : event_list_(event_list) { } // DestructionObserver implementation: virtual void WillDestroyCurrentMessageLoop() OVERRIDE { event_list_->push_back(THREAD_EVENT_MESSAGE_LOOP_DESTROYED); event_list_ = NULL; } private: EventList* event_list_; }; // Task that adds a destruction observer to the current message loop. void RegisterDestructionObserver( base::MessageLoop::DestructionObserver* observer) { base::MessageLoop::current()->AddDestructionObserver(observer); } } // namespace TEST_F(ThreadTest, Restart) { Thread a("Restart"); a.Stop(); EXPECT_FALSE(a.message_loop()); EXPECT_FALSE(a.IsRunning()); EXPECT_TRUE(a.Start()); EXPECT_TRUE(a.message_loop()); EXPECT_TRUE(a.IsRunning()); a.Stop(); EXPECT_FALSE(a.message_loop()); EXPECT_FALSE(a.IsRunning()); EXPECT_TRUE(a.Start()); EXPECT_TRUE(a.message_loop()); EXPECT_TRUE(a.IsRunning()); a.Stop(); EXPECT_FALSE(a.message_loop()); EXPECT_FALSE(a.IsRunning()); a.Stop(); EXPECT_FALSE(a.message_loop()); EXPECT_FALSE(a.IsRunning()); } TEST_F(ThreadTest, StartWithOptions_StackSize) { Thread a("StartWithStackSize"); // Ensure that the thread can work with only 12 kb and still process a // message. Thread::Options options; options.stack_size = 12*1024; EXPECT_TRUE(a.StartWithOptions(options)); EXPECT_TRUE(a.message_loop()); EXPECT_TRUE(a.IsRunning()); bool was_invoked = false; a.message_loop()->PostTask(FROM_HERE, base::Bind(&ToggleValue, &was_invoked)); // wait for the task to run (we could use a kernel event here // instead to avoid busy waiting, but this is sufficient for // testing purposes). for (int i = 100; i >= 0 && !was_invoked; --i) { base::PlatformThread::Sleep(base::TimeDelta::FromMilliseconds(10)); } EXPECT_TRUE(was_invoked); } TEST_F(ThreadTest, TwoTasks) { bool was_invoked = false; { Thread a("TwoTasks"); EXPECT_TRUE(a.Start()); EXPECT_TRUE(a.message_loop()); // Test that all events are dispatched before the Thread object is // destroyed. We do this by dispatching a sleep event before the // event that will toggle our sentinel value. a.message_loop()->PostTask( FROM_HERE, base::Bind( static_cast<void (*)(base::TimeDelta)>( &base::PlatformThread::Sleep), base::TimeDelta::FromMilliseconds(20))); a.message_loop()->PostTask(FROM_HERE, base::Bind(&ToggleValue, &was_invoked)); } EXPECT_TRUE(was_invoked); } TEST_F(ThreadTest, StopSoon) { Thread a("StopSoon"); EXPECT_TRUE(a.Start()); EXPECT_TRUE(a.message_loop()); EXPECT_TRUE(a.IsRunning()); a.StopSoon(); a.StopSoon(); a.Stop(); EXPECT_FALSE(a.message_loop()); EXPECT_FALSE(a.IsRunning()); } TEST_F(ThreadTest, ThreadName) { Thread a("ThreadName"); EXPECT_TRUE(a.Start()); EXPECT_EQ("ThreadName", a.thread_name()); } // Make sure we can't use a thread between Start() and Init(). TEST_F(ThreadTest, SleepInsideInit) { SleepInsideInitThread t; EXPECT_FALSE(t.InitCalled()); t.Start(); EXPECT_TRUE(t.InitCalled()); } // Make sure that the destruction sequence is: // // (1) Thread::CleanUp() // (2) MessageLoop::~MessageLoop() // MessageLoop::DestructionObservers called. TEST_F(ThreadTest, CleanUp) { EventList captured_events; CapturingDestructionObserver loop_destruction_observer(&captured_events); { // Start a thread which writes its event into |captured_events|. CaptureToEventList t(&captured_events); EXPECT_TRUE(t.Start()); EXPECT_TRUE(t.message_loop()); EXPECT_TRUE(t.IsRunning()); // Register an observer that writes into |captured_events| once the // thread's message loop is destroyed. t.message_loop()->PostTask( FROM_HERE, base::Bind(&RegisterDestructionObserver, base::Unretained(&loop_destruction_observer))); // Upon leaving this scope, the thread is deleted. } // Check the order of events during shutdown. ASSERT_EQ(static_cast<size_t>(THREAD_NUM_EVENTS), captured_events.size()); EXPECT_EQ(THREAD_EVENT_INIT, captured_events[0]); EXPECT_EQ(THREAD_EVENT_CLEANUP, captured_events[1]); EXPECT_EQ(THREAD_EVENT_MESSAGE_LOOP_DESTROYED, captured_events[2]); }