From commits-noreply at bitbucket.org Wed Jul 3 11:47:30 2013 From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org) Date: Wed, 03 Jul 2013 09:47:30 -0000 Subject: [Pytest-commit] commit/pytest: hpk42: - add my ep2013 talk to talks page Message-ID: <20130703094730.13674.60247@bitbucket02.managed.contegix.com> 1 new commit in pytest: https://bitbucket.org/hpk42/pytest/commits/a53052043acc/ Changeset: a53052043acc User: hpk42 Date: 2013-07-03 11:47:18 Summary: - add my ep2013 talk to talks page - add "talks/blogs" to the navigation side bar Affected #: 3 files diff -r 4cddadcc82d062982cd37f42f669a332f8ba372f -r a53052043acc302e0e9ded54e30d40809c87fda6 doc/en/_templates/localtoc.html --- a/doc/en/_templates/localtoc.html +++ b/doc/en/_templates/localtoc.html @@ -31,6 +31,8 @@ issues[bb]contact + + Talks/Posts {% extends "basic/localtoc.html" %} diff -r 4cddadcc82d062982cd37f42f669a332f8ba372f -r a53052043acc302e0e9ded54e30d40809c87fda6 doc/en/index.txt --- a/doc/en/index.txt +++ b/doc/en/index.txt @@ -4,9 +4,6 @@ pytest: helps you write better programs ============================================= -.. note:: Upcoming: `professional testing with pytest and tox `_ , 24th-26th June 2013, Leipzig. - - **a mature full-featured Python testing tool** - runs on Posix/Windows, Python 2.4-3.3, PyPy and Jython-2.5.1 diff -r 4cddadcc82d062982cd37f42f669a332f8ba372f -r a53052043acc302e0e9ded54e30d40809c87fda6 doc/en/talks.txt --- a/doc/en/talks.txt +++ b/doc/en/talks.txt @@ -4,8 +4,6 @@ .. _`funcargs`: funcargs.html -.. note:: Upcoming: `professional testing with pytest and tox <`http://www.python-academy.com/courses/specialtopics/python_course_testing.html>`_ , 24th-26th June 2013, Leipzig. - Tutorial examples and blog postings --------------------------------------------- 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 Jul 4 17:20:38 2013 From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org) Date: Thu, 04 Jul 2013 15:20:38 -0000 Subject: [Pytest-commit] commit/pytest: 4 new changesets Message-ID: <20130704152038.27188.52802@bitbucket05.managed.contegix.com> 4 new commits in pytest: https://bitbucket.org/hpk42/pytest/commits/3445cabf1754/ Changeset: 3445cabf1754 User: ctheune Date: 2013-07-03 19:41:05 Summary: Support working in a local virtualenv. Affected #: 1 file diff -r a53052043acc302e0e9ded54e30d40809c87fda6 -r 3445cabf175434fbe8b7a7f452c4418a4fb73095 .hgignore --- a/.hgignore +++ b/.hgignore @@ -4,6 +4,13 @@ .svn .hgsvn +# Ingore local virtualenvs +syntax:glob +lib/ +bin/ +include/ +.Python/ + # These lines are suggested according to the svn:ignore property # Feel free to enable them by uncommenting them syntax:glob https://bitbucket.org/hpk42/pytest/commits/cf1c068a86dc/ Changeset: cf1c068a86dc User: ctheune Date: 2013-07-03 19:43:18 Summary: Compatibility with my spinal cord reflexes: colorize last summary line. Provide a red bar if there are any 'failures'. Otherwise make it green. Affected #: 1 file diff -r 3445cabf175434fbe8b7a7f452c4418a4fb73095 -r cf1c068a86dcf5b61eeafaecc2c04392ac650fa1 _pytest/terminal.py --- a/_pytest/terminal.py +++ b/_pytest/terminal.py @@ -454,10 +454,14 @@ if val: parts.append("%d %s" %(len(val), key)) line = ", ".join(parts) - # XXX coloring msg = "%s in %.2f seconds" %(line, session_duration) if self.verbosity >= 0: - self.write_sep("=", msg, bold=True) + markup = dict(bold=True) + if 'failed' in self.stats: + markup['red'] = True + else: + markup['green'] = True + self.write_sep("=", msg, **markup) #else: # self.write_line(msg, bold=True) https://bitbucket.org/hpk42/pytest/commits/21c0abdd6cd4/ Changeset: 21c0abdd6cd4 Branch: ctheune/typo-1372873724648 User: ctheune Date: 2013-07-03 19:48:57 Summary: Typo Affected #: 1 file diff -r cf1c068a86dcf5b61eeafaecc2c04392ac650fa1 -r 21c0abdd6cd4921cbe73aaa3008eb7a0c5ee7102 .hgignore --- a/.hgignore +++ b/.hgignore @@ -1,10 +1,9 @@ - # Automatically generated by `hgimportsvn` syntax:glob .svn .hgsvn -# Ingore local virtualenvs +# Ignore local virtualenvs syntax:glob lib/ bin/ https://bitbucket.org/hpk42/pytest/commits/29461bb5660a/ Changeset: 29461bb5660a User: ctheune Date: 2013-07-03 19:49:28 Summary: Merged in ctheune/pytest-greenbar-1/ctheune/typo-1372873724648 (pull request #1) Typo Affected #: 1 file diff -r cf1c068a86dcf5b61eeafaecc2c04392ac650fa1 -r 29461bb5660a9c38aed9a8762401afa8f9188149 .hgignore --- a/.hgignore +++ b/.hgignore @@ -1,10 +1,9 @@ - # Automatically generated by `hgimportsvn` syntax:glob .svn .hgsvn -# Ingore local virtualenvs +# Ignore local virtualenvs syntax:glob lib/ bin/ 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 Jul 6 15:51:15 2013 From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org) Date: Sat, 06 Jul 2013 13:51:15 -0000 Subject: [Pytest-commit] commit/pytest: 2 new changesets Message-ID: <20130706135115.4335.81121@bitbucket20.managed.contegix.com> 2 new commits in pytest: https://bitbucket.org/hpk42/pytest/commits/8e8ca29faf60/ Changeset: 8e8ca29faf60 Branch: travis-integration User: bubenkoff Date: 2013-07-06 14:23:02 Summary: add travis integration, fixes for py25 and py27 no pyc tox env Affected #: 4 files diff -r 9d60521c1e64ed81ba7bd40a241e7a631282cc7e -r 8e8ca29faf60b46b46c4c1ffd0808f77a80218d2 .gitignore --- /dev/null +++ b/.gitignore @@ -0,0 +1,32 @@ +# Automatically generated by `hgimportsvn` +.svn +.hgsvn + +# Ignore local virtualenvs +lib/ +bin/ +include/ +.Python/ + +# These lines are suggested according to the svn:ignore property +# Feel free to enable them by uncommenting them +*.pyc +*.pyo +*.swp +*.html +*.class +*.orig +*~ + +doc/*/_build +build/ +dist/ +*.egg-info +issue/ +env/ +3rdparty/ +.tox +.cache +.coverage +.ropeproject + diff -r 9d60521c1e64ed81ba7bd40a241e7a631282cc7e -r 8e8ca29faf60b46b46c4c1ffd0808f77a80218d2 .travis.yml --- /dev/null +++ b/.travis.yml @@ -0,0 +1,10 @@ +language: python +# command to install dependencies +install: "pip install -e . detox" +# # command to run tests +script: detox --recreate +notifications: + irc: + - "chat.freenode.net#pylib" + email: + - pytest-commit at python.org diff -r 9d60521c1e64ed81ba7bd40a241e7a631282cc7e -r 8e8ca29faf60b46b46c4c1ffd0808f77a80218d2 README.rst --- a/README.rst +++ b/README.rst @@ -16,6 +16,9 @@ - many `external plugins `_. +.. image:: https://secure.travis-ci.org/bubenkoff/pytest.png?branch=travis-integration + :target: http://travis-ci.org/bubenkoff/pytest + A simple example for a test:: # content of test_module.py diff -r 9d60521c1e64ed81ba7bd40a241e7a631282cc7e -r 8e8ca29faf60b46b46c4c1ffd0808f77a80218d2 tox.ini --- a/tox.ini +++ b/tox.ini @@ -13,6 +13,10 @@ changedir=. commands= py.test --genscript=pytest1 +[testenv:py25] +setenv = + PIP_INSECURE=1 + [testenv:py27-xdist] changedir=. basepython=python2.7 @@ -29,6 +33,7 @@ deps=pytest-xdist setenv= PYTHONDONTWRITEBYTECODE=1 +distribute=true commands= py.test -n3 -rfsxX \ --junitxml={envlogdir}/junit-{envname}.xml {posargs:testing} https://bitbucket.org/hpk42/pytest/commits/c3a0a8895e55/ Changeset: c3a0a8895e55 User: hpk42 Date: 2013-07-06 15:51:14 Summary: Merged in bubenkoff/pytest/travis-integration (pull request #41) add travis integration, fixes for py25 and py27 no pyc tox env Affected #: 4 files diff -r 9d60521c1e64ed81ba7bd40a241e7a631282cc7e -r c3a0a8895e55c89070142e3849959464d08514b6 .gitignore --- /dev/null +++ b/.gitignore @@ -0,0 +1,32 @@ +# Automatically generated by `hgimportsvn` +.svn +.hgsvn + +# Ignore local virtualenvs +lib/ +bin/ +include/ +.Python/ + +# These lines are suggested according to the svn:ignore property +# Feel free to enable them by uncommenting them +*.pyc +*.pyo +*.swp +*.html +*.class +*.orig +*~ + +doc/*/_build +build/ +dist/ +*.egg-info +issue/ +env/ +3rdparty/ +.tox +.cache +.coverage +.ropeproject + diff -r 9d60521c1e64ed81ba7bd40a241e7a631282cc7e -r c3a0a8895e55c89070142e3849959464d08514b6 .travis.yml --- /dev/null +++ b/.travis.yml @@ -0,0 +1,10 @@ +language: python +# command to install dependencies +install: "pip install -e . detox" +# # command to run tests +script: detox --recreate +notifications: + irc: + - "chat.freenode.net#pylib" + email: + - pytest-commit at python.org diff -r 9d60521c1e64ed81ba7bd40a241e7a631282cc7e -r c3a0a8895e55c89070142e3849959464d08514b6 README.rst --- a/README.rst +++ b/README.rst @@ -16,6 +16,9 @@ - many `external plugins `_. +.. image:: https://secure.travis-ci.org/bubenkoff/pytest.png?branch=travis-integration + :target: http://travis-ci.org/bubenkoff/pytest + A simple example for a test:: # content of test_module.py diff -r 9d60521c1e64ed81ba7bd40a241e7a631282cc7e -r c3a0a8895e55c89070142e3849959464d08514b6 tox.ini --- a/tox.ini +++ b/tox.ini @@ -13,6 +13,10 @@ changedir=. commands= py.test --genscript=pytest1 +[testenv:py25] +setenv = + PIP_INSECURE=1 + [testenv:py27-xdist] changedir=. basepython=python2.7 @@ -29,6 +33,7 @@ deps=pytest-xdist setenv= PYTHONDONTWRITEBYTECODE=1 +distribute=true commands= py.test -n3 -rfsxX \ --junitxml={envlogdir}/junit-{envname}.xml {posargs: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 commits-noreply at bitbucket.org Sat Jul 6 11:04:30 2013 From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org) Date: Sat, 06 Jul 2013 09:04:30 -0000 Subject: [Pytest-commit] commit/pytest: 4 new changesets Message-ID: <20130706090430.12145.36142@bitbucket01.managed.contegix.com> 4 new commits in pytest: https://bitbucket.org/hpk42/pytest/commits/25983ac76a78/ Changeset: 25983ac76a78 User: flub Date: 2013-04-18 13:24:53 Summary: Load conftest files in the correct order initially When the conftest.py files are looked for intially they got loaded starting from the subdir ending at the parent dir(s). Later on during collection any conftest.py files are loaded starting from the parent dir ending at the subdir. Due to how extending fixtures works the latter is correct as otherwise the wrong fixture will be available. So this changes the initial conftest loading to start at the root and go towards the subdir. This does also affect the order of other hooks, hence the order of the reporting being different in testing/test_terminal.py. Affected #: 4 files diff -r fdc28ac2029f1c0be1dac4991c2f1b014c39a03f -r 25983ac76a7887522684bca6a9173d23e5adc30c _pytest/config.py --- a/_pytest/config.py +++ b/_pytest/config.py @@ -196,27 +196,20 @@ self.getconftestmodules(x) def getconftestmodules(self, path): - """ return a list of imported conftest modules for the given path. """ try: clist = self._path2confmods[path] except KeyError: if path is None: - raise ValueError("missing default confest.") - dp = path.dirpath() + raise ValueError("missing default conftest.") clist = [] - if dp != path: - cutdir = self._confcutdir - if cutdir and path != cutdir and not path.relto(cutdir): - pass - else: - conftestpath = path.join("conftest.py") - if conftestpath.check(file=1): - clist.append(self.importconftest(conftestpath)) - clist[:0] = self.getconftestmodules(dp) + for parent in path.parts(): + if self._confcutdir and self._confcutdir.relto(parent): + continue + conftestpath = parent.join("conftest.py") + if conftestpath.check(file=1): + clist.append(self.importconftest(conftestpath)) self._path2confmods[path] = clist - # be defensive: avoid changes from caller side to - # affect us by always returning a copy of the actual list - return clist[:] + return clist def rget(self, name, path=None): mod, value = self.rget_with_confmod(name, path) diff -r fdc28ac2029f1c0be1dac4991c2f1b014c39a03f -r 25983ac76a7887522684bca6a9173d23e5adc30c testing/python/fixture.py --- a/testing/python/fixture.py +++ b/testing/python/fixture.py @@ -102,6 +102,77 @@ "*2 passed*" ]) + def test_extend_fixture_module_class(self, testdir): + testfile = testdir.makepyfile(""" + import pytest + + @pytest.fixture + def spam(): + return 'spam' + + class TestSpam: + + @pytest.fixture + def spam(self, spam): + return spam * 2 + + def test_spam(self, spam): + assert spam == 'spamspam' + """) + result = testdir.runpytest() + result.stdout.fnmatch_lines(["*1 passed*"]) + result = testdir.runpytest(testfile) + result.stdout.fnmatch_lines(["*1 passed*"]) + + def test_extend_fixture_conftest_module(self, testdir): + testdir.makeconftest(""" + import pytest + + @pytest.fixture + def spam(): + return 'spam' + """) + testfile = testdir.makepyfile(""" + import pytest + + @pytest.fixture + def spam(spam): + return spam * 2 + + def test_spam(spam): + assert spam == 'spamspam' + """) + result = testdir.runpytest() + result.stdout.fnmatch_lines(["*1 passed*"]) + result = testdir.runpytest(testfile) + result.stdout.fnmatch_lines(["*1 passed*"]) + + def test_extend_fixture_conftest_conftest(self, testdir): + testdir.makeconftest(""" + import pytest + + @pytest.fixture + def spam(): + return 'spam' + """) + pkg = testdir.mkpydir("pkg") + pkg.join("conftest.py").write(py.code.Source(""" + import pytest + + @pytest.fixture + def spam(spam): + return spam * 2 + """)) + testfile = pkg.join("test_spam.py") + testfile.write(py.code.Source(""" + def test_spam(spam): + assert spam == "spamspam" + """)) + result = testdir.runpytest() + result.stdout.fnmatch_lines(["*1 passed*"]) + result = testdir.runpytest(testfile) + result.stdout.fnmatch_lines(["*1 passed*"]) + def test_funcarg_lookup_error(self, testdir): p = testdir.makepyfile(""" def test_lookup_error(unknown): diff -r fdc28ac2029f1c0be1dac4991c2f1b014c39a03f -r 25983ac76a7887522684bca6a9173d23e5adc30c testing/test_conftest.py --- a/testing/test_conftest.py +++ b/testing/test_conftest.py @@ -204,3 +204,15 @@ """)) result = testdir.runpytest("-h", "--confcutdir=%s" % x, x) result.stdout.fnmatch_lines(["*--xyz*"]) + +def test_conftest_import_order(testdir, monkeypatch): + ct1 = testdir.makeconftest("") + sub = testdir.mkdir("sub") + ct2 = sub.join("conftest.py") + ct2.write("") + def impct(p): + print p + return p + conftest = Conftest() + monkeypatch.setattr(conftest, 'importconftest', impct) + assert conftest.getconftestmodules(sub) == [ct1, ct2] diff -r fdc28ac2029f1c0be1dac4991c2f1b014c39a03f -r 25983ac76a7887522684bca6a9173d23e5adc30c testing/test_terminal.py --- a/testing/test_terminal.py +++ b/testing/test_terminal.py @@ -643,9 +643,9 @@ """) result = testdir.runpytest("a") result.stdout.fnmatch_lines([ + "*hello: 42*", "line1", str(testdir.tmpdir), - "*hello: 42*", ]) @pytest.mark.xfail("not hasattr(os, 'dup')") https://bitbucket.org/hpk42/pytest/commits/32065167d928/ Changeset: 32065167d928 User: bubenkoff Date: 2013-07-06 10:06:12 Summary: merge with upstream Affected #: 60 files diff -r 25983ac76a7887522684bca6a9173d23e5adc30c -r 32065167d92882d1c3cd1c152c5a769f52e67784 .hgignore --- a/.hgignore +++ b/.hgignore @@ -1,9 +1,15 @@ - # Automatically generated by `hgimportsvn` syntax:glob .svn .hgsvn +# Ignore local virtualenvs +syntax:glob +lib/ +bin/ +include/ +.Python/ + # These lines are suggested according to the svn:ignore property # Feel free to enable them by uncommenting them syntax:glob @@ -25,3 +31,4 @@ .tox .cache .coverage +.ropeproject diff -r 25983ac76a7887522684bca6a9173d23e5adc30c -r 32065167d92882d1c3cd1c152c5a769f52e67784 .hgtags --- a/.hgtags +++ b/.hgtags @@ -54,3 +54,9 @@ 8738b828dec53937765db71951ef955cca4c51f6 2.3.2 7fe44182c434f8ac89149a3c340479872a5d5ccb 2.3.3 ef299e57f24218dbdd949498d7e660723636bcc3 2.3.4 +fc3a793e87ec907000a47ea0d3a372a2fe218c0a 2.3.5 +b93ac0cdae02effaa3c136a681cc45bba757fe46 1.4.14 +b93ac0cdae02effaa3c136a681cc45bba757fe46 1.4.14 +0000000000000000000000000000000000000000 1.4.14 +0000000000000000000000000000000000000000 1.4.14 +0000000000000000000000000000000000000000 1.4.14 diff -r 25983ac76a7887522684bca6a9173d23e5adc30c -r 32065167d92882d1c3cd1c152c5a769f52e67784 AUTHORS --- a/AUTHORS +++ b/AUTHORS @@ -6,9 +6,14 @@ Ronny Pfannschmidt Benjamin Peterson Floris Bruynooghe +Jason R. Coombs +Wouter van Ackooy Samuele Pedroni +Brianna Laugher Carl Friedrich Bolz Armin Rigo +Maho +Jaap Broekhuizen Maciek Fijalkowski Guido Wesdorp Brian Dorsey @@ -25,3 +30,4 @@ Daniel Nuri Graham Horler Andreas Zeidler +Brian Okken diff -r 25983ac76a7887522684bca6a9173d23e5adc30c -r 32065167d92882d1c3cd1c152c5a769f52e67784 CHANGELOG --- a/CHANGELOG +++ b/CHANGELOG @@ -1,6 +1,61 @@ -Changes between 2.3.4 and 2.3.5dev +Changes between 2.3.5 and 2.4.DEV ----------------------------------- +- 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"), ...)``. + +- 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 + and doctest examples are counted as separate test items. + thanks Danilo Bellini. + +- fix issue245 by depending on the released py-1.4.14 + which fixes py.io.dupfile to work with files with no + mode. Thanks Jason R. Coombs. + +- fix junitxml generation when test output contains control characters, + addressing issue267, thanks Jaap Broekhuizen + +- honor --tb style for setup/teardown errors as well. Thanks Maho. + +- fix issue307 - use yaml.safe_load in example, thanks Mark Eichin. + + +Changes between 2.3.4 and 2.3.5 +----------------------------------- + +- never consider a fixture function for test function collection + +- allow re-running of test items / helps to fix pytest-reruntests plugin + and also help to keep less fixture/resource references alive + - put captured stdout/stderr into junitxml output even for passing tests (thanks Adam Goucher) diff -r 25983ac76a7887522684bca6a9173d23e5adc30c -r 32065167d92882d1c3cd1c152c5a769f52e67784 _pytest/__init__.py --- a/_pytest/__init__.py +++ b/_pytest/__init__.py @@ -1,2 +1,2 @@ # -__version__ = '2.3.5.dev8' +__version__ = '2.4.0.dev5' diff -r 25983ac76a7887522684bca6a9173d23e5adc30c -r 32065167d92882d1c3cd1c152c5a769f52e67784 _pytest/assertion/rewrite.py --- a/_pytest/assertion/rewrite.py +++ b/_pytest/assertion/rewrite.py @@ -177,6 +177,10 @@ # This happens when we get a EEXIST in find_module creating the # __pycache__ directory and __pycache__ is by some non-dir node. return False + elif err == errno.EACCES: + # The directory is read-only; this can happen for example when + # running the tests in a package installed as root + return False raise try: fp.write(imp.get_magic()) @@ -215,11 +219,17 @@ if (not source.startswith(BOM_UTF8) and (not cookie_re.match(source[0:end1]) or not cookie_re.match(source[end1:end2]))): + if hasattr(state, "_indecode"): + return None # encodings imported us again, we don't rewrite + state._indecode = True try: - source.decode("ascii") - except UnicodeDecodeError: - # Let it fail in real import. - return None + try: + source.decode("ascii") + except UnicodeDecodeError: + # Let it fail in real import. + return None + finally: + del state._indecode # On Python versions which are not 2.7 and less than or equal to 3.1, the # parser expects *nix newlines. if REWRITE_NEWLINES: diff -r 25983ac76a7887522684bca6a9173d23e5adc30c -r 32065167d92882d1c3cd1c152c5a769f52e67784 _pytest/assertion/util.py --- a/_pytest/assertion/util.py +++ b/_pytest/assertion/util.py @@ -10,6 +10,7 @@ # DebugInterpreter. _reprcompare = None + def format_explanation(explanation): """This formats an explanation @@ -85,7 +86,7 @@ def assertrepr_compare(config, op, left, right): """Return specialised explanations for some operators/operands""" - width = 80 - 15 - len(op) - 2 # 15 chars indentation, 1 space around op + width = 80 - 15 - len(op) - 2 # 15 chars indentation, 1 space around op left_repr = py.io.saferepr(left, maxsize=int(width/2)) right_repr = py.io.saferepr(right, maxsize=width-len(left_repr)) summary = '%s %s %s' % (left_repr, op, right_repr) @@ -93,7 +94,7 @@ issequence = lambda x: isinstance(x, (list, tuple)) istext = lambda x: isinstance(x, basestring) isdict = lambda x: isinstance(x, dict) - isset = lambda x: isinstance(x, set) + isset = lambda x: isinstance(x, (set, frozenset)) verbose = config.getoption('verbose') explanation = None @@ -114,9 +115,9 @@ raise except: excinfo = py.code.ExceptionInfo() - explanation = ['(pytest_assertion plugin: representation of ' - 'details failed. Probably an object has a faulty __repr__.)', - str(excinfo)] + explanation = [ + '(pytest_assertion plugin: representation of details failed. ' + 'Probably an object has a faulty __repr__.)', str(excinfo)] if not explanation: return None @@ -132,7 +133,7 @@ """ explanation = [] if not verbose: - i = 0 # just in case left or right has zero length + i = 0 # just in case left or right has zero length for i in range(min(len(left), len(right))): if left[i] != right[i]: break @@ -166,13 +167,15 @@ (i, left[i], right[i])] break if len(left) > len(right): - explanation += ['Left contains more items, ' - 'first extra item: %s' % py.io.saferepr(left[len(right)],)] + explanation += [ + 'Left contains more items, first extra item: %s' % + py.io.saferepr(left[len(right)],)] elif len(left) < len(right): - explanation += ['Right contains more items, ' - 'first extra item: %s' % py.io.saferepr(right[len(left)],)] - return explanation # + _diff_text(py.std.pprint.pformat(left), - # py.std.pprint.pformat(right)) + explanation += [ + 'Right contains more items, first extra item: %s' % + py.io.saferepr(right[len(left)],)] + return explanation # + _diff_text(py.std.pprint.pformat(left), + # py.std.pprint.pformat(right)) def _compare_eq_set(left, right, verbose=False): @@ -210,12 +213,12 @@ if extra_left: explanation.append('Left contains more items:') explanation.extend(py.std.pprint.pformat( - dict((k, left[k]) for k in extra_left)).splitlines()) + dict((k, left[k]) for k in extra_left)).splitlines()) extra_right = set(right) - set(left) if extra_right: explanation.append('Right contains more items:') explanation.extend(py.std.pprint.pformat( - dict((k, right[k]) for k in extra_right)).splitlines()) + dict((k, right[k]) for k in extra_right)).splitlines()) return explanation diff -r 25983ac76a7887522684bca6a9173d23e5adc30c -r 32065167d92882d1c3cd1c152c5a769f52e67784 _pytest/doctest.py --- a/_pytest/doctest.py +++ b/_pytest/doctest.py @@ -34,6 +34,14 @@ self.reprlocation.toterminal(tw) class DoctestItem(pytest.Item): + def __init__(self, name, parent, runner=None, dtest=None): + super(DoctestItem, self).__init__(name, parent) + self.runner = runner + self.dtest = dtest + + def runtest(self): + self.runner.run(self.dtest) + def repr_failure(self, excinfo): doctest = py.std.doctest if excinfo.errisinstance((doctest.DocTestFailure, @@ -76,7 +84,7 @@ return super(DoctestItem, self).repr_failure(excinfo) def reportinfo(self): - return self.fspath, None, "[doctest]" + return self.fspath, None, "[doctest] %s" % self.name class DoctestTextfile(DoctestItem, pytest.File): def runtest(self): @@ -91,8 +99,8 @@ extraglobs=dict(getfixture=fixture_request.getfuncargvalue), raise_on_error=True, verbose=0) -class DoctestModule(DoctestItem, pytest.File): - def runtest(self): +class DoctestModule(pytest.File): + def collect(self): doctest = py.std.doctest if self.fspath.basename == "conftest.py": module = self.config._conftest.importconftest(self.fspath) @@ -102,7 +110,11 @@ self.funcargs = {} self._fixtureinfo = FuncFixtureInfo((), [], {}) fixture_request = FixtureRequest(self) - failed, tot = doctest.testmod( - module, raise_on_error=True, verbose=0, - extraglobs=dict(getfixture=fixture_request.getfuncargvalue), - optionflags=doctest.ELLIPSIS) + doctest_globals = dict(getfixture=fixture_request.getfuncargvalue) + # uses internal doctest module parsing mechanism + finder = doctest.DocTestFinder() + runner = doctest.DebugRunner(verbose=0, optionflags=doctest.ELLIPSIS) + for test in finder.find(module, module.__name__, + extraglobs=doctest_globals): + if test.examples: # skip empty doctests + yield DoctestItem(test.name, self, runner, test) diff -r 25983ac76a7887522684bca6a9173d23e5adc30c -r 32065167d92882d1c3cd1c152c5a769f52e67784 _pytest/junitxml.py --- a/_pytest/junitxml.py +++ b/_pytest/junitxml.py @@ -36,7 +36,8 @@ # | [#x10000-#x10FFFF] _legal_chars = (0x09, 0x0A, 0x0d) _legal_ranges = ( - (0x20, 0xD7FF), + (0x20, 0x7E), + (0x80, 0xD7FF), (0xE000, 0xFFFD), (0x10000, 0x10FFFF), ) @@ -103,7 +104,7 @@ classnames.insert(0, self.prefix) self.tests.append(Junit.testcase( classname=".".join(classnames), - name=names[-1], + name=bin_xml_escape(names[-1]), time=getattr(report, 'duration', 0) )) diff -r 25983ac76a7887522684bca6a9173d23e5adc30c -r 32065167d92882d1c3cd1c152c5a769f52e67784 _pytest/main.py --- a/_pytest/main.py +++ b/_pytest/main.py @@ -97,6 +97,7 @@ if session._testsfailed: session.exitstatus = EXIT_TESTSFAILED finally: + session.startdir.chdir() if initstate >= 2: config.hook.pytest_sessionfinish( session=session, @@ -216,6 +217,9 @@ #: keywords/markers collected from all scopes self.keywords = NodeKeywords(self) + #: allow adding of extra keywords to use for matching + self.extra_keyword_matches = set() + #self.extrainit() @property @@ -307,6 +311,14 @@ chain.reverse() return chain + def listextrakeywords(self): + """ Return a set of all extra keywords in self and any parents.""" + extra_keywords = set() + item = self + for item in self.listchain(): + extra_keywords.update(item.extra_keyword_matches) + return extra_keywords + def listnames(self): return [x.name for x in self.listchain()] @@ -441,6 +453,7 @@ self.shouldstop = False self.trace = config.trace.root.get("collection") self._norecursepatterns = config.getini("norecursedirs") + self.startdir = py.path.local() def pytest_collectstart(self): if self.shouldstop: diff -r 25983ac76a7887522684bca6a9173d23e5adc30c -r 32065167d92882d1c3cd1c152c5a769f52e67784 _pytest/mark.py --- a/_pytest/mark.py +++ b/_pytest/mark.py @@ -1,44 +1,56 @@ """ generic mechanism for marking and selecting python functions. """ import pytest, py + def pytest_namespace(): return {'mark': MarkGenerator()} + def pytest_addoption(parser): group = parser.getgroup("general") - group._addoption('-k', + group._addoption( + '-k', action="store", dest="keyword", default='', metavar="EXPRESSION", help="only run tests which match the given substring expression. " "An expression is a python evaluatable expression " "where all names are substring-matched against test names " - "and keywords. Example: -k 'test_method or test_other' " - "matches all test functions whose name contains " - "'test_method' or 'test_other'.") + "and their parent classes. Example: -k 'test_method or test " + "other' matches all test functions and classes whose name " + "contains 'test_method' or 'test_other'. " + "Additionally keywords are matched to classes and functions " + "containing extra names in their 'extra_keyword_matches' set, " + "as well as functions which have names assigned directly to them." + ) - group._addoption("-m", + group._addoption( + "-m", action="store", dest="markexpr", default="", metavar="MARKEXPR", help="only run tests matching given mark expression. " "example: -m 'mark1 and not mark2'." - ) + ) - group.addoption("--markers", action="store_true", help= - "show markers (builtin, plugin and per-project ones).") + group.addoption( + "--markers", action="store_true", + help="show markers (builtin, plugin and per-project ones)." + ) parser.addini("markers", "markers for test functions", 'linelist') + def pytest_cmdline_main(config): if config.option.markers: config.pluginmanager.do_configure(config) tw = py.io.TerminalWriter() for line in config.getini("markers"): name, rest = line.split(":", 1) - tw.write("@pytest.mark.%s:" % name, bold=True) + tw.write("@pytest.mark.%s:" % name, bold=True) tw.line(rest) tw.line() config.pluginmanager.do_unconfigure(config) return 0 pytest_cmdline_main.tryfirst = True + def pytest_collection_modifyitems(items, config): keywordexpr = config.option.keyword matchexpr = config.option.markexpr @@ -67,32 +79,76 @@ config.hook.pytest_deselected(items=deselected) items[:] = remaining -class BoolDict: - def __init__(self, mydict): - self._mydict = mydict - def __getitem__(self, name): - return name in self._mydict -class SubstringDict: - def __init__(self, mydict): - self._mydict = mydict - def __getitem__(self, name): - for key in self._mydict: - if name in key: +class MarkMapping: + """Provides a local mapping for markers. + Only the marker names from the given :class:`NodeKeywords` will be mapped, + so the names are taken only from :class:`MarkInfo` or + :class:`MarkDecorator` items. + """ + def __init__(self, keywords): + mymarks = set() + for key, value in keywords.items(): + if isinstance(value, MarkInfo) or isinstance(value, MarkDecorator): + mymarks.add(key) + self._mymarks = mymarks + + def __getitem__(self, markname): + return markname in self._mymarks + + +class KeywordMapping: + """Provides a local mapping for keywords. + Given a list of names, map any substring of one of these names to True. + """ + def __init__(self, names): + self._names = names + + def __getitem__(self, subname): + for name in self._names: + if subname in name: return True return False -def matchmark(colitem, matchexpr): - return eval(matchexpr, {}, BoolDict(colitem.keywords)) + +def matchmark(colitem, markexpr): + """Tries to match on any marker names, attached to the given colitem.""" + return eval(markexpr, {}, MarkMapping(colitem.keywords)) + def matchkeyword(colitem, keywordexpr): + """Tries to match given keyword expression to given collector item. + + Will match on the name of colitem, including the names of its parents. + Only matches names of items which are either a :class:`Class` or a + :class:`Function`. + Additionally, matches on names in the 'extra_keyword_matches' set of + any item, as well as names directly assigned to test functions. + """ keywordexpr = keywordexpr.replace("-", "not ") - return eval(keywordexpr, {}, SubstringDict(colitem.keywords)) + mapped_names = set() + + # Add the names of the current item and any parent items + for item in colitem.listchain(): + if not isinstance(item, pytest.Instance): + mapped_names.add(item.name) + + # Add the names added as extra keywords to current or parent items + for name in colitem.listextrakeywords(): + mapped_names.add(name) + + # Add the names attached to the current function through direct assignment + for name in colitem.function.__dict__: + mapped_names.add(name) + + return eval(keywordexpr, {}, KeywordMapping(mapped_names)) + def pytest_configure(config): if config.option.strict: pytest.mark._config = config + class MarkGenerator: """ Factory for :class:`MarkDecorator` objects - exposed as a ``py.test.mark`` singleton instance. Example:: @@ -126,6 +182,7 @@ if name not in self._markers: raise AttributeError("%r not a registered marker" % (name,)) + class MarkDecorator: """ A decorator for test functions and test classes. When applied it will create :class:`MarkInfo` objects which may be @@ -149,7 +206,7 @@ def __repr__(self): d = self.__dict__.copy() name = d.pop('markname') - return "" %(name, d) + return "" % (name, d) def __call__(self, *args, **kwargs): """ if passed a single callable argument: decorate it with mark info. @@ -162,15 +219,17 @@ if hasattr(func, 'pytestmark'): l = func.pytestmark if not isinstance(l, list): - func.pytestmark = [l, self] + func.pytestmark = [l, self] else: - l.append(self) + l.append(self) else: - func.pytestmark = [self] + func.pytestmark = [self] else: holder = getattr(func, self.markname, None) if holder is None: - holder = MarkInfo(self.markname, self.args, self.kwargs) + holder = MarkInfo( + self.markname, self.args, self.kwargs + ) setattr(func, self.markname, holder) else: holder.add(self.args, self.kwargs) @@ -180,6 +239,7 @@ args = self.args + args return self.__class__(self.markname, args=args, kwargs=kw) + class MarkInfo: """ Marking object created by :class:`MarkDecorator` instances. """ def __init__(self, name, args, kwargs): @@ -193,7 +253,8 @@ def __repr__(self): return "" % ( - self.name, self.args, self.kwargs) + self.name, self.args, self.kwargs + ) def add(self, args, kwargs): """ add a MarkInfo with the given args and kwargs. """ @@ -205,4 +266,3 @@ """ yield MarkInfo objects each relating to a marking-call. """ for args, kwargs in self._arglist: yield MarkInfo(self.name, args, kwargs) - diff -r 25983ac76a7887522684bca6a9173d23e5adc30c -r 32065167d92882d1c3cd1c152c5a769f52e67784 _pytest/python.py --- a/_pytest/python.py +++ b/_pytest/python.py @@ -4,6 +4,7 @@ import sys import pytest from _pytest.main import getfslineno +from _pytest.mark import MarkDecorator, MarkInfo from _pytest.monkeypatch import monkeypatch from py._code.code import TerminalRepr @@ -177,7 +178,8 @@ if collector.classnamefilter(name): Class = collector._getcustomclass("Class") return Class(name, parent=collector) - elif collector.funcnamefilter(name) and hasattr(obj, '__call__'): + elif collector.funcnamefilter(name) and hasattr(obj, '__call__') and \ + getfixturemarker(obj) is None: if is_generator(obj): return Generator(name, parent=collector) else: @@ -369,7 +371,9 @@ return mod def setup(self): - setup_module = xunitsetup(self.obj, "setup_module") + setup_module = xunitsetup(self.obj, "setUpModule") + if setup_module is None: + setup_module = xunitsetup(self.obj, "setup_module") if setup_module is not None: #XXX: nose compat hack, move to nose plugin # if it takes a positional arg, its probably a pytest style one @@ -380,7 +384,9 @@ setup_module() def teardown(self): - teardown_module = xunitsetup(self.obj, 'teardown_module') + teardown_module = xunitsetup(self.obj, 'tearDownModule') + if teardown_module is None: + teardown_module = xunitsetup(self.obj, 'teardown_module') if teardown_module is not None: #XXX: nose compat hack, move to nose plugin # if it takes a positional arg, its probably a py.test style one @@ -564,11 +570,13 @@ self._globalid_args = set() self._globalparam = _notexists self._arg2scopenum = {} # used for sorting parametrized resources + self.keywords = {} def copy(self, metafunc): cs = CallSpec2(self.metafunc) cs.funcargs.update(self.funcargs) cs.params.update(self.params) + cs.keywords.update(self.keywords) cs._arg2scopenum.update(self._arg2scopenum) cs._idlist = list(self._idlist) cs._globalid = self._globalid @@ -592,7 +600,7 @@ def id(self): return "-".join(map(str, filter(None, self._idlist))) - def setmulti(self, valtype, argnames, valset, id, scopenum=0): + def setmulti(self, valtype, argnames, valset, id, keywords, scopenum=0): for arg,val in zip(argnames, valset): self._checkargnotcontained(arg) getattr(self, valtype)[arg] = val @@ -604,6 +612,7 @@ if val is _notexists: self._emptyparamspecified = True self._idlist.append(id) + self.keywords.update(keywords) def setall(self, funcargs, id, param): for x in funcargs: @@ -644,13 +653,15 @@ during the collection phase. If you need to setup expensive resources see about setting indirect=True to do it rather at test setup time. - :arg argnames: an argument name or a list of argument names + :arg argnames: a comma-separated string denoting one or more argument + names, or a list/tuple of argument strings. - :arg argvalues: The list of argvalues determines how often a test is invoked - with different argument values. If only one argname was specified argvalues - is a list of simple values. If N argnames were specified, argvalues must - be a list of N-tuples, where each tuple-element specifies a value for its - respective argname. + :arg argvalues: The list of argvalues determines how often a + test is invoked with different argument values. If only one + argname was specified argvalues is a list of simple values. If N + argnames were specified, argvalues must be a list of N-tuples, + where each tuple-element specifies a value for its respective + argname. :arg indirect: if True each argvalue corresponding to an argname will be passed as request.param to its respective argname fixture @@ -666,8 +677,24 @@ It will also override any fixture-function defined scope, allowing to set a dynamic scope using test context or configuration. """ + + # individual parametrized argument sets can be wrapped in a + # marker in which case we unwrap the values and apply the mark + # at Function init + newkeywords = {} + unwrapped_argvalues = [] + for i, argval in enumerate(argvalues): + if isinstance(argval, MarkDecorator): + newmark = MarkDecorator(argval.markname, + argval.args[:-1], argval.kwargs) + newkeywords[i] = {newmark.markname: newmark} + argval = argval.args[-1] + unwrapped_argvalues.append(argval) + argvalues = unwrapped_argvalues + if not isinstance(argnames, (tuple, list)): - argnames = (argnames,) + argnames = [x.strip() for x in argnames.split(",") if x.strip()] + if len(argnames) == 1: argvalues = [(val,) for val in argvalues] if not argvalues: argvalues = [(_notexists,) * len(argnames)] @@ -690,7 +717,7 @@ assert len(valset) == len(argnames) newcallspec = callspec.copy(self) newcallspec.setmulti(valtype, argnames, valset, ids[i], - scopenum) + newkeywords.get(i, {}), scopenum) newcalls.append(newcallspec) self._calls = newcalls @@ -907,6 +934,9 @@ for name, val in (py.builtin._getfuncdict(self.obj) or {}).items(): self.keywords[name] = val + if callspec: + for name, val in callspec.keywords.items(): + self.keywords[name] = val if keywords: for name, val in keywords.items(): self.keywords[name] = val @@ -917,20 +947,25 @@ self.cls, funcargs=not isyield) self.fixturenames = fi.names_closure - if isyield: - assert not callspec, ( + if callspec is not None: + self.callspec = callspec + self._initrequest() + + def _initrequest(self): + if self._isyieldedfunction(): + assert not hasattr(self, "callspec"), ( "yielded functions (deprecated) cannot have funcargs") self.funcargs = {} else: - if callspec is not None: - self.callspec = callspec - self.funcargs = callspec.funcargs or {} + if hasattr(self, "callspec"): + callspec = self.callspec + self.funcargs = callspec.funcargs.copy() self._genid = callspec.id if hasattr(callspec, "param"): self.param = callspec.param else: self.funcargs = {} - self._request = req = FixtureRequest(self) + self._request = FixtureRequest(self) @property def function(self): @@ -1561,15 +1596,7 @@ continue # fixture functions have a pytest_funcarg__ prefix (pre-2.3 style) # or are "@pytest.fixture" marked - try: - marker = obj._pytestfixturefunction - except KeyboardInterrupt: - raise - except Exception: - # some objects raise errors like request (from flask import request) - # we don't expect them to be fixture functions - marker = None - + marker = getfixturemarker(obj) if marker is None: if not name.startswith(self._argprefix): continue @@ -1615,6 +1642,29 @@ except ValueError: pass +def call_fixture_func(fixturefunc, request, kwargs): + if is_generator(fixturefunc): + iter = fixturefunc(**kwargs) + next = getattr(iter, "__next__", None) + if next is None: + next = getattr(iter, "next") + res = next() + def teardown(): + try: + next() + 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) + request.addfinalizer(teardown) + else: + res = fixturefunc(**kwargs) + return res + class FixtureDef: """ A container for a factory definition. """ def __init__(self, fixturemanager, baseid, argname, func, scope, params, @@ -1665,7 +1715,7 @@ fixturefunc = fixturefunc.__get__(request.instance) except AttributeError: pass - result = fixturefunc(**kwargs) + result = call_fixture_func(fixturefunc, request, kwargs) assert not hasattr(self, "cached_result") self.cached_result = result return result @@ -1697,29 +1747,39 @@ def parametrize_sorted(items, ignore, cache, scopenum): if scopenum >= 3: return items - newitems = [] - olditems = [] + + # we pick the first item which has a arg/param combo in the + # requested scope and sort other items with the same combo + # into "newitems" which then is a list of all items using this + # arg/param. + + similar_items = [] + other_items = [] slicing_argparam = None + slicing_index = 0 for item in items: argparamlist = getfuncargparams(item, ignore, scopenum, cache) if slicing_argparam is None and argparamlist: slicing_argparam = argparamlist[0] - slicing_index = len(olditems) + slicing_index = len(other_items) if slicing_argparam in argparamlist: - newitems.append(item) + similar_items.append(item) else: - olditems.append(item) - if newitems: + other_items.append(item) + + if (len(similar_items) + slicing_index) > 1: newignore = ignore.copy() newignore.add(slicing_argparam) - newitems = parametrize_sorted(newitems + olditems[slicing_index:], - newignore, cache, scopenum) - old1 = parametrize_sorted(olditems[:slicing_index], newignore, - cache, scopenum+1) - return old1 + newitems + part2 = parametrize_sorted( + similar_items + other_items[slicing_index:], + newignore, cache, scopenum) + part1 = parametrize_sorted( + other_items[:slicing_index], newignore, + cache, scopenum+1) + return part1 + part2 else: - olditems = parametrize_sorted(olditems, ignore, cache, scopenum+1) - return olditems + newitems + other_items = parametrize_sorted(other_items, ignore, cache, scopenum+1) + return other_items + similar_items def getfuncargparams(item, ignore, scopenum, cache): """ return list of (arg,param) tuple, sorted by broader scope first. """ @@ -1766,6 +1826,18 @@ def xunitsetup(obj, name): meth = getattr(obj, name, None) - if meth is not None: - if not hasattr(meth, "_pytestfixturefunction"): - return meth + if getfixturemarker(meth) is None: + return meth + +def getfixturemarker(obj): + """ return fixturemarker or None if it doesn't exist or raised + exceptions.""" + try: + return getattr(obj, "_pytestfixturefunction", None) + except KeyboardInterrupt: + raise + except Exception: + # some objects raise errors like request (from flask import request) + # we don't expect them to be fixture functions + return None + diff -r 25983ac76a7887522684bca6a9173d23e5adc30c -r 32065167d92882d1c3cd1c152c5a769f52e67784 _pytest/runner.py --- a/_pytest/runner.py +++ b/_pytest/runner.py @@ -63,12 +63,20 @@ return True def runtestprotocol(item, log=True, nextitem=None): + hasrequest = hasattr(item, "_request") + if hasrequest and not item._request: + item._initrequest() rep = call_and_report(item, "setup", log) reports = [rep] if rep.passed: reports.append(call_and_report(item, "call", log)) reports.append(call_and_report(item, "teardown", log, nextitem=nextitem)) + # after all teardown hooks have been called + # want funcargs and request info to go away + if hasrequest: + item._request = False + item.funcargs = None return reports def pytest_runtest_setup(item): @@ -190,7 +198,8 @@ if call.when == "call": longrepr = item.repr_failure(excinfo) else: # exception in setup or teardown - longrepr = item._repr_failure_py(excinfo) + longrepr = item._repr_failure_py(excinfo, + style=item.config.option.tbstyle) return TestReport(item.nodeid, item.location, keywords, outcome, longrepr, when, duration=duration) diff -r 25983ac76a7887522684bca6a9173d23e5adc30c -r 32065167d92882d1c3cd1c152c5a769f52e67784 _pytest/skipping.py --- a/_pytest/skipping.py +++ b/_pytest/skipping.py @@ -89,7 +89,11 @@ if isinstance(expr, py.builtin._basestring): result = cached_eval(self.item.config, expr, d) else: - pytest.fail("expression is not a string") + if self.get("reason") is None: + # XXX better be checked at collection time + pytest.fail("you need to specify reason=STRING " + "when using booleans as conditions.") + result = bool(expr) if result: self.result = True self.expr = expr diff -r 25983ac76a7887522684bca6a9173d23e5adc30c -r 32065167d92882d1c3cd1c152c5a769f52e67784 _pytest/terminal.py --- a/_pytest/terminal.py +++ b/_pytest/terminal.py @@ -454,10 +454,14 @@ if val: parts.append("%d %s" %(len(val), key)) line = ", ".join(parts) - # XXX coloring msg = "%s in %.2f seconds" %(line, session_duration) if self.verbosity >= 0: - self.write_sep("=", msg, bold=True) + markup = dict(bold=True) + if 'failed' in self.stats: + markup['red'] = True + else: + markup['green'] = True + self.write_sep("=", msg, **markup) #else: # self.write_line(msg, bold=True) diff -r 25983ac76a7887522684bca6a9173d23e5adc30c -r 32065167d92882d1c3cd1c152c5a769f52e67784 doc/en/_templates/layout.html --- a/doc/en/_templates/layout.html +++ b/doc/en/_templates/layout.html @@ -3,6 +3,12 @@ {% block relbaritems %} {{ super() }} + + + + {% endblock %} {% block footer %} diff -r 25983ac76a7887522684bca6a9173d23e5adc30c -r 32065167d92882d1c3cd1c152c5a769f52e67784 doc/en/_templates/localtoc.html --- a/doc/en/_templates/localtoc.html +++ b/doc/en/_templates/localtoc.html @@ -31,6 +31,8 @@ issues[bb]contact + + Talks/Posts {% extends "basic/localtoc.html" %} diff -r 25983ac76a7887522684bca6a9173d23e5adc30c -r 32065167d92882d1c3cd1c152c5a769f52e67784 doc/en/announce/index.txt --- a/doc/en/announce/index.txt +++ b/doc/en/announce/index.txt @@ -5,6 +5,7 @@ .. toctree:: :maxdepth: 2 + release-2.3.5 release-2.3.4 release-2.3.3 release-2.3.2 diff -r 25983ac76a7887522684bca6a9173d23e5adc30c -r 32065167d92882d1c3cd1c152c5a769f52e67784 doc/en/announce/release-2.3.5.txt --- a/doc/en/announce/release-2.3.5.txt +++ b/doc/en/announce/release-2.3.5.txt @@ -1,23 +1,61 @@ -pytest-2.3.5: bug fixes +pytest-2.3.5: bug fixes and little improvements =========================================================================== -pytest-2.3.5 is a bug fix release for the pytest testing tool. -See the changelog below for details. And +pytest-2.3.5 is a maintenance release with many bug fixes and little +improvements. See the changelog below for details. No backward +compatibility issues are foreseen and all plugins which worked with the +prior version are expected to work unmodified. Speaking of which, a +few interesting new plugins saw the light last month: + +- pytest-instafail: show failure information while tests are running +- pytest-qt: testing of GUI applications written with QT/Pyside +- pytest-xprocess: managing external processes across test runs +- pytest-random: randomize test ordering + +And several others like pytest-django saw maintenance releases. +For a more complete list, check out +https://pypi.python.org/pypi?%3Aaction=search&term=pytest&submit=search. + +For general information see: http://pytest.org/ -for general information. To install or upgrade pytest: +To install or upgrade pytest: pip install -U pytest # or easy_install -U pytest -best, +Particular thanks to Floris, Ronny, Benjamin and the many bug reporters +and fix providers. + +may the fixtures be with you, holger krekel Changes between 2.3.4 and 2.3.5 ----------------------------------- +- never consider a fixture function for test function collection + +- allow re-running of test items / helps to fix pytest-reruntests plugin + and also help to keep less fixture/resource references alive + +- put captured stdout/stderr into junitxml output even for passing tests + (thanks Adam Goucher) + +- Issue 265 - integrate nose setup/teardown with setupstate + so it doesnt try to teardown if it did not setup + +- issue 271 - dont write junitxml on slave nodes + +- Issue 274 - dont try to show full doctest example + when doctest does not know the example location + +- issue 280 - disable assertion rewriting on buggy CPython 2.6.0 + +- inject "getfixture()" helper to retrieve fixtures from doctests, + thanks Andreas Zeidler + - issue 259 - when assertion rewriting, be consistent with the default source encoding of ASCII on Python 2 @@ -26,7 +64,7 @@ - issue250 unicode/str mixes in parametrization names and values now works - issue257, assertion-triggered compilation of source ending in a - comment line doesn't blow up in python2.5 (fixed through py>=1.4.13) + comment line doesn't blow up in python2.5 (fixed through py>=1.4.13.dev6) - fix --genscript option to generate standalone scripts that also work with python3.3 (importer ordering) diff -r 25983ac76a7887522684bca6a9173d23e5adc30c -r 32065167d92882d1c3cd1c152c5a769f52e67784 doc/en/assert.txt --- a/doc/en/assert.txt +++ b/doc/en/assert.txt @@ -26,7 +26,7 @@ $ py.test test_assert1.py =========================== test session starts ============================ - platform linux2 -- Python 2.7.3 -- pytest-2.3.4 + platform linux2 -- Python 2.7.3 -- pytest-2.3.5 collected 1 items test_assert1.py F @@ -110,7 +110,7 @@ $ py.test test_assert2.py =========================== test session starts ============================ - platform linux2 -- Python 2.7.3 -- pytest-2.3.4 + platform linux2 -- Python 2.7.3 -- pytest-2.3.5 collected 1 items test_assert2.py F diff -r 25983ac76a7887522684bca6a9173d23e5adc30c -r 32065167d92882d1c3cd1c152c5a769f52e67784 doc/en/capture.txt --- a/doc/en/capture.txt +++ b/doc/en/capture.txt @@ -64,7 +64,7 @@ $ py.test =========================== test session starts ============================ - platform linux2 -- Python 2.7.3 -- pytest-2.3.4 + platform linux2 -- Python 2.7.3 -- pytest-2.3.5 collected 2 items test_module.py .F @@ -78,7 +78,7 @@ test_module.py:9: AssertionError ----------------------------- Captured stdout ------------------------------ - setting up + setting up ==================== 1 failed, 1 passed in 0.01 seconds ==================== Accessing captured output from a test function diff -r 25983ac76a7887522684bca6a9173d23e5adc30c -r 32065167d92882d1c3cd1c152c5a769f52e67784 doc/en/conf.py --- a/doc/en/conf.py +++ b/doc/en/conf.py @@ -17,7 +17,7 @@ # # The full version, including alpha/beta/rc tags. # The short X.Y version. -version = release = "2.3.4.1" +version = release = "2.3.5" import sys, os diff -r 25983ac76a7887522684bca6a9173d23e5adc30c -r 32065167d92882d1c3cd1c152c5a769f52e67784 doc/en/doctest.txt --- a/doc/en/doctest.txt +++ b/doc/en/doctest.txt @@ -44,7 +44,7 @@ $ py.test =========================== test session starts ============================ - platform linux2 -- Python 2.7.3 -- pytest-2.3.4 + platform linux2 -- Python 2.7.3 -- pytest-2.3.5 collected 1 items mymodule.py . diff -r 25983ac76a7887522684bca6a9173d23e5adc30c -r 32065167d92882d1c3cd1c152c5a769f52e67784 doc/en/example/markers.txt --- a/doc/en/example/markers.txt +++ b/doc/en/example/markers.txt @@ -28,7 +28,7 @@ $ py.test -v -m webtest =========================== test session starts ============================ - platform linux2 -- Python 2.7.3 -- pytest-2.3.4 -- /home/hpk/p/pytest/.tox/regen/bin/python + platform linux2 -- Python 2.7.3 -- pytest-2.3.5 -- /home/hpk/p/pytest/.tox/regen/bin/python collecting ... collected 3 items test_server.py:3: test_send_http PASSED @@ -40,7 +40,7 @@ $ py.test -v -m "not webtest" =========================== test session starts ============================ - platform linux2 -- Python 2.7.3 -- pytest-2.3.4 -- /home/hpk/p/pytest/.tox/regen/bin/python + platform linux2 -- Python 2.7.3 -- pytest-2.3.5 -- /home/hpk/p/pytest/.tox/regen/bin/python collecting ... collected 3 items test_server.py:6: test_something_quick PASSED @@ -61,7 +61,7 @@ $ py.test -v -k http # running with the above defined example module =========================== test session starts ============================ - platform linux2 -- Python 2.7.3 -- pytest-2.3.4 -- /home/hpk/p/pytest/.tox/regen/bin/python + platform linux2 -- Python 2.7.3 -- pytest-2.3.5 -- /home/hpk/p/pytest/.tox/regen/bin/python collecting ... collected 3 items test_server.py:3: test_send_http PASSED @@ -73,7 +73,7 @@ $ py.test -k "not send_http" -v =========================== test session starts ============================ - platform linux2 -- Python 2.7.3 -- pytest-2.3.4 -- /home/hpk/p/pytest/.tox/regen/bin/python + platform linux2 -- Python 2.7.3 -- pytest-2.3.5 -- /home/hpk/p/pytest/.tox/regen/bin/python collecting ... collected 3 items test_server.py:6: test_something_quick PASSED @@ -86,7 +86,7 @@ $ py.test -k "http or quick" -v =========================== test session starts ============================ - platform linux2 -- Python 2.7.3 -- pytest-2.3.4 -- /home/hpk/p/pytest/.tox/regen/bin/python + platform linux2 -- Python 2.7.3 -- pytest-2.3.5 -- /home/hpk/p/pytest/.tox/regen/bin/python collecting ... collected 3 items test_server.py:3: test_send_http PASSED @@ -185,6 +185,29 @@ in which case it will be applied to all functions and methods defined in the module. +.. _`marking individual tests when using parametrize`: + +Marking individual tests when using parametrize +----------------------------------------------- + +When using parametrize, applying a mark will make it apply +to each individual test. However it is also possible to +apply a marker to an individual test instance:: + + import pytest + + @pytest.mark.foo + @pytest.mark.parametrize(("n", "expected"), [ + (1, 2), + pytest.mark.bar((1, 3)), + (2, 3), + ]) + def test_increment(n, expected): + assert n + 1 == expected + +In this example the mark "foo" will apply to each of the three +tests, whereas the "bar" mark is only applied to the second test. +Skip and xfail marks can also be applied in this way, see :ref:`skip/xfail with parametrize`. .. _`adding a custom marker from a plugin`: @@ -232,7 +255,7 @@ $ py.test -E stage2 =========================== test session starts ============================ - platform linux2 -- Python 2.7.3 -- pytest-2.3.4 + platform linux2 -- Python 2.7.3 -- pytest-2.3.5 collected 1 items test_someenv.py s @@ -243,7 +266,7 @@ $ py.test -E stage1 =========================== test session starts ============================ - platform linux2 -- Python 2.7.3 -- pytest-2.3.4 + platform linux2 -- Python 2.7.3 -- pytest-2.3.5 collected 1 items test_someenv.py . @@ -360,12 +383,12 @@ $ py.test -rs # this option reports skip reasons =========================== test session starts ============================ - platform linux2 -- Python 2.7.3 -- pytest-2.3.4 + platform linux2 -- Python 2.7.3 -- pytest-2.3.5 collected 4 items test_plat.py s.s. ========================= short test summary info ========================== - SKIP [2] /tmp/doc-exec-133/conftest.py:12: cannot run on platform linux2 + SKIP [2] /tmp/doc-exec-273/conftest.py:12: cannot run on platform linux2 =================== 2 passed, 2 skipped in 0.01 seconds ==================== @@ -373,7 +396,7 @@ $ py.test -m linux2 =========================== test session starts ============================ - platform linux2 -- Python 2.7.3 -- pytest-2.3.4 + platform linux2 -- Python 2.7.3 -- pytest-2.3.5 collected 4 items test_plat.py . @@ -424,7 +447,7 @@ $ py.test -m interface --tb=short =========================== test session starts ============================ - platform linux2 -- Python 2.7.3 -- pytest-2.3.4 + platform linux2 -- Python 2.7.3 -- pytest-2.3.5 collected 4 items test_module.py FF @@ -445,7 +468,7 @@ $ py.test -m "interface or event" --tb=short =========================== test session starts ============================ - platform linux2 -- Python 2.7.3 -- pytest-2.3.4 + platform linux2 -- Python 2.7.3 -- pytest-2.3.5 collected 4 items test_module.py FFF diff -r 25983ac76a7887522684bca6a9173d23e5adc30c -r 32065167d92882d1c3cd1c152c5a769f52e67784 doc/en/example/nonpython.txt --- a/doc/en/example/nonpython.txt +++ b/doc/en/example/nonpython.txt @@ -27,7 +27,7 @@ nonpython $ py.test test_simple.yml =========================== test session starts ============================ - platform linux2 -- Python 2.7.3 -- pytest-2.3.4 + platform linux2 -- Python 2.7.3 -- pytest-2.3.5 collected 2 items test_simple.yml .F @@ -37,7 +37,7 @@ usecase execution failed spec failed: 'some': 'other' no further details known at this point. - ==================== 1 failed, 1 passed in 0.09 seconds ==================== + ==================== 1 failed, 1 passed in 0.05 seconds ==================== You get one dot for the passing ``sub1: sub1`` check and one failure. Obviously in the above ``conftest.py`` you'll want to implement a more @@ -56,7 +56,7 @@ nonpython $ py.test -v =========================== test session starts ============================ - platform linux2 -- Python 2.7.3 -- pytest-2.3.4 -- /home/hpk/p/pytest/.tox/regen/bin/python + platform linux2 -- Python 2.7.3 -- pytest-2.3.5 -- /home/hpk/p/pytest/.tox/regen/bin/python collecting ... collected 2 items test_simple.yml:1: usecase: ok PASSED @@ -67,17 +67,17 @@ usecase execution failed spec failed: 'some': 'other' no further details known at this point. - ==================== 1 failed, 1 passed in 0.04 seconds ==================== + ==================== 1 failed, 1 passed in 0.05 seconds ==================== While developing your custom test collection and execution it's also interesting to just look at the collection tree:: nonpython $ py.test --collectonly =========================== test session starts ============================ - platform linux2 -- Python 2.7.3 -- pytest-2.3.4 + platform linux2 -- Python 2.7.3 -- pytest-2.3.5 collected 2 items - ============================= in 0.04 seconds ============================= + ============================= in 0.05 seconds ============================= diff -r 25983ac76a7887522684bca6a9173d23e5adc30c -r 32065167d92882d1c3cd1c152c5a769f52e67784 doc/en/example/nonpython/conftest.py --- a/doc/en/example/nonpython/conftest.py +++ b/doc/en/example/nonpython/conftest.py @@ -9,7 +9,7 @@ class YamlFile(pytest.File): def collect(self): import yaml # we need a yaml parser, e.g. PyYAML - raw = yaml.load(self.fspath.open()) + raw = yaml.safe_load(self.fspath.open()) for name, spec in raw.items(): yield YamlItem(name, self, spec) diff -r 25983ac76a7887522684bca6a9173d23e5adc30c -r 32065167d92882d1c3cd1c152c5a769f52e67784 doc/en/example/parametrize.txt --- a/doc/en/example/parametrize.txt +++ b/doc/en/example/parametrize.txt @@ -104,21 +104,19 @@ $ py.test test_scenarios.py =========================== test session starts ============================ - platform linux2 -- Python 2.7.3 -- pytest-2.4.5dev3 - plugins: xdist, oejskit, pep8, cache, couchdbkit, quickcheck + platform linux2 -- Python 2.7.3 -- pytest-2.3.5 collected 4 items test_scenarios.py .... - ========================= 4 passed in 0.04 seconds ========================= + ========================= 4 passed in 0.01 seconds ========================= If you just collect tests you'll also nicely see 'advanced' and 'basic' as variants for the test function:: $ py.test --collectonly test_scenarios.py =========================== test session starts ============================ - platform linux2 -- Python 2.7.3 -- pytest-2.4.5dev3 - plugins: xdist, oejskit, pep8, cache, couchdbkit, quickcheck + platform linux2 -- Python 2.7.3 -- pytest-2.3.5 collected 4 items @@ -128,7 +126,7 @@ - ============================= in 0.03 seconds ============================= + ============================= in 0.01 seconds ============================= Note that we told ``metafunc.parametrize()`` that your scenario values should be considered class-scoped. With pytest-2.3 this leads to a @@ -182,14 +180,13 @@ $ py.test test_backends.py --collectonly =========================== test session starts ============================ - platform linux2 -- Python 2.7.3 -- pytest-2.4.5dev3 - plugins: xdist, oejskit, pep8, cache, couchdbkit, quickcheck + platform linux2 -- Python 2.7.3 -- pytest-2.3.5 collected 2 items - ============================= in 0.03 seconds ============================= + ============================= in 0.00 seconds ============================= And then when we run the test:: @@ -198,7 +195,7 @@ ================================= FAILURES ================================= _________________________ test_db_initialized[d2] __________________________ - db = + db = def test_db_initialized(db): # a dummy test @@ -253,7 +250,7 @@ ================================= FAILURES ================================= ________________________ TestClass.test_equals[1-2] ________________________ - self = , a = 1, b = 2 + self = , a = 1, b = 2 def test_equals(self, a, b): > assert a == b @@ -327,15 +324,14 @@ $ py.test -rs test_module.py =========================== test session starts ============================ - platform linux2 -- Python 2.7.3 -- pytest-2.4.5dev3 - plugins: xdist, oejskit, pep8, cache, couchdbkit, quickcheck + platform linux2 -- Python 2.7.3 -- pytest-2.3.5 collected 2 items test_module.py .s ========================= short test summary info ========================== - SKIP [1] /tmp/doc-exec-11/conftest.py:10: could not import 'opt2' + SKIP [1] /tmp/doc-exec-275/conftest.py:10: could not import 'opt2' - =================== 1 passed, 1 skipped in 0.04 seconds ==================== + =================== 1 passed, 1 skipped in 0.01 seconds ==================== You'll see that we don't have a ``opt2`` module and thus the second test run of our ``test_func1`` was skipped. A few notes: diff -r 25983ac76a7887522684bca6a9173d23e5adc30c -r 32065167d92882d1c3cd1c152c5a769f52e67784 doc/en/example/pythoncollection.txt --- a/doc/en/example/pythoncollection.txt +++ b/doc/en/example/pythoncollection.txt @@ -43,7 +43,7 @@ $ py.test --collectonly =========================== test session starts ============================ - platform linux2 -- Python 2.7.3 -- pytest-2.3.4 + platform linux2 -- Python 2.7.3 -- pytest-2.3.5 collected 2 items @@ -82,7 +82,7 @@ . $ py.test --collectonly pythoncollection.py =========================== test session starts ============================ - platform linux2 -- Python 2.7.3 -- pytest-2.3.4 + platform linux2 -- Python 2.7.3 -- pytest-2.3.5 collected 3 items @@ -135,7 +135,7 @@ $ py.test --collectonly =========================== test session starts ============================ - platform linux2 -- Python 2.7.3 -- pytest-2.3.4 + platform linux2 -- Python 2.7.3 -- pytest-2.3.5 collected 1 items diff -r 25983ac76a7887522684bca6a9173d23e5adc30c -r 32065167d92882d1c3cd1c152c5a769f52e67784 doc/en/example/reportingdemo.txt --- a/doc/en/example/reportingdemo.txt +++ b/doc/en/example/reportingdemo.txt @@ -13,7 +13,7 @@ assertion $ py.test failure_demo.py =========================== test session starts ============================ - platform linux2 -- Python 2.7.3 -- pytest-2.3.4 + platform linux2 -- Python 2.7.3 -- pytest-2.3.5 collected 39 items failure_demo.py FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF @@ -30,7 +30,7 @@ failure_demo.py:15: AssertionError _________________________ TestFailing.test_simple __________________________ - self = + self = def test_simple(self): def f(): @@ -40,13 +40,13 @@ > assert f() == g() E assert 42 == 43 - E + where 42 = () - E + and 43 = () + E + where 42 = () + E + and 43 = () failure_demo.py:28: AssertionError ____________________ TestFailing.test_simple_multiline _____________________ - self = + self = def test_simple_multiline(self): otherfunc_multi( @@ -66,19 +66,19 @@ failure_demo.py:11: AssertionError ___________________________ TestFailing.test_not ___________________________ - self = + self = def test_not(self): def f(): return 42 > assert not f() E assert not 42 - E + where 42 = () + E + where 42 = () failure_demo.py:38: AssertionError _________________ TestSpecialisedExplanations.test_eq_text _________________ - self = + self = def test_eq_text(self): > assert 'spam' == 'eggs' @@ -89,7 +89,7 @@ failure_demo.py:42: AssertionError _____________ TestSpecialisedExplanations.test_eq_similar_text _____________ - self = + self = def test_eq_similar_text(self): > assert 'foo 1 bar' == 'foo 2 bar' @@ -102,7 +102,7 @@ failure_demo.py:45: AssertionError ____________ TestSpecialisedExplanations.test_eq_multiline_text ____________ - self = + self = def test_eq_multiline_text(self): > assert 'foo\nspam\nbar' == 'foo\neggs\nbar' @@ -115,15 +115,15 @@ failure_demo.py:48: AssertionError ______________ TestSpecialisedExplanations.test_eq_long_text _______________ - self = + self = def test_eq_long_text(self): a = '1'*100 + 'a' + '2'*100 b = '1'*100 + 'b' + '2'*100 > assert a == b E assert '111111111111...2222222222222' == '1111111111111...2222222222222' - E Skipping 90 identical leading characters in diff - E Skipping 91 identical trailing characters in diff + E Skipping 90 identical leading characters in diff, use -v to show + E Skipping 91 identical trailing characters in diff, use -v to show E - 1111111111a222222222 E ? ^ E + 1111111111b222222222 @@ -132,15 +132,15 @@ failure_demo.py:53: AssertionError _________ TestSpecialisedExplanations.test_eq_long_text_multiline __________ - self = + self = def test_eq_long_text_multiline(self): a = '1\n'*100 + 'a' + '2\n'*100 b = '1\n'*100 + 'b' + '2\n'*100 > assert a == b E assert '1\n1\n1\n1\n...n2\n2\n2\n2\n' == '1\n1\n1\n1\n1...n2\n2\n2\n2\n' - E Skipping 190 identical leading characters in diff - E Skipping 191 identical trailing characters in diff + E Skipping 190 identical leading characters in diff, use -v to show + E Skipping 191 identical trailing characters in diff, use -v to show E 1 E 1 E 1 @@ -156,7 +156,7 @@ failure_demo.py:58: AssertionError _________________ TestSpecialisedExplanations.test_eq_list _________________ - self = + self = def test_eq_list(self): > assert [0, 1, 2] == [0, 1, 3] @@ -166,7 +166,7 @@ failure_demo.py:61: AssertionError ______________ TestSpecialisedExplanations.test_eq_list_long _______________ - self = + self = def test_eq_list_long(self): a = [0]*100 + [1] + [3]*100 @@ -178,20 +178,23 @@ failure_demo.py:66: AssertionError _________________ TestSpecialisedExplanations.test_eq_dict _________________ - self = + self = def test_eq_dict(self): - > assert {'a': 0, 'b': 1} == {'a': 0, 'b': 2} - E assert {'a': 0, 'b': 1} == {'a': 0, 'b': 2} - E - {'a': 0, 'b': 1} - E ? ^ - E + {'a': 0, 'b': 2} - E ? ^ + > assert {'a': 0, 'b': 1, 'c': 0} == {'a': 0, 'b': 2, 'd': 0} + E assert {'a': 0, 'b': 1, 'c': 0} == {'a': 0, 'b': 2, 'd': 0} + E Hiding 1 identical items, use -v to show + E Differing items: + E {'b': 1} != {'b': 2} + E Left contains more items: + E {'c': 0} + E Right contains more items: + E {'d': 0} failure_demo.py:69: AssertionError _________________ TestSpecialisedExplanations.test_eq_set __________________ - self = + self = def test_eq_set(self): > assert set([0, 10, 11, 12]) == set([0, 20, 21]) @@ -207,7 +210,7 @@ failure_demo.py:72: AssertionError _____________ TestSpecialisedExplanations.test_eq_longer_list ______________ - self = + self = def test_eq_longer_list(self): > assert [1,2] == [1,2,3] @@ -217,7 +220,7 @@ failure_demo.py:75: AssertionError _________________ TestSpecialisedExplanations.test_in_list _________________ - self = + self = def test_in_list(self): > assert 1 in [0, 2, 3, 4, 5] @@ -226,7 +229,7 @@ failure_demo.py:78: AssertionError __________ TestSpecialisedExplanations.test_not_in_text_multiline __________ - self = + self = def test_not_in_text_multiline(self): text = 'some multiline\ntext\nwhich\nincludes foo\nand a\ntail' @@ -244,7 +247,7 @@ failure_demo.py:82: AssertionError ___________ TestSpecialisedExplanations.test_not_in_text_single ____________ - self = + self = def test_not_in_text_single(self): text = 'single foo line' @@ -257,7 +260,7 @@ failure_demo.py:86: AssertionError _________ TestSpecialisedExplanations.test_not_in_text_single_long _________ - self = + self = def test_not_in_text_single_long(self): text = 'head ' * 50 + 'foo ' + 'tail ' * 20 @@ -270,7 +273,7 @@ failure_demo.py:90: AssertionError ______ TestSpecialisedExplanations.test_not_in_text_single_long_term _______ - self = + self = def test_not_in_text_single_long_term(self): text = 'head ' * 50 + 'f'*70 + 'tail ' * 20 @@ -289,7 +292,7 @@ i = Foo() > assert i.b == 2 E assert 1 == 2 - E + where 1 = .b + E + where 1 = .b failure_demo.py:101: AssertionError _________________________ test_attribute_instance __________________________ @@ -299,8 +302,8 @@ b = 1 > assert Foo().b == 2 E assert 1 == 2 - E + where 1 = .b - E + where = () + E + where 1 = .b + E + where = () failure_demo.py:107: AssertionError __________________________ test_attribute_failure __________________________ @@ -316,7 +319,7 @@ failure_demo.py:116: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ - self = + self = def _get_b(self): > raise Exception('Failed to get attrib') @@ -332,15 +335,15 @@ b = 2 > assert Foo().b == Bar().b E assert 1 == 2 - E + where 1 = .b - E + where = () - E + and 2 = .b - E + where = () + E + where 1 = .b + E + where = () + E + and 2 = .b + E + where = () failure_demo.py:124: AssertionError __________________________ TestRaises.test_raises __________________________ - self = + self = def test_raises(self): s = 'qwe' @@ -352,10 +355,10 @@ > int(s) E ValueError: invalid literal for int() with base 10: 'qwe' - <0-codegen /home/hpk/p/pytest/.tox/regen/lib/python2.7/site-packages/_pytest/python.py:851>:1: ValueError + <0-codegen /home/hpk/p/pytest/.tox/regen/local/lib/python2.7/site-packages/_pytest/python.py:858>:1: ValueError ______________________ TestRaises.test_raises_doesnt _______________________ - self = + self = def test_raises_doesnt(self): > raises(IOError, "int('3')") @@ -364,7 +367,7 @@ failure_demo.py:136: Failed __________________________ TestRaises.test_raise ___________________________ - self = + self = def test_raise(self): > raise ValueError("demo error") @@ -373,7 +376,7 @@ failure_demo.py:139: ValueError ________________________ TestRaises.test_tupleerror ________________________ - self = + self = def test_tupleerror(self): > a,b = [1] @@ -382,7 +385,7 @@ failure_demo.py:142: ValueError ______ TestRaises.test_reinterpret_fails_with_print_for_the_fun_of_it ______ - self = + self = def test_reinterpret_fails_with_print_for_the_fun_of_it(self): l = [1,2,3] @@ -395,7 +398,7 @@ l is [1, 2, 3] ________________________ TestRaises.test_some_error ________________________ - self = + self = def test_some_error(self): > if namenotexi: @@ -423,7 +426,7 @@ <2-codegen 'abc-123' /home/hpk/p/pytest/doc/en/example/assertion/failure_demo.py:162>:2: AssertionError ____________________ TestMoreErrors.test_complex_error _____________________ - self = + self = def test_complex_error(self): def f(): @@ -452,7 +455,7 @@ failure_demo.py:5: AssertionError ___________________ TestMoreErrors.test_z1_unpack_error ____________________ - self = + self = def test_z1_unpack_error(self): l = [] @@ -462,7 +465,7 @@ failure_demo.py:179: ValueError ____________________ TestMoreErrors.test_z2_type_error _____________________ - self = + self = def test_z2_type_error(self): l = 3 @@ -472,19 +475,19 @@ failure_demo.py:183: TypeError ______________________ TestMoreErrors.test_startswith ______________________ - self = + self = def test_startswith(self): s = "123" g = "456" > assert s.startswith(g) - E assert ('456') - E + where = '123'.startswith + E assert ('456') + E + where = '123'.startswith failure_demo.py:188: AssertionError __________________ TestMoreErrors.test_startswith_nested ___________________ - self = + self = def test_startswith_nested(self): def f(): @@ -492,15 +495,15 @@ def g(): return "456" > assert f().startswith(g()) - E assert ('456') - E + where = '123'.startswith - E + where '123' = () - E + and '456' = () + E assert ('456') + E + where = '123'.startswith + E + where '123' = () + E + and '456' = () failure_demo.py:195: AssertionError _____________________ TestMoreErrors.test_global_func ______________________ - self = + self = def test_global_func(self): > assert isinstance(globf(42), float) @@ -510,18 +513,18 @@ failure_demo.py:198: AssertionError _______________________ TestMoreErrors.test_instance _______________________ - self = + self = def test_instance(self): self.x = 6*7 > assert self.x != 42 E assert 42 != 42 - E + where 42 = .x + E + where 42 = .x failure_demo.py:202: AssertionError _______________________ TestMoreErrors.test_compare ________________________ - self = + self = def test_compare(self): > assert globf(10) < 5 @@ -531,7 +534,7 @@ failure_demo.py:205: AssertionError _____________________ TestMoreErrors.test_try_finally ______________________ - self = + self = def test_try_finally(self): x = 1 @@ -540,4 +543,4 @@ E assert 1 == 0 failure_demo.py:210: AssertionError - ======================== 39 failed in 0.25 seconds ========================= + ======================== 39 failed in 0.21 seconds ========================= This diff is so big that we needed to truncate the remainder. https://bitbucket.org/hpk42/pytest/commits/a706f6fd5fc7/ Changeset: a706f6fd5fc7 User: bubenkoff Date: 2013-07-06 10:26:14 Summary: remove unnecessary print Affected #: 1 file diff -r 32065167d92882d1c3cd1c152c5a769f52e67784 -r a706f6fd5fc7fd1bacc50c1ce22b99dc4ca06284 testing/test_conftest.py --- a/testing/test_conftest.py +++ b/testing/test_conftest.py @@ -211,7 +211,6 @@ ct2 = sub.join("conftest.py") ct2.write("") def impct(p): - print p return p conftest = Conftest() monkeypatch.setattr(conftest, 'importconftest', impct) https://bitbucket.org/hpk42/pytest/commits/9d60521c1e64/ Changeset: 9d60521c1e64 User: flub Date: 2013-07-06 11:00:29 Summary: Mention issue 300 in changelog Affected #: 1 file diff -r a706f6fd5fc7fd1bacc50c1ce22b99dc4ca06284 -r 9d60521c1e64ed81ba7bd40a241e7a631282cc7e CHANGELOG --- a/CHANGELOG +++ b/CHANGELOG @@ -1,6 +1,9 @@ Changes between 2.3.5 and 2.4.DEV ----------------------------------- +- fix issue300 - Fix order of conftest loading when starting py.test + in a subdirectory. + - fix issue323 - sorting of many module-scoped arg parametrizations - add support for setUpModule/tearDownModule detection, thanks Brian Okken. 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 Jul 6 16:03:28 2013 From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org) Date: Sat, 06 Jul 2013 14:03:28 -0000 Subject: [Pytest-commit] commit/pytest: 3 new changesets Message-ID: <20130706140328.1712.29691@bitbucket20.managed.contegix.com> 3 new commits in pytest: https://bitbucket.org/hpk42/pytest/commits/bca2dc5e59dd/ Changeset: bca2dc5e59dd User: katarzynaanna Date: 2013-07-06 15:43:59 Summary: improved reporting added intermediate level of quiet reporting: * -q now shows short summary (# passed/failed tests + time) * the former -q is now -qq Affected #: 2 files diff -r 29461bb5660a9c38aed9a8762401afa8f9188149 -r bca2dc5e59ddfe825771be634a74bf79598bcd5e _pytest/terminal.py --- a/_pytest/terminal.py +++ b/_pytest/terminal.py @@ -44,7 +44,7 @@ pass else: config._cleanup.append(lambda: newstdout.close()) - assert stdout.encoding == newstdout.encoding + assert stdout.encoding == newstdout.encoding stdout = newstdout reporter = TerminalReporter(config, stdout) @@ -105,7 +105,7 @@ self.hasmarkup = self._tw.hasmarkup def hasopt(self, char): - char = {'xfailed': 'x', 'skipped': 's'}.get(char,char) + char = {'xfailed': 'x', 'skipped': 's'}.get(char, char) return char in self.reportchars def write_fspath_result(self, fspath, res): @@ -154,7 +154,7 @@ def pytest_plugin_registered(self, plugin): if self.config.option.traceconfig: - msg = "PLUGIN registered: %s" %(plugin,) + msg = "PLUGIN registered: %s" % (plugin,) # XXX this event may happen during setup/teardown time # which unfortunately captures our output here # which garbles our output if we use self.write_line @@ -209,7 +209,7 @@ self.currentfspath = -2 def pytest_collection(self): - if not self.hasmarkup and self.config.option.verbose >=1: + if not self.hasmarkup and self.config.option.verbose >= 1: self.write("collecting ... ", bold=True) def pytest_collectreport(self, report): @@ -325,8 +325,8 @@ stack.append(col) #if col.name == "()": # continue - indent = (len(stack)-1) * " " - self._tw.line("%s%s" %(indent, col)) + indent = (len(stack) - 1) * " " + self._tw.line("%s%s" % (indent, col)) def pytest_sessionfinish(self, exitstatus, __multicall__): __multicall__.execute() @@ -452,9 +452,9 @@ if key: # setup/teardown reports have an empty key, ignore them val = self.stats.get(key, None) if val: - parts.append("%d %s" %(len(val), key)) + parts.append("%d %s" % (len(val), key)) line = ", ".join(parts) - msg = "%s in %.2f seconds" %(line, session_duration) + msg = "%s in %.2f seconds" % (line, session_duration) if self.verbosity >= 0: markup = dict(bold=True) if 'failed' in self.stats: @@ -462,6 +462,10 @@ else: markup['green'] = True self.write_sep("=", msg, **markup) + if self.verbosity == -1: + if line: + self.write("%s, " % line) + self.write("time: %.2f seconds\n" % session_duration) #else: # self.write_line(msg, bold=True) @@ -475,7 +479,7 @@ if m: l.append("-m %r" % m) if l: - self.write_sep("=", "%d tests deselected by %r" %( + self.write_sep("=", "%d tests deselected by %r" % ( len(self.stats['deselected']), " ".join(l)), bold=True) def repr_pythonversion(v=None): diff -r 29461bb5660a9c38aed9a8762401afa8f9188149 -r bca2dc5e59ddfe825771be634a74bf79598bcd5e testing/test_terminal.py --- a/testing/test_terminal.py +++ b/testing/test_terminal.py @@ -1,7 +1,7 @@ """ terminal reporting of the full testing process. """ -import pytest,py +import pytest, py import sys from _pytest.terminal import TerminalReporter, repr_pythonversion, getreportopt @@ -32,7 +32,7 @@ metafunc.addcall(id="verbose", funcargs={'option': Option(verbose=True)}) metafunc.addcall(id="quiet", - funcargs={'option': Option(verbose=-1)}) + funcargs={'option': Option(verbose= -1)}) metafunc.addcall(id="fulltrace", funcargs={'option': Option(fulltrace=True)}) @@ -286,7 +286,7 @@ try: monkeypatch.setattr(sys, 'version_info', (2, 5, 1, 'final', 0)) assert repr_pythonversion() == "2.5.1-final-0" - py.std.sys.version_info = x = (2,3) + py.std.sys.version_info = x = (2, 3) assert repr_pythonversion() == str(x) finally: monkeypatch.undo() # do this early as pytest can get confused @@ -411,7 +411,7 @@ verinfo = ".".join(map(str, py.std.sys.version_info[:3])) result.stdout.fnmatch_lines([ "*===== test session starts ====*", - "platform %s -- Python %s*" %( + "platform %s -- Python %s*" % ( py.std.sys.platform, verinfo), # , py.std.sys.executable), "*test_header_trailer_info.py .", "=* 1 passed in *.[0-9][0-9] seconds *=", @@ -473,6 +473,17 @@ assert 'test session starts' not in s assert p1.basename not in s assert "===" not in s + assert "passed" in s + + def test_more_quiet_reporting(self, testdir): + p1 = testdir.makepyfile("def test_pass(): pass") + result = testdir.runpytest(p1, '-qq') + s = result.stdout.str() + assert 'test session starts' not in s + assert p1.basename not in s + assert "===" not in s + assert "passed" not in s + def test_fail_extra_reporting(testdir): p = testdir.makepyfile("def test_this(): assert 0") @@ -679,5 +690,5 @@ """) result = testdir.runpytest("--tb=native") result.stdout.fnmatch_lines([ - '*File *test_tbstyle_native_setup_error.py", line *, in setup_error_fixture*' + '*File *test_tbstyle_native_setup_error.py", line *, in setup_error_fixture*' ]) https://bitbucket.org/hpk42/pytest/commits/71ae07462c66/ Changeset: 71ae07462c66 User: katarzynaanna Date: 2013-07-06 15:54:33 Summary: merge Affected #: 5 files diff -r bca2dc5e59ddfe825771be634a74bf79598bcd5e -r 71ae07462c66ccea05901442fb0461d7834aea1d CHANGELOG --- a/CHANGELOG +++ b/CHANGELOG @@ -1,6 +1,9 @@ Changes between 2.3.5 and 2.4.DEV ----------------------------------- +- fix issue300 - Fix order of conftest loading when starting py.test + in a subdirectory. + - fix issue323 - sorting of many module-scoped arg parametrizations - add support for setUpModule/tearDownModule detection, thanks Brian Okken. diff -r bca2dc5e59ddfe825771be634a74bf79598bcd5e -r 71ae07462c66ccea05901442fb0461d7834aea1d _pytest/config.py --- a/_pytest/config.py +++ b/_pytest/config.py @@ -196,27 +196,20 @@ self.getconftestmodules(x) def getconftestmodules(self, path): - """ return a list of imported conftest modules for the given path. """ try: clist = self._path2confmods[path] except KeyError: if path is None: - raise ValueError("missing default confest.") - dp = path.dirpath() + raise ValueError("missing default conftest.") clist = [] - if dp != path: - cutdir = self._confcutdir - if cutdir and path != cutdir and not path.relto(cutdir): - pass - else: - conftestpath = path.join("conftest.py") - if conftestpath.check(file=1): - clist.append(self.importconftest(conftestpath)) - clist[:0] = self.getconftestmodules(dp) + for parent in path.parts(): + if self._confcutdir and self._confcutdir.relto(parent): + continue + conftestpath = parent.join("conftest.py") + if conftestpath.check(file=1): + clist.append(self.importconftest(conftestpath)) self._path2confmods[path] = clist - # be defensive: avoid changes from caller side to - # affect us by always returning a copy of the actual list - return clist[:] + return clist def rget(self, name, path=None): mod, value = self.rget_with_confmod(name, path) diff -r bca2dc5e59ddfe825771be634a74bf79598bcd5e -r 71ae07462c66ccea05901442fb0461d7834aea1d testing/python/fixture.py --- a/testing/python/fixture.py +++ b/testing/python/fixture.py @@ -102,6 +102,77 @@ "*2 passed*" ]) + def test_extend_fixture_module_class(self, testdir): + testfile = testdir.makepyfile(""" + import pytest + + @pytest.fixture + def spam(): + return 'spam' + + class TestSpam: + + @pytest.fixture + def spam(self, spam): + return spam * 2 + + def test_spam(self, spam): + assert spam == 'spamspam' + """) + result = testdir.runpytest() + result.stdout.fnmatch_lines(["*1 passed*"]) + result = testdir.runpytest(testfile) + result.stdout.fnmatch_lines(["*1 passed*"]) + + def test_extend_fixture_conftest_module(self, testdir): + testdir.makeconftest(""" + import pytest + + @pytest.fixture + def spam(): + return 'spam' + """) + testfile = testdir.makepyfile(""" + import pytest + + @pytest.fixture + def spam(spam): + return spam * 2 + + def test_spam(spam): + assert spam == 'spamspam' + """) + result = testdir.runpytest() + result.stdout.fnmatch_lines(["*1 passed*"]) + result = testdir.runpytest(testfile) + result.stdout.fnmatch_lines(["*1 passed*"]) + + def test_extend_fixture_conftest_conftest(self, testdir): + testdir.makeconftest(""" + import pytest + + @pytest.fixture + def spam(): + return 'spam' + """) + pkg = testdir.mkpydir("pkg") + pkg.join("conftest.py").write(py.code.Source(""" + import pytest + + @pytest.fixture + def spam(spam): + return spam * 2 + """)) + testfile = pkg.join("test_spam.py") + testfile.write(py.code.Source(""" + def test_spam(spam): + assert spam == "spamspam" + """)) + result = testdir.runpytest() + result.stdout.fnmatch_lines(["*1 passed*"]) + result = testdir.runpytest(testfile) + result.stdout.fnmatch_lines(["*1 passed*"]) + def test_funcarg_lookup_error(self, testdir): p = testdir.makepyfile(""" def test_lookup_error(unknown): diff -r bca2dc5e59ddfe825771be634a74bf79598bcd5e -r 71ae07462c66ccea05901442fb0461d7834aea1d testing/test_conftest.py --- a/testing/test_conftest.py +++ b/testing/test_conftest.py @@ -204,3 +204,14 @@ """)) result = testdir.runpytest("-h", "--confcutdir=%s" % x, x) result.stdout.fnmatch_lines(["*--xyz*"]) + +def test_conftest_import_order(testdir, monkeypatch): + ct1 = testdir.makeconftest("") + sub = testdir.mkdir("sub") + ct2 = sub.join("conftest.py") + ct2.write("") + def impct(p): + return p + conftest = Conftest() + monkeypatch.setattr(conftest, 'importconftest', impct) + assert conftest.getconftestmodules(sub) == [ct1, ct2] diff -r bca2dc5e59ddfe825771be634a74bf79598bcd5e -r 71ae07462c66ccea05901442fb0461d7834aea1d testing/test_terminal.py --- a/testing/test_terminal.py +++ b/testing/test_terminal.py @@ -654,9 +654,9 @@ """) result = testdir.runpytest("a") result.stdout.fnmatch_lines([ + "*hello: 42*", "line1", str(testdir.tmpdir), - "*hello: 42*", ]) @pytest.mark.xfail("not hasattr(os, 'dup')") https://bitbucket.org/hpk42/pytest/commits/c1bf2cd59fb7/ Changeset: c1bf2cd59fb7 User: hpk42 Date: 2013-07-06 16:03:27 Summary: Merged in katarzynaanna/pytest (pull request #42) Improved reporting Affected #: 2 files diff -r c3a0a8895e55c89070142e3849959464d08514b6 -r c1bf2cd59fb7accc4835ff34ca8551b1155a1ae2 _pytest/terminal.py --- a/_pytest/terminal.py +++ b/_pytest/terminal.py @@ -44,7 +44,7 @@ pass else: config._cleanup.append(lambda: newstdout.close()) - assert stdout.encoding == newstdout.encoding + assert stdout.encoding == newstdout.encoding stdout = newstdout reporter = TerminalReporter(config, stdout) @@ -105,7 +105,7 @@ self.hasmarkup = self._tw.hasmarkup def hasopt(self, char): - char = {'xfailed': 'x', 'skipped': 's'}.get(char,char) + char = {'xfailed': 'x', 'skipped': 's'}.get(char, char) return char in self.reportchars def write_fspath_result(self, fspath, res): @@ -154,7 +154,7 @@ def pytest_plugin_registered(self, plugin): if self.config.option.traceconfig: - msg = "PLUGIN registered: %s" %(plugin,) + msg = "PLUGIN registered: %s" % (plugin,) # XXX this event may happen during setup/teardown time # which unfortunately captures our output here # which garbles our output if we use self.write_line @@ -209,7 +209,7 @@ self.currentfspath = -2 def pytest_collection(self): - if not self.hasmarkup and self.config.option.verbose >=1: + if not self.hasmarkup and self.config.option.verbose >= 1: self.write("collecting ... ", bold=True) def pytest_collectreport(self, report): @@ -325,8 +325,8 @@ stack.append(col) #if col.name == "()": # continue - indent = (len(stack)-1) * " " - self._tw.line("%s%s" %(indent, col)) + indent = (len(stack) - 1) * " " + self._tw.line("%s%s" % (indent, col)) def pytest_sessionfinish(self, exitstatus, __multicall__): __multicall__.execute() @@ -452,9 +452,9 @@ if key: # setup/teardown reports have an empty key, ignore them val = self.stats.get(key, None) if val: - parts.append("%d %s" %(len(val), key)) + parts.append("%d %s" % (len(val), key)) line = ", ".join(parts) - msg = "%s in %.2f seconds" %(line, session_duration) + msg = "%s in %.2f seconds" % (line, session_duration) if self.verbosity >= 0: markup = dict(bold=True) if 'failed' in self.stats: @@ -462,6 +462,10 @@ else: markup['green'] = True self.write_sep("=", msg, **markup) + if self.verbosity == -1: + if line: + self.write("%s, " % line) + self.write("time: %.2f seconds\n" % session_duration) #else: # self.write_line(msg, bold=True) @@ -475,7 +479,7 @@ if m: l.append("-m %r" % m) if l: - self.write_sep("=", "%d tests deselected by %r" %( + self.write_sep("=", "%d tests deselected by %r" % ( len(self.stats['deselected']), " ".join(l)), bold=True) def repr_pythonversion(v=None): diff -r c3a0a8895e55c89070142e3849959464d08514b6 -r c1bf2cd59fb7accc4835ff34ca8551b1155a1ae2 testing/test_terminal.py --- a/testing/test_terminal.py +++ b/testing/test_terminal.py @@ -1,7 +1,7 @@ """ terminal reporting of the full testing process. """ -import pytest,py +import pytest, py import sys from _pytest.terminal import TerminalReporter, repr_pythonversion, getreportopt @@ -32,7 +32,7 @@ metafunc.addcall(id="verbose", funcargs={'option': Option(verbose=True)}) metafunc.addcall(id="quiet", - funcargs={'option': Option(verbose=-1)}) + funcargs={'option': Option(verbose= -1)}) metafunc.addcall(id="fulltrace", funcargs={'option': Option(fulltrace=True)}) @@ -286,7 +286,7 @@ try: monkeypatch.setattr(sys, 'version_info', (2, 5, 1, 'final', 0)) assert repr_pythonversion() == "2.5.1-final-0" - py.std.sys.version_info = x = (2,3) + py.std.sys.version_info = x = (2, 3) assert repr_pythonversion() == str(x) finally: monkeypatch.undo() # do this early as pytest can get confused @@ -411,7 +411,7 @@ verinfo = ".".join(map(str, py.std.sys.version_info[:3])) result.stdout.fnmatch_lines([ "*===== test session starts ====*", - "platform %s -- Python %s*" %( + "platform %s -- Python %s*" % ( py.std.sys.platform, verinfo), # , py.std.sys.executable), "*test_header_trailer_info.py .", "=* 1 passed in *.[0-9][0-9] seconds *=", @@ -473,6 +473,17 @@ assert 'test session starts' not in s assert p1.basename not in s assert "===" not in s + assert "passed" in s + + def test_more_quiet_reporting(self, testdir): + p1 = testdir.makepyfile("def test_pass(): pass") + result = testdir.runpytest(p1, '-qq') + s = result.stdout.str() + assert 'test session starts' not in s + assert p1.basename not in s + assert "===" not in s + assert "passed" not in s + def test_fail_extra_reporting(testdir): p = testdir.makepyfile("def test_this(): assert 0") @@ -679,5 +690,5 @@ """) result = testdir.runpytest("--tb=native") result.stdout.fnmatch_lines([ - '*File *test_tbstyle_native_setup_error.py", line *, in setup_error_fixture*' + '*File *test_tbstyle_native_setup_error.py", line *, in setup_error_fixture*' ]) 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 Jul 6 16:04:28 2013 From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org) Date: Sat, 06 Jul 2013 14:04:28 -0000 Subject: [Pytest-commit] commit/pytest: 2 new changesets Message-ID: <20130706140428.1819.50274@bitbucket25.managed.contegix.com> 2 new commits in pytest: https://bitbucket.org/hpk42/pytest/commits/52b615fe1946/ Changeset: 52b615fe1946 User: hpk42 Date: 2013-07-06 15:38:40 Summary: move to development doc target Affected #: 2 files diff -r 9d60521c1e64ed81ba7bd40a241e7a631282cc7e -r 52b615fe194684a209c2d68f4dc9d998bdb9a9e5 doc/en/Makefile --- a/doc/en/Makefile +++ b/doc/en/Makefile @@ -12,7 +12,7 @@ PAPEROPT_letter = -D latex_paper_size=letter ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . -SITETARGET=latest +SITETARGET=dev .PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest diff -r 9d60521c1e64ed81ba7bd40a241e7a631282cc7e -r 52b615fe194684a209c2d68f4dc9d998bdb9a9e5 doc/en/conf.py --- a/doc/en/conf.py +++ b/doc/en/conf.py @@ -17,7 +17,7 @@ # # The full version, including alpha/beta/rc tags. # The short X.Y version. -version = release = "2.3.5" +version = release = "2.4.0.dev" import sys, os https://bitbucket.org/hpk42/pytest/commits/16e79a864e96/ Changeset: 16e79a864e96 User: hpk42 Date: 2013-07-06 16:03:48 Summary: merge Affected #: 6 files diff -r 52b615fe194684a209c2d68f4dc9d998bdb9a9e5 -r 16e79a864e9661f52ef93a2a4afbeae3299664e7 .gitignore --- /dev/null +++ b/.gitignore @@ -0,0 +1,32 @@ +# Automatically generated by `hgimportsvn` +.svn +.hgsvn + +# Ignore local virtualenvs +lib/ +bin/ +include/ +.Python/ + +# These lines are suggested according to the svn:ignore property +# Feel free to enable them by uncommenting them +*.pyc +*.pyo +*.swp +*.html +*.class +*.orig +*~ + +doc/*/_build +build/ +dist/ +*.egg-info +issue/ +env/ +3rdparty/ +.tox +.cache +.coverage +.ropeproject + diff -r 52b615fe194684a209c2d68f4dc9d998bdb9a9e5 -r 16e79a864e9661f52ef93a2a4afbeae3299664e7 .travis.yml --- /dev/null +++ b/.travis.yml @@ -0,0 +1,10 @@ +language: python +# command to install dependencies +install: "pip install -e . detox" +# # command to run tests +script: detox --recreate +notifications: + irc: + - "chat.freenode.net#pylib" + email: + - pytest-commit at python.org diff -r 52b615fe194684a209c2d68f4dc9d998bdb9a9e5 -r 16e79a864e9661f52ef93a2a4afbeae3299664e7 README.rst --- a/README.rst +++ b/README.rst @@ -16,6 +16,9 @@ - many `external plugins `_. +.. image:: https://secure.travis-ci.org/bubenkoff/pytest.png?branch=travis-integration + :target: http://travis-ci.org/bubenkoff/pytest + A simple example for a test:: # content of test_module.py diff -r 52b615fe194684a209c2d68f4dc9d998bdb9a9e5 -r 16e79a864e9661f52ef93a2a4afbeae3299664e7 _pytest/terminal.py --- a/_pytest/terminal.py +++ b/_pytest/terminal.py @@ -44,7 +44,7 @@ pass else: config._cleanup.append(lambda: newstdout.close()) - assert stdout.encoding == newstdout.encoding + assert stdout.encoding == newstdout.encoding stdout = newstdout reporter = TerminalReporter(config, stdout) @@ -105,7 +105,7 @@ self.hasmarkup = self._tw.hasmarkup def hasopt(self, char): - char = {'xfailed': 'x', 'skipped': 's'}.get(char,char) + char = {'xfailed': 'x', 'skipped': 's'}.get(char, char) return char in self.reportchars def write_fspath_result(self, fspath, res): @@ -154,7 +154,7 @@ def pytest_plugin_registered(self, plugin): if self.config.option.traceconfig: - msg = "PLUGIN registered: %s" %(plugin,) + msg = "PLUGIN registered: %s" % (plugin,) # XXX this event may happen during setup/teardown time # which unfortunately captures our output here # which garbles our output if we use self.write_line @@ -209,7 +209,7 @@ self.currentfspath = -2 def pytest_collection(self): - if not self.hasmarkup and self.config.option.verbose >=1: + if not self.hasmarkup and self.config.option.verbose >= 1: self.write("collecting ... ", bold=True) def pytest_collectreport(self, report): @@ -325,8 +325,8 @@ stack.append(col) #if col.name == "()": # continue - indent = (len(stack)-1) * " " - self._tw.line("%s%s" %(indent, col)) + indent = (len(stack) - 1) * " " + self._tw.line("%s%s" % (indent, col)) def pytest_sessionfinish(self, exitstatus, __multicall__): __multicall__.execute() @@ -452,9 +452,9 @@ if key: # setup/teardown reports have an empty key, ignore them val = self.stats.get(key, None) if val: - parts.append("%d %s" %(len(val), key)) + parts.append("%d %s" % (len(val), key)) line = ", ".join(parts) - msg = "%s in %.2f seconds" %(line, session_duration) + msg = "%s in %.2f seconds" % (line, session_duration) if self.verbosity >= 0: markup = dict(bold=True) if 'failed' in self.stats: @@ -462,6 +462,10 @@ else: markup['green'] = True self.write_sep("=", msg, **markup) + if self.verbosity == -1: + if line: + self.write("%s, " % line) + self.write("time: %.2f seconds\n" % session_duration) #else: # self.write_line(msg, bold=True) @@ -475,7 +479,7 @@ if m: l.append("-m %r" % m) if l: - self.write_sep("=", "%d tests deselected by %r" %( + self.write_sep("=", "%d tests deselected by %r" % ( len(self.stats['deselected']), " ".join(l)), bold=True) def repr_pythonversion(v=None): diff -r 52b615fe194684a209c2d68f4dc9d998bdb9a9e5 -r 16e79a864e9661f52ef93a2a4afbeae3299664e7 testing/test_terminal.py --- a/testing/test_terminal.py +++ b/testing/test_terminal.py @@ -1,7 +1,7 @@ """ terminal reporting of the full testing process. """ -import pytest,py +import pytest, py import sys from _pytest.terminal import TerminalReporter, repr_pythonversion, getreportopt @@ -32,7 +32,7 @@ metafunc.addcall(id="verbose", funcargs={'option': Option(verbose=True)}) metafunc.addcall(id="quiet", - funcargs={'option': Option(verbose=-1)}) + funcargs={'option': Option(verbose= -1)}) metafunc.addcall(id="fulltrace", funcargs={'option': Option(fulltrace=True)}) @@ -286,7 +286,7 @@ try: monkeypatch.setattr(sys, 'version_info', (2, 5, 1, 'final', 0)) assert repr_pythonversion() == "2.5.1-final-0" - py.std.sys.version_info = x = (2,3) + py.std.sys.version_info = x = (2, 3) assert repr_pythonversion() == str(x) finally: monkeypatch.undo() # do this early as pytest can get confused @@ -411,7 +411,7 @@ verinfo = ".".join(map(str, py.std.sys.version_info[:3])) result.stdout.fnmatch_lines([ "*===== test session starts ====*", - "platform %s -- Python %s*" %( + "platform %s -- Python %s*" % ( py.std.sys.platform, verinfo), # , py.std.sys.executable), "*test_header_trailer_info.py .", "=* 1 passed in *.[0-9][0-9] seconds *=", @@ -473,6 +473,17 @@ assert 'test session starts' not in s assert p1.basename not in s assert "===" not in s + assert "passed" in s + + def test_more_quiet_reporting(self, testdir): + p1 = testdir.makepyfile("def test_pass(): pass") + result = testdir.runpytest(p1, '-qq') + s = result.stdout.str() + assert 'test session starts' not in s + assert p1.basename not in s + assert "===" not in s + assert "passed" not in s + def test_fail_extra_reporting(testdir): p = testdir.makepyfile("def test_this(): assert 0") @@ -679,5 +690,5 @@ """) result = testdir.runpytest("--tb=native") result.stdout.fnmatch_lines([ - '*File *test_tbstyle_native_setup_error.py", line *, in setup_error_fixture*' + '*File *test_tbstyle_native_setup_error.py", line *, in setup_error_fixture*' ]) diff -r 52b615fe194684a209c2d68f4dc9d998bdb9a9e5 -r 16e79a864e9661f52ef93a2a4afbeae3299664e7 tox.ini --- a/tox.ini +++ b/tox.ini @@ -13,6 +13,10 @@ changedir=. commands= py.test --genscript=pytest1 +[testenv:py25] +setenv = + PIP_INSECURE=1 + [testenv:py27-xdist] changedir=. basepython=python2.7 @@ -29,6 +33,7 @@ deps=pytest-xdist setenv= PYTHONDONTWRITEBYTECODE=1 +distribute=true commands= py.test -n3 -rfsxX \ --junitxml={envlogdir}/junit-{envname}.xml {posargs: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 commits-noreply at bitbucket.org Sat Jul 6 17:07:58 2013 From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org) Date: Sat, 06 Jul 2013 15:07:58 -0000 Subject: [Pytest-commit] commit/pytest: hpk42: add kasia to changelog Message-ID: <20130706150758.9310.38600@bitbucket04.managed.contegix.com> 1 new commit in pytest: https://bitbucket.org/hpk42/pytest/commits/a34f61f77b84/ Changeset: a34f61f77b84 User: hpk42 Date: 2013-07-06 17:06:51 Summary: add kasia to changelog Affected #: 1 file diff -r 16e79a864e9661f52ef93a2a4afbeae3299664e7 -r a34f61f77b84b72e79cc7392451418204728d447 CHANGELOG --- a/CHANGELOG +++ b/CHANGELOG @@ -1,6 +1,9 @@ Changes between 2.3.5 and 2.4.DEV ----------------------------------- +- you can specify "-q" or "-qq" to get different levels of "quieter" + reporting (thanks Katarzyna Jachim) + - fix issue300 - Fix order of conftest loading when starting py.test in a subdirectory. 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 Jul 6 17:17:09 2013 From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org) Date: Sat, 06 Jul 2013 15:17:09 -0000 Subject: [Pytest-commit] commit/pytest: hpk42: add KAsia to authors Message-ID: <20130706151709.18668.56249@bitbucket15.managed.contegix.com> 1 new commit in pytest: https://bitbucket.org/hpk42/pytest/commits/63b5f85e68df/ Changeset: 63b5f85e68df User: hpk42 Date: 2013-07-06 17:17:03 Summary: add KAsia to authors Affected #: 1 file diff -r a34f61f77b84b72e79cc7392451418204728d447 -r 63b5f85e68df7242019300937e01d247f89e9361 AUTHORS --- a/AUTHORS +++ b/AUTHORS @@ -31,3 +31,5 @@ Graham Horler Andreas Zeidler Brian Okken +Katarzyna Jachim + 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 Jul 6 18:58:40 2013 From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org) Date: Sat, 06 Jul 2013 16:58:40 -0000 Subject: [Pytest-commit] commit/pytest: 2 new changesets Message-ID: <20130706165840.13131.25082@bitbucket03.managed.contegix.com> 2 new commits in pytest: https://bitbucket.org/hpk42/pytest/commits/a59fa6c971ab/ Changeset: a59fa6c971ab User: flub Date: 2013-07-06 17:56:54 Summary: Solve fixture ordering when loading plugins from conftest Conftests are plugins with a location attached to them while other plugins do not have a location. When ordering fixturedefs those from plugins without a location need to be listed first. Affected #: 2 files diff -r 63b5f85e68df7242019300937e01d247f89e9361 -r a59fa6c971abd42810301100464c4da7bc363e1d _pytest/python.py --- a/_pytest/python.py +++ b/_pytest/python.py @@ -1472,8 +1472,7 @@ def pytest_plugin_registered(self, plugin): if plugin in self._seenplugins: return - #print "plugin_registered", plugin - nodeid = "" + nodeid = None try: p = py.path.local(plugin.__file__) except AttributeError: @@ -1580,8 +1579,8 @@ for fin in reversed(l): fin() - def parsefactories(self, node_or_obj, nodeid=None, unittest=False): - if nodeid is not None: + def parsefactories(self, node_or_obj, nodeid=_dummy, unittest=False): + if nodeid is not _dummy: holderobj = node_or_obj else: holderobj = node_or_obj.obj @@ -1612,7 +1611,15 @@ marker.scope, marker.params, unittest=unittest) faclist = self._arg2fixturedefs.setdefault(name, []) - faclist.append(fixturedef) + if not fixturedef.has_location: + # All Nones are at the front so this inserts the + # current fixturedef after the existing fixturedefs + # from external plugins but before the fixturedefs + # provided in conftests. + i = faclist.count(None) + else: + i = len(faclist) # append + faclist.insert(i, fixturedef) if marker.autouse: autousenames.append(name) if autousenames: @@ -1670,7 +1677,8 @@ def __init__(self, fixturemanager, baseid, argname, func, scope, params, unittest=False): self._fixturemanager = fixturemanager - self.baseid = baseid + self.baseid = baseid or '' + self.has_location = baseid is not None self.func = func self.argname = argname self.scope = scope @@ -1721,7 +1729,8 @@ return result def __repr__(self): - return "" % (self.argname, self.scope) + return ("" % + (self.argname, self.scope, self.baseid, self.func.__module__)) def getfuncargnames(function, startindex=None): # XXX merge with main.py's varnames diff -r 63b5f85e68df7242019300937e01d247f89e9361 -r a59fa6c971abd42810301100464c4da7bc363e1d testing/python/fixture.py --- a/testing/python/fixture.py +++ b/testing/python/fixture.py @@ -173,6 +173,31 @@ result = testdir.runpytest(testfile) result.stdout.fnmatch_lines(["*1 passed*"]) + def test_extend_fixture_conftest_plugin(request, testdir): + testdir.makepyfile(testplugin=""" + import pytest + + @pytest.fixture + def foo(): + return 7 + """) + testdir.syspathinsert() + testdir.makeconftest(""" + import pytest + + pytest_plugins = 'testplugin' + + @pytest.fixture + def foo(foo): + return foo + 7 + """) + testdir.makepyfile(""" + def test_foo(foo): + assert foo == 14 + """) + result = testdir.runpytest('-s') + assert result.ret == 0 + def test_funcarg_lookup_error(self, testdir): p = testdir.makepyfile(""" def test_lookup_error(unknown): https://bitbucket.org/hpk42/pytest/commits/cc8871c02a5e/ Changeset: cc8871c02a5e User: flub Date: 2013-07-06 18:53:26 Summary: Always check for both ENOENT and ENOTDIR This fixes issue 326. Affected #: 1 file diff -r a59fa6c971abd42810301100464c4da7bc363e1d -r cc8871c02a5e4e34ad8e42121ffefb0651304b67 _pytest/assertion/rewrite.py --- a/_pytest/assertion/rewrite.py +++ b/_pytest/assertion/rewrite.py @@ -15,12 +15,6 @@ from _pytest.assertion import util -# Windows gives ENOENT in places *nix gives ENOTDIR. -if sys.platform.startswith("win"): - PATH_COMPONENT_NOT_DIR = errno.ENOENT -else: - PATH_COMPONENT_NOT_DIR = errno.ENOTDIR - # py.test caches rewritten pycs in __pycache__. if hasattr(imp, "get_tag"): PYTEST_TAG = imp.get_tag() + "-PYTEST" @@ -119,7 +113,7 @@ # common case) or it's blocked by a non-dir node. In the # latter case, we'll ignore it in _write_pyc. pass - elif e == PATH_COMPONENT_NOT_DIR: + elif e in [errno.ENOENT, errno.ENOTDIR]: # One of the path components was not a directory, likely # because we're in a zip file. write = False @@ -173,7 +167,7 @@ fp = open(pyc, "wb") except IOError: err = sys.exc_info()[1].errno - if err == PATH_COMPONENT_NOT_DIR: + if err in [errno.ENOENT, errno.ENOTDIR]: # This happens when we get a EEXIST in find_module creating the # __pycache__ directory and __pycache__ is by some non-dir node. return False 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 Jul 6 20:13:29 2013 From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org) Date: Sat, 06 Jul 2013 18:13:29 -0000 Subject: [Pytest-commit] commit/pytest: 2 new changesets Message-ID: <20130706181329.26548.34290@bitbucket02.managed.contegix.com> 2 new commits in pytest: https://bitbucket.org/hpk42/pytest/commits/af665883e9f2/ Changeset: af665883e9f2 Branch: 329-skipif-requires-expression-as-a-string User: bubenkoff Date: 2013-07-06 18:54:24 Summary: re #329 add test for skipif failure when you pass boolean without the reason. add emphasize to the docs. Affected #: 2 files diff -r 16e79a864e9661f52ef93a2a4afbeae3299664e7 -r af665883e9f2f79a4c4c06580f4aeaf2cb5bfdda doc/en/skipping.txt --- a/doc/en/skipping.txt +++ b/doc/en/skipping.txt @@ -10,11 +10,11 @@ may call helper functions during execution of setup or test functions. A *skip* means that you expect your test to pass unless the environment -(e.g. wrong Python interpreter, missing dependency) prevents it to run. +(e.g. wrong Python interpreter, missing dependency) prevents it to run. And *xfail* means that your test can run but you expect it to fail because there is an implementation problem. -py.test counts and lists *skip* and *xfail* tests separately. Detailed +py.test counts and lists *skip* and *xfail* tests separately. Detailed information about skipped/xfailed tests is not shown by default to avoid cluttering the output. You can use the ``-r`` option to see details corresponding to the "short" letters shown in the test progress:: @@ -35,15 +35,16 @@ when run on a Python3.3 interpreter:: import sys - @pytest.mark.skipif(sys.version_info >= (3,3), + @pytest.mark.skipif(sys.version_info >= (3,3), reason="requires python3.3") def test_function(): ... During test function setup the condition ("sys.version_info >= (3,3)") is checked. If it evaluates to True, the test function will be skipped -with the specified reason. Note that pytest enforces specifying a reason +with the specified reason. Note that pytest enforces specifying a reason in order to report meaningful "skip reasons" (e.g. when using ``-rs``). +If the condition is a string, it will be evaluated as python expression. You can share skipif markers between modules. Consider this test module:: @@ -69,7 +70,7 @@ where you define the markers which you then consistently apply throughout your test suite. -Alternatively, the pre pytest-2.4 way to specify `condition strings `_ instead of booleans will remain fully supported in future +Alternatively, the pre pytest-2.4 way to specify `condition strings `_ instead of booleans will remain fully supported in future versions of pytest. It couldn't be easily used for importing markers between test modules so it's no longer advertised as the primary method. @@ -78,10 +79,10 @@ --------------------------------------------- As with all function :ref:`marking ` you can skip test functions at the -`whole class- or module level`_. If your code targets python2.6 or above you +`whole class- or module level`_. If your code targets python2.6 or above you use the skipif decorator (and any other marker) on classes:: - @pytest.mark.skipif(sys.platform == 'win32', + @pytest.mark.skipif(sys.platform == 'win32', reason="requires windows") class TestPosixCalls: @@ -102,7 +103,7 @@ "will not be setup or run under 'win32' platform" As with the class-decorator, the ``pytestmark`` special name tells -py.test to apply it to each test function in the class. +py.test to apply it to each test function in the class. If you want to skip all test functions of a module, you must use the ``pytestmark`` name on the global level:: @@ -142,7 +143,7 @@ As with skipif_ you can also mark your expectation of a failure on a particular platform:: - @pytest.mark.xfail(sys.version_info >= (3,3), + @pytest.mark.xfail(sys.version_info >= (3,3), reason="python3.3 api changes") def test_function(): ... @@ -159,12 +160,12 @@ =========================== test session starts ============================ platform linux2 -- Python 2.7.3 -- pytest-2.3.5 collected 6 items - + xfail_demo.py xxxxxx ========================= short test summary info ========================== XFAIL xfail_demo.py::test_hello XFAIL xfail_demo.py::test_hello2 - reason: [NOTRUN] + reason: [NOTRUN] XFAIL xfail_demo.py::test_hello3 condition: hasattr(os, 'sep') XFAIL xfail_demo.py::test_hello4 @@ -173,7 +174,7 @@ condition: pytest.__version__[0] != "17" XFAIL xfail_demo.py::test_hello6 reason: reason - + ======================== 6 xfailed in 0.05 seconds ========================= .. _`skip/xfail with parametrize`: @@ -202,7 +203,7 @@ Imperative xfail from within a test or setup function ------------------------------------------------------ -If you cannot declare xfail- of skipif conditions at import +If you cannot declare xfail- of skipif conditions at import time you can also imperatively produce an according outcome imperatively, in test or setup code:: @@ -227,7 +228,7 @@ docutils = pytest.importorskip("docutils", minversion="0.3") -The version will be read from the specified +The version will be read from the specified module's ``__version__`` attribute. @@ -244,18 +245,18 @@ def test_function(): ... -During test function setup the skipif condition is evaluated by calling -``eval('sys.version_info >= (3,0)', namespace)``. The namespace contains +During test function setup the skipif condition is evaluated by calling +``eval('sys.version_info >= (3,0)', namespace)``. The namespace contains all the module globals, and ``os`` and ``sys`` as a minimum. -Since pytest-2.4 `condition booleans`_ are considered preferable +Since pytest-2.4 `condition booleans`_ are considered preferable because markers can then be freely imported between test modules. With strings you need to import not only the marker but all variables everything used by the marker, which violates encapsulation. The reason for specifying the condition as a string was that py.test can -report a summary of skip conditions based purely on the condition string. -With conditions as booleans you are required to specify a ``reason`` string. +report a summary of skip conditions based purely on the condition string. +With conditions as booleans you are required to specify a ``reason`` string. Note that string conditions will remain fully supported and you are free to use them if you have no need for cross-importing markers. @@ -266,7 +267,7 @@ * the namespace is initialized by putting the ``sys`` and ``os`` modules and the pytest ``config`` object into it. - + * updated with the module globals of the test function for which the expression is applied. diff -r 16e79a864e9661f52ef93a2a4afbeae3299664e7 -r af665883e9f2f79a4c4c06580f4aeaf2cb5bfdda testing/test_skipping.py --- a/testing/test_skipping.py +++ b/testing/test_skipping.py @@ -100,6 +100,18 @@ expl = ev.getexplanation() assert expl == "condition: not hasattr(os, 'murks')" + def test_marked_skip_with_not_string(self, testdir): + item = testdir.getitem(""" + import pytest + @pytest.mark.skipif(False) + def test_func(): + pass + """) + ev = MarkEvaluator(item, 'skipif') + with pytest.raises(pytest.fail.Exception) as exc: + assert ev.istrue() + assert """Failed: you need to specify reason=STRING when using booleans as conditions.""" in exc.value.msg + def test_skipif_class(self, testdir): item, = testdir.getitems(""" import pytest https://bitbucket.org/hpk42/pytest/commits/f20b8a999d37/ Changeset: f20b8a999d37 User: hpk42 Date: 2013-07-06 20:13:27 Summary: Merged in bubenkoff/pytest/329-skipif-requires-expression-as-a-string (pull request #43) re #329 add test for skipif failure when you pass boolean without the reason. add emphasize to the docs. Affected #: 2 files diff -r cc8871c02a5e4e34ad8e42121ffefb0651304b67 -r f20b8a999d37a9f5b18d914c011a065ecbd543ee doc/en/skipping.txt --- a/doc/en/skipping.txt +++ b/doc/en/skipping.txt @@ -10,11 +10,11 @@ may call helper functions during execution of setup or test functions. A *skip* means that you expect your test to pass unless the environment -(e.g. wrong Python interpreter, missing dependency) prevents it to run. +(e.g. wrong Python interpreter, missing dependency) prevents it to run. And *xfail* means that your test can run but you expect it to fail because there is an implementation problem. -py.test counts and lists *skip* and *xfail* tests separately. Detailed +py.test counts and lists *skip* and *xfail* tests separately. Detailed information about skipped/xfailed tests is not shown by default to avoid cluttering the output. You can use the ``-r`` option to see details corresponding to the "short" letters shown in the test progress:: @@ -35,15 +35,16 @@ when run on a Python3.3 interpreter:: import sys - @pytest.mark.skipif(sys.version_info >= (3,3), + @pytest.mark.skipif(sys.version_info >= (3,3), reason="requires python3.3") def test_function(): ... During test function setup the condition ("sys.version_info >= (3,3)") is checked. If it evaluates to True, the test function will be skipped -with the specified reason. Note that pytest enforces specifying a reason +with the specified reason. Note that pytest enforces specifying a reason in order to report meaningful "skip reasons" (e.g. when using ``-rs``). +If the condition is a string, it will be evaluated as python expression. You can share skipif markers between modules. Consider this test module:: @@ -69,7 +70,7 @@ where you define the markers which you then consistently apply throughout your test suite. -Alternatively, the pre pytest-2.4 way to specify `condition strings `_ instead of booleans will remain fully supported in future +Alternatively, the pre pytest-2.4 way to specify `condition strings `_ instead of booleans will remain fully supported in future versions of pytest. It couldn't be easily used for importing markers between test modules so it's no longer advertised as the primary method. @@ -78,10 +79,10 @@ --------------------------------------------- As with all function :ref:`marking ` you can skip test functions at the -`whole class- or module level`_. If your code targets python2.6 or above you +`whole class- or module level`_. If your code targets python2.6 or above you use the skipif decorator (and any other marker) on classes:: - @pytest.mark.skipif(sys.platform == 'win32', + @pytest.mark.skipif(sys.platform == 'win32', reason="requires windows") class TestPosixCalls: @@ -102,7 +103,7 @@ "will not be setup or run under 'win32' platform" As with the class-decorator, the ``pytestmark`` special name tells -py.test to apply it to each test function in the class. +py.test to apply it to each test function in the class. If you want to skip all test functions of a module, you must use the ``pytestmark`` name on the global level:: @@ -142,7 +143,7 @@ As with skipif_ you can also mark your expectation of a failure on a particular platform:: - @pytest.mark.xfail(sys.version_info >= (3,3), + @pytest.mark.xfail(sys.version_info >= (3,3), reason="python3.3 api changes") def test_function(): ... @@ -159,12 +160,12 @@ =========================== test session starts ============================ platform linux2 -- Python 2.7.3 -- pytest-2.3.5 collected 6 items - + xfail_demo.py xxxxxx ========================= short test summary info ========================== XFAIL xfail_demo.py::test_hello XFAIL xfail_demo.py::test_hello2 - reason: [NOTRUN] + reason: [NOTRUN] XFAIL xfail_demo.py::test_hello3 condition: hasattr(os, 'sep') XFAIL xfail_demo.py::test_hello4 @@ -173,7 +174,7 @@ condition: pytest.__version__[0] != "17" XFAIL xfail_demo.py::test_hello6 reason: reason - + ======================== 6 xfailed in 0.05 seconds ========================= .. _`skip/xfail with parametrize`: @@ -202,7 +203,7 @@ Imperative xfail from within a test or setup function ------------------------------------------------------ -If you cannot declare xfail- of skipif conditions at import +If you cannot declare xfail- of skipif conditions at import time you can also imperatively produce an according outcome imperatively, in test or setup code:: @@ -227,7 +228,7 @@ docutils = pytest.importorskip("docutils", minversion="0.3") -The version will be read from the specified +The version will be read from the specified module's ``__version__`` attribute. @@ -244,18 +245,18 @@ def test_function(): ... -During test function setup the skipif condition is evaluated by calling -``eval('sys.version_info >= (3,0)', namespace)``. The namespace contains +During test function setup the skipif condition is evaluated by calling +``eval('sys.version_info >= (3,0)', namespace)``. The namespace contains all the module globals, and ``os`` and ``sys`` as a minimum. -Since pytest-2.4 `condition booleans`_ are considered preferable +Since pytest-2.4 `condition booleans`_ are considered preferable because markers can then be freely imported between test modules. With strings you need to import not only the marker but all variables everything used by the marker, which violates encapsulation. The reason for specifying the condition as a string was that py.test can -report a summary of skip conditions based purely on the condition string. -With conditions as booleans you are required to specify a ``reason`` string. +report a summary of skip conditions based purely on the condition string. +With conditions as booleans you are required to specify a ``reason`` string. Note that string conditions will remain fully supported and you are free to use them if you have no need for cross-importing markers. @@ -266,7 +267,7 @@ * the namespace is initialized by putting the ``sys`` and ``os`` modules and the pytest ``config`` object into it. - + * updated with the module globals of the test function for which the expression is applied. diff -r cc8871c02a5e4e34ad8e42121ffefb0651304b67 -r f20b8a999d37a9f5b18d914c011a065ecbd543ee testing/test_skipping.py --- a/testing/test_skipping.py +++ b/testing/test_skipping.py @@ -100,6 +100,18 @@ expl = ev.getexplanation() assert expl == "condition: not hasattr(os, 'murks')" + def test_marked_skip_with_not_string(self, testdir): + item = testdir.getitem(""" + import pytest + @pytest.mark.skipif(False) + def test_func(): + pass + """) + ev = MarkEvaluator(item, 'skipif') + with pytest.raises(pytest.fail.Exception) as exc: + assert ev.istrue() + assert """Failed: you need to specify reason=STRING when using booleans as conditions.""" in exc.value.msg + def test_skipif_class(self, testdir): item, = testdir.getitems(""" 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 Mon Jul 8 15:39:19 2013 From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org) Date: Mon, 08 Jul 2013 13:39:19 -0000 Subject: [Pytest-commit] commit/pytest: hpk42: fix docs wrt norecursedirs, thanks @mgax Message-ID: <20130708133919.2874.79827@app18.ash-private.bitbucket.org> 1 new commit in pytest: https://bitbucket.org/hpk42/pytest/commits/bcf6f8c25f89/ Changeset: bcf6f8c25f89 User: hpk42 Date: 2013-07-08 15:39:14 Summary: fix docs wrt norecursedirs, thanks @mgax Affected #: 1 file diff -r f20b8a999d37a9f5b18d914c011a065ecbd543ee -r bcf6f8c25f89879da9dc994fec8320ad5d1912cc doc/en/customize.txt --- a/doc/en/customize.txt +++ b/doc/en/customize.txt @@ -97,7 +97,7 @@ [seq] matches any character in seq [!seq] matches any char not in seq - Default patterns are ``.* _* CVS {args}``. Setting a ``norecursedir`` + Default patterns are ``.* _darcs CVS {args}``. Setting a ``norecursedir`` replaces the default. Here is an example of how to avoid certain directories:: 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 Jul 8 15:55:41 2013 From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org) Date: Mon, 08 Jul 2013 13:55:41 -0000 Subject: [Pytest-commit] commit/pytest: 3 new changesets Message-ID: <20130708135541.1663.71900@app01.ash-private.bitbucket.org> 3 new commits in pytest: https://bitbucket.org/hpk42/pytest/commits/1c51eba0e480/ Changeset: 1c51eba0e480 Branch: 320-class-scoped-fixture-caching-is-broken-if User: bubenkoff Date: 2013-07-06 21:30:24 Summary: re #320 fallback to test scope if the class-scoped fixture is used in non-class-based test function Affected #: 3 files diff -r cc8871c02a5e4e34ad8e42121ffefb0651304b67 -r 1c51eba0e48065351d33a0087710dae4d49b768d _pytest/python.py --- a/_pytest/python.py +++ b/_pytest/python.py @@ -1317,7 +1317,8 @@ x = self._pyfuncitem.getparent(pytest.Class) if x is not None: return x - scope = "module" + # fallback to function + return self._pyfuncitem if scope == "module": return self._pyfuncitem.getparent(pytest.Module) raise ValueError("unknown finalization scope %r" %(scope,)) diff -r cc8871c02a5e4e34ad8e42121ffefb0651304b67 -r 1c51eba0e48065351d33a0087710dae4d49b768d testing/python/fixture.py --- a/testing/python/fixture.py +++ b/testing/python/fixture.py @@ -513,12 +513,12 @@ def test_request_cachedsetup_class(self, testdir): reprec = testdir.inline_runsource(""" - mysetup = ["hello", "hello2"].pop + mysetup = ["hello", "hello2", "hello3"].pop def pytest_funcarg__something(request): return request.cached_setup(mysetup, scope="class") def test_func1(something): - assert something == "hello2" + assert something == "hello3" def test_func2(something): assert something == "hello2" class TestClass: @@ -1090,7 +1090,7 @@ def arg(): l.append(1) return 0 - @pytest.fixture(scope="class", autouse=True) + @pytest.fixture(scope="module", autouse=True) def something(arg): l.append(2) diff -r cc8871c02a5e4e34ad8e42121ffefb0651304b67 -r 1c51eba0e48065351d33a0087710dae4d49b768d testing/test_fixture_scope.py --- /dev/null +++ b/testing/test_fixture_scope.py @@ -0,0 +1,28 @@ +"""Tests for fixtures with different scoping.""" + + +def test_class_scope_with_normal_tests(testdir): + testpath = testdir.makepyfile(""" + import pytest + + class Box: + value = 0 + + @pytest.fixture(scope='class') + def a(request): + Box.value += 1 + return Box.value + + def test_a(a): + assert a == 1 + + class Test1: + def test_b(self, a): + assert a == 2 + + class Test2: + def test_c(self, a): + assert a == 3""") + reprec = testdir.inline_run(testpath) + for test in ['test_a', 'test_b', 'test_c']: + assert reprec.matchreport(test).passed https://bitbucket.org/hpk42/pytest/commits/635c9c36e481/ Changeset: 635c9c36e481 Branch: 320-class-scoped-fixture-caching-is-broken-if User: hpk42 Date: 2013-07-08 15:45:07 Summary: Close branch 320-class-scoped-fixture-caching-is-broken-if Affected #: 0 files https://bitbucket.org/hpk42/pytest/commits/602a8ebee9ba/ Changeset: 602a8ebee9ba User: hpk42 Date: 2013-07-08 15:54:38 Summary: fix issue320 - fix class scope for fixtures when mixed with module-level functions. Thanks Anatloy Bubenkoff. Affected #: 5 files diff -r bcf6f8c25f89879da9dc994fec8320ad5d1912cc -r 602a8ebee9ba44b91699ad86426711f0aba35f7b AUTHORS --- a/AUTHORS +++ b/AUTHORS @@ -9,6 +9,7 @@ Jason R. Coombs Wouter van Ackooy Samuele Pedroni +Anatoly Bubenkoff Brianna Laugher Carl Friedrich Bolz Armin Rigo diff -r bcf6f8c25f89879da9dc994fec8320ad5d1912cc -r 602a8ebee9ba44b91699ad86426711f0aba35f7b CHANGELOG --- a/CHANGELOG +++ b/CHANGELOG @@ -1,6 +1,9 @@ Changes between 2.3.5 and 2.4.DEV ----------------------------------- +- fix issue320 - fix class scope for fixtures when mixed with + module-level functions. Thanks Anatloy Bubenkoff. + - you can specify "-q" or "-qq" to get different levels of "quieter" reporting (thanks Katarzyna Jachim) diff -r bcf6f8c25f89879da9dc994fec8320ad5d1912cc -r 602a8ebee9ba44b91699ad86426711f0aba35f7b _pytest/python.py --- a/_pytest/python.py +++ b/_pytest/python.py @@ -1317,7 +1317,8 @@ x = self._pyfuncitem.getparent(pytest.Class) if x is not None: return x - scope = "module" + # fallback to function + return self._pyfuncitem if scope == "module": return self._pyfuncitem.getparent(pytest.Module) raise ValueError("unknown finalization scope %r" %(scope,)) diff -r bcf6f8c25f89879da9dc994fec8320ad5d1912cc -r 602a8ebee9ba44b91699ad86426711f0aba35f7b testing/python/fixture.py --- a/testing/python/fixture.py +++ b/testing/python/fixture.py @@ -513,12 +513,12 @@ def test_request_cachedsetup_class(self, testdir): reprec = testdir.inline_runsource(""" - mysetup = ["hello", "hello2"].pop + mysetup = ["hello", "hello2", "hello3"].pop def pytest_funcarg__something(request): return request.cached_setup(mysetup, scope="class") def test_func1(something): - assert something == "hello2" + assert something == "hello3" def test_func2(something): assert something == "hello2" class TestClass: @@ -1090,7 +1090,7 @@ def arg(): l.append(1) return 0 - @pytest.fixture(scope="class", autouse=True) + @pytest.fixture(scope="module", autouse=True) def something(arg): l.append(2) diff -r bcf6f8c25f89879da9dc994fec8320ad5d1912cc -r 602a8ebee9ba44b91699ad86426711f0aba35f7b testing/test_fixture_scope.py --- /dev/null +++ b/testing/test_fixture_scope.py @@ -0,0 +1,28 @@ +"""Tests for fixtures with different scoping.""" + + +def test_class_scope_with_normal_tests(testdir): + testpath = testdir.makepyfile(""" + import pytest + + class Box: + value = 0 + + @pytest.fixture(scope='class') + def a(request): + Box.value += 1 + return Box.value + + def test_a(a): + assert a == 1 + + class Test1: + def test_b(self, a): + assert a == 2 + + class Test2: + def test_c(self, a): + assert a == 3""") + reprec = testdir.inline_run(testpath) + for test in ['test_a', 'test_b', 'test_c']: + assert reprec.matchreport(test).passed 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 Jul 8 16:27:57 2013 From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org) Date: Mon, 08 Jul 2013 14:27:57 -0000 Subject: [Pytest-commit] commit/tox: magopian: fixes #87 : unset VIRTUALENV_PYTHON before calling virtualenv Message-ID: <20130708142757.30313.42936@app07.ash-private.bitbucket.org> 1 new commit in tox: https://bitbucket.org/hpk42/tox/commits/7be299d36432/ Changeset: 7be299d36432 User: magopian Date: 2013-07-06 18:16:30 Summary: fixes #87 : unset VIRTUALENV_PYTHON before calling virtualenv Affected #: 2 files diff -r d315272501bafb1a799fad80b2555002f9bd524d -r 7be299d36432aab33fd7f7ffc0ee5dfd5df6dbf8 tests/test_z_cmdline.py --- a/tests/test_z_cmdline.py +++ b/tests/test_z_cmdline.py @@ -382,6 +382,15 @@ "*create*", ]) +def test_env_VIRTUALENV_PYTHON(initproj, cmd, monkeypatch): + initproj("example123", filedefs={'tox.ini': ''}) + monkeypatch.setenv("VIRTUALENV_PYTHON", '/FOO') + result = cmd.run("tox", "-v", "--notest") + assert not result.ret, result.stdout.lines + result.stdout.fnmatch_lines([ + "*create*", + ]) + def test_sdistonly(initproj, cmd): initproj("example123", filedefs={'tox.ini': """ """}) diff -r d315272501bafb1a799fad80b2555002f9bd524d -r 7be299d36432aab33fd7f7ffc0ee5dfd5df6dbf8 tox/_venv.py --- a/tox/_venv.py +++ b/tox/_venv.py @@ -301,6 +301,10 @@ def _pcall(self, args, venv=True, cwd=None, extraenv={}, action=None, redirect=True): + try: + del os.environ['VIRTUALENV_PYTHON'] + except KeyError: + pass assert cwd cwd.ensure(dir=1) old = self.patchPATH() 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 Tue Jul 9 13:40:10 2013 From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org) Date: Tue, 09 Jul 2013 11:40:10 -0000 Subject: [Pytest-commit] commit/tox: hpk42: always unset PYTHONDONTWRITEBYTE because newer setuptools doesn't like it Message-ID: <20130709114010.25083.68003@app17.ash-private.bitbucket.org> 1 new commit in tox: https://bitbucket.org/hpk42/tox/commits/63d37ba9cc8b/ Changeset: 63d37ba9cc8b User: hpk42 Date: 2013-07-09 13:40:04 Summary: always unset PYTHONDONTWRITEBYTE because newer setuptools doesn't like it Affected #: 6 files diff -r 7be299d36432aab33fd7f7ffc0ee5dfd5df6dbf8 -r 63d37ba9cc8babe926e45f9e8294841148b0451b CHANGELOG --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,8 @@ +1.5.1.dev +----------------- + +- always unset PYTHONDONTWRITEBYTE because newer setuptools doesn't like it + 1.5.0 ----------------- diff -r 7be299d36432aab33fd7f7ffc0ee5dfd5df6dbf8 -r 63d37ba9cc8babe926e45f9e8294841148b0451b doc/index.txt --- a/doc/index.txt +++ b/doc/index.txt @@ -1,7 +1,8 @@ Welcome to the tox automation project =============================================== -.. note:: Upcoming: `professional testing with pytest and tox `_ , 24th-26th June 2013, Leipzig. +.. + Upcoming: `professional testing with pytest and tox `_ , 24th-26th June 2013, Leipzig. vision: standardize testing in Python --------------------------------------------- diff -r 7be299d36432aab33fd7f7ffc0ee5dfd5df6dbf8 -r 63d37ba9cc8babe926e45f9e8294841148b0451b setup.py --- a/setup.py +++ b/setup.py @@ -26,7 +26,7 @@ description='virtualenv-based automation of test activities', long_description=open("README.rst").read(), url='http://tox.testrun.org/', - version='1.5.0', + version='1.5.1.dev1', license='http://opensource.org/licenses/MIT', platforms=['unix', 'linux', 'osx', 'cygwin', 'win32'], author='holger krekel', diff -r 7be299d36432aab33fd7f7ffc0ee5dfd5df6dbf8 -r 63d37ba9cc8babe926e45f9e8294841148b0451b tox/__init__.py --- a/tox/__init__.py +++ b/tox/__init__.py @@ -1,5 +1,5 @@ # -__version__ = '1.5.0' +__version__ = '1.5.1.dev1' class exception: class Error(Exception): diff -r 7be299d36432aab33fd7f7ffc0ee5dfd5df6dbf8 -r 63d37ba9cc8babe926e45f9e8294841148b0451b tox/_venv.py --- a/tox/_venv.py +++ b/tox/_venv.py @@ -301,10 +301,11 @@ def _pcall(self, args, venv=True, cwd=None, extraenv={}, action=None, redirect=True): - try: - del os.environ['VIRTUALENV_PYTHON'] - except KeyError: - pass + for name in ("VIRTUALENV_PYTHON", "PYTHONDONTWRITEBYTECODE"): + try: + del os.environ[name] + except KeyError: + pass assert cwd cwd.ensure(dir=1) old = self.patchPATH() diff -r 7be299d36432aab33fd7f7ffc0ee5dfd5df6dbf8 -r 63d37ba9cc8babe926e45f9e8294841148b0451b toxbootstrap.py --- a/toxbootstrap.py +++ b/toxbootstrap.py @@ -58,7 +58,7 @@ """ -__version__ = '1.5.0' +__version__ = '1.5.1.dev1' import sys import os 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 Jul 10 14:30:15 2013 From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org) Date: Wed, 10 Jul 2013 12:30:15 -0000 Subject: [Pytest-commit] commit/pytest: bubenkoff: Update README.rst Message-ID: <20130710123015.460.56648@app15.ash-private.bitbucket.org> 1 new commit in pytest: https://bitbucket.org/hpk42/pytest/commits/9d9f4a158f2f/ Changeset: 9d9f4a158f2f User: bubenkoff Date: 2013-07-10 13:51:43 Summary: Update README.rst change the travis link and image to be correct Affected #: 1 file diff -r 602a8ebee9ba44b91699ad86426711f0aba35f7b -r 9d9f4a158f2f1b8e4094584701eb6e45168d0d7c README.rst --- a/README.rst +++ b/README.rst @@ -1,4 +1,3 @@ - The ``py.test`` testing tool makes it easy to write small tests, yet scales to support complex functional testing. It provides @@ -16,8 +15,8 @@ - many `external plugins `_. -.. image:: https://secure.travis-ci.org/bubenkoff/pytest.png?branch=travis-integration - :target: http://travis-ci.org/bubenkoff/pytest +.. image:: https://secure.travis-ci.org/hpk42/pytest.png?branch=travis-integration + :target: http://travis-ci.org/hpk42/pytest A simple example for a test:: 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 Jul 11 10:51:17 2013 From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org) Date: Thu, 11 Jul 2013 08:51:17 -0000 Subject: [Pytest-commit] commit/pytest: hpk42: change copyright years Message-ID: <20130711085117.637.82129@app08.ash-private.bitbucket.org> 1 new commit in pytest: https://bitbucket.org/hpk42/pytest/commits/590415123958/ Changeset: 590415123958 User: hpk42 Date: 2013-07-11 10:50:59 Summary: change copyright years Affected #: 1 file diff -r 9d9f4a158f2f1b8e4094584701eb6e45168d0d7c -r 5904151239588a7c187932b044c4e480f6965504 README.rst --- a/README.rst +++ b/README.rst @@ -35,4 +35,4 @@ http://bitbucket.org/hpk42/pytest/issues/ -Copyright Holger Krekel and others, 2004-2012 +Copyright Holger Krekel and others, 2004-2013 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 Jul 11 11:13:16 2013 From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org) Date: Thu, 11 Jul 2013 09:13:16 -0000 Subject: [Pytest-commit] commit/tox: 10 new changesets Message-ID: <20130711091316.21551.77833@app02.ash-private.bitbucket.org> 10 new commits in tox: https://bitbucket.org/hpk42/tox/commits/cd64bee5b235/ Changeset: cd64bee5b235 User: lukaszb Date: 2013-06-14 18:24:17 Summary: Created initial documentation for development environments -- related with #101 Affected #: 2 files diff -r 1c746939df7f57bae11fe81010446d5c8dc3dae6 -r cd64bee5b235661b8b2dc13294a6cc55603a6812 doc/example/devenv.txt --- /dev/null +++ b/doc/example/devenv.txt @@ -0,0 +1,64 @@ + +Development environment +======================= + +Tox can be used to prepare development virtual environment for local projects. +This feature can be useful in order to preserve environment across team members +working on same project. It can be also used by deployment tools to prepare +proper environments. + +*devenv* would be created at specific directory, not within ``.tox`` directory +as other test environments. Other than that, configuration for this environment +is very similar to other tox envs. + + +Configuration +------------- + +Firstly, you need to prepare configuration for your development environment. In +order to do that, we must define proper section at ``tox.ini`` file and tell at +what directory environment should be created. Moreover, we need to specify +python version that should be picked:: + + [devenv] + envdir = devenv + basepython = python2.7 + + +Actually, you can configure a lot more, those are the only required settings. +In example you can add ``deps`` and ``commands`` settings. + + +.. note:: ``envdir`` should be *relative* path to where ``tox.ini`` is located. + + +Creating development environment +-------------------------------- + +Once ``devenv`` section is defined we can instrument tox to create our +environment:: + + tox --devenv + +This will create an environment at path specified by ``envdir`` under ``devenv`` +section. + + + +Full configuration example +-------------------------- + +Let's say we want our development environment sit at ``devenv``. We create this +directory manually and put ``requirements.txt`` file there. We want to work on +Python 2.7. + +Here is example configuration for that:: + + [devenv] + envdir = devenv + changedir = devenv + basepython = python2.7 + commands = + pip install -r requirements.txt + + diff -r 1c746939df7f57bae11fe81010446d5c8dc3dae6 -r cd64bee5b235661b8b2dc13294a6cc55603a6812 doc/examples.txt --- a/doc/examples.txt +++ b/doc/examples.txt @@ -11,4 +11,5 @@ example/nose.txt example/general.txt example/jenkins.txt + example/devenv.txt https://bitbucket.org/hpk42/tox/commits/8b6f18ce8d64/ Changeset: 8b6f18ce8d64 User: lukaszb Date: 2013-06-20 20:31:16 Summary: Updated documentation for development environment Affected #: 2 files diff -r cd64bee5b235661b8b2dc13294a6cc55603a6812 -r 8b6f18ce8d64eff4e9b3776a7553e8231d7a6ec1 doc/config.txt --- a/doc/config.txt +++ b/doc/config.txt @@ -163,6 +163,14 @@ (including when installing the project sdist package). +.. confval:: envdir + + .. versionadded:: 1.5 + + User can set specific path for environment. If path would not be absolute it + would be treated as relative to ``{toxinidir}``. **default**: + ``{toxworkdir}/{envname}`` + Substitutions --------------------- diff -r cd64bee5b235661b8b2dc13294a6cc55603a6812 -r 8b6f18ce8d64eff4e9b3776a7553e8231d7a6ec1 doc/example/devenv.txt --- a/doc/example/devenv.txt +++ b/doc/example/devenv.txt @@ -20,7 +20,7 @@ what directory environment should be created. Moreover, we need to specify python version that should be picked:: - [devenv] + [testenv:devenv] envdir = devenv basepython = python2.7 @@ -38,10 +38,10 @@ Once ``devenv`` section is defined we can instrument tox to create our environment:: - tox --devenv + tox -e devenv --envonly -This will create an environment at path specified by ``envdir`` under ``devenv`` -section. +This will create an environment at path specified by ``envdir`` under +``[testenv:devenv]`` section. @@ -54,7 +54,7 @@ Here is example configuration for that:: - [devenv] + [testenv:devenv] envdir = devenv changedir = devenv basepython = python2.7 https://bitbucket.org/hpk42/tox/commits/9c8a6ba3a8c0/ Changeset: 9c8a6ba3a8c0 User: lukaszb Date: 2013-06-20 21:05:30 Summary: Added tests for envdir config Affected #: 1 file diff -r 8b6f18ce8d64eff4e9b3776a7553e8231d7a6ec1 -r 9c8a6ba3a8c0913d582da4ed9b1913e8d0664fde tests/test_config.py --- a/tests/test_config.py +++ b/tests/test_config.py @@ -44,6 +44,22 @@ assert dep2.indexserver.name == "xyz" assert dep2.indexserver.url == "xyz_repo" + def test_envdir_set_manually(self, tmpdir, newconfig): + config = newconfig([], """ + [testenv:devenv] + envdir = devenv + """) + envconfig = config.envconfigs['devenv'] + assert envconfig.envdir == tmpdir.join('devenv') + + def test_envdir_set_manually_with_substitutions(self, tmpdir, newconfig): + config = newconfig([], """ + [testenv:devenv] + envdir = {toxworkdir}/foobar + """) + envconfig = config.envconfigs['devenv'] + assert envconfig.envdir == config.toxworkdir.join('foobar') + class TestConfigPackage: def test_defaults(self, tmpdir, newconfig): config = newconfig([], "") https://bitbucket.org/hpk42/tox/commits/20ef6ad9ee7d/ Changeset: 20ef6ad9ee7d User: lukaszb Date: 2013-06-20 22:06:49 Summary: Dropped note from devenv - envdir is now better documented at config.txt Affected #: 1 file diff -r 9c8a6ba3a8c0913d582da4ed9b1913e8d0664fde -r 20ef6ad9ee7d952a603c7449b3478ad6b00f28b2 doc/example/devenv.txt --- a/doc/example/devenv.txt +++ b/doc/example/devenv.txt @@ -29,8 +29,6 @@ In example you can add ``deps`` and ``commands`` settings. -.. note:: ``envdir`` should be *relative* path to where ``tox.ini`` is located. - Creating development environment -------------------------------- https://bitbucket.org/hpk42/tox/commits/64a59fad08de/ Changeset: 64a59fad08de User: lukaszb Date: 2013-06-20 22:13:01 Summary: Removed --envonly switch from devenv docs Affected #: 1 file diff -r 20ef6ad9ee7d952a603c7449b3478ad6b00f28b2 -r 64a59fad08de78bdc58fa941eba29b37eeb8f9f8 doc/example/devenv.txt --- a/doc/example/devenv.txt +++ b/doc/example/devenv.txt @@ -36,7 +36,7 @@ Once ``devenv`` section is defined we can instrument tox to create our environment:: - tox -e devenv --envonly + tox -e devenv This will create an environment at path specified by ``envdir`` under ``[testenv:devenv]`` section. https://bitbucket.org/hpk42/tox/commits/a8b1cc2184d5/ Changeset: a8b1cc2184d5 User: lukaszb Date: 2013-06-20 23:20:02 Summary: Documented 'package' config for test envs Affected #: 2 files diff -r 64a59fad08de78bdc58fa941eba29b37eeb8f9f8 -r a8b1cc2184d5241ce0df85e06c4d7a07637f2e91 doc/config.txt --- a/doc/config.txt +++ b/doc/config.txt @@ -171,6 +171,16 @@ would be treated as relative to ``{toxinidir}``. **default**: ``{toxworkdir}/{envname}`` + +.. confval:: package=BOOL + + .. versionadded:: 1.5 + + Set to ``False`` if you don't want tox to perform package-related steps + (building source distribution and installing it). Might be useful for + non-package projects (i.e. all that lacks ``setup.py`` file). **default**: + ``True``. + Substitutions --------------------- diff -r 64a59fad08de78bdc58fa941eba29b37eeb8f9f8 -r a8b1cc2184d5241ce0df85e06c4d7a07637f2e91 doc/example/devenv.txt --- a/doc/example/devenv.txt +++ b/doc/example/devenv.txt @@ -18,11 +18,13 @@ Firstly, you need to prepare configuration for your development environment. In order to do that, we must define proper section at ``tox.ini`` file and tell at what directory environment should be created. Moreover, we need to specify -python version that should be picked:: +python version that should be picked and tell tox not to perform any package +related steps:: [testenv:devenv] envdir = devenv basepython = python2.7 + package = False Actually, you can configure a lot more, those are the only required settings. @@ -53,6 +55,7 @@ Here is example configuration for that:: [testenv:devenv] + package = False envdir = devenv changedir = devenv basepython = python2.7 https://bitbucket.org/hpk42/tox/commits/0eacb212fed4/ Changeset: 0eacb212fed4 User: lukaszb Date: 2013-06-20 23:42:02 Summary: Added package flag to venv config Affected #: 2 files diff -r a8b1cc2184d5241ce0df85e06c4d7a07637f2e91 -r 0eacb212fed4a9fce77fd6ffc2cf2dd619387835 tests/test_config.py --- a/tests/test_config.py +++ b/tests/test_config.py @@ -60,6 +60,20 @@ envconfig = config.envconfigs['devenv'] assert envconfig.envdir == config.toxworkdir.join('foobar') + def test_package_flag(self, tmpdir, newconfig): + config = newconfig([], """ + [testenv:py27] + """) + envconfig = config.envconfigs['py27'] + assert envconfig.package == True + + config = newconfig([], """ + [testenv:py27] + package = False + """) + envconfig = config.envconfigs['py27'] + assert envconfig.package == False + class TestConfigPackage: def test_defaults(self, tmpdir, newconfig): config = newconfig([], "") diff -r a8b1cc2184d5241ce0df85e06c4d7a07637f2e91 -r 0eacb212fed4a9fce77fd6ffc2cf2dd619387835 tox/_config.py --- a/tox/_config.py +++ b/tox/_config.py @@ -256,6 +256,7 @@ reader = IniReader(self._cfg, fallbacksections=["testenv"]) reader.addsubstitions(**subs) vc.envdir = reader.getpath(section, "envdir", "{toxworkdir}/%s" % name) + vc.package = reader.getbool(section, "package", True) vc.args_are_paths = reader.getbool(section, "args_are_paths", True) if reader.getdefault(section, "python", None): raise tox.exception.ConfigError( https://bitbucket.org/hpk42/tox/commits/eacb5ca142ab/ Changeset: eacb5ca142ab User: lukaszb Date: 2013-07-10 23:21:14 Summary: Simplified devenv example docs to use only existing interfaces. Affected #: 1 file diff -r 0eacb212fed4a9fce77fd6ffc2cf2dd619387835 -r eacb5ca142abf0b855d9ed2f8d61857b43ced268 doc/example/devenv.txt --- a/doc/example/devenv.txt +++ b/doc/example/devenv.txt @@ -7,10 +7,6 @@ working on same project. It can be also used by deployment tools to prepare proper environments. -*devenv* would be created at specific directory, not within ``.tox`` directory -as other test environments. Other than that, configuration for this environment -is very similar to other tox envs. - Configuration ------------- @@ -18,18 +14,18 @@ Firstly, you need to prepare configuration for your development environment. In order to do that, we must define proper section at ``tox.ini`` file and tell at what directory environment should be created. Moreover, we need to specify -python version that should be picked and tell tox not to perform any package -related steps:: +python version that should be picked:: [testenv:devenv] envdir = devenv basepython = python2.7 - package = False + commands = + deps = Actually, you can configure a lot more, those are the only required settings. -In example you can add ``deps`` and ``commands`` settings. - +In example you can add ``deps`` and ``commands`` settings. Here, we tell tox +not to pick ``commands`` or ``deps`` from base ``testenv`` configuration. Creating development environment @@ -44,22 +40,18 @@ ``[testenv:devenv]`` section. - Full configuration example -------------------------- -Let's say we want our development environment sit at ``devenv``. We create this -directory manually and put ``requirements.txt`` file there. We want to work on -Python 2.7. +Let's say we want our development environment sit at ``devenv`` and pull +packages from ``requirements.txt`` file which we create at the same directory +as ``tox.ini`` file. We also want to speciy Python version to be 2.7. Here is example configuration for that:: [testenv:devenv] - package = False envdir = devenv - changedir = devenv basepython = python2.7 commands = pip install -r requirements.txt - https://bitbucket.org/hpk42/tox/commits/ecb095208e42/ Changeset: ecb095208e42 User: lukaszb Date: 2013-07-10 23:57:04 Summary: Removed obsolates from devenv doc Affected #: 3 files diff -r eacb5ca142abf0b855d9ed2f8d61857b43ced268 -r ecb095208e429b8df180c36b4c8e8422acca89a7 doc/config.txt --- a/doc/config.txt +++ b/doc/config.txt @@ -172,15 +172,6 @@ ``{toxworkdir}/{envname}`` -.. confval:: package=BOOL - - .. versionadded:: 1.5 - - Set to ``False`` if you don't want tox to perform package-related steps - (building source distribution and installing it). Might be useful for - non-package projects (i.e. all that lacks ``setup.py`` file). **default**: - ``True``. - Substitutions --------------------- diff -r eacb5ca142abf0b855d9ed2f8d61857b43ced268 -r ecb095208e429b8df180c36b4c8e8422acca89a7 doc/example/devenv.txt --- a/doc/example/devenv.txt +++ b/doc/example/devenv.txt @@ -52,6 +52,7 @@ [testenv:devenv] envdir = devenv basepython = python2.7 + deps = commands = pip install -r requirements.txt diff -r eacb5ca142abf0b855d9ed2f8d61857b43ced268 -r ecb095208e429b8df180c36b4c8e8422acca89a7 tox/_config.py --- a/tox/_config.py +++ b/tox/_config.py @@ -256,7 +256,6 @@ reader = IniReader(self._cfg, fallbacksections=["testenv"]) reader.addsubstitions(**subs) vc.envdir = reader.getpath(section, "envdir", "{toxworkdir}/%s" % name) - vc.package = reader.getbool(section, "package", True) vc.args_are_paths = reader.getbool(section, "args_are_paths", True) if reader.getdefault(section, "python", None): raise tox.exception.ConfigError( https://bitbucket.org/hpk42/tox/commits/d0fe35302b52/ Changeset: d0fe35302b52 User: hpk42 Date: 2013-07-11 11:13:15 Summary: Merged in lukaszb/tox (pull request #45) Created initial documentation for development environments -- related with #101 Affected #: 5 files diff -r 63d37ba9cc8babe926e45f9e8294841148b0451b -r d0fe35302b524186be561fdba90f5a0acc81df92 doc/config.txt --- a/doc/config.txt +++ b/doc/config.txt @@ -176,6 +176,15 @@ (including when installing the project sdist package). +.. confval:: envdir + + .. versionadded:: 1.5 + + User can set specific path for environment. If path would not be absolute it + would be treated as relative to ``{toxinidir}``. **default**: + ``{toxworkdir}/{envname}`` + + Substitutions --------------------- diff -r 63d37ba9cc8babe926e45f9e8294841148b0451b -r d0fe35302b524186be561fdba90f5a0acc81df92 doc/example/devenv.txt --- /dev/null +++ b/doc/example/devenv.txt @@ -0,0 +1,58 @@ + +Development environment +======================= + +Tox can be used to prepare development virtual environment for local projects. +This feature can be useful in order to preserve environment across team members +working on same project. It can be also used by deployment tools to prepare +proper environments. + + +Configuration +------------- + +Firstly, you need to prepare configuration for your development environment. In +order to do that, we must define proper section at ``tox.ini`` file and tell at +what directory environment should be created. Moreover, we need to specify +python version that should be picked:: + + [testenv:devenv] + envdir = devenv + basepython = python2.7 + commands = + deps = + + +Actually, you can configure a lot more, those are the only required settings. +In example you can add ``deps`` and ``commands`` settings. Here, we tell tox +not to pick ``commands`` or ``deps`` from base ``testenv`` configuration. + + +Creating development environment +-------------------------------- + +Once ``devenv`` section is defined we can instrument tox to create our +environment:: + + tox -e devenv + +This will create an environment at path specified by ``envdir`` under +``[testenv:devenv]`` section. + + +Full configuration example +-------------------------- + +Let's say we want our development environment sit at ``devenv`` and pull +packages from ``requirements.txt`` file which we create at the same directory +as ``tox.ini`` file. We also want to speciy Python version to be 2.7. + +Here is example configuration for that:: + + [testenv:devenv] + envdir = devenv + basepython = python2.7 + deps = + commands = + pip install -r requirements.txt + diff -r 63d37ba9cc8babe926e45f9e8294841148b0451b -r d0fe35302b524186be561fdba90f5a0acc81df92 doc/examples.txt --- a/doc/examples.txt +++ b/doc/examples.txt @@ -11,4 +11,5 @@ example/nose.txt example/general.txt example/jenkins.txt + example/devenv.txt diff -r 63d37ba9cc8babe926e45f9e8294841148b0451b -r d0fe35302b524186be561fdba90f5a0acc81df92 tests/test_config.py --- a/tests/test_config.py +++ b/tests/test_config.py @@ -44,6 +44,36 @@ assert dep2.indexserver.name == "xyz" assert dep2.indexserver.url == "xyz_repo" + def test_envdir_set_manually(self, tmpdir, newconfig): + config = newconfig([], """ + [testenv:devenv] + envdir = devenv + """) + envconfig = config.envconfigs['devenv'] + assert envconfig.envdir == tmpdir.join('devenv') + + def test_envdir_set_manually_with_substitutions(self, tmpdir, newconfig): + config = newconfig([], """ + [testenv:devenv] + envdir = {toxworkdir}/foobar + """) + envconfig = config.envconfigs['devenv'] + assert envconfig.envdir == config.toxworkdir.join('foobar') + + def test_package_flag(self, tmpdir, newconfig): + config = newconfig([], """ + [testenv:py27] + """) + envconfig = config.envconfigs['py27'] + assert envconfig.package == True + + config = newconfig([], """ + [testenv:py27] + package = False + """) + envconfig = config.envconfigs['py27'] + assert envconfig.package == False + class TestConfigPackage: def test_defaults(self, tmpdir, newconfig): config = newconfig([], "") 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 notifications at travis-ci.org Thu Jul 11 10:56:20 2013 From: notifications at travis-ci.org (Travis CI) Date: Thu, 11 Jul 2013 08:56:20 +0000 Subject: [Pytest-commit] [Failed] hpk42/pytest#1 (master - 69ec80a) Message-ID: <51de73342cad8_2495161688ca@a10c686d-2660-4820-b626-a8d9670fbfb4.mail> Build Update for hpk42/pytest ------------------------------------- Build: #1 Status: Failed Duration: 4 minutes and 36 seconds Commit: 69ec80a (master) Author: holger krekel Message: change copyright years View the changeset: https://github.com/hpk42/pytest/compare/ef97a75bfab6...69ec80a35136 View the full build log and details: https://travis-ci.org/hpk42/pytest/builds/8958951 -- 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 Thu Jul 11 11:30:42 2013 From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org) Date: Thu, 11 Jul 2013 09:30:42 -0000 Subject: [Pytest-commit] commit/pytest: 2 new changesets Message-ID: <20130711093042.4758.95947@app25.ash-private.bitbucket.org> 2 new commits in pytest: https://bitbucket.org/hpk42/pytest/commits/d3ec7566fa0a/ Changeset: d3ec7566fa0a User: bubenkoff Date: 2013-07-11 11:15:31 Summary: fixes for py25 in test_skipping Affected #: 2 files diff -r 9d9f4a158f2f1b8e4094584701eb6e45168d0d7c -r d3ec7566fa0a6fbf5faba45fee1e925f37c4aba0 .gitignore --- a/.gitignore +++ b/.gitignore @@ -29,4 +29,5 @@ .cache .coverage .ropeproject +.idea diff -r 9d9f4a158f2f1b8e4094584701eb6e45168d0d7c -r d3ec7566fa0a6fbf5faba45fee1e925f37c4aba0 testing/test_skipping.py --- a/testing/test_skipping.py +++ b/testing/test_skipping.py @@ -108,8 +108,7 @@ pass """) ev = MarkEvaluator(item, 'skipif') - with pytest.raises(pytest.fail.Exception) as exc: - assert ev.istrue() + exc = pytest.raises(pytest.fail.Exception, "ev.istrue()") assert """Failed: you need to specify reason=STRING when using booleans as conditions.""" in exc.value.msg def test_skipif_class(self, testdir): https://bitbucket.org/hpk42/pytest/commits/d50722bef5f6/ Changeset: d50722bef5f6 User: hpk42 Date: 2013-07-11 11:30:01 Summary: Merge pull request #2 from bubenkoff/py25-test_skipping_faild fixes for py25 in test_skipping Affected #: 2 files diff -r 5904151239588a7c187932b044c4e480f6965504 -r d50722bef5f6846a2f084b13aa6e486fdcdf8ac1 .gitignore --- a/.gitignore +++ b/.gitignore @@ -29,4 +29,5 @@ .cache .coverage .ropeproject +.idea diff -r 5904151239588a7c187932b044c4e480f6965504 -r d50722bef5f6846a2f084b13aa6e486fdcdf8ac1 testing/test_skipping.py --- a/testing/test_skipping.py +++ b/testing/test_skipping.py @@ -108,8 +108,7 @@ pass """) ev = MarkEvaluator(item, 'skipif') - with pytest.raises(pytest.fail.Exception) as exc: - assert ev.istrue() + exc = pytest.raises(pytest.fail.Exception, "ev.istrue()") assert """Failed: you need to specify reason=STRING when using booleans as conditions.""" in exc.value.msg def test_skipif_class(self, testdir): 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 Jul 11 12:08:45 2013 From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org) Date: Thu, 11 Jul 2013 10:08:45 -0000 Subject: [Pytest-commit] commit/pytest: 2 new changesets Message-ID: <20130711100845.15858.25090@app19.ash-private.bitbucket.org> 2 new commits in pytest: https://bitbucket.org/hpk42/pytest/commits/8ffc92ebb246/ Changeset: 8ffc92ebb246 User: bubenkoff Date: 2013-07-11 12:06:13 Summary: correct travis build status image url Affected #: 1 file diff -r d50722bef5f6846a2f084b13aa6e486fdcdf8ac1 -r 8ffc92ebb24609352073a8fc8b232d9870daf06e README.rst --- a/README.rst +++ b/README.rst @@ -15,7 +15,7 @@ - many `external plugins `_. -.. image:: https://secure.travis-ci.org/hpk42/pytest.png?branch=travis-integration +.. image:: https://secure.travis-ci.org/hpk42/pytest.png :target: http://travis-ci.org/hpk42/pytest A simple example for a test:: https://bitbucket.org/hpk42/pytest/commits/a450c7fbb62f/ Changeset: a450c7fbb62f User: hpk42 Date: 2013-07-11 12:08:27 Summary: Merge pull request #3 from bubenkoff/travis-status-icon correct travis build status image url Affected #: 1 file diff -r d50722bef5f6846a2f084b13aa6e486fdcdf8ac1 -r a450c7fbb62fa93d4b9d0d7b60d7a38fc9fafc14 README.rst --- a/README.rst +++ b/README.rst @@ -15,7 +15,7 @@ - many `external plugins `_. -.. image:: https://secure.travis-ci.org/hpk42/pytest.png?branch=travis-integration +.. image:: https://secure.travis-ci.org/hpk42/pytest.png :target: http://travis-ci.org/hpk42/pytest A simple example for a test:: 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 Thu Jul 11 11:38:54 2013 From: notifications at travis-ci.org (Travis CI) Date: Thu, 11 Jul 2013 09:38:54 +0000 Subject: [Pytest-commit] [Fixed] hpk42/pytest#3 (master - 29020ea) Message-ID: <51de7d2dd7d20_22d7874178019@a10c686d-2660-4820-b626-a8d9670fbfb4.mail> Build Update for hpk42/pytest ------------------------------------- Build: #3 Status: Fixed Duration: 7 minutes and 37 seconds Commit: 29020ea (master) Author: holger krekel Message: Merge pull request #2 from bubenkoff/py25-test_skipping_faild fixes for py25 in test_skipping View the changeset: https://github.com/hpk42/pytest/compare/69ec80a35136...29020ea04006 View the full build log and details: https://travis-ci.org/hpk42/pytest/builds/8959825 -- 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 Thu Jul 11 12:21:04 2013 From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org) Date: Thu, 11 Jul 2013 10:21:04 -0000 Subject: [Pytest-commit] commit/pytest: hpk42: send IRC notifications to pytest-dev Message-ID: <20130711102104.27359.31072@app14.ash-private.bitbucket.org> 1 new commit in pytest: https://bitbucket.org/hpk42/pytest/commits/11e35e2bf007/ Changeset: 11e35e2bf007 User: hpk42 Date: 2013-07-11 12:20:38 Summary: send IRC notifications to pytest-dev Affected #: 1 file diff -r a450c7fbb62fa93d4b9d0d7b60d7a38fc9fafc14 -r 11e35e2bf00787903c2d80de3d47f3bf4b8e4106 .travis.yml --- a/.travis.yml +++ b/.travis.yml @@ -5,6 +5,6 @@ script: detox --recreate notifications: irc: - - "chat.freenode.net#pylib" + - "chat.freenode.net#pytest-dev" email: - pytest-commit at python.org 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 Jul 11 16:08:05 2013 From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org) Date: Thu, 11 Jul 2013 14:08:05 -0000 Subject: [Pytest-commit] commit/pytest: hpk42: fix link to kotti Message-ID: <20130711140805.13330.63718@app23.ash-private.bitbucket.org> 1 new commit in pytest: https://bitbucket.org/hpk42/pytest/commits/5eca54d508b4/ Changeset: 5eca54d508b4 User: hpk42 Date: 2013-07-11 16:07:58 Summary: fix link to kotti Affected #: 1 file diff -r 11e35e2bf00787903c2d80de3d47f3bf4b8e4106 -r 5eca54d508b406369a0836f8f6422fd80a1836db doc/en/projects.txt --- a/doc/en/projects.txt +++ b/doc/en/projects.txt @@ -64,8 +64,8 @@ * `pytest-django `_ for Django * `zope.pytest `_ for Zope and Grok -* `pytest__gae `_ for Google App Engine -* There is `some work `_ underway for Kotti, a CMS built in Pyramid/Pylons +* `pytest_gae `_ for Google App Engine +* There is `some work `_ underway for Kotti, a CMS built in Pyramid/Pylons Some organisations using py.test 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 Fri Jul 12 10:36:18 2013 From: notifications at travis-ci.org (Travis CI) Date: Fri, 12 Jul 2013 08:36:18 +0000 Subject: [Pytest-commit] [Passed] bubenkoff/pytest#6 (coveralls-integrations - 0b5e384) Message-ID: <51dfc001f3a70_23a8c4914d@5ab9f7ef-c402-45f9-b3de-e95838f8df90.mail> Build Update for bubenkoff/pytest ------------------------------------- Build: #6 Status: Passed Duration: 8 minutes and 10 seconds Commit: 0b5e384 (coveralls-integrations) Author: Anatoly Bubenkov Message: remove debug print View the changeset: https://github.com/bubenkoff/pytest/compare/61267680f5f2...0b5e384ea63e View the full build log and details: https://travis-ci.org/bubenkoff/pytest/builds/8999386 -- 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 issues-reply at bitbucket.org Fri Jul 12 19:38:26 2013 From: issues-reply at bitbucket.org (Barry Warsaw) Date: Fri, 12 Jul 2013 17:38:26 -0000 Subject: [Pytest-commit] Issue #105: Hidden dependency on $HOME (hpk42/tox) Message-ID: <20130712173826.23408.87177@app11.ash-private.bitbucket.org> New issue 105: Hidden dependency on $HOME https://bitbucket.org/hpk42/tox/issue/105/hidden-dependency-on-home Barry Warsaw: tox appears to have a hidden dependency on a valid $HOME, but in some environments this is not the case. For example, when building the package for Ubuntu, the build environment deliberately sets $HOME to /sbuild-nonexistent. You can reproduce this with hg head by doing: $ HOME=/sbuild-nonexistent tox Traceback (most recent call last): File "/usr/bin/tox", line 9, in load_entry_point('tox==1.4.2', 'console_scripts', 'tox')() File "/usr/lib/python2.7/dist-packages/tox/_cmdline.py", line 25, in main retcode = Session(config).runcommand() File "/usr/lib/python2.7/dist-packages/tox/_cmdline.py", line 273, in runcommand return self.subcommand_test() File "/usr/lib/python2.7/dist-packages/tox/_cmdline.py", line 353, in subcommand_test sdist_path = self.sdist() File "/usr/lib/python2.7/dist-packages/tox/_cmdline.py", line 348, in sdist sdistfile.dirpath().ensure(dir=1) File "/usr/lib/python2.7/dist-packages/py/_path/local.py", line 424, in ensure return p._ensuredirs() File "/usr/lib/python2.7/dist-packages/py/_path/local.py", line 406, in _ensuredirs parent._ensuredirs() File "/usr/lib/python2.7/dist-packages/py/_path/local.py", line 406, in _ensuredirs parent._ensuredirs() File "/usr/lib/python2.7/dist-packages/py/_path/local.py", line 409, in _ensuredirs self.mkdir() File "/usr/lib/python2.7/dist-packages/py/_path/local.py", line 381, in mkdir py.error.checked_call(os.mkdir, str(p)) File "/usr/lib/python2.7/dist-packages/py/_error.py", line 84, in checked_call raise cls("%s%r" % (func.__name__, args)) py.error.EACCES: [Permission denied]: mkdir('/sbuild-nonexistent',) From commits-noreply at bitbucket.org Tue Jul 16 11:29:07 2013 From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org) Date: Tue, 16 Jul 2013 09:29:07 -0000 Subject: [Pytest-commit] commit/tox: hpk42: add new python testing training date Message-ID: <20130716092907.28415.40735@app10.ash-private.bitbucket.org> 1 new commit in tox: https://bitbucket.org/hpk42/tox/commits/2567d6b26c89/ Changeset: 2567d6b26c89 User: hpk42 Date: 2013-07-16 11:29:02 Summary: add new python testing training date Affected #: 1 file diff -r d0fe35302b524186be561fdba90f5a0acc81df92 -r 2567d6b26c8995df6cacee337d90f34418057293 doc/index.txt --- a/doc/index.txt +++ b/doc/index.txt @@ -1,8 +1,7 @@ Welcome to the tox automation project =============================================== -.. - Upcoming: `professional testing with pytest and tox `_ , 24th-26th June 2013, Leipzig. +.. note:: second training: `professional testing with Python `_ , 25-27th November 2013, Leipzig. vision: standardize testing in Python --------------------------------------------- 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 Tue Jul 16 15:30:51 2013 From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org) Date: Tue, 16 Jul 2013 13:30:51 -0000 Subject: [Pytest-commit] commit/pytest: 2 new changesets Message-ID: <20130716133051.5125.21967@app04.ash-private.bitbucket.org> 2 new commits in pytest: https://bitbucket.org/hpk42/pytest/commits/8a8be3def6c7/ Changeset: 8a8be3def6c7 User: pfctdayelise Date: 2013-05-29 04:59:47 Summary: A couple of improvements to parametrize - When not specifying ids, let None and bools use their native string form (like str, int, float) rather than obfuscated form used for objects - When specifying ids, explicitly raise a ValueError if a different number of ids are specified compared to the test cases - Add tests for both these items. Affected #: 2 files diff -r 5d0b6123d6541ad497cc592d72b9ed50a7c26915 -r 8a8be3def6c70aecaca71b7dae05b14aa73eda59 _pytest/python.py --- a/_pytest/python.py +++ b/_pytest/python.py @@ -2,6 +2,7 @@ import py import inspect import sys +from types import NoneType import pytest from _pytest.main import getfslineno from _pytest.mark import MarkDecorator, MarkInfo @@ -705,6 +706,9 @@ raise ValueError("%r uses no fixture %r" %( self.function, arg)) valtype = indirect and "params" or "funcargs" + if ids and len(ids) != len(argvalues): + raise ValueError('%d tests specified with %d ids' %( + len(argvalues), len(ids))) if not ids: ids = idmaker(argnames, argvalues) newcalls = [] @@ -758,7 +762,7 @@ for valindex, valset in enumerate(argvalues): this_id = [] for nameindex, val in enumerate(valset): - if not isinstance(val, (float, int, str)): + if not isinstance(val, (float, int, str, bool, NoneType)): this_id.append(str(argnames[nameindex])+str(valindex)) else: this_id.append(str(val)) diff -r 5d0b6123d6541ad497cc592d72b9ed50a7c26915 -r 8a8be3def6c70aecaca71b7dae05b14aa73eda59 testing/python/metafunc.py --- a/testing/python/metafunc.py +++ b/testing/python/metafunc.py @@ -95,6 +95,17 @@ ids = [x.id for x in metafunc._calls] assert ids == ["basic-abc", "basic-def", "advanced-abc", "advanced-def"] + def test_parametrize_with_wrong_number_of_ids(self, testdir): + def func(x, y): pass + metafunc = self.Metafunc(func) + + with pytest.raises(ValueError): + metafunc.parametrize("x", [1,2], ids=['basic']) + + with pytest.raises(ValueError): + metafunc.parametrize(("x","y"), [("abc", "def"), + ("ghi", "jkl")], ids=["one"]) + def test_parametrize_with_userobjects(self): def func(x, y): pass metafunc = self.Metafunc(func) @@ -121,6 +132,25 @@ result = idmaker((py.builtin._totext("a"), "b"), [({}, '\xc3\xb4')]) assert result == ['a0-\xc3\xb4'] + def test_idmaker_native_strings(self): + from _pytest.python import idmaker + result = idmaker(("a", "b"), [(1.0, -1.1), + (2, -202), + ("three", "three hundred"), + (True, False), + (None, None), + (list("six"), [66, 66]), + ({7}, set("seven")), + (tuple("eight"), (8, -8, 8)) + ]) + assert result == ["1.0--1.1", + "2--202", + "three-three hundred", + "True-False", + "None-None", + "a5-b5", + "a6-b6", + "a7-b7"] def test_addcall_and_parametrize(self): def func(x, y): pass @@ -530,8 +560,6 @@ *test_function*1.3-b1* """) - - @pytest.mark.parametrize(("scope", "length"), [("module", 2), ("function", 4)]) def test_parametrize_scope_overrides(self, testdir, scope, length): https://bitbucket.org/hpk42/pytest/commits/7fd344f34e08/ Changeset: 7fd344f34e08 User: hpk42 Date: 2013-07-16 15:30:48 Summary: Merged in pfctdayelise/pytest (pull request #38) A couple of improvements to parametrize Affected #: 2 files diff -r 5eca54d508b406369a0836f8f6422fd80a1836db -r 7fd344f34e088e3d4a6aa47f93901c4d78153c64 _pytest/python.py --- a/_pytest/python.py +++ b/_pytest/python.py @@ -2,6 +2,7 @@ import py import inspect import sys +from types import NoneType import pytest from _pytest.main import getfslineno from _pytest.mark import MarkDecorator, MarkInfo @@ -709,6 +710,9 @@ raise ValueError("%r uses no fixture %r" %( self.function, arg)) valtype = indirect and "params" or "funcargs" + if ids and len(ids) != len(argvalues): + raise ValueError('%d tests specified with %d ids' %( + len(argvalues), len(ids))) if not ids: ids = idmaker(argnames, argvalues) newcalls = [] @@ -762,7 +766,7 @@ for valindex, valset in enumerate(argvalues): this_id = [] for nameindex, val in enumerate(valset): - if not isinstance(val, (float, int, str)): + if not isinstance(val, (float, int, str, bool, NoneType)): this_id.append(str(argnames[nameindex])+str(valindex)) else: this_id.append(str(val)) diff -r 5eca54d508b406369a0836f8f6422fd80a1836db -r 7fd344f34e088e3d4a6aa47f93901c4d78153c64 testing/python/metafunc.py --- a/testing/python/metafunc.py +++ b/testing/python/metafunc.py @@ -95,6 +95,17 @@ ids = [x.id for x in metafunc._calls] assert ids == ["basic-abc", "basic-def", "advanced-abc", "advanced-def"] + def test_parametrize_with_wrong_number_of_ids(self, testdir): + def func(x, y): pass + metafunc = self.Metafunc(func) + + with pytest.raises(ValueError): + metafunc.parametrize("x", [1,2], ids=['basic']) + + with pytest.raises(ValueError): + metafunc.parametrize(("x","y"), [("abc", "def"), + ("ghi", "jkl")], ids=["one"]) + def test_parametrize_with_userobjects(self): def func(x, y): pass metafunc = self.Metafunc(func) @@ -121,6 +132,25 @@ result = idmaker((py.builtin._totext("a"), "b"), [({}, '\xc3\xb4')]) assert result == ['a0-\xc3\xb4'] + def test_idmaker_native_strings(self): + from _pytest.python import idmaker + result = idmaker(("a", "b"), [(1.0, -1.1), + (2, -202), + ("three", "three hundred"), + (True, False), + (None, None), + (list("six"), [66, 66]), + ({7}, set("seven")), + (tuple("eight"), (8, -8, 8)) + ]) + assert result == ["1.0--1.1", + "2--202", + "three-three hundred", + "True-False", + "None-None", + "a5-b5", + "a6-b6", + "a7-b7"] def test_addcall_and_parametrize(self): def func(x, y): pass @@ -530,8 +560,6 @@ *test_function*1.3-b1* """) - - @pytest.mark.parametrize(("scope", "length"), [("module", 2), ("function", 4)]) def test_parametrize_scope_overrides(self, testdir, scope, length): 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 Tue Jul 16 16:08:55 2013 From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org) Date: Tue, 16 Jul 2013 14:08:55 -0000 Subject: [Pytest-commit] commit/pytest: 3 new changesets Message-ID: <20130716140855.15813.97465@app15.ash-private.bitbucket.org> 3 new commits in pytest: https://bitbucket.org/hpk42/pytest/commits/434928c9c3da/ Changeset: 434928c9c3da User: hpk42 Date: 2013-07-16 11:30:21 Summary: add python testing training Affected #: 1 file diff -r 5eca54d508b406369a0836f8f6422fd80a1836db -r 434928c9c3da6c841b8a80381da5bbd61ac5a694 doc/en/index.txt --- a/doc/en/index.txt +++ b/doc/en/index.txt @@ -1,6 +1,8 @@ .. _features: +.. note:: second training: `professional testing with Python `_ , 25-27th November 2013, Leipzig. + pytest: helps you write better programs ============================================= https://bitbucket.org/hpk42/pytest/commits/f3c0eb5de5f5/ Changeset: f3c0eb5de5f5 User: hpk42 Date: 2013-07-16 15:32:05 Summary: merge better parametrize error messages, thanks Brianna Laugher Affected #: 3 files diff -r 434928c9c3da6c841b8a80381da5bbd61ac5a694 -r f3c0eb5de5f5b6782c7da20131602805d008d476 CHANGELOG --- a/CHANGELOG +++ b/CHANGELOG @@ -56,6 +56,7 @@ - fix issue307 - use yaml.safe_load in example, thanks Mark Eichin. +- better parametrize error messages, thanks Brianna Laugher Changes between 2.3.4 and 2.3.5 ----------------------------------- diff -r 434928c9c3da6c841b8a80381da5bbd61ac5a694 -r f3c0eb5de5f5b6782c7da20131602805d008d476 _pytest/python.py --- a/_pytest/python.py +++ b/_pytest/python.py @@ -2,6 +2,7 @@ import py import inspect import sys +from types import NoneType import pytest from _pytest.main import getfslineno from _pytest.mark import MarkDecorator, MarkInfo @@ -709,6 +710,9 @@ raise ValueError("%r uses no fixture %r" %( self.function, arg)) valtype = indirect and "params" or "funcargs" + if ids and len(ids) != len(argvalues): + raise ValueError('%d tests specified with %d ids' %( + len(argvalues), len(ids))) if not ids: ids = idmaker(argnames, argvalues) newcalls = [] @@ -762,7 +766,7 @@ for valindex, valset in enumerate(argvalues): this_id = [] for nameindex, val in enumerate(valset): - if not isinstance(val, (float, int, str)): + if not isinstance(val, (float, int, str, bool, NoneType)): this_id.append(str(argnames[nameindex])+str(valindex)) else: this_id.append(str(val)) diff -r 434928c9c3da6c841b8a80381da5bbd61ac5a694 -r f3c0eb5de5f5b6782c7da20131602805d008d476 testing/python/metafunc.py --- a/testing/python/metafunc.py +++ b/testing/python/metafunc.py @@ -95,6 +95,17 @@ ids = [x.id for x in metafunc._calls] assert ids == ["basic-abc", "basic-def", "advanced-abc", "advanced-def"] + def test_parametrize_with_wrong_number_of_ids(self, testdir): + def func(x, y): pass + metafunc = self.Metafunc(func) + + with pytest.raises(ValueError): + metafunc.parametrize("x", [1,2], ids=['basic']) + + with pytest.raises(ValueError): + metafunc.parametrize(("x","y"), [("abc", "def"), + ("ghi", "jkl")], ids=["one"]) + def test_parametrize_with_userobjects(self): def func(x, y): pass metafunc = self.Metafunc(func) @@ -121,6 +132,25 @@ result = idmaker((py.builtin._totext("a"), "b"), [({}, '\xc3\xb4')]) assert result == ['a0-\xc3\xb4'] + def test_idmaker_native_strings(self): + from _pytest.python import idmaker + result = idmaker(("a", "b"), [(1.0, -1.1), + (2, -202), + ("three", "three hundred"), + (True, False), + (None, None), + (list("six"), [66, 66]), + ({7}, set("seven")), + (tuple("eight"), (8, -8, 8)) + ]) + assert result == ["1.0--1.1", + "2--202", + "three-three hundred", + "True-False", + "None-None", + "a5-b5", + "a6-b6", + "a7-b7"] def test_addcall_and_parametrize(self): def func(x, y): pass @@ -530,8 +560,6 @@ *test_function*1.3-b1* """) - - @pytest.mark.parametrize(("scope", "length"), [("module", 2), ("function", 4)]) def test_parametrize_scope_overrides(self, testdir, scope, length): https://bitbucket.org/hpk42/pytest/commits/6e5fd004c38d/ Changeset: 6e5fd004c38d User: hpk42 Date: 2013-07-16 15:43:20 Summary: some python2.5/3.3 fixes of Brianna's parametrize improvements Affected #: 4 files diff -r f3c0eb5de5f5b6782c7da20131602805d008d476 -r 6e5fd004c38df785e08fdad5d7eaf3095b8fbd0f _pytest/__init__.py --- a/_pytest/__init__.py +++ b/_pytest/__init__.py @@ -1,2 +1,2 @@ # -__version__ = '2.4.0.dev5' +__version__ = '2.4.0.dev6' diff -r f3c0eb5de5f5b6782c7da20131602805d008d476 -r 6e5fd004c38df785e08fdad5d7eaf3095b8fbd0f _pytest/python.py --- a/_pytest/python.py +++ b/_pytest/python.py @@ -2,7 +2,6 @@ import py import inspect import sys -from types import NoneType import pytest from _pytest.main import getfslineno from _pytest.mark import MarkDecorator, MarkInfo @@ -12,6 +11,8 @@ import _pytest cutdir = py.path.local(_pytest.__file__).dirpath() +NoneType = type(None) + callable = py.builtin.callable def getimfunc(func): diff -r f3c0eb5de5f5b6782c7da20131602805d008d476 -r 6e5fd004c38df785e08fdad5d7eaf3095b8fbd0f setup.py --- a/setup.py +++ b/setup.py @@ -12,7 +12,7 @@ name='pytest', description='py.test: simple powerful testing with Python', long_description = long_description, - version='2.4.0.dev5', + version='2.4.0.dev6', url='http://pytest.org', license='MIT license', platforms=['unix', 'linux', 'osx', 'cygwin', 'win32'], diff -r f3c0eb5de5f5b6782c7da20131602805d008d476 -r 6e5fd004c38df785e08fdad5d7eaf3095b8fbd0f testing/python/metafunc.py --- a/testing/python/metafunc.py +++ b/testing/python/metafunc.py @@ -99,12 +99,12 @@ def func(x, y): pass metafunc = self.Metafunc(func) - with pytest.raises(ValueError): - metafunc.parametrize("x", [1,2], ids=['basic']) + pytest.raises(ValueError, lambda: + metafunc.parametrize("x", [1,2], ids=['basic'])) - with pytest.raises(ValueError): + pytest.raises(ValueError, lambda: metafunc.parametrize(("x","y"), [("abc", "def"), - ("ghi", "jkl")], ids=["one"]) + ("ghi", "jkl")], ids=["one"])) def test_parametrize_with_userobjects(self): def func(x, y): pass @@ -140,7 +140,7 @@ (True, False), (None, None), (list("six"), [66, 66]), - ({7}, set("seven")), + (set([7]), set("seven")), (tuple("eight"), (8, -8, 8)) ]) assert result == ["1.0--1.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 commits-noreply at bitbucket.org Tue Jul 16 17:01:41 2013 From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org) Date: Tue, 16 Jul 2013 15:01:41 -0000 Subject: [Pytest-commit] commit/tox: 9 new changesets Message-ID: <20130716150141.648.17875@app09.ash-private.bitbucket.org> 9 new commits in tox: https://bitbucket.org/hpk42/tox/commits/14d597656e37/ Changeset: 14d597656e37 User: mordred Date: 2013-07-10 22:22:16 Summary: Add option to skip sdist step. First change in a sequence to allow for customization of operational steps. The sdist creation is current unconditional, which for some projects makes running tests unreasonably slow. For instance, OpenStack would prefer to just have python setup.py develop run - but we'd like to put in the support in a flexible way that will allow people to express the pipeline needs of their project. Affected #: 4 files diff -r 63d37ba9cc8babe926e45f9e8294841148b0451b -r 14d597656e378d1abd515bf90076704824b0f9fe doc/config.txt --- a/doc/config.txt +++ b/doc/config.txt @@ -21,6 +21,7 @@ distdir=path # defaults to {toxworkdir}/dist distshare=path # defaults to {homedir}/.tox/distshare envlist=ENVLIST # defaults to the list of all environments + skipsdist=BOOL # defaults to false ``tox`` autodetects if it is running in a Jenkins_ context diff -r 63d37ba9cc8babe926e45f9e8294841148b0451b -r 14d597656e378d1abd515bf90076704824b0f9fe tests/test_z_cmdline.py --- a/tests/test_z_cmdline.py +++ b/tests/test_z_cmdline.py @@ -252,6 +252,23 @@ "*ERROR*unknown*environment*qpwoei*", ]) +def test_skip_sdist(cmd, initproj): + initproj("pkg123-0.7", filedefs={ + 'tests': {'test_hello.py': "def test_hello(): pass"}, + 'setup.py': """ + syntax error + """ + , + 'tox.ini': ''' + [tox] + skipsdist=True + [testenv] + commands=echo done + ''' + }) + result = cmd.run("tox", ) + assert result.ret == 0 + def test_sdist_fails(cmd, initproj): initproj("pkg123-0.7", filedefs={ 'tests': {'test_hello.py': "def test_hello(): pass"}, diff -r 63d37ba9cc8babe926e45f9e8294841148b0451b -r 14d597656e378d1abd515bf90076704824b0f9fe tox/_cmdline.py --- a/tox/_cmdline.py +++ b/tox/_cmdline.py @@ -362,19 +362,24 @@ return sdist_path def subcommand_test(self): - sdist_path = self.sdist() - if not sdist_path: - return 2 + if self.config.skipsdist: + self.report.info("skipping sdist step") + sdist_path = None + else: + sdist_path = self.sdist() + if not sdist_path: + return 2 if self.config.option.sdistonly: return for venv in self.venvlist: if self.setupenv(venv): - self.installpkg(venv, sdist_path) - self.runtestenv(venv, sdist_path) + if not self.config.skipsdist: + self.installpkg(venv, sdist_path) + self.runtestenv(venv) retcode = self._summary() return retcode - def runtestenv(self, venv, sdist_path, redirect=False): + def runtestenv(self, venv, redirect=False): if not self.config.option.notest: if venv.status: return @@ -408,6 +413,7 @@ self.report.keyvalue("toxworkdir: ", self.config.toxworkdir) self.report.keyvalue("setupdir: ", self.config.setupdir) self.report.keyvalue("distshare: ", self.config.distshare) + self.report.keyvalue("skipsdist: ", self.config.skipsdist) self.report.tw.line() for envconfig in self.config.envconfigs.values(): self.report.line("[testenv:%s]" % envconfig.envname, bold=True) diff -r 63d37ba9cc8babe926e45f9e8294841148b0451b -r 14d597656e378d1abd515bf90076704824b0f9fe tox/_config.py --- a/tox/_config.py +++ b/tox/_config.py @@ -194,6 +194,7 @@ homedir=config.homedir) config.toxworkdir = reader.getpath(toxsection, "toxworkdir", "{toxinidir}/.tox") + config.skipsdist = reader.getbool(toxsection, "skipsdist", False) config.minversion = reader.getdefault(toxsection, "minversion", None) # determine indexserver dictionary https://bitbucket.org/hpk42/tox/commits/517fca71add7/ Changeset: 517fca71add7 User: mordred Date: 2013-07-10 22:44:42 Summary: Make sure that the venv is finalized. Skipping sdist causes the config to not be saved, which causes recreation next time. Affected #: 2 files diff -r 14d597656e378d1abd515bf90076704824b0f9fe -r 517fca71add7ed6d191ce04206109c6eceabc58d tox/_cmdline.py --- a/tox/_cmdline.py +++ b/tox/_cmdline.py @@ -327,6 +327,12 @@ return False return True + def finishvenv(self, venv): + action = self.newaction(venv, "finishvenv") + with action: + venv.finish() + return True + def installpkg(self, venv, sdist_path): action = self.newaction(venv, "installpkg", sdist_path) with action: @@ -375,6 +381,8 @@ if self.setupenv(venv): if not self.config.skipsdist: self.installpkg(venv, sdist_path) + else: + self.finishvenv(venv) self.runtestenv(venv) retcode = self._summary() return retcode diff -r 14d597656e378d1abd515bf90076704824b0f9fe -r 517fca71add7ed6d191ce04206109c6eceabc58d tox/_venv.py --- a/tox/_venv.py +++ b/tox/_venv.py @@ -197,11 +197,14 @@ self._pcall(args, venv=False, action=action, cwd=basepath) self.just_created = True + def finish(self): + self._getliveconfig().writeconfig(self.path_config) + def installpkg(self, sdistpath, action): assert action is not None if getattr(self, 'just_created', False): action.setactivity("inst", sdistpath) - self._getliveconfig().writeconfig(self.path_config) + self.finish() extraopts = [] else: action.setactivity("inst-nodeps", sdistpath) https://bitbucket.org/hpk42/tox/commits/5464e3062afa/ Changeset: 5464e3062afa User: mordred Date: 2013-07-10 23:41:45 Summary: Add support for installing via setup.py develop. Affected #: 4 files diff -r 517fca71add7ed6d191ce04206109c6eceabc58d -r 5464e3062afa62ef2731fcc63902041adaffa56f doc/config.txt --- a/doc/config.txt +++ b/doc/config.txt @@ -22,6 +22,7 @@ distshare=path # defaults to {homedir}/.tox/distshare envlist=ENVLIST # defaults to the list of all environments skipsdist=BOOL # defaults to false + usedevelop=BOOL # use python setup.py develop, defaults to false ``tox`` autodetects if it is running in a Jenkins_ context diff -r 517fca71add7ed6d191ce04206109c6eceabc58d -r 5464e3062afa62ef2731fcc63902041adaffa56f tox/_cmdline.py --- a/tox/_cmdline.py +++ b/tox/_cmdline.py @@ -333,6 +333,16 @@ venv.finish() return True + def developpkg(self, venv, setupdir): + action = self.newaction(venv, "developpkg", setupdir) + with action: + try: + venv.developpkg(setupdir, action) + return True + except tox.exception.InvocationError: + venv.status = sys.exc_info()[1] + return False + def installpkg(self, venv, sdist_path): action = self.newaction(venv, "installpkg", sdist_path) with action: @@ -379,10 +389,12 @@ return for venv in self.venvlist: if self.setupenv(venv): - if not self.config.skipsdist: + if self.config.skipsdist: + if self.config.usedevelop: + self.developpkg(venv, self.config.setupdir) + self.finishvenv(venv) + else: self.installpkg(venv, sdist_path) - else: - self.finishvenv(venv) self.runtestenv(venv) retcode = self._summary() return retcode @@ -422,6 +434,7 @@ self.report.keyvalue("setupdir: ", self.config.setupdir) self.report.keyvalue("distshare: ", self.config.distshare) self.report.keyvalue("skipsdist: ", self.config.skipsdist) + self.report.keyvalue("usedevelop: ", self.config.usedevelop) self.report.tw.line() for envconfig in self.config.envconfigs.values(): self.report.line("[testenv:%s]" % envconfig.envname, bold=True) diff -r 517fca71add7ed6d191ce04206109c6eceabc58d -r 5464e3062afa62ef2731fcc63902041adaffa56f tox/_config.py --- a/tox/_config.py +++ b/tox/_config.py @@ -194,7 +194,9 @@ homedir=config.homedir) config.toxworkdir = reader.getpath(toxsection, "toxworkdir", "{toxinidir}/.tox") - config.skipsdist = reader.getbool(toxsection, "skipsdist", False) + config.usedevelop = reader.getbool(toxsection, "usedevelop", False) + config.skipsdist = reader.getbool( + toxsection, "skipsdist", config.usedevelop) config.minversion = reader.getdefault(toxsection, "minversion", None) # determine indexserver dictionary diff -r 517fca71add7ed6d191ce04206109c6eceabc58d -r 5464e3062afa62ef2731fcc63902041adaffa56f tox/_venv.py --- a/tox/_venv.py +++ b/tox/_venv.py @@ -1,4 +1,5 @@ from __future__ import with_statement +import subprocess import sys, os, re import py import tox @@ -200,6 +201,29 @@ def finish(self): self._getliveconfig().writeconfig(self.path_config) + def _needs_reinstall(self, setupdir, action): + setup_py = setupdir.join('setup.py') + setup_cfg = setupdir.join('setup.cfg') + args = [str(self.getconfigexecutable()), str(setup_py), '--name'] + output = subprocess.Popen(args, stdout=subprocess.PIPE, + stderr=subprocess.PIPE) + out = output.communicate() + name = out[0].strip().decode('utf-8') + egg_info = setupdir.join('.'.join((name, 'egg-info'))) + for conf_file in (setup_py, setup_cfg): + if (conf_file.check() + and conf_file.mtime() > egg_info.mtime()): + return True + return False + + def developpkg(self, setupdir, action): + assert action is not None + self.finish() + if not self._needs_reinstall(setupdir, action): + return + extraopts = ['--no-deps'] + self._install(['-e', setupdir], extraopts=extraopts, action=action) + def installpkg(self, sdistpath, action): assert action is not None if getattr(self, 'just_created', False): https://bitbucket.org/hpk42/tox/commits/94ec2412ae39/ Changeset: 94ec2412ae39 User: mordred Date: 2013-07-11 17:29:51 Summary: Address code review comments Use action.popen instead of subprocess.popen. Allow skipsdist=False and usedevelop=True to coexist. Affected #: 2 files diff -r 5464e3062afa62ef2731fcc63902041adaffa56f -r 94ec2412ae399eb7a75031a412c42e94dc572896 tox/_cmdline.py --- a/tox/_cmdline.py +++ b/tox/_cmdline.py @@ -73,7 +73,7 @@ f.flush() return f - def popen(self, args, cwd=None, env=None, redirect=True): + def popen(self, args, cwd=None, env=None, redirect=True, returnout=False): logged_command = "%s$ %s" %(cwd, " ".join(map(str, args))) f = outpath = None if redirect: @@ -82,6 +82,8 @@ self.id, self.msg, args, env)) f.flush() self.popen_outpath = outpath = py.path.local(f.name) + elif returnout: + f = subprocess.PIPE if cwd is None: # XXX cwd = self.session.config.cwd cwd = py.path.local() @@ -389,9 +391,9 @@ return for venv in self.venvlist: if self.setupenv(venv): - if self.config.skipsdist: - if self.config.usedevelop: - self.developpkg(venv, self.config.setupdir) + if self.config.usedevelop: + self.developpkg(venv, self.config.setupdir) + elif self.config.skipsdist: self.finishvenv(venv) else: self.installpkg(venv, sdist_path) diff -r 5464e3062afa62ef2731fcc63902041adaffa56f -r 94ec2412ae399eb7a75031a412c42e94dc572896 tox/_venv.py --- a/tox/_venv.py +++ b/tox/_venv.py @@ -1,5 +1,4 @@ from __future__ import with_statement -import subprocess import sys, os, re import py import tox @@ -205,10 +204,9 @@ setup_py = setupdir.join('setup.py') setup_cfg = setupdir.join('setup.cfg') args = [str(self.getconfigexecutable()), str(setup_py), '--name'] - output = subprocess.Popen(args, stdout=subprocess.PIPE, - stderr=subprocess.PIPE) - out = output.communicate() - name = out[0].strip().decode('utf-8') + output = action.popen(args, cwd=setupdir, redirect=False, + returnout=True) + name = output.strip().decode('utf-8') egg_info = setupdir.join('.'.join((name, 'egg-info'))) for conf_file in (setup_py, setup_cfg): if (conf_file.check() https://bitbucket.org/hpk42/tox/commits/fea763e691c0/ Changeset: fea763e691c0 User: mordred Date: 2013-07-11 17:36:21 Summary: Add test to use usedevelop While we're at it, fix the error that adding the test found. Affected #: 2 files diff -r 94ec2412ae399eb7a75031a412c42e94dc572896 -r fea763e691c022b6eb2de49e26d5d78462ad8614 tests/test_z_cmdline.py --- a/tests/test_z_cmdline.py +++ b/tests/test_z_cmdline.py @@ -359,6 +359,59 @@ ]) +def test_test_develop(cmd, initproj): + initproj("example123-0.5", filedefs={ + 'tests': {'test_hello.py': """ + def test_hello(pytestconfig): + pass + """, + }, + 'tox.ini': ''' + [tox] + usedevelop=True + [testenv] + changedir=tests + commands= + py.test --basetemp={envtmpdir} --junitxml=junit-{envname}.xml [] + deps=pytest + ''' + }) + result = cmd.run("tox") + assert not result.ret + result.stdout.fnmatch_lines([ + "*junit-python.xml*", + "*1 passed*", + ]) + result = cmd.run("tox", "-epython", ) + assert not result.ret + result.stdout.fnmatch_lines([ + "*1 passed*", + "*summary*", + "*python: commands succeeded" + ]) + # see that things work with a different CWD + old = cmd.tmpdir.chdir() + result = cmd.run("tox", "-c", "example123/tox.ini") + assert not result.ret + result.stdout.fnmatch_lines([ + "*1 passed*", + "*summary*", + "*python: commands succeeded" + ]) + old.chdir() + # see that tests can also fail and retcode is correct + testfile = py.path.local("tests").join("test_hello.py") + assert testfile.check() + testfile.write("def test_fail(): assert 0") + result = cmd.run("tox", ) + assert result.ret + result.stdout.fnmatch_lines([ + "*1 failed*", + "*summary*", + "*python: *failed*", + ]) + + def test_test_piphelp(initproj, cmd): initproj("example123", filedefs={'tox.ini': """ # content of: tox.ini diff -r 94ec2412ae399eb7a75031a412c42e94dc572896 -r fea763e691c022b6eb2de49e26d5d78462ad8614 tox/_venv.py --- a/tox/_venv.py +++ b/tox/_venv.py @@ -209,8 +209,8 @@ name = output.strip().decode('utf-8') egg_info = setupdir.join('.'.join((name, 'egg-info'))) for conf_file in (setup_py, setup_cfg): - if (conf_file.check() - and conf_file.mtime() > egg_info.mtime()): + if (not egg_info.check() or (conf_file.check() + and conf_file.mtime() > egg_info.mtime())): return True return False https://bitbucket.org/hpk42/tox/commits/6a52c1c93e08/ Changeset: 6a52c1c93e08 User: mordred Date: 2013-07-11 17:47:44 Summary: Add example documentation for usedevelop Affected #: 1 file diff -r fea763e691c022b6eb2de49e26d5d78462ad8614 -r 6a52c1c93e083f760e46f24219a91ba9f69b926f doc/example/general.txt --- a/doc/example/general.txt +++ b/doc/example/general.txt @@ -144,4 +144,37 @@ [testenv:py27] basepython=/my/path/to/python2.7 +Avoiding expensive sdist +------------------------ + +Some projects are large enough that running and sdist, followed by +an install everytime can be prohibitively costly. To solve this, +there are two different options you can add to the ``tox`` section. First, +you can simply ask tox to please not make an sdist:: + + [tox] + skipsdist=True + +If you do this, your local software package will not be installed into +the virtualenv. You should probably be ok with that, or take steps +to deal with it in your commands section:: + + [testenv] + commands = + python setup.py develop + py.test + +Running setup.py develop is a common enough model that it has its own option:: + + [tox] + usedevelop=True + +Which will set ``skipsdist`` to True and then perform the ``setup.py develop`` +step at the place where ``tox`` normally perfoms the installation of the sdist. +Specifically, it actually runs ``pip install -e .`` behind the scenes, which +itself calls ``setup.py develop``. + +There is an optimization coded in to not bother re-running the command if +``$projectname.egg-info`` is newer than ``setup.py`` or ``setup.cfg``. + .. include:: ../links.txt https://bitbucket.org/hpk42/tox/commits/ddda508fcee1/ Changeset: ddda508fcee1 User: mordred Date: 2013-07-16 16:56:23 Summary: Add command line option for develop Affected #: 3 files diff -r 6a52c1c93e083f760e46f24219a91ba9f69b926f -r ddda508fcee1c7f91d465b5420a25b6b2b9554ef tests/test_z_cmdline.py --- a/tests/test_z_cmdline.py +++ b/tests/test_z_cmdline.py @@ -359,7 +359,14 @@ ]) -def test_test_develop(cmd, initproj): +def test_develop(initproj, cmd): + initproj("example123", filedefs={'tox.ini': """ + """}) + result = cmd.run("tox", "-v", "--develop") + assert not result.ret + assert "sdist-make" not in result.stdout.str() + +def test_test_usedevelop(cmd, initproj): initproj("example123-0.5", filedefs={ 'tests': {'test_hello.py': """ def test_hello(pytestconfig): @@ -376,12 +383,13 @@ deps=pytest ''' }) - result = cmd.run("tox") + result = cmd.run("tox", "-v") assert not result.ret result.stdout.fnmatch_lines([ "*junit-python.xml*", "*1 passed*", ]) + assert "sdist-make" not in result.stdout.str() result = cmd.run("tox", "-epython", ) assert not result.ret result.stdout.fnmatch_lines([ diff -r 6a52c1c93e083f760e46f24219a91ba9f69b926f -r ddda508fcee1c7f91d465b5420a25b6b2b9554ef tox/_cmdline.py --- a/tox/_cmdline.py +++ b/tox/_cmdline.py @@ -391,7 +391,7 @@ return for venv in self.venvlist: if self.setupenv(venv): - if self.config.usedevelop: + if self.config.usedevelop or self.config.option.develop: self.developpkg(venv, self.config.setupdir) elif self.config.skipsdist: self.finishvenv(venv) diff -r 6a52c1c93e083f760e46f24219a91ba9f69b926f -r ddda508fcee1c7f91d465b5420a25b6b2b9554ef tox/_config.py --- a/tox/_config.py +++ b/tox/_config.py @@ -90,6 +90,8 @@ help="skip invoking test commands.") parser.add_argument("--sdistonly", action="store_true", dest="sdistonly", help="only perform the sdist packaging activity.") + parser.add_argument("--develop", action="store_true", dest="develop", + help="install package in the venv using setup.py develop") parser.add_argument("--installpkg", action="store", default=None, help="use specified package for installation into venv") parser.add_argument('-i', action="append", @@ -195,8 +197,9 @@ config.toxworkdir = reader.getpath(toxsection, "toxworkdir", "{toxinidir}/.tox") config.usedevelop = reader.getbool(toxsection, "usedevelop", False) - config.skipsdist = reader.getbool( - toxsection, "skipsdist", config.usedevelop) + config.skipsdist = reader.getbool(toxsection, "skipsdist", + config.usedevelop + or config.option.develop) config.minversion = reader.getdefault(toxsection, "minversion", None) # determine indexserver dictionary https://bitbucket.org/hpk42/tox/commits/dc7637581d8a/ Changeset: dc7637581d8a User: mordred Date: 2013-07-16 16:58:20 Summary: Add documentation for the --develop option Affected #: 1 file diff -r ddda508fcee1c7f91d465b5420a25b6b2b9554ef -r dc7637581d8ad1a3793f9b894ffb0a8f758524ed doc/example/general.txt --- a/doc/example/general.txt +++ b/doc/example/general.txt @@ -169,7 +169,8 @@ [tox] usedevelop=True -Which will set ``skipsdist`` to True and then perform the ``setup.py develop`` +And a corresponding command line option ``--develop``, which will set +``skipsdist`` to True and then perform the ``setup.py develop`` step at the place where ``tox`` normally perfoms the installation of the sdist. Specifically, it actually runs ``pip install -e .`` behind the scenes, which itself calls ``setup.py develop``. https://bitbucket.org/hpk42/tox/commits/bfc4a27950dc/ Changeset: bfc4a27950dc User: hpk42 Date: 2013-07-16 17:01:38 Summary: Merged in mordred/configurable-hooks (pull request #49) Make software installation more configurable Affected #: 6 files diff -r 2567d6b26c8995df6cacee337d90f34418057293 -r bfc4a27950dce50e8d49ede674ca73fdaecdf981 doc/config.txt --- a/doc/config.txt +++ b/doc/config.txt @@ -21,6 +21,8 @@ distdir=path # defaults to {toxworkdir}/dist distshare=path # defaults to {homedir}/.tox/distshare envlist=ENVLIST # defaults to the list of all environments + skipsdist=BOOL # defaults to false + usedevelop=BOOL # use python setup.py develop, defaults to false ``tox`` autodetects if it is running in a Jenkins_ context diff -r 2567d6b26c8995df6cacee337d90f34418057293 -r bfc4a27950dce50e8d49ede674ca73fdaecdf981 doc/example/general.txt --- a/doc/example/general.txt +++ b/doc/example/general.txt @@ -144,4 +144,38 @@ [testenv:py27] basepython=/my/path/to/python2.7 +Avoiding expensive sdist +------------------------ + +Some projects are large enough that running and sdist, followed by +an install everytime can be prohibitively costly. To solve this, +there are two different options you can add to the ``tox`` section. First, +you can simply ask tox to please not make an sdist:: + + [tox] + skipsdist=True + +If you do this, your local software package will not be installed into +the virtualenv. You should probably be ok with that, or take steps +to deal with it in your commands section:: + + [testenv] + commands = + python setup.py develop + py.test + +Running setup.py develop is a common enough model that it has its own option:: + + [tox] + usedevelop=True + +And a corresponding command line option ``--develop``, which will set +``skipsdist`` to True and then perform the ``setup.py develop`` +step at the place where ``tox`` normally perfoms the installation of the sdist. +Specifically, it actually runs ``pip install -e .`` behind the scenes, which +itself calls ``setup.py develop``. + +There is an optimization coded in to not bother re-running the command if +``$projectname.egg-info`` is newer than ``setup.py`` or ``setup.cfg``. + .. include:: ../links.txt diff -r 2567d6b26c8995df6cacee337d90f34418057293 -r bfc4a27950dce50e8d49ede674ca73fdaecdf981 tests/test_z_cmdline.py --- a/tests/test_z_cmdline.py +++ b/tests/test_z_cmdline.py @@ -252,6 +252,23 @@ "*ERROR*unknown*environment*qpwoei*", ]) +def test_skip_sdist(cmd, initproj): + initproj("pkg123-0.7", filedefs={ + 'tests': {'test_hello.py': "def test_hello(): pass"}, + 'setup.py': """ + syntax error + """ + , + 'tox.ini': ''' + [tox] + skipsdist=True + [testenv] + commands=echo done + ''' + }) + result = cmd.run("tox", ) + assert result.ret == 0 + def test_sdist_fails(cmd, initproj): initproj("pkg123-0.7", filedefs={ 'tests': {'test_hello.py': "def test_hello(): pass"}, @@ -342,6 +359,67 @@ ]) +def test_develop(initproj, cmd): + initproj("example123", filedefs={'tox.ini': """ + """}) + result = cmd.run("tox", "-v", "--develop") + assert not result.ret + assert "sdist-make" not in result.stdout.str() + +def test_test_usedevelop(cmd, initproj): + initproj("example123-0.5", filedefs={ + 'tests': {'test_hello.py': """ + def test_hello(pytestconfig): + pass + """, + }, + 'tox.ini': ''' + [tox] + usedevelop=True + [testenv] + changedir=tests + commands= + py.test --basetemp={envtmpdir} --junitxml=junit-{envname}.xml [] + deps=pytest + ''' + }) + result = cmd.run("tox", "-v") + assert not result.ret + result.stdout.fnmatch_lines([ + "*junit-python.xml*", + "*1 passed*", + ]) + assert "sdist-make" not in result.stdout.str() + result = cmd.run("tox", "-epython", ) + assert not result.ret + result.stdout.fnmatch_lines([ + "*1 passed*", + "*summary*", + "*python: commands succeeded" + ]) + # see that things work with a different CWD + old = cmd.tmpdir.chdir() + result = cmd.run("tox", "-c", "example123/tox.ini") + assert not result.ret + result.stdout.fnmatch_lines([ + "*1 passed*", + "*summary*", + "*python: commands succeeded" + ]) + old.chdir() + # see that tests can also fail and retcode is correct + testfile = py.path.local("tests").join("test_hello.py") + assert testfile.check() + testfile.write("def test_fail(): assert 0") + result = cmd.run("tox", ) + assert result.ret + result.stdout.fnmatch_lines([ + "*1 failed*", + "*summary*", + "*python: *failed*", + ]) + + def test_test_piphelp(initproj, cmd): initproj("example123", filedefs={'tox.ini': """ # content of: tox.ini diff -r 2567d6b26c8995df6cacee337d90f34418057293 -r bfc4a27950dce50e8d49ede674ca73fdaecdf981 tox/_cmdline.py --- a/tox/_cmdline.py +++ b/tox/_cmdline.py @@ -73,7 +73,7 @@ f.flush() return f - def popen(self, args, cwd=None, env=None, redirect=True): + def popen(self, args, cwd=None, env=None, redirect=True, returnout=False): logged_command = "%s$ %s" %(cwd, " ".join(map(str, args))) f = outpath = None if redirect: @@ -82,6 +82,8 @@ self.id, self.msg, args, env)) f.flush() self.popen_outpath = outpath = py.path.local(f.name) + elif returnout: + f = subprocess.PIPE if cwd is None: # XXX cwd = self.session.config.cwd cwd = py.path.local() @@ -327,6 +329,22 @@ return False return True + def finishvenv(self, venv): + action = self.newaction(venv, "finishvenv") + with action: + venv.finish() + return True + + def developpkg(self, venv, setupdir): + action = self.newaction(venv, "developpkg", setupdir) + with action: + try: + venv.developpkg(setupdir, action) + return True + except tox.exception.InvocationError: + venv.status = sys.exc_info()[1] + return False + def installpkg(self, venv, sdist_path): action = self.newaction(venv, "installpkg", sdist_path) with action: @@ -362,19 +380,28 @@ return sdist_path def subcommand_test(self): - sdist_path = self.sdist() - if not sdist_path: - return 2 + if self.config.skipsdist: + self.report.info("skipping sdist step") + sdist_path = None + else: + sdist_path = self.sdist() + if not sdist_path: + return 2 if self.config.option.sdistonly: return for venv in self.venvlist: if self.setupenv(venv): - self.installpkg(venv, sdist_path) - self.runtestenv(venv, sdist_path) + if self.config.usedevelop or self.config.option.develop: + self.developpkg(venv, self.config.setupdir) + elif self.config.skipsdist: + self.finishvenv(venv) + else: + self.installpkg(venv, sdist_path) + self.runtestenv(venv) retcode = self._summary() return retcode - def runtestenv(self, venv, sdist_path, redirect=False): + def runtestenv(self, venv, redirect=False): if not self.config.option.notest: if venv.status: return @@ -408,6 +435,8 @@ self.report.keyvalue("toxworkdir: ", self.config.toxworkdir) self.report.keyvalue("setupdir: ", self.config.setupdir) self.report.keyvalue("distshare: ", self.config.distshare) + self.report.keyvalue("skipsdist: ", self.config.skipsdist) + self.report.keyvalue("usedevelop: ", self.config.usedevelop) self.report.tw.line() for envconfig in self.config.envconfigs.values(): self.report.line("[testenv:%s]" % envconfig.envname, bold=True) diff -r 2567d6b26c8995df6cacee337d90f34418057293 -r bfc4a27950dce50e8d49ede674ca73fdaecdf981 tox/_config.py --- a/tox/_config.py +++ b/tox/_config.py @@ -90,6 +90,8 @@ help="skip invoking test commands.") parser.add_argument("--sdistonly", action="store_true", dest="sdistonly", help="only perform the sdist packaging activity.") + parser.add_argument("--develop", action="store_true", dest="develop", + help="install package in the venv using setup.py develop") parser.add_argument("--installpkg", action="store", default=None, help="use specified package for installation into venv") parser.add_argument('-i', action="append", @@ -194,6 +196,10 @@ homedir=config.homedir) config.toxworkdir = reader.getpath(toxsection, "toxworkdir", "{toxinidir}/.tox") + config.usedevelop = reader.getbool(toxsection, "usedevelop", False) + config.skipsdist = reader.getbool(toxsection, "skipsdist", + config.usedevelop + or config.option.develop) config.minversion = reader.getdefault(toxsection, "minversion", None) # determine indexserver dictionary diff -r 2567d6b26c8995df6cacee337d90f34418057293 -r bfc4a27950dce50e8d49ede674ca73fdaecdf981 tox/_venv.py --- a/tox/_venv.py +++ b/tox/_venv.py @@ -197,11 +197,36 @@ self._pcall(args, venv=False, action=action, cwd=basepath) self.just_created = True + def finish(self): + self._getliveconfig().writeconfig(self.path_config) + + def _needs_reinstall(self, setupdir, action): + setup_py = setupdir.join('setup.py') + setup_cfg = setupdir.join('setup.cfg') + args = [str(self.getconfigexecutable()), str(setup_py), '--name'] + output = action.popen(args, cwd=setupdir, redirect=False, + returnout=True) + name = output.strip().decode('utf-8') + egg_info = setupdir.join('.'.join((name, 'egg-info'))) + for conf_file in (setup_py, setup_cfg): + if (not egg_info.check() or (conf_file.check() + and conf_file.mtime() > egg_info.mtime())): + return True + return False + + def developpkg(self, setupdir, action): + assert action is not None + self.finish() + if not self._needs_reinstall(setupdir, action): + return + extraopts = ['--no-deps'] + self._install(['-e', setupdir], extraopts=extraopts, action=action) + def installpkg(self, sdistpath, action): assert action is not None if getattr(self, 'just_created', False): action.setactivity("inst", sdistpath) - self._getliveconfig().writeconfig(self.path_config) + self.finish() extraopts = [] else: action.setactivity("inst-nodeps", sdistpath) 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 Tue Jul 16 17:10:48 2013 From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org) Date: Tue, 16 Jul 2013 15:10:48 -0000 Subject: [Pytest-commit] commit/tox: 2 new changesets Message-ID: <20130716151048.30963.19309@app03.ash-private.bitbucket.org> 2 new commits in tox: https://bitbucket.org/hpk42/tox/commits/f37eb1b0e101/ Changeset: f37eb1b0e101 User: hpk42 Date: 2013-07-16 17:06:22 Summary: add new config options ``usedevelop`` and ``skipsdist`` as well as a command line option ``--develop`` to install the package-under-test in develop mode. thanks Monty Taylor. Affected #: 5 files diff -r bfc4a27950dce50e8d49ede674ca73fdaecdf981 -r f37eb1b0e101edf09eebe9ca4eb5b83de4e9edfe CHANGELOG --- a/CHANGELOG +++ b/CHANGELOG @@ -1,6 +1,10 @@ 1.5.1.dev ----------------- +- add new config options ``usedevelop`` and ``skipsdist`` as well as a + command line option ``--develop`` to install the package-under-test in develop mode. + thanks Monty Tailor for the PR. + - always unset PYTHONDONTWRITEBYTE because newer setuptools doesn't like it 1.5.0 diff -r bfc4a27950dce50e8d49ede674ca73fdaecdf981 -r f37eb1b0e101edf09eebe9ca4eb5b83de4e9edfe CONTRIBUTORS --- a/CONTRIBUTORS +++ b/CONTRIBUTORS @@ -10,3 +10,4 @@ Ronny Pfannschmidt Lukasz Balcerzak Philip Thiem +Monty Taylor diff -r bfc4a27950dce50e8d49ede674ca73fdaecdf981 -r f37eb1b0e101edf09eebe9ca4eb5b83de4e9edfe setup.py --- a/setup.py +++ b/setup.py @@ -26,7 +26,7 @@ description='virtualenv-based automation of test activities', long_description=open("README.rst").read(), url='http://tox.testrun.org/', - version='1.5.1.dev1', + version='1.5.1.dev2', license='http://opensource.org/licenses/MIT', platforms=['unix', 'linux', 'osx', 'cygwin', 'win32'], author='holger krekel', diff -r bfc4a27950dce50e8d49ede674ca73fdaecdf981 -r f37eb1b0e101edf09eebe9ca4eb5b83de4e9edfe tox/__init__.py --- a/tox/__init__.py +++ b/tox/__init__.py @@ -1,5 +1,5 @@ # -__version__ = '1.5.1.dev1' +__version__ = '1.5.1.dev2' class exception: class Error(Exception): diff -r bfc4a27950dce50e8d49ede674ca73fdaecdf981 -r f37eb1b0e101edf09eebe9ca4eb5b83de4e9edfe toxbootstrap.py --- a/toxbootstrap.py +++ b/toxbootstrap.py @@ -58,7 +58,7 @@ """ -__version__ = '1.5.1.dev1' +__version__ = '1.5.1.dev2' import sys import os https://bitbucket.org/hpk42/tox/commits/8c7e68bd1c8e/ Changeset: 8c7e68bd1c8e User: hpk42 Date: 2013-07-16 17:10:42 Summary: remove obsolete tests - i think Lukasz left this in erranously Affected #: 1 file diff -r f37eb1b0e101edf09eebe9ca4eb5b83de4e9edfe -r 8c7e68bd1c8e5e29101ea9dc54c0525476295a0c tests/test_config.py --- a/tests/test_config.py +++ b/tests/test_config.py @@ -60,20 +60,6 @@ envconfig = config.envconfigs['devenv'] assert envconfig.envdir == config.toxworkdir.join('foobar') - def test_package_flag(self, tmpdir, newconfig): - config = newconfig([], """ - [testenv:py27] - """) - envconfig = config.envconfigs['py27'] - assert envconfig.package == True - - config = newconfig([], """ - [testenv:py27] - package = False - """) - envconfig = config.envconfigs['py27'] - assert envconfig.package == False - class TestConfigPackage: def test_defaults(self, tmpdir, newconfig): config = newconfig([], "") 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 Jul 17 09:55:15 2013 From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org) Date: Wed, 17 Jul 2013 07:55:15 -0000 Subject: [Pytest-commit] commit/pytest: hpk42: paint last line red if "failures" or "errors" occured, attribute theuni Message-ID: <20130717075515.6547.3818@app05.ash-private.bitbucket.org> 1 new commit in pytest: https://bitbucket.org/hpk42/pytest/commits/6b10ac6e0a44/ Changeset: 6b10ac6e0a44 User: hpk42 Date: 2013-07-17 09:31:55 Summary: paint last line red if "failures" or "errors" occured, attribute theuni Affected #: 3 files diff -r 6e5fd004c38df785e08fdad5d7eaf3095b8fbd0f -r 6b10ac6e0a44682ce1ba97e06eb61b1c90774938 AUTHORS --- a/AUTHORS +++ b/AUTHORS @@ -33,4 +33,4 @@ Andreas Zeidler Brian Okken Katarzyna Jachim - +Christian Theunert diff -r 6e5fd004c38df785e08fdad5d7eaf3095b8fbd0f -r 6b10ac6e0a44682ce1ba97e06eb61b1c90774938 CHANGELOG --- a/CHANGELOG +++ b/CHANGELOG @@ -1,6 +1,9 @@ Changes between 2.3.5 and 2.4.DEV ----------------------------------- +- 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. diff -r 6e5fd004c38df785e08fdad5d7eaf3095b8fbd0f -r 6b10ac6e0a44682ce1ba97e06eb61b1c90774938 _pytest/terminal.py --- a/_pytest/terminal.py +++ b/_pytest/terminal.py @@ -457,7 +457,7 @@ msg = "%s in %.2f seconds" % (line, session_duration) if self.verbosity >= 0: markup = dict(bold=True) - if 'failed' in self.stats: + if 'failed' in self.stats or 'error' in self.stats: markup['red'] = True else: markup['green'] = True 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 Wed Jul 17 10:29:18 2013 From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org) Date: Wed, 17 Jul 2013 08:29:18 -0000 Subject: [Pytest-commit] commit/pytest: hpk42: SO-17664702: call fixture finalizers even if the fixture function Message-ID: <20130717082918.13491.50727@app01.ash-private.bitbucket.org> 1 new commit in pytest: https://bitbucket.org/hpk42/pytest/commits/39a686b8775f/ Changeset: 39a686b8775f User: hpk42 Date: 2013-07-17 10:29:11 Summary: SO-17664702: call fixture finalizers even if the fixture function partially failed (finalizers would not always be called before) Affected #: 5 files diff -r 6b10ac6e0a44682ce1ba97e06eb61b1c90774938 -r 39a686b8775f100730751756e1b638e7a305e182 CHANGELOG --- a/CHANGELOG +++ b/CHANGELOG @@ -1,6 +1,9 @@ Changes between 2.3.5 and 2.4.DEV ----------------------------------- +- 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. diff -r 6b10ac6e0a44682ce1ba97e06eb61b1c90774938 -r 39a686b8775f100730751756e1b638e7a305e182 _pytest/__init__.py --- a/_pytest/__init__.py +++ b/_pytest/__init__.py @@ -1,2 +1,2 @@ # -__version__ = '2.4.0.dev6' +__version__ = '2.4.0.dev7' diff -r 6b10ac6e0a44682ce1ba97e06eb61b1c90774938 -r 39a686b8775f100730751756e1b638e7a305e182 _pytest/python.py --- a/_pytest/python.py +++ b/_pytest/python.py @@ -1290,15 +1290,21 @@ # route request.addfinalizer to fixturedef mp.setattr(self, "addfinalizer", fixturedef.addfinalizer) - # perform the fixture call - val = fixturedef.execute(request=self) + try: + # perform the fixture call + val = fixturedef.execute(request=self) + finally: + # if the fixture function failed it might still have + # registered finalizers so we can register - # prepare finalization according to scope - # (XXX analyse exact finalizing mechanics / cleanup) - self.session._setupstate.addfinalizer(fixturedef.finish, self.node) - self._fixturemanager.addargfinalizer(fixturedef.finish, argname) - for subargname in fixturedef.argnames: # XXX all deps? - self._fixturemanager.addargfinalizer(fixturedef.finish, subargname) + # prepare finalization according to scope + # (XXX analyse exact finalizing mechanics / cleanup) + self.session._setupstate.addfinalizer(fixturedef.finish, + self.node) + self._fixturemanager.addargfinalizer(fixturedef.finish, argname) + for subargname in fixturedef.argnames: # XXX all deps? + self._fixturemanager.addargfinalizer(fixturedef.finish, + subargname) mp.undo() return val diff -r 6b10ac6e0a44682ce1ba97e06eb61b1c90774938 -r 39a686b8775f100730751756e1b638e7a305e182 setup.py --- a/setup.py +++ b/setup.py @@ -12,7 +12,7 @@ name='pytest', description='py.test: simple powerful testing with Python', long_description = long_description, - version='2.4.0.dev6', + version='2.4.0.dev7', url='http://pytest.org', license='MIT license', platforms=['unix', 'linux', 'osx', 'cygwin', 'win32'], diff -r 6b10ac6e0a44682ce1ba97e06eb61b1c90774938 -r 39a686b8775f100730751756e1b638e7a305e182 testing/python/fixture.py --- a/testing/python/fixture.py +++ b/testing/python/fixture.py @@ -309,6 +309,39 @@ print(ss.stack) assert teardownlist == [1] + def test_request_addfinalizer_failing_setup(self, testdir): + testdir.makepyfile(""" + import pytest + l = [1] + @pytest.fixture + def myfix(request): + request.addfinalizer(l.pop) + assert 0 + def test_fix(myfix): + pass + def test_finalizer_ran(): + assert not l + """) + reprec = testdir.inline_run("-s") + reprec.assertoutcome(failed=1, passed=1) + + def test_request_addfinalizer_failing_setup_module(self, testdir): + testdir.makepyfile(""" + import pytest + l = [1, 2] + @pytest.fixture(scope="module") + def myfix(request): + request.addfinalizer(l.pop) + request.addfinalizer(l.pop) + assert 0 + def test_fix(myfix): + pass + """) + reprec = testdir.inline_run("-s") + mod = reprec.getcalls("pytest_runtest_setup")[0].item.module + assert not mod.l + + def test_request_addfinalizer_partial_setup_failure(self, testdir): p = testdir.makepyfile(""" l = [] 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 Wed Jul 17 10:38:04 2013 From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org) Date: Wed, 17 Jul 2013 08:38:04 -0000 Subject: [Pytest-commit] commit/tox: 5 new changesets Message-ID: <20130717083804.14457.56342@app13.ash-private.bitbucket.org> 5 new commits in tox: https://bitbucket.org/hpk42/tox/commits/f9f760fb37a9/ Changeset: f9f760fb37a9 User: mordred Date: 2013-07-16 17:21:11 Summary: Expand --develop help text to be more correct The --develop option actually triggers pip install -e . instead of calling setup.py develop directly. Be clear about that. Affected #: 1 file diff -r 8c7e68bd1c8e5e29101ea9dc54c0525476295a0c -r f9f760fb37a95f36f934cb010a5a8cacb65f1657 tox/_config.py --- a/tox/_config.py +++ b/tox/_config.py @@ -91,7 +91,8 @@ parser.add_argument("--sdistonly", action="store_true", dest="sdistonly", help="only perform the sdist packaging activity.") parser.add_argument("--develop", action="store_true", dest="develop", - help="install package in the venv using setup.py develop") + help="install package in the venv using setup.py develop using " + "'pip -e .'") parser.add_argument("--installpkg", action="store", default=None, help="use specified package for installation into venv") parser.add_argument('-i', action="append", https://bitbucket.org/hpk42/tox/commits/139084074f08/ Changeset: 139084074f08 User: mordred Date: 2013-07-16 17:25:24 Summary: Don't run --no-deps on intial install Copy the logic from installpkg more completely. Affected #: 1 file diff -r f9f760fb37a95f36f934cb010a5a8cacb65f1657 -r 139084074f081f014758e4549209f8ac5c8a82a6 tox/_venv.py --- a/tox/_venv.py +++ b/tox/_venv.py @@ -216,10 +216,15 @@ def developpkg(self, setupdir, action): assert action is not None - self.finish() + if getattr(self, 'just_created', False): + action.setactivity("inst", sdistpath) + self.finish() + extraopts = [] + else: + action.setactivity("inst-nodeps", sdistpath) + extraopts = ['--no-deps'] if not self._needs_reinstall(setupdir, action): return - extraopts = ['--no-deps'] self._install(['-e', setupdir], extraopts=extraopts, action=action) def installpkg(self, sdistpath, action): https://bitbucket.org/hpk42/tox/commits/1a80bac8f67d/ Changeset: 1a80bac8f67d User: mordred Date: 2013-07-16 17:39:42 Summary: Report better on what we're doing develop mode should emit actions telling us what it's doing. Affected #: 2 files diff -r 139084074f081f014758e4549209f8ac5c8a82a6 -r 1a80bac8f67d32948751b20ee66f98ff03ec29ed tests/test_z_cmdline.py --- a/tests/test_z_cmdline.py +++ b/tests/test_z_cmdline.py @@ -362,7 +362,16 @@ def test_develop(initproj, cmd): initproj("example123", filedefs={'tox.ini': """ """}) - result = cmd.run("tox", "-v", "--develop") + result = cmd.run("tox", "-vv", "--develop") + assert not result.ret + assert "sdist-make" not in result.stdout.str() + +def test_usedevelop(initproj, cmd): + initproj("example123", filedefs={'tox.ini': """ + [tox] + usedevelop=True + """}) + result = cmd.run("tox", "-vv") assert not result.ret assert "sdist-make" not in result.stdout.str() diff -r 139084074f081f014758e4549209f8ac5c8a82a6 -r 1a80bac8f67d32948751b20ee66f98ff03ec29ed tox/_venv.py --- a/tox/_venv.py +++ b/tox/_venv.py @@ -217,14 +217,15 @@ def developpkg(self, setupdir, action): assert action is not None if getattr(self, 'just_created', False): - action.setactivity("inst", sdistpath) + action.setactivity("develop-inst", setupdir) self.finish() extraopts = [] else: - action.setactivity("inst-nodeps", sdistpath) + if not self._needs_reinstall(setupdir, action): + action.setactivity("develop-inst-noop", setupdir) + return + action.setactivity("develop-inst-nodeps", setupdir) extraopts = ['--no-deps'] - if not self._needs_reinstall(setupdir, action): - return self._install(['-e', setupdir], extraopts=extraopts, action=action) def installpkg(self, sdistpath, action): https://bitbucket.org/hpk42/tox/commits/44e60897da2b/ Changeset: 44e60897da2b User: mordred Date: 2013-07-16 17:57:41 Summary: Add develop to the configs that trigger recreate Affected #: 4 files diff -r 1a80bac8f67d32948751b20ee66f98ff03ec29ed -r 44e60897da2beb01adb940ef972bc58d7c076e03 tests/test_config.py --- a/tests/test_config.py +++ b/tests/test_config.py @@ -370,6 +370,7 @@ assert envconfig.changedir == config.setupdir assert envconfig.distribute == False assert envconfig.sitepackages == False + assert envconfig.develop == False assert envconfig.envlogdir == envconfig.envdir.join("log") assert envconfig.setenv is None diff -r 1a80bac8f67d32948751b20ee66f98ff03ec29ed -r 44e60897da2beb01adb940ef972bc58d7c076e03 tests/test_venv.py --- a/tests/test_venv.py +++ b/tests/test_venv.py @@ -473,6 +473,18 @@ venv.update() mocksession.report.expect("verbosity0", "*recreate*") + def test_develop_recreation(self, newconfig, mocksession): + config = newconfig([], "") + envconfig = config.envconfigs['python'] + venv = VirtualEnv(envconfig, session=mocksession) + venv.update() + cconfig = venv._getliveconfig() + cconfig.develop = True + cconfig.writeconfig(venv.path_config) + mocksession._clearmocks() + venv.update() + mocksession.report.expect("verbosity0", "*recreate*") + class TestVenvTest: def test_patchPATH(self, newmocksession, monkeypatch): diff -r 1a80bac8f67d32948751b20ee66f98ff03ec29ed -r 44e60897da2beb01adb940ef972bc58d7c076e03 tox/_config.py --- a/tox/_config.py +++ b/tox/_config.py @@ -262,6 +262,7 @@ vc.config = config reader = IniReader(self._cfg, fallbacksections=["testenv"]) reader.addsubstitions(**subs) + vc.develop = config.usedevelop or config.option.develop vc.envdir = reader.getpath(section, "envdir", "{toxworkdir}/%s" % name) vc.args_are_paths = reader.getbool(section, "args_are_paths", True) if reader.getdefault(section, "python", None): diff -r 1a80bac8f67d32948751b20ee66f98ff03ec29ed -r 44e60897da2beb01adb940ef972bc58d7c076e03 tox/_venv.py --- a/tox/_venv.py +++ b/tox/_venv.py @@ -5,18 +5,20 @@ from tox._config import DepConfig class CreationConfig: - def __init__(self, md5, python, version, distribute, sitepackages, deps): + def __init__(self, md5, python, version, distribute, sitepackages, + develop, deps): self.md5 = md5 self.python = python self.version = version self.distribute = distribute self.sitepackages = sitepackages + self.develop = develop self.deps = deps def writeconfig(self, path): lines = ["%s %s" % (self.md5, self.python)] - lines.append("%s %d %d" % (self.version, self.distribute, - self.sitepackages)) + lines.append("%s %d %d %d" % (self.version, self.distribute, + self.sitepackages, self.develop)) for dep in self.deps: lines.append("%s %s" % dep) path.ensure() @@ -28,15 +30,17 @@ lines = path.readlines(cr=0) value = lines.pop(0).split(None, 1) md5, python = value - version, distribute, sitepackages = lines.pop(0).split(None, 2) + version, distribute, sitepackages, develop = lines.pop(0).split( + None, 3) distribute = bool(int(distribute)) sitepackages = bool(int(sitepackages)) + develop = bool(int(develop)) deps = [] for line in lines: md5, depstring = line.split(None, 1) deps.append((md5, depstring)) return CreationConfig(md5, python, version, - distribute, sitepackages, deps) + distribute, sitepackages, develop, deps) except KeyboardInterrupt: raise except: @@ -48,6 +52,7 @@ and self.version == other.version and self.distribute == other.distribute and self.sitepackages == other.sitepackages + and self.develop == other.develop and self.deps == other.deps) class VirtualEnv(object): @@ -144,13 +149,14 @@ version = tox.__version__ distribute = self.envconfig.distribute sitepackages = self.envconfig.sitepackages + develop = self.envconfig.develop deps = [] for dep in self._getresolvedeps(): raw_dep = dep.name md5 = getdigest(raw_dep) deps.append((md5, raw_dep)) return CreationConfig(md5, python, version, - distribute, sitepackages, deps) + distribute, sitepackages, develop, deps) def _getresolvedeps(self): l = [] https://bitbucket.org/hpk42/tox/commits/fb612de381a7/ Changeset: fb612de381a7 User: mordred Date: 2013-07-16 18:00:05 Summary: Use the python from the venv for setup.py --name Affected #: 1 file diff -r 44e60897da2beb01adb940ef972bc58d7c076e03 -r fb612de381a79f8d7bff9cf2e0bc8fab42237efd tox/_venv.py --- a/tox/_venv.py +++ b/tox/_venv.py @@ -209,7 +209,7 @@ def _needs_reinstall(self, setupdir, action): setup_py = setupdir.join('setup.py') setup_cfg = setupdir.join('setup.cfg') - args = [str(self.getconfigexecutable()), str(setup_py), '--name'] + args = [self.envconfig.envpython, str(setup_py), '--name'] output = action.popen(args, cwd=setupdir, redirect=False, returnout=True) name = output.strip().decode('utf-8') 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 Jul 24 11:16:25 2013 From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org) Date: Wed, 24 Jul 2013 09:16:25 -0000 Subject: [Pytest-commit] commit/pytest: hpk42: bump version Message-ID: <20130724091625.26097.45630@app08.ash-private.bitbucket.org> 1 new commit in pytest: https://bitbucket.org/hpk42/pytest/commits/e753e3d414f7/ Changeset: e753e3d414f7 User: hpk42 Date: 2013-07-24 11:16:19 Summary: bump version Affected #: 2 files diff -r 39a686b8775f100730751756e1b638e7a305e182 -r e753e3d414f786143653d8b95145e2cf03bf746f _pytest/__init__.py --- a/_pytest/__init__.py +++ b/_pytest/__init__.py @@ -1,2 +1,2 @@ # -__version__ = '2.4.0.dev7' +__version__ = '2.4.0.dev8' diff -r 39a686b8775f100730751756e1b638e7a305e182 -r e753e3d414f786143653d8b95145e2cf03bf746f setup.py --- a/setup.py +++ b/setup.py @@ -12,7 +12,7 @@ name='pytest', description='py.test: simple powerful testing with Python', long_description = long_description, - version='2.4.0.dev7', + version='2.4.0.dev8', 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 notifications at travis-ci.org Wed Jul 24 11:21:01 2013 From: notifications at travis-ci.org (Travis CI) Date: Wed, 24 Jul 2013 09:21:01 +0000 Subject: [Pytest-commit] [Broken] hpk42/pytest#22 (master - d1dec1e) Message-ID: <51ef9c7d79180_24fa51e2073e3@509c3f74-6484-4ebe-8685-094ed955e533.mail> Build Update for hpk42/pytest ------------------------------------- Build: #22 Status: Broken Duration: 3 minutes and 48 seconds Commit: d1dec1e (master) Author: holger krekel Message: bump version View the changeset: https://github.com/hpk42/pytest/compare/b763c4879e3b...d1dec1e32cb1 View the full build log and details: https://travis-ci.org/hpk42/pytest/builds/9425921 -- 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 Wed Jul 24 12:08:37 2013 From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org) Date: Wed, 24 Jul 2013 10:08:37 -0000 Subject: [Pytest-commit] commit/pytest: hpk42: stick to virtualenv<1.10 for now because it breaks python2.5 Message-ID: <20130724100837.10702.91292@app24.ash-private.bitbucket.org> 1 new commit in pytest: https://bitbucket.org/hpk42/pytest/commits/ae8fcfe3d678/ Changeset: ae8fcfe3d678 User: hpk42 Date: 2013-07-24 12:08:20 Summary: stick to virtualenv<1.10 for now because it breaks python2.5 Affected #: 1 file diff -r e753e3d414f786143653d8b95145e2cf03bf746f -r ae8fcfe3d6786e54f30bf3a680688ea66d73914e .travis.yml --- a/.travis.yml +++ b/.travis.yml @@ -1,6 +1,6 @@ language: python # command to install dependencies -install: "pip install -e . detox" +install: "pip install 'virtualenv<1.10' -e . 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 commits-noreply at bitbucket.org Wed Jul 24 12:15:02 2013 From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org) Date: Wed, 24 Jul 2013 10:15:02 -0000 Subject: [Pytest-commit] commit/pytest: hpk42: mention github and bitbucket Message-ID: <20130724101502.24393.62807@app09.ash-private.bitbucket.org> 1 new commit in pytest: https://bitbucket.org/hpk42/pytest/commits/abfc7dc64ed4/ Changeset: abfc7dc64ed4 User: hpk42 Date: 2013-07-24 12:14:53 Summary: mention github and bitbucket Affected #: 1 file diff -r ae8fcfe3d6786e54f30bf3a680688ea66d73914e -r abfc7dc64ed4f5a50e917ac8a30863be7e2361a7 README.rst --- a/README.rst +++ b/README.rst @@ -35,4 +35,10 @@ http://bitbucket.org/hpk42/pytest/issues/ +and checkout repos at: + + http://github.com/hpk42/pytest/ (mirror) + http://bitbucket.org/hpk42/pytest/ + + Copyright Holger Krekel and others, 2004-2013 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 Jul 24 14:46:04 2013 From: issues-reply at bitbucket.org (vmalloc) Date: Wed, 24 Jul 2013 12:46:04 -0000 Subject: [Pytest-commit] Issue #106: Add option to use wheel when installing packages (hpk42/tox) Message-ID: <20130724124604.11751.478@app18.ash-private.bitbucket.org> New issue 106: Add option to use wheel when installing packages https://bitbucket.org/hpk42/tox/issue/106/add-option-to-use-wheel-when-installing vmalloc: The new pip 1.4 (and virtualenv 1.10) support the wheel package format, and can automatically cache downloaded and built packages in a custom location. This would be extremely useful for tox, as rerunning it multiple times will require no extra build time... From commits-noreply at bitbucket.org Fri Jul 26 07:41:49 2013 From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org) Date: Fri, 26 Jul 2013 05:41:49 -0000 Subject: [Pytest-commit] commit/pytest: 3 new changesets Message-ID: <20130726054149.3261.3421@app05.ash-private.bitbucket.org> 3 new commits in pytest: https://bitbucket.org/hpk42/pytest/commits/a3f34a534d44/ Changeset: a3f34a534d44 Branch: argparse User: Anthon van der Neut Date: 2013-07-25 15:33:43 Summary: moving from optparse to argparse. Major difficulty is that argparse does not have Option objects -> added class Argument Needed explicit call of MyOptionParser.format_epilog as argparse does not have that. The parse_arg epilog argument wraps the text, which is not the same (could be handled with a special formatter). - parser.parse() now returns single argument (with positional args in .file_or_dir) - "file_or_dir" made a class variable Config._file_or_dir and used in help and tests - added code for argcomplete (because of which this all started!) addoption: - if option type is a string ('int' or 'string', this converted to int resp. str - if option type is 'count' this is changed to the type of choices[0] testing: - added tests for Argument - test_mark.test_keyword_extra split as ['-k', '-mykeyword'] generates argparse error test split in two and one marked as fail - testing hints, multiline and more strickt (for if someone moves format_epilog to epilog argument of parse_args without Formatter) - test for destination derived from long option with internal dash - renamed second test_parseopt.test_parse() to test_parse2 as it was not tested at all (the first was tested.) Affected #: 11 files diff -r abfc7dc64ed4f5a50e917ac8a30863be7e2361a7 -r a3f34a534d44feee1c77f5122402c761259a11d9 _pytest/capture.py --- a/_pytest/capture.py +++ b/_pytest/capture.py @@ -6,7 +6,7 @@ def pytest_addoption(parser): group = parser.getgroup("general") group._addoption('--capture', action="store", default=None, - metavar="method", type="choice", choices=['fd', 'sys', 'no'], + metavar="method", choices=['fd', 'sys', 'no'], help="per-test capturing method: one of fd (default)|sys|no.") group._addoption('-s', action="store_const", const="no", dest="capture", help="shortcut for --capture=no.") diff -r abfc7dc64ed4f5a50e917ac8a30863be7e2361a7 -r a3f34a534d44feee1c77f5122402c761259a11d9 _pytest/config.py --- a/_pytest/config.py +++ b/_pytest/config.py @@ -80,16 +80,20 @@ for group in groups: if group.options: desc = group.description or group.name - optgroup = py.std.optparse.OptionGroup(optparser, desc) - optgroup.add_options(group.options) - optparser.add_option_group(optgroup) + arggroup = optparser.add_argument_group(desc) + for option in group.options: + n = option.names() + a = option.attrs() + arggroup.add_argument(*n, **a) + optparser.add_argument(Config._file_or_dir, nargs='*') + try_argcomplete(self.optparser) return self.optparser.parse_args([str(x) for x in args]) def parse_setoption(self, args, option): - parsedoption, args = self.parse(args) + parsedoption = self.parse(args) for name, value in parsedoption.__dict__.items(): setattr(option, name, value) - return args + return getattr(parsedoption, Config._file_or_dir) def addini(self, name, help, type=None, default=None): """ register an ini-file option. @@ -105,7 +109,133 @@ self._inidict[name] = (help, type, default) self._ininames.append(name) +def try_argcomplete(parser): + try: + import argcomplete + except ImportError: + pass + else: + argcomplete.autocomplete(parser) +class ArgumentError(Exception): + """ + Raised if an Argument instance is created with invalid or + inconsistent arguments. + """ + + def __init__(self, msg, option): + self.msg = msg + self.option_id = str(option) + + def __str__(self): + if self.option_id: + return "option %s: %s" % (self.option_id, self.msg) + else: + return self.msg + + +class Argument: + """class that mimics the necessary behaviour of py.std.optparse.Option """ + _typ_map = { + 'int': int, + 'string': str, + } + + def __init__(self, *names, **attrs): + """store parms in private vars for use in add_argument""" + self._attrs = attrs + self._short_opts = [] + self._long_opts = [] + self.dest = attrs.get('dest') + try: + typ = attrs['type'] + except KeyError: + pass + else: + # this might raise a keyerror as well, don't want to catch that + if isinstance(typ, str): + if typ == 'choice': + # argparse expects a type here take it from + # the type of the first element + attrs['type'] = type(attrs['choices'][0]) + else: + attrs['type'] = Argument._typ_map[typ] + # used in test_parseopt -> test_parse_defaultgetter + self.type = attrs['type'] + else: + self.type = typ + try: + # attribute existence is tested in Config._processopt + self.default = attrs['default'] + except KeyError: + pass + self._set_opt_strings(names) + if not self.dest: + if self._long_opts: + self.dest = self._long_opts[0][2:].replace('-', '_') + else: + try: + self.dest = self._short_opts[0][1:] + except IndexError: + raise ArgumentError( + 'need a long or short option', self) + + def names(self): + return self._short_opts + self._long_opts + + def attrs(self): + # update any attributes set by processopt + attrs = 'default dest'.split() + if self.dest: + attrs.append(self.dest) + for attr in attrs: + try: + self._attrs[attr] = getattr(self, attr) + except AttributeError: + pass + return self._attrs + + def _set_opt_strings(self, opts): + """directly from optparse + + might not be necessary as this is passed to argparse later on""" + for opt in opts: + if len(opt) < 2: + raise ArgumentError( + "invalid option string %r: " + "must be at least two characters long" % opt, self) + elif len(opt) == 2: + if not (opt[0] == "-" and opt[1] != "-"): + raise ArgumentError( + "invalid short option string %r: " + "must be of the form -x, (x any non-dash char)" % opt, + self) + self._short_opts.append(opt) + else: + if not (opt[0:2] == "--" and opt[2] != "-"): + raise ArgumentError( + "invalid long option string %r: " + "must start with --, followed by non-dash" % opt, + self) + self._long_opts.append(opt) + + def __repr__(self): + retval = 'Argument(' + if self._short_opts: + retval += '_short_opts: ' + repr(self._short_opts) + ', ' + if self._long_opts: + retval += '_long_opts: ' + repr(self._long_opts) + ', ' + retval += 'dest: ' + repr(self.dest) + ', ' + if hasattr(self, 'type'): + retval += 'type: ' + repr(self.type) + ', ' + if hasattr(self, 'default'): + retval += 'default: ' + repr(self.default) + ', ' + if retval[-2:] == ', ': # always long enough to test ("Argument(" ) + retval = retval[:-2] + retval += ')' + return retval + + class OptionGroup: def __init__(self, name, description="", parser=None): self.name = name @@ -115,11 +245,11 @@ def addoption(self, *optnames, **attrs): """ add an option to this group. """ - option = py.std.optparse.Option(*optnames, **attrs) + option = Argument(*optnames, **attrs) self._addoption_instance(option, shortupper=False) def _addoption(self, *optnames, **attrs): - option = py.std.optparse.Option(*optnames, **attrs) + option = Argument(*optnames, **attrs) self._addoption_instance(option, shortupper=True) def _addoption_instance(self, option, shortupper=False): @@ -132,11 +262,11 @@ self.options.append(option) -class MyOptionParser(py.std.optparse.OptionParser): +class MyOptionParser(py.std.argparse.ArgumentParser): def __init__(self, parser): self._parser = parser - py.std.optparse.OptionParser.__init__(self, usage=parser._usage, - add_help_option=False) + py.std.argparse.ArgumentParser.__init__(self, usage=parser._usage, + add_help=False) def format_epilog(self, formatter): hints = self._parser.hints if hints: @@ -263,12 +393,15 @@ 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 self._parser = Parser( - usage="usage: %prog [options] [file_or_dir] [file_or_dir] [...]", + usage="%%(prog)s [options] [%s] [%s] [...]" % (_a, _a), processopt=self._processopt, ) #: a pluginmanager instance diff -r abfc7dc64ed4f5a50e917ac8a30863be7e2361a7 -r a3f34a534d44feee1c77f5122402c761259a11d9 _pytest/helpconfig.py --- a/_pytest/helpconfig.py +++ b/_pytest/helpconfig.py @@ -62,6 +62,7 @@ def showhelp(config): tw = py.io.TerminalWriter() tw.write(config._parser.optparser.format_help()) + tw.write(config._parser.optparser.format_epilog(None)) tw.line() tw.line() #tw.sep( "=", "config file settings") diff -r abfc7dc64ed4f5a50e917ac8a30863be7e2361a7 -r a3f34a534d44feee1c77f5122402c761259a11d9 _pytest/hookspec.py --- a/_pytest/hookspec.py +++ b/_pytest/hookspec.py @@ -23,7 +23,7 @@ """modify command line arguments before option parsing. """ def pytest_addoption(parser): - """register optparse-style options and ini-style config values. + """register argparse-style options and ini-style config values. This function must be implemented in a :ref:`plugin ` and is called once at the beginning of a test run. diff -r abfc7dc64ed4f5a50e917ac8a30863be7e2361a7 -r a3f34a534d44feee1c77f5122402c761259a11d9 _pytest/main.py --- a/_pytest/main.py +++ b/_pytest/main.py @@ -35,7 +35,7 @@ dest="exitfirst", help="exit instantly on first error or failed test."), group._addoption('--maxfail', metavar="num", - action="store", type="int", dest="maxfail", default=0, + action="store", type=int, dest="maxfail", default=0, help="exit after first num failures or errors.") group._addoption('--strict', action="store_true", help="run pytest in strict mode, warnings become errors.") diff -r abfc7dc64ed4f5a50e917ac8a30863be7e2361a7 -r a3f34a534d44feee1c77f5122402c761259a11d9 _pytest/pastebin.py --- a/_pytest/pastebin.py +++ b/_pytest/pastebin.py @@ -10,7 +10,7 @@ group = parser.getgroup("terminal reporting") group._addoption('--pastebin', metavar="mode", action='store', dest="pastebin", default=None, - type="choice", choices=['failed', 'all'], + choices=['failed', 'all'], help="send failed|all info to bpaste.net pastebin service.") def pytest_configure(__multicall__, config): diff -r abfc7dc64ed4f5a50e917ac8a30863be7e2361a7 -r a3f34a534d44feee1c77f5122402c761259a11d9 _pytest/runner.py --- a/_pytest/runner.py +++ b/_pytest/runner.py @@ -18,7 +18,7 @@ def pytest_addoption(parser): group = parser.getgroup("terminal reporting", "reporting", after="general") group.addoption('--durations', - action="store", type="int", default=None, metavar="N", + action="store", type=int, default=None, metavar="N", help="show N slowest setup/test durations (N=0 for all)."), def pytest_terminal_summary(terminalreporter): diff -r abfc7dc64ed4f5a50e917ac8a30863be7e2361a7 -r a3f34a534d44feee1c77f5122402c761259a11d9 _pytest/terminal.py --- a/_pytest/terminal.py +++ b/_pytest/terminal.py @@ -25,7 +25,7 @@ help="(deprecated, use -r)") group._addoption('--tb', metavar="style", action="store", dest="tbstyle", default='long', - type="choice", choices=['long', 'short', 'no', 'line', 'native'], + choices=['long', 'short', 'no', 'line', 'native'], help="traceback print mode (long/short/line/native/no).") group._addoption('--fulltrace', action="store_true", dest="fulltrace", default=False, diff -r abfc7dc64ed4f5a50e917ac8a30863be7e2361a7 -r a3f34a534d44feee1c77f5122402c761259a11d9 pytest.py --- a/pytest.py +++ b/pytest.py @@ -1,3 +1,4 @@ +# PYTHON_ARGCOMPLETE_OK """ pytest: unit and functional testing with Python. """ diff -r abfc7dc64ed4f5a50e917ac8a30863be7e2361a7 -r a3f34a534d44feee1c77f5122402c761259a11d9 testing/test_mark.py --- a/testing/test_mark.py +++ b/testing/test_mark.py @@ -451,12 +451,22 @@ assert 0 test_one.mykeyword = True """) + reprec = testdir.inline_run("-k", "mykeyword", p) + passed, skipped, failed = reprec.countoutcomes() + assert failed == 1 + + @pytest.mark.xfail + def test_keyword_extra_dash(self, testdir): + p = testdir.makepyfile(""" + def test_one(): + assert 0 + test_one.mykeyword = True + """) + # with argparse the argument to an option cannot + # start with '-' reprec = testdir.inline_run("-k", "-mykeyword", p) passed, skipped, failed = reprec.countoutcomes() assert passed + skipped + failed == 0 - reprec = testdir.inline_run("-k", "mykeyword", p) - passed, skipped, failed = reprec.countoutcomes() - assert failed == 1 def test_no_magic_values(self, testdir): """Make sure the tests do not match on magic values, diff -r abfc7dc64ed4f5a50e917ac8a30863be7e2361a7 -r a3f34a534d44feee1c77f5122402c761259a11d9 testing/test_parseopt.py --- a/testing/test_parseopt.py +++ b/testing/test_parseopt.py @@ -7,8 +7,44 @@ parser = parseopt.Parser(usage="xyz") pytest.raises(SystemExit, 'parser.parse(["-h"])') out, err = capsys.readouterr() - assert err.find("no such option") != -1 + assert err.find("error: unrecognized arguments") != -1 + def test_argument(self): + with pytest.raises(parseopt.ArgumentError): + # need a short or long option + argument = parseopt.Argument() + argument = parseopt.Argument('-t') + assert argument._short_opts == ['-t'] + assert argument._long_opts == [] + assert argument.dest == 't' + argument = parseopt.Argument('-t', '--test') + assert argument._short_opts == ['-t'] + assert argument._long_opts == ['--test'] + assert argument.dest == 'test' + argument = parseopt.Argument('-t', '--test', dest='abc') + assert argument.dest == 'abc' + + def test_argument_type(self): + argument = parseopt.Argument('-t', dest='abc', type='int') + assert argument.type is int + argument = parseopt.Argument('-t', dest='abc', type='string') + assert argument.type is str + argument = parseopt.Argument('-t', dest='abc', type=float) + assert argument.type is float + with pytest.raises(KeyError): + argument = parseopt.Argument('-t', dest='abc', type='choice') + argument = parseopt.Argument('-t', dest='abc', type='choice', + choices=['red', 'blue']) + assert argument.type is str + + def test_argument_processopt(self): + argument = parseopt.Argument('-t', type=int) + argument.default = 42 + argument.dest = 'abc' + res = argument.attrs() + assert res['default'] == 42 + assert res['dest'] == 'abc' + def test_group_add_and_get(self): parser = parseopt.Parser() group = parser.getgroup("hello", description="desc") @@ -36,7 +72,7 @@ group = parseopt.OptionGroup("hello") group.addoption("--option1", action="store_true") assert len(group.options) == 1 - assert isinstance(group.options[0], py.std.optparse.Option) + assert isinstance(group.options[0], parseopt.Argument) def test_group_shortopt_lowercase(self): parser = parseopt.Parser() @@ -58,19 +94,19 @@ def test_parse(self): parser = parseopt.Parser() parser.addoption("--hello", dest="hello", action="store") - option, args = parser.parse(['--hello', 'world']) - assert option.hello == "world" - assert not args + args = parser.parse(['--hello', 'world']) + assert args.hello == "world" + assert not getattr(args, parseopt.Config._file_or_dir) - def test_parse(self): + def test_parse2(self): parser = parseopt.Parser() - option, args = parser.parse([py.path.local()]) - assert args[0] == py.path.local() + args = parser.parse([py.path.local()]) + assert getattr(args, parseopt.Config._file_or_dir)[0] == py.path.local() def test_parse_will_set_default(self): parser = parseopt.Parser() parser.addoption("--hello", dest="hello", default="x", action="store") - option, args = parser.parse([]) + option = parser.parse([]) assert option.hello == "x" del option.hello args = parser.parse_setoption([], option) @@ -87,28 +123,37 @@ assert option.world == 42 assert not args + def test_parse_special_destination(self): + parser = parseopt.Parser() + x = parser.addoption("--ultimate-answer", type=int) + args = parser.parse(['--ultimate-answer', '42']) + assert args.ultimate_answer == 42 + def test_parse_defaultgetter(self): def defaultget(option): - if option.type == "int": + if not hasattr(option, 'type'): + return + if option.type is int: option.default = 42 - elif option.type == "string": + elif option.type is str: option.default = "world" parser = parseopt.Parser(processopt=defaultget) parser.addoption("--this", dest="this", type="int", action="store") parser.addoption("--hello", dest="hello", type="string", action="store") parser.addoption("--no", dest="no", action="store_true") - option, args = parser.parse([]) + option = parser.parse([]) assert option.hello == "world" assert option.this == 42 - + assert option.no is False @pytest.mark.skipif("sys.version_info < (2,5)") def test_addoption_parser_epilog(testdir): testdir.makeconftest(""" def pytest_addoption(parser): parser.hints.append("hello world") + parser.hints.append("from me too") """) result = testdir.runpytest('--help') #assert result.ret != 0 - result.stdout.fnmatch_lines(["*hint: hello world*"]) + result.stdout.fnmatch_lines(["hint: hello world", "hint: from me too"]) https://bitbucket.org/hpk42/pytest/commits/8379d69dcb3f/ Changeset: 8379d69dcb3f Branch: argparse User: Anthon van der Neut Date: 2013-07-25 17:26:48 Summary: auto change %default -> %(default)s in help parameter string (on retrieval) added code for warnings on optparse arguments (type, help), which can be easily switched on with TYPE_WARN = True in config.py installed and tested ( py.test --help ) pytest-quickcheck-0.7 pytest-gae-0.2.2 pytest-growl-0.1 pytest-bdd-0.4.7 pytest-bdd-splinter-0.4.4 pytest-cache-1.0 pytest-capturelog-0.7 pytest-codecheckers-0.2 pytest-contextfixture-0.1.1 pytest-cov-1.6 pytest-flakes-0.1 pytest-incremental-0.3.0 pytest-xdist-1.8 pytest-localserver-0.1.5 pytest-monkeyplus-1.1.0 pytest-oerp-0.2.0 pytest-pep8-1.0.4 pytest-pydev-0.1 pytest-rage-0.1 pytest-runfailed-0.3 pytest-timeout-0.3 pytest-xprocess-0.7 pytest-browsermob-proxy-0.1 pytest-mozwebqa-1.1.1 pytest-random-0.02 pytest-rerunfailures-0.03 pytest-zap-0.1 pytest-blockage-0.1 pytest-django-2.3.0 pytest-figleaf-1.0 pytest-greendots-0.1 pytest-instafail-0.1.0 pytest-konira-0.2 pytest-marker-bugzilla-0.06 pytest-marks-0.4 pytest-poo-0.2 pytest-twisted-1.4 pytest-yamlwsgi-0.6 Affected #: 1 file diff -r a3f34a534d44feee1c77f5122402c761259a11d9 -r 8379d69dcb3fbb3349daf1ae0a6d5441f8289a18 _pytest/config.py --- a/_pytest/config.py +++ b/_pytest/config.py @@ -5,6 +5,12 @@ from _pytest.core import PluginManager import pytest +# 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) config.parse(args) @@ -147,6 +153,17 @@ self._short_opts = [] self._long_opts = [] self.dest = attrs.get('dest') + if TYPE_WARN: + try: + help = attrs['help'] + if '%default' in help: + warnings.warn( + 'py.test now uses argparse. "%default" should be' + ' changed to "%(default)s" ', + FutureWarning, + stacklevel=3) + except KeyError: + pass try: typ = attrs['type'] except KeyError: @@ -155,10 +172,25 @@ # 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( + 'type argument to addoption() is a string %r.' + ' For parsearg this is optional and when supplied ' + ' should be a type.' + ' (options: %s)' % (typ, names), + FutureWarning, + stacklevel=3) # argparse expects a type here take it from # the type of the first element attrs['type'] = type(attrs['choices'][0]) else: + if TYPE_WARN: + warnings.warn( + 'type argument to addoption() is a string %r.' + ' For parsearg this should be a type.' + ' (options: %s)' % (typ, names), + FutureWarning, + stacklevel=3) attrs['type'] = Argument._typ_map[typ] # used in test_parseopt -> test_parse_defaultgetter self.type = attrs['type'] @@ -185,7 +217,7 @@ def attrs(self): # update any attributes set by processopt - attrs = 'default dest'.split() + attrs = 'default dest help'.split() if self.dest: attrs.append(self.dest) for attr in attrs: @@ -193,6 +225,11 @@ self._attrs[attr] = getattr(self, attr) except AttributeError: pass + if self._attrs.get('help'): + a = self._attrs['help'] + a = a.replace('%default', '%(default)s') + #a = a.replace('%prog', '%(prog)s') + self._attrs['help'] = a return self._attrs def _set_opt_strings(self, opts): https://bitbucket.org/hpk42/pytest/commits/4405d5fd6cae/ Changeset: 4405d5fd6cae User: hpk42 Date: 2013-07-26 07:41:43 Summary: Merged in anthon_van_der_neut/pytest/argparse (pull request #46) argparse / argcomplete Affected #: 11 files diff -r abfc7dc64ed4f5a50e917ac8a30863be7e2361a7 -r 4405d5fd6caec4072177911d9af4e1c57fe66cec _pytest/capture.py --- a/_pytest/capture.py +++ b/_pytest/capture.py @@ -6,7 +6,7 @@ def pytest_addoption(parser): group = parser.getgroup("general") group._addoption('--capture', action="store", default=None, - metavar="method", type="choice", choices=['fd', 'sys', 'no'], + metavar="method", choices=['fd', 'sys', 'no'], help="per-test capturing method: one of fd (default)|sys|no.") group._addoption('-s', action="store_const", const="no", dest="capture", help="shortcut for --capture=no.") diff -r abfc7dc64ed4f5a50e917ac8a30863be7e2361a7 -r 4405d5fd6caec4072177911d9af4e1c57fe66cec _pytest/config.py --- a/_pytest/config.py +++ b/_pytest/config.py @@ -5,6 +5,12 @@ from _pytest.core import PluginManager import pytest +# 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) config.parse(args) @@ -80,16 +86,20 @@ for group in groups: if group.options: desc = group.description or group.name - optgroup = py.std.optparse.OptionGroup(optparser, desc) - optgroup.add_options(group.options) - optparser.add_option_group(optgroup) + arggroup = optparser.add_argument_group(desc) + for option in group.options: + n = option.names() + a = option.attrs() + arggroup.add_argument(*n, **a) + optparser.add_argument(Config._file_or_dir, nargs='*') + try_argcomplete(self.optparser) return self.optparser.parse_args([str(x) for x in args]) def parse_setoption(self, args, option): - parsedoption, args = self.parse(args) + parsedoption = self.parse(args) for name, value in parsedoption.__dict__.items(): setattr(option, name, value) - return args + return getattr(parsedoption, Config._file_or_dir) def addini(self, name, help, type=None, default=None): """ register an ini-file option. @@ -105,7 +115,164 @@ self._inidict[name] = (help, type, default) self._ininames.append(name) +def try_argcomplete(parser): + try: + import argcomplete + except ImportError: + pass + else: + argcomplete.autocomplete(parser) +class ArgumentError(Exception): + """ + Raised if an Argument instance is created with invalid or + inconsistent arguments. + """ + + def __init__(self, msg, option): + self.msg = msg + self.option_id = str(option) + + def __str__(self): + if self.option_id: + return "option %s: %s" % (self.option_id, self.msg) + else: + return self.msg + + +class Argument: + """class that mimics the necessary behaviour of py.std.optparse.Option """ + _typ_map = { + 'int': int, + 'string': str, + } + + def __init__(self, *names, **attrs): + """store parms in private vars for use in add_argument""" + self._attrs = attrs + self._short_opts = [] + self._long_opts = [] + self.dest = attrs.get('dest') + if TYPE_WARN: + try: + help = attrs['help'] + if '%default' in help: + warnings.warn( + 'py.test now uses argparse. "%default" should be' + ' changed to "%(default)s" ', + FutureWarning, + stacklevel=3) + except KeyError: + pass + try: + typ = attrs['type'] + except KeyError: + pass + else: + # 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( + 'type argument to addoption() is a string %r.' + ' For parsearg this is optional and when supplied ' + ' should be a type.' + ' (options: %s)' % (typ, names), + FutureWarning, + stacklevel=3) + # argparse expects a type here take it from + # the type of the first element + attrs['type'] = type(attrs['choices'][0]) + else: + if TYPE_WARN: + warnings.warn( + 'type argument to addoption() is a string %r.' + ' For parsearg this should be a type.' + ' (options: %s)' % (typ, names), + FutureWarning, + stacklevel=3) + attrs['type'] = Argument._typ_map[typ] + # used in test_parseopt -> test_parse_defaultgetter + self.type = attrs['type'] + else: + self.type = typ + try: + # attribute existence is tested in Config._processopt + self.default = attrs['default'] + except KeyError: + pass + self._set_opt_strings(names) + if not self.dest: + if self._long_opts: + self.dest = self._long_opts[0][2:].replace('-', '_') + else: + try: + self.dest = self._short_opts[0][1:] + except IndexError: + raise ArgumentError( + 'need a long or short option', self) + + def names(self): + return self._short_opts + self._long_opts + + def attrs(self): + # update any attributes set by processopt + attrs = 'default dest help'.split() + if self.dest: + attrs.append(self.dest) + for attr in attrs: + try: + self._attrs[attr] = getattr(self, attr) + except AttributeError: + pass + if self._attrs.get('help'): + a = self._attrs['help'] + a = a.replace('%default', '%(default)s') + #a = a.replace('%prog', '%(prog)s') + self._attrs['help'] = a + return self._attrs + + def _set_opt_strings(self, opts): + """directly from optparse + + might not be necessary as this is passed to argparse later on""" + for opt in opts: + if len(opt) < 2: + raise ArgumentError( + "invalid option string %r: " + "must be at least two characters long" % opt, self) + elif len(opt) == 2: + if not (opt[0] == "-" and opt[1] != "-"): + raise ArgumentError( + "invalid short option string %r: " + "must be of the form -x, (x any non-dash char)" % opt, + self) + self._short_opts.append(opt) + else: + if not (opt[0:2] == "--" and opt[2] != "-"): + raise ArgumentError( + "invalid long option string %r: " + "must start with --, followed by non-dash" % opt, + self) + self._long_opts.append(opt) + + def __repr__(self): + retval = 'Argument(' + if self._short_opts: + retval += '_short_opts: ' + repr(self._short_opts) + ', ' + if self._long_opts: + retval += '_long_opts: ' + repr(self._long_opts) + ', ' + retval += 'dest: ' + repr(self.dest) + ', ' + if hasattr(self, 'type'): + retval += 'type: ' + repr(self.type) + ', ' + if hasattr(self, 'default'): + retval += 'default: ' + repr(self.default) + ', ' + if retval[-2:] == ', ': # always long enough to test ("Argument(" ) + retval = retval[:-2] + retval += ')' + return retval + + class OptionGroup: def __init__(self, name, description="", parser=None): self.name = name @@ -115,11 +282,11 @@ def addoption(self, *optnames, **attrs): """ add an option to this group. """ - option = py.std.optparse.Option(*optnames, **attrs) + option = Argument(*optnames, **attrs) self._addoption_instance(option, shortupper=False) def _addoption(self, *optnames, **attrs): - option = py.std.optparse.Option(*optnames, **attrs) + option = Argument(*optnames, **attrs) self._addoption_instance(option, shortupper=True) def _addoption_instance(self, option, shortupper=False): @@ -132,11 +299,11 @@ self.options.append(option) -class MyOptionParser(py.std.optparse.OptionParser): +class MyOptionParser(py.std.argparse.ArgumentParser): def __init__(self, parser): self._parser = parser - py.std.optparse.OptionParser.__init__(self, usage=parser._usage, - add_help_option=False) + py.std.argparse.ArgumentParser.__init__(self, usage=parser._usage, + add_help=False) def format_epilog(self, formatter): hints = self._parser.hints if hints: @@ -263,12 +430,15 @@ 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 self._parser = Parser( - usage="usage: %prog [options] [file_or_dir] [file_or_dir] [...]", + usage="%%(prog)s [options] [%s] [%s] [...]" % (_a, _a), processopt=self._processopt, ) #: a pluginmanager instance diff -r abfc7dc64ed4f5a50e917ac8a30863be7e2361a7 -r 4405d5fd6caec4072177911d9af4e1c57fe66cec _pytest/helpconfig.py --- a/_pytest/helpconfig.py +++ b/_pytest/helpconfig.py @@ -62,6 +62,7 @@ def showhelp(config): tw = py.io.TerminalWriter() tw.write(config._parser.optparser.format_help()) + tw.write(config._parser.optparser.format_epilog(None)) tw.line() tw.line() #tw.sep( "=", "config file settings") diff -r abfc7dc64ed4f5a50e917ac8a30863be7e2361a7 -r 4405d5fd6caec4072177911d9af4e1c57fe66cec _pytest/hookspec.py --- a/_pytest/hookspec.py +++ b/_pytest/hookspec.py @@ -23,7 +23,7 @@ """modify command line arguments before option parsing. """ def pytest_addoption(parser): - """register optparse-style options and ini-style config values. + """register argparse-style options and ini-style config values. This function must be implemented in a :ref:`plugin ` and is called once at the beginning of a test run. diff -r abfc7dc64ed4f5a50e917ac8a30863be7e2361a7 -r 4405d5fd6caec4072177911d9af4e1c57fe66cec _pytest/main.py --- a/_pytest/main.py +++ b/_pytest/main.py @@ -35,7 +35,7 @@ dest="exitfirst", help="exit instantly on first error or failed test."), group._addoption('--maxfail', metavar="num", - action="store", type="int", dest="maxfail", default=0, + action="store", type=int, dest="maxfail", default=0, help="exit after first num failures or errors.") group._addoption('--strict', action="store_true", help="run pytest in strict mode, warnings become errors.") diff -r abfc7dc64ed4f5a50e917ac8a30863be7e2361a7 -r 4405d5fd6caec4072177911d9af4e1c57fe66cec _pytest/pastebin.py --- a/_pytest/pastebin.py +++ b/_pytest/pastebin.py @@ -10,7 +10,7 @@ group = parser.getgroup("terminal reporting") group._addoption('--pastebin', metavar="mode", action='store', dest="pastebin", default=None, - type="choice", choices=['failed', 'all'], + choices=['failed', 'all'], help="send failed|all info to bpaste.net pastebin service.") def pytest_configure(__multicall__, config): diff -r abfc7dc64ed4f5a50e917ac8a30863be7e2361a7 -r 4405d5fd6caec4072177911d9af4e1c57fe66cec _pytest/runner.py --- a/_pytest/runner.py +++ b/_pytest/runner.py @@ -18,7 +18,7 @@ def pytest_addoption(parser): group = parser.getgroup("terminal reporting", "reporting", after="general") group.addoption('--durations', - action="store", type="int", default=None, metavar="N", + action="store", type=int, default=None, metavar="N", help="show N slowest setup/test durations (N=0 for all)."), def pytest_terminal_summary(terminalreporter): diff -r abfc7dc64ed4f5a50e917ac8a30863be7e2361a7 -r 4405d5fd6caec4072177911d9af4e1c57fe66cec _pytest/terminal.py --- a/_pytest/terminal.py +++ b/_pytest/terminal.py @@ -25,7 +25,7 @@ help="(deprecated, use -r)") group._addoption('--tb', metavar="style", action="store", dest="tbstyle", default='long', - type="choice", choices=['long', 'short', 'no', 'line', 'native'], + choices=['long', 'short', 'no', 'line', 'native'], help="traceback print mode (long/short/line/native/no).") group._addoption('--fulltrace', action="store_true", dest="fulltrace", default=False, diff -r abfc7dc64ed4f5a50e917ac8a30863be7e2361a7 -r 4405d5fd6caec4072177911d9af4e1c57fe66cec pytest.py --- a/pytest.py +++ b/pytest.py @@ -1,3 +1,4 @@ +# PYTHON_ARGCOMPLETE_OK """ pytest: unit and functional testing with Python. """ diff -r abfc7dc64ed4f5a50e917ac8a30863be7e2361a7 -r 4405d5fd6caec4072177911d9af4e1c57fe66cec testing/test_mark.py --- a/testing/test_mark.py +++ b/testing/test_mark.py @@ -451,12 +451,22 @@ assert 0 test_one.mykeyword = True """) + reprec = testdir.inline_run("-k", "mykeyword", p) + passed, skipped, failed = reprec.countoutcomes() + assert failed == 1 + + @pytest.mark.xfail + def test_keyword_extra_dash(self, testdir): + p = testdir.makepyfile(""" + def test_one(): + assert 0 + test_one.mykeyword = True + """) + # with argparse the argument to an option cannot + # start with '-' reprec = testdir.inline_run("-k", "-mykeyword", p) passed, skipped, failed = reprec.countoutcomes() assert passed + skipped + failed == 0 - reprec = testdir.inline_run("-k", "mykeyword", p) - passed, skipped, failed = reprec.countoutcomes() - assert failed == 1 def test_no_magic_values(self, testdir): """Make sure the tests do not match on magic values, diff -r abfc7dc64ed4f5a50e917ac8a30863be7e2361a7 -r 4405d5fd6caec4072177911d9af4e1c57fe66cec testing/test_parseopt.py --- a/testing/test_parseopt.py +++ b/testing/test_parseopt.py @@ -7,8 +7,44 @@ parser = parseopt.Parser(usage="xyz") pytest.raises(SystemExit, 'parser.parse(["-h"])') out, err = capsys.readouterr() - assert err.find("no such option") != -1 + assert err.find("error: unrecognized arguments") != -1 + def test_argument(self): + with pytest.raises(parseopt.ArgumentError): + # need a short or long option + argument = parseopt.Argument() + argument = parseopt.Argument('-t') + assert argument._short_opts == ['-t'] + assert argument._long_opts == [] + assert argument.dest == 't' + argument = parseopt.Argument('-t', '--test') + assert argument._short_opts == ['-t'] + assert argument._long_opts == ['--test'] + assert argument.dest == 'test' + argument = parseopt.Argument('-t', '--test', dest='abc') + assert argument.dest == 'abc' + + def test_argument_type(self): + argument = parseopt.Argument('-t', dest='abc', type='int') + assert argument.type is int + argument = parseopt.Argument('-t', dest='abc', type='string') + assert argument.type is str + argument = parseopt.Argument('-t', dest='abc', type=float) + assert argument.type is float + with pytest.raises(KeyError): + argument = parseopt.Argument('-t', dest='abc', type='choice') + argument = parseopt.Argument('-t', dest='abc', type='choice', + choices=['red', 'blue']) + assert argument.type is str + + def test_argument_processopt(self): + argument = parseopt.Argument('-t', type=int) + argument.default = 42 + argument.dest = 'abc' + res = argument.attrs() + assert res['default'] == 42 + assert res['dest'] == 'abc' + def test_group_add_and_get(self): parser = parseopt.Parser() group = parser.getgroup("hello", description="desc") @@ -36,7 +72,7 @@ group = parseopt.OptionGroup("hello") group.addoption("--option1", action="store_true") assert len(group.options) == 1 - assert isinstance(group.options[0], py.std.optparse.Option) + assert isinstance(group.options[0], parseopt.Argument) def test_group_shortopt_lowercase(self): parser = parseopt.Parser() @@ -58,19 +94,19 @@ def test_parse(self): parser = parseopt.Parser() parser.addoption("--hello", dest="hello", action="store") - option, args = parser.parse(['--hello', 'world']) - assert option.hello == "world" - assert not args + args = parser.parse(['--hello', 'world']) + assert args.hello == "world" + assert not getattr(args, parseopt.Config._file_or_dir) - def test_parse(self): + def test_parse2(self): parser = parseopt.Parser() - option, args = parser.parse([py.path.local()]) - assert args[0] == py.path.local() + args = parser.parse([py.path.local()]) + assert getattr(args, parseopt.Config._file_or_dir)[0] == py.path.local() def test_parse_will_set_default(self): parser = parseopt.Parser() parser.addoption("--hello", dest="hello", default="x", action="store") - option, args = parser.parse([]) + option = parser.parse([]) assert option.hello == "x" del option.hello args = parser.parse_setoption([], option) @@ -87,28 +123,37 @@ assert option.world == 42 assert not args + def test_parse_special_destination(self): + parser = parseopt.Parser() + x = parser.addoption("--ultimate-answer", type=int) + args = parser.parse(['--ultimate-answer', '42']) + assert args.ultimate_answer == 42 + def test_parse_defaultgetter(self): def defaultget(option): - if option.type == "int": + if not hasattr(option, 'type'): + return + if option.type is int: option.default = 42 - elif option.type == "string": + elif option.type is str: option.default = "world" parser = parseopt.Parser(processopt=defaultget) parser.addoption("--this", dest="this", type="int", action="store") parser.addoption("--hello", dest="hello", type="string", action="store") parser.addoption("--no", dest="no", action="store_true") - option, args = parser.parse([]) + option = parser.parse([]) assert option.hello == "world" assert option.this == 42 - + assert option.no is False @pytest.mark.skipif("sys.version_info < (2,5)") def test_addoption_parser_epilog(testdir): testdir.makeconftest(""" def pytest_addoption(parser): parser.hints.append("hello world") + parser.hints.append("from me too") """) result = testdir.runpytest('--help') #assert result.ret != 0 - result.stdout.fnmatch_lines(["*hint: hello world*"]) + result.stdout.fnmatch_lines(["hint: hello world", "hint: from me too"]) 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 Jul 26 08:59:15 2013 From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org) Date: Fri, 26 Jul 2013 06:59:15 -0000 Subject: [Pytest-commit] commit/pytest: 3 new changesets Message-ID: <20130726065915.20976.55488@app07.ash-private.bitbucket.org> 3 new commits in pytest: https://bitbucket.org/hpk42/pytest/commits/1fdb76c818cc/ Changeset: 1fdb76c818cc Branch: tox_reference User: Anthon van der Neut Date: 2013-07-26 08:14:49 Summary: updated tox to live on testrun.org (the old links are still working on codespeak.net, but those docs are outdated) Affected #: 3 files diff -r abfc7dc64ed4f5a50e917ac8a30863be7e2361a7 -r 1fdb76c818cced9375ca593369558ac764280be1 doc/en/goodpractises.txt --- a/doc/en/goodpractises.txt +++ b/doc/en/goodpractises.txt @@ -23,7 +23,7 @@ If you frequently release code to the public you may want to look into `tox`_, the virtualenv test automation -tool and its `pytest support `_. +tool and its `pytest support `_. The basic idea is to generate a JUnitXML file through the ``--junitxml=PATH`` option and have a continuous integration server like Jenkins_ pick it up and generate reports. diff -r abfc7dc64ed4f5a50e917ac8a30863be7e2361a7 -r 1fdb76c818cced9375ca593369558ac764280be1 doc/en/links.inc --- a/doc/en/links.inc +++ b/doc/en/links.inc @@ -17,5 +17,5 @@ .. _`virtualenv`: http://pypi.python.org/pypi/virtualenv .. _hudson: http://hudson-ci.org/ .. _jenkins: http://jenkins-ci.org/ -.. _tox: http://codespeak.net/tox +.. _tox: http://testrun.org/tox .. _pylib: http://pylib.org diff -r abfc7dc64ed4f5a50e917ac8a30863be7e2361a7 -r 1fdb76c818cced9375ca593369558ac764280be1 doc/en/projects.txt --- a/doc/en/projects.txt +++ b/doc/en/projects.txt @@ -26,7 +26,7 @@ `16000 tests `_ * the `MoinMoin `_ Wiki Engine * `sentry `_, realtime app-maintenance and exception tracking -* `tox `_, virtualenv/Hudson integration tool +* `tox `_, virtualenv/Hudson integration tool * `PIDA `_ framework for integrated development * `PyPM `_ ActiveState's package manager * `Fom `_ a fluid object mapper for FluidDB https://bitbucket.org/hpk42/pytest/commits/40a882235eae/ Changeset: 40a882235eae Branch: tox_reference User: Anthon van der Neut Date: 2013-07-26 08:20:26 Summary: also update testrun.org in ??? Affected #: 3 files diff -r 1fdb76c818cced9375ca593369558ac764280be1 -r 40a882235eaeb6553941602db8bd3bf1222eb876 doc/ja/goodpractises.txt --- a/doc/ja/goodpractises.txt +++ b/doc/ja/goodpractises.txt @@ -39,11 +39,11 @@ .. If you frequently release code to the public you may want to look into `tox`_, the virtualenv test automation - tool and its `pytest support `_. + tool and its `pytest support `_. The basic idea is to generate a JUnitXML file through the ``--junitxml=PATH`` option and have a continuous integration server like Jenkins_ pick it up and generate reports. -???????????????????????virtualenv ?????????? `pytest ???? `_ ??? `tox`_ ??????????????????????? ``--junitxml=PATH`` ???????? JUnitXML ?????????????? Jenkins_ ??????????????????????????????????????????? +???????????????????????virtualenv ?????????? `pytest ???? `_ ??? `tox`_ ??????????????????????? ``--junitxml=PATH`` ???????? JUnitXML ?????????????? Jenkins_ ??????????????????????????????????????????? .. _standalone: .. _`genscript method`: diff -r 1fdb76c818cced9375ca593369558ac764280be1 -r 40a882235eaeb6553941602db8bd3bf1222eb876 doc/ja/links.inc --- a/doc/ja/links.inc +++ b/doc/ja/links.inc @@ -17,4 +17,4 @@ .. _`virtualenv`: http://pypi.python.org/pypi/virtualenv .. _hudson: http://hudson-ci.org/ .. _jenkins: http://jenkins-ci.org/ -.. _tox: http://codespeak.net/tox +.. _tox: http://testrun.org/tox diff -r 1fdb76c818cced9375ca593369558ac764280be1 -r 40a882235eaeb6553941602db8bd3bf1222eb876 doc/ja/projects.txt --- a/doc/ja/projects.txt +++ b/doc/ja/projects.txt @@ -16,7 +16,7 @@ * `PyPy `_, Python with a JIT compiler, running over `16000 tests `_ * the `MoinMoin `_ Wiki Engine - * `tox `_, virtualenv/Hudson integration tool + * `tox `_, virtualenv/Hudson integration tool * `PIDA `_ framework for integrated development * `PyPM `_ ActiveState's package manager * `Fom `_ a fluid object mapper for FluidDB @@ -49,7 +49,7 @@ * `PyPy `_: JIT ?????????? Python? `16000 ??? `_ ????? * `MoinMoin `_: Wiki ???? -* `tox `_: virtualenv/Jenkins ???????????? +* `tox `_: virtualenv/Jenkins ???????????? * `PIDA `_: ??????????? * `PyPM `_: Activestate ????????????? * `Fom `_: FluidDB ? fluid ?????????? https://bitbucket.org/hpk42/pytest/commits/e45e0210341c/ Changeset: e45e0210341c User: hpk42 Date: 2013-07-26 08:59:12 Summary: Merged in anthon_van_der_neut/pytest/tox_reference (pull request #47) update for links to tox Affected #: 6 files diff -r 4405d5fd6caec4072177911d9af4e1c57fe66cec -r e45e0210341cfc1bacfe27c3edf92079c3d40d4b doc/en/goodpractises.txt --- a/doc/en/goodpractises.txt +++ b/doc/en/goodpractises.txt @@ -23,7 +23,7 @@ If you frequently release code to the public you may want to look into `tox`_, the virtualenv test automation -tool and its `pytest support `_. +tool and its `pytest support `_. The basic idea is to generate a JUnitXML file through the ``--junitxml=PATH`` option and have a continuous integration server like Jenkins_ pick it up and generate reports. diff -r 4405d5fd6caec4072177911d9af4e1c57fe66cec -r e45e0210341cfc1bacfe27c3edf92079c3d40d4b doc/en/links.inc --- a/doc/en/links.inc +++ b/doc/en/links.inc @@ -17,5 +17,5 @@ .. _`virtualenv`: http://pypi.python.org/pypi/virtualenv .. _hudson: http://hudson-ci.org/ .. _jenkins: http://jenkins-ci.org/ -.. _tox: http://codespeak.net/tox +.. _tox: http://testrun.org/tox .. _pylib: http://pylib.org diff -r 4405d5fd6caec4072177911d9af4e1c57fe66cec -r e45e0210341cfc1bacfe27c3edf92079c3d40d4b doc/en/projects.txt --- a/doc/en/projects.txt +++ b/doc/en/projects.txt @@ -26,7 +26,7 @@ `16000 tests `_ * the `MoinMoin `_ Wiki Engine * `sentry `_, realtime app-maintenance and exception tracking -* `tox `_, virtualenv/Hudson integration tool +* `tox `_, virtualenv/Hudson integration tool * `PIDA `_ framework for integrated development * `PyPM `_ ActiveState's package manager * `Fom `_ a fluid object mapper for FluidDB diff -r 4405d5fd6caec4072177911d9af4e1c57fe66cec -r e45e0210341cfc1bacfe27c3edf92079c3d40d4b doc/ja/goodpractises.txt --- a/doc/ja/goodpractises.txt +++ b/doc/ja/goodpractises.txt @@ -39,11 +39,11 @@ .. If you frequently release code to the public you may want to look into `tox`_, the virtualenv test automation - tool and its `pytest support `_. + tool and its `pytest support `_. The basic idea is to generate a JUnitXML file through the ``--junitxml=PATH`` option and have a continuous integration server like Jenkins_ pick it up and generate reports. -???????????????????????virtualenv ?????????? `pytest ???? `_ ??? `tox`_ ??????????????????????? ``--junitxml=PATH`` ???????? JUnitXML ?????????????? Jenkins_ ??????????????????????????????????????????? +???????????????????????virtualenv ?????????? `pytest ???? `_ ??? `tox`_ ??????????????????????? ``--junitxml=PATH`` ???????? JUnitXML ?????????????? Jenkins_ ??????????????????????????????????????????? .. _standalone: .. _`genscript method`: diff -r 4405d5fd6caec4072177911d9af4e1c57fe66cec -r e45e0210341cfc1bacfe27c3edf92079c3d40d4b doc/ja/links.inc --- a/doc/ja/links.inc +++ b/doc/ja/links.inc @@ -17,4 +17,4 @@ .. _`virtualenv`: http://pypi.python.org/pypi/virtualenv .. _hudson: http://hudson-ci.org/ .. _jenkins: http://jenkins-ci.org/ -.. _tox: http://codespeak.net/tox +.. _tox: http://testrun.org/tox diff -r 4405d5fd6caec4072177911d9af4e1c57fe66cec -r e45e0210341cfc1bacfe27c3edf92079c3d40d4b doc/ja/projects.txt --- a/doc/ja/projects.txt +++ b/doc/ja/projects.txt @@ -16,7 +16,7 @@ * `PyPy `_, Python with a JIT compiler, running over `16000 tests `_ * the `MoinMoin `_ Wiki Engine - * `tox `_, virtualenv/Hudson integration tool + * `tox `_, virtualenv/Hudson integration tool * `PIDA `_ framework for integrated development * `PyPM `_ ActiveState's package manager * `Fom `_ a fluid object mapper for FluidDB @@ -49,7 +49,7 @@ * `PyPy `_: JIT ?????????? Python? `16000 ??? `_ ????? * `MoinMoin `_: Wiki ???? -* `tox `_: virtualenv/Jenkins ???????????? +* `tox `_: virtualenv/Jenkins ???????????? * `PIDA `_: ??????????? * `PyPM `_: Activestate ????????????? * `Fom `_: FluidDB ? fluid ?????????? 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 Jul 26 08:59:42 2013 From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org) Date: Fri, 26 Jul 2013 06:59:42 -0000 Subject: [Pytest-commit] commit/pytest: 3 new changesets Message-ID: <20130726065942.613.70622@app19.ash-private.bitbucket.org> 3 new commits in pytest: https://bitbucket.org/hpk42/pytest/commits/18a65f4e8303/ Changeset: 18a65f4e8303 User: hpk42 Date: 2013-07-26 07:51:33 Summary: add changelog: integrate option tab-completion when argcomplete is used. Thanks Anthon van der Neut for the PR. This also lets pytest use argparse instead of optparse. Affected #: 2 files diff -r 4405d5fd6caec4072177911d9af4e1c57fe66cec -r 18a65f4e83033fb6ceaaf34b89bffee493f71f6d AUTHORS --- a/AUTHORS +++ b/AUTHORS @@ -34,3 +34,4 @@ Brian Okken Katarzyna Jachim Christian Theunert +Anthon van der Neut diff -r 4405d5fd6caec4072177911d9af4e1c57fe66cec -r 18a65f4e83033fb6ceaaf34b89bffee493f71f6d CHANGELOG --- a/CHANGELOG +++ b/CHANGELOG @@ -1,6 +1,10 @@ Changes between 2.3.5 and 2.4.DEV ----------------------------------- +- integrate option tab-completion when argcomplete is used. Thanks + Anthon van der Neut for the PR. This also lets pytest use argparse + instead of optparse. + - SO-17664702: call fixture finalizers even if the fixture function partially failed (finalizers would not always be called before) https://bitbucket.org/hpk42/pytest/commits/31bc337af337/ Changeset: 31bc337af337 User: hpk42 Date: 2013-07-26 08:59:29 Summary: merge doc changes Affected #: 6 files diff -r 18a65f4e83033fb6ceaaf34b89bffee493f71f6d -r 31bc337af337227d596eff0810bd9aa63b796dbc doc/en/goodpractises.txt --- a/doc/en/goodpractises.txt +++ b/doc/en/goodpractises.txt @@ -23,7 +23,7 @@ If you frequently release code to the public you may want to look into `tox`_, the virtualenv test automation -tool and its `pytest support `_. +tool and its `pytest support `_. The basic idea is to generate a JUnitXML file through the ``--junitxml=PATH`` option and have a continuous integration server like Jenkins_ pick it up and generate reports. diff -r 18a65f4e83033fb6ceaaf34b89bffee493f71f6d -r 31bc337af337227d596eff0810bd9aa63b796dbc doc/en/links.inc --- a/doc/en/links.inc +++ b/doc/en/links.inc @@ -17,5 +17,5 @@ .. _`virtualenv`: http://pypi.python.org/pypi/virtualenv .. _hudson: http://hudson-ci.org/ .. _jenkins: http://jenkins-ci.org/ -.. _tox: http://codespeak.net/tox +.. _tox: http://testrun.org/tox .. _pylib: http://pylib.org diff -r 18a65f4e83033fb6ceaaf34b89bffee493f71f6d -r 31bc337af337227d596eff0810bd9aa63b796dbc doc/en/projects.txt --- a/doc/en/projects.txt +++ b/doc/en/projects.txt @@ -26,7 +26,7 @@ `16000 tests `_ * the `MoinMoin `_ Wiki Engine * `sentry `_, realtime app-maintenance and exception tracking -* `tox `_, virtualenv/Hudson integration tool +* `tox `_, virtualenv/Hudson integration tool * `PIDA `_ framework for integrated development * `PyPM `_ ActiveState's package manager * `Fom `_ a fluid object mapper for FluidDB diff -r 18a65f4e83033fb6ceaaf34b89bffee493f71f6d -r 31bc337af337227d596eff0810bd9aa63b796dbc doc/ja/goodpractises.txt --- a/doc/ja/goodpractises.txt +++ b/doc/ja/goodpractises.txt @@ -39,11 +39,11 @@ .. If you frequently release code to the public you may want to look into `tox`_, the virtualenv test automation - tool and its `pytest support `_. + tool and its `pytest support `_. The basic idea is to generate a JUnitXML file through the ``--junitxml=PATH`` option and have a continuous integration server like Jenkins_ pick it up and generate reports. -???????????????????????virtualenv ?????????? `pytest ???? `_ ??? `tox`_ ??????????????????????? ``--junitxml=PATH`` ???????? JUnitXML ?????????????? Jenkins_ ??????????????????????????????????????????? +???????????????????????virtualenv ?????????? `pytest ???? `_ ??? `tox`_ ??????????????????????? ``--junitxml=PATH`` ???????? JUnitXML ?????????????? Jenkins_ ??????????????????????????????????????????? .. _standalone: .. _`genscript method`: diff -r 18a65f4e83033fb6ceaaf34b89bffee493f71f6d -r 31bc337af337227d596eff0810bd9aa63b796dbc doc/ja/links.inc --- a/doc/ja/links.inc +++ b/doc/ja/links.inc @@ -17,4 +17,4 @@ .. _`virtualenv`: http://pypi.python.org/pypi/virtualenv .. _hudson: http://hudson-ci.org/ .. _jenkins: http://jenkins-ci.org/ -.. _tox: http://codespeak.net/tox +.. _tox: http://testrun.org/tox diff -r 18a65f4e83033fb6ceaaf34b89bffee493f71f6d -r 31bc337af337227d596eff0810bd9aa63b796dbc doc/ja/projects.txt --- a/doc/ja/projects.txt +++ b/doc/ja/projects.txt @@ -16,7 +16,7 @@ * `PyPy `_, Python with a JIT compiler, running over `16000 tests `_ * the `MoinMoin `_ Wiki Engine - * `tox `_, virtualenv/Hudson integration tool + * `tox `_, virtualenv/Hudson integration tool * `PIDA `_ framework for integrated development * `PyPM `_ ActiveState's package manager * `Fom `_ a fluid object mapper for FluidDB @@ -49,7 +49,7 @@ * `PyPy `_: JIT ?????????? Python? `16000 ??? `_ ????? * `MoinMoin `_: Wiki ???? -* `tox `_: virtualenv/Jenkins ???????????? +* `tox `_: virtualenv/Jenkins ???????????? * `PIDA `_: ??????????? * `PyPM `_: Activestate ????????????? * `Fom `_: FluidDB ? fluid ?????????? https://bitbucket.org/hpk42/pytest/commits/085883326638/ Changeset: 085883326638 User: hpk42 Date: 2013-07-26 08:59:31 Summary: make genscript provide information as to compatibility (now that argparse is a dependency on python2.6) Affected #: 7 files diff -r 31bc337af337227d596eff0810bd9aa63b796dbc -r 085883326638467e244499fb4c781452ad850548 CHANGELOG --- a/CHANGELOG +++ b/CHANGELOG @@ -1,9 +1,12 @@ Changes between 2.3.5 and 2.4.DEV ----------------------------------- -- integrate option tab-completion when argcomplete is used. Thanks - Anthon van der Neut for the PR. This also lets pytest use argparse - instead of optparse. +- 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) @@ -68,6 +71,13 @@ - 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 31bc337af337227d596eff0810bd9aa63b796dbc -r 085883326638467e244499fb4c781452ad850548 _pytest/genscript.py --- a/_pytest/genscript.py +++ b/_pytest/genscript.py @@ -1,5 +1,6 @@ """ generate a single-file self-contained version of py.test """ import py +import sys def find_toplevel(name): for syspath in py.std.sys.path: @@ -59,11 +60,21 @@ def pytest_cmdline_main(config): genscript = config.getvalue("genscript") if genscript: + tw = py.io.TerminalWriter() + deps = ['py', '_pytest', 'pytest'] + if sys.version_info < (2,7): + deps.append("argparse") + tw.line("generated script will run on python2.5-python3.3++") + else: + tw.line("WARNING: generated script will not run on python2.6 " + "or below due to 'argparse' dependency. Use python2.6 " + "to generate a python2.5/6 compatible script", red=True) script = generate_script( 'import py; raise SystemExit(py.test.cmdline.main())', - ['py', '_pytest', 'pytest'], + deps, ) - genscript = py.path.local(genscript) genscript.write(script) + tw.line("generated pytest standalone script: %s" % genscript, + bold=True) return 0 diff -r 31bc337af337227d596eff0810bd9aa63b796dbc -r 085883326638467e244499fb4c781452ad850548 _pytest/standalonetemplate.py --- a/_pytest/standalonetemplate.py +++ b/_pytest/standalonetemplate.py @@ -13,6 +13,10 @@ self.sources = sources def find_module(self, fullname, path=None): + if fullname == "argparse" and sys.version_info >= (2,7): + # we were generated with =1.4.15"] + if sys.version_info < (2,7): + install_requires.append("argparse") + setup( name='pytest', description='py.test: simple powerful testing with Python', @@ -21,7 +25,7 @@ entry_points= make_entry_points(), cmdclass = {'test': PyTest}, # the following should be enabled for release - install_requires=['py>=1.4.14'], + install_requires=install_requires, classifiers=['Development Status :: 6 - Mature', 'Intended Audience :: Developers', 'License :: OSI Approved :: MIT License', diff -r 31bc337af337227d596eff0810bd9aa63b796dbc -r 085883326638467e244499fb4c781452ad850548 testing/conftest.py --- a/testing/conftest.py +++ b/testing/conftest.py @@ -77,6 +77,9 @@ 'python2.5': r'C:\Python25\python.exe', 'python2.4': r'C:\Python24\python.exe', 'python3.1': r'C:\Python31\python.exe', + 'python3.2': r'C:\Python32\python.exe', + 'python3.3': r'C:\Python33\python.exe', + 'python3.4': r'C:\Python34\python.exe', } def getexecutable(name, cache={}): diff -r 31bc337af337227d596eff0810bd9aa63b796dbc -r 085883326638467e244499fb4c781452ad850548 testing/test_genscript.py --- a/testing/test_genscript.py +++ b/testing/test_genscript.py @@ -1,10 +1,11 @@ +import pytest import py, os, sys import subprocess -def pytest_funcarg__standalone(request): - return request.cached_setup(scope="module", - setup=lambda: Standalone(request)) + at pytest.fixture(scope="module") +def standalone(request): + return Standalone(request) class Standalone: def __init__(self, request): @@ -20,6 +21,12 @@ return testdir._run(anypython, self.script, *args) def test_gen(testdir, anypython, standalone): + if sys.version_info >= (2,7): + result = testdir._run(anypython, "-c", + "import sys;print sys.version_info >=(2,7)") + if result.stdout.str() == "False": + pytest.skip("genscript called from python2.7 cannot work " + "earlier python versions") result = standalone.run(anypython, testdir, '--version') assert result.ret == 0 result.stderr.fnmatch_lines([ diff -r 31bc337af337227d596eff0810bd9aa63b796dbc -r 085883326638467e244499fb4c781452ad850548 testing/test_parseopt.py --- a/testing/test_parseopt.py +++ b/testing/test_parseopt.py @@ -1,3 +1,4 @@ +from __future__ import with_statement import py, pytest from _pytest import config as parseopt from textwrap import dedent @@ -44,7 +45,7 @@ res = argument.attrs() assert res['default'] == 42 assert res['dest'] == 'abc' - + def test_group_add_and_get(self): parser = parseopt.Parser() group = parser.getgroup("hello", description="desc") @@ -128,7 +129,7 @@ x = parser.addoption("--ultimate-answer", type=int) args = parser.parse(['--ultimate-answer', '42']) assert args.ultimate_answer == 42 - + def test_parse_defaultgetter(self): def defaultget(option): if not hasattr(option, 'type'): 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 Fri Jul 26 09:08:38 2013 From: notifications at travis-ci.org (Travis CI) Date: Fri, 26 Jul 2013 07:08:38 +0000 Subject: [Pytest-commit] [Still Failing] hpk42/pytest#23 (master - 7f98273) Message-ID: <51f22075cf850_22afff0182357@3bd4a3b1-b925-4c40-b0b5-c27b621e98e0.mail> Build Update for hpk42/pytest ------------------------------------- Build: #23 Status: Still Failing Duration: 4 minutes and 15 seconds Commit: 7f98273 (master) Author: holger krekel Message: make genscript provide information as to compatibility (now that argparse is a dependency on python2.6) View the changeset: https://github.com/hpk42/pytest/compare/d1dec1e32cb1...7f982737adab View the full build log and details: https://travis-ci.org/hpk42/pytest/builds/9505744 -- 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 issues-reply at bitbucket.org Sat Jul 27 17:14:59 2013 From: issues-reply at bitbucket.org (anthon_van_der_neut) Date: Sat, 27 Jul 2013 15:14:59 -0000 Subject: [Pytest-commit] Issue #107: provide smart Completers for argcomplete (hpk42/tox) Message-ID: <20130727151459.23684.63992@app01.ash-private.bitbucket.org> New issue 107: provide smart Completers for argcomplete https://bitbucket.org/hpk42/tox/issue/107/provide-smart-completers-for-argcomplete anthon_van_der_neut: To Do: - tox -e could autocomplete with the sections marked in tox.ini ( + all) From commits-noreply at bitbucket.org Mon Jul 29 15:39:33 2013 From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org) Date: Mon, 29 Jul 2013 13:39:33 -0000 Subject: [Pytest-commit] commit/pytest: hpk42: be more liberal with respect to lsof checks because jenkins keeps some files open Message-ID: <20130729133933.12661.23847@app07.ash-private.bitbucket.org> 1 new commit in pytest: https://bitbucket.org/hpk42/pytest/commits/1530d0e3abf3/ Changeset: 1530d0e3abf3 User: hpk42 Date: 2013-07-29 15:39:24 Summary: be more liberal with respect to lsof checks because jenkins keeps some files open Affected #: 1 file diff -r 085883326638467e244499fb4c781452ad850548 -r 1530d0e3abf3e60a37cfc91aac47a5545a11628c testing/conftest.py --- a/testing/conftest.py +++ b/testing/conftest.py @@ -36,7 +36,7 @@ def check_open_files(config): out2 = py.process.cmdexec("lsof -p %d" % pid) lines2 = getopenfiles(out2) - if len(lines2) > config._numfiles + 1: + if len(lines2) > config._numfiles + 3: error = [] error.append("***** %s FD leackage detected" % (len(lines2)-config._numfiles)) 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 Jul 29 15:46:10 2013 From: notifications at travis-ci.org (Travis CI) Date: Mon, 29 Jul 2013 13:46:10 +0000 Subject: [Pytest-commit] [Still Failing] hpk42/pytest#24 (master - aec5ccc) Message-ID: <51f6722265ce8_22d7e4647e@42e72664-fec9-4b8a-98c0-53e4d1b45986.mail> Build Update for hpk42/pytest ------------------------------------- Build: #24 Status: Still Failing Duration: 5 minutes and 44 seconds Commit: aec5ccc (master) Author: holger krekel Message: be more liberal with respect to lsof checks because jenkins keeps some files open View the changeset: https://github.com/hpk42/pytest/compare/7f982737adab...aec5ccc1961f View the full build log and details: https://travis-ci.org/hpk42/pytest/builds/9604749 -- 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 Wed Jul 31 07:51:09 2013 From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org) Date: Wed, 31 Jul 2013 05:51:09 -0000 Subject: [Pytest-commit] commit/pytest: 3 new changesets Message-ID: <20130731055109.29225.71564@app04.ash-private.bitbucket.org> 3 new commits in pytest: https://bitbucket.org/hpk42/pytest/commits/8c92bc4c65aa/ Changeset: 8c92bc4c65aa Branch: argcomplete User: Anthon van der Neut Date: 2013-07-30 11:26:15 Summary: Fixes for argcomplete - separate out most argcomplete related stuff in new file _argcomplete.py (could probably be in the py library) - allow positional arguments to be interspaced with optional arguments ( + test in test_parseopt.py ) - removed double argument in tox.ini - add documentation on installing argcomplete (>=0.5.7 as needed for Python 3), might need improving/incorporation in index. This does not work on 2.5 yet. I have patches for argcomplete (with/print()/"".format) but I am not sure they will be accepted. Agreed with hpk not to push for that. Removing argcomplete and leaving completion code active now works by early exit, so no longer re-runs the programs without parameters (which took long for py.test) test calls bash with a script that redirects filedescriptor 8 (as used by argcomplete), so the result can be tested. Affected #: 5 files diff -r 1530d0e3abf3e60a37cfc91aac47a5545a11628c -r 8c92bc4c65aa426cf9e795dbf4a2654cef71b46c _pytest/_argcomplete.py --- /dev/null +++ b/_pytest/_argcomplete.py @@ -0,0 +1,68 @@ + +"""allow bash-completion for argparse with argcomplete if installed +needs argcomplete>=0.5.6 for python 3.2/3.3 (older versions fail +to find the magic string, so _ARGCOMPLETE env. var is never set, and +this does not need special code. + +argcomplete does not support python 2.5 (although the changes for that +are minor). + +Function try_argcomplete(parser) should be called directly before +the call to ArgumentParser.parse_args(). + +The filescompleter is what you normally would use on the positional +arguments specification, in order to get "dirname/" after "dirn" +instead of the default "dirname ": + + optparser.add_argument(Config._file_or_dir, nargs='*' + ).completer=filescompleter + +Other, application specific, completers should go in the file +doing the add_argument calls as they need to be specified as .completer +attributes as well. (If argcomplete is not installed, the function the +attribute points to will not be used). + +--- +To include this support in another application that has setup.py generated +scripts: +- add the line: + # PYTHON_ARGCOMPLETE_OK + near the top of the main python entry point +- include in the file calling parse_args(): + from _argcomplete import try_argcomplete, filescompleter + , call try_argcomplete just before parse_args(), and optionally add + filescompleter to the positional arguments' add_argument() +If things do not work right away: +- switch on argcomplete debugging with (also helpful when doing custom + completers): + export _ARC_DEBUG=1 +- run: + python-argcomplete-check-easy-install-script $(which appname) + echo $? + will echo 0 if the magic line has been found, 1 if not +- sometimes it helps to find early on errors using: + _ARGCOMPLETE=1 _ARC_DEBUG=1 appname + which should throw a KeyError: 'COMPLINE' (which is properly set by the + global argcomplete script). + +""" + +import sys +import os + +if os.environ.get('_ARGCOMPLETE'): + # argcomplete 0.5.6 is not compatible with python 2.5.6: print/with/format + if sys.version_info[:2] < (2, 6): + sys.exit(1) + try: + import argcomplete + import argcomplete.completers + except ImportError: + sys.exit(-1) + filescompleter = argcomplete.completers.FilesCompleter() + + def try_argcomplete(parser): + argcomplete.autocomplete(parser) +else: + def try_argcomplete(parser): pass + filescompleter = None diff -r 1530d0e3abf3e60a37cfc91aac47a5545a11628c -r 8c92bc4c65aa426cf9e795dbf4a2654cef71b46c _pytest/config.py --- a/_pytest/config.py +++ b/_pytest/config.py @@ -4,6 +4,7 @@ import sys, os from _pytest.core import PluginManager import pytest +from _argcomplete import try_argcomplete, filescompleter # enable after some grace period for plugin writers TYPE_WARN = False @@ -91,7 +92,9 @@ n = option.names() a = option.attrs() arggroup.add_argument(*n, **a) - optparser.add_argument(Config._file_or_dir, nargs='*') + # bash like autocompletion for dirs (appending '/') + optparser.add_argument(Config._file_or_dir, nargs='*' + ).completer=filescompleter try_argcomplete(self.optparser) return self.optparser.parse_args([str(x) for x in args]) @@ -115,13 +118,6 @@ self._inidict[name] = (help, type, default) self._ininames.append(name) -def try_argcomplete(parser): - try: - import argcomplete - except ImportError: - pass - else: - argcomplete.autocomplete(parser) class ArgumentError(Exception): """ @@ -304,6 +300,7 @@ self._parser = parser py.std.argparse.ArgumentParser.__init__(self, usage=parser._usage, add_help=False) + def format_epilog(self, formatter): hints = self._parser.hints if hints: @@ -312,6 +309,18 @@ return s return "" + def parse_args(self, args=None, namespace=None): + """allow splitting of positional arguments""" + args, argv = self.parse_known_args(args, namespace) + if argv: + for arg in argv: + 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) + return args + + class Conftest(object): """ the single place for accessing values and interacting towards conftest modules from py.test objects. diff -r 1530d0e3abf3e60a37cfc91aac47a5545a11628c -r 8c92bc4c65aa426cf9e795dbf4a2654cef71b46c doc/en/bash-completion.txt --- /dev/null +++ b/doc/en/bash-completion.txt @@ -0,0 +1,28 @@ + +.. _bash_completion: + +Setting up bash completion +========================== + +When using bash as your shell, ``py.test`` can use argcomplete +(https://argcomplete.readthedocs.org/) for auto-completion. +For this ``argcomplete`` needs to be installed **and** enabled. + +Install argcomplete using:: + + sudo pip install 'argcomplete>=0.5.7' + +For global activation of all argcomplete enabled python applications run:: + + sudo activate-global-python-argcomplete + +For permanent (but not global) ``py.test`` activation, use:: + + register-python-argcomplete py.test >> ~/.bashrc + +For one-time activation of argcomplete for ``py.test`` only, use:: + + eval "$(register-python-argcomplete py.test)" + + + diff -r 1530d0e3abf3e60a37cfc91aac47a5545a11628c -r 8c92bc4c65aa426cf9e795dbf4a2654cef71b46c testing/test_parseopt.py --- a/testing/test_parseopt.py +++ b/testing/test_parseopt.py @@ -130,6 +130,21 @@ args = parser.parse(['--ultimate-answer', '42']) assert args.ultimate_answer == 42 + def test_parse_split_positional_arguments(self): + parser = parseopt.Parser() + 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'] + args = parser.parse(['-R', '-S', '4', '2', '-R']) + assert getattr(args, parseopt.Config._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 args.R == True + assert args.S == False + def test_parse_defaultgetter(self): def defaultget(option): if not hasattr(option, 'type'): @@ -158,3 +173,28 @@ #assert result.ret != 0 result.stdout.fnmatch_lines(["hint: hello world", "hint: from me too"]) + at pytest.mark.skipif("sys.version_info < (2,5)") +def test_argcomplete(testdir): + import os + p = py.path.local.make_numbered_dir(prefix="test_argcomplete-", + keep=None, rootdir=testdir.tmpdir) + script = p._fastjoin('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 + # so we use bash + fp.write('COMP_WORDBREAKS="$COMP_WORDBREAKS" $(which py.test) ' + '8>&1 9>&2') + os.environ['_ARGCOMPLETE'] = "1" + os.environ['_ARGCOMPLETE_IFS'] = "\x0b" + os.environ['COMP_LINE'] = "py.test --fu" + os.environ['COMP_POINT'] = "12" + os.environ['COMP_WORDBREAKS'] = ' \\t\\n"\\\'><=;|&(:' + + result = testdir.run('bash', str(script), '--fu') + print dir(result), result.ret + if result.ret == 255: + # argcomplete not found + assert True + else: + result.stdout.fnmatch_lines(["--funcargs", "--fulltrace"]) diff -r 1530d0e3abf3e60a37cfc91aac47a5545a11628c -r 8c92bc4c65aa426cf9e795dbf4a2654cef71b46c tox.ini --- a/tox.ini +++ b/tox.ini @@ -43,7 +43,7 @@ deps=twisted pexpect commands= - py.test -rsxf testing/test_unittest.py \ + py.test -rsxf \ --junitxml={envlogdir}/junit-{envname}.xml {posargs:testing/test_unittest.py} [testenv:doctest] changedir=. https://bitbucket.org/hpk42/pytest/commits/f44d44a4142b/ Changeset: f44d44a4142b Branch: argcomplete User: Anthon van der Neut Date: 2013-07-30 12:33:38 Summary: minor adjustment, added test for positional argument completion Affected #: 2 files diff -r 8c92bc4c65aa426cf9e795dbf4a2654cef71b46c -r f44d44a4142b72a260007e413e6419549b3c1dc1 _pytest/_argcomplete.py --- a/_pytest/_argcomplete.py +++ b/_pytest/_argcomplete.py @@ -55,7 +55,6 @@ if sys.version_info[:2] < (2, 6): sys.exit(1) try: - import argcomplete import argcomplete.completers except ImportError: sys.exit(-1) diff -r 8c92bc4c65aa426cf9e795dbf4a2654cef71b46c -r f44d44a4142b72a260007e413e6419549b3c1dc1 testing/test_parseopt.py --- a/testing/test_parseopt.py +++ b/testing/test_parseopt.py @@ -175,10 +175,10 @@ @pytest.mark.skipif("sys.version_info < (2,5)") def test_argcomplete(testdir): + if not py.path.local.sysfind('bash'): + pytest.skip("bash not available") import os - p = py.path.local.make_numbered_dir(prefix="test_argcomplete-", - keep=None, rootdir=testdir.tmpdir) - script = p._fastjoin('test_argcomplete') + script = os.path.join(os.getcwd(), '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 @@ -187,14 +187,22 @@ '8>&1 9>&2') os.environ['_ARGCOMPLETE'] = "1" os.environ['_ARGCOMPLETE_IFS'] = "\x0b" - os.environ['COMP_LINE'] = "py.test --fu" - os.environ['COMP_POINT'] = "12" os.environ['COMP_WORDBREAKS'] = ' \\t\\n"\\\'><=;|&(:' - result = testdir.run('bash', str(script), '--fu') + arg = '--fu' + os.environ['COMP_LINE'] = "py.test " + arg + os.environ['COMP_POINT'] = str(len(os.environ['COMP_LINE'])) + result = testdir.run('bash', str(script), arg) print dir(result), result.ret if result.ret == 255: # argcomplete not found - assert True + pytest.skip("argcomplete not available") else: result.stdout.fnmatch_lines(["--funcargs", "--fulltrace"]) + + os.mkdir('test_argcomplete.d') + arg = 'test_argc' + os.environ['COMP_LINE'] = "py.test " + arg + os.environ['COMP_POINT'] = str(len(os.environ['COMP_LINE'])) + result = testdir.run('bash', str(script), arg) + result.stdout.fnmatch_lines(["test_argcomplete", "test_argcomplete.d/"]) https://bitbucket.org/hpk42/pytest/commits/73015defd88f/ Changeset: 73015defd88f User: hpk42 Date: 2013-07-31 07:51:07 Summary: Merged in anthon_van_der_neut/pytest/argcomplete (pull request #50) Fixes for argcomplete Affected #: 5 files diff -r 1530d0e3abf3e60a37cfc91aac47a5545a11628c -r 73015defd88f6a45ba44fd6e109b03bc8b3e82e9 _pytest/_argcomplete.py --- /dev/null +++ b/_pytest/_argcomplete.py @@ -0,0 +1,67 @@ + +"""allow bash-completion for argparse with argcomplete if installed +needs argcomplete>=0.5.6 for python 3.2/3.3 (older versions fail +to find the magic string, so _ARGCOMPLETE env. var is never set, and +this does not need special code. + +argcomplete does not support python 2.5 (although the changes for that +are minor). + +Function try_argcomplete(parser) should be called directly before +the call to ArgumentParser.parse_args(). + +The filescompleter is what you normally would use on the positional +arguments specification, in order to get "dirname/" after "dirn" +instead of the default "dirname ": + + optparser.add_argument(Config._file_or_dir, nargs='*' + ).completer=filescompleter + +Other, application specific, completers should go in the file +doing the add_argument calls as they need to be specified as .completer +attributes as well. (If argcomplete is not installed, the function the +attribute points to will not be used). + +--- +To include this support in another application that has setup.py generated +scripts: +- add the line: + # PYTHON_ARGCOMPLETE_OK + near the top of the main python entry point +- include in the file calling parse_args(): + from _argcomplete import try_argcomplete, filescompleter + , call try_argcomplete just before parse_args(), and optionally add + filescompleter to the positional arguments' add_argument() +If things do not work right away: +- switch on argcomplete debugging with (also helpful when doing custom + completers): + export _ARC_DEBUG=1 +- run: + python-argcomplete-check-easy-install-script $(which appname) + echo $? + will echo 0 if the magic line has been found, 1 if not +- sometimes it helps to find early on errors using: + _ARGCOMPLETE=1 _ARC_DEBUG=1 appname + which should throw a KeyError: 'COMPLINE' (which is properly set by the + global argcomplete script). + +""" + +import sys +import os + +if os.environ.get('_ARGCOMPLETE'): + # argcomplete 0.5.6 is not compatible with python 2.5.6: print/with/format + if sys.version_info[:2] < (2, 6): + sys.exit(1) + try: + import argcomplete.completers + except ImportError: + sys.exit(-1) + filescompleter = argcomplete.completers.FilesCompleter() + + def try_argcomplete(parser): + argcomplete.autocomplete(parser) +else: + def try_argcomplete(parser): pass + filescompleter = None diff -r 1530d0e3abf3e60a37cfc91aac47a5545a11628c -r 73015defd88f6a45ba44fd6e109b03bc8b3e82e9 _pytest/config.py --- a/_pytest/config.py +++ b/_pytest/config.py @@ -4,6 +4,7 @@ import sys, os from _pytest.core import PluginManager import pytest +from _argcomplete import try_argcomplete, filescompleter # enable after some grace period for plugin writers TYPE_WARN = False @@ -91,7 +92,9 @@ n = option.names() a = option.attrs() arggroup.add_argument(*n, **a) - optparser.add_argument(Config._file_or_dir, nargs='*') + # bash like autocompletion for dirs (appending '/') + optparser.add_argument(Config._file_or_dir, nargs='*' + ).completer=filescompleter try_argcomplete(self.optparser) return self.optparser.parse_args([str(x) for x in args]) @@ -115,13 +118,6 @@ self._inidict[name] = (help, type, default) self._ininames.append(name) -def try_argcomplete(parser): - try: - import argcomplete - except ImportError: - pass - else: - argcomplete.autocomplete(parser) class ArgumentError(Exception): """ @@ -304,6 +300,7 @@ self._parser = parser py.std.argparse.ArgumentParser.__init__(self, usage=parser._usage, add_help=False) + def format_epilog(self, formatter): hints = self._parser.hints if hints: @@ -312,6 +309,18 @@ return s return "" + def parse_args(self, args=None, namespace=None): + """allow splitting of positional arguments""" + args, argv = self.parse_known_args(args, namespace) + if argv: + for arg in argv: + 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) + return args + + class Conftest(object): """ the single place for accessing values and interacting towards conftest modules from py.test objects. diff -r 1530d0e3abf3e60a37cfc91aac47a5545a11628c -r 73015defd88f6a45ba44fd6e109b03bc8b3e82e9 doc/en/bash-completion.txt --- /dev/null +++ b/doc/en/bash-completion.txt @@ -0,0 +1,28 @@ + +.. _bash_completion: + +Setting up bash completion +========================== + +When using bash as your shell, ``py.test`` can use argcomplete +(https://argcomplete.readthedocs.org/) for auto-completion. +For this ``argcomplete`` needs to be installed **and** enabled. + +Install argcomplete using:: + + sudo pip install 'argcomplete>=0.5.7' + +For global activation of all argcomplete enabled python applications run:: + + sudo activate-global-python-argcomplete + +For permanent (but not global) ``py.test`` activation, use:: + + register-python-argcomplete py.test >> ~/.bashrc + +For one-time activation of argcomplete for ``py.test`` only, use:: + + eval "$(register-python-argcomplete py.test)" + + + diff -r 1530d0e3abf3e60a37cfc91aac47a5545a11628c -r 73015defd88f6a45ba44fd6e109b03bc8b3e82e9 testing/test_parseopt.py --- a/testing/test_parseopt.py +++ b/testing/test_parseopt.py @@ -130,6 +130,21 @@ args = parser.parse(['--ultimate-answer', '42']) assert args.ultimate_answer == 42 + def test_parse_split_positional_arguments(self): + parser = parseopt.Parser() + 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'] + args = parser.parse(['-R', '-S', '4', '2', '-R']) + assert getattr(args, parseopt.Config._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 args.R == True + assert args.S == False + def test_parse_defaultgetter(self): def defaultget(option): if not hasattr(option, 'type'): @@ -158,3 +173,36 @@ #assert result.ret != 0 result.stdout.fnmatch_lines(["hint: hello world", "hint: from me too"]) + at pytest.mark.skipif("sys.version_info < (2,5)") +def test_argcomplete(testdir): + if not py.path.local.sysfind('bash'): + pytest.skip("bash not available") + import os + script = os.path.join(os.getcwd(), '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 + # so we use bash + fp.write('COMP_WORDBREAKS="$COMP_WORDBREAKS" $(which py.test) ' + '8>&1 9>&2') + os.environ['_ARGCOMPLETE'] = "1" + os.environ['_ARGCOMPLETE_IFS'] = "\x0b" + os.environ['COMP_WORDBREAKS'] = ' \\t\\n"\\\'><=;|&(:' + + arg = '--fu' + os.environ['COMP_LINE'] = "py.test " + arg + os.environ['COMP_POINT'] = str(len(os.environ['COMP_LINE'])) + result = testdir.run('bash', str(script), arg) + print dir(result), result.ret + if result.ret == 255: + # argcomplete not found + pytest.skip("argcomplete not available") + else: + result.stdout.fnmatch_lines(["--funcargs", "--fulltrace"]) + + os.mkdir('test_argcomplete.d') + arg = 'test_argc' + os.environ['COMP_LINE'] = "py.test " + arg + os.environ['COMP_POINT'] = str(len(os.environ['COMP_LINE'])) + result = testdir.run('bash', str(script), arg) + result.stdout.fnmatch_lines(["test_argcomplete", "test_argcomplete.d/"]) diff -r 1530d0e3abf3e60a37cfc91aac47a5545a11628c -r 73015defd88f6a45ba44fd6e109b03bc8b3e82e9 tox.ini --- a/tox.ini +++ b/tox.ini @@ -43,7 +43,7 @@ deps=twisted pexpect commands= - py.test -rsxf testing/test_unittest.py \ + py.test -rsxf \ --junitxml={envlogdir}/junit-{envname}.xml {posargs:testing/test_unittest.py} [testenv:doctest] changedir=. 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 Wed Jul 31 07:56:10 2013 From: notifications at travis-ci.org (Travis CI) Date: Wed, 31 Jul 2013 05:56:10 +0000 Subject: [Pytest-commit] [Still Failing] hpk42/pytest#25 (master - 95f25c1) Message-ID: <51f8a6fa95e70_23cc4229132@4beb259d-5ef4-49e8-ba4c-a8e21c5e90b0.mail> Build Update for hpk42/pytest ------------------------------------- Build: #25 Status: Still Failing Duration: 3 minutes and 33 seconds Commit: 95f25c1 (master) Author: holger krekel Message: Merged in anthon_van_der_neut/pytest/argcomplete (pull request #50) Fixes for argcomplete View the changeset: https://github.com/hpk42/pytest/compare/aec5ccc1961f...95f25c1bc1c3 View the full build log and details: https://travis-ci.org/hpk42/pytest/builds/9679016 -- 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: