Skip to content

Treat the currently bound standard streams as unstable runtime values - #157

Merged
LuKrO2011 merged 3 commits into
se2p:mainfrom
Esmail-ibraheem:fix/rebound-std-streams
Oct 28, 2026
Merged

LuKrO2011 merged 3 commits into
se2p:mainfrom
Esmail-ibraheem:fix/rebound-std-streams

Conversation

@Esmail-ibraheem

Copy link
Copy Markdown
Contributor

The identity blacklist of process-dependent values is captured when the module is imported in the master process. multiprocess then rebinds sys.stdin to os.devnull in the worker, so a SUT that stores sys.stdin (e.g., shlex.shlex()) got type assertions on the worker's stream, which fail under pytest's default capture. Also compare against the currently bound sys.stdin, sys.stdout and sys.stderr at check time (closes #156).

The identity blacklist of process-dependent values is captured when the
module is imported in the master process. multiprocess then rebinds
sys.stdin to os.devnull in the worker, so a SUT that stores sys.stdin
(e.g., shlex.shlex()) got type assertions on the worker's stream, which
fail under pytest's default capture. Also compare against the currently
bound sys.stdin, sys.stdout and sys.stderr at check time (closes se2p#156).
@codecov-commenter

codecov-commenter commented Oct 26, 2026 •

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 93.51%. Comparing base (bea483d) to head (4d15c0c).

Additional details and impacted files

Impacted file tree graph

@@            Coverage Diff             @@
##             main     #157      +/-   ##
==========================================
- Coverage   93.52%   93.51%   -0.02%     
==========================================
  Files         433      433              
  Lines       59935    59963      +28     
  Branches     5414     5415       +1     
==========================================
+ Hits        56055    56075      +20     
- Misses       2836     2840       +4     
- Partials     1044     1048       +4     
Files with missing lines Coverage Δ
src/pynguin/analyses/module.py 82.04% <100.00%> (ø)
src/pynguin/assertion/assertiontraceobserver.py 91.18% <100.00%> (ø)
tests/analyses/test_native_libraries.py 94.68% <100.00%> (+0.08%) ⬆️
...assertion/test_assertion_generation_integration.py 98.65% <100.00%> (+0.05%) ⬆️
tests/assertion/test_assertiontraceobserver.py 99.80% <100.00%> (+<0.01%) ⬆️

... and 2 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@LuKrO2011

Copy link
Copy Markdown
Member

Thank you for the PR @Esmail-ibraheem.

This fixes the shlex.shlex() case because observers run inside OutputSuppressionContext, so the matched sys.stdin is its devnull file.

Yet the root cause is that stream types report __module__ == "_io", while MODULE_BLACKLIST lists io, so file objects are never blacklisted. With this PR, a SUT that stores sys.stdin.buffer still gets a _io.BufferedReader type assertion that fails under plain pytest.

Can we maybe drop the live sys.std* check and map _io to io in _is_blacklisted_module` instead?

Please also add a regression test that goes through the real executor (e.g. shlex.shlex() and sys.stdin.buffer).

The stream types of io report "_io" as their module, while
MODULE_BLACKLIST lists "io", so file objects were never blacklisted.
Matching the live sys.stdin only covered the stream that
OutputSuppressionContext binds during execution, but not, e.g.,
sys.stdin.buffer. Drop the live check and treat "_io" as "io" in
_is_blacklisted_module instead. Add a regression test that runs a
shlex.shlex() and a holder of sys.stdin/sys.stdin.buffer through the
real executor.
@Esmail-ibraheem

Copy link
Copy Markdown
Contributor Author

Done in f2fa5dc. I dropped the live sys.std* check, and _is_blacklisted_module now treats _io as io, so all io stream types (TextIOWrapper, BufferedReader, FileIO, StringIO, BytesIO) are blacklisted. I added test_no_assertions_on_std_streams, which runs shlex.shlex() and an object holding sys.stdin / sys.stdin.buffer through the real TestCaseExecutor + AssertionGenerator. On main it fails with the _io.BufferedReader assertion. You're right about the mechanism: the stream my check matched was OutputSuppressionContext's devnull. One side effect: StringIO/BytesIO values no longer get assertions either.

@LuKrO2011

Copy link
Copy Markdown
Member

Rather than a hardcoded {"_io": "io"}, could we reuse the top_level.lstrip("_")mapping from analyses/module.py? That would also cover other C-implemented blacklisted modules. Thoughts on that?

Replace the hard-coded {"_io": "io"} mapping with public_top_level_module,
the top_level.lstrip("_") normalisation that _c_is_whitelisted already
used. _is_blacklisted_module now also matches other C implementations of
blacklisted modules, e.g., _asyncio, _csv, _pickle, _queue or _socket.
@Esmail-ibraheem

Copy link
Copy Markdown
Contributor Author

Agreed. Done in 4d15c0c: I extracted public_top_level_module() (the top_level.lstrip("_") normalisation) in analyses/module.py, and both _c_is_whitelisted and _is_blacklisted_module use it now. The raw top-level check stays for private blacklist entries like _thread. Besides _io, this now also covers _asyncio (Future/Task), _csv, _pickle, _queue (Empty, SimpleQueue), _socket, _ssl, _contextvars, _ctypes and others; for example, queue. Empty was not blacklisted before, while queue.Full was. Full suite: 5904 passed on 3.13, 5554 on 3.10.

@LuKrO2011
LuKrO2011 merged commit ca5c1b0 into se2p:main Oct 28, 2026
16 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Generated assertions on sys.stdin fail under pytest's default output capture

3 participants