#include "cunit/cyrunit.h"
#include "lib/slowio.h"
#include "lib/util.h"

#include <inttypes.h>

#if HAVE_VALGRIND_VALGRIND_H
#include <valgrind/valgrind.h>
#else
/* no valgrind header? assume we're NOT running on valgrind. might
 * lead to test failures if we are running on valgrind after all,
 * but without the header there's no way to tell
 */
#define RUNNING_ON_VALGRIND (0)
#endif

static void test_initialize(void)
{
    const struct slowio slowio_zero = {0};
    struct slowio slowio = {0};

    /* negative n_bytes is never valid, not even to initialize */
    slowio_maybe_delay_impl(&slowio, -1);
    CU_ASSERT_EQUAL(0, memcmp(&slowio, &slowio_zero, sizeof(slowio)));

    /* zero n_bytes should initialize */
    slowio_maybe_delay_impl(&slowio, 0);
    CU_ASSERT_NOT_EQUAL(0, memcmp(&slowio, &slowio_zero, sizeof(slowio)));

    /* positive n_bytes should initialize */
    memset(&slowio, 0, sizeof(slowio));
    slowio_maybe_delay_impl(&slowio, 1);
    CU_ASSERT_NOT_EQUAL(0, memcmp(&slowio, &slowio_zero, sizeof(slowio)));
}

static void test_limit1(void)
{
    struct slowio slowio = {0}, saved_slowio;
    int64_t start, end, diff;
    int i;

    /* first call to initialize */
    slowio_maybe_delay_impl(&slowio, 0);

    /* long individual delays */
    for (i = 1; i <= 4; i++) {
        memcpy(&saved_slowio, &slowio, sizeof(slowio));
        start = now_ms();
        slowio_maybe_delay_impl(&slowio, i * SLOWIO_MAX_BYTES_SEC);
        end = now_ms();
        CU_ASSERT_NOT_EQUAL(0, memcmp(&slowio, &saved_slowio, sizeof(slowio)));
        CU_ASSERT_EQUAL(0, slowio.bytes_since_last_delay);

        diff = end - start;
        CU_ASSERT(diff >= 0.8 * (i * 1000));
        CU_ASSERT(diff <= 1.2 * (i * 1000));
    }
}

static void test_limit2(void)
{
    struct slowio slowio = {0}, saved_slowio;
    int64_t start, end, diff;
    int i;

    /* first call to initialize */
    slowio_maybe_delay_impl(&slowio, 0);

    /* many small calls, collectively delayed */
    for (i = 1; i <= 4; i++) {
        const unsigned steps_per_sec = 20;
        const unsigned bytes_per_step = SLOWIO_MAX_BYTES_SEC / steps_per_sec;
        const unsigned ms_per_step = 1000 / steps_per_sec;
        unsigned total_bytes = i * SLOWIO_MAX_BYTES_SEC;
        unsigned j;

        start = now_ms();
        for (j = 0; j < total_bytes; j += bytes_per_step) {
            int64_t inner_start, inner_end, inner_diff;

            memcpy(&saved_slowio, &slowio, sizeof(slowio));
            inner_start = now_ms();
            slowio_maybe_delay_impl(&slowio, bytes_per_step);
            inner_end = now_ms();

            inner_diff = inner_end - inner_start;

            /* we're not doing any actual I/O between calls to
             * slowio_maybe_delay_impl, so it'll look like we're doing our I/O
             * extremely fast, and so every call will add some delay
             */
            CU_ASSERT_EQUAL(0, slowio.bytes_since_last_delay);

            /* looser tolerances -- timings are swingier at this granularity */
            if (verbose && (inner_diff <= 0.25 * ms_per_step
                            || inner_diff >= 4.0 * ms_per_step))
            {
                fprintf(stderr, "%s: inner_diff(%" PRIi64 ") too large/small"
                                " vs ms_per_step(%u..%u)\n",
                                __func__, inner_diff,
                                (unsigned)(0.25 * ms_per_step),
                                (unsigned)(4.0 * ms_per_step));
            }
            CU_ASSERT(inner_diff > 0.25 * ms_per_step);
            CU_ASSERT(inner_diff < 4.0 * ms_per_step);
        }
        end = now_ms();

        diff = end - start;
        CU_ASSERT(diff >= 0.8 * (i * 1000));
        CU_ASSERT(diff <= 1.2 * (i * 1000));
    }
}

static void test_slower_than_rate_limit(void)
{
    struct slowio slowio = {0}, saved_slowio;
    int64_t start, end, diff;
    int i;

    /* first call to initialize */
    slowio_maybe_delay_impl(&slowio, 0);

    /* pretend we're reading/writing slightly slower than the rate limit */
    for (i = 1; i <= 4; i++) {
        struct timespec io_delay = { i, 1000000 }; /* 1 ms over */
        nanosleep(&io_delay, NULL);

        memcpy(&saved_slowio, &slowio, sizeof(slowio));
        start = now_ms();
        slowio_maybe_delay_impl(&slowio, i * SLOWIO_MAX_BYTES_SEC);
        end = now_ms();
        CU_ASSERT_NOT_EQUAL(0, memcmp(&slowio, &saved_slowio, sizeof(slowio)));
        CU_ASSERT_NOT_EQUAL(0, slowio.bytes_since_last_delay);

        diff = end - start;
        /* should have taken basically no time at all */
        if (!RUNNING_ON_VALGRIND) {
            CU_ASSERT(diff < 2 /* ms! */);
        }
    }
}

/* vim: set ft=c: */
