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:


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();
    }