Fix watchdog test timing and run hardware regression fixtures in CI

Arm the test watchdog through initial loop service and assert both recovery boundaries. Update cooperative contact negative controls for the current background APIs and run the existing USB, contact persistence, and startup regressions in Actions.

Validation: 1762 native tests, 48 added-workflow fixture tests, and 18 related persistence/logging checks passed locally.
This commit is contained in:
mikecarper committed 2026-10-04 12:53:37 -07:00
1 parent 9c43efb0bc
commit 6516c719e1
3 files changed
+39 -9

No files matched your search

+10 -1
View File
@@ -159,6 +159,7 @@ jobs:
- name: Verify receive recovery and pinned radio status transport
run: |
python3 -B test/test_dispatcher_startup_watchdog.py -v
python3 -B test/test_radio_receive_contract.py
python3 - <<'PY'
from pathlib import Path
@@ -244,13 +245,21 @@ jobs:
python3 -B test/test_esp32_usb_serial_hygiene.py
python3 -B test/test_esp32_tinyusb_nonblocking.py
python3 -B test/test_esp32_usb_session_fix.py
python3 -B test/test_hwcdc_tx_backport.py -v
python3 -B test/test_esp32_file_buffer_fix.py -v
python3 -B test/test_companion_usb_logging_reply.py
python3 -B test/test_esp32_tinyusb_cooperative_output.py
python3 -B test/test_esp32_tinyusb_role_hygiene.py
python3 -B test/test_stm32_float_conversion.py
- name: Verify Companion contact persistence and OTA sleep guards
run: python3 -B test/test_companion_contact_sleep_contract.py -v
run: |
python3 -B test/test_companion_contact_sleep_contract.py -v
python3 -B test/test_esp32_cooperative_contacts.py -v
python3 -B test/test_esp32_contact_writer_batch.py -v
python3 -B test/test_esp32_contact_crc_batch.py -v
python3 -B test/test_esp32_contact_publication.py -v
python3 -B test/test_esp32_advert_cache.py -v
- name: Verify Companion Bluetooth CLI connection protection
run: python3 -B test/test_companion_bluetooth_control.py -v
+12 -8
View File
@@ -81,10 +81,10 @@ class UncachedESPContactsTest(unittest.TestCase):
'"/contacts3", companionPathPresence, true)',
'"/contacts3", companionPathPresence, false)')
elif disable == "crc_batch":
self.assertIn("_contact_write->serviceCommit(true, 8)", implementation)
anchor = "_contact_write->serviceCommit(true, 8, true)"
self.assertEqual(implementation.count(anchor), 1)
implementation = implementation.replace(
"_contact_write->serviceCommit(true, 8)",
"_contact_write->serviceCommit()")
anchor, "_contact_write->serviceCommit(true, 1, true)")
# Only the native SDK stat seam is substituted. The production
# presence logic still distinguishes ENOENT from metadata failure.
presence = method(source, "static bool companionPathPresence(")
@@ -109,13 +109,17 @@ class UncachedESPContactsTest(unittest.TestCase):
transaction = transaction.replace("ESP_VERIFY_BUFFER_SIZE = 128;",
"ESP_VERIFY_BUFFER_SIZE = 64;")
if disable == "writer":
self.assertIn("ok = _file.setBufferSize(ESP_WRITE_BUFFER_SIZE);", transaction)
anchor = ("ok = _file.setBufferSize(background_contact_write\n"
" ? ESP_BACKGROUND_WRITE_BUFFER_SIZE : ESP_WRITE_BUFFER_SIZE);")
self.assertEqual(transaction.count(anchor), 1)
transaction = transaction.replace(
"ok = _file.setBufferSize(ESP_WRITE_BUFFER_SIZE);",
anchor,
"ok = true; // Negative control: retain default writer buffer.")
elif disable == "reader":
self.assertIn("ok = ok && _verify.setBufferSize(ESP_VERIFY_BUFFER_SIZE);", transaction)
transaction = transaction.replace("ok = ok && _verify.setBufferSize(ESP_VERIFY_BUFFER_SIZE);",
anchor = ("ok = ok && _verify.setBufferSize(\n"
" max_verify_chunks >= 8 ? 512 : ESP_VERIFY_BUFFER_SIZE);")
self.assertEqual(transaction.count(anchor), 1)
transaction = transaction.replace(anchor,
"// Negative control: retain the SDK's default reader buffer.")
transaction = transaction.replace('#include "IdentityStore.h"',
'#include <helpers/IdentityStore.h>')
@@ -167,7 +171,7 @@ class UncachedESPContactsTest(unittest.TestCase):
return measurements
else:
self.assertNotEqual(result.returncode, 0)
marker = ("startup_operations" if disable == "begin" else
marker = ("SPIFFS.startupOperations() - before == 1" if disable == "begin" else
"crc_read_passes == 104" if disable == "crc_batch" else
"largest_backend_" + ("write" if disable == "writer" else "read"))
self.assertIn(marker, result.stderr)
@@ -1040,10 +1040,27 @@ TEST(Dispatcher, RadioOutsideReceiveModeEscalatesOnSecondAttempt) {
TestDispatcher dispatcher(radio, clock, manager);
dispatcher.begin();
// The non-RX watchdog starts when loop() first services the radio.
dispatcher.loop();
EXPECT_EQ(0, radio.soft_recoveries);
EXPECT_EQ(0, radio.hard_recoveries);
EXPECT_EQ(0, dispatcher.getErrFlags() & ERR_EVENT_STARTRX_TIMEOUT);
clock.now = 8000;
dispatcher.loop();
EXPECT_EQ(0, radio.soft_recoveries);
EXPECT_EQ(0, radio.hard_recoveries);
clock.now = 8001;
dispatcher.loop();
EXPECT_EQ(1, radio.soft_recoveries);
EXPECT_EQ(0, radio.hard_recoveries);
EXPECT_NE(0, dispatcher.getErrFlags() & ERR_EVENT_STARTRX_TIMEOUT);
clock.now = 16001;
dispatcher.loop();
EXPECT_EQ(1, radio.soft_recoveries);
EXPECT_EQ(0, radio.hard_recoveries);
clock.now = 16002;
dispatcher.loop();