|
| 1 | + |
| 2 | +import json |
| 3 | +import codecs |
| 4 | +from optparse import make_option |
| 5 | + |
| 6 | +from django.conf import settings |
| 7 | +from django.core.management.base import BaseCommand, CommandError |
| 8 | +from django.utils.six import string_types |
| 9 | + |
| 10 | +class Command(BaseCommand): |
| 11 | + help = "Compare coverage between the latest release and the latest test run." |
| 12 | + option_list = BaseCommand.option_list + ( |
| 13 | + make_option('--sections', default='template,url,code', dest='sections', |
| 14 | + help='Specify which kinds of coverage changes to show. Default: %default'), |
| 15 | + make_option('--release', dest='release', |
| 16 | + help='Which release to use as baseline. Default is the latest release in ' |
| 17 | + 'the release coverage file.'), |
| 18 | + ) |
| 19 | + |
| 20 | + diff_line_format = "%-58s %8s %8s\n" |
| 21 | + valid_sections = ['template', 'url', 'code'] |
| 22 | + |
| 23 | + def read_coverage(self, filename, version=None): |
| 24 | + if isinstance(filename, string_types): |
| 25 | + try: |
| 26 | + file = codecs.open(filename, "r", encoding="utf-8") |
| 27 | + except IOError as e: |
| 28 | + self.stderr.write(u"%s" % e) |
| 29 | + exit(1) |
| 30 | + else: |
| 31 | + file = filename |
| 32 | + try: |
| 33 | + data = json.load(file) |
| 34 | + except ValueError as e: |
| 35 | + self.stderr.write("Failure to read json data from %s: %s" % (filename, e)) |
| 36 | + exit(1) |
| 37 | + version = version or data["version"] |
| 38 | + return data[version], version |
| 39 | + |
| 40 | + def coverage_diff(self, master, latest, sections=','.join(valid_sections), release=None, **options): |
| 41 | + sections = sections.split(',') |
| 42 | + for section in sections: |
| 43 | + if not section in self.valid_sections: |
| 44 | + raise CommandError("Found an unexpected section name, '%s' in the section list. " |
| 45 | + "Valid names are %s or any combination of them."%(section, ','.join(self.valid_sections))) |
| 46 | + master_coverage, mversion = self.read_coverage(master, release) |
| 47 | + latest_coverage, lversion = self.read_coverage(latest) |
| 48 | + self.stdout.write("\nShowing coverage differeces between %s and %s:\n" % (mversion, lversion)) |
| 49 | + for section in sections: |
| 50 | + mcoverage = master_coverage[section]["covered"] |
| 51 | + lcoverage = latest_coverage[section]["covered"] |
| 52 | + # |
| 53 | + mkeys = mcoverage.keys() |
| 54 | + lkeys = lcoverage.keys() |
| 55 | + # |
| 56 | + keys = list(lkeys) |
| 57 | + keys.sort() |
| 58 | + header_written = False |
| 59 | + for key in keys: |
| 60 | + if not key in mcoverage: |
| 61 | + mcoverage[key] = None |
| 62 | + if type(mcoverage[key]) is float or type(lcoverage[key]) is float: |
| 63 | + mval = "%8.2f" % (mcoverage[key] or float('nan')) |
| 64 | + lval = "%8.2f" % (lcoverage[key] or float('nan')) |
| 65 | + else: |
| 66 | + mval = mcoverage[key] |
| 67 | + lval = lcoverage[key] |
| 68 | + if mval != lval: |
| 69 | + if not header_written: |
| 70 | + self.stdout.write(self.diff_line_format % |
| 71 | + ("\n%s"%section.capitalize(), mversion[:7], lversion[:7])) |
| 72 | + self.stdout.write(self.diff_line_format % ("-"*58, "-"*8, "-"*8)) |
| 73 | + header_written = True |
| 74 | + self.stdout.write(self.diff_line_format % (key, mval, lval)) |
| 75 | + lkey_set = set(lkeys) |
| 76 | + rkey_set = set(mkeys) |
| 77 | + missing_key_set = rkey_set - lkey_set |
| 78 | + missing_key_count = len(missing_key_set) |
| 79 | + if missing_key_count > 0: |
| 80 | + self.stdout.write("\nThere were %s items in the %s %s coverage data which\n" |
| 81 | + "were absent from the %s %s coverage data.\n" % (missing_key_count, mversion, section, lversion, section)) |
| 82 | + if missing_key_count <= 10: |
| 83 | + self.stdout.write("\nMissing items:\n") |
| 84 | + for key in missing_key_set: |
| 85 | + self.stdout.write(" %s\n" % key) |
| 86 | + |
| 87 | + |
| 88 | + def handle(self, *filenames, **options): |
| 89 | + if not filenames: |
| 90 | + filenames = [ |
| 91 | + getattr(settings, 'TEST_COVERAGE_MASTER_FILE'), |
| 92 | + getattr(settings, 'TEST_COVERAGE_LATEST_FILE'), |
| 93 | + ] |
| 94 | + # verbosity = int(options.get('verbosity')) |
| 95 | + if len(filenames) != 2: |
| 96 | + raise CommandError( |
| 97 | + "Need two and only two files in order to show coverage difference, " |
| 98 | + "got: %s" % " ".join(filenames)) |
| 99 | + self.coverage_diff(*filenames, **options) |
0 commit comments