Closed jiasli closed 3 years ago
Remove unnecessary cli_ctx reference, which makes pytest --capture=no fail:
cli_ctx
pytest --capture=no
RecursionError: maximum recursion depth exceeded
When pytest captures stdout,
pytest
stdout
io.FileIO
sys.stdout
CLI
CLI.out_file
tests.util.redirect_io
io.StringIO
Since CLI.out_file doesn't equal sys.__stdout__ (the original stdout), it won't be replaced with a colorama.ansitowin32.StreamWrapper:
sys.__stdout__
colorama.ansitowin32.StreamWrapper
https://github.com/microsoft/knack/blob/0d0308bc567f2d25654bb39201c02ab262d7ce0a/knack/cli.py#L195-L197
As PreviewItem (same for ExperimentalItem and Deprecated) keeps a reference to cli_ctx, during invocation
PreviewItem
ExperimentalItem
Deprecated
https://github.com/microsoft/knack/blob/fe3bf5d3a79a3dd2ce5ddb0c38d93843a3380f6f/knack/commands.py#L353
Because io.FileIO can't be pickled, copy.deepcopy fails and reaches
copy.deepcopy
https://github.com/microsoft/knack/blob/5f0bcc2ae416c8f2834db71b49040976e1d7a29d/knack/util.py#L78-L82
This makes cli_ctx not being deepcopied.
However, when pytest is called with pytest --capture=no, it doesn't patch sys.stdout,
Since CLI.out_file equals sys.__stdout__ (the original stdout), it will be replaced with a colorama.ansitowin32.StreamWrapper and be deepcopied.
Because of a circular reference in AnsiToWin32
AnsiToWin32
https://github.com/tartley/colorama/blob/3d8d48a95de10be25b161c914f274ec6c41d3129/colorama/ansitowin32.py#L28-L29
def __getattr__(self, name): return getattr(self.__wrapped, name)
and io.StringIO can be pickled, when self.__wrapped is not set, deepcopy fails with exception RecursionError: maximum recursion depth exceeded.
self.__wrapped
deepcopy
To repro, use this simple script:
import colorama import copy import sys import io sys.stdout = io.StringIO() colorama.init() copy.deepcopy(sys.stdout)
Or
class StreamWrapper(object): def __init__(self, wrapped, converter): # self.__wrapped = wrapped self.__convertor = converter def __getattr__(self, name): return getattr(self.__wrapped, name) sr = StreamWrapper("a", "b") hasattr(sr, '__setstate__')
Description
Remove unnecessary
cli_ctx
reference, which makespytest --capture=no
fail:Root cause
When
pytest
capturesstdout
,pytest
usesio.FileIO
to patchsys.stdout
CLI
,CLI.out_file
points toio.FileIO
objecthttps://github.com/microsoft/knack/blob/0d0308bc567f2d25654bb39201c02ab262d7ce0a/knack/cli.py#L33
tests.util.redirect_io
usesio.StringIO
to patchsys.stdout
, but this doesn't affect the existingCLI.out_file
Since
CLI.out_file
doesn't equalsys.__stdout__
(the originalstdout
), it won't be replaced with acolorama.ansitowin32.StreamWrapper
:https://github.com/microsoft/knack/blob/0d0308bc567f2d25654bb39201c02ab262d7ce0a/knack/cli.py#L195-L197
As
PreviewItem
(same forExperimentalItem
andDeprecated
) keeps a reference tocli_ctx
, during invocationhttps://github.com/microsoft/knack/blob/fe3bf5d3a79a3dd2ce5ddb0c38d93843a3380f6f/knack/commands.py#L353
PreviewItem
is deepcopied ->cli_ctx
is deepcopied ->CLI.out_file
is deepcopiedBecause
io.FileIO
can't be pickled,copy.deepcopy
fails and reacheshttps://github.com/microsoft/knack/blob/5f0bcc2ae416c8f2834db71b49040976e1d7a29d/knack/util.py#L78-L82
This makes
cli_ctx
not being deepcopied.However, when
pytest
is called withpytest --capture=no
, it doesn't patchsys.stdout
,CLI
,CLI.out_file
equalssys.__stdout__
tests.util.redirect_io
usesio.StringIO
to patchsys.stdout
, but this doesn't affect the existingCLI.out_file
Since
CLI.out_file
equalssys.__stdout__
(the originalstdout
), it will be replaced with acolorama.ansitowin32.StreamWrapper
and be deepcopied.Because of a circular reference in
AnsiToWin32
https://github.com/tartley/colorama/blob/3d8d48a95de10be25b161c914f274ec6c41d3129/colorama/ansitowin32.py#L28-L29
and
io.StringIO
can be pickled, whenself.__wrapped
is not set,deepcopy
fails with exceptionRecursionError: maximum recursion depth exceeded
.To repro, use this simple script:
Or