Skip to content

Commit 6d37a75

Browse files
committed
Add integration tests and std compatibility
1 parent 2b57a6f commit 6d37a75

File tree

5 files changed

+233
-9
lines changed

5 files changed

+233
-9
lines changed

.vscode/settings.json

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"rust-analyzer.cargo.features": ["std"]
3+
}

Cargo.toml

+3-1
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,10 @@ cfg-if = "1.0.0"
1616
embassy-time = { git = "https://github.com/embassy-rs/embassy/", rev = "fe8c46bce329efe7921386dd46a493f607453bd8" }
1717
embedded-hal-async = "1.0.0-rc.2"
1818
embedded-hal = "1.0.0-rc.2"
19-
2019
defmt = { version = "0.3.5", optional = true }
2120

21+
tokio = { version = "1.34.0", default-features = false, optional = true }
22+
2223
[dev-dependencies]
2324
embedded-hal-mock = { version = "0.10.0-rc.3", default-features = false, features = [
2425
"eh1",
@@ -31,6 +32,7 @@ claims = "0.7.1"
3132
[features]
3233
default = []
3334
defmt = ["dep:defmt", "embassy-time/defmt"]
35+
std = ["dep:tokio"]
3436

3537
[patch.crates-io]
3638
embedded-hal-mock = { git = "https://github.com/avsaase/embedded-hal-mock/", rev = "bec7a531d1e91fa19ef43abfb4af629000b15e90" }

src/lib.rs

+6-6
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,11 @@ mod config;
1010
mod tests;
1111

1212
cfg_if::cfg_if! {
13-
if #[cfg(not(test))] {
14-
use embassy_time::{with_timeout, Duration, Timer};
15-
} else {
13+
if #[cfg(any(test, feature = "std"))] {
1614
use std::time::Duration;
1715
use tokio::time::timeout as with_timeout;
16+
} else {
17+
use embassy_time::{with_timeout, Duration, Timer};
1818
}
1919
}
2020

@@ -183,10 +183,10 @@ where
183183

184184
async fn delay(duration: Duration) {
185185
cfg_if::cfg_if! {
186-
if #[cfg(not(test))] {
187-
Timer::after(duration).await;
188-
} else {
186+
if #[cfg(any(test, feature = "std"))] {
189187
tokio::time::sleep(duration).await;
188+
} else {
189+
Timer::after(duration).await;
190190
}
191191
}
192192
}

src/tests.rs

-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
// use std::assert_matches::assert_matches;
2-
31
use claims::{assert_matches, assert_none, assert_some_eq};
42
use embedded_hal_mock::eh1::pin::{Mock, State as PinState, Transaction, TransactionKind as Kind};
53

tests/events.rs

