jaraco / keyring

MIT License
1.24k stars 152 forks source link

Tests failing on Python 3.12 #634

Closed jaraco closed 1 year ago

jaraco commented 1 year ago

Tests on Windows are failing to find the backend (https://github.com/jaraco/keyring/actions/runs/5293285392/jobs/9581225875) on Python 3.12.

``` ================================== FAILURES =================================== ____________________________ test_multiprocess_get ____________________________ def test_multiprocess_get(): proc1 = multiprocessing.Process(target=subprocess_get) proc1.start() proc1.join() > assert proc1.exitcode == 0 E AssertionError: assert 1 == 0 E + where 1 = .exitcode tests\test_multiprocess.py:31: AssertionError ---------------------------- Captured stderr call ----------------------------- D:\a\keyring\keyring\keyring\core.py:158: EncodingWarning: 'encoding' argument not specified config.read(_config_path()) Keyring config file contains incorrect values. Config file: C:\ProgramData\Python Keyring\keyringrc.cfg Process Process-1: Traceback (most recent call last): File "C:\hostedtoolcache\windows\Python\3.12.0-beta.2\x64\Lib\multiprocessing\process.py", line 314, in _bootstrap self.run() File "C:\hostedtoolcache\windows\Python\3.12.0-beta.2\x64\Lib\multiprocessing\process.py", line 108, in run self._target(*self._args, **self._kwargs) File "D:\a\keyring\keyring\tests\test_multiprocess.py", line 11, in subprocess_get keyring.get_password('test_app', 'test_user') File "D:\a\keyring\keyring\keyring\core.py", line 56, in get_password return get_keyring().get_password(service_name, username) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "D:\a\keyring\keyring\keyring\backends\fail.py", line 28, in get_password raise NoKeyringError(msg) keyring.errors.NoKeyringError: No recommended backend was available. Install a recommended 3rd party backend package; or, install the keyrings.alt package if you want to use the non-recommended backends. See https://pypi.org/project/keyring for details. ___________________ test_multiprocess_get_after_native_get ____________________ def test_multiprocess_get_after_native_get(): > keyring.get_password('test_app', 'test_user') tests\test_multiprocess.py:35: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ keyring\core.py:56: in get_password return get_keyring().get_password(service_name, username) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ self = service = 'test_app', username = 'test_user', password = None def get_password(self, service, username, *** msg = ( "No recommended backend was available. Install a recommended 3rd " "party backend package; or, install the keyrings.alt package if " "you want to use the non-recommended backends. See " "https://pypi.org/project/keyring for details." ) > raise NoKeyringError(msg) E keyring.errors.NoKeyringError: No recommended backend was available. Install a recommended 3rd party backend package; or, install the keyrings.alt package if you want to use the non-recommended backends. See https://pypi.org/project/keyring for details. keyring\backends\fail.py:28: NoKeyringError ------------------------------ Captured log call ------------------------------ WARNING keyring:core.py:172 Keyring config file contains incorrect values. Config file: C:\ProgramData\Python Keyring\keyringrc.cfg _________________________ test_winvault_always_viable _________________________ @pytest.mark.skipif('sys.platform != "win32"') def test_winvault_always_viable(): """ The WinVault backend should always be viable on Windows. """ > assert keyring.backends.Windows.WinVaultKeyring.viable E AssertionError: assert False E + where False = .viable E + where = .WinVaultKeyring E + where = .Windows E + where = keyring.backends tests\backends\test_Windows.py:70: AssertionError ```

We should figure out what's changed.

jaraco commented 1 year ago

I ran the tests locally with Python 3.12a7 and they pass.

jaraco commented 1 year ago

After upgrading to Python 3.12b2, I get the similar failures in test_multiprocessing, but tests still pass for test_winvault_always_viable.

I say similar errors, because on my machine, the failures aren't about the backend failing to initialize but about "the system cannot find the file specified".

``` ======================================================================= FAILURES ======================================================================== _________________________________________________________________ test_multiprocess_get _________________________________________________________________ def test_multiprocess_get(): proc1 = multiprocessing.Process(target=subprocess_get) proc1.start() proc1.join() > assert proc1.exitcode == 0 E AssertionError: assert 1 == 0 E + where 1 = .exitcode tests\test_multiprocess.py:31: AssertionError ----------------------------------------------------------------- Captured stderr call ------------------------------------------------------------------ C:\Users\jaraco\code\jaraco\keyring\keyring\core.py:158: EncodingWarning: 'encoding' argument not specified config.read(_config_path()) Keyring config file contains incorrect values. Config file: C:\ProgramData\Python Keyring\keyringrc.cfg Process Process-1: Traceback (most recent call last): File "C:\Users\jaraco\code\jaraco\keyring\.tox\python\Lib\site-packages\win32ctypes\pywin32\pywintypes.py", line 33, in pywin32error yield File "C:\Users\jaraco\code\jaraco\keyring\.tox\python\Lib\site-packages\win32ctypes\pywin32\win32cred.py", line 68, in CredRead _authentication._CredRead( File "C:\Users\jaraco\code\jaraco\keyring\.tox\python\Lib\site-packages\win32ctypes\core\ctypes\_util.py", line 61, in check_false raise make_error(function, function_name) OSError: [WinError 2] The system cannot find the file specified. During handling of the above exception, another exception occurred: Traceback (most recent call last): File "C:\Program Files\Python 3.12\Lib\multiprocessing\process.py", line 314, in _bootstrap self.run() File "C:\Program Files\Python 3.12\Lib\multiprocessing\process.py", line 108, in run self._target(*self._args, **self._kwargs) File "C:\Users\jaraco\code\jaraco\keyring\tests\test_multiprocess.py", line 11, in subprocess_get keyring.get_password('test_app', 'test_user') File "C:\Users\jaraco\code\jaraco\keyring\keyring\core.py", line 56, in get_password return get_keyring().get_password(service_name, username) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "C:\Users\jaraco\code\jaraco\keyring\keyring\backends\Windows.py", line 98, in get_password res = self._get_password(service) ^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "C:\Users\jaraco\code\jaraco\keyring\keyring\backends\Windows.py", line 108, in _get_password res = win32cred.CredRead( ^^^^^^^^^^^^^^^^^^^ File "C:\Users\jaraco\code\jaraco\keyring\.tox\python\Lib\site-packages\win32ctypes\pywin32\win32cred.py", line 61, in CredRead with _pywin32error(): File "C:\Program Files\Python 3.12\Lib\contextlib.py", line 155, in __exit__ self.gen.throw(value) File "C:\Users\jaraco\code\jaraco\keyring\.tox\python\Lib\site-packages\win32ctypes\pywin32\pywintypes.py", line 37, in pywin32error raise error(exception.winerror, exception.function, exception.strerror) win32ctypes.pywin32.pywintypes.error: (2, 'CredRead', 'The system cannot find the file specified.') ________________________________________________________ test_multiprocess_get_after_native_get _________________________________________________________ @contextlib.contextmanager def pywin32error(): try: > yield .tox\python\Lib\site-packages\win32ctypes\pywin32\pywintypes.py:33: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ TargetName = 'test_app', Type = 1, Flags = 0 def CredRead(TargetName, Type, Flags=0): """ Retrieves a stored credential. Parameters ---------- TargetName : unicode The target name to fetch from the keyring. Type : int One of the CRED_TYPE_* constants. Flags : int Reserved, always use 0. Returns ------- credentials : dict ``None`` if the target name was not found or A dictionary corresponding to the PyWin32 ``PyCREDENTIAL`` structure. """ if Type != CRED_TYPE_GENERIC: raise ValueError("Type != CRED_TYPE_GENERIC not yet supported") flag = 0 with _pywin32error(): if _backend == 'cffi': ppcreds = _authentication.PPCREDENTIAL() _authentication._CredRead(TargetName, Type, flag, ppcreds) pcreds = _common.dereference(ppcreds) else: pcreds = _authentication.PCREDENTIAL() > _authentication._CredRead( TargetName, Type, flag, _common.byreference(pcreds)) .tox\python\Lib\site-packages\win32ctypes\pywin32\win32cred.py:68: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ result = 0, function = <_FuncPtr object at 0x000002445201CFC0>, arguments = ('test_app', 1, 0, ), args = () def check_false(result, function, arguments, *args): if not bool(result): > raise make_error(function, function_name) E OSError: [WinError 2] The system cannot find the file specified. .tox\python\Lib\site-packages\win32ctypes\core\ctypes\_util.py:61: OSError During handling of the above exception, another exception occurred: def test_multiprocess_get_after_native_get(): > keyring.get_password('test_app', 'test_user') tests\test_multiprocess.py:35: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ keyring\core.py:56: in get_password return get_keyring().get_password(service_name, username) keyring\backends\Windows.py:98: in get_password res = self._get_password(service) keyring\backends\Windows.py:108: in _get_password res = win32cred.CredRead( .tox\python\Lib\site-packages\win32ctypes\pywin32\win32cred.py:61: in CredRead with _pywin32error(): C:\Program Files\Python 3.12\Lib\contextlib.py:155: in __exit__ self.gen.throw(value) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ @contextlib.contextmanager def pywin32error(): try: yield except WindowsError as exception: if not hasattr(exception, 'function'): exception.function = 'unknown' > raise error(exception.winerror, exception.function, exception.strerror) E win32ctypes.pywin32.pywintypes.error: (2, 'CredRead', 'The system cannot find the file specified.') .tox\python\Lib\site-packages\win32ctypes\pywin32\pywintypes.py:37: error ------------------------------------------------------------------- Captured log call ------------------------------------------------------------------- WARNING keyring:core.py:172 Keyring config file contains incorrect values. Config file: C:\ProgramData\Python Keyring\keyringrc.cfg ```
jaraco commented 1 year ago

I created this script in an attempt to replicate the issue:

__requires__ = ['pywin32-ctypes']

import multiprocessing

from win32ctypes.pywin32 import win32cred
from win32ctypes.pywin32 import pywintypes

def run():
    try:
        win32cred.CredRead(Type=win32cred.CRED_TYPE_GENERIC, TargetName='test')
    except pywintypes.error as e:
        if e.winerror == 1168 and e.funcname == 'CredRead':  # not found
            return None
        raise

def run_child():
    proc1 = multiprocessing.Process(target=run)
    proc1.start()
    proc1.join()
    assert proc1.exitcode == 0

__name__ == '__main__' and run_child()

But when I run it, it succeeds without failure, so there's apparently something about keyring that's implicated in the failure.

jaraco commented 1 year ago

Even running under pytest doesn't replicate the failure:

# run with py -3.12 -m pip-run -- -m pytest mod.py
__requires__ = ['pytest', 'pywin32-ctypes']

import multiprocessing

from win32ctypes.pywin32 import win32cred
from win32ctypes.pywin32 import pywintypes

def run():
    try:
        win32cred.CredRead(Type=win32cred.CRED_TYPE_GENERIC, TargetName='test')
    except pywintypes.error as e:
        if e.winerror == 1168 and e.funcname == 'CredRead':  # not found
            return None
        raise

def run_child():
    assert run() is None
    proc1 = multiprocessing.Process(target=run)
    proc1.start()
    proc1.join()
    assert proc1.exitcode == 0

def test_run_child():
    run_child()
jaraco commented 1 year ago

I tried running under pytest too, but that also passes:

# run with py -3.12 -m pip-run -- -m pytest mod.py
__requires__ = ['pytest', 'pywin32-ctypes']

import multiprocessing

from win32ctypes.pywin32 import win32cred
from win32ctypes.pywin32 import pywintypes

def run():
    try:
        win32cred.CredRead(Type=win32cred.CRED_TYPE_GENERIC, TargetName='test')
    except pywintypes.error as e:
        if e.winerror == 1168 and e.funcname == 'CredRead':  # not found
            return None
        raise

def run_child():
    assert run() is None
    proc1 = multiprocessing.Process(target=run)
    proc1.start()
    proc1.join()
    assert proc1.exitcode == 0

def test_run_child():
    run_child()
jaraco commented 1 year ago

That test does fail if I run it from the venv created by tox for keyring tests.

jaraco commented 1 year ago

It seems that either coverage or pytest-cov is implicated, as I can replicate the failure with:

# run with py -3.12 -m pip-run -- -m pytest --cov mod.py mod.py
__requires__ = ['pytest', 'pywin32-ctypes', 'pytest-cov']

import multiprocessing

from win32ctypes.pywin32 import win32cred
from win32ctypes.pywin32 import pywintypes

def run():
    try:
        win32cred.CredRead(Type=win32cred.CRED_TYPE_GENERIC, TargetName='test')
    except pywintypes.error as e:
        if e.winerror == 1168 and e.funcname == 'CredRead':  # not found
            return None
        raise

def run_child():
    assert run() is None
    proc1 = multiprocessing.Process(target=run)
    proc1.start()
    proc1.join()
    assert proc1.exitcode == 0

def test_run_child():
    run_child()

That really sucks. It means that I need pytest, pywin32-ctypes, pytest-cov, and multiprocessing to replicate the failure. I'm worried there isn't going to be any hope of disentangling all of those concerns.