Files
MeshCore/test/test_radio_profile_cli.py
T
mikecarper c650278fa7 Share constrained STM32 code while preserving firmware features
Omit unbound forwarding-region callbacks only for exclusive Companion source roles. Reuse the compact neighbor sorter in repeaters for every RPC order and the CLI list, removing duplicate sort implementations while preserving storage and size gates.
2026-10-27 08:17:23 -07:00

90 lines
4.6 KiB
Python

"""Run the production shared profile command parser and persistence with a memory FS."""
from pathlib import Path
import itertools
import shutil
import subprocess
import tempfile
import unittest
ROOT = Path(__file__).resolve().parents[1]
class RadioProfileCLITest(unittest.TestCase):
def test_all_companions_keep_rf_presets_without_unused_region_code(self):
compiler = shutil.which('g++') or shutil.which('clang++')
self.assertIsNotNone(compiler)
code = r'''
#define main existing_cli_main
#include "test/fixtures/radio_profiles/cli_test.cpp"
#undef main
int main() {
Fixture f;
MediaScope scope;
f.cli.setMediaMeshScopeHandler(MediaScope::update, &scope);
f.cmd("set radio2 mediamesh");
f.advance(2000);
assert(f.radio.p.enabled() && fabsf(f.radio.p.secondary.params.freq - 911.150f) < 0.0001f);
#if defined(COMPANION_RADIO_FULL) || MESH_ROLE_COMPANION
assert(!scope.commits && !scope.checks);
#else
assert(scope.enabled && scope.durable && scope.commits);
#endif
f.cmd("set radio2 off");
assert(!scope.enabled);
printf("%zu", sizeof(mesh::RadioProfileCLI));
}
'''
sizes = {}
with tempfile.TemporaryDirectory(prefix='profile-region-role-') as work:
source = Path(work)/'role.cpp'
source.write_text(code, encoding='ascii')
roles = {'infrastructure': [], 'full': ['-DCOMPANION_RADIO_FULL=1'],
'ordinary': ['-DMESH_ROLE_COMPANION=1']}
for role, flags in roles.items():
companion = role != 'infrastructure'
binary = Path(work)/role
compiled = subprocess.run([
compiler, '-std=c++17', '-Os', '-ffunction-sections',
'-fdata-sections', '-Wl,--gc-sections',
'-DMESH_ENABLE_FLEET_CONTROL=0', '-DMESH_ENABLE_CARRIER_TOOLS=0',
'-DMESH_ENABLE_MESHTEXT_CHAT=0', '-DMESH_ENABLE_HOST_KISS=0',
'-DMESH_ENABLE_ETHERNET_TUNNEL=0', *flags,
'-I', str(ROOT), '-I', str(ROOT/'test/fixtures/radio_profiles/mocks'),
'-I', str(ROOT/'test/mocks'), '-I', str(ROOT/'src'),
str(ROOT/'src/helpers/RadioProfileCLI.cpp'), str(source),
'-o', str(binary)], capture_output=True, text=True, timeout=60)
self.assertEqual(compiled.returncode, 0, compiled.stderr)
run = subprocess.run([str(binary)], capture_output=True, text=True, timeout=10)
self.assertEqual(run.returncode, 0, run.stdout+run.stderr)
sizes[role] = int(run.stdout)
image = binary.read_bytes()
for marker in (b'MediaMesh scope save pending', b'MediaMesh region unavailable or full'):
self.assertEqual(marker in image, not companion)
for role in ('full', 'ordinary'):
self.assertLess(sizes[role], sizes['infrastructure'])
def test_commands_persistence_and_timers(self):
compiler = shutil.which('g++') or shutil.which('clang++')
self.assertIsNotNone(compiler)
with tempfile.TemporaryDirectory() as work:
# Compact STM32 images omit fleet staging. Both paths must preserve
# local schedules, persistence retries and temporary lease bounds.
for fleet_enabled, carrier_enabled in itertools.product((False, True), repeat=2):
exe = Path(work) / f'profiles-{int(fleet_enabled)}-{int(carrier_enabled)}.exe'
subprocess.run([compiler, '-std=c++17', '-Wall', '-Wextra',
f'-DMESH_ENABLE_FLEET_CONTROL={int(fleet_enabled)}',
f'-DMESH_ENABLE_CARRIER_TOOLS={int(carrier_enabled)}',
'-I', str(ROOT/'test/fixtures/radio_profiles/mocks'),
'-I', str(ROOT/'test/mocks'), '-I', str(ROOT/'src'),
str(ROOT/'src/helpers/RadioProfileCLI.cpp'),
str(ROOT/'test/fixtures/radio_profiles/cli_test.cpp'),
'-o', str(exe)], check=True, capture_output=True, text=True)
for scenario in (None, 'permanent_order', 'temporary_boundary', 'lease_creation',
'save_failure', 'schedule_permutations', 'storage_isolation',
'relative_schedule', 'spectrum_overlap', 'mediamesh', 'media_scope'):
with self.subTest(fleet=fleet_enabled, carrier=carrier_enabled,
scenario=scenario or 'commands_persistence_and_timers'):
subprocess.run([str(exe)] + ([scenario] if scenario else []), check=True)
if __name__ == '__main__':
unittest.main()