Skip to content

Commit

Permalink
Add integration tests and std compatibility
Browse files Browse the repository at this point in the history
  • Loading branch information
avsaase committed Dec 1, 2023
1 parent 2b57a6f commit 6d37a75
Show file tree
Hide file tree
Showing 5 changed files with 233 additions and 9 deletions.
3 changes: 3 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"rust-analyzer.cargo.features": ["std"]
}
4 changes: 3 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,10 @@ cfg-if = "1.0.0"
embassy-time = { git = "https://github.com/embassy-rs/embassy/", rev = "fe8c46bce329efe7921386dd46a493f607453bd8" }
embedded-hal-async = "1.0.0-rc.2"
embedded-hal = "1.0.0-rc.2"

defmt = { version = "0.3.5", optional = true }

tokio = { version = "1.34.0", default-features = false, optional = true }

[dev-dependencies]
embedded-hal-mock = { version = "0.10.0-rc.3", default-features = false, features = [
"eh1",
Expand All @@ -31,6 +32,7 @@ claims = "0.7.1"
[features]
default = []
defmt = ["dep:defmt", "embassy-time/defmt"]
std = ["dep:tokio"]

[patch.crates-io]
embedded-hal-mock = { git = "https://github.com/avsaase/embedded-hal-mock/", rev = "bec7a531d1e91fa19ef43abfb4af629000b15e90" }
12 changes: 6 additions & 6 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,11 @@ mod config;
mod tests;

cfg_if::cfg_if! {
if #[cfg(not(test))] {
use embassy_time::{with_timeout, Duration, Timer};
} else {
if #[cfg(any(test, feature = "std"))] {
use std::time::Duration;
use tokio::time::timeout as with_timeout;
} else {
use embassy_time::{with_timeout, Duration, Timer};
}
}

Expand Down Expand Up @@ -183,10 +183,10 @@ where

async fn delay(duration: Duration) {
cfg_if::cfg_if! {
if #[cfg(not(test))] {
Timer::after(duration).await;
} else {
if #[cfg(any(test, feature = "std"))] {
tokio::time::sleep(duration).await;
} else {
Timer::after(duration).await;
}
}
}
2 changes: 0 additions & 2 deletions src/tests.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
// use std::assert_matches::assert_matches;

use claims::{assert_matches, assert_none, assert_some_eq};
use embedded_hal_mock::eh1::pin::{Mock, State as PinState, Transaction, TransactionKind as Kind};

Expand Down
221 changes: 221 additions & 0 deletions tests/events.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,221 @@
use std::{sync::Arc, time::Duration};

use async_button::{Button, ButtonConfig, ButtonEvent, Mode};
use claims::{assert_err, assert_matches};
use embedded_hal::digital::{Error, ErrorType};
use tokio::{sync::RwLock, task::yield_now, time::timeout};

// Use shorter times to speed up test execution
const CONFIG: ButtonConfig = ButtonConfig {
debounce: Duration::from_millis(10),
double_click: Duration::from_millis(100),
long_press: Duration::from_millis(200),
mode: Mode::PullUp,
};

#[tokio::test]
async fn short_press() {
let mut pin = MockPin::new();
let mut button = {
let pin = pin.clone();
Button::new(pin, CONFIG)
};

tokio::spawn(async move {
pin.set_low().await;
sleep_millis(50).await;
pin.set_high().await;
sleep_millis(150).await;
});

let event = button.update().await;
assert_matches!(event, ButtonEvent::ShortPress { count: 1 });
}

#[tokio::test]
async fn double_press() {
let mut pin = MockPin::new();
let mut button = {
let pin = pin.clone();
Button::new(pin, CONFIG)
};

tokio::spawn(async move {
pin.set_low().await;
sleep_millis(50).await;
pin.set_high().await;
sleep_millis(50).await;
pin.set_low().await;
sleep_millis(50).await;
pin.set_high().await;
sleep_millis(150).await;
});

let event = button.update().await;
assert_matches!(event, ButtonEvent::ShortPress { count: 2 });
assert_err!(
timeout(Duration::from_millis(500), button.update()).await,
"Unexpected event"
);
}

