Coverage for tests / test_html.py: 100.000%

682 statements  

« prev     ^ index     » next       coverage.py v7.12.1a0.dev1, created at 2025-11-30 17:57 +0000

1# Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 

2# For details: https://github.com/coveragepy/coveragepy/blob/main/NOTICE.txt 

3 

4"""Tests that HTML generation is awesome.""" 

5 

6from __future__ import annotations 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

7 

8import collections 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

9import datetime 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

10import glob 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

11import json 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

12import os 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

13import os.path 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

14import re 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

15import sys 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

16 

17from html.parser import HTMLParser 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

18from typing import Any, IO 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

19from unittest import mock 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

20 

21import pytest 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

22 

23import coverage 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

24import coverage.html 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

25from coverage import env, Coverage 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

26from coverage.exceptions import NoDataError, NotPython, NoSource 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

27from coverage.files import abs_file, flat_rootname 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

28from coverage.report_core import get_analysis_to_report 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

29from coverage.types import TLineNo, TMorf 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

30 

31from tests import testenv 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

32from tests.coveragetest import CoverageTest, TESTS_DIR 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

33from tests.goldtest import gold_path 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

34from tests.goldtest import compare, contains, contains_rx, doesnt_contain, contains_any 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

35from tests.helpers import assert_coverage_warnings, change_dir 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

36 

37 

38class HtmlTestHelpers(CoverageTest): 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

39 """Methods that help with HTML tests.""" 

40 

41 def create_initial_files(self) -> None: 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

42 """Create the source files we need to run these tests.""" 

43 self.make_file( 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

44 "main_file.py", 

45 """\ 

46 import helper1, helper2 

47 helper1.func1(12) 

48 helper2.func2(12) 

49 """, 

50 ) 

51 self.make_file( 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

52 "helper1.py", 

53 """\ 

54 def func1(x): 

55 if x % 2: 

56 print("odd") 

57 """, 

58 ) 

59 self.make_file( 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

60 "helper2.py", 

61 """\ 

62 def func2(x): 

63 print("x is %d" % x) 

64 """, 

65 ) 

66 

67 def run_coverage( 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8

68 self, 

69 covargs: dict[str, Any] | None = None, 

70 htmlargs: dict[str, Any] | None = None, 

71 ) -> float: 

72 """Run coverage.py on main_file.py, and create an HTML report.""" 

73 self.clean_local_file_imports() 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

74 cov = coverage.Coverage(**(covargs or {})) 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

75 self.start_import_stop(cov, "main_file") 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

76 ret = cov.html_report(**(htmlargs or {})) 1stuvabwxyzcdABCDefEFGHghIJKLijMNOPklQRSTmnUVWXopYZ01qr342

77 self.assert_valid_hrefs() 1stuvabwxyzcdABCDefEFGHghIJKLijMNOPklQRSTmnUVWXopYZ01qr342

78 return ret 1stuvabwxyzcdABCDefEFGHghIJKLijMNOPklQRSTmnUVWXopYZ01qr342

79 

80 def get_html_report_content(self, module: str) -> str: 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

81 """Return the content of the HTML report for `module`.""" 

82 filename = flat_rootname(module) + ".html" 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

83 filename = os.path.join("htmlcov", filename) 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

84 with open(filename, encoding="utf-8") as f: 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

85 return f.read() 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

86 

87 def get_html_index_content(self) -> str: 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

88 """Return the content of index.html. 

89 

90 Time stamps are replaced with a placeholder so that clocks don't matter. 

91 

92 """ 

93 with open("htmlcov/index.html", encoding="utf-8") as f: 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

94 index = f.read() 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

95 index = re.sub( 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

96 r"created at \d{4}-\d{2}-\d{2} \d{2}:\d{2} \+\d{4}", 

97 r"created at YYYY-MM-DD HH:MM +ZZZZ", 

98 index, 

99 ) 

100 index = re.sub( 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

101 r"created at \d{4}-\d{2}-\d{2} \d{2}:\d{2}", 

102 r"created at YYYY-MM-DD HH:MM", 

103 index, 

104 ) 

105 return index 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

106 

107 def get_html_report_text_lines(self, module: str) -> list[str]: 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

108 """Parse the HTML report, and return a list of strings, the text rendered.""" 

109 parser = HtmlReportParser() 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

110 parser.feed(self.get_html_report_content(module)) 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

111 return parser.text() 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

112 

113 def assert_correct_timestamp(self, html: str) -> None: 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

114 """Extract the time stamp from `html`, and assert it is recent.""" 

115 timestamp_pat = r"created at (\d{4})-(\d{2})-(\d{2}) (\d{2}):(\d{2})" 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

116 m = re.search(timestamp_pat, html) 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

117 assert m, "Didn't find a time stamp!" 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

118 timestamp = datetime.datetime(*[int(v) for v in m.groups()]) # type: ignore[arg-type] 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

119 # The time stamp only records the minute, so the delta could be from 

120 # 12:00 to 12:01:59, or two minutes. 

121 self.assert_recent_datetime( 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

122 timestamp, 

123 seconds=120, 

124 msg=f"Time stamp is wrong: {timestamp}", 

125 ) 

126 

127 def assert_valid_hrefs(self, directory: str = "htmlcov") -> None: 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

128 """Assert that the hrefs in htmlcov/*.html are valid. 

129 

130 Doesn't check external links (those with a protocol). 

131 """ 

132 hrefs = collections.defaultdict(set) 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

133 for fname in glob.glob(f"{directory}/*.html"): 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

134 with open(fname, encoding="utf-8") as fhtml: 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

135 html = fhtml.read() 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

136 for href in re.findall(r""" href=['"]([^'"]*)['"]""", html): 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

137 if href.startswith("#"): 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

138 assert re.search(rf""" id=['"]{href[1:]}['"]""", html), ( 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

139 f"Fragment {href!r} in {fname} has no anchor" 

140 ) 

141 continue 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

142 if "://" in href: 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

143 continue 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

144 href = href.partition("#")[0] # ignore fragment in URLs. 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

145 hrefs[href].add(fname) 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

146 for href, sources in hrefs.items(): 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

147 assert os.path.exists(f"{directory}/{href}"), ( 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

148 f"These files link to {href!r}, which doesn't exist: {', '.join(sources)}" 

149 ) 

150 

151 

152class HtmlReportParser(HTMLParser): 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

153 """An HTML parser for our HTML reports. 

154 

155 Assertions are made about the structure we expect. 

156 """ 

157 

158 def __init__(self) -> None: 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

159 super().__init__() 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

160 self.lines: list[list[str]] = [] 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

161 self.in_source = False 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

162 

163 def handle_starttag(self, tag: str, attrs: list[tuple[str, str | None]]) -> None: 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

164 if tag == "main": 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

165 assert attrs == [("id", "source")] 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

166 self.in_source = True 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

167 elif self.in_source and tag == "a": 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

168 dattrs = dict(attrs) 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

169 assert "id" in dattrs 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

170 ida = dattrs["id"] 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

171 assert ida is not None 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

172 assert ida[0] == "t" 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

173 line_no = int(ida[1:]) 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

174 self.lines.append([]) 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

175 assert line_no == len(self.lines) 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

176 

177 def handle_endtag(self, tag: str) -> None: 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

178 if tag == "main": 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

179 self.in_source = False 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

180 

181 def handle_data(self, data: str) -> None: 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

182 if self.in_source and self.lines: 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

183 self.lines[-1].append(data) 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

184 

185 def text(self) -> list[str]: 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

186 """Get the rendered text as a list of strings, one per line.""" 

187 return ["".join(l).rstrip() for l in self.lines] 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

188 

189 

190class FileWriteTracker: 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

191 """A fake object to track how `open` is used to write files.""" 

192 

193 def __init__(self, written: set[str]) -> None: 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

194 self.written = written 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

195 

196 def open(self, filename: str, mode: str = "r", encoding: str | None = None) -> IO[str]: 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

197 """Be just like `open`, but write written file names to `self.written`.""" 

198 if mode.startswith("w"): 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

199 self.written.add(filename.replace("\\", "/")) 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

200 return open(filename, mode, encoding=encoding) 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

201 

202 

203class HtmlDeltaTest(HtmlTestHelpers, CoverageTest): 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

204 """Tests of the HTML delta speed-ups.""" 

205 

206 def setUp(self) -> None: 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

207 super().setUp() 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

208 

209 # At least one of our tests monkey-patches the version of coverage.py, 

210 # so grab it here to restore it later. 

211 self.real_coverage_version = coverage.__version__ 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

212 self.addCleanup(setattr, coverage, "__version__", self.real_coverage_version) 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

213 

214 self.files_written: set[str] 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

215 

216 def run_coverage( 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8

217 self, 

218 covargs: dict[str, Any] | None = None, 

219 htmlargs: dict[str, Any] | None = None, 

220 ) -> float: 

221 """Run coverage in-process for the delta tests. 

222 

223 For the delta tests, we always want `source=.` and we want to track 

224 which files are written. `self.files_written` will be the file names 

225 that were opened for writing in html.py. 

226 

227 """ 

228 covargs = covargs or {} 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

229 covargs["source"] = "." 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

230 self.files_written = set() 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

231 mock_open = FileWriteTracker(self.files_written).open 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

232 with mock.patch("coverage.html.open", mock_open): 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

233 return super().run_coverage(covargs=covargs, htmlargs=htmlargs) 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

234 

235 def assert_htmlcov_files_exist(self) -> None: 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

236 """Assert that all the expected htmlcov files exist.""" 

237 self.assert_exists("htmlcov/index.html") 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

238 self.assert_exists("htmlcov/function_index.html") 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

239 self.assert_exists("htmlcov/class_index.html") 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

240 self.assert_exists("htmlcov/main_file_py.html") 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

241 self.assert_exists("htmlcov/helper1_py.html") 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

242 self.assert_exists("htmlcov/helper2_py.html") 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

243 self.assert_exists("htmlcov/.gitignore") 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

244 # Cache-busted files have random data in the name, but they should all 

245 # be there, and there should only be one of each. 

246 statics = ["style.css", "coverage_html.js", "keybd_closed.png", "favicon_32.png"] 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

247 files = os.listdir("htmlcov") 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

248 for static in statics: 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

249 base, ext = os.path.splitext(static) 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

250 busted_file_pattern = rf"{base}_cb_\w{{8}}{ext}" 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

251 matches = [m for f in files if (m := re.fullmatch(busted_file_pattern, f))] 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

252 assert len(matches) == 1, f"Found {len(matches)} files for {static}" 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

253 

254 def test_html_created(self) -> None: 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

255 # Test basic HTML generation: files should be created. 

256 self.create_initial_files() 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

257 self.run_coverage() 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

258 self.assert_htmlcov_files_exist() 1stuvabwxyzcdABCDefEFGHghIJKLijMNOPklQRSTmnUVWXopYZ01qr342

259 

260 def test_html_delta_from_source_change(self) -> None: 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

261 # HTML generation can create only the files that have changed. 

262 # In this case, helper1 changes because its source is different. 

263 self.create_initial_files() 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

264 self.run_coverage() 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

265 index1 = self.get_html_index_content() 1stuvabwxyzcdABCDefEFGHghIJKLijMNOPklQRSTmnUVWXopYZ01qr342

266 

267 # Now change a file (but only in a comment) and do it again. 

268 self.make_file( 1stuvabwxyzcdABCDefEFGHghIJKLijMNOPklQRSTmnUVWXopYZ01qr342

269 "helper1.py", 

270 """\ 

271 def func1(x): # A nice function 

272 if x % 2: 

273 print("odd") 

274 """, 

275 ) 

276 

277 self.run_coverage() 1stuvabwxyzcdABCDefEFGHghIJKLijMNOPklQRSTmnUVWXopYZ01qr342

278 

279 # Only the changed files should have been created. 

