Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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.
like image 718
Johannes Avatar asked Nov 09 '15 09:11

Johannes


2 Answers

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();
}
like image 61
sehe Avatar answered Oct 07 '22 21:10

sehe


Another way to handle this is to define a simulated clock and specify the type of clock to be used as a template parameter.

#include <chrono>
#include <iostream>
#include <thread>

#include "sim_clock.hpp"

using namespace std::chrono;

template <typename clock_t> void Sleep(milliseconds ms)
{
    std::this_thread::sleep_for(ms);
}

template <> void Sleep<sim_clock>(milliseconds ms)
{
    sim_clock::increment_by(ms);
}

template <typename clock_t = std::chrono::steady_clock> class CanTimeOut
{
  public:
    CanTimeOut() = default;
    ~CanTimeOut() = default;

    milliseconds TimeoutAction(milliseconds maxtime)
    {
        int x = 0;
        auto start = clock_t::now();
        auto timeout = start + maxtime;
        while(timeout > clock_t::now()) { Sleep<clock_t>(milliseconds(1)); }
        return duration_cast<milliseconds>(clock_t::now() - start);
    }
};

#define EXPECT_GE(left, right, test)                                           \
    {                                                                          \
        if(!(left >= right)) {                                                 \
            std::cout << #test << " "                                          \
                      << "!(" << left << " >= " << right << ")" << 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<sim_clock> cto;
    long long timeout = 1000;
    milliseconds actual = cto.TimeoutAction(milliseconds(timeout));
    sim_clock::increment_by(milliseconds(timeout));
    EXPECT_GE(actual.count(), timeout, TestWithSystemClock);
}

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

Here is the simulated clock definition based on steady_clock:

#pragma once
#include <chrono>

struct sim_clock {
    typedef std::chrono::steady_clock::rep rep;
    typedef std::chrono::steady_clock::period period;
    typedef std::chrono::steady_clock::duration duration;
    typedef std::chrono::steady_clock::time_point time_point;

    static time_point now() noexcept;
    static void increment_by(sim_clock::duration d) noexcept;

    static constexpr bool is_steady = true;

    static time_point _now;
};

and implementation:

#include "sim_clock.hpp"

sim_clock::time_point sim_clock::_now;

sim_clock::time_point sim_clock::now() noexcept
{
    return _now;
}

void sim_clock::increment_by(sim_clock::duration d) noexcept
{
    _now += d;
}
like image 26
Daniel Avatar answered Oct 07 '22 22:10

Daniel