use std::sync::Arc;
use a2a_protocol_types::artifact::Artifact;
use a2a_protocol_types::error::{A2aError, A2aResult};
use a2a_protocol_types::events::{StreamResponse, TaskArtifactUpdateEvent};
use a2a_protocol_types::message::Part;
use a2a_protocol_types::task::{ContextId, TaskState};
use super::{CheckResult, Outcome, Report, check, check_with_context};
use crate::executor::AgentExecutor;
use crate::executor_helpers::EventEmitter;
use crate::request_context::RequestContext;
use crate::streaming::EventQueueWriter;
macro_rules! executor {
($name:ident, |$ctx:ident, $queue:ident| $body:block) => {
struct $name;
impl AgentExecutor for $name {
fn execute<'a>(
&'a self,
$ctx: &'a RequestContext,
$queue: &'a dyn EventQueueWriter,
) -> std::pin::Pin<Box<dyn Future<Output = A2aResult<()>> + Send + 'a>> {
Box::pin(async move $body)
}
}
};
}
mod gaps;
fn outcome_of(report: &Report, name: &str) -> Outcome {
report
.results()
.iter()
.find(|r| r.name == name)
.unwrap_or_else(|| panic!("no check named {name} in:\n{report}"))
.outcome
}
fn only_failure_is(report: &Report, name: &str) {
only_failures_are(report, &[name]);
}
fn only_failures_are(report: &Report, names: &[&str]) {
for name in names {
assert_eq!(outcome_of(report, name), Outcome::Fail, "{report}");
}
for r in report.results() {
assert!(
names.contains(&r.name) || r.outcome != Outcome::Fail,
"unexpected extra failure in {}:\n{report}",
r.name
);
}
}
executor!(Good, |ctx, queue| {
let emit = EventEmitter::new(ctx, queue);
if emit.is_cancelled() {
return emit.status(TaskState::Canceled).await;
}
emit.status(TaskState::Working).await?;
if emit.is_cancelled() {
return emit.status(TaskState::Canceled).await;
}
emit.artifact("result", vec![Part::text("done")], None, Some(true))
.await?;
if emit.is_cancelled() {
return emit.status(TaskState::Canceled).await;
}
emit.status(TaskState::Completed).await
});
#[tokio::test]
async fn a_conformant_executor_passes_and_grades_something() {
let report = check(Arc::new(Good)).await;
assert!(report.is_pass(), "{report}");
assert!(
report.graded() > 0,
"a report that grades nothing must not pass"
);
assert_eq!(report.failed(), 0, "{report}");
report.assert_pass();
}
executor!(Silent, |_ctx, _queue| { Ok(()) });
#[tokio::test]
async fn returning_ok_having_emitted_nothing_is_caught() {
only_failure_is(
&check(Arc::new(Silent)).await,
"ends_in_terminal_or_interrupt",
);
}
executor!(StopsAtWorking, |ctx, queue| {
EventEmitter::new(ctx, queue)
.status(TaskState::Working)
.await
});
#[tokio::test]
async fn leaving_the_task_working_is_caught() {
only_failure_is(
&check(Arc::new(StopsAtWorking)).await,
"ends_in_terminal_or_interrupt",
);
}
executor!(TalksAfterFinishing, |ctx, queue| {
let emit = EventEmitter::new(ctx, queue);
if emit.is_cancelled() {
return emit.status(TaskState::Canceled).await;
}
emit.status(TaskState::Completed).await?;
emit.artifact("late", vec![Part::text("too late")], None, Some(true))
.await
});
#[tokio::test]
async fn emitting_after_a_terminal_state_is_caught() {
only_failure_is(
&check(Arc::new(TalksAfterFinishing)).await,
"nothing_after_terminal",
);
}
executor!(IllegalTransition, |ctx, queue| {
let emit = EventEmitter::new(ctx, queue);
emit.status(TaskState::Completed).await?;
emit.status(TaskState::Working).await
});
#[tokio::test]
async fn an_illegal_transition_is_caught() {
let report = check(Arc::new(IllegalTransition)).await;
assert_eq!(
outcome_of(&report, "transitions_are_legal"),
Outcome::Fail,
"{report}"
);
}
executor!(NamelessArtifact, |ctx, queue| {
let emit = EventEmitter::new(ctx, queue);
if emit.is_cancelled() {
return emit.status(TaskState::Canceled).await;
}
emit.status(TaskState::Working).await?;
if emit.is_cancelled() {
return emit.status(TaskState::Canceled).await;
}
let mut artifact = Artifact::new("placeholder", vec![Part::text("x")]);
artifact.id = "".into();
queue
.write(StreamResponse::ArtifactUpdate(TaskArtifactUpdateEvent {
task_id: ctx.task_id.clone(),
context_id: ContextId::new(ctx.context_id.clone()),
artifact,
append: None,
last_chunk: Some(true),
metadata: None,
}))
.await?;
if emit.is_cancelled() {
return emit.status(TaskState::Canceled).await;
}
emit.status(TaskState::Completed).await
});
#[tokio::test]
async fn an_artifact_with_an_empty_id_is_caught() {
only_failure_is(
&check(Arc::new(NamelessArtifact)).await,
"artifacts_have_ids",
);
}
executor!(ParksButErrors, |ctx, queue| {
let emit = EventEmitter::new(ctx, queue);
if emit.is_cancelled() {
return emit.status(TaskState::Canceled).await;
}
emit.status(TaskState::Working).await?;
if emit.is_cancelled() {
return emit.status(TaskState::Canceled).await;
}
emit.status(TaskState::InputRequired).await?;
Err(A2aError::internal("waiting for the caller"))
});
#[tokio::test]
async fn parking_reported_as_an_error_is_caught() {
only_failure_is(
&check(Arc::new(ParksButErrors)).await,
"parking_is_not_an_error",
);
}
executor!(IgnoresCancellation, |ctx, queue| {
let emit = EventEmitter::new(ctx, queue);
emit.status(TaskState::Working).await?;
emit.status(TaskState::Completed).await
});
#[tokio::test]
async fn ignoring_the_cancellation_token_is_caught() {
only_failures_are(
&check(Arc::new(IgnoresCancellation)).await,
&["honours_cancellation", "stops_when_cancelled_mid_run"],
);
}
executor!(Panics, |_ctx, _queue| {
panic!("conformance harness: deliberate panic under test");
});
#[tokio::test]
async fn a_panicking_executor_is_graded_rather_than_propagated() {
let report = check(Arc::new(Panics)).await;
assert_eq!(
outcome_of(&report, "does_not_panic"),
Outcome::Fail,
"{report}"
);
assert!(!report.is_pass());
}
executor!(Parks, |ctx, queue| {
let emit = EventEmitter::new(ctx, queue);
if emit.is_cancelled() {
return emit.status(TaskState::Canceled).await;
}
emit.status(TaskState::Working).await?;
emit.status(TaskState::InputRequired).await
});
#[tokio::test]
async fn not_applicable_is_never_counted_as_a_pass() {
let report = check(Arc::new(Parks)).await;
assert!(report.is_pass(), "{report}");
assert!(
report.results().len() > report.graded(),
"this executor emits no artifacts, so something must be n/a:\n{report}"
);
assert_eq!(
report.passed() + report.failed(),
report.graded(),
"passed + failed must account for exactly the graded checks"
);
}
#[tokio::test]
async fn a_cancel_that_emits_nothing_is_caught() {
struct SilentCancel;
impl AgentExecutor for SilentCancel {
fn execute<'a>(
&'a self,
ctx: &'a RequestContext,
queue: &'a dyn EventQueueWriter,
) -> std::pin::Pin<Box<dyn Future<Output = A2aResult<()>> + Send + 'a>> {
Box::pin(async move {
let emit = EventEmitter::new(ctx, queue);
if emit.is_cancelled() {
return emit.status(TaskState::Canceled).await;
}
emit.status(TaskState::Completed).await
})
}
fn cancel<'a>(
&'a self,
_ctx: &'a RequestContext,
_queue: &'a dyn EventQueueWriter,
) -> std::pin::Pin<Box<dyn Future<Output = A2aResult<()>> + Send + 'a>> {
Box::pin(async { Ok(()) })
}
}
only_failure_is(
&check(Arc::new(SilentCancel)).await,
"cancel_emits_terminal",
);
}
#[tokio::test]
async fn the_default_cancel_satisfies_the_check() {
assert_eq!(
outcome_of(&check(Arc::new(Good)).await, "cancel_emits_terminal"),
Outcome::Pass
);
}
#[tokio::test]
async fn the_report_renders_every_check_with_its_reason() {
let report = check(Arc::new(Silent)).await;
let rendered = report.to_string();
for r in report.results() {
assert!(rendered.contains(r.name), "{rendered}");
assert!(
rendered.contains(r.detail.as_str()),
"every verdict needs its reason"
);
}
assert!(rendered.contains("graded checks passed"), "{rendered}");
}
#[tokio::test]
#[should_panic(expected = "ends_in_terminal_or_interrupt")]
async fn assert_pass_panics_with_the_grid() {
check(Arc::new(Silent)).await.assert_pass();
}
#[tokio::test]
async fn a_panicking_join_is_a_panic_and_a_cancelled_one_is_not() {
let panicked = tokio::spawn(async { panic!("deliberate") })
.await
.expect_err("a panicking task fails its join");
assert!(panicked.is_panic(), "fixture must actually be a panic");
assert!(super::run::is_panic(&panicked));
let handle = tokio::spawn(std::future::pending::<()>());
handle.abort();
let cancelled = handle.await.expect_err("an aborted task fails its join");
assert!(
cancelled.is_cancelled(),
"fixture must actually be a cancel"
);
assert!(
!super::run::is_panic(&cancelled),
"a cancelled task did not panic, and the harness must not report it as one"
);
}
#[test]
fn a_report_that_graded_nothing_is_not_a_pass() {
let empty = Report {
results: Vec::new(),
};
assert_eq!(empty.graded(), 0);
assert_eq!(empty.failed(), 0, "nothing graded means nothing failed");
assert!(
!empty.is_pass(),
"a run that measured nothing must report failure, not full marks"
);
let all_not_applicable = Report {
results: vec![
CheckResult::skip("first", "never applied"),
CheckResult::skip("second", "never applied either"),
],
};
assert_eq!(
all_not_applicable.graded(),
0,
"not-applicable checks are excluded from the graded count"
);
assert!(
!all_not_applicable.is_pass(),
"two checks that never ran are still nothing measured:\n{all_not_applicable}"
);
}