280 self.assert_htmlcov_files_exist() 1stuvabwxyzcdABCDefEFGHghIJKLijMNOPklQRSTmnUVWXopYZ01qr342

281 assert "htmlcov/index.html" in self.files_written 1stuvabwxyzcdABCDefEFGHghIJKLijMNOPklQRSTmnUVWXopYZ01qr342

282 assert "htmlcov/helper1_py.html" in self.files_written 1stuvabwxyzcdABCDefEFGHghIJKLijMNOPklQRSTmnUVWXopYZ01qr342

283 assert "htmlcov/helper2_py.html" not in self.files_written 1stuvabwxyzcdABCDefEFGHghIJKLijMNOPklQRSTmnUVWXopYZ01qr342

284 assert "htmlcov/main_file_py.html" not in self.files_written 1stuvabwxyzcdABCDefEFGHghIJKLijMNOPklQRSTmnUVWXopYZ01qr342

285 

286 # Because the source change was only a comment, the index is the same. 

287 index2 = self.get_html_index_content() 1stuvabwxyzcdABCDefEFGHghIJKLijMNOPklQRSTmnUVWXopYZ01qr342

288 assert index1 == index2 1stuvabwxyzcdABCDefEFGHghIJKLijMNOPklQRSTmnUVWXopYZ01qr342

289 

290 def test_html_delta_from_coverage_change(self) -> None: 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

291 # HTML generation can create only the files that have changed. 

292 # In this case, helper1 changes because its coverage is different. 

293 self.create_initial_files() 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

294 self.run_coverage() 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

295 

296 # Now change a file and do it again. main_file is different, and calls 

297 # helper1 differently. 

298 self.make_file( 1stuvabwxyzcdABCDefEFGHghIJKLijMNOPklQRSTmnUVWXopYZ01qr342

299 "main_file.py", 

300 """\ 

301 import helper1, helper2 

302 helper1.func1(23) 

303 helper2.func2(23) 

304 """, 

305 ) 

306 

307 self.run_coverage() 1stuvabwxyzcdABCDefEFGHghIJKLijMNOPklQRSTmnUVWXopYZ01qr342

308 

309 # Only the changed files should have been created. 

310 self.assert_htmlcov_files_exist() 1stuvabwxyzcdABCDefEFGHghIJKLijMNOPklQRSTmnUVWXopYZ01qr342

311 assert "htmlcov/index.html" in self.files_written 1stuvabwxyzcdABCDefEFGHghIJKLijMNOPklQRSTmnUVWXopYZ01qr342

312 assert "htmlcov/helper1_py.html" in self.files_written 1stuvabwxyzcdABCDefEFGHghIJKLijMNOPklQRSTmnUVWXopYZ01qr342

313 assert "htmlcov/helper2_py.html" not in self.files_written 1stuvabwxyzcdABCDefEFGHghIJKLijMNOPklQRSTmnUVWXopYZ01qr342

314 assert "htmlcov/main_file_py.html" in self.files_written 1stuvabwxyzcdABCDefEFGHghIJKLijMNOPklQRSTmnUVWXopYZ01qr342

315 

316 def test_html_delta_from_settings_change(self) -> None: 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

317 # HTML generation can create only the files that have changed. 

318 # In this case, everything changes because the coverage.py settings 

319 # have changed. 

320 self.create_initial_files() 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

321 self.run_coverage(covargs=dict(omit=[])) 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

322 index1 = self.get_html_index_content() 1stuvabwxyzcdABCDefEFGHghIJKLijMNOPklQRSTmnUVWXopYZ01qr342

323 

324 self.run_coverage(covargs=dict(omit=["xyzzy*"])) 1stuvabwxyzcdABCDefEFGHghIJKLijMNOPklQRSTmnUVWXopYZ01qr342

325 

326 # All the files have been reported again. 

327 self.assert_htmlcov_files_exist() 1stuvabwxyzcdABCDefEFGHghIJKLijMNOPklQRSTmnUVWXopYZ01qr342

328 assert "htmlcov/index.html" in self.files_written 1stuvabwxyzcdABCDefEFGHghIJKLijMNOPklQRSTmnUVWXopYZ01qr342

329 assert "htmlcov/helper1_py.html" in self.files_written 1stuvabwxyzcdABCDefEFGHghIJKLijMNOPklQRSTmnUVWXopYZ01qr342

330 assert "htmlcov/helper2_py.html" in self.files_written 1stuvabwxyzcdABCDefEFGHghIJKLijMNOPklQRSTmnUVWXopYZ01qr342

331 assert "htmlcov/main_file_py.html" in self.files_written 1stuvabwxyzcdABCDefEFGHghIJKLijMNOPklQRSTmnUVWXopYZ01qr342

332 

333 index2 = self.get_html_index_content() 1stuvabwxyzcdABCDefEFGHghIJKLijMNOPklQRSTmnUVWXopYZ01qr342

334 assert index1 == index2 1stuvabwxyzcdABCDefEFGHghIJKLijMNOPklQRSTmnUVWXopYZ01qr342

335 

336 def test_html_delta_from_coverage_version_change(self) -> None: 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

337 # HTML generation can create only the files that have changed. 

338 # In this case, everything changes because the coverage.py version has 

339 # changed. 

340 self.create_initial_files() 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

341 self.run_coverage() 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

342 index1 = self.get_html_index_content() 1stuvabwxyzcdABCDefEFGHghIJKLijMNOPklQRSTmnUVWXopYZ01qr342

343 

344 # "Upgrade" coverage.py! 

345 coverage.__version__ = "XYZZY" 1stuvabwxyzcdABCDefEFGHghIJKLijMNOPklQRSTmnUVWXopYZ01qr342

346 

347 self.run_coverage() 1stuvabwxyzcdABCDefEFGHghIJKLijMNOPklQRSTmnUVWXopYZ01qr342

348 

349 # All the files have been reported again. 

350 self.assert_htmlcov_files_exist() 1stuvabwxyzcdABCDefEFGHghIJKLijMNOPklQRSTmnUVWXopYZ01qr342

351 assert "htmlcov/index.html" in self.files_written 1stuvabwxyzcdABCDefEFGHghIJKLijMNOPklQRSTmnUVWXopYZ01qr342

352 assert "htmlcov/helper1_py.html" in self.files_written 1stuvabwxyzcdABCDefEFGHghIJKLijMNOPklQRSTmnUVWXopYZ01qr342

353 assert "htmlcov/helper2_py.html" in self.files_written 1stuvabwxyzcdABCDefEFGHghIJKLijMNOPklQRSTmnUVWXopYZ01qr342

354 assert "htmlcov/main_file_py.html" in self.files_written 1stuvabwxyzcdABCDefEFGHghIJKLijMNOPklQRSTmnUVWXopYZ01qr342

355 

356 index2 = self.get_html_index_content() 1stuvabwxyzcdABCDefEFGHghIJKLijMNOPklQRSTmnUVWXopYZ01qr342

357 fixed_index2 = index2.replace("XYZZY", self.real_coverage_version) 1stuvabwxyzcdABCDefEFGHghIJKLijMNOPklQRSTmnUVWXopYZ01qr342

358 assert index1 == fixed_index2 1stuvabwxyzcdABCDefEFGHghIJKLijMNOPklQRSTmnUVWXopYZ01qr342

359 

360 def test_file_becomes_100(self) -> None: 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

361 self.create_initial_files() 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

362 self.run_coverage() 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

363 

364 # Now change a file and do it again 

365 self.make_file( 1stuvabwxyzcdABCDefEFGHghIJKLijMNOPklQRSTmnUVWXopYZ01qr342

366 "main_file.py", 

367 """\ 

368 import helper1, helper2 

369 # helper1 is now 100% 

370 helper1.func1(12) 

371 helper1.func1(23) 

372 """, 

373 ) 

374 

375 self.run_coverage(htmlargs=dict(skip_covered=True)) 1stuvabwxyzcdABCDefEFGHghIJKLijMNOPklQRSTmnUVWXopYZ01qr342

376 

377 # The 100% file, skipped, shouldn't be here. 

378 self.assert_doesnt_exist("htmlcov/helper1_py.html") 1stuvabwxyzcdABCDefEFGHghIJKLijMNOPklQRSTmnUVWXopYZ01qr342

379 

380 def test_status_format_change(self) -> None: 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

381 self.create_initial_files() 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

382 self.run_coverage() 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

383 

384 with open("htmlcov/status.json", encoding="utf-8") as status_json: 1stuvabwxyzcdABCDefEFGHghIJKLijMNOPklQRSTmnUVWXopYZ01qr342

385 status_data = json.load(status_json) 1stuvabwxyzcdABCDefEFGHghIJKLijMNOPklQRSTmnUVWXopYZ01qr342

386 

387 assert status_data["format"] == 5 1stuvabwxyzcdABCDefEFGHghIJKLijMNOPklQRSTmnUVWXopYZ01qr342

388 status_data["format"] = 99 1stuvabwxyzcdABCDefEFGHghIJKLijMNOPklQRSTmnUVWXopYZ01qr342

389 with open("htmlcov/status.json", "w", encoding="utf-8") as status_json: 1stuvabwxyzcdABCDefEFGHghIJKLijMNOPklQRSTmnUVWXopYZ01qr342

390 json.dump(status_data, status_json) 1stuvabwxyzcdABCDefEFGHghIJKLijMNOPklQRSTmnUVWXopYZ01qr342

391 

392 self.run_coverage() 1stuvabwxyzcdABCDefEFGHghIJKLijMNOPklQRSTmnUVWXopYZ01qr342

393 

394 # All the files have been reported again. 

395 self.assert_htmlcov_files_exist() 1stuvabwxyzcdABCDefEFGHghIJKLijMNOPklQRSTmnUVWXopYZ01qr342

396 assert "htmlcov/index.html" in self.files_written 1stuvabwxyzcdABCDefEFGHghIJKLijMNOPklQRSTmnUVWXopYZ01qr342

397 assert "htmlcov/helper1_py.html" in self.files_written 1stuvabwxyzcdABCDefEFGHghIJKLijMNOPklQRSTmnUVWXopYZ01qr342

398 assert "htmlcov/helper2_py.html" in self.files_written 1stuvabwxyzcdABCDefEFGHghIJKLijMNOPklQRSTmnUVWXopYZ01qr342

399 assert "htmlcov/main_file_py.html" in self.files_written 1stuvabwxyzcdABCDefEFGHghIJKLijMNOPklQRSTmnUVWXopYZ01qr342

400 

401 def test_dont_overwrite_gitignore(self) -> None: 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

402 self.create_initial_files() 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

403 self.make_file("htmlcov/.gitignore", "# ignore nothing") 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

404 self.run_coverage() 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

405 with open("htmlcov/.gitignore", encoding="utf-8") as fgi: 1stuvabwxyzcdABCDefEFGHghIJKLijMNOPklQRSTmnUVWXopYZ01qr342

406 assert fgi.read() == "# ignore nothing" 1stuvabwxyzcdABCDefEFGHghIJKLijMNOPklQRSTmnUVWXopYZ01qr342

407 

408 def test_dont_write_gitignore_into_existing_directory(self) -> None: 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

409 self.create_initial_files() 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

410 self.make_file("htmlcov/README", "My files: don't touch!") 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

411 self.run_coverage() 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

412 self.assert_doesnt_exist("htmlcov/.gitignore") 1stuvabwxyzcdABCDefEFGHghIJKLijMNOPklQRSTmnUVWXopYZ01qr342

413 self.assert_exists("htmlcov/index.html") 1stuvabwxyzcdABCDefEFGHghIJKLijMNOPklQRSTmnUVWXopYZ01qr342

414 

415 

416class HtmlTitleTest(HtmlTestHelpers, CoverageTest): 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

417 """Tests of the HTML title support.""" 

418 

419 def test_default_title(self) -> None: 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

420 self.create_initial_files() 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

