Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Upgrade vendored truststore to 0.9.0 #12662

Merged
merged 1 commit into from May 3, 2024

Conversation

sethmlarson
Copy link
Contributor

Truststore 0.9.0 is now available with fixes and support for Python 3.13: https://github.com/sethmlarson/truststore/releases/tag/v0.9.0

@pradyunsg
Copy link
Member

FYI: I cancelled the CI run, since it's gonna fail (since main is failing).

@pfmoore pfmoore added this to the 24.1 milestone May 3, 2024
@pradyunsg pradyunsg merged commit 8547b52 into pypa:main May 3, 2024
24 checks passed
@notatallshaw
Copy link
Contributor

pradyunsg added a commit that referenced this pull request May 3, 2024
@notatallshaw
Copy link
Contributor

Reproduced locally on Windows:

> nox -s test-3.13 -- -k test_trustore_can_install
nox > Running session test-3.13
nox > Re-using existing virtual environment at .nox\test-3-13.    
nox > Re-using existing common-wheels at tests/data/common_wheels.
nox > python -m pip install build
nox > python -I -m build --sdist --outdir 'C:\Users\damia\OneDrive\Documents\opensource_contributions\pip\.nox\test-3-13\sdist'
nox > python tools/protected_pip.py install 'C:\Users\damia\OneDrive\Documents\opensource_contributions\pip\.nox\test-3-13\sdist\pip-24.1.dev0.tar.gz'
nox > python tools/protected_pip.py install -r tests/requirements.txt
nox > pytest -k test_trustore_can_install
================================================================================================================ test session starts ================================================================================================================
platform win32 -- Python 3.13.0a6, pytest-8.1.1, pluggy-1.4.0
rootdir: C:\Users\damia\OneDrive\Documents\opensource_contributions\pip
configfile: pyproject.toml
plugins: cov-5.0.0, rerunfailures-14.0, xdist-3.5.0
collected 2563 items / 2561 deselected / 2 selected

tests\functional\test_truststore.py FF                                                                                                                                                                                                         [100%]

===================================================================================================================== FAILURES ====================================================================================================================== 
__________________________________________________________________________________________________________ test_trustore_can_install[PyPI] __________________________________________________________________________________________________________ 

script = <tests.lib.PipTestEnvironment object at 0x000001DB3F6A7CB0>, pip = <function pip.<locals>.pip at 0x000001DB3F2B2480>, package = 'INITools'

    @pytest.mark.skipif(sys.version_info < (3, 10), reason="3.10+ required for truststore")
    @pytest.mark.network
    @pytest.mark.parametrize(
        "package",
        [
            "INITools",
            "https://github.com/pypa/pip-test-package/archive/refs/heads/master.zip",
        ],
        ids=["PyPI", "GitHub"],
    )
    def test_trustore_can_install(
        script: PipTestEnvironment,
        pip: PipRunner,
        package: str,
    ) -> None:
>       result = pip("install", package)

tests\functional\test_truststore.py:45:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
tests\functional\test_truststore.py:14: in pip
    return script.pip(*args, "--use-feature=truststore", **kwargs)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <tests.lib.PipTestEnvironment object at 0x000001DB3F6A7CB0>, cwd = WindowsPath('C:/Users/damia/AppData/Local/Temp/pytest-of-damia/pytest-0/test_trustore_can_install_PyPI0/workspace/scratch'), allow_stderr_error = False
