Skip to content

Commit

Permalink
Add WaitForEdgeForever and WaitForStateForever transactions (#125)
Browse files Browse the repository at this point in the history
These return a Pending future to the callee.
  • Loading branch information
leighleighleigh authored Feb 15, 2025
1 parent fb83b9e commit e11348e
Show file tree
Hide file tree
Showing 2 changed files with 139 additions and 27 deletions.
5 changes: 3 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ eh0 = ["dep:eh0", "dep:nb"]
eh1 = ["dep:eh1", "dep:embedded-hal-nb"]

embedded-time = ["dep:embedded-time", "dep:void"]
embedded-hal-async = ["dep:embedded-hal-async"]
embedded-hal-async = ["dep:embedded-hal-async","dep:futures"]

default = ["eh1", "embedded-time"]

Expand All @@ -33,12 +33,13 @@ eh0 = { package = "embedded-hal", version = "0.2.7", features = ["unproven"], op
eh1 = { package = "embedded-hal", version = "1.0", optional = true }
embedded-hal-nb = { version = "1.0", optional = true }
embedded-hal-async = { version = "1.0", optional = true }
futures = { version = "0.3.31", default-features = false, optional = true }
embedded-time = { version = "0.12", optional = true }
nb = { version = "1.1", optional = true }
void = { version = "^1.0", optional = true }

[dev-dependencies]
tokio = { version = "1.21.1", features = ["rt", "macros"] }
tokio = { version = "1.21.1", features = ["rt", "macros", "time"] }

[package.metadata.docs.rs]
all-features = true
Expand Down
161 changes: 136 additions & 25 deletions src/eh1/digital.rs
Original file line number Diff line number Diff line change
Expand Up @@ -120,12 +120,28 @@ impl Transaction {
Transaction::new(TransactionKind::WaitForState(state))
}

/// Create a new wait_for_state transaction,
/// which returns a forever-Pending future when called.
/// This simulates waiting on a pin that never changes.
#[cfg(feature = "embedded-hal-async")]
pub fn wait_for_state_forever(state: State) -> Transaction {
Transaction::new(TransactionKind::WaitForStateForever(state))
}

/// Crate a new wait_for_edge transaction
#[cfg(feature = "embedded-hal-async")]
pub fn wait_for_edge(edge: Edge) -> Transaction {
Transaction::new(TransactionKind::WaitForEdge(edge))
}

/// Crate a new wait_for_edge transaction,
/// which returns a forever-Pending future when called.
/// This simulates waiting on a pin that never changes.
#[cfg(feature = "embedded-hal-async")]
pub fn wait_for_edge_forever(edge: Edge) -> Transaction {
Transaction::new(TransactionKind::WaitForEdgeForever(edge))
}

/// Add an error return to a transaction
///
/// This is used to mock failure behaviours.
Expand Down Expand Up @@ -157,9 +173,15 @@ pub enum TransactionKind {
/// Wait for the given pin state
#[cfg(feature = "embedded-hal-async")]
WaitForState(State),
/// Wait for the given pin state, returning Pending to simulate a never-changing pin
#[cfg(feature = "embedded-hal-async")]
WaitForStateForever(State),
/// Wait for the given pin edge
#[cfg(feature = "embedded-hal-async")]
WaitForEdge(Edge),
/// Wait for the given pin edge, returning Pending to simulate a never-changing pin
#[cfg(feature = "embedded-hal-async")]
WaitForEdgeForever(Edge),
}

impl TransactionKind {
Expand Down Expand Up @@ -309,6 +331,9 @@ impl StatefulOutputPin for Mock {
}
}

#[cfg(feature = "embedded-hal-async")]
use futures::future::pending;

#[cfg(feature = "embedded-hal-async")]
impl embedded_hal_async::digital::Wait for Mock {
/// Wait for the pin to go high
Expand All @@ -320,14 +345,18 @@ impl embedded_hal_async::digital::Wait for Mock {
.expect("no expectation for pin::wait_for_high call");

assert!(
matches!(kind, TransactionKind::WaitForState(State::High)),
matches!(
kind,
TransactionKind::WaitForState(State::High)
| TransactionKind::WaitForStateForever(State::High)
),
"got call to wait_for_high"
);

if let Some(e) = err {
Err(e)
} else {
Ok(())
match (kind, err) {
(_, Some(e)) => Err(e),
(TransactionKind::WaitForState(State::High), _) => Ok(()),
_ => pending().await,
}
}

Expand All @@ -339,14 +368,18 @@ impl embedded_hal_async::digital::Wait for Mock {
s.next().expect("no expectation for pin::wait_for_low call");

assert!(
matches!(kind, TransactionKind::WaitForState(State::Low)),
matches!(
kind,
TransactionKind::WaitForState(State::Low)
| TransactionKind::WaitForStateForever(State::Low)
),
"got call to wait_for_low"
);

if let Some(e) = err {
Err(e)
} else {
Ok(())
match (kind, err) {
(_, Some(e)) => Err(e),
(TransactionKind::WaitForState(State::Low), _) => Ok(()),
_ => pending().await,
}
}

Expand All @@ -359,14 +392,18 @@ impl embedded_hal_async::digital::Wait for Mock {
.expect("no expectation for pin::wait_for_rising_edge call");

assert!(
matches!(kind, TransactionKind::WaitForEdge(Edge::Rising)),
matches!(
kind,
TransactionKind::WaitForEdge(Edge::Rising)
| TransactionKind::WaitForEdgeForever(Edge::Rising)
),
"got call to wait_for_rising_edge"
);

if let Some(e) = err {
Err(e)
} else {
Ok(())
match (kind, err) {
(_, Some(e)) => Err(e),
(TransactionKind::WaitForEdge(Edge::Rising), _) => Ok(()),
_ => pending().await,
}
}

Expand All @@ -379,14 +416,18 @@ impl embedded_hal_async::digital::Wait for Mock {
.expect("no expectation for pin::wait_for_falling_edge call");

assert!(
matches!(kind, TransactionKind::WaitForEdge(Edge::Falling)),
matches!(
kind,
TransactionKind::WaitForEdge(Edge::Falling)
| TransactionKind::WaitForEdgeForever(Edge::Falling)
),
"got call to wait_for_falling_edge"
);

if let Some(e) = err {
Err(e)
} else {
Ok(())
match (kind, err) {
(_, Some(e)) => Err(e),
(TransactionKind::WaitForEdge(Edge::Falling), _) => Ok(()),
_ => pending().await,
}
}

Expand All @@ -399,24 +440,32 @@ impl embedded_hal_async::digital::Wait for Mock {
.expect("no expectation for pin::wait_for_any_edge call");

assert!(
matches!(kind, TransactionKind::WaitForEdge(Edge::Any)),
matches!(
kind,
TransactionKind::WaitForEdge(Edge::Any)
| TransactionKind::WaitForEdgeForever(Edge::Any)
),
"got call to wait_for_any_edge"
);

if let Some(e) = err {
Err(e)
} else {
Ok(())
match (kind, err) {
(_, Some(e)) => Err(e),
(TransactionKind::WaitForEdge(Edge::Any), _) => Ok(()),
_ => pending().await,
}
}
}

#[cfg(test)]
mod test {
use std::io::ErrorKind;
#[cfg(feature = "embedded-hal-async")]
use std::time::Duration;

use eh1 as embedded_hal;
use embedded_hal::digital::{InputPin, OutputPin, StatefulOutputPin};
#[cfg(feature = "embedded-hal-async")]
use tokio::time::timeout;

use super::{
super::error::MockError,
Expand Down Expand Up @@ -528,6 +577,35 @@ mod test {
pin.done();
}

#[tokio::test]
#[cfg(feature = "embedded-hal-async")]
async fn test_can_wait_for_state_forever() {
use embedded_hal_async::digital::Wait;

let expectations = [
Transaction::new(TransactionKind::WaitForStateForever(State::High)),
Transaction::new(TransactionKind::WaitForStateForever(State::Low)),
Transaction::new(TransactionKind::WaitForStateForever(State::High))
.with_error(MockError::Io(ErrorKind::NotConnected)),
];
let mut pin = Mock::new(&expectations);

// we should not return Ok while asking to wait forever!
timeout(Duration::from_millis(10), pin.wait_for_high())
.await
.expect_err("expected wait_for_high timeout");
timeout(Duration::from_millis(10), pin.wait_for_low())
.await
.expect_err("expected wait_for_low timeout");

// errors are still returned, since maybe awaiting on the pin failed for some reason
pin.wait_for_high()
.await
.expect_err("expected error return");

pin.done();
}

#[tokio::test]
#[cfg(feature = "embedded-hal-async")]
async fn test_can_wait_for_edge() {
Expand All @@ -553,6 +631,39 @@ mod test {
pin.done();
}

#[tokio::test]
#[cfg(feature = "embedded-hal-async")]
async fn test_can_wait_for_edge_forever() {
use embedded_hal_async::digital::Wait;

let expectations = [
Transaction::new(TransactionKind::WaitForEdgeForever(Edge::Rising)),
Transaction::new(TransactionKind::WaitForEdgeForever(Edge::Falling)),
Transaction::new(TransactionKind::WaitForEdgeForever(Edge::Any)),
Transaction::new(TransactionKind::WaitForEdgeForever(Edge::Rising))
.with_error(MockError::Io(ErrorKind::NotConnected)),
];
let mut pin = Mock::new(&expectations);

// we should not return Ok while asking to wait forever!
timeout(Duration::from_millis(10), pin.wait_for_rising_edge())
.await
.expect_err("expected wait_for_rising_edge timeout");
timeout(Duration::from_millis(10), pin.wait_for_falling_edge())
.await
.expect_err("expected wait_for_falling_edge timeout");
timeout(Duration::from_millis(10), pin.wait_for_any_edge())
.await
.expect_err("expected wait_for_any_edge timeout");

// errors are still returned, since maybe awaiting on the pin failed for some reason
pin.wait_for_rising_edge()
.await
.expect_err("expected error return");

pin.done();
}

#[tokio::test]
#[should_panic(expected = "got call to wait_for_rising_edge")]
#[cfg(feature = "embedded-hal-async")]
Expand Down

0 comments on commit e11348e

Please sign in to comment.