:maxdepth: 2
+ release-6.2.1
release-6.2.0
release-6.1.2
release-6.1.1
--- /dev/null
+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
.. 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)
=========================
.. code-block:: bash
$ pytest --version
- pytest 6.2.0
+ pytest 6.2.1
.. _`simpletest`:
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
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.
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
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:
elif isinstance(expected, Mapping):
cls = ApproxMapping
elif _is_numpy_array(expected):
+ expected = _as_numpy_array(expected)
cls = ApproxNumpy
elif (
isinstance(expected, Iterable)
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
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
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
"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