merging 1.0.x changes

--HG--
branch : trunk
This commit is contained in:
holger krekel 2009-07-08 20:08:07 +02:00
commit 8a085c035a
31 changed files with 209 additions and 114 deletions

View File

@ -1,12 +1,19 @@
Changes between 1.0.0b7 and 1.0.0b8
=====================================
* docs: refined funcargs doc, use the term
"factory" instead of "provider", added a new
talk/tutorial doc page
* if plugins use "py.test.importorskip" for importing
a dependency only a warning will be issued instead
of exiting the testing process.
* docs:
- refined funcargs doc , use the term "factory" instead
of "provider"
- added a new talk/tutorial doc page
- better plugin docstrings
* fixed teardown problem related to partially failing funcarg setups
(thanks MrTopf for reporting)
(thanks MrTopf for reporting), "pytest_runtest_teardown" is now
always invoked even if the "pytest_runtest_setup" failed.
* tweaked doctest output for docstrings in py modules,
thanks Radomir.
@ -34,6 +41,8 @@ Changes between 1.0.0b3 and 1.0.0b7
* resolve issue #18, multiprocessing.Manager() and
redirection clash
* make __name__ == "__channelexec__" for remote_exec code
Changes between 1.0.0b1 and 1.0.0b3
=============================================

View File

@ -1,32 +1,18 @@
.hgtags
CHANGELOG
MANIFEST
doc/announce/release-1.0.0.txt
doc/download.txt
doc/test/funcargs.txt
py/__init__.py
py/io/stdcapture.py
py/io/testing/test_stdcapture.py
py/test/collect.py
py/test/funcargs.py
py/test/plugin/pytest_runner.py
py/test/plugin/pytest_terminal.py
py/test/pycollect.py
py/test/testing/test_funcargs.py
py/test/testing/test_genitems.py
setup.py
.hgignore
LICENSE
MANIFEST
README.txt
_findpy.py
doc/announce/release-0.9.0.txt
doc/announce/release-0.9.2.txt
doc/announce/release-1.0.0.txt
doc/announce/releases.txt
doc/bin.txt
doc/code.txt
doc/confrest.py
doc/conftest.py
doc/contact.txt
doc/download.txt
doc/execnet.txt
doc/img/pylib.png
doc/index.txt
@ -41,11 +27,19 @@ doc/test/dist.txt
doc/test/examples.txt
doc/test/extend.txt
doc/test/features.txt
doc/test/funcargs.txt
doc/test/quickstart.txt
doc/test/talks.txt
doc/test/test.txt
doc/test/xunit_setup.txt
doc/xml.txt
example/assertion/failure_demo.py
example/assertion/test_failures.py
example/assertion/test_setup_flow_example.py
example/execnet/popen_read_multiple.py
example/execnet/svn-sync-repo.py
example/execnet/sysinfo.py
example/funcarg/conftest.py
example/funcarg/costlysetup/conftest.py
example/funcarg/costlysetup/sub1/test_quick.py
example/funcarg/costlysetup/sub2/test_two.py
@ -65,10 +59,8 @@ example/funcarg/test_simpleprovider.py
example/genhtml.py
example/genhtmlcss.py
example/genxml.py
example/pytest/failure_demo.py
example/pytest/test_failures.py
example/pytest/test_setup_flow_example.py
py/LICENSE
py/__init__.py
py/_com.py
py/bin/_findpy.py
py/bin/_genscripts.py
@ -174,10 +166,12 @@ py/initpkg.py
py/io/__init__.py
py/io/dupfile.py
py/io/fdcapture.py
py/io/stdcapture.py
py/io/terminalwriter.py
py/io/testing/__init__.py
py/io/testing/test_dupfile.py
py/io/testing/test_fdcapture.py
py/io/testing/test_stdcapture.py
py/io/testing/test_terminalwriter.py
py/log/__init__.py
py/log/consumer.py
@ -292,6 +286,7 @@ py/rest/testing/test_transform.py
py/rest/transform.py
py/test/__init__.py
py/test/cmdline.py
py/test/collect.py
py/test/compat.py
py/test/config.py
py/test/conftesthandle.py
@ -306,6 +301,7 @@ py/test/dist/testing/test_mypickle.py
py/test/dist/testing/test_nodemanage.py
py/test/dist/testing/test_txnode.py
py/test/dist/txnode.py
py/test/funcargs.py
py/test/looponfail/__init__.py
py/test/looponfail/remote.py
py/test/looponfail/testing/__init__.py
@ -334,11 +330,15 @@ py/test/plugin/pytest_recwarn.py
py/test/plugin/pytest_restdoc.py
py/test/plugin/pytest_resultdb.py
py/test/plugin/pytest_resultlog.py
py/test/plugin/pytest_runner.py
py/test/plugin/pytest_terminal.py
py/test/plugin/pytest_tmpdir.py
py/test/plugin/pytest_unittest.py
py/test/plugin/pytest_xfail.py
py/test/plugin/test_pytest_runner.py
py/test/plugin/test_pytest_runner_xunit.py
py/test/pluginmanager.py
py/test/pycollect.py
py/test/session.py
py/test/testing/__init__.py
py/test/testing/acceptance_test.py
@ -353,6 +353,8 @@ py/test/testing/test_compat.py
py/test/testing/test_config.py
py/test/testing/test_conftesthandle.py
py/test/testing/test_deprecated_api.py
py/test/testing/test_funcargs.py
py/test/testing/test_genitems.py
py/test/testing/test_outcome.py
py/test/testing/test_parseopt.py
py/test/testing/test_pickling.py
@ -360,7 +362,6 @@ py/test/testing/test_pluginmanager.py
py/test/testing/test_pycollect.py
py/test/testing/test_recording.py
py/test/testing/test_session.py
py/test/testing/test_setup_functional.py
py/test/testing/test_traceback.py
py/test/web/__init__.py
py/test/web/exception.py
@ -384,3 +385,4 @@ py/xmlobj/testing/test_html.py
py/xmlobj/testing/test_xml.py
py/xmlobj/visit.py
py/xmlobj/xml.py
setup.py

