• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

facet-rs / facet / 20129349901

11 Dec 2025 10:06AM UTC coverage: 57.865% (-0.08%) from 57.942%
20129349901

push

github

fasterthanlime
Setup hooks via conductor and fix clippy

18 of 50 new or added lines in 3 files covered. (36.0%)

9 existing lines in 1 file now uncovered.

28845 of 49849 relevant lines covered (57.86%)

6311.59 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

69.52
/facet-assert/src/same.rs
1
//! Structural sameness checking for Facet types.
2

3
use facet_core::Facet;
4
use facet_diff::{Diff, DiffOptions, diff_new_peek_with_options};
5
use facet_diff_core::layout::{
6
    AnsiBackend, BuildOptions, ColorBackend, DiffFlavor, JsonFlavor, RenderOptions, RustFlavor,
7
    XmlFlavor, build_layout, render_to_string,
8
};
9
use facet_reflect::Peek;
10

11
/// Options for customizing structural comparison behavior.
12
///
13
/// Use the builder pattern to configure options:
14
///
15
/// ```
16
/// use facet_assert::SameOptions;
17
///
18
/// let options = SameOptions::new()
19
///     .float_tolerance(1e-6);
20
/// ```
21
#[derive(Debug, Clone, Default)]
22
pub struct SameOptions {
23
    /// Tolerance for floating-point comparisons.
24
    /// If set, two floats are considered equal if their absolute difference
25
    /// is less than or equal to this value.
26
    float_tolerance: Option<f64>,
27
}
28

29
impl SameOptions {
30
    /// Create a new `SameOptions` with default settings (exact comparison).
31
    pub fn new() -> Self {
10✔
32
        Self::default()
10✔
33
    }
10✔
34

35
    /// Set the tolerance for floating-point comparisons.
36
    ///
37
    /// When set, two `f32` or `f64` values are considered equal if:
38
    /// `|left - right| <= tolerance`
39
    ///
40
    /// # Example
41
    ///
42
    /// ```
43
    /// use facet_assert::{assert_same_with, SameOptions};
44
    ///
45
    /// let a = 1.0000001_f64;
46
    /// let b = 1.0000002_f64;
47
    ///
48
    /// // This would fail with exact comparison:
49
    /// // assert_same!(a, b);
50
    ///
51
    /// // But passes with tolerance:
52
    /// assert_same_with!(a, b, SameOptions::new().float_tolerance(1e-6));
53
    /// ```
54
    pub fn float_tolerance(mut self, tolerance: f64) -> Self {
10✔
55
        self.float_tolerance = Some(tolerance);
10✔
56
        self
10✔
57
    }
10✔
58
}
59

60
/// Result of checking if two values are structurally the same.
61
pub enum Sameness {
62
    /// The values are structurally the same.
63
    Same,
64
    /// The values differ - contains a formatted diff.
65
    Different(String),
66
    /// Encountered an opaque type that cannot be compared.
67
    Opaque {
68
        /// The type name of the opaque type.
69
        type_name: &'static str,
70
    },
71
}
72

73
/// Detailed comparison result that retains the computed diff.
74
pub enum SameReport<'mem, 'facet> {
75
    /// The values are structurally the same.
76
    Same,
77
    /// The values differ - includes a diff report that can be rendered in multiple formats.
78
    Different(Box<DiffReport<'mem, 'facet>>),
79
    /// Encountered an opaque type that cannot be compared.
80
    Opaque {
81
        /// The type name of the opaque type.
82
        type_name: &'static str,
83
    },
84
}
85

86
impl<'mem, 'facet> SameReport<'mem, 'facet> {
87
    /// Returns `true` if the two values matched.
88
    pub fn is_same(&self) -> bool {
×
89
        matches!(self, Self::Same)
×
90
    }
×
91

92
    /// Convert this report into a [`Sameness`] summary, formatting diffs using the legacy display.
93
    pub fn into_sameness(self) -> Sameness {
43✔
94
        match self {
43✔
95
            SameReport::Same => Sameness::Same,
30✔
96
            SameReport::Different(report) => Sameness::Different(report.legacy_string()),
13✔
97
            SameReport::Opaque { type_name } => Sameness::Opaque { type_name },
×
98
        }
99
    }
43✔
100

101
    /// Get the diff report if the values were different.
102
    pub fn diff(&self) -> Option<&DiffReport<'mem, 'facet>> {
×
103
        match self {
×
NEW
104
            SameReport::Different(report) => Some(report.as_ref()),
×
105
            _ => None,
×
106
        }
107
    }
×
108
}
109

110
/// A reusable diff plus its original inputs, allowing rendering in different output styles.
111
pub struct DiffReport<'mem, 'facet> {
112
    diff: Diff<'mem, 'facet>,
113
    left: Peek<'mem, 'facet>,
114
    right: Peek<'mem, 'facet>,
115
}
116

