mirror of https://github.com/zulip/zulip.git
tests: Enforce 100% URL coverage.
We now instrument URL coverage whenever you run the back end tests, and if you run the full suite and fail to test all endpoints, we exit with a non-zero exit code and report failures to you. If you are running just a subset of the test suite, you'll still be able to see var/url_coverage.txt, which has some useful info. With some tweaks to the output from tabbott. Fixes #1441.
This commit is contained in:
parent
d8dee522b6
commit
5f5e6b6d83
|
@ -50,9 +50,6 @@ if __name__ == "__main__":
|
||||||
parser.add_option('--coverage', dest='coverage',
|
parser.add_option('--coverage', dest='coverage',
|
||||||
action="store_true",
|
action="store_true",
|
||||||
default=False, help='Compute test coverage.')
|
default=False, help='Compute test coverage.')
|
||||||
parser.add_option('--url-coverage', dest='url_coverage',
|
|
||||||
action="store_true",
|
|
||||||
default=False, help='Write url coverage data.')
|
|
||||||
parser.add_option('--no-verbose-coverage', dest='verbose_coverage',
|
parser.add_option('--no-verbose-coverage', dest='verbose_coverage',
|
||||||
action="store_false",
|
action="store_false",
|
||||||
default=True, help='Disable verbose print of coverage report.')
|
default=True, help='Disable verbose print of coverage report.')
|
||||||
|
@ -127,6 +124,8 @@ if __name__ == "__main__":
|
||||||
for suite in args:
|
for suite in args:
|
||||||
args[args.index(suite)] = suite.replace("/", ".")
|
args[args.index(suite)] = suite.replace("/", ".")
|
||||||
|
|
||||||
|
full_suite = len(args) == 0
|
||||||
|
|
||||||
if len(args) == 0:
|
if len(args) == 0:
|
||||||
suites = ["zerver.tests",
|
suites = ["zerver.tests",
|
||||||
"analytics.tests"]
|
"analytics.tests"]
|
||||||
|
@ -150,11 +149,11 @@ if __name__ == "__main__":
|
||||||
import cProfile
|
import cProfile
|
||||||
prof = cProfile.Profile()
|
prof = cProfile.Profile()
|
||||||
prof.enable()
|
prof.enable()
|
||||||
if options.url_coverage:
|
|
||||||
# This is kind of hacky, but it's the most reliable way
|
# This is kind of hacky, but it's the most reliable way
|
||||||
# to make sure instrumentation decorators know the
|
# to make sure instrumentation decorators know the
|
||||||
# setting when they run.
|
# setting when they run.
|
||||||
os.environ['TEST_INSTRUMENT_URL_COVERAGE'] = 'TRUE'
|
os.environ['TEST_INSTRUMENT_URL_COVERAGE'] = 'TRUE'
|
||||||
|
|
||||||
# setup() needs to be called after coverage is started to get proper coverage reports of model
|
# setup() needs to be called after coverage is started to get proper coverage reports of model
|
||||||
# files, since part of setup is importing the models for all applications in INSTALLED_APPS.
|
# files, since part of setup is importing the models for all applications in INSTALLED_APPS.
|
||||||
|
@ -169,7 +168,8 @@ if __name__ == "__main__":
|
||||||
|
|
||||||
TestRunner = get_runner(settings)
|
TestRunner = get_runner(settings)
|
||||||
test_runner = TestRunner()
|
test_runner = TestRunner()
|
||||||
failures = test_runner.run_tests(suites, fatal_errors=options.fatal_errors)
|
failures = test_runner.run_tests(suites, fatal_errors=options.fatal_errors,
|
||||||
|
full_suite=full_suite)
|
||||||
|
|
||||||
templates_not_rendered = test_runner.get_shallow_tested_templates()
|
templates_not_rendered = test_runner.get_shallow_tested_templates()
|
||||||
if templates_not_rendered:
|
if templates_not_rendered:
|
||||||
|
|
|
@ -47,6 +47,7 @@ import base64
|
||||||
import mock
|
import mock
|
||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
|
import sys
|
||||||
import time
|
import time
|
||||||
import ujson
|
import ujson
|
||||||
import unittest
|
import unittest
|
||||||
|
@ -249,8 +250,8 @@ def instrument_url(f):
|
||||||
return result
|
return result
|
||||||
return wrapper
|
return wrapper
|
||||||
|
|
||||||
def write_instrumentation_reports():
|
def write_instrumentation_reports(full_suite):
|
||||||
# type: () -> None
|
# type: (bool) -> None
|
||||||
if INSTRUMENTING:
|
if INSTRUMENTING:
|
||||||
calls = INSTRUMENTED_CALLS
|
calls = INSTRUMENTED_CALLS
|
||||||
var_dir = 'var' # TODO make sure path is robust here
|
var_dir = 'var' # TODO make sure path is robust here
|
||||||
|
@ -270,8 +271,9 @@ def write_instrumentation_reports():
|
||||||
''')
|
''')
|
||||||
print(call)
|
print(call)
|
||||||
|
|
||||||
print('URL coverage report is in %s' % (fn,))
|
if full_suite:
|
||||||
print('Try running: ./tools/analyze-url-coverage')
|
print('URL coverage report is in %s' % (fn,))
|
||||||
|
print('Try running: ./tools/analyze-url-coverage')
|
||||||
|
|
||||||
# Find our untested urls.
|
# Find our untested urls.
|
||||||
from zproject.urls import urlpatterns
|
from zproject.urls import urlpatterns
|
||||||
|
@ -286,12 +288,12 @@ def write_instrumentation_reports():
|
||||||
else:
|
else:
|
||||||
untested_patterns.append(pattern.regex.pattern)
|
untested_patterns.append(pattern.regex.pattern)
|
||||||
|
|
||||||
fn = os.path.join(var_dir, 'untested_url_report.txt')
|
|
||||||
with open(fn, 'w') as f:
|
if full_suite and len(untested_patterns):
|
||||||
f.write('untested urls\n')
|
print("\nERROR: Some URLs are untested! Here's the list of untested URLs:")
|
||||||
for untested_pattern in sorted(untested_patterns):
|
for untested_pattern in sorted(untested_patterns):
|
||||||
f.write(' %s\n' % (untested_pattern,))
|
print(" %s" % (untested_pattern,))
|
||||||
print('Untested-url report is in %s' % (fn,))
|
sys.exit(1)
|
||||||
|
|
||||||
def get_all_templates():
|
def get_all_templates():
|
||||||
# type: () -> List[str]
|
# type: () -> List[str]
|
||||||
|
|
|
@ -186,8 +186,9 @@ class Runner(DiscoverRunner):
|
||||||
return failed
|
return failed
|
||||||
return failed
|
return failed
|
||||||
|
|
||||||
def run_tests(self, test_labels, extra_tests=None, **kwargs):
|
def run_tests(self, test_labels, extra_tests=None,
|
||||||
# type: (List[str], Optional[List[TestCase]], **Any) -> bool
|
full_suite=False, **kwargs):
|
||||||
|
# type: (List[str], Optional[List[TestCase]], bool, **Any) -> bool
|
||||||
self.setup_test_environment()
|
self.setup_test_environment()
|
||||||
try:
|
try:
|
||||||
suite = self.build_suite(test_labels, extra_tests)
|
suite = self.build_suite(test_labels, extra_tests)
|
||||||
|
@ -208,5 +209,5 @@ class Runner(DiscoverRunner):
|
||||||
failed = self.run_suite(suite, fatal_errors=kwargs.get('fatal_errors'))
|
failed = self.run_suite(suite, fatal_errors=kwargs.get('fatal_errors'))
|
||||||
self.teardown_test_environment()
|
self.teardown_test_environment()
|
||||||
if not failed:
|
if not failed:
|
||||||
write_instrumentation_reports()
|
write_instrumentation_reports(full_suite=full_suite)
|
||||||
return failed
|
return failed
|
||||||
|
|
Loading…
Reference in New Issue