103103
104104# Package names explicitly referenced in test_gdg_rendered.py (dedicated tests)
105105_DEDICATED_PACKAGES : set [str ] = set ()
106+ _DEDICATED_COUNTS : dict [str , int ] = {}
106107
107108
108109def _load_dedicated_packages () -> set [str ]:
@@ -116,6 +117,37 @@ def _load_dedicated_packages() -> set[str]:
116117 return _DEDICATED_PACKAGES
117118
118119
120+ def _count_dedicated_tests (name : str ) -> int :
121+ """Count dedicated test functions that reference a given package.
122+
123+ Looks for functions decorated with ``@pytest.mark.dedicated`` and counts
124+ how many reference the package name in their decorator block + body.
125+ """
126+ global _DEDICATED_COUNTS # noqa: PLW0603
127+ if not _DEDICATED_COUNTS :
128+ test_file = _PROJECT_ROOT / "tests" / "test_gdg_rendered.py"
129+ if not test_file .exists ():
130+ return 0
131+ content = test_file .read_text ()
132+ # Split into function blocks
133+ fn_positions = [
134+ (m .start (), m .group (1 ))
135+ for m in re .finditer (r"\ndef (test_\w+)" , content )
136+ ]
137+ for idx , (pos , _fn_name ) in enumerate (fn_positions ):
138+ # Look for @pytest.mark.dedicated in the decorator block
139+ decorator_block = content [max (0 , pos - 1500 ) : pos ]
140+ if "@pytest.mark.dedicated" not in decorator_block :
141+ continue
142+ # Get function body
143+ end = fn_positions [idx + 1 ][0 ] if idx + 1 < len (fn_positions ) else len (content )
144+ fn_body = content [pos :end ]
145+ search_text = decorator_block + fn_body
146+ for pkg in set (re .findall (r"gdtest_[a-z0-9_]+" , search_text )):
147+ _DEDICATED_COUNTS [pkg ] = _DEDICATED_COUNTS .get (pkg , 0 ) + 1
148+ return _DEDICATED_COUNTS .get (name , 0 )
149+
150+
119151def _compute_coverage (name : str ) -> dict [str , bool ]:
120152 """Compute test coverage levels for a single package.
121153
@@ -1062,6 +1094,7 @@ def _create_test_coverage_page(name: str) -> str:
10621094 color = "#f38ba8"
10631095
10641096 # Build the coverage table rows
1097+ ded_count = _count_dedicated_tests (name )
10651098 rows = []
10661099 for level in _COVERAGE_LEVELS :
10671100 covered = coverage .get (level , False )
@@ -1077,10 +1110,18 @@ def _create_test_coverage_page(name: str) -> str:
10771110 else :
10781111 icon = "❌"
10791112 row_class = "cov-miss"
1113+ # For DED, show the count badge
1114+ level_display = html .escape (level )
1115+ if level == "DED" and ded_count > 0 :
1116+ level_display = (
1117+ f'{ html .escape (level )} '
1118+ f' <span class="ded-count">×{ ded_count } </span>'
1119+ )
1120+ desc = f"{ ded_count } dedicated test{ 's' if ded_count != 1 else '' } for this package"
10801121 rows .append (
10811122 f'<tr class="{ row_class } ">'
10821123 f"<td>{ icon } </td>"
1083- f'<td class="cov-level">{ html . escape ( level ) } </td>'
1124+ f'<td class="cov-level">{ level_display } </td>'
10841125 f'<td class="cov-fn">{ html .escape (test_fn )} </td>'
10851126 f'<td class="cov-desc">{ html .escape (desc )} </td>'
10861127 f"</tr>"
@@ -1197,6 +1238,11 @@ def _create_test_coverage_page(name: str) -> str:
11971238 .cov-miss .cov-fn {{ color: #484e58; }}
11981239 .cov-excluded .cov-fn {{ color: #484e58; }}
11991240 .cov-desc {{ max-width: 300px; }}
1241+ .ded-count {{
1242+ display: inline-block; padding: 1px 6px; margin-left: 4px;
1243+ border-radius: 10px; font-size: 10px; font-weight: 700;
1244+ background: #a6e3a120; color: #a6e3a1; border: 1px solid #a6e3a140;
1245+ }}
12001246 .counter {{ display: flex; gap: 16px; margin-bottom: 16px; }}
12011247 .counter-item {{
12021248 padding: 4px 12px; border-radius: 4px; font-size: 13px; font-weight: 600;
@@ -1528,7 +1574,8 @@ def _create_test_coverage_summary_page(results: list[dict]) -> str:
15281574 else :
15291575 color = "#f38ba8"
15301576 num = ALL_PACKAGES .index (name ) + 1 if name in ALL_PACKAGES else 0
1531- ded_icon = "✓" if p ["coverage" ].get ("DED" ) else ""
1577+ ded_n = _count_dedicated_tests (name )
1578+ ded_cell = f'<span class="ded-badge">{ ded_n } </span>' if ded_n > 0 else "—"
15321579
15331580 # Level dots (pass/miss/excluded)
15341581 dots = []
@@ -1550,7 +1597,7 @@ def _create_test_coverage_summary_page(results: list[dict]) -> str:
15501597 f'<td class="pkg-score">{ p ["score" ]} /{ p ["max" ]} </td>'
15511598 f'<td class="pkg-excl">{ p ["excluded" ]} </td>'
15521599 f'<td class="dot-row">{ dots_html } </td>'
1553- f'<td class="ded-icon">{ ded_icon } </td>'
1600+ f'<td class="ded-icon">{ ded_cell } </td>'
15541601 f"</tr>"
15551602 )
15561603 pkg_table = "\n " .join (pkg_rows )
@@ -1648,7 +1695,12 @@ def _create_test_coverage_summary_page(results: list[dict]) -> str:
16481695 .dot-pass {{ color: #a6e3a1; }}
16491696 .dot-miss {{ color: #f38ba8; }}
16501697 .dot-excl {{ color: #30363d; }}
1651- .ded-icon {{ color: #a6e3a1; font-weight: 700; text-align: center; }}
1698+ .ded-icon {{ text-align: center; }}
1699+ .ded-badge {{
1700+ display: inline-block; padding: 2px 7px; border-radius: 10px;
1701+ font-size: 11px; font-weight: 700; font-family: "SF Mono", monospace;
1702+ background: #a6e3a118; color: #a6e3a1; border: 1px solid #a6e3a140;
1703+ }}
16521704 .hist-container {{
16531705 display: flex; align-items: flex-end; gap: 6px;
16541706 padding: 0 4px;
0 commit comments