merging 1.0 branch

--HG--
branch : trunk
This commit is contained in:
holger krekel 2009-08-09 19:06:36 +02:00
commit 58db35e70c
28 changed files with 290 additions and 115 deletions

View File

@ -1,3 +1,19 @@
Changes between 1.0.0 and 1.0.1
=====================================
* various unicode fixes: capturing and prints of unicode strings now
work within tests, they are encoded as "utf8" by default, terminalwriting
was adapted and somewhat unified between windows and linux
* fix issue #27: better reporting on non-collectable items given on commandline
(e.g. pyc files)
* "Test" prefixed classes with an __init__ method are *not* collected by default anymore
* terser reporting of collection error tracebacks
* renaming of arguments to some special rather internal hooks
Changes between 1.0.0b9 and 1.0.0 Changes between 1.0.0b9 and 1.0.0
===================================== =====================================

View File

@ -21,7 +21,7 @@ on CPython 2.3 - CPython 2.6.
automatically collects and executes tests automatically collects and executes tests
=============================================== ===============================================
py.test discovers tests automatically by inspect specified py.test discovers tests automatically by inspecting specified
directories or files. By default, it collects all python directories or files. By default, it collects all python
modules a leading ``test_`` or trailing ``_test`` filename. modules a leading ``test_`` or trailing ``_test`` filename.
From each test module every function with a leading ``test_`` From each test module every function with a leading ``test_``

View File

@ -289,6 +289,7 @@ Funcarg Tutorial Examples
.. _`application setup tutorial example`: .. _`application setup tutorial example`:
.. _appsetup:
application specific test setup application specific test setup
--------------------------------------------------------- ---------------------------------------------------------
@ -494,12 +495,13 @@ example: decorating a funcarg in a test module
For larger scale setups it's sometimes useful to decorare For larger scale setups it's sometimes useful to decorare
a funcarg just for a particular test module. We can a funcarg just for a particular test module. We can
extend the `accept example`_ by putting this in our test class: extend the `accept example`_ by putting this in our test module:
.. sourcecode:: python .. sourcecode:: python
def pytest_funcarg__accept(self, request): def pytest_funcarg__accept(request):
arg = request.getfuncargvalue("accept") # call the next factory # call the next factory (living in our conftest.py)
arg = request.getfuncargvalue("accept")
# create a special layout in our tempdir # create a special layout in our tempdir
arg.tmpdir.mkdir("special") arg.tmpdir.mkdir("special")
return arg return arg

View File

