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",
]
[[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"

View File

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

View File

@ -50,6 +50,9 @@ pub struct Context<'a> {
#[cfg(test)]
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
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,
}
}

View File

@ -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<Module<'a>> {
_ => "%",
};
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,10 +77,67 @@ 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_info = context.battery_info_provider.get_battery_info()?;
if battery_info.energy_full != 0.0 {
let battery = BatteryStatus {
percentage: battery_info.energy / battery_info.energy_full * 100.0,
state: battery_info.state,
};
log::debug!("Battery status: {:?}", battery);
Some(battery)
} else {
None
}
}
/// the merge returns Charging if at least one is charging
/// Discharging if at least one is Discharging
/// Full if both are Full or one is Full and the other Unknow
/// Empty if both are Empty or one is Empty and the other Unknow
/// Unknown otherwise
fn merge_battery_states(state1: battery::State, state2: battery::State) -> battery::State {
use battery::State::{Charging, Discharging, Unknown};
if state1 == Charging || state2 == Charging {
Charging
} else if state1 == Discharging || state2 == Discharging {
Discharging
} else if state1 == state2 {
state1
} else if state1 == Unknown {
state2
} else if state2 == Unknown {
state1
} else {
Unknown
}
}
pub struct BatteryInfo {
energy: f32,
energy_full: f32,
state: battery::State,
}
#[derive(Debug)]
struct BatteryStatus {
percentage: f32,
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()?;
let battery_contructor = batteries
Some(
batteries
.filter_map(|battery| match battery {
Ok(battery) => {
log::debug!("Battery found: {:?}", battery);
@ -110,49 +169,258 @@ fn get_battery_status() -> Option<BatteryStatus> {
acc.state = merge_battery_states(acc.state, x.state);
acc
},
);
if battery_contructor.energy_full != 0.0 {
let battery = BatteryStatus {
percentage: battery_contructor.energy / battery_contructor.energy_full * 100.0,
state: battery_contructor.state,
};
log::debug!("Battery status: {:?}", battery);
Some(battery)
} else {
None
),
)
}
}
/// the merge returns Charging if at least one is charging
/// Discharging if at least one is Discharging
/// Full if both are Full or one is Full and the other Unknow
/// Empty if both are Empty or one is Empty and the other Unknow
/// Unknown otherwise
fn merge_battery_states(state1: battery::State, state2: battery::State) -> battery::State {
use battery::State::{Charging, Discharging, Unknown};
if state1 == Charging || state2 == Charging {
Charging
} else if state1 == Discharging || state2 == Discharging {
Discharging
} else if state1 == state2 {
state1
} else if state1 == Unknown {
state2
} else if state2 == Unknown {
state1
} else {
Unknown
#[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);
}
}
struct BatteryInfo {
energy: f32,
energy_full: f32,
state: battery::State,
}
#[derive(Debug)]
struct BatteryStatus {
percentage: f32,
state: battery::State,
}

View File

@ -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;

View File

@ -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<String> {
let ret = crate::print::get_module(self.name, self.context);