// Copyright 2014 The Chromium Authors // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "base/scoped_generic.h" #include #include #include #include #include #include "base/containers/contains.h" #include "base/memory/raw_ptr.h" #include "build/build_config.h" #include "testing/gtest/include/gtest/gtest.h" namespace base { namespace { struct IntTraits { IntTraits(std::vector* freed) : freed_ints(freed) {} static int InvalidValue() { return -1; } void Free(int value) { freed_ints->push_back(value); } raw_ptr> freed_ints; }; using ScopedInt = ScopedGeneric; } // namespace TEST(ScopedGenericTest, ScopedGeneric) { std::vector values_freed; IntTraits traits(&values_freed); // Invalid case, delete should not be called. { ScopedInt a(IntTraits::InvalidValue(), traits); } EXPECT_TRUE(values_freed.empty()); // Simple deleting case. static const int kFirst = 0; { ScopedInt a(kFirst, traits); } ASSERT_EQ(1u, values_freed.size()); ASSERT_EQ(kFirst, values_freed[0]); values_freed.clear(); // Release should return the right value and leave the object empty. { ScopedInt a(kFirst, traits); EXPECT_EQ(kFirst, a.release()); ScopedInt b(IntTraits::InvalidValue(), traits); EXPECT_EQ(IntTraits::InvalidValue(), b.release()); } ASSERT_TRUE(values_freed.empty()); // Reset should free the old value, then the new one should go away when // it goes out of scope. static const int kSecond = 1; { ScopedInt b(kFirst, traits); b.reset(kSecond); ASSERT_EQ(1u, values_freed.size()); ASSERT_EQ(kFirst, values_freed[0]); } ASSERT_EQ(2u, values_freed.size()); ASSERT_EQ(kSecond, values_freed[1]); values_freed.clear(); // Move constructor. { ScopedInt a(kFirst, traits); ScopedInt b(std::move(a)); EXPECT_TRUE(values_freed.empty()); // Nothing should be freed. ASSERT_EQ(IntTraits::InvalidValue(), a.get()); ASSERT_EQ(kFirst, b.get()); } ASSERT_EQ(1u, values_freed.size()); ASSERT_EQ(kFirst, values_freed[0]); values_freed.clear(); // Move assign. { ScopedInt a(kFirst, traits); ScopedInt b(kSecond, traits); b = std::move(a); ASSERT_EQ(1u, values_freed.size()); EXPECT_EQ(kSecond, values_freed[0]); ASSERT_EQ(IntTraits::InvalidValue(), a.get()); ASSERT_EQ(kFirst, b.get()); } ASSERT_EQ(2u, values_freed.size()); EXPECT_EQ(kFirst, values_freed[1]); values_freed.clear(); } TEST(ScopedGenericTest, Operators) { std::vector values_freed; IntTraits traits(&values_freed); static const int kFirst = 0; static const int kSecond = 1; { ScopedInt a(kFirst, traits); EXPECT_TRUE(a == kFirst); EXPECT_FALSE(a != kFirst); EXPECT_FALSE(a == kSecond); EXPECT_TRUE(a != kSecond); EXPECT_TRUE(kFirst == a); EXPECT_FALSE(kFirst != a); EXPECT_FALSE(kSecond == a); EXPECT_TRUE(kSecond != a); } // is_valid(). { ScopedInt a(kFirst, traits); EXPECT_TRUE(a.is_valid()); a.reset(); EXPECT_FALSE(a.is_valid()); } } TEST(ScopedGenericTest, Receive) { std::vector values_freed; IntTraits traits(&values_freed); auto a = std::make_unique(123, traits); EXPECT_EQ(123, a->get()); { ScopedInt::Receiver r(*a); EXPECT_EQ(123, a->get()); *r.get() = 456; EXPECT_EQ(123, a->get()); } EXPECT_EQ(456, a->get()); { ScopedInt::Receiver r(*a); EXPECT_DEATH_IF_SUPPORTED(a.reset(), ""); EXPECT_DEATH_IF_SUPPORTED(ScopedInt::Receiver(*a).get(), ""); } } namespace { struct TrackedIntTraits : public ScopedGenericOwnershipTracking { using OwnerMap = std::unordered_map*>; TrackedIntTraits(std::unordered_set* freed, OwnerMap* owners) : freed(freed), owners(owners) {} static int InvalidValue() { return -1; } void Free(int value) { auto it = owners->find(value); ASSERT_EQ(owners->end(), it); ASSERT_EQ(0U, freed->count(value)); freed->insert(value); } void Acquire(const ScopedGeneric& owner, int value) { auto it = owners->find(value); ASSERT_EQ(owners->end(), it); (*owners)[value] = &owner; } void Release(const ScopedGeneric& owner, int value) { auto it = owners->find(value); ASSERT_NE(owners->end(), it); owners->erase(it); } raw_ptr> freed; raw_ptr owners; }; using ScopedTrackedInt = ScopedGeneric; } // namespace TEST(ScopedGenericTest, OwnershipTracking) { TrackedIntTraits::OwnerMap owners; std::unordered_set freed; TrackedIntTraits traits(&freed, &owners); #define ASSERT_OWNED(value, owner) \ ASSERT_TRUE(base::Contains(owners, value)); \ ASSERT_EQ(&owner, owners[value]); \ ASSERT_FALSE(base::Contains(freed, value)) #define ASSERT_UNOWNED(value) \ ASSERT_FALSE(base::Contains(owners, value)); \ ASSERT_FALSE(base::Contains(freed, value)) #define ASSERT_FREED(value) \ ASSERT_FALSE(base::Contains(owners, value)); \ ASSERT_TRUE(base::Contains(freed, value)) // Constructor. { { ScopedTrackedInt a(0, traits); ASSERT_OWNED(0, a); } ASSERT_FREED(0); } owners.clear(); freed.clear(); // Reset. { ScopedTrackedInt a(0, traits); ASSERT_OWNED(0, a); a.reset(1); ASSERT_FREED(0); ASSERT_OWNED(1, a); a.reset(); ASSERT_FREED(0); ASSERT_FREED(1); } owners.clear(); freed.clear(); // Release. { { ScopedTrackedInt a(0, traits); ASSERT_OWNED(0, a); int released = a.release(); ASSERT_EQ(0, released); ASSERT_UNOWNED(0); } ASSERT_UNOWNED(0); } owners.clear(); freed.clear(); // Move constructor. { ScopedTrackedInt a(0, traits); ASSERT_OWNED(0, a); { ScopedTrackedInt b(std::move(a)); ASSERT_OWNED(0, b); } ASSERT_FREED(0); } owners.clear(); freed.clear(); // Move assignment. { { ScopedTrackedInt a(0, traits); ScopedTrackedInt b(1, traits); ASSERT_OWNED(0, a); ASSERT_OWNED(1, b); a = std::move(b); ASSERT_OWNED(1, a); ASSERT_FREED(0); } ASSERT_FREED(1); } owners.clear(); freed.clear(); #undef ASSERT_OWNED #undef ASSERT_UNOWNED #undef ASSERT_FREED } // Cheesy manual "no compile" test for manually validating changes. #if 0 TEST(ScopedGenericTest, NoCompile) { // Assignment shouldn't work. /*{ ScopedInt a(kFirst, traits); ScopedInt b(a); }*/ // Comparison shouldn't work. /*{ ScopedInt a(kFirst, traits); ScopedInt b(kFirst, traits); if (a == b) { } }*/ // Implicit conversion to bool shouldn't work. /*{ ScopedInt a(kFirst, traits); bool result = a; }*/ } #endif } // namespace base