Repository: wntrblm/nox
Pull request #889 (2024-11-12 04:43:41)

chore: limit the visible items for tab completion
Adding `__all__` and `def __dir__` everwhere so tab completion is clean. Using a trick I've not tried elsewhere to also include tab completion cleanup in `__init__`.



Patch:
diff --git a/.github/workflows/action.yml b/.github/workflows/action.yml
index 62fe99c..4db5a7c 100644
--- a/.github/workflows/action.yml
+++ b/.github/workflows/action.yml
@@ -27,7 +27,7 @@ jobs:
           - macos-14
     steps:
       - uses: actions/checkout@v4
-      - uses: astral-sh/setup-uv@v3
+      - uses: astral-sh/setup-uv@v4
       - uses: ./
       - run: nox --non-interactive --error-on-missing-interpreter --session github_actions_default_tests
 
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 1e3bf8d..d75c8fb 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -44,7 +44,7 @@ jobs:
           python-version: ${{ matrix.python-version }}
           allow-prereleases: true
       - name: Setup uv
-        uses: astral-sh/setup-uv@v3
+        uses: astral-sh/setup-uv@v4
       - name: Set up Miniconda
         uses: conda-incubator/setup-miniconda@v3
         with:
@@ -76,7 +76,7 @@ jobs:
         with:
           python-version: "3.12"
       - name: Setup uv
-        uses: astral-sh/setup-uv@v3
+        uses: astral-sh/setup-uv@v4
       - name: Install Nox-under-test
         run:  uv pip install --system .
       - name: Download individual coverage reports
@@ -110,7 +110,7 @@ jobs:
         with:
           python-version: "3.12"
       - name: Setup uv
-        uses: astral-sh/setup-uv@v3
+        uses: astral-sh/setup-uv@v4
       - name: Install Nox-under-test
         run:  uv pip install --system .
       - name: Docs
diff --git a/nox/__init__.py b/nox/__init__.py
index 5338044..2bded2a 100644
--- a/nox/__init__.py
+++ b/nox/__init__.py
@@ -14,6 +14,8 @@
 
 from __future__ import annotations
 
+from types import ModuleType
+
 from nox import project
 from nox._cli import main
 from nox._options import noxfile_options as options
@@ -34,3 +36,11 @@ __all__ = [
     "project",
     "session",
 ]
+
+
+def __dir__() -> list[str]:
+    # Only nox modules are imported here, so we can safely use globals() to
+    # find nox modules only. Other modules, like types and __future__, are imported
+    # from, so don't populate the module globals with surprising entries.
+    modules = {k for k, v in globals().items() if isinstance(v, ModuleType)}
+    return sorted(set(__all__) | modules)
diff --git a/nox/__main__.py b/nox/__main__.py
index ac183d6..7a4ee45 100644
--- a/nox/__main__.py
+++ b/nox/__main__.py
@@ -26,5 +26,9 @@ from nox._cli import main  # pragma: no cover
 __all__ = ["main"]  # pragma: no cover
 
 
+def __dir__() -> list[str]:
+    return __all__
+
+
 if __name__ == "__main__":  # pragma: no cover
     main()
diff --git a/nox/_cli.py b/nox/_cli.py
index f25dad2..bc69fc3 100644
--- a/nox/_cli.py
+++ b/nox/_cli.py
@@ -23,6 +23,12 @@ from nox import _options, tasks, workflow
 from nox._version import get_nox_version
 from nox.logger import setup_logging
 
+__all__ = ["execute_workflow", "main"]
+
+
+def __dir__() -> list[str]:
+    return __all__
+
 
 def execute_workflow(args: Any) -> int:
     """
diff --git a/nox/_decorators.py b/nox/_decorators.py
index 6de7315..4b4caab 100644
--- a/nox/_decorators.py
+++ b/nox/_decorators.py
@@ -28,6 +28,12 @@ if TYPE_CHECKING:
 
 T = TypeVar("T", bound=Callable[..., Any])
 
+__all__ = ["Call", "Func", "FunctionDecorator", "_copy_func"]
+
+
+def __dir__() -> list[str]:
+    return __all__
+
 
 class FunctionDecorator:
     """This is a function decorator."""
diff --git a/nox/_option_set.py b/nox/_option_set.py
index 10597b9..23a9331 100644
--- a/nox/_option_set.py
+++ b/nox/_option_set.py
@@ -21,8 +21,8 @@ from __future__ import annotations
 import argparse
 import collections
 import functools
-from argparse import ArgumentError as ArgumentError  # noqa: PLC0414
-from argparse import ArgumentParser, Namespace
+from argparse import ArgumentError, ArgumentParser, Namespace
+from collections.abc import Callable, Iterable
 from typing import TYPE_CHECKING, Any, Literal
 
 import argcomplete
@@ -32,6 +32,20 @@ import attrs.validators as av
 if TYPE_CHECKING:
     from collections.abc import Callable, Iterable
 
+__all__ = [
+    "ArgumentError",
+    "NoxOptions",
+    "Option",
+    "OptionGroup",
+    "OptionSet",
+    "make_flag_pair",
+]
+
+
+def __dir__() -> list[str]:
+    return __all__
+
+
 av_opt_str = av.optional(av.instance_of(str))
 av_opt_list_str = av.optional(
     av.deep_iterable(
diff --git a/nox/_options.py b/nox/_options.py
index a98a9bf..10c9515 100644
--- a/nox/_options.py
+++ b/nox/_options.py
@@ -12,6 +12,8 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
+"""All of Nox's configuration options."""
+
 from __future__ import annotations
 
 import argparse
@@ -32,9 +34,15 @@ if TYPE_CHECKING:
 
     from nox._option_set import NoxOptions
 
-ReuseVenvType = Literal["no", "yes", "never", "always"]
 
