Repository: pypa/pip
Pull request #11556 (2022-10-27 16:46:41)

Add section clarifying pip version support
## Why
Since pip is a widely used library by the python community, it would be nice to have some formal documentation out there making clear what versions are supported.

## What these changes do
Adds a section to the release-process page titled Supported Versions, along with language making clear that the latest major version of pip is the only supported version. It also encourages folks make regular updates.

## Other considerations:
Since this is a documentation update, I wasn't sure if this warrants a news file. If folks feel its warranted please let me know and I'll add that to this PR!

## See also:
GH issue [11549](https://github.com/pypa/pip/issues/11549)





Patch:
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 7687b1b8c..c9cfb7b8e 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -122,7 +122,7 @@ jobs:
         if: matrix.os == 'MacOS'
         run: brew install breezy
 
-      - run: pip install nox 'virtualenv<20' 'setuptools != 60.6.0'
+      - run: pip install nox
 
       # Main check
       - name: Run unit tests
@@ -179,7 +179,7 @@ jobs:
           $acl.AddAccessRule($rule)
           Set-Acl "R:\Temp" $acl
 
-      - run: pip install nox 'virtualenv<20'
+      - run: pip install nox
         env:
           TEMP: "R:\\Temp"
 
@@ -261,7 +261,7 @@ jobs:
       - name: Install Ubuntu dependencies
         run: sudo apt-get install bzr
 
-      - run: pip install nox 'virtualenv<20'
+      - run: pip install nox
 
       - name: Run unit tests
         run: >-
diff --git a/docs/html/development/release-process.rst b/docs/html/development/release-process.rst
index 188d3e87b..acff1204e 100644
--- a/docs/html/development/release-process.rst
+++ b/docs/html/development/release-process.rst
@@ -65,6 +65,13 @@ their merits.
   ``pip._internal.utils.deprecation.deprecated``. The function is not a part of
   pip's public API.
 
+Supported Versions
+==================
+
+The latest version of the pip is the only supported version, previous
+versions should be considered unsupported. Users are encouraged to make
+regular updates to their version of pip in order to remain supported.
+
 .. _`Python 2 Support`:
 
 Python 2 Support
diff --git a/news/B5BABEE8-4FFA-4D62-87AB-FE7450141ECF.trivial.rst b/news/B5BABEE8-4FFA-4D62-87AB-FE7450141ECF.trivial.rst
new file mode 100644
index 000000000..720c98885
--- /dev/null
+++ b/news/B5BABEE8-4FFA-4D62-87AB-FE7450141ECF.trivial.rst
@@ -0,0 +1,5 @@
+Git 2.38.1 patched CVE-2022-39253 by disaling automated fetch against a
+``file:`` repository. This breaks git submodule, which is used by a pip test.
+Information on how projects relying on automated fetch should  configure git
+correctly after this change is lacking, so the test is disabled for now until
+someone can come up with a better solution.
diff --git a/setup.cfg b/setup.cfg
index dae2f21b1..1502abfc8 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -63,7 +63,6 @@ xfail_strict = True
 markers =
     network: tests that need network
     incompatible_with_sysconfig
-    incompatible_with_test_venv
     incompatible_with_venv
     no_auto_tempdir_manager
     unit: unit tests
diff --git a/src/pip/_internal/utils/virtualenv.py b/src/pip/_internal/utils/virtualenv.py
index c926db4c3..882e36f5c 100644
--- a/src/pip/_internal/utils/virtualenv.py
+++ b/src/pip/_internal/utils/virtualenv.py
@@ -19,7 +19,7 @@ def _running_under_venv() -> bool:
     return sys.prefix != getattr(sys, "base_prefix", sys.prefix)
 
 
-def _running_under_regular_virtualenv() -> bool:
+def _running_under_legacy_virtualenv() -> bool:
     """Checks if sys.real_prefix is set.
 
     This handles virtual environments created with pypa's virtualenv.
@@ -29,8 +29,8 @@ def _running_under_regular_virtualenv() -> bool:
 
 
 def running_under_virtualenv() -> bool:
-    """Return True if we're running inside a virtualenv, False otherwise."""
-    return _running_under_venv() or _running_under_regular_virtualenv()
+    """True if we're running inside a virtual environment, False otherwise."""
+    return _running_under_venv() or _running_under_legacy_virtualenv()
 
 
 def _get_pyvenv_cfg_lines() -> Optional[List[str]]:
@@ -77,7 +77,7 @@ def _no_global_under_venv() -> bool:
     return False
 
 
-def _no_global_under_regular_virtualenv() -> bool:
+def _no_global_under_legacy_virtualenv() -> bool:
     """Check if "no-global-site-packages.txt" exists beside site.py
 
     This mirrors logic in pypa/virtualenv for determining whether system
@@ -98,7 +98,7 @@ def virtualenv_no_global() -> bool:
     if _running_under_venv():
         return _no_global_under_venv()
 
-    if _running_under_regular_virtualenv():
-        return _no_global_under_regular_virtualenv()
+    if _running_under_legacy_virtualenv():
+        return _no_global_under_legacy_virtualenv()
 
     return False


Test patch:
diff --git a/tests/conftest.py b/tests/conftest.py
index 44aa56026..46975b29b 100644
--- a/tests/conftest.py
+++ b/tests/conftest.py
@@ -108,10 +108,6 @@ def pytest_collection_modifyitems(config: Config, items: List[pytest.Function])
             if item.get_closest_marker("network") is not None:
                 item.add_marker(pytest.mark.flaky(reruns=3, reruns_delay=2))
 
-        if item.get_closest_marker("incompatible_with_test_venv") and config.getoption(
-            "--use-venv"
-        ):
-            item.add_marker(pytest.mark.skip("Incompatible with test venv"))
         if (
             item.get_closest_marker("incompatible_with_venv")
             and sys.prefix != sys.base_prefix
@@ -474,9 +470,6 @@ def virtualenv_template(
         ):
             (venv.bin / exe).unlink()
 
-    # Enable user site packages.
-    venv.user_site_packages = True
-
     # Rename original virtualenv directory to make sure
     # it's not reused by mistake from one of the copies.
     venv_template = tmpdir / "venv_template"
@@ -742,3 +735,8 @@ def mock_server() -> Iterator[MockServer]:
 @pytest.fixture
 def proxy(request: pytest.FixtureRequest) -> str:
     return request.config.getoption("proxy")
+
+
+@pytest.fixture
+def enable_user_site(virtualenv: VirtualEnvironment) -> None:
+    virtualenv.user_site_packages = True
diff --git a/tests/functional/test_build_env.py b/tests/functional/test_build_env.py
index 869e8ad92..93a6b930f 100644
--- a/tests/functional/test_build_env.py
+++ b/tests/functional/test_build_env.py
@@ -204,7 +204,7 @@ def test_build_env_overlay_prefix_has_priority(script: PipTestEnvironment) -> No
     assert result.stdout.strip() == "2.0", str(result)
 
 
-@pytest.mark.incompatible_with_test_venv
+@pytest.mark.usefixtures("enable_user_site")
 def test_build_env_isolation(script: PipTestEnvironment) -> None:
 
     # Create dummy `pkg` wheel.
diff --git a/tests/functional/test_freeze.py b/tests/functional/test_freeze.py
index 535581121..49b362d7e 100644
--- a/tests/functional/test_freeze.py
+++ b/tests/functional/test_freeze.py
@@ -862,7 +862,7 @@ def test_freeze_with_requirement_option_package_repeated_multi_file(
 
 
 @pytest.mark.network
-@pytest.mark.incompatible_with_test_venv
+@pytest.mark.usefixtures("enable_user_site")
 def test_freeze_user(
     script: PipTestEnvironment, virtualenv: VirtualEnvironment, data: TestData
 ) -> None:
@@ -900,7 +900,7 @@ def test_freeze_path(tmpdir: Path, script: PipTestEnvironment, data: TestData) -
 
 
 @pytest.mark.network
-@pytest.mark.incompatible_with_test_venv
+@pytest.mark.usefixtures("enable_user_site")
 def test_freeze_path_exclude_user(
     tmpdir: Path, script: PipTestEnvironment, data: TestData
 ) -> None:
diff --git a/tests/functional/test_install.py b/tests/functional/test_install.py
index 876f2e12a..f61137268 100644
--- a/tests/functional/test_install.py
+++ b/tests/functional/test_install.py
@@ -171,7 +171,7 @@ def test_pep518_allows_missing_requires(
     assert result.files_created
 
 
-@pytest.mark.incompatible_with_test_venv
+@pytest.mark.usefixtures("enable_user_site")
 def test_pep518_with_user_pip(
     script: PipTestEnvironment, pip_src: Path, data: TestData, common_wheels: Path
 ) -> None:
@@ -2106,7 +2106,7 @@ def test_target_install_ignores_distutils_config_install_prefix(
     result.did_not_create(relative_script_base)
 
 
-@pytest.mark.incompatible_with_test_venv
+@pytest.mark.usefixtures("enable_user_site")
 def test_user_config_accepted(script: PipTestEnvironment) -> None:
     # user set in the config file is parsed as 0/1 instead of True/False.
     # Check that this doesn't cause a problem.
diff --git a/tests/functional/test_install_reqs.py b/tests/functional/test_install_reqs.py
index 19c526aab..14e1056ae 100644
--- a/tests/functional/test_install_reqs.py
+++ b/tests/functional/test_install_reqs.py
@@ -305,8 +305,7 @@ def test_install_local_with_subdirectory(script: PipTestEnvironment) -> None:
     result.assert_installed("version_subpkg.py", editable=False)
 
 
-@pytest.mark.incompatible_with_test_venv
-@pytest.mark.usefixtures("with_wheel")
+@pytest.mark.usefixtures("enable_user_site", "with_wheel")
 def test_wheel_user_with_prefix_in_pydistutils_cfg(
     script: PipTestEnvironment, data: TestData
 ) -> None:
diff --git a/tests/functional/test_install_user.py b/tests/functional/test_install_user.py
index d0bdbc3a5..c960d0de4 100644
--- a/tests/functional/test_install_user.py
+++ b/tests/functional/test_install_user.py
@@ -1,6 +1,7 @@
 """
 tests specific to "pip install --user"
 """
+import os
 import textwrap
 from os.path import curdir, isdir, isfile
 from pathlib import Path
@@ -8,7 +9,12 @@ from pathlib import Path
 import pytest
 
 from tests.lib import pyversion  # noqa: F401
-from tests.lib import PipTestEnvironment, TestData, need_svn
+from tests.lib import (
+    PipTestEnvironment,
+    TestData,
+    create_basic_wheel_for_package,
+    need_svn,
+)
 from tests.lib.local_repos import local_checkout
 from tests.lib.venv import VirtualEnvironment
 
@@ -29,9 +35,9 @@ def _patch_dist_in_site_packages(virtualenv: VirtualEnvironment) -> None:
     )
 
 
+@pytest.mark.usefixtures("enable_user_site")
 class Tests_UserSite:
     @pytest.mark.network
-    @pytest.mark.incompatible_with_test_venv
     def test_reset_env_system_site_packages_usersite(
         self, script: PipTestEnvironment
     ) -> None:
@@ -51,7 +57,6 @@ class Tests_UserSite:
     @pytest.mark.xfail
     @pytest.mark.network
     @need_svn
-    @pytest.mark.incompatible_with_test_venv
     def test_install_subversion_usersite_editable_with_distribute(
         self, script: PipTestEnvironment, tmpdir: Path
     ) -> None:
@@ -71,7 +76,6 @@ class Tests_UserSite:
         )
         result.assert_installed("INITools", use_user_site=True)
 
-    @pytest.mark.incompatible_with_test_venv
     @pytest.mark.usefixtures("with_wheel")
     def test_install_from_current_directory_into_usersite(
         self, script: PipTestEnvironment, data: TestData
@@ -117,7 +121,6 @@ class Tests_UserSite:
         )
 
     @pytest.mark.network
-    @pytest.mark.incompatible_with_test_venv
     def test_install_user_conflict_in_usersite(
         self, script: PipTestEnvironment
     ) -> None:
@@ -142,8 +145,6 @@ class Tests_UserSite:
         result2.did_create(egg_info_folder)
         assert not isfile(initools_v3_file), initools_v3_file
 
-    @pytest.mark.network
-    @pytest.mark.incompatible_with_test_venv
     def test_install_user_conflict_in_globalsite(
         self, virtualenv: VirtualEnvironment, script: PipTestEnvironment
     ) -> None:
@@ -151,31 +152,41 @@ class Tests_UserSite:
         Test user install with conflict in global site ignores site and
         installs to usersite
         """
-        _patch_dist_in_site_packages(virtualenv)
+        create_basic_wheel_for_package(script, "initools", "0.1")
+        create_basic_wheel_for_package(script, "initools", "0.2")
 
-        script.pip("install", "INITools==0.2", "--no-binary=:all:")
+        _patch_dist_in_site_packages(virtualenv)
 
-        result2 = script.pip("install", "--user", "INITools==0.1", "--no-binary=:all:")
+        script.pip(
+            "install",
+            "--no-index",
+            "--find-links",
+            script.scratch_path,
+            "initools==0.2",
+        )
+        result2 = script.pip(
+            "install",
+            "--no-index",
+            "--find-links",
+            script.scratch_path,
+            "--user",
+            "initools==0.1",
+        )
 
         # usersite has 0.1
-        # we still test for egg-info because no-binary implies setup.py install
-        egg_info_folder = script.user_site / f"INITools-0.1-py{pyversion}.egg-info"
+        dist_info_folder = script.user_site / "initools-0.1.dist-info"
         initools_folder = script.user_site / "initools"
-        result2.did_create(egg_info_folder)
+        result2.did_create(dist_info_folder)
         result2.did_create(initools_folder)
 
         # site still has 0.2 (can't look in result1; have to check)
-        egg_info_folder = (
-            script.base_path
-            / script.site_packages
-            / f"INITools-0.2-py{pyversion}.egg-info"
+        dist_info_folder = (
+            script.base_path / script.site_packages / "initools-0.2.dist-info"
         )
         initools_folder = script.base_path / script.site_packages / "initools"
-        assert isdir(egg_info_folder)
+        assert isdir(dist_info_folder)
         assert isdir(initools_folder)
 
-    @pytest.mark.network
-    @pytest.mark.incompatible_with_test_venv
     def test_upgrade_user_conflict_in_globalsite(
         self, virtualenv: VirtualEnvironment, script: PipTestEnvironment
     ) -> None:
@@ -183,32 +194,42 @@ class Tests_UserSite:
         Test user install/upgrade with conflict in global site ignores site and
         installs to usersite
         """
+        create_basic_wheel_for_package(script, "initools", "0.2")
+        create_basic_wheel_for_package(script, "initools", "0.3.1")
+
         _patch_dist_in_site_packages(virtualenv)
 
-        script.pip("install", "INITools==0.2", "--no-binary=:all:")
+        script.pip(
+            "install",
+            "--no-index",
+            "--find-links",
+            script.scratch_path,
+            "initools==0.2",
+        )
         result2 = script.pip(
-            "install", "--user", "--upgrade", "INITools", "--no-binary=:all:"
+            "install",
+            "--no-index",
+            "--find-links",
+            script.scratch_path,
+            "--user",
+            "--upgrade",
+            "initools",
         )
 
         # usersite has 0.3.1
-        # we still test for egg-info because no-binary implies setup.py install
-        egg_info_folder = script.user_site / f"INITools-0.3.1-py{pyversion}.egg-info"
+        dist_info_folder = script.user_site / "initools-0.3.1.dist-info"
         initools_folder = script.user_site / "initools"
-        result2.did_create(egg_info_folder)
+        result2.did_create(dist_info_folder)
         result2.did_create(initools_folder)
 
         # site still has 0.2 (can't look in result1; have to check)
-        egg_info_folder = (
-            script.base_path
-            / script.site_packages
-            / f"INITools-0.2-py{pyversion}.egg-info"
+        dist_info_folder = (
+            script.base_path / script.site_packages / "initools-0.2.dist-info"
         )
         initools_folder = script.base_path / script.site_packages / "initools"
-        assert isdir(egg_info_folder), result2.stdout
+        assert isdir(dist_info_folder), result2.stdout
         assert isdir(initools_folder)
 
-    @pytest.mark.network
-    @pytest.mark.incompatible_with_test_venv
     def test_install_user_conflict_in_globalsite_and_usersite(
         self, virtualenv: VirtualEnvironment, script: PipTestEnvironment
     ) -> None:
@@ -216,38 +237,57 @@ class Tests_UserSite:
         Test user install with conflict in globalsite and usersite ignores
         global site and updates usersite.
         """
-        _patch_dist_in_site_packages(virtualenv)
+        initools_v3_file_name = os.path.join("initools", "configparser.py")
+        create_basic_wheel_for_package(script, "initools", "0.1")
+        create_basic_wheel_for_package(script, "initools", "0.2")
+        create_basic_wheel_for_package(
+            script,
+            "initools",
+            "0.3",
+            extra_files={initools_v3_file_name: "# Hi!"},
+        )
 
-        script.pip("install", "INITools==0.2", "--no-binary=:all:")
-        script.pip("install", "--user", "INITools==0.3", "--no-binary=:all:")
+        _patch_dist_in_site_packages(virtualenv)
 
-        result3 = script.pip("install", "--user", "INITools==0.1", "--no-binary=:all:")
+        script.pip(
+            "install",
+            "--no-index",
+            "--find-links",
+            script.scratch_path,
+            "initools==0.2",
+        )
+        script.pip(
+            "install",
+            "--no-index",
+            "--find-links",
+            script.scratch_path,
+            "--user",
+            "initools==0.3",
+        )
+        result3 = script.pip(
+            "install",
+            "--no-index",
+            "--find-links",
+            script.scratch_path,
+            "--user",
+            "initools==0.1",
+        )
 
         # usersite has 0.1
-        # we still test for egg-info because no-binary implies setup.py install
-        egg_info_folder = script.user_site / f"INITools-0.1-py{pyversion}.egg-info"
-        initools_v3_file = (
-            # file only in 0.3
-            script.base_path
-            / script.user_site
-            / "initools"
-            / "configparser.py"
-        )
-        result3.did_create(egg_info_folder)
+        dist_info_folder = script.user_site / "initools-0.1.dist-info"
+        result3.did_create(dist_info_folder)
+        initools_v3_file = script.base_path / script.user_site / initools_v3_file_name
         assert not isfile(initools_v3_file), initools_v3_file
 
         # site still has 0.2 (can't just look in result1; have to check)
-        egg_info_folder = (
-            script.base_path
-            / script.site_packages
-            / f"INITools-0.2-py{pyversion}.egg-info"
+        dist_info_folder = (
+            script.base_path / script.site_packages / "initools-0.2.dist-info"
         )
         initools_folder = script.base_path / script.site_packages / "initools"
-        assert isdir(egg_info_folder)
+        assert isdir(dist_info_folder)
         assert isdir(initools_folder)
 
     @pytest.mark.network
-    @pytest.mark.incompatible_with_test_venv
     def test_install_user_in_global_virtualenv_with_conflict_fails(
         self, script: PipTestEnvironment
     ) -> None:
diff --git a/tests/functional/test_install_vcs_git.py b/tests/functional/test_install_vcs_git.py
index 2171d3162..cb72ec0b2 100644
--- a/tests/functional/test_install_vcs_git.py
+++ b/tests/functional/test_install_vcs_git.py
@@ -544,6 +544,11 @@ def test_reinstalling_works_with_editable_non_master_branch(
 
 # TODO(pnasrat) fix all helpers to do right things with paths on windows.
 @pytest.mark.skipif("sys.platform == 'win32'")
+@pytest.mark.xfail(
+    condition=True,
+    reason="Git submodule against file: is not working; waiting for a good solution",
+    run=True,
+)
 def test_check_submodule_addition(script: PipTestEnvironment) -> None:
     """
     Submodules are pulled in on install and updated on upgrade.
diff --git a/tests/functional/test_install_wheel.py b/tests/functional/test_install_wheel.py
index e988e7419..49c2d1d6d 100644
--- a/tests/functional/test_install_wheel.py
+++ b/tests/functional/test_install_wheel.py
@@ -406,8 +406,7 @@ def test_wheel_record_lines_have_updated_hash_for_scripts(
     ]
 
 
-@pytest.mark.incompatible_with_test_venv
-@pytest.mark.usefixtures("with_wheel")
+@pytest.mark.usefixtures("enable_user_site", "with_wheel")
 def test_install_user_wheel(
     script: PipTestEnvironment, shared_data: TestData, tmpdir: Path
 ) -> None:
diff --git a/tests/functional/test_list.py b/tests/functional/test_list.py
index c7fdec2f2..d05fe9dce 100644
--- a/tests/functional/test_list.py
+++ b/tests/functional/test_list.py
@@ -129,7 +129,7 @@ def test_multiple_exclude_and_normalization(
 
 
 @pytest.mark.network
-@pytest.mark.incompatible_with_test_venv
+@pytest.mark.usefixtures("enable_user_site")
 def test_user_flag(script: PipTestEnvironment, data: TestData) -> None:
     """
     Test the behavior of --user flag in the list command
@@ -144,7 +144,7 @@ def test_user_flag(script: PipTestEnvironment, data: TestData) -> None:
 
 
 @pytest.mark.network
-@pytest.mark.incompatible_with_test_venv
+@pytest.mark.usefixtures("enable_user_site")
 def test_user_columns_flag(script: PipTestEnvironment, data: TestData) -> None:
     """
     Test the behavior of --user --format=columns flags in the list command
@@ -656,7 +656,7 @@ def test_list_path(tmpdir: Path, script: PipTestEnvironment, data: TestData) ->
     assert {"name": "simple", "version": "2.0"} in json_result
 
 
-@pytest.mark.incompatible_with_test_venv
+@pytest.mark.usefixtures("enable_user_site")
 def test_list_path_exclude_user(
     tmpdir: Path, script: PipTestEnvironment, data: TestData
 ) -> None:
diff --git a/tests/functional/test_new_resolver_user.py b/tests/functional/test_new_resolver_user.py
index 2f9fb65ba..4578c3114 100644
--- a/tests/functional/test_new_resolver_user.py
+++ b/tests/functional/test_new_resolver_user.py
@@ -7,7 +7,7 @@ from tests.lib import PipTestEnvironment, create_basic_wheel_for_package
 from tests.lib.venv import VirtualEnvironment
 
 
-@pytest.mark.incompatible_with_test_venv
+@pytest.mark.usefixtures("enable_user_site")
 def test_new_resolver_install_user(script: PipTestEnvironment) -> None:
     create_basic_wheel_for_package(script, "base", "0.1.0")
     result = script.pip(
@@ -22,7 +22,7 @@ def test_new_resolver_install_user(script: PipTestEnvironment) -> None:
     result.did_create(script.user_site / "base")
 
 
-@pytest.mark.incompatible_with_test_venv
+@pytest.mark.usefixtures("enable_user_site")
 def test_new_resolver_install_user_satisfied_by_global_site(
     script: PipTestEnvironment,
 ) -> None:
@@ -53,7 +53,7 @@ def test_new_resolver_install_user_satisfied_by_global_site(
     result.did_not_create(script.user_site / "base")
 
 
-@pytest.mark.incompatible_with_test_venv
+@pytest.mark.usefixtures("enable_user_site")
 def test_new_resolver_install_user_conflict_in_user_site(
     script: PipTestEnvironment,
 ) -> None:
@@ -91,7 +91,7 @@ def test_new_resolver_install_user_conflict_in_user_site(
     result.did_not_create(base_2_dist_info)
 
 
-@pytest.mark.incompatible_with_test_venv
+@pytest.mark.usefixtures("enable_user_site")
 def test_new_resolver_install_user_in_virtualenv_with_conflict_fails(
     script: PipTestEnvironment,
 ) -> None:
@@ -141,8 +141,7 @@ def patch_dist_in_site_packages(virtualenv: VirtualEnvironment) -> None:
     )
 
 
-@pytest.mark.incompatible_with_test_venv
-@pytest.mark.usefixtures("patch_dist_in_site_packages")
+@pytest.mark.usefixtures("enable_user_site", "patch_dist_in_site_packages")
 def test_new_resolver_install_user_reinstall_global_site(
     script: PipTestEnvironment,
 ) -> None:
@@ -177,8 +176,7 @@ def test_new_resolver_install_user_reinstall_global_site(
     assert "base" in site_packages_content
 
 
-@pytest.mark.incompatible_with_test_venv
-@pytest.mark.usefixtures("patch_dist_in_site_packages")
+@pytest.mark.usefixtures("enable_user_site", "patch_dist_in_site_packages")
 def test_new_resolver_install_user_conflict_in_global_site(
     script: PipTestEnvironment,
 ) -> None:
@@ -215,8 +213,7 @@ def test_new_resolver_install_user_conflict_in_global_site(
     assert "base-1.0.0.dist-info" in site_packages_content
 
 
-@pytest.mark.incompatible_with_test_venv
-@pytest.mark.usefixtures("patch_dist_in_site_packages")
+@pytest.mark.usefixtures("enable_user_site", "patch_dist_in_site_packages")
 def test_new_resolver_install_user_conflict_in_global_and_user_sites(
     script: PipTestEnvironment,
 ) -> None:
diff --git a/tests/functional/test_uninstall_user.py b/tests/functional/test_uninstall_user.py
index 6d48fe162..0bf2e6d41 100644
--- a/tests/functional/test_uninstall_user.py
+++ b/tests/functional/test_uninstall_user.py
@@ -6,12 +6,12 @@ from os.path import isdir, isfile, normcase
 import pytest
 
 from tests.functional.test_install_user import _patch_dist_in_site_packages
-from tests.lib import pyversion  # noqa: F401
 from tests.lib import PipTestEnvironment, TestData, assert_all_changes
 from tests.lib.venv import VirtualEnvironment
+from tests.lib.wheel import make_wheel
 
 
-@pytest.mark.incompatible_with_test_venv
+@pytest.mark.usefixtures("enable_user_site")
 class Tests_UninstallUserSite:
     @pytest.mark.network
     def test_uninstall_from_usersite(self, script: PipTestEnvironment) -> None:
@@ -28,14 +28,39 @@ class Tests_UninstallUserSite:
         """
         Test uninstall from usersite (with same dist in global site)
         """
+        entry_points_txt = "[console_scripts]\nscript = pkg:func"
+        make_wheel(
+            "pkg",
+            "0.1",
+            extra_metadata_files={"entry_points.txt": entry_points_txt},
+        ).save_to_dir(script.scratch_path)
+        make_wheel(
+            "pkg",
+            "0.1.1",
+            extra_metadata_files={"entry_points.txt": entry_points_txt},
+        ).save_to_dir(script.scratch_path)
+
         _patch_dist_in_site_packages(virtualenv)
 
-        script.pip_install_local("pip-test-package==0.1", "--no-binary=:all:")
+        script.pip(
+            "install",
+            "--no-index",
+            "--find-links",
+            script.scratch_path,
+            "--no-warn-script-location",
+            "pkg==0.1",
+        )
 
-        result2 = script.pip_install_local(
-            "--user", "pip-test-package==0.1.1", "--no-binary=:all:"
+        result2 = script.pip(
+            "install",
+            "--no-index",
+            "--find-links",
+            script.scratch_path,
+            "--no-warn-script-location",
+            "--user",
+            "pkg==0.1.1",
         )
-        result3 = script.pip("uninstall", "-vy", "pip-test-package")
+        result3 = script.pip("uninstall", "-vy", "pkg")
 
         # uninstall console is mentioning user scripts, but not global scripts
         assert normcase(script.user_bin_path) in result3.stdout, str(result3)
@@ -45,13 +70,8 @@ class Tests_UninstallUserSite:
         assert_all_changes(result2, result3, [script.venv / "build", "cache"])
 
         # site still has 0.2 (can't look in result1; have to check)
-        # keep checking for egg-info because no-binary implies setup.py install
-        egg_info_folder = (
-            script.base_path
-            / script.site_packages
-            / f"pip_test_package-0.1-py{pyversion}.egg-info"
-        )
-        assert isdir(egg_info_folder)
+        dist_info_folder = script.base_path / script.site_packages / "pkg-0.1.dist-info"
+        assert isdir(dist_info_folder)
 
     def test_uninstall_editable_from_usersite(
         self, script: PipTestEnvironment, data: TestData
diff --git a/tests/lib/venv.py b/tests/lib/venv.py
index ab6644bc9..e65a32912 100644
--- a/tests/lib/venv.py
+++ b/tests/lib/venv.py
@@ -3,10 +3,11 @@ import os
 import shutil
 import subprocess
 import sys
+import sysconfig
 import textwrap
 import venv as _venv
 from pathlib import Path
-from typing import TYPE_CHECKING, Optional, Union
+from typing import TYPE_CHECKING, Dict, Optional, Union
 
 import virtualenv as _virtualenv
 
@@ -30,7 +31,7 @@ class VirtualEnvironment:
         location: Path,
         template: Optional["VirtualEnvironment"] = None,
         venv_type: Optional[VirtualEnvironmentType] = None,
-    ):
+    ) -> None:
         self.location = location
         assert template is None or venv_type is None
         self._venv_type: VirtualEnvironmentType
@@ -46,7 +47,13 @@ class VirtualEnvironment:
         self._update_paths()
         self._create()
 
-    def _update_paths(self) -> None:
+    @property
+    def _legacy_virtualenv(self) -> bool:
+        if self._venv_type != "virtualenv":
+            return False
+        return int(_virtualenv.__version__.split(".", 1)[0]) < 20
+
+    def __update_paths_legacy(self) -> None:
         home, lib, inc, bin = _virtualenv.path_locations(self.location)
         self.bin = Path(bin)
         self.site = Path(lib) / "site-packages"
@@ -57,6 +64,21 @@ class VirtualEnvironment:
         else:
             self.lib = Path(lib)
 
+    def _update_paths(self) -> None:
+        if self._legacy_virtualenv:
+            self.__update_paths_legacy()
+            return
+        bases = {
+            "installed_base": self.location,
+            "installed_platbase": self.location,
+            "base": self.location,
+            "platbase": self.location,
+        }
+        paths = sysconfig.get_paths(vars=bases)
+        self.bin = Path(paths["scripts"])
+        self.site = Path(paths["purelib"])
+        self.lib = Path(paths["stdlib"])
+
     def __repr__(self) -> str:
         return f"<VirtualEnvironment {self.location}>"
 
@@ -66,7 +88,11 @@ class VirtualEnvironment:
         if self._template:
             # On Windows, calling `_virtualenv.path_locations(target)`
             # will have created the `target` directory...
-            if sys.platform == "win32" and self.location.exists():
+            if (
+                self._legacy_virtualenv
+                and sys.platform == "win32"
+                and self.location.exists()
+            ):
                 self.location.rmdir()
             # Clone virtual environment from template.
             shutil.copytree(self._template.location, self.location, symlinks=True)
@@ -74,7 +100,7 @@ class VirtualEnvironment:
             self._user_site_packages = self._template.user_site_packages
         else:
             # Create a new virtual environment.
-            if self._venv_type == "virtualenv":
+            if self._legacy_virtualenv:
                 subprocess.check_call(
                     [
                         sys.executable,
@@ -83,20 +109,31 @@ class VirtualEnvironment:
                         "--no-pip",
                         "--no-wheel",
                         "--no-setuptools",
-                        str(self.location),
+                        os.fspath(self.location),
                     ]
                 )
-                self._fix_virtualenv_site_module()
+                self._fix_legacy_virtualenv_site_module()
+            elif self._venv_type == "virtualenv":
+                _virtualenv.cli_run(
+                    [
+                        "--no-pip",
+                        "--no-wheel",
+                        "--no-setuptools",
+                        os.fspath(self.location),
+                    ],
+                )
             elif self._venv_type == "venv":
                 builder = _venv.EnvBuilder()
                 context = builder.ensure_directories(self.location)
                 builder.create_configuration(context)
                 builder.setup_python(context)
                 self.site.mkdir(parents=True, exist_ok=True)
+            else:
+                raise RuntimeError(f"Unsupported venv type {self._venv_type!r}")
             self.sitecustomize = self._sitecustomize
             self.user_site_packages = self._user_site_packages
 
-    def _fix_virtualenv_site_module(self) -> None:
+    def _fix_legacy_virtualenv_site_module(self) -> None:
         # Patch `site.py` so user site work as expected.
         site_py = self.lib / "site.py"
         with open(site_py) as fp:
@@ -131,17 +168,15 @@ class VirtualEnvironment:
         assert compileall.compile_file(str(site_py), quiet=1, force=True)
 
     def _customize_site(self) -> None:
-        contents = ""
-        if self._venv_type == "venv":
+        if self._legacy_virtualenv:
+            contents = ""
+        else:
             # Enable user site (before system).
-            contents += textwrap.dedent(
-                """
+            contents = textwrap.dedent(
+                f"""
                 import os, site, sys
-
                 if not os.environ.get('PYTHONNOUSERSITE', False):
-
-                    site.ENABLE_USER_SITE = True
-
+                    site.ENABLE_USER_SITE = {self._user_site_packages}
                     # First, drop system-sites related paths.
                     original_sys_path = sys.path[:]
                     known_paths = set()
@@ -152,10 +187,9 @@ class VirtualEnvironment:
                         if path in original_sys_path:
                             original_sys_path.remove(path)
                     sys.path = original_sys_path
-
                     # Second, add user-site.
-                    site.addsitedir(site.getusersitepackages())
-
+                    if {self._user_site_packages}:
+                        site.addsitedir(site.getusersitepackages())
                     # Third, add back system-sites related paths.
                     for path in site.getsitepackages():
                         site.addsitedir(path)
@@ -168,6 +202,21 @@ class VirtualEnvironment:
         # Make sure bytecode is up-to-date too.
         assert compileall.compile_file(str(sitecustomize), quiet=1, force=True)
 
+    def _rewrite_pyvenv_cfg(self, replacements: Dict[str, str]) -> None:
+        pyvenv_cfg = self.location.joinpath("pyvenv.cfg")
+        lines = pyvenv_cfg.read_text(encoding="utf-8").splitlines()
+
+        def maybe_replace_line(line: str) -> str:
+            key = line.split("=", 1)[0].strip()
+            try:
+                value = replacements[key]
+            except KeyError:  # No need to replace.
+                return line
+            return f"{key} = {value}"
+
+        lines = [maybe_replace_line(line) for line in lines]
+        pyvenv_cfg.write_text("\n".join(lines), encoding="utf-8")
+
     def clear(self) -> None:
         self._create(clear=True)
 
@@ -192,11 +241,14 @@ class VirtualEnvironment:
     @user_site_packages.setter
     def user_site_packages(self, value: bool) -> None:
         self._user_site_packages = value
-        if self._venv_type == "virtualenv":
+        if self._legacy_virtualenv:
             marker = self.lib / "no-global-site-packages.txt"
             if self._user_site_packages:
                 marker.unlink()
             else:
                 marker.touch()
-        elif self._venv_type == "venv":
+        else:
+            self._rewrite_pyvenv_cfg(
+                {"include-system-site-packages": str(bool(value)).lower()}
+            )
             self._customize_site()
diff --git a/tests/requirements.txt b/tests/requirements.txt
index 9ce6d6207..84b7c14d4 100644
--- a/tests/requirements.txt
+++ b/tests/requirements.txt
@@ -7,7 +7,8 @@ pytest-rerunfailures
 pytest-xdist
 scripttest
 setuptools
-virtualenv < 20.0
+virtualenv < 20.0 ; python_version < '3.10'
+virtualenv >= 20.0 ; python_version >= '3.10'
 werkzeug
 wheel
 tomli-w
diff --git a/tests/unit/test_utils_virtualenv.py b/tests/unit/test_utils_virtualenv.py
index 38d5383ce..94461c6d8 100644
--- a/tests/unit/test_utils_virtualenv.py
+++ b/tests/unit/test_utils_virtualenv.py
@@ -63,7 +63,7 @@ def test_virtualenv_no_global_with_regular_virtualenv(
     monkeypatch.setattr(site, "__file__", os.fspath(tmpdir / "site.py"))
     monkeypatch.setattr(
         virtualenv,
-        "_running_under_regular_virtualenv",
+        "_running_under_legacy_virtualenv",
         lambda: under_virtualenv,
     )
     if no_global_file:
@@ -73,7 +73,7 @@ def test_virtualenv_no_global_with_regular_virtualenv(
 
 
 @pytest.mark.parametrize(
-    "pyvenv_cfg_lines, under_venv, expected, expect_warning",
+    "pyvenv_cfg_lines, under_venv, expect_no_global, expect_warning",
     [
         (None, False, False, False),
         (None, True, True, True),  # this has a warning.
@@ -104,15 +104,15 @@ def test_virtualenv_no_global_with_pep_405_virtual_environment(
     caplog: pytest.LogCaptureFixture,
     pyvenv_cfg_lines: Optional[List[str]],
     under_venv: bool,
-    expected: bool,
+    expect_no_global: bool,
     expect_warning: bool,
 ) -> None:
-    monkeypatch.setattr(virtualenv, "_running_under_regular_virtualenv", lambda: False)
+    monkeypatch.setattr(virtualenv, "_running_under_legacy_virtualenv", lambda: False)
     monkeypatch.setattr(virtualenv, "_get_pyvenv_cfg_lines", lambda: pyvenv_cfg_lines)
     monkeypatch.setattr(virtualenv, "_running_under_venv", lambda: under_venv)
 
     with caplog.at_level(logging.WARNING):
-        assert virtualenv.virtualenv_no_global() == expected
+        assert virtualenv.virtualenv_no_global() == expect_no_global
 
     if expect_warning:
         assert caplog.records