diff --git a/bin-for-dist/makepluginlist.py b/bin-for-dist/makepluginlist.py index 73a1cba6e..b640c52d7 100644 --- a/bin-for-dist/makepluginlist.py +++ b/bin-for-dist/makepluginlist.py @@ -3,28 +3,29 @@ import os, sys WIDTH = 75 plugins = [ - ('plugins for Python test functions', - 'skipping figleaf monkeypatch capture recwarn',), - ('plugins for other testing styles and languages', - 'oejskit unittest nose django doctest restdoc'), - ('plugins for generic reporting and failure logging', - 'pastebin resultlog terminal',), - ('plugins for generic reporting and failure logging', - 'pastebin resultlog terminal',), - ('misc plugins / core functionality', - 'helpconfig pdb mark hooklog') + ('advanced python testing', + 'skipping mark pdb figleaf coverage ' + 'monkeypatch capture recwarn tmpdir',), + ('testing domains', + 'oejskit django'), + ('reporting and failure logging', + 'pastebin xmlresult resultlog terminal',), + ('other testing conventions', + 'unittest nose doctest restdoc'), + ('core debugging / help functionality', + 'helpconfig hooklog') #('internal plugins / core functionality', - # #'pdb keyword hooklog runner execnetcleanup # pytester', - # 'pdb keyword hooklog runner execnetcleanup' # pytester', + # #'runner execnetcleanup # pytester', + # 'runner execnetcleanup' # pytester', #) ] externals = { - 'oejskit': "run javascript tests in real life browsers", - 'django': "support for testing django applications", -# 'coverage': "support for using Ned's coverage module", -# 'xmlresult': "support for generating xml reports " -# "and CruiseControl integration", + 'oejskit': "run javascript tests in real life browsers", + 'django': "for testing django applications", + 'coverage': "for testing with Ned's coverage module ", + 'xmlresult': "for generating xml reports " + "and CruiseControl integration", } def warn(*args): @@ -136,7 +137,7 @@ class PluginOverview(RestWriter): 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.para("%s_ (3rd) %s" %(name, oneliner)) self.add_internal_link(name, htmlpath) else: doc = PluginDoc(docpath) @@ -212,7 +213,7 @@ class PluginDoc(RestWriter): # "py/test/plugin/%s" %(hg_changeset, basename))) self.links.append((basename, "http://bitbucket.org/hpk42/py-trunk/raw/%s/" - "_py/test/plugin/%s" %(pyversion, basename))) + "py/plugin/%s" %(pyversion, basename))) self.links.append(('customize', '../customize.html')) self.links.append(('plugins', 'index.html')) self.links.append(('get in contact', '../../contact.html')) diff --git a/contrib/pytest_coverage/__init__.py b/contrib/pytest_coverage/__init__.py deleted file mode 100644 index 0feb34a17..000000000 --- a/contrib/pytest_coverage/__init__.py +++ /dev/null @@ -1,329 +0,0 @@ -""" -Tested with coverage 2.85 and pygments 1.0 - -TODO: - + 'html-output/*,cover' should be deleted - + credits for coverage - + credits for pygments - + 'Install pygments' after ImportError is to less - + is the way of determining DIR_CSS_RESOURCE ok? - + write plugin test - + '.coverage' still exists in py.test execution dir -""" - -import os -import sys -import re -import shutil -from StringIO import StringIO - -import py - -try: - from pygments import highlight - from pygments.lexers import get_lexer_by_name - from pygments.formatters import HtmlFormatter -except ImportError: - print "Install pygments" # XXX - sys.exit(0) - - -DIR_CUR = str(py.path.local()) -REPORT_FILE = os.path.join(DIR_CUR, '.coverage') -DIR_ANNOTATE_OUTPUT = os.path.join(DIR_CUR, '.coverage_annotate') -COVERAGE_MODULES = set() -# coverage output parsing -REG_COVERAGE_SUMMARY = re.compile('([a-z_\.]+) +([0-9]+) +([0-9]+) +([0-9]+%)') -REG_COVERAGE_SUMMARY_TOTAL = re.compile('(TOTAL) +([0-9]+) +([0-9]+) +([0-9]+%)') -DEFAULT_COVERAGE_OUTPUT = '.coverage_annotation' -# HTML output specific -DIR_CSS_RESOURCE = os.path.dirname(__import__('pytest_coverage').__file__) -CSS_RESOURCE_FILES = ['header_bg.jpg', 'links.gif'] - -COVERAGE_TERM_HEADER = "\nCOVERAGE INFORMATION\n" \ - "====================\n" -HTML_INDEX_HEADER = ''' - - - py.test - Coverage Index - - - - - - Module Coverage - - - - Module - Statements - Executed - Coverage - - ''' -HTML_INDEX_FOOTER = ''' - - - ''' - - -class CoverageHtmlFormatter(HtmlFormatter): - """XXX: doc""" - - def __init__(self, *args, **kwargs): - HtmlFormatter.__init__(self,*args, **kwargs) - self.annotation_infos = kwargs.get('annotation_infos') - - def _highlight_lines(self, tokensource): - """ - XXX: doc - """ - - hls = self.hl_lines - self.annotation_infos = [None] + self.annotation_infos - hls = [l for l, i in enumerate(self.annotation_infos) if i] - for i, (t, value) in enumerate(tokensource): - if t != 1: - yield t, value - if i + 1 in hls: # i + 1 because Python indexes start at 0 - if self.annotation_infos[i+1] == "!": - yield 1, '%s' \ - % value - elif self.annotation_infos[i+1] == ">": - yield 1, '%s' \ - % value - else: - raise ValueError("HHAHA: %s" % self.annotation_infos[i+1]) - else: - yield 1, value - - -def _rename_annotation_files(module_list, dir_annotate_output): - for m in module_list: - mod_fpath = os.path.basename(m.__file__) - if mod_fpath.endswith('pyc'): - mod_fpath = mod_fpath[:-1] - old = os.path.join(dir_annotate_output, '%s,cover'% mod_fpath) - new = os.path.join(dir_annotate_output, '%s,cover'% m.__name__) - if os.path.isfile(old): - shutil.move(old, new) - yield new - -def _generate_module_coverage(mc_path, anotation_infos, src_lines): - #XXX: doc - - code = "".join(src_lines) - mc_path = "%s.html" % mc_path - lexer = get_lexer_by_name("python", stripall=True) - formatter = CoverageHtmlFormatter(linenos=True, noclasses=True, - hl_lines=[1], annotation_infos=anotation_infos) - result = highlight(code, lexer, formatter) - fp = open(mc_path, 'w') - fp.write(result) - fp.close() - -def _parse_modulecoverage(mc_fpath): - #XXX: doc - - fd = open(mc_fpath, 'r') - anotate_infos = [] - src_lines = [] - for line in fd.readlines(): - anotate_info = line[0:2].strip() - if not anotate_info: - anotate_info = None - src_line = line[2:] - anotate_infos.append(anotate_info) - src_lines.append(src_line) - return mc_fpath, anotate_infos, src_lines - -def _parse_coverage_summary(fd): - """Parses coverage summary output.""" - - if hasattr(fd, 'readlines'): - fd.seek(0) - for l in fd.readlines(): - m = REG_COVERAGE_SUMMARY.match(l) - if m: - # yield name, stmts, execs, cover - yield m.group(1), m.group(2), m.group(3), m.group(4) - else: - m = REG_COVERAGE_SUMMARY_TOTAL.match(l) - if m: - # yield name, stmts, execs, cover - yield m.group(1), m.group(2), m.group(3), m.group(4) - - -def _get_coverage_index(mod_name, stmts, execs, cover, annotation_dir): - """ - Generates the index page where are all modulare coverage reports are - linked. - """ - - if mod_name == 'TOTAL': - return '%s%s%s%s\n' % (mod_name, stmts, execs, cover) - covrep_fpath = os.path.join(annotation_dir, '%s,cover.html' % mod_name) - assert os.path.isfile(covrep_fpath) == True - fname = os.path.basename(covrep_fpath) - modlink = '%s' % (fname, mod_name) - return '%s%s%s%s\n' % (modlink, stmts, execs, cover) - - -class CoveragePlugin: - def pytest_addoption(self, parser): - group = parser.addgroup('coverage options') - group.addoption('-C', action='store_true', default=False, - dest = 'coverage', - help=('displays coverage information.')) - group.addoption('--coverage-html', action='store', default=False, - dest='coverage_annotation', - help='path to the coverage HTML output dir.') - group.addoption('--coverage-css-resourcesdir', action='store', - default=DIR_CSS_RESOURCE, - dest='coverage_css_ressourcedir', - help='path to dir with css-resources (%s) for ' - 'being copied to the HTML output dir.' % \ - ", ".join(CSS_RESOURCE_FILES)) - - def pytest_configure(self, config): - if config.getvalue('coverage'): - try: - import coverage - except ImportError: - raise config.Error("To run use the coverage option you have to install " \ - "Ned Batchelder's coverage: "\ - "http://nedbatchelder.com/code/modules/coverage.html") - self.coverage = coverage - self.summary = None - - def pytest_terminal_summary(self, terminalreporter): - if hasattr(self, 'coverage'): - self.coverage.stop() - module_list = [sys.modules[mod] for mod in COVERAGE_MODULES] - module_list.sort() - summary_fd = StringIO() - # get coverage reports by module list - self.coverage.report(module_list, file=summary_fd) - summary = COVERAGE_TERM_HEADER + summary_fd.getvalue() - terminalreporter._tw.write(summary) - - config = terminalreporter.config - dir_annotate_output = config.getvalue('coverage_annotation') - if dir_annotate_output: - if dir_annotate_output == "": - dir_annotate_output = DIR_ANNOTATE_OUTPUT - # create dir - if os.path.isdir(dir_annotate_output): - shutil.rmtree(dir_annotate_output) - os.mkdir(dir_annotate_output) - # generate annotation text files for later parsing - self.coverage.annotate(module_list, dir_annotate_output) - # generate the separate module coverage reports - for mc_fpath in _rename_annotation_files(module_list, \ - dir_annotate_output): - # mc_fpath, anotate_infos, src_lines from _parse_do - _generate_module_coverage(*_parse_modulecoverage(mc_fpath)) - # creating contents for the index pagee for coverage report - idxpage_html = StringIO() - idxpage_html.write(HTML_INDEX_HEADER) - total_sum = None - for args in _parse_coverage_summary(summary_fd): - # mod_name, stmts, execs, cover = args - idxpage_html.write(_get_coverage_index(*args, \ - **dict(annotation_dir=dir_annotate_output))) - idxpage_html.write(HTML_INDEX_FOOTER) - idx_fpath = os.path.join(dir_annotate_output, 'index.html') - idx_fd = open(idx_fpath, 'w') - idx_fd.write(idxpage_html.getvalue()) - idx_fd.close() - - dir_css_resource_dir = config.getvalue('coverage_css_ressourcedir') - if dir_annotate_output and dir_css_resource_dir != "": - if not os.path.isdir(dir_css_resource_dir): - raise config.Error("CSS resource dir not found: '%s'" % \ - dir_css_resource_dir) - for r in CSS_RESOURCE_FILES: - src = os.path.join(dir_css_resource_dir, r) - if os.path.isfile(src): - dest = os.path.join(dir_annotate_output, r) - shutil.copy(src, dest) - - def pytest_collectstart(self, collector): - if isinstance(collector, py.__.test.pycollect.Module): - COVERAGE_MODULES.update(getattr(collector.obj, - 'COVERAGE_MODULES', [])) - - def pytest_testrunstart(self): - print "self.coverage", self.coverage - if hasattr(self, 'coverage'): - print "START coverage" - self.coverage.erase() - self.coverage.start() - - diff --git a/contrib/pytest_coverage/header_bg.jpg b/contrib/pytest_coverage/header_bg.jpg deleted file mode 100644 index ba3513468..000000000 Binary files a/contrib/pytest_coverage/header_bg.jpg and /dev/null differ diff --git a/contrib/pytest_coverage/links.gif b/contrib/pytest_coverage/links.gif deleted file mode 100644 index e160f23b6..000000000 Binary files a/contrib/pytest_coverage/links.gif and /dev/null differ diff --git a/doc/announce/release-1.1.0.txt b/doc/announce/release-1.1.0.txt new file mode 100644 index 000000000..01a0b7b5a --- /dev/null +++ b/doc/announce/release-1.1.0.txt @@ -0,0 +1,115 @@ +py.test/pylib 1.1.0: Python3, Jython, advanced skipping, cleanups ... +-------------------------------------------------------------------------------- + +Features: + +* compatible to Python3 (single py2/py3 source), works with Distribute +* generalized marking_: mark tests one a whole-class or whole-module basis +* conditional skipping_: skip/xfail based on platform/dependencies + +Fixes: + +* code reduction and "de-magification" (e.g. 23 KLoc -> 11 KLOC) +* distribute testing requires the now separately released 'execnet' package +* funcarg-setup/caching, "same-name" test modules now cause an exlicit error +* de-cluttered reporting, --report option for skipped/xfail details + +Compatibilities + +1.1.0 should allow running test code that already worked well with 1.0.2 +plus some more due to improved unittest/nose compatibility. + +More information: + + http://pytest.org + +thanks and have fun, + +holger (http://twitter.com/hpk42) + +.. _marking: ../test/plugin/mark.html +.. _skipping: ../test/plugin/skipping.html + + +Changelog 1.0.2 -> 1.1.0 +----------------------------------------------------------------------- + +* remove py.rest tool and internal namespace - it was + never really advertised and can still be used with + the old release if needed. If there is interest + it could be revived into its own tool i guess. + +* fix issue48 and issue59: raise an Error if the module + from an imported test file does not seem to come from + the filepath - avoids "same-name" confusion that has + been reported repeatedly + +* merged Ronny's nose-compatibility hacks: now + nose-style setup_module() and setup() functions are + supported + +* introduce generalized py.test.mark function marking + +* reshuffle / refine command line grouping + +* deprecate parser.addgroup in favour of getgroup which creates option group + +* add --report command line option that allows to control showing of skipped/xfailed sections + +* generalized skipping: a new way to mark python functions with skipif or xfail + at function, class and modules level based on platform or sys-module attributes. + +* extend py.test.mark decorator to allow for positional args + +* introduce and test "py.cleanup -d" to remove empty directories + +* fix issue #59 - robustify unittest test collection + +* make bpython/help interaction work by adding an __all__ attribute + to ApiModule, cleanup initpkg + +* use MIT license for pylib, add some contributors + +* remove py.execnet code and substitute all usages with 'execnet' proper + +* fix issue50 - cached_setup now caches more to expectations + for test functions with multiple arguments. + +* merge Jarko's fixes, issue #45 and #46 + +* add the ability to specify a path for py.lookup to search in + +* fix a funcarg cached_setup bug probably only occuring + in distributed testing and "module" scope with teardown. + +* many fixes and changes for making the code base python3 compatible, + many thanks to Benjamin Peterson for helping with this. + +* consolidate builtins implementation to be compatible with >=2.3, + add helpers to ease keeping 2 and 3k compatible code + +* deprecate py.compat.doctest|subprocess|textwrap|optparse + +* deprecate py.magic.autopath, remove py/magic directory + +* move pytest assertion handling to py/code and a pytest_assertion + plugin, add "--no-assert" option, deprecate py.magic namespaces + in favour of (less) py.code ones. + +* consolidate and cleanup py/code classes and files + +* cleanup py/misc, move tests to bin-for-dist + +* introduce delattr/delitem/delenv methods to py.test's monkeypatch funcarg + +* consolidate py.log implementation, remove old approach. + +* introduce py.io.TextIO and py.io.BytesIO for distinguishing between + text/unicode and byte-streams (uses underlying standard lib io.* + if available) + +* make py.unittest_convert helper script available which converts "unittest.py" + style files into the simpler assert/direct-test-classes py.test/nosetests + style. The script was written by Laura Creighton. + +* simplified internal localpath implementation diff --git a/doc/announce/releases.txt b/doc/announce/releases.txt index 16ba55118..309c29bac 100644 --- a/doc/announce/releases.txt +++ b/doc/announce/releases.txt @@ -5,10 +5,12 @@ Release notes Contents: .. toctree:: - :maxdepth: 1 + :maxdepth: 2 - announce/release-1.0.2 - announce/release-1.0.1 - announce/release-1.0.0 - announce/release-0.9.2 - announce/release-0.9.0 +.. include: release-1.1.0 +.. include: release-1.0.2 + + release-1.0.1 + release-1.0.0 + release-0.9.2 + release-0.9.0 diff --git a/doc/changelog.txt b/doc/changelog.txt index 4813547fe..fa456042f 100644 --- a/doc/changelog.txt +++ b/doc/changelog.txt @@ -1,6 +1,8 @@ -Changes between 1.0.2 and '1.1.0b1' +Changes between 1.1.0 and 1.0.2 ===================================== +* adjust and improve docs + * remove py.rest tool and internal namespace - it was never really advertised and can still be used with the old release if needed. If there is interest @@ -49,6 +51,9 @@ Changes between 1.0.2 and '1.1.0b1' * fix a funcarg cached_setup bug probably only occuring in distributed testing and "module" scope with teardown. +* many fixes and changes for making the code base python3 compatible, + many thanks to Benjamin Peterson for helping with this. + * consolidate builtins implementation to be compatible with >=2.3, add helpers to ease keeping 2 and 3k compatible code diff --git a/doc/code.txt b/doc/code.txt index 61c3e5593..1fd1eaef7 100644 --- a/doc/code.txt +++ b/doc/code.txt @@ -18,7 +18,7 @@ Contents of the library Every object in the ``py.code`` library wraps a code Python object related to code objects, source code, frames and tracebacks: the ``py.code.Code`` class wraps code objects, ``py.code.Source`` source snippets, -``py.code.Traceback` exception tracebacks, :api:`py.code.Frame`` frame +``py.code.Traceback` exception tracebacks, ``py.code.Frame`` frame objects (as found in e.g. tracebacks) and ``py.code.ExceptionInfo`` the tuple provided by sys.exc_info() (containing exception and traceback information when an exception occurs). Also in the library is a helper function diff --git a/doc/confrest.py b/doc/confrest.py index 6fae19128..953840041 100644 --- a/doc/confrest.py +++ b/doc/confrest.py @@ -1,6 +1,6 @@ import py -from _py.test.plugin.pytest_restdoc import convert_rest_html, strip_html_header +from py.plugin.pytest_restdoc import convert_rest_html, strip_html_header html = py.xml.html @@ -57,23 +57,23 @@ pageTracker._trackPageview(); def fill_menubar(self): items = [ - self.a_docref("install", "install.html"), - self.a_docref("contact", "contact.html"), - self.a_docref("changelog", "changelog.html"), - self.a_docref("faq", "faq.html"), + self.a_docref("INSTALL", "install.html"), + self.a_docref("CONTACT", "contact.html"), + self.a_docref("CHANGELOG", "changelog.html"), + self.a_docref("FAQ", "faq.html"), html.div( html.h3("py.test:"), - self.a_docref("doc index", "test/index.html"), - self.a_docref("features", "test/features.html"), - self.a_docref("quickstart", "test/quickstart.html"), - self.a_docref("tutorials", "test/talks.html"), - self.a_docref("plugins", "test/plugin/index.html"), - self.a_docref("funcargs", "test/funcargs.html"), - self.a_docref("customize", "test/customize.html"), + self.a_docref("Index", "test/index.html"), + self.a_docref("Quickstart", "test/quickstart.html"), + self.a_docref("Features", "test/features.html"), + self.a_docref("Plugins", "test/plugin/index.html"), + self.a_docref("Funcargs", "test/funcargs.html"), + self.a_docref("Customize", "test/customize.html"), + self.a_docref("Tutorials", "test/talks.html"), ), html.div( html.h3("supporting APIs:"), - self.a_docref("pylib index", "index.html"), + self.a_docref("Index", "index.html"), self.a_docref("py.path", "path.html"), self.a_docref("py.code", "code.html"), ) @@ -85,9 +85,10 @@ pageTracker._trackPageview(); self.menubar = html.div(id=css.menubar, *[ html.div(item) for item in items]) version = py.version + announcelink = self.a_docref("%s ANN" % version, + "announce/release-%s.html" %(version,)) self.menubar.insert(0, - html.div("%s" % (py.version), style="font-style: italic;") - ) + html.div(announcelink)) #self.a_href("%s-%s" % (self.title, py.version), # "http://pypi.python.org/pypi/py/%s" % version, #id="versioninfo", diff --git a/doc/faq.txt b/doc/faq.txt index 633ddf756..7705cad6b 100644 --- a/doc/faq.txt +++ b/doc/faq.txt @@ -13,75 +13,79 @@ On naming, nosetests, licensing and magic Why the ``py`` naming? what is it? ------------------------------------ -Because the name was kind of available and there was the +Because the name was available and there was the idea to have the package evolve into a "standard" library kind of thing that works cross-python versions and is not tied to a particular CPython revision or its release cycle. Clearly, this was ambitious and the naming has maybe haunted the project rather than helping it. -There may be a project name change and possibly a -split up into different projects sometime. Why the ``py.test`` naming? ------------------------------------ -the py lib contains other command line tools that -all share the ``py.`` prefix which makes it easy -to use TAB-completion on the shell. Another motivation -was to make it obvious where testing functionality -for the ``py.test`` command line tool is: in the -``py.test`` package name space. +because of TAB-completion under Bash/Shells. If you hit +``py.`` you'll get a list of available development +tools that all share the ``py.`` prefix. Another motivation +was to unify the package ("py.test") and tool filename. What's py.test's relation to ``nosetests``? --------------------------------------------- py.test and nose_ share basic philosophy when it comes to running Python tests. In fact, -with py.test-1.0.1 it is easy to run many test suites +with py.test-1.1.0 it is ever easier to run many test suites that currently work with ``nosetests``. nose_ was created -as a clone of ``py.test`` when it was in the ``0.8`` release +as a clone of ``py.test`` when py.test was in the ``0.8`` release cycle so some of the newer features_ introduced with py.test-1.0 -have no counterpart in nose_. +and py.test-1.1 have no counterpart in nose_. .. _nose: http://somethingaboutorange.com/mrl/projects/nose/0.11.1/ .. _features: test/features.html +.. _apipkg: http://pypi.python.org/pypi/apipkg -What's all this "magic" with py.test? + +What's this "magic" with py.test? ---------------------------------------- -"All this magic" usually boils down to two issues: +issues where people have used the term "magic" in the past: -* There is a special tweak to importing: `py/__init__.py`_ contains - a dictionary which maps the importable ``py.*`` namespaces to - objects in files. When looking at the project source code - you see imports like ``from py.__.test.session import Session``. The - the double ``__`` underscore indicates the "normal" python - filesystem/namespace coupled import, i.e. it points to - ``py/test/session.py``'s ``Session`` object. However, - from the outside you use the "non-underscore" `py namespaces`_ - so this distinction usually only shows up if you hack - on internal code or see internal tracebacks. +* `py/__init__.py`_ uses the apipkg_ mechanism for lazy-importing + and full control on what API you get when importing "import py". -* when an ``assert`` fails, py.test re-interprets the expression - to show intermediate values. This allows to use the plain ``assert`` - statement instead of the many methods that you otherwise need - to mimick this behaviour. This means that in case of a failing - assert, your expressions gets evaluated *twice*. If your expression - has side effects the outcome may be different. If the test suddenly - passes you will get a detailed message. It is good practise, anyway, - to not have asserts with side effects. ``py.test --nomagic`` turns - off assert re-intepretation. +* when an ``assert`` statement fails, py.test re-interprets the expression + to show intermediate values if a test fails. If your expression + has side effects the intermediate values may not be the same, obfuscating + the initial error (this is also explained at the command line if it happens). + ``py.test --no-assert`` turns off assert re-intepretation. + Sidenote: it is good practise to avoid asserts with side effects. -Other than that, ``py.test`` has bugs or quirks like any other computer -software. In fact, it has a *strong* focus on running robustly and has -over a thousand automated tests for its own code base. .. _`py namespaces`: index.html -.. _`py/__init__.py`: http://bitbucket.org/hpk42/py-trunk/src/1.0.x/py/__init__.py +.. _`py/__init__.py`: http://bitbucket.org/hpk42/py-trunk/src/trunk/py/__init__.py -function arguments and parametrized tests -=============================================== +function arguments, parametrized tests and setup +==================================================== + +.. _funcargs: test/funcargs.html + +Is using funcarg- versus xUnit-based setup a style question? +--------------------------------------------------------------- + +It depends. For simple applications or for people experienced +with nose_ or unittest-style test setup using `xUnit style setup`_ +make some sense. For larger test suites, parametrized testing +or setup of complex test resources using funcargs_ is recommended. +Moreover, funcargs are ideal for writing advanced test support +code (like e.g. the monkeypatch_, the tmpdir_ or capture_ funcargs) +because the support code can register setup/teardown functions +in a managed class/module/function scope. + +.. _monkeypatch: test/plugin/monkeypatch.html +.. _tmpdir: test/plugin/tmpdir.html +.. _capture: test/plugin/capture.html +.. _`xUnit style setup`: test/xunit_setup.html +.. _`pytest_nose`: test/plugin/nose.html .. _`why pytest_pyfuncarg__ methods?`: @@ -94,7 +98,7 @@ flexibility we decided to go for `Convention over Configuration`_ and allow to directly specify the factory. Besides removing the need for an indirection it allows to "grep" for ``pytest_funcarg__MYARG`` and will safely find all factory functions for the ``MYARG`` function -argument. It helps to alleviates the de-coupling of function +argument. It helps to alleviate the de-coupling of function argument usage and creation. .. _`Convention over Configuration`: http://en.wikipedia.org/wiki/Convention_over_Configuration diff --git a/doc/index.txt b/doc/index.txt index 46f720a5f..9336b447c 100644 --- a/doc/index.txt +++ b/doc/index.txt @@ -29,7 +29,6 @@ Other (minor) support functionality For the latest Release, see `PyPI project page`_ -.. _`download and installation`: download.html .. _`py-dev at codespeak net`: http://codespeak.net/mailman/listinfo/py-dev .. _`py.log`: log.html .. _`py.io`: io.html diff --git a/doc/install.txt b/doc/install.txt index 8795c332d..8ec1d591a 100644 --- a/doc/install.txt +++ b/doc/install.txt @@ -25,7 +25,7 @@ on Windows you might need to write down the full path to ``easy_install``. The py lib and its tools are expected to work well on Linux, Windows and OSX, Python versions 2.4, 2.5, 2.6 through to -the Python3 versions 3.0 and 3.1. Jython +the Python3 versions 3.0 and 3.1 and Jython .. _mercurial: http://mercurial.selenic.com/wiki/ .. _`Distribute`: @@ -43,15 +43,13 @@ and documentation source with mercurial_:: hg clone https://bitbucket.org/hpk42/py-trunk/ -This currrently contains a 1.0.x branch and the -default 'trunk' branch where mainline development -takes place. +Development usually takes place on the 'trunk' branch. .. There also is a readonly subversion checkout available which contains the latest release:: svn co https://codespeak.net/svn/py/dist -You can go to the python package index and +You can also go to the python package index and download and unpack a TAR file:: http://pypi.python.org/pypi/py/ @@ -64,7 +62,7 @@ With a working `Distribute`_ or setuptools_ installation you can type:: python setup.py develop -in order to work with the tools and the lib of your checkout. +in order to work inline with the tools and the lib of your checkout. .. _`no-setuptools`: diff --git a/doc/path.txt b/doc/path.txt index 836317e46..623cd187d 100644 --- a/doc/path.txt +++ b/doc/path.txt @@ -40,7 +40,7 @@ a ``py.path.local`` object for us (which wraps a directory): >>> foofile.read(1) 'b' -``py.path.svnurl` and :api:`py.path.svnwc`` +``py.path.svnurl` and ``py.path.svnwc`` ---------------------------------------------- Two other ``py.path`` implementations that the py lib provides wrap the diff --git a/doc/test/dist.txt b/doc/test/dist.txt index 6b44cd69b..8bc83aa7f 100644 --- a/doc/test/dist.txt +++ b/doc/test/dist.txt @@ -14,6 +14,8 @@ specify different Python versions and interpreters. **Requirements**: you need to install the `execnet`_ package to perform distributed test runs. +**NOTE**: Version 1.1.0 is not able to distribute tests across Python3/Python2 barriers. + Speed up test runs by sending tests to multiple CPUs ---------------------------------------------------------- diff --git a/doc/test/features.txt b/doc/test/features.txt index 466d515fe..da72fb7ac 100644 --- a/doc/test/features.txt +++ b/doc/test/features.txt @@ -26,7 +26,7 @@ naming patterns. As ``py.test`` operates as a separate cmdline tool you can easily have a command line utility and some tests in the same file. -supports many testing practises and methods +supports several testing practises and methods ================================================================== py.test supports many testing methods conventionally used in diff --git a/doc/test/funcargs.txt b/doc/test/funcargs.txt index 82e993d14..407162c57 100644 --- a/doc/test/funcargs.txt +++ b/doc/test/funcargs.txt @@ -1,43 +1,301 @@ -========================================================== -**funcargs**: test function arguments FTW -========================================================== +============================================================== +**funcargs**: advanced test setup and parametrization +============================================================== .. contents:: :local: :depth: 2 -Goals of the "funcarg" mechanism -========================================== +what are "funcargs" and what are they good for? +================================================= -Since version 1.0 py.test features the "funcarg" mechanism which -allows a Python test function to take arguments independently provided -by factory functions. Factory functions allow to encapsulate -all setup and fixture glue code into nicely separated objects -and provide a natural way for writing python test functions. -Compared to `xUnit style`_ the new mechanism is meant to: - -* make test functions easier to write and to read -* isolate test fixture creation to a single place -* bring new flexibility and power to test state management -* naturally extend towards parametrizing test functions - with multiple argument sets -* enable creation of zero-boilerplate test helper objects that - interact with the execution of a test function, see the - `blog post about the monkeypatch funcarg`_. - -If you find issues or have further suggestions for improving -the mechanism you are welcome to checkout `contact possibilities`_ page. +Named parameters of a test function are called *funcargs* for short. +A Funcarg can be a simple number of a complex object. To perform a +test function call each parameter is setup by a factory function. +To call a test function repeatedly with different funcargs sets +test parameters can be generated. .. _`contact possibilities`: ../contact.html +.. _`parametrizing tests, generalized`: http://tetamap.wordpress.com/2009/05/13/parametrizing-python-tests-generalized/ .. _`blog post about the monkeypatch funcarg`: http://tetamap.wordpress.com/2009/03/03/monkeypatching-in-unit-tests-done-right/ .. _`xUnit style`: xunit_setup.html + +.. _`funcarg factory`: +.. _factory: + +funcarg factories: setting up test function arguments +============================================================== + +Test functions can specify one ore more arguments ("funcargs") +and a test module or plugin can define factory functions that provide +the function argument. Let's look at a simple self-contained +example that you can put into a test module: + +.. sourcecode:: python + + # ./test_simplefactory.py + def pytest_funcarg__myfuncarg(request): + return 42 + + def test_function(myfuncarg): + assert myfuncarg == 17 + +If you run this with ``py.test test_simplefactory.py`` you see something like this: + +.. sourcecode:: python + + =========================== test session starts ============================ + python: platform linux2 -- Python 2.6.2 + test object 1: /home/hpk/hg/py/trunk/example/funcarg/test_simplefactory.py + + test_simplefactory.py F + + ================================ FAILURES ================================== + ______________________________ test_function _______________________________ + + myfuncarg = 42 + + def test_function(myfuncarg): + > assert myfuncarg == 17 + E assert 42 == 17 + + test_simplefactory.py:6: AssertionError + ======================== 1 failed in 0.11 seconds ========================== + + +This means that the test function was called with a ``myfuncarg`` value +of ``42`` and the assert fails accordingly. Here is how py.test +calls the test function: + +1. py.test discovers the ``test_function`` because of the ``test_`` prefix. + The test function needs a function argument named ``myfuncarg``. + A matching factory function is discovered by looking for the + name ``pytest_funcarg__myfuncarg``. + +2. ``pytest_funcarg__myfuncarg(request)`` is called and + returns the value for ``myfuncarg``. + +3. ``test_function(42)`` call is executed. + +Note that if you misspell a function argument or want +to use one that isn't available, you'll see an error +with a list of available function arguments. + +factory functions receive a `request object`_ +which they can use to register setup/teardown +functions or access meta data about a test. + +.. _`request object`: + +funcarg factory request objects +------------------------------------------ + +Request objects are passed to funcarg factories and allow +to access test configuration, test context and `useful caching +and finalization helpers`_. Here is a list of attributes: + +``request.function``: python function object requesting the argument + +``request.cls``: class object where the test function is defined in or None. + +``request.module``: module object where the test function is defined in. + +``request.config``: access to command line opts and general config + +``request.param``: if exists was passed by a previous `metafunc.addcall`_ + +.. _`useful caching and finalization helpers`: + + +registering funcarg related finalizers/cleanup +---------------------------------------------------- + +.. sourcecode:: python + + def addfinalizer(func): + """ call a finalizer function when test function finishes. """ + +Calling ``request.addfinalizer()`` is useful for scheduling teardown +functions. Here is an example for providing a ``myfile`` +object that is to be closed when the execution of a +test function finishes. + +.. sourcecode:: python + + def pytest_funcarg__myfile(self, request): + # ... create and open a unique per-function "myfile" object ... + request.addfinalizer(lambda: myfile.close()) + return myfile + + +managing fixtures across test modules and test runs +---------------------------------------------------------- + +.. sourcecode:: python + + def cached_setup(setup, teardown=None, scope="module", extrakey=None): + """ cache and return result of calling setup(). + + The requested argument name, the scope and the ``extrakey`` + determine the cache key. The scope also determines when + teardown(result) will be called. valid scopes are: + scope == 'function': when the single test function run finishes. + scope == 'module': when tests in a different module are run + scope == 'session': when tests of the session have run. + """ + +Calling ``request.cached_setup()`` helps you to manage fixture +objects across several scopes. For example, for creating a Database object +that is to be setup only once during a test session you can use the helper +like this: + +.. sourcecode:: python + + def pytest_funcarg__database(request): + return request.cached_setup( + setup=lambda: Database("..."), + teardown=lambda val: val.close(), + scope="session" + ) + + +requesting values of other funcargs +--------------------------------------------- + +.. sourcecode:: python + + def getfuncargvalue(name): + """ Lookup and call function argument factory for the given name. + Each function argument is only created once per function setup. + """ + +``request.getfuncargvalue(name)`` calls another funcarg factory function. +You can use this function if you want to `decorate a funcarg`_, i.e. +you want to provide the "normal" value but add something +extra. If a factory cannot be found a ``request.Error`` +exception will be raised. + +.. _`test generators`: +.. _`parametrizing-tests`: + +generating parametrized tests +=========================================================== + +You can parametrize multiple runs of the same test +function by adding new test function calls with different +function argument values. Let's look at a simple self-contained +example: + +.. sourcecode:: python + + # ./test_example.py + def pytest_generate_tests(metafunc): + if "numiter" in metafunc.funcargnames: + for i in range(10): + metafunc.addcall(funcargs=dict(numiter=i)) + + def test_func(numiter): + assert numiter < 9 + +If you run this with ``py.test test_example.py`` you'll get: + +.. sourcecode:: python + + ============================= test session starts ========================== + python: platform linux2 -- Python 2.6.2 + test object 1: /home/hpk/hg/py/trunk/test_example.py + + test_example.py .........F + + ================================ FAILURES ================================== + __________________________ test_func.test_func[9] __________________________ + + numiter = 9 + + def test_func(numiter): + > assert numiter < 9 + E assert 9 < 9 + + /home/hpk/hg/py/trunk/test_example.py:10: AssertionError + + +Here is what happens in detail: + +1. ``pytest_generate_tests(metafunc)`` hook is called once for each test + function. It adds ten new function calls with explicit function arguments. + +2. **execute tests**: ``test_func(numiter)`` is called ten times with + ten different arguments. + +.. _`metafunc object`: + +test generators and metafunc objects +------------------------------------------- + +metafunc objects are passed to the ``pytest_generate_tests`` hook. +They help to inspect a testfunction and to generate tests +according to test configuration or values specified +in the class or module where a test function is defined: + +``metafunc.funcargnames``: set of required function arguments for given function + +``metafunc.function``: underlying python test function + +``metafunc.cls``: class object where the test function is defined in or None. + +``metafunc.module``: the module object where the test function is defined in. + +``metafunc.config``: access to command line opts and general config + + +.. _`metafunc.addcall`: + +the ``metafunc.addcall()`` method +----------------------------------------------- + +.. sourcecode:: python + + def addcall(funcargs={}, id=None, param=None): + """ trigger a new test function call. """ + +``funcargs`` can be a dictionary of argument names +mapped to values - providing it is called *direct parametrization*. + +If you provide an `id`` it will be used for reporting +and identification purposes. If you don't supply an `id` +the stringified counter of the list of added calls will be used. +``id`` values needs to be unique between all +invocations for a given test function. + +``param`` if specified will be seen by any +`funcarg factory`_ as a ``request.param`` attribute. +Setting it is called *indirect parametrization*. + +Indirect parametrization is preferable if test values are +expensive to setup or can only be created in certain environments. +Test generators and thus ``addcall()`` invocations are performed +during test collection which is separate from the actual test +setup and test run phase. With distributed testing collection +and test setup/run happens in different process. + + + .. _`tutorial examples`: Tutorial Examples ======================================= +To see how you can implement custom paramtrization schemes, +see e.g. `parametrizing tests, generalized`_ (blog post). + +To enable creation of test support code that can flexibly +register setup/teardown functions see the `blog post about +the monkeypatch funcarg`_. + +If you find issues or have further suggestions for improving +the mechanism you are welcome to checkout `contact possibilities`_ page. .. _`application setup tutorial example`: .. _appsetup: @@ -274,262 +532,3 @@ methods in a convenient way. .. _`py.path.local`: ../path.html#local .. _`conftest plugin`: customize.html#conftestplugin - -.. _`funcarg factory`: -.. _factory: - -funcarg factories: setting up test function arguments -============================================================== - -Test functions can specify one ore more arguments ("funcargs") -and a test module or plugin can define functions that provide -the function argument. Let's look at a simple self-contained -example that you can put into a test module: - -.. sourcecode:: python - - # ./test_simplefactory.py - def pytest_funcarg__myfuncarg(request): - return 42 - - def test_function(myfuncarg): - assert myfuncarg == 17 - -If you run this with ``py.test test_simplefactory.py`` you see something like this: - -.. sourcecode:: python - - =========================== test session starts ============================ - python: platform linux2 -- Python 2.6.2 - test object 1: /home/hpk/hg/py/trunk/example/funcarg/test_simplefactory.py - - test_simplefactory.py F - - ================================ FAILURES ================================== - ______________________________ test_function _______________________________ - - myfuncarg = 42 - - def test_function(myfuncarg): - > assert myfuncarg == 17 - E assert 42 == 17 - - test_simplefactory.py:6: AssertionError - ======================== 1 failed in 0.11 seconds ========================== - - -This means that the test function got executed and the assertion failed. -Here is how py.test comes to execute this test function: - -1. py.test discovers the ``test_function`` because of the ``test_`` prefix. - The test function needs a function argument named ``myfuncarg``. - A matching factory function is discovered by looking for the special - name ``pytest_funcarg__myfuncarg``. - -2. ``pytest_funcarg__myfuncarg(request)`` is called and - returns the value for ``myfuncarg``. - -3. ``test_function(42)`` call is executed. - -Note that if you misspell a function argument or want -to use one that isn't available, an error with a list of -available function argument is provided. - -For more interesting factory functions that make good use of the -`request object`_ please see the `application setup tutorial example`_. - -.. _`request object`: - -funcarg factory request objects ------------------------------------------- - -Request objects are passed to funcarg factories and allow -to access test configuration, test context and `useful caching -and finalization helpers`_. Here is a list of attributes: - -``request.function``: python function object requesting the argument - -``request.cls``: class object where the test function is defined in or None. - -``request.module``: module object where the test function is defined in. - -``request.config``: access to command line opts and general config - -``request.param``: if exists was passed by a previous `metafunc.addcall`_ - -.. _`useful caching and finalization helpers`: - - -registering funcarg related finalizers/cleanup ----------------------------------------------------- - -.. sourcecode:: python - - def addfinalizer(func): - """ call a finalizer function when test function finishes. """ - -Calling ``request.addfinalizer()`` is useful for scheduling teardown -functions. Here is an example for providing a ``myfile`` -object that is to be closed when the execution of a -test function finishes. - -.. sourcecode:: python - - def pytest_funcarg__myfile(self, request): - # ... create and open a unique per-function "myfile" object ... - request.addfinalizer(lambda: myfile.close()) - return myfile - - -managing fixtures across test modules and test runs ----------------------------------------------------------- - -.. sourcecode:: python - - def cached_setup(setup, teardown=None, scope="module", extrakey=None): - """ cache and return result of calling setup(). - - The requested argument name, the scope and the ``extrakey`` - determine the cache key. The scope also determines when - teardown(result) will be called. valid scopes are: - scope == 'function': when the single test function run finishes. - scope == 'module': when tests in a different module are run - scope == 'session': when tests of the session have run. - """ - -Calling ``request.cached_setup()`` helps you to manage fixture -objects across several scopes. For example, for creating a Database object -that is to be setup only once during a test session you can use the helper -like this: - -.. sourcecode:: python - - def pytest_funcarg__database(request): - return request.cached_setup( - setup=lambda: Database("..."), - teardown=lambda val: val.close(), - scope="session" - ) - - -requesting values of other funcargs ---------------------------------------------- - -.. sourcecode:: python - - def getfuncargvalue(name): - """ Lookup and call function argument factory for the given name. - Each function argument is only created once per function setup. - """ - -``request.getfuncargvalue(name)`` calls another funcarg factory function. -You can use this function if you want to `decorate a funcarg`_, i.e. -you want to provide the "normal" value but add something -extra. If a factory cannot be found a ``request.Error`` -exception will be raised. - -.. _`test generators`: -.. _`parametrizing-tests`: - -generating parametrized tests -=========================================================== - -You can parametrize multiple runs of the same test -function by adding new test function calls with different -function argument values. Let's look at a simple self-contained -example: - -.. sourcecode:: python - - # ./test_example.py - def pytest_generate_tests(metafunc): - if "numiter" in metafunc.funcargnames: - for i in range(10): - metafunc.addcall(funcargs=dict(numiter=i)) - - def test_func(numiter): - assert numiter < 9 - -If you run this with ``py.test test_example.py`` you'll get: - -.. sourcecode:: python - - ============================= test session starts ========================== - python: platform linux2 -- Python 2.6.2 - test object 1: /home/hpk/hg/py/trunk/test_example.py - - test_example.py .........F - - ================================ FAILURES ================================== - __________________________ test_func.test_func[9] __________________________ - - numiter = 9 - - def test_func(numiter): - > assert numiter < 9 - E assert 9 < 9 - - /home/hpk/hg/py/trunk/test_example.py:10: AssertionError - - -Here is what happens in detail: - -1. ``pytest_generate_tests(metafunc)`` hook is called once for each test - function. It adds ten new function calls with explicit function arguments. - -2. **execute tests**: ``test_func(numiter)`` is called ten times with - ten different arguments. - -.. _`metafunc object`: - -test generators and metafunc objects -------------------------------------------- - -metafunc objects are passed to the ``pytest_generate_tests`` hook. -They help to inspect a testfunction and to generate tests -according to test configuration or values specified -in the class or module where a test function is defined: - -``metafunc.funcargnames``: set of required function arguments for given function - -``metafunc.function``: underlying python test function - -``metafunc.cls``: class object where the test function is defined in or None. - -``metafunc.module``: the module object where the test function is defined in. - -``metafunc.config``: access to command line opts and general config - - -.. _`metafunc.addcall`: - -the ``metafunc.addcall()`` method ------------------------------------------------ - -.. sourcecode:: python - - def addcall(funcargs={}, id=None, param=None): - """ trigger a new test function call. """ - -``funcargs`` can be a dictionary of argument names -mapped to values - providing it is called *direct parametrization*. - -If you provide an `id`` it will be used for reporting -and identification purposes. If you don't supply an `id` -the stringified counter of the list of added calls will be used. -``id`` values needs to be unique between all -invocations for a given test function. - -``param`` if specified will be seen by any -`funcarg factory`_ as a ``request.param`` attribute. -Setting it is called *indirect parametrization*. - -Indirect parametrization is preferable if test values are -expensive to setup or can only be created in certain environments. -Test generators and thus ``addcall()`` invocations are performed -during test collection which is separate from the actual test -setup and test run phase. With distributed testing collection -and test setup/run happens in different process. - - - diff --git a/doc/test/plugin/coverage.txt b/doc/test/plugin/coverage.txt new file mode 100644 index 000000000..fcdaa2bb5 --- /dev/null +++ b/doc/test/plugin/coverage.txt @@ -0,0 +1,10 @@ +pytest_xmlresult plugin (EXTERNAL) +========================================== + +This plugin allows to write results in an XML format +compatible to CruiseControl_, see here for download: + + http://github.com/rozza/py.test-plugins + +.. _CruiseControl: http://cruisecontrol.sourceforge.net/ + diff --git a/doc/test/plugin/index.txt b/doc/test/plugin/index.txt index 38978498c..385f927a0 100644 --- a/doc/test/plugin/index.txt +++ b/doc/test/plugin/index.txt @@ -1,63 +1,63 @@ -plugins for Python test functions -================================= +advanced python testing +======================= skipping_ advanced skipping for python test functions, classes or modules. +mark_ generic mechanism for marking python functions. + +pdb_ interactive debugging with the Python Debugger. + figleaf_ write and report coverage data with 'figleaf'. +coverage_ (3rd) for testing with Ned's coverage module + monkeypatch_ safely patch object attributes, dicts and environment variables. capture_ configurable per-test stdout/stderr capturing mechanisms. recwarn_ helpers for asserting deprecation and other warnings. +tmpdir_ provide temporary directories to test functions. -plugins for other testing styles and languages -============================================== -oejskit_ run javascript tests in real life browsers +testing domains +=============== + +oejskit_ (3rd) run javascript tests in real life browsers + +django_ (3rd) for testing django applications + + +reporting and failure logging +============================= + +pastebin_ submit failure or test session information to a pastebin service. + +xmlresult_ (3rd) for generating xml reports and CruiseControl integration + +resultlog_ resultlog plugin for machine-readable logging of test results. + +terminal_ Implements terminal reporting of the full testing process. + + +other testing conventions +========================= unittest_ automatically discover and run traditional "unittest.py" style tests. nose_ nose-compatibility plugin: allow to run nose test suites natively. -django_ support for testing django applications - doctest_ collect and execute doctests from modules and test files. restdoc_ perform ReST syntax, local and remote reference tests on .rst/.txt files. -plugins for generic reporting and failure logging -================================================= - -pastebin_ submit failure or test session information to a pastebin service. - -resultlog_ resultlog plugin for machine-readable logging of test results. - -terminal_ Implements terminal reporting of the full testing process. - - -plugins for generic reporting and failure logging -================================================= - -pastebin_ submit failure or test session information to a pastebin service. - -resultlog_ resultlog plugin for machine-readable logging of test results. - -terminal_ Implements terminal reporting of the full testing process. - - -misc plugins / core functionality -================================= +core debugging / help functionality +=================================== helpconfig_ provide version info, conftest/environment config names. -pdb_ interactive debugging with the Python Debugger. - -mark_ generic mechanism for marking python functions. - hooklog_ log invocations of extension hooks to a file. diff --git a/doc/test/plugin/links.txt b/doc/test/plugin/links.txt index a4bb73a5f..fb75a2b06 100644 --- a/doc/test/plugin/links.txt +++ b/doc/test/plugin/links.txt @@ -1,38 +1,42 @@ .. _`helpconfig`: helpconfig.html .. _`terminal`: terminal.html -.. _`pytest_recwarn.py`: http://bitbucket.org/hpk42/py-trunk/raw/trunk/_py/test/plugin/pytest_recwarn.py +.. _`pytest_recwarn.py`: http://bitbucket.org/hpk42/py-trunk/raw/1.1.0/py/plugin/pytest_recwarn.py .. _`unittest`: unittest.html -.. _`pytest_monkeypatch.py`: http://bitbucket.org/hpk42/py-trunk/raw/trunk/_py/test/plugin/pytest_monkeypatch.py +.. _`pytest_monkeypatch.py`: http://bitbucket.org/hpk42/py-trunk/raw/1.1.0/py/plugin/pytest_monkeypatch.py .. _`pastebin`: pastebin.html .. _`skipping`: skipping.html .. _`plugins`: index.html -.. _`pytest_doctest.py`: http://bitbucket.org/hpk42/py-trunk/raw/trunk/_py/test/plugin/pytest_doctest.py -.. _`capture`: capture.html -.. _`pytest_nose.py`: http://bitbucket.org/hpk42/py-trunk/raw/trunk/_py/test/plugin/pytest_nose.py -.. _`pytest_restdoc.py`: http://bitbucket.org/hpk42/py-trunk/raw/trunk/_py/test/plugin/pytest_restdoc.py -.. _`restdoc`: restdoc.html -.. _`pytest_pastebin.py`: http://bitbucket.org/hpk42/py-trunk/raw/trunk/_py/test/plugin/pytest_pastebin.py .. _`mark`: mark.html -.. _`pytest_figleaf.py`: http://bitbucket.org/hpk42/py-trunk/raw/trunk/_py/test/plugin/pytest_figleaf.py -.. _`pytest_hooklog.py`: http://bitbucket.org/hpk42/py-trunk/raw/trunk/_py/test/plugin/pytest_hooklog.py -.. _`pytest_skipping.py`: http://bitbucket.org/hpk42/py-trunk/raw/trunk/_py/test/plugin/pytest_skipping.py +.. _`tmpdir`: tmpdir.html +.. _`pytest_doctest.py`: http://bitbucket.org/hpk42/py-trunk/raw/1.1.0/py/plugin/pytest_doctest.py +.. _`capture`: capture.html +.. _`pytest_nose.py`: http://bitbucket.org/hpk42/py-trunk/raw/1.1.0/py/plugin/pytest_nose.py +.. _`pytest_restdoc.py`: http://bitbucket.org/hpk42/py-trunk/raw/1.1.0/py/plugin/pytest_restdoc.py +.. _`restdoc`: restdoc.html +.. _`pytest_pastebin.py`: http://bitbucket.org/hpk42/py-trunk/raw/1.1.0/py/plugin/pytest_pastebin.py +.. _`pytest_tmpdir.py`: http://bitbucket.org/hpk42/py-trunk/raw/1.1.0/py/plugin/pytest_tmpdir.py +.. _`pytest_figleaf.py`: http://bitbucket.org/hpk42/py-trunk/raw/1.1.0/py/plugin/pytest_figleaf.py +.. _`pytest_hooklog.py`: http://bitbucket.org/hpk42/py-trunk/raw/1.1.0/py/plugin/pytest_hooklog.py +.. _`pytest_skipping.py`: http://bitbucket.org/hpk42/py-trunk/raw/1.1.0/py/plugin/pytest_skipping.py .. _`checkout the py.test development version`: ../../install.html#checkout -.. _`pytest_helpconfig.py`: http://bitbucket.org/hpk42/py-trunk/raw/trunk/_py/test/plugin/pytest_helpconfig.py +.. _`pytest_helpconfig.py`: http://bitbucket.org/hpk42/py-trunk/raw/1.1.0/py/plugin/pytest_helpconfig.py .. _`oejskit`: oejskit.html .. _`doctest`: doctest.html -.. _`pytest_mark.py`: http://bitbucket.org/hpk42/py-trunk/raw/trunk/_py/test/plugin/pytest_mark.py +.. _`pytest_mark.py`: http://bitbucket.org/hpk42/py-trunk/raw/1.1.0/py/plugin/pytest_mark.py .. _`get in contact`: ../../contact.html -.. _`pytest_capture.py`: http://bitbucket.org/hpk42/py-trunk/raw/trunk/_py/test/plugin/pytest_capture.py +.. _`pytest_capture.py`: http://bitbucket.org/hpk42/py-trunk/raw/1.1.0/py/plugin/pytest_capture.py .. _`figleaf`: figleaf.html .. _`customize`: ../customize.html .. _`hooklog`: hooklog.html -.. _`pytest_terminal.py`: http://bitbucket.org/hpk42/py-trunk/raw/trunk/_py/test/plugin/pytest_terminal.py +.. _`pytest_terminal.py`: http://bitbucket.org/hpk42/py-trunk/raw/1.1.0/py/plugin/pytest_terminal.py .. _`recwarn`: recwarn.html -.. _`pytest_pdb.py`: http://bitbucket.org/hpk42/py-trunk/raw/trunk/_py/test/plugin/pytest_pdb.py +.. _`pytest_pdb.py`: http://bitbucket.org/hpk42/py-trunk/raw/1.1.0/py/plugin/pytest_pdb.py .. _`monkeypatch`: monkeypatch.html +.. _`coverage`: coverage.html .. _`resultlog`: resultlog.html .. _`django`: django.html -.. _`pytest_unittest.py`: http://bitbucket.org/hpk42/py-trunk/raw/trunk/_py/test/plugin/pytest_unittest.py +.. _`xmlresult`: xmlresult.html +.. _`pytest_unittest.py`: http://bitbucket.org/hpk42/py-trunk/raw/1.1.0/py/plugin/pytest_unittest.py .. _`nose`: nose.html -.. _`pytest_resultlog.py`: http://bitbucket.org/hpk42/py-trunk/raw/trunk/_py/test/plugin/pytest_resultlog.py +.. _`pytest_resultlog.py`: http://bitbucket.org/hpk42/py-trunk/raw/1.1.0/py/plugin/pytest_resultlog.py .. _`pdb`: pdb.html diff --git a/doc/test/plugin/tmpdir.txt b/doc/test/plugin/tmpdir.txt new file mode 100644 index 000000000..47514ba75 --- /dev/null +++ b/doc/test/plugin/tmpdir.txt @@ -0,0 +1,39 @@ + +pytest_tmpdir plugin +==================== + +provide temporary directories to test functions. + +.. contents:: + :local: + +usage example:: + + def test_plugin(tmpdir): + tmpdir.join("hello").write("hello") + +.. _`py.path.local`: ../../path.html + +.. _`tmpdir funcarg`: + + +the 'tmpdir' test function argument +----------------------------------- + +return a temporary directory path object +unique to each test function invocation, +created as a sub directory of the base temporary +directory. The returned object is a `py.path.local`_ +path object. + +Start improving this plugin in 30 seconds +========================================= + + +1. Download `pytest_tmpdir.py`_ plugin source code +2. put it somewhere as ``pytest_tmpdir.py`` into your import path +3. a subsequent ``py.test`` run will use your local version + +Checkout customize_, other plugins_ or `get in contact`_. + +.. include:: links.txt diff --git a/doc/test/plugin/xmlresult.txt b/doc/test/plugin/xmlresult.txt new file mode 100644 index 000000000..ec631d4eb --- /dev/null +++ b/doc/test/plugin/xmlresult.txt @@ -0,0 +1,6 @@ +pytest_coverage plugin (EXTERNAL) +========================================== + +This plugin allows to use Ned's coverage package, see + + http://github.com/rozza/py.test-plugins diff --git a/doc/test/quickstart.txt b/doc/test/quickstart.txt index ab35fa02d..c4a8005ef 100644 --- a/doc/test/quickstart.txt +++ b/doc/test/quickstart.txt @@ -7,7 +7,7 @@ Quickstart .. _here: ../install.html -If you have a version of ``easy_install`` (otherwise see here_) just type:: +If you have any ``easy_install`` (otherwise see here_) just type:: easy_install -U py diff --git a/doc/test/xunit_setup.txt b/doc/test/xunit_setup.txt index 0a94794cb..a79750c7d 100644 --- a/doc/test/xunit_setup.txt +++ b/doc/test/xunit_setup.txt @@ -1,14 +1,20 @@ ==================================== -xUnit style setup +extended xUnit style setup ==================================== .. _`funcargs`: funcargs.html +.. _`test parametrization`: funcargs.html#parametrizing-tests +.. _`unittest plugin`: plugin/unittest.html .. _`xUnit`: http://en.wikipedia.org/wiki/XUnit Note: - Since version 1.0 funcargs_ present the recommended way - to manage flexible and scalable test setups. + Since version 1.0 funcargs_ present the new and + more powerful way to manage test setups with larger + test suites. *funcargs* also provide flexible + `test parametrization`_ which goes way beyond + what you can do with the xUnit setup/teardown-method + patter. Python, Java and many other languages have a tradition of using xUnit_ style testing. This typically @@ -19,6 +25,10 @@ scopes for which you can provide setup/teardown hooks to provide test fixtures: per-module, per-class and per-method/function. ``py.test`` will discover and call according methods automatically. + +The `unittest plugin`_ also will intregate ``unittest.TestCase`` +instances into a test run and call respective setup/teardown methods. + All setup/teardown methods are optional. The following methods are called at module level if they exist: diff --git a/py/__init__.py b/py/__init__.py index bed2f5ce3..92646bcd1 100644 --- a/py/__init__.py +++ b/py/__init__.py @@ -2,20 +2,14 @@ """ py.test and pylib: rapid testing and development utils -- `py.test`_: cross-project testing tool with many advanced features -- `py.path`_: path abstractions over local and subversion files -- `py.code`_: dynamic code compile and traceback printing support - -Compatibility: Linux, Win32, OSX, Python versions 2.4 through to 3.1. -For questions please check out http://pylib.org/contact.html - -.. _`py.test`: http://pylib.org/test.html -.. _`py.path`: http://pylib.org/path.html -.. _`py.code`: http://pylib.org/html +this module uses apipkg.py for lazy-loading sub modules +and classes. The initpkg-dictionary below specifies +name->value mappings where value can be another namespace +dictionary or an import path. (c) Holger Krekel and others, 2009 """ -version = "trunk" +version = "1.1.0" __version__ = version = version or "1.1.x" import py.apipkg diff --git a/py/impl/test/parseopt.py b/py/impl/test/parseopt.py index 117860af9..b1edf6118 100644 --- a/py/impl/test/parseopt.py +++ b/py/impl/test/parseopt.py @@ -46,6 +46,7 @@ class Parser: self._groups.insert(i+1, group) return group + addgroup = getgroup def addgroup(self, name, description=""): py.log._apiwarn("1.1", "use getgroup() which gets-or-creates") return self.getgroup(name, description) diff --git a/py/plugin/pytest_default.py b/py/plugin/pytest_default.py index bc23f7270..ae5540d11 100644 --- a/py/plugin/pytest_default.py +++ b/py/plugin/pytest_default.py @@ -70,7 +70,7 @@ def pytest_addoption(parser): add_dist_options(parser) else: parser.epilog = ( - "execnet missing: --looponfailing and distributed testing not available.") + "'execnet' package required for --looponfailing / distributed testing.") def add_dist_options(parser): # see http://pytest.org/help/dist") diff --git a/py/plugin/pytest_skipping.py b/py/plugin/pytest_skipping.py index e733e6b25..ef2fb2cac 100644 --- a/py/plugin/pytest_skipping.py +++ b/py/plugin/pytest_skipping.py @@ -83,7 +83,7 @@ skipping on a missing import dependency -------------------------------------------------- You can use the following import helper at module level -or within a test or setup function. +or within a test or test setup function:: docutils = py.test.importorskip("docutils") diff --git a/py/plugin/pytest_terminal.py b/py/plugin/pytest_terminal.py index 3b9f243eb..e53f4273d 100644 --- a/py/plugin/pytest_terminal.py +++ b/py/plugin/pytest_terminal.py @@ -259,8 +259,8 @@ class TerminalReporter: verinfo = ".".join(map(str, sys.version_info[:3])) msg = "python: platform %s -- Python %s" % (sys.platform, verinfo) + msg += " -- pytest-%s" % (py.__version__) if self.config.option.verbose or self.config.option.debug or getattr(self.config.option, 'pastebin', None): - msg += " -- pytest-%s" % (py.__version__) msg += " -- " + str(sys.executable) self.write_line(msg) diff --git a/py/plugin/pytest_tmpdir.py b/py/plugin/pytest_tmpdir.py index 74ef36b1d..b442ee009 100644 --- a/py/plugin/pytest_tmpdir.py +++ b/py/plugin/pytest_tmpdir.py @@ -1,16 +1,21 @@ -""" - provide temporary directories to test functions and methods. +"""provide temporary directories to test functions. -example: - - pytest_plugins = "pytest_tmpdir" +usage example:: def test_plugin(tmpdir): tmpdir.join("hello").write("hello") +.. _`py.path.local`: ../../path.html + """ import py def pytest_funcarg__tmpdir(request): + """return a temporary directory path object + unique to each test function invocation, + created as a sub directory of the base temporary + directory. The returned object is a `py.path.local`_ + path object. + """ name = request.function.__name__ return request.config.mktemp(name, numbered=True) diff --git a/setup.py b/setup.py index 0793da9a5..7cb24260a 100644 --- a/setup.py +++ b/setup.py @@ -28,7 +28,7 @@ def main(): name='py', description='py.test and pylib: rapid testing and development utils.', long_description = long_description, - version= trunk or '1.1.0b1', + version= trunk or '1.1.0', url='http://pylib.org', license='MIT license', platforms=['unix', 'linux', 'osx', 'cygwin', 'win32'], @@ -42,7 +42,7 @@ def main(): 'py.svnwcrevert = py.cmdline:pysvnwcrevert', 'py.test = py.cmdline:pytest', 'py.which = py.cmdline:pywhich']}, - classifiers=['Development Status :: 4 - Beta', + classifiers=['Development Status :: 5 - Production/Stable', 'Intended Audience :: Developers', 'License :: OSI Approved :: MIT License', 'Operating System :: POSIX', @@ -50,7 +50,6 @@ def main(): 'Operating System :: MacOS :: MacOS X', 'Topic :: Software Development :: Testing', 'Topic :: Software Development :: Libraries', - 'Topic :: System :: Distributed Computing', 'Topic :: Utilities', 'Programming Language :: Python'], packages=['py', diff --git a/testing/log/test_log.py b/testing/log/test_log.py index 85de849ac..69d7675f9 100644 --- a/testing/log/test_log.py +++ b/testing/log/test_log.py @@ -118,7 +118,7 @@ class TestLogConsumer: def test_log_file(self): customlog = tempdir.join('log.out') - py.log.setconsumer("default", open(str(customlog), 'w', buffering=0)) + py.log.setconsumer("default", open(str(customlog), 'w', buffering=1)) py.log.Producer("default")("hello world #1") assert customlog.readlines() == ['[default] hello world #1\n']