// Copyright 2018 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/threading/sequence_bound.h" #include "base/run_loop.h" #include "base/test/scoped_task_environment.h" #include "base/threading/thread_task_runner_handle.h" #include "testing/gtest/include/gtest/gtest.h" namespace base { class SequenceBoundTest : public ::testing::Test { public: // Helpful values that our test classes use. enum Value { kInitialValue = 0, kDifferentValue = 1, // Values used by the Derived class. kDerivedCtorValue = 111, kDerivedDtorValue = 222, // Values used by the Other class. kOtherCtorValue = 333, kOtherDtorValue = 444, }; void SetUp() override { task_runner_ = base::ThreadTaskRunnerHandle::Get(); } void TearDown() override { scoped_task_environment_.RunUntilIdle(); } // Do-nothing base class, just so we can test assignment of derived classes. // It introduces a virtual destructor, so that casting derived classes to // Base should still use the appropriate (virtual) destructor. class Base { public: virtual ~Base() {} }; // Handy class to set an int ptr to different values, to verify that things // are being run properly. class Derived : public Base { public: Derived(Value* ptr) : ptr_(ptr) { *ptr_ = kDerivedCtorValue; } ~Derived() override { *ptr_ = kDerivedDtorValue; } void SetValue(Value value) { *ptr_ = value; } Value* ptr_; }; // Another base class, which sets ints to different values. class Other { public: Other(Value* ptr) : ptr_(ptr) { *ptr = kOtherCtorValue; }; virtual ~Other() { *ptr_ = kOtherDtorValue; } void SetValue(Value value) { *ptr_ = value; } Value* ptr_; }; class MultiplyDerived : public Other, public Derived { public: MultiplyDerived(Value* ptr1, Value* ptr2) : Other(ptr1), Derived(ptr2) {} }; struct VirtuallyDerived : public virtual Base {}; base::test::ScopedTaskEnvironment scoped_task_environment_; scoped_refptr task_runner_; Value value = kInitialValue; }; TEST_F(SequenceBoundTest, ConstructThenPostThenReset) { auto derived = SequenceBound(task_runner_, &value); EXPECT_FALSE(derived.is_null()); // Nothing should happen until we run the message loop. EXPECT_EQ(value, kInitialValue); base::RunLoop().RunUntilIdle(); EXPECT_EQ(value, kDerivedCtorValue); // Post now that the object has been constructed. derived.Post(FROM_HERE, &Derived::SetValue, kDifferentValue); EXPECT_EQ(value, kDerivedCtorValue); base::RunLoop().RunUntilIdle(); EXPECT_EQ(value, kDifferentValue); // Reset it, and make sure that destruction is posted. The owner should // report that it is null immediately. derived.Reset(); EXPECT_TRUE(derived.is_null()); EXPECT_EQ(value, kDifferentValue); base::RunLoop().RunUntilIdle(); EXPECT_EQ(value, kDerivedDtorValue); } TEST_F(SequenceBoundTest, PostBeforeConstruction) { // Construct an object and post a message to it, before construction has been // run on |task_runner_|. auto derived = SequenceBound(task_runner_, &value); derived.Post(FROM_HERE, &Derived::SetValue, kDifferentValue); EXPECT_EQ(value, kInitialValue); // Both construction and SetValue should run. base::RunLoop().RunUntilIdle(); EXPECT_EQ(value, kDifferentValue); } TEST_F(SequenceBoundTest, MoveConstructionFromSameClass) { // Verify that we can move-construct with the same class. auto derived_old = SequenceBound(task_runner_, &value); auto derived_new = std::move(derived_old); EXPECT_TRUE(derived_old.is_null()); EXPECT_FALSE(derived_new.is_null()); base::RunLoop().RunUntilIdle(); EXPECT_EQ(value, kDerivedCtorValue); // Verify that |derived_new| owns the object now, and that the virtual // destructor is called. derived_new.Reset(); EXPECT_EQ(value, kDerivedCtorValue); base::RunLoop().RunUntilIdle(); EXPECT_EQ(value, kDerivedDtorValue); } TEST_F(SequenceBoundTest, MoveConstructionFromDerivedClass) { // Verify that we can move-construct to a base class from a derived class. auto derived = SequenceBound(task_runner_, &value); SequenceBound base(std::move(derived)); EXPECT_TRUE(derived.is_null()); EXPECT_FALSE(base.is_null()); base::RunLoop().RunUntilIdle(); EXPECT_EQ(value, kDerivedCtorValue); // Verify that |base| owns the object now, and that destruction still destroys // Derived properly. base.Reset(); EXPECT_EQ(value, kDerivedCtorValue); base::RunLoop().RunUntilIdle(); EXPECT_EQ(value, kDerivedDtorValue); } TEST_F(SequenceBoundTest, MultiplyDerivedDestructionWorksLeftSuper) { // Verify that everything works when we're casting around in ways that might // change the address. We cast to the left side of MultiplyDerived and then // reset the owner. ASAN will catch free() errors. Value value2 = kInitialValue; auto mderived = SequenceBound(task_runner_, &value, &value2); base::RunLoop().RunUntilIdle(); EXPECT_EQ(value, kOtherCtorValue); EXPECT_EQ(value2, kDerivedCtorValue); SequenceBound other = std::move(mderived); other.Reset(); base::RunLoop().RunUntilIdle(); // Both destructors should have run. EXPECT_EQ(value, kOtherDtorValue); EXPECT_EQ(value2, kDerivedDtorValue); } TEST_F(SequenceBoundTest, MultiplyDerivedDestructionWorksRightSuper) { // Verify that everything works when we're casting around in ways that might // change the address. We cast to the right side of MultiplyDerived and then // reset the owner. ASAN will catch free() errors. Value value2 = kInitialValue; auto mderived = SequenceBound(task_runner_, &value, &value2); base::RunLoop().RunUntilIdle(); EXPECT_EQ(value, kOtherCtorValue); EXPECT_EQ(value2, kDerivedCtorValue); SequenceBound base = std::move(mderived); base.Reset(); base::RunLoop().RunUntilIdle(); // Both destructors should have run. EXPECT_EQ(value, kOtherDtorValue); EXPECT_EQ(value2, kDerivedDtorValue); } TEST_F(SequenceBoundTest, MoveAssignmentFromSameClass) { // Test move-assignment using the same classes. auto derived_old = SequenceBound(task_runner_, &value); SequenceBound derived_new; derived_new = std::move(derived_old); EXPECT_TRUE(derived_old.is_null()); EXPECT_FALSE(derived_new.is_null()); base::RunLoop().RunUntilIdle(); EXPECT_EQ(value, kDerivedCtorValue); // Verify that |derived_new| owns the object now. Also verifies that move // assignment from the same class deletes the outgoing object. derived_new = SequenceBound(); EXPECT_EQ(value, kDerivedCtorValue); base::RunLoop().RunUntilIdle(); EXPECT_EQ(value, kDerivedDtorValue); } TEST_F(SequenceBoundTest, MoveAssignmentFromDerivedClass) { // Move-assignment from a derived class to a base class. auto derived = SequenceBound(task_runner_, &value); SequenceBound base; base = std::move(derived); EXPECT_TRUE(derived.is_null()); EXPECT_FALSE(base.is_null()); base::RunLoop().RunUntilIdle(); EXPECT_EQ(value, kDerivedCtorValue); // Verify that |base| owns the object now, and that destruction still destroys // Derived properly. base.Reset(); EXPECT_EQ(value, kDerivedCtorValue); base::RunLoop().RunUntilIdle(); EXPECT_EQ(value, kDerivedDtorValue); } TEST_F(SequenceBoundTest, MoveAssignmentFromDerivedClassDestroysOldObject) { // Verify that move-assignment from a derived class runs the dtor of the // outgoing object. auto derived = SequenceBound(task_runner_, &value); Value value1 = kInitialValue; Value value2 = kInitialValue; auto mderived = SequenceBound(task_runner_, &value1, &value2); base::RunLoop().RunUntilIdle(); EXPECT_EQ(value, kDerivedCtorValue); // Assign |mderived|, and verify that the original object in |derived| is // destroyed properly. derived = std::move(mderived); base::RunLoop().RunUntilIdle(); EXPECT_EQ(value, kDerivedDtorValue); // Delete |derived|, since it has pointers to local vars. derived.Reset(); base::RunLoop().RunUntilIdle(); } TEST_F(SequenceBoundTest, MultiplyDerivedPostToLeftBaseClass) { // Cast and call methods on the left base class. Value value1 = kInitialValue; Value value2 = kInitialValue; auto mderived = SequenceBound(task_runner_, &value1, &value2); // Cast to Other, the left base. SequenceBound other(std::move(mderived)); other.Post(FROM_HERE, &Other::SetValue, kDifferentValue); base::RunLoop().RunUntilIdle(); EXPECT_EQ(value1, kDifferentValue); EXPECT_EQ(value2, kDerivedCtorValue); other.Reset(); base::RunLoop().RunUntilIdle(); } TEST_F(SequenceBoundTest, MultiplyDerivedPostToRightBaseClass) { // Cast and call methods on the right base class. Value value1 = kInitialValue; Value value2 = kInitialValue; auto mderived = SequenceBound(task_runner_, &value1, &value2); SequenceBound derived(std::move(mderived)); derived.Post(FROM_HERE, &Derived::SetValue, kDifferentValue); base::RunLoop().RunUntilIdle(); EXPECT_EQ(value1, kOtherCtorValue); EXPECT_EQ(value2, kDifferentValue); derived.Reset(); base::RunLoop().RunUntilIdle(); } TEST_F(SequenceBoundTest, MoveConstructionFromNullWorks) { // Verify that this doesn't crash. SequenceBound derived1; SequenceBound derived2(std::move(derived1)); } TEST_F(SequenceBoundTest, MoveAssignmentFromNullWorks) { // Verify that this doesn't crash. SequenceBound derived1; SequenceBound derived2; derived2 = std::move(derived1); } TEST_F(SequenceBoundTest, ResetOnNullObjectWorks) { // Verify that this doesn't crash. SequenceBound derived; derived.Reset(); } TEST_F(SequenceBoundTest, IsVirtualBaseClassOf) { // Check that is_virtual_base_of<> works properly. // Neither |Base| nor |Derived| is a virtual base of the other. static_assert(!internal::is_virtual_base_of::value, "|Base| shouldn't be a virtual base of |Derived|"); static_assert(!internal::is_virtual_base_of::value, "|Derived| shouldn't be a virtual base of |Base|"); // |Base| should be a virtual base class of |VirtuallyDerived|, but not the // other way. static_assert(internal::is_virtual_base_of::value, "|Base| should be a virtual base of |VirtuallyDerived|"); static_assert(!internal::is_virtual_base_of::value, "|VirtuallyDerived shouldn't be a virtual base of |Base|"); } } // namespace base