allow_stderr_warning = False, allow_error = False, args = ('python', '-m', 'pip', 'install', 'INITools', '--use-feature=truststore'), kw = {'expect_stderr': True}, expect_error = None

    def run(
        self,
        *args: str,
        cwd: Optional[StrPath] = None,
        allow_stderr_error: Optional[bool] = None,
        allow_stderr_warning: Optional[bool] = None,
        allow_error: bool = False,
        **kw: Any,
    ) -> TestPipResult:
        """
        :param allow_stderr_error: whether a logged error is allowed in
            stderr.  Passing True for this argument implies
            `allow_stderr_warning` since warnings are weaker than errors.
        :param allow_stderr_warning: whether a logged warning (or
            deprecation message) is allowed in stderr.
        :param allow_error: if True (default is False) does not raise
            exception when the command exit value is non-zero.  Implies
            expect_error, but in contrast to expect_error will not assert
            that the exit value is zero.
        :param expect_error: if False (the default), asserts that the command
            exits with 0.  Otherwise, asserts that the command exits with a
            non-zero exit code.  Passing True also implies allow_stderr_error
            and allow_stderr_warning.
        :param expect_stderr: whether to allow warnings in stderr (equivalent
            to `allow_stderr_warning`).  This argument is an abbreviated
            version of `allow_stderr_warning` and is also kept for backwards
            compatibility.
        """
        if self.verbose:
            print(f">> running {args} {kw}")

        cwd = cwd or self.cwd
        if sys.platform == "win32":
            # Partial fix for ScriptTest.run using `shell=True` on Windows.
            args = tuple(re.sub("([&|<>^])", r"^\1", str(a)) for a in args)

        if allow_error:
            kw["expect_error"] = True

        # Propagate default values.
        expect_error = kw.get("expect_error")
        if expect_error:
            # Then default to allowing logged errors.
            if allow_stderr_error is not None and not allow_stderr_error:
                raise RuntimeError(
                    "cannot pass allow_stderr_error=False with expect_error=True"
                )
            allow_stderr_error = True

        elif kw.get("expect_stderr"):
            # Then default to allowing logged warnings.
            if allow_stderr_warning is not None and not allow_stderr_warning:
                raise RuntimeError(
                    "cannot pass allow_stderr_warning=False with expect_stderr=True"
                )
            allow_stderr_warning = True

        if allow_stderr_error:
            if allow_stderr_warning is not None and not allow_stderr_warning:
                raise RuntimeError(
                    "cannot pass allow_stderr_warning=False with "
                    "allow_stderr_error=True"
                )

        # Default values if not set.
        if allow_stderr_error is None:
            allow_stderr_error = False
        if allow_stderr_warning is None:
            allow_stderr_warning = allow_stderr_error

        # Pass expect_stderr=True to allow any stderr.  We do this because
        # we do our checking of stderr further on in check_stderr().
        kw["expect_stderr"] = True
        # Ignore linter check
        # B026 Star-arg unpacking after a keyword argument is strongly discouraged
>       result = super().run(cwd=cwd, *args, **kw)  # noqa: B026
E       AssertionError: Script returned code: 2

tests\lib\__init__.py:701: AssertionError
--------------------------------------------------------------------------------------------------------------- Captured stdout setup --------------------------------------------------------------------------------------------------------------- 
Obtaining file:///C:/Users/damia/AppData/Local/Temp/pytest-of-damia/pytest-0/pip0/pip
  Installing build dependencies: started
  Installing build dependencies: finished with status 'done'
  Checking if build backend supports build_editable: started
  Checking if build backend supports build_editable: finished with status 'done'
  Getting requirements to build editable: started
  Getting requirements to build editable: finished with status 'done'
  Preparing editable metadata (pyproject.toml): started
  Preparing editable metadata (pyproject.toml): finished with status 'done'
Building wheels for collected packages: pip
  Building editable for pip (pyproject.toml): started
  Building editable for pip (pyproject.toml): finished with status 'done'
  Created wheel for pip: filename=pip-24.1.dev0-0.editable-py3-none-any.whl size=9618 sha256=f2445c9e686321ae5b1921721a00b651673de600bcb82f87510d0cf593764908
  Stored in directory: C:\Users\damia\AppData\Local\Temp\pip-ephem-wheel-cache-on9rc6dx\wheels\e2\6a\c6\49787e61add08a828724881079e7f0808a8b7c138592af1a36
Successfully built pip
Installing collected packages: pip
Successfully installed pip-24.1.dev0
--------------------------------------------------------------------------------------------------------------- Captured stdout call ---------------------------------------------------------------------------------------------------------------- 
Script result: python -m pip install INITools --use-feature=truststore
  return code: 2