@ -1,33 +1,33 @@
.. _`terminal`: terminal.html .. _`terminal`: terminal.html
.. _`pytest_recwarn.py`: http://bitbucket.org/hpk42/py-trunk/raw/3b3ea41060652c47739450a590c4d71625bc05bd/py/test/plugin/pytest_recwarn.py .. _`pytest_recwarn.py`: http://bitbucket.org/hpk42/py-trunk/raw/caf81a2cdf5083be3bfb715d7e56273330a54843/py/test/plugin/pytest_recwarn.py
.. _`unittest`: unittest.html .. _`unittest`: unittest.html
.. _`pytest_monkeypatch.py`: http://bitbucket.org/hpk42/py-trunk/raw/3b3ea41060652c47739450a590c4d71625bc05bd/py/test/plugin/pytest_monkeypatch.py .. _`pytest_monkeypatch.py`: http://bitbucket.org/hpk42/py-trunk/raw/caf81a2cdf5083be3bfb715d7e56273330a54843/py/test/plugin/pytest_monkeypatch.py
.. _`pytest_keyword.py`: http://bitbucket.org/hpk42/py-trunk/raw/3b3ea41060652c47739450a590c4d71625bc05bd/py/test/plugin/pytest_keyword.py .. _`pytest_keyword.py`: http://bitbucket.org/hpk42/py-trunk/raw/caf81a2cdf5083be3bfb715d7e56273330a54843/py/test/plugin/pytest_keyword.py
.. _`pastebin`: pastebin.html .. _`pastebin`: pastebin.html
.. _`plugins`: index.html .. _`plugins`: index.html
.. _`pytest_capture.py`: http://bitbucket.org/hpk42/py-trunk/raw/3b3ea41060652c47739450a590c4d71625bc05bd/py/test/plugin/pytest_capture.py .. _`pytest_capture.py`: http://bitbucket.org/hpk42/py-trunk/raw/caf81a2cdf5083be3bfb715d7e56273330a54843/py/test/plugin/pytest_capture.py
.. _`pytest_doctest.py`: http://bitbucket.org/hpk42/py-trunk/raw/3b3ea41060652c47739450a590c4d71625bc05bd/py/test/plugin/pytest_doctest.py .. _`pytest_doctest.py`: http://bitbucket.org/hpk42/py-trunk/raw/caf81a2cdf5083be3bfb715d7e56273330a54843/py/test/plugin/pytest_doctest.py
.. _`capture`: capture.html .. _`capture`: capture.html
.. _`hooklog`: hooklog.html .. _`hooklog`: hooklog.html
.. _`pytest_restdoc.py`: http://bitbucket.org/hpk42/py-trunk/raw/3b3ea41060652c47739450a590c4d71625bc05bd/py/test/plugin/pytest_restdoc.py .. _`pytest_restdoc.py`: http://bitbucket.org/hpk42/py-trunk/raw/caf81a2cdf5083be3bfb715d7e56273330a54843/py/test/plugin/pytest_restdoc.py
.. _`pytest_hooklog.py`: http://bitbucket.org/hpk42/py-trunk/raw/3b3ea41060652c47739450a590c4d71625bc05bd/py/test/plugin/pytest_hooklog.py .. _`pytest_hooklog.py`: http://bitbucket.org/hpk42/py-trunk/raw/caf81a2cdf5083be3bfb715d7e56273330a54843/py/test/plugin/pytest_hooklog.py
.. _`pytest_pastebin.py`: http://bitbucket.org/hpk42/py-trunk/raw/3b3ea41060652c47739450a590c4d71625bc05bd/py/test/plugin/pytest_pastebin.py .. _`pytest_pastebin.py`: http://bitbucket.org/hpk42/py-trunk/raw/caf81a2cdf5083be3bfb715d7e56273330a54843/py/test/plugin/pytest_pastebin.py
.. _`pytest_figleaf.py`: http://bitbucket.org/hpk42/py-trunk/raw/3b3ea41060652c47739450a590c4d71625bc05bd/py/test/plugin/pytest_figleaf.py .. _`pytest_figleaf.py`: http://bitbucket.org/hpk42/py-trunk/raw/caf81a2cdf5083be3bfb715d7e56273330a54843/py/test/plugin/pytest_figleaf.py
.. _`xfail`: xfail.html .. _`xfail`: xfail.html
.. _`contact`: ../../contact.html .. _`contact`: ../../contact.html
.. _`checkout the py.test development version`: ../../download.html#checkout .. _`checkout the py.test development version`: ../../download.html#checkout
.. _`oejskit`: oejskit.html .. _`oejskit`: oejskit.html
.. _`pytest_xfail.py`: http://bitbucket.org/hpk42/py-trunk/raw/3b3ea41060652c47739450a590c4d71625bc05bd/py/test/plugin/pytest_xfail.py .. _`pytest_xfail.py`: http://bitbucket.org/hpk42/py-trunk/raw/caf81a2cdf5083be3bfb715d7e56273330a54843/py/test/plugin/pytest_xfail.py
.. _`figleaf`: figleaf.html .. _`figleaf`: figleaf.html
.. _`extend`: ../extend.html .. _`extend`: ../extend.html
.. _`pytest_terminal.py`: http://bitbucket.org/hpk42/py-trunk/raw/3b3ea41060652c47739450a590c4d71625bc05bd/py/test/plugin/pytest_terminal.py .. _`pytest_terminal.py`: http://bitbucket.org/hpk42/py-trunk/raw/caf81a2cdf5083be3bfb715d7e56273330a54843/py/test/plugin/pytest_terminal.py
.. _`recwarn`: recwarn.html .. _`recwarn`: recwarn.html
.. _`pytest_pdb.py`: http://bitbucket.org/hpk42/py-trunk/raw/3b3ea41060652c47739450a590c4d71625bc05bd/py/test/plugin/pytest_pdb.py .. _`pytest_pdb.py`: http://bitbucket.org/hpk42/py-trunk/raw/caf81a2cdf5083be3bfb715d7e56273330a54843/py/test/plugin/pytest_pdb.py
.. _`monkeypatch`: monkeypatch.html .. _`monkeypatch`: monkeypatch.html
.. _`resultlog`: resultlog.html .. _`resultlog`: resultlog.html
.. _`keyword`: keyword.html .. _`keyword`: keyword.html
.. _`restdoc`: restdoc.html .. _`restdoc`: restdoc.html
.. _`pytest_unittest.py`: http://bitbucket.org/hpk42/py-trunk/raw/3b3ea41060652c47739450a590c4d71625bc05bd/py/test/plugin/pytest_unittest.py .. _`pytest_unittest.py`: http://bitbucket.org/hpk42/py-trunk/raw/caf81a2cdf5083be3bfb715d7e56273330a54843/py/test/plugin/pytest_unittest.py
.. _`doctest`: doctest.html .. _`doctest`: doctest.html
.. _`pytest_resultlog.py`: http://bitbucket.org/hpk42/py-trunk/raw/3b3ea41060652c47739450a590c4d71625bc05bd/py/test/plugin/pytest_resultlog.py .. _`pytest_resultlog.py`: http://bitbucket.org/hpk42/py-trunk/raw/caf81a2cdf5083be3bfb715d7e56273330a54843/py/test/plugin/pytest_resultlog.py
.. _`pdb`: pdb.html .. _`pdb`: pdb.html

