1# encoding: utf-8 2import operator 3import sys 4import pytest 5import doctest 6 7from pytest import approx 8from operator import eq, ne 9from decimal import Decimal 10from fractions import Fraction 11 12inf, nan = float("inf"), float("nan") 13 14 15class MyDocTestRunner(doctest.DocTestRunner): 16 17 def __init__(self): 18 doctest.DocTestRunner.__init__(self) 19 20 def report_failure(self, out, test, example, got): 21 raise AssertionError( 22 "'{}' evaluates to '{}', not '{}'".format( 23 example.source.strip(), got.strip(), example.want.strip() 24 ) 25 ) 26 27 28class TestApprox(object): 29 30 def test_repr_string(self): 31 plus_minus = u"\u00b1" if sys.version_info[0] > 2 else u"+-" 32 tol1, tol2, infr = "1.0e-06", "2.0e-06", "inf" 33 assert repr(approx(1.0)) == "1.0 {pm} {tol1}".format(pm=plus_minus, tol1=tol1) 34 assert ( 35 repr(approx([1.0, 2.0])) 36 == "approx([1.0 {pm} {tol1}, 2.0 {pm} {tol2}])".format( 37 pm=plus_minus, tol1=tol1, tol2=tol2 38 ) 39 ) 40 assert ( 41 repr(approx((1.0, 2.0))) 42 == "approx((1.0 {pm} {tol1}, 2.0 {pm} {tol2}))".format( 43 pm=plus_minus, tol1=tol1, tol2=tol2 44 ) 45 ) 46 assert repr(approx(inf)) == "inf" 47 assert repr(approx(1.0, rel=nan)) == "1.0 {pm} ???".format(pm=plus_minus) 48 assert ( 49 repr(approx(1.0, rel=inf)) 50 == "1.0 {pm} {infr}".format(pm=plus_minus, infr=infr) 51 ) 52 assert repr(approx(1.0j, rel=inf)) == "1j" 53 54 # Dictionaries aren't ordered, so we need to check both orders. 55 assert repr(approx({"a": 1.0, "b": 2.0})) in ( 56 "approx({{'a': 1.0 {pm} {tol1}, 'b': 2.0 {pm} {tol2}}})".format( 57 pm=plus_minus, tol1=tol1, tol2=tol2 58 ), 59 "approx({{'b': 2.0 {pm} {tol2}, 'a': 1.0 {pm} {tol1}}})".format( 60 pm=plus_minus, tol1=tol1, tol2=tol2 61 ), 62 ) 63 64 def test_operator_overloading(self): 65 assert 1 == approx(1, rel=1e-6, abs=1e-12) 66 assert not (1 != approx(1, rel=1e-6, abs=1e-12)) 67 assert 10 != approx(1, rel=1e-6, abs=1e-12) 68 assert not (10 == approx(1, rel=1e-6, abs=1e-12)) 69 70 def test_exactly_equal(self): 71 examples = [ 72 (2.0, 2.0), 73 (0.1e200, 0.1e200), 74 (1.123e-300, 1.123e-300), 75 (12345, 12345.0), 76 (0.0, -0.0), 77 (345678, 345678), 78 (Decimal("1.0001"), Decimal("1.0001")), 79 (Fraction(1, 3), Fraction(-1, -3)), 80 ] 81 for a, x in examples: 82 assert a == approx(x) 83 84 def test_opposite_sign(self): 85 examples = [(eq, 1e-100, -1e-100), (ne, 1e100, -1e100)] 86 for op, a, x in examples: 87 assert op(a, approx(x)) 88 89 def test_zero_tolerance(self): 90 within_1e10 = [(1.1e-100, 1e-100), (-1.1e-100, -1e-100)] 91 for a, x in within_1e10: 92 assert x == approx(x, rel=0.0, abs=0.0) 93 assert a != approx(x, rel=0.0, abs=0.0) 94 assert a == approx(x, rel=0.0, abs=5e-101) 95 assert a != approx(x, rel=0.0, abs=5e-102) 96 assert a == approx(x, rel=5e-1, abs=0.0) 97 assert a != approx(x, rel=5e-2, abs=0.0) 98 99 def test_negative_tolerance(self): 100 # Negative tolerances are not allowed. 101 illegal_kwargs = [ 102 dict(rel=-1e100), 103 dict(abs=-1e100), 104 dict(rel=1e100, abs=-1e100), 105 dict(rel=-1e100, abs=1e100), 106 dict(rel=-1e100, abs=-1e100), 107 ] 108 for kwargs in illegal_kwargs: 109 with pytest.raises(ValueError): 110 1.1 == approx(1, **kwargs) 111 112 def test_inf_tolerance(self): 113 # Everything should be equal if the tolerance is infinite. 114 large_diffs = [(1, 1000), (1e-50, 1e50), (-1.0, -1e300), (0.0, 10)] 115 for a, x in large_diffs: 116 assert a != approx(x, rel=0.0, abs=0.0) 117 assert a == approx(x, rel=inf, abs=0.0) 118 assert a == approx(x, rel=0.0, abs=inf) 119 assert a == approx(x, rel=inf, abs=inf) 120 121 def test_inf_tolerance_expecting_zero(self): 122 # If the relative tolerance is zero but the expected value is infinite, 123 # the actual tolerance is a NaN, which should be an error. 124 illegal_kwargs = [dict(rel=inf, abs=0.0), dict(rel=inf, abs=inf)] 125 for kwargs in illegal_kwargs: 126 with pytest.raises(ValueError): 127 1 == approx(0, **kwargs) 128 129 def test_nan_tolerance(self): 130 illegal_kwargs = [dict(rel=nan), dict(abs=nan), dict(rel=nan, abs=nan)] 131 for kwargs in illegal_kwargs: 132 with pytest.raises(ValueError): 133 1.1 == approx(1, **kwargs) 134 135 def test_reasonable_defaults(self): 136 # Whatever the defaults are, they should work for numbers close to 1 137 # than have a small amount of floating-point error. 138 assert 0.1 + 0.2 == approx(0.3) 139 140 def test_default_tolerances(self): 141 # This tests the defaults as they are currently set. If you change the 142 # defaults, this test will fail but you should feel free to change it. 143 # None of the other tests (except the doctests) should be affected by 144 # the choice of defaults. 145 examples = [ 146 # Relative tolerance used. 147 (eq, 1e100 + 1e94, 1e100), 148 (ne, 1e100 + 2e94, 1e100), 149 (eq, 1e0 + 1e-6, 1e0), 150 (ne, 1e0 + 2e-6, 1e0), 151 # Absolute tolerance used. 152 (eq, 1e-100, +1e-106), 153 (eq, 1e-100, +2e-106), 154 (eq, 1e-100, 0), 155 ] 156 for op, a, x in examples: 157 assert op(a, approx(x)) 158 159 def test_custom_tolerances(self): 160 assert 1e8 + 1e0 == approx(1e8, rel=5e-8, abs=5e0) 161 assert 1e8 + 1e0 == approx(1e8, rel=5e-9, abs=5e0) 162 assert 1e8 + 1e0 == approx(1e8, rel=5e-8, abs=5e-1) 163 assert 1e8 + 1e0 != approx(1e8, rel=5e-9, abs=5e-1) 164 165 assert 1e0 + 1e-8 == approx(1e0, rel=5e-8, abs=5e-8) 166 assert 1e0 + 1e-8 == approx(1e0, rel=5e-9, abs=5e-8) 167 assert 1e0 + 1e-8 == approx(1e0, rel=5e-8, abs=5e-9) 168 assert 1e0 + 1e-8 != approx(1e0, rel=5e-9, abs=5e-9) 169 170 assert 1e-8 + 1e-16 == approx(1e-8, rel=5e-8, abs=5e-16) 171 assert 1e-8 + 1e-16 == approx(1e-8, rel=5e-9, abs=5e-16) 172 assert 1e-8 + 1e-16 == approx(1e-8, rel=5e-8, abs=5e-17) 173 assert 1e-8 + 1e-16 != approx(1e-8, rel=5e-9, abs=5e-17) 174 175 def test_relative_tolerance(self): 176 within_1e8_rel = [(1e8 + 1e0, 1e8), (1e0 + 1e-8, 1e0), (1e-8 + 1e-16, 1e-8)] 177 for a, x in within_1e8_rel: 178 assert a == approx(x, rel=5e-8, abs=0.0) 179 assert a != approx(x, rel=5e-9, abs=0.0) 180 181 def test_absolute_tolerance(self): 182 within_1e8_abs = [(1e8 + 9e-9, 1e8), (1e0 + 9e-9, 1e0), (1e-8 + 9e-9, 1e-8)] 183 for a, x in within_1e8_abs: 184 assert a == approx(x, rel=0, abs=5e-8) 185 assert a != approx(x, rel=0, abs=5e-9) 186 187 def test_expecting_zero(self): 188 examples = [ 189 (ne, 1e-6, 0.0), 190 (ne, -1e-6, 0.0), 191 (eq, 1e-12, 0.0), 192 (eq, -1e-12, 0.0), 193 (ne, 2e-12, 0.0), 194 (ne, -2e-12, 0.0), 195 (ne, inf, 0.0), 196 (ne, nan, 0.0), 197 ] 198 for op, a, x in examples: 199 assert op(a, approx(x, rel=0.0, abs=1e-12)) 200 assert op(a, approx(x, rel=1e-6, abs=1e-12)) 201 202 def test_expecting_inf(self): 203 examples = [ 204 (eq, inf, inf), 205 (eq, -inf, -inf), 206 (ne, inf, -inf), 207 (ne, 0.0, inf), 208 (ne, nan, inf), 209 ] 210 for op, a, x in examples: 211 assert op(a, approx(x)) 212 213 def test_expecting_nan(self): 214 examples = [ 215 (eq, nan, nan), 216 (eq, -nan, -nan), 217 (eq, nan, -nan), 218 (ne, 0.0, nan), 219 (ne, inf, nan), 220 ] 221 for op, a, x in examples: 222 # Nothing is equal to NaN by default. 223 assert a != approx(x) 224 225 # If ``nan_ok=True``, then NaN is equal to NaN. 226 assert op(a, approx(x, nan_ok=True)) 227 228 def test_int(self): 229 within_1e6 = [(1000001, 1000000), (-1000001, -1000000)] 230 for a, x in within_1e6: 231 assert a == approx(x, rel=5e-6, abs=0) 232 assert a != approx(x, rel=5e-7, abs=0) 233 assert approx(x, rel=5e-6, abs=0) == a 234 assert approx(x, rel=5e-7, abs=0) != a 235 236 def test_decimal(self): 237 within_1e6 = [ 238 (Decimal("1.000001"), Decimal("1.0")), 239 (Decimal("-1.000001"), Decimal("-1.0")), 240 ] 241 for a, x in within_1e6: 242 assert a == approx(x) 243 assert a == approx(x, rel=Decimal("5e-6"), abs=0) 244 assert a != approx(x, rel=Decimal("5e-7"), abs=0) 245 assert approx(x, rel=Decimal("5e-6"), abs=0) == a 246 assert approx(x, rel=Decimal("5e-7"), abs=0) != a 247 248 def test_fraction(self): 249 within_1e6 = [ 250 (1 + Fraction(1, 1000000), Fraction(1)), 251 (-1 - Fraction(-1, 1000000), Fraction(-1)), 252 ] 253 for a, x in within_1e6: 254 assert a == approx(x, rel=5e-6, abs=0) 255 assert a != approx(x, rel=5e-7, abs=0) 256 assert approx(x, rel=5e-6, abs=0) == a 257 assert approx(x, rel=5e-7, abs=0) != a 258 259 def test_complex(self): 260 within_1e6 = [ 261 (1.000001 + 1.0j, 1.0 + 1.0j), 262 (1.0 + 1.000001j, 1.0 + 1.0j), 263 (-1.000001 + 1.0j, -1.0 + 1.0j), 264 (1.0 - 1.000001j, 1.0 - 1.0j), 265 ] 266 for a, x in within_1e6: 267 assert a == approx(x, rel=5e-6, abs=0) 268 assert a != approx(x, rel=5e-7, abs=0) 269 assert approx(x, rel=5e-6, abs=0) == a 270 assert approx(x, rel=5e-7, abs=0) != a 271 272 def test_list(self): 273 actual = [1 + 1e-7, 2 + 1e-8] 274 expected = [1, 2] 275 276 # Return false if any element is outside the tolerance. 277 assert actual == approx(expected, rel=5e-7, abs=0) 278 assert actual != approx(expected, rel=5e-8, abs=0) 279 assert approx(expected, rel=5e-7, abs=0) == actual 280 assert approx(expected, rel=5e-8, abs=0) != actual 281 282 def test_list_wrong_len(self): 283 assert [1, 2] != approx([1]) 284 assert [1, 2] != approx([1, 2, 3]) 285 286 def test_tuple(self): 287 actual = (1 + 1e-7, 2 + 1e-8) 288 expected = (1, 2) 289 290 # Return false if any element is outside the tolerance. 291 assert actual == approx(expected, rel=5e-7, abs=0) 292 assert actual != approx(expected, rel=5e-8, abs=0) 293 assert approx(expected, rel=5e-7, abs=0) == actual 294 assert approx(expected, rel=5e-8, abs=0) != actual 295 296 def test_tuple_wrong_len(self): 297 assert (1, 2) != approx((1,)) 298 assert (1, 2) != approx((1, 2, 3)) 299 300 def test_dict(self): 301 actual = {"a": 1 + 1e-7, "b": 2 + 1e-8} 302 # Dictionaries became ordered in python3.6, so switch up the order here 303 # to make sure it doesn't matter. 304 expected = {"b": 2, "a": 1} 305 306 # Return false if any element is outside the tolerance. 307 assert actual == approx(expected, rel=5e-7, abs=0) 308 assert actual != approx(expected, rel=5e-8, abs=0) 309 assert approx(expected, rel=5e-7, abs=0) == actual 310 assert approx(expected, rel=5e-8, abs=0) != actual 311 312 def test_dict_wrong_len(self): 313 assert {"a": 1, "b": 2} != approx({"a": 1}) 314 assert {"a": 1, "b": 2} != approx({"a": 1, "c": 2}) 315 assert {"a": 1, "b": 2} != approx({"a": 1, "b": 2, "c": 3}) 316 317 def test_numpy_array(self): 318 np = pytest.importorskip("numpy") 319 320 actual = np.array([1 + 1e-7, 2 + 1e-8]) 321 expected = np.array([1, 2]) 322 323 # Return false if any element is outside the tolerance. 324 assert actual == approx(expected, rel=5e-7, abs=0) 325 assert actual != approx(expected, rel=5e-8, abs=0) 326 assert approx(expected, rel=5e-7, abs=0) == expected 327 assert approx(expected, rel=5e-8, abs=0) != actual 328 329 # Should be able to compare lists with numpy arrays. 330 assert list(actual) == approx(expected, rel=5e-7, abs=0) 331 assert list(actual) != approx(expected, rel=5e-8, abs=0) 332 assert actual == approx(list(expected), rel=5e-7, abs=0) 333 assert actual != approx(list(expected), rel=5e-8, abs=0) 334 335 def test_numpy_array_wrong_shape(self): 336 np = pytest.importorskip("numpy") 337 338 a12 = np.array([[1, 2]]) 339 a21 = np.array([[1], [2]]) 340 341 assert a12 != approx(a21) 342 assert a21 != approx(a12) 343 344 def test_doctests(self): 345 parser = doctest.DocTestParser() 346 test = parser.get_doctest( 347 approx.__doc__, {"approx": approx}, approx.__name__, None, None 348 ) 349 runner = MyDocTestRunner() 350 runner.run(test) 351 352 def test_unicode_plus_minus(self, testdir): 353 """ 354 Comparing approx instances inside lists should not produce an error in the detailed diff. 355 Integration test for issue #2111. 356 """ 357 testdir.makepyfile( 358 """ 359 import pytest 360 def test_foo(): 361 assert [3] == [pytest.approx(4)] 362 """ 363 ) 364 expected = "4.0e-06" 365 result = testdir.runpytest() 366 result.stdout.fnmatch_lines( 367 ["*At index 0 diff: 3 != 4 * {}".format(expected), "=* 1 failed in *="] 368 ) 369 370 @pytest.mark.parametrize( 371 "op", 372 [ 373 pytest.param(operator.le, id="<="), 374 pytest.param(operator.lt, id="<"), 375 pytest.param(operator.ge, id=">="), 376 pytest.param(operator.gt, id=">"), 377 ], 378 ) 379 def test_comparison_operator_type_error(self, op): 380 """ 381 pytest.approx should raise TypeError for operators other than == and != (#2003). 382 """ 383 with pytest.raises(TypeError): 384 op(1, approx(1, rel=1e-6, abs=1e-12)) 385 386 def test_numpy_array_with_scalar(self): 387 np = pytest.importorskip("numpy") 388 389 actual = np.array([1 + 1e-7, 1 - 1e-8]) 390 expected = 1.0 391 392 assert actual == approx(expected, rel=5e-7, abs=0) 393 assert actual != approx(expected, rel=5e-8, abs=0) 394 assert approx(expected, rel=5e-7, abs=0) == actual 395 assert approx(expected, rel=5e-8, abs=0) != actual 396 397 def test_numpy_scalar_with_array(self): 398 np = pytest.importorskip("numpy") 399 400 actual = 1.0 401 expected = np.array([1 + 1e-7, 1 - 1e-8]) 402 403 assert actual == approx(expected, rel=5e-7, abs=0) 404 assert actual != approx(expected, rel=5e-8, abs=0) 405 assert approx(expected, rel=5e-7, abs=0) == actual 406 assert approx(expected, rel=5e-8, abs=0) != actual 407