1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
// Copyright (C) 2014-2024 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.

#include <config.h>

#include <exceptions/exceptions.h>
#include <asiolink/interval_timer.h>
#include <asiolink/io_service_signal.h>
#include <asiolink/testutils/timed_signal.h>

#include <gtest/gtest.h><--- Include file:  not found. Please note: Cppcheck does not need standard library headers to get proper results.

#include <functional><--- Include file:  not found. Please note: Cppcheck does not need standard library headers to get proper results.
#include <queue><--- Include file:  not found. Please note: Cppcheck does not need standard library headers to get proper results.

using namespace isc::asiolink::test;
namespace ph = std::placeholders;

namespace isc {
namespace asiolink {

/// @brief Test fixture for testing the use of IO service signals.
///
/// This fixture is exercises IO service signaling.
class IOSignalTest : public ::testing::Test {
public:
    /// @brief IOService instance to process IO.
    asiolink::IOServicePtr io_service_;

    /// @brief Failsafe timer to ensure test(s) do not hang.
    isc::asiolink::IntervalTimer test_timer_;

    /// @brief Maximum time should be allowed to run.
    int test_time_ms_;

    /// @brief IOSignalSet object.
    IOSignalSetPtr io_signal_set_;

    /// @brief Vector to record the signal values received.
    std::vector<int> processed_signals_;

    /// @brief The number of signals that must be received to stop the test.
    int stop_at_count_;

    /// @brief Boolean which causes IOSignalHandler to throw if true.
    bool handler_throw_error_;

    /// @brief Constructor.
    IOSignalTest() :
        io_service_(new asiolink::IOService()), test_timer_(io_service_),
        test_time_ms_(0), io_signal_set_(), processed_signals_(), stop_at_count_(0),
        handler_throw_error_(false) {

        io_signal_set_.reset(new IOSignalSet(io_service_,
                                             std::bind(&IOSignalTest::processSignal,
                                                       this, ph::_1)));
    }

    /// @brief Destructor.
    ~IOSignalTest() {
        io_signal_set_.reset();
        // Make sure the cancel handler for the IOSignalSet is called.
        io_service_->poll();
    }

    /// @brief Method used as the IOSignalSet handler.
    ///
    /// Records the value of the given signal and checks if the desired
    /// number of signals have been received.  If so, the IOService is
    /// stopped which will cause IOService::run() to exit, returning control
    /// to the test.
    ///
    /// @param signum signal number.
    void processSignal(int signum) {
        // Remember the signal we got.
        processed_signals_.push_back(signum);

        // If the flag is on, force a throw to test error handling.
        if (handler_throw_error_) {
            handler_throw_error_ = false;
            isc_throw(BadValue, "processSignal throwing simulated error");
        }

        // If we've hit the number we want stop the IOService. This will cause
        // run to exit.
        if (processed_signals_.size() >= stop_at_count_) {
            io_service_->stop();
        }
    }

    /// @brief Sets the failsafe timer for the test to the given time.
    ///
    /// @param  test_time_ms maximum time in milliseconds the test should
    /// be allowed to run.
    void setTestTime(int test_time_ms) {
        // Fail safe shutdown
        test_time_ms_ = test_time_ms;
        test_timer_.setup(std::bind(&IOSignalTest::testTimerHandler, this),
                          test_time_ms_, asiolink::IntervalTimer::ONE_SHOT);
    }

