4 new commits in tox: https://bitbucket.org/hpk42/tox/commits/4949def7e678/ Changeset: 4949def7e678 User: hpk42 Date: 2013-08-15 13:00:26 Summary: extend pseudo-homedir with .pip/pip.conf so that any "pip" command triggered by tests (such as tox' tests itself) will pick it up. Affected #: 2 files
diff -r 394c7438efdcae278cca3c0bbd6bde6f32aa4362 -r 4949def7e678884e07157f12d7c33aead42227f7 tests/test_venv.py --- a/tests/test_venv.py +++ b/tests/test_venv.py @@ -632,3 +632,15 @@ assert env["HOME"] == str(tmpdir) assert not tmpdir.join(".pydistutils.cfg").check() assert "PIP_INDEX_URL" not in env + +def test_hack_home_env_passthrough(tmpdir, monkeypatch): + from tox._venv import hack_home_env + env = hack_home_env(tmpdir, "http://index") + monkeypatch.setattr(os, "environ", env) + + tmpdir = tmpdir.mkdir("tmpdir2") + env2 = hack_home_env(tmpdir) + assert env2["HOME"] == str(tmpdir) + assert env2["PIP_INDEX_URL"] == "http://index" + assert "index_url = http://index" in \ + tmpdir.join(".pydistutils.cfg").read() diff -r 394c7438efdcae278cca3c0bbd6bde6f32aa4362 -r 4949def7e678884e07157f12d7c33aead42227f7 tox/_venv.py --- a/tox/_venv.py +++ b/tox/_venv.py @@ -457,7 +457,7 @@ locate_via_py(*m.groups()) -def hack_home_env(homedir, index_url): +def hack_home_env(homedir, index_url=None): # XXX HACK (this could also live with tox itself, consider) # if tox uses pip on a package that requires setup_requires # the index url set with pip is usually not recognized @@ -468,9 +468,12 @@ if not homedir.check(): homedir.ensure(dir=1) d = dict(HOME=str(homedir)) + if not index_url: + index_url = os.environ.get("TOX_INDEX_URL") if index_url: homedir.join(".pydistutils.cfg").write( "[easy_install]\n" "index_url = %s\n" % index_url) d["PIP_INDEX_URL"] = index_url + d["TOX_INDEX_URL"] = index_url return d https://bitbucket.org/hpk42/tox/commits/50bd50082b7c/ Changeset: 50bd50082b7c User: hpk42 Date: 2013-08-15 13:00:38 Summary: refine python2.5 and install_command behaviour and documentation. also show i in "--showconfig" and rename internally from "install_command_argv" to install_command for consistency. also deprecate downloadcache, distribute settings. Affected #: 8 files diff -r 4949def7e678884e07157f12d7c33aead42227f7 -r 50bd50082b7cacf9a11ed5605d0af3383db23e8b CHANGELOG --- a/CHANGELOG +++ b/CHANGELOG @@ -1,6 +1,17 @@ 1.6.0.dev ----------------- +- fix issue35: add new EXPERIMENTAL "install_command" testenv-option to configure the + installation command with options for dep/pkg install. Thanks Carl Meyer + for the PR and docs. + + +- address issueintroduce python2.5 support by vendoring the virtualenv-1.9.1 script + and forcing pip<1.4. Also the default [py25] environment modifies the + default installer_command (new config option) to use pip without the "--pre" + which was introduced with pip-1.4 and is required if you want to install non-stable releases. + (tox defaults to install with "--pre" otherwise). + - fix issue1: empty setup files are properly detected, thanks Anthon van der Neuth @@ -11,10 +22,6 @@ - remove toxbootstrap.py for now because it is broken. -- fix issue35: add new "install_command" testenv-option to configure the - installation command with options for dep/pkg install. Thanks Carl Meyer - for the PR and docs. - - fix issue109 and fix issue111: multiple "-e" options are now combined (previously the last one would win). Thanks Anthon van der Neut. diff -r 4949def7e678884e07157f12d7c33aead42227f7 -r 50bd50082b7cacf9a11ed5605d0af3383db23e8b doc/config.txt --- a/doc/config.txt +++ b/doc/config.txt @@ -80,9 +80,12 @@ .. versionadded:: 1.6 - the command to be used for installing packages into the virtual - environment; both the sdist for the package under test and any - defined dependencies. Must contain the substitution key + **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 + the virtual environment; both the package under test + and any defined dependencies. Must contain the substitution key ``{packages}`` which will be replaced by the packages to install. May also contain the substitution key ``{opts}``, which will be replaced by the ``-i`` option to specify index server @@ -94,7 +97,16 @@ :confval:`downloadcache`, and/or your :confval:`install_command` should not include the ``{opts}`` substitution key (in which case those options will have no effect). - **default**: ``pip install {opts} {packages}`` + **default**:: + + pip install --pre {opts} {packages} + + **default on environment names containing 'py25'**:: + + pip install --insecure {opts} {packages}`` + + (this will use pip<1.4 (so no "--pre" option) and + python2.5 typically has no SSL support). .. confval:: whitelist_externals=MULTI-LINE-LIST @@ -137,37 +149,41 @@ .. confval:: downloadcache=path + **DEPRECATED** -- as of August 2013 this option is not very + useful because of pypi's CDN and because of caching pypi + server solutions like `devpi <http://doc.devpi.net>`_. + use this directory for caching downloads. This value is overriden by the environment variable ``PIP_DOWNLOAD_CACHE`` if it exists. If you specify a custom :confval:`install_command` that uses an installer other than pip, your installer must support the `--download-cache` command-line option. **default**: no download cache will be used. - **note**: if creating multiple environments use of a download cache greatly - speeds up the testing process. .. confval:: distribute=True|False - Set to ``True`` if you want to use distribute_ instead of the default - setuptools_ in the virtual environment. Prior to tox-1.5 the - default was True and now is False, meaning ``setuptools`` is used - (note that setuptools-0.7 merged with distribute). In future versions - of tox this option might be ignored and setuptools always chosen. + **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. + **default:** False. .. confval:: sitepackages=True|False Set to ``True`` if you want to create virtual environments that also - have access to globally installed packages. **default:** False, meaning - that virtualenvs will be created with ``--no-site-packages`` by default. + have access to globally installed packages. + + **default:** False, meaning that virtualenvs will be + created without inheriting the global site packages. .. confval:: args_are_paths=BOOL 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 usually - safe to try rewriting). + **default**: True (due to the exists-on-filesystem check it's + usually safe to try rewriting). .. confval:: envtmpdir=path diff -r 4949def7e678884e07157f12d7c33aead42227f7 -r 50bd50082b7cacf9a11ed5605d0af3383db23e8b tests/test_config.py --- a/tests/test_config.py +++ b/tests/test_config.py @@ -473,14 +473,28 @@ assert envconfig.changedir.basename == "abc" assert envconfig.changedir == config.setupdir.join("abc") - def test_install_command(self, newconfig): + def test_install_command_defaults_py25(self, newconfig): + config = newconfig(""" + [testenv:py25] + [testenv:py25-x] + [testenv:py26] + """) + for name in ("py25", "py25-x"): + env = config.envconfigs[name] + assert env.install_command == \ + "pip install --insecure {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] - install_command=pip install --pre {packages} + install_command=some_install {packages} """) envconfig = config.envconfigs['python'] - assert envconfig.install_command_argv == [ - 'pip', 'install', '--pre', '{packages}'] + assert envconfig.install_command == [ + 'some_install', '{packages}'] def test_install_command_must_contain_packages(self, newconfig): py.test.raises(tox.exception.ConfigError, newconfig, """ @@ -506,7 +520,8 @@ envconfig = config.envconfigs['python'] assert envconfig.downloadcache == '/from/env' - def test_downloadcache_only_if_in_config(self, newconfig, tmpdir, monkeypatch): + def test_downloadcache_only_if_in_config(self, newconfig, tmpdir, + monkeypatch): monkeypatch.setenv("PIP_DOWNLOAD_CACHE", tmpdir) config = newconfig('') envconfig = config.envconfigs['python'] @@ -744,7 +759,8 @@ config = newconfig(["-vv"], "") assert config.option.verbosity == 2 - def test_substitution_jenkins_default(self, tmpdir, monkeypatch, newconfig): + def test_substitution_jenkins_default(self, tmpdir, + monkeypatch, newconfig): monkeypatch.setenv("HUDSON_URL", "xyz") config = newconfig(""" [testenv:py24] diff -r 4949def7e678884e07157f12d7c33aead42227f7 -r 50bd50082b7cacf9a11ed5605d0af3383db23e8b tests/test_z_cmdline.py --- a/tests/test_z_cmdline.py +++ b/tests/test_z_cmdline.py @@ -10,11 +10,6 @@ pytest_plugins = "pytester" -if sys.version_info < (2,6): - PIP_INSECURE = "setenv = PIP_INSECURE=1" -else: - PIP_INSECURE = "" - from tox._cmdline import Session from tox._config import parseconfig @@ -379,10 +374,10 @@ 'tox.ini': ''' [testenv] changedir=tests - %s - commands= py.test --basetemp={envtmpdir} --junitxml=junit-{envname}.xml + commands= py.test --basetemp={envtmpdir} \ + --junitxml=junit-{envname}.xml deps=pytest - ''' % PIP_INSECURE + ''' }) def test_toxuone_env(self, cmd, example123): @@ -473,11 +468,10 @@ [testenv] usedevelop=True changedir=tests - %s commands= py.test --basetemp={envtmpdir} --junitxml=junit-{envname}.xml [] deps=pytest - ''' % PIP_INSECURE + ''' }) result = cmd.run("tox", "-v") assert not result.ret @@ -521,9 +515,9 @@ # content of: tox.ini [testenv] commands=pip -h - [testenv:py25] + [testenv:py26] basepython=python - [testenv:py26] + [testenv:py27] basepython=python """}) result = cmd.run("tox") @@ -532,19 +526,19 @@ def test_notest(initproj, cmd): initproj("example123", filedefs={'tox.ini': """ # content of: tox.ini - [testenv:py25] + [testenv:py26] basepython=python """}) result = cmd.run("tox", "-v", "--notest") assert not result.ret result.stdout.fnmatch_lines([ "*summary*", - "*py25*skipped tests*", + "*py26*skipped tests*", ]) - result = cmd.run("tox", "-v", "--notest", "-epy25") + result = cmd.run("tox", "-v", "--notest", "-epy26") assert not result.ret result.stdout.fnmatch_lines([ - "*py25*reusing*", + "*py26*reusing*", ]) def test_PYC(initproj, cmd, monkeypatch): diff -r 4949def7e678884e07157f12d7c33aead42227f7 -r 50bd50082b7cacf9a11ed5605d0af3383db23e8b tox.ini --- a/tox.ini +++ b/tox.ini @@ -8,10 +8,6 @@ commands=py.test --junitxml={envlogdir}/junit-{envname}.xml {posargs} deps=pytest>=2.3.5 -[testenv:py25] # requires virtualenv-1.9.1 -setenvs = - PIP_INSECURE=True - [testenv:docs] basepython=python changedir=doc diff -r 4949def7e678884e07157f12d7c33aead42227f7 -r 50bd50082b7cacf9a11ed5605d0af3383db23e8b tox/_cmdline.py --- a/tox/_cmdline.py +++ b/tox/_cmdline.py @@ -492,6 +492,8 @@ self.report.line(" envlogdir=%s" % envconfig.envlogdir) self.report.line(" changedir=%s" % envconfig.changedir) self.report.line(" args_are_path=%s" % envconfig.args_are_paths) + self.report.line(" install_command=%s" % + envconfig.install_command) self.report.line(" commands=") for command in envconfig.commands: self.report.line(" %s" % command) diff -r 4949def7e678884e07157f12d7c33aead42227f7 -r 50bd50082b7cacf9a11ed5605d0af3383db23e8b tox/_config.py --- a/tox/_config.py +++ b/tox/_config.py @@ -76,8 +76,8 @@ parser.add_argument("-v", nargs=0, action=CountAction, default=0, dest="verbosity", help="increase verbosity of reporting output.") - parser.add_argument("--showconfig", action="store_true", dest="showconfig", - help="show configuration information. ") + parser.add_argument("--showconfig", action="store_true", + help="show configuration information for all environments. ") parser.add_argument("-l", "--listenvs", action="store_true", dest="listenvs", help="show list of test environments") parser.add_argument("-c", action="store", default="tox.ini", @@ -331,15 +331,24 @@ # env var, if present, takes precedence downloadcache = os.environ.get("PIP_DOWNLOAD_CACHE", downloadcache) vc.downloadcache = py.path.local(downloadcache) - vc.install_command_argv = reader.getargv( + + # on python 2.5 we can't use "--pre" and we typically + # need to use --insecure for pip commands because python2.5 + # doesn't support SSL + pip_default_opts = ["{opts}", "{packages}"] + if "py25" in vc.envname: # XXX too rough check for "python2.5" + pip_default_opts.insert(0, "--insecure") + else: + pip_default_opts.insert(0, "--pre") + vc.install_command = reader.getargv( section, "install_command", - "pip install {opts} {packages}", + "pip install " + " ".join(pip_default_opts), replace=False, ) - if '{packages}' not in vc.install_command_argv: + if '{packages}' not in vc.install_command: raise tox.exception.ConfigError( - "'install_command' must contain '{packages}' substitution") + "'install_command' must contain '{packages}' substitution") return vc def _getenvlist(self, reader, toxsection): diff -r 4949def7e678884e07157f12d7c33aead42227f7 -r 50bd50082b7cacf9a11ed5605d0af3383db23e8b tox/_venv.py --- a/tox/_venv.py +++ b/tox/_venv.py @@ -285,7 +285,7 @@ def run_install_command(self, args, indexserver=None, action=None, extraenv=None): - argv = self.envconfig.install_command_argv[:] + 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" https://bitbucket.org/hpk42/tox/commits/efe7d94c863a/ Changeset: efe7d94c863a User: hpk42 Date: 2013-08-15 13:00:39 Summary: move all interpreter information detection to tox/interpreters.py Affected #: 9 files diff -r 50bd50082b7cacf9a11ed5605d0af3383db23e8b -r efe7d94c863aabd6ea2d1513b4513ce9b0d4291c CHANGELOG --- a/CHANGELOG +++ b/CHANGELOG @@ -5,7 +5,6 @@ installation command with options for dep/pkg install. Thanks Carl Meyer for the PR and docs. - - address issueintroduce python2.5 support by vendoring the virtualenv-1.9.1 script and forcing pip<1.4. Also the default [py25] environment modifies the default installer_command (new config option) to use pip without the "--pre" @@ -36,6 +35,9 @@ - if a HOMEDIR cannot be determined, use the toxinidir. +- refactor interpreter information detection to live in new + tox/interpreters.py file, tests in tests/test_interpreters.py. + 1.5.0 ----------------- diff -r 50bd50082b7cacf9a11ed5605d0af3383db23e8b -r efe7d94c863aabd6ea2d1513b4513ce9b0d4291c doc/config.txt --- a/doc/config.txt +++ b/doc/config.txt @@ -88,8 +88,8 @@ and any defined dependencies. Must contain the substitution key ``{packages}`` which will be replaced by the packages to install. May also contain the substitution key ``{opts}``, which - will be replaced by the ``-i`` option to specify index server - (according to :confval:`indexserver` and the ``:indexserver:dep`` + will be replaced by the ``-i INDEXURL`` option if an index server + is active (see :confval:`indexserver` and the ``:indexserver:dep`` syntax of :confval:`deps`) and the ``--download-cache`` option (if you've specified :confval:`downloadcache`). If your installer does not support ``-i`` and ``--download-cache`` command-line options, diff -r 50bd50082b7cacf9a11ed5605d0af3383db23e8b -r efe7d94c863aabd6ea2d1513b4513ce9b0d4291c tests/test_config.py --- a/tests/test_config.py +++ b/tests/test_config.py @@ -5,9 +5,9 @@ from textwrap import dedent import py -from tox._config import IniReader, CommandParser -from tox._config import parseconfig -from tox._config import prepare_parse, _split_env +from tox._config import * +from tox._config import _split_env + class TestVenvConfig: def test_config_parsing_minimal(self, tmpdir, newconfig): @@ -473,13 +473,29 @@ assert envconfig.changedir.basename == "abc" assert envconfig.changedir == config.setupdir.join("abc") - def test_install_command_defaults_py25(self, newconfig): + 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:py25] + [testenv:x25] + basepython = x25 [testenv:py25-x] + basepython = x25 [testenv:py26] + basepython = "python" """) - for name in ("py25", "py25-x"): + for name in ("x25", "py25-x"): env = config.envconfigs[name] assert env.install_command == \ "pip install --insecure {opts} {packages}".split() @@ -714,36 +730,6 @@ assert conf.changedir.basename == 'testing' assert conf.changedir.dirpath().realpath() == tmpdir.realpath() - @pytest.mark.xfailif("sys.platform == 'win32'") - def test_substitution_envsitepackagesdir(self, tmpdir, monkeypatch, - newconfig): - """ - The envsitepackagesdir property is mostly doing system work, - so this test doesn't excercise it very well. - - Usage of envsitepackagesdir on win32/jython will explicitly - throw an exception, - """ - class MockPopen(object): - returncode = 0 - - def __init__(self, *args, **kwargs): - pass - - def communicate(self, *args, **kwargs): - return 'onevalue', 'othervalue' - - monkeypatch.setattr(subprocess, 'Popen', MockPopen) - env = 'py%s' % (''.join(sys.version.split('.')[0:2])) - config = newconfig(""" - [testenv:%s] - commands = {envsitepackagesdir} - """ % (env)) - conf = config.envconfigs[env] - argv = conf.commands - assert argv[0][0] == 'onevalue' - - class TestGlobalOptions: def test_notest(self, newconfig): config = newconfig([], "") diff -r 50bd50082b7cacf9a11ed5605d0af3383db23e8b -r efe7d94c863aabd6ea2d1513b4513ce9b0d4291c tests/test_interpreters.py --- /dev/null +++ b/tests/test_interpreters.py @@ -0,0 +1,95 @@ +import sys +import os + +import pytest +from tox.interpreters import * + +@pytest.fixture +def interpreters(): + return Interpreters() + +@pytest.mark.skipif("sys.platform != 'win32'") +def test_locate_via_py(monkeypatch): + from tox._venv import locate_via_py + class PseudoPy: + def sysexec(self, *args): + assert args[0] == '-3.2' + assert args[1] == '-c' + # Return value needs to actually exist! + return sys.executable + @staticmethod + def ret_pseudopy(name): + assert name == 'py' + return PseudoPy() + # Monkeypatch py.path.local.sysfind to return PseudoPy + monkeypatch.setattr(py.path.local, 'sysfind', ret_pseudopy) + assert locate_via_py('3', '2') == sys.executable + +def test_find_executable(): + p = find_executable(sys.executable) + assert p == py.path.local(sys.executable) + for ver in [""] + "2.4 2.5 2.6 2.7 3.0 3.1 3.2 3.3".split(): + name = "python%s" % ver + if sys.platform == "win32": + pydir = "python%s" % ver.replace(".", "") + x = py.path.local("c:\%s" % pydir) + print (x) + if not x.check(): + continue + else: + if not py.path.local.sysfind(name): + continue + p = find_executable(name) + assert p + popen = py.std.subprocess.Popen([str(p), '-V'], + stderr=py.std.subprocess.PIPE) + stdout, stderr = popen.communicate() + assert ver in py.builtin._totext(stderr, "ascii") + +def test_find_executable_extra(monkeypatch): + @staticmethod + def sysfind(x): + return "hello" + monkeypatch.setattr(py.path.local, "sysfind", sysfind) + t = find_executable("qweqwe") + assert t == "hello" + +def test_run_and_get_interpreter_info(): + name = os.path.basename(sys.executable) + info = run_and_get_interpreter_info(name, sys.executable) + assert info.version_info == tuple(sys.version_info) + assert info.name == name + assert info.executable == sys.executable + +class TestInterpreters: + + def test_get_info_self_exceptions(self, interpreters): + pytest.raises(ValueError, lambda: + interpreters.get_info()) + pytest.raises(ValueError, lambda: + interpreters.get_info(name="12", executable="123")) + + def test_get_executable(self, interpreters): + x = interpreters.get_executable(sys.executable) + assert x == sys.executable + assert not interpreters.get_executable("12l3k1j23") + + def test_get_info__name(self, interpreters): + basename = os.path.basename(sys.executable) + info = interpreters.get_info(basename) + assert info.version_info == tuple(sys.version_info) + assert info.name == basename + assert info.executable == sys.executable + assert info.runnable + + def test_get_info__name_not_exists(self, interpreters): + info = interpreters.get_info("qlwkejqwe") + assert not info.version_info + assert info.name == "qlwkejqwe" + assert not info.executable + assert not info.runnable + + def test_get_sitepackagesdir_error(self, interpreters): + info = interpreters.get_info(sys.executable) + s = interpreters.get_sitepackagesdir(info, "") + assert s diff -r 50bd50082b7cacf9a11ed5605d0af3383db23e8b -r efe7d94c863aabd6ea2d1513b4513ce9b0d4291c tests/test_venv.py --- a/tests/test_venv.py +++ b/tests/test_venv.py @@ -2,9 +2,7 @@ import tox import pytest import os, sys -from tox._venv import VirtualEnv, CreationConfig, getdigest -from tox._venv import find_executable -from tox._venv import _getinterpreterversion +from tox._venv import * py25calls = int(sys.version_info[:2] == (2,5)) @@ -19,52 +17,6 @@ def test_getdigest(tmpdir): assert getdigest(tmpdir) == "0"*32 -@pytest.mark.skipif("sys.platform != 'win32'") -def test_locate_via_py(monkeypatch): - from tox._venv import locate_via_py - class PseudoPy: - def sysexec(self, *args): - assert args[0] == '-3.2' - assert args[1] == '-c' - # Return value needs to actually exist! - return sys.executable - @staticmethod - def ret_pseudopy(name): - assert name == 'py' - return PseudoPy() - # Monkeypatch py.path.local.sysfind to return PseudoPy - monkeypatch.setattr(py.path.local, 'sysfind', ret_pseudopy) - assert locate_via_py('3', '2') == sys.executable - -def test_find_executable(): - p = find_executable(sys.executable) - assert p == py.path.local(sys.executable) - for ver in [""] + "2.4 2.5 2.6 2.7 3.0 3.1 3.2 3.3".split(): - name = "python%s" % ver - if sys.platform == "win32": - pydir = "python%s" % ver.replace(".", "") - x = py.path.local("c:\%s" % pydir) - print (x) - if not x.check(): - continue - else: - if not py.path.local.sysfind(name): - continue - p = find_executable(name) - assert p - popen = py.std.subprocess.Popen([str(p), '-V'], - stderr=py.std.subprocess.PIPE) - stdout, stderr = popen.communicate() - assert ver in py.builtin._totext(stderr, "ascii") - -def test_find_executable_extra(monkeypatch): - @staticmethod - def sysfind(x): - return "hello" - monkeypatch.setattr(py.path.local, "sysfind", sysfind) - t = find_executable("qweqwe") - assert t == "hello" - def test_getsupportedinterpreter(monkeypatch, newconfig, mocksession): config = newconfig([], """ [testenv:python] @@ -83,10 +35,6 @@ py.test.raises(tox.exception.InterpreterNotFound, venv.getsupportedinterpreter) -def test_getinterpreterversion(): - from distutils.sysconfig import get_python_version - version = _getinterpreterversion(sys.executable) - assert version == get_python_version() def test_create(monkeypatch, mocksession, newconfig): config = newconfig([], """ @@ -107,7 +55,7 @@ #assert Envconfig.toxworkdir in args assert venv.getcommandpath("easy_install", cwd=py.path.local()) interp = venv._getliveconfig().python - assert interp == venv.getconfigexecutable() + assert interp == venv.envconfig._basepython_info.executable assert venv.path_config.check(exists=False) @pytest.mark.skipif("sys.platform == 'win32'") @@ -243,7 +191,7 @@ # two different index servers, two calls assert len(l) == 3 args = " ".join(l[0].args) - assert "-i" not in args + assert "-i " not in args assert "dep1" in args args = " ".join(l[1].args) diff -r 50bd50082b7cacf9a11ed5605d0af3383db23e8b -r efe7d94c863aabd6ea2d1513b4513ce9b0d4291c tox/_cmdline.py --- a/tox/_cmdline.py +++ b/tox/_cmdline.py @@ -486,6 +486,8 @@ for envconfig in self.config.envconfigs.values(): self.report.line("[testenv:%s]" % envconfig.envname, bold=True) self.report.line(" basepython=%s" % envconfig.basepython) + self.report.line(" _basepython_info=%s" % + envconfig._basepython_info) self.report.line(" envpython=%s" % envconfig.envpython) self.report.line(" envtmpdir=%s" % envconfig.envtmpdir) self.report.line(" envbindir=%s" % envconfig.envbindir) diff -r 50bd50082b7cacf9a11ed5605d0af3383db23e8b -r efe7d94c863aabd6ea2d1513b4513ce9b0d4291c tox/_config.py --- a/tox/_config.py +++ b/tox/_config.py @@ -8,6 +8,8 @@ import subprocess import textwrap +from tox.interpreters import Interpreters + import py import tox @@ -118,6 +120,7 @@ def __init__(self): self.envconfigs = {} self.invocationcwd = py.path.local() + self.interpreters = Interpreters() class VenvConfig: def __init__(self, **kw): @@ -141,32 +144,10 @@ # no @property to avoid early calling (see callable(subst[key]) checks) def envsitepackagesdir(self): - print_envsitepackagesdir = textwrap.dedent(""" - import sys - from distutils.sysconfig import get_python_lib - sys.stdout.write(get_python_lib(prefix=sys.argv[1])) - """) - - exe = self.getsupportedinterpreter() - # can't use check_output until py27 - proc = subprocess.Popen( - [str(exe), '-c', print_envsitepackagesdir, str(self.envdir)], - stdout=subprocess.PIPE) - odata, edata = proc.communicate() - if proc.returncode: - raise tox.exception.UnsupportedInterpreter( - "Error getting site-packages from %s" % self.basepython) - return odata - - def getconfigexecutable(self): - from tox._venv import find_executable - - python = self.basepython - if not python: - python = sys.executable - x = find_executable(str(python)) - if x: - x = x.realpath() + self.getsupportedinterpreter() # for throwing exceptions + x = self.config.interpreters.get_sitepackagesdir( + info=self._basepython_info, + envdir=self.envdir) return x def getsupportedinterpreter(self): @@ -174,10 +155,11 @@ "jython" in self.basepython: raise tox.exception.UnsupportedInterpreter( "Jython/Windows does not support installing scripts") - config_executable = self.getconfigexecutable() - if not config_executable: + info = self.config.interpreters.get_info(self.basepython) + if not info.executable: raise tox.exception.InterpreterNotFound(self.basepython) - return config_executable + return info.executable + testenvprefix = "testenv:" class parseini: @@ -285,6 +267,7 @@ else: bp = sys.executable vc.basepython = reader.getdefault(section, "basepython", bp) + vc._basepython_info = config.interpreters.get_info(vc.basepython) reader.addsubstitions(envdir=vc.envdir, envname=vc.envname, envbindir=vc.envbindir, envpython=vc.envpython, envsitepackagesdir=vc.envsitepackagesdir) @@ -336,7 +319,8 @@ # need to use --insecure for pip commands because python2.5 # doesn't support SSL pip_default_opts = ["{opts}", "{packages}"] - if "py25" in vc.envname: # XXX too rough check for "python2.5" + info = vc._basepython_info + if info.runnable and info.version_info < (2,6): pip_default_opts.insert(0, "--insecure") else: pip_default_opts.insert(0, "--pre") diff -r 50bd50082b7cacf9a11ed5605d0af3383db23e8b -r efe7d94c863aabd6ea2d1513b4513ce9b0d4291c tox/_venv.py --- a/tox/_venv.py +++ b/tox/_venv.py @@ -145,7 +145,7 @@ return "could not install deps %s" %(self.envconfig.deps,) def _getliveconfig(self): - python = self.getconfigexecutable() + python = self.envconfig._basepython_info.executable md5 = getdigest(python) version = tox.__version__ distribute = self.envconfig.distribute @@ -169,9 +169,6 @@ l.append(dep) return l - def getconfigexecutable(self): - return self.envconfig.getconfigexecutable() - def getsupportedinterpreter(self): return self.envconfig.getsupportedinterpreter() @@ -180,11 +177,11 @@ # return if action is None: action = self.session.newaction(self, "create") + + interpreters = self.envconfig.config.interpreters config_interpreter = self.getsupportedinterpreter() - config_interpreter_version = _getinterpreterversion( - config_interpreter) - use_venv191 = config_interpreter_version < '2.6' - use_pip13 = config_interpreter_version < '2.6' + 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() @@ -389,21 +386,6 @@ self.session.report.verbosity2("setting PATH=%s" % os.environ["PATH"]) return oldPATH -def _getinterpreterversion(executable): - print_python_version = ( - 'from distutils.sysconfig import get_python_version\n' - 'print(get_python_version())\n') - proc = subprocess.Popen([str(executable), '-c', print_python_version], - stdout=subprocess.PIPE) - odata, edata = proc.communicate() - if proc.returncode: - raise tox.exception.UnsupportedInterpreter( - "Error getting python version from %s" % executable) - if sys.version_info[0] == 3: - string = str - else: - string = lambda x, encoding: str(x) - return string(odata, 'ascii').strip() def getdigest(path): path = py.path.local(path) @@ -411,51 +393,6 @@ return "0" * 32 return path.computehash() -if sys.platform != "win32": - def find_executable(name): - return py.path.local.sysfind(name) - -else: - # Exceptions to the usual windows mapping - win32map = { - 'python': sys.executable, - 'jython': "c:\jython2.5.1\jython.bat", - } - def locate_via_py(v_maj, v_min): - ver = "-%s.%s" % (v_maj, v_min) - script = "import sys; print(sys.executable)" - py_exe = py.path.local.sysfind('py') - if py_exe: - try: - exe = py_exe.sysexec(ver, '-c', script).strip() - except py.process.cmdexec.Error: - exe = None - if exe: - exe = py.path.local(exe) - if exe.check(): - return exe - - def find_executable(name): - p = py.path.local.sysfind(name) - if p: - return p - actual = None - # Is this a standard PythonX.Y name? - m = re.match(r"python(\d)\.(\d)", name) - if m: - # The standard names are in predictable places. - actual = r"c:\python%s%s\python.exe" % m.groups() - if not actual: - actual = win32map.get(name, None) - if actual: - actual = py.path.local(actual) - if actual.check(): - return actual - # The standard executables can be found as a last resort via the - # Python launcher py.exe - if m: - locate_via_py(*m.groups()) - def hack_home_env(homedir, index_url=None): # XXX HACK (this could also live with tox itself, consider) diff -r 50bd50082b7cacf9a11ed5605d0af3383db23e8b -r efe7d94c863aabd6ea2d1513b4513ce9b0d4291c tox/interpreters.py --- /dev/null +++ b/tox/interpreters.py @@ -0,0 +1,170 @@ +import sys +import os +import py +import subprocess +import inspect + +class Interpreters: + def __init__(self): + self.name2executable = {} + self.executable2info = {} + + def get_executable(self, name): + """ return path object to the executable for the given + name (e.g. python2.5, python2.7, python etc.) + if name is already an existing path, return name. + If an interpreter cannot be found, return None. + """ + try: + return self.name2executable[name] + except KeyError: + self.name2executable[name] = e = find_executable(name) + return e + + def get_info(self, name=None, executable=None): + if name is None and executable is None: + raise ValueError("need to specify name or executable") + if name: + if executable is not None: + raise ValueError("cannot specify both name, executable") + executable = self.get_executable(name) + if not executable: + return NoInterpreterInfo(name=name) + try: + return self.executable2info[executable] + except KeyError: + info = run_and_get_interpreter_info(name, executable) + self.executable2info[executable] = info + return info + + def get_sitepackagesdir(self, info, envdir): + if not info.executable: + return "" + envdir = str(envdir) + try: + res = exec_on_interpreter(info.executable, + [inspect.getsource(sitepackagesdir), + "print (sitepackagesdir(%r))" % envdir]) + except ExecFailed: + val = sys.exc_info()[1] + print ("execution failed: %s -- %s" %(val.out, val.err)) + return "" + else: + return res["dir"] + +def run_and_get_interpreter_info(name, executable): + assert executable + try: + result = exec_on_interpreter(executable, + [inspect.getsource(pyinfo), "print (pyinfo())"]) + except ExecFailed: + val = sys.exc_info()[1] + return NoInterpreterInfo(name, **val.__dict__) + else: + return InterpreterInfo(name, executable, **result) + +def exec_on_interpreter(executable, source): + if isinstance(source, list): + source = "\n".join(source) + from subprocess import Popen, PIPE + args = [str(executable)] + popen = Popen(args, stdin=PIPE, stdout=PIPE, stderr=PIPE) + popen.stdin.write(source.encode("utf8")) + out, err = popen.communicate() + if popen.returncode: + raise ExecFailed(executable, source, out, err) + try: + result = eval(out) + except Exception: + raise ExecFailed(executable, source, out, + "could not decode %r" % out) + return result + +class ExecFailed(Exception): + def __init__(self, executable, source, out, err): + self.executable = executable + self.source = source + self.out = out + self.err = err + +class InterpreterInfo: + runnable = True + + def __init__(self, name, executable, version_info): + assert name and executable and version_info + self.name = name + self.executable = executable + self.version_info = version_info + + def __str__(self): + return "<executable at %s, version_info %s>" % ( + self.executable, self.version_info) + +class NoInterpreterInfo: + runnable = False + def __init__(self, name, executable=None, + out=None, err="not found"): + self.name = name + self.executable = executable + self.version_info = None + self.out = out + self.err = err + + def __str__(self): + if self.executable: + return "<executable at %s, not runnable>" + else: + return "<executable not found for: %s>" % self.name + +if sys.platform != "win32": + def find_executable(name): + return py.path.local.sysfind(name) + +else: + # Exceptions to the usual windows mapping + win32map = { + 'python': sys.executable, + 'jython': "c:\jython2.5.1\jython.bat", + } + def locate_via_py(v_maj, v_min): + ver = "-%s.%s" % (v_maj, v_min) + script = "import sys; print(sys.executable)" + py_exe = py.path.local.sysfind('py') + if py_exe: + try: + exe = py_exe.sysexec(ver, '-c', script).strip() + except py.process.cmdexec.Error: + exe = None + if exe: + exe = py.path.local(exe) + if exe.check(): + return exe + + def find_executable(name): + p = py.path.local.sysfind(name) + if p: + return p + actual = None + # Is this a standard PythonX.Y name? + m = re.match(r"python(\d)\.(\d)", name) + if m: + # The standard names are in predictable places. + actual = r"c:\python%s%s\python.exe" % m.groups() + if not actual: + actual = win32map.get(name, None) + if actual: + actual = py.path.local(actual) + if actual.check(): + return actual + # The standard executables can be found as a last resort via the + # Python launcher py.exe + if m: + locate_via_py(*m.groups()) + +def pyinfo(): + import sys + return dict(version_info=tuple(sys.version_info)) + +def sitepackagesdir(envdir): + from distutils.sysconfig import get_python_lib + return dict(dir=get_python_lib(envdir)) https://bitbucket.org/hpk42/tox/commits/33e5e5dff406/ Changeset: 33e5e5dff406 User: hpk42 Date: 2013-08-15 14:10:14 Summary: refactor docs and changelog Affected #: 11 files diff -r efe7d94c863aabd6ea2d1513b4513ce9b0d4291c -r 33e5e5dff406e699893a65ecd5044d3eee35b69b CHANGELOG --- a/CHANGELOG +++ b/CHANGELOG @@ -1,24 +1,26 @@ -1.6.0.dev +1.6.0 ----------------- -- fix issue35: add new EXPERIMENTAL "install_command" testenv-option to configure the - installation command with options for dep/pkg install. Thanks Carl Meyer - for the PR and docs. +- fix issue35: add new EXPERIMENTAL "install_command" testenv-option to + configure the installation command with options for dep/pkg install. + Thanks Carl Meyer for the PR and docs. -- address issueintroduce python2.5 support by vendoring the virtualenv-1.9.1 script - and forcing pip<1.4. Also the default [py25] environment modifies the - default installer_command (new config option) to use pip without the "--pre" - which was introduced with pip-1.4 and is required if you want to install non-stable releases. - (tox defaults to install with "--pre" otherwise). +- fix issue91: python2.5 support by vendoring the virtualenv-1.9.1 + script and forcing pip<1.4. Also the default [py25] environment + modifies the default installer_command (new config option) + to use pip without the "--pre" option which was introduced + with pip-1.4 and is now required if you want to install non-stable + releases. (tox defaults to install with "--pre" everywhere). + +- during installation of dependencies HOME is now set to a pseudo + location ({envtmpdir}/pseudo-home). If an index url was specified + a .pydistutils.cfg file will be written with an index_url setting + so that packages defining ``setup_requires`` dependencies will not + silently use your HOME-directory settings or https://pypi.python.org. - fix issue1: empty setup files are properly detected, thanks Anthon van der Neuth -- during installation of dependencies HOME is set to a pseudo - location (envtmpdir/pseudo-home). If an index url was specified - a .pydistutils.cfg file will be written so that index_url - is set if a package contains a ``setup_requires``. - - remove toxbootstrap.py for now because it is broken. - fix issue109 and fix issue111: multiple "-e" options are now combined diff -r efe7d94c863aabd6ea2d1513b4513ce9b0d4291c -r 33e5e5dff406e699893a65ecd5044d3eee35b69b doc/conf.py --- a/doc/conf.py +++ b/doc/conf.py @@ -48,7 +48,7 @@ # built documents. # # The short X.Y version. -release = version = "1.5.0" +release = version = "1.6.0" # The full version, including alpha/beta/rc tags. # The language for content autogenerated by Sphinx. Refer to documentation diff -r efe7d94c863aabd6ea2d1513b4513ce9b0d4291c -r 33e5e5dff406e699893a65ecd5044d3eee35b69b doc/config.txt --- a/doc/config.txt +++ b/doc/config.txt @@ -87,26 +87,22 @@ the virtual environment; both the package under test and any defined dependencies. Must contain the substitution key ``{packages}`` which will be replaced by the packages to - install. May also contain the substitution key ``{opts}``, which - will be replaced by the ``-i INDEXURL`` option if an index server - is active (see :confval:`indexserver` and the ``:indexserver:dep`` - syntax of :confval:`deps`) and the ``--download-cache`` option (if - you've specified :confval:`downloadcache`). If your installer does - not support ``-i`` and ``--download-cache`` command-line options, - you should not use :confval:`indexserver` or - :confval:`downloadcache`, and/or your :confval:`install_command` - should not include the ``{opts}`` substitution key (in which case - those options will have no effect). + install. You should also accept "{opts}" if you are using + pip or easy_install -- it will contain index server options + if you have configured them via :confval:`indexserver` + and the deprecated :confval:`downloadcache` option + if you have configured it. + **default**:: pip install --pre {opts} {packages} - - **default on environment names containing 'py25'**:: + + **default on environments using python2.5**:: pip install --insecure {opts} {packages}`` - (this will use pip<1.4 (so no "--pre" option) and - python2.5 typically has no SSL support). + (this will use pip<1.4 (so no ``--pre`` option) and python2.5 + typically has no SSL support, therefore ``--insecure``). .. confval:: whitelist_externals=MULTI-LINE-LIST diff -r efe7d94c863aabd6ea2d1513b4513ce9b0d4291c -r 33e5e5dff406e699893a65ecd5044d3eee35b69b doc/example/basic.txt --- a/doc/example/basic.txt +++ b/doc/example/basic.txt @@ -114,28 +114,21 @@ .. versionadded:: 1.6 -By default tox uses `pip`_ to install packages (both the sdist of your -package-under-test and any dependencies you specify in ``tox.ini``) into each -test virtualenv, with a command-line like ``pip install --pre -SomePackage==1.0``. - -You can fully customize tox's install-command in your ``tox.ini`` with the -``install_command`` option. For instance, to use ``easy_install`` instead of -`pip`_:: +By default tox uses `pip`_ to install packages, both the +package-under-test and any dependencies you specify in ``tox.ini``. +You can fully customize tox's install-command through the +testenv-specific :confval:`install_command=ARGV` setting. +For instance, to use ``easy_install`` instead of `pip`_:: [testenv] - install_command = easy_install {packages} + install_command = easy_install {opts} {packages} -Or to use pip's ``--find-links`` and ``--no-index`` options to specify an -alternative source for your dependencies:: +Or to use pip's ``--find-links`` and ``--no-index`` options to specify +an alternative source for your dependencies:: [testenv] install_command = pip install --pre --find-links http://packages.example.com --no-index {opts} {packages} -(Including ``{opts}`` is only necessary if you want your install command to -also respect tox's options for setting the download cache and package index -server). - .. _pip: http://pip-installer.org forcing re-creation of virtual environments diff -r efe7d94c863aabd6ea2d1513b4513ce9b0d4291c -r 33e5e5dff406e699893a65ecd5044d3eee35b69b doc/index.txt --- a/doc/index.txt +++ b/doc/index.txt @@ -66,10 +66,13 @@ * supports :ref:`using different / multiple PyPI index servers <multiindex>` -* uses pip_ and distribute_ by default. +* uses pip_ and setuptools_ by default. Experimental + 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. +* **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-platform**: Windows and Unix style environments @@ -77,8 +80,9 @@ (formerly known as Hudson) and helps you to avoid boilerplatish and platform-specific build-step hacks. -* **unified automatic artifact management** between ``tox`` runs both - in a local developer shell as well as in a CI/Jenkins context. +* **full interoperability with devpi**: is integrated with and + is used for testing in the devpi_ system, a versatile pypi + index server and release managing tool. * **driven by a simple ini-style config file** diff -r efe7d94c863aabd6ea2d1513b4513ce9b0d4291c -r 33e5e5dff406e699893a65ecd5044d3eee35b69b doc/links.txt --- a/doc/links.txt +++ b/doc/links.txt @@ -1,4 +1,5 @@ +.. _devpi: http://doc.devpi.net .. _Python: http://www.python.org .. _virtualenv: https://pypi.python.org/pypi/virtualenv .. _virtualenv3: https://pypi.python.org/pypi/virtualenv3 diff -r efe7d94c863aabd6ea2d1513b4513ce9b0d4291c -r 33e5e5dff406e699893a65ecd5044d3eee35b69b setup.py --- a/setup.py +++ b/setup.py @@ -28,7 +28,7 @@ description='virtualenv-based automation of test activities', long_description=open("README.rst").read(), url='http://tox.testrun.org/', - version='1.6rc2', + version='1.6.0', license='http://opensource.org/licenses/MIT', platforms=['unix', 'linux', 'osx', 'cygwin', 'win32'], author='holger krekel', diff -r efe7d94c863aabd6ea2d1513b4513ce9b0d4291c -r 33e5e5dff406e699893a65ecd5044d3eee35b69b tests/test_config.py --- a/tests/test_config.py +++ b/tests/test_config.py @@ -522,19 +522,19 @@ monkeypatch.delenv("PIP_DOWNLOAD_CACHE", raising=False) config = newconfig(""" [testenv] - downloadcache=/the/cache + downloadcache=thecache """) envconfig = config.envconfigs['python'] - assert envconfig.downloadcache == '/the/cache' + assert envconfig.downloadcache.basename == 'thecache' def test_downloadcache_env_override(self, newconfig, monkeypatch): - monkeypatch.setenv("PIP_DOWNLOAD_CACHE", '/from/env') + monkeypatch.setenv("PIP_DOWNLOAD_CACHE", 'fromenv') config = newconfig(""" [testenv] - downloadcache=/from/config + downloadcache=somepath """) envconfig = config.envconfigs['python'] - assert envconfig.downloadcache == '/from/env' + assert envconfig.downloadcache.basename == "fromenv" def test_downloadcache_only_if_in_config(self, newconfig, tmpdir, monkeypatch): diff -r efe7d94c863aabd6ea2d1513b4513ce9b0d4291c -r 33e5e5dff406e699893a65ecd5044d3eee35b69b tests/test_interpreters.py --- a/tests/test_interpreters.py +++ b/tests/test_interpreters.py @@ -10,7 +10,6 @@ @pytest.mark.skipif("sys.platform != 'win32'") def test_locate_via_py(monkeypatch): - from tox._venv import locate_via_py class PseudoPy: def sysexec(self, *args): assert args[0] == '-3.2' @@ -75,10 +74,8 @@ assert not interpreters.get_executable("12l3k1j23") def test_get_info__name(self, interpreters): - basename = os.path.basename(sys.executable) - info = interpreters.get_info(basename) + info = interpreters.get_info(executable=sys.executable) assert info.version_info == tuple(sys.version_info) - assert info.name == basename assert info.executable == sys.executable assert info.runnable diff -r efe7d94c863aabd6ea2d1513b4513ce9b0d4291c -r 33e5e5dff406e699893a65ecd5044d3eee35b69b tox/__init__.py --- a/tox/__init__.py +++ b/tox/__init__.py @@ -1,5 +1,5 @@ # -__version__ = '1.6rc2' +__version__ = '1.6.0' class exception: class Error(Exception): diff -r efe7d94c863aabd6ea2d1513b4513ce9b0d4291c -r 33e5e5dff406e699893a65ecd5044d3eee35b69b tox/interpreters.py --- a/tox/interpreters.py +++ b/tox/interpreters.py @@ -1,6 +1,7 @@ import sys import os import py +import re import subprocess import inspect @@ -59,7 +60,8 @@ [inspect.getsource(pyinfo), "print (pyinfo())"]) except ExecFailed: val = sys.exc_info()[1] - return NoInterpreterInfo(name, **val.__dict__) + return NoInterpreterInfo(name, executable=val.executable, + out=val.out, err=val.err) else: return InterpreterInfo(name, executable, **result) @@ -74,7 +76,7 @@ if popen.returncode: raise ExecFailed(executable, source, out, err) try: - result = eval(out) + result = eval(out.strip()) except Exception: raise ExecFailed(executable, source, out, "could not decode %r" % out) @@ -91,7 +93,7 @@ runnable = True def __init__(self, name, executable, version_info): - assert name and executable and version_info + assert executable and version_info self.name = name self.executable = executable self.version_info = version_info Repository URL: https://bitbucket.org/hpk42/tox/ -- This is a commit notification from bitbucket.org. You are receiving this because you have the service enabled, addressing the recipient of this email. _______________________________________________ pytest-commit mailing list pytest-commit@python.org http://mail.python.org/mailman/listinfo/pytest-commit