-
-
Notifications
You must be signed in to change notification settings - Fork 2.7k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Miscellaneous improvements to approx() #3741
Conversation
Hi @kalekundert, thanks a lot for the PR. I did not take a good look at it yet, but a cursory glance reminds me that we should add changelog notes to the PR. Feel free to add more than one if you feel like it. 👍 |
CHANGELOG.rst
Outdated
@@ -54,6 +54,9 @@ Bug Fixes | |||
- `#3695 <https://github.com/pytest-dev/pytest/issues/3695>`_: Fix ``ApproxNumpy`` initialisation argument mixup, ``abs`` and ``rel`` tolerances were flipped causing strange comparsion results. | |||
Add tests to check ``abs`` and ``rel`` tolerances for ``np.array`` and test for expecting ``nan`` with ``np.array()`` | |||
|
|||
- `#3712 <https://github.com/pytest-dev/pytest/issues/3712>`_: Correctly represent the dimensions of an numpy array when calling ``repr()`` on ``approx()``. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry, I should have been more clear: you should add changelog
files to the changelog/
folder. See the README on https://github.com/pytest-dev/pytest/tree/master/changelog for more instructions. 👍
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nevermind, I did some a small tweak in your branch and fixed the changelog notes myself. 👍 😁
* Hide the internal traceback * Use !r representation instead of !s (the default for {} formatting)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Awesome work, thank you a lot!
src/_pytest/python_api.py
Outdated
for x in self.expected.values(): | ||
if isinstance(x, type(self.expected)): | ||
raise TypeError( | ||
"pytest.approx() does not support nested dictionaries, e.g. {!r}".format( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
for extra support this should also include the key first and it might help to use "pretty formatting" to split larger dicts to something readable
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good idea, done:
____________________________________ test1 ____________________________________
def test1():
> pytest.approx({'a': 1.2, 'b': {1:2}})
E TypeError: pytest.approx() does not support nested dictionaries: key='b' value={1: 2}
E full mapping={'a': 1.2, 'b': {1: 2}}
.tmp\foo.py:5: TypeError
____________________________________ test2 ____________________________________
def test2():
> pytest.approx([1,2,3, [1,2,3]])
E TypeError: pytest.approx() does not support nested data structures: [1, 2, 3] at index 3
E full sequence: [1, 2, 3, [1, 2, 3]]
.tmp\foo.py:8: TypeError
____________________________________ test3 ____________________________________
def test3():
> pytest.approx('foo')
E TypeError: cannot make approximate comparisons to non-numeric values: 'foo'
.tmp\foo.py:11: TypeError
____________________________________ test4 ____________________________________
def test4():
> pytest.approx([1,2,3, ['foo']])
E TypeError: pytest.approx() does not support nested data structures: ['foo'] at index 3
E full sequence: [1, 2, 3, ['foo']]
.tmp\foo.py:14: TypeError
____________________________________ test5 ____________________________________
def test5():
> pytest.approx({'a': 1.2, 'b': {1:'foo'}})
E TypeError: pytest.approx() does not support nested dictionaries: key='b' value={1: 'foo'}
E full mapping={'a': 1.2, 'b': {1: 'foo'}}
.tmp\foo.py:17: TypeError
========================== 5 failed in 0.12 seconds ===========================
testing/python/approx.py
Outdated
np_array = np.array([5.]) | ||
assert approx(np_array) == 5.0 | ||
assert repr(approx(np_array)) == string_expected | ||
examples = [ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
can we put the parameter to np.array
into a parametrize - the importorskip
and wrapping can stay in the test
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
as extra note i realized that is_numpy_array can be massively simplified by sys.modules.get('numpy')
followed by a getattr(...., 'ndarray', None)
also a potential follow-up is support for pypys
micronumpy`
src/_pytest/python_api.py
Outdated
# It might be nice to rewrite this function to account for the | ||
# shape of the array... | ||
import numpy as np | ||
def recursive_map(f, x): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this helper is independent, lets move it out
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
for key, value in self.expected.items(): | ||
if isinstance(value, type(self.expected)): | ||
msg = "pytest.approx() does not support nested dictionaries: key={!r} value={!r}\n full mapping={}" | ||
raise TypeError(msg.format(key, value, pprint.pformat(self.expected))) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
lovely 👍
testing/python/approx.py
Outdated
@@ -439,6 +443,13 @@ def test_foo(): | |||
["*At index 0 diff: 3 != 4 * {}".format(expected), "=* 1 failed in *="] | |||
) | |||
|
|||
@pytest.mark.parametrize( | |||
"x", [None, "string", ["string"], [[1]], {"key": "string"}, {"key": {"key": 1}}] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
i believe test ids would help here
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done 👍
Done as well, let me know if that's what you had in mind. 👍 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
fabulous 👍
src/_pytest/python_api.py
Outdated
pass | ||
|
||
|
||
def recursive_map(f, x): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm a little hestiant about this being a module-level function, because it only works for nested lists (e.g. not tuples or any other kind of iterable). That's fine for ApproxNumpy.__repr__()
, because numpy.tolist()
is guaranteed to return nested lists, but this isn't really a generally useful function.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You got a point, but I think it doesn't hurt leaving it there; it is not part of the public API after all. @RonnyPfannschmidt any thoughts?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
name it recursive_list_map
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done, renamed it _recursive_list_map
as well to make it clear it is an internal API.
Thanks for all the revisions! |
This PR fixes a couple small issues with
approx()
:repr(approx(np.array(...)))
to reflect the dimension of the array being compared. For example,repr(approx([[1, 2], [3, 4]]))
would look something likeapprox([[1±..., 2±...], [3±..., 4±...]])
rather thanapprox([1±..., 2±..., 3±..., 4±...])
. This was supposed to fix Broken output when a multidimensional NumPy array fails to be approximate something #3712, but it turns out that Bug fix #3593 - approx repr in a 0-d numpy array #3615 already fixed it. Still, I think it's nice to have a more accurate repr.