Aleph-w 3.0
A C++ Library for Data Structures and Algorithms
Loading...
Searching...
No Matches
ah_signal_test.cc
Go to the documentation of this file.
1
2/*
3 Aleph_w
4
5 Data structures & Algorithms
6 version 2.0.0b
7 https://github.com/lrleon/Aleph-w
8
9 This file is part of Aleph-w library
10
11 Copyright (c) 2002-2026 Leandro Rabindranath Leon
12
13 Permission is hereby granted, free of charge, to any person obtaining a copy
14 of this software and associated documentation files (the "Software"), to deal
15 in the Software without restriction, including without limitation the rights
16 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
17 copies of the Software, and to permit persons to whom the Software is
18 furnished to do so, subject to the following conditions:
19
20 The above copyright notice and this permission notice shall be included in all
21 copies or substantial portions of the Software.
22
23 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
24 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
25 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
26 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
27 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
28 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
29 SOFTWARE.
30*/
31
32
46#include <gtest/gtest.h>
47
48#include <ah-signal.H>
49
50#include <atomic>
51#include <chrono>
52#include <thread>
53#include <type_traits>
54#include <unistd.h>
55
56namespace
57{
58
59// Global flag for signal handlers (must be volatile sig_atomic_t for safety)
60volatile sig_atomic_t g_signal_received = 0;
61std::atomic<int> g_signal_count{0};
62
63void test_handler(int signo)
64{
65 g_signal_received = signo;
66 g_signal_count.fetch_add(1, std::memory_order_relaxed);
67}
68
69void another_handler(int signo)
70{
71 g_signal_received = signo + 1000;
72}
73
74// Reset global state before each test
75class SignalTestFixture : public ::testing::Test
76{
77protected:
78 void SetUp() override
79 {
80 g_signal_received = 0;
81 g_signal_count.store(0, std::memory_order_relaxed);
82 }
83};
84
85} // namespace
86
87// ============================================================================
88// SignalSet Tests
89// ============================================================================
90
91TEST(SignalSet, DefaultConstructorCreatesEmptySet)
92{
93 SignalSet set;
94 EXPECT_FALSE(set.contains(SIGUSR1));
95 EXPECT_FALSE(set.contains(SIGUSR2));
96 EXPECT_FALSE(set.contains(SIGINT));
97}
98
99TEST(SignalSet, InitializerListConstructor)
100{
101 SignalSet set({SIGUSR1, SIGUSR2, SIGTERM});
102
103 EXPECT_TRUE(set.contains(SIGUSR1));
104 EXPECT_TRUE(set.contains(SIGUSR2));
105 EXPECT_TRUE(set.contains(SIGTERM));
106 EXPECT_FALSE(set.contains(SIGINT));
107}
108
109TEST(SignalSet, AddAndRemove)
110{
111 SignalSet set;
112
113 set.add(SIGUSR1);
114 EXPECT_TRUE(set.contains(SIGUSR1));
115
116 set.add(SIGUSR2);
117 EXPECT_TRUE(set.contains(SIGUSR1));
118 EXPECT_TRUE(set.contains(SIGUSR2));
119
120 set.remove(SIGUSR1);
121 EXPECT_FALSE(set.contains(SIGUSR1));
122 EXPECT_TRUE(set.contains(SIGUSR2));
123}
124
125TEST(SignalSet, FluentInterface)
126{
127 SignalSet set;
128 set.add(SIGUSR1).add(SIGUSR2).remove(SIGUSR1);
129
130 EXPECT_FALSE(set.contains(SIGUSR1));
131 EXPECT_TRUE(set.contains(SIGUSR2));
132}
133
134TEST(SignalSet, ClearAndFill)
135{
136 SignalSet set({SIGUSR1, SIGUSR2});
137
138 set.clear();
139 EXPECT_FALSE(set.contains(SIGUSR1));
140 EXPECT_FALSE(set.contains(SIGUSR2));
141
142 set.fill();
143 EXPECT_TRUE(set.contains(SIGUSR1));
144 EXPECT_TRUE(set.contains(SIGUSR2));
145 EXPECT_TRUE(set.contains(SIGINT));
146}
147
148TEST(SignalSet, StaticFactoryMethods)
149{
150 auto empty = SignalSet::empty();
151 EXPECT_FALSE(empty.contains(SIGUSR1));
152
153 auto full = SignalSet::full();
154 EXPECT_TRUE(full.contains(SIGUSR1));
155 EXPECT_TRUE(full.contains(SIGINT));
156}
157
158TEST(SignalSet, GetReturnsValidPointer)
159{
160 SignalSet set({SIGUSR1});
161
162 sigset_t * ptr = set.get();
163 EXPECT_NE(ptr, nullptr);
164 EXPECT_EQ(sigismember(ptr, SIGUSR1), 1);
165}
166
167// ============================================================================
168// Signal Tests
169// ============================================================================
170
171TEST_F(SignalTestFixture, SignalInstallsHandler)
172{
173 {
174 Signal sig(SIGUSR1, test_handler);
175
176 // Send signal to self
177 EXPECT_EQ(raise(SIGUSR1), 0);
178
179 // Give time for signal delivery
180 usleep(1000);
181
182 EXPECT_EQ(g_signal_received, SIGUSR1);
183 }
184}
185
186TEST_F(SignalTestFixture, SignalRestoresPreviousHandler)
187{
188 // First, install a known handler
189 Signal outer(SIGUSR1, test_handler);
190
191 {
192 // Install a different handler in inner scope
193 Signal inner(SIGUSR1, another_handler);
194
195 raise(SIGUSR1);
196 usleep(1000);
197 EXPECT_EQ(g_signal_received, SIGUSR1 + 1000);
198
199 g_signal_received = 0;
200 }
201 // Inner scope ended, original handler should be restored
202
203 raise(SIGUSR1);
204 usleep(1000);
205 EXPECT_EQ(g_signal_received, SIGUSR1);
206}
207
208TEST_F(SignalTestFixture, SignalWithSigIgn)
209{
210 {
211 Signal sig(SIGUSR1, SIG_IGN);
212
213 // Signal should be ignored
214 raise(SIGUSR1);
215 usleep(1000);
216 // No crash means SIG_IGN worked
217 }
218 SUCCEED();
219}
220
221TEST_F(SignalTestFixture, SignalGetters)
222{
223 Signal sig(SIGUSR1, test_handler, false);
224
225 EXPECT_EQ(sig.signal_number(), SIGUSR1);
226 EXPECT_FALSE(sig.restarts_calls());
227 EXPECT_TRUE(sig.is_active());
228}
229
230TEST_F(SignalTestFixture, SignalRelease)
231{
232 {
233 Signal sig(SIGUSR1, test_handler);
234 sig.release();
235 EXPECT_FALSE(sig.is_active());
236 }
237 // Handler should NOT be restored because release() was called
238
239 // The handler should still be test_handler
240 raise(SIGUSR1);
241 usleep(1000);
242 EXPECT_EQ(g_signal_received, SIGUSR1);
243
244 // Clean up: restore default handler
245 signal(SIGUSR1, SIG_DFL);
246}
247
248TEST_F(SignalTestFixture, SignalMoveConstruction)
249{
250 Signal sig1(SIGUSR1, test_handler);
251 EXPECT_TRUE(sig1.is_active());
252
253 Signal sig2(std::move(sig1));
254
255 EXPECT_FALSE(sig1.is_active());
256 EXPECT_TRUE(sig2.is_active());
257 EXPECT_EQ(sig2.signal_number(), SIGUSR1);
258}
259
260TEST_F(SignalTestFixture, SignalMoveAssignment)
261{
262 Signal sig1(SIGUSR1, test_handler);
263 Signal sig2(SIGUSR2, test_handler);
264
265 sig2 = std::move(sig1);
266
267 EXPECT_FALSE(sig1.is_active());
268 EXPECT_TRUE(sig2.is_active());
269 EXPECT_EQ(sig2.signal_number(), SIGUSR1);
270}
271
272TEST_F(SignalTestFixture, SignalCreateThrowsOnInvalidSignal)
273{
274 // Signal 0 is invalid for sigaction
275 EXPECT_THROW(Signal::create(0, test_handler), SignalError);
276}
277
278TEST_F(SignalTestFixture, SignalTryCreateReturnsError)
279{
280 int error = 0;
281 auto sig = Signal::try_create(0, test_handler, true, error);
282
283 EXPECT_NE(error, 0);
284 EXPECT_FALSE(sig.is_active());
285}
286
287TEST_F(SignalTestFixture, SignalCreateSucceeds)
288{
289 auto sig = Signal::create(SIGUSR1, test_handler);
290
291 EXPECT_TRUE(sig.is_active());
292 EXPECT_EQ(sig.signal_number(), SIGUSR1);
293
294 raise(SIGUSR1);
295 usleep(1000);
296 EXPECT_EQ(g_signal_received, SIGUSR1);
297}
298
299TEST_F(SignalTestFixture, SignalPreviousHandler)
300{
301 Signal outer(SIGUSR1, test_handler);
302
303 {
304 Signal inner(SIGUSR1, another_handler);
305 EXPECT_EQ(inner.previous_handler(), test_handler);
306 }
307}
308
309TEST(SignalTypeTraits, NonCopyable)
310{
311 static_assert(!std::is_copy_constructible_v<Signal>);
312 static_assert(!std::is_copy_assignable_v<Signal>);
313}
314
315TEST(SignalTypeTraits, Movable)
316{
317 static_assert(std::is_move_constructible_v<Signal>);
318 static_assert(std::is_move_assignable_v<Signal>);
319}
320
321// ============================================================================
322// SignalBlocker Tests
323// ============================================================================
324
325TEST_F(SignalTestFixture, SignalBlockerBlocksSignal)
326{
327 Signal sig(SIGUSR1, test_handler);
328
329 {
330 SignalBlocker blocker(SIGUSR1);
331
332 // Send signal - it should be blocked
333 raise(SIGUSR1);
334 usleep(1000);
335
336 // Signal should not have been delivered yet
337 EXPECT_EQ(g_signal_received, 0);
338 }
339 // Blocker destroyed, signal should now be delivered
340 usleep(1000);
341 EXPECT_EQ(g_signal_received, SIGUSR1);
342}
343
344TEST_F(SignalTestFixture, SignalBlockerWithInitializerList)
345{
346 Signal sig1(SIGUSR1, test_handler);
347 Signal sig2(SIGUSR2, test_handler);
348
349 {
350 SignalBlocker blocker({SIGUSR1, SIGUSR2});
351
352 raise(SIGUSR1);
353 raise(SIGUSR2);
354 usleep(1000);
355
356 EXPECT_EQ(g_signal_count.load(), 0);
357 }
358
359 usleep(1000);
360 // Both signals should now be delivered
361 EXPECT_GE(g_signal_count.load(), 1); // At least one delivered
362}
363
364TEST_F(SignalTestFixture, SignalBlockerWithSignalSet)
365{
366 Signal sig(SIGUSR1, test_handler);
367
368 SignalSet set;
369 set.add(SIGUSR1);
370
371 {
372 SignalBlocker blocker(set);
373
374 raise(SIGUSR1);
375 usleep(1000);
376
377 EXPECT_EQ(g_signal_received, 0);
378 }
379
380 usleep(1000);
381 EXPECT_EQ(g_signal_received, SIGUSR1);
382}
383
384TEST_F(SignalTestFixture, SignalBlockerRelease)
385{
386 Signal sig(SIGUSR1, test_handler);
387
388 {
389 SignalBlocker blocker(SIGUSR1);
390 blocker.release();
391
392 raise(SIGUSR1);
393 usleep(1000);
394
395 // Signal still blocked (release only affects destructor)
396 EXPECT_EQ(g_signal_received, 0);
397 }
398 // After destruction, mask should NOT be restored because release() was called
399 // The signal might still be blocked
400
401 // Manually unblock to clean up
402 SignalSet empty;
403 sigprocmask(SIG_SETMASK, empty.get(), nullptr);
404}
405
406TEST_F(SignalTestFixture, SignalBlockerMoveConstruction)
407{
408 Signal sig(SIGUSR1, test_handler);
409
410 SignalBlocker blocker1(SIGUSR1);
411 SignalBlocker blocker2(std::move(blocker1));
412
413 // blocker1 should no longer be active
414 raise(SIGUSR1);
415 usleep(1000);
416 EXPECT_EQ(g_signal_received, 0); // Still blocked by blocker2
417}
418
419TEST(SignalBlockerTypeTraits, NonCopyable)
420{
421 static_assert(!std::is_copy_constructible_v<SignalBlocker>);
422 static_assert(!std::is_copy_assignable_v<SignalBlocker>);
423}
424
425TEST(SignalBlockerTypeTraits, Movable)
426{
427 static_assert(std::is_move_constructible_v<SignalBlocker>);
428 static_assert(std::is_move_assignable_v<SignalBlocker>);
429}
430
431// ============================================================================
432// SignalError Tests
433// ============================================================================
434
435TEST(SignalError, ContainsSignalInfo)
436{
437 SignalError err("Test error", SIGUSR1, EINVAL);
438
439 EXPECT_EQ(err.signal_number(), SIGUSR1);
440 EXPECT_EQ(err.error_code(), EINVAL);
441 EXPECT_STREQ(err.what(), "Test error");
442}
443
444// ============================================================================
445// Utility Function Tests
446// ============================================================================
447
448TEST(SignalUtilities, SignalName)
449{
450 EXPECT_EQ(signal_name(SIGINT), "SIGINT");
451 EXPECT_EQ(signal_name(SIGTERM), "SIGTERM");
452 EXPECT_EQ(signal_name(SIGUSR1), "SIGUSR1");
453 EXPECT_EQ(signal_name(SIGUSR2), "SIGUSR2");
454 EXPECT_EQ(signal_name(SIGKILL), "SIGKILL");
455}
456
457TEST(SignalUtilities, SendSignalToSelf)
458{
459 g_signal_received = 0;
460 Signal sig(SIGUSR1, test_handler);
461
462 EXPECT_TRUE(send_signal_to_self(SIGUSR1));
463 usleep(1000);
464 EXPECT_EQ(g_signal_received, SIGUSR1);
465}
466
467// ============================================================================
468// Backward Compatibility Tests
469// ============================================================================
470
471TEST_F(SignalTestFixture, BackwardCompatibleConstructor)
472{
473 // Original constructor signature used const int & and const bool &
474 const int signo = SIGUSR1;
475 const bool restart = true;
476
477 Signal sig(signo, test_handler, restart);
478
479 EXPECT_EQ(sig.signal_number(), SIGUSR1);
480 EXPECT_TRUE(sig.restarts_calls());
481
482 raise(SIGUSR1);
483 usleep(1000);
484 EXPECT_EQ(g_signal_received, SIGUSR1);
485}
486
487TEST_F(SignalTestFixture, BackwardCompatibleDefaultRestartCalls)
488{
489 // Default for restart_calls should be true
490 Signal sig(SIGUSR1, test_handler);
491
492 EXPECT_TRUE(sig.restarts_calls());
493}
494
495// ============================================================================
496// Integration Tests
497// ============================================================================
498
499TEST_F(SignalTestFixture, NestedSignalHandlers)
500{
501 std::vector<int> received_order;
502
503 auto handler1 = [](int) { g_signal_received = 1; };
504 auto handler2 = [](int) { g_signal_received = 2; };
505 auto handler3 = [](int) { g_signal_received = 3; };
506
507 {
508 Signal sig1(SIGUSR1, handler1);
509 raise(SIGUSR1);
510 usleep(1000);
511 EXPECT_EQ(g_signal_received, 1);
512
513 {
514 Signal sig2(SIGUSR1, handler2);
515 raise(SIGUSR1);
516 usleep(1000);
517 EXPECT_EQ(g_signal_received, 2);
518
519 {
520 Signal sig3(SIGUSR1, handler3);
521 raise(SIGUSR1);
522 usleep(1000);
523 EXPECT_EQ(g_signal_received, 3);
524 }
525
526 raise(SIGUSR1);
527 usleep(1000);
528 EXPECT_EQ(g_signal_received, 2); // Back to handler2
529 }
530
531 raise(SIGUSR1);
532 usleep(1000);
533 EXPECT_EQ(g_signal_received, 1); // Back to handler1
534 }
535}
536
537int main(int argc, char ** argv)
538{
539 ::testing::InitGoogleTest(&argc, argv);
540 return RUN_ALL_TESTS();
541}
POSIX signal handling utilities with RAII semantics.
bool send_signal_to_self(int signo) noexcept
Sends a signal to the current process.
Definition ah-signal.H:652
std::string signal_name(int signo)
Returns the name of a signal.
Definition ah-signal.H:665
int main()
TEST_F(SignalTestFixture, SignalInstallsHandler)
RAII wrapper for temporarily blocking signals.
Definition ah-signal.H:233
void release() noexcept
Releases ownership - the mask won't be restored on destruction.
Definition ah-signal.H:273
Exception thrown when a signal operation fails.
Definition ah-signal.H:96
int signal_number() const noexcept
Returns the signal number involved in the error.
Definition ah-signal.H:111
int error_code() const noexcept
Returns the errno value at the time of failure.
Definition ah-signal.H:114
C++ wrapper for sigset_t with a fluent interface.
Definition ah-signal.H:133
SignalSet & clear() noexcept
Clears the set (removes all signals).
Definition ah-signal.H:177
static SignalSet full() noexcept
Creates a full signal set (all signals).
Definition ah-signal.H:149
static SignalSet empty() noexcept
Creates an empty signal set.
Definition ah-signal.H:157
sigset_t * get() noexcept
Returns a pointer to the underlying sigset_t.
Definition ah-signal.H:198
bool contains(int signo) const noexcept
Checks if a signal is in the set.
Definition ah-signal.H:192
SignalSet & add(int signo) noexcept
Adds a signal to the set.
Definition ah-signal.H:161
SignalSet & remove(int signo) noexcept
Removes a signal from the set.
Definition ah-signal.H:169
RAII wrapper for POSIX signal handler registration.
Definition ah-signal.H:354
int signal_number() const noexcept
Returns the signal number being handled.
Definition ah-signal.H:553
bool restarts_calls() const noexcept
Returns whether SA_RESTART is enabled for this handler.
Definition ah-signal.H:556
void release() noexcept
Releases ownership of the signal handler.
Definition ah-signal.H:550
static Signal try_create(int signo, Handler func, bool restart_calls, int &error_out)
Attempts to create a Signal object.
Definition ah-signal.H:503
bool is_active() const noexcept
Returns whether this object will restore the handler on destruction.
Definition ah-signal.H:559
static Signal create(int signo, Handler func, bool restart_calls=true)
Creates a Signal object or throws on failure.
Definition ah-signal.H:470
Handler previous_handler() const noexcept
Returns the previous handler that will be restored.
Definition ah-signal.H:562
#define TEST(name)