// 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 #include #include "base/compiler_specific.h" #include "base/logging.h" #include "base/memory/scoped_ptr.h" #include "base/memory/scoped_vector.h" #include "net/base/prioritized_dispatcher.h" #include "net/base/request_priority.h" #include "testing/gtest/include/gtest/gtest.h" namespace net { namespace { // We rely on the priority enum values being sequential having starting at 0, // and increasing for higher priorities. COMPILE_ASSERT(MINIMUM_PRIORITY == 0u && MINIMUM_PRIORITY == IDLE && IDLE < LOWEST && LOWEST < HIGHEST && HIGHEST < NUM_PRIORITIES, priority_indexes_incompatible); class PrioritizedDispatcherTest : public testing::Test { public: typedef PrioritizedDispatcher::Priority Priority; // A job that appends |tag| to |log| when started and '.' when finished. // This is intended to confirm the execution order of a sequence of jobs added // to the dispatcher. Note that finishing order of jobs does not matter. class TestJob : public PrioritizedDispatcher::Job { public: TestJob(PrioritizedDispatcher* dispatcher, char tag, Priority priority, std::string* log) : dispatcher_(dispatcher), tag_(tag), priority_(priority), running_(false), log_(log) {} bool running() const { return running_; } const PrioritizedDispatcher::Handle handle() const { return handle_; } void Add() { CHECK(handle_.is_null()); CHECK(!running_); size_t num_queued = dispatcher_->num_queued_jobs(); size_t num_running = dispatcher_->num_running_jobs(); handle_ = dispatcher_->Add(this, priority_); if (handle_.is_null()) { EXPECT_EQ(num_queued, dispatcher_->num_queued_jobs()); EXPECT_TRUE(running_); EXPECT_EQ(num_running + 1, dispatcher_->num_running_jobs()); } else { EXPECT_FALSE(running_); EXPECT_EQ(priority_, handle_.priority()); EXPECT_EQ(tag_, reinterpret_cast(handle_.value())->tag_); EXPECT_EQ(num_running, dispatcher_->num_running_jobs()); } } void ChangePriority(Priority priority) { CHECK(!handle_.is_null()); CHECK(!running_); size_t num_queued = dispatcher_->num_queued_jobs(); size_t num_running = dispatcher_->num_running_jobs(); handle_ = dispatcher_->ChangePriority(handle_, priority); if (handle_.is_null()) { EXPECT_TRUE(running_); EXPECT_EQ(num_queued - 1, dispatcher_->num_queued_jobs()); EXPECT_EQ(num_running + 1, dispatcher_->num_running_jobs()); } else { EXPECT_FALSE(running_); EXPECT_EQ(priority, handle_.priority()); EXPECT_EQ(tag_, reinterpret_cast(handle_.value())->tag_); EXPECT_EQ(num_queued, dispatcher_->num_queued_jobs()); EXPECT_EQ(num_running, dispatcher_->num_running_jobs()); } } void Cancel() { CHECK(!handle_.is_null()); CHECK(!running_); size_t num_queued = dispatcher_->num_queued_jobs(); dispatcher_->Cancel(handle_); EXPECT_EQ(num_queued - 1, dispatcher_->num_queued_jobs()); handle_ = PrioritizedDispatcher::Handle(); } void Finish() { CHECK(running_); running_ = false; log_->append(1u, '.'); dispatcher_->OnJobFinished(); } // PriorityDispatch::Job interface virtual void Start() OVERRIDE { EXPECT_FALSE(running_); handle_ = PrioritizedDispatcher::Handle(); running_ = true; log_->append(1u, tag_); } private: PrioritizedDispatcher* dispatcher_; char tag_; Priority priority_; PrioritizedDispatcher::Handle handle_; bool running_; std::string* log_; }; protected: void Prepare(const PrioritizedDispatcher::Limits& limits) { dispatcher_.reset(new PrioritizedDispatcher(limits)); } TestJob* AddJob(char data, Priority priority) { TestJob* job = new TestJob(dispatcher_.get(), data, priority, &log_); jobs_.push_back(job); job->Add(); return job; } void Expect(std::string log) { EXPECT_EQ(0u, dispatcher_->num_queued_jobs()); EXPECT_EQ(0u, dispatcher_->num_running_jobs()); EXPECT_EQ(log, log_); log_.clear(); } std::string log_; scoped_ptr dispatcher_; ScopedVector jobs_; }; TEST_F(PrioritizedDispatcherTest, AddAFIFO) { // Allow only one running job. PrioritizedDispatcher::Limits limits(NUM_PRIORITIES, 1); Prepare(limits); TestJob* job_a = AddJob('a', IDLE); TestJob* job_b = AddJob('b', IDLE); TestJob* job_c = AddJob('c', IDLE); TestJob* job_d = AddJob('d', IDLE); ASSERT_TRUE(job_a->running()); job_a->Finish(); ASSERT_TRUE(job_b->running()); job_b->Finish(); ASSERT_TRUE(job_c->running()); job_c->Finish(); ASSERT_TRUE(job_d->running()); job_d->Finish(); Expect("a.b.c.d."); } TEST_F(PrioritizedDispatcherTest, AddPriority) { PrioritizedDispatcher::Limits limits(NUM_PRIORITIES, 1); Prepare(limits); TestJob* job_a = AddJob('a', IDLE); TestJob* job_b = AddJob('b', MEDIUM); TestJob* job_c = AddJob('c', HIGHEST); TestJob* job_d = AddJob('d', HIGHEST); TestJob* job_e = AddJob('e', MEDIUM); ASSERT_TRUE(job_a->running()); job_a->Finish(); ASSERT_TRUE(job_c->running()); job_c->Finish(); ASSERT_TRUE(job_d->running()); job_d->Finish(); ASSERT_TRUE(job_b->running()); job_b->Finish(); ASSERT_TRUE(job_e->running()); job_e->Finish(); Expect("a.c.d.b.e."); } TEST_F(PrioritizedDispatcherTest, EnforceLimits) { // Reserve 2 for HIGHEST and 1 for LOW or higher. // This leaves 2 for LOWEST or lower. PrioritizedDispatcher::Limits limits(NUM_PRIORITIES, 5); limits.reserved_slots[HIGHEST] = 2; limits.reserved_slots[LOW] = 1; Prepare(limits); TestJob* job_a = AddJob('a', IDLE); // Uses unreserved slot. TestJob* job_b = AddJob('b', IDLE); // Uses unreserved slot. TestJob* job_c = AddJob('c', LOWEST); // Must wait. TestJob* job_d = AddJob('d', LOW); // Uses reserved slot. TestJob* job_e = AddJob('e', MEDIUM); // Must wait. TestJob* job_f = AddJob('f', HIGHEST); // Uses reserved slot. TestJob* job_g = AddJob('g', HIGHEST); // Uses reserved slot. TestJob* job_h = AddJob('h', HIGHEST); // Must wait. EXPECT_EQ(5u, dispatcher_->num_running_jobs()); EXPECT_EQ(3u, dispatcher_->num_queued_jobs()); ASSERT_TRUE(job_a->running()); ASSERT_TRUE(job_b->running()); ASSERT_TRUE(job_d->running()); ASSERT_TRUE(job_f->running()); ASSERT_TRUE(job_g->running()); // a, b, d, f, g are running. Finish them in any order. job_b->Finish(); // Releases h. job_f->Finish(); job_a->Finish(); job_g->Finish(); // Releases e. job_d->Finish(); ASSERT_TRUE(job_e->running()); ASSERT_TRUE(job_h->running()); // h, e are running. job_e->Finish(); // Releases c. ASSERT_TRUE(job_c->running()); job_c->Finish(); job_h->Finish(); Expect("abdfg.h...e..c.."); } TEST_F(PrioritizedDispatcherTest, ChangePriority) { PrioritizedDispatcher::Limits limits(NUM_PRIORITIES, 1); Prepare(limits); TestJob* job_a = AddJob('a', IDLE); TestJob* job_b = AddJob('b', MEDIUM); TestJob* job_c = AddJob('c', HIGHEST); TestJob* job_d = AddJob('d', HIGHEST); ASSERT_FALSE(job_b->running()); ASSERT_FALSE(job_c->running()); job_b->ChangePriority(HIGHEST); job_c->ChangePriority(MEDIUM); ASSERT_TRUE(job_a->running()); job_a->Finish(); ASSERT_TRUE(job_d->running()); job_d->Finish(); ASSERT_TRUE(job_b->running()); job_b->Finish(); ASSERT_TRUE(job_c->running()); job_c->Finish(); Expect("a.d.b.c."); } TEST_F(PrioritizedDispatcherTest, Cancel) { PrioritizedDispatcher::Limits limits(NUM_PRIORITIES, 1); Prepare(limits); TestJob* job_a = AddJob('a', IDLE); TestJob* job_b = AddJob('b', IDLE); TestJob* job_c = AddJob('c', IDLE); TestJob* job_d = AddJob('d', IDLE); TestJob* job_e = AddJob('e', IDLE); ASSERT_FALSE(job_b->running()); ASSERT_FALSE(job_d->running()); job_b->Cancel(); job_d->Cancel(); ASSERT_TRUE(job_a->running()); job_a->Finish(); ASSERT_TRUE(job_c->running()); job_c->Finish(); ASSERT_TRUE(job_e->running()); job_e->Finish(); Expect("a.c.e."); } TEST_F(PrioritizedDispatcherTest, Evict) { PrioritizedDispatcher::Limits limits(NUM_PRIORITIES, 1); Prepare(limits); TestJob* job_a = AddJob('a', IDLE); TestJob* job_b = AddJob('b', LOW); TestJob* job_c = AddJob('c', HIGHEST); TestJob* job_d = AddJob('d', LOW); TestJob* job_e = AddJob('e', HIGHEST); EXPECT_EQ(job_b, dispatcher_->EvictOldestLowest()); EXPECT_EQ(job_d, dispatcher_->EvictOldestLowest()); ASSERT_TRUE(job_a->running()); job_a->Finish(); ASSERT_TRUE(job_c->running()); job_c->Finish(); ASSERT_TRUE(job_e->running()); job_e->Finish(); Expect("a.c.e."); } TEST_F(PrioritizedDispatcherTest, EvictFromEmpty) { PrioritizedDispatcher::Limits limits(NUM_PRIORITIES, 1); Prepare(limits); EXPECT_TRUE(dispatcher_->EvictOldestLowest() == NULL); } #if GTEST_HAS_DEATH_TEST && !defined(NDEBUG) TEST_F(PrioritizedDispatcherTest, CancelNull) { PrioritizedDispatcher::Limits limits(NUM_PRIORITIES, 1); Prepare(limits); EXPECT_DEBUG_DEATH(dispatcher_->Cancel(PrioritizedDispatcher::Handle()), ""); } TEST_F(PrioritizedDispatcherTest, CancelMissing) { PrioritizedDispatcher::Limits limits(NUM_PRIORITIES, 1); Prepare(limits); AddJob('a', IDLE); TestJob* job_b = AddJob('b', IDLE); PrioritizedDispatcher::Handle handle = job_b->handle(); ASSERT_FALSE(handle.is_null()); dispatcher_->Cancel(handle); EXPECT_DEBUG_DEATH(dispatcher_->Cancel(handle), ""); } // TODO(szym): Fix the PriorityQueue::Pointer check to die here. // http://crbug.com/130846 TEST_F(PrioritizedDispatcherTest, DISABLED_CancelIncompatible) { PrioritizedDispatcher::Limits limits(NUM_PRIORITIES, 1); Prepare(limits); AddJob('a', IDLE); TestJob* job_b = AddJob('b', IDLE); PrioritizedDispatcher::Handle handle = job_b->handle(); ASSERT_FALSE(handle.is_null()); // New dispatcher. Prepare(limits); AddJob('a', IDLE); AddJob('b', IDLE); EXPECT_DEBUG_DEATH(dispatcher_->Cancel(handle), ""); } #endif // GTEST_HAS_DEATH_TEST && !defined(NDEBUG) } // namespace } // namespace net