Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/check.yml
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ concurrency:
name: check
env:
# Crates that require std and won't build on embedded-targets
STD_EXCLUDED_CRATES: "--exclude fw-update-interface-mocks --exclude type-c-interface-test-mocks"
STD_EXCLUDED_CRATES: "--exclude fw-update-interface-mocks --exclude type-c-interface-test-mocks --exclude power-policy-interface-test-mocks"
jobs:

fmt:
Expand Down
11 changes: 11 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 5 additions & 3 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ members = [
"fw-update-interface-mocks",
"mctp-rs",
"type-c-interface-test-mocks",
"power-policy-interface-test-mocks",
]
exclude = ["examples/*"]

Expand Down Expand Up @@ -82,12 +83,12 @@ embedded-hal-nb = "1.0"
embedded-io-async = "0.7.0"
embedded-mcu-hal = "0.3.0"
embassy-futures = "0.1.2"
embassy-imxrt = { git = "https://github.com/OpenDevicePartnership/embassy-imxrt"}
embassy-imxrt = { git = "https://github.com/OpenDevicePartnership/embassy-imxrt" }
embassy-sync = "0.8"
embassy-time = "0.5.1"
embassy-time-driver = "0.2.2"
embedded-batteries-async = "0.3"
embedded-cfu-protocol = { git = "https://github.com/OpenDevicePartnership/embedded-cfu"}
embedded-cfu-protocol = { git = "https://github.com/OpenDevicePartnership/embedded-cfu" }
embedded-hal = "1.0"
embedded-hal-async = "1.0"
embedded-services = { path = "./embedded-service" }
Expand All @@ -99,6 +100,7 @@ mctp-rs = { path = "./mctp-rs" }
num_enum = { version = "0.7.5", default-features = false }
portable-atomic = { version = "1.11", default-features = false }
power-policy-interface = { path = "./power-policy-interface" }
power-policy-interface-test-mocks = { path = "./power-policy-interface-test-mocks" }
paste = "1.0.15"
power-policy-service = { path = "./power-policy-service" }
fixed = "1.23.1"
Expand All @@ -117,7 +119,7 @@ time-alarm-service-relay = { path = "./time-alarm-service-relay" }
type-c-interface = { path = "./type-c-interface" }
type-c-interface-test-mocks = { path = "./type-c-interface-test-mocks" }
syn = "2.0"
tps6699x = { git = "https://github.com/OpenDevicePartnership/tps6699x"}
tps6699x = { git = "https://github.com/OpenDevicePartnership/tps6699x" }
tokio = { version = "1.42.0" }
uuid = { version = "=1.17.0", default-features = false }
zerocopy = "0.8"
16 changes: 16 additions & 0 deletions power-policy-interface-test-mocks/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
[package]
name = "power-policy-interface-test-mocks"
version.workspace = true
edition.workspace = true
rust-version.workspace = true
license.workspace = true
repository.workspace = true

[dependencies]
embassy-sync.workspace = true
embedded-batteries-async.workspace = true
embedded-services = { workspace = true }
power-policy-interface = { workspace = true }

[lints]
workspace = true
135 changes: 135 additions & 0 deletions power-policy-interface-test-mocks/src/charger.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
//! Charger mock implementation for testing

use std::collections::VecDeque;

use embassy_sync::mutex::Mutex;
use embedded_batteries_async::charger::{MilliAmps, MilliVolts};
use embedded_services::{GlobalRawMutex, event::NonBlockingSender};
use power_policy_interface::{capability::ConsumerPowerCapability, charger};

/// Contains a charger function call and its arguments
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum FnCall {
InitCharger,
AttachHandler(ConsumerPowerCapability),
DetachHandler,
IsReady,
ChargingCurrent(MilliAmps),
ChargingVoltage(MilliVolts),
}

/// Mock charger for use in tests
pub struct Mock<S: NonBlockingSender<charger::event::EventData>> {
sender: S,
state: charger::State,
/// Recorded function calls
pub fn_calls: VecDeque<FnCall>,
/// Next results to return for [`charger::Charger::init_charger`]
pub next_result_init_charger: VecDeque<Result<charger::PsuState, core::convert::Infallible>>,
/// Next results to return for [`charger::Charger::attach_handler`]
pub next_result_attach_handler: VecDeque<Result<(), core::convert::Infallible>>,
/// Next results to return for [`charger::Charger::detach_handler`]
pub next_result_detach_handler: VecDeque<Result<(), core::convert::Infallible>>,
/// Next results to return for [`charger::Charger::is_ready`]
pub next_result_is_ready: VecDeque<Result<(), core::convert::Infallible>>,
/// Next results to return for [`embedded_batteries_async::charger::Charger::charging_current`]
pub next_result_charging_current: VecDeque<Result<MilliAmps, core::convert::Infallible>>,
/// Next results to return for [`embedded_batteries_async::charger::Charger::charging_voltage`]
pub next_result_charging_voltage: VecDeque<Result<MilliVolts, core::convert::Infallible>>,
}

