Skip to content

Conversation

@jatalahd
Copy link
Contributor

@jatalahd jatalahd commented Nov 16, 2025

If pyopenssl adapter is used with password protected private key and the manual entry option was not given, only a fail due to invalid password. The password callback used to also be triggered in the case where the private_key_password was None.

What kind of change does this PR introduce?

  • 🐞 bug fix
  • 🐣 feature
  • 📋 docs update
  • 📋 tests/coverage improvement
  • 📋 refactoring
  • 💥 other

📋 What is the related issue number (starting with #)

Resolves #

What is the current behavior? (You can also link to an open issue here)

Manual prompt to enter private key password is not given in server startup
in the case where private_key_password = None. Functionality conflicts with
documentation and with builtin adapter

What is the new behavior (if this is a feature change)?

With this fix the functionality is matching the documentation
and both ssl adapters work in similar fashion.

📋 Other information:

📋 Contribution checklist:

(If you're a first-timer, check out
this guide on making great pull requests)

  • I wrote descriptive pull request text above
  • I think the code is well written
  • I wrote good commit messages
  • I have squashed related commits together after
    the changes have been approved
  • Unit tests for the changes exist
  • Integration tests for the changes exist (if applicable)
  • I used the same coding conventions as the rest of the project
  • The new code doesn't generate linter offenses
  • Documentation reflects the changes
  • The PR relates to only one subject with a clear title
    and description in grammatically correct, complete sentences

@codecov
Copy link

codecov bot commented Nov 16, 2025

❌ 1 Tests Failed:

Tests completed Failed Passed Skipped
245 1 244 11
View the full list of 3 ❄️ flaky test(s)
cheroot/test/test_conn.py::test_invalid_selected_connection

Flake rate in main: 4.55% (Passed 21 times, Failed 1 times)

Stack Traces | 1.13s run time
self = <cheroot.makefile.StreamWriter name=-1>

    #x1B[0m#x1B[94mdef#x1B[39;49;00m#x1B[90m #x1B[39;49;00m#x1B[92m__del__#x1B[39;49;00m(#x1B[96mself#x1B[39;49;00m):#x1B[90m#x1B[39;49;00m
    #x1B[90m    #x1B[39;49;00m#x1B[33m"""Destructor.  Calls close()."""#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
        #x1B[94mtry#x1B[39;49;00m:#x1B[90m#x1B[39;49;00m
            closed = #x1B[96mself#x1B[39;49;00m.closed#x1B[90m#x1B[39;49;00m
        #x1B[94mexcept#x1B[39;49;00m #x1B[96mAttributeError#x1B[39;49;00m:#x1B[90m#x1B[39;49;00m
            #x1B[90m# If getting closed fails, then the object is probably#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
            #x1B[90m# in an unusable state, so ignore.#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
            #x1B[94mreturn#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
    #x1B[90m#x1B[39;49;00m
        #x1B[94mif#x1B[39;49;00m closed:#x1B[90m#x1B[39;49;00m
            #x1B[94mreturn#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
    #x1B[90m#x1B[39;49;00m
        #x1B[94mif#x1B[39;49;00m dealloc_warn := #x1B[96mgetattr#x1B[39;49;00m(#x1B[96mself#x1B[39;49;00m, #x1B[33m"#x1B[39;49;00m#x1B[33m_dealloc_warn#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m, #x1B[94mNone#x1B[39;49;00m):#x1B[90m#x1B[39;49;00m
            dealloc_warn(#x1B[96mself#x1B[39;49;00m)#x1B[90m#x1B[39;49;00m
    #x1B[90m#x1B[39;49;00m
        #x1B[90m# If close() fails, the caller logs the exception with#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
        #x1B[90m# sys.unraisablehook. close() must be called at the end at __del__().#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
>       #x1B[96mself#x1B[39;49;00m.close()#x1B[90m#x1B[39;49;00m

closed     = False
dealloc_warn = <bound method _BufferedIOMixin._dealloc_warn of <cheroot.makefile.StreamWriter name=-1>>
self       = <cheroot.makefile.StreamWriter name=-1>

#x1B[1m#x1B[.../Library/Frameworks/Python.framework/Versions/3.14/lib/python3.14/_pyio.py#x1B[0m:415: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
#x1B[1m#x1B[.../Library/Frameworks/Python.framework/Versions/3.14/lib/python3.14/_pyio.py#x1B[0m:1312: in close
    #x1B[0m#x1B[96mself#x1B[39;49;00m.flush()#x1B[90m#x1B[39;49;00m
        self       = <cheroot.makefile.StreamWriter name=-1>
#x1B[1m#x1B[.../Library/Frameworks/Python.framework/Versions/3.14/lib/python3.14/_pyio.py#x1B[0m:1273: in flush
    #x1B[0m#x1B[96mself#x1B[39;49;00m._flush_unlocked()#x1B[90m#x1B[39;49;00m
        self       = <cheroot.makefile.StreamWriter name=-1>
#x1B[1m#x1B[31mcheroot/makefile.py#x1B[0m:32: in _flush_unlocked
    #x1B[0mn = #x1B[96mself#x1B[39;49;00m.raw.write(#x1B[96mbytes#x1B[39;49;00m(#x1B[96mself#x1B[39;49;00m._write_buf))#x1B[90m#x1B[39;49;00m
        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^#x1B[90m#x1B[39;49;00m
        self       = <cheroot.makefile.StreamWriter name=-1>
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <socket.SocketIO object at 0x1108e8f10>
b = b'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa...aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'

    #x1B[0m#x1B[94mdef#x1B[39;49;00m#x1B[90m #x1B[39;49;00m#x1B[92mwrite#x1B[39;49;00m(#x1B[96mself#x1B[39;49;00m, b):#x1B[90m#x1B[39;49;00m
    #x1B[90m    #x1B[39;49;00m#x1B[33m"""Write the given bytes or bytearray object *b* to the socket#x1B[39;49;00m
    #x1B[33m    and return the number of bytes written.  This can be less than#x1B[39;49;00m
    #x1B[33m    len(b) if not all data could be written.  If the socket is#x1B[39;49;00m
    #x1B[33m    non-blocking and no bytes could be written None is returned.#x1B[39;49;00m
    #x1B[33m    """#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
        #x1B[96mself#x1B[39;49;00m._checkClosed()#x1B[90m#x1B[39;49;00m
        #x1B[96mself#x1B[39;49;00m._checkWritable()#x1B[90m#x1B[39;49;00m
        #x1B[94mtry#x1B[39;49;00m:#x1B[90m#x1B[39;49;00m
>           #x1B[94mreturn#x1B[39;49;00m #x1B[96mself#x1B[39;49;00m._sock.send(b)#x1B[90m#x1B[39;49;00m
                   ^^^^^^^^^^^^^^^^^^#x1B[90m#x1B[39;49;00m
#x1B[1m#x1B[31mE           OSError: [Errno 9] Bad file descriptor#x1B[0m

b          = b'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa...aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'
self       = <socket.SocketIO object at 0x1108e8f10>

#x1B[1m#x1B[.../Library/Frameworks/Python.framework/Versions/3.14/lib/python3.14/socket.py#x1B[0m:743: OSError

#x1B[33mThe above exception was the direct cause of the following exception:#x1B[0m

cls = <class '_pytest.runner.CallInfo'>
func = <function call_and_report.<locals>.<lambda> at 0x110e207d0>
when = 'call'
reraise = (<class '_pytest.outcomes.Exit'>, <class 'KeyboardInterrupt'>)

    #x1B[0m#x1B[37m@classmethod#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
    #x1B[94mdef#x1B[39;49;00m#x1B[90m #x1B[39;49;00m#x1B[92mfrom_call#x1B[39;49;00m(#x1B[90m#x1B[39;49;00m
        #x1B[96mcls#x1B[39;49;00m,#x1B[90m#x1B[39;49;00m
        func: Callable[[], TResult],#x1B[90m#x1B[39;49;00m
        when: Literal[#x1B[33m"#x1B[39;49;00m#x1B[33mcollect#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m, #x1B[33m"#x1B[39;49;00m#x1B[33msetup#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m, #x1B[33m"#x1B[39;49;00m#x1B[33mcall#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m, #x1B[33m"#x1B[39;49;00m#x1B[33mteardown#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m],#x1B[90m#x1B[39;49;00m
        reraise: #x1B[96mtype#x1B[39;49;00m[#x1B[96mBaseException#x1B[39;49;00m] | #x1B[96mtuple#x1B[39;49;00m[#x1B[96mtype#x1B[39;49;00m[#x1B[96mBaseException#x1B[39;49;00m], ...] | #x1B[94mNone#x1B[39;49;00m = #x1B[94mNone#x1B[39;49;00m,#x1B[90m#x1B[39;49;00m
    ) -> CallInfo[TResult]:#x1B[90m#x1B[39;49;00m
    #x1B[90m    #x1B[39;49;00m#x1B[33m"""Call func, wrapping the result in a CallInfo.#x1B[39;49;00m
    #x1B[33m#x1B[39;49;00m
    #x1B[33m    :param func:#x1B[39;49;00m
    #x1B[33m        The function to call. Called without arguments.#x1B[39;49;00m
    #x1B[33m    :type func: Callable[[], _pytest.runner.TResult]#x1B[39;49;00m
    #x1B[33m    :param when:#x1B[39;49;00m
    #x1B[33m        The phase in which the function is called.#x1B[39;49;00m
    #x1B[33m    :param reraise:#x1B[39;49;00m
    #x1B[33m        Exception or exceptions that shall propagate if raised by the#x1B[39;49;00m
    #x1B[33m        function, instead of being wrapped in the CallInfo.#x1B[39;49;00m
    #x1B[33m    """#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
        excinfo = #x1B[94mNone#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
        instant = timing.Instant()#x1B[90m#x1B[39;49;00m
        #x1B[94mtry#x1B[39;49;00m:#x1B[90m#x1B[39;49;00m
>           result: TResult | #x1B[94mNone#x1B[39;49;00m = func()#x1B[90m#x1B[39;49;00m
                                     ^^^^^^#x1B[90m#x1B[39;49;00m

cls        = <class '_pytest.runner.CallInfo'>
duration   = Duration(start=Instant(time=1765282393.01585, perf_count=191.575828708), stop=Instant(time=1765282394.144162, perf_count=192.704144125))
excinfo    = <ExceptionInfo PytestUnraisableExceptionWarning('Exception ignored while calling deallocator <function IOBase.__del__ at 0x1075f3320>: None\n') tblen=19>
func       = <function call_and_report.<locals>.<lambda> at 0x110e207d0>
instant    = Instant(time=1765282393.01585, perf_count=191.575828708)
reraise    = (<class '_pytest.outcomes.Exit'>, <class 'KeyboardInterrupt'>)
result     = None
when       = 'call'

#x1B[1m#x1B[31m..../py/lib/python3.14....../site-packages/_pytest/runner.py#x1B[0m:353: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
#x1B[1m#x1B[31m..../py/lib/python3.14....../site-packages/_pytest/runner.py#x1B[0m:245: in <lambda>
    #x1B[0m#x1B[94mlambda#x1B[39;49;00m: runtest_hook(item=item, **kwds),#x1B[90m#x1B[39;49;00m
            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^#x1B[90m#x1B[39;49;00m
        item       = <Function test_invalid_selected_connection>
        kwds       = {}
        runtest_hook = <HookCaller 'pytest_runtest_call'>
#x1B[1m#x1B[31m..../py/lib/python3.14.../site-packages/pluggy/_hooks.py#x1B[0m:512: in __call__
    #x1B[0m#x1B[94mreturn#x1B[39;49;00m #x1B[96mself#x1B[39;49;00m._hookexec(#x1B[96mself#x1B[39;49;00m.name, #x1B[96mself#x1B[39;49;00m._hookimpls.copy(), kwargs, firstresult)#x1B[90m#x1B[39;49;00m
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^#x1B[90m#x1B[39;49;00m
        firstresult = False
        kwargs     = {'item': <Function test_invalid_selected_connection>}
        self       = <HookCaller 'pytest_runtest_call'>
#x1B[1m#x1B[31m..../py/lib/python3.14.../site-packages/pluggy/_manager.py#x1B[0m:120: in _hookexec
    #x1B[0m#x1B[94mreturn#x1B[39;49;00m #x1B[96mself#x1B[39;49;00m._inner_hookexec(hook_name, methods, kwargs, firstresult)#x1B[90m#x1B[39;49;00m
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^#x1B[90m#x1B[39;49;00m
        firstresult = False
        hook_name  = 'pytest_runtest_call'
        kwargs     = {'item': <Function test_invalid_selected_connection>}
        methods    = [<HookImpl plugin_name='threadexception', plugin=<module '_pytest.threadexception' from '.../work/cheroot/ch...pper name='/dev/null' mode='r' encoding='utf-8'>> _state='suspended' _in_suspended=False> _capture_fixture=None>>, ...]
        self       = <_pytest.config.PytestPluginManager object at 0x10663cd70>
#x1B[1m#x1B[31m..../py/lib/python3.14.../site-packages/_pytest/logging.py#x1B[0m:850: in pytest_runtest_call
    #x1B[0m#x1B[94myield#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
        item       = <Function test_invalid_selected_connection>
        self       = <_pytest.logging.LoggingPlugin object at 0x1079afcb0>
#x1B[1m#x1B[31m..../py/lib/python3.14....../site-packages/pluggy/_callers.py#x1B[0m:53: in run_old_style_hookwrapper
    #x1B[0m#x1B[94mreturn#x1B[39;49;00m result.get_result()#x1B[90m#x1B[39;49;00m
           ^^^^^^^^^^^^^^^^^^^#x1B[90m#x1B[39;49;00m
        args       = [<Function test_invalid_selected_connection>]
        hook_impl  = <HookImpl plugin_name='_cov', plugin=<pytest_cov.plugin.CovPlugin object at 0x107258830>>
        hook_name  = 'pytest_runtest_call'
        result     = <pluggy._result.Result object at 0x107a20f80>
        teardown   = <generator object CovPlugin.pytest_runtest_call at 0x110e164d0>
#x1B[1m#x1B[31m..../py/lib/python3.14....../site-packages/pluggy/_callers.py#x1B[0m:38: in run_old_style_hookwrapper
    #x1B[0mres = #x1B[94myield#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
          ^^^^^#x1B[90m#x1B[39;49;00m
        args       = [<Function test_invalid_selected_connection>]
        hook_impl  = <HookImpl plugin_name='_cov', plugin=<pytest_cov.plugin.CovPlugin object at 0x107258830>>
        hook_name  = 'pytest_runtest_call'
        result     = <pluggy._result.Result object at 0x107a20f80>
        teardown   = <generator object CovPlugin.pytest_runtest_call at 0x110e164d0>
#x1B[1m#x1B[31m..../py/lib/python3.14.../site-packages/_pytest/capture.py#x1B[0m:900: in pytest_runtest_call
    #x1B[0m#x1B[94mreturn#x1B[39;49;00m (#x1B[94myield#x1B[39;49;00m)#x1B[90m#x1B[39;49;00m
            ^^^^^#x1B[90m#x1B[39;49;00m
        item       = <Function test_invalid_selected_connection>
        self       = <CaptureManager _method='fd' _global_capturing=<MultiCapture out=<FDCapture 1 oldfd=7 _state='suspended' tmpfile=<Enco...xtIOWrapper name='/dev/null' mode='r' encoding='utf-8'>> _state='suspended' _in_suspended=False> _capture_fixture=None>
#x1B[1m#x1B[31m..../py/lib/python3.14.../site-packages/_pytest/skipping.py#x1B[0m:268: in pytest_runtest_call
    #x1B[0m#x1B[94mreturn#x1B[39;49;00m (#x1B[94myield#x1B[39;49;00m)#x1B[90m#x1B[39;49;00m
            ^^^^^#x1B[90m#x1B[39;49;00m
        item       = <Function test_invalid_selected_connection>
        xfailed    = None
#x1B[1m#x1B[31m..../py/lib/python3.14........./site-packages/_pytest/unraisableexception.py#x1B[0m:158: in pytest_runtest_call
    #x1B[0mcollect_unraisable(item.config)#x1B[90m#x1B[39;49;00m
        item       = <Function test_invalid_selected_connection>
#x1B[1m#x1B[31m..../py/lib/python3.14........./site-packages/_pytest/unraisableexception.py#x1B[0m:79: in collect_unraisable
    #x1B[0m#x1B[94mraise#x1B[39;49;00m errors[#x1B[94m0#x1B[39;49;00m]#x1B[90m#x1B[39;49;00m
        config     = <_pytest.config.Config object at 0x10663e270>
        msg        = 'Exception ignored while calling deallocator <function IOBase.__del__ at 0x1075f3320>: None\n\nTraceback (most recent ...43, in write\n    return self._sock.send(b)\n           ~~~~~~~~~~~~~~~^^^\nOSError: [Errno 9] Bad file descriptor\n\n'
        pop_unraisable = <built-in method pop of collections.deque object at 0x10798ec50>
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

config = <_pytest.config.Config object at 0x10663e270>

    #x1B[0m#x1B[94mdef#x1B[39;49;00m#x1B[90m #x1B[39;49;00m#x1B[92mcollect_unraisable#x1B[39;49;00m(config: Config) -> #x1B[94mNone#x1B[39;49;00m:#x1B[90m#x1B[39;49;00m
        pop_unraisable = config.stash[unraisable_exceptions].pop#x1B[90m#x1B[39;49;00m
        errors: #x1B[96mlist#x1B[39;49;00m[pytest.PytestUnraisableExceptionWarning | #x1B[96mRuntimeError#x1B[39;49;00m] = []#x1B[90m#x1B[39;49;00m
        meta = #x1B[94mNone#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
        hook_error = #x1B[94mNone#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
        #x1B[94mtry#x1B[39;49;00m:#x1B[90m#x1B[39;49;00m
            #x1B[94mwhile#x1B[39;49;00m #x1B[94mTrue#x1B[39;49;00m:#x1B[90m#x1B[39;49;00m
                #x1B[94mtry#x1B[39;49;00m:#x1B[90m#x1B[39;49;00m
                    meta = pop_unraisable()#x1B[90m#x1B[39;49;00m
                #x1B[94mexcept#x1B[39;49;00m #x1B[96mIndexError#x1B[39;49;00m:#x1B[90m#x1B[39;49;00m
                    #x1B[94mbreak#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
    #x1B[90m#x1B[39;49;00m
                #x1B[94mif#x1B[39;49;00m #x1B[96misinstance#x1B[39;49;00m(meta, #x1B[96mBaseException#x1B[39;49;00m):#x1B[90m#x1B[39;49;00m
                    hook_error = #x1B[96mRuntimeError#x1B[39;49;00m(#x1B[33m"#x1B[39;49;00m#x1B[33mFailed to process unraisable exception#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m)#x1B[90m#x1B[39;49;00m
                    hook_error.__cause__ = meta#x1B[90m#x1B[39;49;00m
                    errors.append(hook_error)#x1B[90m#x1B[39;49;00m
                    #x1B[94mcontinue#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
    #x1B[90m#x1B[39;49;00m
                msg = meta.msg#x1B[90m#x1B[39;49;00m
                #x1B[94mtry#x1B[39;49;00m:#x1B[90m#x1B[39;49;00m
>                   warnings.warn(pytest.PytestUnraisableExceptionWarning(msg))#x1B[90m#x1B[39;49;00m
#x1B[1m#x1B[31mE                   pytest.PytestUnraisableExceptionWarning: Exception ignored while calling deallocator <function IOBase.__del__ at 0x1075f3320>: None#x1B[0m

config     = <_pytest.config.Config object at 0x10663e270>
msg        = 'Exception ignored while calling deallocator <function IOBase.__del__ at 0x1075f3320>: None\n\nTraceback (most recent ...43, in write\n    return self._sock.send(b)\n           ~~~~~~~~~~~~~~~^^^\nOSError: [Errno 9] Bad file descriptor\n\n'
pop_unraisable = <built-in method pop of collections.deque object at 0x10798ec50>

#x1B[1m#x1B[31m..../py/lib/python3.14........./site-packages/_pytest/unraisableexception.py#x1B[0m:67: PytestUnraisableExceptionWarning
cheroot/test/test_conn.py::test_streaming_10[False]

Flake rate in main: 9.09% (Passed 10 times, Failed 1 times)

Stack Traces | 0.022s run time
self = <cheroot.makefile.StreamWriter name=-1>

    #x1B[0m#x1B[94mdef#x1B[39;49;00m#x1B[90m #x1B[39;49;00m#x1B[92m__del__#x1B[39;49;00m(#x1B[96mself#x1B[39;49;00m):#x1B[90m#x1B[39;49;00m
    #x1B[90m    #x1B[39;49;00m#x1B[33m"""Destructor.  Calls close()."""#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
        #x1B[94mtry#x1B[39;49;00m:#x1B[90m#x1B[39;49;00m
            closed = #x1B[96mself#x1B[39;49;00m.closed#x1B[90m#x1B[39;49;00m
        #x1B[94mexcept#x1B[39;49;00m #x1B[96mAttributeError#x1B[39;49;00m:#x1B[90m#x1B[39;49;00m
            #x1B[90m# If getting closed fails, then the object is probably#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
            #x1B[90m# in an unusable state, so ignore.#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
            #x1B[94mreturn#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
    #x1B[90m#x1B[39;49;00m
        #x1B[94mif#x1B[39;49;00m closed:#x1B[90m#x1B[39;49;00m
            #x1B[94mreturn#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
    #x1B[90m#x1B[39;49;00m
        #x1B[94mif#x1B[39;49;00m dealloc_warn := #x1B[96mgetattr#x1B[39;49;00m(#x1B[96mself#x1B[39;49;00m, #x1B[33m"#x1B[39;49;00m#x1B[33m_dealloc_warn#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m, #x1B[94mNone#x1B[39;49;00m):#x1B[90m#x1B[39;49;00m
            dealloc_warn(#x1B[96mself#x1B[39;49;00m)#x1B[90m#x1B[39;49;00m
    #x1B[90m#x1B[39;49;00m
        #x1B[90m# If close() fails, the caller logs the exception with#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
        #x1B[90m# sys.unraisablehook. close() must be called at the end at __del__().#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
>       #x1B[96mself#x1B[39;49;00m.close()#x1B[90m#x1B[39;49;00m

closed     = False
dealloc_warn = <bound method _BufferedIOMixin._dealloc_warn of <cheroot.makefile.StreamWriter name=-1>>
self       = <cheroot.makefile.StreamWriter name=-1>

#x1B[1m#x1B[.../Library/Frameworks/Python.framework/Versions/3.14/lib/python3.14/_pyio.py#x1B[0m:415: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
#x1B[1m#x1B[.../Library/Frameworks/Python.framework/Versions/3.14/lib/python3.14/_pyio.py#x1B[0m:1312: in close
    #x1B[0m#x1B[96mself#x1B[39;49;00m.flush()#x1B[90m#x1B[39;49;00m
        self       = <cheroot.makefile.StreamWriter name=-1>
#x1B[1m#x1B[.../Library/Frameworks/Python.framework/Versions/3.14/lib/python3.14/_pyio.py#x1B[0m:1273: in flush
    #x1B[0m#x1B[96mself#x1B[39;49;00m._flush_unlocked()#x1B[90m#x1B[39;49;00m
        self       = <cheroot.makefile.StreamWriter name=-1>
#x1B[1m#x1B[31mcheroot/makefile.py#x1B[0m:32: in _flush_unlocked
    #x1B[0mn = #x1B[96mself#x1B[39;49;00m.raw.write(#x1B[96mbytes#x1B[39;49;00m(#x1B[96mself#x1B[39;49;00m._write_buf))#x1B[90m#x1B[39;49;00m
        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^#x1B[90m#x1B[39;49;00m
        self       = <cheroot.makefile.StreamWriter name=-1>
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <socket.SocketIO object at 0x107a7f250>, b = b'1'

    #x1B[0m#x1B[94mdef#x1B[39;49;00m#x1B[90m #x1B[39;49;00m#x1B[92mwrite#x1B[39;49;00m(#x1B[96mself#x1B[39;49;00m, b):#x1B[90m#x1B[39;49;00m
    #x1B[90m    #x1B[39;49;00m#x1B[33m"""Write the given bytes or bytearray object *b* to the socket#x1B[39;49;00m
    #x1B[33m    and return the number of bytes written.  This can be less than#x1B[39;49;00m
    #x1B[33m    len(b) if not all data could be written.  If the socket is#x1B[39;49;00m
    #x1B[33m    non-blocking and no bytes could be written None is returned.#x1B[39;49;00m
    #x1B[33m    """#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
        #x1B[96mself#x1B[39;49;00m._checkClosed()#x1B[90m#x1B[39;49;00m
        #x1B[96mself#x1B[39;49;00m._checkWritable()#x1B[90m#x1B[39;49;00m
        #x1B[94mtry#x1B[39;49;00m:#x1B[90m#x1B[39;49;00m
>           #x1B[94mreturn#x1B[39;49;00m #x1B[96mself#x1B[39;49;00m._sock.send(b)#x1B[90m#x1B[39;49;00m
                   ^^^^^^^^^^^^^^^^^^#x1B[90m#x1B[39;49;00m
#x1B[1m#x1B[31mE           OSError: [Errno 9] Bad file descriptor#x1B[0m

b          = b'1'
self       = <socket.SocketIO object at 0x107a7f250>

#x1B[1m#x1B[.../Library/Frameworks/Python.framework/Versions/3.14/lib/python3.14/socket.py#x1B[0m:743: OSError

#x1B[33mThe above exception was the direct cause of the following exception:#x1B[0m

cls = <class '_pytest.runner.CallInfo'>
func = <function call_and_report.<locals>.<lambda> at 0x1080ec9e0>
when = 'call'
reraise = (<class '_pytest.outcomes.Exit'>, <class 'KeyboardInterrupt'>)

    #x1B[0m#x1B[37m@classmethod#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
    #x1B[94mdef#x1B[39;49;00m#x1B[90m #x1B[39;49;00m#x1B[92mfrom_call#x1B[39;49;00m(#x1B[90m#x1B[39;49;00m
        #x1B[96mcls#x1B[39;49;00m,#x1B[90m#x1B[39;49;00m
        func: Callable[[], TResult],#x1B[90m#x1B[39;49;00m
        when: Literal[#x1B[33m"#x1B[39;49;00m#x1B[33mcollect#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m, #x1B[33m"#x1B[39;49;00m#x1B[33msetup#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m, #x1B[33m"#x1B[39;49;00m#x1B[33mcall#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m, #x1B[33m"#x1B[39;49;00m#x1B[33mteardown#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m],#x1B[90m#x1B[39;49;00m
        reraise: #x1B[96mtype#x1B[39;49;00m[#x1B[96mBaseException#x1B[39;49;00m] | #x1B[96mtuple#x1B[39;49;00m[#x1B[96mtype#x1B[39;49;00m[#x1B[96mBaseException#x1B[39;49;00m], ...] | #x1B[94mNone#x1B[39;49;00m = #x1B[94mNone#x1B[39;49;00m,#x1B[90m#x1B[39;49;00m
    ) -> CallInfo[TResult]:#x1B[90m#x1B[39;49;00m
    #x1B[90m    #x1B[39;49;00m#x1B[33m"""Call func, wrapping the result in a CallInfo.#x1B[39;49;00m
    #x1B[33m#x1B[39;49;00m
    #x1B[33m    :param func:#x1B[39;49;00m
    #x1B[33m        The function to call. Called without arguments.#x1B[39;49;00m
    #x1B[33m    :type func: Callable[[], _pytest.runner.TResult]#x1B[39;49;00m
    #x1B[33m    :param when:#x1B[39;49;00m
    #x1B[33m        The phase in which the function is called.#x1B[39;49;00m
    #x1B[33m    :param reraise:#x1B[39;49;00m
    #x1B[33m        Exception or exceptions that shall propagate if raised by the#x1B[39;49;00m
    #x1B[33m        function, instead of being wrapped in the CallInfo.#x1B[39;49;00m
    #x1B[33m    """#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
        excinfo = #x1B[94mNone#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
        instant = timing.Instant()#x1B[90m#x1B[39;49;00m
        #x1B[94mtry#x1B[39;49;00m:#x1B[90m#x1B[39;49;00m
>           result: TResult | #x1B[94mNone#x1B[39;49;00m = func()#x1B[90m#x1B[39;49;00m
                                     ^^^^^^#x1B[90m#x1B[39;49;00m

cls        = <class '_pytest.runner.CallInfo'>
duration   = Duration(start=Instant(time=1765210973.613896, perf_count=759.072238208), stop=Instant(time=1765210973.635524, perf_count=759.093866541))
excinfo    = <ExceptionInfo PytestUnraisableExceptionWarning('Exception ignored while calling deallocator <function IOBase.__del__ at 0x107787320>: None\n') tblen=19>
func       = <function call_and_report.<locals>.<lambda> at 0x1080ec9e0>
instant    = Instant(time=1765210973.613896, perf_count=759.072238208)
reraise    = (<class '_pytest.outcomes.Exit'>, <class 'KeyboardInterrupt'>)
result     = None
when       = 'call'

#x1B[1m#x1B[31m..../py/lib/python3.14....../site-packages/_pytest/runner.py#x1B[0m:353: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
#x1B[1m#x1B[31m..../py/lib/python3.14....../site-packages/_pytest/runner.py#x1B[0m:245: in <lambda>
    #x1B[0m#x1B[94mlambda#x1B[39;49;00m: runtest_hook(item=item, **kwds),#x1B[90m#x1B[39;49;00m
            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^#x1B[90m#x1B[39;49;00m
        item       = <Function test_streaming_10[False]>
        kwds       = {}
        runtest_hook = <HookCaller 'pytest_runtest_call'>
#x1B[1m#x1B[31m..../py/lib/python3.14.../site-packages/pluggy/_hooks.py#x1B[0m:512: in __call__
    #x1B[0m#x1B[94mreturn#x1B[39;49;00m #x1B[96mself#x1B[39;49;00m._hookexec(#x1B[96mself#x1B[39;49;00m.name, #x1B[96mself#x1B[39;49;00m._hookimpls.copy(), kwargs, firstresult)#x1B[90m#x1B[39;49;00m
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^#x1B[90m#x1B[39;49;00m
        firstresult = False
        kwargs     = {'item': <Function test_streaming_10[False]>}
        self       = <HookCaller 'pytest_runtest_call'>
#x1B[1m#x1B[31m..../py/lib/python3.14.../site-packages/pluggy/_manager.py#x1B[0m:120: in _hookexec
    #x1B[0m#x1B[94mreturn#x1B[39;49;00m #x1B[96mself#x1B[39;49;00m._inner_hookexec(hook_name, methods, kwargs, firstresult)#x1B[90m#x1B[39;49;00m
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^#x1B[90m#x1B[39;49;00m
        firstresult = False
        hook_name  = 'pytest_runtest_call'
        kwargs     = {'item': <Function test_streaming_10[False]>}
        methods    = [<HookImpl plugin_name='threadexception', plugin=<module '_pytest.threadexception' from '.../work/cheroot/ch...pper name='/dev/null' mode='r' encoding='utf-8'>> _state='suspended' _in_suspended=False> _capture_fixture=None>>, ...]
        self       = <_pytest.config.PytestPluginManager object at 0x106960d70>
#x1B[1m#x1B[31m..../py/lib/python3.14.../site-packages/_pytest/logging.py#x1B[0m:850: in pytest_runtest_call
    #x1B[0m#x1B[94myield#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
        item       = <Function test_streaming_10[False]>
        self       = <_pytest.logging.LoggingPlugin object at 0x1079ebcb0>
#x1B[1m#x1B[31m..../py/lib/python3.14....../site-packages/pluggy/_callers.py#x1B[0m:53: in run_old_style_hookwrapper
    #x1B[0m#x1B[94mreturn#x1B[39;49;00m result.get_result()#x1B[90m#x1B[39;49;00m
           ^^^^^^^^^^^^^^^^^^^#x1B[90m#x1B[39;49;00m
        args       = [<Function test_streaming_10[False]>]
        hook_impl  = <HookImpl plugin_name='_cov', plugin=<pytest_cov.plugin.CovPlugin object at 0x1073e8830>>
        hook_name  = 'pytest_runtest_call'
        result     = <pluggy._result.Result object at 0x1081c0a00>
        teardown   = <generator object CovPlugin.pytest_runtest_call at 0x107fc9b70>
#x1B[1m#x1B[31m..../py/lib/python3.14....../site-packages/pluggy/_callers.py#x1B[0m:38: in run_old_style_hookwrapper
    #x1B[0mres = #x1B[94myield#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
          ^^^^^#x1B[90m#x1B[39;49;00m
        args       = [<Function test_streaming_10[False]>]
        hook_impl  = <HookImpl plugin_name='_cov', plugin=<pytest_cov.plugin.CovPlugin object at 0x1073e8830>>
        hook_name  = 'pytest_runtest_call'
        result     = <pluggy._result.Result object at 0x1081c0a00>
        teardown   = <generator object CovPlugin.pytest_runtest_call at 0x107fc9b70>
#x1B[1m#x1B[31m..../py/lib/python3.14.../site-packages/_pytest/capture.py#x1B[0m:900: in pytest_runtest_call
    #x1B[0m#x1B[94mreturn#x1B[39;49;00m (#x1B[94myield#x1B[39;49;00m)#x1B[90m#x1B[39;49;00m
            ^^^^^#x1B[90m#x1B[39;49;00m
        item       = <Function test_streaming_10[False]>
        self       = <CaptureManager _method='fd' _global_capturing=<MultiCapture out=<FDCapture 1 oldfd=7 _state='suspended' tmpfile=<Enco...xtIOWrapper name='/dev/null' mode='r' encoding='utf-8'>> _state='suspended' _in_suspended=False> _capture_fixture=None>
#x1B[1m#x1B[31m..../py/lib/python3.14.../site-packages/_pytest/skipping.py#x1B[0m:268: in pytest_runtest_call
    #x1B[0m#x1B[94mreturn#x1B[39;49;00m (#x1B[94myield#x1B[39;49;00m)#x1B[90m#x1B[39;49;00m
            ^^^^^#x1B[90m#x1B[39;49;00m
        item       = <Function test_streaming_10[False]>
        xfailed    = None
#x1B[1m#x1B[31m..../py/lib/python3.14........./site-packages/_pytest/unraisableexception.py#x1B[0m:158: in pytest_runtest_call
    #x1B[0mcollect_unraisable(item.config)#x1B[90m#x1B[39;49;00m
        item       = <Function test_streaming_10[False]>
#x1B[1m#x1B[31m..../py/lib/python3.14........./site-packages/_pytest/unraisableexception.py#x1B[0m:79: in collect_unraisable
    #x1B[0m#x1B[94mraise#x1B[39;49;00m errors[#x1B[94m0#x1B[39;49;00m]#x1B[90m#x1B[39;49;00m
        config     = <_pytest.config.Config object at 0x106962270>
        msg        = 'Exception ignored while calling deallocator <function IOBase.__del__ at 0x107787320>: None\n\nTraceback (most recent ...43, in write\n    return self._sock.send(b)\n           ~~~~~~~~~~~~~~~^^^\nOSError: [Errno 9] Bad file descriptor\n\n'
        pop_unraisable = <built-in method pop of collections.deque object at 0x1079cac50>
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

config = <_pytest.config.Config object at 0x106962270>

    #x1B[0m#x1B[94mdef#x1B[39;49;00m#x1B[90m #x1B[39;49;00m#x1B[92mcollect_unraisable#x1B[39;49;00m(config: Config) -> #x1B[94mNone#x1B[39;49;00m:#x1B[90m#x1B[39;49;00m
        pop_unraisable = config.stash[unraisable_exceptions].pop#x1B[90m#x1B[39;49;00m
        errors: #x1B[96mlist#x1B[39;49;00m[pytest.PytestUnraisableExceptionWarning | #x1B[96mRuntimeError#x1B[39;49;00m] = []#x1B[90m#x1B[39;49;00m
        meta = #x1B[94mNone#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
        hook_error = #x1B[94mNone#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
        #x1B[94mtry#x1B[39;49;00m:#x1B[90m#x1B[39;49;00m
            #x1B[94mwhile#x1B[39;49;00m #x1B[94mTrue#x1B[39;49;00m:#x1B[90m#x1B[39;49;00m
                #x1B[94mtry#x1B[39;49;00m:#x1B[90m#x1B[39;49;00m
                    meta = pop_unraisable()#x1B[90m#x1B[39;49;00m
                #x1B[94mexcept#x1B[39;49;00m #x1B[96mIndexError#x1B[39;49;00m:#x1B[90m#x1B[39;49;00m
                    #x1B[94mbreak#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
    #x1B[90m#x1B[39;49;00m
                #x1B[94mif#x1B[39;49;00m #x1B[96misinstance#x1B[39;49;00m(meta, #x1B[96mBaseException#x1B[39;49;00m):#x1B[90m#x1B[39;49;00m
                    hook_error = #x1B[96mRuntimeError#x1B[39;49;00m(#x1B[33m"#x1B[39;49;00m#x1B[33mFailed to process unraisable exception#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m)#x1B[90m#x1B[39;49;00m
                    hook_error.__cause__ = meta#x1B[90m#x1B[39;49;00m
                    errors.append(hook_error)#x1B[90m#x1B[39;49;00m
                    #x1B[94mcontinue#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
    #x1B[90m#x1B[39;49;00m
                msg = meta.msg#x1B[90m#x1B[39;49;00m
                #x1B[94mtry#x1B[39;49;00m:#x1B[90m#x1B[39;49;00m
>                   warnings.warn(pytest.PytestUnraisableExceptionWarning(msg))#x1B[90m#x1B[39;49;00m
#x1B[1m#x1B[31mE                   pytest.PytestUnraisableExceptionWarning: Exception ignored while calling deallocator <function IOBase.__del__ at 0x107787320>: None#x1B[0m

config     = <_pytest.config.Config object at 0x106962270>
msg        = 'Exception ignored while calling deallocator <function IOBase.__del__ at 0x107787320>: None\n\nTraceback (most recent ...43, in write\n    return self._sock.send(b)\n           ~~~~~~~~~~~~~~~^^^\nOSError: [Errno 9] Bad file descriptor\n\n'
pop_unraisable = <built-in method pop of collections.deque object at 0x1079cac50>

#x1B[1m#x1B[31m..../py/lib/python3.14........./site-packages/_pytest/unraisableexception.py#x1B[0m:67: PytestUnraisableExceptionWarning
cheroot/test/test_core.py::test_parse_uri_unsafe_uri

Flake rate in main: 30.00% (Passed 7 times, Failed 3 times)

Stack Traces | 0.02s run time
self = <cheroot.makefile.StreamWriter name=-1>

    #x1B[0m#x1B[94mdef#x1B[39;49;00m#x1B[90m #x1B[39;49;00m#x1B[92m__del__#x1B[39;49;00m(#x1B[96mself#x1B[39;49;00m):#x1B[90m#x1B[39;49;00m
    #x1B[90m    #x1B[39;49;00m#x1B[33m"""Destructor.  Calls close()."""#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
        #x1B[94mtry#x1B[39;49;00m:#x1B[90m#x1B[39;49;00m
            closed = #x1B[96mself#x1B[39;49;00m.closed#x1B[90m#x1B[39;49;00m
        #x1B[94mexcept#x1B[39;49;00m #x1B[96mAttributeError#x1B[39;49;00m:#x1B[90m#x1B[39;49;00m
            #x1B[90m# If getting closed fails, then the object is probably#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
            #x1B[90m# in an unusable state, so ignore.#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
            #x1B[94mreturn#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
    #x1B[90m#x1B[39;49;00m
        #x1B[94mif#x1B[39;49;00m closed:#x1B[90m#x1B[39;49;00m
            #x1B[94mreturn#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
    #x1B[90m#x1B[39;49;00m
        #x1B[94mif#x1B[39;49;00m dealloc_warn := #x1B[96mgetattr#x1B[39;49;00m(#x1B[96mself#x1B[39;49;00m, #x1B[33m"#x1B[39;49;00m#x1B[33m_dealloc_warn#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m, #x1B[94mNone#x1B[39;49;00m):#x1B[90m#x1B[39;49;00m
            dealloc_warn(#x1B[96mself#x1B[39;49;00m)#x1B[90m#x1B[39;49;00m
    #x1B[90m#x1B[39;49;00m
        #x1B[90m# If close() fails, the caller logs the exception with#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
        #x1B[90m# sys.unraisablehook. close() must be called at the end at __del__().#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
>       #x1B[96mself#x1B[39;49;00m.close()#x1B[90m#x1B[39;49;00m

closed     = False
dealloc_warn = <bound method _BufferedIOMixin._dealloc_warn of <cheroot.makefile.StreamWriter name=-1>>
self       = <cheroot.makefile.StreamWriter name=-1>

#x1B[1m#x1B[.../Library/Frameworks/Python.framework/Versions/3.14/lib/python3.14/_pyio.py#x1B[0m:415: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
#x1B[1m#x1B[.../Library/Frameworks/Python.framework/Versions/3.14/lib/python3.14/_pyio.py#x1B[0m:1312: in close
    #x1B[0m#x1B[96mself#x1B[39;49;00m.flush()#x1B[90m#x1B[39;49;00m
        self       = <cheroot.makefile.StreamWriter name=-1>
#x1B[1m#x1B[.../Library/Frameworks/Python.framework/Versions/3.14/lib/python3.14/_pyio.py#x1B[0m:1273: in flush
    #x1B[0m#x1B[96mself#x1B[39;49;00m._flush_unlocked()#x1B[90m#x1B[39;49;00m
        self       = <cheroot.makefile.StreamWriter name=-1>
#x1B[1m#x1B[31mcheroot/makefile.py#x1B[0m:32: in _flush_unlocked
    #x1B[0mn = #x1B[96mself#x1B[39;49;00m.raw.write(#x1B[96mbytes#x1B[39;49;00m(#x1B[96mself#x1B[39;49;00m._write_buf))#x1B[90m#x1B[39;49;00m
        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^#x1B[90m#x1B[39;49;00m
        self       = <cheroot.makefile.StreamWriter name=-1>
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <socket.SocketIO object at 0x10646c9a0>
b = b'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa...aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'

    #x1B[0m#x1B[94mdef#x1B[39;49;00m#x1B[90m #x1B[39;49;00m#x1B[92mwrite#x1B[39;49;00m(#x1B[96mself#x1B[39;49;00m, b):#x1B[90m#x1B[39;49;00m
    #x1B[90m    #x1B[39;49;00m#x1B[33m"""Write the given bytes or bytearray object *b* to the socket#x1B[39;49;00m
    #x1B[33m    and return the number of bytes written.  This can be less than#x1B[39;49;00m
    #x1B[33m    len(b) if not all data could be written.  If the socket is#x1B[39;49;00m
    #x1B[33m    non-blocking and no bytes could be written None is returned.#x1B[39;49;00m
    #x1B[33m    """#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
        #x1B[96mself#x1B[39;49;00m._checkClosed()#x1B[90m#x1B[39;49;00m
        #x1B[96mself#x1B[39;49;00m._checkWritable()#x1B[90m#x1B[39;49;00m
        #x1B[94mtry#x1B[39;49;00m:#x1B[90m#x1B[39;49;00m
>           #x1B[94mreturn#x1B[39;49;00m #x1B[96mself#x1B[39;49;00m._sock.send(b)#x1B[90m#x1B[39;49;00m
                   ^^^^^^^^^^^^^^^^^^#x1B[90m#x1B[39;49;00m
#x1B[1m#x1B[31mE           OSError: [Errno 9] Bad file descriptor#x1B[0m

b          = b'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa...aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'
self       = <socket.SocketIO object at 0x10646c9a0>

#x1B[1m#x1B[.../Library/Frameworks/Python.framework/Versions/3.14/lib/python3.14/socket.py#x1B[0m:743: OSError

#x1B[33mThe above exception was the direct cause of the following exception:#x1B[0m

cls = <class '_pytest.runner.CallInfo'>
func = <function call_and_report.<locals>.<lambda> at 0x1064c8040>
when = 'call'
reraise = (<class '_pytest.outcomes.Exit'>, <class 'KeyboardInterrupt'>)

    #x1B[0m#x1B[37m@classmethod#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
    #x1B[94mdef#x1B[39;49;00m#x1B[90m #x1B[39;49;00m#x1B[92mfrom_call#x1B[39;49;00m(#x1B[90m#x1B[39;49;00m
        #x1B[96mcls#x1B[39;49;00m,#x1B[90m#x1B[39;49;00m
        func: Callable[[], TResult],#x1B[90m#x1B[39;49;00m
        when: Literal[#x1B[33m"#x1B[39;49;00m#x1B[33mcollect#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m, #x1B[33m"#x1B[39;49;00m#x1B[33msetup#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m, #x1B[33m"#x1B[39;49;00m#x1B[33mcall#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m, #x1B[33m"#x1B[39;49;00m#x1B[33mteardown#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m],#x1B[90m#x1B[39;49;00m
        reraise: #x1B[96mtype#x1B[39;49;00m[#x1B[96mBaseException#x1B[39;49;00m] | #x1B[96mtuple#x1B[39;49;00m[#x1B[96mtype#x1B[39;49;00m[#x1B[96mBaseException#x1B[39;49;00m], ...] | #x1B[94mNone#x1B[39;49;00m = #x1B[94mNone#x1B[39;49;00m,#x1B[90m#x1B[39;49;00m
    ) -> CallInfo[TResult]:#x1B[90m#x1B[39;49;00m
    #x1B[90m    #x1B[39;49;00m#x1B[33m"""Call func, wrapping the result in a CallInfo.#x1B[39;49;00m
    #x1B[33m#x1B[39;49;00m
    #x1B[33m    :param func:#x1B[39;49;00m
    #x1B[33m        The function to call. Called without arguments.#x1B[39;49;00m
    #x1B[33m    :type func: Callable[[], _pytest.runner.TResult]#x1B[39;49;00m
    #x1B[33m    :param when:#x1B[39;49;00m
    #x1B[33m        The phase in which the function is called.#x1B[39;49;00m
    #x1B[33m    :param reraise:#x1B[39;49;00m
    #x1B[33m        Exception or exceptions that shall propagate if raised by the#x1B[39;49;00m
    #x1B[33m        function, instead of being wrapped in the CallInfo.#x1B[39;49;00m
    #x1B[33m    """#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
        excinfo = #x1B[94mNone#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
        instant = timing.Instant()#x1B[90m#x1B[39;49;00m
        #x1B[94mtry#x1B[39;49;00m:#x1B[90m#x1B[39;49;00m
>           result: TResult | #x1B[94mNone#x1B[39;49;00m = func()#x1B[90m#x1B[39;49;00m
                                     ^^^^^^#x1B[90m#x1B[39;49;00m

cls        = <class '_pytest.runner.CallInfo'>
duration   = Duration(start=Instant(time=1765210980.951334, perf_count=766.409676375), stop=Instant(time=1765210980.971477, perf_count=766.429821125))
excinfo    = <ExceptionInfo PytestUnraisableExceptionWarning('Exception ignored while calling deallocator <function IOBase.__del__ at 0x105367320>: None\n') tblen=19>
func       = <function call_and_report.<locals>.<lambda> at 0x1064c8040>
instant    = Instant(time=1765210980.951334, perf_count=766.409676375)
reraise    = (<class '_pytest.outcomes.Exit'>, <class 'KeyboardInterrupt'>)
result     = None
when       = 'call'

#x1B[1m#x1B[31m..../py/lib/python3.14....../site-packages/_pytest/runner.py#x1B[0m:353: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
#x1B[1m#x1B[31m..../py/lib/python3.14....../site-packages/_pytest/runner.py#x1B[0m:245: in <lambda>
    #x1B[0m#x1B[94mlambda#x1B[39;49;00m: runtest_hook(item=item, **kwds),#x1B[90m#x1B[39;49;00m
            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^#x1B[90m#x1B[39;49;00m
        item       = <Function test_parse_uri_unsafe_uri>
        kwds       = {}
        runtest_hook = <HookCaller 'pytest_runtest_call'>
#x1B[1m#x1B[31m..../py/lib/python3.14.../site-packages/pluggy/_hooks.py#x1B[0m:512: in __call__
    #x1B[0m#x1B[94mreturn#x1B[39;49;00m #x1B[96mself#x1B[39;49;00m._hookexec(#x1B[96mself#x1B[39;49;00m.name, #x1B[96mself#x1B[39;49;00m._hookimpls.copy(), kwargs, firstresult)#x1B[90m#x1B[39;49;00m
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^#x1B[90m#x1B[39;49;00m
        firstresult = False
        kwargs     = {'item': <Function test_parse_uri_unsafe_uri>}
        self       = <HookCaller 'pytest_runtest_call'>
#x1B[1m#x1B[31m..../py/lib/python3.14.../site-packages/pluggy/_manager.py#x1B[0m:120: in _hookexec
    #x1B[0m#x1B[94mreturn#x1B[39;49;00m #x1B[96mself#x1B[39;49;00m._inner_hookexec(hook_name, methods, kwargs, firstresult)#x1B[90m#x1B[39;49;00m
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^#x1B[90m#x1B[39;49;00m
        firstresult = False
        hook_name  = 'pytest_runtest_call'
        kwargs     = {'item': <Function test_parse_uri_unsafe_uri>}
        methods    = [<HookImpl plugin_name='threadexception', plugin=<module '_pytest.threadexception' from '.../work/cheroot/ch...pper name='/dev/null' mode='r' encoding='utf-8'>> _state='suspended' _in_suspended=False> _capture_fixture=None>>, ...]
        self       = <_pytest.config.PytestPluginManager object at 0x10461cd70>
#x1B[1m#x1B[31m..../py/lib/python3.14.../site-packages/_pytest/logging.py#x1B[0m:850: in pytest_runtest_call
    #x1B[0m#x1B[94myield#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
        item       = <Function test_parse_uri_unsafe_uri>
        self       = <_pytest.logging.LoggingPlugin object at 0x1055cfcb0>
#x1B[1m#x1B[31m..../py/lib/python3.14....../site-packages/pluggy/_callers.py#x1B[0m:53: in run_old_style_hookwrapper
    #x1B[0m#x1B[94mreturn#x1B[39;49;00m result.get_result()#x1B[90m#x1B[39;49;00m
           ^^^^^^^^^^^^^^^^^^^#x1B[90m#x1B[39;49;00m
        args       = [<Function test_parse_uri_unsafe_uri>]
        hook_impl  = <HookImpl plugin_name='_cov', plugin=<pytest_cov.plugin.CovPlugin object at 0x104fcc830>>
        hook_name  = 'pytest_runtest_call'
        result     = <pluggy._result.Result object at 0x10627b440>
        teardown   = <generator object CovPlugin.pytest_runtest_call at 0x1064ef6a0>
#x1B[1m#x1B[31m..../py/lib/python3.14....../site-packages/pluggy/_callers.py#x1B[0m:38: in run_old_style_hookwrapper
    #x1B[0mres = #x1B[94myield#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
          ^^^^^#x1B[90m#x1B[39;49;00m
        args       = [<Function test_parse_uri_unsafe_uri>]
        hook_impl  = <HookImpl plugin_name='_cov', plugin=<pytest_cov.plugin.CovPlugin object at 0x104fcc830>>
        hook_name  = 'pytest_runtest_call'
        result     = <pluggy._result.Result object at 0x10627b440>
        teardown   = <generator object CovPlugin.pytest_runtest_call at 0x1064ef6a0>
#x1B[1m#x1B[31m..../py/lib/python3.14.../site-packages/_pytest/capture.py#x1B[0m:900: in pytest_runtest_call
    #x1B[0m#x1B[94mreturn#x1B[39;49;00m (#x1B[94myield#x1B[39;49;00m)#x1B[90m#x1B[39;49;00m
            ^^^^^#x1B[90m#x1B[39;49;00m
        item       = <Function test_parse_uri_unsafe_uri>
        self       = <CaptureManager _method='fd' _global_capturing=<MultiCapture out=<FDCapture 1 oldfd=7 _state='suspended' tmpfile=<Enco...xtIOWrapper name='/dev/null' mode='r' encoding='utf-8'>> _state='suspended' _in_suspended=False> _capture_fixture=None>
#x1B[1m#x1B[31m..../py/lib/python3.14.../site-packages/_pytest/skipping.py#x1B[0m:268: in pytest_runtest_call
    #x1B[0m#x1B[94mreturn#x1B[39;49;00m (#x1B[94myield#x1B[39;49;00m)#x1B[90m#x1B[39;49;00m
            ^^^^^#x1B[90m#x1B[39;49;00m
        item       = <Function test_parse_uri_unsafe_uri>
        xfailed    = None
#x1B[1m#x1B[31m..../py/lib/python3.14........./site-packages/_pytest/unraisableexception.py#x1B[0m:158: in pytest_runtest_call
    #x1B[0mcollect_unraisable(item.config)#x1B[90m#x1B[39;49;00m
        item       = <Function test_parse_uri_unsafe_uri>
#x1B[1m#x1B[31m..../py/lib/python3.14........./site-packages/_pytest/unraisableexception.py#x1B[0m:79: in collect_unraisable
    #x1B[0m#x1B[94mraise#x1B[39;49;00m errors[#x1B[94m0#x1B[39;49;00m]#x1B[90m#x1B[39;49;00m
        config     = <_pytest.config.Config object at 0x10461e270>
        msg        = 'Exception ignored while calling deallocator <function IOBase.__del__ at 0x105367320>: None\n\nTraceback (most recent ...43, in write\n    return self._sock.send(b)\n           ~~~~~~~~~~~~~~~^^^\nOSError: [Errno 9] Bad file descriptor\n\n'
        pop_unraisable = <built-in method pop of collections.deque object at 0x1055aec50>
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

config = <_pytest.config.Config object at 0x10461e270>

    #x1B[0m#x1B[94mdef#x1B[39;49;00m#x1B[90m #x1B[39;49;00m#x1B[92mcollect_unraisable#x1B[39;49;00m(config: Config) -> #x1B[94mNone#x1B[39;49;00m:#x1B[90m#x1B[39;49;00m
        pop_unraisable = config.stash[unraisable_exceptions].pop#x1B[90m#x1B[39;49;00m
        errors: #x1B[96mlist#x1B[39;49;00m[pytest.PytestUnraisableExceptionWarning | #x1B[96mRuntimeError#x1B[39;49;00m] = []#x1B[90m#x1B[39;49;00m
        meta = #x1B[94mNone#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
        hook_error = #x1B[94mNone#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
        #x1B[94mtry#x1B[39;49;00m:#x1B[90m#x1B[39;49;00m
            #x1B[94mwhile#x1B[39;49;00m #x1B[94mTrue#x1B[39;49;00m:#x1B[90m#x1B[39;49;00m
                #x1B[94mtry#x1B[39;49;00m:#x1B[90m#x1B[39;49;00m
                    meta = pop_unraisable()#x1B[90m#x1B[39;49;00m
                #x1B[94mexcept#x1B[39;49;00m #x1B[96mIndexError#x1B[39;49;00m:#x1B[90m#x1B[39;49;00m
                    #x1B[94mbreak#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
    #x1B[90m#x1B[39;49;00m
                #x1B[94mif#x1B[39;49;00m #x1B[96misinstance#x1B[39;49;00m(meta, #x1B[96mBaseException#x1B[39;49;00m):#x1B[90m#x1B[39;49;00m
                    hook_error = #x1B[96mRuntimeError#x1B[39;49;00m(#x1B[33m"#x1B[39;49;00m#x1B[33mFailed to process unraisable exception#x1B[39;49;00m#x1B[33m"#x1B[39;49;00m)#x1B[90m#x1B[39;49;00m
                    hook_error.__cause__ = meta#x1B[90m#x1B[39;49;00m
                    errors.append(hook_error)#x1B[90m#x1B[39;49;00m
                    #x1B[94mcontinue#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
    #x1B[90m#x1B[39;49;00m
                msg = meta.msg#x1B[90m#x1B[39;49;00m
                #x1B[94mtry#x1B[39;49;00m:#x1B[90m#x1B[39;49;00m
>                   warnings.warn(pytest.PytestUnraisableExceptionWarning(msg))#x1B[90m#x1B[39;49;00m
#x1B[1m#x1B[31mE                   pytest.PytestUnraisableExceptionWarning: Exception ignored while calling deallocator <function IOBase.__del__ at 0x105367320>: None#x1B[0m

config     = <_pytest.config.Config object at 0x10461e270>
msg        = 'Exception ignored while calling deallocator <function IOBase.__del__ at 0x105367320>: None\n\nTraceback (most recent ...43, in write\n    return self._sock.send(b)\n           ~~~~~~~~~~~~~~~^^^\nOSError: [Errno 9] Bad file descriptor\n\n'
pop_unraisable = <built-in method pop of collections.deque object at 0x1055aec50>

#x1B[1m#x1B[31m..../py/lib/python3.14........./site-packages/_pytest/unraisableexception.py#x1B[0m:67: PytestUnraisableExceptionWarning

To view more test analytics, go to the Test Analytics Dashboard
📋 Got 3 mins? Take this short survey to help us improve Test Analytics.

@webknjaz
Copy link
Member

@jatalahd may I ask why does your checklist and up having * [ x] instead of * [x] in markdown?

@jatalahd
Copy link
Contributor Author

@jatalahd may I ask why does your checklist and up having * [ x] instead of * [x] in markdown?

I truly do not know. I have to pay attention to this next time.

@jatalahd
Copy link
Contributor Author

@webknjaz ; added change log fragment and some answers to your questions. Added also explanation to the main umbrella issue:
cherrypy/cherrypy#1583 (comment)

@webknjaz
Copy link
Member

@jatalahd may I ask why does your checklist and up having * [ x] instead of * [x] in markdown?

I truly do not know. I have to pay attention to this next time.

Let me know. I've seen this happening when other people fill out the form but never learned what the cause is.

@jatalahd jatalahd force-pushed the fix_openssl_private_key_none_passwd branch from bb640b0 to 3f6f038 Compare November 26, 2025 16:21
@jatalahd jatalahd force-pushed the fix_openssl_private_key_none_passwd branch from 3f6f038 to 20885c6 Compare November 29, 2025 13:52
@jatalahd
Copy link
Contributor Author

jatalahd commented Nov 29, 2025

@webknjaz ; In the latest push I have made both adapters to accept callable type for the private_key_password and added a parameter for that in the tests. The getpass approach in the pyopenssl adapter enabled a way to monkeypatch test the "interactive" password callback. Similar test for builtin adapter is not possible in this context. Only way would most likely be to pass the password via subprocess communication.

@webknjaz
Copy link
Member

The getpass approach in the pyopenssl adapter enabled a way to monkeypatch test the "interactive" password callback. Similar test for builtin adapter is not possible in this context.

Cool. I was hoping it'd help like that. With the comments I left inline, I think the builtin adapter should be able to mirror the same idea.

@jatalahd jatalahd force-pushed the fix_openssl_private_key_none_passwd branch from 20885c6 to afc39cd Compare December 1, 2025 18:27
@jatalahd
Copy link
Contributor Author

jatalahd commented Dec 1, 2025

@webknjaz ; Made some progress in the latest push and overall things start to line up. I am not sure if the password prompting callback should be in __init__ but that was the only common module at this point and did not want to create a new module just for one method. But in my opinion this will be very good overall improvement when we get the final cleanup done.

@webknjaz
Copy link
Member

webknjaz commented Dec 2, 2025

I am not sure if the password prompting callback should be in __init__ but that was the only common module at this point and did not want to create a new module just for one method.

Good call.

But in my opinion this will be very good overall improvement when we get the final cleanup done.

Yep, I feel the same.


P.S. Rebase the PR to absorb the fixes on main.

@webknjaz
Copy link
Member

webknjaz commented Dec 5, 2025

@jatalahd you'll need fix a conflict when rebasing.

@jatalahd jatalahd force-pushed the fix_openssl_private_key_none_passwd branch from afc39cd to 529dd97 Compare December 6, 2025 14:46
@jatalahd
Copy link
Contributor Author

jatalahd commented Dec 6, 2025

@webknjaz ; Latest push is closer towards the goal, but I did not manage to resolve all your comments. Let's continue from here.

Copy link
Member

@webknjaz webknjaz left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just two small test improvements left.

@jatalahd jatalahd force-pushed the fix_openssl_private_key_none_passwd branch from 529dd97 to 2692451 Compare December 7, 2025 19:21
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hopefully ok in the latest push.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep, it's now clickable!

@webknjaz
Copy link
Member

webknjaz commented Dec 7, 2025

A few fixes left are mostly cosmetic.

- If pyopenssl adapter was used with password protected private key,
  the manual entry option was not given, only a fail due to
  invalid password. The password callback was triggered also
  in the case where the private_key_password was None.

- Added Callable type as possible private_key_password argument
@jatalahd jatalahd force-pushed the fix_openssl_private_key_none_passwd branch from 2692451 to 75196e3 Compare December 8, 2025 16:18
Copy link
Member

@webknjaz webknjaz left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perfect, thanks!

@webknjaz webknjaz merged commit 39cccaa into cherrypy:main Dec 9, 2025
126 of 144 checks passed
'transform_password_arg',
(
lambda pass_factory: pass_factory().encode('utf-8'),
lambda pass_factory: pass_factory(),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jatalahd so I replaced the deprecated macos-13 with macos-15-intel on main (8b8d6f8) and then merged your PR right after.

The CI now fails on that specific VM under PyPy 3.11 with a SEGFAULT in cheroot/test/test_ssl.py::test_ssl_adapters_with_private_key_password[with-str-password-unencrypted-key-builtin] which this PR touches (but another one introduced originally).

The last successful PR CI run wasn't running pypy under macos: https://github.com/cherrypy/cheroot/actions/runs/20009127282. I just forgot to skip it when changing VMs.

This means that the pathc in this PR isn't necessarily related to the problem but if you have a minute, it might be a good idea to look closer in case this is something that might start happening under classic CPython too...

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Although.. The surrounding comments suggest pre-existing issues with pytest-xdist + PyPy. So never mind that.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

With 41a44f1, the CI should be fine again.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, hopefully it is fine now. I did not see any test issues when running the unit tests locally with Python 3.11 in MacOS15.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, this was just PyPy, not CPython.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, this was just PyPy, not CPython.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants