Search code examples
c++unit-testingc++11c++-chronosystemtime

Are there facilities in std::chrono to assist with injecting system_clock for unit testing


I depend on hardware that may or may not respond. As a consequence I frequently end up writing functions with timeouts. System time is a known source for brittle unit tests so injecting a controlled and stable time seems like a good idea for testing.

I wonder if there are any facilities in std::chrono that help with that. The alternative I see is to write a wrapper around the system time and depend on that adapter.

Here is a minimal example of how a wrapper could look like.

#pragma once
#include <memory>
#include <chrono>
#include <thread>
#include <iostream>

using std::chrono::system_clock;
using std::chrono::milliseconds;
using std::shared_ptr;
using std::make_shared;

class Wrapped_Clock
{
public:
    virtual system_clock::time_point Now() { return system_clock::now(); }
    virtual void Sleep(milliseconds ms) { std::this_thread::sleep_for(ms); }
};

class Mock_Clock : public Wrapped_Clock
{
private:
    system_clock::time_point now;
public:
    Mock_Clock() : now(system_clock::now()){}
    ~Mock_Clock() {}
    system_clock::time_point Now() { return now; }
    void Sleep(milliseconds ms) { }
};

class CanTimeOut
{
private:
    shared_ptr<Wrapped_Clock> sclock;
public:
    CanTimeOut(shared_ptr<Wrapped_Clock> sclock = make_shared<Wrapped_Clock>()) : sclock(sclock) {}
    ~CanTimeOut() {}

    milliseconds TimeoutAction(milliseconds maxtime)
    {
        using std::chrono::duration_cast;
        int x = 0;
        system_clock::time_point start = sclock->Now();
        system_clock::time_point timeout = sclock->Now() + maxtime;
        while (timeout > sclock->Now() && x != 2000)
        {
            sclock->Sleep(milliseconds(1));
            ++x;
        }
        milliseconds elapsed = duration_cast<milliseconds>(sclock->Now() - start);
        return elapsed;
    }

};

#define EXPECT_GE(left, right, test) \
{ if (!(left >= right)) { \
    std::cout << #test << " " << "!(" << left << " >= " << right << ")" << std::endl; \
} }

#define EXPECT_EQ(expected, actual, test) \
{ if (!(expected == actual)) { \
    std::cout << #test << " " << "!(" << expected << " == " << actual << ")" << std::endl; \
} }

void TestWithSystemClock()
{
    CanTimeOut cto;
    long long timeout = 1000;
    milliseconds actual = cto.TimeoutAction(milliseconds(timeout));
    EXPECT_GE(actual.count(), timeout, TestWithSystemClock);
}

void TestWithMockClock()
{
    CanTimeOut cto(make_shared<Mock_Clock>());
    milliseconds actual = cto.TimeoutAction(milliseconds(1000));
    EXPECT_EQ(0, actual.count(), TestWithMockClock);
}

int main()
{
    TestWithSystemClock();
    TestWithMockClock();
}

How much of this can be replaced with functionality from std::chrone?

Edit 1:

  • "What exactly are you testing?" I am controlling time as a test condition to change the behaviour of method calls that depend on time. The Test illustrates that mocking the time and controlling behaviour as a concept works and shows my understanding of it. The point of the minimal example is to show my understanding of mocking time to make it easier to show the differences to the std:: facilities.
  • "spend ~10 words saying what the tests should contrast." The one Test always times out. The other Test shows no passage of time. A third Test that controls an exact and non zero passage of time was not included.
  • "Besides, sleep has nothing to do with the clock. It's not a chrono feature." I needed it to ensure that the one test never loops more than a certain amount before timing out, this simulates some action that takes time and can time out. On the other hand I wanted to build in a shortcut so the second test does not waste time waiting. It would be ok to not mock Sleep as well but the test would take 2 seconds. I recognize the point that Sleep is not a chrono feature and therefor misleading.

Solution

  • It looks, instead, that you are mocking std::this_thread::sleep.

    That's a bit trickier, because it's a namespace with just free functions. It's hard to "inject" a namespace for testing purposes. So, you should, indeed, wrap the functions from that namespace with your own type.

    I'd use static dependency injection, à la C++:

    Live On Coliru

    #include <memory>
    #include <chrono>
    #include <thread>
    #include <iostream>
    
    using std::chrono::system_clock;
    using std::chrono::milliseconds;
    
    struct production {
        using clock = std::chrono::system_clock;
    
        struct this_thread {
            template<typename... A> static auto sleep_for(A&&... a) { return std::this_thread::sleep_for(std::forward<A>(a)...); }
            template<typename... A> static auto sleep_until(A&&... a) { return std::this_thread::sleep_until(std::forward<A>(a)...); }
        };
    };
    
    struct mock {
        struct clock : std::chrono::system_clock {
            using base_type = std::chrono::system_clock;
            static time_point now() { static auto onetime = base_type::now(); return onetime; }
        };
    
        struct this_thread {
            template<typename... A> static auto sleep_for(A&&... a) {}
            template<typename... A> static auto sleep_until(A&&... a) {}
        };
    };
    
    template <typename services = production,
             typename clock = typename services::clock,
             typename this_thread = typename services::this_thread>
    class CanTimeOut
    {
    public:
        milliseconds TimeoutAction(milliseconds maxtime)
        {
            using std::chrono::duration_cast;
    
            int x = 0;
            auto start   = clock::now();
            auto timeout = clock::now() + maxtime;
            while (timeout > clock::now() && x != 2000)
            {
                this_thread::sleep_for(milliseconds(1));
                ++x;
            }
            milliseconds elapsed = duration_cast<milliseconds>(clock::now() - start);
            return elapsed;
        }
    
    };
    
    #define EXPECT_GE(left, right, test) \
    { if (!(left >= right)) { \
        std::cout << #test << " " << "!(" << left << " >= " << right << ")" << std::endl; \
    } }
    
    #define EXPECT_EQ(expected, actual, test) \
    { if (!(expected == actual)) { \
        std::cout << #test << " " << "!(" << expected << " == " << actual << ")" << std::endl; \
    } }
    
    void TestWithSystemClock()
    {
        CanTimeOut<> cto;
        long long timeout = 1000;
        milliseconds actual = cto.TimeoutAction(milliseconds(timeout));
        EXPECT_GE(actual.count(), timeout, TestWithSystemClock);
    }
    
    void TestWithMockClock()
    {
        CanTimeOut<mock> cto;
        milliseconds actual = cto.TimeoutAction(milliseconds(1000));
        EXPECT_EQ(0, actual.count(), TestWithMockClock);
    }
    
    int main()
    {
        TestWithSystemClock();
        TestWithMockClock();
    }