Skip to content

tests/utilities/test_compat.py poisons aenum._common.pyver for the whole process, breaking a later import pcapkit #687

Description

@JarryShaw

tests/utilities/test_compat.py::test_python35_fallback_implementations fakes sys.version_info = (3, 5) and then executes pcapkit/utilities/compat.py:143, from aenum import StrEnum. On Python ≥ 3.11 that is the first-ever import aenum in the process, so aenum/_common.py:17 caches pyver = (3, 5) permanently. Every later use of aenum in that process is then operating under a false Python version.

The consequence is not subtle: a later import pcapkit dies at aenum/_enum.py:1640 with

AttributeError: 'TransportProtocol' object has no attribute '__set_name__'

because aenum skips the descriptor protocol it believes the running interpreter lacks.

Why it is normally invisible

Masked under pytest by tests/conftest.py's pytest_sessionstart, which imports pcapkit eagerly — so aenum is already imported with the true version before any test runs, and the fake assignment caches nothing new.

So this only bites when aenum has not already been imported: the stdlib unittest runner, a bare script, or any future change to that session hook. Confirmed with a standalone script using neither unittest nor mock.

Why the test cannot simply be left alone

The test is legitimate — it exercises the < 3.6 fallback branch of compat.py. The problem is that faking sys.version_info around a real import leaks state into a third-party module's import-time cache, and no amount of restoring sys.version_info afterwards undoes it, because the damage is a value already memoised in aenum._common.

Suggested directions, not prescribed

  1. Import aenum before the fake so the cache is already warm with the true version — smallest change, but relies on ordering that nothing enforces.
  2. Run the fallback branch in a subprocess, so the poisoned cache dies with it. Most robust; costs a process per test.
  3. Reload aenum._common afterwards — fragile, since other modules hold references to the cached value.

Whichever is chosen, a regression test should assert aenum._common.pyver matches the real interpreter after the suite, since that is the invariant being broken.

Provenance

Found by #686's worker while auditing sys.modules leakage for #674, and deliberately not fixed there — it is a different mechanism (a third-party import-time cache, not sys.modules) and tests/utilities/test_compat.py was outside that PR's scope. Reproduction is its measurement; I have not independently re-run it.

Related: #674 (the sys.modules restoration gap, fixed in #686) and the tests/cli/test_main.py leak filed alongside this.

Activity

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

    testPull requests that add or correct tests (test: subject prefix)

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions