Skip to main content

xacli_testing/assert/
snapshot.rs

1//! Snapshot management for testing
2//!
3//! Provides snapshot comparison and creation for test outputs.
4
5use std::{env, fs, path::PathBuf};
6
7use crate::TestingError;
8
9/// Snapshot manager for comparing test outputs against saved snapshots
10pub struct SnapshotManager {
11    /// Snapshot storage directory
12    snapshot_dir: PathBuf,
13    /// Whether to update snapshots (UPDATE_SNAPSHOTS=1)
14    update_mode: bool,
15}
16
17impl SnapshotManager {
18    /// Create a new snapshot manager
19    pub fn new() -> Self {
20        // Default to snapshots/ in the project root
21        let snapshot_dir = env::var("CARGO_MANIFEST_DIR")
22            .map(|p| PathBuf::from(p).join("snapshots"))
23            .unwrap_or_else(|_| PathBuf::from("snapshots"));
24
25        let update_mode = env::var("UPDATE_SNAPSHOTS")
26            .map(|v| v == "1" || v.to_lowercase() == "true")
27            .unwrap_or(false);
28
29        Self {
30            snapshot_dir,
31            update_mode,
32        }
33    }
34
35    /// Set custom snapshot directory
36    pub fn with_dir(mut self, dir: impl Into<PathBuf>) -> Self {
37        self.snapshot_dir = dir.into();
38        self
39    }
40
41    /// Compare actual output against snapshot, or create new snapshot
42    ///
43    /// Returns:
44    /// - `Ok(true)` if snapshot matches or was created
45    /// - `Ok(false)` if snapshot doesn't match
46    /// - `Err(_)` on IO errors
47    pub fn compare_or_create(&self, name: &str, actual: &str) -> Result<bool, TestingError> {
48        let snapshot_path = self.snapshot_dir.join(name);
49
50        // Ensure directory exists
51        if let Some(parent) = snapshot_path.parent() {
52            fs::create_dir_all(parent)?;
53        }
54
55        if snapshot_path.exists() {
56            let expected = fs::read_to_string(&snapshot_path)?;
57
58            if expected == actual {
59                Ok(true)
60            } else if self.update_mode {
61                // Update mode: overwrite snapshot
62                fs::write(&snapshot_path, actual)?;
63                Ok(true)
64            } else {
65                // Mismatch
66                Ok(false)
67            }
68        } else {
69            // Create new snapshot
70            fs::write(&snapshot_path, actual)?;
71            Ok(true)
72        }
73    }
74
75    /// Get diff between snapshot and actual output
76    pub fn diff(&self, name: &str, actual: &str) -> Result<Option<String>, TestingError> {
77        let snapshot_path = self.snapshot_dir.join(name);
78
79        if !snapshot_path.exists() {
80            return Ok(Some(format!("Snapshot '{}' does not exist", name)));
81        }
82
83        let expected = fs::read_to_string(&snapshot_path)?;
84
85        if expected == actual {
86            Ok(None)
87        } else {
88            Ok(Some(format!(
89                "Snapshot mismatch:\n--- Expected ---\n{}\n--- Actual ---\n{}",
90                expected, actual
91            )))
92        }
93    }
94}
95
96impl Default for SnapshotManager {
97    fn default() -> Self {
98        Self::new()
99    }
100}