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