421 self.run_coverage() 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

422 index = self.get_html_index_content() 1stuvabwxyzcdABCDefEFGHghIJKLijMNOPklQRSTmnUVWXopYZ01qr342

423 assert "<title>Coverage report</title>" in index 1stuvabwxyzcdABCDefEFGHghIJKLijMNOPklQRSTmnUVWXopYZ01qr342

424 assert "<h1>Coverage report:" in index 1stuvabwxyzcdABCDefEFGHghIJKLijMNOPklQRSTmnUVWXopYZ01qr342

425 

426 def test_title_set_in_config_file(self) -> None: 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

427 self.create_initial_files() 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

428 self.make_file(".coveragerc", "[html]\ntitle = Metrics & stuff!\n") 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

429 self.run_coverage() 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

430 index = self.get_html_index_content() 1stuvabwxyzcdABCDefEFGHghIJKLijMNOPklQRSTmnUVWXopYZ01qr342

431 assert "<title>Metrics &amp; stuff!</title>" in index 1stuvabwxyzcdABCDefEFGHghIJKLijMNOPklQRSTmnUVWXopYZ01qr342

432 assert "<h1>Metrics &amp; stuff!:" in index 1stuvabwxyzcdABCDefEFGHghIJKLijMNOPklQRSTmnUVWXopYZ01qr342

433 

434 def test_non_ascii_title_set_in_config_file(self) -> None: 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

435 self.create_initial_files() 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

436 self.make_file(".coveragerc", "[html]\ntitle = «ταБЬℓσ» numbers") 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

437 self.run_coverage() 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

438 index = self.get_html_index_content() 1stuvabwxyzcdABCDefEFGHghIJKLijMNOPklQRSTmnUVWXopYZ01qr342

439 assert "<title>&#171;&#964;&#945;&#1041;&#1068;&#8467;&#963;&#187; numbers" in index 1stuvabwxyzcdABCDefEFGHghIJKLijMNOPklQRSTmnUVWXopYZ01qr342

440 assert "<h1>&#171;&#964;&#945;&#1041;&#1068;&#8467;&#963;&#187; numbers" in index 1stuvabwxyzcdABCDefEFGHghIJKLijMNOPklQRSTmnUVWXopYZ01qr342

441 

442 def test_title_set_in_args(self) -> None: 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

443 self.create_initial_files() 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

444 self.make_file(".coveragerc", "[html]\ntitle = Good title\n") 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

445 self.run_coverage(htmlargs=dict(title="«ταБЬℓσ» & stüff!")) 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

446 index = self.get_html_index_content() 1stuvabwxyzcdABCDefEFGHghIJKLijMNOPklQRSTmnUVWXopYZ01qr342

447 expected = ( 1stuvabwxyzcdABCDefEFGHghIJKLijMNOPklQRSTmnUVWXopYZ01qr342

448 "<title>&#171;&#964;&#945;&#1041;&#1068;&#8467;&#963;&#187; " 

449 + "&amp; st&#252;ff!</title>" 

450 ) 

451 assert expected in index 1stuvabwxyzcdABCDefEFGHghIJKLijMNOPklQRSTmnUVWXopYZ01qr342

452 assert "<h1>&#171;&#964;&#945;&#1041;&#1068;&#8467;&#963;&#187; &amp; st&#252;ff!:" in index 1stuvabwxyzcdABCDefEFGHghIJKLijMNOPklQRSTmnUVWXopYZ01qr342

453 

454 

455class HtmlWithUnparsableFilesTest(HtmlTestHelpers, CoverageTest): 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

456 """Test the behavior when measuring unparsable files.""" 

457 

458 def test_dotpy_not_python(self) -> None: 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

459 self.make_file("main.py", "import innocuous") 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

460 self.make_file("innocuous.py", "a = 1") 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

461 cov = coverage.Coverage() 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

462 self.start_import_stop(cov, "main") 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

463 self.make_file("innocuous.py", "<h1>This isn't python!</h1>") 1stuvabwxyzcdABCDefEFGHghIJKLijMNOPklQRSTmnUVWXopYZ01qr342

464 msg = "Couldn't parse '.*innocuous.py' as Python source: .* at line 1" 1stuvabwxyzcdABCDefEFGHghIJKLijMNOPklQRSTmnUVWXopYZ01qr342

465 with pytest.raises(NotPython, match=msg): 1stuvabwxyzcdABCDefEFGHghIJKLijMNOPklQRSTmnUVWXopYZ01qr342

466 cov.html_report() 1stuvabwxyzcdABCDefEFGHghIJKLijMNOPklQRSTmnUVWXopYZ01qr342

467 

468 def test_dotpy_not_python_ignored(self) -> None: 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

469 self.make_file("main.py", "import innocuous") 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

470 self.make_file("innocuous.py", "a = 2") 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

471 cov = coverage.Coverage() 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

472 self.start_import_stop(cov, "main") 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

473 

474 self.make_file("innocuous.py", "<h1>This isn't python!</h1>") 1stuvabwxyzcdABCDefEFGHghIJKLijMNOPklQRSTmnUVWXopYZ01qr342

475 with pytest.warns(Warning) as warns: 1stuvabwxyzcdABCDefEFGHghIJKLijMNOPklQRSTmnUVWXopYZ01qr342

476 cov.html_report(ignore_errors=True) 1stuvabwxyzcdABCDefEFGHghIJKLijMNOPklQRSTmnUVWXopYZ01qr342

477 assert_coverage_warnings( 1stuvabwxyzcdABCDefEFGHghIJKLijMNOPklQRSTmnUVWXopYZ01qr342

478 warns, 

479 re.compile(r"Couldn't parse Python file '.*innocuous.py' \(couldnt-parse\)"), 

480 ) 

481 self.assert_exists("htmlcov/index.html") 1stuvabwxyzcdABCDefEFGHghIJKLijMNOPklQRSTmnUVWXopYZ01qr342

482 # This would be better as a glob, if the HTML layout changes: 

483 self.assert_doesnt_exist("htmlcov/innocuous.html") 1stuvabwxyzcdABCDefEFGHghIJKLijMNOPklQRSTmnUVWXopYZ01qr342

484 

485 def test_dothtml_not_python(self) -> None: 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

486 # Run an "HTML" file 

487 self.make_file("innocuous.html", "a = 3") 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

488 self.make_data_file(lines={abs_file("innocuous.html"): [1]}) 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

489 # Before reporting, change it to be an HTML file. 

490 self.make_file("innocuous.html", "<h1>This isn't python at all!</h1>") 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

491 cov = coverage.Coverage() 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

492 cov.load() 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

493 with pytest.raises(NoDataError, match="No data to report."): 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

494 cov.html_report() 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

495 

496 def test_execed_liar_ignored(self) -> None: 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

497 # Jinja2 sets __file__ to be a non-Python file, and then execs code. 

498 # If that file contains non-Python code, a TokenError shouldn't 

499 # have been raised when writing the HTML report. 

500 source = "exec(compile('','','exec'), {'__file__': 'liar.html'})" 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

501 self.make_file("liar.py", source) 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

502 self.make_file("liar.html", "{# Whoops, not python code #}") 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

503 cov = coverage.Coverage() 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

504 self.start_import_stop(cov, "liar") 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

505 cov.html_report() 1stuvabwxyzcdABCDefEFGHghIJKLijMNOPklQRSTmnUVWXopYZ01qr342

506 self.assert_exists("htmlcov/index.html") 1stuvabwxyzcdABCDefEFGHghIJKLijMNOPklQRSTmnUVWXopYZ01qr342

507 

508 def test_execed_liar_ignored_indentation_error(self) -> None: 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

509 # Jinja2 sets __file__ to be a non-Python file, and then execs code. 

510 # If that file contains untokenizable code, we shouldn't get an 

511 # exception. 

512 source = "exec(compile('','','exec'), {'__file__': 'liar.html'})" 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

513 self.make_file("liar.py", source) 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

514 # Tokenize will raise an IndentationError if it can't dedent. 

515 self.make_file("liar.html", "0\n 2\n 1\n") 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

516 cov = coverage.Coverage() 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

517 self.start_import_stop(cov, "liar") 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

518 cov.html_report() 1stuvabwxyzcdABCDefEFGHghIJKLijMNOPklQRSTmnUVWXopYZ01qr342

519 self.assert_exists("htmlcov/index.html") 1stuvabwxyzcdABCDefEFGHghIJKLijMNOPklQRSTmnUVWXopYZ01qr342

520 

521 def test_decode_error(self) -> None: 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

522 # https://github.com/coveragepy/coveragepy/issues/351 

523 # imp.load_module won't load a file with an undecodable character 

524 # in a comment, though Python will run them. So we'll change the 

525 # file after running. 

526 self.make_file("main.py", "import sub.not_ascii") 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

527 self.make_file("sub/__init__.py") 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

528 self.make_file( 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

529 "sub/not_ascii.py", 

530 """\ 

531 # coding: utf-8 

532 a = 1 # Isn't this great?! 

533 """, 

534 ) 

535 cov = coverage.Coverage() 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

536 self.start_import_stop(cov, "main") 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

537 

538 # Create the undecodable version of the file. make_file is too helpful, 

539 # so get down and dirty with bytes. 

540 with open("sub/not_ascii.py", "wb") as f: 1stuvabwxyzcdABCDefEFGHghIJKLijMNOPklQRSTmnUVWXopYZ01qr342

541 f.write(b"# coding: utf-8\na = 1 # Isn't this great?\xcb!\n") 1stuvabwxyzcdABCDefEFGHghIJKLijMNOPklQRSTmnUVWXopYZ01qr342

542 

543 with open("sub/not_ascii.py", "rb") as f: 1stuvabwxyzcdABCDefEFGHghIJKLijMNOPklQRSTmnUVWXopYZ01qr342

544 undecodable = f.read() 1stuvabwxyzcdABCDefEFGHghIJKLijMNOPklQRSTmnUVWXopYZ01qr342

545 assert b"?\xcb!" in undecodable 1stuvabwxyzcdABCDefEFGHghIJKLijMNOPklQRSTmnUVWXopYZ01qr342

546 

547 cov.html_report() 1stuvabwxyzcdABCDefEFGHghIJKLijMNOPklQRSTmnUVWXopYZ01qr342

548 

549 html_report = self.get_html_report_content("sub/not_ascii.py") 1stuvabwxyzcdABCDefEFGHghIJKLijMNOPklQRSTmnUVWXopYZ01qr342

550 expected = "# Isn't this great?&#65533;!" 1stuvabwxyzcdABCDefEFGHghIJKLijMNOPklQRSTmnUVWXopYZ01qr342

551 assert expected in html_report 1stuvabwxyzcdABCDefEFGHghIJKLijMNOPklQRSTmnUVWXopYZ01qr342

552 

553 def test_formfeeds(self) -> None: 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

554 # https://github.com/coveragepy/coveragepy/issues/360 

555 self.make_file("formfeed.py", "line_one = 1\n\f\nline_two = 2\n") 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

556 cov = coverage.Coverage() 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

557 self.start_import_stop(cov, "formfeed") 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

558 cov.html_report() 1stuvabwxyzcdABCDefEFGHghIJKLijMNOPklQRSTmnUVWXopYZ01qr342

559 

560 formfeed_html = self.get_html_report_content("formfeed.py") 1stuvabwxyzcdABCDefEFGHghIJKLijMNOPklQRSTmnUVWXopYZ01qr342

561 assert "line_two" in formfeed_html 1stuvabwxyzcdABCDefEFGHghIJKLijMNOPklQRSTmnUVWXopYZ01qr342

562 

563 def test_splitlines_special_chars(self) -> None: 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

564 # https://github.com/coveragepy/coveragepy/issues/1512 

565 # See https://docs.python.org/3/library/stdtypes.html#str.splitlines for 

566 # the characters splitlines treats specially that readlines does not. 