#[tokio::test]
async fn long_press() {
let mut pin = MockPin::new();
let mut button = {
let pin = pin.clone();
Button::new(pin, CONFIG)
};

tokio::spawn(async move {
pin.set_low().await;
sleep_millis(250).await;
pin.set_high().await;
sleep_millis(150).await;
});

let event = button.update().await;
assert_matches!(event, ButtonEvent::LongPress);
assert_err!(
timeout(Duration::from_millis(500), button.update()).await,
"Unexpected event"
);
}

#[tokio::test]
async fn two_short_presses() {
let mut pin = MockPin::new();
let mut button = {
let pin = pin.clone();
Button::new(pin, CONFIG)
};

tokio::spawn(async move {
pin.set_low().await;
sleep_millis(50).await;
pin.set_high().await;
sleep_millis(250).await;
pin.set_low().await;
sleep_millis(50).await;
pin.set_high().await;
sleep_millis(250).await;
});

let event = button.update().await;
assert_matches!(event, ButtonEvent::ShortPress { count: 1 });
let event = button.update().await;
assert_matches!(event, ButtonEvent::ShortPress { count: 1 });
assert_err!(
timeout(Duration::from_millis(500), button.update()).await,
"Unexpected event"
);
}

#[tokio::test]
async fn debounce() {
let mut pin = MockPin::new();
let mut button = {
let pin = pin.clone();
Button::new(pin, CONFIG)
};

tokio::spawn(async move {
pin.set_low().await;
pin.set_high().await;
pin.set_low().await;
sleep_millis(50).await;
pin.set_high().await;
sleep_millis(150).await;
});

let event = button.update().await;
assert_matches!(event, ButtonEvent::ShortPress { count: 1 });
assert_err!(
timeout(Duration::from_millis(500), button.update()).await,
"Unexpected event"
);
}

#[derive(Debug, Clone)]
struct MockPin(Arc<RwLock<bool>>);

#[derive(Debug)]
struct MockError;

impl MockPin {
fn new() -> Self {
Self(Arc::new(RwLock::new(true)))
}

async fn set_high(&mut self) {
*self.0.write().await = true;
}

async fn set_low(&mut self) {
*self.0.write().await = false;
}
}

impl embedded_hal::digital::InputPin for MockPin {
fn is_high(&self) -> Result<bool, Self::Error> {
match self.0.try_read() {
Ok(rw) => Ok(*rw),
Err(_) => Err(MockError),
}
}

fn is_low(&self) -> Result<bool, Self::Error> {
match self.0.try_read() {
Ok(rw) => Ok(!*rw),
Err(_) => Err(MockError),
}
}
}

impl embedded_hal_async::digital::Wait for MockPin {
async fn wait_for_high(&mut self) -> Result<(), Self::Error> {
loop {
yield_now().await;
let value = *self.0.read().await;
if value {
return Ok(());
}
}
}

async fn wait_for_low(&mut self) -> Result<(), Self::Error> {
loop {
yield_now().await;
let value = *self.0.read().await;
if !value {
return Ok(());
}
}
}

async fn wait_for_rising_edge(&mut self) -> Result<(), Self::Error> {
unimplemented!()
}

async fn wait_for_falling_edge(&mut self) -> Result<(), Self::Error> {
unimplemented!()
}

async fn wait_for_any_edge(&mut self) -> Result<(), Self::Error> {
unimplemented!()
}
}

impl ErrorType for MockPin {
type Error = MockError;
}

impl Error for MockError {
fn kind(&self) -> embedded_hal::digital::ErrorKind {
embedded_hal::digital::ErrorKind::Other
}
}

async fn sleep_millis(millis: u64) {
tokio::time::sleep(Duration::from_millis(millis)).await;
}

0 comments on commit 6d37a75

Please sign in to comment.