Repository: pex-tool/pex

Issue:
Pex fails to find deadsnakes Python 3.10 on Ubuntu 18.04
With this image:
```Dockerfile
FROM ubuntu:18.04

RUN apt-get update && \
    apt-get install -y software-properties-common && \
    add-apt-repository -y ppa:deadsnakes/ppa

RUN apt-get update && \
    apt-get install -y \
    curl \
    python \
    python-dev \
    python3.8 \
    python3.8-dev \
    python3.8-venv \
    python3.8-distutils \
    python3.9 \
    python3.9-dev \
    python3.9-venv \
    python3.9-distutils \
    python3.10 \
    python3.10-dev \
    python3.10-venv \
    python3.10-distutils
```
I find:
```console
$ docker build -t bionic:deadsnakes-310 .
Sending build context to Docker daemon  2.048kB
Step 1/3 : FROM ubuntu:18.04
 ---> 5a214d77f5d7
Step 2/3 : RUN apt-get update &&     apt-get install -y software-properties-common &&     add-apt-repository -y ppa:deadsnakes/ppa
 ---> Using cache
 ---> b1b5a578052e
Step 3/3 : RUN apt-get update &&     apt-get install -y     curl     python     python-dev     python3.8     python3.8-dev     python3.8-venv     python3.8-distutils     python3.9     python3.9-dev     python3.9-venv     python3.9-distutils     python3.10     python3.10-dev     python3.10-venv     python3.10-distutils
 ---> Using cache
 ---> 29464760100f
Successfully built 29464760100f
Successfully tagged bionic:deadsnakes-310
$ docker run --rm -it bionic:deadsnakes-310 bash -i
root@f4f52262c693:/# curl -sSL https://github.com/pantsbuild/pex/releases/download/v2.1.56/pex > pex-2.1.56 
root@f4f52262c693:/# chmod +x pex-2.1.56 
root@f4f52262c693:/# ./pex-2.1.56 --interpreter-constraint "==3.11.*"         
Could not find a compatible interpreter.

Examined the following interpreters:
1.) /usr/bin/python2.7 CPython==2.7.17
2.) /usr/bin/python3.6 CPython==3.6.9
3.) /usr/bin/python3.8 CPython==3.8.12
4.) /usr/bin/python3.9 CPython==3.9.9

No interpreter compatible with the requested constraints was found:
  Version matches ==3.11.*
root@f4f52262c693:/# 
```

But python3.10 is in fact on the `PATH` with good perms:
```
/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
root@f4f52262c693:/# which python3.10
/usr/bin/python3.10
root@f4f52262c693:/# ls -l /usr/bin/python*
lrwxrwxrwx 1 root root       9 Apr 16  2018 /usr/bin/python -> python2.7
lrwxrwxrwx 1 root root      16 Apr 16  2018 /usr/bin/python-config -> python2.7-config
lrwxrwxrwx 1 root root       9 Apr 16  2018 /usr/bin/python2 -> python2.7
lrwxrwxrwx 1 root root      16 Apr 16  2018 /usr/bin/python2-config -> python2.7-config
-rwxr-xr-x 1 root root 3633000 Feb 27  2021 /usr/bin/python2.7
lrwxrwxrwx 1 root root      33 Feb 27  2021 /usr/bin/python2.7-config -> x86_64-linux-gnu-python2.7-config
lrwxrwxrwx 1 root root       9 Oct 25  2018 /usr/bin/python3 -> python3.6
-rwxr-xr-x 1 root root 5441528 Dec 18 21:38 /usr/bin/python3.10
lrwxrwxrwx 1 root root      34 Dec 18 21:38 /usr/bin/python3.10-config -> x86_64-linux-gnu-python3.10-config
-rwxr-xr-x 2 root root 4526456 Dec  8 21:08 /usr/bin/python3.6
-rwxr-xr-x 2 root root 4526456 Dec  8 21:08 /usr/bin/python3.6m
-rwxr-xr-x 1 root root 5134032 Sep 10 00:16 /usr/bin/python3.8
lrwxrwxrwx 1 root root      33 Sep 10 00:16 /usr/bin/python3.8-config -> x86_64-linux-gnu-python3.8-config
-rwxr-xr-x 1 root root 5385072 Nov 16 03:05 /usr/bin/python3.9
lrwxrwxrwx 1 root root      33 Nov 16 03:05 /usr/bin/python3.9-config -> x86_64-linux-gnu-python3.9-config
lrwxrwxrwx 1 root root      10 Oct 25  2018 /usr/bin/python3m -> python3.6m
root@f4f52262c693:/# python3.10
Python 3.10.1 (main, Dec 18 2021, 21:38:47) [GCC 7.5.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> 
```


Discussion:
Facepalm:
https://github.com/pantsbuild/pex/blob/8b67835e9ced3d79432254f3cd60fc2532630e6a/pex/interpreter.py#L358-L373

