watched_thread_unittest.cc 7.21 KB
Newer Older
1
// Copyright (C) 2018-2019 Internet Systems Consortium, Inc. ("ISC")
2 3 4 5 6 7 8
//
// 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>

9
#include <util/watched_thread.h>
10 11 12 13 14

#include <boost/bind.hpp>
#include <gtest/gtest.h>

#include <unistd.h>
15
#include <atomic>
16 17 18 19 20 21 22

using namespace std;
using namespace isc;
using namespace isc::util;

namespace {

23
/// @brief Test Fixture for testing @c isc::util::WatchedThread
24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
class WatchedThreadTest : public ::testing::Test {
public:
    /// @brief Maximum number of passes allowed in worker event loop
    static const int WORKER_MAX_PASSES;

    /// @brief Constructor.
    WatchedThreadTest() {}

    /// @brief Destructor.
    ~WatchedThreadTest() {
    }

    /// @brief Sleeps for a given number of event periods sleep
    /// Each period is 50 ms.
    void nap(int periods) {
39
        usleep(periods * 50 * 1000);
40 41 42 43
    };

    /// @brief Worker function to be used by the WatchedThread's thread
    ///
44
    /// The function runs 10 passes through an "event" loop.
45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61
    /// On each pass:
    /// - check terminate command
    /// - instigate the desired event (second pass only)
    /// - naps for 1 period (50ms)
    ///
    /// @param watch_type type of event that should occur
    void worker(WatchedThread::WatchType watch_type) {
        for (passes_ = 1; passes_ < WORKER_MAX_PASSES; ++passes_) {

            // Stop if we're told to do it.
            if (wthread_->shouldTerminate()) {
                return;
            }

            // On the second pass, set the event.
            if (passes_ == 2) {
                switch (watch_type) {
62
                case WatchedThread::ERROR:
63 64
                    wthread_->setError("we have an error");
                    break;
65
                case WatchedThread::READY:
66 67
                    wthread_->markReady(watch_type);
                    break;
68
                case WatchedThread::TERMINATE:
69 70 71 72 73 74 75 76 77 78 79 80 81 82
                default:
                    // Do nothing, we're waiting to be told to stop.
                    break;
                }
            }

            // Take a nap.
            nap(1);
        }

        // Indicate why we stopped.
        wthread_->setError("thread expired");
    }

83
    /// @brief Current WatchedThread instance.
84 85 86 87
    WatchedThreadPtr wthread_;

