refactor: Refactor code, add new tests

Refactor code so we use a lib, allowing for easier testing. Adds additional tests for layouts.
This commit is contained in:
Clement Tsang 2020-08-19 13:32:33 -07:00 committed by GitHub
parent f6aa8e5d1d
commit 4b03b4b0b0
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 689 additions and 199 deletions

View file

@ -38,9 +38,11 @@ If you want to help contribute by submitting a PR, by all means, I'm open! In re
- I develop primarily using _stable_ Rust. That is, whatever is the most up-to-date stable version you can get via running - I develop primarily using _stable_ Rust. That is, whatever is the most up-to-date stable version you can get via running
`rustup update stable`. `rustup update stable`.
- There are some tests, they're mostly for sanity checks. Please run `cargo test` to ensure you didn't break anything important, unless the change will break the test (in which case please amend the tests).
- Note that `cargo test` will fail on anything lower than 1.43.0 due to it using a then-introduced env variable. - Note that `cargo test` will fail on anything lower than 1.43.0 due to it using a then-introduced env variable.
- I use both [clippy](https://github.com/rust-lang/rust-clippy) and [rustfmt](https://github.com/rust-lang/rustfmt) in development (with some settings, see [clippy.toml](./clippy.toml) and [rustfmt.toml](rustfmt.toml)). Note clippy must pass to pass CI. - I use both [clippy](https://github.com/rust-lang/rust-clippy) and [rustfmt](https://github.com/rust-lang/rustfmt) in development (with some settings, see [clippy.toml](./clippy.toml) and [rustfmt.toml](rustfmt.toml)). Note clippy must pass to for PRs to be accepted.
- You can check clippy using `cargo +nightly clippy`. - You can check clippy using `cargo +nightly clippy`.

View file

@ -9,10 +9,12 @@ license = "MIT"
categories = ["command-line-utilities", "visualization"] categories = ["command-line-utilities", "visualization"]
description = "A cross-platform graphical process/system monitor with a customizable interface and a multitude of features. Supports Linux, macOS, and Windows." description = "A cross-platform graphical process/system monitor with a customizable interface and a multitude of features. Supports Linux, macOS, and Windows."
readme = "README.md" readme = "README.md"
default-run = "btm"
[[bin]] [[bin]]
name = "btm" name = "btm"
path = "src/main.rs" path = "src/bin/main.rs"
doc = false
[profile.release] [profile.release]
debug = 1 debug = 1

185
src/bin/main.rs Normal file
View file

@ -0,0 +1,185 @@
#![warn(rust_2018_idioms)]
#[allow(unused_imports)]
#[macro_use]
extern crate log;
use bottom::{canvas, constants::*, data_conversion::*, options::*, utils::error, *};
use std::{
boxed::Box,
io::{stdout, Write},
panic,
sync::mpsc,
thread,
time::Duration,
};
use crossterm::{
event::EnableMouseCapture,
execute,
terminal::{enable_raw_mode, EnterAlternateScreen},
};
use tui::{backend::CrosstermBackend, Terminal};
fn main() -> error::Result<()> {
#[cfg(debug_assertions)]
{
utils::logging::init_logger()?;
}
let matches = get_matches();
let config: Config = create_config(matches.value_of("CONFIG_LOCATION"))?;
// Get widget layout separately
let (widget_layout, default_widget_id, default_widget_type_option) =
get_widget_layout(&matches, &config)?;
// Create "app" struct, which will control most of the program and store settings/state
let mut app = build_app(
&matches,
&config,
&widget_layout,
default_widget_id,
&default_widget_type_option,
)?;
// Create painter and set colours.
let mut painter = canvas::Painter::init(widget_layout, app.app_config_fields.table_gap);
generate_config_colours(&config, &mut painter)?;
painter.colours.generate_remaining_cpu_colours();
painter.complete_painter_init();
// Set up input handling
let (sender, receiver) = mpsc::channel();
create_input_thread(sender.clone());
// Cleaning loop
{
let cleaning_sender = sender.clone();
thread::spawn(move || loop {
thread::sleep(Duration::from_millis(
constants::STALE_MAX_MILLISECONDS + 5000,
));
if cleaning_sender.send(BottomEvent::Clean).is_err() {
break;
}
});
}
// Event loop
let (reset_sender, reset_receiver) = mpsc::channel();
create_event_thread(
sender,
reset_receiver,
app.app_config_fields.use_current_cpu_total,
app.app_config_fields.update_rate_in_milliseconds,
app.app_config_fields.temperature_type.clone(),
app.app_config_fields.show_average_cpu,
app.used_widgets.clone(),
);
// Set up up tui and crossterm
let mut stdout_val = stdout();
execute!(stdout_val, EnterAlternateScreen, EnableMouseCapture)?;
enable_raw_mode()?;
let mut terminal = Terminal::new(CrosstermBackend::new(stdout_val))?;
terminal.hide_cursor()?;
// Set panic hook
panic::set_hook(Box::new(|info| panic_hook(info)));
loop {
if let Ok(recv) = receiver.recv_timeout(Duration::from_millis(TICK_RATE_IN_MILLISECONDS)) {
match recv {
BottomEvent::KeyInput(event) => {
if handle_key_event_or_break(event, &mut app, &reset_sender) {
break;
}
handle_force_redraws(&mut app);
}
BottomEvent::MouseInput(event) => {
handle_mouse_event(event, &mut app);
handle_force_redraws(&mut app);
}
BottomEvent::Update(data) => {
app.data_collection.eat_data(&data);
if !app.is_frozen {
// Convert all data into tui-compliant components
// Network
if app.used_widgets.use_net {
let network_data = convert_network_data_points(
&app.data_collection,
false,
app.app_config_fields.use_basic_mode
|| app.app_config_fields.use_old_network_legend,
);
app.canvas_data.network_data_rx = network_data.rx;
app.canvas_data.network_data_tx = network_data.tx;
app.canvas_data.rx_display = network_data.rx_display;
app.canvas_data.tx_display = network_data.tx_display;
if let Some(total_rx_display) = network_data.total_rx_display {
app.canvas_data.total_rx_display = total_rx_display;
}
if let Some(total_tx_display) = network_data.total_tx_display {
app.canvas_data.total_tx_display = total_tx_display;
}
}
// Disk
if app.used_widgets.use_disk {
app.canvas_data.disk_data = convert_disk_row(&app.data_collection);
}
// Temperatures
if app.used_widgets.use_temp {
app.canvas_data.temp_sensor_data = convert_temp_row(&app);
}
// Memory
if app.used_widgets.use_mem {
app.canvas_data.mem_data =
convert_mem_data_points(&app.data_collection, false);
app.canvas_data.swap_data =
convert_swap_data_points(&app.data_collection, false);
let memory_and_swap_labels = convert_mem_labels(&app.data_collection);
app.canvas_data.mem_label_percent = memory_and_swap_labels.0;
app.canvas_data.mem_label_frac = memory_and_swap_labels.1;
app.canvas_data.swap_label_percent = memory_and_swap_labels.2;
app.canvas_data.swap_label_frac = memory_and_swap_labels.3;
}
if app.used_widgets.use_cpu {
// CPU
app.canvas_data.cpu_data =
convert_cpu_data_points(&app.data_collection, false);
}
// Processes
if app.used_widgets.use_proc {
update_all_process_lists(&mut app);
}
// Battery
if app.used_widgets.use_battery {
app.canvas_data.battery_data =
convert_battery_harvest(&app.data_collection);
}
}
}
BottomEvent::Clean => {
app.data_collection
.clean_data(constants::STALE_MAX_MILLISECONDS);
}
}
}
// TODO: [OPT] Should not draw if no change (ie: scroll max)
try_drawing(&mut terminal, &mut app, &mut painter)?;
}
cleanup_terminal(&mut terminal)?;
Ok(())
}

View file

@ -1,14 +1,7 @@
#![warn(rust_2018_idioms)]
#[allow(unused_imports)]
#[macro_use]
extern crate log;
use std::{ use std::{
boxed::Box, boxed::Box,
io::{stdout, Write}, io::{stdout, Write},
panic::{self, PanicInfo}, panic::PanicInfo,
sync::mpsc,
thread, thread,
time::{Duration, Instant}, time::{Duration, Instant},
}; };
@ -16,15 +9,11 @@ use std::{
use clap::*; use clap::*;
use crossterm::{ use crossterm::{
event::{ event::{poll, read, DisableMouseCapture, Event, KeyCode, KeyEvent, KeyModifiers, MouseEvent},
poll, read, DisableMouseCapture, EnableMouseCapture, Event, KeyCode, KeyEvent,
KeyModifiers, MouseEvent,
},
execute, execute,
style::Print, style::Print,
terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen}, terminal::{disable_raw_mode, LeaveAlternateScreen},
}; };
use tui::{backend::CrosstermBackend, Terminal};
use app::{ use app::{
data_harvester::{self, processes::ProcessSorting}, data_harvester::{self, processes::ProcessSorting},
@ -38,30 +27,29 @@ use utils::error;
pub mod app; pub mod app;
mod utils { pub mod utils {
pub mod error; pub mod error;
pub mod gen_util; pub mod gen_util;
pub mod logging; pub mod logging;
} }
mod canvas; pub mod canvas;
mod constants; pub mod constants;
mod data_conversion; pub mod data_conversion;
pub mod options; pub mod options;
enum BottomEvent<I, J> { pub enum BottomEvent<I, J> {
KeyInput(I), KeyInput(I),
MouseInput(J), MouseInput(J),
Update(Box<data_harvester::Data>), Update(Box<data_harvester::Data>),
Clean, Clean,
} }
enum ResetEvent { pub enum ResetEvent {
Reset, Reset,
} }
fn get_matches() -> clap::ArgMatches<'static> { pub fn get_matches() -> clap::ArgMatches<'static> {
clap_app!(app => clap_app!(app =>
(name: crate_name!()) (name: crate_name!())
(version: crate_version!()) (version: crate_version!())
@ -96,163 +84,7 @@ fn get_matches() -> clap::ArgMatches<'static> {
.get_matches() .get_matches()
} }
fn main() -> error::Result<()> { pub fn handle_mouse_event(event: MouseEvent, app: &mut App) {
#[cfg(debug_assertions)]
{
utils::logging::init_logger()?;
}
let matches = get_matches();
let config: Config = create_config(matches.value_of("CONFIG_LOCATION"))?;
// Get widget layout separately
let (widget_layout, default_widget_id) = get_widget_layout(&matches, &config)?;
// Create "app" struct, which will control most of the program and store settings/state
let mut app = build_app(&matches, &config, &widget_layout, default_widget_id)?;
// Create painter and set colours.
let mut painter = canvas::Painter::init(widget_layout, app.app_config_fields.table_gap);
generate_config_colours(&config, &mut painter)?;
painter.colours.generate_remaining_cpu_colours();
painter.complete_painter_init();
// Set up input handling
let (sender, receiver) = mpsc::channel();
create_input_thread(sender.clone());
// Cleaning loop
{
let cleaning_sender = sender.clone();
thread::spawn(move || loop {
thread::sleep(Duration::from_millis(
constants::STALE_MAX_MILLISECONDS + 5000,
));
if cleaning_sender.send(BottomEvent::Clean).is_err() {
break;
}
});
}
// Event loop
let (reset_sender, reset_receiver) = mpsc::channel();
create_event_thread(
sender,
reset_receiver,
app.app_config_fields.use_current_cpu_total,
app.app_config_fields.update_rate_in_milliseconds,
app.app_config_fields.temperature_type.clone(),
app.app_config_fields.show_average_cpu,
app.used_widgets.clone(),
);
// Set up up tui and crossterm
let mut stdout_val = stdout();
execute!(stdout_val, EnterAlternateScreen, EnableMouseCapture)?;
enable_raw_mode()?;
let mut terminal = Terminal::new(CrosstermBackend::new(stdout_val))?;
terminal.hide_cursor()?;
// Set panic hook
panic::set_hook(Box::new(|info| panic_hook(info)));
loop {
if let Ok(recv) = receiver.recv_timeout(Duration::from_millis(TICK_RATE_IN_MILLISECONDS)) {
match recv {
BottomEvent::KeyInput(event) => {
if handle_key_event_or_break(event, &mut app, &reset_sender) {
break;
}
handle_force_redraws(&mut app);
}
BottomEvent::MouseInput(event) => {
handle_mouse_event(event, &mut app);
handle_force_redraws(&mut app);
}
BottomEvent::Update(data) => {
app.data_collection.eat_data(&data);
if !app.is_frozen {
// Convert all data into tui-compliant components
// Network
if app.used_widgets.use_net {
let network_data = convert_network_data_points(
&app.data_collection,
false,
app.app_config_fields.use_basic_mode
|| app.app_config_fields.use_old_network_legend,
);
app.canvas_data.network_data_rx = network_data.rx;
app.canvas_data.network_data_tx = network_data.tx;
app.canvas_data.rx_display = network_data.rx_display;
app.canvas_data.tx_display = network_data.tx_display;
if let Some(total_rx_display) = network_data.total_rx_display {
app.canvas_data.total_rx_display = total_rx_display;
}
if let Some(total_tx_display) = network_data.total_tx_display {
app.canvas_data.total_tx_display = total_tx_display;
}
}
// Disk
if app.used_widgets.use_disk {
app.canvas_data.disk_data = convert_disk_row(&app.data_collection);
}
// Temperatures
if app.used_widgets.use_temp {
app.canvas_data.temp_sensor_data = convert_temp_row(&app);
}
// Memory
if app.used_widgets.use_mem {
app.canvas_data.mem_data =
convert_mem_data_points(&app.data_collection, false);
app.canvas_data.swap_data =
convert_swap_data_points(&app.data_collection, false);
let memory_and_swap_labels = convert_mem_labels(&app.data_collection);
app.canvas_data.mem_label_percent = memory_and_swap_labels.0;
app.canvas_data.mem_label_frac = memory_and_swap_labels.1;
app.canvas_data.swap_label_percent = memory_and_swap_labels.2;
app.canvas_data.swap_label_frac = memory_and_swap_labels.3;
}
if app.used_widgets.use_cpu {
// CPU
app.canvas_data.cpu_data =
convert_cpu_data_points(&app.data_collection, false);
}
// Processes
if app.used_widgets.use_proc {
update_all_process_lists(&mut app);
}
// Battery
if app.used_widgets.use_battery {
app.canvas_data.battery_data =
convert_battery_harvest(&app.data_collection);
}
}
}
BottomEvent::Clean => {
app.data_collection
.clean_data(constants::STALE_MAX_MILLISECONDS);
}
}
}
// TODO: [OPT] Should not draw if no change (ie: scroll max)
try_drawing(&mut terminal, &mut app, &mut painter)?;
}
cleanup_terminal(&mut terminal)?;
Ok(())
}
fn handle_mouse_event(event: MouseEvent, app: &mut App) {
match event { match event {
MouseEvent::ScrollUp(_x, _y, _modifiers) => app.handle_scroll_up(), MouseEvent::ScrollUp(_x, _y, _modifiers) => app.handle_scroll_up(),
MouseEvent::ScrollDown(_x, _y, _modifiers) => app.handle_scroll_down(), MouseEvent::ScrollDown(_x, _y, _modifiers) => app.handle_scroll_down(),
@ -260,7 +92,7 @@ fn handle_mouse_event(event: MouseEvent, app: &mut App) {
}; };
} }
fn handle_key_event_or_break( pub fn handle_key_event_or_break(
event: KeyEvent, app: &mut App, reset_sender: &std::sync::mpsc::Sender<ResetEvent>, event: KeyEvent, app: &mut App, reset_sender: &std::sync::mpsc::Sender<ResetEvent>,
) -> bool { ) -> bool {
// debug!("KeyEvent: {:?}", event); // debug!("KeyEvent: {:?}", event);
@ -348,7 +180,7 @@ fn handle_key_event_or_break(
false false
} }
fn create_config(flag_config_location: Option<&str>) -> error::Result<Config> { pub fn create_config(flag_config_location: Option<&str>) -> error::Result<Config> {
use std::{ffi::OsString, fs}; use std::{ffi::OsString, fs};
let config_path = if let Some(conf_loc) = flag_config_location { let config_path = if let Some(conf_loc) = flag_config_location {
Some(OsString::from(conf_loc)) Some(OsString::from(conf_loc))
@ -399,7 +231,7 @@ fn create_config(flag_config_location: Option<&str>) -> error::Result<Config> {
} }
} }
fn try_drawing( pub fn try_drawing(
terminal: &mut tui::terminal::Terminal<tui::backend::CrosstermBackend<std::io::Stdout>>, terminal: &mut tui::terminal::Terminal<tui::backend::CrosstermBackend<std::io::Stdout>>,
app: &mut App, painter: &mut canvas::Painter, app: &mut App, painter: &mut canvas::Painter,
) -> error::Result<()> { ) -> error::Result<()> {
@ -411,7 +243,7 @@ fn try_drawing(
Ok(()) Ok(())
} }
fn cleanup_terminal( pub fn cleanup_terminal(
terminal: &mut tui::terminal::Terminal<tui::backend::CrosstermBackend<std::io::Stdout>>, terminal: &mut tui::terminal::Terminal<tui::backend::CrosstermBackend<std::io::Stdout>>,
) -> error::Result<()> { ) -> error::Result<()> {
disable_raw_mode()?; disable_raw_mode()?;
@ -425,7 +257,9 @@ fn cleanup_terminal(
Ok(()) Ok(())
} }
fn generate_config_colours(config: &Config, painter: &mut canvas::Painter) -> error::Result<()> { pub fn generate_config_colours(
config: &Config, painter: &mut canvas::Painter,
) -> error::Result<()> {
if let Some(colours) = &config.colors { if let Some(colours) = &config.colors {
if let Some(border_color) = &colours.border_color { if let Some(border_color) = &colours.border_color {
painter.colours.set_border_colour(border_color)?; painter.colours.set_border_colour(border_color)?;
@ -514,7 +348,7 @@ fn generate_config_colours(config: &Config, painter: &mut canvas::Painter) -> er
} }
/// Based on https://github.com/Rigellute/spotify-tui/blob/master/src/main.rs /// Based on https://github.com/Rigellute/spotify-tui/blob/master/src/main.rs
fn panic_hook(panic_info: &PanicInfo<'_>) { pub fn panic_hook(panic_info: &PanicInfo<'_>) {
let mut stdout = stdout(); let mut stdout = stdout();
let msg = match panic_info.payload().downcast_ref::<&'static str>() { let msg = match panic_info.payload().downcast_ref::<&'static str>() {
@ -543,7 +377,7 @@ fn panic_hook(panic_info: &PanicInfo<'_>) {
.unwrap(); .unwrap();
} }
fn handle_force_redraws(app: &mut App) { pub fn handle_force_redraws(app: &mut App) {
// Currently we use an Option... because we might want to future-proof this // Currently we use an Option... because we might want to future-proof this
// if we eventually get widget-specific redrawing! // if we eventually get widget-specific redrawing!
if app.proc_state.force_update_all { if app.proc_state.force_update_all {
@ -573,7 +407,7 @@ fn handle_force_redraws(app: &mut App) {
} }
} }
fn update_all_process_lists(app: &mut App) { pub fn update_all_process_lists(app: &mut App) {
let widget_ids = app let widget_ids = app
.proc_state .proc_state
.widget_states .widget_states
@ -586,7 +420,7 @@ fn update_all_process_lists(app: &mut App) {
}); });
} }
fn update_final_process_list(app: &mut App, widget_id: u64) { pub fn update_final_process_list(app: &mut App, widget_id: u64) {
let is_invalid_or_blank = match app.proc_state.widget_states.get(&widget_id) { let is_invalid_or_blank = match app.proc_state.widget_states.get(&widget_id) {
Some(process_state) => process_state Some(process_state) => process_state
.process_search_state .process_search_state
@ -659,7 +493,7 @@ fn update_final_process_list(app: &mut App, widget_id: u64) {
} }
} }
fn sort_process_data( pub fn sort_process_data(
to_sort_vec: &mut Vec<ConvertedProcessData>, proc_widget_state: &app::ProcWidgetState, to_sort_vec: &mut Vec<ConvertedProcessData>, proc_widget_state: &app::ProcWidgetState,
) { ) {
to_sort_vec.sort_by(|a, b| { to_sort_vec.sort_by(|a, b| {
@ -763,7 +597,7 @@ fn sort_process_data(
} }
} }
fn create_input_thread( pub fn create_input_thread(
sender: std::sync::mpsc::Sender< sender: std::sync::mpsc::Sender<
BottomEvent<crossterm::event::KeyEvent, crossterm::event::MouseEvent>, BottomEvent<crossterm::event::KeyEvent, crossterm::event::MouseEvent>,
>, >,
@ -796,7 +630,7 @@ fn create_input_thread(
}); });
} }
fn create_event_thread( pub fn create_event_thread(
sender: std::sync::mpsc::Sender< sender: std::sync::mpsc::Sender<
BottomEvent<crossterm::event::KeyEvent, crossterm::event::MouseEvent>, BottomEvent<crossterm::event::KeyEvent, crossterm::event::MouseEvent>,
>, >,

View file

@ -10,7 +10,7 @@ use crate::{
use layout_options::*; use layout_options::*;
mod layout_options; pub mod layout_options;
#[derive(Default, Deserialize)] #[derive(Default, Deserialize)]
pub struct Config { pub struct Config {
@ -68,7 +68,7 @@ pub struct ConfigColours {
pub fn build_app( pub fn build_app(
matches: &clap::ArgMatches<'static>, config: &Config, widget_layout: &BottomLayout, matches: &clap::ArgMatches<'static>, config: &Config, widget_layout: &BottomLayout,
default_widget_id: u64, default_widget_id: u64, default_widget_type_option: &Option<BottomWidgetType>,
) -> error::Result<App> { ) -> error::Result<App> {
use BottomWidgetType::*; use BottomWidgetType::*;
let autohide_time = get_autohide_time(&matches, &config); let autohide_time = get_autohide_time(&matches, &config);
@ -96,7 +96,6 @@ pub fn build_app(
None None
}; };
let (default_widget_type_option, _) = get_default_widget_and_count(matches, config)?;
let mut initial_widget_id: u64 = default_widget_id; let mut initial_widget_id: u64 = default_widget_id;
let mut initial_widget_type = Proc; let mut initial_widget_type = Proc;
let is_custom_layout = config.row.is_some(); let is_custom_layout = config.row.is_some();
@ -252,7 +251,7 @@ pub fn build_app(
pub fn get_widget_layout( pub fn get_widget_layout(
matches: &clap::ArgMatches<'static>, config: &Config, matches: &clap::ArgMatches<'static>, config: &Config,
) -> error::Result<(BottomLayout, u64)> { ) -> error::Result<(BottomLayout, u64, Option<BottomWidgetType>)> {
let left_legend = get_use_left_legend(matches, config); let left_legend = get_use_left_legend(matches, config);
let (default_widget_type, mut default_widget_count) = let (default_widget_type, mut default_widget_count) =
get_default_widget_and_count(matches, config)?; get_default_widget_and_count(matches, config)?;
@ -311,7 +310,7 @@ pub fn get_widget_layout(
} }
}; };
Ok((bottom_layout, default_widget_id)) Ok((bottom_layout, default_widget_id, default_widget_type))
} }
fn get_update_rate_in_milliseconds( fn get_update_rate_in_milliseconds(

View file

@ -0,0 +1,469 @@
//! Mocks layout management, so we can check if we broke anything.
use bottom::app::layout_manager::{BottomLayout, BottomWidgetType};
use bottom::constants::{DEFAULT_BATTERY_LAYOUT, DEFAULT_LAYOUT, DEFAULT_WIDGET_ID};
use bottom::options::{layout_options::Row, Config};
use bottom::utils::error;
const PROC_LAYOUT: &str = r##"
[[row]]
[[row.child]]
type="proc"
[[row]]
[[row.child]]
type="proc"
[[row.child]]
type="proc"
[[row]]
[[row.child]]
type="proc"
[[row.child]]
type="proc"
"##;
fn test_create_layout(
rows: &Vec<Row>, default_widget_id: u64, default_widget_type: Option<BottomWidgetType>,
default_widget_count: u64, left_legend: bool,
) -> BottomLayout {
let mut iter_id = 0; // A lazy way of forcing unique IDs *shrugs*
let mut total_height_ratio = 0;
let mut default_widget_count = default_widget_count;
let mut default_widget_id = default_widget_id;
let mut ret_bottom_layout = BottomLayout {
rows: rows
.iter()
.map(|row| {
row.convert_row_to_bottom_row(
&mut iter_id,
&mut total_height_ratio,
&mut default_widget_id,
&default_widget_type,
&mut default_widget_count,
left_legend,
)
})
.collect::<error::Result<Vec<_>>>()
.unwrap(),
total_row_height_ratio: total_height_ratio,
};
ret_bottom_layout.get_movement_mappings();
ret_bottom_layout
}
#[test]
/// Tests the default setup.
fn test_default_movement() {
let rows = toml::from_str::<Config>(DEFAULT_LAYOUT)
.unwrap()
.row
.unwrap();
let ret_bottom_layout = test_create_layout(&rows, DEFAULT_WIDGET_ID, None, 1, false);
// Simple tests for the top CPU widget
assert_eq!(
ret_bottom_layout.rows[0].children[0].children[0].children[0].down_neighbour,
Some(3)
);
assert_eq!(
ret_bottom_layout.rows[0].children[0].children[0].children[0].right_neighbour,
Some(2)
);
assert_eq!(
ret_bottom_layout.rows[0].children[0].children[0].children[0].left_neighbour,
None
);
assert_eq!(
ret_bottom_layout.rows[0].children[0].children[0].children[0].up_neighbour,
None
);
// Test CPU legend
assert_eq!(
ret_bottom_layout.rows[0].children[0].children[0].children[1].down_neighbour,
Some(4)
);
assert_eq!(
ret_bottom_layout.rows[0].children[0].children[0].children[1].right_neighbour,
None
);
assert_eq!(
ret_bottom_layout.rows[0].children[0].children[0].children[1].left_neighbour,
Some(1)
);
assert_eq!(
ret_bottom_layout.rows[0].children[0].children[0].children[1].up_neighbour,
None
);
// Test memory->temp, temp->disk, disk->memory mappings
assert_eq!(
ret_bottom_layout.rows[1].children[0].children[0].children[0].right_neighbour,
Some(4)
);
assert_eq!(
ret_bottom_layout.rows[1].children[1].children[0].children[0].down_neighbour,
Some(5)
);
assert_eq!(
ret_bottom_layout.rows[1].children[1].children[1].children[0].left_neighbour,
Some(3)
);
// Test disk -> processes, processes -> process sort, process sort -> network
assert_eq!(
ret_bottom_layout.rows[1].children[1].children[1].children[0].down_neighbour,
Some(7)
);
assert_eq!(
ret_bottom_layout.rows[2].children[1].children[0].children[1].left_neighbour,
Some(9)
);
assert_eq!(
ret_bottom_layout.rows[2].children[1].children[0].children[0].left_neighbour,
Some(6)
);
}
#[test]
/// Tests battery movement in the default setup.
fn test_default_battery_movement() {
let rows = toml::from_str::<Config>(DEFAULT_BATTERY_LAYOUT)
.unwrap()
.row
.unwrap();
let ret_bottom_layout = test_create_layout(&rows, DEFAULT_WIDGET_ID, None, 1, false);
// Simple tests for the top CPU widget
assert_eq!(
ret_bottom_layout.rows[0].children[0].children[0].children[0].down_neighbour,
Some(4)
);
assert_eq!(
ret_bottom_layout.rows[0].children[0].children[0].children[0].right_neighbour,
Some(2)
);
assert_eq!(
ret_bottom_layout.rows[0].children[0].children[0].children[0].left_neighbour,
None
);
assert_eq!(
ret_bottom_layout.rows[0].children[0].children[0].children[0].up_neighbour,
None
);
// Test CPU legend
assert_eq!(
ret_bottom_layout.rows[0].children[0].children[0].children[1].down_neighbour,
Some(5)
);
assert_eq!(
ret_bottom_layout.rows[0].children[0].children[0].children[1].right_neighbour,
Some(3)
);
assert_eq!(
ret_bottom_layout.rows[0].children[0].children[0].children[1].left_neighbour,
Some(1)
);
assert_eq!(
ret_bottom_layout.rows[0].children[0].children[0].children[1].up_neighbour,
None
);
}
#[test]
/// Tests using left_legend.
fn test_left_legend() {
let rows = toml::from_str::<Config>(DEFAULT_LAYOUT)
.unwrap()
.row
.unwrap();
let ret_bottom_layout = test_create_layout(&rows, DEFAULT_WIDGET_ID, None, 1, true);
// Legend
assert_eq!(
ret_bottom_layout.rows[0].children[0].children[0].children[0].down_neighbour,
Some(3)
);
assert_eq!(
ret_bottom_layout.rows[0].children[0].children[0].children[0].right_neighbour,
Some(1)
);
assert_eq!(
ret_bottom_layout.rows[0].children[0].children[0].children[0].left_neighbour,
None
);
assert_eq!(
ret_bottom_layout.rows[0].children[0].children[0].children[0].up_neighbour,
None
);
// Widget
assert_eq!(
ret_bottom_layout.rows[0].children[0].children[0].children[1].down_neighbour,
Some(3)
);
assert_eq!(
ret_bottom_layout.rows[0].children[0].children[0].children[1].right_neighbour,
None
);
assert_eq!(
ret_bottom_layout.rows[0].children[0].children[0].children[1].left_neighbour,
Some(2)
);
assert_eq!(
ret_bottom_layout.rows[0].children[0].children[0].children[1].up_neighbour,
None
);
}
#[test]
/// Tests explicit default widget.
fn test_default_widget_in_layout() {
let proc_layout = r##"
[[row]]
[[row.child]]
type="proc"
[[row]]
[[row.child]]
type="proc"
[[row.child]]
type="proc"
[[row]]
[[row.child]]
type="proc"
default=true
[[row.child]]
type="proc"
"##;
let rows = toml::from_str::<Config>(proc_layout).unwrap().row.unwrap();
let mut iter_id = 0; // A lazy way of forcing unique IDs *shrugs*
let mut total_height_ratio = 0;
let mut default_widget_count = 1;
let mut default_widget_id = DEFAULT_WIDGET_ID;
let default_widget_type = None;
let left_legend = false;
let mut ret_bottom_layout = BottomLayout {
rows: rows
.iter()
.map(|row| {
row.convert_row_to_bottom_row(
&mut iter_id,
&mut total_height_ratio,
&mut default_widget_id,
&default_widget_type,
&mut default_widget_count,
left_legend,
)
})
.collect::<error::Result<Vec<_>>>()
.unwrap(),
total_row_height_ratio: total_height_ratio,
};
ret_bottom_layout.get_movement_mappings();
assert_eq!(default_widget_id, 10);
}
#[test]
/// Tests default widget by setting type and count.
fn test_default_widget_by_option() {
let rows = toml::from_str::<Config>(PROC_LAYOUT).unwrap().row.unwrap();
let mut iter_id = 0; // A lazy way of forcing unique IDs *shrugs*
let mut total_height_ratio = 0;
let mut default_widget_count = 3;
let mut default_widget_id = DEFAULT_WIDGET_ID;
let default_widget_type = Some(BottomWidgetType::Proc);
let left_legend = false;
let mut ret_bottom_layout = BottomLayout {
rows: rows
.iter()
.map(|row| {
row.convert_row_to_bottom_row(
&mut iter_id,
&mut total_height_ratio,
&mut default_widget_id,
&default_widget_type,
&mut default_widget_count,
left_legend,
)
})
.collect::<error::Result<Vec<_>>>()
.unwrap(),
total_row_height_ratio: total_height_ratio,
};
ret_bottom_layout.get_movement_mappings();
assert_eq!(default_widget_id, 7);
}
#[test]
fn test_proc_custom_layout() {
let rows = toml::from_str::<Config>(PROC_LAYOUT).unwrap().row.unwrap();
let ret_bottom_layout = test_create_layout(&rows, DEFAULT_WIDGET_ID, None, 1, false);
// First proc widget
assert_eq!(
ret_bottom_layout.rows[0].children[0].children[0].children[1].down_neighbour,
Some(2)
);
assert_eq!(
ret_bottom_layout.rows[0].children[0].children[0].children[1].left_neighbour,
Some(3)
);
assert_eq!(
ret_bottom_layout.rows[0].children[0].children[0].children[1].right_neighbour,
None
);
assert_eq!(
ret_bottom_layout.rows[0].children[0].children[0].children[1].up_neighbour,
None
);
// Its search
assert_eq!(
ret_bottom_layout.rows[0].children[0].children[1].children[0].down_neighbour,
Some(4)
);
assert_eq!(
ret_bottom_layout.rows[0].children[0].children[1].children[0].left_neighbour,
None
);
assert_eq!(
ret_bottom_layout.rows[0].children[0].children[1].children[0].right_neighbour,
None
);
assert_eq!(
ret_bottom_layout.rows[0].children[0].children[1].children[0].up_neighbour,
Some(1)
);
// Its sort
assert_eq!(
ret_bottom_layout.rows[0].children[0].children[0].children[0].down_neighbour,
Some(2)
);
assert_eq!(
ret_bottom_layout.rows[0].children[0].children[0].children[0].left_neighbour,
None
);
assert_eq!(
ret_bottom_layout.rows[0].children[0].children[0].children[0].right_neighbour,
Some(1)
);
assert_eq!(
ret_bottom_layout.rows[0].children[0].children[0].children[0].up_neighbour,
None
);
// Let us now test the second row's first widget...
assert_eq!(
ret_bottom_layout.rows[1].children[0].children[0].children[1].down_neighbour,
Some(5)
);
assert_eq!(
ret_bottom_layout.rows[1].children[0].children[0].children[1].left_neighbour,
Some(6)
);
assert_eq!(
ret_bottom_layout.rows[1].children[0].children[0].children[1].right_neighbour,
Some(9)
);
assert_eq!(
ret_bottom_layout.rows[1].children[0].children[0].children[1].up_neighbour,
Some(2)
);
// Sort
assert_eq!(
ret_bottom_layout.rows[1].children[0].children[0].children[0].down_neighbour,
Some(5)
);
assert_eq!(
ret_bottom_layout.rows[1].children[0].children[0].children[0].left_neighbour,
None
);
assert_eq!(
ret_bottom_layout.rows[1].children[0].children[0].children[0].right_neighbour,
Some(4)
);
assert_eq!(
ret_bottom_layout.rows[1].children[0].children[0].children[0].up_neighbour,
Some(2)
);
// Search
assert_eq!(
ret_bottom_layout.rows[1].children[0].children[1].children[0].down_neighbour,
Some(10)
);
assert_eq!(
ret_bottom_layout.rows[1].children[0].children[1].children[0].left_neighbour,
None
);
assert_eq!(
ret_bottom_layout.rows[1].children[0].children[1].children[0].right_neighbour,
Some(8)
);
assert_eq!(
ret_bottom_layout.rows[1].children[0].children[1].children[0].up_neighbour,
Some(4)
);
// Third row, second
assert_eq!(
ret_bottom_layout.rows[2].children[1].children[0].children[1].down_neighbour,
Some(14)
);
assert_eq!(
ret_bottom_layout.rows[2].children[1].children[0].children[1].left_neighbour,
Some(15)
);
assert_eq!(
ret_bottom_layout.rows[2].children[1].children[0].children[1].right_neighbour,
None
);
assert_eq!(
ret_bottom_layout.rows[2].children[1].children[0].children[1].up_neighbour,
Some(8)
);
// Sort
assert_eq!(
ret_bottom_layout.rows[2].children[1].children[0].children[0].down_neighbour,
Some(14)
);
assert_eq!(
ret_bottom_layout.rows[2].children[1].children[0].children[0].left_neighbour,
Some(10)
);
assert_eq!(
ret_bottom_layout.rows[2].children[1].children[0].children[0].right_neighbour,
Some(13)
);
assert_eq!(
ret_bottom_layout.rows[2].children[1].children[0].children[0].up_neighbour,
Some(8)
);
// Search
assert_eq!(
ret_bottom_layout.rows[2].children[1].children[1].children[0].down_neighbour,
None
);
assert_eq!(
ret_bottom_layout.rows[2].children[1].children[1].children[0].left_neighbour,
Some(11)
);
assert_eq!(
ret_bottom_layout.rows[2].children[1].children[1].children[0].right_neighbour,
None
);
assert_eq!(
ret_bottom_layout.rows[2].children[1].children[1].children[0].up_neighbour,
Some(13)
);
}

View file

@ -1 +0,0 @@
// TODO: See if we can mock widget movements and test if they work