Files
MeshCore/test/test_radio_profile_cli.py
T

133 lines
7.4 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_repeater_retains_radio2_presets_schedules_and_transactions(self):
compiler = shutil.which('g++') or shutil.which('clang++')
self.assertIsNotNone(compiler)
with tempfile.TemporaryDirectory(prefix='profile-repeater-') as work:
binary = Path(work) / 'repeater'
compiled = subprocess.run([
compiler, '-std=c++17', '-Os', '-Wall', '-Wextra',
'-DMESH_CLIENT_REPEATER_ONLY=1', '-DMESH_ENABLE_FLEET_CONTROL=0',
'-DMESH_ENABLE_CARRIER_TOOLS=0',
'-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(binary)], capture_output=True, text=True, timeout=60)
self.assertEqual(compiled.returncode, 0, compiled.stderr)
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(scenario=scenario):
ran = subprocess.run([str(binary)] + ([scenario] if scenario else []),
capture_output=True, text=True, timeout=10)
self.assertEqual(ran.returncode, 0, ran.stdout + ran.stderr)
symbols = subprocess.check_output(['nm', '-C', str(binary)], text=True)
self.assertNotIn('RadioProfileCLI::stageChannelPreset(', symbols)
self.assertNotIn('RadioProfileCLI::finishChannelPreset(', symbols)
def test_channel_preset_preserves_secondary_owners(self):
compiler = shutil.which('g++') or shutil.which('clang++')
self.assertIsNotNone(compiler)
with tempfile.TemporaryDirectory(prefix='profile-channel-owner-') as work:
binary = Path(work) / 'channel-owner'
compiled = subprocess.run([
compiler, '-std=c++17', '-Wall', '-Wextra',
'-DMESH_ENABLE_MESHTEXT_CHAT=1',
'-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(binary)], capture_output=True, text=True, timeout=60)
self.assertEqual(compiled.returncode, 0, compiled.stderr)
subprocess.run([str(binary), 'channel_preset'], check=True, timeout=10)
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', 'channel_preset',
'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()