From 53a30046d1d3504964f8f6cff8b8201dbeae09c0 Mon Sep 17 00:00:00 2001 From: Andrew Houts <16907671+ahouts@users.noreply.github.com> Date: Tue, 29 Jun 2021 18:46:41 -0500 Subject: test(battery): add battery tests (#2795) Add some tests to the battery module, make it testable by mocking out the code that fetches battery info. --- Cargo.lock | 96 ++++++++++++++ Cargo.toml | 1 + src/context.rs | 5 + src/modules/battery.rs | 350 +++++++++++++++++++++++++++++++++++++++++++------ src/modules/mod.rs | 3 + src/test/mod.rs | 9 ++ 6 files changed, 423 insertions(+), 41 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index c415492f2..f290d7a46 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -368,6 +368,12 @@ dependencies = [ "syn 1.0.72", ] +[[package]] +name = "difference" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "524cbf6897b527295dff137cec09ecf3a05f4fddffd7dfcd1585403449e74198" + [[package]] name = "digest" version = "0.8.1" @@ -418,6 +424,12 @@ dependencies = [ "rand", ] +[[package]] +name = "downcast" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4bb454f0228b18c7f4c3b0ebbee346ed9c52e7443b0999cd543ff3571205701d" + [[package]] name = "dtoa" version = "0.4.8" @@ -466,6 +478,15 @@ dependencies = [ "instant", ] +[[package]] +name = "float-cmp" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1267f4ac4f343772758f7b1bdcbe767c218bbab93bb432acbf5162bbf85a6c4" +dependencies = [ + "num-traits", +] + [[package]] name = "fnv" version = "1.0.7" @@ -497,6 +518,12 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "fragile" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69a039c3498dc930fe810151a34ba0c1c70b02b8625035592e74432f678591f2" + [[package]] name = "funty" version = "1.1.0" @@ -877,6 +904,33 @@ dependencies = [ "autocfg", ] +[[package]] +name = "mockall" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18d614ad23f9bb59119b8b5670a85c7ba92c5e9adf4385c81ea00c51c8be33d5" +dependencies = [ + "cfg-if 1.0.0", + "downcast", + "fragile", + "lazy_static", + "mockall_derive", + "predicates", + "predicates-tree", +] + +[[package]] +name = "mockall_derive" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5dd4234635bca06fc96c7368d038061e0aae1b00a764dc817e900dc974e3deea" +dependencies = [ + "cfg-if 1.0.0", + "proc-macro2", + "quote 1.0.9", + "syn 1.0.72", +] + [[package]] name = "native-tls" version = "0.2.7" @@ -956,6 +1010,12 @@ dependencies = [ "version_check", ] +[[package]] +name = "normalize-line-endings" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61807f77802ff30975e01f4f071c8ba10c022052f98b3294119f3e615d13e5be" + [[package]] name = "notify-rust" version = "4.5.2" @@ -1212,6 +1272,35 @@ version = "0.2.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac74c624d6b2d21f425f752262f42188365d7b8ff1aff74c82e45136510a4857" +[[package]] +name = "predicates" +version = "1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f49cfaf7fdaa3bfacc6fa3e7054e65148878354a5cfddcf661df4c851f8021df" +dependencies = [ + "difference", + "float-cmp", + "normalize-line-endings", + "predicates-core", + "regex", +] + +[[package]] +name = "predicates-core" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57e35a3326b75e49aa85f5dc6ec15b41108cf5aee58eabb1f274dd18b73c2451" + +[[package]] +name = "predicates-tree" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15f553275e5721409451eb85e15fd9a860a6e5ab4496eb215987502b5f5391f2" +dependencies = [ + "predicates-core", + "treeline", +] + [[package]] name = "proc-macro-crate" version = "0.1.5" @@ -1602,6 +1691,7 @@ dependencies = [ "git2", "indexmap", "log", + "mockall", "native-tls", "nix 0.21.0", "notify-rust", @@ -1796,6 +1886,12 @@ dependencies = [ "serde", ] +[[package]] +name = "treeline" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7f741b240f1a48843f9b8e0444fb55fb2a4ff67293b50a9179dfd5ea67f8d41" + [[package]] name = "typenum" version = "1.13.0" diff --git a/Cargo.toml b/Cargo.toml index b745fcb58..c625b76a1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -94,6 +94,7 @@ shadow-rs = "0.6.2" [dev-dependencies] tempfile = "3.2.0" +mockall = "0.9" [profile.release] codegen-units = 1 diff --git a/src/context.rs b/src/context.rs index 451ce53c0..9c3a88dff 100644 --- a/src/context.rs +++ b/src/context.rs @@ -50,6 +50,9 @@ pub struct Context<'a> { #[cfg(test)] pub cmd: HashMap<&'a str, Option>, + #[cfg(feature = "battery")] + pub battery_info_provider: &'a (dyn crate::modules::BatteryInfoProvider + Send + Sync), + /// Timeout for the execution of commands cmd_timeout: Duration, } @@ -122,6 +125,8 @@ impl<'a> Context<'a> { env: HashMap::new(), #[cfg(test)] cmd: HashMap::new(), + #[cfg(feature = "battery")] + battery_info_provider: &crate::modules::BatteryStatusProviderImpl, cmd_timeout, } } diff --git a/src/modules/battery.rs b/src/modules/battery.rs index b47677294..630d5faa7 100644 --- a/src/modules/battery.rs +++ b/src/modules/battery.rs @@ -1,5 +1,7 @@ use super::{Context, Module, RootModuleConfig, Shell}; use crate::configs::battery::BatteryConfig; +#[cfg(test)] +use mockall::automock; use crate::formatter::StringFormatter; @@ -12,7 +14,7 @@ pub fn module<'a>(context: &'a Context) -> Option> { _ => "%", }; - let battery_status = get_battery_status()?; + let battery_status = get_battery_status(context)?; let BatteryStatus { state, percentage } = battery_status; let mut module = context.new_module("battery"); @@ -75,46 +77,12 @@ pub fn module<'a>(context: &'a Context) -> Option> { } } -fn get_battery_status() -> Option { - let battery_manager = battery::Manager::new().ok()?; - let batteries = battery_manager.batteries().ok()?; - let battery_contructor = batteries - .filter_map(|battery| match battery { - Ok(battery) => { - log::debug!("Battery found: {:?}", battery); - Some(BatteryInfo { - energy: battery.energy().value, - energy_full: battery.energy_full().value, - state: battery.state(), - }) - } - Err(e) => { - let level = if cfg!(target_os = "linux") { - log::Level::Info - } else { - log::Level::Warn - }; - log::log!(level, "Unable to access battery information:\n{}", &e); - None - } - }) - .fold( - BatteryInfo { - energy: 0.0, - energy_full: 0.0, - state: battery::State::Unknown, - }, - |mut acc, x| { - acc.energy += x.energy; - acc.energy_full += x.energy_full; - acc.state = merge_battery_states(acc.state, x.state); - acc - }, - ); - if battery_contructor.energy_full != 0.0 { +fn get_battery_status(context: &Context) -> Option { + let battery_info = context.battery_info_provider.get_battery_info()?; + if battery_info.energy_full != 0.0 { let battery = BatteryStatus { - percentage: battery_contructor.energy / battery_contructor.energy_full * 100.0, - state: battery_contructor.state, + percentage: battery_info.energy / battery_info.energy_full * 100.0, + state: battery_info.state, }; log::debug!("Battery status: {:?}", battery); Some(battery) @@ -145,7 +113,7 @@ fn merge_battery_states(state1: battery::State, state2: battery::State) -> batte } } -struct BatteryInfo { +pub struct BatteryInfo { energy: f32, energy_full: f32, state: battery::State, @@ -156,3 +124,303 @@ struct BatteryStatus { percentage: f32, state: battery::State, } + +#[cfg_attr(test, automock)] +pub trait BatteryInfoProvider { + fn get_battery_info(&self) -> Option; +} + +pub struct BatteryStatusProviderImpl; + +impl BatteryInfoProvider for BatteryStatusProviderImpl { + fn get_battery_info(&self) -> Option { + let battery_manager = battery::Manager::new().ok()?; + let batteries = battery_manager.batteries().ok()?; + Some( + batteries + .filter_map(|battery| match battery { + Ok(battery) => { + log::debug!("Battery found: {:?}", battery); + Some(BatteryInfo { + energy: battery.energy().value, + energy_full: battery.energy_full().value, + state: battery.state(), + }) + } + Err(e) => { + let level = if cfg!(target_os = "linux") { + log::Level::Info + } else { + log::Level::Warn + }; + log::log!(level, "Unable to access battery information:\n{}", &e); + None + } + }) + .fold( + BatteryInfo { + energy: 0.0, + energy_full: 0.0, + state: battery::State::Unknown, + }, + |mut acc, x| { + acc.energy += x.energy; + acc.energy_full += x.energy_full; + acc.state = merge_battery_states(acc.state, x.state); + acc + }, + ), + ) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::test::ModuleRenderer; + use ansi_term::Color; + + #[test] + fn no_battery_status() { + let mut mock = MockBatteryInfoProvider::new(); + + mock.expect_get_battery_info().times(1).returning(|| None); + + let actual = ModuleRenderer::new("battery") + .config(toml::toml! { + [[battery.display]] + threshold = 100 + style = "" + }) + .battery_info_provider(&mock) + .collect(); + let expected = None; + + assert_eq!(expected, actual); + } + + #[test] + fn ignores_zero_capacity_battery() { + let mut mock = MockBatteryInfoProvider::new(); + + mock.expect_get_battery_info().times(1).returning(|| { + Some(BatteryInfo { + energy: 0.0, + energy_full: 0.0, + state: battery::State::Full, + }) + }); + + let actual = ModuleRenderer::new("battery") + .config(toml::toml! { + [[battery.display]] + threshold = 100 + style = "" + }) + .battery_info_provider(&mock) + .collect(); + let expected = None; + + assert_eq!(expected, actual); + } + + #[test] + fn battery_full() { + let mut mock = MockBatteryInfoProvider::new(); + + mock.expect_get_battery_info().times(1).returning(|| { + Some(BatteryInfo { + energy: 1000.0, + energy_full: 1000.0, + state: battery::State::Full, + }) + }); + + let actual = ModuleRenderer::new("battery") + .config(toml::toml! { + [[battery.display]] + threshold = 100 + style = "" + }) + .battery_info_provider(&mock) + .collect(); + let expected = Some(String::from(" 100% ")); + + assert_eq!(expected, actual); + } + + #[test] + fn battery_charging() { + let mut mock = MockBatteryInfoProvider::new(); + + mock.expect_get_battery_info().times(1).returning(|| { + Some(BatteryInfo { + energy: 800.0, + energy_full: 1000.0, + state: battery::State::Charging, + }) + }); + + let actual = ModuleRenderer::new("battery") + .config(toml::toml! { + [[battery.display]] + threshold = 90 + style = "" + }) + .battery_info_provider(&mock) + .collect(); + let expected = Some(String::from(" 80% ")); + + assert_eq!(expected, actual); + } + + #[test] + fn battery_discharging() { + let mut mock = MockBatteryInfoProvider::new(); + + mock.expect_get_battery_info().times(1).returning(|| { + Some(BatteryInfo { + energy: 800.0, + energy_full: 1000.0, + state: battery::State::Discharging, + }) + }); + + let actual = ModuleRenderer::new("battery") + .config(toml::toml! { + [[battery.display]] + threshold = 100 + style = "" + }) + .battery_info_provider(&mock) + .collect(); + let expected = Some(String::from(" 80% ")); + + assert_eq!(expected, actual); + } + + #[test] + fn battery_unknown() { + let mut mock = MockBatteryInfoProvider::new(); + + mock.expect_get_battery_info().times(1).returning(|| { + Some(BatteryInfo { + energy: 0.0, + energy_full: 1.0, + state: battery::State::Unknown, + }) + }); + + let actual = ModuleRenderer::new("battery") + .config(toml::toml! { + [[battery.display]] + threshold = 100 + style = "" + }) + .battery_info_provider(&mock) + .collect(); + let expected = Some(String::from(" 0% ")); + + assert_eq!(expected, actual); + } + + #[test] + fn battery_empty() { + let mut mock = MockBatteryInfoProvider::new(); + + mock.expect_get_battery_info().times(1).returning(|| { + Some(BatteryInfo { + energy: 0.0, + energy_full: 1000.0, + state: battery::State::Empty, + }) + }); + + let actual = ModuleRenderer::new("battery") + .config(toml::toml! { + [[battery.display]] + threshold = 100 + style = "" + }) + .battery_info_provider(&mock) + .collect(); + let expected = Some(String::from(" 0% ")); + + assert_eq!(expected, actual); + } + + #[test] + fn battery_hidden_when_percentage_above_threshold() { + let mut mock = MockBatteryInfoProvider::new(); + + mock.expect_get_battery_info().times(1).returning(|| { + Some(BatteryInfo { + energy: 600.0, + energy_full: 1000.0, + state: battery::State::Full, + }) + }); + + let actual = ModuleRenderer::new("battery") + .config(toml::toml! { + [[battery.display]] + threshold = 50 + style = "" + }) + .battery_info_provider(&mock) + .collect(); + let expected = None; + + assert_eq!(expected, actual); + } + + #[test] + fn battery_uses_style() { + let mut mock = MockBatteryInfoProvider::new(); + + mock.expect_get_battery_info().times(1).returning(|| { + Some(BatteryInfo { + energy: 400.0, + energy_full: 1000.0, + state: battery::State::Discharging, + }) + }); + + let actual = ModuleRenderer::new("battery") + .config(toml::toml! { + [[battery.display]] + threshold = 50 + style = "bold red" + }) + .battery_info_provider(&mock) + .collect(); + let expected = Some(format!("{} ", Color::Red.bold().paint(" 40%"))); + + assert_eq!(expected, actual); + } + + #[test] + fn battery_displayed_precision() { + let mut mock = MockBatteryInfoProvider::new(); + + mock.expect_get_battery_info().times(1).returning(|| { + Some(BatteryInfo { + energy: 129.87654, + energy_full: 1000.0, + state: battery::State::Discharging, + }) + }); + + let actual = ModuleRenderer::new("battery") + .config(toml::toml! { + [[battery.display]] + threshold = 100 + style = "" + }) + .battery_info_provider(&mock) + .collect(); + let expected = Some(String::from(" 13% ")); + + assert_eq!(expected, actual); + } +} diff --git a/src/modules/mod.rs b/src/modules/mod.rs index 674a0fc5b..781bc732a 100644 --- a/src/modules/mod.rs +++ b/src/modules/mod.rs @@ -64,6 +64,9 @@ mod zig; #[cfg(feature = "battery")] mod battery; +#[cfg(feature = "battery")] +pub use self::battery::{BatteryInfoProvider, BatteryStatusProviderImpl}; + use crate::config::RootModuleConfig; use crate::context::{Context, Shell}; use crate::module::Module; diff --git a/src/test/mod.rs b/src/test/mod.rs index c81d5958b..299b6f309 100644 --- a/src/test/mod.rs +++ b/src/test/mod.rs @@ -121,6 +121,15 @@ impl<'a> ModuleRenderer<'a> { self } + #[cfg(feature = "battery")] + pub fn battery_info_provider( + mut self, + battery_info_provider: &'a (dyn crate::modules::BatteryInfoProvider + Send + Sync), + ) -> Self { + self.context.battery_info_provider = battery_info_provider; + self + } + /// Renders the module returning its output pub fn collect(self) -> Option { let ret = crate::print::get_module(self.name, self.context); -- cgit v1.2.3