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