Skip to content

Commit 81ef690

Browse files
committed
TST: Fix edge cases in assert_almost_equal() + tests #4398
Many of the edge cases were related to ordering of the items, but in some cases there were also issues with type checking. This fixes both of those issues and massively expands the testing for this function.
1 parent 3b50b52 commit 81ef690

File tree

2 files changed

+154
-10
lines changed

2 files changed

+154
-10
lines changed

pandas/src/testing.pyx

+62-6
Original file line numberDiff line numberDiff line change
@@ -3,16 +3,47 @@ import numpy as np
33
from pandas import compat
44
from pandas.core.common import isnull
55

6+
cdef NUMERIC_TYPES = (
7+
bool,
8+
int,
9+
float,
10+
np.bool,
11+
np.int8,
12+
np.int16,
13+
np.int32,
14+
np.int64,
15+
np.uint8,
16+
np.uint16,
17+
np.uint32,
18+
np.uint64,
19+
np.float16,
20+
np.float32,
21+
np.float64,
22+
)
23+
24+
cdef bint is_comparable_as_number(obj):
25+
return isinstance(obj, NUMERIC_TYPES)
26+
627
cdef bint isiterable(obj):
728
return hasattr(obj, '__iter__')
829

30+
cdef bint has_length(obj):
31+
return hasattr(obj, '__len__')
32+
33+
cdef bint is_dictlike(obj):
34+
return hasattr(obj, 'keys') and hasattr(obj, '__getitem__')
35+
936
cdef bint decimal_almost_equal(double desired, double actual, int decimal):
1037
# Code from
1138
# http://docs.scipy.org/doc/numpy/reference/generated
1239
# /numpy.testing.assert_almost_equal.html
1340
return abs(desired - actual) < (0.5 * 10.0 ** -decimal)
1441

1542
cpdef assert_dict_equal(a, b, bint compare_keys=True):
43+
assert is_dictlike(a) and is_dictlike(b), (
44+
"Cannot compare dict objects, one or both is not dict-like"
45+
)
46+
1647
a_keys = frozenset(a.keys())
1748
b_keys = frozenset(b.keys())
1849

@@ -33,14 +64,24 @@ cpdef assert_almost_equal(a, b, bint check_less_precise=False):
3364
if isinstance(a, dict) or isinstance(b, dict):
3465
return assert_dict_equal(a, b)
3566

36-
if isinstance(a, compat.string_types):
67+
if (isinstance(a, compat.string_types) or
68+
isinstance(b, compat.string_types)):
3769
assert a == b, "%r != %r" % (a, b)
3870
return True
3971

4072
if isiterable(a):
41-
assert isiterable(b), "First object is iterable, second isn't"
73+
assert isiterable(b), (
74+
"First object is iterable, second isn't: %r != %r" % (a, b)
75+
)
76+
assert has_length(a) and has_length(b), (
77+
"Can't compare objects without length, one or both is invalid: "
78+
"(%r, %r)" % (a, b)
79+
)
80+
4281
na, nb = len(a), len(b)
43-
assert na == nb, "%s != %s" % (na, nb)
82+
assert na == nb, (
83+
"Length of two iterators not the same: %r != %r" % (na, nb)
84+
)
4485
if (isinstance(a, np.ndarray) and
4586
isinstance(b, np.ndarray) and
4687
np.array_equal(a, b)):
@@ -49,12 +90,27 @@ cpdef assert_almost_equal(a, b, bint check_less_precise=False):
4990
for i in xrange(na):
5091
assert_almost_equal(a[i], b[i], check_less_precise)
5192
return True
93+
elif isiterable(b):
94+
assert False, (
95+
"Second object is iterable, first isn't: %r != %r" % (a, b)
96+
)
5297

5398
if isnull(a):
54-
assert isnull(b), "First object is null, second isn't"
99+
assert isnull(b), (
100+
"First object is null, second isn't: %r != %r" % (a, b)
101+
)
102+
return True
103+
elif isnull(b):
104+
assert isnull(a), (
105+
"First object is not null, second is null: %r != %r" % (a, b)
106+
)
55107
return True
56108

57-
if isinstance(a, (bool, float, int, np.float32)):
109+
if is_comparable_as_number(a):
110+
assert is_comparable_as_number(b), (
111+
"First object is numeric, second is not: %r != %r" % (a, b)
112+
)
113+
58114
decimal = 5
59115

60116
# deal with differing dtypes
@@ -81,6 +137,6 @@ cpdef assert_almost_equal(a, b, bint check_less_precise=False):
81137
assert False, 'expected %.5f but got %.5f' % (b, a)
82138

83139
else:
84-
assert a == b, "%s != %s" % (a, b)
140+
assert a == b, "%r != %r" % (a, b)
85141

86142
return True

pandas/tests/test_tests.py

+92-4
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import unittest
55
import warnings
66
import nose
7+
import numpy as np
78
import sys
89

