mirror of
https://github.com/ClementTsang/bottom.git
synced 2024-11-28 11:02:56 +03:00
Made charting look better, switched back to braille markers (its the only way I could make it look good), and dealt with some issues regarding the display of networking.
This commit is contained in:
parent
2435b9d90c
commit
282acd1395
6
TODO.md
6
TODO.md
@ -6,7 +6,9 @@
|
||||
|
||||
* ~~Refreshing - how are we doing that? Are we allowing individual refresh periods per component?~~
|
||||
|
||||
* Write tui display, charting
|
||||
* ~~Write tui display, charting~~
|
||||
|
||||
* Scaling in and out
|
||||
|
||||
* Add custom error because it's really messy
|
||||
|
||||
@ -22,7 +24,7 @@
|
||||
|
||||
* Test for Windows support, mac support
|
||||
|
||||
* Efficiency!!! Make sure no wasted hashmaps, use references, etc.
|
||||
* Efficiency!!!
|
||||
|
||||
* Potentially process managing? Depends on the libraries...
|
||||
|
||||
|
@ -103,7 +103,6 @@ impl DataState {
|
||||
}
|
||||
|
||||
// Filter out stale timed entries
|
||||
// TODO: ideally make this a generic function!
|
||||
let current_instant = std::time::Instant::now();
|
||||
self.data.list_of_cpu_packages = self
|
||||
.data
|
||||
|
@ -56,16 +56,18 @@ pub async fn get_disk_usage_list() -> Result<Vec<DiskData>, heim::Error> {
|
||||
let mut partitions_stream = heim::disk::partitions_physical();
|
||||
|
||||
while let Some(part) = partitions_stream.next().await {
|
||||
let partition = part?; // TODO: Change this? We don't want to error out immediately...
|
||||
let usage = heim::disk::usage(partition.mount_point().to_path_buf()).await?;
|
||||
if let Ok(part) = part {
|
||||
let partition = part;
|
||||
let usage = heim::disk::usage(partition.mount_point().to_path_buf()).await?;
|
||||
|
||||
vec_disks.push(DiskData {
|
||||
free_space : usage.free().get::<heim_common::units::information::megabyte>(),
|
||||
used_space : usage.used().get::<heim_common::units::information::megabyte>(),
|
||||
total_space : usage.total().get::<heim_common::units::information::megabyte>(),
|
||||
mount_point : Box::from(partition.mount_point().to_str().unwrap_or("Name Unavailable")),
|
||||
name : Box::from(partition.device().unwrap_or_else(|| std::ffi::OsStr::new("Name Unavailable")).to_str().unwrap_or("Name Unavailable")),
|
||||
});
|
||||
vec_disks.push(DiskData {
|
||||
free_space : usage.free().get::<heim_common::units::information::megabyte>(),
|
||||
used_space : usage.used().get::<heim_common::units::information::megabyte>(),
|
||||
total_space : usage.total().get::<heim_common::units::information::megabyte>(),
|
||||
mount_point : Box::from(partition.mount_point().to_str().unwrap_or("Name Unavailable")),
|
||||
name : Box::from(partition.device().unwrap_or_else(|| std::ffi::OsStr::new("Name Unavailable")).to_str().unwrap_or("Name Unavailable")),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
vec_disks.sort_by(|a, b| {
|
||||
|
@ -39,15 +39,20 @@ fn vangelis_cpu_usage_calculation(prev_idle : &mut f64, prev_non_idle : &mut f64
|
||||
let first_line = stat_results.split('\n').collect::<Vec<&str>>()[0];
|
||||
let val = first_line.split_whitespace().collect::<Vec<&str>>();
|
||||
|
||||
let user : f64 = val[1].parse::<_>().unwrap_or(-1_f64); // TODO: Better checking
|
||||
let nice : f64 = val[2].parse::<_>().unwrap_or(-1_f64);
|
||||
let system : f64 = val[3].parse::<_>().unwrap_or(-1_f64);
|
||||
let idle : f64 = val[4].parse::<_>().unwrap_or(-1_f64);
|
||||
let iowait : f64 = val[5].parse::<_>().unwrap_or(-1_f64);
|
||||
let irq : f64 = val[6].parse::<_>().unwrap_or(-1_f64);
|
||||
let softirq : f64 = val[7].parse::<_>().unwrap_or(-1_f64);
|
||||
let steal : f64 = val[8].parse::<_>().unwrap_or(-1_f64);
|
||||
let guest : f64 = val[9].parse::<_>().unwrap_or(-1_f64);
|
||||
// SC in case that the parsing will fail due to length:
|
||||
if val.len() <= 10 {
|
||||
return Ok(1.0); // TODO: This is not the greatest...
|
||||
}
|
||||
|
||||
let user : f64 = val[1].parse::<_>().unwrap_or(0_f64);
|
||||
let nice : f64 = val[2].parse::<_>().unwrap_or(0_f64);
|
||||
let system : f64 = val[3].parse::<_>().unwrap_or(0_f64);
|
||||
let idle : f64 = val[4].parse::<_>().unwrap_or(0_f64);
|
||||
let iowait : f64 = val[5].parse::<_>().unwrap_or(0_f64);
|
||||
let irq : f64 = val[6].parse::<_>().unwrap_or(0_f64);
|
||||
let softirq : f64 = val[7].parse::<_>().unwrap_or(0_f64);
|
||||
let steal : f64 = val[8].parse::<_>().unwrap_or(0_f64);
|
||||
let guest : f64 = val[9].parse::<_>().unwrap_or(0_f64);
|
||||
|
||||
let idle = idle + iowait;
|
||||
let non_idle = user + nice + system + irq + softirq + steal + guest;
|
||||
@ -204,12 +209,13 @@ pub async fn get_sorted_processes_list(prev_idle : &mut f64, prev_non_idle : &mu
|
||||
}
|
||||
}
|
||||
else if cfg!(target_os = "macos") {
|
||||
// macOS
|
||||
dbg!("Mac"); // TODO: Remove
|
||||
// TODO: macOS
|
||||
debug!("Mac");
|
||||
}
|
||||
else {
|
||||
dbg!("Else"); // TODO: Remove
|
||||
// Solaris: https://stackoverflow.com/a/4453581
|
||||
// TODO: Others?
|
||||
debug!("Else");
|
||||
// Solaris: https://stackoverflow.com/a/4453581
|
||||
}
|
||||
|
||||
Ok(process_vector)
|
||||
|
@ -7,13 +7,16 @@ use tui::{
|
||||
|
||||
use crate::utils::error;
|
||||
|
||||
const COLOUR_LIST : [Color; 6] = [Color::LightRed, Color::LightGreen, Color::LightYellow, Color::LightBlue, Color::LightCyan, Color::LightMagenta];
|
||||
const COLOUR_LIST : [Color; 6] = [Color::Red, Color::Green, Color::LightYellow, Color::LightBlue, Color::LightCyan, Color::LightMagenta];
|
||||
const TEXT_COLOUR : Color = Color::Gray;
|
||||
const GRAPH_COLOUR : Color = Color::Gray;
|
||||
const BORDER_STYLE_COLOUR : Color = Color::Gray;
|
||||
const GRAPH_MARKER : Marker = Marker::Braille;
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct CanvasData {
|
||||
pub rx_display : String,
|
||||
pub tx_display : String,
|
||||
pub network_data_rx : Vec<(f64, f64)>,
|
||||
pub network_data_tx : Vec<(f64, f64)>,
|
||||
pub disk_data : Vec<Vec<String>>,
|
||||
@ -61,14 +64,14 @@ pub fn draw_data<B : tui::backend::Backend>(terminal : &mut Terminal<B>, canvas_
|
||||
// CPU usage graph
|
||||
{
|
||||
let x_axis : Axis<String> = Axis::default().style(Style::default().fg(GRAPH_COLOUR)).bounds([0.0, 600_000.0]);
|
||||
let y_axis = Axis::default().style(Style::default().fg(GRAPH_COLOUR)).bounds([0.0, 100.0]).labels(&["0%", "50%", "100%"]);
|
||||
let y_axis = Axis::default().style(Style::default().fg(GRAPH_COLOUR)).bounds([-0.5, 100.0]).labels(&["0%", "100%"]);
|
||||
|
||||
let mut dataset_vector : Vec<Dataset> = Vec::new();
|
||||
for (i, cpu) in canvas_data.cpu_data.iter().enumerate() {
|
||||
dataset_vector.push(
|
||||
Dataset::default()
|
||||
.name(&cpu.0)
|
||||
.marker(Marker::Dot)
|
||||
.marker(GRAPH_MARKER)
|
||||
.style(Style::default().fg(COLOUR_LIST[i % COLOUR_LIST.len()]))
|
||||
.data(&(cpu.1)),
|
||||
);
|
||||
@ -85,20 +88,20 @@ pub fn draw_data<B : tui::backend::Backend>(terminal : &mut Terminal<B>, canvas_
|
||||
//Memory usage graph
|
||||
{
|
||||
let x_axis : Axis<String> = Axis::default().style(Style::default().fg(GRAPH_COLOUR)).bounds([0.0, 600_000.0]);
|
||||
let y_axis = Axis::default().style(Style::default().fg(GRAPH_COLOUR)).bounds([0.0, 100.0]).labels(&["0%", "50%", "100%"]);
|
||||
let y_axis = Axis::default().style(Style::default().fg(GRAPH_COLOUR)).bounds([-0.5, 100.0]).labels(&["0%", "100%"]); // Offset as the zero value isn't drawn otherwise...
|
||||
Chart::default()
|
||||
.block(Block::default().title("Memory Usage").borders(Borders::ALL).border_style(border_style))
|
||||
.x_axis(x_axis)
|
||||
.y_axis(y_axis)
|
||||
.datasets(&[
|
||||
Dataset::default()
|
||||
.name(&("MEM :".to_string() + &format!("{:3}%", (canvas_data.mem_data.last().unwrap_or(&(0_f64, 0_f64)).1.round() as u64))))
|
||||
.marker(Marker::Dot)
|
||||
.name(&("RAM:".to_string() + &format!("{:3}%", (canvas_data.mem_data.last().unwrap_or(&(0_f64, 0_f64)).1.round() as u64))))
|
||||
.marker(GRAPH_MARKER)
|
||||
.style(Style::default().fg(Color::LightBlue))
|
||||
.data(&canvas_data.mem_data),
|
||||
Dataset::default()
|
||||
.name(&("SWAP:".to_string() + &format!("{:3}%", (canvas_data.swap_data.last().unwrap_or(&(0_f64, 0_f64)).1.round() as u64))))
|
||||
.marker(Marker::Dot)
|
||||
.name(&("SWP:".to_string() + &format!("{:3}%", (canvas_data.swap_data.last().unwrap_or(&(0_f64, 0_f64)).1.round() as u64))))
|
||||
.marker(GRAPH_MARKER)
|
||||
.style(Style::default().fg(Color::LightYellow))
|
||||
.data(&canvas_data.swap_data),
|
||||
])
|
||||
@ -128,20 +131,20 @@ pub fn draw_data<B : tui::backend::Backend>(terminal : &mut Terminal<B>, canvas_
|
||||
// Network graph
|
||||
{
|
||||
let x_axis : Axis<String> = Axis::default().style(Style::default().fg(GRAPH_COLOUR)).bounds([0.0, 600_000.0]);
|
||||
let y_axis = Axis::default().style(Style::default().fg(GRAPH_COLOUR)).bounds([0.0, 1000.0]).labels(&["0Kb", "1000Kb"]);
|
||||
let y_axis = Axis::default().style(Style::default().fg(GRAPH_COLOUR)).bounds([-0.5, 1_000_000.0]).labels(&["0GB", "1GB"]);
|
||||
Chart::default()
|
||||
.block(Block::default().title("Network").borders(Borders::ALL).border_style(border_style))
|
||||
.x_axis(x_axis)
|
||||
.y_axis(y_axis)
|
||||
.datasets(&[
|
||||
Dataset::default()
|
||||
.name(&("RX:".to_string() + &format!("{:3}%", (canvas_data.network_data_rx.last().unwrap_or(&(0_f64, 0_f64)).1.round() as u64))))
|
||||
.marker(Marker::Dot)
|
||||
.name(&(canvas_data.rx_display))
|
||||
.marker(GRAPH_MARKER)
|
||||
.style(Style::default().fg(Color::LightBlue))
|
||||
.data(&canvas_data.network_data_rx),
|
||||
Dataset::default()
|
||||
.name(&("TX:".to_string() + &format!("{:3}%", (canvas_data.network_data_tx.last().unwrap_or(&(0_f64, 0_f64)).1.round() as u64))))
|
||||
.marker(Marker::Dot)
|
||||
.name(&(canvas_data.tx_display))
|
||||
.marker(GRAPH_MARKER)
|
||||
.style(Style::default().fg(Color::LightYellow))
|
||||
.data(&canvas_data.network_data_tx),
|
||||
])
|
||||
|
132
src/main.rs
132
src/main.rs
@ -41,7 +41,7 @@ fn main() -> error::Result<()> {
|
||||
(about: "A graphical top clone.")
|
||||
(@arg THEME: -t --theme +takes_value "Sets a colour theme.")
|
||||
(@arg AVG_CPU: -a --avgcpu "Enables showing the average CPU usage.")
|
||||
(@arg DEBUG: -d --debug "Enables debug mode.")
|
||||
(@arg DEBUG: -d --debug "Enables debug mode.") // TODO: This isn't done yet!
|
||||
(@group TEMPERATURE_TYPE =>
|
||||
(@arg CELSIUS : -c --celsius "Sets the temperature type to Celsius. This is the default option.")
|
||||
(@arg FAHRENHEIT : -f --fahrenheit "Sets the temperature type to Fahrenheit.")
|
||||
@ -52,11 +52,16 @@ fn main() -> error::Result<()> {
|
||||
.after_help("Themes:")
|
||||
.get_matches();
|
||||
|
||||
let update_rate_in_milliseconds : u64 = matches.value_of("RATE").unwrap_or("1000").parse::<u64>()?;
|
||||
let update_rate_in_milliseconds : u128 = matches.value_of("RATE").unwrap_or("1000").parse::<u128>()?;
|
||||
|
||||
if update_rate_in_milliseconds < 250 {
|
||||
return Err(RustopError::InvalidArg {
|
||||
message : "Please set your rate to be greater than 250 milliseconds.".to_string(),
|
||||
message : "Please set your update rate to be greater than 250 milliseconds.".to_string(),
|
||||
});
|
||||
}
|
||||
else if update_rate_in_milliseconds > u128::from(std::u64::MAX) {
|
||||
return Err(RustopError::InvalidArg {
|
||||
message : "Please set your update rate to be less than unsigned INT_MAX.".to_string(),
|
||||
});
|
||||
}
|
||||
|
||||
@ -72,8 +77,7 @@ fn main() -> error::Result<()> {
|
||||
let show_average_cpu = matches.is_present("AVG_CPU");
|
||||
|
||||
// Create "app" struct, which will control most of the program and store settings/state
|
||||
// TODO: Error handling here because users may be stupid and pass INT_MAX.
|
||||
let mut app = app::App::new(show_average_cpu, temperature_type, update_rate_in_milliseconds);
|
||||
let mut app = app::App::new(show_average_cpu, temperature_type, update_rate_in_milliseconds as u64);
|
||||
|
||||
// Set up input handling
|
||||
let (tx, rx) = mpsc::channel();
|
||||
@ -104,7 +108,7 @@ fn main() -> error::Result<()> {
|
||||
loop {
|
||||
futures::executor::block_on(data_state.update_data());
|
||||
tx.send(Event::Update(Box::from(data_state.data.clone()))).unwrap();
|
||||
thread::sleep(Duration::from_millis(update_rate_in_milliseconds));
|
||||
thread::sleep(Duration::from_millis(update_rate_in_milliseconds as u64));
|
||||
}
|
||||
});
|
||||
}
|
||||
@ -151,6 +155,8 @@ fn main() -> error::Result<()> {
|
||||
let network_data = update_network_data_points(&app_data);
|
||||
canvas_data.network_data_rx = network_data.rx;
|
||||
canvas_data.network_data_tx = network_data.tx;
|
||||
canvas_data.rx_display = network_data.rx_display;
|
||||
canvas_data.tx_display = network_data.tx_display;
|
||||
canvas_data.disk_data = update_disk_row(&app_data);
|
||||
canvas_data.temp_sensor_data = update_temp_row(&app_data, &app.temperature_type);
|
||||
canvas_data.process_data = update_process_row(&app_data);
|
||||
@ -253,8 +259,6 @@ fn update_cpu_data_points(show_avg_cpu : bool, app_data : &data_collection::Data
|
||||
let mut cpu_collection : Vec<Vec<(f64, f64)>> = Vec::new();
|
||||
|
||||
if !app_data.list_of_cpu_packages.is_empty() {
|
||||
// Initially, populate the cpu_collection. We want to inject elements in between if possible.
|
||||
|
||||
// I'm sorry for the if statement but I couldn't be bothered here...
|
||||
for cpu_num in (if show_avg_cpu { 0 } else { 1 })..app_data.list_of_cpu_packages.last().unwrap().cpu_vec.len() {
|
||||
let mut this_cpu_data : Vec<(f64, f64)> = Vec::new();
|
||||
@ -262,10 +266,24 @@ fn update_cpu_data_points(show_avg_cpu : bool, app_data : &data_collection::Data
|
||||
for data in &app_data.list_of_cpu_packages {
|
||||
let current_time = std::time::Instant::now();
|
||||
let current_cpu_usage = data.cpu_vec[cpu_num].cpu_usage;
|
||||
this_cpu_data.push((
|
||||
|
||||
let new_entry = (
|
||||
((STALE_MAX_MILLISECONDS as f64 - current_time.duration_since(data.instant).as_millis() as f64) * 10_f64).floor(),
|
||||
current_cpu_usage,
|
||||
));
|
||||
);
|
||||
|
||||
// Now, inject our joining points...
|
||||
if !this_cpu_data.is_empty() {
|
||||
let previous_element_data = *(this_cpu_data.last().unwrap());
|
||||
for idx in 0..100 {
|
||||
this_cpu_data.push((
|
||||
previous_element_data.0 + ((new_entry.0 - previous_element_data.0) / 100.0 * f64::from(idx)),
|
||||
previous_element_data.1 + ((new_entry.1 - previous_element_data.1) / 100.0 * f64::from(idx)),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
this_cpu_data.push(new_entry);
|
||||
}
|
||||
|
||||
cpu_collection.push(this_cpu_data);
|
||||
@ -298,11 +316,23 @@ fn convert_mem_data(mem_data : &[data_collection::mem::MemData]) -> Vec<(f64, f6
|
||||
|
||||
for data in mem_data {
|
||||
let current_time = std::time::Instant::now();
|
||||
|
||||
result.push((
|
||||
let new_entry = (
|
||||
((STALE_MAX_MILLISECONDS as f64 - current_time.duration_since(data.instant).as_millis() as f64) * 10_f64).floor(),
|
||||
data.mem_used_in_mb as f64 / data.mem_total_in_mb as f64 * 100_f64,
|
||||
));
|
||||
);
|
||||
|
||||
// Now, inject our joining points...
|
||||
if !result.is_empty() {
|
||||
let previous_element_data = *(result.last().unwrap());
|
||||
for idx in 0..100 {
|
||||
result.push((
|
||||
previous_element_data.0 + ((new_entry.0 - previous_element_data.0) / 100.0 * f64::from(idx)),
|
||||
previous_element_data.1 + ((new_entry.1 - previous_element_data.1) / 100.0 * f64::from(idx)),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
result.push(new_entry);
|
||||
//debug!("Pushed: ({}, {})", result.last().unwrap().0, result.last().unwrap().1);
|
||||
}
|
||||
|
||||
@ -312,6 +342,8 @@ fn convert_mem_data(mem_data : &[data_collection::mem::MemData]) -> Vec<(f64, f6
|
||||
struct ConvertedNetworkData {
|
||||
rx : Vec<(f64, f64)>,
|
||||
tx : Vec<(f64, f64)>,
|
||||
rx_display : String,
|
||||
tx_display : String,
|
||||
}
|
||||
|
||||
fn update_network_data_points(app_data : &data_collection::Data) -> ConvertedNetworkData {
|
||||
@ -324,20 +356,80 @@ fn convert_network_data_points(network_data : &[data_collection::network::Networ
|
||||
|
||||
for data in network_data {
|
||||
let current_time = std::time::Instant::now();
|
||||
|
||||
rx.push((
|
||||
let rx_data = (
|
||||
((STALE_MAX_MILLISECONDS as f64 - current_time.duration_since(data.instant).as_millis() as f64) * 10_f64).floor(),
|
||||
data.rx as f64 / 1024.0,
|
||||
));
|
||||
|
||||
tx.push((
|
||||
);
|
||||
let tx_data = (
|
||||
((STALE_MAX_MILLISECONDS as f64 - current_time.duration_since(data.instant).as_millis() as f64) * 10_f64).floor(),
|
||||
data.tx as f64 / 1024.0,
|
||||
));
|
||||
);
|
||||
|
||||
// Now, inject our joining points...
|
||||
if !rx.is_empty() {
|
||||
let previous_element_data = *(rx.last().unwrap());
|
||||
for idx in 0..100 {
|
||||
rx.push((
|
||||
previous_element_data.0 + ((rx_data.0 - previous_element_data.0) / 100.0 * f64::from(idx)),
|
||||
previous_element_data.1 + ((rx_data.1 - previous_element_data.1) / 100.0 * f64::from(idx)),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
// Now, inject our joining points...
|
||||
if !tx.is_empty() {
|
||||
let previous_element_data = *(tx.last().unwrap());
|
||||
for idx in 0..100 {
|
||||
tx.push((
|
||||
previous_element_data.0 + ((tx_data.0 - previous_element_data.0) / 100.0 * f64::from(idx)),
|
||||
previous_element_data.1 + ((tx_data.1 - previous_element_data.1) / 100.0 * f64::from(idx)),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
rx.push(rx_data);
|
||||
tx.push(tx_data);
|
||||
|
||||
debug!("Pushed rx: ({}, {})", rx.last().unwrap().0, rx.last().unwrap().1);
|
||||
debug!("Pushed tx: ({}, {})", tx.last().unwrap().0, tx.last().unwrap().1);
|
||||
}
|
||||
|
||||
ConvertedNetworkData { rx, tx }
|
||||
let rx_display = if network_data.is_empty() {
|
||||
"0B".to_string()
|
||||
}
|
||||
else {
|
||||
let num_bytes = network_data.last().unwrap().rx;
|
||||
if num_bytes < 1024 {
|
||||
format!("RX: {:4} B", num_bytes).to_string()
|
||||
}
|
||||
else if num_bytes < (1024 * 1024) {
|
||||
format!("RX: {:4}KB", num_bytes / 1024).to_string()
|
||||
}
|
||||
else if num_bytes < (1024 * 1024 * 1024) {
|
||||
format!("RX: {:4}MB", num_bytes / 1024 / 1024).to_string()
|
||||
}
|
||||
else {
|
||||
format!("RX: {:4}GB", num_bytes / 1024 / 1024 / 1024).to_string()
|
||||
}
|
||||
};
|
||||
let tx_display = if network_data.is_empty() {
|
||||
"0B".to_string()
|
||||
}
|
||||
else {
|
||||
let num_bytes = network_data.last().unwrap().tx;
|
||||
if num_bytes < 1024 {
|
||||
format!("TX: {:4} B", num_bytes).to_string()
|
||||
}
|
||||
else if num_bytes < (1024 * 1024) {
|
||||
format!("TX: {:4}KB", num_bytes / 1024).to_string()
|
||||
}
|
||||
else if num_bytes < (1024 * 1024 * 1024) {
|
||||
format!("TX: {:4}MB", num_bytes / 1024 / 1024).to_string()
|
||||
}
|
||||
else {
|
||||
format!("TX: {:4}GB", num_bytes / 1024 / 1024 / 1024).to_string()
|
||||
}
|
||||
};
|
||||
|
||||
ConvertedNetworkData { rx, tx, rx_display, tx_display }
|
||||
}
|
||||
|
@ -2,6 +2,7 @@ use assert_cmd::prelude::*; // Add methods on commands
|
||||
use predicates::prelude::*;
|
||||
use std::process::Command; // Run programs // Used for writing assertions
|
||||
|
||||
//======================RATES======================//
|
||||
#[test]
|
||||
fn valid_rate_argument() {
|
||||
}
|
||||
@ -17,6 +18,17 @@ fn test_small_rate() -> Result<(), Box<dyn std::error::Error>> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_large_rate() -> Result<(), Box<dyn std::error::Error>> {
|
||||
Command::new("./target/debug/rustop")
|
||||
.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
|
||||
|
Loading…
Reference in New Issue
Block a user