Skip to content

Commit 78c0915

Browse files
committed
fix: next/prev links in HTML report don't link to skipped files.
Previously, the next link might refer to a file that was skipped because it was empty or 100% covered. Now they do not.
1 parent fa4e6d1 commit 78c0915

File tree

1 file changed

+61
-63
lines changed

1 file changed

+61
-63
lines changed

coverage/html.py

Lines changed: 61 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,15 @@ def data_for_file(self, fr, analysis):
122122
return file_data
123123

124124

125+
class FileToReport:
126+
"""A file we're considering reporting."""
127+
def __init__(self, fr, analysis):
128+
self.fr = fr
129+
self.analysis = analysis
130+
self.rootname = flat_rootname(fr.relative_filename())
131+
self.html_filename = self.rootname + ".html"
132+
133+
125134
class HtmlReporter:
126135
"""HTML reporting."""
127136

@@ -207,40 +216,50 @@ def report(self, morfs):
207216
self.incr.check_global_data(self.config, self.pyfile_html_source)
208217

209218
# Process all the files. For each page we need to supply a link
210-
# to the next page. Therefore in each iteration of the loop we
211-
# work on the fr and analysis from the previous iteration. We
212-
# also need a link to the preceding page (i.e. 2 before the
213-
# current iteration).
214-
analysis_to_report = get_analysis_to_report(self.coverage, morfs)
215-
pluprev_fr, prev_fr = None, None
216-
prev_analysis = None
217-
218-
for fr, analysis in analysis_to_report:
219-
if prev_fr is not None:
220-
self.html_file(prev_fr, prev_analysis, pluprev_fr, fr)
219+
# to the next and previous page.
220+
files_to_report = []
221+
222+
for fr, analysis in get_analysis_to_report(self.coverage, morfs):
223+
ftr = FileToReport(fr, analysis)
224+
should = self.should_report_file(ftr)
225+
if should:
226+
files_to_report.append(ftr)
221227
else:
222-
# This is the first file processed
223-
self.first_fr = fr
224-
pluprev_fr, prev_fr, prev_analysis = prev_fr, fr, analysis
228+
file_be_gone(os.path.join(self.directory, ftr.html_filename))
225229

226-
# One more iteration for the final file. (Or not, if there are
227-
# no files at all.)
228-
if prev_fr is not None:
229-
self.html_file(prev_fr, prev_analysis, pluprev_fr, None)
230-
# This is the last file processed
231-
self.final_fr = prev_fr
230+
for i, ftr in enumerate(files_to_report):
231+
if i == 0:
232+
prev_html = "index.html"
233+
else:
234+
prev_html = files_to_report[i - 1].html_filename
235+
if i == len(files_to_report) - 1:
236+
next_html = "index.html"
237+
else:
238+
next_html = files_to_report[i + 1].html_filename
239+
self.write_html_file(ftr, prev_html, next_html)
232240

233241
if not self.all_files_nums:
234242
raise NoDataError("No data to report.")
235243

236244
self.totals = sum(self.all_files_nums)
237245

238246
# Write the index file.
239-
self.index_file()
247+
if files_to_report:
248+
first_html = files_to_report[0].html_filename
249+
final_html = files_to_report[-1].html_filename
250+
else:
251+
first_html = final_html = "index.html"
252+
self.index_file(first_html, final_html)
240253

241254
self.make_local_static_report_files()
242255
return self.totals.n_statements and self.totals.pc_covered
243256

257+
def make_directory(self):
258+
"""Make sure our htmlcov directory exists."""
259+
ensure_dir(self.directory)
260+
if not os.listdir(self.directory):
261+
self.directory_was_empty = True
262+
244263
def make_local_static_report_files(self):
245264
"""Make local instances of static files for HTML report."""
246265
# The files we provide must always be copied.
@@ -258,27 +277,10 @@ def make_local_static_report_files(self):
258277
if self.extra_css:
259278
shutil.copyfile(self.config.extra_css, os.path.join(self.directory, self.extra_css))
260279

