jellyflow_runtime/runtime/conformance/
approval.rs1use std::path::PathBuf;
2
3use serde::{Deserialize, Serialize};
4
5use super::fixtures::{
6 ConformanceFixtureDirectory, ConformanceFixtureFileError, ConformanceSuiteFile,
7};
8use super::reports::{ConformanceRunError, ConformanceTraceMismatch};
9use super::runner::run_conformance_scenario;
10use super::scenario::ConformanceSuite;
11
12impl ConformanceSuite {
13 pub fn approve_actual_traces(&self) -> ConformanceSuiteApproval {
14 let mut approved = self.clone();
15 let mut scenario_reports = Vec::new();
16 let mut errors = Vec::new();
17
18 for scenario in &mut approved.scenarios {
19 let compiled = scenario.compiled();
20 match run_conformance_scenario(scenario) {
21 Ok(report) => {
22 let expected_event_count = compiled.expected_event_count();
23 let actual_event_count = report.actual_trace.len();
24 let approved_expected_trace = compiled
25 .approval_expected_trace(&scenario.expected_trace, &report.actual_trace);
26 let changed = scenario.expected_trace != approved_expected_trace;
27 scenario.expected_trace = approved_expected_trace;
28 scenario_reports.push(ConformanceScenarioApprovalReport {
29 scenario: scenario.name.clone(),
30 changed,
31 expected_event_count,
32 actual_event_count,
33 mismatches: report.mismatches,
34 });
35 }
36 Err(error) => errors.push(error),
37 }
38 }
39
40 ConformanceSuiteApproval {
41 suite: approved,
42 report: ConformanceSuiteApprovalReport {
43 suite: self.name.clone(),
44 scenario_reports,
45 errors,
46 },
47 }
48 }
49}
50
51impl ConformanceFixtureDirectory {
52 pub fn approve_actual_traces_to_json(
53 &self,
54 ) -> Result<ConformanceFixtureDirectoryApprovalReport, ConformanceFixtureFileError> {
55 let mut approvals = Vec::new();
56 for file in &self.files {
57 let approval = file.suite.approve_actual_traces();
58 if !approval.is_approvable() {
59 return Err(ConformanceFixtureFileError::Approve {
60 path: file.path.display().to_string(),
61 source: ConformanceApprovalError::from_report(approval.report),
62 });
63 }
64 approvals.push((file.path.clone(), approval));
65 }
66
67 for (path, approval) in &approvals {
68 approval.suite.save_json(path)?;
69 }
70
71 Ok(ConformanceFixtureDirectoryApprovalReport {
72 root: self.root.clone(),
73 reports: approvals
74 .into_iter()
75 .map(|(path, approval)| ConformanceSuiteFileApprovalReport {
76 path,
77 report: approval.report,
78 })
79 .collect(),
80 })
81 }
82}
83
84impl ConformanceSuiteFile {
85 pub fn approve_actual_traces_to_json(
86 &self,
87 ) -> Result<ConformanceSuiteApproval, ConformanceFixtureFileError> {
88 let approval = self.suite.approve_actual_traces();
89 if !approval.is_approvable() {
90 return Err(ConformanceFixtureFileError::Approve {
91 path: self.path.display().to_string(),
92 source: ConformanceApprovalError::from_report(approval.report),
93 });
94 }
95 approval.suite.save_json(&self.path)?;
96 Ok(approval)
97 }
98}
99
100#[derive(Debug, Clone, Serialize, Deserialize)]
101pub struct ConformanceSuiteApproval {
102 pub suite: ConformanceSuite,
103 pub report: ConformanceSuiteApprovalReport,
104}
105
106impl ConformanceSuiteApproval {
107 pub fn is_approvable(&self) -> bool {
108 self.report.is_approvable()
109 }
110
111 pub fn has_changes(&self) -> bool {
112 self.report.has_changes()
113 }
114
115 pub fn changed_scenarios(&self) -> usize {
116 self.report.changed_scenarios()
117 }
118
119 pub fn error_count(&self) -> usize {
120 self.report.error_count()
121 }
122}
123
124#[derive(Debug, Clone, Serialize, Deserialize)]
125pub struct ConformanceSuiteApprovalReport {
126 pub suite: String,
127 pub scenario_reports: Vec<ConformanceScenarioApprovalReport>,
128 #[serde(default, skip_serializing_if = "Vec::is_empty")]
129 pub errors: Vec<ConformanceRunError>,
130}
131
132impl ConformanceSuiteApprovalReport {
133 pub fn is_approvable(&self) -> bool {
134 self.errors.is_empty()
135 }
136
137 pub fn has_changes(&self) -> bool {
138 self.scenario_reports.iter().any(|report| report.changed)
139 }
140
141 pub fn changed_scenarios(&self) -> usize {
142 self.scenario_reports
143 .iter()
144 .filter(|report| report.changed)
145 .count()
146 }
147
148 pub fn error_count(&self) -> usize {
149 self.errors.len()
150 }
151}
152
153#[derive(Debug, Clone, Serialize, Deserialize)]
154pub struct ConformanceScenarioApprovalReport {
155 pub scenario: String,
156 pub changed: bool,
157 pub expected_event_count: usize,
158 pub actual_event_count: usize,
159 #[serde(default, skip_serializing_if = "Vec::is_empty")]
160 pub mismatches: Vec<ConformanceTraceMismatch>,
161}
162
163#[derive(Debug, Clone, Serialize, Deserialize)]
164pub struct ConformanceSuiteFileApprovalReport {
165 pub path: PathBuf,
166 pub report: ConformanceSuiteApprovalReport,
167}
168
169#[derive(Debug, Clone, Serialize, Deserialize)]
170pub struct ConformanceFixtureDirectoryApprovalReport {
171 pub root: PathBuf,
172 pub reports: Vec<ConformanceSuiteFileApprovalReport>,
173}
174
175impl ConformanceFixtureDirectoryApprovalReport {
176 pub fn is_approvable(&self) -> bool {
177 self.reports
178 .iter()
179 .all(|report| report.report.is_approvable())
180 }
181
182 pub fn has_changes(&self) -> bool {
183 self.reports
184 .iter()
185 .any(|report| report.report.has_changes())
186 }
187
188 pub fn file_count(&self) -> usize {
189 self.reports.len()
190 }
191
192 pub fn changed_files(&self) -> usize {
193 self.reports
194 .iter()
195 .filter(|report| report.report.has_changes())
196 .count()
197 }
198
199 pub fn changed_scenarios(&self) -> usize {
200 self.reports
201 .iter()
202 .map(|report| report.report.changed_scenarios())
203 .sum()
204 }
205
206 pub fn error_count(&self) -> usize {
207 self.reports
208 .iter()
209 .map(|report| report.report.error_count())
210 .sum()
211 }
212}
213
214#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, thiserror::Error)]
215#[error("failed to approve conformance suite `{suite}` because {error_count} scenario(s) errored")]
216pub struct ConformanceApprovalError {
217 pub suite: String,
218 pub error_count: usize,
219 pub errors: Vec<ConformanceRunError>,
220}
221
222impl ConformanceApprovalError {
223 pub fn from_report(report: ConformanceSuiteApprovalReport) -> Self {
224 Self {
225 suite: report.suite,
226 error_count: report.errors.len(),
227 errors: report.errors,
228 }
229 }
230}