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