Cached parameter annotations introduce cross-test pollution when Path is mocked
#1954
Replies: 1 comment 1 reply
|
Here is a comprehensive breakdown of what is happening under the hood and direct answers to your 5 questions: 1. When defined at module level, is a
|
Uh oh!
There was an error while loading. Please reload this page.
First Check
Example Code
Description
First of all, thanks for your hard work, @tiangolo. Typer is truly an amazing project!
I'm opening this thread:
Context
typer, which takes apathlib.Pathas an argumentpathlib.Pathwork as expectedpyfakefsto abstract I/O operations for unit testing, which replacespathlib.Pathwith a subclassFakePath; these tests may fail based on their execution orderSample test cases
The code snippet shared above implements a simple
echocommand in two different ways:outer_echofunction, defined at module-levelinner_echofunction, defined at fixture level, which provides a "clean" command to every test caseA clean
typer.Typerapplication is provided to every test.Each test case runs twice:
pathlib.Pathpyfakefs.fake_pathlib.FakePathRequired packages
pyfakefspytestpytest-random-order(to validate cross-test pollution)Test results
v0.27.2: allFakeFstests failTest execution: (include the trailing
]in the pattern)Key findings:
FakePathget_click_typepyfakefs(see issue#1334); can be partially mitigated by replacingPath→pathlib.PathPatch: module imports
I've worked around the
pyfakefsbug by replacing direct class imports with module imports. See: module_import.patchThis patch passes both tests using the
inner_echocommand, i.e., when the function is redefined for each test:However, the tests using the
outer_echocommand are still failing. In particular, the last executed test fails. Below, the test with the realpathlib.Pathis executed last - and fails:Below, the test with the in-memory
fake_pathlib.FakePathis executed last - and fails:Patch using
lenient_issubclassI have applied a further patch, replacing
annotation == Path→lenient_issubclass(annotation, pathlib.Path). See:lenient_subclass.patch
With this patch, both test cases with
outer_echopass when theFakeFsis executed first:However, the
FakeFstest fails when it's executed last:An introspection with
inspectin the latter test case shows that:annotationmodule is/home/user/.local/share/uv/python/cpython-3.14.6-linux-x86_64-gnu/lib/python3.14/pathlib/__init__.pypathlib.Pathmodule is/path/to/.venv/lib/python3.14/site-packages/pyfakefs/fake_pathlib.py, because it has been replaced byFakePathAs I understand it:
outer_echocommand is registered once, when the first test runs.pathlib.Pathis run first, the command is registered with apathlib.Pathannotation.pathlib.Pathwhen the second test runs.Pathhas been replaced byFakePath. The test fails becausePathis not a subclass ofFakePath(but the other way around).Note that all
bash/test.shtest cases pass with this patch.Patch: double
lenient_issubclassFinally, I applied a double subclass check in double_subclass.patch, shown briefly below:
All tests pass, regardless of order.
Caveats:
The "double issubclass" patch is unintuitive from a readability perspective.
The patch relies on undocumented behavior, namely, that
Pathmay be something else and that eitherannotationorPathcan be a subclass of the other, depending on the circumstances.It breaks a few test cases, raising the error:
Questions
typercommand created once? Example:outer_echoin the test above.pathlib.Pathannotation, but subsequently the type is replaced bypyfakefs.fake_pathlib.FakePath.typer.Typerapplication in every test. Yet, there is cross-test pollution, indicating that the parameter annotations are cached somewhere else.Path→pathlib.Pathis a no-op.Files
Operating System
Linux
Operating System Details
Debian 7.1.3-1
Project Version
0.27.2
Python Version
3.14.6
Additional Context
All reactions