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}