View File

@ -4,7 +4,40 @@ Talks and Tutorials
.. _`funcargs`: funcargs.html .. _`funcargs`: funcargs.html
a list of the latest talk and tutorial material: tutorial examples and blog postings
---------------------------------------------
function arguments:
- `application setup in test functions with funcargs`_ (doc link)
- `monkey patching done right`_ (blog post, consult `monkeypatch
plugin`_ for actual 1.0 API)
test parametrization:
- `generating parametrized tests with funcargs`_ (doc link)
- `parametrizing tests, generalized`_ (blog entry)
- `putting test-hooks into local or global plugins`_ (blog entry)
distributed testing:
- `simultanously test your code on all platforms`_ (blog entry)
plugins:
- usage examples are in most of the referenced `plugins`_ docs
.. _plugins: plugin/index.html
.. _`monkeypatch plugin`: plugin/monkeypatch.html
.. _`application setup in test functions with funcargs`: funcargs.html#appsetup
.. _`simultanously test your code on all platforms`: http://tetamap.wordpress.com/2009/03/23/new-simultanously-test-your-code-on-all-platforms/
.. _`monkey patching done right`: http://tetamap.wordpress.com/2009/03/03/monkeypatching-in-unit-tests-done-right/
.. _`putting test-hooks into local or global plugins`: http://tetamap.wordpress.com/2009/05/14/putting-test-hooks-into-local-and-global-plugins/
.. _`parametrizing tests, generalized`: http://tetamap.wordpress.com/2009/05/13/parametrizing-python-tests-generalized/
.. _`generating parametrized tests with funcargs`: funcargs.html#test-generators
conference talks and tutorials
----------------------------------------
- `ep2009-rapidtesting.pdf`_ tutorial slides (July 2009): - `ep2009-rapidtesting.pdf`_ tutorial slides (July 2009):

View File

@ -13,6 +13,8 @@ quickstart_: for getting started immediately.
features_: a walk through basic features and usage. features_: a walk through basic features and usage.
`talks, tutorials, examples`_: tutorial examples, slides
`available plugins`_: list of py.test plugins `available plugins`_: list of py.test plugins
funcargs_: powerful parametrized test function setup funcargs_: powerful parametrized test function setup
@ -23,10 +25,9 @@ extend_: intro to extend and customize py.test runs
config_: ``conftest.py`` files and the config object config_: ``conftest.py`` files and the config object
talks_: talk and tutorial slides
.. _`available plugins`: plugin/index.html .. _`available plugins`: plugin/index.html
.. _talks: talks.html .. _`talks, tutorials, examples`: talks.html
.. _quickstart: quickstart.html .. _quickstart: quickstart.html
.. _features: features.html .. _features: features.html
.. _funcargs: funcargs.html .. _funcargs: funcargs.html

View File

