|  | // Copyright 2016 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/task_scheduler/scheduler_worker_stack.h" | 
|  |  | 
|  | #include "base/logging.h" | 
|  | #include "base/memory/ptr_util.h" | 
|  | #include "base/memory/ref_counted.h" | 
|  | #include "base/task_scheduler/scheduler_worker.h" | 
|  | #include "base/task_scheduler/sequence.h" | 
|  | #include "base/task_scheduler/task_tracker.h" | 
|  | #include "base/test/gtest_util.h" | 
|  | #include "base/threading/platform_thread.h" | 
|  | #include "base/time/time.h" | 
|  | #include "testing/gtest/include/gtest/gtest.h" | 
|  |  | 
|  | namespace base { | 
|  | namespace internal { | 
|  |  | 
|  | namespace { | 
|  |  | 
|  | class MockSchedulerWorkerDelegate : public SchedulerWorker::Delegate { | 
|  | public: | 
|  | void OnCanScheduleSequence(scoped_refptr<Sequence> sequence) override { | 
|  | ADD_FAILURE() << "Unexpected call to OnCanScheduleSequence()."; | 
|  | } | 
|  | SchedulerWorker::ThreadLabel GetThreadLabel() const override { | 
|  | return SchedulerWorker::ThreadLabel::DEDICATED; | 
|  | } | 
|  | void OnMainEntry(const SchedulerWorker* worker) override {} | 
|  | scoped_refptr<Sequence> GetWork(SchedulerWorker* worker) override { | 
|  | return nullptr; | 
|  | } | 
|  | void DidRunTask() override { | 
|  | ADD_FAILURE() << "Unexpected call to DidRunTask()"; | 
|  | } | 
|  | void ReEnqueueSequence(scoped_refptr<Sequence> sequence) override { | 
|  | ADD_FAILURE() << "Unexpected call to ReEnqueueSequence()"; | 
|  | } | 
|  | TimeDelta GetSleepTimeout() override { | 
|  | return TimeDelta::Max(); | 
|  | } | 
|  | }; | 
|  |  | 
|  | class TaskSchedulerWorkerStackTest : public testing::Test { | 
|  | protected: | 
|  | void SetUp() override { | 
|  | worker_a_ = MakeRefCounted<SchedulerWorker>( | 
|  | ThreadPriority::NORMAL, WrapUnique(new MockSchedulerWorkerDelegate), | 
|  | task_tracker_.GetTrackedRef()); | 
|  | ASSERT_TRUE(worker_a_); | 
|  | worker_b_ = MakeRefCounted<SchedulerWorker>( | 
|  | ThreadPriority::NORMAL, WrapUnique(new MockSchedulerWorkerDelegate), | 
|  | task_tracker_.GetTrackedRef()); | 
|  | ASSERT_TRUE(worker_b_); | 
|  | worker_c_ = MakeRefCounted<SchedulerWorker>( | 
|  | ThreadPriority::NORMAL, WrapUnique(new MockSchedulerWorkerDelegate), | 
|  | task_tracker_.GetTrackedRef()); | 
|  | ASSERT_TRUE(worker_c_); | 
|  | } | 
|  |  | 
|  | private: | 
|  | TaskTracker task_tracker_ = {"Test"}; | 
|  |  | 
|  | protected: | 
|  | scoped_refptr<SchedulerWorker> worker_a_; | 
|  | scoped_refptr<SchedulerWorker> worker_b_; | 
|  | scoped_refptr<SchedulerWorker> worker_c_; | 
|  | }; | 
|  |  | 
|  | }  // namespace | 
|  |  | 
|  | // Verify that Push() and Pop() add/remove values in FIFO order. | 
|  | TEST_F(TaskSchedulerWorkerStackTest, PushPop) { | 
|  | SchedulerWorkerStack stack; | 
|  | EXPECT_EQ(nullptr, stack.Pop()); | 
|  |  | 
|  | EXPECT_TRUE(stack.IsEmpty()); | 
|  | EXPECT_EQ(0U, stack.Size()); | 
|  |  | 
|  | stack.Push(worker_a_.get()); | 
|  | EXPECT_FALSE(stack.IsEmpty()); | 
|  | EXPECT_EQ(1U, stack.Size()); | 
|  |  | 
|  | stack.Push(worker_b_.get()); | 
|  | EXPECT_FALSE(stack.IsEmpty()); | 
|  | EXPECT_EQ(2U, stack.Size()); | 
|  |  | 
|  | stack.Push(worker_c_.get()); | 
|  | EXPECT_FALSE(stack.IsEmpty()); | 
|  | EXPECT_EQ(3U, stack.Size()); | 
|  |  | 
|  | EXPECT_EQ(worker_c_.get(), stack.Pop()); | 
|  | EXPECT_FALSE(stack.IsEmpty()); | 
|  | EXPECT_EQ(2U, stack.Size()); | 
|  |  | 
|  | stack.Push(worker_c_.get()); | 
|  | EXPECT_FALSE(stack.IsEmpty()); | 
|  | EXPECT_EQ(3U, stack.Size()); | 
|  |  | 
|  | EXPECT_EQ(worker_c_.get(), stack.Pop()); | 
|  | EXPECT_FALSE(stack.IsEmpty()); | 
|  | EXPECT_EQ(2U, stack.Size()); | 
|  |  | 
|  | EXPECT_EQ(worker_b_.get(), stack.Pop()); | 
|  | EXPECT_FALSE(stack.IsEmpty()); | 
|  | EXPECT_EQ(1U, stack.Size()); | 
|  |  | 
|  | EXPECT_EQ(worker_a_.get(), stack.Pop()); | 
|  | EXPECT_TRUE(stack.IsEmpty()); | 
|  | EXPECT_EQ(0U, stack.Size()); | 
|  |  | 
|  | EXPECT_EQ(nullptr, stack.Pop()); | 
|  | } | 
|  |  | 
|  | // Verify that Peek() returns the correct values in FIFO order. | 
|  | TEST_F(TaskSchedulerWorkerStackTest, PeekPop) { | 
|  | SchedulerWorkerStack stack; | 
|  | EXPECT_EQ(nullptr, stack.Peek()); | 
|  |  | 
|  | EXPECT_TRUE(stack.IsEmpty()); | 
|  | EXPECT_EQ(0U, stack.Size()); | 
|  |  | 
|  | stack.Push(worker_a_.get()); | 
|  | EXPECT_EQ(worker_a_.get(), stack.Peek()); | 
|  | EXPECT_FALSE(stack.IsEmpty()); | 
|  | EXPECT_EQ(1U, stack.Size()); | 
|  |  | 
|  | stack.Push(worker_b_.get()); | 
|  | EXPECT_EQ(worker_b_.get(), stack.Peek()); | 
|  | EXPECT_FALSE(stack.IsEmpty()); | 
|  | EXPECT_EQ(2U, stack.Size()); | 
|  |  | 
|  | stack.Push(worker_c_.get()); | 
|  | EXPECT_EQ(worker_c_.get(), stack.Peek()); | 
|  | EXPECT_FALSE(stack.IsEmpty()); | 
|  | EXPECT_EQ(3U, stack.Size()); | 
|  |  | 
|  | EXPECT_EQ(worker_c_.get(), stack.Pop()); | 
|  | EXPECT_EQ(worker_b_.get(), stack.Peek()); | 
|  | EXPECT_FALSE(stack.IsEmpty()); | 
|  | EXPECT_EQ(2U, stack.Size()); | 
|  |  | 
|  | EXPECT_EQ(worker_b_.get(), stack.Pop()); | 
|  | EXPECT_EQ(worker_a_.get(), stack.Peek()); | 
|  | EXPECT_FALSE(stack.IsEmpty()); | 
|  | EXPECT_EQ(1U, stack.Size()); | 
|  |  | 
|  | EXPECT_EQ(worker_a_.get(), stack.Pop()); | 
|  | EXPECT_TRUE(stack.IsEmpty()); | 
|  | EXPECT_EQ(0U, stack.Size()); | 
|  |  | 
|  | EXPECT_EQ(nullptr, stack.Peek()); | 
|  | } | 
|  |  | 
|  | // Verify that Contains() returns true for workers on the stack. | 
|  | TEST_F(TaskSchedulerWorkerStackTest, Contains) { | 
|  | SchedulerWorkerStack stack; | 
|  | EXPECT_FALSE(stack.Contains(worker_a_.get())); | 
|  | EXPECT_FALSE(stack.Contains(worker_b_.get())); | 
|  | EXPECT_FALSE(stack.Contains(worker_c_.get())); | 
|  |  | 
|  | stack.Push(worker_a_.get()); | 
|  | EXPECT_TRUE(stack.Contains(worker_a_.get())); | 
|  | EXPECT_FALSE(stack.Contains(worker_b_.get())); | 
|  | EXPECT_FALSE(stack.Contains(worker_c_.get())); | 
|  |  | 
|  | stack.Push(worker_b_.get()); | 
|  | EXPECT_TRUE(stack.Contains(worker_a_.get())); | 
|  | EXPECT_TRUE(stack.Contains(worker_b_.get())); | 
|  | EXPECT_FALSE(stack.Contains(worker_c_.get())); | 
|  |  | 
|  | stack.Push(worker_c_.get()); | 
|  | EXPECT_TRUE(stack.Contains(worker_a_.get())); | 
|  | EXPECT_TRUE(stack.Contains(worker_b_.get())); | 
|  | EXPECT_TRUE(stack.Contains(worker_c_.get())); | 
|  |  | 
|  | stack.Pop(); | 
|  | EXPECT_TRUE(stack.Contains(worker_a_.get())); | 
|  | EXPECT_TRUE(stack.Contains(worker_b_.get())); | 
|  | EXPECT_FALSE(stack.Contains(worker_c_.get())); | 
|  |  | 
|  | stack.Pop(); | 
|  | EXPECT_TRUE(stack.Contains(worker_a_.get())); | 
|  | EXPECT_FALSE(stack.Contains(worker_b_.get())); | 
|  | EXPECT_FALSE(stack.Contains(worker_c_.get())); | 
|  |  | 
|  | stack.Pop(); | 
|  | EXPECT_FALSE(stack.Contains(worker_a_.get())); | 
|  | EXPECT_FALSE(stack.Contains(worker_b_.get())); | 
|  | EXPECT_FALSE(stack.Contains(worker_c_.get())); | 
|  | } | 
|  |  | 
|  | // Verify that a value can be removed by Remove(). | 
|  | TEST_F(TaskSchedulerWorkerStackTest, Remove) { | 
|  | SchedulerWorkerStack stack; | 
|  | EXPECT_TRUE(stack.IsEmpty()); | 
|  | EXPECT_EQ(0U, stack.Size()); | 
|  |  | 
|  | stack.Push(worker_a_.get()); | 
|  | EXPECT_FALSE(stack.IsEmpty()); | 
|  | EXPECT_EQ(1U, stack.Size()); | 
|  |  | 
|  | stack.Push(worker_b_.get()); | 
|  | EXPECT_FALSE(stack.IsEmpty()); | 
|  | EXPECT_EQ(2U, stack.Size()); | 
|  |  | 
|  | stack.Push(worker_c_.get()); | 
|  | EXPECT_FALSE(stack.IsEmpty()); | 
|  | EXPECT_EQ(3U, stack.Size()); | 
|  |  | 
|  | stack.Remove(worker_b_.get()); | 
|  | EXPECT_FALSE(stack.IsEmpty()); | 
|  | EXPECT_EQ(2U, stack.Size()); | 
|  |  | 
|  | EXPECT_EQ(worker_c_.get(), stack.Pop()); | 
|  | EXPECT_FALSE(stack.IsEmpty()); | 
|  | EXPECT_EQ(1U, stack.Size()); | 
|  |  | 
|  | EXPECT_EQ(worker_a_.get(), stack.Pop()); | 
|  | EXPECT_TRUE(stack.IsEmpty()); | 
|  | EXPECT_EQ(0U, stack.Size()); | 
|  | } | 
|  |  | 
|  | // Verify that a value can be pushed again after it has been removed. | 
|  | TEST_F(TaskSchedulerWorkerStackTest, PushAfterRemove) { | 
|  | SchedulerWorkerStack stack; | 
|  | EXPECT_EQ(0U, stack.Size()); | 
|  | EXPECT_TRUE(stack.IsEmpty()); | 
|  |  | 
|  | stack.Push(worker_a_.get()); | 
|  | EXPECT_EQ(1U, stack.Size()); | 
|  | EXPECT_FALSE(stack.IsEmpty()); | 
|  |  | 
|  | stack.Remove(worker_a_.get()); | 
|  | EXPECT_EQ(0U, stack.Size()); | 
|  | EXPECT_TRUE(stack.IsEmpty()); | 
|  |  | 
|  | stack.Push(worker_a_.get()); | 
|  | EXPECT_EQ(1U, stack.Size()); | 
|  | EXPECT_FALSE(stack.IsEmpty()); | 
|  | } | 
|  |  | 
|  | // Verify that Push() DCHECKs when a value is inserted twice. | 
|  | TEST_F(TaskSchedulerWorkerStackTest, PushTwice) { | 
|  | SchedulerWorkerStack stack; | 
|  | stack.Push(worker_a_.get()); | 
|  | EXPECT_DCHECK_DEATH({ stack.Push(worker_a_.get()); }); | 
|  | } | 
|  |  | 
|  | }  // namespace internal | 
|  | }  // namespace base |