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.
This commit is contained in:
Andrew Houts 2021-06-29 18:46:41 -05:00 committed by GitHub
parent 72e5a544fc
commit 53a30046d1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 423 additions and 41 deletions

96
Cargo.lock generated
View File

@ -368,6 +368,12 @@ dependencies = [
"syn 1.0.72", "syn 1.0.72",
] ]
[[package]]
name = "difference"
version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "524cbf6897b527295dff137cec09ecf3a05f4fddffd7dfcd1585403449e74198"
[[package]] [[package]]
name = "digest" name = "digest"
version = "0.8.1" version = "0.8.1"
@ -418,6 +424,12 @@ dependencies = [
"rand", "rand",
] ]
[[package]]
name = "downcast"
version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4bb454f0228b18c7f4c3b0ebbee346ed9c52e7443b0999cd543ff3571205701d"
[[package]] [[package]]
name = "dtoa" name = "dtoa"
version = "0.4.8" version = "0.4.8"
@ -466,6 +478,15 @@ dependencies = [
"instant", "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]] [[package]]
name = "fnv" name = "fnv"
version = "1.0.7" version = "1.0.7"
@ -497,6 +518,12 @@ dependencies = [
"percent-encoding", "percent-encoding",
] ]
[[package]]
name = "fragile"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "69a039c3498dc930fe810151a34ba0c1c70b02b8625035592e74432f678591f2"
[[package]] [[package]]
name = "funty" name = "funty"
version = "1.1.0" version = "1.1.0"
@ -877,6 +904,33 @@ dependencies = [
"autocfg", "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]] [[package]]
name = "native-tls" name = "native-tls"
version = "0.2.7" version = "0.2.7"
@ -956,6 +1010,12 @@ dependencies = [
"version_check", "version_check",
] ]
[[package]]
name = "normalize-line-endings"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "61807f77802ff30975e01f4f071c8ba10c022052f98b3294119f3e615d13e5be"
[[package]] [[package]]
name = "notify-rust" name = "notify-rust"
version = "4.5.2" version = "4.5.2"
@ -1212,6 +1272,35 @@ version = "0.2.10"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac74c624d6b2d21f425f752262f42188365d7b8ff1aff74c82e45136510a4857" 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]] [[package]]
name = "proc-macro-crate" name = "proc-macro-crate"
version = "0.1.5" version = "0.1.5"
@ -1602,6 +1691,7 @@ dependencies = [
"git2", "git2",
"indexmap", "indexmap",
"log", "log",
"mockall",
"native-tls", "native-tls",
"nix 0.21.0", "nix 0.21.0",
"notify-rust", "notify-rust",
@ -1796,6 +1886,12 @@ dependencies = [
"serde", "serde",
] ]
[[package]]
name = "treeline"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a7f741b240f1a48843f9b8e0444fb55fb2a4ff67293b50a9179dfd5ea67f8d41"
[[package]] [[package]]
name = "typenum" name = "typenum"
version = "1.13.0" version = "1.13.0"

View File

@ -94,6 +94,7 @@ shadow-rs = "0.6.2"
[dev-dependencies] [dev-dependencies]
tempfile = "3.2.0" tempfile = "3.2.0"
mockall = "0.9"
[profile.release] [profile.release]
codegen-units = 1 codegen-units = 1

View File

@ -50,6 +50,9 @@ pub struct Context<'a> {
#[cfg(test)] #[cfg(test)]
pub cmd: HashMap<&'a str, Option<CommandOutput>>, pub cmd: HashMap<&'a str, Option<CommandOutput>>,
#[cfg(feature = "battery")]
pub battery_info_provider: &'a (dyn crate::modules::BatteryInfoProvider + Send + Sync),
/// Timeout for the execution of commands /// Timeout for the execution of commands
cmd_timeout: Duration, cmd_timeout: Duration,
} }
@ -122,6 +125,8 @@ impl<'a> Context<'a> {
env: HashMap::new(), env: HashMap::new(),
#[cfg(test)] #[cfg(test)]
cmd: HashMap::new(), cmd: HashMap::new(),
#[cfg(feature = "battery")]
battery_info_provider: &crate::modules::BatteryStatusProviderImpl,
cmd_timeout, cmd_timeout,
} }
} }

View File

@ -1,5 +1,7 @@
use super::{Context, Module, RootModuleConfig, Shell}; use super::{Context, Module, RootModuleConfig, Shell};
use crate::configs::battery::BatteryConfig; use crate::configs::battery::BatteryConfig;
#[cfg(test)]
use mockall::automock;
use crate::formatter::StringFormatter; use crate::formatter::StringFormatter;
@ -12,7 +14,7 @@ pub fn module<'a>(context: &'a Context) -> Option<Module<'a>> {
_ => "%", _ => "%",
}; };
let battery_status = get_battery_status()?; let battery_status = get_battery_status(context)?;
let BatteryStatus { state, percentage } = battery_status; let BatteryStatus { state, percentage } = battery_status;
let mut module = context.new_module("battery"); let mut module = context.new_module("battery");
@ -75,46 +77,12 @@ pub fn module<'a>(context: &'a Context) -> Option<Module<'a>> {
} }
} }
fn get_battery_status() -> Option<BatteryStatus> { fn get_battery_status(context: &Context) -> Option<BatteryStatus> {
let battery_manager = battery::Manager::new().ok()?; let battery_info = context.battery_info_provider.get_battery_info()?;
let batteries = battery_manager.batteries().ok()?; if battery_info.energy_full != 0.0 {
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 {
let battery = BatteryStatus { let battery = BatteryStatus {
percentage: battery_contructor.energy / battery_contructor.energy_full * 100.0, percentage: battery_info.energy / battery_info.energy_full * 100.0,
state: battery_contructor.state, state: battery_info.state,
}; };
log::debug!("Battery status: {:?}", battery); log::debug!("Battery status: {:?}", battery);
Some(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: f32,
energy_full: f32, energy_full: f32,
state: battery::State, state: battery::State,
@ -156,3 +124,303 @@ struct BatteryStatus {
percentage: f32, percentage: f32,
state: battery::State, state: battery::State,
} }
#[cfg_attr(test, automock)]
pub trait BatteryInfoProvider {
fn get_battery_info(&self) -> Option<BatteryInfo>;
}
pub struct BatteryStatusProviderImpl;
impl BatteryInfoProvider for BatteryStatusProviderImpl {
fn get_battery_info(&self) -> Option<BatteryInfo> {
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);
}
}

View File

@ -64,6 +64,9 @@ mod zig;
#[cfg(feature = "battery")] #[cfg(feature = "battery")]
mod battery; mod battery;
#[cfg(feature = "battery")]
pub use self::battery::{BatteryInfoProvider, BatteryStatusProviderImpl};
use crate::config::RootModuleConfig; use crate::config::RootModuleConfig;
use crate::context::{Context, Shell}; use crate::context::{Context, Shell};
use crate::module::Module; use crate::module::Module;

View File

@ -121,6 +121,15 @@ impl<'a> ModuleRenderer<'a> {
self 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 /// Renders the module returning its output
pub fn collect(self) -> Option<String> { pub fn collect(self) -> Option<String> {
let ret = crate::print::get_module(self.name, self.context); let ret = crate::print::get_module(self.name, self.context);