pytest / makepluginlist.py

import py
import sys
WIDTH = 75

plugins = [
    ('Plugins related to Python test functions and programs', 
            'xfail figleaf monkeypatch iocapture recwarn',),
    ('Plugins for other testing styles and languages', 
            'unittest doctest oejskit restdoc'),
    ('Plugins for generic reporting and failure logging', 
            'pocoo resultlog terminal',),
    #('internal plugins / core functionality', 
    #    #'pdb keyword hooklog runner execnetcleanup # pytester',
    #    'pdb keyword hooklog runner execnetcleanup' # pytester',
    #)
]

externals = {
    'oejskit': 'run javascript tests in real life browsers',

}
                
def warn(*args):
    msg = " ".join(map(str, args))
    print >>sys.stderr, "WARN:", msg

class RestWriter:
    def __init__(self, target):
        self.target = py.path.local(target)
        self.links = []

    def _getmsg(self, args):
        return " ".join(map(str, args))

    def Print(self, *args, **kwargs):
        msg = self._getmsg(args)
        if 'indent' in kwargs:
            indent = kwargs['indent'] * " "
            lines = [(indent + x) for x in msg.split("\n")]
            msg = "\n".join(lines)
        self.out.write(msg)
        if not msg or msg[-1] != "\n":
            self.out.write("\n")
        self.out.flush()

    def sourcecode(self, source):
        lines = str(source).split("\n")
        self.Print(".. sourcecode:: python")
        self.Print()
        for line in lines:
            self.Print("   ", line)

    def _sep(self, separator, args):
        msg = self._getmsg(args)
        sep = len(msg) * separator
        self.Print()
        self.Print(msg)
        self.Print(sep)
        self.Print()


    def h1(self, *args):
        self._sep('=', args)

    def h2(self, *args):
        self._sep('-', args)

    def h3(self, *args):
        self._sep('+', args)

    def li(self, *args):
        msg = self._getmsg(args)
        sep = "* %s" %(msg)
        self.Print(sep)

    def dt(self, term):
        self.Print("``%s``" % term)

    def dd(self, doc):
        self.Print(doc, indent=4)

    def para(self, *args):
        msg = self._getmsg(args)
        self.Print(msg)

    def add_internal_link(self, name, path):
        relpath = path.new(ext=".html").relto(self.target.dirpath())
        self.links.append((name, relpath))

    def write_links(self):
        self.Print()
        for link in self.links:
            #warn(repr(self.link))
            self.Print(".. _`%s`: %s" % (link[0], link[1]))

    def make(self, **kwargs):
        self.out = self.target.open("w")
        self.makerest(**kwargs)
        self.write_links()

        self.out.close()
        print "wrote", self.target
        del self.out
     
class PluginOverview(RestWriter):
    def makerest(self, config):
        plugindir = py.path.local(py.__file__).dirpath("test", "plugin")
        for cat, specs in plugins:
            pluginlist = specs.split()
            self.h1(cat)
            for name in pluginlist:
                oneliner = externals.get(name, None)
                docpath = self.target.dirpath(name).new(ext=".txt")
                if oneliner is not None:
                    htmlpath = docpath.new(ext='.html')
                    self.para("%s_ %s" %(name, oneliner))
                    self.add_internal_link(name, htmlpath)
                else:
                    doc = PluginDoc(docpath)
                    doc.make(config=config, name=name) 
                    self.add_internal_link(name, doc.target)
                    self.para("%s_ %s" %(name, doc.oneliner))
                self.Print()

class HookSpec(RestWriter):
    
    def makerest(self, config):
        module = config.pluginmanager.hook._hookspecs
        source = py.code.Source(module)
        self.h1("hook specification sourcecode")
        self.sourcecode(source)

