From: Ben Darnell Date: Thu, 9 Jun 2011 18:03:49 +0000 (-0700) Subject: Use coverage checker instead of automatic member extraction X-Git-Tag: v2.0.0~20^2~8 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=050797cc5e89fe96118736957342ef4143f8dbb3;p=thirdparty%2Ftornado.git Use coverage checker instead of automatic member extraction --- diff --git a/website/Makefile b/website/Makefile index 45215cffa..d763dba31 100644 --- a/website/Makefile +++ b/website/Makefile @@ -1,6 +1,11 @@ +SPHINXOPTS=-d sphinx/build/doctrees sphinx .PHONY: sphinx sphinx: - sphinx-build -b html -d sphinx/build/doctrees sphinx sphinx/build/html + sphinx-build -b html $(SPHINXOPTS) sphinx/build/html + +.PHONY: coverage +coverage: + sphinx-build -b coverage ${SPHINXOPTS} sphinx/build/coverage clean: rm -rf sphinx/build \ No newline at end of file diff --git a/website/sphinx/conf.py b/website/sphinx/conf.py index 53addb170..17e468dc6 100644 --- a/website/sphinx/conf.py +++ b/website/sphinx/conf.py @@ -4,17 +4,29 @@ import sys sys.path.insert(0, os.path.abspath("../..")) import tornado -print tornado.__file__ +# For our version of sphinx_coverage.py. The version in sphinx 1.0.7 +# has too many false positives; this version comes from upstream HG. +sys.path.append(os.path.abspath(".")) master_doc = "index" project = "Tornado" copyright = "2011, Facebook" -import tornado version = release = tornado.version -extensions = ["sphinx.ext.autodoc"] +extensions = ["sphinx.ext.autodoc", "sphinx_coverage"] autodoc_member_order = "bysource" -autodoc_default_flags = ["members", "undoc-members"] + +coverage_skip_undoc_in_source = True +# I wish this could go in a per-module file... +coverage_ignore_classes = [ + # tornado.web + "ChunkedTransferEncoding", + "GZipContentEncoding", + "OutputTransform", + "TemplateModule", + "url", + ] + diff --git a/website/sphinx/sphinx_coverage.py b/website/sphinx/sphinx_coverage.py new file mode 100644 index 000000000..5ff81d704 --- /dev/null +++ b/website/sphinx/sphinx_coverage.py @@ -0,0 +1,264 @@ +# -*- coding: utf-8 -*- +""" + sphinx.ext.coverage + ~~~~~~~~~~~~~~~~~~~ + + Check Python modules and C API for coverage. Mostly written by Josip + Dzolonga for the Google Highly Open Participation contest. + + :copyright: Copyright 2007-2011 by the Sphinx team, see AUTHORS. + :license: BSD, see LICENSE for details. +""" + +import re +import glob +import inspect +import cPickle as pickle +from os import path + +from sphinx.builders import Builder + + +# utility +def write_header(f, text, char='-'): + f.write(text + '\n') + f.write(char * len(text) + '\n') + +def compile_regex_list(name, exps, warnfunc): + lst = [] + for exp in exps: + try: + lst.append(re.compile(exp)) + except Exception: + warnfunc('invalid regex %r in %s' % (exp, name)) + return lst + + +class CoverageBuilder(Builder): + + name = 'coverage' + + def init(self): + self.c_sourcefiles = [] + for pattern in self.config.coverage_c_path: + pattern = path.join(self.srcdir, pattern) + self.c_sourcefiles.extend(glob.glob(pattern)) + + self.c_regexes = [] + for (name, exp) in self.config.coverage_c_regexes.items(): + try: + self.c_regexes.append((name, re.compile(exp))) + except Exception: + self.warn('invalid regex %r in coverage_c_regexes' % exp) + + self.c_ignorexps = {} + for (name, exps) in self.config.coverage_ignore_c_items.iteritems(): + self.c_ignorexps[name] = compile_regex_list( + 'coverage_ignore_c_items', exps, self.warn) + self.mod_ignorexps = compile_regex_list( + 'coverage_ignore_modules', self.config.coverage_ignore_modules, + self.warn) + self.cls_ignorexps = compile_regex_list( + 'coverage_ignore_classes', self.config.coverage_ignore_classes, + self.warn) + self.fun_ignorexps = compile_regex_list( + 'coverage_ignore_functions', self.config.coverage_ignore_functions, + self.warn) + + def get_outdated_docs(self): + return 'coverage overview' + + def write(self, *ignored): + self.py_undoc = {} + self.build_py_coverage() + self.write_py_coverage() + + self.c_undoc = {} + self.build_c_coverage() + self.write_c_coverage() + + def build_c_coverage(self): + # Fetch all the info from the header files + c_objects = self.env.domaindata['c']['objects'] + for filename in self.c_sourcefiles: + undoc = [] + f = open(filename, 'r') + try: + for line in f: + for key, regex in self.c_regexes: + match = regex.match(line) + if match: + name = match.groups()[0] + if name not in c_objects: + for exp in self.c_ignorexps.get(key, ()): + if exp.match(name): + break + else: + undoc.append((key, name)) + continue + finally: + f.close() + if undoc: + self.c_undoc[filename] = undoc + + def write_c_coverage(self): + output_file = path.join(self.outdir, 'c.txt') + op = open(output_file, 'w') + try: + if self.config.coverage_write_headline: + write_header(op, 'Undocumented C API elements', '=') + op.write('\n') + + for filename, undoc in self.c_undoc.iteritems(): + write_header(op, filename) + for typ, name in undoc: + op.write(' * %-50s [%9s]\n' % (name, typ)) + op.write('\n') + finally: + op.close() + + def build_py_coverage(self): + objects = self.env.domaindata['py']['objects'] + modules = self.env.domaindata['py']['modules'] + + skip_undoc = self.config.coverage_skip_undoc_in_source + + for mod_name in modules: + ignore = False + for exp in self.mod_ignorexps: + if exp.match(mod_name): + ignore = True + break + if ignore: + continue + + try: + mod = __import__(mod_name, fromlist=['foo']) + except ImportError, err: + self.warn('module %s could not be imported: %s' % + (mod_name, err)) + self.py_undoc[mod_name] = {'error': err} + continue + + funcs = [] + classes = {} + + for name, obj in inspect.getmembers(mod): + # diverse module attributes are ignored: + if name[0] == '_': + # begins in an underscore + continue + if not hasattr(obj, '__module__'): + # cannot be attributed to a module + continue + if obj.__module__ != mod_name: + # is not defined in this module + continue + + full_name = '%s.%s' % (mod_name, name) + + if inspect.isfunction(obj): + if full_name not in objects: + for exp in self.fun_ignorexps: + if exp.match(name): + break + else: + if skip_undoc and not obj.__doc__: + continue + funcs.append(name) + elif inspect.isclass(obj): + for exp in self.cls_ignorexps: + if exp.match(name): + break + else: + if full_name not in objects: + if skip_undoc and not obj.__doc__: + continue + # not documented at all + classes[name] = [] + continue + + attrs = [] + + for attr_name in dir(obj): + if attr_name not in obj.__dict__: + continue + attr = getattr(obj, attr_name) + if not (inspect.ismethod(attr) or + inspect.isfunction(attr)): + continue + if attr_name[0] == '_': + # starts with an underscore, ignore it + continue + if skip_undoc and not attr.__doc__: + # skip methods without docstring if wished + continue + + full_attr_name = '%s.%s' % (full_name, attr_name) + if full_attr_name not in objects: + attrs.append(attr_name) + + if attrs: + # some attributes are undocumented + classes[name] = attrs + + self.py_undoc[mod_name] = {'funcs': funcs, 'classes': classes} + + def write_py_coverage(self): + output_file = path.join(self.outdir, 'python.txt') + op = open(output_file, 'w') + failed = [] + try: + if self.config.coverage_write_headline: + write_header(op, 'Undocumented Python objects', '=') + keys = self.py_undoc.keys() + keys.sort() + for name in keys: + undoc = self.py_undoc[name] + if 'error' in undoc: + failed.append((name, undoc['error'])) + else: + if not undoc['classes'] and not undoc['funcs']: + continue + + write_header(op, name) + if undoc['funcs']: + op.write('Functions:\n') + op.writelines(' * %s\n' % x for x in undoc['funcs']) + op.write('\n') + if undoc['classes']: + op.write('Classes:\n') + for name, methods in sorted(undoc['classes'].iteritems()): + if not methods: + op.write(' * %s\n' % name) + else: + op.write(' * %s -- missing methods:\n' % name) + op.writelines(' - %s\n' % x for x in methods) + op.write('\n') + + if failed: + write_header(op, 'Modules that failed to import') + op.writelines(' * %s -- %s\n' % x for x in failed) + finally: + op.close() + + def finish(self): + # dump the coverage data to a pickle file too + picklepath = path.join(self.outdir, 'undoc.pickle') + dumpfile = open(picklepath, 'wb') + try: + pickle.dump((self.py_undoc, self.c_undoc), dumpfile) + finally: + dumpfile.close() + + +def setup(app): + app.add_builder(CoverageBuilder) + app.add_config_value('coverage_ignore_modules', [], False) + app.add_config_value('coverage_ignore_functions', [], False) + app.add_config_value('coverage_ignore_classes', [], False) + app.add_config_value('coverage_c_path', [], False) + app.add_config_value('coverage_c_regexes', {}, False) + app.add_config_value('coverage_ignore_c_items', {}, False) + app.add_config_value('coverage_write_headline', True, False) + app.add_config_value('coverage_skip_undoc_in_source', False, False) diff --git a/website/sphinx/web.rst b/website/sphinx/web.rst index 32ecb024c..20b21ad84 100644 --- a/website/sphinx/web.rst +++ b/website/sphinx/web.rst @@ -2,12 +2,10 @@ =============== .. automodule:: tornado.web - :exclude-members: RequestHandler, Application, asynchronous, addslash, removeslash, URLSpec, url Request handlers ---------------- .. autoclass:: RequestHandler - :exclude-members: initialize, prepare, get, post, put, delete, head, options, get_argument, get_arguments, decode_argument, set_status, set_header, write, flush, finish, render, render_string, send_error, get_error_html, cookies, get_cookie, set_cookie, clear_cookie, clear_all_cookies, get_secure_cookie, set_secure_cookie, create_signed_value **Entry points** @@ -39,8 +37,11 @@ .. automethod:: finish .. automethod:: render .. automethod:: render_string + .. automethod:: redirect .. automethod:: send_error .. automethod:: get_error_html + .. automethod:: clear + **Cookies** @@ -55,11 +56,26 @@ **Other** + .. automethod:: async_callback + .. automethod:: check_xsrf_cookie + .. automethod:: compute_etag + .. automethod:: get_browser_locale + .. automethod:: get_current_user + .. automethod:: get_login_url + .. automethod:: get_status + .. automethod:: get_template_path + .. automethod:: get_user_locale + .. automethod:: on_connection_close + .. automethod:: require_setting + .. automethod:: static_url + .. automethod:: xsrf_form_html + Application configuration ----------------------------- .. autoclass:: Application + :members: .. autoclass:: URLSpec @@ -74,3 +90,12 @@ Everything else --------------- + .. autoexception:: HTTPError + .. autoclass:: UIModule + :members: + + .. autoclass:: ErrorHandler + .. autoclass:: FallbackHandler + .. autoclass:: RedirectHandler + .. autoclass:: StaticFileHandler + :members: