2 new commits in tox: https://bitbucket.org/hpk42/tox/commits/554373915117/ Changeset: 554373915117 User: hpk42 Date: 2015-05-12 12:01:28+00:00 Summary: - store and show information about what is installed in each venv - rename internal methods to accomodate the fact that we are not only installing sdist's Affected #: 13 files
diff -r 453b0b5844f1482f598260c5f08ea3fcd3283740 -r 55437391511703f2fd40f3ee3cf18f89ef8f446a CHANGELOG --- a/CHANGELOG +++ b/CHANGELOG @@ -1,4 +1,4 @@ -2.0.0.dev1 +2.0.0 ----------- - (new) introduce environment variable isolation: @@ -23,6 +23,8 @@ 2.0). If ``False`` (the default), then a non-zero exit code from one command will abort execution of commands for that environment. +- show and store in json the version dependency information for each venv + - remove the long-deprecated "distribute" option as it has no effect these days. - fix issue233: avoid hanging with tox-setuptools integration example. Thanks simonb. @@ -46,6 +48,7 @@ for testenv sections. Can be used from plugins through the tox_add_option hook. + 1.9.2 ----------- diff -r 453b0b5844f1482f598260c5f08ea3fcd3283740 -r 55437391511703f2fd40f3ee3cf18f89ef8f446a doc/Makefile --- a/doc/Makefile +++ b/doc/Makefile @@ -37,7 +37,8 @@ -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 + #dev #latexpdf #@scp $(BUILDDIR)/latex/*.pdf testrun.org:www-tox/latest diff -r 453b0b5844f1482f598260c5f08ea3fcd3283740 -r 55437391511703f2fd40f3ee3cf18f89ef8f446a doc/announce/release-2.0.txt --- /dev/null +++ b/doc/announce/release-2.0.txt @@ -0,0 +1,54 @@ +tox-2.0: plugins, platform, env isolation +========================================== + +tox-2.0 was released to pypi, a major new release with *mostly* +backward-compatible enhancements and fixes: + +- experimental support for plugins, see https://testrun.org/tox/dev/plugins.html + which includes also a refined internal registration mechanism for new testenv + ini options. You can now ask tox which testenv ini parameters exist + with ``tox --help-ini``. + +- ENV isolation: only pass through very few environment variables from the + tox invocation to the test environments. This may break test runs that + previously worked with tox-1.9 -- you need to either use the + ``setenv`` or ``passenv`` ini variables to set appropriate environment + variables. + +- PLATFORM support: you can set ``platform=REGEX`` in your testenv sections + which lets tox skip the environment if the REGEX does not match ``sys.platform``. + +- tox now stops execution of test commands if the first of them fails unless + you set ``ignore_errors=True``. + +Thanks to Volodymyr Vitvitski, Daniel Hahler, Marc Abramowitz, Anthon van +der Neuth and others for contributions. + +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 + + diff -r 453b0b5844f1482f598260c5f08ea3fcd3283740 -r 55437391511703f2fd40f3ee3cf18f89ef8f446a doc/conf.py --- a/doc/conf.py +++ b/doc/conf.py @@ -41,7 +41,7 @@ # General information about the project. project = u'tox' -copyright = u'2013, holger krekel and others' +copyright = u'2015, holger krekel and others' # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the diff -r 453b0b5844f1482f598260c5f08ea3fcd3283740 -r 55437391511703f2fd40f3ee3cf18f89ef8f446a setup.py --- a/setup.py +++ b/setup.py @@ -26,7 +26,7 @@ description='virtualenv-based automation of test activities', long_description=open("README.rst").read(), url='http://tox.testrun.org/', - version='2.0.0.dev2', + version='2.0.0.dev4', license='http://opensource.org/licenses/MIT', platforms=['unix', 'linux', 'osx', 'cygwin', 'win32'], author='holger krekel', diff -r 453b0b5844f1482f598260c5f08ea3fcd3283740 -r 55437391511703f2fd40f3ee3cf18f89ef8f446a tests/test_config.py --- a/tests/test_config.py +++ b/tests/test_config.py @@ -827,13 +827,13 @@ def test_substitution_error(tmpdir, newconfig): py.test.raises(tox.exception.ConfigError, newconfig, """ - [testenv:py24] + [testenv:py27] basepython={xyz} """) def test_substitution_defaults(tmpdir, newconfig): config = newconfig(""" - [testenv:py24] + [testenv:py27] commands = {toxinidir} {toxworkdir} @@ -845,7 +845,7 @@ {distshare} {envlogdir} """) - conf = config.envconfigs['py24'] + conf = config.envconfigs['py27'] argv = conf.commands assert argv[0][0] == config.toxinidir assert argv[1][0] == config.toxworkdir @@ -859,18 +859,18 @@ def test_substitution_positional(self, newconfig): inisource = """ - [testenv:py24] + [testenv:py27] commands = cmd1 [hello] \ world cmd1 {posargs:hello} \ world """ - conf = newconfig([], inisource).envconfigs['py24'] + conf = newconfig([], inisource).envconfigs['py27'] argv = conf.commands assert argv[0] == ["cmd1", "[hello]", "world"] assert argv[1] == ["cmd1", "hello", "world"] - conf = newconfig(['brave', 'new'], inisource).envconfigs['py24'] + conf = newconfig(['brave', 'new'], inisource).envconfigs['py27'] argv = conf.commands assert argv[0] == ["cmd1", "[hello]", "world"] assert argv[1] == ["cmd1", "brave", "new", "world"] @@ -886,58 +886,58 @@ def test_posargs_backslashed_or_quoted(self, tmpdir, newconfig): inisource = """ - [testenv:py24] + [testenv:py27] commands = echo "\{posargs\}" = {posargs} echo "posargs = " "{posargs}" """ - conf = newconfig([], inisource).envconfigs['py24'] + conf = newconfig([], inisource).envconfigs['py27'] argv = conf.commands assert argv[0] == ['echo', '\\{posargs\\}', '='] assert argv[1] == ['echo', 'posargs = ', ""] - conf = newconfig(['dog', 'cat'], inisource).envconfigs['py24'] + conf = newconfig(['dog', 'cat'], inisource).envconfigs['py27'] 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] + [testenv:py27] args_are_paths = True changedir = tests commands = cmd1 {posargs:hello} """ - conf = newconfig([], inisource).envconfigs['py24'] + conf = newconfig([], inisource).envconfigs['py27'] argv = conf.commands assert argv[0] == ["cmd1", "hello"] - conf = newconfig(["tests/hello"], inisource).envconfigs['py24'] + conf = newconfig(["tests/hello"], inisource).envconfigs['py27'] argv = conf.commands assert argv[0] == ["cmd1", "tests/hello"] tmpdir.ensure("tests", "hello") - conf = newconfig(["tests/hello"], inisource).envconfigs['py24'] + conf = newconfig(["tests/hello"], inisource).envconfigs['py27'] argv = conf.commands assert argv[0] == ["cmd1", "hello"] def test_rewrite_simple_posargs(self, tmpdir, newconfig): inisource = """ - [testenv:py24] + [testenv:py27] args_are_paths = True changedir = tests commands = cmd1 {posargs} """ - conf = newconfig([], inisource).envconfigs['py24'] + conf = newconfig([], inisource).envconfigs['py27'] argv = conf.commands assert argv[0] == ["cmd1"] - conf = newconfig(["tests/hello"], inisource).envconfigs['py24'] + conf = newconfig(["tests/hello"], inisource).envconfigs['py27'] argv = conf.commands assert argv[0] == ["cmd1", "tests/hello"] tmpdir.ensure("tests", "hello") - conf = newconfig(["tests/hello"], inisource).envconfigs['py24'] + conf = newconfig(["tests/hello"], inisource).envconfigs['py27'] argv = conf.commands assert argv[0] == ["cmd1", "hello"] @@ -947,12 +947,12 @@ deps= pytest pytest-cov - [testenv:py24] + [testenv:py27] deps= {[testenv]deps} fun """ - conf = newconfig([], inisource).envconfigs['py24'] + conf = newconfig([], inisource).envconfigs['py27'] packages = [dep.name for dep in conf.deps] assert packages == ['pytest', 'pytest-cov', 'fun'] @@ -1165,11 +1165,11 @@ monkeypatch, newconfig): monkeypatch.setenv("HUDSON_URL", "xyz") config = newconfig(""" - [testenv:py24] + [testenv:py27] commands = {distshare} """) - conf = config.envconfigs['py24'] + conf = config.envconfigs['py27'] argv = conf.commands expect_path = config.toxworkdir.join("distshare") assert argv[0][0] == expect_path @@ -1180,11 +1180,11 @@ config = newconfig(""" [tox:jenkins] distshare = {env:WORKSPACE}/hello - [testenv:py24] + [testenv:py27] commands = {distshare} """) - conf = config.envconfigs['py24'] + conf = config.envconfigs['py27'] argv = conf.commands assert argv[0][0] == config.distshare assert config.distshare == tmpdir.join("hello") @@ -1230,7 +1230,7 @@ assert str(env.basepython) == sys.executable def test_default_environments(self, tmpdir, newconfig, monkeypatch): - envs = "py26,py27,py31,py32,py33,py34,jython,pypy,pypy3" + envs = "py26,py27,py32,py33,py34,py35,py36,jython,pypy,pypy3" inisource = """ [tox] envlist = %s @@ -1291,21 +1291,21 @@ assert not config.option.skip_missing_interpreters def test_defaultenv_commandline(self, tmpdir, newconfig, monkeypatch): - config = newconfig(["-epy24"], "") - env = config.envconfigs['py24'] - assert env.basepython == "python2.4" + config = newconfig(["-epy27"], "") + env = config.envconfigs['py27'] + assert env.basepython == "python2.7" assert not env.commands def test_defaultenv_partial_override(self, tmpdir, newconfig, monkeypatch): inisource = """ [tox] - envlist = py24 - [testenv:py24] + envlist = py27 + [testenv:py27] commands= xyz """ config = newconfig([], inisource) - env = config.envconfigs['py24'] - assert env.basepython == "python2.4" + env = config.envconfigs['py27'] + assert env.basepython == "python2.7" assert env.commands == [['xyz']] diff -r 453b0b5844f1482f598260c5f08ea3fcd3283740 -r 55437391511703f2fd40f3ee3cf18f89ef8f446a tests/test_z_cmdline.py --- a/tests/test_z_cmdline.py +++ b/tests/test_z_cmdline.py @@ -83,15 +83,15 @@ }) config = parseconfig([]) session = Session(config) - sdist = session.sdist() + sdist = session.get_installpkg_path() assert sdist.check() assert sdist.ext == ".zip" assert sdist == config.distdir.join(sdist.basename) - sdist2 = session.sdist() + sdist2 = session.get_installpkg_path() assert sdist2 == sdist sdist.write("hello") assert sdist.stat().size < 10 - sdist_new = Session(config).sdist() + sdist_new = Session(config).get_installpkg_path() assert sdist_new == sdist assert sdist_new.stat().size > 10 @@ -106,7 +106,7 @@ }) config = parseconfig([]) session = Session(config) - sdist = session.sdist() + sdist = session.get_installpkg_path() assert sdist.check() assert sdist.ext == ".zip" assert sdist == config.distdir.join(sdist.basename) @@ -683,7 +683,7 @@ p = distshare.ensure("pkg123-1.4.5.zip") distshare.ensure("pkg123-1.4.5a1.zip") session = Session(config) - sdist_path = session.sdist() + sdist_path = session.get_installpkg_path() assert sdist_path == p @@ -691,7 +691,7 @@ p = tmpdir.ensure("pkg123-1.0.zip") config = newconfig(["--installpkg=%s" % p], "") session = Session(config) - sdist_path = session.sdist() + sdist_path = session.get_installpkg_path() assert sdist_path == p @@ -722,7 +722,9 @@ for command in envdata[commandtype]: assert command["output"] assert command["retcode"] - pyinfo = envdata["python"] - assert isinstance(pyinfo["version_info"], list) - assert pyinfo["version"] - assert pyinfo["executable"] + if envname != "GLOB": + assert isinstance(envdata["installed_packages"], list) + pyinfo = envdata["python"] + assert isinstance(pyinfo["version_info"], list) + assert pyinfo["version"] + assert pyinfo["executable"] diff -r 453b0b5844f1482f598260c5f08ea3fcd3283740 -r 55437391511703f2fd40f3ee3cf18f89ef8f446a tox.ini --- a/tox.ini +++ b/tox.ini @@ -5,7 +5,7 @@ commands=echo {posargs} [testenv] -commands= py.test --timeout=60 {posargs} +commands= py.test --timeout=180 {posargs} deps=pytest>=2.3.5 pytest-timeout diff -r 453b0b5844f1482f598260c5f08ea3fcd3283740 -r 55437391511703f2fd40f3ee3cf18f89ef8f446a tox/__init__.py --- a/tox/__init__.py +++ b/tox/__init__.py @@ -1,5 +1,5 @@ # -__version__ = '2.0.0.dev2' +__version__ = '2.0.0.dev4' from .hookspecs import hookspec, hookimpl # noqa diff -r 453b0b5844f1482f598260c5f08ea3fcd3283740 -r 55437391511703f2fd40f3ee3cf18f89ef8f446a tox/_cmdline.py --- a/tox/_cmdline.py +++ b/tox/_cmdline.py @@ -67,10 +67,12 @@ self.venvname = self.venv.name else: self.venvname = "GLOB" - cat = {"runtests": "test", "getenv": "setup"}.get(msg) - if cat: - envlog = session.resultlog.get_envlog(self.venvname) - self.commandlog = envlog.get_commandlog(cat) + if msg == "runtests": + cat = "test" + else: + cat = "setup" + envlog = session.resultlog.get_envlog(self.venvname) + self.commandlog = envlog.get_commandlog(cat) def __enter__(self): self.report.logaction_start(self) @@ -106,7 +108,7 @@ resultjson = self.session.config.option.resultjson if resultjson or redirect: fout = self._initlogpath(self.id) - fout.write("actionid=%s\nmsg=%s\ncmdargs=%r\nenv=%s\n" % ( + fout.write("actionid: %s\nmsg: %s\ncmdargs: %r\nenv: %s\n\n" % ( self.id, self.msg, args, env)) fout.flush() self.popen_outpath = outpath = py.path.local(fout.name) @@ -442,47 +444,47 @@ venv.status = sys.exc_info()[1] return False - def installpkg(self, venv, sdist_path): - """Install source package in the specified virtual environment. + def installpkg(self, venv, path): + """Install package in the specified virtual environment. :param :class:`tox._config.VenvConfig`: Destination environment - :param str sdist_path: Path to the source distribution. + :param str path: Path to the distribution package. :return: True if package installed otherwise False. :rtype: bool """ - self.resultlog.set_header(installpkg=py.path.local(sdist_path)) - action = self.newaction(venv, "installpkg", sdist_path) + self.resultlog.set_header(installpkg=py.path.local(path)) + action = self.newaction(venv, "installpkg", path) with action: try: - venv.installpkg(sdist_path, action) + venv.installpkg(path, action) return True except tox.exception.InvocationError: venv.status = sys.exc_info()[1] return False - def sdist(self): + def get_installpkg_path(self): """ - :return: Path to the source distribution + :return: Path to the distribution :rtype: py.path.local """ if not self.config.option.sdistonly and (self.config.sdistsrc or self.config.option.installpkg): - sdist_path = self.config.option.installpkg - if not sdist_path: - sdist_path = self.config.sdistsrc - sdist_path = self._resolve_pkg(sdist_path) + path = self.config.option.installpkg + if not path: + path = self.config.sdistsrc + path = self._resolve_pkg(path) self.report.info("using package %r, skipping 'sdist' activity " % - str(sdist_path)) + str(path)) else: try: - sdist_path = self._makesdist() + path = self._makesdist() except tox.exception.InvocationError: v = sys.exc_info()[1] self.report.error("FAIL could not package project - v = %r" % v) return - sdistfile = self.config.distshare.join(sdist_path.basename) - if sdistfile != sdist_path: + sdistfile = self.config.distshare.join(path.basename) + if sdistfile != path: self.report.info("copying new sdistfile to %r" % str(sdistfile)) try: @@ -491,16 +493,16 @@ self.report.warning("could not copy distfile to %s" % sdistfile.dirpath()) else: - sdist_path.copy(sdistfile) - return sdist_path + path.copy(sdistfile) + return path def subcommand_test(self): if self.config.skipsdist: self.report.info("skipping sdist step") - sdist_path = None + path = None else: - sdist_path = self.sdist() - if not sdist_path: + path = self.get_installpkg_path() + if not path: return 2 if self.config.option.sdistonly: return @@ -514,7 +516,22 @@ elif self.config.skipsdist or venv.envconfig.skip_install: self.finishvenv(venv) else: - self.installpkg(venv, sdist_path) + self.installpkg(venv, path) + + # write out version dependency information + action = self.newaction(venv, "envreport") + with action: + pip = venv.getcommandpath("pip") + # we can't really call internal helpers here easily :/ + # output = venv._pcall([str(pip), "freeze"], + # cwd=self.config.toxinidir, + # action=action) + output = py.process.cmdexec("%s freeze" % (pip)) + packages = output.strip().split("\n") + action.setactivity("installed", ",".join(packages)) + envlog = self.resultlog.get_envlog(venv.name) + envlog.set_installed(packages) + self.runtestenv(venv) retcode = self._summary() return retcode @@ -589,6 +606,8 @@ self.report.line(" envdir= %s" % envconfig.envdir) self.report.line(" downloadcache=%s" % envconfig.downloadcache) self.report.line(" usedevelop=%s" % envconfig.usedevelop) + self.report.line(" setenv=%s" % envconfig.setenv) + self.report.line(" passenv=%s" % envconfig.passenv) def showenvs(self): for env in self.config.envlist: diff -r 453b0b5844f1482f598260c5f08ea3fcd3283740 -r 55437391511703f2fd40f3ee3cf18f89ef8f446a tox/_config.py --- a/tox/_config.py +++ b/tox/_config.py @@ -21,7 +21,7 @@ default_factors = {'jython': 'jython', 'pypy': 'pypy', 'pypy3': 'pypy3', 'py': sys.executable} -for version in '24,25,26,27,30,31,32,33,34,35'.split(','): +for version in '26,27,32,33,34,35,36'.split(','): default_factors['py' + version] = 'python%s.%s' % tuple(version) hookimpl = pluggy.HookimplMarker("tox") @@ -361,9 +361,18 @@ def passenv(config, reader, section_val): passenv = set(["PATH"]) + + # we ensure that tmp directory settings are passed on + # we could also set it to the per-venv "envtmpdir" + # but this leads to very long paths when run with jenkins + # so we just pass it on by default for now. if sys.platform == "win32": passenv.add("SYSTEMROOT") # needed for python's crypto module passenv.add("PATHEXT") # needed for discovering executables + passenv.add("TEMPDIR") + passenv.add("TMP") + else: + passenv.add("TMPDIR") for spec in section_val: for name in os.environ: if fnmatchcase(name.upper(), spec.upper()): @@ -626,6 +635,7 @@ factors=factors) reader.addsubstitutions(**subs) reader.addsubstitutions(envname=name) + reader.vc = vc for env_attr in config._testenv_attr: atype = env_attr.type diff -r 453b0b5844f1482f598260c5f08ea3fcd3283740 -r 55437391511703f2fd40f3ee3cf18f89ef8f446a tox/_venv.py --- a/tox/_venv.py +++ b/tox/_venv.py @@ -327,7 +327,6 @@ env['VIRTUAL_ENV'] = str(self.path) env.update(extraenv) - return env def test(self, redirect=False): diff -r 453b0b5844f1482f598260c5f08ea3fcd3283740 -r 55437391511703f2fd40f3ee3cf18f89ef8f446a tox/result.py --- a/tox/result.py +++ b/tox/result.py @@ -63,6 +63,9 @@ l = self.dict.setdefault(name, []) return CommandLog(self, l) + def set_installed(self, packages): + self.dict["installed_packages"] = packages + class CommandLog: def __init__(self, envlog, list): https://bitbucket.org/hpk42/tox/commits/5fbd833e8b0e/ Changeset: 5fbd833e8b0e User: hpk42 Date: 2015-05-12 12:20:46+00:00 Summary: rename internal files -- in any case tox offers no external API except for the experimental plugin hooks, use tox internals at your own risk. Affected #: 16 files diff -r 55437391511703f2fd40f3ee3cf18f89ef8f446a -r 5fbd833e8b0e88cf71920a1479f47260317c98b0 CHANGELOG --- a/CHANGELOG +++ b/CHANGELOG @@ -48,6 +48,10 @@ for testenv sections. Can be used from plugins through the tox_add_option hook. +- rename internal files -- tox offers no external API except for the + experimental plugin hooks, use tox internals at your own risk. + + 1.9.2 ----------- diff -r 55437391511703f2fd40f3ee3cf18f89ef8f446a -r 5fbd833e8b0e88cf71920a1479f47260317c98b0 setup.py --- a/setup.py +++ b/setup.py @@ -26,7 +26,7 @@ description='virtualenv-based automation of test activities', long_description=open("README.rst").read(), url='http://tox.testrun.org/', - version='2.0.0.dev4', + version='2.0.0.dev5', license='http://opensource.org/licenses/MIT', platforms=['unix', 'linux', 'osx', 'cygwin', 'win32'], author='holger krekel', diff -r 55437391511703f2fd40f3ee3cf18f89ef8f446a -r 5fbd833e8b0e88cf71920a1479f47260317c98b0 tests/test_config.py --- a/tests/test_config.py +++ b/tests/test_config.py @@ -4,9 +4,9 @@ import py import pytest import tox -import tox._config -from tox._config import * # noqa -from tox._venv import VirtualEnv +import tox.config +from tox.config import * # noqa +from tox.venv import VirtualEnv class TestVenvConfig: @@ -1321,12 +1321,12 @@ """ if make_hashseed is None: make_hashseed = lambda: '123456789' - original_make_hashseed = tox._config.make_hashseed - tox._config.make_hashseed = make_hashseed + 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 + tox.config.make_hashseed = original_make_hashseed return config.envconfigs def _get_envconfig(self, newconfig, args=None, tox_ini=None): diff -r 55437391511703f2fd40f3ee3cf18f89ef8f446a -r 5fbd833e8b0e88cf71920a1479f47260317c98b0 tests/test_interpreters.py --- a/tests/test_interpreters.py +++ b/tests/test_interpreters.py @@ -3,7 +3,7 @@ import pytest from tox.interpreters import * # noqa -from tox._config import get_plugin_manager +from tox.config import get_plugin_manager @pytest.fixture diff -r 55437391511703f2fd40f3ee3cf18f89ef8f446a -r 5fbd833e8b0e88cf71920a1479f47260317c98b0 tests/test_venv.py --- a/tests/test_venv.py +++ b/tests/test_venv.py @@ -3,8 +3,8 @@ import pytest import os import sys -import tox._config -from tox._venv import * # noqa +import tox.config +from tox.venv import * # noqa from tox.interpreters import NoInterpreterInfo # def test_global_virtualenv(capfd): @@ -253,14 +253,14 @@ def test_test_hashseed_is_in_output(newmocksession): - original_make_hashseed = tox._config.make_hashseed - tox._config.make_hashseed = lambda: '123456789' + 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 + tox.config.make_hashseed = original_make_hashseed venv = mocksession.getenv('python') venv.update() venv.test() @@ -620,7 +620,7 @@ mocksession = newmocksession([], "") venv = mocksession.getenv('python') action = mocksession.newaction(venv, "qwe", []) - monkeypatch.setattr(tox._venv, "hack_home_env", None) + monkeypatch.setattr(tox.venv, "hack_home_env", None) venv._install(["x"], action=action) @@ -636,7 +636,7 @@ def test_hack_home_env(tmpdir): - from tox._venv import hack_home_env + from tox.venv import hack_home_env env = hack_home_env(tmpdir, "http://index") assert env["HOME"] == str(tmpdir) assert env["PIP_INDEX_URL"] == "http://index" @@ -650,7 +650,7 @@ def test_hack_home_env_passthrough(tmpdir, monkeypatch): - from tox._venv import hack_home_env + from tox.venv import hack_home_env env = hack_home_env(tmpdir, "http://index") monkeypatch.setattr(os, "environ", env) diff -r 55437391511703f2fd40f3ee3cf18f89ef8f446a -r 5fbd833e8b0e88cf71920a1479f47260317c98b0 tests/test_z_cmdline.py --- a/tests/test_z_cmdline.py +++ b/tests/test_z_cmdline.py @@ -9,8 +9,8 @@ pytest_plugins = "pytester" -from tox._cmdline import Session -from tox._config import parseconfig +from tox.session import Session +from tox.config import parseconfig def test_report_protocol(newconfig): diff -r 55437391511703f2fd40f3ee3cf18f89ef8f446a -r 5fbd833e8b0e88cf71920a1479f47260317c98b0 tox/__init__.py --- a/tox/__init__.py +++ b/tox/__init__.py @@ -1,5 +1,5 @@ # -__version__ = '2.0.0.dev4' +__version__ = '2.0.0.dev5' from .hookspecs import hookspec, hookimpl # noqa @@ -24,4 +24,4 @@ class MissingDependency(Error): """ a dependency could not be found or determined. """ -from tox._cmdline import main as cmdline # noqa +from tox.session import main as cmdline # noqa diff -r 55437391511703f2fd40f3ee3cf18f89ef8f446a -r 5fbd833e8b0e88cf71920a1479f47260317c98b0 tox/__main__.py --- a/tox/__main__.py +++ b/tox/__main__.py @@ -1,3 +1,4 @@ -from tox._cmdline import main +from tox.session import main -main() +if __name__ == "__main__": + main() diff -r 55437391511703f2fd40f3ee3cf18f89ef8f446a -r 5fbd833e8b0e88cf71920a1479f47260317c98b0 tox/_cmdline.py --- a/tox/_cmdline.py +++ /dev/null @@ -1,673 +0,0 @@ -""" -Automatically package and test a Python project against configurable -Python2 and Python3 based virtual environments. Environments are -setup by using virtualenv. Configuration is generally done through an -INI-style "tox.ini" file. -""" -from __future__ import with_statement - -import tox -import py -import os -import sys -import subprocess -from tox._verlib import NormalizedVersion, IrrationalVersionError -from tox._venv import VirtualEnv -from tox._config import parseconfig -from tox.result import ResultLog -from subprocess import STDOUT - - -def now(): - return py.std.time.time() - - -def main(args=None): - try: - config = parseconfig(args) - if config.option.help: - show_help(config) - raise SystemExit(0) - elif config.option.helpini: - show_help_ini(config) - raise SystemExit(0) - retcode = Session(config).runcommand() - raise SystemExit(retcode) - except KeyboardInterrupt: - raise SystemExit(2) - - -def show_help(config): - tw = py.io.TerminalWriter() - tw.write(config._parser.format_help()) - tw.line() - - -def show_help_ini(config): - tw = py.io.TerminalWriter() - tw.sep("-", "per-testenv attributes") - for env_attr in config._testenv_attr: - tw.line("%-15s %-8s default: %s" % - (env_attr.name, "<" + env_attr.type + ">", env_attr.default), bold=True) - tw.line(env_attr.help) - tw.line() - - -class Action(object): - def __init__(self, session, venv, msg, args): - self.venv = venv - self.msg = msg - self.activity = msg.split(" ", 1)[0] - self.session = session - self.report = session.report - self.args = args - self.id = venv and venv.envconfig.envname or "tox" - self._popenlist = [] - if self.venv: - self.venvname = self.venv.name - else: - self.venvname = "GLOB" - if msg == "runtests": - cat = "test" - else: - cat = "setup" - envlog = session.resultlog.get_envlog(self.venvname) - self.commandlog = envlog.get_commandlog(cat) - - def __enter__(self): - self.report.logaction_start(self) - - def __exit__(self, *args): - self.report.logaction_finish(self) - - def setactivity(self, name, msg): - self.activity = name - self.report.verbosity0("%s %s: %s" % (self.venvname, name, msg), bold=True) - - def info(self, name, msg): - self.report.verbosity1("%s %s: %s" % (self.venvname, name, msg), bold=True) - - def _initlogpath(self, actionid): - if self.venv: - logdir = self.venv.envconfig.envlogdir - else: - logdir = self.session.config.logdir - try: - l = logdir.listdir("%s-*" % actionid) - except py.error.ENOENT: - logdir.ensure(dir=1) - l = [] - num = len(l) - path = logdir.join("%s-%s.log" % (actionid, num)) - f = path.open('w') - f.flush() - return f - - def popen(self, args, cwd=None, env=None, redirect=True, returnout=False, ignore_ret=False): - stdout = outpath = None - resultjson = self.session.config.option.resultjson - if resultjson or redirect: - fout = self._initlogpath(self.id) - fout.write("actionid: %s\nmsg: %s\ncmdargs: %r\nenv: %s\n\n" % ( - self.id, self.msg, args, env)) - fout.flush() - self.popen_outpath = outpath = py.path.local(fout.name) - fin = outpath.open() - fin.read() # read the header, so it won't be written to stdout - stdout = fout - elif returnout: - stdout = subprocess.PIPE - if cwd is None: - # XXX cwd = self.session.config.cwd - cwd = py.path.local() - try: - popen = self._popen(args, cwd, env=env, - stdout=stdout, stderr=STDOUT) - 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] - popen.cwd = cwd - popen.action = self - self._popenlist.append(popen) - try: - self.report.logpopen(popen, env=env) - try: - if resultjson and not redirect: - assert popen.stderr is None # prevent deadlock - out = None - last_time = now() - while 1: - fin_pos = fin.tell() - # we have to read one byte at a time, otherwise there - # might be no output for a long time with slow tests - data = fin.read(1) - if data: - sys.stdout.write(data) - if '\n' in data or (now() - last_time) > 1: - # we flush on newlines or after 1 second to - # provide quick enough feedback to the user - # when printing a dot per test - sys.stdout.flush() - last_time = now() - elif popen.poll() is not None: - if popen.stdout is not None: - popen.stdout.close() - break - else: - py.std.time.sleep(0.1) - fin.seek(fin_pos) - fin.close() - else: - out, err = popen.communicate() - except KeyboardInterrupt: - self.report.keyboard_interrupt() - popen.wait() - raise KeyboardInterrupt() - ret = popen.wait() - finally: - self._popenlist.remove(popen) - if ret and not ignore_ret: - invoked = " ".join(map(str, popen.args)) - if outpath: - self.report.error("invocation failed (exit code %d), logfile: %s" % - (ret, outpath)) - out = outpath.read() - self.report.error(out) - if hasattr(self, "commandlog"): - self.commandlog.add_command(popen.args, out, ret) - raise tox.exception.InvocationError( - "%s (see %s)" % (invoked, outpath), ret) - else: - raise tox.exception.InvocationError("%r" % (invoked, ), ret) - if not out and outpath: - out = outpath.read() - if hasattr(self, "commandlog"): - self.commandlog.add_command(popen.args, out, ret) - return out - - def _rewriteargs(self, cwd, args): - newargs = [] - for arg in args: - if sys.platform != "win32" and isinstance(arg, py.path.local): - arg = cwd.bestrelpath(arg) - newargs.append(str(arg)) - - # subprocess does not always take kindly to .py scripts - # so adding the interpreter here. - if sys.platform == "win32": - ext = os.path.splitext(str(newargs[0]))[1].lower() - if ext == '.py' and self.venv: - newargs = [str(self.venv.getcommandpath())] + newargs - - return newargs - - def _popen(self, args, cwd, stdout, stderr, env=None): - args = self._rewriteargs(cwd, args) - 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) - - -class Reporter(object): - actionchar = "-" - - def __init__(self, session): - self.tw = py.io.TerminalWriter() - self.session = session - self._reportedlines = [] - # self.cumulated_time = 0.0 - - def logpopen(self, popen, env): - """ log information about the action.popen() created process. """ - cmd = " ".join(map(str, popen.args)) - if popen.outpath: - self.verbosity1(" %s$ %s >%s" % (popen.cwd, cmd, popen.outpath,)) - else: - self.verbosity1(" %s$ %s " % (popen.cwd, cmd)) - - def logaction_start(self, action): - msg = action.msg + " " + " ".join(map(str, action.args)) - self.verbosity2("%s start: %s" % (action.venvname, msg), bold=True) - assert not hasattr(action, "_starttime") - action._starttime = now() - - def logaction_finish(self, action): - duration = now() - action._starttime - # self.cumulated_time += duration - self.verbosity2("%s finish: %s after %.2f seconds" % ( - action.venvname, action.msg, duration), bold=True) - - def startsummary(self): - self.tw.sep("_", "summary") - - def info(self, msg): - if self.session.config.option.verbosity >= 2: - self.logline(msg) - - def using(self, msg): - if self.session.config.option.verbosity >= 1: - self.logline("using %s" % (msg,), bold=True) - - def keyboard_interrupt(self): - self.error("KEYBOARDINTERRUPT") - -# def venv_installproject(self, venv, pkg): -# self.logline("installing to %s: %s" % (venv.envconfig.envname, pkg)) - - def keyvalue(self, name, value): - if name.endswith(":"): - name += " " - self.tw.write(name, bold=True) - self.tw.write(value) - self.tw.line() - - def line(self, msg, **opts): - self.logline(msg, **opts) - - def good(self, msg): - self.logline(msg, green=True) - - def warning(self, msg): - self.logline("WARNING:" + msg, red=True) - - 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) - - def verbosity0(self, msg, **opts): - if self.session.config.option.verbosity >= 0: - self.logline("%s" % msg, **opts) - - def verbosity1(self, msg, **opts): - if self.session.config.option.verbosity >= 1: - self.logline("%s" % msg, **opts) - - def verbosity2(self, msg, **opts): - if self.session.config.option.verbosity >= 2: - self.logline("%s" % msg, **opts) - - # def log(self, msg): - # py.builtin.print_(msg, file=sys.stderr) - - -class Session: - - def __init__(self, config, popen=subprocess.Popen, Report=Reporter): - self.config = config - self.popen = popen - self.resultlog = ResultLog() - self.report = Report(self) - self.make_emptydir(config.logdir) - config.logdir.ensure(dir=1) - # self.report.using("logdir %s" %(self.config.logdir,)) - self.report.using("tox.ini: %s" % (self.config.toxinipath,)) - self._spec2pkg = {} - self._name2venv = {} - try: - self.venvlist = [ - self.getvenv(x) - for x in self.config.envlist - ] - except LookupError: - raise SystemExit(1) - self._actions = [] - - def _makevenv(self, name): - envconfig = self.config.envconfigs.get(name, None) - if envconfig is None: - self.report.error("unknown environment %r" % name) - raise LookupError(name) - venv = VirtualEnv(envconfig=envconfig, session=self) - self._name2venv[name] = venv - return venv - - def getvenv(self, name): - """ return a VirtualEnv controler object for the 'name' env. """ - try: - return self._name2venv[name] - except KeyError: - return self._makevenv(name) - - def newaction(self, venv, msg, *args): - action = Action(self, venv, msg, args) - self._actions.append(action) - return action - - def runcommand(self): - self.report.using("tox-%s from %s" % (tox.__version__, tox.__file__)) - if self.config.minversion: - minversion = NormalizedVersion(self.config.minversion) - toxversion = NormalizedVersion(tox.__version__) - if toxversion < minversion: - self.report.error( - "tox version is %s, required is at least %s" % ( - toxversion, minversion)) - raise SystemExit(1) - if self.config.option.showconfig: - self.showconfig() - elif self.config.option.listenvs: - self.showenvs() - else: - return self.subcommand_test() - - def _copyfiles(self, srcdir, pathlist, destdir): - for relpath in pathlist: - src = srcdir.join(relpath) - if not src.check(): - self.report.error("missing source file: %s" % (src,)) - raise SystemExit(1) - target = destdir.join(relpath) - target.dirpath().ensure(dir=1) - src.copy(target) - - def _makesdist(self): - setup = self.config.setupdir.join("setup.py") - if not setup.check(): - raise tox.exception.MissingFile(setup) - action = self.newaction(None, "packaging") - with action: - action.setactivity("sdist-make", setup) - self.make_emptydir(self.config.distdir) - action.popen([sys.executable, setup, "sdist", "--formats=zip", - "--dist-dir", self.config.distdir, ], - cwd=self.config.setupdir) - try: - return self.config.distdir.listdir()[0] - except py.error.ENOENT: - # check if empty or comment only - data = [] - with open(str(setup)) as fp: - for line in fp: - if line and line[0] == '#': - continue - data.append(line) - if not ''.join(data).strip(): - self.report.error( - 'setup.py is empty' - ) - raise SystemExit(1) - self.report.error( - 'No dist directory found. Please check setup.py, e.g with:\n' - ' python setup.py sdist' - ) - raise SystemExit(1) - - def make_emptydir(self, path): - if path.check(): - self.report.info(" removing %s" % path) - py.std.shutil.rmtree(str(path), ignore_errors=True) - path.ensure(dir=1) - - def setupenv(self, venv): - action = self.newaction(venv, "getenv", venv.envconfig.envdir) - with action: - venv.status = 0 - envlog = self.resultlog.get_envlog(venv.name) - try: - status = venv.update(action=action) - except tox.exception.InvocationError: - status = sys.exc_info()[1] - if status: - commandlog = envlog.get_commandlog("setup") - commandlog.add_command(["setup virtualenv"], str(status), 1) - venv.status = status - self.report.error(str(status)) - return False - commandpath = venv.getcommandpath("python") - envlog.set_python_info(commandpath) - return True - - def finishvenv(self, venv): - action = self.newaction(venv, "finishvenv") - with action: - venv.finish() - return True - - def developpkg(self, venv, setupdir): - action = self.newaction(venv, "developpkg", setupdir) - with action: - try: - venv.developpkg(setupdir, action) - return True - except tox.exception.InvocationError: - venv.status = sys.exc_info()[1] - return False - - def installpkg(self, venv, path): - """Install package in the specified virtual environment. - - :param :class:`tox._config.VenvConfig`: Destination environment - :param str path: Path to the distribution package. - :return: True if package installed otherwise False. - :rtype: bool - """ - self.resultlog.set_header(installpkg=py.path.local(path)) - action = self.newaction(venv, "installpkg", path) - with action: - try: - venv.installpkg(path, action) - return True - except tox.exception.InvocationError: - venv.status = sys.exc_info()[1] - return False - - def get_installpkg_path(self): - """ - :return: Path to the distribution - :rtype: py.path.local - """ - if not self.config.option.sdistonly and (self.config.sdistsrc or - self.config.option.installpkg): - path = self.config.option.installpkg - if not path: - path = self.config.sdistsrc - path = self._resolve_pkg(path) - self.report.info("using package %r, skipping 'sdist' activity " % - str(path)) - else: - try: - path = self._makesdist() - except tox.exception.InvocationError: - v = sys.exc_info()[1] - self.report.error("FAIL could not package project - v = %r" % - v) - return - sdistfile = self.config.distshare.join(path.basename) - if sdistfile != path: - self.report.info("copying new sdistfile to %r" % - str(sdistfile)) - try: - sdistfile.dirpath().ensure(dir=1) - except py.error.Error: - self.report.warning("could not copy distfile to %s" % - sdistfile.dirpath()) - else: - path.copy(sdistfile) - return path - - def subcommand_test(self): - if self.config.skipsdist: - self.report.info("skipping sdist step") - path = None - else: - path = self.get_installpkg_path() - if not path: - return 2 - if self.config.option.sdistonly: - return - for venv in self.venvlist: - if not venv.matching_platform(): - venv.status = "platform mismatch" - continue # we simply omit non-matching platforms - if self.setupenv(venv): - if venv.envconfig.usedevelop: - self.developpkg(venv, self.config.setupdir) - elif self.config.skipsdist or venv.envconfig.skip_install: - self.finishvenv(venv) - else: - self.installpkg(venv, path) - - # write out version dependency information - action = self.newaction(venv, "envreport") - with action: - pip = venv.getcommandpath("pip") - # we can't really call internal helpers here easily :/ - # output = venv._pcall([str(pip), "freeze"], - # cwd=self.config.toxinidir, - # action=action) - output = py.process.cmdexec("%s freeze" % (pip)) - packages = output.strip().split("\n") - action.setactivity("installed", ",".join(packages)) - envlog = self.resultlog.get_envlog(venv.name) - envlog.set_installed(packages) - - self.runtestenv(venv) - retcode = self._summary() - return retcode - - def runtestenv(self, venv, redirect=False): - if not self.config.option.notest: - if venv.status: - return - venv.test(redirect=redirect) - else: - venv.status = "skipped tests" - - def _summary(self): - self.report.startsummary() - retcode = 0 - for venv in self.venvlist: - status = venv.status - 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 == "platform mismatch": - msg = " %s: %s" % (venv.envconfig.envname, str(status)) - self.report.verbosity1(msg) - elif status and status != "skipped tests": - msg = " %s: %s" % (venv.envconfig.envname, str(status)) - self.report.error(msg) - retcode = 1 - else: - if not status: - status = "commands succeeded" - self.report.good(" %s: %s" % (venv.envconfig.envname, status)) - if not retcode: - self.report.good(" congratulations :)") - - path = self.config.option.resultjson - if path: - path = py.path.local(path) - path.write(self.resultlog.dumps_json()) - self.report.line("wrote json report at: %s" % path) - return retcode - - def showconfig(self): - self.info_versions() - self.report.keyvalue("config-file:", self.config.option.configfile) - self.report.keyvalue("toxinipath: ", self.config.toxinipath) - self.report.keyvalue("toxinidir: ", self.config.toxinidir) - self.report.keyvalue("toxworkdir: ", self.config.toxworkdir) - self.report.keyvalue("setupdir: ", self.config.setupdir) - self.report.keyvalue("distshare: ", self.config.distshare) - self.report.keyvalue("skipsdist: ", self.config.skipsdist) - self.report.tw.line() - for envconfig in self.config.envconfigs.values(): - self.report.line("[testenv:%s]" % envconfig.envname, bold=True) - self.report.line(" basepython=%s" % envconfig.basepython) - self.report.line(" pythoninfo=%s" % (envconfig.python_info,)) - self.report.line(" envpython=%s" % envconfig.envpython) - self.report.line(" envtmpdir=%s" % envconfig.envtmpdir) - self.report.line(" envbindir=%s" % envconfig.envbindir) - 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) - self.report.line(" deps=%s" % envconfig.deps) - self.report.line(" envdir= %s" % envconfig.envdir) - self.report.line(" downloadcache=%s" % envconfig.downloadcache) - self.report.line(" usedevelop=%s" % envconfig.usedevelop) - self.report.line(" setenv=%s" % envconfig.setenv) - self.report.line(" passenv=%s" % envconfig.passenv) - - def showenvs(self): - for env in self.config.envlist: - self.report.line("%s" % env) - - def info_versions(self): - versions = ['tox-%s' % tox.__version__] - try: - version = py.process.cmdexec("virtualenv --version") - except py.process.cmdexec.Error: - versions.append("virtualenv-1.9.1 (vendored)") - else: - versions.append("virtualenv-%s" % version.strip()) - self.report.keyvalue("tool-versions:", " ".join(versions)) - - def _resolve_pkg(self, pkgspec): - try: - return self._spec2pkg[pkgspec] - except KeyError: - self._spec2pkg[pkgspec] = x = self._resolvepkg(pkgspec) - return x - - def _resolvepkg(self, pkgspec): - if not os.path.isabs(str(pkgspec)): - return pkgspec - p = py.path.local(pkgspec) - if p.check(): - return p - if not p.dirpath().check(dir=1): - raise tox.exception.MissingDirectory(p.dirpath()) - self.report.info("determining %s" % p) - candidates = p.dirpath().listdir(p.basename) - if len(candidates) == 0: - raise tox.exception.MissingDependency(pkgspec) - if len(candidates) > 1: - items = [] - for x in candidates: - ver = getversion(x.basename) - if ver is not None: - items.append((ver, x)) - else: - self.report.warning("could not determine version of: %s" % - str(x)) - items.sort() - if not items: - raise tox.exception.MissingDependency(pkgspec) - return items[-1][1] - else: - return candidates[0] - - -_rex_getversion = py.std.re.compile("[\w_\-\+\.]+-(.*)(\.zip|\.tar.gz)") - - -def getversion(basename): - m = _rex_getversion.match(basename) - if m is None: - return None - version = m.group(1) - try: - return NormalizedVersion(version) - except IrrationalVersionError: - return None diff -r 55437391511703f2fd40f3ee3cf18f89ef8f446a -r 5fbd833e8b0e88cf71920a1479f47260317c98b0 tox/_config.py --- a/tox/_config.py +++ /dev/null @@ -1,1072 +0,0 @@ -import argparse -import os -import random -from fnmatch import fnmatchcase -import sys -import re -import shlex -import string -import pkg_resources -import itertools -import pluggy - -import tox.interpreters -from tox import hookspecs - -import py - -import tox - -iswin32 = sys.platform == "win32" - -default_factors = {'jython': 'jython', 'pypy': 'pypy', 'pypy3': 'pypy3', - 'py': sys.executable} -for version in '26,27,32,33,34,35,36'.split(','): - default_factors['py' + version] = 'python%s.%s' % tuple(version) - -hookimpl = pluggy.HookimplMarker("tox") - - -def get_plugin_manager(): - # initialize plugin manager - pm = pluggy.PluginManager("tox") - pm.add_hookspecs(hookspecs) - pm.register(tox._config) - pm.register(tox.interpreters) - pm.load_setuptools_entrypoints("tox") - pm.check_pending() - return pm - - -class MyParser: - def __init__(self): - self.argparser = argparse.ArgumentParser( - description="tox options", add_help=False) - self._testenv_attr = [] - - def add_argument(self, *args, **kwargs): - return self.argparser.add_argument(*args, **kwargs) - - def add_testenv_attribute(self, name, type, help, default=None, postprocess=None): - self._testenv_attr.append(VenvAttribute(name, type, default, help, postprocess)) - - def add_testenv_attribute_obj(self, obj): - assert hasattr(obj, "name") - assert hasattr(obj, "type") - assert hasattr(obj, "help") - assert hasattr(obj, "postprocess") - self._testenv_attr.append(obj) - - def parse_args(self, args): - return self.argparser.parse_args(args) - - def format_help(self): - return self.argparser.format_help() - - -class VenvAttribute: - def __init__(self, name, type, default, help, postprocess): - self.name = name - self.type = type - self.default = default - self.help = help - self.postprocess = postprocess - - -class DepOption: - name = "deps" - type = "line-list" - help = "each line specifies a dependency in pip/setuptools format." - default = () - - def postprocess(self, config, reader, section_val): - deps = [] - for depline in section_val: - m = re.match(r":(\w+):\s*(\S+)", depline) - if m: - iname, name = m.groups() - ixserver = config.indexserver[iname] - else: - name = depline.strip() - ixserver = None - name = self._replace_forced_dep(name, config) - deps.append(DepConfig(name, ixserver)) - return deps - - 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: - return name - for forced_dep in config.option.force_dep: - 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 - - -class PosargsOption: - name = "args_are_paths" - type = "bool" - default = True - help = "treat positional args in commands as paths" - - def postprocess(self, config, reader, section_val): - args = config.option.args - if args: - if section_val: - args = [] - for arg in config.option.args: - if arg: - origpath = config.invocationcwd.join(arg, abs=True) - if origpath.check(): - arg = reader.getpath("changedir", ".").bestrelpath(origpath) - args.append(arg) - reader.addsubstitutions(args) - return section_val - - -class InstallcmdOption: - name = "install_command" - type = "argv" - default = "pip install {opts} {packages}" - help = "install command for dependencies and package under test." - - def postprocess(self, config, reader, section_val): - if '{packages}' not in section_val: - raise tox.exception.ConfigError( - "'install_command' must contain '{packages}' substitution") - return section_val - - -def parseconfig(args=None): - """ - :param list[str] args: Optional list of arguments. - :type pkg: str - :rtype: :class:`Config` - :raise SystemExit: toxinit file is not found - """ - - pm = get_plugin_manager() - - if args is None: - args = sys.argv[1:] - - # prepare command line options - parser = MyParser() - pm.hook.tox_addoption(parser=parser) - - # parse command line options - option = parser.parse_args(args) - interpreters = tox.interpreters.Interpreters(hook=pm.hook) - config = Config(pluginmanager=pm, option=option, interpreters=interpreters) - config._parser = parser - config._testenv_attr = parser._testenv_attr - - # parse ini file - basename = config.option.configfile - if os.path.isabs(basename): - inipath = py.path.local(basename) - else: - for path in py.path.local().parts(reverse=True): - inipath = path.join(basename) - if inipath.check(): - break - else: - feedback("toxini file %r not found" % (basename), sysexit=True) - try: - parseini(config, inipath) - except tox.exception.InterpreterNotFound: - exn = sys.exc_info()[1] - # Use stdout to match test expectations - py.builtin.print_("ERROR: " + str(exn)) - - # post process config object - pm.hook.tox_configure(config=config) - - return config - - -def feedback(msg, sysexit=False): - py.builtin.print_("ERROR: " + msg, file=sys.stderr) - if sysexit: - raise SystemExit(1) - - -class VersionAction(argparse.Action): - def __call__(self, argparser, *args, **kwargs): - version = tox.__version__ - py.builtin.print_("%s imported from %s" % (version, tox.__file__)) - raise SystemExit(0) - - -class CountAction(argparse.Action): - def __call__(self, parser, namespace, values, option_string=None): - if hasattr(namespace, self.dest): - setattr(namespace, self.dest, int(getattr(namespace, self.dest)) + 1) - else: - setattr(namespace, self.dest, 0) - - -@hookimpl -def tox_addoption(parser): - # formatter_class=argparse.ArgumentDefaultsHelpFormatter) - parser.add_argument("--version", nargs=0, action=VersionAction, - dest="version", - help="report version information to stdout.") - parser.add_argument("-h", "--help", action="store_true", dest="help", - help="show help about options") - parser.add_argument("--help-ini", "--hi", action="store_true", dest="helpini", - help="show help about ini-names") - 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", - 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", - dest="configfile", - help="use the specified config file name.") - parser.add_argument("-e", action="append", dest="env", - metavar="envlist", - help="work against specified environments (ALL selects all).") - parser.add_argument("--notest", action="store_true", dest="notest", - help="skip invoking test commands.") - parser.add_argument("--sdistonly", action="store_true", dest="sdistonly", - help="only perform the sdist packaging activity.") - parser.add_argument("--installpkg", action="store", default=None, - metavar="PATH", - help="use specified package for installation into venv, instead of " - "creating an sdist.") - parser.add_argument("--develop", action="store_true", dest="develop", - help="install package in the venv using 'setup.py develop' via " - "'pip -e .'") - parser.add_argument("--set-home", action="store_true", dest="sethome", - help="(experimental) force creating a new $HOME for each test " - "environment and create .pydistutils.cfg|pip.conf files " - "if index servers are specified with tox. ") - parser.add_argument('-i', action="append", - 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="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") - parser.add_argument("--result-json", action="store", - dest="resultjson", metavar="PATH", - help="write a json file with detailed information about " - "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, 4294967295] " - "([1, 1024] on Windows). " - "Passing 'noset' suppresses this behavior.") - 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. REQ Examples " - "'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") - - # add various core venv interpreter attributes - - parser.add_testenv_attribute( - name="envdir", type="path", default="{toxworkdir}/{envname}", - help="venv directory") - - parser.add_testenv_attribute( - name="envtmpdir", type="path", default="{envdir}/tmp", - help="venv temporary directory") - - parser.add_testenv_attribute( - name="envlogdir", type="path", default="{envdir}/log", - help="venv log directory") - - def downloadcache(config, reader, section_val): - if section_val: - # env var, if present, takes precedence - downloadcache = os.environ.get("PIP_DOWNLOAD_CACHE", section_val) - return py.path.local(downloadcache) - - parser.add_testenv_attribute( - name="downloadcache", type="string", default=None, postprocess=downloadcache, - help="(deprecated) set PIP_DOWNLOAD_CACHE.") - - parser.add_testenv_attribute( - name="changedir", type="path", default="{toxinidir}", - help="directory to change to when running commands") - - parser.add_testenv_attribute_obj(PosargsOption()) - - parser.add_testenv_attribute( - name="skip_install", type="bool", default=False, - help="Do not install the current package. This can be used when " - "you need the virtualenv management but do not want to install " - "the current package") - - parser.add_testenv_attribute( - name="ignore_errors", type="bool", default=False, - help="if set to True all commands will be executed irrespective of their " - "result error status.") - - def recreate(config, reader, section_val): - if config.option.recreate: - return True - return section_val - - parser.add_testenv_attribute( - name="recreate", type="bool", default=False, postprocess=recreate, - help="always recreate this test environment.") - - def setenv(config, reader, section_val): - setenv = section_val - if "PYTHONHASHSEED" not in setenv and config.hashseed is not None: - setenv['PYTHONHASHSEED'] = config.hashseed - return setenv - - parser.add_testenv_attribute( - name="setenv", type="dict", postprocess=setenv, - help="list of X=Y lines with environment variable settings") - - def passenv(config, reader, section_val): - passenv = set(["PATH"]) - - # we ensure that tmp directory settings are passed on - # we could also set it to the per-venv "envtmpdir" - # but this leads to very long paths when run with jenkins - # so we just pass it on by default for now. - if sys.platform == "win32": - passenv.add("SYSTEMROOT") # needed for python's crypto module - passenv.add("PATHEXT") # needed for discovering executables - passenv.add("TEMPDIR") - passenv.add("TMP") - else: - passenv.add("TMPDIR") - for spec in section_val: - for name in os.environ: - if fnmatchcase(name.upper(), spec.upper()): - passenv.add(name) - return passenv - - parser.add_testenv_attribute( - name="passenv", type="space-separated-list", postprocess=passenv, - help="environment variables names which shall be passed " - "from tox invocation to test environment when executing commands.") - - parser.add_testenv_attribute( - name="whitelist_externals", type="line-list", - help="each lines specifies a path or basename for which tox will not warn " - "about it coming from outside the test environment.") - - parser.add_testenv_attribute( - name="platform", type="string", default=".*", - help="regular expression which must match against ``sys.platform``. " - "otherwise testenv will be skipped.") - - def sitepackages(config, reader, section_val): - return config.option.sitepackages or section_val - - parser.add_testenv_attribute( - name="sitepackages", type="bool", default=False, postprocess=sitepackages, - help="Set to ``True`` if you want to create virtual environments that also " - "have access to globally installed packages.") - - def pip_pre(config, reader, section_val): - return config.option.pre or section_val - - parser.add_testenv_attribute( - name="pip_pre", type="bool", default=False, postprocess=pip_pre, - help="If ``True``, adds ``--pre`` to the ``opts`` passed to " - "the install command. ") - - def develop(config, reader, section_val): - return not config.option.installpkg and (section_val or config.option.develop) - - parser.add_testenv_attribute( - name="usedevelop", type="bool", postprocess=develop, default=False, - help="install package in develop/editable mode") - - def basepython_default(config, reader, section_val): - if section_val is None: - for f in reader.factors: - if f in default_factors: - return default_factors[f] - return sys.executable - return str(section_val) - - parser.add_testenv_attribute( - name="basepython", type="string", default=None, postprocess=basepython_default, - help="executable name or path of interpreter used to create a " - "virtual test environment.") - - parser.add_testenv_attribute_obj(InstallcmdOption()) - parser.add_testenv_attribute_obj(DepOption()) - - parser.add_testenv_attribute( - name="commands", type="argvlist", default="", - help="each line specifies a test command and can use substitution.") - - -class Config(object): - def __init__(self, pluginmanager, option, interpreters): - self.envconfigs = {} - self.invocationcwd = py.path.local() - self.interpreters = interpreters - self.pluginmanager = pluginmanager - self.option = option - - @property - def homedir(self): - homedir = get_homedir() - if homedir is None: - homedir = self.toxinidir # XXX good idea? - return homedir - - -class VenvConfig: - def __init__(self, envname, config): - self.envname = envname - self.config = config - - @property - def envbindir(self): - if (sys.platform == "win32" - and "jython" not in self.basepython - and "pypy" not in self.basepython): - return self.envdir.join("Scripts") - else: - return self.envdir.join("bin") - - @property - def envpython(self): - if "jython" in str(self.basepython): - name = "jython" - else: - name = "python" - return self.envbindir.join(name) - - # no @property to avoid early calling (see callable(subst[key]) checks) - def envsitepackagesdir(self): - self.getsupportedinterpreter() # for throwing exceptions - x = self.config.interpreters.get_sitepackagesdir( - info=self.python_info, - envdir=self.envdir) - return x - - @property - def python_info(self): - return self.config.interpreters.get_info(envconfig=self) - - def getsupportedinterpreter(self): - if sys.platform == "win32" and self.basepython and \ - "jython" in self.basepython: - raise tox.exception.UnsupportedInterpreter( - "Jython/Windows does not support installing scripts") - info = self.config.interpreters.get_info(envconfig=self) - 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") - return info.executable - - -testenvprefix = "testenv:" - - -def get_homedir(): - try: - return py.path.local._gethomedir() - except Exception: - return None - - -def make_hashseed(): - max_seed = 4294967295 - if sys.platform == 'win32': - max_seed = 1024 - return str(random.randint(1, max_seed)) - - -class parseini: - def __init__(self, config, inipath): - config.toxinipath = inipath - config.toxinidir = config.toxinipath.dirpath() - - self._cfg = py.iniconfig.IniConfig(config.toxinipath) - config._cfg = self._cfg - self.config = config - ctxname = getcontextname() - if ctxname == "jenkins": - reader = SectionReader("tox:jenkins", self._cfg, fallbacksections=['tox']) - distshare_default = "{toxworkdir}/distshare" - elif not ctxname: - reader = SectionReader("tox", self._cfg) - distshare_default = "{homedir}/.tox/distshare" - 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) - config.toxworkdir = reader.getpath("toxworkdir", "{toxinidir}/.tox") - config.minversion = reader.getstring("minversion", None) - - if not config.option.skip_missing_interpreters: - config.option.skip_missing_interpreters = \ - reader.getbool("skip_missing_interpreters", False) - - # determine indexserver dictionary - config.indexserver = {'default': IndexServerConfig('default')} - prefix = "indexserver" - for line in reader.getlist(prefix): - name, url = map(lambda x: x.strip(), line.split("=", 1)) - config.indexserver[name] = IndexServerConfig(name, url) - - override = False - if config.option.indexurl: - for urldef in config.option.indexurl: - m = re.match(r"\W*(\w+)=(\S+)", urldef) - if m is None: - url = urldef - name = "default" - else: - name, url = m.groups() - if not url: - url = None - if name != "ALL": - config.indexserver[name].url = url - else: - override = url - # let ALL override all existing entries - if override: - for name in config.indexserver: - config.indexserver[name] = IndexServerConfig(name, override) - - reader.addsubstitutions(toxworkdir=config.toxworkdir) - config.distdir = reader.getpath("distdir", "{toxworkdir}/dist") - reader.addsubstitutions(distdir=config.distdir) - config.distshare = reader.getpath("distshare", distshare_default) - reader.addsubstitutions(distshare=config.distshare) - config.sdistsrc = reader.getpath("sdistsrc", None) - config.setupdir = reader.getpath("setupdir", "{toxinidir}") - config.logdir = config.toxworkdir.join("log") - - config.envlist, all_envs = self._getenvdata(reader) - - # factors used in config or predefined - known_factors = self._list_section_factors("testenv") - known_factors.update(default_factors) - known_factors.add("python") - - # factors stated in config envlist - stated_envlist = reader.getstring("envlist", replace=False) - if stated_envlist: - for env in _split_env(stated_envlist): - known_factors.update(env.split('-')) - - # configure testenvs - for name in all_envs: - section = testenvprefix + name - factors = set(name.split('-')) - if section in self._cfg or factors <= known_factors: - config.envconfigs[name] = \ - self.make_envconfig(name, section, reader._subs, config) - - all_develop = all(name in config.envconfigs - and config.envconfigs[name].usedevelop - for name in config.envlist) - - config.skipsdist = reader.getbool("skipsdist", all_develop) - - def _list_section_factors(self, section): - factors = set() - if section in self._cfg: - for _, value in self._cfg[section].items(): - exprs = re.findall(r'^([\w{}\.,-]+)\:\s+', value, re.M) - factors.update(*mapcat(_split_factor_expr, exprs)) - return factors - - def make_envconfig(self, name, section, subs, config): - vc = VenvConfig(config=config, envname=name) - factors = set(name.split('-')) - reader = SectionReader(section, self._cfg, fallbacksections=["testenv"], - factors=factors) - reader.addsubstitutions(**subs) - reader.addsubstitutions(envname=name) - reader.vc = vc - - for env_attr in config._testenv_attr: - atype = env_attr.type - if atype in ("bool", "path", "string", "dict", "argv", "argvlist"): - meth = getattr(reader, "get" + atype) - res = meth(env_attr.name, env_attr.default) - elif atype == "space-separated-list": - res = reader.getlist(env_attr.name, sep=" ") - elif atype == "line-list": - res = reader.getlist(env_attr.name, sep="\n") - else: - raise ValueError("unknown type %r" % (atype,)) - - if env_attr.postprocess: - res = env_attr.postprocess(config, reader, res) - setattr(vc, env_attr.name, res) - - if atype == "path": - reader.addsubstitutions(**{env_attr.name: res}) - - if env_attr.name == "install_command": - reader.addsubstitutions(envbindir=vc.envbindir, envpython=vc.envpython, - envsitepackagesdir=vc.envsitepackagesdir) - return vc - - def _getenvdata(self, reader): - envstr = self.config.option.env \ - or os.environ.get("TOXENV") \ - or reader.getstring("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 _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) - 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): - self.name = name - self.indexserver = indexserver - - def __str__(self): - if self.indexserver: - if self.indexserver.name == "default": - return self.name - return ":%s:%s" % (self.indexserver.name, self.name) - return str(self.name) - __repr__ = __str__ - - -class IndexServerConfig: - def __init__(self, name, url=None): - self.name = name - self.url = url - - -#: Check value matches substitution form -#: of referencing value from other section. E.g. {[base]commands} -is_section_substitution = re.compile("{\[[^{}\s]+\]\S+?}").match - - -RE_ITEM_REF = re.compile( - r''' - (?<!\\)[{] - (?:(?P<sub_type>[^[:{}]+):)? # optional sub_type for special rules - (?P<substitution_value>[^{}]*) # substitution key - [}] - ''', - re.VERBOSE) - - -class SectionReader: - def __init__(self, section_name, cfgparser, fallbacksections=None, factors=()): - self.section_name = section_name - self._cfg = cfgparser - self.fallbacksections = fallbacksections or [] - self.factors = factors - self._subs = {} - self._subststack = [] - - def addsubstitutions(self, _posargs=None, **kw): - self._subs.update(kw) - if _posargs: - self.posargs = _posargs - - def getpath(self, name, defaultpath): - toxinidir = self._subs['toxinidir'] - path = self.getstring(name, defaultpath) - if path is None: - return path - return toxinidir.join(path, abs=True) - - def getlist(self, name, sep="\n"): - s = self.getstring(name, None) - if s is None: - return [] - return [x.strip() for x in s.split(sep) if x.strip()] - - def getdict(self, name, default=None, sep="\n"): - s = self.getstring(name, None) - if s is None: - return default or {} - - value = {} - for line in s.split(sep): - if line.strip(): - name, rest = line.split('=', 1) - value[name.strip()] = rest.strip() - - return value - - def getbool(self, name, default=None): - s = self.getstring(name, default) - if not s: - s = default - if s is None: - raise KeyError("no config value [%s] %s found" % ( - self.section_name, name)) - - if not isinstance(s, bool): - if s.lower() == "true": - s = True - elif s.lower() == "false": - s = False - else: - raise tox.exception.ConfigError( - "boolean value %r needs to be 'True' or 'False'") - return s - - def getargvlist(self, name, default=""): - s = self.getstring(name, default, replace=False) - return _ArgvlistReader.getargvlist(self, s) - - def getargv(self, name, default=""): - return self.getargvlist(name, default)[0] - - def getstring(self, name, default=None, replace=True): - x = None - for s in [self.section_name] + 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((self.section_name, name)) - try: - x = self._replace(x) - finally: - assert self._subststack.pop() == (self.section_name, name) - # print "getstring", self.section_name, 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 - - expr, line = m.groups() - if any(fs <= self.factors for fs in _split_factor_expr(expr)): - 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: - 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 envkey not 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, default) - - def _substitute_from_other_section(self, key): - if key.startswith("[") and "]" in key: - i = key.find("]") - section, item = key[1:i], key[i + 1:] - if section in self._cfg and item in self._cfg[section]: - if (section, item) in self._subststack: - raise ValueError('%s already in %s' % ( - (section, item), self._subststack)) - x = str(self._cfg[section][item]) - self._subststack.append((section, item)) - try: - return self._replace(x) - finally: - self._subststack.pop() - - raise tox.exception.ConfigError( - "substitution key %r not found" % key) - - 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) - if py.builtin.callable(val): - val = val() - return str(val) - - def _replace_match(self, match): - g = match.groupdict() - - # special case: opts and packages. Leave {opts} and - # {packages} intact, they are replaced manually in - # _venv.VirtualEnv.run_install_command. - sub_value = g['substitution_value'] - if sub_value in ('opts', 'packages'): - return '{%s}' % sub_value - - handlers = { - 'env': self._replace_env, - None: self._replace_substitution, - } - try: - sub_type = g['sub_type'] - except KeyError: - raise tox.exception.ConfigError( - "Malformed substitution; no substitution type provided") - - try: - handler = handlers[sub_type] - except KeyError: - raise tox.exception.ConfigError("No support for the %s substitution type" % sub_type) - - return handler(match) - - def _replace(self, x): - if '{' in x: - return RE_ITEM_REF.sub(self._replace_match, x) - return x - - -class _ArgvlistReader: - @classmethod - def getargvlist(cls, reader, section_val): - """Parse ``commands`` argvlist multiline string. - - :param str name: Key name in a section. - :param str section_val: Content stored by key. - - :rtype: list[list[str]] - :raise :class:`tox.exception.ConfigError`: - line-continuation ends nowhere while resolving for specified section - """ - commands = [] - current_command = "" - for line in section_val.splitlines(): - line = line.rstrip() - i = line.find("#") - if i != -1: - line = line[:i].rstrip() - if not line: - continue - if line.endswith("\\"): - current_command += " " + line[:-1] - continue - current_command += line - - if is_section_substitution(current_command): - replaced = reader._replace(current_command) - commands.extend(cls.getargvlist(reader, replaced)) - else: - commands.append(cls.processcommand(reader, current_command)) - current_command = "" - else: - if current_command: - raise tox.exception.ConfigError( - "line-continuation ends nowhere while resolving for [%s] %s" % - (reader.section_name, "commands")) - return commands - - @classmethod - def processcommand(cls, reader, command): - posargs = getattr(reader, "posargs", None) - - # 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 == "{posargs}" or word == "[]": - if posargs: - newcommand += " ".join(posargs) - continue - elif word.startswith("{posargs:") and word.endswith("}"): - if posargs: - newcommand += " ".join(posargs) - continue - else: - word = word[9:-1] - new_arg = "" - new_word = reader._replace(word) - new_word = reader._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 - - -class CommandParser(object): - - class State(object): - def __init__(self): - self.word = '' - self.depth = 0 - self.yield_words = [] - - def __init__(self, command): - self.command = command - - def words(self): - ps = CommandParser.State() - - 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 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() == '')) - - def yield_this_word(): - yieldword = ps.word - ps.word = '' - if yieldword: - ps.yield_words.append(yieldword) - - def yield_if_word_ended(): - if word_has_ended(): - yield_this_word() - - def accumulate(): - ps.word += cur_char - - def push_substitution(): - ps.depth += 1 - - def pop_substitution(): - ps.depth -= 1 - - for cur_char in self.command: - if cur_char in string.whitespace: - if ps.depth == 0: - yield_if_word_ended() - accumulate() - elif cur_char == '{': - yield_if_word_ended() - accumulate() - push_substitution() - elif cur_char == '}': - accumulate() - pop_substitution() - else: - yield_if_word_ended() - accumulate() - - if ps.word.strip(): - yield_this_word() - return ps.yield_words - - -def getcontextname(): - if any(env in os.environ for env in ['JENKINS_URL', 'HUDSON_URL']): - return 'jenkins' - return None diff -r 55437391511703f2fd40f3ee3cf18f89ef8f446a -r 5fbd833e8b0e88cf71920a1479f47260317c98b0 tox/_exception.py --- a/tox/_exception.py +++ /dev/null @@ -1,28 +0,0 @@ - -class Error(Exception): - def __str__(self): - return "%s: %s" % (self.__class__.__name__, self.args[0]) - - -class UnsupportedInterpreter(Error): - "signals an unsupported Interpreter" - - -class InterpreterNotFound(Error): - "signals that an interpreter could not be found" - - -class InvocationError(Error): - """ an error while invoking a script. """ - - -class MissingFile(Error): - """ an error while invoking a script. """ - - -class MissingDirectory(Error): - """ a directory did not exist. """ - - -class MissingDependency(Error): - """ a dependency could not be found or determined. """ diff -r 55437391511703f2fd40f3ee3cf18f89ef8f446a -r 5fbd833e8b0e88cf71920a1479f47260317c98b0 tox/_pytestplugin.py --- a/tox/_pytestplugin.py +++ b/tox/_pytestplugin.py @@ -6,10 +6,10 @@ from py.builtin import _isbytes, _istext, print_ from fnmatch import fnmatch import time -from tox._config import parseconfig -from tox._venv import VirtualEnv -from tox._cmdline import Action -from tox.result import ResultLog +from .config import parseconfig +from .venv import VirtualEnv +from .session import Action +from .result import ResultLog def pytest_configure(): @@ -134,7 +134,7 @@ @pytest.fixture def mocksession(request): - from tox._cmdline import Session + from tox.session import Session class MockSession(Session): def __init__(self): This diff is so big that we needed to truncate the remainder. Repository URL: https://bitbucket.org/hpk42/tox/ -- This is a commit notification from bitbucket.org. You are receiving this because you have the service enabled, addressing the recipient of this email. _______________________________________________ pytest-commit mailing list pytest-commit@python.org https://mail.python.org/mailman/listinfo/pytest-commit