Skip to content

Commit

Permalink
Add PwmPin mock (#52)
Browse files Browse the repository at this point in the history
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
  • Loading branch information
rursprung authored Jan 6, 2023
1 parent 3f93a2a commit d435fd7
Showing 1 changed file with 134 additions and 4 deletions.
138 changes: 134 additions & 4 deletions src/pin.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)]
Expand Down Expand Up @@ -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
}
Expand All @@ -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 {
Expand All @@ -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
Expand Down Expand Up @@ -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]
Expand Down Expand Up @@ -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();
}
}

0 comments on commit d435fd7

Please sign in to comment.