Implement suggestions from code review.
- Avoid importing numpy unless necessary. - Mention numpy arrays and dictionaries in the docs. - Add numpy to the list of tox dependencies. - Don't unnecessarily copy arrays or allocate empty space for them. - Use code from compat.py rather than writing py2/3 versions of things myself. - Avoid reimplementing __repr__ for built-in types. - Add an option to consider NaN == NaN, because sometimes people use NaN to mean "missing data".
This commit is contained in:
parent
89292f08dc
commit
8badb47db6
|
@ -125,6 +125,7 @@ if sys.version_info[:2] == (2, 6):
|
||||||
if _PY3:
|
if _PY3:
|
||||||
import codecs
|
import codecs
|
||||||
imap = map
|
imap = map
|
||||||
|
izip = zip
|
||||||
STRING_TYPES = bytes, str
|
STRING_TYPES = bytes, str
|
||||||
UNICODE_TYPES = str,
|
UNICODE_TYPES = str,
|
||||||
|
|
||||||
|
@ -160,7 +161,7 @@ else:
|
||||||
STRING_TYPES = bytes, str, unicode
|
STRING_TYPES = bytes, str, unicode
|
||||||
UNICODE_TYPES = unicode,
|
UNICODE_TYPES = unicode,
|
||||||
|
|
||||||
from itertools import imap # NOQA
|
from itertools import imap, izip # NOQA
|
||||||
|
|
||||||
def _escape_strings(val):
|
def _escape_strings(val):
|
||||||
"""In py2 bytes and str are the same type, so return if it's a bytes
|
"""In py2 bytes and str are the same type, so return if it's a bytes
|
||||||
|
|
|
@ -3,7 +3,7 @@ import sys
|
||||||
|
|
||||||
import py
|
import py
|
||||||
|
|
||||||
from _pytest.compat import isclass
|
from _pytest.compat import isclass, izip
|
||||||
from _pytest.runner import fail
|
from _pytest.runner import fail
|
||||||
import _pytest._code
|
import _pytest._code
|
||||||
|
|
||||||
|
@ -15,15 +15,14 @@ class ApproxBase(object):
|
||||||
or sequences of numbers.
|
or sequences of numbers.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, expected, rel=None, abs=None):
|
def __init__(self, expected, rel=None, abs=None, nan_ok=False):
|
||||||
self.expected = expected
|
self.expected = expected
|
||||||
self.abs = abs
|
self.abs = abs
|
||||||
self.rel = rel
|
self.rel = rel
|
||||||
|
self.nan_ok = nan_ok
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return ', '.join(
|
raise NotImplementedError
|
||||||
repr(self._approx_scalar(x))
|
|
||||||
for x in self._yield_expected())
|
|
||||||
|
|
||||||
def __eq__(self, actual):
|
def __eq__(self, actual):
|
||||||
return all(
|
return all(
|
||||||
|
@ -36,14 +35,7 @@ class ApproxBase(object):
|
||||||
return not (actual == self)
|
return not (actual == self)
|
||||||
|
|
||||||
def _approx_scalar(self, x):
|
def _approx_scalar(self, x):
|
||||||
return ApproxScalar(x, rel=self.rel, abs=self.abs)
|
return ApproxScalar(x, rel=self.rel, abs=self.abs, nan_ok=self.nan_ok)
|
||||||
|
|
||||||
def _yield_expected(self, actual):
|
|
||||||
"""
|
|
||||||
Yield all the expected values associated with this object. This is
|
|
||||||
used to implement the `__repr__` method.
|
|
||||||
"""
|
|
||||||
raise NotImplementedError
|
|
||||||
|
|
||||||
def _yield_comparisons(self, actual):
|
def _yield_comparisons(self, actual):
|
||||||
"""
|
"""
|
||||||
|
@ -53,53 +45,64 @@ class ApproxBase(object):
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
|
|
||||||
|
class ApproxNumpyBase(ApproxBase):
|
||||||
try:
|
|
||||||
import numpy as np
|
|
||||||
|
|
||||||
class ApproxNumpy(ApproxBase, np.ndarray):
|
|
||||||
"""
|
"""
|
||||||
Perform approximate comparisons for numpy arrays.
|
Perform approximate comparisons for numpy arrays.
|
||||||
|
|
||||||
This class must inherit from numpy.ndarray in order to allow the approx
|
This class should not be used directly. Instead, it should be used to make
|
||||||
to be on either side of the `==` operator. The reason for this has to
|
a subclass that also inherits from `np.ndarray`, e.g.::
|
||||||
do with how python decides whether to call `a.__eq__()` or `b.__eq__()`
|
|
||||||
when it encounters `a == b`.
|
|
||||||
|
|
||||||
If `a` and `b` are not related by inheritance, `a` gets priority. So
|
import numpy as np
|
||||||
as long as `a.__eq__` is defined, it will be called. Because most
|
ApproxNumpy = type('ApproxNumpy', (ApproxNumpyBase, np.ndarray), {})
|
||||||
implementations of `a.__eq__` end up calling `b.__eq__`, this detail
|
|
||||||
usually doesn't matter. However, `numpy.ndarray.__eq__` raises an
|
This bizarre invocation is necessary because the object doing the
|
||||||
error complaining that "the truth value of an array with more than
|
approximate comparison must inherit from `np.ndarray`, or it will only work
|
||||||
one element is ambiguous. Use a.any() or a.all()" when compared with a
|
on the left side of the `==` operator. But importing numpy is relatively
|
||||||
custom class, so `b.__eq__` never gets called.
|
expensive, so we also want to avoid that unless we actually have a numpy
|
||||||
|
array to compare.
|
||||||
|
|
||||||
|
The reason why the approx object needs to inherit from `np.ndarray` has to
|
||||||
|
do with how python decides whether to call `a.__eq__()` or `b.__eq__()`
|
||||||
|
when it parses `a == b`. If `a` and `b` are not related by inheritance,
|
||||||
|
`a` gets priority. So as long as `a.__eq__` is defined, it will be called.
|
||||||
|
Because most implementations of `a.__eq__` end up calling `b.__eq__`, this
|
||||||
|
detail usually doesn't matter. However, `np.ndarray.__eq__` treats the
|
||||||
|
approx object as a scalar and builds a new array by comparing it to each
|
||||||
|
item in the original array. `b.__eq__` is called to compare against each
|
||||||
|
individual element in the array, but it has no way (that I can see) to
|
||||||
|
prevent the return value from being an boolean array, and boolean arrays
|
||||||
|
can't be used with assert because "the truth value of an array with more
|
||||||
|
than one element is ambiguous."
|
||||||
|
|
||||||
The trick is that the priority rules change if `a` and `b` are related
|
The trick is that the priority rules change if `a` and `b` are related
|
||||||
by inheritance. Specifically, `b.__eq__` gets priority if `b` is a
|
by inheritance. Specifically, `b.__eq__` gets priority if `b` is a
|
||||||
subclass of `a`. So we can guarantee that `ApproxNumpy.__eq__` gets
|
subclass of `a`. So by inheriting from `np.ndarray`, we can guarantee that
|
||||||
called by inheriting from `numpy.ndarray`.
|
`ApproxNumpy.__eq__` gets called no matter which side of the `==` operator
|
||||||
|
it appears on.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __new__(cls, expected, rel=None, abs=None):
|
def __new__(cls, expected, rel=None, abs=None, nan_ok=False):
|
||||||
"""
|
"""
|
||||||
Numpy uses __new__ (rather than __init__) to initialize objects.
|
Numpy uses __new__ (rather than __init__) to initialize objects.
|
||||||
|
|
||||||
The `expected` argument must be a numpy array. This should be
|
The `expected` argument must be a numpy array. This should be
|
||||||
ensured by the approx() delegator function.
|
ensured by the approx() delegator function.
|
||||||
"""
|
"""
|
||||||
assert isinstance(expected, np.ndarray)
|
obj = super(ApproxNumpyBase, cls).__new__(cls, ())
|
||||||
obj = super(ApproxNumpy, cls).__new__(cls, expected.shape)
|
obj.__init__(expected, rel, abs, nan_ok)
|
||||||
obj.__init__(expected, rel, abs)
|
|
||||||
return obj
|
return obj
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
# It might be nice to rewrite this function to account for the
|
# It might be nice to rewrite this function to account for the
|
||||||
# shape of the array...
|
# shape of the array...
|
||||||
return '[' + ApproxBase.__repr__(self) + ']'
|
return repr(list(
|
||||||
|
self._approx_scalar(x) for x in self.expected))
|
||||||
|
|
||||||
def __eq__(self, actual):
|
def __eq__(self, actual):
|
||||||
|
import numpy as np
|
||||||
|
|
||||||
try:
|
try:
|
||||||
actual = np.array(actual)
|
actual = np.asarray(actual)
|
||||||
except:
|
except:
|
||||||
raise ValueError("cannot cast '{0}' to numpy.ndarray".format(actual))
|
raise ValueError("cannot cast '{0}' to numpy.ndarray".format(actual))
|
||||||
|
|
||||||
|
@ -108,11 +111,9 @@ try:
|
||||||
|
|
||||||
return ApproxBase.__eq__(self, actual)
|
return ApproxBase.__eq__(self, actual)
|
||||||
|
|
||||||
def _yield_expected(self):
|
|
||||||
for x in self.expected:
|
|
||||||
yield x
|
|
||||||
|
|
||||||
def _yield_comparisons(self, actual):
|
def _yield_comparisons(self, actual):
|
||||||
|
import numpy as np
|
||||||
|
|
||||||
# We can be sure that `actual` is a numpy array, because it's
|
# We can be sure that `actual` is a numpy array, because it's
|
||||||
# casted in `__eq__` before being passed to `ApproxBase.__eq__`,
|
# casted in `__eq__` before being passed to `ApproxBase.__eq__`,
|
||||||
# which is the only method that calls this one.
|
# which is the only method that calls this one.
|
||||||
|
@ -120,9 +121,6 @@ try:
|
||||||
yield actual[i], self.expected[i]
|
yield actual[i], self.expected[i]
|
||||||
|
|
||||||
|
|
||||||
except ImportError:
|
|
||||||
np = None
|
|
||||||
|
|
||||||
class ApproxMapping(ApproxBase):
|
class ApproxMapping(ApproxBase):
|
||||||
"""
|
"""
|
||||||
Perform approximate comparisons for mappings where the values are numbers
|
Perform approximate comparisons for mappings where the values are numbers
|
||||||
|
@ -130,8 +128,9 @@ class ApproxMapping(ApproxBase):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
item = lambda k, v: "'{0}': {1}".format(k, self._approx_scalar(v))
|
return repr({
|
||||||
return '{' + ', '.join(item(k,v) for k,v in self.expected.items()) + '}'
|
k: self._approx_scalar(v)
|
||||||
|
for k,v in self.expected.items()})
|
||||||
|
|
||||||
def __eq__(self, actual):
|
def __eq__(self, actual):
|
||||||
if actual.keys() != self.expected.keys():
|
if actual.keys() != self.expected.keys():
|
||||||
|
@ -150,19 +149,19 @@ class ApproxSequence(ApproxBase):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
open, close = '()' if isinstance(self.expected, tuple) else '[]'
|
seq_type = type(self.expected)
|
||||||
return open + ApproxBase.__repr__(self) + close
|
if seq_type not in (tuple, list, set):
|
||||||
|
seq_type = list
|
||||||
|
return repr(seq_type(
|
||||||
|
self._approx_scalar(x) for x in self.expected))
|
||||||
|
|
||||||
def __eq__(self, actual):
|
def __eq__(self, actual):
|
||||||
if len(actual) != len(self.expected):
|
if len(actual) != len(self.expected):
|
||||||
return False
|
return False
|
||||||
return ApproxBase.__eq__(self, actual)
|
return ApproxBase.__eq__(self, actual)
|
||||||
|
|
||||||
def _yield_expected(self):
|
|
||||||
return iter(self.expected)
|
|
||||||
|
|
||||||
def _yield_comparisons(self, actual):
|
def _yield_comparisons(self, actual):
|
||||||
return zip(actual, self.expected)
|
return izip(actual, self.expected)
|
||||||
|
|
||||||
|
|
||||||
class ApproxScalar(ApproxBase):
|
class ApproxScalar(ApproxBase):
|
||||||
|
@ -202,19 +201,17 @@ class ApproxScalar(ApproxBase):
|
||||||
Return true if the given value is equal to the expected value within
|
Return true if the given value is equal to the expected value within
|
||||||
the pre-specified tolerance.
|
the pre-specified tolerance.
|
||||||
"""
|
"""
|
||||||
from numbers import Number
|
|
||||||
|
|
||||||
# Give a good error message we get values to compare that aren't
|
|
||||||
# numbers, rather than choking on them later on.
|
|
||||||
if not isinstance(actual, Number):
|
|
||||||
raise ValueError("approx can only compare numbers, not '{0}'".format(actual))
|
|
||||||
if not isinstance(self.expected, Number):
|
|
||||||
raise ValueError("approx can only compare numbers, not '{0}'".format(self.expected))
|
|
||||||
|
|
||||||
# Short-circuit exact equality.
|
# Short-circuit exact equality.
|
||||||
if actual == self.expected:
|
if actual == self.expected:
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
# Allow the user to control whether NaNs are considered equal to each
|
||||||
|
# other or not. The abs() calls are for compatibility with complex
|
||||||
|
# numbers.
|
||||||
|
if math.isnan(abs(self.expected)):
|
||||||
|
return self.nan_ok and math.isnan(abs(actual))
|
||||||
|
|
||||||
# Infinity shouldn't be approximately equal to anything but itself, but
|
# Infinity shouldn't be approximately equal to anything but itself, but
|
||||||
# if there's a relative tolerance, it will be infinite and infinity
|
# if there's a relative tolerance, it will be infinite and infinity
|
||||||
# will seem approximately equal to everything. The equal-to-itself
|
# will seem approximately equal to everything. The equal-to-itself
|
||||||
|
@ -270,7 +267,7 @@ class ApproxScalar(ApproxBase):
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def approx(expected, rel=None, abs=None):
|
def approx(expected, rel=None, abs=None, nan_ok=False):
|
||||||
"""
|
"""
|
||||||
Assert that two numbers (or two sets of numbers) are equal to each other
|
Assert that two numbers (or two sets of numbers) are equal to each other
|
||||||
within some tolerance.
|
within some tolerance.
|
||||||
|
@ -306,23 +303,35 @@ def approx(expected, rel=None, abs=None):
|
||||||
>>> 0.1 + 0.2 == approx(0.3)
|
>>> 0.1 + 0.2 == approx(0.3)
|
||||||
True
|
True
|
||||||
|
|
||||||
The same syntax also works on sequences of numbers::
|
The same syntax also works for sequences of numbers::
|
||||||
|
|
||||||
>>> (0.1 + 0.2, 0.2 + 0.4) == approx((0.3, 0.6))
|
>>> (0.1 + 0.2, 0.2 + 0.4) == approx((0.3, 0.6))
|
||||||
True
|
True
|
||||||
|
|
||||||
|
Dictionary *values*::
|
||||||
|
|
||||||
>>> {'a': 0.1 + 0.2, 'b': 0.2 + 0.4} == approx({'a': 0.3, 'b': 0.6})
|
>>> {'a': 0.1 + 0.2, 'b': 0.2 + 0.4} == approx({'a': 0.3, 'b': 0.6})
|
||||||
True
|
True
|
||||||
|
|
||||||
|
And ``numpy`` arrays::
|
||||||
|
|
||||||
|
>>> np.array([0.1, 0.2]) + np.array([0.2, 0.4]) == approx(np.array([0.3, 0.6]))
|
||||||
|
True
|
||||||
|
|
||||||
By default, ``approx`` considers numbers within a relative tolerance of
|
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.
|
``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
|
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``.
|
``0.0``, because nothing but ``0.0`` itself is relatively close to ``0.0``.
|
||||||
To handle this case less surprisingly, ``approx`` also considers numbers
|
To handle this case less surprisingly, ``approx`` also considers numbers
|
||||||
within an absolute tolerance of ``1e-12`` of its expected value to be
|
within an absolute tolerance of ``1e-12`` of its expected value to be
|
||||||
equal. Infinite numbers are another special case. They are only
|
equal. Infinity and NaN are special cases. Infinity is only considered
|
||||||
considered equal to themselves, regardless of the relative tolerance. Both
|
equal to itself, regardless of the relative tolerance. NaN is not
|
||||||
the relative and absolute tolerances can be changed by passing arguments to
|
considered equal to anything by default, but you can make it be equal to
|
||||||
the ``approx`` constructor::
|
itself by setting the ``nan_ok`` argument to True. (This is meant to
|
||||||
|
facilitate comparing arrays that use NaN to mean "no data".)
|
||||||
|
|
||||||
|
Both the relative and absolute tolerances can be changed by passing
|
||||||
|
arguments to the ``approx`` constructor::
|
||||||
|
|
||||||
>>> 1.0001 == approx(1)
|
>>> 1.0001 == approx(1)
|
||||||
False
|
False
|
||||||
|
@ -387,10 +396,7 @@ def approx(expected, rel=None, abs=None):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from collections import Mapping, Sequence
|
from collections import Mapping, Sequence
|
||||||
try:
|
from _pytest.compat import STRING_TYPES as String
|
||||||
String = basestring # python2
|
|
||||||
except NameError:
|
|
||||||
String = str, bytes # python3
|
|
||||||
|
|
||||||
# Delegate the comparison to a class that knows how to deal with the type
|
# Delegate the comparison to a class that knows how to deal with the type
|
||||||
# of the expected value (e.g. int, float, list, dict, numpy.array, etc).
|
# of the expected value (e.g. int, float, list, dict, numpy.array, etc).
|
||||||
|
@ -406,8 +412,11 @@ def approx(expected, rel=None, abs=None):
|
||||||
# (i.e. dict). The old code accepted mapping types, but would only compare
|
# (i.e. dict). The old code accepted mapping types, but would only compare
|
||||||
# their keys, which is probably not what most people would expect.
|
# their keys, which is probably not what most people would expect.
|
||||||
|
|
||||||
if np and isinstance(expected, np.ndarray):
|
if _is_numpy_array(expected):
|
||||||
cls = ApproxNumpy
|
# Create the delegate class on the fly. This allow us to inherit from
|
||||||
|
# ``np.ndarray`` while still not importing numpy unless we need to.
|
||||||
|
import numpy as np
|
||||||
|
cls = type('ApproxNumpy', (ApproxNumpyBase, np.ndarray), {})
|
||||||
elif isinstance(expected, Mapping):
|
elif isinstance(expected, Mapping):
|
||||||
cls = ApproxMapping
|
cls = ApproxMapping
|
||||||
elif isinstance(expected, Sequence) and not isinstance(expected, String):
|
elif isinstance(expected, Sequence) and not isinstance(expected, String):
|
||||||
|
@ -415,7 +424,25 @@ def approx(expected, rel=None, abs=None):
|
||||||
else:
|
else:
|
||||||
cls = ApproxScalar
|
cls = ApproxScalar
|
||||||
|
|
||||||
return cls(expected, rel, abs)
|
return cls(expected, rel, abs, nan_ok)
|
||||||
|
|
||||||
|
|
||||||
|
def _is_numpy_array(obj):
|
||||||
|
"""
|
||||||
|
Return true if the given object is a numpy array. Make a special effort to
|
||||||
|
avoid importing numpy unless it's really necessary.
|
||||||
|
"""
|
||||||
|
import inspect
|
||||||
|
|
||||||
|
for cls in inspect.getmro(type(obj)):
|
||||||
|
if cls.__module__ == 'numpy':
|
||||||
|
try:
|
||||||
|
import numpy as np
|
||||||
|
return isinstance(obj, np.ndarray)
|
||||||
|
except ImportError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
# builtin pytest.raises helper
|
# builtin pytest.raises helper
|
||||||
|
@ -555,6 +582,7 @@ def raises(expected_exception, *args, **kwargs):
|
||||||
return _pytest._code.ExceptionInfo()
|
return _pytest._code.ExceptionInfo()
|
||||||
fail(message)
|
fail(message)
|
||||||
|
|
||||||
|
|
||||||
raises.Exception = fail.Exception
|
raises.Exception = fail.Exception
|
||||||
|
|
||||||
class RaisesContext(object):
|
class RaisesContext(object):
|
||||||
|
|
|
@ -218,21 +218,18 @@ class TestApprox(object):
|
||||||
|
|
||||||
def test_expecting_nan(self):
|
def test_expecting_nan(self):
|
||||||
examples = [
|
examples = [
|
||||||
(nan, nan),
|
(eq, nan, nan),
|
||||||
(-nan, -nan),
|
(eq, -nan, -nan),
|
||||||
(nan, -nan),
|
(eq, nan, -nan),
|
||||||
(0.0, nan),
|
(ne, 0.0, nan),
|
||||||
(inf, nan),
|
(ne, inf, nan),
|
||||||
]
|
]
|
||||||
for a, x in examples:
|
for op, a, x in examples:
|
||||||
# If there is a relative tolerance and the expected value is NaN,
|
# Nothing is equal to NaN by default.
|
||||||
# the actual tolerance is a NaN, which should be an error.
|
assert a != approx(x)
|
||||||
with pytest.raises(ValueError):
|
|
||||||
a != approx(x, rel=inf)
|
|
||||||
|
|
||||||
# You can make comparisons against NaN by not specifying a relative
|
# If ``nan_ok=True``, then NaN is equal to NaN.
|
||||||
# tolerance, so only an absolute tolerance is calculated.
|
assert op(a, approx(x, nan_ok=True))
|
||||||
assert a != approx(x, abs=inf)
|
|
||||||
|
|
||||||
def test_int(self):
|
def test_int(self):
|
||||||
within_1e6 = [
|
within_1e6 = [
|
||||||
|
@ -310,8 +307,9 @@ class TestApprox(object):
|
||||||
|
|
||||||
def test_dict(self):
|
def test_dict(self):
|
||||||
actual = {'a': 1 + 1e-7, 'b': 2 + 1e-8}
|
actual = {'a': 1 + 1e-7, 'b': 2 + 1e-8}
|
||||||
expected = {'b': 2, 'a': 1} # Dictionaries became ordered in python3.6,
|
# Dictionaries became ordered in python3.6, so switch up the order here
|
||||||
# so make sure the order doesn't matter
|
# to make sure it doesn't matter.
|
||||||
|
expected = {'b': 2, 'a': 1}
|
||||||
|
|
||||||
# Return false if any element is outside the tolerance.
|
# Return false if any element is outside the tolerance.
|
||||||
assert actual == approx(expected, rel=5e-7, abs=0)
|
assert actual == approx(expected, rel=5e-7, abs=0)
|
||||||
|
@ -325,10 +323,7 @@ class TestApprox(object):
|
||||||
assert {'a': 1, 'b': 2} != approx({'a': 1, 'b': 2, 'c': 3})
|
assert {'a': 1, 'b': 2} != approx({'a': 1, 'b': 2, 'c': 3})
|
||||||
|
|
||||||
def test_numpy_array(self):
|
def test_numpy_array(self):
|
||||||
try:
|
np = pytest.importorskip('numpy')
|
||||||
import numpy as np
|
|
||||||
except ImportError:
|
|
||||||
pytest.skip("numpy not installed")
|
|
||||||
|
|
||||||
actual = np.array([1 + 1e-7, 2 + 1e-8])
|
actual = np.array([1 + 1e-7, 2 + 1e-8])
|
||||||
expected = np.array([1, 2])
|
expected = np.array([1, 2])
|
||||||
|
@ -339,30 +334,27 @@ class TestApprox(object):
|
||||||
assert approx(expected, rel=5e-7, abs=0) == expected
|
assert approx(expected, rel=5e-7, abs=0) == expected
|
||||||
assert approx(expected, rel=5e-8, abs=0) != actual
|
assert approx(expected, rel=5e-8, abs=0) != actual
|
||||||
|
|
||||||
def test_numpy_array_wrong_shape(self):
|
# Should be able to compare lists with numpy arrays.
|
||||||
try:
|
assert list(actual) == approx(expected, rel=5e-7, abs=0)
|
||||||
import numpy as np
|
assert list(actual) != approx(expected, rel=5e-8, abs=0)
|
||||||
except ImportError:
|
assert actual == approx(list(expected), rel=5e-7, abs=0)
|
||||||
pytest.skip("numpy not installed")
|
assert actual != approx(list(expected), rel=5e-8, abs=0)
|
||||||
|
|
||||||
|
def test_numpy_array_wrong_shape(self):
|
||||||
|
np = pytest.importorskip('numpy')
|
||||||
|
|
||||||
import numpy as np
|
|
||||||
a12 = np.array([[1, 2]])
|
a12 = np.array([[1, 2]])
|
||||||
a21 = np.array([[1],[2]])
|
a21 = np.array([[1],[2]])
|
||||||
|
|
||||||
assert a12 != approx(a21)
|
assert a12 != approx(a21)
|
||||||
assert a21 != approx(a12)
|
assert a21 != approx(a12)
|
||||||
|
|
||||||
def test_non_number(self):
|
|
||||||
with pytest.raises(ValueError):
|
|
||||||
1 == approx("1")
|
|
||||||
with pytest.raises(ValueError):
|
|
||||||
"1" == approx(1)
|
|
||||||
|
|
||||||
def test_doctests(self):
|
def test_doctests(self):
|
||||||
|
np = pytest.importorskip('numpy')
|
||||||
parser = doctest.DocTestParser()
|
parser = doctest.DocTestParser()
|
||||||
test = parser.get_doctest(
|
test = parser.get_doctest(
|
||||||
approx.__doc__,
|
approx.__doc__,
|
||||||
{'approx': approx},
|
{'approx': approx ,'np': np},
|
||||||
approx.__name__,
|
approx.__name__,
|
||||||
None, None,
|
None, None,
|
||||||
)
|
)
|
||||||
|
|
Loading…
Reference in New Issue