567 

568 # I'm not exactly sure why we need the "a" strings here, but the old 

569 # code wasn't failing without them. 

570 self.make_file( 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

571 "splitlines_is_weird.py", 

572 """\ 

573 test = { 

574 "0b": ["\x0b0"], "a1": "this is line 2", 

575 "0c": ["\x0c0"], "a2": "this is line 3", 

576 "1c": ["\x1c0"], "a3": "this is line 4", 

577 "1d": ["\x1d0"], "a4": "this is line 5", 

578 "1e": ["\x1e0"], "a5": "this is line 6", 

579 "85": ["\x850"], "a6": "this is line 7", 

580 "2028": ["\u20280"], "a7": "this is line 8", 

581 "2029": ["\u20290"], "a8": "this is line 9", 

582 } 

583 DONE = 1 

584 """, 

585 ) 

586 cov = coverage.Coverage() 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

587 self.start_import_stop(cov, "splitlines_is_weird") 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

588 cov.html_report() 1stuvabwxyzcdABCDefEFGHghIJKLijMNOPklQRSTmnUVWXopYZ01qr342

589 

590 the_html = self.get_html_report_content("splitlines_is_weird.py") 1stuvabwxyzcdABCDefEFGHghIJKLijMNOPklQRSTmnUVWXopYZ01qr342

591 assert "DONE" in the_html 1stuvabwxyzcdABCDefEFGHghIJKLijMNOPklQRSTmnUVWXopYZ01qr342

592 

593 # Check that the lines are properly decoded and reported... 

594 html_lines = the_html.split("\n") 1stuvabwxyzcdABCDefEFGHghIJKLijMNOPklQRSTmnUVWXopYZ01qr342

595 assert any(re.search(r'id="t2".*"this is line 2"', line) for line in html_lines) 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

596 assert any(re.search(r'id="t9".*"this is line 9"', line) for line in html_lines) 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

597 

598 

599class HtmlTest(HtmlTestHelpers, CoverageTest): 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

600 """Moar HTML tests.""" 

601 

602 def test_missing_source_file_incorrect_message(self) -> None: 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

603 # https://github.com/coveragepy/coveragepy/issues/60 

604 self.make_file("thefile.py", "import sub.another\n") 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

605 self.make_file("sub/__init__.py", "") 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

606 self.make_file("sub/another.py", "print('another')\n") 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

607 cov = coverage.Coverage() 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

608 self.start_import_stop(cov, "thefile") 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

609 os.remove("sub/another.py") 1stuvabwxyzcdABCDefEFGHghIJKLijMNOPklQRSTmnUVWXopYZ01qr342

610 

611 missing_file = os.path.join(self.temp_dir, "sub", "another.py") 1stuvabwxyzcdABCDefEFGHghIJKLijMNOPklQRSTmnUVWXopYZ01qr342

612 missing_file = os.path.realpath(missing_file) 1stuvabwxyzcdABCDefEFGHghIJKLijMNOPklQRSTmnUVWXopYZ01qr342

613 msg = "(?i)No source for code: '%s'" % re.escape(missing_file) 1stuvabwxyzcdABCDefEFGHghIJKLijMNOPklQRSTmnUVWXopYZ01qr342

614 with pytest.raises(NoSource, match=msg): 1stuvabwxyzcdABCDefEFGHghIJKLijMNOPklQRSTmnUVWXopYZ01qr342

615 cov.html_report() 1stuvabwxyzcdABCDefEFGHghIJKLijMNOPklQRSTmnUVWXopYZ01qr342

616 

617 def test_extensionless_file_collides_with_extension(self) -> None: 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

618 # It used to be that "program" and "program.py" would both be reported 

619 # to "program.html". Now they are not. 

620 # https://github.com/coveragepy/coveragepy/issues/69 

621 self.make_file("program", "import program\n") 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

622 self.make_file("program.py", "a = 1\n") 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

623 self.make_data_file( 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

624 lines={ 

625 abs_file("program"): [1], 

626 abs_file("program.py"): [1], 

627 } 

628 ) 

629 cov = coverage.Coverage() 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

630 cov.load() 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

631 cov.html_report() 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

632 self.assert_exists("htmlcov/index.html") 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

633 self.assert_exists("htmlcov/program.html") 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

634 self.assert_exists("htmlcov/program_py.html") 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

635 

636 def test_has_date_stamp_in_files(self) -> None: 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

637 self.create_initial_files() 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

638 self.run_coverage() 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

639 

640 with open("htmlcov/index.html", encoding="utf-8") as f: 1stuvabwxyzcdABCDefEFGHghIJKLijMNOPklQRSTmnUVWXopYZ01qr342

641 self.assert_correct_timestamp(f.read()) 1stuvabwxyzcdABCDefEFGHghIJKLijMNOPklQRSTmnUVWXopYZ01qr342

642 with open("htmlcov/main_file_py.html", encoding="utf-8") as f: 1stuvabwxyzcdABCDefEFGHghIJKLijMNOPklQRSTmnUVWXopYZ01qr342

643 self.assert_correct_timestamp(f.read()) 1stuvabwxyzcdABCDefEFGHghIJKLijMNOPklQRSTmnUVWXopYZ01qr342

644 

645 def test_reporting_on_unmeasured_file(self) -> None: 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

646 # It should be ok to ask for an HTML report on a file that wasn't even 

647 # measured at all. https://github.com/coveragepy/coveragepy/issues/403 

648 self.create_initial_files() 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

649 self.make_file("other.py", "a = 1\n") 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

650 self.run_coverage(htmlargs=dict(morfs=["other.py"])) 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

651 self.assert_exists("htmlcov/index.html") 1stuvabwxyzcdABCDefEFGHghIJKLijMNOPklQRSTmnUVWXopYZ01qr342

652 self.assert_exists("htmlcov/other_py.html") 1stuvabwxyzcdABCDefEFGHghIJKLijMNOPklQRSTmnUVWXopYZ01qr342

653 

654 def make_main_and_not_covered(self) -> None: 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

655 """Helper to create files for skip_covered scenarios.""" 

656 self.make_file( 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

657 "main_file.py", 

658 """\ 

659 import not_covered 

660 

661 def normal(): 

662 print("z") 

663 normal() 

664 """, 

665 ) 

666 self.make_file( 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

667 "not_covered.py", 

668 """\ 

669 def not_covered(): 

670 print("n") 

671 """, 

672 ) 

673 

674 def test_report_skip_covered(self) -> None: 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

675 self.make_main_and_not_covered() 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

676 self.run_coverage(htmlargs=dict(skip_covered=True)) 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

677 self.assert_exists("htmlcov/index.html") 1stuvabwxyzcdABCDefEFGHghIJKLijMNOPklQRSTmnUVWXopYZ01qr342

678 self.assert_doesnt_exist("htmlcov/main_file_py.html") 1stuvabwxyzcdABCDefEFGHghIJKLijMNOPklQRSTmnUVWXopYZ01qr342

679 self.assert_exists("htmlcov/not_covered_py.html") 1stuvabwxyzcdABCDefEFGHghIJKLijMNOPklQRSTmnUVWXopYZ01qr342

680 

681 def test_html_skip_covered(self) -> None: 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

682 self.make_main_and_not_covered() 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

683 self.make_file(".coveragerc", "[html]\nskip_covered = True") 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

684 self.run_coverage() 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

685 self.assert_exists("htmlcov/index.html") 1stuvabwxyzcdABCDefEFGHghIJKLijMNOPklQRSTmnUVWXopYZ01qr342

686 self.assert_doesnt_exist("htmlcov/main_file_py.html") 1stuvabwxyzcdABCDefEFGHghIJKLijMNOPklQRSTmnUVWXopYZ01qr342

687 self.assert_exists("htmlcov/not_covered_py.html") 1stuvabwxyzcdABCDefEFGHghIJKLijMNOPklQRSTmnUVWXopYZ01qr342

688 index = self.get_html_index_content() 1stuvabwxyzcdABCDefEFGHghIJKLijMNOPklQRSTmnUVWXopYZ01qr342

689 assert "1 file skipped due to complete coverage." in index 1stuvabwxyzcdABCDefEFGHghIJKLijMNOPklQRSTmnUVWXopYZ01qr342

690 

691 def test_report_skip_covered_branches(self) -> None: 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

692 self.make_main_and_not_covered() 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

693 self.run_coverage(covargs=dict(branch=True), htmlargs=dict(skip_covered=True)) 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

694 self.assert_exists("htmlcov/index.html") 1stuvabwxyzcdABCDefEFGHghIJKLijMNOPklQRSTmnUVWXopYZ01qr342

695 self.assert_doesnt_exist("htmlcov/main_file_py.html") 1stuvabwxyzcdABCDefEFGHghIJKLijMNOPklQRSTmnUVWXopYZ01qr342

696 self.assert_exists("htmlcov/not_covered_py.html") 1stuvabwxyzcdABCDefEFGHghIJKLijMNOPklQRSTmnUVWXopYZ01qr342

697 

698 def test_report_skip_covered_100(self) -> None: 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

699 self.make_file( 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

700 "main_file.py", 

701 """\ 

702 def normal(): 

703 print("z") 

704 normal() 

705 """, 

706 ) 

707 res = self.run_coverage(covargs=dict(source="."), htmlargs=dict(skip_covered=True)) 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

708 assert res == 100.0 1stuvabwxyzcdABCDefEFGHghIJKLijMNOPklQRSTmnUVWXopYZ01qr342

709 self.assert_doesnt_exist("htmlcov/main_file_py.html") 1stuvabwxyzcdABCDefEFGHghIJKLijMNOPklQRSTmnUVWXopYZ01qr342

710 # Since there are no files to report, we can't collect any region 

711 # information, so there are no region-based index pages. 

712 self.assert_doesnt_exist("htmlcov/function_index.html") 1stuvabwxyzcdABCDefEFGHghIJKLijMNOPklQRSTmnUVWXopYZ01qr342

713 self.assert_doesnt_exist("htmlcov/class_index.html") 1stuvabwxyzcdABCDefEFGHghIJKLijMNOPklQRSTmnUVWXopYZ01qr342

714 

715 def test_report_skip_covered_100_functions(self) -> None: 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

716 self.make_file( 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

717 "main_file.py", 

718 """\ 

719 def normal(): 

720 print("z") 

721 def abnormal(): 

722 print("a") 

723 normal() 

724 """, 

725 ) 

726 res = self.run_coverage(covargs=dict(source="."), htmlargs=dict(skip_covered=True)) 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

727 assert res == 80.0 1stuvabwxyzcdABCDefEFGHghIJKLijMNOPklQRSTmnUVWXopYZ01qr342

728 self.assert_exists("htmlcov/main_file_py.html") 1stuvabwxyzcdABCDefEFGHghIJKLijMNOPklQRSTmnUVWXopYZ01qr342

729 # We have a file to report, so we get function and class index pages, 

730 # even though there are no classes. 

731 self.assert_exists("htmlcov/function_index.html") 1stuvabwxyzcdABCDefEFGHghIJKLijMNOPklQRSTmnUVWXopYZ01qr342

732 self.assert_exists("htmlcov/class_index.html") 1stuvabwxyzcdABCDefEFGHghIJKLijMNOPklQRSTmnUVWXopYZ01qr342

733 

734 def make_init_and_main(self) -> None: 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

735 """Helper to create files for skip_empty scenarios.""" 

736 self.make_file("submodule/__init__.py", "") 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

737 self.make_file( 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

738 "main_file.py", 

739 """\ 

740 import submodule 

741 

742 def normal(): 

743 print("z") 

744 normal() 

745 """, 

746 ) 

747 

748 def test_report_skip_empty(self) -> None: 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

749 self.make_init_and_main() 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

750 self.run_coverage(htmlargs=dict(skip_empty=True)) 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

