| 23 | static ADAPTER: OnceCell<Adapter> = OnceCell::const_new(); |
| 24 | |
| 25 | pub async fn get_adapter() -> &'static Adapter { |
| 26 | ADAPTER |
| 27 | .get_or_init(|| async { |
| 28 | // Create the adapter on a dedicated thread with its own tokio runtime. |
| 29 | // This ensures the event-processing task spawned by Adapter::new() |
| 30 | // survives across #[tokio::test] runtime boundaries (each test gets |
| 31 | // its own runtime, which shuts down after the test completes). |
| 32 | let (tx, rx) = tokio::sync::oneshot::channel(); |
| 33 | std::thread::Builder::new() |
| 34 | .name("btleplug-test-adapter".into()) |
| 35 | .spawn(move || { |
| 36 | let rt = tokio::runtime::Builder::new_current_thread() |
| 37 | .enable_all() |
| 38 | .build() |
| 39 | .expect("failed to create adapter runtime"); |
| 40 | rt.block_on(async { |
| 41 | let manager = Manager::new().await.expect("failed to create BLE manager"); |
| 42 | let adapters = manager.adapters().await.expect("failed to get adapters"); |
| 43 | // Leak the manager so it (and the underlying CBCentralManager) |
| 44 | // lives forever. OnceCell keeps the Adapter alive; we need the |
| 45 | // Manager alive too since the Adapter borrows from it internally |
| 46 | // on some platforms. |
| 47 | std::mem::forget(manager); |
| 48 | let adapter = adapters.into_iter().next().expect("no BLE adapters found"); |
| 49 | tx.send(adapter).ok(); |
| 50 | // Block forever so the runtime (and its spawned event loop) |
| 51 | // stays alive. |
| 52 | std::future::pending::<()>().await; |
| 53 | }); |
| 54 | }) |
| 55 | .expect("failed to spawn adapter thread"); |
| 56 | rx.await |
| 57 | .expect("failed to receive adapter from background thread") |
| 58 | }) |
| 59 | .await |
| 60 | } |
| 61 | |
| 62 | /// Best-effort cleanup: disconnect any connected peripherals. |
| 63 | /// This prevents state leakage between tests when running in a shared process (Android). |