impl<S: NonBlockingSender<charger::event::EventData>> Mock<S> {
pub fn new(sender: S) -> Self {
Self {
sender,
state: charger::State::default(),
fn_calls: VecDeque::new(),
next_result_init_charger: VecDeque::new(),
next_result_attach_handler: VecDeque::new(),
next_result_detach_handler: VecDeque::new(),
next_result_is_ready: VecDeque::new(),
next_result_charging_current: VecDeque::new(),
next_result_charging_voltage: VecDeque::new(),
}
}

pub fn assert_state(&self, internal_state: charger::InternalState, capability: Option<ConsumerPowerCapability>) {
assert_eq!(*self.state.internal_state(), internal_state);
assert_eq!(*self.state.capability(), capability);
}

pub async fn simulate_psu_state_change(&mut self, psu_state: charger::PsuState) {
self.sender
.try_send(charger::EventData::PsuStateChange(psu_state))
.unwrap();
}
}

impl<S: NonBlockingSender<charger::event::EventData>> embedded_batteries_async::charger::ErrorType for Mock<S> {
type Error = core::convert::Infallible;
}

impl<S: NonBlockingSender<charger::event::EventData>> embedded_batteries_async::charger::Charger for Mock<S> {
async fn charging_current(&mut self, current: MilliAmps) -> Result<MilliAmps, Self::Error> {
self.fn_calls.push_back(FnCall::ChargingCurrent(current));
self.next_result_charging_current
.pop_front()
.expect("next_result_charging_current not set")
}

async fn charging_voltage(&mut self, voltage: MilliVolts) -> Result<MilliVolts, Self::Error> {
self.fn_calls.push_back(FnCall::ChargingVoltage(voltage));
self.next_result_charging_voltage
.pop_front()
.expect("next_result_charging_voltage not set")
}
}

impl<S: NonBlockingSender<charger::event::EventData>> charger::Charger for Mock<S> {
type ChargerError = core::convert::Infallible;

async fn init_charger(&mut self) -> Result<charger::PsuState, Self::ChargerError> {
self.fn_calls.push_back(FnCall::InitCharger);
self.next_result_init_charger
.pop_front()
.expect("next_result_init_charger not set")
}

fn attach_handler(
&mut self,
capability: ConsumerPowerCapability,
) -> impl Future<Output = Result<(), Self::ChargerError>> {
self.fn_calls.push_back(FnCall::AttachHandler(capability));
let result = self
.next_result_attach_handler
.pop_front()
.expect("next_result_attach_handler not set");
async move { result }
}

fn detach_handler(&mut self) -> impl Future<Output = Result<(), Self::ChargerError>> {
self.fn_calls.push_back(FnCall::DetachHandler);
let result = self
.next_result_detach_handler
.pop_front()
.expect("next_result_detach_handler not set");
async move { result }
}

async fn is_ready(&mut self) -> Result<(), Self::ChargerError> {
self.fn_calls.push_back(FnCall::IsReady);
self.next_result_is_ready
.pop_front()
.expect("next_result_is_ready not set")
}

fn state(&self) -> &charger::State {
&self.state
}

fn state_mut(&mut self) -> &mut charger::State {
&mut self.state
}
}

pub type ChargerType<S> = Mutex<GlobalRawMutex, Mock<S>>;
5 changes: 5 additions & 0 deletions power-policy-interface-test-mocks/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
#![allow(clippy::unwrap_used)]
#![allow(clippy::expect_used)]

pub mod charger;
pub mod psu;
157 changes: 157 additions & 0 deletions power-policy-interface-test-mocks/src/psu.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
//! PSU mock implementation for testing

use std::collections::VecDeque;

use embedded_services::{event::NonBlockingSender, named::Named};
use power_policy_interface::{
capability::{
ConsumerDisconnect, ConsumerPowerCapability, PowerCapability, ProviderFlags, ProviderPowerCapability,
},
psu::{Error, Psu, State, event::EventData},
};

/// Contains a PSU function call and its arguments
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum FnCall {
ConnectConsumer(ConsumerPowerCapability),
ConnectProvider(ProviderPowerCapability),
Disconnect,
}

