Imported Upstream version 6.2.1 upstream/6.2.1
authorDongHun Kwak <dh0128.kwak@samsung.com>
Thu, 18 Mar 2021 02:33:45 +0000 (11:33 +0900)
committerDongHun Kwak <dh0128.kwak@samsung.com>
Thu, 18 Mar 2021 02:33:45 +0000 (11:33 +0900)
doc/en/announce/index.rst
doc/en/announce/release-6.2.1.rst [new file with mode: 0644]
doc/en/changelog.rst
doc/en/getting-started.rst
src/_pytest/pathlib.py
src/_pytest/python_api.py
testing/python/approx.py
testing/test_pathlib.py

index 003a0a1a9cae776bf43215811c126d7f8b3b286a..e7cac2a1c41c5ab558d6059c50d50b74874a3589 100644 (file)
@@ -6,6 +6,7 @@ Release announcements
    :maxdepth: 2
 
 
+   release-6.2.1
    release-6.2.0
    release-6.1.2
    release-6.1.1
diff --git a/doc/en/announce/release-6.2.1.rst b/doc/en/announce/release-6.2.1.rst
new file mode 100644 (file)
index 0000000..f9e7161
--- /dev/null
@@ -0,0 +1,20 @@
+pytest-6.2.1
+=======================================
+
+pytest 6.2.1 has just been released to PyPI.
+
+This is a bug-fix release, being a drop-in replacement. To upgrade::
+
+  pip install --upgrade pytest
+
+The full changelog is available at https://docs.pytest.org/en/stable/changelog.html.
+
+Thanks to all of the contributors to this release:
+
+* Bruno Oliveira
+* Jakob van Santen
+* Ran Benita
+
+
+Happy testing,
+The pytest Development Team
index 77340b1bb849e7e97255f73bd0d76d4fc2e47799..6d66ad1d8dc8b544655d22687a62001e49840434 100644 (file)
@@ -28,6 +28,28 @@ with advance notice in the **Deprecations** section of releases.
 
 .. towncrier release notes start
 
+pytest 6.2.1 (2020-12-15)
+=========================
+
+Bug Fixes
+---------
+
+- `#7678 <https://github.com/pytest-dev/pytest/issues/7678>`_: Fixed bug where ``ImportPathMismatchError`` would be raised for files compiled in
+  the host and loaded later from an UNC mounted path (Windows).
+
+
+- `#8132 <https://github.com/pytest-dev/pytest/issues/8132>`_: Fixed regression in ``approx``: in 6.2.0 ``approx`` no longer raises
+  ``TypeError`` when dealing with non-numeric types, falling back to normal comparison.
+  Before 6.2.0, array types like tf.DeviceArray fell through to the scalar case,
+  and happened to compare correctly to a scalar if they had only one element.
+  After 6.2.0, these types began failing, because they inherited neither from
+  standard Python number hierarchy nor from ``numpy.ndarray``.
+
+  ``approx`` now converts arguments to ``numpy.ndarray`` if they expose the array
+  protocol and are not scalars. This treats array-like objects like numpy arrays,
+  regardless of size.
+
+
 pytest 6.2.0 (2020-12-12)
 =========================
 
index fe15c218cdee0e1feee36319acf7ba9ff64981cd..09410585dc7af8444e6f2bf3db15401ce3075c95 100644 (file)
@@ -28,7 +28,7 @@ Install ``pytest``
 .. code-block:: bash
 
     $ pytest --version
-    pytest 6.2.0
+    pytest 6.2.1
 
 .. _`simpletest`:
 
