Files
MARTe-Integrated-Components/Client/udpscope/tests/DecimateTest.cpp
T
Martino FerrariandClaude Opus 4.6 c1029a25df fix(udpscope): make the time-order test actually exercise the swap, guard the font copy
The ramp data in EmitsPointsInTimeOrder never produced a bucket whose maximum
preceded its minimum, so an implementation ordering the emitted pair by value
instead of by time would have passed. Replaced with an explicit two-bucket case
whose second bucket reverses the order.

file(COPY) is a hard configure error on a missing source, so a checkout without
the sibling StreamHub resources failed to configure despite the ASCII-icon
fallback the block above had just selected.

Plan amended to match on both points.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-08-27 19:39:14 +02:00

91 lines
3.0 KiB
C++

#include "Decimate.h"
#include <gtest/gtest.h>
#include <algorithm>
#include <vector>
using namespace udpscope;
TEST(MinMaxDecimate, PassesShortInputThroughUnchanged) {
const std::vector<double> t{0.0, 1.0, 2.0};
const std::vector<double> v{5.0, 6.0, 7.0};
Series out;
MinMaxDecimate(t.data(), v.data(), t.size(), 100, out);
EXPECT_EQ(out.t, t);
EXPECT_EQ(out.v, v);
}
// The whole reason for preferring min/max over LTTB: a single-sample spike is
// usually the thing the user is looking for, and it must survive decimation.
TEST(MinMaxDecimate, PreservesAnIsolatedSpike) {
std::vector<double> t(1000), v(1000, 0.0);
for (size_t i = 0; i < t.size(); i++) { t[i] = static_cast<double>(i); }
v[437] = 42.0;
Series out;
MinMaxDecimate(t.data(), v.data(), t.size(), 50, out);
ASSERT_FALSE(out.v.empty());
EXPECT_EQ(*std::max_element(out.v.begin(), out.v.end()), 42.0);
}
TEST(MinMaxDecimate, PreservesTheExtremesOfEveryBucket) {
std::vector<double> t(100), v(100);
for (size_t i = 0; i < t.size(); i++) {
t[i] = static_cast<double>(i);
v[i] = (i % 10 == 3) ? -9.0 : ((i % 10 == 7) ? 9.0 : 0.0);
}
Series out;
MinMaxDecimate(t.data(), v.data(), t.size(), 20, out);
EXPECT_EQ(*std::min_element(out.v.begin(), out.v.end()), -9.0);
EXPECT_EQ(*std::max_element(out.v.begin(), out.v.end()), 9.0);
}
// A ring whose timestamps are not monotonic breaks any later binary search by
// time, so the pair emitted per bucket must be ordered by time, not by value.
TEST(MinMaxDecimate, EmitsPointsInTimeOrder) {
// Two buckets of four. In the first the minimum comes before the maximum,
// in the second the order is reversed. An implementation that emitted
// (min, max) by value rather than by time passes on bucket 0 and fails on
// bucket 1, so this data exercises the swap that a monotonically growing
// ramp never triggers.
const double st[8] = {0, 1, 2, 3, 4, 5, 6, 7};
const double sv[8] = {-5, 0, 0, 9, 9, 0, 0, -5};
Series pair;
MinMaxDecimate(st, sv, 8, 4, pair);
ASSERT_EQ(pair.size(), 4u);
const double wantT[4] = {0, 3, 4, 7};
const double wantV[4] = {-5, 9, 9, -5};
for (size_t i = 0; i < 4; i++) {
EXPECT_EQ(pair.t[i], wantT[i]) << "time at " << i;
EXPECT_EQ(pair.v[i], wantV[i]) << "value at " << i;
}
std::vector<double> t(400), v(400);
for (size_t i = 0; i < t.size(); i++) {
t[i] = static_cast<double>(i);
v[i] = (i % 2 == 0) ? -static_cast<double>(i) : static_cast<double>(i);
}
Series out;
MinMaxDecimate(t.data(), v.data(), t.size(), 40, out);
ASSERT_GT(out.t.size(), 1u);
for (size_t i = 1; i < out.t.size(); i++) {
EXPECT_LE(out.t[i - 1], out.t[i]) << "at index " << i;
}
}
TEST(MinMaxDecimate, HandlesEmptyInput) {
Series out;
out.t.push_back(1.0); // must be cleared
MinMaxDecimate(nullptr, nullptr, 0, 10, out);
EXPECT_TRUE(out.t.empty());
EXPECT_TRUE(out.v.empty());
}