From commits-noreply at bitbucket.org Sun Dec 6 18:47:37 2009 From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org) Date: Sun, 6 Dec 2009 17:47:37 +0000 (UTC) Subject: [py-svn] py-trunk commit aee80e832ce5: 2.7's TextIO requires unicode Message-ID: <20091206174737.CBB167EE74@bitbucket.org> # HG changeset patch -- Bitbucket.org # Project py-trunk # URL http://bitbucket.org/hpk42/py-trunk/overview/ # User Benjamin Peterson # Date 1260121661 21600 # Node ID aee80e832ce5305051ccae42427252ec1189929d # Parent f29d1d8a63849e6d6fcb70593c671ed07f69842b 2.7's TextIO requires unicode --- a/testing/path/test_svnauth.py +++ b/testing/path/test_svnauth.py @@ -234,7 +234,7 @@ class TestSvnURLAuth(object): def test_log(self): u = svnurl_no_svn('http://foo.bar/svn/foo', auth=self.auth) - u.popen_output = py.io.TextIO('''\ + u.popen_output = py.io.TextIO(py.builtin._totext('''\ @@ -244,7 +244,7 @@ class TestSvnURLAuth(object): -''') +''', 'ascii')) u.check = lambda *args, **kwargs: True ret = u.log(10, 20, verbose=True) assert '--username="foo" --password="bar"' in u.commands[0] From commits-noreply at bitbucket.org Sun Dec 6 19:19:15 2009 From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org) Date: Sun, 6 Dec 2009 18:19:15 +0000 (UTC) Subject: [py-svn] py-trunk commit 9b7dfb3a1cae: skip tests properly Message-ID: <20091206181915.8B56F7EEEA@bitbucket.org> # HG changeset patch -- Bitbucket.org # Project py-trunk # URL http://bitbucket.org/hpk42/py-trunk/overview/ # User holger krekel # Date 1260123524 -3600 # Node ID 9b7dfb3a1cae2de540e9c621e12910f7b3a030bb # Parent aee80e832ce5305051ccae42427252ec1189929d skip tests properly --- a/bin-for-dist/test_install.py +++ b/bin-for-dist/test_install.py @@ -1,7 +1,8 @@ import py import subprocess import os -import execnet + +execnet = py.test.importorskip("execnet") # --- a/conftest.py +++ b/conftest.py @@ -2,6 +2,8 @@ import py pytest_plugins = '_pytest doctest pytester'.split() +collect_ignore = ['build', 'doc/_build'] + rsyncdirs = ['conftest.py', 'bin', 'py', 'doc', 'testing'] try: --- a/CHANGELOG +++ b/CHANGELOG @@ -2,6 +2,7 @@ Changes between 1.1.2 and 1.1.1 ===================================== - fix assert reinterpreation that sees a call containing "keyword=..." +- skip some install-tests if no execnet is available Changes between 1.1.1 and 1.1.0 ===================================== From commits-noreply at bitbucket.org Sun Dec 6 19:19:17 2009 From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org) Date: Sun, 6 Dec 2009 18:19:17 +0000 (UTC) Subject: [py-svn] py-trunk commit ae9f5238dc28: update some small issues Message-ID: <20091206181917.3AC8A7EEEB@bitbucket.org> # HG changeset patch -- Bitbucket.org # Project py-trunk # URL http://bitbucket.org/hpk42/py-trunk/overview/ # User holger krekel # Date 1260123533 -3600 # Node ID ae9f5238dc28ba4b3cafa7355c68e6467111ce0e # Parent 9b7dfb3a1cae2de540e9c621e12910f7b3a030bb update some small issues --- a/ISSUES.txt +++ b/ISSUES.txt @@ -1,7 +1,7 @@ consider conftest hooks only for items below the dir --------------------------------------------------------- -tags: bug +tags: bug 1.1.2 currently conftest hooks remain registered throughout the whole testing process. Consider to only have them @@ -16,3 +16,20 @@ for not considering a function for test maybe also introduce a py.test.mark.test to explicitely mark a function to become a tested one. Lookup Java JUnit recent strategies/syntax. + +make node._checkcollectable more robust +------------------------------------------------- +tags: bug 1.1.2 + +currently node._checkcollectable() can raise +exceptions for all kinds of reasons ('conftest.py' loading +problems, missing rsync-dirs, platform-skip-at-import-level +issues, ...). It should just return True/False and cause +a good error message. + +call termination with small timeout +------------------------------------------------- +tags: feature 1.1.2 + +Call gateway group termination with a small timeout if available. +Should make dist-testing less likely to leave lost processes. From commits-noreply at bitbucket.org Mon Dec 7 10:23:36 2009 From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org) Date: Mon, 7 Dec 2009 09:23:36 +0000 (UTC) Subject: [py-svn] py-trunk commit be19c0e354ef: adding a warning note, see also issue64. Message-ID: <20091207092336.2FD717EEEB@bitbucket.org> # HG changeset patch -- Bitbucket.org # Project py-trunk # URL http://bitbucket.org/hpk42/py-trunk/overview/ # User holger krekel # Date 1260177784 -3600 # Node ID be19c0e354ef236a6009f0d79c334cd2638075e5 # Parent ae9f5238dc28ba4b3cafa7355c68e6467111ce0e adding a warning note, see also issue64. --- a/doc/test/dist.txt +++ b/doc/test/dist.txt @@ -62,6 +62,13 @@ and send them to remote places for execu You can specify multiple ``--rsyncdir`` directories to be sent to the remote side. +**NOTE:** For py.test to collect and send tests correctly +you not only need to make sure all code and tests +directories are rsynced, but that any test (sub) directory +also has an ``__init__.py`` file because internally +py.test references tests as a fully qualified python +module path. **You will otherwise get strange errors** +during setup of the remote side. Sending tests to remote Socket Servers ---------------------------------------- From commits-noreply at bitbucket.org Mon Dec 7 23:55:06 2009 From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org) Date: Mon, 7 Dec 2009 22:55:06 +0000 (UTC) Subject: [py-svn] py-trunk commit fbc0cbcc5232: another issue Message-ID: <20091207225506.8CC507EE78@bitbucket.org> # HG changeset patch -- Bitbucket.org # Project py-trunk # URL http://bitbucket.org/hpk42/py-trunk/overview/ # User holger krekel # Date 1260188844 -3600 # Node ID fbc0cbcc52326caf934cf29e08f0090c16b8e719 # Parent be19c0e354ef236a6009f0d79c334cd2638075e5 another issue --- a/ISSUES.txt +++ b/ISSUES.txt @@ -33,3 +33,14 @@ tags: feature 1.1.2 Call gateway group termination with a small timeout if available. Should make dist-testing less likely to leave lost processes. + +make capfd skip if 'dup' is not available +------------------------------------------------------- + +tags: feature 1.1.2 + +currently, using 'capfd' as a funcarg will fail because +it cannot call os.dup on setup. Should cause a skip. + +introduce multi-install, i.e. py.test3, py.test-pypy, py.test-jython +and maybe a commandline-"suffix" override? From commits-noreply at bitbucket.org Mon Dec 7 23:55:08 2009 From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org) Date: Mon, 7 Dec 2009 22:55:08 +0000 (UTC) Subject: [py-svn] py-trunk commit 7ce15b038b11: note down dist-testing/execnet issue Message-ID: <20091207225508.3F1D37EE85@bitbucket.org> # HG changeset patch -- Bitbucket.org # Project py-trunk # URL http://bitbucket.org/hpk42/py-trunk/overview/ # User holger krekel # Date 1260226462 -3600 # Node ID 7ce15b038b119564345a41ec9156a43c3c79c86c # Parent fbc0cbcc52326caf934cf29e08f0090c16b8e719 note down dist-testing/execnet issue --- a/ISSUES.txt +++ b/ISSUES.txt @@ -44,3 +44,12 @@ it cannot call os.dup on setup. Should introduce multi-install, i.e. py.test3, py.test-pypy, py.test-jython and maybe a commandline-"suffix" override? + +fix dist-testing: execnet needs to be rsynced over automatically +------------------------------------------------------------------ + +tags: bug 1.1.2 +bb: http://bitbucket.org/hpk42/py-trunk/issue/65/ + +execnet is not rsynced so fails if run in an ssh-situation. +write test and fix. From commits-noreply at bitbucket.org Tue Dec 8 00:07:06 2009 From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org) Date: Mon, 7 Dec 2009 23:07:06 +0000 (UTC) Subject: [py-svn] py-trunk commit 6b1b1a3414c3: add note related to issue64 Message-ID: <20091207230706.42DDC7EE78@bitbucket.org> # HG changeset patch -- Bitbucket.org # Project py-trunk # URL http://bitbucket.org/hpk42/py-trunk/overview/ # User holger krekel # Date 1260227210 -3600 # Node ID 6b1b1a3414c3f5563a5540c73173720fcb318e8f # Parent 7ce15b038b119564345a41ec9156a43c3c79c86c add note related to issue64 --- a/ISSUES.txt +++ b/ISSUES.txt @@ -53,3 +53,14 @@ bb: http://bitbucket.org/hpk42/py-trunk/ execnet is not rsynced so fails if run in an ssh-situation. write test and fix. + + +relax requirement to have tests/testing contain an __init__ +---------------------------------------------------------------- +tags: feature 1.1.2 +bb: http://bitbucket.org/hpk42/py-trunk/issue/64 + +A local test run of a "tests" directory may work +but a remote one fail because the tests directory +does not contain an "__init__.py". Either give +an error or make it work without the __init__.py From commits-noreply at bitbucket.org Thu Dec 24 13:14:09 2009 From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org) Date: Thu, 24 Dec 2009 12:14:09 +0000 (UTC) Subject: [py-svn] py-trunk commit 07fd4e3a6ac9: fix links, partially thanks to fijal Message-ID: <20091224121409.751687EF47@bitbucket.org> # HG changeset patch -- Bitbucket.org # Project py-trunk # URL http://bitbucket.org/hpk42/py-trunk/overview/ # User holger krekel # Date 1260537558 -3600 # Node ID 07fd4e3a6ac9cbc0d0f6c29bff84d17b6fa0a7f9 # Parent 6b1b1a3414c3f5563a5540c73173720fcb318e8f fix links, partially thanks to fijal --- a/doc/test/plugin/links.txt +++ b/doc/test/plugin/links.txt @@ -1,42 +1,42 @@ .. _`helpconfig`: helpconfig.html .. _`terminal`: terminal.html -.. _`pytest_recwarn.py`: http://bitbucket.org/hpk42/py-trunk/raw/1.1.0/py/plugin/pytest_recwarn.py +.. _`pytest_recwarn.py`: http://bitbucket.org/hpk42/py-trunk/raw/1.1.1/py/plugin/pytest_recwarn.py .. _`unittest`: unittest.html -.. _`pytest_monkeypatch.py`: http://bitbucket.org/hpk42/py-trunk/raw/1.1.0/py/plugin/pytest_monkeypatch.py +.. _`pytest_monkeypatch.py`: http://bitbucket.org/hpk42/py-trunk/raw/1.1.1/py/plugin/pytest_monkeypatch.py .. _`pastebin`: pastebin.html .. _`skipping`: skipping.html .. _`plugins`: index.html .. _`mark`: mark.html .. _`tmpdir`: tmpdir.html -.. _`pytest_doctest.py`: http://bitbucket.org/hpk42/py-trunk/raw/1.1.0/py/plugin/pytest_doctest.py +.. _`pytest_doctest.py`: http://bitbucket.org/hpk42/py-trunk/raw/1.1.1/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 +.. _`pytest_nose.py`: http://bitbucket.org/hpk42/py-trunk/raw/1.1.1/py/plugin/pytest_nose.py +.. _`pytest_restdoc.py`: http://bitbucket.org/hpk42/py-trunk/raw/1.1.1/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 +.. _`pytest_pastebin.py`: http://bitbucket.org/hpk42/py-trunk/raw/1.1.1/py/plugin/pytest_pastebin.py +.. _`pytest_tmpdir.py`: http://bitbucket.org/hpk42/py-trunk/raw/1.1.1/py/plugin/pytest_tmpdir.py +.. _`pytest_figleaf.py`: http://bitbucket.org/hpk42/py-trunk/raw/1.1.1/py/plugin/pytest_figleaf.py +.. _`pytest_hooklog.py`: http://bitbucket.org/hpk42/py-trunk/raw/1.1.1/py/plugin/pytest_hooklog.py +.. _`pytest_skipping.py`: http://bitbucket.org/hpk42/py-trunk/raw/1.1.1/py/plugin/pytest_skipping.py .. _`checkout the py.test development version`: ../../install.html#checkout -.. _`pytest_helpconfig.py`: http://bitbucket.org/hpk42/py-trunk/raw/1.1.0/py/plugin/pytest_helpconfig.py +.. _`pytest_helpconfig.py`: http://bitbucket.org/hpk42/py-trunk/raw/1.1.1/py/plugin/pytest_helpconfig.py .. _`oejskit`: oejskit.html .. _`doctest`: doctest.html -.. _`pytest_mark.py`: http://bitbucket.org/hpk42/py-trunk/raw/1.1.0/py/plugin/pytest_mark.py +.. _`pytest_mark.py`: http://bitbucket.org/hpk42/py-trunk/raw/1.1.1/py/plugin/pytest_mark.py .. _`get in contact`: ../../contact.html -.. _`pytest_capture.py`: http://bitbucket.org/hpk42/py-trunk/raw/1.1.0/py/plugin/pytest_capture.py +.. _`pytest_capture.py`: http://bitbucket.org/hpk42/py-trunk/raw/1.1.1/py/plugin/pytest_capture.py .. _`figleaf`: figleaf.html .. _`customize`: ../customize.html .. _`hooklog`: hooklog.html -.. _`pytest_terminal.py`: http://bitbucket.org/hpk42/py-trunk/raw/1.1.0/py/plugin/pytest_terminal.py +.. _`pytest_terminal.py`: http://bitbucket.org/hpk42/py-trunk/raw/1.1.1/py/plugin/pytest_terminal.py .. _`recwarn`: recwarn.html -.. _`pytest_pdb.py`: http://bitbucket.org/hpk42/py-trunk/raw/1.1.0/py/plugin/pytest_pdb.py +.. _`pytest_pdb.py`: http://bitbucket.org/hpk42/py-trunk/raw/1.1.1/py/plugin/pytest_pdb.py .. _`monkeypatch`: monkeypatch.html .. _`coverage`: coverage.html .. _`resultlog`: resultlog.html .. _`django`: django.html .. _`xmlresult`: xmlresult.html -.. _`pytest_unittest.py`: http://bitbucket.org/hpk42/py-trunk/raw/1.1.0/py/plugin/pytest_unittest.py +.. _`pytest_unittest.py`: http://bitbucket.org/hpk42/py-trunk/raw/1.1.1/py/plugin/pytest_unittest.py .. _`nose`: nose.html -.. _`pytest_resultlog.py`: http://bitbucket.org/hpk42/py-trunk/raw/1.1.0/py/plugin/pytest_resultlog.py +.. _`pytest_resultlog.py`: http://bitbucket.org/hpk42/py-trunk/raw/1.1.1/py/plugin/pytest_resultlog.py .. _`pdb`: pdb.html --- a/doc/test/customize.txt +++ b/doc/test/customize.txt @@ -340,7 +340,7 @@ A ``report`` object contains status and report.failed = True or False report.skipped = True or False -The `pytest_terminal plugin`_ uses this hook to print information +The `terminal plugin`_ uses this hook to print information about a test run. The whole protocol described here is implemented via this hook: @@ -359,8 +359,8 @@ The call object contains information abo call.when = "setup", "call" or "teardown" call.outerr = None or tuple of strings representing captured stdout/stderr -.. _`pytest_pdb plugin`: http://bitbucket.org/hpk42/py-trunk/src/tip/py/test/plugin/pytest_pdb.py -.. _`pytest_terminal plugin`: http://bitbucket.org/hpk42/py-trunk/src/tip/py/test/plugin/pytest_terminal.py +.. _`pytest_pdb plugin`: plugin/pdb.html +.. _`terminal plugin`: plugin/terminal.html generic collection hooks --- a/doc/test/plugin/terminal.txt +++ b/doc/test/plugin/terminal.txt @@ -18,7 +18,7 @@ command line options ``-l, --showlocals`` show locals in tracebacks (disabled by default). ``--report=opts`` - comma separated reporting options + comma separated options, valid: skipped,xfailed ``--tb=style`` traceback verboseness (long/short/no). ``--fulltrace`` From commits-noreply at bitbucket.org Thu Dec 24 13:14:11 2009 From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org) Date: Thu, 24 Dec 2009 12:14:11 +0000 (UTC) Subject: [py-svn] py-trunk commit 02f1d618996b: some doc fixes Message-ID: <20091224121411.82E617EF4F@bitbucket.org> # HG changeset patch -- Bitbucket.org # Project py-trunk # URL http://bitbucket.org/hpk42/py-trunk/overview/ # User holger krekel # Date 1261038821 -3600 # Node ID 02f1d618996b91870c382e0e68f1a76efab345ba # Parent 07fd4e3a6ac9cbc0d0f6c29bff84d17b6fa0a7f9 some doc fixes --- a/doc/test/customize.txt +++ b/doc/test/customize.txt @@ -273,6 +273,8 @@ for example: When the test run finishes this corresponding finalizer hook is called: +.. sourcecode:: python + def pytest_unconfigure(config): ... @@ -281,7 +283,9 @@ adding global py.test helpers and functi -------------------------------------------------------------------- If you want to make global helper functions or objects available -to your test code you can implement: +to your test code you can implement: + +.. sourcecode:: python def pytest_namespace(): """ return dictionary with items to be made available on py.test. namespace """ --- a/doc/test/plugin/hookspec.txt +++ b/doc/test/plugin/hookspec.txt @@ -62,7 +62,7 @@ hook specification sourcecode pytest_pycollect_makeitem.firstresult = True def pytest_pyfunc_call(pyfuncitem): - """ perform function call to the with the given function arguments. """ + """ call underlying test function. """ pytest_pyfunc_call.firstresult = True def pytest_generate_tests(metafunc): @@ -73,7 +73,7 @@ hook specification sourcecode # ------------------------------------------------------------------------- def pytest_runtest_protocol(item): - """ implement fixture, run and report protocol. """ + """ implement fixture, run and report about the given test item. """ pytest_runtest_protocol.firstresult = True def pytest_runtest_setup(item): @@ -86,7 +86,7 @@ hook specification sourcecode """ called after pytest_runtest_call(). """ def pytest_runtest_makereport(item, call): - """ make ItemTestReport for the given item and call outcome. """ + """ make a test report for the given item and call outcome. """ pytest_runtest_makereport.firstresult = True def pytest_runtest_logreport(report): --- a/py/plugin/hookspec.py +++ b/py/plugin/hookspec.py @@ -56,7 +56,7 @@ def pytest_pycollect_makeitem(collector, pytest_pycollect_makeitem.firstresult = True def pytest_pyfunc_call(pyfuncitem): - """ perform function call to the with the given function arguments. """ + """ call underlying test function. """ pytest_pyfunc_call.firstresult = True def pytest_generate_tests(metafunc): @@ -67,7 +67,7 @@ def pytest_generate_tests(metafunc): # ------------------------------------------------------------------------- def pytest_runtest_protocol(item): - """ implement fixture, run and report protocol. """ + """ implement fixture, run and report about the given test item. """ pytest_runtest_protocol.firstresult = True def pytest_runtest_setup(item): @@ -80,7 +80,7 @@ def pytest_runtest_teardown(item): """ called after pytest_runtest_call(). """ def pytest_runtest_makereport(item, call): - """ make ItemTestReport for the given item and call outcome. """ + """ make a test report for the given item and call outcome. """ pytest_runtest_makereport.firstresult = True def pytest_runtest_logreport(report): From commits-noreply at bitbucket.org Thu Dec 24 13:14:13 2009 From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org) Date: Thu, 24 Dec 2009 12:14:13 +0000 (UTC) Subject: [py-svn] py-trunk commit 652c3c746a6e: adding a link to the tutorial page Message-ID: <20091224121413.500067EF52@bitbucket.org> # HG changeset patch -- Bitbucket.org # Project py-trunk # URL http://bitbucket.org/hpk42/py-trunk/overview/ # User holger krekel # Date 1261343444 -3600 # Node ID 652c3c746a6e9435a6b835bc31f809d71db6da39 # Parent 02f1d618996b91870c382e0e68f1a76efab345ba adding a link to the tutorial page --- a/doc/test/talks.txt +++ b/doc/test/talks.txt @@ -26,8 +26,11 @@ distributed testing: plugin specific examples: +- `skipping slow tests by default in py.test`_ (blog entry) + - `many examples in the docs for plugins`_ +.. _`skipping slow tests by default in py.test`: http://bruynooghe.blogspot.com/2009/12/skipping-slow-test-by-default-in-pytest.html .. _`making funcargs dependendent on command line options`: funcargs.html#tut-cmdlineoption .. _`many examples in the docs for plugins`: plugin/index.html .. _`monkeypatch plugin`: plugin/monkeypatch.html From commits-noreply at bitbucket.org Thu Dec 24 13:14:13 2009 From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org) Date: Thu, 24 Dec 2009 12:14:13 +0000 (UTC) Subject: [py-svn] py-trunk commit b3eda3294727: add some issues Message-ID: <20091224121413.6CE1E7EF53@bitbucket.org> # HG changeset patch -- Bitbucket.org # Project py-trunk # URL http://bitbucket.org/hpk42/py-trunk/overview/ # User holger krekel # Date 1261343496 -3600 # Node ID b3eda3294727a05c8e9eb6c3a36f8f2a81cc4e5d # Parent 652c3c746a6e9435a6b835bc31f809d71db6da39 add some issues --- a/ISSUES.txt +++ b/ISSUES.txt @@ -8,7 +8,7 @@ the whole testing process. Consider to called if their filesystem location is below a test item. -introduce py.test.mark.ignoretest +introduce py.test.mark.nocollect ------------------------------------------------------- tags: feature @@ -17,6 +17,43 @@ maybe also introduce a py.test.mark.test mark a function to become a tested one. Lookup Java JUnit recent strategies/syntax. +capture plugin: skip on missing os.dup for 'capfd' +-------------------------------------------------------- + +tags: feature + +Currrently for Jython one needs do an explicit skip like this: + + @py.test.mark.skipif("not hasattr(os, 'dup')") + +to avoid a failure when 'capfd' is used. Instead +provide an automatic skip. + + +have imported module mismatch honour relative paths +-------------------------------------------------------- +tags: bug + +With 1.1.1 py.test fails at least on windows if an import +is relative and compared against an absolute conftest.py +path. Normalize. + +allow plugins/conftests to show extra header information +-------------------------------------------------------- +tags: feature + +The test-report header should optionally show information +about the under-test package and versions/locations of +involved packages. + +install py.test with interpreter-specific prefixes +-------------------------------------------------------- +tags: feature + +When installing under python3, jython or pypy-c it is +desirable to (additionally?) install py.test with +respective suffixes. + make node._checkcollectable more robust ------------------------------------------------- tags: bug 1.1.2 From commits-noreply at bitbucket.org Thu Dec 24 13:14:15 2009 From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org) Date: Thu, 24 Dec 2009 12:14:15 +0000 (UTC) Subject: [py-svn] py-trunk commit 96fba87ba03f: create version/interpreter differentiated py.test$VER for cpython, jython, pypy-c's, prepare 1.1.2 release Message-ID: <20091224121415.24C0F7EF54@bitbucket.org> # HG changeset patch -- Bitbucket.org # Project py-trunk # URL http://bitbucket.org/hpk42/py-trunk/overview/ # User holger krekel # Date 1261594732 -3600 # Node ID 96fba87ba03f6f3c5af627d6cf3fd95bdb08516a # Parent b3eda3294727a05c8e9eb6c3a36f8f2a81cc4e5d create version/interpreter differentiated py.test$VER for cpython, jython, pypy-c's, prepare 1.1.2 release --- a/doc/install.txt +++ b/doc/install.txt @@ -3,19 +3,30 @@ Downloading ============== -.. _`PyPI project page`: http://pypi.python.org/pypi/py/ +.. _`index page`: http://pypi.python.org/pypi/py/ -py.test/pylib compat/install info in a nutshell +py.test/pylib installation info in a nutshell =================================================== -PyPI Pyckage name: "**py**", see `PyPI project page`_ for latest version +**Pythons**: 2.4, 2.5, 2.6, 3.0, 3.1, Jython-2.5.1, PyPy-1.1 -Installers: easy_install_ and pip_, setuptools_ or Distribute_ +**Operating systems**: Linux, Windows, OSX, Unix -Pythons: 2.4, 2.5, 2.6, 3.0, 3.1, Jython-2.5.1, PyPy-1.1 +**Requirements**: setuptools_ or Distribute_ -Operating systems: Linux, Windows and OSX + probably many others +**Installers**: easy_install_ and pip_ + +**Distribution names**: + +* PyPI name: ``py`` (see `index page`_ for versions) +* redhat fedora: ``pylib`` +* debian: ``python-codespeak-lib`` +* gentoo: ``pylib`` + +**Installed scripts**: see `bin`_ for which scripts are installed. + +.. _`bin`: bin.html Best practise: install tool and dependencies virtually @@ -28,12 +39,6 @@ you need to run your tests. Local virtu (as opposed to system-wide "global" environments) make for a more reproducible and reliable test environment. -Note: as of November 2009 pytest/pylib 1.1 RPMs and DEB packages -are not available. If you want to easy_install the newest py.test -and pylib do everyone a favour and uninstall older versions -from the global system e.g. like this on Ubuntu:: - - sudo apt-get remove --purge python-codespeak-lib .. _`virtualenv`: http://pypi.python.org/pypi/virtualenv .. _`buildout`: http://www.buildout.org/ --- a/py/__init__.py +++ b/py/__init__.py @@ -9,7 +9,7 @@ dictionary or an import path. (c) Holger Krekel and others, 2009 """ -version = "1.1.1" +version = "1.1.2" __version__ = version = version or "1.1.x" import py.apipkg --- a/CHANGELOG +++ b/CHANGELOG @@ -1,7 +1,11 @@ Changes between 1.1.2 and 1.1.1 ===================================== +- install 'py.test' and `py.which` with a ``-$VERSION`` suffix to + disambiguate between Python3, python2.X, Jython and PyPy installed versions. + - fix assert reinterpreation that sees a call containing "keyword=..." + - skip some install-tests if no execnet is available Changes between 1.1.1 and 1.1.0 --- a/bin-for-dist/test_install.py +++ b/bin-for-dist/test_install.py @@ -143,3 +143,29 @@ def test_plugin_setuptools_entry_point_i venv.setup_develop() out = venv.pytest_getouterr("-h") assert "testpluginopt" in out + +def test_cmdline_entrypoints(): + from setup import cmdline_entrypoints + versioned_scripts = ['py.test', 'py.which'] + unversioned_scripts = versioned_scripts + [ 'py.cleanup', + 'py.convert_unittest', 'py.countloc', 'py.lookup', 'py.svnwcrevert'] + for ver in [(2,4,0), (2,5,0), (2,6,0), (2,7,0), (3,0,1), (3,1,1)]: + for platform in ('posix', 'win32'): + points = cmdline_entrypoints(ver, "posix", 'python') + for script in versioned_scripts: + script_ver = script + "-%s.%s" % ver[:2] + assert script_ver in points + for script in unversioned_scripts: + assert script in points + points = cmdline_entrypoints((2,5,1), "java1.6.123", 'jython') + for script in versioned_scripts: + expected = "%s-jython" % script + assert expected in points + for script in unversioned_scripts: + assert script in points + points = cmdline_entrypoints((2,5,1), "xyz", 'pypy-c-XYZ') + for script in versioned_scripts: + expected = "%s-pypy-c-XYZ" % script + assert expected in points + for script in unversioned_scripts: + assert script in points --- a/doc/bin.txt +++ b/doc/bin.txt @@ -1,22 +1,40 @@ ====================== -``py/bin/`` scripts +pylib scripts ====================== -The py-lib contains some scripts, most of which are -small ones (apart from ``py.test``) that help during -the python development process. If working -from a svn-checkout of py lib you may add ``py/bin`` -to your shell ``PATH`` which should make the scripts -available on your command prompt. +The pylib installs several scripts to support testing and (python) +development. If working from a checkout you may also add ``bin`` to +your ``PATH`` environment variable which makes the scripts available on +your shell prompt. -``py.test`` -=========== +``py.test`` and ``py.test-$VERSION`` +============================================ -The ``py.test`` executable is the main entry point into the py-lib testing tool, -see the `py.test documentation`_. +The ``py.test`` executable is the main tool that the py lib offers; +in fact most code in the py lib is geared towards supporting the +testing process. See the `py.test documentation`_ for extensive +documentation. The ``py.test-$VERSION`` is the same script with +an interpreter specific suffix appended to make +several versions of py.test for using specific interpreters +accessible: + +* CPython2.4: py.test-2.4 +* CPython2.5: py.test-2.5 +* ... +* CPython3.1: py.test-3.1 +* Jython-2.5.1: py.test-jython +* pypy-$SUFFIX: py.test-pypy-$SUFFIX .. _`py.test documentation`: test/index.html +``py.which`` and ``py.which-$VERSION`` +========================================= + +Usage: ``py.which modulename`` + +Print the ``__file__`` of the module that is imported via ``import modulename``. +The version-suffix is the same as with ``py.test`` above. + ``py.cleanup`` ============== @@ -26,7 +44,6 @@ Delete pyc file recursively, starting fr current working directory). Don't follow links and don't recurse into directories with a ".". - ``py.countloc`` =============== @@ -46,25 +63,3 @@ Looks recursively at Python files for a present working directory. Prints the line, with the filename and line-number prepended. -``py.rest`` -=========== - -Usage: ``py.rest [PATHS] [options]`` - -[deprecated in 1.0, will likely be separated] -Loot recursively for .txt files starting from ``PATHS`` and convert them to -html using docutils or to pdf files, if the ``--pdf`` option is used. For -conversion to PDF you will need several command line tools, on Ubuntu Linux -this is **texlive** and **texlive-extra-utils**. - -``py.rest`` has some extra features over rst2html (which is shipped with -docutils). Most of these are still experimental, the one which is most likely -not going to change is the `graphviz`_ directive. With that you can embed .dot -files into your document and have them be converted to png (when outputting -html) and to eps (when outputting pdf). Otherwise the directive works mostly -like the image directive:: - - .. graphviz:: example.dot - :scale: 90 - -.. _`graphviz`: http://www.graphviz.org --- a/setup.py +++ b/setup.py @@ -28,20 +28,13 @@ def main(): name='py', description='py.test and pylib: rapid testing and development utils.', long_description = long_description, - version= trunk or '1.1.1', + version= trunk or '1.1.2', url='http://pylib.org', license='MIT license', platforms=['unix', 'linux', 'osx', 'cygwin', 'win32'], author='holger krekel, Guido Wesdorp, Carl Friedrich Bolz, Armin Rigo, Maciej Fijalkowski & others', author_email='holger at merlinux.eu', - entry_points={'console_scripts': [ - 'py.cleanup = py.cmdline:pycleanup', - 'py.convert_unittest = py.cmdline:pyconvert_unittest', - 'py.countloc = py.cmdline:pycountloc', - 'py.lookup = py.cmdline:pylookup', - 'py.svnwcrevert = py.cmdline:pysvnwcrevert', - 'py.test = py.cmdline:pytest', - 'py.which = py.cmdline:pywhich']}, + entry_points= make_entry_points(), classifiers=['Development Status :: 5 - Production/Stable', 'Intended Audience :: Developers', 'License :: OSI Approved :: MIT License', @@ -69,6 +62,31 @@ def main(): zip_safe=False, ) +def cmdline_entrypoints(versioninfo, platform, basename): + if basename.startswith("pypy"): + points = {'py.test-%s' % basename: 'py.cmdline:pytest', + 'py.which-%s' % basename: 'py.cmdline:pywhich',} + elif platform.startswith('java'): + points = {'py.test-jython': 'py.cmdline:pytest', + 'py.which-jython': 'py.cmdline:pywhich'} + else: # cpython + points = { + 'py.test-%s.%s' % versioninfo[:2] : 'py.cmdline:pytest', + 'py.which-%s.%s' % versioninfo[:2] : 'py.cmdline:pywhich' + } + for x in ['py.cleanup', 'py.convert_unittest', 'py.countloc', + 'py.lookup', 'py.svnwcrevert', 'py.which', 'py.test']: + points[x] = "py.cmdline:%s" % x.replace('.','') + return points + +def make_entry_points(): + basename = os.path.basename(sys.executable) + points = cmdline_entrypoints(sys.version_info, sys.platform, basename) + keys = list(points.keys()) + keys.sort() + l = ["%s = %s" % (x, points[x]) for x in keys] + return {'console_scripts': l} + if __name__ == '__main__': main() From commits-noreply at bitbucket.org Thu Dec 24 13:14:16 2009 From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org) Date: Thu, 24 Dec 2009 12:14:16 +0000 (UTC) Subject: [py-svn] py-trunk commit 48dbf8b0e6c9: cleanup bin-script creation, fix docs, add FAQ entry about py.test --version Message-ID: <20091224121416.98F5B7EF56@bitbucket.org> # HG changeset patch -- Bitbucket.org # Project py-trunk # URL http://bitbucket.org/hpk42/py-trunk/overview/ # User holger krekel # Date 1261654035 -3600 # Node ID 48dbf8b0e6c92a4fb728df8fd54d9a3124812830 # Parent 96fba87ba03f6f3c5af627d6cf3fd95bdb08516a cleanup bin-script creation, fix docs, add FAQ entry about py.test --version --- a/doc/install.txt +++ b/doc/install.txt @@ -116,52 +116,47 @@ in order to work inline with the tools a .. _`directly use a checkout`: -directly use a checkout or tarball +directly use a checkout or tarball / avoid setuptools ------------------------------------------------------------- -Once you got yourself a checkout_ or tarball_ it is usually a good -idea to add the parent directory of the ``py`` package directory -to your ``PYTHONPATH`` and ``py/bin`` or ``py\bin\win32`` to your -system wide ``PATH`` settings. There are helper scripts that -set ``PYTHONPATH`` and ``PATH`` on your system: +Get a checkout_ or tarball_ and add paths to your environment variables: -on windows execute:: +* ``PYTHONPATH`` needs to contain the root directory (where ``py`` resides) +* ``PATH`` needs to contain ``ROOT/bin`` or ``ROOT\bin\win32`` respectively. + +There also are helper scripts that set the variables accordingly. On windows +execute:: # inside autoexec.bat or shell startup c:\\path\to\checkout\bin\env.cmd on linux/OSX add this to your shell initialization:: - # inside .bashrc + # inside e.g. .bashrc eval `python ~/path/to/checkout/bin/env.py` -both of which which will get you good settings -for ``PYTHONPATH`` and ``PATH``. +both of which which will get you good settings. If you install +the pylib this way you can easily ``hg pull && hg up`` or download +a new tarball_ to follow the development tree. -If you install ``py.test`` this way you can easily -``hg pull && hg up`` your checkout to follow the -development tree. -note: scripts look for "nearby" py-lib ------------------------------------------------------ - -Note that all `command line scripts`_ will look -for "nearby" py libs, so if you have a layout like this:: +Note that the scripts manually added like this will look for +py libs in the chain of parent directories of the current working dir. +For example, if you have a layout like this:: mypkg/ subpkg1/ tests/ tests/ - py/ + py/ issuing ``py.test subpkg1`` will use the py lib -from that projects root directory. Giving the -state of Python packaging there can be confusion -in which case issuing:: +from that projects root directory. If in doubt over where +the pylib comes from you can always do:: py.test --version -tells you both version and import location of the tool. +to see where py.test is imported from. .. _`command line scripts`: bin.html .. _contact: contact.html --- a/bin-for-dist/genscripts.py +++ b/bin-for-dist/genscripts.py @@ -1,6 +1,6 @@ -from _findpy import py +import py -bindir = py.magic.autopath().dirpath().dirpath("py").join("bin") +bindir = py.path.local(__file__).dirpath().dirpath("bin") assert bindir.check(), bindir def getbasename(name): @@ -31,5 +31,3 @@ if __name__ == "__main__": if name[0] != "_": genscript_unix(name) genscript_windows(name) - - --- a/doc/faq.txt +++ b/doc/faq.txt @@ -63,6 +63,15 @@ issues where people have used the term " .. _`py namespaces`: index.html .. _`py/__init__.py`: http://bitbucket.org/hpk42/py-trunk/src/trunk/py/__init__.py +Where does my ``py.test`` come/import from? +---------------------------------------------- + +You can issue:: + + py.test --version + +which tells you both version and import location of the tool. + function arguments, parametrized tests and setup ==================================================== --- a/bin/_findpy.py +++ b/bin/_findpy.py @@ -1,7 +1,9 @@ #!/usr/bin/env python # -# find and import a version of 'py' +# find and import a version of 'py' that exists in a parent dir +# of the current working directory. fall back to import a +# globally available version # import sys import os --- a/CHANGELOG +++ b/CHANGELOG @@ -8,6 +8,9 @@ Changes between 1.1.2 and 1.1.1 - skip some install-tests if no execnet is available +- fix docs, fix internal bin/ script generation + + Changes between 1.1.1 and 1.1.0 ===================================== From commits-noreply at bitbucket.org Thu Dec 24 16:05:52 2009 From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org) Date: Thu, 24 Dec 2009 15:05:52 +0000 (UTC) Subject: [py-svn] py-trunk commit 8402eaae5d01: capitialize I Message-ID: <20091224150552.C5CE07EF41@bitbucket.org> # HG changeset patch -- Bitbucket.org # Project py-trunk # URL http://bitbucket.org/hpk42/py-trunk/overview/ # User Benjamin Peterson # Date 1261667183 21600 # Node ID 8402eaae5d018e8748afd2b17b855ba82fbfb3d4 # Parent 48dbf8b0e6c92a4fb728df8fd54d9a3124812830 capitialize I --- a/doc/faq.txt +++ b/doc/faq.txt @@ -112,7 +112,7 @@ argument usage and creation. .. _`Convention over Configuration`: http://en.wikipedia.org/wiki/Convention_over_Configuration -Can i yield multiple values from a factory function? +Can I yield multiple values from a factory function? ----------------------------------------------------- There are two reasons why yielding from a factory function From commits-noreply at bitbucket.org Thu Dec 24 20:02:14 2009 From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org) Date: Thu, 24 Dec 2009 19:02:14 +0000 (UTC) Subject: [py-svn] py-trunk commit f46593400125: fixes to various tests, related to execnet automatic ID generation and other bits. Message-ID: <20091224190214.4E56B7EF47@bitbucket.org> # HG changeset patch -- Bitbucket.org # Project py-trunk # URL http://bitbucket.org/hpk42/py-trunk/overview/ # User holger krekel # Date 1261680194 -3600 # Node ID f465934001250fdeebbe24430bd50d2dc9194751 # Parent 8402eaae5d018e8748afd2b17b855ba82fbfb3d4 fixes to various tests, related to execnet automatic ID generation and other bits. also lowering the version as "1.1.1post1" for now. 1.1.2 is still a bit off. --- a/py/impl/test/dist/txnode.py +++ b/py/impl/test/dist/txnode.py @@ -42,7 +42,7 @@ class TXNode(object): if eventcall == self.ENDMARK: err = self.channel._getremoteerror() if not self._down: - if not err: + if not err or isinstance(err, EOFError): err = "Not properly terminated" self.notify("pytest_testnodedown", node=self, error=err) self._down = True --- a/py/impl/test/dist/dsession.py +++ b/py/impl/test/dist/dsession.py @@ -215,15 +215,16 @@ class DSession(Session): for node, pending in self.node2pending.items(): room = min(self.MAXITEMSPERHOST - len(pending), room) sending = tosend[:room] - for node, pending in self.node2pending.items(): - node.sendlist(sending) - pending.extend(sending) - for item in sending: - nodes = self.item2nodes.setdefault(item, []) - assert node not in nodes - nodes.append(node) - self.config.hook.pytest_itemstart(item=item, node=node) - tosend[:] = tosend[room:] # update inplace + if sending: + for node, pending in self.node2pending.items(): + node.sendlist(sending) + pending.extend(sending) + for item in sending: + nodes = self.item2nodes.setdefault(item, []) + assert node not in nodes + nodes.append(node) + self.config.hook.pytest_itemstart(item=item, node=node) + tosend[:] = tosend[room:] # update inplace if tosend: # we have some left, give it to the main loop self.queueevent("pytest_rescheduleitems", items=tosend) --- a/testing/pytest/dist/acceptance_test.py +++ b/testing/pytest/dist/acceptance_test.py @@ -49,8 +49,8 @@ class TestDistribution: ) result = testdir.runpytest(p1, '-d', '--tx=popen', '--tx=popen') result.stdout.fnmatch_lines([ + "*0*popen*Python*", "*1*popen*Python*", - "*2*popen*Python*", "*2 failed, 1 passed, 1 skipped*", ]) assert result.ret == 1 @@ -73,9 +73,9 @@ class TestDistribution: """) result = testdir.runpytest(p1, '-d') result.stdout.fnmatch_lines([ + "*0*popen*Python*", "*1*popen*Python*", "*2*popen*Python*", - "*3*popen*Python*", "*2 failed, 1 passed, 1 skipped*", ]) assert result.ret == 1 @@ -122,7 +122,7 @@ class TestDistribution: "--tx=popen//chdir=%(dest)s" % locals(), p) assert result.ret == 0 result.stdout.fnmatch_lines([ - "*1* *popen*platform*", + "*0* *popen*platform*", #"RSyncStart: [G1]", #"RSyncFinished: [G1]", "*1 passed*" --- a/py/__init__.py +++ b/py/__init__.py @@ -9,7 +9,7 @@ dictionary or an import path. (c) Holger Krekel and others, 2009 """ -version = "1.1.2" +version = "1.1.1post1" __version__ = version = version or "1.1.x" import py.apipkg --- a/testing/pytest/dist/test_gwmanage.py +++ b/testing/pytest/dist/test_gwmanage.py @@ -37,10 +37,10 @@ class TestGatewayManagerPopen: hm.makegateways() call = hookrecorder.popcall("pytest_gwmanage_newgateway") assert call.gateway.spec == execnet.XSpec("popen") - assert call.gateway.id == "1" + assert call.gateway.id == "gw0" assert call.platinfo.executable == call.gateway._rinfo().executable call = hookrecorder.popcall("pytest_gwmanage_newgateway") - assert call.gateway.id == "2" + assert call.gateway.id == "gw1" assert len(hm.group) == 2 hm.exit() assert not len(hm.group) @@ -66,7 +66,7 @@ class TestGatewayManagerPopen: l = [] hm.rsync(source, notify=lambda *args: l.append(args)) assert len(l) == 1 - assert l[0] == ("rsyncrootready", hm.group['1'].spec, source) + assert l[0] == ("rsyncrootready", hm.group['gw0'].spec, source) hm.exit() dest = dest.join(source.basename) assert dest.join("dir1").check() --- a/testing/plugin/test_pytest_pdb.py +++ b/testing/plugin/test_pytest_pdb.py @@ -36,8 +36,8 @@ class TestPDB: assert i == 1 """) child = testdir.spawn_pytest("--pdb %s" % p1) - #child.expect(".*def test_1.*") - child.expect(".*i = 0.*") + child.expect(".*def test_1") + child.expect(".*i = 0") child.expect("(Pdb)") child.sendeof() child.expect("1 failed") @@ -50,7 +50,7 @@ class TestPDB: py.test.raises(Error, "testdir.parseconfigure('--pdb', '--looponfail')") result = testdir.runpytest("--pdb", "-n", "3") assert result.ret != 0 - assert "incompatible" in result.stdout.str() + assert "incompatible" in result.stderr.str() result = testdir.runpytest("--pdb", "-d", "--tx", "popen") assert result.ret != 0 - assert "incompatible" in result.stdout.str() + assert "incompatible" in result.stderr.str() --- a/testing/pytest/dist/test_txnode.py +++ b/testing/pytest/dist/test_txnode.py @@ -79,7 +79,7 @@ class TestMasterSlaveConnection: node.send(123) # invalid item kwargs = mysetup.geteventargs("pytest_testnodedown") assert kwargs['node'] is node - assert str(kwargs['error']).find("AttributeError") != -1 + assert "Not properly terminated" in str(kwargs['error']) def test_crash_killed(self, testdir, mysetup): if not hasattr(py.std.os, 'kill'): @@ -87,13 +87,13 @@ class TestMasterSlaveConnection: item = testdir.getitem(""" def test_func(): import os - os.kill(os.getpid(), 15) + os.kill(os.getpid(), 9) """) node = mysetup.makenode(item.config) node.send(item) kwargs = mysetup.geteventargs("pytest_testnodedown") assert kwargs['node'] is node - assert str(kwargs['error']).find("Not properly terminated") != -1 + assert "Not properly terminated" in str(kwargs['error']) def test_node_down(self, mysetup): node = mysetup.makenode() --- a/py/plugin/pytest_pdb.py +++ b/py/plugin/pytest_pdb.py @@ -16,9 +16,10 @@ def pytest_addoption(parser): help="start pdb (the Python debugger) on errors.") -def pytest_configure(config): +def pytest_configure(__multicall__, config): if config.option.usepdb: if execnet: + __multicall__.execute() if config.getvalue("looponfail"): raise config.Error("--pdb incompatible with --looponfail.") if config.option.dist != "no": --- 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.2', + version= trunk or '1.1.1post1', url='http://pylib.org', license='MIT license', platforms=['unix', 'linux', 'osx', 'cygwin', 'win32'], From commits-noreply at bitbucket.org Thu Dec 24 20:22:38 2009 From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org) Date: Thu, 24 Dec 2009 19:22:38 +0000 (UTC) Subject: [py-svn] py-trunk commit 781b46bb3282: adding pip requirements file Message-ID: <20091224192238.B64007EF48@bitbucket.org> # HG changeset patch -- Bitbucket.org # Project py-trunk # URL http://bitbucket.org/hpk42/py-trunk/overview/ # User holger krekel # Date 1261682539 -3600 # Node ID 781b46bb3282149966f9264bd79ae7d4ea04501b # Parent f465934001250fdeebbe24430bd50d2dc9194751 adding pip requirements file --- /dev/null +++ b/testing/pip-reqs1.txt @@ -0,0 +1,3 @@ +docutils +pexpect +execnet From commits-noreply at bitbucket.org Thu Dec 24 20:35:29 2009 From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org) Date: Thu, 24 Dec 2009 19:35:29 +0000 (UTC) Subject: [py-svn] py-trunk commit 2168581ec29d: rather use newest execnet always Message-ID: <20091224193529.DF7C27EF47@bitbucket.org> # HG changeset patch -- Bitbucket.org # Project py-trunk # URL http://bitbucket.org/hpk42/py-trunk/overview/ # User holger krekel # Date 1261683318 -3600 # Node ID 2168581ec29d101ea7f17fb2ecab1e5b3717c8d2 # Parent 781b46bb3282149966f9264bd79ae7d4ea04501b rather use newest execnet always --- a/testing/pip-reqs1.txt +++ b/testing/pip-reqs1.txt @@ -1,3 +1,3 @@ docutils pexpect -execnet +hg+http://bitbucket.org/hpk42/execnet#egg=execnet From commits-noreply at bitbucket.org Thu Dec 24 21:47:28 2009 From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org) Date: Thu, 24 Dec 2009 20:47:28 +0000 (UTC) Subject: [py-svn] py-trunk commit df0ad9519409: temporarily adding files for playing with hudson Message-ID: <20091224204728.3FDE57EF15@bitbucket.org> # HG changeset patch -- Bitbucket.org # Project py-trunk # URL http://bitbucket.org/hpk42/py-trunk/overview/ # User holger krekel # Date 1261687621 -3600 # Node ID df0ad95194092c8ff33e800b6d8c409c39a29580 # Parent 2168581ec29d101ea7f17fb2ecab1e5b3717c8d2 temporarily adding files for playing with hudson --- /dev/null +++ b/pytest_xmlresult.py @@ -0,0 +1,189 @@ +""" + xmlresult plugin for machine-readable logging of test results. + Useful for cruisecontrol integration code. + + An adaptation of pytest_resultlog.py +""" + +import time + +def pytest_addoption(parser): + group = parser.addgroup("xmlresult", "xmlresult plugin options") + group.addoption('--xmlresult', action="store", dest="xmlresult", metavar="path", default=None, + help="path for machine-readable xml result log.") + +def pytest_configure(config): + xmlresult = config.option.xmlresult + if xmlresult: + logfile = open(xmlresult, 'w', 1) # line buffered + config._xmlresult = XMLResult(logfile) + config.pluginmanager.register(config._xmlresult) + +def pytest_unconfigure(config): + xmlresult = getattr(config, '_xmlresult', None) + if xmlresult: + xmlresult.logfile.close() + del config._xmlresult + config.pluginmanager.unregister(xmlresult) + +def generic_path(item): + chain = item.listchain() + gpath = [chain[0].name] + fspath = chain[0].fspath + fspart = False + for node in chain[1:]: + newfspath = node.fspath + if newfspath == fspath: + if fspart: + gpath.append(':') + fspart = False + else: + gpath.append('.') + else: + gpath.append('/') + fspart = True + name = node.name + if name[0] in '([': + gpath.pop() + gpath.append(name) + fspath = newfspath + return ''.join(gpath) + +class XMLResult(object): + test_start_time = 0.0 + test_taken_time = 0.0 + test_count = 0 + error_count = 0 + failure_count = 0 + skip_count = 0 + + def __init__(self, logfile): + self.logfile = logfile + self.test_logs = [] + + def write_log_entry(self, testpath, shortrepr, longrepr): + self.test_count += 1 + # Create an xml log entry for the tests + self.test_logs.append('' % (testpath.split(':')[-1], testpath, self.test_taken_time)) + + # Do we have any other data to capture for Errors, Fails and Skips + if shortrepr in ['E', 'F', 'S']: + + if shortrepr == 'E': + self.error_count += 1 + elif shortrepr == 'F': + self.failure_count += 1 + elif shortrepr == 'S': + self.skip_count += 1 + + tag_map = {'E': 'error', 'F': 'failure', 'S': 'skipped'} + self.test_logs.append("<%s>" % tag_map[shortrepr]) + + # Output any more information + for line in longrepr.splitlines(): + self.test_logs.append("" % line) + self.test_logs.append("" % tag_map[shortrepr]) + self.test_logs.append("") + + def log_outcome(self, node, shortrepr, longrepr): + self.write_log_entry(node.name, shortrepr, longrepr) + + def pytest_runtest_logreport(self, report): + code = report.shortrepr + if report.passed: + longrepr = "" + code = "." + elif report.failed: + longrepr = str(report.longrepr) + code = "F" + elif report.skipped: + code = "S" + longrepr = str(report.longrepr.reprcrash.message) + self.log_outcome(report.item, code, longrepr) + + def pytest_runtest_setup(self, item): + self.test_start_time = time.time() + + def pytest_runtest_teardown(self, item): + self.test_taken_time = time.time() - self.test_start_time + + def pytest_collectreport(self, report): + if not report.passed: + if report.failed: + code = "F" + else: + assert report.skipped + code = "S" + longrepr = str(report.longrepr.reprcrash) + self.log_outcome(report.collector, code, longrepr) + + def pytest_internalerror(self, excrepr): + path = excrepr.reprcrash.path + self.errors += 1 + self.write_log_entry(path, '!', str(excrepr)) + + def pytest_sessionstart(self, session): + self.suite_start_time = time.time() + + def pytest_sessionfinish(self, session, exitstatus): + """ + Write the xml output + """ + suite_stop_time = time.time() + suite_time_delta = suite_stop_time - self.suite_start_time + self.logfile.write('') + self.logfile.writelines(self.test_logs) + self.logfile.write('') + self.logfile.close() + + +# Tests +def test_generic(testdir, LineMatcher): + testdir.plugins.append("resultlog") + testdir.makepyfile(""" + import py + def test_pass(): + pass + def test_fail(): + assert 0 + def test_skip(): + py.test.skip("") + """) + testdir.runpytest("--xmlresult=result.xml") + lines = testdir.tmpdir.join("result.xml").readlines(cr=0) + LineMatcher(lines).fnmatch_lines([ + '*testsuite errors="0" failures="1" skips="1" name="" tests="3"*' + ]) + LineMatcher(lines).fnmatch_lines([ + '**' + ]) + +def test_generic_path(): + from py.__.test.collect import Node, Item, FSCollector + p1 = Node('a') + assert p1.fspath is None + p2 = Node('B', parent=p1) + p3 = Node('()', parent = p2) + item = Item('c', parent = p3) + res = generic_path(item) + assert res == 'a.B().c' + + p0 = FSCollector('proj/test') + p1 = FSCollector('proj/test/a', parent=p0) + p2 = Node('B', parent=p1) + p3 = Node('()', parent = p2) + p4 = Node('c', parent=p3) + item = Item('[1]', parent = p4) + + res = generic_path(item) + assert res == 'test/a:B().c[1]' --- /dev/null +++ b/bin-for-dist/hudson.py @@ -0,0 +1,21 @@ +import os, sys, subprocess, urllib + +BUILDNAME=os.environ.get('BUILD_NUMBER', "1") +BIN=os.path.abspath(os.path.join(BUILDNAME, 'bin')) +PYTHON=os.path.join(BIN, 'python') + +def call(*args): + ret = subprocess.call(list(args)) + assert ret == 0 + +def bincall(*args): + args = list(args) + args[0] = os.path.join(BIN, args[0]) + call(*args) + +call("virtualenv", BUILDNAME, '--no-site-packages') +bincall("python", "setup.py", "develop", "-q") +bincall("pip", "install", "-r", "testing/pip-reqs1.txt", + "-q", "--download-cache=download") +bincall("py.test", "-p", "xmlresult", "--xmlresult=junit.xml", + "--report=skipped", "--runslowtest") From commits-noreply at bitbucket.org Fri Dec 25 00:25:14 2009 From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org) Date: Thu, 24 Dec 2009 23:25:14 +0000 (UTC) Subject: [py-svn] py-trunk commit dba87d2e2562: introduce --ignore option to ignore paths during collection Message-ID: <20091224232514.025087EF54@bitbucket.org> # HG changeset patch -- Bitbucket.org # Project py-trunk # URL http://bitbucket.org/hpk42/py-trunk/overview/ # User holger krekel # Date 1261689825 -3600 # Node ID dba87d2e25626ee716bacf2a5447f1f589d30dc0 # Parent df0ad95194092c8ff33e800b6d8c409c39a29580 introduce --ignore option to ignore paths during collection --- a/py/plugin/pytest_default.py +++ b/py/plugin/pytest_default.py @@ -51,6 +51,8 @@ def pytest_addoption(parser): group._addoption('-x', '--exitfirst', action="store_true", dest="exitfirst", default=False, help="exit instantly on first error or failed test."), + group.addoption("--ignore", action="append", metavar="path", + help="ignore path during collection (multi-allowed).") group._addoption('-k', action="store", dest="keyword", default='', help="only run test items matching the given " --- a/testing/plugin/test_pytest_default.py +++ b/testing/plugin/test_pytest_default.py @@ -30,6 +30,15 @@ def test_plugin_already_exists(testdir): assert config.option.plugins == ['default'] config.pluginmanager.do_configure(config) +def test_exclude(testdir): + hellodir = testdir.mkdir("hello") + hellodir.join("test_hello.py").write("x y syntaxerror") + hello2dir = testdir.mkdir("hello2") + hello2dir.join("test_hello2.py").write("x y syntaxerror") + testdir.makepyfile(test_ok="def test_pass(): pass") + result = testdir.runpytest("--ignore=hello", "--ignore=hello2") + assert result.ret == 0 + assert result.stdout.fnmatch_lines(["*1 passed*"]) class TestDistOptions: def setup_method(self, method): --- a/py/impl/test/collect.py +++ b/py/impl/test/collect.py @@ -394,8 +394,12 @@ class Directory(FSCollector): return l def _ignore(self, path): - ignore_paths = self.config.getconftest_pathlist("collect_ignore", path=path) - return ignore_paths and path in ignore_paths + ignore_paths = self.config.getconftest_pathlist("collect_ignore", + path=path) or [] + excludeopt = self.config.getvalue("ignore") + if excludeopt: + ignore_paths.extend([py.path.local(x) for x in excludeopt]) + return path in ignore_paths # XXX more refined would be: if ignore_paths: for p in ignore_paths: --- a/CHANGELOG +++ b/CHANGELOG @@ -1,6 +1,9 @@ Changes between 1.1.2 and 1.1.1 ===================================== +- new option: --ignore will prevent specified path from collection. + Can be specified multiple times. + - install 'py.test' and `py.which` with a ``-$VERSION`` suffix to disambiguate between Python3, python2.X, Jython and PyPy installed versions. From commits-noreply at bitbucket.org Fri Dec 25 00:25:15 2009 From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org) Date: Thu, 24 Dec 2009 23:25:15 +0000 (UTC) Subject: [py-svn] py-trunk commit cea6be014c38: fixing and cleaning up some tests Message-ID: <20091224232515.8DE687EF56@bitbucket.org> # HG changeset patch -- Bitbucket.org # Project py-trunk # URL http://bitbucket.org/hpk42/py-trunk/overview/ # User holger krekel # Date 1261697098 -3600 # Node ID cea6be014c384c1b4e95e597f623cd18bd39a2e5 # Parent dba87d2e25626ee716bacf2a5447f1f589d30dc0 fixing and cleaning up some tests --- a/testing/pip-reqs1.txt +++ b/testing/pip-reqs1.txt @@ -1,3 +1,4 @@ docutils pexpect +figleaf hg+http://bitbucket.org/hpk42/execnet#egg=execnet --- a/py/plugin/pytest_pytester.py +++ b/py/plugin/pytest_pytester.py @@ -4,6 +4,7 @@ funcargs and support code for testing py import py import sys, os +import re import inspect from py.impl.test.config import Config as pytestConfig from py.plugin import hookspec @@ -26,6 +27,7 @@ def pytest_funcarg__reportrecorder(reque request.addfinalizer(lambda: reprec.comregistry.unregister(reprec)) return reprec +rex_outcome = re.compile("(\d+) (\w+)") class RunResult: def __init__(self, ret, outlines, errlines): self.ret = ret @@ -33,6 +35,15 @@ class RunResult: self.errlines = errlines self.stdout = LineMatcher(outlines) self.stderr = LineMatcher(errlines) + def parseoutcomes(self): + for line in reversed(self.outlines): + if 'seconds' in line: + outcomes = rex_outcome.findall(line) + if outcomes: + d = {} + for num, cat in outcomes: + d[cat] = int(num) + return d class TmpTestdir: def __init__(self, request): @@ -245,17 +256,6 @@ class TmpTestdir: return self.config.getfsnode(path) - def prepare(self): - p = self.tmpdir.join("conftest.py") - if not p.check(): - plugins = [x for x in self.plugins if isinstance(x, str)] - if not plugins: - return - p.write("import py ; pytest_plugins = %r" % plugins) - else: - if self.plugins: - print ("warning, ignoring reusing existing %s" % p) - def popen(self, cmdargs, stdout, stderr, **kw): if not hasattr(py.std, 'subprocess'): py.test.skip("no subprocess module") @@ -267,7 +267,6 @@ class TmpTestdir: return py.std.subprocess.Popen(cmdargs, stdout=stdout, stderr=stderr, **kw) def run(self, *cmdargs): - self.prepare() old = self.tmpdir.chdir() #print "chdir", self.tmpdir try: @@ -316,6 +315,9 @@ class TmpTestdir: p = py.path.local.make_numbered_dir(prefix="runpytest-", keep=None, rootdir=self.tmpdir) args = ('--basetemp=%s' % p, ) + args + plugins = [x for x in self.plugins if isinstance(x, str)] + if plugins: + args = ('-p', plugins[0]) + args return self.runpybin("py.test", *args) def spawn_pytest(self, string, expect_timeout=10.0): --- a/bin-for-dist/hudson.py +++ b/bin-for-dist/hudson.py @@ -13,9 +13,10 @@ def bincall(*args): args[0] = os.path.join(BIN, args[0]) call(*args) -call("virtualenv", BUILDNAME, '--no-site-packages') +call("virtualenv", os.path.abspath(BUILDNAME), '--no-site-packages') bincall("python", "setup.py", "develop", "-q") bincall("pip", "install", "-r", "testing/pip-reqs1.txt", "-q", "--download-cache=download") -bincall("py.test", "-p", "xmlresult", "--xmlresult=junit.xml", - "--report=skipped", "--runslowtest") +bincall("py.test", "--ignore", BUILDNAME, + "-p", "xmlresult", "--xmlresult=junit.xml", + "--report=skipped", "--runslowtest", *sys.argv[1:]) --- a/testing/plugin/test_pytest_restdoc.py +++ b/testing/plugin/test_pytest_restdoc.py @@ -8,53 +8,16 @@ def test_deindent(): assert deindent(' foo\n bar\n') == 'foo\n bar\n' assert deindent(' foo\n bar\n') == ' foo\nbar\n' -class TestApigenLinkRole: - disabled = True - - # these tests are moved here from the former py/doc/conftest.py - def test_resolve_linkrole(self): - from py.impl.doc.conftest import get_apigen_relpath - apigen_relpath = get_apigen_relpath() - - assert resolve_linkrole('api', 'py.foo.bar', False) == ( - 'py.foo.bar', apigen_relpath + 'api/foo.bar.html') - assert resolve_linkrole('api', 'py.foo.bar()', False) == ( - 'py.foo.bar()', apigen_relpath + 'api/foo.bar.html') - assert resolve_linkrole('api', 'py', False) == ( - 'py', apigen_relpath + 'api/index.html') - py.test.raises(AssertionError, 'resolve_linkrole("api", "foo.bar")') - assert resolve_linkrole('source', 'py/foo/bar.py', False) == ( - 'py/foo/bar.py', apigen_relpath + 'source/foo/bar.py.html') - assert resolve_linkrole('source', 'py/foo/', False) == ( - 'py/foo/', apigen_relpath + 'source/foo/index.html') - assert resolve_linkrole('source', 'py/', False) == ( - 'py/', apigen_relpath + 'source/index.html') - py.test.raises(AssertionError, 'resolve_linkrole("source", "/foo/bar/")') - - def test_resolve_linkrole_check_api(self): - assert resolve_linkrole('api', 'py.test.ensuretemp') - py.test.raises(AssertionError, "resolve_linkrole('api', 'py.foo.baz')") - - def test_resolve_linkrole_check_source(self): - assert resolve_linkrole('source', 'py/path/common.py') - py.test.raises(AssertionError, - "resolve_linkrole('source', 'py/foo/bar.py')") - - class TestDoctest: def pytest_funcarg__testdir(self, request): testdir = request.getfuncargvalue("testdir") + testdir.plugins.append("restdoc") assert request.module.__name__ == __name__ testdir.makepyfile(confrest= "from py.plugin.pytest_restdoc import Project") # we scope our confrest file so that it doesn't # conflict with another global confrest.py testdir.makepyfile(__init__="") - for p in testdir.plugins: - if p == globals(): - break - else: - testdir.plugins.append(globals()) return testdir def test_doctest_extra_exec(self, testdir): @@ -63,9 +26,8 @@ class TestDoctest: .. >>> raise ValueError >>> None """) - reprec = testdir.inline_run(xtxt) - passed, skipped, failed = reprec.countoutcomes() - assert failed == 1 + result = testdir.runpytest(xtxt) + result.stdout.fnmatch_lines(['*1 fail*']) def test_doctest_basic(self, testdir): xtxt = testdir.maketxtfile(x=""" @@ -86,25 +48,21 @@ class TestDoctest: end """) - reprec = testdir.inline_run(xtxt) - passed, skipped, failed = reprec.countoutcomes() - assert failed == 0 - assert passed + skipped == 2 + result = testdir.runpytest(xtxt) + result.stdout.fnmatch_lines([ + "*2 passed*" + ]) def test_doctest_eol(self, testdir): ytxt = testdir.maketxtfile(y=".. >>> 1 + 1\r\n 2\r\n\r\n") - reprec = testdir.inline_run(ytxt) - passed, skipped, failed = reprec.countoutcomes() - assert failed == 0 - assert passed + skipped == 2 + result = testdir.runpytest(ytxt) + result.stdout.fnmatch_lines(["*2 passed*"]) def test_doctest_indentation(self, testdir): footxt = testdir.maketxtfile(foo= '..\n >>> print ("foo\\n bar")\n foo\n bar\n') - reprec = testdir.inline_run(footxt) - passed, skipped, failed = reprec.countoutcomes() - assert failed == 0 - assert skipped + passed == 2 + result = testdir.runpytest(footxt) + result.stdout.fnmatch_lines(["*2 passed*"]) def test_js_ignore(self, testdir): xtxt = testdir.maketxtfile(xtxt=""" @@ -112,20 +70,14 @@ class TestDoctest: .. _`blah`: javascript:some_function() """) - reprec = testdir.inline_run(xtxt) - passed, skipped, failed = reprec.countoutcomes() - assert failed == 0 - assert skipped + passed == 3 + result = testdir.runpytest(xtxt) + result.stdout.fnmatch_lines(["*3 passed*"]) def test_pytest_doctest_prepare_content(self, testdir): - l = [] - class MyPlugin: - def pytest_doctest_prepare_content(self, content): - l.append(content) + testdir.makeconftest(""" + def pytest_doctest_prepare_content(content): return content.replace("False", "True") - - testdir.plugins.append(MyPlugin()) - + """) xtxt = testdir.maketxtfile(x=""" hello: @@ -133,9 +85,8 @@ class TestDoctest: False """) - reprec = testdir.inline_run(xtxt) - assert len(l) == 1 - passed, skipped, failed = reprec.countoutcomes() - assert passed >= 1 - assert not failed - assert skipped <= 1 + result = testdir.runpytest(xtxt) + outcomes = result.parseoutcomes() + assert outcomes['passed'] >= 1 + assert 'failed' not in outcomes + assert 'skipped' not in outcomes --- a/doc/conftest.py +++ b/doc/conftest.py @@ -2,6 +2,4 @@ import py #py.test.importorskip("pygments") pytest_plugins = ['pytest_restdoc'] -rsyncdirs = ['.'] - collect_ignore = ['test/attic.txt'] From commits-noreply at bitbucket.org Fri Dec 25 00:32:28 2009 From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org) Date: Thu, 24 Dec 2009 23:32:28 +0000 (UTC) Subject: [py-svn] py-trunk commit 3a3525787e28: try to fix windows path issue Message-ID: <20091224233228.CE76F7EF53@bitbucket.org> # HG changeset patch -- Bitbucket.org # Project py-trunk # URL http://bitbucket.org/hpk42/py-trunk/overview/ # User holger krekel # Date 1261697511 -3600 # Node ID 3a3525787e287251d68d31c640e5077a6618f682 # Parent cea6be014c384c1b4e95e597f623cd18bd39a2e5 try to fix windows path issue --- a/bin-for-dist/hudson.py +++ b/bin-for-dist/hudson.py @@ -1,8 +1,6 @@ import os, sys, subprocess, urllib BUILDNAME=os.environ.get('BUILD_NUMBER', "1") -BIN=os.path.abspath(os.path.join(BUILDNAME, 'bin')) -PYTHON=os.path.join(BIN, 'python') def call(*args): ret = subprocess.call(list(args)) @@ -14,6 +12,12 @@ def bincall(*args): call(*args) call("virtualenv", os.path.abspath(BUILDNAME), '--no-site-packages') +BIN=os.path.abspath(os.path.join(BUILDNAME, 'bin')) +if not os.path.exists(BIN): + BIN=os.path.abspath(os.path.join(BUILDNAME, 'Scripts')) + assert os.path.exists(BIN) + +PYTHON=os.path.join(BIN, 'python') bincall("python", "setup.py", "develop", "-q") bincall("pip", "install", "-r", "testing/pip-reqs1.txt", "-q", "--download-cache=download") From commits-noreply at bitbucket.org Fri Dec 25 09:55:18 2009 From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org) Date: Fri, 25 Dec 2009 08:55:18 +0000 (UTC) Subject: [py-svn] py-trunk commit 69e19a983854: windows fixes and print funcargs for keyboardinterrupt traces Message-ID: <20091225085518.71C4C7EF54@bitbucket.org> # HG changeset patch -- Bitbucket.org # Project py-trunk # URL http://bitbucket.org/hpk42/py-trunk/overview/ # User holger krekel # Date 1261731216 -3600 # Node ID 69e19a9838540a6b145790be7355017c388b72c8 # Parent 3a3525787e287251d68d31c640e5077a6618f682 windows fixes and print funcargs for keyboardinterrupt traces --- a/bin-for-dist/test_install.py +++ b/bin-for-dist/test_install.py @@ -66,7 +66,10 @@ class VirtualEnv(object): return "" %(self.path) def _cmd(self, name): - return os.path.join(self.path, 'bin', name) + if sys.platform == "win32": + return os.path.join(self.path, 'Scripts', name) + else: + return os.path.join(self.path, 'bin', name) def ensure(self): if not os.path.exists(self._cmd('python')): --- a/testing/process/test_cmdexec.py +++ b/testing/process/test_cmdexec.py @@ -9,6 +9,11 @@ class Test_exec_cmd: out = cmdexec('echo hallo') assert out.strip() == 'hallo' + def test_simple_newline(self): + import sys + out = cmdexec(r"""%s -c "print ('hello')" """ % sys.executable) + assert out == 'hello\n' + def test_simple_error(self): py.test.raises (cmdexec.Error, cmdexec, 'exit 1') --- a/py/plugin/pytest_terminal.py +++ b/py/plugin/pytest_terminal.py @@ -292,7 +292,7 @@ class TerminalReporter: self.summary_stats() def pytest_keyboard_interrupt(self, excinfo): - self._keyboardinterrupt_memo = excinfo.getrepr() + self._keyboardinterrupt_memo = excinfo.getrepr(funcargs=True) def _report_keyboardinterrupt(self): self.write_sep("!", "KEYBOARD INTERRUPT") --- a/py/impl/process/cmdexec.py +++ b/py/impl/process/cmdexec.py @@ -14,7 +14,9 @@ def cmdexec(cmd): the exception will provide an 'err' attribute containing the error-output from the command. """ - process = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE) + process = subprocess.Popen(cmd, shell=True, + universal_newlines=True, + stdout=subprocess.PIPE, stderr=subprocess.PIPE) out, err = process.communicate() out = py.builtin._totext(out, sys.getdefaultencoding()) err = py.builtin._totext(err, sys.getdefaultencoding()) From commits-noreply at bitbucket.org Fri Dec 25 10:48:11 2009 From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org) Date: Fri, 25 Dec 2009 09:48:11 +0000 (UTC) Subject: [py-svn] py-trunk commit 143a2777a61d: fixing windows32 svn-testing issues Message-ID: <20091225094811.2EE727EE6C@bitbucket.org> # HG changeset patch -- Bitbucket.org # Project py-trunk # URL http://bitbucket.org/hpk42/py-trunk/overview/ # User holger krekel # Date 1261734463 28800 # Node ID 143a2777a61dfda0c228a3bd873e518b5478af4b # Parent 69e19a9838540a6b145790be7355017c388b72c8 fixing windows32 svn-testing issues --- a/testing/path/test_svnurl.py +++ b/testing/path/test_svnurl.py @@ -5,8 +5,8 @@ import time from testing.path.svntestbase import CommonSvnTests def pytest_funcarg__path1(request): - repo, wc = request.getfuncargvalue("repowc1") - return py.path.svnurl(repo) + repo, repourl, wc = request.getfuncargvalue("repowc1") + return py.path.svnurl(repourl) class TestSvnURLCommandPath(CommonSvnTests): @py.test.mark.xfail --- a/testing/path/test_svnwc.py +++ b/testing/path/test_svnwc.py @@ -1,17 +1,9 @@ import py -import sys +import os, sys from py.impl.path.svnwc import InfoSvnWCCommand, XMLWCStatus, parse_wcinfotime from py.impl.path import svnwc as svncommon from testing.path.svntestbase import CommonSvnTests -if sys.platform == 'win32': - def normpath(p): - return p -else: - def normpath(p): - p = py.test.importorskip("win32").GetShortPathName(p) - return os.path.normpath(os.path.normcase(p)) - def test_make_repo(path1, tmpdir): repo = tmpdir.join("repo") py.process.cmdexec('svnadmin create %s' % repo) @@ -32,7 +24,7 @@ def test_make_repo(path1, tmpdir): assert rev is None def pytest_funcarg__path1(request): - repo, wc = request.getfuncargvalue("repowc1") + repo, repourl, wc = request.getfuncargvalue("repowc1") return wc class TestWCSvnCommandPath(CommonSvnTests): @@ -181,7 +173,7 @@ class TestWCSvnCommandPath(CommonSvnTest assert [x.basename for x in s.conflict] == ['conflictsamplefile'] def test_status_external(self, path1, repowc2): - otherrepo, otherwc = repowc2 + otherrepo, otherrepourl, otherwc = repowc2 d = path1.ensure('sampledir', dir=1) try: d.remove() @@ -366,7 +358,8 @@ class TestWCSvnCommandPath(CommonSvnTest try: locked = root.status().locked assert len(locked) == 1 - assert normpath(str(locked[0])) == normpath(str(somefile)) + assert locked[0].basename == somefile.basename + assert locked[0].dirpath().basename == somefile.dirpath().basename #assert somefile.locked() py.test.raises(Exception, 'somefile.lock()') finally: --- a/testing/path/conftest.py +++ b/testing/path/conftest.py @@ -11,7 +11,7 @@ def pytest_funcarg__repowc1(request): modname = request.module.__name__ tmpdir = request.getfuncargvalue("tmpdir") - repo, wc = request.cached_setup( + repo, repourl, wc = request.cached_setup( setup=lambda: getrepowc(tmpdir, "repo-"+modname, "wc-" + modname), scope="module", ) @@ -19,12 +19,13 @@ def pytest_funcarg__repowc1(request): if request.function.__name__.startswith(x): _savedrepowc = save_repowc(repo, wc) request.addfinalizer(lambda: restore_repowc(_savedrepowc)) - return repo, wc + return repo, repourl, wc def pytest_funcarg__repowc2(request): tmpdir = request.getfuncargvalue("tmpdir") name = request.function.__name__ - return getrepowc(tmpdir, "%s-repo-2" % name, "%s-wc-2" % name) + repo, url, wc = getrepowc(tmpdir, "%s-repo-2" % name, "%s-wc-2" % name) + return repo, url, wc def getsvnbin(): if svnbin is None: @@ -46,14 +47,16 @@ def getrepowc(tmpdir, reponame='basetest wcdir.ensure(dir=1) wc = py.path.svnwc(wcdir) if py.std.sys.platform == 'win32': - repo = '/' + str(repo).replace('\\', '/') - wc.checkout(url='file://%s' % repo) + repourl = "file://" + '/' + str(repo).replace('\\', '/') + else: + repourl = "file://%s" % repo + wc.checkout(repourl) print_("checked out new repo into", wc) - return ("file://%s" % repo, wc) + return (repo, repourl, wc) def save_repowc(repo, wc): - repo = py.path.local(repo[len("file://"):]) + assert not str(repo).startswith("file://"), repo assert repo.check() savedrepo = repo.dirpath(repo.basename+".1") savedwc = wc.dirpath(wc.basename+".1") From commits-noreply at bitbucket.org Fri Dec 25 10:56:57 2009 From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org) Date: Fri, 25 Dec 2009 09:56:57 +0000 (UTC) Subject: [py-svn] py-trunk commit fd04d8d8c8cd: fix typo Message-ID: <20091225095657.BE03B7EF0F@bitbucket.org> # HG changeset patch -- Bitbucket.org # Project py-trunk # URL http://bitbucket.org/hpk42/py-trunk/overview/ # User holger krekel # Date 1261734963 -3600 # Node ID fd04d8d8c8cd46011240b6829a0546bd664df4ad # Parent 143a2777a61dfda0c228a3bd873e518b5478af4b fix typo --- a/bin-for-dist/test_install.py +++ b/bin-for-dist/test_install.py @@ -1,6 +1,6 @@ import py import subprocess -import os +import os, sys execnet = py.test.importorskip("execnet") From commits-noreply at bitbucket.org Fri Dec 25 11:17:31 2009 From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org) Date: Fri, 25 Dec 2009 10:17:31 +0000 (UTC) Subject: [py-svn] py-trunk commit 78da77459ff7: another fix for windows Message-ID: <20091225101731.AA0617EF48@bitbucket.org> # HG changeset patch -- Bitbucket.org # Project py-trunk # URL http://bitbucket.org/hpk42/py-trunk/overview/ # User holger krekel # Date 1261736189 28800 # Node ID 78da77459ff7ac56f1417f3eb27dba24db0381df # Parent fd04d8d8c8cd46011240b6829a0546bd664df4ad another fix for windows --- a/bin-for-dist/test_install.py +++ b/bin-for-dist/test_install.py @@ -94,7 +94,7 @@ class VirtualEnv(object): def pytest_getouterr(self, *args): self.ensure() - args = [self._cmd("python"), self._cmd("py.test")] + list(args) + args = [self._cmd("py.test")] + list(args) popen = subprocess.Popen(args, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) out, err = popen.communicate() return out From commits-noreply at bitbucket.org Sun Dec 27 23:05:43 2009 From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org) Date: Sun, 27 Dec 2009 22:05:43 +0000 (UTC) Subject: [py-svn] py-trunk commit f0c844a51152: add script to generate standalone py.test Message-ID: <20091227220543.6162C7EEE2@bitbucket.org> # HG changeset patch -- Bitbucket.org # Project py-trunk # URL http://bitbucket.org/hpk42/py-trunk/overview/ # User Ralf Schmitt # Date 1261951384 -3600 # Node ID f0c844a51152aedfe4b446520f03d3d25893c162 # Parent 78da77459ff7ac56f1417f3eb27dba24db0381df add script to generate standalone py.test --- a/py/impl/test/conftesthandle.py +++ b/py/impl/test/conftesthandle.py @@ -41,7 +41,12 @@ class Conftest(object): if path is None: raise ValueError("missing default conftest.") dp = path.dirpath() - if dp == path: + if dp == path: + if not defaultconftestpath.check(): # zip file, single-file py.test? + import defaultconftest + if self._onimport: + self._onimport(defaultconftest) + return [defaultconftest] return [self.importconftest(defaultconftestpath)] clist = self.getconftestmodules(dp) conftestpath = path.join("conftest.py") --- /dev/null +++ b/bin-for-dist/generate_standalone_pytest.py @@ -0,0 +1,42 @@ +#! /usr/bin/env python + +import os +import cPickle +import zlib +import base64 +import sys + +def main(): + here = os.path.dirname(os.path.abspath(__file__)) + outfile = os.path.join(here, "py.test") + infile = outfile+"-in" + + os.chdir(os.path.dirname(here)) + + files = [] + for dirpath, dirnames, filenames in os.walk("py"): + for f in filenames: + if not f.endswith(".py"): + continue + + fn = os.path.join(dirpath, f) + files.append(fn) + + name2src = {} + for f in files: + k = f.replace("/", ".")[:-3] + name2src[k] = open(f, "rb").read() + + data = cPickle.dumps(name2src, 2) + data = zlib.compress(data, 9) + data = base64.encodestring(data) + + exe = open(infile, "rb").read() + exe = exe.replace("@SOURCES@", data) + + open(outfile, "wb").write(exe) + os.chmod(outfile, 493) # 0755 + sys.stdout.write("generated %s\n" % outfile) + +if __name__=="__main__": + main() --- /dev/null +++ b/bin-for-dist/py.test-in @@ -0,0 +1,50 @@ +#! /usr/bin/env python + +sources = """ + at SOURCES@""" + +import sys +import cPickle +import base64 +import zlib +import imp + +sources = cPickle.loads(zlib.decompress(base64.decodestring(sources))) + +class DictImporter(object): + sources = sources + def find_module(self, fullname, path=None): + if fullname in self.sources: + return self + if fullname+'.__init__' in self.sources: + return self + return None + + def load_module(self, fullname): + # print "load_module:", fullname + import new + + try: + s = self.sources[fullname] + is_pkg = False + except KeyError: + s = self.sources[fullname+'.__init__'] + is_pkg = True + + co = compile(s, fullname, 'exec') + module = sys.modules.setdefault(fullname, new.module(fullname)) + module.__file__ = "%s/%s" % (__file__, fullname) + module.__loader__ = self + if is_pkg: + module.__path__ = [fullname] + + exec co in module.__dict__ + return sys.modules[fullname] + +importer = DictImporter() + +sys.meta_path.append(importer) + +if __name__ == "__main__": + import py + py.cmdline.pytest() From commits-noreply at bitbucket.org Mon Dec 28 16:06:48 2009 From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org) Date: Mon, 28 Dec 2009 15:06:48 +0000 (UTC) Subject: [py-svn] apipkg commit 47756aba150f: adding special attribute __onfirstaccess__ whose value Message-ID: <20091228150648.12EF27EE65@bitbucket.org> # HG changeset patch -- Bitbucket.org # Project apipkg # URL http://bitbucket.org/hpk42/apipkg/overview/ # User holger krekel # Date 1262012786 -3600 # Node ID 47756aba150f1bd68aebc04364a9a44fe78ef524 # Parent 40bc1c4ff6c5cbd8b64b6489551f7ca56e4ac11a adding special attribute __onfirstaccess__ whose value will get called on first access to the Api Module. also adding a CHANGELOG --- a/test_apipkg.py +++ b/test_apipkg.py @@ -208,3 +208,31 @@ def test_error_loading_one_element(monke assert errorloading1.y == 0 py.test.raises(ImportError, 'errorloading1.x') py.test.raises(ImportError, 'errorloading1.x') + +def test_onfirstaccess(monkeypatch): + mod = type(sys)('hello') + monkeypatch.setitem(sys.modules, 'hello', mod) + l = [] + apipkg.initpkg('hello', { + '__onfirstaccess__': lambda: l.append(1), + 'world': 'sys:executable', + 'world2': 'sys:executable', + }) + hello = sys.modules['hello'] + assert hello.world == sys.executable + assert len(l) == 1 + assert hello.world == sys.executable + assert len(l) == 1 + +def test_onfirstaccess__dict__(monkeypatch): + mod = type(sys)('hello') + monkeypatch.setitem(sys.modules, 'hello', mod) + l = [] + apipkg.initpkg('hello', { + '__onfirstaccess__': lambda: l.append(1), + }) + hello = sys.modules['hello'] + hello.__dict__ + assert len(l) == 1 + hello.__dict__ + assert len(l) == 1 --- a/apipkg.py +++ b/apipkg.py @@ -8,7 +8,7 @@ see http://pypi.python.org/pypi/apipkg import sys from types import ModuleType -__version__ = "1.0b2" +__version__ = "1.0b3" def initpkg(pkgname, exportdefs): """ initialize given package from the export definitions. """ @@ -35,6 +35,8 @@ class ApiModule(ModuleType): apimod = ApiModule(subname, importspec, implprefix) sys.modules[subname] = apimod setattr(self, name, apimod) + elif name == '__onfirstaccess__': + self.__map__[name] = importspec else: modpath, attrname = importspec.split(':') if modpath[0] == '.': @@ -45,10 +47,24 @@ class ApiModule(ModuleType): self.__map__[name] = (modpath, attrname) def __repr__(self): + l = [] + if hasattr(self, '__version__'): + l.append("version=" + repr(self.__version__)) + if hasattr(self, '__file__'): + l.append('from ' + repr(self.__file__)) + if l: + return '' % (self.__name__, " ".join(l)) return '' % (self.__name__,) def __getattr__(self, name): try: + func = self.__map__.pop('__onfirstaccess__') + except KeyError: + pass + else: + if func: + func() + try: modpath, attrname = self.__map__[name] except KeyError: raise AttributeError(name) --- /dev/null +++ b/CHANGELOG @@ -0,0 +1,5 @@ +1.0.0b3 (compared to 1.0.0b2) +------------------------------------ + +- added special attribute __onfirstaccess__ whose value will + be called on the first attribute access to the apimodule From commits-noreply at bitbucket.org Tue Dec 29 10:29:42 2009 From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org) Date: Tue, 29 Dec 2009 09:29:42 +0000 (UTC) Subject: [py-svn] py-trunk commit f3825c8a4456: always import defaultconftest by python import path. strike some redundant code. Message-ID: <20091229092942.826547EF00@bitbucket.org> # HG changeset patch -- Bitbucket.org # Project py-trunk # URL http://bitbucket.org/hpk42/py-trunk/overview/ # User holger krekel # Date 1262018986 -3600 # Node ID f3825c8a4456cb1b0e958ecbd75461b8bf63106e # Parent f0c844a51152aedfe4b446520f03d3d25893c162 always import defaultconftest by python import path. strike some redundant code. --- a/testing/pytest/test_conftesthandle.py +++ b/testing/pytest/test_conftesthandle.py @@ -17,6 +17,11 @@ def pytest_funcarg__basedir(request): return d return request.cached_setup(lambda: basedirmaker(request), extrakey=request.param) +def ConftestWithSetinitial(path): + conftest = Conftest() + conftest.setinitial([path]) + return conftest + class TestConftestValueAccessGlobal: def test_basic_init(self, basedir): conftest = Conftest() @@ -45,22 +50,22 @@ class TestConftestValueAccessGlobal: def test_default_Module_setting_is_visible_always(self, basedir): for path in basedir.parts(): - conftest = Conftest(path) + conftest = ConftestWithSetinitial(path) #assert conftest.lget("Module") == py.test.collect.Module assert conftest.rget("Module") == py.test.collect.Module def test_default_has_lower_prio(self, basedir): - conftest = Conftest(basedir.join("adir")) + conftest = ConftestWithSetinitial(basedir.join("adir")) assert conftest.rget('Directory') == 3 #assert conftest.lget('Directory') == py.test.collect.Directory def test_value_access_not_existing(self, basedir): - conftest = Conftest(basedir) + conftest = ConftestWithSetinitial(basedir) py.test.raises(KeyError, "conftest.rget('a')") #py.test.raises(KeyError, "conftest.lget('a')") def test_value_access_by_path(self, basedir): - conftest = Conftest(basedir) + conftest = ConftestWithSetinitial(basedir) assert conftest.rget("a", basedir.join('adir')) == 1 #assert conftest.lget("a", basedir.join('adir')) == 1 assert conftest.rget("a", basedir.join('adir', 'b')) == 1.5 @@ -71,12 +76,12 @@ class TestConftestValueAccessGlobal: #) def test_value_access_with_init_one_conftest(self, basedir): - conftest = Conftest(basedir.join('adir')) + conftest = ConftestWithSetinitial(basedir.join('adir')) assert conftest.rget("a") == 1 #assert conftest.lget("a") == 1 def test_value_access_with_init_two_conftests(self, basedir): - conftest = Conftest(basedir.join("adir", "b")) + conftest = ConftestWithSetinitial(basedir.join("adir", "b")) conftest.rget("a") == 1.5 #conftest.lget("a") == 1 #conftest.lget("b") == 1 @@ -84,7 +89,7 @@ class TestConftestValueAccessGlobal: def test_value_access_with_confmod(self, basedir): topdir = basedir.join("adir", "b") topdir.ensure("xx", dir=True) - conftest = Conftest(topdir) + conftest = ConftestWithSetinitial(topdir) mod, value = conftest.rget_with_confmod("a", topdir) assert value == 1.5 path = py.path.local(mod.__file__) --- a/py/impl/test/conftesthandle.py +++ b/py/impl/test/conftesthandle.py @@ -1,19 +1,17 @@ import py -defaultconftestpath = py.path.local(__file__).dirpath("defaultconftest.py") +from py.impl.test import defaultconftest class Conftest(object): """ the single place for accessing values and interacting towards conftest modules from py.test objects. + (deprecated) Note that triggering Conftest instances to import conftest.py files may result in added cmdline options. - XXX """ - def __init__(self, path=None, onimport=None): + def __init__(self, onimport=None): self._path2confmods = {} self._onimport = onimport - if path is not None: - self.setinitial([path]) def setinitial(self, args): """ try to find a first anchor path for looking up global values @@ -42,17 +40,13 @@ class Conftest(object): raise ValueError("missing default conftest.") dp = path.dirpath() if dp == path: - if not defaultconftestpath.check(): # zip file, single-file py.test? - import defaultconftest - if self._onimport: - self._onimport(defaultconftest) - return [defaultconftest] - return [self.importconftest(defaultconftestpath)] - clist = self.getconftestmodules(dp) - conftestpath = path.join("conftest.py") - if conftestpath.check(file=1): - clist.append(self.importconftest(conftestpath)) - self._path2confmods[path] = clist + clist = [self._postimport(defaultconftest)] + else: + clist = self.getconftestmodules(dp) + conftestpath = path.join("conftest.py") + if conftestpath.check(file=1): + clist.append(self.importconftest(conftestpath)) + self._path2confmods[path] = clist # be defensive: avoid changes from caller side to # affect us by always returning a copy of the actual list return clist[:] @@ -82,6 +76,9 @@ class Conftest(object): mod = conftestpath.pyimport(modname=modname) else: mod = conftestpath.pyimport() + return self._postimport(mod) + + def _postimport(self, mod): if self._onimport: self._onimport(mod) return mod From commits-noreply at bitbucket.org Tue Dec 29 10:29:42 2009 From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org) Date: Tue, 29 Dec 2009 09:29:42 +0000 (UTC) Subject: [py-svn] py-trunk commit b38b9280c073: remove defaultconfest.py and make PluginManager directly do early initialization of default plugins. Message-ID: <20091229092942.A43E97EF01@bitbucket.org> # HG changeset patch -- Bitbucket.org # Project py-trunk # URL http://bitbucket.org/hpk42/py-trunk/overview/ # User holger krekel # Date 1262078811 -3600 # Node ID b38b9280c0732dee15ebe7d5f13d82f11d6dad32 # Parent f3825c8a4456cb1b0e958ecbd75461b8bf63106e remove defaultconfest.py and make PluginManager directly do early initialization of default plugins. --- a/py/impl/test/conftesthandle.py +++ b/py/impl/test/conftesthandle.py @@ -1,5 +1,4 @@ import py -from py.impl.test import defaultconftest class Conftest(object): """ the single place for accessing values and interacting @@ -40,7 +39,7 @@ class Conftest(object): raise ValueError("missing default conftest.") dp = path.dirpath() if dp == path: - clist = [self._postimport(defaultconftest)] + clist = self._path2confmods[path] = [] else: clist = self.getconftestmodules(dp) conftestpath = path.join("conftest.py") --- a/py/impl/test/collect.py +++ b/py/impl/test/collect.py @@ -8,7 +8,7 @@ import py def configproperty(name): def fget(self): #print "retrieving %r property from %s" %(name, self.fspath) - return self.config.getvalue(name, self.fspath) + return self.config._getcollectclass(name, self.fspath) return property(fget) class Node(object): --- a/testing/plugin/conftest.py +++ b/testing/plugin/conftest.py @@ -3,7 +3,7 @@ import py pytest_plugins = "pytester" import py.plugin plugindir = py.path.local(py.plugin.__file__).dirpath() -from py.impl.test.defaultconftest import pytest_plugins as default_plugins +from py.impl.test.pluginmanager import default_plugins def pytest_collect_file(path, parent): if path.basename.startswith("pytest_") and path.ext == ".py": --- a/testing/pytest/test_pluginmanager.py +++ b/testing/pytest/test_pluginmanager.py @@ -148,7 +148,9 @@ class TestBootstrapping: assert pp.isregistered(a1) pp.register(a2, "hello") assert pp.isregistered(a2) - assert pp.getplugins() == [a1, a2] + l = pp.getplugins() + assert a1 in l + assert a2 in l assert pp.getplugin('hello') == a2 pp.unregister(a1) assert not pp.isregistered(a1) @@ -160,13 +162,14 @@ class TestBootstrapping: mod = py.std.types.ModuleType("x.y.pytest_hello") pp.register(mod) assert pp.isregistered(mod) - assert pp.getplugins() == [mod] + l = pp.getplugins() + assert mod in l py.test.raises(AssertionError, "pp.register(mod)") mod2 = py.std.types.ModuleType("pytest_hello") #pp.register(mod2) # double registry py.test.raises(AssertionError, "pp.register(mod)") #assert not pp.isregistered(mod2) - assert pp.getplugins() == [mod] # does not actually modify plugins + assert pp.getplugins() == l def test_canonical_import(self, monkeypatch): mod = py.std.types.ModuleType("pytest_xyz") --- a/testing/pytest/test_pickling.py +++ b/testing/pytest/test_pickling.py @@ -184,7 +184,7 @@ class TestConfigPickling: def test_config__setstate__wired_correctly_in_childprocess(testdir): execnet = py.test.importorskip("execnet") from py.impl.test.dist.mypickle import PickleChannel - gw = execnet.PopenGateway() + gw = execnet.makegateway() channel = gw.remote_exec(""" import py from py.impl.test.dist.mypickle import PickleChannel --- a/py/plugin/pytest_default.py +++ b/py/plugin/pytest_default.py @@ -40,7 +40,7 @@ def pytest_collect_directory(path, paren break else: return - Directory = parent.config.getvalue('Directory', path) + Directory = parent.config._getcollectclass('Directory', path) return Directory(path, parent=parent) def pytest_report_iteminfo(item): --- a/testing/pytest/test_conftesthandle.py +++ b/testing/pytest/test_conftesthandle.py @@ -32,10 +32,10 @@ class TestConftestValueAccessGlobal: l = [] conftest = Conftest(onimport=l.append) conftest.setinitial([basedir.join("adir")]) - assert len(l) == 2 # default + the one + assert len(l) == 1 assert conftest.rget("a") == 1 assert conftest.rget("b", basedir.join("adir", "b")) == 2 - assert len(l) == 3 + assert len(l) == 2 def test_immediate_initialiation_and_incremental_are_the_same(self, basedir): conftest = Conftest() @@ -48,11 +48,11 @@ class TestConftestValueAccessGlobal: conftest.getconftestmodules(basedir.join('b')) assert len(conftest._path2confmods) == snap1 + 2 - def test_default_Module_setting_is_visible_always(self, basedir): - for path in basedir.parts(): - conftest = ConftestWithSetinitial(path) - #assert conftest.lget("Module") == py.test.collect.Module - assert conftest.rget("Module") == py.test.collect.Module + def test_default_Module_setting_is_visible_always(self, basedir, testdir): + basedir.copy(testdir.tmpdir) + config = testdir.Config() + colclass = config._getcollectclass("Module", testdir.tmpdir) + assert colclass == py.test.collect.Module def test_default_has_lower_prio(self, basedir): conftest = ConftestWithSetinitial(basedir.join("adir")) --- a/py/impl/test/config.py +++ b/py/impl/test/config.py @@ -107,6 +107,7 @@ class Config(object): # warning global side effects: # * registering to py lib plugins # * setting py.test.config + py._com.comregistry = py._com.Registry() self.__init__( pluginmanager=py.test._PluginManager(py._com.comregistry), topdir=py.path.local(), @@ -155,11 +156,17 @@ class Config(object): pkgpath = path.pypkgpath() if pkgpath is None: pkgpath = path.check(file=1) and path.dirpath() or path - Dir = self._conftest.rget("Directory", pkgpath) + Dir = self._getcollectclass("Directory", pkgpath) col = Dir(pkgpath) col.config = self return col._getfsnode(path) + def _getcollectclass(self, name, path): + try: + return self.getvalue(name, path) + except KeyError: + return getattr(py.test.collect, name) + def getconftest_pathlist(self, name, path=None): """ return a matching value, which needs to be sequence of filenames that will be returned as a list of Path --- a/py/impl/test/pluginmanager.py +++ b/py/impl/test/pluginmanager.py @@ -5,6 +5,10 @@ import py from py.plugin import hookspec from py.impl.test.outcome import Skipped +default_plugins = ( + "default runner capture terminal mark skipping tmpdir monkeypatch " + "recwarn pdb pastebin unittest helpconfig nose assertion").split() + def check_old_use(mod, modname): clsname = modname[len('pytest_'):].capitalize() + "Plugin" assert not hasattr(mod, clsname), (mod, clsname) @@ -21,6 +25,9 @@ class PluginManager(object): self.hook = py._com.HookRelay( hookspecs=hookspec, registry=self.comregistry) + self.register(self) + for spec in default_plugins: + self.import_plugin(spec) def _getpluginname(self, plugin, name): if name is None: @@ -31,7 +38,8 @@ class PluginManager(object): return name def register(self, plugin, name=None): - assert not self.isregistered(plugin) + assert not self.isregistered(plugin), plugin + assert not self.comregistry.isregistered(plugin), plugin name = self._getpluginname(plugin, name) if name in self._name2plugin: return False @@ -191,31 +199,24 @@ class PluginManager(object): mc.execute() def pytest_plugin_registered(self, plugin): + dic = self.call_plugin(plugin, "pytest_namespace", {}) or {} + for name, value in dic.items(): + setattr(py.test, name, value) if hasattr(self, '_config'): self.call_plugin(plugin, "pytest_addoption", {'parser': self._config._parser}) self.call_plugin(plugin, "pytest_configure", {'config': self._config}) - #dic = self.call_plugin(plugin, "pytest_namespace") - #self._updateext(dic) def call_plugin(self, plugin, methname, kwargs): return py._com.MultiCall( methods=self.listattr(methname, plugins=[plugin]), kwargs=kwargs, firstresult=True).execute() - def _updateext(self, dic): - if dic: - for name, value in dic.items(): - setattr(py.test, name, value) - def do_configure(self, config): assert not hasattr(self, '_config') - config.pluginmanager.register(self) self._config = config config.hook.pytest_configure(config=self._config) - for dic in config.hook.pytest_namespace() or []: - self._updateext(dic) def do_unconfigure(self, config): config = self._config --- a/py/impl/test/defaultconftest.py +++ /dev/null @@ -1,14 +0,0 @@ -import py - -Module = py.test.collect.Module -Directory = py.test.collect.Directory -File = py.test.collect.File - -# python collectors -Class = py.test.collect.Class -Generator = py.test.collect.Generator -Function = py.test.collect.Function -Instance = py.test.collect.Instance - -pytest_plugins = "default runner capture terminal mark skipping tmpdir monkeypatch recwarn pdb pastebin unittest helpconfig nose assertion".split() - From commits-noreply at bitbucket.org Tue Dec 29 12:37:44 2009 From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org) Date: Tue, 29 Dec 2009 11:37:44 +0000 (UTC) Subject: [py-svn] py-trunk commit 756383d54510: simplify pluginmanager, move plugin validation code to plugin, remove unused code Message-ID: <20091229113744.7D56F7EF02@bitbucket.org> # HG changeset patch -- Bitbucket.org # Project py-trunk # URL http://bitbucket.org/hpk42/py-trunk/overview/ # User holger krekel # Date 1262080741 -3600 # Node ID 756383d5451058b00a35aa42d815c9f75fccfdae # Parent b38b9280c0732dee15ebe7d5f13d82f11d6dad32 simplify pluginmanager, move plugin validation code to plugin, remove unused code --- a/py/plugin/pytest_helpconfig.py +++ b/py/plugin/pytest_helpconfig.py @@ -1,7 +1,7 @@ """ provide version info, conftest/environment config names. """ import py -import sys +import inspect, sys def pytest_addoption(parser): group = parser.getgroup('debugconfig') @@ -61,3 +61,73 @@ conftest_options = ( ('collect_ignore', '(relative) paths ignored during collection'), ('rsyncdirs', 'to-be-rsynced directories for dist-testing'), ) + +# ===================================================== +# validate plugin syntax and hooks +# ===================================================== + +def pytest_plugin_registered(manager, plugin): + hookspec = manager.hook._hookspecs + methods = collectattr(plugin) + hooks = collectattr(hookspec) + stringio = py.io.TextIO() + def Print(*args): + if args: + stringio.write(" ".join(map(str, args))) + stringio.write("\n") + + fail = False + while methods: + name, method = methods.popitem() + #print "checking", name + if isgenerichook(name): + continue + if name not in hooks: + Print("found unknown hook:", name) + fail = True + else: + method_args = getargs(method) + if '__multicall__' in method_args: + method_args.remove('__multicall__') + hook = hooks[name] + hookargs = getargs(hook) + for arg in method_args: + if arg not in hookargs: + Print("argument %r not available" %(arg, )) + Print("actual definition: %s" %(formatdef(method))) + Print("available hook arguments: %s" % + ", ".join(hookargs)) + fail = True + break + #if not fail: + # print "matching hook:", formatdef(method) + if fail: + name = getattr(plugin, '__name__', plugin) + raise PluginValidationError("%s:\n%s" %(name, stringio.getvalue())) + +class PluginValidationError(Exception): + """ plugin failed validation. """ + +def isgenerichook(name): + return name == "pytest_plugins" or \ + name.startswith("pytest_funcarg__") + +def getargs(func): + args = inspect.getargs(py.code.getrawcode(func))[0] + startindex = inspect.ismethod(func) and 1 or 0 + return args[startindex:] + +def collectattr(obj, prefixes=("pytest_",)): + methods = {} + for apiname in dir(obj): + for prefix in prefixes: + if apiname.startswith(prefix): + methods[apiname] = getattr(obj, apiname) + return methods + +def formatdef(func): + return "%s%s" %( + func.__name__, + inspect.formatargspec(*inspect.getargspec(func)) + ) + --- a/testing/pytest/test_pluginmanager.py +++ b/testing/pytest/test_pluginmanager.py @@ -1,5 +1,5 @@ import py, os -from py.impl.test.pluginmanager import PluginManager, canonical_importname, collectattr +from py.impl.test.pluginmanager import PluginManager, canonical_importname class TestBootstrapping: def test_consider_env_fails_to_import(self, monkeypatch): @@ -185,14 +185,14 @@ class TestBootstrapping: class hello: def pytest_gurgel(self): pass - py.test.raises(pp.Error, "pp.register(hello())") + py.test.raises(Exception, "pp.register(hello())") def test_register_mismatch_arg(self): pp = PluginManager() class hello: def pytest_configure(self, asd): pass - excinfo = py.test.raises(pp.Error, "pp.register(hello())") + excinfo = py.test.raises(Exception, "pp.register(hello())") def test_canonical_importname(self): for name in 'xyz', 'pytest_xyz', 'pytest_Xyz', 'Xyz': @@ -270,18 +270,6 @@ class TestPytestPluginInteractions: assert not pluginmanager.listattr("hello") assert pluginmanager.listattr("x") == [42] -def test_collectattr(): - class A: - def pytest_hello(self): - pass - class B(A): - def pytest_world(self): - pass - methods = py.builtin.sorted(collectattr(B)) - assert list(methods) == ['pytest_hello', 'pytest_world'] - methods = py.builtin.sorted(collectattr(B())) - assert list(methods) == ['pytest_hello', 'pytest_world'] - @py.test.mark.xfail def test_namespace_has_default_and_env_plugins(testdir): p = testdir.makepyfile(""" --- a/py/plugin/hookspec.py +++ b/py/plugin/hookspec.py @@ -159,7 +159,7 @@ def pytest_looponfailinfo(failreports, r # error handling and internal debugging hooks # ------------------------------------------------------------------------- -def pytest_plugin_registered(plugin): +def pytest_plugin_registered(plugin, manager): """ a new py lib plugin got registered. """ def pytest_plugin_unregistered(plugin): --- a/py/impl/test/pluginmanager.py +++ b/py/impl/test/pluginmanager.py @@ -44,8 +44,7 @@ class PluginManager(object): if name in self._name2plugin: return False self._name2plugin[name] = plugin - self.hook.pytest_plugin_registered(plugin=plugin) - self._checkplugin(plugin) + self.hook.pytest_plugin_registered(manager=self, plugin=plugin) self.comregistry.register(plugin) return True @@ -138,46 +137,6 @@ class PluginManager(object): def _warn(self, msg): print ("===WARNING=== %s" % (msg,)) - def _checkplugin(self, plugin): - # ===================================================== - # check plugin hooks - # ===================================================== - methods = collectattr(plugin) - hooks = collectattr(hookspec) - stringio = py.io.TextIO() - def Print(*args): - if args: - stringio.write(" ".join(map(str, args))) - stringio.write("\n") - - fail = False - while methods: - name, method = methods.popitem() - #print "checking", name - if isgenerichook(name): - continue - if name not in hooks: - Print("found unknown hook:", name) - fail = True - else: - method_args = getargs(method) - if '__multicall__' in method_args: - method_args.remove('__multicall__') - hook = hooks[name] - hookargs = getargs(hook) - for arg in method_args: - if arg not in hookargs: - Print("argument %r not available" %(arg, )) - Print("actual definition: %s" %(formatdef(method))) - Print("available hook arguments: %s" % - ", ".join(hookargs)) - fail = True - break - #if not fail: - # print "matching hook:", formatdef(method) - if fail: - name = getattr(plugin, '__name__', plugin) - raise self.Error("%s:\n%s" %(name, stringio.getvalue())) # # # API for interacting with registered and instantiated plugin objects @@ -224,9 +183,6 @@ class PluginManager(object): config.hook.pytest_unconfigure(config=config) config.pluginmanager.unregister(self) -# -# XXX old code to automatically load classes -# def canonical_importname(name): name = name.lower() modprefix = "pytest_" @@ -254,59 +210,3 @@ def importplugin(importspec): -def isgenerichook(name): - return name == "pytest_plugins" or \ - name.startswith("pytest_funcarg__") - -def getargs(func): - args = py.std.inspect.getargs(py.code.getrawcode(func))[0] - startindex = py.std.inspect.ismethod(func) and 1 or 0 - return args[startindex:] - -def collectattr(obj, prefixes=("pytest_",)): - methods = {} - for apiname in dir(obj): - for prefix in prefixes: - if apiname.startswith(prefix): - methods[apiname] = getattr(obj, apiname) - return methods - -def formatdef(func): - return "%s%s" %( - func.__name__, - py.std.inspect.formatargspec(*py.std.inspect.getargspec(func)) - ) - -if __name__ == "__main__": - import py.plugin - basedir = py._dir.join('_plugin') - 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.impl.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 - - --- a/testing/plugin/test_pytest_helpconfig.py +++ b/testing/plugin/test_pytest_helpconfig.py @@ -1,4 +1,5 @@ import py, os +from py.plugin.pytest_helpconfig import collectattr def test_version(testdir): assert py.version == py.__version__ @@ -16,3 +17,15 @@ def test_helpconfig(testdir): "*cmdline*conftest*ENV*", ]) +def test_collectattr(): + class A: + def pytest_hello(self): + pass + class B(A): + def pytest_world(self): + pass + methods = py.builtin.sorted(collectattr(B)) + assert list(methods) == ['pytest_hello', 'pytest_world'] + methods = py.builtin.sorted(collectattr(B())) + assert list(methods) == ['pytest_hello', 'pytest_world'] + From commits-noreply at bitbucket.org Tue Dec 29 12:37:46 2009 From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org) Date: Tue, 29 Dec 2009 11:37:46 +0000 (UTC) Subject: [py-svn] py-trunk commit 42a36fa714e6: remove/reduce internal global state: py._com.registry is now fully contained and always instantiated from the py.test PluginManager class. Message-ID: <20091229113746.5F5677EEF9@bitbucket.org> # HG changeset patch -- Bitbucket.org # Project py-trunk # URL http://bitbucket.org/hpk42/py-trunk/overview/ # User holger krekel # Date 1262086577 -3600 # Node ID 42a36fa714e665d79b69aa9aa8165871b6f7af2d # Parent 756383d5451058b00a35aa42d815c9f75fccfdae remove/reduce internal global state: py._com.registry is now fully contained and always instantiated from the py.test PluginManager class. --- a/testing/pytest/test_pluginmanager.py +++ b/testing/pytest/test_pluginmanager.py @@ -1,5 +1,7 @@ import py, os from py.impl.test.pluginmanager import PluginManager, canonical_importname +from py.impl.test.pluginmanager import Registry, MultiCall, HookRelay, varnames + class TestBootstrapping: def test_consider_env_fails_to_import(self, monkeypatch): @@ -278,3 +280,184 @@ def test_namespace_has_default_and_env_p """) result = testdir.runpython(p) assert result.ret == 0 + +def test_varnames(): + def f(x): + pass + class A: + def f(self, y): + pass + assert varnames(f) == ("x",) + assert varnames(A().f) == ('y',) + +class TestMultiCall: + def test_uses_copy_of_methods(self): + l = [lambda: 42] + mc = MultiCall(l, {}) + repr(mc) + l[:] = [] + res = mc.execute() + return res == 42 + + def test_call_passing(self): + class P1: + def m(self, __multicall__, x): + assert len(__multicall__.results) == 1 + assert not __multicall__.methods + return 17 + + class P2: + def m(self, __multicall__, x): + assert __multicall__.results == [] + assert __multicall__.methods + return 23 + + p1 = P1() + p2 = P2() + multicall = MultiCall([p1.m, p2.m], {'x': 23}) + assert "23" in repr(multicall) + reslist = multicall.execute() + assert len(reslist) == 2 + # ensure reversed order + assert reslist == [23, 17] + + def test_keyword_args(self): + def f(x): + return x + 1 + class A: + def f(self, x, y): + return x + y + multicall = MultiCall([f, A().f], dict(x=23, y=24)) + assert "'x': 23" in repr(multicall) + assert "'y': 24" in repr(multicall) + reslist = multicall.execute() + assert reslist == [24+23, 24] + assert "2 results" in repr(multicall) + + def test_keywords_call_error(self): + multicall = MultiCall([lambda x: x], {}) + py.test.raises(TypeError, "multicall.execute()") + + def test_call_subexecute(self): + def m(__multicall__): + subresult = __multicall__.execute() + return subresult + 1 + + def n(): + return 1 + + call = MultiCall([n, m], {}, firstresult=True) + res = call.execute() + assert res == 2 + + def test_call_none_is_no_result(self): + def m1(): + return 1 + def m2(): + return None + res = MultiCall([m1, m2], {}, firstresult=True).execute() + assert res == 1 + res = MultiCall([m1, m2], {}).execute() + assert res == [1] + +class TestRegistry: + + def test_register(self): + registry = Registry() + class MyPlugin: + pass + my = MyPlugin() + registry.register(my) + assert list(registry) == [my] + my2 = MyPlugin() + registry.register(my2) + assert list(registry) == [my, my2] + + assert registry.isregistered(my) + assert registry.isregistered(my2) + registry.unregister(my) + assert not registry.isregistered(my) + assert list(registry) == [my2] + + def test_listattr(self): + plugins = Registry() + class api1: + x = 41 + class api2: + x = 42 + class api3: + x = 43 + plugins.register(api1()) + plugins.register(api2()) + plugins.register(api3()) + l = list(plugins.listattr('x')) + assert l == [41, 42, 43] + l = list(plugins.listattr('x', reverse=True)) + assert l == [43, 42, 41] + + class api4: + x = 44 + l = list(plugins.listattr('x', extra=(api4,))) + assert l == [41,42,43,44] + assert len(list(plugins)) == 3 # otherwise extra added + +class TestHookRelay: + def test_happypath(self): + registry = Registry() + class Api: + def hello(self, arg): + pass + + mcm = HookRelay(hookspecs=Api, registry=registry) + assert hasattr(mcm, 'hello') + assert repr(mcm.hello).find("hello") != -1 + class Plugin: + def hello(self, arg): + return arg + 1 + registry.register(Plugin()) + l = mcm.hello(arg=3) + assert l == [4] + assert not hasattr(mcm, 'world') + + def test_only_kwargs(self): + registry = Registry() + class Api: + def hello(self, arg): + pass + mcm = HookRelay(hookspecs=Api, registry=registry) + py.test.raises(TypeError, "mcm.hello(3)") + + def test_firstresult_definition(self): + registry = Registry() + class Api: + def hello(self, arg): pass + hello.firstresult = True + + mcm = HookRelay(hookspecs=Api, registry=registry) + class Plugin: + def hello(self, arg): + return arg + 1 + registry.register(Plugin()) + res = mcm.hello(arg=3) + assert res == 4 + + def test_hooks_extra_plugins(self): + registry = Registry() + class Api: + def hello(self, arg): + pass + hookrelay = HookRelay(hookspecs=Api, registry=registry) + hook_hello = hookrelay.hello + class Plugin: + def hello(self, arg): + return arg + 1 + registry.register(Plugin()) + class Plugin2: + def hello(self, arg): + return arg + 2 + newhook = hookrelay._makecall("hello", extralookup=Plugin2()) + l = newhook(arg=3) + assert l == [5, 4] + l2 = hook_hello(arg=3) + assert l2 == [4] + --- a/testing/root/test_com.py +++ b/testing/root/test_com.py @@ -1,193 +1,3 @@ import py import os -from py.impl._com import Registry, MultiCall, HookRelay, varnames - -def test_varnames(): - def f(x): - pass - class A: - def f(self, y): - pass - assert varnames(f) == ("x",) - assert varnames(A().f) == ('y',) - -class TestMultiCall: - def test_uses_copy_of_methods(self): - l = [lambda: 42] - mc = MultiCall(l, {}) - repr(mc) - l[:] = [] - res = mc.execute() - return res == 42 - - def test_call_passing(self): - class P1: - def m(self, __multicall__, x): - assert len(__multicall__.results) == 1 - assert not __multicall__.methods - return 17 - - class P2: - def m(self, __multicall__, x): - assert __multicall__.results == [] - assert __multicall__.methods - return 23 - - p1 = P1() - p2 = P2() - multicall = MultiCall([p1.m, p2.m], {'x': 23}) - assert "23" in repr(multicall) - reslist = multicall.execute() - assert len(reslist) == 2 - # ensure reversed order - assert reslist == [23, 17] - - def test_keyword_args(self): - def f(x): - return x + 1 - class A: - def f(self, x, y): - return x + y - multicall = MultiCall([f, A().f], dict(x=23, y=24)) - assert "'x': 23" in repr(multicall) - assert "'y': 24" in repr(multicall) - reslist = multicall.execute() - assert reslist == [24+23, 24] - assert "2 results" in repr(multicall) - - def test_keywords_call_error(self): - multicall = MultiCall([lambda x: x], {}) - py.test.raises(TypeError, "multicall.execute()") - - def test_call_subexecute(self): - def m(__multicall__): - subresult = __multicall__.execute() - return subresult + 1 - - def n(): - return 1 - - call = MultiCall([n, m], {}, firstresult=True) - res = call.execute() - assert res == 2 - - def test_call_none_is_no_result(self): - def m1(): - return 1 - def m2(): - return None - res = MultiCall([m1, m2], {}, firstresult=True).execute() - assert res == 1 - res = MultiCall([m1, m2], {}).execute() - assert res == [1] - -class TestRegistry: - - def test_register(self): - registry = Registry() - class MyPlugin: - pass - my = MyPlugin() - registry.register(my) - assert list(registry) == [my] - my2 = MyPlugin() - registry.register(my2) - assert list(registry) == [my, my2] - - assert registry.isregistered(my) - assert registry.isregistered(my2) - registry.unregister(my) - assert not registry.isregistered(my) - assert list(registry) == [my2] - - def test_listattr(self): - plugins = Registry() - class api1: - x = 41 - class api2: - x = 42 - class api3: - x = 43 - plugins.register(api1()) - plugins.register(api2()) - plugins.register(api3()) - l = list(plugins.listattr('x')) - assert l == [41, 42, 43] - l = list(plugins.listattr('x', reverse=True)) - assert l == [43, 42, 41] - - class api4: - x = 44 - l = list(plugins.listattr('x', extra=(api4,))) - assert l == [41,42,43,44] - assert len(list(plugins)) == 3 # otherwise extra added - -def test_api_and_defaults(): - assert isinstance(py._com.comregistry, Registry) - -class TestHookRelay: - def test_happypath(self): - registry = Registry() - class Api: - def hello(self, arg): - pass - - mcm = HookRelay(hookspecs=Api, registry=registry) - assert hasattr(mcm, 'hello') - assert repr(mcm.hello).find("hello") != -1 - class Plugin: - def hello(self, arg): - return arg + 1 - registry.register(Plugin()) - l = mcm.hello(arg=3) - assert l == [4] - assert not hasattr(mcm, 'world') - - def test_only_kwargs(self): - registry = Registry() - class Api: - def hello(self, arg): - pass - mcm = HookRelay(hookspecs=Api, registry=registry) - py.test.raises(TypeError, "mcm.hello(3)") - - def test_firstresult_definition(self): - registry = Registry() - class Api: - def hello(self, arg): pass - hello.firstresult = True - - mcm = HookRelay(hookspecs=Api, registry=registry) - class Plugin: - def hello(self, arg): - return arg + 1 - registry.register(Plugin()) - res = mcm.hello(arg=3) - assert res == 4 - - def test_default_plugins(self): - class Api: pass - mcm = HookRelay(hookspecs=Api, registry=py._com.comregistry) - assert mcm._registry == py._com.comregistry - - def test_hooks_extra_plugins(self): - registry = Registry() - class Api: - def hello(self, arg): - pass - hookrelay = HookRelay(hookspecs=Api, registry=registry) - hook_hello = hookrelay.hello - class Plugin: - def hello(self, arg): - return arg + 1 - registry.register(Plugin()) - class Plugin2: - def hello(self, arg): - return arg + 2 - newhook = hookrelay._makecall("hello", extralookup=Plugin2()) - l = newhook(arg=3) - assert l == [5, 4] - l2 = hook_hello(arg=3) - assert l2 == [4] - --- a/testing/pytest/test_config.py +++ b/testing/pytest/test_config.py @@ -264,9 +264,6 @@ def test_options_on_small_file_do_not_bl ['--traceconfig'], ['-v'], ['-v', '-v']): runfiletest(opts + [path]) -def test_default_registry(): - assert py.test.config.pluginmanager.comregistry is py._com.comregistry - def test_ensuretemp(): # XXX test for deprecation d1 = py.test.ensuretemp('hello') --- a/py/plugin/pytest__pytest.py +++ b/py/plugin/pytest__pytest.py @@ -1,21 +1,17 @@ import py +from py.impl.test.pluginmanager import HookRelay + def pytest_funcarg___pytest(request): return PytestArg(request) class PytestArg: def __init__(self, request): self.request = request - self.monkeypatch = self.request.getfuncargvalue("monkeypatch") - self.comregistry = py._com.Registry() - self.monkeypatch.setattr(py._com, 'comregistry', self.comregistry) - def gethookrecorder(self, hookspecs, registry=None): - if registry is not None: - self.monkeypatch.setattr(py._com, 'comregistry', registry) - self.comregistry = registry - hookrecorder = HookRecorder(self.comregistry) - hookrecorder.start_recording(hookspecs) + def gethookrecorder(self, hook): + hookrecorder = HookRecorder(hook._registry) + hookrecorder.start_recording(hook._hookspecs) self.request.addfinalizer(hookrecorder.finish_recording) return hookrecorder @@ -32,8 +28,8 @@ class ParsedCall: return "" %(self._name, d) class HookRecorder: - def __init__(self, comregistry): - self._comregistry = comregistry + def __init__(self, registry): + self._registry = registry self.calls = [] self._recorders = {} @@ -46,12 +42,12 @@ class HookRecorder: setattr(RecordCalls, name, self._makecallparser(method)) recorder = RecordCalls() self._recorders[hookspecs] = recorder - self._comregistry.register(recorder) - self.hook = py._com.HookRelay(hookspecs, registry=self._comregistry) + self._registry.register(recorder) + self.hook = HookRelay(hookspecs, registry=self._registry) def finish_recording(self): for recorder in self._recorders.values(): - self._comregistry.unregister(recorder) + self._registry.unregister(recorder) self._recorders.clear() def _makecallparser(self, method): --- a/py/__init__.py +++ b/py/__init__.py @@ -25,12 +25,6 @@ py.apipkg.initpkg(__name__, dict( _pydirs = '.impl._metainfo:pydirs', version = 'py:__version__', # backward compatibility - _com = { - 'Registry': '.impl._com:Registry', - 'MultiCall': '.impl._com:MultiCall', - 'comregistry': '.impl._com:comregistry', - 'HookRelay': '.impl._com:HookRelay', - }, cmdline = { 'pytest': '.impl.cmdline.pytest:main', 'pylookup': '.impl.cmdline.pylookup:main', --- a/testing/pytest/dist/test_gwmanage.py +++ b/testing/pytest/dist/test_gwmanage.py @@ -8,17 +8,17 @@ import py import os from py.impl.test.dist.gwmanage import GatewayManager, HostRSync +from py.impl.test.pluginmanager import HookRelay, Registry from py.plugin import hookspec import execnet def pytest_funcarg__hookrecorder(request): _pytest = request.getfuncargvalue('_pytest') hook = request.getfuncargvalue('hook') - return _pytest.gethookrecorder(hook._hookspecs, hook._registry) + return _pytest.gethookrecorder(hook) def pytest_funcarg__hook(request): - registry = py._com.Registry() - return py._com.HookRelay(hookspec, registry) + return HookRelay(hookspec, Registry()) class TestGatewayManagerPopen: def test_popen_no_default_chdir(self, hook): @@ -90,7 +90,6 @@ class pytest_funcarg__mysetup: tmp = request.getfuncargvalue('tmpdir') self.source = tmp.mkdir("source") self.dest = tmp.mkdir("dest") - request.getfuncargvalue("_pytest") # to have patching of py._com.comregistry class TestHRSync: def test_hrsync_filter(self, mysetup): --- a/py/impl/_com.py +++ /dev/null @@ -1,125 +0,0 @@ -""" -py lib plugins and plugin call management -""" - -import py -import inspect - -__all__ = ['Registry', 'MultiCall', 'comregistry', 'HookRelay'] - -class MultiCall: - """ execute a call into multiple python functions/methods. """ - - def __init__(self, methods, kwargs, firstresult=False): - self.methods = methods[:] - self.kwargs = kwargs.copy() - self.kwargs['__multicall__'] = self - self.results = [] - self.firstresult = firstresult - - def __repr__(self): - status = "%d results, %d meths" % (len(self.results), len(self.methods)) - return "" %(status, self.kwargs) - - def execute(self): - while self.methods: - method = self.methods.pop() - kwargs = self.getkwargs(method) - res = method(**kwargs) - if res is not None: - self.results.append(res) - if self.firstresult: - return res - if not self.firstresult: - return self.results - - def getkwargs(self, method): - kwargs = {} - for argname in varnames(method): - try: - kwargs[argname] = self.kwargs[argname] - except KeyError: - pass # might be optional param - return kwargs - -def varnames(func): - ismethod = inspect.ismethod(func) - rawcode = py.code.getrawcode(func) - try: - return rawcode.co_varnames[ismethod:] - except AttributeError: - return () - -class Registry: - """ - Manage Plugins: register/unregister call calls to plugins. - """ - def __init__(self, plugins=None): - if plugins is None: - plugins = [] - self._plugins = plugins - - def register(self, plugin): - assert not isinstance(plugin, str) - assert not plugin in self._plugins - self._plugins.append(plugin) - - def unregister(self, plugin): - self._plugins.remove(plugin) - - def isregistered(self, plugin): - return plugin in self._plugins - - def __iter__(self): - return iter(self._plugins) - - def listattr(self, attrname, plugins=None, extra=(), reverse=False): - l = [] - if plugins is None: - plugins = self._plugins - candidates = list(plugins) + list(extra) - for plugin in candidates: - try: - l.append(getattr(plugin, attrname)) - except AttributeError: - continue - if reverse: - l.reverse() - return l - -class HookRelay: - def __init__(self, hookspecs, registry): - self._hookspecs = hookspecs - self._registry = registry - for name, method in vars(hookspecs).items(): - if name[:1] != "_": - setattr(self, name, self._makecall(name)) - - def _makecall(self, name, extralookup=None): - hookspecmethod = getattr(self._hookspecs, name) - firstresult = getattr(hookspecmethod, 'firstresult', False) - return HookCaller(self, name, firstresult=firstresult, - extralookup=extralookup) - - def _getmethods(self, name, extralookup=()): - return self._registry.listattr(name, extra=extralookup) - - def _performcall(self, name, multicall): - return multicall.execute() - -class HookCaller: - def __init__(self, hookrelay, name, firstresult, extralookup=None): - self.hookrelay = hookrelay - self.name = name - self.firstresult = firstresult - self.extralookup = extralookup and [extralookup] or () - - def __repr__(self): - return "" %(self.name,) - - def __call__(self, **kwargs): - methods = self.hookrelay._getmethods(self.name, self.extralookup) - mc = MultiCall(methods, kwargs, firstresult=self.firstresult) - return self.hookrelay._performcall(self.name, mc) - -comregistry = Registry([]) --- a/py/plugin/pytest_pytester.py +++ b/py/plugin/pytest_pytester.py @@ -22,11 +22,6 @@ def pytest_funcarg__testdir(request): tmptestdir = TmpTestdir(request) return tmptestdir -def pytest_funcarg__reportrecorder(request): - reprec = ReportRecorder(py._com.comregistry) - request.addfinalizer(lambda: reprec.comregistry.unregister(reprec)) - return reprec - rex_outcome = re.compile("(\d+) (\w+)") class RunResult: def __init__(self, ret, outlines, errlines): @@ -71,10 +66,10 @@ class TmpTestdir: def __repr__(self): return "" % (self.tmpdir,) - def Config(self, comregistry=None, topdir=None): + def Config(self, registry=None, topdir=None): if topdir is None: topdir = self.tmpdir.dirpath() - return pytestConfig(comregistry, topdir=topdir) + return pytestConfig(registry, topdir=topdir) def finalize(self): for p in self._syspathremove: @@ -89,19 +84,13 @@ class TmpTestdir: del sys.modules[name] def getreportrecorder(self, obj): - if isinstance(obj, py._com.Registry): - registry = obj - elif hasattr(obj, 'comregistry'): - registry = obj.comregistry - elif hasattr(obj, 'pluginmanager'): - registry = obj.pluginmanager.comregistry - elif hasattr(obj, 'config'): - registry = obj.config.pluginmanager.comregistry - else: - raise ValueError("obj %r provides no comregistry" %(obj,)) - assert isinstance(registry, py._com.Registry) - reprec = ReportRecorder(registry) - reprec.hookrecorder = self._pytest.gethookrecorder(hookspec, registry) + if hasattr(obj, 'config'): + obj = obj.config + if hasattr(obj, 'hook'): + obj = obj.hook + assert hasattr(obj, '_hookspecs'), obj + reprec = ReportRecorder(obj) + reprec.hookrecorder = self._pytest.gethookrecorder(obj) reprec.hook = reprec.hookrecorder.hook return reprec @@ -334,9 +323,10 @@ class PseudoPlugin: self.__dict__.update(vars) class ReportRecorder(object): - def __init__(self, comregistry): - self.comregistry = comregistry - comregistry.register(self) + def __init__(self, hook): + self.hook = hook + self.registry = hook._registry + self.registry.register(self) def getcall(self, name): return self.hookrecorder.getcall(name) @@ -401,7 +391,7 @@ class ReportRecorder(object): self.hookrecorder.calls[:] = [] def unregister(self): - self.comregistry.unregister(self) + self.registry.unregister(self) self.hookrecorder.finish_recording() class LineComp: --- a/testing/plugin/test_pytest__pytest.py +++ b/testing/plugin/test_pytest__pytest.py @@ -1,9 +1,10 @@ import py +import sys from py.plugin.pytest__pytest import HookRecorder +from py.impl.test.pluginmanager import Registry def test_hookrecorder_basic(): - comregistry = py._com.Registry() - rec = HookRecorder(comregistry) + rec = HookRecorder(Registry()) class ApiClass: def xyz(self, arg): pass @@ -15,9 +16,7 @@ def test_hookrecorder_basic(): py.test.raises(ValueError, "rec.popcall('abc')") def test_hookrecorder_basic_no_args_hook(): - import sys - comregistry = py._com.Registry() - rec = HookRecorder(comregistry) + rec = HookRecorder(Registry()) apimod = type(sys)('api') def xyz(): pass @@ -27,23 +26,20 @@ def test_hookrecorder_basic_no_args_hook call = rec.popcall("xyz") assert call._name == "xyz" -reg = py._com.comregistry -def test_functional_default(testdir, _pytest): - assert _pytest.comregistry == py._com.comregistry - assert _pytest.comregistry != reg - def test_functional(testdir, linecomp): reprec = testdir.inline_runsource(""" import py + from py.impl.test.pluginmanager import HookRelay, Registry pytest_plugins="_pytest" def test_func(_pytest): class ApiClass: def xyz(self, arg): pass - rec = _pytest.gethookrecorder(ApiClass) + hook = HookRelay(ApiClass, Registry()) + rec = _pytest.gethookrecorder(hook) class Plugin: def xyz(self, arg): return arg + 1 - rec._comregistry.register(Plugin()) + rec._registry.register(Plugin()) res = rec.hook.xyz(arg=41) assert res == [42] """) --- a/testing/plugin/test_pytest_pytester.py +++ b/testing/plugin/test_pytest_pytester.py @@ -2,10 +2,9 @@ import py from py.plugin.pytest_pytester import LineMatcher, LineComp def test_reportrecorder(testdir): - registry = py._com.Registry() - recorder = testdir.getreportrecorder(registry) + item = testdir.getitem("def test_func(): pass") + recorder = testdir.getreportrecorder(item.config) assert not recorder.getfailures() - item = testdir.getitem("def test_func(): pass") class rep: excinfo = None passed = False --- a/contrib/runtesthelper.py +++ b/contrib/runtesthelper.py @@ -14,6 +14,5 @@ def pytest(argv=None): except SystemExit: pass # we need to reset the global py.test.config object - py._com.comregistry = py._com.comregistry.__class__([]) py.test.config = py.test.config.__class__( - pluginmanager=py.test._PluginManager(py._com.comregistry)) + pluginmanager=py.test._PluginManager()) --- a/testing/pytest/test_pickling.py +++ b/testing/pytest/test_pickling.py @@ -3,14 +3,11 @@ import pickle def setglobals(request): oldconfig = py.test.config - oldcom = py._com.comregistry print("setting py.test.config to None") py.test.config = None - py._com.comregistry = py._com.Registry() def resetglobals(): py.builtin.print_("setting py.test.config to", oldconfig) py.test.config = oldconfig - py._com.comregistry = oldcom request.addfinalizer(resetglobals) def pytest_funcarg__testdir(request): @@ -190,7 +187,7 @@ def test_config__setstate__wired_correct from py.impl.test.dist.mypickle import PickleChannel channel = PickleChannel(channel) config = channel.receive() - assert py.test.config.pluginmanager.comregistry == py._com.comregistry, "comregistry wrong" + assert py.test.config == config """) channel = PickleChannel(channel) config = testdir.parseconfig() --- a/py/impl/test/config.py +++ b/py/impl/test/config.py @@ -107,9 +107,8 @@ class Config(object): # warning global side effects: # * registering to py lib plugins # * setting py.test.config - py._com.comregistry = py._com.Registry() self.__init__( - pluginmanager=py.test._PluginManager(py._com.comregistry), + pluginmanager=py.test._PluginManager(), topdir=py.path.local(), ) # we have to set py.test.config because preparse() @@ -310,6 +309,6 @@ def gettopdir(args): # this is the one per-process instance of py.test configuration config_per_process = Config( - pluginmanager=py.test._PluginManager(py._com.comregistry) + pluginmanager=py.test._PluginManager() ) --- a/py/impl/test/pluginmanager.py +++ b/py/impl/test/pluginmanager.py @@ -2,6 +2,7 @@ managing loading and interacting with pytest plugins. """ import py +import inspect from py.plugin import hookspec from py.impl.test.outcome import Skipped @@ -16,15 +17,10 @@ def check_old_use(mod, modname): class PluginManager(object): class Error(Exception): """signals a plugin specific error.""" - def __init__(self, comregistry=None): - if comregistry is None: - comregistry = py._com.Registry() - self.comregistry = comregistry + def __init__(self): + self.registry = Registry() self._name2plugin = {} - - self.hook = py._com.HookRelay( - hookspecs=hookspec, - registry=self.comregistry) + self.hook = HookRelay(hookspecs=hookspec, registry=self.registry) self.register(self) for spec in default_plugins: self.import_plugin(spec) @@ -39,18 +35,18 @@ class PluginManager(object): def register(self, plugin, name=None): assert not self.isregistered(plugin), plugin - assert not self.comregistry.isregistered(plugin), plugin + assert not self.registry.isregistered(plugin), plugin name = self._getpluginname(plugin, name) if name in self._name2plugin: return False self._name2plugin[name] = plugin self.hook.pytest_plugin_registered(manager=self, plugin=plugin) - self.comregistry.register(plugin) + self.registry.register(plugin) return True def unregister(self, plugin): self.hook.pytest_plugin_unregistered(plugin=plugin) - self.comregistry.unregister(plugin) + self.registry.unregister(plugin) for name, value in list(self._name2plugin.items()): if value == plugin: del self._name2plugin[name] @@ -63,7 +59,7 @@ class PluginManager(object): return True def getplugins(self): - return list(self.comregistry) + return list(self.registry) def getplugin(self, name): try: @@ -143,7 +139,7 @@ class PluginManager(object): # # def listattr(self, attrname, plugins=None, extra=()): - return self.comregistry.listattr(attrname, plugins=plugins, extra=extra) + return self.registry.listattr(attrname, plugins=plugins, extra=extra) def notify_exception(self, excinfo=None): if excinfo is None: @@ -153,8 +149,8 @@ class PluginManager(object): def do_addoption(self, parser): mname = "pytest_addoption" - methods = self.comregistry.listattr(mname, reverse=True) - mc = py._com.MultiCall(methods, {'parser': parser}) + methods = self.registry.listattr(mname, reverse=True) + mc = MultiCall(methods, {'parser': parser}) mc.execute() def pytest_plugin_registered(self, plugin): @@ -168,7 +164,7 @@ class PluginManager(object): {'config': self._config}) def call_plugin(self, plugin, methname, kwargs): - return py._com.MultiCall( + return MultiCall( methods=self.listattr(methname, plugins=[plugin]), kwargs=kwargs, firstresult=True).execute() @@ -210,3 +206,118 @@ def importplugin(importspec): +class MultiCall: + """ execute a call into multiple python functions/methods. """ + + def __init__(self, methods, kwargs, firstresult=False): + self.methods = methods[:] + self.kwargs = kwargs.copy() + self.kwargs['__multicall__'] = self + self.results = [] + self.firstresult = firstresult + + def __repr__(self): + status = "%d results, %d meths" % (len(self.results), len(self.methods)) + return "" %(status, self.kwargs) + + def execute(self): + while self.methods: + method = self.methods.pop() + kwargs = self.getkwargs(method) + res = method(**kwargs) + if res is not None: + self.results.append(res) + if self.firstresult: + return res + if not self.firstresult: + return self.results + + def getkwargs(self, method): + kwargs = {} + for argname in varnames(method): + try: + kwargs[argname] = self.kwargs[argname] + except KeyError: + pass # might be optional param + return kwargs + +def varnames(func): + ismethod = inspect.ismethod(func) + rawcode = py.code.getrawcode(func) + try: + return rawcode.co_varnames[ismethod:] + except AttributeError: + return () + +class Registry: + """ + Manage Plugins: register/unregister call calls to plugins. + """ + def __init__(self, plugins=None): + if plugins is None: + plugins = [] + self._plugins = plugins + + def register(self, plugin): + assert not isinstance(plugin, str) + assert not plugin in self._plugins + self._plugins.append(plugin) + + def unregister(self, plugin): + self._plugins.remove(plugin) + + def isregistered(self, plugin): + return plugin in self._plugins + + def __iter__(self): + return iter(self._plugins) + + def listattr(self, attrname, plugins=None, extra=(), reverse=False): + l = [] + if plugins is None: + plugins = self._plugins + candidates = list(plugins) + list(extra) + for plugin in candidates: + try: + l.append(getattr(plugin, attrname)) + except AttributeError: + continue + if reverse: + l.reverse() + return l + +class HookRelay: + def __init__(self, hookspecs, registry): + self._hookspecs = hookspecs + self._registry = registry + for name, method in vars(hookspecs).items(): + if name[:1] != "_": + setattr(self, name, self._makecall(name)) + + def _makecall(self, name, extralookup=None): + hookspecmethod = getattr(self._hookspecs, name) + firstresult = getattr(hookspecmethod, 'firstresult', False) + return HookCaller(self, name, firstresult=firstresult, + extralookup=extralookup) + + def _getmethods(self, name, extralookup=()): + return self._registry.listattr(name, extra=extralookup) + + def _performcall(self, name, multicall): + return multicall.execute() + +class HookCaller: + def __init__(self, hookrelay, name, firstresult, extralookup=None): + self.hookrelay = hookrelay + self.name = name + self.firstresult = firstresult + self.extralookup = extralookup and [extralookup] or () + + def __repr__(self): + return "" %(self.name,) + + def __call__(self, **kwargs): + methods = self.hookrelay._getmethods(self.name, self.extralookup) + mc = MultiCall(methods, kwargs, firstresult=self.firstresult) + return self.hookrelay._performcall(self.name, mc) + From commits-noreply at bitbucket.org Tue Dec 29 12:37:48 2009 From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org) Date: Tue, 29 Dec 2009 11:37:48 +0000 (UTC) Subject: [py-svn] py-trunk commit 85626112b548: robustiy some randomly failing tests Message-ID: <20091229113748.22C077EF05@bitbucket.org> # HG changeset patch -- Bitbucket.org # Project py-trunk # URL http://bitbucket.org/hpk42/py-trunk/overview/ # User holger krekel # Date 1262086605 -3600 # Node ID 85626112b5485b6ed78f5ced1bc95a3be8b537c3 # Parent 42a36fa714e665d79b69aa9aa8165871b6f7af2d robustiy some randomly failing tests --- a/bin-for-dist/test_install.py +++ b/bin-for-dist/test_install.py @@ -147,7 +147,8 @@ def test_plugin_setuptools_entry_point_i out = venv.pytest_getouterr("-h") assert "testpluginopt" in out -def test_cmdline_entrypoints(): +def test_cmdline_entrypoints(monkeypatch): + monkeypatch.syspath_prepend(py.path.local(__file__).dirpath().dirpath()) from setup import cmdline_entrypoints versioned_scripts = ['py.test', 'py.which'] unversioned_scripts = versioned_scripts + [ 'py.cleanup', --- a/py/impl/path/svnwc.py +++ b/py/impl/path/svnwc.py @@ -429,6 +429,7 @@ class SvnWCCommandPath(common.PathBase): return self strpath = property(lambda x: str(x.localpath), None, None, "string path") + rev = property(lambda x: x.info(usecache=0).rev, None, None, "revision") def __eq__(self, other): return self.localpath == getattr(other, 'localpath', None) @@ -796,7 +797,6 @@ recursively. """ raise py.error.ENOENT(self, "not a versioned resource:" + " %s != %s" % (info.path, self.localpath)) cache.info[self] = info - self.rev = info.rev return info def listdir(self, fil=None, sort=None): --- a/testing/path/test_svnwc.py +++ b/testing/path/test_svnwc.py @@ -12,7 +12,7 @@ def test_make_repo(path1, tmpdir): repo = py.path.svnurl("file://%s" % repo) wc = py.path.svnwc(tmpdir.join("wc")) wc.checkout(repo) - assert wc.info().rev == 0 + assert wc.rev == 0 assert len(wc.listdir()) == 0 p = wc.join("a_file") p.write("test file") --- a/testing/path/test_local.py +++ b/testing/path/test_local.py @@ -499,7 +499,9 @@ class TestPOSIXLocalPath: # we could wait here but timer resolution is very # system dependent path.read() + time.sleep(0.01) atime2 = path.atime() + time.sleep(0.01) duration = time.time() - now assert (atime2-atime1) <= duration From commits-noreply at bitbucket.org Tue Dec 29 14:13:30 2009 From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org) Date: Tue, 29 Dec 2009 13:13:30 +0000 (UTC) Subject: [py-svn] py-trunk commit 2bf7d69d8f1d: simplify Config initialization Message-ID: <20091229131330.BDB868385E@bitbucket.org> # HG changeset patch -- Bitbucket.org # Project py-trunk # URL http://bitbucket.org/hpk42/py-trunk/overview/ # User holger krekel # Date 1262092392 -3600 # Node ID 2bf7d69d8f1d9b4ed8c975ef94942a35c46192e2 # Parent 85626112b5485b6ed78f5ced1bc95a3be8b537c3 simplify Config initialization --- a/py/impl/test/config.py +++ b/py/impl/test/config.py @@ -20,27 +20,22 @@ class Error(Exception): """ Test Configuration Error. """ class Config(object): - """ test configuration object, provides access to config valueso, - the pluginmanager and plugin api. - """ + """ access to config values, pluginmanager and plugin hooks. """ Option = py.std.optparse.Option Error = Error basetemp = None _sessionclass = None - def __init__(self, pluginmanager=None, topdir=None): + def __init__(self, topdir=None): self.option = CmdOptions() self.topdir = topdir self._parser = parseopt.Parser( usage="usage: %prog [options] [file_or_dir] [file_or_dir] [...]", processopt=self._processopt, ) - if pluginmanager is None: - pluginmanager = py.test._PluginManager() - assert isinstance(pluginmanager, py.test._PluginManager) - self.pluginmanager = pluginmanager + self.pluginmanager = py.test._PluginManager() self._conftest = Conftest(onimport=self._onimportconftest) - self.hook = pluginmanager.hook + self.hook = self.pluginmanager.hook def _onimportconftest(self, conftestmodule): self.trace("loaded conftestmodule %r" %(conftestmodule,)) @@ -104,17 +99,12 @@ class Config(object): return l, self.option def __setstate__(self, repr): - # warning global side effects: - # * registering to py lib plugins - # * setting py.test.config - self.__init__( - pluginmanager=py.test._PluginManager(), - topdir=py.path.local(), - ) - # we have to set py.test.config because preparse() - # might load conftest files which have - # py.test.config.addoptions() lines in them + # we have to set py.test.config because loading + # of conftest files may use it (deprecated) + # mainly by py.test.config.addoptions() py.test.config = self + # next line will registers default plugins + self.__init__(topdir=py.path.local()) args, cmdlineopts = repr args = [self.topdir.join(x) for x in args] self.option = cmdlineopts @@ -307,8 +297,5 @@ def gettopdir(args): return pkgdir.dirpath() -# this is the one per-process instance of py.test configuration -config_per_process = Config( - pluginmanager=py.test._PluginManager() -) - +# this is default per-process instance of py.test configuration +config_per_process = Config() --- a/py/plugin/pytest_pytester.py +++ b/py/plugin/pytest_pytester.py @@ -66,10 +66,10 @@ class TmpTestdir: def __repr__(self): return "" % (self.tmpdir,) - def Config(self, registry=None, topdir=None): + def Config(self, topdir=None): if topdir is None: topdir = self.tmpdir.dirpath() - return pytestConfig(registry, topdir=topdir) + return pytestConfig(topdir=topdir) def finalize(self): for p in self._syspathremove: From commits-noreply at bitbucket.org Tue Dec 29 14:51:15 2009 From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org) Date: Tue, 29 Dec 2009 13:51:15 +0000 (UTC) Subject: [py-svn] apipkg commit e630694050b2: refine and better test onfirstaccess handling Message-ID: <20091229135115.2752C7EF05@bitbucket.org> # HG changeset patch -- Bitbucket.org # Project apipkg # URL http://bitbucket.org/hpk42/apipkg/overview/ # User holger krekel # Date 1262094649 -3600 # Node ID e630694050b26b2db57b46b2c68774af4c3b9e08 # Parent 47756aba150f1bd68aebc04364a9a44fe78ef524 refine and better test onfirstaccess handling --- /dev/null +++ b/conftest.py @@ -0,0 +1,9 @@ + +def pytest_generate_tests(metafunc): + multi = getattr(metafunc.function, 'multi', None) + if multi is None: + return + assert len(multi.kwargs) == 1 + for name, l in multi.kwargs.items(): + for val in l: + metafunc.addcall(funcargs={name: val}) --- a/apipkg.py +++ b/apipkg.py @@ -35,8 +35,6 @@ class ApiModule(ModuleType): apimod = ApiModule(subname, importspec, implprefix) sys.modules[subname] = apimod setattr(self, name, apimod) - elif name == '__onfirstaccess__': - self.__map__[name] = importspec else: modpath, attrname = importspec.split(':') if modpath[0] == '.': @@ -57,16 +55,17 @@ class ApiModule(ModuleType): return '' % (self.__name__,) def __getattr__(self, name): - try: - func = self.__map__.pop('__onfirstaccess__') - except KeyError: - pass - else: - if func: - func() + target = None + if '__onfirstaccess__' in self.__map__: + target = self.__map__.pop('__onfirstaccess__') + importobj(*target)() + if name == "__onfirstaccess__": + return target try: modpath, attrname = self.__map__[name] except KeyError: + if target is not None: # retry, onfirstaccess might have set attrs + return getattr(self, name) raise AttributeError(name) else: result = importobj(modpath, attrname) --- a/test_apipkg.py +++ b/test_apipkg.py @@ -209,30 +209,49 @@ def test_error_loading_one_element(monke py.test.raises(ImportError, 'errorloading1.x') py.test.raises(ImportError, 'errorloading1.x') -def test_onfirstaccess(monkeypatch): - mod = type(sys)('hello') - monkeypatch.setitem(sys.modules, 'hello', mod) - l = [] - apipkg.initpkg('hello', { - '__onfirstaccess__': lambda: l.append(1), - 'world': 'sys:executable', - 'world2': 'sys:executable', - }) - hello = sys.modules['hello'] - assert hello.world == sys.executable - assert len(l) == 1 - assert hello.world == sys.executable - assert len(l) == 1 +def test_onfirstaccess(tmpdir, monkeypatch): + pkgdir = tmpdir.mkdir("firstaccess") + pkgdir.join('__init__.py').write(py.code.Source(""" + import apipkg + apipkg.initpkg(__name__, exportdefs={ + '__onfirstaccess__': '.submod:init', + 'l': '.submod:l', + }, + ) + """)) + pkgdir.join('submod.py').write(py.code.Source(""" + l = [] + def init(): + l.append(1) + """)) + monkeypatch.syspath_prepend(tmpdir) + import firstaccess + assert isinstance(firstaccess, apipkg.ApiModule) + assert len(firstaccess.l) == 1 + assert len(firstaccess.l) == 1 -def test_onfirstaccess__dict__(monkeypatch): - mod = type(sys)('hello') - monkeypatch.setitem(sys.modules, 'hello', mod) - l = [] - apipkg.initpkg('hello', { - '__onfirstaccess__': lambda: l.append(1), - }) - hello = sys.modules['hello'] - hello.__dict__ - assert len(l) == 1 - hello.__dict__ - assert len(l) == 1 + at py.test.mark.multi(mode=['attr', 'dict']) +def test_onfirstaccess_setsnewattr(tmpdir, monkeypatch, mode): + pkgname = tmpdir.basename.replace("-", "") + pkgdir = tmpdir.mkdir(pkgname) + pkgdir.join('__init__.py').write(py.code.Source(""" + import apipkg + apipkg.initpkg(__name__, exportdefs={ + '__onfirstaccess__': '.submod:init', + }, + ) + """)) + pkgdir.join('submod.py').write(py.code.Source(""" + def init(): + import %s as pkg + pkg.newattr = 42 + """ % pkgname)) + monkeypatch.syspath_prepend(tmpdir) + mod = __import__(pkgname) + assert isinstance(mod, apipkg.ApiModule) + if mode == 'attr': + assert mod.newattr == 42 + elif mode == "dict": + print mod.__dict__.keys() + assert 'newattr' in mod.__dict__ + assert '__onfirstaccess__' not in vars(mod) --- a/CHANGELOG +++ b/CHANGELOG @@ -1,5 +1,5 @@ 1.0.0b3 (compared to 1.0.0b2) ------------------------------------ -- added special attribute __onfirstaccess__ whose value will - be called on the first attribute access to the apimodule +- added special __onfirstaccess__ attribute whose value will + be called on the first attribute access of an apimodule. From commits-noreply at bitbucket.org Tue Dec 29 16:30:19 2009 From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org) Date: Tue, 29 Dec 2009 15:30:19 +0000 (UTC) Subject: [py-svn] apipkg commit b517c03e87af: further refinements with respect to access to __onfirstaccess__ itself Message-ID: <20091229153019.5DBB27EF07@bitbucket.org> # HG changeset patch -- Bitbucket.org # Project apipkg # URL http://bitbucket.org/hpk42/apipkg/overview/ # User holger krekel # Date 1262096715 -3600 # Node ID b517c03e87afaf52144479d7affb74fd3f512d4c # Parent e630694050b26b2db57b46b2c68774af4c3b9e08 further refinements with respect to access to __onfirstaccess__ itself --- a/test_apipkg.py +++ b/test_apipkg.py @@ -229,8 +229,9 @@ def test_onfirstaccess(tmpdir, monkeypat assert isinstance(firstaccess, apipkg.ApiModule) assert len(firstaccess.l) == 1 assert len(firstaccess.l) == 1 + assert '__onfirstaccess__' not in firstaccess.__all__ - at py.test.mark.multi(mode=['attr', 'dict']) + at py.test.mark.multi(mode=['attr', 'dict', 'onfirst']) def test_onfirstaccess_setsnewattr(tmpdir, monkeypatch, mode): pkgname = tmpdir.basename.replace("-", "") pkgdir = tmpdir.mkdir(pkgname) @@ -254,4 +255,7 @@ def test_onfirstaccess_setsnewattr(tmpdi elif mode == "dict": print mod.__dict__.keys() assert 'newattr' in mod.__dict__ + elif mode == "onfirst": + assert not hasattr(mod, '__onfirstaccess__') + assert not hasattr(mod, '__onfirstaccess__') assert '__onfirstaccess__' not in vars(mod) --- a/apipkg.py +++ b/apipkg.py @@ -26,7 +26,7 @@ def importobj(modpath, attrname): class ApiModule(ModuleType): def __init__(self, name, importspec, implprefix=None): self.__name__ = name - self.__all__ = list(importspec) + self.__all__ = [x for x in importspec if x != '__onfirstaccess__'] self.__map__ = {} self.__implprefix__ = implprefix or name for name, importspec in importspec.items(): @@ -59,12 +59,11 @@ class ApiModule(ModuleType): if '__onfirstaccess__' in self.__map__: target = self.__map__.pop('__onfirstaccess__') importobj(*target)() - if name == "__onfirstaccess__": - return target try: modpath, attrname = self.__map__[name] except KeyError: - if target is not None: # retry, onfirstaccess might have set attrs + if target is not None and name != '__onfirstaccess__': + # retry, onfirstaccess might have set attrs return getattr(self, name) raise AttributeError(name) else: @@ -78,6 +77,7 @@ class ApiModule(ModuleType): dictdescr = ModuleType.__dict__['__dict__'] dict = dictdescr.__get__(self) if dict is not None: + hasattr(self, 'some') for name in self.__all__: hasattr(self, name) # force attribute load, ignore errors return dict From commits-noreply at bitbucket.org Tue Dec 29 16:30:58 2009 From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org) Date: Tue, 29 Dec 2009 15:30:58 +0000 (UTC) Subject: [py-svn] py-trunk commit 5fdeab905d84: cleanup py.test.* namespace, docstrings for improved pydoc and interactive usage. Message-ID: <20091229153058.3712A7EF07@bitbucket.org> # HG changeset patch -- Bitbucket.org # Project py-trunk # URL http://bitbucket.org/hpk42/py-trunk/overview/ # User holger krekel # Date 1262100588 -3600 # Node ID 5fdeab905d846238569414086178fcbdbcc775c6 # Parent 2bf7d69d8f1d9b4ed8c975ef94942a35c46192e2 cleanup py.test.* namespace, docstrings for improved pydoc and interactive usage. use new apipkg __onfirstaccess__ feature to initialize the py.test namespace with the default plugins. This, besides other good implications, means that you can now type: pydoc py.test or help(py.test) --- a/testing/pytest/test_pluginmanager.py +++ b/testing/pytest/test_pluginmanager.py @@ -223,6 +223,7 @@ class TestPytestPluginInteractions: import py def test_hello(): assert hello == "world" + assert 'hello' in py.test.__all__ """) result = testdir.runpytest(p) result.stdout.fnmatch_lines([ --- a/testing/pytest/acceptance_test.py +++ b/testing/pytest/acceptance_test.py @@ -1,4 +1,4 @@ -import py +import sys, py class TestGeneralUsage: def test_config_error(self, testdir): @@ -74,3 +74,18 @@ class TestGeneralUsage: assert result.stderr.fnmatch_lines([ "*ERROR: can't collect: %s" %(p2,) ]) + + + def test_earlyinit(self, testdir): + p = testdir.makepyfile(""" + import py + assert hasattr(py.test, 'mark') + """) + result = testdir._run(sys.executable, p) + assert result.ret == 0 + + def test_pydoc(self, testdir): + result = testdir._run(sys.executable, "-c", "import py ; help(py.test)") + assert result.ret == 0 + s = result.stdout.str() + assert 'MarkGenerator' in s --- a/py/plugin/pytest_mark.py +++ b/py/plugin/pytest_mark.py @@ -78,16 +78,18 @@ tests:: import py def pytest_namespace(): - return {'mark': Mark()} + return {'mark': MarkGenerator()} - -class Mark(object): +class MarkGenerator: + """ non-underscore attributes of this object can be used as decorators for + marking test functions. Example: @py.test.mark.slowtest in front of a + function will set the 'slowtest' marker object on it. """ def __getattr__(self, name): if name[0] == "_": raise AttributeError(name) - return MarkerDecorator(name) + return MarkDecorator(name) -class MarkerDecorator: +class MarkDecorator: """ decorator for setting function attributes. """ def __init__(self, name): self.markname = name @@ -97,15 +99,17 @@ class MarkerDecorator: def __repr__(self): d = self.__dict__.copy() name = d.pop('markname') - return "" %(name, d) + return "" %(name, d) def __call__(self, *args, **kwargs): + """ if passed a single callable argument: decorate it with mark info. + otherwise add *args/**kwargs in-place to mark information. """ if args: if len(args) == 1 and hasattr(args[0], '__call__'): func = args[0] holder = getattr(func, self.markname, None) if holder is None: - holder = Marker(self.markname, self.args, self.kwargs) + holder = MarkInfo(self.markname, self.args, self.kwargs) setattr(func, self.markname, holder) else: holder.kwargs.update(self.kwargs) @@ -116,7 +120,7 @@ class MarkerDecorator: self.kwargs.update(kwargs) return self -class Marker: +class MarkInfo: def __init__(self, name, args, kwargs): self._name = name self.args = args @@ -129,7 +133,7 @@ class Marker: raise AttributeError(name) def __repr__(self): - return "" % ( + return "" % ( self._name, self.args, self.kwargs) @@ -143,6 +147,6 @@ def pytest_pycollect_makeitem(__multical func = getattr(func, 'im_func', func) # py2 for parent in [x for x in (mod, cls) if x]: marker = getattr(parent.obj, 'pytestmark', None) - if isinstance(marker, MarkerDecorator): + if isinstance(marker, MarkDecorator): marker(func) return item --- a/py/__init__.py +++ b/py/__init__.py @@ -40,8 +40,8 @@ py.apipkg.initpkg(__name__, dict( test = { # helpers for use from test functions or collectors + '__onfirstaccess__' : '.impl.test.config:onpytestaccess', '__doc__' : '.impl.test:__doc__', - '_PluginManager' : '.impl.test.pluginmanager:PluginManager', 'raises' : '.impl.test.outcome:raises', 'skip' : '.impl.test.outcome:skip', 'importorskip' : '.impl.test.outcome:importorskip', --- a/py/impl/test/outcome.py +++ b/py/impl/test/outcome.py @@ -47,23 +47,33 @@ class Exit(KeyboardInterrupt): # exposed helper methods def exit(msg): - """ exit testing process immediately. """ + """ exit testing process as if KeyboardInterrupt was triggered. """ __tracebackhide__ = True raise Exit(msg) def skip(msg=""): - """ skip with the given message. """ + """ skip an executing test with the given message. Note: it's usually + better use the py.test.mark.skipif marker to declare a test to be + skipped under certain conditions like mismatching platforms or + dependencies. See the pytest_skipping plugin for details. + """ __tracebackhide__ = True raise Skipped(msg=msg) -def fail(msg="unknown failure"): - """ fail with the given Message. """ +def fail(msg=""): + """ explicitely fail this executing test with the given Message. """ __tracebackhide__ = True raise Failed(msg=msg) def raises(ExpectedException, *args, **kwargs): - """ raise AssertionError, if target code does not raise the expected - exception. + """ if args[0] is callable: raise AssertionError if calling it with + the remaining arguments does not raise the expected exception. + if args[0] is a string: raise AssertionError if executing the + the string in the calling scope does not raise expected exception. + for examples: + x = 5 + raises(TypeError, lambda x: x + 'hello', x=x) + raises(TypeError, "x + 'hello'") """ __tracebackhide__ = True assert args @@ -95,7 +105,10 @@ def raises(ExpectedException, *args, **k expr=args, expected=ExpectedException) def importorskip(modname, minversion=None): - """ return imported module or perform a dynamic skip() """ + """ return imported module if it has a higher __version__ than the + optionally specified 'minversion' - otherwise call py.test.skip() + with a message detailing the mismatch. + """ compile(modname, '', 'eval') # to catch syntaxerrors try: mod = __import__(modname, None, None, ['__doc__']) @@ -114,6 +127,7 @@ def importorskip(modname, minversion=Non return mod + # exitcodes for the command line EXIT_OK = 0 EXIT_TESTSFAILED = 1 --- a/testing/plugin/test_pytest_mark.py +++ b/testing/plugin/test_pytest_mark.py @@ -1,10 +1,10 @@ import py -from py.plugin.pytest_mark import Mark +from py.plugin.pytest_mark import MarkGenerator as Mark class TestMark: def test_pytest_mark_notcallable(self): mark = Mark() - py.test.raises(TypeError, "mark()") + py.test.raises((AttributeError, TypeError), "mark()") def test_pytest_mark_bare(self): mark = Mark() --- a/CHANGELOG +++ b/CHANGELOG @@ -1,4 +1,4 @@ -Changes between 1.1.2 and 1.1.1 +Changes between 1.X and 1.1.1 ===================================== - new option: --ignore will prevent specified path from collection. @@ -7,6 +7,12 @@ Changes between 1.1.2 and 1.1.1 - install 'py.test' and `py.which` with a ``-$VERSION`` suffix to disambiguate between Python3, python2.X, Jython and PyPy installed versions. +- make py.test.* helpers provided by default plugins visible early - + works transparently both for pydoc and for interactive sessions + which will regularly see e.g. py.test.mark and py.test.importorskip. + +- simplify internal plugin manager machinery + - fix assert reinterpreation that sees a call containing "keyword=..." - skip some install-tests if no execnet is available --- a/py/impl/test/__init__.py +++ b/py/impl/test/__init__.py @@ -1,1 +1,1 @@ -""" versatile unit-testing tool + libraries """ +""" assertion and py.test helper API.""" --- a/contrib/runtesthelper.py +++ b/contrib/runtesthelper.py @@ -14,5 +14,4 @@ def pytest(argv=None): except SystemExit: pass # we need to reset the global py.test.config object - py.test.config = py.test.config.__class__( - pluginmanager=py.test._PluginManager()) + py.test.config = py.test.config.__class__() --- a/py/plugin/pytest_skipping.py +++ b/py/plugin/pytest_skipping.py @@ -3,7 +3,7 @@ advanced skipping for python test functi With this plugin you can mark test functions for conditional skipping or as "xfail", expected-to-fail. Skipping a test will avoid running it -at all while xfail-marked tests will run and result in an inverted outcome: +while xfail-marked tests will run and result in an inverted outcome: a pass becomes a failure and a fail becomes a semi-passing one. The need for skipping a test is usually connected to a condition. @@ -121,6 +121,7 @@ within test or setup code. Example:: import py + def pytest_runtest_setup(item): expr, result = evalexpression(item, 'skipif') if result: --- a/py/impl/test/config.py +++ b/py/impl/test/config.py @@ -1,11 +1,14 @@ import py, os from py.impl.test.conftesthandle import Conftest - +from py.impl.test.pluginmanager import PluginManager from py.impl.test import parseopt def ensuretemp(string, dir=1): - """ return temporary directory path with - the given string as the trailing part. + """ (deprecated) return temporary directory path with + the given string as the trailing part. It is usually + better to use the 'tmpdir' function argument which will + take care to provide empty unique directories for each + test call even if the test is called multiple times. """ return py.test.config.ensuretemp(string, dir=dir) @@ -33,7 +36,7 @@ class Config(object): usage="usage: %prog [options] [file_or_dir] [file_or_dir] [...]", processopt=self._processopt, ) - self.pluginmanager = py.test._PluginManager() + self.pluginmanager = PluginManager() self._conftest = Conftest(onimport=self._onimportconftest) self.hook = self.pluginmanager.hook @@ -178,7 +181,7 @@ class Config(object): This function gets invoked during testing session initialization. """ py.log._apiwarn("1.0", "define plugins to add options", stacklevel=2) - group = self._parser.addgroup(groupname) + group = self._parser.getgroup(groupname) for opt in specs: group._addoption_instance(opt) return self.option @@ -296,6 +299,11 @@ def gettopdir(args): else: return pkgdir.dirpath() +def onpytestaccess(): + # it's enough to have our containing module loaded as + # it initializes a per-process config instance + # which loads default plugins which add to py.test.* + pass -# this is default per-process instance of py.test configuration +# a default per-process instance of py.test configuration config_per_process = Config() --- a/py/impl/test/pluginmanager.py +++ b/py/impl/test/pluginmanager.py @@ -15,8 +15,6 @@ def check_old_use(mod, modname): assert not hasattr(mod, clsname), (mod, clsname) class PluginManager(object): - class Error(Exception): - """signals a plugin specific error.""" def __init__(self): self.registry = Registry() self._name2plugin = {} @@ -157,6 +155,7 @@ class PluginManager(object): dic = self.call_plugin(plugin, "pytest_namespace", {}) or {} for name, value in dic.items(): setattr(py.test, name, value) + py.test.__all__.append(name) if hasattr(self, '_config'): self.call_plugin(plugin, "pytest_addoption", {'parser': self._config._parser}) --- a/py/apipkg.py +++ b/py/apipkg.py @@ -8,7 +8,7 @@ see http://pypi.python.org/pypi/apipkg import sys from types import ModuleType -__version__ = "1.0b2" +__version__ = "1.0b3" def initpkg(pkgname, exportdefs): """ initialize given package from the export definitions. """ @@ -26,7 +26,7 @@ def importobj(modpath, attrname): class ApiModule(ModuleType): def __init__(self, name, importspec, implprefix=None): self.__name__ = name - self.__all__ = list(importspec) + self.__all__ = [x for x in importspec if x != '__onfirstaccess__'] self.__map__ = {} self.__implprefix__ = implprefix or name for name, importspec in importspec.items(): @@ -45,12 +45,26 @@ class ApiModule(ModuleType): self.__map__[name] = (modpath, attrname) def __repr__(self): + l = [] + if hasattr(self, '__version__'): + l.append("version=" + repr(self.__version__)) + if hasattr(self, '__file__'): + l.append('from ' + repr(self.__file__)) + if l: + return '' % (self.__name__, " ".join(l)) return '' % (self.__name__,) def __getattr__(self, name): + target = None + if '__onfirstaccess__' in self.__map__: + target = self.__map__.pop('__onfirstaccess__') + importobj(*target)() try: modpath, attrname = self.__map__[name] except KeyError: + if target is not None and name != '__onfirstaccess__': + # retry, onfirstaccess might have set attrs + return getattr(self, name) raise AttributeError(name) else: result = importobj(modpath, attrname) @@ -63,6 +77,7 @@ class ApiModule(ModuleType): dictdescr = ModuleType.__dict__['__dict__'] dict = dictdescr.__get__(self) if dict is not None: + hasattr(self, 'some') for name in self.__all__: hasattr(self, name) # force attribute load, ignore errors return dict From commits-noreply at bitbucket.org Tue Dec 29 16:58:15 2009 From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org) Date: Tue, 29 Dec 2009 15:58:15 +0000 (UTC) Subject: [py-svn] py-trunk commit 216a74f8830b: fixup funcargs docs Message-ID: <20091229155815.8A1027EE88@bitbucket.org> # HG changeset patch -- Bitbucket.org # Project py-trunk # URL http://bitbucket.org/hpk42/py-trunk/overview/ # User holger krekel # Date 1262102276 -3600 # Node ID 216a74f8830b3305d5500edb478cdde34281ed02 # Parent 5fdeab905d846238569414086178fcbdbcc775c6 fixup funcargs docs --- a/doc/test/funcargs.txt +++ b/doc/test/funcargs.txt @@ -1,19 +1,15 @@ ============================================================== -**funcargs**: advanced test setup and parametrization +**funcargs**: advanced test parametrization ============================================================== .. contents:: :local: :depth: 2 -what are "funcargs" and what are they good for? +what is a "funcarg"? ================================================= -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. +A *funcarg* is the short name for "test function argument". Each python test function invocation may receive one or multiple function arguments. Function argument values can be created next to the test code or in separate test configuration files which allows test functions to remain ignorant of how its base test values are created. A test function can also be called multiple times with different sets of function arguments, allowing for arbitrary parametrization. A Funcarg parameter can be any value, a simple number or an application object. .. _`contact possibilities`: ../contact.html .. _`parametrizing tests, generalized`: http://tetamap.wordpress.com/2009/05/13/parametrizing-python-tests-generalized/ @@ -25,7 +21,7 @@ test parameters can be generated. .. _`funcarg factory`: .. _factory: -funcarg factories: setting up test function arguments +funcarg factories: creating test function arguments ============================================================== Test functions can specify one ore more arguments ("funcargs") @@ -92,9 +88,7 @@ functions or access meta data about a te 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 objects represents a handle on a specific python test function call. A request object is passed to a funcarg factory and provides access to test configuration and context as well as some `useful caching and finalization helpers`_. Here is a list of attributes: ``request.function``: python function object requesting the argument From commits-noreply at bitbucket.org Tue Dec 29 18:41:48 2009 From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org) Date: Tue, 29 Dec 2009 17:41:48 +0000 (UTC) Subject: [py-svn] py-trunk commit a6a865e1e25d: some testing hygene: move _reparse testing functionality to actual test support code, un-xfail a now passing test, reduce direct py.test.config usage aiming for deprecation. Message-ID: <20091229174148.C11EF7EF08@bitbucket.org> # HG changeset patch -- Bitbucket.org # Project py-trunk # URL http://bitbucket.org/hpk42/py-trunk/overview/ # User holger krekel # Date 1262106174 -3600 # Node ID a6a865e1e25d692b8df70d656e0067b400015f13 # Parent 216a74f8830b3305d5500edb478cdde34281ed02 some testing hygene: move _reparse testing functionality to actual test support code, un-xfail a now passing test, reduce direct py.test.config usage aiming for deprecation. --- a/conftest.py +++ b/conftest.py @@ -28,21 +28,17 @@ def pytest_addoption(parser): def pytest_funcarg__specssh(request): return getspecssh(request.config) -def getgspecs(config=None): - if config is None: - config = py.test.config +def getgspecs(config): return [execnet.XSpec(spec) for spec in config.getvalueorskip("gspecs")] # configuration information for tests -def getgspecs(config=None): - if config is None: - config = py.test.config +def getgspecs(config): return [execnet.XSpec(spec) for spec in config.getvalueorskip("gspecs")] -def getspecssh(config=None): +def getspecssh(config): xspecs = getgspecs(config) for spec in xspecs: if spec.ssh: @@ -51,7 +47,7 @@ def getspecssh(config=None): return spec py.test.skip("need '--gx ssh=...'") -def getsocketspec(config=None): +def getsocketspec(config): xspecs = getgspecs(config) for spec in xspecs: if spec.socket: --- a/py/plugin/pytest_pytester.py +++ b/py/plugin/pytest_pytester.py @@ -205,6 +205,20 @@ class TmpTestdir: config.parse(args) return config + def reparseconfig(self, args=None): + """ this is used from tests that want to re-invoke parse(). """ + if not args: + args = [self.tmpdir] + from py.impl.test import config + oldconfig = py.test.config + try: + c = config.config_per_process = py.test.config = pytestConfig() + c.basetemp = oldconfig.mktemp("reparse", numbered=True) + c.parse(args) + return c + finally: + config.config_per_process = py.test.config = oldconfig + def parseconfigure(self, *args): config = self.parseconfig(*args) config.pluginmanager.do_configure(config) --- a/testing/pytest/test_session.py +++ b/testing/pytest/test_session.py @@ -1,8 +1,8 @@ import py class SessionTests: - def test_initsession(self, tmpdir): - config = py.test.config._reparse([tmpdir]) + def test_initsession(self, testdir, tmpdir): + config = testdir.reparseconfig() session = config.initsession() assert session.config is config --- a/testing/plugin/test_pytest_default.py +++ b/testing/plugin/test_pytest_default.py @@ -1,9 +1,9 @@ import py from py.plugin.pytest_default import pytest_report_iteminfo -def test_implied_different_sessions(tmpdir): +def test_implied_different_sessions(testdir, tmpdir): def x(*args): - config = py.test.config._reparse([tmpdir] + list(args)) + config = testdir.reparseconfig([tmpdir] + list(args)) try: config.pluginmanager.do_configure(config) except ValueError: --- a/testing/pytest/test_pycollect.py +++ b/testing/pytest/test_pycollect.py @@ -250,8 +250,8 @@ class TestFunction: assert isinstance(modcol, py.test.collect.Module) assert hasattr(modcol.obj, 'test_func') - def test_function_equality(self, tmpdir): - config = py.test.config._reparse([tmpdir]) + def test_function_equality(self, testdir, tmpdir): + config = testdir.reparseconfig() f1 = py.test.collect.Function(name="name", args=(1,), callobj=isinstance) f2 = py.test.collect.Function(name="name", @@ -271,8 +271,8 @@ class TestFunction: assert f1 == f1_b assert not f1 != f1_b - def test_function_equality_with_callspec(self, tmpdir): - config = py.test.config._reparse([tmpdir]) + def test_function_equality_with_callspec(self, testdir, tmpdir): + config = testdir.reparseconfig() class callspec1: param = 1 funcargs = {} --- a/testing/pytest/test_collect.py +++ b/testing/pytest/test_collect.py @@ -52,11 +52,11 @@ class TestCollector: parent = fn.getparent(py.test.collect.Class) assert parent is cls - def test_totrail_and_back(self, tmpdir): + def test_totrail_and_back(self, testdir, tmpdir): a = tmpdir.ensure("a", dir=1) tmpdir.ensure("a", "__init__.py") x = tmpdir.ensure("a", "trail.py") - config = py.test.config._reparse([x]) + config = testdir.reparseconfig([x]) col = config.getfsnode(x) trail = col._totrail() assert len(trail) == 2 @@ -65,8 +65,8 @@ class TestCollector: col2 = py.test.collect.Collector._fromtrail(trail, config) assert col2.listnames() == col.listnames() - def test_totrail_topdir_and_beyond(self, tmpdir): - config = py.test.config._reparse([tmpdir]) + def test_totrail_topdir_and_beyond(self, testdir, tmpdir): + config = testdir.reparseconfig() col = config.getfsnode(config.topdir) trail = col._totrail() assert len(trail) == 2 --- a/testing/pytest/dist/test_nodemanage.py +++ b/testing/pytest/dist/test_nodemanage.py @@ -12,9 +12,9 @@ class pytest_funcarg__mysetup: class TestNodeManager: @py.test.mark.xfail - def test_rsync_roots_no_roots(self, mysetup): + def test_rsync_roots_no_roots(self, testdir, mysetup): mysetup.source.ensure("dir1", "file1").write("hello") - config = py.test.config._reparse([source]) + config = testdir.reparseconfig([source]) nodemanager = NodeManager(config, ["popen//chdir=%s" % mysetup.dest]) assert nodemanager.config.topdir == source == config.topdir nodemanager.rsync_roots() @@ -53,7 +53,7 @@ class TestNodeManager: assert dest.join("dir1", "dir2", 'hello').check() nodemanager.gwmanager.exit() - def test_init_rsync_roots(self, mysetup): + def test_init_rsync_roots(self, testdir, mysetup): source, dest = mysetup.source, mysetup.dest dir2 = source.ensure("dir1", "dir2", dir=1) source.ensure("dir1", "somefile", dir=1) @@ -62,14 +62,14 @@ class TestNodeManager: source.join("conftest.py").write(py.code.Source(""" rsyncdirs = ['dir1/dir2'] """)) - session = py.test.config._reparse([source]).initsession() + session = testdir.reparseconfig([source]).initsession() nodemanager = NodeManager(session.config, ["popen//chdir=%s" % dest]) nodemanager.rsync_roots() assert dest.join("dir2").check() assert not dest.join("dir1").check() assert not dest.join("bogus").check() - def test_rsyncignore(self, mysetup): + def test_rsyncignore(self, testdir, mysetup): source, dest = mysetup.source, mysetup.dest dir2 = source.ensure("dir1", "dir2", dir=1) dir5 = source.ensure("dir5", "dir6", "bogus") @@ -79,7 +79,7 @@ class TestNodeManager: rsyncdirs = ['dir1', 'dir5'] rsyncignore = ['dir1/dir2', 'dir5/dir6'] """)) - session = py.test.config._reparse([source]).initsession() + session = testdir.reparseconfig([source]).initsession() nodemanager = NodeManager(session.config, ["popen//chdir=%s" % dest]) nodemanager.rsync_roots() @@ -88,12 +88,12 @@ class TestNodeManager: assert dest.join("dir5","file").check() assert not dest.join("dir6").check() - def test_optimise_popen(self, mysetup): + def test_optimise_popen(self, testdir, mysetup): source, dest = mysetup.source, mysetup.dest specs = ["popen"] * 3 source.join("conftest.py").write("rsyncdirs = ['a']") source.ensure('a', dir=1) - config = py.test.config._reparse([source]) + config = testdir.reparseconfig([source]) nodemanager = NodeManager(config, specs) nodemanager.rsync_roots() for gwspec in nodemanager.gwmanager.specs: @@ -105,7 +105,7 @@ class TestNodeManager: specs = ["popen"] * 2 source.join("conftest.py").write("rsyncdirs = ['a']") source.ensure('a', dir=1) - config = py.test.config._reparse([source, '--debug']) + config = testdir.reparseconfig([source, '--debug']) assert config.option.debug nodemanager = NodeManager(config, specs) reprec = testdir.getreportrecorder(config).hookrecorder --- a/testing/pytest/test_pluginmanager.py +++ b/testing/pytest/test_pluginmanager.py @@ -273,7 +273,6 @@ class TestPytestPluginInteractions: assert not pluginmanager.listattr("hello") assert pluginmanager.listattr("x") == [42] - at py.test.mark.xfail def test_namespace_has_default_and_env_plugins(testdir): p = testdir.makepyfile(""" import py --- a/testing/pytest/dist/test_txnode.py +++ b/testing/pytest/dist/test_txnode.py @@ -44,7 +44,8 @@ class MySetup: def makenode(self, config=None): if config is None: - config = py.test.config._reparse([]) + testdir = self.request.getfuncargvalue("testdir") + config = testdir.reparseconfig([]) self.config = config self.queue = Queue() self.xspec = execnet.XSpec("popen") --- a/testing/pytest/test_config.py +++ b/testing/pytest/test_config.py @@ -17,7 +17,7 @@ class TestConfigCmdlineParsing: ) """) testdir.chdir() - config = py.test.config._reparse(['-G', '17']) + config = testdir.reparseconfig(['-G', '17']) assert config.option.gdest == 17 def test_parser_addoption_default_env(self, testdir, monkeypatch): @@ -56,11 +56,11 @@ class TestConfigCmdlineParsing: ) """) py.test.raises(ValueError, """ - py.test.config._reparse(['-g', '17']) + testdir.reparseconfig(['-g', '17']) """) - def test_parsing_again_fails(self, tmpdir): - config = py.test.config._reparse([tmpdir]) + def test_parsing_again_fails(self, testdir): + config = testdir.reparseconfig([testdir.tmpdir]) py.test.raises(AssertionError, "config.parse([])") @@ -82,13 +82,11 @@ class TestConfigTmpdir: assert tmp2 != tmp def test_reparse(self, testdir): - config = testdir.Config() - config.basetemp = testdir.mkdir("my") - config2 = config._reparse([]) - assert config2.getbasetemp().relto(config.basetemp) - config3 = config._reparse([]) - assert config3.getbasetemp().relto(config.basetemp) - assert config2.basetemp != config3.basetemp + config2 = testdir.reparseconfig([]) + config3 = testdir.reparseconfig([]) + assert config2.getbasetemp() != config3.getbasetemp() + assert not config2.getbasetemp().relto(config3.getbasetemp()) + assert not config3.getbasetemp().relto(config2.getbasetemp()) class TestConfigAPI: @@ -100,7 +98,7 @@ class TestConfigAPI: assert config.getvalue("x") == 1 assert config.getvalue("x", o.join('sub')) == 2 py.test.raises(KeyError, "config.getvalue('y')") - config = py.test.config._reparse([str(o.join('sub'))]) + config = testdir.reparseconfig([str(o.join('sub'))]) assert config.getvalue("x") == 2 assert config.getvalue("y") == 3 assert config.getvalue("x", o) == 1 @@ -118,18 +116,18 @@ class TestConfigAPI: def test_config_overwrite(self, testdir): o = testdir.tmpdir o.ensure("conftest.py").write("x=1") - config = py.test.config._reparse([str(o)]) + config = testdir.reparseconfig([str(o)]) assert config.getvalue('x') == 1 config.option.x = 2 assert config.getvalue('x') == 2 - config = py.test.config._reparse([str(o)]) + config = testdir.reparseconfig([str(o)]) assert config.getvalue('x') == 1 - def test_getconftest_pathlist(self, tmpdir): + def test_getconftest_pathlist(self, testdir, tmpdir): somepath = tmpdir.join("x", "y", "z") p = tmpdir.join("conftest.py") p.write("pathlist = ['.', %r]" % str(somepath)) - config = py.test.config._reparse([p]) + config = testdir.reparseconfig([p]) assert config.getconftest_pathlist('notexist') is None pl = config.getconftest_pathlist('pathlist') print(pl) @@ -150,8 +148,8 @@ class TestConfigAPI: class TestConfigApi_getcolitems: - def test_getcolitems_onedir(self, tmpdir): - config = py.test.config._reparse([tmpdir]) + def test_getcolitems_onedir(self, testdir): + config = testdir.reparseconfig([testdir.tmpdir]) colitems = config.getcolitems() assert len(colitems) == 1 col = colitems[0] @@ -159,17 +157,17 @@ class TestConfigApi_getcolitems: for col in col.listchain(): assert col.config is config - def test_getcolitems_twodirs(self, tmpdir): - config = py.test.config._reparse([tmpdir, tmpdir]) + def test_getcolitems_twodirs(self, testdir, tmpdir): + config = testdir.reparseconfig([tmpdir, tmpdir]) colitems = config.getcolitems() assert len(colitems) == 2 col1, col2 = colitems assert col1.name == col2.name assert col1.parent == col2.parent - def test_getcolitems_curdir_and_subdir(self, tmpdir): + def test_getcolitems_curdir_and_subdir(self, testdir, tmpdir): a = tmpdir.ensure("a", dir=1) - config = py.test.config._reparse([tmpdir, a]) + config = testdir.reparseconfig([tmpdir, a]) colitems = config.getcolitems() assert len(colitems) == 2 col1, col2 = colitems @@ -179,9 +177,9 @@ class TestConfigApi_getcolitems: for subcol in col.listchain(): assert col.config is config - def test__getcol_global_file(self, tmpdir): + def test__getcol_global_file(self, testdir, tmpdir): x = tmpdir.ensure("x.py") - config = py.test.config._reparse([x]) + config = testdir.reparseconfig([x]) col = config.getfsnode(x) assert isinstance(col, py.test.collect.Module) assert col.name == 'x.py' @@ -190,9 +188,9 @@ class TestConfigApi_getcolitems: for col in col.listchain(): assert col.config is config - def test__getcol_global_dir(self, tmpdir): + def test__getcol_global_dir(self, testdir, tmpdir): x = tmpdir.ensure("a", dir=1) - config = py.test.config._reparse([x]) + config = testdir.reparseconfig([x]) col = config.getfsnode(x) assert isinstance(col, py.test.collect.Directory) print(col.listchain()) @@ -200,10 +198,10 @@ class TestConfigApi_getcolitems: assert col.parent is None assert col.config is config - def test__getcol_pkgfile(self, tmpdir): + def test__getcol_pkgfile(self, testdir, tmpdir): x = tmpdir.ensure("x.py") tmpdir.ensure("__init__.py") - config = py.test.config._reparse([x]) + config = testdir.reparseconfig([x]) col = config.getfsnode(x) assert isinstance(col, py.test.collect.Module) assert col.name == 'x.py' @@ -215,16 +213,16 @@ class TestConfigApi_getcolitems: class TestOptionEffects: def test_boxed_option_default(self, testdir): tmpdir = testdir.tmpdir.ensure("subdir", dir=1) - config = py.test.config._reparse([tmpdir]) + config = testdir.reparseconfig() config.initsession() assert not config.option.boxed py.test.importorskip("execnet") - config = py.test.config._reparse(['-d', tmpdir]) + config = testdir.reparseconfig(['-d', tmpdir]) config.initsession() assert not config.option.boxed def test_is_not_boxed_by_default(self, testdir): - config = py.test.config._reparse([testdir.tmpdir]) + config = testdir.reparseconfig([testdir.tmpdir]) assert not config.option.boxed class TestConfig_gettopdir: --- a/py/impl/test/config.py +++ b/py/impl/test/config.py @@ -227,19 +227,6 @@ class Config(object): self.trace("instantiated session %r" % session) return session - def _reparse(self, args): - """ this is used from tests that want to re-invoke parse(). """ - #assert args # XXX should not be empty - global config_per_process - oldconfig = py.test.config - try: - config_per_process = py.test.config = Config() - config_per_process.basetemp = self.mktemp("reparse", numbered=True) - config_per_process.parse(args) - return config_per_process - finally: - config_per_process = py.test.config = oldconfig - def getxspecs(self): xspeclist = [] for xspec in self.getvalue("tx"): From commits-noreply at bitbucket.org Tue Dec 29 18:41:50 2009 From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org) Date: Tue, 29 Dec 2009 17:41:50 +0000 (UTC) Subject: [py-svn] py-trunk commit 717f09c82287: make looponfailing a bit more robust against relative imports and changed directories - needs more work, probably. Message-ID: <20091229174150.91C757EF0C@bitbucket.org> # HG changeset patch -- Bitbucket.org # Project py-trunk # URL http://bitbucket.org/hpk42/py-trunk/overview/ # User holger krekel # Date 1262108484 -3600 # Node ID 717f09c82287ac415d60579e45c6cafc5c99875d # Parent a6a865e1e25d692b8df70d656e0067b400015f13 make looponfailing a bit more robust against relative imports and changed directories - needs more work, probably. --- a/py/impl/test/looponfail/remote.py +++ b/py/impl/test/looponfail/remote.py @@ -62,22 +62,22 @@ class RemoteControl(object): if hasattr(self, 'gateway'): raise ValueError("already have gateway %r" % self.gateway) self.trace("setting up slave session") - old = self.config.topdir.chdir() - try: - self.gateway = self.initgateway() - finally: - old.chdir() - channel = self.gateway.remote_exec(source=""" + self.gateway = self.initgateway() + channel = self.gateway.remote_exec(""" + import os from py.impl.test.dist.mypickle import PickleChannel from py.impl.test.looponfail.remote import slave_runsession + chdir = channel.receive() outchannel = channel.gateway.newchannel() channel.send(outchannel) channel = PickleChannel(channel) + os.chdir(chdir) # unpickling config uses cwd as topdir config, fullwidth, hasmarkup = channel.receive() import sys sys.stdout = sys.stderr = outchannel.makefile('w') slave_runsession(channel, config, fullwidth, hasmarkup) """) + channel.send(str(self.config.topdir)) remote_outchannel = channel.receive() def write(s): out._file.write(s) --- a/testing/pytest/test_config.py +++ b/testing/pytest/test_config.py @@ -16,7 +16,6 @@ class TestConfigCmdlineParsing: help='t value'), ) """) - testdir.chdir() config = testdir.reparseconfig(['-G', '17']) assert config.option.gdest == 17 @@ -46,19 +45,6 @@ class TestConfigCmdlineParsing: config = testdir.parseconfig() assert config.option.verbose - def test_config_cmdline_options_only_lowercase(self, testdir): - testdir.makepyfile(conftest=""" - import py - Option = py.test.config.Option - options = py.test.config.addoptions("testing group", - Option('-g', '--glong', action="store", default=42, - type="int", dest="gdest", help="g value."), - ) - """) - py.test.raises(ValueError, """ - testdir.reparseconfig(['-g', '17']) - """) - def test_parsing_again_fails(self, testdir): config = testdir.reparseconfig([testdir.tmpdir]) py.test.raises(AssertionError, "config.parse([])") From commits-noreply at bitbucket.org Tue Dec 29 22:26:27 2009 From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org) Date: Tue, 29 Dec 2009 21:26:27 +0000 (UTC) Subject: [py-svn] py-trunk commit 24e0354bb484: some debug info aimed at helping to find out about a randomly failing test_export Message-ID: <20091229212628.00C9B83863@bitbucket.org> # HG changeset patch -- Bitbucket.org # Project py-trunk # URL http://bitbucket.org/hpk42/py-trunk/overview/ # User holger krekel # Date 1262117897 -3600 # Node ID 24e0354bb484e937ce4fa51deef24a18d258606f # Parent 717f09c82287ac415d60579e45c6cafc5c99875d some debug info aimed at helping to find out about a randomly failing test_export test setup issue --- a/testing/path/conftest.py +++ b/testing/path/conftest.py @@ -1,4 +1,5 @@ import py +import sys from py.impl.path import svnwc as svncommon svnbin = py.path.local.sysfind('svn') @@ -9,14 +10,14 @@ def pytest_funcarg__repowc1(request): if svnbin is None: py.test.skip("svn binary not found") - modname = request.module.__name__ tmpdir = request.getfuncargvalue("tmpdir") repo, repourl, wc = request.cached_setup( - setup=lambda: getrepowc(tmpdir, "repo-"+modname, "wc-" + modname), + setup=lambda: getrepowc(tmpdir, "path1repo", "path1wc"), scope="module", ) for x in ('test_remove', 'test_move', 'test_status_deleted'): if request.function.__name__.startswith(x): + print >>sys.stderr, ("saving repo", repo, "for", request.function) _savedrepowc = save_repowc(repo, wc) request.addfinalizer(lambda: restore_repowc(_savedrepowc)) return repo, repourl, wc @@ -66,6 +67,7 @@ def save_repowc(repo, wc): def restore_repowc(obj): savedrepo, savedwc = obj + print >>sys.stderr, ("restoring", savedrepo) repo = savedrepo.new(basename=savedrepo.basename[:-2]) assert repo.check() wc = savedwc.new(basename=savedwc.basename[:-2]) From commits-noreply at bitbucket.org Tue Dec 29 22:26:30 2009 From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org) Date: Tue, 29 Dec 2009 21:26:30 +0000 (UTC) Subject: [py-svn] py-trunk commit 42bec69042c5: streamline some tests and overall reduce py.test.ensuretemp usage, note down issue about deprecation . Message-ID: <20091229212630.00A0E83868@bitbucket.org> # HG changeset patch -- Bitbucket.org # Project py-trunk # URL http://bitbucket.org/hpk42/py-trunk/overview/ # User holger krekel # Date 1262121963 -3600 # Node ID 42bec69042c5559dfd8689bf116ac67f87f1e068 # Parent 24e0354bb484e937ce4fa51deef24a18d258606f streamline some tests and overall reduce py.test.ensuretemp usage, note down issue about deprecation . --- a/ISSUES.txt +++ b/ISSUES.txt @@ -46,14 +46,6 @@ The test-report header should optionally about the under-test package and versions/locations of involved packages. -install py.test with interpreter-specific prefixes --------------------------------------------------------- -tags: feature - -When installing under python3, jython or pypy-c it is -desirable to (additionally?) install py.test with -respective suffixes. - make node._checkcollectable more robust ------------------------------------------------- tags: bug 1.1.2 @@ -101,3 +93,12 @@ A local test run of a "tests" directory but a remote one fail because the tests directory does not contain an "__init__.py". Either give an error or make it work without the __init__.py + +deprecate ensuretemp / introduce funcargs to setup method +-------------------------------------------------------------- +tags: wish 1.1.2 + +The remaining uses of py.test.ensuretemp within the py-test base +itself are for setup methods. Also users have expressed the +wish to have funcargs available to setup functions. Experiment +with allowing funcargs there and finalizing deprecating py.test.ensuretemp. --- a/testing/log/test_log.py +++ b/testing/log/test_log.py @@ -6,7 +6,6 @@ from py.impl.log.log import default_keyw callcapture = py.io.StdCapture.call def setup_module(mod): - mod.tempdir = py.test.ensuretemp("py.log-test") mod._oldstate = default_keywordmapper.getstate() def teardown_module(mod): @@ -116,8 +115,8 @@ class TestLogConsumer: assert not err assert out.strip() == '[xyz] hello' - def test_log_file(self): - customlog = tempdir.join('log.out') + def test_log_file(self, tmpdir): + customlog = tmpdir.join('log.out') 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'] @@ -127,8 +126,8 @@ class TestLogConsumer: res = customlog.readlines() assert res == ['[default] hello world #2\n'] # no append by default! - def test_log_file_append_mode(self): - logfilefn = tempdir.join('log_append.out') + def test_log_file_append_mode(self, tmpdir): + logfilefn = tmpdir.join('log_append.out') # The append mode is on by default, so we don't need to specify it for File py.log.setconsumer("default", py.log.Path(logfilefn, append=True, @@ -144,8 +143,8 @@ class TestLogConsumer: assert lines == ['[default] hello world #1\n', '[default] hello world #1\n'] - def test_log_file_delayed_create(self): - logfilefn = tempdir.join('log_create.out') + def test_log_file_delayed_create(self, tmpdir): + logfilefn = tmpdir.join('log_create.out') py.log.setconsumer("default", py.log.Path(logfilefn, delayed_create=True, buffering=0)) @@ -154,11 +153,11 @@ class TestLogConsumer: lines = logfilefn.readlines() assert lines == ['[default] hello world #1\n'] - def test_keyword_based_log_files(self): + def test_keyword_based_log_files(self, tmpdir): logfiles = [] keywords = 'k1 k2 k3'.split() for key in keywords: - path = tempdir.join(key) + path = tmpdir.join(key) py.log.setconsumer(key, py.log.Path(path, buffering=0)) py.log.Producer('k1')('1') @@ -166,7 +165,7 @@ class TestLogConsumer: py.log.Producer('k3')('3') for key in keywords: - path = tempdir.join(key) + path = tmpdir.join(key) assert path.read().strip() == '[%s] %s' % (key, key[-1]) # disabled for now; the syslog log file can usually be read only by root --- a/testing/pytest/test_config.py +++ b/testing/pytest/test_config.py @@ -248,8 +248,8 @@ def test_options_on_small_file_do_not_bl ['--traceconfig'], ['-v'], ['-v', '-v']): runfiletest(opts + [path]) -def test_ensuretemp(): - # XXX test for deprecation +def test_ensuretemp(recwarn): + #py.test.deprecated_call(py.test.ensuretemp, 'hello') d1 = py.test.ensuretemp('hello') d2 = py.test.ensuretemp('hello') assert d1 == d2 --- a/CHANGELOG +++ b/CHANGELOG @@ -7,6 +7,8 @@ Changes between 1.X and 1.1.1 - install 'py.test' and `py.which` with a ``-$VERSION`` suffix to disambiguate between Python3, python2.X, Jython and PyPy installed versions. +- new "pytestconfig" funcarg allows access to test config object + - make py.test.* helpers provided by default plugins visible early - works transparently both for pydoc and for interactive sessions which will regularly see e.g. py.test.mark and py.test.importorskip. --- a/testing/process/test_killproc.py +++ b/testing/process/test_killproc.py @@ -1,10 +1,9 @@ import py, sys -def test_kill(): +def test_kill(tmpdir): subprocess = py.test.importorskip("subprocess") - tmp = py.test.ensuretemp("test_kill") - t = tmp.join("t.py") + t = tmpdir.join("t.py") t.write("import time ; time.sleep(100)") proc = py.std.subprocess.Popen([sys.executable, str(t)]) assert proc.poll() is None # no return value yet --- a/testing/code/test_source.py +++ b/testing/code/test_source.py @@ -310,10 +310,9 @@ def test_deindent(): assert lines == ['', 'def f():', ' def g():', ' pass', ' '] @py.test.mark.xfail -def test_source_of_class_at_eof_without_newline(): +def test_source_of_class_at_eof_without_newline(tmpdir): # this test fails because the implicit inspect.getsource(A) below # does not return the "x = 1" last line. - tmpdir = py.test.ensuretemp("source_write_read") source = py.code.Source(''' class A(object): def method(self): --- a/testing/cmdline/test_cmdline.py +++ b/testing/cmdline/test_cmdline.py @@ -1,6 +1,17 @@ +import sys, py pytest_plugins = "pytest_pytester" + at py.test.mark.multi(name=[x for x in dir(py.cmdline) if x[0] != "_"]) +def test_cmdmain(name): + main = getattr(py.cmdline, name) + assert py.builtin.callable(main) + assert name[:2] == "py" + scriptname = "py." + name[2:] + if sys.platform == "win32": + scriptname += ".exe" + assert py.path.local.sysfind(scriptname), scriptname + class TestPyLookup: def test_basic(self, testdir): p = testdir.makepyfile(hello="def x(): pass") --- a/testing/cmdline/test_generic.py +++ /dev/null @@ -1,66 +0,0 @@ -import py -import sys - - -def setup_module(mod): - mod.binpath = py._impldir.dirpath('bin') - if not mod.binpath.check(): - py.test.skip("bin-source scripts not installed") - mod.binwinpath = binpath.join("win32") - mod.tmpdir = py.test.ensuretemp(__name__) - mod.iswin32 = sys.platform == "win32" - -def checkmain(name): - main = getattr(py.cmdline, name) - assert py.builtin.callable(main) - assert name[:2] == "py" - scriptname = "py." + name[2:] - assert binpath.join(scriptname).check() - assert binwinpath.join(scriptname + ".cmd").check() - -def checkprocess(script): - assert script.check() - old = tmpdir.ensure(script.basename, dir=1).chdir() - try: - if iswin32: - cmd = script.basename - else: - cmd = "%s" %(script, ) - # XXX distributed testing's rsync does not support - # syncing executable bits - script.chmod(int("777", 8)) - - if script.basename.startswith("py.lookup") or \ - script.basename.startswith("py.which"): - cmd += " sys" - py.builtin.print_("executing", script) - try: - old = script.dirpath().chdir() - try: - py.process.cmdexec(cmd) - finally: - old.chdir() - except py.process.cmdexec.Error: - e = sys.exc_info()[1] - if cmd.find("py.rest") != -1 and \ - e.out.find("module named") != -1: - return - raise - - finally: - old.chdir() - -def test_cmdline_namespace(): - for name in dir(py.cmdline): - if name[0] != "_": - yield checkmain, name - -def test_script_invocation(): - if iswin32: - scripts = binwinpath.listdir("py.*") - else: - scripts = binpath.listdir("py.*") - scripts = [x for x in scripts - if not x.basename.startswith("py.svnwcrevert")] - for script in scripts: - yield checkprocess, script --- a/testing/pytest/acceptance_test.py +++ b/testing/pytest/acceptance_test.py @@ -32,12 +32,12 @@ class TestGeneralUsage: mytemp = testdir.tmpdir.mkdir("mytemp") p = testdir.makepyfile(""" import py - def test_1(): - py.test.ensuretemp('xyz') + def test_1(pytestconfig): + pytestconfig.getbasetemp().ensure("hello") """) result = testdir.runpytest(p, '--basetemp=%s' %mytemp) assert result.ret == 0 - assert mytemp.join('xyz').check(dir=1) + assert mytemp.join('hello').check() def test_assertion_magic(self, testdir): p = testdir.makepyfile(""" --- a/py/plugin/pytest_default.py +++ b/py/plugin/pytest_default.py @@ -28,6 +28,9 @@ def pytest_collect_file(path, parent): if ext == ".py": return parent.Module(path, parent=parent) +def pytest_funcarg__pytestconfig(request): + return request.config + def pytest_collect_directory(path, parent): # XXX reconsider the following comment # not use parent.Directory here as we generally --- a/testing/pytest/test_conftesthandle.py +++ b/testing/pytest/test_conftesthandle.py @@ -8,7 +8,7 @@ def pytest_generate_tests(metafunc): def pytest_funcarg__basedir(request): def basedirmaker(request): - basedir = d = request.config.ensuretemp(request.param) + basedir = d = request.getfuncargvalue("tmpdir") d.ensure("adir/conftest.py").write("a=1 ; Directory = 3") d.ensure("adir/b/conftest.py").write("b=2 ; a = 1.5") if request.param == "inpackage": --- a/py/impl/test/config.py +++ b/py/impl/test/config.py @@ -10,6 +10,7 @@ def ensuretemp(string, dir=1): take care to provide empty unique directories for each test call even if the test is called multiple times. """ + #py.log._apiwarn(">1.1", "use tmpdir function argument") return py.test.config.ensuretemp(string, dir=dir) class CmdOptions(object): --- a/testing/code/test_excinfo.py +++ b/testing/code/test_excinfo.py @@ -256,16 +256,15 @@ def test_codepath_Queue_example(): assert path.check() class TestFormattedExcinfo: - def setup_method(self, method): - self.tmpdir = py.test.ensuretemp("%s_%s" %( - self.__class__.__name__, method.__name__)) - - def importasmod(self, source): - source = py.code.Source(source) - modpath = self.tmpdir.join("mod.py") - self.tmpdir.ensure("__init__.py") - modpath.write(source) - return modpath.pyimport() + def pytest_funcarg__importasmod(self, request): + def importasmod(source): + source = py.code.Source(source) + tmpdir = request.getfuncargvalue("tmpdir") + modpath = tmpdir.join("mod.py") + tmpdir.ensure("__init__.py") + modpath.write(source) + return modpath.pyimport() + return importasmod def excinfo_from_exec(self, source): source = py.code.Source(source).strip() @@ -392,8 +391,8 @@ raise ValueError() assert reprlocals.lines[2] == 'y = 5' assert reprlocals.lines[3] == 'z = 7' - def test_repr_tracebackentry_lines(self): - mod = self.importasmod(""" + def test_repr_tracebackentry_lines(self, importasmod): + mod = importasmod(""" def func1(): raise ValueError("hello\\nworld") """) @@ -423,8 +422,8 @@ raise ValueError() assert loc.lineno == 3 #assert loc.message == "ValueError: hello" - def test_repr_tracebackentry_lines(self): - mod = self.importasmod(""" + def test_repr_tracebackentry_lines(self, importasmod): + mod = importasmod(""" def func1(m, x, y, z): raise ValueError("hello\\nworld") """) @@ -447,8 +446,8 @@ raise ValueError() assert tw.lines[1] == "x = 5, y = 13" assert tw.lines[2] == "z = " + repr('z' * 120) - def test_repr_tracebackentry_short(self): - mod = self.importasmod(""" + def test_repr_tracebackentry_short(self, importasmod): + mod = importasmod(""" def func1(): raise ValueError("hello") def entry(): @@ -470,8 +469,8 @@ raise ValueError() assert lines[1] == ' raise ValueError("hello")' assert lines[2] == 'E ValueError: hello' - def test_repr_tracebackentry_no(self): - mod = self.importasmod(""" + def test_repr_tracebackentry_no(self, importasmod): + mod = importasmod(""" def func1(): raise ValueError("hello") def entry(): @@ -487,8 +486,8 @@ raise ValueError() assert lines[0] == 'E ValueError: hello' assert not lines[1:] - def test_repr_traceback_tbfilter(self): - mod = self.importasmod(""" + def test_repr_traceback_tbfilter(self, importasmod): + mod = importasmod(""" def f(x): raise ValueError(x) def entry(): @@ -502,8 +501,8 @@ raise ValueError() reprtb = p.repr_traceback(excinfo) assert len(reprtb.reprentries) == 3 - def test_repr_traceback_and_excinfo(self): - mod = self.importasmod(""" + def test_repr_traceback_and_excinfo(self, importasmod): + mod = importasmod(""" def f(x): raise ValueError(x) def entry(): @@ -523,8 +522,8 @@ raise ValueError() assert repr.reprcrash.path.endswith("mod.py") assert repr.reprcrash.message == "ValueError: 0" - def test_repr_excinfo_addouterr(self): - mod = self.importasmod(""" + def test_repr_excinfo_addouterr(self, importasmod): + mod = importasmod(""" def entry(): raise ValueError() """) @@ -536,8 +535,8 @@ raise ValueError() assert twmock.lines[-1] == "content" assert twmock.lines[-2] == ("-", "title") - def test_repr_excinfo_reprcrash(self): - mod = self.importasmod(""" + def test_repr_excinfo_reprcrash(self, importasmod): + mod = importasmod(""" def entry(): raise ValueError() """) @@ -548,8 +547,8 @@ raise ValueError() assert repr.reprcrash.message == "ValueError" assert str(repr.reprcrash).endswith("mod.py:3: ValueError") - def test_repr_traceback_recursion(self): - mod = self.importasmod(""" + def test_repr_traceback_recursion(self, importasmod): + mod = importasmod(""" def rec2(x): return rec1(x+1) def rec1(x): @@ -565,12 +564,12 @@ raise ValueError() assert reprtb.extraline == "!!! Recursion detected (same locals & position)" assert str(reprtb) - def test_tb_entry_AssertionError(self): + def test_tb_entry_AssertionError(self, importasmod): # probably this test is a bit redundant # as py/magic/testing/test_assertion.py # already tests correctness of # assertion-reinterpretation logic - mod = self.importasmod(""" + mod = importasmod(""" def somefunc(): x = 1 assert x == 2 @@ -586,8 +585,8 @@ raise ValueError() lines = reprentry.lines assert lines[-1] == "E assert 1 == 2" - def test_reprexcinfo_getrepr(self): - mod = self.importasmod(""" + def test_reprexcinfo_getrepr(self, importasmod): + mod = importasmod(""" def f(x): raise ValueError(x) def entry(): @@ -601,8 +600,8 @@ raise ValueError() assert isinstance(repr, ReprExceptionInfo) assert repr.reprtraceback.style == style - def test_toterminal_long(self): - mod = self.importasmod(""" + def test_toterminal_long(self, importasmod): + mod = importasmod(""" def g(x): raise ValueError(x) def f(): @@ -627,8 +626,8 @@ raise ValueError() assert tw.lines[9] == "" assert tw.lines[10].endswith("mod.py:3: ValueError") - def test_toterminal_long_filenames(self): - mod = self.importasmod(""" + def test_toterminal_long_filenames(self, importasmod): + mod = importasmod(""" def f(): raise ValueError() """) @@ -651,31 +650,22 @@ raise ValueError() finally: old.chdir() - def test_format_excinfo(self): - mod = self.importasmod(""" + @py.test.mark.multi(reproptions=[ + {'style': style, 'showlocals': showlocals, + 'funcargs': funcargs, 'tbfilter': tbfilter + } for style in ("long", "short", "no") + for showlocals in (True, False) + for tbfilter in (True, False) + for funcargs in (True, False)]) + def test_format_excinfo(self, importasmod, reproptions): + mod = importasmod(""" def g(x): raise ValueError(x) def f(): g(3) """) excinfo = py.test.raises(ValueError, mod.f) - def format_and_str(kw): - tw = py.io.TerminalWriter(stringio=True) - repr = excinfo.getrepr(**kw) - repr.toterminal(tw) - assert tw.stringio.getvalue() - - for combo in self.allcombos(): - yield format_and_str, combo - - def allcombos(self): - for style in ("long", "short", "no"): - for showlocals in (True, False): - for tbfilter in (True, False): - for funcargs in (True, False): - kw = {'style': style, - 'showlocals': showlocals, - 'funcargs': funcargs, - 'tbfilter': tbfilter - } - yield kw + tw = py.io.TerminalWriter(stringio=True) + repr = excinfo.getrepr(**reproptions) + repr.toterminal(tw) + assert tw.stringio.getvalue() From commits-noreply at bitbucket.org Wed Dec 30 10:42:44 2009 From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org) Date: Wed, 30 Dec 2009 09:42:44 +0000 (UTC) Subject: [py-svn] py-trunk commit 2f5788d1819f: only consider matching conftest plugins for discovering hooks related to collection nodes. Message-ID: <20091230094244.4C5257EF0E@bitbucket.org> # HG changeset patch -- Bitbucket.org # Project py-trunk # URL http://bitbucket.org/hpk42/py-trunk/overview/ # User holger krekel # Date 1262137018 -3600 # Node ID 2f5788d1819f792a110fa646a51c70e6326e9390 # Parent 4b97998e25d97dd9441dd2fed22d8ae835fb45bb only consider matching conftest plugins for discovering hooks related to collection nodes. --- a/py/impl/test/dist/dsession.py +++ b/py/impl/test/dist/dsession.py @@ -223,7 +223,7 @@ class DSession(Session): nodes = self.item2nodes.setdefault(item, []) assert node not in nodes nodes.append(node) - self.config.hook.pytest_itemstart(item=item, node=node) + item.ihook.pytest_itemstart(item=item, node=node) tosend[:] = tosend[room:] # update inplace if tosend: # we have some left, give it to the main loop @@ -242,7 +242,7 @@ class DSession(Session): # "sending same item %r to multiple " # "not implemented" %(item,)) self.item2nodes.setdefault(item, []).append(node) - self.config.hook.pytest_itemstart(item=item, node=node) + item.ihook.pytest_itemstart(item=item, node=node) pending.extend(sending) tosend[:] = tosend[room:] # update inplace if not tosend: @@ -267,7 +267,7 @@ class DSession(Session): info = "!!! Node %r crashed during running of test %r" %(node, item) rep = runner.ItemTestReport(item=item, excinfo=info, when="???") rep.node = node - self.config.hook.pytest_runtest_logreport(report=rep) + item.ihook.pytest_runtest_logreport(report=rep) def setup(self): """ setup any neccessary resources ahead of the test run. """ --- a/CHANGELOG +++ b/CHANGELOG @@ -9,6 +9,10 @@ Changes between 1.X and 1.1.1 - new "pytestconfig" funcarg allows access to test config object +- collection/item node specific runtest/collect hooks are only called exactly + on matching conftest.py files, i.e. ones which are exactly below + the filesystem path of an item + - robustify capturing to survive if custom pytest_runtest_setup code failed and prevented the capturing setup code from running. --- a/testing/pytest/test_pycollect.py +++ b/testing/pytest/test_pycollect.py @@ -461,3 +461,49 @@ class TestReportinfo: def test_method(self): pass """ + +def test_setup_only_available_in_subdir(testdir): + sub1 = testdir.mkpydir("sub1") + sub2 = testdir.mkpydir("sub2") + sub1.join("conftest.py").write(py.code.Source(""" + import py + def pytest_runtest_setup(item): + assert item.fspath.purebasename == "test_in_sub1" + def pytest_runtest_call(item): + assert item.fspath.purebasename == "test_in_sub1" + def pytest_runtest_teardown(item): + assert item.fspath.purebasename == "test_in_sub1" + """)) + sub2.join("conftest.py").write(py.code.Source(""" + import py + def pytest_runtest_setup(item): + assert item.fspath.purebasename == "test_in_sub2" + def pytest_runtest_call(item): + assert item.fspath.purebasename == "test_in_sub2" + def pytest_runtest_teardown(item): + assert item.fspath.purebasename == "test_in_sub2" + """)) + sub1.join("test_in_sub1.py").write("def test_1(): pass") + sub2.join("test_in_sub2.py").write("def test_2(): pass") + result = testdir.runpytest("-v", "-s") + result.stdout.fnmatch_lines([ + "*2 passed*" + ]) + +def test_generate_tests_only_done_in_subdir(testdir): + sub1 = testdir.mkpydir("sub1") + sub2 = testdir.mkpydir("sub2") + sub1.join("conftest.py").write(py.code.Source(""" + def pytest_generate_tests(metafunc): + assert metafunc.function.__name__ == "test_1" + """)) + sub2.join("conftest.py").write(py.code.Source(""" + def pytest_generate_tests(metafunc): + assert metafunc.function.__name__ == "test_2" + """)) + sub1.join("test_in_sub1.py").write("def test_1(): pass") + sub2.join("test_in_sub2.py").write("def test_2(): pass") + result = testdir.runpytest("-v", "-s", sub1, sub2, sub1) + result.stdout.fnmatch_lines([ + "*3 passed*" + ]) --- a/py/impl/test/pluginmanager.py +++ b/py/impl/test/pluginmanager.py @@ -136,8 +136,8 @@ class PluginManager(object): # API for interacting with registered and instantiated plugin objects # # - def listattr(self, attrname, plugins=None, extra=()): - return self.registry.listattr(attrname, plugins=plugins, extra=extra) + def listattr(self, attrname, plugins=None): + return self.registry.listattr(attrname, plugins=plugins) def notify_exception(self, excinfo=None): if excinfo is None: @@ -271,12 +271,11 @@ class Registry: def __iter__(self): return iter(self._plugins) - def listattr(self, attrname, plugins=None, extra=(), reverse=False): + def listattr(self, attrname, plugins=None, reverse=False): l = [] if plugins is None: plugins = self._plugins - candidates = list(plugins) + list(extra) - for plugin in candidates: + for plugin in plugins: try: l.append(getattr(plugin, attrname)) except AttributeError: @@ -291,32 +290,29 @@ class HookRelay: self._registry = registry for name, method in vars(hookspecs).items(): if name[:1] != "_": - setattr(self, name, self._makecall(name)) - - def _makecall(self, name, extralookup=None): - hookspecmethod = getattr(self._hookspecs, name) - firstresult = getattr(hookspecmethod, 'firstresult', False) - return HookCaller(self, name, firstresult=firstresult, - extralookup=extralookup) - - def _getmethods(self, name, extralookup=()): - return self._registry.listattr(name, extra=extralookup) + firstresult = getattr(method, 'firstresult', False) + hc = HookCaller(self, name, firstresult=firstresult) + setattr(self, name, hc) def _performcall(self, name, multicall): return multicall.execute() class HookCaller: - def __init__(self, hookrelay, name, firstresult, extralookup=None): + def __init__(self, hookrelay, name, firstresult): self.hookrelay = hookrelay self.name = name self.firstresult = firstresult - self.extralookup = extralookup and [extralookup] or () def __repr__(self): return "" %(self.name,) def __call__(self, **kwargs): - methods = self.hookrelay._getmethods(self.name, self.extralookup) + methods = self.hookrelay._registry.listattr(self.name) + mc = MultiCall(methods, kwargs, firstresult=self.firstresult) + return self.hookrelay._performcall(self.name, mc) + + def pcall(self, plugins, **kwargs): + methods = self.hookrelay._registry.listattr(self.name, plugins=plugins) mc = MultiCall(methods, kwargs, firstresult=self.firstresult) return self.hookrelay._performcall(self.name, mc) --- a/py/plugin/pytest_runner.py +++ b/py/plugin/pytest_runner.py @@ -39,7 +39,7 @@ def pytest_runtest_protocol(item): if item.config.getvalue("boxed"): reports = forked_run_report(item) for rep in reports: - item.config.hook.pytest_runtest_logreport(report=rep) + item.ihook.pytest_runtest_logreport(report=rep) else: runtestprotocol(item) return True @@ -85,7 +85,7 @@ def pytest_report_teststatus(report): def call_and_report(item, when, log=True): call = call_runtest_hook(item, when) - hook = item.config.hook + hook = item.ihook report = hook.pytest_runtest_makereport(item=item, call=call) if log and (when == "call" or not report.passed): hook.pytest_runtest_logreport(report=report) @@ -93,8 +93,8 @@ def call_and_report(item, when, log=True def call_runtest_hook(item, when): hookname = "pytest_runtest_" + when - hook = getattr(item.config.hook, hookname) - return CallInfo(lambda: hook(item=item), when=when) + ihook = getattr(item.ihook, hookname) + return CallInfo(lambda: ihook(item=item), when=when) class CallInfo: excinfo = None --- a/py/impl/test/conftesthandle.py +++ b/py/impl/test/conftesthandle.py @@ -11,6 +11,7 @@ class Conftest(object): def __init__(self, onimport=None): self._path2confmods = {} self._onimport = onimport + self._conftestpath2mod = {} def setinitial(self, args): """ try to find a first anchor path for looking up global values @@ -65,17 +66,20 @@ class Conftest(object): raise KeyError(name) def importconftest(self, conftestpath): - # Using caching here looks redundant since ultimately - # sys.modules caches already assert conftestpath.check(), conftestpath - if not conftestpath.dirpath('__init__.py').check(file=1): - # HACK: we don't want any "globally" imported conftest.py, - # prone to conflicts and subtle problems - modname = str(conftestpath).replace('.', conftestpath.sep) - mod = conftestpath.pyimport(modname=modname) - else: - mod = conftestpath.pyimport() - return self._postimport(mod) + try: + return self._conftestpath2mod[conftestpath] + except KeyError: + if not conftestpath.dirpath('__init__.py').check(file=1): + # HACK: we don't want any "globally" imported conftest.py, + # prone to conflicts and subtle problems + modname = str(conftestpath).replace('.', conftestpath.sep) + mod = conftestpath.pyimport(modname=modname) + else: + mod = conftestpath.pyimport() + self._postimport(mod) + self._conftestpath2mod[conftestpath] = mod + return mod def _postimport(self, mod): if self._onimport: --- a/py/impl/test/collect.py +++ b/py/impl/test/collect.py @@ -11,6 +11,18 @@ def configproperty(name): return self.config._getcollectclass(name, self.fspath) return property(fget) +class HookProxy: + def __init__(self, node): + self.node = node + def __getattr__(self, name): + if name[0] == "_": + raise AttributeError(name) + hookmethod = getattr(self.node.config.hook, name) + def call_matching_hooks(**kwargs): + plugins = self.node.config.getmatchingplugins(self.node.fspath) + return hookmethod.pcall(plugins, **kwargs) + return call_matching_hooks + class Node(object): """ base class for Nodes in the collection tree. Collector nodes have children and @@ -29,6 +41,7 @@ class Node(object): self.parent = parent self.config = getattr(parent, 'config', None) self.fspath = getattr(parent, 'fspath', None) + self.ihook = HookProxy(self) def _checkcollectable(self): if not hasattr(self, 'fspath'): @@ -426,13 +439,12 @@ class Directory(FSCollector): return res def consider_file(self, path): - return self.config.hook.pytest_collect_file(path=path, parent=self) + return self.ihook.pytest_collect_file(path=path, parent=self) def consider_dir(self, path, usefilters=None): if usefilters is not None: py.log._apiwarn("0.99", "usefilters argument not needed") - return self.config.hook.pytest_collect_directory( - path=path, parent=self) + return self.ihook.pytest_collect_directory(path=path, parent=self) class Item(Node): """ a basic test item. """ --- a/testing/pytest/test_pluginmanager.py +++ b/testing/pytest/test_pluginmanager.py @@ -395,12 +395,6 @@ class TestRegistry: l = list(plugins.listattr('x', reverse=True)) assert l == [43, 42, 41] - class api4: - x = 44 - l = list(plugins.listattr('x', extra=(api4,))) - assert l == [41,42,43,44] - assert len(list(plugins)) == 3 # otherwise extra added - class TestHookRelay: def test_happypath(self): registry = Registry() @@ -441,23 +435,3 @@ class TestHookRelay: res = mcm.hello(arg=3) assert res == 4 - def test_hooks_extra_plugins(self): - registry = Registry() - class Api: - def hello(self, arg): - pass - hookrelay = HookRelay(hookspecs=Api, registry=registry) - hook_hello = hookrelay.hello - class Plugin: - def hello(self, arg): - return arg + 1 - registry.register(Plugin()) - class Plugin2: - def hello(self, arg): - return arg + 2 - newhook = hookrelay._makecall("hello", extralookup=Plugin2()) - l = newhook(arg=3) - assert l == [5, 4] - l2 = hook_hello(arg=3) - assert l2 == [4] - --- a/py/impl/test/pycollect.py +++ b/py/impl/test/pycollect.py @@ -120,7 +120,7 @@ class PyCollectorMixin(PyobjMixin, py.te return self.join(name) def makeitem(self, name, obj): - return self.config.hook.pytest_pycollect_makeitem( + return self.ihook.pytest_pycollect_makeitem( collector=self, name=name, obj=obj) def _istestclasscandidate(self, name, obj): @@ -137,9 +137,9 @@ class PyCollectorMixin(PyobjMixin, py.te cls = clscol and clscol.obj or None metafunc = funcargs.Metafunc(funcobj, config=self.config, cls=cls, module=module) - gentesthook = self.config.hook._makecall( - "pytest_generate_tests", extralookup=module) - gentesthook(metafunc=metafunc) + gentesthook = self.config.hook.pytest_generate_tests + plugins = self.config.getmatchingplugins(self.fspath) + [module] + gentesthook.pcall(plugins, metafunc=metafunc) if not metafunc._calls: return self.Function(name, parent=self) return funcargs.FunctionCollector(name=name, @@ -338,7 +338,7 @@ class Function(FunctionMixin, py.test.co def runtest(self): """ execute the underlying test function. """ - self.config.hook.pytest_pyfunc_call(pyfuncitem=self) + self.ihook.pytest_pyfunc_call(pyfuncitem=self) def setup(self): super(Function, self).setup() --- a/py/impl/test/config.py +++ b/py/impl/test/config.py @@ -45,6 +45,13 @@ class Config(object): self.trace("loaded conftestmodule %r" %(conftestmodule,)) self.pluginmanager.consider_conftest(conftestmodule) + def getmatchingplugins(self, fspath): + conftests = self._conftest._conftestpath2mod.values() + plugins = [x for x in self.pluginmanager.getplugins() + if x not in conftests] + plugins += self._conftest.getconftestmodules(fspath) + return plugins + def trace(self, msg): if getattr(self.option, 'traceconfig', None): self.hook.pytest_trace(category="config", msg=msg) --- a/py/impl/test/funcargs.py +++ b/py/impl/test/funcargs.py @@ -93,7 +93,7 @@ class FuncargRequest: self.fspath = pyfuncitem.fspath if hasattr(pyfuncitem, '_requestparam'): self.param = pyfuncitem._requestparam - self._plugins = self.config.pluginmanager.getplugins() + self._plugins = self.config.getmatchingplugins(self.fspath) self._plugins.append(self.module) if self.instance is not None: self._plugins.append(self.instance) --- a/testing/pytest/test_funcargs.py +++ b/testing/pytest/test_funcargs.py @@ -482,3 +482,25 @@ class TestGenfuncFunctional: "*test_myfunc*world*FAIL*", "*1 failed, 1 passed*" ]) + + +def test_conftest_funcargs_only_available_in_subdir(testdir): + sub1 = testdir.mkpydir("sub1") + sub2 = testdir.mkpydir("sub2") + sub1.join("conftest.py").write(py.code.Source(""" + import py + def pytest_funcarg__arg1(request): + py.test.raises(Exception, "request.getfuncargvalue('arg2')") + """)) + sub2.join("conftest.py").write(py.code.Source(""" + import py + def pytest_funcarg__arg2(request): + py.test.raises(Exception, "request.getfuncargvalue('arg1')") + """)) + + sub1.join("test_in_sub1.py").write("def test_1(arg1): pass") + sub2.join("test_in_sub2.py").write("def test_2(arg2): pass") + result = testdir.runpytest("-v") + result.stdout.fnmatch_lines([ + "*2 passed*" + ]) From commits-noreply at bitbucket.org Wed Dec 30 10:42:42 2009 From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org) Date: Wed, 30 Dec 2009 09:42:42 +0000 (UTC) Subject: [py-svn] py-trunk commit 4b97998e25d9: fix capturing to be more careful during teardown when a setup never happened (due to e.g. an error in user-provided runtest_setup code) Message-ID: <20091230094242.A8DAA7EF0D@bitbucket.org> # HG changeset patch -- Bitbucket.org # Project py-trunk # URL http://bitbucket.org/hpk42/py-trunk/overview/ # User holger krekel # Date 1262128287 -3600 # Node ID 4b97998e25d97dd9441dd2fed22d8ae835fb45bb # Parent 42bec69042c5559dfd8689bf116ac67f87f1e068 fix capturing to be more careful during teardown when a setup never happened (due to e.g. an error in user-provided runtest_setup code) --- a/testing/plugin/test_pytest_capture.py +++ b/testing/plugin/test_pytest_capture.py @@ -379,3 +379,15 @@ class TestCaptureFuncarg: ]) assert result.ret == 2 +def test_setup_failure_does_not_kill_capturing(testdir): + sub1 = testdir.mkpydir("sub1") + sub1.join("conftest.py").write(py.code.Source(""" + def pytest_runtest_setup(item): + raise ValueError(42) + """)) + sub1.join("test_mod.py").write("def test_func1(): pass") + result = testdir.runpytest(testdir.tmpdir, '--traceconfig') + result.stdout.fnmatch_lines([ + "*ValueError(42)*", + "*1 error*" + ]) --- a/py/plugin/pytest_capture.py +++ b/py/plugin/pytest_capture.py @@ -161,16 +161,20 @@ class CaptureManager: cap.resume() self._capturing = method - def suspendcapture(self): + def suspendcapture(self, item=None): self.deactivate_funcargs() - method = self._capturing - if method != "no": - cap = self._method2capture[method] - outerr = cap.suspend() - else: - outerr = "", "" - del self._capturing - return outerr + if hasattr(self, '_capturing'): + method = self._capturing + if method != "no": + cap = self._method2capture[method] + outerr = cap.suspend() + else: + outerr = "", "" + del self._capturing + if item: + outerr = (item.outerr[0] + outerr[0], item.outerr[1] + outerr[1]) + return outerr + return "", "" def activate_funcargs(self, pyfuncitem): if not hasattr(pyfuncitem, 'funcargs'): @@ -210,9 +214,6 @@ class CaptureManager: def pytest_runtest_teardown(self, item): self.resumecapture_item(item) - def pytest_runtest_teardown(self, item): - self.resumecapture_item(item) - def pytest__teardown_final(self, __multicall__, session): method = self._getmethod(session.config, None) self.resumecapture(method) @@ -231,8 +232,7 @@ class CaptureManager: def pytest_runtest_makereport(self, __multicall__, item, call): self.deactivate_funcargs() rep = __multicall__.execute() - outerr = self.suspendcapture() - outerr = (item.outerr[0] + outerr[0], item.outerr[1] + outerr[1]) + outerr = self.suspendcapture(item) if not rep.passed: addouterr(rep, outerr) if not rep.passed or rep.when == "teardown": --- a/CHANGELOG +++ b/CHANGELOG @@ -9,6 +9,9 @@ Changes between 1.X and 1.1.1 - new "pytestconfig" funcarg allows access to test config object +- robustify capturing to survive if custom pytest_runtest_setup + code failed and prevented the capturing setup code from running. + - make py.test.* helpers provided by default plugins visible early - works transparently both for pydoc and for interactive sessions which will regularly see e.g. py.test.mark and py.test.importorskip. From commits-noreply at bitbucket.org Wed Dec 30 10:42:44 2009 From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org) Date: Wed, 30 Dec 2009 09:42:44 +0000 (UTC) Subject: [py-svn] py-trunk commit e995671376bb: generalize hook calling from collection nodes but stop short Message-ID: <20091230094244.62FC883863@bitbucket.org> # HG changeset patch -- Bitbucket.org # Project py-trunk # URL http://bitbucket.org/hpk42/py-trunk/overview/ # User holger krekel # Date 1262166121 -3600 # Node ID e995671376bb37c6213cc0debeff023d85338287 # Parent 2f5788d1819f792a110fa646a51c70e6326e9390 generalize hook calling from collection nodes but stop short of allowing general hooks in python test modules. It'd be easily possible (a 1-line change) but considering it i refrained from it because the collector API is a bit too low level. pytest_generate_tests and funcarg factories have a limited directly useful interface and are thus less confusing - those are taking advantage of hook discovery in python test modules. --- a/CHANGELOG +++ b/CHANGELOG @@ -9,6 +9,8 @@ Changes between 1.X and 1.1.1 - new "pytestconfig" funcarg allows access to test config object +- allow pytest_generate_tests to be defined in classes as well + - collection/item node specific runtest/collect hooks are only called exactly on matching conftest.py files, i.e. ones which are exactly below the filesystem path of an item --- a/py/impl/test/collect.py +++ b/py/impl/test/collect.py @@ -19,7 +19,7 @@ class HookProxy: raise AttributeError(name) hookmethod = getattr(self.node.config.hook, name) def call_matching_hooks(**kwargs): - plugins = self.node.config.getmatchingplugins(self.node.fspath) + plugins = self.node._getplugins() return hookmethod.pcall(plugins, **kwargs) return call_matching_hooks @@ -43,6 +43,9 @@ class Node(object): self.fspath = getattr(parent, 'fspath', None) self.ihook = HookProxy(self) + def _getplugins(self): + return self.config._getmatchingplugins(self.fspath) + def _checkcollectable(self): if not hasattr(self, 'fspath'): self.parent._memocollect() # to reraise exception --- a/py/impl/test/pycollect.py +++ b/py/impl/test/pycollect.py @@ -34,6 +34,15 @@ class PyobjMixin(object): return property(fget, fset, None, "underlying python object") obj = obj() + def _getplugins(self, withpy=False): + plugins = self.config._getmatchingplugins(self.fspath) + if withpy: + plugins.append(self.getparent(py.test.collect.Module).obj) + inst = self.getparent(py.test.collect.Instance) + if inst is not None: + plugins.append(inst.obj) + return plugins + def _getobj(self): return getattr(self.parent.obj, self.name) @@ -138,8 +147,7 @@ class PyCollectorMixin(PyobjMixin, py.te metafunc = funcargs.Metafunc(funcobj, config=self.config, cls=cls, module=module) gentesthook = self.config.hook.pytest_generate_tests - plugins = self.config.getmatchingplugins(self.fspath) + [module] - gentesthook.pcall(plugins, metafunc=metafunc) + gentesthook.pcall(self._getplugins(withpy=True), metafunc=metafunc) if not metafunc._calls: return self.Function(name, parent=self) return funcargs.FunctionCollector(name=name, @@ -327,6 +335,7 @@ class Function(FunctionMixin, py.test.co self.funcargs = {} if callobj is not _dummy: self._obj = callobj + self.function = getattr(self.obj, 'im_func', self.obj) def _isyieldedfunction(self): return self._args is not None --- a/testing/plugin/test_pytest_runner.py +++ b/testing/plugin/test_pytest_runner.py @@ -285,3 +285,34 @@ def test_callinfo(): assert not hasattr(ci, 'result') assert ci.excinfo assert "exc" in repr(ci) + +# design question: do we want general hooks in python files? +# following passes if withpy defaults to True in pycoll.PyObjMix._getplugins() + at py.test.mark.xfail +def test_runtest_in_module_ordering(testdir): + p1 = testdir.makepyfile(""" + def pytest_runtest_setup(item): # runs after class-level! + item.function.mylist.append("module") + class TestClass: + def pytest_runtest_setup(self, item): + assert not hasattr(item.function, 'mylist') + item.function.mylist = ['class'] + def pytest_funcarg__mylist(self, request): + return request.function.mylist + def pytest_runtest_call(self, item, __multicall__): + try: + __multicall__.execute() + except ValueError: + pass + def test_hello1(self, mylist): + assert mylist == ['class', 'module'], mylist + raise ValueError() + def test_hello2(self, mylist): + assert mylist == ['class', 'module'], mylist + def pytest_runtest_teardown(item): + del item.function.mylist + """) + result = testdir.runpytest(p1) + assert result.stdout.fnmatch_lines([ + "*2 passed*" + ]) --- a/py/impl/test/config.py +++ b/py/impl/test/config.py @@ -45,7 +45,7 @@ class Config(object): self.trace("loaded conftestmodule %r" %(conftestmodule,)) self.pluginmanager.consider_conftest(conftestmodule) - def getmatchingplugins(self, fspath): + def _getmatchingplugins(self, fspath): conftests = self._conftest._conftestpath2mod.values() plugins = [x for x in self.pluginmanager.getplugins() if x not in conftests] --- a/py/impl/test/funcargs.py +++ b/py/impl/test/funcargs.py @@ -93,10 +93,7 @@ class FuncargRequest: self.fspath = pyfuncitem.fspath if hasattr(pyfuncitem, '_requestparam'): self.param = pyfuncitem._requestparam - self._plugins = self.config.getmatchingplugins(self.fspath) - self._plugins.append(self.module) - if self.instance is not None: - self._plugins.append(self.instance) + self._plugins = pyfuncitem._getplugins(withpy=True) self._funcargs = self._pyfuncitem.funcargs.copy() self._name2factory = {} self._currentarg = None --- a/testing/pytest/test_funcargs.py +++ b/testing/pytest/test_funcargs.py @@ -483,6 +483,21 @@ class TestGenfuncFunctional: "*1 failed, 1 passed*" ]) + def test_generate_tests_in_class(self, testdir): + p = testdir.makepyfile(""" + class TestClass: + def pytest_generate_tests(self, metafunc): + metafunc.addcall(funcargs={'hello': 'world'}, id="hello") + + def test_myfunc(self, hello): + assert hello == "world" + """) + result = testdir.runpytest("-v", p) + assert result.stdout.fnmatch_lines([ + "*test_myfunc*hello*PASS*", + "*1 passed*" + ]) + def test_conftest_funcargs_only_available_in_subdir(testdir): sub1 = testdir.mkpydir("sub1") From commits-noreply at bitbucket.org Wed Dec 30 11:19:45 2009 From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org) Date: Wed, 30 Dec 2009 10:19:45 +0000 (UTC) Subject: [py-svn] py-trunk commit cf5995e23461: skip tests using 'capfd' funcarg but not having os.dup. cleanup issues and regen plugin docs. Message-ID: <20091230101945.E30047EF0A@bitbucket.org> # HG changeset patch -- Bitbucket.org # Project py-trunk # URL http://bitbucket.org/hpk42/py-trunk/overview/ # User holger krekel # Date 1262168180 -3600 # Node ID cf5995e234619de5dccd1ff6757b9e63be65e3a9 # Parent e995671376bb37c6213cc0debeff023d85338287 skip tests using 'capfd' funcarg but not having os.dup. cleanup issues and regen plugin docs. --- a/ISSUES.txt +++ b/ISSUES.txt @@ -1,12 +1,3 @@ - -consider conftest hooks only for items below the dir ---------------------------------------------------------- -tags: bug 1.1.2 - -currently conftest hooks remain registered throughout -the whole testing process. Consider to only have them -called if their filesystem location is below a test item. - introduce py.test.mark.nocollect ------------------------------------------------------- @@ -15,19 +6,6 @@ tags: feature for not considering a function for test collection at all. maybe also introduce a py.test.mark.test to explicitely mark a function to become a tested one. Lookup -Java JUnit recent strategies/syntax. - -capture plugin: skip on missing os.dup for 'capfd' --------------------------------------------------------- - -tags: feature - -Currrently for Jython one needs do an explicit skip like this: - - @py.test.mark.skipif("not hasattr(os, 'dup')") - -to avoid a failure when 'capfd' is used. Instead -provide an automatic skip. have imported module mismatch honour relative paths --- a/testing/plugin/test_pytest_capture.py +++ b/testing/plugin/test_pytest_capture.py @@ -391,3 +391,16 @@ def test_setup_failure_does_not_kill_cap "*ValueError(42)*", "*1 error*" ]) + +def test_fdfuncarg_skips_on_no_osdup(testdir): + testdir.makepyfile(""" + import os + del os.dup + def test_hello(capfd): + pass + """) + result = testdir.runpytest() + result.stdout.fnmatch_lines([ + "*1 skipped*" + ]) + --- a/doc/test/plugin/skipping.txt +++ b/doc/test/plugin/skipping.txt @@ -9,7 +9,7 @@ advanced skipping for python test functi With this plugin you can mark test functions for conditional skipping or as "xfail", expected-to-fail. Skipping a test will avoid running it -at all while xfail-marked tests will run and result in an inverted outcome: +while xfail-marked tests will run and result in an inverted outcome: a pass becomes a failure and a fail becomes a semi-passing one. The need for skipping a test is usually connected to a condition. --- a/CHANGELOG +++ b/CHANGELOG @@ -9,6 +9,8 @@ Changes between 1.X and 1.1.1 - new "pytestconfig" funcarg allows access to test config object +- automatically skip tests that need 'capfd' but have no os.dup + - allow pytest_generate_tests to be defined in classes as well - collection/item node specific runtest/collect hooks are only called exactly --- a/doc/test/plugin/links.txt +++ b/doc/test/plugin/links.txt @@ -1,42 +1,42 @@ .. _`helpconfig`: helpconfig.html .. _`terminal`: terminal.html -.. _`pytest_recwarn.py`: http://bitbucket.org/hpk42/py-trunk/raw/1.1.1/py/plugin/pytest_recwarn.py +.. _`pytest_recwarn.py`: http://bitbucket.org/hpk42/py-trunk/raw/1.1.1post1/py/plugin/pytest_recwarn.py .. _`unittest`: unittest.html -.. _`pytest_monkeypatch.py`: http://bitbucket.org/hpk42/py-trunk/raw/1.1.1/py/plugin/pytest_monkeypatch.py +.. _`pytest_monkeypatch.py`: http://bitbucket.org/hpk42/py-trunk/raw/1.1.1post1/py/plugin/pytest_monkeypatch.py .. _`pastebin`: pastebin.html .. _`skipping`: skipping.html .. _`plugins`: index.html .. _`mark`: mark.html .. _`tmpdir`: tmpdir.html -.. _`pytest_doctest.py`: http://bitbucket.org/hpk42/py-trunk/raw/1.1.1/py/plugin/pytest_doctest.py +.. _`pytest_doctest.py`: http://bitbucket.org/hpk42/py-trunk/raw/1.1.1post1/py/plugin/pytest_doctest.py .. _`capture`: capture.html -.. _`pytest_nose.py`: http://bitbucket.org/hpk42/py-trunk/raw/1.1.1/py/plugin/pytest_nose.py -.. _`pytest_restdoc.py`: http://bitbucket.org/hpk42/py-trunk/raw/1.1.1/py/plugin/pytest_restdoc.py +.. _`pytest_nose.py`: http://bitbucket.org/hpk42/py-trunk/raw/1.1.1post1/py/plugin/pytest_nose.py +.. _`pytest_restdoc.py`: http://bitbucket.org/hpk42/py-trunk/raw/1.1.1post1/py/plugin/pytest_restdoc.py .. _`restdoc`: restdoc.html -.. _`pytest_pastebin.py`: http://bitbucket.org/hpk42/py-trunk/raw/1.1.1/py/plugin/pytest_pastebin.py -.. _`pytest_tmpdir.py`: http://bitbucket.org/hpk42/py-trunk/raw/1.1.1/py/plugin/pytest_tmpdir.py -.. _`pytest_figleaf.py`: http://bitbucket.org/hpk42/py-trunk/raw/1.1.1/py/plugin/pytest_figleaf.py -.. _`pytest_hooklog.py`: http://bitbucket.org/hpk42/py-trunk/raw/1.1.1/py/plugin/pytest_hooklog.py -.. _`pytest_skipping.py`: http://bitbucket.org/hpk42/py-trunk/raw/1.1.1/py/plugin/pytest_skipping.py +.. _`pytest_pastebin.py`: http://bitbucket.org/hpk42/py-trunk/raw/1.1.1post1/py/plugin/pytest_pastebin.py +.. _`pytest_tmpdir.py`: http://bitbucket.org/hpk42/py-trunk/raw/1.1.1post1/py/plugin/pytest_tmpdir.py +.. _`pytest_figleaf.py`: http://bitbucket.org/hpk42/py-trunk/raw/1.1.1post1/py/plugin/pytest_figleaf.py +.. _`pytest_hooklog.py`: http://bitbucket.org/hpk42/py-trunk/raw/1.1.1post1/py/plugin/pytest_hooklog.py +.. _`pytest_skipping.py`: http://bitbucket.org/hpk42/py-trunk/raw/1.1.1post1/py/plugin/pytest_skipping.py .. _`checkout the py.test development version`: ../../install.html#checkout -.. _`pytest_helpconfig.py`: http://bitbucket.org/hpk42/py-trunk/raw/1.1.1/py/plugin/pytest_helpconfig.py +.. _`pytest_helpconfig.py`: http://bitbucket.org/hpk42/py-trunk/raw/1.1.1post1/py/plugin/pytest_helpconfig.py .. _`oejskit`: oejskit.html .. _`doctest`: doctest.html -.. _`pytest_mark.py`: http://bitbucket.org/hpk42/py-trunk/raw/1.1.1/py/plugin/pytest_mark.py +.. _`pytest_mark.py`: http://bitbucket.org/hpk42/py-trunk/raw/1.1.1post1/py/plugin/pytest_mark.py .. _`get in contact`: ../../contact.html -.. _`pytest_capture.py`: http://bitbucket.org/hpk42/py-trunk/raw/1.1.1/py/plugin/pytest_capture.py +.. _`pytest_capture.py`: http://bitbucket.org/hpk42/py-trunk/raw/1.1.1post1/py/plugin/pytest_capture.py .. _`figleaf`: figleaf.html .. _`customize`: ../customize.html .. _`hooklog`: hooklog.html -.. _`pytest_terminal.py`: http://bitbucket.org/hpk42/py-trunk/raw/1.1.1/py/plugin/pytest_terminal.py +.. _`pytest_terminal.py`: http://bitbucket.org/hpk42/py-trunk/raw/1.1.1post1/py/plugin/pytest_terminal.py .. _`recwarn`: recwarn.html -.. _`pytest_pdb.py`: http://bitbucket.org/hpk42/py-trunk/raw/1.1.1/py/plugin/pytest_pdb.py +.. _`pytest_pdb.py`: http://bitbucket.org/hpk42/py-trunk/raw/1.1.1post1/py/plugin/pytest_pdb.py .. _`monkeypatch`: monkeypatch.html .. _`coverage`: coverage.html .. _`resultlog`: resultlog.html .. _`django`: django.html .. _`xmlresult`: xmlresult.html -.. _`pytest_unittest.py`: http://bitbucket.org/hpk42/py-trunk/raw/1.1.1/py/plugin/pytest_unittest.py +.. _`pytest_unittest.py`: http://bitbucket.org/hpk42/py-trunk/raw/1.1.1post1/py/plugin/pytest_unittest.py .. _`nose`: nose.html -.. _`pytest_resultlog.py`: http://bitbucket.org/hpk42/py-trunk/raw/1.1.1/py/plugin/pytest_resultlog.py +.. _`pytest_resultlog.py`: http://bitbucket.org/hpk42/py-trunk/raw/1.1.1post1/py/plugin/pytest_resultlog.py .. _`pdb`: pdb.html --- a/doc/test/plugin/hookspec.txt +++ b/doc/test/plugin/hookspec.txt @@ -165,7 +165,7 @@ hook specification sourcecode # error handling and internal debugging hooks # ------------------------------------------------------------------------- - def pytest_plugin_registered(plugin): + def pytest_plugin_registered(plugin, manager): """ a new py lib plugin got registered. """ def pytest_plugin_unregistered(plugin): --- a/doc/test/plugin/capture.txt +++ b/doc/test/plugin/capture.txt @@ -107,7 +107,9 @@ the 'capfd' test function argument captures writes to file descriptors 1 and 2 and makes snapshotted ``(out, err)`` string tuples available -via the ``capsys.readouterr()`` method. +via the ``capsys.readouterr()`` method. If the underlying +platform does not have ``os.dup`` (e.g. Jython) tests using +this funcarg will automatically skip. command line options -------------------- --- a/py/plugin/pytest_capture.py +++ b/py/plugin/pytest_capture.py @@ -182,6 +182,8 @@ class CaptureManager: assert not hasattr(self, '_capturing_funcargs') l = [] for name, obj in pyfuncitem.funcargs.items(): + if name == 'capfd' and not hasattr(os, 'dup'): + py.test.skip("capfd funcarg needs os.dup") if name in ('capsys', 'capfd'): obj._start() l.append(obj) @@ -250,7 +252,9 @@ def pytest_funcarg__capsys(request): def pytest_funcarg__capfd(request): """captures writes to file descriptors 1 and 2 and makes snapshotted ``(out, err)`` string tuples available - via the ``capsys.readouterr()`` method. + via the ``capsys.readouterr()`` method. If the underlying + platform does not have ``os.dup`` (e.g. Jython) tests using + this funcarg will automatically skip. """ return CaptureFuncarg(request, py.io.StdCaptureFD) From commits-noreply at bitbucket.org Wed Dec 30 13:05:36 2009 From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org) Date: Wed, 30 Dec 2009 12:05:36 +0000 (UTC) Subject: [py-svn] py-trunk commit 7c865991c921: update ISSUES some more, introduce duration to RunResult and a failing dist-testing termination test. Message-ID: <20091230120536.F30DE7EF01@bitbucket.org> # HG changeset patch -- Bitbucket.org # Project py-trunk # URL http://bitbucket.org/hpk42/py-trunk/overview/ # User holger krekel # Date 1262169466 -3600 # Node ID 7c865991c921761dc18d4de4becc4f031cd088ca # Parent cf5995e234619de5dccd1ff6757b9e63be65e3a9 update ISSUES some more, introduce duration to RunResult and a failing dist-testing termination test. --- a/ISSUES.txt +++ b/ISSUES.txt @@ -1,16 +1,16 @@ introduce py.test.mark.nocollect ------------------------------------------------------- -tags: feature +tags: feature 1.2 for not considering a function for test collection at all. maybe also introduce a py.test.mark.test to explicitely -mark a function to become a tested one. Lookup - +mark a function to become a tested one. Lookup JUnit +ways of tagging tests. have imported module mismatch honour relative paths -------------------------------------------------------- -tags: bug +tags: bug 1.2 With 1.1.1 py.test fails at least on windows if an import is relative and compared against an absolute conftest.py @@ -18,7 +18,7 @@ path. Normalize. allow plugins/conftests to show extra header information -------------------------------------------------------- -tags: feature +tags: feature 1.2 The test-report header should optionally show information about the under-test package and versions/locations of @@ -26,7 +26,7 @@ involved packages. make node._checkcollectable more robust ------------------------------------------------- -tags: bug 1.1.2 +tags: bug 1.2 currently node._checkcollectable() can raise exceptions for all kinds of reasons ('conftest.py' loading @@ -36,35 +36,41 @@ a good error message. call termination with small timeout ------------------------------------------------- -tags: feature 1.1.2 +tags: feature 1.2 +test: testing/pytest/dist/test_dsession.py - test_terminate_on_hanging_node Call gateway group termination with a small timeout if available. Should make dist-testing less likely to leave lost processes. -make capfd skip if 'dup' is not available -------------------------------------------------------- - -tags: feature 1.1.2 - -currently, using 'capfd' as a funcarg will fail because -it cannot call os.dup on setup. Should cause a skip. - -introduce multi-install, i.e. py.test3, py.test-pypy, py.test-jython -and maybe a commandline-"suffix" override? - fix dist-testing: execnet needs to be rsynced over automatically ------------------------------------------------------------------ -tags: bug 1.1.2 +tags: bug 1.2 bb: http://bitbucket.org/hpk42/py-trunk/issue/65/ execnet is not rsynced so fails if run in an ssh-situation. write test and fix. +dist-testing: fix session hook / setup calling +----------------------------------------------------- +tags: bug 1.2 + +Currently pytest_sessionstart and finish are called +on the master node and not on the slaves. Call +it on slaves and provide a session.nodeid which defaults +to None for the master and contains the gateway id +for slaves. + +have --report=xfailed[-detail] report the actual tracebacks +------------------------------------------------------------------ +tags: feature + +there is no way to induce py.test to display the full tracebacks +of the expected failure. Introduce one. relax requirement to have tests/testing contain an __init__ ---------------------------------------------------------------- -tags: feature 1.1.2 +tags: feature 1.2 bb: http://bitbucket.org/hpk42/py-trunk/issue/64 A local test run of a "tests" directory may work @@ -74,7 +80,7 @@ an error or make it work without the __i deprecate ensuretemp / introduce funcargs to setup method -------------------------------------------------------------- -tags: wish 1.1.2 +tags: experimental-wish 1.2 The remaining uses of py.test.ensuretemp within the py-test base itself are for setup methods. Also users have expressed the --- a/testing/pytest/dist/test_dsession.py +++ b/testing/pytest/dist/test_dsession.py @@ -440,4 +440,19 @@ def test_teardownfails_one_function(test "*1 passed*1 error*" ]) + at py.test.mark.xfail +def test_terminate_on_hangingnode(testdir): + p = testdir.makeconftest(""" + def pytest__teardown_final(session): + if session.nodeid: # running on slave + import time + time.sleep(2) + """) + result = testdir.runpytest(p, '--dist=each', '--tx=popen') + assert result.duration < 2.0 + result.stdout.fnmatch_lines([ + "*0 passed*", + ]) + + --- a/py/plugin/pytest_pytester.py +++ b/py/plugin/pytest_pytester.py @@ -6,6 +6,7 @@ import py import sys, os import re import inspect +import time from py.impl.test.config import Config as pytestConfig from py.plugin import hookspec from py.builtin import print_ @@ -24,12 +25,14 @@ def pytest_funcarg__testdir(request): rex_outcome = re.compile("(\d+) (\w+)") class RunResult: - def __init__(self, ret, outlines, errlines): + def __init__(self, ret, outlines, errlines, duration): self.ret = ret self.outlines = outlines self.errlines = errlines self.stdout = LineMatcher(outlines) self.stderr = LineMatcher(errlines) + self.duration = duration + def parseoutcomes(self): for line in reversed(self.outlines): if 'seconds' in line: @@ -284,6 +287,7 @@ class TmpTestdir: print_("running", cmdargs, "curdir=", py.path.local()) f1 = p1.open("w") f2 = p2.open("w") + now = time.time() popen = self.popen(cmdargs, stdout=f1, stderr=f2, close_fds=(sys.platform != "win32")) ret = popen.wait() @@ -296,7 +300,7 @@ class TmpTestdir: if out: for line in out: py.builtin.print_(line, file=sys.stdout) - return RunResult(ret, out, err) + return RunResult(ret, out, err, time.time()-now) def runpybin(self, scriptname, *args): fullargs = self._getpybinargs(scriptname) + args From commits-noreply at bitbucket.org Wed Dec 30 13:05:38 2009 From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org) Date: Wed, 30 Dec 2009 12:05:38 +0000 (UTC) Subject: [py-svn] py-trunk commit 217b2f0523c0: deprecate use of 'disabled' Message-ID: <20091230120538.AECF37EF02@bitbucket.org> # HG changeset patch -- Bitbucket.org # Project py-trunk # URL http://bitbucket.org/hpk42/py-trunk/overview/ # User holger krekel # Date 1262171618 -3600 # Node ID 217b2f0523c0e1567529ba66d6cd27456b2dd6bf # Parent 7c865991c921761dc18d4de4becc4f031cd088ca deprecate use of 'disabled' --- a/testing/pytest/test_pycollect.py +++ b/testing/pytest/test_pycollect.py @@ -1,7 +1,5 @@ import py -from py.impl.test.outcome import Skipped - class TestModule: def test_module_file_not_found(self, testdir): tmpdir = testdir.tmpdir @@ -50,43 +48,6 @@ class TestClass: """) l = modcol.collect() assert len(l) == 0 - -class TestDisabled: - def test_disabled_module(self, testdir): - modcol = testdir.getmodulecol(""" - disabled = True - def setup_module(mod): - raise ValueError - def test_method(): - pass - """) - l = modcol.collect() - assert len(l) == 1 - py.test.raises(Skipped, "modcol.setup()") - - def test_disabled_class(self, testdir): - modcol = testdir.getmodulecol(""" - class TestClass: - disabled = True - def test_method(self): - pass - """) - l = modcol.collect() - assert len(l) == 1 - modcol = l[0] - assert isinstance(modcol, py.test.collect.Class) - l = modcol.collect() - 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) if py.std.sys.version_info > (3, 0): _func_name_attr = "__name__" --- a/py/plugin/pytest_recwarn.py +++ b/py/plugin/pytest_recwarn.py @@ -72,6 +72,7 @@ def deprecated_call(func, *args, **kwarg warningmodule.warn = warn if not l: #print warningmodule + __tracebackhide__ = True raise AssertionError("%r did not produce DeprecationWarning" %(func,)) return ret --- a/testing/pytest/test_deprecated_api.py +++ b/testing/pytest/test_deprecated_api.py @@ -1,5 +1,6 @@ import py +from py.impl.test.outcome import Skipped class TestCollectDeprecated: @@ -167,3 +168,45 @@ class TestCollectDeprecated: config = testdir.parseconfig(testme) col = config.getfsnode(testme) assert col.collect() == [] + + +class TestDisabled: + def test_disabled_module(self, recwarn, testdir): + modcol = testdir.getmodulecol(""" + disabled = True + def setup_module(mod): + raise ValueError + def test_method(): + pass + """) + l = modcol.collect() + assert len(l) == 1 + recwarn.clear() + py.test.raises(Skipped, "modcol.setup()") + recwarn.pop(DeprecationWarning) + + def test_disabled_class(self, recwarn, testdir): + modcol = testdir.getmodulecol(""" + class TestClass: + disabled = True + def test_method(self): + pass + """) + l = modcol.collect() + assert len(l) == 1 + modcol = l[0] + assert isinstance(modcol, py.test.collect.Class) + l = modcol.collect() + assert len(l) == 1 + recwarn.clear() + py.test.raises(Skipped, "modcol.setup()") + recwarn.pop(DeprecationWarning) + + 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) --- a/py/impl/test/pycollect.py +++ b/py/impl/test/pycollect.py @@ -167,6 +167,8 @@ class Module(py.test.collect.File, PyCol def setup(self): if getattr(self.obj, 'disabled', 0): + py.log._apiwarn(">1.1.1", "%r uses 'disabled' which is deprecated, " + "use pytestmark=..., see pytest_skipping plugin" % (self.obj,)) py.test.skip("%r is disabled" %(self.obj,)) if hasattr(self.obj, 'setup_module'): #XXX: nose compat hack, move to nose plugin @@ -197,6 +199,8 @@ class Class(PyCollectorMixin, py.test.co def setup(self): if getattr(self.obj, 'disabled', 0): + py.log._apiwarn(">1.1.1", "%r uses 'disabled' which is deprecated, " + "use pytestmark=..., see pytest_skipping plugin" % (self.obj,)) py.test.skip("%r is disabled" %(self.obj,)) setup_class = getattr(self.obj, 'setup_class', None) if setup_class is not None: --- a/CHANGELOG +++ b/CHANGELOG @@ -13,6 +13,8 @@ Changes between 1.X and 1.1.1 - allow pytest_generate_tests to be defined in classes as well +- deprecate usage of 'disabled' attribute in favour of pytestmark + - collection/item node specific runtest/collect hooks are only called exactly on matching conftest.py files, i.e. ones which are exactly below the filesystem path of an item From commits-noreply at bitbucket.org Wed Dec 30 13:05:40 2009 From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org) Date: Wed, 30 Dec 2009 12:05:40 +0000 (UTC) Subject: [py-svn] py-trunk commit 1a34c06b5a81: streamline svn test setup a bit, clear caches on setup-restore, hopefully will erase random failures with test_export. Message-ID: <20091230120540.A94B77EF03@bitbucket.org> # HG changeset patch -- Bitbucket.org # Project py-trunk # URL http://bitbucket.org/hpk42/py-trunk/overview/ # User holger krekel # Date 1262174708 -3600 # Node ID 1a34c06b5a815ae8130432b6df09f765d6328fcd # Parent 217b2f0523c0e1567529ba66d6cd27456b2dd6bf streamline svn test setup a bit, clear caches on setup-restore, hopefully will erase random failures with test_export. --- a/testing/path/common.py +++ b/testing/path/common.py @@ -378,7 +378,7 @@ class CommonFSTests(object): newp.move(p) assert p.check() - def test_move_directory(self, path1): + def test_move_dir(self, path1): source = path1.join('sampledir') dest = path1.join('moveddir') source.move(dest) --- a/testing/path/test_svnurl.py +++ b/testing/path/test_svnurl.py @@ -13,22 +13,6 @@ class TestSvnURLCommandPath(CommonSvnTes def test_load(self, path1): super(TestSvnURLCommandPath, self).test_load(path1) - def test_move_file(self, path1): # overrides base class - p = path1.ensure('origfile') - newp = p.dirpath('newfile') - p.move(newp) - assert newp.check(file=1) - newp.remove() - assert not p.check() - - def test_move_dir(self, path1): # overrides base class - p = path1.ensure('origdir', dir=1) - newp = p.dirpath('newdir') - p.move(newp) - assert newp.check(dir=1) - newp.remove() - assert not p.check() - def test_svnurl_needs_arg(self, path1): py.test.raises(TypeError, "py.path.svnurl()") --- a/testing/path/test_cacheutil.py +++ b/testing/path/test_cacheutil.py @@ -22,6 +22,9 @@ class BasicCacheAPITest: self.cache.delentry(100) py.test.raises(KeyError, "self.cache.delentry(100, raising=True)") + def test_clear(self): + self.cache.clear() + class TestBuildcostAccess(BasicCacheAPITest): cache = cacheutil.BuildcostAccessCache(maxentries=128) --- a/testing/path/test_svnwc.py +++ b/testing/path/test_svnwc.py @@ -28,18 +28,6 @@ def pytest_funcarg__path1(request): return wc class TestWCSvnCommandPath(CommonSvnTests): - def test_move_file(self, path1): # overrides base class - try: - super(TestWCSvnCommandPath, self).test_move_file(path1) - finally: - path1.revert(rec=1) - - def test_move_directory(self, path1): # overrides base class - try: - super(TestWCSvnCommandPath, self).test_move_directory(path1) - finally: - path1.revert(rec=1) - def test_status_attributes_simple(self, path1): def assert_nochange(p): s = p.status() --- a/testing/path/conftest.py +++ b/testing/path/conftest.py @@ -76,3 +76,5 @@ def restore_repowc(obj): repo.remove() savedrepo.move(repo) savedwc.localpath.move(wc.localpath) + py.path.svnurl._lsnorevcache.clear() + py.path.svnurl._lsrevcache.clear() --- a/py/impl/path/svnwc.py +++ b/py/impl/path/svnwc.py @@ -235,16 +235,6 @@ class SvnPathBase(common.PathBase): content = self._proplist() return content - def info(self): - """ return an Info structure with svn-provided information. """ - parent = self.dirpath() - nameinfo_seq = parent._listdir_nameinfo() - bn = self.basename - for name, info in nameinfo_seq: - if name == bn: - return info - raise py.error.ENOENT(self) - def size(self): """ Return the size of the file content of the Path. """ return self.info().size --- a/py/impl/path/svnurl.py +++ b/py/impl/path/svnurl.py @@ -213,6 +213,17 @@ checkin message msg.""" lines = [x.strip() for x in lines[1:]] return svncommon.PropListDict(self, lines) + def info(self): + """ return an Info structure with svn-provided information. """ + parent = self.dirpath() + nameinfo_seq = parent._listdir_nameinfo() + bn = self.basename + for name, info in nameinfo_seq: + if name == bn: + return info + raise py.error.ENOENT(self) + + def _listdir_nameinfo(self): """ return sequence of name-info directory entries of self """ def builder(): --- a/py/impl/path/cacheutil.py +++ b/py/impl/path/cacheutil.py @@ -16,6 +16,9 @@ class BasicCache(object): self.prunenum = int(maxentries - maxentries/8) self._dict = {} + def clear(self): + self._dict.clear() + def _getentry(self, key): return self._dict[key] From commits-noreply at bitbucket.org Wed Dec 30 14:06:04 2009 From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org) Date: Wed, 30 Dec 2009 13:06:04 +0000 (UTC) Subject: [py-svn] py-trunk commit ed40b4eda543: fix aimed at passing jstests functional tests: allow to have _fillfuncargs() called even for non-pycollect-object test-items. Message-ID: <20091230130604.D2C5B7EF0A@bitbucket.org> # HG changeset patch -- Bitbucket.org # Project py-trunk # URL http://bitbucket.org/hpk42/py-trunk/overview/ # User holger krekel # Date 1262178341 -3600 # Node ID ed40b4eda543c3e88efd7de7555fb0ebe36cd77d # Parent 1a34c06b5a815ae8130432b6df09f765d6328fcd fix aimed at passing jstests functional tests: allow to have _fillfuncargs() called even for non-pycollect-object test-items. --- a/py/impl/test/funcargs.py +++ b/py/impl/test/funcargs.py @@ -15,6 +15,16 @@ def fillfuncargs(function): request = FuncargRequest(pyfuncitem=function) request._fillfuncargs() +def getplugins(node, withpy=False): # might by any node + plugins = node.config._getmatchingplugins(node.fspath) + if withpy: + mod = node.getparent(py.test.collect.Module) + if mod is not None: + plugins.append(mod.obj) + inst = node.getparent(py.test.collect.Instance) + if inst is not None: + plugins.append(inst.obj) + return plugins _notexists = object() class CallSpec: @@ -93,7 +103,7 @@ class FuncargRequest: self.fspath = pyfuncitem.fspath if hasattr(pyfuncitem, '_requestparam'): self.param = pyfuncitem._requestparam - self._plugins = pyfuncitem._getplugins(withpy=True) + self._plugins = getplugins(pyfuncitem, withpy=True) self._funcargs = self._pyfuncitem.funcargs.copy() self._name2factory = {} self._currentarg = None --- a/py/impl/test/pycollect.py +++ b/py/impl/test/pycollect.py @@ -34,15 +34,6 @@ class PyobjMixin(object): return property(fget, fset, None, "underlying python object") obj = obj() - def _getplugins(self, withpy=False): - plugins = self.config._getmatchingplugins(self.fspath) - if withpy: - plugins.append(self.getparent(py.test.collect.Module).obj) - inst = self.getparent(py.test.collect.Instance) - if inst is not None: - plugins.append(inst.obj) - return plugins - def _getobj(self): return getattr(self.parent.obj, self.name) @@ -147,7 +138,8 @@ class PyCollectorMixin(PyobjMixin, py.te metafunc = funcargs.Metafunc(funcobj, config=self.config, cls=cls, module=module) gentesthook = self.config.hook.pytest_generate_tests - gentesthook.pcall(self._getplugins(withpy=True), metafunc=metafunc) + plugins = funcargs.getplugins(self, withpy=True) + gentesthook.pcall(plugins, metafunc=metafunc) if not metafunc._calls: return self.Function(name, parent=self) return funcargs.FunctionCollector(name=name, --- a/py/impl/test/collect.py +++ b/py/impl/test/collect.py @@ -19,7 +19,7 @@ class HookProxy: raise AttributeError(name) hookmethod = getattr(self.node.config.hook, name) def call_matching_hooks(**kwargs): - plugins = self.node._getplugins() + plugins = self.node.config._getmatchingplugins(self.node.fspath) return hookmethod.pcall(plugins, **kwargs) return call_matching_hooks @@ -43,9 +43,6 @@ class Node(object): self.fspath = getattr(parent, 'fspath', None) self.ihook = HookProxy(self) - def _getplugins(self): - return self.config._getmatchingplugins(self.fspath) - def _checkcollectable(self): if not hasattr(self, 'fspath'): self.parent._memocollect() # to reraise exception --- a/testing/pytest/test_funcargs.py +++ b/testing/pytest/test_funcargs.py @@ -519,3 +519,26 @@ def test_conftest_funcargs_only_availabl result.stdout.fnmatch_lines([ "*2 passed*" ]) + +def test_funcarg_non_pycollectobj(testdir): # rough jstests usage + testdir.makeconftest(""" + import py + def pytest_pycollect_makeitem(collector, name, obj): + if name == "MyClass": + return MyCollector(name, parent=collector) + class MyCollector(py.test.collect.Collector): + def reportinfo(self): + return self.fspath, 3, "xyz" + """) + modcol = testdir.getmodulecol(""" + def pytest_funcarg__arg1(request): + return 42 + class MyClass: + pass + """) + clscol = modcol.collect()[0] + clscol.obj = lambda arg1: None + clscol.funcargs = {} + funcargs.fillfuncargs(clscol) + assert clscol.funcargs['arg1'] == 42 + From commits-noreply at bitbucket.org Wed Dec 30 16:28:04 2009 From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org) Date: Wed, 30 Dec 2009 15:28:04 +0000 (UTC) Subject: [py-svn] py-trunk commit 06dfbfa6fb8d: deprecate direct definition of Directory, Module, ... in conftest.py's, Message-ID: <20091230152804.6E8547EF0D@bitbucket.org> # HG changeset patch -- Bitbucket.org # Project py-trunk # URL http://bitbucket.org/hpk42/py-trunk/overview/ # User holger krekel # Date 1262186339 -3600 # Node ID 06dfbfa6fb8df272db923d742dc06e5ab6a97474 # Parent 851eded2b0e078a64e3e9f850181ae7a7016e435 deprecate direct definition of Directory, Module, ... in conftest.py's, add some pytest collect related tests + some refinements. --- a/CHANGELOG +++ b/CHANGELOG @@ -14,11 +14,16 @@ Changes between 1.X and 1.1.1 - allow pytest_generate_tests to be defined in classes as well - deprecate usage of 'disabled' attribute in favour of pytestmark +- deprecate definition of Directory, Module, Class and Function nodes + in conftest.py files. Use pytest collect hooks instead. - collection/item node specific runtest/collect hooks are only called exactly on matching conftest.py files, i.e. ones which are exactly below the filesystem path of an item +- change: the first pytest_collect_directory hook to return something + will now prevent further hooks to be called. + - robustify capturing to survive if custom pytest_runtest_setup code failed and prevented the capturing setup code from running. --- a/testing/pytest/test_pycollect.py +++ b/testing/pytest/test_pycollect.py @@ -4,7 +4,7 @@ class TestModule: def test_module_file_not_found(self, testdir): tmpdir = testdir.tmpdir fn = tmpdir.join('nada','no') - col = py.test.collect.Module(fn) + col = py.test.collect.Module(fn, config=testdir.Config()) col.config = testdir.parseconfig(tmpdir) py.test.raises(py.error.ENOENT, col.collect) @@ -213,13 +213,13 @@ class TestFunction: def test_function_equality(self, testdir, tmpdir): config = testdir.reparseconfig() - f1 = py.test.collect.Function(name="name", + f1 = py.test.collect.Function(name="name", config=config, args=(1,), callobj=isinstance) - f2 = py.test.collect.Function(name="name", + f2 = py.test.collect.Function(name="name",config=config, args=(1,), callobj=py.builtin.callable) assert not f1 == f2 assert f1 != f2 - f3 = py.test.collect.Function(name="name", + f3 = py.test.collect.Function(name="name", config=config, args=(1,2), callobj=py.builtin.callable) assert not f3 == f2 assert f3 != f2 @@ -227,7 +227,7 @@ class TestFunction: assert not f3 == f1 assert f3 != f1 - f1_b = py.test.collect.Function(name="name", + f1_b = py.test.collect.Function(name="name", config=config, args=(1,), callobj=isinstance) assert f1 == f1_b assert not f1 != f1_b @@ -242,9 +242,9 @@ class TestFunction: param = 1 funcargs = {} id = "world" - f5 = py.test.collect.Function(name="name", + f5 = py.test.collect.Function(name="name", config=config, callspec=callspec1, callobj=isinstance) - f5b = py.test.collect.Function(name="name", + f5b = py.test.collect.Function(name="name", config=config, callspec=callspec2, callobj=isinstance) assert f5 != f5b assert not (f5 == f5b) @@ -313,55 +313,21 @@ class TestSorting: class TestConftestCustomization: - def test_extra_python_files_and_functions(self, testdir): - testdir.makepyfile(conftest=""" + def test_pytest_pycollect_makeitem(self, testdir): + testdir.makeconftest(""" import py class MyFunction(py.test.collect.Function): pass - class Directory(py.test.collect.Directory): - def consider_file(self, path): - if path.check(fnmatch="check_*.py"): - return self.Module(path, parent=self) - return super(Directory, self).consider_file(path) - class myfuncmixin: - Function = MyFunction - def funcnamefilter(self, name): - return name.startswith('check_') - class Module(myfuncmixin, py.test.collect.Module): - def classnamefilter(self, name): - return name.startswith('CustomTestClass') - class Instance(myfuncmixin, py.test.collect.Instance): - pass + def pytest_pycollect_makeitem(collector, name, obj): + if name == "some": + return MyFunction(name, collector) """) - checkfile = testdir.makepyfile(check_file=""" - def check_func(): - assert 42 == 42 - class CustomTestClass: - def check_method(self): - assert 23 == 23 - """) - # check that directory collects "check_" files - config = testdir.parseconfig() - col = config.getfsnode(checkfile.dirpath()) - colitems = col.collect() - assert len(colitems) == 1 - assert isinstance(colitems[0], py.test.collect.Module) - - # check that module collects "check_" functions and methods - config = testdir.parseconfig(checkfile) - col = config.getfsnode(checkfile) - assert isinstance(col, py.test.collect.Module) - colitems = col.collect() - assert len(colitems) == 2 - funccol = colitems[0] - assert isinstance(funccol, py.test.collect.Function) - assert funccol.name == "check_func" - clscol = colitems[1] - assert isinstance(clscol, py.test.collect.Class) - colitems = clscol.collect()[0].collect() - assert len(colitems) == 1 - assert colitems[0].name == "check_method" - + testdir.makepyfile("def some(): pass") + result = testdir.runpytest("--collectonly") + result.stdout.fnmatch_lines([ + "*MyFunction*some*", + ]) + def test_makeitem_non_underscore(self, testdir, monkeypatch): modcol = testdir.getmodulecol("def _hello(): pass") l = [] --- a/py/plugin/hookspec.py +++ b/py/plugin/hookspec.py @@ -26,6 +26,7 @@ def pytest_unconfigure(config): def pytest_collect_directory(path, parent): """ return Collection node or None for the given path. """ +pytest_collect_directory.firstresult = True def pytest_collect_file(path, parent): """ return Collection node or None for the given path. """ --- a/testing/pytest/test_collect.py +++ b/testing/pytest/test_collect.py @@ -193,31 +193,6 @@ class TestPrunetraceback: ]) class TestCustomConftests: - def test_non_python_files(self, testdir): - testdir.makepyfile(conftest=""" - import py - class CustomItem(py.test.collect.Item): - def run(self): - pass - class Directory(py.test.collect.Directory): - def consider_file(self, fspath): - if fspath.ext == ".xxx": - return CustomItem(fspath.basename, parent=self) - """) - checkfile = testdir.makefile(ext="xxx", hello="world") - testdir.makepyfile(x="") - testdir.maketxtfile(x="") - config = testdir.parseconfig() - dircol = config.getfsnode(checkfile.dirpath()) - colitems = dircol.collect() - assert len(colitems) == 1 - assert colitems[0].name == "hello.xxx" - assert colitems[0].__class__.__name__ == "CustomItem" - - item = config.getfsnode(checkfile) - assert item.name == "hello.xxx" - assert item.__class__.__name__ == "CustomItem" - def test_collectignore_exclude_on_option(self, testdir): testdir.makeconftest(""" collect_ignore = ['hello', 'test_world.py'] @@ -237,3 +212,23 @@ class TestCustomConftests: names = [rep.collector.name for rep in reprec.getreports("pytest_collectreport")] assert 'hello' in names assert 'test_world.py' in names + + def test_pytest_fs_collect_hooks_are_seen(self, testdir): + testdir.makeconftest(""" + import py + class MyDirectory(py.test.collect.Directory): + pass + class MyModule(py.test.collect.Module): + pass + def pytest_collect_directory(path, parent): + return MyDirectory(path, parent) + def pytest_collect_file(path, parent): + return MyModule(path, parent) + """) + testdir.makepyfile("def test_x(): pass") + result = testdir.runpytest("--collectonly") + result.stdout.fnmatch_lines([ + "*MyDirectory*", + "*MyModule*", + "*test_x*" + ]) --- a/py/impl/test/collect.py +++ b/py/impl/test/collect.py @@ -36,10 +36,10 @@ class Node(object): - configuration/options for setup/teardown stdout/stderr capturing and execution of test items """ - def __init__(self, name, parent=None): + def __init__(self, name, parent=None, config=None): self.name = name self.parent = parent - self.config = getattr(parent, 'config', None) + self.config = config or parent.config self.fspath = getattr(parent, 'fspath', None) self.ihook = HookProxy(self) @@ -353,9 +353,9 @@ class Collector(Node): return traceback class FSCollector(Collector): - def __init__(self, fspath, parent=None): + def __init__(self, fspath, parent=None, config=None): fspath = py.path.local(fspath) - super(FSCollector, self).__init__(fspath.basename, parent) + super(FSCollector, self).__init__(fspath.basename, parent, config=config) self.fspath = fspath def __getstate__(self): --- a/testing/pytest/test_deprecated_api.py +++ b/testing/pytest/test_deprecated_api.py @@ -37,15 +37,19 @@ class TestCollectDeprecated: def join(self, name): if name == "somefile.py": return self.Module(self.fspath.join(name), parent=self) - Directory = MyDirectory + + def pytest_collect_directory(path, parent): + return MyDirectory(path, parent) """) - p = testdir.makepyfile(somefile=""" + subconf = testdir.mkpydir("subconf") + somefile = subconf.join("somefile.py") + somefile.write(py.code.Source(""" def check(): pass class Cls: def check2(self): pass - """) - config = testdir.parseconfig() - dirnode = config.getfsnode(p.dirpath()) + """)) + config = testdir.parseconfig(somefile) + dirnode = config.getfsnode(somefile.dirpath()) colitems = dirnode.collect() w = recwarn.pop(DeprecationWarning) assert w.filename.find("conftest.py") != -1 @@ -120,6 +124,7 @@ class TestCollectDeprecated: """) modcol = testdir.getmodulecol("def test_func2(): pass") funcitem = modcol.collect()[0] + w = recwarn.pop(DeprecationWarning) # for defining conftest.Function assert funcitem.name == 'test_func2' funcitem._deprecated_testexecution() w = recwarn.pop(DeprecationWarning) @@ -136,6 +141,8 @@ class TestCollectDeprecated: """) modcol = testdir.getmodulecol("def test_some2(): pass") funcitem = modcol.collect()[0] + w = recwarn.pop(DeprecationWarning) + assert "conftest.py" in str(w.message) recwarn.clear() funcitem._deprecated_testexecution() @@ -169,6 +176,7 @@ class TestCollectDeprecated: col = config.getfsnode(testme) assert col.collect() == [] + class TestDisabled: def test_disabled_module(self, recwarn, testdir): @@ -211,6 +219,31 @@ class TestDisabled: """) reprec.assertoutcome(skipped=2) + @py.test.mark.multi(name="Directory Module Class Function".split()) + def test_function_deprecated_run_execute(self, name, testdir, recwarn): + testdir.makeconftest(""" + import py + class %s(py.test.collect.%s): + pass + """ % (name, name)) + p = testdir.makepyfile(""" + class TestClass: + def test_method(self): + pass + def test_function(): + pass + """) + config = testdir.parseconfig() + if name == "Directory": + config.getfsnode(testdir.tmpdir) + elif name in ("Module", "File"): + config.getfsnode(p) + else: + fnode = config.getfsnode(p) + recwarn.clear() + fnode.collect() + w = recwarn.pop(DeprecationWarning) + assert "conftest.py" in str(w.message) def test_config_cmdline_options(recwarn, testdir): testdir.makepyfile(conftest=""" @@ -267,3 +300,80 @@ def test_dist_conftest_options(testdir): "*1 passed*", ]) +def test_conftest_non_python_items(recwarn, testdir): + testdir.makepyfile(conftest=""" + import py + class CustomItem(py.test.collect.Item): + def run(self): + pass + class Directory(py.test.collect.Directory): + def consider_file(self, fspath): + if fspath.ext == ".xxx": + return CustomItem(fspath.basename, parent=self) + """) + checkfile = testdir.makefile(ext="xxx", hello="world") + testdir.makepyfile(x="") + testdir.maketxtfile(x="") + config = testdir.parseconfig() + recwarn.clear() + dircol = config.getfsnode(checkfile.dirpath()) + w = recwarn.pop(DeprecationWarning) + assert str(w.message).find("conftest.py") != -1 + colitems = dircol.collect() + assert len(colitems) == 1 + assert colitems[0].name == "hello.xxx" + assert colitems[0].__class__.__name__ == "CustomItem" + + item = config.getfsnode(checkfile) + assert item.name == "hello.xxx" + assert item.__class__.__name__ == "CustomItem" + +def test_extra_python_files_and_functions(testdir): + testdir.makepyfile(conftest=""" + import py + class MyFunction(py.test.collect.Function): + pass + class Directory(py.test.collect.Directory): + def consider_file(self, path): + if path.check(fnmatch="check_*.py"): + return self.Module(path, parent=self) + return super(Directory, self).consider_file(path) + class myfuncmixin: + Function = MyFunction + def funcnamefilter(self, name): + return name.startswith('check_') + class Module(myfuncmixin, py.test.collect.Module): + def classnamefilter(self, name): + return name.startswith('CustomTestClass') + class Instance(myfuncmixin, py.test.collect.Instance): + pass + """) + checkfile = testdir.makepyfile(check_file=""" + def check_func(): + assert 42 == 42 + class CustomTestClass: + def check_method(self): + assert 23 == 23 + """) + # check that directory collects "check_" files + config = testdir.parseconfig() + col = config.getfsnode(checkfile.dirpath()) + colitems = col.collect() + assert len(colitems) == 1 + assert isinstance(colitems[0], py.test.collect.Module) + + # check that module collects "check_" functions and methods + config = testdir.parseconfig(checkfile) + col = config.getfsnode(checkfile) + assert isinstance(col, py.test.collect.Module) + colitems = col.collect() + assert len(colitems) == 2 + funccol = colitems[0] + assert isinstance(funccol, py.test.collect.Function) + assert funccol.name == "check_func" + clscol = colitems[1] + assert isinstance(clscol, py.test.collect.Class) + colitems = clscol.collect()[0].collect() + assert len(colitems) == 1 + assert colitems[0].name == "check_method" + --- a/py/impl/test/pycollect.py +++ b/py/impl/test/pycollect.py @@ -315,9 +315,9 @@ class Function(FunctionMixin, py.test.co and executing a Python callable test object. """ _genid = None - def __init__(self, name, parent=None, args=None, + def __init__(self, name, parent=None, args=None, config=None, callspec=None, callobj=_dummy): - super(Function, self).__init__(name, parent) + super(Function, self).__init__(name, parent, config=config) self._args = args if self._isyieldedfunction(): assert not callspec, "yielded functions (deprecated) cannot have funcargs" --- a/testing/pytest/test_conftesthandle.py +++ b/testing/pytest/test_conftesthandle.py @@ -48,12 +48,6 @@ class TestConftestValueAccessGlobal: conftest.getconftestmodules(basedir.join('b')) assert len(conftest._path2confmods) == snap1 + 2 - def test_default_Module_setting_is_visible_always(self, basedir, testdir): - basedir.copy(testdir.tmpdir) - config = testdir.Config() - colclass = config._getcollectclass("Module", testdir.tmpdir) - assert colclass == py.test.collect.Module - def test_default_has_lower_prio(self, basedir): conftest = ConftestWithSetinitial(basedir.join("adir")) assert conftest.rget('Directory') == 3 --- a/py/impl/test/config.py +++ b/py/impl/test/config.py @@ -156,16 +156,20 @@ class Config(object): pkgpath = path.pypkgpath() if pkgpath is None: pkgpath = path.check(file=1) and path.dirpath() or path - Dir = self._getcollectclass("Directory", pkgpath) - col = Dir(pkgpath) - col.config = self + tmpcol = py.test.collect.Directory(pkgpath, config=self) + col = tmpcol.ihook.pytest_collect_directory(path=pkgpath, parent=tmpcol) + col.parent = None return col._getfsnode(path) def _getcollectclass(self, name, path): try: - return self.getvalue(name, path) + cls = self.getvalue(name, path) except KeyError: return getattr(py.test.collect, name) + else: + py.log._apiwarn(">1.1", "%r was found in a conftest.py file, " + "use pytest_collect hooks instead." % (cls,)) + return cls def getconftest_pathlist(self, name, path=None): """ return a matching value, which needs to be sequence --- a/testing/plugin/test_pytest_resultlog.py +++ b/testing/plugin/test_pytest_resultlog.py @@ -3,8 +3,9 @@ import os from py.plugin.pytest_resultlog import generic_path, ResultLog from py.impl.test.collect import Node, Item, FSCollector -def test_generic_path(): - p1 = Node('a') +def test_generic_path(testdir): + config = testdir.Config() + p1 = Node('a', config=config) assert p1.fspath is None p2 = Node('B', parent=p1) p3 = Node('()', parent = p2) @@ -13,7 +14,7 @@ def test_generic_path(): res = generic_path(item) assert res == 'a.B().c' - p0 = FSCollector('proj/test') + p0 = FSCollector('proj/test', config=config) p1 = FSCollector('proj/test/a', parent=p0) p2 = Node('B', parent=p1) p3 = Node('()', parent = p2) From commits-noreply at bitbucket.org Wed Dec 30 16:28:01 2009 From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org) Date: Wed, 30 Dec 2009 15:28:01 +0000 (UTC) Subject: [py-svn] py-trunk commit 851eded2b0e0: refine deprecations, move some over to test_deprecated_api Message-ID: <20091230152801.981357EEDE@bitbucket.org> # HG changeset patch -- Bitbucket.org # Project py-trunk # URL http://bitbucket.org/hpk42/py-trunk/overview/ # User holger krekel # Date 1262178440 -3600 # Node ID 851eded2b0e078a64e3e9f850181ae7a7016e435 # Parent ed40b4eda543c3e88efd7de7555fb0ebe36cd77d refine deprecations, move some over to test_deprecated_api --- a/testing/pytest/test_deprecated_api.py +++ b/testing/pytest/test_deprecated_api.py @@ -210,3 +210,60 @@ class TestDisabled: def test_classlevel2(self): pass """) reprec.assertoutcome(skipped=2) + + +def test_config_cmdline_options(recwarn, testdir): + testdir.makepyfile(conftest=""" + import py + def _callback(option, opt_str, value, parser, *args, **kwargs): + option.tdest = True + Option = py.test.config.Option + option = py.test.config.addoptions("testing group", + Option('-G', '--glong', action="store", default=42, + type="int", dest="gdest", help="g value."), + # XXX note: special case, option without a destination + Option('-T', '--tlong', action="callback", callback=_callback, + help='t value'), + ) + """) + recwarn.clear() + config = testdir.reparseconfig(['-G', '17']) + recwarn.pop(DeprecationWarning) + assert config.option.gdest == 17 + +def test_dist_conftest_options(testdir): + p1 = testdir.tmpdir.ensure("dir", 'p1.py') + p1.dirpath("__init__.py").write("") + p1.dirpath("conftest.py").write(py.code.Source(""" + import py + from py.builtin import print_ + print_("importing conftest", __file__) + Option = py.test.config.Option + option = py.test.config.addoptions("someopt", + Option('--someopt', action="store_true", + dest="someopt", default=False)) + dist_rsync_roots = ['../dir'] + print_("added options", option) + print_("config file seen from conftest", py.test.config) + """)) + p1.write(py.code.Source(""" + import py + from %s import conftest + from py.builtin import print_ + def test_1(): + print_("config from test_1", py.test.config) + print_("conftest from test_1", conftest.__file__) + print_("test_1: py.test.config.option.someopt", py.test.config.option.someopt) + print_("test_1: conftest", conftest) + print_("test_1: conftest.option.someopt", conftest.option.someopt) + assert conftest.option.someopt + """ % p1.dirpath().purebasename )) + result = testdir.runpytest('-d', '--tx=popen', p1, '--someopt') + assert result.ret == 0 + result.stderr.fnmatch_lines([ + "*Deprecation*pytest_addoptions*", + ]) + result.stdout.fnmatch_lines([ + "*1 passed*", + ]) + --- a/py/impl/test/config.py +++ b/py/impl/test/config.py @@ -188,7 +188,7 @@ class Config(object): """ add a named group of options to the current testing session. This function gets invoked during testing session initialization. """ - py.log._apiwarn("1.0", "define plugins to add options", stacklevel=2) + py.log._apiwarn("1.0", "define pytest_addoptions(parser) to add options", stacklevel=2) group = self._parser.getgroup(groupname) for opt in specs: group._addoption_instance(opt) --- a/testing/pytest/test_config.py +++ b/testing/pytest/test_config.py @@ -2,23 +2,6 @@ import py class TestConfigCmdlineParsing: - def test_config_cmdline_options(self, testdir): - testdir.makepyfile(conftest=""" - import py - def _callback(option, opt_str, value, parser, *args, **kwargs): - option.tdest = True - Option = py.test.config.Option - option = py.test.config.addoptions("testing group", - Option('-G', '--glong', action="store", default=42, - type="int", dest="gdest", help="g value."), - # XXX note: special case, option without a destination - Option('-T', '--tlong', action="callback", callback=_callback, - help='t value'), - ) - """) - config = testdir.reparseconfig(['-G', '17']) - assert config.option.gdest == 17 - def test_parser_addoption_default_env(self, testdir, monkeypatch): import os config = testdir.Config() --- a/py/impl/log/warning.py +++ b/py/impl/log/warning.py @@ -1,6 +1,6 @@ import py, sys -class Warning(DeprecationWarning): +class DeprecationWarning(DeprecationWarning): def __init__(self, msg, path, lineno): self.msg = msg self.path = path @@ -66,7 +66,7 @@ def warn(msg, stacklevel=1, function=Non if not filename: filename = module path = py.path.local(filename) - warning = Warning(msg, path, lineno) + warning = DeprecationWarning(msg, path, lineno) py.std.warnings.warn_explicit(warning, category=Warning, filename=str(warning.path), lineno=warning.lineno, --- a/testing/pytest/dist/acceptance_test.py +++ b/testing/pytest/dist/acceptance_test.py @@ -1,39 +1,6 @@ import py class TestDistribution: - def test_dist_conftest_options(self, testdir): - p1 = testdir.tmpdir.ensure("dir", 'p1.py') - p1.dirpath("__init__.py").write("") - p1.dirpath("conftest.py").write(py.code.Source(""" - import py - from py.builtin import print_ - print_("importing conftest", __file__) - Option = py.test.config.Option - option = py.test.config.addoptions("someopt", - Option('--someopt', action="store_true", - dest="someopt", default=False)) - dist_rsync_roots = ['../dir'] - print_("added options", option) - print_("config file seen from conftest", py.test.config) - """)) - p1.write(py.code.Source(""" - import py - from %s import conftest - from py.builtin import print_ - def test_1(): - print_("config from test_1", py.test.config) - print_("conftest from test_1", conftest.__file__) - print_("test_1: py.test.config.option.someopt", py.test.config.option.someopt) - print_("test_1: conftest", conftest) - print_("test_1: conftest.option.someopt", conftest.option.someopt) - assert conftest.option.someopt - """ % p1.dirpath().purebasename )) - result = testdir.runpytest('-d', '--tx=popen', p1, '--someopt') - assert result.ret == 0 - extra = result.stdout.fnmatch_lines([ - "*1 passed*", - ]) - def test_manytests_to_one_popen(self, testdir): p1 = testdir.makepyfile(""" import py From commits-noreply at bitbucket.org Wed Dec 30 17:09:10 2009 From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org) Date: Wed, 30 Dec 2009 16:09:10 +0000 (UTC) Subject: [py-svn] py-trunk commit 9e18a1a2a67a: a first go at testing schmir's generate_standalone_pytest script and Message-ID: <20091230160910.66B297EF0D@bitbucket.org> # HG changeset patch -- Bitbucket.org # Project py-trunk # URL http://bitbucket.org/hpk42/py-trunk/overview/ # User holger krekel # Date 1262189325 -3600 # Node ID 9e18a1a2a67a215f5f9ffef2679be08c7061d7c2 # Parent 06dfbfa6fb8df272db923d742dc06e5ab6a97474 a first go at testing schmir's generate_standalone_pytest script and making it work on python2.4-3.1 + jython. --- /dev/null +++ b/bin-for-dist/test_generate_standalone.py @@ -0,0 +1,18 @@ +import py, os +import generate_standalone_pytest +import subprocess +mydir = py.path.local(__file__).dirpath() + +def test_gen(testdir, anypython): + testdir.chdir() + infile = mydir.join("py.test-in") + outfile = testdir.tmpdir.join("mypytest") + generate_standalone_pytest.main(pydir=os.path.dirname(py.__file__), + infile=infile, outfile=outfile) + result = testdir._run(anypython, outfile, '-h') + assert result.ret == 0 + result = testdir._run(anypython, outfile, '--version') + assert result.ret == 0 + result.stderr.fnmatch_lines([ + "*imported from*mypytest" + ]) --- a/conftest.py +++ b/conftest.py @@ -63,3 +63,47 @@ def pytest_generate_tests(metafunc): for name, l in multi.kwargs.items(): for val in l: metafunc.addcall(funcargs={name: val}) + +# XXX copied from execnet's conftest.py - needs to be merged +winpymap = { + 'python2.7': r'C:\Python27\python.exe', + 'python2.6': r'C:\Python26\python.exe', + 'python2.5': r'C:\Python25\python.exe', + 'python2.4': r'C:\Python24\python.exe', + 'python3.1': r'C:\Python31\python.exe', +} + +def pytest_generate_tests(metafunc): + if 'anypython' in metafunc.funcargnames: + for name in ('python2.4', 'python2.5', 'python2.6', + 'python2.7', 'python3.1', 'pypy-c', 'jython'): + metafunc.addcall(id=name, param=name) + +def getexecutable(name, cache={}): + try: + return cache[name] + except KeyError: + executable = py.path.local.sysfind(name) + if executable: + if name == "jython": + import subprocess + popen = subprocess.Popen([str(executable), "--version"], + universal_newlines=True, stderr=subprocess.PIPE) + out, err = popen.communicate() + if not err or "2.5" not in err: + executable = None + cache[name] = executable + return executable + +def pytest_funcarg__anypython(request): + name = request.param + executable = getexecutable(name) + if executable is None: + if sys.platform == "win32": + executable = winpymap.get(name, None) + if executable: + executable = py.path.local(executable) + if executable.check(): + return executable + py.test.skip("no %s found" % (name,)) + return executable --- a/bin-for-dist/generate_standalone_pytest.py +++ b/bin-for-dist/generate_standalone_pytest.py @@ -6,13 +6,10 @@ import zlib import base64 import sys -def main(): - here = os.path.dirname(os.path.abspath(__file__)) - outfile = os.path.join(here, "py.test") - infile = outfile+"-in" - - os.chdir(os.path.dirname(here)) - +def main(pydir, outfile, infile): + os.chdir(os.path.dirname(str(pydir))) + outfile = str(outfile) + infile = str(infile) files = [] for dirpath, dirnames, filenames in os.walk("py"): for f in filenames: @@ -39,4 +36,8 @@ def main(): sys.stdout.write("generated %s\n" % outfile) if __name__=="__main__": - main() + dn = os.path.dirname + pydir = os.path.join(dn(dn(os.path.abspath(__file__))), 'py') + outfile = os.path.join(dn(__file__), "py.test") + infile = outfile+"-in" + main(pydir, outfile, infile) --- a/bin-for-dist/py.test-in +++ b/bin-for-dist/py.test-in @@ -4,12 +4,19 @@ sources = """ @SOURCES@""" import sys -import cPickle import base64 import zlib import imp -sources = cPickle.loads(zlib.decompress(base64.decodestring(sources))) +if sys.version_info >= (3,0): + exec("def do_exec(co, loc): exec(co, loc)\n") + import pickle + sources = sources.encode("ascii") # ensure bytes +else: + import cPickle as pickle + exec("def do_exec(co, loc): exec co in loc\n") + +sources = pickle.loads(zlib.decompress(base64.decodestring(sources))) class DictImporter(object): sources = sources @@ -22,8 +29,7 @@ class DictImporter(object): def load_module(self, fullname): # print "load_module:", fullname - import new - + from types import ModuleType try: s = self.sources[fullname] is_pkg = False @@ -32,13 +38,13 @@ class DictImporter(object): is_pkg = True co = compile(s, fullname, 'exec') - module = sys.modules.setdefault(fullname, new.module(fullname)) + module = sys.modules.setdefault(fullname, ModuleType(fullname)) module.__file__ = "%s/%s" % (__file__, fullname) module.__loader__ = self if is_pkg: module.__path__ = [fullname] - exec co in module.__dict__ + do_exec(co, module.__dict__) return sys.modules[fullname] importer = DictImporter() From commits-noreply at bitbucket.org Wed Dec 30 17:17:59 2009 From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org) Date: Wed, 30 Dec 2009 16:17:59 +0000 (UTC) Subject: [py-svn] py-trunk commit 528f14456c8c: fix accidentally broken pylib's own conftest.py Message-ID: <20091230161759.3ED5E7EF08@bitbucket.org> # HG changeset patch -- Bitbucket.org # Project py-trunk # URL http://bitbucket.org/hpk42/py-trunk/overview/ # User holger krekel # Date 1262189508 -3600 # Node ID 528f14456c8c6feeb5cd13afbc9bb51b499e9111 # Parent 9e18a1a2a67a215f5f9ffef2679be08c7061d7c2 fix accidentally broken pylib's own conftest.py --- a/conftest.py +++ b/conftest.py @@ -1,4 +1,5 @@ import py +import sys pytest_plugins = '_pytest doctest pytester'.split() @@ -57,12 +58,15 @@ def getsocketspec(config): def pytest_generate_tests(metafunc): multi = getattr(metafunc.function, 'multi', None) - if multi is None: - return - assert len(multi.kwargs) == 1 - for name, l in multi.kwargs.items(): - for val in l: - metafunc.addcall(funcargs={name: val}) + if multi is not None: + assert len(multi.kwargs) == 1 + for name, l in multi.kwargs.items(): + for val in l: + metafunc.addcall(funcargs={name: val}) + elif 'anypython' in metafunc.funcargnames: + for name in ('python2.4', 'python2.5', 'python2.6', + 'python2.7', 'python3.1', 'pypy-c', 'jython'): + metafunc.addcall(id=name, param=name) # XXX copied from execnet's conftest.py - needs to be merged winpymap = { @@ -73,12 +77,6 @@ winpymap = { 'python3.1': r'C:\Python31\python.exe', } -def pytest_generate_tests(metafunc): - if 'anypython' in metafunc.funcargnames: - for name in ('python2.4', 'python2.5', 'python2.6', - 'python2.7', 'python3.1', 'pypy-c', 'jython'): - metafunc.addcall(id=name, param=name) - def getexecutable(name, cache={}): try: return cache[name] From commits-noreply at bitbucket.org Wed Dec 30 17:41:06 2009 From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org) Date: Wed, 30 Dec 2009 16:41:06 +0000 (UTC) Subject: [py-svn] py-trunk commit f01f6a7caab9: refine tests to cache single-script and make standalone work with distributed testing. Message-ID: <20091230164106.022187EF0E@bitbucket.org> # HG changeset patch -- Bitbucket.org # Project py-trunk # URL http://bitbucket.org/hpk42/py-trunk/overview/ # User holger krekel # Date 1262191250 -3600 # Node ID f01f6a7caab924d5800e21acd9911ea0f44f9b6b # Parent 528f14456c8c6feeb5cd13afbc9bb51b499e9111 refine tests to cache single-script and make standalone work with distributed testing. --- a/bin-for-dist/test_generate_standalone.py +++ b/bin-for-dist/test_generate_standalone.py @@ -1,18 +1,39 @@ -import py, os +import py, os, sys import generate_standalone_pytest import subprocess mydir = py.path.local(__file__).dirpath() -def test_gen(testdir, anypython): - testdir.chdir() - infile = mydir.join("py.test-in") - outfile = testdir.tmpdir.join("mypytest") - generate_standalone_pytest.main(pydir=os.path.dirname(py.__file__), - infile=infile, outfile=outfile) - result = testdir._run(anypython, outfile, '-h') +def pytest_funcarg__standalone(request): + return request.cached_setup(scope="module", setup=lambda: Standalone(request)) + +class Standalone: + def __init__(self, request): + self.testdir = request.getfuncargvalue("testdir") + infile = mydir.join("py.test-in") + self.script = self.testdir.tmpdir.join("mypytest") + generate_standalone_pytest.main(pydir=os.path.dirname(py.__file__), + infile=infile, outfile=self.script) + + def run(self, anypython, testdir, *args): + testdir.chdir() + return testdir._run(anypython, self.script, *args) + +def test_gen(testdir, anypython, standalone): + result = standalone.run(anypython, testdir, '-h') assert result.ret == 0 - result = testdir._run(anypython, outfile, '--version') + result = standalone.run(anypython, testdir, '--version') assert result.ret == 0 result.stderr.fnmatch_lines([ "*imported from*mypytest" ]) + +def test_rundist(testdir, standalone): + testdir.makepyfile(""" + def test_one(): + pass + """) + result = standalone.run(sys.executable, testdir, '-n', '3') + assert result.ret == 0 + result.fnmatch_lines([ + "*1 passed*" + ]) --- a/py/impl/test/config.py +++ b/py/impl/test/config.py @@ -266,7 +266,10 @@ class Config(object): if not root.check(): raise config.Error("rsyncdir doesn't exist: %r" %(root,)) if pydirs is not None and root.basename in ("py", "_py"): - pydirs.remove(root) # otherwise it's a conflict + try: + pydirs.remove(root) # otherwise it's a conflict + except ValueError: # we run as standalone py.test + pass roots.extend(pydirs) return roots From commits-noreply at bitbucket.org Wed Dec 30 18:11:50 2009 From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org) Date: Wed, 30 Dec 2009 17:11:50 +0000 (UTC) Subject: [py-svn] py-trunk commit 27f764f5693b: fix some standalone-script running issues: Message-ID: <20091230171150.943AB7EF08@bitbucket.org> # HG changeset patch -- Bitbucket.org # Project py-trunk # URL http://bitbucket.org/hpk42/py-trunk/overview/ # User holger krekel # Date 1262193060 -3600 # Node ID 27f764f5693b9e7e3dfc9180c3598cf29adfc58e # Parent f01f6a7caab924d5800e21acd9911ea0f44f9b6b fix some standalone-script running issues: * standalone can run standalone tests * exception handling is more careful with assuming valid filenames * bits here and there --- a/bin-for-dist/test_generate_standalone.py +++ b/bin-for-dist/test_generate_standalone.py @@ -2,6 +2,8 @@ import py, os, sys import generate_standalone_pytest import subprocess mydir = py.path.local(__file__).dirpath() +pybasedir = mydir.join("..") +assert pybasedir.join("py").check() def pytest_funcarg__standalone(request): return request.cached_setup(scope="module", setup=lambda: Standalone(request)) @@ -11,7 +13,7 @@ class Standalone: self.testdir = request.getfuncargvalue("testdir") infile = mydir.join("py.test-in") self.script = self.testdir.tmpdir.join("mypytest") - generate_standalone_pytest.main(pydir=os.path.dirname(py.__file__), + generate_standalone_pytest.main(pybasedir=pybasedir, infile=infile, outfile=self.script) def run(self, anypython, testdir, *args): @@ -34,6 +36,6 @@ def test_rundist(testdir, standalone): """) result = standalone.run(sys.executable, testdir, '-n', '3') assert result.ret == 0 - result.fnmatch_lines([ + result.stdout.fnmatch_lines([ "*1 passed*" ]) --- a/py/impl/code/code.py +++ b/py/impl/code/code.py @@ -537,8 +537,9 @@ class FormattedExcinfo(object): else: if self.style == "short": line = source[line_index].lstrip() + trybasename = getattr(entry.path, 'basename', entry.path) lines.append(' File "%s", line %d, in %s' % ( - entry.path.basename, entry.lineno+1, entry.name)) + trybasename, entry.lineno+1, entry.name)) lines.append(" " + line) if excinfo: lines.extend(self.get_exconly(excinfo, indent=4)) --- a/testing/plugin/test_pytest_doctest.py +++ b/testing/plugin/test_pytest_doctest.py @@ -1,5 +1,7 @@ from py.plugin.pytest_doctest import DoctestModule, DoctestTextfile +pytest_plugins = ["pytest_doctest"] + class TestDoctests: def test_collect_testtextfile(self, testdir): @@ -12,14 +14,15 @@ class TestDoctests: """) for x in (testdir.tmpdir, checkfile): #print "checking that %s returns custom items" % (x,) - items, reprec = testdir.inline_genitems(x) + items, reprec = testdir.inline_genitems(x, '-p', 'doctest') assert len(items) == 1 assert isinstance(items[0], DoctestTextfile) def test_collect_module(self, testdir): path = testdir.makepyfile(whatever="#") for p in (path, testdir.tmpdir): - items, reprec = testdir.inline_genitems(p, '--doctest-modules') + items, reprec = testdir.inline_genitems(p, '-p', 'doctest', + '--doctest-modules') assert len(items) == 1 assert isinstance(items[0], DoctestModule) @@ -29,7 +32,7 @@ class TestDoctests: >>> x == 1 False """) - reprec = testdir.inline_run(p) + reprec = testdir.inline_run(p, '-p', 'doctest') reprec.assertoutcome(failed=1) def test_doctest_unexpected_exception(self, testdir): @@ -41,7 +44,7 @@ class TestDoctests: >>> x 2 """) - reprec = testdir.inline_run(p) + reprec = testdir.inline_run(p, '-p', 'doctest') call = reprec.getcall("pytest_runtest_logreport") assert call.report.failed assert call.report.longrepr @@ -60,7 +63,7 @@ class TestDoctests: ''' """) - reprec = testdir.inline_run(p, "--doctest-modules") + reprec = testdir.inline_run(p, '-p', 'doctest', "--doctest-modules") reprec.assertoutcome(failed=1) def test_doctestmodule_external(self, testdir): @@ -73,7 +76,7 @@ class TestDoctests: 2 ''' """) - result = testdir.runpytest(p, "--doctest-modules") + result = testdir.runpytest(p, '-p', 'doctest', "--doctest-modules") result.stdout.fnmatch_lines([ '004 *>>> i = 0', '005 *>>> i + 1', @@ -91,7 +94,7 @@ class TestDoctests: >>> i + 1 2 """) - result = testdir.runpytest(p) + result = testdir.runpytest(p, '-p', 'doctest') result.stdout.fnmatch_lines([ '001 >>> i = 0', '002 >>> i + 1', --- a/bin-for-dist/generate_standalone_pytest.py +++ b/bin-for-dist/generate_standalone_pytest.py @@ -6,8 +6,8 @@ import zlib import base64 import sys -def main(pydir, outfile, infile): - os.chdir(os.path.dirname(str(pydir))) +def main(pybasedir, outfile, infile): + os.chdir(str(pybasedir)) outfile = str(outfile) infile = str(infile) files = [] @@ -37,7 +37,7 @@ def main(pydir, outfile, infile): if __name__=="__main__": dn = os.path.dirname - pydir = os.path.join(dn(dn(os.path.abspath(__file__))), 'py') + pybasedir = dn(dn(os.path.abspath(__file__))) outfile = os.path.join(dn(__file__), "py.test") infile = outfile+"-in" - main(pydir, outfile, infile) + main(pybasedir, outfile, infile) --- a/testing/plugin/test_pytest_helpconfig.py +++ b/testing/plugin/test_pytest_helpconfig.py @@ -5,9 +5,9 @@ def test_version(testdir): assert py.version == py.__version__ result = testdir.runpytest("--version") assert result.ret == 0 - p = py.path.local(py.__file__).dirpath() + #p = py.path.local(py.__file__).dirpath() assert result.stderr.fnmatch_lines([ - '*py.test*%s*imported from*%s*' % (py.version, p) + '*py.test*%s*imported from*' % (py.version, ) ]) def test_helpconfig(testdir): From commits-noreply at bitbucket.org Wed Dec 30 19:05:46 2009 From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org) Date: Wed, 30 Dec 2009 18:05:46 +0000 (UTC) Subject: [py-svn] py-trunk commit 5a401029c1d4: make inspect.getsource work for standalone py.test by implementing a get_source method on our DictImporter. Message-ID: <20091230180546.6CA6F7EF08@bitbucket.org> # HG changeset patch -- Bitbucket.org # Project py-trunk # URL http://bitbucket.org/hpk42/py-trunk/overview/ # User Ralf Schmitt # Date 1262196106 -3600 # Node ID 5a401029c1d42ceecbae097e2692284913145a4e # Parent 27f764f5693b9e7e3dfc9180c3598cf29adfc58e make inspect.getsource work for standalone py.test by implementing a get_source method on our DictImporter. --- a/bin-for-dist/py.test-in +++ b/bin-for-dist/py.test-in @@ -47,6 +47,14 @@ class DictImporter(object): do_exec(co, module.__dict__) return sys.modules[fullname] + def get_source(self, name): + res = self.sources.get(name) + if res is None: + res = self.sources.get(name+'.__init__') + return res + + + importer = DictImporter() sys.meta_path.append(importer) --- a/py/apipkg.py +++ b/py/apipkg.py @@ -17,6 +17,7 @@ def initpkg(pkgname, exportdefs): mod.__file__ = getattr(oldmod, '__file__', None) mod.__version__ = getattr(oldmod, '__version__', None) mod.__path__ = getattr(oldmod, '__path__', None) + mod.__loader__ = getattr(oldmod, '__loader__', None) sys.modules[pkgname] = mod def importobj(modpath, attrname): From commits-noreply at bitbucket.org Wed Dec 30 19:11:07 2009 From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org) Date: Wed, 30 Dec 2009 18:11:07 +0000 (UTC) Subject: [py-svn] py-trunk commit 9f60daedd927: move standalone script to become a plugin offering "--genscript", Message-ID: <20091230181107.7DC7D7EF0D@bitbucket.org> # HG changeset patch -- Bitbucket.org # Project py-trunk # URL http://bitbucket.org/hpk42/py-trunk/overview/ # User holger krekel # Date 1262196649 -3600 # Node ID 9f60daedd927d002f7c71096df4dd140c6123078 # Parent 5a401029c1d42ceecbae097e2692284913145a4e move standalone script to become a plugin offering "--genscript", adjust paths accordingly and add CHANGELOG entry. --- a/CHANGELOG +++ b/CHANGELOG @@ -1,6 +1,9 @@ Changes between 1.X and 1.1.1 ===================================== +- new option: --genscript=path will generate a standalone py.test script + which will not need any libraries installed. thanks to Ralf Schmitt. + - new option: --ignore will prevent specified path from collection. Can be specified multiple times. --- /dev/null +++ b/testing/plugin/test_pytest_genscript.py @@ -0,0 +1,35 @@ +import py, os, sys +import subprocess + +def pytest_funcarg__standalone(request): + return request.cached_setup(scope="module", setup=lambda: Standalone(request)) + +class Standalone: + def __init__(self, request): + self.testdir = request.getfuncargvalue("testdir") + self.script = self.testdir.tmpdir.join("mypytest") + self.testdir.runpytest("--genscript=%s" % self.script) + + def run(self, anypython, testdir, *args): + testdir.chdir() + return testdir._run(anypython, self.script, *args) + +def test_gen(testdir, anypython, standalone): + result = standalone.run(anypython, testdir, '-h') + assert result.ret == 0 + result = standalone.run(anypython, testdir, '--version') + assert result.ret == 0 + result.stderr.fnmatch_lines([ + "*imported from*mypytest" + ]) + +def test_rundist(testdir, standalone): + testdir.makepyfile(""" + def test_one(): + pass + """) + result = standalone.run(sys.executable, testdir, '-n', '3') + assert result.ret == 0 + result.stdout.fnmatch_lines([ + "*1 passed*" + ]) --- a/pytest_xmlresult.py +++ b/pytest_xmlresult.py @@ -8,7 +8,7 @@ import time def pytest_addoption(parser): - group = parser.addgroup("xmlresult", "xmlresult plugin options") + group = parser.getgroup("xmlresult", "xmlresult plugin options") group.addoption('--xmlresult', action="store", dest="xmlresult", metavar="path", default=None, help="path for machine-readable xml result log.") --- a/bin-for-dist/py.test-in +++ /dev/null @@ -1,64 +0,0 @@ -#! /usr/bin/env python - -sources = """ - at SOURCES@""" - -import sys -import base64 -import zlib -import imp - -if sys.version_info >= (3,0): - exec("def do_exec(co, loc): exec(co, loc)\n") - import pickle - sources = sources.encode("ascii") # ensure bytes -else: - import cPickle as pickle - exec("def do_exec(co, loc): exec co in loc\n") - -sources = pickle.loads(zlib.decompress(base64.decodestring(sources))) - -class DictImporter(object): - sources = sources - def find_module(self, fullname, path=None): - if fullname in self.sources: - return self - if fullname+'.__init__' in self.sources: - return self - return None - - def load_module(self, fullname): - # print "load_module:", fullname - from types import ModuleType - try: - s = self.sources[fullname] - is_pkg = False - except KeyError: - s = self.sources[fullname+'.__init__'] - is_pkg = True - - co = compile(s, fullname, 'exec') - module = sys.modules.setdefault(fullname, ModuleType(fullname)) - module.__file__ = "%s/%s" % (__file__, fullname) - module.__loader__ = self - if is_pkg: - module.__path__ = [fullname] - - do_exec(co, module.__dict__) - return sys.modules[fullname] - - def get_source(self, name): - res = self.sources.get(name) - if res is None: - res = self.sources.get(name+'.__init__') - return res - - - -importer = DictImporter() - -sys.meta_path.append(importer) - -if __name__ == "__main__": - import py - py.cmdline.pytest() --- /dev/null +++ b/py/plugin/pytest_genscript.py @@ -0,0 +1,63 @@ +#! /usr/bin/env python + +import os +import zlib +import base64 +import sys +try: + import pickle +except Importerror: + import cPickle as pickle + +def pytest_addoption(parser): + group = parser.getgroup("general") + group.addoption("--genscript", action="store", default=None, + dest="genscript", metavar="path", + help="create standalone py.test script at given target path.") + +def pytest_configure(config): + genscript = config.getvalue("genscript") + if genscript: + import py + mydir = py.path.local(__file__).dirpath() + infile = mydir.join("standalonetemplate.py") + pybasedir = py.path.local(py.__file__).dirpath().dirpath() + main(pybasedir, outfile=genscript, infile=infile) + raise SystemExit(0) + +def main(pybasedir, outfile, infile): + os.chdir(str(pybasedir)) + outfile = str(outfile) + infile = str(infile) + files = [] + for dirpath, dirnames, filenames in os.walk("py"): + for f in filenames: + if not f.endswith(".py"): + continue + + fn = os.path.join(dirpath, f) + files.append(fn) + + name2src = {} + for f in files: + k = f.replace("/", ".")[:-3] + name2src[k] = open(f, "rb").read() + + data = pickle.dumps(name2src, 2) + data = zlib.compress(data, 9) + data = base64.encodestring(data) + + exe = open(infile, "rb").read() + exe = exe.replace("@SOURCES@", data) + + open(outfile, "wb").write(exe) + os.chmod(outfile, 493) # 0755 + sys.stdout.write("generated standalone py.test at %r, have fun!\n" % outfile) + +if __name__=="__main__": + dn = os.path.dirname + here = os.path.abspath(dn(__file__)) # py/plugin/ + pybasedir = dn(dn(here)) + outfile = os.path.join(os.getcwd(), "py.test-standalone") + infile = os.path.join(here, 'standalonetemplate.py') + main(pybasedir, outfile, infile) --- /dev/null +++ b/py/plugin/standalonetemplate.py @@ -0,0 +1,64 @@ +#! /usr/bin/env python + +sources = """ + at SOURCES@""" + +import sys +import base64 +import zlib +import imp + +if sys.version_info >= (3,0): + exec("def do_exec(co, loc): exec(co, loc)\n") + import pickle + sources = sources.encode("ascii") # ensure bytes + sources = pickle.loads(zlib.decompress(base64.decodebytes(sources))) +else: + import cPickle as pickle + exec("def do_exec(co, loc): exec co in loc\n") + sources = pickle.loads(zlib.decompress(base64.decodestring(sources))) + +class DictImporter(object): + sources = sources + def find_module(self, fullname, path=None): + if fullname in self.sources: + return self + if fullname+'.__init__' in self.sources: + return self + return None + + def load_module(self, fullname): + # print "load_module:", fullname + from types import ModuleType + try: + s = self.sources[fullname] + is_pkg = False + except KeyError: + s = self.sources[fullname+'.__init__'] + is_pkg = True + + co = compile(s, fullname, 'exec') + module = sys.modules.setdefault(fullname, ModuleType(fullname)) + module.__file__ = "%s/%s" % (__file__, fullname) + module.__loader__ = self + if is_pkg: + module.__path__ = [fullname] + + do_exec(co, module.__dict__) + return sys.modules[fullname] + + def get_source(self, name): + res = self.sources.get(name) + if res is None: + res = self.sources.get(name+'.__init__') + return res + + + +importer = DictImporter() + +sys.meta_path.append(importer) + +if __name__ == "__main__": + import py + py.cmdline.pytest() --- a/bin-for-dist/test_generate_standalone.py +++ /dev/null @@ -1,41 +0,0 @@ -import py, os, sys -import generate_standalone_pytest -import subprocess -mydir = py.path.local(__file__).dirpath() -pybasedir = mydir.join("..") -assert pybasedir.join("py").check() - -def pytest_funcarg__standalone(request): - return request.cached_setup(scope="module", setup=lambda: Standalone(request)) - -class Standalone: - def __init__(self, request): - self.testdir = request.getfuncargvalue("testdir") - infile = mydir.join("py.test-in") - self.script = self.testdir.tmpdir.join("mypytest") - generate_standalone_pytest.main(pybasedir=pybasedir, - infile=infile, outfile=self.script) - - def run(self, anypython, testdir, *args): - testdir.chdir() - return testdir._run(anypython, self.script, *args) - -def test_gen(testdir, anypython, standalone): - result = standalone.run(anypython, testdir, '-h') - assert result.ret == 0 - result = standalone.run(anypython, testdir, '--version') - assert result.ret == 0 - result.stderr.fnmatch_lines([ - "*imported from*mypytest" - ]) - -def test_rundist(testdir, standalone): - testdir.makepyfile(""" - def test_one(): - pass - """) - result = standalone.run(sys.executable, testdir, '-n', '3') - assert result.ret == 0 - result.stdout.fnmatch_lines([ - "*1 passed*" - ]) --- a/py/impl/test/pluginmanager.py +++ b/py/impl/test/pluginmanager.py @@ -8,7 +8,7 @@ from py.impl.test.outcome import Skipped default_plugins = ( "default runner capture terminal mark skipping tmpdir monkeypatch " - "recwarn pdb pastebin unittest helpconfig nose assertion").split() + "recwarn pdb pastebin unittest helpconfig nose assertion genscript").split() def check_old_use(mod, modname): clsname = modname[len('pytest_'):].capitalize() + "Plugin" --- a/bin-for-dist/generate_standalone_pytest.py +++ /dev/null @@ -1,43 +0,0 @@ -#! /usr/bin/env python - -import os -import cPickle -import zlib -import base64 -import sys - -def main(pybasedir, outfile, infile): - os.chdir(str(pybasedir)) - outfile = str(outfile) - infile = str(infile) - files = [] - for dirpath, dirnames, filenames in os.walk("py"): - for f in filenames: - if not f.endswith(".py"): - continue - - fn = os.path.join(dirpath, f) - files.append(fn) - - name2src = {} - for f in files: - k = f.replace("/", ".")[:-3] - name2src[k] = open(f, "rb").read() - - data = cPickle.dumps(name2src, 2) - data = zlib.compress(data, 9) - data = base64.encodestring(data) - - exe = open(infile, "rb").read() - exe = exe.replace("@SOURCES@", data) - - open(outfile, "wb").write(exe) - os.chmod(outfile, 493) # 0755 - sys.stdout.write("generated %s\n" % outfile) - -if __name__=="__main__": - dn = os.path.dirname - pybasedir = dn(dn(os.path.abspath(__file__))) - outfile = os.path.join(dn(__file__), "py.test") - infile = outfile+"-in" - main(pybasedir, outfile, infile) From commits-noreply at bitbucket.org Thu Dec 31 11:25:47 2009 From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org) Date: Thu, 31 Dec 2009 10:25:47 +0000 (UTC) Subject: [py-svn] py-trunk commit b94b218d6202: adding a logxml plugin and a --xml=path option generating a junit-xml style result log. The xml result log can be parsed nicely by hudson. Message-ID: <20091231102547.99D497EF1B@bitbucket.org> # HG changeset patch -- Bitbucket.org # Project py-trunk # URL http://bitbucket.org/hpk42/py-trunk/overview/ # User holger krekel # Date 1262255107 -3600 # Node ID b94b218d6202bac11ebfa9fcdf961843d32460cf # Parent 9f60daedd927d002f7c71096df4dd140c6123078 adding a logxml plugin and a --xml=path option generating a junit-xml style result log. The xml result log can be parsed nicely by hudson. Initial code was based on Ross Lawley's pytest_xmlresult plugin. --- a/ISSUES.txt +++ b/ISSUES.txt @@ -78,6 +78,19 @@ but a remote one fail because the tests does not contain an "__init__.py". Either give an error or make it work without the __init__.py +introduce a "RootCollector" +---------------------------------------------------------------- +tags: feature 1.2 + +Currently the top collector is a Directory node and +there also is the notion of a "topdir". See to refine +internal handling such that there is a RootCollector +which holds this topdir (or do away with topdirs?). +Make sure this leads to an improvement in how +tests are shown in hudson which currently sometimes +shows "workspace" and sometimes not as the leading +name. + deprecate ensuretemp / introduce funcargs to setup method -------------------------------------------------------------- tags: experimental-wish 1.2 --- /dev/null +++ b/testing/plugin/test_pytest_logxml.py @@ -0,0 +1,121 @@ + +from xml.dom import minidom + +def runandparse(testdir, *args): + resultpath = testdir.tmpdir.join("junit.xml") + result = testdir.runpytest("--xml=%s" % resultpath, *args) + xmldoc = minidom.parse(str(resultpath)) + return result, xmldoc + +def assert_attr(node, **kwargs): + for name, expected in kwargs.items(): + anode = node.getAttributeNode(name) + assert anode, "node %r has no attribute %r" %(node, name) + val = anode.value + assert val == str(expected) + +class TestPython: + def test_summing_simple(self, testdir): + testdir.makepyfile(""" + import py + def test_pass(): + pass + def test_fail(): + assert 0 + def test_skip(): + py.test.skip("") + """) + result, dom = runandparse(testdir) + assert result.ret + node = dom.getElementsByTagName("testsuite")[0] + assert_attr(node, errors=0, failures=1, skips=1, tests=3) + + def test_setup_error(self, testdir): + testdir.makepyfile(""" + def pytest_funcarg__arg(request): + raise ValueError() + def test_function(arg): + pass + """) + result, dom = runandparse(testdir) + assert result.ret + node = dom.getElementsByTagName("testsuite")[0] + assert_attr(node, errors=1, tests=0) + tnode = node.getElementsByTagName("testcase")[0] + assert_attr(tnode, + classname="test_setup_error.test_setup_error", + name="test_function") + fnode = tnode.getElementsByTagName("error")[0] + assert_attr(fnode, message="test setup failure") + assert "ValueError" in fnode.toxml() + + def test_internal_error(self, testdir): + testdir.makeconftest("def pytest_runtest_protocol(): 0 / 0") + testdir.makepyfile("def test_function(): pass") + result, dom = runandparse(testdir) + assert result.ret + node = dom.getElementsByTagName("testsuite")[0] + assert_attr(node, errors=1, tests=0) + tnode = node.getElementsByTagName("testcase")[0] + assert_attr(tnode, classname="pytest", name="internal") + fnode = tnode.getElementsByTagName("error")[0] + assert_attr(fnode, message="internal error") + assert "Division" in fnode.toxml() + + def test_failure_function(self, testdir): + testdir.makepyfile("def test_fail(): raise ValueError(42)") + result, dom = runandparse(testdir) + assert result.ret + node = dom.getElementsByTagName("testsuite")[0] + assert_attr(node, failures=1, tests=1) + tnode = node.getElementsByTagName("testcase")[0] + assert_attr(tnode, + classname="test_failure_function.test_failure_function", + name="test_fail") + fnode = tnode.getElementsByTagName("failure")[0] + assert_attr(fnode, message="test failure") + assert "ValueError" in fnode.toxml() + + def test_collect_error(self, testdir): + testdir.makepyfile("syntax error") + result, dom = runandparse(testdir) + assert result.ret + node = dom.getElementsByTagName("testsuite")[0] + assert_attr(node, errors=1, tests=0) + tnode = node.getElementsByTagName("testcase")[0] + assert_attr(tnode, + #classname="test_collect_error", + name="test_collect_error") + fnode = tnode.getElementsByTagName("failure")[0] + assert_attr(fnode, message="collection failure") + assert "invalid syntax" in fnode.toxml() + +class TestNonPython: + def test_summing_simple(self, testdir): + testdir.makeconftest(""" + import py + def pytest_collect_file(path, parent): + if path.ext == ".xyz": + return MyItem(path, parent) + class MyItem(py.test.collect.Item): + def __init__(self, path, parent): + super(MyItem, self).__init__(path.basename, parent) + self.fspath = path + def runtest(self): + raise ValueError(42) + def repr_failure(self, excinfo): + return "custom item runtest failed" + """) + testdir.tmpdir.join("myfile.xyz").write("hello") + result, dom = runandparse(testdir) + assert result.ret + node = dom.getElementsByTagName("testsuite")[0] + assert_attr(node, errors=0, failures=1, skips=0, tests=1) + tnode = node.getElementsByTagName("testcase")[0] + assert_attr(tnode, + #classname="test_collect_error", + name="myfile.xyz") + fnode = tnode.getElementsByTagName("failure")[0] + assert_attr(fnode, message="test failure") + assert "custom item runtest failed" in fnode.toxml() + --- a/CHANGELOG +++ b/CHANGELOG @@ -1,6 +1,9 @@ Changes between 1.X and 1.1.1 ===================================== +- new junitxml plugin: --xml=path will generate a junit style xml file + which is parseable e.g. by the hudson continous integration server. + - new option: --genscript=path will generate a standalone py.test script which will not need any libraries installed. thanks to Ralf Schmitt. --- a/pytest_xmlresult.py +++ /dev/null @@ -1,189 +0,0 @@ -""" - xmlresult plugin for machine-readable logging of test results. - Useful for cruisecontrol integration code. - - An adaptation of pytest_resultlog.py -""" - -import time - -def pytest_addoption(parser): - group = parser.getgroup("xmlresult", "xmlresult plugin options") - group.addoption('--xmlresult', action="store", dest="xmlresult", metavar="path", default=None, - help="path for machine-readable xml result log.") - -def pytest_configure(config): - xmlresult = config.option.xmlresult - if xmlresult: - logfile = open(xmlresult, 'w', 1) # line buffered - config._xmlresult = XMLResult(logfile) - config.pluginmanager.register(config._xmlresult) - -def pytest_unconfigure(config): - xmlresult = getattr(config, '_xmlresult', None) - if xmlresult: - xmlresult.logfile.close() - del config._xmlresult - config.pluginmanager.unregister(xmlresult) - -def generic_path(item): - chain = item.listchain() - gpath = [chain[0].name] - fspath = chain[0].fspath - fspart = False - for node in chain[1:]: - newfspath = node.fspath - if newfspath == fspath: - if fspart: - gpath.append(':') - fspart = False - else: - gpath.append('.') - else: - gpath.append('/') - fspart = True - name = node.name - if name[0] in '([': - gpath.pop() - gpath.append(name) - fspath = newfspath - return ''.join(gpath) - -class XMLResult(object): - test_start_time = 0.0 - test_taken_time = 0.0 - test_count = 0 - error_count = 0 - failure_count = 0 - skip_count = 0 - - def __init__(self, logfile): - self.logfile = logfile - self.test_logs = [] - - def write_log_entry(self, testpath, shortrepr, longrepr): - self.test_count += 1 - # Create an xml log entry for the tests - self.test_logs.append('' % (testpath.split(':')[-1], testpath, self.test_taken_time)) - - # Do we have any other data to capture for Errors, Fails and Skips - if shortrepr in ['E', 'F', 'S']: - - if shortrepr == 'E': - self.error_count += 1 - elif shortrepr == 'F': - self.failure_count += 1 - elif shortrepr == 'S': - self.skip_count += 1 - - tag_map = {'E': 'error', 'F': 'failure', 'S': 'skipped'} - self.test_logs.append("<%s>" % tag_map[shortrepr]) - - # Output any more information - for line in longrepr.splitlines(): - self.test_logs.append("" % line) - self.test_logs.append("" % tag_map[shortrepr]) - self.test_logs.append("") - - def log_outcome(self, node, shortrepr, longrepr): - self.write_log_entry(node.name, shortrepr, longrepr) - - def pytest_runtest_logreport(self, report): - code = report.shortrepr - if report.passed: - longrepr = "" - code = "." - elif report.failed: - longrepr = str(report.longrepr) - code = "F" - elif report.skipped: - code = "S" - longrepr = str(report.longrepr.reprcrash.message) - self.log_outcome(report.item, code, longrepr) - - def pytest_runtest_setup(self, item): - self.test_start_time = time.time() - - def pytest_runtest_teardown(self, item): - self.test_taken_time = time.time() - self.test_start_time - - def pytest_collectreport(self, report): - if not report.passed: - if report.failed: - code = "F" - else: - assert report.skipped - code = "S" - longrepr = str(report.longrepr.reprcrash) - self.log_outcome(report.collector, code, longrepr) - - def pytest_internalerror(self, excrepr): - path = excrepr.reprcrash.path - self.errors += 1 - self.write_log_entry(path, '!', str(excrepr)) - - def pytest_sessionstart(self, session): - self.suite_start_time = time.time() - - def pytest_sessionfinish(self, session, exitstatus): - """ - Write the xml output - """ - suite_stop_time = time.time() - suite_time_delta = suite_stop_time - self.suite_start_time - self.logfile.write('') - self.logfile.writelines(self.test_logs) - self.logfile.write('') - self.logfile.close() - - -# Tests -def test_generic(testdir, LineMatcher): - testdir.plugins.append("resultlog") - testdir.makepyfile(""" - import py - def test_pass(): - pass - def test_fail(): - assert 0 - def test_skip(): - py.test.skip("") - """) - testdir.runpytest("--xmlresult=result.xml") - lines = testdir.tmpdir.join("result.xml").readlines(cr=0) - LineMatcher(lines).fnmatch_lines([ - '*testsuite errors="0" failures="1" skips="1" name="" tests="3"*' - ]) - LineMatcher(lines).fnmatch_lines([ - '**' - ]) - -def test_generic_path(): - from py.__.test.collect import Node, Item, FSCollector - p1 = Node('a') - assert p1.fspath is None - p2 = Node('B', parent=p1) - p3 = Node('()', parent = p2) - item = Item('c', parent = p3) - res = generic_path(item) - assert res == 'a.B().c' - - p0 = FSCollector('proj/test') - p1 = FSCollector('proj/test/a', parent=p0) - p2 = Node('B', parent=p1) - p3 = Node('()', parent = p2) - p4 = Node('c', parent=p3) - item = Item('[1]', parent = p4) - - res = generic_path(item) - assert res == 'test/a:B().c[1]' --- a/AUTHORS +++ b/AUTHORS @@ -11,9 +11,10 @@ merlinux GmbH, Germany, office at merlin Contributors include:: +Ross Lawley +Ralf Schmitt Chris Lamb Harald Armin Massa -Ralf Schmitt Martijn Faassen Ian Bicking Jan Balster --- /dev/null +++ b/py/plugin/pytest_logxml.py @@ -0,0 +1,147 @@ +""" + logxml plugin for machine-readable logging of test results. + Based on initial code from Ross Lawley. +""" + +import py +import time + +def pytest_addoption(parser): + group = parser.getgroup("general") + group.addoption('--xml', action="store", dest="xmlpath", + metavar="path", default=None, + help="create junit-xml style report file at the given path.") + +def pytest_configure(config): + xmlpath = config.option.xmlpath + if xmlpath: + config._xml = LogXML(xmlpath) + config.pluginmanager.register(config._xml) + +def pytest_unconfigure(config): + xml = getattr(config, '_xml', None) + if xml: + del config._xml + config.pluginmanager.unregister(xml) + +class LogXML(object): + def __init__(self, logfile): + self.logfile = logfile + self.test_logs = [] + self.passed = self.skipped = 0 + self.failed = self.errors = 0 + self._durations = {} + + def _opentestcase(self, report): + node = report.item + d = {'time': self._durations.pop(report.item, "0")} + names = [x.replace(".py", "") for x in node.listnames()] + d['classname'] = ".".join(names[:-1]) + d['name'] = names[-1] + attrs = ['%s="%s"' % item for item in sorted(d.items())] + self.test_logs.append("\n" % " ".join(attrs)) + + def _closetestcase(self): + self.test_logs.append("") + + def append_pass(self, report): + self.passed += 1 + self._opentestcase(report) + self._closetestcase() + + def append_failure(self, report): + self._opentestcase(report) + s = py.xml.escape(str(report.longrepr)) + #msg = str(report.longrepr.reprtraceback.extraline) + self.test_logs.append( + '%s' % (s)) + self._closetestcase() + self.failed += 1 + + def _opentestcase_collectfailure(self, report): + node = report.collector + d = {'time': '???'} + names = [x.replace(".py", "") for x in node.listnames()] + d['classname'] = ".".join(names[:-1]) + d['name'] = names[-1] + attrs = ['%s="%s"' % item for item in sorted(d.items())] + self.test_logs.append("\n" % " ".join(attrs)) + + def append_collect_failure(self, report): + self._opentestcase_collectfailure(report) + s = py.xml.escape(str(report.longrepr)) + #msg = str(report.longrepr.reprtraceback.extraline) + self.test_logs.append( + '%s' % (s)) + self._closetestcase() + self.errors += 1 + + def append_error(self, report): + self._opentestcase(report) + s = py.xml.escape(str(report.longrepr)) + self.test_logs.append( + '%s' % s) + self._closetestcase() + self.errors += 1 + + def append_skipped(self, report): + self._opentestcase(report) + self.test_logs.append("") + self._closetestcase() + self.skipped += 1 + + def pytest_runtest_logreport(self, report): + if report.passed: + self.append_pass(report) + elif report.failed: + if report.when != "call": + self.append_error(report) + else: + self.append_failure(report) + elif report.skipped: + self.append_skipped(report) + + def pytest_runtest_call(self, item, __multicall__): + start = time.time() + try: + return __multicall__.execute() + finally: + self._durations[item] = time.time() - start + + def pytest_collectreport(self, report): + if not report.passed: + if report.failed: + self.append_collect_failure(report) + else: + self.append_collect_skipped(report) + + def pytest_internalerror(self, excrepr): + self.errors += 1 + data = py.xml.escape(str(excrepr)) + self.test_logs.append( + '\n' + ' ' + '%s' % data) + + def pytest_sessionstart(self, session): + self.suite_start_time = time.time() + + def pytest_sessionfinish(self, session, exitstatus, __multicall__): + logfile = open(self.logfile, 'w', 1) # line buffered + suite_stop_time = time.time() + suite_time_delta = suite_stop_time - self.suite_start_time + numtests = self.passed + self.skipped + self.failed + logfile.write('') + logfile.writelines(self.test_logs) + logfile.write('') + logfile.close() + tw = session.config.pluginmanager.getplugin("terminalreporter")._tw + tw.line() + tw.sep("-", "generated xml file: %s" %(self.logfile)) --- a/py/impl/test/pluginmanager.py +++ b/py/impl/test/pluginmanager.py @@ -8,7 +8,8 @@ from py.impl.test.outcome import Skipped default_plugins = ( "default runner capture terminal mark skipping tmpdir monkeypatch " - "recwarn pdb pastebin unittest helpconfig nose assertion genscript").split() + "recwarn pdb pastebin unittest helpconfig nose assertion genscript " + "logxml").split() def check_old_use(mod, modname): clsname = modname[len('pytest_'):].capitalize() + "Plugin" From commits-noreply at bitbucket.org Thu Dec 31 11:33:37 2009 From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org) Date: Thu, 31 Dec 2009 10:33:37 +0000 (UTC) Subject: [py-svn] py-trunk commit c14662d121dd: fixing invocation Message-ID: <20091231103337.7785D7EF18@bitbucket.org> # HG changeset patch -- Bitbucket.org # Project py-trunk # URL http://bitbucket.org/hpk42/py-trunk/overview/ # User holger krekel # Date 1262255607 -3600 # Node ID c14662d121ddf1631d99e4639b3532d11c91eab9 # Parent b94b218d6202bac11ebfa9fcdf961843d32460cf fixing invocation --- a/bin-for-dist/hudson.py +++ b/bin-for-dist/hudson.py @@ -22,5 +22,5 @@ bincall("python", "setup.py", "develop", bincall("pip", "install", "-r", "testing/pip-reqs1.txt", "-q", "--download-cache=download") bincall("py.test", "--ignore", BUILDNAME, - "-p", "xmlresult", "--xmlresult=junit.xml", + "--xml=junit.xml", "--report=skipped", "--runslowtest", *sys.argv[1:]) From commits-noreply at bitbucket.org Thu Dec 31 11:50:24 2009 From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org) Date: Thu, 31 Dec 2009 10:50:24 +0000 (UTC) Subject: [py-svn] py-trunk commit dd3f0c0bc8a3: fix xml generation for skipped collections of tests Message-ID: <20091231105024.EC54D7EF18@bitbucket.org> # HG changeset patch -- Bitbucket.org # Project py-trunk # URL http://bitbucket.org/hpk42/py-trunk/overview/ # User holger krekel # Date 1262256601 -3600 # Node ID dd3f0c0bc8a35eff3b87dea5f453836fd9e4cce3 # Parent c14662d121ddf1631d99e4639b3532d11c91eab9 fix xml generation for skipped collections of tests --- a/py/plugin/pytest_logxml.py +++ b/py/plugin/pytest_logxml.py @@ -76,6 +76,15 @@ class LogXML(object): self._closetestcase() self.errors += 1 + def append_collect_skipped(self, report): + self._opentestcase_collectfailure(report) + s = py.xml.escape(str(report.longrepr)) + #msg = str(report.longrepr.reprtraceback.extraline) + self.test_logs.append( + '%s' % (s)) + self._closetestcase() + self.skipped += 1 + def append_error(self, report): self._opentestcase(report) s = py.xml.escape(str(report.longrepr)) @@ -130,7 +139,7 @@ class LogXML(object): logfile = open(self.logfile, 'w', 1) # line buffered suite_stop_time = time.time() suite_time_delta = suite_stop_time - self.suite_start_time - numtests = self.passed + self.skipped + self.failed + numtests = self.passed + self.failed logfile.write(' # HG changeset patch -- Bitbucket.org # Project py-trunk # URL http://bitbucket.org/hpk42/py-trunk/overview/ # User holger krekel # Date 1262268632 -3600 # Node ID 2f97b1b6a2cc27197238679b991a8e6a289f65b1 # Parent dd3f0c0bc8a35eff3b87dea5f453836fd9e4cce3 introduce --confcutdir option to early-inhibit lookup of conftest files above a certain directory. --- a/py/plugin/pytest_default.py +++ b/py/plugin/pytest_default.py @@ -73,7 +73,9 @@ def pytest_addoption(parser): "test process debugging and configuration") group.addoption('--basetemp', dest="basetemp", default=None, metavar="dir", help="base temporary directory for this test run.") - + group.addoption('--confcutdir', dest="confcutdir", default=None, + metavar="dir", + help="only load conftest.py's relative to specified dir.") if execnet: add_dist_options(parser) else: --- a/testing/pytest/test_conftesthandle.py +++ b/testing/pytest/test_conftesthandle.py @@ -89,3 +89,46 @@ class TestConftestValueAccessGlobal: path = py.path.local(mod.__file__) assert path.dirpath() == basedir.join("adir", "b") assert path.purebasename == "conftest" + +def test_conftestcutdir(testdir): + conf = testdir.makeconftest("") + p = testdir.mkdir("x") + conftest = Conftest(confcutdir=p) + conftest.setinitial([testdir.tmpdir]) + l = conftest.getconftestmodules(p) + assert len(l) == 0 + l = conftest.getconftestmodules(conf.dirpath()) + assert len(l) == 0 + assert conf not in conftest._conftestpath2mod + # but we can still import a conftest directly + conftest.importconftest(conf) + l = conftest.getconftestmodules(conf.dirpath()) + assert l[0].__file__.startswith(str(conf)) + # and all sub paths get updated properly + l = conftest.getconftestmodules(p) + assert len(l) == 1 + assert l[0].__file__.startswith(str(conf)) + +def test_conftestcutdir_inplace_considered(testdir): + conf = testdir.makeconftest("") + conftest = Conftest(confcutdir=conf.dirpath()) + conftest.setinitial([conf.dirpath()]) + l = conftest.getconftestmodules(conf.dirpath()) + assert len(l) == 1 + assert l[0].__file__.startswith(str(conf)) + +def test_setinitial_confcut(testdir): + conf = testdir.makeconftest("") + sub = testdir.mkdir("sub") + sub.chdir() + for opts in (["--confcutdir=%s" % sub, sub], + [sub, "--confcutdir=%s" % sub], + ["--confcutdir=.", sub], + [sub, "--confcutdir", sub], + [str(sub), "--confcutdir", "."], + ): + conftest = Conftest() + conftest.setinitial(opts) + assert conftest._confcutdir == sub + assert conftest.getconftestmodules(sub) == [] + assert conftest.getconftestmodules(conf.dirpath()) == [] --- a/testing/plugin/test_pytest_default.py +++ b/testing/plugin/test_pytest_default.py @@ -92,3 +92,14 @@ def test_pytest_report_iteminfo(): res = pytest_report_iteminfo(FakeItem()) assert res == "-reportinfo-" + + +def test_conftest_confcutdir(testdir): + testdir.makeconftest("assert 0") + x = testdir.mkdir("x") + x.join("conftest.py").write(py.code.Source(""" + def pytest_addoption(parser): + parser.addoption("--xyz", action="store_true") + """)) + result = testdir.runpytest("-h", "--confcutdir=%s" % x, x) + assert result.stdout.fnmatch_lines(["*--xyz*"]) --- a/py/impl/test/conftesthandle.py +++ b/py/impl/test/conftesthandle.py @@ -8,10 +8,11 @@ class Conftest(object): Note that triggering Conftest instances to import conftest.py files may result in added cmdline options. """ - def __init__(self, onimport=None): + def __init__(self, onimport=None, confcutdir=None): self._path2confmods = {} self._onimport = onimport self._conftestpath2mod = {} + self._confcutdir = confcutdir def setinitial(self, args): """ try to find a first anchor path for looking up global values @@ -23,6 +24,17 @@ class Conftest(object): bootstrapped ... """ current = py.path.local() + opt = '--confcutdir' + for i in range(len(args)): + opt1 = str(args[i]) + if opt1.startswith(opt): + if opt1 == opt: + if len(args) > i: + p = current.join(args[i+1], abs=True) + elif opt1.startswith(opt + "="): + p = current.join(opt1[len(opt)+1:], abs=1) + self._confcutdir = p + break for arg in args + [current]: anchor = current.join(arg, abs=1) if anchor.check(): # we found some file object @@ -37,16 +49,20 @@ class Conftest(object): clist = self._path2confmods[path] except KeyError: if path is None: - raise ValueError("missing default conftest.") + raise ValueError("missing default confest.") dp = path.dirpath() if dp == path: - clist = self._path2confmods[path] = [] + clist = [] else: + cutdir = self._confcutdir clist = self.getconftestmodules(dp) - conftestpath = path.join("conftest.py") - if conftestpath.check(file=1): - clist.append(self.importconftest(conftestpath)) - self._path2confmods[path] = clist + if cutdir and path != cutdir and not path.relto(cutdir): + pass + else: + conftestpath = path.join("conftest.py") + if conftestpath.check(file=1): + clist.append(self.importconftest(conftestpath)) + self._path2confmods[path] = clist # be defensive: avoid changes from caller side to # affect us by always returning a copy of the actual list return clist[:] @@ -77,8 +93,14 @@ class Conftest(object): mod = conftestpath.pyimport(modname=modname) else: mod = conftestpath.pyimport() + self._conftestpath2mod[conftestpath] = mod + dirpath = conftestpath.dirpath() + if dirpath in self._path2confmods: + for path, mods in self._path2confmods.items(): + if path and path.relto(dirpath) or path == dirpath: + assert mod not in mods + mods.append(mod) self._postimport(mod) - self._conftestpath2mod[conftestpath] = mod return mod def _postimport(self, mod): --- a/CHANGELOG +++ b/CHANGELOG @@ -10,6 +10,9 @@ Changes between 1.X and 1.1.1 - new option: --ignore will prevent specified path from collection. Can be specified multiple times. +- new option: --confcutdir=dir will make py.test only consider conftest + files that are relative to the specified dir. + - install 'py.test' and `py.which` with a ``-$VERSION`` suffix to disambiguate between Python3, python2.X, Jython and PyPy installed versions. From commits-noreply at bitbucket.org Thu Dec 31 16:15:33 2009 From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org) Date: Thu, 31 Dec 2009 15:15:33 +0000 (UTC) Subject: [py-svn] py-trunk commit 3b9141d6bd7d: internal: always use scripts found in the environment Message-ID: <20091231151533.A235A7EEDE@bitbucket.org> # HG changeset patch -- Bitbucket.org # Project py-trunk # URL http://bitbucket.org/hpk42/py-trunk/overview/ # User holger krekel # Date 1262272511 -3600 # Node ID 3b9141d6bd7df061b8f4bace364d1ea66fb0fe16 # Parent 2f97b1b6a2cc27197238679b991a8e6a289f65b1 internal: always use scripts found in the environment --- a/py/plugin/pytest_pytester.py +++ b/py/plugin/pytest_pytester.py @@ -307,13 +307,8 @@ class TmpTestdir: return self.run(*fullargs) def _getpybinargs(self, scriptname): - bindir = py._dir.dirpath('bin') - if not bindir.check(): - script = py.path.local.sysfind(scriptname) - else: - script = bindir.join(scriptname) - assert script.check() - return py.std.sys.executable, script + script = py.path.local.sysfind(scriptname) + return script, def runpython(self, script): return self.run(py.std.sys.executable, script) From pedronis at openend.se Thu Dec 31 22:35:34 2009 From: pedronis at openend.se (Samuele Pedroni) Date: Thu, 31 Dec 2009 22:35:34 +0100 Subject: [py-svn] py-trunk commit 3b9141d6bd7d: internal: always use scripts found in the environment In-Reply-To: <20091231151533.A235A7EEDE@bitbucket.org> References: <20091231151533.A235A7EEDE@bitbucket.org> Message-ID: <4B3D1926.2060206@openend.se> this broke the way I tend to run the oejskit tests which use testdir.runpytest for some functional tests, I tend to setup a virtualenv in which the current py.test trunk is installed via setup.py develop, path-to-virtualenv/bin/py.test then is how I invoke py.test and it that case there is no py.test at all findable through the environment. commits-noreply at bitbucket.org wrote: > # HG changeset patch -- Bitbucket.org > # Project py-trunk > # URL http://bitbucket.org/hpk42/py-trunk/overview/ > # User holger krekel > # Date 1262272511 -3600 > # Node ID 3b9141d6bd7df061b8f4bace364d1ea66fb0fe16 > # Parent 2f97b1b6a2cc27197238679b991a8e6a289f65b1 > internal: always use scripts found in the environment > > --- a/py/plugin/pytest_pytester.py > +++ b/py/plugin/pytest_pytester.py > @@ -307,13 +307,8 @@ class TmpTestdir: > return self.run(*fullargs) > > def _getpybinargs(self, scriptname): > - bindir = py._dir.dirpath('bin') > - if not bindir.check(): > - script = py.path.local.sysfind(scriptname) > - else: > - script = bindir.join(scriptname) > - assert script.check() > - return py.std.sys.executable, script > + script = py.path.local.sysfind(scriptname) > + return script, > > def runpython(self, script): > return self.run(py.std.sys.executable, script) > _______________________________________________ > py-svn mailing list > py-svn at codespeak.net > http://codespeak.net/mailman/listinfo/py-svn >