class PluginDoc(RestWriter):
    def makerest(self, config, name):
        config.pluginmanager.import_plugin(name)
        plugin = config.pluginmanager.getplugin(name)
        assert plugin is not None, plugin

        doc = plugin.__doc__.strip()
        i = doc.find("\n")
        if i == -1:
            oneliner = doc
            moduledoc = ""
        else:
            oneliner = doc[:i].strip()
            moduledoc = doc[i+1:].strip()

        self.name = plugin.__name__.split(".")[-1]
        self.oneliner = oneliner 
        self.moduledoc = moduledoc
       
        self.h1("%s plugin" % self.name) # : %s" %(self.name, self.oneliner))
        self.Print(self.oneliner)
        self.Print()
        self.Print(".. contents::")
        self.Print("  :local:")
        self.Print()

        self.Print(moduledoc)
    
        self.emit_funcargs(plugin)
        self.emit_options(plugin)
        self.emit_source(plugin, config.hg_changeset)
        #self.sourcelink = (purename, 
        #    "http://bitbucket.org/hpk42/py-trunk/src/tip/py/test/plugin/" + 
        #    purename + ".py")
        #
    def emit_source(self, plugin, hg_changeset):
        basename = py.path.local(plugin.__file__).basename
        if basename.endswith("pyc"):
            basename = basename[:-1]
        #self.para("`%s`_ source code" % basename)
        #self.links.append((basename, 
        #    "http://bitbucket.org/hpk42/py-trunk/src/tip/py/test/plugin/" +
        #    basename))
        self.h1("Start improving this plugin in 30 seconds")
        self.para(py.code.Source("""
            Do you find the above documentation or the plugin itself lacking? 

            1. Download `%s`_ plugin source code 
            2. put it somewhere as ``%s`` into your import path 
            3. a subsequent ``py.test`` run will use your local version

            Further information: extend_ documentation, other plugins_ or contact_.  
        """ % (basename, basename)))
        #    your work appreciated if you offer back your version.  In this case
        #    it probably makes sense if you `checkout the py.test 
        #    development version`_ and apply your changes to the plugin
        #    version in there. 
        self.links.append((basename, 
            "http://bitbucket.org/hpk42/py-trunk/raw/%s/" 
            "py/test/plugin/%s" %(hg_changeset, basename)))
        self.links.append(('extend', '../extend.html'))
        self.links.append(('plugins', 'index.html'))
        self.links.append(('contact', '../../contact.html'))
        self.links.append(('checkout the py.test development version', 
            '../../download.html#checkout'))
       
        if 0: # this breaks the page layout and makes large doc files
            #self.h2("plugin source code") 
            self.Print()
            self.para("For your convenience here is also an inlined version "
                      "of ``%s``:" %basename)
            #self(or copy-paste from below)
            self.Print()
            self.sourcecode(py.code.Source(plugin))

    def emit_funcargs(self, plugin):
        funcargfuncs = []
        prefix = "pytest_funcarg__"
        for name in vars(plugin):
            if name.startswith(prefix):
                funcargfuncs.append(getattr(plugin, name))
        if not funcargfuncs:
            return
        for func in funcargfuncs:
            argname = func.__name__[len(prefix):]
            self.Print()
            self.Print(".. _`%s funcarg`:" % argname)
            self.Print()
            self.h2("the %r test function argument" % argname)
            if func.__doc__:
                doclines = func.__doc__.split("\n")
                source = py.code.Source("\n".join(doclines[1:]))
                source.lines.insert(0, doclines[0])
                self.para(str(source))
            else:
                self.para("XXX missing docstring")
                warn("missing docstring", func)

    def emit_options(self, plugin):
        from py.__.test.parseopt import Parser
        options = []
        parser = Parser(processopt=options.append)
        if hasattr(plugin, 'pytest_addoption'):
            plugin.pytest_addoption(parser)
        if not options:
            return
        self.h2("command line options")
        self.Print()
        formatter = py.compat.optparse.IndentedHelpFormatter()
        for opt in options:
            switches = formatter.format_option_strings(opt)
            self.Print("``%s``" % switches)
            self.Print(opt.help, indent=4)

if __name__ == "__main__":
    _config = py.test.config
    _config.parse([])
    _config.pluginmanager.do_configure(_config)

    pydir = py.path.local(py.__file__).dirpath()

    cmd = "hg tip --template '{node}'" 
    old = pydir.dirpath().chdir()
    _config.hg_changeset = py.process.cmdexec(cmd).strip()

    testdir = pydir.dirpath("doc", 'test')
   
    ov = PluginOverview(testdir.join("plugin", "index.txt"))
    ov.make(config=_config)
    
    ov = HookSpec(testdir.join("plugin", "hookspec.txt"))
    ov.make(config=_config)
Tip: Filter by directory path e.g. /media app.js to search for public/media/app.js.
Tip: Use camelCasing e.g. ProjME to search for ProjectModifiedEvent.java.
Tip: Filter by extension type e.g. /repo .js to search for all .js files in the /repo directory.
Tip: Separate your search with spaces e.g. /ssh pom.xml to search for src/ssh/pom.xml.
Tip: Use ↑ and ↓ arrow keys to navigate and return to view the file.
Tip: You can also navigate files with Ctrl+j (next) and Ctrl+k (previous) and view the file with Ctrl+o.
Tip: You can also navigate files with Alt+j (next) and Alt+k (previous) and view the file with Alt+o.