    /// @brief Counter used to track the number of passes made
    /// within the thread worker function.
88
    std::atomic<int> passes_;
89 90
};

91
const int WatchedThreadTest::WORKER_MAX_PASSES = 10;
92 93 94 95

/// Verifies the basic operation of the WatchedThread class.
/// It checks that a WatchedThread can be created, can be stopped,
/// and that in set and clear sockets.
96
TEST_F(WatchedThreadTest, watchedThreadClassBasics) {
97

98 99 100
    /// We'll create a WatchedThread and let it run until it expires.  (Note this is more
    /// of a test of WatchedThreadTest itself and ensures that the assumptions made in
    /// our other tests as to why threads have finished are sound.
101 102
    wthread_.reset(new WatchedThread());
    ASSERT_FALSE(wthread_->isRunning());
103
    wthread_->start(boost::bind(&WatchedThreadTest::worker, this, WatchedThread::TERMINATE));
104 105
    ASSERT_TRUE(wthread_->isRunning());

106 107
    // Wait more long enough (we hope) for the thread to expire.
    nap(WORKER_MAX_PASSES * 4);
108 109 110 111 112

    // It should have done the maximum number of passes.
    EXPECT_EQ(passes_, WORKER_MAX_PASSES);

    // Error should be ready and error text should be "thread expired".
113 114 115
    ASSERT_TRUE(wthread_->isReady(WatchedThread::ERROR));
    ASSERT_FALSE(wthread_->isReady(WatchedThread::READY));
    ASSERT_FALSE(wthread_->isReady(WatchedThread::TERMINATE));
116 117 118 119 120 121 122 123
    EXPECT_EQ("thread expired", wthread_->getLastError());

    // Thread is technically still running, so let's stop it.
    EXPECT_TRUE(wthread_->isRunning());
    ASSERT_NO_THROW(wthread_->stop());
    ASSERT_FALSE(wthread_->isRunning());

    /// Now we'll test stopping a thread.
124 125
    /// Start the WatchedThread, let it run a little and then tell it to stop.
    wthread_->start(boost::bind(&WatchedThreadTest::worker, this, WatchedThread::TERMINATE));
126 127 128
    ASSERT_TRUE(wthread_->isRunning());

    // No watches should be ready.
129 130 131
    ASSERT_FALSE(wthread_->isReady(WatchedThread::ERROR));
    ASSERT_FALSE(wthread_->isReady(WatchedThread::READY));
    ASSERT_FALSE(wthread_->isReady(WatchedThread::TERMINATE));
132 133

    // Wait a little while.
134
    nap(3);
135 136 137 138 139 140 141 142 143

    // Tell it to stop.
    wthread_->stop();
    ASSERT_FALSE(wthread_->isRunning());

    // It should have done less than the maximum number of passes.
    EXPECT_LT(passes_, WORKER_MAX_PASSES);

    // No watches should be ready.  Error text should be "thread stopped".
144 145 146
    ASSERT_FALSE(wthread_->isReady(WatchedThread::ERROR));
    ASSERT_FALSE(wthread_->isReady(WatchedThread::READY));
    ASSERT_FALSE(wthread_->isReady(WatchedThread::TERMINATE));
147 148 149 150
    EXPECT_EQ("thread stopped", wthread_->getLastError());


    // Next we'll test error notification.
151 152
    // Start the WatchedThread with a thread that sets an error on the second pass.
    wthread_->start(boost::bind(&WatchedThreadTest::worker, this, WatchedThread::ERROR));
153 154 155
    ASSERT_TRUE(wthread_->isRunning());

    // No watches should be ready.
156 157 158
    ASSERT_FALSE(wthread_->isReady(WatchedThread::ERROR));
    ASSERT_FALSE(wthread_->isReady(WatchedThread::READY));
    ASSERT_FALSE(wthread_->isReady(WatchedThread::TERMINATE));
159 160

    // Wait a little while.
161
    nap(6);
162 163

    // It should now indicate an error.
164
    ASSERT_TRUE(wthread_->isReady(WatchedThread::ERROR));
165 166 167 168 169 170 171 172 173 174
    EXPECT_EQ("we have an error", wthread_->getLastError());

    // Tell it to stop.
    wthread_->stop();
    ASSERT_FALSE(wthread_->isRunning());

    // It should have done less than the maximum number of passes.
    EXPECT_LT(passes_, WORKER_MAX_PASSES);

    // No watches should be ready.  Error text should be "thread stopped".
175 176 177
    ASSERT_FALSE(wthread_->isReady(WatchedThread::ERROR));
    ASSERT_FALSE(wthread_->isReady(WatchedThread::READY));
    ASSERT_FALSE(wthread_->isReady(WatchedThread::TERMINATE));
178 179 180 181
    EXPECT_EQ("thread stopped", wthread_->getLastError());


    // Finally, we'll test data ready notification.
182 183
    // We'll start the WatchedThread with a thread that indicates data ready on its second pass.
    wthread_->start(boost::bind(&WatchedThreadTest::worker, this, WatchedThread::READY));
184 185 186
    ASSERT_TRUE(wthread_->isRunning());

    // No watches should be ready.
187 188 189
    ASSERT_FALSE(wthread_->isReady(WatchedThread::ERROR));
    ASSERT_FALSE(wthread_->isReady(WatchedThread::READY));
    ASSERT_FALSE(wthread_->isReady(WatchedThread::TERMINATE));
190 191

    // Wait a little while.
192
    nap(6);
193 194

    // It should now indicate data ready.
195
    ASSERT_TRUE(wthread_->isReady(WatchedThread::READY));
196 197 198 199 200 201 202 203 204

    // Tell it to stop.
    wthread_->stop();
    ASSERT_FALSE(wthread_->isRunning());

    // It should have done less than the maximum number of passes.
    EXPECT_LT(passes_, WORKER_MAX_PASSES);

    // No watches should be ready.  Error text should be "thread stopped".
205 206 207
    ASSERT_FALSE(wthread_->isReady(WatchedThread::ERROR));
    ASSERT_FALSE(wthread_->isReady(WatchedThread::READY));
    ASSERT_FALSE(wthread_->isReady(WatchedThread::TERMINATE));
208 209 210 211
    EXPECT_EQ("thread stopped", wthread_->getLastError());
}

}