// Standalone tests for reasampler::instrument::engine::Limiter — no VST3, no REAPER, no
// framework. The properties the master bus depends on, asserted rather than judged by ear:
//
//  * bypassed and settled, process() does not touch one byte of the buffers (the byte-identical
//    at-rest path) and reports no reduction;
//  * engaged below the ceiling, the output is the input DELAYED and bit-exact — nothing is
//    louder, quieter or altered at rest, and there is no makeup gain to find;
//  * engaged on program +12 dB over, no output sample passes the ceiling; bypassed, the same
//    program still passes 0 dBFS, so the toggle is doing the work;
//  * the detection is TRUE-peak: a signal whose SAMPLES all clear the ceiling but whose
//    inter-sample peak does not still engages;
//  * the gain is stereo-linked, so a dual-mono signal stays centered across a full toggle;
//  * across a toggle in EITHER direction, every output sample is under the ceiling or exactly
//    the unlimited input — never a fraction of the unlimited input, which is the leak the
//    retired equal-gain crossfade admitted;
//  * the transition's only two discontinuities are the hard edges against silence, one per
//    direction;
//  * the published minimum tracks the limiter's own reduction, not the mute weight: a toggle
//    over content that never crosses the ceiling publishes exactly 1.0 all the way through.

#include "../src/core/instrument/engine/limiter.h"

#include <algorithm>
#include <cmath>
#include <cstdint>
#include <cstdio>
#include <vector>

using namespace reasampler::instrument::engine;

