blob: 1e8206ceea3b598d60bd6483c13747b6c66694c5 [file] [log] [blame]
// Copyright 2019 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.
use std::{
fmt::{self, Debug, Formatter},
sync::Arc,
};
/// Asynchronous extensions to Expectation Predicates
pub mod asynchronous;
/// A Boolean predicate on type `T`. Predicate functions are a boolean algebra
/// just as raw boolean values are; they an be ANDed, ORed, NOTed. This allows
/// a clear and concise language for declaring test expectations.
pub struct Predicate<T> {
inner: Arc<dyn Fn(&T) -> bool + Send + Sync + 'static>,
/// A descriptive piece of text used for debug printing via `{:?}`
description: String,
}
impl<T> Clone for Predicate<T> {
fn clone(&self) -> Predicate<T> {
Predicate { inner: self.inner.clone(), description: self.description.clone() }
}
}
impl<T> Debug for Predicate<T> {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.description)
}
}
impl<T: 'static> Predicate<T> {
pub fn satisfied(&self, t: &T) -> bool {
(self.inner)(t)
}
pub fn or(self, rhs: Predicate<T>) -> Predicate<T> {
let description = format!("({}) OR ({})", self.description, rhs.description);
Predicate {
inner: Arc::new(move |t: &T| -> bool { (self.inner)(t) || (rhs.inner)(t) }),
description,
}
}
pub fn and(self, rhs: Predicate<T>) -> Predicate<T> {
let description = format!("({}) AND ({})", self.description, rhs.description);
Predicate {
inner: Arc::new(move |t: &T| -> bool { (self.inner)(t) && (rhs.inner)(t) }),
description,
}
}
pub fn not(self) -> Predicate<T> {
let description = format!("NOT ({})", self.description);
Predicate { inner: Arc::new(move |t: &T| -> bool { !(self.inner)(t) }), description }
}
pub fn new<F>(f: F, label: Option<&str>) -> Predicate<T>
where
F: Fn(&T) -> bool + Send + Sync + 'static,
{
Predicate {
inner: Arc::new(f),
description: label.unwrap_or("<Unrepresentable Predicate>").to_string(),
}
}
pub fn describe(&self) -> String {
self.description.clone()
}
}
/// Expectations for Bluetooth Peers (i.e. Remote Devices)
pub mod peer {
use super::Predicate;
use {
crate::types::{Address, Peer, PeerId},
fidl_fuchsia_bluetooth_sys::TechnologyType,
};
pub fn name(name: &str) -> Predicate<Peer> {
let name_owned = Some(name.to_string());
Predicate::<Peer>::new(
move |peer| peer.name == name_owned,
Some(&format!("name == {}", name)),
)
}
pub fn identifier(id: PeerId) -> Predicate<Peer> {
Predicate::<Peer>::new(move |peer| peer.id == id, Some(&format!("peer id == {}", id)))
}
pub fn address(address: Address) -> Predicate<Peer> {
Predicate::<Peer>::new(
move |peer| peer.address == address,
Some(&format!("address == {}", address)),
)
}
pub fn technology(tech: TechnologyType) -> Predicate<Peer> {
Predicate::<Peer>::new(
move |peer| peer.technology == tech,
Some(&format!("technology == {:?}", tech)),
)
}
pub fn connected(connected: bool) -> Predicate<Peer> {
Predicate::<Peer>::new(
move |peer| peer.connected == connected,
Some(&format!("connected == {}", connected)),
)
}
pub fn bonded(bonded: bool) -> Predicate<Peer> {
Predicate::<Peer>::new(
move |peer| peer.bonded == bonded,
Some(&format!("bonded == {}", bonded)),
)
}
}
/// Expectations for the Bluetooth Host Driver (bt-host)
pub mod host_driver {
use super::Predicate;
use crate::types::HostInfo;
pub fn name(expected_name: &str) -> Predicate<HostInfo> {
let name = Some(expected_name.to_string());
Predicate::<HostInfo>::new(
move |host_driver| host_driver.local_name == name,
Some(&format!("name == {}", expected_name)),
)
}
pub fn discovering(discovering: bool) -> Predicate<HostInfo> {
Predicate::<HostInfo>::new(
move |host_driver| host_driver.discovering == discovering,
Some(&format!("discovering == {}", discovering)),
)
}
pub fn discoverable(discoverable: bool) -> Predicate<HostInfo> {
Predicate::<HostInfo>::new(
move |host_driver| host_driver.discoverable == discoverable,
Some(&format!("discoverable == {}", discoverable)),
)
}
}
#[cfg(test)]
mod test {
use crate::{
expectation::*,
types::{Address, Peer, PeerId},
};
use fidl_fuchsia_bluetooth_sys::TechnologyType;
const TEST_PEER_NAME: &'static str = "TestPeer";
const TEST_PEER_ADDRESS: Address = Address::Public([1, 0, 0, 0, 0, 0]);
const INCORRECT_PEER_NAME: &'static str = "IncorrectPeer";
const INCORRECT_PEER_ADDRESS: Address = Address::Public([2, 0, 0, 0, 0, 0]);
fn correct_name() -> Predicate<Peer> {
peer::name(TEST_PEER_NAME)
}
fn incorrect_name() -> Predicate<Peer> {
peer::name(INCORRECT_PEER_NAME)
}
fn correct_address() -> Predicate<Peer> {
peer::address(TEST_PEER_ADDRESS)
}
fn incorrect_address() -> Predicate<Peer> {
peer::address(INCORRECT_PEER_ADDRESS)
}
fn test_peer() -> Peer {
Peer {
id: PeerId(1),
address: TEST_PEER_ADDRESS,
technology: TechnologyType::LowEnergy,
connected: false,
bonded: false,
name: Some(TEST_PEER_NAME.into()),
appearance: None,
device_class: None,
rssi: None,
tx_power: None,
services: vec![],
}
}
#[test]
fn simple_predicate_succeeds() {
let predicate =
Predicate::<Peer>::new(move |peer| peer.name == Some(TEST_PEER_NAME.into()), None);
assert!(predicate.satisfied(&test_peer()));
}
#[test]
fn simple_incorrect_predicate_fails() {
let predicate =
Predicate::<Peer>::new(move |peer| peer.name == Some("INCORRECT_NAME".into()), None);
assert!(!predicate.satisfied(&test_peer()));
}
#[test]
fn predicate_and_both_true_succeeds() {
let predicate = correct_name().and(correct_address());
assert!(predicate.satisfied(&test_peer()));
}
#[test]
fn predicate_and_one_or_more_false_fails() {
let predicate = correct_name().and(incorrect_address());
assert!(!predicate.satisfied(&test_peer()));
let predicate = incorrect_name().and(correct_address());
assert!(!predicate.satisfied(&test_peer()));
let predicate = incorrect_name().and(incorrect_address());
assert!(!predicate.satisfied(&test_peer()));
}
#[test]
fn predicate_or_both_false_fails() {
let predicate = incorrect_name().or(incorrect_address());
assert!(!predicate.satisfied(&test_peer()));
}
#[test]
fn predicate_or_one_or_more_true_succeeds() {
let predicate = correct_name().or(correct_address());
assert!(predicate.satisfied(&test_peer()));
let predicate = incorrect_name().or(correct_address());
assert!(predicate.satisfied(&test_peer()));
let predicate = correct_name().or(incorrect_address());
assert!(predicate.satisfied(&test_peer()));
}
#[test]
fn predicate_not_incorrect_succeeds() {
let predicate = incorrect_name().not();
assert!(predicate.satisfied(&test_peer()));
}
#[test]
fn predicate_not_correct_fails() {
let predicate = correct_name().not();
assert!(!predicate.satisfied(&test_peer()));
}
}