| // Copyright 2021 The Abseil Authors. | |
| // | |
| // Licensed under the Apache License, Version 2.0 (the "License"); | |
| // you may not use this file except in compliance with the License. | |
| // You may obtain a copy of the License at | |
| // | |
| // https://www.apache.org/licenses/LICENSE-2.0 | |
| // | |
| // Unless required by applicable law or agreed to in writing, software | |
| // distributed under the License is distributed on an "AS IS" BASIS, | |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| // See the License for the specific language governing permissions and | |
| // limitations under the License. | |
| #include "absl/cleanup/cleanup.h" | |
| #include <functional> | |
| #include <type_traits> | |
| #include <utility> | |
| #include "gtest/gtest.h" | |
| #include "absl/base/config.h" | |
| #include "absl/cleanup/internal/cleanup.h" | |
| #include "absl/utility/utility.h" | |
| namespace { | |
| using Tag = absl::cleanup_internal::Tag; | |
| template <typename Type1, typename Type2> | |
| constexpr bool IsSame() { | |
| return (std::is_same_v<Type1, Type2>); | |
| } | |
| struct IdentityFactory { | |
| template <typename Callback> | |
| static Callback AsCallback(Callback callback) { | |
| return Callback(std::move(callback)); | |
| } | |
| }; | |
| // `FunctorClass` is a type used for testing `absl::Cleanup`. It is intended to | |
| // represent users that make their own move-only callback types outside of | |
| // `std::function` and lambda literals. | |
| class FunctorClass { | |
| using Callback = std::function<void()>; | |
| public: | |
| explicit FunctorClass(Callback callback) : callback_(std::move(callback)) {} | |
| FunctorClass(FunctorClass&& other) | |
| : callback_(std::exchange(other.callback_, Callback())) {} | |
| FunctorClass(const FunctorClass&) = delete; | |
| FunctorClass& operator=(const FunctorClass&) = delete; | |
| FunctorClass& operator=(FunctorClass&&) = delete; | |
| void operator()() const& = delete; | |
| void operator()() && { | |
| ASSERT_TRUE(callback_); | |
| callback_(); | |
| callback_ = nullptr; | |
| } | |
| private: | |
| Callback callback_; | |
| }; | |
| struct FunctorClassFactory { | |
| template <typename Callback> | |
| static FunctorClass AsCallback(Callback callback) { | |
| return FunctorClass(std::move(callback)); | |
| } | |
| }; | |
| struct StdFunctionFactory { | |
| template <typename Callback> | |
| static std::function<void()> AsCallback(Callback callback) { | |
| return std::function<void()>(std::move(callback)); | |
| } | |
| }; | |
| using CleanupTestParams = | |
| ::testing::Types<IdentityFactory, FunctorClassFactory, StdFunctionFactory>; | |
| template <typename> | |
| struct CleanupTest : public ::testing::Test {}; | |
| TYPED_TEST_SUITE(CleanupTest, CleanupTestParams); | |
| bool fn_ptr_called = false; | |
| void FnPtrFunction() { fn_ptr_called = true; } | |
| TYPED_TEST(CleanupTest, FactoryProducesCorrectType) { | |
| { | |
| auto callback = TypeParam::AsCallback([] {}); | |
| auto cleanup = absl::MakeCleanup(std::move(callback)); | |
| static_assert( | |
| IsSame<absl::Cleanup<Tag, decltype(callback)>, decltype(cleanup)>()); | |
| } | |
| { | |
| auto cleanup = absl::MakeCleanup(&FnPtrFunction); | |
| static_assert(IsSame<absl::Cleanup<Tag, void (*)()>, decltype(cleanup)>()); | |
| } | |
| { | |
| auto cleanup = absl::MakeCleanup(FnPtrFunction); | |
| static_assert(IsSame<absl::Cleanup<Tag, void (*)()>, decltype(cleanup)>()); | |
| } | |
| } | |
| TYPED_TEST(CleanupTest, CTADProducesCorrectType) { | |
| { | |
| auto callback = TypeParam::AsCallback([] {}); | |
| absl::Cleanup cleanup = std::move(callback); | |
| static_assert( | |
| IsSame<absl::Cleanup<Tag, decltype(callback)>, decltype(cleanup)>()); | |
| } | |
| { | |
| absl::Cleanup cleanup = &FnPtrFunction; | |
| static_assert(IsSame<absl::Cleanup<Tag, void (*)()>, decltype(cleanup)>()); | |
| } | |
| { | |
| absl::Cleanup cleanup = FnPtrFunction; | |
| static_assert(IsSame<absl::Cleanup<Tag, void (*)()>, decltype(cleanup)>()); | |
| } | |
| } | |
| TYPED_TEST(CleanupTest, FactoryAndCTADProduceSameType) { | |
| { | |
| auto callback = IdentityFactory::AsCallback([] {}); | |
| auto factory_cleanup = absl::MakeCleanup(callback); | |
| absl::Cleanup deduction_cleanup = callback; | |
| static_assert( | |
| IsSame<decltype(factory_cleanup), decltype(deduction_cleanup)>()); | |
| } | |
| { | |
| auto factory_cleanup = | |
| absl::MakeCleanup(FunctorClassFactory::AsCallback([] {})); | |
| absl::Cleanup deduction_cleanup = FunctorClassFactory::AsCallback([] {}); | |
| static_assert( | |
| IsSame<decltype(factory_cleanup), decltype(deduction_cleanup)>()); | |
| } | |
| { | |
| auto factory_cleanup = | |
| absl::MakeCleanup(StdFunctionFactory::AsCallback([] {})); | |
| absl::Cleanup deduction_cleanup = StdFunctionFactory::AsCallback([] {}); | |
| static_assert( | |
| IsSame<decltype(factory_cleanup), decltype(deduction_cleanup)>()); | |
| } | |
| { | |
| auto factory_cleanup = absl::MakeCleanup(&FnPtrFunction); | |
| absl::Cleanup deduction_cleanup = &FnPtrFunction; | |
| static_assert( | |
| IsSame<decltype(factory_cleanup), decltype(deduction_cleanup)>()); | |
| } | |
| { | |
| auto factory_cleanup = absl::MakeCleanup(FnPtrFunction); | |
| absl::Cleanup deduction_cleanup = FnPtrFunction; | |
| static_assert( | |
| IsSame<decltype(factory_cleanup), decltype(deduction_cleanup)>()); | |
| } | |
| } | |
| TYPED_TEST(CleanupTest, BasicUsage) { | |
| bool called = false; | |
| { | |
| auto cleanup = | |
| absl::MakeCleanup(TypeParam::AsCallback([&called] { called = true; })); | |
| EXPECT_FALSE(called); // Constructor shouldn't invoke the callback | |
| } | |
| EXPECT_TRUE(called); // Destructor should invoke the callback | |
| } | |
| TYPED_TEST(CleanupTest, BasicUsageWithFunctionPointer) { | |
| fn_ptr_called = false; | |
| { | |
| auto cleanup = absl::MakeCleanup(TypeParam::AsCallback(&FnPtrFunction)); | |
| EXPECT_FALSE(fn_ptr_called); // Constructor shouldn't invoke the callback | |
| } | |
| EXPECT_TRUE(fn_ptr_called); // Destructor should invoke the callback | |
| } | |
| TYPED_TEST(CleanupTest, Cancel) { | |
| bool called = false; | |
| { | |
| auto cleanup = | |
| absl::MakeCleanup(TypeParam::AsCallback([&called] { called = true; })); | |
| EXPECT_FALSE(called); // Constructor shouldn't invoke the callback | |
| std::move(cleanup).Cancel(); | |
| EXPECT_FALSE(called); // Cancel shouldn't invoke the callback | |
| } | |
| EXPECT_FALSE(called); // Destructor shouldn't invoke the callback | |
| } | |
| TYPED_TEST(CleanupTest, Invoke) { | |
| bool called = false; | |
| { | |
| auto cleanup = | |
| absl::MakeCleanup(TypeParam::AsCallback([&called] { called = true; })); | |
| EXPECT_FALSE(called); // Constructor shouldn't invoke the callback | |
| std::move(cleanup).Invoke(); | |
| EXPECT_TRUE(called); // Invoke should invoke the callback | |
| called = false; // Reset tracker before destructor runs | |
| } | |
| EXPECT_FALSE(called); // Destructor shouldn't invoke the callback | |
| } | |
| TYPED_TEST(CleanupTest, Move) { | |
| bool called = false; | |
| { | |
| auto moved_from_cleanup = | |
| absl::MakeCleanup(TypeParam::AsCallback([&called] { called = true; })); | |
| EXPECT_FALSE(called); // Constructor shouldn't invoke the callback | |
| { | |
| auto moved_to_cleanup = std::move(moved_from_cleanup); | |
| EXPECT_FALSE(called); // Move shouldn't invoke the callback | |
| } | |
| EXPECT_TRUE(called); // Destructor should invoke the callback | |
| called = false; // Reset tracker before destructor runs | |
| } | |
| EXPECT_FALSE(called); // Destructor shouldn't invoke the callback | |
| } | |
| int DestructionCount = 0; | |
| struct DestructionCounter { | |
| void operator()() {} | |
| ~DestructionCounter() { ++DestructionCount; } | |
| }; | |
| TYPED_TEST(CleanupTest, DestructorDestroys) { | |
| { | |
| auto cleanup = | |
| absl::MakeCleanup(TypeParam::AsCallback(DestructionCounter())); | |
| DestructionCount = 0; | |
| } | |
| EXPECT_EQ(DestructionCount, 1); // Engaged cleanup destroys | |
| } | |
| TYPED_TEST(CleanupTest, CancelDestroys) { | |
| { | |
| auto cleanup = | |
| absl::MakeCleanup(TypeParam::AsCallback(DestructionCounter())); | |
| DestructionCount = 0; | |
| std::move(cleanup).Cancel(); | |
| EXPECT_EQ(DestructionCount, 1); // Cancel destroys | |
| } | |
| EXPECT_EQ(DestructionCount, 1); // Canceled cleanup does not double destroy | |
| } | |
| TYPED_TEST(CleanupTest, InvokeDestroys) { | |
| { | |
| auto cleanup = | |
| absl::MakeCleanup(TypeParam::AsCallback(DestructionCounter())); | |
| DestructionCount = 0; | |
| std::move(cleanup).Invoke(); | |
| EXPECT_EQ(DestructionCount, 1); // Invoke destroys | |
| } | |
| EXPECT_EQ(DestructionCount, 1); // Invoked cleanup does not double destroy | |
| } | |
| } // namespace |