static int g_fail = 0;
#define CHECK(cond) do { if(!(cond)) { \
    std::printf("FAIL line %d: %s\n", __LINE__, #cond); ++g_fail; } } while(0)

static constexpr double kRate = 48000.0;

// A deterministic non-repeating pattern, so an untouched-buffer check cannot pass by accident.
static std::vector<float> pattern(int n, float scale = 1.f) {
    std::vector<float> v(static_cast<std::size_t>(n));
    std::uint32_t s = 0x1234567u;
    for (int i = 0; i < n; ++i) {
        s = s * 1664525u + 1013904223u;
        v[static_cast<std::size_t>(i)] =
            scale * (static_cast<float>(static_cast<int>(s >> 8) % 20001 - 10000) / 10000.f);
    }
    return v;
}

// Runs `in` through `lim` in blocks of `block`, returning the output and the smallest gain
// reported across the whole run.
static std::vector<float> runMono(Limiter& lim, const std::vector<float>& in, int block,
                                  float* minGainOut = nullptr) {
    std::vector<float> out = in;
    float lowest = 1.f;
    for (std::size_t i = 0; i < out.size(); i += static_cast<std::size_t>(block)) {
        const int n = static_cast<int>(
            std::min(static_cast<std::size_t>(block), out.size() - i));
        const float g = lim.process(out.data() + i, nullptr, n);
        if (g < lowest) lowest = g;
    }
    if (minGainOut) *minGainOut = lowest;
    return out;
}

static void testBypassedLeavesEveryByteUntouched() {
    Limiter lim;
    lim.prepare(kRate);
    CHECK(!lim.enabled());
    const std::vector<float> in = pattern(2048, 1.8f);  // well over full scale
    float minGain = 0.f;
    const std::vector<float> out = runMono(lim, in, 512, &minGain);
    bool identical = true;
    for (std::size_t i = 0; i < in.size(); ++i) {
        if (out[i] != in[i]) { identical = false; break; }
    }
    CHECK(identical);
    CHECK(minGain == 1.f);
    // And that untouched signal still passes 0 dBFS — the toggle, not the meter, is what
    // stops it.
    float peak = 0.f;
    for (float v : out) peak = std::max(peak, std::fabs(v));
    CHECK(peak > 1.f);
}

static void testEngagedBelowThresholdIsTheInputDelayedBitExactly() {
    Limiter lim;
    lim.setEnabled(true);
    lim.prepare(kRate);  // prepare snaps to the target: no crossfade, no priming
    const int latency = limiterLookaheadSamples(kRate);
    // Comfortably under the ceiling at every sample AND between samples.
    const std::vector<float> in = pattern(4096, 0.4f);
    float minGain = 0.f;
    const std::vector<float> out = runMono(lim, in, 256, &minGain);
    CHECK(minGain == 1.f);  // exactly unity: there is no makeup gain and no residual trim
    bool exact = true;
    for (std::size_t i = static_cast<std::size_t>(latency); i < in.size(); ++i) {
        if (out[i] != in[i - static_cast<std::size_t>(latency)]) { exact = false; break; }
    }
    CHECK(exact);
}

static void testEngagedHoldsTheCeilingOnProgramTwelveDbOver() {
    Limiter lim;
    lim.setEnabled(true);
    lim.prepare(kRate);
    const int latency = limiterLookaheadSamples(kRate);
    const float ceiling = static_cast<float>(limiterCeilingLinear());
    // +12 dB over the ceiling, sustained, with the transient content the pattern gives.
    std::vector<float> in = pattern(24000, ceiling * 3.98f);
    float minGain = 0.f;
    const std::vector<float> out = runMono(lim, in, 128, &minGain);
    CHECK(minGain < 0.4f);  // it really did pull the gain down
    float worst = 0.f;
    for (std::size_t i = static_cast<std::size_t>(latency); i < out.size(); ++i) {
        worst = std::max(worst, std::fabs(out[i]));
    }
    // Sample peak, so the true-peak ceiling is the bound with room to spare for float rounding
    // (ceiling/peak then x*gain admits at most ~2.4e-7 relative overshoot; 1e-6 stays a hard
    // bound without hiding a systematic error the way a much wider tolerance would).
    CHECK(worst <= ceiling * (1.f + 1e-6f));
}

static void testTruePeakDetectionEngagesWhereSamplePeakWouldNot() {
    // fs/4 at 45 degrees: every SAMPLE sits at A/sqrt(2) while the waveform reaches A between
    // them. A sample-peak detector would pass this through untouched.
    const double amp = 1.2;
    const float ceiling = static_cast<float>(limiterCeilingLinear());
    std::vector<float> in(8000);
    for (std::size_t i = 0; i < in.size(); ++i) {
        in[i] = static_cast<float>(
            amp * std::cos(3.14159265358979323846 * (0.5 * static_cast<double>(i) + 0.25)));
    }
    float samplePeak = 0.f;
    for (float v : in) samplePeak = std::max(samplePeak, std::fabs(v));
    CHECK(samplePeak < ceiling);  // the premise: no SAMPLE is over

    Limiter lim;
    lim.setEnabled(true);
    lim.prepare(kRate);
    float minGain = 0.f;
    runMono(lim, in, 256, &minGain);
    CHECK(minGain < 1.f);
}

static void testStereoLinkedGainKeepsDualMonoCenteredAcrossAToggle() {
    Limiter lim;
    lim.prepare(kRate);
    const int latency = limiterLookaheadSamples(kRate);
    const float ceiling = static_cast<float>(limiterCeilingLinear());
    const std::vector<float> src = pattern(48000, ceiling * 2.5f);
    std::vector<float> l = src, r = src;  // dual mono: L and R are the same signal
    const int block = 64;
    bool centered = true;
    // The prime+fade window right after the engage point: the published minimum here is the
    // case Daniel named — it must read the limiter's own reduction on this loud program, not
    // the mute weight (which would read exactly 0 through the prime, old contract).
    const std::size_t engageAt = l.size() / 4;
    const std::size_t muteWindowEnd = engageAt + static_cast<std::size_t>(latency) +
                                       static_cast<std::size_t>(kLimiterMuteSeconds * kRate);
    float minGainDuringMute = 1.f;
    for (std::size_t i = 0; i < l.size(); i += static_cast<std::size_t>(block)) {
        // Toggle on a quarter in and off three quarters in, so the run covers bypassed,
        // the engage mute, fully engaged, the disengage fade, and bypassed again.
        if (i >= l.size() / 4 && !lim.enabled()) lim.setEnabled(true);
        if (i >= (l.size() * 3) / 4 && lim.enabled()) lim.setEnabled(false);
        const int n = static_cast<int>(
            std::min(static_cast<std::size_t>(block), l.size() - i));
        const float g = lim.process(l.data() + i, r.data() + i, n);
        if (i >= engageAt && i < muteWindowEnd && g < minGainDuringMute) minGainDuringMute = g;
    }
    for (std::size_t i = 0; i < l.size(); ++i) {
        if (l[i] != r[i]) { centered = false; break; }
    }
    CHECK(centered);
    // And the engaged stretch really was limited, so the equality above is not equality on an
    // untouched buffer.
    float worstEngaged = 0.f;
    for (std::size_t i = l.size() / 2; i < (l.size() * 3) / 4; ++i) {
        worstEngaged = std::max(worstEngaged, std::fabs(l[i]));
    }
    CHECK(worstEngaged <= ceiling * (1.f + 1e-6f));
    CHECK(worstEngaged > 0.f);
    CHECK(minGainDuringMute > 0.f);  // never the mute's own zero weight
    CHECK(minGainDuringMute < 1.f);  // and it really is reduction, not a no-op read
}

static void testToggleWithNothingOverCeilingPublishesNoReduction() {
    // Daniel's ruling: only show GR when it's really limiting, not just muting. Content that
    // never exceeds the ceiling must publish exactly 1.0 through the WHOLE transition — the
    // prime, both fades, and the settled stretches — because the old effectiveGain contract
    // read 0.0 through the mute regardless of content.
    Limiter lim;
    lim.prepare(kRate);
    const float ceiling = static_cast<float>(limiterCeilingLinear());
    const std::vector<float> src = pattern(48000, ceiling * 0.5f);  // comfortably under, always
    std::vector<float> y = src;
    const int block = 64;
    float minGain = 1.f;
    for (std::size_t i = 0; i < y.size(); i += static_cast<std::size_t>(block)) {
        if (i >= y.size() / 4 && !lim.enabled()) lim.setEnabled(true);
        if (i >= (y.size() * 3) / 4 && lim.enabled()) lim.setEnabled(false);
        const int n = static_cast<int>(
            std::min(static_cast<std::size_t>(block), y.size() - i));
        const float g = lim.process(y.data() + i, nullptr, n);
        if (g < minGain) minGain = g;
    }
    CHECK(minGain == 1.f);
    // And the run really did mute, so `minGain == 1.f` is not vacuous over an untouched buffer.
    bool sawSilenceOverSignal = false;
    for (std::size_t i = 0; i < y.size(); ++i) {
        if (y[i] == 0.f && std::fabs(src[i]) > 0.1f) { sawSilenceOverSignal = true; break; }
    }
    CHECK(sawSilenceOverSignal);
}

static void testTheTransitionsOnlyEdgesAreTheTwoAgainstSilence() {
    // Replaces the retired crossfade's "no step larger than the signal's own", which no longer
    // describes the design: the mute has exactly ONE hard edge per direction, both against
    // silence, and everything between them is continuous. A steady sine well under the
    // ceiling, so this measures the TRANSITION and not limiting. 375 Hz is one cycle per 128
    // samples, so a block-aligned toggle lands on a phase the test can state rather than
    // inherit — at a zero crossing the engage edge would be small for a reason that has
    // nothing to do with the design.
    const double freq = 375.0;  // kRate / 128
    const double amp = 0.5;
    const int block = 32;
    const int engageAt = 12064;     // block-aligned AND one sample past the sine's peak
    const int disengageAt = 36064;
    std::vector<float> in(48000);
    for (std::size_t i = 0; i < in.size(); ++i) {
        in[i] = static_cast<float>(
            amp * std::sin(2.0 * 3.14159265358979323846 * freq * static_cast<double>(i) / kRate));
    }
    std::vector<float> y = in;
    const float naturalStep =
        static_cast<float>(amp * 2.0 * 3.14159265358979323846 * freq / kRate);
    // One fade step's worth of signal: the disengage's last emitted sample sits at most this
    // far above zero, because the fade is stepped AFTER the sample it weighted.
    const float silenceFloor =
        static_cast<float>(amp / (kLimiterMuteSeconds * kRate)) * 1.01f;
    CHECK(std::fabs(in[static_cast<std::size_t>(engageAt) - 1]) > 0.4f);  // the edge has teeth

    Limiter lim;
    lim.prepare(kRate);
    for (std::size_t i = 0; i < y.size(); i += static_cast<std::size_t>(block)) {
        if (static_cast<int>(i) >= engageAt && !lim.enabled()) lim.setEnabled(true);
        if (static_cast<int>(i) >= disengageAt && lim.enabled()) lim.setEnabled(false);
        const int n = static_cast<int>(
            std::min(static_cast<std::size_t>(block), y.size() - i));
        lim.process(y.data() + i, nullptr, n);
    }

    // Engage: the dry path leaves circuit AT the toggle sample, in one step to silence — the
    // sample before it is still the untouched dry buffer, never a partial weight of it.
    CHECK(y[static_cast<std::size_t>(engageAt) - 1] == in[static_cast<std::size_t>(engageAt) - 1]);
    CHECK(y[static_cast<std::size_t>(engageAt)] == 0.f);

    // Disengage: one resume edge, out of near-silence straight into the untouched dry buffer,
    // and nothing written after it.
    std::size_t lastTouched = 0;
    for (std::size_t i = 0; i < y.size(); ++i) {
        if (y[i] != in[i]) lastTouched = i;
    }
    CHECK(static_cast<int>(lastTouched) > disengageAt);
    CHECK(std::fabs(y[lastTouched]) <= silenceFloor);
    bool dryAfterResume = true;
    for (std::size_t i = lastTouched + 1; i < y.size(); ++i) {
        if (y[i] != in[i]) { dryAfterResume = false; break; }
    }
    CHECK(dryAfterResume);

    // Everything BETWEEN the two edges is continuous — both fades and the settled middle.
    float worstStep = 0.f;
    for (std::size_t i = static_cast<std::size_t>(engageAt) + 1; i <= lastTouched; ++i) {
        worstStep = std::max(worstStep, std::fabs(y[i] - y[i - 1]));
    }
    CHECK(worstStep <= naturalStep * 1.2f);
    // And the run really was muted, so the continuity above is not an untouched buffer's.
    bool sawSilenceOverSignal = false;
    for (std::size_t i = 0; i < y.size(); ++i) {
        if (y[i] == 0.f && std::fabs(in[i]) > 0.4f) { sawSilenceOverSignal = true; break; }
    }
    CHECK(sawSilenceOverSignal);
}

// The one rule the transition encodes: every output sample is EITHER under the ceiling OR
// exactly the unlimited input. A fraction of the unlimited input is neither, which is why the
// retired equal-gain crossfade could pass a peak over the ceiling mid-transition.
static bool underCeilingOrExactlyDry(float y, float x, float ceiling) {
    return std::fabs(y) <= ceiling * (1.f + 1e-6f) || y == x;
}

static void testUnlimitedSignalIsNeverEmittedAtAPartialWeight() {
    const float ceiling = static_cast<float>(limiterCeilingLinear());
    // +12 dB over the ceiling for the WHOLE run, so the transition windows are driven, not
    // merely crossed while quiet.
    const std::vector<float> in = pattern(48000, ceiling * 3.98f);
    std::vector<float> y = in;

    Limiter lim;
    lim.prepare(kRate);
    const int block = 64;
    for (std::size_t i = 0; i < y.size(); i += static_cast<std::size_t>(block)) {
        if (i >= y.size() / 4 && !lim.enabled()) lim.setEnabled(true);       // engage
        if (i >= (y.size() * 3) / 4 && lim.enabled()) lim.setEnabled(false);  // disengage
        const int n = static_cast<int>(
            std::min(static_cast<std::size_t>(block), y.size() - i));
        lim.process(y.data() + i, nullptr, n);
    }

    bool held = true;
    bool sawLimited = false, sawMuted = false, sawDry = false;
    for (std::size_t i = 0; i < y.size(); ++i) {
        if (!underCeilingOrExactlyDry(y[i], in[i], ceiling)) { held = false; break; }
        if (y[i] != in[i] && y[i] != 0.f) sawLimited = true;
        if (y[i] == 0.f && std::fabs(in[i]) > ceiling) sawMuted = true;
        if (y[i] == in[i] && std::fabs(in[i]) > ceiling) sawDry = true;
    }
    CHECK(held);
    // Each of the three states the rule distinguishes actually occurred, so `held` is not
    // satisfied by a buffer that was only ever passed through.
    CHECK(sawLimited);
    CHECK(sawMuted);
    CHECK(sawDry);
}

static void testALoudTransientInFlightAtTheToggleCannotSpike() {
    // The toggle flipped while a transient 18 dB over the ceiling is in flight, swept across
    // the whole transition window (the 2 ms prime, the 10 ms fade, and past both) in each
    // direction. Nothing anywhere may land between silence and the unlimited input.
    const float ceiling = static_cast<float>(limiterCeilingLinear());
    const int latency = limiterLookaheadSamples(kRate);
    const int fade = static_cast<int>(kLimiterMuteSeconds * kRate);
    const int block = 32;
    const int toggleAt = 3200;  // a block boundary
    const int offsets[] = {0, 1, latency - 1, latency, latency + 1, fade / 2,
                           fade, fade + latency, fade + 4 * latency};

    for (bool engaging : {true, false}) {
        for (int offset : offsets) {
            std::vector<float> in(
                static_cast<std::size_t>(toggleAt + 2 * fade + 8 * latency), 0.f);
            in[static_cast<std::size_t>(toggleAt + offset)] = ceiling * 8.f;
            std::vector<float> y = in;

            Limiter lim;
            lim.setEnabled(!engaging);
            lim.prepare(kRate);  // prepare snaps to the target: the run starts settled
            for (std::size_t i = 0; i < y.size(); i += static_cast<std::size_t>(block)) {
                if (static_cast<int>(i) >= toggleAt) lim.setEnabled(engaging);
                const int n = static_cast<int>(
                    std::min(static_cast<std::size_t>(block), y.size() - i));
                lim.process(y.data() + i, nullptr, n);
            }

            bool held = true;
            float loudestLimited = 0.f;
            for (std::size_t i = 0; i < y.size(); ++i) {
                if (!underCeilingOrExactlyDry(y[i], in[i], ceiling)) { held = false; break; }
                if (y[i] != in[i]) loudestLimited = std::max(loudestLimited, std::fabs(y[i]));
            }
            CHECK(held);
            // The transient reached the LIMITED path rather than being muted away entirely,
            // so `held` above is not satisfied by silence. The qualifying offset differs by
            // direction because the fade opens at the end of an engage and closes at the
            // start of a disengage.
            if (engaging && offset >= fade + latency) {
                CHECK(loudestLimited > ceiling * 0.9f);
            }
            if (!engaging && offset == 0) CHECK(loudestLimited > ceiling * 0.5f);
        }
    }
}

static void testTransitionSettlesToTheExactEngagedAndBypassedPaths() {
    Limiter lim;
    lim.prepare(kRate);
    const int latency = limiterLookaheadSamples(kRate);
    // The engage costs a `latency`-sample prime, then the fade, then the delay itself.
    const int settle =
        static_cast<int>(kLimiterMuteSeconds * kRate) + 2 * latency + 64;
    const std::vector<float> src = pattern(4 * settle, 0.3f);  // under the ceiling throughout

    std::vector<float> y = src;
    lim.setEnabled(true);
    lim.process(y.data(), nullptr, static_cast<int>(y.size()));
    // Past the crossfade the engaged path is exactly the delayed input again.
    bool exact = true;
    for (std::size_t i = static_cast<std::size_t>(settle); i < y.size(); ++i) {
        if (y[i] != src[i - static_cast<std::size_t>(latency)]) { exact = false; break; }
    }
    CHECK(exact);

    std::vector<float> z = src;
    lim.setEnabled(false);
    lim.process(z.data(), nullptr, static_cast<int>(z.size()));
    bool passthrough = true;
    for (std::size_t i = static_cast<std::size_t>(settle); i < z.size(); ++i) {
        if (z[i] != src[i]) { passthrough = false; break; }
    }
    CHECK(passthrough);
    // And once settled bypassed, the next block is untouched again.
    std::vector<float> w = pattern(512, 1.5f);
    const std::vector<float> before = w;
    CHECK(lim.process(w.data(), nullptr, static_cast<int>(w.size())) == 1.f);
    bool untouched = true;
    for (std::size_t i = 0; i < w.size(); ++i) {
        if (w[i] != before[i]) { untouched = false; break; }
    }
    CHECK(untouched);
}

static void testGainNeverRisesAboveUnity() {
    // "No makeup gain, ever, of any kind" as a property rather than an absence: across quiet,
    // loud and silent material the applied gain is never above 1 and the output magnitude is
    // never above the input's own.
    Limiter lim;
    lim.setEnabled(true);
    lim.prepare(kRate);
    std::vector<float> in = pattern(16000, 2.0f);
    for (std::size_t i = 4000; i < 8000; ++i) in[i] = 0.f;          // a silent stretch
    for (std::size_t i = 8000; i < 12000; ++i) in[i] *= 0.001f;     // and a very quiet one
    float minGain = 0.f;
    const std::vector<float> out = runMono(lim, in, 200, &minGain);
    CHECK(minGain <= 1.f);
    float inPeak = 0.f, outPeak = 0.f;
    for (std::size_t i = 0; i < in.size(); ++i) {
        inPeak = std::max(inPeak, std::fabs(in[i]));
        outPeak = std::max(outPeak, std::fabs(out[i]));
    }
    CHECK(outPeak <= inPeak);
}

static void testAlignmentIdentityHoldsAtTheExactWindowEdge() {
    // Pins the alignment identity window_ = latency_ - kLimiterOsDelay + 1 (limiter.h's
    // comment on window_, otherwise asserted nowhere): a single isolated over-ceiling impulse
    // is reduced to EXACTLY the ceiling at the one output sample the identity predicts
    // (impulseAt + latency), because that is the unique push index where the sliding
    // min-then-average has folded in nothing but this impulse's own detected peak. Shifting
    // the identity by +-1 either lets the impulse's own excess slip just outside the window
    // (undershoots the reduction, sample overshoots the ceiling) or applies the full reduction
    // one sample late (same overshoot at this index) — confirmed by hand-mutating window_'s
    // formula in both directions and observing this assertion fail before restoring it.
    Limiter lim;
    lim.setEnabled(true);
    lim.prepare(kRate);
    const int latency = limiterLookaheadSamples(kRate);
    const float ceiling = static_cast<float>(limiterCeilingLinear());
    const int impulseAt = 500;
    std::vector<float> in(static_cast<std::size_t>(impulseAt + latency + 200), 0.f);
    in[static_cast<std::size_t>(impulseAt)] = ceiling * 4.f;  // isolated, well over
    float minGain = 0.f;
    const std::vector<float> out = runMono(lim, in, 37, &minGain);  // odd block: crosses the edge
    CHECK(minGain > 0.24f && minGain < 0.26f);  // ceiling/peak == 0.25 for this impulse
    const float atEdge = out[static_cast<std::size_t>(impulseAt + latency)];
    CHECK(std::fabs(atEdge - ceiling) <= ceiling * 1e-6f);
    // Every neighbor stays exactly silent — the reduction lands on this one sample, not smeared.
    CHECK(out[static_cast<std::size_t>(impulseAt + latency - 1)] == 0.f);
    CHECK(out[static_cast<std::size_t>(impulseAt + latency + 1)] == 0.f);
}

static void testBakedConstants() {
    CHECK(kLimiterCeilingDbTp == -0.3);
    CHECK(std::fabs(limiterCeilingLinear() - std::pow(10.0, -0.3 / 20.0)) < 1e-12);
    CHECK(limiterCeilingLinear() < 1.0);
    // 2 ms at the common rates, and never below the detector's own group delay.
    CHECK(limiterLookaheadSamples(48000.0) == 96);
    CHECK(limiterLookaheadSamples(44100.0) == 88);
    CHECK(limiterLookaheadSamples(96000.0) == 192);
    CHECK(limiterLookaheadSamples(0.0) == 0);
    CHECK(limiterLookaheadSamples(-1.0) == 0);
    CHECK(limiterLookaheadSamples(100.0) > kLimiterOsDelay);
}

int main() {
    testBypassedLeavesEveryByteUntouched();
    testEngagedBelowThresholdIsTheInputDelayedBitExactly();
    testEngagedHoldsTheCeilingOnProgramTwelveDbOver();
    testTruePeakDetectionEngagesWhereSamplePeakWouldNot();
    testStereoLinkedGainKeepsDualMonoCenteredAcrossAToggle();
    testToggleWithNothingOverCeilingPublishesNoReduction();
    testTheTransitionsOnlyEdgesAreTheTwoAgainstSilence();
    testUnlimitedSignalIsNeverEmittedAtAPartialWeight();
    testALoudTransientInFlightAtTheToggleCannotSpike();
    testTransitionSettlesToTheExactEngagedAndBypassedPaths();
    testGainNeverRisesAboveUnity();
    testAlignmentIdentityHoldsAtTheExactWindowEdge();
    testBakedConstants();
    if (g_fail) {
        std::printf("%d FAILURE(S)\n", g_fail);
        return 1;
    }
    std::printf("limiter tests passed\n");
    return 0;
}