910
from pandas.util.testing import (
@@ -12,12 +13,99 @@
1213

1314
# let's get meta.
1415

15-
class TestUtilTesting(unittest.TestCase):
16+
class TestAssertAlmostEqual(unittest.TestCase):
1617
_multiprocess_can_split_ = True
1718

18-
def test_assert_almost_equal(self):
19-
# don't die because values are not ndarrays
20-
assert_almost_equal(1.1,1.1,check_less_precise=True)
19+
def _assert_almost_equal_both(self, a, b, **kwargs):
20+
assert_almost_equal(a, b, **kwargs)
21+
assert_almost_equal(b, a, **kwargs)
22+
23+
def _assert_not_almost_equal_both(self, a, b, **kwargs):
24+
self.assertRaises(AssertionError, assert_almost_equal, a, b, **kwargs)
25+
self.assertRaises(AssertionError, assert_almost_equal, b, a, **kwargs)
26+
27+
def test_assert_almost_equal_numbers(self):
28+
self._assert_almost_equal_both(1.1, 1.1)
29+
self._assert_almost_equal_both(1.1, 1.100001)
30+
self._assert_almost_equal_both(np.int16(1), 1.000001)
31+
self._assert_almost_equal_both(np.float64(1.1), 1.1)
32+
self._assert_almost_equal_both(np.uint32(5), 5)
33+
34+
self._assert_not_almost_equal_both(1.1, 1)
35+
self._assert_not_almost_equal_both(1.1, True)
36+
self._assert_not_almost_equal_both(1, 2)
37+
self._assert_not_almost_equal_both(1.0001, np.int16(1))
38+
39+
def test_assert_almost_equal_numbers_with_zeros(self):
40+
self._assert_almost_equal_both(0, 0)
41+
self._assert_almost_equal_both(0.000001, 0)
42+
43+
self._assert_not_almost_equal_both(0.001, 0)
44+
self._assert_not_almost_equal_both(1, 0)
45+
46+
def test_assert_almost_equal_numbers_with_mixed(self):
47+
self._assert_not_almost_equal_both(1, 'abc')
48+
self._assert_not_almost_equal_both(1, [1,])
49+
self._assert_not_almost_equal_both(1, object())
50+
51+
def test_assert_almost_equal_dicts(self):
52+
self._assert_almost_equal_both({'a': 1, 'b': 2}, {'a': 1, 'b': 2})
53+
54+
self._assert_not_almost_equal_both({'a': 1, 'b': 2}, {'a': 1, 'b': 3})
55+
self._assert_not_almost_equal_both(
56+
{'a': 1, 'b': 2}, {'a': 1, 'b': 2, 'c': 3}
57+
)
58+
self._assert_not_almost_equal_both({'a': 1}, 1)
59+
self._assert_not_almost_equal_both({'a': 1}, 'abc')
60+
self._assert_not_almost_equal_both({'a': 1}, [1,])
61+
62+
def test_assert_almost_equal_dict_like_object(self):
63+
class DictLikeObj(object):
64+
def keys(self):
65+
return ('a',)
66+
67+
def __getitem__(self, item):
68+
if item == 'a':
69+
return 1
70+
71+
self._assert_almost_equal_both({'a': 1}, DictLikeObj())
72+
73+
self._assert_not_almost_equal_both({'a': 2}, DictLikeObj())
74+
75+
def test_assert_almost_equal_strings(self):
76+
self._assert_almost_equal_both('abc', 'abc')
77+
78+
self._assert_not_almost_equal_both('abc', 'abcd')
79+
self._assert_not_almost_equal_both('abc', 'abd')
80+
self._assert_not_almost_equal_both('abc', 1)
81+
self._assert_not_almost_equal_both('abc', [1,])
82+
83+
def test_assert_almost_equal_iterables(self):
84+
self._assert_almost_equal_both([1, 2, 3], [1, 2, 3])
85+
self._assert_almost_equal_both(np.array([1, 2, 3]), [1, 2, 3])
86+
87+
# Can't compare generators
88+
self._assert_not_almost_equal_both(iter([1, 2, 3]), [1, 2, 3])
89+
90+
self._assert_not_almost_equal_both([1, 2, 3], [1, 2, 4])
91+
self._assert_not_almost_equal_both([1, 2, 3], [1, 2, 3, 4])
92+
self._assert_not_almost_equal_both([1, 2, 3], 1)
93+
94+
def test_assert_almost_equal_null(self):
95+
self._assert_almost_equal_both(None, None)
96+
self._assert_almost_equal_both(None, np.NaN)
97+
98+
self._assert_not_almost_equal_both(None, 0)
99+
self._assert_not_almost_equal_both(np.NaN, 0)
100+
101+
def test_assert_almost_equal_inf(self):
102+
self._assert_almost_equal_both(np.inf, np.inf)
103+
self._assert_almost_equal_both(np.inf, float("inf"))
104+
105+
self._assert_not_almost_equal_both(np.inf, 0)
106+
107+
class TestUtilTesting(unittest.TestCase):
108+
_multiprocess_can_split_ = True
21109

22110
def test_raise_with_traceback(self):
23111
with assertRaisesRegexp(LookupError, "error_text"):

0 commit comments

Comments
 (0)