Resolving patch:
diff --git a/pex/interpreter.py b/pex/interpreter.py
--- a/pex/interpreter.py
+++ b/pex/interpreter.py
@@ -370,7 +370,7 @@ class PythonInterpreter(object):
                 [2-9]
                 (?:.
                     # Minor version
-                    [0-9]
+                    [0-9]+
                     # Some distributions include a suffix on the interpreter name, similar to
                     # PEP-3149. For example, Gentoo has /usr/bin/python3.6m to indicate it was
                     # built with pymalloc


Test patch:
diff --git a/pex/testing.py b/pex/testing.py
--- a/pex/testing.py
+++ b/pex/testing.py
@@ -445,10 +445,10 @@ def bootstrap_python_installer(dest):
 # minutes for a shard.
 PY27 = "2.7.18"
 PY37 = "3.7.11"
-PY38 = "3.8.11"
+PY310 = "3.10.1"
 
-_ALL_PY_VERSIONS = (PY27, PY37, PY38)
-_ALL_PY3_VERSIONS = (PY37, PY38)
+_ALL_PY_VERSIONS = (PY27, PY37, PY310)
+_ALL_PY3_VERSIONS = (PY37, PY310)
 
 
 def ensure_python_distribution(version):
@@ -502,7 +502,10 @@ def ensure_python_distribution(version):
             subprocess.check_call([pyenv, "install", "--keep", version], env=env)
             subprocess.check_call([pip, "install", "-U", "pip"])
 
-    python = os.path.join(interpreter_location, "bin", "python" + version[0:3])
+    major, minor = version.split(".")[:2]
+    python = os.path.join(
+        interpreter_location, "bin", "python{major}.{minor}".format(major=major, minor=minor)
+    )
 
     def run_pyenv(args):
         # type: (Iterable[str]) -> Text
diff --git a/tests/cli/commands/lockfile/test_lockfile.py b/tests/cli/commands/lockfile/test_lockfile.py
--- a/tests/cli/commands/lockfile/test_lockfile.py
+++ b/tests/cli/commands/lockfile/test_lockfile.py
@@ -14,7 +14,7 @@
     IS_MAC,
     PY27,
     PY37,
-    PY38,
+    PY310,
     IntegResults,
     ensure_python_interpreter,
     run_pex_command,
@@ -93,8 +93,8 @@ def py37():
 
 
 @pytest.fixture
-def py38():
-    return create_target(PY38)
+def py310():
+    return create_target(PY310)
 
 
 def test_select_same_target(py27):
@@ -106,13 +106,13 @@ def test_select_same_target(py27):
 
 def test_select_universal_compatible_targets(
     py37,  # type: DistributionTarget
-    py38,  # type: DistributionTarget
+    py310,  # type: DistributionTarget
 ):
     # type: (...) -> None
     assert [
         (py37, SINGLE_PLATFORM_UNIVERSAL_WHEEL.locked_resolves[0]),
-        (py38, SINGLE_PLATFORM_UNIVERSAL_WHEEL.locked_resolves[0]),
-    ] == list(SINGLE_PLATFORM_UNIVERSAL_WHEEL.select([py37, py38]))
+        (py310, SINGLE_PLATFORM_UNIVERSAL_WHEEL.locked_resolves[0]),
+    ] == list(SINGLE_PLATFORM_UNIVERSAL_WHEEL.select([py37, py310]))
 
 
 DUAL_PLATFORM_NATIVE_WHEEL = lockfile.loads(
@@ -189,12 +189,12 @@ def test_select_incompatible_target(py27):
 
 def test_select_compatible_targets(
     py37,  # type: DistributionTarget
-    py38,  # type: DistributionTarget
+    py310,  # type: DistributionTarget
 ):
     # type: (...) -> None
     expected_index = 0 if IS_MAC else 1
     assert [(py37, DUAL_PLATFORM_NATIVE_WHEEL.locked_resolves[expected_index])] == list(
-        DUAL_PLATFORM_NATIVE_WHEEL.select([py37, py38])
+        DUAL_PLATFORM_NATIVE_WHEEL.select([py37, py310])
     )
 
 
diff --git a/tests/integration/cli/commands/test_lock.py b/tests/integration/cli/commands/test_lock.py
--- a/tests/integration/cli/commands/test_lock.py
+++ b/tests/integration/cli/commands/test_lock.py
@@ -17,7 +17,7 @@
 from pex.resolve.resolver_configuration import ResolverVersion
 from pex.resolve.testing import normalize_locked_resolve
 from pex.sorted_tuple import SortedTuple
-from pex.testing import PY38, IntegResults, ensure_python_interpreter, make_env
+from pex.testing import PY310, IntegResults, ensure_python_interpreter, make_env
 from pex.third_party.pkg_resources import Requirement
 from pex.typing import TYPE_CHECKING
 
@@ -234,29 +234,29 @@ def run_lock_update(
     return run_pex3("lock", "update", *args, **env)
 
 
-def ensure_py38():
+def ensure_py310():
     # type: () -> str
-    return ensure_python_interpreter(PY38)
+    return ensure_python_interpreter(PY310)
 
 
 @pytest.fixture
-def py38():
+def py310():
     # type: () -> str
-    return ensure_py38()
+    return ensure_py310()
 
 
-def run_lock_update_for_py38(
+def run_lock_update_for_py310(
     *args,  # type: str
     **env  # type: Optional[str]
 ):
     # type: (...) -> IntegResults
-    py38 = ensure_py38()
+    py38 = ensure_py310()
     return run_lock_update("--python", py38, *args, **env)
 
 
 def test_update_noop(lock_file_path):
     # type: (str) -> None
-    result = run_lock_update_for_py38("-p", "urllib3==1.25.11", lock_file_path)
+    result = run_lock_update_for_py310("-p", "urllib3==1.25.11", lock_file_path)
     result.assert_success()
     assert not result.output
     assert (
@@ -268,7 +268,7 @@ def test_update_noop(lock_file_path):
 
 def test_update_noop_dry_run(lock_file_path):
     # type: (str) -> None
-    result = run_lock_update_for_py38("-n", "-p", "urllib3==1.25.11", lock_file_path)
+    result = run_lock_update_for_py310("-n", "-p", "urllib3==1.25.11", lock_file_path)
     result.assert_success()
     assert (
         "There would be no updates for urllib3 in lock generated by "
@@ -279,7 +279,7 @@ def test_update_noop_dry_run(lock_file_path):
 
 def test_update_targeted_upgrade_miss(lock_file_path):
     # type: (str) -> None
-    result = run_lock_update_for_py38("-p", "not_in_lock==1.0", lock_file_path)
+    result = run_lock_update_for_py310("-p", "not_in_lock==1.0", lock_file_path)
     result.assert_failure()
     assert not result.output
     assert (
@@ -292,7 +292,7 @@ def test_update_targeted_upgrade_miss(lock_file_path):
 def test_update_targeted_upgrade(lock_file_path):
     # type: (str) -> None
     assert SortedTuple() == lockfile.load(lock_file_path).constraints
-    result = run_lock_update_for_py38("-p", "urllib3<1.26.7", lock_file_path)
+    result = run_lock_update_for_py310("-p", "urllib3<1.26.7", lock_file_path)
     result.assert_success()
     assert not result.output
     assert (
@@ -319,7 +319,7 @@ def test_update_targeted_upgrade(lock_file_path):
 
 def test_update_targeted_upgrade_dry_run(lock_file_path):
     # type: (str) -> None
-    result = run_lock_update_for_py38("-n", "-p", "urllib3<1.26.7", lock_file_path)
+    result = run_lock_update_for_py310("-n", "-p", "urllib3<1.26.7", lock_file_path)
     result.assert_success()
     assert (
         "Would update urllib3 from 1.25.11 to 1.26.6 in lock generated by "
@@ -333,7 +333,7 @@ def test_update_targeted_upgrade_dry_run(lock_file_path):
 
 def test_update_targeted_downgrade(lock_file_path):
     # type: (str) -> None
-    result = run_lock_update_for_py38("-p", "urllib3<1.25", lock_file_path)
+    result = run_lock_update_for_py310("-p", "urllib3<1.25", lock_file_path)
     result.assert_success()
     assert not result.output
     assert (
@@ -349,7 +349,7 @@ def test_update_targeted_closure_shrink(lock_file_path):
     # A targeted downgrade of requests, then, should remove newer requests dependencies from the
     # lock.
 
-    result = run_lock_update_for_py38("-p", "requests==2.0.0", lock_file_path)
+    result = run_lock_update_for_py310("-p", "requests==2.0.0", lock_file_path)
     result.assert_success()
     lock_file = lockfile.load(lock_file_path)
     assert 1 == len(lock_file.locked_resolves)
@@ -374,10 +374,10 @@ def test_update_targeted_closure_shrink(lock_file_path):
 def test_update_targeted_impossible(
     lock_file_path,  # type: str
     tmpdir,  # type: Any
-    py38,
+    py310,
 ):
     # type: (...) -> None
-    result = run_lock_update_for_py38("-p", "urllib3<1.16", lock_file_path)
+    result = run_lock_update_for_py310("-p", "urllib3<1.16", lock_file_path)
     result.assert_failure()
     assert not result.output
 
@@ -397,7 +397,7 @@ def test_update_targeted_impossible(
     assert re.match(
         r"^1\.\) {platform}: pid [\d]+ -> ".format(
             platform=DistributionTarget.for_interpreter(
-                PythonInterpreter.from_binary(py38)
+                PythonInterpreter.from_binary(py310)
             ).get_supported_tags()[0]
         ),
         error_lines[9],
@@ -409,7 +409,7 @@ def test_update_targeted_impossible(
         attr.evolve(UPDATE_LOCKFILE, resolver_version=ResolverVersion.PIP_LEGACY),
         updated_lock_file_path,
     )
-    result = run_lock_update_for_py38("-p", "urllib3<1.16", updated_lock_file_path)
+    result = run_lock_update_for_py310("-p", "urllib3<1.16", updated_lock_file_path)
     result.assert_success()
     assert not result.output
     assert (
diff --git a/tests/integration/conftest.py b/tests/integration/conftest.py
--- a/tests/integration/conftest.py
+++ b/tests/integration/conftest.py
@@ -11,7 +11,7 @@
 import pytest
 
 from pex.common import atomic_directory, temporary_dir
-from pex.testing import PY38, ensure_python_venv, make_env, run_pex_command
+from pex.testing import PY310, ensure_python_venv, make_env, run_pex_command
 from pex.typing import TYPE_CHECKING
 
 if TYPE_CHECKING:
@@ -78,7 +78,7 @@ def tmp_workdir():
 @pytest.fixture(scope="module")
 def mitmdump():
     # type: () -> Tuple[str, str]
-    python, pip = ensure_python_venv(PY38)
+    python, pip = ensure_python_venv(PY310)
     subprocess.check_call([pip, "install", "mitmproxy==5.3.0"])
     mitmdump = os.path.join(os.path.dirname(python), "mitmdump")
     return mitmdump, os.path.expanduser("~/.mitmproxy/mitmproxy-ca-cert.pem")
diff --git a/tests/integration/test_integration.py b/tests/integration/test_integration.py
--- a/tests/integration/test_integration.py
+++ b/tests/integration/test_integration.py
@@ -30,7 +30,7 @@
     NOT_CPYTHON27_OR_OSX,
     PY27,
     PY37,
-    PY38,
+    PY310,
     IntegResults,
     WheelBuilder,
     built_wheel,
@@ -111,10 +111,10 @@ def test_pex_root_build():
 def test_pex_root_run():
     # type: () -> None
     python37 = ensure_python_interpreter(PY37)
-    python38 = ensure_python_interpreter(PY38)
+    python310 = ensure_python_interpreter(PY310)
 
     with temporary_dir() as td, temporary_dir() as runtime_pex_root, temporary_dir() as home:
-        pex_env = make_env(HOME=home, PEX_PYTHON_PATH=os.pathsep.join((python37, python38)))
+        pex_env = make_env(HOME=home, PEX_PYTHON_PATH=os.pathsep.join((python37, python310)))
 
         buildtime_pex_root = os.path.join(td, "buildtime_pex_root")
         output_dir = os.path.join(td, "output_dir")
@@ -131,7 +131,7 @@ def test_pex_root_run():
             "--runtime-pex-root={}".format(runtime_pex_root),
             "--interpreter-constraint=CPython=={version}".format(version=PY37),
         ]
-        results = run_pex_command(args=args, env=pex_env, python=python38)
+        results = run_pex_command(args=args, env=pex_env, python=python310)
         results.assert_success()
         assert ["pex.pex"] == os.listdir(output_dir), "Expected built pex file."
         assert [] == os.listdir(home), "Expected empty home dir."
@@ -144,7 +144,7 @@ def test_pex_root_run():
             runtime_pex_root
         ), "Expected runtime pex root to be empty prior to any runs."
 
-        subprocess.check_call(args=[python38, pex_pex, "--version"], env=pex_env)
+        subprocess.check_call(args=[python310, pex_pex, "--version"], env=pex_env)
         assert_interpreters(label="runtime", pex_root=runtime_pex_root)
         assert_installed_wheels(label="runtime", pex_root=runtime_pex_root)
         assert [] == os.listdir(
@@ -695,11 +695,11 @@ def test_pex_source_bundling_pep420():
                 )
 
             pex_path = os.path.join(output_dir, "pex1.pex")
-            py38 = ensure_python_interpreter(PY38)
-            res = run_pex_command(["-o", pex_path, "-D", input_dir, "-e", "exe"], python=py38)
+            py310 = ensure_python_interpreter(PY310)
+            res = run_pex_command(["-o", pex_path, "-D", input_dir, "-e", "exe"], python=py310)
             res.assert_success()
 
-            stdout, rc = run_simple_pex(pex_path, interpreter=PythonInterpreter.from_binary(py38))
+            stdout, rc = run_simple_pex(pex_path, interpreter=PythonInterpreter.from_binary(py310))
 
             assert rc == 0
             assert stdout == b"hello\n"
@@ -928,12 +928,12 @@ def test_setup_python_path():
         )
         results.assert_success()
 
-        py38_interpreter = PythonInterpreter.from_binary(ensure_python_interpreter(PY38))
+        py310_interpreter = PythonInterpreter.from_binary(ensure_python_interpreter(PY310))
 
         py27_env = make_env(PEX_IGNORE_RCFILES="1", PATH=py27_interpreter_dir)
         stdout, rc = run_simple_pex(
             pex,
-            interpreter=py38_interpreter,
+            interpreter=py310_interpreter,
             env=py27_env,
             stdin=b"import more_itertools, sys; print(sys.version_info[:2])",
         )
@@ -943,7 +943,7 @@ def test_setup_python_path():
         py37_env = make_env(PEX_IGNORE_RCFILES="1", PATH=py37_interpreter_dir)
         stdout, rc = run_simple_pex(
             pex,
-            interpreter=py38_interpreter,
+            interpreter=py310_interpreter,
             env=py37_env,
             stdin=b"import more_itertools, sys; print(sys.version_info[:2])",
         )
@@ -954,7 +954,7 @@ def test_setup_python_path():
 def test_setup_python_multiple_transitive_markers():
     # type: () -> None
     py27_interpreter = ensure_python_interpreter(PY27)
-    py38_interpreter = ensure_python_interpreter(PY38)
+    py310_interpreter = ensure_python_interpreter(PY310)
     with temporary_dir() as out:
         pex = os.path.join(out, "pex.pex")
         results = run_pex_command(
@@ -963,7 +963,7 @@ def test_setup_python_multiple_transitive_markers():
                 "--disable-cache",
                 "--python-shebang=#!/usr/bin/env python",
                 "--python={}".format(py27_interpreter),
-                "--python={}".format(py38_interpreter),
+                "--python={}".format(py310_interpreter),
                 "-o",
                 pex,
             ]
@@ -982,18 +982,18 @@ def test_setup_python_multiple_transitive_markers():
         stdout = subprocess.check_output(both_program, env=py27_env)
         assert to_bytes(os.path.realpath(py27_interpreter)) == stdout.strip()
 
-        py38_env = make_env(PATH=os.path.dirname(py38_interpreter))
+        py38_env = make_env(PATH=os.path.dirname(py310_interpreter))
         with pytest.raises(subprocess.CalledProcessError) as err:
             subprocess.check_output(py2_only_program, stderr=subprocess.STDOUT, env=py38_env)
         assert b"ModuleNotFoundError: No module named 'functools32'" in err.value.output
 
         stdout = subprocess.check_output(both_program, env=py38_env)
-        assert to_bytes(os.path.realpath(py38_interpreter)) == stdout.strip()
+        assert to_bytes(os.path.realpath(py310_interpreter)) == stdout.strip()
 
 
 def test_setup_python_direct_markers():
     # type: () -> None
-    py38_interpreter = ensure_python_interpreter(PY38)
+    py310_interpreter = ensure_python_interpreter(PY310)
     with temporary_dir() as out:
         pex = os.path.join(out, "pex.pex")
         results = run_pex_command(
@@ -1001,7 +1001,7 @@ def test_setup_python_direct_markers():
                 'subprocess32==3.2.7; python_version<"3"',
                 "--disable-cache",
                 "--python-shebang=#!/usr/bin/env python",
-                "--python={}".format(py38_interpreter),
+                "--python={}".format(py310_interpreter),
                 "-o",
                 pex,
             ]
@@ -1014,14 +1014,14 @@ def test_setup_python_direct_markers():
             subprocess.check_output(
                 py2_only_program,
                 stderr=subprocess.STDOUT,
-                env=make_env(PATH=os.path.dirname(py38_interpreter)),
+                env=make_env(PATH=os.path.dirname(py310_interpreter)),
             )
         assert b"ModuleNotFoundError: No module named 'subprocess32'" in err.value.output
 
 
 def test_setup_python_multiple_direct_markers():
     # type: () -> None
-    py38_interpreter = ensure_python_interpreter(PY38)
+    py310_interpreter = ensure_python_interpreter(PY310)
     py27_interpreter = ensure_python_interpreter(PY27)
     with temporary_dir() as out:
         pex = os.path.join(out, "pex.pex")
@@ -1030,7 +1030,7 @@ def test_setup_python_multiple_direct_markers():
                 'subprocess32==3.2.7; python_version<"3"',
                 "--disable-cache",
                 "--python-shebang=#!/usr/bin/env python",
-                "--python={}".format(py38_interpreter),
+                "--python={}".format(py310_interpreter),
                 "--python={}".format(py27_interpreter),
                 "-o",
                 pex,
@@ -1044,7 +1044,7 @@ def test_setup_python_multiple_direct_markers():
             subprocess.check_output(
                 py2_only_program,
                 stderr=subprocess.STDOUT,
-                env=make_env(PATH=os.path.dirname(py38_interpreter)),
+                env=make_env(PATH=os.path.dirname(py310_interpreter)),
             )
         assert (
             re.search(b"ModuleNotFoundError: No module named 'subprocess32'", err.value.output)
@@ -1404,7 +1404,7 @@ def test_venv_mode(
     isort_pex_args,  # type: Tuple[str, List[str]]
 ):
     # type: (...) -> None
-    other_interpreter_version = PY38 if sys.version_info[0] == 2 else PY27
+    other_interpreter_version = PY310 if sys.version_info[0] == 2 else PY27
     other_interpreter = ensure_python_interpreter(other_interpreter_version)
 
     pex_file, args = isort_pex_args
@@ -1527,7 +1527,7 @@ def test_pip_issues_9420_workaround():
     # type: () -> None
 
     # N.B.: isort 5.7.0 needs Python >=3.6
-    python = ensure_python_interpreter(PY38)
+    python = ensure_python_interpreter(PY310)
 
     results = run_pex_command(
         args=["--resolver-version", "pip-2020-resolver", "isort[colors]==5.7.0", "colorama==0.4.1"],
@@ -1584,7 +1584,7 @@ def test_constraint_file_from_url(tmpdir):
     # type: (Any) -> None
 
     # N.B.: The fasteners library requires Python >=3.6.
-    python = ensure_python_interpreter(PY38)
+    python = ensure_python_interpreter(PY310)
 
     pex_file = os.path.join(str(tmpdir), "pex")
 
diff --git a/tests/integration/test_interpreter_selection.py b/tests/integration/test_interpreter_selection.py
--- a/tests/integration/test_interpreter_selection.py
+++ b/tests/integration/test_interpreter_selection.py
@@ -10,7 +10,7 @@
 from pex.testing import (
     PY27,
     PY37,
-    PY38,
+    PY310,
     ensure_python_interpreter,
     make_env,
     run_pex_command,
@@ -44,7 +44,7 @@ def test_interpreter_constraints_to_pex_info_py2():
 
 def test_interpreter_constraints_to_pex_info_py3():
     # type: () -> None
-    py3_interpreter = ensure_python_interpreter(PY38)
+    py3_interpreter = ensure_python_interpreter(PY310)
     with temporary_dir() as output_dir:
         # target python 3
         pex_out_path = os.path.join(output_dir, "pex_py3.pex")
@@ -132,7 +132,7 @@ def test_interpreter_constraints_honored_without_ppp_or_pp(tmpdir):
     # type: (Any) -> None
     # Create a pex with interpreter constraints, but for not the default interpreter in the path.
 
-    py38_path = ensure_python_interpreter(PY38)
+    py310_path = ensure_python_interpreter(PY310)
     py37_path = ensure_python_interpreter(PY37)
 
     pex_out_path = os.path.join(str(tmpdir), "pex.pex")
@@ -141,12 +141,12 @@ def test_interpreter_constraints_honored_without_ppp_or_pp(tmpdir):
         PATH=os.pathsep.join(
             [
                 os.path.dirname(py37_path),
-                os.path.dirname(py38_path),
+                os.path.dirname(py310_path),
             ]
         ),
     )
     res = run_pex_command(
-        ["--disable-cache", "--interpreter-constraint===%s" % PY38, "-o", pex_out_path], env=env
+        ["--disable-cache", "--interpreter-constraint===%s" % PY310, "-o", pex_out_path], env=env
     )
     res.assert_success()
 
@@ -160,10 +160,10 @@ def test_interpreter_constraints_honored_without_ppp_or_pp(tmpdir):
     )
     assert rc == 0
 
-    # If the constraints are honored, it will have run python3.8 and not python3.7
+    # If the constraints are honored, it will have run python3.10 and not python3.7
     # Without constraints, we would expect it to use python3.7 as it is the minimum interpreter
     # in the PATH.
-    assert b"3.8\n" == stdout
+    assert b"3.10\n" == stdout
 
 
 def test_interpreter_resolution_pex_python_path_precedence_over_pex_python(tmpdir):
@@ -179,7 +179,7 @@ def test_interpreter_resolution_pex_python_path_precedence_over_pex_python(tmpdi
                 PEX_PYTHON_PATH={ppp}
                 PEX_PYTHON={pp}
                 """.format(
-                    ppp=ppp, pp=ensure_python_interpreter(PY38)
+                    ppp=ppp, pp=ensure_python_interpreter(PY310)
                 )
             )
         )
@@ -247,7 +247,7 @@ def test_pex_exec_with_pex_python_path_only():
         with open(pexrc_path, "w") as pexrc:
             # set pex python path
             pex_python_path = ":".join(
-                [ensure_python_interpreter(PY27), ensure_python_interpreter(PY38)]
+                [ensure_python_interpreter(PY27), ensure_python_interpreter(PY310)]
             )
             pexrc.write("PEX_PYTHON_PATH=%s" % pex_python_path)
 
@@ -274,7 +274,7 @@ def test_pex_exec_with_pex_python_path_and_pex_python_but_no_constraints(tmpdir)
                 PEX_PYTHON_PATH={}
                 PEX_PYTHON=python
                 """.format(
-                    ":".join(os.path.dirname(ensure_python_interpreter(py)) for py in (PY38, PY27))
+                    ":".join(os.path.dirname(ensure_python_interpreter(py)) for py in (PY310, PY27))
                 )
             )
         )
@@ -379,7 +379,7 @@ def test_interpreter_selection_using_os_environ_for_bootstrap_reexec():
         # execute with. The child interpreter is the interpreter we expect the
         # child pex to execute with.
         if sys.version_info[:2] == (3, 8):
-            child_pex_interpreter_version = PY38
+            child_pex_interpreter_version = PY310
         else:
             child_pex_interpreter_version = PY27
 
diff --git a/tests/integration/test_issue_1017.py b/tests/integration/test_issue_1017.py
--- a/tests/integration/test_issue_1017.py
+++ b/tests/integration/test_issue_1017.py
@@ -1,14 +1,14 @@
 # Copyright 2021 Pants project contributors (see CONTRIBUTORS.md).
 # Licensed under the Apache License, Version 2.0 (see LICENSE).
 
-from pex.testing import PY38, ensure_python_interpreter, run_pex_command
+from pex.testing import PY37, ensure_python_interpreter, run_pex_command
 
 
 def test_resolve_python_requires_full_version():
     # type: () -> None
-    python38 = ensure_python_interpreter(PY38)
+    python37 = ensure_python_interpreter(PY37)
     result = run_pex_command(
-        python=python38,
+        python=python37,
         args=[
             "pandas==1.0.5",
             "--",
diff --git a/tests/integration/test_issue_1025.py b/tests/integration/test_issue_1025.py
--- a/tests/integration/test_issue_1025.py
+++ b/tests/integration/test_issue_1025.py
@@ -10,7 +10,7 @@
 from pex.common import temporary_dir
 from pex.executor import Executor
 from pex.interpreter import PythonInterpreter
-from pex.testing import PY38, ensure_python_venv, make_env, run_pex_command, run_simple_pex
+from pex.testing import PY310, ensure_python_venv, make_env, run_pex_command, run_simple_pex
 
 
 @pytest.fixture
@@ -37,7 +37,7 @@ def write_pth(pth_path, sitedir):
 
 
 def test_extras_isolation(create_pth):
-    python, pip = ensure_python_venv(PY38)
+    python, pip = ensure_python_venv(PY310)
     interpreter = PythonInterpreter.from_binary(python)
     _, stdout, _ = interpreter.execute(args=["-c", "import site; print(site.getsitepackages()[0])"])
     with temporary_dir() as tmpdir:
diff --git a/tests/integration/test_issue_1031.py b/tests/integration/test_issue_1031.py
--- a/tests/integration/test_issue_1031.py
+++ b/tests/integration/test_issue_1031.py
@@ -7,7 +7,7 @@
 
 from pex.interpreter import PythonInterpreter
 from pex.orderedset import OrderedSet
-from pex.testing import PY27, PY38, ensure_python_venv, make_env, run_simple_pex_test
+from pex.testing import PY27, PY310, ensure_python_venv, make_env, run_simple_pex_test
 from pex.typing import TYPE_CHECKING
 
 if TYPE_CHECKING:
@@ -18,7 +18,7 @@
     "py_version",
     [
         pytest.param(PY27, id="virtualenv-16.7.10"),
-        pytest.param(PY38, id="pyvenv"),
+        pytest.param(PY310, id="pyvenv"),
     ],
 )
 def test_setuptools_isolation_with_system_site_packages(py_version):
diff --git a/tests/integration/test_issue_1232.py b/tests/integration/test_issue_1232.py
--- a/tests/integration/test_issue_1232.py
+++ b/tests/integration/test_issue_1232.py
@@ -5,7 +5,7 @@
 import shutil
 import subprocess
 
-from pex.testing import PY37, PY38, ensure_python_interpreter, make_env, run_pex_command
+from pex.testing import PY37, PY310, ensure_python_interpreter, make_env, run_pex_command
 from pex.typing import TYPE_CHECKING
 
 if TYPE_CHECKING:
@@ -18,9 +18,9 @@ def test_isolated_pex_zip(tmpdir):
     pex_root = os.path.join(str(tmpdir), "pex_root")
 
     python37 = ensure_python_interpreter(PY37)
-    python38 = ensure_python_interpreter(PY38)
+    python310 = ensure_python_interpreter(PY310)
 
-    pex_env = make_env(PEX_PYTHON_PATH=os.pathsep.join((python37, python38)))
+    pex_env = make_env(PEX_PYTHON_PATH=os.pathsep.join((python37, python310)))
 
     def add_pex_args(*args):
         # type: (*str) -> List[str]
@@ -87,7 +87,7 @@ def vendored_toplevel(isolated_dir):
     modified_pex = os.path.join(str(tmpdir), "modified.pex")
     subprocess.check_call(
         args=add_pex_args(
-            python38, current_pex_pex, modified_pex_src, "-c", "pex", "-o", modified_pex
+            python310, current_pex_pex, modified_pex_src, "-c", "pex", "-o", modified_pex
         ),
         env=pex_env,
     )
@@ -108,7 +108,7 @@ def vendored_toplevel(isolated_dir):
     ansicolors_pex = os.path.join(str(tmpdir), "ansicolors.pex")
     subprocess.check_call(
         args=add_pex_args(
-            python38,
+            python310,
             modified_pex,
             "ansicolors==1.1.8",
             "-o",
@@ -132,7 +132,7 @@ def vendored_toplevel(isolated_dir):
     # ===
     # Force the bootstrap to run interpreter identification which will force a Pex isolation.
     shutil.rmtree(os.path.join(pex_root, "interpreters"))
-    subprocess.check_call(args=[python38, ansicolors_pex, "-c", "import colors"], env=pex_env)
+    subprocess.check_call(args=[python310, ansicolors_pex, "-c", "import colors"], env=pex_env)
     ansicolors_pex_isolated_vendoreds = tally_isolated_vendoreds()
     ansicolors_pex_isolation = set(modified_pex_isolated_vendoreds.keys()) ^ set(
         ansicolors_pex_isolated_vendoreds.keys()
@@ -150,7 +150,7 @@ def vendored_toplevel(isolated_dir):
     ansicolors_pex = os.path.join(str(tmpdir), "ansicolors.old.pex")
     subprocess.check_call(
         args=add_pex_args(
-            python38,
+            python310,
             modified_pex,
             "ansicolors==1.0.2",
             "-o",
@@ -161,7 +161,7 @@ def vendored_toplevel(isolated_dir):
 
     # Force the bootstrap to run interpreter identification which will force a Pex isolation.
     shutil.rmtree(os.path.join(pex_root, "interpreters"))
-    subprocess.check_call(args=[python38, ansicolors_pex, "-c", "import colors"], env=pex_env)
+    subprocess.check_call(args=[python310, ansicolors_pex, "-c", "import colors"], env=pex_env)
     assert (
         ansicolors_pex_isolated_vendoreds == tally_isolated_vendoreds()
     ), "Expecting no new Pex isolations."
diff --git a/tests/integration/test_issue_1336.py b/tests/integration/test_issue_1336.py
--- a/tests/integration/test_issue_1336.py
+++ b/tests/integration/test_issue_1336.py
@@ -4,7 +4,7 @@
 import os
 import subprocess
 
-from pex.testing import PY38, ensure_python_interpreter, run_pex_command
+from pex.testing import PY310, ensure_python_interpreter, run_pex_command
 from pex.typing import TYPE_CHECKING
 
 if TYPE_CHECKING:
@@ -13,7 +13,7 @@
 
 def test_pip_leak(tmpdir):
     # type: (Any) -> None
-    python = ensure_python_interpreter(PY38)
+    python = ensure_python_interpreter(PY310)
     pip = os.path.join(os.path.dirname(python), "pip")
     subprocess.check_call(args=[pip, "install", "setuptools_scm==6.0.1"])
     run_pex_command(args=["--python", python, "bitstring==3.1.7"], python=python).assert_success()
diff --git a/tests/integration/test_issue_1422.py b/tests/integration/test_issue_1422.py
--- a/tests/integration/test_issue_1422.py
+++ b/tests/integration/test_issue_1422.py
@@ -6,7 +6,7 @@
 import subprocess
 import sys
 
-from pex.testing import PY27, PY37, PY38, ensure_python_interpreter, make_env, run_pex_command
+from pex.testing import PY27, PY37, PY310, ensure_python_interpreter, make_env, run_pex_command
 from pex.typing import TYPE_CHECKING
 
 if TYPE_CHECKING:
@@ -58,17 +58,17 @@ def assert_uses_python(
 
     py27 = ensure_python_interpreter(PY27)
     py37 = ensure_python_interpreter(PY37)
-    py38 = ensure_python_interpreter(PY38)
+    py310 = ensure_python_interpreter(PY310)
 
     assert_uses_python(python=sys.executable, expected_version=sys.version_info[:2])
     assert_uses_python(python=py27, expected_version=(2, 7))
     assert_uses_python(python=py37, expected_version=(3, 7))
-    assert_uses_python(python=py38, expected_version=(3, 8))
+    assert_uses_python(python=py310, expected_version=(3, 10))
 
     # When PEX_PYTHON is imprecise, the final python should be chosen by the PEX runtime.
     py27_ppp = os.path.dirname(py27)
     assert_uses_python(
-        python=py38,
+        python=py310,
         expected_version=(2, 7),
         PEX_PYTHON="python3.7",
         PEX_PYTHON_PATH=py27_ppp,
@@ -82,7 +82,7 @@ def assert_uses_python(
     # When PEX_PYTHON is imprecise and not locked down to a minor version, a warning should be
     # issued.
     assert_uses_python(
-        python=py38,
+        python=py310,
         expected_version=(2, 7),
         PEX_PYTHON="python3",
         PEX_PYTHON_PATH=py27_ppp,
@@ -96,13 +96,13 @@ def assert_uses_python(
 
     # When PEX_PYTHON is precise but not on PEX_PYTHON_PATH, the final python should also be chosen
     # by the PEX runtime and selection should fail.
-    _, _, returncode = execute_pex(python=py38, PEX_PYTHON=py37, PEX_PYTHON_PATH=py27_ppp)
+    _, _, returncode = execute_pex(python=py310, PEX_PYTHON=py37, PEX_PYTHON_PATH=py27_ppp)
     assert 0 != returncode
 
     # But when PEX_PYTHON is precise and on the PEX_PYTHON_PATH, the final python should be
     # PEX_PYTHON.
     assert_uses_python(
-        python=py38,
+        python=py310,
         expected_version=(3, 7),
         PEX_PYTHON=py37,
         PEX_PYTHON_PATH=":".join(os.path.dirname(py) for py in (py27, py37)),
diff --git a/tests/integration/test_issue_434.py b/tests/integration/test_issue_434.py
--- a/tests/integration/test_issue_434.py
+++ b/tests/integration/test_issue_434.py
@@ -4,7 +4,7 @@
 import os
 
 from pex.common import temporary_dir
-from pex.testing import PY38, ensure_python_interpreter, run_pex_command, run_simple_pex
+from pex.testing import PY310, ensure_python_interpreter, run_pex_command, run_simple_pex
 
 
 def test_entry_point_targeting():
@@ -13,7 +13,7 @@ def test_entry_point_targeting():
     with temporary_dir() as td:
         pexrc_path = os.path.join(td, ".pexrc")
         with open(pexrc_path, "w") as pexrc:
-            pex_python = ensure_python_interpreter(PY38)
+            pex_python = ensure_python_interpreter(PY310)
             pexrc.write("PEX_PYTHON=%s" % pex_python)
 
         # test pex with entry point
diff --git a/tests/integration/test_issue_898.py b/tests/integration/test_issue_898.py
--- a/tests/integration/test_issue_898.py
+++ b/tests/integration/test_issue_898.py
@@ -6,13 +6,13 @@
 from textwrap import dedent
 
 from pex.common import safe_open, temporary_dir
-from pex.testing import PY27, PY38, ensure_python_interpreter, make_env, run_pex_command
+from pex.testing import PY27, PY310, ensure_python_interpreter, make_env, run_pex_command
 
 
 def test_top_level_requirements_requires_python_env_markers():
     # type: () -> None
     python27 = ensure_python_interpreter(PY27)
-    python38 = ensure_python_interpreter(PY38)
+    python310 = ensure_python_interpreter(PY310)
     with temporary_dir() as td:
         src_dir = os.path.join(td, "src")
         with safe_open(os.path.join(src_dir, "test_issues_898.py"), "w") as fp:
@@ -31,7 +31,7 @@ def test_top_level_requirements_requires_python_env_markers():
         results = run_pex_command(
             args=[
                 "--python={}".format(python27),
-                "--python={}".format(python38),
+                "--python={}".format(python310),
                 "zipp>=1,<=3.1.0",
                 "--sources-directory={}".format(src_dir),
                 "--entry-point=test_issues_898",
@@ -42,7 +42,7 @@ def test_top_level_requirements_requires_python_env_markers():
         results.assert_success()
 
         pex_root = os.path.realpath(os.path.join(td, "pex_root"))
-        for python in python27, python38:
+        for python in python27, python310:
             output = subprocess.check_output([python, pex_file], env=make_env(PEX_ROOT=pex_root))
             zipp_location = os.path.realpath(output.decode("utf-8").strip())
             assert zipp_location.startswith(
diff --git a/tests/integration/test_issue_899.py b/tests/integration/test_issue_899.py
--- a/tests/integration/test_issue_899.py
+++ b/tests/integration/test_issue_899.py
@@ -5,7 +5,7 @@
 
 from pex.interpreter import PythonInterpreter
 from pex.pex_info import PexInfo
-from pex.testing import PY27, PY38, ensure_python_interpreter, run_pex_command, run_simple_pex
+from pex.testing import PY27, PY310, ensure_python_interpreter, run_pex_command, run_simple_pex
 from pex.third_party.pkg_resources import Requirement
 from pex.typing import TYPE_CHECKING
 
@@ -16,13 +16,13 @@
 def test_top_level_environment_markers(tmpdir):
     # type: (Any) -> None
     python27 = ensure_python_interpreter(PY27)
-    python38 = ensure_python_interpreter(PY38)
+    python310 = ensure_python_interpreter(PY310)
 
     pex_file = os.path.join(str(tmpdir), "pex")
 
     requirement = "subprocess32==3.2.7; python_version<'3'"
     results = run_pex_command(
-        args=["--python", python27, "--python", python38, requirement, "-o", pex_file]
+        args=["--python", python27, "--python", python310, requirement, "-o", pex_file]
     )
     results.assert_success()
     requirements = PexInfo.from_pex(pex_file).requirements
@@ -36,19 +36,19 @@ def test_top_level_environment_markers(tmpdir):
     )
     assert 0 == returncode
 
-    py38_interpreter = PythonInterpreter.from_binary(python38)
+    py310_interpreter = PythonInterpreter.from_binary(python310)
 
     output, returncode = run_simple_pex(
         pex_file,
         args=["-c", "import subprocess"],
-        interpreter=py38_interpreter,
+        interpreter=py310_interpreter,
     )
     assert 0 == returncode
 
     output, returncode = run_simple_pex(
         pex_file,
         args=["-c", "import subprocess32"],
-        interpreter=py38_interpreter,
+        interpreter=py310_interpreter,
     )
     assert (
         1 == returncode
diff --git a/tests/integration/test_issue_996.py b/tests/integration/test_issue_996.py
--- a/tests/integration/test_issue_996.py
+++ b/tests/integration/test_issue_996.py
@@ -8,7 +8,7 @@
 from pex.interpreter import PythonInterpreter
 from pex.testing import (
     PY27,
-    PY38,
+    PY310,
     IntegResults,
     ensure_python_interpreter,
     make_env,
@@ -24,13 +24,13 @@
 def test_resolve_local_platform():
     # type: () -> None
     python27 = ensure_python_interpreter(PY27)
-    python38 = ensure_python_interpreter(PY38)
-    pex_python_path = os.pathsep.join((python27, python38))
+    python310 = ensure_python_interpreter(PY310)
+    pex_python_path = os.pathsep.join((python27, python310))
 
     def create_platform_pex(args):
         # type: (List[str]) -> IntegResults
         return run_pex_command(
-            args=["--platform", str(PythonInterpreter.from_binary(python38).platform)] + args,
+            args=["--platform", str(PythonInterpreter.from_binary(python310).platform)] + args,
             python=python27,
             env=make_env(PEX_PYTHON_PATH=pex_python_path),
         )
@@ -54,7 +54,7 @@ def create_platform_pex(args):
         output, returncode = run_simple_pex(
             pex=pex_file,
             args=("-c", "import psutil; print(psutil.cpu_count())"),
-            interpreter=PythonInterpreter.from_binary(python38),
+            interpreter=PythonInterpreter.from_binary(python310),
         )
         assert 0 == returncode
         assert int(output.strip()) >= multiprocessing.cpu_count()
diff --git a/tests/integration/test_reexec.py b/tests/integration/test_reexec.py
--- a/tests/integration/test_reexec.py
+++ b/tests/integration/test_reexec.py
@@ -13,7 +13,7 @@
 from pex.interpreter import PythonInterpreter
 from pex.testing import (
     PY27,
-    PY38,
+    PY310,
     ensure_python_interpreter,
     make_env,
     run_pex_command,
@@ -130,27 +130,27 @@ def test_pex_reexec_constraints_match_current_pythonpath_present():
 
 def test_pex_reexec_constraints_dont_match_current_pex_python_path():
     # type: () -> None
-    py38_interpreter = ensure_python_interpreter(PY38)
+    py310_interpreter = ensure_python_interpreter(PY310)
     py27_interpreter = ensure_python_interpreter(PY27)
     _assert_exec_chain(
-        exec_chain=[py38_interpreter],
-        pex_python_path=[py27_interpreter, py38_interpreter],
-        interpreter_constraints=["=={}".format(PY38)],
+        exec_chain=[py310_interpreter],
+        pex_python_path=[py27_interpreter, py310_interpreter],
+        interpreter_constraints=["=={}".format(PY310)],
     )
 
 
 def test_pex_reexec_constraints_dont_match_current_pex_python_path_min_py_version_selected():
     # type: () -> None
-    py38_interpreter = ensure_python_interpreter(PY38)
+    py310_interpreter = ensure_python_interpreter(PY310)
     py27_interpreter = ensure_python_interpreter(PY27)
     _assert_exec_chain(
-        exec_chain=[py27_interpreter], pex_python_path=[py38_interpreter, py27_interpreter]
+        exec_chain=[py27_interpreter], pex_python_path=[py310_interpreter, py27_interpreter]
     )
 
 
 def test_pex_reexec_constraints_dont_match_current_pex_python():
     # type: () -> None
-    version = PY27 if sys.version_info[:2] == (3, 8) else PY38
+    version = PY27 if sys.version_info[:2] == (3, 8) else PY310
     interpreter = ensure_python_interpreter(version)
     _assert_exec_chain(
         exec_chain=[interpreter],
diff --git a/tests/integration/test_reproducible.py b/tests/integration/test_reproducible.py
--- a/tests/integration/test_reproducible.py
+++ b/tests/integration/test_reproducible.py
@@ -12,7 +12,7 @@
 from pex.testing import (
     PY27,
     PY37,
-    PY38,
+    PY310,
     create_pex_command,
     ensure_python_interpreter,
     run_command_with_jitter,
@@ -78,13 +78,13 @@ def explode_pex(path):
 MAJOR_COMPATIBLE_PYTHONS = (
     (sys.executable, ensure_python_interpreter(PY27))
     if PY2
-    else (sys.executable, ensure_python_interpreter(PY37), ensure_python_interpreter(PY38))
+    else (sys.executable, ensure_python_interpreter(PY37), ensure_python_interpreter(PY310))
 )
 MIXED_MAJOR_PYTHONS = (
     sys.executable,
     ensure_python_interpreter(PY27),
     ensure_python_interpreter(PY37),
-    ensure_python_interpreter(PY38),
+    ensure_python_interpreter(PY310),
 )
 
 
diff --git a/tests/resolve/conftest.py b/tests/resolve/conftest.py
--- a/tests/resolve/conftest.py
+++ b/tests/resolve/conftest.py
@@ -7,7 +7,7 @@
 
 from pex.interpreter import PythonInterpreter
 from pex.platforms import Platform
-from pex.testing import PY27, PY37, PY38, ensure_python_interpreter
+from pex.testing import PY27, PY37, PY310, ensure_python_interpreter
 
 
 @pytest.fixture
@@ -41,6 +41,6 @@ def py37():
 
 
 @pytest.fixture
-def py38():
+def py310():
     # type: () -> PythonInterpreter
-    return PythonInterpreter.from_binary(ensure_python_interpreter(PY38))
+    return PythonInterpreter.from_binary(ensure_python_interpreter(PY310))
diff --git a/tests/resolve/test_target_configuration.py b/tests/resolve/test_target_configuration.py
--- a/tests/resolve/test_target_configuration.py
+++ b/tests/resolve/test_target_configuration.py
@@ -21,7 +21,7 @@ def test_interpreter(
 def test_unique_targets(
     py27,  # type: PythonInterpreter
     py37,  # type: PythonInterpreter
-    py38,  # type: PythonInterpreter
+    py310,  # type: PythonInterpreter
     current_interpreter,  # type: PythonInterpreter
     current_platform,  # type: Platform
 ):
@@ -52,6 +52,6 @@ def test_unique_targets(
     )
 
     assert (
-        OrderedSet(DistributionTarget.for_interpreter(i) for i in (py27, py37, py38))
-        == TargetConfiguration(interpreters=[py27, py37, py38]).unique_targets()
+        OrderedSet(DistributionTarget.for_interpreter(i) for i in (py27, py37, py310))
+        == TargetConfiguration(interpreters=[py27, py37, py310]).unique_targets()
     )
diff --git a/tests/resolve/test_target_options.py b/tests/resolve/test_target_options.py
--- a/tests/resolve/test_target_options.py
+++ b/tests/resolve/test_target_options.py
@@ -132,16 +132,16 @@ def test_configure_interpreter_path(
     parser,  # type: ArgumentParser
     py27,  # type: PythonInterpreter
     py37,  # type: PythonInterpreter
-    py38,  # type: PythonInterpreter
+    py310,  # type: PythonInterpreter
 ):
     # type: (...) -> None
     target_options.register(parser)
 
-    with environment_as(PATH=path_for(py27, py37, py38)):
+    with environment_as(PATH=path_for(py27, py37, py310)):
         assert_interpreter(parser, ["--python", "python"], py27)
         assert_interpreter(parser, ["--python", "python2"], py27)
         assert_interpreter(parser, ["--python", "python3"], py37)
-        assert_interpreter(parser, ["--python", "python3.8"], py38)
+        assert_interpreter(parser, ["--python", "python3.10"], py310)
         with pytest.raises(target_options.InterpreterNotFound):
             compute_target_configuration(parser, args=["--python", "python3.9"])
 
@@ -150,18 +150,18 @@ def test_configure_interpreter_pex_python_path(
     parser,  # type: ArgumentParser
     py27,  # type: PythonInterpreter
     py37,  # type: PythonInterpreter
-    py38,  # type: PythonInterpreter
+    py310,  # type: PythonInterpreter
 ):
     # type: (...) -> None
     target_options.register(parser)
 
-    path_env_var = path_for(py27, py37, py38)
+    path_env_var = path_for(py27, py37, py310)
 
     with ENV.patch(PEX_PYTHON_PATH=path_env_var):
         assert_interpreter(parser, ["--python", "python"], py27)
         assert_interpreter(parser, ["--python", "python2"], py27)
         assert_interpreter(parser, ["--python", "python3"], py37)
-        assert_interpreter(parser, ["--python", "python3.8"], py38)
+        assert_interpreter(parser, ["--python", "python3.10"], py310)
         with pytest.raises(target_options.InterpreterNotFound):
             compute_target_configuration(parser, args=["--python", "python3.9"])
 
@@ -169,19 +169,19 @@ def test_configure_interpreter_pex_python_path(
         assert_interpreter(parser, ["--python", "python2.7"], py27)
 
     assert_interpreter(parser, ["--python-path", path_env_var, "--python", "python3"], py37)
-    assert_interpreter(parser, ["--python-path", py38.binary, "--python", "python3.8"], py38)
+    assert_interpreter(parser, ["--python-path", py310.binary, "--python", "python3.10"], py310)
 
 
 def test_configure_interpreter_constraints(
     parser,  # type: ArgumentParser
     py27,  # type: PythonInterpreter
     py37,  # type: PythonInterpreter
-    py38,  # type: PythonInterpreter
+    py310,  # type: PythonInterpreter
 ):
     # type: (...) -> None
     target_options.register(parser)
 
-    path_env_var = path_for(py38, py27, py37)
+    path_env_var = path_for(py310, py27, py37)
 
     def interpreter_constraint_args(interpreter_constraints):
         # type: (Iterable[str]) -> List[str]
@@ -206,14 +206,14 @@ def assert_interpreter_constraint(
             *expected_interpreters
         )
 
-    assert_interpreter_constraint(["CPython"], [py38, py27, py37], expected_interpreter=py27)
-    assert_interpreter_constraint([">=2"], [py38, py27, py37], expected_interpreter=py27)
-    assert_interpreter_constraint([">=2,!=3.7.*"], [py38, py27], expected_interpreter=py27)
-    assert_interpreter_constraint(["==3.*"], [py38, py37], expected_interpreter=py37)
-    assert_interpreter_constraint(["==3.8.*"], [py38], expected_interpreter=py38)
-    assert_interpreter_constraint([">3"], [py38, py37], expected_interpreter=py37)
+    assert_interpreter_constraint(["CPython"], [py310, py27, py37], expected_interpreter=py27)
+    assert_interpreter_constraint([">=2"], [py310, py27, py37], expected_interpreter=py27)
+    assert_interpreter_constraint([">=2,!=3.7.*"], [py310, py27], expected_interpreter=py27)
+    assert_interpreter_constraint(["==3.*"], [py310, py37], expected_interpreter=py37)
+    assert_interpreter_constraint(["==3.10.*"], [py310], expected_interpreter=py310)
+    assert_interpreter_constraint([">3"], [py310, py37], expected_interpreter=py37)
     assert_interpreter_constraint([">=3.7,<3.8"], [py37], expected_interpreter=py37)
-    assert_interpreter_constraint(["==3.8.*", "==2.7.*"], [py38, py27], expected_interpreter=py27)
+    assert_interpreter_constraint(["==3.10.*", "==2.7.*"], [py310, py27], expected_interpreter=py27)
 
     def assert_interpreter_constraint_not_satisfied(interpreter_constraints):
         # type: (List[str]) -> None
@@ -231,12 +231,12 @@ def test_configure_resolve_local_platforms(
     parser,  # type: ArgumentParser
     py27,  # type: PythonInterpreter
     py37,  # type: PythonInterpreter
-    py38,  # type: PythonInterpreter
+    py310,  # type: PythonInterpreter
 ):
     # type: (...) -> None
     target_options.register(parser)
 
-    path_env_var = path_for(py27, py37, py38)
+    path_env_var = path_for(py27, py37, py310)
 
     def assert_local_platforms(
         platforms,  # type: Iterable[str]
@@ -277,13 +277,13 @@ def assert_local_platforms(
         extra_args=["--interpreter-constraint", "CPython"],
         expected_platforms=[foreign_platform],
         expected_interpreter=py27,
-        expected_interpreters=(py27, py37, py38),
+        expected_interpreters=(py27, py37, py310),
     )
 
     assert_local_platforms(
         platforms=[foreign_platform, str(py27.platform)],
-        extra_args=["--interpreter-constraint", "==3.8.*"],
+        extra_args=["--interpreter-constraint", "==3.10.*"],
         expected_platforms=[foreign_platform],
         expected_interpreter=py27,
-        expected_interpreters=(py38, py27),
+        expected_interpreters=(py310, py27),
     )
diff --git a/tests/test_interpreter.py b/tests/test_interpreter.py
--- a/tests/test_interpreter.py
+++ b/tests/test_interpreter.py
@@ -23,7 +23,7 @@
 from pex.testing import (
     PY27,
     PY37,
-    PY38,
+    PY310,
     PY_VER,
     ensure_python_distribution,
     ensure_python_interpreter,
@@ -40,7 +40,7 @@
     from mock import Mock, patch  # type: ignore[misc,import]
 
 if TYPE_CHECKING:
-    from typing import Any, Iterator, List, Tuple, Optional
+    from typing import Any, Iterator, List, Tuple
 
     from pex.interpreter import InterpreterOrError
 
@@ -115,6 +115,12 @@ def test_binary_name_matching(self):
             "python3",
             "python3.6",
             "python3.6m",
+            "python3.10",
+            "python3.10m",
+            "python3.99",
+            "python3.99m",
+            "python3.123",
+            "python3.123m",
         )
 
         matches = PythonInterpreter._matches_binary_name
@@ -212,7 +218,7 @@ def test_iter_interpreter_path_filter_symlink(self, test_interpreter1, test_inte
     def test_pyenv_shims(self, tmpdir):
         # type: (Any) -> None
         py37, _, run_pyenv = ensure_python_distribution(PY37)
-        py38 = ensure_python_interpreter(PY38)
+        py310 = ensure_python_interpreter(PY310)
 
         pyenv_root = str(run_pyenv(["root"]).strip())
         pyenv_shims = os.path.join(pyenv_root, "shims")
@@ -258,16 +264,16 @@ def assert_shim_inactive(shim_name):
                 with pytest.raises(PythonInterpreter.IdentificationError):
                     interpreter_for_shim(shim_name)
 
-            pyenv_global(PY37, PY38)
+            pyenv_global(PY37, PY310)
             assert_shim("python", py37)
             assert_shim("python3", py37)
             assert_shim("python3.7", py37)
-            assert_shim("python3.8", py38)
+            assert_shim("python3.10", py310)
 
-            pyenv_global(PY38, PY37)
-            assert_shim("python", py38)
-            assert_shim("python3", py38)
-            assert_shim("python3.8", py38)
+            pyenv_global(PY310, PY37)
+            assert_shim("python", py310)
+            assert_shim("python3", py310)
+            assert_shim("python3.10", py310)
             assert_shim("python3.7", py37)
 
             pyenv_local(PY37)
@@ -276,17 +282,17 @@ def assert_shim_inactive(shim_name):
             assert_shim("python3.7", py37)
             assert_shim_inactive("python3.8")
 
-            with pyenv_shell(PY38):
-                assert_shim("python", py38)
-                assert_shim("python3", py38)
-                assert_shim("python3.8", py38)
+            with pyenv_shell(PY310):
+                assert_shim("python", py310)
+                assert_shim("python3", py310)
+                assert_shim("python3.10", py310)
                 assert_shim_inactive("python3.7")
 
-            with pyenv_shell(PY37, PY38):
+            with pyenv_shell(PY37, PY310):
                 assert_shim("python", py37)
                 assert_shim("python3", py37)
                 assert_shim("python3.7", py37)
-                assert_shim("python3.8", py38)
+                assert_shim("python3.10", py310)
 
             # The shim pointer is now invalid since python3.7 was uninstalled and so
             # should be re-read and found invalid.
@@ -404,13 +410,13 @@ def test_resolve_venv_ambient():
 def test_identify_cwd_isolation_issues_1231(tmpdir):
     # type: (Any) -> None
 
-    python38, pip = ensure_python_venv(PY38)
+    python37, pip = ensure_python_venv(PY37)
     polluted_cwd = os.path.join(str(tmpdir), "dir")
     subprocess.check_call(args=[pip, "install", "--target", polluted_cwd, "pex==2.1.16"])
 
     pex_root = os.path.join(str(tmpdir), "pex_root")
     with pushd(polluted_cwd), ENV.patch(PEX_ROOT=pex_root):
-        interp = PythonInterpreter.from_binary(python38)
+        interp = PythonInterpreter.from_binary(python37)
 
     interp_info_files = {
         os.path.join(root, f)
diff --git a/tests/test_pex.py b/tests/test_pex.py
--- a/tests/test_pex.py
+++ b/tests/test_pex.py
@@ -23,7 +23,7 @@
 from pex.testing import (
     IS_PYPY3,
     PY27,
-    PY38,
+    PY310,
     WheelBuilder,
     built_wheel,
     ensure_python_interpreter,
@@ -566,7 +566,7 @@ def test_pex_verify_entry_point_module_should_fail():
 def test_activate_interpreter_different_from_current():
     # type: () -> None
     with temporary_dir() as pex_root:
-        interp_version = PY38 if PY2 else PY27
+        interp_version = PY310 if PY2 else PY27
         custom_interpreter = PythonInterpreter.from_binary(
             ensure_python_interpreter(interp_version)
         )
diff --git a/tests/test_pex_bootstrapper.py b/tests/test_pex_bootstrapper.py
--- a/tests/test_pex_bootstrapper.py
+++ b/tests/test_pex_bootstrapper.py
@@ -19,7 +19,7 @@
     iter_compatible_interpreters,
 )
 from pex.pex_builder import PEXBuilder
-from pex.testing import PY27, PY37, PY38, ensure_python_interpreter
+from pex.testing import PY27, PY37, PY310, ensure_python_interpreter
 from pex.typing import TYPE_CHECKING
 from pex.variables import ENV
 
@@ -54,15 +54,15 @@ def test_find_compatible_interpreters():
     # type: () -> None
     py27 = ensure_python_interpreter(PY27)
     py37 = ensure_python_interpreter(PY37)
-    py38 = ensure_python_interpreter(PY38)
-    path = [py27, py37, py38]
+    py310 = ensure_python_interpreter(PY310)
+    path = [py27, py37, py310]
 
-    assert [py37, py38] == find_interpreters(path, constraints=[">3"])
+    assert [py37, py310] == find_interpreters(path, constraints=[">3"])
     assert [py27] == find_interpreters(path, constraints=["<3"])
 
-    assert [py38] == find_interpreters(path, constraints=[">{}".format(PY37)])
-    assert [py37] == find_interpreters(path, constraints=[">{}, <{}".format(PY27, PY38)])
-    assert [py38] == find_interpreters(path, constraints=[">=3.8"])
+    assert [py310] == find_interpreters(path, constraints=[">{}".format(PY37)])
+    assert [py37] == find_interpreters(path, constraints=[">{}, <{}".format(PY27, PY310)])
+    assert [py310] == find_interpreters(path, constraints=[">=3.10"])
 
     with pytest.raises(UnsatisfiableInterpreterConstraintsError):
         find_interpreters(path, constraints=["<2"])
@@ -147,12 +147,12 @@ def test_find_compatible_interpreters_with_valid_basenames():
     # type: () -> None
     py27 = ensure_python_interpreter(PY27)
     py37 = ensure_python_interpreter(PY37)
-    py38 = ensure_python_interpreter(PY38)
-    path = [py27, py37, py38]
+    py310 = ensure_python_interpreter(PY310)
+    path = [py27, py37, py310]
 
     assert [py37] == find_interpreters(path, valid_basenames=basenames(py37))
-    assert [py27, py38] == find_interpreters(
-        path, valid_basenames=basenames(*reversed([py27, py38]))
+    assert [py27, py310] == find_interpreters(
+        path, valid_basenames=basenames(*reversed([py27, py310]))
     )
 
 
@@ -160,8 +160,8 @@ def test_find_compatible_interpreters_with_valid_basenames_and_constraints():
     # type: () -> None
     py27 = ensure_python_interpreter(PY27)
     py37 = ensure_python_interpreter(PY37)
-    py38 = ensure_python_interpreter(PY38)
-    path = [py27, py37, py38]
+    py310 = ensure_python_interpreter(PY310)
+    path = [py27, py37, py310]
 
     assert [py37] == find_interpreters(
         path, valid_basenames=basenames(py27, py37), constraints=[">=3"]
@@ -170,26 +170,26 @@ def test_find_compatible_interpreters_with_valid_basenames_and_constraints():
 
 def test_find_compatible_interpreters_bias_current():
     # type: () -> None
-    py38 = ensure_python_interpreter(PY38)
+    py310 = ensure_python_interpreter(PY310)
     current_interpreter = PythonInterpreter.get()
-    assert [current_interpreter.binary, py38] == find_interpreters([py38, sys.executable])
-    assert [current_interpreter.binary, py38] == find_interpreters([sys.executable, py38])
+    assert [current_interpreter.binary, py310] == find_interpreters([py310, sys.executable])
+    assert [current_interpreter.binary, py310] == find_interpreters([sys.executable, py310])
 
 
 def test_find_compatible_interpreters_siblings_of_current_issues_1109():
     # type: () -> None
     py27 = ensure_python_interpreter(PY27)
-    py38 = ensure_python_interpreter(PY38)
+    py310 = ensure_python_interpreter(PY310)
 
     with temporary_dir() as path_entry:
         python27 = os.path.join(path_entry, "python2.7")
         shutil.copy(py27, python27)
 
-        python38 = os.path.join(path_entry, "python3.8")
-        shutil.copy(py38, python38)
+        python310 = os.path.join(path_entry, "python3.10")
+        shutil.copy(py310, python310)
 
-        assert [os.path.realpath(p) for p in (python38, python27)] == find_interpreters(
-            path=[path_entry], preferred_interpreter=PythonInterpreter.from_binary(python38)
+        assert [os.path.realpath(p) for p in (python310, python27)] == find_interpreters(
+            path=[path_entry], preferred_interpreter=PythonInterpreter.from_binary(python310)
         )
 
 
@@ -256,9 +256,9 @@ def test_pp_invalid():
 
 def test_pp_exact():
     # type: () -> None
-    py38 = ensure_python_interpreter(PY38)
-    with ENV.patch(PEX_PYTHON=py38):
-        assert PythonInterpreter.from_binary(py38) == find_compatible_interpreter()
+    py310 = ensure_python_interpreter(PY310)
+    with ENV.patch(PEX_PYTHON=py310):
+        assert PythonInterpreter.from_binary(py310) == find_compatible_interpreter()
 
 
 def test_pp_exact_on_ppp():
@@ -266,21 +266,22 @@ def test_pp_exact_on_ppp():
 
     py27 = ensure_python_interpreter(PY27)
     py37 = ensure_python_interpreter(PY37)
-    py38 = ensure_python_interpreter(PY38)
+    py310 = ensure_python_interpreter(PY310)
 
     with ENV.patch(
-        PEX_PYTHON=py38, PEX_PYTHON_PATH=":".join(os.path.dirname(py) for py in (py27, py37, py38))
+        PEX_PYTHON=py310,
+        PEX_PYTHON_PATH=":".join(os.path.dirname(py) for py in (py27, py37, py310)),
     ):
-        assert PythonInterpreter.from_binary(py38) == find_compatible_interpreter()
+        assert PythonInterpreter.from_binary(py310) == find_compatible_interpreter()
 
 
 def test_pp_exact_satisfies_constraints():
     # type: () -> None
 
-    py38 = ensure_python_interpreter(PY38)
+    py310 = ensure_python_interpreter(PY310)
 
-    with ENV.patch(PEX_PYTHON=py38):
-        assert PythonInterpreter.from_binary(py38) == find_compatible_interpreter(
+    with ENV.patch(PEX_PYTHON=py310):
+        assert PythonInterpreter.from_binary(py310) == find_compatible_interpreter(
             interpreter_constraints=[">=3.7"]
         )
 
@@ -288,12 +289,12 @@ def test_pp_exact_satisfies_constraints():
 def test_pp_exact_does_not_satisfy_constraints():
     # type: () -> None
 
-    py38 = ensure_python_interpreter(PY38)
+    py310 = ensure_python_interpreter(PY310)
 
-    with ENV.patch(PEX_PYTHON=py38):
+    with ENV.patch(PEX_PYTHON=py310):
         with pytest.raises(
             UnsatisfiableInterpreterConstraintsError,
-            match=r"Failed to find a compatible PEX_PYTHON={pp}.".format(pp=py38),
+            match=r"Failed to find a compatible PEX_PYTHON={pp}.".format(pp=py310),
         ):
             find_compatible_interpreter(interpreter_constraints=["<=3.7"])
 
@@ -303,13 +304,13 @@ def test_pp_exact_not_on_ppp():
 
     py27 = ensure_python_interpreter(PY27)
     py37 = ensure_python_interpreter(PY37)
-    py38 = ensure_python_interpreter(PY38)
+    py310 = ensure_python_interpreter(PY310)
 
     with ENV.patch(
-        PEX_PYTHON=py38, PEX_PYTHON_PATH=":".join(os.path.dirname(py) for py in (py27, py37))
+        PEX_PYTHON=py310, PEX_PYTHON_PATH=":".join(os.path.dirname(py) for py in (py27, py37))
     ):
         with pytest.raises(
             UnsatisfiableInterpreterConstraintsError,
-            match=r"The specified PEX_PYTHON={pp} did not meet other constraints.".format(pp=py38),
+            match=r"The specified PEX_PYTHON={pp} did not meet other constraints.".format(pp=py310),
         ):
             find_compatible_interpreter()
diff --git a/tests/test_pip.py b/tests/test_pip.py
--- a/tests/test_pip.py
+++ b/tests/test_pip.py
@@ -15,7 +15,7 @@
 from pex.jobs import Job
 from pex.pip import PackageIndexConfiguration, Pip
 from pex.platforms import Platform
-from pex.testing import PY38, ensure_python_interpreter, environment_as
+from pex.testing import PY310, ensure_python_interpreter, environment_as
 from pex.typing import TYPE_CHECKING
 from pex.variables import ENV
 
@@ -140,8 +140,8 @@ def assert_download_platform_markers_issue_1366(
     tmpdir,  # type: Any
 ):
     # type: (...) -> None
-    python38_interpreter = PythonInterpreter.from_binary(ensure_python_interpreter(PY38))
-    pip = create_pip(python38_interpreter)
+    python310_interpreter = PythonInterpreter.from_binary(ensure_python_interpreter(PY310))
+    pip = create_pip(python310_interpreter)
 
     python27_platform = Platform.create("manylinux_2_33_x86_64-cp-27-cp27mu")
     download_dir = os.path.join(str(tmpdir), "downloads")
@@ -184,8 +184,8 @@ def test_download_platform_markers_issue_1366_indeterminate(
     tmpdir,  # type: Any
 ):
     # type: (...) -> None
-    python38_interpreter = PythonInterpreter.from_binary(ensure_python_interpreter(PY38))
-    pip = create_pip(python38_interpreter)
+    python310_interpreter = PythonInterpreter.from_binary(ensure_python_interpreter(PY310))
+    pip = create_pip(python310_interpreter)
 
     python27_platform = Platform.create("manylinux_2_33_x86_64-cp-27-cp27mu")
     download_dir = os.path.join(str(tmpdir), "downloads")
diff --git a/tests/test_resolver.py b/tests/test_resolver.py
--- a/tests/test_resolver.py
+++ b/tests/test_resolver.py
@@ -32,7 +32,7 @@
     IS_PYPY,
     PY27,
     PY37,
-    PY38,
+    PY310,
     PY_VER,
     built_wheel,
     ensure_python_interpreter,
@@ -263,7 +263,7 @@ def test_resolve_current_platform(p537_resolve_cache):
         resolve_p537_wheel_names, cache=p537_resolve_cache, platforms=["current"]
     )
 
-    other_python_version = PY38 if PY_VER == (3, 7) else PY37
+    other_python_version = PY310 if PY_VER == (3, 7) else PY37
     other_python = PythonInterpreter.from_binary(ensure_python_interpreter(other_python_version))
     current_python = PythonInterpreter.get()
 
@@ -293,7 +293,7 @@ def test_resolve_current_and_foreign_platforms(p537_resolve_cache):
 
     assert 2 == len(resolve_current_and_foreign())
 
-    other_python_version = PY38 if PY_VER == (3, 7) else PY37
+    other_python_version = PY310 if PY_VER == (3, 7) else PY37
     other_python = PythonInterpreter.from_binary(ensure_python_interpreter(other_python_version))
     current_python = PythonInterpreter.get()
 
@@ -401,8 +401,8 @@ def test_issues_892():
         """
     ).format(python27=python27)
 
-    python38 = ensure_python_interpreter(PY38)
-    cmd, process = PythonInterpreter.from_binary(python38).open_process(
+    python310 = ensure_python_interpreter(PY310)
+    cmd, process = PythonInterpreter.from_binary(python310).open_process(
         args=["-c", program], stderr=subprocess.PIPE
     )
     _, stderr = process.communicate()
@@ -601,9 +601,9 @@ def py27():
 
 
 @pytest.fixture(scope="module")
-def py38():
+def py310():
     # type: () -> PythonInterpreter
-    return PythonInterpreter.from_binary(ensure_python_interpreter(PY38))
+    return PythonInterpreter.from_binary(ensure_python_interpreter(PY310))
 
 
 @pytest.fixture(scope="module")
@@ -634,7 +634,7 @@ def foreign_platform(
 
 
 @pytest.fixture(scope="module")
-def pex_repository(py27, py38, foreign_platform, manylinux):
+def pex_repository(py27, py310, foreign_platform, manylinux):
     # type () -> str
 
     # N.B.: requests 2.25.1 constrains urllib3 to <1.27,>=1.21.1 and pick 1.26.2 on its own as of
@@ -642,7 +642,7 @@ def pex_repository(py27, py38, foreign_platform, manylinux):
     constraints_file = create_constraints_file("urllib3==1.26.1")
 
     return create_pex_repository(
-        interpreters=[py27, py38],
+        interpreters=[py27, py310],
         platforms=[foreign_platform],
         requirements=["requests[security,socks]==2.25.1"],
         constraint_files=[constraints_file],
@@ -653,7 +653,7 @@ def pex_repository(py27, py38, foreign_platform, manylinux):
 def test_resolve_from_pex(
     pex_repository,  # type: str
     py27,  # type: PythonInterpreter
-    py38,  # type: PythonInterpreter
+    py310,  # type: PythonInterpreter
     foreign_platform,  # type: Platform
     manylinux,  # type: Optional[str]
 ):
@@ -665,7 +665,7 @@ def test_resolve_from_pex(
     result = resolve_from_pex(
         pex=pex_repository,
         requirements=direct_requirements,
-        interpreters=[py27, py38],
+        interpreters=[py27, py310],
         platforms=[foreign_platform],
         assume_manylinux=manylinux,
     )
@@ -724,7 +724,7 @@ def test_resolve_from_pex_subset(
 
 def test_resolve_from_pex_not_found(
     pex_repository,  # type: str
-    py38,  # type: PythonInterpreter
+    py310,  # type: PythonInterpreter
 ):
     # type: (...) -> None
 
@@ -732,7 +732,7 @@ def test_resolve_from_pex_not_found(
         resolve_from_pex(
             pex=pex_repository,
             requirements=["pex"],
-            interpreters=[py38],
+            interpreters=[py310],
         )
     assert "A distribution for pex could not be resolved in this environment." in str(
         exec_info.value
@@ -742,13 +742,13 @@ def test_resolve_from_pex_not_found(
         resolve_from_pex(
             pex=pex_repository,
             requirements=["requests==1.0.0"],
-            interpreters=[py38],
+            interpreters=[py310],
         )
     message = str(exec_info.value)
     assert (
         "Failed to resolve requirements from PEX environment @ {}".format(pex_repository) in message
     )
-    assert "Needed {} compatible dependencies for:".format(py38.platform) in message
+    assert "Needed {} compatible dependencies for:".format(py310.platform) in message
     assert "1: requests==1.0.0" in message
     assert "But this pex only contains:" in message
     assert "requests-2.25.1-py2.py3-none-any.whl" in message
@@ -757,7 +757,7 @@ def test_resolve_from_pex_not_found(
 def test_resolve_from_pex_intransitive(
     pex_repository,  # type: str
     py27,  # type: PythonInterpreter
-    py38,  # type: PythonInterpreter
+    py310,  # type: PythonInterpreter
     foreign_platform,  # type: Platform
     manylinux,  # type: Optional[str]
 ):
@@ -767,7 +767,7 @@ def test_resolve_from_pex_intransitive(
         pex=pex_repository,
         requirements=["requests"],
         transitive=False,
-        interpreters=[py27, py38],
+        interpreters=[py27, py310],
         platforms=[foreign_platform],
         assume_manylinux=manylinux,
     ).installed_distributions
diff --git a/tests/tools/commands/test_interpreter_command.py b/tests/tools/commands/test_interpreter_command.py
--- a/tests/tools/commands/test_interpreter_command.py
+++ b/tests/tools/commands/test_interpreter_command.py
@@ -11,7 +11,7 @@
 from pex.common import safe_mkdtemp
 from pex.interpreter import PythonInterpreter
 from pex.pex_builder import PEXBuilder
-from pex.testing import PY37, PY38, ensure_python_interpreter
+from pex.testing import PY37, PY310, ensure_python_interpreter
 from pex.tools.commands.virtualenv import Virtualenv
 from pex.typing import TYPE_CHECKING
 
@@ -29,9 +29,9 @@ def python37():
 
 
 @pytest.fixture(scope="module")
-def python38():
+def python310():
     # type: () -> PythonInterpreter
-    return PythonInterpreter.from_binary(ensure_python_interpreter(PY38))
+    return PythonInterpreter.from_binary(ensure_python_interpreter(PY310))
 
 
 @attr.s(frozen=True)
@@ -82,10 +82,10 @@ def run(
 @pytest.fixture(scope="module")
 def interpreter_tool(
     python37,  # type: PythonInterpreter
-    python38,  # type: PythonInterpreter
+    python310,  # type: PythonInterpreter
 ):
     # type: (...) -> InterpreterTool
-    return InterpreterTool.create(python37, python38)
+    return InterpreterTool.create(python37, python310)
 
 
 def expected_basic(interpreter):
@@ -104,13 +104,13 @@ def test_basic(
 
 def test_basic_all(
     python37,  # type: PythonInterpreter
-    python38,  # type: PythonInterpreter
+    python310,  # type: PythonInterpreter
     interpreter_tool,  # type: InterpreterTool
 ):
     # type: (...) -> None
     output = interpreter_tool.run("-a")
     assert [
-        expected_basic(interpreter) for interpreter in (python37, python38)
+        expected_basic(interpreter) for interpreter in (python37, python310)
     ] == output.splitlines()
 
 
@@ -134,12 +134,12 @@ def test_verbose(
 
 def test_verbose_all(
     python37,  # type: PythonInterpreter
-    python38,  # type: PythonInterpreter
+    python310,  # type: PythonInterpreter
     interpreter_tool,  # type: InterpreterTool
 ):
     # type: (...) -> None
     output = interpreter_tool.run("-va")
-    assert [expected_verbose(interpreter) for interpreter in (python37, python38)] == [
+    assert [expected_verbose(interpreter) for interpreter in (python37, python310)] == [
         json.loads(line) for line in output.splitlines()
     ]
 
@@ -172,10 +172,10 @@ def test_verbose_verbose_verbose(
 
 
 def test_verbose_verbose_verbose_venv(
-    python38,  # type: PythonInterpreter
+    python310,  # type: PythonInterpreter
 ):
     # type: (...) -> None
-    venv = Virtualenv.create(venv_dir=safe_mkdtemp(), interpreter=python38, force=True)
+    venv = Virtualenv.create(venv_dir=safe_mkdtemp(), interpreter=python310, force=True)
     assert venv.interpreter.is_venv
 
     # N.B.: Non-venv-mode PEXes always escape venvs to prevent `sys.path` contamination unless
@@ -186,6 +186,6 @@ def test_verbose_verbose_verbose_venv(
     expected.update(
         env_markers=venv.interpreter.identity.env_markers,
         venv=True,
-        base_interpreter=python38.binary,
+        base_interpreter=python310.binary,
     )
     assert expected == json.loads(output)
diff --git a/tests/tools/commands/test_repository.py b/tests/tools/commands/test_repository.py
--- a/tests/tools/commands/test_repository.py
+++ b/tests/tools/commands/test_repository.py
@@ -14,7 +14,7 @@
 import pytest
 
 from pex.common import DETERMINISTIC_DATETIME, open_zip, safe_open, temporary_dir
-from pex.testing import PY38, ensure_python_venv, run_command_with_jitter, run_pex_command
+from pex.testing import PY310, ensure_python_venv, run_command_with_jitter, run_pex_command
 from pex.third_party.packaging.specifiers import SpecifierSet
 from pex.third_party.pkg_resources import Distribution, Requirement
 from pex.typing import TYPE_CHECKING
@@ -244,7 +244,7 @@ def test_extract_lifecycle(pex, pex_tools_env, tmpdir):
     )
     result.assert_success()
 
-    _, pip = ensure_python_venv(PY38)
+    _, pip = ensure_python_venv(PY310)
     subprocess.check_call(
         args=[pip, "install", "--no-index", "--find-links", find_links_url, "example"]
     )
diff --git a/tests/tools/commands/test_venv.py b/tests/tools/commands/test_venv.py
--- a/tests/tools/commands/test_venv.py
+++ b/tests/tools/commands/test_venv.py
@@ -14,15 +14,14 @@
 from textwrap import dedent
 
 import pytest
-from test_execution_mode import ExecutionMode
 
-from pex.common import safe_copy, safe_mkdtemp, safe_open, safe_rmtree, temporary_dir, touch
+from pex.common import safe_mkdtemp, safe_open, temporary_dir, touch
 from pex.compatibility import PY2
 from pex.executor import Executor
 from pex.interpreter import PythonInterpreter
 from pex.layout import Layout
 from pex.pex_builder import CopyMode, PEXBuilder
-from pex.testing import IS_PYPY, PY38, PY_VER, ensure_python_interpreter, run_pex_command
+from pex.testing import IS_PYPY, PY310, PY_VER, ensure_python_interpreter, run_pex_command
 from pex.tools.commands.virtualenv import Virtualenv
 from pex.typing import TYPE_CHECKING, cast
 from pex.util import named_temporary_file
@@ -428,23 +427,25 @@ def assert_venv_process(
 def test_venv_copies(tmpdir):
     # type: (Any) -> None
 
-    python38 = ensure_python_interpreter(PY38)
+    python310 = ensure_python_interpreter(PY310)
 
     pex_file = os.path.join(str(tmpdir), "venv.pex")
-    result = run_pex_command(args=["-o", pex_file, "--include-tools"], python=python38)
+    result = run_pex_command(args=["-o", pex_file, "--include-tools"], python=python310)
     result.assert_success()
 
     PEX_TOOLS = make_env(PEX_TOOLS=1)
 
     venv_symlinks = os.path.join(str(tmpdir), "venv.symlinks")
-    subprocess.check_call(args=[python38, pex_file, "venv", venv_symlinks], env=PEX_TOOLS)
+    subprocess.check_call(args=[python310, pex_file, "venv", venv_symlinks], env=PEX_TOOLS)
     venv_symlinks_interpreter = PythonInterpreter.from_binary(
         os.path.join(venv_symlinks, "bin", "python")
     )
     assert os.path.islink(venv_symlinks_interpreter.binary)
 
     venv_copies = os.path.join(str(tmpdir), "venv.copies")
-    subprocess.check_call(args=[python38, pex_file, "venv", "--copies", venv_copies], env=PEX_TOOLS)
+    subprocess.check_call(
+        args=[python310, pex_file, "venv", "--copies", venv_copies], env=PEX_TOOLS
+    )
     venv_copies_interpreter = PythonInterpreter.from_binary(
         os.path.join(venv_copies, "bin", "python")
     )