Skip to main content

polars_core/
testing.rs

1//! Testing utilities.
2
3use crate::prelude::*;
4
5impl Series {
6    /// Check if series are equal. Note that `None == None` evaluates to `false`
7    pub fn equals(&self, other: &Series) -> bool {
8        if self.null_count() > 0 || other.null_count() > 0 {
9            false
10        } else {
11            self.equals_missing(other)
12        }
13    }
14
15    /// Check if all values in series are equal where `None == None` evaluates to `true`.
16    pub fn equals_missing(&self, other: &Series) -> bool {
17        match (self.dtype(), other.dtype()) {
18            // Two [`Datetime`](DataType::Datetime) series are *not* equal if their timezones
19            // are different, regardless if they represent the same UTC time or not.
20            #[cfg(feature = "timezones")]
21            (DataType::Datetime(_, tz_lhs), DataType::Datetime(_, tz_rhs)) if tz_lhs != tz_rhs => {
22                return false;
23            },
24            _ => {},
25        }
26
27        // Differs from Partial::eq in that numerical dtype may be different
28        self.len() == other.len() && self.null_count() == other.null_count() && {
29            let eq = self.equal_missing(other);
30            match eq {
31                Ok(b) => b.all(),
32                Err(_) => false,
33            }
34        }
35    }
36}
37
38impl PartialEq for Series {
39    fn eq(&self, other: &Self) -> bool {
40        self.equals_missing(other)
41    }
42}
43
44impl DataFrame {
45    /// Check if [`DataFrame`]' schemas are equal.
46    pub fn schema_equal(&self, other: &DataFrame) -> PolarsResult<()> {
47        for (lhs, rhs) in self.columns().iter().zip(other.columns().iter()) {
48            polars_ensure!(
49                lhs.name() == rhs.name(),
50                SchemaMismatch: "column name mismatch: left-hand = '{}', right-hand = '{}'",
51                lhs.name(), rhs.name()
52            );
53            polars_ensure!(
54                lhs.dtype() == rhs.dtype(),
55                SchemaMismatch: "column datatype mismatch: left-hand = '{}', right-hand = '{}'",
56                lhs.dtype(), rhs.dtype()
57            );
58        }
59        Ok(())
60    }
61
62    /// Check if [`DataFrame`]s are equal. Note that `None == None` evaluates to `false`
63    ///
64    /// # Example
65    ///
66    /// ```rust
67    /// # use polars_core::prelude::*;
68    /// let df1: DataFrame = df!("Atomic number" => &[1, 51, 300],
69    ///                         "Element" => &[Some("Hydrogen"), Some("Antimony"), None])?;
70    /// let df2: DataFrame = df!("Atomic number" => &[1, 51, 300],
71    ///                         "Element" => &[Some("Hydrogen"), Some("Antimony"), None])?;
72    ///
73    /// assert!(!df1.equals(&df2));
74    /// # Ok::<(), PolarsError>(())
75    /// ```
76    pub fn equals(&self, other: &DataFrame) -> bool {
77        if self.shape() != other.shape() {
78            return false;
79        }
80        for (left, right) in self.columns().iter().zip(other.columns()) {
81            if left.name() != right.name() || !left.equals(right) {
82                return false;
83            }
84        }
85        true
86    }
87
88    /// Check if all values in [`DataFrame`]s are equal where `None == None` evaluates to `true`.
89    ///
90    /// # Example
91    ///
92    /// ```rust
93    /// # use polars_core::prelude::*;
94    /// let df1: DataFrame = df!("Atomic number" => &[1, 51, 300],
95    ///                         "Element" => &[Some("Hydrogen"), Some("Antimony"), None])?;
96    /// let df2: DataFrame = df!("Atomic number" => &[1, 51, 300],
97    ///                         "Element" => &[Some("Hydrogen"), Some("Antimony"), None])?;
98    ///
99    /// assert!(df1.equals_missing(&df2));
100    /// # Ok::<(), PolarsError>(())
101    /// ```
102    pub fn equals_missing(&self, other: &DataFrame) -> bool {
103        if self.shape() != other.shape() {
104            return false;
105        }
106        for (left, right) in self.columns().iter().zip(other.columns()) {
107            if left.name() != right.name() || !left.equals_missing(right) {
108                return false;
109            }
110        }
111        true
112    }
113}
114
115impl PartialEq for DataFrame {
116    fn eq(&self, other: &Self) -> bool {
117        self.shape() == other.shape()
118            && self
119                .columns()
120                .iter()
121                .zip(other.columns().iter())
122                .all(|(s1, s2)| s1.equals_missing(s2))
123    }
124}
125
126/// Asserts that two expressions of type [`DataFrame`] are equal according to [`DataFrame::equals`]
127/// at runtime.
128///
129/// If the expression are not equal, the program will panic with a message that displays
130/// both dataframes.
131#[macro_export]
132macro_rules! assert_df_eq {
133    ($a:expr, $b:expr $(,)?) => {
134        let a: &$crate::frame::DataFrame = &$a;
135        let b: &$crate::frame::DataFrame = &$b;
136        assert!(a.equals(b), "expected {:?}\nto equal {:?}", a, b);
137    };
138}
139
140#[cfg(test)]
141mod test {
142    use crate::prelude::*;
143
144    #[test]
145    fn test_series_equals() {
146        let a = Series::new("a".into(), &[1_u32, 2, 3]);
147        let b = Series::new("a".into(), &[1_u32, 2, 3]);
148        assert!(a.equals(&b));
149
150        let s = Series::new("foo".into(), &[None, Some(1i64)]);
151        assert!(s.equals_missing(&s));
152    }
153
154    #[test]
155    fn test_series_dtype_not_equal() {
156        let s_i32 = Series::new("a".into(), &[1_i32, 2_i32]);
157        let s_i64 = Series::new("a".into(), &[1_i64, 2_i64]);
158        assert!(s_i32.dtype() != s_i64.dtype());
159        assert!(s_i32.equals(&s_i64));
160    }
161
162    #[test]
163    fn test_df_equal() {
164        let a = Column::new("a".into(), [1, 2, 3].as_ref());
165        let b = Column::new("b".into(), [1, 2, 3].as_ref());
166
167        let df1 = DataFrame::new_infer_height(vec![a, b]).unwrap();
168        assert!(df1.equals(&df1))
169    }
170
171    #[test]
172    fn assert_df_eq_passes() {
173        let df = df!("a" => [1], "b" => [2]).unwrap();
174        assert_df_eq!(df, df);
175        drop(df); // Ensure `assert_df_eq!` does not consume its arguments.
176    }
177
178    #[test]
179    #[should_panic(expected = "to equal")]
180    fn assert_df_eq_panics() {
181        assert_df_eq!(df!("a" => [1]).unwrap(), df!("a" => [2]).unwrap(),);
182    }
183
184    #[test]
185    fn test_df_partialeq() {
186        let df1 = df!("a" => &[1, 2, 3],
187                      "b" => &[4, 5, 6])
188        .unwrap();
189        let df2 = df!("b" => &[4, 5, 6],
190                      "a" => &[1, 2, 3])
191        .unwrap();
192        let df3 = df!("" => &[Some(1), None]).unwrap();
193        let df4 = df!("" => &[f32::NAN]).unwrap();
194
195        assert_eq!(df1, df1);
196        assert_ne!(df1, df2);
197        assert_eq!(df2, df2);
198        assert_ne!(df2, df3);
199        assert_eq!(df3, df3);
200        assert_eq!(df4, df4);
201    }
202}