751 self.assert_exists("htmlcov/index.html") 1stuvabwxyzcdABCDefEFGHghIJKLijMNOPklQRSTmnUVWXopYZ01qr342

752 self.assert_exists("htmlcov/main_file_py.html") 1stuvabwxyzcdABCDefEFGHghIJKLijMNOPklQRSTmnUVWXopYZ01qr342

753 self.assert_doesnt_exist("htmlcov/submodule___init___py.html") 1stuvabwxyzcdABCDefEFGHghIJKLijMNOPklQRSTmnUVWXopYZ01qr342

754 index = self.get_html_index_content() 1stuvabwxyzcdABCDefEFGHghIJKLijMNOPklQRSTmnUVWXopYZ01qr342

755 assert "1 empty file skipped." in index 1stuvabwxyzcdABCDefEFGHghIJKLijMNOPklQRSTmnUVWXopYZ01qr342

756 

757 def test_html_skip_empty(self) -> None: 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

758 self.make_init_and_main() 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

759 self.make_file(".coveragerc", "[html]\nskip_empty = True") 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

760 self.run_coverage() 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

761 self.assert_exists("htmlcov/index.html") 1stuvabwxyzcdABCDefEFGHghIJKLijMNOPklQRSTmnUVWXopYZ01qr342

762 self.assert_exists("htmlcov/main_file_py.html") 1stuvabwxyzcdABCDefEFGHghIJKLijMNOPklQRSTmnUVWXopYZ01qr342

763 self.assert_doesnt_exist("htmlcov/submodule___init___py.html") 1stuvabwxyzcdABCDefEFGHghIJKLijMNOPklQRSTmnUVWXopYZ01qr342

764 

765 

766def filepath_to_regex(path: str) -> str: 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

767 """Create a regex for scrubbing a file path.""" 

768 regex = re.escape(path) 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

769 # If there's a backslash, let it match either slash. 

770 regex = regex.replace(r"\\", r"[\\/]") 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

771 if env.WINDOWS: 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

772 regex = "(?i)" + regex 1abcdefghijkl5mn6op7qr82

773 return regex 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

774 

775 

776def compare_html( 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8

777 expected: str, 

778 actual: str, 

779 extra_scrubs: list[tuple[str, str]] | None = None, 

780) -> None: 

781 """Specialized compare function for our HTML files.""" 

782 __tracebackhide__ = True # pytest, please don't show me this function. 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

783 scrubs = [ 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

784 (r"/coverage\.readthedocs\.io/?[-.\w/]*", "/coverage.readthedocs.io/VER"), 

785 (r"coverage\.py v[\d.abcdev]+", "coverage.py vVER"), 

786 (r"created at \d\d\d\d-\d\d-\d\d \d\d:\d\d [-+]\d\d\d\d", "created at DATE"), 

787 (r"created at \d\d\d\d-\d\d-\d\d \d\d:\d\d", "created at DATE"), 

788 # Static files have cache busting. 

789 (r"_cb_\w{8}\.", "_CB."), 

790 # Un-prettify file paths. 

791 (r"&#8201;[/\\]&#8201;", "/"), 

792 # Occasionally an absolute path is in the HTML report. 

793 (filepath_to_regex(TESTS_DIR), "TESTS_DIR"), 

794 (filepath_to_regex(flat_rootname(str(TESTS_DIR))), "_TESTS_DIR"), 

795 # The temp dir the tests make. 

796 (filepath_to_regex(os.getcwd()), "TEST_TMPDIR"), 

797 (filepath_to_regex(flat_rootname(str(os.getcwd()))), "_TEST_TMPDIR"), 

798 (filepath_to_regex(abs_file(os.getcwd())), "TEST_TMPDIR"), 

799 (filepath_to_regex(flat_rootname(str(abs_file(os.getcwd())))), "_TEST_TMPDIR"), 

800 (r"/private/var/[\w/]+/pytest-of-\w+/pytest-\d+/(popen-gw\d+/)?t\d+", "TEST_TMPDIR"), 

801 # If the gold files were created on Windows, we need to scrub Windows paths also: 

802 (r"[A-Z]:\\Users\\[\w\\]+\\pytest-of-\w+\\pytest-\d+\\(popen-gw\d+\\)?t\d+", "TEST_TMPDIR"), 

803 ] 

804 if extra_scrubs: 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

805 scrubs += extra_scrubs 1stuvabwxyzcdABCDefEFGHghIJKLijMNOPklQRSTmnUVWXopYZ01qr342

806 compare(expected, actual, file_pattern="*.html", scrubs=scrubs) 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

807 

808 

809def unbust(directory: str) -> None: 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

810 """Find files with cache busting, and rename them to simple names. 

811 

812 This makes it possible for us to compare gold files. 

813 """ 

814 with change_dir(directory): 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

815 for fname in os.listdir("."): 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

816 base, ext = os.path.splitext(fname) 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

817 base, _, _ = base.partition("_cb_") 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

818 if base != fname: 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

819 os.rename(fname, base + ext) 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

820 

821 

822class HtmlGoldTest(HtmlTestHelpers, CoverageTest): 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

823 """Tests of HTML reporting that use gold files.""" 

824 

825 def test_a(self) -> None: 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

826 self.make_file( 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

827 "a.py", 

828 """\ 

829 if 1 < 2: 

830 # Needed a < to look at HTML entities. 

831 a = 3 

832 else: 

833 a = 4 

834 """, 

835 ) 

836 

837 cov = coverage.Coverage() 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

838 a = self.start_import_stop(cov, "a") 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

839 cov.html_report(a, directory="out/a") 1stuvabwxyzcdABCDefEFGHghIJKLijMNOPklQRSTmnUVWXopYZ01qr342

840 

841 compare_html(gold_path("html/a"), "out/a") 1stuvabwxyzcdABCDefEFGHghIJKLijMNOPklQRSTmnUVWXopYZ01qr342

842 contains( 1stuvabwxyzcdABCDefEFGHghIJKLijMNOPklQRSTmnUVWXopYZ01qr342

843 "out/a/a_py.html", 

844 ( 

845 '<span class="key">if</span> <span class="num">1</span> ' 

846 + '<span class="op">&lt;</span> <span class="num">2</span>' 

847 ), 

848 ( 

849 ' <span class="nam">a</span> ' 

850 + '<span class="op">=</span> <span class="num">3</span>' 

851 ), 

852 '<span class="pc_cov">67%</span>', 

853 ) 

854 contains( 1stuvabwxyzcdABCDefEFGHghIJKLijMNOPklQRSTmnUVWXopYZ01qr342

855 "out/a/index.html", 

856 '<a href="a_py.html">a.py</a>', 

857 '<span class="pc_cov">67%</span>', 

858 '<td data-ratio="2 3">67%</td>', 

859 ) 

860 

861 def test_b_branch(self) -> None: 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

862 self.make_file( 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

863 "b.py", 

864 """\ 

865 def one(x): 

866 # This will be a branch that misses the else. 

867 if x < 2: 

868 a = 3 

869 else: 

870 a = 4 

871 

872 one(1) 

873 

874 def two(x): 

875 # A missed else that branches to "exit" 

876 if x: 

877 a = 5 

878 

879 two(1) 

880 

881 def three(): 

882 try: 

883 # This if has two branches, *neither* one taken. 

884 if name_error_this_variable_doesnt_exist: 

885 a = 1 

886 else: 

887 a = 2 

888 except: 

889 pass 

890 

891 three() 

892 """, 

893 ) 

894 

895 cov = coverage.Coverage(branch=True) 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

896 b = self.start_import_stop(cov, "b") 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

897 cov.html_report(b, directory="out/b_branch") 1stuvabwxyzcdABCDefEFGHghIJKLijMNOPklQRSTmnUVWXopYZ01qr342

898 compare_html(gold_path("html/b_branch"), "out/b_branch") 1stuvabwxyzcdABCDefEFGHghIJKLijMNOPklQRSTmnUVWXopYZ01qr342

899 contains( 1stuvabwxyzcdABCDefEFGHghIJKLijMNOPklQRSTmnUVWXopYZ01qr342

900 "out/b_branch/b_py.html", 

901 ( 

902 '<span class="key">if</span> <span class="nam">x</span> ' 

903 + '<span class="op">&lt;</span> <span class="num">2</span>' 

904 ), 

905 ( 

906 ' <span class="nam">a</span> <span class="op">=</span> ' 

907 + '<span class="num">3</span>' 

908 ), 

909 '<span class="pc_cov">70%</span>', 

910 ( 

911 '<span class="annotate short">3&#x202F;&#x219B;&#x202F;6</span>' 

912 + '<span class="annotate long">line 3 didn\'t jump to line 6 ' 

913 + "because the condition on line 3 was always true</span>" 

914 ), 

915 ( 

916 '<span class="annotate short">12&#x202F;&#x219B;&#x202F;exit</span>' 

917 + "<span class=\"annotate long\">line 12 didn't return from function 'two' " 

918 + "because the condition on line 12 was always true</span>" 

919 ), 

920 ( 

921 '<span class="annotate short">20&#x202F;&#x219B;&#x202F;anywhere</span>' 

922 + '<span class="annotate long">line 20 didn\'t jump anywhere: ' 

923 + "it always raised an exception.</span>" 

924 ), 

925 ) 

926 contains( 1stuvabwxyzcdABCDefEFGHghIJKLijMNOPklQRSTmnUVWXopYZ01qr342

927 "out/b_branch/index.html", 

928 '<a href="b_py.html">b.py</a>', 

929 '<span class="pc_cov">70%</span>', 

930 '<td data-ratio="16 23">70%</td>', 

931 ) 

932 

933 def test_bom(self) -> None: 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

934 self.make_file( 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

935 "bom.py", 

936 bytes=b"""\ 

937\xef\xbb\xbf# A Python source file in utf-8, with BOM. 

938math = "3\xc3\x974 = 12, \xc3\xb72 = 6\xc2\xb10" 

939 

940assert len(math) == 18 

941assert len(math.encode('utf-8')) == 21 

942""".replace(b"\n", b"\r\n"), 

943 ) 

944 

945 # It's important that the source file really have a BOM, which can 

946 # get lost, so check that it's really there, and that we have \r\n 

947 # line endings. 

948 with open("bom.py", "rb") as f: 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

949 data = f.read() 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

950 assert data[:3] == b"\xef\xbb\xbf" 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

951 assert data.count(b"\r\n") == 5 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

952 

953 cov = coverage.Coverage() 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

954 bom = self.start_import_stop(cov, "bom") 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

955 cov.html_report(bom, directory="out/bom") 1stuvabwxyzcdABCDefEFGHghIJKLijMNOPklQRSTmnUVWXopYZ01qr342

956 

957 compare_html(gold_path("html/bom"), "out/bom") 1stuvabwxyzcdABCDefEFGHghIJKLijMNOPklQRSTmnUVWXopYZ01qr342

958 contains( 1stuvabwxyzcdABCDefEFGHghIJKLijMNOPklQRSTmnUVWXopYZ01qr342

959 "out/bom/bom_py.html", 

960 '<span class="str">"3&#215;4 = 12, &#247;2 = 6&#177;0"</span>', 

961 ) 

962 

963 def test_isolatin1(self) -> None: 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

964 self.make_file( 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

965 "isolatin1.py", 

966 bytes=b"""\ 

967# -*- coding: iso8859-1 -*- 

968# A Python source file in another encoding. 

969 

970math = "3\xd74 = 12, \xf72 = 6\xb10" 

971assert len(math) == 18 

972""", 

973 ) 

974 

975 cov = coverage.Coverage() 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

976 isolatin1 = self.start_import_stop(cov, "isolatin1") 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

977 cov.html_report(isolatin1, directory="out/isolatin1") 1stuvabwxyzcdABCDefEFGHghIJKLijMNOPklQRSTmnUVWXopYZ01qr342

978 

