Refactoring.

This commit is contained in:
ClementTsang 2020-02-29 17:07:47 -05:00
parent eb2622467f
commit 4c98fe4fde
14 changed files with 246 additions and 234 deletions

View File

@ -3,7 +3,6 @@ max_width = 100
newline_style = "Unix"
reorder_imports = true
fn_args_layout = "Compressed"
hard_tabs = true
merge_derives = true
reorder_modules = true
tab_spaces = 4

View File

@ -609,11 +609,11 @@ impl App {
WidgetPosition::ProcessSearch => {
if self.process_search_state.search_state.is_enabled
&& self.get_cursor_position()
< self
.process_search_state
.search_state
.current_search_query
.len()
< self
.process_search_state
.search_state
.current_search_query
.len()
{
self.process_search_state
.search_state
@ -712,7 +712,8 @@ impl App {
pub fn on_up_key(&mut self) {
if !self.is_in_dialog() {
if let WidgetPosition::ProcessSearch = self.current_widget_selected {} else {
if let WidgetPosition::ProcessSearch = self.current_widget_selected {
} else {
self.decrement_position_count();
}
}
@ -720,7 +721,8 @@ impl App {
pub fn on_down_key(&mut self) {
if !self.is_in_dialog() {
if let WidgetPosition::ProcessSearch = self.current_widget_selected {} else {
if let WidgetPosition::ProcessSearch = self.current_widget_selected {
} else {
self.increment_position_count();
}
}
@ -858,7 +860,8 @@ impl App {
let current_key_press_inst = Instant::now();
if current_key_press_inst
.duration_since(self.last_key_press)
.as_millis() > constants::MAX_KEY_TIMEOUT_IN_MILLISECONDS
.as_millis()
> constants::MAX_KEY_TIMEOUT_IN_MILLISECONDS
{
self.reset_multi_tap_keys();
}
@ -1172,12 +1175,14 @@ impl App {
WidgetPosition::Process => {
self.app_scroll_positions
.process_scroll_state
.current_scroll_position = self.canvas_data.finalized_process_data.len() as u64 - 1
.current_scroll_position =
self.canvas_data.finalized_process_data.len() as u64 - 1
}
WidgetPosition::Temp => {
self.app_scroll_positions
.temp_scroll_state
.current_scroll_position = self.canvas_data.temp_sensor_data.len() as u64 - 1
.current_scroll_position =
self.canvas_data.temp_sensor_data.len() as u64 - 1
}
WidgetPosition::Disk => {
self.app_scroll_positions
@ -1247,7 +1252,7 @@ impl App {
if current_posn as i64 + num_to_change_by >= 0
&& current_posn as i64 + num_to_change_by
< self.canvas_data.finalized_process_data.len() as i64
< self.canvas_data.finalized_process_data.len() as i64
{
self.app_scroll_positions
.process_scroll_state
@ -1263,7 +1268,7 @@ impl App {
if current_posn as i64 + num_to_change_by >= 0
&& current_posn as i64 + num_to_change_by
< self.canvas_data.temp_sensor_data.len() as i64
< self.canvas_data.temp_sensor_data.len() as i64
{
self.app_scroll_positions
.temp_scroll_state

View File

@ -15,7 +15,7 @@
use std::time::Instant;
use std::vec::Vec;
use crate::data_harvester::{cpu, Data, disks, mem, network, processes, temperature};
use crate::data_harvester::{cpu, disks, mem, network, processes, temperature, Data};
pub type TimeOffset = f64;
pub type Value = f64;

View File

@ -149,13 +149,13 @@ impl DataState {
let temp_data_fut = temperature::get_temperature_data(&self.sys, &self.temperature_type);
let (net_data, mem_res, swap_res, disk_res, io_res, temp_res) = join!(
network_data_fut,
mem_data_fut,
swap_data_fut,
disk_data_fut,
disk_io_usage_fut,
temp_data_fut
);
network_data_fut,
mem_data_fut,
swap_data_fut,
disk_data_fut,
disk_io_usage_fut,
temp_data_fut
);
// After async
self.data.network = net_data;

View File

@ -68,13 +68,13 @@ pub async fn get_disk_usage_list() -> crate::utils::error::Result<Vec<DiskHarves
.mount_point()
.to_str()
.unwrap_or("Name Unavailable"))
.to_string(),
.to_string(),
name: (partition
.device()
.unwrap_or_else(|| std::ffi::OsStr::new("Name Unavailable"))
.to_str()
.unwrap_or("Name Unavailable"))
.to_string(),
.to_string(),
});
}
}

View File

@ -1,7 +1,7 @@
use std::{
collections::{hash_map::RandomState, HashMap},
process::Command,
time::Instant,
collections::{hash_map::RandomState, HashMap},
process::Command,
time::Instant,
};
use sysinfo::{ProcessExt, ProcessorExt, System, SystemExt};

View File

@ -37,13 +37,15 @@ pub async fn get_temperature_data(
temperature: match temp_type {
TemperatureType::Celsius => sensor
.current()
.get::<thermodynamic_temperature::degree_celsius>(),
.get::<thermodynamic_temperature::degree_celsius>(
),
TemperatureType::Kelvin => {
sensor.current().get::<thermodynamic_temperature::kelvin>()
}
TemperatureType::Fahrenheit => sensor
.current()
.get::<thermodynamic_temperature::degree_fahrenheit>(),
.get::<thermodynamic_temperature::degree_fahrenheit>(
),
},
});
}

View File

@ -3,10 +3,10 @@ use std::process::Command;
// Copied from SO: https://stackoverflow.com/a/55231715
#[cfg(target_os = "windows")]
use winapi::{
shared::{minwindef::DWORD, ntdef::HANDLE},
um::{
processthreadsapi::{OpenProcess, TerminateProcess},
winnt::{PROCESS_QUERY_INFORMATION, PROCESS_TERMINATE},
shared::{minwindef::DWORD, ntdef::HANDLE},
um::{
processthreadsapi::{OpenProcess, TerminateProcess},
winnt::{PROCESS_QUERY_INFORMATION, PROCESS_TERMINATE},
},
};
@ -38,10 +38,10 @@ pub fn kill_process_given_pid(pid: u32) -> crate::utils::error::Result<()> {
Command::new("kill").arg(pid.to_string()).output()?;
} else if cfg!(target_os = "windows") {
#[cfg(target_os = "windows")]
{
let process = Process::open(pid as DWORD)?;
process.kill()?;
}
{
let process = Process::open(pid as DWORD)?;
process.kill()?;
}
} else {
return Err(BottomError::GenericError(
"Sorry, support operating systems outside the main three are not implemented yet!"

View File

@ -2,12 +2,12 @@ use std::cmp::max;
use std::collections::HashMap;
use tui::{
backend,
layout::{Alignment, Constraint, Direction, Layout, Rect},
style::{Color, Style},
Terminal,
terminal::Frame,
widgets::{Axis, Block, Borders, Chart, Dataset, Marker, Paragraph, Row, Table, Text, Widget},
backend,
layout::{Alignment, Constraint, Direction, Layout, Rect},
style::{Color, Style},
terminal::Frame,
widgets::{Axis, Block, Borders, Chart, Dataset, Marker, Paragraph, Row, Table, Text, Widget},
Terminal,
};
use unicode_segmentation::UnicodeSegmentation;
use unicode_width::UnicodeWidthStr;
@ -16,10 +16,10 @@ use canvas_colours::*;
use drawing_utils::*;
use crate::{
app::{self, data_harvester::processes::ProcessHarvest, WidgetPosition},
constants::*,
data_conversion::{ConvertedCpuData, ConvertedProcessData},
utils::error,
app::{self, data_harvester::processes::ProcessHarvest, WidgetPosition},
constants::*,
data_conversion::{ConvertedCpuData, ConvertedProcessData},
utils::error,
};
mod canvas_colours;
@ -35,32 +35,32 @@ const NETWORK_HEADERS: [&str; 4] = ["RX", "TX", "Total RX", "Total TX"];
const FORCE_MIN_THRESHOLD: usize = 5;
lazy_static! {
static ref DEFAULT_TEXT_STYLE: Style = Style::default().fg(Color::Gray);
static ref DEFAULT_HEADER_STYLE: Style = Style::default().fg(Color::LightBlue);
static ref DISK_HEADERS_LENS: Vec<usize> = DISK_HEADERS
.iter()
.map(|entry| max(FORCE_MIN_THRESHOLD, entry.len()))
.collect::<Vec<_>>();
static ref CPU_LEGEND_HEADER_LENS: Vec<usize> = CPU_LEGEND_HEADER
.iter()
.map(|entry| max(FORCE_MIN_THRESHOLD, entry.len()))
.collect::<Vec<_>>();
static ref CPU_SELECT_LEGEND_HEADER_LENS: Vec<usize> = CPU_SELECT_LEGEND_HEADER
.iter()
.map(|entry| max(FORCE_MIN_THRESHOLD, entry.len()))
.collect::<Vec<_>>();
static ref TEMP_HEADERS_LENS: Vec<usize> = TEMP_HEADERS
.iter()
.map(|entry| max(FORCE_MIN_THRESHOLD, entry.len()))
.collect::<Vec<_>>();
static ref MEM_HEADERS_LENS: Vec<usize> = MEM_HEADERS
.iter()
.map(|entry| max(FORCE_MIN_THRESHOLD, entry.len()))
.collect::<Vec<_>>();
static ref NETWORK_HEADERS_LENS: Vec<usize> = NETWORK_HEADERS
.iter()
.map(|entry| max(FORCE_MIN_THRESHOLD, entry.len()))
.collect::<Vec<_>>();
static ref DEFAULT_TEXT_STYLE: Style = Style::default().fg(Color::Gray);
static ref DEFAULT_HEADER_STYLE: Style = Style::default().fg(Color::LightBlue);
static ref DISK_HEADERS_LENS: Vec<usize> = DISK_HEADERS
.iter()
.map(|entry| max(FORCE_MIN_THRESHOLD, entry.len()))
.collect::<Vec<_>>();
static ref CPU_LEGEND_HEADER_LENS: Vec<usize> = CPU_LEGEND_HEADER
.iter()
.map(|entry| max(FORCE_MIN_THRESHOLD, entry.len()))
.collect::<Vec<_>>();
static ref CPU_SELECT_LEGEND_HEADER_LENS: Vec<usize> = CPU_SELECT_LEGEND_HEADER
.iter()
.map(|entry| max(FORCE_MIN_THRESHOLD, entry.len()))
.collect::<Vec<_>>();
static ref TEMP_HEADERS_LENS: Vec<usize> = TEMP_HEADERS
.iter()
.map(|entry| max(FORCE_MIN_THRESHOLD, entry.len()))
.collect::<Vec<_>>();
static ref MEM_HEADERS_LENS: Vec<usize> = MEM_HEADERS
.iter()
.map(|entry| max(FORCE_MIN_THRESHOLD, entry.len()))
.collect::<Vec<_>>();
static ref NETWORK_HEADERS_LENS: Vec<usize> = NETWORK_HEADERS
.iter()
.map(|entry| max(FORCE_MIN_THRESHOLD, entry.len()))
.collect::<Vec<_>>();
}
#[derive(Default)]
@ -677,9 +677,10 @@ impl Painter {
app::WidgetPosition::Cpu => {
if itx as u64
== app_state
.app_scroll_positions
.cpu_scroll_state
.current_scroll_position - start_position
.app_scroll_positions
.cpu_scroll_state
.current_scroll_position
- start_position
{
self.colours.currently_selected_text_style
} else if app_state.app_config_fields.show_average_cpu && itx == 0 {
@ -687,7 +688,7 @@ impl Painter {
} else {
self.colours.cpu_colour_styles[itx
+ start_position as usize
% self.colours.cpu_colour_styles.len()]
% self.colours.cpu_colour_styles.len()]
}
}
_ => {
@ -696,7 +697,7 @@ impl Painter {
} else {
self.colours.cpu_colour_styles[itx
+ start_position as usize
% self.colours.cpu_colour_styles.len()]
% self.colours.cpu_colour_styles.len()]
}
}
},
@ -738,34 +739,34 @@ impl Painter {
} else {
CPU_LEGEND_HEADER
}
.iter(),
.iter(),
cpu_rows,
)
.block(
Block::default()
.title(&title)
.title_style(if app_state.is_expanded {
self.colours.highlighted_border_style
} else {
match app_state.current_widget_selected {
app::WidgetPosition::Cpu => self.colours.highlighted_border_style,
_ => self.colours.border_style,
}
})
.borders(Borders::ALL)
.border_style(match app_state.current_widget_selected {
.block(
Block::default()
.title(&title)
.title_style(if app_state.is_expanded {
self.colours.highlighted_border_style
} else {
match app_state.current_widget_selected {
app::WidgetPosition::Cpu => self.colours.highlighted_border_style,
_ => self.colours.border_style,
}),
)
.header_style(self.colours.table_header_style)
.widths(
&(intrinsic_widths
.iter()
.map(|calculated_width| Constraint::Length(*calculated_width as u16))
.collect::<Vec<_>>()),
)
.render(f, draw_loc);
}
})
.borders(Borders::ALL)
.border_style(match app_state.current_widget_selected {
app::WidgetPosition::Cpu => self.colours.highlighted_border_style,
_ => self.colours.border_style,
}),
)
.header_style(self.colours.table_header_style)
.widths(
&(intrinsic_widths
.iter()
.map(|calculated_width| Constraint::Length(*calculated_width as u16))
.collect::<Vec<_>>()),
)
.render(f, draw_loc);
}
fn draw_memory_graph<B: backend::Backend>(
@ -999,9 +1000,10 @@ impl Painter {
app::WidgetPosition::Temp => {
if temp_row_counter as u64
== app_state
.app_scroll_positions
.temp_scroll_state
.current_scroll_position - start_position
.app_scroll_positions
.temp_scroll_state
.current_scroll_position
- start_position
{
temp_row_counter = -1;
self.colours.currently_selected_text_style
@ -1095,9 +1097,10 @@ impl Painter {
app::WidgetPosition::Disk => {
if disk_counter as u64
== app_state
.app_scroll_positions
.disk_scroll_state
.current_scroll_position - start_position
.app_scroll_positions
.disk_scroll_state
.current_scroll_position
- start_position
{
disk_counter = -1;
self.colours.currently_selected_text_style
@ -1395,9 +1398,10 @@ impl Painter {
app::WidgetPosition::Process => {
if process_counter as u64
== app_state
.app_scroll_positions
.process_scroll_state
.current_scroll_position - start_position
.app_scroll_positions
.process_scroll_state
.current_scroll_position
- start_position
{
process_counter = -1;
self.colours.currently_selected_text_style
@ -1419,7 +1423,7 @@ impl Painter {
} else {
"PID(p)"
}
.to_string();
.to_string();
let mut name = "Name(n)".to_string();
let mut cpu = "CPU%(c)".to_string();
let mut mem = "Mem%(m)".to_string();

View File

@ -13,28 +13,28 @@ pub const STANDARD_FOURTH_COLOUR: Color = Color::LightGreen;
pub const AVG_COLOUR: Color = Color::Red;
lazy_static! {
static ref COLOR_NAME_LOOKUP_TABLE: HashMap<&'static str, Color> = [
("reset", Color::Reset),
("black", Color::Black),
("red", Color::Red),
("green", Color::Green),
("yellow", Color::Yellow),
("blue", Color::Blue),
("magenta", Color::Magenta),
("cyan", Color::Cyan),
("gray", Color::Gray),
("darkgray", Color::DarkGray),
("lightred", Color::LightRed),
("lightgreen", Color::LightGreen),
("lightyellow", Color::LightYellow),
("lightblue", Color::LightBlue),
("lightmagenta", Color::LightMagenta),
("lightcyan", Color::LightCyan),
("white", Color::White)
]
.iter()
.copied()
.collect();
static ref COLOR_NAME_LOOKUP_TABLE: HashMap<&'static str, Color> = [
("reset", Color::Reset),
("black", Color::Black),
("red", Color::Red),
("green", Color::Green),
("yellow", Color::Yellow),
("blue", Color::Blue),
("magenta", Color::Magenta),
("cyan", Color::Cyan),
("gray", Color::Gray),
("darkgray", Color::DarkGray),
("lightred", Color::LightRed),
("lightgreen", Color::LightGreen),
("lightyellow", Color::LightYellow),
("lightblue", Color::LightBlue),
("lightmagenta", Color::LightMagenta),
("lightcyan", Color::LightCyan),
("white", Color::White)
]
.iter()
.copied()
.collect();
}
/// Generates random colours. Strategy found from

View File

@ -6,13 +6,13 @@ use std::collections::HashMap;
use constants::*;
use crate::{
app::{
App,
data_farmer,
data_harvester::{self, processes::ProcessHarvest},
app::{
data_farmer,
data_harvester::{self, processes::ProcessHarvest},
App,
},
constants,
utils::gen_util::{get_exact_byte_values, get_simple_byte_values},
constants,
utils::gen_util::{get_exact_byte_values, get_simple_byte_values},
};
#[derive(Default, Debug)]
@ -54,10 +54,10 @@ pub fn convert_temp_row(app: &App) -> Vec<Vec<String>> {
sensor.component_name.to_string(),
(sensor.temperature.ceil() as u64).to_string()
+ match temp_type {
data_harvester::temperature::TemperatureType::Celsius => "C",
data_harvester::temperature::TemperatureType::Kelvin => "K",
data_harvester::temperature::TemperatureType::Fahrenheit => "F",
},
data_harvester::temperature::TemperatureType::Celsius => "C",
data_harvester::temperature::TemperatureType::Kelvin => "K",
data_harvester::temperature::TemperatureType::Fahrenheit => "F",
},
]);
}
}
@ -191,15 +191,16 @@ pub fn convert_mem_labels(current_data: &data_farmer::DataCollection) -> (String
} else {
"RAM:".to_string()
+ &format!(
"{:3.0}%",
(current_data.memory_harvest.mem_used_in_mb as f64 * 100.0
/ current_data.memory_harvest.mem_total_in_mb as f64)
.round()
) + &format!(
" {:.1}GB/{:.1}GB",
current_data.memory_harvest.mem_used_in_mb as f64 / 1024.0,
(current_data.memory_harvest.mem_total_in_mb as f64 / 1024.0).round()
)
"{:3.0}%",
(current_data.memory_harvest.mem_used_in_mb as f64 * 100.0
/ current_data.memory_harvest.mem_total_in_mb as f64)
.round()
)
+ &format!(
" {:.1}GB/{:.1}GB",
current_data.memory_harvest.mem_used_in_mb as f64 / 1024.0,
(current_data.memory_harvest.mem_total_in_mb as f64 / 1024.0).round()
)
};
let swap_label = if current_data.swap_harvest.mem_total_in_mb == 0 {
@ -207,15 +208,16 @@ pub fn convert_mem_labels(current_data: &data_farmer::DataCollection) -> (String
} else {
"SWP:".to_string()
+ &format!(
"{:3.0}%",
(current_data.swap_harvest.mem_used_in_mb as f64 * 100.0
/ current_data.swap_harvest.mem_total_in_mb as f64)
.round()
) + &format!(
" {:.1}GB/{:.1}GB",
current_data.swap_harvest.mem_used_in_mb as f64 / 1024.0,
(current_data.swap_harvest.mem_total_in_mb as f64 / 1024.0).round()
)
"{:3.0}%",
(current_data.swap_harvest.mem_used_in_mb as f64 * 100.0
/ current_data.swap_harvest.mem_total_in_mb as f64)
.round()
)
+ &format!(
" {:.1}GB/{:.1}GB",
current_data.swap_harvest.mem_used_in_mb as f64 / 1024.0,
(current_data.swap_harvest.mem_total_in_mb as f64 / 1024.0).round()
)
};
(mem_label, swap_label)

View File

@ -10,29 +10,29 @@ extern crate lazy_static;
extern crate log;
use std::{
boxed::Box,
io::{stdout, Write},
panic::{self, PanicInfo},
sync::mpsc,
thread,
time::{Duration, Instant},
boxed::Box,
io::{stdout, Write},
panic::{self, PanicInfo},
sync::mpsc,
thread,
time::{Duration, Instant},
};
use crossterm::{
event::{
DisableMouseCapture, EnableMouseCapture, Event as CEvent, KeyCode, KeyEvent, KeyModifiers, MouseEvent,
poll, read,
event::{
poll, read, DisableMouseCapture, EnableMouseCapture, Event as CEvent, KeyCode, KeyEvent,
KeyModifiers, MouseEvent,
},
execute,
style::Print,
terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen},
terminal::LeaveAlternateScreen,
execute,
style::Print,
terminal::LeaveAlternateScreen,
terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen},
};
use tui::{backend::CrosstermBackend, Terminal};
use app::{
App,
data_harvester::{self, processes::ProcessSorting},
data_harvester::{self, processes::ProcessSorting},
App,
};
use constants::*;
use data_conversion::*;
@ -435,10 +435,10 @@ fn cleanup_terminal(
) -> error::Result<()> {
disable_raw_mode()?;
execute!(
terminal.backend_mut(),
DisableMouseCapture,
LeaveAlternateScreen
)?;
terminal.backend_mut(),
DisableMouseCapture,
LeaveAlternateScreen
)?;
terminal.show_cursor()?;
Ok(())
@ -543,15 +543,15 @@ fn panic_hook(panic_info: &PanicInfo<'_>) {
// Print stack trace. Must be done after!
execute!(
stdout,
Print(format!(
"thread '<unnamed>' panicked at '{}', {}\n\r{}",
msg,
panic_info.location().unwrap(),
stacktrace
)),
)
.unwrap();
stdout,
Print(format!(
"thread '<unnamed>' panicked at '{}', {}\n\r{}",
msg,
panic_info.location().unwrap(),
stacktrace
)),
)
.unwrap();
}
fn update_final_process_list(app: &mut App) {

View File

@ -1,9 +1,9 @@
use serde::Deserialize;
use crate::{
app::{self, App, data_harvester},
constants::*,
utils::error::{self, BottomError},
app::{self, data_harvester, App},
constants::*,
utils::error::{self, BottomError},
};
#[derive(Default, Deserialize)]

View File

@ -10,64 +10,64 @@ use predicates::prelude::*;
//======================RATES======================//
fn get_os_binary_loc() -> String {
if cfg!(target_os = "linux") {
"./target/x86_64-unknown-linux-gnu/debug/btm".to_string()
} else if cfg!(target_os = "windows") {
"./target/x86_64-pc-windows-msvc/debug/btm".to_string()
} else if cfg!(target_os = "macos") {
"./target/x86_64-apple-darwin/debug/btm".to_string()
} else {
"".to_string()
}
if cfg!(target_os = "linux") {
"./target/x86_64-unknown-linux-gnu/debug/btm".to_string()
} else if cfg!(target_os = "windows") {
"./target/x86_64-pc-windows-msvc/debug/btm".to_string()
} else if cfg!(target_os = "macos") {
"./target/x86_64-apple-darwin/debug/btm".to_string()
} else {
"".to_string()
}
}
#[test]
fn test_small_rate() -> Result<(), Box<dyn std::error::Error>> {
Command::new(get_os_binary_loc())
.arg("-r")
.arg("249")
.assert()
.failure()
.stderr(predicate::str::contains("rate to be greater than 250"));
Ok(())
Command::new(get_os_binary_loc())
.arg("-r")
.arg("249")
.assert()
.failure()
.stderr(predicate::str::contains("rate to be greater than 250"));
Ok(())
}
#[test]
fn test_large_rate() -> Result<(), Box<dyn std::error::Error>> {
Command::new(get_os_binary_loc())
.arg("-r")
.arg("18446744073709551616")
.assert()
.failure()
.stderr(predicate::str::contains(
"rate to be less than unsigned INT_MAX.",
));
Ok(())
Command::new(get_os_binary_loc())
.arg("-r")
.arg("18446744073709551616")
.assert()
.failure()
.stderr(predicate::str::contains(
"rate to be less than unsigned INT_MAX.",
));
Ok(())
}
#[test]
fn test_negative_rate() -> Result<(), Box<dyn std::error::Error>> {
// This test should auto fail due to how clap works
Command::new(get_os_binary_loc())
.arg("-r")
.arg("-1000")
.assert()
.failure()
.stderr(predicate::str::contains(
"wasn't expected, or isn't valid in this context",
));
// This test should auto fail due to how clap works
Command::new(get_os_binary_loc())
.arg("-r")
.arg("-1000")
.assert()
.failure()
.stderr(predicate::str::contains(
"wasn't expected, or isn't valid in this context",
));
Ok(())
Ok(())
}
#[test]
fn test_invalid_rate() -> Result<(), Box<dyn std::error::Error>> {
Command::new(get_os_binary_loc())
.arg("-r")
.arg("100-1000")
.assert()
.failure()
.stderr(predicate::str::contains("invalid digit"));
Command::new(get_os_binary_loc())
.arg("-r")
.arg("100-1000")
.assert()
.failure()
.stderr(predicate::str::contains("invalid digit"));
Ok(())
Ok(())
}