#include "TimeBase.h" #include 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 // Arrival wanders either side of the prediction. wallSec is a local receive // timestamp, so it only ever advances — jitter shows up as the gap growing // and shrinking, never as the clock going backwards. EXPECT_DOUBLE_EQ(off.map(11.0, 1001.02), 1001.0); // +0.02 late EXPECT_DOUBLE_EQ(off.map(12.0, 1001.97), 1002.0); // -0.03 early } // The threshold has to be symmetric. A producer whose clock steps FORWARD (an // NTP correction on the producer's host, say) puts the prediction permanently // ahead of the wall clock — a one-sided "recalibrate only when wall is ahead" // test never fires for it, and the trace sits in the future for the rest of the // run. TEST(ClockOffset, RecalibratesWhenTheProducerClockJumpsForward) { ClockOffset off; off.map(10.0, 1000.0); // offset = 990 // Producer leaps 100 s ahead while only 1 s of wall time passes. EXPECT_DOUBLE_EQ(off.map(111.0, 1001.0), 1001.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(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(i) * 1000000u, 1000.0 + i * 0.001); } EXPECT_FALSE(fit.ready()); fit.add(static_cast(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(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); }