5 new commits in tox: https://bitbucket.org/hpk42/tox/commits/f6f6e21a6172/ Changeset: f6f6e21a6172 Branch: echo-captured-output User: fschulze Date: 2015-01-29 12:59:26+00:00 Summary: Add ``--result-tee`` option to echo output captured for the json result to stdout. Affected #: 2 files
diff -r df35850bb8d3d8a482f4415a6cab86dad23c951a -r f6f6e21a6172a3ad3dd264746123f0660576dd69 tox/_cmdline.py --- a/tox/_cmdline.py +++ b/tox/_cmdline.py @@ -11,6 +11,7 @@ import os import sys import subprocess +import time from tox._verlib import NormalizedVersion, IrrationalVersionError from tox._venv import VirtualEnv from tox._config import parseconfig @@ -80,20 +81,24 @@ def popen(self, args, cwd=None, env=None, redirect=True, returnout=False): logged_command = "%s$ %s" %(cwd, " ".join(map(str, args))) - f = outpath = None + stdout = outpath = None resultjson = self.session.config.option.resultjson + resulttee = self.session.config.option.resulttee if resultjson or redirect: f = self._initlogpath(self.id) f.write("actionid=%s\nmsg=%s\ncmdargs=%r\nenv=%s\n" %( self.id, self.msg, args, env)) f.flush() self.popen_outpath = outpath = py.path.local(f.name) + stdout = f elif returnout: - f = subprocess.PIPE + stdout = subprocess.PIPE + if resultjson and resulttee: + stdout = subprocess.PIPE if cwd is None: # XXX cwd = self.session.config.cwd cwd = py.path.local() - popen = self._popen(args, cwd, env=env, stdout=f, stderr=STDOUT) + popen = self._popen(args, cwd, env=env, stdout=stdout, stderr=STDOUT) popen.outpath = outpath popen.args = [str(x) for x in args] popen.cwd = cwd @@ -102,7 +107,23 @@ try: self.report.logpopen(popen, env=env) try: - out, err = popen.communicate() + if resultjson and resulttee: + assert popen.stderr is None # prevent deadlock + out = None + last_time = time.time() + while 1: + data = popen.stdout.read(1) + if data: + sys.stdout.write(data) + if '\n' in data or (time.time() - last_time) > 5: + sys.stdout.flush() + last_time = time.time() + f.write(data) + elif popen.poll() is not None: + popen.stdout.close() + break + else: + out, err = popen.communicate() except KeyboardInterrupt: self.report.keyboard_interrupt() popen.wait() diff -r df35850bb8d3d8a482f4415a6cab86dad23c951a -r f6f6e21a6172a3ad3dd264746123f0660576dd69 tox/_config.py --- a/tox/_config.py +++ b/tox/_config.py @@ -116,6 +116,9 @@ "all commands and results involved. This will turn off " "pass-through output from running test commands which is " "instead captured into the json result file.") + parser.add_argument("--result-tee", action="store_true", + dest="resulttee", + help="echo output of --result-json to stdout while it is captured.") parser.add_argument("args", nargs="*", help="additional arguments available to command positional substitution") return parser https://bitbucket.org/hpk42/tox/commits/8b053b6f34d4/ Changeset: 8b053b6f34d4 Branch: echo-captured-output User: fschulze Date: 2015-01-29 13:25:16+00:00 Summary: Merged default Affected #: 36 files diff -r f6f6e21a6172a3ad3dd264746123f0660576dd69 -r 8b053b6f34d40c9d31ae9ac3f95736131b5648cf .hgignore --- a/.hgignore +++ b/.hgignore @@ -14,3 +14,4 @@ doc/_build/ tox.egg-info .tox +.cache diff -r f6f6e21a6172a3ad3dd264746123f0660576dd69 -r 8b053b6f34d40c9d31ae9ac3f95736131b5648cf .hgtags --- a/.hgtags +++ b/.hgtags @@ -15,3 +15,8 @@ 8fcc1bed3e918b625d85864cc3f4623578852e7e 1.5.0 33e5e5dff406e699893a65ecd5044d3eee35b69b 1.6.0 2e580ee6feea934cc2e683635abded27c0de0be9 1.6.1 +5b4e536b8d3810c791b742b2a8723c53b8d3d720 1.7.0 +c7155565c89d1bb3684c881ca774d921188223a0 1.7.1 +e319e464470a5885505ab3e1da1a3a7abe5f86e2 1.7.2 +b7374e501bde055c5c2b572e6512d22e10f60088 1.8.0 +2aa9b587d12ae4b325cb4d5a9a801a222ffc328c 1.8.1 diff -r f6f6e21a6172a3ad3dd264746123f0660576dd69 -r 8b053b6f34d40c9d31ae9ac3f95736131b5648cf CHANGELOG --- a/CHANGELOG +++ b/CHANGELOG @@ -1,9 +1,139 @@ -1.6.2.dev +1.9.0.dev +----------- + +- fix issue193: Remove ``--pre`` from the default ``install_command``; by + default tox will now only install final releases from PyPI for unpinned + dependencies. Use ``pip_pre = true`` in a testenv or the ``--pre`` + command-line option to restore the previous behavior. + +- fix issue199: fill resultlog structure ahead of virtualenv creation + +- refine determination if we run from Jenkins, thanks Borge Lanes. + + +1.8.1 +----------- + +- fix issue190: allow setenv to be empty. + +- allow escaping curly braces with "\". Thanks Marc Abramowitz for the PR. + +- allow "." names in environment names such that "py27-django1.7" is a + valid environment name. Thanks Alex Gaynor and Alex Schepanovski. + +- report subprocess exit code when execution fails. Thanks Marius + Gedminas. + +1.8.0 +----------- + +- new multi-dimensional configuration support. Many thanks to + Alexander Schepanovski for the complete PR with docs. + And to Mike Bayer and others for testing and feedback. + +- fix issue148: remove "__PYVENV_LAUNCHER__" from os.environ when starting + subprocesses. Thanks Steven Myint. + +- fix issue152: set VIRTUAL_ENV when running test commands, + thanks Florian Ludwig. + +- better report if we can't get version_info from an interpreter + executable. Thanks Floris Bruynooghe. + + +1.7.2 +----------- + +- fix issue150: parse {posargs} more like we used to do it pre 1.7.0. + The 1.7.0 behaviour broke a lot of OpenStack projects. + See PR85 and the issue discussions for (far) more details, hopefully + resulting in a more refined behaviour in the 1.8 series. + And thanks to Clark Boylan for the PR. + +- fix issue59: add a config variable ``skip-missing-interpreters`` as well as + command line option ``--skip-missing-interpreters`` which won't fail the + build if Python interpreters listed in tox.ini are missing. Thanks + Alexandre Conrad for PR104. + +- fix issue164: better traceback info in case of failing test commands. + Thanks Marc Abramowitz for PR92. + +- support optional env variable substitution, thanks Morgan Fainberg + for PR86. + +- limit python hashseed to 1024 on Windows to prevent possible + memory errors. Thanks March Schlaich for the PR90. + +1.7.1 --------- +- fix issue162: don't list python 2.5 as compatibiliy/supported + +- fix issue158 and fix issue155: windows/virtualenv properly works now: + call virtualenv through "python -m virtualenv" with the same + interpreter which invoked tox. Thanks Chris Withers, Ionel Maries Cristian. + +1.7.0 +--------- + +- don't lookup "pip-script" anymore but rather just "pip" on windows + as this is a pip implementation detail and changed with pip-1.5. + It might mean that tox-1.7 is not able to install a different pip + version into a virtualenv anymore. + +- drop Python2.5 compatibility because it became too hard due + to the setuptools-2.0 dropping support. tox now has no + support for creating python2.5 based environments anymore + and all internal special-handling has been removed. + +- merged PR81: new option --force-dep which allows to + override tox.ini specified dependencies in setuptools-style. + For example "--force-dep 'django<1.6'" will make sure + that any environment using "django" as a dependency will + get the latest 1.5 release. Thanks Bruno Oliveria for + the complete PR. + +- merged PR125: tox now sets "PYTHONHASHSEED" to a random value + and offers a "--hashseed" option to repeat a test run with a specific seed. + You can also use --hashsheed=noset to instruct tox to leave the value + alone. Thanks Chris Jerdonek for all the work behind this. + +- fix issue132: removing zip_safe setting (so it defaults to false) + to allow installation of tox + via easy_install/eggs. Thanks Jenisys. + +- fix issue126: depend on virtualenv>=1.11.2 so that we can rely + (hopefully) on a pip version which supports --pre. (tox by default + uses to --pre). also merged in PR84 so that we now call "virtualenv" + directly instead of looking up interpreters. Thanks Ionel Maries Cristian. + This also fixes issue140. + +- fix issue130: you can now set install_command=easy_install {opts} {packages} + and expect it to work for repeated tox runs (previously it only worked + when always recreating). Thanks jenisys for precise reporting. + +- fix issue129: tox now uses Popen(..., universal_newlines=True) to force + creation of unicode stdout/stderr streams. fixes a problem on specific + platform configs when creating virtualenvs with Python3.3. Thanks + Jorgen Schäfer or investigation and solution sketch. + - fix issue128: enable full substitution in install_command, thanks for the PR to Ronald Evers +- rework and simplify "commands" parsing and in particular posargs + substitutions to avoid various win32/posix related quoting issues. + +- make sure that the --installpkg option trumps any usedevelop settings + in tox.ini or + +- introduce --no-network to tox's own test suite to skip tests + requiring networks + +- introduce --sitepackages to force sitepackages=True in all + environments. + +- fix issue105 -- don't depend on an existing HOME directory from tox tests. + 1.6.1 ----- diff -r f6f6e21a6172a3ad3dd264746123f0660576dd69 -r 8b053b6f34d40c9d31ae9ac3f95736131b5648cf CONTRIBUTORS --- a/CONTRIBUTORS +++ b/CONTRIBUTORS @@ -3,6 +3,7 @@ Krisztian Fekete Marc Abramowitz +Aleaxner Schepanovski Sridhar Ratnakumar Barry Warsaw Chris Rose @@ -11,3 +12,20 @@ Lukasz Balcerzak Philip Thiem Monty Taylor +Bruno Oliveira +Ionel Maries Cristian +Anatoly techntonik +Matt Jeffery +Chris Jerdonek +Ronald Evers +Carl Meyer +Anthon van der Neuth +Matt Good +Mattieu Agopian +Asmund Grammeltwedt +Ionel Maries Cristian +Alexandre Conrad +Morgan Fainberg +Marc Schlaich +Clark Boylan +Eugene Yunak diff -r f6f6e21a6172a3ad3dd264746123f0660576dd69 -r 8b053b6f34d40c9d31ae9ac3f95736131b5648cf README.rst --- a/README.rst +++ b/README.rst @@ -21,5 +21,5 @@ have fun, -holger krekel, May 2013 +holger krekel, 2014 diff -r f6f6e21a6172a3ad3dd264746123f0660576dd69 -r 8b053b6f34d40c9d31ae9ac3f95736131b5648cf doc/announce/release-1.8.txt --- /dev/null +++ b/doc/announce/release-1.8.txt @@ -0,0 +1,54 @@ +tox 1.8: Generative/combinatorial environments specs +============================================================================= + +I am happy to announce tox 1.8 which implements parametrized environments. + +See https://tox.testrun.org/latest/config.html#generating-environments-conditional-settings +for examples and the new backward compatible syntax in your tox.ini file. + +Many thanks to Alexander Schepanovski for implementing and refining +it based on the specifcation draft. + +More documentation about tox in general: + + http://tox.testrun.org/ + +Installation: + + pip install -U tox + +code hosting and issue tracking on bitbucket: + + https://bitbucket.org/hpk42/tox + +What is tox? +---------------- + +tox standardizes and automates tedious test activities driven from a +simple ``tox.ini`` file, including: + +* creation and management of different virtualenv environments + with different Python interpreters +* packaging and installing your package into each of them +* running your test tool of choice, be it nose, py.test or unittest2 or other tools such as "sphinx" doc checks +* testing dev packages against each other without needing to upload to PyPI + +best, +Holger Krekel, merlinux GmbH + + +Changes 1.8 (compared to 1.7.2) +--------------------------------------- + +- new multi-dimensional configuration support. Many thanks to + Alexander Schepanovski for the complete PR with docs. + And to Mike Bayer and others for testing and feedback. + +- fix issue148: remove "__PYVENV_LAUNCHER__" from os.environ when starting + subprocesses. Thanks Steven Myint. + +- fix issue152: set VIRTUAL_ENV when running test commands, + thanks Florian Ludwig. + +- better report if we can't get version_info from an interpreter + executable. Thanks Floris Bruynooghe. diff -r f6f6e21a6172a3ad3dd264746123f0660576dd69 -r 8b053b6f34d40c9d31ae9ac3f95736131b5648cf doc/conf.py --- a/doc/conf.py +++ b/doc/conf.py @@ -48,7 +48,8 @@ # built documents. # # The short X.Y version. -release = version = "1.6.1" +release = "1.8" +version = "1.8.1" # The full version, including alpha/beta/rc tags. # The language for content autogenerated by Sphinx. Refer to documentation diff -r f6f6e21a6172a3ad3dd264746123f0660576dd69 -r 8b053b6f34d40c9d31ae9ac3f95736131b5648cf doc/config-v2.txt --- a/doc/config-v2.txt +++ b/doc/config-v2.txt @@ -195,7 +195,7 @@ tox comes with predefined settings for certain variants, namely: * ``{easy,pip}`` use easy_install or pip respectively -* ``{py24,py25,py26,py27,py31,py32,py33,pypy19]`` use the respective +* ``{py24,py25,py26,py27,py31,py32,py33,py34,pypy19]`` use the respective pythonNN or PyPy interpreter * ``{win32,linux,darwin}`` defines the according ``platform``. diff -r f6f6e21a6172a3ad3dd264746123f0660576dd69 -r 8b053b6f34d40c9d31ae9ac3f95736131b5648cf doc/config.txt --- a/doc/config.txt +++ b/doc/config.txt @@ -28,20 +28,31 @@ (by checking for existence of the ``JENKINS_URL`` environment variable) and will first lookup global tox settings in this section:: - [tox:hudson] - ... # override [tox] settings for the hudson context - # note: for hudson distshare defaults to ``{toxworkdir}/distshare``. + [tox:jenkins] + ... # override [tox] settings for the jenkins context + # note: for jenkins distshare defaults to ``{toxworkdir}/distshare``. +.. confval:: skip_missing_interpreters=BOOL -envlist setting -+++++++++++++++ + .. versionadded:: 1.7.2 -Determining the environment list that ``tox`` is to operate on -happens in this order: + Setting this to ``True`` is equivalent of passing the + ``--skip-missing-interpreters`` command line option, and will force ``tox`` to + return success even if some of the specified environments were missing. This is + useful for some CI systems or running on a developer box, where you might only + have a subset of all your supported interpreters installed but don't want to + mark the build as failed because of it. As expected, the command line switch + always overrides this setting if passed on the invokation. + **Default:** ``False`` -* command line option ``-eENVLIST`` -* environment variable ``TOXENV`` -* ``tox.ini`` file's ``envlist`` +.. confval:: envlist=CSV + + Determining the environment list that ``tox`` is to operate on + happens in this order (if any is found, no further lookups are made): + + * command line option ``-eENVLIST`` + * environment variable ``TOXENV`` + * ``tox.ini`` file's ``envlist`` Virtualenv test environment settings @@ -80,7 +91,7 @@ .. versionadded:: 1.6 - **WARNING**: This setting is **EXPERIMENTAL** so use with care + **WARNING**: This setting is **EXPERIMENTAL** so use with care and be ready to adapt your tox.ini's with post-1.6 tox releases. the ``install_command`` setting is used for installing packages into @@ -94,22 +105,29 @@ if you have configured it. **default**:: - - pip install --pre {opts} {packages} - - **default on environments using python2.5**:: - pip install {opts} {packages}`` + pip install {opts} {packages} - this will use pip<1.4 which has no ``--pre`` option. Note also - that for python2.5 support you may need to install ssl and/or - use ``setenv = PIP_INSECURE=1`` in a py25 based testenv. +.. confval:: pip_pre=True|False(default) + + .. versionadded:: 1.9 + + If ``True``, adds ``--pre`` to the ``opts`` passed to + :confval:`install_command`. If :confval:`install_command` uses pip, this + will cause it to install the latest available pre-release of any + dependencies without a specified version. If ``False`` (the default), pip + will only install final releases of unpinned dependencies. + + Passing the ``--pre`` command-line option to tox will force this to + ``True`` for all testenvs. + + Don't set this option if your :confval:`install_command` does not use pip. .. confval:: whitelist_externals=MULTI-LINE-LIST each line specifies a command name (in glob-style pattern format) which can be used in the ``commands`` section without triggering - a "not installed in virtualenv" warning. Example: if you use the + a "not installed in virtualenv" warning. Example: if you use the unix ``make`` for running tests you can list ``whitelist_externals=make`` or ``whitelist_externals=/usr/bin/make`` if you want more precision. If you don't want tox to issue a warning in any case, just use @@ -136,7 +154,7 @@ using the ``{toxinidir}`` as the current working directory. .. confval:: setenv=MULTI-LINE-LIST - + .. versionadded:: 0.9 each line contains a NAME=VALUE environment variable setting which @@ -164,17 +182,17 @@ **DEPRECATED** -- as of August 2013 you should use setuptools which has merged most of distribute_ 's changes. Just use - the default, Luke! In future versions of tox this option might - be ignored and setuptools always chosen. + the default, Luke! In future versions of tox this option might + be ignored and setuptools always chosen. **default:** False. .. confval:: sitepackages=True|False Set to ``True`` if you want to create virtual environments that also - have access to globally installed packages. + have access to globally installed packages. - **default:** False, meaning that virtualenvs will be + **default:** False, meaning that virtualenvs will be created without inheriting the global site packages. .. confval:: args_are_paths=BOOL @@ -182,7 +200,7 @@ treat positional arguments passed to ``tox`` as file system paths and - if they exist on the filesystem - rewrite them according to the ``changedir``. - **default**: True (due to the exists-on-filesystem check it's + **default**: True (due to the exists-on-filesystem check it's usually safe to try rewriting). .. confval:: envtmpdir=path @@ -202,7 +220,7 @@ .. versionadded:: 0.9 Multi-line ``name = URL`` definitions of python package servers. - Depedencies can specify using a specified index server through the + Dependencies can specify using a specified index server through the ``:indexservername:depname`` pattern. The ``default`` indexserver definition determines where unscoped dependencies and the sdist install installs from. Example:: @@ -241,6 +259,10 @@ Any ``key=value`` setting in an ini-file can make use of value substitution through the ``{...}`` string-substitution pattern. +You can escape curly braces with the ``\`` character if you need them, for example:: + + commands = echo "\{posargs\}" = {posargs} + Globally available substitutions ++++++++++++++++++++++++++++++++ @@ -292,6 +314,26 @@ and raise an Error if the environment variable does not exist. + +environment variable substitutions with default values +++++++++++++++++++++++++++++++++++++++++++++++++++++++ + +If you specify a substitution string like this:: + + {env:KEY:DEFAULTVALUE} + +then the value will be retrieved as ``os.environ['KEY']`` +and replace with DEFAULTVALUE if the environment variable does not +exist. + +If you specify a substitution string like this:: + + {env:KEY:} + +then the value will be retrieved as ``os.environ['KEY']`` +and replace with and empty string if the environment variable does not +exist. + .. _`command positional substitution`: .. _`positional substitution`: @@ -347,7 +389,7 @@ pytest mock pytest-xdist - + [testenv:dulwich] deps = dulwich @@ -359,6 +401,152 @@ {[base]deps} +Generating environments, conditional settings +--------------------------------------------- + +.. versionadded:: 1.8 + +Suppose you want to test your package against python2.6, python2.7 and against +several versions of a dependency, say Django 1.5 and Django 1.6. You can +accomplish that by writing down 2*2 = 4 ``[testenv:*]`` sections and then +listing all of them in ``envlist``. + +However, a better approach looks like this:: + + [tox] + envlist = {py26,py27}-django{15,16} + + [testenv] + basepython = + py26: python2.6 + py27: python2.7 + deps = + pytest + django15: Django>=1.5,<1.6 + django16: Django>=1.6,<1.7 + py26: unittest2 + commands = py.test + +This uses two new facilities of tox-1.8: + +- generative envlist declarations where each envname + consists of environment parts or "factors" + +- "factor" specific settings + +Let's go through this step by step. + + +Generative envlist ++++++++++++++++++++++++ + +:: + + envlist = {py26,py27}-django{15,16} + +This is bash-style syntax and will create ``2*2=4`` environment names +like this:: + + py26-django15 + py26-django16 + py27-django15 + py27-django16 + +You can still list environments explicitly along with generated ones:: + + envlist = {py26,py27}-django{15,16}, docs, flake + +.. note:: + + To help with understanding how the variants will produce section values, + you can ask tox to show their expansion with a new option:: + + $ tox -l + py26-django15 + py26-django16 + py27-django15 + py27-django16 + docs + flake + + +Factors and factor-conditional settings +++++++++++++++++++++++++++++++++++++++++ + +Parts of an environment name delimited by hyphens are called factors and can +be used to set values conditionally:: + + basepython = + py26: python2.6 + py27: python2.7 + +This conditional setting will lead to either ``python2.6`` or +``python2.7`` used as base python, e.g. ``python2.6`` is selected if current +environment contains ``py26`` factor. + +In list settings such as ``deps`` or ``commands`` you can freely intermix +optional lines with unconditional ones:: + + deps = + pytest + django15: Django>=1.5,<1.6 + django16: Django>=1.6,<1.7 + py26: unittest2 + +Reading it line by line: + +- ``pytest`` will be included unconditionally, +- ``Django>=1.5,<1.6`` will be included for environments containing ``django15`` factor, +- ``Django>=1.6,<1.7`` similarly depends on ``django16`` factor, +- ``unittest`` will be loaded for Python 2.6 environments. + +.. note:: + + Tox provides good defaults for basepython setting, so the above + ini-file can be further reduced by omitting the ``basepython`` + setting. + + +Complex factor conditions ++++++++++++++++++++++++++ + +Sometimes you need to specify same line for several factors or create a special +case for a combination of factors. Here is how you do it:: + + [tox] + envlist = py{26,27,33}-django{15,16}-{sqlite,mysql} + + [testenv] + deps = + py33-mysql: PyMySQL ; use if both py33 and mysql are in an env name + py26,py27: urllib3 ; use if any of py26 or py27 are in an env name + py{26,27}-sqlite: mock ; mocking sqlite in python 2.x + +Take a look at first ``deps`` line. It shows how you can special case something +for a combination of factors, you just join combining factors with a hyphen. +This particular line states that ``PyMySQL`` will be loaded for python 3.3, +mysql environments, e.g. ``py33-django15-mysql`` and ``py33-django16-mysql``. + +The second line shows how you use same line for several factors - by listing +them delimited by commas. It's possible to list not only simple factors, but +also their combinations like ``py26-sqlite,py27-sqlite``. + +Finally, factor expressions are expanded the same way as envlist, so last +example could be rewritten as ``py{26,27}-sqlite``. + +.. note:: + + Factors don't do substring matching against env name, instead every + hyphenated expression is split by ``-`` and if ALL the factors in an + expression are also factors of an env then that condition is considered + hold. + + For example, environment ``py26-mysql``: + + - could be matched with expressions ``py26``, ``py26-mysql``, + ``mysql-py26``, + - but not with ``py2`` or ``py26-sql``. + Other Rules and notes ===================== diff -r f6f6e21a6172a3ad3dd264746123f0660576dd69 -r 8b053b6f34d40c9d31ae9ac3f95736131b5648cf doc/example/basic.txt --- a/doc/example/basic.txt +++ b/doc/example/basic.txt @@ -1,4 +1,3 @@ - Basic usage ============================================= @@ -13,6 +12,7 @@ [tox] envlist = py26,py27 [testenv] + deps=pytest # or 'nose' or ... commands=py.test # or 'nosetests' or ... To sdist-package, install and test your project, you can @@ -38,8 +38,10 @@ py31 py32 py33 + py34 jython pypy + pypy3 However, you can also create your own test environment names, see some of the examples in :doc:`examples <../examples>`. @@ -74,7 +76,7 @@ deps = -rrequirements.txt -All installation commands are executed using ``{toxinidir}}`` +All installation commands are executed using ``{toxinidir}`` (the directory where ``tox.ini`` resides) as the current working directory. Therefore, the underlying ``pip`` installation will assume ``requirements.txt`` to exist at ``{toxinidir}/requirements.txt``. @@ -175,6 +177,31 @@ from the ``subdir`` below the directory where your ``tox.ini`` file resides. +special handling of PYTHONHASHSEED +------------------------------------------- + +.. versionadded:: 1.6.2 + +By default, Tox sets PYTHONHASHSEED_ for test commands to a random integer +generated when ``tox`` is invoked. This mimics Python's hash randomization +enabled by default starting `in Python 3.3`_. To aid in reproducing test +failures, Tox displays the value of ``PYTHONHASHSEED`` in the test output. + +You can tell Tox to use an explicit hash seed value via the ``--hashseed`` +command-line option to ``tox``. You can also override the hash seed value +per test environment in ``tox.ini`` as follows:: + + [testenv] + setenv = + PYTHONHASHSEED = 100 + +If you wish to disable this feature, you can pass the command line option +``--hashseed=noset`` when ``tox`` is invoked. You can also disable it from the +``tox.ini`` by setting ``PYTHONHASHSEED = 0`` as described above. + +.. _`in Python 3.3`: http://docs.python.org/3/whatsnew/3.3.html#builtin-functions-and-types +.. _PYTHONHASHSEED: http://docs.python.org/using/cmdline.html#envvar-PYTHONHASHSEED + Integration with setuptools/distribute test commands ---------------------------------------------------- @@ -186,6 +213,10 @@ import sys class Tox(TestCommand): + user_options = [('tox-args=', 'a', "Arguments to pass to tox")] + def initialize_options(self): + TestCommand.initialize_options(self) + self.tox_args = None def finalize_options(self): TestCommand.finalize_options(self) self.test_args = [] @@ -193,7 +224,8 @@ def run_tests(self): #import here, cause outside the eggs aren't loaded import tox - errno = tox.cmdline(self.test_args) + import shlex + errno = tox.cmdline(args=shlex.split(self.tox_args)) sys.exit(errno) setup( @@ -206,5 +238,9 @@ python setup.py test -this will install tox and then run tox. +this will install tox and then run tox. You can pass arguments to ``tox`` +using the ``--tox-args`` or ``-a`` command-line options. For example:: + python setup.py test -a "-epy27" + +is equivalent to running ``tox -epy27``. diff -r f6f6e21a6172a3ad3dd264746123f0660576dd69 -r 8b053b6f34d40c9d31ae9ac3f95736131b5648cf doc/example/devenv.txt --- a/doc/example/devenv.txt +++ b/doc/example/devenv.txt @@ -1,61 +1,78 @@ - +======================= 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. +Tox can be used for just preparing different virtual environments required by a +project. +This feature can be used by deployment tools when preparing deployed project +environments. It can also be used for setting up normalized project development +environments and thus help reduce the risk of different team members using +mismatched development environments. -Configuration -------------- +Here are some examples illustrating how to set up a project's development +environment using tox. For illustration purposes, let us call the development +environment ``devenv``. -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 that the package should be installed -with ``setup.py develop``:: + +Example 1: Basic scenario +========================= + +Step 1 - Configure the development environment +---------------------------------------------- + +First, we prepare the tox configuration for our development environment by +defining a ``[testenv:devenv]`` section in the project's ``tox.ini`` +configuration file:: [testenv:devenv] envdir = devenv basepython = python2.7 usedevelop = True + +In it we state: + +- what directory to locate the environment in, +- what Python executable to use in the environment, +- that our project should be installed into the environment using ``setup.py + develop``, as opposed to building and installing its source distribution using + ``setup.py install``. + +Actually, we can configure a lot more, and these are only the required settings. +For example, we can add the following to our configuration, telling tox not to +reuse ``commands`` or ``deps`` settings from the base ``[testenv]`` +configuration:: + 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. +Step 2 - Create the development environment +------------------------------------------- - -Creating development environment --------------------------------- - -Once ``devenv`` section is defined we can instrument tox to create our -environment:: +Once the ``[testenv:devenv]`` configuration section has been defined, we create +the actual development environment by running the following:: tox -e devenv -This will create an environment at path specified by ``envdir`` under -``[testenv:devenv]`` section. +This creates the environment at the path specified by the environment's +``envdir`` configuration value. -Full configuration example --------------------------- +Example 2: A more complex scenario +================================== -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, and use -``setup.py develop`` to work in development mode instead of building and -installing an ``sdist`` package. +Let us say we want our project development environment to: -Here is example configuration for that:: +- be located in the ``devenv`` directory, +- use Python executable ``python2.7``, +- pull packages from ``requirements.txt``, located in the same directory as + ``tox.ini``. + +Here is an example configuration for the described scenario:: [testenv:devenv] envdir = devenv basepython = python2.7 usedevelop = True deps = -rrequirements.txt - diff -r f6f6e21a6172a3ad3dd264746123f0660576dd69 -r 8b053b6f34d40c9d31ae9ac3f95736131b5648cf doc/example/jenkins.txt --- a/doc/example/jenkins.txt +++ b/doc/example/jenkins.txt @@ -32,7 +32,9 @@ The last point requires that your test command creates JunitXML files, for example with ``py.test`` it is done like this: - commands=py.test --junitxml=junit-{envname}.xml +.. code-block:: ini + + commands = py.test --junitxml=junit-{envname}.xml @@ -57,7 +59,7 @@ exec urllib.urlopen(url).read() in d d['cmdline'](['--recreate']) -The downloaded `toxbootstrap.py`_ file downloads all neccessary files to +The downloaded `toxbootstrap.py` file downloads all neccessary files to install ``tox`` in a virtual sub environment. Notes: * uncomment the line containing ``USETOXDEV`` to use the latest @@ -68,7 +70,6 @@ will cause tox to reinstall all virtual environments all the time which is often what one wants in CI server contexts) -.. _`toxbootstrap.py`: https://bitbucket.org/hpk42/tox/raw/default/toxbootstrap.py Integrating "sphinx" documentation checks in a Jenkins job ---------------------------------------------------------------- diff -r f6f6e21a6172a3ad3dd264746123f0660576dd69 -r 8b053b6f34d40c9d31ae9ac3f95736131b5648cf doc/example/pytest.txt --- a/doc/example/pytest.txt +++ b/doc/example/pytest.txt @@ -23,7 +23,7 @@ deps=pytest # PYPI package providing py.test commands= py.test \ - [] # substitute with tox' positional arguments + {posargs} # substitute with tox' positional arguments you can now invoke ``tox`` in the directory where your ``tox.ini`` resides. ``tox`` will sdist-package your project, create two virtualenv environments @@ -49,7 +49,7 @@ commands= py.test \ --basetemp={envtmpdir} \ # py.test tempdir setting - [] # substitute with tox' positional arguments + {posargs} # substitute with tox' positional arguments you can invoke ``tox`` in the directory where your ``tox.ini`` resides. Differently than in the previous example the ``py.test`` command @@ -73,7 +73,7 @@ --basetemp={envtmpdir} \ --confcutdir=.. \ -n 3 \ # use three sub processes - [] + {posargs} .. _`listed as a known issue`: @@ -99,16 +99,13 @@ files are outside the import path, then pass ``--pyargs mypkg`` to pytest. -Installed tests are particularly convenient when combined with -`Distribute's 2to3 support` (``use_2to3``). +With tests that won't be installed, the simplest way to run them +against your installed package is to avoid ``__init__.py`` files in test +directories; pytest will still find and import them by adding their +parent directory to ``sys.path`` but they won't be copied to +other places or be found by Python's import system outside of pytest. -With tests that won't be installed, the simplest way is to avoid -``__init__.py`` files in test directories; pytest will still find them -but they won't be copied to other places or be found by Python's import -system. +.. _`fully qualified name`: http://pytest.org/latest/goodpractises.html#test-package-name -.. _`fully qualified name`: http://pytest.org/latest/goodpractises.html#package-name - -.. _`Distribute's 2to3 support`: http://packages.python.org/distribute/python3.html .. include:: ../links.txt diff -r f6f6e21a6172a3ad3dd264746123f0660576dd69 -r 8b053b6f34d40c9d31ae9ac3f95736131b5648cf doc/index.txt --- a/doc/index.txt +++ b/doc/index.txt @@ -1,8 +1,6 @@ Welcome to the tox automation project =============================================== -.. note:: second training: `professional testing with Python <http://www.python-academy.com/courses/specialtopics/python_course_testing.html>`_ , 25-27th November 2013, Leipzig. - vision: standardize testing in Python --------------------------------------------- @@ -70,9 +68,8 @@ support for configuring the installer command through :confval:`install_command=ARGV`. -* **cross-Python compatible**: Python-2.5 up to Python-3.3, - Jython and pypy_ support. Python-2.5 is supported through - a vendored ``virtualenv-1.9.1`` script. +* **cross-Python compatible**: CPython-2.6, 2.7, 3.2 and higher, + Jython and pypy_. * **cross-platform**: Windows and Unix style environments diff -r f6f6e21a6172a3ad3dd264746123f0660576dd69 -r 8b053b6f34d40c9d31ae9ac3f95736131b5648cf doc/install.txt --- a/doc/install.txt +++ b/doc/install.txt @@ -4,7 +4,7 @@ Install info in a nutshell ---------------------------------- -**Pythons**: CPython 2.4-3.3, Jython-2.5.1, pypy-1.9ff +**Pythons**: CPython 2.6-3.3, Jython-2.5.1, pypy-1.9ff **Operating systems**: Linux, Windows, OSX, Unix diff -r f6f6e21a6172a3ad3dd264746123f0660576dd69 -r 8b053b6f34d40c9d31ae9ac3f95736131b5648cf setup.py --- a/setup.py +++ b/setup.py @@ -18,17 +18,15 @@ def main(): version = sys.version_info[:2] - install_requires = ['virtualenv>=1.9.1', 'py>=1.4.15', ] - if version < (2, 7) or (3, 0) <= version <= (3, 1): + install_requires = ['virtualenv>=1.11.2', 'py>=1.4.17', ] + if version < (2, 7): install_requires += ['argparse'] - if version < (2,6): - install_requires += ["simplejson"] setup( name='tox', description='virtualenv-based automation of test activities', long_description=open("README.rst").read(), url='http://tox.testrun.org/', - version='1.6.2.dev1', + version='1.8.2.dev1', license='http://opensource.org/licenses/MIT', platforms=['unix', 'linux', 'osx', 'cygwin', 'win32'], author='holger krekel', @@ -40,7 +38,6 @@ tests_require=['tox'], cmdclass={"test": Tox}, install_requires=install_requires, - zip_safe=True, classifiers=[ 'Development Status :: 5 - Production/Stable', 'Intended Audience :: Developers', diff -r f6f6e21a6172a3ad3dd264746123f0660576dd69 -r 8b053b6f34d40c9d31ae9ac3f95736131b5648cf tests/conftest.py --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,2 +1,2 @@ -from tox._pytestplugin import * +from tox._pytestplugin import * # noqa diff -r f6f6e21a6172a3ad3dd264746123f0660576dd69 -r 8b053b6f34d40c9d31ae9ac3f95736131b5648cf tests/test_config.py --- a/tests/test_config.py +++ b/tests/test_config.py @@ -1,11 +1,11 @@ -import tox -import pytest -import os, sys -import subprocess +import sys from textwrap import dedent import py -from tox._config import * +import pytest +import tox +import tox._config +from tox._config import * # noqa from tox._config import _split_env @@ -61,6 +61,43 @@ envconfig = config.envconfigs['devenv'] assert envconfig.envdir == config.toxworkdir.join('foobar') + def test_force_dep_version(self, initproj): + """ + Make sure we can override dependencies configured in tox.ini when using the command line option + --force-dep. + """ + initproj("example123-0.5", filedefs={ + 'tox.ini': ''' + [tox] + + [testenv] + deps= + dep1==1.0 + dep2>=2.0 + dep3 + dep4==4.0 + ''' + }) + config = parseconfig( + ['--force-dep=dep1==1.5', '--force-dep=dep2==2.1', + '--force-dep=dep3==3.0']) + assert config.option.force_dep== [ + 'dep1==1.5', 'dep2==2.1', 'dep3==3.0'] + assert [str(x) for x in config.envconfigs['python'].deps] == [ + 'dep1==1.5', 'dep2==2.1', 'dep3==3.0', 'dep4==4.0', + ] + + def test_is_same_dep(self): + """ + Ensure correct parseini._is_same_dep is working with a few samples. + """ + assert parseini._is_same_dep('pkg_hello-world3==1.0', 'pkg_hello-world3') + assert parseini._is_same_dep('pkg_hello-world3==1.0', 'pkg_hello-world3>=2.0') + assert parseini._is_same_dep('pkg_hello-world3==1.0', 'pkg_hello-world3>2.0') + assert parseini._is_same_dep('pkg_hello-world3==1.0', 'pkg_hello-world3<2.0') + assert parseini._is_same_dep('pkg_hello-world3==1.0', 'pkg_hello-world3<=2.0') + assert not parseini._is_same_dep('pkg_hello-world3==1.0', 'otherpkg>=2.0') + class TestConfigPackage: def test_defaults(self, tmpdir, newconfig): config = newconfig([], "") @@ -69,10 +106,10 @@ envconfig = config.envconfigs['python'] assert envconfig.args_are_paths assert not envconfig.recreate + assert not envconfig.pip_pre def test_defaults_distshare(self, tmpdir, newconfig): config = newconfig([], "") - envconfig = config.envconfigs['python'] assert config.distshare == config.homedir.join(".tox", "distshare") def test_defaults_changed_dir(self, tmpdir, newconfig): @@ -111,6 +148,20 @@ assert get_homedir() == "123" +class TestGetcontextname: + def test_blank(self, monkeypatch): + monkeypatch.setattr(os, "environ", {}) + assert getcontextname() is None + + def test_jenkins(self, monkeypatch): + monkeypatch.setattr(os, "environ", {"JENKINS_URL": "xyz"}) + assert getcontextname() == "jenkins" + + def test_hudson_legacy(self, monkeypatch): + monkeypatch.setattr(os, "environ", {"HUDSON_URL": "xyz"}) + assert getcontextname() == "jenkins" + + class TestIniParser: def test_getdefault_single(self, tmpdir, newconfig): config = newconfig(""" @@ -130,6 +181,7 @@ key2={xyz} """) reader = IniReader(config._cfg, fallbacksections=['mydefault']) + assert reader is not None py.test.raises(tox.exception.ConfigError, 'reader.getdefault("mydefault", "key2")') @@ -204,6 +256,22 @@ py.test.raises(tox.exception.ConfigError, 'reader.getdefault("section", "key2")') + def test_getdefault_environment_substitution_with_default(self, monkeypatch, newconfig): + monkeypatch.setenv("KEY1", "hello") + config = newconfig(""" + [section] + key1={env:KEY1:DEFAULT_VALUE} + key2={env:KEY2:DEFAULT_VALUE} + key3={env:KEY3:} + """) + reader = IniReader(config._cfg) + x = reader.getdefault("section", "key1") + assert x == "hello" + x = reader.getdefault("section", "key2") + assert x == "DEFAULT_VALUE" + x = reader.getdefault("section", "key3") + assert x == "" + def test_getdefault_other_section_substitution(self, newconfig): config = newconfig(""" [section] @@ -240,9 +308,19 @@ # "reader.getargvlist('section', 'key1')") assert reader.getargvlist('section', 'key1') == [] x = reader.getargvlist("section", "key2") - assert x == [["cmd1", "with space", "grr"], + assert x == [["cmd1", "with", "space", "grr"], ["cmd2", "grr"]] + def test_argvlist_windows_escaping(self, tmpdir, newconfig): + config = newconfig(""" + [section] + comm = py.test {posargs} + """) + reader = IniReader(config._cfg) + reader.addsubstitutions([r"hello\this"]) + argv = reader.getargv("section", "comm") + assert argv == ["py.test", "hello\\this"] + def test_argvlist_multiline(self, tmpdir, newconfig): config = newconfig(""" [section] @@ -256,7 +334,7 @@ # "reader.getargvlist('section', 'key1')") assert reader.getargvlist('section', 'key1') == [] x = reader.getargvlist("section", "key2") - assert x == [["cmd1", "with space", "grr"]] + assert x == [["cmd1", "with", "space", "grr"]] def test_argvlist_quoting_in_command(self, tmpdir, newconfig): @@ -298,7 +376,36 @@ assert argvlist[0] == ["cmd1"] assert argvlist[1] == ["cmd2", "value2", "other"] - def test_positional_arguments_are_only_replaced_when_standing_alone(self, tmpdir, newconfig): + def test_argvlist_quoted_posargs(self, tmpdir, newconfig): + config = newconfig(""" + [section] + key2= + cmd1 --foo-args='{posargs}' + cmd2 -f '{posargs}' + cmd3 -f {posargs} + """) + reader = IniReader(config._cfg) + reader.addsubstitutions(["foo", "bar"]) + assert reader.getargvlist('section', 'key1') == [] + x = reader.getargvlist("section", "key2") + assert x == [["cmd1", "--foo-args=foo bar"], + ["cmd2", "-f", "foo bar"], + ["cmd3", "-f", "foo", "bar"]] + + def test_argvlist_posargs_with_quotes(self, tmpdir, newconfig): + config = newconfig(""" + [section] + key2= + cmd1 -f {posargs} + """) + reader = IniReader(config._cfg) + reader.addsubstitutions(["foo", "'bar", "baz'"]) + assert reader.getargvlist('section', 'key1') == [] + x = reader.getargvlist("section", "key2") + assert x == [["cmd1", "-f", "foo", "bar baz"]] + + def test_positional_arguments_are_only_replaced_when_standing_alone(self, + tmpdir, newconfig): config = newconfig(""" [section] key= @@ -394,7 +501,25 @@ assert envconfig.sitepackages == False assert envconfig.develop == False assert envconfig.envlogdir == envconfig.envdir.join("log") - assert envconfig.setenv is None + assert list(envconfig.setenv.keys()) == ['PYTHONHASHSEED'] + hashseed = envconfig.setenv['PYTHONHASHSEED'] + assert isinstance(hashseed, str) + # The following line checks that hashseed parses to an integer. + int_hashseed = int(hashseed) + # hashseed is random by default, so we can't assert a specific value. + assert int_hashseed > 0 + + def test_sitepackages_switch(self, tmpdir, newconfig): + config = newconfig(["--sitepackages"], "") + envconfig = config.envconfigs['python'] + assert envconfig.sitepackages == True + + def test_installpkg_tops_develop(self, newconfig): + config = newconfig(["--installpkg=abc"], """ + [testenv] + usedevelop = True + """) + assert not config.envconfigs["python"].develop def test_specific_command_overrides(self, tmpdir, newconfig): config = newconfig(""" @@ -444,7 +569,7 @@ envconfig = config.envconfigs['python'] assert envconfig.envpython == envconfig.envbindir.join("python") - @pytest.mark.parametrize("bp", ["jython", "pypy"]) + @pytest.mark.parametrize("bp", ["jython", "pypy", "pypy3"]) def test_envbindir_jython(self, tmpdir, newconfig, bp): config = newconfig(""" [testenv] @@ -484,36 +609,6 @@ assert envconfig.changedir.basename == "abc" assert envconfig.changedir == config.setupdir.join("abc") - def test_install_command_defaults_py25(self, newconfig, monkeypatch): - from tox.interpreters import Interpreters - def get_info(self, name): - if "x25" in name: - class I: - runnable = True - executable = "python2.5" - version_info = (2,5) - else: - class I: - runnable = False - executable = "python" - return I - monkeypatch.setattr(Interpreters, "get_info", get_info) - config = newconfig(""" - [testenv:x25] - basepython = x25 - [testenv:py25-x] - basepython = x25 - [testenv:py26] - basepython = "python" - """) - for name in ("x25", "py25-x"): - env = config.envconfigs[name] - assert env.install_command == \ - "pip install {opts} {packages}".split() - env = config.envconfigs["py26"] - assert env.install_command == \ - "pip install --pre {opts} {packages}".split() - def test_install_command_setting(self, newconfig): config = newconfig(""" [testenv] @@ -540,6 +635,24 @@ 'some_install', '--arg=%s/foo' % config.toxinidir, 'python', '{opts}', '{packages}'] + def test_pip_pre(self, newconfig): + config = newconfig(""" + [testenv] + pip_pre=true + """) + envconfig = config.envconfigs['python'] + assert envconfig.pip_pre + + def test_pip_pre_cmdline_override(self, newconfig): + config = newconfig( + ['--pre'], + """ + [testenv] + pip_pre=false + """) + envconfig = config.envconfigs['python'] + assert envconfig.pip_pre + def test_downloadcache(self, newconfig, monkeypatch): monkeypatch.delenv("PIP_DOWNLOAD_CACHE", raising=False) config = newconfig(""" @@ -567,14 +680,14 @@ def test_simple(tmpdir, newconfig): config = newconfig(""" - [testenv:py24] - basepython=python2.4 - [testenv:py25] - basepython=python2.5 + [testenv:py26] + basepython=python2.6 + [testenv:py27] + basepython=python2.7 """) assert len(config.envconfigs) == 2 - assert "py24" in config.envconfigs - assert "py25" in config.envconfigs + assert "py26" in config.envconfigs + assert "py27" in config.envconfigs def test_substitution_error(tmpdir, newconfig): py.test.raises(tox.exception.ConfigError, newconfig, """ @@ -626,6 +739,23 @@ assert argv[0] == ["cmd1", "[hello]", "world"] assert argv[1] == ["cmd1", "brave", "new", "world"] + def test_posargs_backslashed_or_quoted(self, tmpdir, newconfig): + inisource = """ + [testenv:py24] + commands = + echo "\{posargs\}" = {posargs} + echo "posargs = " "{posargs}" + """ + conf = newconfig([], inisource).envconfigs['py24'] + argv = conf.commands + assert argv[0] == ['echo', '\\{posargs\\}', '='] + assert argv[1] == ['echo', 'posargs = ', ""] + + conf = newconfig(['dog', 'cat'], inisource).envconfigs['py24'] + argv = conf.commands + assert argv[0] == ['echo', '\\{posargs\\}', '=', 'dog', 'cat'] + assert argv[1] == ['echo', 'posargs = ', 'dog cat'] + def test_rewrite_posargs(self, tmpdir, newconfig): inisource = """ [testenv:py24] @@ -752,6 +882,100 @@ assert conf.changedir.basename == 'testing' assert conf.changedir.dirpath().realpath() == tmpdir.realpath() + def test_factors(self, newconfig): + inisource=""" + [tox] + envlist = a-x,b + + [testenv] + deps= + dep-all + a: dep-a + b: dep-b + x: dep-x + """ + conf = newconfig([], inisource) + configs = conf.envconfigs + assert [dep.name for dep in configs['a-x'].deps] == \ + ["dep-all", "dep-a", "dep-x"] + assert [dep.name for dep in configs['b'].deps] == ["dep-all", "dep-b"] + + def test_factor_ops(self, newconfig): + inisource=""" + [tox] + envlist = {a,b}-{x,y} + + [testenv] + deps= + a,b: dep-a-or-b + a-x: dep-a-and-x + {a,b}-y: dep-ab-and-y + """ + configs = newconfig([], inisource).envconfigs + get_deps = lambda env: [dep.name for dep in configs[env].deps] + assert get_deps("a-x") == ["dep-a-or-b", "dep-a-and-x"] + assert get_deps("a-y") == ["dep-a-or-b", "dep-ab-and-y"] + assert get_deps("b-x") == ["dep-a-or-b"] + assert get_deps("b-y") == ["dep-a-or-b", "dep-ab-and-y"] + + def test_default_factors(self, newconfig): + inisource=""" + [tox] + envlist = py{26,27,33,34}-dep + + [testenv] + deps= + dep: dep + """ + conf = newconfig([], inisource) + configs = conf.envconfigs + for name, config in configs.items(): + assert config.basepython == 'python%s.%s' % (name[2], name[3]) + + @pytest.mark.issue188 + def test_factors_in_boolean(self, newconfig): + inisource=""" + [tox] + envlist = py{27,33} + + [testenv] + recreate = + py27: True + """ + configs = newconfig([], inisource).envconfigs + assert configs["py27"].recreate + assert not configs["py33"].recreate + + @pytest.mark.issue190 + def test_factors_in_setenv(self, newconfig): + inisource=""" + [tox] + envlist = py27,py26 + + [testenv] + setenv = + py27: X = 1 + """ + configs = newconfig([], inisource).envconfigs + assert configs["py27"].setenv["X"] == "1" + assert "X" not in configs["py26"].setenv + + def test_period_in_factor(self, newconfig): + inisource=""" + [tox] + envlist = py27-{django1.6,django1.7} + + [testenv] + deps = + django1.6: Django==1.6 + django1.7: Django==1.7 + """ + configs = newconfig([], inisource).envconfigs + assert sorted(configs) == ["py27-django1.6", "py27-django1.7"] + assert [d.name for d in configs["py27-django1.6"].deps] \ + == ["Django==1.6"] + + class TestGlobalOptions: def test_notest(self, newconfig): config = newconfig([], "") @@ -836,7 +1060,7 @@ assert str(env.basepython) == sys.executable def test_default_environments(self, tmpdir, newconfig, monkeypatch): - envs = "py24,py25,py26,py27,py31,py32,jython,pypy" + envs = "py26,py27,py31,py32,py33,py34,jython,pypy,pypy3" inisource = """ [tox] envlist = %s @@ -848,13 +1072,30 @@ env = config.envconfigs[name] if name == "jython": assert env.basepython == "jython" - elif name == "pypy": - assert env.basepython == "pypy" + elif name.startswith("pypy"): + assert env.basepython == name else: assert name.startswith("py") bp = "python%s.%s" %(name[2], name[3]) assert env.basepython == bp + def test_envlist_expansion(self, newconfig): + inisource = """ + [tox] + envlist = py{26,27},docs + """ + config = newconfig([], inisource) + assert config.envlist == ["py26", "py27", "docs"] + + def test_envlist_cross_product(self, newconfig): + inisource = """ + [tox] + envlist = py{26,27}-dep{1,2} + """ + config = newconfig([], inisource) + assert config.envlist == \ + ["py26-dep1", "py26-dep2", "py27-dep1", "py27-dep2"] + def test_minversion(self, tmpdir, newconfig, monkeypatch): inisource = """ [tox] @@ -863,6 +1104,22 @@ config = newconfig([], inisource) assert config.minversion == "3.0" + def test_skip_missing_interpreters_true(self, tmpdir, newconfig, monkeypatch): + inisource = """ + [tox] + skip_missing_interpreters = True + """ + config = newconfig([], inisource) + assert config.option.skip_missing_interpreters + + def test_skip_missing_interpreters_false(self, tmpdir, newconfig, monkeypatch): + inisource = """ + [tox] + skip_missing_interpreters = False + """ + config = newconfig([], inisource) + assert not config.option.skip_missing_interpreters + def test_defaultenv_commandline(self, tmpdir, newconfig, monkeypatch): config = newconfig(["-epy24"], "") env = config.envconfigs['py24'] @@ -881,6 +1138,123 @@ assert env.basepython == "python2.4" assert env.commands == [['xyz']] +class TestHashseedOption: + + def _get_envconfigs(self, newconfig, args=None, tox_ini=None, + make_hashseed=None): + if args is None: + args = [] + if tox_ini is None: + tox_ini = """ + [testenv] + """ + if make_hashseed is None: + make_hashseed = lambda: '123456789' + original_make_hashseed = tox._config.make_hashseed + tox._config.make_hashseed = make_hashseed + try: + config = newconfig(args, tox_ini) + finally: + tox._config.make_hashseed = original_make_hashseed + return config.envconfigs + + def _get_envconfig(self, newconfig, args=None, tox_ini=None): + envconfigs = self._get_envconfigs(newconfig, args=args, + tox_ini=tox_ini) + return envconfigs["python"] + + def _check_hashseed(self, envconfig, expected): + assert envconfig.setenv == {'PYTHONHASHSEED': expected} + + def _check_testenv(self, newconfig, expected, args=None, tox_ini=None): + envconfig = self._get_envconfig(newconfig, args=args, tox_ini=tox_ini) + self._check_hashseed(envconfig, expected) + + def test_default(self, tmpdir, newconfig): + self._check_testenv(newconfig, '123456789') + + def test_passing_integer(self, tmpdir, newconfig): + args = ['--hashseed', '1'] + self._check_testenv(newconfig, '1', args=args) + + def test_passing_string(self, tmpdir, newconfig): + args = ['--hashseed', 'random'] + self._check_testenv(newconfig, 'random', args=args) + + def test_passing_empty_string(self, tmpdir, newconfig): + args = ['--hashseed', ''] + self._check_testenv(newconfig, '', args=args) + + @pytest.mark.xfail(sys.version_info >= (3,2), + reason="at least Debian python 3.2/3.3 have a bug: " + "http://bugs.python.org/issue11884") + def test_passing_no_argument(self, tmpdir, newconfig): + """Test that passing no arguments to --hashseed is not allowed.""" + args = ['--hashseed'] + try: + self._check_testenv(newconfig, '', args=args) + except SystemExit: + e = sys.exc_info()[1] + assert e.code == 2 + return + assert False # getting here means we failed the test. + + def test_setenv(self, tmpdir, newconfig): + """Check that setenv takes precedence.""" + tox_ini = """ + [testenv] + setenv = + PYTHONHASHSEED = 2 + """ + self._check_testenv(newconfig, '2', tox_ini=tox_ini) + args = ['--hashseed', '1'] + self._check_testenv(newconfig, '2', args=args, tox_ini=tox_ini) + + def test_noset(self, tmpdir, newconfig): + args = ['--hashseed', 'noset'] + envconfig = self._get_envconfig(newconfig, args=args) + assert envconfig.setenv is None + + def test_noset_with_setenv(self, tmpdir, newconfig): + tox_ini = """ + [testenv] + setenv = + PYTHONHASHSEED = 2 + """ + args = ['--hashseed', 'noset'] + self._check_testenv(newconfig, '2', args=args, tox_ini=tox_ini) + + def test_one_random_hashseed(self, tmpdir, newconfig): + """Check that different testenvs use the same random seed.""" + tox_ini = """ + [testenv:hash1] + [testenv:hash2] + """ + next_seed = [1000] + # This function is guaranteed to generate a different value each time. + def make_hashseed(): + next_seed[0] += 1 + return str(next_seed[0]) + # Check that make_hashseed() works. + assert make_hashseed() == '1001' + envconfigs = self._get_envconfigs(newconfig, tox_ini=tox_ini, + make_hashseed=make_hashseed) + self._check_hashseed(envconfigs["hash1"], '1002') + # Check that hash2's value is not '1003', for example. + self._check_hashseed(envconfigs["hash2"], '1002') + + def test_setenv_in_one_testenv(self, tmpdir, newconfig): + """Check using setenv in one of multiple testenvs.""" + tox_ini = """ + [testenv:hash1] + setenv = + PYTHONHASHSEED = 2 + [testenv:hash2] + """ + envconfigs = self._get_envconfigs(newconfig, tox_ini=tox_ini) + self._check_hashseed(envconfigs["hash1"], '2') + self._check_hashseed(envconfigs["hash2"], '123456789') + class TestIndexServer: def test_indexserver(self, tmpdir, newconfig): config = newconfig(""" @@ -1000,6 +1374,29 @@ "*ERROR*tox.ini*not*found*", ]) + def test_showconfig_with_force_dep_version(self, cmd, initproj): + initproj('force_dep_version', filedefs={ + 'tox.ini': ''' + [tox] + + [testenv] + deps= + dep1==2.3 + dep2 + ''', + }) + result = cmd.run("tox", "--showconfig") + assert result.ret == 0 + result.stdout.fnmatch_lines([ + r'*deps=*dep1==2.3, dep2*', + ]) + # override dep1 specific version, and force version for dep2 + result = cmd.run("tox", "--showconfig", "--force-dep=dep1", + "--force-dep=dep2==5.0") + assert result.ret == 0 + result.stdout.fnmatch_lines([ + r'*deps=*dep1, dep2==5.0*', + ]) class TestArgumentParser: @@ -1077,3 +1474,13 @@ p = CommandParser(cmd) parsed = list(p.words()) assert parsed == ['nosetests', ' ', '-v', ' ', '-a', ' ', '!deferred', ' ', '--with-doctest', ' ', '[]'] + + + @pytest.mark.skipif("sys.platform != 'win32'") + def test_commands_with_backslash(self, newconfig): + config = newconfig([r"hello\world"], """ + [testenv:py26] + commands = some {posargs} + """) + envconfig = config.envconfigs["py26"] + assert envconfig.commands[0] == ["some", r"hello\world"] diff -r f6f6e21a6172a3ad3dd264746123f0660576dd69 -r 8b053b6f34d40c9d31ae9ac3f95736131b5648cf tests/test_interpreters.py --- a/tests/test_interpreters.py +++ b/tests/test_interpreters.py @@ -2,7 +2,7 @@ import os import pytest -from tox.interpreters import * +from tox.interpreters import * # noqa @pytest.fixture def interpreters(): This diff is so big that we needed to truncate the remainder. https://bitbucket.org/hpk42/tox/commits/a56d17ed9e34/ Changeset: a56d17ed9e34 Branch: echo-captured-output User: fschulze Date: 2015-02-05 13:21:44+00:00 Summary: Always tee the output to stdout when --report-json is used. Affected #: 2 files diff -r 8b053b6f34d40c9d31ae9ac3f95736131b5648cf -r a56d17ed9e345db64962bd856d521d4f92c5af65 tox/_cmdline.py --- a/tox/_cmdline.py +++ b/tox/_cmdline.py @@ -80,21 +80,20 @@ return f def popen(self, args, cwd=None, env=None, redirect=True, returnout=False): - logged_command = "%s$ %s" %(cwd, " ".join(map(str, args))) stdout = outpath = None resultjson = self.session.config.option.resultjson - resulttee = self.session.config.option.resulttee if resultjson or redirect: f = self._initlogpath(self.id) f.write("actionid=%s\nmsg=%s\ncmdargs=%r\nenv=%s\n" %( self.id, self.msg, args, env)) f.flush() self.popen_outpath = outpath = py.path.local(f.name) - stdout = f + if resultjson: + stdout = subprocess.PIPE + else: + stdout = f elif returnout: stdout = subprocess.PIPE - if resultjson and resulttee: - stdout = subprocess.PIPE if cwd is None: # XXX cwd = self.session.config.cwd cwd = py.path.local() @@ -113,15 +112,20 @@ try: self.report.logpopen(popen, env=env) try: - if resultjson and resulttee: + if resultjson and not redirect: assert popen.stderr is None # prevent deadlock out = None last_time = time.time() while 1: + # we have to read one byte at a time, otherwise there + # might be no output for a long time with slow tests data = popen.stdout.read(1) if data: sys.stdout.write(data) if '\n' in data or (time.time() - last_time) > 5: + # we flush on newlines or after 5 seconds to + # provide quick enough feedback to the user + # when printing a dot per test sys.stdout.flush() last_time = time.time() f.write(data) diff -r 8b053b6f34d40c9d31ae9ac3f95736131b5648cf -r a56d17ed9e345db64962bd856d521d4f92c5af65 tox/_config.py --- a/tox/_config.py +++ b/tox/_config.py @@ -117,9 +117,6 @@ "all commands and results involved. This will turn off " "pass-through output from running test commands which is " "instead captured into the json result file.") - parser.add_argument("--result-tee", action="store_true", - dest="resulttee", - help="echo output of --result-json to stdout while it is captured.") # We choose 1 to 4294967295 because it is the range of PYTHONHASHSEED. parser.add_argument("--hashseed", action="store", metavar="SEED", default=None, https://bitbucket.org/hpk42/tox/commits/f52220770a97/ Changeset: f52220770a97 Branch: echo-captured-output User: fschulze Date: 2015-02-05 13:24:39+00:00 Summary: Add changelog entry. Affected #: 1 file diff -r a56d17ed9e345db64962bd856d521d4f92c5af65 -r f52220770a979458e126ac7fcc5fb2acefe1ec44 CHANGELOG --- a/CHANGELOG +++ b/CHANGELOG @@ -10,6 +10,7 @@ - refine determination if we run from Jenkins, thanks Borge Lanes. +- echo output to stdout when ``--report-json`` is used 1.8.1 ----------- https://bitbucket.org/hpk42/tox/commits/27b8bdcddd6a/ Changeset: 27b8bdcddd6a User: hpk42 Date: 2015-02-09 11:13:56+00:00 Summary: Merged in fschulze/tox/echo-captured-output (pull request #132) Add ``--result-tee`` option to echo output captured for the json result to stdout. Affected #: 3 files diff -r 50f3b98fc65b951ab07e6b5e3fbd5a2fc9c48f2c -r 27b8bdcddd6a197ad600afac03a1e3fb61cc6a5b CHANGELOG --- a/CHANGELOG +++ b/CHANGELOG @@ -10,6 +10,7 @@ - refine determination if we run from Jenkins, thanks Borge Lanes. +- echo output to stdout when ``--report-json`` is used 1.8.1 ----------- diff -r 50f3b98fc65b951ab07e6b5e3fbd5a2fc9c48f2c -r 27b8bdcddd6a197ad600afac03a1e3fb61cc6a5b tox/_cmdline.py --- a/tox/_cmdline.py +++ b/tox/_cmdline.py @@ -11,6 +11,7 @@ import os import sys import subprocess +import time from tox._verlib import NormalizedVersion, IrrationalVersionError from tox._venv import VirtualEnv from tox._config import parseconfig @@ -79,7 +80,7 @@ return f def popen(self, args, cwd=None, env=None, redirect=True, returnout=False): - f = outpath = None + stdout = outpath = None resultjson = self.session.config.option.resultjson if resultjson or redirect: f = self._initlogpath(self.id) @@ -87,14 +88,18 @@ self.id, self.msg, args, env)) f.flush() self.popen_outpath = outpath = py.path.local(f.name) + if resultjson: + stdout = subprocess.PIPE + else: + stdout = f elif returnout: - f = subprocess.PIPE + stdout = subprocess.PIPE if cwd is None: # XXX cwd = self.session.config.cwd cwd = py.path.local() try: popen = self._popen(args, cwd, env=env, - stdout=f, stderr=STDOUT) + stdout=stdout, stderr=STDOUT) except OSError as e: self.report.error("invocation failed (errno %d), args: %s, cwd: %s" % (e.errno, args, cwd)) @@ -107,7 +112,28 @@ try: self.report.logpopen(popen, env=env) try: - out, err = popen.communicate() + if resultjson and not redirect: + assert popen.stderr is None # prevent deadlock + out = None + last_time = time.time() + while 1: + # we have to read one byte at a time, otherwise there + # might be no output for a long time with slow tests + data = popen.stdout.read(1) + if data: + sys.stdout.write(data) + if '\n' in data or (time.time() - last_time) > 5: + # we flush on newlines or after 5 seconds to + # provide quick enough feedback to the user + # when printing a dot per test + sys.stdout.flush() + last_time = time.time() + f.write(data) + elif popen.poll() is not None: + popen.stdout.close() + break + else: + out, err = popen.communicate() except KeyboardInterrupt: self.report.keyboard_interrupt() popen.wait() 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. _______________________________________________ pytest-commit mailing list pytest-commit@python.org https://mail.python.org/mailman/listinfo/pytest-commit