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