use std::time::Instant;
use shep_core::protocol::{BusEvent, DogSource, ProcessInfo};
use shep_core::status::ProcStatus;
use crate::lookout::app::{ActionVerb, App, Control, KeyPress, Msg, RowKey};
use crate::lookout::tail::{Stream, Tail};
use crate::lookout::theme::Palette;
use super::bleats::line;
use super::palette::plain;
pub fn app_with(flock: Vec<ProcessInfo>, palette: Palette) -> App {
let t0 = Instant::now();
let mut app = App::new(
palette,
Control::ReadOnly,
"/home/ada/.shep".to_string(),
t0,
);
app.update(Msg::Snapshot {
rows: flock,
at: t0,
});
app
}
pub fn sheep_in_fold(id: u32, name: &str, fold: Option<&str>) -> ProcessInfo {
ProcessInfo::builder(id, name, ProcStatus::Online)
.pid(Some(1000 + id))
.uptime_ms(60_000)
.fold(fold.map(str::to_string))
.build()
}
#[must_use]
pub fn sheep_in_fold_with_status(
id: u32,
name: &str,
fold: Option<&str>,
status: ProcStatus,
) -> ProcessInfo {
ProcessInfo::builder(id, name, status)
.pid(Some(1000 + id))
.uptime_ms(60_000)
.fold(fold.map(str::to_string))
.build()
}
pub fn sheep_with(
id: u32,
name: &str,
fold: Option<&str>,
uptime_ms: u64,
memory: Option<u64>,
restarts: u32,
) -> ProcessInfo {
ProcessInfo::builder(id, name, ProcStatus::Online)
.pid(Some(1000 + id))
.uptime_ms(uptime_ms)
.memory_bytes(memory)
.restarts(restarts)
.fold(fold.map(str::to_string))
.build()
}
pub fn instance_in_fold(id: u32, name: &str, slot: u32, fold: Option<&str>) -> ProcessInfo {
ProcessInfo::builder(id, name, ProcStatus::Online)
.pid(Some(1000 + id))
.uptime_ms(60_000)
.instance(Some(slot))
.fold(fold.map(str::to_string))
.build()
}
pub fn flock_of(count: u32, with_readings: u32) -> Vec<ProcessInfo> {
(0..count)
.map(|id| {
let reports = id < with_readings;
ProcessInfo::builder(id, format!("sheep-{id}"), ProcStatus::Online)
.pid(Some(48_000 + id))
.uptime_ms(4_512_000)
.cpu_percent(reports.then_some(3.5))
.memory_bytes(reports.then_some(182 << 20))
.out_file(Some(format!("/home/ada/.shep/logs/sheep-{id}-out.log")))
.err_file(Some(format!("/home/ada/.shep/logs/sheep-{id}-err.log")))
.build()
})
.collect()
}
pub fn app_with_a_dog() -> App {
let flock = vec![
ProcessInfo::builder(1, "web", ProcStatus::Online)
.pid(Some(48_001))
.uptime_ms(4_512_000)
.build(),
ProcessInfo::builder(2, "api", ProcStatus::Online)
.pid(Some(48_002))
.uptime_ms(4_512_000)
.build(),
ProcessInfo::builder(90, "otel", ProcStatus::Online)
.pid(Some(90_000))
.dog(Some(DogSource::BuiltIn { grants: None }))
.build(),
];
app_with(flock, plain())
}
pub fn app_with_a_dog_selected_and_control() -> App {
let decoy = ProcessInfo::builder(0, "!decoy", ProcStatus::Online).build();
let dog = ProcessInfo::builder(90, "otel", ProcStatus::Online)
.pid(Some(90_000))
.dog(Some(DogSource::Adopted {
grants: None,
path: "/opt/otel".to_string(),
channel: false,
}))
.build();
let mut app = app_with(vec![decoy, dog], plain());
app.set_control_for_tests(Control::Allowed);
app.update(Msg::Key(KeyPress::SelectDown));
app
}
pub fn app_with_a_built_in_dog_selected_and_control() -> App {
let decoy = ProcessInfo::builder(0, "!decoy", ProcStatus::Online).build();
let dog = ProcessInfo::builder(90, "otel", ProcStatus::Online)
.pid(Some(90_000))
.dog(Some(DogSource::BuiltIn { grants: None }))
.build();
let mut app = app_with(vec![decoy, dog], plain());
app.set_control_for_tests(Control::Allowed);
app.update(Msg::Key(KeyPress::SelectDown));
app
}
pub fn with_no_selection() -> App {
app_with(Vec::new(), plain())
}
pub fn with_selection(info: ProcessInfo) -> App {
with_selection_and_palette(info, plain())
}
pub fn with_selection_and_palette(info: ProcessInfo, palette: Palette) -> App {
assert!(
info.id > 0,
"the decoy takes id 0, so the sheep under test cannot"
);
let wanted = info.id;
let decoy = ProcessInfo::builder(0, "!decoy", ProcStatus::Online).build();
let mut app = app_with(vec![decoy, info], palette);
app.update(Msg::Key(KeyPress::SelectDown));
assert_eq!(
app.selected(),
Some(RowKey::Sheep(wanted)),
"the sheep under test must end up selected, and on row 1: the mutation \
this fixture exists to catch reads row 0 instead"
);
app
}
pub fn full_app() -> App {
let mut app = app_with(flock_of(12, 12), plain());
app.update(Msg::Bleats {
tail: Tail {
lines: (0..10)
.map(|n| line(Stream::Out, &format!("line-{n}")))
.collect(),
missed_lines: 0,
missed_bytes: 0,
read_bytes: 1_024,
note: None,
},
});
app
}
pub fn filtered_app(query: &str) -> App {
filtered_app_of(named_flock(), query)
}
pub fn filtered_app_of(flock: Vec<ProcessInfo>, query: &str) -> App {
let mut app = app_with(flock, plain());
if !query.is_empty() {
app.update(Msg::Key(KeyPress::FilterStart));
for typed in query.chars() {
app.update(Msg::Key(KeyPress::TextChar(typed)));
}
app.update(Msg::Key(KeyPress::TextApply));
}
app
}
pub fn editing_app(query: &str) -> App {
let mut app = app_with(named_flock(), plain());
app.update(Msg::Key(KeyPress::FilterStart));
for typed in query.chars() {
app.update(Msg::Key(KeyPress::TextChar(typed)));
}
app
}
fn named_flock() -> Vec<ProcessInfo> {
[(1, "web"), (2, "api"), (3, "web-worker"), (4, "cron")]
.into_iter()
.map(|(id, name)| {
ProcessInfo::builder(id, name, ProcStatus::Online)
.pid(Some(48_000 + id))
.uptime_ms(4_512_000)
.build()
})
.collect()
}
pub fn allowed_app() -> App {
let mut app = app_with(named_flock(), plain());
app.set_control_for_tests(Control::Allowed);
for _ in 0..named_flock().len() {
if app.selected() == Some(RowKey::Sheep(2)) {
break;
}
app.update(Msg::Key(KeyPress::SelectDown));
}
assert_eq!(
app.selected(),
Some(RowKey::Sheep(2)),
"the cursor must end up on api"
);
app
}
pub fn armed_app(verb: ActionVerb) -> App {
let mut app = allowed_app();
app.update(Msg::Key(KeyPress::Action(verb)));
app
}
pub fn acting_app(verb: ActionVerb) -> App {
let mut app = armed_app(verb);
app.update(Msg::Key(KeyPress::Confirm));
app
}
pub fn armed_app_with_a_filter_and_a_notice() -> App {
let mut app = filtered_app("api");
app.set_control_for_tests(Control::Allowed);
app.update(Msg::Key(KeyPress::Action(ActionVerb::Stop)));
app.update(Msg::Event(BusEvent::Dropped { count: 3 }));
app
}