"""Execute optional RAM timing aggregation without radio hardware.""" from pathlib import Path import os import subprocess import tempfile import unittest from test_radio_receive_contract import method import test_radio_profile_scan as scan ROOT = Path(__file__).resolve().parents[1] class ProfileSwitchTimingTest(unittest.TestCase): def compile_run(self, source): with tempfile.TemporaryDirectory() as directory: folder = Path(directory) (folder / "Arduino.h").write_text( "#pragma once\n#include \n#include \n") cpp, binary = folder / "test.cpp", folder / "test.exe" cpp.write_text(source, encoding="ascii") built = subprocess.run([os.environ.get("CXX", "g++"), "-std=c++17", "-Wall", "-Wextra", "-I", str(folder), "-I", str(ROOT / "src"), str(cpp), "-o", str(binary)], capture_output=True, text=True) self.assertEqual(built.returncode, 0, built.stderr) ran = subprocess.run([str(binary)], capture_output=True, text=True) self.assertEqual(ran.returncode, 0, ran.stderr) @staticmethod def production_globals(): source = (ROOT / "src/helpers/radiolib/RadioLibWrappers.cpp").read_text() return source.split("#if MESH_HIL_PROFILE_TIMING\n", 1)[1].split("#endif", 1)[0] def test_direction_histogram_clock_wrap_failures_and_split_sum(self): self.compile_run(r''' #include #include #define MESH_HIL_PROFILE_TIMING 1 #define NRF52_PLATFORM 1 struct FakeDwt { uint32_t CYCCNT=0; } fakeDwt; #define DWT (&fakeDwt) static bool clockEnabled=false; static unsigned enableCalls=0; bool dwt_enabled() { return clockEnabled; } void dwt_enable() { clockEnabled=true; ++enableCalls; } #include @GLOBALS@ int main() { using namespace mesh::profiletiming; auto& capture=mesh_hil_profile_timing; assert(capture.magic==Magic && capture.version==3 && capture.size_bytes==2192); assert(capture.clock_hz==64000000 && capture.sequence==0); initializeClock(); assert(enableCalls==1 && capture.clock_state==2); initializeClock(); assert(enableCalls==1 && capture.clock_state==3 && capture.clock_initializations==2); fakeDwt.CYCCNT=1234; assert(readClock()==1234); // Raw counter wrap inside an awake hop is handled before /64 conversion. uint32_t ticks=0xfffff000U; begin(0,1,ticks); for(uint32_t stage=0;stage(stage),ticks); } finish(Success,ticks); assert(capture.sequence==2 && !(capture.sequence&1)); const auto& forward=capture.direction[0]; assert(forward.successes==1 && forward.busy==0 && forward.failures==0); assert(forward.last_mask==2047 && forward.last_us[Whole]==850); assert(forward.worst_us[Whole]==850 && forward.worst_us[Frequency]==70); assert(forward.stage[Whole].sum_low_us==850 && forward.stage[Whole].sum_high_us==0); assert(forward.stage[Whole].min_us==850 && forward.stage[Whole].max_us==850); assert(forward.stage[Whole].histogram[13]==1); for(uint32_t stage=0;stage #include #define MESH_HIL_PROFILE_TIMING 1 #define MESH_ENABLE_TX_BEACONS 1 #define RADIOLIB_ERR_NONE 0 #define STATE_IDLE 0 #define STATE_RX 1 #define STATE_TX_WAIT 3 static uint32_t now_us=0; uint32_t micros() { return now_us; } #include @GLOBALS@ struct RadioLibWrapper { bool _beacon_owned=false,_beacon_restoring=false,_cw_active=false; bool _profile_repair_required=false,_rx_hold_continuous=true; bool _rx_ps_armed=false,_profile_refresh_required=false; bool pending=false,busy=false,receiving=false,post_irq=false; uint8_t state=STATE_RX; unsigned standby_calls=0; int16_t standby_result=0; struct Chip { RadioLibWrapper* owner; int16_t standby() { ++owner->standby_calls;now_us+=20; if(owner->post_irq) owner->pending=true; return owner->standby_result; } } chip{this}; Chip* _radio=&chip; bool hasPendingRadioInterrupt() { now_us+=2;return pending; } bool isChipBusy() { now_us+=3;return busy; } bool isReceivingPacket() { now_us+=5;return receiving; } void claimRadioInterrupt() { now_us+=4;pending=false; } uint8_t beginReconfigure(); }; @METHOD@ int main() { using namespace mesh::profiletiming; auto& direction=mesh_hil_profile_timing.direction[0]; // Success, already idle, each admission guard, both standby failures, // an IRQ arriving in standby, and the physical-repair branch. for(unsigned scenario=0;scenario<13;++scenario) { RadioLibWrapper radio; uint8_t expected_result=2; unsigned admission=0,standby=0,post=0,calls=0; switch(scenario) { case 0: expected_result=1;admission=10;standby=20;post=2;calls=1;break; case 1: radio.state=STATE_IDLE;expected_result=0;admission=10;post=2;break; case 2: radio.pending=true;admission=2;break; case 3: radio.state=STATE_TX_WAIT;admission=2;break; case 4: radio.busy=true;admission=5;break; case 5: radio.receiving=true;admission=10;break; case 6: radio._cw_active=true;break; case 7: radio.standby_result=-1;expected_result=3;admission=10;standby=20;calls=1;break; case 8: radio.post_irq=true;admission=10;standby=20;post=2;calls=1;break; case 9: radio._profile_repair_required=true;expected_result=1;admission=7;standby=20;post=4;calls=1;break; case 10: radio._profile_repair_required=true;radio.busy=true;admission=3;break; case 11: radio._profile_repair_required=true;radio.standby_result=-1; expected_result=3;admission=7;standby=20;calls=1;break; case 12: radio._beacon_owned=true;break; } now_us=0;begin(0,1,now_us); const uint32_t samples=direction.stage[Admission].count; assert(radio.beginReconfigure()==expected_result); assert(radio.standby_calls==calls); const Result result=expected_result<2 ? Success : expected_result==2 ? Busy : Failure; finish(result,now_us); assert(direction.last_mask==((1U<