test-backend: Add 100% test coverage assertions.

This adds an assertion, when `test-backend` is run with `--coverage`,
that we have 100% test coverage on a list of files that we expect to.
There's a whitelist/blacklist, managed in tools/test-backend.

Fixes #3363.
This commit is contained in:
Tim Abbott 2017-02-18 16:26:52 -08:00
parent 1abfdc340a
commit ecadb33fbc
2 changed files with 113 additions and 1 deletions

View File

@ -242,4 +242,7 @@ typically simulate their behavior using mocks.
modules. You can use the `--coverage` option to generate coverage modules. You can use the `--coverage` option to generate coverage
reports, and new code should have 100% coverage, which generally requires reports, and new code should have 100% coverage, which generally requires
testing not only the "happy path" but also error handling code and testing not only the "happy path" but also error handling code and
edge cases. edge cases. Note that `test-backend --coverage` will assert that
various specific files in the project have 100% test coverage and
throw an error if their coverage has fallen. One of our project goals
is to expand that checking to ever-larger parts of the codebase.

View File

@ -2,6 +2,7 @@
from __future__ import print_function from __future__ import print_function
from __future__ import absolute_import from __future__ import absolute_import
import glob
import optparse import optparse
import os import os
import sys import sys
@ -15,6 +16,100 @@ import django
from django.conf import settings from django.conf import settings
from django.test.utils import get_runner from django.test.utils import get_runner
target_fully_covered = {path for target in [
'analytics/tests/*.py',
'analytics/lib/*.py',
'zerver/context_processors.py',
'zerver/lib/alert_words.py',
'zerver/lib/attachments.py',
'zerver/lib/avatar_hash.py',
'zerver/lib/context_managers.py',
'zerver/lib/domains.py',
'zerver/lib/emoji.py',
'zerver/lib/i18n.py',
'zerver/lib/mention.py',
'zerver/lib/message.py',
'zerver/lib/name_restrictions.py',
'zerver/lib/retention.py',
'zerver/lib/streams.py',
'zerver/lib/users.py',
'zerver/lib/webhooks/*.py',
'zerver/views/*.py',
# Once we have a nice negative tests system, we can add these:
# 'zerver/webhooks/*/*.py',
# 'zerver/webhooks/*/*/*.py',
'zproject/backends.py',
# Uncovered but in exclude list and we'd like to have included soon
'confirmation/models.py',
'zerver/decorator.py',
'zerver/lib/actions.py',
'zerver/lib/events.py',
'zerver/lib/bugdown/__init__.py',
'zerver/lib/events.py',
'zerver/lib/integrations.py',
'zerver/lib/message.py',
'zerver/lib/narrow.py',
'zerver/lib/notifications.py',
'zerver/lib/push_notifications.py',
'zerver/lib/request.py',
'zerver/lib/upload.py',
'zerver/lib/validator.py',
'zerver/models.py',
] for path in glob.glob(target)}
not_yet_fully_covered = {
# Goal is for analytics to have 100% coverage
'analytics/lib/counts.py',
'analytics/lib/fixtures.py',
'analytics/lib/time_utils.py',
# Major lib files should have 100% coverage
'confirmation/models.py',
'zerver/decorator.py',
'zerver/lib/actions.py',
'zerver/lib/events.py',
'zerver/lib/bugdown/__init__.py',
'zerver/lib/events.py',
'zerver/lib/integrations.py',
'zerver/lib/message.py',
'zerver/lib/narrow.py',
'zerver/lib/notifications.py',
'zerver/lib/push_notifications.py',
'zerver/lib/request.py',
'zerver/lib/upload.py',
'zerver/lib/validator.py',
'zerver/models.py',
# Test files should have full coverage; it's a bug in the test if
# they don't!
'zerver/tests/test_auth_backends.py',
'zerver/tests/test_bugdown.py',
'zerver/tests/test_decorators.py',
'zerver/tests/test_email_mirror.py',
'zerver/tests/test_events.py',
'zerver/tests/test_narrow.py',
'zerver/tests/test_signup.py',
'zerver/tests/test_subs.py',
'zerver/tests/test_templates.py',
'zerver/tests/test_tornado.py',
'zerver/tests/test_urls.py',
'zerver/tests/tests.py',
# Getting views file coverage to 100% is a major project goal
'zerver/views/auth.py',
'zerver/views/integrations.py',
'zerver/views/messages.py',
'zerver/views/report.py',
'zerver/views/unsubscribe.py',
'zerver/views/zephyr.py',
'zerver/views/realm.py',
'zerver/views/invite.py',
'zerver/views/home.py',
'zerver/views/registration.py',
'zerver/views/events_register.py',
# Getting this to 100% is a major project goal.
'zproject/backends.py',
}
enforce_fully_covered = sorted(target_fully_covered - not_yet_fully_covered)
if __name__ == "__main__": if __name__ == "__main__":
TOOLS_DIR = os.path.dirname(os.path.abspath(__file__)) TOOLS_DIR = os.path.dirname(os.path.abspath(__file__))
os.chdir(os.path.dirname(TOOLS_DIR)) os.chdir(os.path.dirname(TOOLS_DIR))
@ -185,6 +280,20 @@ if __name__ == "__main__":
cov.report(show_missing=False) cov.report(show_missing=False)
cov.html_report(directory='var/coverage') cov.html_report(directory='var/coverage')
print("HTML report saved to var/coverage") print("HTML report saved to var/coverage")
if full_suite and not failures and options.coverage:
# Assert that various files have full coverage
for path in enforce_fully_covered:
missing_lines = cov.analysis2(path)[3]
if len(missing_lines) > 0:
print("ERROR: %s no longer has complete backend test coverage" % (path,))
print(" Lines missing coverage: %s" % (missing_lines,))
print()
failures = True
if failures:
print("It looks like your changes lost 100% test coverage in one or more files")
print("Usually, the right fix for this is to add some tests.")
print("But also check out the include/exclude lists in tools/test-backend.")
print("To run this check locally, use `test-backend --coverage`.")
if options.profile: if options.profile:
prof.disable() prof.disable()
prof.dump_stats("/tmp/profile.data") prof.dump_stats("/tmp/profile.data")