-- stderr: --------------------
ERROR: Exception:
Traceback (most recent call last):
  File "C:\Users\damia\AppData\Local\Temp\pytest-of-damia\pytest-0\pip0\pip\src\pip\_internal\cli\base_command.py", line 180, in exc_logging_wrapper
    status = run_func(*args)
             ~~~~~~~~^^^^^^^
  File "C:\Users\damia\AppData\Local\Temp\pytest-of-damia\pytest-0\pip0\pip\src\pip\_internal\cli\req_command.py", line 247, in wrapper
    return func(self, options, args)
           ~~~~^^^^^^^^^^^^^^^^^^^^^
  File "C:\Users\damia\AppData\Local\Temp\pytest-of-damia\pytest-0\pip0\pip\src\pip\_internal\commands\install.py", line 377, in run
    requirement_set = resolver.resolve(
                      ~~~~~~~~~~~~~~~~^
        reqs, check_supported_wheels=not options.target_dir
        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    )
    ^
  File "C:\Users\damia\AppData\Local\Temp\pytest-of-damia\pytest-0\pip0\pip\src\pip\_internal\resolution\resolvelib\resolver.py", line 95, in resolve
    result = self._result = resolver.resolve(
                            ~~~~~~~~~~~~~~~~^
        collected.requirements, max_rounds=limit_how_complex_resolution_can_be
        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    )
    ^
  File "C:\Users\damia\AppData\Local\Temp\pytest-of-damia\pytest-0\pip0\pip\src\pip\_vendor\resolvelib\resolvers.py", line 546, in resolve
    state = resolution.resolve(requirements, max_rounds=max_rounds)
            ~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Users\damia\AppData\Local\Temp\pytest-of-damia\pytest-0\pip0\pip\src\pip\_vendor\resolvelib\resolvers.py", line 397, in resolve
    self._add_to_criteria(self.state.criteria, r, parent=None)
    ~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Users\damia\AppData\Local\Temp\pytest-of-damia\pytest-0\pip0\pip\src\pip\_vendor\resolvelib\resolvers.py", line 173, in _add_to_criteria
    if not criterion.candidates:
           ^^^^^^^^^^^^^^^^^^^^
  File "C:\Users\damia\AppData\Local\Temp\pytest-of-damia\pytest-0\pip0\pip\src\pip\_vendor\resolvelib\structs.py", line 156, in __bool__
    return bool(self._sequence)
           ~~~~^^^^^^^^^^^^^^^^
  File "C:\Users\damia\AppData\Local\Temp\pytest-of-damia\pytest-0\pip0\pip\src\pip\_internal\resolution\resolvelib\found_candidates.py", line 155, in __bool__
    return any(self)
           ~~~^^^^^^
  File "C:\Users\damia\AppData\Local\Temp\pytest-of-damia\pytest-0\pip0\pip\src\pip\_internal\resolution\resolvelib\found_candidates.py", line 143, in <genexpr>
    return (c for c in iterator if id(c) not in self._incompatible_ids)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Users\damia\AppData\Local\Temp\pytest-of-damia\pytest-0\pip0\pip\src\pip\_internal\resolution\resolvelib\found_candidates.py", line 44, in _iter_built
    for version, func in infos:
    ...<6 lines>...
        versions_found.add(version)
  File "C:\Users\damia\AppData\Local\Temp\pytest-of-damia\pytest-0\pip0\pip\src\pip\_internal\resolution\resolvelib\factory.py", line 298, in iter_index_candidate_infos
    result = self._finder.find_best_candidate(
             ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^
        project_name=name,
        ^^^^^^^^^^^^^^^^^^
        specifier=specifier,
        ^^^^^^^^^^^^^^^^^^^^
        hashes=hashes,
        ^^^^^^^^^^^^^^
    )
    ^
  File "C:\Users\damia\AppData\Local\Temp\pytest-of-damia\pytest-0\pip0\pip\src\pip\_internal\index\package_finder.py", line 881, in find_best_candidate
    candidates = self.find_all_candidates(project_name)
                 ~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^
  File "C:\Users\damia\AppData\Local\Temp\pytest-of-damia\pytest-0\pip0\pip\src\pip\_internal\index\package_finder.py", line 822, in find_all_candidates
    page_candidates = list(page_candidates_it)
                      ~~~~^^^^^^^^^^^^^^^^^^^^
  File "C:\Users\damia\AppData\Local\Temp\pytest-of-damia\pytest-0\pip0\pip\src\pip\_internal\index\sources.py", line 194, in page_candidates
    yield from self._candidates_from_page(self._link)
               ~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^
  File "C:\Users\damia\AppData\Local\Temp\pytest-of-damia\pytest-0\pip0\pip\src\pip\_internal\index\package_finder.py", line 782, in process_project_url
    index_response = self._link_collector.fetch_response(project_url)
                     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^
  File "C:\Users\damia\AppData\Local\Temp\pytest-of-damia\pytest-0\pip0\pip\src\pip\_internal\index\collector.py", line 448, in fetch_response
    return _get_index_content(location, session=self.session)
           ~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Users\damia\AppData\Local\Temp\pytest-of-damia\pytest-0\pip0\pip\src\pip\_internal\index\collector.py", line 352, in _get_index_content
    resp = _get_simple_response(url, session=session)
           ~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Users\damia\AppData\Local\Temp\pytest-of-damia\pytest-0\pip0\pip\src\pip\_internal\index\collector.py", line 131, in _get_simple_response
    resp = session.get(
           ~~~~~~~~~~~^
        url,
        ^^^^
    ...<22 lines>...
        },
        ^^
    )
    ^
  File "C:\Users\damia\AppData\Local\Temp\pytest-of-damia\pytest-0\pip0\pip\src\pip\_vendor\requests\sessions.py", line 602, in get
    return self.request("GET", url, **kwargs)
           ~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Users\damia\AppData\Local\Temp\pytest-of-damia\pytest-0\pip0\pip\src\pip\_internal\network\session.py", line 522, in request
    return super().request(method, url, *args, **kwargs)
           ~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Users\damia\AppData\Local\Temp\pytest-of-damia\pytest-0\pip0\pip\src\pip\_vendor\requests\sessions.py", line 589, in request
    resp = self.send(prep, **send_kwargs)
           ~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^
  File "C:\Users\damia\AppData\Local\Temp\pytest-of-damia\pytest-0\pip0\pip\src\pip\_vendor\requests\sessions.py", line 703, in send
    r = adapter.send(request, **kwargs)
        ~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^
  File "C:\Users\damia\AppData\Local\Temp\pytest-of-damia\pytest-0\pip0\pip\src\pip\_vendor\cachecontrol\adapter.py", line 76, in send
    resp = super().send(request, stream, timeout, verify, cert, proxies)
           ~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Users\damia\AppData\Local\Temp\pytest-of-damia\pytest-0\pip0\pip\src\pip\_vendor\requests\adapters.py", line 486, in send
    resp = conn.urlopen(
           ~~~~~~~~~~~~^
        method=request.method,
        ^^^^^^^^^^^^^^^^^^^^^^
    ...<9 lines>...
        chunked=chunked,
        ^^^^^^^^^^^^^^^^
    )
    ^
  File "C:\Users\damia\AppData\Local\Temp\pytest-of-damia\pytest-0\pip0\pip\src\pip\_vendor\urllib3\connectionpool.py", line 715, in urlopen
    httplib_response = self._make_request(
                       ~~~~~~~~~~~~~~~~~~^
        conn,
        ^^^^^
    ...<5 lines>...
        chunked=chunked,
        ^^^^^^^^^^^^^^^^
    )
    ^
  File "C:\Users\damia\AppData\Local\Temp\pytest-of-damia\pytest-0\pip0\pip\src\pip\_vendor\urllib3\connectionpool.py", line 404, in _make_request
    self._validate_conn(conn)
    ~~~~~~~~~~~~~~~~~~~^^^^^^
  File "C:\Users\damia\AppData\Local\Temp\pytest-of-damia\pytest-0\pip0\pip\src\pip\_vendor\urllib3\connectionpool.py", line 1058, in _validate_conn
    conn.connect()
    ~~~~~~~~~~~~^^
  File "C:\Users\damia\AppData\Local\Temp\pytest-of-damia\pytest-0\pip0\pip\src\pip\_vendor\urllib3\connection.py", line 419, in connect
    self.sock = ssl_wrap_socket(
                ~~~~~~~~~~~~~~~^
        sock=conn,
        ^^^^^^^^^^
    ...<8 lines>...
        tls_in_tls=tls_in_tls,
        ^^^^^^^^^^^^^^^^^^^^^^
    )
    ^
  File "C:\Users\damia\AppData\Local\Temp\pytest-of-damia\pytest-0\pip0\pip\src\pip\_vendor\urllib3\util\ssl_.py", line 449, in ssl_wrap_socket
    ssl_sock = _ssl_wrap_socket_impl(
               ~~~~~~~~~~~~~~~~~~~~~^
        sock, context, tls_in_tls, server_hostname=server_hostname
        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    )
    ^
  File "C:\Users\damia\AppData\Local\Temp\pytest-of-damia\pytest-0\pip0\pip\src\pip\_vendor\urllib3\util\ssl_.py", line 493, in _ssl_wrap_socket_impl
    return ssl_context.wrap_socket(sock, server_hostname=server_hostname)
           ~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Users\damia\AppData\Local\Temp\pytest-of-damia\pytest-0\pip0\pip\src\pip\_vendor\truststore\_api.py", line 103, in wrap_socket
    _verify_peercerts(ssl_sock, server_hostname=server_hostname)
    ~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Users\damia\AppData\Local\Temp\pytest-of-damia\pytest-0\pip0\pip\src\pip\_vendor\truststore\_api.py", line 307, in _verify_peercerts
    _verify_peercerts_impl(
    ~~~~~~~~~~~~~~~~~~~~~~^
        sock_or_sslobj.context, cert_bytes, server_hostname=server_hostname
        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    )
    ^
  File "C:\Users\damia\AppData\Local\Temp\pytest-of-damia\pytest-0\pip0\pip\src\pip\_vendor\truststore\_windows.py", line 343, in _verify_peercerts_impl
    len(cert_bytes),
    ~~~^^^^^^^^^^^^
