diff options
-rw-r--r-- | CHANGELOG | 7 | ||||
-rw-r--r-- | CONTRIBUTORS | 2 | ||||
-rw-r--r-- | doc/config.txt | 12 | ||||
-rw-r--r-- | doc/example/basic.txt | 16 | ||||
-rw-r--r-- | tests/test_config.py | 12 | ||||
-rw-r--r-- | tests/test_quickstart.py | 23 | ||||
-rw-r--r-- | tox/_cmdline.py | 40 | ||||
-rw-r--r-- | tox/_config.py | 14 | ||||
-rw-r--r-- | tox/_quickstart.py | 2 | ||||
-rw-r--r-- | tox/_venv.py | 17 |
10 files changed, 124 insertions, 21 deletions
@@ -10,6 +10,13 @@ - refine determination if we run from Jenkins, thanks Borge Lanes. +- echo output to stdout when ``--report-json`` is used + +- fix issue11: add a ``skip_install`` per-testenv setting which + prevents the installation of a package. Thanks Julian Krause. + +- fix issue124: ignore command exit codes; when a command has a "-" prefix, + tox will ignore the exit code of that command 1.8.1 ----------- diff --git a/CONTRIBUTORS b/CONTRIBUTORS index ef8576d..b3e523e 100644 --- a/CONTRIBUTORS +++ b/CONTRIBUTORS @@ -24,8 +24,10 @@ Matt Good Mattieu Agopian Asmund Grammeltwedt Ionel Maries Cristian +Julian Krause Alexandre Conrad Morgan Fainberg Marc Schlaich Clark Boylan Eugene Yunak +Mark Hirota diff --git a/doc/config.txt b/doc/config.txt index 6ffd02b..d743e1b 100644 --- a/doc/config.txt +++ b/doc/config.txt @@ -86,6 +86,8 @@ Complete list of settings that you can put into ``testenv*`` sections: will be appended (and may contain another ``\`` character ...). For eventually performing a call to ``subprocess.Popen(args, ...)`` ``args`` are determined by splitting the whole command by whitespace. + Similar to ``make`` recipe lines, any command with a leading ``-`` + will ignore the exit code. .. confval:: install_command=ARGV @@ -252,6 +254,16 @@ Complete list of settings that you can put into ``testenv*`` sections: **default**: ``False`` +.. confval:: skip_install=BOOL + + .. versionadded:: 1.9 + + Do not install the current package. This can be used when you need the + virtualenv management but do not want to install the current package + into that environment. + + **default**: ``False`` + Substitutions ------------- diff --git a/doc/example/basic.txt b/doc/example/basic.txt index 49baeee..916990e 100644 --- a/doc/example/basic.txt +++ b/doc/example/basic.txt @@ -244,3 +244,19 @@ using the ``--tox-args`` or ``-a`` command-line options. For example:: python setup.py test -a "-epy27" is equivalent to running ``tox -epy27``. + +Ignoring a command exit code +---------------------------- + +In some cases, you may want to ignore a command exit code. For example:: + + [testenv:py27] + commands = coverage erase + {envbindir}/python setup.py develop + coverage run -p setup.py test + coverage combine + - coverage html + {envbindir}/flake8 loads + +By using the ``-`` prefix, similar to a ``make`` recipe line, you can ignore +the exit code for that command. diff --git a/tests/test_config.py b/tests/test_config.py index 229f035..f957771 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -960,6 +960,18 @@ class TestConfigTestEnv: assert configs["py27"].setenv["X"] == "1" assert "X" not in configs["py26"].setenv + @pytest.mark.issue191 + def test_factor_use_not_checked(self, newconfig): + inisource=""" + [tox] + envlist = py27-{a,b} + + [testenv] + deps = b: test + """ + configs = newconfig([], inisource).envconfigs + assert set(configs.keys()) == set(['py27-a', 'py27-b']) + @pytest.mark.issue198 def test_factors_groups_touch(self, newconfig): inisource=""" diff --git a/tests/test_quickstart.py b/tests/test_quickstart.py index df8a98f..5aaacc2 100644 --- a/tests/test_quickstart.py +++ b/tests/test_quickstart.py @@ -37,6 +37,7 @@ class TestToxQuickstartMain(object): 'Y', # py32 'Y', # py33 'Y', # py34 + 'Y', # py35 'Y', # pypy 'N', # jython 'py.test', # command to run tests @@ -54,7 +55,7 @@ class TestToxQuickstartMain(object): # and then run "tox" from this directory. [tox] -envlist = py26, py27, py32, py33, py34, pypy +envlist = py26, py27, py32, py33, py34, py35, pypy [testenv] commands = py.test @@ -77,6 +78,7 @@ deps = 'Y', # py32 'Y', # py33 'Y', # py34 + 'Y', # py35 'Y', # pypy 'N', # jython 'nosetests', # command to run tests @@ -94,7 +96,7 @@ deps = # and then run "tox" from this directory. [tox] -envlist = py26, py27, py32, py33, py34, pypy +envlist = py26, py27, py32, py33, py34, py35, pypy [testenv] commands = nosetests @@ -117,6 +119,7 @@ deps = 'Y', # py32 'Y', # py33 'Y', # py34 + 'Y', # py35 'Y', # pypy 'N', # jython 'trial', # command to run tests @@ -134,7 +137,7 @@ deps = # and then run "tox" from this directory. [tox] -envlist = py26, py27, py32, py33, py34, pypy +envlist = py26, py27, py32, py33, py34, py35, pypy [testenv] commands = trial @@ -157,6 +160,7 @@ deps = 'Y', # py32 'Y', # py33 'Y', # py34 + 'Y', # py35 'Y', # pypy 'N', # jython 'py.test', # command to run tests @@ -173,7 +177,7 @@ deps = # and then run "tox" from this directory. [tox] -envlist = py26, py27, py32, py33, py34, pypy +envlist = py26, py27, py32, py33, py34, py35, pypy [testenv] commands = py.test @@ -272,7 +276,7 @@ deps = # and then run "tox" from this directory. [tox] -envlist = py26, py27, py32, py33, py34, pypy, jython +envlist = py26, py27, py32, py33, py34, py35, pypy, jython [testenv] commands = py.test @@ -295,6 +299,7 @@ deps = '', # py32 '', # py33 '', # py34 + '', # py35 '', # pypy '', # jython '', # command to run tests @@ -312,7 +317,7 @@ deps = # and then run "tox" from this directory. [tox] -envlist = py26, py27, py32, py33, py34, pypy, jython +envlist = py26, py27, py32, py33, py34, py35, pypy, jython [testenv] commands = {envpython} setup.py test @@ -339,6 +344,7 @@ deps = '', # py32 '', # py33 '', # py34 + '', # py35 '', # pypy '', # jython '', # command to run tests @@ -357,7 +363,7 @@ deps = # and then run "tox" from this directory. [tox] -envlist = py26, py27, py32, py33, py34, pypy, jython +envlist = py26, py27, py32, py33, py34, py35, pypy, jython [testenv] commands = {envpython} setup.py test @@ -459,6 +465,7 @@ deps = 'py32': True, 'py33': True, 'py34': True, + 'py35': True, 'pypy': True, 'commands': 'nosetests -v', 'deps': 'nose', @@ -470,7 +477,7 @@ deps = # and then run "tox" from this directory. [tox] -envlist = py27, py32, py33, py34, pypy +envlist = py27, py32, py33, py34, py35, pypy [testenv] commands = nosetests -v diff --git a/tox/_cmdline.py b/tox/_cmdline.py index 0df2f17..9362d86 100644 --- a/tox/_cmdline.py +++ b/tox/_cmdline.py @@ -11,6 +11,7 @@ import py import os import sys import subprocess +import time from tox._verlib import NormalizedVersion, IrrationalVersionError from tox._venv import VirtualEnv from tox._config import parseconfig @@ -78,8 +79,8 @@ class Action(object): f.flush() return f - def popen(self, args, cwd=None, env=None, redirect=True, returnout=False): - f = outpath = None + def popen(self, args, cwd=None, env=None, redirect=True, returnout=False, ignore_ret=False): + stdout = outpath = None resultjson = self.session.config.option.resultjson if resultjson or redirect: f = self._initlogpath(self.id) @@ -87,14 +88,18 @@ class Action(object): 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 @@ class Action(object): 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() @@ -115,7 +141,7 @@ class Action(object): ret = popen.wait() finally: self._popenlist.remove(popen) - if ret: + if ret and not ignore_ret: invoked = " ".join(map(str, popen.args)) if outpath: self.report.error("invocation failed (exit code %d), logfile: %s" % @@ -448,7 +474,7 @@ class Session: if self.setupenv(venv): if venv.envconfig.develop: self.developpkg(venv, self.config.setupdir) - elif self.config.skipsdist: + elif self.config.skipsdist or venv.envconfig.skip_install: self.finishvenv(venv) else: self.installpkg(venv, sdist_path) diff --git a/tox/_config.py b/tox/_config.py index 82154e3..3d26686 100644 --- a/tox/_config.py +++ b/tox/_config.py @@ -18,7 +18,7 @@ iswin32 = sys.platform == "win32" default_factors = {'jython': 'jython', 'pypy': 'pypy', 'pypy3': 'pypy3', 'py': sys.executable} -for version in '24,25,26,27,30,31,32,33,34'.split(','): +for version in '24,25,26,27,30,31,32,33,34,35'.split(','): default_factors['py' + version] = 'python%s.%s' % tuple(version) def parseconfig(args=None, pkg=None): @@ -287,10 +287,18 @@ class parseini: config.envlist, all_envs = self._getenvdata(reader, toxsection) - # configure testenvs + # factors used in config or predefined known_factors = self._list_section_factors("testenv") known_factors.update(default_factors) known_factors.add("python") + + # factors stated in config envlist + stated_envlist = reader.getdefault(toxsection, "envlist", replace=False) + if stated_envlist: + for env in _split_env(stated_envlist): + known_factors.update(env.split('-')) + + # configure testenvs for name in all_envs: section = testenvprefix + name factors = set(name.split('-')) @@ -395,6 +403,8 @@ class parseini: vc.pip_pre = config.option.pre or reader.getbool( section, "pip_pre", False) + vc.skip_install = reader.getbool(section, "skip_install", False) + return vc def _getenvdata(self, reader, toxsection): diff --git a/tox/_quickstart.py b/tox/_quickstart.py index 098eb61..e7c4f03 100644 --- a/tox/_quickstart.py +++ b/tox/_quickstart.py @@ -56,7 +56,7 @@ except NameError: term_input = input -all_envs = ['py26', 'py27', 'py32', 'py33', 'py34', 'pypy', 'jython'] +all_envs = ['py26', 'py27', 'py32', 'py33', 'py34', 'py35', 'pypy', 'jython'] PROMPT_PREFIX = '> ' diff --git a/tox/_venv.py b/tox/_venv.py index 937a881..6bcb88a 100644 --- a/tox/_venv.py +++ b/tox/_venv.py @@ -345,8 +345,19 @@ class VirtualEnv(object): message = "commands[%s] | %s" % (i, ' '.join( [str(x) for x in argv])) action.setactivity("runtests", message) + # check to see if we need to ignore the return code + # if so, we need to alter the command line arguments + if argv[0].startswith("-"): + ignore_ret = True + if argv[0] == "-": + del argv[0] + else: + argv[0] = argv[0].lstrip("-") + else: + ignore_ret = False + try: - self._pcall(argv, cwd=cwd, action=action, redirect=redirect) + self._pcall(argv, cwd=cwd, action=action, redirect=redirect, ignore_ret=ignore_ret) except tox.exception.InvocationError: val = sys.exc_info()[1] self.session.report.error(str(val)) @@ -357,7 +368,7 @@ class VirtualEnv(object): raise def _pcall(self, args, venv=True, cwd=None, extraenv={}, - action=None, redirect=True): + action=None, redirect=True, ignore_ret=False): for name in ("VIRTUALENV_PYTHON", "PYTHONDONTWRITEBYTECODE"): try: del os.environ[name] @@ -369,7 +380,7 @@ class VirtualEnv(object): try: args[0] = self.getcommandpath(args[0], venv, cwd) env = self._getenv(extraenv) - return action.popen(args, cwd=cwd, env=env, redirect=redirect) + return action.popen(args, cwd=cwd, env=env, redirect=redirect, ignore_ret=ignore_ret) finally: os.environ['PATH'] = old |