Skip to content

Preserve caret positions in Python 3.11+ tracebacks - #91

Open
pablogsal wants to merge 1 commit into
ionelmc:masterfrom
pablogsal:master
Open

pablogsal wants to merge 1 commit into
ionelmc:masterfrom
pablogsal:master

Conversation

@pablogsal

Copy link
Copy Markdown

Python 3.11 introduced detailed error locations with caret indicators
pointing to the exact expression that caused an exception. When
tracebacks are serialized and reconstructed through tblib, this
column position information was lost, making debugging reconstructed
exceptions harder. This fixes issue #76.

The solution captures column offsets from traceback objects and
embeds them in custom co_linetable bytes when reconstructing code
objects. CPython 3.11+ and PyPy 3.11+ use different linetable
formats, so separate encoding functions handle each implementation.
The fix also ensures __traceback_maker is defined in globals during
code execution to prevent NameError at the wrong instruction index,
which would capture incorrect column positions from tb_lasti.

@codecov

codecov Bot commented Jan 25, 2026 •

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 94.78673% with 11 lines in your changes missing coverage. Please review.
✅ Project coverage is 96.64%. Comparing base (d88606a) to head (c9f6bde).

Files with missing lines Patch % Lines
src/tblib/__init__.py 88.09% 5 Missing and 5 partials ⚠️
tests/test_tblib.py 99.14% 0 Missing and 1 partial ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##           master      #91      +/-   ##
==========================================
- Coverage   96.96%   96.64%   -0.32%     
==========================================
  Files           9        9              
  Lines         758      955     +197     
  Branches       53       75      +22     
==========================================
+ Hits          735      923     +188     
- Misses         18       21       +3     
- Partials        5       11       +6     
Flag Coverage Δ
py310 (macos/arm64) 71.77% <45.97%> (-7.46%) ⬇️
py310 (ubuntu/x64) 71.77% <45.97%> (-7.46%) ⬇️
py310 (windows/x64) 71.77% <45.97%> (-7.46%) ⬇️
py311 (macos/arm64) 94.64% <86.72%> (-2.05%) ⬇️
py311 (ubuntu/x64) 94.64% <86.72%> (-2.05%) ⬇️
py311 (windows/x64) 94.64% <86.72%> (-2.05%) ⬇️
py312 (macos/arm64) 94.64% <86.72%> (-2.05%) ⬇️
py312 (ubuntu/x64) 94.64% <86.72%> (-2.05%) ⬇️
py312 (windows/x64) 94.64% <86.72%> (-2.05%) ⬇️
py313 (macos/arm64) 94.64% <86.72%> (-2.05%) ⬇️
py313 (ubuntu/x64) 94.64% <86.72%> (-2.05%) ⬇️
py313 (windows/x64) 94.64% <86.72%> (-2.05%) ⬇️
py314 (macos/arm64) 94.22% <86.72%> (-1.94%) ⬇️
py314 (ubuntu/x64) 94.22% <86.72%> (-1.94%) ⬇️
py314 (windows/x64) 94.22% <86.72%> (-1.94%) ⬇️
py39 (macos/arm64) 71.30% <45.02%> (-7.59%) ⬇️
py39 (ubuntu/x64) 71.30% <45.02%> (-7.59%) ⬇️
py39 (windows/x64) 71.30% <45.02%> (-7.59%) ⬇️
pypy310 (macos/arm64) 69.88% <45.97%> (-6.97%) ⬇️
pypy310 (ubuntu/x64) 69.88% <45.97%> (-6.97%) ⬇️
pypy310 (windows/x64) 69.88% <45.97%> (-6.97%) ⬇️
pypy311 (macos/arm64) 93.28% <89.09%> (-1.03%) ⬇️
pypy311 (ubuntu/x64) 93.28% <89.09%> (-1.03%) ⬇️
pypy311 (windows/x64) 93.28% <89.09%> (-1.03%) ⬇️
pypy39 (macos/arm64) 69.42% <45.02%> (-7.10%) ⬇️
pypy39 (ubuntu/x64) 69.42% <45.02%> (-7.10%) ⬇️
pypy39 (windows/x64) 69.42% <45.02%> (-7.10%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

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

Python 3.11 introduced detailed error locations with caret indicators
pointing to the exact expression that caused an exception. When
tracebacks are serialized and reconstructed through tblib, this
column position information was lost, making debugging reconstructed
exceptions harder. This fixes issue ionelmc#76.

The solution captures column offsets from traceback objects and
embeds them in custom co_linetable bytes when reconstructing code
objects. CPython 3.11+ and PyPy 3.11+ use different linetable
formats, so separate encoding functions handle each implementation.
The fix also ensures __traceback_maker is defined in globals during
code execution to prevent NameError at the wrong instruction index,
which would capture incorrect column positions from tb_lasti.

Add test for __traceback_maker globals bug

Without __traceback_maker in the globals dict during exec, the
LOAD_NAME instruction raises NameError at tb_lasti=2 instead of
RAISE_VARARGS executing at tb_lasti=4. This captures column
positions from the wrong bytecode instruction, showing positions
for just the name '__traceback_maker' (columns 6-23) instead of
the full 'raise __traceback_maker' expression (columns 0-23).

This test verifies the fix catches this by asserting the column
start is 0, not 6, proving the exception originates from the
correct instruction with accurate position information.
hoodmane added a commit to cloudflare/workers-py that referenced this pull request Sep 21, 2026
This changes testlib to propagate error context correctly from the tests inside
workerd back out to the host pytest session. Previously, any error
traceback would always show the host code that looked up the test results which
is test support code and not related to the actual cause of the problem. Now it
shows correct tracebacks. The techniques here are based on similar techniques in
pytest-pyodide.

Using tblib + pickle we serialize exceptions. For robustness we use a custom
pickler that pickles most things into strings, other than builtin types and
types from tblib which we need to handle correctly.

On the unpickling side, we synthesize dummy exception classes on the fly for any
exceptions that are missing. This is a simpler way to deal with the problems
we've had in pytest-pyodide with tracebacks being messed up for exception
classes that are only defined in the Pyodide session.

I used a fork of tblib called pyodide-tblib for the traceback serializing. It
includes ionelmc/python-tblib#91 which fixes column positioning, a fix that
makes multiline positions work correctly, and a fix that gets the first line of
functions correct. This makes the context exactly match what pytest would show
natively.
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.

1 participant