diff --git a/changelog/7638.bugfix.rst b/changelog/7638.bugfix.rst new file mode 100644 index 000000000..ea3257b67 --- /dev/null +++ b/changelog/7638.bugfix.rst @@ -0,0 +1 @@ +Fix handling of command-line options that appear as paths but trigger an OS-level syntax error on Windows, such as the options used internally by ``pytest-xdist``. diff --git a/src/_pytest/config/findpaths.py b/src/_pytest/config/findpaths.py index 65120e484..7a2bba5a7 100644 --- a/src/_pytest/config/findpaths.py +++ b/src/_pytest/config/findpaths.py @@ -1,6 +1,5 @@ import itertools import os -import sys from typing import Dict from typing import Iterable from typing import List @@ -146,20 +145,13 @@ def get_dirs_from_args(args: Iterable[str]) -> List[Path]: return path return path.parent - if sys.version_info < (3, 8): - - def safe_exists(path: Path) -> bool: - # On Python<3.8, this can throw on paths that contain characters - # unrepresentable at the OS level. - try: - return path.exists() - except OSError: - return False - - else: - - def safe_exists(path: Path) -> bool: + def safe_exists(path: Path) -> bool: + # This can throw on paths that contain characters unrepresentable at the OS level, + # or with invalid syntax on Windows + try: return path.exists() + except OSError: + return False # These look like paths but may not exist possible_paths = ( diff --git a/testing/test_findpaths.py b/testing/test_findpaths.py index acb982b4c..974dcf8f3 100644 --- a/testing/test_findpaths.py +++ b/testing/test_findpaths.py @@ -2,6 +2,7 @@ from textwrap import dedent import pytest from _pytest.config.findpaths import get_common_ancestor +from _pytest.config.findpaths import get_dirs_from_args from _pytest.config.findpaths import load_config_dict_from_file from _pytest.pathlib import Path @@ -108,3 +109,17 @@ class TestCommonAncestor: fn = tmp_path / "foo.py" fn.touch() assert get_common_ancestor([fn]) == tmp_path + + +def test_get_dirs_from_args(tmp_path): + """get_dirs_from_args() skips over non-existing directories and files""" + fn = tmp_path / "foo.py" + fn.touch() + d = tmp_path / "tests" + d.mkdir() + option = "--foobar=/foo.txt" + # xdist uses options in this format for its rsync feature (#7638) + xdist_rsync_option = "popen=c:/dest" + assert get_dirs_from_args( + [str(fn), str(tmp_path / "does_not_exist"), str(d), option, xdist_rsync_option] + ) == [fn.parent, d]