use std::fmt::{Debug, Display};
use serde::Serialize;
use crate::event::process::ProcessEvent;
use crate::event::prompt::PromptRequest;
use crate::event::{Event, EventSender, SendError};
#[derive(Clone, Debug)]
pub struct Output {
sender: EventSender,
}
impl Output {
pub fn new(sender: EventSender) -> Self {
Self { sender }
}
pub async fn message(&self, message: impl Display) -> Result<(), SendError> {
self.sender.send(Event::Message(message.to_string())).await
}
pub async fn detail(&self, detail: impl Display) -> Result<(), SendError> {
self.sender.send(Event::Detail(detail.to_string())).await
}
pub async fn artifact<T>(&self, value: T) -> Result<(), SendError>
where
T: Display + Serialize + Debug + Send + Sync + 'static,
{
self.sender.send(Event::Artifact(Box::new(value))).await
}
pub async fn process_event(&self, event: ProcessEvent) -> Result<(), SendError> {
self.sender.send(Event::Process(Box::new(event))).await
}
pub async fn prompt(&self, request: PromptRequest) -> Result<(), SendError> {
self.sender.send(Event::Prompt(Box::new(request))).await
}
}
#[cfg(test)]
mod tests {
#![allow(clippy::missing_panics_doc)]
use std::fmt;
use serde::Serialize;
use super::*;
use crate::event::event_channel;
use crate::event::process::RunId;
use crate::process::Invocation;
use crate::prompt::Confirm;
#[derive(Clone, Debug, Serialize)]
struct TestArtifact {
value: String,
}
impl fmt::Display for TestArtifact {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.value)
}
}
#[tokio::test]
async fn artifact_sends_artifact_event() {
let (sender, mut receiver) = event_channel();
let output = Output::new(sender);
output
.artifact(TestArtifact {
value: "result".to_string(),
})
.await
.expect("should send");
let event = receiver.recv().await.expect("should receive");
assert!(matches!(event, Event::Artifact(ref a) if a.to_string() == "result"));
}
#[tokio::test]
async fn artifact_to_closed_channel_returns_error() {
let (sender, receiver) = event_channel();
let output = Output::new(sender);
drop(receiver);
let error = output
.artifact(TestArtifact {
value: "lost".to_string(),
})
.await
.expect_err("should fail");
assert_eq!(error.to_string(), "event channel closed");
}
#[tokio::test]
async fn clone_produces_independent_handle() {
let (sender, mut receiver) = event_channel();
let output = Output::new(sender);
let cloned = output.clone();
output.message("from original").await.expect("should send");
cloned.message("from clone").await.expect("should send");
let first = receiver.recv().await.expect("should receive first");
let second = receiver.recv().await.expect("should receive second");
assert!(matches!(first, Event::Message(ref s) if s == "from original"));
assert!(matches!(second, Event::Message(ref s) if s == "from clone"));
}
#[tokio::test]
async fn process_event_sends_process_event() {
let (sender, mut receiver) = event_channel();
let output = Output::new(sender);
let invocation = Invocation::new("git").arg("status");
output
.process_event(ProcessEvent::Started {
id: RunId::next(),
invocation: invocation.clone(),
process_id: None,
})
.await
.expect("should send");
assert_eq!(
receiver.recv().await.map(|event| match event {
Event::Process(event) => event.to_string(),
Event::Message(text) => text,
Event::Detail(text) => text,
Event::Artifact(artifact) => artifact.to_string(),
Event::Prompt(request) => format!("{request:?}"),
}),
Some(format!("$ {invocation}"))
);
}
#[tokio::test]
async fn prompt_sends_prompt_event() {
let (sender, mut receiver) = event_channel();
let output = Output::new(sender);
let (request, _pending) = PromptRequest::confirm(Confirm::new("Release?"));
output.prompt(request).await.expect("should send");
assert_eq!(
receiver.recv().await.map(|event| match event {
Event::Prompt(request) => request.question().to_owned(),
Event::Process(event) => event.to_string(),
Event::Message(text) => text,
Event::Detail(text) => text,
Event::Artifact(artifact) => artifact.to_string(),
}),
Some("Release?".to_owned())
);
}
#[tokio::test]
async fn detail_sends_detail_event() {
let (sender, mut receiver) = event_channel();
let output = Output::new(sender);
output
.detail("supplementary info")
.await
.expect("should send");
let event = receiver.recv().await.expect("should receive");
assert!(matches!(event, Event::Detail(ref s) if s == "supplementary info"));
}
#[tokio::test]
async fn detail_to_closed_channel_returns_error() {
let (sender, receiver) = event_channel();
let output = Output::new(sender);
drop(receiver);
let error = output.detail("lost").await.expect_err("should fail");
assert_eq!(error.to_string(), "event channel closed");
}
#[tokio::test]
async fn message_sends_message_event() {
let (sender, mut receiver) = event_channel();
let output = Output::new(sender);
output.message("hello").await.expect("should send");
let event = receiver.recv().await.expect("should receive");
assert!(matches!(event, Event::Message(ref s) if s == "hello"));
}
#[tokio::test]
async fn message_to_closed_channel_returns_error() {
let (sender, receiver) = event_channel();
let output = Output::new(sender);
drop(receiver);
let error = output.message("lost").await.expect_err("should fail");
assert_eq!(error.to_string(), "event channel closed");
}
#[tokio::test]
async fn message_with_format_args_sends_formatted_string() {
let (sender, mut receiver) = event_channel();
let output = Output::new(sender);
output
.message(format!("count: {}", 42))
.await
.expect("should send");
let event = receiver.recv().await.expect("should receive");
assert!(matches!(event, Event::Message(ref s) if s == "count: 42"));
}
#[test]
fn trait_send() {
fn assert_send<T: Send>() {}
assert_send::<Output>();
}
#[test]
fn trait_sync() {
fn assert_sync<T: Sync>() {}
assert_sync::<Output>();
}
#[test]
fn trait_unpin() {
fn assert_unpin<T: Unpin>() {}
assert_unpin::<Output>();
}
}