140 lines
6.4 KiB
Python
140 lines
6.4 KiB
Python
"""Run real role deadlines while preserving timed sleep and interrupt idle."""
|
|
from pathlib import Path
|
|
import os
|
|
import subprocess
|
|
import tempfile
|
|
import unittest
|
|
|
|
from test_radio_receive_contract import method
|
|
|
|
ROOT = Path(__file__).resolve().parents[1]
|
|
|
|
HARNESS = r'''
|
|
#include <cassert>
|
|
#include <cstdint>
|
|
#include <initializer_list>
|
|
#include <helpers/PowerManagementUtils.h>
|
|
#define SENSOR_READ_INTERVAL_SECS 60
|
|
static uint32_t now_ms = 1000;
|
|
uint32_t millis() { return now_ms; }
|
|
struct Node {
|
|
bool pending = false, queue = false, retry = false;
|
|
uint32_t queue_ms = 0, retry_ms = 0;
|
|
struct { bool battery_alert_enabled = false; } _prefs;
|
|
struct { int getNumClients() const { return 0; } } acl;
|
|
unsigned long next_flood_advert = 0, next_local_advert = 0;
|
|
unsigned long dirty_contacts_expiry = 0, next_recent_repeater_sweep = 0;
|
|
unsigned long next_battery_alert_check = 0, next_push = 0;
|
|
unsigned long radio_apply_retry_at = 0, set_radio_at = 0, revert_radio_at = 0;
|
|
uint32_t last_read_time = 100;
|
|
struct Alert { unsigned long send_expiry = 0; } alert;
|
|
Alert* alert_tasks[1] = {&alert};
|
|
int num_alert_tasks = 0;
|
|
struct Clock { uint32_t getCurrentTime() const { return 100; } } clock;
|
|
const Clock* getRTCClock() const { return &clock; }
|
|
bool hasPendingWork() const { return pending; }
|
|
bool getNextQueueWakeDelay(uint32_t& out) const { out = queue_ms; return queue; }
|
|
bool getNextRetryWakeDelay(uint32_t& out) const { out = retry_ms; return retry; }
|
|
bool millisHasNowPassed(unsigned long at) const {
|
|
return int32_t(now_ms - uint32_t(at)) > 0;
|
|
}
|
|
uint32_t limitSleepToScheduledRadioWork(uint32_t seconds) const { return seconds; }
|
|
uint32_t limitSleepToMillisTimer(unsigned long, uint32_t) const;
|
|
uint32_t getPowerSaveSleepSeconds(uint32_t) const;
|
|
};
|
|
@METHODS@
|
|
int main() {
|
|
Node node;
|
|
assert(mesh::power::powerSaveSleepSeconds(0) == 0);
|
|
#if EXPECT_INTERRUPT_IDLE
|
|
assert(mesh::power::powerSaveSleepSeconds(UINT32_MAX) == 4294968U);
|
|
#else
|
|
assert(mesh::power::powerSaveSleepSeconds(UINT32_MAX) == 4294967U);
|
|
#endif
|
|
assert(node.getPowerSaveSleepSeconds(30) == 30);
|
|
assert(node.getPowerSaveSleepSeconds(0) == 0);
|
|
assert(node.limitSleepToMillisTimer(0, 30) == 30); // Disabled timer sentinel.
|
|
assert(node.limitSleepToMillisTimer(1001, 0) == 0);
|
|
node.pending = true;
|
|
assert(node.getPowerSaveSleepSeconds(30) == 0);
|
|
node.pending = false;
|
|
for (uint32_t now : {1000U, UINT32_MAX - 15U}) {
|
|
now_ms = now;
|
|
// Timed sleep ends before the deadline. Event-driven boards instead permit
|
|
// interrupt idle for every positive remainder, including across rollover.
|
|
for (uint32_t ms : {0U, 1U, 999U, 1000U, 1001U, 1999U, 2000U,
|
|
2500U, 29999U, 30000U, 30001U, 60000U}) {
|
|
const uint32_t whole_seconds = ms / 1000;
|
|
#if EXPECT_INTERRUPT_IDLE
|
|
// Event-driven boards ignore seconds and wake at the next IRQ.
|
|
const uint32_t idle_seconds = whole_seconds + (ms % 1000 != 0);
|
|
#else
|
|
const uint32_t idle_seconds = whole_seconds;
|
|
#endif
|
|
const uint32_t expected = idle_seconds < 30 ? idle_seconds : 30;
|
|
node.queue = true; node.queue_ms = ms;
|
|
assert(node.getPowerSaveSleepSeconds(30) == expected);
|
|
node.queue = false; node.retry = true; node.retry_ms = ms;
|
|
assert(node.getPowerSaveSleepSeconds(30) == expected);
|
|
node.retry = false;
|
|
const uint32_t deadline = now + ms;
|
|
assert(deadline != 0); // Zero has a separate intentional sentinel meaning.
|
|
assert(node.limitSleepToMillisTimer(deadline, 30) == expected);
|
|
node.next_flood_advert = deadline;
|
|
assert(node.getPowerSaveSleepSeconds(30) == expected);
|
|
node.next_flood_advert = 0;
|
|
}
|
|
assert(node.limitSleepToMillisTimer(uint32_t(now - 1), 30) == 0);
|
|
node.queue = true; node.queue_ms = 2500;
|
|
node.retry = true; node.retry_ms = 1001;
|
|
#if EXPECT_INTERRUPT_IDLE
|
|
assert(node.getPowerSaveSleepSeconds(30) == 2); // Interrupt idle is permitted.
|
|
#else
|
|
assert(node.getPowerSaveSleepSeconds(30) == 1); // Earliest timed owner wins.
|
|
#endif
|
|
node.dirty_contacts_expiry = uint32_t(now + 999);
|
|
#if EXPECT_INTERRUPT_IDLE
|
|
assert(node.getPowerSaveSleepSeconds(30) == 1);
|
|
#else
|
|
assert(node.getPowerSaveSleepSeconds(30) == 0);
|
|
#endif
|
|
node.dirty_contacts_expiry = 0;
|
|
node.queue = node.retry = false;
|
|
}
|
|
}
|
|
'''
|
|
|
|
|
|
class PowerSavingDeadlineTests(unittest.TestCase):
|
|
def test_real_role_deadlines_preserve_timed_and_interrupt_idle(self):
|
|
with tempfile.TemporaryDirectory(prefix='meshcore-sleep-deadlines-') as directory:
|
|
work = Path(directory)
|
|
for role, filename, name in (
|
|
('repeater', 'examples/simple_repeater/MyMesh.cpp', 'MyMesh'),
|
|
('room', 'examples/simple_room_server/MyMesh.cpp', 'MyMesh'),
|
|
('sensor', 'examples/simple_sensor/SensorMesh.cpp', 'SensorMesh')):
|
|
for platform in ('', 'NRF52_PLATFORM', 'RP2040_PLATFORM', 'STM32_PLATFORM'):
|
|
with self.subTest(role=role, platform=platform or 'timed'):
|
|
source = (ROOT / filename).read_text()
|
|
methods = '\n'.join(method(source, signature) for signature in (
|
|
f'uint32_t {name}::limitSleepToMillisTimer(',
|
|
f'uint32_t {name}::getPowerSaveSleepSeconds('))
|
|
methods = methods.replace(name + '::', 'Node::')
|
|
cpp, exe = work / (role + '.cpp'), work / role
|
|
cpp.write_text(HARNESS.replace('@METHODS@', methods))
|
|
sanitizers = [] if os.name == 'nt' else [
|
|
'-fsanitize=address,undefined', '-fno-sanitize-recover=all',
|
|
'-fno-pie', '-no-pie']
|
|
built = subprocess.run([os.environ.get('CXX', 'g++'), '-std=c++17',
|
|
'-Wall', '-Wextra', '-Werror', *sanitizers,
|
|
'-DEXPECT_INTERRUPT_IDLE=' + ('1' if platform else '0'),
|
|
*(['-D' + platform + '=1'] if platform else []), '-I', str(ROOT / 'src'),
|
|
str(cpp), '-o', str(exe)], capture_output=True, text=True)
|
|
self.assertEqual(built.returncode, 0, built.stdout + built.stderr)
|
|
ran = subprocess.run([str(exe)], capture_output=True, text=True)
|
|
self.assertEqual(ran.returncode, 0, ran.stdout + ran.stderr)
|
|
|
|
|
|
if __name__ == '__main__':
|
|
unittest.main()
|