979 compare_html(gold_path("html/isolatin1"), "out/isolatin1") 1stuvabwxyzcdABCDefEFGHghIJKLijMNOPklQRSTmnUVWXopYZ01qr342

980 contains( 1stuvabwxyzcdABCDefEFGHghIJKLijMNOPklQRSTmnUVWXopYZ01qr342

981 "out/isolatin1/isolatin1_py.html", 

982 '<span class="str">"3&#215;4 = 12, &#247;2 = 6&#177;0"</span>', 

983 ) 

984 

985 def make_main_etc(self) -> None: 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

986 """Make main.py and m1-m3.py for other tests.""" 

987 self.make_file( 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

988 "main.py", 

989 """\ 

990 import m1 

991 import m2 

992 import m3 

993 

994 a = 5 

995 b = 6 

996 

997 assert m1.m1a == 1 

998 assert m2.m2a == 1 

999 assert m3.m3a == 1 

1000 """, 

1001 ) 

1002 self.make_file( 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

1003 "m1.py", 

1004 """\ 

1005 m1a = 1 

1006 m1b = 2 

1007 """, 

1008 ) 

1009 self.make_file( 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

1010 "m2.py", 

1011 """\ 

1012 m2a = 1 

1013 m2b = 2 

1014 """, 

1015 ) 

1016 self.make_file( 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

1017 "m3.py", 

1018 """\ 

1019 m3a = 1 

1020 m3b = 2 

1021 """, 

1022 ) 

1023 

1024 def test_omit_1(self) -> None: 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

1025 self.make_main_etc() 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

1026 cov = coverage.Coverage(include=["./*"]) 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

1027 self.start_import_stop(cov, "main") 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

1028 cov.html_report(directory="out/omit_1") 1stuvabwxyzcdABCDefEFGHghIJKLijMNOPklQRSTmnUVWXopYZ01qr342

1029 compare_html(gold_path("html/omit_1"), "out/omit_1") 1stuvabwxyzcdABCDefEFGHghIJKLijMNOPklQRSTmnUVWXopYZ01qr342

1030 

1031 def test_omit_2(self) -> None: 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

1032 self.make_main_etc() 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

1033 cov = coverage.Coverage(include=["./*"]) 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

1034 self.start_import_stop(cov, "main") 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

1035 cov.html_report(directory="out/omit_2", omit=["m1.py"]) 1stuvabwxyzcdABCDefEFGHghIJKLijMNOPklQRSTmnUVWXopYZ01qr342

1036 compare_html(gold_path("html/omit_2"), "out/omit_2") 1stuvabwxyzcdABCDefEFGHghIJKLijMNOPklQRSTmnUVWXopYZ01qr342

1037 

1038 def test_omit_3(self) -> None: 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

1039 self.make_main_etc() 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

1040 cov = coverage.Coverage(include=["./*"]) 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

1041 self.start_import_stop(cov, "main") 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

1042 cov.html_report(directory="out/omit_3", omit=["m1.py", "m2.py"]) 1stuvabwxyzcdABCDefEFGHghIJKLijMNOPklQRSTmnUVWXopYZ01qr342

1043 compare_html(gold_path("html/omit_3"), "out/omit_3") 1stuvabwxyzcdABCDefEFGHghIJKLijMNOPklQRSTmnUVWXopYZ01qr342

1044 

1045 def test_omit_4(self) -> None: 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

1046 self.make_main_etc() 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

1047 self.make_file( 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

1048 "omit4.ini", 

1049 """\ 

1050 [report] 

1051 omit = m2.py 

1052 """, 

1053 ) 

1054 

1055 cov = coverage.Coverage(config_file="omit4.ini", include=["./*"]) 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

1056 self.start_import_stop(cov, "main") 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

1057 cov.html_report(directory="out/omit_4") 1stuvabwxyzcdABCDefEFGHghIJKLijMNOPklQRSTmnUVWXopYZ01qr342

1058 compare_html(gold_path("html/omit_4"), "out/omit_4") 1stuvabwxyzcdABCDefEFGHghIJKLijMNOPklQRSTmnUVWXopYZ01qr342

1059 

1060 def test_omit_5(self) -> None: 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

1061 self.make_main_etc() 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

1062 self.make_file( 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

1063 "omit5.ini", 

1064 """\ 

1065 [report] 

1066 omit = 

1067 fooey 

1068 gooey, m[23]*, kablooey 

1069 helloworld 

1070 

1071 [html] 

1072 directory = out/omit_5 

1073 """, 

1074 ) 

1075 

1076 cov = coverage.Coverage(config_file="omit5.ini", include=["./*"]) 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

1077 self.start_import_stop(cov, "main") 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

1078 cov.html_report() 1stuvabwxyzcdABCDefEFGHghIJKLijMNOPklQRSTmnUVWXopYZ01qr342

1079 compare_html(gold_path("html/omit_5"), "out/omit_5") 1stuvabwxyzcdABCDefEFGHghIJKLijMNOPklQRSTmnUVWXopYZ01qr342

1080 

1081 def test_other(self) -> None: 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

1082 self.make_file( 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

1083 "src/here.py", 

1084 """\ 

1085 import other 

1086 

1087 if 1 < 2: 

1088 h = 3 

1089 else: 

1090 h = 4 

1091 """, 

1092 ) 

1093 self.make_file( 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

1094 "othersrc/other.py", 

1095 """\ 

1096 # A file in another directory. We're checking that it ends up in the 

1097 # HTML report. 

1098 

1099 print("This is the other src!") 

1100 """, 

1101 ) 

1102 

1103 with change_dir("src"): 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

1104 sys.path.insert(0, "../othersrc") 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

1105 cov = coverage.Coverage(include=["./*", "../othersrc/*"]) 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

1106 self.start_import_stop(cov, "here") 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

1107 cov.html_report(directory="../out/other") 1stuvabwxyzcdABCDefEFGHghIJKLijMNOPklQRSTmnUVWXopYZ01qr342

1108 

1109 # Different platforms will name the "other" file differently. Rename it 

1110 actual_file = list(glob.glob("out/other/*_other_py.html")) 1stuvabwxyzcdABCDefEFGHghIJKLijMNOPklQRSTmnUVWXopYZ01qr342

1111 assert len(actual_file) == 1 1stuvabwxyzcdABCDefEFGHghIJKLijMNOPklQRSTmnUVWXopYZ01qr342

1112 os.rename(actual_file[0], "out/other/blah_blah_other_py.html") 1stuvabwxyzcdABCDefEFGHghIJKLijMNOPklQRSTmnUVWXopYZ01qr342

1113 

1114 compare_html( 1stuvabwxyzcdABCDefEFGHghIJKLijMNOPklQRSTmnUVWXopYZ01qr342

1115 gold_path("html/other"), 

1116 "out/other", 

1117 extra_scrubs=[ 

1118 (r'href="z_[0-9a-z]{16}_other_', 'href="_TEST_TMPDIR_other_othersrc_'), 

1119 (r"TEST_TMPDIR\\othersrc\\other.py", "TEST_TMPDIR/othersrc/other.py"), 

1120 ], 

1121 ) 

1122 contains( 1stuvabwxyzcdABCDefEFGHghIJKLijMNOPklQRSTmnUVWXopYZ01qr342

1123 "out/other/index.html", 

1124 '<a href="here_py.html">here.py</a>', 

1125 'other_py.html">', 

1126 "other.py</a>", 

1127 ) 

1128 

1129 def test_partial(self) -> None: 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

1130 self.make_file( 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

1131 "partial.py", 

1132 """\ 

1133 # partial branches and excluded lines 

1134 a = 2 

1135 

1136 while "no peephole".upper(): # t4 

1137 break 

1138 

1139 while a: # pragma: no branch 

1140 break 

1141 

1142 if 0: 

1143 never_happen() 

1144 

1145 if 13: 

1146 a = 14 

1147 

1148 if a == 16: 

1149 raise ZeroDivisionError("17") 

1150 """, 

1151 ) 

1152 self.make_file( 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

1153 "partial.ini", 

1154 """\ 

1155 [run] 

1156 branch = True 

1157 

1158 [report] 

1159 exclude_lines = 

1160 raise ZeroDivisionError 

1161 """, 

1162 ) 

1163 

1164 cov = coverage.Coverage(config_file="partial.ini") 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

1165 partial = self.start_import_stop(cov, "partial") 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

1166 

1167 cov.html_report(partial, directory="out/partial") 1stuvabwxyzcdABCDefEFGHghIJKLijMNOPklQRSTmnUVWXopYZ01qr342

1168 compare_html(gold_path("html/partial"), "out/partial") 1stuvabwxyzcdABCDefEFGHghIJKLijMNOPklQRSTmnUVWXopYZ01qr342

1169 contains_rx( 1stuvabwxyzcdABCDefEFGHghIJKLijMNOPklQRSTmnUVWXopYZ01qr342

1170 "out/partial/partial_py.html", 

1171 r'<p class="par run show_par">.* id="t4"', 

1172 r'<p class="run">.* id="t7"', 

1173 # The "if 0" and "if 1" statements are marked as run. 

1174 r'<p class="run">.* id="t10"', 

1175 # The "raise ZeroDivisionError" is excluded by regex in the .ini. 

1176 r'<p class="exc show_exc">.* id="t17"', 

1177 ) 

1178 contains( 1stuvabwxyzcdABCDefEFGHghIJKLijMNOPklQRSTmnUVWXopYZ01qr342

1179 "out/partial/index.html", 

1180 '<a href="partial_py.html">partial.py</a>', 

1181 '<span class="pc_cov">92%</span>', 

1182 ) 

1183 

1184 def test_styled(self) -> None: 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

1185 self.make_file( 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

1186 "a.py", 

1187 """\ 

1188 if 1 < 2: 

1189 # Needed a < to look at HTML entities. 

1190 a = 3 

1191 else: 

1192 a = 4 

1193 """, 

1194 ) 

1195 

1196 self.make_file("myfile/myextra.css", "/* Doesn't matter what's here, it gets copied. */\n") 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

1197 

1198 cov = coverage.Coverage() 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

1199 a = self.start_import_stop(cov, "a") 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

1200 cov.html_report(a, directory="out/styled", extra_css="myfile/myextra.css") 1stuvabwxyzcdABCDefEFGHghIJKLijMNOPklQRSTmnUVWXopYZ01qr342

1201 self.assert_valid_hrefs("out/styled") 1stuvabwxyzcdABCDefEFGHghIJKLijMNOPklQRSTmnUVWXopYZ01qr342

1202 compare_html(gold_path("html/styled"), "out/styled") 1stuvabwxyzcdABCDefEFGHghIJKLijMNOPklQRSTmnUVWXopYZ01qr342

1203 unbust("out/styled") 1stuvabwxyzcdABCDefEFGHghIJKLijMNOPklQRSTmnUVWXopYZ01qr342

1204 compare(gold_path("html/styled"), "out/styled", file_pattern="*.css") 1stuvabwxyzcdABCDefEFGHghIJKLijMNOPklQRSTmnUVWXopYZ01qr342

1205 contains_rx( 1stuvabwxyzcdABCDefEFGHghIJKLijMNOPklQRSTmnUVWXopYZ01qr342

1206 "out/styled/a_py.html", 

1207 r'<link rel="stylesheet" href="myextra_cb_\w{8}.css" type="text/css">', 

1208 ( 

1209 r'<span class="key">if</span> <span class="num">1</span> ' 

1210 + r'<span class="op">&lt;</span> <span class="num">2</span>' 

1211 ), 

1212 ( 

1213 r' <span class="nam">a</span> <span class="op">=</span> ' 

1214 + r'<span class="num">3</span>' 

1215 ), 

1216 r'<span class="pc_cov">67%</span>', 

1217 ) 

