From 4a7f3253f63de57dca04b8cb8a04f12f9398dd5b Mon Sep 17 00:00:00 2001 From: evgeny Date: Thu, 29 Oct 2026 22:39:15 +0100 Subject: [PATCH] Test WinRT BLE exception isolation and pending shutdown --- lib/ble/CMakeLists.txt | 7 +++ lib/ble/tests/test_ble_winrt.cpp | 98 ++++++++++++++++++++++++++++++++ 2 files changed, 105 insertions(+) create mode 100644 lib/ble/tests/test_ble_winrt.cpp diff --git a/lib/ble/CMakeLists.txt b/lib/ble/CMakeLists.txt index 20e97829..f1383227 100644 --- a/lib/ble/CMakeLists.txt +++ b/lib/ble/CMakeLists.txt @@ -24,6 +24,13 @@ if(WIN32) find_path(BLE_WINRT_INCLUDE winrt/Windows.Devices.Bluetooth.h REQUIRED) target_include_directories(vibechat_ble PRIVATE "${BLE_WINRT_INCLUDE}") target_link_libraries(vibechat_ble PRIVATE windowsapp) + add_executable(test_ble_winrt tests/test_ble_winrt.cpp) + target_compile_features(test_ble_winrt PRIVATE cxx_std_20) + target_include_directories(test_ble_winrt PRIVATE "${BLE_WINRT_INCLUDE}") + target_link_libraries(test_ble_winrt PRIVATE uasync windowsapp) + set_target_properties(test_ble_winrt PROPERTIES AUTOMOC OFF) + add_test(NAME test_ble_winrt COMMAND test_ble_winrt) + set_tests_properties(test_ble_winrt PROPERTIES TIMEOUT 30) elseif(CMAKE_SYSTEM_NAME STREQUAL "Linux") find_package(PkgConfig REQUIRED) pkg_check_modules(BLE_SYSTEMD REQUIRED IMPORTED_TARGET libsystemd) diff --git a/lib/ble/tests/test_ble_winrt.cpp b/lib/ble/tests/test_ble_winrt.cpp new file mode 100644 index 00000000..86883fc4 --- /dev/null +++ b/lib/ble/tests/test_ble_winrt.cpp @@ -0,0 +1,98 @@ +/* Настоящий WinRT mailbox без радио: исключения, изоляция link и отмена поздних callbacks. + * Включаем backend для доступа к внутренним событиям; общий transport заменён наблюдателем. */ +#include "../ble_winrt.cpp" +#include + +#define CHECK(value) do { if (!(value)) { DEBUG_ERROR(CAT, "CHECK failed line=%d: %s", __LINE__, #value); std::abort(); } } while (0) + +struct ble_transport { + WinState* state = nullptr; + unsigned radio_failures = 0, link_failures = 0; + ble_link_id closed = 0; + std::string reason; +}; +extern "C" void ble_backend_closed(ble_transport* t, ble_link_id id, const char* reason) { + ++t->link_failures; t->closed = id; t->reason = reason; t->state->close(id); +} +extern "C" void ble_backend_status(ble_transport* t, int ready, const char* reason) { + CHECK(!ready); ++t->radio_failures; t->reason = reason; + while (!t->state->links.empty()) t->state->close(t->state->links.begin()->first); +} +/* Эти события не должны возникать в проверке mailbox. */ +extern "C" ble_link_id ble_backend_incoming(ble_transport*, const ble_endpoint*) { CHECK(false); return 0; } +extern "C" void ble_backend_advertising(ble_transport*, ble_state, const char*) { CHECK(false); } +extern "C" void ble_backend_broadcast(ble_transport*, const ble_endpoint*, const uint8_t*, size_t, int) { CHECK(false); } +extern "C" void ble_backend_opened(ble_transport*, ble_link_id, unsigned) { CHECK(false); } +extern "C" void ble_backend_received(ble_transport*, ble_link_id, ble_wire_channel, const uint8_t*, size_t) { CHECK(false); } +extern "C" void ble_backend_sent(ble_transport*, ble_link_id, int) { CHECK(false); } + +static std::shared_ptr make_state(UASYNC* ua, ble_transport& owner) { + auto s = std::make_shared(); s->ua = ua; s->owner = &owner; owner.state = s.get(); + for (ble_link_id id : {7, 9}) { + auto l = std::make_unique(); l->id = id; s->links.emplace(id, std::move(l)); + } + return s; +} + +/* Ошибка GATT одного пира не переводит radio в failed и не теряет событие другого пира. */ +static void link_exception(UASYNC* ua) { + ble_transport owner; + auto s = make_state(ua, owner); + bool other_processed = false; + s->post("test.ValueChanged", 7, [](WinState& state) { + state.step("test.CharacteristicValue", 7); + throw hresult_error(E_NOINTERFACE, L"test interface absent"); + }); + s->post("test.other_peer", 9, [&other_processed](WinState&) { other_processed = true; }); + uasync_poll(ua, 0); + CHECK(owner.link_failures == 1 && owner.closed == 7 && owner.radio_failures == 0); + CHECK(!s->failed && !s->link(7) && s->link(9) && other_processed); + CHECK(owner.reason.find("phase=test.CharacteristicValue link=7 HRESULT=0x80004002") != std::string::npos); + s->shutdown(); + DEBUG_INFO(CAT, "WinRT test PASS: link exception preserves other peer and exact HRESULT context"); +} + +/* Общая ошибка сохраняет точную операцию даже при выключенном TRACE и останавливает batch. */ +static void radio_exception(UASYNC* ua) { + ble_transport owner; + auto s = make_state(ua, owner); + bool processed = false; + s->post("test.Received", 0, [](WinState& state) { + state.step("test.BluetoothAddressType"); + throw hresult_error(E_NOINTERFACE, L"test interface absent"); + }); + s->post("test.after_failure", 9, [&processed](WinState&) { processed = true; }); + uasync_poll(ua, 0); + CHECK(owner.radio_failures == 1 && owner.link_failures == 0 && s->failed); + CHECK(s->links.empty() && !processed); + CHECK(owner.reason.find("phase=test.BluetoothAddressType link=0 HRESULT=0x80004002") != std::string::npos); + s->shutdown(); + DEBUG_INFO(CAT, "WinRT test PASS: radio exception stops queued delivery with TRACE disabled"); +} + +/* shutdown отменяет posted_task, освобождает captures и отвергает позднее событие Windows. */ +static void pending_shutdown(UASYNC* ua) { + ble_transport owner; + auto s = make_state(ua, owner); + auto payload = std::make_shared(0); + std::weak_ptr weak_payload = payload; + bool processed = false; + s->post("test.pending", 9, [payload, &processed](WinState&) { processed = true; }); + payload.reset(); CHECK(!weak_payload.expired()); + s->shutdown(); CHECK(weak_payload.expired()); + s->post("test.late", 9, [&processed](WinState&) { processed = true; }); + CHECK(s->events.empty() && !s->posted); + std::weak_ptr weak = s; s.reset(); + uasync_poll(ua, 0); + CHECK(weak.expired() && !processed && owner.radio_failures == 0 && owner.link_failures == 0); + DEBUG_INFO(CAT, "WinRT test PASS: shutdown cancels queued work and rejects late callbacks"); +} + +int main() { + debug_config_init(); debug_set_level(DEBUG_LEVEL_INFO); + init_apartment(apartment_type::multi_threaded); + UASYNC* ua = uasync_create(); CHECK(ua); + link_exception(ua); radio_exception(ua); pending_shutdown(ua); + uasync_destroy(ua, 0); uninit_apartment(); + return 0; +}