// Copyright 2013 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/basictypes.h" #include "base/bind.h" #include "base/bind_helpers.h" #include "base/location.h" #include "base/logging.h" #include "base/memory/ref_counted.h" #include "base/memory/scoped_ptr.h" #include "base/message_loop/message_loop.h" #include "base/sequence_checker.h" #include "base/test/sequenced_worker_pool_owner.h" #include "base/threading/thread.h" #include "testing/gtest/include/gtest/gtest.h" // Duplicated from base/sequence_checker.h so that we can be good citizens // there and undef the macro. #if (!defined(NDEBUG) || defined(DCHECK_ALWAYS_ON)) #define ENABLE_SEQUENCE_CHECKER 1 #else #define ENABLE_SEQUENCE_CHECKER 0 #endif namespace base { namespace { const size_t kNumWorkerThreads = 3; // Simple class to exercise the basics of SequenceChecker. // DoStuff should verify that it's called on a valid sequenced thread. // SequenceCheckedObject can be destroyed on any thread (like WeakPtr). class SequenceCheckedObject { public: SequenceCheckedObject() {} ~SequenceCheckedObject() {} // Verifies that it was called on the same thread as the constructor. void DoStuff() { DCHECK(sequence_checker_.CalledOnValidSequencedThread()); } void DetachFromSequence() { sequence_checker_.DetachFromSequence(); } private: SequenceChecker sequence_checker_; DISALLOW_COPY_AND_ASSIGN(SequenceCheckedObject); }; class SequenceCheckerTest : public testing::Test { public: SequenceCheckerTest() : other_thread_("sequence_checker_test_other_thread") {} virtual ~SequenceCheckerTest() {} virtual void SetUp() OVERRIDE { other_thread_.Start(); ResetPool(); } virtual void TearDown() OVERRIDE { other_thread_.Stop(); pool()->Shutdown(); } protected: base::Thread* other_thread() { return &other_thread_; } const scoped_refptr<SequencedWorkerPool>& pool() { return pool_owner_->pool(); } void PostDoStuffToWorkerPool(SequenceCheckedObject* sequence_checked_object, const std::string& token_name) { pool()->PostNamedSequencedWorkerTask( token_name, FROM_HERE, base::Bind(&SequenceCheckedObject::DoStuff, base::Unretained(sequence_checked_object))); } void PostDoStuffToOtherThread( SequenceCheckedObject* sequence_checked_object) { other_thread()->message_loop()->PostTask( FROM_HERE, base::Bind(&SequenceCheckedObject::DoStuff, base::Unretained(sequence_checked_object))); } void PostDeleteToOtherThread( scoped_ptr<SequenceCheckedObject> sequence_checked_object) { other_thread()->message_loop()->DeleteSoon( FROM_HERE, sequence_checked_object.release()); } // Destroys the SequencedWorkerPool instance, blocking until it is fully shut // down, and creates a new instance. void ResetPool() { pool_owner_.reset(new SequencedWorkerPoolOwner(kNumWorkerThreads, "test")); } void MethodOnDifferentThreadDeathTest(); void DetachThenCallFromDifferentThreadDeathTest(); void DifferentSequenceTokensDeathTest(); void WorkerPoolAndSimpleThreadDeathTest(); void TwoDifferentWorkerPoolsDeathTest(); private: MessageLoop message_loop_; // Needed by SequencedWorkerPool to function. base::Thread other_thread_; scoped_ptr<SequencedWorkerPoolOwner> pool_owner_; }; TEST_F(SequenceCheckerTest, CallsAllowedOnSameThread) { scoped_ptr<SequenceCheckedObject> sequence_checked_object( new SequenceCheckedObject); // Verify that DoStuff doesn't assert. sequence_checked_object->DoStuff(); // Verify that the destructor doesn't assert. sequence_checked_object.reset(); } TEST_F(SequenceCheckerTest, DestructorAllowedOnDifferentThread) { scoped_ptr<SequenceCheckedObject> sequence_checked_object( new SequenceCheckedObject); // Verify the destructor doesn't assert when called on a different thread. PostDeleteToOtherThread(sequence_checked_object.Pass()); other_thread()->Stop(); } TEST_F(SequenceCheckerTest, DetachFromSequence) { scoped_ptr<SequenceCheckedObject> sequence_checked_object( new SequenceCheckedObject); // Verify that DoStuff doesn't assert when called on a different thread after // a call to DetachFromSequence. sequence_checked_object->DetachFromSequence(); PostDoStuffToOtherThread(sequence_checked_object.get()); other_thread()->Stop(); } TEST_F(SequenceCheckerTest, SameSequenceTokenValid) { scoped_ptr<SequenceCheckedObject> sequence_checked_object( new SequenceCheckedObject); sequence_checked_object->DetachFromSequence(); PostDoStuffToWorkerPool(sequence_checked_object.get(), "A"); PostDoStuffToWorkerPool(sequence_checked_object.get(), "A"); PostDoStuffToWorkerPool(sequence_checked_object.get(), "A"); PostDoStuffToWorkerPool(sequence_checked_object.get(), "A"); pool()->FlushForTesting(); PostDeleteToOtherThread(sequence_checked_object.Pass()); other_thread()->Stop(); } TEST_F(SequenceCheckerTest, DetachSequenceTokenValid) { scoped_ptr<SequenceCheckedObject> sequence_checked_object( new SequenceCheckedObject); sequence_checked_object->DetachFromSequence(); PostDoStuffToWorkerPool(sequence_checked_object.get(), "A"); PostDoStuffToWorkerPool(sequence_checked_object.get(), "A"); pool()->FlushForTesting(); sequence_checked_object->DetachFromSequence(); PostDoStuffToWorkerPool(sequence_checked_object.get(), "B"); PostDoStuffToWorkerPool(sequence_checked_object.get(), "B"); pool()->FlushForTesting(); PostDeleteToOtherThread(sequence_checked_object.Pass()); other_thread()->Stop(); } #if GTEST_HAS_DEATH_TEST || !ENABLE_SEQUENCE_CHECKER void SequenceCheckerTest::MethodOnDifferentThreadDeathTest() { scoped_ptr<SequenceCheckedObject> sequence_checked_object( new SequenceCheckedObject); // DoStuff should assert in debug builds only when called on a // different thread. PostDoStuffToOtherThread(sequence_checked_object.get()); other_thread()->Stop(); } #if ENABLE_SEQUENCE_CHECKER TEST_F(SequenceCheckerTest, MethodNotAllowedOnDifferentThreadDeathTestInDebug) { // The default style "fast" does not support multi-threaded tests. ::testing::FLAGS_gtest_death_test_style = "threadsafe"; ASSERT_DEATH({ MethodOnDifferentThreadDeathTest(); }, ""); } #else TEST_F(SequenceCheckerTest, MethodAllowedOnDifferentThreadDeathTestInRelease) { MethodOnDifferentThreadDeathTest(); } #endif // ENABLE_SEQUENCE_CHECKER void SequenceCheckerTest::DetachThenCallFromDifferentThreadDeathTest() { scoped_ptr<SequenceCheckedObject> sequence_checked_object( new SequenceCheckedObject); // DoStuff doesn't assert when called on a different thread // after a call to DetachFromSequence. sequence_checked_object->DetachFromSequence(); PostDoStuffToOtherThread(sequence_checked_object.get()); other_thread()->Stop(); // DoStuff should assert in debug builds only after moving to // another thread. sequence_checked_object->DoStuff(); } #if ENABLE_SEQUENCE_CHECKER TEST_F(SequenceCheckerTest, DetachFromSequenceDeathTestInDebug) { // The default style "fast" does not support multi-threaded tests. ::testing::FLAGS_gtest_death_test_style = "threadsafe"; ASSERT_DEATH({ DetachThenCallFromDifferentThreadDeathTest(); }, ""); } #else TEST_F(SequenceCheckerTest, DetachFromThreadDeathTestInRelease) { DetachThenCallFromDifferentThreadDeathTest(); } #endif // ENABLE_SEQUENCE_CHECKER void SequenceCheckerTest::DifferentSequenceTokensDeathTest() { scoped_ptr<SequenceCheckedObject> sequence_checked_object( new SequenceCheckedObject); sequence_checked_object->DetachFromSequence(); PostDoStuffToWorkerPool(sequence_checked_object.get(), "A"); PostDoStuffToWorkerPool(sequence_checked_object.get(), "A"); PostDoStuffToWorkerPool(sequence_checked_object.get(), "B"); PostDoStuffToWorkerPool(sequence_checked_object.get(), "B"); pool()->FlushForTesting(); PostDeleteToOtherThread(sequence_checked_object.Pass()); other_thread()->Stop(); } #if ENABLE_SEQUENCE_CHECKER TEST_F(SequenceCheckerTest, DifferentSequenceTokensDeathTestInDebug) { // The default style "fast" does not support multi-threaded tests. ::testing::FLAGS_gtest_death_test_style = "threadsafe"; ASSERT_DEATH({ DifferentSequenceTokensDeathTest(); }, ""); } #else TEST_F(SequenceCheckerTest, DifferentSequenceTokensDeathTestInRelease) { DifferentSequenceTokensDeathTest(); } #endif // ENABLE_SEQUENCE_CHECKER void SequenceCheckerTest::WorkerPoolAndSimpleThreadDeathTest() { scoped_ptr<SequenceCheckedObject> sequence_checked_object( new SequenceCheckedObject); sequence_checked_object->DetachFromSequence(); PostDoStuffToWorkerPool(sequence_checked_object.get(), "A"); PostDoStuffToWorkerPool(sequence_checked_object.get(), "A"); pool()->FlushForTesting(); PostDoStuffToOtherThread(sequence_checked_object.get()); other_thread()->Stop(); } #if ENABLE_SEQUENCE_CHECKER TEST_F(SequenceCheckerTest, WorkerPoolAndSimpleThreadDeathTestInDebug) { // The default style "fast" does not support multi-threaded tests. ::testing::FLAGS_gtest_death_test_style = "threadsafe"; ASSERT_DEATH({ WorkerPoolAndSimpleThreadDeathTest(); }, ""); } #else TEST_F(SequenceCheckerTest, WorkerPoolAndSimpleThreadDeathTestInRelease) { WorkerPoolAndSimpleThreadDeathTest(); } #endif // ENABLE_SEQUENCE_CHECKER void SequenceCheckerTest::TwoDifferentWorkerPoolsDeathTest() { scoped_ptr<SequenceCheckedObject> sequence_checked_object( new SequenceCheckedObject); sequence_checked_object->DetachFromSequence(); PostDoStuffToWorkerPool(sequence_checked_object.get(), "A"); PostDoStuffToWorkerPool(sequence_checked_object.get(), "A"); pool()->FlushForTesting(); SequencedWorkerPoolOwner second_pool_owner(kNumWorkerThreads, "test2"); second_pool_owner.pool()->PostNamedSequencedWorkerTask( "A", FROM_HERE, base::Bind(&SequenceCheckedObject::DoStuff, base::Unretained(sequence_checked_object.get()))); second_pool_owner.pool()->FlushForTesting(); second_pool_owner.pool()->Shutdown(); } #if ENABLE_SEQUENCE_CHECKER TEST_F(SequenceCheckerTest, TwoDifferentWorkerPoolsDeathTestInDebug) { // The default style "fast" does not support multi-threaded tests. ::testing::FLAGS_gtest_death_test_style = "threadsafe"; ASSERT_DEATH({ TwoDifferentWorkerPoolsDeathTest(); }, ""); } #else TEST_F(SequenceCheckerTest, TwoDifferentWorkerPoolsDeathTestInRelease) { TwoDifferentWorkerPoolsDeathTest(); } #endif // ENABLE_SEQUENCE_CHECKER #endif // GTEST_HAS_DEATH_TEST || !ENABLE_SEQUENCE_CHECKER } // namespace } // namespace base // Just in case we ever get lumped together with other compilation units. #undef ENABLE_SEQUENCE_CHECKER