// 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