TypeError: object of type '_ssl.Certificate' has no len()

_________________________________________________________________________________________________________ test_trustore_can_install[GitHub] _________________________________________________________________________________________________________ 

script = <tests.lib.PipTestEnvironment object at 0x000001DB3F517750>, pip = <function pip.<locals>.pip at 0x000001DB40A9D300>, package = 'https://github.com/pypa/pip-test-package/archive/refs/heads/master.zip'

    @pytest.mark.skipif(sys.version_info < (3, 10), reason="3.10+ required for truststore")
    @pytest.mark.network
    @pytest.mark.parametrize(
        "package",
        [
            "INITools",
            "https://github.com/pypa/pip-test-package/archive/refs/heads/master.zip",
        ],
        ids=["PyPI", "GitHub"],
    )
    def test_trustore_can_install(
        script: PipTestEnvironment,
        pip: PipRunner,
        package: str,
    ) -> None:
>       result = pip("install", package)

tests\functional\test_truststore.py:45:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
tests\functional\test_truststore.py:14: in pip
    return script.pip(*args, "--use-feature=truststore", **kwargs)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <tests.lib.PipTestEnvironment object at 0x000001DB3F517750>, cwd = WindowsPath('C:/Users/damia/AppData/Local/Temp/pytest-of-damia/pytest-0/test_trustore_can_install_GitH0/workspace/scratch'), allow_stderr_error = False