117
impl<'mem, 'facet> DiffReport<'mem, 'facet> {
118
    /// Access the raw diff tree.
119
    pub fn diff(&self) -> &Diff<'mem, 'facet> {
×
120
        &self.diff
×
121
    }
×
122

123
    /// Peek into the left-hand value.
124
    pub fn left(&self) -> Peek<'mem, 'facet> {
×
125
        self.left
×
126
    }
×
127

128
    /// Peek into the right-hand value.
129
    pub fn right(&self) -> Peek<'mem, 'facet> {
×
130
        self.right
×
131
    }
×
132

133
    /// Format the diff using the legacy tree display (same output as before).
134
    pub fn legacy_string(&self) -> String {
13✔
135
        format!("{}", self.diff)
13✔
136
    }
13✔
137

138
    /// Render the diff with a custom flavor and render/build options.
139
    pub fn render_with_options<B: ColorBackend, F: DiffFlavor>(
3✔
140
        &self,
3✔
141
        flavor: &F,
3✔
142
        build_opts: &BuildOptions,
3✔
143
        render_opts: &RenderOptions<B>,
3✔
144
    ) -> String {
3✔
145
        let layout = build_layout(&self.diff, self.left, self.right, build_opts, flavor);
3✔
146
        render_to_string(&layout, render_opts, flavor)
3✔
147
    }
3✔
148

149
    /// Render using ANSI colors with the provided flavor.
150
    pub fn render_ansi_with<F: DiffFlavor>(&self, flavor: &F) -> String {
×
151
        let build_opts = BuildOptions::default();
×
152
        let render_opts = RenderOptions::<AnsiBackend>::default();
×
153
        self.render_with_options(flavor, &build_opts, &render_opts)
×
154
    }
×
155

156
    /// Render without colors using the provided flavor.
157
    pub fn render_plain_with<F: DiffFlavor>(&self, flavor: &F) -> String {
3✔
158
        let build_opts = BuildOptions::default();
3✔
159
        let render_opts = RenderOptions::plain();
3✔
160
        self.render_with_options(flavor, &build_opts, &render_opts)
3✔
161
    }
3✔
162

163
    /// Render using the Rust flavor with ANSI colors.
164
    pub fn render_ansi_rust(&self) -> String {
×
165
        self.render_ansi_with(&RustFlavor)
×
166
    }
×
167

168
    /// Render using the Rust flavor without colors.
169
    pub fn render_plain_rust(&self) -> String {
1✔
170
        self.render_plain_with(&RustFlavor)
1✔
171
    }
1✔
172

173
    /// Render using the JSON flavor with ANSI colors.
174
    pub fn render_ansi_json(&self) -> String {
×
175
        self.render_ansi_with(&JsonFlavor)
×
176
    }
×
177

178
    /// Render using the JSON flavor without colors.
179
    pub fn render_plain_json(&self) -> String {
1✔
180
        self.render_plain_with(&JsonFlavor)
1✔
181
    }
1✔
182

183
    /// Render using the XML flavor with ANSI colors.
184
    pub fn render_ansi_xml(&self) -> String {
×
185
        self.render_ansi_with(&XmlFlavor)
×
186
    }
×
187

188
    /// Render using the XML flavor without colors.
189
    pub fn render_plain_xml(&self) -> String {
1✔
190
        self.render_plain_with(&XmlFlavor)
1✔
191
    }
1✔
192
}
193

194
/// Check if two Facet values are structurally the same.
195
///
196
/// This does NOT require `PartialEq` - it walks the structure via reflection.
197
/// Two values are "same" if they have the same structure and values, even if
198
/// they have different type names.
199
///
200
/// Returns [`Sameness::Opaque`] if either value contains an opaque type.
201
pub fn check_same<'f, T: Facet<'f>, U: Facet<'f>>(left: &T, right: &U) -> Sameness {
33✔
202
    check_same_report(left, right).into_sameness()
33✔
203
}
33✔
204

205
/// Check if two Facet values are structurally the same, returning a detailed report.
206
pub fn check_same_report<'f, 'mem, T: Facet<'f>, U: Facet<'f>>(
34✔
207
    left: &'mem T,
34✔
208
    right: &'mem U,
34✔
209
) -> SameReport<'mem, 'f> {
34✔
210
    check_same_with_report(left, right, SameOptions::default())
34✔
211
}
34✔
212

213
/// Check if two Facet values are structurally the same, with custom options.
214
///
215
/// Like [`check_same`], but allows configuring comparison behavior via [`SameOptions`].
216
///
217
/// # Example
218
///
219
/// ```
220
/// use facet_assert::{check_same_with, SameOptions, Sameness};
221
///
222
/// let a = 1.0000001_f64;
223
/// let b = 1.0000002_f64;
224
///
225
/// // With tolerance, these are considered the same
226
/// let options = SameOptions::new().float_tolerance(1e-6);
227
/// assert!(matches!(check_same_with(&a, &b, options), Sameness::Same));
228
/// ```
229
pub fn check_same_with<'f, T: Facet<'f>, U: Facet<'f>>(
6✔
230
    left: &T,
6✔
231
    right: &U,
6✔
232
    options: SameOptions,
6✔
233
) -> Sameness {
6✔
234
    check_same_with_report(left, right, options).into_sameness()
6✔
235
}
6✔
236

237
/// Detailed comparison with custom options.
238
pub fn check_same_with_report<'f, 'mem, T: Facet<'f>, U: Facet<'f>>(
40✔
239
    left: &'mem T,
40✔
240
    right: &'mem U,
40✔
241
    options: SameOptions,
40✔
242
) -> SameReport<'mem, 'f> {
40✔
243
    let left_peek = Peek::new(left);
40✔
244
    let right_peek = Peek::new(right);
40✔
245

246
    // Convert SameOptions to DiffOptions
247
    let diff_options = if let Some(tol) = options.float_tolerance {
40✔
248
        DiffOptions::new().with_float_tolerance(tol)
6✔
249
    } else {
250
        DiffOptions::new()
34✔
251
    };
252

253
    // Compute diff with options applied during computation
254
    let diff = diff_new_peek_with_options(left_peek, right_peek, &diff_options);
40✔
255

256
    if diff.is_equal() {
40✔
257
        SameReport::Same
26✔
258
    } else {
259
        SameReport::Different(Box::new(DiffReport {
14✔
260
            diff,
14✔
261
            left: left_peek,
14✔
262
            right: right_peek,
14✔
263
        }))
14✔
264
    }
265
}
40✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc