Files
MARTe-Integrated-Components/Client/udpscope/tests/TimeBaseTest.cpp
T
Martino FerrariandClaude Sonnet 4.6 41ab151a2f feat(udpscope): producer-clock calibration and hrt tick-rate fit
Adds TimeBase.h/cpp with ClockOffset (latched wall-clock offset with
one-sided recalibration on positive drift only, so early-arriving packets
do not wobble the trace) and HrtRateFit (sliding-window OLS that recovers
an unknown hrt tick rate from receive timestamps). Also adds
TimeSignalScale() which maps UDPS type codes to seconds-per-count.
9 new tests, all 28 pass.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-08-27 19:50:24 +02:00

100 lines
3.2 KiB
C++

#include "TimeBase.h"
#include <gtest/gtest.h>
using namespace udpscope;
TEST(ClockOffset, MapsTheFirstReadingOntoWallClockExactly) {
ClockOffset off;
EXPECT_FALSE(off.valid());
const double wall = 1756291200.5;
EXPECT_DOUBLE_EQ(off.map(10.0, wall), wall);
EXPECT_TRUE(off.valid());
}
// Network delay jitters the arrival time. If the offset chased every packet
// the whole trace would wobble, so it is latched and only corrected on real
// drift.
TEST(ClockOffset, HoldsTheOffsetThroughSmallArrivalJitter) {
ClockOffset off;
off.map(10.0, 1000.0); // offset = 990
EXPECT_DOUBLE_EQ(off.map(11.0, 1001.02), 1001.0);
EXPECT_DOUBLE_EQ(off.map(12.0, 1000.97), 1002.0);
}
TEST(ClockOffset, RecalibratesWhenDriftExceedsTheThreshold) {
ClockOffset off;
off.map(10.0, 1000.0); // offset = 990
/* Producer clock jumped (restart, re-phase): 5 s of error is not jitter. */
const double mapped = off.map(11.0, 1006.0);
EXPECT_DOUBLE_EQ(mapped, 1006.0);
}
TEST(ClockOffset, ResetForgetsTheCalibration) {
ClockOffset off;
off.map(10.0, 1000.0);
off.reset();
EXPECT_FALSE(off.valid());
EXPECT_DOUBLE_EQ(off.map(50.0, 2000.0), 2000.0);
}
// The tick rate of the producer's high-resolution timer is not carried by the
// protocol, and StreamHub's trick of using the local MARTe timer frequency only
// works on the producer's own host. Recover it from the data instead.
TEST(HrtRateFit, RecoversAKnownTickRate) {
HrtRateFit fit;
const double ticksPerSec = 2.5e9;
EXPECT_FALSE(fit.ready());
for (int i = 0; i < 64; i++) {
const double wall = 1000.0 + i * 0.01;
fit.add(static_cast<uint64_t>(wall * ticksPerSec), wall);
}
ASSERT_TRUE(fit.ready());
EXPECT_NEAR(fit.ticksPerSecond(), ticksPerSec, ticksPerSec * 1e-6);
}
TEST(HrtRateFit, IsNotReadyBeforeTheMinimumSampleCount) {
HrtRateFit fit;
for (size_t i = 0; i < HrtRateFit::kMinSamples - 1; i++) {
fit.add(static_cast<uint64_t>(i) * 1000000u, 1000.0 + i * 0.001);
}
EXPECT_FALSE(fit.ready());
fit.add(static_cast<uint64_t>(HrtRateFit::kMinSamples) * 1000000u,
1000.0 + HrtRateFit::kMinSamples * 0.001);
EXPECT_TRUE(fit.ready());
}
TEST(HrtRateFit, ToSecondsUsesTheFittedRate) {
HrtRateFit fit;
const double ticksPerSec = 1.0e9;
for (int i = 0; i < 64; i++) {
const double wall = 500.0 + i * 0.005;
fit.add(static_cast<uint64_t>(wall * ticksPerSec), wall);
}
ASSERT_TRUE(fit.ready());
EXPECT_NEAR(fit.toSeconds(2000000000ull), 2.0, 1e-4);
}
TEST(HrtRateFit, SurvivesAStalledClock) {
HrtRateFit fit;
for (int i = 0; i < 64; i++) {
fit.add(12345u, 1000.0 + i * 0.01); /* hrt never advances */
}
/* A degenerate fit must not produce a rate that would divide by zero. */
if (fit.ready()) {
EXPECT_GT(fit.ticksPerSecond(), 0.0);
}
}
TEST(TimeSignalScale, UsesNanosecondsForUint64AndMicrosecondsOtherwise) {
EXPECT_DOUBLE_EQ(TimeSignalScale(6 /* UDPS_T_UINT64 */), 1.0e-9);
EXPECT_DOUBLE_EQ(TimeSignalScale(9 /* UDPS_T_FLOAT64 */), 1.0e-6);
EXPECT_DOUBLE_EQ(TimeSignalScale(4 /* UDPS_T_UINT32 */), 1.0e-6);
}