133 lines
7.4 KiB
Python
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()
|