-"""All of Nox's configuration options."""
+__all__ = ["ReuseVenvType", "noxfile_options", "options"]
+
+
+def __dir__() -> list[str]:
+    return __all__
+
+
+ReuseVenvType = Literal["no", "yes", "never", "always"]
 
 options = _option_set.OptionSet(
     description="Nox is a Python automation toolkit.", add_help=False
diff --git a/nox/_parametrize.py b/nox/_parametrize.py
index 15f802e..e9e42e2 100644
--- a/nox/_parametrize.py
+++ b/nox/_parametrize.py
@@ -21,6 +21,12 @@ from typing import TYPE_CHECKING, Any, Iterable, Union
 if TYPE_CHECKING:
     from collections.abc import Callable, Sequence
 
+__all__ = ["Param", "parametrize_decorator", "update_param_specs"]
+
+
+def __dir__() -> list[str]:
+    return __all__
+
 
 class Param:
     """A class that encapsulates a single set of parameters to a parametrized
diff --git a/nox/_resolver.py b/nox/_resolver.py
index e95af67..06f7171 100644
--- a/nox/_resolver.py
+++ b/nox/_resolver.py
@@ -18,6 +18,13 @@ import itertools
 from collections import OrderedDict
 from typing import Hashable, Iterable, Iterator, Mapping, TypeVar
 
+__all__ = ["CycleError", "lazy_stable_topo_sort"]
+
+
+def __dir__() -> list[str]:
+    return __all__
+
+
 Node = TypeVar("Node", bound=Hashable)
 
 
diff --git a/nox/_typing.py b/nox/_typing.py
index ac2d5ca..12af780 100644
--- a/nox/_typing.py
+++ b/nox/_typing.py
@@ -14,8 +14,13 @@
 
 from __future__ import annotations
 
+from typing import Sequence, Union
+
 __all__ = ["Python"]
 
-from typing import Sequence, Union
+
+def __dir__() -> list[str]:
+    return __all__
+
 
 Python = Union[str, Sequence[str], bool, None]
diff --git a/nox/_version.py b/nox/_version.py
index f233c09..aeddbd9 100644
--- a/nox/_version.py
+++ b/nox/_version.py
@@ -21,6 +21,12 @@ from importlib import metadata
 from packaging.specifiers import InvalidSpecifier, SpecifierSet
 from packaging.version import InvalidVersion, Version
 
+__all__ = ["InvalidVersionSpecifier", "VersionCheckFailed", "check_nox_version"]
+
+
+def __dir__() -> list[str]:
+    return __all__
+
 
 class VersionCheckFailed(Exception):
     """The Nox version does not satisfy what ``nox.needs_version`` specifies."""
diff --git a/nox/command.py b/nox/command.py
index 976e7f9..fe30307 100644
--- a/nox/command.py
+++ b/nox/command.py
@@ -29,6 +29,13 @@ if TYPE_CHECKING:
     from collections.abc import Iterable, Mapping, Sequence
     from typing import IO
 
+__all__ = ["CommandFailed", "ExternalType", "run", "which"]
+
+
+def __dir__() -> list[str]:
+    return __all__
+
+
 ExternalType = Literal["error", True, False]
 
 
@@ -40,10 +47,12 @@ class CommandFailed(Exception):
         self.reason = reason
 
 
-def which(program: str | os.PathLike[str], paths: Sequence[str] | None) -> str:
+def which(
+    program: str | os.PathLike[str], paths: Sequence[str | os.PathLike[str]] | None
+) -> str:
     """Finds the full path to an executable."""
     if paths is not None:
-        full_path = shutil.which(program, path=os.pathsep.join(paths))
+        full_path = shutil.which(program, path=os.pathsep.join(str(p) for p in paths))
         if full_path:
             return os.fspath(full_path)
 
@@ -79,7 +88,7 @@ def run(
     *,
     env: Mapping[str, str | None] | None = ...,
     silent: Literal[True],
-    paths: Sequence[str] | None = ...,
+    paths: Sequence[str | os.PathLike[str]] | None = ...,
     success_codes: Iterable[int] | None = ...,
     log: bool = ...,
     external: ExternalType = ...,
@@ -96,7 +105,7 @@ def run(
     *,
     env: Mapping[str, str | None] | None = ...,
     silent: Literal[False] = ...,
-    paths: Sequence[str] | None = ...,
+    paths: Sequence[str | os.PathLike[str]] | None = ...,
     success_codes: Iterable[int] | None = ...,
     log: bool = ...,
     external: ExternalType = ...,
@@ -113,7 +122,7 @@ def run(
     *,
     env: Mapping[str, str | None] | None = ...,
     silent: bool,
-    paths: Sequence[str] | None = ...,
+    paths: Sequence[str | os.PathLike[str]] | None = ...,
     success_codes: Iterable[int] | None = ...,
     log: bool = ...,
     external: ExternalType = ...,
@@ -129,7 +138,7 @@ def run(
     *,
     env: Mapping[str, str | None] | None = None,
     silent: bool = False,
-    paths: Sequence[str] | None = None,
+    paths: Sequence[str | os.PathLike[str]] | None = None,
     success_codes: Iterable[int] | None = None,
     log: bool = True,
     external: ExternalType = False,
@@ -153,7 +162,7 @@ def run(
         logger.info(full_cmd)
 
         is_external_tool = paths is not None and not any(
-            cmd_path.startswith(path) for path in paths
+            cmd_path.startswith(str(path)) for path in paths
         )
         if is_external_tool:
             if external == "error":
diff --git a/nox/logger.py b/nox/logger.py
index 6dd2360..8841f58 100644
--- a/nox/logger.py
+++ b/nox/logger.py
@@ -19,6 +19,13 @@ from typing import Any, cast
 
 from colorlog import ColoredFormatter
 
+__all__ = ["OUTPUT", "SUCCESS", "logger", "setup_logging"]
+
+
+def __dir__() -> list[str]:
+    return __all__
+
+
 SUCCESS = 25
 OUTPUT = logging.DEBUG - 1
 
diff --git a/nox/manifest.py b/nox/manifest.py
index c3ca9fd..8b7d9e7 100644
--- a/nox/manifest.py
+++ b/nox/manifest.py
@@ -28,6 +28,13 @@ if TYPE_CHECKING:
     import argparse
     from collections.abc import Iterable, Iterator, Sequence
 
+__all__ = ["WARN_PYTHONS_IGNORED", "Manifest", "keyword_match"]
+
+
+def __dir__() -> list[str]:
+    return __all__
+
+
 WARN_PYTHONS_IGNORED = "python_ignored"
 
 
diff --git a/nox/popen.py b/nox/popen.py
index b6f7bc6..016d03c 100644
--- a/nox/popen.py
+++ b/nox/popen.py
@@ -23,6 +23,18 @@ from typing import IO, TYPE_CHECKING
 if TYPE_CHECKING:
     from collections.abc import Mapping, Sequence
 
+__all__ = [
+    "DEFAULT_INTERRUPT_TIMEOUT",
+    "DEFAULT_TERMINATE_TIMEOUT",
+    "decode_output",
+    "popen",
+]
+
+
+def __dir__() -> list[str]:
+    return __all__
+
+
 DEFAULT_INTERRUPT_TIMEOUT = 0.3
 DEFAULT_TERMINATE_TIMEOUT = 0.2
 
diff --git a/nox/registry.py b/nox/registry.py
index 8669b54..59f30f4 100644
--- a/nox/registry.py
+++ b/nox/registry.py
@@ -26,6 +26,13 @@ if TYPE_CHECKING:
 
     from ._typing import Python
 
+__all__ = ["get", "session_decorator"]
+
+
+def __dir__() -> list[str]:
+    return __all__
+
+
 RawFunc = Callable[..., Any]
 
 _REGISTRY: collections.OrderedDict[str, Func] = collections.OrderedDict()
diff --git a/nox/sessions.py b/nox/sessions.py
index 8851c5a..b38fce3 100644
--- a/nox/sessions.py
+++ b/nox/sessions.py
@@ -59,6 +59,12 @@ if TYPE_CHECKING:
     from nox.command import ExternalType
     from nox.manifest import Manifest
 
+__all__ = ["Result", "Session", "SessionRunner", "Status", "nox"]
+
+
+def __dir__() -> list[str]:
+    return __all__
+
 
 @contextlib.contextmanager
 def _chdir(path: str) -> Generator[None, None, None]:
diff --git a/nox/tasks.py b/nox/tasks.py
index 99dd3ee..02762fe 100644
--- a/nox/tasks.py
+++ b/nox/tasks.py
@@ -35,6 +35,22 @@ if TYPE_CHECKING:
     import types
     from argparse import Namespace
 
+__all__ = [
+    "create_report",
+    "discover_manifest",
+    "filter_manifest",
+    "final_reduce",
+    "honor_list_request",
+    "load_nox_module",
+    "merge_noxfile_options",
+    "print_summary",
+    "run_manifest",
+]
+
+
+def __dir__() -> list[str]:
+    return __all__
+
 
 def _load_and_exec_nox_module(global_config: Namespace) -> types.ModuleType:
     """
diff --git a/nox/tox_to_nox.py b/nox/tox_to_nox.py
index a8e183c..087585d 100644
--- a/nox/tox_to_nox.py
+++ b/nox/tox_to_nox.py
@@ -34,6 +34,14 @@ if sys.version_info < (3, 9):
 else:
     from importlib.resources import files
 
+
+__all__ = ["main"]
+
+
+def __dir__() -> list[str]:
+    return __all__
+
+
 TOX_VERSION = metadata.version("tox")
 
 TOX4 = int(TOX_VERSION.split(".")[0]) >= 4
@@ -81,7 +89,7 @@ def main() -> None:
     args = parser.parse_args()
 
     if TOX4:
-        output = check_output(["tox", "config"], text=True)  # noqa: S607
+        output = check_output(["tox", "config"], text=True, encoding="utf-8")  # noqa: S607
         original_config = ConfigParser()
         original_config.read_string(output)
         config: dict[str, dict[str, Any]] = {}
diff --git a/nox/virtualenv.py b/nox/virtualenv.py
index 2584b90..dcf2b0f 100644
--- a/nox/virtualenv.py
+++ b/nox/virtualenv.py
@@ -39,6 +39,31 @@ if TYPE_CHECKING:
 
     from nox._typing import Python
 
+__all__ = [
+    "ALL_VENVS",
+    "HAS_UV",
+    "OPTIONAL_VENVS",
+    "UV",
+    "UV_PYTHON_SUPPORT",
+    "CondaEnv",
+    "InterpreterNotFound",
+    "PassthroughEnv",
+    "ProcessEnv",
+    "VirtualEnv",
+    "find_uv",
+    "get_virtualenv",
+    "get_virtualenv",
+    "uv_install_python",
+    "uv_install_python",
+    "uv_version",
+    "uv_version",
+]
+
+
+def __dir__() -> list[str]:
+    return __all__
+
+
 # Problematic environment variables that are stripped from all commands inside
 # of a virtualenv. See https://github.com/theacodes/nox/issues/44
 _BLACKLISTED_ENV_VARS = frozenset(
@@ -80,6 +105,7 @@ def uv_version() -> version.Version:
             check=False,
             text=True,
             capture_output=True,
+            encoding="utf-8",
         )
     except FileNotFoundError:
         logger.info("uv binary not found.")
diff --git a/nox/workflow.py b/nox/workflow.py
index 1f31376..e812fe6 100644
--- a/nox/workflow.py
+++ b/nox/workflow.py
@@ -20,6 +20,12 @@ if TYPE_CHECKING:
     import argparse
     from collections.abc import Callable, Iterable
 
+__all__ = ["execute"]
+
+
+def __dir__() -> list[str]:
+    return __all__
+
 
 def execute(
     workflow: Iterable[Callable[..., Any]], global_config: argparse.Namespace
diff --git a/noxfile.py b/noxfile.py
index 81cf3c5..75dd7e3 100644
--- a/noxfile.py
+++ b/noxfile.py
@@ -60,6 +60,7 @@ def tests(session: nox.Session, tox_version: str) -> None:
     session.install("-e.[tox_to_nox]")
     if tox_version != "latest":
         session.install(f"tox{tox_version}")
+    extra_env = {"PYTHONWARNDEFAULTENCODING": "1"} if tox_version == "latest" else {}
     session.run(
         "pytest",
         "--cov",
@@ -69,6 +70,7 @@ def tests(session: nox.Session, tox_version: str) -> None:
         *session.posargs,
         env={
             "COVERAGE_FILE": coverage_file,
+            **extra_env,
         },
     )
 


Test patch:
diff --git a/tests/conftest.py b/tests/conftest.py
index 4bb2f3a..ecda624 100644
--- a/tests/conftest.py
+++ b/tests/conftest.py
@@ -48,7 +48,7 @@ def generate_noxfile_options(tmp_path: Path) -> Callable[..., str]:
                 text = re.sub(rf"(# )?nox.options.{opt}", f"nox.options.{opt}", text)
             text = Template(text).safe_substitute(**option_mapping)
         path = tmp_path / "noxfile.py"
-        path.write_text(text)
+        path.write_text(text, encoding="utf8")
         return str(path)
 
     return generate_noxfile
diff --git a/tests/test__version.py b/tests/test__version.py
index 772dd9a..9c88c58 100644
--- a/tests/test__version.py
+++ b/tests/test__version.py
@@ -37,7 +37,7 @@ if TYPE_CHECKING:
 def temp_noxfile(tmp_path: Path) -> Callable[[str], str]:
     def make_temp_noxfile(content: str) -> str:
         path = tmp_path / "noxfile.py"
-        path.write_text(content)
+        path.write_text(content, encoding="utf8")
         return str(path)
 
     return make_temp_noxfile
diff --git a/tests/test_command.py b/tests/test_command.py
index 62c783c..bfb8527 100644
--- a/tests/test_command.py
+++ b/tests/test_command.py
@@ -25,7 +25,7 @@ import sys
 import time
 from pathlib import Path
 from textwrap import dedent
-from typing import TYPE_CHECKING, Any
+from typing import Any
 from unittest import mock
 
 import pytest
@@ -33,9 +33,6 @@ import pytest
 import nox.command
 import nox.popen
 
-if TYPE_CHECKING:
-    from _pytest.compat import LEGACY_PATH
-
 PYTHON = sys.executable
 
 skip_on_windows_primary_console_session = pytest.mark.skipif(
@@ -214,36 +211,30 @@ def test_run_path_existent(tmp_path: Path) -> None:
         )
 
 
-def test_run_external_warns(
-    tmpdir: LEGACY_PATH, caplog: pytest.LogCaptureFixture
-) -> None:
+def test_run_external_warns(tmp_path: Path, caplog: pytest.LogCaptureFixture) -> None:
     caplog.set_level(logging.WARNING)
 
-    nox.command.run([PYTHON, "--version"], silent=True, paths=[tmpdir.strpath])
+    nox.command.run([PYTHON, "--version"], silent=True, paths=[tmp_path])
 
     assert "external=True" in caplog.text
 
 
 def test_run_external_silences(
-    tmpdir: LEGACY_PATH, caplog: pytest.LogCaptureFixture
+    tmp_path: Path, caplog: pytest.LogCaptureFixture
 ) -> None:
     caplog.set_level(logging.WARNING)
 
-    nox.command.run(
-        [PYTHON, "--version"], silent=True, paths=[tmpdir.strpath], external=True
-    )
+    nox.command.run([PYTHON, "--version"], silent=True, paths=[tmp_path], external=True)
 
     assert "external=True" not in caplog.text
 
 
-def test_run_external_raises(
-    tmpdir: LEGACY_PATH, caplog: pytest.LogCaptureFixture
-) -> None:
+def test_run_external_raises(tmp_path: Path, caplog: pytest.LogCaptureFixture) -> None:
     caplog.set_level(logging.ERROR)
 
     with pytest.raises(nox.command.CommandFailed):
         nox.command.run(
-            [PYTHON, "--version"], silent=True, paths=[tmpdir.strpath], external="error"
+            [PYTHON, "--version"], silent=True, paths=[tmp_path], external="error"
         )
 
     assert "external=True" in caplog.text
@@ -430,8 +421,8 @@ def test_interrupt_handled_on_windows() -> None:
     run_pytest_in_new_console_session("test_interrupt_handled")
 
 
-def test_custom_stdout(capsys: pytest.CaptureFixture[str], tmpdir: LEGACY_PATH) -> None:
-    with open(str(tmpdir / "out.txt"), "w+") as stdout:
+def test_custom_stdout(capsys: pytest.CaptureFixture[str], tmp_path: Path) -> None:
+    with (tmp_path / "out.txt").open("w+", encoding="utf-8") as stdout:
         nox.command.run(
             [
                 PYTHON,
@@ -453,16 +444,16 @@ def test_custom_stdout(capsys: pytest.CaptureFixture[str], tmpdir: LEGACY_PATH)
         assert "err" in tempfile_contents
 
 
-def test_custom_stdout_silent_flag(tmpdir: LEGACY_PATH) -> None:
-    with open(str(tmpdir / "out.txt"), "w+") as stdout:  # noqa: SIM117
+def test_custom_stdout_silent_flag(tmp_path: Path) -> None:
+    with (tmp_path / "out.txt").open("w+", encoding="utf-8") as stdout:  # noqa: SIM117
         with pytest.raises(ValueError, match="silent"):
             nox.command.run([PYTHON, "-c", 'print("hi")'], stdout=stdout, silent=True)
 
 
 def test_custom_stdout_failed_command(
-    capsys: pytest.CaptureFixture[str], tmpdir: LEGACY_PATH
+    capsys: pytest.CaptureFixture[str], tmp_path: Path
 ) -> None:
-    with open(str(tmpdir / "out.txt"), "w+") as stdout:
+    with (tmp_path / "out.txt").open("w+", encoding="utf-8") as stdout:
         with pytest.raises(nox.command.CommandFailed):
             nox.command.run(
                 [
@@ -485,8 +476,8 @@ def test_custom_stdout_failed_command(
         assert "err" in tempfile_contents
 
 
-def test_custom_stderr(capsys: pytest.CaptureFixture[str], tmpdir: LEGACY_PATH) -> None:
-    with open(str(tmpdir / "err.txt"), "w+") as stderr:
+def test_custom_stderr(capsys: pytest.CaptureFixture[str], tmp_path: Path) -> None:
+    with (tmp_path / "err.txt").open("w+", encoding="utf-8") as stderr:
         nox.command.run(
             [
                 PYTHON,
@@ -509,9 +500,9 @@ def test_custom_stderr(capsys: pytest.CaptureFixture[str], tmpdir: LEGACY_PATH)
 
 
 def test_custom_stderr_failed_command(
-    capsys: pytest.CaptureFixture[str], tmpdir: LEGACY_PATH
+    capsys: pytest.CaptureFixture[str], tmp_path: Path
 ) -> None:
-    with open(str(tmpdir / "out.txt"), "w+") as stderr:
+    with (tmp_path / "out.txt").open("w+", encoding="utf-8") as stderr:
         with pytest.raises(nox.command.CommandFailed):
             nox.command.run(
                 [
diff --git a/tests/test_main.py b/tests/test_main.py
index 67cfdbb..f1bda81 100644
--- a/tests/test_main.py
+++ b/tests/test_main.py
@@ -694,14 +694,14 @@ def generate_noxfile_options_pythons(tmp_path: Path) -> Callable[[str, str, str]
         default_session: str, default_python: str, alternate_python: str
     ) -> str:
         path = Path(RESOURCES) / "noxfile_options_pythons.py"
-        text = path.read_text()
+        text = path.read_text(encoding="utf-8")
         text = text.format(
             default_session=default_session,
             default_python=default_python,
             alternate_python=alternate_python,
         )
         path = tmp_path / "noxfile.py"
-        path.write_text(text)
+        path.write_text(text, encoding="utf-8")
         return str(path)
 
     return generate_noxfile
diff --git a/tests/test_sessions.py b/tests/test_sessions.py
index 1ed2352..f96e404 100644
--- a/tests/test_sessions.py
+++ b/tests/test_sessions.py
@@ -40,9 +40,6 @@ import nox.virtualenv
 from nox import _options
 from nox.logger import logger
 
-if typing.TYPE_CHECKING:
-    from _pytest.compat import LEGACY_PATH
-
 HAS_CONDA = shutil.which("conda") is not None
 has_conda = pytest.mark.skipif(not HAS_CONDA, reason="Missing conda command.")
 
@@ -212,50 +209,54 @@ class TestSession:
 
             assert session.interactive is False
 
-    def test_chdir(self, tmpdir: LEGACY_PATH) -> None:
-        cdto = str(tmpdir.join("cdbby").ensure(dir=True))
+    def test_chdir(self, tmp_path: Path) -> None:
+        cdbby = tmp_path / "cdbby"
+        cdbby.mkdir()
         current_cwd = os.getcwd()
 
         session, _ = self.make_session_and_runner()
 
-        session.chdir(cdto)
+        session.chdir(str(cdbby))
 
-        assert os.getcwd() == cdto
+        assert cdbby.samefile(".")
         os.chdir(current_cwd)
 
-    def test_chdir_ctx(self, tmpdir: LEGACY_PATH) -> None:
-        cdto = str(tmpdir.join("cdbby").ensure(dir=True))
-        current_cwd = os.getcwd()
+    def test_chdir_ctx(self, tmp_path: Path) -> None:
+        cdbby = tmp_path / "cdbby"
+        cdbby.mkdir()
+        current_cwd = Path.cwd().resolve()
 
         session, _ = self.make_session_and_runner()
 
-        with session.chdir(cdto):
-            assert os.getcwd() == cdto
+        with session.chdir(cdbby):
+            assert cdbby.samefile(".")
 
-        assert os.getcwd() == current_cwd
+        assert current_cwd.samefile(".")
 
         os.chdir(current_cwd)
 
-    def test_invoked_from(self, tmpdir: LEGACY_PATH) -> None:
-        cdto = str(tmpdir.join("cdbby").ensure(dir=True))
-        current_cwd = os.getcwd()
+    def test_invoked_from(self, tmp_path: Path) -> None:
+        cdbby = tmp_path / "cdbby"
+        cdbby.mkdir()
+        current_cwd = Path.cwd().resolve()
 
         session, _ = self.make_session_and_runner()
 
-        session.chdir(cdto)
+        session.chdir(cdbby)
 
-        assert session.invoked_from == current_cwd
+        assert current_cwd.samefile(session.invoked_from)
         os.chdir(current_cwd)
 
-    def test_chdir_pathlib(self, tmpdir: LEGACY_PATH) -> None:
-        cdto = str(tmpdir.join("cdbby").ensure(dir=True))
-        current_cwd = os.getcwd()
+    def test_chdir_pathlib(self, tmp_path: Path) -> None:
+        cdbby = tmp_path / "cdbby"
+        cdbby.mkdir()
+        current_cwd = Path.cwd().resolve()
 
         session, _ = self.make_session_and_runner()
 
-        session.chdir(Path(cdto))
+        session.chdir(cdbby)
 
-        assert os.getcwd() == cdto
+        assert cdbby.samefile(".")
         os.chdir(current_cwd)
 
     def test_run_bad_args(self) -> None:
diff --git a/tests/test_tasks.py b/tests/test_tasks.py
index 28f8325..5d25db1 100644
--- a/tests/test_tasks.py
+++ b/tests/test_tasks.py
@@ -148,7 +148,7 @@ def test_load_nox_module_needs_version_static(tmp_path: Path) -> None:
         """
     )
     noxfile = tmp_path / "noxfile.py"
-    noxfile.write_text(text)
+    noxfile.write_text(text, encoding="utf-8")
     config = _options.options.namespace(noxfile=str(noxfile))
     assert tasks.load_nox_module(config) == 2
 
@@ -162,7 +162,7 @@ def test_load_nox_module_needs_version_dynamic(tmp_path: Path) -> None:
         """
     )
     noxfile = tmp_path / "noxfile.py"
-    noxfile.write_text(text)
+    noxfile.write_text(text, encoding="utf-8")
     config = _options.options.namespace(noxfile=str(noxfile))
     tasks.load_nox_module(config)
     # Dynamic version requirements are not checked.
diff --git a/tests/test_toml.py b/tests/test_toml.py
index 54f8868..b0022c1 100644
--- a/tests/test_toml.py
+++ b/tests/test_toml.py
@@ -14,7 +14,8 @@ def test_load_pyproject(tmp_path: Path) -> None:
         name = "hi"
         version = "1.0"
         dependencies = ["numpy", "requests"]
-        """
+        """,
+        encoding="utf-8",
     )
 
     toml = nox.project.load_toml(filepath)
@@ -43,7 +44,8 @@ def test_load_script_block(tmp_path: Path, example: str) -> None:
             data = resp.json()
             pprint([(k, v["title"]) for k, v in data.items()][:10])
             """
-        )
+        ),
+        encoding="utf-8",
     )
 
     toml = nox.project.load_toml(filepath)
@@ -64,7 +66,8 @@ def test_load_no_script_block(tmp_path: Path) -> None:
             data = resp.json()
             pprint([(k, v["title"]) for k, v in data.items()][:10])
             """
-        )
+        ),
+        encoding="utf-8",
     )
 
     with pytest.raises(ValueError, match="No script block found"):
@@ -94,7 +97,8 @@ def test_load_multiple_script_block(tmp_path: Path) -> None:
             data = resp.json()
             pprint([(k, v["title"]) for k, v in data.items()][:10])
             """
-        )
+        ),
+        encoding="utf-8",
     )
 
     with pytest.raises(ValueError, match="Multiple script blocks found"):
diff --git a/tests/test_tox_to_nox.py b/tests/test_tox_to_nox.py
index 048b5d5..4146ea4 100644
--- a/tests/test_tox_to_nox.py
+++ b/tests/test_tox_to_nox.py
@@ -14,16 +14,17 @@
 
 from __future__ import annotations
 
+import os
 import sys
 import textwrap
-from typing import TYPE_CHECKING, Any
+from pathlib import Path
+from typing import TYPE_CHECKING
 
 import pytest
 
 if TYPE_CHECKING:
     from collections.abc import Callable
 
-    from _pytest.compat import LEGACY_PATH
 
 tox_to_nox = pytest.importorskip("nox.tox_to_nox")
 
@@ -33,21 +34,22 @@ PYTHON_VERSION_NODOT = PYTHON_VERSION.replace(".", "")
 
 
 @pytest.fixture
-def makeconfig(tmpdir: LEGACY_PATH) -> Callable[[str], str]:
+def makeconfig(tmp_path: Path) -> Callable[[str], str]:
     def makeconfig(toxini_content: str) -> str:
-        tmpdir.join("tox.ini").write(toxini_content)
-        old = tmpdir.chdir()
+        tmp_path.joinpath("tox.ini").write_text(toxini_content, encoding="utf-8")
+        old = Path.cwd().resolve()
+        os.chdir(tmp_path)
         try:
             sys.argv = [sys.executable]
             tox_to_nox.main()
-            return tmpdir.join("noxfile.py").read()  # type: ignore[no-any-return]
+            return tmp_path.joinpath("noxfile.py").read_text(encoding="utf-8")
         finally:
-            old.chdir()
+            os.chdir(old)
 
     return makeconfig
 
 
-def test_trivial(makeconfig: Callable[..., Any]) -> None:
+def test_trivial(makeconfig: Callable[[str], str]) -> None:
     result = makeconfig(
         textwrap.dedent(
             f"""
@@ -72,7 +74,7 @@ def test_trivial(makeconfig: Callable[..., Any]) -> None:
     )
 
 
-def test_skipinstall(makeconfig: Callable[..., Any]) -> None:
+def test_skipinstall(makeconfig: Callable[[str], str]) -> None:
     result = makeconfig(
         textwrap.dedent(
             f"""
@@ -99,7 +101,7 @@ def test_skipinstall(makeconfig: Callable[..., Any]) -> None:
     )
 
 
-def test_usedevelop(makeconfig: Callable[..., Any]) -> None:
+def test_usedevelop(makeconfig: Callable[[str], str]) -> None:
     result = makeconfig(
         textwrap.dedent(
             f"""
@@ -127,7 +129,7 @@ def test_usedevelop(makeconfig: Callable[..., Any]) -> None:
     )
 
 
-def test_commands(makeconfig: Callable[..., Any]) -> None:
+def test_commands(makeconfig: Callable[[str], str]) -> None:
     result = makeconfig(
         textwrap.dedent(
             f"""
@@ -163,7 +165,7 @@ def test_commands(makeconfig: Callable[..., Any]) -> None:
     )
 
 
-def test_deps(makeconfig: Callable[..., Any]) -> None:
+def test_deps(makeconfig: Callable[[str], str]) -> None:
     result = makeconfig(
         textwrap.dedent(
             f"""
@@ -195,7 +197,7 @@ def test_deps(makeconfig: Callable[..., Any]) -> None:
     )
 
 
-def test_env(makeconfig: Callable[..., Any]) -> None:
+def test_env(makeconfig: Callable[[str], str]) -> None:
     result = makeconfig(
         textwrap.dedent(
             f"""
@@ -229,7 +231,7 @@ def test_env(makeconfig: Callable[..., Any]) -> None:
     )
 
 
-def test_chdir(makeconfig: Callable[..., Any]) -> None:
+def test_chdir(makeconfig: Callable[[str], str]) -> None:
     result = makeconfig(
         textwrap.dedent(
             f"""
@@ -259,7 +261,7 @@ def test_chdir(makeconfig: Callable[..., Any]) -> None:
     )
 
 
-def test_dash_in_envname(makeconfig: Callable[..., Any]) -> None:
+def test_dash_in_envname(makeconfig: Callable[[str], str]) -> None:
     result = makeconfig(
         textwrap.dedent(
             f"""
@@ -289,7 +291,7 @@ def test_dash_in_envname(makeconfig: Callable[..., Any]) -> None:
 
 @pytest.mark.skipif(TOX4, reason="Not supported in tox 4.")
 def test_non_identifier_in_envname(
-    makeconfig: Callable[..., Any], capfd: pytest.CaptureFixture[str]
+    makeconfig: Callable[[str], str], capfd: pytest.CaptureFixture[str]
 ) -> None:
     result = makeconfig(
         textwrap.dedent(
@@ -325,7 +327,7 @@ def test_non_identifier_in_envname(
     )
 
 
-def test_descriptions_into_docstrings(makeconfig: Callable[..., Any]) -> None:
+def test_descriptions_into_docstrings(makeconfig: Callable[[str], str]) -> None:
     result = makeconfig(
         textwrap.dedent(
             f"""
diff --git a/tests/test_virtualenv.py b/tests/test_virtualenv.py
index 04282fd..a05885c 100644
--- a/tests/test_virtualenv.py
+++ b/tests/test_virtualenv.py
@@ -36,8 +36,6 @@ import nox.virtualenv
 if TYPE_CHECKING:
     from collections.abc import Callable
 
-    from _pytest.compat import LEGACY_PATH
-
     from nox.virtualenv import CondaEnv, ProcessEnv, VirtualEnv
 
 IS_WINDOWS = nox.virtualenv._SYSTEM == "Windows"
@@ -57,31 +55,29 @@ class TextProcessResult(NamedTuple):
 
 @pytest.fixture
 def make_one(
-    tmpdir: LEGACY_PATH,
-) -> Callable[
-    ..., tuple[nox.virtualenv.VirtualEnv | nox.virtualenv.ProcessEnv, LEGACY_PATH]
-]:
+    tmp_path: Path,
+) -> Callable[..., tuple[nox.virtualenv.VirtualEnv | nox.virtualenv.ProcessEnv, Path]]:
     def factory(
         *args: Any, venv_backend: str = "virtualenv", **kwargs: Any
-    ) -> tuple[nox.virtualenv.VirtualEnv | nox.virtualenv.ProcessEnv, LEGACY_PATH]:
-        location = tmpdir.join("venv")
+    ) -> tuple[nox.virtualenv.VirtualEnv | nox.virtualenv.ProcessEnv, Path]:
+        location = tmp_path.joinpath("venv")
         try:
             venv_fn = nox.virtualenv.ALL_VENVS[venv_backend]
         except KeyError:
             venv_fn = functools.partial(
                 nox.virtualenv.VirtualEnv, venv_backend=venv_backend
             )
-        venv = venv_fn(location.strpath, *args, **kwargs)
+        venv = venv_fn(str(location), *args, **kwargs)
         return (venv, location)
 
     return factory
 
 
 @pytest.fixture
-def make_conda(tmpdir: LEGACY_PATH) -> Callable[..., tuple[CondaEnv, LEGACY_PATH]]:
-    def factory(*args: Any, **kwargs: Any) -> tuple[CondaEnv, LEGACY_PATH]:
-        location = tmpdir.join("condaenv")
-        venv = nox.virtualenv.CondaEnv(location.strpath, *args, **kwargs)
+def make_conda(tmp_path: Path) -> Callable[..., tuple[CondaEnv, Path]]:
+    def factory(*args: Any, **kwargs: Any) -> tuple[CondaEnv, Path]:
+        location = tmp_path.joinpath("condaenv")
+        venv = nox.virtualenv.CondaEnv(str(location), *args, **kwargs)
         return (venv, location)
 
     return factory
@@ -150,7 +146,7 @@ def test_process_env_create() -> None:
 
 def test_invalid_venv_create(
     make_one: Callable[
-        ..., tuple[nox.virtualenv.VirtualEnv | nox.virtualenv.ProcessEnv, LEGACY_PATH]
+        ..., tuple[nox.virtualenv.VirtualEnv | nox.virtualenv.ProcessEnv, Path]
     ],
 ) -> None:
     with pytest.raises(ValueError, match="venv_backend 'invalid' not recognized"):
@@ -158,7 +154,7 @@ def test_invalid_venv_create(
 
 
 def test_condaenv_constructor_defaults(
-    make_conda: Callable[..., tuple[CondaEnv, Any]],
+    make_conda: Callable[..., tuple[CondaEnv, Path]],
 ) -> None:
     venv, _ = make_conda()
     assert venv.location
@@ -167,7 +163,7 @@ def test_condaenv_constructor_defaults(
 
 
 def test_condaenv_constructor_explicit(
-    make_conda: Callable[..., tuple[CondaEnv, Any]],
+    make_conda: Callable[..., tuple[CondaEnv, Path]],
 ) -> None:
     venv, _ = make_conda(interpreter="3.5", reuse_existing=True)
     assert venv.location
@@ -176,67 +172,67 @@ def test_condaenv_constructor_explicit(
 
 
 @has_conda
-def test_condaenv_create(make_conda: Callable[..., tuple[CondaEnv, Any]]) -> None:
+def test_condaenv_create(make_conda: Callable[..., tuple[CondaEnv, Path]]) -> None:
     venv, dir_ = make_conda()
     venv.create()
 
     if IS_WINDOWS:
-        assert dir_.join("python.exe").check()
-        assert dir_.join("Scripts", "pip.exe").check()
-        assert dir_.join("Library").check()
+        assert dir_.joinpath("python.exe").exists()
+        assert dir_.joinpath("Scripts", "pip.exe").exists()
+        assert dir_.joinpath("Library").exists()
     else:
-        assert dir_.join("bin", "python").check()
-        assert dir_.join("bin", "pip").check()
-        assert dir_.join("lib").check()
+        assert dir_.joinpath("bin", "python").exists()
+        assert dir_.joinpath("bin", "pip").exists()
+        assert dir_.joinpath("lib").exists()
 
     # Test running create on an existing environment. It should be deleted.
-    dir_.ensure("test.txt")
+    dir_.joinpath("test.txt").touch()
     venv.create()
-    assert not dir_.join("test.txt").check()
+    assert not dir_.joinpath("test.txt").exists()
 
     # Test running create on an existing environment with reuse_existing
     # enabled, it should not be deleted.
-    dir_.ensure("test.txt")
-    assert dir_.join("test.txt").check()
+    dir_.joinpath("test.txt").touch()
+    assert dir_.joinpath("test.txt").exists()
     venv.reuse_existing = True
     venv.create()
-    assert dir_.join("test.txt").check()
+    assert dir_.joinpath("test.txt").exists()
     assert venv._reused
 
 
 @has_conda
 def test_condaenv_create_with_params(
-    make_conda: Callable[..., tuple[CondaEnv, Any]],
+    make_conda: Callable[..., tuple[CondaEnv, Path]],
 ) -> None:
     venv, dir_ = make_conda(venv_params=["--verbose"])
     venv.create()
     if IS_WINDOWS:
-        assert dir_.join("python.exe").check()
-        assert dir_.join("Scripts", "pip.exe").check()
+        assert dir_.joinpath("python.exe").exists()
+        assert dir_.joinpath("Scripts", "pip.exe").exists()
     else:
-        assert dir_.join("bin", "python").check()
-        assert dir_.join("bin", "pip").check()
+        assert dir_.joinpath("bin", "python").exists()
+        assert dir_.joinpath("bin", "pip").exists()
 
 
 @has_conda
 def test_condaenv_create_interpreter(
-    make_conda: Callable[..., tuple[CondaEnv, Any]],
+    make_conda: Callable[..., tuple[CondaEnv, Path]],
 ) -> None:
     venv, dir_ = make_conda(interpreter="3.8")
     venv.create()
     if IS_WINDOWS:
-        assert dir_.join("python.exe").check()
-        assert dir_.join("python38.dll").check()
-        assert dir_.join("python38.pdb").check()
-        assert not dir_.join("python38.exe").check()
+        assert dir_.joinpath("python.exe").exists()
+        assert dir_.joinpath("python38.dll").exists()
+        assert dir_.joinpath("python38.pdb").exists()
+        assert not dir_.joinpath("python38.exe").exists()
     else:
-        assert dir_.join("bin", "python").check()
-        assert dir_.join("bin", "python3.8").check()
+        assert dir_.joinpath("bin", "python").exists()
+        assert dir_.joinpath("bin", "python3.8").exists()
 
 
 @has_conda
 def test_conda_env_create_verbose(
-    make_conda: Callable[..., tuple[CondaEnv, Any]],
+    make_conda: Callable[..., tuple[CondaEnv, Path]],
 ) -> None:
     venv, _dir = make_conda()
     with mock.patch("nox.virtualenv.nox.command.run") as mock_run:
@@ -254,26 +250,26 @@ def test_conda_env_create_verbose(
 
 
 @mock.patch("nox.virtualenv._SYSTEM", new="Windows")
-def test_condaenv_bin_windows(make_conda: Callable[..., tuple[CondaEnv, Any]]) -> None:
+def test_condaenv_bin_windows(make_conda: Callable[..., tuple[CondaEnv, Path]]) -> None:
     venv, dir_ = make_conda()
     assert [
-        dir_.strpath,
-        dir_.join("Library", "mingw-w64", "bin").strpath,
-        dir_.join("Library", "usr", "bin").strpath,
-        dir_.join("Library", "bin").strpath,
-        dir_.join("Scripts").strpath,
-        dir_.join("bin").strpath,
+        str(dir_),
+        str(dir_.joinpath("Library", "mingw-w64", "bin")),
+        str(dir_.joinpath("Library", "usr", "bin")),
+        str(dir_.joinpath("Library", "bin")),
+        str(dir_.joinpath("Scripts")),
+        str(dir_.joinpath("bin")),
     ] == venv.bin_paths
 
 
 @has_conda
-def test_condaenv_(make_conda: Callable[..., tuple[CondaEnv, Any]]) -> None:
+def test_condaenv_(make_conda: Callable[..., tuple[CondaEnv, Path]]) -> None:
     venv, _dir = make_conda()
     assert not venv.is_offline()
 
 
 @has_conda
-def test_condaenv_detection(make_conda: Callable[..., tuple[CondaEnv, Any]]) -> None:
+def test_condaenv_detection(make_conda: Callable[..., tuple[CondaEnv, Path]]) -> None:
     venv, dir_ = make_conda()
     venv.create()
     conda = shutil.which("conda")
@@ -290,12 +286,12 @@ def test_condaenv_detection(make_conda: Callable[..., tuple[CondaEnv, Any]]) ->
 
     output_match = path_regex.search(output)
     assert output_match
-    assert output_match.group("env_dir") == dir_.strpath
+    assert dir_.samefile(output_match.group("env_dir"))
 
 
 @has_uv
 def test_uv_creation(
-    make_one: Callable[..., tuple[VirtualEnv, Any]],
+    make_one: Callable[..., tuple[VirtualEnv, Path]],
 ) -> None:
     venv, _ = make_one(venv_backend="uv")
     assert venv.location
@@ -309,13 +305,13 @@ def test_uv_creation(
 
 @has_uv
 def test_uv_managed_python(
-    make_one: Callable[..., tuple[VirtualEnv, Any]],
+    make_one: Callable[..., tuple[VirtualEnv, Path]],
 ) -> None:
     make_one(interpreter="cpython3.12", venv_backend="uv")
 
 
 def test_constructor_defaults(
-    make_one: Callable[..., tuple[VirtualEnv, Any]],
+    make_one: Callable[..., tuple[VirtualEnv, Path]],
 ) -> None:
     venv, _ = make_one()
     assert venv.location
@@ -326,7 +322,7 @@ def test_constructor_defaults(
 
 @pytest.mark.skipif(IS_WINDOWS, reason="Not testing multiple interpreters on Windows.")
 def test_constructor_explicit(
-    make_one: Callable[..., tuple[VirtualEnv, Any]],
+    make_one: Callable[..., tuple[VirtualEnv, Path]],
 ) -> None:
     venv, _ = make_one(interpreter="python3.5", reuse_existing=True)
     assert venv.location
@@ -336,7 +332,7 @@ def test_constructor_explicit(
 
 def test_env(
     monkeypatch: pytest.MonkeyPatch,
-    make_one: Callable[..., tuple[VirtualEnv, Any]],
+    make_one: Callable[..., tuple[VirtualEnv, Path]],
 ) -> None:
     monkeypatch.setenv("SIGIL", "123")
     venv, _ = make_one()
@@ -348,7 +344,7 @@ def test_env(
 
 def test_blacklisted_env(
     monkeypatch: pytest.MonkeyPatch,
-    make_one: Callable[..., tuple[VirtualEnv, Any]],
+    make_one: Callable[..., tuple[VirtualEnv, Path]],
 ) -> None:
     monkeypatch.setenv("__PYVENV_LAUNCHER__", "meep")
     venv, _ = make_one()
@@ -359,7 +355,7 @@ def test_blacklisted_env(
 
 def test__clean_location(
     monkeypatch: pytest.MonkeyPatch,
-    make_one: Callable[..., tuple[VirtualEnv, Any]],
+    make_one: Callable[..., tuple[VirtualEnv, Path]],
 ) -> None:
     venv, dir_ = make_one()
 
@@ -374,23 +370,23 @@ def test__clean_location(
         mock.MagicMock(),
     )
     monkeypatch.delattr(nox.virtualenv.shutil, "rmtree")  # type: ignore[attr-defined]
-    assert not dir_.check()
+    assert not dir_.exists()
     assert venv._clean_location()
 
     # Reuse existing, and currently exists.
     # Should return False indicating that the venv doesn't need to be created.
     dir_.mkdir()
-    assert dir_.check()
+    assert dir_.exists()
     venv.reuse_existing = True
     assert not venv._clean_location()
 
     # Don't reuse existing, and currently exists.
     # Should return True indicating the venv needs to be created.
     monkeypatch.undo()
-    assert dir_.check()
+    assert dir_.exists()
     venv.reuse_existing = False
     assert venv._clean_location()
-    assert not dir_.check()
+    assert not dir_.exists()
 
     # Reuse existing, but doesn't exist.
     # Should return True indicating the venv needs to be created.
@@ -399,7 +395,7 @@ def test__clean_location(
 
 
 def test_bin_paths(
-    make_one: Callable[..., tuple[VirtualEnv | ProcessEnv, Any]],
+    make_one: Callable[..., tuple[VirtualEnv | ProcessEnv, Path]],
 ) -> None:
     venv, dir_ = make_one()
 
@@ -407,26 +403,23 @@ def test_bin_paths(
     assert len(venv.bin_paths) == 1
     assert venv.bin_paths[0] == venv.bin
 
-    if IS_WINDOWS:
-        assert dir_.join("Scripts").strpath == venv.bin
-    else:
-        assert dir_.join("bin").strpath == venv.bin
+    assert str(dir_.joinpath("Scripts" if IS_WINDOWS else "bin")) == venv.bin
 
 
 @mock.patch("nox.virtualenv._SYSTEM", new="Windows")
 def test_bin_windows(
-    make_one: Callable[..., tuple[VirtualEnv | ProcessEnv, Any]],
+    make_one: Callable[..., tuple[VirtualEnv | ProcessEnv, Path]],
 ) -> None:
     venv, dir_ = make_one()
     assert venv.bin_paths
     assert len(venv.bin_paths) == 1
     assert venv.bin_paths[0] == venv.bin
-    assert dir_.join("Scripts").strpath == venv.bin
+    assert str(dir_.joinpath("Scripts")) == venv.bin
 
 
 def test_create(
     monkeypatch: pytest.MonkeyPatch,
-    make_one: Callable[..., tuple[VirtualEnv, Any]],
+    make_one: Callable[..., tuple[VirtualEnv, Path]],
 ) -> None:
     monkeypatch.setenv("CONDA_PREFIX", "no-prefix-allowed")
     monkeypatch.setenv("NOT_CONDA_PREFIX", "something-else")
@@ -438,33 +431,33 @@ def test_create(
     assert "NOT_CONDA_PREFIX" in venv.env
 
     if IS_WINDOWS:
-        assert dir_.join("Scripts", "python.exe").check()
-        assert dir_.join("Scripts", "pip.exe").check()
-        assert dir_.join("Lib").check()
+        assert dir_.joinpath("Scripts", "python.exe").exists()
+        assert dir_.joinpath("Scripts", "pip.exe").exists()
+        assert dir_.joinpath("Lib").exists()
     else:
-        assert dir_.join("bin", "python").check()
-        assert dir_.join("bin", "pip").check()
-        assert dir_.join("lib").check()
+        assert dir_.joinpath("bin", "python").exists()
+        assert dir_.joinpath("bin", "pip").exists()
+        assert dir_.joinpath("lib").exists()
 
     # Test running create on an existing environment. It should be deleted.
-    dir_.ensure("test.txt")
+    dir_.joinpath("test.txt").touch()
     venv.create()
-    assert not dir_.join("test.txt").check()
+    assert not dir_.joinpath("test.txt").exists()
 
     # Test running create on an existing environment with reuse_existing
     # enabled, it should not be deleted.
-    dir_.ensure("test.txt")
-    assert dir_.join("test.txt").check()
+    dir_.joinpath("test.txt").touch()
+    assert dir_.joinpath("test.txt").exists()
     venv.reuse_existing = True
 
     venv.create()
 
     assert venv._reused
-    assert dir_.join("test.txt").check()
+    assert dir_.joinpath("test.txt").exists()
 
 
 def test_create_reuse_environment(
-    make_one: Callable[..., tuple[VirtualEnv | ProcessEnv, Any]],
+    make_one: Callable[..., tuple[VirtualEnv | ProcessEnv, Path]],
 ) -> None:
     venv, _location = make_one(reuse_existing=True)
     venv.create()
@@ -475,7 +468,7 @@ def test_create_reuse_environment(
 
 
 def test_create_reuse_environment_with_different_interpreter(
-    make_one: Callable[..., tuple[VirtualEnv | ProcessEnv, Any]],
+    make_one: Callable[..., tuple[VirtualEnv | ProcessEnv, Path]],
     monkeypatch: pytest.MonkeyPatch,
 ) -> None:
     # Making the reuse requirement more strict
@@ -488,17 +481,17 @@ def test_create_reuse_environment_with_different_interpreter(
     monkeypatch.setattr(venv, "_check_reused_environment_interpreter", lambda: False)
 
     # Create a marker file. It should be gone after the environment is re-created.
-    location.join("marker").ensure()
+    location.joinpath("marker").touch()
 
     reused = not venv.create()
 
     assert not reused
-    assert not location.join("marker").check()
+    assert not location.joinpath("marker").exists()
 
 
 @has_uv
 def test_create_reuse_stale_venv_environment(
-    make_one: Callable[..., tuple[VirtualEnv | ProcessEnv, Any]],
+    make_one: Callable[..., tuple[VirtualEnv | ProcessEnv, Path]],
 ) -> None:
     venv, location = make_one(reuse_existing=True)
     venv.create()
@@ -510,7 +503,7 @@ def test_create_reuse_stale_venv_environment(
     version = 3.9.6
     uv = 0.1.9
     """
-    location.join("pyvenv.cfg").write(dedent(pyvenv_cfg))
+    location.joinpath("pyvenv.cfg").write_text(dedent(pyvenv_cfg), encoding="utf-8")
 
     reused = not venv.create()
 
@@ -519,7 +512,7 @@ def test_create_reuse_stale_venv_environment(
 
 def test_not_stale_virtualenv_environment(
     make_one: Callable[
-        ..., tuple[nox.virtualenv.VirtualEnv | nox.virtualenv.ProcessEnv, Any]
+        ..., tuple[nox.virtualenv.VirtualEnv | nox.virtualenv.ProcessEnv, Path]
     ],
     monkeypatch: pytest.MonkeyPatch,
 ) -> None:
@@ -537,7 +530,7 @@ def test_not_stale_virtualenv_environment(
 
 @has_conda
 def test_stale_virtualenv_to_conda_environment(
-    make_one: Callable[..., tuple[VirtualEnv | ProcessEnv, Any]],
+    make_one: Callable[..., tuple[VirtualEnv | ProcessEnv, Path]],
 ) -> None:
     venv, _location = make_one(reuse_existing=True, venv_backend="virtualenv")
     venv.create()
@@ -552,7 +545,7 @@ def test_stale_virtualenv_to_conda_environment(
 
 @has_conda
 def test_reuse_conda_environment(
-    make_one: Callable[..., tuple[VirtualEnv | ProcessEnv, Any]],
+    make_one: Callable[..., tuple[VirtualEnv | ProcessEnv, Path]],
 ) -> None:
     venv, _ = make_one(reuse_existing=True, venv_backend="conda")
     venv.create()
@@ -566,7 +559,7 @@ def test_reuse_conda_environment(
 # This mocks micromamba so that it doesn't need to be installed.
 @has_conda
 def test_micromamba_environment(
-    make_one: Callable[..., tuple[VirtualEnv | ProcessEnv, Any]],
+    make_one: Callable[..., tuple[VirtualEnv | ProcessEnv, Path]],
     monkeypatch: pytest.MonkeyPatch,
 ) -> None:
     conda_path = shutil.which("conda")
@@ -591,7 +584,7 @@ def test_micromamba_environment(
 )
 @has_conda
 def test_micromamba_channel_environment(
-    make_one: Callable[..., tuple[VirtualEnv, Any]],
+    make_one: Callable[..., tuple[VirtualEnv, Path]],
     monkeypatch: pytest.MonkeyPatch,
     params: list[str],
 ) -> None:
@@ -624,7 +617,7 @@ def test_micromamba_channel_environment(
     ],
 )
 def test_stale_environment(
-    make_one: Callable[..., tuple[VirtualEnv, Any]],
+    make_one: Callable[..., tuple[VirtualEnv, Path]],
     frm: str,
     to: str,
     result: bool,
@@ -643,7 +636,7 @@ def test_stale_environment(
 
 
 def test_passthrough_environment_venv_backend(
-    make_one: Callable[..., tuple[ProcessEnv, Any]],
+    make_one: Callable[..., tuple[ProcessEnv, Path]],
 ) -> None:
     venv, _ = make_one(venv_backend="none")
     venv.create()
@@ -652,7 +645,7 @@ def test_passthrough_environment_venv_backend(
 
 @has_uv
 def test_create_reuse_stale_virtualenv_environment(
-    make_one: Callable[..., tuple[VirtualEnv | ProcessEnv, Any]],
+    make_one: Callable[..., tuple[VirtualEnv | ProcessEnv, Path]],
     monkeypatch: pytest.MonkeyPatch,
 ) -> None:
     monkeypatch.setenv("NOX_ENABLE_STALENESS_CHECK", "1")
@@ -670,7 +663,7 @@ def test_create_reuse_stale_virtualenv_environment(
     base-exec-prefix = /usr
     base-executable = /usr/bin/python3.9
     """
-    location.join("pyvenv.cfg").write(dedent(pyvenv_cfg))
+    location.joinpath("pyvenv.cfg").write_text(dedent(pyvenv_cfg), encoding="utf-8")
 
     reused = not venv.create()
 
@@ -681,14 +674,16 @@ def test_create_reuse_stale_virtualenv_environment(
 
 @has_uv
 def test_create_reuse_uv_environment(
-    make_one: Callable[..., tuple[VirtualEnv | ProcessEnv, Any]],
+    make_one: Callable[..., tuple[VirtualEnv | ProcessEnv, Path]],
 ) -> None:
     venv, location = make_one(reuse_existing=True, venv_backend="uv")
     venv.create()
 
     # Place a spurious occurrence of "uv" in the pyvenv.cfg.
-    pyvenv_cfg = location.join("pyvenv.cfg")
-    pyvenv_cfg.write(pyvenv_cfg.read() + "bogus = uv\n")
+    pyvenv_cfg = location.joinpath("pyvenv.cfg")
+    pyvenv_cfg.write_text(
+        pyvenv_cfg.read_text(encoding="utf-8") + "bogus = uv\n", encoding="utf-8"
+    )
 
     reused = not venv.create()
 
@@ -779,7 +774,7 @@ def test_uv_install(requested_python: str, expected_result: bool) -> None:
 
 
 def test_create_reuse_venv_environment(
-    make_one: Callable[..., tuple[VirtualEnv | ProcessEnv, Any]],
+    make_one: Callable[..., tuple[VirtualEnv | ProcessEnv, Path]],
     monkeypatch: pytest.MonkeyPatch,
 ) -> None:
     # Making the reuse requirement more strict
@@ -789,8 +784,11 @@ def test_create_reuse_venv_environment(
     venv.create()
 
     # Place a spurious occurrence of "virtualenv" in the pyvenv.cfg.
-    pyvenv_cfg = location.join("pyvenv.cfg")
-    pyvenv_cfg.write(pyvenv_cfg.read() + "bogus = virtualenv\n")
+    pyvenv_cfg = location.joinpath("pyvenv.cfg")
+    pyvenv_cfg.write_text(
+        pyvenv_cfg.read_text(encoding="utf-8") + "bogus = virtualenv\n",
+        encoding="utf-8",
+    )
 
     reused = not venv.create()
 
@@ -800,17 +798,17 @@ def test_create_reuse_venv_environment(
 
 @pytest.mark.skipif(IS_WINDOWS, reason="Avoid 'No pyvenv.cfg file' error on Windows.")
 def test_create_reuse_oldstyle_virtualenv_environment(
-    make_one: Callable[..., tuple[VirtualEnv | ProcessEnv, Any]],
+    make_one: Callable[..., tuple[VirtualEnv | ProcessEnv, Path]],
 ) -> None:
     venv, location = make_one(reuse_existing=True)
     venv.create()
 
-    pyvenv_cfg = location.join("pyvenv.cfg")
-    if not pyvenv_cfg.check():
+    pyvenv_cfg = location.joinpath("pyvenv.cfg")
+    if not pyvenv_cfg.exists():
         pytest.skip("Requires virtualenv >= 20.0.0.")
 
     # virtualenv < 20.0.0 does not create a pyvenv.cfg file.
-    pyvenv_cfg.remove()
+    pyvenv_cfg.unlink()
 
     reused = not venv.create()
 
@@ -820,7 +818,7 @@ def test_create_reuse_oldstyle_virtualenv_environment(
 
 @pytest.mark.skipif(IS_WINDOWS, reason="Avoid 'No pyvenv.cfg file' error on Windows.")
 def test_inner_functions_reusing_venv(
-    make_one: Callable[..., tuple[VirtualEnv, Any]],
+    make_one: Callable[..., tuple[VirtualEnv, Path]],
     monkeypatch: pytest.MonkeyPatch,
 ) -> None:
     monkeypatch.setenv("NOX_ENABLE_STALENESS_CHECK", "1")
@@ -834,7 +832,7 @@ def test_inner_functions_reusing_venv(
     version = 3.10
     base-prefix = foo
     """
-    location.join("pyvenv.cfg").write(dedent(pyvenv_cfg))
+    location.joinpath("pyvenv.cfg").write_text(dedent(pyvenv_cfg), encoding="utf-8")
 
     config = venv._read_pyvenv_cfg()
     assert config
@@ -851,7 +849,7 @@ def test_inner_functions_reusing_venv(
     reason="Python 2.7 unsupported for virtualenv>=20.22.0",
 )
 def test_create_reuse_python2_environment(
-    make_one: Callable[..., tuple[VirtualEnv, Any]],
+    make_one: Callable[..., tuple[VirtualEnv, Path]],
 ) -> None:
     venv, _location = make_one(reuse_existing=True, interpreter="2.7")
 
@@ -866,7 +864,7 @@ def test_create_reuse_python2_environment(
 
 
 def test_create_venv_backend(
-    make_one: Callable[..., tuple[VirtualEnv, Any]],
+    make_one: Callable[..., tuple[VirtualEnv, Path]],
 ) -> None:
     venv, _dir = make_one(venv_backend="venv")
     venv.create()
@@ -874,16 +872,16 @@ def test_create_venv_backend(
 
 @pytest.mark.skipif(IS_WINDOWS, reason="Not testing multiple interpreters on Windows.")
 def test_create_interpreter(
-    make_one: Callable[..., tuple[VirtualEnv, Any]],
+    make_one: Callable[..., tuple[VirtualEnv, Path]],
 ) -> None:
     venv, dir_ = make_one(interpreter="python3")
     venv.create()
-    assert dir_.join("bin", "python").check()
-    assert dir_.join("bin", "python3").check()
+    assert dir_.joinpath("bin", "python").exists()
+    assert dir_.joinpath("bin", "python3").exists()
 
 
 def test__resolved_interpreter_none(
-    make_one: Callable[..., tuple[VirtualEnv, Any]],
+    make_one: Callable[..., tuple[VirtualEnv, Path]],
 ) -> None:
     # Establish that the _resolved_interpreter method is a no-op if the
     # interpreter is not set.
@@ -905,7 +903,7 @@ def test__resolved_interpreter_none(
 @mock.patch.object(shutil, "which", return_value=True)
 def test__resolved_interpreter_numerical_non_windows(
     which: mock.Mock,
-    make_one: Callable[..., tuple[VirtualEnv, Any]],
+    make_one: Callable[..., tuple[VirtualEnv, Path]],
     input_: str,
     expected: str,
 ) -> None:
@@ -920,7 +918,7 @@ def test__resolved_interpreter_numerical_non_windows(
 @mock.patch.object(shutil, "which", return_value=False)
 def test__resolved_interpreter_invalid_numerical_id(
     which: mock.Mock,
-    make_one: Callable[..., tuple[VirtualEnv, Any]],
+    make_one: Callable[..., tuple[VirtualEnv, Path]],
     input_: str,
 ) -> None:
     venv, _ = make_one(interpreter=input_)
@@ -934,7 +932,7 @@ def test__resolved_interpreter_invalid_numerical_id(
 @mock.patch("nox.virtualenv._SYSTEM", new="Linux")
 @mock.patch.object(shutil, "which", return_value=False)
 def test__resolved_interpreter_32_bit_non_windows(
-    which: mock.Mock, make_one: Callable[..., tuple[VirtualEnv, Any]]
+    which: mock.Mock, make_one: Callable[..., tuple[VirtualEnv, Path]]
 ) -> None:
     venv, _ = make_one(interpreter="3.6-32")
 
@@ -946,7 +944,7 @@ def test__resolved_interpreter_32_bit_non_windows(
 @mock.patch("nox.virtualenv._SYSTEM", new="Linux")
 @mock.patch.object(shutil, "which", return_value=True)
 def test__resolved_interpreter_non_windows(
-    which: mock.Mock, make_one: Callable[..., tuple[VirtualEnv, Any]]
+    which: mock.Mock, make_one: Callable[..., tuple[VirtualEnv, Path]]
 ) -> None:
     # Establish that the interpreter is simply passed through resolution
     # on non-Windows.
@@ -959,7 +957,7 @@ def test__resolved_interpreter_non_windows(
 @mock.patch("nox.virtualenv._SYSTEM", new="Windows")
 @mock.patch.object(shutil, "which")
 def test__resolved_interpreter_windows_full_path(
-    which: mock.Mock, make_one: Callable[..., tuple[VirtualEnv, Any]]
+    which: mock.Mock, make_one: Callable[..., tuple[VirtualEnv, Path]]
 ) -> None:
     # Establish that if we get a fully-qualified system path (on Windows
     # or otherwise) and the path exists, that we accept it.
@@ -984,7 +982,7 @@ def test__resolved_interpreter_windows_full_path(
 def test__resolved_interpreter_windows_pyexe(
     which: mock.Mock,
     run: mock.Mock,
-    make_one: Callable[..., tuple[VirtualEnv, Any]],
+    make_one: Callable[..., tuple[VirtualEnv, Path]],
     input_: str,
     expected: str,
 ) -> None:
@@ -1017,7 +1015,7 @@ def test__resolved_interpreter_windows_pyexe(
 @mock.patch.object(subprocess, "run")
 @mock.patch.object(shutil, "which")
 def test__resolved_interpreter_windows_pyexe_fails(
-    which: mock.Mock, run: mock.Mock, make_one: Callable[..., tuple[VirtualEnv, Any]]
+    which: mock.Mock, run: mock.Mock, make_one: Callable[..., tuple[VirtualEnv, Path]]
 ) -> None:
     # Establish that if the py launcher fails, we give the right error.
     venv, _ = make_one(interpreter="python3.6")
@@ -1041,7 +1039,7 @@ def test__resolved_interpreter_windows_pyexe_fails(
 @mock.patch("nox.virtualenv._SYSTEM", new="Windows")
 @mock.patch("nox.virtualenv.UV_PYTHON_SUPPORT", new=False)
 def test__resolved_interpreter_windows_path_and_version(
-    make_one: Callable[..., tuple[VirtualEnv, Any]],
+    make_one: Callable[..., tuple[VirtualEnv, Path]],
     patch_sysfind: Callable[..., None],
 ) -> None:
     # Establish that if we get a standard pythonX.Y path, we look it
@@ -1073,7 +1071,7 @@ def test__resolved_interpreter_windows_path_and_version_fails(
     input_: str,
     sysfind_result: None | str,
     sysexec_result: str,
-    make_one: Callable[..., tuple[VirtualEnv, Any]],
+    make_one: Callable[..., tuple[VirtualEnv, Path]],
     patch_sysfind: Callable[..., None],
 ) -> None:
     # Establish that if we get a standard pythonX.Y path, we look it
@@ -1094,7 +1092,7 @@ def test__resolved_interpreter_windows_path_and_version_fails(
 @mock.patch("nox.virtualenv._SYSTEM", new="Windows")
 @mock.patch.object(shutil, "which")
 def test__resolved_interpreter_not_found(
-    which: mock.Mock, make_one: Callable[..., tuple[VirtualEnv, Any]]
+    which: mock.Mock, make_one: Callable[..., tuple[VirtualEnv, Path]]
 ) -> None:
     # Establish that if an interpreter cannot be found at a standard
     # location on Windows, we raise a useful error.
@@ -1111,7 +1109,7 @@ def test__resolved_interpreter_not_found(
 @mock.patch("nox.virtualenv._SYSTEM", new="Windows")
 @mock.patch("nox.virtualenv.locate_via_py", new=lambda _: None)  # type: ignore[misc]  # noqa: PT008
 def test__resolved_interpreter_nonstandard(
-    make_one: Callable[..., tuple[VirtualEnv, Any]],
+    make_one: Callable[..., tuple[VirtualEnv, Path]],
 ) -> None:
     # Establish that we do not try to resolve non-standard locations
     # on Windows.
@@ -1124,7 +1122,7 @@ def test__resolved_interpreter_nonstandard(
 @mock.patch("nox.virtualenv._SYSTEM", new="Linux")
 @mock.patch.object(shutil, "which", return_value=True)
 def test__resolved_interpreter_cache_result(
-    which: mock.Mock, make_one: Callable[..., tuple[VirtualEnv, Any]]
+    which: mock.Mock, make_one: Callable[..., tuple[VirtualEnv, Path]]
 ) -> None:
     venv, _ = make_one(interpreter="3.6")
 
@@ -1140,7 +1138,7 @@ def test__resolved_interpreter_cache_result(
 @mock.patch("nox.virtualenv._SYSTEM", new="Linux")
 @mock.patch.object(shutil, "which", return_value=None)
 def test__resolved_interpreter_cache_failure(
-    which: mock.Mock, make_one: Callable[..., tuple[VirtualEnv, Any]]
+    which: mock.Mock, make_one: Callable[..., tuple[VirtualEnv, Path]]
 ) -> None:
     venv, _ = make_one(interpreter="3.7-32")