| // Copyright 2018 The Fuchsia Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #![feature(async_await, await_macro, futures_api)] |
| |
| use { |
| failure::{Error, ResultExt}, |
| fidl_fuchsia_bluetooth::Bool, |
| fidl_fuchsia_bluetooth_control::{AdapterInfo, AdapterState, RemoteDevice, TechnologyType}, |
| fidl_fuchsia_bluetooth_host::{HostEvent, HostProxy}, |
| fuchsia_async::{self as fasync, TimeoutExt}, |
| fuchsia_bluetooth::{ |
| error::Error as BtError, |
| fake_hci::FakeHciDevice, |
| host, |
| util::clone_host_state, |
| }, |
| fuchsia_vfs_watcher::{WatchEvent as VfsWatchEvent, Watcher as VfsWatcher}, |
| fuchsia_zircon::DurationNum, |
| futures::{future, task, Future, FutureExt, Poll, TryFutureExt, TryStreamExt}, |
| parking_lot::RwLock, |
| slab::Slab, |
| std::{ |
| borrow::Borrow, |
| collections::HashMap, |
| fs::File, |
| io::{self, Write}, |
| path::PathBuf, |
| pin::Pin, |
| sync::Arc, |
| }, |
| }; |
| |
| mod common; |
| |
| const TIMEOUT: i64 = 10; // in seconds |
| const BT_HOST_DIR: &str = "/dev/class/bt-host"; |
| |
| type HostTestPtr = Arc<RwLock<HostTest>>; |
| |
| struct HostTest { |
| // Fake bt-hci device. |
| fake_hci_dev: Option<FakeHciDevice>, |
| |
| // Access to the bt-host device under test. |
| host_path: String, |
| host_proxy: HostProxy, |
| |
| // Current bt-host adapter state. |
| host_info: AdapterInfo, |
| |
| // All known remote devices, indexed by their identifiers. |
| remote_devices: HashMap<String, RemoteDevice>, |
| |
| // Tasks that are interested in being woken up when the adapter state changes. |
| host_state_tasks: Slab<task::Waker>, |
| } |
| |
| impl HostTest { |
| fn new( |
| hci: FakeHciDevice, host_path: String, host: HostProxy, info: AdapterInfo, |
| ) -> HostTestPtr { |
| Arc::new(RwLock::new(HostTest { |
| fake_hci_dev: Some(hci), |
| host_path: host_path, |
| host_proxy: host, |
| host_info: info, |
| remote_devices: HashMap::new(), |
| host_state_tasks: Slab::new(), |
| })) |
| } |
| |
| // Returns a Future that handles Host interface events. |
| fn events_future(test_state: HostTestPtr) -> impl Future<Output = Result<(), Error>> { |
| let stream = test_state.read().host_proxy.take_event_stream(); |
| stream |
| .try_for_each(move |evt| { |
| match evt { |
| HostEvent::OnAdapterStateChanged { state } => { |
| test_state.write().handle_adapter_state_changed(state); |
| } |
| HostEvent::OnDeviceUpdated { device } => { |
| test_state.write().handle_device_updated(device); |
| } |
| HostEvent::OnDeviceRemoved { identifier } => { |
| test_state.write().handle_device_removed(identifier); |
| } |
| // TODO(armansito): handle other events |
| evt => { |
| eprintln!("Unhandled event: {:?}", evt); |
| } |
| } |
| future::ready(Ok(())) |
| }) |
| .err_into() |
| } |
| |
| // Returns a Future that resolves when the bt-host transitions to a state that matches the |
| // valid fields of `target`. For example, if `target_state` is |
| // |
| // AdapterState { |
| // local_name: None, |
| // discoverable: Some(Box::new(Bool { value: true })), |
| // discovering: Some(Box::new(Bool { value: true })), |
| // local_service_uuids: None |
| // } |
| // |
| // then the Future will resolve when the adapter becomes discoverable AND discovering. Other |
| // fields will be ignored. |
| // |
| // If the adapter is already in the requested state, then the Future will resolve the first |
| // time it gets polled. |
| fn on_adapter_state_change( |
| test_state: HostTestPtr, target_state: AdapterState, |
| ) -> impl Future<Output = Result<AdapterState, Error>> { |
| let err_msg = format!( |
| "timed out waiting for adapter state (expected: {:?})", |
| target_state |
| ); |
| AdapterStateFuture::new(test_state, target_state) |
| .on_timeout(TIMEOUT.seconds().after_now(), move || { |
| Err(BtError::new(&err_msg).into()) |
| }) |
| } |
| |
| // Returns a Future that resolves when the state of a particular RemoteDevice matches |
| // `target_state`. If `id` is None, then the Future will resolve when any device matches |
| // `target_state`. Otherwise, the Future will resolve when the state of the requested device |
| // changes. |
| fn on_device_update<'a>( |
| test_state: HostTestPtr, id: Option<&'a str>, target_state: RemoteDeviceExpectation, |
| ) -> impl Future<Output = Result<(), Error>> + 'a { |
| let err_msg = format!( |
| "timed out waiting for remote device state (expected: {:?})", |
| target_state |
| ); |
| RemoteDeviceStateFuture::new(test_state, id, Some(target_state)) |
| .on_timeout(TIMEOUT.seconds().after_now(), move || { |
| Err(BtError::new(&err_msg).into()) |
| }) |
| } |
| |
| fn close_fake_hci(&mut self) { |
| self.fake_hci_dev = None; |
| } |
| |
| fn store_task(&mut self, task: task::Waker) -> usize { |
| self.host_state_tasks.insert(task) |
| } |
| |
| fn remove_task(&mut self, key: usize) { |
| if self.host_state_tasks.contains(key) { |
| self.host_state_tasks.remove(key); |
| } |
| } |
| |
| fn find_device_by_address(&self, address: &str) -> Result<&RemoteDevice, Error> { |
| self.remote_devices |
| .values() |
| .find(|dev| dev.address == address) |
| .ok_or(BtError::new(&format!("device with address '{}' not found", address)).into()) |
| } |
| |
| // Handle the OnAdapterStateChanged event. |
| fn handle_adapter_state_changed(&mut self, state_change: AdapterState) { |
| let base = match self.host_info.state { |
| Some(ref state) => clone_host_state(state.borrow()), |
| None => AdapterState { |
| local_name: None, |
| discoverable: None, |
| discovering: None, |
| local_service_uuids: None, |
| }, |
| }; |
| let new_state = apply_delta(base, state_change); |
| self.host_info.state = Some(Box::new(new_state)); |
| self.notify_host_state_changed(); |
| } |
| |
| // Handle the OnDeviceUpdated event |
| fn handle_device_updated(&mut self, device: RemoteDevice) { |
| self.remote_devices |
| .insert(device.identifier.clone(), device); |
| self.notify_host_state_changed(); |
| } |
| |
| // Handle the OnDeviceRemoved event |
| fn handle_device_removed(&mut self, id: String) { |
| self.remote_devices.remove(&id); |
| self.notify_host_state_changed(); |
| } |
| |
| fn notify_host_state_changed(&mut self) { |
| for task in &self.host_state_tasks { |
| task.1.wake(); |
| } |
| self.host_state_tasks.clear() |
| } |
| } |
| |
| // Returns the value of `field` in `delta` if it's valid. Otherwise returns the value in `base`. |
| macro_rules! updated_field { |
| ($base:ident, $delta:ident, $field:ident) => { |
| match $delta.$field { |
| None => $base.$field, |
| value => value, |
| } |
| }; |
| } |
| |
| // Applies `delta` to `base`. |
| fn apply_delta(base: AdapterState, delta: AdapterState) -> AdapterState { |
| AdapterState { |
| local_name: updated_field!(base, delta, local_name), |
| discoverable: updated_field!(base, delta, discoverable), |
| discovering: updated_field!(base, delta, discovering), |
| local_service_uuids: updated_field!(base, delta, local_service_uuids), |
| } |
| } |
| |
| // Macro for comparing optional fields within a target against fields in `base`. Fields of `target` |
| // are assumed to have the Option type while the optionality of a `base` field is determined by |
| // the syntax. For example, given: |
| // |
| // struct A { |
| // foo: i64, |
| // bar: Option<String>, |
| // } |
| // |
| // struct Target { |
| // foo: Option<i64>, |
| // bar: Option<String>, |
| // } |
| // |
| // Fields can be matched by invoking: compare_fields!(a, b, [foo, (bar)]) |
| macro_rules! compare_field { |
| ($base:ident, $target:ident, &$field:ident) => { |
| compare_field!($base, $target, &$field) |
| }; |
| |
| // Compare fields assuming the base field is not an Option type. |
| ($base:ident, $target:ident, $field:ident) => { |
| match &$target.$field { |
| None => None, |
| Some(value) => Some(value == &$base.$field), |
| } |
| }; |
| |
| // Compare fields assuming the base field is an Option type. |
| ($base:ident, $target:ident, ($field:ident)) => { |
| match &$target.$field { |
| None => None, |
| value => Some(value == &$base.$field), |
| } |
| }; |
| } |
| |
| macro_rules! compare_fields { |
| ($base:ident, $target:ident, [$($field:tt),*]) => {{ |
| let mut all_none = true; |
| $({ |
| if let Some(matched) = compare_field!($base, $target, $field) { |
| all_none = false; |
| if !matched { |
| return false; |
| } |
| } |
| })* |
| !all_none |
| }}; |
| } |
| |
| // Struct for comparing individual fields a RemoteDevice. |
| #[derive(Clone, Debug, Default)] |
| struct RemoteDeviceExpectation { |
| name: Option<String>, |
| address: Option<String>, |
| technology: Option<TechnologyType>, |
| connected: Option<bool>, |
| bonded: Option<bool>, |
| } |
| |
| // Returns true if all valid fields of `target` match their equivalents in `base`. Returns false if |
| // there are any mismatches or if all fields of `target` are None. |
| fn compare_adapter_states(base: &AdapterState, target: &AdapterState) -> bool { |
| compare_fields!( |
| base, |
| target, |
| [ |
| (local_name), |
| (discoverable), |
| (discovering), |
| (local_service_uuids) |
| ] |
| ) |
| } |
| |
| fn compare_remote_device(base: &RemoteDevice, target: &RemoteDeviceExpectation) -> bool { |
| compare_fields!( |
| base, |
| target, |
| [(name), address, technology, connected, bonded] |
| ) |
| } |
| |
| struct StateUpdateFutureInner { |
| test_state: HostTestPtr, |
| waker_key: Option<usize>, |
| } |
| |
| impl StateUpdateFutureInner { |
| fn new(test: HostTestPtr) -> StateUpdateFutureInner { |
| StateUpdateFutureInner { |
| test_state: test, |
| waker_key: None, |
| } |
| } |
| |
| fn maybe_remove_waker(&mut self) { |
| if let Some(key) = self.waker_key { |
| self.test_state.write().remove_task(key); |
| self.waker_key = None; |
| } |
| } |
| |
| fn store_task(&mut self, lw: &task::LocalWaker) { |
| let key = self.test_state.write().store_task(lw.clone().into_waker()); |
| self.waker_key = Some(key); |
| } |
| } |
| |
| // A future that resolves when the bt-host's AdapterState changes to a certain target value. |
| struct AdapterStateFuture { |
| inner: StateUpdateFutureInner, |
| |
| // The expected adapter state. |
| target_host_state: AdapterState, |
| } |
| |
| impl AdapterStateFuture { |
| fn new(test: HostTestPtr, target_state: AdapterState) -> Self { |
| AdapterStateFuture { |
| inner: StateUpdateFutureInner::new(test), |
| target_host_state: target_state, |
| } |
| } |
| } |
| |
| impl std::marker::Unpin for AdapterStateFuture {} |
| |
| #[must_use = "futures do nothing unless polled"] |
| impl Future for AdapterStateFuture { |
| type Output = Result<AdapterState, Error>; |
| |
| fn poll(mut self: Pin<&mut Self>, lw: &task::LocalWaker) -> Poll<Self::Output> { |
| self.inner.maybe_remove_waker(); |
| let states_match: bool = match &self.inner.test_state.read().host_info.state { |
| None => false, |
| Some(state) => compare_adapter_states(state.borrow(), &self.target_host_state), |
| }; |
| if states_match { |
| Poll::Ready(Ok(clone_host_state(&self.target_host_state))) |
| } else { |
| self.inner.store_task(lw); |
| Poll::Pending |
| } |
| } |
| } |
| |
| // A future that resolves when the state of a remote device changes to a target value. |
| struct RemoteDeviceStateFuture<'a> { |
| inner: StateUpdateFutureInner, |
| |
| // The identifier of the desired RemoteDevice. If None, then this Future can resolve with any |
| // device. |
| target_dev_id: Option<&'a str>, |
| |
| // The expected state a RemoteDevice is expected to reach. If the state is |
| // None then the Future will resolve when the device gets removed. If both `target_dev_state` |
| // and `target_dev_id` are None, then the Future will resolve when any device gets removed. |
| target_dev_state: Option<RemoteDeviceExpectation>, |
| } |
| |
| impl<'a> RemoteDeviceStateFuture<'a> { |
| fn new( |
| test: HostTestPtr, target_id: Option<&'a str>, |
| target_state: Option<RemoteDeviceExpectation>, |
| ) -> Self { |
| RemoteDeviceStateFuture { |
| inner: StateUpdateFutureInner::new(test), |
| target_dev_id: target_id, |
| target_dev_state: target_state, |
| } |
| } |
| |
| fn look_for_match(&self) -> bool { |
| match &self.target_dev_id { |
| None => self.match_any_device(&self.target_dev_state), |
| Some(id) => self.match_device_by_id(id, &self.target_dev_state), |
| } |
| } |
| |
| fn match_any_device(&self, target: &Option<RemoteDeviceExpectation>) -> bool { |
| target.as_ref().map_or(false, |target| { |
| self.inner |
| .test_state |
| .read() |
| .remote_devices |
| .values() |
| .find(|dev| compare_remote_device(dev, &target)) |
| .is_some() |
| }) |
| } |
| |
| fn match_device_by_id(&self, id: &'a str, target: &Option<RemoteDeviceExpectation>) -> bool { |
| match self.inner.test_state.read().remote_devices.get(id) { |
| None => target.is_none(), |
| Some(dev) => match target { |
| None => false, |
| Some(target) => compare_remote_device(dev, target), |
| }, |
| } |
| } |
| } |
| |
| impl<'a> std::marker::Unpin for RemoteDeviceStateFuture<'a> {} |
| |
| #[must_use = "futures do nothing unless polled"] |
| impl<'a> Future for RemoteDeviceStateFuture<'a> { |
| type Output = Result<(), Error>; |
| |
| fn poll(mut self: Pin<&mut Self>, lw: &task::LocalWaker) -> Poll<Self::Output> { |
| self.inner.maybe_remove_waker(); |
| if self.look_for_match() { |
| Poll::Ready(Ok(())) |
| } else { |
| self.inner.store_task(lw); |
| Poll::Pending |
| } |
| } |
| } |
| |
| // Returns a Future that resolves when a bt-host device gets added under the given topological |
| // path. |
| async fn watch_for_new_host_helper( |
| mut watcher: VfsWatcher, parent_topo_path: String, |
| ) -> Result<(File, PathBuf), Error> { |
| while let Some(msg) = await!(watcher.try_next())? { |
| match msg.event { |
| VfsWatchEvent::EXISTING | VfsWatchEvent::ADD_FILE => { |
| let path = PathBuf::from(format!( |
| "{}/{}", |
| BT_HOST_DIR, |
| msg.filename.to_string_lossy() |
| )); |
| let host_fd = common::open_rdwr(&path)?; |
| let host_topo_path = fdio::device_get_topo_path(&host_fd)?; |
| if host_topo_path.starts_with(parent_topo_path.as_str()) { |
| return Ok((host_fd, path.clone())); |
| } |
| } |
| _ => (), |
| } |
| } |
| unreachable!(); |
| } |
| |
| // Returns a Future that resolves when the bt-host device with the given path gets removed. |
| async fn wait_for_host_removal_helper(mut watcher: VfsWatcher, path: String) -> Result<(), Error> { |
| while let Some(msg) = await!(watcher.try_next())? { |
| match msg.event { |
| VfsWatchEvent::REMOVE_FILE => { |
| let actual_path = format!("{}/{}", BT_HOST_DIR, msg.filename.to_string_lossy()); |
| if path == actual_path { |
| return Ok(()); |
| } |
| } |
| _ => (), |
| } |
| } |
| unreachable!(); |
| } |
| |
| // Wraps a Future inside a timeout that logs a message and resolves to an error on expiration. |
| fn timeout<T, F>(fut: F, msg: &'static str) -> impl Future<Output = Result<T, Error>> |
| where |
| F: Future<Output = Result<T, Error>>, |
| { |
| fut.on_timeout(TIMEOUT.seconds().after_now(), move || { |
| Err(BtError::new(msg).into()) |
| }) |
| } |
| |
| fn watch_for_new_host( |
| watcher: VfsWatcher, fake_hci_topo_path: String, |
| ) -> impl Future<Output = Result<(File, PathBuf), Error>> { |
| timeout( |
| watch_for_new_host_helper(watcher, fake_hci_topo_path), |
| "timed out waiting for bt-host", |
| ) |
| } |
| |
| fn wait_for_host_removal( |
| watcher: VfsWatcher, path: String, |
| ) -> impl Future<Output = Result<(), Error>> { |
| timeout( |
| wait_for_host_removal_helper(watcher, path), |
| "timed out waiting for bt-host removal", |
| ) |
| } |
| |
| // Creates a fake bt-hci device and returns the corresponding bt-host device once it gets created. |
| async fn setup_emulated_host_test() -> Result<HostTestPtr, Error> { |
| let fake_hci = FakeHciDevice::new()?; |
| let fake_hci_topo_path = fdio::device_get_topo_path(fake_hci.file())?; |
| |
| let dir = File::open(&BT_HOST_DIR)?; |
| let watcher = VfsWatcher::new(&dir)?; |
| let (host_dev_fd, path) = await!(watch_for_new_host(watcher, fake_hci_topo_path))?; |
| |
| // Open a Host FIDL interface channel to the bt-host device. |
| let fidl_handle = host::open_host_channel(&host_dev_fd)?; |
| let host = HostProxy::new(fasync::Channel::from_channel(fidl_handle.into())?); |
| let info = await!(host.get_info())?; |
| |
| Ok(HostTest::new( |
| fake_hci, |
| path.to_string_lossy().to_string(), |
| host, |
| info, |
| )) |
| } |
| |
| async fn run_test_async<F, Fut>(test_func: F) -> Result<(), Error> |
| where |
| F: FnOnce(HostTestPtr) -> Fut, |
| Fut: Future<Output = Result<(), Error>>, |
| { |
| let host_test = await!(setup_emulated_host_test())?; |
| |
| // Start processing events in a background task. |
| fasync::spawn(HostTest::events_future(host_test.clone()).map(|_| ())); |
| |
| // Run the test and obtain the test result. |
| let result = await!(test_func(host_test.clone())); |
| |
| // Shut down the fake bt-hci device and make sure the bt-host device gets removed. |
| let dir = File::open(&BT_HOST_DIR)?; |
| let watcher = VfsWatcher::new(&dir)?; |
| host_test.write().close_fake_hci(); |
| await!(wait_for_host_removal( |
| watcher, |
| host_test.read().host_path.clone() |
| ))?; |
| |
| if result.is_ok() { |
| println!("\x1b[32mPASSED\x1b[0m"); |
| } else { |
| println!("\x1b[31mFAILED\x1b[0m"); |
| } |
| result |
| } |
| |
| fn run_test<F, Fut>(test_func: F) -> Result<(), Error> |
| where |
| F: FnOnce(HostTestPtr) -> Fut, |
| Fut: Future<Output = Result<(), Error>>, |
| { |
| let mut executor = fasync::Executor::new().context("error creating event loop")?; |
| executor.run_singlethreaded(run_test_async(test_func)) |
| } |
| |
| // Prints out the test name and runs the test. |
| macro_rules! run_test { |
| ($name:ident) => {{ |
| print!("{}...", stringify!($name)); |
| io::stdout().flush().unwrap(); |
| run_test($name) |
| }}; |
| } |
| |
| fn expect_eq<T>(expected: &T, actual: &T) -> Result<(), Error> |
| where |
| T: std::fmt::Debug + std::cmp::PartialEq, |
| { |
| if *expected == *actual { |
| Ok(()) |
| } else { |
| Err(BtError::new(&format!( |
| "failed - expected '{:#?}', found: '{:#?}'", |
| expected, actual |
| )) |
| .into()) |
| } |
| } |
| |
| macro_rules! expect_eq { |
| ($expected:expr, $actual:expr) => { |
| expect_eq(&$expected, &$actual) |
| }; |
| } |
| |
| macro_rules! expect_true { |
| ($condition:expr) => { |
| if $condition{ |
| Ok(()) |
| } else { |
| Err(BtError::new(&format!( |
| "condition is not true: {}", |
| stringify!($condition) |
| )).into()) |
| } as Result<(), Error> |
| } |
| } |
| |
| fn expect_remote_device( |
| test_state: &HostTestPtr, address: &str, expected: &RemoteDeviceExpectation, |
| ) -> Result<(), Error> { |
| expect_true!(compare_remote_device( |
| test_state.read().find_device_by_address(address)?, |
| expected |
| )) |
| } |
| |
| // ========= Test Cases ========= |
| |
| // Tests that the device address is 0. |
| async fn test_bd_addr(test_state: HostTestPtr) -> Result<(), Error> { |
| let info = await!(test_state.read().host_proxy.get_info()) |
| .map_err(|_| BtError::new("failed to read adapter info"))?; |
| expect_eq!("00:00:00:00:00:00", info.address.as_str()) |
| } |
| |
| // Tests that setting the local name succeeds. |
| // TODO(armansito): Test for FakeHciDevice state changes. |
| async fn test_set_local_name(test_state: HostTestPtr) -> Result<(), Error> { |
| let name = "test1234"; |
| await!(test_state.read().host_proxy.set_local_name(&name))?; |
| let state_change = AdapterState { |
| local_name: Some(name.to_string()), |
| discoverable: None, |
| discovering: None, |
| local_service_uuids: None, |
| }; |
| await!(HostTest::on_adapter_state_change( |
| test_state.clone(), |
| state_change |
| ))?; |
| |
| Ok(()) |
| } |
| |
| // Tests that host state updates when discoverable mode is turned on. |
| // TODO(armansito): Test for FakeHciDevice state changes. |
| async fn test_discoverable(test_state: HostTestPtr) -> Result<(), Error> { |
| // Enable discoverable mode. |
| await!(test_state.read().host_proxy.set_discoverable(true))?; |
| let discoverable_state = AdapterState { |
| local_name: None, |
| discoverable: Some(Box::new(Bool { value: true })), |
| discovering: None, |
| local_service_uuids: None, |
| }; |
| await!(HostTest::on_adapter_state_change( |
| test_state.clone(), |
| discoverable_state |
| ))?; |
| |
| // Disable discoverable mode |
| await!(test_state.read().host_proxy.set_discoverable(false))?; |
| let non_discoverable_state = AdapterState { |
| local_name: None, |
| discoverable: Some(Box::new(Bool { value: false })), |
| discovering: None, |
| local_service_uuids: None, |
| }; |
| await!(HostTest::on_adapter_state_change( |
| test_state.clone(), |
| non_discoverable_state |
| ))?; |
| |
| Ok(()) |
| } |
| |
| // Tests that host state updates when discovery is started and stopped. |
| // TODO(armansito): Test for FakeHciDevice state changes. |
| async fn test_discovery(test_state: HostTestPtr) -> Result<(), Error> { |
| // Start discovery. "discovering" should get set to true. |
| await!(test_state.read().host_proxy.start_discovery())?; |
| let discovering_state = AdapterState { |
| local_name: None, |
| discoverable: None, |
| discovering: Some(Box::new(Bool { value: true })), |
| local_service_uuids: None, |
| }; |
| await!(HostTest::on_adapter_state_change( |
| test_state.clone(), |
| discovering_state |
| ))?; |
| |
| // The host should discover a fake device. |
| // TODO(NET-1457): The name is currently hard-coded in |
| // garnet/drivers/bluetooth/hci/fake/fake-device.cpp:89. Configure this dynamically when it is |
| // supported. |
| let new_device = RemoteDeviceExpectation { |
| name: Some("Fake".to_string()), |
| ..Default::default() |
| }; |
| await!(HostTest::on_device_update( |
| test_state.clone(), |
| None, |
| new_device |
| ))?; |
| |
| // Stop discovery. "discovering" should get set to false. |
| await!(test_state.read().host_proxy.stop_discovery())?; |
| let not_discovering_state = AdapterState { |
| local_name: None, |
| discoverable: None, |
| discovering: Some(Box::new(Bool { value: false })), |
| local_service_uuids: None, |
| }; |
| await!(HostTest::on_adapter_state_change( |
| test_state.clone(), |
| not_discovering_state |
| ))?; |
| |
| Ok(()) |
| } |
| |
| // Tests that "close" cancels all operations. |
| // TODO(armansito): Test for FakeHciDevice state changes. |
| async fn test_close(test_state: HostTestPtr) -> Result<(), Error> { |
| // Enable all procedures. |
| await!(test_state.read().host_proxy.start_discovery())?; |
| await!(test_state.read().host_proxy.set_discoverable(true))?; |
| |
| let active_state = AdapterState { |
| local_name: None, |
| discoverable: Some(Box::new(Bool { value: true })), |
| discovering: Some(Box::new(Bool { value: true })), |
| local_service_uuids: None, |
| }; |
| await!(HostTest::on_adapter_state_change( |
| test_state.clone(), |
| active_state |
| ))?; |
| |
| // Close should cancel these procedures. |
| test_state.read().host_proxy.close()?; |
| let closed_state_update = AdapterState { |
| local_name: None, |
| discoverable: Some(Box::new(Bool { value: false })), |
| discovering: Some(Box::new(Bool { value: false })), |
| local_service_uuids: None, |
| }; |
| await!(HostTest::on_adapter_state_change( |
| test_state.clone(), |
| closed_state_update |
| ))?; |
| |
| Ok(()) |
| } |
| |
| // Tests that "list_devices" returns devices from a host's cache. |
| async fn test_list_devices(test_state: HostTestPtr) -> Result<(), Error> { |
| // Devices should be initially empty. |
| let mut devices = await!(test_state.read().host_proxy.list_devices())?; |
| expect_eq!(vec![] as Vec<RemoteDevice>, devices)?; |
| |
| // Wait for all fake devices to be discovered. |
| // TODO(NET-1457): Add support for setting these up programmatically instead of hardcoding |
| // them. The fake HCI driver currently sets up one LE and one BR/EDR device. |
| await!(test_state.read().host_proxy.start_discovery())?; |
| let expected_le = RemoteDeviceExpectation { |
| address: Some("00:00:00:00:00:01".to_string()), |
| technology: Some(TechnologyType::LowEnergy), |
| ..Default::default() |
| }; |
| let expected_bredr = RemoteDeviceExpectation { |
| address: Some("00:00:00:00:00:02".to_string()), |
| technology: Some(TechnologyType::Classic), |
| ..Default::default() |
| }; |
| await!(HostTest::on_device_update( |
| test_state.clone(), |
| None, |
| expected_le.clone() |
| ))?; |
| await!(HostTest::on_device_update( |
| test_state.clone(), |
| None, |
| expected_bredr.clone() |
| ))?; |
| |
| // List the host's devices |
| devices = await!(test_state.read().host_proxy.list_devices())?; |
| |
| // Both fake devices should be in the map. |
| expect_eq!(2, devices.len())?; |
| expect_remote_device(&test_state, "00:00:00:00:00:01", &expected_le)?; |
| expect_remote_device(&test_state, "00:00:00:00:00:02", &expected_bredr)?; |
| Ok(()) |
| } |
| |
| fn main() -> Result<(), Error> { |
| println!("TEST BEGIN"); |
| |
| run_test!(test_bd_addr)?; |
| run_test!(test_set_local_name)?; |
| run_test!(test_discoverable)?; |
| run_test!(test_discovery)?; |
| run_test!(test_close)?; |
| run_test!(test_list_devices)?; |
| |
| println!("ALL TESTS PASSED"); |
| Ok(()) |
| } |