View File

@ -1,34 +1,49 @@
py.test / py lib 1.0.0: distributed testing and dynamic code deployment
py.test / py lib 1.0.0: new test plugins, funcargs and cleanups
============================================================================
Welcome to the 1.0 release bringing new flexibility and
power to testing with Python! Main news:
power to testing with Python. Main news:
* new py.test plugin architecture, some examples:
* improved test architecture, featuring super-simple project
specific or cross-project single-file plugins, e.g:
pytest_xfail.py: mark tests as "expected to fail"
pytest_pocoo.py: automatically send tracebacks to pocoo paste service
pytest_monkeypatch.py: safely patch parts of your environment in a test function
pytest_figleaf.py: generate html coverage reports
pytest_resultlog.py: generate buildbot-friendly output
* pytest_unittest.py: run traditional unittest.py tests
* pytest_xfail.py: mark tests as "expected to fail"
* pytest_pocoo.py: automatically send tracebacks to pocoo paste service
* pytest_monkeypatch.py: safely patch parts of your environment in a test function
* pytest_figleaf.py: generate html coverage reports
* pytest_resultlog.py: generate buildbot-friendly output
and much more!
and many more!
* funcargs - the new flexible mechanism for managing all your test setup/fixture needs!
* funcargs - bringing new flexibilty and zero-boilerplate to Python testing:
- cleanly separated test code and test configuration and test value setup
- ideal for integration and functional tests
- new generative tests -> deprecation of yield-generated tests
* distributed testing and distributed execution (py.execnet):
- new unified "TX" URL scheme for specifying remote resources
- new sync/async ways to handle multiple remote processes
- much improved documentation
* flexibly distribute tests to multiple computers from the command line
See the py.test documentation for more info:
http://pytest.org
The py lib contains the py.test tool and offers its well-tested code
independently from the testing tool, mainly:
The py lib also got smaller and focuses on offering much of the
well-tested py.test code in independent namespaces:
* py.execnet: ad-hoc code distribution to SSH, Socket and local sub processes
* py.code: support for dynamically running and debugging python code
* py.code: higher-level introspection and dynamic generation of python code
* py.path: path abstractions over local and subversion files
Some non-strictly-test related code, notably greenlets/co-routines
and apigen now live on their own and have been removed, also simplifying
the installation procedures.
The whole package works well with Linux, OSX and Win32, on
Python 2.3, 2.4, 2.5 and 2.6. (Expect Python3 compatibility soon!)

View File

@ -238,3 +238,19 @@ socketserver::
socketgw = py.execnet.SocketGateway.new_remote(popengw, ("127.0.0.1", 0))
print socketgw._rinfo() # print some info about the remote environment
Sending a module / checking if run through remote_exec
--------------------------------------------------------------
You can pass a module object to ``remote_exec`` in which case
its source code will be sent. No dependencies will be transferred
so the module must be self-contained or only use modules that are
installed on the "other" side. Module code can detect if it is
running in a remote_exec situation by checking for the special
``__name__`` attribute like this::
if __name__ == '__channelexec__':
# ... call module functions ...

View File

@ -9,6 +9,6 @@ def test_failure_demo_fails_properly(testdir):
passed, skipped, failed = reprec.countoutcomes()
assert passed == 0
assert failed == 20, failed
colreports = reprec.getnamed("collectionreport")
colreports = reprec.getreports("pytest_collectreport")
failed = len([x.failed for x in colreports])
assert failed == 5

View File

@ -0,0 +1,3 @@
import py
collect_ignore = 'mysetup', 'mysetup2', 'test_simpleprovider.py', 'parametrize'

View File

@ -230,7 +230,7 @@ class Gateway(object):
from sys import exc_info
channel, (source, outid, errid) = item
try:
loc = { 'channel' : channel }
loc = { 'channel' : channel, '__name__': '__channelexec__'}
self._trace("execution starts:", repr(source)[:50])
close = self._local_redirect_thread_output(outid, errid)
try:

View File

@ -92,6 +92,11 @@ class BasicRemoteExecution:
def test_repr_doesnt_crash(self):
assert isinstance(repr(self), str)
def test_attribute__name__(self):
channel = self.gw.remote_exec("channel.send(__name__)")
name = channel.receive()
assert name == "__channelexec__"
def test_correct_setup_no_py(self):
channel = self.gw.remote_exec("""
import sys

View File

@ -1,4 +1,4 @@
""" Plugin implementing defaults and general options. """
""" default hooks and general py.test options. """
import py
@ -115,7 +115,7 @@ def pytest_addoption(parser):
def pytest_configure(config):
fixoptions(config)
setsession(config)
loadplugins(config)
#xxxloadplugins(config)
def fixoptions(config):
if config.option.numprocesses:
@ -124,7 +124,7 @@ def fixoptions(config):
if config.option.distload:
config.option.dist = "load"
def loadplugins(config):
def xxxloadplugins(config):
for name in config.getvalue("plugin"):
print "importing", name
config.pluginmanager.import_plugin(name)

View File

@ -1,5 +1,5 @@
"""
automatically collect and execute doctests.
collect and execute doctests from modules and test files.
"""
import py

View File

@ -1,5 +1,5 @@
"""
cleanup gateways that were instantiated during a test function run.
cleanup execnet gateways during test function runs.
"""
import py

View File

@ -3,8 +3,7 @@ write and report coverage data using the 'figleaf' module.
"""
import py
figleaf = py.test.importorskip("figleaf")
import figleaf.annotate_html
figleaf = py.test.importorskip("figleaf.annotate_html")
def pytest_addoption(parser):
group = parser.addgroup('figleaf options')

View File

@ -1,4 +1,4 @@
""" log calling of plugin hooks to a file. """
""" log invocations of extension hooks to a file. """
import py
def pytest_addoption(parser):

View File

@ -1,6 +1,5 @@
"""
'capsys' and 'capfd' funcargs for capturing stdout/stderror either
by intercepting sys.stdout/stderr or File Descriptors 1/2.
'capsys' and 'capfd' funcargs for capturing stdout/stderror.
Calling the reset() method of the capture funcargs gives
a out/err tuple of strings representing the captured streams.

View File

@ -1,6 +1,5 @@
"""
py.test.mark / keyword plugin
"""
import py

View File

@ -1,13 +1,13 @@
"""
"monkeypatch" funcarg for safely patching objects,
dictionaries and environment variables during the execution of
a test. "monkeypatch" has three helper functions:
safely patch object attributes, dicts and environment variables.
monkeypatch.setattr(obj, name, value)
monkeypatch.setitem(obj, name, value)
monkeypatch.setenv(name, value)
the "monkeypatch" funcarg has three helper functions:
After the test has run modifications will be undone.
monkeypatch.setattr(obj, name, value)
monkeypatch.setitem(obj, name, value)
monkeypatch.setenv(name, value)
After the test has run modifications will be undone.
"""
import os

View File

@ -1,6 +1,5 @@
"""
interactive debugging with a PDB prompt.
interactive debugging with the Python Debugger.
"""
import py
import pdb, sys, linecache

View File

@ -1,5 +1,5 @@
"""
py.test plugin for sending testing failure information to paste.pocoo.org
submit failure information to paste.pocoo.org
"""
import py

View File

@ -1,5 +1,5 @@
"""
funcargs and support code for testing py.test functionality.
funcargs and support code for testing py.test's own functionality.
"""
import py

View File

@ -1,5 +1,5 @@
"""
help performing checks for deprecation and other warnings. Provides:
helpers for asserting deprecation and other warnings.
recwarn: function argument where one can call recwarn.pop() to get
the last warning that would have been shown.

View File

@ -1,6 +1,5 @@
"""
perform ReST specific tests on .txt files, including
linkchecks and remote URL checks.
perform ReST syntax, local and remote reference tests on .rst/.txt files.
"""
import py
@ -17,7 +16,7 @@ def pytest_addoption(parser):
help="force generation of html files.")
def pytest_collect_file(path, parent):
if path.ext == ".txt":
if path.ext in (".txt", ".rst"):
project = getproject(path)
if project is not None:
return ReSTFile(path, parent=parent, project=project)
@ -362,6 +361,7 @@ def test_deindent():
class TestApigenLinkRole:
disabled = True
# these tests are moved here from the former py/doc/conftest.py
def test_resolve_linkrole(self):
from py.__.doc.conftest import get_apigen_relpath

View File

@ -1,9 +1,5 @@
"""
collect and run test items.
* executing test items
* running collectors
* and generating report events about it
collect and run test items and create reports.
"""
import py
@ -53,7 +49,7 @@ def runtestprotocol(item, log=True):
reports = [rep]
if rep.passed:
reports.append(call_and_report(item, "call", log))
reports.append(call_and_report(item, "teardown", log))
reports.append(call_and_report(item, "teardown", log))
return reports
def pytest_runtest_setup(item):
@ -225,11 +221,14 @@ class SetupState(object):
colitem = self.stack.pop()
self._teardown_with_finalization(colitem)
def _teardown_with_finalization(self, colitem):
def _callfinalizers(self, colitem):
finalizers = self._finalizers.pop(colitem, None)
while finalizers:
fin = finalizers.pop()
fin()
def _teardown_with_finalization(self, colitem):
self._callfinalizers(colitem)
if colitem:
colitem.teardown()
for colitem in self._finalizers:
@ -242,17 +241,19 @@ class SetupState(object):
assert not self._finalizers
def teardown_exact(self, item):
assert self.stack and self.stack[-1] == item
self._pop_and_teardown()
if item == self.stack[-1]:
self._pop_and_teardown()
else:
self._callfinalizers(item)
def prepare(self, colitem):
""" setup objects along the collector chain to the test-method
Teardown any unneccessary previously setup objects."""
and teardown previously setup objects."""
needed_collectors = colitem.listchain()
while self.stack:
if self.stack == needed_collectors[:len(self.stack)]:
break
self._pop_and_teardown()
for col in needed_collectors[len(self.stack):]:
self.stack.append(col)
col.setup()
self.stack.append(col)

View File

@ -1,3 +1,6 @@
"""
terminal reporting of the full testing process.
"""
import py
import sys

View File

@ -86,7 +86,8 @@ class BaseFunctionalTests:
#assert rep.skipped.reason == "hello"
#assert rep.skipped.location.lineno == 3
#assert rep.skipped.location.lineno == 3
assert len(reports) == 1
assert len(reports) == 2
assert reports[1].passed # teardown
def test_failure_in_setup_function(self, testdir):
reports = testdir.runitem("""
@ -101,7 +102,7 @@ class BaseFunctionalTests:
assert not rep.passed
assert rep.failed
assert rep.when == "setup"
assert len(reports) == 1
assert len(reports) == 2
def test_failure_in_teardown_function(self, testdir):
reports = testdir.runitem("""
@ -156,7 +157,7 @@ class BaseFunctionalTests:
def test_func():
pass
""")
assert len(reports) == 1
assert len(reports) == 2
rep = reports[0]
print rep
assert not rep.skipped

View File

@ -55,6 +55,7 @@ def test_class_setup(testdir):
""")
reprec.assertoutcome(passed=1+2+1)
def test_method_setup(testdir):
reprec = testdir.inline_runsource("""
class TestSetupMethod:

View File

