// Copyright (C) 2015-2023 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 #include #include #include #include #include namespace { using namespace isc; using namespace isc::util; using namespace boost::posix_time; /// @brief @c StopwatchImpl mock object. /// /// This class derives from the @c StopwatchImpl to override the /// @c StopwatchImpl::getCurrentTime. This method is internally called by /// the @c StopwatchImpl to determine the current time. By providing the /// implementation of this method which returns the fixed (well known) /// timestamp value we can obtain the deterministic values from the accessors /// of this class. /// /// This class also includes some convenience methods to return the time /// durations in milliseconds. class StopwatchMock : public StopwatchImpl { public: /// @brief Constructor. /// /// @param ref_time Reference time, i.e. the arbitrary time value from /// which time is measured. The @c current_time_ value returned by the /// @c StopwatchMock::getCurrentTime is initialized to this value. /// Subsequent calls to the @c StopwatchMock::ffwd move the value of /// the @c current_time_ forward. StopwatchMock(const ptime& ref_time); /// @brief Fast forward time. /// /// Moves the value of the @c current_time_ forward by the specified /// number of milliseconds (microseconds). As a result the timestamp /// returned by the @c StopwatchMock::getCurrentTime moves by this value. /// This simulates the time progress. /// /// @param ms Specifies the number of milliseconds to move current time. /// @param us Specifies the number of fractional microseconds to move /// current time. void ffwd(const uint32_t ms, const uint32_t us = 0); /// @brief Returns the last duration in milliseconds. uint32_t getLastDurationInMs() const; /// @brief Returns the total duration in milliseconds. uint32_t getTotalDurationInMs() const; protected: /// @brief Returns the current time. /// /// This method returns the fixed @c current_time_ timestamp. virtual ptime getCurrentTime() const; private: /// @brief Holds the current time to be returned by the /// @c StopwatchMock::getCurrentTime. ptime current_time_; }; StopwatchMock::StopwatchMock(const ptime& ref_time) : StopwatchImpl(), current_time_(ref_time) { } void StopwatchMock::ffwd(const uint32_t ms, const uint32_t us) { current_time_ += milliseconds(ms); current_time_ += microseconds(us); } uint32_t StopwatchMock::getLastDurationInMs() const { return (getLastDuration().total_milliseconds()); } uint32_t StopwatchMock::getTotalDurationInMs() const { return (getTotalDuration().total_milliseconds()); } ptime StopwatchMock::getCurrentTime() const { return (current_time_); } /// @brief Test fixture class for testing @c StopwatchImpl. class StopwatchTest : public ::testing::Test { public: /// @brief Constructor StopwatchTest() = default; /// @brief Destructor virtual ~StopwatchTest() = default; protected: /// @brief Set up the test. /// /// Initializes the reference time to be used to create the instances /// of the @c StopwatchMock objects. virtual void SetUp(); /// @brief Holds the reference time to be used to create the instances /// of the @c StopwatchMock objects. ptime ref_time_; }; void StopwatchTest::SetUp() { ref_time_ = microsec_clock::universal_time(); } /// This test checks the behavior of the stopwatch when it is started /// and stopped multiple times. It uses the StopwatchMock object to /// control the "time flow" by setting the current time to arbitrary /// values using the StopwatchMock::ffwd. In addition, this test /// checks that the stopwatch can be reset. TEST_F(StopwatchTest, multipleMeasurements) { StopwatchMock stopwatch(ref_time_); // The stopwatch shouldn't automatically start. The initial // durations should be set to 0. EXPECT_EQ(0, stopwatch.getLastDurationInMs()); EXPECT_EQ(0, stopwatch.getTotalDurationInMs()); stopwatch.start(); // Even though the stopwatch is started, the time is still set to // the initial value. The durations should not be affected. EXPECT_EQ(0, stopwatch.getLastDurationInMs()); EXPECT_EQ(0, stopwatch.getTotalDurationInMs()); // Move the time by 10 ms. stopwatch.ffwd(10); // It should be possible to retrieve the durations even when the // stopwatch is running. EXPECT_EQ(10, stopwatch.getLastDurationInMs()); EXPECT_EQ(10, stopwatch.getTotalDurationInMs()); // Now stop it and make sure that the same values are returned. stopwatch.stop(); EXPECT_EQ(10, stopwatch.getLastDurationInMs()); EXPECT_EQ(10, stopwatch.getTotalDurationInMs()); // Start it again, but don't move the time forward yet. stopwatch.start(); // The new duration should be 0, but the total should be equal to // the previously measured duration. EXPECT_EQ(0, stopwatch.getLastDurationInMs()); EXPECT_EQ(10, stopwatch.getTotalDurationInMs()); // Move time by 5 ms. stopwatch.ffwd(5); // New measured duration should be 5 ms. The total should be 15 ms. EXPECT_EQ(5, stopwatch.getLastDurationInMs()); EXPECT_EQ(15, stopwatch.getTotalDurationInMs()); // Stop it again and make sure the values returned are the same. stopwatch.stop(); EXPECT_EQ(5, stopwatch.getLastDurationInMs()); EXPECT_EQ(15, stopwatch.getTotalDurationInMs()); // Move the time forward while the stopwatch is stopped. stopwatch.ffwd(8); // The measured values should not be affected. EXPECT_EQ(5, stopwatch.getLastDurationInMs()); EXPECT_EQ(15, stopwatch.getTotalDurationInMs()); // Stop should be no-op in this case. stopwatch.stop(); EXPECT_EQ(5, stopwatch.getLastDurationInMs()); EXPECT_EQ(15, stopwatch.getTotalDurationInMs()); // Start the stopwatch again. stopwatch.start(); // Move time by 3 ms. stopwatch.ffwd(3); // Since the stopwatch is running, the measured duration should // get updated again. EXPECT_EQ(3, stopwatch.getLastDurationInMs()); EXPECT_EQ(18, stopwatch.getTotalDurationInMs()); // Move the time by 2 ms. stopwatch.ffwd(2); // Start should be no-op in this case. stopwatch.start(); // But the durations should be updated. EXPECT_EQ(5, stopwatch.getLastDurationInMs()); EXPECT_EQ(20, stopwatch.getTotalDurationInMs()); // Make sure we can reset. stopwatch.reset(); EXPECT_EQ(0, stopwatch.getLastDurationInMs()); EXPECT_EQ(0, stopwatch.getTotalDurationInMs()); } // This test checks that the stopwatch works when the real clock is in use. TEST_F(StopwatchTest, realTime) { // Initially, the measured time should be 0. Stopwatch stopwatch; EXPECT_EQ(0, stopwatch.getLastMilliseconds()); EXPECT_EQ(0, stopwatch.getTotalMilliseconds()); // Start the stopwatch. stopwatch.start(); // Sleep for 1 ms. The stopwatch should measure this duration. usleep(1000); stopwatch.stop(); // The measured duration should be greater or equal 1 ms. long current_duration = stopwatch.getLastMilliseconds(); EXPECT_GE(current_duration, 1); EXPECT_EQ(current_duration, stopwatch.getTotalMilliseconds()); // Sleep for another 2 ms while the stopwatch is in the stopped state. usleep(2000); // In the stopped state, we should still have old durations measured. EXPECT_EQ(current_duration, stopwatch.getLastMilliseconds()); EXPECT_EQ(current_duration, stopwatch.getTotalMilliseconds()); // Start it again. stopwatch.start(); // Sleep for 1 ms. usleep(1000); // The durations should get updated as appropriate. EXPECT_GE(stopwatch.getLastMilliseconds(), 1); EXPECT_GE(stopwatch.getTotalMilliseconds(), 2); } // Make sure that we can obtain the durations as microseconds. TEST_F(StopwatchTest, getLastMicroseconds) { Stopwatch stopwatch; stopwatch.start(); usleep(1000); stopwatch.stop(); long current_duration = stopwatch.getLastMicroseconds(); EXPECT_GE(current_duration, 1000); EXPECT_EQ(current_duration, stopwatch.getTotalMicroseconds()); } // Make sure that we can use the "autostart" option to start the time // measurement in the constructor. TEST_F(StopwatchTest, autostart) { Stopwatch stopwatch(true); usleep(1000); stopwatch.stop(); EXPECT_GE(stopwatch.getLastMilliseconds(), 1); EXPECT_EQ(stopwatch.getLastMilliseconds(), stopwatch.getTotalMilliseconds()); } // Make sure that the conversion to the loggable string works as expected. TEST_F(StopwatchTest, logFormat) { time_duration duration = microseconds(223043); EXPECT_EQ("223.043 ms", StopwatchImpl::logFormat(duration)); duration = microseconds(1234); EXPECT_EQ("1.234 ms", StopwatchImpl::logFormat(duration)); duration = microseconds(2000); EXPECT_EQ("2.000 ms", StopwatchImpl::logFormat(duration)); duration = milliseconds(2100); EXPECT_EQ("2.10 s", StopwatchImpl::logFormat(duration)); duration = milliseconds(3123); EXPECT_EQ("3.12 s", StopwatchImpl::logFormat(duration)); } } // end of anonymous namespace