1use crate::{
2 AuditEntry, ClearReason, CollectionDiffTrace, InvariantResultTrace, OutputFrameKind, OutputKey,
3 RebaselineReason, ResourceCommand, ResourceKey, ScopeId, ScopeLifecycleTrace,
4 StagedInputChange, TransactionId, TransactionPhase, TransactionResult,
5};
6use crate::{NodeId, Revision};
7
8#[derive(Clone, Debug, Eq, PartialEq)]
10#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
11pub struct TransactionTrace {
12 pub transaction_id: TransactionId,
14 pub revision: Revision,
16 pub staged_input_changes: Vec<StagedInputChange>,
18 pub changed_inputs: Vec<NodeId>,
20 pub dirty_roots: Vec<NodeId>,
22 pub recomputed_derived_nodes: Vec<NodeId>,
24 pub changed_derived_nodes: Vec<NodeId>,
26 pub recomputed_collection_nodes: Vec<NodeId>,
28 pub changed_collection_nodes: Vec<NodeId>,
30 pub collection_diffs: Vec<CollectionDiffTrace>,
32 pub resource_commands: Vec<ResourceCommandTrace>,
34 pub output_frames: Vec<OutputFrameTrace>,
36 pub scope_events: Vec<ScopeLifecycleTrace>,
38 pub audit_log: Vec<AuditEntry>,
40 pub phase_trace: Vec<TransactionPhase>,
42 pub invariant_results: Vec<InvariantResultTrace>,
44}
45
46impl TransactionTrace {
47 pub fn from_result<C, O>(result: &TransactionResult<C, O>) -> Self {
49 Self {
50 transaction_id: result.transaction_id,
51 revision: result.revision,
52 staged_input_changes: result.staged_input_changes.clone(),
53 changed_inputs: result.changed_inputs.clone(),
54 dirty_roots: result.dirty_roots.clone(),
55 recomputed_derived_nodes: result.recomputed_derived_nodes.clone(),
56 changed_derived_nodes: result.changed_derived_nodes.clone(),
57 recomputed_collection_nodes: result.recomputed_collection_nodes.clone(),
58 changed_collection_nodes: result.changed_collection_nodes.clone(),
59 collection_diffs: result.collection_diffs.clone(),
60 resource_commands: result
61 .resource_plan
62 .commands()
63 .iter()
64 .map(ResourceCommandTrace::from_command)
65 .collect(),
66 output_frames: result
67 .output_frames
68 .iter()
69 .map(|frame| OutputFrameTrace {
70 output_key: frame.output_key,
71 scope: frame.scope,
72 transaction_id: frame.transaction_id,
73 revision: frame.revision,
74 kind: OutputFrameKindTrace::from_kind(&frame.kind),
75 })
76 .collect(),
77 scope_events: result.scope_events.clone(),
78 audit_log: result.audit_log.clone(),
79 phase_trace: result.phase_trace.clone(),
80 invariant_results: result.invariant_results.clone(),
81 }
82 }
83}
84
85#[derive(Clone, Debug, Eq, PartialEq)]
87#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
88pub struct ResourceCommandTrace {
89 pub key: ResourceKey,
91 pub scope: ScopeId,
93 pub kind: ResourceCommandKind,
95 pub transition: ResourceTransitionPolicy,
97}
98
99impl ResourceCommandTrace {
100 fn from_command<C>(command: &ResourceCommand<C>) -> Self {
101 Self {
102 key: command.key().clone(),
103 scope: command.scope(),
104 kind: ResourceCommandKind::from_command(command),
105 transition: ResourceTransitionPolicy::from_command(command),
106 }
107 }
108}
109
110#[derive(Copy, Clone, Debug, Eq, PartialEq)]
112#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
113pub enum ResourceCommandKind {
114 Open,
116 Close,
118 Replace,
120 Refresh,
122}
123
124impl ResourceCommandKind {
125 pub(crate) fn from_command<C>(command: &ResourceCommand<C>) -> Self {
126 match command {
127 ResourceCommand::Open { .. } => Self::Open,
128 ResourceCommand::Close { .. } => Self::Close,
129 ResourceCommand::Replace { .. } => Self::Replace,
130 ResourceCommand::Refresh { .. } => Self::Refresh,
131 }
132 }
133}
134
135#[derive(Copy, Clone, Debug, Eq, PartialEq)]
137#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
138pub enum ResourceTransitionPolicy {
139 Open,
141 Close,
143 ReplaceAtomically,
145 Refresh,
147 Noop,
149}
150
151impl ResourceTransitionPolicy {
152 pub(crate) fn from_command<C>(command: &ResourceCommand<C>) -> Self {
153 match command {
154 ResourceCommand::Open { .. } => Self::Open,
155 ResourceCommand::Close { .. } => Self::Close,
156 ResourceCommand::Replace { .. } => Self::ReplaceAtomically,
157 ResourceCommand::Refresh { .. } => Self::Refresh,
158 }
159 }
160}
161
162#[derive(Clone, Debug, Eq, PartialEq)]
164#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
165pub struct OutputFrameTrace {
166 pub output_key: OutputKey,
168 pub scope: ScopeId,
170 pub transaction_id: TransactionId,
172 pub revision: Revision,
174 pub kind: OutputFrameKindTrace,
176}
177
178#[derive(Copy, Clone, Debug, Eq, PartialEq)]
180#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
181pub enum OutputFrameKindTrace {
182 Baseline,
184 Delta,
186 Clear(ClearReason),
188 Rebaseline(RebaselineReason),
190}
191
192impl OutputFrameKindTrace {
193 pub(crate) fn from_kind<O>(kind: &OutputFrameKind<O>) -> Self {
194 match kind {
195 OutputFrameKind::Baseline(_) => Self::Baseline,
196 OutputFrameKind::Delta(_) => Self::Delta,
197 OutputFrameKind::Clear(reason) => Self::Clear(*reason),
198 OutputFrameKind::Rebaseline(_, reason) => Self::Rebaseline(*reason),
199 }
200 }
201}
202
203impl<C, O> TransactionResult<C, O> {
204 pub fn trace(&self) -> TransactionTrace {
206 TransactionTrace::from_result(self)
207 }
208}
209
210#[derive(Clone, Debug, Eq, PartialEq)]
212#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
213pub struct TraceMismatch {
214 pub expected: Vec<TransactionTrace>,
216 pub actual: Vec<TransactionTrace>,
218}
219
220pub fn assert_transaction_traces_match(
222 expected: &[TransactionTrace],
223 actual: &[TransactionTrace],
224) -> Result<(), TraceMismatch> {
225 if expected == actual {
226 Ok(())
227 } else {
228 Err(TraceMismatch {
229 expected: expected.to_vec(),
230 actual: actual.to_vec(),
231 })
232 }
233}