Files
openthread/tests/unit/test_trickle_timer.cpp
T
Jonathan Hui fa374236a5 [tests] fix flake in trickle timer unit test (#13172)
This commit fixes a frequent unit test flake in ot-test-trickle_timer
under the TestTrickleTimerMinMaxIntervalChange test case.

The test case starts the trickle timer with Imin = 2000 and
Imax = 2000. The random time t (mTimeInInterval) is chosen in
[1000, 2000), so t can range up to 1999.

When t randomly evaluates to 1999, t + 1 becomes 2000. Calling
timer.SetIntervalMax(2000) triggers an early-exit optimization
in TrickleTimer::SetIntervalMax because mIntervalMax is already 2000,
leaving the scheduled timer's fire time unchanged. The test then
crashes on the assertion expecting the fire time to have changed.

This is resolved by setting the new interval max to
Min(t + 1, interval - 1). This ensures that the requested value is
strictly less than 2000 even when t = 1999, successfully triggering
the interval shortening and rescheduling logic tested by this case.
2026-05-28 15:07:05 -07:00

522 lines
16 KiB
C++

/*
* Copyright (c) 2023, The OpenThread Authors.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. Neither the name of the copyright holder nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
#include "test_platform.h"
#include "common/code_utils.hpp"
#include "common/debug.hpp"
#include "common/num_utils.hpp"
#include "common/trickle_timer.hpp"
#include "instance/instance.hpp"
namespace ot {
static Instance *sInstance;
static uint32_t sNow = 0;
static uint32_t sAlarmTime;
static bool sAlarmOn = false;
extern "C" {
void otPlatAlarmMilliStop(otInstance *) { sAlarmOn = false; }
void otPlatAlarmMilliStartAt(otInstance *, uint32_t aT0, uint32_t aDt)
{
sAlarmOn = true;
sAlarmTime = aT0 + aDt;
}
uint32_t otPlatAlarmMilliGetNow(void) { return sNow; }
} // extern "C"
void AdvanceTime(uint32_t aDuration)
{
uint32_t time = sNow + aDuration;
while (TimeMilli(sAlarmTime) <= TimeMilli(time))
{
sNow = sAlarmTime;
otPlatAlarmMilliFired(sInstance);
}
sNow = time;
}
class TrickleTimerTester : public TrickleTimer
{
public:
explicit TrickleTimerTester(Instance &aInstance)
: TrickleTimer(aInstance, HandleTimerFired)
, mDidFire(false)
{
}
Time GetFireTime(void) const { return TimerMilli::GetFireTime(); }
uint32_t GetInterval(void) const { return TrickleTimer::mInterval; }
uint32_t GetTimeInInterval(void) const { return TrickleTimer::mTimeInInterval; }
void VerifyTimerDidFire(void)
{
VerifyOrQuit(mDidFire);
mDidFire = false;
}
void VerifyTimerDidNotFire(void) const { VerifyOrQuit(!mDidFire); }
static void RemoveAll(Instance &aInstance) { TimerMilli::RemoveAll(aInstance); }
private:
static void HandleTimerFired(TrickleTimer &aTimer) { static_cast<TrickleTimerTester &>(aTimer).HandleTimerFired(); }
void HandleTimerFired(void) { mDidFire = true; }
bool mDidFire;
};
void AlarmFired(otInstance *aInstance) { otPlatAlarmMilliFired(aInstance); }
void TestTrickleTimerPlainMode(void)
{
static constexpr uint32_t kMinInterval = 2000;
static constexpr uint32_t kMaxInterval = 5000;
Instance *instance = testInitInstance();
TrickleTimerTester timer(*instance);
uint32_t interval;
sInstance = instance;
TrickleTimerTester::RemoveAll(*instance);
printf("TestTrickleTimerPlainMode() ");
// Validate that timer picks a random interval between min and max
// on start.
sNow = 1000;
timer.Start(TrickleTimer::kModePlainTimer, kMinInterval, kMaxInterval, 0);
VerifyOrQuit(timer.IsRunning());
VerifyOrQuit(timer.GetIntervalMax() == kMaxInterval);
VerifyOrQuit(timer.GetIntervalMin() == kMinInterval);
interval = timer.GetInterval();
VerifyOrQuit((interval >= kMinInterval) && (interval <= kMaxInterval));
for (uint8_t iter = 0; iter <= 10; iter++)
{
AdvanceTime(interval);
timer.VerifyTimerDidFire();
// The plain mode trickle timer restarts with a new random
// interval between min and max.
VerifyOrQuit(timer.IsRunning());
interval = timer.GetInterval();
VerifyOrQuit((interval >= kMinInterval) && (interval <= kMaxInterval));
}
printf(" --> PASSED\n");
testFreeInstance(instance);
}
void TestTrickleTimerTrickleMode(uint32_t aRedundancyConstant, uint32_t aConsistentCalls)
{
static constexpr uint32_t kMinInterval = 1000;
static constexpr uint32_t kMaxInterval = 9000;
Instance *instance = testInitInstance();
TrickleTimerTester timer(*instance);
uint32_t interval;
uint32_t t;
sInstance = instance;
TrickleTimerTester::RemoveAll(*instance);
printf("TestTrickleTimerTrickleMode(aRedundancyConstant:%u, aConsistentCalls:%u) ", aRedundancyConstant,
aConsistentCalls);
sNow = 1000;
timer.Start(TrickleTimer::kModeTrickle, kMinInterval, kMaxInterval, aRedundancyConstant);
// Validate that trickle timer starts with random interval between
// min/max.
VerifyOrQuit(timer.IsRunning());
VerifyOrQuit(timer.GetIntervalMax() == kMaxInterval);
VerifyOrQuit(timer.GetIntervalMin() == kMinInterval);
interval = timer.GetInterval();
VerifyOrQuit((kMinInterval <= interval) && (interval <= kMaxInterval));
t = timer.GetTimeInInterval();
VerifyOrQuit((interval / 2 <= t) && (t <= interval));
// After `IndicateInconsistent()` should go back to min
// interval.
timer.IndicateInconsistent();
VerifyOrQuit(timer.IsRunning());
interval = timer.GetInterval();
VerifyOrQuit(interval == kMinInterval);
t = timer.GetTimeInInterval();
VerifyOrQuit((interval / 2 <= t) && (t <= interval));
for (uint8_t iter = 0; iter < 10; iter++)
{
for (uint32_t index = 0; index < aConsistentCalls; index++)
{
timer.IndicateConsistent();
}
AdvanceTime(t);
if (aConsistentCalls < aRedundancyConstant)
{
timer.VerifyTimerDidFire();
}
else
{
timer.VerifyTimerDidNotFire();
}
AdvanceTime(interval - t);
// Verify that interval is doubling each time up
// to max interval.
VerifyOrQuit(timer.IsRunning());
VerifyOrQuit(timer.GetInterval() == Min(interval * 2, kMaxInterval));
interval = timer.GetInterval();
t = timer.GetTimeInInterval();
VerifyOrQuit((interval / 2 <= t) && (t <= interval));
}
AdvanceTime(t);
timer.IndicateInconsistent();
VerifyOrQuit(timer.IsRunning());
interval = timer.GetInterval();
VerifyOrQuit(interval == kMinInterval);
printf(" --> PASSED\n");
testFreeInstance(instance);
}
void TestTrickleTimerMinMaxIntervalChange(void)
{
Instance *instance = testInitInstance();
TrickleTimerTester timer(*instance);
TimeMilli fireTime;
uint32_t interval;
uint32_t t;
uint32_t newIntervalMax;
sInstance = instance;
TrickleTimerTester::RemoveAll(*instance);
printf("TestTrickleTimerMinMaxIntervalChange()");
sNow = 1000;
timer.Start(TrickleTimer::kModeTrickle, 2000, 4000);
VerifyOrQuit(timer.IsRunning());
VerifyOrQuit(timer.GetIntervalMin() == 2000);
VerifyOrQuit(timer.GetIntervalMax() == 4000);
//- - - - - - - - - - - - - - - - - - - - - - - - - - - -
// Validate that `SetIntervalMin()` to a larger value than
// previously set does not impact the current interval.
timer.IndicateInconsistent();
interval = timer.GetInterval();
t = timer.GetTimeInInterval();
fireTime = timer.GetFireTime();
VerifyOrQuit(interval == 2000);
VerifyOrQuit((interval / 2 <= t) && (t < interval));
// Change `IntervalMin` before time `t`.
timer.SetIntervalMin(3000);
VerifyOrQuit(timer.IsRunning());
VerifyOrQuit(timer.GetIntervalMin() == 3000);
VerifyOrQuit(timer.GetIntervalMax() == 4000);
VerifyOrQuit(interval == timer.GetInterval());
VerifyOrQuit(t == timer.GetTimeInInterval());
VerifyOrQuit(fireTime == timer.GetFireTime());
AdvanceTime(t);
timer.VerifyTimerDidFire();
fireTime = timer.GetFireTime();
// Change `IntervalMin` after time `t`.
timer.SetIntervalMin(3500);
VerifyOrQuit(timer.IsRunning());
VerifyOrQuit(timer.GetIntervalMin() == 3500);
VerifyOrQuit(timer.GetIntervalMax() == 4000);
VerifyOrQuit(interval == timer.GetInterval());
VerifyOrQuit(t == timer.GetTimeInInterval());
VerifyOrQuit(fireTime == timer.GetFireTime());
//- - - - - - - - - - - - - - - - - - - - - - - - - - - -
// Validate that `SetIntervalMin()` to a smaller value
// also does not impact the current interval.
timer.IndicateInconsistent();
interval = timer.GetInterval();
t = timer.GetTimeInInterval();
fireTime = timer.GetFireTime();
VerifyOrQuit(interval == 3500);
VerifyOrQuit((interval / 2 <= t) && (t < interval));
// Change `IntervalMin` before time `t`.
timer.SetIntervalMin(3000);
VerifyOrQuit(timer.IsRunning());
VerifyOrQuit(timer.GetIntervalMin() == 3000);
VerifyOrQuit(timer.GetIntervalMax() == 4000);
VerifyOrQuit(interval == timer.GetInterval());
VerifyOrQuit(t == timer.GetTimeInInterval());
VerifyOrQuit(fireTime == timer.GetFireTime());
AdvanceTime(t);
timer.VerifyTimerDidFire();
fireTime = timer.GetFireTime();
// Change `IntervalMin` after time `t`.
timer.SetIntervalMin(2000);
VerifyOrQuit(timer.IsRunning());
VerifyOrQuit(timer.GetIntervalMin() == 2000);
VerifyOrQuit(timer.GetIntervalMax() == 4000);
VerifyOrQuit(interval == timer.GetInterval());
VerifyOrQuit(t == timer.GetTimeInInterval());
VerifyOrQuit(fireTime == timer.GetFireTime());
//- - - - - - - - - - - - - - - - - - - - - - - - - - - -
// Validate that changing `IntervalMax` to a larger value
// than the current interval being used by timer, does not
// impact the current internal.
timer.IndicateInconsistent();
interval = timer.GetInterval();
t = timer.GetTimeInInterval();
fireTime = timer.GetFireTime();
VerifyOrQuit(interval == 2000);
VerifyOrQuit((interval / 2 <= t) && (t < interval));
// Change `IntervalMax` before time `t`.
timer.SetIntervalMax(2500);
VerifyOrQuit(timer.GetIntervalMax() == 2500);
VerifyOrQuit(timer.IsRunning());
VerifyOrQuit(interval == timer.GetInterval());
VerifyOrQuit(t == timer.GetTimeInInterval());
VerifyOrQuit(fireTime == timer.GetFireTime());
AdvanceTime(t);
timer.VerifyTimerDidFire();
fireTime = timer.GetFireTime();
// Change `IntervalMax` after time `t`.
timer.SetIntervalMax(3000);
VerifyOrQuit(interval == timer.GetInterval());
VerifyOrQuit(t == timer.GetTimeInInterval());
VerifyOrQuit(fireTime == timer.GetFireTime());
timer.Stop();
VerifyOrQuit(!timer.IsRunning());
//- - - - - - - - - - - - - - - - - - - - - - - - - - - -
// Check behavior when the new `IntervalMax` is smaller
// than the current interval being used by timer.
// New `Imax` is smaller than `t` and before now.
//
// |<---- interval --^-------------------------------->|
// |<---- t ---------^------------------>| |
// |<---- new Imax --^--->| | |
// | now | | |
timer.Start(TrickleTimer::kModeTrickle, 2000, 2000);
interval = timer.GetInterval();
t = timer.GetTimeInInterval();
fireTime = timer.GetFireTime();
VerifyOrQuit(interval == 2000);
VerifyOrQuit((interval / 2 <= t) && (t < interval));
timer.SetIntervalMin(500);
AdvanceTime(100);
timer.VerifyTimerDidNotFire();
timer.SetIntervalMax(500);
VerifyOrQuit(timer.GetInterval() == 500);
VerifyOrQuit(timer.GetTimeInInterval() == 500);
VerifyOrQuit(timer.GetFireTime() != fireTime);
timer.VerifyTimerDidNotFire();
AdvanceTime(400);
timer.VerifyTimerDidFire();
// New `Imax` is smaller than `t` and after now.
//
// |<---- interval --------------^-------------------->|
// |<---- t ---------------------^------>| |
// |<---- new Imax ------>| ^ | |
// | | now | |
timer.Start(TrickleTimer::kModeTrickle, 2000, 2000);
interval = timer.GetInterval();
t = timer.GetTimeInInterval();
fireTime = timer.GetFireTime();
VerifyOrQuit(interval == 2000);
VerifyOrQuit((interval / 2 <= t) && (t < interval));
timer.SetIntervalMin(500);
AdvanceTime(800);
timer.VerifyTimerDidNotFire();
timer.SetIntervalMax(500);
VerifyOrQuit(timer.GetInterval() == 500);
VerifyOrQuit(timer.GetTimeInInterval() == 500);
VerifyOrQuit(timer.GetFireTime() != fireTime);
timer.VerifyTimerDidNotFire();
AdvanceTime(0);
timer.VerifyTimerDidFire();
// New `Imax` is larger than `t` and before now.
//
// |<---- interval --------------------------------^-->|
// |<---- t ---------------------------->| ^ |
// |<---- new Imax --------------------------->| ^ |
// | | | now |
timer.Start(TrickleTimer::kModeTrickle, 2000, 2000);
interval = timer.GetInterval();
t = timer.GetTimeInInterval();
VerifyOrQuit(interval == 2000);
VerifyOrQuit((interval / 2 <= t) && (t < interval));
timer.SetIntervalMin(500);
AdvanceTime(1999);
timer.VerifyTimerDidFire();
newIntervalMax = Min(t + 1, interval - 1);
timer.SetIntervalMax(newIntervalMax);
VerifyOrQuit(timer.GetInterval() == newIntervalMax);
fireTime = timer.GetFireTime();
// Check that new interval is started immediately.
AdvanceTime(0);
timer.VerifyTimerDidNotFire();
VerifyOrQuit(fireTime != timer.GetFireTime());
VerifyOrQuit(timer.GetInterval() == timer.GetIntervalMax());
// New `Imax` is larger than `t` and after now.
//
// |<---- interval -------------------------^--------->|
// |<---- t ---------------------------->| ^ |
// |<---- new Imax -------------------------^->| |
// | | now | |
timer.Start(TrickleTimer::kModeTrickle, 2000, 2000);
interval = timer.GetInterval();
t = timer.GetTimeInInterval();
VerifyOrQuit(interval == 2000);
VerifyOrQuit((interval / 2 <= t) && (t < interval));
timer.SetIntervalMin(500);
AdvanceTime(t);
timer.VerifyTimerDidFire();
newIntervalMax = Min(t + 1, interval - 1);
timer.SetIntervalMax(newIntervalMax);
VerifyOrQuit(timer.GetInterval() == newIntervalMax);
fireTime = timer.GetFireTime();
AdvanceTime(1);
timer.VerifyTimerDidNotFire();
VerifyOrQuit(fireTime != timer.GetFireTime());
VerifyOrQuit(timer.GetInterval() == timer.GetIntervalMax());
printf(" --> PASSED\n");
testFreeInstance(instance);
}
} // namespace ot
int main(void)
{
ot::TestTrickleTimerPlainMode();
ot::TestTrickleTimerTrickleMode(/* aRedundancyConstant */ 5, /* aConsistentCalls */ 3);
ot::TestTrickleTimerTrickleMode(/* aRedundancyConstant */ 3, /* aConsistentCalls */ 3);
ot::TestTrickleTimerMinMaxIntervalChange();
printf("All tests passed\n");
return 0;
}