1218 contains_rx( 1stuvabwxyzcdABCDefEFGHghIJKLijMNOPklQRSTmnUVWXopYZ01qr342

1219 "out/styled/index.html", 

1220 r'<link rel="stylesheet" href="myextra_cb_\w{8}.css" type="text/css">', 

1221 r'<a href="a_py.html">a.py</a>', 

1222 r'<span class="pc_cov">67%</span>', 

1223 ) 

1224 

1225 def test_multiline(self) -> None: 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

1226 self.make_file( 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

1227 "multiline.py", 

1228 """\ 

1229 x = 0 

1230 if ( 

1231 x or x 

1232 ): 

1233 print( 

1234 # wut 

1235 "hello", 

1236 ) 

1237 else: 

1238 print( 

1239 "bye", 

1240 # wut 

1241 ) 

1242 

1243 if 0: # pragma: no cover 

1244 print( 

1245 "never" 

1246 ) 

1247 """, 

1248 ) 

1249 cov = coverage.Coverage(branch=True) 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

1250 multiline = self.start_import_stop(cov, "multiline") 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

1251 cov.html_report(multiline, directory="out/multiline") 1stuvabwxyzcdABCDefEFGHghIJKLijMNOPklQRSTmnUVWXopYZ01qr342

1252 compare_html(gold_path("html/multiline"), "out/multiline") 1stuvabwxyzcdABCDefEFGHghIJKLijMNOPklQRSTmnUVWXopYZ01qr342

1253 contains( 1stuvabwxyzcdABCDefEFGHghIJKLijMNOPklQRSTmnUVWXopYZ01qr342

1254 "out/multiline/multiline_py.html", 

1255 '<p class="mis mis2 show_mis"><span class="n"><a id="t6" href="#t6">6</a></span>', 

1256 '<p class="exc exc2 show_exc"><span class="n"><a id="t17" href="#t17">17</a>', 

1257 ) 

1258 

1259 def test_tabbed(self) -> None: 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

1260 # The file contents would look like this with 8-space tabs: 

1261 # x = 1 

1262 # if x: 

1263 # a = "tabbed" # aligned comments 

1264 # if x: # look nice 

1265 # b = "no spaces" # when they 

1266 # c = "done" # line up. 

1267 self.make_file( 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

1268 "tabbed.py", 

1269 """\ 

1270 x = 1 

1271 if x: 

1272 \ta = "Tabbed"\t\t\t\t# Aligned comments 

1273 \tif x:\t\t\t\t\t# look nice 

1274 \t\tb = "No spaces"\t\t\t# when they 

1275 \tc = "Done"\t\t\t\t# line up. 

1276 """, 

1277 ) 

1278 

1279 cov = coverage.Coverage() 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

1280 tabbed = self.start_import_stop(cov, "tabbed") 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

1281 cov.html_report(tabbed, directory="out") 1stuvabwxyzcdABCDefEFGHghIJKLijMNOPklQRSTmnUVWXopYZ01qr342

1282 

1283 # Editors like to change things, make sure our source file still has tabs. 

1284 contains("tabbed.py", "\tif x:\t\t\t\t\t# look nice") 1stuvabwxyzcdABCDefEFGHghIJKLijMNOPklQRSTmnUVWXopYZ01qr342

1285 

1286 contains( 1stuvabwxyzcdABCDefEFGHghIJKLijMNOPklQRSTmnUVWXopYZ01qr342

1287 "out/tabbed_py.html", 

1288 '> <span class="key">if</span> ' 

1289 + '<span class="nam">x</span><span class="op">:</span>' 

1290 + " " 

1291 + '<span class="com"># look nice</span>', 

1292 ) 

1293 

1294 doesnt_contain("out/tabbed_py.html", "\t") 1stuvabwxyzcdABCDefEFGHghIJKLijMNOPklQRSTmnUVWXopYZ01qr342

1295 

1296 def test_bug_1828(self) -> None: 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

1297 # https://github.com/coveragepy/coveragepy/pull/1828 

1298 self.make_file( 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

1299 "backslashes.py", 

1300 """\ 

1301 a = ["aaa",\\ 

1302 "bbb \\ 

1303 ccc"] 

1304 """, 

1305 ) 

1306 

1307 cov = coverage.Coverage() 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

1308 backslashes = self.start_import_stop(cov, "backslashes") 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

1309 cov.html_report(backslashes) 1stuvabwxyzcdABCDefEFGHghIJKLijMNOPklQRSTmnUVWXopYZ01qr342

1310 

1311 contains( 1stuvabwxyzcdABCDefEFGHghIJKLijMNOPklQRSTmnUVWXopYZ01qr342

1312 "htmlcov/backslashes_py.html", 

1313 # line 2 is `"bbb \` 

1314 r'<a id="t2" href="#t2">2</a></span>' 

1315 + r'<span class="t"> <span class="str">"bbb \</span>', 

1316 # line 3 is `ccc"]` 

1317 r'<a id="t3" href="#t3">3</a></span>' 

1318 + r'<span class="t"><span class="str"> ccc"</span><span class="op">]</span>', 

1319 ) 

1320 

1321 assert self.get_html_report_text_lines("backslashes.py") == [ 1stuvabwxyzcdABCDefEFGHghIJKLijMNOPklQRSTmnUVWXopYZ01qr342

1322 '1a = ["aaa",\\', 

1323 '2 "bbb \\', 

1324 '3 ccc"]', 

1325 ] 

1326 

1327 @pytest.mark.parametrize( 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

1328 "leader", 

1329 ["", "f", "r", "fr", "rf"], 

1330 ids=["string", "f-string", "raw_string", "f-raw_string", "raw_f-string"], 

1331 ) 

1332 def test_bug_1836(self, leader: str) -> None: 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

1333 # https://github.com/coveragepy/coveragepy/issues/1836 

1334 self.make_file( 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

1335 "py312_fstrings.py", 

1336 f"""\ 

1337 prog_name = 'bug.py' 

1338 err_msg = {leader}'''\\ 

1339 {{prog_name}}: ERROR: This is the first line of the error. 

1340 {{prog_name}}: ERROR: This is the second line of the error. 

1341 \\ 

1342 {{prog_name}}: ERROR: This is the third line of the error. 

1343 ''' 

1344 """, 

1345 ) 

1346 

1347 cov = coverage.Coverage() 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

1348 py312_fstrings = self.start_import_stop(cov, "py312_fstrings") 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

1349 cov.html_report(py312_fstrings) 1stuvabwxyzcdABCDefEFGHghIJKLijMNOPklQRSTmnUVWXopYZ01qr342

1350 

1351 assert self.get_html_report_text_lines("py312_fstrings.py") == [ 1stuvabwxyzcdABCDefEFGHghIJKLijMNOPklQRSTmnUVWXopYZ01qr342

1352 "1" + "prog_name = 'bug.py'", 

1353 "2" + f"err_msg = {leader}'''\\", 

1354 "3" + "{prog_name}: ERROR: This is the first line of the error.", 

1355 "4" + "{prog_name}: ERROR: This is the second line of the error.", 

1356 "5" + "\\", 

1357 "6" + "{prog_name}: ERROR: This is the third line of the error.", 

1358 "7" + "'''", 

1359 ] 

1360 

1361 def test_bug_1980(self) -> None: 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

1362 self.make_file( 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

1363 "fstring_middle.py", 

1364 """\ 

1365 x = 1 

1366 f'Look: {x} {{x}}!' 

1367 """, 

1368 ) 

1369 

1370 cov = coverage.Coverage() 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

1371 the_mod = self.start_import_stop(cov, "fstring_middle") 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

1372 cov.html_report(the_mod) 1stuvabwxyzcdABCDefEFGHghIJKLijMNOPklQRSTmnUVWXopYZ01qr342

1373 

1374 assert self.get_html_report_text_lines("fstring_middle.py") == [ 1stuvabwxyzcdABCDefEFGHghIJKLijMNOPklQRSTmnUVWXopYZ01qr342

1375 "1" + "x = 1", 

1376 "2" + "f'Look: {x} {{x}}!'", 

1377 ] 

1378 

1379 def test_unicode(self) -> None: 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

1380 surrogate = "\U000e0100" 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

1381 

1382 self.make_file( 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

1383 "unicode.py", 

1384 """\ 

1385 # -*- coding: utf-8 -*- 

1386 # A Python source file with exotic characters. 

1387 

1388 upside_down = "ʎd˙ǝbɐɹǝʌoɔ" 

1389 surrogate = "db40,dd00: x@" 

1390 """.replace("@", surrogate), 

1391 ) 

1392 

1393 cov = coverage.Coverage() 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

1394 unimod = self.start_import_stop(cov, "unicode") 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

1395 cov.html_report(unimod, directory="out/unicode") 1stuvabwxyzcdABCDefEFGHghIJKLijMNOPklQRSTmnUVWXopYZ01qr342

1396 

1397 compare_html(gold_path("html/unicode"), "out/unicode") 1stuvabwxyzcdABCDefEFGHghIJKLijMNOPklQRSTmnUVWXopYZ01qr342

1398 contains( 1stuvabwxyzcdABCDefEFGHghIJKLijMNOPklQRSTmnUVWXopYZ01qr342

1399 "out/unicode/unicode_py.html", 

1400 '<span class="str">"&#654;d&#729;&#477;b&#592;&#633;&#477;&#652;o&#596;"</span>', 

1401 ) 

1402 

1403 contains_any( 1stuvabwxyzcdABCDefEFGHghIJKLijMNOPklQRSTmnUVWXopYZ01qr342

1404 "out/unicode/unicode_py.html", 

1405 '<span class="str">"db40,dd00: x&#56128;&#56576;"</span>', 

1406 '<span class="str">"db40,dd00: x&#917760;"</span>', 

1407 ) 

1408 

1409 def test_accented_dot_py(self) -> None: 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

1410 # Make a file with a non-ascii character in the filename. 

1411 self.make_file("h\xe2t.py", "print('accented')") 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

1412 self.make_data_file(lines={abs_file("h\xe2t.py"): [1]}) 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

1413 cov = coverage.Coverage() 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

1414 cov.load() 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

1415 cov.html_report() 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

1416 self.assert_exists("htmlcov/h\xe2t_py.html") 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

1417 with open("htmlcov/index.html", encoding="utf-8") as indexf: 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

1418 index = indexf.read() 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

1419 assert '<a href="h&#226;t_py.html">h&#226;t.py</a>' in index 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

1420 

1421 def test_accented_directory(self) -> None: 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

1422 # Make a file with a non-ascii character in the directory name. 

1423 self.make_file("\xe2/accented.py", "print('accented')") 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

1424 self.make_data_file(lines={abs_file("\xe2/accented.py"): [1]}) 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

1425 

1426 # The HTML report uses ascii-encoded HTML entities. 

1427 cov = coverage.Coverage() 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

1428 cov.load() 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

1429 cov.html_report() 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

1430 self.assert_exists("htmlcov/z_5786906b6f0ffeb4_accented_py.html") 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

1431 with open("htmlcov/index.html", encoding="utf-8") as indexf: 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

1432 index = indexf.read() 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

1433 expected = ( 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

1434 '<a href="z_5786906b6f0ffeb4_accented_py.html">&#226;&#8201;%s&#8201;accented.py</a>' 

1435 ) 

1436 assert expected % os.sep in index 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

1437 

1438 

1439@pytest.mark.skipif(not testenv.DYN_CONTEXTS, reason="No dynamic contexts with this core.") 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

1440class HtmlWithContextsTest(HtmlTestHelpers, CoverageTest): 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

1441 """Tests of the HTML reports with shown contexts.""" 

1442 

1443 EMPTY = coverage.html.HtmlDataGeneration.EMPTY 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

1444 

1445 def html_data_from_cov(self, cov: Coverage, morf: TMorf) -> coverage.html.FileData: 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

1446 """Get HTML report data from a `Coverage` object for a morf.""" 

