tests: gui tests
This commit is contained in:
@@ -157,7 +157,8 @@ see <a href="https://forums.raspberrypi.com/viewtopic.php?t=203128" target='_bla
|
||||
|
||||
## Tests
|
||||
|
||||
As of yet untested, needs work
|
||||
~~As of yet untested, needs work~~
|
||||
The work has been done
|
||||
|
||||
```shell
|
||||
cargo test -- --test-threads=1
|
||||
|
||||
@@ -630,3 +630,98 @@ impl Columns {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use ratatui::widgets::ListItem;
|
||||
|
||||
use crate::{
|
||||
app_data::{ContainerImage, Logs},
|
||||
ui::log_sanitizer,
|
||||
};
|
||||
|
||||
use super::{ByteStats, ContainerName, CpuStats, LogsTz};
|
||||
|
||||
#[test]
|
||||
// Display CpuStats as a string
|
||||
fn test_container_state_cpustats_to_string() {
|
||||
let test = |f: f64, s: &str| {
|
||||
assert_eq!(CpuStats::new(f).to_string(), s);
|
||||
};
|
||||
|
||||
test(0.0, "00.00%");
|
||||
test(1.5, "01.50%");
|
||||
test(15.15, "15.15%");
|
||||
test(150.15, "150.15%");
|
||||
}
|
||||
|
||||
#[test]
|
||||
// Display bytestats as a string, convert into correct data unit (Kb, MB, GB)
|
||||
fn test_container_state_bytestats_to_string() {
|
||||
let test = |u: u64, s: &str| {
|
||||
assert_eq!(ByteStats::new(u).to_string(), s);
|
||||
};
|
||||
|
||||
test(0, "0.00 kB");
|
||||
test(150, "0.15 kB");
|
||||
test(1500, "1.50 kB");
|
||||
test(150_000, "150.00 kB");
|
||||
test(1_500_000, "1.50 MB");
|
||||
test(15_000_000, "15.00 MB");
|
||||
test(150_000_000, "150.00 MB");
|
||||
test(1_500_000_000, "1.50 GB");
|
||||
test(15_000_000_000, "15.00 GB");
|
||||
test(150_000_000_000, "150.00 GB");
|
||||
}
|
||||
|
||||
#[test]
|
||||
/// ContainerName as string truncated correctly
|
||||
fn test_container_state_container_name_to_string() {
|
||||
let result = ContainerName::from("name_01");
|
||||
assert_eq!(result.to_string(), "name_01");
|
||||
|
||||
let result = ContainerName::from("name_01_name_01_name_01_name_01_");
|
||||
assert_eq!(result.to_string(), "name_01_name_01_name_01_name_…");
|
||||
|
||||
let result = result.get();
|
||||
assert_eq!(result, "name_01_name_01_name_01_name_01_");
|
||||
}
|
||||
|
||||
#[test]
|
||||
/// ContainerImage as string truncated correctly
|
||||
fn test_container_state_container_image() {
|
||||
let result = ContainerImage::from("name_01");
|
||||
assert_eq!(result.to_string(), "name_01");
|
||||
|
||||
let result = ContainerImage::from("name_01_name_01_name_01_name_01_");
|
||||
assert_eq!(result.to_string(), "name_01_name_01_name_01_name_…");
|
||||
|
||||
let result = result.get();
|
||||
assert_eq!(result, "name_01_name_01_name_01_name_01_");
|
||||
}
|
||||
|
||||
#[test]
|
||||
/// Logs can only contain 1 entry per LogzTz
|
||||
fn test_container_state_logz() {
|
||||
let input = "2023-01-14T19:13:30.783138328Z Lorem ipsum dolor sit amet";
|
||||
let tz = LogsTz::from(input);
|
||||
let mut logs = Logs::default();
|
||||
let line = log_sanitizer::remove_ansi(input);
|
||||
|
||||
logs.insert(ListItem::new(line.clone()), tz.clone());
|
||||
logs.insert(ListItem::new(line.clone()), tz.clone());
|
||||
logs.insert(ListItem::new(line), tz);
|
||||
|
||||
assert_eq!(logs.logs.items.len(), 1);
|
||||
|
||||
let input = "2023-01-15T19:13:30.783138328Z Lorem ipsum dolor sit amet";
|
||||
let tz = LogsTz::from(input);
|
||||
let line = log_sanitizer::remove_ansi(input);
|
||||
|
||||
logs.insert(ListItem::new(line.clone()), tz.clone());
|
||||
logs.insert(ListItem::new(line.clone()), tz.clone());
|
||||
logs.insert(ListItem::new(line), tz);
|
||||
|
||||
assert_eq!(logs.logs.items.len(), 2);
|
||||
}
|
||||
}
|
||||
|
||||
+47
-155
@@ -54,9 +54,9 @@ impl fmt::Display for Header {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(debug_assertions))]
|
||||
/// Global app_state, stored in an Arc<Mutex>
|
||||
#[derive(Debug, Clone)]
|
||||
#[cfg(not(test))]
|
||||
pub struct AppData {
|
||||
containers: StatefulList<ContainerItem>,
|
||||
error: Option<AppError>,
|
||||
@@ -64,31 +64,17 @@ pub struct AppData {
|
||||
pub args: CliArgs,
|
||||
}
|
||||
|
||||
#[cfg(debug_assertions)]
|
||||
/// Global app_state, stored in an Arc<Mutex>
|
||||
#[derive(Debug, Clone)]
|
||||
#[cfg(test)]
|
||||
pub struct AppData {
|
||||
containers: StatefulList<ContainerItem>,
|
||||
error: Option<AppError>,
|
||||
sorted_by: Option<(Header, SortedOrder)>,
|
||||
debug_string: String,
|
||||
pub containers: StatefulList<ContainerItem>,
|
||||
pub error: Option<AppError>,
|
||||
pub sorted_by: Option<(Header, SortedOrder)>,
|
||||
pub args: CliArgs,
|
||||
}
|
||||
|
||||
impl AppData {
|
||||
#[cfg(debug_assertions)]
|
||||
pub fn get_debug_string(&self) -> &str {
|
||||
&self.debug_string
|
||||
}
|
||||
|
||||
#[cfg(debug_assertions)]
|
||||
#[allow(unused)]
|
||||
pub fn push_debug_string(&mut self, x: &str) {
|
||||
self.debug_string.push_str(x);
|
||||
}
|
||||
|
||||
/// Generate a default app_state
|
||||
#[cfg(not(debug_assertions))]
|
||||
pub fn default(args: CliArgs) -> Self {
|
||||
Self {
|
||||
args,
|
||||
@@ -98,18 +84,6 @@ impl AppData {
|
||||
}
|
||||
}
|
||||
|
||||
/// Generate a default app_state
|
||||
#[cfg(debug_assertions)]
|
||||
pub fn default(args: CliArgs) -> Self {
|
||||
Self {
|
||||
args,
|
||||
containers: StatefulList::new(vec![]),
|
||||
error: None,
|
||||
sorted_by: None,
|
||||
debug_string: String::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Current time as unix timestamp
|
||||
#[allow(clippy::expect_used)]
|
||||
fn get_systemtime() -> u64 {
|
||||
@@ -380,7 +354,7 @@ impl AppData {
|
||||
.map_or_else(String::new, |ci| {
|
||||
let logs_len = ci.logs.get_state_title();
|
||||
let prefix = if logs_len.is_empty() {
|
||||
String::new()
|
||||
String::from(" ")
|
||||
} else {
|
||||
format!("{logs_len} ")
|
||||
};
|
||||
@@ -690,70 +664,21 @@ impl AppData {
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[allow(clippy::unwrap_used, clippy::many_single_char_names, unused)]
|
||||
#[allow(clippy::unwrap_used, clippy::many_single_char_names)]
|
||||
mod tests {
|
||||
|
||||
use super::*;
|
||||
use crate::tests::{gen_appdata, gen_container_summary, gen_containers};
|
||||
use std::collections::VecDeque;
|
||||
|
||||
use super::*;
|
||||
|
||||
const fn gen_args() -> CliArgs {
|
||||
CliArgs {
|
||||
color: false,
|
||||
docker_interval: 1000,
|
||||
gui: true,
|
||||
host: None,
|
||||
in_container: false,
|
||||
save_dir: None,
|
||||
raw: false,
|
||||
show_self: false,
|
||||
timestamp: false,
|
||||
use_cli: false,
|
||||
}
|
||||
}
|
||||
|
||||
fn gen_item(id: &ContainerId, index: usize) -> ContainerItem {
|
||||
ContainerItem::new(
|
||||
u64::try_from(index).unwrap(),
|
||||
id.clone(),
|
||||
format!("image_{index}"),
|
||||
false,
|
||||
format!("container_{index}"),
|
||||
State::Running,
|
||||
format!("Up {index} hour"),
|
||||
)
|
||||
}
|
||||
|
||||
fn gen_appdata(containers: &[ContainerItem]) -> AppData {
|
||||
AppData {
|
||||
containers: StatefulList::new(containers.to_vec()),
|
||||
error: None,
|
||||
sorted_by: None,
|
||||
debug_string: String::new(),
|
||||
args: gen_args(),
|
||||
}
|
||||
}
|
||||
|
||||
fn gen_containers() -> (Vec<ContainerId>, Vec<ContainerItem>) {
|
||||
let ids = (1..=3)
|
||||
.map(|i| ContainerId::from(format!("{i}").as_str()))
|
||||
.collect::<Vec<_>>();
|
||||
let containers = ids
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(index, id)| gen_item(id, index + 1))
|
||||
.collect::<Vec<_>>();
|
||||
(ids, containers)
|
||||
}
|
||||
|
||||
// ******** //
|
||||
// ******* //
|
||||
// Sort by //
|
||||
// ******** //
|
||||
// ******* //
|
||||
|
||||
#[test]
|
||||
/// Sort by header: name
|
||||
fn test_app_data_set_sort_by_header_name() {
|
||||
let (ids, containers) = gen_containers();
|
||||
let (_ids, containers) = gen_containers();
|
||||
|
||||
let mut app_data = gen_appdata(&containers);
|
||||
|
||||
@@ -780,7 +705,7 @@ mod tests {
|
||||
#[test]
|
||||
/// Sort by header: state
|
||||
fn test_app_data_set_sort_by_header_state() {
|
||||
let (ids, containers) = gen_containers();
|
||||
let (_ids, containers) = gen_containers();
|
||||
|
||||
let mut app_data = gen_appdata(&containers);
|
||||
|
||||
@@ -817,7 +742,7 @@ mod tests {
|
||||
#[test]
|
||||
/// Sort by header: status
|
||||
fn test_app_data_set_sort_by_header_status() {
|
||||
let (ids, containers) = gen_containers();
|
||||
let (_ids, containers) = gen_containers();
|
||||
|
||||
let mut app_data = gen_appdata(&containers);
|
||||
|
||||
@@ -853,7 +778,7 @@ mod tests {
|
||||
#[test]
|
||||
/// Sort by header: cpu
|
||||
fn test_app_data_set_sort_by_header_cpu() {
|
||||
let (ids, containers) = gen_containers();
|
||||
let (_ids, containers) = gen_containers();
|
||||
|
||||
let mut app_data = gen_appdata(&containers);
|
||||
|
||||
@@ -890,7 +815,7 @@ mod tests {
|
||||
#[test]
|
||||
/// Sort by header: memory
|
||||
fn test_app_data_set_sort_by_header_mem() {
|
||||
let (ids, containers) = gen_containers();
|
||||
let (_ids, containers) = gen_containers();
|
||||
|
||||
let mut app_data = gen_appdata(&containers);
|
||||
|
||||
@@ -927,7 +852,7 @@ mod tests {
|
||||
#[test]
|
||||
/// Sort by header: id
|
||||
fn test_app_data_set_sort_by_header_id() {
|
||||
let (ids, containers) = gen_containers();
|
||||
let (_ids, containers) = gen_containers();
|
||||
|
||||
let mut app_data = gen_appdata(&containers);
|
||||
|
||||
@@ -954,7 +879,7 @@ mod tests {
|
||||
#[test]
|
||||
/// Sort by header: image
|
||||
fn test_app_data_set_sort_by_header_image() {
|
||||
let (ids, containers) = gen_containers();
|
||||
let (_ids, containers) = gen_containers();
|
||||
|
||||
let mut app_data = gen_appdata(&containers);
|
||||
|
||||
@@ -981,7 +906,7 @@ mod tests {
|
||||
#[test]
|
||||
/// Sort by header: rx
|
||||
fn test_app_data_set_sort_by_header_rx() {
|
||||
let (ids, containers) = gen_containers();
|
||||
let (_ids, containers) = gen_containers();
|
||||
|
||||
let mut app_data = gen_appdata(&containers);
|
||||
|
||||
@@ -1018,7 +943,7 @@ mod tests {
|
||||
#[test]
|
||||
/// Sort by header: tx
|
||||
fn test_app_data_set_sort_by_header_tx() {
|
||||
let (ids, containers) = gen_containers();
|
||||
let (_ids, containers) = gen_containers();
|
||||
|
||||
let mut app_data = gen_appdata(&containers);
|
||||
|
||||
@@ -1055,7 +980,7 @@ mod tests {
|
||||
#[test]
|
||||
/// Sort by header when selected headers match
|
||||
fn test_app_data_set_sort_by_header_match() {
|
||||
let (ids, containers) = gen_containers();
|
||||
let (_ids, containers) = gen_containers();
|
||||
|
||||
let mut app_data = gen_appdata(&containers);
|
||||
|
||||
@@ -1082,7 +1007,7 @@ mod tests {
|
||||
#[test]
|
||||
/// reset sorted
|
||||
fn test_app_data_reset_sorted() {
|
||||
let (ids, containers) = gen_containers();
|
||||
let (_ids, containers) = gen_containers();
|
||||
|
||||
let mut app_data = gen_appdata(&containers);
|
||||
|
||||
@@ -1121,18 +1046,18 @@ mod tests {
|
||||
#[test]
|
||||
/// Get len of current containers vec
|
||||
fn test_app_data_get_container_len() {
|
||||
let (ids, containers) = gen_containers();
|
||||
let mut app_data = gen_appdata(&containers);
|
||||
let (_ids, containers) = gen_containers();
|
||||
let app_data = gen_appdata(&containers);
|
||||
assert_eq!(app_data.get_container_len(), 3);
|
||||
}
|
||||
|
||||
#[test]
|
||||
/// Select the first container
|
||||
fn test_app_data_containers_start() {
|
||||
let (ids, containers) = gen_containers();
|
||||
let (_ids, containers) = gen_containers();
|
||||
let mut app_data = gen_appdata(&containers);
|
||||
|
||||
/// No container selected
|
||||
// No container selected
|
||||
let result = app_data.get_container_state();
|
||||
assert_eq!(result.selected(), None);
|
||||
assert_eq!(result.offset(), 0);
|
||||
@@ -1174,7 +1099,7 @@ mod tests {
|
||||
/// advance container list state by one
|
||||
/// get get_selected_container_id() & get_selected_container_id_state_name() return valid Some data
|
||||
fn test_app_data_containers_next() {
|
||||
let (ids, containers) = gen_containers();
|
||||
let (_ids, containers) = gen_containers();
|
||||
let mut app_data = gen_appdata(&containers);
|
||||
|
||||
// Advance list state by 1
|
||||
@@ -1202,7 +1127,7 @@ mod tests {
|
||||
/// advance container list state to the end
|
||||
/// get get_selected_container_id() & get_selected_container_id_state_name() return valid Some data
|
||||
fn test_app_data_containers_end() {
|
||||
let (ids, containers) = gen_containers();
|
||||
let (_ids, containers) = gen_containers();
|
||||
let mut app_data = gen_appdata(&containers);
|
||||
|
||||
app_data.containers_end();
|
||||
@@ -1240,7 +1165,7 @@ mod tests {
|
||||
#[test]
|
||||
/// go to previous container
|
||||
fn test_app_data_containers_prev() {
|
||||
let (ids, containers) = gen_containers();
|
||||
let (_ids, containers) = gen_containers();
|
||||
let mut app_data = gen_appdata(&containers);
|
||||
|
||||
app_data.containers_end();
|
||||
@@ -1253,7 +1178,7 @@ mod tests {
|
||||
#[test]
|
||||
// Get the currently selected container
|
||||
fn test_app_data_get_selected_container() {
|
||||
let (ids, mut containers) = gen_containers();
|
||||
let (_ids, mut containers) = gen_containers();
|
||||
let mut app_data = gen_appdata(&containers);
|
||||
|
||||
let result = app_data.get_selected_container();
|
||||
@@ -1265,7 +1190,7 @@ mod tests {
|
||||
let result = app_data.get_selected_container();
|
||||
assert_eq!(result, Some(&containers[1]));
|
||||
|
||||
/// As above, but now as mut
|
||||
// As above, but now as mut
|
||||
let result = app_data.get_mut_selected_container();
|
||||
assert_eq!(result, Some(&mut containers[1]));
|
||||
}
|
||||
@@ -1273,7 +1198,7 @@ mod tests {
|
||||
#[test]
|
||||
// Get mut container by id
|
||||
fn test_app_data_get_container_by_id() {
|
||||
let (ids, mut containers) = gen_containers();
|
||||
let (_ids, mut containers) = gen_containers();
|
||||
let mut app_data = gen_appdata(&containers);
|
||||
|
||||
let result = app_data.get_container_by_id(&ContainerId::from("2"));
|
||||
@@ -1283,7 +1208,7 @@ mod tests {
|
||||
#[test]
|
||||
// Get just the containers name by id
|
||||
fn test_app_data_get_container_name_by_id() {
|
||||
let (ids, mut containers) = gen_containers();
|
||||
let (_ids, containers) = gen_containers();
|
||||
let mut app_data = gen_appdata(&containers);
|
||||
|
||||
let result = app_data.get_container_name_by_id(&ContainerId::from("2"));
|
||||
@@ -1293,7 +1218,7 @@ mod tests {
|
||||
#[test]
|
||||
// Get the id of the currently selected container
|
||||
fn test_app_data_get_selected_container_id() {
|
||||
let (ids, mut containers) = gen_containers();
|
||||
let (_ids, containers) = gen_containers();
|
||||
let mut app_data = gen_appdata(&containers);
|
||||
app_data.containers_end();
|
||||
|
||||
@@ -1303,7 +1228,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_app_data_get_selected_container_id_state_name() {
|
||||
let (ids, mut containers) = gen_containers();
|
||||
let (_ids, containers) = gen_containers();
|
||||
let mut app_data = gen_appdata(&containers);
|
||||
app_data.containers_end();
|
||||
|
||||
@@ -1325,7 +1250,7 @@ mod tests {
|
||||
#[test]
|
||||
/// Docker commands returned correctly
|
||||
fn test_app_data_selected_docker_command() {
|
||||
let (ids, mut containers) = gen_containers();
|
||||
let (_ids, containers) = gen_containers();
|
||||
let mut app_data = gen_appdata(&containers);
|
||||
|
||||
// No commands when no container selected
|
||||
@@ -1343,7 +1268,7 @@ mod tests {
|
||||
#[test]
|
||||
/// Docker command next works
|
||||
fn test_app_data_selected_docker_command_next() {
|
||||
let (ids, mut containers) = gen_containers();
|
||||
let (_ids, containers) = gen_containers();
|
||||
let mut app_data = gen_appdata(&containers);
|
||||
app_data.containers_start();
|
||||
app_data.docker_controls_start();
|
||||
@@ -1356,7 +1281,7 @@ mod tests {
|
||||
#[test]
|
||||
/// Dockercommand end works, and next has no effect when at end
|
||||
fn test_app_data_selected_docker_command_end() {
|
||||
let (ids, mut containers) = gen_containers();
|
||||
let (_ids, containers) = gen_containers();
|
||||
let mut app_data = gen_appdata(&containers);
|
||||
app_data.containers_start();
|
||||
app_data.docker_controls_end();
|
||||
@@ -1364,7 +1289,7 @@ mod tests {
|
||||
let result = app_data.selected_docker_controls();
|
||||
assert_eq!(result, Some(DockerControls::Delete));
|
||||
|
||||
/// Next has no effect when at end
|
||||
// Next has no effect when at end
|
||||
app_data.docker_controls_next();
|
||||
let result = app_data.selected_docker_controls();
|
||||
assert_eq!(result, Some(DockerControls::Delete));
|
||||
@@ -1373,7 +1298,7 @@ mod tests {
|
||||
#[test]
|
||||
/// Docker commands previous works, and has no effect when at start
|
||||
fn test_app_data_selected_docker_command_previous() {
|
||||
let (ids, mut containers) = gen_containers();
|
||||
let (_ids, containers) = gen_containers();
|
||||
let mut app_data = gen_appdata(&containers);
|
||||
app_data.containers_start();
|
||||
app_data.docker_controls_end();
|
||||
@@ -1382,7 +1307,7 @@ mod tests {
|
||||
let result = app_data.selected_docker_controls();
|
||||
assert_eq!(result, Some(DockerControls::Stop));
|
||||
|
||||
/// previous has no effect when at start
|
||||
// previous has no effect when at start
|
||||
app_data.docker_controls_start();
|
||||
app_data.docker_controls_previous();
|
||||
let result = app_data.selected_docker_controls();
|
||||
@@ -1696,7 +1621,7 @@ mod tests {
|
||||
#[test]
|
||||
/// Chart data returned correctly
|
||||
fn test_app_data_get_chart_data() {
|
||||
let (ids, containers) = gen_containers();
|
||||
let (_ids, containers) = gen_containers();
|
||||
let mut app_data = gen_appdata(&containers);
|
||||
|
||||
let result = app_data.get_chart_data();
|
||||
@@ -1734,8 +1659,8 @@ mod tests {
|
||||
#[test]
|
||||
/// Header widths return correctly
|
||||
fn test_app_data_get_width() {
|
||||
let (ids, containers) = gen_containers();
|
||||
let mut app_data = gen_appdata(&containers);
|
||||
let (_ids, containers) = gen_containers();
|
||||
let app_data = gen_appdata(&containers);
|
||||
|
||||
let result = app_data.get_width();
|
||||
let expected = Columns {
|
||||
@@ -1780,45 +1705,12 @@ mod tests {
|
||||
#[test]
|
||||
/// Update stats functioning
|
||||
fn test_app_data_update_containers() {
|
||||
let (ids, containers) = gen_containers();
|
||||
let (_ids, containers) = gen_containers();
|
||||
let mut app_data = gen_appdata(&containers);
|
||||
let result_pre = app_data.get_container_items().clone();
|
||||
|
||||
let mut input = vec![
|
||||
ContainerSummary {
|
||||
id: Some("1".to_owned()),
|
||||
names: Some(vec!["container_1".to_owned()]),
|
||||
image: Some("image_1".to_owned()),
|
||||
image_id: Some("1".to_owned()),
|
||||
command: None,
|
||||
created: Some(1),
|
||||
ports: None,
|
||||
size_rw: None,
|
||||
size_root_fs: None,
|
||||
labels: None,
|
||||
state: Some("paused".to_owned()),
|
||||
status: Some("Up 1 hour".to_owned()),
|
||||
host_config: None,
|
||||
network_settings: None,
|
||||
mounts: None,
|
||||
},
|
||||
ContainerSummary {
|
||||
id: Some("2".to_owned()),
|
||||
names: Some(vec!["container_2".to_owned()]),
|
||||
image: Some("image_2".to_owned()),
|
||||
image_id: Some("2".to_owned()),
|
||||
command: None,
|
||||
created: Some(2),
|
||||
ports: None,
|
||||
size_rw: None,
|
||||
size_root_fs: None,
|
||||
labels: None,
|
||||
state: Some("dead".to_owned()),
|
||||
status: Some("Up 2 hour".to_owned()),
|
||||
host_config: None,
|
||||
network_settings: None,
|
||||
mounts: None,
|
||||
},
|
||||
gen_container_summary(1, "paused"),
|
||||
gen_container_summary(2, "dead"),
|
||||
];
|
||||
|
||||
app_data.update_containers(&mut input);
|
||||
|
||||
+145
-3
@@ -71,6 +71,7 @@ pub struct DockerData {
|
||||
impl DockerData {
|
||||
/// Use docker stats to calculate current cpu usage
|
||||
#[allow(clippy::cast_precision_loss)]
|
||||
// FIX: this can overflow
|
||||
fn calculate_usage(stats: &Stats) -> f64 {
|
||||
let mut cpu_percentage = 0.0;
|
||||
let previous_cpu = stats.precpu_stats.cpu_usage.total_usage;
|
||||
@@ -162,7 +163,6 @@ impl DockerData {
|
||||
/// Update all stats, spawn each container into own tokio::spawn thread
|
||||
fn update_all_container_stats(&mut self, all_ids: &[(State, ContainerId)]) {
|
||||
for (state, id) in all_ids {
|
||||
// let init = self.init.as_ref().map_or_else(|| None, |x| Some((Arc::clone(x), all_ids.len())));
|
||||
let docker = Arc::clone(&self.docker);
|
||||
let app_data = Arc::clone(&self.app_data);
|
||||
let spawns = Arc::clone(&self.spawns);
|
||||
@@ -436,7 +436,7 @@ impl DockerData {
|
||||
}
|
||||
|
||||
/// Send an update message every x ms, where x is the args.docker_interval
|
||||
fn croner(args: &CliArgs, docker_tx: Sender<DockerMessage>) {
|
||||
fn scheduler(args: &CliArgs, docker_tx: Sender<DockerMessage>) {
|
||||
let update_duration = std::time::Duration::from_millis(u64::from(args.docker_interval));
|
||||
let mut now = std::time::Instant::now();
|
||||
tokio::spawn(async move {
|
||||
@@ -472,10 +472,152 @@ impl DockerData {
|
||||
spawns: Arc::new(Mutex::new(HashMap::new())),
|
||||
};
|
||||
inner.initialise_container_data().await;
|
||||
Self::croner(&args, docker_tx);
|
||||
Self::scheduler(&args, docker_tx);
|
||||
inner.message_handler().await;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// tests, use redis-test container, check logs exists, and selector of logs, and that it increases, and matches end, when you run restart on the docker containers
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use bollard::container::{
|
||||
BlkioStats, CPUStats, CPUUsage, MemoryStats, PidsStats, StorageStats, ThrottlingData,
|
||||
};
|
||||
|
||||
use super::*;
|
||||
|
||||
#[allow(clippy::too_many_lines)]
|
||||
fn gen_stats(x: u64, y: u64) -> Stats {
|
||||
Stats {
|
||||
read: String::new(),
|
||||
preread: String::new(),
|
||||
num_procs: 0,
|
||||
pids_stats: PidsStats {
|
||||
current: None,
|
||||
limit: None,
|
||||
},
|
||||
network: None,
|
||||
networks: None,
|
||||
memory_stats: MemoryStats {
|
||||
stats: None,
|
||||
max_usage: None,
|
||||
usage: None,
|
||||
failcnt: None,
|
||||
limit: None,
|
||||
commit: None,
|
||||
commit_peak: None,
|
||||
commitbytes: None,
|
||||
commitpeakbytes: None,
|
||||
privateworkingset: None,
|
||||
},
|
||||
blkio_stats: BlkioStats {
|
||||
io_service_bytes_recursive: None,
|
||||
io_serviced_recursive: None,
|
||||
io_queue_recursive: None,
|
||||
io_service_time_recursive: None,
|
||||
io_wait_time_recursive: None,
|
||||
io_merged_recursive: None,
|
||||
io_time_recursive: None,
|
||||
sectors_recursive: None,
|
||||
},
|
||||
cpu_stats: CPUStats {
|
||||
cpu_usage: CPUUsage {
|
||||
percpu_usage: Some(vec![
|
||||
291_593_800,
|
||||
182_192_900,
|
||||
195_048_700,
|
||||
23_032_300,
|
||||
132_928_700,
|
||||
235_555_600,
|
||||
120_225_700,
|
||||
175_752_000,
|
||||
213_060_300,
|
||||
95_321_600,
|
||||
226_821_000,
|
||||
0,
|
||||
109_151_300,
|
||||
0,
|
||||
86_240_200,
|
||||
1_884_400,
|
||||
59_077_300,
|
||||
23_224_900,
|
||||
95_386_300,
|
||||
144_987_400,
|
||||
]),
|
||||
total_usage: 250_000_000,
|
||||
usage_in_usermode: 1_020_000_000,
|
||||
usage_in_kernelmode: 1_030_000_000,
|
||||
},
|
||||
system_cpu_usage: Some(x),
|
||||
online_cpus: Some(1),
|
||||
throttling_data: ThrottlingData {
|
||||
periods: 0,
|
||||
throttled_periods: 0,
|
||||
throttled_time: 0,
|
||||
},
|
||||
},
|
||||
precpu_stats: CPUStats {
|
||||
cpu_usage: CPUUsage {
|
||||
percpu_usage: Some(vec![
|
||||
291_593_800,
|
||||
182_192_900,
|
||||
195_048_700,
|
||||
23_032_300,
|
||||
132_928_700,
|
||||
235_555_600,
|
||||
120_225_700,
|
||||
175_752_000,
|
||||
213_060_300,
|
||||
95_321_600,
|
||||
226_821_000,
|
||||
0,
|
||||
109_151_300,
|
||||
0,
|
||||
86_240_200,
|
||||
1_884_400,
|
||||
59_077_300,
|
||||
23_224_900,
|
||||
93_831_100,
|
||||
144_987_400,
|
||||
]),
|
||||
total_usage: 200_000_000,
|
||||
usage_in_usermode: 1_020_000_000,
|
||||
usage_in_kernelmode: 1_020_000_000,
|
||||
},
|
||||
system_cpu_usage: Some(y),
|
||||
online_cpus: Some(1),
|
||||
throttling_data: ThrottlingData {
|
||||
periods: 0,
|
||||
throttled_periods: 0,
|
||||
throttled_time: 0,
|
||||
},
|
||||
},
|
||||
storage_stats: StorageStats {
|
||||
read_count_normalized: None,
|
||||
read_size_bytes: None,
|
||||
write_count_normalized: None,
|
||||
write_size_bytes: None,
|
||||
},
|
||||
name: "/container_1".to_owned(),
|
||||
id: "1".to_owned(),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[allow(clippy::float_cmp)]
|
||||
/// Test the stats calculator, had to cheat here to get round input/outputs
|
||||
fn test_calculate_usage_no_previous_cpu() {
|
||||
let stats = gen_stats(1_000_000_000, 900_000_000);
|
||||
let result = DockerData::calculate_usage(&stats);
|
||||
assert_eq!(result, 50.0);
|
||||
|
||||
let stats = gen_stats(1_000_000_000, 800_000_000);
|
||||
let result = DockerData::calculate_usage(&stats);
|
||||
assert_eq!(result, 25.0);
|
||||
|
||||
let stats = gen_stats(1_000_000_000, 750_000_000);
|
||||
let result = DockerData::calculate_usage(&stats);
|
||||
assert_eq!(result, 20.00);
|
||||
}
|
||||
}
|
||||
|
||||
+79
@@ -164,3 +164,82 @@ async fn main() {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[allow(clippy::unwrap_used, clippy::many_single_char_names, unused)]
|
||||
mod tests {
|
||||
use bollard::service::ContainerSummary;
|
||||
|
||||
use crate::{
|
||||
app_data::{AppData, ContainerId, ContainerItem, State, StatefulList},
|
||||
parse_args::CliArgs,
|
||||
};
|
||||
|
||||
pub const fn gen_args() -> CliArgs {
|
||||
CliArgs {
|
||||
color: false,
|
||||
docker_interval: 1000,
|
||||
gui: true,
|
||||
host: None,
|
||||
in_container: false,
|
||||
save_dir: None,
|
||||
raw: false,
|
||||
show_self: false,
|
||||
timestamp: false,
|
||||
use_cli: false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn gen_item(id: &ContainerId, index: usize) -> ContainerItem {
|
||||
ContainerItem::new(
|
||||
u64::try_from(index).unwrap(),
|
||||
id.clone(),
|
||||
format!("image_{index}"),
|
||||
false,
|
||||
format!("container_{index}"),
|
||||
State::Running,
|
||||
format!("Up {index} hour"),
|
||||
)
|
||||
}
|
||||
|
||||
pub fn gen_appdata(containers: &[ContainerItem]) -> AppData {
|
||||
AppData {
|
||||
containers: StatefulList::new(containers.to_vec()),
|
||||
error: None,
|
||||
sorted_by: None,
|
||||
args: gen_args(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn gen_containers() -> (Vec<ContainerId>, Vec<ContainerItem>) {
|
||||
let ids = (1..=3)
|
||||
.map(|i| ContainerId::from(format!("{i}").as_str()))
|
||||
.collect::<Vec<_>>();
|
||||
let containers = ids
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(index, id)| gen_item(id, index + 1))
|
||||
.collect::<Vec<_>>();
|
||||
(ids, containers)
|
||||
}
|
||||
|
||||
pub fn gen_container_summary(index: usize, state: &str) -> ContainerSummary {
|
||||
ContainerSummary {
|
||||
id: Some(format!("{index}")),
|
||||
names: Some(vec![format!("container_{}", index)]),
|
||||
image: Some(format!("image_{index}")),
|
||||
image_id: Some(format!("{index}")),
|
||||
command: None,
|
||||
created: Some(i64::try_from(index).unwrap()),
|
||||
ports: None,
|
||||
size_rw: None,
|
||||
size_root_fs: None,
|
||||
labels: None,
|
||||
state: Some(state.to_owned()),
|
||||
status: Some(format!("Up {index} hour")),
|
||||
host_config: None,
|
||||
network_settings: None,
|
||||
mounts: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -72,3 +72,79 @@ pub mod log_sanitizer {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use ratatui::{
|
||||
style::{Color, Style},
|
||||
text::{Line, Span},
|
||||
};
|
||||
|
||||
use super::log_sanitizer;
|
||||
|
||||
// This spells out "oxker", with each char having a foreground and background colour
|
||||
const INPUT: &str = "\x1b[31;47mo\x1b[32;40mx\x1b[33;41mk\x1b[34;42me\x1b[35;43mr\x1b[0m";
|
||||
|
||||
#[test]
|
||||
/// Return test raw, as in show escape codes
|
||||
fn color_match_raw() {
|
||||
let result = log_sanitizer::raw(INPUT);
|
||||
let expected = vec![Line {
|
||||
spans: [Span {
|
||||
content: std::borrow::Cow::Borrowed(
|
||||
"\x1b[31;47mo\x1b[32;40mx\x1b[33;41mk\x1b[34;42me\x1b[35;43mr\x1b[0m",
|
||||
),
|
||||
style: Style::default(),
|
||||
}]
|
||||
.to_vec(),
|
||||
alignment: None,
|
||||
}];
|
||||
assert_eq!(result, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
// Use the escape codes to colorize the text
|
||||
fn color_match_colorize() {
|
||||
let result = log_sanitizer::colorize_logs(INPUT);
|
||||
let expected = vec![Line {
|
||||
spans: vec![
|
||||
Span {
|
||||
content: std::borrow::Cow::Borrowed("o"),
|
||||
style: Style::default().fg(Color::Red).bg(Color::White),
|
||||
},
|
||||
Span {
|
||||
content: std::borrow::Cow::Borrowed("x"),
|
||||
style: Style::default().fg(Color::Green).bg(Color::Black),
|
||||
},
|
||||
Span {
|
||||
content: std::borrow::Cow::Borrowed("k"),
|
||||
style: Style::default().fg(Color::Yellow).bg(Color::Red),
|
||||
},
|
||||
Span {
|
||||
content: std::borrow::Cow::Borrowed("e"),
|
||||
style: Style::default().fg(Color::Blue).bg(Color::Green),
|
||||
},
|
||||
Span {
|
||||
content: std::borrow::Cow::Borrowed("r"),
|
||||
style: Style::default().fg(Color::Magenta).bg(Color::Yellow),
|
||||
},
|
||||
],
|
||||
alignment: None,
|
||||
}];
|
||||
assert_eq!(result, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
// Remove all escape ansi codes from given input
|
||||
fn color_match_remove_ansi() {
|
||||
let result = log_sanitizer::remove_ansi(INPUT);
|
||||
let expected = vec![Line {
|
||||
spans: vec![Span {
|
||||
content: std::borrow::Cow::Borrowed("oxker"),
|
||||
style: Style::default(),
|
||||
}],
|
||||
alignment: None,
|
||||
}];
|
||||
assert_eq!(result, expected);
|
||||
}
|
||||
}
|
||||
|
||||
+1747
-92
File diff suppressed because it is too large
Load Diff
+7
-28
@@ -217,22 +217,6 @@ impl Ui {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(debug_assertions))]
|
||||
fn get_wholelayout(f: &Frame) -> std::rc::Rc<[ratatui::layout::Rect]> {
|
||||
Layout::default()
|
||||
.direction(Direction::Vertical)
|
||||
.constraints([Constraint::Min(1), Constraint::Min(100)].as_ref())
|
||||
.split(f.size())
|
||||
}
|
||||
|
||||
#[cfg(debug_assertions)]
|
||||
fn get_wholelayout(f: &Frame) -> std::rc::Rc<[ratatui::layout::Rect]> {
|
||||
Layout::default()
|
||||
.direction(Direction::Vertical)
|
||||
.constraints([Constraint::Min(1), Constraint::Min(1), Constraint::Min(100)].as_ref())
|
||||
.split(f.size())
|
||||
}
|
||||
|
||||
/// Frequent data required by multiple framde drawing functions, can reduce mutex reads by placing it all in here
|
||||
#[derive(Debug)]
|
||||
pub struct FrameData {
|
||||
@@ -279,21 +263,16 @@ impl From<(MutexGuard<'_, AppData>, MutexGuard<'_, GuiState>)> for FrameData {
|
||||
fn draw_frame(f: &mut Frame, app_data: &Arc<Mutex<AppData>>, gui_state: &Arc<Mutex<GuiState>>) {
|
||||
let fd = FrameData::from((app_data.lock(), gui_state.lock()));
|
||||
|
||||
let whole_layout = get_wholelayout(f);
|
||||
#[cfg(debug_assertions)]
|
||||
draw_blocks::debug_bar(whole_layout[0], f, app_data.lock().get_debug_string());
|
||||
|
||||
#[cfg(debug_assertions)]
|
||||
let whole_layout_split = (1, 2);
|
||||
|
||||
#[cfg(not(debug_assertions))]
|
||||
let whole_layout_split = (0, 1);
|
||||
let whole_layout = Layout::default()
|
||||
.direction(Direction::Vertical)
|
||||
.constraints([Constraint::Min(1), Constraint::Min(100)].as_ref())
|
||||
.split(f.size());
|
||||
|
||||
// Split into 3, containers+controls, logs, then graphs
|
||||
let upper_main = Layout::default()
|
||||
.direction(Direction::Vertical)
|
||||
.constraints([Constraint::Max(fd.height), Constraint::Percentage(50)].as_ref())
|
||||
.split(whole_layout[whole_layout_split.1]);
|
||||
.split(whole_layout[1]);
|
||||
|
||||
let top_split = if fd.has_containers {
|
||||
vec![Constraint::Percentage(90), Constraint::Percentage(10)]
|
||||
@@ -318,11 +297,11 @@ fn draw_frame(f: &mut Frame, app_data: &Arc<Mutex<AppData>>, gui_state: &Arc<Mut
|
||||
.constraints(lower_split)
|
||||
.split(upper_main[1]);
|
||||
|
||||
draw_blocks::containers(app_data, top_panel[0], f, &fd, gui_state, &fd.columns);
|
||||
draw_blocks::containers(app_data, top_panel[0], f, &fd, gui_state);
|
||||
|
||||
draw_blocks::logs(app_data, lower_main[0], f, &fd, gui_state);
|
||||
|
||||
draw_blocks::heading_bar(whole_layout[whole_layout_split.0], f, &fd, gui_state);
|
||||
draw_blocks::heading_bar(whole_layout[0], f, &fd, gui_state);
|
||||
|
||||
if let Some(id) = fd.delete_confirm.as_ref() {
|
||||
app_data.lock().get_container_name_by_id(id).map_or_else(
|
||||
|
||||
Reference in New Issue
Block a user