Repository: pypa/pip

Issue:
`pip list` should not perform a self-update check
### Description

Running `pip` in env which is cu off from public network I found that on each execution it tries to check availability of its new version.
It causes long freezing of pip command and atthe end it prints message `WARNING: There was an error checking the latest version of pip.`


### Expected behavior

IMO `pip` should **only** do what is specified in commad lie parameters and nothing more.


### pip version

22.3.1

### Python version

3.8.16

### OS

Linux x86/64

### How to Reproduce

Run `pip list` in env which is cut off from access to the public network.

### Output

Example:
```
$ time pip list
Package                       Version
----------------------------- -----------------
alabaster                     0.7.12
appdirs                       1.4.4

[..]

urllib3                       1.26.12
wheel                         0.38.4
zipp                          3.11.0
WARNING: There was an error checking the latest version of pip.

real    2m0.771s
user    0m0.605s
sys     0m0.114s
```

### Code of Conduct

- [X] I agree to follow the [PSF Code of Conduct](https://www.python.org/psf/conduct/).

Discussion:

Patch:
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index bb46b772b..922e1440d 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -54,6 +54,13 @@ repos:
     types: [file]
     exclude: NEWS.rst  # The errors flagged in NEWS.rst are old.
 
+- repo: https://github.com/codespell-project/codespell
+  rev: v2.2.6
+  hooks:
+    - id: codespell
+      exclude: AUTHORS.txt|tests/data
+      args: ["--ignore-words", tools/codespell-ignore.txt]
+
 - repo: local
   hooks:
   - id: news-fragment-filenames
diff --git a/NEWS.rst b/NEWS.rst
index 6b1dff81e..ce6d8e6dd 100644
--- a/NEWS.rst
+++ b/NEWS.rst
@@ -304,7 +304,7 @@ Improved Documentation
 
 - Cross-reference the ``--python`` flag from the ``--prefix`` flag,
   and mention limitations of ``--prefix`` regarding script installation. (`#11775 <https://github.com/pypa/pip/issues/11775>`_)
-- Add SECURITY.md to make the policy offical. (`#11809 <https://github.com/pypa/pip/issues/11809>`_)
+- Add SECURITY.md to make the policy official. (`#11809 <https://github.com/pypa/pip/issues/11809>`_)
 - Add username to Git over SSH example. (`#11838 <https://github.com/pypa/pip/issues/11838>`_)
 - Quote extras in the pip install docs to guard shells with default glob
   qualifiers, like zsh. (`#11842 <https://github.com/pypa/pip/issues/11842>`_)
diff --git a/docs/html/ux-research-design/research-results/improving-pips-documentation.md b/docs/html/ux-research-design/research-results/improving-pips-documentation.md
index bec2120cd..765a8f106 100644
--- a/docs/html/ux-research-design/research-results/improving-pips-documentation.md
+++ b/docs/html/ux-research-design/research-results/improving-pips-documentation.md
@@ -36,7 +36,7 @@ We also:
 1. Asked for volunteers to participate in a diary study, documenting their experience solving pip problems. Unfortunately this was not completed due to lack of interest from the community.
 2. Asked for user feedback on the pip documentation site:
    ![screenshot of user feedback mechanism on pip docs](https://i.imgur.com/WJVjl8N.png)
-   Unfortunatly, we did not gather any useful feedback via this effort
+   Unfortunately, we did not gather any useful feedback via this effort
 3. [Installed analytics on the pip docs](https://github.com/pypa/pip/pull/9146). We are waiting for this to be merged and start providing useful data.
 
 ## Results
diff --git a/docs/html/ux-research-design/research-results/personas.md b/docs/html/ux-research-design/research-results/personas.md
index 06b84a8d4..e4f1a3df4 100644
--- a/docs/html/ux-research-design/research-results/personas.md
+++ b/docs/html/ux-research-design/research-results/personas.md
@@ -180,7 +180,7 @@ Making software was as defined earlier as "are you working on something reusable
 
 > "I have written software, sometimes for business and personal reasons. At one point I worked on a django website project, that was being used by 1000s of people. I don't think any of my live projects are based.
 
-> "Most of it is for sysadmin, automation. I lke to use python instead of shell scripting. I manage a server with wordpress sites. I wrote a script to update these sites, mailman list and sql DB management, and for different utilities." **- Participant 240313542**
+> "Most of it is for sysadmin, automation. I [like] to use python instead of shell scripting. I manage a server with wordpress sites. I wrote a script to update these sites, mailman list and sql DB management, and for different utilities." **- Participant 240313542**
 
 > "I use Python for creating things - like outputs for data scientist, software engineer. I make software to look at patterns, and analyse stuff. I think I'm a maker because someone else is using - they are colleagues. Usually its non-technical colleagues. I produce outputs - make data understandable. They use the results, or a package it behind a flask app. Or analyse graphs." **- Participant 240426799**
 
diff --git a/docs/html/ux-research-design/research-results/pip-upgrade-conflict.md b/docs/html/ux-research-design/research-results/pip-upgrade-conflict.md
index 6f35a550c..9261a3185 100644
--- a/docs/html/ux-research-design/research-results/pip-upgrade-conflict.md
+++ b/docs/html/ux-research-design/research-results/pip-upgrade-conflict.md
@@ -64,5 +64,5 @@ From the 407 responses that answered "why" a particular solution was best, the f
 Based on the results of this research, the pip UX team has made the following recommendations to the development team:
 
 - While the current behaviour exists, [warn the user when conflicts are introduced](https://github.com/pypa/pip/issues/7744#issuecomment-717573440)
-- [Change the current behaviour](https://github.com/pypa/pip/issues/9094), so that pip takes into account packages that are already installed when upgrading other packages. Show the user a warning when pip anticipates a depdenency conflict (as per option 4)
+- [Change the current behaviour](https://github.com/pypa/pip/issues/9094), so that pip takes into account packages that are already installed when upgrading other packages. Show the user a warning when pip anticipates a dependency conflict (as per option 4)
 - Explore [the possibility of adding additional flags to the upgrade command](https://github.com/pypa/pip/issues/9095), to give users more control
diff --git a/docs/html/ux-research-design/research-results/prioritizing-features.md b/docs/html/ux-research-design/research-results/prioritizing-features.md
index 12e6b8b24..3642042f3 100644
--- a/docs/html/ux-research-design/research-results/prioritizing-features.md
+++ b/docs/html/ux-research-design/research-results/prioritizing-features.md
@@ -85,9 +85,9 @@ Results varied by the amount of Python experience the user had.
 
 ![Screenshot of Warn about broken dependencies](https://i.imgur.com/uNv2tnG.png)
 
-#### Upgrade packages to the lastest version
+#### Upgrade packages to the latest version
 
-![Screenshot of Upgrade packages to the lastest version](https://i.imgur.com/pQgCLBO.png)
+![Screenshot of Upgrade packages to the latest version](https://i.imgur.com/pQgCLBO.png)
 
 #### Install packages from an alternative package index, or indexes
 
diff --git a/docs/html/ux-research-design/research-results/users-and-security.md b/docs/html/ux-research-design/research-results/users-and-security.md
index d9b79a057..fbc8f492f 100644
--- a/docs/html/ux-research-design/research-results/users-and-security.md
+++ b/docs/html/ux-research-design/research-results/users-and-security.md
@@ -62,7 +62,7 @@ Participants who spent a lot of their time writing Python code - either for comm
 
 They thought about where the software would be used, who would use it, and possible attack surfaces.
 
-> "On the basic point, I have to think about attack surfaces. If I am writing the thing (software), I have to give a crap. I have to answer the emails! In the code I push to[ pypi.org](http://pypi.org/) I think about it doubley. What could people do with this code? Whether I do a good job, that's different! I am aware of it when publishing it or making it publically available. Whether I do a good job, that's different! I am aware of it when publishing it or making it publically available. I rely on community resources - Python security related, I follow security people blogs, Twitter. I use Hypothesis for fuzz-testing. I also rely on having security policies in place and a reporting mechanism. I steer clear of crypto, I rely on other peoples. There's a certain amount of knowledge in the Python community, I am actively involved in it. If something happens, I will hear about it. I use Twitter, if something happens, in the morning it can take me awhile to figure out what's happened. I have a lot of trust in the ecosystem to be self healing. As long as you don't stray too far-off the reservation (into using odd or uncommon or new packages), it's a better sense of security." **- Participant (data scientist turned Python developer)**
+> "On the basic point, I have to think about attack surfaces. If I am writing the thing (software), I have to give a crap. I have to answer the emails! In the code I push to[ pypi.org](http://pypi.org/) I think about it doubley. What could people do with this code? Whether I do a good job, that's different! I am aware of it when publishing it or making it [publicly] available. Whether I do a good job, that's different! I am aware of it when publishing it or making it [publicly] available. I rely on community resources - Python security related, I follow security people blogs, Twitter. I use Hypothesis for fuzz-testing. I also rely on having security policies in place and a reporting mechanism. I steer clear of crypto, I rely on other peoples. There's a certain amount of knowledge in the Python community, I am actively involved in it. If something happens, I will hear about it. I use Twitter, if something happens, in the morning it can take me awhile to figure out what's happened. I have a lot of trust in the ecosystem to be self healing. As long as you don't stray too far-off the reservation (into using odd or uncommon or new packages), it's a better sense of security." **- Participant (data scientist turned Python developer)**
 
 > Yes, because I'm liable for that. If the problem is my code, and I deliver something and they get attacked. I'm screwed. **- Participant (professional Python developer and trainer)**
 
diff --git a/news/11677.feature.rst b/news/11677.feature.rst
new file mode 100644
index 000000000..b68a0db87
--- /dev/null
+++ b/news/11677.feature.rst
@@ -0,0 +1,1 @@
+``pip list`` no longer performs the pip version check unless ``--outdated`` or ``--uptodate`` is given.
diff --git a/news/12453.feature.rst b/news/12453.feature.rst
new file mode 100644
index 000000000..704cd012a
--- /dev/null
+++ b/news/12453.feature.rst
@@ -0,0 +1,1 @@
+Improve performance of resolution of large dependency trees, with more caching.
diff --git a/news/287f037c-108f-48dd-80a0-489921a6b2f3.trivial.rst b/news/287f037c-108f-48dd-80a0-489921a6b2f3.trivial.rst
new file mode 100644
index 000000000..446d4f9fe
--- /dev/null
+++ b/news/287f037c-108f-48dd-80a0-489921a6b2f3.trivial.rst
@@ -0,0 +1,1 @@
+Add codespell pre-commit hook to catch common misspellings.
diff --git a/news/4CCE4788-B8B3-402E-9A88-2981AD074999.trivial.rst b/news/4CCE4788-B8B3-402E-9A88-2981AD074999.trivial.rst
new file mode 100644
index 000000000..e69de29bb
diff --git a/src/pip/_internal/cache.py b/src/pip/_internal/cache.py
index f45ac23e9..6b4512672 100644
--- a/src/pip/_internal/cache.py
+++ b/src/pip/_internal/cache.py
@@ -44,7 +44,7 @@ class Cache:
         """Get parts of part that must be os.path.joined with cache_dir"""
 
         # We want to generate an url to use as our cache key, we don't want to
-        # just re-use the URL because it might have other items in the fragment
+        # just reuse the URL because it might have other items in the fragment
         # and we don't care about those.
         key_parts = {"url": link.url_without_fragment}
         if link.hash_name is not None and link.hash is not None:
diff --git a/src/pip/_internal/commands/list.py b/src/pip/_internal/commands/list.py
index e551dda9a..f51091991 100644
--- a/src/pip/_internal/commands/list.py
+++ b/src/pip/_internal/commands/list.py
@@ -135,6 +135,10 @@ class ListCommand(IndexGroupCommand):
         self.parser.insert_option_group(0, index_opts)
         self.parser.insert_option_group(0, self.cmd_opts)
 
+    def handle_pip_version_check(self, options: Values) -> None:
+        if options.outdated or options.uptodate:
+            super().handle_pip_version_check(options)
+
     def _build_package_finder(
         self, options: Values, session: PipSession
     ) -> PackageFinder:
diff --git a/src/pip/_internal/resolution/legacy/resolver.py b/src/pip/_internal/resolution/legacy/resolver.py
index 1d8cb915a..1dd0d7041 100644
--- a/src/pip/_internal/resolution/legacy/resolver.py
+++ b/src/pip/_internal/resolution/legacy/resolver.py
@@ -10,9 +10,6 @@ for sub-dependencies
     a. "first found, wins" (where the order is breadth first)
 """
 
-# The following comment should be removed at some point in the future.
-# mypy: strict-optional=False
-
 import logging
 import sys
 from collections import defaultdict
@@ -52,7 +49,7 @@ from pip._internal.utils.packaging import check_requires_python
 
 logger = logging.getLogger(__name__)
 
-DiscoveredDependencies = DefaultDict[str, List[InstallRequirement]]
+DiscoveredDependencies = DefaultDict[Optional[str], List[InstallRequirement]]
 
 
 def _check_dist_requires_python(
@@ -321,6 +318,7 @@ class Resolver(BaseResolver):
         """
         # Don't uninstall the conflict if doing a user install and the
         # conflict is not a user install.
+        assert req.satisfied_by is not None
         if not self.use_user_site or req.satisfied_by.in_usersite:
             req.should_reinstall = True
         req.satisfied_by = None
@@ -419,6 +417,8 @@ class Resolver(BaseResolver):
 
         if self.wheel_cache is None or self.preparer.require_hashes:
             return
+
+        assert req.link is not None, "_find_requirement_link unexpectedly returned None"
         cache_entry = self.wheel_cache.get_cache_entry(
             link=req.link,
             package_name=req.name,
@@ -532,6 +532,7 @@ class Resolver(BaseResolver):
         with indent_log():
             # We add req_to_install before its dependencies, so that we
             # can refer to it when adding dependencies.
+            assert req_to_install.name is not None
             if not requirement_set.has_requirement(req_to_install.name):
                 # 'unnamed' requirements will get added here
                 # 'unnamed' requirements can only come from being directly
diff --git a/src/pip/_internal/resolution/resolvelib/candidates.py b/src/pip/_internal/resolution/resolvelib/candidates.py
index 49a1f660c..9d15b8fda 100644
--- a/src/pip/_internal/resolution/resolvelib/candidates.py
+++ b/src/pip/_internal/resolution/resolvelib/candidates.py
@@ -352,13 +352,13 @@ class AlreadyInstalledCandidate(Candidate):
     def __repr__(self) -> str:
         return f"{self.__class__.__name__}({self.dist!r})"
 
-    def __hash__(self) -> int:
-        return hash((self.__class__, self.name, self.version))
+    def __eq__(self, other: object) -> bool:
+        if not isinstance(other, AlreadyInstalledCandidate):
+            return NotImplemented
+        return self.name == other.name and self.version == other.version
 
-    def __eq__(self, other: Any) -> bool:
-        if isinstance(other, self.__class__):
-            return self.name == other.name and self.version == other.version
-        return False
+    def __hash__(self) -> int:
+        return hash((self.name, self.version))
 
     @property
     def project_name(self) -> NormalizedName:
@@ -519,7 +519,7 @@ class ExtrasCandidate(Candidate):
     def _calculate_valid_requested_extras(self) -> FrozenSet[str]:
         """Get a list of valid extras requested by this candidate.
 
-        The user (or upstream dependant) may have specified extras that the
+        The user (or upstream dependent) may have specified extras that the
         candidate doesn't support. Any unsupported extras are dropped, and each
         cause a warning to be logged here.
         """
diff --git a/src/pip/_internal/resolution/resolvelib/factory.py b/src/pip/_internal/resolution/resolvelib/factory.py
index 781c54fd8..e36df15d4 100644
--- a/src/pip/_internal/resolution/resolvelib/factory.py
+++ b/src/pip/_internal/resolution/resolvelib/factory.py
@@ -3,6 +3,7 @@ import functools
 import logging
 from typing import (
     TYPE_CHECKING,
+    Callable,
     Dict,
     FrozenSet,
     Iterable,
@@ -391,6 +392,7 @@ class Factory:
         incompatibilities: Mapping[str, Iterator[Candidate]],
         constraint: Constraint,
         prefers_installed: bool,
+        is_satisfied_by: Callable[[Requirement, Candidate], bool],
     ) -> Iterable[Candidate]:
         # Collect basic lookup information from the requirements.
         explicit_candidates: Set[Candidate] = set()
@@ -456,7 +458,7 @@ class Factory:
             for c in explicit_candidates
             if id(c) not in incompat_ids
             and constraint.is_satisfied_by(c)
-            and all(req.is_satisfied_by(c) for req in requirements[identifier])
+            and all(is_satisfied_by(req, c) for req in requirements[identifier])
         )
 
     def _make_requirements_from_install_req(
diff --git a/src/pip/_internal/resolution/resolvelib/provider.py b/src/pip/_internal/resolution/resolvelib/provider.py
index 315fb9c89..fb0dd85f1 100644
--- a/src/pip/_internal/resolution/resolvelib/provider.py
+++ b/src/pip/_internal/resolution/resolvelib/provider.py
@@ -1,5 +1,6 @@
 import collections
 import math
+from functools import lru_cache
 from typing import (
     TYPE_CHECKING,
     Dict,
@@ -234,8 +235,10 @@ class PipProvider(_ProviderBase):
             constraint=constraint,
             prefers_installed=(not _eligible_for_upgrade(identifier)),
             incompatibilities=incompatibilities,
+            is_satisfied_by=self.is_satisfied_by,
         )
 
+    @lru_cache(maxsize=None)
     def is_satisfied_by(self, requirement: Requirement, candidate: Candidate) -> bool:
         return requirement.is_satisfied_by(candidate)
 
diff --git a/src/pip/_internal/resolution/resolvelib/requirements.py b/src/pip/_internal/resolution/resolvelib/requirements.py
index 4af4a9f25..f980a356f 100644
--- a/src/pip/_internal/resolution/resolvelib/requirements.py
+++ b/src/pip/_internal/resolution/resolvelib/requirements.py
@@ -1,3 +1,5 @@
+from typing import Any
+
 from pip._vendor.packaging.specifiers import SpecifierSet
 from pip._vendor.packaging.utils import NormalizedName, canonicalize_name
 
@@ -17,6 +19,14 @@ class ExplicitRequirement(Requirement):
     def __repr__(self) -> str:
         return f"{self.__class__.__name__}({self.candidate!r})"
 
+    def __hash__(self) -> int:
+        return hash(self.candidate)
+
+    def __eq__(self, other: Any) -> bool:
+        if not isinstance(other, ExplicitRequirement):
+            return False
+        return self.candidate == other.candidate
+
     @property
     def project_name(self) -> NormalizedName:
         # No need to canonicalize - the candidate did this
@@ -49,6 +59,14 @@ class SpecifierRequirement(Requirement):
     def __repr__(self) -> str:
         return f"{self.__class__.__name__}({str(self._ireq.req)!r})"
 
+    def __eq__(self, other: object) -> bool:
+        if not isinstance(other, SpecifierRequirement):
+            return NotImplemented
+        return str(self._ireq) == str(other._ireq)
+
+    def __hash__(self) -> int:
+        return hash(str(self._ireq))
+
     @property
     def project_name(self) -> NormalizedName:
         assert self._ireq.req, "Specifier-backed ireq is always PEP 508"
@@ -98,12 +116,21 @@ class SpecifierWithoutExtrasRequirement(SpecifierRequirement):
         self._ireq = install_req_drop_extras(ireq)
         self._extras = frozenset(canonicalize_name(e) for e in self._ireq.extras)
 
+    def __eq__(self, other: object) -> bool:
+        if not isinstance(other, SpecifierWithoutExtrasRequirement):
+            return NotImplemented
+        return str(self._ireq) == str(other._ireq)
+
+    def __hash__(self) -> int:
+        return hash(str(self._ireq))
+
 
 class RequiresPythonRequirement(Requirement):
     """A requirement representing Requires-Python metadata."""
 
     def __init__(self, specifier: SpecifierSet, match: Candidate) -> None:
         self.specifier = specifier
+        self._specifier_string = str(specifier)  # for faster __eq__
         self._candidate = match
 
     def __str__(self) -> str:
@@ -112,6 +139,17 @@ class RequiresPythonRequirement(Requirement):
     def __repr__(self) -> str:
         return f"{self.__class__.__name__}({str(self.specifier)!r})"
 
+    def __hash__(self) -> int:
+        return hash((self._specifier_string, self._candidate))
+
+    def __eq__(self, other: Any) -> bool:
+        if not isinstance(other, RequiresPythonRequirement):
+            return False
+        return (
+            self._specifier_string == other._specifier_string
+            and self._candidate == other._candidate
+        )
+
     @property
     def project_name(self) -> NormalizedName:
         return self._candidate.project_name
@@ -148,6 +186,14 @@ class UnsatisfiableRequirement(Requirement):
     def __repr__(self) -> str:
         return f"{self.__class__.__name__}({str(self._name)!r})"
 
+    def __eq__(self, other: object) -> bool:
+        if not isinstance(other, UnsatisfiableRequirement):
+            return NotImplemented
+        return self._name == other._name
+
+    def __hash__(self) -> int:
+        return hash(self._name)
+
     @property
     def project_name(self) -> NormalizedName:
         return self._name
diff --git a/src/pip/_internal/utils/_jaraco_text.py b/src/pip/_internal/utils/_jaraco_text.py
index e06947c05..6ccf53b7a 100644
--- a/src/pip/_internal/utils/_jaraco_text.py
+++ b/src/pip/_internal/utils/_jaraco_text.py
@@ -88,7 +88,7 @@ def join_continuation(lines):
     ['foobarbaz']
 
     Not sure why, but...
-    The character preceeding the backslash is also elided.
+    The character preceding the backslash is also elided.
 
     >>> list(join_continuation(['goo\\', 'dly']))
     ['godly']
diff --git a/tools/codespell-ignore.txt b/tools/codespell-ignore.txt
new file mode 100644
index 000000000..288f59735
--- /dev/null
+++ b/tools/codespell-ignore.txt
@@ -0,0 +1,9 @@
+# An actual English word
+lousily
+followings
+# A contributor first name
+wil
+# Codebase variable or class names
+uptodate
+afile
+failer


Test patch:
diff --git a/tests/conftest.py b/tests/conftest.py
index 4ede5c70f..35101cef2 100644
--- a/tests/conftest.py
+++ b/tests/conftest.py
@@ -495,7 +495,7 @@ def virtualenv_template(
         dist_info, venv.site / dist_info.name, dirs_exist_ok=True, symlinks=True
     )
     # Create placeholder ``easy-install.pth``, as several tests depend on its
-    # existance.  TODO: Ensure ``tests.lib.TestPipResult.files_updated`` correctly
+    # existence.  TODO: Ensure ``tests.lib.TestPipResult.files_updated`` correctly
     # detects changed files.
     venv.site.joinpath("easy-install.pth").touch()
 
diff --git a/tests/functional/test_cli.py b/tests/functional/test_cli.py
index de3b1259f..3c1661521 100644
--- a/tests/functional/test_cli.py
+++ b/tests/functional/test_cli.py
@@ -69,7 +69,7 @@ def test_no_network_imports(command: str, tmp_path: Path) -> None:
     This helps to reduce the startup time of these commands.
 
     Note: This won't catch lazy network imports, but it'll catch top-level
-    network imports which were accidently added (which is the most likely way
+    network imports which were accidentally added (which is the most likely way
     to regress anyway).
     """
     file = tmp_path / f"imported_modules_for_{command}.txt"
diff --git a/tests/functional/test_install.py b/tests/functional/test_install.py
index c013f2eaf..b65212f92 100644
--- a/tests/functional/test_install.py
+++ b/tests/functional/test_install.py
@@ -1731,7 +1731,7 @@ def test_install_builds_wheels(script: PipTestEnvironment, data: TestData) -> No
     assert "Building wheel for wheelb" in str(res), str(res)
     assert "Failed to build wheelbroken" in str(res), str(res)
     # Wheels are built for local directories, but not cached.
-    assert "Building wheel for requir" in str(res), str(res)
+    assert "Building wheel for require" in str(res), str(res)
     # into the cache
     assert wheels != [], str(res)
     assert wheels == [
@@ -1754,7 +1754,7 @@ def test_install_no_binary_builds_wheels(
     )
     # Wheels are built for all requirements
     assert "Building wheel for wheelb" in str(res), str(res)
-    assert "Building wheel for requir" in str(res), str(res)
+    assert "Building wheel for require" in str(res), str(res)
     assert "Building wheel for upper" in str(res), str(res)
     # Wheelbroken failed to build
     assert "Failed to build wheelbroken" in str(res), str(res)
diff --git a/tests/functional/test_new_resolver_user.py b/tests/functional/test_new_resolver_user.py
index 4cd063113..1660924f2 100644
--- a/tests/functional/test_new_resolver_user.py
+++ b/tests/functional/test_new_resolver_user.py
@@ -27,7 +27,7 @@ def test_new_resolver_install_user_satisfied_by_global_site(
     script: PipTestEnvironment,
 ) -> None:
     """
-    An install a matching version to user site should re-use a global site
+    An install a matching version to user site should reuse a global site
     installation if it satisfies.
     """
     create_basic_wheel_for_package(script, "base", "1.0.0")
diff --git a/tests/unit/resolution_resolvelib/test_requirement.py b/tests/unit/resolution_resolvelib/test_requirement.py
index b8cd13cb5..b7b0395b0 100644
--- a/tests/unit/resolution_resolvelib/test_requirement.py
+++ b/tests/unit/resolution_resolvelib/test_requirement.py
@@ -23,6 +23,14 @@ from tests.lib import TestData
 #   Editables
 
 
+def _is_satisfied_by(requirement: Requirement, candidate: Candidate) -> bool:
+    """A helper function to check if a requirement is satisfied by a candidate.
+
+    Used for mocking PipProvider.is_satified_by.
+    """
+    return requirement.is_satisfied_by(candidate)
+
+
 @pytest.fixture
 def test_cases(data: TestData) -> Iterator[List[Tuple[str, str, int]]]:
     def _data_file(name: str) -> Path:
@@ -80,6 +88,7 @@ def test_new_resolver_correct_number_of_matches(
             {},
             Constraint.empty(),
             prefers_installed=False,
+            is_satisfied_by=_is_satisfied_by,
         )
         assert sum(1 for _ in matches) == match_count
 
@@ -98,6 +107,7 @@ def test_new_resolver_candidates_match_requirement(
             {},
             Constraint.empty(),
             prefers_installed=False,
+            is_satisfied_by=_is_satisfied_by,
         )
         for c in candidates:
             assert isinstance(c, Candidate)
diff --git a/tests/unit/test_collector.py b/tests/unit/test_collector.py
index b786454e0..2aaedeedf 100644
--- a/tests/unit/test_collector.py
+++ b/tests/unit/test_collector.py
@@ -911,7 +911,7 @@ def test_collect_sources__non_existing_path() -> None:
             index_url="ignored-by-no-index",
             extra_index_urls=[],
             no_index=True,
-            find_links=[os.path.join("this", "doesnt", "exist")],
+            find_links=[os.path.join("this", "does", "not", "exist")],
         ),
     )
     sources = collector.collect_sources(
diff --git a/tests/unit/test_commands.py b/tests/unit/test_commands.py
index 7a5c4e831..4f6366513 100644
--- a/tests/unit/test_commands.py
+++ b/tests/unit/test_commands.py
@@ -1,3 +1,4 @@
+import os
 from typing import Callable, List
 from unittest import mock
 
@@ -104,6 +105,10 @@ def test_index_group_handle_pip_version_check(
     options.disable_pip_version_check = disable_pip_version_check
     options.no_index = no_index
 
+    # See test test_list_pip_version_check() below.
+    if command_name == "list":
+        expected_called = False
+
     command.handle_pip_version_check(options)
     if expected_called:
         mock_version_check.assert_called_once()
@@ -120,3 +125,20 @@ def test_requirement_commands() -> None:
         return isinstance(command, RequirementCommand)
 
     check_commands(is_requirement_command, ["download", "install", "wheel"])
+
+
+@pytest.mark.parametrize("flag", ["", "--outdated", "--uptodate"])
+@mock.patch("pip._internal.cli.req_command.pip_self_version_check")
+@mock.patch.dict(os.environ, {"PIP_DISABLE_PIP_VERSION_CHECK": "no"})
+def test_list_pip_version_check(version_check_mock: mock.Mock, flag: str) -> None:
+    """
+    Ensure that pip list doesn't perform a version self-check unless given
+    --outdated or --uptodate (as they require hitting the network anyway).
+    """
+    command = create_command("list")
+    command.run = lambda *args, **kwargs: 0  # type: ignore[method-assign]
+    command.main([flag])
+    if flag != "":
+        version_check_mock.assert_called_once()
+    else:
+        version_check_mock.assert_not_called()