/// Mock PSU for use in tests
pub struct Mock<S: NonBlockingSender<EventData>> {
sender: S,
name: &'static str,
pub state: State,
/// Recorded function calls
pub fn_calls: VecDeque<FnCall>,
/// Next results to return for [`Psu::connect_consumer`]
pub next_result_connect_consumer: VecDeque<Result<(), Error>>,
/// Next results to return for [`Psu::connect_provider`]
pub next_result_connect_provider: VecDeque<Result<(), Error>>,
/// Next results to return for [`Psu::disconnect`]
pub next_result_disconnect: VecDeque<Result<(), Error>>,
}

impl<S: NonBlockingSender<EventData>> Mock<S> {
pub fn new(name: &'static str, sender: S) -> Self {
Self {
name,
sender,
state: Default::default(),
fn_calls: VecDeque::new(),
next_result_connect_consumer: VecDeque::new(),
next_result_connect_provider: VecDeque::new(),
next_result_disconnect: VecDeque::new(),
}
}

pub async fn simulate_consumer_connection(&mut self, capability: ConsumerPowerCapability) {
self.state.attach().unwrap();
self.sender.try_send(EventData::Attached).unwrap();
self.state.update_consumer_power_capability(Some(capability)).unwrap();
self.sender
.try_send(EventData::UpdatedConsumerCapability(Some(capability)))
.unwrap();
}

/// Simulate an already-attached consumer renegotiating a new power capability.
pub async fn simulate_update_consumer_power_capability(&mut self, capability: Option<ConsumerPowerCapability>) {
self.state.update_consumer_power_capability(capability).unwrap();
self.sender
.try_send(EventData::UpdatedConsumerCapability(capability))
.unwrap();
}

pub async fn simulate_detach(&mut self) {
self.state.detach();
self.sender.try_send(EventData::Detached).unwrap();
}

pub async fn simulate_provider_connection(&mut self, capability: PowerCapability) {
self.state.attach().unwrap();
self.sender.try_send(EventData::Attached).unwrap();

let capability = Some(ProviderPowerCapability {
capability,
flags: ProviderFlags::none(),
});
self.state
.update_requested_provider_power_capability(capability)
.unwrap();
self.sender
.try_send(EventData::RequestedProviderCapability(capability))
.unwrap();
}

pub async fn simulate_disconnect(&mut self) {
self.state.disconnect(true).unwrap();
self.sender
.try_send(EventData::Disconnected(ConsumerDisconnect::none()))
.unwrap();
}

pub async fn simulate_update_requested_provider_power_capability(
&mut self,
capability: Option<ProviderPowerCapability>,
) {
self.state
.update_requested_provider_power_capability(capability)
.unwrap();
self.sender
.try_send(EventData::RequestedProviderCapability(capability))
.unwrap();
}
}

impl<S: NonBlockingSender<EventData>> Psu for Mock<S> {
async fn connect_consumer(&mut self, capability: ConsumerPowerCapability) -> Result<(), Error> {
self.fn_calls.push_back(FnCall::ConnectConsumer(capability));
let result = self
.next_result_connect_consumer
.pop_front()
.expect("next_result_connect_consumer not set");
if result.is_ok() {
self.state.connect_consumer(capability).unwrap();
}
result
}

async fn connect_provider(&mut self, capability: ProviderPowerCapability) -> Result<(), Error> {
self.fn_calls.push_back(FnCall::ConnectProvider(capability));
let result = self
.next_result_connect_provider
.pop_front()
.expect("next_result_connect_provider not set");
if result.is_ok() {
self.state.connect_provider(capability).unwrap();
}
result
}

async fn disconnect(&mut self) -> Result<(), Error> {
self.fn_calls.push_back(FnCall::Disconnect);
let result = self
.next_result_disconnect
.pop_front()
.expect("next_result_disconnect not set");
if result.is_ok() {
self.state.disconnect(false).unwrap();
}
result
}

fn state(&self) -> &State {
&self.state
}

fn state_mut(&mut self) -> &mut State {
&mut self.state
}
}

impl<S: NonBlockingSender<EventData>> Named for Mock<S> {
fn name(&self) -> &'static str {
self.name
}
}
1 change: 1 addition & 0 deletions power-policy-service/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ tokio = { workspace = true, features = ["rt", "macros", "time"] }
env_logger = "0.11.8"
log = { workspace = true }
embedded-batteries-async = { workspace = true }
power-policy-interface-test-mocks = { workspace = true }
# TODO: figure out why enabling the log feature here causes running tests at the workspace level to fail to compile
# Uncomment this line to enable log output in tests
# power-policy-service = { workspace = true, features = ["log"] }
Expand Down
Loading
Loading