├── clippy.toml ├── rustfmt.toml ├── .gitignore ├── .github ├── dependabot.yml └── workflows │ └── ci.yml ├── src ├── eh1.rs ├── eh0.rs ├── eh0 │ ├── error.rs │ ├── delay.rs │ ├── timer.rs │ ├── adc.rs │ ├── spi.rs │ ├── digital.rs │ ├── i2c.rs │ └── serial.rs ├── eh1 │ ├── error.rs │ ├── pwm.rs │ ├── serial.rs │ ├── delay.rs │ ├── i2c.rs │ └── digital.rs ├── lib.rs └── common.rs ├── CONTRIBUTING.md ├── RELEASING.md ├── LICENSE-MIT ├── Cargo.toml ├── README.md ├── CHANGELOG.md └── LICENSE-APACHE /clippy.toml: -------------------------------------------------------------------------------- 1 | msrv = "1.75" 2 | -------------------------------------------------------------------------------- /rustfmt.toml: -------------------------------------------------------------------------------- 1 | imports_granularity = "Crate" 2 | group_imports = "StdExternalCrate" 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.swp 2 | /target 3 | **/*.rs.bk 4 | Cargo.lock 5 | 6 | # IDEs 7 | .vscode 8 | .idea 9 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "github-actions" 4 | directory: "/" 5 | schedule: 6 | interval: "weekly" 7 | - package-ecosystem: "cargo" 8 | directory: "/" 9 | schedule: 10 | interval: "weekly" 11 | -------------------------------------------------------------------------------- /src/eh1.rs: -------------------------------------------------------------------------------- 1 | //! This is a collection of types that implement the embedded-hal version 1.x 2 | //! traits. 3 | //! 4 | //! ## Usage 5 | //! 6 | //! See module-level docs for more information. 7 | 8 | mod error; 9 | pub use crate::eh1::error::MockError; 10 | 11 | pub mod delay; 12 | pub mod digital; 13 | pub mod i2c; 14 | pub mod pwm; 15 | pub mod serial; 16 | pub mod spi; 17 | -------------------------------------------------------------------------------- /src/eh0.rs: -------------------------------------------------------------------------------- 1 | //! This is a collection of types that implement the embedded-hal version 0.x 2 | //! traits. 3 | //! 4 | //! ## Usage 5 | //! 6 | //! See module-level docs for more information. 7 | 8 | mod error; 9 | pub use error::MockError; 10 | 11 | pub mod adc; 12 | pub mod delay; 13 | pub mod digital; 14 | pub mod i2c; 15 | pub mod serial; 16 | pub mod spi; 17 | #[cfg(feature = "embedded-time")] 18 | pub mod timer; 19 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contribution Guidelines 2 | 3 | - Add tests and docs for any new functionality 4 | - Format the code with [rustfmt](https://github.com/rust-lang/rustfmt) 5 | (Install with `rustup component add rustfmt`, run with `cargo fmt`) 6 | - Use meaningful commit messages in imperative mood: Please follow the advice 7 | in [this blogpost](http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html). 8 | 9 | Thanks for your contributions :) 10 | -------------------------------------------------------------------------------- /RELEASING.md: -------------------------------------------------------------------------------- 1 | # Releasing 2 | 3 | Set variables: 4 | 5 | $ export VERSION=X.Y.Z 6 | $ export GPG_KEY=20EE002D778AE197EF7D0D2CB993FF98A90C9AB1 7 | 8 | Update version numbers: 9 | 10 | $ vim Cargo.toml 11 | 12 | Update changelog: 13 | 14 | $ vim CHANGELOG.md 15 | 16 | Commit & tag: 17 | 18 | $ git commit -S${GPG_KEY} -m "Release v${VERSION}" 19 | $ git tag -s -u ${GPG_KEY} v${VERSION} -m "Version ${VERSION}" 20 | 21 | Publish: 22 | 23 | $ cargo publish 24 | $ git push && git push --tags 25 | -------------------------------------------------------------------------------- /src/eh0/error.rs: -------------------------------------------------------------------------------- 1 | use std::{error::Error as StdError, fmt, io}; 2 | 3 | /// Errors that may occur during mocking. 4 | #[derive(PartialEq, Eq, Clone, Debug)] 5 | pub enum MockError { 6 | /// An I/O-Error occurred 7 | Io(io::ErrorKind), 8 | } 9 | 10 | impl From for MockError { 11 | fn from(e: io::Error) -> Self { 12 | MockError::Io(e.kind()) 13 | } 14 | } 15 | 16 | impl fmt::Display for MockError { 17 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 18 | match self { 19 | MockError::Io(kind) => write!(f, "I/O error: {:?}", kind), 20 | } 21 | } 22 | } 23 | 24 | impl StdError for MockError {} 25 | -------------------------------------------------------------------------------- /src/eh1/error.rs: -------------------------------------------------------------------------------- 1 | use std::{error::Error as StdError, fmt, io}; 2 | 3 | use eh1 as embedded_hal; 4 | use embedded_hal::digital::ErrorKind::{self, Other}; 5 | 6 | /// Errors that may occur during mocking. 7 | #[derive(PartialEq, Eq, Clone, Debug)] 8 | pub enum MockError { 9 | /// An I/O-Error occurred 10 | Io(io::ErrorKind), 11 | } 12 | 13 | impl embedded_hal::digital::Error for MockError { 14 | fn kind(&self) -> ErrorKind { 15 | Other 16 | } 17 | } 18 | 19 | impl From for MockError { 20 | fn from(e: io::Error) -> Self { 21 | MockError::Io(e.kind()) 22 | } 23 | } 24 | 25 | impl fmt::Display for MockError { 26 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 27 | match self { 28 | MockError::Io(kind) => write!(f, "I/O error: {:?}", kind), 29 | } 30 | } 31 | } 32 | 33 | impl StdError for MockError {} 34 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | Copyright (C) 2018-2024 Danilo Bargen and contributors 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of 4 | this software and associated documentation files (the "Software"), to deal in 5 | the Software without restriction, including without limitation the rights to 6 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 7 | of the Software, and to permit persons to whom the Software is furnished to do 8 | so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. 20 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | on: 2 | # trigger on pushes to the main branch 3 | push: 4 | branches: 5 | - main 6 | # trigger on all pull requests 7 | pull_request: 8 | # enable manual triggering 9 | workflow_dispatch: 10 | 11 | name: CI 12 | 13 | jobs: 14 | build: 15 | name: Build and Test 16 | runs-on: ubuntu-latest 17 | env: {"RUSTFLAGS": "-D warnings"} 18 | strategy: 19 | matrix: 20 | toolchain: 21 | - "1.75" 22 | - "stable" 23 | steps: 24 | - uses: actions/checkout@v6 25 | - uses: dtolnay/rust-toolchain@master 26 | with: 27 | toolchain: ${{ matrix.toolchain }} 28 | 29 | # Diagnostics 30 | - name: Show versions 31 | run: | 32 | rustc --version 33 | cargo --version 34 | 35 | # Build main crate 36 | - name: Build 37 | run: cargo build 38 | - name: Build (all features) 39 | if: ${{ matrix.toolchain == '1.75' }} 40 | run: cargo build --all-features 41 | 42 | # Test main crate 43 | - name: Test 44 | run: cargo test 45 | - name: Test (all features) 46 | if: ${{ matrix.toolchain == '1.75' }} 47 | run: cargo test --all-features 48 | 49 | # Check code formatting 50 | format: 51 | name: Check code formatting 52 | runs-on: ubuntu-latest 53 | steps: 54 | - uses: actions/checkout@v6 55 | - uses: dtolnay/rust-toolchain@nightly 56 | with: 57 | components: rustfmt 58 | - run: cargo +nightly fmt --all -- --check 59 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "embedded-hal-mock" 3 | version = "0.11.1" 4 | authors = ["Danilo Bargen "] 5 | categories = ["embedded", "hardware-support", "development-tools::testing"] 6 | description = "A collection of mocked devices that implement the embedded-hal traits" 7 | documentation = "https://docs.rs/embedded-hal-mock" 8 | keywords = ["hal", "io", "spi", "i2c", "delay"] 9 | license = "MIT OR Apache-2.0" 10 | readme = "README.md" 11 | repository = "https://github.com/dbrgn/embedded-hal-mock" 12 | include = [ 13 | "**/*.rs", 14 | "Cargo.toml", 15 | "README.md", 16 | "CHANGELOG.md", 17 | "LICENSE-MIT", 18 | "LICENSE-APACHE", 19 | ] 20 | edition = "2021" 21 | 22 | [features] 23 | eh0 = ["dep:eh0", "dep:nb"] 24 | eh1 = ["dep:eh1", "dep:embedded-hal-nb"] 25 | 26 | embedded-time = ["dep:embedded-time", "dep:void"] 27 | embedded-hal-async = ["dep:embedded-hal-async","dep:futures"] 28 | 29 | default = ["eh1", "embedded-time"] 30 | 31 | [dependencies] 32 | eh0 = { package = "embedded-hal", version = "0.2.7", features = ["unproven"], optional = true } 33 | eh1 = { package = "embedded-hal", version = "1.0", optional = true } 34 | embedded-hal-nb = { version = "1.0", optional = true } 35 | embedded-hal-async = { version = "1.0", optional = true } 36 | futures = { version = "0.3.31", default-features = false, optional = true } 37 | embedded-time = { version = "0.12", optional = true } 38 | nb = { version = "1.1", optional = true } 39 | void = { version = "^1.0", optional = true } 40 | 41 | [dev-dependencies] 42 | tokio = { version = "1.21.1", features = ["rt", "macros", "time"] } 43 | 44 | [package.metadata.docs.rs] 45 | all-features = true 46 | rustdoc-args = ["--cfg", "docsrs"] 47 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! This is a collection of types that implement the embedded-hal traits. 2 | //! 3 | //! The implementations never access real hardware. Instead, the hardware is 4 | //! mocked or no-op implementations are used. 5 | //! 6 | //! The goal of the crate is to be able to test drivers in CI without having 7 | //! access to hardware. 8 | //! 9 | //! ## Usage 10 | //! 11 | //! The general approach for testing drivers using mocks: 12 | //! 13 | //! 1. Define the expectations: A list of transactions (e.g. a read or write 14 | //! operation) that you expect the driver-under-test to invoke on the mocked 15 | //! hardware 16 | //! 2. Instantiate the mock with the expectations 17 | //! 3. Run the test code 18 | //! 4. At the end of the test code, call the `.done()` method on the mock to 19 | //! ensure that all expectations were met 20 | //! 21 | //! For more information, see module-level docs. 22 | //! 23 | //! **Note:** Mocks contain an `Arc` internally and can be cloned freely. This 24 | //! means you can clone a mock before passing it to the driver, and then call 25 | //! `.done()` on the second mock instance without having to reclaim the first 26 | //! instance from the driver. 27 | //! 28 | //! ## embedded_hal Version Support 29 | //! 30 | //! This crate supports both version 0.x and version 1.x of embedded-hal. By 31 | //! default only support for version 0.x is enabled. To enable support for 32 | //! version 1.x, use the `eh1` feature. 33 | //! 34 | //! ## Cargo Features 35 | //! 36 | //! There are currently the following cargo features: 37 | //! 38 | //! - `eh0`: Provide module [`eh0`] that mocks embedded-hal version 0.x 39 | //! (enabled by default) 40 | //! - `eh1`: Provide module [`eh1`] that mocks embedded-hal version 1.x 41 | //! (enabled by default) 42 | //! - `embedded-time`: Enable the [`eh0::timer`] module (enabled by default) 43 | //! - `embedded-hal-async`: Provide mocks for embedded-hal-async in [`eh1`] 44 | #![cfg_attr(docsrs, feature(doc_cfg), feature(doc_auto_cfg))] 45 | #![deny(missing_docs)] 46 | 47 | pub mod common; 48 | #[cfg(feature = "eh0")] 49 | pub mod eh0; 50 | #[cfg(feature = "eh1")] 51 | pub mod eh1; 52 | -------------------------------------------------------------------------------- /src/eh0/delay.rs: -------------------------------------------------------------------------------- 1 | //! Delay mock implementations. 2 | //! 3 | //! ## Usage 4 | //! 5 | //! If the actual sleep duration is not important, simply create a 6 | //! [`NoopDelay`](struct.NoopDelay.html) instance. There will be no actual 7 | //! delay. This is useful for fast tests, where you don't actually need to wait 8 | //! for the hardware. 9 | //! 10 | //! If you do want the real delay behavior, use 11 | //! [`StdSleep`](struct.StdSleep.html) which uses 12 | //! [`std::thread::sleep`](https://doc.rust-lang.org/std/thread/fn.sleep.html) 13 | //! to implement the delay. 14 | 15 | use std::{thread, time::Duration}; 16 | 17 | use eh0 as embedded_hal; 18 | use embedded_hal::blocking::delay; 19 | 20 | /// A `Delay` implementation that does not actually block. 21 | pub struct NoopDelay; 22 | 23 | impl NoopDelay { 24 | /// Create a new `NoopDelay` instance. 25 | pub fn new() -> Self { 26 | NoopDelay 27 | } 28 | } 29 | 30 | impl Default for NoopDelay { 31 | fn default() -> Self { 32 | Self::new() 33 | } 34 | } 35 | 36 | macro_rules! impl_noop_delay_us { 37 | ($type:ty) => { 38 | impl delay::DelayUs<$type> for NoopDelay { 39 | /// A no-op delay implementation. 40 | fn delay_us(&mut self, _n: $type) {} 41 | } 42 | }; 43 | } 44 | 45 | impl_noop_delay_us!(u8); 46 | impl_noop_delay_us!(u16); 47 | impl_noop_delay_us!(u32); 48 | impl_noop_delay_us!(u64); 49 | 50 | macro_rules! impl_noop_delay_ms { 51 | ($type:ty) => { 52 | impl delay::DelayMs<$type> for NoopDelay { 53 | /// A no-op delay implementation. 54 | fn delay_ms(&mut self, _n: $type) {} 55 | } 56 | }; 57 | } 58 | 59 | impl_noop_delay_ms!(u8); 60 | impl_noop_delay_ms!(u16); 61 | impl_noop_delay_ms!(u32); 62 | impl_noop_delay_ms!(u64); 63 | 64 | /// A `Delay` implementation that uses `std::thread::sleep`. 65 | pub struct StdSleep; 66 | 67 | impl StdSleep { 68 | /// Create a new `StdSleep` instance. 69 | pub fn new() -> Self { 70 | StdSleep 71 | } 72 | } 73 | 74 | impl Default for StdSleep { 75 | fn default() -> Self { 76 | Self::new() 77 | } 78 | } 79 | 80 | macro_rules! impl_stdsleep_delay_us { 81 | ($type:ty) => { 82 | impl delay::DelayUs<$type> for StdSleep { 83 | /// A `Delay` implementation that uses `std::thread::sleep`. 84 | fn delay_us(&mut self, n: $type) { 85 | thread::sleep(Duration::from_micros(n as u64)); 86 | } 87 | } 88 | }; 89 | } 90 | 91 | impl_stdsleep_delay_us!(u8); 92 | impl_stdsleep_delay_us!(u16); 93 | impl_stdsleep_delay_us!(u32); 94 | impl_stdsleep_delay_us!(u64); 95 | 96 | macro_rules! impl_stdsleep_delay_ms { 97 | ($type:ty) => { 98 | impl delay::DelayMs<$type> for StdSleep { 99 | /// A `Delay` implementation that uses `std::thread::sleep`. 100 | fn delay_ms(&mut self, n: $type) { 101 | thread::sleep(Duration::from_millis(n as u64)); 102 | } 103 | } 104 | }; 105 | } 106 | 107 | impl_stdsleep_delay_ms!(u8); 108 | impl_stdsleep_delay_ms!(u16); 109 | impl_stdsleep_delay_ms!(u32); 110 | impl_stdsleep_delay_ms!(u64); 111 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # embedded-hal-mock 2 | 3 | [![GitHub Actions][github-actions-badge]][github-actions] 4 | [![Crates.io Version][version-badge]][crates-io] 5 | 6 | This is a collection of types that implement the embedded-hal traits. 7 | 8 | The implementations never access real hardware. Instead, the hardware is mocked 9 | or no-op implementations are used. 10 | 11 | The goal of the crate is to be able to test drivers in CI without having access 12 | to hardware. 13 | 14 | [Docs](https://docs.rs/embedded-hal-mock/) 15 | 16 | ## Usage 17 | 18 | See module-level docs for more information. 19 | 20 | ## embedded_hal version 21 | 22 | This crate supports both version 0.x and version 1.x of embedded-hal. By default only support 23 | for version 0.x is enabled. To enable support for version 1.x, use the `eh1` feature. 24 | 25 | ## Cargo Features 26 | 27 | There are currently the following cargo features: 28 | 29 | - `eh0`: Provide module `eh0` that mocks embedded-hal version 0.x 30 | - `eh1`: Provide module `eh1` that mocks embedded-hal version 1.x (enabled by default) 31 | - `embedded-time`: Enable the `eh0::timer` module (enabled by default) 32 | - `embedded-hal-async`: Provide mocks for embedded-hal-async in `eh1` 33 | 34 | ## no\_std 35 | 36 | Currently this crate is not `no_std`. If you think this is important, let 37 | me know. 38 | 39 | ## Status 40 | 41 | | Feature | embedded-hal | embeded-hal-async | 42 | |---------------------------------------------|--------------|-------------------| 43 | | I²C | ✅ | ✅ | 44 | | SPI | ✅ | ✅ | 45 | | No-op delay | ✅ | ✅ | 46 | | Actual delay | ✅ | ✅ | 47 | | Serial | ✅ | - | 48 | | RNG | - | - | 49 | | I/O pins (including PWM) | ✅ | ✅ | 50 | | ADC | ✅ | - | 51 | | Timers (with `embedded-time` Cargo feature) | ✅ | - | 52 | 53 | Pull requests for more mock implementations are welcome! :) 54 | 55 | ## Minimum Supported Rust Version (MSRV) 56 | 57 | This crate is guaranteed to compile on the latest stable Rust release. It 58 | *might* compile with older versions but that may change in any new patch 59 | release. 60 | 61 | ## Development Version of `embedded-hal` 62 | 63 | If you would like to use the current development version of `embedded-hal` (or any other version), 64 | so long as they are API compatible you can use a patch field in your `Cargo.toml` file to override 65 | the dependency version. 66 | 67 | ```yaml 68 | [patch.crates-io] 69 | eh1 = { git = "https://github.com/rust-embedded/embedded-hal" } 70 | ``` 71 | 72 | ## License 73 | 74 | Licensed under either of 75 | 76 | * Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or 77 | http://www.apache.org/licenses/LICENSE-2.0) 78 | * MIT license ([LICENSE-MIT](LICENSE-MIT) or 79 | http://opensource.org/licenses/MIT) at your option. 80 | 81 | ### Contributing 82 | 83 | Unless you explicitly state otherwise, any contribution intentionally submitted 84 | for inclusion in the work by you, as defined in the Apache-2.0 license, shall 85 | be dual licensed as above, without any additional terms or conditions. 86 | 87 | 88 | [github-actions]: https://github.com/dbrgn/embedded-hal-mock/actions/workflows/ci.yml 89 | [github-actions-badge]: https://github.com/dbrgn/embedded-hal-mock/actions/workflows/ci.yml/badge.svg 90 | [crates-io]: https://crates.io/crates/embedded-hal-mock 91 | [version-badge]: https://img.shields.io/crates/v/embedded-hal-mock.svg 92 | -------------------------------------------------------------------------------- /src/eh1/pwm.rs: -------------------------------------------------------------------------------- 1 | //! Mock implementations for 2 | //! [`embedded_hal::pwm`](https://docs.rs/embedded-hal/1/embedded_hal/pwm/index.html). 3 | //! 4 | //! Usage example: 5 | //! ``` 6 | //! use std::io::ErrorKind; 7 | //! 8 | //! # use eh1 as embedded_hal; 9 | //! use embedded_hal::pwm::SetDutyCycle; 10 | //! use embedded_hal_mock::eh1::{ 11 | //! pwm::{Mock as PwmMock, Transaction as PwmTransaction}, 12 | //! MockError, 13 | //! }; 14 | //! 15 | //! // Configure expectations 16 | //! let expectations = [ 17 | //! PwmTransaction::max_duty_cycle(100), 18 | //! PwmTransaction::set_duty_cycle(50), 19 | //! PwmTransaction::set_duty_cycle(101).with_error(MockError::Io(ErrorKind::NotConnected)), 20 | //! ]; 21 | //! 22 | //! // Create pin 23 | //! let mut pwm = PwmMock::new(&expectations); 24 | //! 25 | //! // Run and test 26 | //! pwm.set_duty_cycle_percent(50).unwrap(); 27 | //! pwm.set_duty_cycle(101).expect_err("expected error return"); 28 | //! 29 | //! // Finalise expectations 30 | //! pwm.done(); 31 | //! ``` 32 | 33 | use eh1::pwm::{ErrorKind, ErrorType, SetDutyCycle}; 34 | 35 | use crate::{common::Generic, eh1::MockError}; 36 | 37 | /// MockPwm transaction 38 | #[derive(PartialEq, Clone, Debug)] 39 | pub struct Transaction { 40 | /// Kind is the transaction kind (and data) expected 41 | kind: TransactionKind, 42 | /// An optional error return value for a transaction. This is in addition 43 | /// to `kind` to allow validation that the transaction kind is correct 44 | /// prior to returning the error. 45 | err: Option, 46 | } 47 | 48 | impl Transaction { 49 | /// Create a new PWM transaction 50 | pub fn new(kind: TransactionKind) -> Transaction { 51 | Transaction { kind, err: None } 52 | } 53 | 54 | /// Create a new [`TransactionKind::GetMaxDutyCycle`] transaction for [`SetDutyCycle::max_duty_cycle`]. 55 | pub fn max_duty_cycle(duty: u16) -> Transaction { 56 | Transaction::new(TransactionKind::GetMaxDutyCycle(duty)) 57 | } 58 | 59 | /// Create a new [`TransactionKind::SetDutyCycle`] transaction for [`SetDutyCycle::set_duty_cycle`]. 60 | pub fn set_duty_cycle(duty: u16) -> Transaction { 61 | Transaction::new(TransactionKind::SetDutyCycle(duty)) 62 | } 63 | 64 | /// Add an error return to a transaction 65 | /// 66 | /// This is used to mock failure behaviours. 67 | pub fn with_error(mut self, error: MockError) -> Self { 68 | self.err = Some(error); 69 | self 70 | } 71 | } 72 | 73 | /// MockPwm transaction kind 74 | #[derive(PartialEq, Clone, Debug)] 75 | pub enum TransactionKind { 76 | /// [`SetDutyCycle::max_duty_cycle`] which will return the defined duty. 77 | GetMaxDutyCycle(u16), 78 | /// [`SetDutyCycle::set_duty_cycle`] with the expected duty. 79 | SetDutyCycle(u16), 80 | } 81 | 82 | /// Mock PWM `SetDutyCycle` implementation 83 | pub type Mock = Generic; 84 | 85 | impl eh1::pwm::Error for MockError { 86 | fn kind(&self) -> ErrorKind { 87 | ErrorKind::Other 88 | } 89 | } 90 | 91 | impl ErrorType for Mock { 92 | type Error = MockError; 93 | } 94 | 95 | impl SetDutyCycle for Mock { 96 | fn max_duty_cycle(&self) -> u16 { 97 | let mut s = self.clone(); 98 | 99 | let Transaction { kind, err } = s.next().expect("no expectation for max_duty_cycle call"); 100 | 101 | assert_eq!(err, None, "error not supported by max_duty_cycle!"); 102 | 103 | match kind { 104 | TransactionKind::GetMaxDutyCycle(duty) => duty, 105 | other => panic!("expected max_duty_cycle, got {:?}", other), 106 | } 107 | } 108 | 109 | fn set_duty_cycle(&mut self, duty: u16) -> Result<(), Self::Error> { 110 | let Transaction { kind, err } = 111 | self.next().expect("no expectation for set_duty_cycle call"); 112 | 113 | assert_eq!( 114 | kind, 115 | TransactionKind::SetDutyCycle(duty), 116 | "expected set_duty_cycle" 117 | ); 118 | 119 | if let Some(e) = err { 120 | Err(e) 121 | } else { 122 | Ok(()) 123 | } 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /src/eh0/timer.rs: -------------------------------------------------------------------------------- 1 | //! Provides a mocked [embedded_time::Clock] that can be used for host-side 2 | //! testing crates that use [embedded_hal::timer]. 3 | //! 4 | //! The provided [embedded_time::Clock] implementation is thread safe and can 5 | //! be freely skipped forward with nanosecond precision. 6 | //! 7 | //! # Usage 8 | //! 9 | //! ```rust 10 | //! # use eh0 as embedded_hal; 11 | //! use embedded_hal::timer::CountDown; 12 | //! use embedded_hal_mock::eh0::timer::MockClock; 13 | //! use embedded_time::duration::*; 14 | //! 15 | //! let mut clock = MockClock::new(); 16 | //! let mut timer = clock.get_timer(); 17 | //! timer.start(100.nanoseconds()); 18 | //! // hand over timer to embedded-hal based driver 19 | //! // continue to tick clock 20 | //! clock.tick(50.nanoseconds()); 21 | //! assert_eq!(timer.wait(), Err(nb::Error::WouldBlock)); 22 | //! clock.tick(50.nanoseconds()); 23 | //! assert_eq!(timer.wait(), Ok(())); 24 | //! clock.tick(50.nanoseconds()); 25 | //! assert_eq!(timer.wait(), Err(nb::Error::WouldBlock)); 26 | //! clock.tick(50.nanoseconds()); 27 | //! assert_eq!(timer.wait(), Ok(())); 28 | //! ``` 29 | 30 | use std::{ 31 | convert::Infallible, 32 | sync::{ 33 | atomic::{AtomicU64, Ordering}, 34 | Arc, 35 | }, 36 | }; 37 | 38 | use eh0 as embedded_hal; 39 | use embedded_hal::timer::{Cancel, CountDown, Periodic}; 40 | pub use embedded_time::Clock; 41 | use embedded_time::{clock, duration::*, fraction::Fraction, Instant}; 42 | use void::Void; 43 | 44 | /// A simulated clock that can be used in tests. 45 | #[derive(Clone, Debug)] 46 | pub struct MockClock { 47 | ticks: Arc, 48 | } 49 | 50 | impl Clock for MockClock { 51 | type T = u64; 52 | const SCALING_FACTOR: Fraction = Fraction::new(1, 1_000_000_000); 53 | 54 | fn try_now(&self) -> Result, clock::Error> { 55 | let ticks: u64 = self.ticks.load(Ordering::Relaxed); 56 | Ok(Instant::::new(ticks)) 57 | } 58 | } 59 | 60 | impl Default for MockClock { 61 | fn default() -> Self { 62 | MockClock { 63 | ticks: Arc::new(AtomicU64::new(0)), 64 | } 65 | } 66 | } 67 | 68 | impl MockClock { 69 | /// Creates a new simulated clock. 70 | pub fn new() -> Self { 71 | Self::default() 72 | } 73 | 74 | /// Returns the number of elapsed nanoseconds. 75 | pub fn elapsed(&self) -> Nanoseconds { 76 | Nanoseconds(self.ticks.load(Ordering::Relaxed)) 77 | } 78 | 79 | /// Forward the clock by `ticks` amount. 80 | pub fn tick(&mut self, ticks: T) 81 | where 82 | T: Into>, 83 | { 84 | self.ticks.fetch_add(ticks.into().0, Ordering::Relaxed); 85 | } 86 | 87 | /// Get a new timer based on the clock. 88 | pub fn get_timer(&self) -> MockTimer { 89 | let clock = self.clone(); 90 | let duration = Nanoseconds(1); 91 | let expiration = clock.try_now().unwrap(); 92 | MockTimer { 93 | clock: self.clone(), 94 | duration, 95 | expiration, 96 | started: false, 97 | } 98 | } 99 | } 100 | 101 | /// A simulated timer that can be used in tests. 102 | pub struct MockTimer { 103 | clock: MockClock, 104 | duration: Nanoseconds, 105 | expiration: Instant, 106 | started: bool, 107 | } 108 | 109 | impl CountDown for MockTimer { 110 | type Time = Nanoseconds; 111 | 112 | fn start(&mut self, count: T) 113 | where 114 | T: Into, 115 | { 116 | let now = self.clock.try_now().unwrap(); 117 | self.duration = count.into(); 118 | self.expiration = now + self.duration; 119 | self.started = true; 120 | } 121 | 122 | fn wait(&mut self) -> nb::Result<(), Void> { 123 | let now = self.clock.try_now().unwrap(); 124 | if self.started && now >= self.expiration { 125 | self.expiration = now + self.duration; 126 | Ok(()) 127 | } else { 128 | Err(nb::Error::WouldBlock) 129 | } 130 | } 131 | } 132 | 133 | impl Periodic for MockTimer {} 134 | 135 | impl Cancel for MockTimer { 136 | type Error = Infallible; 137 | 138 | fn cancel(&mut self) -> Result<(), Self::Error> { 139 | self.started = false; 140 | Ok(()) 141 | } 142 | } 143 | 144 | #[cfg(test)] 145 | mod test { 146 | use super::*; 147 | 148 | #[test] 149 | fn count_down() { 150 | let mut clock = MockClock::new(); 151 | let mut timer = clock.get_timer(); 152 | timer.start(100.nanoseconds()); 153 | clock.tick(50.nanoseconds()); 154 | assert_eq!(timer.wait(), Err(nb::Error::WouldBlock)); 155 | clock.tick(50.nanoseconds()); 156 | assert_eq!(timer.wait(), Ok(())); 157 | clock.tick(50.nanoseconds()); 158 | assert_eq!(timer.wait(), Err(nb::Error::WouldBlock)); 159 | clock.tick(50.nanoseconds()); 160 | assert_eq!(timer.wait(), Ok(())); 161 | } 162 | } 163 | -------------------------------------------------------------------------------- /src/eh0/adc.rs: -------------------------------------------------------------------------------- 1 | //! ADC mock implementation. 2 | //! 3 | //! ## Usage 4 | //! 5 | //! ``` 6 | //! # use eh0 as embedded_hal; 7 | //! use embedded_hal::adc::OneShot; 8 | //! use embedded_hal_mock::eh0::adc::{Mock, MockChan0, MockChan1, Transaction}; 9 | //! 10 | //! // Configure expectations: expected input channel numbers and values returned by read operations 11 | //! let expectations = [ 12 | //! Transaction::read(0, 0xab), 13 | //! Transaction::read(1, 0xabcd) 14 | //! ]; 15 | //! let mut adc = Mock::new(&expectations); 16 | //! 17 | //! // Reading 18 | //! assert_eq!(0xab, adc.read(&mut MockChan0 {}).unwrap()); 19 | //! assert_eq!(0xabcd, adc.read(&mut MockChan1 {}).unwrap()); 20 | //! 21 | //! // Finalise expectations 22 | //! adc.done(); 23 | //! ``` 24 | //! 25 | //! ## Testing Error Handling 26 | //! 27 | //! Attach an error to test error handling. An error is returned when such a transaction is executed. 28 | //! 29 | //! ``` 30 | //! # use eh0 as embedded_hal; 31 | //! use std::io::ErrorKind; 32 | //! 33 | //! use embedded_hal::adc::OneShot; 34 | //! use embedded_hal_mock::eh0::{ 35 | //! adc::{Mock, MockChan1, Transaction}, 36 | //! MockError, 37 | //! }; 38 | //! 39 | //! // Configure expectations 40 | //! let expectations = [ 41 | //! Transaction::read(1, 0xabba).with_error(MockError::Io(ErrorKind::InvalidData)) 42 | //! ]; 43 | //! let mut adc = Mock::new(&expectations); 44 | //! 45 | //! // Reading returns an error 46 | //! adc.read(&mut MockChan1 {}) 47 | //! .expect_err("expected error return"); 48 | //! 49 | //! // Finalise expectations 50 | //! adc.done(); 51 | //! ``` 52 | 53 | use std::fmt::Debug; 54 | 55 | use eh0 as embedded_hal; 56 | use embedded_hal::adc::{Channel, OneShot}; 57 | use nb; 58 | 59 | use super::error::MockError; 60 | use crate::common::Generic; 61 | 62 | /// ADC transaction type 63 | /// 64 | /// Models an ADC read 65 | #[derive(Clone, Debug, PartialEq, Eq)] 66 | pub struct Transaction { 67 | expected_chan: u8, 68 | response: T, 69 | /// An optional error return for a transaction. 70 | err: Option, 71 | } 72 | 73 | impl Transaction { 74 | /// Create a read transaction 75 | pub fn read(chan: u8, resp: T) -> Transaction { 76 | Transaction { 77 | expected_chan: chan, 78 | response: resp, 79 | err: None, 80 | } 81 | } 82 | 83 | /// Add an error return to a transaction. 84 | /// 85 | /// This is used to mock failure behaviour. 86 | pub fn with_error(mut self, error: MockError) -> Self { 87 | self.err = Some(error); 88 | self 89 | } 90 | } 91 | 92 | /// Mock ADC implementation 93 | pub struct MockAdc; 94 | 95 | macro_rules! mock_channel { 96 | ($ADC:ident, $($pin:ident => $chan:expr),+ $(,)*) => { 97 | $( 98 | /// Mock ADC channel implementation 99 | #[derive(Clone, Debug, PartialEq, Eq)] 100 | pub struct $pin; 101 | 102 | impl Channel<$ADC> for $pin { 103 | type ID = u8; 104 | 105 | fn channel() -> u8 { $chan } 106 | } 107 | )+ 108 | }; 109 | } 110 | 111 | mock_channel!(MockAdc, 112 | MockChan0 => 0_u8, 113 | MockChan1 => 1_u8, 114 | MockChan2 => 2_u8, 115 | ); 116 | 117 | /// Mock ADC implementation 118 | /// 119 | /// Mock ADC implements OneShot trait reading operation. Returned type can be either derived from 120 | /// definition of expectations or specified explicitly. Explicit ADC read return type can be used 121 | /// to mock specific ADC accuracy. 122 | pub type Mock = Generic>; 123 | 124 | impl OneShot for Mock 125 | where 126 | Pin: Channel, 127 | T: Clone + Debug + PartialEq, 128 | { 129 | type Error = MockError; 130 | 131 | fn read(&mut self, _pin: &mut Pin) -> nb::Result { 132 | let w = self.next().expect("unexpected read call"); 133 | assert_eq!(w.expected_chan, Pin::channel(), "unexpected channel"); 134 | match w.err { 135 | Some(e) => Err(nb::Error::Other(e)), 136 | None => Ok(w.response), 137 | } 138 | } 139 | } 140 | 141 | #[cfg(test)] 142 | mod test { 143 | use std::io::ErrorKind; 144 | 145 | use eh0 as embedded_hal; 146 | use embedded_hal::adc::OneShot; 147 | 148 | use super::{super::error::MockError, *}; 149 | 150 | #[test] 151 | fn test_adc_single_read16() { 152 | let expectations = [Transaction::read(0, 0xabcdu16)]; 153 | let mut adc = Mock::new(&expectations); 154 | 155 | assert_eq!(0xabcdu16, adc.read(&mut MockChan0 {}).unwrap()); 156 | 157 | adc.done(); 158 | } 159 | 160 | #[test] 161 | fn test_adc_single_read32() { 162 | let expectations = [Transaction::read(0, 0xabcdabcdu32)]; 163 | let mut adc = Mock::new(&expectations); 164 | 165 | assert_eq!(0xabcdabcdu32, adc.read(&mut MockChan0 {}).unwrap()); 166 | 167 | adc.done(); 168 | } 169 | 170 | #[test] 171 | fn test_adc_mult_read() { 172 | let expectations = [ 173 | Transaction::read(0, 0xabcd), 174 | Transaction::read(1, 0xabba), 175 | Transaction::read(2, 0xbaab), 176 | ]; 177 | let mut adc = Mock::new(&expectations); 178 | 179 | assert_eq!(0xabcd, adc.read(&mut MockChan0 {}).unwrap()); 180 | assert_eq!(0xabba, adc.read(&mut MockChan1 {}).unwrap()); 181 | assert_eq!(0xbaab, adc.read(&mut MockChan2 {}).unwrap()); 182 | 183 | adc.done(); 184 | } 185 | 186 | #[test] 187 | fn test_adc_err_read() { 188 | let expectations = [ 189 | Transaction::read(0, 0xabcd), 190 | Transaction::read(1, 0xabba).with_error(MockError::Io(ErrorKind::InvalidData)), 191 | ]; 192 | let mut adc = Mock::new(&expectations); 193 | 194 | assert_eq!(0xabcd, adc.read(&mut MockChan0 {}).unwrap()); 195 | adc.read(&mut MockChan1 {}) 196 | .expect_err("expected error return"); 197 | 198 | adc.done(); 199 | } 200 | } 201 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. 4 | 5 | The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) 6 | and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). 7 | 8 | 9 | ## Unreleased 10 | 11 | ### Added 12 | 13 | ### Fixed 14 | 15 | ### Changed 16 | 17 | - Drop fixed MSRV policy (#124) 18 | - **Breaking**: the `eh0` feature is no longer part of the default features. 19 | it still exists as an optional feature and has to be explicitly added when needed. 20 | 21 | 22 | ## 0.11.1 - 2024-06-02 23 | 24 | This release only includes a documentation fix (with regards to default-enabled 25 | Cargo features), but no code changes. 26 | 27 | 28 | ## 0.11.0 - 2024-05-30 29 | 30 | This release adds support for various async APIs as defined in 31 | `embedded-hal-async`. To use these features, you need to enable the 32 | `embedded-hal-async` Cargo feature. 33 | 34 | If you're upgrading from an earlier version, please note that the `pin` module 35 | was renamed to `digital` to match `embedded-hal`. 36 | 37 | ### Added 38 | 39 | - Add `ToggleableOutputPin` support for `eh0::digital::Mock` (#89) 40 | - Add `StatefulOutputPin` support for `eh1::digital::Mock` (#89) 41 | - Async support for `eh1::i2c::Mock` (#119) 42 | - Async support for `eh1::digital::Mock` (#91) 43 | - Async delay for `NoopDelay` / `StdSleep` (#104) 44 | - New `CheckedDelay` mock that supports both sync and async delays (#104) 45 | 46 | ### Changed 47 | 48 | - Rename `pin` module to `digital` to match embedded-hal (#113) 49 | - Improve top level usage docs (#117) 50 | 51 | 52 | ## 0.10.0 - 2024-01-10 53 | 54 | This release contains a big change: `embedded-hal-mock` now supports both 55 | `embedded-hal` 0.x and 1.x! The two variants are accessible through 56 | `embedded_hal_mock::eh0::*` and `embedded_hal_mock::eh1::*`. If there are any 57 | issues, leave feedback in the GitHub issue tracker. 58 | 59 | Additionally, tests now fail if you forgot to call `.done()`. This should 60 | reduce the chance of accidentally writing a broken test. 61 | 62 | This release contains commits by 12 different people, thanks a lot for the 63 | contributions! 64 | 65 | ### Migrating to 0.10.0 66 | 67 | - Update your imports: Change `use embedded_hal_mock::*` to 68 | `use embedded_hal_mock::eh0::*` 69 | - Rename all `.expect(...)` calls on mocks to `.update_expectations(...)` 70 | - Rename all `eh0::delay::MockNoop` usages to `eh0::delay::NoopDelay` 71 | - Run your tests to ensure that you don't have any missing `.done()` calls in 72 | your code 73 | - Look through the rest of the changes below and check if they might affect 74 | your code 75 | 76 | ### Added 77 | 78 | - Support for both `embedded-hal` 0.x and 1.x in the same crate (#75) 79 | - Print a warning to stderr and fail test if a mock is dropped without having 80 | calling `.done()` on it, or if `.done()` is called twice (#59, #61) 81 | - Implement mock for `eh1::pwm::SetDutyCycle` 82 | 83 | ### Fixed 84 | 85 | - `Generic` mock: Fix a bug that caused the call to `.done()` to fail if 86 | `.next()` was called on the mock after all expectations have already been 87 | consumed (#58) 88 | - Fix assertion error message for SPI `transfer` and ` transfer_in_place` (#90) 89 | 90 | ### Changed 91 | 92 | - Renamed `.expect(...)` method to `.update_expectations(...)` to avoid 93 | confusion with the expect method in `Option` and `Result` (#63) 94 | - When updating expectations on a mock by calling `.expect(...)` / 95 | `.update_expectations(...)` on it, assert that previous expectations have 96 | been consumed (#63) 97 | - Rename `delay::MockNoop` to `delay::NoopDelay`. 98 | - Changed the eh1 SPI implementation to be generic over word size 99 | - Updated `nb` dependency from 0.1 to 1.1 (#107) 100 | - Bump minimal supported Rust version (MSRV) to 1.63 (or 1.75 if you use 101 | embedded-hal 1.0) 102 | - The minimal supported Rust version (MSRV) is specified in the `Cargo.toml` to 103 | offer clearer error messages to consumers with outdated Rust versions 104 | 105 | 106 | ## 0.9.0 - 2023-01-07 107 | 108 | ### Added 109 | 110 | - Implement `WriteIter` and `WriteIterRead` for i2c mock (#44) 111 | - Implement `PwmPin` for pin mock (#52) 112 | - Add mock for timers using embedded-time with nanosecond precision (#40) 113 | 114 | ### Changed 115 | 116 | - Bump minimal supported Rust version (MSRV) to 1.60 117 | - Switch to Rust 2021 edition (#55) 118 | - Switch from CircleCI to GitHub Actions (#50) 119 | 120 | 121 | ## 0.8.0 - 2021-08-16 122 | 123 | ### Added 124 | 125 | - Add one-shot ADC mock (#38) 126 | 127 | 128 | ## 0.7.2 - 2020-06-02 129 | 130 | ### Added 131 | 132 | - Implement `std::Error` trait for `MockError` (#31) 133 | - serial: Implement error expectations (#32) 134 | 135 | 136 | ## 0.7.1 - 2020-01-03 137 | 138 | ### Added 139 | 140 | - i2c: Implement error expectations (#29) 141 | 142 | ### Fixed 143 | 144 | - Fix link to digital pin docs (#28) 145 | 146 | 147 | ## 0.7.0 - 2019-05-22 148 | 149 | ### Added 150 | 151 | - The serial transaction API now has two new constructor methods: `read_many` 152 | and `write_many`. 153 | 154 | ### Changed 155 | 156 | - The serial transaction API changed: The `Transaction::write` function now 157 | expects a single word, not a collection of words. To add a transaction for 158 | many writes, use `Transaction::write_many` instead. 159 | 160 | ### Fixed 161 | 162 | - Make the serial mock actually cloneable 163 | 164 | 165 | ## 0.6.0 - 2019-05-10 166 | 167 | ### Added 168 | 169 | - Add serial device mock (#21) 170 | - Add InputPin and OutputPin mocks (#18) 171 | 172 | ### Changed 173 | 174 | - `MockError::Io` now wraps an `io::ErrorKind` instance instead of `io::Error`. 175 | 176 | 177 | ## 0.5.0 - 2019-01-07 178 | 179 | ### Added 180 | 181 | - SPI: Add support for non-blocking `FullDuplex` mode (#14) 182 | 183 | ### Changed 184 | 185 | - Require Rust 1.31+ 186 | - Apply and enforce rustfmt 187 | 188 | 189 | ## 0.4.1 - 2018-12-26 190 | 191 | ### Added 192 | 193 | - Add `StdSleep` delay implementation based on `std::thread::sleep` (#8) 194 | - Add `new()` methods to `MockNoop` and `StdSleep` 195 | 196 | ### Fixed 197 | 198 | - Fix error messages for unfulfilled I²C expectations (#12) 199 | 200 | 201 | ## 0.4.0 - 2018-10-22 202 | 203 | ### Changed 204 | 205 | - I²C mock has a new transaction based API, matching the SPI mock (#4) 206 | 207 | 208 | ## 0.3.0 - 2018-10-12 209 | 210 | ### Added 211 | 212 | - SPI mock implementation (#2) 213 | - Set up CI (#3) 214 | 215 | ### Changed 216 | 217 | - Restructure crate: 218 | - `I2cMock` is now at `i2c::Mock` 219 | - `DelayMockNoop` is now at `delay::MockNoop` 220 | - Move all docs into crate docs (so it can be tested) 221 | 222 | 223 | ## 0.2.0 - 2018-06-18 224 | 225 | ### Changed 226 | 227 | - Upgrade to `embedded-hal` 0.2. 228 | 229 | 230 | ## 0.1.0 - 2018-03-31 231 | 232 | Initial release on crates.io. 233 | -------------------------------------------------------------------------------- /src/common.rs: -------------------------------------------------------------------------------- 1 | //! Common functionality used by the mock implementations. 2 | 3 | use std::{ 4 | collections::VecDeque, 5 | fmt::Debug, 6 | sync::{Arc, Mutex}, 7 | thread, 8 | }; 9 | 10 | /// Generic mock implementation. 11 | /// 12 | /// ⚠️ **Do not create this directly as end user! This is only a building block 13 | /// for creating mocks.** 14 | /// 15 | /// This type supports the specification and evaluation of expectations to 16 | /// allow automated testing of hal drivers. Mismatches between expectations 17 | /// will cause runtime assertions to assist in locating the source of the 18 | /// fault. 19 | /// 20 | /// Note that the implementation uses an `Arc>` internally, so a 21 | /// cloned instance of the mock can be used to check the expectations of the 22 | /// original instance that has been moved into a driver. 23 | #[derive(Debug, Clone)] 24 | pub struct Generic { 25 | expected: Arc>>, 26 | done_called: Arc>, 27 | } 28 | 29 | impl<'a, T: 'a> Generic 30 | where 31 | T: Clone + Debug + PartialEq, 32 | { 33 | /// Create a new mock interface 34 | /// 35 | /// This creates a new generic mock interface with initial expectations 36 | pub fn new(expected: E) -> Generic 37 | where 38 | E: IntoIterator, 39 | { 40 | let mut g = Generic { 41 | expected: Arc::new(Mutex::new(VecDeque::new())), 42 | done_called: Arc::new(Mutex::new(DoneCallDetector::new())), 43 | }; 44 | 45 | g.update_expectations(expected); 46 | 47 | g 48 | } 49 | 50 | /// Update expectations on the interface 51 | /// 52 | /// When this method is called, first it is ensured that existing 53 | /// expectations are all consumed by calling [`done()`](#method.done) 54 | /// internally (if not called already). Afterwards, the new expectations 55 | /// are set. 56 | pub fn update_expectations(&mut self, expected: E) 57 | where 58 | E: IntoIterator, 59 | { 60 | // Ensure that existing expectations are consumed 61 | self.done_impl(false); 62 | 63 | // Collect new expectations into vector 64 | let new_expectations: VecDeque = expected.into_iter().cloned().collect(); 65 | 66 | // Lock internal state 67 | let mut expected = self.expected.lock().unwrap(); 68 | let mut done_called = self.done_called.lock().unwrap(); 69 | 70 | // Update expectations 71 | *expected = new_expectations; 72 | 73 | // Reset done call detector 74 | done_called.reset(); 75 | } 76 | 77 | /// Deprecated alias of `update_expectations`. 78 | #[deprecated( 79 | since = "0.10.0", 80 | note = "The method 'expect' was renamed to 'update_expectations'" 81 | )] 82 | pub fn expect(&mut self, expected: E) 83 | where 84 | E: IntoIterator, 85 | { 86 | self.update_expectations(expected) 87 | } 88 | 89 | /// Assert that all expectations on a given mock have been consumed. 90 | pub fn done(&mut self) { 91 | self.done_impl(true); 92 | } 93 | 94 | fn done_impl(&mut self, panic_if_already_done: bool) { 95 | self.done_called 96 | .lock() 97 | .unwrap() 98 | .mark_as_called(panic_if_already_done); 99 | let e = self.expected.lock().unwrap(); 100 | assert!(e.is_empty(), "Not all expectations consumed"); 101 | } 102 | } 103 | 104 | /// Iterator impl for use in mock impls 105 | impl Iterator for Generic 106 | where 107 | T: Clone + Debug + PartialEq, 108 | { 109 | type Item = T; 110 | fn next(&mut self) -> Option { 111 | self.expected.lock().unwrap().pop_front() 112 | } 113 | } 114 | 115 | /// Struct used to detect whether or not the `.done()` method was called. 116 | #[derive(Debug)] 117 | pub(crate) struct DoneCallDetector { 118 | called: bool, 119 | } 120 | 121 | impl DoneCallDetector { 122 | pub(crate) fn new() -> Self { 123 | Self { called: false } 124 | } 125 | 126 | /// Mark the `.done()` method as called. 127 | /// 128 | /// Note: When calling this method twice, an assertion failure will be 129 | /// triggered if `panic_if_already_done` is true. 130 | pub(crate) fn mark_as_called(&mut self, panic_if_already_done: bool) { 131 | if panic_if_already_done { 132 | assert!(!self.called, "The `.done()` method was called twice!"); 133 | } 134 | self.called = true; 135 | } 136 | 137 | /// Reset the detector. 138 | pub(crate) fn reset(&mut self) { 139 | self.called = false; 140 | } 141 | } 142 | 143 | impl Drop for DoneCallDetector { 144 | fn drop(&mut self) { 145 | // Ensure that the `.done()` method was called on the mock before 146 | // dropping. 147 | if !self.called && !thread::panicking() { 148 | let msg = "WARNING: A mock (from embedded-hal-mock) was dropped \ 149 | without calling the `.done()` method. \ 150 | See https://github.com/dbrgn/embedded-hal-mock/issues/34 \ 151 | for more details."; 152 | 153 | // Note: We cannot use the print macros here, since they get 154 | // captured by the Cargo test runner. Instead, write to stderr 155 | // directly. 156 | use std::io::Write; 157 | let mut stderr = std::io::stderr(); 158 | stderr.write_all(b"\x1b[31m").ok(); 159 | stderr.write_all(msg.as_bytes()).ok(); 160 | stderr.write_all(b"\x1b[m\n").ok(); 161 | stderr.flush().ok(); 162 | 163 | // Panic! 164 | // 165 | // (Note: Inside a `Drop` implementation, panic should only be used 166 | // if not already panicking: 167 | // https://doc.rust-lang.org/std/ops/trait.Drop.html#panics 168 | // This is ensured by checking `!thread::panicking()`.) 169 | panic!("{}", msg); 170 | } 171 | } 172 | } 173 | 174 | #[cfg(test)] 175 | mod tests { 176 | use super::*; 177 | 178 | mod generic_mock { 179 | use super::*; 180 | 181 | #[test] 182 | fn success() { 183 | let expectations = [0u8, 1u8]; 184 | let mut mock: Generic = Generic::new(&expectations); 185 | 186 | assert_eq!(mock.next(), Some(0u8)); 187 | assert_eq!(mock.next(), Some(1u8)); 188 | assert_eq!(mock.next(), None); 189 | assert_eq!(mock.next(), None); 190 | 191 | mock.done(); 192 | } 193 | 194 | #[test] 195 | #[should_panic( 196 | expected = "WARNING: A mock (from embedded-hal-mock) was dropped without calling the `.done()` method. See https://github.com/dbrgn/embedded-hal-mock/issues/34 for more details." 197 | )] 198 | fn panic_if_drop_not_called() { 199 | let expectations = [0u8, 1u8]; 200 | let mut mock: Generic = Generic::new(&expectations); 201 | assert_eq!(mock.next(), Some(0u8)); 202 | assert_eq!(mock.next(), Some(1u8)); 203 | } 204 | 205 | #[test] 206 | #[should_panic(expected = "The `.done()` method was called twice!")] 207 | fn panic_if_drop_called_twice() { 208 | let expectations = [0u8, 1u8]; 209 | let mut mock: Generic = Generic::new(&expectations); 210 | assert_eq!(mock.next(), Some(0u8)); 211 | assert_eq!(mock.next(), Some(1u8)); 212 | mock.done(); 213 | mock.done(); 214 | } 215 | } 216 | } 217 | -------------------------------------------------------------------------------- /LICENSE-APACHE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | -------------------------------------------------------------------------------- /src/eh0/spi.rs: -------------------------------------------------------------------------------- 1 | //! SPI mock implementations. 2 | //! 3 | //! This mock supports the specification and checking of expectations to allow 4 | //! automated testing of SPI based drivers. Mismatches between expected and 5 | //! real SPI transactions will cause runtime assertions to assist with locating 6 | //! faults. 7 | //! 8 | //! ## Usage 9 | //! 10 | //! ``` 11 | //! # use eh0 as embedded_hal; 12 | //! use embedded_hal::{ 13 | //! blocking::spi::{Transfer, Write}, 14 | //! spi::FullDuplex, 15 | //! }; 16 | //! use embedded_hal_mock::eh0::spi::{Mock as SpiMock, Transaction as SpiTransaction}; 17 | //! 18 | //! // Configure expectations 19 | //! let expectations = [ 20 | //! SpiTransaction::send(0x09), 21 | //! SpiTransaction::read(0x0A), 22 | //! SpiTransaction::send(0xFE), 23 | //! SpiTransaction::read(0xFF), 24 | //! SpiTransaction::write(vec![1, 2]), 25 | //! SpiTransaction::transfer(vec![3, 4], vec![5, 6]), 26 | //! ]; 27 | //! 28 | //! let mut spi = SpiMock::new(&expectations); 29 | //! // FullDuplex transfers 30 | //! spi.send(0x09); 31 | //! assert_eq!(spi.read().unwrap(), 0x0A); 32 | //! spi.send(0xFE); 33 | //! assert_eq!(spi.read().unwrap(), 0xFF); 34 | //! 35 | //! // Writing 36 | //! spi.write(&vec![1, 2]).unwrap(); 37 | //! 38 | //! // Transferring 39 | //! let mut buf = vec![3, 4]; 40 | //! spi.transfer(&mut buf).unwrap(); 41 | //! assert_eq!(buf, vec![5, 6]); 42 | //! 43 | //! // Finalise expectations 44 | //! spi.done(); 45 | //! ``` 46 | use eh0 as embedded_hal; 47 | use embedded_hal::{blocking::spi, spi::FullDuplex}; 48 | 49 | use super::error::MockError; 50 | use crate::common::Generic; 51 | 52 | /// SPI Transaction mode 53 | #[derive(Clone, Debug, PartialEq, Eq)] 54 | pub enum Mode { 55 | /// Write transaction 56 | Write, 57 | /// Write and read transaction 58 | Transfer, 59 | /// Send transaction 60 | Send, 61 | /// After a send transaction in real HW a Read is available 62 | Read, 63 | } 64 | 65 | /// SPI transaction type 66 | /// 67 | /// Models an SPI write or transfer (with response) 68 | #[derive(Clone, Debug, PartialEq, Eq)] 69 | pub struct Transaction { 70 | expected_mode: Mode, 71 | expected_data: Vec, 72 | response: Vec, 73 | } 74 | 75 | impl Transaction { 76 | /// Create a write transaction 77 | pub fn write(expected: Vec) -> Transaction { 78 | Transaction { 79 | expected_mode: Mode::Write, 80 | expected_data: expected, 81 | response: Vec::new(), 82 | } 83 | } 84 | 85 | /// Create a transfer transaction 86 | pub fn transfer(expected: Vec, response: Vec) -> Transaction { 87 | Transaction { 88 | expected_mode: Mode::Transfer, 89 | expected_data: expected, 90 | response, 91 | } 92 | } 93 | 94 | /// Create a transfer transaction 95 | pub fn send(expected: u8) -> Transaction { 96 | Transaction { 97 | expected_mode: Mode::Send, 98 | expected_data: [expected].to_vec(), 99 | response: Vec::new(), 100 | } 101 | } 102 | 103 | /// Create a transfer transaction 104 | pub fn read(response: u8) -> Transaction { 105 | Transaction { 106 | expected_mode: Mode::Read, 107 | expected_data: Vec::new(), 108 | response: [response].to_vec(), 109 | } 110 | } 111 | } 112 | 113 | /// Mock SPI implementation 114 | /// 115 | /// This supports the specification and checking of expectations to allow 116 | /// automated testing of SPI based drivers. Mismatches between expected and 117 | /// real SPI transactions will cause runtime assertions to assist with locating 118 | /// faults. 119 | /// 120 | /// See the usage section in the module level docs for an example. 121 | pub type Mock = Generic; 122 | 123 | impl spi::Write for Mock { 124 | type Error = MockError; 125 | 126 | /// spi::Write implementation for Mock 127 | /// 128 | /// This will cause an assertion if the write call does not match the next expectation 129 | fn write(&mut self, buffer: &[u8]) -> Result<(), Self::Error> { 130 | let w = self.next().expect("no expectation for spi::write call"); 131 | assert_eq!(w.expected_mode, Mode::Write, "spi::write unexpected mode"); 132 | assert_eq!( 133 | &w.expected_data, &buffer, 134 | "spi::write data does not match expectation" 135 | ); 136 | Ok(()) 137 | } 138 | } 139 | 140 | impl FullDuplex for Mock { 141 | type Error = MockError; 142 | /// spi::FullDuplex implementeation for Mock 143 | /// 144 | /// This will call the nonblocking read/write primitives. 145 | fn send(&mut self, buffer: u8) -> nb::Result<(), Self::Error> { 146 | let data = self.next().expect("no expectation for spi::send call"); 147 | assert_eq!(data.expected_mode, Mode::Send, "spi::send unexpected mode"); 148 | assert_eq!( 149 | data.expected_data[0], buffer, 150 | "spi::send data does not match expectation" 151 | ); 152 | Ok(()) 153 | } 154 | 155 | /// spi::FullDuplex implementeation for Mock 156 | /// 157 | /// This will call the nonblocking read/write primitives. 158 | fn read(&mut self) -> nb::Result { 159 | let w = self.next().expect("no expectation for spi::read call"); 160 | assert_eq!(w.expected_mode, Mode::Read, "spi::Read unexpected mode"); 161 | assert_eq!( 162 | 1, 163 | w.response.len(), 164 | "mismatched response length for spi::read" 165 | ); 166 | let buffer: u8 = w.response[0]; 167 | Ok(buffer) 168 | } 169 | } 170 | 171 | impl spi::Transfer for Mock { 172 | type Error = MockError; 173 | 174 | /// spi::Transfer implementation for Mock 175 | /// 176 | /// This writes the provided response to the buffer and will cause an assertion if the written data does not match the next expectation 177 | fn transfer<'w>(&mut self, buffer: &'w mut [u8]) -> Result<&'w [u8], Self::Error> { 178 | let w = self.next().expect("no expectation for spi::transfer call"); 179 | assert_eq!( 180 | w.expected_mode, 181 | Mode::Transfer, 182 | "spi::transfer unexpected mode" 183 | ); 184 | assert_eq!( 185 | &w.expected_data, &buffer, 186 | "spi::transfer write data does not match expectation" 187 | ); 188 | assert_eq!( 189 | buffer.len(), 190 | w.response.len(), 191 | "mismatched response length for spi::transfer" 192 | ); 193 | buffer.copy_from_slice(&w.response); 194 | Ok(buffer) 195 | } 196 | } 197 | 198 | impl spi::WriteIter for Mock { 199 | type Error = MockError; 200 | 201 | fn write_iter(&mut self, words: WI) -> Result<(), Self::Error> 202 | where 203 | WI: IntoIterator, 204 | { 205 | let w = self 206 | .next() 207 | .expect("no expectation for spi::write_iter call"); 208 | let buffer = words.into_iter().collect::>(); 209 | assert_eq!( 210 | w.expected_mode, 211 | Mode::Write, 212 | "spi::write_iter unexpected mode" 213 | ); 214 | assert_eq!( 215 | &w.expected_data, &buffer, 216 | "spi::write_iter data does not match expectation" 217 | ); 218 | Ok(()) 219 | } 220 | } 221 | 222 | #[cfg(test)] 223 | mod test { 224 | use eh0 as embedded_hal; 225 | use embedded_hal::blocking::spi::{Transfer, Write, WriteIter}; 226 | 227 | use super::*; 228 | 229 | #[test] 230 | fn test_spi_mock_send() { 231 | let mut spi = Mock::new(&[Transaction::send(10)]); 232 | 233 | let _ = spi.send(10).unwrap(); 234 | 235 | spi.done(); 236 | } 237 | 238 | #[test] 239 | fn test_spi_mock_read() { 240 | let mut spi = Mock::new(&[Transaction::read(10)]); 241 | 242 | let ans = spi.read().unwrap(); 243 | 244 | assert_eq!(ans, 10); 245 | 246 | spi.done(); 247 | } 248 | 249 | #[test] 250 | fn test_spi_mock_multiple1() { 251 | let expectations = [ 252 | Transaction::write(vec![1, 2]), 253 | Transaction::send(9), 254 | Transaction::read(10), 255 | Transaction::send(0xFE), 256 | Transaction::read(0xFF), 257 | Transaction::transfer(vec![3, 4], vec![5, 6]), 258 | ]; 259 | let mut spi = Mock::new(&expectations); 260 | 261 | spi.write(&vec![1, 2]).unwrap(); 262 | 263 | let _ = spi.send(0x09); 264 | assert_eq!(spi.read().unwrap(), 0x0a); 265 | let _ = spi.send(0xfe); 266 | assert_eq!(spi.read().unwrap(), 0xFF); 267 | let mut v = vec![3, 4]; 268 | spi.transfer(&mut v).unwrap(); 269 | 270 | assert_eq!(v, vec![5, 6]); 271 | 272 | spi.done(); 273 | } 274 | 275 | #[test] 276 | fn test_spi_mock_write() { 277 | let expectations = [Transaction::write(vec![10, 12])]; 278 | let mut spi = Mock::new(&expectations); 279 | 280 | spi.write(&vec![10, 12]).unwrap(); 281 | 282 | spi.done(); 283 | } 284 | 285 | #[test] 286 | fn test_spi_mock_write_iter() { 287 | let expectations = [Transaction::write(vec![10, 12])]; 288 | let mut spi = Mock::new(&expectations); 289 | 290 | spi.write_iter(vec![10, 12u8]).unwrap(); 291 | 292 | spi.done(); 293 | } 294 | 295 | #[test] 296 | fn test_spi_mock_transfer() { 297 | let expectations = [Transaction::transfer(vec![10, 12], vec![12, 13])]; 298 | let mut spi = Mock::new(&expectations); 299 | 300 | let mut v = vec![10, 12]; 301 | spi.transfer(&mut v).unwrap(); 302 | 303 | assert_eq!(v, vec![12, 13]); 304 | 305 | spi.done(); 306 | } 307 | 308 | #[test] 309 | fn test_spi_mock_multiple() { 310 | let expectations = [ 311 | Transaction::write(vec![1, 2]), 312 | Transaction::transfer(vec![3, 4], vec![5, 6]), 313 | ]; 314 | let mut spi = Mock::new(&expectations); 315 | 316 | spi.write(&vec![1, 2]).unwrap(); 317 | 318 | let mut v = vec![3, 4]; 319 | spi.transfer(&mut v).unwrap(); 320 | 321 | assert_eq!(v, vec![5, 6]); 322 | 323 | spi.done(); 324 | } 325 | 326 | #[test] 327 | #[should_panic(expected = "spi::write data does not match expectation")] 328 | fn test_spi_mock_write_err() { 329 | let expectations = [Transaction::write(vec![10, 12])]; 330 | let mut spi = Mock::new(&expectations); 331 | spi.write(&vec![10, 12, 12]).unwrap(); 332 | } 333 | 334 | #[test] 335 | #[should_panic(expected = "spi::write_iter data does not match expectation")] 336 | fn test_spi_mock_write_iter_err() { 337 | let expectations = [Transaction::write(vec![10, 12])]; 338 | let mut spi = Mock::new(&expectations); 339 | spi.write_iter(vec![10, 12, 12u8]).unwrap(); 340 | } 341 | 342 | #[test] 343 | #[should_panic(expected = "spi::transfer write data does not match expectation")] 344 | fn test_spi_mock_transfer_err() { 345 | let expectations = [Transaction::transfer(vec![10, 12], vec![12, 15])]; 346 | let mut spi = Mock::new(&expectations); 347 | spi.transfer(&mut vec![10, 13]).unwrap(); 348 | } 349 | 350 | #[test] 351 | #[should_panic(expected = "spi::write data does not match expectation")] 352 | fn test_spi_mock_multiple_transaction_err() { 353 | let expectations = [ 354 | Transaction::write(vec![10, 12]), 355 | Transaction::write(vec![10, 12]), 356 | ]; 357 | let mut spi = Mock::new(&expectations); 358 | spi.write(&vec![10, 12, 10]).unwrap(); 359 | } 360 | 361 | #[test] 362 | #[should_panic(expected = "spi::write unexpected mode")] 363 | fn test_spi_mock_mode_err() { 364 | let expectations = [Transaction::transfer(vec![10, 12], vec![])]; 365 | let mut spi = Mock::new(&expectations); 366 | // Write instead of transfer 367 | spi.write(&vec![10, 12, 12]).unwrap(); 368 | } 369 | } 370 | -------------------------------------------------------------------------------- /src/eh0/digital.rs: -------------------------------------------------------------------------------- 1 | //! Mock digital [`InputPin`], [`OutputPin`], and [`ToggleableOutputPin`] v2 implementations 2 | //! 3 | //! [`InputPin`]: https://docs.rs/embedded-hal/0.2/embedded_hal/digital/v2/trait.InputPin.html 4 | //! [`OutputPin`]: https://docs.rs/embedded-hal/0.2/embedded_hal/digital/v2/trait.OutputPin.html 5 | //! [`ToggleableOutputPin`]: https://docs.rs/embedded-hal/0.2/embedded_hal/digital/v2/trait.ToggleableOutputPin.html 6 | //! 7 | //! ``` 8 | //! # use eh0 as embedded_hal; 9 | //! use std::io::ErrorKind; 10 | //! 11 | //! use embedded_hal::digital::v2::{InputPin, OutputPin, ToggleableOutputPin}; 12 | //! use embedded_hal_mock::eh0::{ 13 | //! digital::{Mock as PinMock, State as PinState, Transaction as PinTransaction}, 14 | //! MockError, 15 | //! }; 16 | //! 17 | //! let err = MockError::Io(ErrorKind::NotConnected); 18 | //! 19 | //! // Configure expectations 20 | //! let expectations = [ 21 | //! PinTransaction::get(PinState::High), 22 | //! PinTransaction::get(PinState::High), 23 | //! PinTransaction::set(PinState::Low), 24 | //! PinTransaction::set(PinState::High).with_error(err.clone()), 25 | //! PinTransaction::toggle(), 26 | //! ]; 27 | //! 28 | //! // Create pin 29 | //! let mut pin = PinMock::new(&expectations); 30 | //! 31 | //! // Run and test 32 | //! assert_eq!(pin.is_high().unwrap(), true); 33 | //! assert_eq!(pin.is_low().unwrap(), false); 34 | //! 35 | //! pin.set_low().unwrap(); 36 | //! pin.set_high().expect_err("expected error return"); 37 | //! 38 | //! pin.toggle().unwrap(); 39 | //! 40 | //! pin.done(); 41 | //! 42 | //! // Update expectations 43 | //! pin.update_expectations(&[]); 44 | //! // ... 45 | //! pin.done(); 46 | //! ``` 47 | 48 | use eh0 as embedded_hal; 49 | use embedded_hal::{ 50 | digital::v2::{InputPin, OutputPin, ToggleableOutputPin}, 51 | PwmPin, 52 | }; 53 | 54 | use super::error::MockError; 55 | use crate::common::Generic; 56 | 57 | /// The type used for the duty of the [`PwmPin`] mock. 58 | pub type PwmDuty = u16; 59 | 60 | /// MockPin transaction 61 | #[derive(PartialEq, Eq, Clone, Debug)] 62 | pub struct Transaction { 63 | /// Kind is the transaction kind (and data) expected 64 | kind: TransactionKind, 65 | /// An optional error return value for a transaction. This is in addition 66 | /// to `kind` to allow validation that the transaction kind is correct 67 | /// prior to returning the error. 68 | err: Option, 69 | } 70 | 71 | #[derive(PartialEq, Eq, Copy, Clone, Debug)] 72 | /// Digital pin value enumeration 73 | pub enum State { 74 | /// Digital low state 75 | Low, 76 | /// Digital high state 77 | High, 78 | } 79 | 80 | impl Transaction { 81 | /// Create a new pin transaction 82 | pub fn new(kind: TransactionKind) -> Transaction { 83 | Transaction { kind, err: None } 84 | } 85 | 86 | /// Create a new get transaction 87 | pub fn get(state: State) -> Transaction { 88 | Transaction::new(TransactionKind::Get(state)) 89 | } 90 | 91 | /// Create a new toggle transaction 92 | pub fn toggle() -> Transaction { 93 | Transaction::new(TransactionKind::Toggle) 94 | } 95 | 96 | /// Create a new get transaction 97 | pub fn set(state: State) -> Transaction { 98 | Transaction::new(TransactionKind::Set(state)) 99 | } 100 | 101 | /// Create a new disable transaction 102 | pub fn disable() -> Transaction { 103 | Transaction::new(TransactionKind::Disable) 104 | } 105 | 106 | /// Create a new enable transaction 107 | pub fn enable() -> Transaction { 108 | Transaction::new(TransactionKind::Enable) 109 | } 110 | 111 | /// Create a new get_duty transaction 112 | pub fn get_duty(duty: PwmDuty) -> Transaction { 113 | Transaction::new(TransactionKind::GetDuty(duty)) 114 | } 115 | 116 | /// Create a new get_max_duty transaction 117 | pub fn get_max_duty(max_duty: PwmDuty) -> Transaction { 118 | Transaction::new(TransactionKind::GetMaxDuty(max_duty)) 119 | } 120 | 121 | /// Create a new set_duty transaction 122 | pub fn set_duty(expected_duty: PwmDuty) -> Transaction { 123 | Transaction::new(TransactionKind::SetDuty(expected_duty)) 124 | } 125 | 126 | /// Add an error return to a transaction 127 | /// 128 | /// This is used to mock failure behaviours. 129 | /// 130 | /// Note that this can only be used for methods which actually return a [`Result`]; 131 | /// trying to invoke this for others will lead to an assertion error! 132 | pub fn with_error(mut self, error: MockError) -> Self { 133 | assert!( 134 | self.kind.supports_errors(), 135 | "the transaction kind supports errors" 136 | ); 137 | self.err = Some(error); 138 | self 139 | } 140 | } 141 | 142 | /// MockPin transaction kind. 143 | #[derive(PartialEq, Eq, Clone, Debug)] 144 | pub enum TransactionKind { 145 | /// Set the pin state 146 | Set(State), 147 | /// Get the pin state 148 | Get(State), 149 | /// Toggle the pin state 150 | Toggle, 151 | /// Disable a [`PwmPin`] using [`PwmPin::disable`] 152 | Disable, 153 | /// Enable a [`PwmPin`] using [`PwmPin::enable`] 154 | Enable, 155 | /// Query the duty of a [`PwmPin`] using [`PwmPin::get_duty`], returning the specified value 156 | GetDuty(PwmDuty), 157 | /// Query the max. duty of a [`PwmPin`] using [`PwmPin::get_max_duty`], returning the specified value 158 | GetMaxDuty(PwmDuty), 159 | /// Set the duty of a [`PwmPin`] using [`PwmPin::set_duty`], expecting the specified value 160 | SetDuty(PwmDuty), 161 | } 162 | 163 | impl TransactionKind { 164 | fn is_get(&self) -> bool { 165 | match self { 166 | TransactionKind::Get(_) => true, 167 | _ => false, 168 | } 169 | } 170 | 171 | /// Specifies whether the actual API returns a [`Result`] (= supports errors) or not. 172 | fn supports_errors(&self) -> bool { 173 | match self { 174 | TransactionKind::Set(_) | TransactionKind::Get(_) | TransactionKind::Toggle => true, 175 | _ => false, 176 | } 177 | } 178 | } 179 | 180 | /// Mock Pin implementation 181 | pub type Mock = Generic; 182 | 183 | /// Single digital push-pull output pin 184 | impl OutputPin for Mock { 185 | /// Error type 186 | type Error = MockError; 187 | 188 | /// Drives the pin low 189 | fn set_low(&mut self) -> Result<(), Self::Error> { 190 | let Transaction { kind, err } = self.next().expect("no expectation for pin::set_low call"); 191 | 192 | assert_eq!( 193 | kind, 194 | TransactionKind::Set(State::Low), 195 | "expected pin::set_low" 196 | ); 197 | 198 | match err { 199 | Some(e) => Err(e), 200 | None => Ok(()), 201 | } 202 | } 203 | 204 | /// Drives the pin high 205 | fn set_high(&mut self) -> Result<(), Self::Error> { 206 | let Transaction { kind, err } = self.next().expect("no expectation for pin::set_high call"); 207 | 208 | assert_eq!( 209 | kind, 210 | TransactionKind::Set(State::High), 211 | "expected pin::set_high" 212 | ); 213 | 214 | match err { 215 | Some(e) => Err(e), 216 | None => Ok(()), 217 | } 218 | } 219 | } 220 | 221 | impl InputPin for Mock { 222 | /// Error type 223 | type Error = MockError; 224 | 225 | /// Is the input pin high? 226 | fn is_high(&self) -> Result { 227 | let mut s = self.clone(); 228 | 229 | let Transaction { kind, err } = s.next().expect("no expectation for pin::is_high call"); 230 | 231 | assert!(kind.is_get(), "expected pin::get"); 232 | 233 | if let Some(e) = err { 234 | Err(e) 235 | } else if let TransactionKind::Get(v) = kind { 236 | Ok(v == State::High) 237 | } else { 238 | unreachable!(); 239 | } 240 | } 241 | 242 | /// Is the input pin low? 243 | fn is_low(&self) -> Result { 244 | let mut s = self.clone(); 245 | 246 | let Transaction { kind, err } = s.next().expect("no expectation for pin::is_low call"); 247 | 248 | assert!(kind.is_get(), "expected pin::get"); 249 | 250 | if let Some(e) = err { 251 | Err(e) 252 | } else if let TransactionKind::Get(v) = kind { 253 | Ok(v == State::Low) 254 | } else { 255 | unreachable!(); 256 | } 257 | } 258 | } 259 | 260 | /// Single digital output pin that can be toggled between high and low states 261 | impl ToggleableOutputPin for Mock { 262 | /// Error type 263 | type Error = MockError; 264 | 265 | /// Toggle the pin low to high or high to low 266 | fn toggle(&mut self) -> Result<(), Self::Error> { 267 | let Transaction { kind, err } = self.next().expect("no expectation for pin::toggle call"); 268 | 269 | assert_eq!(kind, TransactionKind::Toggle, "expected pin::toggle"); 270 | 271 | match err { 272 | Some(e) => Err(e), 273 | None => Ok(()), 274 | } 275 | } 276 | } 277 | 278 | impl PwmPin for Mock { 279 | type Duty = PwmDuty; 280 | 281 | fn disable(&mut self) { 282 | // Note: Error is being ignored, because method doesn't return a result 283 | let Transaction { kind, .. } = self.next().expect("no expectation for pin::disable call"); 284 | 285 | assert_eq!(kind, TransactionKind::Disable, "expected pin::disable"); 286 | } 287 | 288 | fn enable(&mut self) { 289 | // Note: Error is being ignored, because method doesn't return a result 290 | let Transaction { kind, .. } = self.next().expect("no expectation for pin::enable call"); 291 | 292 | assert_eq!(kind, TransactionKind::Enable, "expected pin::enable"); 293 | } 294 | 295 | fn get_duty(&self) -> Self::Duty { 296 | let mut s = self.clone(); 297 | 298 | // Note: Error is being ignored, because method doesn't return a result 299 | let Transaction { kind, .. } = s.next().expect("no expectation for pin::get_duty call"); 300 | 301 | if let TransactionKind::GetDuty(duty) = kind { 302 | duty 303 | } else { 304 | panic!("expected pin::get_duty"); 305 | } 306 | } 307 | 308 | fn get_max_duty(&self) -> Self::Duty { 309 | let mut s = self.clone(); 310 | 311 | // Note: Error is being ignored, because method doesn't return a result 312 | let Transaction { kind, .. } = s.next().expect("no expectation for pin::get_max_duty call"); 313 | 314 | if let TransactionKind::GetMaxDuty(max_duty) = kind { 315 | max_duty 316 | } else { 317 | panic!("expected pin::get_max_duty"); 318 | } 319 | } 320 | 321 | fn set_duty(&mut self, duty: Self::Duty) { 322 | // Note: Error is being ignored, because method doesn't return a result 323 | let Transaction { kind, .. } = self.next().expect("no expectation for pin::set_duty call"); 324 | 325 | assert_eq!( 326 | kind, 327 | TransactionKind::SetDuty(duty), 328 | "expected pin::set_duty" 329 | ); 330 | } 331 | } 332 | 333 | #[cfg(test)] 334 | mod test { 335 | use std::io::ErrorKind; 336 | 337 | use eh0 as embedded_hal; 338 | use embedded_hal::{ 339 | digital::v2::{InputPin, OutputPin}, 340 | PwmPin, 341 | }; 342 | 343 | use super::{super::error::MockError, TransactionKind::*, *}; 344 | 345 | #[test] 346 | fn test_input_pin() { 347 | let expectations = [ 348 | Transaction::new(Get(State::High)), 349 | Transaction::new(Get(State::High)), 350 | Transaction::new(Get(State::Low)), 351 | Transaction::new(Get(State::Low)), 352 | Transaction::new(Get(State::High)).with_error(MockError::Io(ErrorKind::NotConnected)), 353 | ]; 354 | let mut pin = Mock::new(&expectations); 355 | 356 | assert_eq!(pin.is_high().unwrap(), true); 357 | assert_eq!(pin.is_low().unwrap(), false); 358 | assert_eq!(pin.is_high().unwrap(), false); 359 | assert_eq!(pin.is_low().unwrap(), true); 360 | 361 | pin.is_low().expect_err("expected error return"); 362 | 363 | pin.done(); 364 | } 365 | 366 | #[test] 367 | fn test_output_pin() { 368 | let expectations = [ 369 | Transaction::new(Set(State::High)), 370 | Transaction::new(Set(State::Low)), 371 | Transaction::new(Set(State::High)).with_error(MockError::Io(ErrorKind::NotConnected)), 372 | ]; 373 | let mut pin = Mock::new(&expectations); 374 | 375 | pin.set_high().unwrap(); 376 | pin.set_low().unwrap(); 377 | 378 | pin.set_high().expect_err("expected error return"); 379 | 380 | pin.done(); 381 | } 382 | 383 | #[test] 384 | fn test_toggleable_output_pin() { 385 | let expectations = [ 386 | Transaction::new(Toggle), 387 | Transaction::toggle(), 388 | Transaction::new(Toggle).with_error(MockError::Io(ErrorKind::NotConnected)), 389 | ]; 390 | let mut pin = Mock::new(&expectations); 391 | 392 | pin.toggle().unwrap(); 393 | pin.toggle().unwrap(); 394 | 395 | pin.toggle().expect_err("expected error return"); 396 | 397 | pin.done(); 398 | } 399 | 400 | #[test] 401 | fn test_pwm_pin() { 402 | let expected_duty = 10_000; 403 | let expectations = [ 404 | Transaction::new(Enable), 405 | Transaction::new(GetMaxDuty(expected_duty)), 406 | Transaction::new(SetDuty(expected_duty)), 407 | Transaction::new(GetDuty(expected_duty)), 408 | Transaction::new(Disable), 409 | ]; 410 | let mut pin = Mock::new(&expectations); 411 | 412 | pin.enable(); 413 | let max_duty = pin.get_max_duty(); 414 | pin.set_duty(max_duty); 415 | assert_eq!(pin.get_duty(), expected_duty); 416 | pin.disable(); 417 | 418 | pin.done(); 419 | } 420 | } 421 | -------------------------------------------------------------------------------- /src/eh0/i2c.rs: -------------------------------------------------------------------------------- 1 | //! I²C mock implementations. 2 | //! 3 | //! ## Usage 4 | //! 5 | //! ``` 6 | //! # use eh0 as embedded_hal; 7 | //! use embedded_hal::{ 8 | //! blocking::i2c::{Read, Write, WriteRead}, 9 | //! prelude::*, 10 | //! }; 11 | //! use embedded_hal_mock::eh0::i2c::{Mock as I2cMock, Transaction as I2cTransaction}; 12 | //! 13 | //! // Configure expectations 14 | //! let expectations = [ 15 | //! I2cTransaction::write(0xaa, vec![1, 2]), 16 | //! I2cTransaction::read(0xbb, vec![3, 4]), 17 | //! ]; 18 | //! let mut i2c = I2cMock::new(&expectations); 19 | //! 20 | //! // Writing 21 | //! i2c.write(0xaa, &vec![1, 2]).unwrap(); 22 | //! 23 | //! // Reading 24 | //! let mut buf = vec![0; 2]; 25 | //! i2c.read(0xbb, &mut buf).unwrap(); 26 | //! assert_eq!(buf, vec![3, 4]); 27 | //! 28 | //! // Finalise expectations 29 | //! i2c.done(); 30 | //! ``` 31 | //! 32 | //! ## Transactions 33 | //! 34 | //! There are currently three transaction types: 35 | //! 36 | //! - `Read`: This expects an I²C `read` command and will return the wrapped bytes. 37 | //! - `Write`: This expects an I²C `write` command with the wrapped bytes. 38 | //! - `WriteRead`: This expects an I²C `write_read` command where the 39 | //! `expected` bytes are written and the `response` bytes are returned. 40 | //! 41 | //! ## Testing Error Handling 42 | //! 43 | //! If you want to test error handling of your code, you can attach an error to 44 | //! a transaction. When the transaction is executed, an error is returned. 45 | //! 46 | //! ``` 47 | //! # use eh0 as embedded_hal; 48 | //! # use embedded_hal::prelude::*; 49 | //! # use embedded_hal::blocking::i2c::{Read, Write, WriteRead}; 50 | //! # use embedded_hal_mock::eh0::i2c::{Mock as I2cMock, Transaction as I2cTransaction}; 51 | //! use std::io::ErrorKind; 52 | //! 53 | //! use embedded_hal_mock::eh0::MockError; 54 | //! 55 | //! // Configure expectations 56 | //! let expectations = [ 57 | //! I2cTransaction::write(0xaa, vec![1, 2]), 58 | //! I2cTransaction::read(0xbb, vec![3, 4]).with_error(MockError::Io(ErrorKind::Other)), 59 | //! ]; 60 | //! let mut i2c = I2cMock::new(&expectations); 61 | //! 62 | //! // Writing returns without an error 63 | //! i2c.write(0xaa, &vec![1, 2]).unwrap(); 64 | //! 65 | //! // Reading returns an error 66 | //! let mut buf = vec![0; 2]; 67 | //! let err = i2c.read(0xbb, &mut buf).unwrap_err(); 68 | //! assert_eq!(err, MockError::Io(ErrorKind::Other)); 69 | //! 70 | //! // Finalise expectations 71 | //! i2c.done(); 72 | //! ``` 73 | 74 | use eh0 as embedded_hal; 75 | use embedded_hal::blocking::i2c; 76 | 77 | use super::error::MockError; 78 | use crate::common::Generic; 79 | 80 | /// I2C Transaction modes 81 | #[derive(Clone, Debug, PartialEq, Eq)] 82 | pub enum Mode { 83 | /// Write transaction 84 | Write, 85 | /// Read transaction 86 | Read, 87 | /// Write and read transaction 88 | WriteRead, 89 | } 90 | 91 | /// I2C Transaction type 92 | /// 93 | /// Models an I2C read or write 94 | #[derive(Clone, Debug, PartialEq, Eq)] 95 | pub struct Transaction { 96 | expected_mode: Mode, 97 | expected_addr: u8, 98 | expected_data: Vec, 99 | response_data: Vec, 100 | /// An optional error return for a transaction. 101 | /// 102 | /// This is in addition to the mode to allow validation that the 103 | /// transaction mode is correct prior to returning the error. 104 | expected_err: Option, 105 | } 106 | 107 | impl Transaction { 108 | /// Create a Write transaction 109 | pub fn write(addr: u8, expected: Vec) -> Transaction { 110 | Transaction { 111 | expected_mode: Mode::Write, 112 | expected_addr: addr, 113 | expected_data: expected, 114 | response_data: Vec::new(), 115 | expected_err: None, 116 | } 117 | } 118 | 119 | /// Create a Read transaction 120 | pub fn read(addr: u8, response: Vec) -> Transaction { 121 | Transaction { 122 | expected_mode: Mode::Read, 123 | expected_addr: addr, 124 | expected_data: Vec::new(), 125 | response_data: response, 126 | expected_err: None, 127 | } 128 | } 129 | 130 | /// Create a WriteRead transaction 131 | pub fn write_read(addr: u8, expected: Vec, response: Vec) -> Transaction { 132 | Transaction { 133 | expected_mode: Mode::WriteRead, 134 | expected_addr: addr, 135 | expected_data: expected, 136 | response_data: response, 137 | expected_err: None, 138 | } 139 | } 140 | 141 | /// Add an error return to a transaction 142 | /// 143 | /// This is used to mock failure behaviours. 144 | /// 145 | /// Note: When attaching this to a read transaction, the response in the 146 | /// expectation will not actually be written to the buffer. 147 | pub fn with_error(mut self, error: MockError) -> Self { 148 | self.expected_err = Some(error); 149 | self 150 | } 151 | } 152 | 153 | /// Mock I2C implementation 154 | /// 155 | /// This supports the specification and evaluation of expectations to allow automated testing of I2C based drivers. 156 | /// Mismatches between expectations will cause runtime assertions to assist in locating the source of the fault. 157 | pub type Mock = Generic; 158 | 159 | impl i2c::Read for Mock { 160 | type Error = MockError; 161 | 162 | fn read(&mut self, address: u8, buffer: &mut [u8]) -> Result<(), Self::Error> { 163 | let e = self 164 | .next() 165 | .expect("no pending expectation for i2c::read call"); 166 | 167 | assert_eq!(e.expected_mode, Mode::Read, "i2c::read unexpected mode"); 168 | assert_eq!(e.expected_addr, address, "i2c::read address mismatch"); 169 | 170 | assert_eq!( 171 | buffer.len(), 172 | e.response_data.len(), 173 | "i2c:read mismatched response length" 174 | ); 175 | 176 | match e.expected_err { 177 | Some(err) => Err(err), 178 | None => { 179 | buffer.copy_from_slice(&e.response_data); 180 | Ok(()) 181 | } 182 | } 183 | } 184 | } 185 | 186 | impl i2c::Write for Mock { 187 | type Error = MockError; 188 | 189 | fn write(&mut self, address: u8, bytes: &[u8]) -> Result<(), Self::Error> { 190 | let e = self 191 | .next() 192 | .expect("no pending expectation for i2c::write call"); 193 | 194 | assert_eq!(e.expected_mode, Mode::Write, "i2c::write unexpected mode"); 195 | assert_eq!(e.expected_addr, address, "i2c::write address mismatch"); 196 | assert_eq!( 197 | e.expected_data, bytes, 198 | "i2c::write data does not match expectation" 199 | ); 200 | 201 | match e.expected_err { 202 | Some(err) => Err(err), 203 | None => Ok(()), 204 | } 205 | } 206 | } 207 | 208 | impl i2c::WriteRead for Mock { 209 | type Error = MockError; 210 | 211 | fn write_read( 212 | &mut self, 213 | address: u8, 214 | bytes: &[u8], 215 | buffer: &mut [u8], 216 | ) -> Result<(), Self::Error> { 217 | let e = self 218 | .next() 219 | .expect("no pending expectation for i2c::write_read call"); 220 | 221 | assert_eq!( 222 | e.expected_mode, 223 | Mode::WriteRead, 224 | "i2c::write_read unexpected mode" 225 | ); 226 | assert_eq!(e.expected_addr, address, "i2c::write_read address mismatch"); 227 | assert_eq!( 228 | e.expected_data, bytes, 229 | "i2c::write_read write data does not match expectation" 230 | ); 231 | 232 | assert_eq!( 233 | buffer.len(), 234 | e.response_data.len(), 235 | "i2c::write_read mismatched response length" 236 | ); 237 | 238 | match e.expected_err { 239 | Some(err) => Err(err), 240 | None => { 241 | buffer.copy_from_slice(&e.response_data); 242 | Ok(()) 243 | } 244 | } 245 | } 246 | } 247 | 248 | impl i2c::WriteIterRead for Mock { 249 | type Error = MockError; 250 | 251 | fn write_iter_read( 252 | &mut self, 253 | address: u8, 254 | bytes: B, 255 | buffer: &mut [u8], 256 | ) -> Result<(), Self::Error> 257 | where 258 | B: IntoIterator, 259 | { 260 | // Just collect the bytes and pass them on to the WriteRead::write_read implementation 261 | use embedded_hal::blocking::i2c::WriteRead; 262 | let bytes: Vec<_> = bytes.into_iter().collect(); 263 | self.write_read(address, bytes.as_slice(), buffer) 264 | } 265 | } 266 | 267 | impl i2c::WriteIter for Mock { 268 | type Error = MockError; 269 | 270 | fn write(&mut self, address: u8, bytes: B) -> Result<(), Self::Error> 271 | where 272 | B: IntoIterator, 273 | { 274 | // Just collect the bytes and pass them on to the Write::write implementation 275 | use embedded_hal::blocking::i2c::Write; 276 | let bytes: Vec<_> = bytes.into_iter().collect(); 277 | Write::write(self, address, bytes.as_slice()) 278 | } 279 | } 280 | 281 | #[cfg(test)] 282 | mod test { 283 | use std::{io::ErrorKind as IoErrorKind, time::SystemTime}; 284 | 285 | use eh0 as embedded_hal; 286 | use embedded_hal::blocking::i2c::{Read, Write, WriteRead}; 287 | 288 | use super::{super::error::MockError, *}; 289 | 290 | #[test] 291 | fn write() { 292 | let expectations = [Transaction::write(0xaa, vec![10, 12])]; 293 | let mut i2c = Mock::new(&expectations); 294 | 295 | i2c.write(0xaa, &vec![10, 12]).unwrap(); 296 | 297 | i2c.done(); 298 | } 299 | 300 | #[test] 301 | fn read() { 302 | let expectations = [Transaction::read(0xaa, vec![1, 2])]; 303 | let mut i2c = Mock::new(&expectations); 304 | 305 | let mut buf = vec![0; 2]; 306 | i2c.read(0xaa, &mut buf).unwrap(); 307 | assert_eq!(vec![1, 2], buf); 308 | 309 | i2c.done(); 310 | } 311 | 312 | #[test] 313 | fn write_read() { 314 | let expectations = [Transaction::write_read(0xaa, vec![1, 2], vec![3, 4])]; 315 | let mut i2c = Mock::new(&expectations); 316 | 317 | let v = vec![1, 2]; 318 | let mut buf = vec![0; 2]; 319 | i2c.write_read(0xaa, &v, &mut buf).unwrap(); 320 | assert_eq!(vec![3, 4], buf); 321 | 322 | i2c.done(); 323 | } 324 | 325 | #[test] 326 | fn multiple_transactions() { 327 | let expectations = [ 328 | Transaction::write(0xaa, vec![1, 2]), 329 | Transaction::read(0xbb, vec![3, 4]), 330 | ]; 331 | let mut i2c = Mock::new(&expectations); 332 | 333 | i2c.write(0xaa, &vec![1, 2]).unwrap(); 334 | 335 | let mut v = vec![0; 2]; 336 | i2c.read(0xbb, &mut v).unwrap(); 337 | assert_eq!(v, vec![3, 4]); 338 | 339 | i2c.done(); 340 | } 341 | 342 | #[test] 343 | #[should_panic(expected = "i2c::write data does not match expectation")] 344 | fn write_data_mismatch() { 345 | let expectations = [Transaction::write(0xaa, vec![1, 2])]; 346 | let mut i2c = Mock::new(&expectations); 347 | 348 | let _ = i2c.write(0xaa, &vec![1, 3]); 349 | } 350 | 351 | #[test] 352 | #[should_panic(expected = "i2c::write unexpected mode")] 353 | fn transaction_type_mismatch() { 354 | let expectations = [Transaction::read(0xaa, vec![10, 12])]; 355 | let mut i2c = Mock::new(&expectations); 356 | 357 | let mut buf = vec![0; 2]; 358 | let _ = i2c.write(0xaa, &mut buf); 359 | } 360 | 361 | #[test] 362 | #[should_panic(expected = "i2c::write_read address mismatch")] 363 | fn address_mismatch() { 364 | let expectations = [Transaction::write_read(0xbb, vec![1, 2], vec![3, 4])]; 365 | let mut i2c = Mock::new(&expectations); 366 | 367 | let v = vec![1, 2]; 368 | let mut buf = vec![0; 2]; 369 | let _ = i2c.write_read(0xaa, &v, &mut buf); 370 | } 371 | 372 | #[test] 373 | #[should_panic(expected = "Not all expectations consumed")] 374 | fn unconsumed_expectations() { 375 | let expectations = [ 376 | Transaction::write(0xaa, vec![10, 12]), 377 | Transaction::write(0xaa, vec![10, 12]), 378 | ]; 379 | let mut i2c = Mock::new(&expectations); 380 | 381 | i2c.write(0xaa, &vec![10, 12]).unwrap(); 382 | 383 | i2c.done(); 384 | } 385 | 386 | #[test] 387 | fn clone_linked_to_original() { 388 | let expectations = [ 389 | Transaction::read(0xaa, vec![1, 2]), 390 | Transaction::write(0xbb, vec![3, 4]), 391 | ]; 392 | let mut i2c = Mock::new(&expectations); 393 | 394 | // Clone mock. The clone should be linked to the same data as the original. 395 | let mut i2c_clone = i2c.clone(); 396 | 397 | // Read on the original mock 398 | let mut buf = vec![0; 2]; 399 | i2c.read(0xaa, &mut buf).unwrap(); 400 | assert_eq!(vec![1, 2], buf); 401 | 402 | // Write on the clone 403 | i2c_clone.write(0xbb, &[3, 4]).unwrap(); 404 | 405 | // Randomly call `.done()` on the original mock, or on the clone. 406 | // Use "system time % 2" as poor man's `rand()`. 407 | let now = SystemTime::now() 408 | .duration_since(SystemTime::UNIX_EPOCH) 409 | .unwrap(); 410 | if now.as_millis() % 2 == 0 { 411 | i2c.done(); 412 | } else { 413 | i2c_clone.done(); 414 | } 415 | } 416 | 417 | mod with_error { 418 | use super::*; 419 | 420 | #[test] 421 | fn write() { 422 | let expected_err = MockError::Io(IoErrorKind::Other); 423 | let mut i2c = Mock::new(&[ 424 | Transaction::write(0xaa, vec![10, 12]).with_error(expected_err.clone()) 425 | ]); 426 | let err = i2c.write(0xaa, &vec![10, 12]).unwrap_err(); 427 | assert_eq!(err, expected_err); 428 | i2c.done(); 429 | } 430 | 431 | /// The transaction mode should still be validated. 432 | #[test] 433 | #[should_panic(expected = "i2c::read unexpected mode")] 434 | fn write_wrong_mode() { 435 | let mut i2c = Mock::new(&[Transaction::write(0xaa, vec![10, 12]) 436 | .with_error(MockError::Io(IoErrorKind::Other))]); 437 | let mut buf = vec![0; 2]; 438 | let _ = i2c.read(0xaa, &mut buf); 439 | } 440 | 441 | /// The transaction bytes should still be validated. 442 | #[test] 443 | #[should_panic(expected = "i2c::write data does not match expectation")] 444 | fn write_wrong_data() { 445 | let mut i2c = Mock::new(&[Transaction::write(0xaa, vec![10, 12]) 446 | .with_error(MockError::Io(IoErrorKind::Other))]); 447 | let _ = i2c.write(0xaa, &vec![10, 13]); 448 | } 449 | 450 | #[test] 451 | fn read() { 452 | let expected_err = MockError::Io(IoErrorKind::Other); 453 | let mut i2c = 454 | Mock::new( 455 | &[Transaction::read(0xaa, vec![10, 12]).with_error(expected_err.clone())], 456 | ); 457 | let mut buf = vec![0; 2]; 458 | let err = i2c.read(0xaa, &mut buf).unwrap_err(); 459 | assert_eq!(err, expected_err); 460 | i2c.done(); 461 | } 462 | 463 | #[test] 464 | fn write_read() { 465 | let expected_err = MockError::Io(IoErrorKind::Other); 466 | let mut i2c = Mock::new(&[Transaction::write_read(0xaa, vec![10, 12], vec![13, 14]) 467 | .with_error(expected_err.clone())]); 468 | let mut buf = vec![0; 2]; 469 | let err = i2c.write_read(0xaa, &[10, 12], &mut buf).unwrap_err(); 470 | assert_eq!(err, expected_err); 471 | i2c.done(); 472 | } 473 | 474 | /// The transaction bytes should still be validated. 475 | #[test] 476 | #[should_panic(expected = "i2c::write_read write data does not match expectation")] 477 | fn write_read_wrong_data() { 478 | let mut i2c = Mock::new(&[Transaction::write_read(0xaa, vec![10, 12], vec![13, 14]) 479 | .with_error(MockError::Io(IoErrorKind::Other))]); 480 | let mut buf = vec![0; 2]; 481 | let _ = i2c.write_read(0xaa, &vec![10, 13], &mut buf); 482 | } 483 | } 484 | } 485 | -------------------------------------------------------------------------------- /src/eh1/serial.rs: -------------------------------------------------------------------------------- 1 | //! Serial mock implementations. 2 | //! 3 | //! You can set expectations for serial read and write transactions on a mock 4 | //! Serial device. Creating error transactions is supported as well. 5 | //! 6 | //! Note that the `embedded_hal` crate provides both non-blocking and blocking 7 | //! serial traits. You can use the same mock for both interfaces. 8 | //! 9 | //! ## Usage: Non-blocking serial traits 10 | //! 11 | //! ``` 12 | //! # use eh1 as embedded_hal; 13 | //! // Note that we're using the non-blocking serial traits 14 | //! use embedded_hal_mock::eh1::serial::{Mock as SerialMock, Transaction as SerialTransaction}; 15 | //! use embedded_hal_nb::serial::{Read, Write}; 16 | //! 17 | //! // Configure expectations 18 | //! let expectations = [ 19 | //! SerialTransaction::read(0x0A), 20 | //! SerialTransaction::read_many(b"xy"), 21 | //! SerialTransaction::write_many([1, 2]), // (1) 22 | //! SerialTransaction::flush(), 23 | //! ]; 24 | //! 25 | //! let mut serial = SerialMock::new(&expectations); 26 | //! 27 | //! // Expect three reads 28 | //! assert_eq!(serial.read().unwrap(), 0x0A); 29 | //! assert_eq!(serial.read().unwrap(), b'x'); 30 | //! assert_eq!(serial.read().unwrap(), b'y'); 31 | //! 32 | //! // When designing against the non-blocking serial 33 | //! // trait, we expect two separate writes. These could be 34 | //! // expressed as two separate transactions, too. See (1) above. 35 | //! serial.write(1).unwrap(); 36 | //! serial.write(2).unwrap(); 37 | //! 38 | //! // Finally, we expect a flush 39 | //! serial.flush().unwrap(); 40 | //! 41 | //! // When you believe there are no more calls on the mock, 42 | //! // call done() to assert there are no pending transactions. 43 | //! serial.done(); 44 | //! ``` 45 | //! 46 | //! ## Testing Error Handling 47 | //! 48 | //! If you want to test error handling of your code, you can also add error 49 | //! transactions. When the transaction is executed, an error is returned. 50 | //! 51 | //! ``` 52 | //! # use eh1 as embedded_hal; 53 | //! # use embedded_hal_mock::eh1::serial::{ 54 | //! # Mock as SerialMock, 55 | //! # Transaction as SerialTransaction, 56 | //! # }; 57 | //! use embedded_hal_nb::{ 58 | //! nb, 59 | //! serial::{ErrorKind, Read, Write}, 60 | //! }; 61 | //! 62 | //! // Configure expectations 63 | //! let expectations = [ 64 | //! SerialTransaction::read(42), 65 | //! SerialTransaction::read_error(nb::Error::WouldBlock), 66 | //! SerialTransaction::write_error(23, nb::Error::Other(ErrorKind::Other)), 67 | //! SerialTransaction::flush_error(nb::Error::Other(ErrorKind::Parity)), 68 | //! ]; 69 | //! let mut serial = SerialMock::new(&expectations); 70 | //! 71 | //! // The first read will succeed 72 | //! assert_eq!(serial.read().unwrap(), 42); 73 | //! 74 | //! // The second read will return an error 75 | //! assert_eq!(serial.read().unwrap_err(), nb::Error::WouldBlock); 76 | //! 77 | //! // The following write/flush calls will return errors as well 78 | //! assert_eq!( 79 | //! serial.write(23).unwrap_err(), 80 | //! nb::Error::Other(ErrorKind::Other) 81 | //! ); 82 | //! assert_eq!( 83 | //! serial.flush().unwrap_err(), 84 | //! nb::Error::Other(ErrorKind::Parity) 85 | //! ); 86 | //! 87 | //! // When you believe there are no more calls on the mock, 88 | //! // call done() to assert there are no pending transactions. 89 | //! serial.done(); 90 | //! ``` 91 | 92 | // This module is implemented a little differently than the spi and i2c 93 | // modules. We'll note that, unlike the spi and i2c modules which share the 94 | // foundational Generic transaction queue, we provide our own implementation. 95 | // We found that, in keeping with the established API design and the unique 96 | // features of the embedded_hal serial traits (described in the note below), 97 | // this was a necessary trade- off. We welcome any other ideas that allow us to 98 | // take advantage of the common components. 99 | // 100 | // We also generalize over a trait's `Word`, rather than requiring consumers to 101 | // use traits that operate on `u8`s. This does not make the public API any more 102 | // confusing for users, and it permits maximal flexibility. 103 | 104 | use std::{ 105 | collections::VecDeque, 106 | sync::{Arc, Mutex}, 107 | }; 108 | 109 | use embedded_hal_nb::{ 110 | nb, serial, 111 | serial::{ErrorKind, ErrorType}, 112 | }; 113 | 114 | use crate::common::DoneCallDetector; 115 | 116 | // Note that mode is private 117 | // 118 | // Although it is public in both the spi and i2c modules, the variants are not 119 | // required to be in the public interface. We chose to not supply them publicly 120 | // to consumers because there is no public API that readily uses them. 121 | 122 | /// Serial communication mode 123 | #[derive(Debug, Clone)] 124 | enum Mode { 125 | /// A serial read that returns a word 126 | Read(Word), 127 | /// A serial read that returns an error 128 | ReadError(nb::Error), 129 | /// A serial write that transmits a word 130 | Write(Word), 131 | /// A serial write that returns an error 132 | WriteError(Word, nb::Error), 133 | /// A flush call 134 | Flush, 135 | /// A flush call that returns an error 136 | FlushError(nb::Error), 137 | } 138 | 139 | /// A serial transaction 140 | /// 141 | /// Transactions can either be reads, writes, or flushes. A collection of 142 | /// transactions represent the expected operations that are performed on your 143 | /// serial device. 144 | /// 145 | /// # Example 146 | /// 147 | /// ```no_run 148 | /// use embedded_hal_mock::eh1::serial::{Mock, Transaction}; 149 | /// 150 | /// // We expect, in order, 151 | /// // 1. A read that returns 0x23, 152 | /// // 2. A write of [0x55, 0xAA] 153 | /// // 3. A flush 154 | /// let transactions = [ 155 | /// Transaction::read(0x23), 156 | /// Transaction::write_many([0x55, 0xAA]), 157 | /// Transaction::flush(), 158 | /// ]; 159 | /// 160 | /// let mut serial = Mock::new(&transactions); 161 | /// ``` 162 | pub struct Transaction { 163 | /// A collection of modes 164 | /// 165 | /// Since we need to express a blocking write in terms of multiple writes, 166 | /// we aggregate all of them into this member. Then, they are handed-off to 167 | /// the mock on construction. 168 | mode: Vec>, 169 | } 170 | 171 | impl Transaction 172 | where 173 | Word: Clone, 174 | { 175 | /// Expect a serial read that returns the expected word 176 | pub fn read(word: Word) -> Self { 177 | Transaction { 178 | mode: vec![Mode::Read(word)], 179 | } 180 | } 181 | 182 | /// Expect a serial read that returns the expected words 183 | pub fn read_many(words: Ws) -> Self 184 | where 185 | Ws: AsRef<[Word]>, 186 | { 187 | Transaction { 188 | mode: words.as_ref().iter().cloned().map(Mode::Read).collect(), 189 | } 190 | } 191 | 192 | /// Expect a serial read that returns an error 193 | pub fn read_error(error: nb::Error) -> Self { 194 | Transaction { 195 | mode: vec![Mode::ReadError(error)], 196 | } 197 | } 198 | 199 | /// Expect a serial write that transmits the specified word 200 | pub fn write(word: Word) -> Self { 201 | Transaction { 202 | mode: vec![Mode::Write(word)], 203 | } 204 | } 205 | 206 | /// Expect a serial write that transmits the specified words 207 | pub fn write_many(words: Ws) -> Self 208 | where 209 | Ws: AsRef<[Word]>, 210 | { 211 | Transaction { 212 | mode: words.as_ref().iter().cloned().map(Mode::Write).collect(), 213 | } 214 | } 215 | 216 | /// Expect a serial write that returns an error after transmitting the 217 | /// specified word 218 | pub fn write_error(word: Word, error: nb::Error) -> Self { 219 | Transaction { 220 | mode: vec![Mode::WriteError(word, error)], 221 | } 222 | } 223 | 224 | /// Expect a caller to flush the serial buffers 225 | pub fn flush() -> Self { 226 | Transaction { 227 | mode: vec![Mode::Flush], 228 | } 229 | } 230 | 231 | /// Expect a serial flush that returns an error 232 | pub fn flush_error(error: nb::Error) -> Self { 233 | Transaction { 234 | mode: vec![Mode::FlushError(error)], 235 | } 236 | } 237 | } 238 | 239 | /// Mock serial device 240 | /// 241 | /// The mock serial device can be loaded with expected transactions, then 242 | /// passed-on into a serial device user. If the expectations were not met in 243 | /// the specified order, the type causes a panic and describes what expectation 244 | /// wasn't met. 245 | /// 246 | /// The type is clonable so that it may be shared with a serial device user. 247 | /// Under the hood, both cloned mocks will share the same state, allowing your 248 | /// handle to eventually call `done()`, if desired. 249 | #[derive(Clone)] 250 | pub struct Mock { 251 | expected_modes: Arc>>>, 252 | done_called: Arc>, 253 | } 254 | 255 | impl Mock { 256 | /// Create a serial mock that will expect the provided transactions 257 | pub fn new(transactions: &[Transaction]) -> Self { 258 | let mut ser = Mock { 259 | expected_modes: Arc::new(Mutex::new(VecDeque::new())), 260 | done_called: Arc::new(Mutex::new(DoneCallDetector::new())), 261 | }; 262 | ser.update_expectations(transactions); 263 | ser 264 | } 265 | 266 | /// Update expectations on the interface 267 | /// 268 | /// When this method is called, first it is ensured that existing 269 | /// expectations are all consumed by calling [`done()`](#method.done) 270 | /// internally (if not called already). Afterwards, the new expectations 271 | /// are set. 272 | pub fn update_expectations(&mut self, transactions: &[Transaction]) { 273 | // Ensure that existing expectations are consumed 274 | self.done_impl(false); 275 | 276 | // Lock internal state 277 | let mut expected = self.expected_modes.lock().unwrap(); 278 | let mut done_called = self.done_called.lock().unwrap(); 279 | 280 | // Update expectations 281 | *expected = transactions 282 | .iter() 283 | .fold(VecDeque::new(), |mut modes, transaction| { 284 | modes.extend(transaction.mode.clone()); 285 | modes 286 | }); 287 | 288 | // Reset done call detector 289 | done_called.reset(); 290 | } 291 | 292 | /// Deprecated alias of `update_expectations`. 293 | #[deprecated( 294 | since = "0.10.0", 295 | note = "The method 'expect' was renamed to 'update_expectations'" 296 | )] 297 | pub fn expect(&mut self, transactions: &[Transaction]) { 298 | self.update_expectations(transactions) 299 | } 300 | 301 | /// Asserts that all expectations up to this point were satisfied. 302 | /// Panics if there are unsatisfied expectations. 303 | pub fn done(&mut self) { 304 | self.done_impl(true); 305 | } 306 | 307 | fn done_impl(&mut self, panic_if_already_done: bool) { 308 | self.done_called 309 | .lock() 310 | .unwrap() 311 | .mark_as_called(panic_if_already_done); 312 | 313 | let modes = self 314 | .expected_modes 315 | .lock() 316 | .expect("unable to lock serial mock in call to done"); 317 | assert!( 318 | modes.is_empty(), 319 | "serial mock has unsatisfied expectations after call to done" 320 | ); 321 | } 322 | 323 | /// Pop the next transaction out of the queue 324 | fn pop(&mut self) -> Option> { 325 | self.expected_modes 326 | .lock() 327 | .expect("unable to lock serial mock in call to pop") 328 | .pop_front() 329 | } 330 | } 331 | 332 | impl ErrorType for Mock { 333 | type Error = ErrorKind; 334 | } 335 | 336 | impl serial::Read for Mock 337 | where 338 | Word: Copy + Clone + std::fmt::Debug, 339 | { 340 | fn read(&mut self) -> nb::Result { 341 | let t = self.pop().expect("called serial::read with no expectation"); 342 | match t { 343 | Mode::Read(word) => Ok(word), 344 | Mode::ReadError(error) => Err(error), 345 | other => panic!( 346 | "expected to perform a serial transaction '{:?}', but instead did a read", 347 | other 348 | ), 349 | } 350 | } 351 | } 352 | 353 | impl serial::Write for Mock 354 | where 355 | Word: PartialEq + std::fmt::Debug + Copy + Clone, 356 | { 357 | fn write(&mut self, word: Word) -> nb::Result<(), Self::Error> { 358 | let t = self 359 | .pop() 360 | .expect("called serial::write with no expectation"); 361 | 362 | let assert_write = |expectation: Word| { 363 | assert_eq!( 364 | expectation, word, 365 | "serial::write expected to write {:?} but actually wrote {:?}", 366 | expectation, word 367 | ); 368 | }; 369 | 370 | match t { 371 | Mode::Write(expectation) => { 372 | assert_write(expectation); 373 | Ok(()) 374 | } 375 | Mode::WriteError(expectation, error) => { 376 | assert_write(expectation); 377 | Err(error) 378 | } 379 | other => panic!( 380 | "expected to perform a serial transaction '{:?}' but instead did a write of {:?}", 381 | other, word 382 | ), 383 | } 384 | } 385 | 386 | fn flush(&mut self) -> nb::Result<(), Self::Error> { 387 | let t = self 388 | .pop() 389 | .expect("called serial::flush with no expectation"); 390 | match t { 391 | Mode::Flush => Ok(()), 392 | Mode::FlushError(error) => Err(error), 393 | mode => panic!( 394 | "expected to perform a serial transaction '{:?}' but instead did a flush", 395 | mode 396 | ), 397 | } 398 | } 399 | } 400 | 401 | #[cfg(test)] 402 | mod test { 403 | use embedded_hal_nb::serial::{ErrorKind, Read, Write}; 404 | 405 | use super::*; 406 | 407 | #[test] 408 | fn test_serial_mock_read() { 409 | let ts = [Transaction::read(0x54)]; 410 | let mut ser = Mock::new(&ts); 411 | let r = ser.read().expect("failed to read"); 412 | assert_eq!(r, 0x54); 413 | ser.done(); 414 | } 415 | 416 | #[test] 417 | fn test_serial_mock_write_single_value_nonblocking() { 418 | let ts = [Transaction::write(0xAB)]; 419 | let mut ser = Mock::new(&ts); 420 | ser.write(0xAB).unwrap(); 421 | ser.done(); 422 | } 423 | 424 | #[test] 425 | fn test_serial_mock_write_many_values_nonblocking() { 426 | let ts = [Transaction::write_many([0xAB, 0xCD, 0xEF])]; 427 | let mut ser = Mock::new(&ts); 428 | ser.write(0xAB).unwrap(); 429 | ser.write(0xCD).unwrap(); 430 | ser.write(0xEF).unwrap(); 431 | ser.done(); 432 | } 433 | 434 | #[test] 435 | fn test_serial_mock_read_many_values_nonblocking() { 436 | let ts = [Transaction::read_many([0xAB, 0xCD, 0xEF])]; 437 | let mut ser = Mock::new(&ts); 438 | assert_eq!(0xAB, ser.read().unwrap()); 439 | assert_eq!(0xCD, ser.read().unwrap()); 440 | assert_eq!(0xEF, ser.read().unwrap()); 441 | ser.done(); 442 | } 443 | 444 | #[test] 445 | #[should_panic(expected = "serial::write expected to write 18 but actually wrote 20")] 446 | fn test_serial_mock_wrong_write() { 447 | let ts = [Transaction::write(0x12)]; 448 | let mut ser = Mock::new(&ts); 449 | ser.write(0x14).unwrap(); 450 | } 451 | 452 | #[test] 453 | fn test_serial_mock_flush() { 454 | let ts = [Transaction::flush()]; 455 | let mut ser: Mock = Mock::new(&ts); 456 | ser.flush().unwrap(); 457 | ser.done(); 458 | } 459 | 460 | #[test] 461 | #[should_panic(expected = "serial mock has unsatisfied expectations after call to done")] 462 | fn test_serial_mock_pending_transactions() { 463 | let ts = [Transaction::read(0x54)]; 464 | let mut ser = Mock::new(&ts); 465 | ser.done(); 466 | } 467 | 468 | #[test] 469 | #[should_panic(expected = "serial mock has unsatisfied expectations after call to done")] 470 | fn test_serial_mock_reuse_pending_transactions() { 471 | let ts = [Transaction::read(0x54)]; 472 | let mut ser = Mock::new(&ts); 473 | let r = ser.read().expect("failed to read"); 474 | assert_eq!(r, 0x54); 475 | ser.done(); 476 | ser.update_expectations(&ts); 477 | ser.done(); 478 | } 479 | 480 | #[test] 481 | #[should_panic( 482 | expected = "expected to perform a serial transaction 'Write(84)' but instead did a flush" 483 | )] 484 | fn test_serial_mock_expected_write() { 485 | let ts = [Transaction::write(0x54)]; 486 | let mut ser = Mock::new(&ts); 487 | ser.flush().unwrap(); 488 | } 489 | 490 | #[test] 491 | #[should_panic( 492 | expected = "expected to perform a serial transaction 'Flush', but instead did a read" 493 | )] 494 | fn test_serial_mock_expected_flush() { 495 | let ts = [Transaction::flush()]; 496 | let mut ser: Mock = Mock::new(&ts); 497 | ser.read().unwrap(); 498 | } 499 | 500 | #[test] 501 | fn test_serial_mock_read_error() { 502 | let error = nb::Error::WouldBlock; 503 | let ts = [Transaction::read_error(error.clone())]; 504 | let mut ser: Mock = Mock::new(&ts); 505 | assert_eq!(ser.read().unwrap_err(), error); 506 | ser.done(); 507 | } 508 | 509 | #[test] 510 | fn test_serial_mock_write_error() { 511 | let error = nb::Error::Other(ErrorKind::Parity); 512 | let ts = [Transaction::write_error(42, error.clone())]; 513 | let mut ser: Mock = Mock::new(&ts); 514 | assert_eq!(ser.write(42).unwrap_err(), error); 515 | ser.done(); 516 | } 517 | 518 | #[test] 519 | #[should_panic(expected = "serial::write expected to write 42 but actually wrote 23")] 520 | fn test_serial_mock_write_error_wrong_data() { 521 | let error = nb::Error::Other(ErrorKind::Parity); 522 | let ts = [Transaction::write_error(42, error.clone())]; 523 | let mut ser: Mock = Mock::new(&ts); 524 | // The data to be written should still be verified, even if there's an 525 | // error attached. 526 | ser.write(23).unwrap(); 527 | } 528 | 529 | #[test] 530 | fn test_serial_mock_flush_error() { 531 | let error = nb::Error::Other(ErrorKind::Overrun); 532 | let ts = [Transaction::flush_error(error.clone())]; 533 | let mut ser: Mock = Mock::new(&ts); 534 | assert_eq!(ser.flush().unwrap_err(), error); 535 | ser.done(); 536 | } 537 | } 538 | -------------------------------------------------------------------------------- /src/eh1/delay.rs: -------------------------------------------------------------------------------- 1 | //! Delay mock implementations, implementing both sync and async 2 | //! [`DelayNs`](https://docs.rs/embedded-hal/latest/embedded_hal/delay/trait.DelayNs.html) 3 | //! traits. 4 | //! 5 | //! ## Choosing a Delay Implementation 6 | //! 7 | //! There are three implementations available depending on your use case: 8 | //! 9 | //! - If you want **no actual delay**, create a 10 | //! [`NoopDelay`](struct.NoopDelay.html) stub. It will always return 11 | //! immediately, without a delay. This is useful for fast tests, where you 12 | //! don't actually need to wait for the hardware. 13 | //! - If you do want the **real delay behavior** when running your tests, use 14 | //! [`StdSleep`](struct.StdSleep.html) stub implementation, which uses 15 | //! [`std::thread::sleep`](https://doc.rust-lang.org/std/thread/fn.sleep.html) 16 | //! to implement the delay. 17 | //! - For a **configurable delay** implementation that supports expectations, 18 | //! use the [`CheckedDelay`](type.CheckedDelay.html) mock. By default it 19 | //! doesn't perform an actual delay, but allows the user to enable them 20 | //! individually for each expected call. 21 | //! 22 | //! ## Usage 23 | //! 24 | //! ``` 25 | //! # use eh1 as embedded_hal; 26 | //! use std::time::Duration; 27 | //! 28 | //! use embedded_hal::delay::DelayNs; 29 | //! use embedded_hal_mock::eh1::delay::{CheckedDelay, NoopDelay, StdSleep, Transaction}; 30 | //! 31 | //! // No actual delay 32 | //! 33 | //! let mut delay = NoopDelay::new(); 34 | //! delay.delay_ms(50); // Returns immediately 35 | //! 36 | //! // Real delay 37 | //! 38 | //! let mut delay = StdSleep::new(); 39 | //! delay.delay_ms(50); // Will sleep for 50 ms 40 | //! 41 | //! // Configurable mock 42 | //! 43 | //! let transactions = vec![ 44 | //! Transaction::delay_ns(50_000_000), 45 | //! Transaction::delay_us(60_000), 46 | //! Transaction::delay_ms(70).wait(), 47 | //! ]; 48 | //! 49 | //! let mut delay = CheckedDelay::new(&transactions); 50 | //! 51 | //! delay.delay_ms(50); // Conversion to nanoseconds 52 | //! delay.delay_ms(60); // Conversion to microseconds 53 | //! delay.delay_ms(70); // This will actually delay 54 | //! delay.done(); 55 | //! 56 | //! let mut delay = NoopDelay::new(); 57 | //! delay.delay_ms(50); // No checks are performed 58 | //! ``` 59 | 60 | use std::{thread, time::Duration}; 61 | 62 | use eh1 as embedded_hal; 63 | use embedded_hal::delay; 64 | 65 | use crate::common::Generic; 66 | 67 | /// Delay transaction 68 | #[derive(PartialEq, Eq, Clone, Debug)] 69 | pub struct Transaction { 70 | /// Kind is the transaction kind (and data) expected 71 | kind: TransactionKind, 72 | real_delay: bool, 73 | } 74 | 75 | /// Nanoseconds per microsecond 76 | const NANOS_PER_US: u64 = 1_000; 77 | /// Nanoseconds per millisecond 78 | const NANOS_PER_MS: u64 = 1_000_000; 79 | 80 | impl Transaction { 81 | /// Create a new delay transaction 82 | pub fn new(kind: TransactionKind) -> Transaction { 83 | Transaction { 84 | kind, 85 | real_delay: false, 86 | } 87 | } 88 | 89 | /// Create a new delay_ns transaction 90 | pub fn delay_ns(ns: u32) -> Transaction { 91 | Transaction::new(TransactionKind::DelayNs(ns.into())) 92 | } 93 | 94 | /// Create a new delay_us transaction 95 | pub fn delay_us(us: u32) -> Transaction { 96 | Transaction::new(TransactionKind::DelayNs(us as u64 * NANOS_PER_US)) 97 | } 98 | 99 | /// Create a new delay_ms transaction 100 | pub fn delay_ms(ms: u32) -> Transaction { 101 | Transaction::new(TransactionKind::DelayNs(ms as u64 * NANOS_PER_MS)) 102 | } 103 | 104 | /// Create a new blocking delay_ns transaction 105 | pub fn blocking_delay_ns(ns: u32) -> Transaction { 106 | Transaction::new(TransactionKind::BlockingDelayNs(ns.into())) 107 | } 108 | 109 | /// Create a new blocking delay_us transaction 110 | pub fn blocking_delay_us(us: u32) -> Transaction { 111 | Transaction::new(TransactionKind::BlockingDelayNs(us as u64 * NANOS_PER_US)) 112 | } 113 | 114 | /// Create new blocking delay_ms transaction 115 | pub fn blocking_delay_ms(ms: u32) -> Transaction { 116 | Transaction::new(TransactionKind::BlockingDelayNs(ms as u64 * NANOS_PER_MS)) 117 | } 118 | 119 | /// Create a new async delay_ns transaction 120 | #[cfg(feature = "embedded-hal-async")] 121 | pub fn async_delay_ns(ns: u32) -> Transaction { 122 | Transaction::new(TransactionKind::AsyncDelayNs(ns.into())) 123 | } 124 | 125 | /// Create a new async delay_us transaction 126 | #[cfg(feature = "embedded-hal-async")] 127 | pub fn async_delay_us(us: u32) -> Transaction { 128 | Transaction::new(TransactionKind::AsyncDelayNs(us as u64 * NANOS_PER_US)) 129 | } 130 | 131 | /// Create a new async delay_ms transaction 132 | #[cfg(feature = "embedded-hal-async")] 133 | pub fn async_delay_ms(ms: u32) -> Transaction { 134 | Transaction::new(TransactionKind::AsyncDelayNs(ms as u64 * NANOS_PER_MS)) 135 | } 136 | 137 | /// Perform an actual delay for this transaction 138 | pub fn wait(mut self) -> Transaction { 139 | self.real_delay = true; 140 | self 141 | } 142 | } 143 | 144 | /// MockDelay transaction kind. 145 | #[derive(PartialEq, Eq, Clone, Debug)] 146 | pub enum TransactionKind { 147 | /// Expect any type of delay in nanoseconds. 148 | /// 149 | /// The delay may be either blocking or async. In most cases, this is what you'll want to use. 150 | DelayNs(u64), 151 | /// Expect a blocking delay in nanoseconds 152 | /// 153 | /// The delay must be blocking. Expectation will fail for async delays. 154 | BlockingDelayNs(u64), 155 | /// Expect an async delay in nanoseconds 156 | /// 157 | /// The delay must be async. Expectation will fail for blocking delays. 158 | AsyncDelayNs(u64), 159 | } 160 | 161 | /// Mock Delay implementation with checked calls 162 | /// 163 | /// This supports the specification and checking of expectations to allow 164 | /// automated testing of delay based drivers. Mismatches between expected and 165 | /// real delay transactions will cause runtime assertions to assist with locating 166 | /// faults. 167 | /// 168 | /// See the usage section in the module level docs for an example. 169 | pub type CheckedDelay = Generic; 170 | 171 | impl delay::DelayNs for CheckedDelay { 172 | fn delay_ns(&mut self, ns: u32) { 173 | let transaction = self.next().expect("no expectation for delay call"); 174 | 175 | match transaction.kind { 176 | TransactionKind::BlockingDelayNs(n) => assert_eq!(n, ns.into(), "wrong delay value"), 177 | TransactionKind::DelayNs(n) => assert_eq!(n, ns.into(), "wrong delay value"), 178 | _ => panic!( 179 | "Wrong kind of delay. Expected DelayNs or BlockingDelayNs got {:?}", 180 | transaction.kind 181 | ), 182 | } 183 | 184 | if transaction.real_delay { 185 | thread::sleep(Duration::from_nanos(ns as u64)); 186 | } 187 | } 188 | 189 | fn delay_us(&mut self, us: u32) { 190 | let transaction = self.next().expect("no expectation for delay call"); 191 | match transaction.kind { 192 | TransactionKind::BlockingDelayNs(n) => { 193 | assert_eq!(n, us as u64 * NANOS_PER_US, "wrong delay value") 194 | } 195 | TransactionKind::DelayNs(n) => { 196 | assert_eq!(n, us as u64 * NANOS_PER_US, "wrong delay value") 197 | } 198 | _ => panic!( 199 | "Wrong kind of delay. Expected DelayNs or BlockingDelayNs got {:?}", 200 | transaction.kind 201 | ), 202 | } 203 | if transaction.real_delay { 204 | thread::sleep(Duration::from_micros(us as u64)); 205 | } 206 | } 207 | 208 | fn delay_ms(&mut self, ms: u32) { 209 | let transaction = self.next().expect("no expectation for delay call"); 210 | match transaction.kind { 211 | TransactionKind::BlockingDelayNs(n) => { 212 | assert_eq!(n, ms as u64 * NANOS_PER_MS, "wrong delay value") 213 | } 214 | TransactionKind::DelayNs(n) => { 215 | assert_eq!(n, ms as u64 * NANOS_PER_MS, "wrong delay value") 216 | } 217 | _ => panic!( 218 | "Wrong kind of delay. Expected DelayNs or BlockingDelayNs got {:?}", 219 | transaction.kind 220 | ), 221 | } 222 | 223 | if transaction.real_delay { 224 | thread::sleep(Duration::from_millis(ms as u64)); 225 | } 226 | } 227 | } 228 | 229 | #[cfg(feature = "embedded-hal-async")] 230 | impl embedded_hal_async::delay::DelayNs for CheckedDelay { 231 | async fn delay_ns(&mut self, ns: u32) { 232 | let transaction = self.next().expect("no expectation for delay call"); 233 | 234 | match transaction.kind { 235 | TransactionKind::AsyncDelayNs(n) => assert_eq!(n, ns.into(), "delay unexpected value"), 236 | TransactionKind::DelayNs(n) => assert_eq!(n, ns.into(), "delay unexpected value"), 237 | _ => panic!( 238 | "Wrong kind of delay. Expected DelayNs or AsyncDelayNs got {:?}", 239 | transaction.kind 240 | ), 241 | } 242 | 243 | if transaction.real_delay { 244 | thread::sleep(Duration::from_nanos(ns as u64)); 245 | } 246 | } 247 | 248 | async fn delay_us(&mut self, us: u32) { 249 | let transaction = self.next().expect("no expectation for delay call"); 250 | match transaction.kind { 251 | TransactionKind::AsyncDelayNs(n) => { 252 | assert_eq!(n, us as u64 * NANOS_PER_US, "wrong delay value") 253 | } 254 | TransactionKind::DelayNs(n) => { 255 | assert_eq!(n, us as u64 * NANOS_PER_US, "wrong delay value") 256 | } 257 | _ => panic!( 258 | "Wrong kind of delay. Expected DelayNs or AsyncDelayNs got {:?}", 259 | transaction.kind 260 | ), 261 | } 262 | 263 | if transaction.real_delay { 264 | thread::sleep(Duration::from_micros(us as u64)); 265 | } 266 | } 267 | 268 | async fn delay_ms(&mut self, ms: u32) { 269 | let transaction = self.next().expect("no expectation for delay call"); 270 | match transaction.kind { 271 | TransactionKind::AsyncDelayNs(n) => { 272 | assert_eq!(n, ms as u64 * NANOS_PER_MS, "wrong delay value") 273 | } 274 | TransactionKind::DelayNs(n) => { 275 | assert_eq!(n, ms as u64 * NANOS_PER_MS, "wrong delay value") 276 | } 277 | _ => panic!( 278 | "Wrong kind of delay. Expected DelayNs or AsyncDelayNs got {:?}", 279 | transaction.kind 280 | ), 281 | } 282 | 283 | if transaction.real_delay { 284 | thread::sleep(Duration::from_millis(ms as u64)); 285 | } 286 | } 287 | } 288 | 289 | /// A `Delay` implementation that does not actually block. 290 | pub struct NoopDelay; 291 | 292 | impl NoopDelay { 293 | /// Create a new `NoopDelay` instance. 294 | pub fn new() -> Self { 295 | NoopDelay 296 | } 297 | } 298 | 299 | impl Default for NoopDelay { 300 | fn default() -> Self { 301 | Self::new() 302 | } 303 | } 304 | 305 | impl delay::DelayNs for NoopDelay { 306 | fn delay_ns(&mut self, _ns: u32) { 307 | // no-op 308 | } 309 | } 310 | 311 | #[cfg(feature = "embedded-hal-async")] 312 | impl embedded_hal_async::delay::DelayNs for NoopDelay { 313 | async fn delay_ns(&mut self, _ns: u32) { 314 | // no-op 315 | } 316 | } 317 | 318 | /// A `Delay` implementation that uses `std::thread::sleep`. 319 | pub struct StdSleep; 320 | 321 | impl StdSleep { 322 | /// Create a new `StdSleep` instance. 323 | pub fn new() -> Self { 324 | StdSleep 325 | } 326 | } 327 | 328 | impl Default for StdSleep { 329 | fn default() -> Self { 330 | Self::new() 331 | } 332 | } 333 | 334 | impl delay::DelayNs for StdSleep { 335 | fn delay_ns(&mut self, ns: u32) { 336 | thread::sleep(Duration::from_nanos(ns as u64)); 337 | } 338 | } 339 | 340 | #[cfg(feature = "embedded-hal-async")] 341 | impl embedded_hal_async::delay::DelayNs for StdSleep { 342 | async fn delay_ns(&mut self, ns: u32) { 343 | thread::sleep(Duration::from_nanos(ns as u64)); 344 | } 345 | } 346 | 347 | #[cfg(test)] 348 | mod test { 349 | use std::time::Instant; 350 | 351 | use super::*; 352 | 353 | #[test] 354 | fn test_noop_sleep() { 355 | use embedded_hal::delay::DelayNs; 356 | 357 | let mut delay = NoopDelay::new(); 358 | let now = Instant::now(); 359 | delay.delay_ms(1000); 360 | assert!(now.elapsed().as_millis() < 100); 361 | } 362 | 363 | #[tokio::test] 364 | #[cfg(feature = "embedded-hal-async")] 365 | async fn test_noop_sleep_async() { 366 | use embedded_hal_async::delay::DelayNs; 367 | 368 | let mut delay = NoopDelay::new(); 369 | let now = Instant::now(); 370 | delay.delay_ms(1000).await; 371 | assert!(now.elapsed().as_millis() < 100); 372 | } 373 | 374 | #[test] 375 | fn test_std_sleep() { 376 | use embedded_hal::delay::DelayNs; 377 | 378 | let mut delay = StdSleep::new(); 379 | let now = Instant::now(); 380 | delay.delay_ms(1000); 381 | assert!(now.elapsed().as_millis() >= 1000); 382 | } 383 | 384 | #[tokio::test] 385 | #[cfg(feature = "embedded-hal-async")] 386 | async fn test_std_sleep_async() { 387 | use embedded_hal_async::delay::DelayNs; 388 | 389 | let mut delay = StdSleep::new(); 390 | let now = Instant::now(); 391 | delay.delay_ms(1000).await; 392 | assert!(now.elapsed().as_millis() >= 1000); 393 | } 394 | 395 | #[test] 396 | fn test_checked_sleep() { 397 | use embedded_hal::delay::DelayNs; 398 | 399 | let transactions = vec![ 400 | Transaction::delay_ns(1000), 401 | Transaction::delay_ns(2000), 402 | Transaction::delay_ns(3000), 403 | ]; 404 | 405 | let mut delay = CheckedDelay::new(&transactions); 406 | let now = Instant::now(); 407 | delay.delay_ns(1000); 408 | delay.delay_ns(2000); 409 | delay.delay_ns(3000); 410 | assert!(now.elapsed().as_millis() < 100); 411 | delay.done(); 412 | } 413 | 414 | #[tokio::test] 415 | #[cfg(feature = "embedded-hal-async")] 416 | async fn test_checked_sleep_async() { 417 | use embedded_hal_async::delay::DelayNs; 418 | 419 | let transactions = vec![ 420 | Transaction::async_delay_ns(1000), 421 | Transaction::async_delay_ns(2000), 422 | Transaction::async_delay_ns(3000), 423 | Transaction::blocking_delay_ns(4000), 424 | Transaction::delay_ns(5000), 425 | Transaction::delay_ns(6000), 426 | ]; 427 | 428 | let mut delay = CheckedDelay::new(&transactions); 429 | let now = Instant::now(); 430 | delay.delay_ns(1000).await; 431 | delay.delay_ns(2000).await; 432 | delay.delay_ns(3000).await; 433 | embedded_hal::delay::DelayNs::delay_ns(&mut delay, 4000); 434 | embedded_hal::delay::DelayNs::delay_ns(&mut delay, 5000); 435 | delay.delay_ns(6000).await; 436 | 437 | assert!(now.elapsed().as_millis() < 100); 438 | delay.done(); 439 | } 440 | 441 | #[test] 442 | fn test_checked_sleep_conversions() { 443 | use embedded_hal::delay::DelayNs; 444 | 445 | let transactions = vec![ 446 | Transaction::delay_ns(1000), 447 | Transaction::delay_us(1000), 448 | Transaction::delay_ms(1), 449 | ]; 450 | 451 | let mut delay = CheckedDelay::new(&transactions); 452 | let now = Instant::now(); 453 | delay.delay_us(1); 454 | delay.delay_ms(1); 455 | delay.delay_ns(1_000_000); 456 | assert!(now.elapsed().as_millis() < 100); 457 | delay.done(); 458 | } 459 | 460 | #[tokio::test] 461 | #[cfg(feature = "embedded-hal-async")] 462 | async fn test_checked_sleep_conversions_async() { 463 | use embedded_hal_async::delay::DelayNs; 464 | 465 | let transactions = vec![ 466 | Transaction::async_delay_ns(1000), 467 | Transaction::async_delay_us(1000), 468 | Transaction::async_delay_ms(1), 469 | ]; 470 | 471 | let mut delay = CheckedDelay::new(&transactions); 472 | let now = Instant::now(); 473 | delay.delay_us(1).await; 474 | delay.delay_ms(1).await; 475 | delay.delay_ns(1_000_000).await; 476 | assert!(now.elapsed().as_millis() < 100); 477 | delay.done(); 478 | } 479 | 480 | #[test] 481 | fn test_checked_sleep_real_delay() { 482 | use embedded_hal::delay::DelayNs; 483 | 484 | let transactions = vec![ 485 | Transaction::delay_ns(50_000_000).wait(), 486 | Transaction::delay_us(50_000).wait(), 487 | Transaction::delay_ms(50).wait(), 488 | ]; 489 | 490 | let mut delay = CheckedDelay::new(&transactions); 491 | let now = Instant::now(); 492 | delay.delay_ms(50); 493 | delay.delay_ms(50); 494 | delay.delay_ms(50); 495 | 496 | assert!(now.elapsed().as_millis() >= 150); 497 | assert!(now.elapsed().as_millis() < 1500); 498 | delay.done(); 499 | } 500 | 501 | #[tokio::test] 502 | #[cfg(feature = "embedded-hal-async")] 503 | async fn test_checked_sleep_real_delay_async() { 504 | use embedded_hal_async::delay::DelayNs; 505 | 506 | let transactions = vec![ 507 | Transaction::async_delay_ns(50_000_000).wait(), 508 | Transaction::async_delay_us(50_000).wait(), 509 | Transaction::async_delay_ms(50).wait(), 510 | ]; 511 | 512 | let mut delay = CheckedDelay::new(&transactions); 513 | let now = Instant::now(); 514 | delay.delay_ms(50).await; 515 | delay.delay_ms(50).await; 516 | delay.delay_ms(50).await; 517 | 518 | assert!(now.elapsed().as_millis() >= 150); 519 | assert!(now.elapsed().as_millis() < 1500); 520 | delay.done(); 521 | } 522 | 523 | #[test] 524 | fn test_checked_sleep_overflow() { 525 | use embedded_hal::delay::DelayNs; 526 | 527 | let transactions = vec![ 528 | Transaction::delay_us(4295000), 529 | Transaction::delay_ms(4295), 530 | Transaction::delay_us(4295000 * 100), 531 | Transaction::delay_ms(4295 * 100), 532 | ]; 533 | 534 | let mut delay = CheckedDelay::new(&transactions); 535 | let now = Instant::now(); 536 | delay.delay_us(4295000); 537 | delay.delay_ms(4295); 538 | delay.delay_us(4295000 * 100); 539 | delay.delay_ms(4295 * 100); 540 | assert!(now.elapsed().as_millis() < 100); 541 | delay.done(); 542 | } 543 | 544 | #[test] 545 | #[should_panic(expected = "wrong delay value")] 546 | fn test_checked_sleep_overflow_err() { 547 | use embedded_hal::delay::DelayNs; 548 | 549 | let transactions = vec![Transaction::delay_us(4295)]; 550 | 551 | let mut delay = CheckedDelay::new(&transactions); 552 | let now = Instant::now(); 553 | delay.delay_ms(4); 554 | assert!(now.elapsed().as_millis() < 100); 555 | delay.done(); 556 | } 557 | 558 | #[tokio::test] 559 | #[cfg(feature = "embedded-hal-async")] 560 | #[should_panic(expected = "wrong delay value")] 561 | async fn test_checked_sleep_overflow_async_err() { 562 | use embedded_hal_async::delay::DelayNs; 563 | 564 | let transactions = vec![Transaction::async_delay_us(4295)]; 565 | 566 | let mut delay = CheckedDelay::new(&transactions); 567 | let now = Instant::now(); 568 | delay.delay_ms(4).await; 569 | assert!(now.elapsed().as_millis() < 100); 570 | delay.done(); 571 | } 572 | } 573 | -------------------------------------------------------------------------------- /src/eh1/i2c.rs: -------------------------------------------------------------------------------- 1 | //! I²C mock implementations. 2 | //! 3 | //! ## Usage 4 | //! 5 | //! ``` 6 | //! # use eh1 as embedded_hal; 7 | //! use embedded_hal::i2c::{ErrorKind, I2c, Operation}; 8 | //! use embedded_hal_mock::eh1::i2c::{Mock as I2cMock, Transaction as I2cTransaction}; 9 | //! 10 | //! // Configure expectations 11 | //! let expectations = [ 12 | //! I2cTransaction::write(0xaa, vec![1, 2]), 13 | //! I2cTransaction::read(0xbb, vec![3, 4]), 14 | //! ]; 15 | //! let mut i2c = I2cMock::new(&expectations); 16 | //! 17 | //! // Writing 18 | //! i2c.write(0xaa, &vec![1, 2]).unwrap(); 19 | //! 20 | //! // Reading 21 | //! let mut buf = vec![0; 2]; 22 | //! i2c.read(0xbb, &mut buf).unwrap(); 23 | //! assert_eq!(buf, vec![3, 4]); 24 | //! 25 | //! // Finalise expectations 26 | //! i2c.done(); 27 | //! ``` 28 | //! 29 | //! ## Testing Error Handling 30 | //! 31 | //! If you want to test error handling of your code, you can attach an error to 32 | //! a transaction. When the transaction is executed, an error is returned. 33 | //! 34 | //! ``` 35 | //! # use eh1 as embedded_hal; 36 | //! # use embedded_hal::i2c::I2c; 37 | //! # use embedded_hal::i2c::ErrorKind; 38 | //! # use embedded_hal_mock::eh1::i2c::{Mock as I2cMock, Transaction as I2cTransaction}; 39 | //! 40 | //! // Configure expectations 41 | //! let expectations = [ 42 | //! I2cTransaction::write(0xaa, vec![1, 2]), 43 | //! I2cTransaction::read(0xbb, vec![3, 4]).with_error(ErrorKind::Other), 44 | //! ]; 45 | //! let mut i2c = I2cMock::new(&expectations); 46 | //! 47 | //! // Writing returns without an error 48 | //! i2c.write(0xaa, &vec![1, 2]).unwrap(); 49 | //! 50 | //! // Reading returns an error 51 | //! let mut buf = vec![0; 2]; 52 | //! let err = i2c.read(0xbb, &mut buf).unwrap_err(); 53 | //! assert_eq!(err, ErrorKind::Other); 54 | //! 55 | //! // Finalise expectations 56 | //! i2c.done(); 57 | //! ``` 58 | 59 | use eh1 as embedded_hal; 60 | use embedded_hal::i2c::{self, ErrorKind, ErrorType, I2c}; 61 | 62 | use crate::common::Generic; 63 | 64 | /// I2C Transaction modes 65 | #[derive(Clone, Debug, PartialEq, Eq)] 66 | pub enum Mode { 67 | /// Write transaction 68 | Write, 69 | /// Read transaction 70 | Read, 71 | /// Write and read transaction 72 | WriteRead, 73 | /// Mark the start of a transaction 74 | TransactionStart, 75 | /// Mark the end of a transaction 76 | TransactionEnd, 77 | } 78 | 79 | /// I2C Transaction type 80 | /// 81 | /// Models an I2C read or write 82 | #[derive(Clone, Debug, PartialEq, Eq)] 83 | pub struct Transaction { 84 | expected_mode: Mode, 85 | expected_addr: u8, 86 | expected_data: Vec, 87 | response_data: Vec, 88 | /// An optional error return for a transaction. 89 | /// 90 | /// This is in addition to the mode to allow validation that the 91 | /// transaction mode is correct prior to returning the error. 92 | expected_err: Option, 93 | } 94 | 95 | impl Transaction { 96 | /// Create a Write transaction 97 | pub fn write(addr: u8, expected: Vec) -> Transaction { 98 | Transaction { 99 | expected_mode: Mode::Write, 100 | expected_addr: addr, 101 | expected_data: expected, 102 | response_data: Vec::new(), 103 | expected_err: None, 104 | } 105 | } 106 | 107 | /// Create a Read transaction 108 | pub fn read(addr: u8, response: Vec) -> Transaction { 109 | Transaction { 110 | expected_mode: Mode::Read, 111 | expected_addr: addr, 112 | expected_data: Vec::new(), 113 | response_data: response, 114 | expected_err: None, 115 | } 116 | } 117 | 118 | /// Create a WriteRead transaction 119 | pub fn write_read(addr: u8, expected: Vec, response: Vec) -> Transaction { 120 | Transaction { 121 | expected_mode: Mode::WriteRead, 122 | expected_addr: addr, 123 | expected_data: expected, 124 | response_data: response, 125 | expected_err: None, 126 | } 127 | } 128 | 129 | /// Create nested transactions 130 | pub fn transaction_start(addr: u8) -> Transaction { 131 | Transaction { 132 | expected_mode: Mode::TransactionStart, 133 | expected_addr: addr, 134 | expected_data: Vec::new(), 135 | response_data: Vec::new(), 136 | expected_err: None, 137 | } 138 | } 139 | 140 | /// Create nested transactions 141 | pub fn transaction_end(addr: u8) -> Transaction { 142 | Transaction { 143 | expected_mode: Mode::TransactionEnd, 144 | expected_addr: addr, 145 | expected_data: Vec::new(), 146 | response_data: Vec::new(), 147 | expected_err: None, 148 | } 149 | } 150 | 151 | /// Add an error return to a transaction 152 | /// 153 | /// This is used to mock failure behaviours. 154 | /// 155 | /// Note: When attaching this to a read transaction, the response in the 156 | /// expectation will not actually be written to the buffer. 157 | pub fn with_error(mut self, error: ErrorKind) -> Self { 158 | self.expected_err = Some(error); 159 | self 160 | } 161 | } 162 | 163 | /// Mock I2C implementation 164 | /// 165 | /// This supports the specification and evaluation of expectations to allow 166 | /// automated testing of I2C based drivers. Mismatches between expectations 167 | /// will cause runtime assertions to assist in locating the source of the 168 | /// fault. 169 | pub type Mock = Generic; 170 | 171 | impl ErrorType for Mock { 172 | type Error = ErrorKind; 173 | } 174 | 175 | impl I2c for Mock { 176 | fn read(&mut self, address: u8, buffer: &mut [u8]) -> Result<(), Self::Error> { 177 | let e = self 178 | .next() 179 | .expect("no pending expectation for i2c::read call"); 180 | 181 | assert_eq!(e.expected_mode, Mode::Read, "i2c::read unexpected mode"); 182 | assert_eq!(e.expected_addr, address, "i2c::read address mismatch"); 183 | 184 | assert_eq!( 185 | buffer.len(), 186 | e.response_data.len(), 187 | "i2c:read mismatched response length" 188 | ); 189 | 190 | match e.expected_err { 191 | Some(err) => Err(err), 192 | None => { 193 | buffer.copy_from_slice(&e.response_data); 194 | Ok(()) 195 | } 196 | } 197 | } 198 | 199 | fn write(&mut self, address: u8, bytes: &[u8]) -> Result<(), Self::Error> { 200 | let e = self 201 | .next() 202 | .expect("no pending expectation for i2c::write call"); 203 | 204 | assert_eq!(e.expected_mode, Mode::Write, "i2c::write unexpected mode"); 205 | assert_eq!(e.expected_addr, address, "i2c::write address mismatch"); 206 | assert_eq!( 207 | e.expected_data, bytes, 208 | "i2c::write data does not match expectation" 209 | ); 210 | 211 | match e.expected_err { 212 | Some(err) => Err(err), 213 | None => Ok(()), 214 | } 215 | } 216 | 217 | fn write_read( 218 | &mut self, 219 | address: u8, 220 | bytes: &[u8], 221 | buffer: &mut [u8], 222 | ) -> Result<(), Self::Error> { 223 | let e = self 224 | .next() 225 | .expect("no pending expectation for i2c::write_read call"); 226 | 227 | assert_eq!( 228 | e.expected_mode, 229 | Mode::WriteRead, 230 | "i2c::write_read unexpected mode" 231 | ); 232 | assert_eq!(e.expected_addr, address, "i2c::write_read address mismatch"); 233 | assert_eq!( 234 | e.expected_data, bytes, 235 | "i2c::write_read write data does not match expectation" 236 | ); 237 | 238 | assert_eq!( 239 | buffer.len(), 240 | e.response_data.len(), 241 | "i2c::write_read mismatched response length" 242 | ); 243 | 244 | match e.expected_err { 245 | Some(err) => Err(err), 246 | None => { 247 | buffer.copy_from_slice(&e.response_data); 248 | Ok(()) 249 | } 250 | } 251 | } 252 | 253 | fn transaction<'a>( 254 | &mut self, 255 | address: u8, 256 | operations: &mut [i2c::Operation<'a>], 257 | ) -> Result<(), Self::Error> { 258 | let w = self 259 | .next() 260 | .expect("no pending expectation for i2c::transaction call"); 261 | 262 | assert_eq!( 263 | w.expected_mode, 264 | Mode::TransactionStart, 265 | "i2c::transaction_start unexpected mode" 266 | ); 267 | 268 | for op in operations { 269 | match op { 270 | i2c::Operation::Read(r) => self.read(address, r), 271 | i2c::Operation::Write(w) => self.write(address, w), 272 | } 273 | .unwrap(); 274 | } 275 | 276 | let w = self 277 | .next() 278 | .expect("no pending expectation for i2c::transaction call"); 279 | 280 | assert_eq!( 281 | w.expected_mode, 282 | Mode::TransactionEnd, 283 | "i2c::transaction_end unexpected mode" 284 | ); 285 | 286 | Ok(()) 287 | } 288 | } 289 | 290 | #[cfg(feature = "embedded-hal-async")] 291 | impl embedded_hal_async::i2c::I2c for Mock { 292 | async fn read(&mut self, address: u8, buffer: &mut [u8]) -> Result<(), Self::Error> { 293 | I2c::read(self, address, buffer) 294 | } 295 | 296 | async fn write(&mut self, address: u8, bytes: &[u8]) -> Result<(), Self::Error> { 297 | I2c::write(self, address, bytes) 298 | } 299 | 300 | async fn write_read( 301 | &mut self, 302 | address: u8, 303 | bytes: &[u8], 304 | buffer: &mut [u8], 305 | ) -> Result<(), Self::Error> { 306 | I2c::write_read(self, address, bytes, buffer) 307 | } 308 | 309 | async fn transaction<'a>( 310 | &mut self, 311 | address: u8, 312 | operations: &mut [i2c::Operation<'a>], 313 | ) -> Result<(), Self::Error> { 314 | I2c::transaction(self, address, operations) 315 | } 316 | } 317 | 318 | #[cfg(test)] 319 | mod test { 320 | use std::time::SystemTime; 321 | 322 | use super::*; 323 | 324 | #[test] 325 | fn write() { 326 | let expectations = [Transaction::write(0xaa, vec![10, 12])]; 327 | let mut i2c = Mock::new(&expectations); 328 | 329 | i2c.write(0xaa, &vec![10, 12]).unwrap(); 330 | 331 | i2c.done(); 332 | } 333 | 334 | #[test] 335 | fn read() { 336 | let expectations = [Transaction::read(0xaa, vec![1, 2])]; 337 | let mut i2c = Mock::new(&expectations); 338 | 339 | let mut buf = vec![0; 2]; 340 | i2c.read(0xaa, &mut buf).unwrap(); 341 | assert_eq!(vec![1, 2], buf); 342 | 343 | i2c.done(); 344 | } 345 | 346 | #[test] 347 | fn write_read() { 348 | let expectations = [Transaction::write_read(0xaa, vec![1, 2], vec![3, 4])]; 349 | let mut i2c = Mock::new(&expectations); 350 | 351 | let v = vec![1, 2]; 352 | let mut buf = vec![0; 2]; 353 | i2c.write_read(0xaa, &v, &mut buf).unwrap(); 354 | assert_eq!(vec![3, 4], buf); 355 | 356 | i2c.done(); 357 | } 358 | 359 | #[test] 360 | fn multiple_transactions() { 361 | let expectations = [ 362 | Transaction::write(0xaa, vec![1, 2]), 363 | Transaction::read(0xbb, vec![3, 4]), 364 | ]; 365 | let mut i2c = Mock::new(&expectations); 366 | 367 | i2c.write(0xaa, &vec![1, 2]).unwrap(); 368 | 369 | let mut v = vec![0; 2]; 370 | i2c.read(0xbb, &mut v).unwrap(); 371 | 372 | assert_eq!(v, vec![3, 4]); 373 | 374 | i2c.done(); 375 | } 376 | 377 | #[test] 378 | fn test_i2c_mock_multiple_transaction() { 379 | let expectations = [ 380 | Transaction::transaction_start(0xaa), 381 | Transaction::write(0xaa, vec![1, 2]), 382 | Transaction::read(0xaa, vec![3, 4]), 383 | Transaction::transaction_end(0xaa), 384 | ]; 385 | let mut i2c = Mock::new(&expectations); 386 | 387 | let mut v = vec![0u8; 2]; 388 | i2c.transaction( 389 | 0xaa, 390 | &mut [ 391 | i2c::Operation::Write(&vec![1, 2]), 392 | i2c::Operation::Read(&mut v), 393 | ], 394 | ) 395 | .unwrap(); 396 | 397 | assert_eq!(v, vec![3, 4]); 398 | 399 | i2c.done(); 400 | } 401 | 402 | #[test] 403 | #[should_panic(expected = "i2c::write data does not match expectation")] 404 | fn write_data_mismatch() { 405 | let expectations = [Transaction::write(0xaa, vec![1, 2])]; 406 | let mut i2c = Mock::new(&expectations); 407 | 408 | i2c.write(0xaa, &vec![1, 3]).unwrap(); 409 | } 410 | 411 | #[test] 412 | #[should_panic(expected = "i2c::write unexpected mode")] 413 | fn transaction_type_mismatch() { 414 | let expectations = [Transaction::read(0xaa, vec![10, 12])]; 415 | let mut i2c = Mock::new(&expectations); 416 | 417 | let mut buf = vec![0; 2]; 418 | i2c.write(0xaa, &mut buf).unwrap(); 419 | } 420 | 421 | #[test] 422 | #[should_panic(expected = "i2c::write_read address mismatch")] 423 | fn address_mismatch() { 424 | let expectations = [Transaction::write_read(0xbb, vec![1, 2], vec![3, 4])]; 425 | let mut i2c = Mock::new(&expectations); 426 | 427 | let v = vec![1, 2]; 428 | let mut buf = vec![0; 2]; 429 | i2c.write_read(0xaa, &v, &mut buf).unwrap(); 430 | } 431 | 432 | #[test] 433 | #[should_panic(expected = "i2c::write unexpected mode")] 434 | fn test_i2c_mock_mode_err() { 435 | let expectations = [Transaction::read(0xaa, vec![10, 12])]; 436 | let mut i2c = Mock::new(&expectations); 437 | 438 | i2c.write(0xaa, &vec![10, 12]).unwrap(); 439 | 440 | i2c.done(); 441 | } 442 | 443 | #[test] 444 | #[should_panic(expected = "Not all expectations consumed")] 445 | fn unconsumed_expectations() { 446 | let expectations = [ 447 | Transaction::write(0xaa, vec![10, 12]), 448 | Transaction::write(0xaa, vec![10, 12]), 449 | ]; 450 | let mut i2c = Mock::new(&expectations); 451 | 452 | i2c.write(0xaa, &vec![10, 12]).unwrap(); 453 | 454 | i2c.done(); 455 | } 456 | 457 | #[test] 458 | fn clone_linked_to_original() { 459 | let expectations = [ 460 | Transaction::read(0xaa, vec![1, 2]), 461 | Transaction::write(0xbb, vec![3, 4]), 462 | ]; 463 | let mut i2c = Mock::new(&expectations); 464 | 465 | // Clone mock. The clone should be linked to the same data as the original. 466 | let mut i2c_clone = i2c.clone(); 467 | 468 | // Read on the original mock 469 | let mut buf = vec![0; 2]; 470 | i2c.read(0xaa, &mut buf).unwrap(); 471 | assert_eq!(vec![1, 2], buf); 472 | 473 | // Write on the clone 474 | i2c_clone.write(0xbb, &[3, 4]).unwrap(); 475 | 476 | // Randomly call `.done()` on the original mock, or on the clone. 477 | // Use "system time % 2" as poor man's `rand()`. 478 | let now = SystemTime::now() 479 | .duration_since(SystemTime::UNIX_EPOCH) 480 | .unwrap(); 481 | if now.as_millis() % 2 == 0 { 482 | i2c.done(); 483 | } else { 484 | i2c_clone.done(); 485 | } 486 | } 487 | 488 | mod with_error { 489 | use super::*; 490 | 491 | #[test] 492 | fn write() { 493 | let expected_err = ErrorKind::Other; 494 | let mut i2c = Mock::new(&[ 495 | Transaction::write(0xaa, vec![10, 12]).with_error(expected_err.clone()) 496 | ]); 497 | let err = i2c.write(0xaa, &vec![10, 12]).unwrap_err(); 498 | assert_eq!(err, expected_err); 499 | i2c.done(); 500 | } 501 | 502 | /// The transaction mode should still be validated. 503 | #[test] 504 | #[should_panic(expected = "i2c::read unexpected mode")] 505 | fn write_wrong_mode() { 506 | let mut i2c = 507 | Mock::new(&[Transaction::write(0xaa, vec![10, 12]).with_error(ErrorKind::Other)]); 508 | let mut buf = vec![0; 2]; 509 | let _ = i2c.read(0xaa, &mut buf); 510 | } 511 | 512 | /// The transaction bytes should still be validated. 513 | #[test] 514 | #[should_panic(expected = "i2c::write data does not match expectation")] 515 | fn write_wrong_data() { 516 | let mut i2c = 517 | Mock::new(&[Transaction::write(0xaa, vec![10, 12]).with_error(ErrorKind::Other)]); 518 | let _ = i2c.write(0xaa, &vec![10, 13]); 519 | } 520 | 521 | #[test] 522 | fn read() { 523 | let expected_err = ErrorKind::Other; 524 | let mut i2c = 525 | Mock::new( 526 | &[Transaction::read(0xaa, vec![10, 12]).with_error(expected_err.clone())], 527 | ); 528 | let mut buf = vec![0; 2]; 529 | let err = i2c.read(0xaa, &mut buf).unwrap_err(); 530 | assert_eq!(err, expected_err); 531 | i2c.done(); 532 | } 533 | 534 | /// The transaction mode should still be validated. 535 | #[test] 536 | #[should_panic(expected = "i2c::write unexpected mode")] 537 | fn read_wrong_mode() { 538 | let mut i2c = 539 | Mock::new(&[Transaction::read(0xaa, vec![10, 12]).with_error(ErrorKind::Other)]); 540 | let _ = i2c.write(0xaa, &vec![10, 12]); 541 | } 542 | 543 | #[test] 544 | fn write_read() { 545 | let expected_err = ErrorKind::Other; 546 | let mut i2c = Mock::new(&[Transaction::write_read(0xaa, vec![10, 12], vec![13, 14]) 547 | .with_error(expected_err.clone())]); 548 | let mut buf = vec![0; 2]; 549 | let err = i2c.write_read(0xaa, &[10, 12], &mut buf).unwrap_err(); 550 | assert_eq!(err, expected_err); 551 | i2c.done(); 552 | } 553 | 554 | /// The transaction mode should still be validated. 555 | #[test] 556 | #[should_panic(expected = "i2c::write unexpected mode")] 557 | fn write_read_wrong_mode() { 558 | let mut i2c = Mock::new(&[Transaction::write_read(0xaa, vec![10, 12], vec![13, 14]) 559 | .with_error(ErrorKind::Other)]); 560 | let _ = i2c.write(0xaa, &vec![10, 12]); 561 | } 562 | 563 | /// The transaction bytes should still be validated. 564 | #[test] 565 | #[should_panic(expected = "i2c::write_read write data does not match expectation")] 566 | fn write_read_wrong_data() { 567 | let mut i2c = Mock::new(&[Transaction::write_read(0xaa, vec![10, 12], vec![13, 14]) 568 | .with_error(ErrorKind::Other)]); 569 | let mut buf = vec![0; 2]; 570 | let _ = i2c.write_read(0xaa, &vec![10, 13], &mut buf); 571 | } 572 | } 573 | 574 | /// Test that the async trait impls call the synchronous variants under the hood. 575 | #[tokio::test] 576 | #[cfg(feature = "embedded-hal-async")] 577 | async fn async_impls() { 578 | use embedded_hal_async::i2c::I2c; 579 | let expectations = [ 580 | Transaction::read(0xaa, vec![1, 2]), 581 | Transaction::write(0xaa, vec![10, 12]), 582 | Transaction::write_read(0xaa, vec![3, 4], vec![5, 6]), 583 | Transaction::transaction_start(0xbb), 584 | Transaction::write(0xbb, vec![7, 8]), 585 | Transaction::transaction_end(0xbb), 586 | ]; 587 | let mut i2c = Mock::new(&expectations); 588 | 589 | // Test read 590 | let mut buf = vec![0; 2]; 591 | I2c::read(&mut i2c, 0xaa, &mut buf).await.unwrap(); 592 | assert_eq!(vec![1, 2], buf); 593 | 594 | // Test write 595 | I2c::write(&mut i2c, 0xaa, &vec![10, 12]).await.unwrap(); 596 | 597 | // Test write_read 598 | let mut buf = vec![0; 2]; 599 | I2c::write_read(&mut i2c, 0xaa, &vec![3, 4], &mut buf) 600 | .await 601 | .unwrap(); 602 | assert_eq!(vec![5, 6], buf); 603 | 604 | // Test transaction 605 | I2c::transaction(&mut i2c, 0xbb, &mut [i2c::Operation::Write(&vec![7, 8])]) 606 | .await 607 | .unwrap(); 608 | 609 | i2c.done(); 610 | } 611 | } 612 | -------------------------------------------------------------------------------- /src/eh0/serial.rs: -------------------------------------------------------------------------------- 1 | //! Serial mock implementations. 2 | //! 3 | //! You can set expectations for serial read and write transactions on a mock 4 | //! Serial device. Creating error transactions is supported as well. 5 | //! 6 | //! Note that the `embedded_hal` crate provides both non-blocking and blocking 7 | //! serial traits. You can use the same mock for both interfaces. 8 | //! 9 | //! ## Usage: Non-blocking serial traits 10 | //! 11 | //! ``` 12 | //! # use eh0 as embedded_hal; 13 | //! // Note that we're using the non-blocking serial traits 14 | //! use embedded_hal::serial::{Read, Write}; 15 | //! use embedded_hal_mock::eh0::serial::{Mock as SerialMock, Transaction as SerialTransaction}; 16 | //! 17 | //! // Configure expectations 18 | //! let expectations = [ 19 | //! SerialTransaction::read(0x0A), 20 | //! SerialTransaction::read_many(b"xy"), 21 | //! SerialTransaction::write_many([1, 2]), // (1) 22 | //! SerialTransaction::flush(), 23 | //! ]; 24 | //! 25 | //! let mut serial = SerialMock::new(&expectations); 26 | //! 27 | //! // Expect three reads 28 | //! assert_eq!(serial.read().unwrap(), 0x0A); 29 | //! assert_eq!(serial.read().unwrap(), b'x'); 30 | //! assert_eq!(serial.read().unwrap(), b'y'); 31 | //! 32 | //! // When designing against the non-blocking serial 33 | //! // trait, we expect two separate writes. These could be 34 | //! // expressed as two separate transactions, too. See (1) above. 35 | //! serial.write(1).unwrap(); 36 | //! serial.write(2).unwrap(); 37 | //! 38 | //! // Finally, we expect a flush 39 | //! serial.flush().unwrap(); 40 | //! 41 | //! // When you believe there are no more calls on the mock, 42 | //! // call done() to assert there are no pending transactions. 43 | //! serial.done(); 44 | //! ``` 45 | //! 46 | //! ## Usage: Blocking serial trait 47 | //! 48 | //! ``` 49 | //! # use eh0 as embedded_hal; 50 | //! // Note that we're using the blocking serial write trait 51 | //! use embedded_hal::{blocking::serial::Write, serial::Read}; 52 | //! use embedded_hal_mock::eh0::serial::{Mock as SerialMock, Transaction as SerialTransaction}; 53 | //! 54 | //! // Configure expectations 55 | //! let expectations = [ 56 | //! SerialTransaction::read(0x0A), 57 | //! SerialTransaction::read_many(b"xy"), 58 | //! SerialTransaction::write_many([1, 2]), // (2) 59 | //! SerialTransaction::flush(), 60 | //! ]; 61 | //! 62 | //! let mut serial = SerialMock::new(&expectations); 63 | //! 64 | //! // Expect three reads 65 | //! assert_eq!(serial.read().unwrap(), 0x0A); 66 | //! assert_eq!(serial.read().unwrap(), b'x'); 67 | //! assert_eq!(serial.read().unwrap(), b'y'); 68 | //! 69 | //! // We use the blocking write here, and we assert that 70 | //! // two words are written. See (2) above. 71 | //! serial.bwrite_all(&[1, 2]).unwrap(); 72 | //! 73 | //! // Finally, we expect a flush. Note that this is 74 | //! // a *blocking* flush from the blocking serial trait. 75 | //! serial.bflush().unwrap(); 76 | //! 77 | //! // When you believe there are no more calls on the mock, 78 | //! // call done() to assert there are no pending transactions. 79 | //! serial.done(); 80 | //! ``` 81 | //! 82 | //! ## Testing Error Handling 83 | //! 84 | //! If you want to test error handling of your code, you can also add error 85 | //! transactions. When the transaction is executed, an error is returned. 86 | //! 87 | //! ``` 88 | //! # use eh0 as embedded_hal; 89 | //! # use embedded_hal::prelude::*; 90 | //! # use embedded_hal_mock::eh0::serial::{ 91 | //! # Mock as SerialMock, 92 | //! # Transaction as SerialTransaction, 93 | //! # }; 94 | //! use std::io::ErrorKind; 95 | //! 96 | //! use embedded_hal_mock::eh0::MockError; 97 | //! 98 | //! // Configure expectations 99 | //! let expectations = [ 100 | //! SerialTransaction::read(42), 101 | //! SerialTransaction::read_error(nb::Error::WouldBlock), 102 | //! SerialTransaction::write_error(23, nb::Error::Other(MockError::Io(ErrorKind::Other))), 103 | //! SerialTransaction::flush_error(nb::Error::Other(MockError::Io(ErrorKind::Interrupted))), 104 | //! ]; 105 | //! let mut serial = SerialMock::new(&expectations); 106 | //! 107 | //! // The first read will succeed 108 | //! assert_eq!(serial.read().unwrap(), 42); 109 | //! 110 | //! // The second read will return an error 111 | //! assert_eq!(serial.read().unwrap_err(), nb::Error::WouldBlock); 112 | //! 113 | //! // The following write/flush calls will return errors as well 114 | //! assert_eq!( 115 | //! serial.write(23).unwrap_err(), 116 | //! nb::Error::Other(MockError::Io(ErrorKind::Other)) 117 | //! ); 118 | //! assert_eq!( 119 | //! serial.flush().unwrap_err(), 120 | //! nb::Error::Other(MockError::Io(ErrorKind::Interrupted)) 121 | //! ); 122 | //! 123 | //! // When you believe there are no more calls on the mock, 124 | //! // call done() to assert there are no pending transactions. 125 | //! serial.done(); 126 | //! ``` 127 | 128 | // This module is implemented a little differently than the spi and i2c 129 | // modules. We'll note that, unlike the spi and i2c modules which share the 130 | // foundational Generic transaction queue, we provide our own implementation. 131 | // We found that, in keeping with the established API design and the unique 132 | // features of the embedded_hal serial traits (described in the note below), 133 | // this was a necessary trade- off. We welcome any other ideas that allow us to 134 | // take advantage of the common components. 135 | // 136 | // We also generalize over a trait's `Word`, rather than requiring consumers to 137 | // use traits that operate on `u8`s. This does not make the public API any more 138 | // confusing for users, and it permits maximal flexibility. 139 | 140 | use std::{ 141 | collections::VecDeque, 142 | sync::{Arc, Mutex}, 143 | }; 144 | 145 | use eh0 as embedded_hal; 146 | use embedded_hal::{blocking::serial::write, serial}; 147 | 148 | use super::error::MockError; 149 | use crate::common::DoneCallDetector; 150 | 151 | // Note that mode is private 152 | // 153 | // Although it is public in both the spi and i2c modules, the variants are not 154 | // required to be in the public interface. We chose to not supply them publicly 155 | // to consumers because there is no public API that readily uses them. 156 | 157 | /// Serial communication mode 158 | #[derive(Debug, Clone)] 159 | enum Mode { 160 | /// A serial read that returns a word 161 | Read(Word), 162 | /// A serial read that returns an error 163 | ReadError(nb::Error), 164 | /// A serial write that transmits a word 165 | Write(Word), 166 | /// A serial write that returns an error 167 | WriteError(Word, nb::Error), 168 | /// A flush call 169 | Flush, 170 | /// A flush call that returns an error 171 | FlushError(nb::Error), 172 | } 173 | 174 | /// A serial transaction 175 | /// 176 | /// Transactions can either be reads, writes, or flushes. A collection of 177 | /// transactions represent the expected operations that are performed on your 178 | /// serial device. 179 | /// 180 | /// # Example 181 | /// 182 | /// ```no_run 183 | /// use embedded_hal_mock::eh0::serial::{Mock, Transaction}; 184 | /// 185 | /// // We expect, in order, 186 | /// // 1. A read that returns 0x23, 187 | /// // 2. A write of [0x55, 0xAA] 188 | /// // 3. A flush 189 | /// let transactions = [ 190 | /// Transaction::read(0x23), 191 | /// Transaction::write_many([0x55, 0xAA]), 192 | /// Transaction::flush(), 193 | /// ]; 194 | /// 195 | /// let mut serial = Mock::new(&transactions); 196 | /// ``` 197 | pub struct Transaction { 198 | /// A collection of modes 199 | /// 200 | /// Since we need to express a blocking write in terms of multiple writes, 201 | /// we aggregate all of them into this member. Then, they are handed-off to 202 | /// the mock on construction. 203 | mode: Vec>, 204 | } 205 | 206 | impl Transaction 207 | where 208 | Word: Clone, 209 | { 210 | /// Expect a serial read that returns the expected word 211 | pub fn read(word: Word) -> Self { 212 | Transaction { 213 | mode: vec![Mode::Read(word)], 214 | } 215 | } 216 | 217 | /// Expect a serial read that returns the expected words 218 | pub fn read_many(words: Ws) -> Self 219 | where 220 | Ws: AsRef<[Word]>, 221 | { 222 | Transaction { 223 | mode: words.as_ref().iter().cloned().map(Mode::Read).collect(), 224 | } 225 | } 226 | 227 | /// Expect a serial read that returns an error 228 | pub fn read_error(error: nb::Error) -> Self { 229 | Transaction { 230 | mode: vec![Mode::ReadError(error)], 231 | } 232 | } 233 | 234 | /// Expect a serial write that transmits the specified word 235 | pub fn write(word: Word) -> Self { 236 | Transaction { 237 | mode: vec![Mode::Write(word)], 238 | } 239 | } 240 | 241 | /// Expect a serial write that transmits the specified words 242 | pub fn write_many(words: Ws) -> Self 243 | where 244 | Ws: AsRef<[Word]>, 245 | { 246 | Transaction { 247 | mode: words.as_ref().iter().cloned().map(Mode::Write).collect(), 248 | } 249 | } 250 | 251 | /// Expect a serial write that returns an error after transmitting the 252 | /// specified word 253 | pub fn write_error(word: Word, error: nb::Error) -> Self { 254 | Transaction { 255 | mode: vec![Mode::WriteError(word, error)], 256 | } 257 | } 258 | 259 | /// Expect a caller to flush the serial buffers 260 | pub fn flush() -> Self { 261 | Transaction { 262 | mode: vec![Mode::Flush], 263 | } 264 | } 265 | 266 | /// Expect a serial flush that returns an error 267 | pub fn flush_error(error: nb::Error) -> Self { 268 | Transaction { 269 | mode: vec![Mode::FlushError(error)], 270 | } 271 | } 272 | } 273 | 274 | /// Mock serial device 275 | /// 276 | /// The mock serial device can be loaded with expected transactions, then 277 | /// passed-on into a serial device user. If the expectations were not met in 278 | /// the specified order, the type causes a panic and describes what expectation 279 | /// wasn't met. 280 | /// 281 | /// The type is clonable so that it may be shared with a serial device user. 282 | /// Under the hood, both cloned mocks will share the same state, allowing your 283 | /// handle to eventually call `done()`, if desired. 284 | #[derive(Clone)] 285 | pub struct Mock { 286 | expected_modes: Arc>>>, 287 | done_called: Arc>, 288 | } 289 | 290 | impl Mock { 291 | /// Create a serial mock that will expect the provided transactions 292 | pub fn new(transactions: &[Transaction]) -> Self { 293 | let mut ser = Mock { 294 | expected_modes: Arc::new(Mutex::new(VecDeque::new())), 295 | done_called: Arc::new(Mutex::new(DoneCallDetector::new())), 296 | }; 297 | ser.update_expectations(transactions); 298 | ser 299 | } 300 | 301 | /// Update expectations on the interface 302 | /// 303 | /// When this method is called, first it is ensured that existing 304 | /// expectations are all consumed by calling [`done()`](#method.done) 305 | /// internally (if not called already). Afterwards, the new expectations 306 | /// are set. 307 | pub fn update_expectations(&mut self, transactions: &[Transaction]) { 308 | // Ensure that existing expectations are consumed 309 | self.done_impl(false); 310 | 311 | // Lock internal state 312 | let mut expected = self.expected_modes.lock().unwrap(); 313 | let mut done_called = self.done_called.lock().unwrap(); 314 | 315 | // Update expectations 316 | *expected = transactions 317 | .iter() 318 | .fold(VecDeque::new(), |mut modes, transaction| { 319 | modes.extend(transaction.mode.clone()); 320 | modes 321 | }); 322 | 323 | // Reset done call detector 324 | done_called.reset(); 325 | } 326 | 327 | /// Deprecated alias of `update_expectations`. 328 | #[deprecated( 329 | since = "0.10.0", 330 | note = "The method 'expect' was renamed to 'update_expectations'" 331 | )] 332 | pub fn expect(&mut self, transactions: &[Transaction]) { 333 | self.update_expectations(transactions) 334 | } 335 | 336 | /// Asserts that all expectations up to this point were satisfied. 337 | /// Panics if there are unsatisfied expectations. 338 | pub fn done(&mut self) { 339 | self.done_impl(true); 340 | } 341 | 342 | fn done_impl(&mut self, panic_if_already_done: bool) { 343 | self.done_called 344 | .lock() 345 | .unwrap() 346 | .mark_as_called(panic_if_already_done); 347 | 348 | let modes = self 349 | .expected_modes 350 | .lock() 351 | .expect("unable to lock serial mock in call to done"); 352 | assert!( 353 | modes.is_empty(), 354 | "serial mock has unsatisfied expectations after call to done" 355 | ); 356 | } 357 | 358 | /// Pop the next transaction out of the queue 359 | fn pop(&mut self) -> Option> { 360 | self.expected_modes 361 | .lock() 362 | .expect("unable to lock serial mock in call to pop") 363 | .pop_front() 364 | } 365 | } 366 | 367 | impl serial::Read for Mock 368 | where 369 | Word: Clone + std::fmt::Debug, 370 | { 371 | type Error = MockError; 372 | 373 | fn read(&mut self) -> nb::Result { 374 | let t = self.pop().expect("called serial::read with no expectation"); 375 | match t { 376 | Mode::Read(word) => Ok(word), 377 | Mode::ReadError(error) => Err(error), 378 | other => panic!( 379 | "expected to perform a serial transaction '{:?}', but instead did a read", 380 | other 381 | ), 382 | } 383 | } 384 | } 385 | 386 | impl serial::Write for Mock 387 | where 388 | Word: PartialEq + std::fmt::Debug + Clone, 389 | { 390 | type Error = MockError; 391 | 392 | fn write(&mut self, word: Word) -> nb::Result<(), Self::Error> { 393 | let t = self 394 | .pop() 395 | .expect("called serial::write with no expectation"); 396 | 397 | let assert_write = |expectation: Word| { 398 | assert_eq!( 399 | expectation, word, 400 | "serial::write expected to write {:?} but actually wrote {:?}", 401 | expectation, word 402 | ); 403 | }; 404 | 405 | match t { 406 | Mode::Write(expectation) => { 407 | assert_write(expectation); 408 | Ok(()) 409 | } 410 | Mode::WriteError(expectation, error) => { 411 | assert_write(expectation); 412 | Err(error) 413 | } 414 | other => panic!( 415 | "expected to perform a serial transaction '{:?}' but instead did a write of {:?}", 416 | other, word 417 | ), 418 | } 419 | } 420 | 421 | fn flush(&mut self) -> nb::Result<(), Self::Error> { 422 | let t = self 423 | .pop() 424 | .expect("called serial::flush with no expectation"); 425 | match t { 426 | Mode::Flush => Ok(()), 427 | Mode::FlushError(error) => Err(error), 428 | mode => panic!( 429 | "expected to perform a serial transaction '{:?}' but instead did a flush", 430 | mode 431 | ), 432 | } 433 | } 434 | } 435 | 436 | // Note: We attempted to provide our own implementation of 437 | // embedded_hal::blocking::serial::Write. However, we're unable 438 | // to override it due to the blanket default implementation provided by 439 | // the embedded_hal crate. It comes down to the fact that, if we were 440 | // to provide an embedded_hal::blocking::serial::Write implementation 441 | // here, any user of embedded_hal would be free to implement the *default* 442 | // version for our type. Therefore, we conform to the default implementation, 443 | // knowing that the default is implemented in terms of the non-blocking 444 | // trait, which is defined above. 445 | // 446 | // If you know a way around this, please let us know! 447 | impl write::Default for Mock where Word: PartialEq + std::fmt::Debug + Clone {} 448 | 449 | #[cfg(test)] 450 | mod test { 451 | use std::io; 452 | 453 | use eh0 as embedded_hal; 454 | use embedded_hal::{ 455 | blocking::serial::Write as BWrite, 456 | serial::{Read, Write}, 457 | }; 458 | 459 | use super::{super::error::MockError, *}; 460 | 461 | #[test] 462 | fn test_serial_mock_read() { 463 | let ts = [Transaction::read(0x54)]; 464 | let mut ser = Mock::new(&ts); 465 | let r = ser.read().expect("failed to read"); 466 | assert_eq!(r, 0x54); 467 | ser.done(); 468 | } 469 | 470 | #[test] 471 | fn test_serial_mock_write_single_value_nonblocking() { 472 | let ts = [Transaction::write(0xAB)]; 473 | let mut ser = Mock::new(&ts); 474 | ser.write(0xAB).unwrap(); 475 | ser.done(); 476 | } 477 | 478 | #[test] 479 | fn test_serial_mock_write_many_values_nonblocking() { 480 | let ts = [Transaction::write_many([0xAB, 0xCD, 0xEF])]; 481 | let mut ser = Mock::new(&ts); 482 | ser.write(0xAB).unwrap(); 483 | ser.write(0xCD).unwrap(); 484 | ser.write(0xEF).unwrap(); 485 | ser.done(); 486 | } 487 | 488 | #[test] 489 | fn test_serial_mock_read_many_values_nonblocking() { 490 | let ts = [Transaction::read_many([0xAB, 0xCD, 0xEF])]; 491 | let mut ser = Mock::new(&ts); 492 | assert_eq!(0xAB, ser.read().unwrap()); 493 | assert_eq!(0xCD, ser.read().unwrap()); 494 | assert_eq!(0xEF, ser.read().unwrap()); 495 | ser.done(); 496 | } 497 | 498 | #[test] 499 | fn test_serial_mock_blocking_write() { 500 | let ts = [Transaction::write_many([0xAB, 0xCD, 0xEF])]; 501 | let mut ser = Mock::new(&ts); 502 | ser.bwrite_all(&[0xAB, 0xCD, 0xEF]).unwrap(); 503 | ser.done(); 504 | } 505 | 506 | #[test] 507 | #[should_panic(expected = "called serial::write with no expectation")] 508 | fn test_serial_mock_blocking_write_more_than_expected() { 509 | let ts = [Transaction::write_many([0xAB, 0xCD])]; 510 | let mut ser = Mock::new(&ts); 511 | ser.bwrite_all(&[0xAB, 0xCD, 0xEF]).unwrap(); 512 | ser.done(); 513 | } 514 | 515 | #[test] 516 | #[should_panic(expected = "serial mock has unsatisfied expectations after call to done")] 517 | fn test_serial_mock_blocking_write_not_enough() { 518 | let ts = [Transaction::write_many([0xAB, 0xCD, 0xEF, 0x00])]; 519 | let mut ser = Mock::new(&ts); 520 | ser.bwrite_all(&[0xAB, 0xCD, 0xEF]).unwrap(); 521 | ser.done(); 522 | } 523 | 524 | #[test] 525 | #[should_panic(expected = "serial::write expected to write 18 but actually wrote 20")] 526 | fn test_serial_mock_wrong_write() { 527 | let ts = [Transaction::write(0x12)]; 528 | let mut ser = Mock::new(&ts); 529 | ser.write(0x14).unwrap(); 530 | } 531 | 532 | #[test] 533 | fn test_serial_mock_flush() { 534 | let ts = [Transaction::flush()]; 535 | let mut ser: Mock = Mock::new(&ts); 536 | ser.flush().unwrap(); 537 | ser.done(); 538 | } 539 | 540 | #[test] 541 | fn test_serial_mock_blocking_flush() { 542 | let ts = [Transaction::flush()]; 543 | let mut ser: Mock = Mock::new(&ts); 544 | ser.bflush().unwrap(); 545 | ser.done(); 546 | } 547 | 548 | #[test] 549 | #[should_panic(expected = "serial mock has unsatisfied expectations after call to done")] 550 | fn test_serial_mock_pending_transactions() { 551 | let ts = [Transaction::read(0x54)]; 552 | let mut ser = Mock::new(&ts); 553 | ser.done(); 554 | } 555 | 556 | #[test] 557 | #[should_panic(expected = "serial mock has unsatisfied expectations after call to done")] 558 | fn test_serial_mock_reuse_pending_transactions() { 559 | let ts = [Transaction::read(0x54)]; 560 | let mut ser = Mock::new(&ts); 561 | let r = ser.read().expect("failed to read"); 562 | assert_eq!(r, 0x54); 563 | ser.done(); 564 | ser.update_expectations(&ts); 565 | ser.done(); 566 | } 567 | 568 | #[test] 569 | #[should_panic( 570 | expected = "expected to perform a serial transaction 'Read(84)' but instead did a write of 119" 571 | )] 572 | fn test_serial_mock_expected_read() { 573 | let ts = [Transaction::read(0x54)]; 574 | let mut ser = Mock::new(&ts); 575 | ser.bwrite_all(&[0x77]).unwrap(); 576 | } 577 | 578 | #[test] 579 | #[should_panic( 580 | expected = "expected to perform a serial transaction 'Write(84)' but instead did a flush" 581 | )] 582 | fn test_serial_mock_expected_write() { 583 | let ts = [Transaction::write(0x54)]; 584 | let mut ser = Mock::new(&ts); 585 | ser.flush().unwrap(); 586 | } 587 | 588 | #[test] 589 | #[should_panic( 590 | expected = "expected to perform a serial transaction 'Flush', but instead did a read" 591 | )] 592 | fn test_serial_mock_expected_flush() { 593 | let ts = [Transaction::flush()]; 594 | let mut ser: Mock = Mock::new(&ts); 595 | ser.read().unwrap(); 596 | } 597 | 598 | #[test] 599 | fn test_serial_mock_read_error() { 600 | let error = nb::Error::WouldBlock; 601 | let ts = [Transaction::read_error(error.clone())]; 602 | let mut ser: Mock = Mock::new(&ts); 603 | assert_eq!(ser.read().unwrap_err(), error); 604 | ser.done(); 605 | } 606 | 607 | #[test] 608 | fn test_serial_mock_write_error() { 609 | let error = nb::Error::Other(MockError::Io(io::ErrorKind::NotConnected)); 610 | let ts = [Transaction::write_error(42, error.clone())]; 611 | let mut ser: Mock = Mock::new(&ts); 612 | assert_eq!(ser.write(42).unwrap_err(), error); 613 | ser.done(); 614 | } 615 | 616 | #[test] 617 | #[should_panic(expected = "serial::write expected to write 42 but actually wrote 23")] 618 | fn test_serial_mock_write_error_wrong_data() { 619 | let error = nb::Error::Other(MockError::Io(io::ErrorKind::NotConnected)); 620 | let ts = [Transaction::write_error(42, error.clone())]; 621 | let mut ser: Mock = Mock::new(&ts); 622 | // The data to be written should still be verified, even if there's an 623 | // error attached. 624 | ser.write(23).unwrap(); 625 | } 626 | 627 | #[test] 628 | fn test_serial_mock_flush_error() { 629 | let error = nb::Error::Other(MockError::Io(io::ErrorKind::TimedOut)); 630 | let ts = [Transaction::flush_error(error.clone())]; 631 | let mut ser: Mock = Mock::new(&ts); 632 | assert_eq!(ser.flush().unwrap_err(), error); 633 | ser.done(); 634 | } 635 | } 636 | -------------------------------------------------------------------------------- /src/eh1/digital.rs: -------------------------------------------------------------------------------- 1 | //! Mock digital [`InputPin`], [`OutputPin`], and [`StatefulOutputPin`] implementations 2 | //! Also mock calls to [`Wait`], assuming the `embedded-hal-async` feature is enabled. 3 | //! 4 | //! [`InputPin`]: https://docs.rs/embedded-hal/1/embedded_hal/digital/trait.InputPin.html 5 | //! [`OutputPin`]: https://docs.rs/embedded-hal/1/embedded_hal/digital/trait.OutputPin.html 6 | //! [`StatefulOutputPin`]: https://docs.rs/embedded-hal/1/embedded_hal/digital/trait.StatefulOutputPin.html 7 | //! [`Wait`]: https://docs.rs/embedded-hal-async/1/embedded_hal_async/digital/trait.Wait.html 8 | //! 9 | //! ``` 10 | //! # use eh1 as embedded_hal; 11 | //! use std::io::ErrorKind; 12 | //! 13 | //! use embedded_hal::digital::{InputPin, OutputPin, StatefulOutputPin}; 14 | //! use embedded_hal_mock::eh1::{ 15 | //! digital::{Mock as PinMock, State as PinState, Transaction as PinTransaction}, 16 | //! MockError, 17 | //! }; 18 | //! 19 | //! let err = MockError::Io(ErrorKind::NotConnected); 20 | //! 21 | //! // Configure expectations 22 | //! let expectations = [ 23 | //! PinTransaction::get(PinState::High), 24 | //! PinTransaction::get(PinState::High), 25 | //! PinTransaction::set(PinState::Low), 26 | //! PinTransaction::set(PinState::High).with_error(err.clone()), 27 | //! PinTransaction::get_state(PinState::High), 28 | //! PinTransaction::toggle(), 29 | //! PinTransaction::get_state(PinState::Low), 30 | //! ]; 31 | //! 32 | //! // Create pin 33 | //! let mut pin = PinMock::new(&expectations); 34 | //! 35 | //! // Run and test 36 | //! assert_eq!(pin.is_high().unwrap(), true); 37 | //! assert_eq!(pin.is_low().unwrap(), false); 38 | //! 39 | //! pin.set_low().unwrap(); 40 | //! pin.set_high().expect_err("expected error return"); 41 | //! 42 | //! pin.is_set_high().unwrap(); 43 | //! pin.toggle().unwrap(); 44 | //! pin.is_set_low().unwrap(); 45 | //! 46 | //! pin.done(); 47 | //! 48 | //! // Update expectations 49 | //! pin.update_expectations(&[]); 50 | //! // ... 51 | //! pin.done(); 52 | //! ``` 53 | 54 | use eh1 as embedded_hal; 55 | use embedded_hal::digital::{ErrorType, InputPin, OutputPin, StatefulOutputPin}; 56 | 57 | use crate::{common::Generic, eh1::error::MockError}; 58 | 59 | /// MockPin transaction 60 | #[derive(PartialEq, Eq, Clone, Debug)] 61 | pub struct Transaction { 62 | /// Kind is the transaction kind (and data) expected 63 | kind: TransactionKind, 64 | /// An optional error return value for a transaction. This is in addition 65 | /// to `kind` to allow validation that the transaction kind is correct 66 | /// prior to returning the error. 67 | err: Option, 68 | } 69 | 70 | #[derive(PartialEq, Eq, Copy, Clone, Debug)] 71 | /// Digital pin value enumeration 72 | pub enum State { 73 | /// Digital low state 74 | Low, 75 | /// Digital high state 76 | High, 77 | } 78 | 79 | #[cfg(feature = "embedded-hal-async")] 80 | #[derive(PartialEq, Eq, Copy, Clone, Debug)] 81 | /// Digital pin edge enumeration 82 | pub enum Edge { 83 | /// Digital rising edge 84 | Rising, 85 | /// Digital falling edge 86 | Falling, 87 | /// Either digital rising or falling edge 88 | Any, 89 | } 90 | 91 | impl Transaction { 92 | /// Create a new pin transaction 93 | pub fn new(kind: TransactionKind) -> Transaction { 94 | Transaction { kind, err: None } 95 | } 96 | 97 | /// Create a new get transaction 98 | pub fn get(state: State) -> Transaction { 99 | Transaction::new(TransactionKind::Get(state)) 100 | } 101 | 102 | /// Create a new get transaction 103 | pub fn set(state: State) -> Transaction { 104 | Transaction::new(TransactionKind::Set(state)) 105 | } 106 | 107 | /// Create a new toggle transaction 108 | pub fn toggle() -> Transaction { 109 | Transaction::new(TransactionKind::Toggle) 110 | } 111 | 112 | /// Create a new get stateful pin state transaction 113 | pub fn get_state(state: State) -> Transaction { 114 | Transaction::new(TransactionKind::GetState(state)) 115 | } 116 | 117 | /// Create a new wait_for_state transaction 118 | #[cfg(feature = "embedded-hal-async")] 119 | pub fn wait_for_state(state: State) -> Transaction { 120 | Transaction::new(TransactionKind::WaitForState(state)) 121 | } 122 | 123 | /// Create a new wait_for_state transaction, 124 | /// which returns a forever-Pending future when called. 125 | /// This simulates waiting on a pin that never changes. 126 | #[cfg(feature = "embedded-hal-async")] 127 | pub fn wait_for_state_forever(state: State) -> Transaction { 128 | Transaction::new(TransactionKind::WaitForStateForever(state)) 129 | } 130 | 131 | /// Crate a new wait_for_edge transaction 132 | #[cfg(feature = "embedded-hal-async")] 133 | pub fn wait_for_edge(edge: Edge) -> Transaction { 134 | Transaction::new(TransactionKind::WaitForEdge(edge)) 135 | } 136 | 137 | /// Crate a new wait_for_edge transaction, 138 | /// which returns a forever-Pending future when called. 139 | /// This simulates waiting on a pin that never changes. 140 | #[cfg(feature = "embedded-hal-async")] 141 | pub fn wait_for_edge_forever(edge: Edge) -> Transaction { 142 | Transaction::new(TransactionKind::WaitForEdgeForever(edge)) 143 | } 144 | 145 | /// Add an error return to a transaction 146 | /// 147 | /// This is used to mock failure behaviours. 148 | /// 149 | /// Note that this can only be used for methods which actually return a 150 | /// [`Result`]; trying to invoke this for others will lead to an assertion 151 | /// error! 152 | pub fn with_error(mut self, error: MockError) -> Self { 153 | assert!( 154 | self.kind.supports_errors(), 155 | "the transaction kind supports errors" 156 | ); 157 | self.err = Some(error); 158 | self 159 | } 160 | } 161 | 162 | /// MockPin transaction kind. 163 | #[derive(PartialEq, Eq, Clone, Debug)] 164 | pub enum TransactionKind { 165 | /// Set the pin state 166 | Set(State), 167 | /// Get the pin state 168 | Get(State), 169 | /// Toggle the pin state 170 | Toggle, 171 | /// Get the set state of the stateful pin 172 | GetState(State), 173 | /// Wait for the given pin state 174 | #[cfg(feature = "embedded-hal-async")] 175 | WaitForState(State), 176 | /// Wait for the given pin state, returning Pending to simulate a never-changing pin 177 | #[cfg(feature = "embedded-hal-async")] 178 | WaitForStateForever(State), 179 | /// Wait for the given pin edge 180 | #[cfg(feature = "embedded-hal-async")] 181 | WaitForEdge(Edge), 182 | /// Wait for the given pin edge, returning Pending to simulate a never-changing pin 183 | #[cfg(feature = "embedded-hal-async")] 184 | WaitForEdgeForever(Edge), 185 | } 186 | 187 | impl TransactionKind { 188 | fn is_get(&self) -> bool { 189 | match self { 190 | TransactionKind::Get(_) => true, 191 | _ => false, 192 | } 193 | } 194 | 195 | /// Specifies whether the actual API returns a [`Result`] (= supports errors) or not. 196 | fn supports_errors(&self) -> bool { 197 | true 198 | } 199 | } 200 | 201 | /// Mock Pin implementation 202 | pub type Mock = Generic; 203 | 204 | impl ErrorType for Mock { 205 | type Error = MockError; 206 | } 207 | 208 | /// Single digital push-pull output pin 209 | impl OutputPin for Mock { 210 | /// Drives the pin low 211 | fn set_low(&mut self) -> Result<(), Self::Error> { 212 | let Transaction { kind, err } = self.next().expect("no expectation for pin::set_low call"); 213 | 214 | assert_eq!( 215 | kind, 216 | TransactionKind::Set(State::Low), 217 | "expected pin::set_low" 218 | ); 219 | 220 | match err { 221 | Some(e) => Err(e), 222 | None => Ok(()), 223 | } 224 | } 225 | 226 | /// Drives the pin high 227 | fn set_high(&mut self) -> Result<(), Self::Error> { 228 | let Transaction { kind, err } = self.next().expect("no expectation for pin::set_high call"); 229 | 230 | assert_eq!( 231 | kind, 232 | TransactionKind::Set(State::High), 233 | "expected pin::set_high" 234 | ); 235 | 236 | match err { 237 | Some(e) => Err(e), 238 | None => Ok(()), 239 | } 240 | } 241 | } 242 | 243 | impl InputPin for Mock { 244 | /// Is the input pin high? 245 | fn is_high(&mut self) -> Result { 246 | let mut s = self.clone(); 247 | 248 | let Transaction { kind, err } = s.next().expect("no expectation for pin::is_high call"); 249 | 250 | assert!(kind.is_get(), "expected pin::get"); 251 | 252 | if let Some(e) = err { 253 | Err(e) 254 | } else if let TransactionKind::Get(v) = kind { 255 | Ok(v == State::High) 256 | } else { 257 | unreachable!(); 258 | } 259 | } 260 | 261 | /// Is the input pin low? 262 | fn is_low(&mut self) -> Result { 263 | let mut s = self.clone(); 264 | 265 | let Transaction { kind, err } = s.next().expect("no expectation for pin::is_low call"); 266 | 267 | assert!(kind.is_get(), "expected pin::get"); 268 | 269 | if let Some(e) = err { 270 | Err(e) 271 | } else if let TransactionKind::Get(v) = kind { 272 | Ok(v == State::Low) 273 | } else { 274 | unreachable!(); 275 | } 276 | } 277 | } 278 | 279 | /// Single digital output pin that remembers its state and can be toggled between high and low states 280 | impl StatefulOutputPin for Mock { 281 | /// Toggle the pin low to high or high to low 282 | fn toggle(&mut self) -> Result<(), Self::Error> { 283 | let Transaction { kind, err } = self.next().expect("no expectation for pin::toggle call"); 284 | 285 | assert_eq!(kind, TransactionKind::Toggle, "expected pin::toggle"); 286 | 287 | match err { 288 | Some(e) => Err(e), 289 | None => Ok(()), 290 | } 291 | } 292 | 293 | /// Is the output pin set high? 294 | fn is_set_high(&mut self) -> Result { 295 | let mut s = self.clone(); 296 | 297 | let Transaction { kind, err } = s.next().expect("no expectation for pin::is_set_high call"); 298 | 299 | assert!( 300 | matches!(kind, TransactionKind::GetState(_)), 301 | "expected pin::is_set_high" 302 | ); 303 | 304 | if let Some(e) = err { 305 | Err(e) 306 | } else if let TransactionKind::GetState(v) = kind { 307 | Ok(v == State::High) 308 | } else { 309 | unreachable!(); 310 | } 311 | } 312 | 313 | /// Is the output pin set low? 314 | fn is_set_low(&mut self) -> Result { 315 | let mut s = self.clone(); 316 | 317 | let Transaction { kind, err } = s.next().expect("no expectation for pin::is_set_low call"); 318 | 319 | assert!( 320 | matches!(kind, TransactionKind::GetState(_)), 321 | "expected pin::is_set_low" 322 | ); 323 | 324 | if let Some(e) = err { 325 | Err(e) 326 | } else if let TransactionKind::GetState(v) = kind { 327 | Ok(v == State::Low) 328 | } else { 329 | unreachable!(); 330 | } 331 | } 332 | } 333 | 334 | #[cfg(feature = "embedded-hal-async")] 335 | use futures::future::pending; 336 | 337 | #[cfg(feature = "embedded-hal-async")] 338 | impl embedded_hal_async::digital::Wait for Mock { 339 | /// Wait for the pin to go high 340 | async fn wait_for_high(&mut self) -> Result<(), Self::Error> { 341 | let mut s = self.clone(); 342 | 343 | let Transaction { kind, err } = s 344 | .next() 345 | .expect("no expectation for pin::wait_for_high call"); 346 | 347 | assert!( 348 | matches!( 349 | kind, 350 | TransactionKind::WaitForState(State::High) 351 | | TransactionKind::WaitForStateForever(State::High) 352 | ), 353 | "got call to wait_for_high" 354 | ); 355 | 356 | match (kind, err) { 357 | (_, Some(e)) => Err(e), 358 | (TransactionKind::WaitForState(State::High), _) => Ok(()), 359 | _ => pending().await, 360 | } 361 | } 362 | 363 | /// Wait for the pin to go low 364 | async fn wait_for_low(&mut self) -> Result<(), Self::Error> { 365 | let mut s = self.clone(); 366 | 367 | let Transaction { kind, err } = 368 | s.next().expect("no expectation for pin::wait_for_low call"); 369 | 370 | assert!( 371 | matches!( 372 | kind, 373 | TransactionKind::WaitForState(State::Low) 374 | | TransactionKind::WaitForStateForever(State::Low) 375 | ), 376 | "got call to wait_for_low" 377 | ); 378 | 379 | match (kind, err) { 380 | (_, Some(e)) => Err(e), 381 | (TransactionKind::WaitForState(State::Low), _) => Ok(()), 382 | _ => pending().await, 383 | } 384 | } 385 | 386 | /// Wait for the pin to have a rising edge 387 | async fn wait_for_rising_edge(&mut self) -> Result<(), Self::Error> { 388 | let mut s = self.clone(); 389 | 390 | let Transaction { kind, err } = s 391 | .next() 392 | .expect("no expectation for pin::wait_for_rising_edge call"); 393 | 394 | assert!( 395 | matches!( 396 | kind, 397 | TransactionKind::WaitForEdge(Edge::Rising) 398 | | TransactionKind::WaitForEdgeForever(Edge::Rising) 399 | ), 400 | "got call to wait_for_rising_edge" 401 | ); 402 | 403 | match (kind, err) { 404 | (_, Some(e)) => Err(e), 405 | (TransactionKind::WaitForEdge(Edge::Rising), _) => Ok(()), 406 | _ => pending().await, 407 | } 408 | } 409 | 410 | /// Wait for the pin to have a falling edge 411 | async fn wait_for_falling_edge(&mut self) -> Result<(), Self::Error> { 412 | let mut s = self.clone(); 413 | 414 | let Transaction { kind, err } = s 415 | .next() 416 | .expect("no expectation for pin::wait_for_falling_edge call"); 417 | 418 | assert!( 419 | matches!( 420 | kind, 421 | TransactionKind::WaitForEdge(Edge::Falling) 422 | | TransactionKind::WaitForEdgeForever(Edge::Falling) 423 | ), 424 | "got call to wait_for_falling_edge" 425 | ); 426 | 427 | match (kind, err) { 428 | (_, Some(e)) => Err(e), 429 | (TransactionKind::WaitForEdge(Edge::Falling), _) => Ok(()), 430 | _ => pending().await, 431 | } 432 | } 433 | 434 | /// Wait for the pin to have either a rising or falling edge 435 | async fn wait_for_any_edge(&mut self) -> Result<(), Self::Error> { 436 | let mut s = self.clone(); 437 | 438 | let Transaction { kind, err } = s 439 | .next() 440 | .expect("no expectation for pin::wait_for_any_edge call"); 441 | 442 | assert!( 443 | matches!( 444 | kind, 445 | TransactionKind::WaitForEdge(Edge::Any) 446 | | TransactionKind::WaitForEdgeForever(Edge::Any) 447 | ), 448 | "got call to wait_for_any_edge" 449 | ); 450 | 451 | match (kind, err) { 452 | (_, Some(e)) => Err(e), 453 | (TransactionKind::WaitForEdge(Edge::Any), _) => Ok(()), 454 | _ => pending().await, 455 | } 456 | } 457 | } 458 | 459 | #[cfg(test)] 460 | mod test { 461 | use std::io::ErrorKind; 462 | #[cfg(feature = "embedded-hal-async")] 463 | use std::time::Duration; 464 | 465 | use eh1 as embedded_hal; 466 | use embedded_hal::digital::{InputPin, OutputPin, StatefulOutputPin}; 467 | #[cfg(feature = "embedded-hal-async")] 468 | use tokio::time::timeout; 469 | 470 | use super::{ 471 | super::error::MockError, 472 | TransactionKind::{Get, GetState, Set, Toggle}, 473 | *, 474 | }; 475 | 476 | #[test] 477 | fn test_input_pin() { 478 | let expectations = [ 479 | Transaction::new(Get(State::High)), 480 | Transaction::new(Get(State::High)), 481 | Transaction::new(Get(State::Low)), 482 | Transaction::new(Get(State::Low)), 483 | Transaction::new(Get(State::High)).with_error(MockError::Io(ErrorKind::NotConnected)), 484 | ]; 485 | let mut pin = Mock::new(&expectations); 486 | 487 | assert_eq!(pin.is_high().unwrap(), true); 488 | assert_eq!(pin.is_low().unwrap(), false); 489 | assert_eq!(pin.is_high().unwrap(), false); 490 | assert_eq!(pin.is_low().unwrap(), true); 491 | 492 | pin.is_low().expect_err("expected error return"); 493 | 494 | pin.done(); 495 | } 496 | 497 | #[test] 498 | fn test_output_pin() { 499 | let expectations = [ 500 | Transaction::new(Set(State::High)), 501 | Transaction::new(Set(State::Low)), 502 | Transaction::new(Set(State::High)).with_error(MockError::Io(ErrorKind::NotConnected)), 503 | ]; 504 | let mut pin = Mock::new(&expectations); 505 | 506 | pin.set_high().unwrap(); 507 | pin.set_low().unwrap(); 508 | 509 | pin.set_high().expect_err("expected error return"); 510 | 511 | pin.done(); 512 | } 513 | 514 | #[test] 515 | fn test_stateful_output_pin() { 516 | let expectations = [ 517 | Transaction::new(GetState(State::Low)), 518 | Transaction::get_state(State::Low), 519 | Transaction::new(Toggle), 520 | Transaction::get_state(State::High), 521 | Transaction::get_state(State::High), 522 | Transaction::toggle(), 523 | Transaction::get_state(State::Low).with_error(MockError::Io(ErrorKind::NotConnected)), 524 | Transaction::toggle().with_error(MockError::Io(ErrorKind::NotConnected)), 525 | ]; 526 | let mut pin = Mock::new(&expectations); 527 | 528 | assert!(pin.is_set_low().unwrap()); 529 | assert!(!pin.is_set_high().unwrap()); 530 | pin.toggle().unwrap(); 531 | assert!(pin.is_set_high().unwrap()); 532 | assert!(!pin.is_set_low().unwrap()); 533 | pin.toggle().unwrap(); 534 | 535 | pin.is_set_low() 536 | .expect_err("expected an error when getting state"); 537 | pin.toggle() 538 | .expect_err("expected an error when toggling state"); 539 | 540 | pin.done(); 541 | } 542 | 543 | #[tokio::test] 544 | #[cfg(feature = "embedded-hal-async")] 545 | async fn test_can_wait_for_state() { 546 | use embedded_hal_async::digital::Wait; 547 | 548 | let expectations = [ 549 | Transaction::new(TransactionKind::WaitForState(State::High)), 550 | Transaction::new(TransactionKind::WaitForState(State::Low)), 551 | Transaction::new(TransactionKind::WaitForState(State::High)) 552 | .with_error(MockError::Io(ErrorKind::NotConnected)), 553 | ]; 554 | let mut pin = Mock::new(&expectations); 555 | 556 | pin.wait_for_high().await.unwrap(); 557 | pin.wait_for_low().await.unwrap(); 558 | 559 | pin.wait_for_high() 560 | .await 561 | .expect_err("expected error return"); 562 | 563 | pin.done(); 564 | } 565 | 566 | #[tokio::test] 567 | #[should_panic(expected = "got call to wait_for_high")] 568 | #[cfg(feature = "embedded-hal-async")] 569 | async fn test_wait_for_wrong_state() { 570 | use embedded_hal_async::digital::Wait; 571 | 572 | let expectations = [Transaction::wait_for_state(State::Low)]; 573 | let mut pin = Mock::new(&expectations); 574 | 575 | pin.wait_for_high().await.unwrap(); 576 | 577 | pin.done(); 578 | } 579 | 580 | #[tokio::test] 581 | #[cfg(feature = "embedded-hal-async")] 582 | async fn test_can_wait_for_state_forever() { 583 | use embedded_hal_async::digital::Wait; 584 | 585 | let expectations = [ 586 | Transaction::new(TransactionKind::WaitForStateForever(State::High)), 587 | Transaction::new(TransactionKind::WaitForStateForever(State::Low)), 588 | Transaction::new(TransactionKind::WaitForStateForever(State::High)) 589 | .with_error(MockError::Io(ErrorKind::NotConnected)), 590 | ]; 591 | let mut pin = Mock::new(&expectations); 592 | 593 | // we should not return Ok while asking to wait forever! 594 | timeout(Duration::from_millis(10), pin.wait_for_high()) 595 | .await 596 | .expect_err("expected wait_for_high timeout"); 597 | timeout(Duration::from_millis(10), pin.wait_for_low()) 598 | .await 599 | .expect_err("expected wait_for_low timeout"); 600 | 601 | // errors are still returned, since maybe awaiting on the pin failed for some reason 602 | pin.wait_for_high() 603 | .await 604 | .expect_err("expected error return"); 605 | 606 | pin.done(); 607 | } 608 | 609 | #[tokio::test] 610 | #[cfg(feature = "embedded-hal-async")] 611 | async fn test_can_wait_for_edge() { 612 | use embedded_hal_async::digital::Wait; 613 | 614 | let expectations = [ 615 | Transaction::new(TransactionKind::WaitForEdge(Edge::Rising)), 616 | Transaction::new(TransactionKind::WaitForEdge(Edge::Falling)), 617 | Transaction::new(TransactionKind::WaitForEdge(Edge::Any)), 618 | Transaction::new(TransactionKind::WaitForEdge(Edge::Rising)) 619 | .with_error(MockError::Io(ErrorKind::NotConnected)), 620 | ]; 621 | let mut pin = Mock::new(&expectations); 622 | 623 | pin.wait_for_rising_edge().await.unwrap(); 624 | pin.wait_for_falling_edge().await.unwrap(); 625 | pin.wait_for_any_edge().await.unwrap(); 626 | 627 | pin.wait_for_rising_edge() 628 | .await 629 | .expect_err("expected error return"); 630 | 631 | pin.done(); 632 | } 633 | 634 | #[tokio::test] 635 | #[cfg(feature = "embedded-hal-async")] 636 | async fn test_can_wait_for_edge_forever() { 637 | use embedded_hal_async::digital::Wait; 638 | 639 | let expectations = [ 640 | Transaction::new(TransactionKind::WaitForEdgeForever(Edge::Rising)), 641 | Transaction::new(TransactionKind::WaitForEdgeForever(Edge::Falling)), 642 | Transaction::new(TransactionKind::WaitForEdgeForever(Edge::Any)), 643 | Transaction::new(TransactionKind::WaitForEdgeForever(Edge::Rising)) 644 | .with_error(MockError::Io(ErrorKind::NotConnected)), 645 | ]; 646 | let mut pin = Mock::new(&expectations); 647 | 648 | // we should not return Ok while asking to wait forever! 649 | timeout(Duration::from_millis(10), pin.wait_for_rising_edge()) 650 | .await 651 | .expect_err("expected wait_for_rising_edge timeout"); 652 | timeout(Duration::from_millis(10), pin.wait_for_falling_edge()) 653 | .await 654 | .expect_err("expected wait_for_falling_edge timeout"); 655 | timeout(Duration::from_millis(10), pin.wait_for_any_edge()) 656 | .await 657 | .expect_err("expected wait_for_any_edge timeout"); 658 | 659 | // errors are still returned, since maybe awaiting on the pin failed for some reason 660 | pin.wait_for_rising_edge() 661 | .await 662 | .expect_err("expected error return"); 663 | 664 | pin.done(); 665 | } 666 | 667 | #[tokio::test] 668 | #[should_panic(expected = "got call to wait_for_rising_edge")] 669 | #[cfg(feature = "embedded-hal-async")] 670 | async fn test_wait_for_wrong_edge() { 671 | use embedded_hal_async::digital::Wait; 672 | 673 | let expectations = [Transaction::wait_for_edge(Edge::Falling)]; 674 | let mut pin = Mock::new(&expectations); 675 | 676 | pin.wait_for_rising_edge().await.unwrap(); 677 | 678 | pin.done(); 679 | } 680 | } 681 | --------------------------------------------------------------------------------