diff --git a/.hgtags b/.hgtags index e152cc457..e5cfd8b7a 100644 --- a/.hgtags +++ b/.hgtags @@ -1 +1,8 @@ 52c6d9e78777a5a34e813123997dfc614a1a4767 1.0.0b3 +1c7aaa8c61f3b0945921a9acc7beb184201aed4b 1.0.0b4 +1c7aaa8c61f3b0945921a9acc7beb184201aed4b 1.0.0b4 +0000000000000000000000000000000000000000 1.0.0b4 +0000000000000000000000000000000000000000 1.0.0b4 +8cd6eb91eba313b012d6e568f37d844dc0751f2e 1.0.0b4 +8cd6eb91eba313b012d6e568f37d844dc0751f2e 1.0.0b4 +0000000000000000000000000000000000000000 1.0.0b4 diff --git a/CHANGELOG b/CHANGELOG index 9982545f1..f0a0e9e91 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,19 @@ +Changes between 1.0.0b3 and 1.0.0 +============================================= + +* remove scope-argument from request.addfinalizer() because + request.cached_setup has the scope arg. TOOWTDI. + +* perform setup finalization before reporting failures + +* apply modified patches from Andreas Kloeckner to allow + test functions to have no func_code (#22) and to make + "-k" and function keywords work (#20) + +* apply patch from Daniel Peolzleithner (issue #23) + +* resolve issue #18, multiprocessing.Manager() and + redirection clash Changes between 1.0.0b1 and 1.0.0b3 ============================================= diff --git a/MANIFEST b/MANIFEST index fd52490e9..665e10835 100644 --- a/MANIFEST +++ b/MANIFEST @@ -1,20 +1,32 @@ -.hgignore +.hgtags CHANGELOG -LICENSE 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 README.txt -TODO.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 @@ -29,7 +41,6 @@ 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/test.txt doc/test/xunit_setup.txt @@ -59,7 +70,6 @@ example/pytest/test_failures.py example/pytest/test_setup_flow_example.py ez_setup.py py/LICENSE -py/__init__.py py/_com.py py/bin/_findpy.py py/bin/_genscripts.py @@ -165,12 +175,10 @@ 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 @@ -285,7 +293,6 @@ 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 @@ -300,7 +307,6 @@ 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 @@ -328,14 +334,11 @@ 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/pluginmanager.py -py/test/pycollect.py py/test/session.py py/test/testing/__init__.py py/test/testing/acceptance_test.py @@ -350,8 +353,6 @@ 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 @@ -382,5 +383,4 @@ py/xmlobj/testing/__init__.py py/xmlobj/testing/test_html.py py/xmlobj/testing/test_xml.py py/xmlobj/visit.py -py/xmlobj/xml.py -setup.py \ No newline at end of file +py/xmlobj/xml.py \ No newline at end of file diff --git a/doc/announce/release-1.0.0.txt b/doc/announce/release-1.0.0.txt index a63b8b312..372eaf9aa 100644 --- a/doc/announce/release-1.0.0.txt +++ b/doc/announce/release-1.0.0.txt @@ -1,22 +1,38 @@ -py lib 1.0.0: distributed testing and dynamic code deployment -=============================================================== +py.test / py lib 1.0.0: distributed testing and dynamic code deployment +============================================================================ -XXX draft +Welcome to the 1.0 release bringing new flexibility and +power to testing with Python! Main news: -Welcome to the 1.0.0 py lib release - a python library aiming -to support agile and test-driven development. +* new py.test plugin architecture, some examples: -It works with Linux, OSX and Win32, on Python 2.3, 2.4, 2.5 and 2.6. + 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 -Main API/Tool Features: + and much more! + +* funcargs - the new flexible mechanism for managing all your test setup/fixture needs! + +* 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: -* py.test: cross-project testing tool with many advanced features * py.execnet: ad-hoc code distribution to SSH, Socket and local sub processes * py.code: support for dynamically running and debugging python code * py.path: path abstractions over local and subversion files -Download/Install: http://codespeak.net/py/1.0.0/download.html -Documentation/API: http://codespeak.net/py/1.0.0/index.html +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!) + +Download/Install: http://codespeak.net/py/dist/download.html best, holger diff --git a/doc/download.txt b/doc/download.txt index 170b2e1b5..39c02b91f 100644 --- a/doc/download.txt +++ b/doc/download.txt @@ -2,7 +2,7 @@ Downloading ============== -.. _`PyPI project page`: http://pypi.python.org/pypi?%3Aaction=pkg_edit&name=py +.. _`PyPI project page`: http://pypi.python.org/pypi/py/ Latest Release, see `PyPI project page`_ diff --git a/doc/test/funcargs.txt b/doc/test/funcargs.txt index b4b915eb4..45cdb137b 100644 --- a/doc/test/funcargs.txt +++ b/doc/test/funcargs.txt @@ -120,7 +120,27 @@ to access test configuration and test context: ``request.param``: if exists was passed by a `parametrizing test generator`_ -perform scoped setup and teardown +teardown/cleanup after test function execution +------------------------------------------------ + +.. 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 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 + + +perform scope-specific setup and cleanup --------------------------------------------- .. sourcecode:: python @@ -148,30 +168,6 @@ example for providing a value that is to be setup only once during a test run: ) -cleanup after test function execution ---------------------------------------------- - -.. sourcecode:: python - - def addfinalizer(func, scope="function"): - """ register calling a a finalizer function. - 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.addfinalizer()`` is useful for scheduling teardown -functions. The given scope determines when the teardown function -will be called. Here is a basic example for providing a ``myfile`` -object that is to be closed when the 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 - requesting values of other funcargs --------------------------------------------- diff --git a/py/__init__.py b/py/__init__.py index 2188708ed..fd95abfba 100644 --- a/py/__init__.py +++ b/py/__init__.py @@ -19,7 +19,7 @@ For questions please check out http://pylib.org/contact.html """ from initpkg import initpkg -version = "1.0.0b3" +version = "trunk" initpkg(__name__, description = "py.test and pylib: advanced testing tool and networking lib", diff --git a/py/io/stdcapture.py b/py/io/stdcapture.py index 2c4bd376c..a43d4bf0e 100644 --- a/py/io/stdcapture.py +++ b/py/io/stdcapture.py @@ -136,6 +136,11 @@ class DontReadFromInput: readline = read readlines = read __iter__ = read + + def fileno(self): + raise ValueError("redirected Stdin is pseudofile, has no fileno()") + def isatty(self): + return False try: devnullpath = os.devnull diff --git a/py/io/testing/test_stdcapture.py b/py/io/testing/test_stdcapture.py index e6cb8ea1f..5e857c5b2 100644 --- a/py/io/testing/test_stdcapture.py +++ b/py/io/testing/test_stdcapture.py @@ -1,6 +1,15 @@ import os, sys import py +def test_dontreadfrominput(): + from py.__.io.stdcapture import DontReadFromInput + f = DontReadFromInput() + assert not f.isatty() + py.test.raises(IOError, f.read) + py.test.raises(IOError, f.readlines) + py.test.raises(IOError, iter, f) + py.test.raises(ValueError, f.fileno) + class TestStdCapture: def getcapture(self, **kw): return py.io.StdCapture(**kw) diff --git a/py/test/collect.py b/py/test/collect.py index 33611274d..a56ec496c 100644 --- a/py/test/collect.py +++ b/py/test/collect.py @@ -204,7 +204,7 @@ class Node(object): def _matchonekeyword(self, key, chain): elems = key.split(".") # XXX O(n^2), anyone cares? - chain = [item._keywords() for item in chain if item._keywords()] + chain = [item.readkeywords() for item in chain if item._keywords()] for start, _ in enumerate(chain): if start + len(elems) > len(chain): return False diff --git a/py/test/funcargs.py b/py/test/funcargs.py index ec30c353e..74f36a054 100644 --- a/py/test/funcargs.py +++ b/py/test/funcargs.py @@ -113,7 +113,7 @@ class FuncargRequest: val = setup() cache[cachekey] = val if teardown is not None: - self.addfinalizer(lambda: teardown(val), scope=scope) + self._addfinalizer(lambda: teardown(val), scope=scope) return val def getfuncargvalue(self, argname): @@ -142,10 +142,14 @@ class FuncargRequest: return None raise ValueError("unknown finalization scope %r" %(scope,)) - def addfinalizer(self, finalizer, scope="function"): + def _addfinalizer(self, finalizer, scope): colitem = self._getscopeitem(scope) self.config._setupstate.addfinalizer(finalizer=finalizer, colitem=colitem) + def addfinalizer(self, finalizer): + """ call the given finalizer after test function finished execution. """ + self._addfinalizer(finalizer, scope="function") + def __repr__(self): return "" %(self._pyfuncitem) diff --git a/py/test/plugin/pytest_runner.py b/py/test/plugin/pytest_runner.py index 62404084a..151881567 100644 --- a/py/test/plugin/pytest_runner.py +++ b/py/test/plugin/pytest_runner.py @@ -19,11 +19,14 @@ def pytest_addoption(parser): action="store_true", dest="boxed", default=False, help="box each test run in a separate process") +# XXX move to pytest_sessionstart and fix py.test owns tests def pytest_configure(config): config._setupstate = SetupState() -def pytest_unconfigure(config): - config._setupstate.teardown_all() +def pytest_sessionfinish(session, exitstatus, excrepr=None): + # XXX see above + if hasattr(session.config, '_setupstate'): + session.config._setupstate.teardown_all() def pytest_make_collect_report(collector): call = collector.config.guardedcall( diff --git a/py/test/plugin/pytest_terminal.py b/py/test/plugin/pytest_terminal.py index 1098b3095..1133efbbd 100644 --- a/py/test/plugin/pytest_terminal.py +++ b/py/test/plugin/pytest_terminal.py @@ -204,6 +204,7 @@ class TerminalReporter: msg = "python: platform %s -- Python %s" % (sys.platform, verinfo) if self.config.option.verbose or self.config.option.debug: msg += " -- " + str(sys.executable) + msg += " -- pytest-%s" % (py.__version__) self.write_line(msg) if self.config.option.debug or self.config.option.traceconfig: @@ -227,7 +228,8 @@ class TerminalReporter: for i, testarg in py.builtin.enumerate(self.config.args): self.write_line("test object %d: %s" %(i+1, testarg)) - def pytest_sessionfinish(self, session, exitstatus, excrepr=None): + def pytest_sessionfinish(self, __call__, session, exitstatus, excrepr=None): + __call__.execute() self._tw.line("") if exitstatus in (0, 1, 2): self.summary_failures() diff --git a/py/test/pycollect.py b/py/test/pycollect.py index eb010f66a..76394a588 100644 --- a/py/test/pycollect.py +++ b/py/test/pycollect.py @@ -133,11 +133,11 @@ class PyCollectorMixin(PyobjMixin, py.test.collect.Collector): res = self._deprecated_join(name) if res is not None: return res - if obj.func_code.co_flags & 32: # generator function + if is_generator(obj): # XXX deprecation warning return self.Generator(name, parent=self) - else: - return self._genfunctions(name, obj) + else: + return self._genfunctions(name, obj) def _genfunctions(self, name, funcobj): module = self.getparent(Module).obj @@ -152,6 +152,12 @@ class PyCollectorMixin(PyobjMixin, py.test.collect.Collector): return self.Function(name, parent=self) return funcargs.FunctionCollector(name=name, parent=self, calls=metafunc._calls) + +def is_generator(func): + try: + return (func.func_code.co_flags & 32) # generator function + except AttributeError: # c / builtin functions have no func_code + return False class Module(py.test.collect.File, PyCollectorMixin): def _getobj(self): diff --git a/py/test/testing/test_funcargs.py b/py/test/testing/test_funcargs.py index 1b4b4ea86..4f981c40c 100644 --- a/py/test/testing/test_funcargs.py +++ b/py/test/testing/test_funcargs.py @@ -163,15 +163,11 @@ class TestRequest: req._fillfuncargs() assert item.funcargs == {'something': 1} - def test_request_addfinalizer_scopes(self, testdir): + def test_request_addfinalizer(self, testdir): item = testdir.getitem(""" teardownlist = [] def pytest_funcarg__something(request): - for scope in ("function", "module", "session"): - request.addfinalizer( - lambda x=scope: teardownlist.append(x), - scope=scope) - + request.addfinalizer(lambda: teardownlist.append(1)) def test_func(something): pass """) req = funcargs.FuncargRequest(item) @@ -183,16 +179,7 @@ class TestRequest: assert not teardownlist ss.teardown_exact(item) print ss.stack - assert teardownlist == ['function'] - ss.teardown_exact(item.parent) - assert teardownlist == ['function', 'module'] - ss.teardown_all() - assert teardownlist == ['function', 'module', 'session'] - - def test_request_addfinalizer_unknown_scope(self, testdir): - item = testdir.getitem("def test_func(): pass") - req = funcargs.FuncargRequest(item) - py.test.raises(ValueError, "req.addfinalizer(None, scope='xyz')") + assert teardownlist == [1] def test_request_getmodulepath(self, testdir): modcol = testdir.getmodulecol("def test_somefunc(): pass") @@ -200,7 +187,6 @@ class TestRequest: req = funcargs.FuncargRequest(item) assert req.fspath == modcol.fspath - class TestRequestCachedSetup: def test_request_cachedsetup(self, testdir): item1,item2 = testdir.getitems(""" diff --git a/py/test/testing/test_genitems.py b/py/test/testing/test_genitems.py index f917e5386..ebe7a9f6f 100644 --- a/py/test/testing/test_genitems.py +++ b/py/test/testing/test_genitems.py @@ -121,3 +121,16 @@ class TestKeywordSelection: item = dlist[0].items[0] assert item.name == "test_one" + + def test_keyword_extra(self, testdir): + p = testdir.makepyfile(""" + def test_one(): + assert 0 + test_one.mykeyword = True + """) + reprec = testdir.inline_run("-k", "-mykeyword", p) + passed, skipped, failed = reprec.countoutcomes() + assert passed + skipped + failed == 0 + reprec = testdir.inline_run("-k", "mykeyword", p) + passed, skipped, failed = reprec.countoutcomes() + assert failed == 1 diff --git a/setup.py b/setup.py index 69e618d4b..3976cd284 100644 --- a/setup.py +++ b/setup.py @@ -2,21 +2,16 @@ autogenerated by gensetup.py setup file for 'py' package based on: -changeset: 1164:506f4c5f741b -branch: trunk -tag: tip -user: holger krekel -date: Tue Jun 23 11:33:20 2009 +0200 -summary: Added tag 1.0.0b3 for changeset 52c6d9e78777 - +branch: 1.0.x +revision: 1180:548548a177f3fa2fa175dfa9b8e96188e94ab6c8 """ import os, sys import ez_setup ez_setup.use_setuptools() -from setuptools import setup, Extension - +from setuptools import setup + long_description = """ advanced testing and development support library: @@ -41,7 +36,7 @@ def main(): name='py', description='py.test and pylib: advanced testing tool and networking lib', long_description = long_description, - version='1.0.0b3', + version='trunk', url='http://pylib.org', license='MIT license', platforms=['unix', 'linux', 'osx', 'cygwin', 'win32'], @@ -66,10 +61,7 @@ def main(): 'Topic :: System :: Distributed Computing', 'Topic :: Utilities', 'Programming Language :: Python'], - packages=['example.funcarg.mysetup', - 'example.funcarg.mysetup2', - 'py', - 'py.builtin', + packages=['py.builtin', 'py.builtin.testing', 'py.cmdline', 'py.cmdline.testing',