summaryrefslogtreecommitdiffstats
path: root/base
diff options
context:
space:
mode:
authorfdoray <fdoray@chromium.org>2016-03-18 15:44:55 -0700
committerCommit bot <commit-bot@chromium.org>2016-03-18 22:47:56 +0000
commit6477fbded94522e26875bbf405710060d01ea971 (patch)
tree944811ea09c89e188bfb6c05fd45f1384156971e /base
parent85d385a2ec9f6d365fee5a14fd2a5ea564ffb1a7 (diff)
downloadchromium_src-6477fbded94522e26875bbf405710060d01ea971.zip
chromium_src-6477fbded94522e26875bbf405710060d01ea971.tar.gz
chromium_src-6477fbded94522e26875bbf405710060d01ea971.tar.bz2
TaskScheduler [4/9] Priority Queue
This change is a subset of https://codereview.chromium.org/1698183005/ A PriorityQueue holds Sequences of Tasks. It supports Push, Pop and Peek operations through a Transaction object. A SequenceSortKey must be provided to push a Sequence into a PriorityQueue. Sequences are sorted according to their SequenceSortKey. The SequenceSortKey of a Sequence never changes while it is in the PriorityQueue (even if Tasks are pushed/popped from the Sequence). BUG=553459 Review URL: https://codereview.chromium.org/1709713002 Cr-Commit-Position: refs/heads/master@{#382115}
Diffstat (limited to 'base')
-rw-r--r--base/BUILD.gn4
-rw-r--r--base/base.gyp2
-rw-r--r--base/base.gypi2
-rw-r--r--base/task_scheduler/priority_queue.cc89
-rw-r--r--base/task_scheduler/priority_queue.h139
-rw-r--r--base/task_scheduler/priority_queue_unittest.cc245
-rw-r--r--base/task_scheduler/scheduler_lock_unittest.cc8
-rw-r--r--base/task_scheduler/test_utils.h19
8 files changed, 501 insertions, 7 deletions
diff --git a/base/BUILD.gn b/base/BUILD.gn
index 1cbc063..0ac9233 100644
--- a/base/BUILD.gn
+++ b/base/BUILD.gn
@@ -766,6 +766,8 @@ component("base") {
"task_runner.cc",
"task_runner.h",
"task_runner_util.h",
+ "task_scheduler/priority_queue.cc",
+ "task_scheduler/priority_queue.h",
"task_scheduler/scheduler_lock.h",
"task_scheduler/scheduler_lock_impl.cc",
"task_scheduler/scheduler_lock_impl.h",
@@ -1815,9 +1817,11 @@ test("base_unittests") {
"system_monitor/system_monitor_unittest.cc",
"task/cancelable_task_tracker_unittest.cc",
"task_runner_util_unittest.cc",
+ "task_scheduler/priority_queue_unittest.cc",
"task_scheduler/scheduler_lock_unittest.cc",
"task_scheduler/sequence_sort_key_unittest.cc",
"task_scheduler/sequence_unittest.cc",
+ "task_scheduler/test_utils.h",
"template_util_unittest.cc",
"test/histogram_tester_unittest.cc",
"test/icu_test_util.cc",
diff --git a/base/base.gyp b/base/base.gyp
index d7f3519..2591803 100644
--- a/base/base.gyp
+++ b/base/base.gyp
@@ -549,9 +549,11 @@
'system_monitor/system_monitor_unittest.cc',
'task/cancelable_task_tracker_unittest.cc',
'task_runner_util_unittest.cc',
+ 'task_scheduler/priority_queue_unittest.cc',
'task_scheduler/scheduler_lock_unittest.cc',
'task_scheduler/sequence_sort_key_unittest.cc',
'task_scheduler/sequence_unittest.cc',
+ 'task_scheduler/test_utils.h',
'template_util_unittest.cc',
'test/histogram_tester_unittest.cc',
'test/test_pending_task_unittest.cc',
diff --git a/base/base.gypi b/base/base.gypi
index 5d7693f..dac46e2 100644
--- a/base/base.gypi
+++ b/base/base.gypi
@@ -636,6 +636,8 @@
'task_runner.cc',
'task_runner.h',
'task_runner_util.h',
+ 'task_scheduler/priority_queue.cc',
+ 'task_scheduler/priority_queue.h',
'task_scheduler/scheduler_lock.h',
'task_scheduler/scheduler_lock_impl.cc',
'task_scheduler/scheduler_lock_impl.h',
diff --git a/base/task_scheduler/priority_queue.cc b/base/task_scheduler/priority_queue.cc
new file mode 100644
index 0000000..8eb4b86
--- /dev/null
+++ b/base/task_scheduler/priority_queue.cc
@@ -0,0 +1,89 @@
+// 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/priority_queue.h"
+
+#include <utility>
+
+#include "base/logging.h"
+
+namespace base {
+namespace internal {
+
+PriorityQueue::SequenceAndSortKey::SequenceAndSortKey()
+ : sort_key(TaskPriority::LOWEST, TimeTicks()) {}
+
+PriorityQueue::SequenceAndSortKey::SequenceAndSortKey(
+ scoped_refptr<Sequence> sequence,
+ const SequenceSortKey& sort_key)
+ : sequence(std::move(sequence)), sort_key(sort_key) {}
+
+PriorityQueue::SequenceAndSortKey::~SequenceAndSortKey() = default;
+
+PriorityQueue::Transaction::Transaction(PriorityQueue* outer_queue)
+ : auto_lock_(new AutoSchedulerLock(outer_queue->container_lock_)),
+ outer_queue_(outer_queue) {
+ DCHECK(CalledOnValidThread());
+}
+
+PriorityQueue::Transaction::~Transaction() {
+ DCHECK(CalledOnValidThread());
+
+ // Run the sequence insertion callback once for each Sequence that was
+ // inserted in the PriorityQueue during the lifetime of this Transaction.
+ // Perform this outside the scope of PriorityQueue's lock to avoid imposing an
+ // unnecessary lock dependency on |sequence_inserted_callback_|'s destination.
+ auto_lock_.reset();
+ for (size_t i = 0; i < num_pushed_sequences_; ++i)
+ outer_queue_->sequence_inserted_callback_.Run();
+}
+
+void PriorityQueue::Transaction::Push(
+ scoped_ptr<SequenceAndSortKey> sequence_and_sort_key) {
+ DCHECK(CalledOnValidThread());
+ DCHECK(!sequence_and_sort_key->is_null());
+
+ outer_queue_->container_.push(std::move(sequence_and_sort_key));
+ ++num_pushed_sequences_;
+}
+
+const PriorityQueue::SequenceAndSortKey& PriorityQueue::Transaction::Peek()
+ const {
+ DCHECK(CalledOnValidThread());
+
+ // TODO(fdoray): Add an IsEmpty() method to Transaction and require Peek() to
+ // be called on a non-empty PriorityQueue only.
+ if (outer_queue_->container_.empty())
+ return outer_queue_->empty_sequence_and_sort_key_;
+
+ return *outer_queue_->container_.top();
+}
+
+void PriorityQueue::Transaction::Pop() {
+ DCHECK(CalledOnValidThread());
+ DCHECK(!outer_queue_->container_.empty());
+ outer_queue_->container_.pop();
+}
+
+PriorityQueue::PriorityQueue(const Closure& sequence_inserted_callback)
+ : sequence_inserted_callback_(sequence_inserted_callback) {
+ DCHECK(!sequence_inserted_callback_.is_null());
+}
+
+PriorityQueue::PriorityQueue(const Closure& sequence_inserted_callback,
+ const PriorityQueue* predecessor_priority_queue)
+ : container_lock_(&predecessor_priority_queue->container_lock_),
+ sequence_inserted_callback_(sequence_inserted_callback) {
+ DCHECK(!sequence_inserted_callback_.is_null());
+ DCHECK(predecessor_priority_queue);
+}
+
+PriorityQueue::~PriorityQueue() = default;
+
+scoped_ptr<PriorityQueue::Transaction> PriorityQueue::BeginTransaction() {
+ return make_scoped_ptr(new Transaction(this));
+}
+
+} // namespace internal
+} // namespace base
diff --git a/base/task_scheduler/priority_queue.h b/base/task_scheduler/priority_queue.h
new file mode 100644
index 0000000..e37dae9
--- /dev/null
+++ b/base/task_scheduler/priority_queue.h
@@ -0,0 +1,139 @@
+// 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.
+
+#ifndef BASE_TASK_SCHEDULER_PRIORITY_QUEUE_H_
+#define BASE_TASK_SCHEDULER_PRIORITY_QUEUE_H_
+
+#include <queue>
+#include <vector>
+
+#include "base/base_export.h"
+#include "base/callback.h"
+#include "base/macros.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/task_scheduler/scheduler_lock.h"
+#include "base/task_scheduler/sequence.h"
+#include "base/task_scheduler/sequence_sort_key.h"
+#include "base/threading/non_thread_safe.h"
+
+namespace base {
+namespace internal {
+
+// A PriorityQueue holds Sequences of Tasks. This class is thread-safe.
+class BASE_EXPORT PriorityQueue {
+ public:
+ // An immutable struct combining a Sequence and the sort key that determines
+ // its position in a PriorityQueue.
+ struct BASE_EXPORT SequenceAndSortKey {
+ // Constructs a null SequenceAndSortKey.
+ SequenceAndSortKey();
+
+ // Constructs a SequenceAndSortKey with the given |sequence| and |sort_key|.
+ SequenceAndSortKey(scoped_refptr<Sequence> sequence,
+ const SequenceSortKey& sort_key);
+
+ ~SequenceAndSortKey();
+
+ // Returns true if this is a null SequenceAndSortKey.
+ bool is_null() const { return !sequence; }
+
+ const scoped_refptr<Sequence> sequence;
+ const SequenceSortKey sort_key;
+ };
+
+ // A Transaction can perform multiple operations atomically on a
+ // PriorityQueue. While a Transaction is alive, it is guaranteed that nothing
+ // else will access the PriorityQueue.
+ //
+ // A WorkerThread needs to be able to Peek sequences from both its
+ // PriorityQueues (single-threaded and shared) and then Pop the sequence with
+ // the highest priority. If the Peek and the Pop are done through the same
+ // Transaction, it is guaranteed that the PriorityQueue hasn't changed between
+ // the 2 operations.
+ class BASE_EXPORT Transaction : public NonThreadSafe {
+ public:
+ ~Transaction();
+
+ // Inserts |sequence_and_sort_key| in the PriorityQueue.
+ void Push(scoped_ptr<SequenceAndSortKey> sequence_and_sort_key);
+
+ // Returns the SequenceAndSortKey with the highest priority or a null
+ // SequenceAndSortKey if the PriorityQueue is empty. The reference becomes
+ // invalid the next time that a Sequence is popped from the PriorityQueue.
+ const SequenceAndSortKey& Peek() const;
+
+ // Removes the SequenceAndSortKey with the highest priority from the
+ // PriorityQueue. Cannot be called on an empty PriorityQueue.
+ void Pop();
+
+ private:
+ friend class PriorityQueue;
+
+ explicit Transaction(PriorityQueue* outer_queue);
+
+ // Holds the lock of |outer_queue_| for most of the lifetime of this
+ // Transaction. Using a scoped_ptr allows the destructor to release the lock
+ // before performing internal operations which have to be done outside of
+ // its scope.
+ scoped_ptr<AutoSchedulerLock> auto_lock_;
+
+ PriorityQueue* const outer_queue_;
+
+ // Number of times that Push() has been called on this Transaction.
+ size_t num_pushed_sequences_ = 0;
+
+ DISALLOW_COPY_AND_ASSIGN(Transaction);
+ };
+
+ // |sequence_inserted_callback| is a non-null callback invoked when the
+ // Transaction is done for each Push that was performed with the Transaction.
+ explicit PriorityQueue(const Closure& sequence_inserted_callback);
+
+ // |sequence_inserted_callback| is a non-null callback invoked when the
+ // Transaction is done for each Push that was performed with the Transaction.
+ // |predecessor_priority_queue| is a PriorityQueue for which a thread is
+ // allowed to have an active Transaction when it creates a Transaction for
+ // this PriorityQueue.
+ PriorityQueue(const Closure& sequence_inserted_callback,
+ const PriorityQueue* predecessor_priority_queue);
+
+ ~PriorityQueue();
+
+ // Begins a Transaction. This method cannot be called on a thread which has an
+ // active Transaction unless the last Transaction created on the thread was
+ // for the allowed predecessor specified in the constructor of this
+ // PriorityQueue.
+ scoped_ptr<Transaction> BeginTransaction();
+
+ private:
+ struct SequenceAndSortKeyComparator {
+ bool operator()(const scoped_ptr<SequenceAndSortKey>& left,
+ const scoped_ptr<SequenceAndSortKey>& right) const {
+ return left->sort_key < right->sort_key;
+ }
+ };
+ using ContainerType =
+ std::priority_queue<scoped_ptr<SequenceAndSortKey>,
+ std::vector<scoped_ptr<SequenceAndSortKey>>,
+ SequenceAndSortKeyComparator>;
+
+ // Synchronizes access to |container_|.
+ SchedulerLock container_lock_;
+
+ ContainerType container_;
+
+ const Closure sequence_inserted_callback_;
+
+ // A null SequenceAndSortKey returned by Peek() when the PriorityQueue is
+ // empty.
+ const SequenceAndSortKey empty_sequence_and_sort_key_;
+
+ DISALLOW_COPY_AND_ASSIGN(PriorityQueue);
+};
+
+} // namespace internal
+} // namespace base
+
+#endif // BASE_TASK_SCHEDULER_PRIORITY_QUEUE_H_
diff --git a/base/task_scheduler/priority_queue_unittest.cc b/base/task_scheduler/priority_queue_unittest.cc
new file mode 100644
index 0000000..aef95f1
--- /dev/null
+++ b/base/task_scheduler/priority_queue_unittest.cc
@@ -0,0 +1,245 @@
+// 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/priority_queue.h"
+
+#include "base/bind.h"
+#include "base/bind_helpers.h"
+#include "base/macros.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/synchronization/waitable_event.h"
+#include "base/task_scheduler/sequence.h"
+#include "base/task_scheduler/task.h"
+#include "base/task_scheduler/task_traits.h"
+#include "base/task_scheduler/test_utils.h"
+#include "base/threading/platform_thread.h"
+#include "base/threading/simple_thread.h"
+#include "base/time/time.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace base {
+namespace internal {
+
+namespace {
+
+class PriorityQueueCallbackMock {
+ public:
+ PriorityQueueCallbackMock() = default;
+ MOCK_METHOD0(SequenceInsertedInPriorityQueue, void());
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(PriorityQueueCallbackMock);
+};
+
+class ThreadBeginningTransaction : public SimpleThread {
+ public:
+ explicit ThreadBeginningTransaction(PriorityQueue* priority_queue)
+ : SimpleThread("ThreadBeginningTransaction"),
+ priority_queue_(priority_queue),
+ transaction_began_(true, false) {}
+
+ // SimpleThread:
+ void Run() override {
+ scoped_ptr<PriorityQueue::Transaction> transaction =
+ priority_queue_->BeginTransaction();
+ transaction_began_.Signal();
+ }
+
+ void ExpectTransactionDoesNotBegin() {
+ // After a few milliseconds, the call to BeginTransaction() should not have
+ // returned.
+ EXPECT_FALSE(
+ transaction_began_.TimedWait(TimeDelta::FromMilliseconds(250)));
+ }
+
+ private:
+ PriorityQueue* const priority_queue_;
+ WaitableEvent transaction_began_;
+
+ DISALLOW_COPY_AND_ASSIGN(ThreadBeginningTransaction);
+};
+
+void ExpectSequenceAndSortKeyEq(
+ const PriorityQueue::SequenceAndSortKey& expected,
+ const PriorityQueue::SequenceAndSortKey& actual) {
+ EXPECT_EQ(expected.sequence, actual.sequence);
+ EXPECT_EQ(expected.sort_key.priority, actual.sort_key.priority);
+ EXPECT_EQ(expected.sort_key.next_task_sequenced_time,
+ actual.sort_key.next_task_sequenced_time);
+}
+
+#define EXPECT_SEQUENCE_AND_SORT_KEY_EQ(expected, actual) \
+ do { \
+ SCOPED_TRACE(""); \
+ ExpectSequenceAndSortKeyEq(expected, actual); \
+ } while (false)
+
+} // namespace
+
+TEST(TaskSchedulerPriorityQueueTest, PushPopPeek) {
+ // Create test sequences.
+ scoped_refptr<Sequence> sequence_a(new Sequence);
+ sequence_a->PushTask(make_scoped_ptr(
+ new Task(FROM_HERE, Closure(),
+ TaskTraits().WithPriority(TaskPriority::USER_VISIBLE))));
+ SequenceSortKey sort_key_a = sequence_a->GetSortKey();
+
+ scoped_refptr<Sequence> sequence_b(new Sequence);
+ sequence_b->PushTask(make_scoped_ptr(
+ new Task(FROM_HERE, Closure(),
+ TaskTraits().WithPriority(TaskPriority::USER_BLOCKING))));
+ SequenceSortKey sort_key_b = sequence_b->GetSortKey();
+
+ scoped_refptr<Sequence> sequence_c(new Sequence);
+ sequence_c->PushTask(make_scoped_ptr(
+ new Task(FROM_HERE, Closure(),
+ TaskTraits().WithPriority(TaskPriority::USER_BLOCKING))));
+ SequenceSortKey sort_key_c = sequence_c->GetSortKey();
+
+ scoped_refptr<Sequence> sequence_d(new Sequence);
+ sequence_d->PushTask(make_scoped_ptr(
+ new Task(FROM_HERE, Closure(),
+ TaskTraits().WithPriority(TaskPriority::BACKGROUND))));
+ SequenceSortKey sort_key_d = sequence_d->GetSortKey();
+
+ // Create a PriorityQueue and a Transaction.
+ testing::StrictMock<PriorityQueueCallbackMock> mock;
+ PriorityQueue pq(
+ Bind(&PriorityQueueCallbackMock::SequenceInsertedInPriorityQueue,
+ Unretained(&mock)));
+ scoped_ptr<PriorityQueue::Transaction> transaction(pq.BeginTransaction());
+ EXPECT_SEQUENCE_AND_SORT_KEY_EQ(PriorityQueue::SequenceAndSortKey(),
+ transaction->Peek());
+
+ // Push |sequence_a| in the PriorityQueue. It becomes the sequence with the
+ // highest priority.
+ transaction->Push(make_scoped_ptr(
+ new PriorityQueue::SequenceAndSortKey(sequence_a, sort_key_a)));
+ EXPECT_SEQUENCE_AND_SORT_KEY_EQ(
+ PriorityQueue::SequenceAndSortKey(sequence_a, sort_key_a),
+ transaction->Peek());
+
+ // Push |sequence_b| in the PriorityQueue. It becomes the sequence with the
+ // highest priority.
+ transaction->Push(make_scoped_ptr(
+ new PriorityQueue::SequenceAndSortKey(sequence_b, sort_key_b)));
+ EXPECT_SEQUENCE_AND_SORT_KEY_EQ(
+ PriorityQueue::SequenceAndSortKey(sequence_b, sort_key_b),
+ transaction->Peek());
+
+ // Push |sequence_c| in the PriorityQueue. |sequence_b| is still the sequence
+ // with the highest priority.
+ transaction->Push(make_scoped_ptr(
+ new PriorityQueue::SequenceAndSortKey(sequence_c, sort_key_c)));
+ EXPECT_SEQUENCE_AND_SORT_KEY_EQ(
+ PriorityQueue::SequenceAndSortKey(sequence_b, sort_key_b),
+ transaction->Peek());
+
+ // Push |sequence_d| in the PriorityQueue. |sequence_b| is still the sequence
+ // with the highest priority.
+ transaction->Push(make_scoped_ptr(
+ new PriorityQueue::SequenceAndSortKey(sequence_d, sort_key_d)));
+ EXPECT_SEQUENCE_AND_SORT_KEY_EQ(
+ PriorityQueue::SequenceAndSortKey(sequence_b, sort_key_b),
+ transaction->Peek());
+
+ // Pop |sequence_b| from the PriorityQueue. |sequence_c| becomes the sequence
+ // with the highest priority.
+ transaction->Pop();
+ EXPECT_SEQUENCE_AND_SORT_KEY_EQ(
+ PriorityQueue::SequenceAndSortKey(sequence_c, sort_key_c),
+ transaction->Peek());
+
+ // Pop |sequence_c| from the PriorityQueue. |sequence_a| becomes the sequence
+ // with the highest priority.
+ transaction->Pop();
+ EXPECT_SEQUENCE_AND_SORT_KEY_EQ(
+ PriorityQueue::SequenceAndSortKey(sequence_a, sort_key_a),
+ transaction->Peek());
+
+ // Pop |sequence_a| from the PriorityQueue. |sequence_d| becomes the sequence
+ // with the highest priority.
+ transaction->Pop();
+ EXPECT_SEQUENCE_AND_SORT_KEY_EQ(
+ PriorityQueue::SequenceAndSortKey(sequence_d, sort_key_d),
+ transaction->Peek());
+
+ // Pop |sequence_d| from the PriorityQueue. It is now empty.
+ transaction->Pop();
+ EXPECT_SEQUENCE_AND_SORT_KEY_EQ(PriorityQueue::SequenceAndSortKey(),
+ transaction->Peek());
+
+ // Expect 4 calls to mock.SequenceInsertedInPriorityQueue() when the
+ // Transaction is destroyed.
+ EXPECT_CALL(mock, SequenceInsertedInPriorityQueue()).Times(4);
+ transaction.reset();
+}
+
+// Check that creating Transactions on the same thread for 2 unrelated
+// PriorityQueues causes a crash.
+TEST(TaskSchedulerPriorityQueueTest, IllegalTwoTransactionsSameThread) {
+ PriorityQueue pq_a(Bind(&DoNothing));
+ PriorityQueue pq_b(Bind(&DoNothing));
+
+ EXPECT_DCHECK_DEATH(
+ {
+ scoped_ptr<PriorityQueue::Transaction> transaction_a =
+ pq_a.BeginTransaction();
+ scoped_ptr<PriorityQueue::Transaction> transaction_b =
+ pq_b.BeginTransaction();
+ },
+ "");
+}
+
+// Check that there is no crash when Transactions are created on the same thread
+// for 2 PriorityQueues which have a predecessor relationship.
+TEST(TaskSchedulerPriorityQueueTest, LegalTwoTransactionsSameThread) {
+ PriorityQueue pq_a(Bind(&DoNothing));
+ PriorityQueue pq_b(Bind(&DoNothing), &pq_a);
+
+ // This shouldn't crash.
+ scoped_ptr<PriorityQueue::Transaction> transaction_a =
+ pq_a.BeginTransaction();
+ scoped_ptr<PriorityQueue::Transaction> transaction_b =
+ pq_b.BeginTransaction();
+}
+
+// Check that it is possible to begin multiple Transactions for the same
+// PriorityQueue on different threads. The call to BeginTransaction() on the
+// second thread should block until the Transaction has ended on the first
+// thread.
+TEST(TaskSchedulerPriorityQueueTest, TwoTransactionsTwoThreads) {
+ PriorityQueue pq(Bind(&DoNothing));
+
+ // Call BeginTransaction() on this thread and keep the Transaction alive.
+ scoped_ptr<PriorityQueue::Transaction> transaction = pq.BeginTransaction();
+
+ // Call BeginTransaction() on another thread.
+ ThreadBeginningTransaction thread_beginning_transaction(&pq);
+ thread_beginning_transaction.Start();
+
+ // After a few milliseconds, the call to BeginTransaction() on the other
+ // thread should not have returned.
+ thread_beginning_transaction.ExpectTransactionDoesNotBegin();
+
+ // End the Transaction on the current thread.
+ transaction.reset();
+
+ // The other thread should exit after its call to BeginTransaction() returns.
+ thread_beginning_transaction.Join();
+}
+
+TEST(TaskSchedulerPriorityQueueTest, SequenceAndSortKeyIsNull) {
+ EXPECT_TRUE(PriorityQueue::SequenceAndSortKey().is_null());
+
+ const PriorityQueue::SequenceAndSortKey non_null_sequence_andsort_key(
+ make_scoped_refptr(new Sequence),
+ SequenceSortKey(TaskPriority::USER_VISIBLE, TimeTicks()));
+ EXPECT_FALSE(non_null_sequence_andsort_key.is_null());
+}
+
+} // namespace internal
+} // namespace base
diff --git a/base/task_scheduler/scheduler_lock_unittest.cc b/base/task_scheduler/scheduler_lock_unittest.cc
index 48b8b08..6267559 100644
--- a/base/task_scheduler/scheduler_lock_unittest.cc
+++ b/base/task_scheduler/scheduler_lock_unittest.cc
@@ -10,6 +10,7 @@
#include "base/macros.h"
#include "base/rand_util.h"
#include "base/synchronization/waitable_event.h"
+#include "base/task_scheduler/test_utils.h"
#include "base/threading/platform_thread.h"
#include "base/threading/simple_thread.h"
#include "testing/gtest/include/gtest/gtest.h"
@@ -18,13 +19,6 @@ namespace base {
namespace internal {
namespace {
-// Death tests misbehave on Android.
-#if DCHECK_IS_ON() && defined(GTEST_HAS_DEATH_TEST) && !defined(OS_ANDROID)
-#define EXPECT_DCHECK_DEATH(statement, regex) EXPECT_DEATH(statement, regex)
-#else
-#define EXPECT_DCHECK_DEATH(statement, regex)
-#endif
-
// Adapted from base::Lock's BasicLockTestThread to make sure
// Acquire()/Release() don't crash.
class BasicLockTestThread : public SimpleThread {
diff --git a/base/task_scheduler/test_utils.h b/base/task_scheduler/test_utils.h
new file mode 100644
index 0000000..bafd09a
--- /dev/null
+++ b/base/task_scheduler/test_utils.h
@@ -0,0 +1,19 @@
+// 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.
+
+#ifndef BASE_TASK_SCHEDULER_TEST_UTILS_H_
+#define BASE_TASK_SCHEDULER_TEST_UTILS_H_
+
+#include "base/logging.h"
+#include "build/build_config.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+// Death tests misbehave on Android.
+#if DCHECK_IS_ON() && defined(GTEST_HAS_DEATH_TEST) && !defined(OS_ANDROID)
+#define EXPECT_DCHECK_DEATH(statement, regex) EXPECT_DEATH(statement, regex)
+#else
+#define EXPECT_DCHECK_DEATH(statement, regex)
+#endif
+
+#endif // BASE_TASK_SCHEDULER_TEST_UTILS_H_