From 240d314f364061504bc0c7a75f3ada974490629d Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Fri, 19 Jul 2019 21:59:43 +0200 Subject: [PATCH 1/2] copy test and changelog from #5607 by @niccodemus --- changelog/5606.bugfix.rst | 2 ++ testing/python/integration.py | 28 ++++++++++++++++++++++++++++ 2 files changed, 30 insertions(+) create mode 100644 changelog/5606.bugfix.rst diff --git a/changelog/5606.bugfix.rst b/changelog/5606.bugfix.rst new file mode 100644 index 000000000..82332ba99 --- /dev/null +++ b/changelog/5606.bugfix.rst @@ -0,0 +1,2 @@ +Fixed internal error when test functions were patched with objects that cannot be compared +for truth values against others, like ``numpy`` arrays. diff --git a/testing/python/integration.py b/testing/python/integration.py index a6b8dddf3..73419eef4 100644 --- a/testing/python/integration.py +++ b/testing/python/integration.py @@ -178,6 +178,34 @@ class TestMockDecoration: reprec = testdir.inline_run() reprec.assertoutcome(passed=2) + def test_mock_sentinel_check_against_numpy_like(self, testdir): + """Ensure our function that detects mock arguments compares against sentinels using + identity to circumvent objects which can't be compared with equality against others + in a truth context, like with numpy arrays (#5606). + """ + testdir.makepyfile( + dummy=""" + class NumpyLike: + def __init__(self, value): + self.value = value + def __eq__(self, other): + raise ValueError("like numpy, cannot compare against others for truth") + FOO = NumpyLike(10) + """ + ) + testdir.makepyfile( + """ + from unittest.mock import patch + import dummy + class Test(object): + @patch("dummy.FOO", new=dummy.NumpyLike(50)) + def test_hello(self): + assert dummy.FOO.value == 50 + """ + ) + reprec = testdir.inline_run() + reprec.assertoutcome(passed=1) + def test_mock(self, testdir): pytest.importorskip("mock", "1.0.1") testdir.makepyfile( From 8c7d9124bae66b9f4b0152b28e0729c7211b8488 Mon Sep 17 00:00:00 2001 From: Ronny Pfannschmidt Date: Fri, 19 Jul 2019 22:10:45 +0200 Subject: [PATCH 2/2] switch num_mock_patch_args to do identity checks for the sentinels --- src/_pytest/compat.py | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/src/_pytest/compat.py b/src/_pytest/compat.py index d238061b4..52ffc36bc 100644 --- a/src/_pytest/compat.py +++ b/src/_pytest/compat.py @@ -64,13 +64,18 @@ def num_mock_patch_args(function): patchings = getattr(function, "patchings", None) if not patchings: return 0 - mock_modules = [sys.modules.get("mock"), sys.modules.get("unittest.mock")] - if any(mock_modules): - sentinels = [m.DEFAULT for m in mock_modules if m is not None] - return len( - [p for p in patchings if not p.attribute_name and p.new in sentinels] - ) - return len(patchings) + + mock_sentinel = getattr(sys.modules.get("mock"), "DEFAULT", object()) + ut_mock_sentinel = getattr(sys.modules.get("unittest.mock"), "DEFAULT", object()) + + return len( + [ + p + for p in patchings + if not p.attribute_name + and (p.new is mock_sentinel or p.new is ut_mock_sentinel) + ] + ) def getfuncargnames(function, is_method=False, cls=None):