use std::ffi::OsStr;
use std::time::Instant;
use ratatui::text::Line;
use shep_client::RequestError;
use shep_core::protocol::{BusEvent, Lamb, ProcessInfo, Response};
use shep_core::status::ProcStatus;
use super::super::app::{ActionVerb, App, Control, KeyPress, LambWalk, Msg, Sent};
use super::super::source::HostSample;
use super::super::tail::{Stream, Tail, TailLine};
use super::super::theme::Palette;
pub fn plain() -> Palette {
Palette::detect(None, None, None)
}
pub fn coloured() -> Palette {
Palette::detect(None, Some(OsStr::new("xterm-256color")), None)
}
pub fn app_with(flock: Vec<ProcessInfo>, palette: Palette) -> App {
let t0 = Instant::now();
let mut app = App::new(
palette,
Control::ReadOnly,
"/home/rin/.shep".to_string(),
t0,
);
app.update(Msg::Snapshot {
rows: flock,
at: t0,
});
app
}
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/rin/.shep/logs/sheep-{id}-out.log")))
.err_file(Some(format!("/home/rin/.shep/logs/sheep-{id}-err.log")))
.build()
})
.collect()
}
pub fn sample() -> HostSample {
HostSample {
load: (2.31, 4.10, 3.88),
cores: Some(10),
memory_total_bytes: 32 << 30,
memory_used_bytes: 12 * (1 << 30) + (410 << 20),
uptime_seconds: 6 * 86_400 + 3 * 3_600,
}
}
pub fn with_host(sample: HostSample, flock: Vec<ProcessInfo>) -> App {
let mut app = app_with(flock, plain());
app.update(Msg::Host {
sample: Some(sample),
});
app
}
pub fn with_host_none(flock: Vec<ProcessInfo>, unsupported: bool) -> App {
let mut app = app_with(flock, plain());
if unsupported {
app.update(Msg::Host { sample: None });
}
app
}
pub fn with_feed(tail: Tail) -> App {
let mut app = app_with(flock_of(3, 0), plain());
app.update(Msg::Bleats { tail });
app
}
pub fn with_feed_and_selection(tail: Tail, id: u32) -> App {
let mut app = app_with(flock_of(3, 0), plain());
for _ in 0..id {
app.update(Msg::Key(KeyPress::SelectDown));
}
app.update(Msg::Bleats { tail });
app
}
pub fn with_feed_and_palette(tail: Tail, palette: Palette) -> App {
let mut app = app_with(flock_of(3, 0), palette);
app.update(Msg::Bleats { tail });
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 decoy = ProcessInfo::builder(0, "decoy", ProcStatus::Online).build();
let mut app = app_with(vec![decoy, info], palette);
app.update(Msg::Key(KeyPress::SelectDown));
app
}
pub fn sheep_with_lambs() -> ProcessInfo {
ProcessInfo::builder(9, "gateway", ProcStatus::Online)
.pid(Some(48_301))
.lambs(Some(vec![
Lamb::new(48_302, "node"),
Lamb::new(48_303, "sh"),
]))
.build()
}
pub fn with_lamb_reading(walk: LambWalk) -> App {
with_lamb_reading_for(9, walk)
}
pub fn with_lamb_reading_for(id: u32, walk: LambWalk) -> App {
let mut app = with_selection(sheep_with_lambs());
app.update(Msg::Replied {
sent: Sent::Lambs { id },
result: reply_for(id, &walk),
});
app
}
pub fn app_with_lamb_reading_at(walk: LambWalk) -> (App, Instant) {
let t0 = Instant::now();
let mut app = with_selection(sheep_with_lambs());
app.update(Msg::Tick { now: t0 });
app.update(Msg::Replied {
sent: Sent::Lambs { id: 9 },
result: reply_for(9, &walk),
});
(app, t0)
}
fn reply_for(id: u32, walk: &LambWalk) -> Result<Response, RequestError> {
let lambs = match walk {
LambWalk::Failed => return Err(RequestError::Closed),
LambWalk::NotWalked => None,
LambWalk::Walked(lambs) => Some(lambs.clone()),
};
Ok(Response::Described(vec![
ProcessInfo::builder(id, "gateway", ProcStatus::Online)
.pid(Some(48_301))
.lambs(lambs)
.build(),
]))
}
pub fn lamb_line_of(app: &App) -> String {
render_all(&super::detail::detail_lines(app, 200))
.lines()
.find(|line| line.starts_with("lambs "))
.map(str::to_string)
.expect("the pane has a lamb line")
}
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 line(stream: Stream, text: &str) -> TailLine {
TailLine {
stream,
text: text.to_string(),
}
}
pub fn rendered(line: &Line<'static>) -> String {
line.spans
.iter()
.map(|span| span.content.as_ref())
.collect()
}
pub fn render_all(lines: &[Line<'static>]) -> String {
lines.iter().map(rendered).collect::<Vec<_>>().join("\n")
}
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::FilterChar(typed)));
}
app.update(Msg::Key(KeyPress::FilterApply));
}
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::FilterChar(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);
app.update(Msg::Key(KeyPress::SelectDown));
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
}