1#![cfg_attr(not(feature = "std"), no_std)]
40#![cfg_attr(docsrs, feature(doc_cfg))]
41#![warn(missing_docs)]
42
43#[cfg(doctest)]
45mod test_readme {
46 macro_rules! external_doc_test {
47 ($x:expr) => {
48 #[doc = $x]
49 extern "C" {}
50 };
51 }
52
53 external_doc_test!(include_str!("../README.md"));
54}
55
56extern crate alloc;
57extern crate core;
58
59use alloc::format;
60use alloc::string::String;
61use alloc::vec::Vec;
62use core::fmt::{Arguments, Debug};
63#[cfg(feature = "color")]
64#[cfg(windows)]
65use std::sync::Once;
66
67#[cfg(feature = "color")]
68#[cfg(windows)]
69static INIT_COLOR: Once = Once::new();
70
71#[cfg(feature = "color")]
72#[cfg(windows)]
73static mut COLOR_ENABLED: bool = false;
74
75#[macro_export]
107macro_rules! assert_eq_unordered {
108 ($left:expr, $right:expr $(,)?) => {
109 $crate::pass_or_panic($crate::compare_unordered($left, $right), core::option::Option::None);
110 };
111 ($left:expr, $right:expr, $($arg:tt)+) => {
112 $crate::pass_or_panic(
113 $crate::compare_unordered($left, $right),
114 core::option::Option::Some(core::format_args!($($arg)+))
115 );
116 };
117}
118
119#[macro_export]
151macro_rules! assert_eq_unordered_sort {
152 ($left:expr, $right:expr $(,)?) => {
153 $crate::pass_or_panic($crate::compare_unordered_sort($left, $right), core::option::Option::None);
154 };
155 ($left:expr, $right:expr, $($arg:tt)+) => {
156 $crate::pass_or_panic(
157 $crate::compare_unordered_sort($left, $right),
158 core::option::Option::Some(core::format_args!($($arg)+))
159 );
160 };
161}
162
163#[cfg(feature = "color")]
164#[cfg(windows)]
165#[inline]
166fn init_color() -> bool {
167 unsafe {
169 INIT_COLOR.call_once(|| {
170 COLOR_ENABLED = ansi_term::enable_ansi_support().is_ok();
171 });
172 COLOR_ENABLED
173 }
174}
175
176#[cfg(feature = "color")]
177#[cfg(not(windows))]
178#[inline]
179const fn init_color() -> bool {
180 true
181}
182
183#[doc(hidden)]
184pub enum CompareResult {
185 Equal,
186 NotEqualDiffElements(String, String, String),
187}
188
189#[cfg(feature = "color")]
190#[doc(hidden)]
191#[inline]
192pub fn pass_or_panic(result: CompareResult, msg: Option<Arguments>) {
193 if init_color() {
194 color_pass_or_panic(result, msg)
195 } else {
196 plain_pass_or_panic(result, msg);
197 }
198}
199
200#[cfg(not(feature = "color"))]
201#[doc(hidden)]
202#[inline]
203pub fn pass_or_panic(result: CompareResult, msg: Option<Arguments>) {
204 plain_pass_or_panic(result, msg);
205}
206
207#[cfg(feature = "color")]
208fn color_pass_or_panic(result: CompareResult, msg: Option<Arguments>) {
209 match result {
210 CompareResult::NotEqualDiffElements(in_both, in_left_not_right, in_right_not_left) => {
211 use ansi_term::Color::{Green, Red, Yellow};
212
213 let msg = match msg {
214 Some(msg) => msg.to_string(),
215 None => {
216 format!(
217 "The {} did not contain the {} as the {}",
218 Red.paint("left"),
219 Yellow.paint("same items"),
220 Green.paint("right"),
221 )
222 }
223 };
224
225 let both = Yellow.paint(format!("In both: {in_both}"));
226 let left = Red.paint(format!("In left: {in_left_not_right}"));
227 let right = Green.paint(format!("In right: {in_right_not_left}"));
228
229 panic!("{msg}:\n{both}\n{left}\n{right}\n");
230 }
231 CompareResult::Equal => {}
232 }
233}
234
235fn plain_pass_or_panic(result: CompareResult, msg: Option<Arguments>) {
236 match result {
237 CompareResult::NotEqualDiffElements(in_both, in_left_not_right, in_right_not_left) => {
238 let msg = match msg {
239 Some(msg) => msg,
240 None => format_args!("The left did not contain the same items as the right"),
242 };
243
244 panic!(
245 "{msg}:\nIn both: {in_both}\nIn left: {in_left_not_right}\nIn right: {in_right_not_left}"
246 );
247 }
248 CompareResult::Equal => {}
249 }
250}
251
252fn compare_elem_by_elem<I, T>(left: I, right: Vec<T>) -> CompareResult
253where
254 I: IntoIterator<Item = T> + PartialEq,
255 T: Debug + PartialEq,
256{
257 let mut in_right_not_left: Vec<_> = right;
258 let mut in_left_not_right = Vec::new();
259 let mut in_both = Vec::with_capacity(in_right_not_left.len());
261
262 for elem1 in left {
263 match in_right_not_left.iter().position(|elem2| &elem1 == elem2) {
264 Some(idx) => {
265 in_both.push(elem1);
266 in_right_not_left.remove(idx);
267 }
268 None => {
269 in_left_not_right.push(elem1);
270 }
271 }
272 }
273
274 if !in_left_not_right.is_empty() || !in_right_not_left.is_empty() {
275 CompareResult::NotEqualDiffElements(
276 format!("{in_both:#?}"),
277 format!("{in_left_not_right:#?}"),
278 format!("{in_right_not_left:#?}"),
279 )
280 } else {
281 CompareResult::Equal
282 }
283}
284
285#[doc(hidden)]
286pub fn compare_unordered<I, T>(left: I, right: I) -> CompareResult
287where
288 I: IntoIterator<Item = T> + PartialEq,
289 T: Debug + PartialEq,
290{
291 if left != right {
293 let right = right.into_iter().collect();
295 compare_elem_by_elem(left, right)
296 } else {
297 CompareResult::Equal
298 }
299}
300
301#[doc(hidden)]
302pub fn compare_unordered_sort<I, T>(left: I, right: I) -> CompareResult
303where
304 I: IntoIterator<Item = T> + PartialEq,
305 T: Debug + Ord + PartialEq,
306{
307 if left != right {
309 let mut left: Vec<_> = left.into_iter().collect();
311 let mut right: Vec<_> = right.into_iter().collect();
312
313 left.sort_unstable();
314 right.sort_unstable();
315
316 if left != right {
317 compare_elem_by_elem(left, right)
319 } else {
320 CompareResult::Equal
321 }
322 } else {
323 CompareResult::Equal
324 }
325}
326
327#[cfg(test)]
328mod tests {
329 use crate::{compare_unordered, compare_unordered_sort, CompareResult};
330 use alloc::vec::Vec;
331 use alloc::{format, vec};
332 use core::fmt::Debug;
333
334 #[derive(Debug, PartialEq)]
335 struct MyType(i32);
336
337 #[derive(Debug, Eq, Ord, PartialEq, PartialOrd)]
338 struct MyTypeSort(i32);
339
340 fn validate_results<T: Debug>(
341 result: CompareResult,
342 both_expected: Vec<T>,
343 left_expected: Vec<T>,
344 right_expected: Vec<T>,
345 ) {
346 match result {
347 CompareResult::NotEqualDiffElements(both_actual, left_actual, right_actual) => {
348 assert_eq!(format!("{both_expected:#?}"), both_actual);
349 assert_eq!(format!("{left_expected:#?}"), left_actual);
350 assert_eq!(format!("{right_expected:#?}"), right_actual);
351 }
352 _ => {
353 panic!("Left and right were expected to have have different elements");
354 }
355 }
356 }
357
358 macro_rules! make_tests {
359 ($func:ident, $type:ident) => {
360 #[test]
361 fn compare_unordered_not_equal_diff_elem() {
362 let left = vec![$type(1), $type(2), $type(4), $type(5)];
363 let right = vec![$type(2), $type(0), $type(4)];
364
365 validate_results(
366 $func(left, right),
367 vec![$type(2), $type(4)],
368 vec![$type(1), $type(5)],
369 vec![$type(0)],
370 );
371 }
372
373 #[test]
374 fn compare_unordered_not_equal_dup_elem_diff_len() {
375 let left = vec![$type(2), $type(4), $type(4)];
376 let right = vec![$type(4), $type(2)];
377
378 validate_results(
379 $func(left, right),
380 vec![$type(2), $type(4)],
381 vec![$type(4)],
382 vec![],
383 );
384 }
385
386 #[test]
387 fn compare_unordered_not_equal_dup_elem() {
388 let left = vec![$type(2), $type(2), $type(2), $type(4)];
389 let right = vec![$type(2), $type(4), $type(4), $type(4)];
390
391 validate_results(
392 $func(left, right),
393 vec![$type(2), $type(4)],
394 vec![$type(2), $type(2)],
395 vec![$type(4), $type(4)],
396 );
397 }
398
399 #[test]
400 fn compare_unordered_equal_diff_order() {
401 let left = vec![$type(1), $type(2), $type(4), $type(5)];
402 let right = vec![$type(5), $type(2), $type(1), $type(4)];
403
404 assert!(matches!($func(left, right), CompareResult::Equal));
405 }
406
407 #[test]
408 fn compare_unordered_equal_same_order() {
409 let left = vec![$type(1), $type(2), $type(4), $type(5)];
410 let right = vec![$type(1), $type(2), $type(4), $type(5)];
411
412 assert!(matches!($func(left, right), CompareResult::Equal));
413 }
414 };
415 }
416
417 mod regular {
418 use super::*;
419
420 make_tests!(compare_unordered, MyType);
421 }
422
423 mod sort {
424 use super::*;
425
426 make_tests!(compare_unordered_sort, MyTypeSort);
427 }
428}