datatracker/ietf/utils/management/commands/coverage_changes.py
Henrik Levkowetz aa5e61d958 Updated all urlpatterns to use ietf.utils.urls.url() instead of django's,
in order to autogenerate dotted path url pattern names.  Updated a number
of url reverses to use dotted path, and removed explicit url pattern names
as needed.

Changed some imports to prevent import of ietf.urls before django
initialization was complete.


Changed 3 cases of form classes being curried to functions; django 1.10
didn't accept that.

Started converting old-style middleware classes to new-style middleware
functions (incomplete).

Tweaked a nomcom decorator to preserve function names and attributes, like
a good decorator should.

Replaced the removed django templatetag 'removetags' with our own version
which uses bleach, and does sanitizing in addition to removing explicitly
mentionied html tags.

Rewrote the filename argument handling in a management command which had
broken with the upgrade.
 - Legacy-Id: 12818
2017-02-11 14:43:01 +00:00

212 lines
9.2 KiB
Python

# Copyright The IETF Trust 2016, All Rights Reserved
import os
import json
import codecs
import gzip
from django.conf import settings
from django.core.management.base import BaseCommand, CommandError
from django.utils.six import string_types
import debug # pyflakes:ignore
class Command(BaseCommand):
name, _ = os.path.splitext(os.path.basename(__file__))
help = ("Show coverage info for the latest test run. By default, the difference in \n"
"coverage compared with the latest available release data is shown.\n"
"\n"
"Examples:\n"
"\n"
" Show the coverage difference from the previous release:\n"
" $ manage.py {name}\n"
"\n"
" Show the coverage difference with another release:\n"
" $ manage.py {name} --release=6.0.0\n"
"\n"
" Show the coverage difference for a provided data-file:\n"
" $ manage.py {name} release-coverage.json.gz beeblebrox.json\n"
"\n"
" List URLs which are not covered:\n"
" $ manage.py {name} --absolute --sections=url | grep False\n"
"\n".format(**locals())
)
args = "[[master_json] latest_json]"
def create_parser(self, prog_name, subcommand):
import argparse
parser = super(Command, self).create_parser(prog_name, subcommand)
parser.formatter_class = argparse.RawDescriptionHelpFormatter
return parser
def add_arguments(self, parser):
parser.add_argument('filenames', nargs="*")
parser.add_argument('--sections', default='template,url,code', dest='sections',
help='Specify which kinds of coverage changes to show. Default: %(default)s\n')
parser.add_argument('--release', dest='release',
help='Which release to use as baseline. Default is the latest release in '
'the release coverage file.')
parser.add_argument('--absolute', dest='absolute', action='store_true', default=False,
help='Show absolute figures instead of changes from last release.')
diff_line_format = "%-58s %8s %8s\n"
list_line_format = "%-68s %8s\n"
valid_sections = ['template', 'url', 'code']
def read_coverage(self, filename, version=None):
if isinstance(filename, string_types):
try:
if filename.endswith(".gz"):
file = gzip.open(filename, "rb")
else:
file = codecs.open(filename, "r", encoding="utf-8")
except IOError as e:
self.stderr.write(u"%s" % e)
exit(1)
else:
file = filename
try:
data = json.load(file)
except ValueError as e:
raise CommandError("Failure to read json data from %s: %s" % (filename, e))
version = version or data["version"]
if not version in data:
raise CommandError("There is no data for version %s available in %s" % (version, filename))
return data[version], version
def coverage_diff(self, master, latest, sections, release=None, **options):
master_coverage, mversion = self.read_coverage(master, release)
latest_coverage, lversion = self.read_coverage(latest)
self.stdout.write("\nShowing coverage differeces between %s and %s:\n" % (mversion, lversion))
for section in sections:
mcoverage = master_coverage[section]["covered"]
mformat = master_coverage[section].get("format", 1)
lcoverage = latest_coverage[section]["covered"]
lformat = latest_coverage[section].get("format", 1)
#
mkeys = mcoverage.keys()
lkeys = lcoverage.keys()
#
keys = list(lkeys)
keys.sort()
header_written = False
for key in keys:
mkey = key
if not mkey in mcoverage:
if mkey.endswith(".py"):
mkey = mkey[:-3]
else:
mkey = mkey + ".py"
if not mkey in mcoverage:
mlines, mcov = None, None
else:
if mformat == 1:
mlines, mcov = None, mcoverage[mkey]
elif mformat == 2:
mlines, mcov = mcoverage[mkey]
else:
raise CommandError("The release coverage data has an unknown format ('%s'), quitting." % mformat)
if lformat == 1:
llines, lcov = None, lcoverage[key]
elif lformat == 2:
llines, lcov = lcoverage[key]
else:
raise CommandError("The latest coverage data has an unknown format ('%s'), quitting." % lformat)
if type(mcov) is float or type(lcov) is float:
mval = ("%5.1f" % (100*mcov)) if mcov else "-"
lval = ("%5.1f %%" % (100*lcov)) if lcov else "- "
else:
mval = mcov
lval = lcov
if mcov != lcov:
if not header_written:
self.stdout.write(self.diff_line_format %
("\n%s"%section.capitalize(), mversion[:7], lversion[:7]))
self.stdout.write(self.diff_line_format % ("-"*58, "-"*8, "-"*8))
header_written = True
self.stdout.write(self.diff_line_format % (key, mval, lval))
lkey_set = set(lkeys)
rkey_set = set(mkeys)
missing_key_set = rkey_set - lkey_set
missing_key_count = len(missing_key_set)
if missing_key_count > 0:
self.stdout.write("\nThere were %s items in the %s %s coverage data which\n"
"were absent from the %s %s coverage data.\n" % (missing_key_count, mversion, section, lversion, section))
if missing_key_count <= 10:
self.stdout.write("\nMissing items:\n")
for key in missing_key_set:
self.stdout.write(" %s\n" % key)
def coverage_list(self, latest, sections, **options):
latest_coverage, lversion = self.read_coverage(latest)
self.stdout.write("\nShowing coverage for %s:\n" % (lversion, ))
for section in sections:
lcoverage = latest_coverage[section]["covered"]
lformat = latest_coverage[section].get("format", 1)
#
lkeys = lcoverage.keys()
#
keys = list(lkeys)
keys.sort()
header_written = False
for key in keys:
if lformat == 1:
llines, lcov = None, lcoverage[key]
elif lformat == 2:
llines, lcov = lcoverage[key]
else:
raise CommandError("The latest coverage data has an unknown format ('%s'), quitting." % lformat)
if type(lcov) is float:
lval = ("%5.1f %%" % (100*lcov)) if lcov else "- "
else:
lval = lcov
if not header_written:
self.stdout.write(self.list_line_format %
("\n%s"%section.capitalize(), lversion[:7]))
self.stdout.write(self.list_line_format % ("-"*58, "-"*8, ))
header_written = True
self.stdout.write(self.list_line_format % (key, lval))
def handle(self, *args, **options):
sections = options.get('sections', ','.join(self.valid_sections))
options.pop('sections')
sections = sections.split(',')
for section in sections:
if not section in self.valid_sections:
raise CommandError("Found an unexpected section name, '%s' in the section list. "
"Valid names are %s or any combination of them."%(section, ','.join(self.valid_sections)))
absolute = options.get('absolute', False)
filenames = options.get('filenames')
if absolute:
if not filenames:
filenames = [
getattr(settings, 'TEST_COVERAGE_LATEST_FILE'),
]
if len(filenames) != 1:
raise CommandError(
"Coverage can be listed only for one json coverage-data file, "
"got: %s" % " ".join(filenames))
self.coverage_list(filenames[0], sections=sections, **options)
else:
# verbosity = int(options.get('verbosity'))
if not filenames:
filenames = [
getattr(settings, 'TEST_COVERAGE_MASTER_FILE'),
getattr(settings, 'TEST_COVERAGE_LATEST_FILE'),
]
if len(filenames) != 2:
raise CommandError(
"Need two and only two files in order to show coverage difference, "
"got: %s" % " ".join(filenames))
self.coverage_diff(*filenames, sections=sections, **options)