@ -20,7 +20,7 @@ For questions please check out http://pylib.org/contact.html
from initpkg import initpkg from initpkg import initpkg
trunk = None trunk = None
version = trunk or "1.0.0" version = trunk or "1.0.1"
initpkg(__name__, initpkg(__name__,
description = "py.test and pylib: advanced testing tool and networking lib", description = "py.test and pylib: advanced testing tool and networking lib",

View File

@ -17,6 +17,16 @@ class MultiCall:
self.kwargs = kwargs self.kwargs = kwargs
self.results = [] self.results = []
def __repr__(self):
args = []
if self.args:
args.append("posargs=%r" %(self.args,))
kw = self.kwargs
args.append(", ".join(["%s=%r" % x for x in self.kwargs.items()]))
args = " ".join(args)
status = "results: %r, rmethods: %r" % (self.results, self.methods)
return "<MultiCall %s %s>" %(args, status)
def execute(self, firstresult=False): def execute(self, firstresult=False):
while self.methods: while self.methods:
currentmethod = self.methods.pop() currentmethod = self.methods.pop()

View File

@ -1,7 +1,7 @@
License for modules in py/compat directory License for modules in py/compat directory
============================================================== ==============================================================
The "*.py" files in py/compat/ and subdirectories are are all The "*.py" files in py/compat/ and subdirectories are all
- except when otherwise stated at the beginning of the file - - except when otherwise stated at the beginning of the file -
copyrighted by the Python Software Foundation and licensed copyrighted by the Python Software Foundation and licensed
under the Python Software License of which you can find a copy under the Python Software License of which you can find a copy

View File

@ -76,7 +76,7 @@ class StdCaptureFD(Capture):
os.close(fd) os.close(fd)
if out: if out:
tmpfile = None tmpfile = None
if isinstance(out, file): if hasattr(out, 'write'):
tmpfile = out tmpfile = out
self.out = py.io.FDCapture(1, tmpfile=tmpfile) self.out = py.io.FDCapture(1, tmpfile=tmpfile)
if patchsys: if patchsys:
@ -84,7 +84,7 @@ class StdCaptureFD(Capture):
if err: if err:
if mixed and out: if mixed and out:
tmpfile = self.out.tmpfile tmpfile = self.out.tmpfile
elif isinstance(err, file): elif hasattr(err, 'write'):
tmpfile = err tmpfile = err
else: else:
tmpfile = None tmpfile = None

View File

@ -139,6 +139,7 @@ class TerminalWriter(object):
Black=40, Red=41, Green=42, Yellow=43, Black=40, Red=41, Green=42, Yellow=43,
Blue=44, Purple=45, Cyan=46, White=47, Blue=44, Purple=45, Cyan=46, White=47,
bold=1, light=2, blink=5, invert=7) bold=1, light=2, blink=5, invert=7)
_encoding = "utf-8"
def __init__(self, file=None, stringio=False): def __init__(self, file=None, stringio=False):
if file is None: if file is None:
@ -194,58 +195,27 @@ class TerminalWriter(object):
def write(self, s, **kw): def write(self, s, **kw):
if s: if s:
s = str(s) s = self._getbytestring(s)
if self.hasmarkup and kw: if self.hasmarkup and kw:
s = self.markup(s, **kw) s = self.markup(s, **kw)
self._file.write(s) self._file.write(s)
self._file.flush() self._file.flush()
def _getbytestring(self, s):
if isinstance(s, unicode):
return s.encode(self._encoding)
elif not isinstance(s, str):
return str(s)
return s
def line(self, s='', **kw): def line(self, s='', **kw):
self.write(s, **kw) self.write(s, **kw)
self.write('\n') self.write('\n')
class Win32ConsoleWriter(object): class Win32ConsoleWriter(TerminalWriter):
def __init__(self, file=None, stringio=False):
if file is None:
if stringio:
self.stringio = file = py.std.cStringIO.StringIO()
else:
file = py.std.sys.stdout
elif callable(file):
file = WriteFile(file)
self._file = file
self.fullwidth = get_terminal_width()
self.hasmarkup = should_do_markup(file)
def sep(self, sepchar, title=None, fullwidth=None, **kw):
if fullwidth is None:
fullwidth = self.fullwidth
# the goal is to have the line be as long as possible
# under the condition that len(line) <= fullwidth
if title is not None:
# we want 2 + 2*len(fill) + len(title) <= fullwidth
# i.e. 2 + 2*len(sepchar)*N + len(title) <= fullwidth
# 2*len(sepchar)*N <= fullwidth - len(title) - 2
# N <= (fullwidth - len(title) - 2) // (2*len(sepchar))
N = (fullwidth - len(title) - 2) // (2*len(sepchar))
fill = sepchar * N
line = "%s %s %s" % (fill, title, fill)
else:
# we want len(sepchar)*N <= fullwidth
# i.e. N <= fullwidth // len(sepchar)
line = sepchar * (fullwidth // len(sepchar))
# in some situations there is room for an extra sepchar at the right,
# in particular if we consider that with a sepchar like "_ " the
# trailing space is not important at the end of the line
if len(line) + len(sepchar.rstrip()) <= fullwidth:
line += sepchar.rstrip()
self.line(line, **kw)
def write(self, s, **kw): def write(self, s, **kw):
if s: if s:
s = str(s) s = self._getbytestring(s)
if self.hasmarkup: if self.hasmarkup:
handle = GetStdHandle(STD_OUTPUT_HANDLE) handle = GetStdHandle(STD_OUTPUT_HANDLE)
@ -269,8 +239,8 @@ class Win32ConsoleWriter(object):
if self.hasmarkup: if self.hasmarkup:
SetConsoleTextAttribute(handle, FOREGROUND_WHITE) SetConsoleTextAttribute(handle, FOREGROUND_WHITE)
def line(self, s='', **kw): def line(self, s="", **kw):
self.write(s + '\n', **kw) self.write(s+"\n", **kw)
if sys.platform == 'win32': if sys.platform == 'win32':
TerminalWriter = Win32ConsoleWriter TerminalWriter = Win32ConsoleWriter

View File

@ -37,6 +37,16 @@ class BaseTests:
assert len(l) == 1 assert len(l) == 1
assert l[0] == "hello\n" assert l[0] == "hello\n"
def test_line_unicode(self):
tw = self.getwriter()
for encoding in 'utf8', 'latin1':
tw._encoding = encoding
msg = unicode('b\u00f6y', 'utf8')
tw.line(msg)
l = self.getlines()
assert not isinstance(l[0], unicode)
assert unicode(l[0], encoding) == msg + "\n"
def test_sep_no_title(self): def test_sep_no_title(self):
tw = self.getwriter() tw = self.getwriter()
tw.sep("-", fullwidth=60) tw.sep("-", fullwidth=60)
@ -85,6 +95,16 @@ class BaseTests:
l = self.getlines() l = self.getlines()
assert len(l[0]) == len(l[1]) assert len(l[0]) == len(l[1])
class TestTmpfile(BaseTests):
def getwriter(self):
self.path = py.test.config.ensuretemp("terminalwriter").ensure("tmpfile")
self.tw = py.io.TerminalWriter(self.path.open('w+'))
return self.tw
def getlines(self):
io = self.tw._file
io.flush()
return self.path.open('r').readlines()
class TestStringIO(BaseTests): class TestStringIO(BaseTests):
def getwriter(self): def getwriter(self):
self.tw = py.io.TerminalWriter(stringio=True) self.tw = py.io.TerminalWriter(stringio=True)

View File

@ -10,6 +10,7 @@ class TestMultiCall:
def test_uses_copy_of_methods(self): def test_uses_copy_of_methods(self):
l = [lambda: 42] l = [lambda: 42]
mc = MultiCall(l) mc = MultiCall(l)
repr(mc)
l[:] = [] l[:] = []
res = mc.execute() res = mc.execute()
return res == 42 return res == 42
@ -33,16 +34,27 @@ class TestMultiCall:
p1 = P1() p1 = P1()
p2 = P2() p2 = P2()
multicall = MultiCall([p1.m, p2.m], 23) multicall = MultiCall([p1.m, p2.m], 23)
assert "23" in repr(multicall)
reslist = multicall.execute() reslist = multicall.execute()
assert len(reslist) == 2 assert len(reslist) == 2
# ensure reversed order # ensure reversed order
assert reslist == [23, 17] assert reslist == [23, 17]
def test_keyword_args(self):
def f(x):
return x + 1
multicall = MultiCall([f], x=23)
assert "x=23" in repr(multicall)
reslist = multicall.execute()
assert reslist == [24]
assert "24" in repr(multicall)
def test_optionalcallarg(self): def test_optionalcallarg(self):
class P1: class P1:
def m(self, x): def m(self, x):
return x return x
call = MultiCall([P1().m], 23) call = MultiCall([P1().m], 23)
assert "23" in repr(call)
assert call.execute() == [23] assert call.execute() == [23]
assert call.execute(firstresult=True) == 23 assert call.execute(firstresult=True) == 23

View File

@ -4,6 +4,7 @@ Collectors and test Items form a tree
that is usually built iteratively. that is usually built iteratively.
""" """
import py import py
pydir = py.path.local(py.__file__).dirpath()
def configproperty(name): def configproperty(name):
def fget(self): def fget(self):
@ -166,16 +167,13 @@ class Node(object):
if colitem.fspath == fspath or colitem.name == basename: if colitem.fspath == fspath or colitem.name == basename:
l.append(colitem) l.append(colitem)
if not l: if not l:
msg = ("Collector %r does not provide %r colitem " raise self.config.Error("can't collect: %s" %(fspath,))
"existing colitems are: %s" %
(cur, fspath, colitems))
raise AssertionError(msg)
if basenames: if basenames:
if len(l) > 1: if len(l) > 1:
msg = ("Collector %r has more than one %r colitem " msg = ("Collector %r has more than one %r colitem "
"existing colitems are: %s" % "existing colitems are: %s" %
(cur, fspath, colitems)) (cur, fspath, colitems))
raise AssertionError(msg) raise self.config.Error("xxx-too many test types for: %s" % (fspath, ))
cur = l[0] cur = l[0]
else: else:
if len(l) > 1: if len(l) > 1:
@ -332,6 +330,15 @@ class Collector(Node):
""" """
return self.collect_by_name(name) return self.collect_by_name(name)
def _prunetraceback(self, traceback):
if hasattr(self, 'fspath'):
path = self.fspath
ntraceback = traceback.cut(path=self.fspath)
if ntraceback == traceback:
ntraceback = ntraceback.cut(excludepath=pydir)
traceback = ntraceback.filter()
return traceback
class FSCollector(Collector): class FSCollector(Collector):
def __init__(self, fspath, parent=None): def __init__(self, fspath, parent=None):
fspath = py.path.local(fspath) fspath = py.path.local(fspath)

View File

@ -145,11 +145,9 @@ class SlaveNode(object):
if call.excinfo: if call.excinfo:
# likely it is not collectable here because of # likely it is not collectable here because of
# platform/import-dependency induced skips # platform/import-dependency induced skips
# XXX somewhat ugly shortcuts - also makes a collection # we fake a setup-error report with the obtained exception
# failure into an ItemTestReport - this might confuse # and do not care about capturing or non-runner hooks
# pytest_runtest_logreport hooks
rep = self.runner.pytest_runtest_makereport(item=item, call=call) rep = self.runner.pytest_runtest_makereport(item=item, call=call)
self.pytest_runtest_logreport(rep) self.pytest_runtest_logreport(rep)
return return
item.config.hook.pytest_runtest_protocol(item=item) item.config.hook.pytest_runtest_protocol(item=item)

View File

@ -91,7 +91,7 @@ def pytest__teardown_final(session):
""" called before test session finishes. """ """ called before test session finishes. """
pytest__teardown_final.firstresult = True pytest__teardown_final.firstresult = True
def pytest__teardown_final_logerror(rep): def pytest__teardown_final_logerror(report):
""" called if runtest_teardown_final failed. """ """ called if runtest_teardown_final failed. """
# ------------------------------------------------------------------------- # -------------------------------------------------------------------------
@ -108,8 +108,8 @@ def pytest_sessionfinish(session, exitstatus):
# hooks for influencing reporting (invoked from pytest_terminal) # hooks for influencing reporting (invoked from pytest_terminal)
# ------------------------------------------------------------------------- # -------------------------------------------------------------------------
def pytest_report_teststatus(rep): def pytest_report_teststatus(report):
""" return shortletter and verbose word. """ """ return result-category, shortletter and verbose word for reporting."""
pytest_report_teststatus.firstresult = True pytest_report_teststatus.firstresult = True
def pytest_terminal_summary(terminalreporter): def pytest_terminal_summary(terminalreporter):

View File

@ -107,11 +107,24 @@ class CaptureManager:
def __init__(self): def __init__(self):
self._method2capture = {} self._method2capture = {}
def _maketempfile(self):
f = py.std.tempfile.TemporaryFile()
newf = py.io.dupfile(f)
f.close()
return ustream(newf)
def _makestringio(self):
return py.std.StringIO.StringIO()
def _startcapture(self, method): def _startcapture(self, method):
if method == "fd": if method == "fd":
return py.io.StdCaptureFD() return py.io.StdCaptureFD(
out=self._maketempfile(), err=self._maketempfile()
)
elif method == "sys": elif method == "sys":
return py.io.StdCapture() return py.io.StdCapture(
out=self._makestringio(), err=self._makestringio()
)
else: else:
raise ValueError("unknown capturing method: %r" % method) raise ValueError("unknown capturing method: %r" % method)
@ -252,3 +265,13 @@ class CaptureFuncarg:
def close(self): def close(self):
self.capture.reset() self.capture.reset()
del self.capture del self.capture
def ustream(f):
import codecs
encoding = getattr(f, 'encoding', None) or "UTF-8"
reader = codecs.getreader(encoding)
writer = codecs.getwriter(encoding)
srw = codecs.StreamReaderWriter(f, reader, writer)
srw.encoding = encoding
return srw

View File

@ -24,7 +24,7 @@ def pytest_sessionfinish(session, exitstatus):
hook = session.config.hook hook = session.config.hook
rep = hook.pytest__teardown_final(session=session) rep = hook.pytest__teardown_final(session=session)
if rep: if rep:
hook.pytest__teardown_final_logerror(rep=rep) hook.pytest__teardown_final_logerror(report=rep)
def pytest_make_collect_report(collector): def pytest_make_collect_report(collector):
result = excinfo = None result = excinfo = None
@ -72,12 +72,12 @@ def pytest__teardown_final(session):
rep = TeardownErrorReport(call.excinfo) rep = TeardownErrorReport(call.excinfo)
return rep return rep
def pytest_report_teststatus(rep): def pytest_report_teststatus(report):
if rep.when in ("setup", "teardown"): if report.when in ("setup", "teardown"):
if rep.failed: if report.failed:
# category, shortletter, verbose-word # category, shortletter, verbose-word
return "error", "E", "ERROR" return "error", "E", "ERROR"
elif rep.skipped: elif report.skipped:
return "skipped", "s", "SKIPPED" return "skipped", "s", "SKIPPED"
else: else:
return "", "", "" return "", "", ""
@ -108,6 +108,13 @@ class CallInfo:
except: except:
self.excinfo = py.code.ExceptionInfo() self.excinfo = py.code.ExceptionInfo()
def __repr__(self):
if self.excinfo:
status = "exception: %s" % str(self.excinfo.value)
else:
status = "result: %r" % (self.result,)
return "<CallInfo when=%r %s>" % (self.when, status)
def forked_run_report(item): def forked_run_report(item):
# for now, we run setup/teardown in the subprocess # for now, we run setup/teardown in the subprocess
# XXX optionally allow sharing of setup/teardown # XXX optionally allow sharing of setup/teardown

View File

@ -82,7 +82,7 @@ class TerminalReporter:
self._tw.sep(sep, title, **markup) self._tw.sep(sep, title, **markup)
def getcategoryletterword(self, rep): def getcategoryletterword(self, rep):
res = self.config.hook.pytest_report_teststatus(rep=rep) res = self.config.hook.pytest_report_teststatus(report=rep)
if res: if res:
return res return res
for cat in 'skipped failed passed ???'.split(): for cat in 'skipped failed passed ???'.split():
@ -184,8 +184,8 @@ class TerminalReporter:
fspath, lineno, msg = self._getreportinfo(item) fspath, lineno, msg = self._getreportinfo(item)
self.write_fspath_result(fspath, "") self.write_fspath_result(fspath, "")
def pytest__teardown_final_logerror(self, rep): def pytest__teardown_final_logerror(self, report):
self.stats.setdefault("error", []).append(rep) self.stats.setdefault("error", []).append(report)
def pytest_runtest_logreport(self, report): def pytest_runtest_logreport(self, report):
rep = report rep = report

View File

@ -35,12 +35,11 @@ def pytest_runtest_makereport(__call__, item, call):
res.failed = True res.failed = True
return res return res
def pytest_report_teststatus(rep): def pytest_report_teststatus(report):
""" return shortletter and verbose word. """ if 'xfail' in report.keywords:
if 'xfail' in rep.keywords: if report.skipped:
if rep.skipped:
return "xfailed", "x", "xfail" return "xfailed", "x", "xfail"
elif rep.failed: elif report.failed:
return "xpassed", "P", "xpass" return "xpassed", "P", "xpass"
# called by the terminalreporter instance/plugin # called by the terminalreporter instance/plugin

View File

@ -1,5 +1,5 @@
import py, os, sys import py, os, sys
from py.__.test.plugin.pytest_capture import CaptureManager from py.__.test.plugin.pytest_capture import CaptureManager, ustream
class TestCaptureManager: class TestCaptureManager:
@ -54,6 +54,29 @@ class TestCaptureManager:
finally: finally:
capouter.reset() capouter.reset()
@py.test.mark.multi(method=['fd', 'sys'])
def test_capturing_unicode(testdir, method):
testdir.makepyfile("""
# taken from issue 227 from nosests
def test_unicode():
import sys
print sys.stdout
print u'b\\u00f6y'
""")
result = testdir.runpytest("--capture=%s" % method)
result.stdout.fnmatch_lines([
"*1 passed*"
])
def test_ustream_helper(testdir):
p = testdir.makepyfile("hello")
f = p.open('w')
#f.encoding = "utf8"
x = ustream(f)
x.write(u'b\\00f6y')
x.close()
def test_collect_capturing(testdir): def test_collect_capturing(testdir):
p = testdir.makepyfile(""" p = testdir.makepyfile("""
print "collect %s failure" % 13 print "collect %s failure" % 13

View File

@ -271,3 +271,13 @@ def test_functional_boxed(testdir):
"*1 failed*" "*1 failed*"
]) ])
def test_callinfo():
ci = runner.CallInfo(lambda: 0, '123')
assert ci.when == "123"
assert ci.result == 0
assert "result" in repr(ci)
ci = runner.CallInfo(lambda: 0/0, '123')
assert ci.when == "123"
assert not hasattr(ci, 'result')
assert ci.excinfo
assert "exc" in repr(ci)

