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