|  | // 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 <stddef.h> | 
|  | #include <stdint.h> | 
|  |  | 
|  | #include <utility> | 
|  | #include <vector> | 
|  |  | 
|  | #include "base/bind.h" | 
|  | #include "base/debug/leak_annotations.h" | 
|  | #include "base/macros.h" | 
|  | #include "base/memory/ptr_util.h" | 
|  | #include "base/message_loop/message_loop.h" | 
|  | #include "base/message_loop/message_loop_current.h" | 
|  | #include "base/run_loop.h" | 
|  | #include "base/single_thread_task_runner.h" | 
|  | #include "base/synchronization/waitable_event.h" | 
|  | #include "base/test/gtest_util.h" | 
|  | #include "base/third_party/dynamic_annotations/dynamic_annotations.h" | 
|  | #include "base/threading/platform_thread.h" | 
|  | #include "base/time/time.h" | 
|  | #include "build/build_config.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"); | 
|  | } | 
|  | ~SleepInsideInitThread() override { Stop(); } | 
|  |  | 
|  | void Init() override { | 
|  | base::PlatformThread::Sleep(base::TimeDelta::FromMilliseconds(500)); | 
|  | init_called_ = true; | 
|  | } | 
|  | bool InitCalled() { return init_called_; } | 
|  |  | 
|  | private: | 
|  | bool init_called_; | 
|  |  | 
|  | DISALLOW_COPY_AND_ASSIGN(SleepInsideInitThread); | 
|  | }; | 
|  |  | 
|  | 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) { | 
|  | } | 
|  |  | 
|  | ~CaptureToEventList() override { Stop(); } | 
|  |  | 
|  | void Init() override { event_list_->push_back(THREAD_EVENT_INIT); } | 
|  |  | 
|  | void CleanUp() override { event_list_->push_back(THREAD_EVENT_CLEANUP); } | 
|  |  | 
|  | private: | 
|  | EventList* event_list_; | 
|  |  | 
|  | DISALLOW_COPY_AND_ASSIGN(CaptureToEventList); | 
|  | }; | 
|  |  | 
|  | // Observer that writes a value into |event_list| when a message loop has been | 
|  | // destroyed. | 
|  | class CapturingDestructionObserver | 
|  | : public base::MessageLoopCurrent::DestructionObserver { | 
|  | public: | 
|  | // |event_list| must remain valid throughout the observer's lifetime. | 
|  | explicit CapturingDestructionObserver(EventList* event_list) | 
|  | : event_list_(event_list) { | 
|  | } | 
|  |  | 
|  | // DestructionObserver implementation: | 
|  | void WillDestroyCurrentMessageLoop() override { | 
|  | event_list_->push_back(THREAD_EVENT_MESSAGE_LOOP_DESTROYED); | 
|  | event_list_ = nullptr; | 
|  | } | 
|  |  | 
|  | private: | 
|  | EventList* event_list_; | 
|  |  | 
|  | DISALLOW_COPY_AND_ASSIGN(CapturingDestructionObserver); | 
|  | }; | 
|  |  | 
|  | // Task that adds a destruction observer to the current message loop. | 
|  | void RegisterDestructionObserver( | 
|  | base::MessageLoopCurrent::DestructionObserver* observer) { | 
|  | base::MessageLoopCurrent::Get()->AddDestructionObserver(observer); | 
|  | } | 
|  |  | 
|  | // Task that calls GetThreadId() of |thread|, stores the result into |id|, then | 
|  | // signal |event|. | 
|  | void ReturnThreadId(base::Thread* thread, | 
|  | base::PlatformThreadId* id, | 
|  | base::WaitableEvent* event) { | 
|  | *id = thread->GetThreadId(); | 
|  | event->Signal(); | 
|  | } | 
|  |  | 
|  | }  // namespace | 
|  |  | 
|  | TEST_F(ThreadTest, StartWithOptions_StackSize) { | 
|  | Thread a("StartWithStackSize"); | 
|  | // Ensure that the thread can work with only 12 kb and still process a | 
|  | // message. At the same time, we should scale with the bitness of the system | 
|  | // where 12 kb is definitely not enough. | 
|  | // 12 kb = 3072 Slots on a 32-bit system, so we'll scale based off of that. | 
|  | Thread::Options options; | 
|  | #if defined(ADDRESS_SANITIZER) || !defined(NDEBUG) | 
|  | // ASan bloats the stack variables and overflows the 3072 slot stack. Some | 
|  | // debug builds also grow the stack too much. | 
|  | options.stack_size = 2 * 3072 * sizeof(uintptr_t); | 
|  | #else | 
|  | options.stack_size = 3072 * sizeof(uintptr_t); | 
|  | #endif | 
|  | EXPECT_TRUE(a.StartWithOptions(options)); | 
|  | EXPECT_TRUE(a.message_loop()); | 
|  | EXPECT_TRUE(a.IsRunning()); | 
|  |  | 
|  | base::WaitableEvent event(base::WaitableEvent::ResetPolicy::AUTOMATIC, | 
|  | base::WaitableEvent::InitialState::NOT_SIGNALED); | 
|  | a.task_runner()->PostTask( | 
|  | FROM_HERE, | 
|  | base::BindOnce(&base::WaitableEvent::Signal, base::Unretained(&event))); | 
|  | event.Wait(); | 
|  | } | 
|  |  | 
|  | // Intentional test-only race for otherwise untestable code, won't fix. | 
|  | // https://crbug.com/634383 | 
|  | #if !defined(THREAD_SANITIZER) | 
|  | TEST_F(ThreadTest, StartWithOptions_NonJoinable) { | 
|  | Thread* a = new Thread("StartNonJoinable"); | 
|  | // Non-joinable threads have to be leaked for now (see | 
|  | // Thread::Options::joinable for details). | 
|  | ANNOTATE_LEAKING_OBJECT_PTR(a); | 
|  |  | 
|  | Thread::Options options; | 
|  | options.joinable = false; | 
|  | EXPECT_TRUE(a->StartWithOptions(options)); | 
|  | EXPECT_TRUE(a->message_loop()); | 
|  | EXPECT_TRUE(a->IsRunning()); | 
|  |  | 
|  | // Without this call this test is racy. The above IsRunning() succeeds because | 
|  | // of an early-return condition while between Start() and StopSoon(), after | 
|  | // invoking StopSoon() below this early-return condition is no longer | 
|  | // satisfied and the real |is_running_| bit has to be checked. It could still | 
|  | // be false if the message loop hasn't started for real in practice. This is | 
|  | // only a requirement for this test because the non-joinable property forces | 
|  | // it to use StopSoon() and not wait for a complete Stop(). | 
|  | EXPECT_TRUE(a->WaitUntilThreadStarted()); | 
|  |  | 
|  | // Make the thread block until |block_event| is signaled. | 
|  | base::WaitableEvent block_event( | 
|  | base::WaitableEvent::ResetPolicy::AUTOMATIC, | 
|  | base::WaitableEvent::InitialState::NOT_SIGNALED); | 
|  | a->task_runner()->PostTask(FROM_HERE, | 
|  | base::BindOnce(&base::WaitableEvent::Wait, | 
|  | base::Unretained(&block_event))); | 
|  |  | 
|  | a->StopSoon(); | 
|  | EXPECT_TRUE(a->IsRunning()); | 
|  |  | 
|  | // Unblock the task and give a bit of extra time to unwind QuitWhenIdle(). | 
|  | block_event.Signal(); | 
|  | base::PlatformThread::Sleep(base::TimeDelta::FromMilliseconds(20)); | 
|  |  | 
|  | // The thread should now have stopped on its own. | 
|  | EXPECT_FALSE(a->IsRunning()); | 
|  | } | 
|  | #endif | 
|  |  | 
|  | TEST_F(ThreadTest, TwoTasksOnJoinableThread) { | 
|  | bool was_invoked = false; | 
|  | { | 
|  | Thread a("TwoTasksOnJoinableThread"); | 
|  | 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.task_runner()->PostTask( | 
|  | FROM_HERE, base::BindOnce(static_cast<void (*)(base::TimeDelta)>( | 
|  | &base::PlatformThread::Sleep), | 
|  | base::TimeDelta::FromMilliseconds(20))); | 
|  | a.task_runner()->PostTask(FROM_HERE, | 
|  | base::BindOnce(&ToggleValue, &was_invoked)); | 
|  | } | 
|  | EXPECT_TRUE(was_invoked); | 
|  | } | 
|  |  | 
|  | TEST_F(ThreadTest, DestroyWhileRunningIsSafe) { | 
|  | Thread a("DestroyWhileRunningIsSafe"); | 
|  | EXPECT_TRUE(a.Start()); | 
|  | EXPECT_TRUE(a.WaitUntilThreadStarted()); | 
|  | } | 
|  |  | 
|  | // TODO(gab): Enable this test when destroying a non-joinable Thread instance | 
|  | // is supported (proposal @ https://crbug.com/629139#c14). | 
|  | TEST_F(ThreadTest, DISABLED_DestroyWhileRunningNonJoinableIsSafe) { | 
|  | { | 
|  | Thread a("DestroyWhileRunningNonJoinableIsSafe"); | 
|  | Thread::Options options; | 
|  | options.joinable = false; | 
|  | EXPECT_TRUE(a.StartWithOptions(options)); | 
|  | EXPECT_TRUE(a.WaitUntilThreadStarted()); | 
|  | } | 
|  |  | 
|  | // Attempt to catch use-after-frees from the non-joinable thread in the | 
|  | // scope of this test if any. | 
|  | base::PlatformThread::Sleep(base::TimeDelta::FromMilliseconds(20)); | 
|  | } | 
|  |  | 
|  | TEST_F(ThreadTest, StopSoon) { | 
|  | Thread a("StopSoon"); | 
|  | EXPECT_TRUE(a.Start()); | 
|  | EXPECT_TRUE(a.message_loop()); | 
|  | EXPECT_TRUE(a.IsRunning()); | 
|  | a.StopSoon(); | 
|  | a.Stop(); | 
|  | EXPECT_FALSE(a.message_loop()); | 
|  | EXPECT_FALSE(a.IsRunning()); | 
|  | } | 
|  |  | 
|  | TEST_F(ThreadTest, StopTwiceNop) { | 
|  | Thread a("StopTwiceNop"); | 
|  | EXPECT_TRUE(a.Start()); | 
|  | EXPECT_TRUE(a.message_loop()); | 
|  | EXPECT_TRUE(a.IsRunning()); | 
|  | a.StopSoon(); | 
|  | // Calling StopSoon() a second time should be a nop. | 
|  | a.StopSoon(); | 
|  | a.Stop(); | 
|  | // Same with Stop(). | 
|  | a.Stop(); | 
|  | EXPECT_FALSE(a.message_loop()); | 
|  | EXPECT_FALSE(a.IsRunning()); | 
|  | // Calling them when not running should also nop. | 
|  | a.StopSoon(); | 
|  | a.Stop(); | 
|  | } | 
|  |  | 
|  | // TODO(gab): Enable this test in conjunction with re-enabling the sequence | 
|  | // check in Thread::Stop() as part of http://crbug.com/629139. | 
|  | TEST_F(ThreadTest, DISABLED_StopOnNonOwningThreadIsDeath) { | 
|  | Thread a("StopOnNonOwningThreadDeath"); | 
|  | EXPECT_TRUE(a.StartAndWaitForTesting()); | 
|  |  | 
|  | Thread b("NonOwningThread"); | 
|  | b.Start(); | 
|  | EXPECT_DCHECK_DEATH({ | 
|  | // Stopping |a| on |b| isn't allowed. | 
|  | b.task_runner()->PostTask( | 
|  | FROM_HERE, base::BindOnce(&Thread::Stop, base::Unretained(&a))); | 
|  | // Block here so the DCHECK on |b| always happens in this scope. | 
|  | base::PlatformThread::Sleep(base::TimeDelta::Max()); | 
|  | }); | 
|  | } | 
|  |  | 
|  | TEST_F(ThreadTest, TransferOwnershipAndStop) { | 
|  | std::unique_ptr<Thread> a = | 
|  | std::make_unique<Thread>("TransferOwnershipAndStop"); | 
|  | EXPECT_TRUE(a->StartAndWaitForTesting()); | 
|  | EXPECT_TRUE(a->IsRunning()); | 
|  |  | 
|  | Thread b("TakingOwnershipThread"); | 
|  | b.Start(); | 
|  |  | 
|  | base::WaitableEvent event(base::WaitableEvent::ResetPolicy::MANUAL, | 
|  | base::WaitableEvent::InitialState::NOT_SIGNALED); | 
|  |  | 
|  | // a->DetachFromSequence() should allow |b| to use |a|'s Thread API. | 
|  | a->DetachFromSequence(); | 
|  | b.task_runner()->PostTask( | 
|  | FROM_HERE, base::BindOnce( | 
|  | [](std::unique_ptr<Thread> thread_to_stop, | 
|  | base::WaitableEvent* event_to_signal) -> void { | 
|  | thread_to_stop->Stop(); | 
|  | event_to_signal->Signal(); | 
|  | }, | 
|  | std::move(a), base::Unretained(&event))); | 
|  |  | 
|  | event.Wait(); | 
|  | } | 
|  |  | 
|  | TEST_F(ThreadTest, StartTwice) { | 
|  | Thread a("StartTwice"); | 
|  |  | 
|  | 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()); | 
|  | } | 
|  |  | 
|  | // Intentional test-only race for otherwise untestable code, won't fix. | 
|  | // https://crbug.com/634383 | 
|  | #if !defined(THREAD_SANITIZER) | 
|  | TEST_F(ThreadTest, StartTwiceNonJoinableNotAllowed) { | 
|  | LOG(ERROR) << __FUNCTION__; | 
|  | Thread* a = new Thread("StartTwiceNonJoinable"); | 
|  | // Non-joinable threads have to be leaked for now (see | 
|  | // Thread::Options::joinable for details). | 
|  | ANNOTATE_LEAKING_OBJECT_PTR(a); | 
|  |  | 
|  | Thread::Options options; | 
|  | options.joinable = false; | 
|  | EXPECT_TRUE(a->StartWithOptions(options)); | 
|  | EXPECT_TRUE(a->message_loop()); | 
|  | EXPECT_TRUE(a->IsRunning()); | 
|  |  | 
|  | // Signaled when last task on |a| is processed. | 
|  | base::WaitableEvent last_task_event( | 
|  | base::WaitableEvent::ResetPolicy::AUTOMATIC, | 
|  | base::WaitableEvent::InitialState::NOT_SIGNALED); | 
|  | a->task_runner()->PostTask( | 
|  | FROM_HERE, base::BindOnce(&base::WaitableEvent::Signal, | 
|  | base::Unretained(&last_task_event))); | 
|  |  | 
|  | // StopSoon() is non-blocking, Yield() to |a|, wait for last task to be | 
|  | // processed and a little more for QuitWhenIdle() to unwind before considering | 
|  | // the thread "stopped". | 
|  | a->StopSoon(); | 
|  | base::PlatformThread::YieldCurrentThread(); | 
|  | last_task_event.Wait(); | 
|  | base::PlatformThread::Sleep(base::TimeDelta::FromMilliseconds(20)); | 
|  |  | 
|  | // This test assumes that the above was sufficient to let the thread fully | 
|  | // stop. | 
|  | ASSERT_FALSE(a->IsRunning()); | 
|  |  | 
|  | // Restarting it should not be allowed. | 
|  | EXPECT_DCHECK_DEATH(a->Start()); | 
|  | } | 
|  | #endif | 
|  |  | 
|  | TEST_F(ThreadTest, ThreadName) { | 
|  | Thread a("ThreadName"); | 
|  | EXPECT_TRUE(a.Start()); | 
|  | EXPECT_EQ("ThreadName", a.thread_name()); | 
|  | } | 
|  |  | 
|  | TEST_F(ThreadTest, ThreadId) { | 
|  | Thread a("ThreadId0"); | 
|  | Thread b("ThreadId1"); | 
|  | a.Start(); | 
|  | b.Start(); | 
|  |  | 
|  | // Post a task that calls GetThreadId() on the created thread. | 
|  | base::WaitableEvent event(base::WaitableEvent::ResetPolicy::AUTOMATIC, | 
|  | base::WaitableEvent::InitialState::NOT_SIGNALED); | 
|  | base::PlatformThreadId id_from_new_thread; | 
|  | a.task_runner()->PostTask( | 
|  | FROM_HERE, | 
|  | base::BindOnce(ReturnThreadId, &a, &id_from_new_thread, &event)); | 
|  |  | 
|  | // Call GetThreadId() on the current thread before calling event.Wait() so | 
|  | // that this test can find a race issue with TSAN. | 
|  | base::PlatformThreadId id_from_current_thread = a.GetThreadId(); | 
|  |  | 
|  | // Check if GetThreadId() returns consistent value in both threads. | 
|  | event.Wait(); | 
|  | EXPECT_EQ(id_from_current_thread, id_from_new_thread); | 
|  |  | 
|  | // A started thread should have a valid ID. | 
|  | EXPECT_NE(base::kInvalidThreadId, a.GetThreadId()); | 
|  | EXPECT_NE(base::kInvalidThreadId, b.GetThreadId()); | 
|  |  | 
|  | // Each thread should have a different thread ID. | 
|  | EXPECT_NE(a.GetThreadId(), b.GetThreadId()); | 
|  | } | 
|  |  | 
|  | TEST_F(ThreadTest, ThreadIdWithRestart) { | 
|  | Thread a("ThreadIdWithRestart"); | 
|  | base::PlatformThreadId previous_id = base::kInvalidThreadId; | 
|  |  | 
|  | for (size_t i = 0; i < 16; ++i) { | 
|  | EXPECT_TRUE(a.Start()); | 
|  | base::PlatformThreadId current_id = a.GetThreadId(); | 
|  | EXPECT_NE(previous_id, current_id); | 
|  | previous_id = current_id; | 
|  | a.Stop(); | 
|  | } | 
|  | } | 
|  |  | 
|  | // Make sure Init() is called after Start() and before | 
|  | // WaitUntilThreadInitialized() returns. | 
|  | TEST_F(ThreadTest, SleepInsideInit) { | 
|  | SleepInsideInitThread t; | 
|  | EXPECT_FALSE(t.InitCalled()); | 
|  | t.StartAndWaitForTesting(); | 
|  | EXPECT_TRUE(t.InitCalled()); | 
|  | } | 
|  |  | 
|  | // Make sure that the destruction sequence is: | 
|  | // | 
|  | //  (1) Thread::CleanUp() | 
|  | //  (2) MessageLoop::~MessageLoop() | 
|  | //      MessageLoopCurrent::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.task_runner()->PostTask( | 
|  | FROM_HERE, | 
|  | base::BindOnce(&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]); | 
|  | } | 
|  |  | 
|  | TEST_F(ThreadTest, ThreadNotStarted) { | 
|  | Thread a("Inert"); | 
|  | EXPECT_FALSE(a.task_runner()); | 
|  | } | 
|  |  | 
|  | TEST_F(ThreadTest, MultipleWaitUntilThreadStarted) { | 
|  | Thread a("MultipleWaitUntilThreadStarted"); | 
|  | EXPECT_TRUE(a.Start()); | 
|  | // It's OK to call WaitUntilThreadStarted() multiple times. | 
|  | EXPECT_TRUE(a.WaitUntilThreadStarted()); | 
|  | EXPECT_TRUE(a.WaitUntilThreadStarted()); | 
|  | } | 
|  |  | 
|  | TEST_F(ThreadTest, FlushForTesting) { | 
|  | Thread a("FlushForTesting"); | 
|  |  | 
|  | // Flushing a non-running thread should be a no-op. | 
|  | a.FlushForTesting(); | 
|  |  | 
|  | ASSERT_TRUE(a.Start()); | 
|  |  | 
|  | // Flushing a thread with no tasks shouldn't block. | 
|  | a.FlushForTesting(); | 
|  |  | 
|  | constexpr base::TimeDelta kSleepPerTestTask = | 
|  | base::TimeDelta::FromMilliseconds(50); | 
|  | constexpr size_t kNumSleepTasks = 5; | 
|  |  | 
|  | const base::TimeTicks ticks_before_post = base::TimeTicks::Now(); | 
|  |  | 
|  | for (size_t i = 0; i < kNumSleepTasks; ++i) { | 
|  | a.task_runner()->PostTask( | 
|  | FROM_HERE, | 
|  | base::BindOnce(&base::PlatformThread::Sleep, kSleepPerTestTask)); | 
|  | } | 
|  |  | 
|  | // All tasks should have executed, as reflected by the elapsed time. | 
|  | a.FlushForTesting(); | 
|  | EXPECT_GE(base::TimeTicks::Now() - ticks_before_post, | 
|  | kNumSleepTasks * kSleepPerTestTask); | 
|  |  | 
|  | a.Stop(); | 
|  |  | 
|  | // Flushing a stopped thread should be a no-op. | 
|  | a.FlushForTesting(); | 
|  | } | 
|  |  | 
|  | namespace { | 
|  |  | 
|  | // A Thread which uses a MessageLoop on the stack. It won't start a real | 
|  | // underlying thread (instead its messages can be processed by a RunLoop on the | 
|  | // stack). | 
|  | class ExternalMessageLoopThread : public Thread { | 
|  | public: | 
|  | ExternalMessageLoopThread() : Thread("ExternalMessageLoopThread") {} | 
|  |  | 
|  | ~ExternalMessageLoopThread() override { Stop(); } | 
|  |  | 
|  | void InstallMessageLoop() { SetMessageLoop(&external_message_loop_); } | 
|  |  | 
|  | void VerifyUsingExternalMessageLoop( | 
|  | bool expected_using_external_message_loop) { | 
|  | EXPECT_EQ(expected_using_external_message_loop, | 
|  | using_external_message_loop()); | 
|  | } | 
|  |  | 
|  | private: | 
|  | base::MessageLoop external_message_loop_; | 
|  |  | 
|  | DISALLOW_COPY_AND_ASSIGN(ExternalMessageLoopThread); | 
|  | }; | 
|  |  | 
|  | }  // namespace | 
|  |  | 
|  | TEST_F(ThreadTest, ExternalMessageLoop) { | 
|  | ExternalMessageLoopThread a; | 
|  | EXPECT_FALSE(a.message_loop()); | 
|  | EXPECT_FALSE(a.IsRunning()); | 
|  | a.VerifyUsingExternalMessageLoop(false); | 
|  |  | 
|  | a.InstallMessageLoop(); | 
|  | EXPECT_TRUE(a.message_loop()); | 
|  | EXPECT_TRUE(a.IsRunning()); | 
|  | a.VerifyUsingExternalMessageLoop(true); | 
|  |  | 
|  | bool ran = false; | 
|  | a.task_runner()->PostTask( | 
|  | FROM_HERE, base::BindOnce([](bool* toggled) { *toggled = true; }, &ran)); | 
|  | base::RunLoop().RunUntilIdle(); | 
|  | EXPECT_TRUE(ran); | 
|  |  | 
|  | a.Stop(); | 
|  | EXPECT_FALSE(a.message_loop()); | 
|  | EXPECT_FALSE(a.IsRunning()); | 
|  | a.VerifyUsingExternalMessageLoop(true); | 
|  |  | 
|  | // Confirm that running any remaining tasks posted from Stop() goes smoothly | 
|  | // (e.g. https://codereview.chromium.org/2135413003/#ps300001 crashed if | 
|  | // StopSoon() posted Thread::ThreadQuitHelper() while |run_loop_| was null). | 
|  | base::RunLoop().RunUntilIdle(); | 
|  | } |