Skip to main content

assertables/assert_bag/
assert_bag_superbag.rs

1//! Assert a bag is a superbag of another.
2//!
3//! Pseudocode:<br>
4//! (a_collection ⇒ a_bag) ⊃ (b_collection ⇒ b_bag)
5//!
6//! # Example
7//!
8//! ```rust
9//! use assertables::*;
10//!
11//! let a = [1, 1, 1];
12//! let b = [1, 1];
13//! assert_bag_superbag!(a, b);
14//! ```
15//!
16//! This implementation uses [`::std::collections::BTreeMap`](https://doc.rust-lang.org/std/collections/struct.BTreeMap.html) to count items and sort them.
17//!
18//! # Module macros
19//!
20//! * [`assert_bag_superbag`](macro@crate::assert_bag_superbag)
21//! * [`assert_bag_superbag_as_result`](macro@crate::assert_bag_superbag_as_result)
22//! * [`debug_assert_bag_superbag`](macro@crate::debug_assert_bag_superbag)
23
24/// Assert a bag is a superbag of another.
25///
26/// Pseudocode:<br>
27/// (a_collection ⇒ a_bag) ⊃ (b_collection ⇒ b_bag)
28///
29/// * If true, return Result `Ok((a, b))`.
30///
31/// * Otherwise, return Result `Err(message)`.
32///
33/// This macro is useful for runtime checks, such as checking parameters,
34/// or sanitizing inputs, or handling different results in different ways.
35///
36/// # Module macros
37///
38/// * [`assert_bag_superbag`](macro@crate::assert_bag_superbag)
39/// * [`assert_bag_superbag_as_result`](macro@crate::assert_bag_superbag_as_result)
40/// * [`debug_assert_bag_superbag`](macro@crate::debug_assert_bag_superbag)
41///
42#[macro_export]
43macro_rules! assert_bag_superbag_as_result {
44    ($a_collection:expr, $b_collection:expr $(,)?) => {
45        match (&$a_collection, &$b_collection) {
46            (a_collection, b_collection) => {
47                let a = $crate::assert_bag_impl_prep!(a_collection);
48                let b = $crate::assert_bag_impl_prep!(b_collection);
49                if b_collection.into_iter().all(|key| {
50                    a.contains_key(&key)
51                        && b.contains_key(&key)
52                        && a.get_key_value(&key) >= b.get_key_value(&key)
53                }) {
54                    Ok((a, b))
55                } else {
56                    Err(
57                        format!(
58                            concat!(
59                                "assertion failed: `assert_bag_superbag!(a_collection, b_collection)`\n",
60                                "https://docs.rs/assertables/9.9.0/assertables/macro.assert_bag_superbag.html\n",
61                                " a label: `{}`,\n",
62                                " a debug: `{:?}`,\n",
63                                " b label: `{}`,\n",
64                                " b debug: `{:?}`,\n",
65                                "   a bag: `{:?}`,\n",
66                                "   b bag: `{:?}`"
67                            ),
68                            stringify!($a_collection),
69                            a_collection,
70                            stringify!($b_collection),
71                            b_collection,
72                            a,
73                            b
74                        )
75                    )
76                }
77            }
78        }
79    };
80}
81
82#[cfg(test)]
83mod test_assert_bag_superbag_as_result {
84    use std::collections::BTreeMap;
85    // use std::sync::Once;
86
87    #[test]
88    fn success() {
89        let a = [1, 1, 1];
90        let b = [1, 1];
91        for _ in 0..1 {
92            let actual = assert_bag_superbag_as_result!(a, b);
93            assert_eq!(
94                actual.unwrap(),
95                (BTreeMap::from([(&1, 3)]), BTreeMap::from([(&1, 2)]))
96            );
97        }
98    }
99
100    //TODO
101    // #[test]
102    // fn success_once() {
103    //     static A: Once = Once::new();
104    //     fn a() -> [i32; 3] {
105    //         if A.is_completed() {
106    //             panic!("A.is_completed()")
107    //         } else {
108    //             A.call_once(|| {})
109    //         }
110    //         [1, 1, 1]
111    //     }
112
113    //     static B: Once = Once::new();
114    //     fn b() -> [i32; 2] {
115    //         if B.is_completed() {
116    //             panic!("B.is_completed()")
117    //         } else {
118    //             B.call_once(|| {})
119    //         }
120    //         [1, 1]
121    //     }
122
123    //     assert_eq!(A.is_completed(), false);
124    //     assert_eq!(B.is_completed(), false);
125    //     let result = assert_bag_superbag_as_result!(a(), b());
126    //     assert!(result.is_ok());
127    //     assert_eq!(A.is_completed(), true);
128    //     assert_eq!(B.is_completed(), true);
129    // }
130
131    #[test]
132    fn failure_because_key_is_missing() {
133        let a = [1, 1];
134        let b = [2, 2];
135        let actual = assert_bag_superbag_as_result!(a, b);
136        let message = concat!(
137            "assertion failed: `assert_bag_superbag!(a_collection, b_collection)`\n",
138            "https://docs.rs/assertables/9.9.0/assertables/macro.assert_bag_superbag.html\n",
139            " a label: `a`,\n",
140            " a debug: `[1, 1]`,\n",
141            " b label: `b`,\n",
142            " b debug: `[2, 2]`,\n",
143            "   a bag: `{1: 2}`,\n",
144            "   b bag: `{2: 2}`"
145        );
146        assert_eq!(actual.unwrap_err(), message);
147    }
148
149    #[test]
150    fn failure_because_val_count_is_excessive() {
151        let a = [1, 1];
152        let b = [1, 1, 1];
153        let actual = assert_bag_superbag_as_result!(a, b);
154        let message = concat!(
155            "assertion failed: `assert_bag_superbag!(a_collection, b_collection)`\n",
156            "https://docs.rs/assertables/9.9.0/assertables/macro.assert_bag_superbag.html\n",
157            " a label: `a`,\n",
158            " a debug: `[1, 1]`,\n",
159            " b label: `b`,\n",
160            " b debug: `[1, 1, 1]`,\n",
161            "   a bag: `{1: 2}`,\n",
162            "   b bag: `{1: 3}`"
163        );
164        assert_eq!(actual.unwrap_err(), message);
165    }
166}
167
168/// Assert a bag is a superbag of another.
169///
170/// Pseudocode:<br>
171/// (a_collection ⇒ a_bag) ⊃ (b_collection ⇒ b_bag)
172///
173/// * If true, return `(a, b)`.
174///
175/// * Otherwise, call [`panic!`] in order to print the values of the
176///   expressions with their debug representations.
177///
178/// # Examples
179///
180/// ```rust
181/// use assertables::*;
182/// # use std::panic;
183///
184/// # fn main() {
185/// let a = [1, 1, 1];
186/// let b = [1, 1];
187/// assert_bag_superbag!(a, b);
188///
189/// # let result = panic::catch_unwind(|| {
190/// // This will panic
191/// let a = [1, 1];
192/// let b = [1, 1, 1];
193/// assert_bag_superbag!(a, b);
194/// # });
195/// // assertion failed: `assert_bag_superbag!(a_collection, b_collection)`
196/// // https://docs.rs/assertables/…/assertables/macro.assert_bag_superbag.html
197/// //  a label: `a`,
198/// //  a debug: `[1, 1]`,
199/// //  b label: `b`,
200/// //  b debug: `[1, 1, 1]`,
201/// //    a bag: `{1: 2}`,
202/// //    b bag: `{1: 3}`
203/// # let actual = result.unwrap_err().downcast::<String>().unwrap().to_string();
204/// # let message = concat!(
205/// #     "assertion failed: `assert_bag_superbag!(a_collection, b_collection)`\n",
206/// #     "https://docs.rs/assertables/9.9.0/assertables/macro.assert_bag_superbag.html\n",
207/// #     " a label: `a`,\n",
208/// #     " a debug: `[1, 1]`,\n",
209/// #     " b label: `b`,\n",
210/// #     " b debug: `[1, 1, 1]`,\n",
211/// #     "   a bag: `{1: 2}`,\n",
212/// #     "   b bag: `{1: 3}`"
213/// # );
214/// # assert_eq!(actual, message);
215/// # }
216/// ```
217///
218/// This implementation uses [`::std::collections::BTreeMap`](https://doc.rust-lang.org/std/collections/struct.BTreeMap.html) to count items and sort them.
219///
220/// # Module macros
221///
222/// * [`assert_bag_superbag`](macro@crate::assert_bag_superbag)
223/// * [`assert_bag_superbag_as_result`](macro@crate::assert_bag_superbag_as_result)
224/// * [`debug_assert_bag_superbag`](macro@crate::debug_assert_bag_superbag)
225///
226#[macro_export]
227macro_rules! assert_bag_superbag {
228    ($a_collection:expr, $b_collection:expr $(,)?) => {
229        match $crate::assert_bag_superbag_as_result!($a_collection, $b_collection) {
230            Ok(x) => x,
231            Err(err) => panic!("{}", err),
232        }
233    };
234    ($a_collection:expr, $b_collection:expr, $($message:tt)+) => {
235        match $crate::assert_bag_superbag_as_result!($a_collection, $b_collection) {
236            Ok(x) => x,
237            Err(err) => panic!("{}\n{}", format_args!($($message)+), err),
238        }
239    };
240}
241
242#[cfg(test)]
243mod test_assert_bag_superbag {
244    use std::collections::BTreeMap;
245    use std::panic;
246
247    #[test]
248    fn success() {
249        let a = [1, 1, 1];
250        let b = [1, 1];
251        for _ in 0..1 {
252            let actual = assert_bag_superbag!(a, b);
253            let expect = (BTreeMap::from([(&1, 3)]), BTreeMap::from([(&1, 2)]));
254            assert_eq!(actual, expect);
255        }
256    }
257
258    #[test]
259    fn failure_because_key_is_missing() {
260        let result = panic::catch_unwind(|| {
261            let a = [1, 1];
262            let b = [2, 2];
263            let _actual = assert_bag_superbag!(a, b);
264        });
265        let message = concat!(
266            "assertion failed: `assert_bag_superbag!(a_collection, b_collection)`\n",
267            "https://docs.rs/assertables/9.9.0/assertables/macro.assert_bag_superbag.html\n",
268            " a label: `a`,\n",
269            " a debug: `[1, 1]`,\n",
270            " b label: `b`,\n",
271            " b debug: `[2, 2]`,\n",
272            "   a bag: `{1: 2}`,\n",
273            "   b bag: `{2: 2}`"
274        );
275        assert_eq!(
276            result
277                .unwrap_err()
278                .downcast::<String>()
279                .unwrap()
280                .to_string(),
281            message
282        );
283    }
284
285    #[test]
286    fn failure_because_val_count_is_excessive() {
287        let result = panic::catch_unwind(|| {
288            let a = [1, 1];
289            let b = [1, 1, 1];
290            let _actual = assert_bag_superbag!(a, b);
291        });
292        let message = concat!(
293            "assertion failed: `assert_bag_superbag!(a_collection, b_collection)`\n",
294            "https://docs.rs/assertables/9.9.0/assertables/macro.assert_bag_superbag.html\n",
295            " a label: `a`,\n",
296            " a debug: `[1, 1]`,\n",
297            " b label: `b`,\n",
298            " b debug: `[1, 1, 1]`,\n",
299            "   a bag: `{1: 2}`,\n",
300            "   b bag: `{1: 3}`"
301        );
302        assert_eq!(
303            result
304                .unwrap_err()
305                .downcast::<String>()
306                .unwrap()
307                .to_string(),
308            message
309        );
310    }
311}
312
313/// Assert a bag is a superbag of another.
314///
315/// Pseudocode:<br>
316/// (a_collection ⇒ a_bag) ⊃ (b_collection ⇒ b_bag)
317///
318/// This macro provides the same statements as [`assert_bag_superbag`](macro.assert_bag_superbag.html),
319/// except this macro's statements are only enabled in non-optimized
320/// builds by default. An optimized build will not execute this macro's
321/// statements unless `-C debug-assertions` is passed to the compiler.
322///
323/// This macro is useful for checks that are too expensive to be present
324/// in a release build but may be helpful during development.
325///
326/// The result of expanding this macro is always type checked.
327///
328/// An unchecked assertion allows a program in an inconsistent state to
329/// keep running, which might have unexpected consequences but does not
330/// introduce unsafety as long as this only happens in safe code. The
331/// performance cost of assertions, however, is not measurable in general.
332/// Replacing `assert*!` with `debug_assert*!` is thus only encouraged
333/// after thorough profiling, and more importantly, only in safe code!
334///
335/// This macro is intended to work in a similar way to
336/// [`::std::debug_assert`](https://doc.rust-lang.org/std/macro.debug_assert.html).
337///
338/// # Module macros
339///
340/// * [`assert_bag_superbag`](macro@crate::assert_bag_superbag)
341/// * [`assert_bag_superbag_as_result`](macro@crate::assert_bag_superbag_as_result)
342/// * [`debug_assert_bag_superbag`](macro@crate::debug_assert_bag_superbag)
343///
344#[macro_export]
345macro_rules! debug_assert_bag_superbag {
346    ($($arg:tt)*) => {
347        if cfg!(debug_assertions) {
348            $crate::assert_bag_superbag!($($arg)*);
349        }
350    };
351}
352
353#[cfg(test)]
354mod test_debug_assert_bag_superbag {
355    use std::collections::BTreeMap;
356    use std::panic;
357
358    #[test]
359    fn success() {
360        let a = [1, 1, 1];
361        let b = [1, 1];
362        for _ in 0..1 {
363            let _actual = debug_assert_bag_superbag!(a, b);
364            let _expect = (BTreeMap::from([(&1, 3)]), BTreeMap::from([(&1, 2)]));
365            // assert_eq!(actual, expect);
366        }
367    }
368
369    #[test]
370    fn failure_because_key_is_missing() {
371        let result = panic::catch_unwind(|| {
372            let a = [1, 1];
373            let b = [2, 2];
374            let _actual = debug_assert_bag_superbag!(a, b);
375        });
376        let message = concat!(
377            "assertion failed: `assert_bag_superbag!(a_collection, b_collection)`\n",
378            "https://docs.rs/assertables/9.9.0/assertables/macro.assert_bag_superbag.html\n",
379            " a label: `a`,\n",
380            " a debug: `[1, 1]`,\n",
381            " b label: `b`,\n",
382            " b debug: `[2, 2]`,\n",
383            "   a bag: `{1: 2}`,\n",
384            "   b bag: `{2: 2}`"
385        );
386        assert_eq!(
387            result
388                .unwrap_err()
389                .downcast::<String>()
390                .unwrap()
391                .to_string(),
392            message
393        );
394    }
395
396    #[test]
397    fn failure_because_val_count_is_excessive() {
398        let result = panic::catch_unwind(|| {
399            let a = [1, 1];
400            let b = [1, 1, 1];
401            let _actual = debug_assert_bag_superbag!(a, b);
402        });
403        let message = concat!(
404            "assertion failed: `assert_bag_superbag!(a_collection, b_collection)`\n",
405            "https://docs.rs/assertables/9.9.0/assertables/macro.assert_bag_superbag.html\n",
406            " a label: `a`,\n",
407            " a debug: `[1, 1]`,\n",
408            " b label: `b`,\n",
409            " b debug: `[1, 1, 1]`,\n",
410            "   a bag: `{1: 2}`,\n",
411            "   b bag: `{1: 3}`"
412        );
413        assert_eq!(
414            result
415                .unwrap_err()
416                .downcast::<String>()
417                .unwrap()
418                .to_string(),
419            message
420        );
421    }
422}