Merge pull request #1441 from kalekundert/features
Add a convenience function for floating-point comparisons
This commit is contained in:
commit
5fd82078ad
1
AUTHORS
1
AUTHORS
|
@ -50,6 +50,7 @@ Jason R. Coombs
|
|||
Joshua Bronson
|
||||
Jurko Gospodnetić
|
||||
Katarzyna Jachim
|
||||
Kale Kundert
|
||||
Kevin Cox
|
||||
Lee Kamentsky
|
||||
Lukas Bednar
|
||||
|
|
|
@ -11,11 +11,12 @@
|
|||
for a fixture (to solve the funcarg-shadowing-fixture problem).
|
||||
Thanks `@novas0x2a`_ for the complete PR (`#1444`_).
|
||||
|
||||
*
|
||||
* New ``approx()`` function for easily comparing floating-point numbers in
|
||||
tests.
|
||||
Thanks `@kalekundert`_ for the complete PR (`#1441`_).
|
||||
|
||||
*
|
||||
|
||||
|
||||
**Changes**
|
||||
|
||||
*
|
||||
|
@ -26,10 +27,11 @@
|
|||
|
||||
.. _@milliams: https://github.com/milliams
|
||||
.. _@novas0x2a: https://github.com/novas0x2a
|
||||
.. _@kalekundert: https://github.com/kalekundert
|
||||
|
||||
.. _#1428: https://github.com/pytest-dev/pytest/pull/1428
|
||||
.. _#1444: https://github.com/pytest-dev/pytest/pull/1444
|
||||
|
||||
.. _#1441: https://github.com/pytest-dev/pytest/pull/1441
|
||||
|
||||
2.9.1.dev1
|
||||
==========
|
||||
|
|
|
@ -1,10 +1,12 @@
|
|||
""" Python test discovery, setup and run of test functions. """
|
||||
|
||||
import fnmatch
|
||||
import functools
|
||||
import inspect
|
||||
import re
|
||||
import types
|
||||
import sys
|
||||
import math
|
||||
|
||||
import py
|
||||
import pytest
|
||||
|
@ -269,7 +271,8 @@ def pytest_namespace():
|
|||
return {
|
||||
'fixture': fixture,
|
||||
'yield_fixture': yield_fixture,
|
||||
'raises' : raises,
|
||||
'raises': raises,
|
||||
'approx': approx,
|
||||
'collect': {
|
||||
'Module': Module, 'Class': Class, 'Instance': Instance,
|
||||
'Function': Function, 'Generator': Generator,
|
||||
|
@ -1211,7 +1214,8 @@ def getlocation(function, curdir):
|
|||
# builtin pytest.raises helper
|
||||
|
||||
def raises(expected_exception, *args, **kwargs):
|
||||
""" assert that a code block/function call raises ``expected_exception``
|
||||
"""
|
||||
Assert that a code block/function call raises ``expected_exception``
|
||||
and raise a failure exception otherwise.
|
||||
|
||||
This helper produces a ``ExceptionInfo()`` object (see below).
|
||||
|
@ -1344,6 +1348,255 @@ class RaisesContext(object):
|
|||
self.excinfo.__init__(tp)
|
||||
return issubclass(self.excinfo.type, self.expected_exception)
|
||||
|
||||
# builtin pytest.approx helper
|
||||
|
||||
class approx(object):
|
||||
"""
|
||||
Assert that two numbers (or two sets of numbers) are equal to each other
|
||||
within some tolerance.
|
||||
|
||||
Due to the `intricacies of floating-point arithmetic`__, numbers that we
|
||||
would intuitively expect to be equal are not always so::
|
||||
|
||||
>>> 0.1 + 0.2 == 0.3
|
||||
False
|
||||
|
||||
__ https://docs.python.org/3/tutorial/floatingpoint.html
|
||||
|
||||
This problem is commonly encountered when writing tests, e.g. when making
|
||||
sure that floating-point values are what you expect them to be. One way to
|
||||
deal with this problem is to assert that two floating-point numbers are
|
||||
equal to within some appropriate tolerance::
|
||||
|
||||
>>> abs((0.1 + 0.2) - 0.3) < 1e-6
|
||||
True
|
||||
|
||||
However, comparisons like this are tedious to write and difficult to
|
||||
understand. Furthermore, absolute comparisons like the one above are
|
||||
usually discouraged because there's no tolerance that works well for all
|
||||
situations. ``1e-6`` is good for numbers around ``1``, but too small for
|
||||
very big numbers and too big for very small ones. It's better to express
|
||||
the tolerance as a fraction of the expected value, but relative comparisons
|
||||
like that are even more difficult to write correctly and concisely.
|
||||
|
||||
The ``approx`` class performs floating-point comparisons using a syntax
|
||||
that's as intuitive as possible::
|
||||
|
||||
>>> from pytest import approx
|
||||
>>> 0.1 + 0.2 == approx(0.3)
|
||||
True
|
||||
|
||||
The same syntax also works on sequences of numbers::
|
||||
|
||||
>>> (0.1 + 0.2, 0.2 + 0.4) == approx((0.3, 0.6))
|
||||
True
|
||||
|
||||
By default, ``approx`` considers numbers within a relative tolerance of
|
||||
``1e-6`` (i.e. one part in a million) of its expected value to be equal.
|
||||
This treatment would lead to surprising results if the expected value was
|
||||
``0.0``, because nothing but ``0.0`` itself is relatively close to ``0.0``.
|
||||
To handle this case less surprisingly, ``approx`` also considers numbers
|
||||
within an absolute tolerance of ``1e-12`` of its expected value to be
|
||||
equal. Infinite numbers are another special case. They are only
|
||||
considered equal to themselves, regardless of the relative tolerance. Both
|
||||
the relative and absolute tolerances can be changed by passing arguments to
|
||||
the ``approx`` constructor::
|
||||
|
||||
>>> 1.0001 == approx(1)
|
||||
False
|
||||
>>> 1.0001 == approx(1, rel=1e-3)
|
||||
True
|
||||
>>> 1.0001 == approx(1, abs=1e-3)
|
||||
True
|
||||
|
||||
If you specify ``abs`` but not ``rel``, the comparison will not consider
|
||||
the relative tolerance at all. In other words, two numbers that are within
|
||||
the default relative tolerance of ``1e-6`` will still be considered unequal
|
||||
if they exceed the specified absolute tolerance. If you specify both
|
||||
``abs`` and ``rel``, the numbers will be considered equal if either
|
||||
tolerance is met::
|
||||
|
||||
>>> 1 + 1e-8 == approx(1)
|
||||
True
|
||||
>>> 1 + 1e-8 == approx(1, abs=1e-12)
|
||||
False
|
||||
>>> 1 + 1e-8 == approx(1, rel=1e-6, abs=1e-12)
|
||||
True
|
||||
|
||||
If you're thinking about using ``approx``, then you might want to know how
|
||||
it compares to other good ways of comparing floating-point numbers. All of
|
||||
these algorithms are based on relative and absolute tolerances and should
|
||||
agree for the most part, but they do have meaningful differences:
|
||||
|
||||
- ``math.isclose(a, b, rel_tol=1e-9, abs_tol=0.0)``: True if the relative
|
||||
tolerance is met w.r.t. either ``a`` or ``b`` or if the absolute
|
||||
tolerance is met. Because the relative tolerance is calculated w.r.t.
|
||||
both ``a`` and ``b``, this test is symmetric (i.e. neither ``a`` nor
|
||||
``b`` is a "reference value"). You have to specify an absolute tolerance
|
||||
if you want to compare to ``0.0`` because there is no tolerance by
|
||||
default. Only available in python>=3.5. `More information...`__
|
||||
|
||||
__ https://docs.python.org/3/library/math.html#math.isclose
|
||||
|
||||
- ``numpy.isclose(a, b, rtol=1e-5, atol=1e-8)``: True if the difference
|
||||
between ``a`` and ``b`` is less that the sum of the relative tolerance
|
||||
w.r.t. ``b`` and the absolute tolerance. Because the relative tolerance
|
||||
is only calculated w.r.t. ``b``, this test is asymmetric and you can
|
||||
think of ``b`` as the reference value. Support for comparing sequences
|
||||
is provided by ``numpy.allclose``. `More information...`__
|
||||
|
||||
__ http://docs.scipy.org/doc/numpy-1.10.0/reference/generated/numpy.isclose.html
|
||||
|
||||
- ``unittest.TestCase.assertAlmostEqual(a, b)``: True if ``a`` and ``b``
|
||||
are within an absolute tolerance of ``1e-7``. No relative tolerance is
|
||||
considered and the absolute tolerance cannot be changed, so this function
|
||||
is not appropriate for very large or very small numbers. Also, it's only
|
||||
available in subclasses of ``unittest.TestCase`` and it's ugly because it
|
||||
doesn't follow PEP8. `More information...`__
|
||||
|
||||
__ https://docs.python.org/3/library/unittest.html#unittest.TestCase.assertAlmostEqual
|
||||
|
||||
- ``a == pytest.approx(b, rel=1e-6, abs=1e-12)``: True if the relative
|
||||
tolerance is met w.r.t. ``b`` or if the absolute tolerance is met.
|
||||
Because the relative tolerance is only calculated w.r.t. ``b``, this test
|
||||
is asymmetric and you can think of ``b`` as the reference value. In the
|
||||
special case that you explicitly specify an absolute tolerance but not a
|
||||
relative tolerance, only the absolute tolerance is considered.
|
||||
"""
|
||||
|
||||
def __init__(self, expected, rel=None, abs=None):
|
||||
self.expected = expected
|
||||
self.abs = abs
|
||||
self.rel = rel
|
||||
|
||||
def __repr__(self):
|
||||
return ', '.join(repr(x) for x in self.expected)
|
||||
|
||||
def __eq__(self, actual):
|
||||
from collections import Iterable
|
||||
if not isinstance(actual, Iterable):
|
||||
actual = [actual]
|
||||
if len(actual) != len(self.expected):
|
||||
return False
|
||||
return all(a == x for a, x in zip(actual, self.expected))
|
||||
|
||||
def __ne__(self, actual):
|
||||
return not (actual == self)
|
||||
|
||||
@property
|
||||
def expected(self):
|
||||
# Regardless of whether the user-specified expected value is a number
|
||||
# or a sequence of numbers, return a list of ApproxNotIterable objects
|
||||
# that can be compared against.
|
||||
from collections import Iterable
|
||||
approx_non_iter = lambda x: ApproxNonIterable(x, self.rel, self.abs)
|
||||
if isinstance(self._expected, Iterable):
|
||||
return [approx_non_iter(x) for x in self._expected]
|
||||
else:
|
||||
return [approx_non_iter(self._expected)]
|
||||
|
||||
@expected.setter
|
||||
def expected(self, expected):
|
||||
self._expected = expected
|
||||
|
||||
|
||||
class ApproxNonIterable(object):
|
||||
"""
|
||||
Perform approximate comparisons for single numbers only.
|
||||
|
||||
In other words, the ``expected`` attribute for objects of this class must
|
||||
be some sort of number. This is in contrast to the ``approx`` class, where
|
||||
the ``expected`` attribute can either be a number of a sequence of numbers.
|
||||
This class is responsible for making comparisons, while ``approx`` is
|
||||
responsible for abstracting the difference between numbers and sequences of
|
||||
numbers. Although this class can stand on its own, it's only meant to be
|
||||
used within ``approx``.
|
||||
"""
|
||||
|
||||
def __init__(self, expected, rel=None, abs=None):
|
||||
self.expected = expected
|
||||
self.abs = abs
|
||||
self.rel = rel
|
||||
|
||||
def __repr__(self):
|
||||
# Infinities aren't compared using tolerances, so don't show a
|
||||
# tolerance.
|
||||
if math.isinf(self.expected):
|
||||
return str(self.expected)
|
||||
|
||||
# If a sensible tolerance can't be calculated, self.tolerance will
|
||||
# raise a ValueError. In this case, display '???'.
|
||||
try:
|
||||
vetted_tolerance = '{:.1e}'.format(self.tolerance)
|
||||
except ValueError:
|
||||
vetted_tolerance = '???'
|
||||
|
||||
plus_minus = u'{0} \u00b1 {1}'.format(self.expected, vetted_tolerance)
|
||||
|
||||
# In python2, __repr__() must return a string (i.e. not a unicode
|
||||
# object). In python3, __repr__() must return a unicode object
|
||||
# (although now strings are unicode objects and bytes are what
|
||||
# strings were).
|
||||
if sys.version_info[0] == 2:
|
||||
return plus_minus.encode('utf-8')
|
||||
else:
|
||||
return plus_minus
|
||||
|
||||
def __eq__(self, actual):
|
||||
# Short-circuit exact equality.
|
||||
if actual == self.expected:
|
||||
return True
|
||||
|
||||
# Infinity shouldn't be approximately equal to anything but itself, but
|
||||
# if there's a relative tolerance, it will be infinite and infinity
|
||||
# will seem approximately equal to everything. The equal-to-itself
|
||||
# case would have been short circuited above, so here we can just
|
||||
# return false if the expected value is infinite. The abs() call is
|
||||
# for compatibility with complex numbers.
|
||||
if math.isinf(abs(self.expected)):
|
||||
return False
|
||||
|
||||
# Return true if the two numbers are within the tolerance.
|
||||
return abs(self.expected - actual) <= self.tolerance
|
||||
|
||||
def __ne__(self, actual):
|
||||
return not (actual == self)
|
||||
|
||||
@property
|
||||
def tolerance(self):
|
||||
set_default = lambda x, default: x if x is not None else default
|
||||
|
||||
# Figure out what the absolute tolerance should be. ``self.abs`` is
|
||||
# either None or a value specified by the user.
|
||||
absolute_tolerance = set_default(self.abs, 1e-12)
|
||||
|
||||
if absolute_tolerance < 0:
|
||||
raise ValueError("absolute tolerance can't be negative: {}".format(absolute_tolerance))
|
||||
if math.isnan(absolute_tolerance):
|
||||
raise ValueError("absolute tolerance can't be NaN.")
|
||||
|
||||
# If the user specified an absolute tolerance but not a relative one,
|
||||
# just return the absolute tolerance.
|
||||
if self.rel is None:
|
||||
if self.abs is not None:
|
||||
return absolute_tolerance
|
||||
|
||||
# Figure out what the relative tolerance should be. ``self.rel`` is
|
||||
# either None or a value specified by the user. This is done after
|
||||
# we've made sure the user didn't ask for an absolute tolerance only,
|
||||
# because we don't want to raise errors about the relative tolerance if
|
||||
# we aren't even going to use it.
|
||||
relative_tolerance = set_default(self.rel, 1e-6) * abs(self.expected)
|
||||
|
||||
if relative_tolerance < 0:
|
||||
raise ValueError("relative tolerance can't be negative: {}".format(absolute_tolerance))
|
||||
if math.isnan(relative_tolerance):
|
||||
raise ValueError("relative tolerance can't be NaN.")
|
||||
|
||||
# Return the larger of the relative and absolute tolerances.
|
||||
return max(relative_tolerance, absolute_tolerance)
|
||||
|
||||
|
||||
#
|
||||
# the basic pytest Function item
|
||||
#
|
||||
|
|
|
@ -35,6 +35,11 @@ Examples at :ref:`assertraises`.
|
|||
|
||||
.. autofunction:: deprecated_call
|
||||
|
||||
Comparing floating point numbers
|
||||
--------------------------------
|
||||
|
||||
.. autoclass:: approx
|
||||
|
||||
Raising a specific test outcome
|
||||
--------------------------------------
|
||||
|
||||
|
@ -48,7 +53,7 @@ you can rather use declarative marks, see :ref:`skipping`.
|
|||
.. autofunction:: _pytest.skipping.xfail
|
||||
.. autofunction:: _pytest.runner.exit
|
||||
|
||||
fixtures and requests
|
||||
Fixtures and requests
|
||||
-----------------------------------------------------
|
||||
|
||||
To mark a fixture function:
|
||||
|
|
|
@ -0,0 +1,286 @@
|
|||
# encoding: utf-8
|
||||
|
||||
import pytest
|
||||
import doctest
|
||||
|
||||
from pytest import approx
|
||||
from operator import eq, ne
|
||||
from decimal import Decimal
|
||||
from fractions import Fraction
|
||||
inf, nan = float('inf'), float('nan')
|
||||
|
||||
class MyDocTestRunner(doctest.DocTestRunner):
|
||||
|
||||
def __init__(self):
|
||||
doctest.DocTestRunner.__init__(self)
|
||||
|
||||
def report_failure(self, out, test, example, got):
|
||||
raise AssertionError("'{}' evaluates to '{}', not '{}'".format(
|
||||
example.source.strip(), got.strip(), example.want.strip()))
|
||||
|
||||
|
||||
class TestApprox:
|
||||
|
||||
def test_repr_string(self):
|
||||
# Just make sure the Unicode handling doesn't raise any exceptions.
|
||||
print(approx(1.0))
|
||||
print(approx([1.0, 2.0, 3.0]))
|
||||
print(approx(inf))
|
||||
print(approx(1.0, rel=nan))
|
||||
print(approx(1.0, rel=inf))
|
||||
|
||||
def test_operator_overloading(self):
|
||||
assert 1 == approx(1, rel=1e-6, abs=1e-12)
|
||||
assert not (1 != approx(1, rel=1e-6, abs=1e-12))
|
||||
assert 10 != approx(1, rel=1e-6, abs=1e-12)
|
||||
assert not (10 == approx(1, rel=1e-6, abs=1e-12))
|
||||
|
||||
def test_exactly_equal(self):
|
||||
examples = [
|
||||
(2.0, 2.0),
|
||||
(0.1e200, 0.1e200),
|
||||
(1.123e-300, 1.123e-300),
|
||||
(12345, 12345.0),
|
||||
(0.0, -0.0),
|
||||
(345678, 345678),
|
||||
(Decimal('1.0001'), Decimal('1.0001')),
|
||||
(Fraction(1, 3), Fraction(-1, -3)),
|
||||
]
|
||||
for a, x in examples:
|
||||
assert a == approx(x)
|
||||
|
||||
def test_opposite_sign(self):
|
||||
examples = [
|
||||
(eq, 1e-100, -1e-100),
|
||||
(ne, 1e100, -1e100),
|
||||
]
|
||||
for op, a, x in examples:
|
||||
assert op(a, approx(x))
|
||||
|
||||
def test_zero_tolerance(self):
|
||||
within_1e10 = [
|
||||
(1.1e-100, 1e-100),
|
||||
(-1.1e-100, -1e-100),
|
||||
]
|
||||
for a, x in within_1e10:
|
||||
assert x == approx(x, rel=0.0, abs=0.0)
|
||||
assert a != approx(x, rel=0.0, abs=0.0)
|
||||
assert a == approx(x, rel=0.0, abs=5e-101)
|
||||
assert a != approx(x, rel=0.0, abs=5e-102)
|
||||
assert a == approx(x, rel=5e-1, abs=0.0)
|
||||
assert a != approx(x, rel=5e-2, abs=0.0)
|
||||
|
||||
def test_negative_tolerance(self):
|
||||
# Negative tolerances are not allowed.
|
||||
illegal_kwargs = [
|
||||
dict(rel=-1e100),
|
||||
dict(abs=-1e100),
|
||||
dict(rel=1e100, abs=-1e100),
|
||||
dict(rel=-1e100, abs=1e100),
|
||||
dict(rel=-1e100, abs=-1e100),
|
||||
]
|
||||
for kwargs in illegal_kwargs:
|
||||
with pytest.raises(ValueError):
|
||||
1.1 == approx(1, **kwargs)
|
||||
|
||||
def test_inf_tolerance(self):
|
||||
# Everything should be equal if the tolerance is infinite.
|
||||
large_diffs = [
|
||||
(1, 1000),
|
||||
(1e-50, 1e50),
|
||||
(-1.0, -1e300),
|
||||
(0.0, 10),
|
||||
]
|
||||
for a, x in large_diffs:
|
||||
assert a != approx(x, rel=0.0, abs=0.0)
|
||||
assert a == approx(x, rel=inf, abs=0.0)
|
||||
assert a == approx(x, rel=0.0, abs=inf)
|
||||
assert a == approx(x, rel=inf, abs=inf)
|
||||
|
||||
def test_inf_tolerance_expecting_zero(self):
|
||||
# If the relative tolerance is zero but the expected value is infinite,
|
||||
# the actual tolerance is a NaN, which should be an error.
|
||||
illegal_kwargs = [
|
||||
dict(rel=inf, abs=0.0),
|
||||
dict(rel=inf, abs=inf),
|
||||
]
|
||||
for kwargs in illegal_kwargs:
|
||||
with pytest.raises(ValueError):
|
||||
1 == approx(0, **kwargs)
|
||||
|
||||
def test_nan_tolerance(self):
|
||||
illegal_kwargs = [
|
||||
dict(rel=nan),
|
||||
dict(abs=nan),
|
||||
dict(rel=nan, abs=nan),
|
||||
]
|
||||
for kwargs in illegal_kwargs:
|
||||
with pytest.raises(ValueError):
|
||||
1.1 == approx(1, **kwargs)
|
||||
|
||||
def test_reasonable_defaults(self):
|
||||
# Whatever the defaults are, they should work for numbers close to 1
|
||||
# than have a small amount of floating-point error.
|
||||
assert 0.1 + 0.2 == approx(0.3)
|
||||
|
||||
def test_default_tolerances(self):
|
||||
# This tests the defaults as they are currently set. If you change the
|
||||
# defaults, this test will fail but you should feel free to change it.
|
||||
# None of the other tests (except the doctests) should be affected by
|
||||
# the choice of defaults.
|
||||
examples = [
|
||||
# Relative tolerance used.
|
||||
(eq, 1e100 + 1e94, 1e100),
|
||||
(ne, 1e100 + 2e94, 1e100),
|
||||
(eq, 1e0 + 1e-6, 1e0),
|
||||
(ne, 1e0 + 2e-6, 1e0),
|
||||
# Absolute tolerance used.
|
||||
(eq, 1e-100, + 1e-106),
|
||||
(eq, 1e-100, + 2e-106),
|
||||
(eq, 1e-100, 0),
|
||||
]
|
||||
for op, a, x in examples:
|
||||
assert op(a, approx(x))
|
||||
|
||||
def test_custom_tolerances(self):
|
||||
assert 1e8 + 1e0 == approx(1e8, rel=5e-8, abs=5e0)
|
||||
assert 1e8 + 1e0 == approx(1e8, rel=5e-9, abs=5e0)
|
||||
assert 1e8 + 1e0 == approx(1e8, rel=5e-8, abs=5e-1)
|
||||
assert 1e8 + 1e0 != approx(1e8, rel=5e-9, abs=5e-1)
|
||||
|
||||
assert 1e0 + 1e-8 == approx(1e0, rel=5e-8, abs=5e-8)
|
||||
assert 1e0 + 1e-8 == approx(1e0, rel=5e-9, abs=5e-8)
|
||||
assert 1e0 + 1e-8 == approx(1e0, rel=5e-8, abs=5e-9)
|
||||
assert 1e0 + 1e-8 != approx(1e0, rel=5e-9, abs=5e-9)
|
||||
|
||||
assert 1e-8 + 1e-16 == approx(1e-8, rel=5e-8, abs=5e-16)
|
||||
assert 1e-8 + 1e-16 == approx(1e-8, rel=5e-9, abs=5e-16)
|
||||
assert 1e-8 + 1e-16 == approx(1e-8, rel=5e-8, abs=5e-17)
|
||||
assert 1e-8 + 1e-16 != approx(1e-8, rel=5e-9, abs=5e-17)
|
||||
|
||||
def test_relative_tolerance(self):
|
||||
within_1e8_rel = [
|
||||
(1e8 + 1e0, 1e8),
|
||||
(1e0 + 1e-8, 1e0),
|
||||
(1e-8 + 1e-16, 1e-8),
|
||||
]
|
||||
for a, x in within_1e8_rel:
|
||||
assert a == approx(x, rel=5e-8, abs=0.0)
|
||||
assert a != approx(x, rel=5e-9, abs=0.0)
|
||||
|
||||
def test_absolute_tolerance(self):
|
||||
within_1e8_abs = [
|
||||
(1e8 + 9e-9, 1e8),
|
||||
(1e0 + 9e-9, 1e0),
|
||||
(1e-8 + 9e-9, 1e-8),
|
||||
]
|
||||
for a, x in within_1e8_abs:
|
||||
assert a == approx(x, rel=0, abs=5e-8)
|
||||
assert a != approx(x, rel=0, abs=5e-9)
|
||||
|
||||
def test_expecting_zero(self):
|
||||
examples = [
|
||||
(ne, 1e-6, 0.0),
|
||||
(ne, -1e-6, 0.0),
|
||||
(eq, 1e-12, 0.0),
|
||||
(eq, -1e-12, 0.0),
|
||||
(ne, 2e-12, 0.0),
|
||||
(ne, -2e-12, 0.0),
|
||||
(ne, inf, 0.0),
|
||||
(ne, nan, 0.0),
|
||||
]
|
||||
for op, a, x in examples:
|
||||
assert op(a, approx(x, rel=0.0, abs=1e-12))
|
||||
assert op(a, approx(x, rel=1e-6, abs=1e-12))
|
||||
|
||||
def test_expecting_inf(self):
|
||||
examples = [
|
||||
(eq, inf, inf),
|
||||
(eq, -inf, -inf),
|
||||
(ne, inf, -inf),
|
||||
(ne, 0.0, inf),
|
||||
(ne, nan, inf),
|
||||
]
|
||||
for op, a, x in examples:
|
||||
assert op(a, approx(x))
|
||||
|
||||
def test_expecting_nan(self):
|
||||
examples = [
|
||||
(nan, nan),
|
||||
(-nan, -nan),
|
||||
(nan, -nan),
|
||||
(0.0, nan),
|
||||
(inf, nan),
|
||||
]
|
||||
for a, x in examples:
|
||||
# If there is a relative tolerance and the expected value is NaN,
|
||||
# the actual tolerance is a NaN, which should be an error.
|
||||
with pytest.raises(ValueError):
|
||||
a != approx(x, rel=inf)
|
||||
|
||||
# You can make comparisons against NaN by not specifying a relative
|
||||
# tolerance, so only an absolute tolerance is calculated.
|
||||
assert a != approx(x, abs=inf)
|
||||
|
||||
def test_expecting_sequence(self):
|
||||
within_1e8 = [
|
||||
(1e8 + 1e0, 1e8),
|
||||
(1e0 + 1e-8, 1e0),
|
||||
(1e-8 + 1e-16, 1e-8),
|
||||
]
|
||||
actual, expected = zip(*within_1e8)
|
||||
assert actual == approx(expected, rel=5e-8, abs=0.0)
|
||||
|
||||
def test_expecting_sequence_wrong_len(self):
|
||||
assert [1, 2] != approx([1])
|
||||
assert [1, 2] != approx([1,2,3])
|
||||
|
||||
def test_complex(self):
|
||||
within_1e6 = [
|
||||
( 1.000001 + 1.0j, 1.0 + 1.0j),
|
||||
(1.0 + 1.000001j, 1.0 + 1.0j),
|
||||
(-1.000001 + 1.0j, -1.0 + 1.0j),
|
||||
(1.0 - 1.000001j, 1.0 - 1.0j),
|
||||
]
|
||||
for a, x in within_1e6:
|
||||
assert a == approx(x, rel=5e-6, abs=0)
|
||||
assert a != approx(x, rel=5e-7, abs=0)
|
||||
|
||||
def test_int(self):
|
||||
within_1e6 = [
|
||||
(1000001, 1000000),
|
||||
(-1000001, -1000000),
|
||||
]
|
||||
for a, x in within_1e6:
|
||||
assert a == approx(x, rel=5e-6, abs=0)
|
||||
assert a != approx(x, rel=5e-7, abs=0)
|
||||
|
||||
def test_decimal(self):
|
||||
within_1e6 = [
|
||||
(Decimal('1.000001'), Decimal('1.0')),
|
||||
(Decimal('-1.000001'), Decimal('-1.0')),
|
||||
]
|
||||
for a, x in within_1e6:
|
||||
assert a == approx(x, rel=Decimal('5e-6'), abs=0)
|
||||
assert a != approx(x, rel=Decimal('5e-7'), abs=0)
|
||||
|
||||
def test_fraction(self):
|
||||
within_1e6 = [
|
||||
(1 + Fraction(1, 1000000), Fraction(1)),
|
||||
(-1 - Fraction(-1, 1000000), Fraction(-1)),
|
||||
]
|
||||
for a, x in within_1e6:
|
||||
assert a == approx(x, rel=5e-6, abs=0)
|
||||
assert a != approx(x, rel=5e-7, abs=0)
|
||||
|
||||
def test_doctests(self):
|
||||
parser = doctest.DocTestParser()
|
||||
test = parser.get_doctest(
|
||||
approx.__doc__,
|
||||
{'approx': approx},
|
||||
approx.__name__,
|
||||
None, None,
|
||||
)
|
||||
runner = MyDocTestRunner()
|
||||
runner.run(test)
|
||||
|
Loading…
Reference in New Issue