1447 with self.assert_warnings(cov, []): 1stuvabwxyzcdABCDefEFGHghIJKLijMNOPklQRSTmnUVWXopYZ01qr342

1448 datagen = coverage.html.HtmlDataGeneration(cov) 1stuvabwxyzcdABCDefEFGHghIJKLijMNOPklQRSTmnUVWXopYZ01qr342

1449 fr, analysis = next(iter(get_analysis_to_report(cov, [morf]))) 1stuvabwxyzcdABCDefEFGHghIJKLijMNOPklQRSTmnUVWXopYZ01qr342

1450 file_data = datagen.data_for_file(fr, analysis) 1stuvabwxyzcdABCDefEFGHghIJKLijMNOPklQRSTmnUVWXopYZ01qr342

1451 return file_data 1stuvabwxyzcdABCDefEFGHghIJKLijMNOPklQRSTmnUVWXopYZ01qr342

1452 

1453 SOURCE = """\ 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

1454 def helper(lineno): 

1455 x = 2 

1456 

1457 def test_one(): 

1458 a = 5 

1459 helper(6) 

1460 

1461 def test_two(): 

1462 a = 9 

1463 b = 10 

1464 if a > 11: 

1465 b = 12 

1466 assert a == (13-4) 

1467 assert b == (14-4) 

1468 helper( 

1469 16 

1470 ) 

1471 

1472 test_one() 

1473 x = 20 

1474 helper(21) 

1475 test_two() 

1476 """ 

1477 

1478 OUTER_LINES = [1, 4, 8, 19, 20, 21, 2, 22] 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

1479 TEST_ONE_LINES = [5, 6, 2] 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

1480 TEST_TWO_LINES = [9, 10, 11, 13, 14, 15, 2] 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

1481 

1482 def test_dynamic_contexts(self) -> None: 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

1483 self.make_file("two_tests.py", self.SOURCE) 1stuvabwxyzcdABCDefEFGHghIJKLijMNOPklQRSTmnUVWXopYZ01qr342

1484 cov = coverage.Coverage(source=["."]) 1stuvabwxyzcdABCDefEFGHghIJKLijMNOPklQRSTmnUVWXopYZ01qr342

1485 cov.set_option("run:dynamic_context", "test_function") 1stuvabwxyzcdABCDefEFGHghIJKLijMNOPklQRSTmnUVWXopYZ01qr342

1486 cov.set_option("html:show_contexts", True) 1stuvabwxyzcdABCDefEFGHghIJKLijMNOPklQRSTmnUVWXopYZ01qr342

1487 mod = self.start_import_stop(cov, "two_tests") 1stuvabwxyzcdABCDefEFGHghIJKLijMNOPklQRSTmnUVWXopYZ01qr342

1488 d = self.html_data_from_cov(cov, mod) 1stuvabwxyzcdABCDefEFGHghIJKLijMNOPklQRSTmnUVWXopYZ01qr342

1489 context_labels = [self.EMPTY, "two_tests.test_one", "two_tests.test_two"] 1stuvabwxyzcdABCDefEFGHghIJKLijMNOPklQRSTmnUVWXopYZ01qr342

1490 expected_lines = [self.OUTER_LINES, self.TEST_ONE_LINES, self.TEST_TWO_LINES] 1stuvabwxyzcdABCDefEFGHghIJKLijMNOPklQRSTmnUVWXopYZ01qr342

1491 for label, expected in zip(context_labels, expected_lines): 1stuvabwxyzcdABCDefEFGHghIJKLijMNOPklQRSTmnUVWXopYZ01qr342

1492 actual = [ 1stuvabwxyzcdABCDefEFGHghIJKLijMNOPklQRSTmnUVWXopYZ01qr342

1493 ld.number 

1494 for ld in d.lines 

1495 if label == ld.contexts_label or label in (ld.contexts or ()) 

1496 ] 

1497 assert sorted(expected) == sorted(actual) 1stuvabwxyzcdABCDefEFGHghIJKLijMNOPklQRSTmnUVWXopYZ01qr342

1498 

1499 cov.html_report(mod, directory="out/contexts") 1stuvabwxyzcdABCDefEFGHghIJKLijMNOPklQRSTmnUVWXopYZ01qr342

1500 compare_html(gold_path("html/contexts"), "out/contexts") 1stuvabwxyzcdABCDefEFGHghIJKLijMNOPklQRSTmnUVWXopYZ01qr342

1501 

1502 def test_filtered_dynamic_contexts(self) -> None: 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

1503 self.make_file("two_tests.py", self.SOURCE) 1stuvabwxyzcdABCDefEFGHghIJKLijMNOPklQRSTmnUVWXopYZ01qr342

1504 cov = coverage.Coverage(source=["."]) 1stuvabwxyzcdABCDefEFGHghIJKLijMNOPklQRSTmnUVWXopYZ01qr342

1505 cov.set_option("run:dynamic_context", "test_function") 1stuvabwxyzcdABCDefEFGHghIJKLijMNOPklQRSTmnUVWXopYZ01qr342

1506 cov.set_option("html:show_contexts", True) 1stuvabwxyzcdABCDefEFGHghIJKLijMNOPklQRSTmnUVWXopYZ01qr342

1507 cov.set_option("report:contexts", ["test_one"]) 1stuvabwxyzcdABCDefEFGHghIJKLijMNOPklQRSTmnUVWXopYZ01qr342

1508 mod = self.start_import_stop(cov, "two_tests") 1stuvabwxyzcdABCDefEFGHghIJKLijMNOPklQRSTmnUVWXopYZ01qr342

1509 d = self.html_data_from_cov(cov, mod) 1stuvabwxyzcdABCDefEFGHghIJKLijMNOPklQRSTmnUVWXopYZ01qr342

1510 

1511 context_labels = [self.EMPTY, "two_tests.test_one", "two_tests.test_two"] 1stuvabwxyzcdABCDefEFGHghIJKLijMNOPklQRSTmnUVWXopYZ01qr342

1512 expected_lines: list[list[TLineNo]] = [[], self.TEST_ONE_LINES, []] 1stuvabwxyzcdABCDefEFGHghIJKLijMNOPklQRSTmnUVWXopYZ01qr342

1513 for label, expected in zip(context_labels, expected_lines): 1stuvabwxyzcdABCDefEFGHghIJKLijMNOPklQRSTmnUVWXopYZ01qr342

1514 actual = [ld.number for ld in d.lines if label in (ld.contexts or ())] 1stuvabwxyzcdABCDefEFGHghIJKLijMNOPklQRSTmnUVWXopYZ01qr342

1515 assert sorted(expected) == sorted(actual) 1stuvabwxyzcdABCDefEFGHghIJKLijMNOPklQRSTmnUVWXopYZ01qr342

1516 

1517 def test_no_contexts_warns_no_contexts(self) -> None: 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

1518 # If no contexts were collected, then show_contexts emits a warning. 

1519 self.make_file("two_tests.py", self.SOURCE) 1stuvabwxyzcdABCDefEFGHghIJKLijMNOPklQRSTmnUVWXopYZ01qr342

1520 cov = coverage.Coverage(source=["."]) 1stuvabwxyzcdABCDefEFGHghIJKLijMNOPklQRSTmnUVWXopYZ01qr342

1521 cov.set_option("html:show_contexts", True) 1stuvabwxyzcdABCDefEFGHghIJKLijMNOPklQRSTmnUVWXopYZ01qr342

1522 self.start_import_stop(cov, "two_tests") 1stuvabwxyzcdABCDefEFGHghIJKLijMNOPklQRSTmnUVWXopYZ01qr342

1523 with self.assert_warnings(cov, ["No contexts were measured"]): 1stuvabwxyzcdABCDefEFGHghIJKLijMNOPklQRSTmnUVWXopYZ01qr342

1524 cov.html_report() 1stuvabwxyzcdABCDefEFGHghIJKLijMNOPklQRSTmnUVWXopYZ01qr342

1525 

1526 def test_dynamic_contexts_relative_files(self) -> None: 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

1527 self.make_file("two_tests.py", self.SOURCE) 1stuvabwxyzcdABCDefEFGHghIJKLijMNOPklQRSTmnUVWXopYZ01qr342

1528 self.make_file("config", "[run]\nrelative_files = True") 1stuvabwxyzcdABCDefEFGHghIJKLijMNOPklQRSTmnUVWXopYZ01qr342

1529 cov = coverage.Coverage(source=["."], config_file="config") 1stuvabwxyzcdABCDefEFGHghIJKLijMNOPklQRSTmnUVWXopYZ01qr342

1530 cov.set_option("run:dynamic_context", "test_function") 1stuvabwxyzcdABCDefEFGHghIJKLijMNOPklQRSTmnUVWXopYZ01qr342

1531 cov.set_option("html:show_contexts", True) 1stuvabwxyzcdABCDefEFGHghIJKLijMNOPklQRSTmnUVWXopYZ01qr342

1532 mod = self.start_import_stop(cov, "two_tests") 1stuvabwxyzcdABCDefEFGHghIJKLijMNOPklQRSTmnUVWXopYZ01qr342

1533 d = self.html_data_from_cov(cov, mod) 1stuvabwxyzcdABCDefEFGHghIJKLijMNOPklQRSTmnUVWXopYZ01qr342

1534 context_labels = [self.EMPTY, "two_tests.test_one", "two_tests.test_two"] 1stuvabwxyzcdABCDefEFGHghIJKLijMNOPklQRSTmnUVWXopYZ01qr342

1535 expected_lines = [self.OUTER_LINES, self.TEST_ONE_LINES, self.TEST_TWO_LINES] 1stuvabwxyzcdABCDefEFGHghIJKLijMNOPklQRSTmnUVWXopYZ01qr342

1536 for label, expected in zip(context_labels, expected_lines): 1stuvabwxyzcdABCDefEFGHghIJKLijMNOPklQRSTmnUVWXopYZ01qr342

1537 actual = [ 1stuvabwxyzcdABCDefEFGHghIJKLijMNOPklQRSTmnUVWXopYZ01qr342

1538 ld.number 

1539 for ld in d.lines 

1540 if label == ld.contexts_label or label in (ld.contexts or ()) 

1541 ] 

1542 assert sorted(expected) == sorted(actual) 1stuvabwxyzcdABCDefEFGHghIJKLijMNOPklQRSTmnUVWXopYZ01qr342

1543 

1544 

1545class HtmlHelpersTest(HtmlTestHelpers, CoverageTest): 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

1546 """Tests of the helpers in HtmlTestHelpers.""" 

1547 

1548 def test_bad_link(self) -> None: 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

1549 # Does assert_valid_hrefs detect links to non-existent files? 

1550 self.make_file("htmlcov/index.html", "<a href='nothing.html'>Nothing</a>") 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

1551 msg = "These files link to 'nothing.html', which doesn't exist: htmlcov.index.html" 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

1552 with pytest.raises(AssertionError, match=msg): 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

1553 self.assert_valid_hrefs() 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

1554 

1555 def test_bad_anchor(self) -> None: 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

1556 # Does assert_valid_hrefs detect fragments that go nowhere? 

1557 self.make_file("htmlcov/index.html", "<a href='#nothing'>Nothing</a>") 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

1558 msg = "Fragment '#nothing' in htmlcov.index.html has no anchor" 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

1559 with pytest.raises(AssertionError, match=msg): 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

1560 self.assert_valid_hrefs() 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

1561 

1562 

1563@pytest.mark.parametrize( 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

1564 "n, key", 

1565 [ 

1566 (0, "a"), 

1567 (1, "b"), 

1568 (999999999, "e9S_p"), 

1569 ], 

1570) 

1571def test_encode_int(n: int, key: str) -> None: 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342

1572 assert coverage.html.encode_int(n) == key 1stuvabwxyzcdABCDefEFGHghIJKLijMN9OP!kl5QR#ST$mn6UV%WX'op7YZ(01)qr8342