Skip to content

Commit 2b284f0

Browse files
committed
FIX: Filter LOG statements from coverage data, not just HTML report
The previous implementation only filtered LOG statements in the HTML report using genhtml --omit-lines, but the coverage.xml sent to Azure DevOps still included them, causing incorrect coverage percentage (25%). Changes: - Add Python filter to remove DA entries for LOG macro lines from total.info - Filter uses regex pattern '\bLOG[A-Z_]*\s*\(' to match all LOG variants - Properly recalculate LH/LF counters after filtering - Both genhtml and lcov_cobertura now use the filtered data This ensures consistent coverage metrics across HTML reports and Azure DevOps.
1 parent d9f3988 commit 2b284f0

1 file changed

Lines changed: 101 additions & 8 deletions

File tree

generate_codecov.sh

Lines changed: 101 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -88,20 +88,113 @@ echo "[STEP 4] Merging Python + C++ coverage"
8888
echo "==================================="
8989

9090
# Merge LCOV reports (ignore inconsistencies in Python LCOV export)
91-
# Use --omit-lines to exclude LOG macro calls from coverage
92-
# The regex matches LOG, LOG_ERROR, LOG_WARNING, etc.
93-
echo "[ACTION] Merging Python and C++ coverage with LOG exclusion"
94-
lcov -a python-coverage.info -a cpp-coverage.info -o total.info \
95-
--ignore-errors inconsistent,corrupt \
96-
--omit-lines '\bLOG[A-Z_]*\('
91+
echo "[ACTION] Merging Python and C++ coverage"
92+
lcov -a python-coverage.info -a cpp-coverage.info -o total-unfiltered.info \
93+
--ignore-errors inconsistent,corrupt
9794

98-
echo "[INFO] LOG statements excluded from coverage using --omit-lines"
95+
echo "[INFO] Coverage merged successfully"
9996

10097
# Normalize paths so everything starts from mssql_python/
10198
echo "[ACTION] Normalizing paths in LCOV report"
102-
sed -i "s|$(pwd)/||g" total.info
99+
sed -i "s|$(pwd)/||g" total-unfiltered.info
100+
101+
# Filter out LOG statements from coverage data
102+
# Since we joined multi-line LOGs during build, they're now on single lines
103+
# We can filter them using a regex pattern
104+
echo "[ACTION] Filtering LOG statements from coverage data"
105+
python3 - <<'PYTHON_FILTER'
106+
import re
107+
import os
108+
109+
with open('total-unfiltered.info', 'r') as f:
110+
content = f.read()
111+
112+
# Process file section by section
113+
sections = content.split('SF:')
114+
output_sections = []
115+
116+
if sections[0].strip():
117+
output_sections.append(sections[0]) # Keep any header
118+
119+
total_excluded = 0
120+
LOG_PATTERN = re.compile(r'\bLOG[A-Z_]*\s*\(')
121+
122+
for section in sections[1:]:
123+
if not section.strip():
124+
continue
125+
126+
lines = section.split('\n')
127+
source_file = lines[0].strip()
128+
129+
# Read source file to find lines with LOG macros
130+
excluded_lines = set()
131+
if os.path.exists(source_file):
132+
try:
133+
with open(source_file, 'r', encoding='utf-8', errors='ignore') as src:
134+
for line_num, src_line in enumerate(src, 1):
135+
if LOG_PATTERN.search(src_line):
136+
excluded_lines.add(line_num)
137+
except Exception as e:
138+
print(f"[WARNING] Could not read {source_file}: {e}")
139+
140+
# Process section lines - filter DA entries and recalculate LH/LF
141+
new_section = [f'SF:{source_file}']
142+
da_entries = []
143+
other_lines = []
144+
lines_hit = 0
145+
lines_found = 0
146+
147+
for line in lines[1:]:
148+
line = line.rstrip()
149+
if not line:
150+
continue
151+
152+
if line.startswith('DA:'):
153+
# DA:line_number,hit_count
154+
match = re.match(r'DA:(\d+),(\d+)', line)
155+
if match:
156+
line_num = int(match.group(1))
157+
hit_count = int(match.group(2))
158+
159+
if line_num not in excluded_lines:
160+
da_entries.append(line)
161+
lines_found += 1
162+
if hit_count > 0:
163+
lines_hit += 1
164+
else:
165+
total_excluded += 1
166+
elif line.startswith('LH:') or line.startswith('LF:'):
167+
# Skip old counters - we'll add new ones
168+
continue
169+
else:
170+
other_lines.append(line)
171+
172+
# Rebuild section with correct order: SF, DA entries, other, LH/LF, end_of_record
173+
new_section.extend(da_entries)
174+
175+
# Add other lines (FN, FNDA, etc.) but keep end_of_record for last
176+
for line in other_lines:
177+
if line != 'end_of_record':
178+
new_section.append(line)
179+
180+
# Add recalculated counters
181+
new_section.append(f'LH:{lines_hit}')
182+
new_section.append(f'LF:{lines_found}')
183+
new_section.append('end_of_record')
184+
185+
output_sections.append('\n'.join(new_section))
186+
187+
# Write filtered coverage with recalculated statistics
188+
with open('total.info', 'w') as f:
189+
f.write('\n'.join(output_sections))
190+
191+
print(f"[INFO] Filtered {total_excluded} LOG statement lines from coverage")
192+
PYTHON_FILTER
193+
194+
echo "[INFO] LOG statements filtered from coverage data"
103195

104196
# Generate full HTML report
197+
echo "[ACTION] Generating HTML coverage report"
105198
genhtml total.info \
106199
--output-directory unified-coverage \
107200
--quiet \

0 commit comments

Comments
 (0)