Skip to content

Test failures with Python 3.15.0b1: test_bad_payload_exception[zlib] and test_loads_unsafe[zlib] #420

Open

Description

@mgorny

Hope you don't mind some early Python 3.15 testing reports.

$ uv venv -p 3.15
Using CPython 3.15.0b1 interpreter at: /usr/bin/python3.15
Creating virtual environment at: .venv
Activate with: source .venv/bin/activate
$ uv pip install -e . --group=tests
Resolved 9 packages in 2ms
      Built itsdangerous @ file:///tmp/itsdangerous
Prepared 1 package in 164ms
░░░░░░░░░░░░░░░░░░░░ [0/9] Installing wheels...                                                                                        warning: Failed to hardlink files; falling back to full copy. This may lead to degraded performance.
         If the cache and target directories are on different filesystems, hardlinking may not be supported.
         If this is intentional, set `export UV_LINK_MODE=copy` or use `--link-mode=copy` to suppress this warning.
Installed 9 packages in 15ms
 + freezegun==1.5.5
 + iniconfig==2.3.0
 + itsdangerous==2.3.0.dev0 (from file:///tmp/itsdangerous)
 + packaging==26.2
 + pluggy==1.6.0
 + pygments==2.20.0
 + pytest==9.0.3
 + python-dateutil==2.9.0.post0
 + six==1.17.0
$ .venv/bin/pytest
========================================================= test session starts =========================================================
platform linux -- Python 3.15.0b1, pytest-9.0.3, pluggy-1.6.0
rootdir: /tmp/itsdangerous
configfile: pyproject.toml
testpaths: tests
collected 297 items                                                                                                                   

tests/test_itsdangerous/test_encoding.py ........                                                                               [  2%]
tests/test_itsdangerous/test_serializer.py .........................................                                            [ 16%]
tests/test_itsdangerous/test_signer.py .................                                                                        [ 22%]
tests/test_itsdangerous/test_timed.py ......................................................................................... [ 52%]
............                                                                                                                    [ 56%]
tests/test_itsdangerous/test_url_safe.py ...................................................................................... [ 85%]
.........................F.F................                                                                                    [100%]

============================================================== FAILURES ===============================================================
_____________________________________ TestURLSafeTimedSerializer.test_bad_payload_exception[zlib] _____________________________________

self = <test_itsdangerous.test_url_safe.TestURLSafeTimedSerializer object at 0x7f30bde13650>
serializer = <itsdangerous.url_safe.URLSafeTimedSerializer object at 0x7f30bd735860>
value = 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa...aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'

    def test_bad_payload_exception(self, serializer: Serializer, value: Any):
        original = serializer.dumps(value)
        payload = original.rsplit(coerce_str(original, "."), 1)[0]  # type: ignore
        bad = serializer.make_signer().sign(payload[:-1])
    
>       with pytest.raises(BadPayload) as exc_info:
             ^^^^^^^^^^^^^^^^^^^^^^^^^
E       Failed: DID NOT RAISE <class 'itsdangerous.exc.BadPayload'>

tests/test_itsdangerous/test_serializer.py:85: Failed
_________________________________________ TestURLSafeTimedSerializer.test_loads_unsafe[zlib] __________________________________________

self = <test_itsdangerous.test_url_safe.TestURLSafeTimedSerializer object at 0x7f30bde183d0>
serializer = <itsdangerous.url_safe.URLSafeTimedSerializer object at 0x7f30bd730af0>
value = 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa...aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'

    def test_loads_unsafe(self, serializer: Serializer, value: Any):
        signed = serializer.dumps(value)
        assert serializer.loads_unsafe(signed) == (True, value)
    
        bad_signed = signed[:-1]
        assert serializer.loads_unsafe(bad_signed) == (False, value)
    
        payload = signed.rsplit(coerce_str(signed, "."), 1)[0]  # type: ignore
        bad_payload = serializer.make_signer().sign(payload[:-1])[:-1]
>       assert serializer.loads_unsafe(bad_payload) == (False, None)
E       AssertionError: assert (False, 'aaaa...aaaaaaaaaaaa') == (False, None)
E         
E         At index 1 diff: 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa...
E         
E         ...Full output truncated (2 lines hidden), use '-vv' to show

tests/test_itsdangerous/test_serializer.py:99: AssertionError
======================================================= short test summary info =======================================================
FAILED tests/test_itsdangerous/test_url_safe.py::TestURLSafeTimedSerializer::test_bad_payload_exception[zlib] - Failed: DID NOT RAISE <class 'itsdangerous.exc.BadPayload'>
FAILED tests/test_itsdangerous/test_url_safe.py::TestURLSafeTimedSerializer::test_loads_unsafe[zlib] - AssertionError: assert (False, 'aaaa...aaaaaaaaaaaa') == (False, None)
==================================================== 2 failed, 295 passed in 0.85s ====================================================

Environment:

  • Python version: 3.15.0b1
  • ItsDangerous version: 672971d

Activity

  1. mgorny commented on Jun 30, 2026

    @mgorny
    Author

    Additional data point: it seems to only happen with zlib-ng (2.3.3) is used as libz.so. Curious enough, the issue doesn't occur with Python 3.14 while zlib-ng is also used.

  2. mgorny commented on Jul 1, 2026

    @mgorny
    Author

    Okay, so the plot thickens. I've added some prints to debug this further…

    On a vanilla zlib system I get:

    original='.eJxTShwFo2AUDHugBAD6Fns8.TgPVoQ.fRXLPUYVblkvmE1sUMSzMUJ1o1Q'
    coerce_str(original, ".")='.'
    original.rsplit(coerce_str(original, "."), 1)=['.eJxTShwFo2AUDHugBAD6Fns8.TgPVoQ', 'fRXLPUYVblkvmE1sUMSzMUJ1o1Q']
    payload='.eJxTShwFo2AUDHugBAD6Fns8.TgPVoQ'
    bad=b'.eJxTShwFo2AUDHugBAD6Fns8.TgPVo.TgPVoQ.xJ6ilY1mwU2f3RfxWNmEW7iucXk'
    

    On a zlib-ng system I get:

    original='.eJxTShwFo2AUJA53oAQA-hZ7PA.TgPVoQ.amvmNAgB7mXR7AdT4wK09muSNTs'
    coerce_str(original, ".")='.'
    original.rsplit(coerce_str(original, "."), 1)=['.eJxTShwFo2AUJA53oAQA-hZ7PA.TgPVoQ', 'amvmNAgB7mXR7AdT4wK09muSNTs']
    payload='.eJxTShwFo2AUJA53oAQA-hZ7PA.TgPVoQ'
    bad=b'.eJxTShwFo2AUJA53oAQA-hZ7PA.TgPVo.TgPVoQ.O2EO45SXd2Gb7iX6LJNwLSOEr-Q'
    serializer.loads(bad)='aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'
    

    But the curious thing is, the results are the same for Python 3.14 and 3.15 — except for the fact that zlib-ng + 3.14 throws BadPayload as expected, so there's no serializer.loads(bad) there, while zlib-ng + 3.15 doesn't throw and gives you the payload.

  3. mgorny commented on Jul 1, 2026

    @mgorny
    Author

    And I've just noticed #421. While I don't think the solution there is exactly correct, adding padded=True to restore the old behavior does fix the test failures indeed. @davidism, did you close that PR because it's slop or some other reason? I don't know if I should open a PR adding padded=True or if that's not the right solution here.

  4. davidism commented on Jul 2, 2026

    @davidism
    Member

    Yes, that was AI junk. I haven't looked into what the correct solution is. If you want to author a PR without AI generated code, I'm happy to review it.

  5. added a commit that references this issue on Jul 3, 2026
    d9f3b25
  6. mgorny commented on Jul 3, 2026

    @mgorny
    Author

    Filed #428. It's the conservative approach: making Python 3.15 behave like older Python versions.

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions