OLD | NEW |
(Empty) | |
| 1 # -*- coding: utf-8 -*- |
| 2 # Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 |
| 3 # For details: https://bitbucket.org/ned/coveragepy/src/default/NOTICE.txt |
| 4 |
| 5 """Tests that HTML generation is awesome.""" |
| 6 |
| 7 import datetime |
| 8 import glob |
| 9 import os |
| 10 import os.path |
| 11 import re |
| 12 import sys |
| 13 |
| 14 import coverage |
| 15 import coverage.files |
| 16 import coverage.html |
| 17 from coverage.misc import CoverageException, NotPython, NoSource |
| 18 |
| 19 from tests.coveragetest import CoverageTest |
| 20 from tests.goldtest import CoverageGoldTest |
| 21 from tests.goldtest import change_dir, compare, contains, doesnt_contain, contai
ns_any |
| 22 |
| 23 |
| 24 class HtmlTestHelpers(CoverageTest): |
| 25 """Methods that help with HTML tests.""" |
| 26 |
| 27 def create_initial_files(self): |
| 28 """Create the source files we need to run these tests.""" |
| 29 self.make_file("main_file.py", """\ |
| 30 import helper1, helper2 |
| 31 helper1.func1(12) |
| 32 helper2.func2(12) |
| 33 """) |
| 34 self.make_file("helper1.py", """\ |
| 35 def func1(x): |
| 36 if x % 2: |
| 37 print("odd") |
| 38 """) |
| 39 self.make_file("helper2.py", """\ |
| 40 def func2(x): |
| 41 print("x is %d" % x) |
| 42 """) |
| 43 |
| 44 def run_coverage(self, covargs=None, htmlargs=None): |
| 45 """Run coverage.py on main_file.py, and create an HTML report.""" |
| 46 self.clean_local_file_imports() |
| 47 cov = coverage.Coverage(**(covargs or {})) |
| 48 self.start_import_stop(cov, "main_file") |
| 49 cov.html_report(**(htmlargs or {})) |
| 50 |
| 51 def remove_html_files(self): |
| 52 """Remove the HTML files created as part of the HTML report.""" |
| 53 os.remove("htmlcov/index.html") |
| 54 os.remove("htmlcov/main_file_py.html") |
| 55 os.remove("htmlcov/helper1_py.html") |
| 56 os.remove("htmlcov/helper2_py.html") |
| 57 |
| 58 def get_html_report_content(self, module): |
| 59 """Return the content of the HTML report for `module`.""" |
| 60 filename = module.replace(".", "_").replace("/", "_") + ".html" |
| 61 filename = os.path.join("htmlcov", filename) |
| 62 with open(filename) as f: |
| 63 return f.read() |
| 64 |
| 65 def get_html_index_content(self): |
| 66 """Return the content of index.html. |
| 67 |
| 68 Timestamps are replaced with a placeholder so that clocks don't matter. |
| 69 |
| 70 """ |
| 71 with open("htmlcov/index.html") as f: |
| 72 index = f.read() |
| 73 index = re.sub( |
| 74 r"created at \d{4}-\d{2}-\d{2} \d{2}:\d{2}", |
| 75 r"created at YYYY-MM-DD HH:MM", |
| 76 index, |
| 77 ) |
| 78 return index |
| 79 |
| 80 def assert_correct_timestamp(self, html): |
| 81 """Extract the timestamp from `html`, and assert it is recent.""" |
| 82 timestamp_pat = r"created at (\d{4})-(\d{2})-(\d{2}) (\d{2}):(\d{2})" |
| 83 m = re.search(timestamp_pat, html) |
| 84 self.assertTrue(m, "Didn't find a timestamp!") |
| 85 timestamp = datetime.datetime(*map(int, m.groups())) |
| 86 # The timestamp only records the minute, so the delta could be from |
| 87 # 12:00 to 12:01:59, or two minutes. |
| 88 self.assert_recent_datetime( |
| 89 timestamp, |
| 90 seconds=120, |
| 91 msg="Timestamp is wrong: {0}".format(timestamp), |
| 92 ) |
| 93 |
| 94 |
| 95 class HtmlDeltaTest(HtmlTestHelpers, CoverageTest): |
| 96 """Tests of the HTML delta speed-ups.""" |
| 97 |
| 98 def setUp(self): |
| 99 super(HtmlDeltaTest, self).setUp() |
| 100 |
| 101 # At least one of our tests monkey-patches the version of coverage.py, |
| 102 # so grab it here to restore it later. |
| 103 self.real_coverage_version = coverage.__version__ |
| 104 self.addCleanup(self.cleanup_coverage_version) |
| 105 |
| 106 def cleanup_coverage_version(self): |
| 107 """A cleanup.""" |
| 108 coverage.__version__ = self.real_coverage_version |
| 109 |
| 110 def test_html_created(self): |
| 111 # Test basic HTML generation: files should be created. |
| 112 self.create_initial_files() |
| 113 self.run_coverage() |
| 114 |
| 115 self.assert_exists("htmlcov/index.html") |
| 116 self.assert_exists("htmlcov/main_file_py.html") |
| 117 self.assert_exists("htmlcov/helper1_py.html") |
| 118 self.assert_exists("htmlcov/helper2_py.html") |
| 119 self.assert_exists("htmlcov/style.css") |
| 120 self.assert_exists("htmlcov/coverage_html.js") |
| 121 |
| 122 def test_html_delta_from_source_change(self): |
| 123 # HTML generation can create only the files that have changed. |
| 124 # In this case, helper1 changes because its source is different. |
| 125 self.create_initial_files() |
| 126 self.run_coverage() |
| 127 index1 = self.get_html_index_content() |
| 128 self.remove_html_files() |
| 129 |
| 130 # Now change a file and do it again |
| 131 self.make_file("helper1.py", """\ |
| 132 def func1(x): # A nice function |
| 133 if x % 2: |
| 134 print("odd") |
| 135 """) |
| 136 |
| 137 self.run_coverage() |
| 138 |
| 139 # Only the changed files should have been created. |
| 140 self.assert_exists("htmlcov/index.html") |
| 141 self.assert_exists("htmlcov/helper1_py.html") |
| 142 self.assert_doesnt_exist("htmlcov/main_file_py.html") |
| 143 self.assert_doesnt_exist("htmlcov/helper2_py.html") |
| 144 index2 = self.get_html_index_content() |
| 145 self.assertMultiLineEqual(index1, index2) |
| 146 |
| 147 def test_html_delta_from_coverage_change(self): |
| 148 # HTML generation can create only the files that have changed. |
| 149 # In this case, helper1 changes because its coverage is different. |
| 150 self.create_initial_files() |
| 151 self.run_coverage() |
| 152 self.remove_html_files() |
| 153 |
| 154 # Now change a file and do it again |
| 155 self.make_file("main_file.py", """\ |
| 156 import helper1, helper2 |
| 157 helper1.func1(23) |
| 158 helper2.func2(23) |
| 159 """) |
| 160 |
| 161 self.run_coverage() |
| 162 |
| 163 # Only the changed files should have been created. |
| 164 self.assert_exists("htmlcov/index.html") |
| 165 self.assert_exists("htmlcov/helper1_py.html") |
| 166 self.assert_exists("htmlcov/main_file_py.html") |
| 167 self.assert_doesnt_exist("htmlcov/helper2_py.html") |
| 168 |
| 169 def test_html_delta_from_settings_change(self): |
| 170 # HTML generation can create only the files that have changed. |
| 171 # In this case, everything changes because the coverage.py settings |
| 172 # have changed. |
| 173 self.create_initial_files() |
| 174 self.run_coverage(covargs=dict(omit=[])) |
| 175 index1 = self.get_html_index_content() |
| 176 self.remove_html_files() |
| 177 |
| 178 self.run_coverage(covargs=dict(omit=['xyzzy*'])) |
| 179 |
| 180 # All the files have been reported again. |
| 181 self.assert_exists("htmlcov/index.html") |
| 182 self.assert_exists("htmlcov/helper1_py.html") |
| 183 self.assert_exists("htmlcov/main_file_py.html") |
| 184 self.assert_exists("htmlcov/helper2_py.html") |
| 185 index2 = self.get_html_index_content() |
| 186 self.assertMultiLineEqual(index1, index2) |
| 187 |
| 188 def test_html_delta_from_coverage_version_change(self): |
| 189 # HTML generation can create only the files that have changed. |
| 190 # In this case, everything changes because the coverage.py version has |
| 191 # changed. |
| 192 self.create_initial_files() |
| 193 self.run_coverage() |
| 194 index1 = self.get_html_index_content() |
| 195 self.remove_html_files() |
| 196 |
| 197 # "Upgrade" coverage.py! |
| 198 coverage.__version__ = "XYZZY" |
| 199 |
| 200 self.run_coverage() |
| 201 |
| 202 # All the files have been reported again. |
| 203 self.assert_exists("htmlcov/index.html") |
| 204 self.assert_exists("htmlcov/helper1_py.html") |
| 205 self.assert_exists("htmlcov/main_file_py.html") |
| 206 self.assert_exists("htmlcov/helper2_py.html") |
| 207 index2 = self.get_html_index_content() |
| 208 fixed_index2 = index2.replace("XYZZY", self.real_coverage_version) |
| 209 self.assertMultiLineEqual(index1, fixed_index2) |
| 210 |
| 211 |
| 212 class HtmlTitleTest(HtmlTestHelpers, CoverageTest): |
| 213 """Tests of the HTML title support.""" |
| 214 |
| 215 def test_default_title(self): |
| 216 self.create_initial_files() |
| 217 self.run_coverage() |
| 218 index = self.get_html_index_content() |
| 219 self.assertIn("<title>Coverage report</title>", index) |
| 220 self.assertIn("<h1>Coverage report:", index) |
| 221 |
| 222 def test_title_set_in_config_file(self): |
| 223 self.create_initial_files() |
| 224 self.make_file(".coveragerc", "[html]\ntitle = Metrics & stuff!\n") |
| 225 self.run_coverage() |
| 226 index = self.get_html_index_content() |
| 227 self.assertIn("<title>Metrics & stuff!</title>", index) |
| 228 self.assertIn("<h1>Metrics & stuff!:", index) |
| 229 |
| 230 def test_non_ascii_title_set_in_config_file(self): |
| 231 self.create_initial_files() |
| 232 self.make_file(".coveragerc", "[html]\ntitle = «ταБЬℓσ» numbers") |
| 233 self.run_coverage() |
| 234 index = self.get_html_index_content() |
| 235 self.assertIn( |
| 236 "<title>«ταБЬℓσ»" |
| 237 " numbers", index |
| 238 ) |
| 239 self.assertIn( |
| 240 "<h1>«ταБЬℓσ»" |
| 241 " numbers", index |
| 242 ) |
| 243 |
| 244 def test_title_set_in_args(self): |
| 245 self.create_initial_files() |
| 246 self.make_file(".coveragerc", "[html]\ntitle = Good title\n") |
| 247 self.run_coverage(htmlargs=dict(title="«ταБЬℓσ» & stüff!")) |
| 248 index = self.get_html_index_content() |
| 249 self.assertIn( |
| 250 "<title>«ταБЬℓσ»" |
| 251 " & stüff!</title>", index |
| 252 ) |
| 253 self.assertIn( |
| 254 "<h1>«ταБЬℓσ»" |
| 255 " & stüff!:", index |
| 256 ) |
| 257 |
| 258 |
| 259 class HtmlWithUnparsableFilesTest(HtmlTestHelpers, CoverageTest): |
| 260 """Test the behavior when measuring unparsable files.""" |
| 261 |
| 262 def test_dotpy_not_python(self): |
| 263 self.make_file("innocuous.py", "a = 1") |
| 264 cov = coverage.Coverage() |
| 265 self.start_import_stop(cov, "innocuous") |
| 266 self.make_file("innocuous.py", "<h1>This isn't python!</h1>") |
| 267 msg = "Couldn't parse '.*innocuous.py' as Python source: .* at line 1" |
| 268 with self.assertRaisesRegex(NotPython, msg): |
| 269 cov.html_report() |
| 270 |
| 271 def test_dotpy_not_python_ignored(self): |
| 272 self.make_file("innocuous.py", "a = 2") |
| 273 cov = coverage.Coverage() |
| 274 self.start_import_stop(cov, "innocuous") |
| 275 self.make_file("innocuous.py", "<h1>This isn't python!</h1>") |
| 276 cov.html_report(ignore_errors=True) |
| 277 self.assert_exists("htmlcov/index.html") |
| 278 # This would be better as a glob, if the HTML layout changes: |
| 279 self.assert_doesnt_exist("htmlcov/innocuous.html") |
| 280 |
| 281 def test_dothtml_not_python(self): |
| 282 # We run a .html file, and when reporting, we can't parse it as |
| 283 # Python. Since it wasn't .py, no error is reported. |
| 284 |
| 285 # Run an "HTML" file |
| 286 self.make_file("innocuous.html", "a = 3") |
| 287 self.run_command("coverage run innocuous.html") |
| 288 # Before reporting, change it to be an HTML file. |
| 289 self.make_file("innocuous.html", "<h1>This isn't python at all!</h1>") |
| 290 output = self.run_command("coverage html") |
| 291 self.assertEqual(output.strip(), "No data to report.") |
| 292 |
| 293 def test_execed_liar_ignored(self): |
| 294 # Jinja2 sets __file__ to be a non-Python file, and then execs code. |
| 295 # If that file contains non-Python code, a TokenError shouldn't |
| 296 # have been raised when writing the HTML report. |
| 297 source = "exec(compile('','','exec'), {'__file__': 'liar.html'})" |
| 298 self.make_file("liar.py", source) |
| 299 self.make_file("liar.html", "{# Whoops, not python code #}") |
| 300 cov = coverage.Coverage() |
| 301 self.start_import_stop(cov, "liar") |
| 302 cov.html_report() |
| 303 self.assert_exists("htmlcov/index.html") |
| 304 |
| 305 def test_execed_liar_ignored_indentation_error(self): |
| 306 # Jinja2 sets __file__ to be a non-Python file, and then execs code. |
| 307 # If that file contains untokenizable code, we shouldn't get an |
| 308 # exception. |
| 309 source = "exec(compile('','','exec'), {'__file__': 'liar.html'})" |
| 310 self.make_file("liar.py", source) |
| 311 # Tokenize will raise an IndentationError if it can't dedent. |
| 312 self.make_file("liar.html", "0\n 2\n 1\n") |
| 313 cov = coverage.Coverage() |
| 314 self.start_import_stop(cov, "liar") |
| 315 cov.html_report() |
| 316 self.assert_exists("htmlcov/index.html") |
| 317 |
| 318 def test_decode_error(self): |
| 319 # https://bitbucket.org/ned/coveragepy/issue/351/files-with-incorrect-en
coding-are-ignored |
| 320 # imp.load_module won't load a file with an undecodable character |
| 321 # in a comment, though Python will run them. So we'll change the |
| 322 # file after running. |
| 323 self.make_file("main.py", "import sub.not_ascii") |
| 324 self.make_file("sub/__init__.py") |
| 325 self.make_file("sub/not_ascii.py", """\ |
| 326 # coding: utf8 |
| 327 a = 1 # Isn't this great?! |
| 328 """) |
| 329 cov = coverage.Coverage() |
| 330 self.start_import_stop(cov, "main") |
| 331 |
| 332 # Create the undecodable version of the file. make_file is too helpful, |
| 333 # so get down and dirty with bytes. |
| 334 with open("sub/not_ascii.py", "wb") as f: |
| 335 f.write(b"# coding: utf8\na = 1 # Isn't this great?\xcb!\n") |
| 336 |
| 337 with open("sub/not_ascii.py", "rb") as f: |
| 338 undecodable = f.read() |
| 339 self.assertIn(b"?\xcb!", undecodable) |
| 340 |
| 341 cov.html_report() |
| 342 |
| 343 html_report = self.get_html_report_content("sub/not_ascii.py") |
| 344 expected = "# Isn't this great?�!" |
| 345 self.assertIn(expected, html_report) |
| 346 |
| 347 def test_formfeeds(self): |
| 348 # https://bitbucket.org/ned/coveragepy/issue/360/html-reports-get-confus
ed-by-l-in-the-code |
| 349 self.make_file("formfeed.py", "line_one = 1\n\f\nline_two = 2\n") |
| 350 cov = coverage.Coverage() |
| 351 self.start_import_stop(cov, "formfeed") |
| 352 cov.html_report() |
| 353 |
| 354 formfeed_html = self.get_html_report_content("formfeed.py") |
| 355 self.assertIn("line_two", formfeed_html) |
| 356 |
| 357 |
| 358 class HtmlTest(HtmlTestHelpers, CoverageTest): |
| 359 """Moar HTML tests.""" |
| 360 |
| 361 def test_missing_source_file_incorrect_message(self): |
| 362 # https://bitbucket.org/ned/coveragepy/issue/60 |
| 363 self.make_file("thefile.py", "import sub.another\n") |
| 364 self.make_file("sub/__init__.py", "") |
| 365 self.make_file("sub/another.py", "print('another')\n") |
| 366 cov = coverage.Coverage() |
| 367 self.start_import_stop(cov, 'thefile') |
| 368 os.remove("sub/another.py") |
| 369 |
| 370 missing_file = os.path.join(self.temp_dir, "sub", "another.py") |
| 371 missing_file = os.path.realpath(missing_file) |
| 372 msg = "(?i)No source for code: '%s'" % re.escape(missing_file) |
| 373 with self.assertRaisesRegex(NoSource, msg): |
| 374 cov.html_report() |
| 375 |
| 376 def test_extensionless_file_collides_with_extension(self): |
| 377 # It used to be that "program" and "program.py" would both be reported |
| 378 # to "program.html". Now they are not. |
| 379 # https://bitbucket.org/ned/coveragepy/issue/69 |
| 380 self.make_file("program", "import program\n") |
| 381 self.make_file("program.py", "a = 1\n") |
| 382 self.run_command("coverage run program") |
| 383 self.run_command("coverage html") |
| 384 self.assert_exists("htmlcov/index.html") |
| 385 self.assert_exists("htmlcov/program.html") |
| 386 self.assert_exists("htmlcov/program_py.html") |
| 387 |
| 388 def test_has_date_stamp_in_files(self): |
| 389 self.create_initial_files() |
| 390 self.run_coverage() |
| 391 |
| 392 with open("htmlcov/index.html") as f: |
| 393 self.assert_correct_timestamp(f.read()) |
| 394 with open("htmlcov/main_file_py.html") as f: |
| 395 self.assert_correct_timestamp(f.read()) |
| 396 |
| 397 def test_reporting_on_unmeasured_file(self): |
| 398 # It should be ok to ask for an HTML report on a file that wasn't even |
| 399 # measured at all. https://bitbucket.org/ned/coveragepy/issues/403 |
| 400 self.create_initial_files() |
| 401 self.make_file("other.py", "a = 1\n") |
| 402 self.run_coverage(htmlargs=dict(morfs=['other.py'])) |
| 403 self.assert_exists("htmlcov/index.html") |
| 404 self.assert_exists("htmlcov/other_py.html") |
| 405 |
| 406 def test_shining_panda_fix(self): |
| 407 # The ShiningPanda plugin looks for "status.dat" to find HTML reports. |
| 408 # Accomodate them, but only if we are running under Jenkins. |
| 409 self.set_environ("JENKINS_URL", "Something or other") |
| 410 self.create_initial_files() |
| 411 self.run_coverage() |
| 412 self.assert_exists("htmlcov/status.dat") |
| 413 |
| 414 |
| 415 class HtmlStaticFileTest(CoverageTest): |
| 416 """Tests of the static file copying for the HTML report.""" |
| 417 |
| 418 def setUp(self): |
| 419 super(HtmlStaticFileTest, self).setUp() |
| 420 self.original_path = list(coverage.html.STATIC_PATH) |
| 421 self.addCleanup(self.cleanup_static_path) |
| 422 |
| 423 def cleanup_static_path(self): |
| 424 """A cleanup.""" |
| 425 coverage.html.STATIC_PATH = self.original_path |
| 426 |
| 427 def test_copying_static_files_from_system(self): |
| 428 # Make a new place for static files. |
| 429 self.make_file("static_here/jquery.min.js", "Not Really JQuery!") |
| 430 coverage.html.STATIC_PATH.insert(0, "static_here") |
| 431 |
| 432 self.make_file("main.py", "print(17)") |
| 433 cov = coverage.Coverage() |
| 434 self.start_import_stop(cov, "main") |
| 435 cov.html_report() |
| 436 |
| 437 with open("htmlcov/jquery.min.js") as f: |
| 438 jquery = f.read() |
| 439 self.assertEqual(jquery, "Not Really JQuery!") |
| 440 |
| 441 def test_copying_static_files_from_system_in_dir(self): |
| 442 # Make a new place for static files. |
| 443 INSTALLED = [ |
| 444 "jquery/jquery.min.js", |
| 445 "jquery-hotkeys/jquery.hotkeys.js", |
| 446 "jquery-isonscreen/jquery.isonscreen.js", |
| 447 "jquery-tablesorter/jquery.tablesorter.min.js", |
| 448 ] |
| 449 for fpath in INSTALLED: |
| 450 self.make_file(os.path.join("static_here", fpath), "Not real.") |
| 451 coverage.html.STATIC_PATH.insert(0, "static_here") |
| 452 |
| 453 self.make_file("main.py", "print(17)") |
| 454 cov = coverage.Coverage() |
| 455 self.start_import_stop(cov, "main") |
| 456 cov.html_report() |
| 457 |
| 458 for fpath in INSTALLED: |
| 459 the_file = os.path.basename(fpath) |
| 460 with open(os.path.join("htmlcov", the_file)) as f: |
| 461 contents = f.read() |
| 462 self.assertEqual(contents, "Not real.") |
| 463 |
| 464 def test_cant_find_static_files(self): |
| 465 # Make the path point to useless places. |
| 466 coverage.html.STATIC_PATH = ["/xyzzy"] |
| 467 |
| 468 self.make_file("main.py", "print(17)") |
| 469 cov = coverage.Coverage() |
| 470 self.start_import_stop(cov, "main") |
| 471 msg = "Couldn't find static file u?'.*'" |
| 472 with self.assertRaisesRegex(CoverageException, msg): |
| 473 cov.html_report() |
| 474 |
| 475 |
| 476 class HtmlGoldTests(CoverageGoldTest): |
| 477 """Tests of HTML reporting that use gold files.""" |
| 478 |
| 479 root_dir = 'tests/farm/html' |
| 480 |
| 481 def test_a(self): |
| 482 self.output_dir("out/a") |
| 483 |
| 484 with change_dir("src"): |
| 485 # pylint: disable=import-error |
| 486 cov = coverage.Coverage() |
| 487 cov.start() |
| 488 import a # pragma: nested |
| 489 cov.stop() # pragma: nested |
| 490 cov.html_report(a, directory='../out/a') |
| 491 |
| 492 compare("gold_a", "out/a", size_within=10, file_pattern="*.html") |
| 493 contains( |
| 494 "out/a/a_py.html", |
| 495 ('<span class="key">if</span> <span class="num">1</span> ' |
| 496 '<span class="op"><</span> <span class="num">2</span>'), |
| 497 (' <span class="nam">a</span> ' |
| 498 '<span class="op">=</span> <span class="num">3</span>'), |
| 499 '<span class="pc_cov">67%</span>', |
| 500 ) |
| 501 contains( |
| 502 "out/a/index.html", |
| 503 '<a href="a_py.html">a.py</a>', |
| 504 '<span class="pc_cov">67%</span>', |
| 505 '<td class="right" data-ratio="2 3">67%</td>', |
| 506 ) |
| 507 |
| 508 def test_b_branch(self): |
| 509 self.output_dir("out/b_branch") |
| 510 |
| 511 with change_dir("src"): |
| 512 # pylint: disable=import-error |
| 513 cov = coverage.Coverage(branch=True) |
| 514 cov.start() |
| 515 import b # pragma: nested |
| 516 cov.stop() # pragma: nested |
| 517 cov.html_report(b, directory="../out/b_branch") |
| 518 |
| 519 compare("gold_b_branch", "out/b_branch", size_within=10, file_pattern="*
.html") |
| 520 contains( |
| 521 "out/b_branch/b_py.html", |
| 522 ('<span class="key">if</span> <span class="nam">x</span> ' |
| 523 '<span class="op"><</span> <span class="num">2</span>'), |
| 524 (' <span class="nam">a</span> <span class="op">=</span>
' |
| 525 '<span class="num">3</span>'), |
| 526 '<span class="pc_cov">70%</span>', |
| 527 ('<span class="annotate" title="Line 8 was executed, but never jumpe
d to line 11">' |
| 528 '8 ↛ 11 [?]</span>'), |
| 529 ('<span class="annotate" title="Line 17 was executed, but never jump
ed ' |
| 530 'to the function exit">17 ↛ exit [?]</span>'), |
| 531 ('<span class="annotate" title="Line 25 was executed, but never jump
ed ' |
| 532 'to line 26 or line 28">25 ↛ 26, ' |
| 533 '25 ↛ 28 [?]</span>'), |
| 534 ) |
| 535 contains( |
| 536 "out/b_branch/index.html", |
| 537 '<a href="b_py.html">b.py</a>', |
| 538 '<span class="pc_cov">70%</span>', |
| 539 '<td class="right" data-ratio="16 23">70%</td>', |
| 540 ) |
| 541 |
| 542 def test_bom(self): |
| 543 self.output_dir("out/bom") |
| 544 |
| 545 with change_dir("src"): |
| 546 # pylint: disable=import-error |
| 547 cov = coverage.Coverage() |
| 548 cov.start() |
| 549 import bom # pragma: nested |
| 550 cov.stop() # pragma: nested |
| 551 cov.html_report(bom, directory="../out/bom") |
| 552 |
| 553 compare("gold_bom", "out/bom", size_within=10, file_pattern="*.html") |
| 554 contains( |
| 555 "out/bom/bom_py.html", |
| 556 '<span class="str">"3×4 = 12, ÷2 = 6±0"</sp
an>', |
| 557 ) |
| 558 |
| 559 def test_isolatin1(self): |
| 560 self.output_dir("out/isolatin1") |
| 561 |
| 562 with change_dir("src"): |
| 563 # pylint: disable=import-error |
| 564 cov = coverage.Coverage() |
| 565 cov.start() |
| 566 import isolatin1 # pragma: nested |
| 567 cov.stop() # pragma: nested |
| 568 cov.html_report(isolatin1, directory="../out/isolatin1") |
| 569 |
| 570 compare("gold_isolatin1", "out/isolatin1", size_within=10, file_pattern=
"*.html") |
| 571 contains( |
| 572 "out/isolatin1/isolatin1_py.html", |
| 573 '<span class="str">"3×4 = 12, ÷2 = 6±0"</sp
an>', |
| 574 ) |
| 575 |
| 576 def test_omit_1(self): |
| 577 self.output_dir("out/omit_1") |
| 578 |
| 579 with change_dir("src"): |
| 580 # pylint: disable=import-error, unused-variable |
| 581 cov = coverage.Coverage(include=["./*"]) |
| 582 cov.start() |
| 583 import main # pragma: nested |
| 584 cov.stop() # pragma: nested |
| 585 cov.html_report(directory="../out/omit_1") |
| 586 |
| 587 compare("gold_omit_1", "out/omit_1", size_within=10, file_pattern="*.htm
l") |
| 588 |
| 589 def test_omit_2(self): |
| 590 self.output_dir("out/omit_2") |
| 591 |
| 592 with change_dir("src"): |
| 593 # pylint: disable=import-error, unused-variable |
| 594 cov = coverage.Coverage(include=["./*"]) |
| 595 cov.start() |
| 596 import main # pragma: nested |
| 597 cov.stop() # pragma: nested |
| 598 cov.html_report(directory="../out/omit_2", omit=["m1.py"]) |
| 599 |
| 600 compare("gold_omit_2", "out/omit_2", size_within=10, file_pattern="*.htm
l") |
| 601 |
| 602 def test_omit_3(self): |
| 603 self.output_dir("out/omit_3") |
| 604 |
| 605 with change_dir("src"): |
| 606 # pylint: disable=import-error, unused-variable |
| 607 cov = coverage.Coverage(include=["./*"]) |
| 608 cov.start() |
| 609 import main # pragma: nested |
| 610 cov.stop() # pragma: nested |
| 611 cov.html_report(directory="../out/omit_3", omit=["m1.py", "m2.py"]) |
| 612 |
| 613 compare("gold_omit_3", "out/omit_3", size_within=10, file_pattern="*.htm
l") |
| 614 |
| 615 def test_omit_4(self): |
| 616 self.output_dir("out/omit_4") |
| 617 |
| 618 with change_dir("src"): |
| 619 # pylint: disable=import-error, unused-variable |
| 620 cov = coverage.Coverage(config_file="omit4.ini", include=["./*"]) |
| 621 cov.start() |
| 622 import main # pragma: nested |
| 623 cov.stop() # pragma: nested |
| 624 cov.html_report(directory="../out/omit_4") |
| 625 |
| 626 compare("gold_omit_4", "out/omit_4", size_within=10, file_pattern="*.htm
l") |
| 627 |
| 628 def test_omit_5(self): |
| 629 self.output_dir("out/omit_5") |
| 630 |
| 631 with change_dir("src"): |
| 632 # pylint: disable=import-error, unused-variable |
| 633 cov = coverage.Coverage(config_file="omit5.ini", include=["./*"]) |
| 634 cov.start() |
| 635 import main # pragma: nested |
| 636 cov.stop() # pragma: nested |
| 637 cov.html_report() |
| 638 |
| 639 compare("gold_omit_5", "out/omit_5", size_within=10, file_pattern="*.htm
l") |
| 640 |
| 641 def test_other(self): |
| 642 self.output_dir("out/other") |
| 643 |
| 644 with change_dir("src"): |
| 645 # pylint: disable=import-error, unused-variable |
| 646 sys.path.insert(0, "../othersrc") |
| 647 cov = coverage.Coverage(include=["./*", "../othersrc/*"]) |
| 648 cov.start() |
| 649 import here # pragma: nested |
| 650 cov.stop() # pragma: nested |
| 651 cov.html_report(directory="../out/other") |
| 652 |
| 653 # Different platforms will name the "other" file differently. Rename it |
| 654 for p in glob.glob("out/other/*_other_py.html"): |
| 655 os.rename(p, "out/other/blah_blah_other_py.html") |
| 656 |
| 657 compare("gold_other", "out/other", size_within=10, file_pattern="*.html"
) |
| 658 contains( |
| 659 "out/other/index.html", |
| 660 '<a href="here_py.html">here.py</a>', |
| 661 'other_py.html">', 'other.py</a>', |
| 662 ) |
| 663 |
| 664 def test_partial(self): |
| 665 self.output_dir("out/partial") |
| 666 |
| 667 with change_dir("src"): |
| 668 # pylint: disable=import-error |
| 669 cov = coverage.Coverage(branch=True) |
| 670 cov.start() |
| 671 import partial # pragma: nested |
| 672 cov.stop() # pragma: nested |
| 673 cov.html_report(partial, directory="../out/partial") |
| 674 |
| 675 compare("gold_partial", "out/partial", size_within=10, file_pattern="*.h
tml") |
| 676 contains( |
| 677 "out/partial/partial_py.html", |
| 678 '<p id="t8" class="stm run hide_run">', |
| 679 '<p id="t11" class="stm run hide_run">', |
| 680 '<p id="t14" class="stm run hide_run">', |
| 681 # The "if 0" and "if 1" statements are optimized away. |
| 682 '<p id="t17" class="pln">', |
| 683 ) |
| 684 contains( |
| 685 "out/partial/index.html", |
| 686 '<a href="partial_py.html">partial.py</a>', |
| 687 ) |
| 688 contains( |
| 689 "out/partial/index.html", |
| 690 '<span class="pc_cov">100%</span>' |
| 691 ) |
| 692 |
| 693 def test_styled(self): |
| 694 self.output_dir("out/styled") |
| 695 |
| 696 with change_dir("src"): |
| 697 # pylint: disable=import-error |
| 698 cov = coverage.Coverage() |
| 699 cov.start() |
| 700 import a # pragma: nested |
| 701 cov.stop() # pragma: nested |
| 702 cov.html_report(a, directory="../out/styled", extra_css="extra.css") |
| 703 |
| 704 compare("gold_styled", "out/styled", size_within=10, file_pattern="*.htm
l") |
| 705 compare("gold_styled", "out/styled", size_within=10, file_pattern="*.css
") |
| 706 contains( |
| 707 "out/styled/a_py.html", |
| 708 '<link rel="stylesheet" href="extra.css" type="text/css">', |
| 709 ('<span class="key">if</span> <span class="num">1</span> ' |
| 710 '<span class="op"><</span> <span class="num">2</span>'), |
| 711 (' <span class="nam">a</span> <span class="op">=</span>
' |
| 712 '<span class="num">3</span>'), |
| 713 '<span class="pc_cov">67%</span>' |
| 714 ) |
| 715 contains( |
| 716 "out/styled/index.html", |
| 717 '<link rel="stylesheet" href="extra.css" type="text/css">', |
| 718 '<a href="a_py.html">a.py</a>', |
| 719 '<span class="pc_cov">67%</span>' |
| 720 ) |
| 721 |
| 722 def test_tabbed(self): |
| 723 self.output_dir("out/tabbed") |
| 724 |
| 725 with change_dir("src"): |
| 726 # pylint: disable=import-error |
| 727 cov = coverage.Coverage() |
| 728 cov.start() |
| 729 import tabbed # pragma: nested |
| 730 cov.stop() # pragma: nested |
| 731 cov.html_report(tabbed, directory="../out/tabbed") |
| 732 |
| 733 # Editors like to change things, make sure our source file still has tab
s. |
| 734 contains("src/tabbed.py", "\tif x:\t\t\t\t\t# look nice") |
| 735 |
| 736 contains( |
| 737 "out/tabbed/tabbed_py.html", |
| 738 '> <span class="key">if</span> ' |
| 739 '<span class="nam">x</span><span class="op">:</span>' |
| 740 ' &nbs
p; ' |
| 741 ' ' |
| 742 '<span class="com"># look nice</span>' |
| 743 ) |
| 744 |
| 745 doesnt_contain("out/tabbed/tabbed_py.html", "\t") |
| 746 |
| 747 def test_unicode(self): |
| 748 self.output_dir("out/unicode") |
| 749 |
| 750 with change_dir("src"): |
| 751 # pylint: disable=import-error, redefined-builtin |
| 752 cov = coverage.Coverage() |
| 753 cov.start() |
| 754 import unicode # pragma: nested |
| 755 cov.stop() # pragma: nested |
| 756 cov.html_report(unicode, directory="../out/unicode") |
| 757 |
| 758 compare("gold_unicode", "out/unicode", size_within=10, file_pattern="*.h
tml") |
| 759 contains( |
| 760 "out/unicode/unicode_py.html", |
| 761 ('<span class="str">"ʎd˙ǝbɐɹǝA
2;oɔ"' |
| 762 '</span>'), |
| 763 ) |
| 764 |
| 765 contains_any( |
| 766 "out/unicode/unicode_py.html", |
| 767 '<span class="str">"db40,dd00: x��"</span>', |
| 768 '<span class="str">"db40,dd00: x󠄀"</span>', |
| 769 ) |
OLD | NEW |