Test WinRT BLE exception isolation and pending shutdown

This commit is contained in:
evgeny committed 2026-10-29 22:39:15 +01:00
1 parent dc70174af9
commit 4a7f3253f6
2 files changed
+105

No files matched your search

+7
View File
@@ -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)
+98
View File
@@ -0,0 +1,98 @@
/* Настоящий WinRT mailbox без радио: исключения, изоляция link и отмена поздних callbacks.
* Включаем backend для доступа к внутренним событиям; общий transport заменён наблюдателем. */
#include "../ble_winrt.cpp"
#include <cstdlib>
#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<WinState> make_state(UASYNC* ua, ble_transport& owner) {
auto s = std::make_shared<WinState>(); s->ua = ua; s->owner = &owner; owner.state = s.get();
for (ble_link_id id : {7, 9}) {
auto l = std::make_unique<WinLink>(); 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<int>(0);
std::weak_ptr<int> 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<WinState> 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;
}