Skip to content

Commit

Permalink
Merge pull request #55 from audunhalland/embedded-hal
Browse files Browse the repository at this point in the history
feat: Mock embedded-hal
  • Loading branch information
audunhalland authored Apr 8, 2024
2 parents ea463fb + 842ff6b commit d25ca15
Show file tree
Hide file tree
Showing 14 changed files with 386 additions and 54 deletions.
1 change: 1 addition & 0 deletions .github/workflows/rust.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ jobs:
--mutually-exclusive-features std,critical-section \
--mutually-exclusive-features std,spin-lock \
--group-features mock-std,mock-tokio-1,mock-futures-io-0-3 \
--group-features critical-section,spin-lock \
--exclude-features nightly-tests,unstable-doc-cfg \
test
- name: Doctest
Expand Down
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,12 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## Unreleased
### Added
- Support for mocking [embedded-hal](https://docs.rs/embedded-hal/latest/embedded_hal/) ([#55](https://github.com/audunhalland/unimock/pull/55)).
### Fixed
- Compile error encountered `&[NoDebug]` arguments.
- Default methods when the trait needs generic arguments.
- Missing documentation warning for the generated `with_types` function.

## [0.6.4] - 2024-04-01
### Added
Expand Down
14 changes: 12 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,14 @@ std = ["once_cell/std"]
pretty-print = ["dep:pretty_assertions"]
fragile = ["std", "dep:fragile"]
spin-lock = ["dep:spin"]
critical-section = ["once_cell/critical-section"]
mock-core = []
mock-std = ["std", "mock-core"]
mock-futures-io-0-3 = ["std", "dep:futures-io-0-3"]
mock-tokio-1 = ["std", "dep:tokio-1"]
mock-embedded-hal-1 = ["dep:embedded-hal-1", "mock-core"]
nightly-tests = []
unstable-doc-cfg = []
critical-section = ["once_cell/critical-section"]

[dependencies]
unimock_macros = { path = "unimock_macros", version = "0.6.4" }
Expand All @@ -35,6 +36,7 @@ fragile = { version = "2.0.0", optional = true }
spin = { version = "0.9.8", optional = true }
futures-io-0-3 = { package = "futures-io", version = "0.3.30", optional = true }
tokio-1 = { package = "tokio", version = "1.36", default-features = false, optional = true }
embedded-hal-1 = { package = "embedded-hal", version = "1.0.0", optional = true }

[dev-dependencies]
async-trait = "0.1"
Expand All @@ -47,7 +49,15 @@ rustversion = "1"
doctest = false

[package.metadata.docs.rs]
features = ["unstable-doc-cfg", "fragile", "mock-core", "mock-std", "mock-futures-io-0-3", "mock-tokio-1"]
features = [
"unstable-doc-cfg",
"fragile",
"mock-core",
"mock-std",
"mock-futures-io-0-3",
"mock-tokio-1",
"mock-embedded-hal-1",
]

[workspace]
members = ["unimock_macros"]
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -290,6 +290,8 @@ Unimock can be used in a `no_std` environment. The `"std"` feature is enabled by

The `no_std` environment depends on [alloc](https://doc.rust-lang.org/alloc/) and requires a global allocator.
Some unimock features rely on a working implementation of Mutex, and the `spin-lock` feature enables this for `no_std`.
The `critical-section` feature is also required for `no_std`.
These two features will likely merge into one in some future breaking release.


## Mock APIs for central crates
Expand Down
2 changes: 2 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -310,6 +310,8 @@
//!
//! The `no_std` environment depends on [alloc](https://doc.rust-lang.org/alloc/) and requires a global allocator.
//! Some unimock features rely on a working implementation of Mutex, and the `spin-lock` feature enables this for `no_std`.
//! The `critical-section` feature is also required for `no_std`.
//! These two features will likely merge into one in some future breaking release.
//!
//!
//! ## Mock APIs for central crates
Expand Down
212 changes: 212 additions & 0 deletions src/mock/embedded_hal_1.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,212 @@
//! Mock APIs for `embedded-hal` traits
/// Mock APIs for `embedded_hal::delay` traits
#[cfg(feature = "mock-embedded-hal-1")]
pub mod delay {
use crate::unimock;

#[unimock(prefix=crate, api=DelayNsMock, mirror=embedded_hal_1::delay::DelayNs)]
pub trait DelayNs {
fn delay_ns(&mut self, ns: u32);

fn delay_us(&mut self, us: u32) {}
fn delay_ms(&mut self, ms: u32) {}
}
}

/// Mock APIs for `embedded_hal::digital` traits
#[cfg(feature = "mock-embedded-hal-1")]
pub mod digital {
use crate::{private::DefaultImplDelegator, unimock, Unimock};
use embedded_hal_1::digital::{ErrorKind, ErrorType, PinState};

#[unimock(prefix=crate, api=ErrorMock, mirror=embedded_hal_1::digital::Error)]
pub trait Error {
fn kind(&self) -> ErrorKind;
}

#[unimock(prefix=crate, api=InputPinMock, mirror=embedded_hal_1::digital::InputPin)]
pub trait InputPin: ErrorType {
fn is_high(&mut self) -> Result<bool, <Self as ErrorType>::Error>;
fn is_low(&mut self) -> Result<bool, <Self as ErrorType>::Error>;
}

#[unimock(prefix=crate, api=OutputPinMock, mirror=embedded_hal_1::digital::OutputPin)]
pub trait OutputPin: ErrorType {
// Required methods
fn set_low(&mut self) -> Result<(), <Self as ErrorType>::Error>;
fn set_high(&mut self) -> Result<(), <Self as ErrorType>::Error>;

// Provided method
fn set_state(&mut self, state: PinState) -> Result<(), <Self as ErrorType>::Error> {}
}

#[unimock(prefix=crate, api=StatefulOutputPinMock, mirror=embedded_hal_1::digital::StatefulOutputPin)]
pub trait StatefulOutputPin: OutputPin {
// Required methods
fn is_set_high(&mut self) -> Result<bool, <Self as ErrorType>::Error>;
fn is_set_low(&mut self) -> Result<bool, <Self as ErrorType>::Error>;

// Provided method
fn toggle(&mut self) -> Result<(), <Self as ErrorType>::Error> {}
}

impl ErrorType for Unimock {
type Error = Unimock;
}
impl ErrorType for DefaultImplDelegator {
type Error = Unimock;
}
}

/// Mock APIs for `embedded_hal::i2c` traits
#[cfg(feature = "mock-embedded-hal-1")]
pub mod i2c {
use crate::{private::DefaultImplDelegator, unimock, Unimock};
use embedded_hal_1::i2c::{AddressMode, ErrorKind, ErrorType, Operation};

#[unimock(prefix=crate, api=ErrorMock, mirror=embedded_hal_1::i2c::Error)]
pub trait Error {
fn kind(&self) -> ErrorKind;
}

#[unimock(prefix=crate, api=I2cMock, mirror=embedded_hal_1::i2c::I2c)]
pub trait I2c<A: AddressMode>: ErrorType {
// Required methods
fn transaction(
&mut self,
address: A,
operations: &mut [Operation<'_>],
) -> Result<(), <Self as ErrorType>::Error>;

// Provided methods
fn read(&mut self, address: A, read: &mut [u8]) -> Result<(), <Self as ErrorType>::Error> {}

fn write(&mut self, address: A, write: &[u8]) -> Result<(), <Self as ErrorType>::Error> {}

fn write_read(
&mut self,
address: A,
write: &[u8],
read: &mut [u8],
) -> Result<(), <Self as ErrorType>::Error> {
}
}

impl ErrorType for Unimock {
type Error = Unimock;
}
impl ErrorType for DefaultImplDelegator {
type Error = Unimock;
}
}

/// Mock APIs for `embedded_hal::pwm` traits
#[cfg(feature = "mock-embedded-hal-1")]
pub mod pwm {
use crate::{private::DefaultImplDelegator, unimock, Unimock};
use embedded_hal_1::pwm::{ErrorKind, ErrorType};

#[unimock(prefix=crate, api=ErrorMock, mirror=embedded_hal_1::pwm::Error)]
pub trait Error {
fn kind(&self) -> ErrorKind;
}

#[unimock(prefix=crate, api=SetDutyCycleMock, mirror=embedded_hal_1::pwm::SetDutyCycle)]
pub trait SetDutyCycle: ErrorType {
// Required methods
fn max_duty_cycle(&self) -> u16;
fn set_duty_cycle(&mut self, duty: u16) -> Result<(), <Self as ErrorType>::Error>;

// Provided methods
fn set_duty_cycle_fully_off(&mut self) -> Result<(), <Self as ErrorType>::Error> {}

fn set_duty_cycle_fully_on(&mut self) -> Result<(), <Self as ErrorType>::Error> {}

fn set_duty_cycle_fraction(
&mut self,
num: u16,
denom: u16,
) -> Result<(), <Self as ErrorType>::Error> {
}

fn set_duty_cycle_percent(
&mut self,
percent: u8,
) -> Result<(), <Self as ErrorType>::Error> {
}
}

impl ErrorType for Unimock {
type Error = Unimock;
}
impl ErrorType for DefaultImplDelegator {
type Error = Unimock;
}
}

/// Mock APIs for `embedded_hal::spi` traits
#[cfg(feature = "mock-embedded-hal-1")]
pub mod spi {
use crate::{private::DefaultImplDelegator, unimock, Unimock};
use embedded_hal_1::spi::{ErrorKind, ErrorType, Operation};

#[unimock(prefix=crate, api=ErrorMock, mirror=embedded_hal_1::spi::Error)]
pub trait Error {
fn kind(&self) -> ErrorKind;
}

#[unimock(prefix=crate, api=SpiBusMock, mirror=embedded_hal_1::spi::SpiBus)]
pub trait SpiBus<Word: Copy + 'static> {
// Required methods
fn read(&mut self, words: &mut [Word]) -> Result<(), <Self as ErrorType>::Error>;

fn write(&mut self, words: &[Word]) -> Result<(), <Self as ErrorType>::Error>;

fn transfer(
&mut self,
read: &mut [Word],
write: &[Word],
) -> Result<(), <Self as ErrorType>::Error>;

fn transfer_in_place(
&mut self,
words: &mut [Word],
) -> Result<(), <Self as ErrorType>::Error>;
fn flush(&mut self) -> Result<(), <Self as ErrorType>::Error>;
}

#[unimock(prefix=crate, api=SpiDeviceMock, mirror=embedded_hal_1::spi::SpiDevice)]
pub trait SpiDevice<Word: Copy + 'static> {
// Required method
fn transaction(
&mut self,
operations: &mut [Operation<'_, Word>],
) -> Result<(), <Self as ErrorType>::Error>;

// Provided methods
fn read(&mut self, buf: &mut [Word]) -> Result<(), <Self as ErrorType>::Error> {}

fn write(&mut self, buf: &[Word]) -> Result<(), <Self as ErrorType>::Error> {}

fn transfer(
&mut self,
read: &mut [Word],
write: &[Word],
) -> Result<(), <Self as ErrorType>::Error> {
}

fn transfer_in_place(
&mut self,
buf: &mut [Word],
) -> Result<(), <Self as ErrorType>::Error> {
}
}

impl ErrorType for Unimock {
type Error = Unimock;
}
impl ErrorType for DefaultImplDelegator {
type Error = Unimock;
}
}
1 change: 1 addition & 0 deletions src/mock/mod.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
pub mod core;
pub mod embedded_hal_1;
pub mod futures_0_3;
pub mod std;
pub mod tokio_1;
1 change: 1 addition & 0 deletions test_all_stable.sh
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ cargo hack --feature-powerset \
--mutually-exclusive-features std,critical-section \
--mutually-exclusive-features std,spin-lock \
--group-features mock-std,mock-tokio-1,mock-futures-io-0-3 \
--group-features critical-section,spin-lock \
--exclude-features nightly-tests,unstable-doc-cfg \
test
cargo test --doc --features mock-core,mock-std
41 changes: 2 additions & 39 deletions tests/it/basic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -296,43 +296,6 @@ fn test_multiple() {
);
}

mod no_debug {
use super::*;

pub enum PrimitiveEnum {
Foo,
Bar,
}

#[unimock(api=VeryPrimitiveMock)]
trait VeryPrimitive {
fn primitive(&self, a: PrimitiveEnum, b: &str) -> PrimitiveEnum;
}

#[test]
fn can_match_a_non_debug_argument() {
let unimock = Unimock::new(VeryPrimitiveMock::primitive.stub(|each| {
each.call(matching!(PrimitiveEnum::Bar, _))
.answers(&|_, _, _| PrimitiveEnum::Foo);
}));

match unimock.primitive(PrimitiveEnum::Bar, "") {
PrimitiveEnum::Foo => {}
PrimitiveEnum::Bar => panic!(),
}
}

#[test]
#[should_panic(expected = "VeryPrimitive::primitive(?, \"\"): No matching call patterns.")]
fn should_format_non_debug_input_with_a_question_mark() {
Unimock::new(VeryPrimitiveMock::primitive.stub(|each| {
each.call(matching!(PrimitiveEnum::Bar, _))
.answers(&|_, _, _| PrimitiveEnum::Foo);
}))
.primitive(PrimitiveEnum::Foo, "");
}
}

#[test]
fn should_debug_reference_to_debug_implementing_type() {
#[derive(Debug)]
Expand Down Expand Up @@ -452,7 +415,7 @@ mod custom_api_module {
}

#[test]
#[should_panic = "Single::func: Expected Single::func(_) at tests/it/basic.rs:459 to match exactly 1 call, but it actually matched no calls.\nMock for Single::func was never called. Dead mocks should be removed."]
#[should_panic = "Single::func: Expected Single::func(_) at tests/it/basic.rs:422 to match exactly 1 call, but it actually matched no calls.\nMock for Single::func was never called. Dead mocks should be removed."]
fn test_without_module() {
Unimock::new(
FakeSingle::func
Expand Down Expand Up @@ -709,7 +672,7 @@ mod responders_in_series {

#[test]
#[should_panic(
expected = "Series::series: Expected Series::series() at tests/it/basic.rs:685 to match at least 4 calls, but it actually matched 2 calls."
expected = "Series::series: Expected Series::series() at tests/it/basic.rs:648 to match at least 4 calls, but it actually matched 2 calls."
)]
fn series_not_fully_generated_should_panic() {
let b = Unimock::new(clause());
Expand Down
10 changes: 7 additions & 3 deletions tests/it/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ mod default_impl;
mod errors;
mod generic;
mod matching_eq;
mod test_debug;

#[cfg(any(feature = "std", feature = "spin-lock"))]
mod matching_pat;
Expand All @@ -35,14 +36,17 @@ mod async_fn;
#[cfg(all(feature = "mock-core", feature = "mock-std"))]
mod std;

#[cfg(all(feature = "mock-tokio-1", feature = "std"))]
mod test_mock_tokio;

#[cfg(feature = "fragile")]
mod test_fragile;

mod unmock;

#[cfg(all(feature = "mock-tokio-1", feature = "std"))]
mod test_mock_tokio;

#[cfg(feature = "mock-embedded-hal-1")]
mod test_mock_embedded_hal;

fn main() {}

trait AsyncTest {
Expand Down
Loading

0 comments on commit d25ca15

Please sign in to comment.