From dd66ae774ca9a93a2c1bd471e3b1ba2075044f15 Mon Sep 17 00:00:00 2001 From: Clement Tsang <34804052+ClementTsang@users.noreply.github.com> Date: Mon, 15 Jan 2024 04:19:18 -0500 Subject: [PATCH] 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 --- .github/workflows/ci.yml | 2 +- Cargo.lock | 150 +++++++++- Cargo.toml | 3 + src/app/layout_manager.rs | 273 +++++++++--------- src/canvas.rs | 91 +++--- src/options.rs | 16 +- src/options/config/layout.rs | 84 +++--- tests/{ => integration}/arg_tests.rs | 56 +--- .../{ => integration}/invalid_config_tests.rs | 88 +++--- .../layout_management_tests.rs | 0 .../layout_movement_tests.rs | 0 tests/integration/main.rs | 11 + tests/integration/util.rs | 144 +++++++++ tests/integration/valid_config_tests.rs | 69 +++++ tests/util.rs | 76 ----- tests/valid_configs/all_proc.toml | 22 ++ tests/valid_configs/cpu_doughnut.toml | 29 ++ tests/{ => valid_configs}/empty_config.toml | 0 tests/valid_configs/many_proc.toml | 22 ++ 19 files changed, 724 insertions(+), 412 deletions(-) rename tests/{ => integration}/arg_tests.rs (76%) rename tests/{ => integration}/invalid_config_tests.rs (53%) rename tests/{ => integration}/layout_management_tests.rs (100%) rename tests/{ => integration}/layout_movement_tests.rs (100%) create mode 100644 tests/integration/main.rs create mode 100644 tests/integration/util.rs create mode 100644 tests/integration/valid_config_tests.rs delete mode 100644 tests/util.rs create mode 100644 tests/valid_configs/all_proc.toml create mode 100644 tests/valid_configs/cpu_doughnut.toml rename tests/{ => valid_configs}/empty_config.toml (100%) create mode 100644 tests/valid_configs/many_proc.toml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index bff02a44..07d78d56 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -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, } diff --git a/Cargo.lock b/Cargo.lock index 30e79e1a..f7f171f9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -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" diff --git a/Cargo.toml b/Cargo.toml index 39a0cfce..45805ddc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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" diff --git a/src/app/layout_manager.rs b/src/app/layout_manager.rs index ca353f52..e7904c9a 100644 --- a/src/app/layout_manager.rs +++ b/src/app/layout_manager.rs @@ -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 }, + Grow { minimum: Option }, +} -// pub struct BottomContainer { -// children: Vec, -// 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, 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) -> Self { + self.constraint = IntermediaryConstraint::Grow { minimum }; self } } @@ -768,9 +762,7 @@ impl BottomRow { pub struct BottomCol { pub children: Vec, 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) -> Self { + self.constraint = IntermediaryConstraint::Grow { minimum }; self } } @@ -809,9 +799,7 @@ impl BottomCol { pub struct BottomColRow { pub children: Vec, 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) -> 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, pub right_neighbour: Option, pub up_neighbour: Option, pub down_neighbour: Option, - /// 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) -> 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) -> 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 | +--------------------------+ ", ))) diff --git a/src/canvas.rs b/src/canvas.rs index 3a02803b..0f3e7202 100644 --- a/src/canvas.rs +++ b/src/canvas.rs @@ -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 { + pub fn init(layout: BottomLayout, styling: CanvasStyling) -> anyhow::Result { // 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(), }; diff --git a/src/options.rs b/src/options.rs index 86d44e75..95c780f6 100644 --- a/src/options.rs +++ b/src/options.rs @@ -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 diff --git a/src/options/config/layout.rs b/src/options/config/layout.rs index 4b3e587a..45c2a0ab 100644 --- a/src/options/config/layout.rs +++ b/src/options/config/layout.rs @@ -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 = Vec::new(); for widget in child { let widget_type = widget.widget_type.parse::()?; *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)) } } diff --git a/tests/arg_tests.rs b/tests/integration/arg_tests.rs similarity index 76% rename from tests/arg_tests.rs rename to tests/integration/arg_tests.rs index af4df7b8..13ebc5f0 100644 --- a/tests/arg_tests.rs +++ b/tests/integration/arg_tests.rs @@ -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() diff --git a/tests/invalid_config_tests.rs b/tests/integration/invalid_config_tests.rs similarity index 53% rename from tests/invalid_config_tests.rs rename to tests/integration/invalid_config_tests.rs index 62e2a376..92a67eac 100644 --- a/tests/invalid_config_tests.rs +++ b/tests/integration/invalid_config_tests.rs @@ -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")); diff --git a/tests/layout_management_tests.rs b/tests/integration/layout_management_tests.rs similarity index 100% rename from tests/layout_management_tests.rs rename to tests/integration/layout_management_tests.rs diff --git a/tests/layout_movement_tests.rs b/tests/integration/layout_movement_tests.rs similarity index 100% rename from tests/layout_movement_tests.rs rename to tests/integration/layout_movement_tests.rs diff --git a/tests/integration/main.rs b/tests/integration/main.rs new file mode 100644 index 00000000..01c3f9f5 --- /dev/null +++ b/tests/integration/main.rs @@ -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; diff --git a/tests/integration/util.rs b/tests/integration/util.rs new file mode 100644 index 00000000..e1ee1ffd --- /dev/null +++ b/tests/integration/util.rs @@ -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 { + 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::>(); + + 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, Box) { + 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()) +} diff --git a/tests/integration/valid_config_tests.rs b/tests/integration/valid_config_tests.rs new file mode 100644 index 00000000..b152edb3 --- /dev/null +++ b/tests/integration/valid_config_tests.rs @@ -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) -> 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"]); +} diff --git a/tests/util.rs b/tests/util.rs deleted file mode 100644 index b89a3027..00000000 --- a/tests/util.rs +++ /dev/null @@ -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 { - 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::>(); - - 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::>() - .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 - } - } -} diff --git a/tests/valid_configs/all_proc.toml b/tests/valid_configs/all_proc.toml new file mode 100644 index 00000000..781e3836 --- /dev/null +++ b/tests/valid_configs/all_proc.toml @@ -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 diff --git a/tests/valid_configs/cpu_doughnut.toml b/tests/valid_configs/cpu_doughnut.toml new file mode 100644 index 00000000..c099078f --- /dev/null +++ b/tests/valid_configs/cpu_doughnut.toml @@ -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" diff --git a/tests/empty_config.toml b/tests/valid_configs/empty_config.toml similarity index 100% rename from tests/empty_config.toml rename to tests/valid_configs/empty_config.toml diff --git a/tests/valid_configs/many_proc.toml b/tests/valid_configs/many_proc.toml new file mode 100644 index 00000000..440eb469 --- /dev/null +++ b/tests/valid_configs/many_proc.toml @@ -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