From commits-noreply at bitbucket.org Wed Sep 4 14:55:30 2013 From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org) Date: Wed, 04 Sep 2013 12:55:30 -0000 Subject: [Pytest-commit] commit/tox: 4 new changesets Message-ID: <20130904125530.26514.57329@app10.ash-private.bitbucket.org> 4 new commits in tox: https://bitbucket.org/hpk42/tox/commits/cf9ab5359f68/ Changeset: cf9ab5359f68 User: hpk42 Date: 2013-08-21 13:45:17 Summary: remove prints Affected #: 1 file diff -r 714d84b76856a23f7b4967903130b95cd973475a -r cf9ab5359f68a6e6516012d72cc6c066dbb0612f tox/interpreters.py --- a/tox/interpreters.py +++ b/tox/interpreters.py @@ -20,7 +20,6 @@ return self.name2executable[name] except KeyError: self.name2executable[name] = e = find_executable(name) - print ("executable for %s is %s" %(name, e)) return e def get_info(self, name=None, executable=None): @@ -32,7 +31,6 @@ executable = self.get_executable(name) if not executable: return NoInterpreterInfo(name=name) - print ("get info for %s" % executable) try: return self.executable2info[executable] except KeyError: https://bitbucket.org/hpk42/tox/commits/eb540f23a82e/ Changeset: eb540f23a82e User: hpk42 Date: 2013-09-04 14:32:42 Summary: fix test runs on environments without a home directory (in which case we use toxinidir as the homedir) Affected #: 4 files diff -r cf9ab5359f68a6e6516012d72cc6c066dbb0612f -r eb540f23a82efc49cc06f9ecd469506549b325bd CHANGELOG --- a/CHANGELOG +++ b/CHANGELOG @@ -12,6 +12,9 @@ - fix issue118: correctly have two tests use realpath(). Thanks Barry Warsaw. +- fix test runs on environments without a home directory + (in this case we use toxinidir as the homedir) + 1.6.0 ----------------- diff -r cf9ab5359f68a6e6516012d72cc6c066dbb0612f -r eb540f23a82efc49cc06f9ecd469506549b325bd tests/test_config.py --- a/tests/test_config.py +++ b/tests/test_config.py @@ -73,8 +73,7 @@ def test_defaults_distshare(self, tmpdir, newconfig): config = newconfig([], "") envconfig = config.envconfigs['python'] - homedir = py.path.local._gethomedir() - assert config.distshare == homedir.join(".tox", "distshare") + assert config.distshare == config.homedir.join(".tox", "distshare") def test_defaults_changed_dir(self, tmpdir, newconfig): tmpdir.mkdir("abc").chdir() @@ -100,6 +99,18 @@ old.chdir() assert config.toxinipath == toxinipath +def test_get_homedir(monkeypatch): + monkeypatch.setattr(py.path.local, "_gethomedir", + classmethod(lambda x: {}[1])) + assert not get_homedir() + monkeypatch.setattr(py.path.local, "_gethomedir", + classmethod(lambda x: 0/0)) + assert not get_homedir() + monkeypatch.setattr(py.path.local, "_gethomedir", + classmethod(lambda x: "123")) + assert get_homedir() == "123" + + class TestIniParser: def test_getdefault_single(self, tmpdir, newconfig): config = newconfig(""" @@ -582,7 +593,7 @@ assert argv[3][0] == conf.envbindir assert argv[4][0] == conf.envtmpdir assert argv[5][0] == conf.envpython - assert argv[6][0] == str(py.path.local._gethomedir()) + assert argv[6][0] == str(config.homedir) assert argv[7][0] == config.homedir.join(".tox", "distshare") assert argv[8][0] == conf.envlogdir @@ -903,8 +914,7 @@ pypi = http://pypi.python.org/simple """ config = newconfig([], inisource) - homedir = str(py.path.local._gethomedir()) - expected = "file://%s/.pip/downloads/simple" % homedir + expected = "file://%s/.pip/downloads/simple" % config.homedir assert config.indexserver['default'].url == expected assert config.indexserver['local1'].url == \ config.indexserver['default'].url diff -r cf9ab5359f68a6e6516012d72cc6c066dbb0612f -r eb540f23a82efc49cc06f9ecd469506549b325bd tests/test_z_cmdline.py --- a/tests/test_z_cmdline.py +++ b/tests/test_z_cmdline.py @@ -625,18 +625,19 @@ sdist_path = session.sdist() assert sdist_path == p -#@pytest.mark.xfail("sys.platform == 'win32'", reason="test needs better impl") + at pytest.mark.xfail("sys.platform == 'win32' and sys.version_info < (2,6)", + reason="test needs better impl") def test_envsitepackagesdir(cmd, initproj): initproj("pkg512-0.0.5", filedefs={ 'tox.ini': """ [testenv] commands= - python -c "print('X:{envsitepackagesdir}')" + python -c "print(r'X:{envsitepackagesdir}')" """}) result = cmd.run("tox") assert result.ret == 0 result.stdout.fnmatch_lines(""" - X:*.tox*python*site-packages* + X:*tox*site-packages* """) def verify_json_report_format(data, testenvs=True): diff -r cf9ab5359f68a6e6516012d72cc6c066dbb0612f -r eb540f23a82efc49cc06f9ecd469506549b325bd tox/_config.py --- a/tox/_config.py +++ b/tox/_config.py @@ -120,12 +120,19 @@ help="additional arguments available to command positional substition") return parser -class Config: +class Config(object): def __init__(self): self.envconfigs = {} self.invocationcwd = py.path.local() self.interpreters = Interpreters() + @property + def homedir(self): + homedir = get_homedir() + if homedir is None: + homedir = self.toxinidir # XXX good idea? + return homedir + class VenvConfig: def __init__(self, **kw): self.__dict__.update(kw) @@ -166,6 +173,12 @@ testenvprefix = "testenv:" +def get_homedir(): + try: + return py.path.local._gethomedir() + except Exception: + return None + class parseini: def __init__(self, config, inipath): config.toxinipath = inipath @@ -186,9 +199,7 @@ else: raise ValueError("invalid context") - config.homedir = py.path.local._gethomedir() - if config.homedir is None: - config.homedir = config.toxinidir # XXX good idea? + reader.addsubstitions(toxinidir=config.toxinidir, homedir=config.homedir) config.toxworkdir = reader.getpath(toxsection, "toxworkdir", https://bitbucket.org/hpk42/tox/commits/264fe64185e4/ Changeset: 264fe64185e4 User: hpk42 Date: 2013-09-04 14:54:02 Summary: fix issue117: python2.5 fix: don't use ``--insecure`` option because its very existence depends on presence of "ssl". If you want to support python2.5/pip1.3.1 based test environments you need to install ssl and/or use PIP_INSECURE=1 through ``setenv``. section. Affected #: 4 files diff -r eb540f23a82efc49cc06f9ecd469506549b325bd -r 264fe64185e4559c73d48af9db7471ce66bfe64f CHANGELOG --- a/CHANGELOG +++ b/CHANGELOG @@ -15,6 +15,11 @@ - fix test runs on environments without a home directory (in this case we use toxinidir as the homedir) +- fix issue117: python2.5 fix: don't use ``--insecure`` option because + its very existence depends on presence of "ssl". If you + want to support python2.5/pip1.3.1 based test environments you need + to install ssl and/or use PIP_INSECURE=1 through ``setenv``. section. + 1.6.0 ----------------- diff -r eb540f23a82efc49cc06f9ecd469506549b325bd -r 264fe64185e4559c73d48af9db7471ce66bfe64f doc/config.txt --- a/doc/config.txt +++ b/doc/config.txt @@ -99,10 +99,11 @@ **default on environments using python2.5**:: - pip install --insecure {opts} {packages}`` + pip install {opts} {packages}`` - (this will use pip<1.4 (so no ``--pre`` option) and python2.5 - typically has no SSL support, therefore ``--insecure``). + this will use pip<1.4 which has no ``--pre`` option. Note also + that for python2.5 support you may need to install ssl and/or + use ``setenv = PIP_INSECURE=1`` in a py25 based testenv. .. confval:: whitelist_externals=MULTI-LINE-LIST diff -r eb540f23a82efc49cc06f9ecd469506549b325bd -r 264fe64185e4559c73d48af9db7471ce66bfe64f tests/test_config.py --- a/tests/test_config.py +++ b/tests/test_config.py @@ -509,7 +509,7 @@ for name in ("x25", "py25-x"): env = config.envconfigs[name] assert env.install_command == \ - "pip install --insecure {opts} {packages}".split() + "pip install {opts} {packages}".split() env = config.envconfigs["py26"] assert env.install_command == \ "pip install --pre {opts} {packages}".split() diff -r eb540f23a82efc49cc06f9ecd469506549b325bd -r 264fe64185e4559c73d48af9db7471ce66bfe64f tox/_config.py --- a/tox/_config.py +++ b/tox/_config.py @@ -330,13 +330,11 @@ downloadcache = os.environ.get("PIP_DOWNLOAD_CACHE", downloadcache) vc.downloadcache = py.path.local(downloadcache) - # on python 2.5 we can't use "--pre" and we typically - # need to use --insecure for pip commands because python2.5 - # doesn't support SSL + # on pip-1.3.1/python 2.5 we can't use "--pre". pip_default_opts = ["{opts}", "{packages}"] info = vc._basepython_info if info.runnable and info.version_info < (2,6): - pip_default_opts.insert(0, "--insecure") + pass else: pip_default_opts.insert(0, "--pre") vc.install_command = reader.getargv( https://bitbucket.org/hpk42/tox/commits/c3ba84e0cc42/ Changeset: c3ba84e0cc42 User: hpk42 Date: 2013-09-04 14:55:13 Summary: merge Affected #: 0 files Repository URL: https://bitbucket.org/hpk42/tox/ -- This is a commit notification from bitbucket.org. You are receiving this because you have the service enabled, addressing the recipient of this email. From commits-noreply at bitbucket.org Wed Sep 4 15:56:11 2013 From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org) Date: Wed, 04 Sep 2013 13:56:11 -0000 Subject: [Pytest-commit] commit/tox: 2 new changesets Message-ID: <20130904135611.10843.30518@app02.ash-private.bitbucket.org> 2 new commits in tox: https://bitbucket.org/hpk42/tox/commits/10157852d890/ Changeset: 10157852d890 User: hpk42 Date: 2013-09-04 15:37:24 Summary: fix issue102: change to {toxinidir} when installing packages and dependencies. this allows to use relative path like in "-rrequirements.txt". Affected #: 7 files diff -r c3ba84e0cc42ff7e2f59c1d481e8994461fd6e47 -r 10157852d89032acc68c6ffadda02bce713b91b8 CHANGELOG --- a/CHANGELOG +++ b/CHANGELOG @@ -20,6 +20,9 @@ want to support python2.5/pip1.3.1 based test environments you need to install ssl and/or use PIP_INSECURE=1 through ``setenv``. section. +- fix issue102: change to {toxinidir} when installing dependencies. + this allows to use relative path like in "-rrequirements.txt". + 1.6.0 ----------------- diff -r c3ba84e0cc42ff7e2f59c1d481e8994461fd6e47 -r 10157852d89032acc68c6ffadda02bce713b91b8 doc/conf.py --- a/doc/conf.py +++ b/doc/conf.py @@ -48,7 +48,7 @@ # built documents. # # The short X.Y version. -release = version = "1.6.0" +release = version = "1.6.1" # The full version, including alpha/beta/rc tags. # The language for content autogenerated by Sphinx. Refer to documentation diff -r c3ba84e0cc42ff7e2f59c1d481e8994461fd6e47 -r 10157852d89032acc68c6ffadda02bce713b91b8 doc/config.txt --- a/doc/config.txt +++ b/doc/config.txt @@ -124,7 +124,7 @@ test-specific dependencies -.to be installed into the environment prior to project package installation. Each line defines a dependency, which will be - passed to easy_install/pip for processing. A line specify a file, + passed to the installer command for processing. A line specify a file, an URL or a package name. You can additionally specify an :confval:`indexserver` to use for installing this dependency. All derived dependencies (deps required by the dep) will then be @@ -132,6 +132,9 @@ deps = :myindexserver:pkg + (Experimentally introduced in 1.6.1) all installer commands are executed + using the ``{toxinidir}`` as the current working directory. + .. confval:: setenv=MULTI-LINE-LIST .. versionadded:: 0.9 diff -r c3ba84e0cc42ff7e2f59c1d481e8994461fd6e47 -r 10157852d89032acc68c6ffadda02bce713b91b8 doc/example/basic.txt --- a/doc/example/basic.txt +++ b/doc/example/basic.txt @@ -44,7 +44,7 @@ However, you can also create your own test environment names, see some of the examples in :doc:`examples <../examples>`. -whitelisting a non-virtualenv commands +whitelisting non-virtualenv commands ----------------------------------------------- .. versionadded:: 1.5 @@ -64,6 +64,21 @@ .. _multiindex: +depending on requirements.txt +----------------------------------------------- + +.. versionadded:: 1.6.1 + +(experimental) If you have a ``requirements.txt`` file +you can add it to your ``deps`` variable like this:: + + deps = -rrequirements.txt + +All installation commands are executed using ``{toxinidir}}`` +(the directory where ``tox.ini`` resides) as the current +working directory. Therefore, the underlying ``pip`` installation +will assume ``requirements.txt`` to exist at ``{toxinidir}/requirements.txt``. + using a different default PyPI url ----------------------------------------------- diff -r c3ba84e0cc42ff7e2f59c1d481e8994461fd6e47 -r 10157852d89032acc68c6ffadda02bce713b91b8 doc/example/devenv.txt --- a/doc/example/devenv.txt +++ b/doc/example/devenv.txt @@ -57,7 +57,5 @@ envdir = devenv basepython = python2.7 usedevelop = True - deps = - commands = - pip install -r requirements.txt + deps = -rrequirements.txt diff -r c3ba84e0cc42ff7e2f59c1d481e8994461fd6e47 -r 10157852d89032acc68c6ffadda02bce713b91b8 tests/test_venv.py --- a/tests/test_venv.py +++ b/tests/test_venv.py @@ -133,7 +133,7 @@ venv.install_deps() assert len(l) == 2 + py25calls args = l[-1].args - assert l[-1].cwd == venv.envconfig.envlogdir + assert l[-1].cwd == venv.envconfig.config.toxinidir assert "pip" in str(args[0]) assert args[1] == "install" #arg = "--download-cache=" + str(venv.envconfig.downloadcache) @@ -163,7 +163,7 @@ venv.install_deps() assert len(l) == 2 + py25calls args = l[-1].args - assert l[-1].cwd == venv.envconfig.envlogdir + assert l[-1].cwd == venv.envconfig.config.toxinidir assert "pip" in str(args[0]) assert args[1] == "install" assert "dep1" in args diff -r c3ba84e0cc42ff7e2f59c1d481e8994461fd6e47 -r 10157852d89032acc68c6ffadda02bce713b91b8 tox/_venv.py --- a/tox/_venv.py +++ b/tox/_venv.py @@ -216,7 +216,7 @@ action.setactivity('pip-downgrade', 'pip<1.4') argv = ["easy_install"] + \ self._installopts(indexserver) + ['pip<1.4'] - self._pcall(argv, cwd=self.envconfig.envlogdir, + self._pcall(argv, cwd=self.envconfig.config.toxinidir, action=action) def finish(self): @@ -299,8 +299,8 @@ env = dict(PYTHONIOENCODING='utf_8') if extraenv is not None: env.update(extraenv) - self._pcall(argv, cwd=self.envconfig.envlogdir, - extraenv=env, action=action) + self._pcall(argv, cwd=self.envconfig.config.toxinidir, + extraenv=env, action=action) def _install(self, deps, extraopts=None, action=None): if not deps: https://bitbucket.org/hpk42/tox/commits/2e580ee6feea/ Changeset: 2e580ee6feea User: hpk42 Date: 2013-09-04 15:55:01 Summary: pass PIP_INSECURE as env var in py25 environment Affected #: 1 file diff -r 10157852d89032acc68c6ffadda02bce713b91b8 -r 2e580ee6feea934cc2e683635abded27c0de0be9 tox.ini --- a/tox.ini +++ b/tox.ini @@ -18,6 +18,9 @@ --junitxml={envlogdir}/junit-{envname}.xml \ check_sphinx.py {posargs} +[testenv:py25] +setenv= PIP_INSECURE=1 + [pytest] rsyncdirs=tests tox Repository URL: https://bitbucket.org/hpk42/tox/ -- This is a commit notification from bitbucket.org. You are receiving this because you have the service enabled, addressing the recipient of this email. From commits-noreply at bitbucket.org Wed Sep 4 16:25:38 2013 From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org) Date: Wed, 04 Sep 2013 14:25:38 -0000 Subject: [Pytest-commit] commit/tox: hpk42: Added tag 1.6.1 for changeset 2e580ee6feea Message-ID: <20130904142538.23118.76778@app12.ash-private.bitbucket.org> 1 new commit in tox: https://bitbucket.org/hpk42/tox/commits/ee4b2b06a1ac/ Changeset: ee4b2b06a1ac User: hpk42 Date: 2013-09-04 16:25:33 Summary: Added tag 1.6.1 for changeset 2e580ee6feea Affected #: 1 file diff -r 2e580ee6feea934cc2e683635abded27c0de0be9 -r ee4b2b06a1acf19f9cec24aa2ca580e6c1084ef6 .hgtags --- a/.hgtags +++ b/.hgtags @@ -14,3 +14,4 @@ f5177c612fbadb8552c58693fa7249388c1c1bd3 1.4.3 8fcc1bed3e918b625d85864cc3f4623578852e7e 1.5.0 33e5e5dff406e699893a65ecd5044d3eee35b69b 1.6.0 +2e580ee6feea934cc2e683635abded27c0de0be9 1.6.1 Repository URL: https://bitbucket.org/hpk42/tox/ -- This is a commit notification from bitbucket.org. You are receiving this because you have the service enabled, addressing the recipient of this email. From commits-noreply at bitbucket.org Thu Sep 5 12:10:27 2013 From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org) Date: Thu, 05 Sep 2013 10:10:27 -0000 Subject: [Pytest-commit] commit/pytest: 2 new changesets Message-ID: <20130905101027.12016.5256@app10.ash-private.bitbucket.org> 2 new commits in pytest: https://bitbucket.org/hpk42/pytest/commits/66d67f169635/ Changeset: 66d67f169635 Branch: fix-nose-docs User: mcmtroffaes Date: 2013-08-23 12:59:57 Summary: Fix instructions for nose users. Affected #: 1 file diff -r 933a880e9acc5352fda851e5a9a5422c7e555202 -r 66d67f169635bbdbc82849cfd0e1ceb6d60315a3 doc/en/nose.txt --- a/doc/en/nose.txt +++ b/doc/en/nose.txt @@ -12,6 +12,7 @@ After :ref:`installation` type:: + python setup.py develop # make sure tests can import our package py.test # instead of 'nosetests' and you should be able to run your nose style tests and https://bitbucket.org/hpk42/pytest/commits/5d6cf6594ddd/ Changeset: 5d6cf6594ddd User: hpk42 Date: 2013-09-05 12:10:25 Summary: Merged in mcmtroffaes/pytest/fix-nose-docs (pull request #67) Fix instructions for nose users. Affected #: 1 file diff -r 933a880e9acc5352fda851e5a9a5422c7e555202 -r 5d6cf6594dddc547420fccf9e09306af34b1cecb doc/en/nose.txt --- a/doc/en/nose.txt +++ b/doc/en/nose.txt @@ -12,6 +12,7 @@ After :ref:`installation` type:: + python setup.py develop # make sure tests can import our package py.test # instead of 'nosetests' and you should be able to run your nose style tests and Repository URL: https://bitbucket.org/hpk42/pytest/ -- This is a commit notification from bitbucket.org. You are receiving this because you have the service enabled, addressing the recipient of this email. From commits-noreply at bitbucket.org Thu Sep 5 15:43:25 2013 From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org) Date: Thu, 05 Sep 2013 13:43:25 -0000 Subject: [Pytest-commit] commit/pytest: hpk42: fix issue169: respect --tb=style with setup/teardown errors as well. Message-ID: <20130905134325.20380.92828@app13.ash-private.bitbucket.org> 1 new commit in pytest: https://bitbucket.org/hpk42/pytest/commits/024def3ca24f/ Changeset: 024def3ca24f User: hpk42 Date: 2013-09-05 15:43:19 Summary: fix issue169: respect --tb=style with setup/teardown errors as well. Affected #: 2 files diff -r 5d6cf6594dddc547420fccf9e09306af34b1cecb -r 024def3ca24fabc6fcf2176871d40acd49d4f32e CHANGELOG --- a/CHANGELOG +++ b/CHANGELOG @@ -119,6 +119,8 @@ Changes between 2.3.4 and 2.3.5 ----------------------------------- +- fix issue169: respect --tb=style with setup/teardown errors as well. + - never consider a fixture function for test function collection - allow re-running of test items / helps to fix pytest-reruntests plugin diff -r 5d6cf6594dddc547420fccf9e09306af34b1cecb -r 024def3ca24fabc6fcf2176871d40acd49d4f32e testing/test_runner.py --- a/testing/test_runner.py +++ b/testing/test_runner.py @@ -483,3 +483,14 @@ assert result.ret == 1 assert "UnicodeEncodeError" not in result.stderr.str() + + +def test_failure_in_setup(testdir): + testdir.makepyfile(""" + def setup_module(): + 0/0 + def test_func(): + pass + """) + result = testdir.runpytest("--tb=line") + assert "def setup_module" not in result.stdout.str() Repository URL: https://bitbucket.org/hpk42/pytest/ -- This is a commit notification from bitbucket.org. You are receiving this because you have the service enabled, addressing the recipient of this email. From commits-noreply at bitbucket.org Thu Sep 5 22:22:22 2013 From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org) Date: Thu, 05 Sep 2013 20:22:22 -0000 Subject: [Pytest-commit] commit/pytest: hpk42: show more info if the test fails Message-ID: <20130905202222.20652.79627@app12.ash-private.bitbucket.org> 1 new commit in pytest: https://bitbucket.org/hpk42/pytest/commits/3e3e5213be60/ Changeset: 3e3e5213be60 User: hpk42 Date: 2013-09-05 22:22:14 Summary: show more info if the test fails Affected #: 1 file diff -r 024def3ca24fabc6fcf2176871d40acd49d4f32e -r 3e3e5213be60c60131b8051805946a8f0822fb58 testing/test_runner.py --- a/testing/test_runner.py +++ b/testing/test_runner.py @@ -185,7 +185,10 @@ # so we would end up calling test functions while # sys.exc_info would return the indexerror # from guessing the lastitem - assert sys.exc_info()[0] is None + excinfo = sys.exc_info() + import traceback + assert excinfo[0] is None, \ + traceback.format_exception(*excinfo) def teardown_function(func): raise ValueError(42) """) Repository URL: https://bitbucket.org/hpk42/pytest/ -- This is a commit notification from bitbucket.org. You are receiving this because you have the service enabled, addressing the recipient of this email. From commits-noreply at bitbucket.org Fri Sep 6 09:00:07 2013 From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org) Date: Fri, 06 Sep 2013 07:00:07 -0000 Subject: [Pytest-commit] commit/tox: 2 new changesets Message-ID: <20130906070007.18481.85151@app13.ash-private.bitbucket.org> 2 new commits in tox: https://bitbucket.org/hpk42/tox/commits/9a1898a79154/ Changeset: 9a1898a79154 Branch: glarrain/readme-removed-duplicated-line-1378443596400 User: glarrain Date: 2013-09-06 07:00:04 Summary: README: removed duplicated line Affected #: 1 file diff -r ee4b2b06a1acf19f9cec24aa2ca580e6c1084ef6 -r 9a1898a79154fdde9389d8437aa42eb8a88b1858 README.rst --- a/README.rst +++ b/README.rst @@ -18,8 +18,6 @@ - repository: https://bitbucket.org/hpk42/tox -have fun, - have fun, https://bitbucket.org/hpk42/tox/commits/f7f70c5ce0b4/ Changeset: f7f70c5ce0b4 User: hpk42 Date: 2013-09-06 09:00:05 Summary: Merged in glarrain/tox/glarrain/readme-removed-duplicated-line-1378443596400 (pull request #71) README: removed duplicated line Affected #: 1 file diff -r ee4b2b06a1acf19f9cec24aa2ca580e6c1084ef6 -r f7f70c5ce0b49656688bb78f8b4f5a1a8f489d7f README.rst --- a/README.rst +++ b/README.rst @@ -18,8 +18,6 @@ - repository: https://bitbucket.org/hpk42/tox -have fun, - have fun, Repository URL: https://bitbucket.org/hpk42/tox/ -- This is a commit notification from bitbucket.org. You are receiving this because you have the service enabled, addressing the recipient of this email. From commits-noreply at bitbucket.org Fri Sep 6 12:09:19 2013 From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org) Date: Fri, 06 Sep 2013 10:09:19 -0000 Subject: [Pytest-commit] commit/pytest: 3 new changesets Message-ID: <20130906100919.23135.55131@app07.ash-private.bitbucket.org> 3 new commits in pytest: https://bitbucket.org/hpk42/pytest/commits/902e8493c89d/ Changeset: 902e8493c89d User: hpk42 Date: 2013-09-05 22:32:35 Summary: another way how bash reacts when no argcomplete is there? Affected #: 1 file diff -r 3e3e5213be60c60131b8051805946a8f0822fb58 -r 902e8493c89d57d9ff9121cc3bff7a8c2461160c testing/test_parseopt.py --- a/testing/test_parseopt.py +++ b/testing/test_parseopt.py @@ -266,6 +266,8 @@ if result.ret == 255: # argcomplete not found pytest.skip("argcomplete not available") + elif not result.stdout.str(): + pytest.skip("bash provided no output, argcomplete not available?") else: if py.std.sys.version_info < (2,7): result.stdout.lines = result.stdout.lines[0].split('\x0b') https://bitbucket.org/hpk42/pytest/commits/4fc42aa21181/ Changeset: 4fc42aa21181 User: hpk42 Date: 2013-09-06 10:07:06 Summary: xfail some tests for doctest support if pdbpp is installed Affected #: 2 files diff -r 902e8493c89d57d9ff9121cc3bff7a8c2461160c -r 4fc42aa2118143e97ec1c67a0151191d4566ec3c _pytest/main.py --- a/_pytest/main.py +++ b/_pytest/main.py @@ -373,7 +373,7 @@ # the set of exceptions to interpret as "Skip the whole module" during # collection skip_exceptions = (_pytest.runner.Skipped,) - + class CollectError(Exception): """ an error during collection, contains a custom message. """ diff -r 902e8493c89d57d9ff9121cc3bff7a8c2461160c -r 4fc42aa2118143e97ec1c67a0151191d4566ec3c testing/test_doctest.py --- a/testing/test_doctest.py +++ b/testing/test_doctest.py @@ -1,6 +1,11 @@ from _pytest.doctest import DoctestItem, DoctestModule, DoctestTextfile import py, pytest +import pdb + +xfail_if_pdbpp_installed = pytest.mark.xfail(hasattr(pdb, "__author__"), + reason="doctest/pdbpp problem: https://bitbucket.org/antocuni/pdb/issue/24/doctests-fail-when-pdbpp-is-installed") + class TestDoctests: def test_collect_testtextfile(self, testdir): @@ -154,6 +159,7 @@ reprec = testdir.inline_run(p, "--doctest-modules") reprec.assertoutcome(failed=1) + @xfail_if_pdbpp_installed def test_doctestmodule_external_and_issue116(self, testdir): p = testdir.mkpydir("hello") p.join("__init__.py").write(py.code.Source(""" @@ -193,6 +199,7 @@ "*test_txtfile_failing.txt:2: DocTestFailure" ]) + @xfail_if_pdbpp_installed def test_txtfile_with_fixtures(self, testdir): p = testdir.maketxtfile(""" >>> dir = getfixture('tmpdir') @@ -202,6 +209,8 @@ reprec = testdir.inline_run(p, ) reprec.assertoutcome(passed=1) + + @xfail_if_pdbpp_installed def test_doctestmodule_with_fixtures(self, testdir): p = testdir.makepyfile(""" ''' @@ -213,6 +222,7 @@ reprec = testdir.inline_run(p, "--doctest-modules") reprec.assertoutcome(passed=1) + @xfail_if_pdbpp_installed def test_doctestmodule_three_tests(self, testdir): p = testdir.makepyfile(""" ''' @@ -238,6 +248,7 @@ reprec = testdir.inline_run(p, "--doctest-modules") reprec.assertoutcome(passed=3) + @xfail_if_pdbpp_installed def test_doctestmodule_two_tests_one_fail(self, testdir): p = testdir.makepyfile(""" class MyClass: https://bitbucket.org/hpk42/pytest/commits/1d3ebb313d68/ Changeset: 1d3ebb313d68 User: hpk42 Date: 2013-09-06 11:56:04 Summary: - fix issue181: --pdb now also works on collect errors. This was implemented by a slight internal refactoring and the introduction of a new hook ``pytest_exception_interact`` hook. - fix issue341: introduce new experimental hook for IDEs/terminals to intercept debugging: ``pytest_exception_interact(node, call, report)``. Affected #: 12 files diff -r 4fc42aa2118143e97ec1c67a0151191d4566ec3c -r 1d3ebb313d68d4bcb3f1a2e575febf196689731b CHANGELOG --- a/CHANGELOG +++ b/CHANGELOG @@ -1,6 +1,14 @@ Changes between 2.3.5 and 2.4.DEV ----------------------------------- +- fix issue181: --pdb now also works on collect errors (and + on internal errors) . This was implemented by a slight internal + refactoring and the introduction of a new hook + ``pytest_exception_interact`` hook (see below). + +- fix issue341: introduce new experimental hook for IDEs/terminals to + intercept debugging: ``pytest_exception_interact(node, call, report)``. + - PR27: correctly handle nose.SkipTest during collection. Thanks Antonio Cuni, Ronny Pfannschmidt. diff -r 4fc42aa2118143e97ec1c67a0151191d4566ec3c -r 1d3ebb313d68d4bcb3f1a2e575febf196689731b _pytest/__init__.py --- a/_pytest/__init__.py +++ b/_pytest/__init__.py @@ -1,2 +1,2 @@ # -__version__ = '2.4.0.dev11' +__version__ = '2.4.0.dev12' diff -r 4fc42aa2118143e97ec1c67a0151191d4566ec3c -r 1d3ebb313d68d4bcb3f1a2e575febf196689731b _pytest/core.py --- a/_pytest/core.py +++ b/_pytest/core.py @@ -298,7 +298,8 @@ showlocals=getattr(option, 'showlocals', False), style=style, ) - res = self.hook.pytest_internalerror(excrepr=excrepr) + res = self.hook.pytest_internalerror(excrepr=excrepr, + excinfo=excinfo) if not py.builtin.any(res): for line in str(excrepr).split("\n"): sys.stderr.write("INTERNALERROR> %s\n" %line) diff -r 4fc42aa2118143e97ec1c67a0151191d4566ec3c -r 1d3ebb313d68d4bcb3f1a2e575febf196689731b _pytest/hookspec.py --- a/_pytest/hookspec.py +++ b/_pytest/hookspec.py @@ -241,8 +241,18 @@ def pytest_plugin_unregistered(plugin): """ a py lib plugin got unregistered. """ -def pytest_internalerror(excrepr): +def pytest_internalerror(excrepr, excinfo): """ called for internal errors. """ def pytest_keyboard_interrupt(excinfo): """ called for keyboard interrupt. """ + +def pytest_exception_interact(node, call, report): + """ (experimental, new in 2.4) called when + an exception was raised which can potentially be + interactively handled. + + This hook is only called if an exception was raised + that is not an internal exception like "skip.Exception". + """ + diff -r 4fc42aa2118143e97ec1c67a0151191d4566ec3c -r 1d3ebb313d68d4bcb3f1a2e575febf196689731b _pytest/main.py --- a/_pytest/main.py +++ b/_pytest/main.py @@ -10,7 +10,7 @@ from UserDict import DictMixin as MappingMixin from _pytest.mark import MarkInfo -import _pytest.runner +from _pytest.runner import collect_one_node, Skipped tracebackcutdir = py.path.local(_pytest.__file__).dirpath() @@ -372,7 +372,7 @@ # the set of exceptions to interpret as "Skip the whole module" during # collection - skip_exceptions = (_pytest.runner.Skipped,) + skip_exceptions = (Skipped,) class CollectError(Exception): """ an error during collection, contains a custom message. """ @@ -512,8 +512,7 @@ parts = self._parsearg(arg) self._initialparts.append(parts) self._initialpaths.add(parts[0]) - self.ihook.pytest_collectstart(collector=self) - rep = self.ihook.pytest_make_collect_report(collector=self) + rep = collect_one_node(self) self.ihook.pytest_collectreport(report=rep) self.trace.root.indent -= 1 if self._notfound: @@ -642,8 +641,7 @@ resultnodes.append(node) continue assert isinstance(node, pytest.Collector) - node.ihook.pytest_collectstart(collector=node) - rep = node.ihook.pytest_make_collect_report(collector=node) + rep = collect_one_node(node) if rep.passed: has_matched = False for x in rep.result: @@ -664,8 +662,7 @@ yield node else: assert isinstance(node, pytest.Collector) - node.ihook.pytest_collectstart(collector=node) - rep = node.ihook.pytest_make_collect_report(collector=node) + rep = collect_one_node(node) if rep.passed: for subnode in rep.result: for x in self.genitems(subnode): diff -r 4fc42aa2118143e97ec1c67a0151191d4566ec3c -r 1d3ebb313d68d4bcb3f1a2e575febf196689731b _pytest/pdb.py --- a/_pytest/pdb.py +++ b/_pytest/pdb.py @@ -52,31 +52,26 @@ pytestPDB.item = None class PdbInvoke: - @pytest.mark.tryfirst - def pytest_runtest_makereport(self, item, call, __multicall__): - rep = __multicall__.execute() - if not call.excinfo or \ - call.excinfo.errisinstance(pytest.skip.Exception) or \ - call.excinfo.errisinstance(py.std.bdb.BdbQuit): - return rep - if hasattr(rep, "wasxfail"): - return rep - return _enter_pdb(item, call.excinfo, rep) + def pytest_exception_interact(self, node, call, report): + return _enter_pdb(node, call.excinfo, report) + def pytest_internalerror(self, excrepr, excinfo): + for line in str(excrepr).split("\n"): + sys.stderr.write("INTERNALERROR> %s\n" %line) + sys.stderr.flush() + tb = _postmortem_traceback(excinfo) + post_mortem(tb) - -def _enter_pdb(item, excinfo, rep): - # we assume that the above execute() suspended capturing +def _enter_pdb(node, excinfo, rep): # XXX we re-use the TerminalReporter's terminalwriter # because this seems to avoid some encoding related troubles # for not completely clear reasons. - tw = item.config.pluginmanager.getplugin("terminalreporter")._tw + tw = node.config.pluginmanager.getplugin("terminalreporter")._tw tw.line() tw.sep(">", "traceback") rep.toterminal(tw) tw.sep(">", "entering PDB") - tb = _postmortem_traceback(excinfo) post_mortem(tb) rep._pdbshown = True diff -r 4fc42aa2118143e97ec1c67a0151191d4566ec3c -r 1d3ebb313d68d4bcb3f1a2e575febf196689731b _pytest/runner.py --- a/_pytest/runner.py +++ b/_pytest/runner.py @@ -108,8 +108,16 @@ report = hook.pytest_runtest_makereport(item=item, call=call) if log: hook.pytest_runtest_logreport(report=report) + if check_interactive_exception(call, report): + hook.pytest_exception_interact(node=item, call=call, report=report) return report +def check_interactive_exception(call, report): + return call.excinfo and not ( + hasattr(report, "wasxfail") or + call.excinfo.errisinstance(skip.Exception) or + call.excinfo.errisinstance(py.std.bdb.BdbQuit)) + def call_runtest_hook(item, when, **kwds): hookname = "pytest_runtest_" + when ihook = getattr(item.ihook, hookname) @@ -268,8 +276,11 @@ if not hasattr(errorinfo, "toterminal"): errorinfo = CollectErrorRepr(errorinfo) longrepr = errorinfo - return CollectReport(collector.nodeid, outcome, longrepr, + rep = CollectReport(collector.nodeid, outcome, longrepr, getattr(call, 'result', None)) + rep.call = call # see collect_one_node + return rep + class CollectReport(BaseReport): def __init__(self, nodeid, outcome, longrepr, result, sections=(), **extra): @@ -364,6 +375,16 @@ col._prepare_exc = sys.exc_info() raise +def collect_one_node(collector): + ihook = collector.ihook + ihook.pytest_collectstart(collector=collector) + rep = ihook.pytest_make_collect_report(collector=collector) + call = rep.__dict__.pop("call", None) + if call and check_interactive_exception(call, rep): + ihook.pytest_exception_interact(node=collector, call=call, report=rep) + return rep + + # ============================================================= # Test OutcomeExceptions and helpers for creating them. diff -r 4fc42aa2118143e97ec1c67a0151191d4566ec3c -r 1d3ebb313d68d4bcb3f1a2e575febf196689731b doc/en/plugins.txt --- a/doc/en/plugins.txt +++ b/doc/en/plugins.txt @@ -359,6 +359,17 @@ .. autofunction:: pytest_runtest_logreport + +Debugging/Interaction hooks +-------------------------------------- + +There are few hooks which can be used for special +reporting or interaction with exceptions: + +.. autofunction:: pytest_internalerror +.. autofunction:: pytest_keyboard_interrupt +.. autofunction:: pytest_exception_interact + Reference of objects involved in hooks =========================================================== diff -r 4fc42aa2118143e97ec1c67a0151191d4566ec3c -r 1d3ebb313d68d4bcb3f1a2e575febf196689731b setup.py --- a/setup.py +++ b/setup.py @@ -11,7 +11,7 @@ name='pytest', description='py.test: simple powerful testing with Python', long_description = long_description, - version='2.4.0.dev11', + version='2.4.0.dev12', url='http://pytest.org', license='MIT license', platforms=['unix', 'linux', 'osx', 'cygwin', 'win32'], diff -r 4fc42aa2118143e97ec1c67a0151191d4566ec3c -r 1d3ebb313d68d4bcb3f1a2e575febf196689731b testing/python/integration.py --- a/testing/python/integration.py +++ b/testing/python/integration.py @@ -1,4 +1,5 @@ import pytest, py, sys +from _pytest import runner class TestOEJSKITSpecials: def test_funcarg_non_pycollectobj(self, testdir): # rough jstests usage @@ -18,7 +19,7 @@ pass """) # this hook finds funcarg factories - rep = modcol.ihook.pytest_make_collect_report(collector=modcol) + rep = runner.collect_one_node(collector=modcol) clscol = rep.result[0] clscol.obj = lambda arg1: None clscol.funcargs = {} @@ -46,7 +47,7 @@ pass """) # this hook finds funcarg factories - rep = modcol.ihook.pytest_make_collect_report(collector=modcol) + rep = runner.collect_one_node(modcol) clscol = rep.result[0] clscol.obj = lambda: None clscol.funcargs = {} diff -r 4fc42aa2118143e97ec1c67a0151191d4566ec3c -r 1d3ebb313d68d4bcb3f1a2e575febf196689731b testing/test_pdb.py --- a/testing/test_pdb.py +++ b/testing/test_pdb.py @@ -1,6 +1,8 @@ import py, pytest import sys +from test_doctest import xfail_if_pdbpp_installed + class TestPDB: def pytest_funcarg__pdblist(self, request): monkeypatch = request.getfuncargvalue("monkeypatch") @@ -85,6 +87,32 @@ if child.isalive(): child.wait() + def test_pdb_interaction_on_collection_issue181(self, testdir): + p1 = testdir.makepyfile(""" + import pytest + xxx + """) + child = testdir.spawn_pytest("--pdb %s" % p1) + #child.expect(".*import pytest.*") + child.expect("(Pdb)") + child.sendeof() + child.expect("1 error") + if child.isalive(): + child.wait() + + def test_pdb_interaction_on_internal_error(self, testdir): + testdir.makeconftest(""" + def pytest_runtest_protocol(): + 0/0 + """) + p1 = testdir.makepyfile("def test_func(): pass") + child = testdir.spawn_pytest("--pdb %s" % p1) + #child.expect(".*import pytest.*") + child.expect("(Pdb)") + child.sendeof() + if child.isalive(): + child.wait() + def test_pdb_interaction_capturing_simple(self, testdir): p1 = testdir.makepyfile(""" import pytest @@ -122,6 +150,7 @@ if child.isalive(): child.wait() + @xfail_if_pdbpp_installed def test_pdb_interaction_doctest(self, testdir): p1 = testdir.makepyfile(""" import pytest diff -r 4fc42aa2118143e97ec1c67a0151191d4566ec3c -r 1d3ebb313d68d4bcb3f1a2e575febf196689731b testing/test_runner.py --- a/testing/test_runner.py +++ b/testing/test_runner.py @@ -1,5 +1,5 @@ import pytest, py, sys, os -from _pytest import runner +from _pytest import runner, main from py._code.code import ReprExceptionInfo class TestSetupState: @@ -298,7 +298,7 @@ class TestClass: pass """) - rep = runner.pytest_make_collect_report(col) + rep = runner.collect_one_node(col) assert not rep.failed assert not rep.skipped assert rep.passed @@ -318,7 +318,7 @@ def test_func(): pass """) - rep = runner.pytest_make_collect_report(col) + rep = main.collect_one_node(col) assert not rep.failed assert not rep.passed assert rep.skipped Repository URL: https://bitbucket.org/hpk42/pytest/ -- This is a commit notification from bitbucket.org. You are receiving this because you have the service enabled, addressing the recipient of this email. From commits-noreply at bitbucket.org Fri Sep 6 12:16:43 2013 From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org) Date: Fri, 06 Sep 2013 10:16:43 -0000 Subject: [Pytest-commit] commit/pytest: hpk42: fix issue352: fix reference to py.path.local description in tmpdir documentation Message-ID: <20130906101643.21072.41400@app07.ash-private.bitbucket.org> 1 new commit in pytest: https://bitbucket.org/hpk42/pytest/commits/5c8cf890c3b7/ Changeset: 5c8cf890c3b7 User: hpk42 Date: 2013-09-06 12:16:05 Summary: fix issue352: fix reference to py.path.local description in tmpdir documentation (generally to be found at http://pytest.org/latest/tmpdir.html ) Affected #: 1 file diff -r 1d3ebb313d68d4bcb3f1a2e575febf196689731b -r 5c8cf890c3b7fb1b75bc89edcec0515b2ac21aa0 doc/en/tmpdir.txt --- a/doc/en/tmpdir.txt +++ b/doc/en/tmpdir.txt @@ -68,6 +68,6 @@ configure a basetemp directory for the sub processes such that all temporary data lands below a single per-test run basetemp directory. -.. _`py.path.local`: http://py.rtfd.org/path.html +.. _`py.path.local`: http://py.rtfd.org/en/latest/path.html Repository URL: https://bitbucket.org/hpk42/pytest/ -- This is a commit notification from bitbucket.org. You are receiving this because you have the service enabled, addressing the recipient of this email. From commits-noreply at bitbucket.org Fri Sep 6 12:33:06 2013 From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org) Date: Fri, 06 Sep 2013 10:33:06 -0000 Subject: [Pytest-commit] commit/pytest: hpk42: fix 1 new commit in pytest: https://bitbucket.org/hpk42/pytest/commits/1200d39c921f/ Changeset: 1200d39c921f User: hpk42 Date: 2013-09-06 12:32:55 Summary: fix 1 new commit in pytest: https://bitbucket.org/hpk42/pytest/commits/2596c9358665/ Changeset: 2596c9358665 User: hpk42 Date: 2013-09-06 12:48:54 Summary: add nose.SkipTest for python2.6 -- which apparently is a subclass from python2.7 on. addresses issue236 Affected #: 1 file diff -r 1200d39c921f8af41b3c62be2a017bcf7f165b1c -r 2596c935866532b97023e739dd978dc15796743c _pytest/nose.py --- a/_pytest/nose.py +++ b/_pytest/nose.py @@ -5,13 +5,13 @@ import sys from _pytest import unittest - def pytest_runtest_makereport(__multicall__, item, call): SkipTest = getattr(sys.modules.get('nose', None), 'SkipTest', None) if SkipTest: if call.excinfo and call.excinfo.errisinstance(SkipTest): # let's substitute the excinfo with a py.test.skip one - call2 = call.__class__(lambda: py.test.skip(str(call.excinfo.value)), call.when) + call2 = call.__class__(lambda: + pytest.skip(str(call.excinfo.value)), call.when) call.excinfo = call2.excinfo @@ -40,10 +40,12 @@ # del item.parent._nosegensetup def pytest_make_collect_report(collector): - if sys.modules.get("unittest"): - SkipTest = getattr(py.std.unittest, "SkipTest", None) - if SkipTest is not None: - collector.skip_exceptions += (SkipTest,) + SkipTest = getattr(sys.modules.get('unittest', None), 'SkipTest', None) + if SkipTest is not None: + collector.skip_exceptions += (SkipTest,) + SkipTest = getattr(sys.modules.get('nose', None), 'SkipTest', None) + if SkipTest is not None: + collector.skip_exceptions += (SkipTest,) if isinstance(collector, pytest.Generator): call_optional(collector.obj, 'setup') Repository URL: https://bitbucket.org/hpk42/pytest/ -- This is a commit notification from bitbucket.org. You are receiving this because you have the service enabled, addressing the recipient of this email. From commits-noreply at bitbucket.org Fri Sep 6 15:29:08 2013 From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org) Date: Fri, 06 Sep 2013 13:29:08 -0000 Subject: [Pytest-commit] commit/pytest: hpk42: make "import pdb ; pdb.set_trace()" work natively wrt capturing (no "-s" needed Message-ID: <20130906132908.11526.25121@app06.ash-private.bitbucket.org> 1 new commit in pytest: https://bitbucket.org/hpk42/pytest/commits/8ccfe395100d/ Changeset: 8ccfe395100d User: hpk42 Date: 2013-09-06 15:29:00 Summary: make "import pdb ; pdb.set_trace()" work natively wrt capturing (no "-s" needed anymore), turning ``pytest.set_trace()`` into a mere shortcut. Affected #: 3 files diff -r 2596c935866532b97023e739dd978dc15796743c -r 8ccfe395100dfe6be00805a25b2516aec702949a CHANGELOG --- a/CHANGELOG +++ b/CHANGELOG @@ -1,6 +1,9 @@ Changes between 2.3.5 and 2.4.DEV ----------------------------------- +- make "import pdb ; pdb.set_trace()" work natively wrt capturing (no "-s" needed + anymore), making ``pytest.set_trace()`` a mere shortcut. + - fix issue181: --pdb now also works on collect errors (and on internal errors) . This was implemented by a slight internal refactoring and the introduction of a new hook diff -r 2596c935866532b97023e739dd978dc15796743c -r 8ccfe395100dfe6be00805a25b2516aec702949a _pytest/pdb.py --- a/_pytest/pdb.py +++ b/_pytest/pdb.py @@ -16,6 +16,12 @@ if config.getvalue("usepdb"): config.pluginmanager.register(PdbInvoke(), 'pdbinvoke') + old_trace = py.std.pdb.set_trace + def fin(): + py.std.pdb.set_trace = old_trace + py.std.pdb.set_trace = pytest.set_trace + config._cleanup.append(fin) + class pytestPDB: """ Pseudo PDB that defers to the real pdb. """ item = None diff -r 2596c935866532b97023e739dd978dc15796743c -r 8ccfe395100dfe6be00805a25b2516aec702949a testing/test_pdb.py --- a/testing/test_pdb.py +++ b/testing/test_pdb.py @@ -134,6 +134,22 @@ if child.isalive(): child.wait() + def test_pdb_set_trace_interception(self, testdir): + p1 = testdir.makepyfile(""" + import pdb + def test_1(): + pdb.set_trace() + """) + child = testdir.spawn_pytest(str(p1)) + child.expect("test_1") + child.expect("(Pdb)") + child.sendeof() + rest = child.read() + assert "1 failed" in rest + assert "reading from stdin while output" not in rest + if child.isalive(): + child.wait() + def test_pdb_and_capsys(self, testdir): p1 = testdir.makepyfile(""" import pytest Repository URL: https://bitbucket.org/hpk42/pytest/ -- This is a commit notification from bitbucket.org. You are receiving this because you have the service enabled, addressing the recipient of this email. From commits-noreply at bitbucket.org Sat Sep 7 09:16:38 2013 From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org) Date: Sat, 07 Sep 2013 07:16:38 -0000 Subject: [Pytest-commit] commit/pytest: 3 new changesets Message-ID: <20130907071638.23726.5446@app10.ash-private.bitbucket.org> 3 new commits in pytest: https://bitbucket.org/hpk42/pytest/commits/847d2afe11ef/ Changeset: 847d2afe11ef Branch: parametrize-fails-when-values-are User: bubenkoff Date: 2013-09-06 22:42:54 Summary: initial Affected #: 0 files https://bitbucket.org/hpk42/pytest/commits/b3da592fbc92/ Changeset: b3da592fbc92 Branch: parametrize-fails-when-values-are User: bubenkoff Date: 2013-09-07 02:30:09 Summary: parametrize fails when values are unhashable - tests Affected #: 2 files diff -r 847d2afe11efe401500985ac99994009095404c2 -r b3da592fbc92a9fd0afb7019320f13f5dc1823f1 testing/python/collect.py --- a/testing/python/collect.py +++ b/testing/python/collect.py @@ -329,6 +329,24 @@ reprec = testdir.inline_run() reprec.assertoutcome(passed=1) + def test_parametrize_with_non_hashable_values(self, testdir): + """Test parametrization with non-hashable values.""" + testdir.makepyfile(""" + archival_mapping = { + '1.0': {'tag': '1.0'}, + '1.2.2a1': {'tag': 'release-1.2.2a1'}, + } + + import pytest + @pytest.mark.parametrize('key value'.split(), + archival_mapping.items()) + def test_archival_to_version(key, value): + assert key in archival_mapping + assert value == archival_mapping[key] + """) + rec = testdir.inline_run() + rec.assertoutcome(passed=2) + def test_function_equality_with_callspec(self, testdir, tmpdir): items = testdir.getitems(""" import pytest diff -r 847d2afe11efe401500985ac99994009095404c2 -r b3da592fbc92a9fd0afb7019320f13f5dc1823f1 testing/test_parametrize_with_fixture.py --- a/testing/test_parametrize_with_fixture.py +++ /dev/null @@ -1,1 +0,0 @@ -import pytest https://bitbucket.org/hpk42/pytest/commits/e766eae72f12/ Changeset: e766eae72f12 User: hpk42 Date: 2013-09-07 09:16:35 Summary: Merged in paylogic/pytest/parametrize-fails-when-values-are (pull request #68) parametrize fails when values are unhashable - add test Affected #: 2 files diff -r 8ccfe395100dfe6be00805a25b2516aec702949a -r e766eae72f1254187119636fb4613b03fda00d62 testing/python/collect.py --- a/testing/python/collect.py +++ b/testing/python/collect.py @@ -329,6 +329,24 @@ reprec = testdir.inline_run() reprec.assertoutcome(passed=1) + def test_parametrize_with_non_hashable_values(self, testdir): + """Test parametrization with non-hashable values.""" + testdir.makepyfile(""" + archival_mapping = { + '1.0': {'tag': '1.0'}, + '1.2.2a1': {'tag': 'release-1.2.2a1'}, + } + + import pytest + @pytest.mark.parametrize('key value'.split(), + archival_mapping.items()) + def test_archival_to_version(key, value): + assert key in archival_mapping + assert value == archival_mapping[key] + """) + rec = testdir.inline_run() + rec.assertoutcome(passed=2) + def test_function_equality_with_callspec(self, testdir, tmpdir): items = testdir.getitems(""" import pytest diff -r 8ccfe395100dfe6be00805a25b2516aec702949a -r e766eae72f1254187119636fb4613b03fda00d62 testing/test_parametrize_with_fixture.py --- a/testing/test_parametrize_with_fixture.py +++ /dev/null @@ -1,1 +0,0 @@ -import pytest Repository URL: https://bitbucket.org/hpk42/pytest/ -- This is a commit notification from bitbucket.org. You are receiving this because you have the service enabled, addressing the recipient of this email. From commits-noreply at bitbucket.org Sat Sep 7 09:24:52 2013 From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org) Date: Sat, 07 Sep 2013 07:24:52 -0000 Subject: [Pytest-commit] commit/pytest: 3 new changesets Message-ID: <20130907072452.4886.40746@app05.ash-private.bitbucket.org> 3 new commits in pytest: https://bitbucket.org/hpk42/pytest/commits/596706f44bf5/ Changeset: 596706f44bf5 Branch: fix-nose-docs User: hpk42 Date: 2013-09-07 09:24:15 Summary: close Affected #: 0 files https://bitbucket.org/hpk42/pytest/commits/4230f7e6f640/ Changeset: 4230f7e6f640 Branch: fix-broken-tests User: hpk42 Date: 2013-09-07 09:24:24 Summary: close Affected #: 0 files https://bitbucket.org/hpk42/pytest/commits/a76b97c04b2b/ Changeset: a76b97c04b2b Branch: parametrize-fails-when-values-are User: hpk42 Date: 2013-09-07 09:24:32 Summary: close Affected #: 0 files Repository URL: https://bitbucket.org/hpk42/pytest/ -- This is a commit notification from bitbucket.org. You are receiving this because you have the service enabled, addressing the recipient of this email. From notifications at travis-ci.org Sat Sep 7 11:27:16 2013 From: notifications at travis-ci.org (Travis CI) Date: Sat, 07 Sep 2013 09:27:16 +0000 Subject: [Pytest-commit] [Still Failing] hpk42/pytest#37 (master - 3c3f11e) Message-ID: <522af17414050_220f7e267165@5540ff04-a724-46f9-87ff-718c28e5dc08.mail> Build Update for hpk42/pytest ------------------------------------- Build: #37 Status: Still Failing Duration: 36 seconds Commit: 3c3f11e (master) Author: holger krekel Message: Merged in paylogic/pytest/parametrize-fails-when-values-are (pull request #68) parametrize fails when values are unhashable - add test View the changeset: https://github.com/hpk42/pytest/compare/9203b85f0d18...3c3f11e40cd5 View the full build log and details: https://travis-ci.org/hpk42/pytest/builds/11090036 -- You can configure recipients for build notifications in your .travis.yml file. See http://about.travis-ci.org/docs/user/build-configuration -------------- next part -------------- An HTML attachment was scrubbed... URL: From commits-noreply at bitbucket.org Mon Sep 9 08:22:32 2013 From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org) Date: Mon, 09 Sep 2013 06:22:32 -0000 Subject: [Pytest-commit] commit/pytest: 2 new changesets Message-ID: <20130909062232.21569.68781@app09.ash-private.bitbucket.org> 2 new commits in pytest: https://bitbucket.org/hpk42/pytest/commits/add97f165fc3/ Changeset: add97f165fc3 User: bubenkoff Date: 2013-09-07 20:23:44 Summary: only force tox to upgrade Affected #: 1 file diff -r e766eae72f1254187119636fb4613b03fda00d62 -r add97f165fc3a6ca2886fadc73ac310da65e08b7 .travis.yml --- a/.travis.yml +++ b/.travis.yml @@ -1,6 +1,6 @@ language: python # command to install dependencies -install: "pip install 'virtualenv<1.10' -e . detox" +install: "pip install -U detox" # # command to run tests script: detox --recreate notifications: https://bitbucket.org/hpk42/pytest/commits/90e7a7a4c03e/ Changeset: 90e7a7a4c03e User: hpk42 Date: 2013-09-09 08:12:07 Summary: Merge pull request #6 from bubenkoff/fix-failing-travis only force tox to upgrade Affected #: 1 file diff -r e766eae72f1254187119636fb4613b03fda00d62 -r 90e7a7a4c03e0339d6228fd40403ea7824e250f1 .travis.yml --- a/.travis.yml +++ b/.travis.yml @@ -1,6 +1,6 @@ language: python # command to install dependencies -install: "pip install 'virtualenv<1.10' -e . detox" +install: "pip install -U detox" # # command to run tests script: detox --recreate notifications: Repository URL: https://bitbucket.org/hpk42/pytest/ -- This is a commit notification from bitbucket.org. You are receiving this because you have the service enabled, addressing the recipient of this email. From notifications at travis-ci.org Mon Sep 9 08:16:41 2013 From: notifications at travis-ci.org (Travis CI) Date: Mon, 09 Sep 2013 06:16:41 +0000 Subject: [Pytest-commit] [Fixed] hpk42/pytest#40 (master - a7123a2) Message-ID: <522d67c9d6d8_2283413447c@9c8fdc7f-e86b-49dc-aab4-58502381537c.mail> Build Update for hpk42/pytest ------------------------------------- Build: #40 Status: Fixed Duration: 4 minutes and 5 seconds Commit: a7123a2 (master) Author: holger krekel Message: Merge pull request #6 from bubenkoff/fix-failing-travis only force tox to upgrade View the changeset: https://github.com/hpk42/pytest/compare/3c3f11e40cd5...a7123a2f948d View the full build log and details: https://travis-ci.org/hpk42/pytest/builds/11140509 -- You can configure recipients for build notifications in your .travis.yml file. See http://about.travis-ci.org/docs/user/build-configuration -------------- next part -------------- An HTML attachment was scrubbed... URL: From commits-noreply at bitbucket.org Mon Sep 9 09:57:54 2013 From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org) Date: Mon, 09 Sep 2013 07:57:54 -0000 Subject: [Pytest-commit] commit/pytest: hpk42: fix issue333: fix a case of bad unittest/pytest hook interaction. Message-ID: <20130909075754.9905.80945@app02.ash-private.bitbucket.org> 1 new commit in pytest: https://bitbucket.org/hpk42/pytest/commits/a2b5a4f7d445/ Changeset: a2b5a4f7d445 User: hpk42 Date: 2013-09-09 09:56:53 Summary: fix issue333: fix a case of bad unittest/pytest hook interaction. Affected #: 3 files diff -r 90e7a7a4c03e0339d6228fd40403ea7824e250f1 -r a2b5a4f7d4451f5aa1d270ec928520ee081ed003 CHANGELOG --- a/CHANGELOG +++ b/CHANGELOG @@ -4,6 +4,8 @@ - make "import pdb ; pdb.set_trace()" work natively wrt capturing (no "-s" needed anymore), making ``pytest.set_trace()`` a mere shortcut. +- fix issue333: fix a case of bad unittest/pytest hook interaction. + - fix issue181: --pdb now also works on collect errors (and on internal errors) . This was implemented by a slight internal refactoring and the introduction of a new hook diff -r 90e7a7a4c03e0339d6228fd40403ea7824e250f1 -r a2b5a4f7d4451f5aa1d270ec928520ee081ed003 _pytest/unittest.py --- a/_pytest/unittest.py +++ b/_pytest/unittest.py @@ -150,7 +150,10 @@ if isinstance(item, TestCaseFunction): if item._excinfo: call.excinfo = item._excinfo.pop(0) - del call.result + try: + del call.result + except AttributeError: + pass # twisted trial support def pytest_runtest_protocol(item, __multicall__): diff -r 90e7a7a4c03e0339d6228fd40403ea7824e250f1 -r a2b5a4f7d4451f5aa1d270ec928520ee081ed003 testing/test_unittest.py --- a/testing/test_unittest.py +++ b/testing/test_unittest.py @@ -654,3 +654,21 @@ reprec = testdir.inline_run(testpath) reprec.assertoutcome(passed=1, failed=1) + +def test_issue333_result_clearing(testdir): + testdir.makeconftest(""" + def pytest_runtest_call(__multicall__, item): + __multicall__.execute() + assert 0 + """) + testdir.makepyfile(""" + import unittest + class TestIt(unittest.TestCase): + def test_func(self): + 0/0 + """) + + reprec = testdir.inline_run() + reprec.assertoutcome(failed=1) + + Repository URL: https://bitbucket.org/hpk42/pytest/ -- This is a commit notification from bitbucket.org. You are receiving this because you have the service enabled, addressing the recipient of this email. From issues-reply at bitbucket.org Mon Sep 9 18:01:23 2013 From: issues-reply at bitbucket.org (Barry Warsaw) Date: Mon, 09 Sep 2013 16:01:23 -0000 Subject: [Pytest-commit] Issue #122: sitepackages=True inhibits console_scripts entry points (hpk42/tox) Message-ID: <20130909160123.6938.28065@app01.ash-private.bitbucket.org> New issue 122: sitepackages=True inhibits console_scripts entry points https://bitbucket.org/hpk42/tox/issue/122/sitepackages-true-inhibits-console_scripts Barry Warsaw: In `bzr branch lp:ubuntu-system-image/client` I have two console_scripts in the setup.py's entry_points. One creates system-image-cli and the other creates system-image-dbus. My tox.ini has [testenv] commands = python setup.py nosetests sitepackages=True This is because at build-time (both for local testing and during Ubuntu package build) we do not want any dependencies downloaded from PyPI. Everything must come from an installed package. However, if the Ubuntu packages system-image-cli and system-image-dbus are installed (which provide the /usr/bin scripts of the same name), then `tox --notest -r` will not install the console_scripts into the tox environment, and this breaks the tests. This problem may be related to https://bitbucket.org/hpk42/tox/issue/80/package-cant-be-imported-when-sitepackages but I'm not sure. From commits-noreply at bitbucket.org Tue Sep 10 08:25:33 2013 From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org) Date: Tue, 10 Sep 2013 06:25:33 -0000 Subject: [Pytest-commit] commit/pytest: Anthon van der Neut: argcomplete_win: skip testing of argcomplete on windows Message-ID: <20130910062533.8399.25046@app11.ash-private.bitbucket.org> 1 new commit in pytest: https://bitbucket.org/hpk42/pytest/commits/5239f31ebf82/ Changeset: 5239f31ebf82 User: Anthon van der Neut Date: 2013-09-09 12:41:29 Summary: argcomplete_win: skip testing of argcomplete on windows Affected #: 1 file diff -r a2b5a4f7d4451f5aa1d270ec928520ee081ed003 -r 5239f31ebf82eee712fc9f54f414e34afdfc1ddf testing/test_argcomplete.py --- a/testing/test_argcomplete.py +++ b/testing/test_argcomplete.py @@ -72,6 +72,7 @@ # the following barfs with a syntax error on py2.5 # @pytest.mark.skipif("sys.version_info < (2,6)") class TestArgComplete: + @pytest.mark.skipif("sys.platform == 'win32'") @pytest.mark.skipif("sys.version_info < (2,6)") def test_compare_with_compgen(self): from _pytest._argcomplete import FastFilesCompleter @@ -80,6 +81,7 @@ for x in '/ /d /data qqq'.split(): assert equal_with_bash(x, ffc, fc, out=py.std.sys.stdout) + @pytest.mark.skipif("sys.platform == 'win32'") @pytest.mark.skipif("sys.version_info < (2,6)") def test_remove_dir_prefix(self): """this is not compatible with compgen but it is with bash itself: Repository URL: https://bitbucket.org/hpk42/pytest/ -- This is a commit notification from bitbucket.org. You are receiving this because you have the service enabled, addressing the recipient of this email. From issues-reply at bitbucket.org Wed Sep 11 21:04:45 2013 From: issues-reply at bitbucket.org (Erik Pragt) Date: Wed, 11 Sep 2013 19:04:45 -0000 Subject: [Pytest-commit] Issue #123: No such file or directory: '.tox/py33/bin/pip' (hpk42/tox) Message-ID: <20130911190445.28179.71591@app11.ash-private.bitbucket.org> New issue 123: No such file or directory: '.tox/py33/bin/pip' https://bitbucket.org/hpk42/tox/issue/123/no-such-file-or-directory-tox-py33-bin-pip Erik Pragt: Hi, I'm trying to use tox, but when I run it, I get the following error (full stacktrace below): FileNotFoundError: [Errno 2] No such file or directory: '.tox/py33/bin/pip' I'm running Tox on a project created with Cookiecutter, in a virtualenv, on my Mac. This is my tox.ini: [tox] envlist = py33, style [testenv] setenv = PYTHONPATH = {toxinidir}:{toxinidir}/HuetonApi deps = -r{toxinidir}/requirements.txt pytest commands = python setup.py test [testenv:style] deps = -r{toxinidir}/requirements.txt flake8 commands = python setup.py flake8 And I have no idea how to fix this :-) thanks for the help! **Full stacktrace:** py33 installdeps: -r/Users/erikp/User Files/Projects/python/hueton/HuetonApi/requirements.txt, pytest Traceback (most recent call last): File "/Users/erikp/Envs/hueton/bin/tox", line 9, in load_entry_point('tox==1.6.1', 'console_scripts', 'tox')() File "/Users/erikp/Envs/hueton/lib/python3.3/site-packages/tox/_cmdline.py", line 26, in main retcode = Session(config).runcommand() File "/Users/erikp/Envs/hueton/lib/python3.3/site-packages/tox/_cmdline.py", line 301, in runcommand return self.subcommand_test() File "/Users/erikp/Envs/hueton/lib/python3.3/site-packages/tox/_cmdline.py", line 433, in subcommand_test if self.setupenv(venv): File "/Users/erikp/Envs/hueton/lib/python3.3/site-packages/tox/_cmdline.py", line 358, in setupenv status = venv.update(action=action) File "/Users/erikp/Envs/hueton/lib/python3.3/site-packages/tox/_venv.py", line 142, in update self.install_deps(action) File "/Users/erikp/Envs/hueton/lib/python3.3/site-packages/tox/_venv.py", line 272, in install_deps self._install(deps, action=action) File "/Users/erikp/Envs/hueton/lib/python3.3/site-packages/tox/_venv.py", line 334, in _install extraenv=extraenv) File "/Users/erikp/Envs/hueton/lib/python3.3/site-packages/tox/_venv.py", line 303, in run_install_command extraenv=env, action=action) File "/Users/erikp/Envs/hueton/lib/python3.3/site-packages/tox/_venv.py", line 382, in _pcall return action.popen(args, cwd=cwd, env=env, redirect=redirect) File "/Users/erikp/Envs/hueton/lib/python3.3/site-packages/tox/_cmdline.py", line 96, in popen popen = self._popen(args, cwd, env=env, stdout=f, stderr=STDOUT) File "/Users/erikp/Envs/hueton/lib/python3.3/site-packages/tox/_cmdline.py", line 153, in _popen stdout=stdout, stderr=stderr, env=env) File "/Library/Frameworks/Python.framework/Versions/3.3/lib/python3.3/subprocess.py", line 820, in __init__ restore_signals, start_new_session) File "/Library/Frameworks/Python.framework/Versions/3.3/lib/python3.3/subprocess.py", line 1438, in _execute_child raise child_exception_type(errno_num, err_msg) FileNotFoundError: [Errno 2] No such file or directory: '.tox/py33/bin/pip' From issues-reply at bitbucket.org Tue Sep 24 13:17:41 2013 From: issues-reply at bitbucket.org (=?utf-8?q?Tarek_Ziad=C3=A9?=) Date: Tue, 24 Sep 2013 11:17:41 -0000 Subject: [Pytest-commit] Issue #124: ignore command exit codes (hpk42/tox) Message-ID: <20130924111741.17419.73079@app10.ash-private.bitbucket.org> New issue 124: ignore command exit codes https://bitbucket.org/hpk42/tox/issue/124/ignore-command-exit-codes Tarek Ziad?: Sometimes it's desirable to ignore the exit code of a command, in the commands option of a testenv section. I would like to suggest adding a "-" prefix like in Makefiles. When a command has such a prefix, tox should ignore the exit code of that command. example: [testenv:py27] ``` #!ini commands = coverage erase {envbindir}/python setup.py develop coverage run -p setup.py test coverage combine - coverage html {envbindir}/flake8 loads ``` Here, the 5th command may return false, but this can be ignore From commits-noreply at bitbucket.org Thu Sep 26 08:46:11 2013 From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org) Date: Thu, 26 Sep 2013 06:46:11 -0000 Subject: [Pytest-commit] commit/pytest: hpk42: fix issue355: junitxml generates name="pytest" tag. Message-ID: <20130926064611.19919.39841@app09.ash-private.bitbucket.org> 1 new commit in pytest: https://bitbucket.org/hpk42/pytest/commits/5ac2e164e7b4/ Changeset: 5ac2e164e7b4 User: hpk42 Date: 2013-09-26 08:45:50 Summary: fix issue355: junitxml generates name="pytest" tag. Affected #: 3 files diff -r 5239f31ebf82eee712fc9f54f414e34afdfc1ddf -r 5ac2e164e7b41350274ce40f0495f82817e3c68c CHANGELOG --- a/CHANGELOG +++ b/CHANGELOG @@ -23,7 +23,9 @@ monkeypatch.replace("requests.get", myfunc will replace the "get" function of the "requests" module with ``myfunc``. - + +- fix issue355: junitxml puts name="pytest" attribute to testsuite tag. + - fix issue322: tearDownClass is not run if setUpClass failed. Thanks Mathieu Agopian for the initial fix. Also make all of pytest/nose finalizer mimick the same generic behaviour: if a setupX exists and fails, diff -r 5239f31ebf82eee712fc9f54f414e34afdfc1ddf -r 5ac2e164e7b41350274ce40f0495f82817e3c68c _pytest/junitxml.py --- a/_pytest/junitxml.py +++ b/_pytest/junitxml.py @@ -62,8 +62,8 @@ def pytest_addoption(parser): group = parser.getgroup("terminal reporting") - group.addoption('--junitxml', '--junit-xml', action="store", - dest="xmlpath", metavar="path", default=None, + group.addoption('--junitxml', '--junit-xml', action="store", + dest="xmlpath", metavar="path", default=None, help="create junit-xml style report file at given path.") group.addoption('--junitprefix', '--junit-prefix', action="store", metavar="str", default=None, @@ -217,7 +217,7 @@ logfile.write('') logfile.write(Junit.testsuite( self.tests, - name="", + name="pytest", errors=self.errors, failures=self.failed, skips=self.skipped, diff -r 5239f31ebf82eee712fc9f54f414e34afdfc1ddf -r 5ac2e164e7b41350274ce40f0495f82817e3c68c testing/test_junitxml.py --- a/testing/test_junitxml.py +++ b/testing/test_junitxml.py @@ -37,7 +37,7 @@ result, dom = runandparse(testdir) assert result.ret node = dom.getElementsByTagName("testsuite")[0] - assert_attr(node, errors=0, failures=1, skips=3, tests=2) + assert_attr(node, name="pytest", errors=0, failures=1, skips=3, tests=2) def test_timing_function(self, testdir): testdir.makepyfile(""" Repository URL: https://bitbucket.org/hpk42/pytest/ -- This is a commit notification from bitbucket.org. You are receiving this because you have the service enabled, addressing the recipient of this email. From issues-reply at bitbucket.org Thu Sep 26 12:40:11 2013 From: issues-reply at bitbucket.org (Chris Jerdonek) Date: Thu, 26 Sep 2013 10:40:11 -0000 Subject: [Pytest-commit] Issue #125: add a {randomseed} substitution string (hpk42/tox) Message-ID: <20130926104011.26594.5183@app03.ash-private.bitbucket.org> New issue 125: add a {randomseed} substitution string https://bitbucket.org/hpk42/tox/issue/125/add-a-randomseed-substitution-string Chris Jerdonek: It would be nice to expose a special substitution string called something like {randomseed} that would generate a random integer value each time it is used. This would be useful for example in cases like this: setenv = PYTHONHASHSEED = {randomseed} An associated command-line option `--randomseed` to force it to a particular value would also be good. See this [e-mail thread](http://lists.idyll.org/pipermail/testing-in-python/2013-September/005679.html) for background. Note that to be consistent with supporting `--randomseed`, {randomseed} should probably yield the same value every time it is used (e.g. if it appears more than once in tox.ini). In other words, it is a global value established once when tox is invoked. From commits-noreply at bitbucket.org Thu Sep 26 12:57:45 2013 From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org) Date: Thu, 26 Sep 2013 10:57:45 -0000 Subject: [Pytest-commit] commit/pytest: hpk42: introduce yieldctx=True in the @pytest.fixture decorator. Refactor tests and docs. Message-ID: <20130926105745.2097.25181@app10.ash-private.bitbucket.org> 1 new commit in pytest: https://bitbucket.org/hpk42/pytest/commits/e4ca07d8ab69/ Changeset: e4ca07d8ab69 User: hpk42 Date: 2013-09-26 12:57:21 Summary: introduce yieldctx=True in the @pytest.fixture decorator. Refactor tests and docs. Affected #: 3 files diff -r 5ac2e164e7b41350274ce40f0495f82817e3c68c -r e4ca07d8ab69b50c6a13d04cbff8417d5a2ad0b0 _pytest/python.py --- a/_pytest/python.py +++ b/_pytest/python.py @@ -26,19 +26,21 @@ class FixtureFunctionMarker: - def __init__(self, scope, params, autouse=False): + def __init__(self, scope, params, autouse=False, yieldctx=False): self.scope = scope self.params = params self.autouse = autouse + self.yieldctx = yieldctx def __call__(self, function): if inspect.isclass(function): - raise ValueError("class fixtures not supported (may be in the future)") + raise ValueError( + "class fixtures not supported (may be in the future)") function._pytestfixturefunction = self return function -def fixture(scope="function", params=None, autouse=False): +def fixture(scope="function", params=None, autouse=False, yieldctx=False): """ (return a) decorator to mark a fixture factory function. This decorator can be used (with or or without parameters) to define @@ -59,12 +61,17 @@ :arg autouse: if True, the fixture func is activated for all tests that can see it. If False (the default) then an explicit reference is needed to activate the fixture. + + :arg yieldctx: if True, the fixture function yields a fixture value. + Code after such a ``yield`` statement is treated as + teardown code. """ if callable(scope) and params is None and autouse == False: # direct decoration - return FixtureFunctionMarker("function", params, autouse)(scope) + return FixtureFunctionMarker( + "function", params, autouse, yieldctx)(scope) else: - return FixtureFunctionMarker(scope, params, autouse=autouse) + return FixtureFunctionMarker(scope, params, autouse, yieldctx) defaultfuncargprefixmarker = fixture() @@ -1616,6 +1623,7 @@ assert not name.startswith(self._argprefix) fixturedef = FixtureDef(self, nodeid, name, obj, marker.scope, marker.params, + marker.yieldctx, unittest=unittest) faclist = self._arg2fixturedefs.setdefault(name, []) if not fixturedef.has_location: @@ -1656,8 +1664,18 @@ except ValueError: pass -def call_fixture_func(fixturefunc, request, kwargs): - if is_generator(fixturefunc): +def fail_fixturefunc(fixturefunc, msg): + fs, lineno = getfslineno(fixturefunc) + location = "%s:%s" % (fs, lineno+1) + source = py.code.Source(fixturefunc) + pytest.fail(msg + ":\n\n" + str(source.indent()) + "\n" + location, + pytrace=False) + +def call_fixture_func(fixturefunc, request, kwargs, yieldctx): + if yieldctx: + if not is_generator(fixturefunc): + fail_fixturefunc(fixturefunc, + msg="yieldctx=True requires yield statement") iter = fixturefunc(**kwargs) next = getattr(iter, "__next__", None) if next is None: @@ -1669,11 +1687,8 @@ except StopIteration: pass else: - fs, lineno = getfslineno(fixturefunc) - location = "%s:%s" % (fs, lineno+1) - pytest.fail( - "fixture function %s has more than one 'yield': \n%s" % - (fixturefunc.__name__, location), pytrace=False) + fail_fixturefunc(fixturefunc, + "fixture function has more than one 'yield'") request.addfinalizer(teardown) else: res = fixturefunc(**kwargs) @@ -1682,7 +1697,7 @@ class FixtureDef: """ A container for a factory definition. """ def __init__(self, fixturemanager, baseid, argname, func, scope, params, - unittest=False): + yieldctx, unittest=False): self._fixturemanager = fixturemanager self.baseid = baseid or '' self.has_location = baseid is not None @@ -1693,6 +1708,7 @@ self.params = params startindex = unittest and 1 or None self.argnames = getfuncargnames(func, startindex=startindex) + self.yieldctx = yieldctx self.unittest = unittest self._finalizer = [] @@ -1730,7 +1746,8 @@ fixturefunc = fixturefunc.__get__(request.instance) except AttributeError: pass - result = call_fixture_func(fixturefunc, request, kwargs) + result = call_fixture_func(fixturefunc, request, kwargs, + self.yieldctx) assert not hasattr(self, "cached_result") self.cached_result = result return result diff -r 5ac2e164e7b41350274ce40f0495f82817e3c68c -r e4ca07d8ab69b50c6a13d04cbff8417d5a2ad0b0 doc/en/fixture.txt --- a/doc/en/fixture.txt +++ b/doc/en/fixture.txt @@ -40,7 +40,7 @@ .. _`@pytest.fixture`: .. _`pytest.fixture`: -Fixtures as Function arguments (funcargs) +Fixtures as Function arguments ----------------------------------------- Test functions can receive fixture objects by naming them as an input @@ -70,7 +70,8 @@ $ py.test test_smtpsimple.py =========================== test session starts ============================ - platform linux2 -- Python 2.7.3 -- pytest-2.3.5 + platform linux2 -- Python 2.7.3 -- pytest-2.4.0.dev12 + plugins: xdist, pep8, cov, cache, capturelog, instafail collected 1 items test_smtpsimple.py F @@ -78,7 +79,7 @@ ================================= FAILURES ================================= ________________________________ test_ehlo _________________________________ - smtp = + smtp = def test_ehlo(smtp): response, msg = smtp.ehlo() @@ -88,7 +89,7 @@ E assert 0 test_smtpsimple.py:12: AssertionError - ========================= 1 failed in 0.20 seconds ========================= + ========================= 1 failed in 0.17 seconds ========================= In the failure traceback we see that the test function was called with a ``smtp`` argument, the ``smtplib.SMTP()`` instance created by the fixture @@ -123,7 +124,7 @@ but is not anymore advertised as the primary means of declaring fixture functions. -Funcargs a prime example of dependency injection +"Funcargs" a prime example of dependency injection --------------------------------------------------- When injecting fixtures to test functions, pytest-2.0 introduced the @@ -142,7 +143,7 @@ .. _smtpshared: -Working with a module-shared fixture +Sharing a fixture across tests in a module (or class/session) ----------------------------------------------------------------- .. regendoc:wipe @@ -188,7 +189,8 @@ $ py.test test_module.py =========================== test session starts ============================ - platform linux2 -- Python 2.7.3 -- pytest-2.3.5 + platform linux2 -- Python 2.7.3 -- pytest-2.4.0.dev12 + plugins: xdist, pep8, cov, cache, capturelog, instafail collected 2 items test_module.py FF @@ -196,7 +198,7 @@ ================================= FAILURES ================================= ________________________________ test_ehlo _________________________________ - smtp = + smtp = def test_ehlo(smtp): response = smtp.ehlo() @@ -208,7 +210,7 @@ test_module.py:6: AssertionError ________________________________ test_noop _________________________________ - smtp = + smtp = def test_noop(smtp): response = smtp.noop() @@ -217,7 +219,7 @@ E assert 0 test_module.py:11: AssertionError - ========================= 2 failed in 0.26 seconds ========================= + ========================= 2 failed in 0.16 seconds ========================= You see the two ``assert 0`` failing and more importantly you can also see that the same (module-scoped) ``smtp`` object was passed into the two @@ -233,62 +235,17 @@ # the returned fixture value will be shared for # all tests needing it -.. _`contextfixtures`: +.. _`finalization`: -fixture finalization / teardowns +fixture finalization / executing teardown code ------------------------------------------------------------- -pytest supports two styles of fixture finalization: +pytest supports execution of fixture specific finalization code +when the fixture goes out of scope. By accepting a ``request`` object +into your fixture function you can call its ``request.addfinalizer`` one +or multiple times:: -- (new in pytest-2.4) by writing a contextmanager fixture - generator where a fixture value is "yielded" and the remainder - of the function serves as the teardown code. This integrates - very well with existing context managers. - -- by making a fixture function accept a ``request`` argument - with which it can call ``request.addfinalizer(teardownfunction)`` - to register a teardown callback function. - -Both methods are strictly equivalent from pytest's view and will -remain supported in the future. - -Because a number of people prefer the new contextmanager style -we describe it first:: - - # content of test_ctxfixture.py - - import smtplib - import pytest - - @pytest.fixture(scope="module") - def smtp(): - smtp = smtplib.SMTP("merlinux.eu") - yield smtp # provide the fixture value - print ("teardown smtp") - smtp.close() - -pytest detects that you are using a ``yield`` in your fixture function, -turns it into a generator and: - -a) iterates once into it for producing the value -b) iterates a second time for tearing the fixture down, expecting - a StopIteration (which is produced automatically from the Python - runtime when the generator returns). - -.. note:: - - The teardown will execute independently of the status of test functions. - You do not need to write the teardown code into a ``try-finally`` clause - like you would usually do with ``contextlib.contextmanager`` decorated - functions. - - If the fixture generator yields a second value pytest will report - an error. Yielding cannot be used for parametrization. We'll describe - ways to implement parametrization further below. - -Prior to pytest-2.4 you always needed to register a finalizer by accepting -a ``request`` object into your fixture function and calling -``request.addfinalizer`` with a teardown function:: + # content of conftest.py import smtplib import pytest @@ -299,24 +256,38 @@ def fin(): print ("teardown smtp") smtp.close() + request.addfinalizer(fin) return smtp # provide the fixture value -This method of registering a finalizer reads more indirect -than the new contextmanager style syntax because ``fin`` -is a callback function. +The ``fin`` function will execute when the last test using +the fixture in the module has finished execution. +Let's execute it:: + + $ py.test -s -q --tb=no + FF + 2 failed in 0.16 seconds + teardown smtp + +We see that the ``smtp`` instance is finalized after the two +tests finished execution. Note that if we decorated our fixture +function with ``scope='function'`` then fixture setup and cleanup would +occur around each single test. In either case the test +module itself does not need to change or know about these details +of fixture setup. + +Note that pytest-2.4 introduced an alternative `yield-context `_ +mechanism which allows to interact nicely with context managers. .. _`request-context`: -Fixtures can interact with the requesting test context +Fixtures can introspect the requesting test context ------------------------------------------------------------- -pytest provides a builtin :py:class:`request ` object, -which fixture functions can use to introspect the function, class or module -for which they are invoked. - +Fixture function can accept the :py:class:`request ` object +to introspect the "requesting" test function, class or module context. Further extending the previous ``smtp`` fixture example, let's -read an optional server URL from the module namespace:: +read an optional server URL from the test module which uses our fixture:: # content of conftest.py import pytest @@ -326,22 +297,21 @@ def smtp(request): server = getattr(request.module, "smtpserver", "merlinux.eu") smtp = smtplib.SMTP(server) - yield smtp # provide the fixture - print ("finalizing %s" % smtp) - smtp.close() + + def fin(): + print ("finalizing %s (%s)" % (smtp, server)) + smtp.close() + + return smtp -The finalizing part after the ``yield smtp`` statement will execute -when the last test using the ``smtp`` fixture has executed:: +We use the ``request.module`` attribute to optionally obtain an +``smtpserver`` attribute from the test module. If we just execute +again, nothing much has changed:: $ py.test -s -q --tb=no FF - finalizing - -We see that the ``smtp`` instance is finalized after the two -tests which use it finished executin. If we rather specify -``scope='function'`` then fixture setup and cleanup occurs -around each single test. Note that in either case the test -module itself does not need to change! + 2 failed in 0.17 seconds + teardown smtp Let's quickly create another test module that actually sets the server URL in its module namespace:: @@ -361,12 +331,11 @@ ______________________________ test_showhelo _______________________________ test_anothersmtp.py:5: in test_showhelo > assert 0, smtp.helo() - E AssertionError: (250, 'mail.python.org') + E AssertionError: (250, 'hq.merlinux.eu') voila! The ``smtp`` fixture function picked up our mail server name from the module namespace. - .. _`fixture-parametrize`: Parametrizing a fixture @@ -392,9 +361,11 @@ params=["merlinux.eu", "mail.python.org"]) def smtp(request): smtp = smtplib.SMTP(request.param) - yield smtp - print ("finalizing %s" % smtp) - smtp.close() + def fin(): + print ("finalizing %s" % smtp) + smtp.close() + request.addfinalizer(fin) + return smtp The main change is the declaration of ``params`` with :py:func:`@pytest.fixture <_pytest.python.fixture>`, a list of values @@ -407,7 +378,7 @@ ================================= FAILURES ================================= __________________________ test_ehlo[merlinux.eu] __________________________ - smtp = + smtp = def test_ehlo(smtp): response = smtp.ehlo() @@ -419,7 +390,7 @@ test_module.py:6: AssertionError __________________________ test_noop[merlinux.eu] __________________________ - smtp = + smtp = def test_noop(smtp): response = smtp.noop() @@ -430,7 +401,7 @@ test_module.py:11: AssertionError ________________________ test_ehlo[mail.python.org] ________________________ - smtp = + smtp = def test_ehlo(smtp): response = smtp.ehlo() @@ -441,7 +412,7 @@ test_module.py:5: AssertionError ________________________ test_noop[mail.python.org] ________________________ - smtp = + smtp = def test_noop(smtp): response = smtp.noop() @@ -450,6 +421,7 @@ E assert 0 test_module.py:11: AssertionError + 4 failed in 6.04 seconds We see that our two test functions each ran twice, against the different ``smtp`` instances. Note also, that with the ``mail.python.org`` @@ -489,13 +461,15 @@ $ py.test -v test_appsetup.py =========================== test session starts ============================ - platform linux2 -- Python 2.7.3 -- pytest-2.3.5 -- /home/hpk/p/pytest/.tox/regen/bin/python + platform linux2 -- Python 2.7.3 -- pytest-2.4.0.dev12 -- /home/hpk/venv/0/bin/python + cachedir: /tmp/doc-exec-120/.cache + plugins: xdist, pep8, cov, cache, capturelog, instafail collecting ... collected 2 items + test_appsetup.py:12: test_smtp_exists[mail.python.org] PASSED test_appsetup.py:12: test_smtp_exists[merlinux.eu] PASSED - test_appsetup.py:12: test_smtp_exists[mail.python.org] PASSED - ========================= 2 passed in 5.38 seconds ========================= + ========================= 2 passed in 6.98 seconds ========================= Due to the parametrization of ``smtp`` the test will run twice with two different ``App`` instances and respective smtp servers. There is no @@ -534,8 +508,9 @@ def modarg(request): param = request.param print "create", param - yield param - print ("fin %s" % param) + def fin(): + print ("fin %s" % param) + return param @pytest.fixture(scope="function", params=[1,2]) def otherarg(request): @@ -552,31 +527,31 @@ $ py.test -v -s test_module.py =========================== test session starts ============================ - platform linux2 -- Python 2.7.3 -- pytest-2.3.5 -- /home/hpk/p/pytest/.tox/regen/bin/python + platform linux2 -- Python 2.7.3 -- pytest-2.4.0.dev12 -- /home/hpk/venv/0/bin/python + cachedir: /tmp/doc-exec-120/.cache + plugins: xdist, pep8, cov, cache, capturelog, instafail collecting ... collected 8 items - test_module.py:16: test_0[1] PASSED - test_module.py:16: test_0[2] PASSED - test_module.py:18: test_1[mod1] PASSED - test_module.py:20: test_2[1-mod1] PASSED - test_module.py:20: test_2[2-mod1] PASSED - test_module.py:18: test_1[mod2] PASSED - test_module.py:20: test_2[1-mod2] PASSED - test_module.py:20: test_2[2-mod2] PASSED + test_module.py:15: test_0[1] PASSED + test_module.py:15: test_0[2] PASSED + test_module.py:17: test_1[mod1] PASSED + test_module.py:19: test_2[1-mod1] PASSED + test_module.py:19: test_2[2-mod1] PASSED + test_module.py:17: test_1[mod2] PASSED + test_module.py:19: test_2[1-mod2] PASSED + test_module.py:19: test_2[2-mod2] PASSED - ========================= 8 passed in 0.01 seconds ========================= + ========================= 8 passed in 0.02 seconds ========================= test0 1 test0 2 create mod1 test1 mod1 test2 1 mod1 test2 2 mod1 - fin mod1 create mod2 test1 mod2 test2 1 mod2 test2 2 mod2 - fin mod2 You can see that the parametrized module-scoped ``modarg`` resource caused an ordering of test execution that lead to the fewest possible "active" resources. The finalizer for the ``mod1`` parametrized resource was executed @@ -632,6 +607,7 @@ $ py.test -q .. + 2 passed in 0.02 seconds You can specify multiple fixtures like this:: @@ -702,6 +678,7 @@ $ py.test -q .. + 2 passed in 0.02 seconds Here is how autouse fixtures work in other scopes: @@ -750,3 +727,62 @@ fixtures functions starts at test classes, then test modules, then ``conftest.py`` files and finally builtin and third party plugins. + +.. _yieldctx: + +Fixture functions using "yield" / context manager integration +--------------------------------------------------------------- + +.. versionadded:: 2.4 + +pytest-2.4 allows fixture functions to use a ``yield`` instead +of a ``return`` statement to provide a fixture value. Let's +look at a quick example before discussing advantages:: + + # content of conftest.py + + import smtplib + import pytest + + @pytest.fixture(scope="module", yieldctx=True) + def smtp(): + smtp = smtplib.SMTP("merlinux.eu") + yield smtp # provide the fixture value + print ("teardown smtp after a yield") + smtp.close() + +In contrast to the `finalization`_ example, our fixture +function uses a single ``yield`` to provide the ``smtp`` fixture +value. The code after the ``yield`` statement serves as the +teardown code, avoiding the indirection of registering a +teardown function. More importantly, it also allows to +seemlessly re-use existing context managers, for example:: + + @pytest.fixture(yieldctx=True) + def somefixture(): + with open("somefile") as f: + yield f.readlines() + +The file ``f`` will be closed once ``somefixture`` goes out of scope. +It is possible to achieve the same result by using a ``request.addfinalizer`` +call but it is more boilerplate and not very obvious unless +you know about the exact ``__enter__|__exit__`` protocol of with-style +context managers. + +For some background, here is the protocol pytest follows for when +``yieldctx=True`` is specified in the fixture decorator: + +a) iterate once into the generator for producing the value +b) iterate a second time for tearing the fixture down, expecting + a StopIteration (which is produced automatically from the Python + runtime when the generator returns). + +The teardown will always execute, independently of the outcome of +test functions. You do **not need** to write the teardown code into a +``try-finally`` clause like you would usually do with +:py:func:`contextlib.contextmanager` decorated functions. + +If the fixture generator yields a second value pytest will report +an error. Yielding cannot be used for parametrization, rather +see `fixture-parametrize`_. + diff -r 5ac2e164e7b41350274ce40f0495f82817e3c68c -r e4ca07d8ab69b50c6a13d04cbff8417d5a2ad0b0 testing/python/fixture.py --- a/testing/python/fixture.py +++ b/testing/python/fixture.py @@ -1980,7 +1980,7 @@ def test_simple(self, testdir): testdir.makepyfile(""" import pytest - @pytest.fixture + @pytest.fixture(yieldctx=True) def arg1(): print ("setup") yield 1 @@ -2004,7 +2004,7 @@ def test_scoped(self, testdir): testdir.makepyfile(""" import pytest - @pytest.fixture(scope="module") + @pytest.fixture(scope="module", yieldctx=True) def arg1(): print ("setup") yield 1 @@ -2025,7 +2025,7 @@ def test_setup_exception(self, testdir): testdir.makepyfile(""" import pytest - @pytest.fixture(scope="module") + @pytest.fixture(scope="module", yieldctx=True) def arg1(): pytest.fail("setup") yield 1 @@ -2041,7 +2041,7 @@ def test_teardown_exception(self, testdir): testdir.makepyfile(""" import pytest - @pytest.fixture(scope="module") + @pytest.fixture(scope="module", yieldctx=True) def arg1(): yield 1 pytest.fail("teardown") @@ -2054,11 +2054,10 @@ *1 passed*1 error* """) - def test_yields_more_than_one(self, testdir): testdir.makepyfile(""" import pytest - @pytest.fixture(scope="module") + @pytest.fixture(scope="module", yieldctx=True) def arg1(): yield 1 yield 2 @@ -2072,3 +2071,20 @@ """) + def test_no_yield(self, testdir): + testdir.makepyfile(""" + import pytest + @pytest.fixture(scope="module", yieldctx=True) + def arg1(): + return 1 + def test_1(arg1): + pass + """) + result = testdir.runpytest("-s") + result.stdout.fnmatch_lines(""" + *yieldctx*requires*yield* + *yieldctx=True* + *def arg1* + """) + + Repository URL: https://bitbucket.org/hpk42/pytest/ -- This is a commit notification from bitbucket.org. You are receiving this because you have the service enabled, addressing the recipient of this email. From commits-noreply at bitbucket.org Fri Sep 27 10:22:05 2013 From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org) Date: Fri, 27 Sep 2013 08:22:05 -0000 Subject: [Pytest-commit] commit/pytest: 2 new changesets Message-ID: <20130927082205.5233.95590@app12.ash-private.bitbucket.org> 2 new commits in pytest: https://bitbucket.org/hpk42/pytest/commits/a3261d045144/ Changeset: a3261d045144 User: hpk42 Date: 2013-09-27 09:49:39 Summary: don't manipulate FDs at all if output capturing is turned off. Affected #: 2 files diff -r e4ca07d8ab69b50c6a13d04cbff8417d5a2ad0b0 -r a3261d045144baf409b00ca4a260727361d7f403 _pytest/terminal.py --- a/_pytest/terminal.py +++ b/_pytest/terminal.py @@ -34,9 +34,10 @@ def pytest_configure(config): config.option.verbose -= config.option.quiet # we try hard to make printing resilient against - # later changes on FD level. + # later changes on FD level. (unless capturing is turned off) stdout = py.std.sys.stdout - if hasattr(os, 'dup') and hasattr(stdout, 'fileno'): + capture = config.option.capture != "no" + if capture and hasattr(os, 'dup') and hasattr(stdout, 'fileno'): try: newstdout = py.io.dupfile(stdout, buffering=1, encoding=stdout.encoding) diff -r e4ca07d8ab69b50c6a13d04cbff8417d5a2ad0b0 -r a3261d045144baf409b00ca4a260727361d7f403 testing/test_terminal.py --- a/testing/test_terminal.py +++ b/testing/test_terminal.py @@ -677,6 +677,13 @@ "*2 passed*" ]) +def test_nofd_manipulation_with_capture_disabled(testdir): + from _pytest.terminal import pytest_configure + config = testdir.parseconfig("--capture=no") + stdout = sys.stdout + pytest_configure(config) + reporter = config.pluginmanager.getplugin('terminalreporter') + assert reporter._tw._file == stdout def test_tbstyle_native_setup_error(testdir): p = testdir.makepyfile(""" @@ -684,7 +691,7 @@ @pytest.fixture def setup_error_fixture(): raise Exception("error in exception") - + def test_error_fixture(setup_error_fixture): pass """) https://bitbucket.org/hpk42/pytest/commits/c1b01e0df4b3/ Changeset: c1b01e0df4b3 User: hpk42 Date: 2013-09-27 10:21:23 Summary: rework docs to demonstrate and discuss current yield syntax in more depth. Affected #: 3 files diff -r a3261d045144baf409b00ca4a260727361d7f403 -r c1b01e0df4b3bb4bd865af33bf0134e1ef6d519e doc/en/apiref.txt --- a/doc/en/apiref.txt +++ b/doc/en/apiref.txt @@ -11,6 +11,7 @@ customize.txt assert.txt fixture.txt + yieldfixture.txt parametrize.txt xunit_setup.txt capture.txt diff -r a3261d045144baf409b00ca4a260727361d7f403 -r c1b01e0df4b3bb4bd865af33bf0134e1ef6d519e doc/en/fixture.txt --- a/doc/en/fixture.txt +++ b/doc/en/fixture.txt @@ -79,7 +79,7 @@ ================================= FAILURES ================================= ________________________________ test_ehlo _________________________________ - smtp = + smtp = def test_ehlo(smtp): response, msg = smtp.ehlo() @@ -198,7 +198,7 @@ ================================= FAILURES ================================= ________________________________ test_ehlo _________________________________ - smtp = + smtp = def test_ehlo(smtp): response = smtp.ehlo() @@ -210,7 +210,7 @@ test_module.py:6: AssertionError ________________________________ test_noop _________________________________ - smtp = + smtp = def test_noop(smtp): response = smtp.noop() @@ -219,7 +219,7 @@ E assert 0 test_module.py:11: AssertionError - ========================= 2 failed in 0.16 seconds ========================= + ========================= 2 failed in 0.18 seconds ========================= You see the two ``assert 0`` failing and more importantly you can also see that the same (module-scoped) ``smtp`` object was passed into the two @@ -266,8 +266,7 @@ $ py.test -s -q --tb=no FF - 2 failed in 0.16 seconds - teardown smtp + 2 failed in 0.20 seconds We see that the ``smtp`` instance is finalized after the two tests finished execution. Note that if we decorated our fixture @@ -276,8 +275,9 @@ module itself does not need to change or know about these details of fixture setup. -Note that pytest-2.4 introduced an alternative `yield-context `_ -mechanism which allows to interact nicely with context managers. +Note that pytest-2.4 introduced an experimental alternative +:ref:`yield fixture mechanism ` for easier context manager integration +and more linear writing of teardown code. .. _`request-context`: @@ -310,8 +310,7 @@ $ py.test -s -q --tb=no FF - 2 failed in 0.17 seconds - teardown smtp + 2 failed in 0.18 seconds Let's quickly create another test module that actually sets the server URL in its module namespace:: @@ -331,7 +330,7 @@ ______________________________ test_showhelo _______________________________ test_anothersmtp.py:5: in test_showhelo > assert 0, smtp.helo() - E AssertionError: (250, 'hq.merlinux.eu') + E AssertionError: (250, 'mail.python.org') voila! The ``smtp`` fixture function picked up our mail server name from the module namespace. @@ -378,7 +377,7 @@ ================================= FAILURES ================================= __________________________ test_ehlo[merlinux.eu] __________________________ - smtp = + smtp = def test_ehlo(smtp): response = smtp.ehlo() @@ -390,7 +389,7 @@ test_module.py:6: AssertionError __________________________ test_noop[merlinux.eu] __________________________ - smtp = + smtp = def test_noop(smtp): response = smtp.noop() @@ -401,7 +400,7 @@ test_module.py:11: AssertionError ________________________ test_ehlo[mail.python.org] ________________________ - smtp = + smtp = def test_ehlo(smtp): response = smtp.ehlo() @@ -412,7 +411,7 @@ test_module.py:5: AssertionError ________________________ test_noop[mail.python.org] ________________________ - smtp = + smtp = def test_noop(smtp): response = smtp.noop() @@ -421,7 +420,7 @@ E assert 0 test_module.py:11: AssertionError - 4 failed in 6.04 seconds + 4 failed in 6.47 seconds We see that our two test functions each ran twice, against the different ``smtp`` instances. Note also, that with the ``mail.python.org`` @@ -462,14 +461,14 @@ $ py.test -v test_appsetup.py =========================== test session starts ============================ platform linux2 -- Python 2.7.3 -- pytest-2.4.0.dev12 -- /home/hpk/venv/0/bin/python - cachedir: /tmp/doc-exec-120/.cache + cachedir: /tmp/doc-exec-127/.cache plugins: xdist, pep8, cov, cache, capturelog, instafail collecting ... collected 2 items test_appsetup.py:12: test_smtp_exists[mail.python.org] PASSED test_appsetup.py:12: test_smtp_exists[merlinux.eu] PASSED - ========================= 2 passed in 6.98 seconds ========================= + ========================= 2 passed in 6.07 seconds ========================= Due to the parametrization of ``smtp`` the test will run twice with two different ``App`` instances and respective smtp servers. There is no @@ -528,30 +527,30 @@ $ py.test -v -s test_module.py =========================== test session starts ============================ platform linux2 -- Python 2.7.3 -- pytest-2.4.0.dev12 -- /home/hpk/venv/0/bin/python - cachedir: /tmp/doc-exec-120/.cache + cachedir: /tmp/doc-exec-127/.cache plugins: xdist, pep8, cov, cache, capturelog, instafail collecting ... collected 8 items - test_module.py:15: test_0[1] PASSED - test_module.py:15: test_0[2] PASSED - test_module.py:17: test_1[mod1] PASSED - test_module.py:19: test_2[1-mod1] PASSED - test_module.py:19: test_2[2-mod1] PASSED - test_module.py:17: test_1[mod2] PASSED - test_module.py:19: test_2[1-mod2] PASSED - test_module.py:19: test_2[2-mod2] PASSED + test_module.py:15: test_0[1] test0 1 + PASSED + test_module.py:15: test_0[2] test0 2 + PASSED + test_module.py:17: test_1[mod1] create mod1 + test1 mod1 + PASSED + test_module.py:19: test_2[1-mod1] test2 1 mod1 + PASSED + test_module.py:19: test_2[2-mod1] test2 2 mod1 + PASSED + test_module.py:17: test_1[mod2] create mod2 + test1 mod2 + PASSED + test_module.py:19: test_2[1-mod2] test2 1 mod2 + PASSED + test_module.py:19: test_2[2-mod2] test2 2 mod2 + PASSED ========================= 8 passed in 0.02 seconds ========================= - test0 1 - test0 2 - create mod1 - test1 mod1 - test2 1 mod1 - test2 2 mod1 - create mod2 - test1 mod2 - test2 1 mod2 - test2 2 mod2 You can see that the parametrized module-scoped ``modarg`` resource caused an ordering of test execution that lead to the fewest possible "active" resources. The finalizer for the ``mod1`` parametrized resource was executed @@ -728,61 +727,3 @@ ``conftest.py`` files and finally builtin and third party plugins. -.. _yieldctx: - -Fixture functions using "yield" / context manager integration ---------------------------------------------------------------- - -.. versionadded:: 2.4 - -pytest-2.4 allows fixture functions to use a ``yield`` instead -of a ``return`` statement to provide a fixture value. Let's -look at a quick example before discussing advantages:: - - # content of conftest.py - - import smtplib - import pytest - - @pytest.fixture(scope="module", yieldctx=True) - def smtp(): - smtp = smtplib.SMTP("merlinux.eu") - yield smtp # provide the fixture value - print ("teardown smtp after a yield") - smtp.close() - -In contrast to the `finalization`_ example, our fixture -function uses a single ``yield`` to provide the ``smtp`` fixture -value. The code after the ``yield`` statement serves as the -teardown code, avoiding the indirection of registering a -teardown function. More importantly, it also allows to -seemlessly re-use existing context managers, for example:: - - @pytest.fixture(yieldctx=True) - def somefixture(): - with open("somefile") as f: - yield f.readlines() - -The file ``f`` will be closed once ``somefixture`` goes out of scope. -It is possible to achieve the same result by using a ``request.addfinalizer`` -call but it is more boilerplate and not very obvious unless -you know about the exact ``__enter__|__exit__`` protocol of with-style -context managers. - -For some background, here is the protocol pytest follows for when -``yieldctx=True`` is specified in the fixture decorator: - -a) iterate once into the generator for producing the value -b) iterate a second time for tearing the fixture down, expecting - a StopIteration (which is produced automatically from the Python - runtime when the generator returns). - -The teardown will always execute, independently of the outcome of -test functions. You do **not need** to write the teardown code into a -``try-finally`` clause like you would usually do with -:py:func:`contextlib.contextmanager` decorated functions. - -If the fixture generator yields a second value pytest will report -an error. Yielding cannot be used for parametrization, rather -see `fixture-parametrize`_. - diff -r a3261d045144baf409b00ca4a260727361d7f403 -r c1b01e0df4b3bb4bd865af33bf0134e1ef6d519e doc/en/yieldfixture.txt --- /dev/null +++ b/doc/en/yieldfixture.txt @@ -0,0 +1,132 @@ + +.. _yieldctx: + +Fixture functions using "yield" / context manager integration +--------------------------------------------------------------- + +.. versionadded:: 2.4 + +.. regendoc:wipe + +pytest-2.4 allows fixture functions to seemlessly use a ``yield`` instead +of a ``return`` statement to provide a fixture value while otherwise +fully supporting all other fixture features. + +.. note:: + + "yielding" fixture values is an experimental feature and its exact + declaration may change later but earliest in a 2.5 release. You can thus + safely use this feature in the 2.4 series but may need to adapt your + fixtures later. Test functions themselves will not need to change + (they can be completely ignorant of the return/yield modes of + fixture functions). + +Let's look at a simple standalone-example using the new ``yield`` syntax:: + + # content of test_yield.py + + import pytest + + @pytest.fixture(yieldctx=True) + def passwd(): + print ("\nsetup before yield") + f = open("/etc/passwd") + yield f.readlines() + print ("teardown after yield") + f.close() + + def test_has_lines(passwd): + print ("test called") + assert passwd + +In contrast to :ref:`finalization through registering callbacks +`, our fixture function used a ``yield`` +statement to provide the lines of the ``/etc/passwd`` file. +The code after the ``yield`` statement serves as the teardown code, +avoiding the indirection of registering a teardown callback function. + +Let's run it with output capturing disabled:: + + $ py.test -q -s test_yield.py + + setup before yield + test called + .teardown after yield + + 1 passed in 0.01 seconds + +We can also seemlessly use the new syntax with ``with`` statements. +Let's simplify the above ``passwd`` fixture:: + + # content of test_yield2.py + + import pytest + + @pytest.fixture(yieldctx=True) + def passwd(): + with open("/etc/passwd") as f: + yield f.readlines() + + def test_has_lines(passwd): + assert len(passwd) >= 1 + +The file ``f`` will be closed after the test finished execution +because the Python ``file`` object supports finalization when +the ``with`` statement ends. + +Note that the new syntax is fully integrated with using ``scope``, +``params`` and other fixture features. Changing existing +fixture functions to use ``yield`` is thus straight forward. + +Discussion and future considerations / feedback +++++++++++++++++++++++++++++++++++++++++++++++++++++ + +The yield-syntax has been discussed by pytest users extensively. +In general, the advantages of the using a ``yield`` fixture syntax are: + +- easy provision of fixtures in conjunction with context managers. + +- no need to register a callback, providing for more synchronous + control flow in the fixture function. Also there is no need to accept + the ``request`` object into the fixture function just for providing + finalization code. + +However, there are also limitations or foreseeable irritations: + +- usually ``yield`` is typically used for producing multiple values. + But fixture functions can only yield exactly one value. + Yielding a second fixture value will get you an error. + It's possible we can evolve pytest to allow for producing + multiple values as an alternative to current parametrization. + For now, you can just use the normal + :ref:`fixture parametrization ` + mechanisms together with ``yield``-style fixtures. + +- the ``yield`` syntax is similar to what + :py:func:`contextlib.contextmanager` decorated functions + provide. With pytest fixture functions, the "after yield" part will + always be invoked, independently from the exception status + of the test function which uses the fixture. The pytest + behaviour makes sense if you consider that many different + test functions might use a module or session scoped fixture. + Some test functions might raise exceptions and others not, + so how could pytest re-raise a single exception at the + ``yield`` point in the fixture function? + +- lastly ``yield`` introduces more than one way to write + fixture functions, so what's the obvious way to a newcomer? + Newcomers reading the docs will see feature examples using the + ``return`` style so should use that, if in doubt. + Others can start experimenting with writing yield-style fixtures + and possibly help evolving them further. + +Some developers also expressed their preference for +rather introduce a new ``@pytest.yieldfixture`` decorator +instead of a keyword argument, or for assuming the above +yield-semantics automatically by introspecting if a fixture +function is a generator. Depending on more experiences and +feedback during the 2.4 cycle, we revisit theses issues. + +If you want to feedback or participate in the ongoing +discussion, please join our :ref:`contact channels`. +you are most welcome. Repository URL: https://bitbucket.org/hpk42/pytest/ -- This is a commit notification from bitbucket.org. You are receiving this because you have the service enabled, addressing the recipient of this email. From commits-noreply at bitbucket.org Fri Sep 27 10:58:09 2013 From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org) Date: Fri, 27 Sep 2013 08:58:09 -0000 Subject: [Pytest-commit] commit/pytest: hpk42: remove gittip mention for now. Message-ID: <20130927085809.4205.66888@app07.ash-private.bitbucket.org> 1 new commit in pytest: https://bitbucket.org/hpk42/pytest/commits/8f4c9f56a90f/ Changeset: 8f4c9f56a90f User: hpk42 Date: 2013-09-27 10:58:04 Summary: remove gittip mention for now. Affected #: 1 file diff -r c1b01e0df4b3bb4bd865af33bf0134e1ef6d519e -r 8f4c9f56a90fa62074cc2c52a4158f1f09adb084 doc/en/_templates/layout.html --- a/doc/en/_templates/layout.html +++ b/doc/en/_templates/layout.html @@ -5,9 +5,6 @@ - {% endblock %} Repository URL: https://bitbucket.org/hpk42/pytest/ -- This is a commit notification from bitbucket.org. You are receiving this because you have the service enabled, addressing the recipient of this email. From commits-noreply at bitbucket.org Fri Sep 27 12:34:52 2013 From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org) Date: Fri, 27 Sep 2013 10:34:52 -0000 Subject: [Pytest-commit] commit/pytest: 2 new changesets Message-ID: <20130927103452.24957.80056@app05.ash-private.bitbucket.org> 2 new commits in pytest: https://bitbucket.org/hpk42/pytest/commits/23278f23f092/ Changeset: 23278f23f092 User: hpk42 Date: 2013-09-27 12:28:34 Summary: fix some tests wrt to expecting output now that pytest does no introduce unwanted "buffering" on "-s" calls. Affected #: 4 files diff -r 8f4c9f56a90fa62074cc2c52a4158f1f09adb084 -r 23278f23f0923076011f554bb875e332fde21125 _pytest/python.py --- a/_pytest/python.py +++ b/_pytest/python.py @@ -1623,7 +1623,7 @@ assert not name.startswith(self._argprefix) fixturedef = FixtureDef(self, nodeid, name, obj, marker.scope, marker.params, - marker.yieldctx, + yieldctx=marker.yieldctx, unittest=unittest) faclist = self._arg2fixturedefs.setdefault(name, []) if not fixturedef.has_location: diff -r 8f4c9f56a90fa62074cc2c52a4158f1f09adb084 -r 23278f23f0923076011f554bb875e332fde21125 testing/python/fixture.py --- a/testing/python/fixture.py +++ b/testing/python/fixture.py @@ -1993,12 +1993,12 @@ """) result = testdir.runpytest("-s") result.stdout.fnmatch_lines(""" - setup - test1 1 - teardown - setup - test2 1 - teardown + *setup* + *test1 1* + *teardown* + *setup* + *test2 1* + *teardown* """) def test_scoped(self, testdir): @@ -2016,10 +2016,10 @@ """) result = testdir.runpytest("-s") result.stdout.fnmatch_lines(""" - setup - test1 1 - test2 1 - teardown + *setup* + *test1 1* + *test2 1* + *teardown* """) def test_setup_exception(self, testdir): diff -r 8f4c9f56a90fa62074cc2c52a4158f1f09adb084 -r 23278f23f0923076011f554bb875e332fde21125 testing/test_fixture_finalizer.py --- a/testing/test_fixture_finalizer.py +++ b/testing/test_fixture_finalizer.py @@ -28,4 +28,4 @@ """)) reprec = testdir.runpytest("-s") for test in ['test_browser']: - reprec.stdout.fnmatch_lines('Finalized') + reprec.stdout.fnmatch_lines('*Finalized*') diff -r 8f4c9f56a90fa62074cc2c52a4158f1f09adb084 -r 23278f23f0923076011f554bb875e332fde21125 testing/test_terminal.py --- a/testing/test_terminal.py +++ b/testing/test_terminal.py @@ -672,7 +672,7 @@ stdout = k.pop() stdout.close() """) - result = testdir.runpytest("-s") + result = testdir.runpytest() result.stdout.fnmatch_lines([ "*2 passed*" ]) https://bitbucket.org/hpk42/pytest/commits/95a817c87a02/ Changeset: 95a817c87a02 User: hpk42 Date: 2013-09-27 12:33:06 Summary: merge monkeypatch.replace into monkeypatch.setattr, also support monkeypatch.delattr. Affected #: 4 files diff -r 23278f23f0923076011f554bb875e332fde21125 -r 95a817c87a020ef2c325244d6f596943eaf5d0ae CHANGELOG --- a/CHANGELOG +++ b/CHANGELOG @@ -17,10 +17,10 @@ - PR27: correctly handle nose.SkipTest during collection. Thanks Antonio Cuni, Ronny Pfannschmidt. -- new monkeypatch.replace() to avoid imports and provide a shorter +- new monkeypatch.setattr() variant to provide a shorter invocation for patching out classes/functions from modules: - monkeypatch.replace("requests.get", myfunc + monkeypatch.setattr("requests.get", myfunc) will replace the "get" function of the "requests" module with ``myfunc``. diff -r 23278f23f0923076011f554bb875e332fde21125 -r 95a817c87a020ef2c325244d6f596943eaf5d0ae _pytest/monkeypatch.py --- a/_pytest/monkeypatch.py +++ b/_pytest/monkeypatch.py @@ -25,6 +25,39 @@ request.addfinalizer(mpatch.undo) return mpatch + + +def derive_importpath(import_path): + if not isinstance(import_path, str) or "." not in import_path: + raise TypeError("must be absolute import path string, not %r" % + (import_path,)) + rest = [] + target = import_path + while target: + try: + obj = __import__(target, None, None, "__doc__") + except ImportError: + if "." not in target: + __tracebackhide__ = True + pytest.fail("could not import any sub part: %s" % + import_path) + target, name = target.rsplit(".", 1) + rest.append(name) + else: + assert rest + try: + while len(rest) > 1: + attr = rest.pop() + obj = getattr(obj, attr) + attr = rest[0] + getattr(obj, attr) + except AttributeError: + __tracebackhide__ = True + pytest.fail("object %r has no attribute %r" % (obj, attr)) + return attr, obj + + + notset = object() class monkeypatch: @@ -34,70 +67,66 @@ self._setitem = [] self._cwd = None - def replace(self, import_path, value): - """ replace the object specified by a dotted ``import_path`` - with the given ``value``. + def setattr(self, target, name, value=notset, raising=True): + """ set attribute value on target, memorizing the old value. + By default raise AttributeError if the attribute did not exist. - For example ``replace("os.path.abspath", value)`` will - trigger an ``import os.path`` and a subsequent - setattr(os.path, "abspath", value). Or to prevent - the requests library from performing requests you can call - ``replace("requests.sessions.Session.request", None)`` - which will lead to an import of ``requests.sessions`` and a call - to ``setattr(requests.sessions.Session, "request", None)``. + For convenience you can specify a string as ``target`` which + will be interpreted as a dotted import path, with the last part + being the attribute name. Example: + ``monkeypatch.setattr("os.getcwd", lambda x: "/")`` + would set the ``getcwd`` function of the ``os`` module. + + The ``raising`` value determines if the setattr should fail + if the attribute is not already present (defaults to True + which means it will raise). """ - if not isinstance(import_path, str) or "." not in import_path: - raise TypeError("must be absolute import path string, not %r" % - (import_path,)) - rest = [] - target = import_path - while target: - try: - obj = __import__(target, None, None, "__doc__") - except ImportError: - if "." not in target: - __tracebackhide__ = True - pytest.fail("could not import any sub part: %s" % - import_path) - target, name = target.rsplit(".", 1) - rest.append(name) - else: - assert rest - try: - while len(rest) > 1: - attr = rest.pop() - obj = getattr(obj, attr) - attr = rest[0] - getattr(obj, attr) - except AttributeError: - __tracebackhide__ = True - pytest.fail("object %r has no attribute %r" % (obj, attr)) - return self.setattr(obj, attr, value) + __tracebackhide__ = True - def setattr(self, obj, name, value, raising=True): - """ set attribute ``name`` on ``obj`` to ``value``, by default - raise AttributeEror if the attribute did not exist. + if value is notset: + if not isinstance(target, str): + raise TypeError("use setattr(target, name, value) or " + "setattr(target, value) with target being a dotted " + "import string") + value = name + name, target = derive_importpath(target) - """ - oldval = getattr(obj, name, notset) + oldval = getattr(target, name, notset) if raising and oldval is notset: - raise AttributeError("%r has no attribute %r" %(obj, name)) + raise AttributeError("%r has no attribute %r" %(target, name)) # avoid class descriptors like staticmethod/classmethod - if inspect.isclass(obj): - oldval = obj.__dict__.get(name, notset) - self._setattr.insert(0, (obj, name, oldval)) - setattr(obj, name, value) + if inspect.isclass(target): + oldval = target.__dict__.get(name, notset) + self._setattr.insert(0, (target, name, oldval)) + setattr(target, name, value) - def delattr(self, obj, name, raising=True): - """ delete attribute ``name`` from ``obj``, by default raise - AttributeError it the attribute did not previously exist. """ - if not hasattr(obj, name): + def delattr(self, target, name=notset, raising=True): + """ delete attribute ``name`` from ``target``, by default raise + AttributeError it the attribute did not previously exist. + + If no ``name`` is specified and ``target`` is a string + it will be interpreted as a dotted import path with the + last part being the attribute name. + + If raising is set to false, the attribute is allowed to not + pre-exist. + """ + __tracebackhide__ = True + if name is notset: + if not isinstance(target, str): + raise TypeError("use delattr(target, name) or " + "delattr(target) with target being a dotted " + "import string") + name, target = derive_importpath(target) + + if not hasattr(target, name): if raising: raise AttributeError(name) else: - self._setattr.insert(0, (obj, name, getattr(obj, name, notset))) - delattr(obj, name) + self._setattr.insert(0, (target, name, + getattr(target, name, notset))) + delattr(target, name) def setitem(self, dic, name, value): """ set dictionary entry ``name`` to value. """ diff -r 23278f23f0923076011f554bb875e332fde21125 -r 95a817c87a020ef2c325244d6f596943eaf5d0ae doc/en/monkeypatch.txt --- a/doc/en/monkeypatch.txt +++ b/doc/en/monkeypatch.txt @@ -48,11 +48,28 @@ import pytest @pytest.fixture(autouse=True) def no_requests(monkeypatch): - monkeypatch.replace("requests.session.Session.request", None) + monkeypatch.delattr("requests.session.Session.request") -This autouse fixture will be executed for all test functions and it -will replace the method ``request.session.Session.request`` with the -value None so that any attempts to create http requests will fail. +This autouse fixture will be executed for each test function and it +will delete the method ``request.session.Session.request`` +so that any attempts within tests to create http requests will fail. + +example: setting an attribute on some class +------------------------------------------------------ + +If you need to patch out ``os.getcwd()`` to return an artifical +value:: + + def test_some_interaction(monkeypatch): + monkeypatch.setattr("os.getcwd", lambda: "/") + +which is equivalent to the long form:: + + def test_some_interaction(monkeypatch): + import os + monkeypatch.setattr(os, "getcwd", lambda: "/") + + Method reference of the monkeypatch function argument ----------------------------------------------------- diff -r 23278f23f0923076011f554bb875e332fde21125 -r 95a817c87a020ef2c325244d6f596943eaf5d0ae testing/test_monkeypatch.py --- a/testing/test_monkeypatch.py +++ b/testing/test_monkeypatch.py @@ -35,26 +35,32 @@ monkeypatch.undo() # double-undo makes no modification assert A.x == 5 -class TestReplace: +class TestSetattrWithImportPath: def test_string_expression(self, monkeypatch): - monkeypatch.replace("os.path.abspath", lambda x: "hello2") + monkeypatch.setattr("os.path.abspath", lambda x: "hello2") assert os.path.abspath("123") == "hello2" def test_string_expression_class(self, monkeypatch): - monkeypatch.replace("_pytest.config.Config", 42) + monkeypatch.setattr("_pytest.config.Config", 42) import _pytest assert _pytest.config.Config == 42 def test_wrong_target(self, monkeypatch): - pytest.raises(TypeError, lambda: monkeypatch.replace(None, None)) + pytest.raises(TypeError, lambda: monkeypatch.setattr(None, None)) def test_unknown_import(self, monkeypatch): pytest.raises(pytest.fail.Exception, - lambda: monkeypatch.replace("unkn123.classx", None)) + lambda: monkeypatch.setattr("unkn123.classx", None)) def test_unknown_attr(self, monkeypatch): pytest.raises(pytest.fail.Exception, - lambda: monkeypatch.replace("os.path.qweqwe", None)) + lambda: monkeypatch.setattr("os.path.qweqwe", None)) + + def test_delattr(self, monkeypatch): + monkeypatch.delattr("os.path.abspath") + assert not hasattr(os.path, "abspath") + monkeypatch.undo() + assert os.path.abspath def test_delattr(): class A: @@ -262,3 +268,5 @@ monkeypatch.undo() assert Sample.hello() + + Repository URL: https://bitbucket.org/hpk42/pytest/ -- This is a commit notification from bitbucket.org. You are receiving this because you have the service enabled, addressing the recipient of this email. From commits-noreply at bitbucket.org Fri Sep 27 12:44:04 2013 From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org) Date: Fri, 27 Sep 2013 10:44:04 -0000 Subject: [Pytest-commit] commit/pytest: hpk42: bump version Message-ID: <20130927104404.23932.39831@app12.ash-private.bitbucket.org> 1 new commit in pytest: https://bitbucket.org/hpk42/pytest/commits/25c0386e2968/ Changeset: 25c0386e2968 User: hpk42 Date: 2013-09-27 12:43:49 Summary: bump version Affected #: 2 files diff -r 95a817c87a020ef2c325244d6f596943eaf5d0ae -r 25c0386e2968797cfeafd040517b369149867ce5 _pytest/__init__.py --- a/_pytest/__init__.py +++ b/_pytest/__init__.py @@ -1,2 +1,2 @@ # -__version__ = '2.4.0.dev12' +__version__ = '2.4.0.dev13' diff -r 95a817c87a020ef2c325244d6f596943eaf5d0ae -r 25c0386e2968797cfeafd040517b369149867ce5 setup.py --- a/setup.py +++ b/setup.py @@ -11,7 +11,7 @@ name='pytest', description='py.test: simple powerful testing with Python', long_description = long_description, - version='2.4.0.dev12', + version='2.4.0.dev13', url='http://pytest.org', license='MIT license', platforms=['unix', 'linux', 'osx', 'cygwin', 'win32'], Repository URL: https://bitbucket.org/hpk42/pytest/ -- This is a commit notification from bitbucket.org. You are receiving this because you have the service enabled, addressing the recipient of this email. From commits-noreply at bitbucket.org Fri Sep 27 12:58:35 2013 From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org) Date: Fri, 27 Sep 2013 10:58:35 -0000 Subject: [Pytest-commit] commit/pytest: hpk42: re-order CHANGELOG and group it into "features" and "bugs" and "known incompatibilities" Message-ID: <20130927105835.32104.93107@app04.ash-private.bitbucket.org> 1 new commit in pytest: https://bitbucket.org/hpk42/pytest/commits/05ab108a29e7/ Changeset: 05ab108a29e7 User: hpk42 Date: 2013-09-27 12:58:26 Summary: re-order CHANGELOG and group it into "features" and "bugs" and "known incompatibilities" Affected #: 2 files diff -r 25c0386e2968797cfeafd040517b369149867ce5 -r 05ab108a29e7a9fdfb295b133ad077d622f7a21f CHANGELOG --- a/CHANGELOG +++ b/CHANGELOG @@ -1,22 +1,44 @@ Changes between 2.3.5 and 2.4.DEV ----------------------------------- +known incompatibilities: + +- if calling --genscript from python2.7 or above, you only get a + standalone script which works on python2.7 or above. Use Python2.6 + to also get a python2.5 compatible version. + +- all xunit-style teardown methods (nose-style, pytest-style, + unittest-style) will not be called if the corresponding setup method failed, + see issue322 below. + +new features: + +- experimentally allow fixture functions to "yield" instead of "return" + a fixture value, allowing direct integration with with-context managers + in fixture functions and avoiding registration of finalization callbacks. + Thanks Andreas Pelme, Vladimir Keleshev, Floris Bruynooghe, Ronny Pfannschmidt + and many others for discussions. + +- allow boolean expression directly with skipif/xfail + if a "reason" is also specified. Rework skipping documentation + to recommend "condition as booleans" because it prevents surprises + when importing markers between modules. Specifying conditions + as strings will remain fully supported. + +- reporting: color the last line red or green depending if failures/errors occured + or everything passed. thanks Christian Theunert. + - make "import pdb ; pdb.set_trace()" work natively wrt capturing (no "-s" needed anymore), making ``pytest.set_trace()`` a mere shortcut. -- fix issue333: fix a case of bad unittest/pytest hook interaction. - - fix issue181: --pdb now also works on collect errors (and on internal errors) . This was implemented by a slight internal refactoring and the introduction of a new hook - ``pytest_exception_interact`` hook (see below). + ``pytest_exception_interact`` hook (see next item). - fix issue341: introduce new experimental hook for IDEs/terminals to intercept debugging: ``pytest_exception_interact(node, call, report)``. -- PR27: correctly handle nose.SkipTest during collection. Thanks - Antonio Cuni, Ronny Pfannschmidt. - - new monkeypatch.setattr() variant to provide a shorter invocation for patching out classes/functions from modules: @@ -24,22 +46,41 @@ will replace the "get" function of the "requests" module with ``myfunc``. -- fix issue355: junitxml puts name="pytest" attribute to testsuite tag. - - fix issue322: tearDownClass is not run if setUpClass failed. Thanks Mathieu Agopian for the initial fix. Also make all of pytest/nose finalizer mimick the same generic behaviour: if a setupX exists and fails, - don't run teardownX. This also introduces a new method "node.addfinalizer()" + don't run teardownX. This internally introduces a new method "node.addfinalizer()" helper which can only be called during the setup phase of a node. -- fix issue336: autouse fixture in plugins should work again. +- add support for setUpModule/tearDownModule detection, thanks Brian Okken. -- change to use hyphen-separated long options but keep the old spelling +- integrate tab-completion on options through use of "argcomplete". + Thanks Anthon van der Neut for the PR. + +- change option names to be hyphen-separated long options but keep the old spelling backward compatible. py.test -h will only show the hyphenated version, for example "--collect-only" but "--collectonly" will remain valid as well (for backward-compat reasons). Many thanks to Anthon van der Neut for the implementation and to Hynek Schlawack for pushing us. +- fix issue 308 - allow to mark/xfail/skip individual parameter sets + when parametrizing. Thanks Brianna Laugher. + +Bug fixes: + +- pytest now uses argparse instead of optparse (thanks Anthon) which + means that "argparse" is added as a dependency if installing into python2.6 + environments or below. + +- fix issue333: fix a case of bad unittest/pytest hook interaction. + +- PR27: correctly handle nose.SkipTest during collection. Thanks + Antonio Cuni, Ronny Pfannschmidt. + +- fix issue355: junitxml puts name="pytest" attribute to testsuite tag. + +- fix issue336: autouse fixture in plugins should work again. + - fix issue279: improve object comparisons on assertion failure for standard datatypes and recognise collections.abc. Thanks to Brianna Laugher and Mathieu Agopian. @@ -53,19 +94,9 @@ - fix issue305: ignore any problems when writing pyc files. -- integrate tab-completion on options through use of "argcomplete". - Thanks Anthon van der Neut for the PR. - -- pytest now uses argparse instead of optparse (thanks Anthon) which - means that "argparse" is added as a dependency if installing into python2.6 - environments or below. - - SO-17664702: call fixture finalizers even if the fixture function partially failed (finalizers would not always be called before) -- color the last line red or green depending if failures/errors occured - or everything passed. thanks Christian Theunert. - - fix issue320 - fix class scope for fixtures when mixed with module-level functions. Thanks Anatloy Bubenkoff. @@ -77,17 +108,12 @@ - fix issue323 - sorting of many module-scoped arg parametrizations -- add support for setUpModule/tearDownModule detection, thanks Brian Okken. - - make sessionfinish hooks execute with the same cwd-context as at session start (helps fix plugin behaviour which write output files with relative path such as pytest-cov) - fix issue316 - properly reference collection hooks in docs -- fix issue 308 - allow to mark/xfail/skip individual parameter sets - when parametrizing. Thanks Brianna Laugher. - - simplify parametrize() signature: allow to pass a CSV-separated string to specify argnames. For example: ``pytest.mark.parametrize("input,expected", [(1,2), (2,3)])`` is possible now in addition to the prior ``pytest.mark.parametrize(("input", "expected"), ...)``. @@ -95,15 +121,6 @@ - fix issue 306 - cleanup of -k/-m options to only match markers/test names/keywords respectively. Thanks Wouter van Ackooy. -- (experimental) allow fixture functions to be - implemented as context managers. Thanks Andreas Pelme, - Vladimir Keleshev. - -- (experimental) allow boolean expression directly with skipif/xfail - if a "reason" is also specified. Rework skipping documentation - to recommend "condition as booleans" because it prevents surprises - when importing markers between modules. Specifying conditions - as strings will remain fully supported. - improved doctest counting for doctests in python modules -- files without any doctest items will not show up anymore @@ -124,12 +141,6 @@ - better parametrize error messages, thanks Brianna Laugher -known incompatibilities: - -- if calling --genscript from python2.7 or above, you only get a - standalone script which works on python2.7 or above. Use Python2.6 - to also get a python2.5 compatible version. - Changes between 2.3.4 and 2.3.5 ----------------------------------- diff -r 25c0386e2968797cfeafd040517b369149867ce5 -r 05ab108a29e7a9fdfb295b133ad077d622f7a21f doc/en/skipping.txt --- a/doc/en/skipping.txt +++ b/doc/en/skipping.txt @@ -29,7 +29,7 @@ Marking a test function to be skipped ------------------------------------------- -.. versionadded:: 2.4 +.. versionadded:: 2.0, 2.4 Here is an example of marking a test function to be skipped when run on a Python3.3 interpreter:: Repository URL: https://bitbucket.org/hpk42/pytest/ -- This is a commit notification from bitbucket.org. You are receiving this because you have the service enabled, addressing the recipient of this email. From commits-noreply at bitbucket.org Fri Sep 27 14:16:01 2013 From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org) Date: Fri, 27 Sep 2013 12:16:01 -0000 Subject: [Pytest-commit] commit/pytest: hpk42: is actually a new feature, the syntax: pytest.mark.parametrize("arg1, arg2", ...) Message-ID: <20130927121601.19752.71088@app07.ash-private.bitbucket.org> 1 new commit in pytest: https://bitbucket.org/hpk42/pytest/commits/a9bfa2e8b08d/ Changeset: a9bfa2e8b08d User: hpk42 Date: 2013-09-27 14:15:53 Summary: is actually a new feature, the syntax: pytest.mark.parametrize("arg1,arg2", ...) Affected #: 1 file diff -r 05ab108a29e7a9fdfb295b133ad077d622f7a21f -r a9bfa2e8b08d7db4f2d68a63dcf80441666fe818 CHANGELOG --- a/CHANGELOG +++ b/CHANGELOG @@ -52,6 +52,12 @@ don't run teardownX. This internally introduces a new method "node.addfinalizer()" helper which can only be called during the setup phase of a node. +- simplify pytest.mark.parametrize() signature: allow to pass a CSV-separated string + to specify argnames. For example: + ``pytest.mark.parametrize("input,expected", [(1,2), (2,3)])`` + works as well as the previous: + ``pytest.mark.parametrize(("input", "expected"), ...)``. + - add support for setUpModule/tearDownModule detection, thanks Brian Okken. - integrate tab-completion on options through use of "argcomplete". @@ -114,10 +120,6 @@ - fix issue316 - properly reference collection hooks in docs -- simplify parametrize() signature: allow to pass a CSV-separated string - to specify argnames. For example: ``pytest.mark.parametrize("input,expected", [(1,2), (2,3)])`` is possible now in addition to the prior - ``pytest.mark.parametrize(("input", "expected"), ...)``. - - fix issue 306 - cleanup of -k/-m options to only match markers/test names/keywords respectively. Thanks Wouter van Ackooy. Repository URL: https://bitbucket.org/hpk42/pytest/ -- This is a commit notification from bitbucket.org. You are receiving this because you have the service enabled, addressing the recipient of this email. From commits-noreply at bitbucket.org Fri Sep 27 15:48:10 2013 From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org) Date: Fri, 27 Sep 2013 13:48:10 -0000 Subject: [Pytest-commit] commit/pytest: hpk42: add terminalreporter.section|line methods to print extra information. Message-ID: <20130927134810.31459.85196@app07.ash-private.bitbucket.org> 1 new commit in pytest: https://bitbucket.org/hpk42/pytest/commits/9b4dae68a5a4/ Changeset: 9b4dae68a5a4 User: hpk42 Date: 2013-09-27 15:48:03 Summary: add terminalreporter.section|line methods to print extra information. Affected #: 5 files diff -r a9bfa2e8b08d7db4f2d68a63dcf80441666fe818 -r 9b4dae68a5a41d75bbe4c48b1b5d45806e522717 CHANGELOG --- a/CHANGELOG +++ b/CHANGELOG @@ -142,7 +142,9 @@ - better parametrize error messages, thanks Brianna Laugher - +- pytest_terminal_summary(terminalreporter) hooks can now use + ".section(title)" and ".line(msg)" methods to print extra + information at the end of a test run. Changes between 2.3.4 and 2.3.5 ----------------------------------- diff -r a9bfa2e8b08d7db4f2d68a63dcf80441666fe818 -r 9b4dae68a5a41d75bbe4c48b1b5d45806e522717 _pytest/hookspec.py --- a/_pytest/hookspec.py +++ b/_pytest/hookspec.py @@ -221,7 +221,7 @@ pytest_report_teststatus.firstresult = True def pytest_terminal_summary(terminalreporter): - """ add additional section in terminal summary reporting. """ + """ add additional section in terminal summary reporting. """ # ------------------------------------------------------------------------- # doctest hooks diff -r a9bfa2e8b08d7db4f2d68a63dcf80441666fe818 -r 9b4dae68a5a41d75bbe4c48b1b5d45806e522717 _pytest/terminal.py --- a/_pytest/terminal.py +++ b/_pytest/terminal.py @@ -100,7 +100,7 @@ self.startdir = self.curdir = py.path.local() if file is None: file = py.std.sys.stdout - self._tw = py.io.TerminalWriter(file) + self._tw = self.writer = py.io.TerminalWriter(file) self.currentfspath = None self.reportchars = getreportopt(config) self.hasmarkup = self._tw.hasmarkup @@ -148,6 +148,12 @@ self.ensure_newline() self._tw.sep(sep, title, **markup) + def section(self, title, sep="=", **kw): + self._tw.sep(sep, title, **kw) + + def line(self, msg, **kw): + self._tw.line(msg, **kw) + def pytest_internalerror(self, excrepr): for line in str(excrepr).split("\n"): self.write_line("INTERNALERROR> " + line) @@ -179,6 +185,7 @@ res = self.config.hook.pytest_report_teststatus(report=rep) cat, letter, word = res self.stats.setdefault(cat, []).append(rep) + self._tests_ran = True if not letter and not word: # probably passed setup/teardown return diff -r a9bfa2e8b08d7db4f2d68a63dcf80441666fe818 -r 9b4dae68a5a41d75bbe4c48b1b5d45806e522717 testing/test_mark.py --- a/testing/test_mark.py +++ b/testing/test_mark.py @@ -317,7 +317,7 @@ request.applymarker(pytest.mark.hello) def pytest_terminal_summary(terminalreporter): l = terminalreporter.stats['passed'] - terminalreporter._tw.line("keyword: %s" % l[0].keywords) + terminalreporter.writer.line("keyword: %s" % l[0].keywords) """) testdir.makepyfile(""" def test_func(arg): diff -r a9bfa2e8b08d7db4f2d68a63dcf80441666fe818 -r 9b4dae68a5a41d75bbe4c48b1b5d45806e522717 testing/test_terminal.py --- a/testing/test_terminal.py +++ b/testing/test_terminal.py @@ -699,3 +699,16 @@ result.stdout.fnmatch_lines([ '*File *test_tbstyle_native_setup_error.py", line *, in setup_error_fixture*' ]) + +def test_terminal_summary(testdir): + testdir.makeconftest(""" + def pytest_terminal_summary(terminalreporter): + w = terminalreporter + w.section("hello") + w.line("world") + """) + result = testdir.runpytest() + result.stdout.fnmatch_lines(""" + *==== hello ====* + world + """) Repository URL: https://bitbucket.org/hpk42/pytest/ -- This is a commit notification from bitbucket.org. You are receiving this because you have the service enabled, addressing the recipient of this email. From issues-reply at bitbucket.org Fri Sep 27 19:35:40 2013 From: issues-reply at bitbucket.org (Charles Cloud) Date: Fri, 27 Sep 2013 17:35:40 -0000 Subject: [Pytest-commit] Issue #126: Tox fails with no such option: --pre (hpk42/tox) Message-ID: <20130927173540.3949.41875@app06.ash-private.bitbucket.org> New issue 126: Tox fails with no such option: --pre https://bitbucket.org/hpk42/tox/issue/126/tox-fails-with-no-such-option-pre Charles Cloud: Offending line: https://bitbucket.org/hpk42/tox/src/f7f70c5ce0b49656688bb78f8b4f5a1a8f489d7f/tox/_config.py?at=default#cl-333 This option fails with `pip==1.3.1`. From commits-noreply at bitbucket.org Sat Sep 28 22:30:13 2013 From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org) Date: Sat, 28 Sep 2013 20:30:13 -0000 Subject: [Pytest-commit] commit/pytest: 5 new changesets Message-ID: <20130928203013.24337.23394@app03.ash-private.bitbucket.org> 5 new commits in pytest: https://bitbucket.org/hpk42/pytest/commits/0572f79c5f9c/ Changeset: 0572f79c5f9c User: hpk42 Date: 2013-09-28 09:52:41 Summary: move FILE_OR_DIR constant out Affected #: 2 files diff -r 9b4dae68a5a41d75bbe4c48b1b5d45806e522717 -r 0572f79c5f9c786d1c643352d639c8f7112a8edd _pytest/config.py --- a/_pytest/config.py +++ b/_pytest/config.py @@ -93,7 +93,7 @@ a = option.attrs() arggroup.add_argument(*n, **a) # bash like autocompletion for dirs (appending '/') - optparser.add_argument(Config._file_or_dir, nargs='*' + optparser.add_argument(FILE_OR_DIR, nargs='*' ).completer=filescompleter try_argcomplete(self.optparser) return self.optparser.parse_args([str(x) for x in args]) @@ -102,7 +102,7 @@ parsedoption = self.parse(args) for name, value in parsedoption.__dict__.items(): setattr(option, name, value) - return getattr(parsedoption, Config._file_or_dir) + return getattr(parsedoption, FILE_OR_DIR) def addini(self, name, help, type=None, default=None): """ register an ini-file option. @@ -323,22 +323,9 @@ if arg and arg[0] == '-': msg = py.std.argparse._('unrecognized arguments: %s') self.error(msg % ' '.join(argv)) - getattr(args, Config._file_or_dir).extend(argv) + getattr(args, FILE_OR_DIR).extend(argv) return args -# #pylib 2013-07-31 -# (12:05:53) anthon: hynek: can you get me a list of preferred py.test -# long-options with '-' inserted at the right places? -# (12:08:29) hynek: anthon, hpk: generally I'd love the following, decide -# yourself which you agree and which not: -# (12:10:51) hynek: --exit-on-first --full-trace --junit-xml --junit-prefix -# --result-log --collect-only --conf-cut-dir --trace-config -# --no-magic -# (12:18:21) hpk: hynek,anthon: makes sense to me. -# (13:40:30) hpk: hynek: let's not change names, rather only deal with -# hyphens for now -# (13:40:50) hynek: then --exit-first *shrug* - class DropShorterLongHelpFormatter(py.std.argparse.HelpFormatter): """shorten help for long options that differ only in extra hyphens @@ -504,15 +491,15 @@ def __repr__(self): return "" %(self.__dict__,) +FILE_OR_DIR = 'file_or_dir' class Config(object): """ access to configuration values, pluginmanager and plugin hooks. """ - _file_or_dir = 'file_or_dir' def __init__(self, pluginmanager=None): #: access to command line option as attributes. #: (deprecated), use :py:func:`getoption() <_pytest.config.Config.getoption>` instead self.option = CmdOptions() - _a = self._file_or_dir + _a = FILE_OR_DIR self._parser = Parser( usage="%%(prog)s [options] [%s] [%s] [...]" % (_a, _a), processopt=self._processopt, diff -r 9b4dae68a5a41d75bbe4c48b1b5d45806e522717 -r 0572f79c5f9c786d1c643352d639c8f7112a8edd testing/test_parseopt.py --- a/testing/test_parseopt.py +++ b/testing/test_parseopt.py @@ -95,11 +95,11 @@ parser.addoption("--hello", dest="hello", action="store") args = parser.parse(['--hello', 'world']) assert args.hello == "world" - assert not getattr(args, parseopt.Config._file_or_dir) + assert not getattr(args, parseopt.FILE_OR_DIR) def test_parse2(self, parser): args = parser.parse([py.path.local()]) - assert getattr(args, parseopt.Config._file_or_dir)[0] == py.path.local() + assert getattr(args, parseopt.FILE_OR_DIR)[0] == py.path.local() def test_parse_will_set_default(self, parser): parser.addoption("--hello", dest="hello", default="x", action="store") @@ -128,13 +128,13 @@ parser.addoption("-R", action='store_true') parser.addoption("-S", action='store_false') args = parser.parse(['-R', '4', '2', '-S']) - assert getattr(args, parseopt.Config._file_or_dir) == ['4', '2'] + assert getattr(args, parseopt.FILE_OR_DIR) == ['4', '2'] args = parser.parse(['-R', '-S', '4', '2', '-R']) - assert getattr(args, parseopt.Config._file_or_dir) == ['4', '2'] + assert getattr(args, parseopt.FILE_OR_DIR) == ['4', '2'] assert args.R == True assert args.S == False args = parser.parse(['-R', '4', '-S', '2']) - assert getattr(args, parseopt.Config._file_or_dir) == ['4', '2'] + assert getattr(args, parseopt.FILE_OR_DIR) == ['4', '2'] assert args.R == True assert args.S == False https://bitbucket.org/hpk42/pytest/commits/db80055e00dd/ Changeset: db80055e00dd User: hpk42 Date: 2013-09-28 22:22:53 Summary: simplify Config constructor Affected #: 4 files diff -r 0572f79c5f9c786d1c643352d639c8f7112a8edd -r db80055e00dd94a6f5daf266ccee1deee0aac291 _pytest/config.py --- a/_pytest/config.py +++ b/_pytest/config.py @@ -495,7 +495,7 @@ class Config(object): """ access to configuration values, pluginmanager and plugin hooks. """ - def __init__(self, pluginmanager=None): + def __init__(self, pluginmanager): #: access to command line option as attributes. #: (deprecated), use :py:func:`getoption() <_pytest.config.Config.getoption>` instead self.option = CmdOptions() @@ -505,7 +505,7 @@ processopt=self._processopt, ) #: a pluginmanager instance - self.pluginmanager = pluginmanager or PluginManager(load=True) + self.pluginmanager = pluginmanager self.trace = self.pluginmanager.trace.root.get("config") self._conftest = Conftest(onimport=self._onimportconftest) self.hook = self.pluginmanager.hook @@ -516,7 +516,7 @@ @classmethod def fromdictargs(cls, option_dict, args): """ constructor useable for subprocesses. """ - config = cls() + config = cls(PluginManager(load=True)) # XXX slightly crude way to initialize capturing import _pytest.capture _pytest.capture.pytest_cmdline_parse(config.pluginmanager, args) diff -r 0572f79c5f9c786d1c643352d639c8f7112a8edd -r db80055e00dd94a6f5daf266ccee1deee0aac291 testing/test_config.py --- a/testing/test_config.py +++ b/testing/test_config.py @@ -82,7 +82,7 @@ class TestConfigAPI: def test_config_trace(self, testdir): - config = testdir.Config() + config = testdir.parseconfig() l = [] config.trace.root.setwriter(l.append) config.trace("hello") diff -r 0572f79c5f9c786d1c643352d639c8f7112a8edd -r db80055e00dd94a6f5daf266ccee1deee0aac291 testing/test_core.py --- a/testing/test_core.py +++ b/testing/test_core.py @@ -319,7 +319,7 @@ def pytest_myhook(xyz): return xyz + 1 """) - config = testdir.Config() + config = testdir.Config(PluginManager(load=True)) config._conftest.importconftest(conf) print(config.pluginmanager.getplugins()) res = config.hook.pytest_myhook(xyz=10) diff -r 0572f79c5f9c786d1c643352d639c8f7112a8edd -r db80055e00dd94a6f5daf266ccee1deee0aac291 testing/test_tmpdir.py --- a/testing/test_tmpdir.py +++ b/testing/test_tmpdir.py @@ -36,7 +36,7 @@ class TestTempdirHandler: def test_mktemp(self, testdir): - config = testdir.Config() + config = testdir.parseconfig() config.option.basetemp = testdir.mkdir("hello") t = TempdirHandler(config) tmp = t.mktemp("world") https://bitbucket.org/hpk42/pytest/commits/1e3fe8b9de1a/ Changeset: 1e3fe8b9de1a User: hpk42 Date: 2013-09-28 22:22:55 Summary: refine fromdictargs to avoid an uncessary re-setup of the pluginmanager Affected #: 2 files diff -r db80055e00dd94a6f5daf266ccee1deee0aac291 -r 1e3fe8b9de1ab56da5e4d02e60f82cc6cf8740c3 _pytest/config.py --- a/_pytest/config.py +++ b/_pytest/config.py @@ -516,7 +516,9 @@ @classmethod def fromdictargs(cls, option_dict, args): """ constructor useable for subprocesses. """ - config = cls(PluginManager(load=True)) + from _pytest.core import get_plugin_manager + pluginmanager = get_plugin_manager() + config = cls(pluginmanager) # XXX slightly crude way to initialize capturing import _pytest.capture _pytest.capture.pytest_cmdline_parse(config.pluginmanager, args) diff -r db80055e00dd94a6f5daf266ccee1deee0aac291 -r 1e3fe8b9de1ab56da5e4d02e60f82cc6cf8740c3 _pytest/core.py --- a/_pytest/core.py +++ b/_pytest/core.py @@ -460,8 +460,15 @@ _preinit = [] def _preloadplugins(): + assert not _preinit _preinit.append(PluginManager(load=True)) +def get_plugin_manager(): + if _preinit: + return _preinit.pop(0) + else: # subsequent calls to main will create a fresh instance + return PluginManager(load=True) + def _prepareconfig(args=None, plugins=None): if args is None: args = sys.argv[1:] @@ -471,16 +478,12 @@ if not isinstance(args, str): raise ValueError("not a string or argument list: %r" % (args,)) args = py.std.shlex.split(args) - if _preinit: - _pluginmanager = _preinit.pop(0) - else: # subsequent calls to main will create a fresh instance - _pluginmanager = PluginManager(load=True) - hook = _pluginmanager.hook + pluginmanager = get_plugin_manager() if plugins: for plugin in plugins: - _pluginmanager.register(plugin) - return hook.pytest_cmdline_parse( - pluginmanager=_pluginmanager, args=args) + pluginmanager.register(plugin) + return pluginmanager.hook.pytest_cmdline_parse( + pluginmanager=pluginmanager, args=args) def main(args=None, plugins=None): """ return exit code, after performing an in-process test run. https://bitbucket.org/hpk42/pytest/commits/967c8ec78e69/ Changeset: 967c8ec78e69 User: hpk42 Date: 2013-09-28 22:22:57 Summary: remove very likely unused pytest_plugin_unregister hook (pytest itself and all plugins i know don't use it) Affected #: 3 files diff -r 1e3fe8b9de1ab56da5e4d02e60f82cc6cf8740c3 -r 967c8ec78e6945a30090d98ffc607ba6935b3fc3 CHANGELOG --- a/CHANGELOG +++ b/CHANGELOG @@ -11,6 +11,9 @@ unittest-style) will not be called if the corresponding setup method failed, see issue322 below. +- the pytest_plugin_unregister hook wasn't ever properly called + and there is no known implementation of the hook - so it got removed. + new features: - experimentally allow fixture functions to "yield" instead of "return" diff -r 1e3fe8b9de1ab56da5e4d02e60f82cc6cf8740c3 -r 967c8ec78e6945a30090d98ffc607ba6935b3fc3 _pytest/core.py --- a/_pytest/core.py +++ b/_pytest/core.py @@ -114,7 +114,6 @@ if plugin is None: plugin = self.getplugin(name=name) self._plugins.remove(plugin) - self.hook.pytest_plugin_unregistered(plugin=plugin) for name, value in list(self._name2plugin.items()): if value == plugin: del self._name2plugin[name] diff -r 1e3fe8b9de1ab56da5e4d02e60f82cc6cf8740c3 -r 967c8ec78e6945a30090d98ffc607ba6935b3fc3 _pytest/hookspec.py --- a/_pytest/hookspec.py +++ b/_pytest/hookspec.py @@ -236,10 +236,7 @@ # ------------------------------------------------------------------------- def pytest_plugin_registered(plugin, manager): - """ a new py lib plugin got registered. """ - -def pytest_plugin_unregistered(plugin): - """ a py lib plugin got unregistered. """ + """ a new pytest plugin got registered. """ def pytest_internalerror(excrepr, excinfo): """ called for internal errors. """ https://bitbucket.org/hpk42/pytest/commits/64445644395f/ Changeset: 64445644395f User: hpk42 Date: 2013-09-28 22:23:00 Summary: introduce pluginmanager.ensure_teardown() which allows Affected #: 7 files diff -r 967c8ec78e6945a30090d98ffc607ba6935b3fc3 -r 64445644395fd57f600e0027d78692971ccd44d2 _pytest/capture.py --- a/_pytest/capture.py +++ b/_pytest/capture.py @@ -22,6 +22,13 @@ method = "sys" capman = CaptureManager(method) pluginmanager.register(capman, "capturemanager") + # make sure that capturemanager is properly reset at final shutdown + def teardown(): + try: + capman.reset_capturings() + except ValueError: + pass + pluginmanager.add_shutdown(teardown) def addouterr(rep, outerr): for secname, content in zip(["out", "err"], outerr): @@ -82,6 +89,7 @@ for name, cap in self._method2capture.items(): cap.reset() + def resumecapture_item(self, item): method = self._getmethod(item.config, item.fspath) if not hasattr(item, 'outerr'): diff -r 967c8ec78e6945a30090d98ffc607ba6935b3fc3 -r 64445644395fd57f600e0027d78692971ccd44d2 _pytest/core.py --- a/_pytest/core.py +++ b/_pytest/core.py @@ -80,6 +80,7 @@ self._hints = [] self.trace = TagTracer().get("pluginmanage") self._plugin_distinfo = [] + self._shutdown = [] if os.environ.get('PYTEST_DEBUG'): err = sys.stderr encoding = getattr(err, 'encoding', 'utf8') @@ -118,6 +119,17 @@ if value == plugin: del self._name2plugin[name] + def add_shutdown(self, func): + self._shutdown.append(func) + + def ensure_shutdown(self): + while self._shutdown: + func = self._shutdown.pop() + func() + self._plugins = [] + self._name2plugin.clear() + self._listattrcache.clear() + def isregistered(self, plugin, name=None): if self.getplugin(name) is not None: return True @@ -286,7 +298,7 @@ config = self._config del self._config config.hook.pytest_unconfigure(config=config) - config.pluginmanager.unregister(self) + config.pluginmanager.ensure_shutdown() def notify_exception(self, excinfo, option=None): if option and option.fulltrace: diff -r 967c8ec78e6945a30090d98ffc607ba6935b3fc3 -r 64445644395fd57f600e0027d78692971ccd44d2 _pytest/main.py --- a/_pytest/main.py +++ b/_pytest/main.py @@ -106,6 +106,7 @@ exitstatus=session.exitstatus) if initstate >= 1: config.pluginmanager.do_unconfigure(config) + config.pluginmanager.ensure_shutdown() return session.exitstatus def pytest_cmdline_main(config): diff -r 967c8ec78e6945a30090d98ffc607ba6935b3fc3 -r 64445644395fd57f600e0027d78692971ccd44d2 _pytest/pytester.py --- a/_pytest/pytester.py +++ b/_pytest/pytester.py @@ -83,7 +83,8 @@ def finish_recording(self): for recorder in self._recorders.values(): - self._pluginmanager.unregister(recorder) + if self._pluginmanager.isregistered(recorder): + self._pluginmanager.unregister(recorder) self._recorders.clear() def _makecallparser(self, method): @@ -361,7 +362,7 @@ if not plugins: plugins = [] plugins.append(Collect()) - ret = self.pytestmain(list(args), plugins=plugins) + ret = pytest.main(list(args), plugins=plugins) reprec = rec[0] reprec.ret = ret assert len(rec) == 1 @@ -376,14 +377,15 @@ args.append("--basetemp=%s" % self.tmpdir.dirpath('basetemp')) import _pytest.core config = _pytest.core._prepareconfig(args, self.plugins) - # the in-process pytest invocation needs to avoid leaking FDs - # so we register a "reset_capturings" callmon the capturing manager - # and make sure it gets called - config._cleanup.append( - config.pluginmanager.getplugin("capturemanager").reset_capturings) - import _pytest.config - self.request.addfinalizer( - lambda: _pytest.config.pytest_unconfigure(config)) + # we don't know what the test will do with this half-setup config + # object and thus we make sure it gets unconfigured properly in any + # case (otherwise capturing could still be active, for example) + def ensure_unconfigure(): + if hasattr(config.pluginmanager, "_config"): + config.pluginmanager.do_unconfigure(config) + config.pluginmanager.ensure_shutdown() + + self.request.addfinalizer(ensure_unconfigure) return config def parseconfigure(self, *args): @@ -428,17 +430,6 @@ return py.std.subprocess.Popen(cmdargs, stdout=stdout, stderr=stderr, **kw) - def pytestmain(self, *args, **kwargs): - class ResetCapturing: - @pytest.mark.trylast - def pytest_unconfigure(self, config): - capman = config.pluginmanager.getplugin("capturemanager") - capman.reset_capturings() - plugins = kwargs.setdefault("plugins", []) - rc = ResetCapturing() - plugins.append(rc) - return pytest.main(*args, **kwargs) - def run(self, *cmdargs): return self._run(*cmdargs) diff -r 967c8ec78e6945a30090d98ffc607ba6935b3fc3 -r 64445644395fd57f600e0027d78692971ccd44d2 testing/acceptance_test.py --- a/testing/acceptance_test.py +++ b/testing/acceptance_test.py @@ -391,15 +391,15 @@ def test_equivalence_pytest_pytest(self): assert pytest.main == py.test.cmdline.main - def test_invoke_with_string(self, testdir, capsys): - retcode = testdir.pytestmain("-h") + def test_invoke_with_string(self, capsys): + retcode = pytest.main("-h") assert not retcode out, err = capsys.readouterr() assert "--help" in out pytest.raises(ValueError, lambda: pytest.main(0)) - def test_invoke_with_path(self, testdir, capsys): - retcode = testdir.pytestmain(testdir.tmpdir) + def test_invoke_with_path(self, tmpdir, capsys): + retcode = pytest.main(tmpdir) assert not retcode out, err = capsys.readouterr() @@ -408,7 +408,7 @@ def pytest_addoption(self, parser): parser.addoption("--myopt") - testdir.pytestmain(["-h"], plugins=[MyPlugin()]) + pytest.main(["-h"], plugins=[MyPlugin()]) out, err = capsys.readouterr() assert "--myopt" in out diff -r 967c8ec78e6945a30090d98ffc607ba6935b3fc3 -r 64445644395fd57f600e0027d78692971ccd44d2 testing/test_collection.py --- a/testing/test_collection.py +++ b/testing/test_collection.py @@ -123,7 +123,7 @@ def pytest_collect_file(self, path, parent): wascalled.append(path) testdir.makefile(".abc", "xyz") - testdir.pytestmain([testdir.tmpdir], plugins=[Plugin()]) + pytest.main([testdir.tmpdir], plugins=[Plugin()]) assert len(wascalled) == 1 assert wascalled[0].ext == '.abc' @@ -134,7 +134,7 @@ wascalled.append(path.basename) testdir.mkdir("hello") testdir.mkdir("world") - testdir.pytestmain(testdir.tmpdir, plugins=[Plugin()]) + pytest.main(testdir.tmpdir, plugins=[Plugin()]) assert "hello" in wascalled assert "world" in wascalled diff -r 967c8ec78e6945a30090d98ffc607ba6935b3fc3 -r 64445644395fd57f600e0027d78692971ccd44d2 testing/test_doctest.py --- a/testing/test_doctest.py +++ b/testing/test_doctest.py @@ -4,7 +4,7 @@ import pdb xfail_if_pdbpp_installed = pytest.mark.xfail(hasattr(pdb, "__author__"), - reason="doctest/pdbpp problem: https://bitbucket.org/antocuni/pdb/issue/24/doctests-fail-when-pdbpp-is-installed") + reason="doctest/pdbpp problem: https://bitbucket.org/antocuni/pdb/issue/24/doctests-fail-when-pdbpp-is-installed", run=False) class TestDoctests: Repository URL: https://bitbucket.org/hpk42/pytest/ -- This is a commit notification from bitbucket.org. You are receiving this because you have the service enabled, addressing the recipient of this email. From commits-noreply at bitbucket.org Mon Sep 30 08:36:39 2013 From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org) Date: Mon, 30 Sep 2013 06:36:39 -0000 Subject: [Pytest-commit] commit/pytest: hpk42: avoid creation of file in os.getcwd() cc avanderneut Message-ID: <20130930063639.27633.72763@app04.ash-private.bitbucket.org> 1 new commit in pytest: https://bitbucket.org/hpk42/pytest/commits/1fe40df11278/ Changeset: 1fe40df11278 User: hpk42 Date: 2013-09-30 08:36:31 Summary: avoid creation of file in os.getcwd() cc avanderneut Affected #: 1 file diff -r 64445644395fd57f600e0027d78692971ccd44d2 -r 1fe40df1127840c4aaf946c8dc955c2d7d6e1912 testing/test_parseopt.py --- a/testing/test_parseopt.py +++ b/testing/test_parseopt.py @@ -245,7 +245,7 @@ if not py.path.local.sysfind('bash'): pytest.skip("bash not available") import os - script = os.path.join(os.getcwd(), 'test_argcomplete') + script = str(testdir.tmpdir.join("test_argcomplete")) with open(str(script), 'w') as fp: # redirect output from argcomplete to stdin and stderr is not trivial # http://stackoverflow.com/q/12589419/1307905 Repository URL: https://bitbucket.org/hpk42/pytest/ -- This is a commit notification from bitbucket.org. You are receiving this because you have the service enabled, addressing the recipient of this email. From commits-noreply at bitbucket.org Mon Sep 30 13:14:31 2013 From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org) Date: Mon, 30 Sep 2013 11:14:31 -0000 Subject: [Pytest-commit] commit/pytest: 4 new changesets Message-ID: <20130930111431.18798.10925@app11.ash-private.bitbucket.org> 4 new commits in pytest: https://bitbucket.org/hpk42/pytest/commits/2d3ddee7ba00/ Changeset: 2d3ddee7ba00 User: hpk42 Date: 2013-09-30 10:19:06 Summary: localize some argcomplete-related functionality Affected #: 1 file diff -r 1fe40df1127840c4aaf946c8dc955c2d7d6e1912 -r 2d3ddee7ba00489c9d3da6cd12c4a2ac433e567f _pytest/config.py --- a/_pytest/config.py +++ b/_pytest/config.py @@ -2,15 +2,7 @@ import py import sys, os -from _pytest.core import PluginManager import pytest -from _pytest._argcomplete import try_argcomplete, filescompleter - -# enable after some grace period for plugin writers -TYPE_WARN = False -if TYPE_WARN: - import warnings - def pytest_cmdline_parse(pluginmanager, args): config = Config(pluginmanager) @@ -82,6 +74,7 @@ self._anonymous.addoption(*opts, **attrs) def parse(self, args): + from _pytest._argcomplete import try_argcomplete, filescompleter self.optparser = optparser = MyOptionParser(self) groups = self._groups + [self._anonymous] for group in groups: @@ -142,6 +135,8 @@ 'int': int, 'string': str, } + # enable after some grace period for plugin writers + TYPE_WARN = False def __init__(self, *names, **attrs): """store parms in private vars for use in add_argument""" @@ -149,11 +144,11 @@ self._short_opts = [] self._long_opts = [] self.dest = attrs.get('dest') - if TYPE_WARN: + if self.TYPE_WARN: try: help = attrs['help'] if '%default' in help: - warnings.warn( + py.std.warnings.warn( 'py.test now uses argparse. "%default" should be' ' changed to "%(default)s" ', FutureWarning, @@ -168,8 +163,8 @@ # this might raise a keyerror as well, don't want to catch that if isinstance(typ, str): if typ == 'choice': - if TYPE_WARN: - warnings.warn( + if self.TYPE_WARN: + py.std.warnings.warn( 'type argument to addoption() is a string %r.' ' For parsearg this is optional and when supplied ' ' should be a type.' @@ -180,8 +175,8 @@ # the type of the first element attrs['type'] = type(attrs['choices'][0]) else: - if TYPE_WARN: - warnings.warn( + if self.TYPE_WARN: + py.std.warnings.warn( 'type argument to addoption() is a string %r.' ' For parsearg this should be a type.' ' (options: %s)' % (typ, names), https://bitbucket.org/hpk42/pytest/commits/1a7f73bd9982/ Changeset: 1a7f73bd9982 User: hpk42 Date: 2013-09-30 13:14:14 Summary: shift pytest_configure/unconfigure/addoption/namespace hook calling to config object. The _pytest.config module itself is no longer a plugin but the actual config instance is plugin-registered as ``pytestconfig``. This allows to put most pytest specific logic to _pytest.config instead of in the core pluginmanager. Affected #: 8 files diff -r 2d3ddee7ba00489c9d3da6cd12c4a2ac433e567f -r 1a7f73bd9982815bc9de2c3944171d69aa2658a0 _pytest/config.py --- a/_pytest/config.py +++ b/_pytest/config.py @@ -2,20 +2,6 @@ import py import sys, os -import pytest - -def pytest_cmdline_parse(pluginmanager, args): - config = Config(pluginmanager) - config.parse(args) - return config - -def pytest_unconfigure(config): - while 1: - try: - fin = config._cleanup.pop() - except IndexError: - break - fin() class Parser: """ Parser for command line arguments and ini-file values. """ @@ -507,13 +493,46 @@ self._inicache = {} self._opt2dest = {} self._cleanup = [] + self.pluginmanager.register(self, "pytestconfig") + self._configured = False + + def pytest_plugin_registered(self, plugin): + call_plugin = self.pluginmanager.call_plugin + dic = call_plugin(plugin, "pytest_namespace", {}) or {} + if dic: + import pytest + setns(pytest, dic) + call_plugin(plugin, "pytest_addoption", {'parser': self._parser}) + if self._configured: + call_plugin(plugin, "pytest_configure", {'config': self}) + + def do_configure(self): + assert not self._configured + self._configured = True + self.hook.pytest_configure(config=self) + + def do_unconfigure(self): + assert self._configured + self._configured = False + self.hook.pytest_unconfigure(config=self) + self.pluginmanager.ensure_shutdown() + + def pytest_cmdline_parse(self, pluginmanager, args): + assert self == pluginmanager.config, (self, pluginmanager.config) + self.parse(args) + return self + + def pytest_unconfigure(config): + while config._cleanup: + fin = config._cleanup.pop() + fin() @classmethod def fromdictargs(cls, option_dict, args): """ constructor useable for subprocesses. """ from _pytest.core import get_plugin_manager pluginmanager = get_plugin_manager() - config = cls(pluginmanager) + config = pluginmanager.config # XXX slightly crude way to initialize capturing import _pytest.capture _pytest.capture.pytest_cmdline_parse(config.pluginmanager, args) @@ -572,11 +591,11 @@ self.pluginmanager.consider_setuptools_entrypoints() self.pluginmanager.consider_env() self._setinitialconftest(args) - self.pluginmanager.do_addoption(self._parser) if addopts: self.hook.pytest_cmdline_preparse(config=self, args=args) def _checkversion(self): + import pytest minver = self.inicfg.get('minversion', None) if minver: ver = minver.split(".") @@ -723,3 +742,23 @@ return iniconfig['pytest'] return {} + +def setns(obj, dic): + import pytest + for name, value in dic.items(): + if isinstance(value, dict): + mod = getattr(obj, name, None) + if mod is None: + modname = "pytest.%s" % name + mod = py.std.types.ModuleType(modname) + sys.modules[modname] = mod + mod.__all__ = [] + setattr(obj, name, mod) + obj.__all__.append(name) + setns(mod, value) + else: + setattr(obj, name, value) + obj.__all__.append(name) + #if obj != pytest: + # pytest.__all__.append(name) + setattr(pytest, name, value) diff -r 2d3ddee7ba00489c9d3da6cd12c4a2ac433e567f -r 1a7f73bd9982815bc9de2c3944171d69aa2658a0 _pytest/core.py --- a/_pytest/core.py +++ b/_pytest/core.py @@ -1,17 +1,17 @@ """ pytest PluginManager, basic initialization and tracing. -(c) Holger Krekel 2004-2010 """ import sys, os import inspect import py from _pytest import hookspec # the extension point definitions +from _pytest.config import Config assert py.__version__.split(".")[:2] >= ['1', '4'], ("installation problem: " "%s is too old, remove or upgrade 'py'" % (py.__version__)) default_plugins = ( - "config mark main terminal runner python pdb unittest capture skipping " + "mark main terminal runner python pdb unittest capture skipping " "tmpdir monkeypatch recwarn pastebin helpconfig nose assertion genscript " "junitxml resultlog doctest").split() @@ -91,6 +91,7 @@ self.trace.root.setwriter(err.write) self.hook = HookRelay([hookspec], pm=self) self.register(self) + self.config = Config(self) # XXX unclear if the attr is needed if load: for spec in default_plugins: self.import_plugin(spec) @@ -100,7 +101,8 @@ return name = name or getattr(plugin, '__name__', str(id(plugin))) if self.isregistered(plugin, name): - raise ValueError("Plugin already registered: %s=%s" %(name, plugin)) + raise ValueError("Plugin already registered: %s=%s\n%s" %( + name, plugin, self._name2plugin)) #self.trace("registering", name, plugin) self._name2plugin[name] = plugin self.call_plugin(plugin, "pytest_addhooks", {'pluginmanager': self}) @@ -220,7 +222,6 @@ if self.getplugin(modname) is not None: return try: - #self.trace("importing", modname) mod = importplugin(modname) except KeyboardInterrupt: raise @@ -247,59 +248,12 @@ "trylast: mark a hook implementation function such that the " "plugin machinery will try to call it last/as late as possible.") - def pytest_plugin_registered(self, plugin): - import pytest - dic = self.call_plugin(plugin, "pytest_namespace", {}) or {} - if dic: - self._setns(pytest, dic) - if hasattr(self, '_config'): - self.call_plugin(plugin, "pytest_addoption", - {'parser': self._config._parser}) - self.call_plugin(plugin, "pytest_configure", - {'config': self._config}) - - def _setns(self, obj, dic): - import pytest - for name, value in dic.items(): - if isinstance(value, dict): - mod = getattr(obj, name, None) - if mod is None: - modname = "pytest.%s" % name - mod = py.std.types.ModuleType(modname) - sys.modules[modname] = mod - mod.__all__ = [] - setattr(obj, name, mod) - obj.__all__.append(name) - self._setns(mod, value) - else: - setattr(obj, name, value) - obj.__all__.append(name) - #if obj != pytest: - # pytest.__all__.append(name) - setattr(pytest, name, value) - def pytest_terminal_summary(self, terminalreporter): tw = terminalreporter._tw if terminalreporter.config.option.traceconfig: for hint in self._hints: tw.line("hint: %s" % hint) - def do_addoption(self, parser): - mname = "pytest_addoption" - methods = reversed(self.listattr(mname)) - MultiCall(methods, {'parser': parser}).execute() - - def do_configure(self, config): - assert not hasattr(self, '_config') - self._config = config - config.hook.pytest_configure(config=self._config) - - def do_unconfigure(self, config): - config = self._config - del self._config - config.hook.pytest_unconfigure(config=config) - config.pluginmanager.ensure_shutdown() - def notify_exception(self, excinfo, option=None): if option and option.fulltrace: style = "long" @@ -350,6 +304,7 @@ name = importspec try: mod = "_pytest." + name + #print >>sys.stderr, "tryimport", mod __import__(mod) return sys.modules[mod] except ImportError: @@ -358,6 +313,7 @@ # raise pass # try: + #print >>sys.stderr, "tryimport", importspec __import__(importspec) except ImportError: raise ImportError(importspec) diff -r 2d3ddee7ba00489c9d3da6cd12c4a2ac433e567f -r 1a7f73bd9982815bc9de2c3944171d69aa2658a0 _pytest/helpconfig.py --- a/_pytest/helpconfig.py +++ b/_pytest/helpconfig.py @@ -54,9 +54,9 @@ sys.stderr.write(line + "\n") return 0 elif config.option.help: - config.pluginmanager.do_configure(config) + config.do_configure() showhelp(config) - config.pluginmanager.do_unconfigure(config) + config.do_unconfigure() return 0 def showhelp(config): diff -r 2d3ddee7ba00489c9d3da6cd12c4a2ac433e567f -r 1a7f73bd9982815bc9de2c3944171d69aa2658a0 _pytest/main.py --- a/_pytest/main.py +++ b/_pytest/main.py @@ -76,7 +76,7 @@ initstate = 0 try: try: - config.pluginmanager.do_configure(config) + config.do_configure() initstate = 1 config.hook.pytest_sessionstart(session=session) initstate = 2 @@ -105,7 +105,7 @@ session=session, exitstatus=session.exitstatus) if initstate >= 1: - config.pluginmanager.do_unconfigure(config) + config.do_unconfigure() config.pluginmanager.ensure_shutdown() return session.exitstatus diff -r 2d3ddee7ba00489c9d3da6cd12c4a2ac433e567f -r 1a7f73bd9982815bc9de2c3944171d69aa2658a0 _pytest/mark.py --- a/_pytest/mark.py +++ b/_pytest/mark.py @@ -1,5 +1,5 @@ """ generic mechanism for marking and selecting python functions. """ -import pytest, py +import py def pytest_namespace(): @@ -39,14 +39,14 @@ def pytest_cmdline_main(config): if config.option.markers: - config.pluginmanager.do_configure(config) + config.do_configure() tw = py.io.TerminalWriter() for line in config.getini("markers"): name, rest = line.split(":", 1) tw.write("@pytest.mark.%s:" % name, bold=True) tw.line(rest) tw.line() - config.pluginmanager.do_unconfigure(config) + config.do_unconfigure() return 0 pytest_cmdline_main.tryfirst = True @@ -129,6 +129,7 @@ mapped_names = set() # Add the names of the current item and any parent items + import pytest for item in colitem.listchain(): if not isinstance(item, pytest.Instance): mapped_names.add(item.name) @@ -145,6 +146,7 @@ def pytest_configure(config): + import pytest if config.option.strict: pytest.mark._config = config diff -r 2d3ddee7ba00489c9d3da6cd12c4a2ac433e567f -r 1a7f73bd9982815bc9de2c3944171d69aa2658a0 _pytest/pytester.py --- a/_pytest/pytester.py +++ b/_pytest/pytester.py @@ -390,9 +390,9 @@ def parseconfigure(self, *args): config = self.parseconfig(*args) - config.pluginmanager.do_configure(config) + config.do_configure() self.request.addfinalizer(lambda: - config.pluginmanager.do_unconfigure(config)) + config.do_unconfigure()) return config def getitem(self, source, funcname="test_func"): diff -r 2d3ddee7ba00489c9d3da6cd12c4a2ac433e567f -r 1a7f73bd9982815bc9de2c3944171d69aa2658a0 testing/test_core.py --- a/testing/test_core.py +++ b/testing/test_core.py @@ -261,13 +261,13 @@ assert pm.getplugins() my2 = MyPlugin() pm.register(my2) - assert pm.getplugins()[1:] == [my, my2] + assert pm.getplugins()[2:] == [my, my2] assert pm.isregistered(my) assert pm.isregistered(my2) pm.unregister(my) assert not pm.isregistered(my) - assert pm.getplugins()[1:] == [my2] + assert pm.getplugins()[2:] == [my2] def test_listattr(self): plugins = PluginManager() @@ -319,7 +319,7 @@ def pytest_myhook(xyz): return xyz + 1 """) - config = testdir.Config(PluginManager(load=True)) + config = PluginManager(load=True).config config._conftest.importconftest(conf) print(config.pluginmanager.getplugins()) res = config.hook.pytest_myhook(xyz=10) @@ -383,13 +383,13 @@ config.pluginmanager.register(A()) assert len(l) == 0 - config.pluginmanager.do_configure(config=config) + config.do_configure() assert len(l) == 1 config.pluginmanager.register(A()) # leads to a configured() plugin assert len(l) == 2 assert l[0] != l[1] - config.pluginmanager.do_unconfigure(config=config) + config.do_unconfigure() config.pluginmanager.register(A()) assert len(l) == 2 diff -r 2d3ddee7ba00489c9d3da6cd12c4a2ac433e567f -r 1a7f73bd9982815bc9de2c3944171d69aa2658a0 testing/test_session.py --- a/testing/test_session.py +++ b/testing/test_session.py @@ -208,13 +208,14 @@ testdir.parseconfig("-p", "nqweotexistent") """) #pytest.raises(ImportError, - # "config.pluginmanager.do_configure(config)" + # "config.do_configure(config)" #) def test_plugin_already_exists(testdir): config = testdir.parseconfig("-p", "terminal") assert config.option.plugins == ['terminal'] - config.pluginmanager.do_configure(config) + config.do_configure() + config.do_unconfigure() def test_exclude(testdir): hellodir = testdir.mkdir("hello") https://bitbucket.org/hpk42/pytest/commits/0371f8e21864/ Changeset: 0371f8e21864 User: hpk42 Date: 2013-09-30 13:14:14 Summary: some more separation of core pluginmanager from pytest specific functionality. Idea is to have the PluginManager be re-useable from other projects at some point. Affected #: 9 files diff -r 1a7f73bd9982815bc9de2c3944171d69aa2658a0 -r 0371f8e218643f9c780189ade8f213016dea974d _pytest/config.py --- a/_pytest/config.py +++ b/_pytest/config.py @@ -2,6 +2,87 @@ import py import sys, os +from _pytest import hookspec # the extension point definitions +from _pytest.core import PluginManager + +# pytest startup + +def main(args=None, plugins=None): + """ return exit code, after performing an in-process test run. + + :arg args: list of command line arguments. + + :arg plugins: list of plugin objects to be auto-registered during + initialization. + """ + config = _prepareconfig(args, plugins) + exitstatus = config.hook.pytest_cmdline_main(config=config) + return exitstatus + +class cmdline: # compatibility namespace + main = staticmethod(main) + +class UsageError(Exception): + """ error in py.test usage or invocation""" + +_preinit = [] + +default_plugins = ( + "mark main terminal runner python pdb unittest capture skipping " + "tmpdir monkeypatch recwarn pastebin helpconfig nose assertion genscript " + "junitxml resultlog doctest").split() + +def _preloadplugins(): + assert not _preinit + _preinit.append(get_plugin_manager()) + +def get_plugin_manager(): + if _preinit: + return _preinit.pop(0) + # subsequent calls to main will create a fresh instance + pluginmanager = PytestPluginManager() + pluginmanager.config = config = Config(pluginmanager) # XXX attr needed? + for spec in default_plugins: + pluginmanager.import_plugin(spec) + return pluginmanager + +def _prepareconfig(args=None, plugins=None): + if args is None: + args = sys.argv[1:] + elif isinstance(args, py.path.local): + args = [str(args)] + elif not isinstance(args, (tuple, list)): + if not isinstance(args, str): + raise ValueError("not a string or argument list: %r" % (args,)) + args = py.std.shlex.split(args) + pluginmanager = get_plugin_manager() + if plugins: + for plugin in plugins: + pluginmanager.register(plugin) + return pluginmanager.hook.pytest_cmdline_parse( + pluginmanager=pluginmanager, args=args) + +class PytestPluginManager(PluginManager): + def __init__(self, hookspecs=[hookspec]): + super(PytestPluginManager, self).__init__(hookspecs=hookspecs) + self.register(self) + if os.environ.get('PYTEST_DEBUG'): + err = sys.stderr + encoding = getattr(err, 'encoding', 'utf8') + try: + err = py.io.dupfile(err, encoding=encoding) + except Exception: + pass + self.trace.root.setwriter(err.write) + + def pytest_configure(self, config): + config.addinivalue_line("markers", + "tryfirst: mark a hook implementation function such that the " + "plugin machinery will try to call it first/as early as possible.") + config.addinivalue_line("markers", + "trylast: mark a hook implementation function such that the " + "plugin machinery will try to call it last/as late as possible.") + class Parser: """ Parser for command line arguments and ini-file values. """ @@ -494,10 +575,15 @@ self._opt2dest = {} self._cleanup = [] self.pluginmanager.register(self, "pytestconfig") + self.pluginmanager.set_register_callback(self._register_plugin) self._configured = False - def pytest_plugin_registered(self, plugin): + def _register_plugin(self, plugin, name): call_plugin = self.pluginmanager.call_plugin + call_plugin(plugin, "pytest_addhooks", + {'pluginmanager': self.pluginmanager}) + self.hook.pytest_plugin_registered(plugin=plugin, + manager=self.pluginmanager) dic = call_plugin(plugin, "pytest_namespace", {}) or {} if dic: import pytest @@ -527,10 +613,26 @@ fin = config._cleanup.pop() fin() + def notify_exception(self, excinfo, option=None): + if option and option.fulltrace: + style = "long" + else: + style = "native" + excrepr = excinfo.getrepr(funcargs=True, + showlocals=getattr(option, 'showlocals', False), + style=style, + ) + res = self.hook.pytest_internalerror(excrepr=excrepr, + excinfo=excinfo) + if not py.builtin.any(res): + for line in str(excrepr).split("\n"): + sys.stderr.write("INTERNALERROR> %s\n" %line) + sys.stderr.flush() + + @classmethod def fromdictargs(cls, option_dict, args): """ constructor useable for subprocesses. """ - from _pytest.core import get_plugin_manager pluginmanager = get_plugin_manager() config = pluginmanager.config # XXX slightly crude way to initialize capturing diff -r 1a7f73bd9982815bc9de2c3944171d69aa2658a0 -r 0371f8e218643f9c780189ade8f213016dea974d _pytest/core.py --- a/_pytest/core.py +++ b/_pytest/core.py @@ -4,17 +4,10 @@ import sys, os import inspect import py -from _pytest import hookspec # the extension point definitions -from _pytest.config import Config assert py.__version__.split(".")[:2] >= ['1', '4'], ("installation problem: " "%s is too old, remove or upgrade 'py'" % (py.__version__)) -default_plugins = ( - "mark main terminal runner python pdb unittest capture skipping " - "tmpdir monkeypatch recwarn pastebin helpconfig nose assertion genscript " - "junitxml resultlog doctest").split() - class TagTracer: def __init__(self): self._tag2proc = {} @@ -73,7 +66,7 @@ return self.__class__(self.root, self.tags + (name,)) class PluginManager(object): - def __init__(self, load=False): + def __init__(self, hookspecs=None): self._name2plugin = {} self._listattrcache = {} self._plugins = [] @@ -81,20 +74,11 @@ self.trace = TagTracer().get("pluginmanage") self._plugin_distinfo = [] self._shutdown = [] - if os.environ.get('PYTEST_DEBUG'): - err = sys.stderr - encoding = getattr(err, 'encoding', 'utf8') - try: - err = py.io.dupfile(err, encoding=encoding) - except Exception: - pass - self.trace.root.setwriter(err.write) - self.hook = HookRelay([hookspec], pm=self) - self.register(self) - self.config = Config(self) # XXX unclear if the attr is needed - if load: - for spec in default_plugins: - self.import_plugin(spec) + self.hook = HookRelay(hookspecs or [], pm=self) + + def set_register_callback(self, callback): + assert not hasattr(self, "_registercallback") + self._registercallback = callback def register(self, plugin, name=None, prepend=False): if self._name2plugin.get(name, None) == -1: @@ -105,8 +89,9 @@ name, plugin, self._name2plugin)) #self.trace("registering", name, plugin) self._name2plugin[name] = plugin - self.call_plugin(plugin, "pytest_addhooks", {'pluginmanager': self}) - self.hook.pytest_plugin_registered(manager=self, plugin=plugin) + reg = getattr(self, "_registercallback", None) + if reg is not None: + reg(plugin, name) if not prepend: self._plugins.append(plugin) else: @@ -139,8 +124,8 @@ if plugin == val: return True - def addhooks(self, spec): - self.hook._addhooks(spec, prefix="pytest_") + def addhooks(self, spec, prefix="pytest_"): + self.hook._addhooks(spec, prefix=prefix) def getplugins(self): return list(self._plugins) @@ -240,36 +225,6 @@ self.register(mod, modname) self.consider_module(mod) - def pytest_configure(self, config): - config.addinivalue_line("markers", - "tryfirst: mark a hook implementation function such that the " - "plugin machinery will try to call it first/as early as possible.") - config.addinivalue_line("markers", - "trylast: mark a hook implementation function such that the " - "plugin machinery will try to call it last/as late as possible.") - - def pytest_terminal_summary(self, terminalreporter): - tw = terminalreporter._tw - if terminalreporter.config.option.traceconfig: - for hint in self._hints: - tw.line("hint: %s" % hint) - - def notify_exception(self, excinfo, option=None): - if option and option.fulltrace: - style = "long" - else: - style = "native" - excrepr = excinfo.getrepr(funcargs=True, - showlocals=getattr(option, 'showlocals', False), - style=style, - ) - res = self.hook.pytest_internalerror(excrepr=excrepr, - excinfo=excinfo) - if not py.builtin.any(res): - for line in str(excrepr).split("\n"): - sys.stderr.write("INTERNALERROR> %s\n" %line) - sys.stderr.flush() - def listattr(self, attrname, plugins=None): if plugins is None: plugins = self._plugins @@ -424,46 +379,3 @@ self.trace.root.indent -= 1 return res -_preinit = [] - -def _preloadplugins(): - assert not _preinit - _preinit.append(PluginManager(load=True)) - -def get_plugin_manager(): - if _preinit: - return _preinit.pop(0) - else: # subsequent calls to main will create a fresh instance - return PluginManager(load=True) - -def _prepareconfig(args=None, plugins=None): - if args is None: - args = sys.argv[1:] - elif isinstance(args, py.path.local): - args = [str(args)] - elif not isinstance(args, (tuple, list)): - if not isinstance(args, str): - raise ValueError("not a string or argument list: %r" % (args,)) - args = py.std.shlex.split(args) - pluginmanager = get_plugin_manager() - if plugins: - for plugin in plugins: - pluginmanager.register(plugin) - return pluginmanager.hook.pytest_cmdline_parse( - pluginmanager=pluginmanager, args=args) - -def main(args=None, plugins=None): - """ return exit code, after performing an in-process test run. - - :arg args: list of command line arguments. - - :arg plugins: list of plugin objects to be auto-registered during - initialization. - """ - config = _prepareconfig(args, plugins) - exitstatus = config.hook.pytest_cmdline_main(config=config) - return exitstatus - -class UsageError(Exception): - """ error in py.test usage or invocation""" - diff -r 1a7f73bd9982815bc9de2c3944171d69aa2658a0 -r 0371f8e218643f9c780189ade8f213016dea974d _pytest/main.py --- a/_pytest/main.py +++ b/_pytest/main.py @@ -91,7 +91,7 @@ session.exitstatus = EXIT_INTERRUPTED except: excinfo = py.code.ExceptionInfo() - config.pluginmanager.notify_exception(excinfo, config.option) + config.notify_exception(excinfo, config.option) session.exitstatus = EXIT_INTERNALERROR if excinfo.errisinstance(SystemExit): sys.stderr.write("mainloop: caught Spurious SystemExit!\n") diff -r 1a7f73bd9982815bc9de2c3944171d69aa2658a0 -r 0371f8e218643f9c780189ade8f213016dea974d _pytest/pytester.py --- a/_pytest/pytester.py +++ b/_pytest/pytester.py @@ -375,8 +375,8 @@ break else: args.append("--basetemp=%s" % self.tmpdir.dirpath('basetemp')) - import _pytest.core - config = _pytest.core._prepareconfig(args, self.plugins) + import _pytest.config + config = _pytest.config._prepareconfig(args, self.plugins) # we don't know what the test will do with this half-setup config # object and thus we make sure it gets unconfigured properly in any # case (otherwise capturing could still be active, for example) diff -r 1a7f73bd9982815bc9de2c3944171d69aa2658a0 -r 0371f8e218643f9c780189ade8f213016dea974d _pytest/terminal.py --- a/_pytest/terminal.py +++ b/_pytest/terminal.py @@ -342,6 +342,7 @@ if exitstatus in (0, 1, 2, 4): self.summary_errors() self.summary_failures() + self.summary_hints() self.config.hook.pytest_terminal_summary(terminalreporter=self) if exitstatus == 2: self._report_keyboardinterrupt() @@ -407,6 +408,11 @@ l.append(x) return l + def summary_hints(self): + if self.config.option.traceconfig: + for hint in self.config.pluginmanager._hints: + self._tw.line("hint: %s" % hint) + def summary_failures(self): if self.config.option.tbstyle != "no": reports = self.getreports('failed') diff -r 1a7f73bd9982815bc9de2c3944171d69aa2658a0 -r 0371f8e218643f9c780189ade8f213016dea974d pytest.py --- a/pytest.py +++ b/pytest.py @@ -8,9 +8,11 @@ # we trigger the below "else" condition by the following import import pytest raise SystemExit(pytest.main()) -else: - # we are simply imported - from _pytest.core import main, UsageError, _preloadplugins - from _pytest import core as cmdline - from _pytest import __version__ - _preloadplugins() # to populate pytest.* namespace so help(pytest) works + +# else we are imported + +from _pytest.config import main, UsageError, _preloadplugins, cmdline +from _pytest import __version__ + +_preloadplugins() # to populate pytest.* namespace so help(pytest) works + diff -r 1a7f73bd9982815bc9de2c3944171d69aa2658a0 -r 0371f8e218643f9c780189ade8f213016dea974d testing/test_config.py --- a/testing/test_config.py +++ b/testing/test_config.py @@ -320,3 +320,18 @@ def test_toolongargs_issue224(testdir): result = testdir.runpytest("-m", "hello" * 500) assert result.ret == 0 + +def test_notify_exception(testdir, capfd): + config = testdir.parseconfig() + excinfo = pytest.raises(ValueError, "raise ValueError(1)") + config.notify_exception(excinfo) + out, err = capfd.readouterr() + assert "ValueError" in err + class A: + def pytest_internalerror(self, excrepr): + return True + config.pluginmanager.register(A()) + config.notify_exception(excinfo) + out, err = capfd.readouterr() + assert not err + diff -r 1a7f73bd9982815bc9de2c3944171d69aa2658a0 -r 0371f8e218643f9c780189ade8f213016dea974d testing/test_core.py --- a/testing/test_core.py +++ b/testing/test_core.py @@ -1,6 +1,7 @@ import pytest, py, os from _pytest.core import PluginManager from _pytest.core import MultiCall, HookRelay, varnames +from _pytest.config import get_plugin_manager class TestBootstrapping: @@ -149,7 +150,7 @@ mod = py.std.types.ModuleType("x") mod.pytest_plugins = "pytest_a" aplugin = testdir.makepyfile(pytest_a="#") - pluginmanager = PluginManager() + pluginmanager = get_plugin_manager() reprec = testdir.getreportrecorder(pluginmanager) #syspath.prepend(aplugin.dirpath()) py.std.sys.path.insert(0, str(aplugin.dirpath())) @@ -224,36 +225,21 @@ assert pp.isregistered(mod) def test_register_mismatch_method(self): - pp = PluginManager(load=True) + pp = get_plugin_manager() class hello: def pytest_gurgel(self): pass pytest.raises(Exception, "pp.register(hello())") def test_register_mismatch_arg(self): - pp = PluginManager(load=True) + pp = get_plugin_manager() class hello: def pytest_configure(self, asd): pass excinfo = pytest.raises(Exception, "pp.register(hello())") - - def test_notify_exception(self, capfd): - pp = PluginManager() - excinfo = pytest.raises(ValueError, "raise ValueError(1)") - pp.notify_exception(excinfo) - out, err = capfd.readouterr() - assert "ValueError" in err - class A: - def pytest_internalerror(self, excrepr): - return True - pp.register(A()) - pp.notify_exception(excinfo) - out, err = capfd.readouterr() - assert not err - def test_register(self): - pm = PluginManager(load=False) + pm = get_plugin_manager() class MyPlugin: pass my = MyPlugin() @@ -261,13 +247,13 @@ assert pm.getplugins() my2 = MyPlugin() pm.register(my2) - assert pm.getplugins()[2:] == [my, my2] + assert pm.getplugins()[-2:] == [my, my2] assert pm.isregistered(my) assert pm.isregistered(my2) pm.unregister(my) assert not pm.isregistered(my) - assert pm.getplugins()[2:] == [my2] + assert pm.getplugins()[-1:] == [my2] def test_listattr(self): plugins = PluginManager() @@ -284,7 +270,7 @@ assert l == [41, 42, 43] def test_hook_tracing(self): - pm = PluginManager() + pm = get_plugin_manager() saveindent = [] class api1: x = 41 @@ -319,7 +305,7 @@ def pytest_myhook(xyz): return xyz + 1 """) - config = PluginManager(load=True).config + config = get_plugin_manager().config config._conftest.importconftest(conf) print(config.pluginmanager.getplugins()) res = config.hook.pytest_myhook(xyz=10) diff -r 1a7f73bd9982815bc9de2c3944171d69aa2658a0 -r 0371f8e218643f9c780189ade8f213016dea974d testing/test_pytester.py --- a/testing/test_pytester.py +++ b/testing/test_pytester.py @@ -104,7 +104,7 @@ def test_func(_pytest): class ApiClass: def pytest_xyz(self, arg): "x" - hook = HookRelay([ApiClass], PluginManager(load=False)) + hook = HookRelay([ApiClass], PluginManager()) rec = _pytest.gethookrecorder(hook) class Plugin: def pytest_xyz(self, arg): https://bitbucket.org/hpk42/pytest/commits/cd10ce8eb344/ Changeset: cd10ce8eb344 User: hpk42 Date: 2013-09-30 13:14:16 Summary: fix issue358 -- introduce new pytest_load_initial_conftests hook and make capturing initialization use it, relying on a new (somewhat internal) parser.parse_known_args() method. This also addresses issue359 -- plugins like pytest-django could implement a pytest_load_initial_conftests hook like the capture plugin. Affected #: 7 files diff -r 0371f8e218643f9c780189ade8f213016dea974d -r cd10ce8eb3440922c538b67256cbf92b09a29b6f CHANGELOG --- a/CHANGELOG +++ b/CHANGELOG @@ -75,6 +75,9 @@ - fix issue 308 - allow to mark/xfail/skip individual parameter sets when parametrizing. Thanks Brianna Laugher. +- call new experimental pytest_load_initial_conftests hook to allow + 3rd party plugins to do something before a conftest is loaded. + Bug fixes: - pytest now uses argparse instead of optparse (thanks Anthon) which diff -r 0371f8e218643f9c780189ade8f213016dea974d -r cd10ce8eb3440922c538b67256cbf92b09a29b6f _pytest/capture.py --- a/_pytest/capture.py +++ b/_pytest/capture.py @@ -1,6 +1,7 @@ """ per-test stdout/stderr capturing mechanisms, ``capsys`` and ``capfd`` function arguments. """ import pytest, py +import sys import os def pytest_addoption(parser): @@ -12,23 +13,34 @@ help="shortcut for --capture=no.") @pytest.mark.tryfirst -def pytest_cmdline_parse(pluginmanager, args): - # we want to perform capturing already for plugin/conftest loading - if '-s' in args or "--capture=no" in args: - method = "no" - elif hasattr(os, 'dup') and '--capture=sys' not in args: +def pytest_load_initial_conftests(early_config, parser, args, __multicall__): + ns = parser.parse_known_args(args) + method = ns.capture + if not method: method = "fd" - else: + if method == "fd" and not hasattr(os, "dup"): method = "sys" capman = CaptureManager(method) - pluginmanager.register(capman, "capturemanager") + early_config.pluginmanager.register(capman, "capturemanager") # make sure that capturemanager is properly reset at final shutdown def teardown(): try: capman.reset_capturings() except ValueError: pass - pluginmanager.add_shutdown(teardown) + early_config.pluginmanager.add_shutdown(teardown) + + # finally trigger conftest loading but while capturing (issue93) + capman.resumecapture() + try: + try: + return __multicall__.execute() + finally: + out, err = capman.suspendcapture() + except: + sys.stdout.write(out) + sys.stderr.write(err) + raise def addouterr(rep, outerr): for secname, content in zip(["out", "err"], outerr): @@ -89,7 +101,6 @@ for name, cap in self._method2capture.items(): cap.reset() - def resumecapture_item(self, item): method = self._getmethod(item.config, item.fspath) if not hasattr(item, 'outerr'): diff -r 0371f8e218643f9c780189ade8f213016dea974d -r cd10ce8eb3440922c538b67256cbf92b09a29b6f _pytest/config.py --- a/_pytest/config.py +++ b/_pytest/config.py @@ -141,8 +141,14 @@ self._anonymous.addoption(*opts, **attrs) def parse(self, args): - from _pytest._argcomplete import try_argcomplete, filescompleter - self.optparser = optparser = MyOptionParser(self) + from _pytest._argcomplete import try_argcomplete + self.optparser = self._getparser() + try_argcomplete(self.optparser) + return self.optparser.parse_args([str(x) for x in args]) + + def _getparser(self): + from _pytest._argcomplete import filescompleter + optparser = MyOptionParser(self) groups = self._groups + [self._anonymous] for group in groups: if group.options: @@ -155,8 +161,7 @@ # bash like autocompletion for dirs (appending '/') optparser.add_argument(FILE_OR_DIR, nargs='*' ).completer=filescompleter - try_argcomplete(self.optparser) - return self.optparser.parse_args([str(x) for x in args]) + return optparser def parse_setoption(self, args, option): parsedoption = self.parse(args) @@ -164,6 +169,11 @@ setattr(option, name, value) return getattr(parsedoption, FILE_OR_DIR) + def parse_known_args(self, args): + optparser = self._getparser() + args = [str(x) for x in args] + return optparser.parse_known_args(args)[0] + def addini(self, name, help, type=None, default=None): """ register an ini-file option. @@ -635,9 +645,6 @@ """ constructor useable for subprocesses. """ pluginmanager = get_plugin_manager() config = pluginmanager.config - # XXX slightly crude way to initialize capturing - import _pytest.capture - _pytest.capture.pytest_cmdline_parse(config.pluginmanager, args) config._preparse(args, addopts=False) config.option.__dict__.update(option_dict) for x in config.option.plugins: @@ -663,21 +670,9 @@ plugins += self._conftest.getconftestmodules(fspath) return plugins - def _setinitialconftest(self, args): - # capture output during conftest init (#issue93) - # XXX introduce load_conftest hook to avoid needing to know - # about capturing plugin here - capman = self.pluginmanager.getplugin("capturemanager") - capman.resumecapture() - try: - try: - self._conftest.setinitial(args) - finally: - out, err = capman.suspendcapture() # logging might have got it - except: - sys.stdout.write(out) - sys.stderr.write(err) - raise + def pytest_load_initial_conftests(self, parser, args): + self._conftest.setinitial(args) + pytest_load_initial_conftests.trylast = True def _initini(self, args): self.inicfg = getcfg(args, ["pytest.ini", "tox.ini", "setup.cfg"]) @@ -692,9 +687,8 @@ self.pluginmanager.consider_preparse(args) self.pluginmanager.consider_setuptools_entrypoints() self.pluginmanager.consider_env() - self._setinitialconftest(args) - if addopts: - self.hook.pytest_cmdline_preparse(config=self, args=args) + self.hook.pytest_load_initial_conftests(early_config=self, + args=args, parser=self._parser) def _checkversion(self): import pytest @@ -715,6 +709,8 @@ "can only parse cmdline args at most once per Config object") self._origargs = args self._preparse(args) + # XXX deprecated hook: + self.hook.pytest_cmdline_preparse(config=self, args=args) self._parser.hints.extend(self.pluginmanager._hints) args = self._parser.parse_setoption(args, self.option) if not args: diff -r 0371f8e218643f9c780189ade8f213016dea974d -r cd10ce8eb3440922c538b67256cbf92b09a29b6f _pytest/hookspec.py --- a/_pytest/hookspec.py +++ b/_pytest/hookspec.py @@ -20,7 +20,7 @@ pytest_cmdline_parse.firstresult = True def pytest_cmdline_preparse(config, args): - """modify command line arguments before option parsing. """ + """(deprecated) modify command line arguments before option parsing. """ def pytest_addoption(parser): """register argparse-style options and ini-style config values. @@ -52,6 +52,10 @@ implementation will invoke the configure hooks and runtest_mainloop. """ pytest_cmdline_main.firstresult = True +def pytest_load_initial_conftests(args, early_config, parser): + """ implements loading initial conftests. + """ + def pytest_configure(config): """ called after command line options have been parsed and all plugins and initial conftest files been loaded. diff -r 0371f8e218643f9c780189ade8f213016dea974d -r cd10ce8eb3440922c538b67256cbf92b09a29b6f testing/test_capture.py --- a/testing/test_capture.py +++ b/testing/test_capture.py @@ -484,3 +484,13 @@ result = testdir.runpytest() assert result.ret == 0 assert 'hello19' not in result.stdout.str() + +def test_capture_early_option_parsing(testdir): + testdir.makeconftest(""" + def pytest_runtest_setup(): + print ("hello19") + """) + testdir.makepyfile("def test_func(): pass") + result = testdir.runpytest("-vs") + assert result.ret == 0 + assert 'hello19' in result.stdout.str() diff -r 0371f8e218643f9c780189ade8f213016dea974d -r cd10ce8eb3440922c538b67256cbf92b09a29b6f testing/test_config.py --- a/testing/test_config.py +++ b/testing/test_config.py @@ -335,3 +335,18 @@ out, err = capfd.readouterr() assert not err + +def test_load_initial_conftest_last_ordering(testdir): + from _pytest.config import get_plugin_manager + pm = get_plugin_manager() + class My: + def pytest_load_initial_conftests(self): + pass + m = My() + pm.register(m) + l = pm.listattr("pytest_load_initial_conftests") + assert l[-1].__module__ == "_pytest.capture" + assert l[-2] == m.pytest_load_initial_conftests + assert l[-3].__module__ == "_pytest.config" + + diff -r 0371f8e218643f9c780189ade8f213016dea974d -r cd10ce8eb3440922c538b67256cbf92b09a29b6f testing/test_parseopt.py --- a/testing/test_parseopt.py +++ b/testing/test_parseopt.py @@ -101,6 +101,12 @@ args = parser.parse([py.path.local()]) assert getattr(args, parseopt.FILE_OR_DIR)[0] == py.path.local() + def test_parse_known_args(self, parser): + args = parser.parse_known_args([py.path.local()]) + parser.addoption("--hello", action="store_true") + ns = parser.parse_known_args(["x", "--y", "--hello", "this"]) + assert ns.hello + def test_parse_will_set_default(self, parser): parser.addoption("--hello", dest="hello", default="x", action="store") option = parser.parse([]) Repository URL: https://bitbucket.org/hpk42/pytest/ -- This is a commit notification from bitbucket.org. You are receiving this because you have the service enabled, addressing the recipient of this email. From commits-noreply at bitbucket.org Mon Sep 30 13:23:40 2013 From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org) Date: Mon, 30 Sep 2013 11:23:40 -0000 Subject: [Pytest-commit] commit/pytest: hpk42: add missing entry to CHANGELOG Message-ID: <20130930112340.29787.34864@app13.ash-private.bitbucket.org> 1 new commit in pytest: https://bitbucket.org/hpk42/pytest/commits/7da5c02ece03/ Changeset: 7da5c02ece03 User: hpk42 Date: 2013-09-30 13:23:32 Summary: add missing entry to CHANGELOG Affected #: 1 file diff -r cd10ce8eb3440922c538b67256cbf92b09a29b6f -r 7da5c02ece03592164e4e681ab7463de2a65cedb CHANGELOG --- a/CHANGELOG +++ b/CHANGELOG @@ -80,6 +80,9 @@ Bug fixes: +- fix issue358 - capturing options are now parsed more properly + by using a new parser.parse_known_args method. + - pytest now uses argparse instead of optparse (thanks Anthon) which means that "argparse" is added as a dependency if installing into python2.6 environments or below. Repository URL: https://bitbucket.org/hpk42/pytest/ -- This is a commit notification from bitbucket.org. You are receiving this because you have the service enabled, addressing the recipient of this email. From commits-noreply at bitbucket.org Mon Sep 30 13:57:28 2013 From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org) Date: Mon, 30 Sep 2013 11:57:28 -0000 Subject: [Pytest-commit] commit/pytest: 2 new changesets Message-ID: <20130930115728.24901.30096@app11.ash-private.bitbucket.org> 2 new commits in pytest: https://bitbucket.org/hpk42/pytest/commits/5968d75acef9/ Changeset: 5968d75acef9 User: hpk42 Date: 2013-09-30 13:42:39 Summary: strike keyword argument in favour of new pytest.yield_fixture decorator Affected #: 5 files diff -r 7da5c02ece03592164e4e681ab7463de2a65cedb -r 5968d75acef96230be6fe6b8d0bd656e1628a731 CHANGELOG --- a/CHANGELOG +++ b/CHANGELOG @@ -16,9 +16,12 @@ new features: -- experimentally allow fixture functions to "yield" instead of "return" - a fixture value, allowing direct integration with with-context managers - in fixture functions and avoiding registration of finalization callbacks. +- experimentally introduce a new pytest.yield_fixture decorator which + has exactly the same parameters as pytest.fixture but expects + a ``yield`` statement instead of a ``return statement`` from fixture functions. + This allows direct integration with with-context managers + in fixture functions and generally avoids registering of finalization callbacks + in favour of treating the "after-yield" as teardown code. Thanks Andreas Pelme, Vladimir Keleshev, Floris Bruynooghe, Ronny Pfannschmidt and many others for discussions. diff -r 7da5c02ece03592164e4e681ab7463de2a65cedb -r 5968d75acef96230be6fe6b8d0bd656e1628a731 _pytest/python.py --- a/_pytest/python.py +++ b/_pytest/python.py @@ -40,7 +40,7 @@ return function -def fixture(scope="function", params=None, autouse=False, yieldctx=False): +def fixture(scope="function", params=None, autouse=False): """ (return a) decorator to mark a fixture factory function. This decorator can be used (with or or without parameters) to define @@ -62,16 +62,29 @@ can see it. If False (the default) then an explicit reference is needed to activate the fixture. - :arg yieldctx: if True, the fixture function yields a fixture value. - Code after such a ``yield`` statement is treated as - teardown code. """ if callable(scope) and params is None and autouse == False: # direct decoration return FixtureFunctionMarker( - "function", params, autouse, yieldctx)(scope) + "function", params, autouse)(scope) else: - return FixtureFunctionMarker(scope, params, autouse, yieldctx) + return FixtureFunctionMarker(scope, params, autouse) + +def yield_fixture(scope="function", params=None, autouse=False): + """ (return a) decorator to mark a yield-fixture factory function + (EXPERIMENTAL). + + This takes the same arguments as :py:func:`pytest.fixture` but + expects a fixture function to use a ``yield`` instead of a ``return`` + statement to provide a fixture. See + http://pytest.org/en/latest/yieldfixture.html for more info. + """ + if callable(scope) and params is None and autouse == False: + # direct decoration + return FixtureFunctionMarker( + "function", params, autouse, yieldctx=True)(scope) + else: + return FixtureFunctionMarker(scope, params, autouse, yieldctx=True) defaultfuncargprefixmarker = fixture() @@ -136,6 +149,7 @@ raises.Exception = pytest.fail.Exception return { 'fixture': fixture, + 'yield_fixture': yield_fixture, 'raises' : raises, 'collect': { 'Module': Module, 'Class': Class, 'Instance': Instance, @@ -1675,7 +1689,7 @@ if yieldctx: if not is_generator(fixturefunc): fail_fixturefunc(fixturefunc, - msg="yieldctx=True requires yield statement") + msg="yield_fixture requires yield statement in function") iter = fixturefunc(**kwargs) next = getattr(iter, "__next__", None) if next is None: @@ -1688,7 +1702,7 @@ pass else: fail_fixturefunc(fixturefunc, - "fixture function has more than one 'yield'") + "yield_fixture function has more than one 'yield'") request.addfinalizer(teardown) else: res = fixturefunc(**kwargs) diff -r 7da5c02ece03592164e4e681ab7463de2a65cedb -r 5968d75acef96230be6fe6b8d0bd656e1628a731 doc/en/fixture.txt --- a/doc/en/fixture.txt +++ b/doc/en/fixture.txt @@ -276,8 +276,8 @@ of fixture setup. Note that pytest-2.4 introduced an experimental alternative -:ref:`yield fixture mechanism ` for easier context manager integration -and more linear writing of teardown code. +:ref:`yield fixture mechanism ` for easier context manager +integration and more linear writing of teardown code. .. _`request-context`: diff -r 7da5c02ece03592164e4e681ab7463de2a65cedb -r 5968d75acef96230be6fe6b8d0bd656e1628a731 doc/en/yieldfixture.txt --- a/doc/en/yieldfixture.txt +++ b/doc/en/yieldfixture.txt @@ -1,5 +1,5 @@ -.. _yieldctx: +.. _yieldfixture: Fixture functions using "yield" / context manager integration --------------------------------------------------------------- @@ -16,10 +16,9 @@ "yielding" fixture values is an experimental feature and its exact declaration may change later but earliest in a 2.5 release. You can thus - safely use this feature in the 2.4 series but may need to adapt your - fixtures later. Test functions themselves will not need to change - (they can be completely ignorant of the return/yield modes of - fixture functions). + safely use this feature in the 2.4 series but may need to adapt later. + Test functions themselves will not need to change (as a general + feature, they are ignorant of how fixtures are setup). Let's look at a simple standalone-example using the new ``yield`` syntax:: @@ -27,7 +26,7 @@ import pytest - @pytest.fixture(yieldctx=True) + @pytest.yield_fixture def passwd(): print ("\nsetup before yield") f = open("/etc/passwd") @@ -62,7 +61,7 @@ import pytest - @pytest.fixture(yieldctx=True) + @pytest.yield_fixture def passwd(): with open("/etc/passwd") as f: yield f.readlines() diff -r 7da5c02ece03592164e4e681ab7463de2a65cedb -r 5968d75acef96230be6fe6b8d0bd656e1628a731 testing/python/fixture.py --- a/testing/python/fixture.py +++ b/testing/python/fixture.py @@ -1980,7 +1980,7 @@ def test_simple(self, testdir): testdir.makepyfile(""" import pytest - @pytest.fixture(yieldctx=True) + @pytest.yield_fixture def arg1(): print ("setup") yield 1 @@ -2004,7 +2004,7 @@ def test_scoped(self, testdir): testdir.makepyfile(""" import pytest - @pytest.fixture(scope="module", yieldctx=True) + @pytest.yield_fixture(scope="module") def arg1(): print ("setup") yield 1 @@ -2025,7 +2025,7 @@ def test_setup_exception(self, testdir): testdir.makepyfile(""" import pytest - @pytest.fixture(scope="module", yieldctx=True) + @pytest.yield_fixture(scope="module") def arg1(): pytest.fail("setup") yield 1 @@ -2041,7 +2041,7 @@ def test_teardown_exception(self, testdir): testdir.makepyfile(""" import pytest - @pytest.fixture(scope="module", yieldctx=True) + @pytest.yield_fixture(scope="module") def arg1(): yield 1 pytest.fail("teardown") @@ -2057,7 +2057,7 @@ def test_yields_more_than_one(self, testdir): testdir.makepyfile(""" import pytest - @pytest.fixture(scope="module", yieldctx=True) + @pytest.yield_fixture(scope="module") def arg1(): yield 1 yield 2 @@ -2074,7 +2074,7 @@ def test_no_yield(self, testdir): testdir.makepyfile(""" import pytest - @pytest.fixture(scope="module", yieldctx=True) + @pytest.yield_fixture(scope="module") def arg1(): return 1 def test_1(arg1): @@ -2082,9 +2082,8 @@ """) result = testdir.runpytest("-s") result.stdout.fnmatch_lines(""" - *yieldctx*requires*yield* - *yieldctx=True* + *yield_fixture*requires*yield* + *yield_fixture* *def arg1* """) - https://bitbucket.org/hpk42/pytest/commits/6a3d8793bf5f/ Changeset: 6a3d8793bf5f User: hpk42 Date: 2013-09-30 13:56:54 Summary: disallow yield in non-yield-fixtures for now. This is an incompataibility but we want to prepare for possibly merging fixture and yield_fixture some day. Affected #: 3 files diff -r 5968d75acef96230be6fe6b8d0bd656e1628a731 -r 6a3d8793bf5fe8f9be829bddb8bf1e95ee183323 CHANGELOG --- a/CHANGELOG +++ b/CHANGELOG @@ -14,12 +14,18 @@ - the pytest_plugin_unregister hook wasn't ever properly called and there is no known implementation of the hook - so it got removed. +- pytest.fixture-decorated functions cannot be generators (i.e. use yield) anymore. + This change might be reversed in 2.4.1 if it causes unforeseen real-life issues. + However, you can always write and return an inner function/generator + and change the fixture consumer to iterate over the returned generator. + This change was done in lieu of the new ``pytest.yield_fixture`` decorator, see below. + new features: -- experimentally introduce a new pytest.yield_fixture decorator which - has exactly the same parameters as pytest.fixture but expects +- experimentally introduce a new ``pytest.yield_fixture`` decorator which + accepts exactly the same parameters as pytest.fixture but mandates a ``yield`` statement instead of a ``return statement`` from fixture functions. - This allows direct integration with with-context managers + This allows direct integration with "with-style" context managers in fixture functions and generally avoids registering of finalization callbacks in favour of treating the "after-yield" as teardown code. Thanks Andreas Pelme, Vladimir Keleshev, Floris Bruynooghe, Ronny Pfannschmidt diff -r 5968d75acef96230be6fe6b8d0bd656e1628a731 -r 6a3d8793bf5fe8f9be829bddb8bf1e95ee183323 _pytest/python.py --- a/_pytest/python.py +++ b/_pytest/python.py @@ -1705,6 +1705,11 @@ "yield_fixture function has more than one 'yield'") request.addfinalizer(teardown) else: + if is_generator(fixturefunc): + fail_fixturefunc(fixturefunc, + msg="pytest.fixture functions cannot use ``yield``. " + "Instead write and return an inner function/generator " + "and let the consumer call and iterate over it.") res = fixturefunc(**kwargs) return res diff -r 5968d75acef96230be6fe6b8d0bd656e1628a731 -r 6a3d8793bf5fe8f9be829bddb8bf1e95ee183323 testing/python/fixture.py --- a/testing/python/fixture.py +++ b/testing/python/fixture.py @@ -2087,3 +2087,18 @@ *def arg1* """) + def test_yield_not_allowed_in_non_yield(self, testdir): + testdir.makepyfile(""" + import pytest + @pytest.fixture(scope="module") + def arg1(): + yield 1 + def test_1(arg1): + pass + """) + result = testdir.runpytest("-s") + result.stdout.fnmatch_lines(""" + *fixture*cannot use*yield* + *def arg1* + """) + Repository URL: https://bitbucket.org/hpk42/pytest/ -- This is a commit notification from bitbucket.org. You are receiving this because you have the service enabled, addressing the recipient of this email. From notifications at travis-ci.org Mon Sep 30 16:13:50 2013 From: notifications at travis-ci.org (Travis CI) Date: Mon, 30 Sep 2013 14:13:50 +0000 Subject: [Pytest-commit] [Broken] hpk42/pytest#42 (master - 73e396a) Message-ID: <5249871dd1f60_22a5a1091d9@55042031-63ce-4317-b134-23b6c3813a62.mail> Build Update for hpk42/pytest ------------------------------------- Build: #42 Status: Broken Duration: 4 minutes and 30 seconds Commit: 73e396a (master) Author: holger krekel Message: disallow yield in non-yield-fixtures for now. This is an incompataibility but we want to prepare for possibly merging fixture and yield_fixture some day. View the changeset: https://github.com/hpk42/pytest/compare/fd0572f69d5e...73e396a20804 View the full build log and details: https://travis-ci.org/hpk42/pytest/builds/11963122 -- You can configure recipients for build notifications in your .travis.yml file. See http://about.travis-ci.org/docs/user/build-configuration -------------- next part -------------- An HTML attachment was scrubbed... URL: From commits-noreply at bitbucket.org Mon Sep 30 16:51:45 2013 From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org) Date: Mon, 30 Sep 2013 14:51:45 -0000 Subject: [Pytest-commit] commit/pytest: hpk42: bump version, some windows test fixes, prevent logging from raising exceptions at the end (finally), add py25 to tox.ini. Message-ID: <20130930145145.2583.23190@app10.ash-private.bitbucket.org> 1 new commit in pytest: https://bitbucket.org/hpk42/pytest/commits/cbeecb03918a/ Changeset: cbeecb03918a User: hpk42 Date: 2013-09-30 16:09:26 Summary: bump version, some windows test fixes, prevent logging from raising exceptions at the end (finally), add py25 to tox.ini. Affected #: 7 files diff -r 6a3d8793bf5fe8f9be829bddb8bf1e95ee183323 -r cbeecb03918ab64f80f31af81c65a78799efe443 CHANGELOG --- a/CHANGELOG +++ b/CHANGELOG @@ -14,22 +14,23 @@ - the pytest_plugin_unregister hook wasn't ever properly called and there is no known implementation of the hook - so it got removed. -- pytest.fixture-decorated functions cannot be generators (i.e. use yield) anymore. - This change might be reversed in 2.4.1 if it causes unforeseen real-life issues. - However, you can always write and return an inner function/generator - and change the fixture consumer to iterate over the returned generator. - This change was done in lieu of the new ``pytest.yield_fixture`` decorator, see below. +- pytest.fixture-decorated functions cannot be generators (i.e. use + yield) anymore. This change might be reversed in 2.4.1 if it causes + unforeseen real-life issues. However, you can always write and return + an inner function/generator and change the fixture consumer to iterate + over the returned generator. This change was done in lieu of the new + ``pytest.yield_fixture`` decorator, see below. new features: -- experimentally introduce a new ``pytest.yield_fixture`` decorator which - accepts exactly the same parameters as pytest.fixture but mandates - a ``yield`` statement instead of a ``return statement`` from fixture functions. - This allows direct integration with "with-style" context managers - in fixture functions and generally avoids registering of finalization callbacks - in favour of treating the "after-yield" as teardown code. - Thanks Andreas Pelme, Vladimir Keleshev, Floris Bruynooghe, Ronny Pfannschmidt - and many others for discussions. +- experimentally introduce a new ``pytest.yield_fixture`` decorator + which accepts exactly the same parameters as pytest.fixture but + mandates a ``yield`` statement instead of a ``return statement`` from + fixture functions. This allows direct integration with "with-style" + context managers in fixture functions and generally avoids registering + of finalization callbacks in favour of treating the "after-yield" as + teardown code. Thanks Andreas Pelme, Vladimir Keleshev, Floris + Bruynooghe, Ronny Pfannschmidt and many others for discussions. - allow boolean expression directly with skipif/xfail if a "reason" is also specified. Rework skipping documentation @@ -37,11 +38,12 @@ when importing markers between modules. Specifying conditions as strings will remain fully supported. -- reporting: color the last line red or green depending if failures/errors occured - or everything passed. thanks Christian Theunert. +- reporting: color the last line red or green depending if + failures/errors occured or everything passed. thanks Christian + Theunert. -- make "import pdb ; pdb.set_trace()" work natively wrt capturing (no "-s" needed - anymore), making ``pytest.set_trace()`` a mere shortcut. +- make "import pdb ; pdb.set_trace()" work natively wrt capturing (no + "-s" needed anymore), making ``pytest.set_trace()`` a mere shortcut. - fix issue181: --pdb now also works on collect errors (and on internal errors) . This was implemented by a slight internal @@ -59,13 +61,14 @@ will replace the "get" function of the "requests" module with ``myfunc``. - fix issue322: tearDownClass is not run if setUpClass failed. Thanks - Mathieu Agopian for the initial fix. Also make all of pytest/nose finalizer - mimick the same generic behaviour: if a setupX exists and fails, - don't run teardownX. This internally introduces a new method "node.addfinalizer()" - helper which can only be called during the setup phase of a node. + Mathieu Agopian for the initial fix. Also make all of pytest/nose + finalizer mimick the same generic behaviour: if a setupX exists and + fails, don't run teardownX. This internally introduces a new method + "node.addfinalizer()" helper which can only be called during the setup + phase of a node. -- simplify pytest.mark.parametrize() signature: allow to pass a CSV-separated string - to specify argnames. For example: +- simplify pytest.mark.parametrize() signature: allow to pass a + CSV-separated string to specify argnames. For example: ``pytest.mark.parametrize("input,expected", [(1,2), (2,3)])`` works as well as the previous: ``pytest.mark.parametrize(("input", "expected"), ...)``. @@ -75,11 +78,12 @@ - integrate tab-completion on options through use of "argcomplete". Thanks Anthon van der Neut for the PR. -- change option names to be hyphen-separated long options but keep the old spelling - backward compatible. py.test -h will only show the hyphenated version, - for example "--collect-only" but "--collectonly" will remain valid as well - (for backward-compat reasons). Many thanks to Anthon van der Neut for - the implementation and to Hynek Schlawack for pushing us. +- change option names to be hyphen-separated long options but keep the + old spelling backward compatible. py.test -h will only show the + hyphenated version, for example "--collect-only" but "--collectonly" + will remain valid as well (for backward-compat reasons). Many thanks to + Anthon van der Neut for the implementation and to Hynek Schlawack for + pushing us. - fix issue 308 - allow to mark/xfail/skip individual parameter sets when parametrizing. Thanks Brianna Laugher. @@ -141,7 +145,6 @@ - fix issue 306 - cleanup of -k/-m options to only match markers/test names/keywords respectively. Thanks Wouter van Ackooy. - - improved doctest counting for doctests in python modules -- files without any doctest items will not show up anymore and doctest examples are counted as separate test items. diff -r 6a3d8793bf5fe8f9be829bddb8bf1e95ee183323 -r cbeecb03918ab64f80f31af81c65a78799efe443 _pytest/__init__.py --- a/_pytest/__init__.py +++ b/_pytest/__init__.py @@ -1,2 +1,2 @@ # -__version__ = '2.4.0.dev13' +__version__ = '2.4.0.dev14' diff -r 6a3d8793bf5fe8f9be829bddb8bf1e95ee183323 -r cbeecb03918ab64f80f31af81c65a78799efe443 _pytest/capture.py --- a/_pytest/capture.py +++ b/_pytest/capture.py @@ -29,6 +29,11 @@ except ValueError: pass early_config.pluginmanager.add_shutdown(teardown) + # make sure logging does not raise exceptions if it is imported + def silence_logging_at_shutdown(): + if "logging" in sys.modules: + sys.modules["logging"].raiseExceptions = False + early_config.pluginmanager.add_shutdown(silence_logging_at_shutdown) # finally trigger conftest loading but while capturing (issue93) capman.resumecapture() diff -r 6a3d8793bf5fe8f9be829bddb8bf1e95ee183323 -r cbeecb03918ab64f80f31af81c65a78799efe443 setup.py --- a/setup.py +++ b/setup.py @@ -11,7 +11,7 @@ name='pytest', description='py.test: simple powerful testing with Python', long_description = long_description, - version='2.4.0.dev13', + version='2.4.0.dev14', url='http://pytest.org', license='MIT license', platforms=['unix', 'linux', 'osx', 'cygwin', 'win32'], diff -r 6a3d8793bf5fe8f9be829bddb8bf1e95ee183323 -r cbeecb03918ab64f80f31af81c65a78799efe443 testing/acceptance_test.py --- a/testing/acceptance_test.py +++ b/testing/acceptance_test.py @@ -586,9 +586,9 @@ result = testdir.runpytest("--durations=10") assert result.ret == 0 - result.stdout.fnmatch_lines([ - "*durations*", - "* setup *test_1*", - "* call *test_1*", - ]) + result.stdout.fnmatch_lines_random(""" + *durations* + * setup *test_1* + * call *test_1* + """) diff -r 6a3d8793bf5fe8f9be829bddb8bf1e95ee183323 -r cbeecb03918ab64f80f31af81c65a78799efe443 testing/test_capture.py --- a/testing/test_capture.py +++ b/testing/test_capture.py @@ -437,7 +437,6 @@ ]) assert result.ret == 2 - @pytest.mark.xfail("sys.version_info < (2,7)") @pytest.mark.issue14 def test_capture_and_logging(self, testdir): p = testdir.makepyfile(""" diff -r 6a3d8793bf5fe8f9be829bddb8bf1e95ee183323 -r cbeecb03918ab64f80f31af81c65a78799efe443 tox.ini --- a/tox.ini +++ b/tox.ini @@ -1,6 +1,6 @@ [tox] distshare={homedir}/.tox/distshare -envlist=py26,py27,py27-nobyte,py32,py33,py27-xdist,trial +envlist=py25,py26,py27,py27-nobyte,py32,py33,py27-xdist,trial [testenv] changedir=testing Repository URL: https://bitbucket.org/hpk42/pytest/ -- This is a commit notification from bitbucket.org. You are receiving this because you have the service enabled, addressing the recipient of this email. From notifications at travis-ci.org Mon Sep 30 16:59:26 2013 From: notifications at travis-ci.org (Travis CI) Date: Mon, 30 Sep 2013 14:59:26 +0000 Subject: [Pytest-commit] [Fixed] hpk42/pytest#43 (master - f73dfe1) Message-ID: <524991cdc5440_2294697763@801f665b-2650-4a55-8525-2e97748ef93a.mail> Build Update for hpk42/pytest ------------------------------------- Build: #43 Status: Fixed Duration: 6 minutes and 37 seconds Commit: f73dfe1 (master) Author: holger krekel Message: bump version, some windows test fixes, prevent logging from raising exceptions at the end (finally), add py25 to tox.ini. View the changeset: https://github.com/hpk42/pytest/compare/73e396a20804...f73dfe19ecbc View the full build log and details: https://travis-ci.org/hpk42/pytest/builds/11965089 -- You can configure recipients for build notifications in your .travis.yml file. See http://about.travis-ci.org/docs/user/build-configuration -------------- next part -------------- An HTML attachment was scrubbed... URL: From commits-noreply at bitbucket.org Mon Sep 30 21:39:40 2013 From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org) Date: Mon, 30 Sep 2013 19:39:40 -0000 Subject: [Pytest-commit] commit/pytest: hpk42: fix usage docs for "-k" (addresses issue357) Message-ID: <20130930193940.29005.97788@app12.ash-private.bitbucket.org> 1 new commit in pytest: https://bitbucket.org/hpk42/pytest/commits/b2058c24af7a/ Changeset: b2058c24af7a User: hpk42 Date: 2013-09-30 21:39:32 Summary: fix usage docs for "-k" (addresses issue357) Affected #: 1 file diff -r cbeecb03918ab64f80f31af81c65a78799efe443 -r b2058c24af7a3cc8d64d037abc7622cac4c5d446 doc/en/usage.txt --- a/doc/en/usage.txt +++ b/doc/en/usage.txt @@ -44,8 +44,11 @@ Several test run options:: py.test test_mod.py # run tests in module - py.test somepath # run all tests below path - py.test -k string # only run tests whose names contain a string + py.test somepath # run all tests below somepath + py.test -k stringexpr # only run tests with names that match the + # the "string expression", e.g. "MyClass and not method" + # will select TestMyClass.test_something + # but not TestMyClass.test_method_simple Import 'pkg' and use its filesystem location to find and run tests:: Repository URL: https://bitbucket.org/hpk42/pytest/ -- This is a commit notification from bitbucket.org. You are receiving this because you have the service enabled, addressing the recipient of this email.