diff options
| author | holger krekel <holger@merlinux.eu> | 2015-05-12 14:01:28 +0200 |
|---|---|---|
| committer | holger krekel <holger@merlinux.eu> | 2015-05-12 14:01:28 +0200 |
| commit | 904844849ae03baab8d37b87267aed27c55e8db0 (patch) | |
| tree | 40fffd7df8d2119ecb2b0c8710c65d2724ce63a3 | |
| parent | 7549cce84c52115e0b8f47737753698579a6ac0b (diff) | |
| download | tox-904844849ae03baab8d37b87267aed27c55e8db0.tar.gz | |
- 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
| -rw-r--r-- | CHANGELOG | 5 | ||||
| -rw-r--r-- | doc/Makefile | 3 | ||||
| -rw-r--r-- | doc/announce/release-2.0.txt | 54 | ||||
| -rw-r--r-- | doc/conf.py | 2 | ||||
| -rw-r--r-- | setup.py | 2 | ||||
| -rw-r--r-- | tests/test_config.py | 62 | ||||
| -rw-r--r-- | tests/test_z_cmdline.py | 22 | ||||
| -rw-r--r-- | tox.ini | 2 | ||||
| -rw-r--r-- | tox/__init__.py | 2 | ||||
| -rw-r--r-- | tox/_cmdline.py | 73 | ||||
| -rw-r--r-- | tox/_config.py | 12 | ||||
| -rw-r--r-- | tox/_venv.py | 1 | ||||
| -rw-r--r-- | tox/result.py | 3 |
13 files changed, 167 insertions, 76 deletions
@@ -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 --git a/doc/Makefile b/doc/Makefile index d3393d6..4ffb98b 100644 --- a/doc/Makefile +++ b/doc/Makefile @@ -37,7 +37,8 @@ clean: -rm -rf $(BUILDDIR)/* install: clean html - @rsync -avz $(BUILDDIR)/html/ testrun.org:/www/testrun.org/tox/dev + @rsync -avz $(BUILDDIR)/html/ testrun.org:/www/testrun.org/tox/latest + #dev #latexpdf #@scp $(BUILDDIR)/latex/*.pdf testrun.org:www-tox/latest diff --git a/doc/announce/release-2.0.txt b/doc/announce/release-2.0.txt new file mode 100644 index 0000000..9f87844 --- /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 --git a/doc/conf.py b/doc/conf.py index 73f5168..802ccd4 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -41,7 +41,7 @@ master_doc = 'index' # 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 @@ -26,7 +26,7 @@ def main(): description='virtualenv-based automation of test activities', long_description=open("README.rst").read(), url='http://tox.testrun.org/', - version='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 --git a/tests/test_config.py b/tests/test_config.py index 87f7f6d..e9c941a 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -827,13 +827,13 @@ class TestConfigTestEnv: 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 @@ class TestConfigTestEnv: {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 @@ class TestConfigTestEnv: 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 @@ class TestConfigTestEnv: 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 @@ class TestConfigTestEnv: 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 @@ class TestGlobalOptions: 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 @@ class TestGlobalOptions: 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 @@ class TestGlobalOptions: 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 @@ class TestGlobalOptions: 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 --git a/tests/test_z_cmdline.py b/tests/test_z_cmdline.py index e793804..b5cbc4e 100644 --- a/tests/test_z_cmdline.py +++ b/tests/test_z_cmdline.py @@ -83,15 +83,15 @@ class TestSession: }) 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 @@ class TestSession: }) 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 @@ def test_sdist_latest(tmpdir, newconfig): 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 @@ def test_installpkg(tmpdir, newconfig): 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 @@ def verify_json_report_format(data, testenvs=True): 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"] @@ -5,7 +5,7 @@ envlist=py27,py26,py34,py33,pypy,flakes commands=echo {posargs} [testenv] -commands= py.test --timeout=60 {posargs} +commands= py.test --timeout=180 {posargs} deps=pytest>=2.3.5 pytest-timeout diff --git a/tox/__init__.py b/tox/__init__.py index e0bff5a..7e71062 100644 --- 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 --git a/tox/_cmdline.py b/tox/_cmdline.py index 2a9f391..0c2f9d6 100644 --- a/tox/_cmdline.py +++ b/tox/_cmdline.py @@ -67,10 +67,12 @@ class Action(object): 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 @@ class Action(object): 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 @@ class Session: 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 @@ class Session: 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 @@ class Session: 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 @@ class Session: 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 --git a/tox/_config.py b/tox/_config.py index cbbf5e9..85ff822 100644 --- a/tox/_config.py +++ b/tox/_config.py @@ -21,7 +21,7 @@ iswin32 = sys.platform == "win32" default_factors = {'jython': 'jython', 'pypy': 'pypy', 'pypy3': 'pypy3', 'py': sys.executable} -for version in '24,25,26,27,30,31,32,33,34,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 tox_addoption(parser): 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 @@ class parseini: factors=factors) reader.addsubstitutions(**subs) reader.addsubstitutions(envname=name) + reader.vc = vc for env_attr in config._testenv_attr: atype = env_attr.type diff --git a/tox/_venv.py b/tox/_venv.py index d4852bc..d52a6e5 100644 --- a/tox/_venv.py +++ b/tox/_venv.py @@ -327,7 +327,6 @@ class VirtualEnv(object): env['VIRTUAL_ENV'] = str(self.path) env.update(extraenv) - return env def test(self, redirect=False): diff --git a/tox/result.py b/tox/result.py index 7bedd3f..96ac612 100644 --- a/tox/result.py +++ b/tox/result.py @@ -63,6 +63,9 @@ class EnvLog: 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): |
