From d435fd7b066fa33835e2b090bb21d35ccc6c12ce Mon Sep 17 00:00:00 2001 From: Ralph Ursprung <39383228+rursprung@users.noreply.github.com> Date: Fri, 6 Jan 2023 23:40:36 +0100 Subject: [PATCH] Add `PwmPin` mock (#52) this extends the `Pin::Mock` to also cover the [`PwmPin`] trait. some observations on this commit: * i took the liberty of just hardcoding the `PwmPin::Duty` to be an `u16` (wrapped in the `PwmDuty` type because it's used in other places here in the module as well). i don't see an easy way to make this configurable because i'd have to add it as a generic attribute to `Pin` and then it'd have to be defined by everyone using it (unnecessary for all non-PWM use-cases). but i think this should be acceptable as `u16` probably covers most/all use-cases. * the current code features quite some code duplication (essentially the method check implementations are all the same for all setters / all getters). i've continued this for now but it might be worth a refactoring in the future (i haven't touched it because i'm not sure if there's some strategic decision behind this?) * there's a `TransactionKind::is_get` API for which i don't really see the reason (why not just check it directly?). i've decided to not copy that approach for the others as it'd IMHO just bloat the code and i'd instead suggest to remove `TransactionKind::is_get` at a later point. * `PwmPin` only exists on `embedded-hal` 0.x, it is currently missing from the planned 1.0 release. see rust-embedded/embedded-hal#358 for the discussion on this. once it has (hopefully) been re-added there a corresponding mock can be provided here also for 1.x. [`PwmPin`]: https://docs.rs/embedded-hal/0.2.7/embedded_hal/trait.PwmPin.html --- src/pin.rs | 138 +++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 134 insertions(+), 4 deletions(-) diff --git a/src/pin.rs b/src/pin.rs index 9b35e76..e954bb8 100644 --- a/src/pin.rs +++ b/src/pin.rs @@ -43,6 +43,10 @@ use crate::common::Generic; use crate::error::MockError; use embedded_hal::digital::v2::{InputPin, OutputPin}; +use embedded_hal::PwmPin; + +/// The type used for the duty of the [`PwmPin`] mock. +pub type PwmDuty = u16; /// MockPin transaction #[derive(PartialEq, Clone, Debug)] @@ -80,10 +84,42 @@ impl Transaction { Transaction::new(TransactionKind::Set(state)) } + /// Create a new disable transaction + pub fn disable() -> Transaction { + Transaction::new(TransactionKind::Disable) + } + + /// Create a new enable transaction + pub fn enable() -> Transaction { + Transaction::new(TransactionKind::Enable) + } + + /// Create a new get_duty transaction + pub fn get_duty(duty: PwmDuty) -> Transaction { + Transaction::new(TransactionKind::GetDuty(duty)) + } + + /// Create a new get_max_duty transaction + pub fn get_max_duty(max_duty: PwmDuty) -> Transaction { + Transaction::new(TransactionKind::GetMaxDuty(max_duty)) + } + + /// Create a new set_duty transaction + pub fn set_duty(expected_duty: PwmDuty) -> Transaction { + Transaction::new(TransactionKind::SetDuty(expected_duty)) + } + /// Add an error return to a transaction /// /// This is used to mock failure behaviours. + /// + /// Note that this can only be used for methods which actually return a [`Result`]; + /// trying to invoke this for others will lead to an assertion error! pub fn with_error(mut self, error: MockError) -> Self { + assert!( + self.kind.supports_errors(), + "the transaction kind supports errors" + ); self.err = Some(error); self } @@ -92,10 +128,20 @@ impl Transaction { /// MockPin transaction kind, either Get or Set with the associated State #[derive(PartialEq, Clone, Debug)] pub enum TransactionKind { - /// Set(true) for set_high or Set(false) for set_low + /// `Set(true)` for [`OutputPin::set_high`] or `Set(false)` for [`OutputPin::set_low`] Set(State), - /// Get(true) for high value or Get(false) for low value + /// `Get(true)` for high value or `Get(false)` for low value. To be used with [`InputPin::is_high`] and [`InputPin::is_low`]. Get(State), + /// Disable a [`PwmPin`] using [`PwmPin::disable`] + Disable, + /// Enable a [`PwmPin`] using [`PwmPin::enable`] + Enable, + /// Query the duty of a [`PwmPin`] using [`PwmPin::get_duty`], returning the specified value + GetDuty(PwmDuty), + /// Query the max. duty of a [`PwmPin`] using [`PwmPin::get_max_duty`], returning the specified value + GetMaxDuty(PwmDuty), + /// Set the duty of a [`PwmPin`] using [`PwmPin::set_duty`], expecting the specified value + SetDuty(PwmDuty), } impl TransactionKind { @@ -105,6 +151,14 @@ impl TransactionKind { _ => false, } } + + /// Specifies whether the actual API returns a [`Result`] (= supports errors) or not. + fn supports_errors(&self) -> bool { + match self { + TransactionKind::Set(_) | TransactionKind::Get(_) => true, + _ => false, + } + } } /// Mock Pin implementation @@ -187,15 +241,70 @@ impl InputPin for Mock { } } +impl PwmPin for Mock { + type Duty = PwmDuty; + + fn disable(&mut self) { + // Note: Error is being ignored, because method doesn't return a result + let Transaction { kind, .. } = self.next().expect("no expectation for pin::disable call"); + + assert_eq!(kind, TransactionKind::Disable, "expected pin::disable"); + } + + fn enable(&mut self) { + // Note: Error is being ignored, because method doesn't return a result + let Transaction { kind, .. } = self.next().expect("no expectation for pin::enable call"); + + assert_eq!(kind, TransactionKind::Enable, "expected pin::enable"); + } + + fn get_duty(&self) -> Self::Duty { + let mut s = self.clone(); + + // Note: Error is being ignored, because method doesn't return a result + let Transaction { kind, .. } = s.next().expect("no expectation for pin::get_duty call"); + + if let TransactionKind::GetDuty(duty) = kind { + duty + } else { + panic!("expected pin::get_duty"); + } + } + + fn get_max_duty(&self) -> Self::Duty { + let mut s = self.clone(); + + // Note: Error is being ignored, because method doesn't return a result + let Transaction { kind, .. } = s.next().expect("no expectation for pin::get_max_duty call"); + + if let TransactionKind::GetMaxDuty(max_duty) = kind { + max_duty + } else { + panic!("expected pin::get_max_duty"); + } + } + + fn set_duty(&mut self, duty: Self::Duty) { + // Note: Error is being ignored, because method doesn't return a result + let Transaction { kind, .. } = self.next().expect("no expectation for pin::set_duty call"); + + assert_eq!( + kind, + TransactionKind::SetDuty(duty), + "expected pin::set_duty" + ); + } +} + #[cfg(test)] mod test { - use std::io::ErrorKind; use crate::error::MockError; use embedded_hal::digital::v2::{InputPin, OutputPin}; + use embedded_hal::PwmPin; - use crate::pin::TransactionKind::{Get, Set}; + use crate::pin::TransactionKind::{Disable, Enable, Get, GetDuty, GetMaxDuty, Set, SetDuty}; use crate::pin::{Mock, State, Transaction}; #[test] @@ -235,4 +344,25 @@ mod test { pin.done(); } + + #[test] + fn test_pwm_pin() { + let expected_duty = 10_000; + let expectations = [ + Transaction::new(Enable), + Transaction::new(GetMaxDuty(expected_duty)), + Transaction::new(SetDuty(expected_duty)), + Transaction::new(GetDuty(expected_duty)), + Transaction::new(Disable), + ]; + let mut pin = Mock::new(&expectations); + + pin.enable(); + let max_duty = pin.get_max_duty(); + pin.set_duty(max_duty); + assert_eq!(pin.get_duty(), expected_duty); + pin.disable(); + + pin.done(); + } }