Using pytest, sometimes does not capture stderr
Cameron Simpson
cs at cskk.id.au
Mon Apr 5 00:25:54 EDT 2021
On 05Apr2021 13:56, David <bouncingcats at gmail.com> wrote:
>On Mon, 5 Apr 2021 at 13:44, Cameron Simpson <cs at cskk.id.au> wrote:
>> On 05Apr2021 13:28, David <bouncingcats at gmail.com> wrote:
>> >Can anyone explain why the module_2.py test fails?
>> >Is it because stderr during module import is not the same as during test?
>> >Is it something to do with mutable defaults?
>> >How to investigate this?
>> >And how can I get the test to pass without changing module_2?
>
>> The code in module_2.py runs at different times.
>
>> When it is imported, sys.stderr is the OS-provided stderr. That
>> reference is kept in MSG_DESTINATION.
>
>> Then your test code runs, and changes sys.stderr. It then runs msg(),
>> which writes to the _original_ sys.stderr as preserved by
>> MSG_DESTINATION. Thus not captured.
>
>> By contrast, module_1.py looks up sys.stderr inside msg(), and finds the
>> new one the code harness put at sys.stderr. So it writes to the thing
>> that captures stuff.
>
>Thanks for confirming my suspicions so quickly. What you wrote
>makes sense, but there are two points that still puzzle me.
>1) The final line of the pytest failure output seems to shows that
> pytest did capture (or is at least aware of) the stderr message
> from module_2.
Yes. Unsure what's going on there. It could be timing. Suppose this
happens:
- pytest pushes a capturing stderr onto sys.stderr
- pytest loads your module, which imports module_1 and module_2
- the test runner pushes a separate stderr capturer for the test?
- module_1 finds the per-test sys.stderr value
- module_2 finds pytest's outermost capturer (present when it was
imported), and doesn't look up sys.stderr at test time, instead using
the outer capturer
>2) My actual code that I would like to test does look like module_2.
> Is there any way to test it with pytest?
I'd be inclined to give msg() an optional file= parameter:
def msg(*args, file=None):
if file is None:
file = MSG_DESTINATION
print(*args, file=file)
Then your test code can go:
msg("a", "message", file=sys.stderr)
which looks up sys.stderr as it is inside the test itself, and passes it
to msg(). Thus captured.
If you truly need to test msg() _without_ the file= parameter, you could
monkey patch module_2:
old_MSG_DESTINATION = module_2.MSG_DESTINATION
module_2.MSG_DESTINATION = sys.stderr
# now the module_2 module has an updated reference for sys.stderr
...
msg("a", "message")
...
module_2.MSG_DESTINATION = old_MSG_DESTINATION
# normality restored
Cheers,
Cameron Simpson <cs at cskk.id.au>
More information about the Python-list
mailing list