208 lines
8.5 KiB
Python
208 lines
8.5 KiB
Python
#!/usr/bin/env python3
|
|
"""Run the actual task wrapper and BLE startup with injected allocation failures."""
|
|
|
|
from pathlib import Path
|
|
import subprocess
|
|
import tempfile
|
|
import unittest
|
|
|
|
from test_t096_full_memory import method
|
|
|
|
ROOT = Path(__file__).resolve().parents[1]
|
|
|
|
RTOS = r'''
|
|
#pragma once
|
|
#include <cstdint>
|
|
using BaseType_t = int;
|
|
using UBaseType_t = unsigned;
|
|
using TaskHandle_t = void*;
|
|
using TaskFunction_t = void (*)(void*);
|
|
using configSTACK_DEPTH_TYPE = uint16_t;
|
|
constexpr BaseType_t pdPASS = 1;
|
|
'''
|
|
|
|
HARNESS = r'''
|
|
#include "Arduino.h"
|
|
#include <cassert>
|
|
#include <atomic>
|
|
#include <cstdio>
|
|
#include <cstring>
|
|
#include <helpers/BluetoothMac.h>
|
|
#include <helpers/BleMotaProtocol.h>
|
|
#include <helpers/nrf52/BleTaskStartup.h>
|
|
#ifndef COMPANION_FEATURE_BLE_MOTA_SOURCE
|
|
#define COMPANION_FEATURE_BLE_MOTA_SOURCE 1
|
|
#endif
|
|
#define BLE_DEBUG_PRINTLN(...) do {} while (0)
|
|
constexpr int BANDWIDTH_MAX = 3, NRF_SUCCESS = 0, ERROR_NONE = 0;
|
|
constexpr int BLE_GAP_ADDR_TYPE_RANDOM_STATIC = 1, BLE_TX_POWER = 4;
|
|
constexpr int BLE_MIN_CONN_INTERVAL = 12, BLE_MAX_CONN_INTERVAL = 24;
|
|
constexpr int BLE_SLAVE_LATENCY = 4, BLE_CONN_SUP_TIMEOUT = 200;
|
|
constexpr int SECMODE_ENC_WITH_MITM = 1, SECMODE_NO_ACCESS = 0;
|
|
constexpr int CHR_PROPS_NOTIFY = 1, CHR_PROPS_WRITE = 2;
|
|
constexpr int BLE_GAP_ADV_TYPE_CONNECTABLE_SCANNABLE_UNDIRECTED = 0;
|
|
constexpr int BLE_GAP_ADV_FLAGS_LE_ONLY_GENERAL_DISC_MODE = 2;
|
|
constexpr int BLE_ADV_INTERVAL_MIN = 32, BLE_ADV_INTERVAL_MAX = 244, BLE_ADV_FAST_TIMEOUT = 30;
|
|
struct ble_gap_addr_t { int addr_type; uint8_t addr[6]; };
|
|
struct ble_gap_conn_params_t { unsigned min_conn_interval, max_conn_interval, slave_latency, conn_sup_timeout; };
|
|
int sd_ble_gap_addr_set(ble_gap_addr_t*) { return NRF_SUCCESS; }
|
|
int sd_ble_gap_addr_get(ble_gap_addr_t* a) { memset(a, 0, sizeof(*a)); return NRF_SUCCESS; }
|
|
int sd_ble_gap_ppcp_set(ble_gap_conn_params_t*) { return NRF_SUCCESS; }
|
|
namespace mesh {
|
|
struct Logging { void println(const char*) {} };
|
|
Logging& usbLoggingPort() { static Logging port; return port; }
|
|
}
|
|
|
|
static const char* fail_task = nullptr;
|
|
static int fail_service = 0;
|
|
static bool fail_softdevice = false;
|
|
static unsigned last_depth;
|
|
extern "C" BaseType_t __wrap_xTaskCreate(TaskFunction_t, const char*,
|
|
configSTACK_DEPTH_TYPE, void*, UBaseType_t, TaskHandle_t*);
|
|
extern "C" BaseType_t __real_xTaskCreate(TaskFunction_t, const char* name,
|
|
configSTACK_DEPTH_TYPE depth, void*, UBaseType_t, TaskHandle_t* out) {
|
|
last_depth = depth;
|
|
bool fail = fail_task && name && strcmp(name, fail_task) == 0;
|
|
if (out) *out = fail ? nullptr : reinterpret_cast<void*>(1);
|
|
return fail ? 0 : pdPASS;
|
|
}
|
|
struct Settings {
|
|
template<class... T> void clearBonds(T...) {}
|
|
template<class... T> void setMITM(T...) {}
|
|
template<class... T> void setPIN(T...) {}
|
|
template<class... T> void setIOCaps(T...) {}
|
|
template<class... T> void setPairPasskeyCallback(T...) {}
|
|
template<class... T> void setPairCompleteCallback(T...) {}
|
|
template<class... T> void setConnectCallback(T...) {}
|
|
template<class... T> void setDisconnectCallback(T...) {}
|
|
template<class... T> void setSecuredCallback(T...) {}
|
|
template<class... T> void setType(T...) {}
|
|
template<class... T> void addFlags(T...) {}
|
|
template<class... T> void addTxPower(T...) {}
|
|
template<class... T> void addService(T...) {}
|
|
template<class... T> void addName(T...) {}
|
|
template<class... T> void setInterval(T...) {}
|
|
template<class... T> void setFastTimeout(T...) {}
|
|
template<class... T> void restartOnDisconnect(T...) {}
|
|
};
|
|
struct BluefruitStub {
|
|
Settings Security, Periph, Advertising, ScanResponse;
|
|
int starts = 0;
|
|
void configPrphBandwidth(int) {}
|
|
bool begin() {
|
|
++starts;
|
|
if (fail_softdevice) return false;
|
|
TaskHandle_t task;
|
|
// Match the pinned core: it ignores both worker creation return values.
|
|
__wrap_xTaskCreate(nullptr, "BLE", 1280, nullptr, 3, &task);
|
|
__wrap_xTaskCreate(nullptr, "SOC", 200, nullptr, 3, &task);
|
|
return true;
|
|
}
|
|
void setTxPower(int) {}
|
|
void setName(const char*) {}
|
|
template<class T> void setEventCallback(T) {}
|
|
} Bluefruit;
|
|
struct Service {
|
|
int id;
|
|
int begin() { return fail_service == id ? 1 : ERROR_NONE; }
|
|
template<class... T> void setPermission(T...) {}
|
|
template<class... T> void setRxCallback(T...) {}
|
|
template<class... T> void setProperties(T...) {}
|
|
template<class... T> void setMaxLen(T...) {}
|
|
template<class... T> void setUserDescriptor(T...) {}
|
|
template<class... T> void setWriteCallback(T...) {}
|
|
};
|
|
struct MotaStream {
|
|
template<class... T> void setSender(T...) {}
|
|
void setActive(bool) {}
|
|
};
|
|
struct SerialBLEInterface {
|
|
@STARTUP_FIELDS@
|
|
std::atomic<bool> _successfulConnectionPending{false};
|
|
std::atomic<uint32_t> _successfulConnectionStarted{0};
|
|
std::atomic<bool> _bondedOnlyRecoveryPending{false}, _advertisingSuppressed{false};
|
|
bool _stealth_pair_once = false, _bonded_only = false, _bonded_only_configure_pending = false;
|
|
mesh::companion::BluetoothPeerIdentity _pending_bonded_peer;
|
|
Service bleuart{1}, _mota_service{2}, _mota_request{3}, _mota_response{4}, bledfu{5};
|
|
MotaStream _mota_stream;
|
|
static void onPairingPasskey() {}
|
|
static void onPairingComplete() {}
|
|
static void onConnect() {}
|
|
static void onDisconnect() {}
|
|
static void onSecured() {}
|
|
static void onBLEEvent() {}
|
|
static void onBleUartRX() {}
|
|
static void sendMotaRequest() {}
|
|
static void onMotaResponse() {}
|
|
void configureBondedOnlyAdvertising(const mesh::companion::BluetoothPeerIdentity&, bool) {}
|
|
bool begin(const char*, const char*, uint32_t, const uint8_t* = nullptr,
|
|
bool = false, bool = false,
|
|
const mesh::companion::BluetoothPeerIdentity* = nullptr);
|
|
};
|
|
static SerialBLEInterface* instance = nullptr;
|
|
@BEGIN@
|
|
int main() {
|
|
mesh::nrf52::resetBleTaskStartup();
|
|
assert(!mesh::nrf52::bleTasksStarted());
|
|
__wrap_xTaskCreate(nullptr, "loop", 1024, nullptr, 1, nullptr);
|
|
assert(last_depth == 2048 && !mesh::nrf52::bleTasksStarted());
|
|
__wrap_xTaskCreate(nullptr, "callback", 768, nullptr, 1, nullptr);
|
|
assert(last_depth == 768);
|
|
__wrap_xTaskCreate(nullptr, nullptr, 100, nullptr, 1, nullptr);
|
|
assert(last_depth == 100);
|
|
for (int fault = 0; fault <= 8; ++fault) {
|
|
#if !COMPANION_FEATURE_BLE_MOTA_SOURCE
|
|
if (fault >= 5 && fault <= 7) continue;
|
|
#endif
|
|
fail_softdevice = fault == 1;
|
|
fail_task = fault == 2 ? "BLE" : fault == 3 ? "SOC" : nullptr;
|
|
fail_service = fault >= 4 ? fault - 3 : 0;
|
|
SerialBLEInterface port;
|
|
const int starts = Bluefruit.starts;
|
|
const bool expected = fault == 0;
|
|
assert(port.begin("MC-", "T096", 123456) == expected);
|
|
assert(Bluefruit.starts == starts + 1);
|
|
assert((instance == &port) == expected);
|
|
// Retrying a failed partial init must never allocate another stack/FIFO.
|
|
for (int retry = 0; retry < 4; ++retry) {
|
|
assert(port.begin("MC-", "T096", 123456) == expected);
|
|
assert(Bluefruit.starts == starts + 1);
|
|
}
|
|
}
|
|
}
|
|
'''
|
|
|
|
|
|
class Nrf52BleStartupTest(unittest.TestCase):
|
|
def test_all_startup_failures_are_reported_without_reinitializing(self):
|
|
begin = method((ROOT / "src/helpers/nrf52/SerialBLEInterface.cpp").read_text(),
|
|
"bool SerialBLEInterface::begin(")
|
|
header = (ROOT / "src/helpers/nrf52/SerialBLEInterface.h").read_text()
|
|
# Use the real in-class startup initializers, too.
|
|
fields = "\n".join(line for line in header.splitlines()
|
|
if "bool _begin_" in line)
|
|
self.assertTrue(fields)
|
|
source = HARNESS.replace("@BEGIN@", begin).replace("@STARTUP_FIELDS@", fields)
|
|
with tempfile.TemporaryDirectory(prefix="meshcore-ble-start-") as temp:
|
|
temp = Path(temp)
|
|
(temp / "Arduino.h").write_text(RTOS)
|
|
for name in ("FreeRTOS.h", "task.h"):
|
|
(temp / name).write_text('#include "Arduino.h"\n')
|
|
cpp = temp / "startup.cpp"
|
|
cpp.write_text(source)
|
|
binary = temp / "startup"
|
|
for mota in (0, 1):
|
|
with self.subTest(mota=mota):
|
|
built = subprocess.run([
|
|
"c++", "-std=c++17", "-DNRF52_PLATFORM=1",
|
|
f"-DCOMPANION_FEATURE_BLE_MOTA_SOURCE={mota}", "-I" + str(temp),
|
|
"-I" + str(ROOT / "src"), str(cpp), str(ROOT / "src/Nrf52LoopStack.cpp"),
|
|
"-o", str(binary)], text=True, capture_output=True)
|
|
self.assertEqual(built.returncode, 0, built.stderr)
|
|
result = subprocess.run([str(binary)], text=True, capture_output=True)
|
|
self.assertEqual(result.returncode, 0, result.stderr)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|