Files
utun2/lib/ble/tests/test_ble_winrt.cpp
T

210 lines
12 KiB
C++

/* 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;
}
using AddressType = Windows::Devices::Bluetooth::BluetoothAddressType;
/* Настоящий QueryInterface: объект 1809 не реализует EventArgs2, объект 2004 реализует. */
template<typename... Extra>
struct TestReceivedArgs : implements<TestReceivedArgs<Extra...>, IBluetoothLEAdvertisementReceivedEventArgs, Extra...> {
uint64_t address = 0;
AddressType type = AddressType::Unspecified;
uint64_t BluetoothAddress() { return address; }
AddressType BluetoothAddressType() { return type; }
int16_t RawSignalStrengthInDBm() { CHECK(false); return 0; }
BluetoothLEAdvertisementType AdvertisementType() { CHECK(false); return BluetoothLEAdvertisementType::ConnectableUndirected; }
DateTime Timestamp() { CHECK(false); return {}; }
BluetoothLEAdvertisement Advertisement() { CHECK(false); return nullptr; }
IReference<int16_t> TransmitPowerLevelInDBm() { CHECK(false); return nullptr; }
bool IsAnonymous() { CHECK(false); return false; }
bool IsConnectable() { CHECK(false); return false; }
bool IsScannable() { CHECK(false); return false; }
bool IsDirected() { CHECK(false); return false; }
bool IsScanResponse() { CHECK(false); return false; }
};
/* Отсутствие свойства не ломает radio и не подменяет неизвестный тип на public/random. */
static void advertisement_addresses(UASYNC* ua) {
ble_transport owner;
auto s = make_state(ua, owner);
auto legacy = make_self<TestReceivedArgs<>>();
legacy->address = 0xc123456789ab;
auto args = legacy.as<BluetoothLEAdvertisementReceivedEventArgs>();
CHECK(!args.try_as<IBluetoothLEAdvertisementReceivedEventArgs2>());
unsigned processed = 0;
s->post("Watcher.Received", 0, [args, &processed](WinState& state) {
auto ep = state.advertisement_endpoint(args);
CHECK(std::string(ep.id) == "c123456789ab/2"); ++processed;
});
/* Ведущие нули сохраняются; высокий бит адреса не используется для угадывания типа. */
auto leading_zero = make_self<TestReceivedArgs<>>(); leading_zero->address = 0x12345678;
args = leading_zero.as<BluetoothLEAdvertisementReceivedEventArgs>();
s->post("Watcher.Received", 0, [args, &processed](WinState& state) {
auto ep = state.advertisement_endpoint(args);
CHECK(std::string(ep.id) == "000012345678/2"); ++processed;
});
for (auto type : {AddressType::Public, AddressType::Random, AddressType::Unspecified}) {
auto modern = make_self<TestReceivedArgs<IBluetoothLEAdvertisementReceivedEventArgs2>>();
modern->address = legacy->address; modern->type = type;
args = modern.as<BluetoothLEAdvertisementReceivedEventArgs>();
s->post("Watcher.Received", 0, [args, type, &processed](WinState& state) {
auto ep = state.advertisement_endpoint(args);
CHECK(std::string(ep.id) == "c123456789ab/" + std::to_string(int(type))); ++processed;
});
}
uasync_poll(ua, 0);
CHECK(processed == 5 && !s->failed && owner.radio_failures == 0 && owner.link_failures == 0);
CHECK(s->link(7) && s->link(9));
s->shutdown();
DEBUG_INFO(CAT, "WinRT test PASS: 1809 events use unspecified type; 2004 events preserve public/random/unspecified");
}
struct TestPublisher : implements<TestPublisher, IBluetoothLEAdvertisementPublisher> {
bool read_error = false;
BluetoothLEAdvertisementPublisherStatus Status() {
if (read_error) throw hresult_error(E_ACCESSDENIED, L"test publisher status denied");
return BluetoothLEAdvertisementPublisherStatus::Started;
}
BluetoothLEAdvertisement Advertisement() { CHECK(false); return nullptr; }
void Start() { CHECK(false); }
void Stop() { CHECK(false); }
event_token StatusChanged(const TypedEventHandler<BluetoothLEAdvertisementPublisher,
BluetoothLEAdvertisementPublisherStatusChangedEventArgs>&) { CHECK(false); return {}; }
void StatusChanged(event_token) noexcept { CHECK(false); }
};
struct TestProvider : implements<TestProvider, IGattServiceProvider> {
bool read_error = false;
GattLocalService Service() { CHECK(false); return nullptr; }
GattServiceProviderAdvertisementStatus AdvertisementStatus() {
if (read_error) throw hresult_error(E_ACCESSDENIED, L"test GATT status denied");
return GattServiceProviderAdvertisementStatus::Stopped;
}
void StartAdvertising() { CHECK(false); }
void StartAdvertising(const GattServiceProviderAdvertisingParameters&) { CHECK(false); }
void StopAdvertising() { CHECK(false); }
event_token AdvertisementStatusChanged(const TypedEventHandler<GattServiceProvider,
GattServiceProviderAdvertisementStatusChangedEventArgs>&) { CHECK(false); return {}; }
void AdvertisementStatusChanged(event_token) noexcept { CHECK(false); }
};
/* Снимок отличает ОС от задержанных событий; ошибка одного getter не мешает остальным и не останавливает radio. */
static void radio_snapshot(UASYNC* ua) {
ble_transport owner;
auto s = make_state(ua, owner);
auto empty = s->radio_status();
CHECK(empty.publisher == -1 && empty.service == -1 && empty.scan == -1);
auto publisher = make_self<TestPublisher>(); auto provider = make_self<TestProvider>();
s->publisher = publisher.as<BluetoothLEAdvertisementPublisher>();
s->provider = provider.as<GattServiceProvider>();
s->publisher_status = int(BluetoothLEAdvertisementPublisherStatus::Waiting); s->service_ready = true;
auto actual = s->radio_status();
CHECK(actual.publisher == 2 && actual.service == 1 && actual.scan == -1);
publisher->read_error = true;
actual = s->radio_status(); CHECK(actual.publisher == -2 && actual.service == 1);
publisher->read_error = false; provider->read_error = true;
actual = s->radio_status(); CHECK(actual.publisher == 2 && actual.service == -2);
CHECK(!s->failed && !owner.radio_failures && !owner.link_failures && s->links.size() == 2);
CHECK(s->publisher_status == 1 && s->service_ready);
s->publisher = nullptr; s->provider = nullptr; s->shutdown();
DEBUG_INFO(CAT, "WinRT test PASS: actual radio snapshot tolerates getter failures without changing radio/events/links");
}
/* Ошибка 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);
/* Таймер проходит реальный callback, перевзводится и затем отменяется вместе с mailbox. */
s->diagnostics_timer = uasync_set_timeout(ua, 0, s.get(), WinState::diagnostics_tick, "test_diagnostics");
CHECK(s->diagnostics_timer);
uasync_poll(ua, 0);
CHECK(s->diagnostics_timer);
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->diagnostics_timer);
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 diagnostics timer/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);
advertisement_addresses(ua); radio_snapshot(ua); link_exception(ua); radio_exception(ua); pending_shutdown(ua);
uasync_destroy(ua, 0); uninit_apartment();
return 0;
}