allow_stderr_warning = False, allow_error = False, args = ('python', '-m', 'pip', 'install', 'https://github.com/pypa/pip-test-package/archive/refs/heads/master.zip', '--use-feature=truststore'), kw = {'expect_stderr': True}, expect_error = None 

    def run(
        self,
        *args: str,
        cwd: Optional[StrPath] = None,
        allow_stderr_error: Optional[bool] = None,
        allow_stderr_warning: Optional[bool] = None,
        allow_error: bool = False,
        **kw: Any,
    ) -> TestPipResult:
        """
        :param allow_stderr_error: whether a logged error is allowed in
            stderr.  Passing True for this argument implies
            `allow_stderr_warning` since warnings are weaker than errors.
        :param allow_stderr_warning: whether a logged warning (or
            deprecation message) is allowed in stderr.
        :param allow_error: if True (default is False) does not raise
            exception when the command exit value is non-zero.  Implies
            expect_error, but in contrast to expect_error will not assert
            that the exit value is zero.
        :param expect_error: if False (the default), asserts that the command
            exits with 0.  Otherwise, asserts that the command exits with a
            non-zero exit code.  Passing True also implies allow_stderr_error
            and allow_stderr_warning.
        :param expect_stderr: whether to allow warnings in stderr (equivalent
            to `allow_stderr_warning`).  This argument is an abbreviated
            version of `allow_stderr_warning` and is also kept for backwards
            compatibility.
        """
        if self.verbose:
            print(f">> running {args} {kw}")

        cwd = cwd or self.cwd
        if sys.platform == "win32":
            # Partial fix for ScriptTest.run using `shell=True` on Windows.
            args = tuple(re.sub("([&|<>^])", r"^\1", str(a)) for a in args)

        if allow_error:
            kw["expect_error"] = True

        # Propagate default values.
        expect_error = kw.get("expect_error")
        if expect_error:
            # Then default to allowing logged errors.
            if allow_stderr_error is not None and not allow_stderr_error:
                raise RuntimeError(
                    "cannot pass allow_stderr_error=False with expect_error=True"
                )
            allow_stderr_error = True

        elif kw.get("expect_stderr"):
            # Then default to allowing logged warnings.
            if allow_stderr_warning is not None and not allow_stderr_warning:
                raise RuntimeError(
                    "cannot pass allow_stderr_warning=False with expect_stderr=True"
                )
            allow_stderr_warning = True

        if allow_stderr_error:
            if allow_stderr_warning is not None and not allow_stderr_warning:
                raise RuntimeError(
                    "cannot pass allow_stderr_warning=False with "
                    "allow_stderr_error=True"
                )

        # Default values if not set.
        if allow_stderr_error is None:
            allow_stderr_error = False
        if allow_stderr_warning is None:
            allow_stderr_warning = allow_stderr_error

        # Pass expect_stderr=True to allow any stderr.  We do this because
        # we do our checking of stderr further on in check_stderr().
        kw["expect_stderr"] = True
        # Ignore linter check
        # B026 Star-arg unpacking after a keyword argument is strongly discouraged