View File

@ -125,18 +125,12 @@ def test_func_generator_setup(testdir):
def test_method_setup_uses_fresh_instances(testdir): def test_method_setup_uses_fresh_instances(testdir):
reprec = testdir.inline_runsource(""" reprec = testdir.inline_runsource("""
class TestSelfState1: class TestSelfState1:
def __init__(self): memory = []
self.hello = 42
def test_hello(self): def test_hello(self):
self.world = 23 self.memory.append(self)
def test_afterhello(self):
assert not hasattr(self, 'world') def test_afterhello(self):
assert self.hello == 42 assert self != self.memory[0]
class TestSelfState2: """)
def test_hello(self): reprec.assertoutcome(passed=2, failed=0)
self.world = 10
def test_world(self):
assert not hasattr(self, 'world')
""")
reprec.assertoutcome(passed=4, failed=0)

View File

@ -123,8 +123,7 @@ class PyCollectorMixin(PyobjMixin, py.test.collect.Collector):
collector=self, name=name, obj=obj) collector=self, name=name, obj=obj)
if res is not None: if res is not None:
return res return res
if (self.classnamefilter(name)) and \ if self._istestclasscandidate(name, obj):
py.std.inspect.isclass(obj):
res = self._deprecated_join(name) res = self._deprecated_join(name)
if res is not None: if res is not None:
return res return res
@ -139,14 +138,25 @@ class PyCollectorMixin(PyobjMixin, py.test.collect.Collector):
else: else:
return self._genfunctions(name, obj) return self._genfunctions(name, obj)
def _istestclasscandidate(self, name, obj):
if self.classnamefilter(name) and \
py.std.inspect.isclass(obj):
if hasinit(obj):
# XXX WARN
return False
return True
def _genfunctions(self, name, funcobj): def _genfunctions(self, name, funcobj):
module = self.getparent(Module).obj module = self.getparent(Module).obj
# due to _buildname2items funcobj is the raw function, we need # due to _buildname2items funcobj is the raw function, we need
# to work to get at the class # to work to get at the class
clscol = self.getparent(Class) clscol = self.getparent(Class)
cls = clscol and clscol.obj or None cls = clscol and clscol.obj or None
metafunc = funcargs.Metafunc(funcobj, config=self.config, cls=cls, module=module) metafunc = funcargs.Metafunc(funcobj, config=self.config,
gentesthook = self.config.hook.pytest_generate_tests.clone(extralookup=module) cls=cls, module=module)
gentesthook = self.config.hook.pytest_generate_tests.clone(
extralookup=module)
gentesthook(metafunc=metafunc) gentesthook(metafunc=metafunc)
if not metafunc._calls: if not metafunc._calls:
return self.Function(name, parent=self) return self.Function(name, parent=self)
@ -371,3 +381,9 @@ class Function(FunctionMixin, py.test.collect.Item):
def __ne__(self, other): def __ne__(self, other):
return not self == other return not self == other
def hasinit(obj):
init = getattr(obj, '__init__', None)
if init:
if not isinstance(init, type(object.__init__)):
return True

