b741e37503536cfbc7977e316b1f4e607b82cd16
[folly.git] / folly / test / ScopeGuardTest.cpp
1 /*
2  * Copyright 2013 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 #include "folly/Portability.h"
19
20 #include <gflags/gflags.h>
21 #include <gtest/gtest.h>
22 #include <glog/logging.h>
23
24 #include <functional>
25 #include <stdexcept>
26
27 using folly::ScopeGuard;
28 using folly::makeGuard;
29 using std::vector;
30
31 double returnsDouble() {
32   return 0.0;
33 }
34
35 class MyFunctor {
36  public:
37   explicit MyFunctor(int* ptr) : ptr_(ptr) {}
38
39   void operator()() {
40     ++*ptr_;
41   }
42
43  private:
44   int* ptr_;
45 };
46
47 TEST(ScopeGuard, DifferentWaysToBind) {
48   {
49     // There is implicit conversion from func pointer
50     // double (*)() to function<void()>.
51     ScopeGuard g = makeGuard(returnsDouble);
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   }
62   EXPECT_EQ(0, v.size());
63
64   {
65     // bind member function with args. v is passed-by-value!
66     ScopeGuard g = makeGuard(std::bind(push_back, v, 2));
67   }
68   EXPECT_EQ(0, v.size()); // push_back happened on a copy of v... fail!
69
70   // pass in an argument by pointer so to avoid copy.
71   {
72     ScopeGuard g = makeGuard(std::bind(push_back, &v, 4));
73   }
74   EXPECT_EQ(1, v.size());
75
76   {
77     // pass in an argument by reference so to avoid copy.
78     ScopeGuard g = makeGuard(std::bind(push_back, std::ref(v), 4));
79   }
80   EXPECT_EQ(2, v.size());
81
82   // lambda with a reference to v
83   {
84     ScopeGuard g = makeGuard([&] { v.push_back(5); });
85   }
86   EXPECT_EQ(3, v.size());
87
88   // lambda with a copy of v
89   {
90     ScopeGuard g = makeGuard([v] () mutable { v.push_back(6); });
91   }
92   EXPECT_EQ(3, v.size());
93
94   // functor object
95   int n = 0;
96   {
97     MyFunctor f(&n);
98     ScopeGuard g = makeGuard(f);
99   }
100   EXPECT_EQ(1, n);
101
102   // temporary functor object
103   n = 0;
104   {
105     ScopeGuard g = makeGuard(MyFunctor(&n));
106   }
107   EXPECT_EQ(1, n);
108
109   // Use auto instead of ScopeGuard
110   n = 2;
111   {
112     auto g = makeGuard(MyFunctor(&n));
113   }
114   EXPECT_EQ(3, n);
115
116   // Use const auto& instead of ScopeGuard
117   n = 10;
118   {
119     const auto& g = makeGuard(MyFunctor(&n));
120   }
121   EXPECT_EQ(11, n);
122 }
123
124 TEST(ScopeGuard, GuardException) {
125   EXPECT_DEATH({
126     ScopeGuard g = makeGuard([&] {
127       throw std::runtime_error("destructors should never throw!");
128     });
129   },
130 #if FOLLY_USE_LIBCPP
131   "terminate called throwing an exception"
132 #else
133   "destructors should never throw"
134 #endif
135   );
136 }
137
138 /**
139  * Add an integer to a vector iff it was inserted into the
140  * db successfuly. Here is a schematic of how you would accomplish
141  * this with scope guard.
142  */
143 void testUndoAction(bool failure) {
144   vector<int64_t> v;
145   { // defines a "mini" scope
146
147     // be optimistic and insert this into memory
148     v.push_back(1);
149
150     // The guard is triggered to undo the insertion unless dismiss() is called.
151     ScopeGuard guard = makeGuard([&] { v.pop_back(); });
152
153     // Do some action; Use the failure argument to pretend
154     // if it failed or succeeded.
155
156     // if there was no failure, dismiss the undo guard action.
157     if (!failure) {
158       guard.dismiss();
159     }
160   } // all stack allocated in the mini-scope will be destroyed here.
161
162   if (failure) {
163     EXPECT_EQ(0, v.size()); // the action failed => undo insertion
164   } else {
165     EXPECT_EQ(1, v.size()); // the action succeeded => keep insertion
166   }
167 }
168
169 TEST(ScopeGuard, UndoAction) {
170   testUndoAction(true);
171   testUndoAction(false);
172 }
173
174 /**
175  * Sometimes in a try catch block we want to execute a piece of code
176  * regardless if an exception happened or not. For example, you want
177  * to close a db connection regardless if an exception was thrown during
178  * insertion. In Java and other languages there is a finally clause that
179  * helps accomplish this:
180  *
181  *   try {
182  *     dbConn.doInsert(sql);
183  *   } catch (const DbException& dbe) {
184  *     dbConn.recordFailure(dbe);
185  *   } catch (const CriticalException& e) {
186  *     throw e; // re-throw the exception
187  *   } finally {
188  *     dbConn.closeConnection(); // executes no matter what!
189  *   }
190  *
191  * We can approximate this behavior in C++ with ScopeGuard.
192  */
193 enum class ErrorBehavior {
194   SUCCESS,
195   HANDLED_ERROR,
196   UNHANDLED_ERROR,
197 };
198
199 void testFinally(ErrorBehavior error) {
200   bool cleanupOccurred = false;
201
202   try {
203     ScopeGuard guard = makeGuard([&] { cleanupOccurred = true; });
204
205     try {
206       if (error == ErrorBehavior::HANDLED_ERROR) {
207         throw std::runtime_error("throwing an expected error");
208       } else if (error == ErrorBehavior::UNHANDLED_ERROR) {
209         throw "never throw raw strings";
210       }
211     } catch (const std::runtime_error&) {
212     }
213   } catch (...) {
214     // Outer catch to swallow the error for the UNHANDLED_ERROR behavior
215   }
216
217   EXPECT_TRUE(cleanupOccurred);
218 }
219
220 TEST(ScopeGuard, TryCatchFinally) {
221   testFinally(ErrorBehavior::SUCCESS);
222   testFinally(ErrorBehavior::HANDLED_ERROR);
223   testFinally(ErrorBehavior::UNHANDLED_ERROR);
224 }
225
226 TEST(ScopeGuard, TEST_SCOPE_EXIT) {
227   int x = 0;
228   {
229     SCOPE_EXIT { ++x; };
230     EXPECT_EQ(0, x);
231   }
232   EXPECT_EQ(1, x);
233 }
234
235 class Foo {
236 public:
237   Foo() {}
238   ~Foo() {
239     try {
240       auto e = std::current_exception();
241       int test = 0;
242       {
243         SCOPE_EXIT { ++test; };
244         EXPECT_EQ(0, test);
245       }
246       EXPECT_EQ(1, test);
247     } catch (const std::exception& ex) {
248       LOG(FATAL) << "Unexpected exception: " << ex.what();
249     }
250   }
251 };
252
253 TEST(ScopeGuard, TEST_SCOPE_FAILURE2) {
254   try {
255     Foo f;
256     throw std::runtime_error("test");
257   } catch (...) {
258   }
259 }
260
261 int main(int argc, char** argv) {
262   testing::InitGoogleTest(&argc, argv);
263   google::ParseCommandLineFlags(&argc, &argv, true);
264   return RUN_ALL_TESTS();
265 }