xacli_testing/assert/
mod.rs1mod code;
6mod helpers;
7mod output;
8mod snapshot;
9
10pub use code::{code, failure, success};
11pub use output::{stderr, stdout};
12pub use snapshot::SnapshotManager;
13
14use crate::{ExecuteResult, Result, TestingError};
15
16pub trait Asserter {
17 fn validate(&self, result: &ExecuteResult) -> Result<AssertResult>;
18}
19
20type ValidateFn = Box<dyn Fn(&ExecuteResult) -> Result<()>>;
22
23pub struct ExecuteAsserter {
25 pub name: String,
26 pub title: String,
27 validate: ValidateFn,
28}
29
30impl ExecuteAsserter {
31 pub fn new(name: impl Into<String>, title: impl Into<String>, validate: ValidateFn) -> Self {
33 Self {
34 name: name.into(),
35 title: title.into(),
36 validate,
37 }
38 }
39}
40
41impl Asserter for ExecuteAsserter {
42 fn validate(&self, result: &ExecuteResult) -> Result<AssertResult> {
43 match (self.validate)(result) {
44 Ok(()) => Ok(AssertResult {
45 name: self.name.clone(),
46 title: self.title.clone(),
47 success: true,
48 messages: vec![],
49 }),
50 Err(TestingError::Assertion(msg)) => Ok(AssertResult {
51 name: self.name.clone(),
52 title: self.title.clone(),
53 success: false,
54 messages: vec![msg],
55 }),
56 Err(e) => Ok(AssertResult {
57 name: self.name.clone(),
58 title: self.title.clone(),
59 success: false,
60 messages: vec![e.to_string()],
61 }),
62 }
63 }
64}
65
66#[derive(Debug, Clone)]
68pub struct AssertResult {
69 pub name: String,
70 pub title: String,
71 pub success: bool,
72 pub messages: Vec<String>,
73}
74
75pub fn and(asserters: Vec<Box<dyn Asserter>>) -> Box<dyn Asserter> {
77 Box::new(ExecuteAsserter::new(
78 "and",
79 "All assertions must pass",
80 Box::new(move |result| {
81 for asserter in &asserters {
82 let res = asserter.validate(result)?;
83 if !res.success {
84 return Err(TestingError::Assertion(res.messages.join("; ")));
85 }
86 }
87 Ok(())
88 }),
89 ))
90}
91
92pub fn or(asserters: Vec<Box<dyn Asserter>>) -> Box<dyn Asserter> {
94 Box::new(ExecuteAsserter::new(
95 "or",
96 "At least one assertion must pass",
97 Box::new(move |result| {
98 let mut errors = vec![];
99 for asserter in &asserters {
100 let res = asserter.validate(result)?;
101 if res.success {
102 return Ok(());
103 }
104 errors.extend(res.messages);
105 }
106 Err(TestingError::Assertion(format!(
107 "None of the assertions passed: {}",
108 errors.join("; ")
109 )))
110 }),
111 ))
112}
113
114pub fn snapshot(name: &str) -> Box<dyn Asserter> {
116 let name = name.to_string();
117 let title = format!("Snapshot: {}", name);
118 let snapshot_name = name.clone();
119
120 Box::new(ExecuteAsserter::new(
121 name,
122 title,
123 Box::new(move |result| {
124 let manager = SnapshotManager::new();
125 let actual = format!("stdout:\n{}\nstderr:\n{}", result.stdout, result.stderr);
126
127 match manager.compare_or_create(&snapshot_name, &actual) {
128 Ok(true) => Ok(()),
129 Ok(false) => {
130 let diff = manager
131 .diff(&snapshot_name, &actual)
132 .ok()
133 .flatten()
134 .unwrap_or_else(|| "Snapshot mismatch".to_string());
135 Err(TestingError::Assertion(diff))
136 }
137 Err(e) => Err(e),
138 }
139 }),
140 ))
141}