+221
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,221 @@
1+
use std::{sync::Arc, time::Duration};
2+
3+
use async_button::{Button, ButtonConfig, ButtonEvent, Mode};
4+
use claims::{assert_err, assert_matches};
5+
use embedded_hal::digital::{Error, ErrorType};
6+
use tokio::{sync::RwLock, task::yield_now, time::timeout};
7+
8+
// Use shorter times to speed up test execution
9+
const CONFIG: ButtonConfig = ButtonConfig {
10+
debounce: Duration::from_millis(10),
11+
double_click: Duration::from_millis(100),
12+
long_press: Duration::from_millis(200),
13+
mode: Mode::PullUp,
14+
};
15+
16+
#[tokio::test]
17+
async fn short_press() {
18+
let mut pin = MockPin::new();
19+
let mut button = {
20+
let pin = pin.clone();
21+
Button::new(pin, CONFIG)
22+
};
23+
24+
tokio::spawn(async move {
25+
pin.set_low().await;
26+
sleep_millis(50).await;
27+
pin.set_high().await;
28+
sleep_millis(150).await;
29+
});
30+
31+
let event = button.update().await;
32+
assert_matches!(event, ButtonEvent::ShortPress { count: 1 });
33+
}
34+
35+
#[tokio::test]
36+
async fn double_press() {
37+
let mut pin = MockPin::new();
38+
let mut button = {
39+
let pin = pin.clone();
40+
Button::new(pin, CONFIG)
41+
};
42+
43+
tokio::spawn(async move {
44+
pin.set_low().await;
45+
sleep_millis(50).await;
46+
pin.set_high().await;
47+
sleep_millis(50).await;
48+
pin.set_low().await;
49+
sleep_millis(50).await;
50+
pin.set_high().await;
51+
sleep_millis(150).await;
52+
});
53+
54+
let event = button.update().await;
55+
assert_matches!(event, ButtonEvent::ShortPress { count: 2 });
56+
assert_err!(
57+
timeout(Duration::from_millis(500), button.update()).await,
58+
"Unexpected event"
59+
);
60+
}
61+
62+
#[tokio::test]
63+
async fn long_press() {
64+
let mut pin = MockPin::new();
65+
let mut button = {
66+
let pin = pin.clone();
67+
Button::new(pin, CONFIG)
68+
};
69+
70+
tokio::spawn(async move {
71+
pin.set_low().await;
72+
sleep_millis(250).await;
73+
pin.set_high().await;
74+
sleep_millis(150).await;
75+
});
76+
77+
let event = button.update().await;
78+
assert_matches!(event, ButtonEvent::LongPress);
79+
assert_err!(
80+
timeout(Duration::from_millis(500), button.update()).await,
81+
"Unexpected event"
82+
);
83+
}
84+
85+
#[tokio::test]
86+
async fn two_short_presses() {
87+
let mut pin = MockPin::new();
88+
let mut button = {
89+
let pin = pin.clone();
90+
Button::new(pin, CONFIG)
91+
};
92+
93+
tokio::spawn(async move {
94+
pin.set_low().await;
95+
sleep_millis(50).await;
96+
pin.set_high().await;
97+
sleep_millis(250).await;
98+
pin.set_low().await;
99+
sleep_millis(50).await;
100+
pin.set_high().await;
101+
sleep_millis(250).await;
102+
});
103+
104+
let event = button.update().await;
105+
assert_matches!(event, ButtonEvent::ShortPress { count: 1 });
106+
let event = button.update().await;
107+
assert_matches!(event, ButtonEvent::ShortPress { count: 1 });
108+
assert_err!(
109+
timeout(Duration::from_millis(500), button.update()).await,
110+
"Unexpected event"
111+
);
112+
}
113+
114+
#[tokio::test]
115+
async fn debounce() {
116+
let mut pin = MockPin::new();
117+
let mut button = {
118+
let pin = pin.clone();
119+
Button::new(pin, CONFIG)
120+
};
121+
122+
tokio::spawn(async move {
123+
pin.set_low().await;
124+
pin.set_high().await;
125+
pin.set_low().await;
126+
sleep_millis(50).await;
127+
pin.set_high().await;
128+
sleep_millis(150).await;
129+
});
130+
131+
let event = button.update().await;
132+
assert_matches!(event, ButtonEvent::ShortPress { count: 1 });
133+
assert_err!(
134+
timeout(Duration::from_millis(500), button.update()).await,
135+
"Unexpected event"
136+
);
137+
}
138+
139+
#[derive(Debug, Clone)]
140+
struct MockPin(Arc<RwLock<bool>>);
141+
142+
#[derive(Debug)]
143+
struct MockError;
144+
145+
impl MockPin {
146+
fn new() -> Self {
147+
Self(Arc::new(RwLock::new(true)))
148+
}
149+
150+
async fn set_high(&mut self) {
151+
*self.0.write().await = true;
152+
}
153+
154+
async fn set_low(&mut self) {
155+
*self.0.write().await = false;
156+
}
157+
}
158+
159+
impl embedded_hal::digital::InputPin for MockPin {
160+
fn is_high(&self) -> Result<bool, Self::Error> {
161+
match self.0.try_read() {
162+
Ok(rw) => Ok(*rw),
163+
Err(_) => Err(MockError),
164+
}
165+
}
166+
167+
fn is_low(&self) -> Result<bool, Self::Error> {
168+
match self.0.try_read() {
169+
Ok(rw) => Ok(!*rw),
170+
Err(_) => Err(MockError),
171+
}
172+
}
173+
}
174+
175+
impl embedded_hal_async::digital::Wait for MockPin {
176+
async fn wait_for_high(&mut self) -> Result<(), Self::Error> {
177+
loop {
178+
yield_now().await;
179+
let value = *self.0.read().await;
180+
if value {
181+
return Ok(());
182+
}
183+
}
184+
}
185+
186+
async fn wait_for_low(&mut self) -> Result<(), Self::Error> {
187+
loop {
188+
yield_now().await;
189+
let value = *self.0.read().await;
190+
if !value {
191+
return Ok(());
192+
}
193+
}
194+
}
195+
196+
async fn wait_for_rising_edge(&mut self) -> Result<(), Self::Error> {
197+
unimplemented!()
198+
}
199+
200+
async fn wait_for_falling_edge(&mut self) -> Result<(), Self::Error> {
201+
unimplemented!()
202+
}
203+
204+
async fn wait_for_any_edge(&mut self) -> Result<(), Self::Error> {
205+
unimplemented!()
206+
}
207+
}
208+
209+
impl ErrorType for MockPin {
210+
type Error = MockError;
211+
}
212+
213+
impl Error for MockError {
214+
fn kind(&self) -> embedded_hal::digital::ErrorKind {
215+
embedded_hal::digital::ErrorKind::Other
216+
}
217+
}
218+
219+
async fn sleep_millis(millis: u64) {
220+
tokio::time::sleep(Duration::from_millis(millis)).await;
221+
}

0 commit comments

Comments
 (0)