Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Do not collect symlinked tests under Windows #12050

Merged
merged 7 commits into from
Mar 3, 2024
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions AUTHORS
Original file line number Diff line number Diff line change
Expand Up @@ -283,6 +283,7 @@ Mike Hoyle (hoylemd)
Mike Lundy
Milan Lesnek
Miro Hrončok
mrbean-bremen
Nathaniel Compton
Nathaniel Waisbrot
Ned Batchelder
Expand Down
1 change: 1 addition & 0 deletions changelog/12039.bugfix.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Fixed a regression in ``8.0.2`` where tests created using :fixture:`tmp_path` have been collected multiple times in CI under Windows.
8 changes: 7 additions & 1 deletion src/_pytest/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -924,7 +924,13 @@
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])
same_file = os.path.samefile(node.path, matchparts[0])

Check warning on line 927 in src/_pytest/main.py

View check run for this annotation

Codecov / codecov/patch

src/_pytest/main.py#L927

Added line #L927 was not covered by tests
# we don't want to find links, so we at least
# exclude symlinks to regular directories
is_match = same_file and os.path.islink(

Check warning on line 930 in src/_pytest/main.py

View check run for this annotation

Codecov / codecov/patch

src/_pytest/main.py#L930

Added line #L930 was not covered by tests
node.path
) == os.path.islink(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
Expand Down
31 changes: 30 additions & 1 deletion testing/test_collection.py
Original file line number Diff line number Diff line change
Expand Up @@ -1765,7 +1765,7 @@ def test_foo(): assert True

@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."""
"""Reproducer for #11895: short paths not collected on Windows."""
short_path = tempfile.mkdtemp()
if "~" not in short_path: # pragma: no cover
if running_on_ci():
Expand All @@ -1789,6 +1789,26 @@ def test_collect_short_file_windows(pytester: Pytester) -> None:
assert result.parseoutcomes() == {"passed": 1}


def test_not_collect_symlink_syblings(
pytester: Pytester, tmp_path: Path, request: pytest.FixtureRequest
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @nicoddemus, that's much better! Nitpicking: its "siblings", not "syblings".

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks!

) -> None:
"""
Do not collect from directories that are symlinks to other directories in the same path.

The check for short paths under Windows via os.path.samefile, introduced in #11936, also finds the symlinked
directory created by tmp_path/tmpdir.

#12039
"""
# Use tmp_path because it creates a symlink with the name "current" next to the directory it creates.
assert tmp_path.parent.joinpath(f"{request.node.name}current").is_symlink() is True

tmp_path.joinpath("test_foo.py").write_text("def test(): pass", encoding="UTF-8")

result = pytester.runpytest_subprocess(tmp_path, "-sv")
result.assert_outcomes(passed=1)


def test_pyargs_collection_tree(pytester: Pytester, monkeypatch: MonkeyPatch) -> None:
"""When using `--pyargs`, the collection tree of a pyargs collection
argument should only include parents in the import path, not up to confcutdir.
Expand Down Expand Up @@ -1832,3 +1852,12 @@ def test_pyargs_collection_tree(pytester: Pytester, monkeypatch: MonkeyPatch) ->
],
consecutive=True,
)


def test_collect_symlinks(pytester: Pytester, tmpdir) -> None:
"""Regression test for #12039: Tests collected multiple times under Windows."""
test_file = Path(tmpdir) / "symlink_collection_test.py"
test_file.write_text("def test(): pass", encoding="UTF-8")
result = pytester.runpytest(tmpdir)
# this failed in CI only (GitHub actions)
assert result.parseoutcomes() == {"passed": 1}