OpenVPN 3 Core Library
Loading...
Searching...
No Matches
test_cleanup.cpp
Go to the documentation of this file.
1#include "test_common.hpp"
2
4
5#include <memory>
6#include <stdexcept>
7#include <vector>
8
9using namespace openvpn;
10
12{
13 bool ran_cleanup = false;
14 {
15 auto c = Cleanup([&]()
16 { ran_cleanup = true; });
17 static_assert(std::is_nothrow_move_constructible<decltype(c)>::value,
18 "Cleanup should be noexcept MoveConstructible");
19 }
20 ASSERT_TRUE(ran_cleanup) << "cleanup didn't run as expected";
21}
22
23TEST(misc, cleanup_basic_execution)
24{
25 int counter = 0;
26 {
27 auto c = Cleanup([&counter]()
28 { ++counter; });
29 }
30 ASSERT_EQ(counter, 1) << "cleanup should execute once on scope exit";
31}
32
33TEST(misc, cleanup_multiple_guards)
34{
35 std::vector<int> execution_order;
36 {
37 auto c1 = Cleanup([&]()
38 { execution_order.push_back(1); });
39 auto c2 = Cleanup([&]()
40 { execution_order.push_back(2); });
41 auto c3 = Cleanup([&]()
42 { execution_order.push_back(3); });
43 }
44 ASSERT_EQ(execution_order.size(), 3);
45 // Destructors run in reverse order
46 ASSERT_EQ(execution_order[0], 3);
47 ASSERT_EQ(execution_order[1], 2);
48 ASSERT_EQ(execution_order[2], 1);
49}
50
51TEST(misc, cleanup_dismiss)
52{
53 int counter = 0;
54 {
55 auto c = Cleanup([&counter]()
56 { ++counter; });
57 c.dismiss();
58 }
59 ASSERT_EQ(counter, 0) << "cleanup should not execute after dismiss()";
60}
61
62TEST(misc, cleanup_dismiss_idempotent)
63{
64 int counter = 0;
65 {
66 auto c = Cleanup([&counter]()
67 { ++counter; });
68 c.dismiss();
69 c.dismiss(); // Should be safe to call multiple times
70 c.dismiss();
71 }
72 ASSERT_EQ(counter, 0) << "multiple dismiss() calls should be safe";
73}
74
75TEST(misc, cleanup_release)
76{
77 int counter = 0;
78 std::optional<std::function<void()>> released;
79 {
80 auto c = Cleanup([&counter]()
81 { ++counter; });
82 released = c.release();
83 }
84 ASSERT_EQ(counter, 0) << "cleanup should not execute after release()";
85 ASSERT_TRUE(released.has_value()) << "release() should return the callable";
86
87 // Manually invoke the released callable
88 std::invoke(*released);
89 ASSERT_EQ(counter, 1) << "released callable should still be invocable";
90}
91
92TEST(misc, cleanup_release_empty)
93{
94 int counter = 0;
95 std::optional<std::function<void()>> released;
96 {
97 auto c = Cleanup([&counter]()
98 { ++counter; });
99 c.dismiss();
100 released = c.release();
101 }
102 ASSERT_EQ(counter, 0) << "cleanup should not execute";
103 ASSERT_FALSE(released.has_value()) << "release() after dismiss() should return empty optional";
104}
105
106TEST(misc, cleanup_move_constructor)
107{
108 int counter = 0;
109 {
110 auto c1 = Cleanup([&counter]()
111 { ++counter; });
112 auto c2 = std::move(c1);
113 // c1 is now moved-from, c2 owns the cleanup
114 }
115 ASSERT_EQ(counter, 1) << "cleanup should execute once from moved-to object";
116}
117
118TEST(misc, cleanup_move_constructor_no_double_execute)
119{
120 int counter = 0;
121 {
122 auto c1 = Cleanup([&counter]()
123 { ++counter; });
124 {
125 auto c2 = std::move(c1);
126 } // c2 destroyed here, should execute
127 } // c1 destroyed here, should NOT execute
128 ASSERT_EQ(counter, 1) << "cleanup should execute only once after move";
129}
130
131TEST(misc, cleanup_exception_swallowed)
132{
133 bool cleanup_started = false;
134 try
135 {
136 auto c = Cleanup([&]()
137 {
138 cleanup_started = true;
139 throw std::runtime_error("exception in cleanup"); });
140 // Destructor runs here when c goes out of scope
141 }
142 catch (...)
143 {
144 // Exception should NOT escape destructor
145 FAIL() << "Exception should be swallowed by noexcept destructor";
146 }
147 ASSERT_TRUE(cleanup_started) << "cleanup should start executing";
148}
149
150TEST(misc, cleanup_noexcept_destructor)
151{
152 // Verify destructor is noexcept
153 auto c = Cleanup([]() {});
154 static_assert(std::is_nothrow_destructible_v<decltype(c)>, "Destructor should be noexcept");
155}
156
157TEST(misc, cleanup_with_mutable_lambda)
158{
159 int counter = 0;
160 {
161 auto c = Cleanup([&counter, call_count = 0]() mutable
162 {
163 ++call_count;
164 counter = call_count; });
165 }
166 ASSERT_EQ(counter, 1) << "mutable lambda should work correctly";
167}
168
169TEST(misc, cleanup_with_captured_unique_ptr)
170{
171 bool deleted = false;
172 struct Deleter
173 {
174 bool *flag;
175 void operator()(int *p)
176 {
177 *flag = true;
178 delete p;
179 }
180 };
181 {
182 std::unique_ptr<int, Deleter> ptr(new int(42), Deleter{&deleted});
183 auto c = Cleanup([p = std::move(ptr)]() mutable
184 { p.reset(); });
185 }
186 ASSERT_TRUE(deleted) << "unique_ptr should be properly moved and deleted";
187}
188
189TEST(misc, cleanup_concept_constraint)
190{
191 // Should compile with invocable types
192 auto c1 = Cleanup([]() {});
193 auto c2 = Cleanup(std::function<void()>([]() {}));
194
195 struct Callable
196 {
197 void operator()() const
198 {
199 }
200 };
201 auto c3 = Cleanup(Callable{});
202
203 // Should NOT compile with non-invocable types (uncomment to test)
204 // auto c4 = Cleanup(42); // Error: does not satisfy std::invocable
205}
206
207TEST(misc, cleanup_dismiss_then_destroy)
208{
209 int counter = 0;
210 {
211 auto c = Cleanup([&counter]()
212 { ++counter; });
213 c.dismiss();
214 // Additional operations after dismiss
215 ASSERT_EQ(counter, 0);
216 }
217 ASSERT_EQ(counter, 0) << "dismissed cleanup should not execute on destruction";
218}
219
220TEST(misc, cleanup_factory_function)
221{
222 int counter = 0;
223 {
224 auto c = Cleanup([&counter]()
225 { ++counter; });
226 // Just verify it compiles and works - type checking is implementation detail
227 }
228 ASSERT_EQ(counter, 1);
229}
230
231TEST(misc, cleanup_return_from_function)
232{
233 int counter = 0;
234 auto make_cleanup = [&counter]()
235 {
236 return Cleanup([&counter]()
237 { ++counter; });
238 };
239 {
240 auto c = make_cleanup();
241 }
242 ASSERT_EQ(counter, 1) << "cleanup returned from function should work";
243}
244
245TEST(misc, cleanup_nested_scopes)
246{
247 std::vector<int> order;
248 {
249 auto c1 = Cleanup([&]()
250 { order.push_back(1); });
251 {
252 auto c2 = Cleanup([&]()
253 { order.push_back(2); });
254 {
255 auto c3 = Cleanup([&]()
256 { order.push_back(3); });
257 }
258 }
259 }
260 ASSERT_EQ(order.size(), 3);
261 ASSERT_EQ(order[0], 3);
262 ASSERT_EQ(order[1], 2);
263 ASSERT_EQ(order[2], 1);
264}
265
266TEST(misc, cleanup_conditional_dismiss)
267{
268 int counter = 0;
269 bool should_cleanup = false;
270 {
271 auto c = Cleanup([&counter]()
272 { ++counter; });
273 if (!should_cleanup)
274 {
275 c.dismiss();
276 }
277 }
278 ASSERT_EQ(counter, 0);
279
280 should_cleanup = true;
281 {
282 auto c = Cleanup([&counter]()
283 { ++counter; });
284 if (!should_cleanup)
285 {
286 c.dismiss();
287 }
288 }
289 ASSERT_EQ(counter, 1);
290}
CleanupType< F > Cleanup(F method) noexcept(std::is_nothrow_move_constructible_v< F >)
Factory function to create a CleanupType object.
Definition cleanup.hpp:124
TEST(Misc, Cleanup)