diff --git a/doc/en/plugins_index.py b/doc/en/plugins_index.py new file mode 100644 index 000000000..96726cdc0 --- /dev/null +++ b/doc/en/plugins_index.py @@ -0,0 +1,179 @@ +''' +Script to generate the file `plugins_index.txt` with information about pytest plugins taken directly +from a live PyPI server. + +This will evolve to include test compatibility (pythons and pytest versions) information also. +''' +from collections import namedtuple +import datetime +from distutils.version import LooseVersion +import itertools +from optparse import OptionParser +import os +import sys +import xmlrpclib + + +#=================================================================================================== +# iter_plugins +#=================================================================================================== +def iter_plugins(client, search='pytest-'): + ''' + Returns an iterator of (name, version) from PyPI. + + :param client: xmlrpclib.ServerProxy + :param search: package names to search for + ''' + for plug_data in client.search({'name' : search}): + yield plug_data['name'], plug_data['version'] + + +#=================================================================================================== +# get_latest_versions +#=================================================================================================== +def get_latest_versions(plugins): + ''' + Returns an iterator of (name, version) from the given list of (name, version), but returning + only the latest version of the package. Uses distutils.LooseVersion to ensure compatibility + with PEP386. + ''' + plugins = [(name, LooseVersion(version)) for (name, version) in plugins] + for name, grouped_plugins in itertools.groupby(plugins, key=lambda x: x[0]): + name, loose_version = list(grouped_plugins)[-1] + yield name, str(loose_version) + + +#=================================================================================================== +# obtain_plugins_table +#=================================================================================================== +def obtain_plugins_table(plugins, client): + ''' + Returns information to populate a table of plugins, their versions, authors, etc. + + The returned information is a list of columns of `ColumnData` namedtuples(text, link). Link + can be None if the text for that column should not be linked to anything. + + :param plugins: list of (name, version) + :param client: xmlrpclib.ServerProxy + + ''' + rows = [] + ColumnData = namedtuple('ColumnData', 'text link') + headers = ['Name', 'Version', 'Author', 'Summary'] + + for package_name, version in plugins: + release_data = client.release_data(package_name, version) + row = ( + ColumnData(package_name, release_data['package_url']), + ColumnData(version, release_data['release_url']), + ColumnData(release_data['author'], release_data['author_email']), + ColumnData(release_data['summary'], None), + ) + assert len(row) == len(headers) + rows.append(row) + + return headers, rows + + +#=================================================================================================== +# generate_plugins_index_from_table +#=================================================================================================== +def generate_plugins_index_from_table(filename, headers, rows): + ''' + Generates a RST file with the table data given. + + :param filename: output filename + :param headers: see `obtain_plugins_table` + :param rows: see `obtain_plugins_table` + ''' + # creates a list of rows, each being a str containing appropriate column text and link + table_texts = [] + for row in rows: + column_texts = [] + for i, col_data in enumerate(row): + text = '`%s <%s>`_' % (col_data.text, col_data.link) if col_data.link else col_data.text + column_texts.append(text) + table_texts.append(column_texts) + + # compute max length of each column so we can build the rst table + column_lengths = [len(x) for x in headers] + for column_texts in table_texts: + for i, row_text in enumerate(column_texts): + column_lengths[i] = max(column_lengths[i], len(row_text) + 2) + + def get_row_limiter(char): + return ' '.join(char * length for length in column_lengths) + + with file(filename, 'w') as f: + # write welcome + print >> f, '.. _plugins_index:' + print >> f + print >> f, 'List of Third-Party Plugins' + print >> f, '===========================' + print >> f + + # table + print >> f, get_row_limiter('=') + for i, header in enumerate(headers): + print >> f, '{:^{fill}}'.format(header, fill=column_lengths[i]), + print >> f + print >> f, get_row_limiter('=') + + for column_texts in table_texts: + for i, row_text in enumerate(column_texts): + print >> f, '{:^{fill}}'.format(row_text, fill=column_lengths[i]), + print >> f + print >> f + print >> f, get_row_limiter('=') + print >> f + + print >> f, '*(Updated on %s)*' % _get_today_as_str() + + +#=================================================================================================== +# _get_today_as_str +#=================================================================================================== +def _get_today_as_str(): + ''' + internal. only exists so we can patch it in testing. + ''' + return datetime.date.today().strftime('%Y-%m-%d') + + +#=================================================================================================== +# generate_plugins_index +#=================================================================================================== +def generate_plugins_index(client, filename): + ''' + Generates an RST file with a table of the latest pytest plugins found in PyPI. + + :param client: xmlrpclib.ServerProxy + :param filename: output filename + ''' + plugins = get_latest_versions(iter_plugins(client)) + headers, rows = obtain_plugins_table(plugins, client) + generate_plugins_index_from_table(filename, headers, rows) + + +#=================================================================================================== +# main +#=================================================================================================== +def main(argv): + filename = os.path.join(os.path.dirname(__file__), 'plugins_index.txt') + url = 'http://pypi.python.org/pypi' + + parser = OptionParser(description='Generates a restructured document of pytest plugins from PyPI') + parser.add_option('-f', '--filename', default=filename, help='output filename [default: %default]') + parser.add_option('-u', '--url', default=url, help='url of PyPI server to obtain data from [default: %default]') + (options, _) = parser.parse_args(argv[1:]) + + client = xmlrpclib.ServerProxy(options.url) + generate_plugins_index(client, options.filename) + print 'OK' + return 0 + +#=================================================================================================== +# main +#=================================================================================================== +if __name__ == '__main__': + sys.exit(main(sys.argv)) diff --git a/doc/en/plugins_index.txt b/doc/en/plugins_index.txt new file mode 100644 index 000000000..a73044839 --- /dev/null +++ b/doc/en/plugins_index.txt @@ -0,0 +1,61 @@ +.. _plugins_index: + +List of Third-Party Plugins +=========================== + +================================================================================== ============================================================================ ==================================================================================== ============================================================================================================================================= + Name Version Author Summary +================================================================================== ============================================================================ ==================================================================================== ============================================================================================================================================= + `pytest-bdd `_ `0.6.1 `_ `Oleg Pidsadnyi `_ BDD for pytest + `pytest-bdd-splinter `_ `0.5.2 `_ `Oleg Pidsadnyi `_ Splinter subplugin for Pytest BDD plugin + `pytest-bench `_ `0.1.0 `_ `Concordus Applications `_ Benchmark utility that plugs into pytest. + `pytest-blockage `_ `0.1 `_ `UNKNOWN `_ Disable network requests during a test run. + `pytest-browsermob-proxy `_ `0.1 `_ `Dave Hunt `_ BrowserMob proxy plugin for py.test. + `pytest-bugzilla `_ `0.2 `_ `Noufal Ibrahim `_ py.test bugzilla integration plugin + `pytest-cache `_ `1.0 `_ `Holger Krekel `_ pytest plugin with mechanisms for caching across test runs + `pytest-capturelog `_ `0.7 `_ `Meme Dough `_ py.test plugin to capture log messages + `pytest-codecheckers `_ `0.2 `_ `Ronny Pfannschmidt `_ pytest plugin to add source code sanity checks (pep8 and friends) + `pytest-contextfixture `_ `0.1.1 `_ `Andreas Pelme `_ Define pytest fixtures as context managers. + `pytest-couchdbkit `_ `0.5.1 `_ `RonnyPfannschmidt `_ py.test extension for per-test couchdb databases using couchdbkit + `pytest-cov `_ `1.6 `_ `Meme Dough `_ py.test plugin for coverage reporting with support for both centralised and distributed testing, including subprocesses and multiprocessing + `pytest-dbfixtures `_ `0.3.8.3 `_ `Clearcode - The A Room `_ dbfixtures plugin for py.test. + `pytest-django `_ `2.3.1 `_ `Andreas Pelme `_ A Django plugin for py.test. + `pytest-django-lite `_ `0.1.0 `_ `David Cramer `_ The bare minimum to integrate py.test with Django. + `pytest-figleaf `_ `1.0 `_ `holger krekel `_ py.test figleaf coverage plugin + `pytest-flakes `_ `0.2 `_ `Florian Schulze, Holger Krekel and Ronny Pfannschmidt `_ pytest plugin to check source code with pyflakes + `pytest-greendots `_ `0.2 `_ `UNKNOWN `_ Green progress dots + `pytest-growl `_ `0.1 `_ `Anthony Long `_ Growl notifications for pytest results. + `pytest-incremental `_ `0.3.0 `_ `Eduardo Naufel Schettino `_ an incremental test runner (pytest plugin) + `pytest-instafail `_ `0.1.0 `_ `Janne Vanhala `_ py.test plugin to show failures instantly + `pytest-ipdb `_ `0.1-prerelease `_ `Matthew de Verteuil `_ A py.test plug-in to enable drop to ipdb debugger on test failure. + `pytest-konira `_ `0.2 `_ `Alfredo Deza `_ Run Konira DSL tests with py.test + `pytest-localserver `_ `0.3 `_ `Sebastian Rahlf `_ py.test plugin to test server connections locally. + `pytest-marker-bugzilla `_ `0.06 `_ `Eric Sammons `_ py.test bugzilla integration plugin, using markers + `pytest-markfiltration `_ `0.8 `_ `adam goucher `_ UNKNOWN + `pytest-marks `_ `0.4 `_ `adam goucher `_ UNKNOWN + `pytest-monkeyplus `_ `1.1.0 `_ `Virgil Dupras `_ pytest's monkeypatch subclass with extra functionalities + `pytest-mozwebqa `_ `1.1.1 `_ `Dave Hunt `_ Mozilla WebQA plugin for py.test. + `pytest-oerp `_ `0.2.0 `_ `Leonardo Santagada `_ pytest plugin to test OpenERP modules + `pytest-osxnotify `_ `0.1.4 `_ `Daniel Bader `_ OS X notifications for py.test results. + `pytest-paste-config `_ `0.1 `_ `UNKNOWN `_ Allow setting the path to a paste config file + `pytest-pep8 `_ `1.0.5 `_ `Holger Krekel and Ronny Pfannschmidt `_ pytest plugin to check PEP8 requirements + `pytest-poo `_ `0.2 `_ `Andreas Pelme `_ Visualize your crappy tests + `pytest-pydev `_ `0.1 `_ `Sebastian Rahlf `_ py.test plugin to connect to a remote debug server with PyDev or PyCharm. + `pytest-qt `_ `1.0.2 `_ `Bruno Oliveira `_ pytest plugin that adds fixtures for testing Qt (PyQt and PySide) applications. + `pytest-quickcheck `_ `0.7 `_ `Tetsuya Morimoto `_ pytest plugin to generate random data inspired by QuickCheck + `pytest-rage `_ `0.1 `_ `Leonardo Santagada `_ pytest plugin to implement PEP712 + `pytest-random `_ `0.02 `_ `Leah Klearman `_ py.test plugin to randomize tests + `pytest-rerunfailures `_ `0.03 `_ `Leah Klearman `_ py.test plugin to re-run tests to eliminate flakey failures + `pytest-runfailed `_ `0.3 `_ `Dimitri Merejkowsky `_ implement a --failed option for pytest + `pytest-runner `_ `2.0 `_ `Jason R. Coombs `_ UNKNOWN + `pytest-sugar `_ `0.2.2 `_ `Teemu, Janne Vanhala `_ py.test plugin that adds instafail, ETA and neat graphics + `pytest-timeout `_ `0.3 `_ `Floris Bruynooghe `_ pytest plugin to abort tests after a timeout + `pytest-twisted `_ `1.4 `_ `Ralf Schmitt `_ A twisted plugin for py.test. + `pytest-xdist `_ `1.9 `_ `holger krekel and contributors `_ py.test xdist plugin for distributed testing and loop-on-failing modes + `pytest-xprocess `_ `0.8 `_ `Holger Krekel `_ pytest plugin to manage external processes across test runs + `pytest-yamlwsgi `_ `0.6 `_ `Ali Afshar `_ Run tests against wsgi apps defined in yaml + `pytest-zap `_ `0.1 `_ `Dave Hunt `_ OWASP ZAP plugin for py.test. + +================================================================================== ============================================================================ ==================================================================================== ============================================================================================================================================= + +*(Updated on 2013-10-11)* diff --git a/doc/en/test_plugins_index.py b/doc/en/test_plugins_index.py new file mode 100644 index 000000000..4183a9e13 --- /dev/null +++ b/doc/en/test_plugins_index.py @@ -0,0 +1,88 @@ +import os +import xmlrpclib + + +#=================================================================================================== +# test_plugins_index +#=================================================================================================== +def test_plugins_index(tmpdir, monkeypatch): + ''' + Blackbox testing for plugins_index script. Calls main() generating a file and compares produced + output to expected. + + .. note:: if the test fails, a file named `test_plugins_index.obtained` will be generated in + the same directory as this test file. Ensure the contents are correct and overwrite + the global `expected_output` with the new contents. + ''' + import plugins_index + + # dummy interface to xmlrpclib.ServerProxy + class DummyProxy(object): + + expected_url = 'http://dummy.pypi' + def __init__(self, url): + assert url == self.expected_url + + def search(self, query): + assert query == {'name' : 'pytest-'} + return [ + {'name': 'pytest-plugin1', 'version' : '0.8'}, + {'name': 'pytest-plugin1', 'version' : '1.0'}, + {'name': 'pytest-plugin2', 'version' : '1.2'}, + ] + + def release_data(self, package_name, version): + results = { + ('pytest-plugin1', '1.0') : { + 'package_url' : 'http://plugin1', + 'release_url' : 'http://plugin1/1.0', + 'author' : 'someone', + 'author_email' : 'someone@py.com', + 'summary' : 'some plugin', + }, + + ('pytest-plugin2', '1.2') : { + 'package_url' : 'http://plugin2', + 'release_url' : 'http://plugin2/1.2', + 'author' : 'other', + 'author_email' : 'other@py.com', + 'summary' : 'some other plugin', + }, + } + + return results[(package_name, version)] + + + monkeypatch.setattr(xmlrpclib, 'ServerProxy', DummyProxy, 'foo') + monkeypatch.setattr(plugins_index, '_get_today_as_str', lambda: '2013-10-20') + + output_file = str(tmpdir.join('output.txt')) + assert plugins_index.main(['', '-f', output_file, '-u', DummyProxy.expected_url]) == 0 + + with file(output_file, 'rU') as f: + obtained_output = f.read() + + if obtained_output != expected_output: + obtained_file = os.path.splitext(__file__)[0] + '.obtained' + with file(obtained_file, 'w') as f: + f.write(obtained_output) + + assert obtained_output == expected_output + + +expected_output = '''\ +.. _plugins_index: + +List of Third-Party Plugins +=========================== + +==================================== ============================= ============================= =================== + Name Version Author Summary +==================================== ============================= ============================= =================== + `pytest-plugin1 `_ `1.0 `_ `someone `_ some plugin + `pytest-plugin2 `_ `1.2 `_ `other `_ some other plugin + +==================================== ============================= ============================= =================== + +*(Updated on 2013-10-20)* +'''