polars_testing/asserts/
frame.rs

1/// Asserts that two DataFrames are equal according to the specified options.
2///
3/// This macro compares two Polars DataFrame objects and panics with a detailed error message if they are not equal.
4/// It provides two forms:
5/// - With custom comparison options
6/// - With default comparison options
7///
8/// # Example
9///
10/// ```
11/// use polars_core::prelude::*;
12/// use polars_testing::assert_dataframe_equal;
13/// use polars_testing::asserts::DataFrameEqualOptions;
14///
15/// // Create two DataFrames to compare
16/// let df1 = df! {
17///     "a" => [1, 2, 3],
18///     "b" => [4.0, 5.0, 6.0],
19/// }.unwrap();
20/// let df2 = df! {
21///     "a" => [1, 2, 3],
22///     "b" => [4.0, 5.0, 6.0],
23/// }.unwrap();
24///
25/// // Assert with default options
26/// assert_dataframe_equal!(&df1, &df2);
27///
28/// // Assert with custom options
29/// let options = DataFrameEqualOptions::default()
30///     .with_check_exact(true)
31///     .with_check_row_order(false);
32/// assert_dataframe_equal!(&df1, &df2, options);
33/// ```
34///
35/// # Panics
36///
37/// Panics when the DataFrames are not equal according to the specified comparison criteria.
38///
39#[macro_export]
40macro_rules! assert_dataframe_equal {
41    ($left:expr, $right:expr $(, $options:expr)?) => {
42        #[allow(unused_assignments)]
43        #[allow(unused_mut)]
44        let mut options = $crate::asserts::DataFrameEqualOptions::default();
45        $(options = $options;)?
46
47        match $crate::asserts::assert_dataframe_equal($left, $right, options) {
48            Ok(_) => {},
49            Err(e) => panic!("{}", e),
50        }
51    };
52}
53
54#[cfg(test)]
55mod tests {
56    #[allow(unused_imports)]
57    use polars_core::prelude::*;
58    use polars_core::{disable_string_cache, enable_string_cache};
59
60    // Testing default struct implementation
61    #[test]
62    fn test_dataframe_equal_options() {
63        let options = crate::asserts::DataFrameEqualOptions::default();
64
65        assert!(options.check_row_order);
66        assert!(options.check_column_order);
67        assert!(options.check_dtypes);
68        assert!(!options.check_exact);
69        assert_eq!(options.rtol, 1e-5);
70        assert_eq!(options.atol, 1e-8);
71        assert!(!options.categorical_as_str);
72    }
73
74    // Testing dataframe schema equality parameters
75    #[test]
76    #[should_panic(expected = "height (row count) mismatch")]
77    fn test_dataframe_height_mismatch() {
78        let df1 = DataFrame::new(vec![
79            Series::new("col1".into(), &[1, 2]).into(),
80            Series::new("col2".into(), &["a", "b"]).into(),
81        ])
82        .unwrap();
83
84        let df2 = DataFrame::new(vec![
85            Series::new("col1".into(), &[1, 2, 3]).into(),
86            Series::new("col2".into(), &["a", "b", "c"]).into(),
87        ])
88        .unwrap();
89
90        assert_dataframe_equal!(&df1, &df2);
91    }
92
93    #[test]
94    #[should_panic(expected = "columns mismatch")]
95    fn test_dataframe_column_mismatch() {
96        let df1 = DataFrame::new(vec![
97            Series::new("col1".into(), &[1, 2, 3]).into(),
98            Series::new("col2".into(), &["a", "b", "c"]).into(),
99        ])
100        .unwrap();
101
102        let df2 = DataFrame::new(vec![
103            Series::new("col1".into(), &[1, 2, 3]).into(),
104            Series::new("different_col".into(), &["a", "b", "c"]).into(),
105        ])
106        .unwrap();
107
108        assert_dataframe_equal!(&df1, &df2);
109    }
110
111    #[test]
112    #[should_panic(expected = "data types do not match")]
113    fn test_dataframe_dtype_mismatch() {
114        let df1 = DataFrame::new(vec![
115            Series::new("col1".into(), &[1, 2, 3]).into(),
116            Series::new("col2".into(), &["a", "b", "c"]).into(),
117        ])
118        .unwrap();
119
120        let df2 = DataFrame::new(vec![
121            Series::new("col1".into(), &[1.0, 2.0, 3.0]).into(),
122            Series::new("col2".into(), &["a", "b", "c"]).into(),
123        ])
124        .unwrap();
125
126        assert_dataframe_equal!(&df1, &df2);
127    }
128
129    #[test]
130    fn test_dataframe_dtype_mismatch_ignored() {
131        let df1 = DataFrame::new(vec![
132            Series::new("col1".into(), &[1, 2, 3]).into(),
133            Series::new("col2".into(), &["a", "b", "c"]).into(),
134        ])
135        .unwrap();
136
137        let df2 = DataFrame::new(vec![
138            Series::new("col1".into(), &[1.0, 2.0, 3.0]).into(),
139            Series::new("col2".into(), &["a", "b", "c"]).into(),
140        ])
141        .unwrap();
142
143        let options = crate::asserts::DataFrameEqualOptions::default().with_check_dtypes(false);
144        assert_dataframe_equal!(&df1, &df2, options);
145    }
146
147    #[test]
148    #[should_panic(expected = "columns are not in the same order")]
149    fn test_dataframe_column_order_mismatch() {
150        let df1 = DataFrame::new(vec![
151            Series::new("col1".into(), &[1, 2, 3]).into(),
152            Series::new("col2".into(), &["a", "b", "c"]).into(),
153        ])
154        .unwrap();
155
156        let df2 = DataFrame::new(vec![
157            Series::new("col2".into(), &["a", "b", "c"]).into(),
158            Series::new("col1".into(), &[1, 2, 3]).into(),
159        ])
160        .unwrap();
161
162        assert_dataframe_equal!(&df1, &df2);
163    }
164
165    #[test]
166    fn test_dataframe_column_order_mismatch_ignored() {
167        let df1 = DataFrame::new(vec![
168            Series::new("col1".into(), &[1, 2, 3]).into(),
169            Series::new("col2".into(), &["a", "b", "c"]).into(),
170        ])
171        .unwrap();
172
173        let df2 = DataFrame::new(vec![
174            Series::new("col2".into(), &["a", "b", "c"]).into(),
175            Series::new("col1".into(), &[1, 2, 3]).into(),
176        ])
177        .unwrap();
178
179        let options =
180            crate::asserts::DataFrameEqualOptions::default().with_check_column_order(false);
181        assert_dataframe_equal!(&df1, &df2, options);
182    }
183
184    #[test]
185    #[should_panic(expected = "columns mismatch: [\"col3\"] in left, but not in right")]
186    fn test_dataframe_left_has_extra_column() {
187        let df1 = DataFrame::new(vec![
188            Series::new("col1".into(), &[1, 2, 3]).into(),
189            Series::new("col2".into(), &["a", "b", "c"]).into(),
190            Series::new("col3".into(), &[true, false, true]).into(),
191        ])
192        .unwrap();
193
194        let df2 = DataFrame::new(vec![
195            Series::new("col1".into(), &[1, 2, 3]).into(),
196            Series::new("col2".into(), &["a", "b", "c"]).into(),
197        ])
198        .unwrap();
199
200        assert_dataframe_equal!(&df1, &df2);
201    }
202
203    #[test]
204    #[should_panic(expected = "columns mismatch: [\"col3\"] in right, but not in left")]
205    fn test_dataframe_right_has_extra_column() {
206        let df1 = DataFrame::new(vec![
207            Series::new("col1".into(), &[1, 2, 3]).into(),
208            Series::new("col2".into(), &["a", "b", "c"]).into(),
209        ])
210        .unwrap();
211
212        let df2 = DataFrame::new(vec![
213            Series::new("col1".into(), &[1, 2, 3]).into(),
214            Series::new("col2".into(), &["a", "b", "c"]).into(),
215            Series::new("col3".into(), &[true, false, true]).into(),
216        ])
217        .unwrap();
218
219        assert_dataframe_equal!(&df1, &df2);
220    }
221
222    // Testing basic equality
223    #[test]
224    #[should_panic(expected = "value mismatch for column")]
225    fn test_dataframe_value_mismatch() {
226        let df1 = DataFrame::new(vec![
227            Series::new("col1".into(), &[1, 2, 3]).into(),
228            Series::new("col2".into(), &["a", "b", "c"]).into(),
229            Series::new("col3".into(), &[true, false, true]).into(),
230        ])
231        .unwrap();
232
233        let df2 = DataFrame::new(vec![
234            Series::new("col1".into(), &[1, 2, 3]).into(),
235            Series::new("col2".into(), &["a", "b", "changed"]).into(),
236            Series::new("col3".into(), &[true, false, true]).into(),
237        ])
238        .unwrap();
239
240        assert_dataframe_equal!(&df1, &df2);
241    }
242
243    #[test]
244    fn test_dataframe_equal() {
245        let df1 = DataFrame::new(vec![
246            Series::new("col1".into(), &[1, 2, 3]).into(),
247            Series::new("col2".into(), &["a", "b", "c"]).into(),
248            Series::new("col3".into(), &[true, false, true]).into(),
249        ])
250        .unwrap();
251
252        let df2 = DataFrame::new(vec![
253            Series::new("col1".into(), &[1, 2, 3]).into(),
254            Series::new("col2".into(), &["a", "b", "c"]).into(),
255            Series::new("col3".into(), &[true, false, true]).into(),
256        ])
257        .unwrap();
258
259        assert_dataframe_equal!(&df1, &df2);
260    }
261
262    #[test]
263    #[should_panic(expected = "value mismatch")]
264    fn test_dataframe_row_order_mismatch() {
265        let df1 = DataFrame::new(vec![
266            Series::new("col1".into(), &[1, 2, 3]).into(),
267            Series::new("col2".into(), &["a", "b", "c"]).into(),
268        ])
269        .unwrap();
270
271        let df2 = DataFrame::new(vec![
272            Series::new("col1".into(), &[3, 1, 2]).into(),
273            Series::new("col2".into(), &["c", "a", "b"]).into(),
274        ])
275        .unwrap();
276
277        assert_dataframe_equal!(&df1, &df2);
278    }
279
280    #[test]
281    fn test_dataframe_row_order_ignored() {
282        let df1 = DataFrame::new(vec![
283            Series::new("col1".into(), &[1, 2, 3]).into(),
284            Series::new("col2".into(), &["a", "b", "c"]).into(),
285        ])
286        .unwrap();
287
288        let df2 = DataFrame::new(vec![
289            Series::new("col1".into(), &[3, 1, 2]).into(),
290            Series::new("col2".into(), &["c", "a", "b"]).into(),
291        ])
292        .unwrap();
293
294        let options = crate::asserts::DataFrameEqualOptions::default().with_check_row_order(false);
295        assert_dataframe_equal!(&df1, &df2, options);
296    }
297
298    // Testing more comprehensive equality
299    #[test]
300    #[should_panic(expected = "value mismatch")]
301    fn test_dataframe_complex_mismatch() {
302        let df1 = DataFrame::new(vec![
303            Series::new("integers".into(), &[1, 2, 3, 4, 5]).into(),
304            Series::new("floats".into(), &[1.1, 2.2, 3.3, 4.4, 5.5]).into(),
305            Series::new("strings".into(), &["a", "b", "c", "d", "e"]).into(),
306            Series::new("booleans".into(), &[true, false, true, false, true]).into(),
307            Series::new("opt_ints".into(), &[Some(1), None, Some(3), Some(4), None]).into(),
308        ])
309        .unwrap();
310
311        let df2 = DataFrame::new(vec![
312            Series::new("integers".into(), &[1, 2, 99, 4, 5]).into(),
313            Series::new("floats".into(), &[1.1, 2.2, 3.3, 9.9, 5.5]).into(),
314            Series::new("strings".into(), &["a", "b", "c", "CHANGED", "e"]).into(),
315            Series::new("booleans".into(), &[true, false, false, false, true]).into(),
316            Series::new("opt_ints".into(), &[Some(1), None, Some(3), None, None]).into(),
317        ])
318        .unwrap();
319
320        assert_dataframe_equal!(&df1, &df2);
321    }
322
323    #[test]
324    fn test_dataframe_complex_match() {
325        let df1 = DataFrame::new(vec![
326            Series::new("integers".into(), &[1, 2, 3, 4, 5]).into(),
327            Series::new("floats".into(), &[1.1, 2.2, 3.3, 4.4, 5.5]).into(),
328            Series::new("strings".into(), &["a", "b", "c", "d", "e"]).into(),
329            Series::new("booleans".into(), &[true, false, true, false, true]).into(),
330            Series::new("opt_ints".into(), &[Some(1), None, Some(3), Some(4), None]).into(),
331        ])
332        .unwrap();
333
334        let df2 = DataFrame::new(vec![
335            Series::new("integers".into(), &[1, 2, 3, 4, 5]).into(),
336            Series::new("floats".into(), &[1.1, 2.2, 3.3, 4.4, 5.5]).into(),
337            Series::new("strings".into(), &["a", "b", "c", "d", "e"]).into(),
338            Series::new("booleans".into(), &[true, false, true, false, true]).into(),
339            Series::new("opt_ints".into(), &[Some(1), None, Some(3), Some(4), None]).into(),
340        ])
341        .unwrap();
342
343        assert_dataframe_equal!(&df1, &df2);
344    }
345
346    // Testing float value precision equality
347    #[test]
348    #[should_panic(expected = "value mismatch")]
349    fn test_dataframe_numeric_exact_fail() {
350        let df1 = DataFrame::new(vec![
351            Series::new("col1".into(), &[1.0000001, 2.0000002, 3.0000003]).into(),
352        ])
353        .unwrap();
354
355        let df2 =
356            DataFrame::new(vec![Series::new("col1".into(), &[1.0, 2.0, 3.0]).into()]).unwrap();
357
358        let options = crate::asserts::DataFrameEqualOptions::default().with_check_exact(true);
359        assert_dataframe_equal!(&df1, &df2, options);
360    }
361
362    #[test]
363    fn test_dataframe_numeric_tolerance_pass() {
364        let df1 = DataFrame::new(vec![
365            Series::new("col1".into(), &[1.0000001, 2.0000002, 3.0000003]).into(),
366        ])
367        .unwrap();
368
369        let df2 =
370            DataFrame::new(vec![Series::new("col1".into(), &[1.0, 2.0, 3.0]).into()]).unwrap();
371
372        assert_dataframe_equal!(&df1, &df2);
373    }
374
375    // Testing equality with special values
376    #[test]
377    fn test_empty_dataframe_equal() {
378        let df1 = DataFrame::default();
379        let df2 = DataFrame::default();
380
381        assert_dataframe_equal!(&df1, &df2);
382    }
383
384    #[test]
385    fn test_empty_dataframe_schema_equal() {
386        let df1 = DataFrame::new(vec![
387            Series::new("col1".into(), &Vec::<i32>::new()).into(),
388            Series::new("col2".into(), &Vec::<String>::new()).into(),
389        ])
390        .unwrap();
391
392        let df2 = DataFrame::new(vec![
393            Series::new("col1".into(), &Vec::<i32>::new()).into(),
394            Series::new("col2".into(), &Vec::<String>::new()).into(),
395        ])
396        .unwrap();
397
398        assert_dataframe_equal!(&df1, &df2);
399    }
400
401    #[test]
402    #[should_panic(expected = "value mismatch")]
403    fn test_dataframe_single_row_mismatch() {
404        let df1 = DataFrame::new(vec![
405            Series::new("col1".into(), &[42]).into(),
406            Series::new("col2".into(), &["value"]).into(),
407            Series::new("col3".into(), &[true]).into(),
408        ])
409        .unwrap();
410
411        let df2 = DataFrame::new(vec![
412            Series::new("col1".into(), &[42]).into(),
413            Series::new("col2".into(), &["different"]).into(),
414            Series::new("col3".into(), &[true]).into(),
415        ])
416        .unwrap();
417
418        assert_dataframe_equal!(&df1, &df2);
419    }
420
421    #[test]
422    fn test_dataframe_single_row_match() {
423        let df1 = DataFrame::new(vec![
424            Series::new("col1".into(), &[42]).into(),
425            Series::new("col2".into(), &["value"]).into(),
426            Series::new("col3".into(), &[true]).into(),
427        ])
428        .unwrap();
429
430        let df2 = DataFrame::new(vec![
431            Series::new("col1".into(), &[42]).into(),
432            Series::new("col2".into(), &["value"]).into(),
433            Series::new("col3".into(), &[true]).into(),
434        ])
435        .unwrap();
436
437        assert_dataframe_equal!(&df1, &df2);
438    }
439
440    #[test]
441    #[should_panic(expected = "value mismatch")]
442    fn test_dataframe_null_values_mismatch() {
443        let df1 = DataFrame::new(vec![
444            Series::new("col1".into(), &[Some(1), None, Some(3)]).into(),
445        ])
446        .unwrap();
447
448        let df2 = DataFrame::new(vec![
449            Series::new("col1".into(), &[Some(1), Some(2), None]).into(),
450        ])
451        .unwrap();
452
453        assert_dataframe_equal!(&df1, &df2);
454    }
455
456    #[test]
457    fn test_dataframe_null_values_match() {
458        let df1 = DataFrame::new(vec![
459            Series::new("col1".into(), &[Some(1), None, Some(3)]).into(),
460        ])
461        .unwrap();
462
463        let df2 = DataFrame::new(vec![
464            Series::new("col1".into(), &[Some(1), None, Some(3)]).into(),
465        ])
466        .unwrap();
467
468        assert_dataframe_equal!(&df1, &df2);
469    }
470
471    #[test]
472    #[should_panic(expected = "value mismatch")]
473    fn test_dataframe_nan_values_mismatch() {
474        let df1 = DataFrame::new(vec![
475            Series::new("col1".into(), &[1.0, f64::NAN, 3.0]).into(),
476        ])
477        .unwrap();
478
479        let df2 = DataFrame::new(vec![
480            Series::new("col1".into(), &[1.0, 2.0, f64::NAN]).into(),
481        ])
482        .unwrap();
483
484        assert_dataframe_equal!(&df1, &df2);
485    }
486
487    #[test]
488    fn test_dataframe_nan_values_match() {
489        let df1 = DataFrame::new(vec![
490            Series::new("col1".into(), &[1.0, f64::NAN, 3.0]).into(),
491        ])
492        .unwrap();
493
494        let df2 = DataFrame::new(vec![
495            Series::new("col1".into(), &[1.0, f64::NAN, 3.0]).into(),
496        ])
497        .unwrap();
498
499        assert_dataframe_equal!(&df1, &df2);
500    }
501
502    #[test]
503    #[should_panic(expected = "value mismatch")]
504    fn test_dataframe_infinity_values_mismatch() {
505        let df1 = DataFrame::new(vec![
506            Series::new("col1".into(), &[1.0, f64::INFINITY, 3.0]).into(),
507        ])
508        .unwrap();
509
510        let df2 = DataFrame::new(vec![
511            Series::new("col1".into(), &[1.0, f64::NEG_INFINITY, 3.0]).into(),
512        ])
513        .unwrap();
514
515        assert_dataframe_equal!(&df1, &df2);
516    }
517
518    #[test]
519    fn test_dataframe_infinity_values_match() {
520        let df1 = DataFrame::new(vec![
521            Series::new("col1".into(), &[1.0, f64::INFINITY, 3.0]).into(),
522        ])
523        .unwrap();
524
525        let df2 = DataFrame::new(vec![
526            Series::new("col1".into(), &[1.0, f64::INFINITY, 3.0]).into(),
527        ])
528        .unwrap();
529
530        assert_dataframe_equal!(&df1, &df2);
531    }
532
533    // Testing categorical operations
534    #[test]
535    #[should_panic(expected = "data types do not match")]
536    fn test_dataframe_categorical_as_string_mismatch() {
537        enable_string_cache();
538
539        let mut categorical = Series::new("categories".into(), &["a", "b", "c", "a"]);
540        categorical = categorical
541            .cast(&DataType::Categorical(None, Default::default()))
542            .unwrap();
543
544        let df1 = DataFrame::new(vec![categorical.into()]).unwrap();
545
546        let df2 = DataFrame::new(vec![
547            Series::new("categories".into(), &["a", "b", "c", "a"]).into(),
548        ])
549        .unwrap();
550
551        assert_dataframe_equal!(&df1, &df2);
552
553        disable_string_cache();
554    }
555
556    #[test]
557    fn test_dataframe_categorical_as_string_match() {
558        enable_string_cache();
559
560        let mut categorical1 = Series::new("categories".into(), &["a", "b", "c", "a"]);
561        categorical1 = categorical1
562            .cast(&DataType::Categorical(None, Default::default()))
563            .unwrap();
564        let df1 = DataFrame::new(vec![categorical1.into()]).unwrap();
565
566        let mut categorical2 = Series::new("categories".into(), &["a", "b", "c", "a"]);
567        categorical2 = categorical2
568            .cast(&DataType::Categorical(None, Default::default()))
569            .unwrap();
570        let df2 = DataFrame::new(vec![categorical2.into()]).unwrap();
571
572        let options =
573            crate::asserts::DataFrameEqualOptions::default().with_categorical_as_str(true);
574        assert_dataframe_equal!(&df1, &df2, options);
575
576        disable_string_cache();
577    }
578
579    // Testing nested types
580    #[test]
581    #[should_panic(expected = "value mismatch")]
582    fn test_dataframe_nested_values_mismatch() {
583        let df1 = DataFrame::new(vec![
584            Series::new(
585                "list_col".into(),
586                &[
587                    Some(vec![1, 2, 3]),
588                    Some(vec![4, 5, 6]),
589                    None,
590                    Some(vec![7, 8, 9]),
591                ],
592            )
593            .into(),
594        ])
595        .unwrap();
596
597        let df2 = DataFrame::new(vec![
598            Series::new(
599                "list_col".into(),
600                &[
601                    Some(vec![1, 2, 3]),
602                    Some(vec![4, 5, 99]),
603                    None,
604                    Some(vec![7, 8, 9]),
605                ],
606            )
607            .into(),
608        ])
609        .unwrap();
610
611        assert_dataframe_equal!(&df1, &df2);
612    }
613
614    #[test]
615    fn test_dataframe_nested_values_match() {
616        let df1 = DataFrame::new(vec![
617            Series::new(
618                "list_col".into(),
619                &[Some(vec![1, 2, 3]), Some(vec![]), None, Some(vec![7, 8, 9])],
620            )
621            .into(),
622        ])
623        .unwrap();
624
625        let df2 = DataFrame::new(vec![
626            Series::new(
627                "list_col".into(),
628                &[Some(vec![1, 2, 3]), Some(vec![]), None, Some(vec![7, 8, 9])],
629            )
630            .into(),
631        ])
632        .unwrap();
633
634        assert_dataframe_equal!(&df1, &df2);
635    }
636}