summaryrefslogtreecommitdiff
path: root/coverage/html.py
diff options
context:
space:
mode:
authorNed Batchelder <ned@nedbatchelder.com>2019-06-17 14:19:47 -0400
committerNed Batchelder <ned@nedbatchelder.com>2019-06-17 18:33:57 -0400
commit1ae442ac70efa74d50a98568d7d7e01b3b7792b8 (patch)
tree2e03f6c08c7bfd76fde220016fac63260f7def89 /coverage/html.py
parent9b75b1ae769a1cf8255c4193f51af0c599c6bd4c (diff)
downloadpython-coveragepy-git-1ae442ac70efa74d50a98568d7d7e01b3b7792b8.tar.gz
Make the incremental checker a little smarter.
Diffstat (limited to 'coverage/html.py')
-rw-r--r--coverage/html.py145
1 files changed, 74 insertions, 71 deletions
diff --git a/coverage/html.py b/coverage/html.py
index b1ae29e6..1ae2c94b 100644
--- a/coverage/html.py
+++ b/coverage/html.py
@@ -99,8 +99,8 @@ class HtmlReporter(object):
def __init__(self, cov):
self.coverage = cov
- self.config =self.coverage.config
- self.directory = None
+ self.config = self.coverage.config
+ self.directory = self.config.html_dir
title = self.config.html_title
if env.PY2:
title = title.decode("utf8")
@@ -132,7 +132,7 @@ class HtmlReporter(object):
self.files = []
self.all_files_nums = []
self.has_arcs = self.data.has_arcs()
- self.status = HtmlStatus()
+ self.incr = IncrementalChecker(self.directory)
self.totals = Numbers()
def report(self, morfs):
@@ -141,19 +141,10 @@ class HtmlReporter(object):
`morfs` is a list of modules or file names.
"""
- # Read the status data.
- self.status.read(self.config.html_dir)
-
- # Check that this run used the same settings as the last run.
- m = Hasher()
- m.update(self.config)
- m.update(self.pyfile_html_source)
- these_settings = m.hexdigest()
- if self.status.settings_hash() != these_settings:
- self.status.reset()
- self.status.set_settings_hash(these_settings)
-
- self.directory = self.config.html_dir
+ # Read the status data and check that this run used the same
+ # global data as the last run.
+ self.incr.read()
+ self.incr.check_global_data(self.config, self.pyfile_html_source)
# Process all the files.
self.coverage.get_data().set_query_contexts(self.config.query_contexts)
@@ -185,13 +176,6 @@ class HtmlReporter(object):
os.path.join(self.directory, self.extra_css)
)
- def file_hash(self, fr):
- """Compute a hash that changes if the file needs to be re-reported."""
- m = Hasher()
- m.update(fr.source().encode('utf-8'))
- add_data_to_hash(self.data, fr.filename, m)
- return m.hexdigest()
-
def html_file(self, fr, analysis):
"""Generate an HTML file for one source file."""
rootname = flat_rootname(fr.relative_filename())
@@ -213,15 +197,10 @@ class HtmlReporter(object):
return
# Find out if the file on disk is already correct.
- this_hash = self.file_hash(fr)
- that_hash = self.status.file_hash(rootname)
- if this_hash == that_hash:
- # Nothing has changed to require the file to be reported again.
- self.files.append(self.status.index_info(rootname))
+ if self.incr.can_skip_file(self.data, fr, rootname):
+ self.files.append(self.incr.index_info(rootname))
return
- self.status.set_file_hash(rootname, this_hash)
-
# Write the HTML page for this file.
file_data = self.data_for_file(fr, analysis)
for ldata in file_data['lines']:
@@ -272,9 +251,10 @@ class HtmlReporter(object):
'relative_filename': fr.relative_filename(),
}
self.files.append(index_info)
- self.status.set_index_info(rootname, index_info)
+ self.incr.set_index_info(rootname, index_info)
def data_for_file(self, fr, analysis):
+ """Produce the data needed for one file's report."""
if self.has_arcs:
missing_branch_arcs = analysis.missing_branch_arcs()
arcs_executed = analysis.arcs_executed()
@@ -349,56 +329,57 @@ class HtmlReporter(object):
write_html(os.path.join(self.directory, "index.html"), html)
# Write the latest hashes for next time.
- self.status.write(self.directory)
+ self.incr.write()
-class HtmlStatus(object):
- """The status information we keep to support incremental reporting."""
+class IncrementalChecker(object):
+ """Logic and data to support incremental reporting."""
STATUS_FILE = "status.json"
- STATUS_FORMAT = 1
+ STATUS_FORMAT = 2
# pylint: disable=wrong-spelling-in-comment,useless-suppression
# The data looks like:
#
# {
- # 'format': 1,
- # 'settings': '540ee119c15d52a68a53fe6f0897346d',
- # 'version': '4.0a1',
- # 'files': {
- # 'cogapp___init__': {
- # 'hash': 'e45581a5b48f879f301c0f30bf77a50c',
- # 'index': {
- # 'html_filename': 'cogapp___init__.html',
- # 'name': 'cogapp/__init__',
- # 'nums': <coverage.results.Numbers object at 0x10ab7ed0>,
+ # "format": 2,
+ # "globals": "540ee119c15d52a68a53fe6f0897346d",
+ # "version": "4.0a1",
+ # "files": {
+ # "cogapp___init__": {
+ # "hash": "e45581a5b48f879f301c0f30bf77a50c",
+ # "index": {
+ # "html_filename": "cogapp___init__.html",
+ # "relative_filename": "cogapp/__init__",
+ # "nums": [ 1, 14, 0, 0, 0, 0, 0 ]
# }
# },
# ...
- # 'cogapp_whiteutils': {
- # 'hash': '8504bb427fc488c4176809ded0277d51',
- # 'index': {
- # 'html_filename': 'cogapp_whiteutils.html',
- # 'name': 'cogapp/whiteutils',
- # 'nums': <coverage.results.Numbers object at 0x10ab7d90>,
+ # "cogapp_whiteutils": {
+ # "hash": "8504bb427fc488c4176809ded0277d51",
+ # "index": {
+ # "html_filename": "cogapp_whiteutils.html",
+ # "relative_filename": "cogapp/whiteutils",
+ # "nums": [ 1, 59, 0, 1, 28, 2, 2 ]
# }
- # },
- # },
+ # }
+ # }
# }
- def __init__(self):
+ def __init__(self, directory):
+ self.directory = directory
self.reset()
def reset(self):
- """Initialize to empty."""
- self.settings = ''
+ """Initialize to empty. Causes all files to be reported."""
+ self.globals = ''
self.files = {}
- def read(self, directory):
- """Read the last status in `directory`."""
+ def read(self):
+ """Read the information we stored last time."""
usable = False
try:
- status_file = os.path.join(directory, self.STATUS_FILE)
+ status_file = os.path.join(self.directory, self.STATUS_FILE)
with open(status_file) as fstatus:
status = json.load(fstatus)
except (IOError, ValueError):
@@ -415,13 +396,13 @@ class HtmlStatus(object):
for filename, fileinfo in iitems(status['files']):
fileinfo['index']['nums'] = Numbers(*fileinfo['index']['nums'])
self.files[filename] = fileinfo
- self.settings = status['settings']
+ self.globals = status['globals']
else:
self.reset()
- def write(self, directory):
- """Write the current status to `directory`."""
- status_file = os.path.join(directory, self.STATUS_FILE)
+ def write(self):
+ """Write the current status."""
+ status_file = os.path.join(self.directory, self.STATUS_FILE)
files = {}
for filename, fileinfo in iitems(self.files):
fileinfo['index']['nums'] = fileinfo['index']['nums'].init_args()
@@ -430,7 +411,7 @@ class HtmlStatus(object):
status = {
'format': self.STATUS_FORMAT,
'version': coverage.__version__,
- 'settings': self.settings,
+ 'globals': self.globals,
'files': files,
}
with open(status_file, "w") as fout:
@@ -440,16 +421,38 @@ class HtmlStatus(object):
# Accommodate them if we are running under Jenkins.
# https://issues.jenkins-ci.org/browse/JENKINS-28428
if "JENKINS_URL" in os.environ:
- with open(os.path.join(directory, "status.dat"), "w") as dat:
+ with open(os.path.join(self.directory, "status.dat"), "w") as dat:
dat.write("https://issues.jenkins-ci.org/browse/JENKINS-28428\n")
- def settings_hash(self):
- """Get the hash of the coverage.py settings."""
- return self.settings
+ def check_global_data(self, *data):
+ """Check the global data that can affect incremental reporting."""
+ m = Hasher()
+ for d in data:
+ m.update(d)
+ these_globals = m.hexdigest()
+ if self.globals != these_globals:
+ self.reset()
+ self.globals = these_globals
+
+ def can_skip_file(self, data, fr, rootname):
+ """Can we skip reporting this file?
- def set_settings_hash(self, settings):
- """Set the hash of the coverage.py settings."""
- self.settings = settings
+ `data` is a CoverageData object, `fr` is a `FileReporter`, and
+ `rootname` is the name being used for the file.
+ """
+ m = Hasher()
+ m.update(fr.source().encode('utf-8'))
+ add_data_to_hash(data, fr.filename, m)
+ this_hash = m.hexdigest()
+
+ that_hash = self.file_hash(rootname)
+
+ if this_hash == that_hash:
+ # Nothing has changed to require the file to be reported again.
+ return True
+ else:
+ self.set_file_hash(rootname, this_hash)
+ return False
def file_hash(self, fname):
"""Get the hash of `fname`'s contents."""