index 6a36ae17ab2724626724c253927c5e1a7eaa56d1..8875a28f84b62fe765b210a20c81c153fd0779bf 100644 (file)
@@ -543,7 +543,7 @@ def import_path(
             module_file = module_file[: -(len(os.path.sep + "__init__.py"))]
 
         try:
-            is_same = os.path.samefile(str(path), module_file)
+            is_same = _is_same(str(path), module_file)
         except FileNotFoundError:
             is_same = False
 
@@ -553,6 +553,20 @@ def import_path(
     return mod
 
 
+# Implement a special _is_same function on Windows which returns True if the two filenames
+# compare equal, to circumvent os.path.samefile returning False for mounts in UNC (#7678).
+if sys.platform.startswith("win"):
+
+    def _is_same(f1: str, f2: str) -> bool:
+        return Path(f1) == Path(f2) or os.path.samefile(f1, f2)
+
+
+else:
+
+    def _is_same(f1: str, f2: str) -> bool:
+        return os.path.samefile(f1, f2)
+
+
 def resolve_package_path(path: Path) -> Optional[Path]:
     """Return the Python package path by looking for the last
     directory upwards which still contains an __init__.py.
index bae2076892b581546ceff1b7f92f227528e1b873..81ce4f89539b7718ca2b36ca916a58bef9ef2cdc 100644 (file)
@@ -15,9 +15,14 @@ from typing import overload
 from typing import Pattern
 from typing import Tuple
 from typing import Type
+from typing import TYPE_CHECKING
 from typing import TypeVar
 from typing import Union
 
+if TYPE_CHECKING:
+    from numpy import ndarray
+
+
 import _pytest._code
 from _pytest.compat import final
 from _pytest.compat import STRING_TYPES
@@ -232,10 +237,11 @@ class ApproxScalar(ApproxBase):
     def __eq__(self, actual) -> bool:
         """Return whether the given value is equal to the expected value
         within the pre-specified tolerance."""
-        if _is_numpy_array(actual):
+        asarray = _as_numpy_array(actual)
+        if asarray is not None:
             # Call ``__eq__()`` manually to prevent infinite-recursion with
             # numpy<1.13.  See #3748.
-            return all(self.__eq__(a) for a in actual.flat)
+            return all(self.__eq__(a) for a in asarray.flat)
 
         # Short-circuit exact equality.
         if actual == self.expected:
@@ -521,6 +527,7 @@ def approx(expected, rel=None, abs=None, nan_ok: bool = False) -> ApproxBase:
     elif isinstance(expected, Mapping):
         cls = ApproxMapping
     elif _is_numpy_array(expected):
+        expected = _as_numpy_array(expected)
         cls = ApproxNumpy
     elif (
         isinstance(expected, Iterable)
@@ -536,16 +543,30 @@ def approx(expected, rel=None, abs=None, nan_ok: bool = False) -> ApproxBase:
 
 
 def _is_numpy_array(obj: object) -> bool:
-    """Return true if the given object is a numpy array.
+    """
+    Return true if the given object is implicitly convertible to ndarray,
+    and numpy is already imported.
+    """
+    return _as_numpy_array(obj) is not None
+
 
-    A special effort is made to avoid importing numpy unless it's really necessary.
+def _as_numpy_array(obj: object) -> Optional["ndarray"]:
+    """
+    Return an ndarray if the given object is implicitly convertible to ndarray,
+    and numpy is already imported, otherwise None.
     """
     import sys
 
     np: Any = sys.modules.get("numpy")
     if np is not None:
-        return isinstance(obj, np.ndarray)
-    return False
+        # avoid infinite recursion on numpy scalars, which have __array__
+        if np.isscalar(obj):
+            return None
+        elif isinstance(obj, np.ndarray):
+            return obj
+        elif hasattr(obj, "__array__") or hasattr("obj", "__array_interface__"):
+            return np.asarray(obj)
+    return None
 
 
 # builtin pytest.raises helper
index 91c1f3f85de70de55edc57c147b57d3c782d41fb..e76d6b774d6fff7f1c7b950da7f89d98cd9d6cac 100644 (file)
@@ -447,6 +447,36 @@ class TestApprox:
         assert a12 != approx(a21)
         assert a21 != approx(a12)
 
+    def test_numpy_array_protocol(self):
+        """
+        array-like objects such as tensorflow's DeviceArray are handled like ndarray.
+        See issue #8132
+        """
+        np = pytest.importorskip("numpy")
+
+        class DeviceArray:
+            def __init__(self, value, size):
+                self.value = value
+                self.size = size
+
+            def __array__(self):
+                return self.value * np.ones(self.size)
+
+        class DeviceScalar:
+            def __init__(self, value):
+                self.value = value
+
+            def __array__(self):
+                return np.array(self.value)
+
+        expected = 1
+        actual = 1 + 1e-6
+        assert approx(expected) == DeviceArray(actual, size=1)
+        assert approx(expected) == DeviceArray(actual, size=2)
+        assert approx(expected) == DeviceScalar(actual)
+        assert approx(DeviceScalar(expected)) == actual
+        assert approx(DeviceScalar(expected)) == DeviceScalar(actual)
+
     def test_doctests(self, mocked_doctest_runner) -> None:
         import doctest
 
index 0507e3d68666d19d684578f7b32551ef4958fabc..f60b9f26369e63cb6da1846f88bd7a12bedb4eff 100644 (file)
@@ -7,6 +7,7 @@ from textwrap import dedent
 import py
 
 import pytest
+from _pytest.monkeypatch import MonkeyPatch
 from _pytest.pathlib import bestrelpath
 from _pytest.pathlib import commonpath
 from _pytest.pathlib import ensure_deletable
@@ -414,3 +415,23 @@ def test_visit_ignores_errors(tmpdir) -> None:
         "bar",
         "foo",
     ]
+
+
+@pytest.mark.skipif(not sys.platform.startswith("win"), reason="Windows only")
+def test_samefile_false_negatives(tmp_path: Path, monkeypatch: MonkeyPatch) -> None:
+    """
+    import_file() should not raise ImportPathMismatchError if the paths are exactly
+    equal on Windows. It seems directories mounted as UNC paths make os.path.samefile
+    return False, even when they are clearly equal.
+    """
+    module_path = tmp_path.joinpath("my_module.py")
+    module_path.write_text("def foo(): return 42")
+    monkeypatch.syspath_prepend(tmp_path)
+
+    with monkeypatch.context() as mp:
+        # Forcibly make os.path.samefile() return False here to ensure we are comparing
+        # the paths too. Using a context to narrow the patch as much as possible given
+        # this is an important system function.
+        mp.setattr(os.path, "samefile", lambda x, y: False)
+        module = import_path(module_path)
+    assert getattr(module, "foo")() == 42