use crate::collect::run::RunFailure;
use crate::model::types::{Pane, PaneKey};
pub trait Agents: Send + Sync {
fn name(&self) -> &'static str;
fn sessions(&self) -> Result<Vec<String>, RunFailure>;
fn list(&self, session: &str) -> Result<Vec<Pane>, RunFailure>;
fn read(&self, pane: &PaneKey, lines: u16) -> Result<Vec<String>, RunFailure>;
fn focus(&self, pane: &PaneKey) -> Result<(), RunFailure>;
}
impl<A: Agents + ?Sized> Agents for std::sync::Arc<A> {
fn name(&self) -> &'static str {
(**self).name()
}
fn sessions(&self) -> Result<Vec<String>, RunFailure> {
(**self).sessions()
}
fn list(&self, session: &str) -> Result<Vec<Pane>, RunFailure> {
(**self).list(session)
}
fn read(&self, pane: &PaneKey, lines: u16) -> Result<Vec<String>, RunFailure> {
(**self).read(pane, lines)
}
fn focus(&self, pane: &PaneKey) -> Result<(), RunFailure> {
(**self).focus(pane)
}
}
#[cfg(test)]
mod tests {
use super::testing::{pane, Fake, THE_FAKE};
use super::*;
use crate::model::types::testing::{key, A_SESSION};
use crate::model::types::PaneStatus;
use pretty_assertions::assert_eq;
use std::sync::Arc;
#[test]
fn a_shared_provider_answers_as_the_one_it_holds() {
let alone = Fake::holding(vec![pane("w:p1", "/srv/work", PaneStatus::Idle)])
.showing("w:p1", ["what the pane drew"]);
let shared: Arc<dyn Agents> = Arc::new(alone);
assert_eq!(shared.name(), THE_FAKE);
assert_eq!(
shared.sessions().expect("the provider runs one session"),
[A_SESSION]
);
assert_eq!(
shared.list(A_SESSION).expect("the provider holds one pane")[0].pane_id,
"w:p1"
);
assert_eq!(
shared.read(&key("w:p1"), 1).expect("the pane was staged"),
["what the pane drew"]
);
assert_eq!(shared.focus(&key("w:p1")), Ok(()));
}
#[test]
fn a_focus_the_provider_refused_is_refused_through_the_share() {
let refusing = Fake::holding(Vec::new()).unfocusable(RunFailure {
kind: crate::collect::run::FailureKind::Gone,
program: THE_FAKE.to_string(),
detail: "the pane is not there".to_string(),
unreadable: None,
});
let shared: Arc<dyn Agents> = Arc::new(refusing);
assert_eq!(
shared.focus(&key("w:gone")).map_err(|failure| failure.kind),
Err(crate::collect::run::FailureKind::Gone)
);
}
}
#[cfg(feature = "testing")]
pub mod testing {
use std::collections::BTreeMap;
use std::path::PathBuf;
use std::sync::Mutex;
use super::*;
use crate::model::types::testing::A_SESSION;
use crate::model::types::PaneStatus;
pub const THE_FAKE: &str = "a fake provider";
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Asked {
Sessions,
List { session: String },
Read { pane: PaneKey, lines: u16 },
Focus { pane: PaneKey },
}
pub fn pane(id: &str, cwd: &str, status: PaneStatus) -> Pane {
Pane::answered(
A_SESSION.to_string(),
id.to_string(),
PathBuf::from(cwd),
status,
)
}
pub fn in_session(mut pane: Pane, session: &str) -> Pane {
pane.session = session.to_string();
pane
}
pub fn named(mut pane: Pane, bead: &str) -> Pane {
pane.display_agent = Some(bead.to_string());
pane
}
pub fn titled(mut pane: Pane, title: &str) -> Pane {
pane.title = Some(title.to_string());
pane
}
#[derive(Default)]
pub struct Fake {
sessions: Option<RunFailure>,
panes: Vec<Pane>,
unanswering: BTreeMap<String, RunFailure>,
reads: BTreeMap<PaneKey, Result<Vec<String>, RunFailure>>,
focus: Option<RunFailure>,
asked: Mutex<Vec<Asked>>,
}
impl Fake {
pub fn holding(panes: Vec<Pane>) -> Self {
Self {
panes,
..Self::default()
}
}
pub fn unlistable(failure: RunFailure) -> Self {
Self {
sessions: Some(failure),
..Self::default()
}
}
pub fn not_answering_for(mut self, session: &str, failure: RunFailure) -> Self {
self.unanswering.insert(session.to_string(), failure);
self
}
pub fn showing<'a>(mut self, pane: &str, lines: impl IntoIterator<Item = &'a str>) -> Self {
self.reads.insert(
crate::model::types::testing::key(pane),
Ok(lines.into_iter().map(str::to_string).collect()),
);
self
}
pub fn unfocusable(mut self, failure: RunFailure) -> Self {
self.focus = Some(failure);
self
}
pub fn asked(&self) -> Vec<Asked> {
self.asked
.lock()
.expect("no test panics holding this")
.clone()
}
fn note(&self, question: Asked) {
self.asked
.lock()
.expect("no test panics holding this")
.push(question);
}
}
fn no_such_pane(pane: &PaneKey) -> RunFailure {
RunFailure {
kind: crate::collect::run::FailureKind::Gone,
program: THE_FAKE.to_string(),
detail: format!("no test staged a read of {} in {}", pane.id, pane.session),
unreadable: None,
}
}
impl Agents for Fake {
fn name(&self) -> &'static str {
THE_FAKE
}
fn sessions(&self) -> Result<Vec<String>, RunFailure> {
self.note(Asked::Sessions);
if let Some(failure) = &self.sessions {
return Err(failure.clone());
}
let mut sessions = vec![A_SESSION.to_string()];
for session in self
.panes
.iter()
.map(|pane| &pane.session)
.chain(self.unanswering.keys())
{
if !sessions.contains(session) {
sessions.push(session.clone());
}
}
Ok(sessions)
}
fn list(&self, session: &str) -> Result<Vec<Pane>, RunFailure> {
self.note(Asked::List {
session: session.to_string(),
});
if let Some(failure) = self.unanswering.get(session) {
return Err(failure.clone());
}
Ok(self
.panes
.iter()
.filter(|pane| pane.session == session)
.cloned()
.collect())
}
fn read(&self, pane: &PaneKey, lines: u16) -> Result<Vec<String>, RunFailure> {
self.note(Asked::Read {
pane: pane.clone(),
lines,
});
self.reads
.get(pane)
.cloned()
.unwrap_or_else(|| Err(no_such_pane(pane)))
}
fn focus(&self, pane: &PaneKey) -> Result<(), RunFailure> {
self.note(Asked::Focus { pane: pane.clone() });
self.focus.clone().map_or(Ok(()), Err)
}
}
}