polars_testing/asserts/
series.rs

1/// Asserts that two series are equal according to the specified options.
2///
3/// This macro compares two Polars Series 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_series_equal;
13/// use polars_testing::asserts::SeriesEqualOptions;
14///
15/// // Create two series to compare
16/// let s1 = Series::new("a".into(), &[1, 2, 3]);
17/// let s2 = Series::new("a".into(), &[1, 2, 3]);
18///
19/// // Assert with default options
20/// assert_series_equal!(&s1, &s2);
21///
22/// // Assert with custom options
23/// let options = SeriesEqualOptions::default()
24///     .with_check_exact(true)
25///     .with_check_dtypes(false);
26/// assert_series_equal!(&s1, &s2, options);
27/// ```
28///
29/// # Panics
30///
31/// Panics when the series are not equal according to the specified comparison criteria.
32///
33#[macro_export]
34macro_rules! assert_series_equal {
35    ($left:expr, $right:expr $(, $options:expr)?) => {
36        {
37            #[allow(unused_assignments)]
38            #[allow(unused_mut)]
39            let mut options = $crate::asserts::SeriesEqualOptions::default();
40            $(options = $options;)?
41
42            match $crate::asserts::assert_series_equal($left, $right, options) {
43                Ok(_) => {},
44                Err(e) => panic!("{}", e),
45            }
46        }
47    };
48}
49
50#[cfg(test)]
51mod tests {
52    use polars_core::prelude::*;
53    use polars_core::{disable_string_cache, enable_string_cache};
54
55    // Testing default struct implementation
56    #[test]
57    fn test_series_equal_options() {
58        let options = crate::asserts::SeriesEqualOptions::default();
59
60        assert!(options.check_dtypes);
61        assert!(options.check_names);
62        assert!(options.check_order);
63        assert!(options.check_exact);
64        assert_eq!(options.rtol, 1e-5);
65        assert_eq!(options.atol, 1e-8);
66        assert!(!options.categorical_as_str);
67    }
68
69    // Testing with basic parameters
70    #[test]
71    #[should_panic(expected = "length mismatch")]
72    fn test_series_length_mismatch() {
73        let s1 = Series::new("".into(), &[1, 2]);
74        let s2 = Series::new("".into(), &[1, 2, 3]);
75
76        assert_series_equal!(&s1, &s2);
77    }
78
79    #[test]
80    #[should_panic(expected = "name mismatch")]
81    fn test_series_names_mismatch() {
82        let s1 = Series::new("s1".into(), &[1, 2, 3]);
83        let s2 = Series::new("s2".into(), &[1, 2, 3]);
84
85        assert_series_equal!(&s1, &s2);
86    }
87
88    #[test]
89    fn test_series_check_names_false() {
90        let s1 = Series::new("s1".into(), &[1, 2, 3]);
91        let s2 = Series::new("s2".into(), &[1, 2, 3]);
92
93        let options = crate::asserts::SeriesEqualOptions::default().with_check_names(false);
94
95        assert_series_equal!(&s1, &s2, options);
96    }
97
98    #[test]
99    #[should_panic(expected = "dtype mismatch")]
100    fn test_series_dtype_mismatch() {
101        let s1 = Series::new("".into(), &[1, 2, 3]);
102        let s2 = Series::new("".into(), &["1", "2", "3"]);
103
104        assert_series_equal!(&s1, &s2);
105    }
106
107    #[test]
108    fn test_series_check_dtypes_false() {
109        let s1 = Series::new("s1".into(), &[1, 2, 3]);
110        let s2 = Series::new("s1".into(), &[1.0, 2.0, 3.0]);
111
112        let options = crate::asserts::SeriesEqualOptions::default().with_check_dtypes(false);
113
114        assert_series_equal!(&s1, &s2, options);
115    }
116
117    // Testing basic equality
118    #[test]
119    #[should_panic(expected = "exact value mismatch")]
120    fn test_series_value_mismatch_int() {
121        let s1 = Series::new("".into(), &[1, 2, 3]);
122        let s2 = Series::new("".into(), &[2, 3, 4]);
123
124        assert_series_equal!(&s1, &s2);
125    }
126
127    #[test]
128    fn test_series_values_match_int() {
129        let s1 = Series::new("".into(), &[1, 2, 3]);
130        let s2 = Series::new("".into(), &[1, 2, 3]);
131
132        assert_series_equal!(&s1, &s2);
133    }
134
135    #[test]
136    #[should_panic(expected = "exact value mismatch")]
137    fn test_series_value_mismatch_str() {
138        let s1 = Series::new("".into(), &["foo", "bar"]);
139        let s2 = Series::new("".into(), &["moo", "car"]);
140
141        assert_series_equal!(&s1, &s2);
142    }
143
144    #[test]
145    fn test_series_values_match_str() {
146        let s1 = Series::new("".into(), &["foo", "bar"]);
147        let s2 = Series::new("".into(), &["foo", "bar"]);
148
149        assert_series_equal!(&s1, &s2);
150    }
151
152    #[test]
153    #[should_panic(expected = "exact value mismatch")]
154    fn test_series_values_mismatch_float() {
155        let s1 = Series::new("".into(), &[1.1, 2.2, 3.3]);
156        let s2 = Series::new("".into(), &[2.2, 3.3, 4.4]);
157
158        assert_series_equal!(&s1, &s2);
159    }
160
161    #[test]
162    fn test_series_values_match_float() {
163        let s1 = Series::new("".into(), &[1.1, 2.2, 3.3]);
164        let s2 = Series::new("".into(), &[1.1, 2.2, 3.3]);
165
166        assert_series_equal!(&s1, &s2);
167    }
168
169    // Testing float value precision equality
170    #[test]
171    #[should_panic(expected = "values not within tolerance")]
172    fn test_series_float_exceeded_tol() {
173        let s1 = Series::new("".into(), &[1.0, 2.2, 3.3]);
174        let s2 = Series::new("".into(), &[1.00012, 2.200025, 3.300035]);
175
176        let options = crate::asserts::SeriesEqualOptions::default().with_check_exact(false);
177
178        assert_series_equal!(&s1, &s2, options);
179    }
180
181    #[test]
182    fn test_series_float_within_tol() {
183        let s1 = Series::new("".into(), &[1.0, 2.0, 3.0]);
184        let s2 = Series::new("".into(), &[1.000005, 2.000015, 3.000025]);
185
186        let options = crate::asserts::SeriesEqualOptions::default().with_check_exact(false);
187
188        assert_series_equal!(&s1, &s2, options);
189    }
190
191    #[test]
192    fn test_series_float_exact_tolerance_boundary() {
193        let s1 = Series::new("".into(), &[1.0, 2.0, 3.0]);
194        let s2 = Series::new("".into(), &[1.0, 2.0 + 1e-5, 3.0]);
195
196        let options = crate::asserts::SeriesEqualOptions::default().with_check_exact(false);
197
198        assert_series_equal!(&s1, &s2, options);
199    }
200
201    #[test]
202    fn test_series_float_custom_rtol() {
203        let s1 = Series::new("".into(), &[10.0, 100.0, 1000.0]);
204        let s2 = Series::new("".into(), &[10.05, 100.1, 1000.2]);
205
206        let options = crate::asserts::SeriesEqualOptions::default()
207            .with_check_exact(false)
208            .with_rtol(0.01);
209
210        assert_series_equal!(&s1, &s2, options);
211    }
212
213    #[test]
214    #[should_panic(expected = "values not within tolerance")]
215    fn test_series_float_custom_atol() {
216        let s1 = Series::new("".into(), &[0.001, 0.01, 0.1]);
217        let s2 = Series::new("".into(), &[0.001, 0.02, 0.1]);
218
219        let options = crate::asserts::SeriesEqualOptions::default()
220            .with_check_exact(false)
221            .with_atol(0.005);
222
223        assert_series_equal!(&s1, &s2, options);
224    }
225
226    // Testing equality with special values
227    #[test]
228    fn test_series_empty_equal() {
229        let s1 = Series::default();
230        let s2 = Series::default();
231
232        assert_series_equal!(&s1, &s2);
233    }
234
235    #[test]
236    fn test_series_nan_equal() {
237        let s1 = Series::new("".into(), &[f64::NAN, f64::NAN, f64::NAN]);
238        let s2 = Series::new("".into(), &[f64::NAN, f64::NAN, f64::NAN]);
239
240        assert_series_equal!(&s1, &s2);
241    }
242
243    #[test]
244    fn test_series_null_equal() {
245        let s1 = Series::new("".into(), &[None::<i32>, None::<i32>, None::<i32>]);
246        let s2 = Series::new("".into(), &[None::<i32>, None::<i32>, None::<i32>]);
247
248        assert_series_equal!(&s1, &s2);
249    }
250
251    #[test]
252    #[should_panic(expected = "exact value mismatch")]
253    fn test_series_infinity_values_mismatch() {
254        let s1 = Series::new("".into(), &[1.0, f64::INFINITY, 3.0]);
255        let s2 = Series::new("".into(), &[1.0, f64::NEG_INFINITY, 3.0]);
256
257        assert_series_equal!(&s1, &s2);
258    }
259
260    #[test]
261    fn test_series_infinity_values_match() {
262        let s1 = Series::new("".into(), &[1.0, f64::INFINITY, f64::NEG_INFINITY]);
263        let s2 = Series::new("".into(), &[1.0, f64::INFINITY, f64::NEG_INFINITY]);
264
265        assert_series_equal!(&s1, &s2);
266    }
267
268    // Testing null and nan counts for floats
269    #[test]
270    #[should_panic(expected = "null value mismatch")]
271    fn test_series_check_exact_false_null() {
272        let s1 = Series::new("".into(), &[Some(1.0), None::<f64>, Some(3.0)]);
273        let s2 = Series::new("".into(), &[Some(1.0), Some(2.0), Some(3.0)]);
274
275        let options = crate::asserts::SeriesEqualOptions::default().with_check_exact(false);
276
277        assert_series_equal!(&s1, &s2, options);
278    }
279
280    #[test]
281    #[should_panic(expected = "nan value mismatch")]
282    fn test_series_check_exact_false_nan() {
283        let s1 = Series::new("".into(), &[1.0, f64::NAN, 3.0]);
284        let s2 = Series::new("".into(), &[1.0, 2.0, 3.0]);
285
286        let options = crate::asserts::SeriesEqualOptions::default().with_check_exact(false);
287
288        assert_series_equal!(&s1, &s2, options);
289    }
290
291    // Testing sorting operations
292    #[test]
293    #[should_panic(expected = "exact value mismatch")]
294    fn test_series_sorting_unequal() {
295        let s1 = Series::new("".into(), &[Some(1), Some(2), Some(3), None::<i32>]);
296        let s2 = Series::new("".into(), &[Some(2), None::<i32>, Some(3), Some(1)]);
297
298        let options = crate::asserts::SeriesEqualOptions::default();
299
300        assert_series_equal!(&s1, &s2, options);
301    }
302
303    #[test]
304    fn test_series_sorting_equal() {
305        let s1 = Series::new("".into(), &[Some(1), Some(2), Some(3), None::<i32>]);
306        let s2 = Series::new("".into(), &[Some(2), None::<i32>, Some(3), Some(1)]);
307
308        let options = crate::asserts::SeriesEqualOptions::default().with_check_order(false);
309
310        assert_series_equal!(&s1, &s2, options);
311    }
312
313    // Testing categorical operations
314    #[test]
315    #[should_panic(expected = "exact value mismatch")]
316    fn test_series_categorical_mismatch() {
317        enable_string_cache();
318
319        let s1 = Series::new("".into(), &["apple", "banana", "cherry"])
320            .cast(&DataType::Categorical(None, Default::default()))
321            .unwrap();
322        let s2 = Series::new("".into(), &["apple", "orange", "cherry"])
323            .cast(&DataType::Categorical(None, Default::default()))
324            .unwrap();
325
326        assert_series_equal!(&s1, &s2);
327
328        disable_string_cache();
329    }
330
331    #[test]
332    fn test_series_categorical_match() {
333        let s1 = Series::new("".into(), &["apple", "banana", "cherry"])
334            .cast(&DataType::Categorical(None, Default::default()))
335            .unwrap();
336        let s2 = Series::new("".into(), &["apple", "banana", "cherry"])
337            .cast(&DataType::Categorical(None, Default::default()))
338            .unwrap();
339
340        assert_series_equal!(&s1, &s2);
341    }
342
343    #[test]
344    #[should_panic(expected = "exact value mismatch")]
345    fn test_series_categorical_str_mismatch() {
346        let s1 = Series::new("".into(), &["apple", "banana", "cherry"])
347            .cast(&DataType::Categorical(None, Default::default()))
348            .unwrap();
349        let s2 = Series::new("".into(), &["apple", "orange", "cherry"])
350            .cast(&DataType::Categorical(None, Default::default()))
351            .unwrap();
352
353        let options = crate::asserts::SeriesEqualOptions::default().with_categorical_as_str(true);
354
355        assert_series_equal!(&s1, &s2, options);
356    }
357
358    #[test]
359    fn test_series_categorical_str_match() {
360        let s1 = Series::new("".into(), &["apple", "banana", "cherry"])
361            .cast(&DataType::Categorical(None, Default::default()))
362            .unwrap();
363        let s2 = Series::new("".into(), &["apple", "banana", "cherry"])
364            .cast(&DataType::Categorical(None, Default::default()))
365            .unwrap();
366
367        let options = crate::asserts::SeriesEqualOptions::default().with_categorical_as_str(true);
368
369        assert_series_equal!(&s1, &s2, options);
370    }
371
372    // Testing equality of nested values
373    #[test]
374    #[should_panic(expected = "exact value mismatch")]
375    fn test_series_list_values_int_mismatch() {
376        let s1 = Series::new(
377            "".into(),
378            &[
379                [1, 2, 3].iter().collect::<Series>(),
380                [4, 5, 6].iter().collect::<Series>(),
381                [7, 8, 9].iter().collect::<Series>(),
382            ],
383        );
384
385        let s2 = Series::new(
386            "".into(),
387            &[
388                [0, 2, 3].iter().collect::<Series>(),
389                [4, 7, 6].iter().collect::<Series>(),
390                [7, 8, 10].iter().collect::<Series>(),
391            ],
392        );
393
394        assert_series_equal!(&s1, &s2);
395    }
396
397    #[test]
398    fn test_series_list_values_int_match() {
399        let s1 = Series::new(
400            "".into(),
401            &[
402                [1, 2, 3].iter().collect::<Series>(),
403                [4, 5, 6].iter().collect::<Series>(),
404                [7, 8, 9].iter().collect::<Series>(),
405            ],
406        );
407
408        let s2 = Series::new(
409            "".into(),
410            &[
411                [1, 2, 3].iter().collect::<Series>(),
412                [4, 5, 6].iter().collect::<Series>(),
413                [7, 8, 9].iter().collect::<Series>(),
414            ],
415        );
416
417        assert_series_equal!(&s1, &s2);
418    }
419
420    #[test]
421    #[should_panic(expected = "nested value mismatch")]
422    fn test_series_list_values_float_mismatch() {
423        let s1 = Series::new(
424            "".into(),
425            &[
426                [1.1, 2.0, 3.0].iter().collect::<Series>(),
427                [4.0, 5.0, 6.0].iter().collect::<Series>(),
428                [7.0, 8.0, 9.0].iter().collect::<Series>(),
429            ],
430        );
431
432        let s2 = Series::new(
433            "".into(),
434            &[
435                [0.5, 2.0, 3.0].iter().collect::<Series>(),
436                [4.0, 7.5, 6.0].iter().collect::<Series>(),
437                [7.0, 8.0, 10.2].iter().collect::<Series>(),
438            ],
439        );
440
441        assert_series_equal!(&s1, &s2);
442    }
443
444    #[test]
445    fn test_series_list_values_float_match() {
446        let s1 = Series::new(
447            "".into(),
448            &[
449                [1.1, 2.0, 3.0].iter().collect::<Series>(),
450                [4.0, 5.0, 6.0].iter().collect::<Series>(),
451                [7.0, 8.0, 9.0].iter().collect::<Series>(),
452            ],
453        );
454
455        let s2 = Series::new(
456            "".into(),
457            &[
458                [1.1, 2.0, 3.0].iter().collect::<Series>(),
459                [4.0, 5.0, 6.0].iter().collect::<Series>(),
460                [7.0, 8.0, 9.0].iter().collect::<Series>(),
461            ],
462        );
463
464        assert_series_equal!(&s1, &s2);
465    }
466
467    #[test]
468    #[should_panic(expected = "exact value mismatch")]
469    fn test_series_struct_values_str_mismatch() {
470        let field1 = Series::new("field1".into(), &["a", "d", "g"]);
471        let field2 = Series::new("field2".into(), &["b", "e", "h"]);
472
473        let s1_fields = [field1.clone(), field2.clone()];
474        let s1_struct =
475            StructChunked::from_series("".into(), field1.len(), s1_fields.iter()).unwrap();
476        let s1 = s1_struct.into_series();
477
478        let field1_alt = Series::new("field1".into(), &["a", "DIFFERENT", "g"]);
479        let field2_alt = Series::new("field2".into(), &["b", "e", "h"]);
480
481        let s2_fields = [field1_alt.clone(), field2_alt.clone()];
482        let s2_struct =
483            StructChunked::from_series("".into(), field1_alt.len(), s2_fields.iter()).unwrap();
484        let s2 = s2_struct.into_series();
485
486        assert_series_equal!(&s1, &s2);
487    }
488
489    #[test]
490    fn test_series_struct_values_str_match() {
491        let field1 = Series::new("field1".into(), &["a", "d", "g"]);
492        let field2 = Series::new("field2".into(), &["b", "e", "h"]);
493
494        let s1_fields = [field1.clone(), field2.clone()];
495        let s1_struct =
496            StructChunked::from_series("".into(), field1.len(), s1_fields.iter()).unwrap();
497        let s1 = s1_struct.into_series();
498
499        let s2_fields = [field1.clone(), field2.clone()];
500        let s2_struct =
501            StructChunked::from_series("".into(), field1.len(), s2_fields.iter()).unwrap();
502        let s2 = s2_struct.into_series();
503
504        assert_series_equal!(&s1, &s2);
505    }
506
507    #[test]
508    #[should_panic(expected = "exact value mismatch")]
509    fn test_series_struct_values_mixed_mismatch() {
510        let id = Series::new("id".into(), &[1, 2, 3]);
511        let value = Series::new("value".into(), &["a", "b", "c"]);
512        let active = Series::new("active".into(), &[true, false, true]);
513
514        let s1_fields = [id.clone(), value.clone(), active.clone()];
515        let s1_struct = StructChunked::from_series("".into(), id.len(), s1_fields.iter()).unwrap();
516        let s1 = s1_struct.into_series();
517
518        let id_alt = Series::new("id".into(), &[1, 99, 3]);
519        let s2_fields = [id_alt.clone(), value.clone(), active.clone()];
520        let s2_struct = StructChunked::from_series("".into(), id.len(), s2_fields.iter()).unwrap();
521        let s2 = s2_struct.into_series();
522
523        assert_series_equal!(&s1, &s2);
524    }
525
526    #[test]
527    fn test_series_struct_values_mixed_match() {
528        let id = Series::new("id".into(), &[1, 2, 3]);
529        let value = Series::new("value".into(), &["a", "b", "c"]);
530        let active = Series::new("active".into(), &[true, false, true]);
531
532        let s1_fields = [id.clone(), value.clone(), active.clone()];
533        let s1_struct = StructChunked::from_series("".into(), id.len(), s1_fields.iter()).unwrap();
534        let s1 = s1_struct.into_series();
535
536        let s2_fields = [id.clone(), value.clone(), active.clone()];
537        let s2_struct = StructChunked::from_series("".into(), id.len(), s2_fields.iter()).unwrap();
538        let s2 = s2_struct.into_series();
539
540        assert_series_equal!(&s1, &s2);
541    }
542
543    // Testing equality of deeply nested values
544    #[test]
545    #[should_panic(expected = "nested value mismatch")]
546    fn test_deeply_nested_list_float_mismatch() {
547        let inner_list_1 = Series::new("inner".into(), &[1.0, 2.0]);
548        let outer_list_1 = Series::new("outer".into(), &[inner_list_1]);
549        let s1 = Series::new("nested".into(), &[outer_list_1]);
550
551        let inner_list_2 = Series::new("inner".into(), &[1.0, 3.0]);
552        let outer_list_2 = Series::new("outer".into(), &[inner_list_2]);
553        let s2 = Series::new("nested".into(), &[outer_list_2]);
554
555        assert_series_equal!(&s1, &s2);
556    }
557    #[test]
558    fn test_deeply_nested_list_float_match() {
559        let inner_list_1 = Series::new("".into(), &[1.0, 2.0]);
560        let outer_list_1 = Series::new("".into(), &[inner_list_1]);
561
562        let s1 = Series::new("".into(), &[outer_list_1]);
563
564        let inner_list_2 = Series::new("".into(), &[1.0, 2.0]);
565        let outer_list_2 = Series::new("".into(), &[inner_list_2]);
566        let s2 = Series::new("".into(), &[outer_list_2]);
567
568        assert_series_equal!(&s1, &s2);
569    }
570
571    // Testing equality of temporal types
572    #[test]
573    #[should_panic(expected = "exact value mismatch")]
574    fn test_series_datetime_values_mismatch() {
575        let dt1: i64 = 1672567200000000000;
576        let dt2: i64 = 1672653600000000000;
577        let dt3: i64 = 1672657200000000000;
578
579        let s1 = Series::new("".into(), &[dt1, dt2])
580            .cast(&DataType::Datetime(TimeUnit::Nanoseconds, None))
581            .unwrap();
582        let s2 = Series::new("".into(), &[dt1, dt3])
583            .cast(&DataType::Datetime(TimeUnit::Nanoseconds, None))
584            .unwrap();
585
586        assert_series_equal!(&s1, &s2);
587    }
588
589    #[test]
590    fn test_series_datetime_values_match() {
591        let dt1: i64 = 1672567200000000000;
592        let dt2: i64 = 1672653600000000000;
593
594        let s1 = Series::new("".into(), &[dt1, dt2])
595            .cast(&DataType::Datetime(TimeUnit::Nanoseconds, None))
596            .unwrap();
597        let s2 = Series::new("".into(), &[dt1, dt2])
598            .cast(&DataType::Datetime(TimeUnit::Nanoseconds, None))
599            .unwrap();
600
601        assert_series_equal!(&s1, &s2);
602    }
603
604    // Testing equality of decimal types
605    #[test]
606    #[should_panic(expected = "exact value mismatch")]
607    fn test_series_decimal_values_mismatch() {
608        let s1 = Series::new("".into(), &[1, 2])
609            .cast(&DataType::Decimal(Some(10), Some(2)))
610            .unwrap();
611        let s2 = Series::new("".into(), &[1, 3])
612            .cast(&DataType::Decimal(Some(10), Some(2)))
613            .unwrap();
614
615        assert_series_equal!(&s1, &s2);
616    }
617
618    #[test]
619    fn test_series_decimal_values_match() {
620        let s1 = Series::new("".into(), &[1, 2])
621            .cast(&DataType::Decimal(Some(10), Some(2)))
622            .unwrap();
623        let s2 = Series::new("".into(), &[1, 2])
624            .cast(&DataType::Decimal(Some(10), Some(2)))
625            .unwrap();
626
627        assert_series_equal!(&s1, &s2);
628    }
629
630    // Testing equality of binary types
631    #[test]
632    #[should_panic(expected = "exact value mismatch")]
633    fn test_series_binary_values_mismatch() {
634        let s1 = Series::new("".into(), &[vec![1u8, 2, 3], vec![4, 5, 6]])
635            .cast(&DataType::Binary)
636            .unwrap();
637        let s2 = Series::new("".into(), &[vec![1u8, 2, 3], vec![4, 5, 7]])
638            .cast(&DataType::Binary)
639            .unwrap();
640
641        assert_series_equal!(&s1, &s2);
642    }
643
644    #[test]
645    fn test_series_binary_values_match() {
646        let s1 = Series::new("".into(), &[vec![1u8, 2, 3], vec![4, 5, 6]])
647            .cast(&DataType::Binary)
648            .unwrap();
649        let s2 = Series::new("".into(), &[vec![1u8, 2, 3], vec![4, 5, 6]])
650            .cast(&DataType::Binary)
651            .unwrap();
652
653        assert_series_equal!(&s1, &s2);
654    }
655}