From f914825d783da586e20ad77e1b7347b97a4510ea Mon Sep 17 00:00:00 2001 From: holger krekel Date: Mon, 14 Oct 2013 12:04:28 +0200 Subject: fix parsing/escaping bugs on windows32 --- CHANGELOG | 2 ++ tests/test_config.py | 22 ++++++++++++++++++++++ tox/_config.py | 21 +++++++++++++++++++-- 3 files changed, 43 insertions(+), 2 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 8e4b71c..6661295 100755 --- a/CHANGELOG +++ b/CHANGELOG @@ -4,6 +4,8 @@ - fix issue128: enable full substitution in install_command, thanks for the PR to Ronald Evers +- fix windows parsing/escaping + 1.6.1 ----- diff --git a/tests/test_config.py b/tests/test_config.py index e117ca0..3776788 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -243,6 +243,16 @@ class TestIniParser: 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] @@ -1077,3 +1087,15 @@ class TestCommandParser: p = CommandParser(cmd) parsed = list(p.words()) assert parsed == ['nosetests', ' ', '-v', ' ', '-a', ' ', '!deferred', ' ', '--with-doctest', ' ', '[]'] + +def test_argv_unquote_single_args(): + argv = ["hello", '"hello2"', "'hello3'"] + newargv = unquote_single_args(argv) + assert newargv == ["hello", "hello2", "hello3"] + +def test_argv_roundrobin(): + argv = ["hello", "this\\that"] + assert string2argv(argv2string(argv)) == argv + argv = ["hello world"] + assert string2argv(argv2string(argv)) == argv + diff --git a/tox/_config.py b/tox/_config.py index d692b6f..e707789 100644 --- a/tox/_config.py +++ b/tox/_config.py @@ -14,6 +14,7 @@ import py import tox +iswin32 = sys.platform == "win32" defaultenvs = {'jython': 'jython', 'pypy': 'pypy'} for _name in "py,py24,py25,py26,py27,py30,py31,py32,py33,py34".split(","): @@ -487,7 +488,7 @@ class IniReader: command = self.getdefault( section, name, default=default, replace=replace) - return shlex.split(command.strip()) + return string2argv(command.strip()) def getbool(self, section, name, default=None): s = self.getdefault(section, name, default) @@ -533,7 +534,7 @@ class IniReader: posargs = self._subs.get('_posargs', None) if posargs: - return " ".join(posargs) + return argv2string(posargs) value = value_func() if value: @@ -704,3 +705,19 @@ def getcontextname(): if 'HUDSON_URL' in os.environ: return 'jenkins' return None + + +def unquote_single_args(argv): + newargv = [] + for arg in argv: + if len(arg) >=2 and arg[0] == arg[-1]: + if arg[0] in ("'", '"'): + arg = arg[1:-1] + newargv.append(arg) + return newargv + +def string2argv(cmd): + return unquote_single_args(shlex.split(cmd, posix=False)) + +def argv2string(argv): + return subprocess.list2cmdline(argv) -- cgit v1.2.1 From a563aecda09d01b4ecd278315360148de6a36be9 Mon Sep 17 00:00:00 2001 From: holger krekel Date: Tue, 22 Oct 2013 10:59:09 +0200 Subject: 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. --- CHANGELOG | 5 +++++ tox/_cmdline.py | 4 +++- tox/_pytestplugin.py | 1 + tox/_venv.py | 2 +- 4 files changed, 10 insertions(+), 2 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 6661295..d153797 100755 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,6 +1,11 @@ 1.6.2.dev --------- +- 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 diff --git a/tox/_cmdline.py b/tox/_cmdline.py index 24e47ba..5407b4a 100644 --- a/tox/_cmdline.py +++ b/tox/_cmdline.py @@ -93,7 +93,8 @@ class Action(object): 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=f, stderr=STDOUT) popen.outpath = outpath popen.args = [str(x) for x in args] popen.cwd = cwd @@ -150,6 +151,7 @@ class Action(object): if env is None: env = os.environ.copy() return self.session.popen(args, shell=False, cwd=str(cwd), + universal_newlines=True, stdout=stdout, stderr=stderr, env=env) diff --git a/tox/_pytestplugin.py b/tox/_pytestplugin.py index 6c90c8d..4958617 100644 --- a/tox/_pytestplugin.py +++ b/tox/_pytestplugin.py @@ -130,6 +130,7 @@ def pytest_funcarg__mocksession(request): def make_emptydir(self, path): pass def popen(self, args, cwd, shell=None, + universal_newlines=False, stdout=None, stderr=None, env=None): pm = pcallMock(args, cwd, env, stdout, stderr, shell) self._pcalls.append(pm) diff --git a/tox/_venv.py b/tox/_venv.py index ec7ada2..cb53ec0 100644 --- a/tox/_venv.py +++ b/tox/_venv.py @@ -228,7 +228,7 @@ class VirtualEnv(object): args = [self.envconfig.envpython, str(setup_py), '--name'] output = action.popen(args, cwd=setupdir, redirect=False, returnout=True) - name = output.strip().decode('utf-8') + name = output.strip() 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() -- cgit v1.2.1 From e801379ce7565a8c53f492f8174ecf709979576a Mon Sep 17 00:00:00 2001 From: holger krekel Date: Tue, 22 Oct 2013 11:04:11 +0200 Subject: more windows parsing fixes --- CHANGELOG | 3 +- setup.py | 2 +- tests/test_config.py | 20 ++++----- tox/__init__.py | 2 +- tox/_config.py | 119 ++++++++++++++++----------------------------------- 5 files changed, 52 insertions(+), 94 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index d153797..026f078 100755 --- a/CHANGELOG +++ b/CHANGELOG @@ -9,7 +9,8 @@ - fix issue128: enable full substitution in install_command, thanks for the PR to Ronald Evers -- fix windows parsing/escaping +- rework and simplify "commands" parsing and in particular posargs + substitutions to avoid various win32/posix related quoting issues. 1.6.1 ----- diff --git a/setup.py b/setup.py index a8eb184..6d18cf0 100644 --- a/setup.py +++ b/setup.py @@ -28,7 +28,7 @@ def main(): 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.6.2.dev2', license='http://opensource.org/licenses/MIT', platforms=['unix', 'linux', 'osx', 'cygwin', 'win32'], author='holger krekel', diff --git a/tests/test_config.py b/tests/test_config.py index 3776788..e873c9e 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -1088,14 +1088,14 @@ class TestCommandParser: parsed = list(p.words()) assert parsed == ['nosetests', ' ', '-v', ' ', '-a', ' ', '!deferred', ' ', '--with-doctest', ' ', '[]'] -def test_argv_unquote_single_args(): - argv = ["hello", '"hello2"', "'hello3'"] - newargv = unquote_single_args(argv) - assert newargv == ["hello", "hello2", "hello3"] - -def test_argv_roundrobin(): - argv = ["hello", "this\\that"] - assert string2argv(argv2string(argv)) == argv - argv = ["hello world"] - assert string2argv(argv2string(argv)) == argv + + @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 --git a/tox/__init__.py b/tox/__init__.py index 79b9cd3..b383e94 100644 --- a/tox/__init__.py +++ b/tox/__init__.py @@ -1,5 +1,5 @@ # -__version__ = '1.6.2.dev1' +__version__ = '1.6.2.dev2' class exception: class Error(Exception): diff --git a/tox/_config.py b/tox/_config.py index e707789..58fa7c0 100644 --- a/tox/_config.py +++ b/tox/_config.py @@ -413,7 +413,7 @@ class IniReader: def addsubstitutions(self, _posargs=None, **kw): self._subs.update(kw) if _posargs: - self._subs['_posargs'] = _posargs + self.posargs = _posargs def getpath(self, section, name, defaultpath): toxinidir = self._subs['toxinidir'] @@ -468,27 +468,38 @@ class IniReader: return commandlist def _processcommand(self, command): - posargs = self._subs.get('_posargs', None) - words = list(CommandParser(command).words()) - new_command = '' - for word in words: - if word == '[]': - if posargs: - new_command += ' '.join(posargs) - continue + posargs = getattr(self, "posargs", None) - new_word = self._replace(word, quote=True) - # two passes; we might have substitutions in the result - new_word = self._replace(new_word, quote=True) - new_command += new_word + # special treat posargs which might contain multiple arguments + # in their defaults + newcommand = "" + for word in CommandParser(command).words(): + if word.startswith("{posargs:") and word.endswith("}"): + if posargs: + word = "{posargs}" + else: + word = word[9:-1] + newcommand += word - return shlex.split(new_command.strip()) + # now we can properly parse the command + argv = [] + for arg in shlex.split(newcommand): + if arg in ('[]', "{posargs}"): + if posargs: + argv.extend(posargs) + continue + new_arg = "" + for word in CommandParser(arg).words(): + new_word = self._replace(word) + new_word = self._replace(new_word) + new_arg += new_word + argv.append(new_arg) + return argv def getargv(self, section, name, default=None, replace=True): command = self.getdefault( - section, name, default=default, replace=replace) - - return string2argv(command.strip()) + section, name, default=default, replace=False) + return self._processcommand(command.strip()) def getbool(self, section, name, default=None): s = self.getdefault(section, name, default) @@ -527,22 +538,7 @@ class IniReader: #print "getdefault", section, name, "returned", repr(x) return x - def _replace_posargs(self, match, quote): - return self._do_replace_posargs(lambda: match.group('substitution_value')) - - def _do_replace_posargs(self, value_func): - posargs = self._subs.get('_posargs', None) - - if posargs: - return argv2string(posargs) - - value = value_func() - if value: - return value - - return '' - - def _replace_env(self, match, quote): + def _replace_env(self, match): envkey = match.group('substitution_value') if not envkey: raise tox.exception.ConfigError( @@ -555,7 +551,7 @@ class IniReader: return os.environ[envkey] - def _substitute_from_other_section(self, key, quote): + def _substitute_from_other_section(self, key): if key.startswith("[") and "]" in key: i = key.find("]") section, item = key[1:i], key[i+1:] @@ -566,37 +562,25 @@ class IniReader: x = str(self._cfg[section][item]) self._subststack.append((section, item)) try: - return self._replace(x, quote=quote) + return self._replace(x) finally: self._subststack.pop() raise tox.exception.ConfigError( "substitution key %r not found" % key) - def _replace_substitution(self, match, quote): + def _replace_substitution(self, match): sub_key = match.group('substitution_value') val = self._subs.get(sub_key, None) if val is None: - val = self._substitute_from_other_section(sub_key, quote) + val = self._substitute_from_other_section(sub_key) if py.builtin.callable(val): val = val() - if quote: - return '"%s"' % str(val).replace('"', r'\"') - else: - return str(val) + return str(val) - def _is_bare_posargs(self, groupdict): - return groupdict.get('substitution_value', None) == 'posargs' \ - and not groupdict.get('sub_type') - - def _replace_match(self, match, quote): + def _replace_match(self, match): g = match.groupdict() - # special case: posargs. If there is a 'posargs' substitution value - # and no type, handle it as empty posargs - if self._is_bare_posargs(g): - return self._do_replace_posargs(lambda: '') - # special case: opts and packages. Leave {opts} and # {packages} intact, they are replaced manually in # _venv.VirtualEnv.run_install_command. @@ -605,7 +589,6 @@ class IniReader: return '{%s}' % sub_value handlers = { - 'posargs' : self._replace_posargs, 'env' : self._replace_env, None : self._replace_substitution, } @@ -619,22 +602,11 @@ class IniReader: except KeyError: raise tox.exception.ConfigError("No support for the %s substitution type" % sub_type) - # quoting is done in handlers, as at least posargs handling is special: - # all of its arguments are inserted as separate parameters - return handler(match, quote) + return handler(match) - def _replace_match_quote(self, match): - return self._replace_match(match, quote=True) - def _replace_match_no_quote(self, match): - return self._replace_match(match, quote=False) - - def _replace(self, x, quote=False): + def _replace(self, x): if '{' in x: - if quote: - replace_func = self._replace_match_quote - else: - replace_func = self._replace_match_no_quote - return RE_ITEM_REF.sub(replace_func, x) + return RE_ITEM_REF.sub(self._replace_match, x) return x def _parse_command(self, command): @@ -706,18 +678,3 @@ def getcontextname(): return 'jenkins' return None - -def unquote_single_args(argv): - newargv = [] - for arg in argv: - if len(arg) >=2 and arg[0] == arg[-1]: - if arg[0] in ("'", '"'): - arg = arg[1:-1] - newargv.append(arg) - return newargv - -def string2argv(cmd): - return unquote_single_args(shlex.split(cmd, posix=False)) - -def argv2string(argv): - return subprocess.list2cmdline(argv) -- cgit v1.2.1 From af8484d797b26b6517eb0b35ba34422ef71e421f Mon Sep 17 00:00:00 2001 From: holger krekel Date: Tue, 22 Oct 2013 11:04:11 +0200 Subject: a command line specified --installpkg trumps any develop option --- CHANGELOG | 3 +++ tests/test_config.py | 10 +++++++++- tox/_config.py | 3 ++- 3 files changed, 14 insertions(+), 2 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 026f078..3bff145 100755 --- a/CHANGELOG +++ b/CHANGELOG @@ -12,6 +12,9 @@ - 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 + 1.6.1 ----- diff --git a/tests/test_config.py b/tests/test_config.py index e873c9e..fa5d8e9 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -308,7 +308,8 @@ class TestIniParser: 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_positional_arguments_are_only_replaced_when_standing_alone(self, + tmpdir, newconfig): config = newconfig(""" [section] key= @@ -406,6 +407,13 @@ class TestConfigTestEnv: assert envconfig.envlogdir == envconfig.envdir.join("log") assert envconfig.setenv is None + 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(""" [testenv] diff --git a/tox/_config.py b/tox/_config.py index 58fa7c0..de668ed 100644 --- a/tox/_config.py +++ b/tox/_config.py @@ -272,7 +272,8 @@ class parseini: vc.config = config reader = IniReader(self._cfg, fallbacksections=["testenv"]) reader.addsubstitutions(**subs) - vc.develop = reader.getbool(section, "usedevelop", config.option.develop) + vc.develop = not config.option.installpkg and \ + reader.getbool(section, "usedevelop", 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): -- cgit v1.2.1 From 838d35ad0b707ebe5dc6a3ee7c20be737501a6ed Mon Sep 17 00:00:00 2001 From: holger krekel Date: Thu, 24 Oct 2013 09:38:45 +0200 Subject: fix issue130: you can now set install_command=easy_install {opts} {packages} and expect it to run without the need to recreate. Thanks jenisys for precise reporting. --- CHANGELOG | 4 ++++ tests/test_venv.py | 10 ++++++---- tox/_venv.py | 17 ++++++++++------- 3 files changed, 20 insertions(+), 11 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 3bff145..14889c2 100755 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,6 +1,10 @@ 1.6.2.dev --------- +- fix issue130: you can now set install_command=easy_install {opts} {packages} + and expect it to run without the need to recreate. 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 diff --git a/tests/test_venv.py b/tests/test_venv.py index 67bdb92..801f02a 100644 --- a/tests/test_venv.py +++ b/tests/test_venv.py @@ -520,8 +520,10 @@ def test_installpkg_upgrade(newmocksession, tmpdir): mocksession.installpkg(venv, pkg) l = mocksession._pcalls assert len(l) == 1 - assert '-U' in l[0].args - assert '--no-deps' in l[0].args + index = l[0].args.index(str(pkg)) + assert index >= 0 + assert '-U' in l[0].args[:index] + assert '--no-deps' in l[0].args[:index] def test_run_install_command(newmocksession): mocksession = newmocksession([], "") @@ -529,7 +531,7 @@ def test_run_install_command(newmocksession): venv.just_created = True venv.envconfig.envdir.ensure(dir=1) action = mocksession.newaction(venv, "hello") - venv.run_install_command(args=["whatever"], action=action) + venv.run_install_command(packages=["whatever"], action=action) l = mocksession._pcalls assert len(l) == 1 assert 'pip' in l[0].args[0] @@ -548,7 +550,7 @@ def test_run_custom_install_command(newmocksession): venv.just_created = True venv.envconfig.envdir.ensure(dir=1) action = mocksession.newaction(venv, "hello") - venv.run_install_command(args=["whatever"], action=action) + venv.run_install_command(packages=["whatever"], action=action) l = mocksession._pcalls assert len(l) == 1 assert 'easy_install' in l[0].args[0] diff --git a/tox/_venv.py b/tox/_venv.py index cb53ec0..de7f443 100644 --- a/tox/_venv.py +++ b/tox/_venv.py @@ -280,17 +280,18 @@ class VirtualEnv(object): l.append("--download-cache=%s" % self.envconfig.downloadcache) return l - def run_install_command(self, args, indexserver=None, action=None, + def run_install_command(self, packages, options=(), + indexserver=None, action=None, extraenv=None): argv = self.envconfig.install_command[:] # use pip-script on win32 to avoid the executable locking if argv[0] == "pip" and sys.platform == "win32": argv[0] = "pip-script.py" i = argv.index('{packages}') - argv[i:i+1] = args + argv[i:i+1] = packages if '{opts}' in argv: i = argv.index('{opts}') - argv[i:i+1] = self._installopts(indexserver) + argv[i:i+1] = list(options) for x in ('PIP_RESPECT_VIRTUALENV', 'PIP_REQUIRE_VIRTUALENV'): try: del os.environ[x] @@ -320,7 +321,6 @@ class VirtualEnv(object): l.append(ixserver) assert ixserver.url is None or isinstance(ixserver.url, str) - extraopts = extraopts or [] for ixserver in l: if self.envconfig.config.option.sethome: extraenv = hack_home_env( @@ -329,9 +329,12 @@ class VirtualEnv(object): else: extraenv = {} - args = d[ixserver] + extraopts - self.run_install_command(args, ixserver.url, action, - extraenv=extraenv) + packages = d[ixserver] + options = self._installopts(ixserver.url) + if extraopts: + options.extend(extraopts) + self.run_install_command(packages=packages, options=options, + action=action, extraenv=extraenv) def _getenv(self): env = self.envconfig.setenv -- cgit v1.2.1 From d0dab5fd7caaf90e25d85def4d7e5cf47265bef4 Mon Sep 17 00:00:00 2001 From: holger krekel Date: Thu, 24 Oct 2013 09:43:56 +0200 Subject: fix issue126: depend on virtualenv>=1.10.1 so that we can rely (hopefully) on a pip version which supports --pre. (tox by default uses to --pre). Note that tox also vendors an older virtualenv for supporting python2.5 -- although the latter will be dropped at some point. --- CHANGELOG | 9 +++++++-- setup.py | 2 +- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 14889c2..7ae0301 100755 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,9 +1,14 @@ 1.6.2.dev --------- +- fix issue126: depend on virtualenv>=1.10.1 so that we can rely + (hopefully) on a pip version which supports --pre. (tox by default + uses to --pre). Note that tox also vendors an older virtualenv + for supporting python2.5 -- although the latter will be dropped at some point. + - fix issue130: you can now set install_command=easy_install {opts} {packages} - and expect it to run without the need to recreate. Thanks jenisys for - precise reporting. + 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 diff --git a/setup.py b/setup.py index 6d18cf0..193cbd4 100644 --- a/setup.py +++ b/setup.py @@ -18,7 +18,7 @@ class Tox(TestCommand): def main(): version = sys.version_info[:2] - install_requires = ['virtualenv>=1.9.1', 'py>=1.4.15', ] + install_requires = ['virtualenv>=1.10.1', 'py>=1.4.17', ] if version < (2, 7) or (3, 0) <= version <= (3, 1): install_requires += ['argparse'] if version < (2,6): -- cgit v1.2.1 From 12c229a216b3fb51e990613d88104b607b39f0cb Mon Sep 17 00:00:00 2001 From: holger krekel Date: Tue, 12 Nov 2013 11:27:42 +0200 Subject: fix issue132: removing zip_safe setting (so it defaults to false) to allow installation of tox via easy_install/eggs. Thanks Jenisys. --- CHANGELOG | 4 ++++ setup.py | 1 - 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGELOG b/CHANGELOG index 7ae0301..aeb8b05 100755 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,6 +1,10 @@ 1.6.2.dev --------- +- 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.10.1 so that we can rely (hopefully) on a pip version which supports --pre. (tox by default uses to --pre). Note that tox also vendors an older virtualenv diff --git a/setup.py b/setup.py index 193cbd4..2d0114c 100644 --- a/setup.py +++ b/setup.py @@ -40,7 +40,6 @@ def main(): tests_require=['tox'], cmdclass={"test": Tox}, install_requires=install_requires, - zip_safe=True, classifiers=[ 'Development Status :: 5 - Production/Stable', 'Intended Audience :: Developers', -- cgit v1.2.1 From 85c7a80c4024780f9d091b98d0a65ac08936ac04 Mon Sep 17 00:00:00 2001 From: Chris Jerdonek Date: Thu, 14 Nov 2013 01:51:26 -0800 Subject: Address issue #125 by adding a --hashseed command-line option. This commit also causes Tox to set PYTHONHASHSEED for test commands to a random integer generated when tox is invoked. See the issue here: https://bitbucket.org/hpk42/tox/issue/125 --- doc/example/basic.txt | 21 +++++++++ tests/test_config.py | 123 +++++++++++++++++++++++++++++++++++++++++++++++++- tests/test_venv.py | 15 ++++++ tox/_config.py | 23 +++++++++- tox/_venv.py | 21 +++++---- 5 files changed, 191 insertions(+), 12 deletions(-) diff --git a/doc/example/basic.txt b/doc/example/basic.txt index 55b0848..19a15d4 100644 --- a/doc/example/basic.txt +++ b/doc/example/basic.txt @@ -175,6 +175,27 @@ a PYTHONPATH setting that will lead Python to also import 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:hash] + setenv = + PYTHONHASHSEED = 100 + +.. _`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 ---------------------------------------------------- diff --git a/tests/test_config.py b/tests/test_config.py index fa5d8e9..ad336ef 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -5,6 +5,7 @@ import subprocess from textwrap import dedent import py +import tox._config from tox._config import * from tox._config import _split_env @@ -405,7 +406,13 @@ class TestConfigTestEnv: 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_installpkg_tops_develop(self, newconfig): config = newconfig(["--installpkg=abc"], """ @@ -899,6 +906,120 @@ class TestGlobalOptions: 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) + + 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(""" diff --git a/tests/test_venv.py b/tests/test_venv.py index 801f02a..6f4a06a 100644 --- a/tests/test_venv.py +++ b/tests/test_venv.py @@ -2,6 +2,7 @@ import py import tox import pytest import os, sys +import tox._config from tox._venv import * py25calls = int(sys.version_info[:2] == (2,5)) @@ -231,6 +232,20 @@ def test_install_recreate(newmocksession, tmpdir): venv.update() mocksession.report.expect("verbosity0", "*recreate*") +def test_test_hashseed_is_in_output(newmocksession): + original_make_hashseed = tox._config.make_hashseed + tox._config.make_hashseed = lambda: '123456789' + try: + mocksession = newmocksession([], ''' + [testenv] + ''') + finally: + tox._config.make_hashseed = original_make_hashseed + venv = mocksession.getenv('python') + venv.update() + venv.test() + mocksession.report.expect("verbosity0", "python runtests: PYTHONHASHSEED='123456789'") + def test_test_runtests_action_command_is_in_output(newmocksession): mocksession = newmocksession([], ''' [testenv] diff --git a/tox/_config.py b/tox/_config.py index de668ed..e1cc966 100644 --- a/tox/_config.py +++ b/tox/_config.py @@ -1,6 +1,7 @@ import argparse import distutils.sysconfig import os +import random import sys import re import shlex @@ -117,6 +118,12 @@ def prepare_parse(pkgname): "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.") + # We choose 1 to 4294967295 because it is the range of PYTHONHASHSEED. + parser.add_argument("--hashseed", action="store", + metavar="SEED", default=None, + help="set PYTHONHASHSEED to SEED before running commands. " + "Defaults to a random integer in the range 1 to 4294967295. " + "Passing 'noset' suppresses this behavior.") parser.add_argument("args", nargs="*", help="additional arguments available to command positional substitution") return parser @@ -180,6 +187,9 @@ def get_homedir(): except Exception: return None +def make_hashseed(): + return str(random.randint(1, 4294967295)) + class parseini: def __init__(self, config, inipath): config.toxinipath = inipath @@ -200,6 +210,13 @@ class parseini: else: raise ValueError("invalid context") + if config.option.hashseed is None: + hashseed = make_hashseed() + elif config.option.hashseed == 'noset': + hashseed = None + else: + hashseed = config.option.hashseed + config.hashseed = hashseed reader.addsubstitutions(toxinidir=config.toxinidir, homedir=config.homedir) @@ -306,7 +323,11 @@ class parseini: arg = vc.changedir.bestrelpath(origpath) args.append(arg) reader.addsubstitutions(args) - vc.setenv = reader.getdict(section, 'setenv') + setenv = {} + if config.hashseed is not None: + setenv['PYTHONHASHSEED'] = config.hashseed + setenv.update(reader.getdict(section, 'setenv')) + vc.setenv = setenv if not vc.setenv: vc.setenv = None diff --git a/tox/_venv.py b/tox/_venv.py index de7f443..c0d7dec 100644 --- a/tox/_venv.py +++ b/tox/_venv.py @@ -336,14 +336,13 @@ class VirtualEnv(object): self.run_install_command(packages=packages, options=options, action=action, extraenv=extraenv) - def _getenv(self): - env = self.envconfig.setenv - if env: - env_arg = os.environ.copy() - env_arg.update(env) - else: - env_arg = None - return env_arg + def _getenv(self, extraenv={}): + env = os.environ.copy() + setenv = self.envconfig.setenv + if setenv: + env.update(setenv) + env.update(extraenv) + return env def test(self, redirect=False): action = self.session.newaction(self, "runtests") @@ -351,6 +350,9 @@ class VirtualEnv(object): self.status = 0 self.session.make_emptydir(self.envconfig.envtmpdir) cwd = self.envconfig.changedir + env = self._getenv() + # Display PYTHONHASHSEED to assist with reproducibility. + action.setactivity("runtests", "PYTHONHASHSEED=%r" % env.get('PYTHONHASHSEED')) for i, argv in enumerate(self.envconfig.commands): # have to make strings as _pcall changes argv[0] to a local() # happens if the same environment is invoked twice @@ -380,8 +382,7 @@ class VirtualEnv(object): old = self.patchPATH() try: args[0] = self.getcommandpath(args[0], venv, cwd) - env = self._getenv() or os.environ.copy() - env.update(extraenv) + env = self._getenv(extraenv) return action.popen(args, cwd=cwd, env=env, redirect=redirect) finally: os.environ['PATH'] = old -- cgit v1.2.1 From f5ffa8b9f96540c0b855f5c90187d226d7117912 Mon Sep 17 00:00:00 2001 From: holger krekel Date: Fri, 15 Nov 2013 11:08:42 +0100 Subject: merged PR125: tox now sets "PYTHONHASHSEED" if it is not set and offers a "--hashseed" option to repeat a test run with a specific seed. Thanks Chris Jerdonek for all the work behind this. --- CHANGELOG | 4 ++++ tests/test_config.py | 3 +++ 2 files changed, 7 insertions(+) diff --git a/CHANGELOG b/CHANGELOG index aeb8b05..e7a61ad 100755 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,6 +1,10 @@ 1.6.2.dev --------- +- merged PR125: tox now sets "PYTHONHASHSEED" if it is not set + and offers a "--hashseed" option to repeat a test run with a specific seed. + 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. diff --git a/tests/test_config.py b/tests/test_config.py index ad336ef..f07f191 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -953,6 +953,9 @@ class TestHashseedOption: 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'] -- cgit v1.2.1 From e20548ba593c3726a93ac46d885d96eca4c77fce Mon Sep 17 00:00:00 2001 From: holger krekel Date: Fri, 15 Nov 2013 12:29:01 +0100 Subject: fix changelog entry for hashseed. Thanks Chris Jerdonek. --- CHANGELOG | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index e7a61ad..439d88f 100755 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,9 +1,10 @@ 1.6.2.dev --------- -- merged PR125: tox now sets "PYTHONHASHSEED" if it is not set +- merged PR125: tox now sets "PYTHONHASHSEED" to a random value and offers a "--hashseed" option to repeat a test run with a specific seed. - Thanks Chris Jerdonek for all the work behind this. + You can also use --hashsheed=notset 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 -- cgit v1.2.1 From 8f1b64a35a51a81ee85f65d3a6776297169e135b Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Thu, 21 Nov 2013 20:15:14 -0200 Subject: Added --force-dep-version command-line option --- CONTRIBUTORS | 1 + tests/test_config.py | 59 ++++++++++++++++++++++++++++++++++++++++++++++++++-- tox/_config.py | 31 +++++++++++++++++++++++++++ 3 files changed, 89 insertions(+), 2 deletions(-) diff --git a/CONTRIBUTORS b/CONTRIBUTORS index c65d497..9d8784a 100644 --- a/CONTRIBUTORS +++ b/CONTRIBUTORS @@ -11,3 +11,4 @@ Ronny Pfannschmidt Lukasz Balcerzak Philip Thiem Monty Taylor +Bruno Oliveira diff --git a/tests/test_config.py b/tests/test_config.py index f07f191..517ec29 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -62,6 +62,41 @@ class TestVenvConfig: 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-version. + """ + initproj("example123-0.5", filedefs={ + 'tox.ini': ''' + [tox] + + [testenv] + deps= + dep1==1.0 + dep2>=2.0 + dep3 + dep4==4.0 + ''' + }) + config = parseconfig( + ['--force-dep-version=dep1==1.5', '--force-dep-version=dep2==2.1', '--force-dep-version=dep3==3.0']) + assert config.option.force_dep_version == ['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([], "") @@ -1142,6 +1177,28 @@ class TestCmdInvocation: "*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-version=dep1", "--force-dep-version=dep2==5.0") + assert result.ret == 0 + result.stdout.fnmatch_lines([ + r'*deps=*dep1, dep2==5.0*', + ]) class TestArgumentParser: @@ -1229,5 +1286,3 @@ class TestCommandParser: """) envconfig = config.envconfigs["py26"] assert envconfig.commands[0] == ["some", r"hello\world"] - - diff --git a/tox/_config.py b/tox/_config.py index e1cc966..0348080 100644 --- a/tox/_config.py +++ b/tox/_config.py @@ -8,6 +8,7 @@ import shlex import string import subprocess import textwrap +import pkg_resources from tox.interpreters import Interpreters @@ -124,6 +125,10 @@ def prepare_parse(pkgname): help="set PYTHONHASHSEED to SEED before running commands. " "Defaults to a random integer in the range 1 to 4294967295. " "Passing 'noset' suppresses this behavior.") + parser.add_argument("--force-dep-version", action="append", + metavar="DEP==VER", default=None, + help="Forces a certain version of one of the dependencies " + "when configuring the virtual environment.") parser.add_argument("args", nargs="*", help="additional arguments available to command positional substitution") return parser @@ -343,6 +348,7 @@ class parseini: else: name = depline.strip() ixserver = None + name = self._replace_forced_dep(name, config) vc.deps.append(DepConfig(name, ixserver)) vc.distribute = reader.getbool(section, "distribute", False) vc.sitepackages = reader.getbool(section, "sitepackages", False) @@ -385,6 +391,31 @@ class parseini: envlist.sort() return envlist + def _replace_forced_dep(self, name, config): + """ + Override the given dependency config name taking --force-dep-version + option into account. + + :param name: dep config, for example ["pkg==1.0", "other==2.0"]. + :param config: Config instance + :return: the new dependency that should be used for virtual environments + """ + if not config.option.force_dep_version: + return name + for forced_dep in config.option.force_dep_version: + if self._is_same_dep(forced_dep, name): + return forced_dep + return name + + @classmethod + def _is_same_dep(cls, dep1, dep2): + """ + Returns True if both dependency definitions refer to the same package, even if versions differ. + """ + dep1_name = pkg_resources.Requirement.parse(dep1).project_name + dep2_name = pkg_resources.Requirement.parse(dep2).project_name + return dep1_name == dep2_name + def _split_env(env): """if handed a list, action="append" was used for -e """ envlist = [] -- cgit v1.2.1 From 74e599f5d3360203703c0090450254ba84b6489b Mon Sep 17 00:00:00 2001 From: holger krekel Date: Fri, 22 Nov 2013 07:51:21 +0100 Subject: merged and slightly modified 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. --- CHANGELOG | 11 +++++++++-- tests/test_config.py | 11 +++++++---- tox/_config.py | 14 ++++++++------ 3 files changed, 24 insertions(+), 12 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 439d88f..1736649 100755 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,6 +1,13 @@ 1.6.2.dev --------- +- 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=notset to instruct tox to leave the value @@ -21,8 +28,8 @@ - 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. + 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 diff --git a/tests/test_config.py b/tests/test_config.py index 517ec29..84e4366 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -65,7 +65,7 @@ class TestVenvConfig: 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-version. + --force-dep. """ initproj("example123-0.5", filedefs={ 'tox.ini': ''' @@ -80,8 +80,10 @@ class TestVenvConfig: ''' }) config = parseconfig( - ['--force-dep-version=dep1==1.5', '--force-dep-version=dep2==2.1', '--force-dep-version=dep3==3.0']) - assert config.option.force_dep_version == ['dep1==1.5', 'dep2==2.1', 'dep3==3.0'] + ['--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', ] @@ -1194,7 +1196,8 @@ class TestCmdInvocation: r'*deps=*dep1==2.3, dep2*', ]) # override dep1 specific version, and force version for dep2 - result = cmd.run("tox", "--showconfig", "--force-dep-version=dep1", "--force-dep-version=dep2==5.0") + 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*', diff --git a/tox/_config.py b/tox/_config.py index 0348080..c20e251 100644 --- a/tox/_config.py +++ b/tox/_config.py @@ -125,10 +125,11 @@ def prepare_parse(pkgname): help="set PYTHONHASHSEED to SEED before running commands. " "Defaults to a random integer in the range 1 to 4294967295. " "Passing 'noset' suppresses this behavior.") - parser.add_argument("--force-dep-version", action="append", - metavar="DEP==VER", default=None, + parser.add_argument("--force-dep", action="append", + metavar="REQ", default=None, help="Forces a certain version of one of the dependencies " - "when configuring the virtual environment.") + "when configuring the virtual environment. REQ Examples " + "'pytest<2.4' or 'django>=1.6'.") parser.add_argument("args", nargs="*", help="additional arguments available to command positional substitution") return parser @@ -400,9 +401,9 @@ class parseini: :param config: Config instance :return: the new dependency that should be used for virtual environments """ - if not config.option.force_dep_version: + if not config.option.force_dep: return name - for forced_dep in config.option.force_dep_version: + for forced_dep in config.option.force_dep: if self._is_same_dep(forced_dep, name): return forced_dep return name @@ -410,7 +411,8 @@ class parseini: @classmethod def _is_same_dep(cls, dep1, dep2): """ - Returns True if both dependency definitions refer to the same package, even if versions differ. + Returns True if both dependency definitions refer to the + same package, even if versions differ. """ dep1_name = pkg_resources.Requirement.parse(dep1).project_name dep2_name = pkg_resources.Requirement.parse(dep2).project_name -- cgit v1.2.1 From 1cba578c7bbaa5040557316fa539af4ed839d692 Mon Sep 17 00:00:00 2001 From: holger krekel Date: Wed, 27 Nov 2013 17:16:28 +0100 Subject: clarify we want test dependencies, not app dependencies. --- tox/_quickstart.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tox/_quickstart.py b/tox/_quickstart.py index 4b39d04..b5a2825 100644 --- a/tox/_quickstart.py +++ b/tox/_quickstart.py @@ -56,7 +56,7 @@ except NameError: term_input = input -all_envs = ['py24', 'py25', 'py26', 'py27', 'py30', 'py31', 'py32', 'py33', 'pypy', 'jython'] +all_envs = ['py25', 'py26', 'py27', 'py30', 'py31', 'py32', 'py33', 'pypy', 'jython'] PROMPT_PREFIX = '> ' @@ -191,7 +191,7 @@ What command should be used to test your project -- examples: default_deps = 'twisted' print(''' -What dependencies does your project have?''') +What extra dependencies do your tests have?''') do_prompt(d, 'deps', 'Comma-separated list of dependencies', default_deps) -- cgit v1.2.1 -- cgit v1.2.1 From c19dd7508a66496da7915d1832f76335dd4edba5 Mon Sep 17 00:00:00 2001 From: Matt Jeffery Date: Mon, 9 Dec 2013 12:20:04 +0000 Subject: Correctly determine the package version if the package file name contains a period before the version number. --- tox/_cmdline.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox/_cmdline.py b/tox/_cmdline.py index 5407b4a..a42eb79 100644 --- a/tox/_cmdline.py +++ b/tox/_cmdline.py @@ -557,7 +557,7 @@ class Session: return candidates[0] -_rex_getversion = py.std.re.compile("[\w_\-\+]+-(.*)(\.zip|\.tar.gz)") +_rex_getversion = py.std.re.compile("[\w_\-\+\.]+-(.*)(\.zip|\.tar.gz)") def getversion(basename): m = _rex_getversion.match(basename) if m is None: -- cgit v1.2.1 From cfc835360b33f3cc9eabecd24f5d04b3d1c25a1c Mon Sep 17 00:00:00 2001 From: holger krekel Date: Mon, 9 Dec 2013 14:33:46 +0100 Subject: 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 virtualenv environments anymore. --- CHANGELOG | 4 + tests/test_config.py | 42 +- tests/test_quickstart.py | 38 +- tox.ini | 2 +- tox/_config.py | 11 +- tox/_pytestplugin.py | 12 +- tox/_quickstart.py | 2 +- tox/_venv.py | 20 +- tox/interpreters.py | 2 +- tox/vendor/virtualenv.py | 2581 ---------------------------------------------- 10 files changed, 49 insertions(+), 2665 deletions(-) delete mode 100755 tox/vendor/virtualenv.py diff --git a/CHANGELOG b/CHANGELOG index 1736649..bcd2387 100755 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,6 +1,10 @@ 1.6.2.dev --------- +- 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 virtualenv environments anymore. + - 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 diff --git a/tests/test_config.py b/tests/test_config.py index 84e4366..9cd9d44 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -546,36 +546,6 @@ class TestConfigTestEnv: 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] @@ -629,14 +599,14 @@ class TestConfigTestEnv: 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, """ diff --git a/tests/test_quickstart.py b/tests/test_quickstart.py index 772e905..c376d4b 100644 --- a/tests/test_quickstart.py +++ b/tests/test_quickstart.py @@ -23,10 +23,12 @@ class TestToxQuickstartMain(object): return mock_term_input - def test_quickstart_main_choose_individual_pythons_and_pytest(self, monkeypatch): + def test_quickstart_main_choose_individual_pythons_and_pytest(self, + monkeypatch): monkeypatch.setattr( tox._quickstart, 'term_input', - self.get_mock_term_input(['4', 'Y', 'Y', 'Y', 'Y', 'N', 'N', 'Y', 'Y', 'Y', 'N', 'py.test', 'pytest'])) + self.get_mock_term_input( + ['4', 'Y', 'Y', 'Y', 'Y', 'Y', 'N', 'py.test', 'pytest'])) tox._quickstart.main(argv=['tox-quickstart']) @@ -37,7 +39,7 @@ class TestToxQuickstartMain(object): # and then run "tox" from this directory. [tox] -envlist = py24, py25, py26, py27, py32, py33, pypy +envlist = py26, py27, py32, py33, pypy [testenv] commands = py.test @@ -50,7 +52,8 @@ deps = def test_quickstart_main_choose_individual_pythons_and_nose_adds_deps(self, monkeypatch): monkeypatch.setattr( tox._quickstart, 'term_input', - self.get_mock_term_input(['4', 'Y', 'Y', 'Y', 'Y', 'N', 'N', 'Y', 'Y', 'Y', 'N', 'nosetests', ''])) + self.get_mock_term_input(['4', 'Y', 'Y', 'Y', 'Y', 'Y', 'N', + 'nosetests', ''])) tox._quickstart.main(argv=['tox-quickstart']) @@ -61,7 +64,7 @@ deps = # and then run "tox" from this directory. [tox] -envlist = py24, py25, py26, py27, py32, py33, pypy +envlist = py26, py27, py32, py33, pypy [testenv] commands = nosetests @@ -74,7 +77,8 @@ deps = def test_quickstart_main_choose_individual_pythons_and_trial_adds_deps(self, monkeypatch): monkeypatch.setattr( tox._quickstart, 'term_input', - self.get_mock_term_input(['4', 'Y', 'Y', 'Y', 'Y', 'N', 'N', 'Y', 'Y', 'Y', 'N', 'trial', ''])) + self.get_mock_term_input(['4', 'Y', 'Y', 'Y', 'Y', 'Y', 'N', + 'trial', ''])) tox._quickstart.main(argv=['tox-quickstart']) @@ -85,7 +89,7 @@ deps = # and then run "tox" from this directory. [tox] -envlist = py24, py25, py26, py27, py32, py33, pypy +envlist = py26, py27, py32, py33, pypy [testenv] commands = trial @@ -98,8 +102,8 @@ deps = def test_quickstart_main_choose_individual_pythons_and_pytest_adds_deps(self, monkeypatch): monkeypatch.setattr( tox._quickstart, 'term_input', - self.get_mock_term_input(['4', 'Y', 'Y', 'Y', 'Y', 'N', 'N', 'Y', 'Y', 'Y', 'N', 'py.test', ''])) - + self.get_mock_term_input(['4', 'Y', 'Y', 'Y', 'Y', 'Y', 'N', + 'py.test', ''])) tox._quickstart.main(argv=['tox-quickstart']) expected_tox_ini = """ @@ -109,7 +113,7 @@ deps = # and then run "tox" from this directory. [tox] -envlist = py24, py25, py26, py27, py32, py33, pypy +envlist = py26, py27, py32, py33, pypy [testenv] commands = py.test @@ -181,7 +185,7 @@ deps = # and then run "tox" from this directory. [tox] -envlist = py24, py25, py26, py27, py30, py31, py32, py33, pypy, jython +envlist = py26, py27, py32, py33, pypy, jython [testenv] commands = py.test @@ -194,7 +198,7 @@ deps = def test_quickstart_main_choose_individual_pythons_and_defaults(self, monkeypatch): monkeypatch.setattr( tox._quickstart, 'term_input', - self.get_mock_term_input(['4', '', '', '', '', '', '', '', '', '', '', '', ''])) + self.get_mock_term_input(['4', '', '', '', '', '', '', '', '', '', ''])) tox._quickstart.main(argv=['tox-quickstart']) @@ -205,7 +209,7 @@ deps = # and then run "tox" from this directory. [tox] -envlist = py24, py25, py26, py27, py30, py31, py32, py33, pypy, jython +envlist = py26, py27, py32, py33, pypy, jython [testenv] commands = {envpython} setup.py test @@ -224,7 +228,7 @@ deps = monkeypatch.setattr( tox._quickstart, 'term_input', - self.get_mock_term_input(['4', '', '', '', '', '', '', '', '', '', '', '', '', ''])) + self.get_mock_term_input(['4', '', '', '', '', '', '', '', '', '', '', ''])) tox._quickstart.main(argv=['tox-quickstart']) @@ -235,7 +239,7 @@ deps = # and then run "tox" from this directory. [tox] -envlist = py24, py25, py26, py27, py30, py31, py32, py33, pypy, jython +envlist = py26, py27, py32, py33, pypy, jython [testenv] commands = {envpython} setup.py test @@ -249,8 +253,6 @@ deps = class TestToxQuickstart(object): def test_pytest(self): d = { - 'py24': True, - 'py25': True, 'py26': True, 'py27': True, 'py32': True, @@ -266,7 +268,7 @@ class TestToxQuickstart(object): # and then run "tox" from this directory. [tox] -envlist = py24, py25, py26, py27, py32, py33, pypy +envlist = py26, py27, py32, py33, pypy [testenv] commands = py.test diff --git a/tox.ini b/tox.ini index 40f532d..29525c9 100644 --- a/tox.ini +++ b/tox.ini @@ -1,5 +1,5 @@ [tox] -envlist=py25,py27,py26,py32,py33,pypy +envlist=py27,py33,py26,py32,pypy [testenv:X] commands=echo {posargs} diff --git a/tox/_config.py b/tox/_config.py index c20e251..e7ac443 100644 --- a/tox/_config.py +++ b/tox/_config.py @@ -183,6 +183,9 @@ class VenvConfig: info = self.config.interpreters.get_info(self.basepython) if not info.executable: raise tox.exception.InterpreterNotFound(self.basepython) + if info.version_info < (2,6): + raise tox.exception.UnsupportedInterpreter( + "python2.5 is not supported anymore, sorry") return info.executable testenvprefix = "testenv:" @@ -360,13 +363,7 @@ class parseini: downloadcache = os.environ.get("PIP_DOWNLOAD_CACHE", downloadcache) vc.downloadcache = py.path.local(downloadcache) - # on pip-1.3.1/python 2.5 we can't use "--pre". - pip_default_opts = ["{opts}", "{packages}"] - info = vc._basepython_info - if info.runnable and info.version_info < (2,6): - pass - else: - pip_default_opts.insert(0, "--pre") + pip_default_opts = ["--pre", "{opts}", "{packages}"] vc.install_command = reader.getargv( section, "install_command", diff --git a/tox/_pytestplugin.py b/tox/_pytestplugin.py index 4958617..f6421f4 100644 --- a/tox/_pytestplugin.py +++ b/tox/_pytestplugin.py @@ -19,7 +19,8 @@ def pytest_configure(): def pytest_report_header(): return "tox comes from: %r" % (tox.__file__) -def pytest_funcarg__newconfig(request, tmpdir): +@pytest.fixture +def newconfig(request, tmpdir): def newconfig(args, source=None): if source is None: source = args @@ -34,7 +35,8 @@ def pytest_funcarg__newconfig(request, tmpdir): old.chdir() return newconfig -def pytest_funcarg__cmd(request): +@pytest.fixture +def cmd(request): return Cmd(request) class ReportExpectMock: @@ -113,7 +115,8 @@ class pcallMock: def wait(self): pass -def pytest_funcarg__mocksession(request): +@pytest.fixture +def mocksession(request): from tox._cmdline import Session class MockSession(Session): def __init__(self): @@ -137,7 +140,8 @@ def pytest_funcarg__mocksession(request): return pm return MockSession() -def pytest_funcarg__newmocksession(request): +@pytest.fixture +def newmocksession(request): mocksession = request.getfuncargvalue("mocksession") newconfig = request.getfuncargvalue("newconfig") def newmocksession(args, source): diff --git a/tox/_quickstart.py b/tox/_quickstart.py index b5a2825..f66f1e2 100644 --- a/tox/_quickstart.py +++ b/tox/_quickstart.py @@ -56,7 +56,7 @@ except NameError: term_input = input -all_envs = ['py25', 'py26', 'py27', 'py30', 'py31', 'py32', 'py33', 'pypy', 'jython'] +all_envs = ['py26', 'py27', 'py32', 'py33', 'pypy', 'jython'] PROMPT_PREFIX = '> ' diff --git a/tox/_venv.py b/tox/_venv.py index c0d7dec..58093fc 100644 --- a/tox/_venv.py +++ b/tox/_venv.py @@ -181,14 +181,9 @@ class VirtualEnv(object): interpreters = self.envconfig.config.interpreters config_interpreter = self.getsupportedinterpreter() info = interpreters.get_info(executable=config_interpreter) - use_venv191 = use_pip13 = info.version_info < (2,6) - if not use_venv191: - f, path, _ = py.std.imp.find_module("virtualenv") - f.close() - venvscript = path.rstrip("co") - else: - venvscript = py.path.local(tox.__file__).dirpath( - "vendor", "virtualenv.py") + f, path, _ = py.std.imp.find_module("virtualenv") + f.close() + venvscript = path.rstrip("co") args = [config_interpreter, str(venvscript)] if self.envconfig.distribute: args.append("--distribute") @@ -210,14 +205,7 @@ class VirtualEnv(object): args.append(self.path.basename) self._pcall(args, venv=False, action=action, cwd=basepath) self.just_created = True - if use_pip13: - indexserver = self.envconfig.config.indexserver['default'].url - action = self.session.newaction(self, "pip_downgrade") - action.setactivity('pip-downgrade', 'pip<1.4') - argv = ["easy_install"] + \ - self._installopts(indexserver) + ['pip<1.4'] - self._pcall(argv, cwd=self.envconfig.config.toxinidir, - action=action) + def finish(self): self._getliveconfig().writeconfig(self.path_config) diff --git a/tox/interpreters.py b/tox/interpreters.py index 514ea39..e225fcc 100644 --- a/tox/interpreters.py +++ b/tox/interpreters.py @@ -12,7 +12,7 @@ class Interpreters: def get_executable(self, name): """ return path object to the executable for the given - name (e.g. python2.5, python2.7, python etc.) + name (e.g. python2.6, python2.7, python etc.) if name is already an existing path, return name. If an interpreter cannot be found, return None. """ diff --git a/tox/vendor/virtualenv.py b/tox/vendor/virtualenv.py deleted file mode 100755 index ccb6eec..0000000 --- a/tox/vendor/virtualenv.py +++ /dev/null @@ -1,2581 +0,0 @@ -#!/usr/bin/env python -"""Create a "virtual" Python installation -""" - -# If you change the version here, change it in setup.py -# and docs/conf.py as well. -__version__ = "1.9.1" # following best practices -virtualenv_version = __version__ # legacy, again - -import base64 -import sys -import os -import codecs -import optparse -import re -import shutil -import logging -import tempfile -import zlib -import errno -import glob -import distutils.sysconfig -from distutils.util import strtobool -import struct -import subprocess - -if sys.version_info < (2, 5): - print('ERROR: %s' % sys.exc_info()[1]) - print('ERROR: this script requires Python 2.5 or greater.') - sys.exit(101) - -try: - set -except NameError: - from sets import Set as set -try: - basestring -except NameError: - basestring = str - -try: - import ConfigParser -except ImportError: - import configparser as ConfigParser - -join = os.path.join -py_version = 'python%s.%s' % (sys.version_info[0], sys.version_info[1]) - -is_jython = sys.platform.startswith('java') -is_pypy = hasattr(sys, 'pypy_version_info') -is_win = (sys.platform == 'win32') -is_cygwin = (sys.platform == 'cygwin') -is_darwin = (sys.platform == 'darwin') -abiflags = getattr(sys, 'abiflags', '') - -user_dir = os.path.expanduser('~') -if is_win: - default_storage_dir = os.path.join(user_dir, 'virtualenv') -else: - default_storage_dir = os.path.join(user_dir, '.virtualenv') -default_config_file = os.path.join(default_storage_dir, 'virtualenv.ini') - -if is_pypy: - expected_exe = 'pypy' -elif is_jython: - expected_exe = 'jython' -else: - expected_exe = 'python' - - -REQUIRED_MODULES = ['os', 'posix', 'posixpath', 'nt', 'ntpath', 'genericpath', - 'fnmatch', 'locale', 'encodings', 'codecs', - 'stat', 'UserDict', 'readline', 'copy_reg', 'types', - 're', 'sre', 'sre_parse', 'sre_constants', 'sre_compile', - 'zlib'] - -REQUIRED_FILES = ['lib-dynload', 'config'] - -majver, minver = sys.version_info[:2] -if majver == 2: - if minver >= 6: - REQUIRED_MODULES.extend(['warnings', 'linecache', '_abcoll', 'abc']) - if minver >= 7: - REQUIRED_MODULES.extend(['_weakrefset']) - if minver <= 3: - REQUIRED_MODULES.extend(['sets', '__future__']) -elif majver == 3: - # Some extra modules are needed for Python 3, but different ones - # for different versions. - REQUIRED_MODULES.extend(['_abcoll', 'warnings', 'linecache', 'abc', 'io', - '_weakrefset', 'copyreg', 'tempfile', 'random', - '__future__', 'collections', 'keyword', 'tarfile', - 'shutil', 'struct', 'copy', 'tokenize', 'token', - 'functools', 'heapq', 'bisect', 'weakref', - 'reprlib']) - if minver >= 2: - REQUIRED_FILES[-1] = 'config-%s' % majver - if minver == 3: - import sysconfig - platdir = sysconfig.get_config_var('PLATDIR') - REQUIRED_FILES.append(platdir) - # The whole list of 3.3 modules is reproduced below - the current - # uncommented ones are required for 3.3 as of now, but more may be - # added as 3.3 development continues. - REQUIRED_MODULES.extend([ - #"aifc", - #"antigravity", - #"argparse", - #"ast", - #"asynchat", - #"asyncore", - "base64", - #"bdb", - #"binhex", - #"bisect", - #"calendar", - #"cgi", - #"cgitb", - #"chunk", - #"cmd", - #"codeop", - #"code", - #"colorsys", - #"_compat_pickle", - #"compileall", - #"concurrent", - #"configparser", - #"contextlib", - #"cProfile", - #"crypt", - #"csv", - #"ctypes", - #"curses", - #"datetime", - #"dbm", - #"decimal", - #"difflib", - #"dis", - #"doctest", - #"dummy_threading", - "_dummy_thread", - #"email", - #"filecmp", - #"fileinput", - #"formatter", - #"fractions", - #"ftplib", - #"functools", - #"getopt", - #"getpass", - #"gettext", - #"glob", - #"gzip", - "hashlib", - #"heapq", - "hmac", - #"html", - #"http", - #"idlelib", - #"imaplib", - #"imghdr", - "imp", - "importlib", - #"inspect", - #"json", - #"lib2to3", - #"logging", - #"macpath", - #"macurl2path", - #"mailbox", - #"mailcap", - #"_markupbase", - #"mimetypes", - #"modulefinder", - #"multiprocessing", - #"netrc", - #"nntplib", - #"nturl2path", - #"numbers", - #"opcode", - #"optparse", - #"os2emxpath", - #"pdb", - #"pickle", - #"pickletools", - #"pipes", - #"pkgutil", - #"platform", - #"plat-linux2", - #"plistlib", - #"poplib", - #"pprint", - #"profile", - #"pstats", - #"pty", - #"pyclbr", - #"py_compile", - #"pydoc_data", - #"pydoc", - #"_pyio", - #"queue", - #"quopri", - #"reprlib", - "rlcompleter", - #"runpy", - #"sched", - #"shelve", - #"shlex", - #"smtpd", - #"smtplib", - #"sndhdr", - #"socket", - #"socketserver", - #"sqlite3", - #"ssl", - #"stringprep", - #"string", - #"_strptime", - #"subprocess", - #"sunau", - #"symbol", - #"symtable", - #"sysconfig", - #"tabnanny", - #"telnetlib", - #"test", - #"textwrap", - #"this", - #"_threading_local", - #"threading", - #"timeit", - #"tkinter", - #"tokenize", - #"token", - #"traceback", - #"trace", - #"tty", - #"turtledemo", - #"turtle", - #"unittest", - #"urllib", - #"uuid", - #"uu", - #"wave", - #"weakref", - #"webbrowser", - #"wsgiref", - #"xdrlib", - #"xml", - #"xmlrpc", - #"zipfile", - ]) - -if is_pypy: - # these are needed to correctly display the exceptions that may happen - # during the bootstrap - REQUIRED_MODULES.extend(['traceback', 'linecache']) - -class Logger(object): - - """ - Logging object for use in command-line script. Allows ranges of - levels, to avoid some redundancy of displayed information. - """ - - DEBUG = logging.DEBUG - INFO = logging.INFO - NOTIFY = (logging.INFO+logging.WARN)/2 - WARN = WARNING = logging.WARN - ERROR = logging.ERROR - FATAL = logging.FATAL - - LEVELS = [DEBUG, INFO, NOTIFY, WARN, ERROR, FATAL] - - def __init__(self, consumers): - self.consumers = consumers - self.indent = 0 - self.in_progress = None - self.in_progress_hanging = False - - def debug(self, msg, *args, **kw): - self.log(self.DEBUG, msg, *args, **kw) - def info(self, msg, *args, **kw): - self.log(self.INFO, msg, *args, **kw) - def notify(self, msg, *args, **kw): - self.log(self.NOTIFY, msg, *args, **kw) - def warn(self, msg, *args, **kw): - self.log(self.WARN, msg, *args, **kw) - def error(self, msg, *args, **kw): - self.log(self.ERROR, msg, *args, **kw) - def fatal(self, msg, *args, **kw): - self.log(self.FATAL, msg, *args, **kw) - def log(self, level, msg, *args, **kw): - if args: - if kw: - raise TypeError( - "You may give positional or keyword arguments, not both") - args = args or kw - rendered = None - for consumer_level, consumer in self.consumers: - if self.level_matches(level, consumer_level): - if (self.in_progress_hanging - and consumer in (sys.stdout, sys.stderr)): - self.in_progress_hanging = False - sys.stdout.write('\n') - sys.stdout.flush() - if rendered is None: - if args: - rendered = msg % args - else: - rendered = msg - rendered = ' '*self.indent + rendered - if hasattr(consumer, 'write'): - consumer.write(rendered+'\n') - else: - consumer(rendered) - - def start_progress(self, msg): - assert not self.in_progress, ( - "Tried to start_progress(%r) while in_progress %r" - % (msg, self.in_progress)) - if self.level_matches(self.NOTIFY, self._stdout_level()): - sys.stdout.write(msg) - sys.stdout.flush() - self.in_progress_hanging = True - else: - self.in_progress_hanging = False - self.in_progress = msg - - def end_progress(self, msg='done.'): - assert self.in_progress, ( - "Tried to end_progress without start_progress") - if self.stdout_level_matches(self.NOTIFY): - if not self.in_progress_hanging: - # Some message has been printed out since start_progress - sys.stdout.write('...' + self.in_progress + msg + '\n') - sys.stdout.flush() - else: - sys.stdout.write(msg + '\n') - sys.stdout.flush() - self.in_progress = None - self.in_progress_hanging = False - - def show_progress(self): - """If we are in a progress scope, and no log messages have been - shown, write out another '.'""" - if self.in_progress_hanging: - sys.stdout.write('.') - sys.stdout.flush() - - def stdout_level_matches(self, level): - """Returns true if a message at this level will go to stdout""" - return self.level_matches(level, self._stdout_level()) - - def _stdout_level(self): - """Returns the level that stdout runs at""" - for level, consumer in self.consumers: - if consumer is sys.stdout: - return level - return self.FATAL - - def level_matches(self, level, consumer_level): - """ - >>> l = Logger([]) - >>> l.level_matches(3, 4) - False - >>> l.level_matches(3, 2) - True - >>> l.level_matches(slice(None, 3), 3) - False - >>> l.level_matches(slice(None, 3), 2) - True - >>> l.level_matches(slice(1, 3), 1) - True - >>> l.level_matches(slice(2, 3), 1) - False - """ - if isinstance(level, slice): - start, stop = level.start, level.stop - if start is not None and start > consumer_level: - return False - if stop is not None and stop <= consumer_level: - return False - return True - else: - return level >= consumer_level - - #@classmethod - def level_for_integer(cls, level): - levels = cls.LEVELS - if level < 0: - return levels[0] - if level >= len(levels): - return levels[-1] - return levels[level] - - level_for_integer = classmethod(level_for_integer) - -# create a silent logger just to prevent this from being undefined -# will be overridden with requested verbosity main() is called. -logger = Logger([(Logger.LEVELS[-1], sys.stdout)]) - -def mkdir(path): - if not os.path.exists(path): - logger.info('Creating %s', path) - os.makedirs(path) - else: - logger.info('Directory %s already exists', path) - -def copyfileordir(src, dest): - if os.path.isdir(src): - shutil.copytree(src, dest, True) - else: - shutil.copy2(src, dest) - -def copyfile(src, dest, symlink=True): - if not os.path.exists(src): - # Some bad symlink in the src - logger.warn('Cannot find file %s (bad symlink)', src) - return - if os.path.exists(dest): - logger.debug('File %s already exists', dest) - return - if not os.path.exists(os.path.dirname(dest)): - logger.info('Creating parent directories for %s' % os.path.dirname(dest)) - os.makedirs(os.path.dirname(dest)) - if not os.path.islink(src): - srcpath = os.path.abspath(src) - else: - srcpath = os.readlink(src) - if symlink and hasattr(os, 'symlink') and not is_win: - logger.info('Symlinking %s', dest) - try: - os.symlink(srcpath, dest) - except (OSError, NotImplementedError): - logger.info('Symlinking failed, copying to %s', dest) - copyfileordir(src, dest) - else: - logger.info('Copying to %s', dest) - copyfileordir(src, dest) - -def writefile(dest, content, overwrite=True): - if not os.path.exists(dest): - logger.info('Writing %s', dest) - f = open(dest, 'wb') - f.write(content.encode('utf-8')) - f.close() - return - else: - f = open(dest, 'rb') - c = f.read() - f.close() - if c != content.encode("utf-8"): - if not overwrite: - logger.notify('File %s exists with different content; not overwriting', dest) - return - logger.notify('Overwriting %s with new content', dest) - f = open(dest, 'wb') - f.write(content.encode('utf-8')) - f.close() - else: - logger.info('Content %s already in place', dest) - -def rmtree(dir): - if os.path.exists(dir): - logger.notify('Deleting tree %s', dir) - shutil.rmtree(dir) - else: - logger.info('Do not need to delete %s; already gone', dir) - -def make_exe(fn): - if hasattr(os, 'chmod'): - oldmode = os.stat(fn).st_mode & 0xFFF # 0o7777 - newmode = (oldmode | 0x16D) & 0xFFF # 0o555, 0o7777 - os.chmod(fn, newmode) - logger.info('Changed mode of %s to %s', fn, oct(newmode)) - -def _find_file(filename, dirs): - for dir in reversed(dirs): - files = glob.glob(os.path.join(dir, filename)) - if files and os.path.isfile(files[0]): - return True, files[0] - return False, filename - -def _install_req(py_executable, unzip=False, distribute=False, - search_dirs=None, never_download=False): - - if search_dirs is None: - search_dirs = file_search_dirs() - - if not distribute: - egg_path = 'setuptools-*-py%s.egg' % sys.version[:3] - found, egg_path = _find_file(egg_path, search_dirs) - project_name = 'setuptools' - bootstrap_script = EZ_SETUP_PY - tgz_path = None - else: - # Look for a distribute egg (these are not distributed by default, - # but can be made available by the user) - egg_path = 'distribute-*-py%s.egg' % sys.version[:3] - found, egg_path = _find_file(egg_path, search_dirs) - project_name = 'distribute' - if found: - tgz_path = None - bootstrap_script = DISTRIBUTE_FROM_EGG_PY - else: - # Fall back to sdist - # NB: egg_path is not None iff tgz_path is None - # iff bootstrap_script is a generic setup script accepting - # the standard arguments. - egg_path = None - tgz_path = 'distribute-*.tar.gz' - found, tgz_path = _find_file(tgz_path, search_dirs) - bootstrap_script = DISTRIBUTE_SETUP_PY - - if is_jython and os._name == 'nt': - # Jython's .bat sys.executable can't handle a command line - # argument with newlines - fd, ez_setup = tempfile.mkstemp('.py') - os.write(fd, bootstrap_script) - os.close(fd) - cmd = [py_executable, ez_setup] - else: - cmd = [py_executable, '-c', bootstrap_script] - if unzip and egg_path: - cmd.append('--always-unzip') - env = {} - remove_from_env = ['__PYVENV_LAUNCHER__'] - if logger.stdout_level_matches(logger.DEBUG) and egg_path: - cmd.append('-v') - - old_chdir = os.getcwd() - if egg_path is not None and os.path.exists(egg_path): - logger.info('Using existing %s egg: %s' % (project_name, egg_path)) - cmd.append(egg_path) - if os.environ.get('PYTHONPATH'): - env['PYTHONPATH'] = egg_path + os.path.pathsep + os.environ['PYTHONPATH'] - else: - env['PYTHONPATH'] = egg_path - elif tgz_path is not None and os.path.exists(tgz_path): - # Found a tgz source dist, let's chdir - logger.info('Using existing %s egg: %s' % (project_name, tgz_path)) - os.chdir(os.path.dirname(tgz_path)) - # in this case, we want to be sure that PYTHONPATH is unset (not - # just empty, really unset), else CPython tries to import the - # site.py that it's in virtualenv_support - remove_from_env.append('PYTHONPATH') - elif never_download: - logger.fatal("Can't find any local distributions of %s to install " - "and --never-download is set. Either re-run virtualenv " - "without the --never-download option, or place a %s " - "distribution (%s) in one of these " - "locations: %r" % (project_name, project_name, - egg_path or tgz_path, - search_dirs)) - sys.exit(1) - elif egg_path: - logger.info('No %s egg found; downloading' % project_name) - cmd.extend(['--always-copy', '-U', project_name]) - else: - logger.info('No %s tgz found; downloading' % project_name) - logger.start_progress('Installing %s...' % project_name) - logger.indent += 2 - cwd = None - if project_name == 'distribute': - env['DONT_PATCH_SETUPTOOLS'] = 'true' - - def _filter_ez_setup(line): - return filter_ez_setup(line, project_name) - - if not os.access(os.getcwd(), os.W_OK): - cwd = tempfile.mkdtemp() - if tgz_path is not None and os.path.exists(tgz_path): - # the current working dir is hostile, let's copy the - # tarball to a temp dir - target = os.path.join(cwd, os.path.split(tgz_path)[-1]) - shutil.copy(tgz_path, target) - try: - call_subprocess(cmd, show_stdout=False, - filter_stdout=_filter_ez_setup, - extra_env=env, - remove_from_env=remove_from_env, - cwd=cwd) - finally: - logger.indent -= 2 - logger.end_progress() - if cwd is not None: - shutil.rmtree(cwd) - if os.getcwd() != old_chdir: - os.chdir(old_chdir) - if is_jython and os._name == 'nt': - os.remove(ez_setup) - -def file_search_dirs(): - here = os.path.dirname(os.path.abspath(__file__)) - dirs = ['.', here, - join(here, 'virtualenv_support')] - if os.path.splitext(os.path.dirname(__file__))[0] != 'virtualenv': - # Probably some boot script; just in case virtualenv is installed... - try: - import virtualenv - except ImportError: - pass - else: - dirs.append(os.path.join(os.path.dirname(virtualenv.__file__), 'virtualenv_support')) - return [d for d in dirs if os.path.isdir(d)] - -def install_setuptools(py_executable, unzip=False, - search_dirs=None, never_download=False): - _install_req(py_executable, unzip, - search_dirs=search_dirs, never_download=never_download) - -def install_distribute(py_executable, unzip=False, - search_dirs=None, never_download=False): - _install_req(py_executable, unzip, distribute=True, - search_dirs=search_dirs, never_download=never_download) - -_pip_re = re.compile(r'^pip-.*(zip|tar.gz|tar.bz2|tgz|tbz)$', re.I) -def install_pip(py_executable, search_dirs=None, never_download=False): - if search_dirs is None: - search_dirs = file_search_dirs() - - filenames = [] - for dir in search_dirs: - filenames.extend([join(dir, fn) for fn in os.listdir(dir) - if _pip_re.search(fn)]) - filenames = [(os.path.basename(filename).lower(), i, filename) for i, filename in enumerate(filenames)] - filenames.sort() - filenames = [filename for basename, i, filename in filenames] - if not filenames: - filename = 'pip' - else: - filename = filenames[-1] - easy_install_script = 'easy_install' - if is_win: - easy_install_script = 'easy_install-script.py' - # There's two subtle issues here when invoking easy_install. - # 1. On unix-like systems the easy_install script can *only* be executed - # directly if its full filesystem path is no longer than 78 characters. - # 2. A work around to [1] is to use the `python path/to/easy_install foo` - # pattern, but that breaks if the path contains non-ASCII characters, as - # you can't put the file encoding declaration before the shebang line. - # The solution is to use Python's -x flag to skip the first line of the - # script (and any ASCII decoding errors that may have occurred in that line) - cmd = [py_executable, '-x', join(os.path.dirname(py_executable), easy_install_script), filename] - # jython and pypy don't yet support -x - if is_jython or is_pypy: - cmd.remove('-x') - if filename == 'pip': - if never_download: - logger.fatal("Can't find any local distributions of pip to install " - "and --never-download is set. Either re-run virtualenv " - "without the --never-download option, or place a pip " - "source distribution (zip/tar.gz/tar.bz2) in one of these " - "locations: %r" % search_dirs) - sys.exit(1) - logger.info('Installing pip from network...') - else: - logger.info('Installing existing %s distribution: %s' % ( - os.path.basename(filename), filename)) - logger.start_progress('Installing pip...') - logger.indent += 2 - def _filter_setup(line): - return filter_ez_setup(line, 'pip') - try: - call_subprocess(cmd, show_stdout=False, - filter_stdout=_filter_setup) - finally: - logger.indent -= 2 - logger.end_progress() - -def filter_ez_setup(line, project_name='setuptools'): - if not line.strip(): - return Logger.DEBUG - if project_name == 'distribute': - for prefix in ('Extracting', 'Now working', 'Installing', 'Before', - 'Scanning', 'Setuptools', 'Egg', 'Already', - 'running', 'writing', 'reading', 'installing', - 'creating', 'copying', 'byte-compiling', 'removing', - 'Processing'): - if line.startswith(prefix): - return Logger.DEBUG - return Logger.DEBUG - for prefix in ['Reading ', 'Best match', 'Processing setuptools', - 'Copying setuptools', 'Adding setuptools', - 'Installing ', 'Installed ']: - if line.startswith(prefix): - return Logger.DEBUG - return Logger.INFO - - -class UpdatingDefaultsHelpFormatter(optparse.IndentedHelpFormatter): - """ - Custom help formatter for use in ConfigOptionParser that updates - the defaults before expanding them, allowing them to show up correctly - in the help listing - """ - def expand_default(self, option): - if self.parser is not None: - self.parser.update_defaults(self.parser.defaults) - return optparse.IndentedHelpFormatter.expand_default(self, option) - - -class ConfigOptionParser(optparse.OptionParser): - """ - Custom option parser which updates its defaults by by checking the - configuration files and environmental variables - """ - def __init__(self, *args, **kwargs): - self.config = ConfigParser.RawConfigParser() - self.files = self.get_config_files() - self.config.read(self.files) - optparse.OptionParser.__init__(self, *args, **kwargs) - - def get_config_files(self): - config_file = os.environ.get('VIRTUALENV_CONFIG_FILE', False) - if config_file and os.path.exists(config_file): - return [config_file] - return [default_config_file] - - def update_defaults(self, defaults): - """ - Updates the given defaults with values from the config files and - the environ. Does a little special handling for certain types of - options (lists). - """ - # Then go and look for the other sources of configuration: - config = {} - # 1. config files - config.update(dict(self.get_config_section('virtualenv'))) - # 2. environmental variables - config.update(dict(self.get_environ_vars())) - # Then set the options with those values - for key, val in config.items(): - key = key.replace('_', '-') - if not key.startswith('--'): - key = '--%s' % key # only prefer long opts - option = self.get_option(key) - if option is not None: - # ignore empty values - if not val: - continue - # handle multiline configs - if option.action == 'append': - val = val.split() - else: - option.nargs = 1 - if option.action == 'store_false': - val = not strtobool(val) - elif option.action in ('store_true', 'count'): - val = strtobool(val) - try: - val = option.convert_value(key, val) - except optparse.OptionValueError: - e = sys.exc_info()[1] - print("An error occured during configuration: %s" % e) - sys.exit(3) - defaults[option.dest] = val - return defaults - - def get_config_section(self, name): - """ - Get a section of a configuration - """ - if self.config.has_section(name): - return self.config.items(name) - return [] - - def get_environ_vars(self, prefix='VIRTUALENV_'): - """ - Returns a generator with all environmental vars with prefix VIRTUALENV - """ - for key, val in os.environ.items(): - if key.startswith(prefix): - yield (key.replace(prefix, '').lower(), val) - - def get_default_values(self): - """ - Overridding to make updating the defaults after instantiation of - the option parser possible, update_defaults() does the dirty work. - """ - if not self.process_default_values: - # Old, pre-Optik 1.5 behaviour. - return optparse.Values(self.defaults) - - defaults = self.update_defaults(self.defaults.copy()) # ours - for option in self._get_all_options(): - default = defaults.get(option.dest) - if isinstance(default, basestring): - opt_str = option.get_opt_string() - defaults[option.dest] = option.check_value(opt_str, default) - return optparse.Values(defaults) - - -def main(): - parser = ConfigOptionParser( - version=virtualenv_version, - usage="%prog [OPTIONS] DEST_DIR", - formatter=UpdatingDefaultsHelpFormatter()) - - parser.add_option( - '-v', '--verbose', - action='count', - dest='verbose', - default=0, - help="Increase verbosity") - - parser.add_option( - '-q', '--quiet', - action='count', - dest='quiet', - default=0, - help='Decrease verbosity') - - parser.add_option( - '-p', '--python', - dest='python', - metavar='PYTHON_EXE', - help='The Python interpreter to use, e.g., --python=python2.5 will use the python2.5 ' - 'interpreter to create the new environment. The default is the interpreter that ' - 'virtualenv was installed with (%s)' % sys.executable) - - parser.add_option( - '--clear', - dest='clear', - action='store_true', - help="Clear out the non-root install and start from scratch") - - parser.set_defaults(system_site_packages=False) - parser.add_option( - '--no-site-packages', - dest='system_site_packages', - action='store_false', - help="Don't give access to the global site-packages dir to the " - "virtual environment (default)") - - parser.add_option( - '--system-site-packages', - dest='system_site_packages', - action='store_true', - help="Give access to the global site-packages dir to the " - "virtual environment") - - parser.add_option( - '--unzip-setuptools', - dest='unzip_setuptools', - action='store_true', - help="Unzip Setuptools or Distribute when installing it") - - parser.add_option( - '--relocatable', - dest='relocatable', - action='store_true', - help='Make an EXISTING virtualenv environment relocatable. ' - 'This fixes up scripts and makes all .pth files relative') - - parser.add_option( - '--distribute', '--use-distribute', # the second option is for legacy reasons here. Hi Kenneth! - dest='use_distribute', - action='store_true', - help='Use Distribute instead of Setuptools. Set environ variable ' - 'VIRTUALENV_DISTRIBUTE to make it the default ') - - parser.add_option( - '--no-setuptools', - dest='no_setuptools', - action='store_true', - help='Do not install distribute/setuptools (or pip) ' - 'in the new virtualenv.') - - parser.add_option( - '--no-pip', - dest='no_pip', - action='store_true', - help='Do not install pip in the new virtualenv.') - - parser.add_option( - '--setuptools', - dest='use_distribute', - action='store_false', - help='Use Setuptools instead of Distribute. Set environ variable ' - 'VIRTUALENV_SETUPTOOLS to make it the default ') - - # Set this to True to use distribute by default, even in Python 2. - parser.set_defaults(use_distribute=False) - - default_search_dirs = file_search_dirs() - parser.add_option( - '--extra-search-dir', - dest="search_dirs", - action="append", - default=default_search_dirs, - help="Directory to look for setuptools/distribute/pip distributions in. " - "You can add any number of additional --extra-search-dir paths.") - - parser.add_option( - '--never-download', - dest="never_download", - action="store_true", - help="Never download anything from the network. Instead, virtualenv will fail " - "if local distributions of setuptools/distribute/pip are not present.") - - parser.add_option( - '--prompt', - dest='prompt', - help='Provides an alternative prompt prefix for this environment') - - if 'extend_parser' in globals(): - extend_parser(parser) - - options, args = parser.parse_args() - - global logger - - if 'adjust_options' in globals(): - adjust_options(options, args) - - verbosity = options.verbose - options.quiet - logger = Logger([(Logger.level_for_integer(2 - verbosity), sys.stdout)]) - - if options.python and not os.environ.get('VIRTUALENV_INTERPRETER_RUNNING'): - env = os.environ.copy() - interpreter = resolve_interpreter(options.python) - if interpreter == sys.executable: - logger.warn('Already using interpreter %s' % interpreter) - else: - logger.notify('Running virtualenv with interpreter %s' % interpreter) - env['VIRTUALENV_INTERPRETER_RUNNING'] = 'true' - file = __file__ - if file.endswith('.pyc'): - file = file[:-1] - popen = subprocess.Popen([interpreter, file] + sys.argv[1:], env=env) - raise SystemExit(popen.wait()) - - # Force --distribute on Python 3, since setuptools is not available. - if majver > 2: - options.use_distribute = True - - if os.environ.get('PYTHONDONTWRITEBYTECODE') and not options.use_distribute: - print( - "The PYTHONDONTWRITEBYTECODE environment variable is " - "not compatible with setuptools. Either use --distribute " - "or unset PYTHONDONTWRITEBYTECODE.") - sys.exit(2) - if not args: - print('You must provide a DEST_DIR') - parser.print_help() - sys.exit(2) - if len(args) > 1: - print('There must be only one argument: DEST_DIR (you gave %s)' % ( - ' '.join(args))) - parser.print_help() - sys.exit(2) - - home_dir = args[0] - - if os.environ.get('WORKING_ENV'): - logger.fatal('ERROR: you cannot run virtualenv while in a workingenv') - logger.fatal('Please deactivate your workingenv, then re-run this script') - sys.exit(3) - - if 'PYTHONHOME' in os.environ: - logger.warn('PYTHONHOME is set. You *must* activate the virtualenv before using it') - del os.environ['PYTHONHOME'] - - if options.relocatable: - make_environment_relocatable(home_dir) - return - - create_environment(home_dir, - site_packages=options.system_site_packages, - clear=options.clear, - unzip_setuptools=options.unzip_setuptools, - use_distribute=options.use_distribute, - prompt=options.prompt, - search_dirs=options.search_dirs, - never_download=options.never_download, - no_setuptools=options.no_setuptools, - no_pip=options.no_pip) - if 'after_install' in globals(): - after_install(options, home_dir) - -def call_subprocess(cmd, show_stdout=True, - filter_stdout=None, cwd=None, - raise_on_returncode=True, extra_env=None, - remove_from_env=None): - cmd_parts = [] - for part in cmd: - if len(part) > 45: - part = part[:20]+"..."+part[-20:] - if ' ' in part or '\n' in part or '"' in part or "'" in part: - part = '"%s"' % part.replace('"', '\\"') - if hasattr(part, 'decode'): - try: - part = part.decode(sys.getdefaultencoding()) - except UnicodeDecodeError: - part = part.decode(sys.getfilesystemencoding()) - cmd_parts.append(part) - cmd_desc = ' '.join(cmd_parts) - if show_stdout: - stdout = None - else: - stdout = subprocess.PIPE - logger.debug("Running command %s" % cmd_desc) - if extra_env or remove_from_env: - env = os.environ.copy() - if extra_env: - env.update(extra_env) - if remove_from_env: - for varname in remove_from_env: - env.pop(varname, None) - else: - env = None - try: - proc = subprocess.Popen( - cmd, stderr=subprocess.STDOUT, stdin=None, stdout=stdout, - cwd=cwd, env=env) - except Exception: - e = sys.exc_info()[1] - logger.fatal( - "Error %s while executing command %s" % (e, cmd_desc)) - raise - all_output = [] - if stdout is not None: - stdout = proc.stdout - encoding = sys.getdefaultencoding() - fs_encoding = sys.getfilesystemencoding() - while 1: - line = stdout.readline() - try: - line = line.decode(encoding) - except UnicodeDecodeError: - line = line.decode(fs_encoding) - if not line: - break - line = line.rstrip() - all_output.append(line) - if filter_stdout: - level = filter_stdout(line) - if isinstance(level, tuple): - level, line = level - logger.log(level, line) - if not logger.stdout_level_matches(level): - logger.show_progress() - else: - logger.info(line) - else: - proc.communicate() - proc.wait() - if proc.returncode: - if raise_on_returncode: - if all_output: - logger.notify('Complete output from command %s:' % cmd_desc) - logger.notify('\n'.join(all_output) + '\n----------------------------------------') - raise OSError( - "Command %s failed with error code %s" - % (cmd_desc, proc.returncode)) - else: - logger.warn( - "Command %s had error code %s" - % (cmd_desc, proc.returncode)) - - -def create_environment(home_dir, site_packages=False, clear=False, - unzip_setuptools=False, use_distribute=False, - prompt=None, search_dirs=None, never_download=False, - no_setuptools=False, no_pip=False): - """ - Creates a new environment in ``home_dir``. - - If ``site_packages`` is true, then the global ``site-packages/`` - directory will be on the path. - - If ``clear`` is true (default False) then the environment will - first be cleared. - """ - home_dir, lib_dir, inc_dir, bin_dir = path_locations(home_dir) - - py_executable = os.path.abspath(install_python( - home_dir, lib_dir, inc_dir, bin_dir, - site_packages=site_packages, clear=clear)) - - install_distutils(home_dir) - - if not no_setuptools: - if use_distribute: - install_distribute(py_executable, unzip=unzip_setuptools, - search_dirs=search_dirs, never_download=never_download) - else: - install_setuptools(py_executable, unzip=unzip_setuptools, - search_dirs=search_dirs, never_download=never_download) - - if not no_pip: - install_pip(py_executable, search_dirs=search_dirs, never_download=never_download) - - install_activate(home_dir, bin_dir, prompt) - -def is_executable_file(fpath): - return os.path.isfile(fpath) and os.access(fpath, os.X_OK) - -def path_locations(home_dir): - """Return the path locations for the environment (where libraries are, - where scripts go, etc)""" - # XXX: We'd use distutils.sysconfig.get_python_inc/lib but its - # prefix arg is broken: http://bugs.python.org/issue3386 - if is_win: - # Windows has lots of problems with executables with spaces in - # the name; this function will remove them (using the ~1 - # format): - mkdir(home_dir) - if ' ' in home_dir: - import ctypes - GetShortPathName = ctypes.windll.kernel32.GetShortPathNameW - size = max(len(home_dir)+1, 256) - buf = ctypes.create_unicode_buffer(size) - try: - u = unicode - except NameError: - u = str - ret = GetShortPathName(u(home_dir), buf, size) - if not ret: - print('Error: the path "%s" has a space in it' % home_dir) - print('We could not determine the short pathname for it.') - print('Exiting.') - sys.exit(3) - home_dir = str(buf.value) - lib_dir = join(home_dir, 'Lib') - inc_dir = join(home_dir, 'Include') - bin_dir = join(home_dir, 'Scripts') - if is_jython: - lib_dir = join(home_dir, 'Lib') - inc_dir = join(home_dir, 'Include') - bin_dir = join(home_dir, 'bin') - elif is_pypy: - lib_dir = home_dir - inc_dir = join(home_dir, 'include') - bin_dir = join(home_dir, 'bin') - elif not is_win: - lib_dir = join(home_dir, 'lib', py_version) - multiarch_exec = '/usr/bin/multiarch-platform' - if is_executable_file(multiarch_exec): - # In Mageia (2) and Mandriva distros the include dir must be like: - # virtualenv/include/multiarch-x86_64-linux/python2.7 - # instead of being virtualenv/include/python2.7 - p = subprocess.Popen(multiarch_exec, stdout=subprocess.PIPE, stderr=subprocess.PIPE) - stdout, stderr = p.communicate() - # stdout.strip is needed to remove newline character - inc_dir = join(home_dir, 'include', stdout.strip(), py_version + abiflags) - else: - inc_dir = join(home_dir, 'include', py_version + abiflags) - bin_dir = join(home_dir, 'bin') - return home_dir, lib_dir, inc_dir, bin_dir - - -def change_prefix(filename, dst_prefix): - prefixes = [sys.prefix] - - if is_darwin: - prefixes.extend(( - os.path.join("/Library/Python", sys.version[:3], "site-packages"), - os.path.join(sys.prefix, "Extras", "lib", "python"), - os.path.join("~", "Library", "Python", sys.version[:3], "site-packages"), - # Python 2.6 no-frameworks - os.path.join("~", ".local", "lib","python", sys.version[:3], "site-packages"), - # System Python 2.7 on OSX Mountain Lion - os.path.join("~", "Library", "Python", sys.version[:3], "lib", "python", "site-packages"))) - - if hasattr(sys, 'real_prefix'): - prefixes.append(sys.real_prefix) - if hasattr(sys, 'base_prefix'): - prefixes.append(sys.base_prefix) - prefixes = list(map(os.path.expanduser, prefixes)) - prefixes = list(map(os.path.abspath, prefixes)) - # Check longer prefixes first so we don't split in the middle of a filename - prefixes = sorted(prefixes, key=len, reverse=True) - filename = os.path.abspath(filename) - for src_prefix in prefixes: - if filename.startswith(src_prefix): - _, relpath = filename.split(src_prefix, 1) - if src_prefix != os.sep: # sys.prefix == "/" - assert relpath[0] == os.sep - relpath = relpath[1:] - return join(dst_prefix, relpath) - assert False, "Filename %s does not start with any of these prefixes: %s" % \ - (filename, prefixes) - -def copy_required_modules(dst_prefix): - import imp - # If we are running under -p, we need to remove the current - # directory from sys.path temporarily here, so that we - # definitely get the modules from the site directory of - # the interpreter we are running under, not the one - # virtualenv.py is installed under (which might lead to py2/py3 - # incompatibility issues) - _prev_sys_path = sys.path - if os.environ.get('VIRTUALENV_INTERPRETER_RUNNING'): - sys.path = sys.path[1:] - try: - for modname in REQUIRED_MODULES: - if modname in sys.builtin_module_names: - logger.info("Ignoring built-in bootstrap module: %s" % modname) - continue - try: - f, filename, _ = imp.find_module(modname) - except ImportError: - logger.info("Cannot import bootstrap module: %s" % modname) - else: - if f is not None: - f.close() - # special-case custom readline.so on OS X, but not for pypy: - if modname == 'readline' and sys.platform == 'darwin' and not ( - is_pypy or filename.endswith(join('lib-dynload', 'readline.so'))): - dst_filename = join(dst_prefix, 'lib', 'python%s' % sys.version[:3], 'readline.so') - else: - dst_filename = change_prefix(filename, dst_prefix) - copyfile(filename, dst_filename) - if filename.endswith('.pyc'): - pyfile = filename[:-1] - if os.path.exists(pyfile): - copyfile(pyfile, dst_filename[:-1]) - finally: - sys.path = _prev_sys_path - - -def subst_path(prefix_path, prefix, home_dir): - prefix_path = os.path.normpath(prefix_path) - prefix = os.path.normpath(prefix) - home_dir = os.path.normpath(home_dir) - if not prefix_path.startswith(prefix): - logger.warn('Path not in prefix %r %r', prefix_path, prefix) - return - return prefix_path.replace(prefix, home_dir, 1) - - -def install_python(home_dir, lib_dir, inc_dir, bin_dir, site_packages, clear): - """Install just the base environment, no distutils patches etc""" - if sys.executable.startswith(bin_dir): - print('Please use the *system* python to run this script') - return - - if clear: - rmtree(lib_dir) - ## FIXME: why not delete it? - ## Maybe it should delete everything with #!/path/to/venv/python in it - logger.notify('Not deleting %s', bin_dir) - - if hasattr(sys, 'real_prefix'): - logger.notify('Using real prefix %r' % sys.real_prefix) - prefix = sys.real_prefix - elif hasattr(sys, 'base_prefix'): - logger.notify('Using base prefix %r' % sys.base_prefix) - prefix = sys.base_prefix - else: - prefix = sys.prefix - mkdir(lib_dir) - fix_lib64(lib_dir) - stdlib_dirs = [os.path.dirname(os.__file__)] - if is_win: - stdlib_dirs.append(join(os.path.dirname(stdlib_dirs[0]), 'DLLs')) - elif is_darwin: - stdlib_dirs.append(join(stdlib_dirs[0], 'site-packages')) - if hasattr(os, 'symlink'): - logger.info('Symlinking Python bootstrap modules') - else: - logger.info('Copying Python bootstrap modules') - logger.indent += 2 - try: - # copy required files... - for stdlib_dir in stdlib_dirs: - if not os.path.isdir(stdlib_dir): - continue - for fn in os.listdir(stdlib_dir): - bn = os.path.splitext(fn)[0] - if fn != 'site-packages' and bn in REQUIRED_FILES: - copyfile(join(stdlib_dir, fn), join(lib_dir, fn)) - # ...and modules - copy_required_modules(home_dir) - finally: - logger.indent -= 2 - mkdir(join(lib_dir, 'site-packages')) - import site - site_filename = site.__file__ - if site_filename.endswith('.pyc'): - site_filename = site_filename[:-1] - elif site_filename.endswith('$py.class'): - site_filename = site_filename.replace('$py.class', '.py') - site_filename_dst = change_prefix(site_filename, home_dir) - site_dir = os.path.dirname(site_filename_dst) - writefile(site_filename_dst, SITE_PY) - writefile(join(site_dir, 'orig-prefix.txt'), prefix) - site_packages_filename = join(site_dir, 'no-global-site-packages.txt') - if not site_packages: - writefile(site_packages_filename, '') - - if is_pypy or is_win: - stdinc_dir = join(prefix, 'include') - else: - stdinc_dir = join(prefix, 'include', py_version + abiflags) - if os.path.exists(stdinc_dir): - copyfile(stdinc_dir, inc_dir) - else: - logger.debug('No include dir %s' % stdinc_dir) - - platinc_dir = distutils.sysconfig.get_python_inc(plat_specific=1) - if platinc_dir != stdinc_dir: - platinc_dest = distutils.sysconfig.get_python_inc( - plat_specific=1, prefix=home_dir) - if platinc_dir == platinc_dest: - # Do platinc_dest manually due to a CPython bug; - # not http://bugs.python.org/issue3386 but a close cousin - platinc_dest = subst_path(platinc_dir, prefix, home_dir) - if platinc_dest: - # PyPy's stdinc_dir and prefix are relative to the original binary - # (traversing virtualenvs), whereas the platinc_dir is relative to - # the inner virtualenv and ignores the prefix argument. - # This seems more evolved than designed. - copyfile(platinc_dir, platinc_dest) - - # pypy never uses exec_prefix, just ignore it - if sys.exec_prefix != prefix and not is_pypy: - if is_win: - exec_dir = join(sys.exec_prefix, 'lib') - elif is_jython: - exec_dir = join(sys.exec_prefix, 'Lib') - else: - exec_dir = join(sys.exec_prefix, 'lib', py_version) - for fn in os.listdir(exec_dir): - copyfile(join(exec_dir, fn), join(lib_dir, fn)) - - if is_jython: - # Jython has either jython-dev.jar and javalib/ dir, or just - # jython.jar - for name in 'jython-dev.jar', 'javalib', 'jython.jar': - src = join(prefix, name) - if os.path.exists(src): - copyfile(src, join(home_dir, name)) - # XXX: registry should always exist after Jython 2.5rc1 - src = join(prefix, 'registry') - if os.path.exists(src): - copyfile(src, join(home_dir, 'registry'), symlink=False) - copyfile(join(prefix, 'cachedir'), join(home_dir, 'cachedir'), - symlink=False) - - mkdir(bin_dir) - py_executable = join(bin_dir, os.path.basename(sys.executable)) - if 'Python.framework' in prefix: - # OS X framework builds cause validation to break - # https://github.com/pypa/virtualenv/issues/322 - if os.environ.get('__PYVENV_LAUNCHER__'): - os.unsetenv('__PYVENV_LAUNCHER__') - if re.search(r'/Python(?:-32|-64)*$', py_executable): - # The name of the python executable is not quite what - # we want, rename it. - py_executable = os.path.join( - os.path.dirname(py_executable), 'python') - - logger.notify('New %s executable in %s', expected_exe, py_executable) - pcbuild_dir = os.path.dirname(sys.executable) - pyd_pth = os.path.join(lib_dir, 'site-packages', 'virtualenv_builddir_pyd.pth') - if is_win and os.path.exists(os.path.join(pcbuild_dir, 'build.bat')): - logger.notify('Detected python running from build directory %s', pcbuild_dir) - logger.notify('Writing .pth file linking to build directory for *.pyd files') - writefile(pyd_pth, pcbuild_dir) - else: - pcbuild_dir = None - if os.path.exists(pyd_pth): - logger.info('Deleting %s (not Windows env or not build directory python)' % pyd_pth) - os.unlink(pyd_pth) - - if sys.executable != py_executable: - ## FIXME: could I just hard link? - executable = sys.executable - shutil.copyfile(executable, py_executable) - make_exe(py_executable) - if is_win or is_cygwin: - pythonw = os.path.join(os.path.dirname(sys.executable), 'pythonw.exe') - if os.path.exists(pythonw): - logger.info('Also created pythonw.exe') - shutil.copyfile(pythonw, os.path.join(os.path.dirname(py_executable), 'pythonw.exe')) - python_d = os.path.join(os.path.dirname(sys.executable), 'python_d.exe') - python_d_dest = os.path.join(os.path.dirname(py_executable), 'python_d.exe') - if os.path.exists(python_d): - logger.info('Also created python_d.exe') - shutil.copyfile(python_d, python_d_dest) - elif os.path.exists(python_d_dest): - logger.info('Removed python_d.exe as it is no longer at the source') - os.unlink(python_d_dest) - # we need to copy the DLL to enforce that windows will load the correct one. - # may not exist if we are cygwin. - py_executable_dll = 'python%s%s.dll' % ( - sys.version_info[0], sys.version_info[1]) - py_executable_dll_d = 'python%s%s_d.dll' % ( - sys.version_info[0], sys.version_info[1]) - pythondll = os.path.join(os.path.dirname(sys.executable), py_executable_dll) - pythondll_d = os.path.join(os.path.dirname(sys.executable), py_executable_dll_d) - pythondll_d_dest = os.path.join(os.path.dirname(py_executable), py_executable_dll_d) - if os.path.exists(pythondll): - logger.info('Also created %s' % py_executable_dll) - shutil.copyfile(pythondll, os.path.join(os.path.dirname(py_executable), py_executable_dll)) - if os.path.exists(pythondll_d): - logger.info('Also created %s' % py_executable_dll_d) - shutil.copyfile(pythondll_d, pythondll_d_dest) - elif os.path.exists(pythondll_d_dest): - logger.info('Removed %s as the source does not exist' % pythondll_d_dest) - os.unlink(pythondll_d_dest) - if is_pypy: - # make a symlink python --> pypy-c - python_executable = os.path.join(os.path.dirname(py_executable), 'python') - if sys.platform in ('win32', 'cygwin'): - python_executable += '.exe' - logger.info('Also created executable %s' % python_executable) - copyfile(py_executable, python_executable) - - if is_win: - for name in 'libexpat.dll', 'libpypy.dll', 'libpypy-c.dll', 'libeay32.dll', 'ssleay32.dll', 'sqlite.dll': - src = join(prefix, name) - if os.path.exists(src): - copyfile(src, join(bin_dir, name)) - - if os.path.splitext(os.path.basename(py_executable))[0] != expected_exe: - secondary_exe = os.path.join(os.path.dirname(py_executable), - expected_exe) - py_executable_ext = os.path.splitext(py_executable)[1] - if py_executable_ext == '.exe': - # python2.4 gives an extension of '.4' :P - secondary_exe += py_executable_ext - if os.path.exists(secondary_exe): - logger.warn('Not overwriting existing %s script %s (you must use %s)' - % (expected_exe, secondary_exe, py_executable)) - else: - logger.notify('Also creating executable in %s' % secondary_exe) - shutil.copyfile(sys.executable, secondary_exe) - make_exe(secondary_exe) - - if '.framework' in prefix: - if 'Python.framework' in prefix: - logger.debug('MacOSX Python framework detected') - # Make sure we use the the embedded interpreter inside - # the framework, even if sys.executable points to - # the stub executable in ${sys.prefix}/bin - # See http://groups.google.com/group/python-virtualenv/ - # browse_thread/thread/17cab2f85da75951 - original_python = os.path.join( - prefix, 'Resources/Python.app/Contents/MacOS/Python') - if 'EPD' in prefix: - logger.debug('EPD framework detected') - original_python = os.path.join(prefix, 'bin/python') - shutil.copy(original_python, py_executable) - - # Copy the framework's dylib into the virtual - # environment - virtual_lib = os.path.join(home_dir, '.Python') - - if os.path.exists(virtual_lib): - os.unlink(virtual_lib) - copyfile( - os.path.join(prefix, 'Python'), - virtual_lib) - - # And then change the install_name of the copied python executable - try: - mach_o_change(py_executable, - os.path.join(prefix, 'Python'), - '@executable_path/../.Python') - except: - e = sys.exc_info()[1] - logger.warn("Could not call mach_o_change: %s. " - "Trying to call install_name_tool instead." % e) - try: - call_subprocess( - ["install_name_tool", "-change", - os.path.join(prefix, 'Python'), - '@executable_path/../.Python', - py_executable]) - except: - logger.fatal("Could not call install_name_tool -- you must " - "have Apple's development tools installed") - raise - - if not is_win: - # Ensure that 'python', 'pythonX' and 'pythonX.Y' all exist - py_exe_version_major = 'python%s' % sys.version_info[0] - py_exe_version_major_minor = 'python%s.%s' % ( - sys.version_info[0], sys.version_info[1]) - py_exe_no_version = 'python' - required_symlinks = [ py_exe_no_version, py_exe_version_major, - py_exe_version_major_minor ] - - py_executable_base = os.path.basename(py_executable) - - if py_executable_base in required_symlinks: - # Don't try to symlink to yourself. - required_symlinks.remove(py_executable_base) - - for pth in required_symlinks: - full_pth = join(bin_dir, pth) - if os.path.exists(full_pth): - os.unlink(full_pth) - os.symlink(py_executable_base, full_pth) - - if is_win and ' ' in py_executable: - # There's a bug with subprocess on Windows when using a first - # argument that has a space in it. Instead we have to quote - # the value: - py_executable = '"%s"' % py_executable - # NOTE: keep this check as one line, cmd.exe doesn't cope with line breaks - cmd = [py_executable, '-c', 'import sys;out=sys.stdout;' - 'getattr(out, "buffer", out).write(sys.prefix.encode("utf-8"))'] - logger.info('Testing executable with %s %s "%s"' % tuple(cmd)) - try: - proc = subprocess.Popen(cmd, - stdout=subprocess.PIPE) - proc_stdout, proc_stderr = proc.communicate() - except OSError: - e = sys.exc_info()[1] - if e.errno == errno.EACCES: - logger.fatal('ERROR: The executable %s could not be run: %s' % (py_executable, e)) - sys.exit(100) - else: - raise e - - proc_stdout = proc_stdout.strip().decode("utf-8") - proc_stdout = os.path.normcase(os.path.abspath(proc_stdout)) - norm_home_dir = os.path.normcase(os.path.abspath(home_dir)) - if hasattr(norm_home_dir, 'decode'): - norm_home_dir = norm_home_dir.decode(sys.getfilesystemencoding()) - if proc_stdout != norm_home_dir: - logger.fatal( - 'ERROR: The executable %s is not functioning' % py_executable) - logger.fatal( - 'ERROR: It thinks sys.prefix is %r (should be %r)' - % (proc_stdout, norm_home_dir)) - logger.fatal( - 'ERROR: virtualenv is not compatible with this system or executable') - if is_win: - logger.fatal( - 'Note: some Windows users have reported this error when they ' - 'installed Python for "Only this user" or have multiple ' - 'versions of Python installed. Copying the appropriate ' - 'PythonXX.dll to the virtualenv Scripts/ directory may fix ' - 'this problem.') - sys.exit(100) - else: - logger.info('Got sys.prefix result: %r' % proc_stdout) - - pydistutils = os.path.expanduser('~/.pydistutils.cfg') - if os.path.exists(pydistutils): - logger.notify('Please make sure you remove any previous custom paths from ' - 'your %s file.' % pydistutils) - ## FIXME: really this should be calculated earlier - - fix_local_scheme(home_dir) - - if site_packages: - if os.path.exists(site_packages_filename): - logger.info('Deleting %s' % site_packages_filename) - os.unlink(site_packages_filename) - - return py_executable - - -def install_activate(home_dir, bin_dir, prompt=None): - home_dir = os.path.abspath(home_dir) - if is_win or is_jython and os._name == 'nt': - files = { - 'activate.bat': ACTIVATE_BAT, - 'deactivate.bat': DEACTIVATE_BAT, - 'activate.ps1': ACTIVATE_PS, - } - - # MSYS needs paths of the form /c/path/to/file - drive, tail = os.path.splitdrive(home_dir.replace(os.sep, '/')) - home_dir_msys = (drive and "/%s%s" or "%s%s") % (drive[:1], tail) - - # Run-time conditional enables (basic) Cygwin compatibility - home_dir_sh = ("""$(if [ "$OSTYPE" "==" "cygwin" ]; then cygpath -u '%s'; else echo '%s'; fi;)""" % - (home_dir, home_dir_msys)) - files['activate'] = ACTIVATE_SH.replace('__VIRTUAL_ENV__', home_dir_sh) - - else: - files = {'activate': ACTIVATE_SH} - - # suppling activate.fish in addition to, not instead of, the - # bash script support. - files['activate.fish'] = ACTIVATE_FISH - - # same for csh/tcsh support... - files['activate.csh'] = ACTIVATE_CSH - - files['activate_this.py'] = ACTIVATE_THIS - if hasattr(home_dir, 'decode'): - home_dir = home_dir.decode(sys.getfilesystemencoding()) - vname = os.path.basename(home_dir) - for name, content in files.items(): - content = content.replace('__VIRTUAL_PROMPT__', prompt or '') - content = content.replace('__VIRTUAL_WINPROMPT__', prompt or '(%s)' % vname) - content = content.replace('__VIRTUAL_ENV__', home_dir) - content = content.replace('__VIRTUAL_NAME__', vname) - content = content.replace('__BIN_NAME__', os.path.basename(bin_dir)) - writefile(os.path.join(bin_dir, name), content) - -def install_distutils(home_dir): - distutils_path = change_prefix(distutils.__path__[0], home_dir) - mkdir(distutils_path) - ## FIXME: maybe this prefix setting should only be put in place if - ## there's a local distutils.cfg with a prefix setting? - home_dir = os.path.abspath(home_dir) - ## FIXME: this is breaking things, removing for now: - #distutils_cfg = DISTUTILS_CFG + "\n[install]\nprefix=%s\n" % home_dir - writefile(os.path.join(distutils_path, '__init__.py'), DISTUTILS_INIT) - writefile(os.path.join(distutils_path, 'distutils.cfg'), DISTUTILS_CFG, overwrite=False) - -def fix_local_scheme(home_dir): - """ - Platforms that use the "posix_local" install scheme (like Ubuntu with - Python 2.7) need to be given an additional "local" location, sigh. - """ - try: - import sysconfig - except ImportError: - pass - else: - if sysconfig._get_default_scheme() == 'posix_local': - local_path = os.path.join(home_dir, 'local') - if not os.path.exists(local_path): - os.mkdir(local_path) - for subdir_name in os.listdir(home_dir): - if subdir_name == 'local': - continue - os.symlink(os.path.abspath(os.path.join(home_dir, subdir_name)), \ - os.path.join(local_path, subdir_name)) - -def fix_lib64(lib_dir): - """ - Some platforms (particularly Gentoo on x64) put things in lib64/pythonX.Y - instead of lib/pythonX.Y. If this is such a platform we'll just create a - symlink so lib64 points to lib - """ - if [p for p in distutils.sysconfig.get_config_vars().values() - if isinstance(p, basestring) and 'lib64' in p]: - logger.debug('This system uses lib64; symlinking lib64 to lib') - assert os.path.basename(lib_dir) == 'python%s' % sys.version[:3], ( - "Unexpected python lib dir: %r" % lib_dir) - lib_parent = os.path.dirname(lib_dir) - top_level = os.path.dirname(lib_parent) - lib_dir = os.path.join(top_level, 'lib') - lib64_link = os.path.join(top_level, 'lib64') - assert os.path.basename(lib_parent) == 'lib', ( - "Unexpected parent dir: %r" % lib_parent) - if os.path.lexists(lib64_link): - return - os.symlink('lib', lib64_link) - -def resolve_interpreter(exe): - """ - If the executable given isn't an absolute path, search $PATH for the interpreter - """ - if os.path.abspath(exe) != exe: - paths = os.environ.get('PATH', '').split(os.pathsep) - for path in paths: - if os.path.exists(os.path.join(path, exe)): - exe = os.path.join(path, exe) - break - if not os.path.exists(exe): - logger.fatal('The executable %s (from --python=%s) does not exist' % (exe, exe)) - raise SystemExit(3) - if not is_executable(exe): - logger.fatal('The executable %s (from --python=%s) is not executable' % (exe, exe)) - raise SystemExit(3) - return exe - -def is_executable(exe): - """Checks a file is executable""" - return os.access(exe, os.X_OK) - -############################################################ -## Relocating the environment: - -def make_environment_relocatable(home_dir): - """ - Makes the already-existing environment use relative paths, and takes out - the #!-based environment selection in scripts. - """ - home_dir, lib_dir, inc_dir, bin_dir = path_locations(home_dir) - activate_this = os.path.join(bin_dir, 'activate_this.py') - if not os.path.exists(activate_this): - logger.fatal( - 'The environment doesn\'t have a file %s -- please re-run virtualenv ' - 'on this environment to update it' % activate_this) - fixup_scripts(home_dir) - fixup_pth_and_egg_link(home_dir) - ## FIXME: need to fix up distutils.cfg - -OK_ABS_SCRIPTS = ['python', 'python%s' % sys.version[:3], - 'activate', 'activate.bat', 'activate_this.py'] - -def fixup_scripts(home_dir): - # This is what we expect at the top of scripts: - shebang = '#!%s/bin/python' % os.path.normcase(os.path.abspath(home_dir)) - # This is what we'll put: - new_shebang = '#!/usr/bin/env python%s' % sys.version[:3] - if is_win: - bin_suffix = 'Scripts' - else: - bin_suffix = 'bin' - bin_dir = os.path.join(home_dir, bin_suffix) - home_dir, lib_dir, inc_dir, bin_dir = path_locations(home_dir) - for filename in os.listdir(bin_dir): - filename = os.path.join(bin_dir, filename) - if not os.path.isfile(filename): - # ignore subdirs, e.g. .svn ones. - continue - f = open(filename, 'rb') - try: - try: - lines = f.read().decode('utf-8').splitlines() - except UnicodeDecodeError: - # This is probably a binary program instead - # of a script, so just ignore it. - continue - finally: - f.close() - if not lines: - logger.warn('Script %s is an empty file' % filename) - continue - if not lines[0].strip().startswith(shebang): - if os.path.basename(filename) in OK_ABS_SCRIPTS: - logger.debug('Cannot make script %s relative' % filename) - elif lines[0].strip() == new_shebang: - logger.info('Script %s has already been made relative' % filename) - else: - logger.warn('Script %s cannot be made relative (it\'s not a normal script that starts with %s)' - % (filename, shebang)) - continue - logger.notify('Making script %s relative' % filename) - script = relative_script([new_shebang] + lines[1:]) - f = open(filename, 'wb') - f.write('\n'.join(script).encode('utf-8')) - f.close() - -def relative_script(lines): - "Return a script that'll work in a relocatable environment." - activate = "import os; activate_this=os.path.join(os.path.dirname(os.path.realpath(__file__)), 'activate_this.py'); execfile(activate_this, dict(__file__=activate_this)); del os, activate_this" - # Find the last future statement in the script. If we insert the activation - # line before a future statement, Python will raise a SyntaxError. - activate_at = None - for idx, line in reversed(list(enumerate(lines))): - if line.split()[:3] == ['from', '__future__', 'import']: - activate_at = idx + 1 - break - if activate_at is None: - # Activate after the shebang. - activate_at = 1 - return lines[:activate_at] + ['', activate, ''] + lines[activate_at:] - -def fixup_pth_and_egg_link(home_dir, sys_path=None): - """Makes .pth and .egg-link files use relative paths""" - home_dir = os.path.normcase(os.path.abspath(home_dir)) - if sys_path is None: - sys_path = sys.path - for path in sys_path: - if not path: - path = '.' - if not os.path.isdir(path): - continue - path = os.path.normcase(os.path.abspath(path)) - if not path.startswith(home_dir): - logger.debug('Skipping system (non-environment) directory %s' % path) - continue - for filename in os.listdir(path): - filename = os.path.join(path, filename) - if filename.endswith('.pth'): - if not os.access(filename, os.W_OK): - logger.warn('Cannot write .pth file %s, skipping' % filename) - else: - fixup_pth_file(filename) - if filename.endswith('.egg-link'): - if not os.access(filename, os.W_OK): - logger.warn('Cannot write .egg-link file %s, skipping' % filename) - else: - fixup_egg_link(filename) - -def fixup_pth_file(filename): - lines = [] - prev_lines = [] - f = open(filename) - prev_lines = f.readlines() - f.close() - for line in prev_lines: - line = line.strip() - if (not line or line.startswith('#') or line.startswith('import ') - or os.path.abspath(line) != line): - lines.append(line) - else: - new_value = make_relative_path(filename, line) - if line != new_value: - logger.debug('Rewriting path %s as %s (in %s)' % (line, new_value, filename)) - lines.append(new_value) - if lines == prev_lines: - logger.info('No changes to .pth file %s' % filename) - return - logger.notify('Making paths in .pth file %s relative' % filename) - f = open(filename, 'w') - f.write('\n'.join(lines) + '\n') - f.close() - -def fixup_egg_link(filename): - f = open(filename) - link = f.readline().strip() - f.close() - if os.path.abspath(link) != link: - logger.debug('Link in %s already relative' % filename) - return - new_link = make_relative_path(filename, link) - logger.notify('Rewriting link %s in %s as %s' % (link, filename, new_link)) - f = open(filename, 'w') - f.write(new_link) - f.close() - -def make_relative_path(source, dest, dest_is_directory=True): - """ - Make a filename relative, where the filename is dest, and it is - being referred to from the filename source. - - >>> make_relative_path('/usr/share/something/a-file.pth', - ... '/usr/share/another-place/src/Directory') - '../another-place/src/Directory' - >>> make_relative_path('/usr/share/something/a-file.pth', - ... '/home/user/src/Directory') - '../../../home/user/src/Directory' - >>> make_relative_path('/usr/share/a-file.pth', '/usr/share/') - './' - """ - source = os.path.dirname(source) - if not dest_is_directory: - dest_filename = os.path.basename(dest) - dest = os.path.dirname(dest) - dest = os.path.normpath(os.path.abspath(dest)) - source = os.path.normpath(os.path.abspath(source)) - dest_parts = dest.strip(os.path.sep).split(os.path.sep) - source_parts = source.strip(os.path.sep).split(os.path.sep) - while dest_parts and source_parts and dest_parts[0] == source_parts[0]: - dest_parts.pop(0) - source_parts.pop(0) - full_parts = ['..']*len(source_parts) + dest_parts - if not dest_is_directory: - full_parts.append(dest_filename) - if not full_parts: - # Special case for the current directory (otherwise it'd be '') - return './' - return os.path.sep.join(full_parts) - - - -############################################################ -## Bootstrap script creation: - -def create_bootstrap_script(extra_text, python_version=''): - """ - Creates a bootstrap script, which is like this script but with - extend_parser, adjust_options, and after_install hooks. - - This returns a string that (written to disk of course) can be used - as a bootstrap script with your own customizations. The script - will be the standard virtualenv.py script, with your extra text - added (your extra text should be Python code). - - If you include these functions, they will be called: - - ``extend_parser(optparse_parser)``: - You can add or remove options from the parser here. - - ``adjust_options(options, args)``: - You can change options here, or change the args (if you accept - different kinds of arguments, be sure you modify ``args`` so it is - only ``[DEST_DIR]``). - - ``after_install(options, home_dir)``: - - After everything is installed, this function is called. This - is probably the function you are most likely to use. An - example would be:: - - def after_install(options, home_dir): - subprocess.call([join(home_dir, 'bin', 'easy_install'), - 'MyPackage']) - subprocess.call([join(home_dir, 'bin', 'my-package-script'), - 'setup', home_dir]) - - This example immediately installs a package, and runs a setup - script from that package. - - If you provide something like ``python_version='2.5'`` then the - script will start with ``#!/usr/bin/env python2.5`` instead of - ``#!/usr/bin/env python``. You can use this when the script must - be run with a particular Python version. - """ - filename = __file__ - if filename.endswith('.pyc'): - filename = filename[:-1] - f = codecs.open(filename, 'r', encoding='utf-8') - content = f.read() - f.close() - py_exe = 'python%s' % python_version - content = (('#!/usr/bin/env %s\n' % py_exe) - + '## WARNING: This file is generated\n' - + content) - return content.replace('##EXT' 'END##', extra_text) - -##EXTEND## - -def convert(s): - b = base64.b64decode(s.encode('ascii')) - return zlib.decompress(b).decode('utf-8') - -##file site.py -SITE_PY = convert(""" -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 -UM3ckC/Q8aP8VLy+mjYY5MxOtAdgjULwf2RtvCc= -""") - -##file ez_setup.py -EZ_SETUP_PY = convert(""" -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 -qL7+C1AkCcw= -""") - -##file distribute_from_egg.py -DISTRIBUTE_FROM_EGG_PY = convert(""" -eJw9j8tqAzEMRfcG/4MgmxQyptkGusonZBmGoGTUGYFfWPKE6dfXTkM3gqt7rh47OKP3NMF3SQFW -LlrRU1zhybpAxoKBlIqcrNnBdRjQP3GTocYfzmNrrCPQPN9iwzpxSQfQhWBi0cL3qtRtYIG/4Mv0 -KApY5hooqrOGQ05FQTaxptF9Fnx16Rq0XofjaE1XGXVxHIWK7j8P8EY/rHndLqQ1a0pe3COFgHFy -hLLdWkDbi/DeEpCjNb3u/zccT2Ob8gtnwVyI -""") - -##file distribute_setup.py -DISTRIBUTE_SETUP_PY = convert(""" -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 -""") - -##file activate.sh -ACTIVATE_SH = convert(""" -eJytVVFvokAQfudXTLEPtTlLeo9tvMSmJpq02hSvl7u2wRUG2QR2DSxSe7n/frOACEVNLlceRHa+ -nfl25pvZDswCnoDPQ4QoTRQsENIEPci4CsBMZBq7CAsuLOYqvmYKTTj3YxnBgiXBudGBjUzBZUJI -BXEqgCvweIyuCjeG4eF2F5x14bcB9KQiQQWrjSddI1/oQIx6SYYeoFjzWIoIhYI1izlbhJjkKO7D -M/QEmKfO9O7WeRo/zr4P7pyHwWxkwitcgwpQ5Ej96OX+PmiFwLeVjFUOrNYKaq1Nud3nR2n8nI2m -k9H0friPTGVsUdptaxGrTEfpNVFEskxpXtUkkCkl1UNF9cgLBkx48J4EXyALuBtAwNYIjF5kcmUU -abMKmMq1ULoiRbgsDEkTSsKSGFCJ6Z8vY/2xYiSacmtyAfCDdCNTVZoVF8vSTQOoEwSnOrngBkws -MYGMBMg8/bMBLSYKS7pYEXP0PqT+ZmBT0Xuy+Pplj5yn4aM9nk72JD8/Wi+Gr98sD9eWSMOwkapD -BbUv91XSvmyVkICt2tmXR4tWmrcUCsjWOpw87YidEC8i0gdTSOFhouJUNxR+4NYBG0MftoCTD9F7 -2rTtxG3oPwY1b2HncYwhrlmj6Wq924xtGDWqfdNxap+OYxplEurnMVo9RWks+rH8qKEtx7kZT5zJ -4H7oOFclrN6uFe+d+nW2aIUsSgs/42EIPuOhXq+jEo3S6tX6w2ilNkDnIpHCWdEQhFgwj9pkk7FN -l/y5eQvRSIQ5+TrL05lewxWpt/Lbhes5cJF3mLET1MGhcKCF+40tNWnUulxrpojwDo2sObdje3Bz -N3QeHqf3D7OjEXMVV8LN3ZlvuzoWHqiUcNKHtwNd0IbvPGKYYM31nPKCgkUILw3KL+Y8l7aO1ArS -Ad37nIU0fCj5NE5gQCuC5sOSu+UdI2NeXg/lFkQIlFpdWVaWZRfvqGiirC9o6liJ9FXGYrSY9mI1 -D/Ncozgn13vJvsznr7DnkJWXsyMH7e42ljdJ+aqNDF1bFnKWFLdj31xtaJYK6EXFgqmV/ymD/ROG -+n8O9H8f5vsGOWXsL1+1k3g= -""") - -##file activate.fish -ACTIVATE_FISH = convert(""" -eJyVVWFv2jAQ/c6vuBoqQVWC9nVSNVGVCaS2VC2rNLWVZZILWAs2s52wVvvxsyEJDrjbmgpK7PP5 -3bt3d22YLbmGlGcIq1wbmCPkGhPYcLMEEsGciwGLDS+YwSjlekngLFVyBe73GXSXxqw/DwbuTS8x -yyKpFr1WG15lDjETQhpQuQBuIOEKY5O9tlppLqxHKSDByjVAPwEy+mXtCq5MzjIUBTCRgEKTKwFG -gpBqxTLYXgN2myspVigMaYF92tZSowGZJf4mFExxNs9Qb614CgZtmH0BpEOn11f0cXI/+za8pnfD -2ZjA1sg9zlV/8QvcMhxbNu0QwgYokn/d+n02nt6Opzcjcnx1vXcIoN74O4ymWQXmHURfJw9jenc/ -vbmb0enj6P5+cuVhqlKm3S0u2XRtRbA2QQAhV7VhBF0rsgUX9Ur1rBUXJgVSy8O751k8mzY5OrKH -RW3eaQhYGTr8hrXO59ALhxQ83mCsDLAid3T72CCSdJhaFE+fXgicXAARUiR2WeVO37gH3oYHzFKo -9k7CaPZ1UeNwH1tWuXA4uFKYYcEa8vaKqXl7q1UpygMPhFLvlVKyNzsSM3S2km7UBOl4xweUXk5u -6e3wZmQ9leY1XE/Ili670tr9g/5POBBpGIJXCCF79L1siarl/dbESa8mD8PL61GpzqpzuMS7tqeB -1YkALrRBloBMbR9yLcVx7frQAgUqR7NZIuzkEu110gbNit1enNs82Rx5utq7Z3prU78HFRgulqNC -OTwbqJa9vkJFclQgZSjbKeBgSsUtCtt9D8OwAbIVJuewQdfvQRaoFE9wd1TmCuRG7OgJ1bVXGHc7 -z5WDL/WW36v2oi37CyVBak61+yPBA9C1qqGxzKQqZ0oPuocU9hpud0PIp8sDHkXR1HKkNlzjuUWA -a0enFUyzOWZA4yXGP+ZMI3Tdt2OuqU/SO4q64526cPE0A7ZyW2PMbWZiZ5HamIZ2RcCKLXhcDl2b -vXL+eccQoRzem80mekPDEiyiWK4GWqZmwxQOmPM0eIfgp1P9cqrBsewR2p/DPMtt+pfcYM+Ls2uh -hALufTAdmGl8B1H3VPd2af8fQAc4PgqjlIBL9cGQqNpXaAwe3LrtVn8AkZTUxg== -""") - -##file activate.csh -ACTIVATE_CSH = convert(""" -eJx9VG1P2zAQ/u5fcYQKNgTNPtN1WxlIQ4KCUEGaxuQ6yYVYSuzKdhqVX7+zk3bpy5YPUXL3PPfc -ne98DLNCWshliVDV1kGCUFvMoJGugMjq2qQIiVSxSJ1cCofD1BYRnOVGV0CfZ0N2DD91DalQSjsw -tQLpIJMGU1euvPe7QeJlkKzgWixlhnAt4aoUVsLnLBiy5NtbJWQ5THX1ZciYKKWwkOFaE04dUm6D -r/zh7pq/3D7Nnid3/HEy+wFHY/gEJydg0aFaQrBFgz1c5DG1IhTs+UZgsBC2GMFBlaeH+8dZXwcW -VPvCjXdlAvCfQsE7al0+07XjZvrSCUevR5dnkVeKlFYZmUztG4BdzL2u9KyLVabTU0bdfg7a0hgs -cSmUg6UwUiQl2iHrcbcVGNvPCiLOe7+cRwG13z9qRGgx2z6DHjfm/Op2yqeT+xvOLzs0PTKHDz2V -tkckFHoQfQRXoGJAj9el0FyJCmEMhzgMS4sB7KPOE2ExoLcSieYwDvR+cP8cg11gKkVJc2wRcm1g -QhYFlXiTaTfO2ki0fQoiFM4tLuO4aZrhOzqR4dIPcWx17hphMBY+Srwh7RTyN83XOWkcSPh1Pg/k -TXX/jbJTbMtUmcxZ+/bbqOsy82suFQg/BhdSOTRhMNBHlUarCpU7JzBhmkKmRejKOQzayQe6MWoa -n1wqWmuh6LZAaHxcdeqIlVLhIBJdO9/kbl0It2oEXQj+eGjJOuvOIR/YGRqvFhttUB2XTvLXYN2H -37CBdbW2W7j2r2+VsCn0doVWcFG1/4y1VwBjfwAyoZhD -""") - -##file activate.bat -ACTIVATE_BAT = convert(""" -eJx9UdEKgjAUfW6wfxjiIH+hEDKUFHSKLCMI7kNOEkIf9P9pTJ3OLJ/03HPPPed4Es9XS9qqwqgT -PbGKKOdXL4aAFS7A4gvAwgijuiKlqOpGlATS2NeMLE+TjJM9RkQ+SmqAXLrBo1LLIeLdiWlD6jZt -r7VNubWkndkXaxg5GO3UaOOKS6drO3luDDiO5my3iA0YAKGzPRV1ack8cOdhysI0CYzIPzjSiH5X -0QcvC8Lfaj0emsVKYF2rhL5L3fCkVjV76kShi59NHwDniAHzkgDgqBcwOgTMx+gDQQqXCw== -""") - -##file deactivate.bat -DEACTIVATE_BAT = convert(""" -eJxzSE3OyFfIT0vj4ipOLVEI8wwKCXX0iXf1C7Pl4spMU0hJTcvMS01RiPf3cYmHyQYE+fsGhCho -cCkAAUibEkTEVhWLMlUlLk6QGixStlyaeCyJDPHw9/Pw93VFsQguim4ZXAJoIUw5DhX47XUM8UCx -EchHtwsohN1bILUgw61c/Vy4AJYPYm4= -""") - -##file activate.ps1 -ACTIVATE_PS = convert(""" -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 -QastYw== -""") - -##file distutils-init.py -DISTUTILS_INIT = convert(""" -eJytV1uL4zYUfvevOE0ottuMW9q3gVDa3aUMXXbLMlDKMBiNrSTqOJKRlMxkf33PkXyRbGe7Dw2E -UXTu37lpxLFV2oIyifAncxmOL0xLIfcG+gv80x9VW6maw7o/CANSWWBwFtqeWMPlGY6qPjV8A0bB -C4eKSTgZ5LRgFeyErMEeOBhbN+Ipgeizhjtnhkn7DdyjuNLPoCS0l/ayQTG0djwZC08cLXozeMss -aG5EzQ0IScpnWtHSTXuxByV/QCmxE7y+eS0uxWeoheaVVfqSJHiU7Mhhi6gULbOHorshkrEnKxpT -0n3A8Y8SMpuwZx6aoix3ouFlmW8gHRSkeSJ2g7hU+kiHLDaQw3bmRDaTGfTnty7gPm0FHbIBg9U9 -oh1kZzAFLaue2R6htPCtAda2nGlDSUJ4PZBgCJBGVcwKTAMz/vJiLD+Oin5Z5QlvDPdulC6EsiyE -NFzb7McNTKJzbJqzphx92VKRFY1idenzmq3K0emRcbWBD0ryqc4NZGmKOOOX9Pz5x+/l27tP797c -f/z0d+4NruGNai8uAM0bfsYaw8itFk8ny41jsfpyO+BWlpqfhcG4yxLdi/0tQqoT4a8Vby382mt8 -p7XSo7aWGdPBc+b6utaBmCQ7rQKQoWtAuthQCiold2KfJIPTT8xwg9blPumc+YDZC/wYGdAyHpJk -vUbHbHWAp5No6pK/WhhLEWrFjUwtPEv1Agf8YmnsuXUQYkeZoHm8ogP16gt2uHoxcEMdf2C6pmbw -hUMsWGhanboh4IzzmsIpWs134jVPqD/c74bZHdY69UKKSn/+KfVhxLgUlToemayLMYQOqfEC61bh -cbhwaqoGUzIyZRFHPmau5juaWqwRn3mpWmoEA5nhzS5gog/5jbcFQqOZvmBasZtwYlG93k5GEiyw -buHhMWLjDarEGpMGB2LFs5nIJkhp/nUmZneFaRth++lieJtHepIvKgx6PJqIlD9X2j6pG1i9x3pZ -5bHuCPFiirGHeO7McvoXkz786GaKVzC9DSpnOxJdc4xm6NSVq7lNEnKdVlnpu9BNYoKX2Iq3wvgh -gGEUM66kK6j4NiyoneuPLSwaCWDxczgaolEWpiMyDVDb7dNuLAbriL8ig8mmeju31oNvQdpnvEPC -1vAXbWacGRVrGt/uXN/gU0CDDwgooKRrHfTBb1/s9lYZ8ZqOBU0yLvpuP6+K9hLFsvIjeNhBi0KL -MlOuWRn3FRwx5oHXjl0YImUx0+gLzjGchrgzca026ETmYJzPD+IpuKzNi8AFn048Thd63OdD86M6 -84zE8yQm0VqXdbbgvub2pKVnS76icBGdeTHHXTKspUmr4NYo/furFLKiMdQzFjHJNcdAnMhltBJK -0/IKX3DVFqvPJ2dLE7bDBkH0l/PJ29074+F0CsGYOxsb7U3myTUncYfXqnLLfa6sJybX4g+hmcjO -kMRBfA1JellfRRKJcyRpxdS4rIl6FdmQCWjo/o9Qz7yKffoP4JHjOvABcRn4CZIT2RH4jnxmfpVG -qgLaAvQBNfuO6X0/Ux02nb4FKx3vgP+XnkX0QW9pLy/NsXgdN24dD3LxO2Nwil7Zlc1dqtP3d7/h -kzp1/+7hGBuY4pk0XD/0Ao/oTe/XGrfyM773aB7iUhgkpy+dwAMalxMP0DrBcsVw/6p25+/hobP9 -GBknrWExDhLJ1bwt1NcCNblaFbMKCyvmX0PeRaQ= -""") - -##file distutils.cfg -DISTUTILS_CFG = convert(""" -eJxNj00KwkAMhfc9xYNuxe4Ft57AjYiUtDO1wXSmNJnK3N5pdSEEAu8nH6lxHVlRhtDHMPATA4uH -xJ4EFmGbvfJiicSHFRzUSISMY6hq3GLCRLnIvSTnEefN0FIjw5tF0Hkk9Q5dRunBsVoyFi24aaLg -9FDOlL0FPGluf4QjcInLlxd6f6rqkgPu/5nHLg0cXCscXoozRrP51DRT3j9QNl99AP53T2Q= -""") - -##file activate_this.py -ACTIVATE_THIS = convert(""" -eJyNU01v2zAMvetXEB4K21jmDOstQA4dMGCHbeihlyEIDMWmG62yJEiKE//7kXKdpN2KzYBt8euR -fKSyLPs8wiEo8wh4wqZTGou4V6Hm0wJa1cSiTkJdr8+GsoTRHuCotBayiWqQEYGtMCgfD1KjGYBe -5a3p0cRKiAe2NtLADikftnDco0ko/SFEVgEZ8aRC5GLux7i3BpSJ6J1H+i7A2CjiHq9z7JRZuuQq -siwTIvpxJYCeuWaBpwZdhB+yxy/eWz+ZvVSU8C4E9FFZkyxFsvCT/ZzL8gcz9aXVE14Yyp2M+2W0 -y7n5mp0qN+avKXvbsyyzUqjeWR8hjGE+2iCE1W1tQ82hsCZN9UzlJr+/e/iab8WfqsmPI6pWeUPd -FrMsd4H/55poeO9n54COhUs+sZNEzNtg/wanpjpuqHJaxs76HtZryI/K3H7KJ/KDIhqcbJ7kI4ar -XL+sMgXnX0D+Te2Iy5xdP8yueSlQB/x/ED2BTAtyE3K4SYUN6AMNfbO63f4lBW3bUJPbTL+mjSxS -PyRfJkZRgj+VbFv+EzHFi5pKwUEepa4JslMnwkowSRCXI+m5XvEOvtuBrxHdhLalG0JofYBok6qj -YdN2dEngUlbC4PG60M1WEN0piu7Nq7on0mgyyUw3iV1etLo6r/81biWdQ9MWHFaePWZYaq+nmp+t -s3az+sj7eA0jfgPfeoN1 -""") - -MH_MAGIC = 0xfeedface -MH_CIGAM = 0xcefaedfe -MH_MAGIC_64 = 0xfeedfacf -MH_CIGAM_64 = 0xcffaedfe -FAT_MAGIC = 0xcafebabe -BIG_ENDIAN = '>' -LITTLE_ENDIAN = '<' -LC_LOAD_DYLIB = 0xc -maxint = majver == 3 and getattr(sys, 'maxsize') or getattr(sys, 'maxint') - - -class fileview(object): - """ - A proxy for file-like objects that exposes a given view of a file. - Modified from macholib. - """ - - def __init__(self, fileobj, start=0, size=maxint): - if isinstance(fileobj, fileview): - self._fileobj = fileobj._fileobj - else: - self._fileobj = fileobj - self._start = start - self._end = start + size - self._pos = 0 - - def __repr__(self): - return '' % ( - self._start, self._end, self._fileobj) - - def tell(self): - return self._pos - - def _checkwindow(self, seekto, op): - if not (self._start <= seekto <= self._end): - raise IOError("%s to offset %d is outside window [%d, %d]" % ( - op, seekto, self._start, self._end)) - - def seek(self, offset, whence=0): - seekto = offset - if whence == os.SEEK_SET: - seekto += self._start - elif whence == os.SEEK_CUR: - seekto += self._start + self._pos - elif whence == os.SEEK_END: - seekto += self._end - else: - raise IOError("Invalid whence argument to seek: %r" % (whence,)) - self._checkwindow(seekto, 'seek') - self._fileobj.seek(seekto) - self._pos = seekto - self._start - - def write(self, bytes): - here = self._start + self._pos - self._checkwindow(here, 'write') - self._checkwindow(here + len(bytes), 'write') - self._fileobj.seek(here, os.SEEK_SET) - self._fileobj.write(bytes) - self._pos += len(bytes) - - def read(self, size=maxint): - assert size >= 0 - here = self._start + self._pos - self._checkwindow(here, 'read') - size = min(size, self._end - here) - self._fileobj.seek(here, os.SEEK_SET) - bytes = self._fileobj.read(size) - self._pos += len(bytes) - return bytes - - -def read_data(file, endian, num=1): - """ - Read a given number of 32-bits unsigned integers from the given file - with the given endianness. - """ - res = struct.unpack(endian + 'L' * num, file.read(num * 4)) - if len(res) == 1: - return res[0] - return res - - -def mach_o_change(path, what, value): - """ - Replace a given name (what) in any LC_LOAD_DYLIB command found in - the given binary with a new name (value), provided it's shorter. - """ - - def do_macho(file, bits, endian): - # Read Mach-O header (the magic number is assumed read by the caller) - cputype, cpusubtype, filetype, ncmds, sizeofcmds, flags = read_data(file, endian, 6) - # 64-bits header has one more field. - if bits == 64: - read_data(file, endian) - # The header is followed by ncmds commands - for n in range(ncmds): - where = file.tell() - # Read command header - cmd, cmdsize = read_data(file, endian, 2) - if cmd == LC_LOAD_DYLIB: - # The first data field in LC_LOAD_DYLIB commands is the - # offset of the name, starting from the beginning of the - # command. - name_offset = read_data(file, endian) - file.seek(where + name_offset, os.SEEK_SET) - # Read the NUL terminated string - load = file.read(cmdsize - name_offset).decode() - load = load[:load.index('\0')] - # If the string is what is being replaced, overwrite it. - if load == what: - file.seek(where + name_offset, os.SEEK_SET) - file.write(value.encode() + '\0'.encode()) - # Seek to the next command - file.seek(where + cmdsize, os.SEEK_SET) - - def do_file(file, offset=0, size=maxint): - file = fileview(file, offset, size) - # Read magic number - magic = read_data(file, BIG_ENDIAN) - if magic == FAT_MAGIC: - # Fat binaries contain nfat_arch Mach-O binaries - nfat_arch = read_data(file, BIG_ENDIAN) - for n in range(nfat_arch): - # Read arch header - cputype, cpusubtype, offset, size, align = read_data(file, BIG_ENDIAN, 5) - do_file(file, offset, size) - elif magic == MH_MAGIC: - do_macho(file, 32, BIG_ENDIAN) - elif magic == MH_CIGAM: - do_macho(file, 32, LITTLE_ENDIAN) - elif magic == MH_MAGIC_64: - do_macho(file, 64, BIG_ENDIAN) - elif magic == MH_CIGAM_64: - do_macho(file, 64, LITTLE_ENDIAN) - - assert(len(what) >= len(value)) - do_file(open(path, 'r+b')) - - -if __name__ == '__main__': - main() - -## TODO: -## Copy python.exe.manifest -## Monkeypatch distutils.sysconfig -- cgit v1.2.1 From b05f70be1296bf9c00af1020faeb8a8209e23066 Mon Sep 17 00:00:00 2001 From: holger krekel Date: Mon, 9 Dec 2013 15:28:06 +0100 Subject: remove training note --- CHANGELOG | 2 +- doc/index.txt | 2 -- tests/test_config.py | 2 +- 3 files changed, 2 insertions(+), 4 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index bcd2387..1bd4117 100755 --- a/CHANGELOG +++ b/CHANGELOG @@ -3,7 +3,7 @@ - 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 virtualenv environments anymore. + support for creating python2.5 based environments anymore. - merged PR81: new option --force-dep which allows to override tox.ini specified dependencies in setuptools-style. diff --git a/doc/index.txt b/doc/index.txt index 3e5aa99..7fcc310 100644 --- 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 `_ , 25-27th November 2013, Leipzig. - vision: standardize testing in Python --------------------------------------------- diff --git a/tests/test_config.py b/tests/test_config.py index 9cd9d44..5d11d88 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -868,7 +868,7 @@ class TestGlobalOptions: 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,jython,pypy" inisource = """ [tox] envlist = %s -- cgit v1.2.1 From 142f0130c09177989c20a4c5527156aab93311b2 Mon Sep 17 00:00:00 2001 From: holger krekel Date: Mon, 9 Dec 2013 15:54:19 +0100 Subject: introduce --no-network switch for running tests --- CHANGELOG | 9 ++++++--- tests/test_venv.py | 6 +++--- tox.ini | 2 +- tox/_pytestplugin.py | 9 +++++++++ 4 files changed, 19 insertions(+), 7 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 1bd4117..09de503 100755 --- a/CHANGELOG +++ b/CHANGELOG @@ -3,7 +3,8 @@ - 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. + 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. @@ -23,8 +24,7 @@ - fix issue126: depend on virtualenv>=1.10.1 so that we can rely (hopefully) on a pip version which supports --pre. (tox by default - uses to --pre). Note that tox also vendors an older virtualenv - for supporting python2.5 -- although the latter will be dropped at some point. + uses to --pre). - 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 @@ -44,6 +44,9 @@ - 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 + 1.6.1 ----- diff --git a/tests/test_venv.py b/tests/test_venv.py index 6f4a06a..ad968d1 100644 --- a/tests/test_venv.py +++ b/tests/test_venv.py @@ -307,11 +307,11 @@ def test_install_command_not_installed(newmocksession): def test_install_python3(tmpdir, newmocksession): - if not py.path.local.sysfind('python3.1'): - py.test.skip("needs python3.1") + if not py.path.local.sysfind('python3.3'): + pytest.skip("needs python3.3") mocksession = newmocksession([], """ [testenv:py123] - basepython=python3.1 + basepython=python3.3 deps= dep1 dep2 diff --git a/tox.ini b/tox.ini index 29525c9..f2fc791 100644 --- a/tox.ini +++ b/tox.ini @@ -23,4 +23,4 @@ setenv= PIP_INSECURE=1 [pytest] rsyncdirs=tests tox - +addopts = -rsxXf diff --git a/tox/_pytestplugin.py b/tox/_pytestplugin.py index f6421f4..4f00d3f 100644 --- a/tox/_pytestplugin.py +++ b/tox/_pytestplugin.py @@ -16,6 +16,12 @@ def pytest_configure(): if 'HUDSON_URL' in os.environ: del os.environ['HUDSON_URL'] + +def pytest_addoption(parser): + parser.addoption("--no-network", action="store_true", + dest="no_network", + help="don't run tests requiring network") + def pytest_report_header(): return "tox comes from: %r" % (tox.__file__) @@ -37,6 +43,8 @@ def newconfig(request, tmpdir): @pytest.fixture def cmd(request): + if request.config.option.no_network: + pytest.skip("--no-network was specified, test cannot run") return Cmd(request) class ReportExpectMock: @@ -156,6 +164,7 @@ class Cmd: self.request = request current = py.path.local() self.request.addfinalizer(current.chdir) + def chdir(self, target): target.chdir() -- cgit v1.2.1 From 37674ee538a97cbc5c29135b139049b4b4bbdf3d Mon Sep 17 00:00:00 2001 From: holger krekel Date: Mon, 9 Dec 2013 16:06:00 +0100 Subject: introduce --sitepackages to force sitepackages=True in all environments. Was wanted by Debian maintainer Barry Warsaw. --- CHANGELOG | 3 +++ tests/test_config.py | 5 +++++ tox/_config.py | 9 +++++++-- 3 files changed, 15 insertions(+), 2 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 09de503..b7687e2 100755 --- a/CHANGELOG +++ b/CHANGELOG @@ -47,6 +47,9 @@ - introduce --no-network to tox's own test suite to skip tests requiring networks +- introduce --sitepackages to force sitepackages=True in all + environments. + 1.6.1 ----- diff --git a/tests/test_config.py b/tests/test_config.py index 5d11d88..b8d0aa3 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -451,6 +451,11 @@ class TestConfigTestEnv: # 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] diff --git a/tox/_config.py b/tox/_config.py index e7ac443..6006fa9 100644 --- a/tox/_config.py +++ b/tox/_config.py @@ -129,7 +129,10 @@ def prepare_parse(pkgname): metavar="REQ", default=None, help="Forces a certain version of one of the dependencies " "when configuring the virtual environment. REQ Examples " - "'pytest<2.4' or 'django>=1.6'.") + "'pytest<2.7' or 'django>=1.6'.") + parser.add_argument("--sitepackages", action="store_true", + help="override sitepackages setting to True in all envs") + parser.add_argument("args", nargs="*", help="additional arguments available to command positional substitution") return parser @@ -355,7 +358,9 @@ class parseini: name = self._replace_forced_dep(name, config) vc.deps.append(DepConfig(name, ixserver)) vc.distribute = reader.getbool(section, "distribute", False) - vc.sitepackages = reader.getbool(section, "sitepackages", False) + vc.sitepackages = self.config.option.sitepackages or \ + reader.getbool(section, "sitepackages", False) + vc.downloadcache = None downloadcache = reader.getdefault(section, "downloadcache") if downloadcache: -- cgit v1.2.1 From b0234b123c89bb51c306200a792d70a8d84ef872 Mon Sep 17 00:00:00 2001 From: holger krekel Date: Mon, 9 Dec 2013 16:11:33 +0100 Subject: also remove py25calls special handling in tests --- tests/test_venv.py | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/tests/test_venv.py b/tests/test_venv.py index ad968d1..e4d0b29 100644 --- a/tests/test_venv.py +++ b/tests/test_venv.py @@ -5,8 +5,6 @@ import os, sys import tox._config from tox._venv import * -py25calls = int(sys.version_info[:2] == (2,5)) - #def test_global_virtualenv(capfd): # v = VirtualEnv() # l = v.list() @@ -126,13 +124,13 @@ def test_install_deps_wildcard(newmocksession): venv = mocksession.getenv("py123") venv.create() l = mocksession._pcalls - assert len(l) == 1 + py25calls + assert len(l) == 1 distshare = venv.session.config.distshare distshare.ensure("dep1-1.0.zip") distshare.ensure("dep1-1.1.zip") venv.install_deps() - assert len(l) == 2 + py25calls + assert len(l) == 2 args = l[-1].args assert l[-1].cwd == venv.envconfig.config.toxinidir assert "pip" in str(args[0]) @@ -159,10 +157,10 @@ def test_install_downloadcache(newmocksession, monkeypatch, tmpdir, envdc): venv = mocksession.getenv("py123") venv.create() l = mocksession._pcalls - assert len(l) == 1 + py25calls + assert len(l) == 1 venv.install_deps() - assert len(l) == 2 + py25calls + assert len(l) == 2 args = l[-1].args assert l[-1].cwd == venv.envconfig.config.toxinidir assert "pip" in str(args[0]) @@ -187,7 +185,7 @@ def test_install_deps_indexserver(newmocksession): venv = mocksession.getenv('py123') venv.create() l = mocksession._pcalls - assert len(l) == 1 + py25calls + assert len(l) == 1 l[:] = [] venv.install_deps() -- cgit v1.2.1 From 910045779225c9891fadff7c1a18572a02df20ae Mon Sep 17 00:00:00 2001 From: holger krekel Date: Mon, 9 Dec 2013 16:16:36 +0100 Subject: bump to 1.7.0.dev1 --- setup.py | 2 +- tox/__init__.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/setup.py b/setup.py index 2d0114c..ecce413 100644 --- a/setup.py +++ b/setup.py @@ -28,7 +28,7 @@ def main(): description='virtualenv-based automation of test activities', long_description=open("README.rst").read(), url='http://tox.testrun.org/', - version='1.6.2.dev2', + version='1.7.0.dev1', license='http://opensource.org/licenses/MIT', platforms=['unix', 'linux', 'osx', 'cygwin', 'win32'], author='holger krekel', diff --git a/tox/__init__.py b/tox/__init__.py index b383e94..0c62362 100644 --- a/tox/__init__.py +++ b/tox/__init__.py @@ -1,5 +1,5 @@ # -__version__ = '1.6.2.dev2' +__version__ = '1.7.0.dev1' class exception: class Error(Exception): -- cgit v1.2.1 From 20f704b92bd964ec88b0a4eefe920a30c360452e Mon Sep 17 00:00:00 2001 From: holger krekel Date: Mon, 9 Dec 2013 16:28:27 +0100 Subject: fix issue105 -- don't depend on an existing HOME directory from tox tests. --- CHANGELOG | 2 ++ tests/test_venv.py | 2 ++ tox/_cmdline.py | 9 +++++++-- 3 files changed, 11 insertions(+), 2 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index b7687e2..e6bd4eb 100755 --- a/CHANGELOG +++ b/CHANGELOG @@ -50,6 +50,8 @@ - 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 --git a/tests/test_venv.py b/tests/test_venv.py index e4d0b29..0ac8f90 100644 --- a/tests/test_venv.py +++ b/tests/test_venv.py @@ -117,6 +117,8 @@ def test_create_sitepackages(monkeypatch, mocksession, newconfig): def test_install_deps_wildcard(newmocksession): mocksession = newmocksession([], """ + [tox] + distshare = {toxworkdir}/distshare [testenv:py123] deps= {distshare}/dep1-* diff --git a/tox/_cmdline.py b/tox/_cmdline.py index a42eb79..7c6cd69 100644 --- a/tox/_cmdline.py +++ b/tox/_cmdline.py @@ -417,8 +417,13 @@ class Session: if sdistfile != sdist_path: self.report.info("copying new sdistfile to %r" % str(sdistfile)) - sdistfile.dirpath().ensure(dir=1) - sdist_path.copy(sdistfile) + try: + sdistfile.dirpath().ensure(dir=1) + except py.error.Error: + self.report.warning("could not copy distfile to %s" % + sdistfile.dirpath()) + else: + sdist_path.copy(sdistfile) return sdist_path def subcommand_test(self): -- cgit v1.2.1 From 117d03ab4ae7cc3d793289a56ab74a9aaf12419f Mon Sep 17 00:00:00 2001 From: holger krekel Date: Tue, 10 Dec 2013 15:57:30 +0100 Subject: small updates to tox/pytest section --- doc/example/pytest.txt | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/doc/example/pytest.txt b/doc/example/pytest.txt index 98fa077..cba8741 100755 --- a/doc/example/pytest.txt +++ b/doc/example/pytest.txt @@ -23,7 +23,7 @@ and the following ``tox.ini`` content:: 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 @@ and the following ``tox.ini`` content:: 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 @@ to make ``tox`` use this feature:: --basetemp={envtmpdir} \ --confcutdir=.. \ -n 3 \ # use three sub processes - [] + {posargs} .. _`listed as a known issue`: @@ -99,16 +99,13 @@ Alternatively, it is possible to use ``changedir`` so that checked-out 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 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. +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. .. _`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 -- cgit v1.2.1 From 1f2afb3f66e812308cd5d30590448a7fdd84295a Mon Sep 17 00:00:00 2001 From: anatoly techtonik Date: Mon, 30 Dec 2013 19:52:09 +0300 Subject: allow to run tox as 'python -m tox', which is handy on Windoze --- tox/__main__.py | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 tox/__main__.py diff --git a/tox/__main__.py b/tox/__main__.py new file mode 100644 index 0000000..d60aab2 --- /dev/null +++ b/tox/__main__.py @@ -0,0 +1,3 @@ +from tox._cmdline import main + +main() -- cgit v1.2.1 From be2f2f58807fbcf15602b169d4057b6589cbbf79 Mon Sep 17 00:00:00 2001 From: Ionel Maries Cristian Date: Sat, 11 Jan 2014 17:59:44 +0200 Subject: Change tox to use the virtualenv bin instead of invoking it with the current interpreter. Virtualenv might have been installed with a completely different interpreter (and might not work at all!) or it could have other issues caused by invoking the module directly (virtualenv 1.11 known to break). Also add a `virtualenvbin` config option in case it need overriding. --- tests/test_venv.py | 7 ++----- tox/_config.py | 2 +- tox/_venv.py | 7 +------ 3 files changed, 4 insertions(+), 12 deletions(-) diff --git a/tests/test_venv.py b/tests/test_venv.py index 0ac8f90..77181d1 100644 --- a/tests/test_venv.py +++ b/tests/test_venv.py @@ -48,11 +48,8 @@ def test_create(monkeypatch, mocksession, newconfig): l = mocksession._pcalls assert len(l) >= 1 args = l[0].args - assert "virtualenv" in str(args[1]) + assert str(args[0]).endswith("virtualenv") if sys.platform != "win32": - # realpath is needed for stuff like the debian symlinks - assert py.path.local(sys.executable).realpath() \ - == py.path.local(args[0]).realpath() #assert Envconfig.toxworkdir in args assert venv.getcommandpath("easy_install", cwd=py.path.local()) interp = venv._getliveconfig().python @@ -321,7 +318,7 @@ def test_install_python3(tmpdir, newmocksession): l = mocksession._pcalls assert len(l) == 1 args = l[0].args - assert str(args[1]).endswith('virtualenv.py') + assert str(args[0]).endswith('virtualenv') l[:] = [] action = mocksession.newaction(venv, "hello") venv._install(["hello"], action=action) diff --git a/tox/_config.py b/tox/_config.py index 6006fa9..928305c 100644 --- a/tox/_config.py +++ b/tox/_config.py @@ -357,6 +357,7 @@ class parseini: ixserver = None name = self._replace_forced_dep(name, config) vc.deps.append(DepConfig(name, ixserver)) + vc.virtualenvbin = reader.getdefault(section, "virtualenvbin", "virtualenv") vc.distribute = reader.getbool(section, "distribute", False) vc.sitepackages = self.config.option.sitepackages or \ reader.getbool(section, "sitepackages", False) @@ -734,4 +735,3 @@ def getcontextname(): if 'HUDSON_URL' in os.environ: return 'jenkins' return None - diff --git a/tox/_venv.py b/tox/_venv.py index 58093fc..4ed7b47 100644 --- a/tox/_venv.py +++ b/tox/_venv.py @@ -178,13 +178,8 @@ class VirtualEnv(object): if action is None: action = self.session.newaction(self, "create") - interpreters = self.envconfig.config.interpreters config_interpreter = self.getsupportedinterpreter() - info = interpreters.get_info(executable=config_interpreter) - f, path, _ = py.std.imp.find_module("virtualenv") - f.close() - venvscript = path.rstrip("co") - args = [config_interpreter, str(venvscript)] + args = [self.envconfig.virtualenvbin] if self.envconfig.distribute: args.append("--distribute") else: -- cgit v1.2.1 From 20eb6ebe1f80228c8691d1d8ecc0de3dc60d9f69 Mon Sep 17 00:00:00 2001 From: Ionel Cristian Maries Date: Sat, 11 Jan 2014 18:09:50 +0200 Subject: Improve assertion to pass on windows. --- tests/test_venv.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_venv.py b/tests/test_venv.py index 77181d1..c2ed371 100644 --- a/tests/test_venv.py +++ b/tests/test_venv.py @@ -48,7 +48,7 @@ def test_create(monkeypatch, mocksession, newconfig): l = mocksession._pcalls assert len(l) >= 1 args = l[0].args - assert str(args[0]).endswith("virtualenv") + assert "virtualenv" in str(args[0]) if sys.platform != "win32": #assert Envconfig.toxworkdir in args assert venv.getcommandpath("easy_install", cwd=py.path.local()) -- cgit v1.2.1 From e29fc94757f9d5d1cf1a1e98d2004cb80d394b4c Mon Sep 17 00:00:00 2001 From: Ionel Cristian Maries Date: Sun, 12 Jan 2014 15:18:23 +0200 Subject: Remove virtualenvbin config option. --- tox/_config.py | 1 - tox/_venv.py | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/tox/_config.py b/tox/_config.py index 928305c..3644d8b 100644 --- a/tox/_config.py +++ b/tox/_config.py @@ -357,7 +357,6 @@ class parseini: ixserver = None name = self._replace_forced_dep(name, config) vc.deps.append(DepConfig(name, ixserver)) - vc.virtualenvbin = reader.getdefault(section, "virtualenvbin", "virtualenv") vc.distribute = reader.getbool(section, "distribute", False) vc.sitepackages = self.config.option.sitepackages or \ reader.getbool(section, "sitepackages", False) diff --git a/tox/_venv.py b/tox/_venv.py index 4ed7b47..3ad0817 100644 --- a/tox/_venv.py +++ b/tox/_venv.py @@ -179,7 +179,7 @@ class VirtualEnv(object): action = self.session.newaction(self, "create") config_interpreter = self.getsupportedinterpreter() - args = [self.envconfig.virtualenvbin] + args = ['virtualenv'] if self.envconfig.distribute: args.append("--distribute") else: -- cgit v1.2.1 From 04cdb6f55ef53f1f4196969d47f6216b03661d74 Mon Sep 17 00:00:00 2001 From: holger krekel Date: Sun, 12 Jan 2014 18:46:49 +0100 Subject: add changelog entry and extend list of contributors --- CHANGELOG | 3 ++- CONTRIBUTORS | 10 ++++++++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/CHANGELOG b/CHANGELOG index e6bd4eb..284a310 100755 --- a/CHANGELOG +++ b/CHANGELOG @@ -24,7 +24,8 @@ - fix issue126: depend on virtualenv>=1.10.1 so that we can rely (hopefully) on a pip version which supports --pre. (tox by default - uses to --pre). + uses to --pre). also merged in PR84 so that we now call "virtualenv" + directly instead of looking up interpreters. Thanks Ionel Maries Cristian. - 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 diff --git a/CONTRIBUTORS b/CONTRIBUTORS index 9d8784a..a9ac89a 100644 --- a/CONTRIBUTORS +++ b/CONTRIBUTORS @@ -12,3 +12,13 @@ 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 -- cgit v1.2.1 From b33b1f5d79634e4b214d3a8c7ca110cd4b77349b Mon Sep 17 00:00:00 2001 From: holger krekel Date: Thu, 23 Jan 2014 08:48:15 +0100 Subject: fix issue140: depend on virtualenv>=1.11.1 --- CHANGELOG | 10 ++++++++-- setup.py | 4 ++-- tox.ini | 5 +++++ tox/__init__.py | 2 +- tox/_venv.py | 2 -- 5 files changed, 16 insertions(+), 7 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 284a310..5964fe1 100755 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,6 +1,11 @@ -1.6.2.dev +1.7.0.dev --------- +- 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 @@ -22,10 +27,11 @@ to allow installation of tox via easy_install/eggs. Thanks Jenisys. -- fix issue126: depend on virtualenv>=1.10.1 so that we can rely +- fix issue126: depend on virtualenv>=1.11.1 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 diff --git a/setup.py b/setup.py index ecce413..b5f43da 100644 --- a/setup.py +++ b/setup.py @@ -18,7 +18,7 @@ class Tox(TestCommand): def main(): version = sys.version_info[:2] - install_requires = ['virtualenv>=1.10.1', 'py>=1.4.17', ] + install_requires = ['virtualenv>=1.11.1', 'py>=1.4.17', ] if version < (2, 7) or (3, 0) <= version <= (3, 1): install_requires += ['argparse'] if version < (2,6): @@ -28,7 +28,7 @@ def main(): description='virtualenv-based automation of test activities', long_description=open("README.rst").read(), url='http://tox.testrun.org/', - version='1.7.0.dev1', + version='1.7.0.dev2', license='http://opensource.org/licenses/MIT', platforms=['unix', 'linux', 'osx', 'cygwin', 'win32'], author='holger krekel', diff --git a/tox.ini b/tox.ini index f2fc791..58858d3 100644 --- a/tox.ini +++ b/tox.ini @@ -8,6 +8,11 @@ commands=echo {posargs} commands=py.test --junitxml={envlogdir}/junit-{envname}.xml {posargs} deps=pytest>=2.3.5 +[testenv:x] +setenv= + HELLO=echo hello world +commands={env:HELLO} + [testenv:docs] basepython=python changedir=doc diff --git a/tox/__init__.py b/tox/__init__.py index 0c62362..b0e2267 100644 --- a/tox/__init__.py +++ b/tox/__init__.py @@ -1,5 +1,5 @@ # -__version__ = '1.7.0.dev1' +__version__ = '1.7.0.dev2' class exception: class Error(Exception): diff --git a/tox/_venv.py b/tox/_venv.py index 3ad0817..f8282d8 100644 --- a/tox/_venv.py +++ b/tox/_venv.py @@ -268,8 +268,6 @@ class VirtualEnv(object): extraenv=None): argv = self.envconfig.install_command[:] # use pip-script on win32 to avoid the executable locking - if argv[0] == "pip" and sys.platform == "win32": - argv[0] = "pip-script.py" i = argv.index('{packages}') argv[i:i+1] = packages if '{opts}' in argv: -- cgit v1.2.1 From 6e0c35f06044848b7d2015c36b77c25ae2576025 Mon Sep 17 00:00:00 2001 From: holger krekel Date: Wed, 29 Jan 2014 14:03:13 +0100 Subject: try to go for 1.7.0 --- CHANGELOG | 4 ++-- README.rst | 2 +- setup.py | 4 ++-- tox.ini | 5 ----- tox/__init__.py | 2 +- 5 files changed, 6 insertions(+), 11 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 5964fe1..2fe431f 100755 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,4 +1,4 @@ -1.7.0.dev +1.7.0 --------- - don't lookup "pip-script" anymore but rather just "pip" on windows @@ -27,7 +27,7 @@ to allow installation of tox via easy_install/eggs. Thanks Jenisys. -- fix issue126: depend on virtualenv>=1.11.1 so that we can rely +- 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. diff --git a/README.rst b/README.rst index 0fb6c68..2beeadd 100644 --- a/README.rst +++ b/README.rst @@ -21,5 +21,5 @@ For more information and the repository please checkout: have fun, -holger krekel, May 2013 +holger krekel, January 2014 diff --git a/setup.py b/setup.py index b5f43da..5a61aa1 100644 --- a/setup.py +++ b/setup.py @@ -18,7 +18,7 @@ class Tox(TestCommand): def main(): version = sys.version_info[:2] - install_requires = ['virtualenv>=1.11.1', 'py>=1.4.17', ] + install_requires = ['virtualenv>=1.11.2', 'py>=1.4.17', ] if version < (2, 7) or (3, 0) <= version <= (3, 1): install_requires += ['argparse'] if version < (2,6): @@ -28,7 +28,7 @@ def main(): description='virtualenv-based automation of test activities', long_description=open("README.rst").read(), url='http://tox.testrun.org/', - version='1.7.0.dev2', + version='1.7.0', license='http://opensource.org/licenses/MIT', platforms=['unix', 'linux', 'osx', 'cygwin', 'win32'], author='holger krekel', diff --git a/tox.ini b/tox.ini index 58858d3..f2fc791 100644 --- a/tox.ini +++ b/tox.ini @@ -8,11 +8,6 @@ commands=echo {posargs} commands=py.test --junitxml={envlogdir}/junit-{envname}.xml {posargs} deps=pytest>=2.3.5 -[testenv:x] -setenv= - HELLO=echo hello world -commands={env:HELLO} - [testenv:docs] basepython=python changedir=doc diff --git a/tox/__init__.py b/tox/__init__.py index b0e2267..5ff0e85 100644 --- a/tox/__init__.py +++ b/tox/__init__.py @@ -1,5 +1,5 @@ # -__version__ = '1.7.0.dev2' +__version__ = '1.7.0' class exception: class Error(Exception): -- cgit v1.2.1 -- cgit v1.2.1 From 5f34f7e67420d51cc13566e2132462308cd263ca Mon Sep 17 00:00:00 2001 From: holger krekel Date: Wed, 29 Jan 2014 14:24:11 +0100 Subject: fix doc version --- doc/conf.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/conf.py b/doc/conf.py index d2f74ed..fda4ac9 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -48,7 +48,7 @@ copyright = u'2013, holger krekel and others' # built documents. # # The short X.Y version. -release = version = "1.6.1" +release = version = "1.7.0" # The full version, including alpha/beta/rc tags. # The language for content autogenerated by Sphinx. Refer to documentation -- cgit v1.2.1 From b77b75f4655480b32475f2bb1efaa592516db30c Mon Sep 17 00:00:00 2001 From: Clark Boylan Date: Fri, 7 Feb 2014 19:38:24 -0800 Subject: Fix command expansion and parsing. Tox testenv commands are parsed to expand variable substitutions and construct the argv list that will be passed to exec. Prior to this commit this parsing ate quotes surrounding variables and treated multiword variables as single argv items. Neither behavior was correct. To fix this create the expanded command before handing it off to shlex to do the tokenization of the argv list. Doing the parsing in this order ensures it is correct. --- tests/test_config.py | 4 ++-- tox/_config.py | 41 +++++++++++++++++++++++------------------ 2 files changed, 25 insertions(+), 20 deletions(-) diff --git a/tests/test_config.py b/tests/test_config.py index b8d0aa3..7ffc4f5 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -278,7 +278,7 @@ class TestIniParser: # "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): @@ -304,7 +304,7 @@ class TestIniParser: # "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): diff --git a/tox/_config.py b/tox/_config.py index 3644d8b..7639f63 100644 --- a/tox/_config.py +++ b/tox/_config.py @@ -527,30 +527,35 @@ class IniReader: def _processcommand(self, command): posargs = getattr(self, "posargs", None) - # special treat posargs which might contain multiple arguments - # in their defaults + # Iterate through each word of the command substituting as + # appropriate to construct the new command string. This + # string is then broken up into exec argv components using + # shlex. newcommand = "" for word in CommandParser(command).words(): - if word.startswith("{posargs:") and word.endswith("}"): + if word == "{posargs}" or word == "[]": if posargs: - word = "{posargs}" + newcommand += " ".join(posargs) + continue + elif word.startswith("{posargs:") and word.endswith("}"): + if posargs: + newcommand += " ".join(posargs) + continue else: word = word[9:-1] - newcommand += word - - # now we can properly parse the command - argv = [] - for arg in shlex.split(newcommand): - if arg in ('[]', "{posargs}"): - if posargs: - argv.extend(posargs) - continue new_arg = "" - for word in CommandParser(arg).words(): - new_word = self._replace(word) - new_word = self._replace(new_word) - new_arg += new_word - argv.append(new_arg) + new_word = self._replace(word) + new_word = self._replace(new_word) + new_arg += new_word + newcommand += new_arg + + # Construct shlex object that will not escape any values, + # use all values as is in argv. + shlexer = shlex.shlex(newcommand, posix=True) + shlexer.whitespace_split = True + shlexer.escape = '' + shlexer.commenters = '' + argv = list(shlexer) return argv def getargv(self, section, name, default=None, replace=True): -- cgit v1.2.1 From 23380da3f729a37106cc6ca2023429bdb547c780 Mon Sep 17 00:00:00 2001 From: Clark Boylan Date: Fri, 7 Feb 2014 20:33:48 -0800 Subject: Add tests for posargs quoting. Two new tests ensure posargs quoting works as expected. When {posargs} is surrounded by quotes in the commands list the contents of posargs should be a single entry in the argv list for the commands. When the posargs themselves include quotes the same behavior should occur. --- tests/test_config.py | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/tests/test_config.py b/tests/test_config.py index 7ffc4f5..93d7659 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -346,6 +346,34 @@ class TestIniParser: assert argvlist[0] == ["cmd1"] assert argvlist[1] == ["cmd2", "value2", "other"] + 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(""" -- cgit v1.2.1 From 7e202871837f63b0642073d5b659205da566e569 Mon Sep 17 00:00:00 2001 From: Morgan Fainberg Date: Mon, 10 Mar 2014 13:39:48 -0700 Subject: Support optional ENV variable substitution in tox.ini Add in support for optional ENV variable substitutions where instead of raising an error if the environmental variable does not exist in os.environ, the value is substituted with an empty string. --- doc/config.txt | 12 ++++++++++++ tests/test_config.py | 13 +++++++++++++ tox/_config.py | 18 +++++++++++++----- 3 files changed, 38 insertions(+), 5 deletions(-) diff --git a/doc/config.txt b/doc/config.txt index 8eb8f3a..971ca68 100644 --- a/doc/config.txt +++ b/doc/config.txt @@ -292,6 +292,18 @@ then the value will be retrieved as ``os.environ['KEY']`` and raise an Error if the environment variable does not exist. + +optional environment variable substitutions +++++++++++++++++++++++++++++++++++ + +If you specify a substitution string like this:: + + {optionalenv:KEY} + +then the value will be retrieved as ``os.environ['KEY']`` +and replace with '' if the environment variable does not +exist + .. _`command positional substitution`: .. _`positional substitution`: diff --git a/tests/test_config.py b/tests/test_config.py index b8d0aa3..419d021 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -242,6 +242,19 @@ class TestIniParser: py.test.raises(tox.exception.ConfigError, 'reader.getdefault("section", "key2")') + def test_getdefault_environment_optional_sub(self, monkeypatch, newconfig): + monkeypatch.setenv("KEY1", "hello") + config = newconfig(""" + [section] + key1={optionalenv:KEY1} + key2={optionalenv:KEY2} + """) + reader = IniReader(config._cfg) + x = reader.getdefault("section", "key1") + assert x == "hello" + x = reader.getdefault("section", "key2") + assert x == "" + def test_getdefault_other_section_substitution(self, newconfig): config = newconfig(""" [section] diff --git a/tox/_config.py b/tox/_config.py index 3644d8b..4f6fa33 100644 --- a/tox/_config.py +++ b/tox/_config.py @@ -595,18 +595,25 @@ class IniReader: #print "getdefault", section, name, "returned", repr(x) return x - def _replace_env(self, match): + def _do_replace_env(self, match, error=True): envkey = match.group('substitution_value') if not envkey: raise tox.exception.ConfigError( 'env: requires an environment variable name') if not envkey in os.environ: - raise tox.exception.ConfigError( - "substitution env:%r: unkown environment variable %r" % - (envkey, envkey)) + if error: + raise tox.exception.ConfigError( + "substitution env:%r: unkown environment variable %r" % + (envkey, envkey)) + + return os.environ.get(envkey, '') + + def _replace_env(self, match): + return self._do_replace_env(match) - return os.environ[envkey] + def _replace_env_no_error(self, match): + return self._do_replace_env(match, error=False) def _substitute_from_other_section(self, key): if key.startswith("[") and "]" in key: @@ -647,6 +654,7 @@ class IniReader: handlers = { 'env' : self._replace_env, + 'optionalenv' : self._replace_env_no_error, None : self._replace_substitution, } try: -- cgit v1.2.1 From ae737eac6e792cbc6f05a527f9398876f84170c6 Mon Sep 17 00:00:00 2001 From: Morgan Fainberg Date: Wed, 19 Mar 2014 00:18:30 -0700 Subject: Make optionalenv defenv to support default values Change the 'optionalenv' substitution to 'defenv' and support providing a default substitution value. Format is "defenv:DEFAULTVALUE:KEY". --- doc/config.txt | 18 +++++++++++++----- tests/test_config.py | 9 ++++++--- tox/_config.py | 31 +++++++++++++++++++------------ 3 files changed, 38 insertions(+), 20 deletions(-) diff --git a/doc/config.txt b/doc/config.txt index 971ca68..5a9134d 100644 --- a/doc/config.txt +++ b/doc/config.txt @@ -293,16 +293,24 @@ and raise an Error if the environment variable does not exist. -optional environment variable substitutions -++++++++++++++++++++++++++++++++++ +environment variable substitutions with default values +++++++++++++++++++++++++++++++++++++++++++++++++++++++ + +If you specify a substitution string like this:: + + {defenv:DEFAULTVALUE:KEY} + +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:: - {optionalenv:KEY} + {defenv::KEY} then the value will be retrieved as ``os.environ['KEY']`` -and replace with '' if the environment variable does not -exist +and replace with and empty string if the environment variable does not +exist. .. _`command positional substitution`: .. _`positional substitution`: diff --git a/tests/test_config.py b/tests/test_config.py index 419d021..3084001 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -242,17 +242,20 @@ class TestIniParser: py.test.raises(tox.exception.ConfigError, 'reader.getdefault("section", "key2")') - def test_getdefault_environment_optional_sub(self, monkeypatch, newconfig): + def test_getdefault_environment_substitution_with_default(self, monkeypatch, newconfig): monkeypatch.setenv("KEY1", "hello") config = newconfig(""" [section] - key1={optionalenv:KEY1} - key2={optionalenv:KEY2} + key1={defenv:DEFAULT_VALUE:KEY1} + key2={defenv:DEFAULT_VALUE:KEY2} + key3={defenv::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): diff --git a/tox/_config.py b/tox/_config.py index 4f6fa33..468af5f 100644 --- a/tox/_config.py +++ b/tox/_config.py @@ -595,25 +595,32 @@ class IniReader: #print "getdefault", section, name, "returned", repr(x) return x - def _do_replace_env(self, match, error=True): - envkey = match.group('substitution_value') + def _do_replace_env(self, envkey, default=None): if not envkey: raise tox.exception.ConfigError( 'env: requires an environment variable name') - if not envkey in os.environ: - if error: - raise tox.exception.ConfigError( - "substitution env:%r: unkown environment variable %r" % - (envkey, envkey)) + if not envkey in os.environ and default is None: + raise tox.exception.ConfigError( + "substitution env:%r: unkown environment variable %r" % + (envkey, envkey)) - return os.environ.get(envkey, '') + return os.environ.get(envkey, default) def _replace_env(self, match): - return self._do_replace_env(match) + envkey = match.group('substitution_value') + return self._do_replace_env(envkey) + + def _replace_env_with_default(self, match): + envkey = match.group('substitution_value') + try: + default, envkey = envkey.split(':', 1) + except ValueError: + raise tox.exception.ConfigError( + "substitution 'defenv:%r': malformed, expected " + "'defenv:DEFAULTVALUE:KEY'" % match) - def _replace_env_no_error(self, match): - return self._do_replace_env(match, error=False) + return self._do_replace_env(envkey, default=default) def _substitute_from_other_section(self, key): if key.startswith("[") and "]" in key: @@ -654,7 +661,7 @@ class IniReader: handlers = { 'env' : self._replace_env, - 'optionalenv' : self._replace_env_no_error, + 'defenv' : self._replace_env_with_default, None : self._replace_substitution, } try: -- cgit v1.2.1 From 8c396daf515e3a0371a41b94225fc42091252e56 Mon Sep 17 00:00:00 2001 From: Jurko Gospodneti? Date: Sun, 23 Mar 2014 14:11:12 +0100 Subject: fix documentation typo --- doc/example/devenv.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/example/devenv.txt b/doc/example/devenv.txt index 571f89c..19981ac 100644 --- a/doc/example/devenv.txt +++ b/doc/example/devenv.txt @@ -47,7 +47,7 @@ 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, and use +as ``tox.ini`` file. We also want to specify Python version to be 2.7, and use ``setup.py develop`` to work in development mode instead of building and installing an ``sdist`` package. -- cgit v1.2.1 From f89b6912886ac8835a2489c77e5891e8316870eb Mon Sep 17 00:00:00 2001 From: Jurko Gospodneti? Date: Sun, 23 Mar 2014 14:48:08 +0100 Subject: stylistic documentation wording cleanup --- doc/example/devenv.txt | 81 ++++++++++++++++++++++++++++++-------------------- 1 file changed, 49 insertions(+), 32 deletions(-) diff --git a/doc/example/devenv.txt b/doc/example/devenv.txt index 19981ac..f4afc37 100644 --- 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. + +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``. -Configuration -------------- +Example 1: Basic scenario +========================= -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``:: +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 - commands = - deps = +In it we state: -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. +- 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 = -Creating development environment --------------------------------- -Once ``devenv`` section is defined we can instrument tox to create our -environment:: +Step 2 - Create the development 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. + +Example 2: A more complex scenario +================================== -Full configuration example --------------------------- +Let us say we want our project development environment to: -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 specify Python version to be 2.7, and use -``setup.py develop`` to work in development mode instead of building and -installing an ``sdist`` package. +- 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 example configuration for that:: +Here is an example configuration for the described scenario:: [testenv:devenv] envdir = devenv basepython = python2.7 usedevelop = True deps = -rrequirements.txt - -- cgit v1.2.1 From 361622b71bd5e9e2427045e614eaaf7a30935861 Mon Sep 17 00:00:00 2001 From: Jurko Gospodneti? Date: Sun, 23 Mar 2014 16:28:36 +0100 Subject: fix documentation typo --- doc/config.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/config.txt b/doc/config.txt index 8eb8f3a..094d7a5 100644 --- a/doc/config.txt +++ b/doc/config.txt @@ -202,7 +202,7 @@ Complete list of settings that you can put into ``testenv*`` sections: .. 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:: -- cgit v1.2.1 From 252b8ca8aec48032b271f91c10086faf91776fae Mon Sep 17 00:00:00 2001 From: Jurko Gospodneti? Date: Sun, 23 Mar 2014 16:29:11 +0100 Subject: trim trailing spaces in doc/config.txt --- doc/config.txt | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/doc/config.txt b/doc/config.txt index 094d7a5..34b88af 100644 --- a/doc/config.txt +++ b/doc/config.txt @@ -80,7 +80,7 @@ Complete list of settings that you can put into ``testenv*`` sections: .. 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,12 +94,12 @@ Complete list of settings that you can put into ``testenv*`` sections: 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 @@ -109,7 +109,7 @@ Complete list of settings that you can put into ``testenv*`` sections: 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 +136,7 @@ Complete list of settings that you can put into ``testenv*`` sections: 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 +164,17 @@ Complete list of settings that you can put into ``testenv*`` sections: **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 +182,7 @@ Complete list of settings that you can put into ``testenv*`` sections: 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 @@ -347,7 +347,7 @@ You can put default values in one section and reference them in others to avoid pytest mock pytest-xdist - + [testenv:dulwich] deps = dulwich -- cgit v1.2.1 From d36aa0c35ff63963a5b5866e5039305ddf2954ae Mon Sep 17 00:00:00 2001 From: holger krekel Date: Mon, 24 Mar 2014 13:27:39 +0100 Subject: fix issue162: don't list python 2.5 as compatibiliy/supported --- CHANGELOG | 5 +++++ doc/config.txt | 8 -------- doc/index.txt | 5 ++--- doc/install.txt | 2 +- 4 files changed, 8 insertions(+), 12 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 2fe431f..b6ecabd 100755 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,8 @@ +XXX +--------- + +- fix issue162: don't list python 2.5 as compatibiliy/supported + 1.7.0 --------- diff --git a/doc/config.txt b/doc/config.txt index 34b88af..28068a3 100644 --- a/doc/config.txt +++ b/doc/config.txt @@ -97,14 +97,6 @@ Complete list of settings that you can put into ``testenv*`` sections: pip install --pre {opts} {packages} - **default on environments using python2.5**:: - - 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:: whitelist_externals=MULTI-LINE-LIST each line specifies a command name (in glob-style pattern format) diff --git a/doc/index.txt b/doc/index.txt index 7fcc310..c1baae2 100644 --- a/doc/index.txt +++ b/doc/index.txt @@ -68,9 +68,8 @@ Current features 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**: Python-2.6 up to Python-3.3, + Jython and pypy_ support. * **cross-platform**: Windows and Unix style environments diff --git a/doc/install.txt b/doc/install.txt index 5914ac1..2851571 100644 --- a/doc/install.txt +++ b/doc/install.txt @@ -4,7 +4,7 @@ tox installation 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 -- cgit v1.2.1 From 19b1d324dbf3207bb71b1c89ee07d014d9ec8f01 Mon Sep 17 00:00:00 2001 From: Ionel Maries Cristian Date: Tue, 25 Mar 2014 02:58:56 +0200 Subject: Backed out changeset: 0b3ce1895d03 --- tox/_config.py | 1 + tox/_venv.py | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/tox/_config.py b/tox/_config.py index 3644d8b..928305c 100644 --- a/tox/_config.py +++ b/tox/_config.py @@ -357,6 +357,7 @@ class parseini: ixserver = None name = self._replace_forced_dep(name, config) vc.deps.append(DepConfig(name, ixserver)) + vc.virtualenvbin = reader.getdefault(section, "virtualenvbin", "virtualenv") vc.distribute = reader.getbool(section, "distribute", False) vc.sitepackages = self.config.option.sitepackages or \ reader.getbool(section, "sitepackages", False) diff --git a/tox/_venv.py b/tox/_venv.py index f8282d8..00f528a 100644 --- a/tox/_venv.py +++ b/tox/_venv.py @@ -179,7 +179,7 @@ class VirtualEnv(object): action = self.session.newaction(self, "create") config_interpreter = self.getsupportedinterpreter() - args = ['virtualenv'] + args = [self.envconfig.virtualenvbin] if self.envconfig.distribute: args.append("--distribute") else: -- cgit v1.2.1 From 451f2911246c56f77bbd8eacddaabb74a36bdf40 Mon Sep 17 00:00:00 2001 From: Ionel Maries Cristian Date: Tue, 25 Mar 2014 02:59:09 +0200 Subject: Backed out changeset: f54ba7b918e8 --- tests/test_venv.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_venv.py b/tests/test_venv.py index c2ed371..77181d1 100644 --- a/tests/test_venv.py +++ b/tests/test_venv.py @@ -48,7 +48,7 @@ def test_create(monkeypatch, mocksession, newconfig): l = mocksession._pcalls assert len(l) >= 1 args = l[0].args - assert "virtualenv" in str(args[0]) + assert str(args[0]).endswith("virtualenv") if sys.platform != "win32": #assert Envconfig.toxworkdir in args assert venv.getcommandpath("easy_install", cwd=py.path.local()) -- cgit v1.2.1 From 9e66006ad8071b286b8f4dcaa02113987b14ef02 Mon Sep 17 00:00:00 2001 From: Ionel Maries Cristian Date: Tue, 25 Mar 2014 02:59:17 +0200 Subject: Backed out changeset: 228071477cfb --- tests/test_venv.py | 7 +++++-- tox/_config.py | 2 +- tox/_venv.py | 7 ++++++- 3 files changed, 12 insertions(+), 4 deletions(-) diff --git a/tests/test_venv.py b/tests/test_venv.py index 77181d1..0ac8f90 100644 --- a/tests/test_venv.py +++ b/tests/test_venv.py @@ -48,8 +48,11 @@ def test_create(monkeypatch, mocksession, newconfig): l = mocksession._pcalls assert len(l) >= 1 args = l[0].args - assert str(args[0]).endswith("virtualenv") + assert "virtualenv" in str(args[1]) if sys.platform != "win32": + # realpath is needed for stuff like the debian symlinks + assert py.path.local(sys.executable).realpath() \ + == py.path.local(args[0]).realpath() #assert Envconfig.toxworkdir in args assert venv.getcommandpath("easy_install", cwd=py.path.local()) interp = venv._getliveconfig().python @@ -318,7 +321,7 @@ def test_install_python3(tmpdir, newmocksession): l = mocksession._pcalls assert len(l) == 1 args = l[0].args - assert str(args[0]).endswith('virtualenv') + assert str(args[1]).endswith('virtualenv.py') l[:] = [] action = mocksession.newaction(venv, "hello") venv._install(["hello"], action=action) diff --git a/tox/_config.py b/tox/_config.py index 928305c..6006fa9 100644 --- a/tox/_config.py +++ b/tox/_config.py @@ -357,7 +357,6 @@ class parseini: ixserver = None name = self._replace_forced_dep(name, config) vc.deps.append(DepConfig(name, ixserver)) - vc.virtualenvbin = reader.getdefault(section, "virtualenvbin", "virtualenv") vc.distribute = reader.getbool(section, "distribute", False) vc.sitepackages = self.config.option.sitepackages or \ reader.getbool(section, "sitepackages", False) @@ -735,3 +734,4 @@ def getcontextname(): if 'HUDSON_URL' in os.environ: return 'jenkins' return None + diff --git a/tox/_venv.py b/tox/_venv.py index 00f528a..1013460 100644 --- a/tox/_venv.py +++ b/tox/_venv.py @@ -178,8 +178,13 @@ class VirtualEnv(object): if action is None: action = self.session.newaction(self, "create") + interpreters = self.envconfig.config.interpreters config_interpreter = self.getsupportedinterpreter() - args = [self.envconfig.virtualenvbin] + info = interpreters.get_info(executable=config_interpreter) + f, path, _ = py.std.imp.find_module("virtualenv") + f.close() + venvscript = path.rstrip("co") + args = [config_interpreter, str(venvscript)] if self.envconfig.distribute: args.append("--distribute") else: -- cgit v1.2.1 From 447745c5ab210cc96fb3d0eccbcca7013c7757f5 Mon Sep 17 00:00:00 2001 From: Ionel Maries Cristian Date: Tue, 25 Mar 2014 03:19:30 +0200 Subject: Refix virtualenv calling - use `python -m virtualenv` instead. --- tests/test_venv.py | 2 +- tox/_venv.py | 7 +------ 2 files changed, 2 insertions(+), 7 deletions(-) diff --git a/tests/test_venv.py b/tests/test_venv.py index 0ac8f90..07b89f4 100644 --- a/tests/test_venv.py +++ b/tests/test_venv.py @@ -321,7 +321,7 @@ def test_install_python3(tmpdir, newmocksession): l = mocksession._pcalls assert len(l) == 1 args = l[0].args - assert str(args[1]).endswith('virtualenv.py') + assert str(args[1]).endswith('virtualenv') l[:] = [] action = mocksession.newaction(venv, "hello") venv._install(["hello"], action=action) diff --git a/tox/_venv.py b/tox/_venv.py index 1013460..d0f2e64 100644 --- a/tox/_venv.py +++ b/tox/_venv.py @@ -178,13 +178,8 @@ class VirtualEnv(object): if action is None: action = self.session.newaction(self, "create") - interpreters = self.envconfig.config.interpreters config_interpreter = self.getsupportedinterpreter() - info = interpreters.get_info(executable=config_interpreter) - f, path, _ = py.std.imp.find_module("virtualenv") - f.close() - venvscript = path.rstrip("co") - args = [config_interpreter, str(venvscript)] + args = [sys.executable, '-mvirtualenv'] if self.envconfig.distribute: args.append("--distribute") else: -- cgit v1.2.1 From 8dc60bce471a2da03d28c9a6d307ba2784dd3c6a Mon Sep 17 00:00:00 2001 From: holger krekel Date: Tue, 25 Mar 2014 07:28:38 +0100 Subject: 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. --- CHANGELOG | 4 ++++ CONTRIBUTORS | 1 + 2 files changed, 5 insertions(+) diff --git a/CHANGELOG b/CHANGELOG index b6ecabd..5357037 100755 --- a/CHANGELOG +++ b/CHANGELOG @@ -3,6 +3,10 @@ XXX - 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 --------- diff --git a/CONTRIBUTORS b/CONTRIBUTORS index a9ac89a..0244c2b 100644 --- a/CONTRIBUTORS +++ b/CONTRIBUTORS @@ -22,3 +22,4 @@ Anthon van der Neuth Matt Good Mattieu Agopian Asmund Grammeltwedt +Ionel Maries Cristian -- cgit v1.2.1 From b8da2d118ee1ea618a8981c7dadd8b295f43ef23 Mon Sep 17 00:00:00 2001 From: schlamar Date: Tue, 25 Mar 2014 08:44:38 +0100 Subject: Limit PYTHONHASHSEED to 1024 on Windows to prevent possible MemoryErrors. See http://bugs.python.org/issue20954. --- tox/_config.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/tox/_config.py b/tox/_config.py index 3644d8b..9e16b7d 100644 --- a/tox/_config.py +++ b/tox/_config.py @@ -123,7 +123,8 @@ def prepare_parse(pkgname): parser.add_argument("--hashseed", action="store", metavar="SEED", default=None, help="set PYTHONHASHSEED to SEED before running commands. " - "Defaults to a random integer in the range 1 to 4294967295. " + "Defaults to a random integer in the range [1, 4294967295] " + "([1, 1024] on Windows). " "Passing 'noset' suppresses this behavior.") parser.add_argument("--force-dep", action="append", metavar="REQ", default=None, @@ -200,7 +201,10 @@ def get_homedir(): return None def make_hashseed(): - return str(random.randint(1, 4294967295)) + max_seed = 4294967295 + if sys.platform == 'win32': + max_seed = 1024 + return str(random.randint(1, max_seed)) class parseini: def __init__(self, config, inipath): -- cgit v1.2.1 From ba555c4fd155dd9fc13d1cb988d9af56a56cae57 Mon Sep 17 00:00:00 2001 From: schlamar Date: Tue, 25 Mar 2014 08:51:10 +0100 Subject: Restrict PYTHONHASHSEED limitation on Windows to Python < 3.4. --- tox/_config.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox/_config.py b/tox/_config.py index 9e16b7d..23d86f4 100644 --- a/tox/_config.py +++ b/tox/_config.py @@ -202,7 +202,7 @@ def get_homedir(): def make_hashseed(): max_seed = 4294967295 - if sys.platform == 'win32': + if sys.platform == 'win32' and sys.version_info < (3, 4): max_seed = 1024 return str(random.randint(1, max_seed)) -- cgit v1.2.1 From 7a61fdad07cd778602f687f085eb15e235d06198 Mon Sep 17 00:00:00 2001 From: Morgan Fainberg Date: Tue, 25 Mar 2014 15:29:38 -0700 Subject: Make optional env replacements use the ``env`` keyword The ':' character is a reserved character in shell, meaning it cannot be used as part of the ENV variable name. If the ENV key has a ':' in it split on the ':' and use the second value as the default instead of raising an exception. --- doc/config.txt | 4 ++-- tests/test_config.py | 6 +++--- tox/_config.py | 29 +++++++++++------------------ 3 files changed, 16 insertions(+), 23 deletions(-) diff --git a/doc/config.txt b/doc/config.txt index 5a9134d..c53df68 100644 --- a/doc/config.txt +++ b/doc/config.txt @@ -298,7 +298,7 @@ environment variable substitutions with default values If you specify a substitution string like this:: - {defenv:DEFAULTVALUE:KEY} + {env:KEY:DEFAULTVALUE} then the value will be retrieved as ``os.environ['KEY']`` and replace with DEFAULTVALUE if the environment variable does not @@ -306,7 +306,7 @@ exist. If you specify a substitution string like this:: - {defenv::KEY} + {env:KEY:} then the value will be retrieved as ``os.environ['KEY']`` and replace with and empty string if the environment variable does not diff --git a/tests/test_config.py b/tests/test_config.py index 3084001..3647673 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -246,9 +246,9 @@ class TestIniParser: monkeypatch.setenv("KEY1", "hello") config = newconfig(""" [section] - key1={defenv:DEFAULT_VALUE:KEY1} - key2={defenv:DEFAULT_VALUE:KEY2} - key3={defenv::KEY3} + key1={env:KEY1:DEFAULT_VALUE} + key2={env:KEY2:DEFAULT_VALUE} + key3={env:KEY3:} """) reader = IniReader(config._cfg) x = reader.getdefault("section", "key1") diff --git a/tox/_config.py b/tox/_config.py index 468af5f..86ab3a0 100644 --- a/tox/_config.py +++ b/tox/_config.py @@ -595,11 +595,20 @@ class IniReader: #print "getdefault", section, name, "returned", repr(x) return x - def _do_replace_env(self, envkey, default=None): - if not envkey: + def _replace_env(self, match): + match_value = match.group('substitution_value') + if not match_value: raise tox.exception.ConfigError( 'env: requires an environment variable name') + default = None + envkey_split = match_value.split(':', 1) + + if len(envkey_split) is 2: + envkey, default = envkey_split + else: + envkey = match_value + if not envkey in os.environ and default is None: raise tox.exception.ConfigError( "substitution env:%r: unkown environment variable %r" % @@ -607,21 +616,6 @@ class IniReader: return os.environ.get(envkey, default) - def _replace_env(self, match): - envkey = match.group('substitution_value') - return self._do_replace_env(envkey) - - def _replace_env_with_default(self, match): - envkey = match.group('substitution_value') - try: - default, envkey = envkey.split(':', 1) - except ValueError: - raise tox.exception.ConfigError( - "substitution 'defenv:%r': malformed, expected " - "'defenv:DEFAULTVALUE:KEY'" % match) - - return self._do_replace_env(envkey, default=default) - def _substitute_from_other_section(self, key): if key.startswith("[") and "]" in key: i = key.find("]") @@ -661,7 +655,6 @@ class IniReader: handlers = { 'env' : self._replace_env, - 'defenv' : self._replace_env_with_default, None : self._replace_substitution, } try: -- cgit v1.2.1 From 128601efabaffb3e4c726964b6e4103b43e0e791 Mon Sep 17 00:00:00 2001 From: holger krekel Date: Fri, 28 Mar 2014 15:19:10 +0100 Subject: 1.7.1 quick bugfix release --- CHANGELOG | 2 +- README.rst | 2 +- setup.py | 2 +- tox/__init__.py | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 5357037..e6168cb 100755 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,4 +1,4 @@ -XXX +1.7.1 --------- - fix issue162: don't list python 2.5 as compatibiliy/supported diff --git a/README.rst b/README.rst index 2beeadd..c107df0 100644 --- a/README.rst +++ b/README.rst @@ -21,5 +21,5 @@ For more information and the repository please checkout: have fun, -holger krekel, January 2014 +holger krekel, 2014 diff --git a/setup.py b/setup.py index 5a61aa1..582d315 100644 --- a/setup.py +++ b/setup.py @@ -28,7 +28,7 @@ def main(): description='virtualenv-based automation of test activities', long_description=open("README.rst").read(), url='http://tox.testrun.org/', - version='1.7.0', + version='1.7.1', license='http://opensource.org/licenses/MIT', platforms=['unix', 'linux', 'osx', 'cygwin', 'win32'], author='holger krekel', diff --git a/tox/__init__.py b/tox/__init__.py index 5ff0e85..8f0acb1 100644 --- a/tox/__init__.py +++ b/tox/__init__.py @@ -1,5 +1,5 @@ # -__version__ = '1.7.0' +__version__ = '1.7.1' class exception: class Error(Exception): -- cgit v1.2.1 -- cgit v1.2.1 From cfbbf08b467a7417a11ef775689e14d9d7eb4def Mon Sep 17 00:00:00 2001 From: holger krekel Date: Fri, 28 Mar 2014 15:32:27 +0100 Subject: bump doc version to 1.7.1 --- doc/conf.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/conf.py b/doc/conf.py index fda4ac9..76dc4c3 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -48,7 +48,7 @@ copyright = u'2013, holger krekel and others' # built documents. # # The short X.Y version. -release = version = "1.7.0" +release = version = "1.7.1" # The full version, including alpha/beta/rc tags. # The language for content autogenerated by Sphinx. Refer to documentation -- cgit v1.2.1 From cb12ef1863bfeaab64857117ea7c993898482a39 Mon Sep 17 00:00:00 2001 From: Matthias Bach Date: Tue, 1 Apr 2014 07:18:43 +0200 Subject: Update jenkins specific documentation to use proper section name. The configuration documentation still refered to tox:hudson instead of tox:jenkins. fixes #136 --- doc/config.txt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/doc/config.txt b/doc/config.txt index 28068a3..054400d 100644 --- a/doc/config.txt +++ b/doc/config.txt @@ -28,9 +28,9 @@ List of optional global options:: (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``. envlist setting -- cgit v1.2.1 From 2cd6b250355d795f87c6d05487b5983900617b1a Mon Sep 17 00:00:00 2001 From: Marc Abramowitz Date: Thu, 3 Apr 2014 14:39:22 -0700 Subject: Log more info when catch OSError while doing a popen Fixes: https://bitbucket.org/hpk42/tox/issue/164/if-install_command-raises-an-oserror-get-a --- tox/_cmdline.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/tox/_cmdline.py b/tox/_cmdline.py index 7c6cd69..04296c4 100644 --- a/tox/_cmdline.py +++ b/tox/_cmdline.py @@ -93,8 +93,13 @@ class Action(object): 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) + try: + popen = self._popen(args, cwd, env=env, + stdout=f, stderr=STDOUT) + except OSError: + self.report.error("invocation failed, args: %s, cwd: %s" % + (args, cwd)) + raise popen.outpath = outpath popen.args = [str(x) for x in args] popen.cwd = cwd -- cgit v1.2.1 From a3c4eb4a7c0f5128948e7346e900a5d79d85ecb0 Mon Sep 17 00:00:00 2001 From: Marc Abramowitz Date: Thu, 3 Apr 2014 15:45:55 -0700 Subject: Allow backslashing curly braces to prevent expansion e.g.: commands=echo \{posargs\} = {posargs} --- tox/_config.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tox/_config.py b/tox/_config.py index 6006fa9..4d54924 100644 --- a/tox/_config.py +++ b/tox/_config.py @@ -451,8 +451,8 @@ class IndexServerConfig: self.url = url RE_ITEM_REF = re.compile( - ''' - [{] + r''' + (?[^[:{}]+):)? # optional sub_type for special rules (?P[^{}]*) # substitution key [}] @@ -686,7 +686,7 @@ class CommandParser(object): def word_has_ended(): return ((cur_char in string.whitespace and ps.word and ps.word[-1] not in string.whitespace) or - (cur_char == '{' and ps.depth == 0) or + (cur_char == '{' and ps.depth == 0 and not ps.word.endswith('\\')) or (ps.depth == 0 and ps.word and ps.word[-1] == '}') or (cur_char not in string.whitespace and ps.word and ps.word.strip() == '')) -- cgit v1.2.1 From 64d58c3d3deef2e212eb6a63a8d0f6af348924e6 Mon Sep 17 00:00:00 2001 From: Marc Abramowitz Date: Thu, 24 Apr 2014 07:58:29 -0700 Subject: doc: Update Jenkins example to use code-block --- doc/example/jenkins.txt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/doc/example/jenkins.txt b/doc/example/jenkins.txt index bfdceee..a3e7de2 100644 --- a/doc/example/jenkins.txt +++ b/doc/example/jenkins.txt @@ -32,7 +32,9 @@ using these steps: 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 -- cgit v1.2.1 From b3cd545123a5e57c8970af5645f683cd469d117d Mon Sep 17 00:00:00 2001 From: Marc Abramowitz Date: Thu, 24 Apr 2014 08:04:56 -0700 Subject: tox.ini: Add py33 and py34 --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index f2fc791..d5bcdcd 100644 --- a/tox.ini +++ b/tox.ini @@ -1,5 +1,5 @@ [tox] -envlist=py27,py33,py26,py32,pypy +envlist=py27,py33,py26,py32,py33,py34,pypy [testenv:X] commands=echo {posargs} -- cgit v1.2.1 From 598b1e18592590eb57f4df2e6b614c79bdb2e2c1 Mon Sep 17 00:00:00 2001 From: Marc Abramowitz Date: Thu, 24 Apr 2014 10:27:16 -0700 Subject: doc: Fix broken links --- doc/example/jenkins.txt | 3 +-- doc/example/pytest.txt | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/doc/example/jenkins.txt b/doc/example/jenkins.txt index bfdceee..57b71ab 100644 --- a/doc/example/jenkins.txt +++ b/doc/example/jenkins.txt @@ -57,7 +57,7 @@ with this:: 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 +68,6 @@ install ``tox`` in a virtual sub environment. Notes: 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 --git a/doc/example/pytest.txt b/doc/example/pytest.txt index cba8741..6f98cf3 100755 --- a/doc/example/pytest.txt +++ b/doc/example/pytest.txt @@ -105,7 +105,7 @@ 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. -.. _`fully qualified name`: http://pytest.org/latest/goodpractises.html#package-name +.. _`fully qualified name`: http://pytest.org/latest/goodpractises.html#test-package-name .. include:: ../links.txt -- cgit v1.2.1 From 74d05ce7a12b5e2708e5ded12732f56b54968c4d Mon Sep 17 00:00:00 2001 From: Marc Abramowitz Date: Fri, 25 Apr 2014 13:32:57 -0700 Subject: tox-quickstart: Add py34 --- tox/_quickstart.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox/_quickstart.py b/tox/_quickstart.py index f66f1e2..098eb61 100644 --- a/tox/_quickstart.py +++ b/tox/_quickstart.py @@ -56,7 +56,7 @@ except NameError: term_input = input -all_envs = ['py26', 'py27', 'py32', 'py33', 'pypy', 'jython'] +all_envs = ['py26', 'py27', 'py32', 'py33', 'py34', 'pypy', 'jython'] PROMPT_PREFIX = '> ' -- cgit v1.2.1 From a395bf5f6cbe40b01e766ffdd5ed6b4137534bc5 Mon Sep 17 00:00:00 2001 From: Marc Abramowitz Date: Sun, 27 Apr 2014 01:50:53 -0700 Subject: Fix test_quickstart failures that were introduced by adding py34 to tox-quickstart in 94842aabbbcc --- tests/test_quickstart.py | 30 ++++++++++++++++-------------- 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/tests/test_quickstart.py b/tests/test_quickstart.py index c376d4b..709fdc6 100644 --- a/tests/test_quickstart.py +++ b/tests/test_quickstart.py @@ -28,7 +28,7 @@ class TestToxQuickstartMain(object): monkeypatch.setattr( tox._quickstart, 'term_input', self.get_mock_term_input( - ['4', 'Y', 'Y', 'Y', 'Y', 'Y', 'N', 'py.test', 'pytest'])) + ['4', 'Y', 'Y', 'Y', 'Y', 'Y', 'Y', 'N', 'py.test', 'pytest'])) tox._quickstart.main(argv=['tox-quickstart']) @@ -39,7 +39,7 @@ class TestToxQuickstartMain(object): # and then run "tox" from this directory. [tox] -envlist = py26, py27, py32, py33, pypy +envlist = py26, py27, py32, py33, py34, pypy [testenv] commands = py.test @@ -52,7 +52,7 @@ deps = def test_quickstart_main_choose_individual_pythons_and_nose_adds_deps(self, monkeypatch): monkeypatch.setattr( tox._quickstart, 'term_input', - self.get_mock_term_input(['4', 'Y', 'Y', 'Y', 'Y', 'Y', 'N', + self.get_mock_term_input(['4', 'Y', 'Y', 'Y', 'Y', 'Y', 'Y', 'N', 'nosetests', ''])) tox._quickstart.main(argv=['tox-quickstart']) @@ -64,7 +64,7 @@ deps = # and then run "tox" from this directory. [tox] -envlist = py26, py27, py32, py33, pypy +envlist = py26, py27, py32, py33, py34, pypy [testenv] commands = nosetests @@ -77,7 +77,7 @@ deps = def test_quickstart_main_choose_individual_pythons_and_trial_adds_deps(self, monkeypatch): monkeypatch.setattr( tox._quickstart, 'term_input', - self.get_mock_term_input(['4', 'Y', 'Y', 'Y', 'Y', 'Y', 'N', + self.get_mock_term_input(['4', 'Y', 'Y', 'Y', 'Y', 'Y', 'Y', 'N', 'trial', ''])) tox._quickstart.main(argv=['tox-quickstart']) @@ -89,7 +89,7 @@ deps = # and then run "tox" from this directory. [tox] -envlist = py26, py27, py32, py33, pypy +envlist = py26, py27, py32, py33, py34, pypy [testenv] commands = trial @@ -102,7 +102,7 @@ deps = def test_quickstart_main_choose_individual_pythons_and_pytest_adds_deps(self, monkeypatch): monkeypatch.setattr( tox._quickstart, 'term_input', - self.get_mock_term_input(['4', 'Y', 'Y', 'Y', 'Y', 'Y', 'N', + self.get_mock_term_input(['4', 'Y', 'Y', 'Y', 'Y', 'Y', 'Y', 'N', 'py.test', ''])) tox._quickstart.main(argv=['tox-quickstart']) @@ -113,7 +113,7 @@ deps = # and then run "tox" from this directory. [tox] -envlist = py26, py27, py32, py33, pypy +envlist = py26, py27, py32, py33, py34, pypy [testenv] commands = py.test @@ -185,7 +185,7 @@ deps = # and then run "tox" from this directory. [tox] -envlist = py26, py27, py32, py33, pypy, jython +envlist = py26, py27, py32, py33, py34, pypy, jython [testenv] commands = py.test @@ -198,7 +198,7 @@ deps = def test_quickstart_main_choose_individual_pythons_and_defaults(self, monkeypatch): monkeypatch.setattr( tox._quickstart, 'term_input', - self.get_mock_term_input(['4', '', '', '', '', '', '', '', '', '', ''])) + self.get_mock_term_input(['4', '', '', '', '', '', '', '', '', '', '', ''])) tox._quickstart.main(argv=['tox-quickstart']) @@ -209,7 +209,7 @@ deps = # and then run "tox" from this directory. [tox] -envlist = py26, py27, py32, py33, pypy, jython +envlist = py26, py27, py32, py33, py34, pypy, jython [testenv] commands = {envpython} setup.py test @@ -239,7 +239,7 @@ deps = # and then run "tox" from this directory. [tox] -envlist = py26, py27, py32, py33, pypy, jython +envlist = py26, py27, py32, py33, py34, pypy, jython [testenv] commands = {envpython} setup.py test @@ -257,6 +257,7 @@ class TestToxQuickstart(object): 'py27': True, 'py32': True, 'py33': True, + 'py34': True, 'pypy': True, 'commands': 'py.test', 'deps': 'pytest', @@ -268,7 +269,7 @@ class TestToxQuickstart(object): # and then run "tox" from this directory. [tox] -envlist = py26, py27, py32, py33, pypy +envlist = py26, py27, py32, py33, py34, pypy [testenv] commands = py.test @@ -339,6 +340,7 @@ deps = 'py27': True, 'py32': True, 'py33': True, + 'py34': True, 'pypy': True, 'commands': 'nosetests -v', 'deps': 'nose', @@ -350,7 +352,7 @@ deps = # and then run "tox" from this directory. [tox] -envlist = py27, py32, py33, pypy +envlist = py27, py32, py33, py34, pypy [testenv] commands = nosetests -v -- cgit v1.2.1 From f28bcae5c2713b6a0382b8e0849befdf1196b540 Mon Sep 17 00:00:00 2001 From: Marc Abramowitz Date: Sun, 27 Apr 2014 09:24:33 -0700 Subject: tox.ini: Fix envlist It was listing py33 twice and was in no discernible order. --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index d5bcdcd..1a0027a 100644 --- a/tox.ini +++ b/tox.ini @@ -1,5 +1,5 @@ [tox] -envlist=py27,py33,py26,py32,py33,py34,pypy +envlist=py27,py26,py34,py33,py32,pypy [testenv:X] commands=echo {posargs} -- cgit v1.2.1 From c6df1a6a4175078fb4051dbe7f2d11af468b6b4b Mon Sep 17 00:00:00 2001 From: Marc Abramowitz Date: Sun, 27 Apr 2014 17:42:34 -0700 Subject: tox.ini: Add "flakes" target --- tox.ini | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index 1a0027a..6bcca5e 100644 --- a/tox.ini +++ b/tox.ini @@ -1,5 +1,5 @@ [tox] -envlist=py27,py26,py34,py33,py32,pypy +envlist=py27,py26,py34,py33,py32,pypy,flakes [testenv:X] commands=echo {posargs} @@ -18,6 +18,10 @@ commands= --junitxml={envlogdir}/junit-{envname}.xml \ check_sphinx.py {posargs} +[testenv:flakes] +deps = pytest-flakes>=0.2 +commands = py.test --flakes -m flakes tox tests + [testenv:py25] setenv= PIP_INSECURE=1 -- cgit v1.2.1 From 11b313d1d874027843ebbd7af16a32e5859e8055 Mon Sep 17 00:00:00 2001 From: Marc Abramowitz Date: Sun, 27 Apr 2014 17:43:12 -0700 Subject: Fix "flakes" problems --- tests/conftest.py | 2 +- tests/test_config.py | 11 +++++------ tests/test_interpreters.py | 2 +- tests/test_venv.py | 10 +++++----- tests/test_z_cmdline.py | 4 +--- tox/__init__.py | 2 +- tox/_cmdline.py | 4 ++-- tox/_config.py | 7 ++----- tox/_venv.py | 8 +++----- tox/_verlib.py | 1 - tox/interpreters.py | 2 -- 11 files changed, 21 insertions(+), 32 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index 1d4bdde..3e2493b 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,2 +1,2 @@ -from tox._pytestplugin import * +from tox._pytestplugin import * # noqa diff --git a/tests/test_config.py b/tests/test_config.py index b8d0aa3..5e766cf 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -1,12 +1,11 @@ -import tox -import pytest -import os, sys -import subprocess +import sys from textwrap import dedent import py +import pytest +import tox import tox._config -from tox._config import * +from tox._config import * # noqa from tox._config import _split_env @@ -110,7 +109,6 @@ class TestConfigPackage: 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): @@ -168,6 +166,7 @@ class TestIniParser: key2={xyz} """) reader = IniReader(config._cfg, fallbacksections=['mydefault']) + assert reader is not None py.test.raises(tox.exception.ConfigError, 'reader.getdefault("mydefault", "key2")') diff --git a/tests/test_interpreters.py b/tests/test_interpreters.py index 39eabc6..f06a69a 100644 --- a/tests/test_interpreters.py +++ b/tests/test_interpreters.py @@ -2,7 +2,7 @@ import sys import os import pytest -from tox.interpreters import * +from tox.interpreters import * # noqa @pytest.fixture def interpreters(): diff --git a/tests/test_venv.py b/tests/test_venv.py index 07b89f4..dbfec41 100644 --- a/tests/test_venv.py +++ b/tests/test_venv.py @@ -3,7 +3,7 @@ import tox import pytest import os, sys import tox._config -from tox._venv import * +from tox._venv import * # noqa #def test_global_virtualenv(capfd): # v = VirtualEnv() @@ -277,7 +277,7 @@ def test_install_command_not_installed(newmocksession, monkeypatch): venv = mocksession.getenv('python') venv.test() mocksession.report.expect("warning", "*test command found but not*") - assert venv.status == "commands failed" + assert venv.status == 0 def test_install_command_whitelisted(newmocksession, monkeypatch): mocksession = newmocksession(['--recreate'], """ @@ -295,7 +295,7 @@ def test_install_command_whitelisted(newmocksession, monkeypatch): assert venv.status == "commands failed" @pytest.mark.skipif("not sys.platform.startswith('linux')") -def test_install_command_not_installed(newmocksession): +def test_install_command_not_installed_bash(newmocksession): mocksession = newmocksession(['--recreate'], """ [testenv] commands= @@ -387,7 +387,7 @@ class TestCreationConfig: [testenv] deps={distshare}/xyz-* """) - xyz = config.distshare.ensure("xyz-1.2.0.zip") + config.distshare.ensure("xyz-1.2.0.zip") xyz2 = config.distshare.ensure("xyz-1.2.1.zip") envconfig = config.envconfigs['python'] venv = VirtualEnv(envconfig, session=mocksession) @@ -507,7 +507,6 @@ def test_setenv_added_to_pcall(tmpdir, mocksession, newconfig): l = mocksession._pcalls assert len(l) == 2 for x in l: - args = x.args env = x.env assert env is not None assert 'ENV_VAR' in env @@ -585,6 +584,7 @@ def test_command_relative_issue26(newmocksession, tmpdir, monkeypatch): mocksession.report.not_expect("warning", "*test command found but not*") monkeypatch.setenv("PATH", str(tmpdir)) x4 = venv.getcommandpath("x", cwd=tmpdir) + assert x4.endswith('/x') mocksession.report.expect("warning", "*test command found but not*") def test_sethome_only_on_option(newmocksession, monkeypatch): diff --git a/tests/test_z_cmdline.py b/tests/test_z_cmdline.py index 343c142..537db5d 100644 --- a/tests/test_z_cmdline.py +++ b/tests/test_z_cmdline.py @@ -1,7 +1,6 @@ import tox import py import pytest -import sys from tox._pytestplugin import ReportExpectMock try: import json @@ -129,7 +128,6 @@ class TestSession: }) config = parseconfig([]) session = Session(config) - envlist = ['hello', 'world'] envs = session.venvlist assert len(envs) == 2 env1, env2 = envs @@ -582,6 +580,7 @@ def test_separate_sdist_no_sdistfile(cmd, initproj): l = distshare.listdir() assert len(l) == 1 sdistfile = l[0] + assert 'pkg123-0.7.zip' in str(sdistfile) def test_separate_sdist(cmd, initproj): distshare = cmd.tmpdir.join("distshare") @@ -611,7 +610,6 @@ def test_sdist_latest(tmpdir, newconfig): distshare=%s sdistsrc={distshare}/pkg123-* """ % distshare) - p0 = distshare.ensure("pkg123-1.3.5.zip") p = distshare.ensure("pkg123-1.4.5.zip") distshare.ensure("pkg123-1.4.5a1.zip") session = Session(config) diff --git a/tox/__init__.py b/tox/__init__.py index 8f0acb1..d7b2155 100644 --- a/tox/__init__.py +++ b/tox/__init__.py @@ -20,4 +20,4 @@ class exception: class MissingDependency(Error): """ a dependency could not be found or determined. """ -from tox._cmdline import main as cmdline +from tox._cmdline import main as cmdline # noqa diff --git a/tox/_cmdline.py b/tox/_cmdline.py index 7c6cd69..192658d 100644 --- a/tox/_cmdline.py +++ b/tox/_cmdline.py @@ -79,7 +79,6 @@ class Action(object): return f def popen(self, args, cwd=None, env=None, redirect=True, returnout=False): - logged_command = "%s$ %s" %(cwd, " ".join(map(str, args))) f = outpath = None resultjson = self.session.config.option.resultjson if resultjson or redirect: @@ -411,7 +410,8 @@ class Session: sdist_path = self._makesdist() except tox.exception.InvocationError: v = sys.exc_info()[1] - self.report.error("FAIL could not package project") + self.report.error("FAIL could not package project - v = %r" % + v) return sdistfile = self.config.distshare.join(sdist_path.basename) if sdistfile != sdist_path: diff --git a/tox/_config.py b/tox/_config.py index 6006fa9..2753780 100644 --- a/tox/_config.py +++ b/tox/_config.py @@ -1,13 +1,10 @@ import argparse -import distutils.sysconfig import os import random import sys import re import shlex import string -import subprocess -import textwrap import pkg_resources from tox.interpreters import Interpreters @@ -205,7 +202,7 @@ def make_hashseed(): class parseini: def __init__(self, config, inipath): config.toxinipath = inipath - config.toxinidir = toxinidir = config.toxinipath.dirpath() + config.toxinidir = config.toxinipath.dirpath() self._cfg = py.iniconfig.IniConfig(config.toxinipath) config._cfg = self._cfg @@ -239,7 +236,7 @@ class parseini: # determine indexserver dictionary config.indexserver = {'default': IndexServerConfig('default')} prefix = "indexserver" - for line in reader.getlist(toxsection, "indexserver"): + for line in reader.getlist(toxsection, prefix): name, url = map(lambda x: x.strip(), line.split("=", 1)) config.indexserver[name] = IndexServerConfig(name, url) diff --git a/tox/_venv.py b/tox/_venv.py index d0f2e64..7e9f45f 100644 --- a/tox/_venv.py +++ b/tox/_venv.py @@ -1,6 +1,5 @@ from __future__ import with_statement -import sys, os, re -import subprocess +import sys, os import py import tox from tox._config import DepConfig @@ -121,8 +120,6 @@ class VirtualEnv(object): """ if action is None: action = self.session.newaction(self, "update") - report = self.session.report - name = self.envconfig.envname rconfig = CreationConfig.readconfig(self.path_config) if not self.envconfig.recreate and rconfig and \ rconfig.matches(self._getliveconfig()): @@ -142,7 +139,8 @@ class VirtualEnv(object): self.install_deps(action) except tox.exception.InvocationError: v = sys.exc_info()[1] - return "could not install deps %s" %(self.envconfig.deps,) + return "could not install deps %s; v = %r" % ( + self.envconfig.deps, v) def _getliveconfig(self): python = self.envconfig._basepython_info.executable diff --git a/tox/_verlib.py b/tox/_verlib.py index 1df3645..a234176 100644 --- a/tox/_verlib.py +++ b/tox/_verlib.py @@ -8,7 +8,6 @@ licensed under the PSF license (i guess) """ -import sys import re class IrrationalVersionError(Exception): diff --git a/tox/interpreters.py b/tox/interpreters.py index e225fcc..5d38929 100644 --- a/tox/interpreters.py +++ b/tox/interpreters.py @@ -1,8 +1,6 @@ import sys -import os import py import re -import subprocess import inspect class Interpreters: -- cgit v1.2.1 -- cgit v1.2.1 From adcb5b45141ad54afb19aea96d1bc979e0909e0d Mon Sep 17 00:00:00 2001 From: Marc Abramowitz Date: Mon, 28 Apr 2014 09:35:11 -0700 Subject: test_quickstart: Use multilines and add comments for better understanding as suggested by Ronny Pfannschmidt at https://bitbucket.org/hpk42/tox/pull-request/99/fix-test_quickstart-failures/diff#comment-1746221 --- tests/test_quickstart.py | 160 ++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 139 insertions(+), 21 deletions(-) diff --git a/tests/test_quickstart.py b/tests/test_quickstart.py index 709fdc6..df8a98f 100644 --- a/tests/test_quickstart.py +++ b/tests/test_quickstart.py @@ -6,6 +6,7 @@ import tox._quickstart def cleandir(tmpdir): tmpdir.chdir() + class TestToxQuickstartMain(object): def mock_term_input_return_values(self, return_values): @@ -23,12 +24,26 @@ class TestToxQuickstartMain(object): return mock_term_input - def test_quickstart_main_choose_individual_pythons_and_pytest(self, - monkeypatch): + def test_quickstart_main_choose_individual_pythons_and_pytest( + self, + monkeypatch): monkeypatch.setattr( tox._quickstart, 'term_input', self.get_mock_term_input( - ['4', 'Y', 'Y', 'Y', 'Y', 'Y', 'Y', 'N', 'py.test', 'pytest'])) + [ + '4', # Python versions: choose one by one + 'Y', # py26 + 'Y', # py27 + 'Y', # py32 + 'Y', # py33 + 'Y', # py34 + 'Y', # pypy + 'N', # jython + 'py.test', # command to run tests + 'pytest' # test dependencies + ] + ) + ) tox._quickstart.main(argv=['tox-quickstart']) @@ -49,11 +64,26 @@ deps = result = open('tox.ini').read() assert(result == expected_tox_ini) - def test_quickstart_main_choose_individual_pythons_and_nose_adds_deps(self, monkeypatch): + def test_quickstart_main_choose_individual_pythons_and_nose_adds_deps( + self, + monkeypatch): monkeypatch.setattr( tox._quickstart, 'term_input', - self.get_mock_term_input(['4', 'Y', 'Y', 'Y', 'Y', 'Y', 'Y', 'N', - 'nosetests', ''])) + self.get_mock_term_input( + [ + '4', # Python versions: choose one by one + 'Y', # py26 + 'Y', # py27 + 'Y', # py32 + 'Y', # py33 + 'Y', # py34 + 'Y', # pypy + 'N', # jython + 'nosetests', # command to run tests + '' # test dependencies + ] + ) + ) tox._quickstart.main(argv=['tox-quickstart']) @@ -74,11 +104,26 @@ deps = result = open('tox.ini').read() assert(result == expected_tox_ini) - def test_quickstart_main_choose_individual_pythons_and_trial_adds_deps(self, monkeypatch): + def test_quickstart_main_choose_individual_pythons_and_trial_adds_deps( + self, + monkeypatch): monkeypatch.setattr( tox._quickstart, 'term_input', - self.get_mock_term_input(['4', 'Y', 'Y', 'Y', 'Y', 'Y', 'Y', 'N', - 'trial', ''])) + self.get_mock_term_input( + [ + '4', # Python versions: choose one by one + 'Y', # py26 + 'Y', # py27 + 'Y', # py32 + 'Y', # py33 + 'Y', # py34 + 'Y', # pypy + 'N', # jython + 'trial', # command to run tests + '' # test dependencies + ] + ) + ) tox._quickstart.main(argv=['tox-quickstart']) @@ -99,11 +144,26 @@ deps = result = open('tox.ini').read() assert(result == expected_tox_ini) - def test_quickstart_main_choose_individual_pythons_and_pytest_adds_deps(self, monkeypatch): + def test_quickstart_main_choose_individual_pythons_and_pytest_adds_deps( + self, + monkeypatch): monkeypatch.setattr( tox._quickstart, 'term_input', - self.get_mock_term_input(['4', 'Y', 'Y', 'Y', 'Y', 'Y', 'Y', 'N', - 'py.test', ''])) + self.get_mock_term_input( + [ + '4', # Python versions: choose one by one + 'Y', # py26 + 'Y', # py27 + 'Y', # py32 + 'Y', # py33 + 'Y', # py34 + 'Y', # pypy + 'N', # jython + 'py.test', # command to run tests + '' # test dependencies + ] + ) + ) tox._quickstart.main(argv=['tox-quickstart']) expected_tox_ini = """ @@ -123,10 +183,19 @@ deps = result = open('tox.ini').read() assert(result == expected_tox_ini) - def test_quickstart_main_choose_py27_and_pytest_adds_deps(self, monkeypatch): + def test_quickstart_main_choose_py27_and_pytest_adds_deps( + self, + monkeypatch): monkeypatch.setattr( tox._quickstart, 'term_input', - self.get_mock_term_input(['1', 'py.test', ''])) + self.get_mock_term_input( + [ + '1', # py27 + 'py.test', # command to run tests + '' # test dependencies + ] + ) + ) tox._quickstart.main(argv=['tox-quickstart']) @@ -147,10 +216,19 @@ deps = result = open('tox.ini').read() assert(result == expected_tox_ini) - def test_quickstart_main_choose_py27_and_py33_and_pytest_adds_deps(self, monkeypatch): + def test_quickstart_main_choose_py27_and_py33_and_pytest_adds_deps( + self, + monkeypatch): monkeypatch.setattr( tox._quickstart, 'term_input', - self.get_mock_term_input(['2', 'py.test', ''])) + self.get_mock_term_input( + [ + '2', # py27 and py33 + 'py.test', # command to run tests + '' # test dependencies + ] + ) + ) tox._quickstart.main(argv=['tox-quickstart']) @@ -171,10 +249,19 @@ deps = result = open('tox.ini').read() assert(result == expected_tox_ini) - def test_quickstart_main_choose_all_pythons_and_pytest_adds_deps(self, monkeypatch): + def test_quickstart_main_choose_all_pythons_and_pytest_adds_deps( + self, + monkeypatch): monkeypatch.setattr( tox._quickstart, 'term_input', - self.get_mock_term_input(['3', 'py.test', ''])) + self.get_mock_term_input( + [ + '3', # all Python versions + 'py.test', # command to run tests + '' # test dependencies + ] + ) + ) tox._quickstart.main(argv=['tox-quickstart']) @@ -195,10 +282,26 @@ deps = result = open('tox.ini').read() assert(result == expected_tox_ini) - def test_quickstart_main_choose_individual_pythons_and_defaults(self, monkeypatch): + def test_quickstart_main_choose_individual_pythons_and_defaults( + self, + monkeypatch): monkeypatch.setattr( tox._quickstart, 'term_input', - self.get_mock_term_input(['4', '', '', '', '', '', '', '', '', '', '', ''])) + self.get_mock_term_input( + [ + '4', # Python versions: choose one by one + '', # py26 + '', # py27 + '', # py32 + '', # py33 + '', # py34 + '', # pypy + '', # jython + '', # command to run tests + '' # test dependencies + ] + ) + ) tox._quickstart.main(argv=['tox-quickstart']) @@ -228,7 +331,22 @@ deps = monkeypatch.setattr( tox._quickstart, 'term_input', - self.get_mock_term_input(['4', '', '', '', '', '', '', '', '', '', '', ''])) + self.get_mock_term_input( + [ + '4', # Python versions: choose one by one + '', # py26 + '', # py27 + '', # py32 + '', # py33 + '', # py34 + '', # pypy + '', # jython + '', # command to run tests + '', # test dependencies + '', # tox.ini already exists; overwrite? + ] + ) + ) tox._quickstart.main(argv=['tox-quickstart']) -- cgit v1.2.1 From 0b808e4640861a1f5196201db99a865839781e48 Mon Sep 17 00:00:00 2001 From: Marc Abramowitz Date: Mon, 28 Apr 2014 23:19:28 -0700 Subject: Add test_run_custom_install_command_error --- tests/test_z_cmdline.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/tests/test_z_cmdline.py b/tests/test_z_cmdline.py index 343c142..bdbd1ea 100644 --- a/tests/test_z_cmdline.py +++ b/tests/test_z_cmdline.py @@ -193,6 +193,19 @@ def test_minversion(cmd, initproj): ]) assert result.ret +def test_run_custom_install_command_error(cmd, initproj): + initproj("interp123-0.5", filedefs={ + 'tox.ini': ''' + [testenv] + install_command=./tox.ini {opts} {packages} + ''' + }) + result = cmd.run("tox") + result.stdout.fnmatch_lines([ + "ERROR: invocation failed, args: ['*/tox.ini*", + ]) + assert result.ret + def test_unknown_interpreter_and_env(cmd, initproj): initproj("interp123-0.5", filedefs={ 'tests': {'test_hello.py': "def test_hello(): pass"}, -- cgit v1.2.1 From 3bd552c799ea29f90eb6a759cd4270a9681bc55d Mon Sep 17 00:00:00 2001 From: Alexandre Conrad Date: Wed, 30 Apr 2014 17:22:37 -0700 Subject: make test more specific, less prone to failures Checking for the non-existence of the string "virtualenv" could fail because paths may contain the word virtualenv. --- tests/test_z_cmdline.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_z_cmdline.py b/tests/test_z_cmdline.py index 537db5d..2e10c24 100644 --- a/tests/test_z_cmdline.py +++ b/tests/test_z_cmdline.py @@ -565,7 +565,7 @@ def test_sdistonly(initproj, cmd): result.stdout.fnmatch_lines([ "*sdist-make*setup.py*", ]) - assert "virtualenv" not in result.stdout.str() + assert "-mvirtualenv" not in result.stdout.str() def test_separate_sdist_no_sdistfile(cmd, initproj): distshare = cmd.tmpdir.join("distshare") -- cgit v1.2.1 From ecd8e2fc9d278b099464e8b835e31146971eaa9b Mon Sep 17 00:00:00 2001 From: Alexandre Conrad Date: Wed, 30 Apr 2014 23:17:45 -0700 Subject: support skipping interpreters if any are missing This implements the option --skip-missing-interpreters. If this option is passed to tox, the tests won't fail if interpreters are missing. The exit status will be 0 if all tests pass but interpreters were missing. --- tests/test_z_cmdline.py | 16 ++++++++++++++++ tox/_cmdline.py | 12 +++++++++++- tox/_config.py | 2 ++ 3 files changed, 29 insertions(+), 1 deletion(-) diff --git a/tests/test_z_cmdline.py b/tests/test_z_cmdline.py index 537db5d..6d2575f 100644 --- a/tests/test_z_cmdline.py +++ b/tests/test_z_cmdline.py @@ -229,6 +229,22 @@ def test_unknown_interpreter(cmd, initproj): "*ERROR*InterpreterNotFound*xyz_unknown_interpreter*", ]) +def test_skip_unknown_interpreter(cmd, initproj): + initproj("interp123-0.5", filedefs={ + 'tests': {'test_hello.py': "def test_hello(): pass"}, + 'tox.ini': ''' + [testenv:python] + basepython=xyz_unknown_interpreter + [testenv] + changedir=tests + ''' + }) + result = cmd.run("tox", "--skip-missing-interpreters") + assert not result.ret + result.stdout.fnmatch_lines([ + "*SKIPPED*InterpreterNotFound*xyz_unknown_interpreter*", + ]) + def test_unknown_dep(cmd, initproj): initproj("dep123-0.7", filedefs={ 'tests': {'test_hello.py': "def test_hello(): pass"}, diff --git a/tox/_cmdline.py b/tox/_cmdline.py index 192658d..5d58a18 100644 --- a/tox/_cmdline.py +++ b/tox/_cmdline.py @@ -222,6 +222,9 @@ class Reporter(object): def error(self, msg): self.logline("ERROR: " + msg, red=True) + def skip(self, msg): + self.logline("SKIPPED:" + msg, yellow=True) + def logline(self, msg, **opts): self._reportedlines.append(msg) self.tw.line("%s" % msg, **opts) @@ -461,7 +464,14 @@ class Session: retcode = 0 for venv in self.venvlist: status = venv.status - if status and status != "skipped tests": + if isinstance(status, tox.exception.InterpreterNotFound): + msg = " %s: %s" %(venv.envconfig.envname, str(status)) + if self.config.option.skip_missing_interpreters: + self.report.skip(msg) + else: + retcode = 1 + self.report.error(msg) + elif status and status != "skipped tests": msg = " %s: %s" %(venv.envconfig.envname, str(status)) self.report.error(msg) retcode = 1 diff --git a/tox/_config.py b/tox/_config.py index 2753780..57df84d 100644 --- a/tox/_config.py +++ b/tox/_config.py @@ -129,6 +129,8 @@ def prepare_parse(pkgname): "'pytest<2.7' or 'django>=1.6'.") parser.add_argument("--sitepackages", action="store_true", help="override sitepackages setting to True in all envs") + parser.add_argument("--skip-missing-interpreters", action="store_true", + help="don't fail tests for missing interpreters") parser.add_argument("args", nargs="*", help="additional arguments available to command positional substitution") -- cgit v1.2.1 From 33f11ebdcbe4da15449fcaac3ef6e3a7f4839a12 Mon Sep 17 00:00:00 2001 From: Alexandre Conrad Date: Thu, 1 May 2014 12:12:42 -0700 Subject: update CHANGELOG and CONTRIBUTORS --- CHANGELOG | 6 ++++++ CONTRIBUTORS | 1 + 2 files changed, 7 insertions(+) diff --git a/CHANGELOG b/CHANGELOG index e6168cb..99b24c9 100755 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,9 @@ +development +----------- + +- fix issue59: add option "--skip-missing-interpreters" which won't fail the + build if Python interpreters listed in tox.ini are missing. + 1.7.1 --------- diff --git a/CONTRIBUTORS b/CONTRIBUTORS index 0244c2b..74557f6 100644 --- a/CONTRIBUTORS +++ b/CONTRIBUTORS @@ -23,3 +23,4 @@ Matt Good Mattieu Agopian Asmund Grammeltwedt Ionel Maries Cristian +Alexandre Conrad -- cgit v1.2.1 From 9805f6fbaad6724df1bd168e60dd955519d0fe68 Mon Sep 17 00:00:00 2001 From: Marc Abramowitz Date: Thu, 1 May 2014 14:18:18 -0700 Subject: .hgignore: Ignore .cache dir created by flakes --- .hgignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.hgignore b/.hgignore index cf24d86..8cacf58 100644 --- a/.hgignore +++ b/.hgignore @@ -14,3 +14,4 @@ dist doc/_build/ tox.egg-info .tox +.cache -- cgit v1.2.1 From 8794480c7b84345c038639325913796810c12eea Mon Sep 17 00:00:00 2001 From: Paul Moore Date: Tue, 6 May 2014 14:44:45 +0100 Subject: Return value of locate_via_py was being ignored --- tox/interpreters.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox/interpreters.py b/tox/interpreters.py index 5d38929..75318c5 100644 --- a/tox/interpreters.py +++ b/tox/interpreters.py @@ -159,7 +159,7 @@ else: # The standard executables can be found as a last resort via the # Python launcher py.exe if m: - locate_via_py(*m.groups()) + return locate_via_py(*m.groups()) def pyinfo(): import sys -- cgit v1.2.1 From d3e444560eb36b9c69f35eeb3fb9174a1498e3cc Mon Sep 17 00:00:00 2001 From: holger krekel Date: Thu, 8 May 2014 21:04:03 +0200 Subject: fix issue59 -- add --skip-if-missing-interpreter. Thanks Alexandre Conrad for the PR. --- CHANGELOG | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG b/CHANGELOG index 99b24c9..8afc346 100755 --- a/CHANGELOG +++ b/CHANGELOG @@ -2,7 +2,8 @@ development ----------- - fix issue59: add option "--skip-missing-interpreters" which won't fail the - build if Python interpreters listed in tox.ini are missing. + build if Python interpreters listed in tox.ini are missing. Thanks + Alexandre Conrad for the PR. 1.7.1 --------- -- cgit v1.2.1 From be42875b9259dd0fa652536ff6e0e3874f509c52 Mon Sep 17 00:00:00 2001 From: holger krekel Date: Sat, 10 May 2014 09:22:07 +0200 Subject: fix issue164: better traceback info in case of failing test commands. Thanks Marc Abramowitz for the PR. --- CHANGELOG | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG b/CHANGELOG index 8afc346..55f6689 100755 --- a/CHANGELOG +++ b/CHANGELOG @@ -5,6 +5,10 @@ development build if Python interpreters listed in tox.ini are missing. Thanks Alexandre Conrad for the PR. +- fix issue164: better traceback info in case of failing test commands. + Thanks Marc Abramowitz for the PR. + + 1.7.1 --------- -- cgit v1.2.1 From ecc8d45e048234e7d57b9b7cc14d09d8181d573b Mon Sep 17 00:00:00 2001 From: holger krekel Date: Sat, 10 May 2014 11:43:40 +0200 Subject: remove executable bit --- CHANGELOG | 0 1 file changed, 0 insertions(+), 0 deletions(-) mode change 100755 => 100644 CHANGELOG diff --git a/CHANGELOG b/CHANGELOG old mode 100755 new mode 100644 -- cgit v1.2.1 From e25df52e2b548e43a7d90c9cc15620dcbb3bf811 Mon Sep 17 00:00:00 2001 From: holger krekel Date: Sat, 10 May 2014 12:10:37 +0200 Subject: changelog entry: support optional env variable substitution, thanks Morgan Fainberg for the complete PR. --- CHANGELOG | 3 +++ CONTRIBUTORS | 1 + 2 files changed, 4 insertions(+) diff --git a/CHANGELOG b/CHANGELOG index 55f6689..562c00d 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -8,6 +8,9 @@ development - fix issue164: better traceback info in case of failing test commands. Thanks Marc Abramowitz for the PR. +- support optional env variable substitution, thanks Morgan Fainberg + for the complete PR. + 1.7.1 --------- diff --git a/CONTRIBUTORS b/CONTRIBUTORS index 74557f6..3ba25f8 100644 --- a/CONTRIBUTORS +++ b/CONTRIBUTORS @@ -24,3 +24,4 @@ Mattieu Agopian Asmund Grammeltwedt Ionel Maries Cristian Alexandre Conrad +Morgan Fainberg -- cgit v1.2.1 From f1dc157d43fc34d0479ea351c6299b4ff90c4c51 Mon Sep 17 00:00:00 2001 From: holger krekel Date: Sat, 10 May 2014 12:17:53 +0200 Subject: limit python hashseed to 1024 on Windows to prevent possible memory errors. --- CHANGELOG | 8 +++++--- CONTRIBUTORS | 1 + 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 562c00d..1617043 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -3,14 +3,16 @@ development - fix issue59: add option "--skip-missing-interpreters" which won't fail the build if Python interpreters listed in tox.ini are missing. Thanks - Alexandre Conrad for the PR. + Alexandre Conrad for PR104. - fix issue164: better traceback info in case of failing test commands. - Thanks Marc Abramowitz for the PR. + Thanks Marc Abramowitz for PR92. - support optional env variable substitution, thanks Morgan Fainberg - for the complete PR. + for PR86. +- limit python hashseed to 1024 on Windows to prevent possible + memory errors. Thanks March Schlaich for the PR90. 1.7.1 --------- diff --git a/CONTRIBUTORS b/CONTRIBUTORS index 3ba25f8..aeb597e 100644 --- a/CONTRIBUTORS +++ b/CONTRIBUTORS @@ -25,3 +25,4 @@ Asmund Grammeltwedt Ionel Maries Cristian Alexandre Conrad Morgan Fainberg +Marc Schleich -- cgit v1.2.1 From da21bb87b0932a054f106ddfaf641dbd7910330e Mon Sep 17 00:00:00 2001 From: holger krekel Date: Sun, 11 May 2014 10:43:29 +0200 Subject: fix Marc's name :) --- CONTRIBUTORS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CONTRIBUTORS b/CONTRIBUTORS index aeb597e..e2f428c 100644 --- a/CONTRIBUTORS +++ b/CONTRIBUTORS @@ -25,4 +25,4 @@ Asmund Grammeltwedt Ionel Maries Cristian Alexandre Conrad Morgan Fainberg -Marc Schleich +Marc Schlaich -- cgit v1.2.1 From 20de40c837b9616bb08c155fe108b61cc50cfae6 Mon Sep 17 00:00:00 2001 From: holger krekel Date: Sun, 11 May 2014 19:29:35 +0200 Subject: make it clear that tox supports python2.6/2.7 and python3.2ff --- doc/index.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/index.txt b/doc/index.txt index c1baae2..17d063d 100644 --- a/doc/index.txt +++ b/doc/index.txt @@ -68,8 +68,8 @@ Current features support for configuring the installer command through :confval:`install_command=ARGV`. -* **cross-Python compatible**: Python-2.6 up to Python-3.3, - Jython and pypy_ support. +* **cross-Python compatible**: CPython-2.6, 2.7, 3.2 and higher, + Jython and pypy_. * **cross-platform**: Windows and Unix style environments -- cgit v1.2.1 From d37e3d69bf0496828eaf140f16d53db5ae9c8c80 Mon Sep 17 00:00:00 2001 From: Marc Abramowitz Date: Sun, 11 May 2014 22:44:09 -0700 Subject: Add test_posargs_backslashed_or_quoted --- tests/test_config.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/tests/test_config.py b/tests/test_config.py index b8d0aa3..c4ec9cc 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -663,6 +663,23 @@ class TestConfigTestEnv: 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] -- cgit v1.2.1 From da140fed8e96394ad49224a45d76a525b62f00b2 Mon Sep 17 00:00:00 2001 From: holger krekel Date: Thu, 15 May 2014 11:41:19 +0200 Subject: 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. --- CHANGELOG | 6 ++++++ CONTRIBUTORS | 1 + setup.py | 2 +- tox/__init__.py | 2 +- 4 files changed, 9 insertions(+), 2 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 1617043..73408b3 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,6 +1,12 @@ development ----------- +- 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 option "--skip-missing-interpreters" which won't fail the build if Python interpreters listed in tox.ini are missing. Thanks Alexandre Conrad for PR104. diff --git a/CONTRIBUTORS b/CONTRIBUTORS index e2f428c..25c7788 100644 --- a/CONTRIBUTORS +++ b/CONTRIBUTORS @@ -26,3 +26,4 @@ Ionel Maries Cristian Alexandre Conrad Morgan Fainberg Marc Schlaich +Clark Boylan diff --git a/setup.py b/setup.py index 582d315..fe22de3 100644 --- a/setup.py +++ b/setup.py @@ -28,7 +28,7 @@ def main(): description='virtualenv-based automation of test activities', long_description=open("README.rst").read(), url='http://tox.testrun.org/', - version='1.7.1', + version='1.7.2.dev1', license='http://opensource.org/licenses/MIT', platforms=['unix', 'linux', 'osx', 'cygwin', 'win32'], author='holger krekel', diff --git a/tox/__init__.py b/tox/__init__.py index d7b2155..537c37d 100644 --- a/tox/__init__.py +++ b/tox/__init__.py @@ -1,5 +1,5 @@ # -__version__ = '1.7.1' +__version__ = '1.7.2.dev1' class exception: class Error(Exception): -- cgit v1.2.1 From 05d13005fba6e15241488ff677453ad33e6f0c23 Mon Sep 17 00:00:00 2001 From: Evgeny Vereshchagin Date: Sat, 17 May 2014 18:50:58 +0000 Subject: Add deps to basic.txt example. Without deps command doesn't work. --- doc/example/basic.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/example/basic.txt b/doc/example/basic.txt index 19a15d4..ccfa3a5 100644 --- a/doc/example/basic.txt +++ b/doc/example/basic.txt @@ -1,4 +1,3 @@ - Basic usage ============================================= @@ -13,6 +12,7 @@ reside next to your ``setup.py`` file:: [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 -- cgit v1.2.1 From 2fb40807b914cc6e1bc4e5ef877133ab08c1c43a Mon Sep 17 00:00:00 2001 From: Trevor Bekolay Date: Mon, 19 May 2014 18:34:48 +0000 Subject: Better setuptools integration in basic --- doc/example/basic.txt | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/doc/example/basic.txt b/doc/example/basic.txt index 19a15d4..9c2d984 100644 --- a/doc/example/basic.txt +++ b/doc/example/basic.txt @@ -1,4 +1,3 @@ - Basic usage ============================================= @@ -207,6 +206,10 @@ a test run when ``python setup.py test`` is issued:: 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 = [] @@ -214,7 +217,8 @@ a test run when ``python setup.py test`` is issued:: 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( @@ -227,5 +231,9 @@ Now if you run:: 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``. \ No newline at end of file -- cgit v1.2.1 From 858ce1ca31946eec9df56bd7673fa71c07fed644 Mon Sep 17 00:00:00 2001 From: Jihyeok Seo Date: Fri, 30 May 2014 15:15:30 +0900 Subject: we now support py34 --- doc/config-v2.txt | 2 +- doc/example/basic.txt | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/doc/config-v2.txt b/doc/config-v2.txt index cf4cf3b..42973c4 100644 --- a/doc/config-v2.txt +++ b/doc/config-v2.txt @@ -195,7 +195,7 @@ Default settings related to environments names/variants 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 --git a/doc/example/basic.txt b/doc/example/basic.txt index 9c2d984..81f76f3 100644 --- a/doc/example/basic.txt +++ b/doc/example/basic.txt @@ -37,6 +37,7 @@ Available "default" test environments names are:: py31 py32 py33 + py34 jython pypy @@ -236,4 +237,4 @@ using the ``--tox-args`` or ``-a`` command-line options. For example:: python setup.py test -a "-epy27" -is equivalent to running ``tox -epy27``. \ No newline at end of file +is equivalent to running ``tox -epy27``. -- cgit v1.2.1 From 821ec92c19d5ef356947252819b9232288c0b9c2 Mon Sep 17 00:00:00 2001 From: holger krekel Date: Mon, 2 Jun 2014 15:23:35 +0200 Subject: backout change b0afb96 from @schlamar --- tox/_config.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox/_config.py b/tox/_config.py index 5c6398a..0a9dc1a 100644 --- a/tox/_config.py +++ b/tox/_config.py @@ -201,7 +201,7 @@ def get_homedir(): def make_hashseed(): max_seed = 4294967295 - if sys.platform == 'win32' and sys.version_info < (3, 4): + if sys.platform == 'win32': max_seed = 1024 return str(random.randint(1, max_seed)) -- cgit v1.2.1 From 37947b687b27bd8e0a8921d22502629b240c6ecb Mon Sep 17 00:00:00 2001 From: Eugene Yunak Date: Wed, 18 Jun 2014 12:09:13 +0300 Subject: add support for setting skip_missing_interpreters as a config option and not just a command line flag --- CHANGELOG | 3 ++- CONTRIBUTORS | 1 + doc/config.txt | 7 +++++++ tests/test_config.py | 8 ++++++++ tox/_config.py | 4 ++++ 5 files changed, 22 insertions(+), 1 deletion(-) diff --git a/CHANGELOG b/CHANGELOG index 73408b3..b4e7155 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -7,7 +7,8 @@ development resulting in a more refined behaviour in the 1.8 series. And thanks to Clark Boylan for the PR. -- fix issue59: add option "--skip-missing-interpreters" which won't fail the +- 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. diff --git a/CONTRIBUTORS b/CONTRIBUTORS index 25c7788..d10950b 100644 --- a/CONTRIBUTORS +++ b/CONTRIBUTORS @@ -27,3 +27,4 @@ Alexandre Conrad Morgan Fainberg Marc Schlaich Clark Boylan +Eugene Yunak diff --git a/doc/config.txt b/doc/config.txt index a6ca4c8..fd7f79f 100644 --- a/doc/config.txt +++ b/doc/config.txt @@ -22,6 +22,7 @@ List of optional global options:: distshare=path # defaults to {homedir}/.tox/distshare envlist=ENVLIST # defaults to the list of all environments skipsdist=BOOL # defaults to false + skip_missing_interpreters=BOOL # defaults to false ``tox`` autodetects if it is running in a Jenkins_ context @@ -32,6 +33,12 @@ and will first lookup global tox settings in this section:: ... # override [tox] settings for the jenkins context # note: for jenkins distshare defaults to ``{toxworkdir}/distshare``. +Setting ``skip_missing_interpreters`` 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. envlist setting +++++++++++++++ diff --git a/tests/test_config.py b/tests/test_config.py index c55f43a..86f93e7 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -943,6 +943,14 @@ class TestGlobalOptions: config = newconfig([], inisource) assert config.minversion == "3.0" + def test_skip_missing_interpreters(self, tmpdir, newconfig, monkeypatch): + inisource = """ + [tox] + skip_missing_interpreters = True + """ + config = newconfig([], inisource) + assert config.option.skip_missing_interpreters + def test_defaultenv_commandline(self, tmpdir, newconfig, monkeypatch): config = newconfig(["-epy24"], "") env = config.envconfigs['py24'] diff --git a/tox/_config.py b/tox/_config.py index 0a9dc1a..45d626a 100644 --- a/tox/_config.py +++ b/tox/_config.py @@ -239,6 +239,10 @@ class parseini: "{toxinidir}/.tox") config.minversion = reader.getdefault(toxsection, "minversion", None) + if not config.option.skip_missing_interpreters: + config.option.skip_missing_interpreters = \ + reader.getdefault(toxsection, "skip_missing_interpreters", None) + # determine indexserver dictionary config.indexserver = {'default': IndexServerConfig('default')} prefix = "indexserver" -- cgit v1.2.1 From e9011ab3375632098a7d8877927b746b407c41e7 Mon Sep 17 00:00:00 2001 From: Eugene Yunak Date: Wed, 18 Jun 2014 12:53:27 +0300 Subject: skip_missing_interpreters: fix bool handling and add appropriate test, thanks hpk42@ for the tip! --- tests/test_config.py | 10 +++++++++- tox/_config.py | 2 +- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/tests/test_config.py b/tests/test_config.py index 86f93e7..ff743c4 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -943,7 +943,7 @@ class TestGlobalOptions: config = newconfig([], inisource) assert config.minversion == "3.0" - def test_skip_missing_interpreters(self, tmpdir, newconfig, monkeypatch): + def test_skip_missing_interpreters_true(self, tmpdir, newconfig, monkeypatch): inisource = """ [tox] skip_missing_interpreters = True @@ -951,6 +951,14 @@ class TestGlobalOptions: 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'] diff --git a/tox/_config.py b/tox/_config.py index 45d626a..4f89d00 100644 --- a/tox/_config.py +++ b/tox/_config.py @@ -241,7 +241,7 @@ class parseini: if not config.option.skip_missing_interpreters: config.option.skip_missing_interpreters = \ - reader.getdefault(toxsection, "skip_missing_interpreters", None) + reader.getbool(toxsection, "skip_missing_interpreters", False) # determine indexserver dictionary config.indexserver = {'default': IndexServerConfig('default')} -- cgit v1.2.1 From 82bc07071e538b45156ce2baecf747f639559740 Mon Sep 17 00:00:00 2001 From: Eugene Yunak Date: Wed, 18 Jun 2014 13:03:34 +0300 Subject: skip_missing_interpreters: move documentation into a separate confval section --- doc/config.txt | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/doc/config.txt b/doc/config.txt index fd7f79f..23bf128 100644 --- a/doc/config.txt +++ b/doc/config.txt @@ -22,7 +22,6 @@ List of optional global options:: distshare=path # defaults to {homedir}/.tox/distshare envlist=ENVLIST # defaults to the list of all environments skipsdist=BOOL # defaults to false - skip_missing_interpreters=BOOL # defaults to false ``tox`` autodetects if it is running in a Jenkins_ context @@ -33,12 +32,18 @@ and will first lookup global tox settings in this section:: ... # override [tox] settings for the jenkins context # note: for jenkins distshare defaults to ``{toxworkdir}/distshare``. -Setting ``skip_missing_interpreters`` 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. +.. confval:: skip_missing_interpreters=BOOL + + .. versionadded:: 1.7.2 + + 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`` envlist setting +++++++++++++++ -- cgit v1.2.1 From bfb9423685f02a98801981a60720e21dce694b67 Mon Sep 17 00:00:00 2001 From: holger krekel Date: Wed, 18 Jun 2014 12:11:01 +0200 Subject: also put "envlist" into its own section. --- doc/config.txt | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/doc/config.txt b/doc/config.txt index 23bf128..9288ee2 100644 --- a/doc/config.txt +++ b/doc/config.txt @@ -45,15 +45,14 @@ and will first lookup global tox settings in this section:: always overrides this setting if passed on the invokation. **Default:** ``False`` -envlist setting -+++++++++++++++ +.. confval:: envlist=CSV -Determining the environment list that ``tox`` is to operate on -happens in this order: + 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`` + * command line option ``-eENVLIST`` + * environment variable ``TOXENV`` + * ``tox.ini`` file's ``envlist`` Virtualenv test environment settings -- cgit v1.2.1 From c4166e3a4924f860b02c6886a955f3b51bb4f689 Mon Sep 17 00:00:00 2001 From: Alexander Schepanovski Date: Fri, 20 Jun 2014 18:10:04 +0800 Subject: First stab at multidimensional config --- tox/_config.py | 72 +++++++++++++++++++++++++++++++++++++++++++++------------- 1 file changed, 56 insertions(+), 16 deletions(-) diff --git a/tox/_config.py b/tox/_config.py index 4f89d00..5935f74 100644 --- a/tox/_config.py +++ b/tox/_config.py @@ -303,10 +303,18 @@ class parseini: config.skipsdist = reader.getbool(toxsection, "skipsdist", all_develop) - def _makeenvconfig(self, name, section, subs, config): + # interpolate missing configs + for name in config.envlist: + if name not in config.envconfigs: + config.envconfigs[name] = \ + self._makeenvconfig(name, "testenv", reader._subs, config, + factors=name.split('-')) + + def _makeenvconfig(self, name, section, subs, config, factors=()): vc = VenvConfig(envname=name) vc.config = config - reader = IniReader(self._cfg, fallbacksections=["testenv"]) + reader = IniReader(self._cfg, fallbacksections=["testenv"], + factors=factors) reader.addsubstitutions(**subs) vc.develop = not config.option.installpkg and \ reader.getbool(section, "usedevelop", config.option.develop) @@ -391,16 +399,33 @@ class parseini: if not env: env = os.environ.get("TOXENV", None) if not env: - envlist = reader.getlist(toxsection, "envlist", sep=",") + envstr = reader.getdefault(toxsection, "envlist", default="", + replace=False) + envlist = self._expand_envstr(envstr) if not envlist: envlist = self.config.envconfigs.keys() return envlist + # TODO: move envsplit magic to _split_env() envlist = _split_env(env) if "ALL" in envlist: envlist = list(self.config.envconfigs) envlist.sort() return envlist + def _expand_envstr(self, envstr): + from itertools import groupby, product, chain + + # split by commas not in groups + tokens = re.split(r'(\{[^}]+\})|,', envstr) + envlist = [''.join(g).strip() for k, g in groupby(tokens, key=bool) if k] + + def expand(env): + tokens = re.split(r'\{([^}]+)\}', env) + parts = [token.split(',') for token in tokens] + return [''.join(variant) for variant in product(*parts)] + + return list(chain(*map(expand, envlist))) + def _replace_forced_dep(self, name, config): """ Override the given dependency config name taking --force-dep-version @@ -468,9 +493,10 @@ RE_ITEM_REF = re.compile( class IniReader: - def __init__(self, cfgparser, fallbacksections=None): + def __init__(self, cfgparser, fallbacksections=None, factors=()): self._cfg = cfgparser self.fallbacksections = fallbacksections or [] + self.factors = factors self._subs = {} self._subststack = [] @@ -586,18 +612,19 @@ class IniReader: return s def getdefault(self, section, name, default=None, replace=True): - try: - x = self._cfg[section][name] - except KeyError: - for fallbacksection in self.fallbacksections: - try: - x = self._cfg[fallbacksection][name] - except KeyError: - pass - else: - break - else: - x = default + x = None + for s in [section] + self.fallbacksections: + try: + x = self._cfg[s][name] + break + except KeyError: + continue + + if x is None: + x = default + else: + x = self._apply_factors(x) + if replace and x and hasattr(x, 'replace'): self._subststack.append((section, name)) try: @@ -607,6 +634,19 @@ class IniReader: #print "getdefault", section, name, "returned", repr(x) return x + def _apply_factors(self, s): + def factor_line(line): + m = re.search(r'^(!)?(\w+)?\:\s*(.+)', line) + if not m: + return line + + negate, factor, line = m.groups() + if bool(negate) ^ (factor in self.factors): + return line + + lines = s.strip().splitlines() + return '\n'.join(filter(None, map(factor_line, lines))) + def _replace_env(self, match): match_value = match.group('substitution_value') if not match_value: -- cgit v1.2.1 From 2b04e91cfc046b67ee5d2fc3b32357022b6cde37 Mon Sep 17 00:00:00 2001 From: Stefan Scherfke Date: Wed, 25 Jun 2014 09:41:37 +0200 Subject: Add support for PyPy3 (env "pypy3"). --- tests/test_config.py | 8 ++++---- tox/_config.py | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/tests/test_config.py b/tests/test_config.py index ff743c4..bc5a683 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -554,7 +554,7 @@ class TestConfigTestEnv: 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] @@ -916,7 +916,7 @@ class TestGlobalOptions: assert str(env.basepython) == sys.executable def test_default_environments(self, tmpdir, newconfig, monkeypatch): - envs = "py26,py27,py31,py32,py33,jython,pypy" + envs = "py26,py27,py31,py32,py33,py34,jython,pypy,pypy3" inisource = """ [tox] envlist = %s @@ -928,8 +928,8 @@ class TestGlobalOptions: 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]) diff --git a/tox/_config.py b/tox/_config.py index 4f89d00..1495bd8 100644 --- a/tox/_config.py +++ b/tox/_config.py @@ -15,7 +15,7 @@ import tox iswin32 = sys.platform == "win32" -defaultenvs = {'jython': 'jython', 'pypy': 'pypy'} +defaultenvs = {'jython': 'jython', 'pypy': 'pypy', 'pypy3': 'pypy3'} for _name in "py,py24,py25,py26,py27,py30,py31,py32,py33,py34".split(","): if _name == "py": basepython = sys.executable -- cgit v1.2.1 From 8a1d420a321dbb8b8fbb1e74a72a6d0e7114e6eb Mon Sep 17 00:00:00 2001 From: Alexander Schepanovski Date: Sat, 28 Jun 2014 22:17:46 +0800 Subject: Fix regressions after adding factors --- tox/_config.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox/_config.py b/tox/_config.py index 5935f74..39bc375 100644 --- a/tox/_config.py +++ b/tox/_config.py @@ -636,7 +636,7 @@ class IniReader: def _apply_factors(self, s): def factor_line(line): - m = re.search(r'^(!)?(\w+)?\:\s*(.+)', line) + m = re.search(r'^(!)?(\w+)\:\s+(.+)', line) if not m: return line -- cgit v1.2.1 From 578c6f2e23ce585f83e27c9c5866a0e3475b965c Mon Sep 17 00:00:00 2001 From: Alexander Schepanovski Date: Sun, 29 Jun 2014 17:01:28 +0800 Subject: Parse env and args for factors, detect undefined envs --- tox/_config.py | 120 ++++++++++++++++++++++++++++----------------------------- 1 file changed, 58 insertions(+), 62 deletions(-) diff --git a/tox/_config.py b/tox/_config.py index 39bc375..1dadc8a 100644 --- a/tox/_config.py +++ b/tox/_config.py @@ -6,6 +6,7 @@ import re import shlex import string import pkg_resources +import itertools from tox.interpreters import Interpreters @@ -280,22 +281,19 @@ class parseini: config.sdistsrc = reader.getpath(toxsection, "sdistsrc", None) config.setupdir = reader.getpath(toxsection, "setupdir", "{toxinidir}") config.logdir = config.toxworkdir.join("log") - for sectionwrapper in self._cfg: - section = sectionwrapper.name - if section.startswith(testenvprefix): - name = section[len(testenvprefix):] - envconfig = self._makeenvconfig(name, section, reader._subs, - config) - config.envconfigs[name] = envconfig - if not config.envconfigs: - config.envconfigs['python'] = \ - self._makeenvconfig("python", "_xz_9", reader._subs, config) - config.envlist = self._getenvlist(reader, toxsection) - for name in config.envlist: - if name not in config.envconfigs: - if name in defaultenvs: - config.envconfigs[name] = \ - self._makeenvconfig(name, "_xz_9", reader._subs, config) + + config.envlist, all_envs = self._getenvdata(reader, toxsection) + + # configure testenvs + known_factors = self._list_section_factors("testenv") + known_factors.update(defaultenvs) + known_factors.add("python") + for name in all_envs: + section = testenvprefix + name + factors = set(name.split('-')) + if section in self._cfg or factors & known_factors: + config.envconfigs[name] = \ + self._makeenvconfig(name, section, reader._subs, config) all_develop = all(name in config.envconfigs and config.envconfigs[name].develop @@ -303,18 +301,18 @@ class parseini: config.skipsdist = reader.getbool(toxsection, "skipsdist", all_develop) - # interpolate missing configs - for name in config.envlist: - if name not in config.envconfigs: - config.envconfigs[name] = \ - self._makeenvconfig(name, "testenv", reader._subs, config, - factors=name.split('-')) + def _list_section_factors(self, section): + factors = set() + if section in self._cfg: + for _, value in self._cfg[section].items(): + factors.update(re.findall(r'^(!)?(\w+)\:\s+(.+)', value)) + return factors - def _makeenvconfig(self, name, section, subs, config, factors=()): + def _makeenvconfig(self, name, section, subs, config): vc = VenvConfig(envname=name) vc.config = config reader = IniReader(self._cfg, fallbacksections=["testenv"], - factors=factors) + factors=name.split('-')) reader.addsubstitutions(**subs) vc.develop = not config.option.installpkg and \ reader.getbool(section, "usedevelop", config.option.develop) @@ -394,37 +392,25 @@ class parseini: "'install_command' must contain '{packages}' substitution") return vc - def _getenvlist(self, reader, toxsection): - env = self.config.option.env - if not env: - env = os.environ.get("TOXENV", None) - if not env: - envstr = reader.getdefault(toxsection, "envlist", default="", - replace=False) - envlist = self._expand_envstr(envstr) - if not envlist: - envlist = self.config.envconfigs.keys() - return envlist - # TODO: move envsplit magic to _split_env() - envlist = _split_env(env) - if "ALL" in envlist: - envlist = list(self.config.envconfigs) - envlist.sort() - return envlist - - def _expand_envstr(self, envstr): - from itertools import groupby, product, chain - - # split by commas not in groups - tokens = re.split(r'(\{[^}]+\})|,', envstr) - envlist = [''.join(g).strip() for k, g in groupby(tokens, key=bool) if k] - - def expand(env): - tokens = re.split(r'\{([^}]+)\}', env) - parts = [token.split(',') for token in tokens] - return [''.join(variant) for variant in product(*parts)] - - return list(chain(*map(expand, envlist))) + def _getenvdata(self, reader, toxsection): + envstr = self.config.option.env \ + or os.environ.get("TOXENV") \ + or reader.getdefault(toxsection, "envlist", replace=False) \ + or [] + envlist = _split_env(envstr) + + # collect section envs + all_envs = set(envlist) - set(["ALL"]) + for section in self._cfg: + if section.name.startswith(testenvprefix): + all_envs.add(section.name[len(testenvprefix):]) + if not all_envs: + all_envs.add("python") + + if not envlist or "ALL" in envlist: + envlist = sorted(all_envs) + + return envlist, all_envs def _replace_forced_dep(self, name, config): """ @@ -454,15 +440,25 @@ class parseini: def _split_env(env): """if handed a list, action="append" was used for -e """ - envlist = [] if not isinstance(env, list): env = [env] - for to_split in env: - for single_env in to_split.split(","): - # "remove True or", if not allowing multiple same runs, update tests - if True or single_env not in envlist: - envlist.append(single_env) - return envlist + return mapcat(_expand_envstr, env) + +def _expand_envstr(envstr): + # split by commas not in groups + tokens = re.split(r'(\{[^}]+\})|,', envstr) + envlist = [''.join(g).strip() + for k, g in itertools.groupby(tokens, key=bool) if k] + + def expand(env): + tokens = re.split(r'\{([^}]+)\}', env) + parts = [token.split(',') for token in tokens] + return [''.join(variant) for variant in itertools.product(*parts)] + + return mapcat(expand, envlist) + +def mapcat(f, seq): + return list(itertools.chain.from_iterable(map(f, seq))) class DepConfig: def __init__(self, name, indexserver=None): -- cgit v1.2.1 From b445c4e81c404b991c7b643e206562ea0dc1f6b5 Mon Sep 17 00:00:00 2001 From: Alexander Schepanovski Date: Thu, 3 Jul 2014 18:22:28 +0800 Subject: Test factors and envlist expansion --- tests/test_config.py | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/tests/test_config.py b/tests/test_config.py index ff743c4..87ed36a 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -832,6 +832,24 @@ class TestConfigTestEnv: assert conf.changedir.basename == 'testing' assert conf.changedir.dirpath().realpath() == tmpdir.realpath() + def test_factors(self, newconfig): + inisource=""" + [tox] + envlist = a,b + + [testenv] + deps= + dep-all + a: dep-a + b: dep-b + !a: dep-not-a + """ + conf = newconfig([], inisource) + configs = conf.envconfigs + assert [dep.name for dep in configs['a'].deps] == ["dep-all", "dep-a"] + assert [dep.name for dep in configs['b'].deps] == \ + ["dep-all", "dep-b", "dep-not-a"] + class TestGlobalOptions: def test_notest(self, newconfig): config = newconfig([], "") @@ -935,6 +953,23 @@ class TestGlobalOptions: 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] -- cgit v1.2.1 From fa48190fed81c28e393c162ef7189b7cc1f62332 Mon Sep 17 00:00:00 2001 From: Alexander Schepanovski Date: Thu, 3 Jul 2014 18:34:23 +0800 Subject: Fix undefined env check --- tox/_config.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tox/_config.py b/tox/_config.py index 1dadc8a..0d9f8af 100644 --- a/tox/_config.py +++ b/tox/_config.py @@ -291,7 +291,7 @@ class parseini: for name in all_envs: section = testenvprefix + name factors = set(name.split('-')) - if section in self._cfg or factors & known_factors: + if section in self._cfg or factors <= known_factors: config.envconfigs[name] = \ self._makeenvconfig(name, section, reader._subs, config) @@ -305,7 +305,7 @@ class parseini: factors = set() if section in self._cfg: for _, value in self._cfg[section].items(): - factors.update(re.findall(r'^(!)?(\w+)\:\s+(.+)', value)) + factors.update(re.findall(r'^!?(\w+)\:\s+', value, re.M)) return factors def _makeenvconfig(self, name, section, subs, config): -- cgit v1.2.1 From ee2cb51fe1a45f2799859b29aeb83e3a1c5a1a96 Mon Sep 17 00:00:00 2001 From: holger krekel Date: Tue, 15 Jul 2014 09:53:42 +0200 Subject: bump version and release 1.7.2 --- CHANGELOG | 2 +- doc/conf.py | 2 +- setup.py | 2 +- tests/test_venv.py | 2 +- tox/__init__.py | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index b4e7155..88babdc 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,4 +1,4 @@ -development +1.7.2 ----------- - fix issue150: parse {posargs} more like we used to do it pre 1.7.0. diff --git a/doc/conf.py b/doc/conf.py index 76dc4c3..ceb3397 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -48,7 +48,7 @@ copyright = u'2013, holger krekel and others' # built documents. # # The short X.Y version. -release = version = "1.7.1" +release = version = "1.7.2" # The full version, including alpha/beta/rc tags. # The language for content autogenerated by Sphinx. Refer to documentation diff --git a/setup.py b/setup.py index fe22de3..8e2f8f0 100644 --- a/setup.py +++ b/setup.py @@ -28,7 +28,7 @@ def main(): description='virtualenv-based automation of test activities', long_description=open("README.rst").read(), url='http://tox.testrun.org/', - version='1.7.2.dev1', + version='1.7.2', license='http://opensource.org/licenses/MIT', platforms=['unix', 'linux', 'osx', 'cygwin', 'win32'], author='holger krekel', diff --git a/tests/test_venv.py b/tests/test_venv.py index dbfec41..6482e09 100644 --- a/tests/test_venv.py +++ b/tests/test_venv.py @@ -584,7 +584,7 @@ def test_command_relative_issue26(newmocksession, tmpdir, monkeypatch): mocksession.report.not_expect("warning", "*test command found but not*") monkeypatch.setenv("PATH", str(tmpdir)) x4 = venv.getcommandpath("x", cwd=tmpdir) - assert x4.endswith('/x') + assert x4.endswith(os.sep + 'x') mocksession.report.expect("warning", "*test command found but not*") def test_sethome_only_on_option(newmocksession, monkeypatch): diff --git a/tox/__init__.py b/tox/__init__.py index 537c37d..530f3c4 100644 --- a/tox/__init__.py +++ b/tox/__init__.py @@ -1,5 +1,5 @@ # -__version__ = '1.7.2.dev1' +__version__ = '1.7.2' class exception: class Error(Exception): -- cgit v1.2.1 -- cgit v1.2.1 From 1135a2a07207907db8cd24bd6bd5407fa1fe902f Mon Sep 17 00:00:00 2001 From: Alexander Schepanovski Date: Thu, 17 Jul 2014 15:12:02 +0800 Subject: Docs on factors and envlist expansion --- doc/config.txt | 109 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 109 insertions(+) diff --git a/doc/config.txt b/doc/config.txt index 9288ee2..8837664 100644 --- a/doc/config.txt +++ b/doc/config.txt @@ -382,6 +382,115 @@ You can put default values in one section and reference them in others to avoid {[base]deps} +Generating environments and selecting factors +--------------------------------------------- + +.. 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 would be generating ``envlist`` and then selecting +dependencies this way:: + + [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 + !py27: unittest2 + commands = py.test + +Let's go through this step by step. + + +Generating environments ++++++++++++++++++++++++ + +:: + + 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 explicit environments along with generated ones:: + + envlist = {py26,py27}-django{15,16}, docs, flake + + +Factors ++++++++ + +A parts of environment names delimited by hyphens are called factors and could +be used to alter values of ``[testenv]`` settings:: + + 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 + !py27: unittest2 + +A last line here uses negation of a factor, this means ``unittest2`` will be +in ``deps`` for all pythons except python2.7. The whole effect of this setting +definition could be described with a table: + +=============== ================================== +environment deps +=============== ================================== +py26-django15 pytest, Django>=1.5,<1.6, unitest2 +py26-django16 pytest, Django>=1.6,<1.7, unitest2 +py27-django15 pytest, Django>=1.5,<1.6 +py27-django16 pytest, Django>=1.6,<1.7 +=============== ================================== + +And this table can significantly grow as you have more dependencies and other +factors such as platform, python version and/or database. + +.. note:: + + Tox provides good defaults for basepython setting, so the above ini-file can be + further reduced by omitting it. + + +Showing all expanded sections ++++++++++++++++++++++++++++++ + +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 + Other Rules and notes ===================== -- cgit v1.2.1 From d95962973c14568343cafb62be4279d4312d5975 Mon Sep 17 00:00:00 2001 From: Alexander Schepanovski Date: Thu, 17 Jul 2014 16:27:47 +0800 Subject: Reimplement defaultenvs as default factors --- tests/test_config.py | 14 ++++++++++++++ tox/_config.py | 21 ++++++++------------- 2 files changed, 22 insertions(+), 13 deletions(-) diff --git a/tests/test_config.py b/tests/test_config.py index 87ed36a..d7a7451 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -850,6 +850,20 @@ class TestConfigTestEnv: assert [dep.name for dep in configs['b'].deps] == \ ["dep-all", "dep-b", "dep-not-a"] + 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]) + class TestGlobalOptions: def test_notest(self, newconfig): config = newconfig([], "") diff --git a/tox/_config.py b/tox/_config.py index 0d9f8af..3a12d93 100644 --- a/tox/_config.py +++ b/tox/_config.py @@ -16,13 +16,9 @@ import tox iswin32 = sys.platform == "win32" -defaultenvs = {'jython': 'jython', 'pypy': 'pypy'} -for _name in "py,py24,py25,py26,py27,py30,py31,py32,py33,py34".split(","): - if _name == "py": - basepython = sys.executable - else: - basepython = "python" + ".".join(_name[2:4]) - defaultenvs[_name] = basepython +default_factors = {'jython': 'jython', 'pypy': 'pypy', 'py': sys.executable} +for version in '24,25,26,27,30,31,32,33,34'.split(','): + default_factors['py' + version] = 'python%s.%s' % tuple(version) def parseconfig(args=None, pkg=None): if args is None: @@ -286,7 +282,7 @@ class parseini: # configure testenvs known_factors = self._list_section_factors("testenv") - known_factors.update(defaultenvs) + known_factors.update(default_factors) known_factors.add("python") for name in all_envs: section = testenvprefix + name @@ -311,8 +307,9 @@ class parseini: def _makeenvconfig(self, name, section, subs, config): vc = VenvConfig(envname=name) vc.config = config + factors = set(name.split('-')) reader = IniReader(self._cfg, fallbacksections=["testenv"], - factors=name.split('-')) + factors=factors) reader.addsubstitutions(**subs) vc.develop = not config.option.installpkg and \ reader.getbool(section, "usedevelop", config.option.develop) @@ -321,10 +318,8 @@ class parseini: if reader.getdefault(section, "python", None): raise tox.exception.ConfigError( "'python=' key was renamed to 'basepython='") - if name in defaultenvs: - bp = defaultenvs[name] - else: - bp = sys.executable + bp = next((default_factors[f] for f in factors if f in default_factors), + sys.executable) vc.basepython = reader.getdefault(section, "basepython", bp) vc._basepython_info = config.interpreters.get_info(vc.basepython) reader.addsubstitutions(envdir=vc.envdir, envname=vc.envname, -- cgit v1.2.1 From 601f0907efbe85192e5c6a55d99530273687b8de Mon Sep 17 00:00:00 2001 From: holger krekel Date: Sun, 20 Jul 2014 17:36:10 +0200 Subject: some streamlining of the docs --- doc/config.txt | 60 +++++++++++++++++++++++++++++++--------------------------- 1 file changed, 32 insertions(+), 28 deletions(-) diff --git a/doc/config.txt b/doc/config.txt index 8837664..0721a87 100644 --- a/doc/config.txt +++ b/doc/config.txt @@ -382,7 +382,7 @@ You can put default values in one section and reference them in others to avoid {[base]deps} -Generating environments and selecting factors +Generating environments, conditional settings --------------------------------------------- .. versionadded:: 1.8 @@ -392,8 +392,7 @@ 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 would be generating ``envlist`` and then selecting -dependencies this way:: +However, a better approach looks like this:: [tox] envlist = {py26,py27}-django{15,16} @@ -409,10 +408,16 @@ dependencies this way:: !py27: unittest2 commands = py.test -Let's go through this step by step. +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. -Generating environments +Generative envlist +++++++++++++++++++++++ :: @@ -427,16 +432,29 @@ like this:: py27-django15 py27-django16 -You can still list explicit environments along with generated ones:: +You can still list environments explicitely 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 -+++++++ +Factors and factor-conditional settings +++++++++++++++++++++++++++++++++++++++++ -A parts of environment names delimited by hyphens are called factors and could -be used to alter values of ``[testenv]`` settings:: +Parts of an environment name delimited by hyphens are called factors and can +be used to set values conditionally:: basepython = py26: python2.6 @@ -455,7 +473,7 @@ optional lines with unconditional ones:: django16: Django>=1.6,<1.7 !py27: unittest2 -A last line here uses negation of a factor, this means ``unittest2`` will be +The last line here uses negation of a factor, this means ``unittest2`` will be in ``deps`` for all pythons except python2.7. The whole effect of this setting definition could be described with a table: @@ -473,23 +491,9 @@ factors such as platform, python version and/or database. .. note:: - Tox provides good defaults for basepython setting, so the above ini-file can be - further reduced by omitting it. - - -Showing all expanded sections -+++++++++++++++++++++++++++++ - -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 + Tox provides good defaults for basepython setting, so the above + ini-file can be further reduced by omitting the ``basepython`` + setting. Other Rules and notes -- cgit v1.2.1 From 7c8828adf6819dda1ead6c7ee419714f2fc42b8c Mon Sep 17 00:00:00 2001 From: Florian Ludwig Date: Thu, 24 Jul 2014 17:31:35 +0200 Subject: set VIRTUAL_ENV for test commands --- tox/_venv.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tox/_venv.py b/tox/_venv.py index 7e9f45f..df37eac 100644 --- a/tox/_venv.py +++ b/tox/_venv.py @@ -320,6 +320,7 @@ class VirtualEnv(object): setenv = self.envconfig.setenv if setenv: env.update(setenv) + env['VIRTUAL_ENV'] = str(self.path) env.update(extraenv) return env -- cgit v1.2.1 From f481bf7849fd162968bc7e13bc4e70c37a8aba03 Mon Sep 17 00:00:00 2001 From: Thomas Khyn Date: Sat, 2 Aug 2014 16:08:48 +1200 Subject: Fixed console encoding issue The issue occured with python 3.3 on windows with dependencies needing to be compiled (e.g. markupsafe) --- tests/test_venv.py | 2 -- tox/_venv.py | 11 +++++++---- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/tests/test_venv.py b/tests/test_venv.py index 6482e09..f6a2f32 100644 --- a/tests/test_venv.py +++ b/tests/test_venv.py @@ -552,8 +552,6 @@ def test_run_install_command(newmocksession): assert 'install' in l[0].args env = l[0].env assert env is not None - assert 'PYTHONIOENCODING' in env - assert env['PYTHONIOENCODING'] == 'utf_8' def test_run_custom_install_command(newmocksession): mocksession = newmocksession([], """ diff --git a/tox/_venv.py b/tox/_venv.py index 7e9f45f..d5df526 100644 --- a/tox/_venv.py +++ b/tox/_venv.py @@ -1,5 +1,6 @@ from __future__ import with_statement import sys, os +import codecs import py import tox from tox._config import DepConfig @@ -276,11 +277,13 @@ class VirtualEnv(object): del os.environ[x] except KeyError: pass - env = dict(PYTHONIOENCODING='utf_8') - if extraenv is not None: - env.update(extraenv) + old_stdout = sys.stdout + sys.stdout = codecs.getwriter('utf8')(sys.stdout) + if extraenv is None: + extraenv = {} self._pcall(argv, cwd=self.envconfig.config.toxinidir, - extraenv=env, action=action) + extraenv=extraenv, action=action) + sys.stdout = old_stdout def _install(self, deps, extraopts=None, action=None): if not deps: -- cgit v1.2.1 From 7969038573c8f2e7646a048ac8c738cd208ed571 Mon Sep 17 00:00:00 2001 From: Alexander Schepanovski Date: Sat, 9 Aug 2014 20:55:53 +0800 Subject: Remove factor negation support --- tests/test_config.py | 10 +++++----- tox/_config.py | 8 ++++---- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/tests/test_config.py b/tests/test_config.py index 28afa6c..fc429a4 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -835,20 +835,20 @@ class TestConfigTestEnv: def test_factors(self, newconfig): inisource=""" [tox] - envlist = a,b + envlist = a-x,b [testenv] deps= dep-all a: dep-a b: dep-b - !a: dep-not-a + x: dep-x """ conf = newconfig([], inisource) configs = conf.envconfigs - assert [dep.name for dep in configs['a'].deps] == ["dep-all", "dep-a"] - assert [dep.name for dep in configs['b'].deps] == \ - ["dep-all", "dep-b", "dep-not-a"] + 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_default_factors(self, newconfig): inisource=""" diff --git a/tox/_config.py b/tox/_config.py index 6e8e706..2503962 100644 --- a/tox/_config.py +++ b/tox/_config.py @@ -302,7 +302,7 @@ class parseini: factors = set() if section in self._cfg: for _, value in self._cfg[section].items(): - factors.update(re.findall(r'^!?(\w+)\:\s+', value, re.M)) + factors.update(re.findall(r'^(\w+)\:\s+', value, re.M)) return factors def _makeenvconfig(self, name, section, subs, config): @@ -628,12 +628,12 @@ class IniReader: def _apply_factors(self, s): def factor_line(line): - m = re.search(r'^(!)?(\w+)\:\s+(.+)', line) + m = re.search(r'^(\w+)\:\s+(.+)', line) if not m: return line - negate, factor, line = m.groups() - if bool(negate) ^ (factor in self.factors): + factor, line = m.groups() + if factor in self.factors: return line lines = s.strip().splitlines() -- cgit v1.2.1 From 95c5fee5612759f9ec37e97e419776a6a359f3a5 Mon Sep 17 00:00:00 2001 From: Alexander Schepanovski Date: Sat, 9 Aug 2014 22:19:09 +0800 Subject: Support boolean ops on factors --- tests/test_config.py | 18 ++++++++++++++++++ tox/_config.py | 14 ++++++++++---- 2 files changed, 28 insertions(+), 4 deletions(-) diff --git a/tests/test_config.py b/tests/test_config.py index fc429a4..7cbb656 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -850,6 +850,24 @@ class TestConfigTestEnv: ["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] diff --git a/tox/_config.py b/tox/_config.py index 2503962..26603c9 100644 --- a/tox/_config.py +++ b/tox/_config.py @@ -302,7 +302,8 @@ class parseini: factors = set() if section in self._cfg: for _, value in self._cfg[section].items(): - factors.update(re.findall(r'^(\w+)\:\s+', value, re.M)) + exprs = re.findall(r'^([\w{},-]+)\:\s+', value, re.M) + factors.update(*mapcat(_split_factor_expr, exprs)) return factors def _makeenvconfig(self, name, section, subs, config): @@ -434,12 +435,17 @@ class parseini: dep2_name = pkg_resources.Requirement.parse(dep2).project_name return dep1_name == dep2_name + def _split_env(env): """if handed a list, action="append" was used for -e """ if not isinstance(env, list): env = [env] return mapcat(_expand_envstr, env) +def _split_factor_expr(expr): + partial_envs = _expand_envstr(expr) + return [set(e.split('-')) for e in partial_envs] + def _expand_envstr(envstr): # split by commas not in groups tokens = re.split(r'(\{[^}]+\})|,', envstr) @@ -628,12 +634,12 @@ class IniReader: def _apply_factors(self, s): def factor_line(line): - m = re.search(r'^(\w+)\:\s+(.+)', line) + m = re.search(r'^([\w{},-]+)\:\s+(.+)', line) if not m: return line - factor, line = m.groups() - if factor in self.factors: + expr, line = m.groups() + if any(fs <= self.factors for fs in _split_factor_expr(expr)): return line lines = s.strip().splitlines() -- cgit v1.2.1 From a018e738f4e8d700fb6c8d32e6c0d6746d2c2e61 Mon Sep 17 00:00:00 2001 From: Alexander Schepanovski Date: Sat, 9 Aug 2014 22:44:41 +0800 Subject: Docs for factor expressions --- doc/config.txt | 68 +++++++++++++++++++++++++++++++++++++--------------------- 1 file changed, 44 insertions(+), 24 deletions(-) diff --git a/doc/config.txt b/doc/config.txt index 0721a87..a084246 100644 --- a/doc/config.txt +++ b/doc/config.txt @@ -405,7 +405,7 @@ However, a better approach looks like this:: pytest django15: Django>=1.5,<1.6 django16: Django>=1.6,<1.7 - !py27: unittest2 + py26: unittest2 commands = py.test This uses two new facilities of tox-1.8: @@ -417,6 +417,7 @@ This uses two new facilities of tox-1.8: Let's go through this step by step. + Generative envlist +++++++++++++++++++++++ @@ -432,7 +433,7 @@ like this:: py27-django15 py27-django16 -You can still list environments explicitely along with generated ones:: +You can still list environments explicitly along with generated ones:: envlist = {py26,py27}-django{15,16}, docs, flake @@ -441,13 +442,13 @@ You can still list environments explicitely along with generated ones:: 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 + $ tox -l + py26-django15 + py26-django16 + py27-django15 + py27-django16 + docs + flake Factors and factor-conditional settings @@ -471,23 +472,14 @@ optional lines with unconditional ones:: pytest django15: Django>=1.5,<1.6 django16: Django>=1.6,<1.7 - !py27: unittest2 + py26: unittest2 -The last line here uses negation of a factor, this means ``unittest2`` will be -in ``deps`` for all pythons except python2.7. The whole effect of this setting -definition could be described with a table: +Reading it line by line: -=============== ================================== -environment deps -=============== ================================== -py26-django15 pytest, Django>=1.5,<1.6, unitest2 -py26-django16 pytest, Django>=1.6,<1.7, unitest2 -py27-django15 pytest, Django>=1.5,<1.6 -py27-django16 pytest, Django>=1.6,<1.7 -=============== ================================== - -And this table can significantly grow as you have more dependencies and other -factors such as platform, python version and/or database. +- ``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:: @@ -496,6 +488,34 @@ factors such as platform, python version and/or database. 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{25,26,27}-django{14,15,16}-{sqlite,mysql} + + [testenv] + deps = + py25-django14: simplejson ; use it only for this specific combination + py25,py26: unittest2 ; use it for both py25 and py26 + py{25,26}-django14: mock ; patching whatever in older python/django combo + +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 ``simplejson`` will be loaded for python 2.5, django 1.4 +environments, e.g. ``py25-django14-sqlite`` and ``py25-django14-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 ``py25-django14,py26-django14``. + +Finally, factor expressions are expanded the same way as envlist, so a last example +could be rewritten as ``py{25,26}-django14``. + + Other Rules and notes ===================== -- cgit v1.2.1 From 0a8c4513f6ea3f51b2e8861eae2fbb6faa0a3892 Mon Sep 17 00:00:00 2001 From: Alexander Schepanovski Date: Mon, 11 Aug 2014 18:17:59 +0800 Subject: Update complex factor docs --- doc/config.txt | 39 ++++++++++++++++++++++++++------------- 1 file changed, 26 insertions(+), 13 deletions(-) diff --git a/doc/config.txt b/doc/config.txt index a084246..8ce64db 100644 --- a/doc/config.txt +++ b/doc/config.txt @@ -491,29 +491,42 @@ Reading it line by line: 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:: +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{25,26,27}-django{14,15,16}-{sqlite,mysql} + envlist = py{26,27,33}-django{15,16}-{sqlite,mysql} [testenv] deps = - py25-django14: simplejson ; use it only for this specific combination - py25,py26: unittest2 ; use it for both py25 and py26 - py{25,26}-django14: mock ; patching whatever in older python/django combo + 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 ``simplejson`` will be loaded for python 2.5, django 1.4 -environments, e.g. ``py25-django14-sqlite`` and ``py25-django14-mysql``. +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 ``py25-django14,py26-django14``. +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 a last example -could be rewritten as ``py{25,26}-django14``. +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 -- cgit v1.2.1 From d3be3dbf74a11ce4a0631dd4f1879c537060ace4 Mon Sep 17 00:00:00 2001 From: Ivan Larin Date: Tue, 19 Aug 2014 06:58:35 +0000 Subject: docs add pypy3 support --- doc/example/basic.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/doc/example/basic.txt b/doc/example/basic.txt index 6b115c8..562e2bf 100644 --- a/doc/example/basic.txt +++ b/doc/example/basic.txt @@ -41,6 +41,7 @@ Available "default" test environments names are:: py34 jython pypy + pypy3 However, you can also create your own test environment names, see some of the examples in :doc:`examples <../examples>`. -- cgit v1.2.1 From c0af5a5eb243d3eb0b4cdd8e452e00838c187010 Mon Sep 17 00:00:00 2001 From: Steven Myint Date: Mon, 1 Sep 2014 08:12:16 -0700 Subject: Fix "__PYVENV_LAUNCHER__" problem This resolves the use of the non-virtualenv Python executable. This fixes #171. --- tox/_venv.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tox/_venv.py b/tox/_venv.py index d5df526..fea2f0c 100644 --- a/tox/_venv.py +++ b/tox/_venv.py @@ -272,7 +272,8 @@ class VirtualEnv(object): if '{opts}' in argv: i = argv.index('{opts}') argv[i:i+1] = list(options) - for x in ('PIP_RESPECT_VIRTUALENV', 'PIP_REQUIRE_VIRTUALENV'): + for x in ('PIP_RESPECT_VIRTUALENV', 'PIP_REQUIRE_VIRTUALENV', + '__PYVENV_LAUNCHER__'): try: del os.environ[x] except KeyError: -- cgit v1.2.1 From bf52a34ed710df36143bc006fa7750f35550ef9e Mon Sep 17 00:00:00 2001 From: holger krekel Date: Tue, 2 Sep 2014 14:55:16 +0200 Subject: fix issue148: ignore __PYVENV_LAUNCHER__ for subproc --- CHANGELOG | 3 +++ doc/Makefile | 2 +- doc/conf.py | 3 ++- setup.py | 2 +- tests/test_venv.py | 2 ++ tox/__init__.py | 2 +- 6 files changed, 10 insertions(+), 4 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 9d991c7..4597560 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -4,6 +4,9 @@ - new multi-dimensional configuration support. Many thanks to Alexander Schepanovski for the complete PR with docs. +- fix issue148: remove "__PYVENV_LAUNCHER__" from os.environ when starting + subprocesses. Thanks Seven Myint. + 1.7.2 ----------- diff --git a/doc/Makefile b/doc/Makefile index 77b2083..d3393d6 100644 --- a/doc/Makefile +++ b/doc/Makefile @@ -37,7 +37,7 @@ clean: -rm -rf $(BUILDDIR)/* install: clean html - @rsync -avz $(BUILDDIR)/html/ testrun.org:/www/testrun.org/tox/latest + @rsync -avz $(BUILDDIR)/html/ testrun.org:/www/testrun.org/tox/dev #latexpdf #@scp $(BUILDDIR)/latex/*.pdf testrun.org:www-tox/latest diff --git a/doc/conf.py b/doc/conf.py index ceb3397..0221f3c 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -48,7 +48,8 @@ copyright = u'2013, holger krekel and others' # built documents. # # The short X.Y version. -release = version = "1.7.2" +release = "1.8" +version = "1.8.0.dev" # The full version, including alpha/beta/rc tags. # The language for content autogenerated by Sphinx. Refer to documentation diff --git a/setup.py b/setup.py index 8e2f8f0..f8390bb 100644 --- a/setup.py +++ b/setup.py @@ -28,7 +28,7 @@ def main(): description='virtualenv-based automation of test activities', long_description=open("README.rst").read(), url='http://tox.testrun.org/', - version='1.7.2', + version='1.8.0.dev1', license='http://opensource.org/licenses/MIT', platforms=['unix', 'linux', 'osx', 'cygwin', 'win32'], author='holger krekel', diff --git a/tests/test_venv.py b/tests/test_venv.py index f6a2f32..00e5f8b 100644 --- a/tests/test_venv.py +++ b/tests/test_venv.py @@ -485,9 +485,11 @@ class TestVenvTest: py.test.raises(ZeroDivisionError, "venv._pcall([1,2,3])") monkeypatch.setenv("PIP_RESPECT_VIRTUALENV", "1") monkeypatch.setenv("PIP_REQUIRE_VIRTUALENV", "1") + monkeypatch.setenv("__PYVENV_LAUNCHER__", "1") py.test.raises(ZeroDivisionError, "venv.run_install_command(['qwe'])") assert 'PIP_RESPECT_VIRTUALENV' not in os.environ assert 'PIP_REQUIRE_VIRTUALENV' not in os.environ + assert '__PYVENV_LAUNCHER__' not in os.environ def test_setenv_added_to_pcall(tmpdir, mocksession, newconfig): pkg = tmpdir.ensure("package.tar.gz") diff --git a/tox/__init__.py b/tox/__init__.py index 530f3c4..e12f703 100644 --- a/tox/__init__.py +++ b/tox/__init__.py @@ -1,5 +1,5 @@ # -__version__ = '1.7.2' +__version__ = '1.8.0.dev1' class exception: class Error(Exception): -- cgit v1.2.1 From 809e11e42c0526fc1893e940b68c8011cf368459 Mon Sep 17 00:00:00 2001 From: holger krekel Date: Tue, 2 Sep 2014 15:00:04 +0200 Subject: fix typo in stevens name --- CHANGELOG | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG b/CHANGELOG index 4597560..f0aadbc 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -5,7 +5,7 @@ Alexander Schepanovski for the complete PR with docs. - fix issue148: remove "__PYVENV_LAUNCHER__" from os.environ when starting - subprocesses. Thanks Seven Myint. + subprocesses. Thanks Steven Myint. 1.7.2 -- cgit v1.2.1 From 584fd45df00855b87308f4e4fc2d2e16efdbebb3 Mon Sep 17 00:00:00 2001 From: Floris Bruynooghe Date: Mon, 8 Sep 2014 22:24:36 +0100 Subject: Better error reporting for a bad interpreter When an interpreter's binary exists but no valid version_info is extracted the error message right now simply says <2.5 is not supported which is not very helpful. This tries to improve the error reporting for this case. --- tox/_config.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tox/_config.py b/tox/_config.py index 26603c9..4882055 100644 --- a/tox/_config.py +++ b/tox/_config.py @@ -184,6 +184,9 @@ class VenvConfig: info = self.config.interpreters.get_info(self.basepython) if not info.executable: raise tox.exception.InterpreterNotFound(self.basepython) + if not info.version_info: + raise tox.exception.InvocationError( + 'Failed to get version_info for %s: %s' % (info.name, info.err)) if info.version_info < (2,6): raise tox.exception.UnsupportedInterpreter( "python2.5 is not supported anymore, sorry") -- cgit v1.2.1 From d5d42e21988896981f2028b1d986f13c3b5cbeb8 Mon Sep 17 00:00:00 2001 From: holger krekel Date: Tue, 23 Sep 2014 16:04:03 +0200 Subject: fix issue188 make boolean config vars use the default if the config file specifies an empty string --- CHANGELOG | 2 ++ setup.py | 2 +- tests/test_config.py | 15 +++++++++++++++ tox/__init__.py | 2 +- tox/_config.py | 3 +++ 5 files changed, 22 insertions(+), 2 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index f0aadbc..9e69564 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -3,11 +3,13 @@ - new multi-dimensional configuration support. Many thanks to Alexander Schepanovski for the complete PR with docs. + And to Mike Bayer for filing an issue wrt to setting booleans. - fix issue148: remove "__PYVENV_LAUNCHER__" from os.environ when starting subprocesses. Thanks Steven Myint. + 1.7.2 ----------- diff --git a/setup.py b/setup.py index f8390bb..a9ba348 100644 --- a/setup.py +++ b/setup.py @@ -28,7 +28,7 @@ def main(): description='virtualenv-based automation of test activities', long_description=open("README.rst").read(), url='http://tox.testrun.org/', - version='1.8.0.dev1', + version='1.8.0.dev2', license='http://opensource.org/licenses/MIT', platforms=['unix', 'linux', 'osx', 'cygwin', 'win32'], author='holger krekel', diff --git a/tests/test_config.py b/tests/test_config.py index 7cbb656..efbbc80 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -882,6 +882,21 @@ class TestConfigTestEnv: 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 + + class TestGlobalOptions: def test_notest(self, newconfig): config = newconfig([], "") diff --git a/tox/__init__.py b/tox/__init__.py index e12f703..775ed11 100644 --- a/tox/__init__.py +++ b/tox/__init__.py @@ -1,5 +1,5 @@ # -__version__ = '1.8.0.dev1' +__version__ = '1.8.0.dev2' class exception: class Error(Exception): diff --git a/tox/_config.py b/tox/_config.py index 26603c9..07f2365 100644 --- a/tox/_config.py +++ b/tox/_config.py @@ -596,9 +596,12 @@ class IniReader: def getbool(self, section, name, default=None): s = self.getdefault(section, name, default) + if not s: + s = default if s is None: raise KeyError("no config value [%s] %s found" % ( section, name)) + if not isinstance(s, bool): if s.lower() == "true": s = True -- cgit v1.2.1 From 5bd0130f9de902a71afb984a7bfee1921120ab32 Mon Sep 17 00:00:00 2001 From: holger krekel Date: Tue, 23 Sep 2014 16:20:43 +0200 Subject: fix issue152: set VIRTUAL_ENV when running test commands, thanks Florian Ludwig. --- CHANGELOG | 2 ++ tests/test_venv.py | 1 + 2 files changed, 3 insertions(+) diff --git a/CHANGELOG b/CHANGELOG index 9e69564..d4dd250 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -8,6 +8,8 @@ - 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. 1.7.2 diff --git a/tests/test_venv.py b/tests/test_venv.py index 00e5f8b..fe2bed7 100644 --- a/tests/test_venv.py +++ b/tests/test_venv.py @@ -513,6 +513,7 @@ def test_setenv_added_to_pcall(tmpdir, mocksession, newconfig): assert env is not None assert 'ENV_VAR' in env assert env['ENV_VAR'] == 'value' + assert env['VIRTUAL_ENV'] == str(venv.path) for e in os.environ: assert e in env -- cgit v1.2.1 From c0585af5db16b43db28ee9c54e1d3c2edb8404c3 Mon Sep 17 00:00:00 2001 From: holger krekel Date: Tue, 23 Sep 2014 16:38:01 +0200 Subject: add test and changelog for better report if we can't get version_info from an interpreter executable. --- CHANGELOG | 3 +++ tests/test_venv.py | 7 +++++++ 2 files changed, 10 insertions(+) diff --git a/CHANGELOG b/CHANGELOG index d4dd250..3e4f573 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -11,6 +11,9 @@ - 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 ----------- diff --git a/tests/test_venv.py b/tests/test_venv.py index fe2bed7..b1d1f2e 100644 --- a/tests/test_venv.py +++ b/tests/test_venv.py @@ -4,6 +4,7 @@ import pytest import os, sys import tox._config from tox._venv import * # noqa +from tox.interpreters import NoInterpreterInfo #def test_global_virtualenv(capfd): # v = VirtualEnv() @@ -34,6 +35,12 @@ def test_getsupportedinterpreter(monkeypatch, newconfig, mocksession): monkeypatch.setattr(venv.envconfig, 'basepython', 'notexistingpython') py.test.raises(tox.exception.InterpreterNotFound, venv.getsupportedinterpreter) + monkeypatch.undo() + # check that we properly report when no version_info is present + info = NoInterpreterInfo(name=venv.name) + info.executable = "something" + monkeypatch.setattr(config.interpreters, "get_info", lambda *args: info) + pytest.raises(tox.exception.InvocationError, venv.getsupportedinterpreter) def test_create(monkeypatch, mocksession, newconfig): -- cgit v1.2.1 From badeb9829a4bce22dfc9955c5fb59d3d92199743 Mon Sep 17 00:00:00 2001 From: holger krekel Date: Wed, 24 Sep 2014 15:12:16 +0200 Subject: bump versions, add announcement --- CHANGELOG | 4 ++-- doc/Makefile | 2 +- doc/announce/release-1.8.txt | 54 ++++++++++++++++++++++++++++++++++++++++++++ doc/conf.py | 2 +- setup.py | 2 +- tox/__init__.py | 2 +- 6 files changed, 60 insertions(+), 6 deletions(-) create mode 100644 doc/announce/release-1.8.txt diff --git a/CHANGELOG b/CHANGELOG index 3e4f573..62f2d3c 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,9 +1,9 @@ -1.8.0.dev1 +1.8.0 ----------- - new multi-dimensional configuration support. Many thanks to Alexander Schepanovski for the complete PR with docs. - And to Mike Bayer for filing an issue wrt to setting booleans. + And to Mike Bayer and others for testing and feedback. - fix issue148: remove "__PYVENV_LAUNCHER__" from os.environ when starting subprocesses. Thanks Steven Myint. diff --git a/doc/Makefile b/doc/Makefile index d3393d6..77b2083 100644 --- a/doc/Makefile +++ b/doc/Makefile @@ -37,7 +37,7 @@ clean: -rm -rf $(BUILDDIR)/* install: clean html - @rsync -avz $(BUILDDIR)/html/ testrun.org:/www/testrun.org/tox/dev + @rsync -avz $(BUILDDIR)/html/ testrun.org:/www/testrun.org/tox/latest #latexpdf #@scp $(BUILDDIR)/latex/*.pdf testrun.org:www-tox/latest diff --git a/doc/announce/release-1.8.txt b/doc/announce/release-1.8.txt new file mode 100644 index 0000000..2318e54 --- /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/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 --git a/doc/conf.py b/doc/conf.py index 0221f3c..92ed895 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -49,7 +49,7 @@ copyright = u'2013, holger krekel and others' # # The short X.Y version. release = "1.8" -version = "1.8.0.dev" +version = "1.8.0" # The full version, including alpha/beta/rc tags. # The language for content autogenerated by Sphinx. Refer to documentation diff --git a/setup.py b/setup.py index a9ba348..3a9f9bc 100644 --- a/setup.py +++ b/setup.py @@ -28,7 +28,7 @@ def main(): description='virtualenv-based automation of test activities', long_description=open("README.rst").read(), url='http://tox.testrun.org/', - version='1.8.0.dev2', + version='1.8.0', license='http://opensource.org/licenses/MIT', platforms=['unix', 'linux', 'osx', 'cygwin', 'win32'], author='holger krekel', diff --git a/tox/__init__.py b/tox/__init__.py index 775ed11..3ce3574 100644 --- a/tox/__init__.py +++ b/tox/__init__.py @@ -1,5 +1,5 @@ # -__version__ = '1.8.0.dev2' +__version__ = '1.8.0' class exception: class Error(Exception): -- cgit v1.2.1 -- cgit v1.2.1 From 7a67dfecc84c5e4592e751148aed3fb772ec55a2 Mon Sep 17 00:00:00 2001 From: holger krekel Date: Wed, 24 Sep 2014 15:53:48 +0200 Subject: fix release announce --- doc/announce/release-1.8.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/announce/release-1.8.txt b/doc/announce/release-1.8.txt index 2318e54..b8a2218 100644 --- a/doc/announce/release-1.8.txt +++ b/doc/announce/release-1.8.txt @@ -3,7 +3,7 @@ tox 1.8: Generative/combinatorial environments specs I am happy to announce tox 1.8 which implements parametrized environments. -See https://tox.testrun.org/config.html#generating-environments-conditional-settings +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 -- cgit v1.2.1 From f893673691db90335d9f60434fbe7c3ba11a7a2c Mon Sep 17 00:00:00 2001 From: holger krekel Date: Wed, 24 Sep 2014 16:27:55 +0200 Subject: bump to dev version --- setup.py | 2 +- tox/__init__.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/setup.py b/setup.py index 3a9f9bc..9c79cd1 100644 --- a/setup.py +++ b/setup.py @@ -28,7 +28,7 @@ def main(): description='virtualenv-based automation of test activities', long_description=open("README.rst").read(), url='http://tox.testrun.org/', - version='1.8.0', + version='1.8.1.dev1', license='http://opensource.org/licenses/MIT', platforms=['unix', 'linux', 'osx', 'cygwin', 'win32'], author='holger krekel', diff --git a/tox/__init__.py b/tox/__init__.py index 3ce3574..47b85e4 100644 --- a/tox/__init__.py +++ b/tox/__init__.py @@ -1,5 +1,5 @@ # -__version__ = '1.8.0' +__version__ = '1.8.1.dev1' class exception: class Error(Exception): -- cgit v1.2.1 From 5f16ba6fc7dc2d172373c8e1cdeeec553fe2b2e9 Mon Sep 17 00:00:00 2001 From: holger krekel Date: Thu, 25 Sep 2014 15:38:44 +0200 Subject: py25 not supported anymore --- setup.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/setup.py b/setup.py index 9c79cd1..7bd4d40 100644 --- a/setup.py +++ b/setup.py @@ -19,10 +19,8 @@ class Tox(TestCommand): def main(): version = sys.version_info[:2] install_requires = ['virtualenv>=1.11.2', 'py>=1.4.17', ] - if version < (2, 7) or (3, 0) <= version <= (3, 1): + if version < (2, 7): install_requires += ['argparse'] - if version < (2,6): - install_requires += ["simplejson"] setup( name='tox', description='virtualenv-based automation of test activities', -- cgit v1.2.1 From f8a4a6218d4431b1a227f907685e88c5a3ad35a7 Mon Sep 17 00:00:00 2001 From: holger krekel Date: Thu, 25 Sep 2014 15:43:21 +0200 Subject: fix issue190: allow setenv to be empty --- CHANGELOG | 6 ++++++ tests/test_config.py | 14 ++++++++++++++ tox/_config.py | 2 ++ 3 files changed, 22 insertions(+) diff --git a/CHANGELOG b/CHANGELOG index 62f2d3c..3e73166 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,9 @@ +1.8.1.dev +----------- + +- fix issue190: allow setenv to be empty. + + 1.8.0 ----------- diff --git a/tests/test_config.py b/tests/test_config.py index efbbc80..bf74133 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -896,6 +896,20 @@ class TestConfigTestEnv: 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 + class TestGlobalOptions: def test_notest(self, newconfig): diff --git a/tox/_config.py b/tox/_config.py index d0f8c3e..3c1b8c2 100644 --- a/tox/_config.py +++ b/tox/_config.py @@ -526,6 +526,8 @@ class IniReader: value = {} for line in s.split(sep): + if not line.strip(): + continue name, rest = line.split('=', 1) value[name.strip()] = rest.strip() -- cgit v1.2.1 From badbed7115dc6de2207b95f1fd1e7c3fddeced37 Mon Sep 17 00:00:00 2001 From: Marius Gedminas Date: Fri, 26 Sep 2014 15:39:24 +0300 Subject: Report subprocess exit code when invocation fails Fixes #192 --- tests/test_z_cmdline.py | 2 +- tox/_cmdline.py | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/tests/test_z_cmdline.py b/tests/test_z_cmdline.py index 283c993..57aba64 100644 --- a/tests/test_z_cmdline.py +++ b/tests/test_z_cmdline.py @@ -200,7 +200,7 @@ def test_run_custom_install_command_error(cmd, initproj): }) result = cmd.run("tox") result.stdout.fnmatch_lines([ - "ERROR: invocation failed, args: ['*/tox.ini*", + "ERROR: invocation failed (errno 13), args: ['*/tox.ini*", ]) assert result.ret diff --git a/tox/_cmdline.py b/tox/_cmdline.py index aed71ec..0df2f17 100644 --- a/tox/_cmdline.py +++ b/tox/_cmdline.py @@ -95,9 +95,9 @@ class Action(object): try: popen = self._popen(args, cwd, env=env, stdout=f, stderr=STDOUT) - except OSError: - self.report.error("invocation failed, args: %s, cwd: %s" % - (args, cwd)) + except OSError as e: + self.report.error("invocation failed (errno %d), args: %s, cwd: %s" % + (e.errno, args, cwd)) raise popen.outpath = outpath popen.args = [str(x) for x in args] @@ -118,8 +118,8 @@ class Action(object): if ret: invoked = " ".join(map(str, popen.args)) if outpath: - self.report.error("invocation failed, logfile: %s" % - outpath) + self.report.error("invocation failed (exit code %d), logfile: %s" % + (ret, outpath)) out = outpath.read() self.report.error(out) if hasattr(self, "commandlog"): -- cgit v1.2.1 From a06d5df17553b2ef51b092b1aa83db35f967c546 Mon Sep 17 00:00:00 2001 From: Alex Gaynor Date: Fri, 26 Sep 2014 15:36:48 +0000 Subject: Allow "." in factor names for multi-dimensional tests. --- tox/_config.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox/_config.py b/tox/_config.py index 3c1b8c2..5e8765b 100644 --- a/tox/_config.py +++ b/tox/_config.py @@ -305,7 +305,7 @@ class parseini: factors = set() if section in self._cfg: for _, value in self._cfg[section].items(): - exprs = re.findall(r'^([\w{},-]+)\:\s+', value, re.M) + exprs = re.findall(r'^([\w{}\.,-]+)\:\s+', value, re.M) factors.update(*mapcat(_split_factor_expr, exprs)) return factors -- cgit v1.2.1 From 73e938245015e1e19ae6decd6671be3b98c06c60 Mon Sep 17 00:00:00 2001 From: Alex Gaynor Date: Fri, 26 Sep 2014 19:32:11 +0000 Subject: Added a test for period in factor --- tests/test_config.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/tests/test_config.py b/tests/test_config.py index bf74133..615de30 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -909,6 +909,14 @@ class TestConfigTestEnv: 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} + """ + configs = newconfig([], inisource).envconfigs + assert list(configs) == ["py27-django1.6", "py27-django-1.7"] class TestGlobalOptions: -- cgit v1.2.1 From e6c99fa4b0a2056009b69144e3a8ed908e2ac053 Mon Sep 17 00:00:00 2001 From: Marius Gedminas Date: Sun, 28 Sep 2014 12:25:50 +0300 Subject: Do not rely on specific errno numbers in tests The error number differs on different platforms (Windows vs Linux). --- tests/test_z_cmdline.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_z_cmdline.py b/tests/test_z_cmdline.py index 57aba64..00188ac 100644 --- a/tests/test_z_cmdline.py +++ b/tests/test_z_cmdline.py @@ -200,7 +200,7 @@ def test_run_custom_install_command_error(cmd, initproj): }) result = cmd.run("tox") result.stdout.fnmatch_lines([ - "ERROR: invocation failed (errno 13), args: ['*/tox.ini*", + "ERROR: invocation failed (errno *), args: ['*/tox.ini*", ]) assert result.ret -- cgit v1.2.1 From fde8f18e5baa7c4bad05be63847d2bb4ac971248 Mon Sep 17 00:00:00 2001 From: Carl Meyer Date: Mon, 6 Oct 2014 17:36:05 -0600 Subject: Add --pre and testenv pip_pre options, no --pre by default. --- CHANGELOG | 9 +++++++++ doc/config.txt | 17 ++++++++++++++++- tests/test_config.py | 19 +++++++++++++++++++ tests/test_venv.py | 19 +++++++++++++++++++ tox/_config.py | 8 ++++++-- tox/_venv.py | 2 ++ 6 files changed, 71 insertions(+), 3 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 3e73166..711f93e 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,12 @@ +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. + + 1.8.1.dev ----------- diff --git a/doc/config.txt b/doc/config.txt index 8ce64db..2fba5f7 100644 --- a/doc/config.txt +++ b/doc/config.txt @@ -106,7 +106,22 @@ Complete list of settings that you can put into ``testenv*`` sections: **default**:: - pip install --pre {opts} {packages} + pip install {opts} {packages} + +.. 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 diff --git a/tests/test_config.py b/tests/test_config.py index bf74133..79e9a74 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -106,6 +106,7 @@ class TestConfigPackage: 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([], "") @@ -620,6 +621,24 @@ class TestConfigTestEnv: '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(""" diff --git a/tests/test_venv.py b/tests/test_venv.py index b1d1f2e..942888f 100644 --- a/tests/test_venv.py +++ b/tests/test_venv.py @@ -211,6 +211,25 @@ def test_install_deps_indexserver(newmocksession): assert "-i ABC" in args assert "dep3" in args +def test_install_deps_pre(newmocksession): + mocksession = newmocksession([], """ + [testenv] + pip_pre=true + deps= + dep1 + """) + venv = mocksession.getenv('python') + venv.create() + l = mocksession._pcalls + assert len(l) == 1 + l[:] = [] + + venv.install_deps() + assert len(l) == 1 + args = " ".join(l[0].args) + assert "--pre " in args + assert "dep1" in args + def test_installpkg_indexserver(newmocksession, tmpdir): mocksession = newmocksession([], """ [tox] diff --git a/tox/_config.py b/tox/_config.py index 3c1b8c2..a778b11 100644 --- a/tox/_config.py +++ b/tox/_config.py @@ -105,6 +105,8 @@ def prepare_parse(pkgname): dest="indexurl", metavar="URL", help="set indexserver url (if URL is of form name=url set the " "url for the 'name' indexserver, specifically)") + parser.add_argument("--pre", action="store_true", dest="pre", + help="pass --pre option to install_command") parser.add_argument("-r", "--recreate", action="store_true", dest="recreate", help="force recreation of virtual environments") @@ -381,15 +383,17 @@ class parseini: downloadcache = os.environ.get("PIP_DOWNLOAD_CACHE", downloadcache) vc.downloadcache = py.path.local(downloadcache) - pip_default_opts = ["--pre", "{opts}", "{packages}"] vc.install_command = reader.getargv( section, "install_command", - "pip install " + " ".join(pip_default_opts), + "pip install {opts} {packages}", ) if '{packages}' not in vc.install_command: raise tox.exception.ConfigError( "'install_command' must contain '{packages}' substitution") + vc.pip_pre = config.option.pre or reader.getbool( + section, "pip_pre", False) + return vc def _getenvdata(self, reader, toxsection): diff --git a/tox/_venv.py b/tox/_venv.py index 0c56cc0..937a881 100644 --- a/tox/_venv.py +++ b/tox/_venv.py @@ -260,6 +260,8 @@ class VirtualEnv(object): if self.envconfig.downloadcache: self.envconfig.downloadcache.ensure(dir=1) l.append("--download-cache=%s" % self.envconfig.downloadcache) + if self.envconfig.pip_pre: + l.append("--pre") return l def run_install_command(self, packages, options=(), -- cgit v1.2.1 From c5f1982a9d2dabf6f7eeae0e795d74523ad253eb Mon Sep 17 00:00:00 2001 From: Carl Meyer Date: Tue, 7 Oct 2014 10:44:37 -0600 Subject: Expand help text for --pre option. --- tox/_config.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tox/_config.py b/tox/_config.py index a778b11..ee450d8 100644 --- a/tox/_config.py +++ b/tox/_config.py @@ -106,7 +106,8 @@ def prepare_parse(pkgname): help="set indexserver url (if URL is of form name=url set the " "url for the 'name' indexserver, specifically)") parser.add_argument("--pre", action="store_true", dest="pre", - help="pass --pre option to install_command") + help="install pre-releases and development versions of dependencies. " + "This will pass the --pre option to install_command (pip by default).") parser.add_argument("-r", "--recreate", action="store_true", dest="recreate", help="force recreation of virtual environments") -- cgit v1.2.1 From f21eb4bba1e7d18b7718aeaadb9384dd74fcf8e6 Mon Sep 17 00:00:00 2001 From: Alexander Schepanovski Date: Sun, 12 Oct 2014 23:10:19 +0800 Subject: Recognize period in envnames in factor conditions --- tests/test_config.py | 15 +++++++++++---- tox/_config.py | 2 +- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/tests/test_config.py b/tests/test_config.py index 615de30..43b5a34 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -909,14 +909,21 @@ class TestConfigTestEnv: 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} + [tox] + envlist = py27-{django1.6,django1.7} + + [testenv] + deps = + django1.6: Django==1.6 + django1.7: Django==1.7 """ configs = newconfig([], inisource).envconfigs - assert list(configs) == ["py27-django1.6", "py27-django-1.7"] + 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: diff --git a/tox/_config.py b/tox/_config.py index 5e8765b..5d259c4 100644 --- a/tox/_config.py +++ b/tox/_config.py @@ -642,7 +642,7 @@ class IniReader: def _apply_factors(self, s): def factor_line(line): - m = re.search(r'^([\w{},-]+)\:\s+(.+)', line) + m = re.search(r'^([\w{}\.,-]+)\:\s+(.+)', line) if not m: return line -- cgit v1.2.1 From ccb84ba00a039662cf4c60944f76222f4dff236d Mon Sep 17 00:00:00 2001 From: holger krekel Date: Fri, 24 Oct 2014 14:14:11 +0200 Subject: add some changelogs for the merged PRs --- CHANGELOG | 8 ++++++++ doc/config.txt | 4 ++++ 2 files changed, 12 insertions(+) diff --git a/CHANGELOG b/CHANGELOG index 3e73166..d998145 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -3,6 +3,14 @@ - 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 ----------- diff --git a/doc/config.txt b/doc/config.txt index 8ce64db..91587f2 100644 --- a/doc/config.txt +++ b/doc/config.txt @@ -244,6 +244,10 @@ Substitutions 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 ++++++++++++++++++++++++++++++++ -- cgit v1.2.1 From 64597d1c161024ade70b290edb85914e91a759fd Mon Sep 17 00:00:00 2001 From: holger krekel Date: Fri, 24 Oct 2014 14:58:00 +0200 Subject: adapt test from @msabramo about curly braches (fixes related to issue150) --- tests/test_config.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/test_config.py b/tests/test_config.py index c823cb3..443f5bb 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -716,12 +716,12 @@ class TestConfigTestEnv: conf = newconfig([], inisource).envconfigs['py24'] argv = conf.commands assert argv[0] == ['echo', '\\{posargs\\}', '='] - assert argv[1] == ['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'] + assert argv[1] == ['echo', 'posargs = ', 'dog cat'] def test_rewrite_posargs(self, tmpdir, newconfig): inisource = """ -- cgit v1.2.1 From 498053a37892ef116f2dbf313ef98d81b2ab0aa4 Mon Sep 17 00:00:00 2001 From: holger krekel Date: Fri, 24 Oct 2014 15:00:32 +0200 Subject: port fix of test --- tests/test_config.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/test_config.py b/tests/test_config.py index 18ad222..461b7b6 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -735,12 +735,12 @@ class TestConfigTestEnv: conf = newconfig([], inisource).envconfigs['py24'] argv = conf.commands assert argv[0] == ['echo', '\\{posargs\\}', '='] - assert argv[1] == ['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'] + assert argv[1] == ['echo', 'posargs = ', 'dog cat'] def test_rewrite_posargs(self, tmpdir, newconfig): inisource = """ -- cgit v1.2.1 From 455f8035d02631231c6adaa964390e9b751c3bff Mon Sep 17 00:00:00 2001 From: holger krekel Date: Fri, 24 Oct 2014 20:18:37 +0200 Subject: finalize 1.8.1 --- CHANGELOG | 2 +- doc/conf.py | 2 +- setup.py | 2 +- tox/__init__.py | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index d998145..37f963a 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,4 +1,4 @@ -1.8.1.dev +1.8.1 ----------- - fix issue190: allow setenv to be empty. diff --git a/doc/conf.py b/doc/conf.py index 92ed895..3096b10 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -49,7 +49,7 @@ copyright = u'2013, holger krekel and others' # # The short X.Y version. release = "1.8" -version = "1.8.0" +version = "1.8.1" # The full version, including alpha/beta/rc tags. # The language for content autogenerated by Sphinx. Refer to documentation diff --git a/setup.py b/setup.py index 7bd4d40..c8424cf 100644 --- a/setup.py +++ b/setup.py @@ -26,7 +26,7 @@ def main(): description='virtualenv-based automation of test activities', long_description=open("README.rst").read(), url='http://tox.testrun.org/', - version='1.8.1.dev1', + version='1.8.1', license='http://opensource.org/licenses/MIT', platforms=['unix', 'linux', 'osx', 'cygwin', 'win32'], author='holger krekel', diff --git a/tox/__init__.py b/tox/__init__.py index 47b85e4..5c38ad7 100644 --- a/tox/__init__.py +++ b/tox/__init__.py @@ -1,5 +1,5 @@ # -__version__ = '1.8.1.dev1' +__version__ = '1.8.1' class exception: class Error(Exception): -- cgit v1.2.1 -- cgit v1.2.1 From 9bfeba98f75849347f8622bbff048f71d15d60f4 Mon Sep 17 00:00:00 2001 From: holger krekel Date: Mon, 27 Oct 2014 13:06:29 +0100 Subject: fix issue199 -- fill result log structure ahead of creating virtualenv --- CHANGELOG | 6 +++++- setup.py | 2 +- tests/test_result.py | 12 ++++++++++++ tox/__init__.py | 2 +- tox/result.py | 11 ++++------- 5 files changed, 23 insertions(+), 10 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 37f963a..cfb5891 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,8 @@ +1.8.2.dev +----------- + +- fix issue199: fill resultlog structure ahead of virtualenv creation + 1.8.1 ----------- @@ -11,7 +16,6 @@ - report subprocess exit code when execution fails. Thanks Marius Gedminas. - 1.8.0 ----------- diff --git a/setup.py b/setup.py index c8424cf..46730a0 100644 --- a/setup.py +++ b/setup.py @@ -26,7 +26,7 @@ def main(): description='virtualenv-based automation of test activities', long_description=open("README.rst").read(), url='http://tox.testrun.org/', - version='1.8.1', + version='1.8.2.dev1', license='http://opensource.org/licenses/MIT', platforms=['unix', 'linux', 'osx', 'cygwin', 'win32'], author='holger krekel', diff --git a/tests/test_result.py b/tests/test_result.py index 6df834a..206a99c 100644 --- a/tests/test_result.py +++ b/tests/test_result.py @@ -10,6 +10,18 @@ def pkg(tmpdir): p.write("whatever") return p +def test_pre_set_header(pkg): + replog = ResultLog() + d = replog.dict + assert replog.dict == d + assert replog.dict["reportversion"] == "1" + assert replog.dict["toxversion"] == tox.__version__ + assert replog.dict["platform"] == sys.platform + assert replog.dict["host"] == py.std.socket.getfqdn() + data = replog.dumps_json() + replog2 = ResultLog.loads_json(data) + assert replog2.dict == replog.dict + def test_set_header(pkg): replog = ResultLog() d = replog.dict diff --git a/tox/__init__.py b/tox/__init__.py index 5c38ad7..6797068 100644 --- a/tox/__init__.py +++ b/tox/__init__.py @@ -1,5 +1,5 @@ # -__version__ = '1.8.1' +__version__ = '1.8.2.dev1' class exception: class Error(Exception): diff --git a/tox/result.py b/tox/result.py index 694138c..9954044 100644 --- a/tox/result.py +++ b/tox/result.py @@ -1,9 +1,7 @@ import sys import py -try: - import json -except ImportError: - import simplejson as json +from tox import __version__ as toxver +import json class ResultLog: @@ -11,12 +9,11 @@ class ResultLog: if dict is None: dict = {} self.dict = dict - - def set_header(self, installpkg): - from tox import __version__ as toxver self.dict.update({"reportversion": "1", "toxversion": toxver}) self.dict["platform"] = sys.platform self.dict["host"] = py.std.socket.getfqdn() + + def set_header(self, installpkg): self.dict["installpkg"] = dict( md5=installpkg.computehash("md5"), sha256=installpkg.computehash("sha256"), -- cgit v1.2.1 From cfebbb2b8943866739bb09a911b54256bc9365cc Mon Sep 17 00:00:00 2001 From: Tom V Date: Mon, 10 Nov 2014 18:57:49 +0000 Subject: Typo: extra } in docs, basic.txt --- doc/example/basic.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/example/basic.txt b/doc/example/basic.txt index 562e2bf..f9a0606 100644 --- a/doc/example/basic.txt +++ b/doc/example/basic.txt @@ -76,7 +76,7 @@ you can add it to your ``deps`` variable like this:: 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``. -- cgit v1.2.1 From 441f908c8a28dbc231516a607d594ad104edd86c Mon Sep 17 00:00:00 2001 From: Borge Lanes Date: Tue, 2 Dec 2014 09:03:48 +0100 Subject: Determine Jenkins environment as described in docs HUDSON_URL is still checked for backward compatibility. --- tox/_config.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox/_config.py b/tox/_config.py index ceae6b9..41f02ec 100644 --- a/tox/_config.py +++ b/tox/_config.py @@ -802,7 +802,7 @@ class CommandParser(object): return ps.yield_words def getcontextname(): - if 'HUDSON_URL' in os.environ: + if any(env in os.environ for env in ['JENKINS_URL', 'HUDSON_URL']): return 'jenkins' return None -- cgit v1.2.1 From a38d68b06cf4e712384ef3531f0abaa5a00e5aff Mon Sep 17 00:00:00 2001 From: Borge Lanes Date: Wed, 10 Dec 2014 22:37:30 +0100 Subject: Add test for getcontextname() --- tests/test_config.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/tests/test_config.py b/tests/test_config.py index 461b7b6..1c58a4a 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -148,6 +148,20 @@ def test_get_homedir(monkeypatch): 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(""" -- cgit v1.2.1 From e3486379267657e8b7d20319a9445626f93def59 Mon Sep 17 00:00:00 2001 From: holger krekel Date: Thu, 11 Dec 2014 12:14:24 +0100 Subject: refine determination if we run from Jenkins, thanks Borge Lanes. --- CHANGELOG | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG b/CHANGELOG index 9dc44da..9542099 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -8,6 +8,8 @@ - fix issue199: fill resultlog structure ahead of virtualenv creation +- refine determination if we run from Jenkins, thanks Borge Lanes. + 1.8.1 ----------- -- cgit v1.2.1 From ac65fa83ff498ab5ee02a3cc73deef3c30619a00 Mon Sep 17 00:00:00 2001 From: Alexandre Conrad Date: Sat, 20 Dec 2014 00:54:58 +0000 Subject: explain how to disable PYTHONHASHSEED --- CHANGELOG | 2 +- doc/example/basic.txt | 6 +++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 9542099..55ab8ae 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -95,7 +95,7 @@ - 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=notset to instruct tox to leave the value + 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) diff --git a/doc/example/basic.txt b/doc/example/basic.txt index f9a0606..49baeee 100644 --- a/doc/example/basic.txt +++ b/doc/example/basic.txt @@ -191,10 +191,14 @@ 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:hash] + [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 -- cgit v1.2.1