/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* 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
*
* http://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 <folly/ScopeGuard.h>
#include <condition_variable>
#include <functional>
#include <stdexcept>
#include <thread>
#include <glog/logging.h>
#include <folly/lang/Keep.h>
#include <folly/portability/GTest.h>
using folly::makeDismissedGuard;
using folly::makeGuard;
using std::vector;
struct in_scope {};
struct in_guard {};
extern "C" FOLLY_KEEP void check_folly_scope_exit_opaque() {
SCOPE_EXIT {
folly::detail::keep_sink(in_guard{});
};
folly::detail::keep_sink(in_scope{});
}
extern "C" FOLLY_KEEP void check_folly_scope_exit_opaque_noexcept() {
SCOPE_EXIT {
folly::detail::keep_sink(in_guard{});
};
folly::detail::keep_sink_nx(in_scope{});
}
extern "C" FOLLY_KEEP [[noreturn]] void check_folly_scope_exit_visible_throw() {
SCOPE_EXIT {
folly::detail::keep_sink(in_guard{});
};
throw 0;
}
extern "C" FOLLY_KEEP void check_folly_scope_exit_visible_throw_cond(bool b) {
SCOPE_EXIT {
folly::detail::keep_sink(in_guard{});
};
b ? void(throw 0) : void();
}
extern "C" FOLLY_KEEP void check_folly_scope_success_opaque() {
SCOPE_SUCCESS {
folly::detail::keep_sink(in_guard{});
};
folly::detail::keep_sink(in_scope{});
}
extern "C" FOLLY_KEEP void check_folly_scope_success_opaque_noexcept() {
SCOPE_SUCCESS {
folly::detail::keep_sink(in_guard{});
};
folly::detail::keep_sink_nx(in_scope{});
}
extern "C" FOLLY_KEEP [[noreturn]] void
check_folly_scope_success_visible_throw() {
SCOPE_SUCCESS {
folly::detail::keep_sink(in_guard{});
};
throw 0;
}
extern "C" FOLLY_KEEP void check_folly_scope_success_visible_throw_cond(
bool b) {
SCOPE_SUCCESS {
folly::detail::keep_sink(in_guard{});
};
b ? void(throw 0) : void();
}
extern "C" FOLLY_KEEP void check_folly_scope_fail_opaque() {
SCOPE_FAIL {
folly::detail::keep_sink(in_guard{});
};
folly::detail::keep_sink(in_scope{});
}
extern "C" FOLLY_KEEP void check_folly_scope_fail_opaque_noexcept() {
SCOPE_FAIL {
folly::detail::keep_sink(in_guard{});
};
folly::detail::keep_sink_nx(in_scope{});
}
extern "C" FOLLY_KEEP [[noreturn]] void check_folly_scope_fail_visible_throw() {
SCOPE_FAIL {
folly::detail::keep_sink(in_guard{});
};
throw 0;
}
extern "C" FOLLY_KEEP void check_folly_scope_fail_visible_throw_cond(bool b) {
SCOPE_FAIL {
folly::detail::keep_sink(in_guard{});
};
b ? void(throw 0) : void();
}
double returnsDouble() {
return 0.0;
}
class MyFunctor {
public:
explicit MyFunctor(int* ptr) : ptr_(ptr) {}
void operator()() { ++*ptr_; }
private:
int* ptr_;
};
TEST(ScopeGuard, DifferentWaysToBind) {
vector<int> v;
void (vector<int>::*push_back)(int const&) = &vector<int>::push_back;
v.push_back(1);
{
// binding to member function.
auto g = makeGuard(std::bind(&vector<int>::pop_back, &v));
(void)g;
}
EXPECT_EQ(0, v.size());
{
// bind member function with args. v is passed-by-value!
auto g = makeGuard(std::bind(push_back, v, 2));
(void)g;
}
EXPECT_EQ(0, v.size()); // push_back happened on a copy of v... fail!
// pass in an argument by pointer so to avoid copy.
{
auto g = makeGuard(std::bind(push_back, &v, 4));
(void)g;
}
EXPECT_EQ(1, v.size());
{
// pass in an argument by reference so to avoid copy.
auto g = makeGuard(std::bind(push_back, std::ref(v), 4));
(void)g;
}
EXPECT_EQ(2, v.size());
// lambda with a reference to v
{
auto g = makeGuard([&] { v.push_back(5); });
(void)g;
}
EXPECT_EQ(3, v.size());
// lambda with a copy of v
{
auto g = makeGuard([v]() mutable { v.push_back(6); });
(void)g;
}
EXPECT_EQ(3, v.size());
// functor object
int n = 0;
{
MyFunctor f(&n);
auto g = makeGuard(f);
(void)g;
}
EXPECT_EQ(1, n);
// temporary functor object
n = 0;
{
auto g = makeGuard(MyFunctor(&n));
(void)g;
}
EXPECT_EQ(1, n);
// Use auto instead of ScopeGuard
n = 2;
{
auto g = makeGuard(MyFunctor(&n));
(void)g;
}
EXPECT_EQ(3, n);
// Use const auto& instead of ScopeGuard
n = 10;
{
const auto& g = makeGuard(MyFunctor(&n));
(void)g;
}
EXPECT_EQ(11, n);
}
TEST(ScopeGuard, GuardException) {
EXPECT_DEATH(
(void)makeGuard(
[] { throw std::runtime_error("dtors should never throw!"); }),
"dtors should never throw!");
}
/**
* Add an integer to a vector iff it was inserted into the
* db successfuly. Here is a schematic of how you would accomplish
* this with scope guard.
*/
void testUndoAction(bool failure) {
vector<int64_t> v;
{ // defines a "mini" scope
// be optimistic and insert this into memory
v.push_back(1);
// The guard is triggered to undo the insertion unless dismiss() is called.
auto guard = makeGuard([&] { v.pop_back(); });
// Do some action; Use the failure argument to pretend
// if it failed or succeeded.
// if there was no failure, dismiss the undo guard action.
if (!failure) {
guard.dismiss();
}
} // all stack allocated in the mini-scope will be destroyed here.
if (failure) {
EXPECT_EQ(0, v.size()); // the action failed => undo insertion
} else {
EXPECT_EQ(1, v.size()); // the action succeeded => keep insertion
}
}
TEST(ScopeGuard, UndoAction) {
testUndoAction(true);
testUndoAction(false);
}
TEST(ScopeGuard, MakeDismissedGuard) {
auto test = [](bool shouldFire) {
bool fired = false;
{
auto guard = makeDismissedGuard([&] { fired = true; });
if (shouldFire) {
guard.rehire();
}
}
EXPECT_EQ(shouldFire, fired);
};
test(true);
test(false);
}
/**
* Sometimes in a try catch block we want to execute a piece of code
* regardless if an exception happened or not. For example, you want
* to close a db connection regardless if an exception was thrown during
* insertion. In Java and other languages there is a finally clause that
* helps accomplish this:
*
* try {
* dbConn.doInsert(sql);
* } catch (const DbException& dbe) {
* dbConn.recordFailure(dbe);
* } catch (const CriticalException& e) {
* throw e; // re-throw the exception
* } finally {
* dbConn.closeConnection(); // executes no matter what!
* }
*
* We can approximate this behavior in C++ with ScopeGuard.
*/
enum class ErrorBehavior {
SUCCESS,
HANDLED_ERROR,
UNHANDLED_ERROR,
};
void testFinally(ErrorBehavior error) {
bool cleanupOccurred = false;
try {
auto guard = makeGuard([&] { cleanupOccurred = true; });
(void)guard;
try {
if (error == ErrorBehavior::HANDLED_ERROR) {
throw std::runtime_error("throwing an expected error");
} else if (error == ErrorBehavior::UNHANDLED_ERROR) {
throw "never throw raw strings";
}
} catch (const std::runtime_error&) {
}
} catch (...) {
// Outer catch to swallow the error for the UNHANDLED_ERROR behavior
}
EXPECT_TRUE(cleanupOccurred);
}
TEST(ScopeGuard, TryCatchFinally) {
testFinally(ErrorBehavior::SUCCESS);
testFinally(ErrorBehavior::HANDLED_ERROR);
testFinally(ErrorBehavior::UNHANDLED_ERROR);
}
TEST(ScopeGuard, TESTScopeExit) {
int x = 0;
{
SCOPE_EXIT {
++x;
};
EXPECT_EQ(0, x);
}
EXPECT_EQ(1, x);
}
class Foo {
public:
Foo() {}
~Foo() {
try {
auto e = std::current_exception();
int test = 0;
{
SCOPE_EXIT {
++test;
};
EXPECT_EQ(0, test);
}
EXPECT_EQ(1, test);
} catch (const std::exception& ex) {
LOG(FATAL) << "Unexpected exception: " << ex.what();
}
}
};
TEST(ScopeGuard, TESTScopeFailure2) {
try {
Foo f;
throw std::runtime_error("test");
} catch (...) {
}
}
void testScopeFailAndScopeSuccess(ErrorBehavior error, bool expectFail) {
bool scopeFailExecuted = false;
bool scopeSuccessExecuted = false;
try {
SCOPE_FAIL {
scopeFailExecuted = true;
};
SCOPE_SUCCESS {
scopeSuccessExecuted = true;
};
try {
if (error == ErrorBehavior::HANDLED_ERROR) {
throw std::runtime_error("throwing an expected error");
} else if (error == ErrorBehavior::UNHANDLED_ERROR) {
throw "never throw raw strings";
}
} catch (const std::runtime_error&) {
}
} catch (...) {
// Outer catch to swallow the error for the UNHANDLED_ERROR behavior
}
EXPECT_EQ(expectFail, scopeFailExecuted);
EXPECT_EQ(!expectFail, scopeSuccessExecuted);
}
TEST(ScopeGuard, TESTScopeFailExceptionPtr) {
bool catchExecuted = false;
bool failExecuted = false;
try {
SCOPE_FAIL {
failExecuted = true;
};
std::exception_ptr ep;
try {
throw std::runtime_error("test");
} catch (...) {
ep = std::current_exception();
}
std::rethrow_exception(ep);
} catch (const std::exception&) {
catchExecuted = true;
}
EXPECT_TRUE(catchExecuted);
EXPECT_TRUE(failExecuted);
}
TEST(ScopeGuard, TESTScopeFailAndScopeSuccess) {
testScopeFailAndScopeSuccess(ErrorBehavior::SUCCESS, false);
testScopeFailAndScopeSuccess(ErrorBehavior::HANDLED_ERROR, false);
testScopeFailAndScopeSuccess(ErrorBehavior::UNHANDLED_ERROR, true);
}
TEST(ScopeGuard, TESTScopeSuccessThrow) {
auto lambda = []() {
SCOPE_SUCCESS {
throw std::runtime_error("ehm");
};
};
EXPECT_THROW(lambda(), std::runtime_error);
}
TEST(ScopeGuard, TESTThrowingCleanupAction) {
struct ThrowingCleanupAction {
// clang-format off
explicit ThrowingCleanupAction(int& scopeExitExecuted)
: scopeExitExecuted_(scopeExitExecuted) {}
[[noreturn]] ThrowingCleanupAction(const ThrowingCleanupAction& other)
: scopeExitExecuted_(other.scopeExitExecuted_) {
throw std::runtime_error("whoa");
}
// clang-format on
void operator()() { ++scopeExitExecuted_; }
private:
int& scopeExitExecuted_;
};
int scopeExitExecuted = 0;
ThrowingCleanupAction onExit(scopeExitExecuted);
EXPECT_THROW((void)makeGuard(onExit), std::runtime_error);
EXPECT_EQ(scopeExitExecuted, 1);
}