Files
micropython/tests/ports/rp2/rp2_lightsleep_thread.py
Damien George b3c8ed6c33 tests/ports/rp2: Decrease test lower bound for thread lightsleep time.
This makes the rp2-specific lightsleep test more lenient, so it passes.

This test was added in PR #16454 / 69993daa5c
and even back then it did not pass reliably on RP2040.  The issue is that
threads can race each other to enter lightsleep mode, and when one of them
actually stops the CPU clock the other thread is stopped as well.
Depending on whether the other thread was just entering or just exiting
lightsleep itself, the total duration of all sleeps can vary and can be as
small as 2*T.

Therefore, adjust the minimum to allow 2*T.

`machine.lightsleep()` is anyway 1) specified only to wait at most the
given time; and 2) not well specified when multiple threads call it at the
same time.  So it seems OK to make the test more lenient.

Work done in collaboration with @projectgus.

Signed-off-by: Damien George <damien@micropython.org>
2025-09-08 11:07:36 +10:00

72 lines
2.7 KiB
Python

# Verify that a thread running on CPU1 can go to lightsleep
# and wake up in the expected timeframe
import _thread
import time
import unittest
from machine import lightsleep
N_SLEEPS = 5
SLEEP_MS = 250
IDEAL_RUNTIME = N_SLEEPS * SLEEP_MS
MIN_RUNTIME = 2 * SLEEP_MS
MAX_RUNTIME = (N_SLEEPS + 1) * SLEEP_MS
MAX_DELTA = 20
class LightSleepInThread(unittest.TestCase):
def thread_entry(self, is_thread=True):
for _ in range(N_SLEEPS):
lightsleep(SLEEP_MS)
if is_thread:
self.thread_done = True
def elapsed_ms(self):
return time.ticks_diff(time.ticks_ms(), self.t0)
def setUp(self):
self.thread_done = False
self.t0 = time.ticks_ms()
def test_cpu0_busy(self):
_thread.start_new_thread(self.thread_entry, ())
# CPU0 is busy-waiting not asleep itself
while not self.thread_done:
self.assertLessEqual(self.elapsed_ms(), MAX_RUNTIME)
self.assertAlmostEqual(self.elapsed_ms(), IDEAL_RUNTIME, delta=MAX_DELTA)
def test_cpu0_sleeping(self):
_thread.start_new_thread(self.thread_entry, ())
time.sleep_ms(MAX_RUNTIME)
self.assertTrue(self.thread_done)
self.assertAlmostEqual(self.elapsed_ms(), MAX_RUNTIME, delta=MAX_DELTA)
def test_cpu0_also_lightsleep(self):
_thread.start_new_thread(self.thread_entry, ())
time.sleep_ms(50) # account for any delay in starting the thread
self.thread_entry(False) # does the same lightsleep loop, doesn't set the done flag
while not self.thread_done:
time.sleep_ms(10)
#
# Only one thread can actually be in lightsleep at a time to avoid
# races, but otherwise the behaviour when both threads call lightsleep()
# is unspecified.
#
# Currently, the other thread will return immediately if one is already
# in lightsleep doing set up, or waking up. When a thread is actually in the
# sleep section of lightsleep, the CPU clock is stopped and that also stops
# the other thread. It's possible for the total sleep time of this test to
# be less than IDEAL_RUNTIME due to the order in which each thread gets to go
# to sleep first and whether the other thread is paused before or after it
# tries to enter lightsleep itself.
#
# Note this test case is really only here to ensure that the rp2 hasn't
# hung or failed to sleep at all - not to verify any correct behaviour
# when there's a race to call lightsleep().
self.assertGreaterEqual(self.elapsed_ms(), MIN_RUNTIME - MAX_DELTA)
self.assertLessEqual(self.elapsed_ms(), IDEAL_RUNTIME * 2 + MAX_DELTA)
if __name__ == "__main__":
unittest.main()