>       result = super().run(cwd=cwd, *args, **kw)  # noqa: B026
E       AssertionError: Script returned code: 2

tests\lib\__init__.py:701: AssertionError
--------------------------------------------------------------------------------------------------------------- Captured stdout call ---------------------------------------------------------------------------------------------------------------- 
Script result: python -m pip install https://github.com/pypa/pip-test-package/archive/refs/heads/master.zip --use-feature=truststore
  return code: 2
-- stderr: --------------------
ERROR: Exception:
Traceback (most recent call last):
  File "C:\Users\damia\AppData\Local\Temp\pytest-of-damia\pytest-0\pip0\pip\src\pip\_internal\cli\base_command.py", line 180, in exc_logging_wrapper
    status = run_func(*args)
             ~~~~~~~~^^^^^^^
  File "C:\Users\damia\AppData\Local\Temp\pytest-of-damia\pytest-0\pip0\pip\src\pip\_internal\cli\req_command.py", line 247, in wrapper
    return func(self, options, args)
           ~~~~^^^^^^^^^^^^^^^^^^^^^
  File "C:\Users\damia\AppData\Local\Temp\pytest-of-damia\pytest-0\pip0\pip\src\pip\_internal\commands\install.py", line 377, in run
    requirement_set = resolver.resolve(
                      ~~~~~~~~~~~~~~~~^
        reqs, check_supported_wheels=not options.target_dir
        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    )
    ^
  File "C:\Users\damia\AppData\Local\Temp\pytest-of-damia\pytest-0\pip0\pip\src\pip\_internal\resolution\resolvelib\resolver.py", line 76, in resolve
    collected = self.factory.collect_root_requirements(root_reqs)
                ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^
  File "C:\Users\damia\AppData\Local\Temp\pytest-of-damia\pytest-0\pip0\pip\src\pip\_internal\resolution\resolvelib\factory.py", line 536, in collect_root_requirements
    reqs = list(
           ~~~~^
        self._make_requirements_from_install_req(
        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    ...<2 lines>...
        )
        ^
    )
    ^
  File "C:\Users\damia\AppData\Local\Temp\pytest-of-damia\pytest-0\pip0\pip\src\pip\_internal\resolution\resolvelib\factory.py", line 492, in _make_requirements_from_install_req
    cand = self._make_base_candidate_from_link(
           ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^
        ireq.link,
        ^^^^^^^^^^
    ...<2 lines>...
        version=None,
        ^^^^^^^^^^^^^
    )
    ^
  File "C:\Users\damia\AppData\Local\Temp\pytest-of-damia\pytest-0\pip0\pip\src\pip\_internal\resolution\resolvelib\factory.py", line 229, in _make_base_candidate_from_link
    self._link_candidate_cache[link] = LinkCandidate(
                                       ~~~~~~~~~~~~~^
        link,
        ^^^^^
    ...<3 lines>...
        version=version,
        ^^^^^^^^^^^^^^^^
    )
    ^
  File "C:\Users\damia\AppData\Local\Temp\pytest-of-damia\pytest-0\pip0\pip\src\pip\_internal\resolution\resolvelib\candidates.py", line 294, in __init__
    super().__init__(
    ~~~~~~~~~~~~~~~~^
        link=link,
        ^^^^^^^^^^
    ...<4 lines>...
        version=version,
        ^^^^^^^^^^^^^^^^
    )
    ^
  File "C:\Users\damia\AppData\Local\Temp\pytest-of-damia\pytest-0\pip0\pip\src\pip\_internal\resolution\resolvelib\candidates.py", line 156, in __init__
    self.dist = self._prepare()
                ~~~~~~~~~~~~~^^
  File "C:\Users\damia\AppData\Local\Temp\pytest-of-damia\pytest-0\pip0\pip\src\pip\_internal\resolution\resolvelib\candidates.py", line 226, in _prepare
    dist = self._prepare_distribution()
           ~~~~~~~~~~~~~~~~~~~~~~~~~~^^
  File "C:\Users\damia\AppData\Local\Temp\pytest-of-damia\pytest-0\pip0\pip\src\pip\_internal\resolution\resolvelib\candidates.py", line 305, in _prepare_distribution
    return preparer.prepare_linked_requirement(self._ireq, parallel_builds=True)
           ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Users\damia\AppData\Local\Temp\pytest-of-damia\pytest-0\pip0\pip\src\pip\_internal\operations\prepare.py", line 527, in prepare_linked_requirement
    return self._prepare_linked_requirement(req, parallel_builds)
           ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Users\damia\AppData\Local\Temp\pytest-of-damia\pytest-0\pip0\pip\src\pip\_internal\operations\prepare.py", line 598, in _prepare_linked_requirement
    local_file = unpack_url(
                 ~~~~~~~~~~^
        link,
        ^^^^^
    ...<4 lines>...
        hashes,
        ^^^^^^^
    )
    ^
  File "C:\Users\damia\AppData\Local\Temp\pytest-of-damia\pytest-0\pip0\pip\src\pip\_internal\operations\prepare.py", line 170, in unpack_url
    file = get_http_url(
           ~~~~~~~~~~~~^
        link,
        ^^^^^
    ...<2 lines>...
        hashes=hashes,
        ^^^^^^^^^^^^^^
    )
    ^
  File "C:\Users\damia\AppData\Local\Temp\pytest-of-damia\pytest-0\pip0\pip\src\pip\_internal\operations\prepare.py", line 111, in get_http_url
    from_path, content_type = download(link, temp_dir.path)
                              ~~~~~~~~^^^^^^^^^^^^^^^^^^^^^
  File "C:\Users\damia\AppData\Local\Temp\pytest-of-damia\pytest-0\pip0\pip\src\pip\_internal\network\download.py", line 135, in __call__
    resp = _http_get_download(self._session, link)
           ~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^
  File "C:\Users\damia\AppData\Local\Temp\pytest-of-damia\pytest-0\pip0\pip\src\pip\_internal\network\download.py", line 118, in _http_get_download
    resp = session.get(target_url, headers=HEADERS, stream=True)
           ~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Users\damia\AppData\Local\Temp\pytest-of-damia\pytest-0\pip0\pip\src\pip\_vendor\requests\sessions.py", line 602, in get
    return self.request("GET", url, **kwargs)
           ~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Users\damia\AppData\Local\Temp\pytest-of-damia\pytest-0\pip0\pip\src\pip\_internal\network\session.py", line 522, in request
    return super().request(method, url, *args, **kwargs)
           ~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Users\damia\AppData\Local\Temp\pytest-of-damia\pytest-0\pip0\pip\src\pip\_vendor\requests\sessions.py", line 589, in request
    resp = self.send(prep, **send_kwargs)
           ~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^
  File "C:\Users\damia\AppData\Local\Temp\pytest-of-damia\pytest-0\pip0\pip\src\pip\_vendor\requests\sessions.py", line 703, in send
    r = adapter.send(request, **kwargs)
        ~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^
  File "C:\Users\damia\AppData\Local\Temp\pytest-of-damia\pytest-0\pip0\pip\src\pip\_vendor\cachecontrol\adapter.py", line 76, in send
    resp = super().send(request, stream, timeout, verify, cert, proxies)
           ~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Users\damia\AppData\Local\Temp\pytest-of-damia\pytest-0\pip0\pip\src\pip\_vendor\requests\adapters.py", line 486, in send
    resp = conn.urlopen(
           ~~~~~~~~~~~~^
        method=request.method,
        ^^^^^^^^^^^^^^^^^^^^^^
    ...<9 lines>...
        chunked=chunked,
        ^^^^^^^^^^^^^^^^
    )
    ^
  File "C:\Users\damia\AppData\Local\Temp\pytest-of-damia\pytest-0\pip0\pip\src\pip\_vendor\urllib3\connectionpool.py", line 715, in urlopen
    httplib_response = self._make_request(
                       ~~~~~~~~~~~~~~~~~~^
        conn,
        ^^^^^
    ...<5 lines>...
        chunked=chunked,
        ^^^^^^^^^^^^^^^^
    )
    ^
  File "C:\Users\damia\AppData\Local\Temp\pytest-of-damia\pytest-0\pip0\pip\src\pip\_vendor\urllib3\connectionpool.py", line 404, in _make_request
    self._validate_conn(conn)
    ~~~~~~~~~~~~~~~~~~~^^^^^^
  File "C:\Users\damia\AppData\Local\Temp\pytest-of-damia\pytest-0\pip0\pip\src\pip\_vendor\urllib3\connectionpool.py", line 1058, in _validate_conn
    conn.connect()
    ~~~~~~~~~~~~^^
  File "C:\Users\damia\AppData\Local\Temp\pytest-of-damia\pytest-0\pip0\pip\src\pip\_vendor\urllib3\connection.py", line 419, in connect
    self.sock = ssl_wrap_socket(
                ~~~~~~~~~~~~~~~^
        sock=conn,
        ^^^^^^^^^^
    ...<8 lines>...
        tls_in_tls=tls_in_tls,
        ^^^^^^^^^^^^^^^^^^^^^^
    )
    ^
  File "C:\Users\damia\AppData\Local\Temp\pytest-of-damia\pytest-0\pip0\pip\src\pip\_vendor\urllib3\util\ssl_.py", line 449, in ssl_wrap_socket
    ssl_sock = _ssl_wrap_socket_impl(
               ~~~~~~~~~~~~~~~~~~~~~^
        sock, context, tls_in_tls, server_hostname=server_hostname
        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    )
    ^
  File "C:\Users\damia\AppData\Local\Temp\pytest-of-damia\pytest-0\pip0\pip\src\pip\_vendor\urllib3\util\ssl_.py", line 493, in _ssl_wrap_socket_impl
    return ssl_context.wrap_socket(sock, server_hostname=server_hostname)
           ~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Users\damia\AppData\Local\Temp\pytest-of-damia\pytest-0\pip0\pip\src\pip\_vendor\truststore\_api.py", line 103, in wrap_socket
    _verify_peercerts(ssl_sock, server_hostname=server_hostname)
    ~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Users\damia\AppData\Local\Temp\pytest-of-damia\pytest-0\pip0\pip\src\pip\_vendor\truststore\_api.py", line 307, in _verify_peercerts
    _verify_peercerts_impl(
    ~~~~~~~~~~~~~~~~~~~~~~^
        sock_or_sslobj.context, cert_bytes, server_hostname=server_hostname
        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    )
    ^
  File "C:\Users\damia\AppData\Local\Temp\pytest-of-damia\pytest-0\pip0\pip\src\pip\_vendor\truststore\_windows.py", line 343, in _verify_peercerts_impl
    len(cert_bytes),
    ~~~^^^^^^^^^^^^
