# -*- coding: utf-8 -*-
"""Tests that HTML generation is awesome."""
import os.path
import re
import coverage
from coverage import env
import coverage.html
from coverage.misc import CoverageException, NotPython, NoSource
from tests.coveragetest import CoverageTest
class HtmlTestHelpers(CoverageTest):
"""Methods that help with HTML tests."""
def create_initial_files(self):
"""Create the source files we need to run these tests."""
self.make_file("main_file.py", """\
import helper1, helper2
helper1.func1(12)
helper2.func2(12)
""")
self.make_file("helper1.py", """\
def func1(x):
if x % 2:
print("odd")
""")
self.make_file("helper2.py", """\
def func2(x):
print("x is %d" % x)
""")
def run_coverage(self, covargs=None, htmlargs=None):
"""Run coverage on main_file.py, and create an HTML report."""
self.clean_local_file_imports()
cov = coverage.coverage(**(covargs or {}))
self.start_import_stop(cov, "main_file")
cov.html_report(**(htmlargs or {}))
def remove_html_files(self):
"""Remove the HTML files created as part of the HTML report."""
os.remove("htmlcov/index.html")
os.remove("htmlcov/main_file_py.html")
os.remove("htmlcov/helper1_py.html")
os.remove("htmlcov/helper2_py.html")
def get_html_report_content(self, module):
"""Return the content of the HTML report for `module`."""
filename = module.replace(".", "_").replace("/", "_") + ".html"
filename = os.path.join("htmlcov", filename)
with open(filename) as f:
return f.read()
class HtmlDeltaTest(HtmlTestHelpers, CoverageTest):
"""Tests of the HTML delta speed-ups."""
def setUp(self):
super(HtmlDeltaTest, self).setUp()
# At least one of our tests monkey-patches the version of coverage,
# so grab it here to restore it later.
self.real_coverage_version = coverage.__version__
self.addCleanup(self.restore_coverage_version)
def restore_coverage_version(self):
"""A cleanup."""
coverage.__version__ = self.real_coverage_version
def test_html_created(self):
# Test basic HTML generation: files should be created.
self.create_initial_files()
self.run_coverage()
self.assert_exists("htmlcov/index.html")
self.assert_exists("htmlcov/main_file_py.html")
self.assert_exists("htmlcov/helper1_py.html")
self.assert_exists("htmlcov/helper2_py.html")
self.assert_exists("htmlcov/style.css")
self.assert_exists("htmlcov/coverage_html.js")
def test_html_delta_from_source_change(self):
# HTML generation can create only the files that have changed.
# In this case, helper1 changes because its source is different.
self.create_initial_files()
self.run_coverage()
with open("htmlcov/index.html") as f:
index1 = f.read()
self.remove_html_files()
# Now change a file and do it again
self.make_file("helper1.py", """\
def func1(x): # A nice function
if x % 2:
print("odd")
""")
self.run_coverage()
# Only the changed files should have been created.
self.assert_exists("htmlcov/index.html")
self.assert_exists("htmlcov/helper1_py.html")
self.assert_doesnt_exist("htmlcov/main_file_py.html")
self.assert_doesnt_exist("htmlcov/helper2_py.html")
with open("htmlcov/index.html") as f:
index2 = f.read()
self.assertMultiLineEqual(index1, index2)
def test_html_delta_from_coverage_change(self):
# HTML generation can create only the files that have changed.
# In this case, helper1 changes because its coverage is different.
self.create_initial_files()
self.run_coverage()
self.remove_html_files()
# Now change a file and do it again
self.make_file("main_file.py", """\
import helper1, helper2
helper1.func1(23)
helper2.func2(23)
""")
self.run_coverage()
# Only the changed files should have been created.
self.assert_exists("htmlcov/index.html")
self.assert_exists("htmlcov/helper1_py.html")
self.assert_exists("htmlcov/main_file_py.html")
self.assert_doesnt_exist("htmlcov/helper2_py.html")
def test_html_delta_from_settings_change(self):
# HTML generation can create only the files that have changed.
# In this case, everything changes because the coverage settings have
# changed.
self.create_initial_files()
self.run_coverage(covargs=dict(omit=[]))
with open("htmlcov/index.html") as f:
index1 = f.read()
self.remove_html_files()
self.run_coverage(covargs=dict(omit=['xyzzy*']))
# All the files have been reported again.
self.assert_exists("htmlcov/index.html")
self.assert_exists("htmlcov/helper1_py.html")
self.assert_exists("htmlcov/main_file_py.html")
self.assert_exists("htmlcov/helper2_py.html")
with open("htmlcov/index.html") as f:
index2 = f.read()
self.assertMultiLineEqual(index1, index2)
def test_html_delta_from_coverage_version_change(self):
# HTML generation can create only the files that have changed.
# In this case, everything changes because the coverage version has
# changed.
self.create_initial_files()
self.run_coverage()
with open("htmlcov/index.html") as f:
index1 = f.read()
self.remove_html_files()
# "Upgrade" coverage.py!
coverage.__version__ = "XYZZY"
self.run_coverage()
# All the files have been reported again.
self.assert_exists("htmlcov/index.html")
self.assert_exists("htmlcov/helper1_py.html")
self.assert_exists("htmlcov/main_file_py.html")
self.assert_exists("htmlcov/helper2_py.html")
with open("htmlcov/index.html") as f:
index2 = f.read()
fixed_index2 = index2.replace("XYZZY", self.real_coverage_version)
self.assertMultiLineEqual(index1, fixed_index2)
class HtmlTitleTest(HtmlTestHelpers, CoverageTest):
"""Tests of the HTML title support."""
def test_default_title(self):
self.create_initial_files()
self.run_coverage()
with open("htmlcov/index.html") as f:
index = f.read()
self.assertIn("
Coverage report", index)
self.assertIn("Coverage report:", index)
def test_title_set_in_config_file(self):
self.create_initial_files()
self.make_file(".coveragerc", "[html]\ntitle = Metrics & stuff!\n")
self.run_coverage()
with open("htmlcov/index.html") as f:
index = f.read()
self.assertIn("Metrics & stuff!", index)
self.assertIn("Metrics & stuff!:", index)
def test_non_ascii_title_set_in_config_file(self):
self.create_initial_files()
self.make_file(".coveragerc",
"[html]\ntitle = «ταБЬℓσ» numbers"
)
self.run_coverage()
with open("htmlcov/index.html") as f:
index = f.read()
self.assertIn(
"«ταБЬℓσ»"
" numbers", index
)
self.assertIn(
"«ταБЬℓσ»"
" numbers", index
)
def test_title_set_in_args(self):
self.create_initial_files()
self.make_file(".coveragerc", "[html]\ntitle = Good title\n")
self.run_coverage(htmlargs=dict(title="«ταБЬℓσ» & stüff!"))
with open("htmlcov/index.html") as f:
index = f.read()
self.assertIn(
"«ταБЬℓσ»"
" & stüff!", index
)
self.assertIn(
"«ταБЬℓσ»"
" & stüff!:", index
)
class HtmlWithUnparsableFilesTest(HtmlTestHelpers, CoverageTest):
"""Test the behavior when measuring unparsable files."""
def test_dotpy_not_python(self):
self.make_file("innocuous.py", "a = 1")
cov = coverage.coverage()
self.start_import_stop(cov, "innocuous")
self.make_file("innocuous.py", "This isn't python!
")
msg = "Couldn't parse '.*innocuous.py' as Python source: .* at line 1"
with self.assertRaisesRegex(NotPython, msg):
cov.html_report()
def test_dotpy_not_python_ignored(self):
self.make_file("innocuous.py", "a = 2")
cov = coverage.coverage()
self.start_import_stop(cov, "innocuous")
self.make_file("innocuous.py", "This isn't python!
")
cov.html_report(ignore_errors=True)
self.assert_exists("htmlcov/index.html")
# This would be better as a glob, if the HTML layout changes:
self.assert_doesnt_exist("htmlcov/innocuous.html")
def test_dothtml_not_python(self):
# We run a .html file, and when reporting, we can't parse it as
# Python. Since it wasn't .py, no error is reported.
# Run an "HTML" file
self.make_file("innocuous.html", "a = 3")
self.run_command("coverage run innocuous.html")
# Before reporting, change it to be an HTML file.
self.make_file("innocuous.html", "This isn't python at all!
")
output = self.run_command("coverage html")
self.assertEqual(output.strip(), "No data to report.")
def test_execed_liar_ignored(self):
# Jinja2 sets __file__ to be a non-Python file, and then execs code.
# If that file contains non-Python code, a TokenError shouldn't
# have been raised when writing the HTML report.
source = "exec(compile('','','exec'), {'__file__': 'liar.html'})"
self.make_file("liar.py", source)
self.make_file("liar.html", "{# Whoops, not python code #}")
cov = coverage.coverage()
self.start_import_stop(cov, "liar")
cov.html_report()
self.assert_exists("htmlcov/index.html")
def test_execed_liar_ignored_indentation_error(self):
# Jinja2 sets __file__ to be a non-Python file, and then execs code.
# If that file contains untokenizable code, we shouldn't get an
# exception.
source = "exec(compile('','','exec'), {'__file__': 'liar.html'})"
self.make_file("liar.py", source)
# Tokenize will raise an IndentationError if it can't dedent.
self.make_file("liar.html", "0\n 2\n 1\n")
cov = coverage.coverage()
self.start_import_stop(cov, "liar")
cov.html_report()
self.assert_exists("htmlcov/index.html")
# TODO: enable this test, and then fix this:
# https://bitbucket.org/ned/coveragepy/issue/351/files-with-incorrect-encoding-are-ignored
def test_decode_error(self):
# imp.load_module won't load a file with an undecodable character
# in a comment, though Python will run them. So we'll change the
# file after running.
self.make_file("main.py", "import sub.not_ascii")
self.make_file("sub/__init__.py")
self.make_file("sub/not_ascii.py", """\
# coding: utf8
a = 1 # Isn't this great?!
""")
cov = coverage.coverage()
self.start_import_stop(cov, "main")
# Create the undecodable version of the file. make_file is too helpful,
# so get down and dirty with bytes.
with open("sub/not_ascii.py", "wb") as f:
f.write(b"# coding: utf8\na = 1 # Isn't this great?\xcb!\n")
with open("sub/not_ascii.py", "rb") as f:
undecodable = f.read()
self.assertIn(b"?\xcb!", undecodable)
cov.html_report()
html_report = self.get_html_report_content("sub/not_ascii.py")
if env.PY2:
expected = "# Isn't this great?�!"
else:
expected = "# Isn't this great?Ë!"
self.assertIn(expected, html_report)
def test_formfeeds(self):
# https://bitbucket.org/ned/coveragepy/issue/360/html-reports-get-confused-by-l-in-the-code
self.make_file("formfeed.py", "line_one = 1\n\f\nline_two = 2\n")
cov = coverage.coverage()
self.start_import_stop(cov, "formfeed")
cov.html_report()
formfeed_html = self.get_html_report_content("formfeed.py")
self.assertIn("line_two", formfeed_html)
class HtmlTest(CoverageTest):
"""Moar HTML tests."""
def test_missing_source_file_incorrect_message(self):
# https://bitbucket.org/ned/coveragepy/issue/60
self.make_file("thefile.py", "import sub.another\n")
self.make_file("sub/__init__.py", "")
self.make_file("sub/another.py", "print('another')\n")
cov = coverage.coverage()
self.start_import_stop(cov, 'thefile')
os.remove("sub/another.py")
missing_file = os.path.join(self.temp_dir, "sub", "another.py")
missing_file = os.path.realpath(missing_file)
msg = "(?i)No source for code: '%s'" % re.escape(missing_file)
with self.assertRaisesRegex(NoSource, msg):
cov.html_report()
def test_extensionless_file_collides_with_extension(self):
# It used to be that "afile" and "afile.py" would both be reported to
# "afile.html". Now they are not.
# https://bitbucket.org/ned/coveragepy/issue/69
self.make_file("afile", "import afile\n")
self.make_file("afile.py", "a = 1\n")
self.run_command("coverage run afile")
self.run_command("coverage html")
self.assert_exists("htmlcov/index.html")
self.assert_exists("htmlcov/afile.html")
self.assert_exists("htmlcov/afile_py.html")
class HtmlStaticFileTest(CoverageTest):
"""Tests of the static file copying for the HTML report."""
def setUp(self):
super(HtmlStaticFileTest, self).setUp()
self.original_path = list(coverage.html.STATIC_PATH)
self.addCleanup(self.restore_static_path)
def restore_static_path(self):
"""A cleanup."""
coverage.html.STATIC_PATH = self.original_path
def test_copying_static_files_from_system(self):
# Make a new place for static files.
self.make_file("static_here/jquery.min.js", "Not Really JQuery!")
coverage.html.STATIC_PATH.insert(0, "static_here")
self.make_file("main.py", "print(17)")
cov = coverage.coverage()
self.start_import_stop(cov, "main")
cov.html_report()
with open("htmlcov/jquery.min.js") as f:
jquery = f.read()
self.assertEqual(jquery, "Not Really JQuery!")
def test_copying_static_files_from_system_in_dir(self):
# Make a new place for static files.
INSTALLED = [
"jquery/jquery.min.js",
"jquery-hotkeys/jquery.hotkeys.js",
"jquery-isonscreen/jquery.isonscreen.js",
"jquery-tablesorter/jquery.tablesorter.min.js",
]
for fpath in INSTALLED:
self.make_file(os.path.join("static_here", fpath), "Not real.")
coverage.html.STATIC_PATH.insert(0, "static_here")
self.make_file("main.py", "print(17)")
cov = coverage.coverage()
self.start_import_stop(cov, "main")
cov.html_report()
for fpath in INSTALLED:
the_file = os.path.basename(fpath)
with open(os.path.join("htmlcov", the_file)) as f:
contents = f.read()
self.assertEqual(contents, "Not real.")
def test_cant_find_static_files(self):
# Make the path point to useless places.
coverage.html.STATIC_PATH = ["/xyzzy"]
self.make_file("main.py", "print(17)")
cov = coverage.coverage()
self.start_import_stop(cov, "main")
msg = "Couldn't find static file u?'.*'"
with self.assertRaisesRegex(CoverageException, msg):
cov.html_report()