refactor: simplify some config -> constraints code (#1383)

* refactor: simplify some config -> constraints code

* iteratively progress...

* update bcr; this might need testing since I removed some old proc code

* widget side

* fix battery

* fix widget tests with bandaid for now

The issue was that the calculations assume a certain ratio for CPU
legends.

* add some tests

* bump up

* fix proc drawing issues

So with the proc widget in certain places, there would be a panic during
constraint determination.

Looks like back when I wrote this I made some gross assumptions about
certain things. In particular, the problem here was that the search
added an additional "one" height, so that needs to be accounted for
after we removed the "doubling" code.

* tests

* fix tests

* reorganize tests

* clippy

* fix cross tests not working

* fix builds for android
This commit is contained in:
Clement Tsang 2024-01-15 04:19:18 -05:00 committed by GitHub
parent f093902aef
commit dd66ae774c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 724 additions and 412 deletions

View File

@ -236,7 +236,7 @@ jobs:
target: "aarch64-linux-android",
cross: true,
rust: stable,
cross-version: "git:d6511b7b166c18640f81b8f6a74d9eef380f7ded",
cross-version: "git:cabfc3b02d1edec03869fabdabf6a7f8b0519160",
no-default-features: true,
}

150
Cargo.lock generated
View File

@ -176,6 +176,7 @@ dependencies = [
"log",
"mach2",
"nvml-wrapper",
"portable-pty",
"predicates",
"ratatui",
"regex",
@ -352,7 +353,7 @@ dependencies = [
"autocfg",
"cfg-if",
"crossbeam-utils",
"memoffset",
"memoffset 0.9.0",
]
[[package]]
@ -476,6 +477,12 @@ version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10"
[[package]]
name = "downcast-rs"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9ea835d29036a4087793836fa931b08837ad5e957da9e23886b29586fb9b6650"
[[package]]
name = "either"
version = "1.9.0"
@ -606,6 +613,15 @@ version = "2.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e186cfbae8084e513daff4240b4797e342f988cecda4fb6c939150f96315fd8"
[[package]]
name = "ioctl-rs"
version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f7970510895cee30b3e9128319f2cefd4bde883a39f38baa279567ba3a7eb97d"
dependencies = [
"libc",
]
[[package]]
name = "itertools"
version = "0.11.0"
@ -639,6 +655,12 @@ dependencies = [
"static_assertions",
]
[[package]]
name = "lazy_static"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
[[package]]
name = "lazycell"
version = "1.3.0"
@ -718,6 +740,15 @@ version = "2.6.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f665ee40bc4a3c5590afb1e9677db74a508659dfd71e126420da8274909a0167"
[[package]]
name = "memoffset"
version = "0.6.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5aa361d4faea93603064a027415f07bd8e1d5c88c9fbf68bf56a285428fd79ce"
dependencies = [
"autocfg",
]
[[package]]
name = "memoffset"
version = "0.9.0"
@ -748,6 +779,20 @@ dependencies = [
"windows-sys 0.48.0",
]
[[package]]
name = "nix"
version = "0.25.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f346ff70e7dbfd675fe90590b92d59ef2de15a8779ae305ebcbfd3f0caf59be4"
dependencies = [
"autocfg",
"bitflags 1.3.2",
"cfg-if",
"libc",
"memoffset 0.6.5",
"pin-utils",
]
[[package]]
name = "nix"
version = "0.26.4"
@ -876,6 +921,33 @@ version = "1.0.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "de3145af08024dea9fa9914f381a17b8fc6034dfb00f3a84013f7ff43f29ed4c"
[[package]]
name = "pin-utils"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
[[package]]
name = "portable-pty"
version = "0.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "806ee80c2a03dbe1a9fb9534f8d19e4c0546b790cde8fd1fea9d6390644cb0be"
dependencies = [
"anyhow",
"bitflags 1.3.2",
"downcast-rs",
"filedescriptor",
"lazy_static",
"libc",
"log",
"nix 0.25.1",
"serial",
"shared_library",
"shell-words",
"winapi",
"winreg",
]
[[package]]
name = "powerfmt"
version = "0.2.0"
@ -1111,6 +1183,64 @@ dependencies = [
"serde",
]
[[package]]
name = "serial"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a1237a96570fc377c13baa1b88c7589ab66edced652e43ffb17088f003db3e86"
dependencies = [
"serial-core",
"serial-unix",
"serial-windows",
]
[[package]]
name = "serial-core"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3f46209b345401737ae2125fe5b19a77acce90cd53e1658cda928e4fe9a64581"
dependencies = [
"libc",
]
[[package]]
name = "serial-unix"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f03fbca4c9d866e24a459cbca71283f545a37f8e3e002ad8c70593871453cab7"
dependencies = [
"ioctl-rs",
"libc",
"serial-core",
"termios",
]
[[package]]
name = "serial-windows"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "15c6d3b776267a75d31bbdfd5d36c0ca051251caafc285827052bc53bcdc8162"
dependencies = [
"libc",
"serial-core",
]
[[package]]
name = "shared_library"
version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5a9e7e0f2bfae24d8a5b5a66c5b257a83c7412304311512a0c054cd5e619da11"
dependencies = [
"lazy_static",
"libc",
]
[[package]]
name = "shell-words"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "24188a676b6ae68c3b2cb3a01be17fbf7240ce009799bb56d5b1409051e78fde"
[[package]]
name = "signal-hook"
version = "0.3.17"
@ -1275,6 +1405,15 @@ dependencies = [
"windows-sys 0.48.0",
]
[[package]]
name = "termios"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d5d9cf598a6d7ce700a4e6a9199da127e6819a61e64b68609683cc9a01b5683a"
dependencies = [
"libc",
]
[[package]]
name = "termtree"
version = "0.4.1"
@ -1616,6 +1755,15 @@ dependencies = [
"memchr",
]
[[package]]
name = "winreg"
version = "0.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "80d0f4e272c85def139476380b12f9ac60926689dd2e01d4923222f40580869d"
dependencies = [
"winapi",
]
[[package]]
name = "wrapcenum-derive"
version = "0.4.0"

View File

@ -135,6 +135,9 @@ cargo-husky = { version = "1.5.0", default-features = false, features = [
] }
predicates = "3.0.3"
[target.'cfg(all(target_arch = "x86_64", target_os = "linux"))'.dev-dependencies]
portable-pty = "0.8.1"
[build-dependencies]
clap = { version = "4.4.14", features = ["default", "cargo", "wrap_help"] }
clap_complete = "4.4.6"

View File

@ -66,20 +66,20 @@ impl BottomLayout {
col_row_mapping.insert(
(
widget_width * 100 / col_row.total_widget_ratio,
(widget_width + widget.width_ratio) * 100
(widget_width + widget.constraint.ratio()) * 100
/ col_row.total_widget_ratio,
),
widget.widget_id,
);
}
}
widget_width += widget.width_ratio;
widget_width += widget.constraint.ratio();
}
if is_valid_col_row {
col_mapping.insert(
(
col_row_height * 100 / col.total_col_row_ratio,
(col_row_height + col_row.col_row_height_ratio) * 100
(col_row_height + col_row.constraint.ratio()) * 100
/ col.total_col_row_ratio,
),
(col.total_col_row_ratio, col_row_mapping),
@ -87,31 +87,31 @@ impl BottomLayout {
is_valid_col = true;
}
col_row_height += col_row.col_row_height_ratio;
col_row_height += col_row.constraint.ratio();
}
if is_valid_col {
row_mapping.insert(
(
row_width * 100 / row.total_col_ratio,
(row_width + col.col_width_ratio) * 100 / row.total_col_ratio,
(row_width + col.constraint.ratio()) * 100 / row.total_col_ratio,
),
(row.total_col_ratio, col_mapping),
);
is_valid_row = true;
}
row_width += col.col_width_ratio;
row_width += col.constraint.ratio();
}
if is_valid_row {
layout_mapping.insert(
(
total_height * 100 / self.total_row_height_ratio,
(total_height + row.row_height_ratio) * 100 / self.total_row_height_ratio,
(total_height + row.constraint.ratio()) * 100 / self.total_row_height_ratio,
),
(self.total_row_height_ratio, row_mapping),
);
}
total_height += row.row_height_ratio;
total_height += row.constraint.ratio();
}
// Now pass through a second time; this time we want to build up
@ -121,20 +121,20 @@ impl BottomLayout {
let mut col_cursor = 0;
let row_height_percentage_start = height_cursor * 100 / self.total_row_height_ratio;
let row_height_percentage_end =
(height_cursor + row.row_height_ratio) * 100 / self.total_row_height_ratio;
(height_cursor + row.constraint.ratio()) * 100 / self.total_row_height_ratio;
for col in &mut row.children {
let mut col_row_cursor = 0;
let col_width_percentage_start = col_cursor * 100 / row.total_col_ratio;
let col_width_percentage_end =
(col_cursor + col.col_width_ratio) * 100 / row.total_col_ratio;
(col_cursor + col.constraint.ratio()) * 100 / row.total_col_ratio;
for col_row in &mut col.children {
let mut widget_cursor = 0;
let col_row_height_percentage_start =
col_row_cursor * 100 / col.total_col_row_ratio;
let col_row_height_percentage_end =
(col_row_cursor + col_row.col_row_height_ratio) * 100
(col_row_cursor + col_row.constraint.ratio()) * 100
/ col.total_col_row_ratio;
let col_row_children_len = col_row.children.len();
@ -147,7 +147,8 @@ impl BottomLayout {
let widget_width_percentage_start =
widget_cursor * 100 / col_row.total_widget_ratio;
let widget_width_percentage_end =
(widget_cursor + widget.width_ratio) * 100 / col_row.total_widget_ratio;
(widget_cursor + widget.constraint.ratio()) * 100
/ col_row.total_widget_ratio;
if let Some(current_row) = layout_mapping
.get(&(row_height_percentage_start, row_height_percentage_end))
@ -523,91 +524,85 @@ impl BottomLayout {
}
}
}
widget_cursor += widget.width_ratio;
widget_cursor += widget.constraint.ratio();
}
col_row_cursor += col_row.col_row_height_ratio;
col_row_cursor += col_row.constraint.ratio();
}
col_cursor += col.col_width_ratio;
col_cursor += col.constraint.ratio();
}
height_cursor += row.row_height_ratio;
height_cursor += row.constraint.ratio();
}
}
pub fn init_basic_default(use_battery: bool) -> Self {
let table_widgets = if use_battery {
let disk_widget = BottomWidget::new(BottomWidgetType::Disk, 4)
.canvas_handle_width(true)
.canvas_handled()
.up_neighbour(Some(100))
.left_neighbour(Some(8))
.right_neighbour(Some(DEFAULT_WIDGET_ID + 2));
let proc_sort = BottomWidget::new(BottomWidgetType::ProcSort, DEFAULT_WIDGET_ID + 2)
.canvas_handle_width(true)
.canvas_handled()
.up_neighbour(Some(100))
.down_neighbour(Some(DEFAULT_WIDGET_ID + 1))
.left_neighbour(Some(4))
.right_neighbour(Some(DEFAULT_WIDGET_ID))
.width_ratio(1)
.ratio(1)
.parent_reflector(Some((WidgetDirection::Right, 2)));
let proc = BottomWidget::new(BottomWidgetType::Proc, DEFAULT_WIDGET_ID)
.canvas_handle_width(true)
.canvas_handled()
.up_neighbour(Some(100))
.down_neighbour(Some(DEFAULT_WIDGET_ID + 1))
.left_neighbour(Some(DEFAULT_WIDGET_ID + 2))
.right_neighbour(Some(7))
.width_ratio(2);
.ratio(2);
let proc_search =
BottomWidget::new(BottomWidgetType::ProcSearch, DEFAULT_WIDGET_ID + 1)
.canvas_handle_width(true)
.canvas_handled()
.up_neighbour(Some(DEFAULT_WIDGET_ID))
.left_neighbour(Some(4))
.right_neighbour(Some(7))
.parent_reflector(Some((WidgetDirection::Up, 1)));
let temp = BottomWidget::new(BottomWidgetType::Temp, 7)
.canvas_handle_width(true)
.canvas_handled()
.up_neighbour(Some(100))
.left_neighbour(Some(DEFAULT_WIDGET_ID))
.right_neighbour(Some(8));
let battery = BottomWidget::new(BottomWidgetType::Battery, 8)
.canvas_handle_width(true)
.canvas_handled()
.up_neighbour(Some(100))
.left_neighbour(Some(7))
.right_neighbour(Some(4));
vec![
BottomCol::new(vec![
BottomColRow::new(vec![disk_widget]).canvas_handle_height(true)
])
.canvas_handle_width(true),
BottomCol::new(vec![BottomColRow::new(vec![disk_widget]).canvas_handled()])
.canvas_handled(),
BottomCol::new(vec![
BottomColRow::new(vec![proc_sort, proc])
.canvas_handle_height(true)
.canvas_handled()
.total_widget_ratio(3),
BottomColRow::new(vec![proc_search]).canvas_handle_height(true),
BottomColRow::new(vec![proc_search]).canvas_handled(),
])
.canvas_handle_width(true),
BottomCol::new(vec![
BottomColRow::new(vec![temp]).canvas_handle_height(true)
])
.canvas_handle_width(true),
BottomCol::new(vec![
BottomColRow::new(vec![battery]).canvas_handle_height(true)
])
.canvas_handle_width(true),
.canvas_handled(),
BottomCol::new(vec![BottomColRow::new(vec![temp]).canvas_handled()])
.canvas_handled(),
BottomCol::new(vec![BottomColRow::new(vec![battery]).canvas_handled()])
.canvas_handled(),
]
} else {
let disk = BottomWidget::new(BottomWidgetType::Disk, 4)
.canvas_handle_width(true)
.canvas_handled()
.up_neighbour(Some(100))
.left_neighbour(Some(7))
.right_neighbour(Some(DEFAULT_WIDGET_ID + 2));
let proc_sort = BottomWidget::new(BottomWidgetType::ProcSort, DEFAULT_WIDGET_ID + 2)
.canvas_handle_width(true)
.canvas_handled()
.up_neighbour(Some(100))
.down_neighbour(Some(DEFAULT_WIDGET_ID + 1))
.left_neighbour(Some(4))
@ -615,7 +610,7 @@ impl BottomLayout {
.parent_reflector(Some((WidgetDirection::Right, 2)));
let proc = BottomWidget::new(BottomWidgetType::Proc, DEFAULT_WIDGET_ID)
.canvas_handle_width(true)
.canvas_handled()
.up_neighbour(Some(100))
.down_neighbour(Some(DEFAULT_WIDGET_ID + 1))
.left_neighbour(Some(DEFAULT_WIDGET_ID + 2))
@ -623,110 +618,111 @@ impl BottomLayout {
let proc_search =
BottomWidget::new(BottomWidgetType::ProcSearch, DEFAULT_WIDGET_ID + 1)
.canvas_handle_width(true)
.canvas_handled()
.up_neighbour(Some(DEFAULT_WIDGET_ID))
.left_neighbour(Some(4))
.right_neighbour(Some(7))
.parent_reflector(Some((WidgetDirection::Up, 1)));
let temp = BottomWidget::new(BottomWidgetType::Temp, 7)
.canvas_handle_width(true)
.canvas_handled()
.up_neighbour(Some(100))
.left_neighbour(Some(DEFAULT_WIDGET_ID))
.right_neighbour(Some(4));
vec![
BottomCol::new(vec![BottomColRow::new(vec![disk]).canvas_handled()])
.canvas_handled(),
BottomCol::new(vec![
BottomColRow::new(vec![disk]).canvas_handle_height(true)
BottomColRow::new(vec![proc_sort, proc]).canvas_handled(),
BottomColRow::new(vec![proc_search]).canvas_handled(),
])
.canvas_handle_width(true),
BottomCol::new(vec![
BottomColRow::new(vec![proc_sort, proc]).canvas_handle_height(true),
BottomColRow::new(vec![proc_search]).canvas_handle_height(true),
])
.canvas_handle_width(true),
BottomCol::new(vec![
BottomColRow::new(vec![temp]).canvas_handle_height(true)
])
.canvas_handle_width(true),
.canvas_handled(),
BottomCol::new(vec![BottomColRow::new(vec![temp]).canvas_handled()])
.canvas_handled(),
]
};
let cpu = BottomWidget::new(BottomWidgetType::BasicCpu, 1)
.canvas_handle_width(true)
.canvas_handled()
.down_neighbour(Some(2));
let mem = BottomWidget::new(BottomWidgetType::BasicMem, 2)
.canvas_handle_width(true)
.canvas_handled()
.up_neighbour(Some(1))
.down_neighbour(Some(100))
.right_neighbour(Some(3));
let net = BottomWidget::new(BottomWidgetType::BasicNet, 3)
.canvas_handle_width(true)
.canvas_handled()
.up_neighbour(Some(1))
.down_neighbour(Some(100))
.left_neighbour(Some(2));
let table = BottomWidget::new(BottomWidgetType::BasicTables, 100)
.canvas_handle_width(true)
.canvas_handled()
.up_neighbour(Some(2));
BottomLayout {
total_row_height_ratio: 3,
rows: vec![
BottomRow::new(vec![BottomCol::new(vec![
BottomColRow::new(vec![cpu]).canvas_handle_height(true)
BottomColRow::new(vec![cpu]).canvas_handled()
])
.canvas_handle_width(true)])
.canvas_handle_height(true),
.canvas_handled()])
.canvas_handled(),
BottomRow::new(vec![BottomCol::new(vec![BottomColRow::new(vec![
mem, net,
])
.canvas_handle_height(true)])
.canvas_handle_width(true)])
.canvas_handle_height(true),
.canvas_handled()])
.canvas_handled()])
.canvas_handled(),
BottomRow::new(vec![BottomCol::new(vec![
BottomColRow::new(vec![table]).canvas_handle_height(true)
BottomColRow::new(vec![table]).canvas_handled()
])
.canvas_handle_width(true)])
.canvas_handle_height(true),
BottomRow::new(table_widgets).canvas_handle_height(true),
.canvas_handled()])
.canvas_handled(),
BottomRow::new(table_widgets).canvas_handled(),
],
}
}
}
// pub enum BottomLayoutNode {
// Container(BottomContainer),
// Widget(BottomWidget),
// }
#[derive(Clone, Debug)]
pub enum IntermediaryConstraint {
PartialRatio(u32),
CanvasHandled { ratio: Option<u32> },
Grow { minimum: Option<u32> },
}
// pub struct BottomContainer {
// children: Vec<BottomLayoutNode>,
// root_ratio: u32,
// growth_type: BottomLayoutNodeSizing,
// }
impl Default for IntermediaryConstraint {
fn default() -> Self {
IntermediaryConstraint::PartialRatio(1)
}
}
// pub enum BottomContainerType {
// Row,
// Col,
// }
// pub enum BottomLayoutNodeSizing {
// Ratio(u32),
// CanvasHandles,
// FlexGrow,
// }
impl IntermediaryConstraint {
pub fn ratio(&self) -> u32 {
match self {
IntermediaryConstraint::PartialRatio(val) => *val,
IntermediaryConstraint::Grow { minimum } => match minimum {
Some(val) => *val,
None => 1,
},
IntermediaryConstraint::CanvasHandled { ratio } => match ratio {
Some(val) => *val,
None => 1,
},
}
}
}
/// Represents a single row in the layout.
#[derive(Clone, Debug)]
pub struct BottomRow {
pub children: Vec<BottomCol>,
pub total_col_ratio: u32,
pub row_height_ratio: u32,
pub canvas_handle_height: bool,
pub flex_grow: bool,
pub constraint: IntermediaryConstraint,
}
impl BottomRow {
@ -734,9 +730,7 @@ impl BottomRow {
Self {
children,
total_col_ratio: 1,
row_height_ratio: 1,
canvas_handle_height: false,
flex_grow: false,
constraint: IntermediaryConstraint::default(),
}
}
@ -745,18 +739,18 @@ impl BottomRow {
self
}
pub fn row_height_ratio(mut self, row_height_ratio: u32) -> Self {
self.row_height_ratio = row_height_ratio;
pub fn ratio(mut self, row_height_ratio: u32) -> Self {
self.constraint = IntermediaryConstraint::PartialRatio(row_height_ratio);
self
}
pub fn canvas_handle_height(mut self, canvas_handle_height: bool) -> Self {
self.canvas_handle_height = canvas_handle_height;
pub fn canvas_handled(mut self) -> Self {
self.constraint = IntermediaryConstraint::CanvasHandled { ratio: None };
self
}
pub fn flex_grow(mut self, flex_grow: bool) -> Self {
self.flex_grow = flex_grow;
pub fn grow(mut self, minimum: Option<u32>) -> Self {
self.constraint = IntermediaryConstraint::Grow { minimum };
self
}
}
@ -768,9 +762,7 @@ impl BottomRow {
pub struct BottomCol {
pub children: Vec<BottomColRow>,
pub total_col_row_ratio: u32,
pub col_width_ratio: u32,
pub canvas_handle_width: bool,
pub flex_grow: bool,
pub constraint: IntermediaryConstraint,
}
impl BottomCol {
@ -778,9 +770,7 @@ impl BottomCol {
Self {
children,
total_col_row_ratio: 1,
col_width_ratio: 1,
canvas_handle_width: false,
flex_grow: false,
constraint: IntermediaryConstraint::default(),
}
}
@ -789,18 +779,18 @@ impl BottomCol {
self
}
pub fn col_width_ratio(mut self, col_width_ratio: u32) -> Self {
self.col_width_ratio = col_width_ratio;
pub fn ratio(mut self, col_width_ratio: u32) -> Self {
self.constraint = IntermediaryConstraint::PartialRatio(col_width_ratio);
self
}
pub fn canvas_handle_width(mut self, canvas_handle_width: bool) -> Self {
self.canvas_handle_width = canvas_handle_width;
pub fn canvas_handled(mut self) -> Self {
self.constraint = IntermediaryConstraint::CanvasHandled { ratio: None };
self
}
pub fn flex_grow(mut self, flex_grow: bool) -> Self {
self.flex_grow = flex_grow;
pub fn grow(mut self, minimum: Option<u32>) -> Self {
self.constraint = IntermediaryConstraint::Grow { minimum };
self
}
}
@ -809,9 +799,7 @@ impl BottomCol {
pub struct BottomColRow {
pub children: Vec<BottomWidget>,
pub total_widget_ratio: u32,
pub col_row_height_ratio: u32,
pub canvas_handle_height: bool,
pub flex_grow: bool,
pub constraint: IntermediaryConstraint,
}
impl BottomColRow {
@ -819,9 +807,7 @@ impl BottomColRow {
Self {
children,
total_widget_ratio: 1,
col_row_height_ratio: 1,
canvas_handle_height: false,
flex_grow: false,
constraint: IntermediaryConstraint::default(),
}
}
@ -830,18 +816,18 @@ impl BottomColRow {
self
}
pub(crate) fn col_row_height_ratio(mut self, col_row_height_ratio: u32) -> Self {
self.col_row_height_ratio = col_row_height_ratio;
pub fn ratio(mut self, col_row_height_ratio: u32) -> Self {
self.constraint = IntermediaryConstraint::PartialRatio(col_row_height_ratio);
self
}
pub(crate) fn canvas_handle_height(mut self, canvas_handle_height: bool) -> Self {
self.canvas_handle_height = canvas_handle_height;
pub fn canvas_handled(mut self) -> Self {
self.constraint = IntermediaryConstraint::CanvasHandled { ratio: None };
self
}
pub(crate) fn flex_grow(mut self, flex_grow: bool) -> Self {
self.flex_grow = flex_grow;
pub fn grow(mut self, minimum: Option<u32>) -> Self {
self.constraint = IntermediaryConstraint::Grow { minimum };
self
}
}
@ -872,18 +858,12 @@ impl WidgetDirection {
pub struct BottomWidget {
pub widget_type: BottomWidgetType,
pub widget_id: u64,
pub width_ratio: u32,
pub constraint: IntermediaryConstraint,
pub left_neighbour: Option<u64>,
pub right_neighbour: Option<u64>,
pub up_neighbour: Option<u64>,
pub down_neighbour: Option<u64>,
/// If set to true, the canvas will override any ratios.
pub canvas_handle_width: bool,
/// Whether we want this widget to take up all available room (and ignore any ratios).
pub flex_grow: bool,
/// The value is the direction to bounce, as well as the parent offset.
pub parent_reflector: Option<(WidgetDirection, u64)>,
@ -899,24 +879,17 @@ impl BottomWidget {
Self {
widget_type,
widget_id,
width_ratio: 1,
constraint: IntermediaryConstraint::default(),
left_neighbour: None,
right_neighbour: None,
up_neighbour: None,
down_neighbour: None,
canvas_handle_width: false,
flex_grow: false,
parent_reflector: None,
top_left_corner: None,
bottom_right_corner: None,
}
}
pub(crate) fn width_ratio(mut self, width_ratio: u32) -> Self {
self.width_ratio = width_ratio;
self
}
pub(crate) fn left_neighbour(mut self, left_neighbour: Option<u64>) -> Self {
self.left_neighbour = left_neighbour;
self
@ -937,13 +910,23 @@ impl BottomWidget {
self
}
pub(crate) fn canvas_handle_width(mut self, canvas_handle_width: bool) -> Self {
self.canvas_handle_width = canvas_handle_width;
pub(crate) fn ratio(mut self, width_ratio: u32) -> Self {
self.constraint = IntermediaryConstraint::PartialRatio(width_ratio);
self
}
pub(crate) fn flex_grow(mut self, flex_grow: bool) -> Self {
self.flex_grow = flex_grow;
pub fn canvas_handled(mut self) -> Self {
self.constraint = IntermediaryConstraint::CanvasHandled { ratio: None };
self
}
pub fn canvas_with_ratio(mut self, ratio: u32) -> Self {
self.constraint = IntermediaryConstraint::CanvasHandled { ratio: Some(ratio) };
self
}
pub fn grow(mut self, minimum: Option<u32>) -> Self {
self.constraint = IntermediaryConstraint::Grow { minimum };
self
}
@ -1037,6 +1020,8 @@ Supported widget names:
| disk |
+--------------------------+
| batt, battery |
+--------------------------+
| empty |
+--------------------------+
",
)))
@ -1059,6 +1044,8 @@ Supported widget names:
| temp, temperature |
+--------------------------+
| disk |
+--------------------------+
| empty |
+--------------------------+
",
)))

View File

@ -18,7 +18,7 @@ use tui::{
use crate::{
app::{
layout_manager::{BottomColRow, BottomLayout, BottomWidgetType},
layout_manager::{BottomColRow, BottomLayout, BottomWidgetType, IntermediaryConstraint},
App,
},
constants::*,
@ -80,7 +80,7 @@ pub enum LayoutConstraint {
}
impl Painter {
pub fn init(widget_layout: BottomLayout, styling: CanvasStyling) -> anyhow::Result<Self> {
pub fn init(layout: BottomLayout, styling: CanvasStyling) -> anyhow::Result<Self> {
// Now for modularity; we have to also initialize the base layouts!
// We want to do this ONCE and reuse; after this we can just construct
// based on the console size.
@ -90,56 +90,69 @@ impl Painter {
let mut col_row_constraints = Vec::new();
let mut layout_constraints = Vec::new();
widget_layout.rows.iter().for_each(|row| {
if row.canvas_handle_height {
row_constraints.push(LayoutConstraint::CanvasHandled);
} else {
row_constraints.push(LayoutConstraint::Ratio(
row.row_height_ratio,
widget_layout.total_row_height_ratio,
));
layout.rows.iter().for_each(|row| {
match row.constraint {
IntermediaryConstraint::PartialRatio(val) => {
row_constraints
.push(LayoutConstraint::Ratio(val, layout.total_row_height_ratio));
}
IntermediaryConstraint::CanvasHandled { .. } => {
row_constraints.push(LayoutConstraint::CanvasHandled);
}
IntermediaryConstraint::Grow { .. } => {
row_constraints.push(LayoutConstraint::Grow);
}
}
let mut new_col_constraints = Vec::new();
let mut new_widget_constraints = Vec::new();
let mut new_col_row_constraints = Vec::new();
row.children.iter().for_each(|col| {
if col.canvas_handle_width {
new_col_constraints.push(LayoutConstraint::CanvasHandled);
} else {
new_col_constraints.push(LayoutConstraint::Ratio(
col.col_width_ratio,
row.total_col_ratio,
));
match col.constraint {
IntermediaryConstraint::PartialRatio(val) => {
new_col_constraints.push(LayoutConstraint::Ratio(val, row.total_col_ratio));
}
IntermediaryConstraint::CanvasHandled { .. } => {
new_col_constraints.push(LayoutConstraint::CanvasHandled);
}
IntermediaryConstraint::Grow { .. } => {
new_col_constraints.push(LayoutConstraint::Grow);
}
}
let mut new_new_col_row_constraints = Vec::new();
let mut new_new_widget_constraints = Vec::new();
col.children.iter().for_each(|col_row| {
if col_row.canvas_handle_height {
new_new_col_row_constraints.push(LayoutConstraint::CanvasHandled);
} else if col_row.flex_grow {
new_new_col_row_constraints.push(LayoutConstraint::Grow);
} else {
new_new_col_row_constraints.push(LayoutConstraint::Ratio(
col_row.col_row_height_ratio,
col.total_col_row_ratio,
));
match col_row.constraint {
IntermediaryConstraint::PartialRatio(val) => {
new_new_col_row_constraints
.push(LayoutConstraint::Ratio(val, col.total_col_row_ratio));
}
IntermediaryConstraint::CanvasHandled { .. } => {
new_new_col_row_constraints.push(LayoutConstraint::CanvasHandled);
}
IntermediaryConstraint::Grow { .. } => {
new_new_col_row_constraints.push(LayoutConstraint::Grow);
}
}
let mut new_new_new_widget_constraints = Vec::new();
col_row.children.iter().for_each(|widget| {
if widget.canvas_handle_width {
new_new_new_widget_constraints.push(LayoutConstraint::CanvasHandled);
} else if widget.flex_grow {
new_new_new_widget_constraints.push(LayoutConstraint::Grow);
} else {
new_new_new_widget_constraints.push(LayoutConstraint::Ratio(
widget.width_ratio,
col_row.total_widget_ratio,
));
}
});
col_row
.children
.iter()
.for_each(|widget| match widget.constraint {
IntermediaryConstraint::PartialRatio(val) => {
new_new_new_widget_constraints
.push(LayoutConstraint::Ratio(val, col_row.total_widget_ratio));
}
IntermediaryConstraint::CanvasHandled { .. } => {
new_new_new_widget_constraints
.push(LayoutConstraint::CanvasHandled);
}
IntermediaryConstraint::Grow { .. } => {
new_new_new_widget_constraints.push(LayoutConstraint::Grow);
}
});
new_new_widget_constraints.push(new_new_new_widget_constraints);
});
new_col_row_constraints.push(new_new_col_row_constraints);
@ -158,7 +171,7 @@ impl Painter {
col_constraints,
col_row_constraints,
layout_constraints,
widget_layout,
widget_layout: layout,
derived_widget_draw_locs: Vec::default(),
};

View File

@ -747,14 +747,6 @@ fn get_default_widget_and_count(
fn get_use_battery(matches: &ArgMatches, config: &Config) -> bool {
#[cfg(feature = "battery")]
{
if matches.get_flag("battery") {
return true;
} else if let Some(flags) = &config.flags {
if let Some(battery) = flags.battery {
return battery;
}
}
if let Ok(battery_manager) = Manager::new() {
if let Ok(batteries) = battery_manager.batteries() {
if batteries.count() == 0 {
@ -762,6 +754,14 @@ fn get_use_battery(matches: &ArgMatches, config: &Config) -> bool {
}
}
}
if matches.get_flag("battery") {
return true;
} else if let Some(flags) = &config.flags {
if let Some(battery) = flags.battery {
return battery;
}
}
}
false

View File

@ -2,7 +2,7 @@ use serde::{Deserialize, Serialize};
use crate::{app::layout_manager::*, error::Result};
/// Represents a row. This has a length of some sort (optional) and a vector
/// Represents a row. This has a length of some sort (optional) and a vector
/// of children.
#[derive(Clone, Deserialize, Debug, Serialize)]
#[serde(rename = "row")]
@ -19,21 +19,15 @@ fn new_cpu(left_legend: bool, iter_id: &mut u64) -> BottomColRow {
if left_legend {
BottomColRow::new(vec![
BottomWidget::new(BottomWidgetType::CpuLegend, legend_id)
.width_ratio(3)
.canvas_handle_width(true)
.canvas_with_ratio(3)
.parent_reflector(Some((WidgetDirection::Right, 1))),
BottomWidget::new(BottomWidgetType::Cpu, cpu_id)
.width_ratio(17)
.flex_grow(true),
BottomWidget::new(BottomWidgetType::Cpu, cpu_id).grow(Some(17)),
])
} else {
BottomColRow::new(vec![
BottomWidget::new(BottomWidgetType::Cpu, cpu_id)
.width_ratio(17)
.flex_grow(true),
BottomWidget::new(BottomWidgetType::Cpu, cpu_id).grow(Some(17)),
BottomWidget::new(BottomWidgetType::CpuLegend, legend_id)
.width_ratio(3)
.canvas_handle_width(true)
.canvas_with_ratio(3)
.parent_reflector(Some((WidgetDirection::Left, 1))),
])
}
@ -42,13 +36,12 @@ fn new_cpu(left_legend: bool, iter_id: &mut u64) -> BottomColRow {
fn new_proc_sort(sort_id: u64) -> BottomWidget {
BottomWidget::new(BottomWidgetType::ProcSort, sort_id)
.canvas_handle_width(true)
.canvas_handled()
.parent_reflector(Some((WidgetDirection::Right, 2)))
.width_ratio(1)
}
fn new_proc(proc_id: u64) -> BottomWidget {
BottomWidget::new(BottomWidgetType::Proc, proc_id).width_ratio(2)
BottomWidget::new(BottomWidgetType::Proc, proc_id).ratio(2)
}
fn new_proc_search(search_id: u64) -> BottomWidget {
@ -99,7 +92,7 @@ impl Row {
children.push(match widget_type {
BottomWidgetType::Cpu => {
BottomCol::new(vec![new_cpu(left_legend, iter_id)])
.col_width_ratio(width_ratio)
.ratio(width_ratio)
}
BottomWidgetType::Proc => {
let proc_id = *iter_id;
@ -110,34 +103,31 @@ impl Row {
new_proc_sort(*iter_id),
new_proc(proc_id),
])
.total_widget_ratio(3)
.flex_grow(true),
.grow(None)
.total_widget_ratio(3),
BottomColRow::new(vec![new_proc_search(proc_search_id)])
.canvas_handle_height(true),
.canvas_handled(),
])
.total_col_row_ratio(2)
.col_width_ratio(width_ratio)
.ratio(width_ratio)
}
_ => BottomCol::new(vec![BottomColRow::new(vec![BottomWidget::new(
widget_type,
*iter_id,
)])])
.col_width_ratio(width_ratio),
.ratio(width_ratio),
});
}
RowChildren::Col { ratio, child } => {
let col_width_ratio = ratio.unwrap_or(1);
total_col_ratio += col_width_ratio;
let mut total_col_row_ratio = 0;
let mut contains_proc = false;
let mut col_row_children: Vec<BottomColRow> = Vec::new();
for widget in child {
let widget_type = widget.widget_type.parse::<BottomWidgetType>()?;
*iter_id += 1;
let col_row_height_ratio = widget.ratio.unwrap_or(1);
total_col_row_ratio += col_row_height_ratio;
if let Some(default_widget_type_val) = default_widget_type {
if *default_widget_type_val == widget_type
@ -159,13 +149,17 @@ impl Row {
match widget_type {
BottomWidgetType::Cpu => {
let col_row_height_ratio = widget.ratio.unwrap_or(1);
total_col_row_ratio += col_row_height_ratio;
col_row_children.push(
new_cpu(left_legend, iter_id)
.col_row_height_ratio(col_row_height_ratio),
new_cpu(left_legend, iter_id).ratio(col_row_height_ratio),
);
}
BottomWidgetType::Proc => {
contains_proc = true;
let col_row_height_ratio = widget.ratio.unwrap_or(1) + 1;
total_col_row_ratio += col_row_height_ratio;
let proc_id = *iter_id;
let proc_search_id = *iter_id + 1;
*iter_id += 2;
@ -174,35 +168,25 @@ impl Row {
new_proc_sort(*iter_id),
new_proc(proc_id),
])
.col_row_height_ratio(col_row_height_ratio)
.ratio(col_row_height_ratio)
.total_widget_ratio(3),
);
col_row_children.push(
BottomColRow::new(vec![new_proc_search(proc_search_id)])
.canvas_handle_height(true)
.col_row_height_ratio(col_row_height_ratio),
.canvas_handled(),
);
}
_ => col_row_children.push(
BottomColRow::new(vec![BottomWidget::new(
widget_type,
*iter_id,
)])
.col_row_height_ratio(col_row_height_ratio),
),
}
}
_ => {
let col_row_height_ratio = widget.ratio.unwrap_or(1);
total_col_row_ratio += col_row_height_ratio;
if contains_proc {
// Must adjust ratios to work with proc
total_col_row_ratio *= 2;
for child in &mut col_row_children {
// Multiply all non-proc or proc-search ratios by 2
if !child.children.is_empty() {
match child.children[0].widget_type {
BottomWidgetType::ProcSearch => {}
_ => child.col_row_height_ratio *= 2,
}
col_row_children.push(
BottomColRow::new(vec![BottomWidget::new(
widget_type,
*iter_id,
)])
.ratio(col_row_height_ratio),
)
}
}
}
@ -210,7 +194,7 @@ impl Row {
children.push(
BottomCol::new(col_row_children)
.total_col_row_ratio(total_col_row_ratio)
.col_width_ratio(col_width_ratio),
.ratio(col_width_ratio),
);
}
}
@ -219,7 +203,7 @@ impl Row {
Ok(BottomRow::new(children)
.total_col_ratio(total_col_ratio)
.row_height_ratio(row_ratio))
.ratio(row_ratio))
}
}

View File

@ -1,15 +1,13 @@
//! These tests are mostly here just to ensure that invalid results will be caught when passing arguments.
mod util;
use assert_cmd::prelude::*;
use predicates::prelude::*;
use util::*;
use crate::util::{btm_command, no_cfg_btm_command};
#[test]
fn test_small_rate() {
btm_command()
.arg("-C")
.arg("./tests/empty_config.toml")
btm_command(&["-C", "./tests/valid_configs/empty_config.toml"])
.arg("-r")
.arg("249")
.assert()
@ -21,9 +19,7 @@ fn test_small_rate() {
#[test]
fn test_large_default_time() {
btm_command()
.arg("-C")
.arg("./tests/empty_config.toml")
no_cfg_btm_command()
.arg("-t")
.arg("18446744073709551616")
.assert()
@ -33,9 +29,7 @@ fn test_large_default_time() {
#[test]
fn test_small_default_time() {
btm_command()
.arg("-C")
.arg("./tests/empty_config.toml")
no_cfg_btm_command()
.arg("-t")
.arg("900")
.assert()
@ -47,9 +41,7 @@ fn test_small_default_time() {
#[test]
fn test_large_delta_time() {
btm_command()
.arg("-C")
.arg("./tests/empty_config.toml")
no_cfg_btm_command()
.arg("-d")
.arg("18446744073709551616")
.assert()
@ -59,9 +51,7 @@ fn test_large_delta_time() {
#[test]
fn test_small_delta_time() {
btm_command()
.arg("-C")
.arg("./tests/empty_config.toml")
no_cfg_btm_command()
.arg("-d")
.arg("900")
.assert()
@ -73,9 +63,7 @@ fn test_small_delta_time() {
#[test]
fn test_large_rate() {
btm_command()
.arg("-C")
.arg("./tests/empty_config.toml")
no_cfg_btm_command()
.arg("-r")
.arg("18446744073709551616")
.assert()
@ -86,9 +74,7 @@ fn test_large_rate() {
#[test]
fn test_negative_rate() {
// This test should auto fail due to how clap works
btm_command()
.arg("-C")
.arg("./tests/empty_config.toml")
no_cfg_btm_command()
.arg("-r")
.arg("-1000")
.assert()
@ -98,9 +84,7 @@ fn test_negative_rate() {
#[test]
fn test_invalid_rate() {
btm_command()
.arg("-C")
.arg("./tests/empty_config.toml")
no_cfg_btm_command()
.arg("-r")
.arg("100-1000")
.assert()
@ -110,9 +94,7 @@ fn test_invalid_rate() {
#[test]
fn test_conflicting_temps() {
btm_command()
.arg("-C")
.arg("./tests/empty_config.toml")
no_cfg_btm_command()
.arg("-c")
.arg("-f")
.assert()
@ -122,9 +104,7 @@ fn test_conflicting_temps() {
#[test]
fn test_invalid_default_widget_1() {
btm_command()
.arg("-C")
.arg("./tests/empty_config.toml")
no_cfg_btm_command()
.arg("--default_widget_type")
.arg("fake_widget")
.assert()
@ -134,9 +114,7 @@ fn test_invalid_default_widget_1() {
#[test]
fn test_invalid_default_widget_2() {
btm_command()
.arg("-C")
.arg("./tests/empty_config.toml")
no_cfg_btm_command()
.arg("--default_widget_type")
.arg("cpu")
.arg("--default_widget_count")
@ -150,9 +128,7 @@ fn test_invalid_default_widget_2() {
#[test]
fn test_missing_default_widget_type() {
btm_command()
.arg("-C")
.arg("./tests/empty_config.toml")
no_cfg_btm_command()
.arg("--default_widget_count")
.arg("3")
.assert()
@ -165,7 +141,7 @@ fn test_missing_default_widget_type() {
#[test]
#[cfg_attr(feature = "battery", ignore)]
fn test_battery_flag() {
btm_command()
no_cfg_btm_command()
.arg("--battery")
.assert()
.failure()
@ -177,7 +153,7 @@ fn test_battery_flag() {
#[test]
#[cfg_attr(feature = "gpu", ignore)]
fn test_gpu_flag() {
btm_command()
no_cfg_btm_command()
.arg("--enable_gpu")
.assert()
.failure()

View File

@ -1,16 +1,13 @@
mod util;
//! These tests are for testing some invalid config-file-specific options.
use assert_cmd::prelude::*;
use predicates::prelude::*;
use util::*;
// These tests are for testing some config file-specific options.
use crate::util::btm_command;
#[test]
fn test_toml_mismatch_type() {
btm_command()
.arg("-C")
.arg("./tests/invalid_configs/toml_mismatch_type.toml")
btm_command(&["-C", "./tests/invalid_configs/toml_mismatch_type.toml"])
.assert()
.failure()
.stderr(predicate::str::contains("invalid type"));
@ -18,9 +15,7 @@ fn test_toml_mismatch_type() {
#[test]
fn test_empty_layout() {
btm_command()
.arg("-C")
.arg("./tests/invalid_configs/empty_layout.toml")
btm_command(&["-C", "./tests/invalid_configs/empty_layout.toml"])
.assert()
.failure()
.stderr(predicate::str::contains("at least one widget"));
@ -28,21 +23,20 @@ fn test_empty_layout() {
#[test]
fn test_invalid_layout_widget_type() {
btm_command()
.arg("-C")
.arg("./tests/invalid_configs/invalid_layout_widget_type.toml")
.assert()
.failure()
.stderr(predicate::str::contains("invalid widget name"));
btm_command(&[
"-C",
"./tests/invalid_configs/invalid_layout_widget_type.toml",
])
.assert()
.failure()
.stderr(predicate::str::contains("invalid widget name"));
}
/// This test isn't really needed as this is technically covered by TOML spec.
/// However, I feel like it's worth checking anyways - not like it takes long.
#[test]
fn test_duplicate_temp_type() {
btm_command()
.arg("-C")
.arg("./tests/invalid_configs/duplicate_temp_type.toml")
btm_command(&["-C", "./tests/invalid_configs/duplicate_temp_type.toml"])
.assert()
.failure()
.stderr(predicate::str::contains("duplicate key"));
@ -51,9 +45,7 @@ fn test_duplicate_temp_type() {
/// Checks for if a hex is valid
#[test]
fn test_invalid_colour_hex() {
btm_command()
.arg("-C")
.arg("./tests/invalid_configs/invalid_colour_hex.toml")
btm_command(&["-C", "./tests/invalid_configs/invalid_colour_hex.toml"])
.assert()
.failure()
.stderr(predicate::str::contains("invalid hex color"));
@ -62,9 +54,7 @@ fn test_invalid_colour_hex() {
/// Checks for if a hex is too long
#[test]
fn test_invalid_colour_hex_2() {
btm_command()
.arg("-C")
.arg("./tests/invalid_configs/invalid_colour_hex_2.toml")
btm_command(&["-C", "./tests/invalid_configs/invalid_colour_hex_2.toml"])
.assert()
.failure()
.stderr(predicate::str::contains("invalid hex color"));
@ -74,9 +64,7 @@ fn test_invalid_colour_hex_2() {
/// boundary errors!
#[test]
fn test_invalid_colour_hex_3() {
btm_command()
.arg("-C")
.arg("./tests/invalid_configs/invalid_colour_hex_3.toml")
btm_command(&["-C", "./tests/invalid_configs/invalid_colour_hex_3.toml"])
.assert()
.failure()
.stderr(predicate::str::contains("invalid hex color"));
@ -84,9 +72,7 @@ fn test_invalid_colour_hex_3() {
#[test]
fn test_invalid_colour_name() {
btm_command()
.arg("-C")
.arg("./tests/invalid_configs/invalid_colour_name.toml")
btm_command(&["-C", "./tests/invalid_configs/invalid_colour_name.toml"])
.assert()
.failure()
.stderr(predicate::str::contains("invalid named color"));
@ -94,9 +80,7 @@ fn test_invalid_colour_name() {
#[test]
fn test_invalid_colour_rgb() {
btm_command()
.arg("-C")
.arg("./tests/invalid_configs/invalid_colour_rgb.toml")
btm_command(&["-C", "./tests/invalid_configs/invalid_colour_rgb.toml"])
.assert()
.failure()
.stderr(predicate::str::contains("invalid RGB"));
@ -104,9 +88,7 @@ fn test_invalid_colour_rgb() {
#[test]
fn test_invalid_colour_rgb_2() {
btm_command()
.arg("-C")
.arg("./tests/invalid_configs/invalid_colour_rgb_2.toml")
btm_command(&["-C", "./tests/invalid_configs/invalid_colour_rgb_2.toml"])
.assert()
.failure()
.stderr(predicate::str::contains("invalid RGB"));
@ -114,9 +96,7 @@ fn test_invalid_colour_rgb_2() {
#[test]
fn test_invalid_colour_string() {
btm_command()
.arg("-C")
.arg("./tests/invalid_configs/invalid_colour_string.toml")
btm_command(&["-C", "./tests/invalid_configs/invalid_colour_string.toml"])
.assert()
.failure()
.stderr(predicate::str::contains("invalid named color"));
@ -124,29 +104,29 @@ fn test_invalid_colour_string() {
#[test]
fn test_lone_default_widget_count() {
btm_command()
.arg("-C")
.arg("./tests/invalid_configs/lone_default_widget_count.toml")
.assert()
.failure()
.stderr(predicate::str::contains("it must be used with"));
btm_command(&[
"-C",
"./tests/invalid_configs/lone_default_widget_count.toml",
])
.assert()
.failure()
.stderr(predicate::str::contains("it must be used with"));
}
#[test]
fn test_invalid_default_widget_count() {
btm_command()
.arg("-C")
.arg("./tests/invalid_configs/invalid_default_widget_count.toml")
.assert()
.failure()
.stderr(predicate::str::contains("number too large"));
btm_command(&[
"-C",
"./tests/invalid_configs/invalid_default_widget_count.toml",
])
.assert()
.failure()
.stderr(predicate::str::contains("number too large"));
}
#[test]
fn test_invalid_process_column() {
btm_command()
.arg("-C")
.arg("./tests/invalid_configs/invalid_process_column.toml")
btm_command(&["-C", "./tests/invalid_configs/invalid_process_column.toml"])
.assert()
.failure()
.stderr(predicate::str::contains("doesn't match"));

11
tests/integration/main.rs Normal file
View File

@ -0,0 +1,11 @@
//! Integration tests for bottom.
mod util;
mod arg_tests;
mod invalid_config_tests;
mod layout_management_tests;
mod layout_movement_tests;
#[cfg(all(target_arch = "x86_64", target_os = "linux"))]
mod valid_config_tests;

144
tests/integration/util.rs Normal file
View File

@ -0,0 +1,144 @@
use std::{env, ffi::OsString, path::Path, process::Command};
use hashbrown::HashMap;
#[cfg(all(target_arch = "x86_64", target_os = "linux"))]
use portable_pty::{native_pty_system, Child, CommandBuilder, MasterPty, PtySize};
pub fn abs_path(path: &str) -> OsString {
Path::new(path).canonicalize().unwrap().into_os_string()
}
/// Returns a QEMU runner target given an architecture.
fn get_qemu_target(arch: &str) -> &str {
match arch {
"armv7" => "arm",
"i686" => "i386",
"powerpc" => "ppc",
"powerpc64le" => "ppc64le",
_ => arch,
}
}
/// This is required since running binary tests via cross can cause be tricky! We need to basically "magically" grab
/// the correct runner in some cases, which can be done by inspecting env variables that should only show up while
/// using cross.
///
/// Originally inspired by [ripgrep's test files](https://cs.github.com/BurntSushi/ripgrep/blob/9f0e88bcb14e02da1b88872435b17d74786640b5/tests/util.rs#L470),
/// but adapted to work more generally with the architectures supported by bottom after looking through cross'
/// [linux-runner](https://github.com/cross-rs/cross/blob/main/docker/linux-runner) file.
fn cross_runner() -> Option<String> {
const TARGET_RUNNER: &str = "CARGO_TARGET_RUNNER";
const CROSS_RUNNER: &str = "CROSS_RUNNER";
let env_mapping = env::vars_os()
.filter_map(|(k, v)| {
let (k, v) = (k.to_string_lossy(), v.to_string_lossy());
if k.starts_with("CARGO_TARGET_") && k.ends_with("_RUNNER") && !v.is_empty() {
Some((TARGET_RUNNER.to_string(), v.to_string()))
} else if k == CROSS_RUNNER && !v.is_empty() {
Some((k.to_string(), v.to_string()))
} else {
None
}
})
.collect::<HashMap<_, _>>();
if let Some(cross_runner) = env_mapping.get(CROSS_RUNNER) {
if cross_runner == "qemu-user" {
env_mapping.get(TARGET_RUNNER).map(|target_runner| {
format!(
"qemu-{}",
get_qemu_target(target_runner.split_ascii_whitespace().last().unwrap())
)
})
} else {
None
}
} else {
env_mapping.get(TARGET_RUNNER).cloned()
}
}
const BTM_EXE_PATH: &str = env!("CARGO_BIN_EXE_btm");
const RUNNER_ENV_VARS: [(&str, &str); 1] = [("NO_COLOR", "1")];
const DEFAULT_CFG: [&str; 2] = ["-C", "./tests/valid_configs/empty_config.toml"];
/// Returns the [`Command`] of a binary invocation of bottom, alongside
/// any required env variables.
pub fn btm_command(args: &[&str]) -> Command {
let mut cmd = match cross_runner() {
None => Command::new(BTM_EXE_PATH),
Some(runner) => {
let mut cmd = Command::new(runner);
cmd.envs(RUNNER_ENV_VARS);
cmd.arg(BTM_EXE_PATH);
cmd
}
};
let mut prev = "";
for arg in args.iter() {
if prev == "-C" {
// This is the config file; make sure we set it to absolute path!
cmd.arg(abs_path(arg));
} else {
cmd.arg(arg);
}
prev = arg;
}
cmd
}
/// Returns the [`Command`] of a binary invocation of bottom, alongside
/// any required env variables, and with the default, empty config file.
pub fn no_cfg_btm_command() -> Command {
btm_command(&DEFAULT_CFG)
}
/// Spawns `btm` in a pty, returning the pair alongside a handle to the child.
#[cfg(all(target_arch = "x86_64", target_os = "linux"))]
pub fn spawn_btm_in_pty(args: &[&str]) -> (Box<dyn MasterPty>, Box<dyn Child>) {
let native_pty = native_pty_system();
let pair = native_pty
.openpty(PtySize {
rows: 100,
cols: 100,
pixel_width: 1,
pixel_height: 1,
})
.unwrap();
let btm_exe = BTM_EXE_PATH;
let mut cmd = match cross_runner() {
None => CommandBuilder::new(btm_exe),
Some(runner) => {
let mut cmd = CommandBuilder::new(runner);
for (env, val) in RUNNER_ENV_VARS {
cmd.env(env, val);
}
cmd.arg(BTM_EXE_PATH);
cmd
}
};
let args = if args.is_empty() { &DEFAULT_CFG } else { args };
let mut prev = "";
for arg in args.iter() {
if prev == "-C" {
// This is the config file; make sure we set it to absolute path!
cmd.arg(abs_path(arg));
} else {
cmd.arg(arg);
}
prev = arg;
}
(pair.master, pair.slave.spawn_command(cmd).unwrap())
}

View File

@ -0,0 +1,69 @@
//! Tests config files that have sometimes caused issues despite being valid.
use std::{io::Read, thread, time::Duration};
use crate::util::spawn_btm_in_pty;
fn reader_to_string(mut reader: Box<dyn Read>) -> String {
let mut buf = String::default();
reader.read_to_string(&mut buf).unwrap();
buf
}
fn run_and_kill(args: &[&str]) {
let (master, mut handle) = spawn_btm_in_pty(args);
let reader = master.try_clone_reader().unwrap();
let _ = master.take_writer().unwrap();
const TIMES_CHECKED: u64 = 6; // Check 6 times, once every 500ms, for 3 seconds total.
for _ in 0..TIMES_CHECKED {
thread::sleep(Duration::from_millis(500));
match handle.try_wait() {
Ok(Some(exit)) => {
println!("output: {}", reader_to_string(reader));
panic!("program terminated unexpectedly (exit status: {exit:?})");
}
Err(e) => {
println!("output: {}", reader_to_string(reader));
panic!("error while trying to wait: {e}")
}
_ => {}
}
}
handle.kill().unwrap();
}
#[test]
fn test_basic() {
run_and_kill(&[]);
}
/// A test to ensure that a bad config will fail the `run_and_kill` function.
#[test]
#[should_panic]
fn test_bad_basic() {
run_and_kill(&["--this_does_not_exist"]);
}
#[test]
fn test_empty() {
run_and_kill(&["-C", "./tests/valid_configs/empty_config.toml"]);
}
#[test]
fn test_many_proc() {
run_and_kill(&["-C", "./tests/valid_configs/many_proc.toml"]);
}
#[test]
fn test_all_proc() {
run_and_kill(&["-C", "./tests/valid_configs/all_proc.toml"]);
}
#[test]
fn test_cpu_doughnut() {
run_and_kill(&["-C", "./tests/valid_configs/cpu_doughnut.toml"]);
}

View File

@ -1,76 +0,0 @@
use std::{env, process::Command};
use hashbrown::HashMap;
/// Returns a QEMU runner target given an architecture.
fn get_qemu_target(arch: &str) -> &str {
match arch {
"armv7" => "arm",
"i686" => "i386",
"powerpc" => "ppc",
"powerpc64le" => "ppc64le",
_ => arch,
}
}
/// This is required since running binary tests via cross can cause be tricky! We need to basically "magically" grab
/// the correct runner in some cases, which can be done by inspecting env variables that should only show up while
/// using cross.
///
/// Originally inspired by [ripgrep's test files](https://cs.github.com/BurntSushi/ripgrep/blob/9f0e88bcb14e02da1b88872435b17d74786640b5/tests/util.rs#L470),
/// but adapted to work more generally with the architectures supported by bottom after looking through cross'
/// [linux-runner](https://github.com/cross-rs/cross/blob/main/docker/linux-runner) file.
fn cross_runner() -> Option<String> {
const TARGET_RUNNER: &str = "CARGO_TARGET_RUNNER";
const CROSS_RUNNER: &str = "CROSS_RUNNER";
let env_mapping = env::vars_os()
.filter_map(|(k, v)| {
let (k, v) = (k.to_string_lossy(), v.to_string_lossy());
if k.starts_with("CARGO_TARGET_") && k.ends_with("_RUNNER") && !v.is_empty() {
Some((TARGET_RUNNER.to_string(), v.to_string()))
} else if k == CROSS_RUNNER && !v.is_empty() {
Some((k.to_string(), v.to_string()))
} else {
None
}
})
.collect::<HashMap<_, _>>();
if let Some(cross_runner) = env_mapping.get(CROSS_RUNNER) {
if cross_runner == "qemu-user" {
env_mapping.get(TARGET_RUNNER).map(|target_runner| {
format!(
"qemu-{}",
get_qemu_target(
target_runner
.split_ascii_whitespace()
.collect::<Vec<_>>()
.last()
.unwrap()
)
)
})
} else {
None
}
} else {
env_mapping.get(TARGET_RUNNER).cloned()
}
}
/// Returns the [`Command`] of a binary invocation of bottom, alongside
/// any required env variables.
pub fn btm_command() -> Command {
let btm_exe = env!("CARGO_BIN_EXE_btm");
match cross_runner() {
None => Command::new(btm_exe),
Some(runner) => {
let mut cmd = Command::new(runner);
cmd.env("NO_COLOR", "1");
cmd.arg(btm_exe);
cmd
}
}
}

View File

@ -0,0 +1,22 @@
[[row]]
ratio = 30
[[row.child]]
type = "proc"
[[row]]
ratio = 40
[[row.child]]
ratio = 4
type = "proc"
[[row.child]]
ratio = 3
[[row.child.child]]
type = "proc"
[[row.child.child]]
type = "proc"
[[row]]
ratio = 30
[[row.child]]
type = "proc"
[[row.child]]
type = "proc"
default = true

View File

@ -0,0 +1,29 @@
[[row]]
[[row.child]]
type = "cpu"
[[row.child]]
type = "cpu"
[[row.child]]
type = "cpu"
[[row]]
[[row.child]]
[[row.child.child]]
type = "cpu"
[[row.child.child]]
type = "cpu"
[[row.child]]
type = "empty"
[[row.child]]
[[row.child.child]]
type = "cpu"
[[row.child.child]]
type = "cpu"
[[row]]
[[row.child]]
type = "cpu"
[[row.child]]
type = "cpu"
[[row.child]]
type = "cpu"

View File

@ -0,0 +1,22 @@
[[row]]
ratio = 30
[[row.child]]
type = "cpu"
[[row]]
ratio = 40
[[row.child]]
ratio = 4
type = "mem"
[[row.child]]
ratio = 3
[[row.child.child]]
type = "proc"
[[row.child.child]]
type = "proc"
[[row]]
ratio = 30
[[row.child]]
type = "net"
[[row.child]]
type = "proc"
default = true