// 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 #include #include #include "gtest/gtest.h" #include "absl/base/config.h" #include "absl/utility/utility.h" namespace { using Tag = absl::cleanup_internal::Tag; template constexpr bool IsSame() { return (std::is_same::value); } struct IdentityFactory { template 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; 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 static FunctorClass AsCallback(Callback callback) { return FunctorClass(std::move(callback)); } }; struct StdFunctionFactory { template static std::function AsCallback(Callback callback) { return std::function(std::move(callback)); } }; using CleanupTestParams = ::testing::Types; template 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, decltype(cleanup)>(), ""); } { auto cleanup = absl::MakeCleanup(&FnPtrFunction); static_assert(IsSame, decltype(cleanup)>(), ""); } { auto cleanup = absl::MakeCleanup(FnPtrFunction); static_assert(IsSame, decltype(cleanup)>(), ""); } } #if defined(ABSL_HAVE_CLASS_TEMPLATE_ARGUMENT_DEDUCTION) TYPED_TEST(CleanupTest, CTADProducesCorrectType) { { auto callback = TypeParam::AsCallback([] {}); absl::Cleanup cleanup = std::move(callback); static_assert( IsSame, decltype(cleanup)>(), ""); } { absl::Cleanup cleanup = &FnPtrFunction; static_assert(IsSame, decltype(cleanup)>(), ""); } { absl::Cleanup cleanup = FnPtrFunction; static_assert(IsSame, decltype(cleanup)>(), ""); } } TYPED_TEST(CleanupTest, FactoryAndCTADProduceSameType) { { auto callback = IdentityFactory::AsCallback([] {}); auto factory_cleanup = absl::MakeCleanup(callback); absl::Cleanup deduction_cleanup = callback; static_assert( IsSame(), ""); } { auto factory_cleanup = absl::MakeCleanup(FunctorClassFactory::AsCallback([] {})); absl::Cleanup deduction_cleanup = FunctorClassFactory::AsCallback([] {}); static_assert( IsSame(), ""); } { auto factory_cleanup = absl::MakeCleanup(StdFunctionFactory::AsCallback([] {})); absl::Cleanup deduction_cleanup = StdFunctionFactory::AsCallback([] {}); static_assert( IsSame(), ""); } { auto factory_cleanup = absl::MakeCleanup(&FnPtrFunction); absl::Cleanup deduction_cleanup = &FnPtrFunction; static_assert( IsSame(), ""); } { auto factory_cleanup = absl::MakeCleanup(FnPtrFunction); absl::Cleanup deduction_cleanup = FnPtrFunction; static_assert( IsSame(), ""); } } #endif // defined(ABSL_HAVE_CLASS_TEMPLATE_ARGUMENT_DEDUCTION) 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