From commits-noreply at bitbucket.org Wed Apr 14 13:30:38 2010 From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org) Date: Wed, 14 Apr 2010 11:30:38 +0000 (UTC) Subject: [py-svn] py-trunk commit 4200bb45187b: add links to new plugins Message-ID: <20100414113038.474F77EF41@bitbucket.org> # HG changeset patch -- Bitbucket.org # Project py-trunk # URL http://bitbucket.org/hpk42/py-trunk/overview/ # User holger krekel # Date 1271081292 -7200 # Node ID 4200bb45187bb04d257161bb9b0a67da0cdcec31 # Parent 4a6529152c0184a6eebd3796a14e3c04ed1099f1 add links to new plugins --- a/doc/test/plugin/links.txt +++ b/doc/test/plugin/links.txt @@ -19,6 +19,7 @@ .. _`pytest_tmpdir.py`: http://bitbucket.org/hpk42/py-trunk/raw/1.2.1/py/_plugin/pytest_tmpdir.py .. _`terminal`: terminal.html .. _`pytest_hooklog.py`: http://bitbucket.org/hpk42/py-trunk/raw/1.2.1/py/_plugin/pytest_hooklog.py +.. _`capturelog`: capturelog.html .. _`junitxml`: junitxml.html .. _`pytest_skipping.py`: http://bitbucket.org/hpk42/py-trunk/raw/1.2.1/py/_plugin/pytest_skipping.py .. _`checkout the py.test development version`: ../../install.html#checkout @@ -35,6 +36,7 @@ .. _`recwarn`: recwarn.html .. _`pytest_pdb.py`: http://bitbucket.org/hpk42/py-trunk/raw/1.2.1/py/_plugin/pytest_pdb.py .. _`monkeypatch`: monkeypatch.html +.. _`coverage`: coverage.html .. _`resultlog`: resultlog.html .. _`pytest_junitxml.py`: http://bitbucket.org/hpk42/py-trunk/raw/1.2.1/py/_plugin/pytest_junitxml.py .. _`django`: django.html --- a/bin-for-dist/makepluginlist.py +++ b/bin-for-dist/makepluginlist.py @@ -5,10 +5,10 @@ WIDTH = 75 plugins = [ ('advanced python testing', 'skipping mark pdb figleaf ' - 'monkeypatch capture recwarn tmpdir',), + 'monkeypatch coverage capture capturelog recwarn tmpdir',), ('distributed testing, CI and deployment', 'xdist pastebin junitxml resultlog genscript',), - ('testing domains and conventions', + ('testing domains and conventions codecheckers', 'oejskit django unittest nose doctest restdoc'), ('internal, debugging, help functionality', 'helpconfig terminal hooklog') @@ -22,6 +22,9 @@ externals = { 'oejskit': "run javascript tests in real life browsers", 'xdist': None, 'figleaf': None, + 'capturelog': None, + 'coverage': None, + 'codecheckers': None, 'django': "for testing django applications", } --- a/doc/test/plugin/coverage.txt +++ b/doc/test/plugin/coverage.txt @@ -1,9 +1,51 @@ -pytest_coverage plugin (EXTERNAL) -========================================== -This plugin allows to use Ned's coverage_ package, see +Write and report coverage data with the 'coverage' package. +=========================================================== - http://github.com/rozza/py.test-plugins +.. contents:: + :local: -.. _coverage: http://pypi.python.org/pypi/coverage +Note: Original code by Ross Lawley. + +Install +-------------- + +Use pip to (un)install:: + + pip install pytest-coverage + pip uninstall pytest-coverage + +or alternatively use easy_install to install:: + + easy_install pytest-coverage + + +Usage +------------- + +To get full test coverage reports for a particular package type:: + + py.test --cover-report=report + +command line options +-------------------- + + +``--cover=COVERPACKAGES`` + (multi allowed) only include info from specified package. +``--cover-report=REPORT_TYPE`` + html: Directory for html output. + report: Output a text report. + annotate: Annotate your source code for which lines were executed and which were not. + xml: Output an xml report compatible with the cobertura plugin for hudson. +``--cover-directory=DIRECTORY`` + Directory for the reports (html / annotate results) defaults to ./coverage +``--cover-xml-file=XML_FILE`` + File for the xml report defaults to ./coverage.xml +``--cover-show-missing`` + Show missing files +``--cover-ignore-errors=IGNORE_ERRORS`` + Ignore errors of finding source files for code. + +.. include:: links.txt --- a/doc/test/plugin/index.txt +++ b/doc/test/plugin/index.txt @@ -12,8 +12,12 @@ figleaf_ (external) report test coverage monkeypatch_ safely patch object attributes, dicts and environment variables. +coverage_ (external) Write and report coverage data with the 'coverage' package. + capture_ configurable per-test stdout/stderr capturing mechanisms. +capturelog_ (external) capture output of logging module. + recwarn_ helpers for asserting deprecation and other warnings. tmpdir_ provide temporary directories to test functions. @@ -33,8 +37,8 @@ resultlog_ non-xml machine-readable logg genscript_ generate standalone test script to be distributed along with an application. -testing domains and conventions -=============================== +testing domains and conventions codecheckers +============================================ oejskit_ (external) run javascript tests in real life browsers --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,9 @@ +Changes between 1.2.1 and XXX +===================================== + +- added links to the new capturelog and coverage plugins + + Changes between 1.2.1 and 1.2.0 ===================================== From commits-noreply at bitbucket.org Wed Apr 14 13:30:40 2010 From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org) Date: Wed, 14 Apr 2010 11:30:40 +0000 (UTC) Subject: [py-svn] py-trunk commit e475f32a8daf: fix issue78 - strike superflous "import sys" that cause unbound Message-ID: <20100414113040.56ED47EF42@bitbucket.org> # HG changeset patch -- Bitbucket.org # Project py-trunk # URL http://bitbucket.org/hpk42/py-trunk/overview/ # User holger krekel # Date 1271244612 -7200 # Node ID e475f32a8daffd60e27a644da95c1de0577b7cad # Parent 4200bb45187bb04d257161bb9b0a67da0cdcec31 fix issue78 - strike superflous "import sys" that cause unbound local var errors. --- a/py/_code/_assertionold.py +++ b/py/_code/_assertionold.py @@ -447,7 +447,6 @@ def report_failure(e): def check(s, frame=None): if frame is None: - import sys frame = sys._getframe(1) frame = py.code.Frame(frame) expr = parse(s, 'eval') @@ -518,7 +517,6 @@ def getfailure(e): def run(s, frame=None): if frame is None: - import sys frame = sys._getframe(1) frame = py.code.Frame(frame) module = Interpretable(parse(s, 'exec').node) From commits-noreply at bitbucket.org Wed Apr 14 15:06:33 2010 From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org) Date: Wed, 14 Apr 2010 13:06:33 +0000 (UTC) Subject: [py-svn] py-trunk commit 97558e665d87: fix issue86 - point to pytest-xdist plugin for looponfailing feature. Message-ID: <20100414130633.04CF17EF11@bitbucket.org> # HG changeset patch -- Bitbucket.org # Project py-trunk # URL http://bitbucket.org/hpk42/py-trunk/overview/ # User holger krekel # Date 1271250339 -7200 # Node ID 97558e665d8700a2be673b98c3fa927528401e11 # Parent e475f32a8daffd60e27a644da95c1de0577b7cad fix issue86 - point to pytest-xdist plugin for looponfailing feature. --- a/doc/test/features.txt +++ b/doc/test/features.txt @@ -176,7 +176,8 @@ advanced test selection and running mode .. _`selection by keyword`: -``py.test --looponfailing`` allows to run a test suite, +``py.test --looponfailing`` (implemented through the external +`pytest-xdist`_ plugin) allows to run a test suite, memorize all failures and then loop over the failing set of tests until they all pass. It will re-start running the tests when it detects file changes in your project. --- a/CHANGELOG +++ b/CHANGELOG @@ -2,6 +2,8 @@ Changes between 1.2.1 and XXX ===================================== - added links to the new capturelog and coverage plugins +- (issue87) fix unboundlocal error in assertionold code +- (issue86) improve documentation for looponfailing Changes between 1.2.1 and 1.2.0 From commits-noreply at bitbucket.org Mon Apr 19 14:44:36 2010 From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org) Date: Mon, 19 Apr 2010 12:44:36 +0000 (UTC) Subject: [py-svn] py-trunk commit 381509aaa6ad: add capturelog generated plugin text Message-ID: <20100419124436.5ED577EF14@bitbucket.org> # HG changeset patch -- Bitbucket.org # Project py-trunk # URL http://bitbucket.org/hpk42/py-trunk/overview # User holger krekel # Date 1271681051 -7200 # Node ID 381509aaa6ad365614974fdd1f10494a53c953a8 # Parent 97558e665d8700a2be673b98c3fa927528401e11 add capturelog generated plugin text --- /dev/null +++ b/doc/test/plugin/capturelog.txt @@ -0,0 +1,86 @@ + +capture output of logging module. +================================= + + +.. contents:: + :local: + +Installation +------------ + +You can install the `pytest-capturelog pypi`_ package +with pip:: + + pip install pytest-capturelog + +or with easy install:: + + easy_install pytest-capturelog + +.. _`pytest-capturelog pypi`: http://pypi.python.org/pypi/pytest-capturelog/ + +Usage +----- + +If the plugin is installed log messages are captured by default and for +each failed test will be shown in the same manner as captured stdout and +stderr. + +Running without options:: + + py.test test_capturelog.py + +Shows failed tests like so:: + + -------------------------- Captured log --------------------------- + test_capturelog.py 26 INFO text going to logger + ------------------------- Captured stdout ------------------------- + text going to stdout + ------------------------- Captured stderr ------------------------- + text going to stderr + ==================== 2 failed in 0.02 seconds ===================== + +By default each captured log message shows the module, line number, +log level and message. Showing the exact module and line number is +useful for testing and debugging. If desired the log format and date +format can be specified to anything that the logging module supports. + +Running pytest specifying formatting options:: + + py.test --log-format="%(asctime)s %(levelname)s %(message)s" --log-date-format="%Y-%m-%d %H:%M:%S" test_capturelog.py + +Shows failed tests like so:: + + -------------------------- Captured log --------------------------- + 2010-04-10 14:48:44 INFO text going to logger + ------------------------- Captured stdout ------------------------- + text going to stdout + ------------------------- Captured stderr ------------------------- + text going to stderr + ==================== 2 failed in 0.02 seconds ===================== + +Further it is possible to disable capturing of logs completely with:: + + py.test --nocapturelog test_capturelog.py + +Shows failed tests in the normal manner as no logs were captured:: + + ------------------------- Captured stdout ------------------------- + text going to stdout + ------------------------- Captured stderr ------------------------- + text going to stderr + ==================== 2 failed in 0.02 seconds ===================== + +command line options +-------------------- + + +``--nocapturelog`` + disable log capture +``--log-format=LOG_FORMAT`` + log format as used by the logging module +``--log-date-format=LOG_DATE_FORMAT`` + log date format as used by the logging module + +.. include:: links.txt From commits-noreply at bitbucket.org Tue Apr 20 19:46:32 2010 From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org) Date: Tue, 20 Apr 2010 17:46:32 +0000 (UTC) Subject: [py-svn] py-trunk commit 5e0646b82f03: refine win32 checks to also work on top of jython/win32 Message-ID: <20100420174632.3F33F7EF72@bitbucket.org> # HG changeset patch -- Bitbucket.org # Project py-trunk # URL http://bitbucket.org/hpk42/py-trunk/overview # User holger krekel # Date 1271785541 25200 # Node ID 5e0646b82f031ebdfc6e947129e8e978d1a988fe # Parent 381509aaa6ad365614974fdd1f10494a53c953a8 refine win32 checks to also work on top of jython/win32 --- a/py/_path/local.py +++ b/py/_path/local.py @@ -5,7 +5,7 @@ import sys, os, stat, re, atexit import py from py._path import common -iswin32 = sys.platform == "win32" +iswin32 = sys.platform == "win32" or (os._name == 'nt') class Stat(object): def __getattr__(self, name): --- a/testing/path/test_local.py +++ b/testing/path/test_local.py @@ -193,7 +193,7 @@ class TestLocalPath(common.CommonFSTests assert l[2] == p3 class TestExecutionOnWindows: - pytestmark = py.test.mark.skipif("sys.platform != 'win32'") + pytestmark = py.test.mark.skipif("not (sys.platform == 'win32' or os._name == 'nt')") def test_sysfind(self): x = py.path.local.sysfind('cmd') @@ -201,7 +201,7 @@ class TestExecutionOnWindows: assert py.path.local.sysfind('jaksdkasldqwe') is None class TestExecution: - pytestmark = py.test.mark.skipif("sys.platform == 'win32'") + pytestmark = py.test.mark.skipif("sys.platform == 'win32' or os._name == 'nt'") def test_sysfind(self): x = py.path.local.sysfind('test') @@ -357,7 +357,7 @@ def test_samefile(tmpdir): assert p.samefile(p) class TestWINLocalPath: - pytestmark = py.test.mark.skipif("sys.platform != 'win32'") + pytestmark = py.test.mark.skipif("sys.platform != 'win32' and os._name != 'win32'") def test_owner_group_not_implemented(self, path1): py.test.raises(NotImplementedError, "path1.stat().owner") @@ -406,7 +406,7 @@ class TestWINLocalPath: old.chdir() class TestPOSIXLocalPath: - pytestmark = py.test.mark.skipif("sys.platform == 'win32'") + pytestmark = py.test.mark.skipif("sys.platform == 'win32' or os._name == 'nt'") def test_hardlink(self, tmpdir): linkpath = tmpdir.join('test') --- a/py/_io/terminalwriter.py +++ b/py/_io/terminalwriter.py @@ -133,7 +133,8 @@ def ansi_print(text, esc, file=None, new def should_do_markup(file): return hasattr(file, 'isatty') and file.isatty() \ - and os.environ.get('TERM') != 'dumb' + and os.environ.get('TERM') != 'dumb' \ + and not (os._name == 'nt' and sys.platform.startswith('java')) class TerminalWriter(object): _esctable = dict(black=30, red=31, green=32, yellow=33, --- a/CHANGELOG +++ b/CHANGELOG @@ -4,6 +4,7 @@ Changes between 1.2.1 and XXX - added links to the new capturelog and coverage plugins - (issue87) fix unboundlocal error in assertionold code - (issue86) improve documentation for looponfailing +- fix jython/win32 issues Changes between 1.2.1 and 1.2.0 From commits-noreply at bitbucket.org Tue Apr 20 19:49:51 2010 From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org) Date: Tue, 20 Apr 2010 17:49:51 +0000 (UTC) Subject: [py-svn] py-trunk commit f2c33cd6aba7: fix check to work when there is no jython Message-ID: <20100420174951.51B317EF70@bitbucket.org> # HG changeset patch -- Bitbucket.org # Project py-trunk # URL http://bitbucket.org/hpk42/py-trunk/overview # User holger krekel # Date 1271785729 25200 # Node ID f2c33cd6aba71e1d50e877c4fcaab7b6cb48e2d3 # Parent 5e0646b82f031ebdfc6e947129e8e978d1a988fe fix check to work when there is no jython --- a/py/_io/terminalwriter.py +++ b/py/_io/terminalwriter.py @@ -134,7 +134,7 @@ def ansi_print(text, esc, file=None, new def should_do_markup(file): return hasattr(file, 'isatty') and file.isatty() \ and os.environ.get('TERM') != 'dumb' \ - and not (os._name == 'nt' and sys.platform.startswith('java')) + and not (sys.platform.startswith('java') and os._name == 'nt') class TerminalWriter(object): _esctable = dict(black=30, red=31, green=32, yellow=33, From commits-noreply at bitbucket.org Tue Apr 20 20:09:04 2010 From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org) Date: Tue, 20 Apr 2010 18:09:04 +0000 (UTC) Subject: [py-svn] py-trunk commit 0e098b4734bc: refining the win32 checks some further Message-ID: <20100420180904.5C9957EF6F@bitbucket.org> # HG changeset patch -- Bitbucket.org # Project py-trunk # URL http://bitbucket.org/hpk42/py-trunk/overview # User holger krekel # Date 1271786932 -7200 # Node ID 0e098b4734bc30dc29d6e9a2a03b653399ddb041 # Parent f2c33cd6aba71e1d50e877c4fcaab7b6cb48e2d3 refining the win32 checks some further --- a/py/_path/local.py +++ b/py/_path/local.py @@ -5,7 +5,7 @@ import sys, os, stat, re, atexit import py from py._path import common -iswin32 = sys.platform == "win32" or (os._name == 'nt') +iswin32 = sys.platform == "win32" or (getattr(os, '_name', False) == 'nt') class Stat(object): def __getattr__(self, name): --- a/testing/path/test_local.py +++ b/testing/path/test_local.py @@ -4,6 +4,11 @@ from py.path import local from testing.path import common failsonjython = py.test.mark.xfail("sys.platform.startswith('java')") +win32only = py.test.mark.skipif( + "not (sys.platform == 'win32' or getattr(os, '_name', None) == 'nt')") +failsonwin32 = py.test.mark.skipif( + "sys.platform == 'win32' or getattr(os, '_name', None) == 'nt'") + def pytest_funcarg__path1(request): def setup(): @@ -193,7 +198,7 @@ class TestLocalPath(common.CommonFSTests assert l[2] == p3 class TestExecutionOnWindows: - pytestmark = py.test.mark.skipif("not (sys.platform == 'win32' or os._name == 'nt')") + pytestmark = win32only def test_sysfind(self): x = py.path.local.sysfind('cmd') @@ -201,7 +206,7 @@ class TestExecutionOnWindows: assert py.path.local.sysfind('jaksdkasldqwe') is None class TestExecution: - pytestmark = py.test.mark.skipif("sys.platform == 'win32' or os._name == 'nt'") + pytestmark = failsonwin32 def test_sysfind(self): x = py.path.local.sysfind('test') @@ -357,7 +362,7 @@ def test_samefile(tmpdir): assert p.samefile(p) class TestWINLocalPath: - pytestmark = py.test.mark.skipif("sys.platform != 'win32' and os._name != 'win32'") + pytestmark = win32only def test_owner_group_not_implemented(self, path1): py.test.raises(NotImplementedError, "path1.stat().owner") @@ -406,7 +411,7 @@ class TestWINLocalPath: old.chdir() class TestPOSIXLocalPath: - pytestmark = py.test.mark.skipif("sys.platform == 'win32' or os._name == 'nt'") + pytestmark = failsonwin32 def test_hardlink(self, tmpdir): linkpath = tmpdir.join('test') From commits-noreply at bitbucket.org Wed Apr 21 12:50:32 2010 From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org) Date: Wed, 21 Apr 2010 10:50:32 +0000 (UTC) Subject: [py-svn] py-trunk commit 95af339213dc: a couple of more fixes/refinements for getting py.test to run better on jython/win32 Message-ID: <20100421105032.6BE357EF3D@bitbucket.org> # HG changeset patch -- Bitbucket.org # Project py-trunk # URL http://bitbucket.org/hpk42/py-trunk/overview # User holger krekel # Date 1271847003 25200 # Node ID 95af339213dc1c085fba4c2ef246107162aa46c9 # Parent 0e098b4734bc30dc29d6e9a2a03b653399ddb041 a couple of more fixes/refinements for getting py.test to run better on jython/win32 --- a/testing/acceptance_test.py +++ b/testing/acceptance_test.py @@ -85,7 +85,7 @@ class TestGeneralUsage: assert result.ret == 0 def test_pydoc(self, testdir): - result = testdir.runpython_c("import py ; help(py.test)") + result = testdir.runpython_c("import py;help(py.test)") assert result.ret == 0 s = result.stdout.str() assert 'MarkGenerator' in s --- a/py/_path/common.py +++ b/py/_path/common.py @@ -184,7 +184,7 @@ newline will be removed from the end of #assert strrelpath[-1] == self.sep #assert strrelpath[-2] != self.sep strself = str(self) - if sys.platform == "win32": + if sys.platform == "win32" or getattr(os, '_name', None) == 'nt': if os.path.normcase(strself).startswith( os.path.normcase(strrelpath)): return strself[len(strrelpath):] --- a/py/_plugin/pytest_skipping.py +++ b/py/_plugin/pytest_skipping.py @@ -196,7 +196,7 @@ def evalexpression(item, keyword): expr, result = None, True for expr in markholder.args: if isinstance(expr, str): - result = eval(expr, d) + result = cached_eval(item.config, expr, d) else: result = expr if not result: @@ -204,6 +204,18 @@ def evalexpression(item, keyword): return expr, result return None, False +def cached_eval(config, expr, d): + if not hasattr(config, '_evalcache'): + config._evalcache = {} + try: + return config._evalcache[expr] + except KeyError: + #import sys + #print >>sys.stderr, ("cache-miss: %r" % expr) + config._evalcache[expr] = x = eval(expr, d) + return x + + def folded_skips(skipped): d = {} for event in skipped: --- a/testing/path/test_local.py +++ b/testing/path/test_local.py @@ -4,9 +4,11 @@ from py.path import local from testing.path import common failsonjython = py.test.mark.xfail("sys.platform.startswith('java')") +failsonjywin32 = py.test.mark.xfail("sys.platform.startswith('java') " + "and getattr(os, '_name', None) == 'nt'") win32only = py.test.mark.skipif( "not (sys.platform == 'win32' or getattr(os, '_name', None) == 'nt')") -failsonwin32 = py.test.mark.skipif( +skiponwin32 = py.test.mark.skipif( "sys.platform == 'win32' or getattr(os, '_name', None) == 'nt'") @@ -90,6 +92,7 @@ class TestLocalPath(common.CommonFSTests finally: f.close() + @failsonjywin32 def test_setmtime(self): import tempfile import time @@ -206,7 +209,7 @@ class TestExecutionOnWindows: assert py.path.local.sysfind('jaksdkasldqwe') is None class TestExecution: - pytestmark = failsonwin32 + pytestmark = skiponwin32 def test_sysfind(self): x = py.path.local.sysfind('test') @@ -411,7 +414,7 @@ class TestWINLocalPath: old.chdir() class TestPOSIXLocalPath: - pytestmark = failsonwin32 + pytestmark = skiponwin32 def test_hardlink(self, tmpdir): linkpath = tmpdir.join('test') --- a/py/_plugin/pytest_pytester.py +++ b/py/_plugin/pytest_pytester.py @@ -315,8 +315,8 @@ class TmpTestdir: else: cmdlinename = scriptname.replace(".", "") assert hasattr(py.cmdline, cmdlinename), cmdlinename - source = ("import sys ; sys.path.insert(0, %r); " - "import py ; py.cmdline.%s()" % + source = ("import sys;sys.path.insert(0,%r);" + "import py;py.cmdline.%s()" % (str(py._pydir.dirpath()), cmdlinename)) return (sys.executable, "-c", source,) @@ -328,7 +328,7 @@ class TmpTestdir: def _getsysprepend(self): if not self.request.config.getvalue("toolsonpath"): - s = "import sys ; sys.path.insert(0, %r) ; " % str(py._pydir.dirpath()) + s = "import sys;sys.path.insert(0,%r);" % str(py._pydir.dirpath()) else: s = "" return s --- a/.hgignore +++ b/.hgignore @@ -13,6 +13,7 @@ syntax:glob *.html *.class *.orig +*~ build/ dist/ From commits-noreply at bitbucket.org Wed Apr 21 14:50:00 2010 From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org) Date: Wed, 21 Apr 2010 12:50:00 +0000 (UTC) Subject: [py-svn] py-trunk commit 616ff9c76f53: robustify check Message-ID: <20100421125000.813AC7EF65@bitbucket.org> # HG changeset patch -- Bitbucket.org # Project py-trunk # URL http://bitbucket.org/hpk42/py-trunk/overview # User holger krekel # Date 1271854001 -7200 # Node ID 616ff9c76f537dd5134f98f26be7f262f50fd26c # Parent 0e098b4734bc30dc29d6e9a2a03b653399ddb041 robustify check --- a/testing/plugin/test_pytest_runner_xunit.py +++ b/testing/plugin/test_pytest_runner_xunit.py @@ -192,7 +192,7 @@ def test_setup_funcarg_setup_not_called_ def setup_module(mod): raise ValueError(42) def pytest_funcarg__hello(request): - raise ValueError(43) + raise ValueError("xyz43") def test_function1(hello): pass def test_function2(hello): @@ -206,7 +206,7 @@ def test_setup_funcarg_setup_not_called_ "*ValueError*42*", "*2 error*" ]) - assert "43" not in result.stdout.str() + assert "xyz43" not in result.stdout.str() From commits-noreply at bitbucket.org Wed Apr 21 14:50:02 2010 From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org) Date: Wed, 21 Apr 2010 12:50:02 +0000 (UTC) Subject: [py-svn] py-trunk commit acbba79c2237: merge in fixes Message-ID: <20100421125002.BE5E07EF69@bitbucket.org> # HG changeset patch -- Bitbucket.org # Project py-trunk # URL http://bitbucket.org/hpk42/py-trunk/overview # User holger krekel # Date 1271854178 -7200 # Node ID acbba79c22372ab7826c7860f6b6cf014e75b1dd # Parent 4b286bee57cc23da122680f46c61fb0d02445162 # Parent 95af339213dc1c085fba4c2ef246107162aa46c9 merge in fixes From commits-noreply at bitbucket.org Wed Apr 21 14:50:02 2010 From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org) Date: Wed, 21 Apr 2010 12:50:02 +0000 (UTC) Subject: [py-svn] py-trunk commit 4b286bee57cc: robustify a check to not randomly fail Message-ID: <20100421125002.AAD7F7EF68@bitbucket.org> # HG changeset patch -- Bitbucket.org # Project py-trunk # URL http://bitbucket.org/hpk42/py-trunk/overview # User holger krekel # Date 1271854064 -7200 # Node ID 4b286bee57cc23da122680f46c61fb0d02445162 # Parent 616ff9c76f537dd5134f98f26be7f262f50fd26c robustify a check to not randomly fail From commits-noreply at bitbucket.org Wed Apr 21 15:32:13 2010 From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org) Date: Wed, 21 Apr 2010 13:32:13 +0000 (UTC) Subject: [py-svn] py-trunk commit 540361eb4d03: use taskkill cmdline for jython/win32 but skip test on jython because it does not return a subprocess PID Message-ID: <20100421133213.DF0057EF11@bitbucket.org> # HG changeset patch -- Bitbucket.org # Project py-trunk # URL http://bitbucket.org/hpk42/py-trunk/overview # User holger krekel # Date 1271856199 25200 # Node ID 540361eb4d03cbcbe24bd0bffeab915d04576de3 # Parent acbba79c22372ab7826c7860f6b6cf014e75b1dd use taskkill cmdline for jython/win32 but skip test on jython because it does not return a subprocess PID --- a/py/_process/killproc.py +++ b/py/_process/killproc.py @@ -1,7 +1,7 @@ import py import os, sys -if sys.platform == "win32": +if sys.platform == "win32" or getattr(os, '_name', '') == 'nt': try: import ctypes except ImportError: --- a/testing/process/test_killproc.py +++ b/testing/process/test_killproc.py @@ -1,6 +1,7 @@ import py, sys + at py.test.mark.skipif("sys.platform.startswith('java')") def test_kill(tmpdir): subprocess = py.test.importorskip("subprocess") t = tmpdir.join("t.py") From commits-noreply at bitbucket.org Thu Apr 22 11:58:40 2010 From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org) Date: Thu, 22 Apr 2010 09:58:40 +0000 (UTC) Subject: [py-svn] py-trunk commit a1bbe5eefc21: introduce an experimental approach for allowing dynamic addition of hooks from plugin. Plugins may register new hooks by implementing the new Message-ID: <20100422095840.7D4E57EF84@bitbucket.org> # HG changeset patch -- Bitbucket.org # Project py-trunk # URL http://bitbucket.org/hpk42/py-trunk/overview # User holger krekel # Date 1271930277 -7200 # Node ID a1bbe5eefc21377e8ac5e714019c74088cb96d28 # Parent 540361eb4d03cbcbe24bd0bffeab915d04576de3 introduce an experimental approach for allowing dynamic addition of hooks from plugin. Plugins may register new hooks by implementing the new pytest_registerhooks(pluginmanager) and call pluginmanager.registerhooks(module) with the referenced 'module' object containing the hooks. The new pytest_registerhooks is called after pytest_addoption and before pytest_configure. --- a/py/_test/pluginmanager.py +++ b/py/_test/pluginmanager.py @@ -7,7 +7,7 @@ from py._plugin import hookspec from py._test.outcome import Skipped default_plugins = ( - "default runner capture terminal mark skipping tmpdir monkeypatch " + "default runner capture mark terminal skipping tmpdir monkeypatch " "recwarn pdb pastebin unittest helpconfig nose assertion genscript " "junitxml doctest").split() @@ -20,7 +20,7 @@ class PluginManager(object): self.registry = Registry() self._name2plugin = {} self._hints = [] - self.hook = HookRelay(hookspecs=hookspec, registry=self.registry) + self.hook = HookRelay([hookspec], registry=self.registry) self.register(self) for spec in default_plugins: self.import_plugin(spec) @@ -40,6 +40,8 @@ class PluginManager(object): if name in self._name2plugin: return False self._name2plugin[name] = plugin + self.call_plugin(plugin, "pytest_registerhooks", + {'pluginmanager': self}) self.hook.pytest_plugin_registered(manager=self, plugin=plugin) self.registry.register(plugin) return True @@ -58,6 +60,9 @@ class PluginManager(object): if plugin == val: return True + def registerhooks(self, spec): + self.hook._registerhooks(spec) + def getplugins(self): return list(self.registry) @@ -301,13 +306,21 @@ class Registry: class HookRelay: def __init__(self, hookspecs, registry): - self._hookspecs = hookspecs + if not isinstance(hookspecs, list): + hookspecs = [hookspecs] + self._hookspecs = [] self._registry = registry + for hookspec in hookspecs: + self._registerhooks(hookspec) + + def _registerhooks(self, hookspecs): + self._hookspecs.append(hookspecs) for name, method in vars(hookspecs).items(): if name[:1] != "_": firstresult = getattr(method, 'firstresult', False) hc = HookCaller(self, name, firstresult=firstresult) setattr(self, name, hc) + #print ("setting new hook", name) def _performcall(self, name, multicall): return multicall.execute() --- a/py/_plugin/hookspec.py +++ b/py/_plugin/hookspec.py @@ -9,6 +9,9 @@ hook specifications for py.test plugins def pytest_addoption(parser): """ called before commandline parsing. """ +def pytest_registerhooks(pluginmanager): + """ called after commandline parsing before pytest_configure. """ + def pytest_namespace(): """ return dict of name->object which will get stored at py.test. namespace""" @@ -133,31 +136,6 @@ def pytest_doctest_prepare_content(conte """ return processed content for a given doctest""" pytest_doctest_prepare_content.firstresult = True -# ------------------------------------------------------------------------- -# distributed testing -# ------------------------------------------------------------------------- - -def pytest_gwmanage_newgateway(gateway, platinfo): - """ called on new raw gateway creation. """ - -def pytest_gwmanage_rsyncstart(source, gateways): - """ called before rsyncing a directory to remote gateways takes place. """ - -def pytest_gwmanage_rsyncfinish(source, gateways): - """ called after rsyncing a directory to remote gateways takes place. """ - -def pytest_testnodeready(node): - """ Test Node is ready to operate. """ - -def pytest_testnodedown(node, error): - """ Test Node is down. """ - -def pytest_rescheduleitems(items): - """ reschedule Items from a node that went down. """ - -def pytest_looponfailinfo(failreports, rootdirs): - """ info for repeating failing tests. """ - # ------------------------------------------------------------------------- # error handling and internal debugging hooks --- a/py/_plugin/pytest_terminal.py +++ b/py/_plugin/pytest_terminal.py @@ -6,6 +6,8 @@ This is a good source for looking at the import py import sys +optionalhook = py.test.mark.optionalhook + def pytest_addoption(parser): group = parser.getgroup("terminal reporting", "reporting", after="general") group._addoption('-v', '--verbose', action="count", @@ -130,6 +132,15 @@ class TerminalReporter: for line in str(excrepr).split("\n"): self.write_line("INTERNALERROR> " + line) + def pytest_plugin_registered(self, plugin): + if self.config.option.traceconfig: + msg = "PLUGIN registered: %s" %(plugin,) + # XXX this event may happen during setup/teardown time + # which unfortunately captures our output here + # which garbles our output if we use self.write_line + self.write_line(msg) + + @optionalhook def pytest_gwmanage_newgateway(self, gateway, platinfo): #self.write_line("%s instantiated gateway from spec %r" %(gateway.id, gateway.spec._spec)) d = {} @@ -149,30 +160,37 @@ class TerminalReporter: self.write_line(infoline) self.gateway2info[gateway] = infoline - def pytest_plugin_registered(self, plugin): - if self.config.option.traceconfig: - msg = "PLUGIN registered: %s" %(plugin,) - # XXX this event may happen during setup/teardown time - # which unfortunately captures our output here - # which garbles our output if we use self.write_line - self.write_line(msg) - + @optionalhook def pytest_testnodeready(self, node): self.write_line("[%s] txnode ready to receive tests" %(node.gateway.id,)) + @optionalhook def pytest_testnodedown(self, node, error): if error: self.write_line("[%s] node down, error: %s" %(node.gateway.id, error)) + @optionalhook + def pytest_rescheduleitems(self, items): + if self.config.option.debug: + self.write_sep("!", "RESCHEDULING %s " %(items,)) + + @optionalhook + def pytest_looponfailinfo(self, failreports, rootdirs): + if failreports: + self.write_sep("#", "LOOPONFAILING", red=True) + for report in failreports: + loc = self._getcrashline(report) + self.write_line(loc, red=True) + self.write_sep("#", "waiting for changes") + for rootdir in rootdirs: + self.write_line("### Watching: %s" %(rootdir,), bold=True) + + def pytest_trace(self, category, msg): if self.config.option.debug or \ self.config.option.traceconfig and category.find("config") != -1: self.write_line("[%s] %s" %(category, msg)) - def pytest_rescheduleitems(self, items): - if self.config.option.debug: - self.write_sep("!", "RESCHEDULING %s " %(items,)) - def pytest_deselected(self, items): self.stats.setdefault('deselected', []).append(items) @@ -274,16 +292,6 @@ class TerminalReporter: else: excrepr.reprcrash.toterminal(self._tw) - def pytest_looponfailinfo(self, failreports, rootdirs): - if failreports: - self.write_sep("#", "LOOPONFAILING", red=True) - for report in failreports: - loc = self._getcrashline(report) - self.write_line(loc, red=True) - self.write_sep("#", "waiting for changes") - for rootdir in rootdirs: - self.write_line("### Watching: %s" %(rootdir,), bold=True) - def _getcrashline(self, report): try: return report.longrepr.reprcrash --- a/py/__init__.py +++ b/py/__init__.py @@ -8,9 +8,8 @@ dictionary or an import path. (c) Holger Krekel and others, 2004-2010 """ -version = "1.2.1" +_version__ = version = "1.2.2" -__version__ = version = version or "1.2.x" import py.apipkg py.apipkg.initpkg(__name__, dict( --- a/testing/plugin/test_pytest_helpconfig.py +++ b/testing/plugin/test_pytest_helpconfig.py @@ -29,3 +29,24 @@ def test_collectattr(): methods = py.builtin.sorted(collectattr(B())) assert list(methods) == ['pytest_hello', 'pytest_world'] +def test_hookvalidation_unknown(testdir): + testdir.makeconftest(""" + def pytest_hello(xyz): + pass + """) + result = testdir.runpytest() + assert result.ret != 0 + assert result.stderr.fnmatch_lines([ + '*unknown hook*pytest_hello*' + ]) + +def test_hookvalidation_optional(testdir): + testdir.makeconftest(""" + import py + @py.test.mark.optionalhook + def pytest_hello(xyz): + pass + """) + result = testdir.runpytest() + assert result.ret == 0 + --- a/testing/plugin/test_pytest__pytest.py +++ b/testing/plugin/test_pytest__pytest.py @@ -34,7 +34,7 @@ def test_functional(testdir, linecomp): def test_func(_pytest): class ApiClass: def xyz(self, arg): pass - hook = HookRelay(ApiClass, Registry()) + hook = HookRelay([ApiClass], Registry()) rec = _pytest.gethookrecorder(hook) class Plugin: def xyz(self, arg): --- a/CHANGELOG +++ b/CHANGELOG @@ -1,6 +1,7 @@ -Changes between 1.2.1 and XXX -===================================== +Changes between 1.2.1 and 1.2.2 (release pending) +================================================== +- new mechanism to allow plugins to register new hooks - added links to the new capturelog and coverage plugins - (issue87) fix unboundlocal error in assertionold code - (issue86) improve documentation for looponfailing --- a/py/_plugin/pytest__pytest.py +++ b/py/_plugin/pytest__pytest.py @@ -34,15 +34,18 @@ class HookRecorder: self._recorders = {} def start_recording(self, hookspecs): - assert hookspecs not in self._recorders - class RecordCalls: - _recorder = self - for name, method in vars(hookspecs).items(): - if name[0] != "_": - setattr(RecordCalls, name, self._makecallparser(method)) - recorder = RecordCalls() - self._recorders[hookspecs] = recorder - self._registry.register(recorder) + if not isinstance(hookspecs, (list, tuple)): + hookspecs = [hookspecs] + for hookspec in hookspecs: + assert hookspec not in self._recorders + class RecordCalls: + _recorder = self + for name, method in vars(hookspec).items(): + if name[0] != "_": + setattr(RecordCalls, name, self._makecallparser(method)) + recorder = RecordCalls() + self._recorders[hookspec] = recorder + self._registry.register(recorder) self.hook = HookRelay(hookspecs, registry=self._registry) def finish_recording(self): --- a/py/_plugin/pytest_helpconfig.py +++ b/py/_plugin/pytest_helpconfig.py @@ -93,9 +93,11 @@ def pytest_report_header(config): # ===================================================== def pytest_plugin_registered(manager, plugin): - hookspec = manager.hook._hookspecs methods = collectattr(plugin) - hooks = collectattr(hookspec) + hooks = {} + for hookspec in manager.hook._hookspecs: + hooks.update(collectattr(hookspec)) + stringio = py.io.TextIO() def Print(*args): if args: @@ -109,10 +111,13 @@ def pytest_plugin_registered(manager, pl if isgenerichook(name): continue if name not in hooks: - Print("found unknown hook:", name) - fail = True + if not getattr(method, 'optionalhook', False): + Print("found unknown hook:", name) + fail = True else: + #print "checking", method method_args = getargs(method) + #print "method_args", method_args if '__multicall__' in method_args: method_args.remove('__multicall__') hook = hooks[name] --- a/setup.py +++ b/setup.py @@ -27,7 +27,7 @@ def main(): name='py', description='py.test and pylib: rapid testing and development utils.', long_description = long_description, - version= trunk or '1.2.1', + version= trunk or '1.2.2', url='http://pylib.org', license='MIT license', platforms=['unix', 'linux', 'osx', 'cygwin', 'win32'], From commits-noreply at bitbucket.org Thu Apr 22 16:52:13 2010 From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org) Date: Thu, 22 Apr 2010 14:52:13 +0000 (UTC) Subject: [py-svn] py-trunk commit e3c665a01405: fix version numbers Message-ID: <20100422145213.F16597EF63@bitbucket.org> # HG changeset patch -- Bitbucket.org # Project py-trunk # URL http://bitbucket.org/hpk42/py-trunk/overview # User holger krekel # Date 1271947902 -7200 # Node ID e3c665a014058758284fe581f3c5e8cda670bd48 # Parent a1bbe5eefc21377e8ac5e714019c74088cb96d28 fix version numbers --- a/setup.py +++ b/setup.py @@ -21,13 +21,12 @@ For questions please check out http://py (c) Holger Krekel and others, 2004-2010 """ -trunk = None def main(): setup( name='py', description='py.test and pylib: rapid testing and development utils.', long_description = long_description, - version= trunk or '1.2.2', + version= '1.2.2', url='http://pylib.org', license='MIT license', platforms=['unix', 'linux', 'osx', 'cygwin', 'win32'], --- a/py/__init__.py +++ b/py/__init__.py @@ -8,7 +8,7 @@ dictionary or an import path. (c) Holger Krekel and others, 2004-2010 """ -_version__ = version = "1.2.2" +__version__ = version = "1.2.2" import py.apipkg From py-svn at codespeak.net Thu Apr 22 20:46:52 2010 From: py-svn at codespeak.net (USA VIAGRA ®) Date: Thu, 22 Apr 2010 22:46:52 +0400 Subject: [py-svn] April Discount #04382 Message-ID: <20100422185233.2207.qmail@201-13-26-217.dsl.telesp.net.br> http://ftxgzvqj.ru?912345083=py-svn at codespeak.net From commits-noreply at bitbucket.org Fri Apr 23 12:07:02 2010 From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org) Date: Fri, 23 Apr 2010 10:07:02 +0000 (UTC) Subject: [py-svn] py-trunk commit 65c135fcabab: fixes for testrun on jython Message-ID: <20100423100702.599257EE75@bitbucket.org> # HG changeset patch -- Bitbucket.org # Project py-trunk # URL http://bitbucket.org/hpk42/py-trunk/overview # User hpk at tannit.openend.se # Date 1272017129 -7200 # Node ID 65c135fcabab52164b362acd986a0aa3a579774c # Parent e3c665a014058758284fe581f3c5e8cda670bd48 fixes for testrun on jython --- a/testing/io_/test_terminalwriter.py +++ b/testing/io_/test_terminalwriter.py @@ -38,9 +38,12 @@ def test_terminalwriter_dumb_term_no_mar def isatty(self): return True monkeypatch.setattr(sys, 'stdout', MyFile()) - assert sys.stdout.isatty() - tw = py.io.TerminalWriter() - assert not tw.hasmarkup + try: + assert sys.stdout.isatty() + tw = py.io.TerminalWriter() + assert not tw.hasmarkup + finally: + monkeypatch.undo() def test_unicode_encoding(): msg = py.builtin._totext('b\u00f6y', 'utf8') --- a/testing/test_outcome.py +++ b/testing/test_outcome.py @@ -68,5 +68,7 @@ def test_pytest_cmdline_main(testdir): py.test.cmdline.main([__file__]) """ % (str(py._pydir.dirpath()))) import subprocess - ret = subprocess.call([sys.executable, str(p)]) + popen = subprocess.Popen([sys.executable, str(p)], stdout=subprocess.PIPE) + s = popen.stdout.read() + ret = popen.wait() assert ret == 0 --- a/testing/io_/test_capture.py +++ b/testing/io_/test_capture.py @@ -1,6 +1,8 @@ import os, sys import py +needsdup = py.test.mark.skipif("not hasattr(os, 'dup')") + from py.builtin import print_ if sys.version_info >= (3,0): @@ -72,6 +74,7 @@ def pytest_funcarg__tmpfile(request): request.addfinalizer(f.close) return f + at needsdup def test_dupfile(tmpfile): somefile = tmpfile flist = [] @@ -91,6 +94,8 @@ def test_dupfile(tmpfile): somefile.close() class TestFDCapture: + pytestmark = needsdup + def test_stdout(self, tmpfile): fd = tmpfile.fileno() cap = py.io.FDCapture(fd) @@ -261,6 +266,8 @@ class TestStdCapture: assert not err class TestStdCaptureFD(TestStdCapture): + pytestmark = needsdup + def getcapture(self, **kw): return py.io.StdCaptureFD(**kw) @@ -289,6 +296,7 @@ class TestStdCaptureFD(TestStdCapture): assert out.startswith("3") assert err.startswith("4") + at needsdup def test_capture_no_sys(): capsys = py.io.StdCapture() try: @@ -303,6 +311,7 @@ def test_capture_no_sys(): finally: capsys.reset() + at needsdup def test_callcapture_nofd(): def func(x, y): oswritebytes(1, "hello") From commits-noreply at bitbucket.org Fri Apr 23 12:32:09 2010 From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org) Date: Fri, 23 Apr 2010 10:32:09 +0000 (UTC) Subject: [py-svn] py-trunk commit 47b51ad2d27c: update distribute Message-ID: <20100423103209.8E3CC7EF08@bitbucket.org> # HG changeset patch -- Bitbucket.org # Project py-trunk # URL http://bitbucket.org/hpk42/py-trunk/overview # User holger krekel # Date 1272018671 -7200 # Node ID 47b51ad2d27cd2c41b5039f02a2e169857b3ddcd # Parent 65c135fcabab52164b362acd986a0aa3a579774c update distribute --- a/distribute_setup.py +++ b/distribute_setup.py @@ -46,19 +46,21 @@ except ImportError: args = [quote(arg) for arg in args] return os.spawnl(os.P_WAIT, sys.executable, *args) == 0 -DEFAULT_VERSION = "0.6.6" +DEFAULT_VERSION = "0.6.10" DEFAULT_URL = "http://pypi.python.org/packages/source/d/distribute/" +SETUPTOOLS_FAKED_VERSION = "0.6c11" + SETUPTOOLS_PKG_INFO = """\ Metadata-Version: 1.0 Name: setuptools -Version: 0.6c9 +Version: %s Summary: xxxx Home-page: xxx Author: xxx Author-email: xxx License: xxx Description: xxx -""" +""" % SETUPTOOLS_FAKED_VERSION def _install(tarball): @@ -79,12 +81,14 @@ def _install(tarball): # installing log.warn('Installing Distribute') - assert _python_cmd('setup.py', 'install') + if not _python_cmd('setup.py', 'install'): + log.warn('Something went wrong during the installation.') + log.warn('See the error message above.') finally: os.chdir(old_wd) -def _build_egg(tarball, to_dir): +def _build_egg(egg, tarball, to_dir): # extracting the tarball tmpdir = tempfile.mkdtemp() log.warn('Extracting in %s', tmpdir) @@ -104,27 +108,28 @@ def _build_egg(tarball, to_dir): log.warn('Building a Distribute egg in %s', to_dir) _python_cmd('setup.py', '-q', 'bdist_egg', '--dist-dir', to_dir) - # returning the result - for file in os.listdir(to_dir): - if fnmatch.fnmatch(file, 'distribute-%s*.egg' % DEFAULT_VERSION): - return os.path.join(to_dir, file) - - raise IOError('Could not build the egg.') finally: os.chdir(old_wd) + # returning the result + log.warn(egg) + if not os.path.exists(egg): + raise IOError('Could not build the egg.') def _do_download(version, download_base, to_dir, download_delay): - tarball = download_setuptools(version, download_base, - to_dir, download_delay) - egg = _build_egg(tarball, to_dir) + egg = os.path.join(to_dir, 'distribute-%s-py%d.%d.egg' + % (version, sys.version_info[0], sys.version_info[1])) + if not os.path.exists(egg): + tarball = download_setuptools(version, download_base, + to_dir, download_delay) + _build_egg(egg, tarball, to_dir) sys.path.insert(0, egg) import setuptools setuptools.bootstrap_install_from = egg def use_setuptools(version=DEFAULT_VERSION, download_base=DEFAULT_URL, - to_dir=os.curdir, download_delay=15, no_fake=False): + to_dir=os.curdir, download_delay=15, no_fake=True): # making sure we use the absolute path to_dir = os.path.abspath(to_dir) was_imported = 'pkg_resources' in sys.modules or \ @@ -134,7 +139,7 @@ def use_setuptools(version=DEFAULT_VERSI import pkg_resources if not hasattr(pkg_resources, '_distribute'): if not no_fake: - fake_setuptools() + _fake_setuptools() raise ImportError except ImportError: return _do_download(version, download_base, to_dir, download_delay) @@ -159,7 +164,8 @@ def use_setuptools(version=DEFAULT_VERSI return _do_download(version, download_base, to_dir, download_delay) finally: - _create_fake_setuptools_pkg_info(to_dir) + if not no_fake: + _create_fake_setuptools_pkg_info(to_dir) def download_setuptools(version=DEFAULT_VERSION, download_base=DEFAULT_URL, to_dir=os.curdir, delay=15): @@ -218,22 +224,34 @@ def _patch_file(path, content): def _same_content(path, content): return open(path).read() == content +def _no_sandbox(function): + def __no_sandbox(*args, **kw): + try: + from setuptools.sandbox import DirectorySandbox + def violation(*args): + pass + DirectorySandbox._old = DirectorySandbox._violation + DirectorySandbox._violation = violation + patched = True + except ImportError: + patched = False + try: + return function(*args, **kw) + finally: + if patched: + DirectorySandbox._violation = DirectorySandbox._old + del DirectorySandbox._old + + return __no_sandbox + + at _no_sandbox def _rename_path(path): new_name = path + '.OLD.%s' % time.time() log.warn('Renaming %s into %s', path, new_name) - try: - from setuptools.sandbox import DirectorySandbox - def _violation(*args): - pass - DirectorySandbox._violation = _violation - except ImportError: - pass - os.rename(path, new_name) return new_name - def _remove_flat_installation(placeholder): if not os.path.isdir(placeholder): log.warn('Unkown installation at %s', placeholder) @@ -273,22 +291,26 @@ def _after_install(dist): placeholder = dist.get_command_obj('install').install_purelib _create_fake_setuptools_pkg_info(placeholder) + at _no_sandbox def _create_fake_setuptools_pkg_info(placeholder): if not placeholder or not os.path.exists(placeholder): log.warn('Could not find the install location') return pyver = '%s.%s' % (sys.version_info[0], sys.version_info[1]) - setuptools_file = 'setuptools-0.6c9-py%s.egg-info' % pyver + setuptools_file = 'setuptools-%s-py%s.egg-info' % \ + (SETUPTOOLS_FAKED_VERSION, pyver) pkg_info = os.path.join(placeholder, setuptools_file) if os.path.exists(pkg_info): log.warn('%s already exists', pkg_info) return + log.warn('Creating %s', pkg_info) f = open(pkg_info, 'w') try: f.write(SETUPTOOLS_PKG_INFO) finally: f.close() + pth_file = os.path.join(placeholder, 'setuptools.pth') log.warn('Creating %s', pth_file) f = open(pth_file, 'w') @@ -297,7 +319,6 @@ def _create_fake_setuptools_pkg_info(pla finally: f.close() - def _patch_egg_dir(path): # let's check if it's already patched pkg_info = os.path.join(path, 'EGG-INFO', 'PKG-INFO') @@ -319,7 +340,7 @@ def _patch_egg_dir(path): def _before_install(): log.warn('Before install bootstrap.') - fake_setuptools() + _fake_setuptools() def _under_prefix(location): @@ -340,7 +361,7 @@ def _under_prefix(location): return True -def fake_setuptools(): +def _fake_setuptools(): log.warn('Scanning installed packages') try: import pkg_resources --- a/CHANGELOG +++ b/CHANGELOG @@ -6,6 +6,7 @@ Changes between 1.2.1 and 1.2.2 (release - (issue87) fix unboundlocal error in assertionold code - (issue86) improve documentation for looponfailing - fix jython/win32 issues +- ship distribute_setup.py version 0.6.10 Changes between 1.2.1 and 1.2.0 From commits-noreply at bitbucket.org Fri Apr 23 13:30:16 2010 From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org) Date: Fri, 23 Apr 2010 11:30:16 +0000 (UTC) Subject: [py-svn] py-trunk commit 27f91ac07ea8: a couple of more mostly jython-related fixes Message-ID: <20100423113016.ED3647EF39@bitbucket.org> # HG changeset patch -- Bitbucket.org # Project py-trunk # URL http://bitbucket.org/hpk42/py-trunk/overview # User holger krekel # Date 1272022168 -7200 # Node ID 27f91ac07ea82ccaff78a76d73b50a19f60c82eb # Parent 47b51ad2d27cd2c41b5039f02a2e169857b3ddcd a couple of more mostly jython-related fixes --- a/testing/path/common.py +++ b/testing/path/common.py @@ -132,7 +132,7 @@ class CommonFSTests(object): assert not l1.relto(l2) assert not l2.relto(l1) - @py.test.mark.xfail("sys.platform.startswith('java')") + #@py.test.mark.xfail("sys.platform.startswith('java')") def test_listdir(self, path1): l = path1.listdir() assert path1.join('sampledir') in l @@ -178,7 +178,7 @@ class CommonFSTests(object): assert "sampledir" in l assert "otherdir" in l - @py.test.mark.xfail("sys.platform.startswith('java')") + #@py.test.mark.xfail("sys.platform.startswith('java')") def test_visit_ignore(self, path1): p = path1.join('nonexisting') assert list(p.visit(ignore=py.error.ENOENT)) == [] --- a/testing/code/test_frame.py +++ /dev/null @@ -1,2 +0,0 @@ -import sys -import py --- a/testing/code/test_source.py +++ b/testing/code/test_source.py @@ -2,6 +2,8 @@ from py.code import Source import py import sys +failsonjython = py.test.mark.xfail("sys.platform.startswith('java')") + def test_source_str_function(): x = Source("3") assert str(x) == "3" @@ -78,6 +80,7 @@ def test_source_strip_multiline(): source2 = source.strip() assert source2.lines == [" hello"] + at failsonjython def test_syntaxerror_rerepresentation(): ex = py.test.raises(SyntaxError, py.code.compile, 'x x') assert ex.value.lineno == 1 @@ -129,6 +132,7 @@ class TestSourceParsingAndCompiling: exec (co, d) assert d['x'] == 3 + @failsonjython def test_compile_and_getsource_simple(self): co = py.code.compile("x=3") exec (co) @@ -199,6 +203,7 @@ class TestSourceParsingAndCompiling: assert isinstance(mod, ast.Module) compile(mod, "", "exec") + @failsonjython def test_compile_and_getsource(self): co = self.source.compile() py.builtin.exec_(co, globals()) @@ -255,6 +260,7 @@ def test_getstartingblock_multiline(): l = [i for i in x.source.lines if i.strip()] assert len(l) == 4 + at failsonjython def test_getline_finally(): def c(): pass excinfo = py.test.raises(TypeError, """ @@ -268,6 +274,7 @@ def test_getline_finally(): source = excinfo.traceback[-1].statement assert str(source).strip() == 'c(1)' + at failsonjython def test_getfuncsource_dynamic(): source = """ def f(): @@ -334,6 +341,7 @@ def test_getsource_fallback(): src = getsource(x) assert src == expected + at failsonjython def test_idem_compile_and_getsource(): from py._code.source import getsource expected = "def x(): pass" @@ -347,6 +355,7 @@ def test_findsource_fallback(): assert 'test_findsource_simple' in str(src) assert src[lineno] == ' def x():' + at failsonjython def test_findsource___source__(): from py._code.source import findsource co = py.code.compile("""if 1: @@ -364,6 +373,7 @@ def test_findsource___source__(): assert src[lineno] == " def x():" + at failsonjython def test_getfslineno(): from py.code import getfslineno --- a/py/_path/svnurl.py +++ b/py/_path/svnurl.py @@ -353,6 +353,8 @@ def parse_time_with_missing_year(timestr day = time.strptime(tparts.pop(0), '%d')[2] last = tparts.pop(0) # year or hour:minute try: + if ":" in last: + raise ValueError() year = time.strptime(last, '%Y')[0] hour = minute = 0 except ValueError: --- a/testing/log/test_log.py +++ b/testing/log/test_log.py @@ -117,7 +117,7 @@ class TestLogConsumer: def test_log_file(self, tmpdir): customlog = tmpdir.join('log.out') - py.log.setconsumer("default", open(str(customlog), 'w', buffering=1)) + py.log.setconsumer("default", open(str(customlog), 'w', 1)) py.log.Producer("default")("hello world #1") assert customlog.readlines() == ['[default] hello world #1\n'] --- a/testing/code/test_excinfo.py +++ b/testing/code/test_excinfo.py @@ -3,6 +3,8 @@ import py from py._code.code import FormattedExcinfo, ReprExceptionInfo queue = py.builtin._tryimport('queue', 'Queue') +failsonjython = py.test.mark.xfail("sys.platform.startswith('java')") + class TWMock: def __init__(self): self.lines = [] @@ -82,6 +84,7 @@ class TestTraceback_f_g_h: assert s.startswith("def f():") assert s.endswith("raise ValueError") + @failsonjython def test_traceback_entry_getsource_in_construct(self): source = py.code.Source("""\ def xyz(): @@ -216,7 +219,7 @@ def test_excinfo_repr(): def test_excinfo_str(): excinfo = py.test.raises(ValueError, h) s = str(excinfo) - assert s.startswith(__file__[:-1]) # pyc file + assert s.startswith(__file__[:-9]) # pyc file and $py.class assert s.endswith("ValueError") assert len(s.split(":")) >= 3 # on windows it's 4 @@ -290,6 +293,7 @@ class TestFormattedExcinfo: assert lines[0] == "| def f(x):" assert lines[1] == " pass" + @failsonjython def test_repr_source_excinfo(self): """ check if indentation is right """ pr = FormattedExcinfo() From commits-noreply at bitbucket.org Fri Apr 23 19:07:00 2010 From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org) Date: Fri, 23 Apr 2010 17:07:00 +0000 (UTC) Subject: [py-svn] py-trunk commit 7d94c647674d: another couple of checks on jython, still some problems Message-ID: <20100423170700.0A0477EE85@bitbucket.org> # HG changeset patch -- Bitbucket.org # Project py-trunk # URL http://bitbucket.org/hpk42/py-trunk/overview # User holger krekel # Date 1272042322 -7200 # Node ID 7d94c647674daa3aa07088c3167254cdd04debc1 # Parent 27f91ac07ea82ccaff78a76d73b50a19f60c82eb another couple of checks on jython, still some problems --- a/testing/path/common.py +++ b/testing/path/common.py @@ -132,7 +132,7 @@ class CommonFSTests(object): assert not l1.relto(l2) assert not l2.relto(l1) - #@py.test.mark.xfail("sys.platform.startswith('java')") + @py.test.mark.xfail("sys.platform.startswith('java')") def test_listdir(self, path1): l = path1.listdir() assert path1.join('sampledir') in l @@ -178,7 +178,7 @@ class CommonFSTests(object): assert "sampledir" in l assert "otherdir" in l - #@py.test.mark.xfail("sys.platform.startswith('java')") + @py.test.mark.xfail("sys.platform.startswith('java')") def test_visit_ignore(self, path1): p = path1.join('nonexisting') assert list(p.visit(ignore=py.error.ENOENT)) == [] --- a/py/_cmdline/pyconvert_unittest.py +++ b/py/_cmdline/pyconvert_unittest.py @@ -1,6 +1,10 @@ import re import sys -import parser + +try: + import parser +except ImportError: + parser = None d={} # d is the dictionary of unittest changes, keyed to the old name --- a/testing/path/test_svnurl.py +++ b/testing/path/test_svnurl.py @@ -13,6 +13,12 @@ class TestSvnURLCommandPath(CommonSvnTes def test_load(self, path1): super(TestSvnURLCommandPath, self).test_load(path1) + # the following two work on jython but not in local/svnwc + def test_listdir(self, path1): + super(TestSvnURLCommandPath, self).test_listdir(path1) + def test_visit_ignore(self, path1): + super(TestSvnURLCommandPath, self).test_visit_ignore(path1) + def test_svnurl_needs_arg(self, path1): py.test.raises(TypeError, "py.path.svnurl()") --- a/py/_code/code.py +++ b/py/_code/code.py @@ -523,7 +523,8 @@ class FormattedExcinfo(object): source = py.code.Source("???") line_index = 0 else: - line_index = entry.lineno - entry.getfirstlinesource() + # entry.getfirstlinesource() can be -1, should be 0 on jython + line_index = entry.lineno - max(entry.getfirstlinesource(), 0) lines = [] if self.style == "long": --- a/testing/root/test_py_imports.py +++ b/testing/root/test_py_imports.py @@ -9,7 +9,7 @@ def checksubpackage(name): keys = dir(obj) assert len(keys) > 0 print (obj.__map__) - for name in obj.__map__: + for name in list(obj.__map__): assert hasattr(obj, name), (obj, name) def test_dir(): --- a/testing/cmdline/test_convert_unittest.py +++ b/testing/cmdline/test_convert_unittest.py @@ -1,3 +1,5 @@ +import py +py.test.importorskip("parser") from py._cmdline.pyconvert_unittest import rewrite_utest --- a/testing/plugin/test_pytest_terminal.py +++ b/testing/plugin/test_pytest_terminal.py @@ -82,7 +82,7 @@ class TestTerminal: ]) def test_collect_fail(self, testdir, option): - p = testdir.makepyfile("import xyz") + p = testdir.makepyfile("import xyz\n") result = testdir.runpytest(*option._getcmdargs()) result.stdout.fnmatch_lines([ "*test_collect_fail.py E*", --- a/testing/plugin/test_pytest__pytest.py +++ b/testing/plugin/test_pytest__pytest.py @@ -1,5 +1,5 @@ import py -import sys +import os, sys from py._plugin.pytest__pytest import HookRecorder from py._test.pluginmanager import Registry @@ -17,7 +17,7 @@ def test_hookrecorder_basic(): def test_hookrecorder_basic_no_args_hook(): rec = HookRecorder(Registry()) - apimod = type(sys)('api') + apimod = type(os)('api') def xyz(): pass apimod.xyz = xyz --- a/testing/root/test_oldmagic.py +++ b/testing/root/test_oldmagic.py @@ -1,8 +1,10 @@ import py import sys, os +def fass(): + assert 1 == 2 def check_assertion(): - excinfo = py.test.raises(AssertionError, "assert 1 == 2") + excinfo = py.test.raises(AssertionError, fass) s = excinfo.exconly(tryshort=True) if not s == "assert 1 == 2": raise ValueError("assertion not enabled: got %s" % s) @@ -16,6 +18,7 @@ def test_invoke_assertion(recwarn, monke py.magic.revoke(assertion=True) recwarn.pop(DeprecationWarning) + at py.test.mark.skipif("sys.platform.startswith('java')") def test_invoke_compile(recwarn, monkeypatch): monkeypatch.setattr(py.builtin.builtins, 'compile', None) py.magic.invoke(compile=True) From commits-noreply at bitbucket.org Fri Apr 23 19:29:33 2010 From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org) Date: Fri, 23 Apr 2010 17:29:33 +0000 (UTC) Subject: [py-svn] py-trunk commit 5b46c3828463: this should test and fix the same issue that was committed in Message-ID: <20100423172933.17B577EF0F@bitbucket.org> # HG changeset patch -- Bitbucket.org # Project py-trunk # URL http://bitbucket.org/hpk42/py-trunk/overview # User holger krekel # Date 1272043721 -7200 # Node ID 5b46c382846322fa7f1701ab749a960aac2d78fe # Parent 7d94c647674daa3aa07088c3167254cdd04debc1 this should test and fix the same issue that was committed in the pypy svn-repo as r72534 --- a/py/_test/outcome.py +++ b/py/_test/outcome.py @@ -100,7 +100,7 @@ def raises(ExpectedException, *args, **k k = ", ".join(["%s=%r" % x for x in kwargs.items()]) if k: k = ', ' + k - expr = '%s(%r%s)' %(func.__name__, args, k) + expr = '%s(%r%s)' %(getattr(func, '__name__', func), args, k) raise ExceptionFailure(msg="DID NOT RAISE", expr=args, expected=ExpectedException) --- a/testing/test_outcome.py +++ b/testing/test_outcome.py @@ -15,6 +15,16 @@ class TestRaises: def test_raises_function(self): py.test.raises(ValueError, int, 'hello') + def test_raises_callable_no_exception(self): + from py._test.outcome import ExceptionFailure + class A: + def __call__(self): + pass + try: + py.test.raises(ValueError, A()) + except ExceptionFailure: + pass + def test_pytest_exit(): try: py.test.exit("hello") From commits-noreply at bitbucket.org Sat Apr 24 03:28:27 2010 From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org) Date: Sat, 24 Apr 2010 01:28:27 +0000 (UTC) Subject: [py-svn] py-trunk commit 4be9b9ce4c62: provide encoding to dupfile() for py3 Message-ID: <20100424012827.C8D4A7EEEA@bitbucket.org> # HG changeset patch -- Bitbucket.org # Project py-trunk # URL http://bitbucket.org/hpk42/py-trunk/overview # User Benjamin Peterson # Date 1272072454 18000 # Node ID 4be9b9ce4c62e0cae65debb2e3ec10795c110101 # Parent e3c665a014058758284fe581f3c5e8cda670bd48 provide encoding to dupfile() for py3 --- a/testing/io_/test_capture.py +++ b/testing/io_/test_capture.py @@ -76,7 +76,7 @@ def test_dupfile(tmpfile): somefile = tmpfile flist = [] for i in range(5): - nf = py.io.dupfile(somefile) + nf = py.io.dupfile(somefile, encoding="utf-8") assert nf != somefile assert nf.fileno() != somefile.fileno() assert nf not in flist From commits-noreply at bitbucket.org Sat Apr 24 03:28:29 2010 From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org) Date: Sat, 24 Apr 2010 01:28:29 +0000 (UTC) Subject: [py-svn] py-trunk commit ee52e1cb604f: merge main Message-ID: <20100424012829.D136C7EF00@bitbucket.org> # HG changeset patch -- Bitbucket.org # Project py-trunk # URL http://bitbucket.org/hpk42/py-trunk/overview # User Benjamin Peterson # Date 1272072512 18000 # Node ID ee52e1cb604f03bba06bcdd137f458b01dba3532 # Parent 4be9b9ce4c62e0cae65debb2e3ec10795c110101 # Parent 5b46c382846322fa7f1701ab749a960aac2d78fe merge main --- a/testing/code/test_frame.py +++ /dev/null @@ -1,2 +0,0 @@ -import sys -import py --- a/testing/io_/test_capture.py +++ b/testing/io_/test_capture.py @@ -1,6 +1,8 @@ import os, sys import py +needsdup = py.test.mark.skipif("not hasattr(os, 'dup')") + from py.builtin import print_ if sys.version_info >= (3,0): @@ -72,6 +74,7 @@ def pytest_funcarg__tmpfile(request): request.addfinalizer(f.close) return f + at needsdup def test_dupfile(tmpfile): somefile = tmpfile flist = [] @@ -91,6 +94,8 @@ def test_dupfile(tmpfile): somefile.close() class TestFDCapture: + pytestmark = needsdup + def test_stdout(self, tmpfile): fd = tmpfile.fileno() cap = py.io.FDCapture(fd) @@ -261,6 +266,8 @@ class TestStdCapture: assert not err class TestStdCaptureFD(TestStdCapture): + pytestmark = needsdup + def getcapture(self, **kw): return py.io.StdCaptureFD(**kw) @@ -289,6 +296,7 @@ class TestStdCaptureFD(TestStdCapture): assert out.startswith("3") assert err.startswith("4") + at needsdup def test_capture_no_sys(): capsys = py.io.StdCapture() try: @@ -303,6 +311,7 @@ def test_capture_no_sys(): finally: capsys.reset() + at needsdup def test_callcapture_nofd(): def func(x, y): oswritebytes(1, "hello") From commits-noreply at bitbucket.org Sat Apr 24 03:49:12 2010 From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org) Date: Sat, 24 Apr 2010 01:49:12 +0000 (UTC) Subject: [py-svn] py-trunk commit 7fa0f8cbb3c7: add a helper to get a function's code Message-ID: <20100424014912.781E07EEDE@bitbucket.org> # HG changeset patch -- Bitbucket.org # Project py-trunk # URL http://bitbucket.org/hpk42/py-trunk/overview # User Benjamin Peterson # Date 1272073180 18000 # Node ID 7fa0f8cbb3c754d43937d176e3a2f1b64e4b0d92 # Parent ee52e1cb604f03bba06bcdd137f458b01dba3532 add a helper to get a function's code --- a/py/_builtin.py +++ b/py/_builtin.py @@ -111,6 +111,9 @@ if sys.version_info >= (3, 0): def _getfuncdict(function): return getattr(function, "__dict__", None) + def _getcode(function): + return getattr(function, "__code__", None) + def execfile(fn, globs=None, locs=None): if globs is None: back = sys._getframe(1) @@ -147,6 +150,9 @@ else: def _getfuncdict(function): return getattr(function, "__dict__", None) + def _getcode(function): + return getattr(function, "func_code", None) + def print_(*args, **kwargs): """ minimal backport of py3k print statement. """ sep = ' ' --- a/testing/root/test_builtin.py +++ b/testing/root/test_builtin.py @@ -1,4 +1,5 @@ import sys +import types import py from py.builtin import set, frozenset, reversed, sorted @@ -146,3 +147,8 @@ def test_tryimport(): assert x == py x = py.builtin._tryimport('asldkajsdl', 'py.path') assert x == py.path + +def test_getcode(): + code = py.builtin._getcode(test_getcode) + assert isinstance(code, types.CodeType) + assert py.builtin._getcode(4) is None --- a/py/__init__.py +++ b/py/__init__.py @@ -125,6 +125,7 @@ py.apipkg.initpkg(__name__, dict( '_istext' : '._builtin:_istext', '_getimself' : '._builtin:_getimself', '_getfuncdict' : '._builtin:_getfuncdict', + '_getcode' : '._builtin:_getcode', 'builtins' : '._builtin:builtins', 'execfile' : '._builtin:execfile', 'callable' : '._builtin:callable', From commits-noreply at bitbucket.org Sat Apr 24 03:49:14 2010 From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org) Date: Sat, 24 Apr 2010 01:49:14 +0000 (UTC) Subject: [py-svn] py-trunk commit a2d0599603e6: fetch code object compatibly Message-ID: <20100424014914.6702D7EEDE@bitbucket.org> # HG changeset patch -- Bitbucket.org # Project py-trunk # URL http://bitbucket.org/hpk42/py-trunk/overview # User Benjamin Peterson # Date 1272073429 18000 # Node ID a2d0599603e611152571adb0e5fee7b9eb9936cf # Parent 7fa0f8cbb3c754d43937d176e3a2f1b64e4b0d92 fetch code object compatibly --- a/py/_plugin/pytest_terminal.py +++ b/py/_plugin/pytest_terminal.py @@ -505,7 +505,7 @@ class ShowFuncargSession(Session): def getlocation(self, function): import inspect fn = py.path.local(inspect.getfile(function)) - lineno = function.func_code.co_firstlineno + lineno = py.builtin._getcode(function).co_firstlineno if fn.relto(self.fspath): fn = fn.relto(self.fspath) return "%s:%d" %(fn, lineno+1) From commits-noreply at bitbucket.org Sat Apr 24 03:49:16 2010 From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org) Date: Sat, 24 Apr 2010 01:49:16 +0000 (UTC) Subject: [py-svn] py-trunk commit 227770df7a49: make test source syntax valid Message-ID: <20100424014916.4C5EC7EEE1@bitbucket.org> # HG changeset patch -- Bitbucket.org # Project py-trunk # URL http://bitbucket.org/hpk42/py-trunk/overview # User Benjamin Peterson # Date 1272073740 18000 # Node ID 227770df7a495e54d88e32cb8bc75331cdf4d439 # Parent a2d0599603e611152571adb0e5fee7b9eb9936cf make test source syntax valid --- a/testing/plugin/test_pytest_terminal.py +++ b/testing/plugin/test_pytest_terminal.py @@ -279,10 +279,9 @@ class TestTerminal: def pytest_report_header(config): return "hello: info" """) - testdir.mkdir("a").join("conftest.py").write("""if 1: - def pytest_report_header(config): - return ["line1", "line2"] - """) + testdir.mkdir("a").join("conftest.py").write(""" +def pytest_report_header(config): + return ["line1", "line2"]""") result = testdir.runpytest("a") result.stdout.fnmatch_lines([ "*hello: info*", From commits-noreply at bitbucket.org Mon Apr 26 10:14:57 2010 From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org) Date: Mon, 26 Apr 2010 08:14:57 +0000 (UTC) Subject: [py-svn] pytest-xdist commit e25fe5309e17: add to changelog Message-ID: <20100426081457.96FE97F08B@bitbucket.org> # HG changeset patch -- Bitbucket.org # Project pytest-xdist # URL http://bitbucket.org/hpk42/pytest-xdist/overview # User holger krekel # Date 1272269595 -7200 # Node ID e25fe5309e17578d9fab6b7661b4fd624086058e # Parent 870611b1ce77af2db83311d57e26f9ade13ce581 add to changelog --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,11 @@ +1.2 +------------------------- + +- introduce a new data input/output mechanism to allow the master side + to send and receive data from a slave. + +- fix PyPI description + 1.1 ------------------------- From commits-noreply at bitbucket.org Mon Apr 26 10:14:55 2010 From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org) Date: Mon, 26 Apr 2010 08:14:55 +0000 (UTC) Subject: [py-svn] pytest-xdist commit 870611b1ce77: Fix description. Message-ID: <20100426081455.6CAD97F089@bitbucket.org> # HG changeset patch -- Bitbucket.org # Project pytest-xdist # URL http://bitbucket.org/hpk42/pytest-xdist/overview # User Meme Dough # Date 1272257695 -36000 # Node ID 870611b1ce77af2db83311d57e26f9ade13ce581 # Parent a215e02c4bff050f0ce29e313838906f51abf686 Fix description. --- a/setup.py +++ b/setup.py @@ -12,7 +12,7 @@ from xdist import __version__ setup( name="pytest-xdist", version=__version__, - description='py.test figleaf coverage plugin', + description='py.test xdist plugin for distributed testing and loop-on-failing modes', long_description=__doc__, license='GPLv2 or later', author='holger krekel and contributors', From commits-noreply at bitbucket.org Mon Apr 26 18:36:40 2010 From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org) Date: Mon, 26 Apr 2010 16:36:40 +0000 (UTC) Subject: [py-svn] py-trunk commit b6403d15ec5c: factor out session main loop so that distribute testing can make use of it Message-ID: <20100426163640.799A27EF0D@bitbucket.org> # HG changeset patch -- Bitbucket.org # Project py-trunk # URL http://bitbucket.org/hpk42/py-trunk/overview # User holger krekel # Date 1272297399 -7200 # Node ID b6403d15ec5cd3ff9cb73663d6583b269c61c3e8 # Parent 227770df7a495e54d88e32cb8bc75331cdf4d439 factor out session main loop so that distribute testing can make use of it --- a/py/_test/session.py +++ b/py/_test/session.py @@ -97,22 +97,26 @@ class Session(object): self.shouldstop = False self.sessionstarts() exitstatus = outcome.EXIT_OK - captured_excinfo = None try: - for item in self.collect(colitems): - if self.shouldstop: - break - if not self.config.option.collectonly: - item.config.hook.pytest_runtest_protocol(item=item) + self._mainloop(colitems) + if self._testsfailed: + exitstatus = outcome.EXIT_TESTSFAILED + self.sessionfinishes(exitstatus=exitstatus) except KeyboardInterrupt: excinfo = py.code.ExceptionInfo() self.config.hook.pytest_keyboard_interrupt(excinfo=excinfo) exitstatus = outcome.EXIT_INTERRUPTED except: excinfo = py.code.ExceptionInfo() - self.config.pluginmanager.notify_exception(captured_excinfo) + self.config.pluginmanager.notify_exception(excinfo) exitstatus = outcome.EXIT_INTERNALERROR - if exitstatus == 0 and self._testsfailed: - exitstatus = outcome.EXIT_TESTSFAILED - self.sessionfinishes(exitstatus=exitstatus) + if exitstatus in (outcome.EXIT_INTERNALERROR, outcome.EXIT_INTERRUPTED): + self.sessionfinishes(exitstatus=exitstatus) return exitstatus + + def _mainloop(self, colitems): + for item in self.collect(colitems): + if self.shouldstop: + break + if not self.config.option.collectonly: + item.config.hook.pytest_runtest_protocol(item=item) From commits-noreply at bitbucket.org Mon Apr 26 18:53:01 2010 From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org) Date: Mon, 26 Apr 2010 16:53:01 +0000 (UTC) Subject: [py-svn] pytest-xdist commit 3f47ac13adae: internally rename SlaveNode to SlaveSession which fits better Message-ID: <20100426165301.329897EEEA@bitbucket.org> # HG changeset patch -- Bitbucket.org # Project pytest-xdist # URL http://bitbucket.org/hpk42/pytest-xdist/overview # User holger krekel # Date 1272289711 -7200 # Node ID 3f47ac13adae28d90269d63cabfe6d5c01bf0ee0 # Parent e25fe5309e17578d9fab6b7661b4fd624086058e internally rename SlaveNode to SlaveSession which fits better --- a/xdist/txnode.py +++ b/xdist/txnode.py @@ -89,11 +89,11 @@ def install_slave(node): import os, sys sys.path.insert(0, os.getcwd()) from xdist.mypickle import PickleChannel - from xdist.txnode import SlaveNode + from xdist.txnode import SlaveSession channel.send("basicimport") channel = PickleChannel(channel) - slavenode = SlaveNode(channel) - slavenode.run() + session = SlaveSession(channel) + session.run() """) channel.receive() channel = PickleChannel(channel) @@ -108,7 +108,7 @@ def install_slave(node): channel.send((config, node.slaveinput, basetemp, node.gateway.id)) return channel -class SlaveNode(object): +class SlaveSession(object): def __init__(self, channel): self.channel = channel From commits-noreply at bitbucket.org Mon Apr 26 18:53:03 2010 From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org) Date: Mon, 26 Apr 2010 16:53:03 +0000 (UTC) Subject: [py-svn] pytest-xdist commit e6baa833d832: fixes issue79 - call hooks more systematically on slave nodes and also in the case of SIGINT Message-ID: <20100426165303.852547EF4B@bitbucket.org> # HG changeset patch -- Bitbucket.org # Project pytest-xdist # URL http://bitbucket.org/hpk42/pytest-xdist/overview # User holger krekel # Date 1272299753 -7200 # Node ID e6baa833d83216d98e4df1a527b97525bb5deb62 # Parent 3f47ac13adae28d90269d63cabfe6d5c01bf0ee0 fixes issue79 - call hooks more systematically on slave nodes and also in the case of SIGINT reorganize internal session code to share more code with the "normal" non-distributed session --- a/xdist/txnode.py +++ b/xdist/txnode.py @@ -3,7 +3,7 @@ """ import py from xdist.mypickle import PickleChannel -from py._test import outcome +from py._test.session import Session class TXNode(object): """ Represents a Test Execution environment in the controlling process. @@ -45,7 +45,7 @@ class TXNode(object): err = self.channel._getremoteerror() if not self._down: if not err or isinstance(err, EOFError): - err = "Not properly terminated" + err = "Not properly terminated" # lost connection? self.notify("pytest_testnodedown", node=self, error=err) self._down = True return @@ -55,7 +55,8 @@ class TXNode(object): elif eventname == "slavefinished": self._down = True self.slaveoutput = kwargs['slaveoutput'] - self.notify("pytest_testnodedown", error=None, node=self) + error = kwargs['error'] + self.notify("pytest_testnodedown", error=error, node=self) elif eventname in ("pytest_runtest_logreport", "pytest__teardown_final_logerror"): kwargs['report'].node = self @@ -92,8 +93,16 @@ def install_slave(node): from xdist.txnode import SlaveSession channel.send("basicimport") channel = PickleChannel(channel) - session = SlaveSession(channel) - session.run() + import py + config, slaveinput, basetemp, nodeid = channel.receive() + config.slaveinput = slaveinput + config.slaveoutput = {} + if basetemp: + config.basetemp = py.path.local(basetemp) + config.nodeid = nodeid + config.pluginmanager.do_configure(config) + session = SlaveSession(config, channel, nodeid) + session.dist_main() """) channel.receive() channel = PickleChannel(channel) @@ -108,9 +117,11 @@ def install_slave(node): channel.send((config, node.slaveinput, basetemp, node.gateway.id)) return channel -class SlaveSession(object): - def __init__(self, channel): +class SlaveSession(Session): + def __init__(self, config, channel, nodeid): self.channel = channel + self.nodeid = nodeid + super(SlaveSession, self).__init__(config=config) def __repr__(self): return "<%s channel=%s>" %(self.__class__.__name__, self.channel) @@ -124,39 +135,31 @@ class SlaveSession(object): def pytest__teardown_final_logerror(self, report): self.sendevent("pytest__teardown_final_logerror", report=report) - def run(self): - channel = self.channel - self.config, slaveinput, basetemp, self.nodeid = channel.receive() - if basetemp: - self.config.basetemp = py.path.local(basetemp) - self.config.slaveinput = slaveinput - self.config.slaveoutput = {} - self.config.pluginmanager.do_configure(self.config) - self.config.pluginmanager.register(self) + def pytest_keyboard_interrupt(self, excinfo): + self._slaveerror = "SIGINT" + + def pytest_internalerror(self, excrepr): + self._slaveerror = "internal-error" + self.sendevent("pytest_internalerror", excrepr=excrepr) + + def dist_main(self): self.runner = self.config.pluginmanager.getplugin("pytest_runner") self.sendevent("slaveready") - try: - self.config.hook.pytest_sessionstart(session=self) - while 1: - task = channel.receive() - if task is None: - break - if isinstance(task, list): - for item in task: - self.run_single(item=item) - else: - self.run_single(item=task) - self.config.hook.pytest_sessionfinish( - session=self, - exitstatus=outcome.EXIT_OK) - except KeyboardInterrupt: - raise - except: - er = py.code.ExceptionInfo().getrepr(funcargs=True, showlocals=True) - self.sendevent("pytest_internalerror", excrepr=er) - raise - else: - self.sendevent("slavefinished", slaveoutput=self.config.slaveoutput) + self.main(None) + error = getattr(self, '_slaveerror', None) + self.sendevent("slavefinished", error=error, + slaveoutput=self.config.slaveoutput) + + def _mainloop(self, colitems): + while 1: + task = self.channel.receive() + if task is None: + break + if isinstance(task, list): + for item in task: + self.run_single(item=item) + else: + self.run_single(item=task) def run_single(self, item): call = self.runner.CallInfo(item._reraiseunpicklingproblem, when='setup') --- a/testing/acceptance_test.py +++ b/testing/acceptance_test.py @@ -152,3 +152,23 @@ class TestDistribution: "*1 passed*" ]) assert result.ret == 0 + + def test_keyboardinterrupt_hooks_issue79(self, testdir): + testdir.makepyfile(__init__="", test_one=""" + def test_hello(): + raise KeyboardInterrupt() + """) + testdir.makeconftest(""" + def pytest_sessionfinish(session): + if hasattr(session.config, 'slaveoutput'): + session.config.slaveoutput['s2'] = 42 + def pytest_testnodedown(node, error): + assert node.slaveoutput['s2'] == 42 + print "s2call-finished" + """) + args = ["-n1"] + result = testdir.runpytest(*args) + s = result.stdout.str() + assert result.ret + assert 'SIGINT' in s + assert 's2call' in s --- a/testing/test_txnode.py +++ b/testing/test_txnode.py @@ -81,7 +81,7 @@ class TestMasterSlaveConnection: node.send(123) # invalid item kwargs = mysetup.geteventargs("pytest_testnodedown") assert kwargs['node'] is node - assert isinstance(kwargs['error'], execnet.RemoteError) + #assert isinstance(kwargs['error'], execnet.RemoteError) def test_crash_killed(self, testdir, mysetup): if not hasattr(py.std.os, 'kill'): --- a/xdist/dsession.py +++ b/xdist/dsession.py @@ -132,15 +132,16 @@ class DSession(Session): call(**kwargs) # termination conditions - if ((loopstate.testsfailed and self.config.option.exitfirst) or + if (not self.node2pending or + (loopstate.testsfailed and self.config.option.exitfirst) or (not self.item2nodes and not colitems and not self.queue.qsize())): if self.config.option.exitfirst: raise ExitFirstInterrupt() self.triggershutdown() loopstate.shuttingdown = True - elif not self.node2pending: - loopstate.exitstatus = outcome.EXIT_NOHOSTS - + if not self.node2pending: + loopstate.exitstatus = outcome.EXIT_NOHOSTS + def loop_once_shutdown(self, loopstate): # once we are in shutdown mode we dont send # events other than HostDown upstream --- a/CHANGELOG +++ b/CHANGELOG @@ -1,6 +1,9 @@ 1.2 ------------------------- +- fix issue79: sessionfinish/teardown hooks more systematically + on the slave side + - introduce a new data input/output mechanism to allow the master side to send and receive data from a slave. From commits-noreply at bitbucket.org Mon Apr 26 18:57:34 2010 From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org) Date: Mon, 26 Apr 2010 16:57:34 +0000 (UTC) Subject: [py-svn] pytest-xdist commit f4525fd1fae6: fix py3 print issue Message-ID: <20100426165734.A82A37EEEA@bitbucket.org> # HG changeset patch -- Bitbucket.org # Project pytest-xdist # URL http://bitbucket.org/hpk42/pytest-xdist/overview # User holger krekel # Date 1272301028 -7200 # Node ID f4525fd1fae687bd7777be2ecf456be457f53c10 # Parent e6baa833d83216d98e4df1a527b97525bb5deb62 fix py3 print issue --- a/testing/acceptance_test.py +++ b/testing/acceptance_test.py @@ -164,7 +164,7 @@ class TestDistribution: session.config.slaveoutput['s2'] = 42 def pytest_testnodedown(node, error): assert node.slaveoutput['s2'] == 42 - print "s2call-finished" + print ("s2call-finished") """) args = ["-n1"] result = testdir.runpytest(*args) From commits-noreply at bitbucket.org Tue Apr 27 12:23:59 2010 From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org) Date: Tue, 27 Apr 2010 10:23:59 +0000 (UTC) Subject: [py-svn] py-trunk commit e387b83b6267: fix unicode issues (port of pypy/py repo changeset r72526 by Armin) Message-ID: <20100427102359.3574A7EF0C@bitbucket.org> # HG changeset patch -- Bitbucket.org # Project py-trunk # URL http://bitbucket.org/hpk42/py-trunk/overview # User holger krekel # Date 1272363793 -7200 # Node ID e387b83b6267b3fd7e44b59b899299e528ce6733 # Parent b6403d15ec5cd3ff9cb73663d6583b269c61c3e8 fix unicode issues (port of pypy/py repo changeset r72526 by Armin) --- a/py/_code/code.py +++ b/py/_code/code.py @@ -581,7 +581,10 @@ class TerminalRepr: def __str__(self): tw = py.io.TerminalWriter(stringio=True) self.toterminal(tw) - return tw.stringio.getvalue().strip() + s = tw.stringio.getvalue().strip() + if sys.version_info[0] < 3: + s = s.encode('utf-8') + return s def __repr__(self): return "<%s instance at %0x>" %(self.__class__, id(self)) --- a/testing/code/test_code.py +++ b/testing/code/test_code.py @@ -192,3 +192,10 @@ def test_builtin_patch_unpatch(monkeypat assert cpy_builtin.AssertionError is Sub assert cpy_builtin.compile == mycompile + +def test_unicode_handling(testdir): + value = py.builtin._totext('\xc4\x85\xc4\x87\n', 'utf-8').encode('utf8') + def f(): + raise Exception(value) + excinfo = py.test.raises(Exception, f) + s = str(excinfo) --- a/CHANGELOG +++ b/CHANGELOG @@ -2,10 +2,11 @@ Changes between 1.2.1 and 1.2.2 (release ================================================== - new mechanism to allow plugins to register new hooks +- fixes for handling of unicode exception values - added links to the new capturelog and coverage plugins - (issue87) fix unboundlocal error in assertionold code - (issue86) improve documentation for looponfailing -- fix jython/win32 issues +- fixes for making jython/win32 work more properly - ship distribute_setup.py version 0.6.10 From commits-noreply at bitbucket.org Tue Apr 27 15:16:35 2010 From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org) Date: Tue, 27 Apr 2010 13:16:35 +0000 (UTC) Subject: [py-svn] py-trunk commit e368347f7d13: (fixes issue85) correctly write non-ascii test output to junitxml files, refine some internal methods for it Message-ID: <20100427131635.A61367EEF7@bitbucket.org> # HG changeset patch -- Bitbucket.org # Project py-trunk # URL http://bitbucket.org/hpk42/py-trunk/overview # User holger krekel # Date 1272374143 -7200 # Node ID e368347f7d1342098d61d6abe8b54361e7859b4f # Parent e387b83b6267b3fd7e44b59b899299e528ce6733 (fixes issue85) correctly write non-ascii test output to junitxml files, refine some internal methods for it --- a/CHANGELOG +++ b/CHANGELOG @@ -2,6 +2,7 @@ Changes between 1.2.1 and 1.2.2 (release ================================================== - new mechanism to allow plugins to register new hooks +- (issue85) fix junitxml plugin to handle tests with non-ascii output - fixes for handling of unicode exception values - added links to the new capturelog and coverage plugins - (issue87) fix unboundlocal error in assertionold code --- a/testing/plugin/test_pytest_junitxml.py +++ b/testing/plugin/test_pytest_junitxml.py @@ -1,5 +1,6 @@ from xml.dom import minidom +import py def runandparse(testdir, *args): resultpath = testdir.tmpdir.join("junit.xml") @@ -118,6 +119,19 @@ class TestPython: fnode = tnode.getElementsByTagName("skipped")[0] assert_attr(fnode, message="collection skipped") + def test_unicode(self, testdir): + value = 'hx\xc4\x85\xc4\x87\n' + testdir.makepyfile(""" + def test_hello(): + print (%r) + assert 0 + """ % value) + result, dom = runandparse(testdir) + assert result.ret == 1 + tnode = dom.getElementsByTagName("testcase")[0] + fnode = tnode.getElementsByTagName("failure")[0] + assert "hx" in fnode.toxml() + class TestNonPython: def test_summing_simple(self, testdir): testdir.makeconftest(""" --- a/py/_plugin/pytest_junitxml.py +++ b/py/_plugin/pytest_junitxml.py @@ -43,6 +43,10 @@ class LogXML(object): def _closetestcase(self): self.test_logs.append("") + + def appendlog(self, fmt, *args): + args = tuple([py.xml.escape(arg) for arg in args]) + self.test_logs.append(fmt % args) def append_pass(self, report): self.passed += 1 @@ -51,10 +55,9 @@ class LogXML(object): 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.appendlog('%s', + report.longrepr) self._closetestcase() self.failed += 1 @@ -69,33 +72,30 @@ class LogXML(object): 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.appendlog('%s', + report.longrepr) 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.appendlog('%s', + report.longrepr) self._closetestcase() self.skipped += 1 def append_error(self, report): self._opentestcase(report) - s = py.xml.escape(str(report.longrepr)) - self.test_logs.append( - '%s' % s) + self.appendlog('%s', + report.longrepr) self._closetestcase() self.errors += 1 def append_skipped(self, report): self._opentestcase(report) - self.test_logs.append("") + self.appendlog("") self._closetestcase() self.skipped += 1 @@ -126,7 +126,7 @@ class LogXML(object): def pytest_internalerror(self, excrepr): self.errors += 1 - data = py.xml.escape(str(excrepr)) + data = py.xml.escape(excrepr) self.test_logs.append( '\n' ' ' @@ -136,7 +136,11 @@ class LogXML(object): self.suite_start_time = time.time() def pytest_sessionfinish(self, session, exitstatus, __multicall__): - logfile = open(self.logfile, 'w', 1) # line buffered + if py.std.sys.version_info[0] < 3: + logfile = py.std.codecs.open(self.logfile, 'w', encoding='utf-8') + else: + logfile = open(self.logfile, 'w', encoding='utf-8') + suite_stop_time = time.time() suite_time_delta = suite_stop_time - self.suite_start_time numtests = self.passed + self.failed --- a/py/_code/code.py +++ b/py/_code/code.py @@ -417,6 +417,12 @@ class ExceptionInfo(object): loc = ReprFileLocation(entry.path, entry.lineno + 1, self.exconly()) return str(loc) + def __unicode__(self): + entry = self.traceback[-1] + loc = ReprFileLocation(entry.path, entry.lineno + 1, self.exconly()) + return unicode(loc) + + class FormattedExcinfo(object): """ presenting information about failing Functions and Generators. """ # for traceback entries @@ -579,11 +585,15 @@ class FormattedExcinfo(object): class TerminalRepr: def __str__(self): + s = self.__unicode__() + if sys.version_info[0] < 3: + s = s.encode('utf-8') + return s + + def __unicode__(self): tw = py.io.TerminalWriter(stringio=True) self.toterminal(tw) s = tw.stringio.getvalue().strip() - if sys.version_info[0] < 3: - s = s.encode('utf-8') return s def __repr__(self): --- a/testing/root/test_xmlgen.py +++ b/testing/root/test_xmlgen.py @@ -5,6 +5,25 @@ from py._xmlgen import unicode, html class ns(py.xml.Namespace): pass +def test_escape(): + uvalue = py.builtin._totext('\xc4\x85\xc4\x87\n', 'utf-8') + class A: + def __unicode__(self): + return uvalue + def __str__(self): + x = self.__unicode__() + if py.std.sys.version_info[0] < 3: + return x.encode('utf-8') + return x + y = py.xml.escape(uvalue) + assert y == uvalue + x = py.xml.escape(A()) + assert x == uvalue + if py.std.sys.version_info[0] < 3: + assert isinstance(x, unicode) + assert isinstance(y, unicode) + + def test_tag_with_text(): x = ns.hello("world") u = unicode(x) --- a/testing/code/test_code.py +++ b/testing/code/test_code.py @@ -199,3 +199,5 @@ def test_unicode_handling(testdir): raise Exception(value) excinfo = py.test.raises(Exception, f) s = str(excinfo) + if sys.version_info[0] < 3: + u = unicode(excinfo) --- a/py/_xmlgen.py +++ b/py/_xmlgen.py @@ -238,6 +238,7 @@ class _escape: def __call__(self, ustring): """ xml-escape the given unicode string. """ + ustring = unicode(ustring) return self.charef_rex.sub(self._replacer, ustring) escape = _escape() From commits-noreply at bitbucket.org Tue Apr 27 15:49:39 2010 From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org) Date: Tue, 27 Apr 2010 13:49:39 +0000 (UTC) Subject: [py-svn] py-trunk commit c9b711ddbfe4: (fixes issue83) don't try to import conftest from an invalid package path, refine path.pyimport() logic Message-ID: <20100427134939.84DDE7EF1D@bitbucket.org> # HG changeset patch -- Bitbucket.org # Project py-trunk # URL http://bitbucket.org/hpk42/py-trunk/overview # User holger krekel # Date 1272376153 -7200 # Node ID c9b711ddbfe441d8b5ae5d17975db3559399210c # Parent e368347f7d1342098d61d6abe8b54361e7859b4f (fixes issue83) don't try to import conftest from an invalid package path, refine path.pyimport() logic --- a/py/_path/local.py +++ b/py/_path/local.py @@ -456,7 +456,8 @@ class LocalPath(FSBase): def pypkgpath(self, pkgname=None): """ return the path's package path by looking for the given pkgname. If pkgname is None then look for the last - directory upwards which still contains an __init__.py. + directory upwards which still contains an __init__.py + and whose basename is python-importable. Return None if a pkgpath can not be determined. """ pkgpath = None @@ -464,6 +465,8 @@ class LocalPath(FSBase): if pkgname is None: if parent.check(file=1): continue + if not isimportable(parent.basename): + break if parent.join('__init__.py').check(): pkgpath = parent continue @@ -797,3 +800,6 @@ def autopath(globs=None): ret.pkgdir = pkgdir return ret + +def isimportable(name): + return name[0].isalpha() and name.isalnum() --- a/testing/path/test_local.py +++ b/testing/path/test_local.py @@ -355,6 +355,14 @@ def test_pypkgdir(tmpdir): assert pkg.pypkgpath() == pkg assert pkg.join('subdir', '__init__.py').pypkgpath() == pkg +def test_pypkgdir_unimportable(tmpdir): + pkg = tmpdir.ensure('pkg1-1', dir=1) # unimportable + pkg.ensure("__init__.py") + subdir = pkg.ensure("subdir/__init__.py").dirpath() + assert subdir.pypkgpath() == subdir + assert subdir.ensure("xyz.py").pypkgpath() == subdir + assert not pkg.pypkgpath() + def test_homedir(): homedir = py.path.local._gethomedir() assert homedir.check(dir=1) --- a/testing/test_conftesthandle.py +++ b/testing/test_conftesthandle.py @@ -90,6 +90,13 @@ class TestConftestValueAccessGlobal: assert path.dirpath() == basedir.join("adir", "b") assert path.purebasename == "conftest" +def test_conftest_in_nonpkg_with_init(tmpdir): + tmpdir.ensure("adir-1.0/conftest.py").write("a=1 ; Directory = 3") + tmpdir.ensure("adir-1.0/b/conftest.py").write("b=2 ; a = 1.5") + tmpdir.ensure("adir-1.0/b/__init__.py") + tmpdir.ensure("adir-1.0/__init__.py") + conftest = ConftestWithSetinitial(tmpdir.join("adir-1.0", "b")) + def test_conftestcutdir(testdir): conf = testdir.makeconftest("") p = testdir.mkdir("x") --- a/testing/test_config.py +++ b/testing/test_config.py @@ -169,6 +169,7 @@ class TestConfigApi_getinitialnodes: assert col.config is config def test_pkgfile(self, testdir, tmpdir): + tmpdir = tmpdir.join("subdir") x = tmpdir.ensure("x.py") tmpdir.ensure("__init__.py") config = testdir.reparseconfig([x]) From commits-noreply at bitbucket.org Tue Apr 27 16:13:01 2010 From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org) Date: Tue, 27 Apr 2010 14:13:01 +0000 (UTC) Subject: [py-svn] py-trunk commit fb268e522e03: refine/fix isimportable-logic and ensure that 'tmpdir' has a python-importable name Message-ID: <20100427141301.02B497EF1C@bitbucket.org> # HG changeset patch -- Bitbucket.org # Project py-trunk # URL http://bitbucket.org/hpk42/py-trunk/overview # User holger krekel # Date 1272377425 -7200 # Node ID fb268e522e039509461e5d9c4aa007daacf2aec0 # Parent c9b711ddbfe441d8b5ae5d17975db3559399210c refine/fix isimportable-logic and ensure that 'tmpdir' has a python-importable name --- a/py/_path/local.py +++ b/py/_path/local.py @@ -802,4 +802,8 @@ def autopath(globs=None): def isimportable(name): - return name[0].isalpha() and name.isalnum() + if name: + if not (name[0].isalpha() or name[0] == '_'): + return False + name= name.replace("_", '') + return not name or name.isalnum() --- a/py/_test/config.py +++ b/py/_test/config.py @@ -151,7 +151,7 @@ class Config(object): if not numbered: return basetemp.mkdir(basename) else: - return py.path.local.make_numbered_dir(prefix=basename + "-", + return py.path.local.make_numbered_dir(prefix=basename, keep=0, rootdir=basetemp, lock_timeout=None) def getinitialnodes(self): --- a/testing/path/test_local.py +++ b/testing/path/test_local.py @@ -363,6 +363,17 @@ def test_pypkgdir_unimportable(tmpdir): assert subdir.ensure("xyz.py").pypkgpath() == subdir assert not pkg.pypkgpath() +def test_isimportable(): + from py._path.local import isimportable + assert not isimportable("") + assert isimportable("x") + assert isimportable("x1") + assert isimportable("x_1") + assert isimportable("_") + assert isimportable("_1") + assert not isimportable("x-1") + assert not isimportable("x:1") + def test_homedir(): homedir = py.path.local._gethomedir() assert homedir.check(dir=1) --- a/testing/plugin/test_pytest_tmpdir.py +++ b/testing/plugin/test_pytest_tmpdir.py @@ -5,5 +5,5 @@ def test_funcarg(testdir): item = testdir.getitem("def test_func(tmpdir): pass") p = pytest_funcarg__tmpdir(FuncargRequest(item)) assert p.check() - bn = p.basename.strip("0123456789-") + bn = p.basename.strip("0123456789") assert bn.endswith("test_func") From commits-noreply at bitbucket.org Tue Apr 27 16:37:52 2010 From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org) Date: Tue, 27 Apr 2010 14:37:52 +0000 (UTC) Subject: [py-svn] py-trunk commit dea3c96b016d: refining changelog + draft release announcement Message-ID: <20100427143752.B9E497EF1D@bitbucket.org> # HG changeset patch -- Bitbucket.org # Project py-trunk # URL http://bitbucket.org/hpk42/py-trunk/overview # User holger krekel # Date 1272379050 -7200 # Node ID dea3c96b016d6da00bccb4adf007bc112932c885 # Parent fb268e522e039509461e5d9c4aa007daacf2aec0 refining changelog + draft release announcement --- /dev/null +++ b/doc/announce/release-1.2.2.txt @@ -0,0 +1,34 @@ +py.test/pylib 1.2.2: bugfixes and per-plugin hooks +-------------------------------------------------------------------------------- + +py.test-1.2.2 is a minor release with brings a number of +bug fixes and improved compatibility with Python3 and Jython-2.5.1 on Windows. +It also is the first release that allows plugins to dynamically register +new hooks (which might be implemented by yet other plugins) allowing plugins to +interoperate without requiring a new core release. + +py.test is an advanced automated testing tool working with +Python2, Python3 and Jython versions on all major operating +systems. It has a simple plugin architecture and can run many +existing common Python test suites without modification. It offers +some unique features not found in other testing tools. +See http://pytest.org for more info. + +For more detailed information see the changelog below. + +cheers and have fun, + +holger + +Changes between 1.2.1 and 1.2.2 (release pending) +================================================== + +- new mechanism to allow plugins to register new hooks +- (issue85) fix junitxml plugin to handle tests with non-ascii output +- fix some python3 compatibility issues (thanks Benjamin Peterson) +- fixes for making the jython/win32 combination work +- fixes for handling of unicode exception values +- added links to the new capturelog and coverage plugins +- (issue87) fix unboundlocal error in assertionold code +- (issue86) improve documentation for looponfailing +- ship distribute_setup.py version 0.6.10 --- a/CHANGELOG +++ b/CHANGELOG @@ -3,11 +3,12 @@ Changes between 1.2.1 and 1.2.2 (release - new mechanism to allow plugins to register new hooks - (issue85) fix junitxml plugin to handle tests with non-ascii output +- fix some python3 compatibility issues (thanks Benjamin Peterson) +- fixes for making the jython/win32 combination work - fixes for handling of unicode exception values - added links to the new capturelog and coverage plugins - (issue87) fix unboundlocal error in assertionold code - (issue86) improve documentation for looponfailing -- fixes for making jython/win32 work more properly - ship distribute_setup.py version 0.6.10 From commits-noreply at bitbucket.org Tue Apr 27 17:53:46 2010 From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org) Date: Tue, 27 Apr 2010 15:53:46 +0000 (UTC) Subject: [py-svn] py-trunk commit d622f7f24d98: strike unused (buggy) keyword param Message-ID: <20100427155346.6B5227EEED@bitbucket.org> # HG changeset patch -- Bitbucket.org # Project py-trunk # URL http://bitbucket.org/hpk42/py-trunk/overview # User holger krekel # Date 1272383587 -7200 # Node ID d622f7f24d98556d71e37e395e2ed0c857ad72fb # Parent dea3c96b016d6da00bccb4adf007bc112932c885 strike unused (buggy) keyword param --- a/py/_test/collect.py +++ b/py/_test/collect.py @@ -110,17 +110,15 @@ class Node(object): setattr(self, attrname, res) return res - def listchain(self, rootfirst=False): + def listchain(self): """ return list of all parent collectors up to self, - starting form root of collection tree. """ + starting from root of collection tree. """ l = [self] while 1: - x = l[-1] + x = l[0] if x.parent is not None and x.parent.parent is not None: - l.append(x.parent) + l.insert(0, x.parent) else: - if not rootfirst: - l.reverse() return l def listnames(self): From commits-noreply at bitbucket.org Tue Apr 27 21:16:51 2010 From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org) Date: Tue, 27 Apr 2010 19:16:51 +0000 (UTC) Subject: [py-svn] py-trunk commit 52f5dfc7ef98: * properly expose and document runtest-protocol related Exceptions Message-ID: <20100427191651.B62AD7EF2D@bitbucket.org> # HG changeset patch -- Bitbucket.org # Project py-trunk # URL http://bitbucket.org/hpk42/py-trunk/overview # User holger krekel # Date 1272395589 -7200 # Node ID 52f5dfc7ef98dd8d1cbca6314aae3fb83a360706 # Parent d622f7f24d98556d71e37e395e2ed0c857ad72fb * properly expose and document runtest-protocol related Exceptions and move all definitions to the runner plugin for now. * also move EXIT codes to session.py, obsoleting outcome.py alltogether. --- a/py/_plugin/pytest_pdb.py +++ b/py/_plugin/pytest_pdb.py @@ -3,7 +3,6 @@ interactive debugging with the Python De """ import py import pdb, sys, linecache -from py._test.outcome import Skipped def pytest_addoption(parser): group = parser.getgroup("general") @@ -17,7 +16,8 @@ def pytest_configure(config): class PdbInvoke: def pytest_runtest_makereport(self, item, call): - if call.excinfo and not call.excinfo.errisinstance(Skipped): + if call.excinfo and not \ + call.excinfo.errisinstance(py.test.exc.Skipped): # play well with capturing, slightly hackish capman = item.config.pluginmanager.getplugin('capturemanager') capman.suspendcapture() --- a/py/_plugin/pytest_runner.py +++ b/py/_plugin/pytest_runner.py @@ -3,7 +3,23 @@ collect and run test items and create re """ import py, sys -from py._test.outcome import Skipped + +def pytest_namespace(): + class exc: + """ namespace holding py.test runner exceptions. """ + Skipped = Skipped + ExceptionFailure = ExceptionFailure + Failed = Failed + Exit = Exit + + return { + 'exc' : exc, + 'raises' : raises, + 'skip' : skip, + 'importorskip' : importorskip, + 'fail' : fail, + 'exit' : exit, + } # # pytest plugin hooks @@ -141,7 +157,7 @@ class ItemTestReport(BaseReport): self.failed = True shortrepr = "?" longrepr = excinfo - elif excinfo.errisinstance(Skipped): + elif excinfo.errisinstance(py.test.exc.Skipped): self.skipped = True shortrepr = "s" longrepr = self.item._repr_failure_py(excinfo) @@ -180,7 +196,7 @@ class CollectReport(BaseReport): self.result = result else: self.longrepr = self.collector._repr_failure_py(excinfo) - if excinfo.errisinstance(Skipped): + if excinfo.errisinstance(py.test.exc.Skipped): self.skipped = True self.reason = str(excinfo.value) else: @@ -259,3 +275,125 @@ class SetupState(object): except Exception: col._prepare_exc = sys.exc_info() raise + +# ============================================================= +# Test OutcomeExceptions and helpers for creating them. + + +class OutcomeException(Exception): + """ OutcomeException and its subclass instances indicate and + contain info about test and collection outcomes. + """ + def __init__(self, msg=None, excinfo=None): + self.msg = msg + self.excinfo = excinfo + + def __repr__(self): + if self.msg: + return repr(self.msg) + return "<%s instance>" %(self.__class__.__name__,) + __str__ = __repr__ + +class Skipped(OutcomeException): + # XXX slighly hackish: on 3k we fake to live in the builtins + # in order to have Skipped exception printing shorter/nicer + __module__ = 'builtins' + +class Failed(OutcomeException): + """ raised from an explicit call to py.test.fail() """ + +class ExceptionFailure(Failed): + """ raised by py.test.raises on an exception-assertion mismatch. """ + def __init__(self, expr, expected, msg=None, excinfo=None): + Failed.__init__(self, msg=msg, excinfo=excinfo) + self.expr = expr + self.expected = expected + +class Exit(KeyboardInterrupt): + """ raised by py.test.exit for immediate program exits without tracebacks and reporter/summary. """ + def __init__(self, msg="unknown reason"): + self.msg = msg + KeyboardInterrupt.__init__(self, msg) + +# exposed helper methods + +def exit(msg): + """ exit testing process as if KeyboardInterrupt was triggered. """ + __tracebackhide__ = True + raise Exit(msg) + +def skip(msg=""): + """ 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=""): + """ explicitely fail an currently-executing test with the given Message. """ + __tracebackhide__ = True + raise Failed(msg=msg) + +def raises(ExpectedException, *args, **kwargs): + """ 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 + if isinstance(args[0], str): + code, = args + assert isinstance(code, str) + frame = sys._getframe(1) + loc = frame.f_locals.copy() + loc.update(kwargs) + #print "raises frame scope: %r" % frame.f_locals + try: + code = py.code.Source(code).compile() + py.builtin.exec_(code, frame.f_globals, loc) + # XXX didn'T mean f_globals == f_locals something special? + # this is destroyed here ... + except ExpectedException: + return py.code.ExceptionInfo() + else: + func = args[0] + try: + func(*args[1:], **kwargs) + except ExpectedException: + return py.code.ExceptionInfo() + k = ", ".join(["%s=%r" % x for x in kwargs.items()]) + if k: + k = ', ' + k + expr = '%s(%r%s)' %(getattr(func, '__name__', func), args, k) + raise ExceptionFailure(msg="DID NOT RAISE", + expr=args, expected=ExpectedException) + +def importorskip(modname, minversion=None): + """ 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__']) + except ImportError: + py.test.skip("could not import %r" %(modname,)) + if minversion is None: + return mod + verattr = getattr(mod, '__version__', None) + if isinstance(minversion, str): + minver = minversion.split(".") + else: + minver = list(minversion) + if verattr is None or verattr.split(".") < minver: + py.test.skip("module %r has __version__ %r, required is: %r" %( + modname, verattr, minversion)) + return mod + --- a/testing/plugin/test_pytest_doctest.py +++ b/testing/plugin/test_pytest_doctest.py @@ -45,8 +45,6 @@ class TestDoctests: reprec.assertoutcome(failed=1) def test_doctest_unexpected_exception(self, testdir): - from py._test.outcome import Failed - p = testdir.maketxtfile(""" >>> i = 0 >>> i = 1 --- a/py/__init__.py +++ b/py/__init__.py @@ -38,11 +38,6 @@ py.apipkg.initpkg(__name__, dict( # helpers for use from test functions or collectors '__onfirstaccess__' : '._test.config:onpytestaccess', '__doc__' : '._test:__doc__', - 'raises' : '._test.outcome:raises', - 'skip' : '._test.outcome:skip', - 'importorskip' : '._test.outcome:importorskip', - 'fail' : '._test.outcome:fail', - 'exit' : '._test.outcome:exit', # configuration/initialization related test api 'config' : '._test.config:config_per_process', 'ensuretemp' : '._test.config:ensuretemp', --- a/py/_test/outcome.py +++ /dev/null @@ -1,136 +0,0 @@ -""" - Test OutcomeExceptions and helpers for creating them. - py.test.skip|fail|raises helper implementations - -""" - -import py -import sys - -class OutcomeException(Exception): - """ OutcomeException and its subclass instances indicate and - contain info about test and collection outcomes. - """ - def __init__(self, msg=None, excinfo=None): - self.msg = msg - self.excinfo = excinfo - - def __repr__(self): - if self.msg: - return repr(self.msg) - return "<%s instance>" %(self.__class__.__name__,) - __str__ = __repr__ - -class Passed(OutcomeException): - pass - -class Skipped(OutcomeException): - # XXX slighly hackish: on 3k we fake to live in the builtins - # in order to have Skipped exception printing shorter/nicer - __module__ = 'builtins' - -class Failed(OutcomeException): - pass - -class ExceptionFailure(Failed): - def __init__(self, expr, expected, msg=None, excinfo=None): - Failed.__init__(self, msg=msg, excinfo=excinfo) - self.expr = expr - self.expected = expected - -class Exit(KeyboardInterrupt): - """ for immediate program exits without tracebacks and reporter/summary. """ - def __init__(self, msg="unknown reason"): - self.msg = msg - KeyboardInterrupt.__init__(self, msg) - -# exposed helper methods - -def exit(msg): - """ exit testing process as if KeyboardInterrupt was triggered. """ - __tracebackhide__ = True - raise Exit(msg) - -def skip(msg=""): - """ 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=""): - """ explicitely fail this executing test with the given Message. """ - __tracebackhide__ = True - raise Failed(msg=msg) - -def raises(ExpectedException, *args, **kwargs): - """ 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 - if isinstance(args[0], str): - code, = args - assert isinstance(code, str) - frame = sys._getframe(1) - loc = frame.f_locals.copy() - loc.update(kwargs) - #print "raises frame scope: %r" % frame.f_locals - try: - code = py.code.Source(code).compile() - py.builtin.exec_(code, frame.f_globals, loc) - # XXX didn'T mean f_globals == f_locals something special? - # this is destroyed here ... - except ExpectedException: - return py.code.ExceptionInfo() - else: - func = args[0] - try: - func(*args[1:], **kwargs) - except ExpectedException: - return py.code.ExceptionInfo() - k = ", ".join(["%s=%r" % x for x in kwargs.items()]) - if k: - k = ', ' + k - expr = '%s(%r%s)' %(getattr(func, '__name__', func), args, k) - raise ExceptionFailure(msg="DID NOT RAISE", - expr=args, expected=ExpectedException) - -def importorskip(modname, minversion=None): - """ 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__']) - except ImportError: - py.test.skip("could not import %r" %(modname,)) - if minversion is None: - return mod - verattr = getattr(mod, '__version__', None) - if isinstance(minversion, str): - minver = minversion.split(".") - else: - minver = list(minversion) - if verattr is None or verattr.split(".") < minver: - py.test.skip("module %r has __version__ %r, required is: %r" %( - modname, verattr, minversion)) - return mod - - - -# exitcodes for the command line -EXIT_OK = 0 -EXIT_TESTSFAILED = 1 -EXIT_INTERRUPTED = 2 -EXIT_INTERNALERROR = 3 -EXIT_NOHOSTS = 4 --- a/CHANGELOG +++ b/CHANGELOG @@ -9,6 +9,8 @@ Changes between 1.2.1 and 1.2.2 (release - added links to the new capturelog and coverage plugins - (issue87) fix unboundlocal error in assertionold code - (issue86) improve documentation for looponfailing +- expose some internal test running exceptions under py.test.exc.* + and shift raises/importorskip etc. helper definitions to runner plugin . - ship distribute_setup.py version 0.6.10 --- a/py/_test/pluginmanager.py +++ b/py/_test/pluginmanager.py @@ -4,7 +4,6 @@ managing loading and interacting with py import py import inspect from py._plugin import hookspec -from py._test.outcome import Skipped default_plugins = ( "default runner capture mark terminal skipping tmpdir monkeypatch " @@ -139,7 +138,7 @@ class PluginManager(object): mod = importplugin(modname) except KeyboardInterrupt: raise - except Skipped: + except py.test.exc.Skipped: e = py.std.sys.exc_info()[1] self._hints.append("skipped plugin %r: %s" %((modname, e.msg))) else: --- a/testing/test_config.py +++ b/testing/test_config.py @@ -75,13 +75,14 @@ class TestConfigAPI: py.test.raises(KeyError, 'config.getvalue("y", o)') def test_config_getvalueorskip(self, testdir): - from py._test.outcome import Skipped config = testdir.parseconfig() - py.test.raises(Skipped, "config.getvalueorskip('hello')") + py.test.raises(py.test.exc.Skipped, + "config.getvalueorskip('hello')") verbose = config.getvalueorskip("verbose") assert verbose == config.option.verbose config.option.hello = None - py.test.raises(Skipped, "config.getvalueorskip('hello')") + py.test.raises(py.test.exc.Skipped, + "config.getvalueorskip('hello')") def test_config_overwrite(self, testdir): o = testdir.tmpdir --- a/testing/root/test_py_imports.py +++ b/testing/root/test_py_imports.py @@ -1,7 +1,6 @@ import py import types import sys -from py._test.outcome import Skipped def checksubpackage(name): obj = getattr(py, name) @@ -52,7 +51,7 @@ def test_importall(): modpath = 'py.%s' % relpath try: check_import(modpath) - except Skipped: + except py.test.exc.Skipped: pass def check_import(modpath): --- a/testing/test_outcome.py +++ b/testing/test_outcome.py @@ -16,13 +16,12 @@ class TestRaises: py.test.raises(ValueError, int, 'hello') def test_raises_callable_no_exception(self): - from py._test.outcome import ExceptionFailure class A: def __call__(self): pass try: py.test.raises(ValueError, A()) - except ExceptionFailure: + except py.test.exc.ExceptionFailure: pass def test_pytest_exit(): @@ -41,23 +40,23 @@ def test_exception_printing_skip(): assert s.startswith("Skipped") def test_importorskip(): - from py._test.outcome import Skipped, importorskip - assert importorskip == py.test.importorskip + importorskip = py.test.importorskip try: sys = importorskip("sys") assert sys == py.std.sys #path = py.test.importorskip("os.path") #assert path == py.std.os.path - py.test.raises(Skipped, "py.test.importorskip('alskdj')") + py.test.raises(py.test.exc.Skipped, + "py.test.importorskip('alskdj')") py.test.raises(SyntaxError, "py.test.importorskip('x y z')") py.test.raises(SyntaxError, "py.test.importorskip('x=y')") path = importorskip("py", minversion=".".join(py.__version__)) mod = py.std.types.ModuleType("hello123") mod.__version__ = "1.3" - py.test.raises(Skipped, """ + py.test.raises(py.test.exc.Skipped, """ py.test.importorskip("hello123", minversion="5.0") """) - except Skipped: + except py.test.exc.Skipped: print(py.code.ExceptionInfo()) py.test.fail("spurious skip") --- a/testing/plugin/test_pytest_runner.py +++ b/testing/plugin/test_pytest_runner.py @@ -197,14 +197,13 @@ class BaseFunctionalTests: assert rep.when == "call" def test_exit_propagates(self, testdir): - from py._test.outcome import Exit try: testdir.runitem(""" - from py._test.outcome import Exit + import py def test_func(): - raise Exit() + raise py.test.exc.Exit() """) - except Exit: + except py.test.exc.Exit: pass else: py.test.fail("did not raise") @@ -216,7 +215,6 @@ class TestExecutionNonForked(BaseFunctio return f def test_keyboardinterrupt_propagates(self, testdir): - from py._test.outcome import Exit try: testdir.runitem(""" def test_func(): --- a/py/_test/session.py +++ b/py/_test/session.py @@ -6,7 +6,13 @@ """ import py -from py._test import outcome + +# exitcodes for the command line +EXIT_OK = 0 +EXIT_TESTSFAILED = 1 +EXIT_INTERRUPTED = 2 +EXIT_INTERNALERROR = 3 +EXIT_NOHOSTS = 4 # imports used for genitems() Item = py.test.collect.Item @@ -96,21 +102,21 @@ class Session(object): """ main loop for running tests. """ self.shouldstop = False self.sessionstarts() - exitstatus = outcome.EXIT_OK + exitstatus = EXIT_OK try: self._mainloop(colitems) if self._testsfailed: - exitstatus = outcome.EXIT_TESTSFAILED + exitstatus = EXIT_TESTSFAILED self.sessionfinishes(exitstatus=exitstatus) except KeyboardInterrupt: excinfo = py.code.ExceptionInfo() self.config.hook.pytest_keyboard_interrupt(excinfo=excinfo) - exitstatus = outcome.EXIT_INTERRUPTED + exitstatus = EXIT_INTERRUPTED except: excinfo = py.code.ExceptionInfo() self.config.pluginmanager.notify_exception(excinfo) - exitstatus = outcome.EXIT_INTERNALERROR - if exitstatus in (outcome.EXIT_INTERNALERROR, outcome.EXIT_INTERRUPTED): + exitstatus = EXIT_INTERNALERROR + if exitstatus in (EXIT_INTERNALERROR, EXIT_INTERRUPTED): self.sessionfinishes(exitstatus=exitstatus) return exitstatus --- a/testing/test_pycollect.py +++ b/testing/test_pycollect.py @@ -444,8 +444,7 @@ def test_modulecol_roundtrip(testdir): class TestTracebackCutting: def test_skip_simple(self): - from py._test.outcome import Skipped - excinfo = py.test.raises(Skipped, 'py.test.skip("xxx")') + excinfo = py.test.raises(py.test.exc.Skipped, 'py.test.skip("xxx")') assert excinfo.traceback[-1].frame.code.name == "skip" assert excinfo.traceback[-1].ishidden() --- a/testing/test_deprecated_api.py +++ b/testing/test_deprecated_api.py @@ -1,6 +1,5 @@ import py -from py._test.outcome import Skipped class TestCollectDeprecated: @@ -191,7 +190,7 @@ class TestDisabled: l = modcol.collect() assert len(l) == 1 recwarn.clear() - py.test.raises(Skipped, "modcol.setup()") + py.test.raises(py.test.exc.Skipped, "modcol.setup()") recwarn.pop(DeprecationWarning) def test_disabled_class(self, recwarn, testdir): @@ -208,7 +207,7 @@ class TestDisabled: l = modcol.collect() assert len(l) == 1 recwarn.clear() - py.test.raises(Skipped, "modcol.setup()") + py.test.raises(py.test.exc.Skipped, "modcol.setup()") recwarn.pop(DeprecationWarning) def test_disabled_class_functional(self, testdir): From commits-noreply at bitbucket.org Tue Apr 27 21:17:05 2010 From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org) Date: Tue, 27 Apr 2010 19:17:05 +0000 (UTC) Subject: [py-svn] pytest-xdist commit 7c0ba8a503dc: adapt for changed py-trunk, new outcome exceptions Message-ID: <20100427191705.C366F7EF1D@bitbucket.org> # HG changeset patch -- Bitbucket.org # Project pytest-xdist # URL http://bitbucket.org/hpk42/pytest-xdist/overview # User holger krekel # Date 1272395807 -7200 # Node ID 7c0ba8a503dc79dee9d6631c907643fc06576da5 # Parent f4525fd1fae687bd7777be2ecf456be457f53c10 adapt for changed py-trunk, new outcome exceptions --- a/testing/test_dsession.py +++ b/testing/test_dsession.py @@ -1,5 +1,5 @@ from xdist.dsession import DSession -from py._test import outcome +from py._test import session as outcome import py import execnet --- a/xdist/dsession.py +++ b/xdist/dsession.py @@ -1,6 +1,5 @@ import py -from py._test.session import Session -from py._test import outcome +from py._test import session from xdist.nodemanage import NodeManager queue = py.builtin._tryimport('queue', 'Queue') @@ -62,7 +61,7 @@ class LoopState(object): class ExitFirstInterrupt(KeyboardInterrupt): pass -class DSession(Session): +class DSession(session.Session): """ Session drives the collection and running of tests and generates test events for reporters. @@ -140,7 +139,7 @@ class DSession(Session): self.triggershutdown() loopstate.shuttingdown = True if not self.node2pending: - loopstate.exitstatus = outcome.EXIT_NOHOSTS + loopstate.exitstatus = session.EXIT_NOHOSTS def loop_once_shutdown(self, loopstate): # once we are in shutdown mode we dont send @@ -154,16 +153,16 @@ class DSession(Session): self.config.hook.pytest_runtest_logreport(**kwargs) elif eventname == "pytest_internalerror": self.config.hook.pytest_internalerror(**kwargs) - loopstate.exitstatus = outcome.EXIT_INTERNALERROR + loopstate.exitstatus = session.EXIT_INTERNALERROR elif eventname == "pytest__teardown_final_logerror": self.config.hook.pytest__teardown_final_logerror(**kwargs) - loopstate.exitstatus = outcome.EXIT_TESTSFAILED + loopstate.exitstatus = session.EXIT_TESTSFAILED if not self.node2pending: # finished if loopstate.testsfailed: - loopstate.exitstatus = outcome.EXIT_TESTSFAILED + loopstate.exitstatus = session.EXIT_TESTSFAILED else: - loopstate.exitstatus = outcome.EXIT_OK + loopstate.exitstatus = session.EXIT_OK #self.config.pluginmanager.unregister(loopstate) def _initloopstate(self, colitems): @@ -183,16 +182,16 @@ class DSession(Session): except KeyboardInterrupt: excinfo = py.code.ExceptionInfo() if excinfo.errisinstance(ExitFirstInterrupt): - exitstatus = outcome.EXIT_TESTSFAILED + exitstatus = session.EXIT_TESTSFAILED else: self.config.hook.pytest_keyboard_interrupt(excinfo=excinfo) - exitstatus = outcome.EXIT_INTERRUPTED + exitstatus = session.EXIT_INTERRUPTED except: self.config.pluginmanager.notify_exception() - exitstatus = outcome.EXIT_INTERNALERROR + exitstatus = session.EXIT_INTERNALERROR self.config.pluginmanager.unregister(loopstate) if exitstatus == 0 and self._testsfailed: - exitstatus = outcome.EXIT_TESTSFAILED + exitstatus = session.EXIT_TESTSFAILED return exitstatus def triggershutdown(self): From commits-noreply at bitbucket.org Tue Apr 27 21:27:00 2010 From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org) Date: Tue, 27 Apr 2010 19:27:00 +0000 (UTC) Subject: [py-svn] pytest-xdist commit a3c7af80b76f: fix python3 issues and add info to changelog Message-ID: <20100427192700.164BD7EF4B@bitbucket.org> # HG changeset patch -- Bitbucket.org # Project pytest-xdist # URL http://bitbucket.org/hpk42/pytest-xdist/overview # User holger krekel # Date 1272396395 -7200 # Node ID a3c7af80b76fb2d03036753ebf22c21305ad35d0 # Parent 7c0ba8a503dc79dee9d6631c907643fc06576da5 fix python3 issues and add info to changelog --- a/xdist/mypickle.py +++ b/xdist/mypickle.py @@ -100,16 +100,25 @@ class ImmutablePickler: return res def _updatepicklememo(self): - for x, obj in self._unpicklememo.items(): + for x, obj in explode(self._unpicklememo.items()): self._picklememo[id(obj)] = (fromkey(x), obj) def _updateunpicklememo(self): - for key,obj in self._picklememo.values(): + for key,obj in explode(self._picklememo.values()): key = makekey(key) if key in self._unpicklememo: assert self._unpicklememo[key] is obj self._unpicklememo[key] = obj +def explode(obj): + try: + obj[0] + except TypeError: + return list(obj) + except IndexError: + pass + return obj + NO_ENDMARKER_WANTED = object() class UnpickleError(Exception): --- a/testing/test_mypickle.py +++ b/testing/test_mypickle.py @@ -252,3 +252,10 @@ class TestPickleChannelFunctional: channel.waitclose(timeout=2) + +def test_explode(): + from xdist.mypickle import explode + assert isinstance(explode({}.values()), list) + l = [] + assert isinstance(explode(l), list) + assert explode(l) is l --- a/CHANGELOG +++ b/CHANGELOG @@ -7,6 +7,10 @@ 1.2 - introduce a new data input/output mechanism to allow the master side to send and receive data from a slave. +- use new register hooks facility of py.test>=1.2.2 + +- fix some python3 related pickling related race conditions + - fix PyPI description 1.1 From commits-noreply at bitbucket.org Wed Apr 28 08:43:26 2010 From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org) Date: Wed, 28 Apr 2010 06:43:26 +0000 (UTC) Subject: [py-svn] py-trunk commit 7cf598723a72: * rather expose internal exceptions under py.test.ACTION.Exception Message-ID: <20100428064326.B4BC07EF1F@bitbucket.org> # HG changeset patch -- Bitbucket.org # Project py-trunk # URL http://bitbucket.org/hpk42/py-trunk/overview # User holger krekel # Date 1272436976 -7200 # Node ID 7cf598723a729f36167fad764a15526c7b39a780 # Parent 52f5dfc7ef98dd8d1cbca6314aae3fb83a360706 * rather expose internal exceptions under py.test.ACTION.Exception with ACTION being skip, fail, exit, raises. * move and refine test_outcome.py tests into runner tests --- a/py/_plugin/pytest_pdb.py +++ b/py/_plugin/pytest_pdb.py @@ -17,7 +17,7 @@ def pytest_configure(config): class PdbInvoke: def pytest_runtest_makereport(self, item, call): if call.excinfo and not \ - call.excinfo.errisinstance(py.test.exc.Skipped): + call.excinfo.errisinstance(py.test.skip.Exception): # play well with capturing, slightly hackish capman = item.config.pluginmanager.getplugin('capturemanager') capman.suspendcapture() --- a/py/_plugin/pytest_runner.py +++ b/py/_plugin/pytest_runner.py @@ -5,15 +5,7 @@ collect and run test items and create re import py, sys def pytest_namespace(): - class exc: - """ namespace holding py.test runner exceptions. """ - Skipped = Skipped - ExceptionFailure = ExceptionFailure - Failed = Failed - Exit = Exit - return { - 'exc' : exc, 'raises' : raises, 'skip' : skip, 'importorskip' : importorskip, @@ -157,7 +149,7 @@ class ItemTestReport(BaseReport): self.failed = True shortrepr = "?" longrepr = excinfo - elif excinfo.errisinstance(py.test.exc.Skipped): + elif excinfo.errisinstance(py.test.skip.Exception): self.skipped = True shortrepr = "s" longrepr = self.item._repr_failure_py(excinfo) @@ -196,7 +188,7 @@ class CollectReport(BaseReport): self.result = result else: self.longrepr = self.collector._repr_failure_py(excinfo) - if excinfo.errisinstance(py.test.exc.Skipped): + if excinfo.errisinstance(py.test.skip.Exception): self.skipped = True self.reason = str(excinfo.value) else: @@ -322,6 +314,8 @@ def exit(msg): __tracebackhide__ = True raise Exit(msg) +exit.Exception = Exit + def skip(msg=""): """ 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 @@ -331,11 +325,15 @@ def skip(msg=""): __tracebackhide__ = True raise Skipped(msg=msg) +skip.Exception = Skipped + def fail(msg=""): """ explicitely fail an currently-executing test with the given Message. """ __tracebackhide__ = True raise Failed(msg=msg) +fail.Exception = Failed + def raises(ExpectedException, *args, **kwargs): """ if args[0] is callable: raise AssertionError if calling it with the remaining arguments does not raise the expected exception. @@ -375,6 +373,8 @@ def raises(ExpectedException, *args, **k raise ExceptionFailure(msg="DID NOT RAISE", expr=args, expected=ExpectedException) +raises.Exception = ExceptionFailure + def importorskip(modname, minversion=None): """ return imported module if it has a higher __version__ than the optionally specified 'minversion' - otherwise call py.test.skip() --- a/CHANGELOG +++ b/CHANGELOG @@ -9,8 +9,9 @@ Changes between 1.2.1 and 1.2.2 (release - added links to the new capturelog and coverage plugins - (issue87) fix unboundlocal error in assertionold code - (issue86) improve documentation for looponfailing -- expose some internal test running exceptions under py.test.exc.* - and shift raises/importorskip etc. helper definitions to runner plugin . +- expose test outcome related exceptions as py.test.skip.Exception, + py.test.raises.Exception etc., useful mostly for plugins + doing special outcome interpreteration/tweaking - ship distribute_setup.py version 0.6.10 --- a/py/_test/pluginmanager.py +++ b/py/_test/pluginmanager.py @@ -138,7 +138,7 @@ class PluginManager(object): mod = importplugin(modname) except KeyboardInterrupt: raise - except py.test.exc.Skipped: + except py.test.skip.Exception: e = py.std.sys.exc_info()[1] self._hints.append("skipped plugin %r: %s" %((modname, e.msg))) else: --- a/testing/test_config.py +++ b/testing/test_config.py @@ -76,12 +76,12 @@ class TestConfigAPI: def test_config_getvalueorskip(self, testdir): config = testdir.parseconfig() - py.test.raises(py.test.exc.Skipped, + py.test.raises(py.test.skip.Exception, "config.getvalueorskip('hello')") verbose = config.getvalueorskip("verbose") assert verbose == config.option.verbose config.option.hello = None - py.test.raises(py.test.exc.Skipped, + py.test.raises(py.test.skip.Exception, "config.getvalueorskip('hello')") def test_config_overwrite(self, testdir): --- a/testing/root/test_py_imports.py +++ b/testing/root/test_py_imports.py @@ -51,7 +51,7 @@ def test_importall(): modpath = 'py.%s' % relpath try: check_import(modpath) - except py.test.exc.Skipped: + except py.test.skip.Exception: pass def check_import(modpath): --- a/testing/test_outcome.py +++ /dev/null @@ -1,83 +0,0 @@ - -import py -import sys - -class TestRaises: - def test_raises(self): - py.test.raises(ValueError, "int('qwe')") - - def test_raises_exec(self): - py.test.raises(ValueError, "a,x = []") - - def test_raises_syntax_error(self): - py.test.raises(SyntaxError, "qwe qwe qwe") - - def test_raises_function(self): - py.test.raises(ValueError, int, 'hello') - - def test_raises_callable_no_exception(self): - class A: - def __call__(self): - pass - try: - py.test.raises(ValueError, A()) - except py.test.exc.ExceptionFailure: - pass - -def test_pytest_exit(): - try: - py.test.exit("hello") - except: - excinfo = py.code.ExceptionInfo() - assert excinfo.errisinstance(KeyboardInterrupt) - -def test_exception_printing_skip(): - try: - py.test.skip("hello") - except Exception: - excinfo = py.code.ExceptionInfo() - s = excinfo.exconly(tryshort=True) - assert s.startswith("Skipped") - -def test_importorskip(): - importorskip = py.test.importorskip - try: - sys = importorskip("sys") - assert sys == py.std.sys - #path = py.test.importorskip("os.path") - #assert path == py.std.os.path - py.test.raises(py.test.exc.Skipped, - "py.test.importorskip('alskdj')") - py.test.raises(SyntaxError, "py.test.importorskip('x y z')") - py.test.raises(SyntaxError, "py.test.importorskip('x=y')") - path = importorskip("py", minversion=".".join(py.__version__)) - mod = py.std.types.ModuleType("hello123") - mod.__version__ = "1.3" - py.test.raises(py.test.exc.Skipped, """ - py.test.importorskip("hello123", minversion="5.0") - """) - except py.test.exc.Skipped: - print(py.code.ExceptionInfo()) - py.test.fail("spurious skip") - -def test_importorskip_imports_last_module_part(): - import os - ospath = py.test.importorskip("os.path") - assert os.path == ospath - - -def test_pytest_cmdline_main(testdir): - p = testdir.makepyfile(""" - import sys - sys.path.insert(0, %r) - import py - def test_hello(): - assert 1 - if __name__ == '__main__': - py.test.cmdline.main([__file__]) - """ % (str(py._pydir.dirpath()))) - import subprocess - popen = subprocess.Popen([sys.executable, str(p)], stdout=subprocess.PIPE) - s = popen.stdout.read() - ret = popen.wait() - assert ret == 0 --- a/testing/plugin/test_pytest_runner.py +++ b/testing/plugin/test_pytest_runner.py @@ -1,4 +1,4 @@ -import py +import py, sys from py._plugin import pytest_runner as runner from py._code.code import ReprExceptionInfo @@ -201,9 +201,9 @@ class BaseFunctionalTests: testdir.runitem(""" import py def test_func(): - raise py.test.exc.Exit() + raise py.test.exit.Exception() """) - except py.test.exc.Exit: + except py.test.exit.Exception: pass else: py.test.fail("did not raise") @@ -313,3 +313,91 @@ def test_runtest_in_module_ordering(test assert result.stdout.fnmatch_lines([ "*2 passed*" ]) + +class TestRaises: + def test_raises(self): + py.test.raises(ValueError, "int('qwe')") + + def test_raises_exec(self): + py.test.raises(ValueError, "a,x = []") + + def test_raises_syntax_error(self): + py.test.raises(SyntaxError, "qwe qwe qwe") + + def test_raises_function(self): + py.test.raises(ValueError, int, 'hello') + + def test_raises_callable_no_exception(self): + class A: + def __call__(self): + pass + try: + py.test.raises(ValueError, A()) + except py.test.raises.Exception: + pass + +def test_pytest_exit(): + try: + py.test.exit("hello") + except py.test.exit.Exception: + excinfo = py.code.ExceptionInfo() + assert excinfo.errisinstance(KeyboardInterrupt) + +def test_pytest_fail(): + try: + py.test.fail("hello") + except py.test.fail.Exception: + excinfo = py.code.ExceptionInfo() + s = excinfo.exconly(tryshort=True) + assert s.startswith("Failed") + +def test_exception_printing_skip(): + try: + py.test.skip("hello") + except py.test.skip.Exception: + excinfo = py.code.ExceptionInfo() + s = excinfo.exconly(tryshort=True) + assert s.startswith("Skipped") + +def test_importorskip(): + importorskip = py.test.importorskip + try: + sys = importorskip("sys") + assert sys == py.std.sys + #path = py.test.importorskip("os.path") + #assert path == py.std.os.path + py.test.raises(py.test.skip.Exception, + "py.test.importorskip('alskdj')") + py.test.raises(SyntaxError, "py.test.importorskip('x y z')") + py.test.raises(SyntaxError, "py.test.importorskip('x=y')") + path = importorskip("py", minversion=".".join(py.__version__)) + mod = py.std.types.ModuleType("hello123") + mod.__version__ = "1.3" + py.test.raises(py.test.skip.Exception, """ + py.test.importorskip("hello123", minversion="5.0") + """) + except py.test.skip.Exception: + print(py.code.ExceptionInfo()) + py.test.fail("spurious skip") + +def test_importorskip_imports_last_module_part(): + import os + ospath = py.test.importorskip("os.path") + assert os.path == ospath + + +def test_pytest_cmdline_main(testdir): + p = testdir.makepyfile(""" + import sys + sys.path.insert(0, %r) + import py + def test_hello(): + assert 1 + if __name__ == '__main__': + py.test.cmdline.main([__file__]) + """ % (str(py._pydir.dirpath()))) + import subprocess + popen = subprocess.Popen([sys.executable, str(p)], stdout=subprocess.PIPE) + s = popen.stdout.read() + ret = popen.wait() + assert ret == 0 --- a/testing/test_pycollect.py +++ b/testing/test_pycollect.py @@ -444,7 +444,7 @@ def test_modulecol_roundtrip(testdir): class TestTracebackCutting: def test_skip_simple(self): - excinfo = py.test.raises(py.test.exc.Skipped, 'py.test.skip("xxx")') + excinfo = py.test.raises(py.test.skip.Exception, 'py.test.skip("xxx")') assert excinfo.traceback[-1].frame.code.name == "skip" assert excinfo.traceback[-1].ishidden() --- a/testing/test_deprecated_api.py +++ b/testing/test_deprecated_api.py @@ -190,7 +190,7 @@ class TestDisabled: l = modcol.collect() assert len(l) == 1 recwarn.clear() - py.test.raises(py.test.exc.Skipped, "modcol.setup()") + py.test.raises(py.test.skip.Exception, "modcol.setup()") recwarn.pop(DeprecationWarning) def test_disabled_class(self, recwarn, testdir): @@ -207,7 +207,7 @@ class TestDisabled: l = modcol.collect() assert len(l) == 1 recwarn.clear() - py.test.raises(py.test.exc.Skipped, "modcol.setup()") + py.test.raises(py.test.skip.Exception, "modcol.setup()") recwarn.pop(DeprecationWarning) def test_disabled_class_functional(self, testdir): From commits-noreply at bitbucket.org Wed Apr 28 15:24:54 2010 From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org) Date: Wed, 28 Apr 2010 13:24:54 +0000 (UTC) Subject: [py-svn] py-trunk commit d720312ad7cd: fix some py3 issues, particularly for 3.1.2 which has truncate(0) not change the file position Message-ID: <20100428132454.A3F0C7EEE1@bitbucket.org> # HG changeset patch -- Bitbucket.org # Project py-trunk # URL http://bitbucket.org/hpk42/py-trunk/overview # User holger krekel # Date 1272453725 -7200 # Node ID d720312ad7cd714d6eceb9642f5df22824ea73aa # Parent 7cf598723a729f36167fad764a15526c7b39a780 fix some py3 issues, particularly for 3.1.2 which has truncate(0) not change the file position so that a seek(0) is needed in addition. --- a/py/_io/capture.py +++ b/py/_io/capture.py @@ -310,9 +310,11 @@ class StdCapture(Capture): if self._out: out = sys.stdout.getvalue() sys.stdout.truncate(0) + sys.stdout.seek(0) if self._err: err = sys.stderr.getvalue() sys.stderr.truncate(0) + sys.stderr.seek(0) return out, err class DontReadFromInput: --- a/py/_plugin/pytest_pytester.py +++ b/py/_plugin/pytest_pytester.py @@ -443,7 +443,8 @@ class LineComp: """ __tracebackhide__ = True val = self.stringio.getvalue() - self.stringio.truncate(0) # remove what we got + self.stringio.truncate(0) + self.stringio.seek(0) lines1 = val.split("\n") return LineMatcher(lines1).fnmatch_lines(lines2) --- a/py/_plugin/pytest_runner.py +++ b/py/_plugin/pytest_runner.py @@ -287,12 +287,13 @@ class OutcomeException(Exception): __str__ = __repr__ class Skipped(OutcomeException): - # XXX slighly hackish: on 3k we fake to live in the builtins + # XXX hackish: on 3k we fake to live in the builtins # in order to have Skipped exception printing shorter/nicer __module__ = 'builtins' class Failed(OutcomeException): """ raised from an explicit call to py.test.fail() """ + __module__ = 'builtins' class ExceptionFailure(Failed): """ raised by py.test.raises on an exception-assertion mismatch. """ From commits-noreply at bitbucket.org Wed Apr 28 15:24:56 2010 From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org) Date: Wed, 28 Apr 2010 13:24:56 +0000 (UTC) Subject: [py-svn] py-trunk commit 7bdbc2a2fd04: * various jython related fixes. Message-ID: <20100428132456.AF6387EF68@bitbucket.org> # HG changeset patch -- Bitbucket.org # Project py-trunk # URL http://bitbucket.org/hpk42/py-trunk/overview # User holger krekel # Date 1272461078 -7200 # Node ID 7bdbc2a2fd04dc3dea4d5e7d5761e537c5d7ab77 # Parent d720312ad7cd714d6eceb9642f5df22824ea73aa * various jython related fixes. * more care for print-errors including unicode-encoding related errors. --- a/testing/plugin/test_pytest_capture.py +++ b/testing/plugin/test_pytest_capture.py @@ -393,7 +393,8 @@ def test_setup_failure_does_not_kill_cap def test_fdfuncarg_skips_on_no_osdup(testdir): testdir.makepyfile(""" import os - del os.dup + if hasattr(os, 'dup'): + del os.dup def test_hello(capfd): pass """) --- a/CHANGELOG +++ b/CHANGELOG @@ -5,14 +5,14 @@ Changes between 1.2.1 and 1.2.2 (release - (issue85) fix junitxml plugin to handle tests with non-ascii output - fix some python3 compatibility issues (thanks Benjamin Peterson) - fixes for making the jython/win32 combination work -- fixes for handling of unicode exception values -- added links to the new capturelog and coverage plugins +- fixes for handling of unicode exception values and unprintable objects - (issue87) fix unboundlocal error in assertionold code - (issue86) improve documentation for looponfailing - expose test outcome related exceptions as py.test.skip.Exception, py.test.raises.Exception etc., useful mostly for plugins doing special outcome interpreteration/tweaking - ship distribute_setup.py version 0.6.10 +- added links to the new capturelog and coverage plugins Changes between 1.2.1 and 1.2.0 --- a/testing/plugin/test_pytest_junitxml.py +++ b/testing/plugin/test_pytest_junitxml.py @@ -1,6 +1,6 @@ from xml.dom import minidom -import py +import py, sys def runandparse(testdir, *args): resultpath = testdir.tmpdir.join("junit.xml") @@ -104,7 +104,7 @@ class TestPython: name="test_collect_error") fnode = tnode.getElementsByTagName("failure")[0] assert_attr(fnode, message="collection failure") - assert "invalid syntax" in fnode.toxml() + assert "SyntaxError" in fnode.toxml() def test_collect_skipped(self, testdir): testdir.makepyfile("import py ; py.test.skip('xyz')") @@ -130,7 +130,8 @@ class TestPython: assert result.ret == 1 tnode = dom.getElementsByTagName("testcase")[0] fnode = tnode.getElementsByTagName("failure")[0] - assert "hx" in fnode.toxml() + if not sys.platform.startswith("java"): + assert "hx" in fnode.toxml() class TestNonPython: def test_summing_simple(self, testdir): --- a/testing/plugin/test_pytest_terminal.py +++ b/testing/plugin/test_pytest_terminal.py @@ -97,7 +97,7 @@ class TestTerminal: excinfo = py.test.raises(ValueError, "raise ValueError('hello')") rep.pytest_internalerror(excinfo.getrepr()) linecomp.assert_contains_lines([ - "INTERNALERROR> *raise ValueError*" + "INTERNALERROR> *ValueError*hello*" ]) def test_writeline(self, testdir, linecomp): --- a/py/_plugin/pytest_junitxml.py +++ b/py/_plugin/pytest_junitxml.py @@ -144,6 +144,7 @@ class LogXML(object): suite_stop_time = time.time() suite_time_delta = suite_stop_time - self.suite_start_time numtests = self.passed + self.failed + logfile.write('') logfile.write('") else: # This formatting could all be handled by the - # _repr() function, which is only repr.Repr in + # _repr() function, which is only reprlib.Repr in # disguise, so is very configurable. str_repr = self._saferepr(value) #if len(str_repr) < 70 or not isinstance(value, @@ -591,14 +591,23 @@ class TerminalRepr: return s def __unicode__(self): - tw = py.io.TerminalWriter(stringio=True) + l = [] + tw = py.io.TerminalWriter(l.append) self.toterminal(tw) - s = tw.stringio.getvalue().strip() - return s + l = map(unicode_or_repr, l) + return "".join(l).strip() def __repr__(self): return "<%s instance at %0x>" %(self.__class__, id(self)) +def unicode_or_repr(obj): + try: + return py.builtin._totext(obj) + except KeyboardInterrupt: + raise + except Exception: + return "" % safe_repr(obj) + class ReprExceptionInfo(TerminalRepr): def __init__(self, reprtraceback, reprcrash): self.reprtraceback = reprtraceback @@ -709,17 +718,17 @@ class ReprFuncArgs(TerminalRepr): -class SafeRepr(repr.Repr): +class SafeRepr(reprlib.Repr): """ subclass of repr.Repr that limits the resulting size of repr() and includes information on exceptions raised during the call. """ def __init__(self, *args, **kwargs): - repr.Repr.__init__(self, *args, **kwargs) + reprlib.Repr.__init__(self, *args, **kwargs) self.maxstring = 240 # 3 * 80 chars self.maxother = 160 # 2 * 80 chars def repr(self, x): - return self._callhelper(repr.Repr.repr, self, x) + return self._callhelper(reprlib.Repr.repr, self, x) def repr_instance(self, x, level): return self._callhelper(builtin_repr, x) --- a/py/_plugin/pytest_unittest.py +++ b/py/_plugin/pytest_unittest.py @@ -21,7 +21,9 @@ def pytest_pycollect_makeitem(collector, return # nobody derived unittest.TestCase try: isunit = issubclass(obj, py.std.unittest.TestCase) - except TypeError: + except KeyboardInterrupt: + raise + except Exception: pass else: if isunit: --- a/py/_io/terminalwriter.py +++ b/py/_io/terminalwriter.py @@ -145,15 +145,17 @@ class TerminalWriter(object): # XXX deprecate stringio argument def __init__(self, file=None, stringio=False, encoding=None): - self.encoding = encoding if file is None: if stringio: self.stringio = file = py.io.TextIO() else: file = py.std.sys.stdout + if hasattr(file, 'encoding'): + encoding = file.encoding elif hasattr(file, '__call__'): file = WriteFile(file, encoding=encoding) + self.encoding = encoding self._file = file self.fullwidth = get_terminal_width() self.hasmarkup = should_do_markup(file) @@ -200,18 +202,22 @@ class TerminalWriter(object): def write(self, s, **kw): if s: - s = self._getbytestring(s) - if self.hasmarkup and kw: - s = self.markup(s, **kw) + if not isinstance(self._file, WriteFile): + s = self._getbytestring(s) + if self.hasmarkup and kw: + s = self.markup(s, **kw) self._file.write(s) self._file.flush() def _getbytestring(self, s): # XXX review this and the whole logic - if self.encoding and sys.version_info < (3,0) and isinstance(s, unicode): + if self.encoding and sys.version_info[0] < 3 and isinstance(s, unicode): return s.encode(self.encoding) elif not isinstance(s, str): - return str(s) + try: + return str(s) + except UnicodeEncodeError: + return "" % type(s).__name__ return s def line(self, s='', **kw): --- a/testing/plugin/test_pytest_resultlog.py +++ b/testing/plugin/test_pytest_resultlog.py @@ -142,7 +142,7 @@ class TestWithFunctionIntegration: entry_lines = entry.splitlines() assert entry_lines[0].startswith('! ') - assert os.path.basename(__file__)[:-1] in entry_lines[0] #.py/.pyc + assert os.path.basename(__file__)[:-9] in entry_lines[0] #.pyc/class assert entry_lines[-1][0] == ' ' assert 'ValueError' in entry --- a/py/_builtin.py +++ b/py/_builtin.py @@ -93,7 +93,7 @@ if sys.version_info >= (3, 0): # some backward compatibility helpers _basestring = str - def _totext(obj, encoding): + def _totext(obj, encoding=None): if isinstance(obj, bytes): obj = obj.decode(encoding) elif not isinstance(obj, str): --- a/testing/io_/test_terminalwriter.py +++ b/testing/io_/test_terminalwriter.py @@ -129,8 +129,8 @@ class TestTerminalWriter: tw.write("x\n", red=True) l = tw.getlines() if sys.platform != "win32": - assert len(l[0]) > 2, l - assert len(l[1]) > 2, l + assert len(l[0]) >= 2, l + assert len(l[1]) >= 2, l def test_attr_fullwidth(self, tw): tw.sep("-", "hello", fullwidth=70) --- a/testing/code/test_code.py +++ b/testing/code/test_code.py @@ -193,7 +193,7 @@ def test_builtin_patch_unpatch(monkeypat assert cpy_builtin.compile == mycompile -def test_unicode_handling(testdir): +def test_unicode_handling(): value = py.builtin._totext('\xc4\x85\xc4\x87\n', 'utf-8').encode('utf8') def f(): raise Exception(value) @@ -201,3 +201,19 @@ def test_unicode_handling(testdir): s = str(excinfo) if sys.version_info[0] < 3: u = unicode(excinfo) + +def test_unicode_or_repr(): + from py._code.code import unicode_or_repr + assert unicode_or_repr('hello') == "hello" + if sys.version_info[0] < 3: + s = unicode_or_repr('\xf6\xc4\x85') + else: + s = eval("unicode_or_repr(b'\\f6\\xc4\\x85')") + assert 'print-error' in s + assert 'c4' in s + class A: + def __repr__(self): + raise ValueError() + s = unicode_or_repr(A()) + assert 'print-error' in s + assert 'ValueError' in s From commits-noreply at bitbucket.org Wed Apr 28 23:51:08 2010 From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org) Date: Wed, 28 Apr 2010 21:51:08 +0000 (UTC) Subject: [py-svn] py-trunk commit 501c24cfb652: some py3 encoding fixes Message-ID: <20100428215108.8EC957EF71@bitbucket.org> # HG changeset patch -- Bitbucket.org # Project py-trunk # URL http://bitbucket.org/hpk42/py-trunk/overview # User Benjamin Peterson # Date 1272491496 18000 # Node ID 501c24cfb6521d53caa4e81ba87d1d3c7629e761 # Parent 7bdbc2a2fd04dc3dea4d5e7d5761e537c5d7ab77 some py3 encoding fixes Also return True if fnmatch succeeds. --- a/py/_plugin/pytest_pytester.py +++ b/py/_plugin/pytest_pytester.py @@ -116,8 +116,8 @@ class TmpTestdir: ret = None for name, value in items: p = self.tmpdir.join(name).new(ext=ext) - source = py.code.Source(value) - p.write(str(py.code.Source(value)).lstrip()) + source = str(py.code.Source(value)).lstrip() + p.write(source.encode("utf-8"), "wb") if ret is None: ret = p return ret @@ -286,20 +286,21 @@ class TmpTestdir: p1 = self.tmpdir.join("stdout") p2 = self.tmpdir.join("stderr") print_("running", cmdargs, "curdir=", py.path.local()) - f1 = p1.open("w") - f2 = p2.open("w") + f1 = p1.open("wb") + f2 = p2.open("wb") now = time.time() popen = self.popen(cmdargs, stdout=f1, stderr=f2, close_fds=(sys.platform != "win32")) ret = popen.wait() f1.close() f2.close() - out, err = p1.readlines(cr=0), p2.readlines(cr=0) + out = p1.read("rb").decode("utf-8").splitlines() + err = p2.read("rb").decode("utf-8").splitlines() if err: - for line in err: + for line in err: py.builtin.print_(line, file=sys.stderr) if out: - for line in out: + for line in out: py.builtin.print_(line, file=sys.stdout) return RunResult(ret, out, err, time.time()-now) @@ -462,10 +463,10 @@ class LineMatcher: lines2 = lines2.strip().lines from fnmatch import fnmatch - __tracebackhide__ = True lines1 = self.lines[:] nextline = None extralines = [] + __tracebackhide__ = True for line in lines2: nomatchprinted = False while lines1: @@ -484,8 +485,5 @@ class LineMatcher: print_(" and:", repr(nextline)) extralines.append(nextline) else: - if line != nextline: - #__tracebackhide__ = True - raise AssertionError("expected line not found: %r" % line) - extralines.extend(lines1) - return extralines + assert line == nextline + return True From commits-noreply at bitbucket.org Thu Apr 29 00:13:06 2010 From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org) Date: Wed, 28 Apr 2010 22:13:06 +0000 (UTC) Subject: [py-svn] py-trunk commit bce06f772dce: remove the unused return value of fnmatch_lines Message-ID: <20100428221306.6CF957EF11@bitbucket.org> # HG changeset patch -- Bitbucket.org # Project py-trunk # URL http://bitbucket.org/hpk42/py-trunk/overview # User Benjamin Peterson # Date 1272492758 18000 # Node ID bce06f772dce796c5b5a43df9abdfabbf5afd7ff # Parent 501c24cfb6521d53caa4e81ba87d1d3c7629e761 remove the unused return value of fnmatch_lines --- a/testing/plugin/test_pytest_capture.py +++ b/testing/plugin/test_pytest_capture.py @@ -187,7 +187,7 @@ class TestPerTestCapturing: pass """) result = testdir.runpytest(p) - assert result.stdout.fnmatch_lines([ + result.stdout.fnmatch_lines([ '*teardown_function*', '*Captured stdout*', "setup func1*", @@ -205,7 +205,7 @@ class TestPerTestCapturing: pass """) result = testdir.runpytest(p) - assert result.stdout.fnmatch_lines([ + result.stdout.fnmatch_lines([ "*def teardown_module(mod):*", "*Captured stdout*", "*teardown module*", @@ -271,7 +271,7 @@ class TestLoggingInteraction: print ("suspend2 and captured %s" % (outerr,)) """) result = testdir.runpython(p) - assert result.stdout.fnmatch_lines([ + result.stdout.fnmatch_lines([ "suspeneded and captured*hello1*", "suspend2 and captured*hello2*WARNING:root:hello3*", ]) @@ -358,7 +358,7 @@ class TestCaptureFuncarg: pass """) result = testdir.runpytest(p) - assert result.stdout.fnmatch_lines([ + result.stdout.fnmatch_lines([ "*test_partial_setup_failure*", "*1 error*", ]) --- a/testing/acceptance_test.py +++ b/testing/acceptance_test.py @@ -8,7 +8,7 @@ class TestGeneralUsage: """) result = testdir.runpytest(testdir.tmpdir) assert result.ret != 0 - assert result.stderr.fnmatch_lines([ + result.stderr.fnmatch_lines([ '*ERROR: hello' ]) @@ -24,7 +24,7 @@ class TestGeneralUsage: """) result = testdir.runpytest("-p", "xyz", "--xyz=123") assert result.ret == 0 - assert result.stdout.fnmatch_lines([ + result.stdout.fnmatch_lines([ '*1 passed*', ]) @@ -46,7 +46,7 @@ class TestGeneralUsage: assert x """) result = testdir.runpytest(p) - extra = result.stdout.fnmatch_lines([ + result.stdout.fnmatch_lines([ "> assert x", "E assert 0", ]) @@ -60,7 +60,7 @@ class TestGeneralUsage: """) testdir.makepyfile(import_fails="import does_not_work") result = testdir.runpytest(p) - extra = result.stdout.fnmatch_lines([ + result.stdout.fnmatch_lines([ #XXX on jython this fails: "> import import_fails", "E ImportError: No module named does_not_work", ]) @@ -71,7 +71,7 @@ class TestGeneralUsage: p2 = testdir.makefile(".pyc", "123") result = testdir.runpytest(p1, p2) assert result.ret != 0 - assert result.stderr.fnmatch_lines([ + result.stderr.fnmatch_lines([ "*ERROR: can't collect: %s" %(p2,) ]) --- a/testing/plugin/test_pytest_default.py +++ b/testing/plugin/test_pytest_default.py @@ -23,7 +23,7 @@ def test_exclude(testdir): 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*"]) + result.stdout.fnmatch_lines(["*1 passed*"]) def test_pytest_report_iteminfo(): class FakeItem(object): @@ -43,4 +43,4 @@ def test_conftest_confcutdir(testdir): parser.addoption("--xyz", action="store_true") """)) result = testdir.runpytest("-h", "--confcutdir=%s" % x, x) - assert result.stdout.fnmatch_lines(["*--xyz*"]) + result.stdout.fnmatch_lines(["*--xyz*"]) --- a/testing/plugin/test_pytest_terminal.py +++ b/testing/plugin/test_pytest_terminal.py @@ -226,7 +226,7 @@ class TestTerminal: pass """) result = testdir.runpytest(p2) - assert result.stdout.fnmatch_lines([ + result.stdout.fnmatch_lines([ "*test_p2.py .", "*1 passed*", ]) @@ -461,7 +461,7 @@ class TestTerminalFunctional: """ ) result = testdir.runpytest("-k", "test_two:", testpath) - extra = result.stdout.fnmatch_lines([ + result.stdout.fnmatch_lines([ "*test_deselected.py ..", "=* 1 test*deselected by 'test_two:'*=", ]) @@ -494,7 +494,7 @@ class TestTerminalFunctional: result = testdir.runpytest() finally: old.chdir() - extra = result.stdout.fnmatch_lines([ + result.stdout.fnmatch_lines([ "test_passes.py ..", "* 2 pass*", ]) @@ -507,7 +507,7 @@ class TestTerminalFunctional: """) result = testdir.runpytest() verinfo = ".".join(map(str, py.std.sys.version_info[:3])) - extra = result.stdout.fnmatch_lines([ + result.stdout.fnmatch_lines([ "*===== test session starts ====*", "python: platform %s -- Python %s*" %( py.std.sys.platform, verinfo), # , py.std.sys.executable), @@ -577,13 +577,13 @@ def test_terminalreporter_reportopt_conf assert not tr.hasopt('qwe') """) result = testdir.runpytest() - assert result.stdout.fnmatch_lines([ + result.stdout.fnmatch_lines([ "*1 passed*" ]) def test_trace_reporting(testdir): result = testdir.runpytest("--traceconfig") - assert result.stdout.fnmatch_lines([ + result.stdout.fnmatch_lines([ "*active plugins*" ]) assert result.ret == 0 @@ -592,7 +592,7 @@ def test_trace_reporting(testdir): def test_show_funcarg(testdir, option): args = option._getcmdargs() + ["--funcargs"] result = testdir.runpytest(*args) - assert result.stdout.fnmatch_lines([ + result.stdout.fnmatch_lines([ "*tmpdir*", "*temporary directory*", ] --- a/testing/test_funcargs.py +++ b/testing/test_funcargs.py @@ -83,7 +83,7 @@ class TestFillFuncArgs: assert something is self """) result = testdir.runpytest(p) - assert result.stdout.fnmatch_lines([ + result.stdout.fnmatch_lines([ "*1 passed*" ]) @@ -201,7 +201,7 @@ class TestRequest: assert len(l) == 1 """) result = testdir.runpytest(p) - assert result.stdout.fnmatch_lines([ + result.stdout.fnmatch_lines([ "*1 passed*1 error*" ]) @@ -430,7 +430,7 @@ class TestGenfuncFunctional: assert arg1 == arg2 """) result = testdir.runpytest("-v", p) - assert result.stdout.fnmatch_lines([ + result.stdout.fnmatch_lines([ "*test_myfunc*0*PASS*", "*test_myfunc*1*FAIL*", "*1 failed, 1 passed*" @@ -451,7 +451,7 @@ class TestGenfuncFunctional: assert arg1 in (10, 20) """) result = testdir.runpytest("-v", p) - assert result.stdout.fnmatch_lines([ + result.stdout.fnmatch_lines([ "*test_func1*0*PASS*", "*test_func1*1*FAIL*", "*test_func2*PASS*", @@ -478,7 +478,7 @@ class TestGenfuncFunctional: assert arg1 == arg2 """) result = testdir.runpytest("-v", p) - assert result.stdout.fnmatch_lines([ + result.stdout.fnmatch_lines([ "*test_myfunc*hello*PASS*", "*test_myfunc*world*FAIL*", "*1 failed, 1 passed*" @@ -494,7 +494,7 @@ class TestGenfuncFunctional: assert hello == "world" """) result = testdir.runpytest("-v", p) - assert result.stdout.fnmatch_lines([ + result.stdout.fnmatch_lines([ "*test_myfunc*hello*PASS*", "*1 passed*" ]) @@ -511,7 +511,7 @@ class TestGenfuncFunctional: self.x = 1 """) result = testdir.runpytest("-v", p) - assert result.stdout.fnmatch_lines([ + result.stdout.fnmatch_lines([ "*test_func*0*PASS*", "*test_func*1*PASS*", "*2 pass*", --- a/testing/plugin/test_pytest_skipping.py +++ b/testing/plugin/test_pytest_skipping.py @@ -8,7 +8,7 @@ def test_xfail_not_report_default(testdi assert 0 """) result = testdir.runpytest(p, '-v') - extra = result.stdout.fnmatch_lines([ + result.stdout.fnmatch_lines([ "*1 expected failures*--report=xfailed*", ]) @@ -19,7 +19,7 @@ def test_skip_not_report_default(testdir py.test.skip("hello") """) result = testdir.runpytest(p, '-v') - extra = result.stdout.fnmatch_lines([ + result.stdout.fnmatch_lines([ "*1 skipped*--report=skipped*", ]) @@ -35,7 +35,7 @@ def test_xfail_decorator(testdir): assert 1 """) result = testdir.runpytest(p, '--report=xfailed') - extra = result.stdout.fnmatch_lines([ + result.stdout.fnmatch_lines([ "*expected failures*", "*test_one.test_this*test_one.py:4*", "*UNEXPECTEDLY PASSING*", @@ -52,7 +52,7 @@ def test_xfail_at_module(testdir): assert 0 """) result = testdir.runpytest(p, '--report=xfailed') - extra = result.stdout.fnmatch_lines([ + result.stdout.fnmatch_lines([ "*expected failures*", "*test_intentional_xfail*:4*", "*1 xfailed*" @@ -67,7 +67,7 @@ def test_xfail_evalfalse_but_fails(testd assert 0 """) result = testdir.runpytest(p, '--report=xfailed') - extra = result.stdout.fnmatch_lines([ + result.stdout.fnmatch_lines([ "*test_xfail_evalfalse_but_fails*:4*", "*1 failed*" ]) @@ -81,7 +81,7 @@ def test_skipif_decorator(testdir): assert 0 """) result = testdir.runpytest(p, '--report=skipped') - extra = result.stdout.fnmatch_lines([ + result.stdout.fnmatch_lines([ "*Skipped*platform*", "*1 skipped*" ]) @@ -99,7 +99,7 @@ def test_skipif_class(testdir): assert 0 """) result = testdir.runpytest(p) - extra = result.stdout.fnmatch_lines([ + result.stdout.fnmatch_lines([ "*2 skipped*" ]) @@ -165,7 +165,7 @@ def test_skipped_reasons_functional(test """ ) result = testdir.runpytest('--report=skipped') - extra = result.stdout.fnmatch_lines([ + result.stdout.fnmatch_lines([ "*test_one.py ss", "*test_two.py S", "___* skipped test summary *_", --- a/py/_plugin/pytest_pytester.py +++ b/py/_plugin/pytest_pytester.py @@ -486,4 +486,3 @@ class LineMatcher: extralines.append(nextline) else: assert line == nextline - return True --- a/testing/plugin/test_pytest_pytester.py +++ b/testing/plugin/test_pytest_pytester.py @@ -63,7 +63,7 @@ def test_testdir_runs_with_plugin(testdi assert 1 """) result = testdir.runpytest() - assert result.stdout.fnmatch_lines([ + result.stdout.fnmatch_lines([ "*1 passed*" ]) --- a/testing/plugin/test_pytest_helpconfig.py +++ b/testing/plugin/test_pytest_helpconfig.py @@ -6,14 +6,14 @@ def test_version(testdir): result = testdir.runpytest("--version") assert result.ret == 0 #p = py.path.local(py.__file__).dirpath() - assert result.stderr.fnmatch_lines([ + result.stderr.fnmatch_lines([ '*py.test*%s*imported from*' % (py.version, ) ]) def test_helpconfig(testdir): result = testdir.runpytest("--help-config") assert result.ret == 0 - assert result.stdout.fnmatch_lines([ + result.stdout.fnmatch_lines([ "*cmdline*conftest*ENV*", ]) @@ -36,7 +36,7 @@ def test_hookvalidation_unknown(testdir) """) result = testdir.runpytest() assert result.ret != 0 - assert result.stderr.fnmatch_lines([ + result.stderr.fnmatch_lines([ '*unknown hook*pytest_hello*' ]) --- a/testing/plugin/test_pytest_runner.py +++ b/testing/plugin/test_pytest_runner.py @@ -310,7 +310,7 @@ def test_runtest_in_module_ordering(test del item.function.mylist """) result = testdir.runpytest(p1) - assert result.stdout.fnmatch_lines([ + result.stdout.fnmatch_lines([ "*2 passed*" ]) --- a/testing/plugin/test_pytest_mark.py +++ b/testing/plugin/test_pytest_mark.py @@ -56,7 +56,7 @@ class TestFunctional: assert hasattr(test_hello, 'hello') """) result = testdir.runpytest(p) - assert result.stdout.fnmatch_lines(["*passed*"]) + result.stdout.fnmatch_lines(["*passed*"]) def test_mark_per_module(self, testdir): item = testdir.getitem(""" --- a/testing/test_pluginmanager.py +++ b/testing/test_pluginmanager.py @@ -81,7 +81,7 @@ class TestBootstrapping: monkeypatch.setenv('PYTEST_PLUGINS', 'pytest_x500', prepend=",") result = testdir.runpytest(p) assert result.ret == 0 - extra = result.stdout.fnmatch_lines(["*1 passed in*"]) + result.stdout.fnmatch_lines(["*1 passed in*"]) def test_import_plugin_importname(self, testdir): pluginmanager = PluginManager() --- a/testing/test_parseopt.py +++ b/testing/test_parseopt.py @@ -115,5 +115,5 @@ def test_addoption_parser_epilog(testdir """) result = testdir.runpytest('--help') #assert result.ret != 0 - assert result.stdout.fnmatch_lines(["*hint: hello world*"]) + result.stdout.fnmatch_lines(["*hint: hello world*"]) From commits-noreply at bitbucket.org Thu Apr 29 00:21:58 2010 From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org) Date: Wed, 28 Apr 2010 22:21:58 +0000 (UTC) Subject: [py-svn] py-trunk commit d42910531b64: be more robust about bad std stream encodings Message-ID: <20100428222158.186837EF84@bitbucket.org> # HG changeset patch -- Bitbucket.org # Project py-trunk # URL http://bitbucket.org/hpk42/py-trunk/overview # User Benjamin Peterson # Date 1272493189 18000 # Node ID d42910531b648556b0473dd6c07f7896476a8bc5 # Parent bce06f772dce796c5b5a43df9abdfabbf5afd7ff be more robust about bad std stream encodings --- a/py/_plugin/pytest_pytester.py +++ b/py/_plugin/pytest_pytester.py @@ -296,12 +296,14 @@ class TmpTestdir: f2.close() out = p1.read("rb").decode("utf-8").splitlines() err = p2.read("rb").decode("utf-8").splitlines() - if err: - for line in err: - py.builtin.print_(line, file=sys.stderr) - if out: - for line in out: - py.builtin.print_(line, file=sys.stdout) + def dump_lines(lines, fp): + try: + for line in lines: + py.builtin.print_(line, file=fp) + except UnicodeEncodeError: + print("couldn't print to %s because of encoding" % (fp,)) + dump_lines(out, sys.stdout) + dump_lines(err, sys.stderr) return RunResult(ret, out, err, time.time()-now) def runpybin(self, scriptname, *args): From commits-noreply at bitbucket.org Thu Apr 29 00:49:07 2010 From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org) Date: Wed, 28 Apr 2010 22:49:07 +0000 (UTC) Subject: [py-svn] pytest-xdist commit 65ce742e0e1a: adapt for fnmatch_lines changes on trunk Message-ID: <20100428224907.092DF7EF84@bitbucket.org> # HG changeset patch -- Bitbucket.org # Project pytest-xdist # URL http://bitbucket.org/hpk42/pytest-xdist/overview # User holger krekel # Date 1272494831 -7200 # Node ID 65ce742e0e1a4cd1c5d364be6e7e378390467d98 # Parent a3c7af80b76fb2d03036753ebf22c21305ad35d0 adapt for fnmatch_lines changes on trunk --- a/testing/test_boxed.py +++ b/testing/test_boxed.py @@ -8,7 +8,7 @@ def test_functional_boxed(testdir): os.kill(os.getpid(), 15) """) result = testdir.runpytest(p1, "--boxed") - assert result.stdout.fnmatch_lines([ + result.stdout.fnmatch_lines([ "*CRASHED*", "*1 failed*" ]) From commits-noreply at bitbucket.org Thu Apr 29 10:54:57 2010 From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org) Date: Thu, 29 Apr 2010 08:54:57 +0000 (UTC) Subject: [py-svn] py-trunk commit 571dde886a2c: fix a py3k related skip - py.io.TextIO on py3k should probably Message-ID: <20100429085457.E5FCD7EF90@bitbucket.org> # HG changeset patch -- Bitbucket.org # Project py-trunk # URL http://bitbucket.org/hpk42/py-trunk/overview # User holger krekel # Date 1272531020 -7200 # Node ID 571dde886a2c4424b98d794bf2d6beaca4359721 # Parent f67a76b4d53c91ff8490ab3610f908c5f789e3ce fix a py3k related skip - py.io.TextIO on py3k should probably not allow to write bytes to it. --- a/testing/io_/test_capture.py +++ b/testing/io_/test_capture.py @@ -43,8 +43,7 @@ class TestTextIO: f = py.io.TextIO() if sys.version_info >= (3,0): f.write("\u00f6") - py.test.skip("3k IO beahviour?") - f.write(bytes("hello", 'UTF-8')) + py.test.raises(TypeError, "f.write(bytes('hello', 'UTF-8'))") else: f.write(unicode("\u00f6", 'UTF-8')) f.write("hello") # bytes From commits-noreply at bitbucket.org Thu Apr 29 10:55:02 2010 From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org) Date: Thu, 29 Apr 2010 08:55:02 +0000 (UTC) Subject: [py-svn] py-trunk commit f67a76b4d53c: make py.io.ansi_print and py.io.get_terminal_width() directly available. Message-ID: <20100429085502.066597EF97@bitbucket.org> # HG changeset patch -- Bitbucket.org # Project py-trunk # URL http://bitbucket.org/hpk42/py-trunk/overview # User holger krekel # Date 1272530990 -7200 # Node ID f67a76b4d53c91ff8490ab3610f908c5f789e3ce # Parent ab65e0695c3941c364d642b51ba59b247c8fa5c2 make py.io.ansi_print and py.io.get_terminal_width() directly available. --- a/testing/io_/test_terminalwriter.py +++ b/testing/io_/test_terminalwriter.py @@ -2,6 +2,10 @@ import py import os, sys from py._io import terminalwriter +def test_get_terminal_width(): + x = py.io.get_terminal_width + assert x == terminalwriter.get_terminal_width + def test_terminal_width_COLUMNS(monkeypatch): """ Dummy test for get_terminal_width """ @@ -147,3 +151,13 @@ def test_attr_hasmarkup(): tw.line("hello", bold=True) s = tw.stringio.getvalue() assert len(s) > len("hello") + +def test_ansi_print(): + # we have no easy way to construct a file that + # represents a terminal + f = py.io.TextIO() + f.isatty = lambda: True + py.io.ansi_print("hello", 0x32, file=f) + text2 = f.getvalue() + assert text2.find("hello") != -1 + assert len(text2) >= len("hello") --- a/py/_cmdline/pylookup.py +++ b/py/_cmdline/pylookup.py @@ -9,7 +9,7 @@ prepended.""" import sys, os import py -from py._io.terminalwriter import ansi_print, terminal_width +from py.io import ansi_print, get_terminal_width import re def rec(p): @@ -21,6 +21,8 @@ parser.add_option("-i", "--ignore-case", parser.add_option("-C", "--context", action="store", type="int", dest="context", default=0, help="How many lines of output to show") +terminal_width = get_terminal_width() + def find_indexes(search_line, string): indexes = [] before = 0 --- a/py/__init__.py +++ b/py/__init__.py @@ -138,6 +138,8 @@ py.apipkg.initpkg(__name__, dict( 'StdCapture' : '._io.capture:StdCapture', 'StdCaptureFD' : '._io.capture:StdCaptureFD', 'TerminalWriter' : '._io.terminalwriter:TerminalWriter', + 'ansi_print' : '._io.terminalwriter:ansi_print', + 'get_terminal_width' : '._io.terminalwriter:get_terminal_width', }, # small and mean xml/html generation From commits-noreply at bitbucket.org Thu Apr 29 10:55:00 2010 From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org) Date: Thu, 29 Apr 2010 08:55:00 +0000 (UTC) Subject: [py-svn] py-trunk commit ab65e0695c39: expose py.code._reinterpret functions so that pypy and internal Message-ID: <20100429085500.1FF317EF94@bitbucket.org> # HG changeset patch -- Bitbucket.org # Project py-trunk # URL http://bitbucket.org/hpk42/py-trunk/overview # User holger krekel # Date 1272496856 -7200 # Node ID ab65e0695c3941c364d642b51ba59b247c8fa5c2 # Parent d42910531b648556b0473dd6c07f7896476a8bc5 expose py.code._reinterpret functions so that pypy and internal uses don't need to go through internal implementation imports --- a/testing/code/test_assertion.py +++ b/testing/code/test_assertion.py @@ -1,7 +1,5 @@ import py -#pytestmark = py.test.mark.skipif("sys.platform.startswith('java')") - def exvalue(): return py.std.sys.exc_info()[1] @@ -201,3 +199,7 @@ class TestView: assert codelines == ["4 + 5", "getitem('', 'join')", "setattr('x', 'y', 3)", "12 - 1"] +def test_underscore_api(): + py.code._AssertionError + py.code._reinterpret_old # used by pypy + py.code._reinterpret --- a/py/_plugin/pytest_assertion.py +++ b/py/_plugin/pytest_assertion.py @@ -8,9 +8,6 @@ def pytest_addoption(parser): help="disable python assert expression reinterpretation."), def pytest_configure(config): - #if sys.platform.startswith("java"): - # return # XXX assertions don't work yet with jython 2.5.1 - if not config.getvalue("noassert") and not config.getvalue("nomagic"): warn_about_missing_assertion() config._oldassertion = py.builtin.builtins.AssertionError --- a/py/_code/code.py +++ b/py/_code/code.py @@ -199,9 +199,8 @@ class TracebackEntry(object): """Reinterpret the failing statement and returns a detailed information about what operations are performed.""" if self.exprinfo is None: - from py._code import assertion source = str(self.statement).strip() - x = assertion.interpret(source, self.frame, should_fail=True) + x = py.code._reinterpret(source, self.frame, should_fail=True) if not isinstance(x, str): raise TypeError("interpret returned non-string %r" % (x,)) self.exprinfo = x --- a/py/_code/assertion.py +++ b/py/_code/assertion.py @@ -37,12 +37,6 @@ def _format_explanation(explanation): return '\n'.join(result) -if sys.version_info >= (2, 6) or (sys.platform.startswith("java")): - from py._code._assertionnew import interpret -else: - from py._code._assertionold import interpret - - class AssertionError(BuiltinAssertionError): def __init__(self, *args): @@ -65,7 +59,7 @@ class AssertionError(BuiltinAssertionErr # this can also occur during reinterpretation, when the # co_filename is set to "". if source: - self.msg = interpret(source, f, should_fail=True) + self.msg = reinterpret(source, f, should_fail=True) if not self.args: self.args = (self.msg,) else: @@ -73,3 +67,11 @@ class AssertionError(BuiltinAssertionErr if sys.version_info > (3, 0): AssertionError.__module__ = "builtins" + reinterpret_old = "old reinterpretation not available for py3" +else: + from py._code._assertionold import interpret as reinterpret_old +if sys.version_info >= (2, 6) or (sys.platform.startswith("java")): + from py._code._assertionnew import interpret as reinterpret +else: + reinterpret = reinterpret_old + --- a/py/__init__.py +++ b/py/__init__.py @@ -98,6 +98,8 @@ py.apipkg.initpkg(__name__, dict( 'patch_builtins' : '._code.code:patch_builtins', 'unpatch_builtins' : '._code.code:unpatch_builtins', '_AssertionError' : '._code.assertion:AssertionError', + '_reinterpret_old' : '._code.assertion:reinterpret_old', + '_reinterpret' : '._code.assertion:reinterpret', }, # backports and additions of builtins From commits-noreply at bitbucket.org Thu Apr 29 14:20:53 2010 From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org) Date: Thu, 29 Apr 2010 12:20:53 +0000 (UTC) Subject: [py-svn] py-trunk commit 7e17beb6c7c0: introduce new py.io.saferepr for printing the 'repr' of an object safely Message-ID: <20100429122053.600BB7EF80@bitbucket.org> # HG changeset patch -- Bitbucket.org # Project py-trunk # URL http://bitbucket.org/hpk42/py-trunk/overview # User holger krekel # Date 1272543427 -7200 # Node ID 7e17beb6c7c0e28f35f4ee60ce6d075e03f72446 # Parent 571dde886a2c4424b98d794bf2d6beaca4359721 introduce new py.io.saferepr for printing the 'repr' of an object safely and without consuming too much space --- a/py/__init__.py +++ b/py/__init__.py @@ -140,6 +140,7 @@ py.apipkg.initpkg(__name__, dict( 'TerminalWriter' : '._io.terminalwriter:TerminalWriter', 'ansi_print' : '._io.terminalwriter:ansi_print', 'get_terminal_width' : '._io.terminalwriter:get_terminal_width', + 'saferepr' : '._io.saferepr:saferepr', }, # small and mean xml/html generation --- a/py/_code/code.py +++ b/py/_code/code.py @@ -147,7 +147,7 @@ class Frame(object): def repr(self, object): """ return a 'safe' (non-recursive, one-line) string repr for 'object' """ - return safe_repr(object) + return py.io.saferepr(object) def is_true(self, object): return object @@ -457,7 +457,7 @@ class FormattedExcinfo(object): return source def _saferepr(self, obj): - return safe_repr(obj) + return py.io.saferepr(obj) def repr_args(self, entry): if self.funcargs: @@ -605,7 +605,7 @@ def unicode_or_repr(obj): except KeyboardInterrupt: raise except Exception: - return "" % safe_repr(obj) + return "" % py.io.saferepr(obj) class ReprExceptionInfo(TerminalRepr): def __init__(self, reprtraceback, reprcrash): @@ -717,48 +717,6 @@ class ReprFuncArgs(TerminalRepr): -class SafeRepr(reprlib.Repr): - """ subclass of repr.Repr that limits the resulting size of repr() - and includes information on exceptions raised during the call. - """ - def __init__(self, *args, **kwargs): - reprlib.Repr.__init__(self, *args, **kwargs) - self.maxstring = 240 # 3 * 80 chars - self.maxother = 160 # 2 * 80 chars - - def repr(self, x): - return self._callhelper(reprlib.Repr.repr, self, x) - - def repr_instance(self, x, level): - return self._callhelper(builtin_repr, x) - - def _callhelper(self, call, x, *args): - try: - # Try the vanilla repr and make sure that the result is a string - s = call(x, *args) - except (KeyboardInterrupt, MemoryError, SystemExit): - raise - except: - cls, e, tb = sys.exc_info() - try: - exc_name = cls.__name__ - except: - exc_name = 'unknown' - try: - exc_info = str(e) - except: - exc_info = 'unknown' - return '<[%s("%s") raised in repr()] %s object at 0x%x>' % ( - exc_name, exc_info, x.__class__.__name__, id(x)) - else: - if len(s) > self.maxstring: - i = max(0, (self.maxstring-3)//2) - j = max(0, self.maxstring-3-i) - s = s[:i] + '...' + s[len(s)-j:] - return s - -safe_repr = SafeRepr().repr - oldbuiltins = {} def patch_builtins(assertion=True, compile=True): --- /dev/null +++ b/testing/io_/test_saferepr.py @@ -0,0 +1,99 @@ +from __future__ import generators +import py +import sys + +saferepr = py.io.saferepr + +class TestSafeRepr: + def test_simple_repr(self): + assert saferepr(1) == '1' + assert saferepr(None) == 'None' + + def test_maxsize(self): + s = saferepr('x'*50, maxsize=25) + assert len(s) == 25 + expected = repr('x'*10 + '...' + 'x'*10) + assert s == expected + + def test_maxsize_error_on_instance(self): + class A: + def __repr__(self): + raise ValueError('...') + + s = saferepr(('*'*50, A()), maxsize=25) + assert len(s) == 25 + assert s[0] == '(' and s[-1] == ')' + + def test_exceptions(self): + class BrokenRepr: + def __init__(self, ex): + self.ex = ex + foo = 0 + def __repr__(self): + raise self.ex + class BrokenReprException(Exception): + __str__ = None + __repr__ = None + assert 'Exception' in saferepr(BrokenRepr(Exception("broken"))) + s = saferepr(BrokenReprException("really broken")) + assert 'TypeError' in s + if py.std.sys.version_info < (2,6): + assert 'unknown' in saferepr(BrokenRepr("string")) + else: + assert 'TypeError' in saferepr(BrokenRepr("string")) + + def test_big_repr(self): + from py._io.saferepr import SafeRepr + assert len(saferepr(range(1000))) <= \ + len('[' + SafeRepr().maxlist * "1000" + ']') + + def test_repr_on_newstyle(self): + class Function(object): + def __repr__(self): + return "<%s>" %(self.name) + try: + s = saferepr(Function()) + except Exception: + py.test.fail("saferepr failed for newstyle class") + +def test_builtin_patch_unpatch(monkeypatch): + cpy_builtin = py.builtin.builtins + comp = cpy_builtin.compile + def mycompile(*args, **kwargs): + return comp(*args, **kwargs) + class Sub(AssertionError): + pass + monkeypatch.setattr(cpy_builtin, 'AssertionError', Sub) + monkeypatch.setattr(cpy_builtin, 'compile', mycompile) + py.code.patch_builtins() + assert cpy_builtin.AssertionError != Sub + assert cpy_builtin.compile != mycompile + py.code.unpatch_builtins() + assert cpy_builtin.AssertionError is Sub + assert cpy_builtin.compile == mycompile + + +def test_unicode_handling(): + value = py.builtin._totext('\xc4\x85\xc4\x87\n', 'utf-8').encode('utf8') + def f(): + raise Exception(value) + excinfo = py.test.raises(Exception, f) + s = str(excinfo) + if sys.version_info[0] < 3: + u = unicode(excinfo) + +def test_unicode_or_repr(): + from py._code.code import unicode_or_repr + assert unicode_or_repr('hello') == "hello" + if sys.version_info[0] < 3: + s = unicode_or_repr('\xf6\xc4\x85') + else: + s = eval("unicode_or_repr(b'\\f6\\xc4\\x85')") + assert 'print-error' in s + assert 'c4' in s + class A: + def __repr__(self): + raise ValueError() + s = unicode_or_repr(A()) + assert 'print-error' in s + assert 'ValueError' in s --- a/CHANGELOG +++ b/CHANGELOG @@ -8,6 +8,10 @@ Changes between 1.2.1 and 1.2.2 (release - fixes for handling of unicode exception values and unprintable objects - (issue87) fix unboundlocal error in assertionold code - (issue86) improve documentation for looponfailing +- expose previously internal commonly useful methods: + py.io.get_terminal_with() -> return terminal width + py.io.ansi_print(...) -> print colored/bold text on linux/win32 + py.io.saferepr(obj) -> return limited representation string - expose test outcome related exceptions as py.test.skip.Exception, py.test.raises.Exception etc., useful mostly for plugins doing special outcome interpreteration/tweaking --- a/testing/code/test_code.py +++ b/testing/code/test_code.py @@ -1,7 +1,6 @@ from __future__ import generators import py import sys -from py._code.code import safe_repr failsonjython = py.test.mark.xfail("sys.platform.startswith('java')") @@ -139,43 +138,6 @@ def test_code_from_func(): -class TestSafeRepr: - def test_simple_repr(self): - assert safe_repr(1) == '1' - assert safe_repr(None) == 'None' - - def test_exceptions(self): - class BrokenRepr: - def __init__(self, ex): - self.ex = ex - foo = 0 - def __repr__(self): - raise self.ex - class BrokenReprException(Exception): - __str__ = None - __repr__ = None - assert 'Exception' in safe_repr(BrokenRepr(Exception("broken"))) - s = safe_repr(BrokenReprException("really broken")) - assert 'TypeError' in s - if py.std.sys.version_info < (2,6): - assert 'unknown' in safe_repr(BrokenRepr("string")) - else: - assert 'TypeError' in safe_repr(BrokenRepr("string")) - - def test_big_repr(self): - from py._code.code import SafeRepr - assert len(safe_repr(range(1000))) <= \ - len('[' + SafeRepr().maxlist * "1000" + ']') - - def test_repr_on_newstyle(self): - class Function(object): - def __repr__(self): - return "<%s>" %(self.name) - try: - s = safe_repr(Function()) - except Exception: - py.test.fail("saferepr failed for newstyle class") - def test_builtin_patch_unpatch(monkeypatch): cpy_builtin = py.builtin.builtins comp = cpy_builtin.compile --- /dev/null +++ b/py/_io/saferepr.py @@ -0,0 +1,55 @@ +import py +import sys, os.path + +builtin_repr = repr + +reprlib = py.builtin._tryimport('repr', 'reprlib') + +sysex = (KeyboardInterrupt, MemoryError, SystemExit) + +class SafeRepr(reprlib.Repr): + """ subclass of repr.Repr that limits the resulting size of repr() + and includes information on exceptions raised during the call. + """ + def repr(self, x): + return self._callhelper(reprlib.Repr.repr, self, x) + + def repr_instance(self, x, level): + return self._callhelper(builtin_repr, x) + + def _callhelper(self, call, x, *args): + try: + # Try the vanilla repr and make sure that the result is a string + s = call(x, *args) + except sysex: + raise + except: + cls, e, tb = sys.exc_info() + exc_name = getattr(cls, '__name__', 'unknown') + try: + exc_info = str(e) + except sysex: + raise + except: + exc_info = 'unknown' + return '<[%s("%s") raised in repr()] %s object at 0x%x>' % ( + exc_name, exc_info, x.__class__.__name__, id(x)) + else: + if len(s) > self.maxsize: + i = max(0, (self.maxsize-3)//2) + j = max(0, self.maxsize-3-i) + s = s[:i] + '...' + s[len(s)-j:] + return s + +def saferepr(obj, maxsize=240): + """ return a size-limited safe repr-string for the given object. + Failing __repr__ functions of user instances will be represented + with a short exception info and 'saferepr' generally takes + care to never raise exceptions itself. This function is a wrapper + around the Repr/reprlib functionality of the standard 2.6 lib. + """ + # review exception handling + srepr = SafeRepr() + srepr.maxstring = maxsize + srepr.maxsize = maxsize + return srepr.repr(obj) From commits-noreply at bitbucket.org Thu Apr 29 16:20:58 2010 From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org) Date: Thu, 29 Apr 2010 14:20:58 +0000 (UTC) Subject: [py-svn] py-trunk commit a340029c7afc: introduce a new pytest_ignore_collect_path(path, config) hook - Message-ID: <20100429142058.A409A7EF21@bitbucket.org> # HG changeset patch -- Bitbucket.org # Project py-trunk # URL http://bitbucket.org/hpk42/py-trunk/overview # User holger krekel # Date 1272550855 -7200 # Node ID a340029c7afce22d3f93c307b15f62b5723f6ece # Parent 58359564336bea792644504ff0f74aa9c3fee299 introduce a new pytest_ignore_collect_path(path, config) hook - returning a true value will prevent considering the path for collection The hook is called for both files and directory paths. --- a/py/_test/collect.py +++ b/py/_test/collect.py @@ -296,28 +296,15 @@ class Directory(FSCollector): l.append(res) return l - def _ignore(self, path): - 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: - if path == p or path.relto(p): - return True - def consider(self, path): - if self._ignore(path): - return + if self.ihook.pytest_ignore_collect_path(path=path, config=self.config): + return if path.check(file=1): res = self.consider_file(path) elif path.check(dir=1): res = self.consider_dir(path) else: - res = None + res = None if isinstance(res, list): # throw out identical results l = [] --- a/py/_plugin/pytest_default.py +++ b/py/_plugin/pytest_default.py @@ -24,6 +24,20 @@ def pytest_funcarg__pytestconfig(request """ the pytest config object with access to command line opts.""" return request.config +def pytest_ignore_collect_path(path, config): + ignore_paths = config.getconftest_pathlist("collect_ignore", path=path) + ignore_paths = ignore_paths or [] + excludeopt = 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: + if path == p or path.relto(p): + return True + + def pytest_collect_directory(path, parent): # XXX reconsider the following comment # not use parent.Directory here as we generally --- a/testing/test_collect.py +++ b/testing/test_collect.py @@ -157,6 +157,21 @@ class TestPrunetraceback: ]) class TestCustomConftests: + def test_ignore_collect_path(self, testdir): + testdir.makeconftest(""" + def pytest_ignore_collect_path(path, config): + return path.basename.startswith("x") or \ + path.basename == "test_one.py" + """) + testdir.mkdir("xy123").ensure("test_hello.py").write( + "syntax error" + ) + testdir.makepyfile("def test_hello(): pass") + testdir.makepyfile(test_one="syntax error") + result = testdir.runpytest() + assert result.ret == 0 + result.stdout.fnmatch_lines(["*1 passed*"]) + def test_collectignore_exclude_on_option(self, testdir): testdir.makeconftest(""" collect_ignore = ['hello', 'test_world.py'] --- a/py/_plugin/hookspec.py +++ b/py/_plugin/hookspec.py @@ -27,6 +27,13 @@ def pytest_unconfigure(config): # collection hooks # ------------------------------------------------------------------------- +def pytest_ignore_collect_path(path, config): + """ return true value to prevent considering this path for collection. + This hook is consulted for all files and directories prior to considering + collection hooks. + """ +pytest_ignore_collect_path.firstresult = True + def pytest_collect_directory(path, parent): """ return Collection node or None for the given path. """ pytest_collect_directory.firstresult = True --- a/CHANGELOG +++ b/CHANGELOG @@ -8,6 +8,12 @@ Changes between 1.2.1 and 1.2.2 (release - fixes for handling of unicode exception values and unprintable objects - (issue87) fix unboundlocal error in assertionold code - (issue86) improve documentation for looponfailing +- add a new pytest_ignore_collect_path(path, config) hook to allow projects and + plugins to define exclusion behaviour for their directory structure - + for example you may define in a conftest.py this method: + def pytest_ignore_collect_path(path): + return path.check(link=1) + to prevent even a collection try of any tests in symlinked dirs. - expose previously internal commonly useful methods: py.io.get_terminal_with() -> return terminal width py.io.ansi_print(...) -> print colored/bold text on linux/win32 From commits-noreply at bitbucket.org Thu Apr 29 16:20:56 2010 From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org) Date: Thu, 29 Apr 2010 14:20:56 +0000 (UTC) Subject: [py-svn] py-trunk commit 58359564336b: refine to allow more characters for instance reprs like it was before Message-ID: <20100429142056.CBCD17EEFE@bitbucket.org> # HG changeset patch -- Bitbucket.org # Project py-trunk # URL http://bitbucket.org/hpk42/py-trunk/overview # User holger krekel # Date 1272549179 -7200 # Node ID 58359564336bea792644504ff0f74aa9c3fee299 # Parent 7e17beb6c7c0e28f35f4ee60ce6d075e03f72446 refine to allow more characters for instance reprs like it was before --- a/py/_io/saferepr.py +++ b/py/_io/saferepr.py @@ -52,4 +52,5 @@ def saferepr(obj, maxsize=240): srepr = SafeRepr() srepr.maxstring = maxsize srepr.maxsize = maxsize + srepr.maxother = 160 return srepr.repr(obj) From commits-noreply at bitbucket.org Thu Apr 29 16:54:40 2010 From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org) Date: Thu, 29 Apr 2010 14:54:40 +0000 (UTC) Subject: [py-svn] py-trunk commit 3839155a85a8: introduce new pytest_pycollect_makemodule(path, parent) hook for Message-ID: <20100429145440.14D457EF7A@bitbucket.org> # HG changeset patch -- Bitbucket.org # Project py-trunk # URL http://bitbucket.org/hpk42/py-trunk/overview # User holger krekel # Date 1272552809 -7200 # Node ID 3839155a85a88d587b79d67a362d5d194e33be64 # Parent a340029c7afce22d3f93c307b15f62b5723f6ece introduce new pytest_pycollect_makemodule(path, parent) hook for allowing customization of the Module collection object for a matching test module. --- a/py/_plugin/pytest_default.py +++ b/py/_plugin/pytest_default.py @@ -18,7 +18,11 @@ def pytest_collect_file(path, parent): if pb.startswith("test_") or pb.endswith("_test") or \ path in parent.config._argfspaths: if ext == ".py": - return parent.Module(path, parent=parent) + return parent.ihook.pytest_pycollect_makemodule( + path=path, parent=parent) + +def pytest_pycollect_makemodule(path, parent): + return parent.Module(path, parent) def pytest_funcarg__pytestconfig(request): """ the pytest config object with access to command line opts.""" --- a/testing/test_pycollect.py +++ b/testing/test_pycollect.py @@ -313,6 +313,23 @@ class TestSorting: class TestConftestCustomization: + def test_pytest_pycollect_module(self, testdir): + testdir.makeconftest(""" + import py + class MyModule(py.test.collect.Module): + pass + def pytest_pycollect_makemodule(path, parent): + if path.basename == "test_xyz.py": + return MyModule(path, parent) + """) + testdir.makepyfile("def some(): pass") + testdir.makepyfile(test_xyz="") + result = testdir.runpytest("--collectonly") + result.stdout.fnmatch_lines([ + "* return terminal width py.io.ansi_print(...) -> print colored/bold text on linux/win32 From commits-noreply at bitbucket.org Fri Apr 30 09:53:13 2010 From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org) Date: Fri, 30 Apr 2010 07:53:13 +0000 (UTC) Subject: [py-svn] py-trunk commit 5964c0da6429: add close method to DontReadFromInput so multiprocessing can close it Message-ID: <20100430075313.160D57EE2B@bitbucket.org> # HG changeset patch -- Bitbucket.org # Project py-trunk # URL http://bitbucket.org/hpk42/py-trunk/overview # User Ronny Pfannschmidt # Date 1272563203 -7200 # Node ID 5964c0da64293bb2d33a7506a4d4da7afbc40c6e # Parent 3839155a85a88d587b79d67a362d5d194e33be64 add close method to DontReadFromInput so multiprocessing can close it --- a/py/_io/capture.py +++ b/py/_io/capture.py @@ -334,6 +334,8 @@ class DontReadFromInput: raise ValueError("redirected Stdin is pseudofile, has no fileno()") def isatty(self): return False + def close(self): + pass try: devnullpath = os.devnull --- a/testing/io_/test_capture.py +++ b/testing/io_/test_capture.py @@ -66,6 +66,7 @@ def test_dontreadfrominput(): py.test.raises(IOError, f.readlines) py.test.raises(IOError, iter, f) py.test.raises(ValueError, f.fileno) + f.close() # just for completeness def pytest_funcarg__tmpfile(request): testdir = request.getfuncargvalue("testdir") From commits-noreply at bitbucket.org Fri Apr 30 09:53:14 2010 From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org) Date: Fri, 30 Apr 2010 07:53:14 +0000 (UTC) Subject: [py-svn] py-trunk commit c3cb8c7b94aa: fixing and adding to CHANGELOG Message-ID: <20100430075314.D0AA57EF73@bitbucket.org> # HG changeset patch -- Bitbucket.org # Project py-trunk # URL http://bitbucket.org/hpk42/py-trunk/overview # User holger krekel # Date 1272614006 -7200 # Node ID c3cb8c7b94aa242cb8b99cd1e95b3bd0691dced0 # Parent 5964c0da64293bb2d33a7506a4d4da7afbc40c6e fixing and adding to CHANGELOG --- a/CHANGELOG +++ b/CHANGELOG @@ -3,7 +3,7 @@ Changes between 1.2.1 and 1.2.2 (release - new mechanism to allow plugins to register new hooks - (issue85) fix junitxml plugin to handle tests with non-ascii output -- fix some python3 compatibility issues (thanks Benjamin Peterson) +- fix/refine python3 compatibility (thanks Benjamin Peterson) - fixes for making the jython/win32 combination work - fixes for handling of unicode exception values and unprintable objects - (issue87) fix unboundlocal error in assertionold code @@ -14,7 +14,7 @@ Changes between 1.2.1 and 1.2.2 (release def pytest_ignore_collect_path(path): return path.check(link=1) to prevent even a collection try of any tests in symlinked dirs. -- introduce new pytest_pycollect_makemodule(path, parent) hook for +- new pytest_pycollect_makemodule(path, parent) hook for allowing customization of the Module collection object for a matching test module. - expose previously internal commonly useful methods: @@ -23,7 +23,8 @@ Changes between 1.2.1 and 1.2.2 (release py.io.saferepr(obj) -> return limited representation string - expose test outcome related exceptions as py.test.skip.Exception, py.test.raises.Exception etc., useful mostly for plugins - doing special outcome interpreteration/tweaking + doing special outcome interpretation/tweaking +- refine IO capturing: stdin-redirect pseudo-file now has a NOP close() method - ship distribute_setup.py version 0.6.10 - added links to the new capturelog and coverage plugins From commits-noreply at bitbucket.org Fri Apr 30 15:56:26 2010 From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org) Date: Fri, 30 Apr 2010 13:56:26 +0000 (UTC) Subject: [py-svn] pytest-xdist commit 2853276d3fd9: fixing a (likely) race condition when simultanous pickling/unpickling can leave Message-ID: <20100430135626.88C247EF21@bitbucket.org> # HG changeset patch -- Bitbucket.org # Project pytest-xdist # URL http://bitbucket.org/hpk42/pytest-xdist/overview # User holger krekel # Date 1272635808 -7200 # Node ID 2853276d3fd97472e67417ef167918024309b1a6 # Parent 65ce742e0e1a4cd1c5d364be6e7e378390467d98 fixing a (likely) race condition when simultanous pickling/unpickling can leave inconsistent memo states. Rather than adding locks, the fix actually simplifies the code by keeping the pickling/unpickling memo's in sync directly and more efficiently, also removing a long outstanding XXX. --- a/testing/test_mypickle.py +++ b/testing/test_mypickle.py @@ -252,10 +252,3 @@ class TestPickleChannelFunctional: channel.waitclose(timeout=2) - -def test_explode(): - from xdist.mypickle import explode - assert isinstance(explode({}.values()), list) - l = [] - assert isinstance(explode(l), list) - assert explode(l) is l --- a/xdist/mypickle.py +++ b/xdist/mypickle.py @@ -31,13 +31,12 @@ class MyPickler(Pickler): """ Pickler with a custom memoize() to take care of unique ID creation. See the usage in ImmutablePickler - XXX we could probably extend Pickler - and Unpickler classes to directly - update the other'S memos. """ - def __init__(self, file, protocol, uneven): + def __init__(self, immo, file, protocol, uneven): Pickler.__init__(self, file, protocol) self.uneven = uneven + self._unpicklememo = immo._unpicklememo + self.memo = immo._picklememo def memoize(self, obj): if self.fast: @@ -47,13 +46,26 @@ class MyPickler(Pickler): key = memo_len * 2 + self.uneven self.write(self.put(key)) self.memo[id(obj)] = key, obj + key = makekey(key) + if key in self._unpicklememo: + assert self._unpicklememo[key] is obj + dict.__setitem__(self._unpicklememo, key, obj) #if sys.version_info < (3,0): # def save_string(self, obj, pack=struct.pack): # obj = unicode(obj) # self.save_unicode(obj, pack=pack) # Pickler.dispatch[str] = save_string - + +class UnpicklingDict(dict): + def __init__(self, picklememo): + super(UnpicklingDict, self).__init__() + self._picklememo = picklememo + + def __setitem__(self, key, obj): + super(UnpicklingDict, self).__setitem__(key, obj) + self._picklememo[id(obj)] = (fromkey(key), obj) + class ImmutablePickler: def __init__(self, uneven, protocol=0): """ ImmutablePicklers are instantiated in Pairs. @@ -64,7 +76,7 @@ class ImmutablePickler: parameter. """ self._picklememo = {} - self._unpicklememo = {} + self._unpicklememo = UnpicklingDict(self._picklememo) self._protocol = protocol self.uneven = uneven and 1 or 0 @@ -73,18 +85,13 @@ class ImmutablePickler: # which be the case e.g. if you want to pickle # from a forked process back to the original f = py.io.BytesIO() - pickler = MyPickler(f, self._protocol, uneven=self.uneven) - pickler.memo = self._picklememo + pickler = MyPickler(self, f, self._protocol, uneven=self.uneven) pickler.memoize(obj) - self._updateunpicklememo() def dumps(self, obj): f = py.io.BytesIO() - pickler = MyPickler(f, self._protocol, uneven=self.uneven) - pickler.memo = self._picklememo + pickler = MyPickler(self, f, self._protocol, uneven=self.uneven) pickler.dump(obj) - if obj is not None: - self._updateunpicklememo() #print >>debug, "dumped", obj #print >>debug, "picklememo", self._picklememo return f.getvalue() @@ -94,30 +101,10 @@ class ImmutablePickler: unpickler = Unpickler(f) unpickler.memo = self._unpicklememo res = unpickler.load() - self._updatepicklememo() #print >>debug, "loaded", res #print >>debug, "unpicklememo", self._unpicklememo return res - def _updatepicklememo(self): - for x, obj in explode(self._unpicklememo.items()): - self._picklememo[id(obj)] = (fromkey(x), obj) - - def _updateunpicklememo(self): - for key,obj in explode(self._picklememo.values()): - key = makekey(key) - if key in self._unpicklememo: - assert self._unpicklememo[key] is obj - self._unpicklememo[key] = obj - -def explode(obj): - try: - obj[0] - except TypeError: - return list(obj) - except IndexError: - pass - return obj NO_ENDMARKER_WANTED = object() From commits-noreply at bitbucket.org Fri Apr 30 16:28:10 2010 From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org) Date: Fri, 30 Apr 2010 14:28:10 +0000 (UTC) Subject: [py-svn] pytest-xdist commit c74b6d807c87: update CHANGELOG Message-ID: <20100430142810.483077EF1D@bitbucket.org> # HG changeset patch -- Bitbucket.org # Project pytest-xdist # URL http://bitbucket.org/hpk42/pytest-xdist/overview # User holger krekel # Date 1272637690 -7200 # Node ID c74b6d807c87240f9bd9889d8c93a1ae328c3325 # Parent 2853276d3fd97472e67417ef167918024309b1a6 update CHANGELOG --- a/CHANGELOG +++ b/CHANGELOG @@ -3,14 +3,11 @@ 1.2 - fix issue79: sessionfinish/teardown hooks more systematically on the slave side - - introduce a new data input/output mechanism to allow the master side to send and receive data from a slave. - -- use new register hooks facility of py.test>=1.2.2 - +- fix race condition in underlying pickling/unpickling handling +- use and require new register hooks facility of py.test>=1.2.2 - fix some python3 related pickling related race conditions - - fix PyPI description 1.1 From commits-noreply at bitbucket.org Fri Apr 30 20:03:42 2010 From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org) Date: Fri, 30 Apr 2010 18:03:42 +0000 (UTC) Subject: [py-svn] py-trunk commit 593d06e2b51f: update implementation ISSUES, add one for session/refinements/a collection crash Message-ID: <20100430180342.A7AB07EF8C@bitbucket.org> # HG changeset patch -- Bitbucket.org # Project py-trunk # URL http://bitbucket.org/hpk42/py-trunk/overview # User holger krekel # Date 1272650637 -7200 # Node ID 593d06e2b51fe14cbce018324b8253467a4a6752 # Parent c3cb8c7b94aa242cb8b99cd1e95b3bd0691dced0 update implementation ISSUES, add one for session/refinements/a collection crash --- a/ISSUES.txt +++ b/ISSUES.txt @@ -1,3 +1,16 @@ +refine session initialization / fix custom collect crash +--------------------------------------------------------------- +tags: bug 1.2 core xdist + +When calling "py.test path/X" py.test can crash if the collection +of that directory is skipped. Calling "py.test path" will give +proper output. The reason is that for the very first colitems +getinitialnodes() and a collection is done before the fully +controlled session and pytest_make_collect_report protocol takes over. +Try to remove the redundant getinitialnodes related logic and amend +the session collect logic to care for this "initial" case as well. +Apart from simplification a side effect the dsession's session +and the core session probably converge some more. introduce py.test.mark.nocollect ------------------------------------------------------- @@ -115,6 +128,10 @@ tags: experimental-wish 1.2 Users have expressed the wish to have funcargs available to setup functions. Experiment with allowing funcargs there - it might also help to make the py.test.ensuretemp and config deprecation. +For filling funcargs for setup methods, we could call funcarg +factories with a request object that not have a cls/function +attributes. However, how to handle parametrized test functions +and funcargs? consider pytest_addsyspath hook ----------------------------------------- @@ -146,7 +163,7 @@ Now that external plugins are becoming m it would be useful to have external plugins along with their versions displayed as a header line. -generate/deal with plugin docs +generate/refine plugin doc generation ---------------------------------------------------------------- tags: feature 1.2 @@ -155,16 +172,3 @@ have docs living with the plugin and req be available on doc generation time, at least when the target is the website? Or rather go for interactive help? -improved reporting on funcarg usage / name mismatches ----------------------------------------------------------------- -tags: feature 1.2 - -see to improve help and support for funcarg usage, -i.e. when a funcarg does not match any provided one. -Also consider implementing py.test --funcargs to -show available funcargs - it should honour the -path::TestClass syntax so one can easily inspect -where funcargs come from or which are available. - - -