Skip to main content

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