diff --git a/doc/en/_templates/slim_searchbox.html b/doc/en/_templates/slim_searchbox.html index e98ad4ed905..f088ff8d312 100644 --- a/doc/en/_templates/slim_searchbox.html +++ b/doc/en/_templates/slim_searchbox.html @@ -5,11 +5,10 @@ - + {%- endif %} diff --git a/doc/en/announce/index.rst b/doc/en/announce/index.rst index 78045114667..5374e8c7596 100644 --- a/doc/en/announce/index.rst +++ b/doc/en/announce/index.rst @@ -6,6 +6,7 @@ Release announcements :maxdepth: 2 + release-8.0.2 release-8.0.1 release-8.0.0 release-8.0.0rc2 diff --git a/doc/en/announce/release-8.0.2.rst b/doc/en/announce/release-8.0.2.rst new file mode 100644 index 00000000000..c42159c57cf --- /dev/null +++ b/doc/en/announce/release-8.0.2.rst @@ -0,0 +1,18 @@ +pytest-8.0.2 +======================================= + +pytest 8.0.2 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: + +* Ran Benita + + +Happy testing, +The pytest Development Team diff --git a/doc/en/changelog.rst b/doc/en/changelog.rst index f5019541ae3..f01842c9b92 100644 --- a/doc/en/changelog.rst +++ b/doc/en/changelog.rst @@ -28,6 +28,21 @@ with advance notice in the **Deprecations** section of releases. .. towncrier release notes start +pytest 8.0.2 (2024-02-24) +========================= + +Bug Fixes +--------- + +- `#11895 `_: Fix collection on Windows where initial paths contain the short version of a path (for example ``c:\PROGRA~1\tests``). + + +- `#11953 `_: Fix an ``IndexError`` crash raising from ``getstatementrange_ast``. + + +- `#12021 `_: Reverted a fix to `--maxfail` handling in pytest 8.0.0 because it caused a regression in pytest-xdist whereby session fixture teardowns may get executed multiple times when the max-fails is reached. + + pytest 8.0.1 (2024-02-16) ========================= @@ -85,6 +100,8 @@ Bug Fixes - `#11706 `_: Fix reporting of teardown errors in higher-scoped fixtures when using `--maxfail` or `--stepwise`. + NOTE: This change was reverted in pytest 8.0.2 to fix a `regression `_ it caused in pytest-xdist. + - `#11758 `_: Fixed ``IndexError: string index out of range`` crash in ``if highlighted[-1] == "\n" and source[-1] != "\n"``. This bug was introduced in pytest 8.0.0rc1. diff --git a/doc/en/example/parametrize.rst b/doc/en/example/parametrize.rst index 99afaded84c..c6ac6489979 100644 --- a/doc/en/example/parametrize.rst +++ b/doc/en/example/parametrize.rst @@ -162,7 +162,7 @@ objects, they are still using the default pytest representation: rootdir: /home/sweet/project collected 8 items - + @@ -239,7 +239,7 @@ If you just collect tests you'll also nicely see 'advanced' and 'basic' as varia rootdir: /home/sweet/project collected 4 items - + @@ -318,7 +318,7 @@ Let's first see how it looks like at collection time: rootdir: /home/sweet/project collected 2 items - + diff --git a/doc/en/example/pythoncollection.rst b/doc/en/example/pythoncollection.rst index 89acb7d3b65..7207ca2ae63 100644 --- a/doc/en/example/pythoncollection.rst +++ b/doc/en/example/pythoncollection.rst @@ -152,7 +152,7 @@ The test collection would look like this: configfile: pytest.ini collected 2 items - + @@ -215,7 +215,7 @@ You can always peek at the collection tree without running tests like this: configfile: pytest.ini collected 3 items - + diff --git a/doc/en/getting-started.rst b/doc/en/getting-started.rst index 114d69328c2..f1919886495 100644 --- a/doc/en/getting-started.rst +++ b/doc/en/getting-started.rst @@ -22,7 +22,7 @@ Install ``pytest`` .. code-block:: bash $ pytest --version - pytest 8.0.1 + pytest 8.0.2 .. _`simpletest`: diff --git a/doc/en/how-to/fixtures.rst b/doc/en/how-to/fixtures.rst index 281b0d7f284..960e60c92b4 100644 --- a/doc/en/how-to/fixtures.rst +++ b/doc/en/how-to/fixtures.rst @@ -1418,7 +1418,7 @@ Running the above tests results in the following test IDs being used: rootdir: /home/sweet/project collected 12 items - + diff --git a/doc/en/requirements.txt b/doc/en/requirements.txt index 36801746aae..52415740470 100644 --- a/doc/en/requirements.txt +++ b/doc/en/requirements.txt @@ -2,7 +2,7 @@ pallets-sphinx-themes pluggy>=1.2.0 pygments-pytest>=2.3.0 sphinx-removed-in>=0.2.0 -sphinx>=5,<8 +sphinx>=7 sphinxcontrib-trio sphinxcontrib-svg2pdfconverter # Pin packaging because it no longer handles 'latest' version, which diff --git a/src/_pytest/_code/source.py b/src/_pytest/_code/source.py index 359da868c2d..835cd1d7b6a 100644 --- a/src/_pytest/_code/source.py +++ b/src/_pytest/_code/source.py @@ -196,7 +196,9 @@ def getstatementrange_ast( # by using the BlockFinder helper used which inspect.getsource() uses itself. block_finder = inspect.BlockFinder() # If we start with an indented line, put blockfinder to "started" mode. - block_finder.started = source.lines[start][0].isspace() + block_finder.started = ( + bool(source.lines[start]) and source.lines[start][0].isspace() + ) it = ((x + "\n") for x in source.lines[start:end]) try: for tok in tokenize.generate_tokens(lambda: next(it)): diff --git a/src/_pytest/main.py b/src/_pytest/main.py index f1c05754b2f..fd9dddfa318 100644 --- a/src/_pytest/main.py +++ b/src/_pytest/main.py @@ -902,6 +902,10 @@ def collect(self) -> Iterator[Union[nodes.Item, nodes.Collector]]: # Path part e.g. `/a/b/` in `/a/b/test_file.py::TestIt::test_it`. if isinstance(matchparts[0], Path): is_match = node.path == matchparts[0] + if sys.platform == "win32" and not is_match: + # In case the file paths do not match, fallback to samefile() to + # account for short-paths on Windows (#11895). + is_match = os.path.samefile(node.path, matchparts[0]) # Name part e.g. `TestIt` in `/a/b/test_file.py::TestIt::test_it`. else: # TODO: Remove parametrized workaround once collection structure contains diff --git a/src/_pytest/runner.py b/src/_pytest/runner.py index 131b125ed59..5befb0af11c 100644 --- a/src/_pytest/runner.py +++ b/src/_pytest/runner.py @@ -132,10 +132,6 @@ def runtestprotocol( show_test_item(item) if not item.config.getoption("setuponly", False): reports.append(call_and_report(item, "call", log)) - # If the session is about to fail or stop, teardown everything - this is - # necessary to correctly report fixture teardown errors (see #11706) - if item.session.shouldfail or item.session.shouldstop: - nextitem = None reports.append(call_and_report(item, "teardown", log, nextitem=nextitem)) # After all teardown hooks have been called # want funcargs and request info to go away. diff --git a/testing/test_collection.py b/testing/test_collection.py index b2780eb73ae..c7923c5efb6 100644 --- a/testing/test_collection.py +++ b/testing/test_collection.py @@ -3,9 +3,11 @@ import pprint import shutil import sys +import tempfile import textwrap from typing import List +from _pytest.assertion.util import running_on_ci from _pytest.config import ExitCode from _pytest.fixtures import FixtureRequest from _pytest.main import _in_venv @@ -1758,3 +1760,29 @@ def test_foo(): assert True assert result.ret == ExitCode.OK assert result.parseoutcomes() == {"passed": 1} + + +@pytest.mark.skipif(not sys.platform.startswith("win"), reason="Windows only") +def test_collect_short_file_windows(pytester: Pytester) -> None: + """Reproducer for #11895: short paths not colleced on Windows.""" + short_path = tempfile.mkdtemp() + if "~" not in short_path: # pragma: no cover + if running_on_ci(): + # On CI, we are expecting that under the current GitHub actions configuration, + # tempfile.mkdtemp() is producing short paths, so we want to fail to prevent + # this from silently changing without us noticing. + pytest.fail( + f"tempfile.mkdtemp() failed to produce a short path on CI: {short_path}" + ) + else: + # We want to skip failing this test locally in this situation because + # depending on the local configuration tempfile.mkdtemp() might not produce a short path: + # For example, user might have configured %TEMP% exactly to avoid generating short paths. + pytest.skip( + f"tempfile.mkdtemp() failed to produce a short path: {short_path}, skipping" + ) + + test_file = Path(short_path).joinpath("test_collect_short_file_windows.py") + test_file.write_text("def test(): pass", encoding="UTF-8") + result = pytester.runpytest(short_path) + assert result.parseoutcomes() == {"passed": 1} diff --git a/testing/test_runner.py b/testing/test_runner.py index 769ce149234..d0fae834bcd 100644 --- a/testing/test_runner.py +++ b/testing/test_runner.py @@ -1088,53 +1088,3 @@ def func() -> None: with pytest.raises(TypeError) as excinfo: OutcomeException(func) # type: ignore assert str(excinfo.value) == expected - - -def test_teardown_session_failed(pytester: Pytester) -> None: - """Test that higher-scoped fixture teardowns run in the context of the last - item after the test session bails early due to --maxfail. - - Regression test for #11706. - """ - pytester.makepyfile( - """ - import pytest - - @pytest.fixture(scope="module") - def baz(): - yield - pytest.fail("This is a failing teardown") - - def test_foo(baz): - pytest.fail("This is a failing test") - - def test_bar(): pass - """ - ) - result = pytester.runpytest("--maxfail=1") - result.assert_outcomes(failed=1, errors=1) - - -def test_teardown_session_stopped(pytester: Pytester) -> None: - """Test that higher-scoped fixture teardowns run in the context of the last - item after the test session bails early due to --stepwise. - - Regression test for #11706. - """ - pytester.makepyfile( - """ - import pytest - - @pytest.fixture(scope="module") - def baz(): - yield - pytest.fail("This is a failing teardown") - - def test_foo(baz): - pytest.fail("This is a failing test") - - def test_bar(): pass - """ - ) - result = pytester.runpytest("--stepwise") - result.assert_outcomes(failed=1, errors=1)