1#![cfg_attr(not(feature = "std"), no_std)]
48#![cfg_attr(docsrs, feature(doc_cfg))]
49#![warn(missing_docs)]
50
51#[cfg(doctest)]
53mod test_readme {
54 macro_rules! external_doc_test {
55 ($x:expr) => {
56 #[doc = $x]
57 extern "C" {}
58 };
59 }
60
61 external_doc_test!(include_str!("../README.md"));
62}
63
64extern crate alloc;
65extern crate core;
66
67use alloc::format;
68use alloc::string::String;
69use alloc::vec::Vec;
70use core::fmt::{Arguments, Debug};
71#[cfg(feature = "color")]
72#[cfg(windows)]
73use std::sync::Once;
74
75#[cfg(feature = "color")]
76#[cfg(windows)]
77static INIT_COLOR: Once = Once::new();
78
79#[cfg(feature = "color")]
80#[cfg(windows)]
81static mut COLOR_ENABLED: bool = false;
82
83#[macro_export]
116macro_rules! assert_eq_unordered {
117 ($left:expr, $right:expr $(,)?) => {
118 $crate::pass_or_panic($crate::compare_unordered($left, $right), core::option::Option::None);
119 };
120 ($left:expr, $right:expr, $($arg:tt)+) => {
121 $crate::pass_or_panic(
122 $crate::compare_unordered($left, $right),
123 core::option::Option::Some(core::format_args!($($arg)+))
124 );
125 };
126}
127
128#[macro_export]
149macro_rules! assert_eq_unordered_iter {
150 ($left:expr, $right:expr $(,)?) => {
151 $crate::pass_or_panic($crate::compare_unordered_iter($left, $right), core::option::Option::None);
152 };
153 ($left:expr, $right:expr, $($arg:tt)+) => {
154 $crate::pass_or_panic(
155 $crate::compare_unordered_iter($left, $right),
156 core::option::Option::Some(core::format_args!($($arg)+))
157 );
158 };
159}
160
161#[macro_export]
194macro_rules! assert_eq_unordered_sort {
195 ($left:expr, $right:expr $(,)?) => {
196 $crate::pass_or_panic($crate::compare_unordered_sort($left, $right), core::option::Option::None);
197 };
198 ($left:expr, $right:expr, $($arg:tt)+) => {
199 $crate::pass_or_panic(
200 $crate::compare_unordered_sort($left, $right),
201 core::option::Option::Some(core::format_args!($($arg)+))
202 );
203 };
204}
205
206#[macro_export]
227macro_rules! assert_eq_unordered_sort_iter {
228 ($left:expr, $right:expr $(,)?) => {
229 $crate::pass_or_panic($crate::compare_unordered_sort_iter($left, $right), core::option::Option::None);
230 };
231 ($left:expr, $right:expr, $($arg:tt)+) => {
232 $crate::pass_or_panic(
233 $crate::compare_unordered_sort_iter($left, $right),
234 core::option::Option::Some(core::format_args!($($arg)+))
235 );
236 };
237}
238
239#[cfg(feature = "color")]
240#[cfg(windows)]
241#[inline]
242fn init_color() -> bool {
243 unsafe {
245 INIT_COLOR.call_once(|| {
246 COLOR_ENABLED = ansi_term::enable_ansi_support().is_ok();
247 });
248 COLOR_ENABLED
249 }
250}
251
252#[cfg(feature = "color")]
253#[cfg(not(windows))]
254#[inline]
255const fn init_color() -> bool {
256 true
257}
258
259#[doc(hidden)]
260pub enum CompareResult {
261 Equal,
262 NotEqualDiffElements(String, String, String),
263}
264
265#[cfg(feature = "color")]
266#[doc(hidden)]
267#[inline]
268pub fn pass_or_panic(result: CompareResult, msg: Option<Arguments>) {
269 if init_color() {
270 color_pass_or_panic(result, msg)
271 } else {
272 plain_pass_or_panic(result, msg);
273 }
274}
275
276#[cfg(not(feature = "color"))]
277#[doc(hidden)]
278#[inline]
279pub fn pass_or_panic(result: CompareResult, msg: Option<Arguments>) {
280 plain_pass_or_panic(result, msg);
281}
282
283#[cfg(feature = "color")]
284fn color_pass_or_panic(result: CompareResult, msg: Option<Arguments>) {
285 match result {
286 CompareResult::NotEqualDiffElements(in_both, in_left_not_right, in_right_not_left) => {
287 use ansi_term::Color::{Green, Red, Yellow};
288
289 let msg = match msg {
290 Some(msg) => msg.to_string(),
291 None => {
292 format!(
293 "The {} did not contain the {} as the {}",
294 Red.paint("left"),
295 Yellow.paint("same items"),
296 Green.paint("right"),
297 )
298 }
299 };
300
301 let both = Yellow.paint(format!("In both: {in_both}"));
302 let left = Red.paint(format!("In left: {in_left_not_right}"));
303 let right = Green.paint(format!("In right: {in_right_not_left}"));
304
305 panic!("{msg}:\n{both}\n{left}\n{right}\n");
306 }
307 CompareResult::Equal => {}
308 }
309}
310
311fn plain_pass_or_panic(result: CompareResult, msg: Option<Arguments>) {
312 match result {
313 CompareResult::NotEqualDiffElements(in_both, in_left_not_right, in_right_not_left) => {
314 let msg = match msg {
315 Some(msg) => msg,
316 None => format_args!("The left did not contain the same items as the right"),
318 };
319
320 panic!(
321 "{msg}:\nIn both: {in_both}\nIn left: {in_left_not_right}\nIn right: {in_right_not_left}"
322 );
323 }
324 CompareResult::Equal => {}
325 }
326}
327
328fn compare_elem_by_elem<I, T>(left: I, right: Vec<T>) -> CompareResult
329where
330 I: IntoIterator<Item = T>,
331 T: Debug + PartialEq,
332{
333 let mut in_right_not_left: Vec<_> = right;
334 let mut in_left_not_right = Vec::new();
335 let mut in_both = Vec::with_capacity(in_right_not_left.len());
337
338 for elem1 in left {
339 match in_right_not_left.iter().position(|elem2| &elem1 == elem2) {
340 Some(idx) => {
341 in_both.push(elem1);
342 in_right_not_left.remove(idx);
343 }
344 None => {
345 in_left_not_right.push(elem1);
346 }
347 }
348 }
349
350 if !in_left_not_right.is_empty() || !in_right_not_left.is_empty() {
351 CompareResult::NotEqualDiffElements(
352 format!("{in_both:#?}"),
353 format!("{in_left_not_right:#?}"),
354 format!("{in_right_not_left:#?}"),
355 )
356 } else {
357 CompareResult::Equal
358 }
359}
360
361#[doc(hidden)]
362pub fn compare_unordered_iter<L, R, T>(left: L, right: R) -> CompareResult
363where
364 L: IntoIterator<Item = T>,
365 R: IntoIterator<Item = T>,
366 T: Debug + PartialEq,
367{
368 let right = right.into_iter().collect();
369 compare_elem_by_elem(left, right)
370}
371
372#[doc(hidden)]
373pub fn compare_unordered<L, R, T>(left: L, right: R) -> CompareResult
374where
375 L: IntoIterator<Item = T> + PartialEq<R>,
376 R: IntoIterator<Item = T>,
377 T: Debug + PartialEq,
378{
379 if left != right {
381 compare_unordered_iter(left, right)
383 } else {
384 CompareResult::Equal
385 }
386}
387
388#[doc(hidden)]
389pub fn compare_unordered_sort_iter<L, R, T>(left: L, right: R) -> CompareResult
390where
391 L: IntoIterator<Item = T>,
392 R: IntoIterator<Item = T>,
393 T: Debug + Ord,
394{
395 let mut left: Vec<_> = left.into_iter().collect();
397 let mut right: Vec<_> = right.into_iter().collect();
398
399 left.sort_unstable();
400 right.sort_unstable();
401
402 if left != right {
403 compare_elem_by_elem(left, right)
405 } else {
406 CompareResult::Equal
407 }
408}
409
410#[doc(hidden)]
411pub fn compare_unordered_sort<L, R, T>(left: L, right: R) -> CompareResult
412where
413 L: IntoIterator<Item = T> + PartialEq<R>,
414 R: IntoIterator<Item = T>,
415 T: Debug + Ord,
416{
417 if left != right {
419 compare_unordered_sort_iter(left, right)
421 } else {
422 CompareResult::Equal
423 }
424}
425
426#[cfg(test)]
427mod tests {
428 use crate::{
429 compare_unordered, compare_unordered_iter, compare_unordered_sort,
430 compare_unordered_sort_iter, CompareResult,
431 };
432 use alloc::vec::Vec;
433 use alloc::{format, vec};
434 use core::fmt::Debug;
435
436 #[derive(Debug, PartialEq)]
437 struct MyType(i32);
438
439 #[derive(Debug, Eq, Ord, PartialEq, PartialOrd)]
440 struct MyTypeSort(i32);
441
442 fn validate_results<T: Debug>(
443 result: CompareResult,
444 both_expected: Vec<T>,
445 left_expected: Vec<T>,
446 right_expected: Vec<T>,
447 ) {
448 match result {
449 CompareResult::NotEqualDiffElements(both_actual, left_actual, right_actual) => {
450 assert_eq!(format!("{both_expected:#?}"), both_actual);
451 assert_eq!(format!("{left_expected:#?}"), left_actual);
452 assert_eq!(format!("{right_expected:#?}"), right_actual);
453 }
454 _ => {
455 panic!("Left and right were expected to have have different elements");
456 }
457 }
458 }
459
460 macro_rules! make_tests {
461 ($func:ident, $type:ident) => {
462 #[test]
463 fn compare_unordered_not_equal_diff_elem() {
464 let left = vec![$type(1), $type(2), $type(4), $type(5)];
465 let right = vec![$type(2), $type(0), $type(4)];
466
467 validate_results(
468 $func(left, right),
469 vec![$type(2), $type(4)],
470 vec![$type(1), $type(5)],
471 vec![$type(0)],
472 );
473 }
474
475 #[test]
476 fn compare_unordered_not_equal_dup_elem_diff_len() {
477 let left = vec![$type(2), $type(4), $type(4)];
478 let right = vec![$type(4), $type(2)];
479
480 validate_results(
481 $func(left, right),
482 vec![$type(2), $type(4)],
483 vec![$type(4)],
484 vec![],
485 );
486 }
487
488 #[test]
489 fn compare_unordered_not_equal_dup_elem() {
490 let left = vec![$type(2), $type(2), $type(2), $type(4)];
491 let right = vec![$type(2), $type(4), $type(4), $type(4)];
492
493 validate_results(
494 $func(left, right),
495 vec![$type(2), $type(4)],
496 vec![$type(2), $type(2)],
497 vec![$type(4), $type(4)],
498 );
499 }
500
501 #[test]
502 fn compare_unordered_equal_diff_order() {
503 let left = vec![$type(1), $type(2), $type(4), $type(5)];
504 let right = vec![$type(5), $type(2), $type(1), $type(4)];
505
506 assert!(matches!($func(left, right), CompareResult::Equal));
507 }
508
509 #[test]
510 fn compare_unordered_equal_same_order() {
511 let left = vec![$type(1), $type(2), $type(4), $type(5)];
512 let right = vec![$type(1), $type(2), $type(4), $type(5)];
513
514 assert!(matches!($func(left, right), CompareResult::Equal));
515 }
516
517 #[test]
518 fn compare_unordered_equal_different_collection_types() {
519 let left = vec![$type(1), $type(2), $type(4), $type(5)];
520 let right = [$type(5), $type(2), $type(1), $type(4)];
521
522 assert!(matches!($func(left, right), CompareResult::Equal));
523 }
524 };
525 }
526
527 macro_rules! make_tests_iter {
528 ($func:ident, $type:ident) => {
529 #[test]
530 fn compare_unordered_not_equal_diff_elem() {
531 let left = vec![$type(1), $type(2), $type(4), $type(5)].into_iter();
532 let right = vec![$type(2), $type(0), $type(4)].into_iter();
533
534 validate_results(
535 $func(left, right),
536 vec![$type(2), $type(4)],
537 vec![$type(1), $type(5)],
538 vec![$type(0)],
539 );
540 }
541
542 #[test]
543 fn compare_unordered_not_equal_dup_elem_diff_len() {
544 let left = vec![$type(2), $type(4), $type(4)].into_iter();
545 let right = vec![$type(4), $type(2)].into_iter();
546
547 validate_results(
548 $func(left, right),
549 vec![$type(2), $type(4)],
550 vec![$type(4)],
551 vec![],
552 );
553 }
554
555 #[test]
556 fn compare_unordered_not_equal_dup_elem() {
557 let left = vec![$type(2), $type(2), $type(2), $type(4)].into_iter();
558 let right = vec![$type(2), $type(4), $type(4), $type(4)].into_iter();
559
560 validate_results(
561 $func(left, right),
562 vec![$type(2), $type(4)],
563 vec![$type(2), $type(2)],
564 vec![$type(4), $type(4)],
565 );
566 }
567
568 #[test]
569 fn compare_unordered_equal_diff_order() {
570 let left = vec![$type(1), $type(2), $type(4), $type(5)].into_iter();
571 let right = vec![$type(5), $type(2), $type(1), $type(4)].into_iter();
572
573 assert!(matches!($func(left, right), CompareResult::Equal));
574 }
575
576 #[test]
577 fn compare_unordered_equal_same_order() {
578 let left = vec![$type(1), $type(2), $type(4), $type(5)].into_iter();
579 let right = vec![$type(1), $type(2), $type(4), $type(5)].into_iter();
580
581 assert!(matches!($func(left, right), CompareResult::Equal));
582 }
583
584 #[test]
585 fn compare_unordered_equal_different_iterator_types() {
586 let left = vec![$type(1), $type(2), $type(4), $type(5)];
587 let right = [$type(5), $type(2), $type(1), $type(4)].into_iter();
588
589 assert!(matches!($func(left, right), CompareResult::Equal));
590 }
591 };
592 }
593
594 mod regular {
595 use super::*;
596
597 make_tests!(compare_unordered, MyType);
598 }
599
600 mod regular_iter {
601 use super::*;
602
603 make_tests_iter!(compare_unordered_iter, MyType);
604 }
605
606 mod sort {
607 use super::*;
608
609 make_tests!(compare_unordered_sort, MyTypeSort);
610 }
611
612 mod sort_iter {
613 use super::*;
614
615 make_tests_iter!(compare_unordered_sort_iter, MyTypeSort);
616 }
617}