fix a multiline comment warning
[folly.git] / folly / test / ScopeGuardTest.cpp
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
26 using folly::makeGuard;
27 using std::vector;
28
29 double returnsDouble() {
30   return 0.0;
31 }
32
33 class 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
45 TEST(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
133 TEST(ScopeGuard, GuardException) {
134   EXPECT_DEATH(
135       makeGuard([] { throw std::runtime_error("dtors should never throw!"); }),
136       "dtors should never throw!");
137 }
138
139 /**
140  * Add an integer to a vector iff it was inserted into the
141  * db successfuly. Here is a schematic of how you would accomplish
142  * this with scope guard.
143  */
144 void testUndoAction(bool failure) {
145   vector<int64_t> v;
146   { // defines a "mini" scope
147
148     // be optimistic and insert this into memory
149     v.push_back(1);
150
151     // The guard is triggered to undo the insertion unless dismiss() is called.
152     auto guard = makeGuard([&] { v.pop_back(); });
153
154     // Do some action; Use the failure argument to pretend
155     // if it failed or succeeded.
156
157     // if there was no failure, dismiss the undo guard action.
158     if (!failure) {
159       guard.dismiss();
160     }
161   } // all stack allocated in the mini-scope will be destroyed here.
162
163   if (failure) {
164     EXPECT_EQ(0, v.size()); // the action failed => undo insertion
165   } else {
166     EXPECT_EQ(1, v.size()); // the action succeeded => keep insertion
167   }
168 }
169
170 TEST(ScopeGuard, UndoAction) {
171   testUndoAction(true);
172   testUndoAction(false);
173 }
174
175 /**
176  * Sometimes in a try catch block we want to execute a piece of code
177  * regardless if an exception happened or not. For example, you want
178  * to close a db connection regardless if an exception was thrown during
179  * insertion. In Java and other languages there is a finally clause that
180  * helps accomplish this:
181  *
182  *   try {
183  *     dbConn.doInsert(sql);
184  *   } catch (const DbException& dbe) {
185  *     dbConn.recordFailure(dbe);
186  *   } catch (const CriticalException& e) {
187  *     throw e; // re-throw the exception
188  *   } finally {
189  *     dbConn.closeConnection(); // executes no matter what!
190  *   }
191  *
192  * We can approximate this behavior in C++ with ScopeGuard.
193  */
194 enum class ErrorBehavior {
195   SUCCESS,
196   HANDLED_ERROR,
197   UNHANDLED_ERROR,
198 };
199
200 void testFinally(ErrorBehavior error) {
201   bool cleanupOccurred = false;
202
203   try {
204     auto guard = makeGuard([&] { cleanupOccurred = true; });
205     (void)guard;
206
207     try {
208       if (error == ErrorBehavior::HANDLED_ERROR) {
209         throw std::runtime_error("throwing an expected error");
210       } else if (error == ErrorBehavior::UNHANDLED_ERROR) {
211         throw "never throw raw strings";
212       }
213     } catch (const std::runtime_error&) {
214     }
215   } catch (...) {
216     // Outer catch to swallow the error for the UNHANDLED_ERROR behavior
217   }
218
219   EXPECT_TRUE(cleanupOccurred);
220 }
221
222 TEST(ScopeGuard, TryCatchFinally) {
223   testFinally(ErrorBehavior::SUCCESS);
224   testFinally(ErrorBehavior::HANDLED_ERROR);
225   testFinally(ErrorBehavior::UNHANDLED_ERROR);
226 }
227
228 TEST(ScopeGuard, TEST_SCOPE_EXIT) {
229   int x = 0;
230   {
231     SCOPE_EXIT { ++x; };
232     EXPECT_EQ(0, x);
233   }
234   EXPECT_EQ(1, x);
235 }
236
237 class Foo {
238  public:
239   Foo() {}
240   ~Foo() {
241     try {
242       auto e = std::current_exception();
243       int test = 0;
244       {
245         SCOPE_EXIT { ++test; };
246         EXPECT_EQ(0, test);
247       }
248       EXPECT_EQ(1, test);
249     } catch (const std::exception& ex) {
250       LOG(FATAL) << "Unexpected exception: " << ex.what();
251     }
252   }
253 };
254
255 TEST(ScopeGuard, TEST_SCOPE_FAILURE2) {
256   try {
257     Foo f;
258     throw std::runtime_error("test");
259   } catch (...) {
260   }
261 }
262
263 void testScopeFailAndScopeSuccess(ErrorBehavior error, bool expectFail) {
264   bool scopeFailExecuted = false;
265   bool scopeSuccessExecuted = false;
266
267   try {
268     SCOPE_FAIL { scopeFailExecuted = true; };
269     SCOPE_SUCCESS { scopeSuccessExecuted = true; };
270
271     try {
272       if (error == ErrorBehavior::HANDLED_ERROR) {
273         throw std::runtime_error("throwing an expected error");
274       } else if (error == ErrorBehavior::UNHANDLED_ERROR) {
275         throw "never throw raw strings";
276       }
277     } catch (const std::runtime_error&) {
278     }
279   } catch (...) {
280     // Outer catch to swallow the error for the UNHANDLED_ERROR behavior
281   }
282
283   EXPECT_EQ(expectFail, scopeFailExecuted);
284   EXPECT_EQ(!expectFail, scopeSuccessExecuted);
285 }
286
287 TEST(ScopeGuard, TEST_SCOPE_FAIL_AND_SCOPE_SUCCESS) {
288   testScopeFailAndScopeSuccess(ErrorBehavior::SUCCESS, false);
289   testScopeFailAndScopeSuccess(ErrorBehavior::HANDLED_ERROR, false);
290   testScopeFailAndScopeSuccess(ErrorBehavior::UNHANDLED_ERROR, true);
291 }
292
293 TEST(ScopeGuard, TEST_SCOPE_SUCCESS_THROW) {
294   auto lambda = []() {
295     SCOPE_SUCCESS { throw std::runtime_error("ehm"); };
296   };
297   EXPECT_THROW(lambda(), std::runtime_error);
298 }
299
300 TEST(ScopeGuard, TEST_THROWING_CLEANUP_ACTION) {
301   struct ThrowingCleanupAction {
302     explicit ThrowingCleanupAction(int& scopeExitExecuted)
303         : scopeExitExecuted_(scopeExitExecuted) {}
304     [[noreturn]]
305     ThrowingCleanupAction(const ThrowingCleanupAction& other)
306         : scopeExitExecuted_(other.scopeExitExecuted_) {
307       throw std::runtime_error("whoa");
308     }
309     void operator()() { ++scopeExitExecuted_; }
310
311    private:
312     int& scopeExitExecuted_;
313   };
314   int scopeExitExecuted = 0;
315   ThrowingCleanupAction onExit(scopeExitExecuted);
316   EXPECT_THROW(makeGuard(onExit), std::runtime_error);
317   EXPECT_EQ(scopeExitExecuted, 1);
318 }