View File

@ -67,3 +67,12 @@ class TestGeneralUsage:
"E ImportError: No module named does_not_work", "E ImportError: No module named does_not_work",
]) ])
assert result.ret == 1 assert result.ret == 1
def test_not_collectable_arguments(self, testdir):
p1 = testdir.makepyfile("")
p2 = testdir.makefile(".pyc", "123")
result = testdir.runpytest(p1, p2)
assert result.ret != 0
assert result.stderr.fnmatch_lines([
"*ERROR: can't collect: %s" %(p2,)
])

View File

@ -179,6 +179,18 @@ class TestCollectPluginHooks:
names = [rep.collector.name for rep in colreports] names = [rep.collector.name for rep in colreports]
assert names.count("hello") == 1 assert names.count("hello") == 1
class TestPrunetraceback:
def test_collection_error(self, testdir):
p = testdir.makepyfile("""
import not_exists
""")
result = testdir.runpytest(p)
assert "__import__" not in result.stdout.str(), "too long traceback"
result.stdout.fnmatch_lines([
"*ERROR during collection*",
">*import not_exists*"
])
class TestCustomConftests: class TestCustomConftests:
def test_non_python_files(self, testdir): def test_non_python_files(self, testdir):
testdir.makepyfile(conftest=""" testdir.makepyfile(conftest="""

View File

@ -39,6 +39,19 @@ class TestModule:
modcol = testdir.getmodulecol("pytest_plugins='xasdlkj',") modcol = testdir.getmodulecol("pytest_plugins='xasdlkj',")
py.test.raises(ImportError, "modcol.obj") py.test.raises(ImportError, "modcol.obj")
class TestClass:
def test_class_with_init_not_collected(self, testdir):
modcol = testdir.getmodulecol("""
class TestClass1:
def __init__(self):
pass
class TestClass2(object):
def __init__(self):
pass
""")
l = modcol.collect()
assert len(l) == 0
class TestDisabled: class TestDisabled:
def test_disabled_module(self, testdir): def test_disabled_module(self, testdir):
modcol = testdir.getmodulecol(""" modcol = testdir.getmodulecol("""

View File

@ -31,7 +31,7 @@ def main():
name='py', name='py',
description='py.test and pylib: advanced testing tool and networking lib', description='py.test and pylib: advanced testing tool and networking lib',
long_description = long_description, long_description = long_description,
version= trunk or '1.0.0', version= trunk or '1.0.1',
url='http://pylib.org', url='http://pylib.org',
license='MIT license', license='MIT license',
platforms=['unix', 'linux', 'osx', 'cygwin', 'win32'], platforms=['unix', 'linux', 'osx', 'cygwin', 'win32'],