Skip to content

Commit b7095b5

Browse files
committed
Added a managment command to show the difference in coverage data between the latest release and the latest test run, and a test for the same.
- Legacy-Id: 9162
1 parent 381d379 commit b7095b5

2 files changed

Lines changed: 186 additions & 0 deletions

File tree

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
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)
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
from django.core.management import call_command
2+
from django.test import TestCase
3+
from django.utils.six import StringIO
4+
5+
import debug # pyflakes:ignore
6+
7+
class CoverageChangeTestCase(TestCase):
8+
9+
def test_coverage_change(self):
10+
master = StringIO(
11+
"""{
12+
"5.12.0": {
13+
"code": {
14+
"coverage": 0.5921474057048117,
15+
"covered": {
16+
"ietf/api": 0.6493506493506493,
17+
"ietf/community/constants": 0.0,
18+
"ietf/dbtemplate/forms": 0.4782608695652174
19+
}
20+
},
21+
"template": {
22+
"coverage": 0.7604166666666666,
23+
"covered": {
24+
"admin/group/group/change_form.html": false,
25+
"base.html": true,
26+
"community/customize_display.html": false,
27+
"doc/add_comment.html": true
28+
}
29+
},
30+
"time": "2015-02-25T22:01:02Z",
31+
"url": {
32+
"coverage": 0.5374732334047109,
33+
"covered": {
34+
"^$": true,
35+
"^accounts/$": true,
36+
"^api/v1/?$": true,
37+
"^community/personal/$": false
38+
}
39+
}
40+
},
41+
"version": "5.12.0"
42+
}
43+
""")
44+
latest = StringIO(
45+
"""{
46+
"latest": {
47+
"code": {
48+
"coverage": 0.5921474057048117,
49+
"covered": {
50+
"ietf/api": 0.65,
51+
"ietf/community/constants": 50,
52+
"ietf/dbtemplate/forms": 0.4782608695652174
53+
}
54+
},
55+
"template": {
56+
"coverage": 0.7604166666666666,
57+
"covered": {
58+
"admin/group/group/change_form.html": true,
59+
"base.html": true,
60+
"community/customize_display.html": false,
61+
"doc/add_comment.html": true
62+
}
63+
},
64+
"time": "2015-02-25T22:01:02Z",
65+
"url": {
66+
"coverage": 0.5374732334047109,
67+
"covered": {
68+
"^$": true,
69+
"^accounts/$": true,
70+
"^api/v1/?$": false,
71+
"^community/personal/$": true
72+
}
73+
}
74+
},
75+
"version":"latest"
76+
}
77+
""")
78+
output = StringIO()
79+
call_command('coverage_changes', master, latest, stdout=output)
80+
text = output.getvalue()
81+
for l in [
82+
r"admin/group/group/change_form.html False True",
83+
r"^api/v1/?$ True False",
84+
r"^community/personal/$ False True",
85+
r"ietf/community/constants nan 50.00",
86+
]:
87+
self.assertTrue(l in text, msg="Missing line in coverage_change output:\n'%s'\n"%l)

0 commit comments

Comments
 (0)