TypeError: object of type '_ssl.Certificate' has no len()

-- stdout: --------------------
Collecting https://github.com/pypa/pip-test-package/archive/refs/heads/master.zip

================================================================================================================= warnings summary ================================================================================================================== 
.nox\test-3-13\Lib\site-packages\pip\_internal\metadata\pkg_resources.py:8
  C:\Users\damia\OneDrive\Documents\opensource_contributions\pip\.nox\test-3-13\Lib\site-packages\pip\_internal\metadata\pkg_resources.py:8: DeprecationWarning: pkg_resources is deprecated as an API. See https://setuptools.pypa.io/en/latest/pkg_resources.html
    from pip._vendor import pkg_resources

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
============================================================================================================== short test summary info ============================================================================================================== 
FAILED tests/functional/test_truststore.py::test_trustore_can_install[PyPI] - AssertionError: Script returned code: 2
FAILED tests/functional/test_truststore.py::test_trustore_can_install[GitHub] - AssertionError: Script returned code: 2
================================================================================================== 2 failed, 2561 deselected, 1 warning in 14.40s =================================================================================================== 
nox > Command pytest -k test_trustore_can_install failed with exit code 1
nox > Session test-3.13 failed.

@pradyunsg
Copy link
Member

Whee, thanks for flagging that @notatallshaw!

@sethmlarson
Copy link
Contributor Author

Ugh, sorry about that. I'll take a look and see why there's a difference between our CI and what you're seeing.

@pradyunsg
Copy link
Member

I've reverted this for now, since I think it's a bit more expedient to get the beta release out soon.

@notatallshaw
Copy link
Contributor

Ugh, sorry about that. I'll take a look and see why there's a difference between our CI and what you're seeing.

Looks like your CI is only testing Python 3.13 on Ubuntu: https://github.com/sethmlarson/truststore/blob/main/.github/workflows/ci.yml#L41

And as Murphy's law goes, it was Mac and Windows it failed on.

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

5 participants