coveragepy
coveragepy copied to clipboard
Problem with html rendering of f-string with double curly brackets
Describe the bug
Generated html coverage report of file with f-string with double curly brackets like: f"{{this is not interpolated}}" is wrongly rendered by removing one set of bracket.
To Reproduce
- What version of Python are you using?
Python 3.13.3 - What version of coverage.py shows the problem?
Coverage.py, version 7.8.2 with C extension
coverage debug sys
-- sys -------------------------------------------------------
coverage_version: 7.8.2
coverage_module: D:\Temp\python_coverage\.venv\Lib\site-packages\coverage\__init__.py
core: -none-
CTracer: available from D:\Temp\python_coverage\.venv\Lib\site-packages\coverage\tracer.cp313-win_amd64.pyd
plugins.file_tracers: -none-
plugins.configurers: -none-
plugins.context_switchers: -none-
configs_attempted: D:\Temp\python_coverage\.coveragerc
D:\Temp\python_coverage\setup.cfg
D:\Temp\python_coverage\tox.ini
D:\Temp\python_coverage\pyproject.toml
configs_read: -none-
config_file: None
config_contents: -none-
data_file: -none-
python: 3.13.3 (tags/v3.13.3:6280bb5, Apr 8 2025, 14:47:33) [MSC v.1943 64 bit (AMD64)]
platform: Windows-10-10.0.19045-SP0
implementation: CPython
gil_enabled: True
executable: D:\Temp\python_coverage\.venv\Scripts\python.exe
def_encoding: utf-8
fs_encoding: utf-8
pid: 6600
cwd: D:\Temp\python_coverage
path: D:\Temp\python_coverage\.venv\Scripts\coverage.exe
C:\Python313\python313.zip
C:\Python313\DLLs
C:\Python313\Lib
C:\Python313
D:\Temp\python_coverage\.venv
D:\Temp\python_coverage\.venv\Lib\site-packages
environment: TEMP = C:\Users\mgouin\AppData\Local\Temp
TMP = C:\Users\mgouin\AppData\Local\Temp
command_line: D:\Temp\python_coverage\.venv\Scripts\coverage debug sys
sqlite3_sqlite_version: 3.49.1
sqlite3_temp_store: 0
sqlite3_compile_options: ATOMIC_INTRINSICS=0, COMPILER=msvc-1943, DEFAULT_AUTOVACUUM,
DEFAULT_CACHE_SIZE=-2000, DEFAULT_FILE_FORMAT=4,
DEFAULT_JOURNAL_SIZE_LIMIT=-1, DEFAULT_MMAP_SIZE=0, DEFAULT_PAGE_SIZE=4096,
DEFAULT_PCACHE_INITSZ=20, DEFAULT_RECURSIVE_TRIGGERS,
DEFAULT_SECTOR_SIZE=4096, DEFAULT_SYNCHRONOUS=2,
DEFAULT_WAL_AUTOCHECKPOINT=1000, DEFAULT_WAL_SYNCHRONOUS=2,
DEFAULT_WORKER_THREADS=0, DIRECT_OVERFLOW_READ, ENABLE_FTS3, ENABLE_FTS4,
ENABLE_FTS5, ENABLE_MATH_FUNCTIONS, ENABLE_RTREE, MALLOC_SOFT_LIMIT=1024,
MAX_ATTACHED=10, MAX_COLUMN=2000, MAX_COMPOUND_SELECT=500,
MAX_DEFAULT_PAGE_SIZE=8192, MAX_EXPR_DEPTH=1000, MAX_FUNCTION_ARG=1000,
MAX_LENGTH=1000000000, MAX_LIKE_PATTERN_LENGTH=50000,
MAX_MMAP_SIZE=0x7fff0000, MAX_PAGE_COUNT=0xfffffffe, MAX_PAGE_SIZE=65536,
MAX_SQL_LENGTH=1000000000, MAX_TRIGGER_DEPTH=1000,
MAX_VARIABLE_NUMBER=32766, MAX_VDBE_OP=250000000, MAX_WORKER_THREADS=8,
MUTEX_W32, OMIT_AUTOINIT, SYSTEM_MALLOC, TEMP_STORE=1, THREADSAFE=1
- What versions of what packages do you have installed? The output of
pip freezeis helpful.
pip freeze
coverage==7.8.2
- What code shows the problem?
Simple file: main.py :
i = 54
print(f"{{this is not an interpolated variable}} but this is: i = {i}")
- What commands should we run to reproduce the problem? Be specific. Include everything, even
git clone,pip install, and so on. Explain like we're five!
Create the main.py file with content above.
Generate coverage:
coverage run main.py
Generate html report:
coverage html
Open the html report in htmlcov/main_py.html
You see that the report is not showing the correct content of the print line.
In plain text:
print(f"{ this is not an interpolated variable} but this is: i = {i}")
Expected behavior
The html rendered line should match exactly the source code which should be:
print(f"{{this is not an interpolated variable}} but this is: i = {i}")
Additional context n/a
Hmm, I don't see the same behavior. I tried with 3.13. Then I tried running under 3.13 and generating the html with 3.9, then I did the reverse. In all cases the HTML looked correct:
Something else must be different. Can you attach the HTML file with the problem?
Apparently, I cannot attach html files:
However, I looked at it myself and it is clear the problem:
<!DOCTYPE html>
<html lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>Coverage for main.py: 100%</title>
<link rel="icon" sizes="32x32" href="favicon_32_cb_58284776.png">
<link rel="stylesheet" href="style_cb_718ce007.css" type="text/css">
<script src="coverage_html_cb_497bf287.js" defer></script>
</head>
<body class="pyfile">
<header>
<div class="content">
<h1>
<span class="text">Coverage for </span><b>main.py</b>:
<span class="pc_cov">100%</span>
</h1>
<aside id="help_panel_wrapper">
<input id="help_panel_state" type="checkbox">
<label for="help_panel_state">
<img id="keyboard_icon" src="keybd_closed_cb_ce680311.png" alt="Show/hide keyboard shortcuts">
</label>
<div id="help_panel">
<p class="legend">Shortcuts on this page</p>
<div class="keyhelp">
<p>
<kbd>r</kbd>
<kbd>m</kbd>
<kbd>x</kbd>
toggle line displays
</p>
<p>
<kbd>j</kbd>
<kbd>k</kbd>
next/prev highlighted chunk
</p>
<p>
<kbd>0</kbd> (zero) top of page
</p>
<p>
<kbd>1</kbd> (one) first highlighted chunk
</p>
<p>
<kbd>[</kbd>
<kbd>]</kbd>
prev/next file
</p>
<p>
<kbd>u</kbd> up to the index
</p>
<p>
<kbd>?</kbd> show/hide this help
</p>
</div>
</div>
</aside>
<h2>
<span class="text">2 statements </span>
<button type="button" class="run button_toggle_run" value="run" data-shortcut="r" title="Toggle lines run">2<span class="text"> run</span></button>
<button type="button" class="mis show_mis button_toggle_mis" value="mis" data-shortcut="m" title="Toggle lines missing">0<span class="text"> missing</span></button>
<button type="button" class="exc show_exc button_toggle_exc" value="exc" data-shortcut="x" title="Toggle lines excluded">0<span class="text"> excluded</span></button>
</h2>
<p class="text">
<a id="prevFileLink" class="nav" href="index.html">« prev</a>
<a id="indexLink" class="nav" href="index.html">^ index</a>
<a id="nextFileLink" class="nav" href="index.html">» next</a>
<a class="nav" href="https://coverage.readthedocs.io/en/7.8.2">coverage.py v7.8.2</a>,
created at 2025-06-06 10:10 -0400
</p>
<aside class="hidden">
<button type="button" class="button_next_chunk" data-shortcut="j"></button>
<button type="button" class="button_prev_chunk" data-shortcut="k"></button>
<button type="button" class="button_top_of_page" data-shortcut="0"></button>
<button type="button" class="button_first_chunk" data-shortcut="1"></button>
<button type="button" class="button_prev_file" data-shortcut="["></button>
<button type="button" class="button_next_file" data-shortcut="]"></button>
<button type="button" class="button_to_index" data-shortcut="u"></button>
<button type="button" class="button_show_hide_help" data-shortcut="?"></button>
</aside>
</div>
</header>
<main id="source">
<p class="run"><span class="n"><a id="t1" href="#t1">1</a></span><span class="t"><span class="nam">i</span> <span class="op">=</span> <span class="num">54</span> </span><span class="r"></span></p>
<p class="run"><span class="n"><a id="t2" href="#t2">2</a></span><span class="t"><span class="nam">print</span><span class="op">(</span><span class="fst">f"</span><span class="fst">{</span> <span class="fst">this is not an interpolated variable}</span> <span class="fst"> but this is: i = </span><span class="op">{</span><span class="nam">i</span><span class="op">}</span><span class="fst">"</span><span class="op">)</span> </span><span class="r"></span></p>
<p class="pln"><span class="n"><a id="t3" href="#t3">3</a></span><span class="t"> </span><span class="r"></span></p>
</main>
<footer>
<div class="content">
<p>
<a class="nav" href="index.html">« prev</a>
<a class="nav" href="index.html">^ index</a>
<a class="nav" href="index.html">» next</a>
<a class="nav" href="https://coverage.readthedocs.io/en/7.8.2">coverage.py v7.8.2</a>,
created at 2025-06-06 10:10 -0400
</p>
</div>
</footer>
</body>
</html>
The relevant issue:
<p class="run"><span class="n"><a id="t2" href="#t2">2</a></span><span class="t"><span class="nam">print</span><span class="op">(</span><span class="fst">f"</span><span class="fst">{</span> <span class="fst">this is not an interpolated variable}</span> <span class="fst"> but this is: i = </span><span class="op">{</span><span class="nam">i</span><span class="op">}</span><span class="fst">"</span><span class="op">)</span> </span><span class="r"></span></p>
See there is an empty space:
| here
-----------------------------------V
f"</span><span class="fst">{</span> <span class="fst">this is not
Also:
interpolated variable}</span> <span
Hmm, I'm not sure what I did wrong last time. Now I see the same thing you do. This is a change in the behavior of the tokenize module in Python 3.12. I suspect it won't be considered a Python bug, so I'll have to fix it in coverage.
Ok, thanks for the update!
Mathieu
On Fri., Jun. 6, 2025, 12:11 Ned Batchelder, @.***> wrote:
nedbat left a comment (nedbat/coveragepy#1980) https://github.com/nedbat/coveragepy/issues/1980#issuecomment-2949762379
Hmm, I'm not sure what I did wrong last time. Now I see the same thing you do. This is a change in the behavior of the tokenize module in Python 3.12. I suspect it won't be considered a Python bug, so I'll have to fix it in coverage.
— Reply to this email directly, view it on GitHub https://github.com/nedbat/coveragepy/issues/1980#issuecomment-2949762379, or unsubscribe https://github.com/notifications/unsubscribe-auth/ACYG2PQUCXQJCRIXL67VB5D3CG4RPAVCNFSM6AAAAAB6V33VS2VHI2DSMVQWIX3LMV43OSLTON2WKQ3PNVWWK3TUHMZDSNBZG43DEMZXHE . You are receiving this because you authored the thread.Message ID: @.***>
This is fixed in commit 9f94c87677fd86c7ced9ea2f3cddfcc0b4d5bc2d.
This is now released as part of coverage 7.9.0.
Thanks !