261-
def html_file(self, fr, analysis, prev_fr, next_fr):
262-
"""Generate an HTML file for one source file."""
263-
rootname = flat_rootname(fr.relative_filename())
264-
html_filename = rootname + ".html"
265-
if prev_fr is not None:
266-
prev_html = flat_rootname(prev_fr.relative_filename()) + ".html"
267-
else:
268-
prev_html = "index.html"
269-
270-
if next_fr is not None:
271-
next_html = flat_rootname(next_fr.relative_filename()) + ".html"
272-
else:
273-
next_html = "index.html"
274-
275-
ensure_dir(self.directory)
276-
if not os.listdir(self.directory):
277-
self.directory_was_empty = True
278-
html_path = os.path.join(self.directory, html_filename)
279-
280+
def should_report_file(self, ftr):
281+
"""Determine if we'll report this file."""
280282
# Get the numbers for this file.
281-
nums = analysis.numbers
283+
nums = ftr.analysis.numbers
282284
self.all_files_nums.append(nums)
283285

284286
if self.skip_covered:
@@ -287,24 +289,28 @@ def html_file(self, fr, analysis, prev_fr, next_fr):
287289
no_missing_branches = (nums.n_partial_branches == 0)
288290
if no_missing_lines and no_missing_branches:
289291
# If there's an existing file, remove it.
290-
file_be_gone(html_path)
291292
self.skipped_covered_count += 1
292-
return
293+
return False
293294

294295
if self.skip_empty:
295296
# Don't report on empty files.
296297
if nums.n_statements == 0:
297-
file_be_gone(html_path)
298298
self.skipped_empty_count += 1
299-
return
299+
return False
300+
301+
return True
302+
303+
def write_html_file(self, ftr, prev_html, next_html):
304+
"""Generate an HTML file for one source file."""
305+
self.make_directory()
300306

301307
# Find out if the file on disk is already correct.
302-
if self.incr.can_skip_file(self.data, fr, rootname):
303-
self.file_summaries.append(self.incr.index_info(rootname))
308+
if self.incr.can_skip_file(self.data, ftr.fr, ftr.rootname):
309+
self.file_summaries.append(self.incr.index_info(ftr.rootname))
304310
return
305311

306312
# Write the HTML page for this file.
307-
file_data = self.datagen.data_for_file(fr, analysis)
313+
file_data = self.datagen.data_for_file(ftr.fr, ftr.analysis)
308314
for ldata in file_data.lines:
309315
# Build the HTML for the line.
310316
html = []
@@ -348,6 +354,7 @@ def html_file(self, fr, analysis, prev_fr, next_fr):
348354
css_classes.append(self.template_globals['category'][ldata.category])
349355
ldata.css_class = ' '.join(css_classes) or "pln"
350356

357+
html_path = os.path.join(self.directory, ftr.html_filename)
351358
html = self.source_tmpl.render({
352359
**file_data.__dict__,
353360
'prev_html': prev_html,
@@ -357,15 +364,16 @@ def html_file(self, fr, analysis, prev_fr, next_fr):
357364

358365
# Save this file's information for the index file.
359366
index_info = {
360-
'nums': nums,
361-
'html_filename': html_filename,
362-
'relative_filename': fr.relative_filename(),
367+
'nums': ftr.analysis.numbers,
368+
'html_filename': ftr.html_filename,
369+
'relative_filename': ftr.fr.relative_filename(),
363370
}
364371
self.file_summaries.append(index_info)
365-
self.incr.set_index_info(rootname, index_info)
372+
self.incr.set_index_info(ftr.rootname, index_info)
366373

367-
def index_file(self):
374+
def index_file(self, first_html, final_html):
368375
"""Write the index.html file for this report."""
376+
self.make_directory()
369377
index_tmpl = Templite(read_data("index.html"), self.template_globals)
370378

371379
skipped_covered_msg = skipped_empty_msg = ""
@@ -376,16 +384,6 @@ def index_file(self):
376384
n = self.skipped_empty_count
377385
skipped_empty_msg = f"{n} empty file{plural(n)} skipped."
378386

379-
if self.first_fr is not None:
380-
first_html = flat_rootname(self.first_fr.relative_filename()) + ".html"
381-
else:
382-
first_html = "index.html"
383-
384-
if self.final_fr is not None:
385-
final_html = flat_rootname(self.final_fr.relative_filename()) + ".html"
386-
else:
387-
final_html = "index.html"
388-
389387
html = index_tmpl.render({
390388
'files': self.file_summaries,
391389
'totals': self.totals,

0 commit comments

Comments
 (0)