Skip to content

fix: sysmon coverage for nested context managers#2123

Open
miketheman wants to merge 5 commits intocoveragepy:mainfrom
miketheman:miketheman/sysmon-dynamic
Open

fix: sysmon coverage for nested context managers#2123
miketheman wants to merge 5 commits intocoveragepy:mainfrom
miketheman:miketheman/sysmon-dynamic

Conversation

@miketheman
Copy link
Contributor

@miketheman miketheman commented Jan 27, 2026

Resolves #1987

  • Extract helper for frame references
  • Track last lines during sysmon branch coverage to catch them all, even during exceptions

Signed-off-by: Mike Fiedler <miketheman@gmail.com>
Signed-off-by: Mike Fiedler <miketheman@gmail.com>
Signed-off-by: Mike Fiedler <miketheman@gmail.com>
@miketheman miketheman changed the title test: nested context managers with sysmon fix: sysmon coverage for nested context managers Jan 27, 2026
Signed-off-by: Mike Fiedler <miketheman@gmail.com>
@miketheman miketheman force-pushed the miketheman/sysmon-dynamic branch from fc197cc to 02cca71 Compare January 27, 2026 21:47
@miketheman miketheman marked this pull request as ready for review January 27, 2026 22:02
@nedbat
Copy link
Member

nedbat commented Jan 28, 2026

Can you tell me more about the theory of this fix? I was surprised to see UNWIND events handled, since this problem didn't involve exceptions. Indeed, deleting that addition didn't change the outcome. We don't need the UNWIND handling.

What does last_lines do for us here and how does it fix the measurement of with statements?

@miketheman
Copy link
Contributor Author

Can you tell me more about the theory of this fix? I was surprised to see UNWIND events handled, since this problem didn't involve exceptions. Indeed, deleting that addition didn't change the outcome. We don't need the UNWIND handling.

I can back that out, however my understanding is if there's an observed exception during the test suite run, memory will bloat since last_lines won't be "emptied" as a result.

What does last_lines do for us here and how does it fix the measurement of with statements?

It maps each active function call (by frame id) to the last line we saw in it.
The same function can be called multiple times, and each call needs its own tracking.

Why do we need to track stuff? When we see a LINE event, remember it. When we see the next LINE event, record the transition.

An example:

LINE event: line 5 → remember "last line was 5"
LINE event: line 6 → record arc (5→6), remember "last line was 6"
LINE event: line 7 → record arc (6→7), remember "last line was 7"

Previously, sysmon would say "I've seen line 5, don't tell me about it again." Now we need to hear about every line execution to track transitions.

@nedbat
Copy link
Member

nedbat commented Jan 28, 2026

Oh, I hadn't noticed this yet:

        # Don't return DISABLE: keep getting LINE events for arc tracking.
        return None

This undoes the entire benefit of sys.monitoring. If we're going to change that we might as well use the C tracer.

Instead of disabling all line visits for all lines, only do so when we encounter
a multi-visit, like in a `with` block, thus preserving most of sysmon's
performance benefits in most cases.

Signed-off-by: Mike Fiedler <miketheman@gmail.com>
@miketheman
Copy link
Contributor Author

Oh, I hadn't noticed this yet:

        # Don't return DISABLE: keep getting LINE events for arc tracking.
        return None

This undoes the entire benefit of sys.monitoring. If we're going to change that we might as well use the C tracer.

Very good point - shows how much I know about sysmon! 🤦

I took another pass at this, and added a helper method that checks the bytecode and makes a decision based on that, so the sysmon performance would be preserved for most code except those using these deeply-nested context managers.

Let me know what you think!

return jumps


def multi_visit_lines(code: CodeType) -> set[TLineNo]:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is interesting. Can you show the dis output of the with statements to help me understand what this function is identifying?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tried to get some information this way, by replacing the behavior from InstructionWalker with inline calls to dis - I'm not 100% certain it's right, but maybe it helps? https://gist.github.com/miketheman/0ff15be2efea2573ae46e52bef069c77#file-debug1_output-txt

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.

3.14: branch coverage incorrect with triple-nested context manager

2 participants