Repository: wntrblm/nox

Issue:
File system is busy for serveral seconds after nox session completes when using the virutalenv backend.
### Current Behavior

If you were to run nox -s <session_name> on a session from a working directory on a mounted file system, and then immediately unmount the file system after nox completes:

```bash
nox -s <session>
umount /mnt/mount_point
```
Umount will fail because the fileystem is held open by some scripts that look something like this:

```
python3 -m pip download --progress-bar off --disable-pip-version-check --only-binary=:all: --no-deps --python-version 3.8 -d /jenkins/.local/share/virtualenv/wheel/house setuptools<71.0.0
```


### Expected Behavior

All file access for processes related to nox are complete and nox is able to unmount the file system.

### Steps To Reproduce

1. With a simple noxfile containing a session that installs 3 requirements located on a removable file system
3. Run the following script:

```bash
cd /mnt/path/to/noxfile_dir
nox -s simple_session
cd -
umount /mnt/path
```

This should result in a failure.


### Environment

```Markdown
- OS: Ubuntu 20.04
- Python: 3.8.x (latest in ubuntu 20.04
- Nox: 2024.4.15
```


### Anything else?

The suggested fix for this would be to always add the [--no-periodic-update](https://virtualenv.pypa.io/en/latest/cli_interface.html#no-periodic-update) flag to nox's internal invocations of virtualenv.

Making this a default virtualenv backend specific feature would developers to avoid having to add complex logic when dealing with cases where `uv|virtualenv` is specified as the default backend. (--no-periodic-update is a virtualenv specific feature so adding it unconditionally would break other uv backends.

Discussion:
@cjolowicz: I'd suggest setting the environment variable corresponding to that option in your CI environment.

https://virtualenv.pypa.io/en/latest/cli_interface.html#env-vars@jamesharris-garmin: That seems overly combersome. and it was unclear how to set it in a configuration file.  Either way I don't see how this is desirable in nox to just periodically grab new versions of the virtualenv embedded dependencies as part of the default behavior.@henryiii: FYI, I think we used to support versions before 20.0.24, but now that's below our minimum, so it's safe to add now.@jamesharris-garmin: YAY! Thank you!

Solution patch:
diff --git a/nox/virtualenv.py b/nox/virtualenv.py
index 2a8d8ba..e416ed5 100644
--- a/nox/virtualenv.py
+++ b/nox/virtualenv.py
@@ -453,6 +453,7 @@ class VirtualEnv(ProcessEnv):
               be ``py -3.6-32``
             * ``X.Y.Z``, e.g. ``3.4.9``
             * ``pythonX.Y``, e.g. ``python2.7``
+            * ``pypyX.Y``, e.g. ``pypy3.10`` (also ``pypy-3.10`` allowed)
             * A path in the filesystem to a Python executable
 
             If not specified, this will use the currently running Python.
@@ -472,6 +473,10 @@ class VirtualEnv(ProcessEnv):
         venv_backend: str = "virtualenv",
         venv_params: Sequence[str] = (),
     ):
+        # "pypy-" -> "pypy"
+        if interpreter and interpreter.startswith("pypy-"):
+            interpreter = interpreter[:4] + interpreter[5:]
+
         self.location_name = location
         self.location = os.path.abspath(location)
         self.interpreter = interpreter
@@ -663,7 +668,13 @@ class VirtualEnv(ProcessEnv):
             return False
 
         if self.venv_backend == "virtualenv":
-            cmd = [sys.executable, "-m", "virtualenv", self.location]
+            cmd = [
+                sys.executable,
+                "-m",
+                "virtualenv",
+                self.location,
+                "--no-periodic-update",
+            ]
             if self.interpreter:
                 cmd.extend(["-p", self._resolved_interpreter])
         elif self.venv_backend == "uv":
diff --git a/noxfile.py b/noxfile.py
index 7531963..81dda9e 100644
--- a/noxfile.py
+++ b/noxfile.py
@@ -148,7 +148,8 @@ def docs(session: nox.Session) -> None:
 # The following sessions are only to be run in CI to check the nox GHA action
 def _check_python_version(session: nox.Session) -> None:
     if session.python.startswith("pypy"):
-        python_version = session.python[4:]
+        # Drop starting "pypy" and maybe "-"
+        python_version = session.python.lstrip("py-")
         implementation = "pypy"
     else:
         python_version = session.python
@@ -170,7 +171,7 @@ def _check_python_version(session: nox.Session) -> None:
 @nox.session(
     python=[
         *ALL_PYTHONS,
-        "pypy3.10",
+        "pypy-3.10",
     ],
     default=False,
 )


Test patch:
diff --git a/tests/test_virtualenv.py b/tests/test_virtualenv.py
index 9f1a6e7..dcc620a 100644
--- a/tests/test_virtualenv.py
+++ b/tests/test_virtualenv.py
@@ -322,6 +322,16 @@ def test_constructor_defaults(
     assert venv.venv_backend == "virtualenv"
 
 
+def test_constructor_pypy_dash(
+    make_one: Callable[..., tuple[VirtualEnv, Path]],
+) -> None:
+    venv, _ = make_one(interpreter="pypy-3.10")
+    assert venv.location
+    assert venv.interpreter == "pypy3.10"
+    assert venv.reuse_existing is False
+    assert venv.venv_backend == "virtualenv"
+
+
 @pytest.mark.skipif(IS_WINDOWS, reason="Not testing multiple interpreters on Windows.")
 def test_constructor_explicit(
     make_one: Callable[..., tuple[VirtualEnv, Path]],