1/*
2 * Copyright 2011-present Facebook, Inc.
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17#include <folly/ScopeGuard.h>
18
19#include <glog/logging.h>
20
21#include <functional>
22#include <stdexcept>
23
24#include <folly/portability/GTest.h>
25
26using folly::makeGuard;
27using std::vector;
28
29double returnsDouble() {
30 return 0.0;
31}
32
33class MyFunctor {
34 public:
35 explicit MyFunctor(int* ptr) : ptr_(ptr) {}
36
37 void operator()() {
38 ++*ptr_;
39 }
40
41 private:
42 int* ptr_;
43};
44
45TEST(ScopeGuard, DifferentWaysToBind) {
46 {
47 // There is implicit conversion from func pointer
48 // double (*)() to function<void()>.
49 auto g = makeGuard(returnsDouble);
50 (void)g;
51 }
52
53 vector<int> v;
54 void (vector<int>::*push_back)(int const&) = &vector<int>::push_back;
55
56 v.push_back(1);
57 {
58 // binding to member function.
59 auto g = makeGuard(std::bind(&vector<int>::pop_back, &v));
60 (void)g;
61 }
62 EXPECT_EQ(0, v.size());
63
64 {
65 // bind member function with args. v is passed-by-value!
66 auto g = makeGuard(std::bind(push_back, v, 2));
67 (void)g;
68 }
69 EXPECT_EQ(0, v.size()); // push_back happened on a copy of v... fail!
70
71 // pass in an argument by pointer so to avoid copy.
72 {
73 auto g = makeGuard(std::bind(push_back, &v, 4));
74 (void)g;
75 }
76 EXPECT_EQ(1, v.size());
77
78 {
79 // pass in an argument by reference so to avoid copy.
80 auto g = makeGuard(std::bind(push_back, std::ref(v), 4));
81 (void)g;
82 }
83 EXPECT_EQ(2, v.size());
84
85 // lambda with a reference to v
86 {
87 auto g = makeGuard([&] { v.push_back(5); });
88 (void)g;
89 }
90 EXPECT_EQ(3, v.size());
91
92 // lambda with a copy of v
93 {
94 auto g = makeGuard([v]() mutable { v.push_back(6); });
95 (void)g;
96 }
97 EXPECT_EQ(3, v.size());
98
99 // functor object
100 int n = 0;
101 {
102 MyFunctor f(&n);
103 auto g = makeGuard(f);
104 (void)g;
105 }
106 EXPECT_EQ(1, n);
107
108 // temporary functor object
109 n = 0;
110 {
111 auto g = makeGuard(MyFunctor(&n));
112 (void)g;
113 }
114 EXPECT_EQ(1, n);
115
116 // Use auto instead of ScopeGuard
117 n = 2;
118 {
119 auto g = makeGuard(MyFunctor(&n));
120 (void)g;
121 }
122 EXPECT_EQ(3, n);
123
124 // Use const auto& instead of ScopeGuard
125 n = 10;
126 {
127 const auto& g = makeGuard(MyFunctor(&n));
128 (void)g;
129 }
130 EXPECT_EQ(11, n);
131}
132
133TEST(ScopeGuard, GuardException) {
134 EXPECT_DEATH(
135 (void)makeGuard(
136 [] { throw std::runtime_error("dtors should never throw!"); }),
137 "dtors should never throw!");
138}
139
140/**
141 * Add an integer to a vector iff it was inserted into the
142 * db successfuly. Here is a schematic of how you would accomplish
143 * this with scope guard.
144 */
145void testUndoAction(bool failure) {
146 vector<int64_t> v;
147 { // defines a "mini" scope
148
149 // be optimistic and insert this into memory
150 v.push_back(1);
151
152 // The guard is triggered to undo the insertion unless dismiss() is called.
153 auto guard = makeGuard([&] { v.pop_back(); });
154
155 // Do some action; Use the failure argument to pretend
156 // if it failed or succeeded.
157
158 // if there was no failure, dismiss the undo guard action.
159 if (!failure) {
160 guard.dismiss();
161 }
162 } // all stack allocated in the mini-scope will be destroyed here.
163
164 if (failure) {
165 EXPECT_EQ(0, v.size()); // the action failed => undo insertion
166 } else {
167 EXPECT_EQ(1, v.size()); // the action succeeded => keep insertion
168 }
169}
170
171TEST(ScopeGuard, UndoAction) {
172 testUndoAction(true);
173 testUndoAction(false);
174}
175
176/**
177 * Sometimes in a try catch block we want to execute a piece of code
178 * regardless if an exception happened or not. For example, you want
179 * to close a db connection regardless if an exception was thrown during
180 * insertion. In Java and other languages there is a finally clause that
181 * helps accomplish this:
182 *
183 * try {
184 * dbConn.doInsert(sql);
185 * } catch (const DbException& dbe) {
186 * dbConn.recordFailure(dbe);
187 * } catch (const CriticalException& e) {
188 * throw e; // re-throw the exception
189 * } finally {
190 * dbConn.closeConnection(); // executes no matter what!
191 * }
192 *
193 * We can approximate this behavior in C++ with ScopeGuard.
194 */
195enum class ErrorBehavior {
196 SUCCESS,
197 HANDLED_ERROR,
198 UNHANDLED_ERROR,
199};
200
201void testFinally(ErrorBehavior error) {
202 bool cleanupOccurred = false;
203
204 try {
205 auto guard = makeGuard([&] { cleanupOccurred = true; });
206 (void)guard;
207
208 try {
209 if (error == ErrorBehavior::HANDLED_ERROR) {
210 throw std::runtime_error("throwing an expected error");
211 } else if (error == ErrorBehavior::UNHANDLED_ERROR) {
212 throw "never throw raw strings";
213 }
214 } catch (const std::runtime_error&) {
215 }
216 } catch (...) {
217 // Outer catch to swallow the error for the UNHANDLED_ERROR behavior
218 }
219
220 EXPECT_TRUE(cleanupOccurred);
221}
222
223TEST(ScopeGuard, TryCatchFinally) {
224 testFinally(ErrorBehavior::SUCCESS);
225 testFinally(ErrorBehavior::HANDLED_ERROR);
226 testFinally(ErrorBehavior::UNHANDLED_ERROR);
227}
228
229TEST(ScopeGuard, TEST_SCOPE_EXIT) {
230 int x = 0;
231 {
232 SCOPE_EXIT {
233 ++x;
234 };
235 EXPECT_EQ(0, x);
236 }
237 EXPECT_EQ(1, x);
238}
239
240class Foo {
241 public:
242 Foo() {}
243 ~Foo() {
244 try {
245 auto e = std::current_exception();
246 int test = 0;
247 {
248 SCOPE_EXIT {
249 ++test;
250 };
251 EXPECT_EQ(0, test);
252 }
253 EXPECT_EQ(1, test);
254 } catch (const std::exception& ex) {
255 LOG(FATAL) << "Unexpected exception: " << ex.what();
256 }
257 }
258};
259
260TEST(ScopeGuard, TEST_SCOPE_FAILURE2) {
261 try {
262 Foo f;
263 throw std::runtime_error("test");
264 } catch (...) {
265 }
266}
267
268void testScopeFailAndScopeSuccess(ErrorBehavior error, bool expectFail) {
269 bool scopeFailExecuted = false;
270 bool scopeSuccessExecuted = false;
271
272 try {
273 SCOPE_FAIL {
274 scopeFailExecuted = true;
275 };
276 SCOPE_SUCCESS {
277 scopeSuccessExecuted = true;
278 };
279
280 try {
281 if (error == ErrorBehavior::HANDLED_ERROR) {
282 throw std::runtime_error("throwing an expected error");
283 } else if (error == ErrorBehavior::UNHANDLED_ERROR) {
284 throw "never throw raw strings";
285 }
286 } catch (const std::runtime_error&) {
287 }
288 } catch (...) {
289 // Outer catch to swallow the error for the UNHANDLED_ERROR behavior
290 }
291
292 EXPECT_EQ(expectFail, scopeFailExecuted);
293 EXPECT_EQ(!expectFail, scopeSuccessExecuted);
294}
295
296TEST(ScopeGuard, TEST_SCOPE_FAIL_AND_SCOPE_SUCCESS) {
297 testScopeFailAndScopeSuccess(ErrorBehavior::SUCCESS, false);
298 testScopeFailAndScopeSuccess(ErrorBehavior::HANDLED_ERROR, false);
299 testScopeFailAndScopeSuccess(ErrorBehavior::UNHANDLED_ERROR, true);
300}
301
302TEST(ScopeGuard, TEST_SCOPE_SUCCESS_THROW) {
303 auto lambda = []() {
304 SCOPE_SUCCESS {
305 throw std::runtime_error("ehm");
306 };
307 };
308 EXPECT_THROW(lambda(), std::runtime_error);
309}
310
311TEST(ScopeGuard, TEST_THROWING_CLEANUP_ACTION) {
312 struct ThrowingCleanupAction {
313 // clang-format off
314 explicit ThrowingCleanupAction(int& scopeExitExecuted)
315 : scopeExitExecuted_(scopeExitExecuted) {}
316 [[noreturn]] ThrowingCleanupAction(const ThrowingCleanupAction& other)
317 : scopeExitExecuted_(other.scopeExitExecuted_) {
318 throw std::runtime_error("whoa");
319 }
320 // clang-format on
321 void operator()() {
322 ++scopeExitExecuted_;
323 }
324
325 private:
326 int& scopeExitExecuted_;
327 };
328 int scopeExitExecuted = 0;
329 ThrowingCleanupAction onExit(scopeExitExecuted);
330 EXPECT_THROW((void)makeGuard(onExit), std::runtime_error);
331 EXPECT_EQ(scopeExitExecuted, 1);
332}
333