] > doctest_checker.py (Logilab.org)

File doctest_checker.py

name
doctest_checker.py
download
#!/usr/bin/env python

# This program is free software; you can redistribute it and/or modify it under
# the terms of the GNU General Public License as published by the Free Software
# Foundation; either version 2 of the License, or (at your option) any later
# version.
#
# This program is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details
#
# You should have received a copy of the GNU General Public License along with
# this program; if not, write to the Free Software Foundation, Inc.,
# 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
#
# Written by Ben Sasson (Scorpio) scorpios666@gmail.com

"""
This PyLint checker executes all the doctests defined in a module, using the
standard module doctest and it's objects.
"""

__revision__ = 1

from logilab.common.ureports import Table
from pylint.checkers import BaseChecker
from pylint.interfaces import IASTNGChecker

import doctest

MSGS = {
    'W1001': ('No tests were defined', 'Used when module defines no doctests'),
    'E1001': ('Doctest failed:\n%s', "Used when a module's doctest was failed"),
    }

def _get_module(name):
    """
    Imports and retrives the module specified in name argument. This is a code
    snippet taken from python __import__ documentation.
    """

    mod = __import__(name)
    components = name.split('.')
    for comp in components[1:]:
        mod = getattr(mod, comp)
    return mod


class DoctestChecker(BaseChecker):
    """
    This checker executes all the doctest defined in the module.
    """

    __implements__ = IASTNGChecker

    name = 'doctest_checker'
    msgs = MSGS
    priority = -4
    options = ()

    def __init__(self, linter):
        BaseChecker.__init__(self, linter)
        self.stats = None
        self.reports = (('R1001', 'Doctest results', self.report_doctest_stats),)

    def open(self):
        """init statistics"""
        self.stats = self.linter.add_stats(doctest_results=[])

    def visit_module(self, node):
        """
        Called by PyLint framework when visiting a module. In this checker,
        the method is used to find and execute all the doctests the visited
        module defines, store the results and notify for errors and warnings.
        
        The method issues the following warnings:
        W1001
        
        The method issues the following errors:
        E1001
        
        See MSGS for further details about them.
        """

        module = _get_module(node.name)

        failed_tests_results = []

        def _store_results(test):
            """Stores the failed tests output for later usage."""
            failed_tests_results.append(test)

        runner = doctest.DocTestRunner()
        finder = doctest.DocTestFinder()

        for test in finder.find(module, module.__name__):
            runner.run(test, out=_store_results)

        failed_tests, tests = runner.failures, runner.tries

        if not tests:
            self.add_message('W1001', node=node)
        else:
            if failed_tests:
                for failed_test in failed_tests_results:
                    self.add_message('E1001',
                        node=node,
                        args=failed_test.lstrip('\n*'))
        self.stats['doctest_results'].append((node.name, tests, failed_tests))

    def report_doctest_stats(self, sect, stats, old_stats):
        """
        Append the statistics gathered into the report. For now, old_stats are
        not used.
        """

        def _generate_line(name, tests, failed_tests):
            """
            Return a tuple represents a line in the report, filled with the
            results of the module "name".
            
            Line format is:
            name, total tests, failed tests, % tests passed, status
            
            status can be on of the following:
            OK:         All the tests passed.
            FAILED:     At least one of the tests failed.
            WARNING:    No tests were defined.
            """

            if tests:
                percent_passed = float(tests - failed_tests)/tests * 100
                if failed_tests:
                    status = '[FAILED]'
                else:
                    status = '[OK]'
            else:
                percent_passed = 0.0
                status = '[WARNING]'

            return (name,
                str(tests),
                str(failed_tests),
                '%.2f' % percent_passed,
                status)

        doctest_results = stats['doctest_results']
        lines = ('Name ', 'Tests ', 'Failures ', '% Passed ', 'Status ')

        total_tests = 0
        total_failed = 0
        for module_name, tests, failed_tests in doctest_results:
            total_tests += tests
            total_failed += failed_tests
            lines += _generate_line(module_name, tests, failed_tests)
        lines += _generate_line('Total', total_tests, total_failed)
        sect.append(Table(children=lines, cols=5, rheaders=1))


def register(linter):
    """required method to auto register this checker"""
    linter.register_checker(DoctestChecker(linter))