@ -3,6 +3,7 @@ managing loading and interacting with pytest plugins.
"""
import py
from py.__.test.plugin import hookspec
from py.__.test.outcome import Skipped
def check_old_use(mod, modname):
clsname = modname[len('pytest_'):].capitalize() + "Plugin"
@ -96,10 +97,20 @@ class PluginManager(object):
modname = canonical_importname(spec)
if modname in self.impname2plugin:
return
mod = importplugin(modname)
check_old_use(mod, modname)
self.register(mod)
self.consider_module(mod)
try:
mod = importplugin(modname)
except KeyboardInterrupt:
raise
except Skipped, e:
self._warn("could not import plugin %r, reason: %r" %(
(modname, e.msg)))
else:
check_old_use(mod, modname)
self.register(mod)
self.consider_module(mod)
def _warn(self, msg):
print "===WARNING=== %s" % (msg,)
def _checkplugin(self, plugin):
# =====================================================
@ -243,3 +254,36 @@ def formatdef(func):
py.std.inspect.formatargspec(*py.std.inspect.getargspec(func))
)
if __name__ == "__main__":
import py.__.test.plugin
basedir = py.path.local(py.__.test.plugin.__file__).dirpath()
name2text = {}
for p in basedir.listdir("pytest_*"):
if p.ext == ".py" or (
p.check(dir=1) and p.join("__init__.py").check()):
impname = p.purebasename
if impname.find("__") != -1:
continue
try:
plugin = importplugin(impname)
except (ImportError, py.__.test.outcome.Skipped):
name2text[impname] = "IMPORT ERROR"
else:
doc = plugin.__doc__ or ""
doc = doc.strip()
name2text[impname] = doc
for name in sorted(name2text.keys()):
text = name2text[name]
if name[0] == "_":
continue
print "%-20s %s" % (name, text.split("\n")[0])
#text = py.std.textwrap.wrap(name2text[name],
# width = 80,
# initial_indent="%s: " % name,
# replace_whitespace = False)
#for line in text:
# print line

View File

@ -7,12 +7,27 @@ class TestBootstrapping:
monkeypatch.setitem(os.environ, 'PYTEST_PLUGINS', 'nonexistingmodule')
py.test.raises(ImportError, "pluginmanager.consider_env()")
def test_preparse_args(self, monkeypatch):
def test_preparse_args(self):
pluginmanager = PluginManager()
py.test.raises(ImportError, """
pluginmanager.consider_preparse(["xyz", "-p", "hello123"])
""")
def test_plugin_skip(self, testdir, monkeypatch):
testdir.makepyfile(pytest_skipping1="""
import py
py.test.skip("hello")
""")
result = testdir.runpytest("-p", "skipping1")
result.stdout.fnmatch_lines([
"*WARNING*could not import plugin*skipping1*hello*"
])
monkeypatch.setenv("PYTEST_PLUGINS", "skipping1")
result = testdir.runpytest()
result.stdout.fnmatch_lines([
"*WARNING*could not import plugin*skipping1*hello*"
])
def test_consider_env_plugin_instantiation(self, testdir, monkeypatch):
pluginmanager = PluginManager()
testdir.syspathinsert()
@ -221,30 +236,6 @@ class TestPytestPluginInteractions:
assert not pluginmanager.listattr("hello")
assert pluginmanager.listattr("x") == [42]
@py.test.mark.xfail # setup call methods
def test_call_setup_participants(self, testdir):
testdir.makepyfile(
conftest="""
import py
def pytest_method(self, x):
return x+1
pytest_plugin = "pytest_someplugin",
"""
)
testdir.makepyfile(pytest_someplugin="""
def pytest_method(self, x):
return x+1
""")
modcol = testdir.getmodulecol("""
def pytest_method(x):
return x+0
""")
l = []
call = modcol.config.pluginmanager.setupcall(modcol, "pytest_method", 1)
assert len(call.methods) == 3
results = call.execute()
assert results == [1,2,2]
def test_collectattr():
class A:
def pytest_hello(self):

View File

@ -39,6 +39,7 @@ class TestModule:
modcol = testdir.getmodulecol("pytest_plugins='xasdlkj',")
py.test.raises(ImportError, "modcol.obj")
class TestDisabled:
def test_disabled_module(self, testdir):
modcol = testdir.getmodulecol("""
disabled = True
@ -51,7 +52,6 @@ class TestModule:
assert len(l) == 1
py.test.raises(Skipped, "modcol.setup()")
class TestClass:
def test_disabled_class(self, testdir):
modcol = testdir.getmodulecol("""
class TestClass:
@ -67,6 +67,15 @@ class TestClass:
assert len(l) == 1
py.test.raises(Skipped, "modcol.setup()")
def test_disabled_class_functional(self, testdir):
reprec = testdir.inline_runsource("""
class TestSimpleClassSetup:
disabled = True
def test_classlevel(self): pass
def test_classlevel2(self): pass
""")
reprec.assertoutcome(skipped=2)
class TestGenerator:
def test_generative_functions(self, testdir):
modcol = testdir.getmodulecol("""

View File

@ -1,6 +1,5 @@
"""
autogenerated by gensetup.py
setup file for 'py' package based on:
py lib / py.test setup.py file, autogenerated by gensetup.py
"""
import os, sys
@ -145,4 +144,4 @@ def main():
if __name__ == '__main__':
main()