pub mod agent_report;
pub mod compact;
pub mod concise;
pub mod curl;
pub mod diff;
pub mod event_stream;
pub mod failure;
pub mod failures_command;
pub mod fixture_writer;
pub mod html;
pub mod human;
pub mod inspect;
pub mod json;
pub mod json_parse;
pub mod junit;
pub mod llm;
pub mod pack_context;
pub mod progress;
pub mod redaction;
pub mod rerun;
pub mod run_dir;
pub mod shape_diagnosis;
pub mod state_writer;
pub mod summary;
pub mod tap;
use crate::assert::types::{FailureCategory, RunResult, StepResult, TestResult};
use std::path::PathBuf;
use std::str::FromStr;
pub fn compute_exit_code(run_result: &RunResult) -> i32 {
let mut exit_code = if run_result.passed() { 0 } else { 1 };
for step in all_steps(run_result) {
match step.error_category {
Some(FailureCategory::ConnectionError)
| Some(FailureCategory::Timeout)
| Some(FailureCategory::CaptureError) => return 3,
Some(FailureCategory::ParseError) | Some(FailureCategory::UnresolvedTemplate) => {
exit_code = exit_code.max(2)
}
Some(FailureCategory::SkippedDueToFailedCapture)
| Some(FailureCategory::SkippedDueToFailFast)
| Some(FailureCategory::SkippedByCondition)
| Some(FailureCategory::AssertionFailed)
| Some(FailureCategory::ResponseShapeMismatch)
| None => {}
}
}
exit_code
}
fn all_steps(run_result: &RunResult) -> impl Iterator<Item = &StepResult> {
run_result.file_results.iter().flat_map(|file| {
file.setup_results
.iter()
.chain(file.test_results.iter().flat_map(steps_from_test))
.chain(file.teardown_results.iter())
})
}
fn steps_from_test(test: &TestResult) -> impl Iterator<Item = &StepResult> {
test.step_results.iter()
}
#[derive(Debug, Clone, Copy, Default)]
pub struct RenderOptions {
pub only_failed: bool,
pub verbose: bool,
pub no_color: bool,
pub verbose_responses: bool,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum OutputFormat {
Human,
Json,
Junit,
Tap,
Html,
Curl,
CurlAll,
Compact,
Llm,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct OutputTarget {
pub format: OutputFormat,
pub path: Option<PathBuf>,
}
impl FromStr for OutputFormat {
type Err = String;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s.to_lowercase().as_str() {
"human" => Ok(OutputFormat::Human),
"json" => Ok(OutputFormat::Json),
"junit" => Ok(OutputFormat::Junit),
"tap" => Ok(OutputFormat::Tap),
"html" => Ok(OutputFormat::Html),
"curl" => Ok(OutputFormat::Curl),
"curl-all" => Ok(OutputFormat::CurlAll),
"compact" => Ok(OutputFormat::Compact),
"llm" => Ok(OutputFormat::Llm),
other => Err(format!("Unknown output format: '{}'", other)),
}
}
}
impl FromStr for OutputTarget {
type Err = String;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let (format_raw, path) = match s.split_once('=') {
Some((format, path)) => (format, Some(PathBuf::from(path))),
None => (s, None),
};
let format = format_raw.parse::<OutputFormat>()?;
Ok(Self { format, path })
}
}
impl OutputTarget {
pub fn writes_to_stdout(&self) -> bool {
self.path.is_none() && self.format != OutputFormat::Html
}
}
pub fn render(result: &RunResult, format: OutputFormat) -> String {
render_with_options(result, format, RenderOptions::default())
}
pub fn render_with_options(
result: &RunResult,
format: OutputFormat,
opts: RenderOptions,
) -> String {
match format {
OutputFormat::Human => human::render_with_options(result, opts),
OutputFormat::Json => {
json::render_with_options(result, json::JsonOutputMode::Verbose, opts)
}
OutputFormat::Junit => junit::render(result),
OutputFormat::Tap => tap::render(result),
OutputFormat::Html => html::render(result),
OutputFormat::Curl => curl::render_failures(result),
OutputFormat::CurlAll => curl::render_all(result),
OutputFormat::Compact => compact::render_with_options(result, opts),
OutputFormat::Llm => llm::render_with_options(result, opts),
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn output_format_from_str() {
assert_eq!("human".parse::<OutputFormat>(), Ok(OutputFormat::Human));
assert_eq!("json".parse::<OutputFormat>(), Ok(OutputFormat::Json));
assert_eq!("junit".parse::<OutputFormat>(), Ok(OutputFormat::Junit));
assert_eq!("tap".parse::<OutputFormat>(), Ok(OutputFormat::Tap));
assert_eq!("html".parse::<OutputFormat>(), Ok(OutputFormat::Html));
assert_eq!("curl".parse::<OutputFormat>(), Ok(OutputFormat::Curl));
assert_eq!(
"curl-all".parse::<OutputFormat>(),
Ok(OutputFormat::CurlAll)
);
assert_eq!("JSON".parse::<OutputFormat>(), Ok(OutputFormat::Json));
assert_eq!("HTML".parse::<OutputFormat>(), Ok(OutputFormat::Html));
assert_eq!("compact".parse::<OutputFormat>(), Ok(OutputFormat::Compact));
assert_eq!("llm".parse::<OutputFormat>(), Ok(OutputFormat::Llm));
assert_eq!("LLM".parse::<OutputFormat>(), Ok(OutputFormat::Llm));
assert!("unknown".parse::<OutputFormat>().is_err());
}
#[test]
fn output_target_from_format_only() {
assert_eq!(
"json".parse::<OutputTarget>(),
Ok(OutputTarget {
format: OutputFormat::Json,
path: None,
})
);
}
#[test]
fn output_target_from_format_and_path() {
assert_eq!(
"junit=reports/junit.xml".parse::<OutputTarget>(),
Ok(OutputTarget {
format: OutputFormat::Junit,
path: Some(PathBuf::from("reports/junit.xml")),
})
);
}
#[test]
fn html_without_path_does_not_write_to_stdout() {
let target = "html".parse::<OutputTarget>().unwrap();
assert!(!target.writes_to_stdout());
}
}