    /// @brief Failsafe timer expiration handler.
    void testTimerHandler() {
        io_service_->stop();
        FAIL() << "Test Time: " << test_time_ms_ << " expired";
    }
};

// Test the basic mechanics of IOSignal by handling one signal occurrence.
TEST_F(IOSignalTest, singleSignalTest) {<--- syntax error
    // Set test fail safe.
    setTestTime(1000);

    // Register to receive SIGINT.
    ASSERT_NO_THROW(io_signal_set_->add(SIGINT));

    // Use TimedSignal to generate SIGINT 100 ms after we start IOService::run.
    TimedSignal sig_int(io_service_, SIGINT, 100);

    // The first handler executed is the IOSignal's internal timer expire
    // callback.
    io_service_->runOne();

    // The next handler executed is IOSignal's handler.
    io_service_->runOne();

    // Polling once to be sure.
    io_service_->poll();

    // Verify that we processed the signal.
    ASSERT_EQ(1, processed_signals_.size());

    // Now check that signal value is correct.
    EXPECT_EQ(SIGINT, processed_signals_[0]);

    // Set test fail safe.
    setTestTime(1000);

    // Unregister the receive of SIGINT.
    ASSERT_NO_THROW(io_signal_set_->remove(SIGINT));

    // Use TimedSignal to generate SIGINT 100 ms after we start IOService::run.
    TimedSignal sig_int_too_late(io_service_, SIGINT, 100);

    // The first handler executed is the IOSignal's internal timer expire
    // callback.
    io_service_->runOne();

    // The next handler executed is IOSignal's handler.
    io_service_->runOne();

    // Polling once to be sure.
    io_service_->poll();

    // Verify that we did not process the signal.
    ASSERT_EQ(1, processed_signals_.size());
}

// Test verifies that signals can be delivered rapid-fire without falling over.
TEST_F(IOSignalTest, hammer) {
    // Set test fail safe.  We want to allow at least 100 ms per signal,
    // plus a bit more so 6 seconds ought to be enough.
    setTestTime(6000);

    // Register to receive SIGINT.
    ASSERT_NO_THROW(io_signal_set_->add(SIGINT));

    // Stop the test after 50 signals. This allows 100ms+ per signal
    // so even sluggish VMs should handle it.
    stop_at_count_ = 50;

    // User a repeating TimedSignal so we should generate a signal every 1 ms
    // until we hit our stop count.
    TimedSignal sig_int(io_service_, SIGINT, 1,
                        asiolink::IntervalTimer::REPEATING);

    // Start processing IO.  This should continue until we stop either by
    // hitting the stop count or if things go wrong, max test time.
    io_service_->run();

    // Verify we received the expected number of signals.
    EXPECT_EQ(stop_at_count_, processed_signals_.size());

    // Now check that each signal value is correct. This is sort of a silly
    // check but it does ensure things didn't go off the rails somewhere.
    for (int i = 0; i < processed_signals_.size(); ++i) {
        EXPECT_EQ(SIGINT, processed_signals_[i]);
    }
}

// Verifies that handler exceptions are caught.
TEST_F(IOSignalTest, handlerThrow) {
    // Set test fail safe.
    setTestTime(1000);

    // Register to receive SIGINT.
    ASSERT_NO_THROW(io_signal_set_->add(SIGINT));

    // Set the stop after we've done at least 1 all the way through.
    stop_at_count_ = 1;

    // Use TimedSignal to generate SIGINT after we start IOService::run.
    TimedSignal sig_int(io_service_, SIGINT, 100,
                        asiolink::IntervalTimer::REPEATING);

    // Set the test flag to cause the handler to throw an exception.
    handler_throw_error_ = true;

    // Start processing IO.  This should fail with the handler exception.
    ASSERT_NO_THROW(io_service_->run());

    // Verify that the we hit the throw block.  The flag will be false
    // we will have skipped the stop count check so number signals processed
    // is stop_at_count_ + 1.
    EXPECT_FALSE(handler_throw_error_);
    EXPECT_EQ(stop_at_count_ + 1, processed_signals_.size());
}

// Verifies that we can handle a mixed set of signals.
TEST_F(IOSignalTest, mixedSignals) {
    // Set test fail safe.
    setTestTime(1000);

    // Register to receive SIGINT, SIGUSR1, and SIGUSR2.
    ASSERT_NO_THROW(io_signal_set_->add(SIGINT));
    ASSERT_NO_THROW(io_signal_set_->add(SIGUSR1));
    ASSERT_NO_THROW(io_signal_set_->add(SIGUSR2));

    // Stop the IO run once we have received eight signals.
    stop_at_count_ = 8;

    // Since signal order arrival cannot be guaranteed, we'll use
    // explicit one shot signals so we can guarantee how many
    // of each signal we should get.
    TimedSignal sig1(io_service_, SIGINT, 2);
    TimedSignal sig2(io_service_, SIGUSR1, 2);
    TimedSignal sig3(io_service_, SIGINT, 2);
    TimedSignal sig4(io_service_, SIGUSR2, 2);
    TimedSignal sig5(io_service_, SIGINT, 2);
    TimedSignal sig6(io_service_, SIGUSR1, 2);
    TimedSignal sig7(io_service_, SIGINT, 2);
    TimedSignal sig8(io_service_, SIGUSR2, 2);

    // Start processing IO.  This should continue until we stop either by
    // hitting the stop count or if things go wrong, max test time.
    io_service_->run();

    // Verify we received the expected number of signals.
    ASSERT_EQ(stop_at_count_, processed_signals_.size());

    // There is no guarantee that the signals will always be delivered in the
    // order they are raised, but all of them should get delivered.  Loop
    // through and tally them up.
    int sigint_cnt = 0;
    int sigusr1_cnt = 0;
    int sigusr2_cnt = 0;
    for (int i = 0; i < stop_at_count_; ++i) {
        switch (processed_signals_[i]) {
        case SIGINT:
            ++sigint_cnt;
            break;
        case SIGUSR1:
            ++sigusr1_cnt;
            break;
        case SIGUSR2:
            ++sigusr2_cnt;
            break;
        default:
            FAIL() << "Invalid signal value: "
                   << processed_signals_[i]
                   << " at i:" << i;
            break;
        }
    }

    // See if our counts are correct.
    EXPECT_EQ(sigint_cnt, 4);
    EXPECT_EQ(sigusr1_cnt, 2);
    EXPECT_EQ(sigusr2_cnt, 2);
}

} // namespace asiolink
} // namespace isc