tox-2.3.1/0000775000175000017500000000000012633511761011724 5ustar hpkhpk00000000000000tox-2.3.1/ISSUES.txt0000664000175000017500000001166012633511761013504 0ustar hpkhpk00000000000000introduce package and setup.py less sdist creation ----------------------------------------------------------- Example sections for tox itself:: [pkgdef] basename = pytest description = virtualenv-based automation of test activities authors = holger krekel url = http://tox.testrun.org entry_points = console_scripts: tox=tox:cmdline requires = py packages = preuploadenvs = sphinx classifiers= Development Status :: 6 - Mature Intended Audience :: Developers License :: OSI Approved :: MIT License Topic :: Software Development :: Testing Topic :: Software Development :: Libraries Topic :: Utilities This would generate three different packages: - the main one containing the app with the specified description, etc. It has a test-requires pointing to the test package, which classifiers - the test package only containing the tests and setup.py depending on the main package and all requirements collected from the testenv - the doc package containing generated html and txt files (to be installable via a setup.py as well?) Here is what happens when tox is invoked now: - version gets auto-incremented (in setup.py and $PACKAGE/__init__.py files) - main setup.py generated, files copied, sdist generated - test setup.py generated, files copied, sdist generated - doc setup.py generated, doc environment run, files copied, sdist generated - if --upload is specified, all packages are uploaded under their respective names: tox-VER tests-tox-VER docs-tox-VER - tox --sync creates a test result file for the tests-tox-VER run and uploads it to testrun.org -- .tox/projectkeys contains a file that was created by visiting testrun.org and registering/logging in. - download toxslave and execute it: toxslave --userkey=... [--project tox] which will query testrun.org for outstanding testruns [for the tox project], download packages, execute them and report back to the server merge tox and detox? ---------------------------------------------------------------- maybe it's time to merge it? http://lists.idyll.org/pipermail/testing-in-python/2012-October/005205.html pyc files / test distributions ----------------------------------------------------------- investigate pyc cleaning, see http://lists.idyll.org/pipermail/testing-in-python/2012-October/005205.html allow config overlays ----------------------------------------------------------- tags: 1.5 feature You can specify wildcards in section titles. [testenv] testsrc=testing/**.py commands=py.test {posargs} [testenv:py3*] use2to3 = True [testenv:py32] deps = ... Configuration values are now looked up in a stricter-matches-first manner: If environment "py32" is considered then first the section under py32, then py3*, then testenv. support 2to3 configurations / test file postprocessing ----------------------------------------------------------- tags: 1.5 feature Tests should be copied and transformed before being executed. Setup a test definition:: [testenv] testsrc=testing/**.py commands=py.test {posargs} [testenv:py3*] use2to3 = True would transform all specified test files for all py3 environments such that a subsequent test run:: $ tox -e py25,py32 testing/test_hello.py causes a transformation of the test files to prepare test runs on Python3 interpreters. The ``posargs`` specified files will be rewritten to point to the transformed test files. export support for travis ---------------------------------------- tag: 1.6 feature look into ways to support integration of tox with travis. - run tox from travis - let tox read commands from travis config - generate .travis.yml from tox.ini - generate tox.ini from .travis.yml For the last two, take a look at http://pypi.python.org/pypi/panci/ allow user-specific python interpreters ------------------------------------------------ users should be able to define their set of python executables to be used for creating virtualenvs. .toxrc/interpreters: pypy-c=~/p/pypy/branch/sys-prefix/pypy-c ... non-cached test dependencies --------------------------------------------------------------- if there is a dependency on a URL whose content changes the download-cache mechanism will prevent it from being reloaded. Introduce a 'nocache:' prefix which will inhibit using the cache. Also make and document a guarantee on the deps order so that one can influence the exact configuration (e.g. use a dev-version of some package which a later dependency or the original package depends upon - i.e. deps should be installed first). test and make "in-pkg" tests work --------------------------------------- it is common to put tests into pkg subdirs, possibly even with an __init__. See if/how this can be made to work. Maybe also re-consider how py.test does its importing, maybe add a pytest_addsyspath(testmodule) and look how nose does it in detail. tox-2.3.1/setup.cfg0000664000175000017500000000037612633511761013553 0ustar hpkhpk00000000000000[build_sphinx] source-dir = doc/ build-dir = doc/build all_files = 1 [upload_sphinx] upload-dir = doc/en/build/html [bdist_wheel] universal = 1 [devpi:upload] formats = sdist.tgz,bdist_wheel [egg_info] tag_build = tag_date = 0 tag_svn_revision = 0 tox-2.3.1/PKG-INFO0000664000175000017500000000317012633511761013022 0ustar hpkhpk00000000000000Metadata-Version: 1.1 Name: tox Version: 2.3.1 Summary: virtualenv-based automation of test activities Home-page: http://tox.testrun.org/ Author: holger krekel Author-email: holger@merlinux.eu License: http://opensource.org/licenses/MIT Description: What is Tox? -------------------- Tox is a generic virtualenv management and test command line tool you can use for: * checking your package installs correctly with different Python versions and interpreters * running your tests in each of the environments, configuring your test tool of choice * acting as a frontend to Continuous Integration servers, greatly reducing boilerplate and merging CI and shell-based testing. For more information and the repository please checkout: - homepage: http://tox.testrun.org - repository: https://bitbucket.org/hpk42/tox have fun, holger krekel, 2015 Platform: unix Platform: linux Platform: osx Platform: cygwin Platform: win32 Classifier: Development Status :: 5 - Production/Stable Classifier: Intended Audience :: Developers Classifier: License :: OSI Approved :: MIT License Classifier: Operating System :: POSIX Classifier: Operating System :: Microsoft :: Windows Classifier: Operating System :: MacOS :: MacOS X Classifier: Topic :: Software Development :: Testing Classifier: Topic :: Software Development :: Libraries Classifier: Topic :: Utilities Classifier: Programming Language :: Python Classifier: Programming Language :: Python :: 3 tox-2.3.1/README.rst0000664000175000017500000000113612633511761013414 0ustar hpkhpk00000000000000 What is Tox? -------------------- Tox is a generic virtualenv management and test command line tool you can use for: * checking your package installs correctly with different Python versions and interpreters * running your tests in each of the environments, configuring your test tool of choice * acting as a frontend to Continuous Integration servers, greatly reducing boilerplate and merging CI and shell-based testing. For more information and the repository please checkout: - homepage: http://tox.testrun.org - repository: https://bitbucket.org/hpk42/tox have fun, holger krekel, 2015 tox-2.3.1/tox/0000775000175000017500000000000012633511761012536 5ustar hpkhpk00000000000000tox-2.3.1/tox/_pytestplugin.py0000664000175000017500000002405012633511761016017 0ustar hpkhpk00000000000000import py import pytest import tox import os import sys from py.builtin import _isbytes, _istext, print_ from fnmatch import fnmatch import time from .config import parseconfig from .venv import VirtualEnv from .session import Action from .result import ResultLog def pytest_configure(): if 'TOXENV' in os.environ: del os.environ['TOXENV'] if 'HUDSON_URL' in os.environ: del os.environ['HUDSON_URL'] def pytest_addoption(parser): parser.addoption("--no-network", action="store_true", dest="no_network", help="don't run tests requiring network") def pytest_report_header(): return "tox comes from: %r" % (tox.__file__) @pytest.fixture def newconfig(request, tmpdir): def newconfig(args, source=None, plugins=()): if source is None: source = args args = [] s = py.std.textwrap.dedent(source) p = tmpdir.join("tox.ini") p.write(s) old = tmpdir.chdir() try: return parseconfig(args, plugins=plugins) finally: old.chdir() return newconfig @pytest.fixture def cmd(request): if request.config.option.no_network: pytest.skip("--no-network was specified, test cannot run") return Cmd(request) class ReportExpectMock: def __init__(self, session): self._calls = [] self._index = -1 self.session = session def clear(self): self._calls[:] = [] def __getattr__(self, name): if name[0] == "_": raise AttributeError(name) def generic_report(*args, **kwargs): self._calls.append((name,) + args) print ("%s" % (self._calls[-1], )) return generic_report def action(self, venv, msg, *args): self._calls.append(("action", venv, msg)) print ("%s" % (self._calls[-1], )) return Action(self.session, venv, msg, args) def getnext(self, cat): __tracebackhide__ = True newindex = self._index + 1 while newindex < len(self._calls): call = self._calls[newindex] lcat = call[0] if fnmatch(lcat, cat): self._index = newindex return call newindex += 1 raise LookupError( "looking for %r, no reports found at >=%d in %r" % (cat, self._index + 1, self._calls)) def expect(self, cat, messagepattern="*", invert=False): __tracebackhide__ = True if not messagepattern.startswith("*"): messagepattern = "*" + messagepattern while self._index < len(self._calls): try: call = self.getnext(cat) except LookupError: break for lmsg in call[1:]: lmsg = str(lmsg).replace("\n", " ") if fnmatch(lmsg, messagepattern): if invert: raise AssertionError("found %s(%r), didn't expect it" % (cat, messagepattern)) return if not invert: raise AssertionError( "looking for %s(%r), no reports found at >=%d in %r" % (cat, messagepattern, self._index + 1, self._calls)) def not_expect(self, cat, messagepattern="*"): return self.expect(cat, messagepattern, invert=True) class pcallMock: def __init__(self, args, cwd, env, stdout, stderr, shell): self.arg0 = args[0] self.args = args[1:] self.cwd = cwd self.env = env self.stdout = stdout self.stderr = stderr self.shell = shell def communicate(self): return "", "" def wait(self): pass @pytest.fixture def mocksession(request): from tox.session import Session class MockSession(Session): def __init__(self): self._clearmocks() self.config = request.getfuncargvalue("newconfig")([], "") self.resultlog = ResultLog() self._actions = [] def getenv(self, name): return VirtualEnv(self.config.envconfigs[name], session=self) def _clearmocks(self): self._pcalls = [] self._spec2pkg = {} self.report = ReportExpectMock(self) def make_emptydir(self, path): pass def popen(self, args, cwd, shell=None, universal_newlines=False, stdout=None, stderr=None, env=None): pm = pcallMock(args, cwd, env, stdout, stderr, shell) self._pcalls.append(pm) return pm return MockSession() @pytest.fixture def newmocksession(request): mocksession = request.getfuncargvalue("mocksession") newconfig = request.getfuncargvalue("newconfig") def newmocksession(args, source, plugins=()): mocksession.config = newconfig(args, source, plugins=plugins) return mocksession return newmocksession class Cmd: def __init__(self, request): self.tmpdir = request.getfuncargvalue("tmpdir") self.request = request current = py.path.local() self.request.addfinalizer(current.chdir) def chdir(self, target): target.chdir() def popen(self, argv, stdout, stderr, **kw): if not hasattr(py.std, 'subprocess'): py.test.skip("no subprocess module") env = os.environ.copy() env['PYTHONPATH'] = ":".join(filter(None, [ str(os.getcwd()), env.get('PYTHONPATH', '')])) kw['env'] = env # print "env", env return py.std.subprocess.Popen(argv, stdout=stdout, stderr=stderr, **kw) def run(self, *argv): argv = [str(x) for x in argv] assert py.path.local.sysfind(str(argv[0])), argv[0] p1 = self.tmpdir.join("stdout") p2 = self.tmpdir.join("stderr") print("%s$ %s" % (os.getcwd(), " ".join(argv))) f1 = p1.open("wb") f2 = p2.open("wb") now = time.time() popen = self.popen(argv, stdout=f1, stderr=f2, close_fds=(sys.platform != "win32")) ret = popen.wait() f1.close() f2.close() out = p1.read("rb") out = getdecoded(out).splitlines() err = p2.read("rb") err = getdecoded(err).splitlines() def dump_lines(lines, fp): try: for line in lines: py.builtin.print_(line, file=fp) except UnicodeEncodeError: print("couldn't print to %s because of encoding" % (fp,)) dump_lines(out, sys.stdout) dump_lines(err, sys.stderr) return RunResult(ret, out, err, time.time() - now) def getdecoded(out): try: return out.decode("utf-8") except UnicodeDecodeError: return "INTERNAL not-utf8-decodeable, truncated string:\n%s" % ( py.io.saferepr(out),) class RunResult: def __init__(self, ret, outlines, errlines, duration): self.ret = ret self.outlines = outlines self.errlines = errlines self.stdout = LineMatcher(outlines) self.stderr = LineMatcher(errlines) self.duration = duration class LineMatcher: def __init__(self, lines): self.lines = lines def str(self): return "\n".join(self.lines) def fnmatch_lines(self, lines2): if isinstance(lines2, str): lines2 = py.code.Source(lines2) if isinstance(lines2, py.code.Source): lines2 = lines2.strip().lines from fnmatch import fnmatch lines1 = self.lines[:] nextline = None extralines = [] __tracebackhide__ = True for line in lines2: nomatchprinted = False while lines1: nextline = lines1.pop(0) if line == nextline: print_("exact match:", repr(line)) break elif fnmatch(nextline, line): print_("fnmatch:", repr(line)) print_(" with:", repr(nextline)) break else: if not nomatchprinted: print_("nomatch:", repr(line)) nomatchprinted = True print_(" and:", repr(nextline)) extralines.append(nextline) else: assert line == nextline @pytest.fixture def initproj(request, tmpdir): """ create a factory function for creating example projects. """ def initproj(nameversion, filedefs=None): if filedefs is None: filedefs = {} if _istext(nameversion) or _isbytes(nameversion): parts = nameversion.split("-") if len(parts) == 1: parts.append("0.1") name, version = parts else: name, version = nameversion base = tmpdir.ensure(name, dir=1) create_files(base, filedefs) if 'setup.py' not in filedefs: create_files(base, {'setup.py': ''' from setuptools import setup setup( name='%(name)s', description='%(name)s project', version='%(version)s', license='MIT', platforms=['unix', 'win32'], packages=['%(name)s', ], ) ''' % locals()}) if name not in filedefs: create_files(base, { name: {'__init__.py': '__version__ = %r' % version} }) manifestlines = [] for p in base.visit(lambda x: x.check(file=1)): manifestlines.append("include %s" % p.relto(base)) create_files(base, {"MANIFEST.in": "\n".join(manifestlines)}) print ("created project in %s" % (base,)) base.chdir() return initproj def create_files(base, filedefs): for key, value in filedefs.items(): if isinstance(value, dict): create_files(base.ensure(key, dir=1), value) elif isinstance(value, str): s = py.std.textwrap.dedent(value) base.join(key).write(s) tox-2.3.1/tox/hookspecs.py0000664000175000017500000000227512633511761015114 0ustar hpkhpk00000000000000""" Hook specifications for tox. """ from pluggy import HookspecMarker, HookimplMarker hookspec = HookspecMarker("tox") hookimpl = HookimplMarker("tox") @hookspec def tox_addoption(parser): """ add command line options to the argparse-style parser object.""" @hookspec def tox_configure(config): """ called after command line options have been parsed and the ini-file has been read. Please be aware that the config object layout may change as its API was not designed yet wrt to providing stability (it was an internal thing purely before tox-2.0). """ @hookspec(firstresult=True) def tox_get_python_executable(envconfig): """ return a python executable for the given python base name. The first plugin/hook which returns an executable path will determine it. ``envconfig`` is the testenv configuration which contains per-testenv configuration, notably the ``.envname`` and ``.basepython`` setting. """ @hookspec def tox_testenv_create(venv, action): """ [experimental] perform creation action for this venv. """ @hookspec def tox_testenv_install_deps(venv, action): """ [experimental] perform install dependencies action for this venv. """ tox-2.3.1/tox/result.py0000664000175000017500000000450512633511761014432 0ustar hpkhpk00000000000000import sys import py from tox import __version__ as toxver import json class ResultLog: def __init__(self, dict=None): if dict is None: dict = {} self.dict = dict self.dict.update({"reportversion": "1", "toxversion": toxver}) self.dict["platform"] = sys.platform self.dict["host"] = py.std.socket.getfqdn() def set_header(self, installpkg): """ :param py.path.local installpkg: Path ot the package. """ self.dict["installpkg"] = dict( md5=installpkg.computehash("md5"), sha256=installpkg.computehash("sha256"), basename=installpkg.basename, ) def get_envlog(self, name): testenvs = self.dict.setdefault("testenvs", {}) d = testenvs.setdefault(name, {}) return EnvLog(self, name, d) def dumps_json(self): return json.dumps(self.dict, indent=2) @classmethod def loads_json(cls, data): return cls(json.loads(data)) class EnvLog: def __init__(self, reportlog, name, dict): self.reportlog = reportlog self.name = name self.dict = dict def set_python_info(self, pythonexecutable): pythonexecutable = py.path.local(pythonexecutable) out = pythonexecutable.sysexec("-c", "import sys; " "print (sys.executable);" "print (list(sys.version_info)); " "print (sys.version)") lines = out.splitlines() executable = lines.pop(0) version_info = eval(lines.pop(0)) version = "\n".join(lines) self.dict["python"] = dict( executable=executable, version_info=version_info, version=version) def get_commandlog(self, name): 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): self.envlog = envlog self.list = list def add_command(self, argv, output, retcode): d = {} self.list.append(d) d["command"] = argv d["output"] = output d["retcode"] = str(retcode) return d tox-2.3.1/tox/_quickstart.py0000664000175000017500000001754212633511761015452 0ustar hpkhpk00000000000000# -*- coding: utf-8 -*- """ tox._quickstart ~~~~~~~~~~~~~~~~~ Command-line script to quickly setup tox.ini for a Python project This file was heavily inspired by and uses code from ``sphinx-quickstart`` in the BSD-licensed `Sphinx project`_. .. Sphinx project_: http://sphinx.pocoo.org/ License for Sphinx ================== Copyright (c) 2007-2011 by the Sphinx team (see AUTHORS file). All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. """ import sys from os import path from codecs import open TERM_ENCODING = getattr(sys.stdin, 'encoding', None) from tox import __version__ # function to get input from terminal -- overridden by the test suite try: # this raw_input is not converted by 2to3 term_input = raw_input except NameError: term_input = input all_envs = ['py26', 'py27', 'py32', 'py33', 'py34', 'py35', 'pypy', 'jython'] PROMPT_PREFIX = '> ' QUICKSTART_CONF = '''\ # Tox (http://tox.testrun.org/) is a tool for running tests # in multiple virtualenvs. This configuration file will run the # test suite on all supported python versions. To use it, "pip install tox" # and then run "tox" from this directory. [tox] envlist = %(envlist)s [testenv] commands = %(commands)s deps = %(deps)s ''' class ValidationError(Exception): """Raised for validation errors.""" def nonempty(x): if not x: raise ValidationError("Please enter some text.") return x def choice(*l): def val(x): if x not in l: raise ValidationError('Please enter one of %s.' % ', '.join(l)) return x return val def boolean(x): if x.upper() not in ('Y', 'YES', 'N', 'NO'): raise ValidationError("Please enter either 'y' or 'n'.") return x.upper() in ('Y', 'YES') def suffix(x): if not (x[0:1] == '.' and len(x) > 1): raise ValidationError("Please enter a file suffix, " "e.g. '.rst' or '.txt'.") return x def ok(x): return x def do_prompt(d, key, text, default=None, validator=nonempty): while True: if default: prompt = PROMPT_PREFIX + '%s [%s]: ' % (text, default) else: prompt = PROMPT_PREFIX + text + ': ' x = term_input(prompt) if default and not x: x = default if sys.version_info < (3, ) and not isinstance(x, unicode): # for Python 2.x, try to get a Unicode string out of it if x.decode('ascii', 'replace').encode('ascii', 'replace') != x: if TERM_ENCODING: x = x.decode(TERM_ENCODING) else: print('* Note: non-ASCII characters entered ' 'and terminal encoding unknown -- assuming ' 'UTF-8 or Latin-1.') try: x = x.decode('utf-8') except UnicodeDecodeError: x = x.decode('latin1') try: x = validator(x) except ValidationError: err = sys.exc_info()[1] print('* ' + str(err)) continue break d[key] = x def ask_user(d): """Ask the user for quickstart values missing from *d*. """ print('Welcome to the Tox %s quickstart utility.' % __version__) print(''' This utility will ask you a few questions and then generate a simple tox.ini file to help get you started using tox. Please enter values for the following settings (just press Enter to accept a default value, if one is given in brackets).''') sys.stdout.write('\n') print(''' What Python versions do you want to test against? Choices: [1] py27 [2] py27, py33 [3] (All versions) %s [4] Choose each one-by-one''' % ', '.join(all_envs)) do_prompt(d, 'canned_pyenvs', 'Enter the number of your choice', '3', choice('1', '2', '3', '4')) if d['canned_pyenvs'] == '1': d['py27'] = True elif d['canned_pyenvs'] == '2': for pyenv in ('py27', 'py33'): d[pyenv] = True elif d['canned_pyenvs'] == '3': for pyenv in all_envs: d[pyenv] = True elif d['canned_pyenvs'] == '4': for pyenv in all_envs: if pyenv not in d: do_prompt(d, pyenv, 'Test your project with %s (Y/n)' % pyenv, 'Y', boolean) print(''' What command should be used to test your project -- examples: - py.test - python setup.py test - nosetests package.module - trial package.module''') do_prompt(d, 'commands', 'Command to run to test project', '{envpython} setup.py test') default_deps = ' ' if 'py.test' in d['commands']: default_deps = 'pytest' if 'nosetests' in d['commands']: default_deps = 'nose' if 'trial' in d['commands']: default_deps = 'twisted' print(''' What extra dependencies do your tests have?''') do_prompt(d, 'deps', 'Comma-separated list of dependencies', default_deps) def process_input(d): d['envlist'] = ', '.join([env for env in all_envs if d.get(env) is True]) d['deps'] = '\n' + '\n'.join([ ' %s' % dep.strip() for dep in d['deps'].split(',')]) return d def rtrim_right(text): lines = [] for line in text.split("\n"): lines.append(line.rstrip()) return "\n".join(lines) def generate(d, overwrite=True, silent=False): """Generate project based on values in *d*.""" conf_text = QUICKSTART_CONF % d conf_text = rtrim_right(conf_text) def write_file(fpath, mode, content): print('Creating file %s.' % fpath) f = open(fpath, mode, encoding='utf-8') try: f.write(content) finally: f.close() sys.stdout.write('\n') fpath = 'tox.ini' if path.isfile(fpath) and not overwrite: print('File %s already exists.' % fpath) do_prompt(d, 'fpath', 'Alternative path to write tox.ini contents to', 'tox-generated.ini') fpath = d['fpath'] write_file(fpath, 'w', conf_text) if silent: return sys.stdout.write('\n') print('Finished: A tox.ini file has been created. For information on this file, ' 'see http://tox.testrun.org/latest/config.html') print(''' Execute `tox` to test your project. ''') def main(argv=sys.argv): d = {} if len(argv) > 3: print('Usage: tox-quickstart [root]') sys.exit(1) elif len(argv) == 2: d['path'] = argv[1] try: ask_user(d) except (KeyboardInterrupt, EOFError): print() print('[Interrupted.]') return d = process_input(d) generate(d, overwrite=False) if __name__ == '__main__': main() tox-2.3.1/tox/config.py0000664000175000017500000012526712633511761014372 0ustar hpkhpk00000000000000import 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") _dummy = object() def get_plugin_manager(plugins=()): # initialize plugin manager import tox.venv pm = pluggy.PluginManager("tox") pm.add_hookspecs(hookspecs) pm.register(tox.config) pm.register(tox.interpreters) pm.register(tox.venv) pm.load_setuptools_entrypoints("tox") for plugin in plugins: pm.register(plugin) pm.check_pending() return pm class Parser: """ command line and ini-parser control object. """ def __init__(self): self.argparser = argparse.ArgumentParser( description="tox options", add_help=False) self._testenv_attr = [] def add_argument(self, *args, **kwargs): """ add argument to command line parser. This takes the same arguments that ``argparse.ArgumentParser.add_argument``. """ return self.argparser.add_argument(*args, **kwargs) def add_testenv_attribute(self, name, type, help, default=None, postprocess=None): """ add an ini-file variable for "testenv" section. Types are specified as strings like "bool", "line-list", "string", "argv", "path", "argvlist". The ``postprocess`` function will be called for each testenv like ``postprocess(testenv_config=testenv_config, value=value)`` where ``value`` is the value as read from the ini (or the default value) and ``testenv_config`` is a :py:class:`tox.config.TestenvConfig` instance which will receive all ini-variables as object attributes. Any postprocess function must return a value which will then be set as the final value in the testenv section. """ self._testenv_attr.append(VenvAttribute(name, type, default, help, postprocess)) def add_testenv_attribute_obj(self, obj): """ add an ini-file variable as an object. This works as the ``add_testenv_attribute`` function but expects "name", "type", "help", and "postprocess" attributes on the object. """ 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, testenv_config, value): deps = [] config = testenv_config.config for depline in value: 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 try: dep2_name = pkg_resources.Requirement.parse(dep2).project_name except pkg_resources.RequirementParseError: # we couldn't parse a version, probably a URL return False 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, testenv_config, value): config = testenv_config.config args = config.option.args if args: if value: args = [] for arg in config.option.args: if arg: origpath = config.invocationcwd.join(arg, abs=True) if origpath.check(): arg = testenv_config.changedir.bestrelpath(origpath) args.append(arg) testenv_config._reader.addsubstitutions(args) return value class InstallcmdOption: name = "install_command" type = "argv" default = "pip install {opts} {packages}" help = "install command for dependencies and package under test." def postprocess(self, testenv_config, value): if '{packages}' not in value: raise tox.exception.ConfigError( "'install_command' must contain '{packages}' substitution") return value def parseconfig(args=None, plugins=()): """ :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(plugins) if args is None: args = sys.argv[1:] # prepare command line options parser = Parser() 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) class SetenvDict: def __init__(self, dict, reader): self.reader = reader self.definitions = dict self.resolved = {} self._lookupstack = [] def __contains__(self, name): return name in self.definitions def get(self, name, default=None): try: return self.resolved[name] except KeyError: try: if name in self._lookupstack: raise KeyError(name) val = self.definitions[name] except KeyError: return os.environ.get(name, default) self._lookupstack.append(name) try: self.resolved[name] = res = self.reader._replace(val) finally: self._lookupstack.pop() return res def __getitem__(self, name): x = self.get(name, _dummy) if x is _dummy: raise KeyError(name) return x def keys(self): return self.definitions.keys() def __setitem__(self, name, value): self.definitions[name] = value self.resolved[name] = value @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('-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.") # 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") parser.add_testenv_attribute( name="envdir", type="path", default="{toxworkdir}/{envname}", help="venv directory") # add various core venv interpreter attributes def setenv(testenv_config, value): setenv = value config = testenv_config.config 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_setenv", postprocess=setenv, help="list of X=Y lines with environment variable settings") def basepython_default(testenv_config, value): if value is None: for f in testenv_config.factors: if f in default_factors: return default_factors[f] return sys.executable return str(value) 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( 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(testenv_config, value): if value: # env var, if present, takes precedence downloadcache = os.environ.get("PIP_DOWNLOAD_CACHE", value) 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(testenv_config, value): if testenv_config.config.option.recreate: return True return value parser.add_testenv_attribute( name="recreate", type="bool", default=False, postprocess=recreate, help="always recreate this test environment.") def passenv(testenv_config, value): # Flatten the list to deal with space-separated values. value = list( itertools.chain.from_iterable( [x.split(' ') for x in value])) passenv = set(["PATH", "PIP_INDEX_URL", "LANG", "LD_LIBRARY_PATH"]) # read in global passenv settings p = os.environ.get("TOX_TESTENV_PASSENV", None) if p is not None: passenv.update(x for x in p.split() if x) # 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("SYSTEMDRIVE") # needed for pip6 passenv.add("SYSTEMROOT") # needed for python's crypto module passenv.add("PATHEXT") # needed for discovering executables passenv.add("TEMP") passenv.add("TMP") else: passenv.add("TMPDIR") for spec in value: for name in os.environ: if fnmatchcase(name.upper(), spec.upper()): passenv.add(name) return passenv parser.add_testenv_attribute( name="passenv", type="line-list", postprocess=passenv, help="environment variables needed during executing test commands " "(taken from invocation environment). Note that tox always " "passes through some basic environment variables which are " "needed for basic functioning of the Python system. " "See --showconfig for the eventual passenv setting.") 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(testenv_config, value): return testenv_config.config.option.sitepackages or value 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(testenv_config, value): return testenv_config.config.option.pre or value 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(testenv_config, value): option = testenv_config.config.option return not option.installpkg and (value or option.develop) parser.add_testenv_attribute( name="usedevelop", type="bool", postprocess=develop, default=False, help="install package in develop/editable mode") 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.") parser.add_testenv_attribute( "ignore_outcome", type="bool", default=False, help="if set to True a failing result of this testenv will not make " "tox fail, only a warning will be produced") class Config(object): """ Global Tox config object. """ def __init__(self, pluginmanager, option, interpreters): #: dictionary containing envname to envconfig mappings self.envconfigs = {} self.invocationcwd = py.path.local() self.interpreters = interpreters self.pluginmanager = pluginmanager #: option namespace containing all parsed command line options self.option = option @property def homedir(self): homedir = get_homedir() if homedir is None: homedir = self.toxinidir # XXX good idea? return homedir class TestenvConfig: """ Testenv Configuration object. In addition to some core attributes/properties this config object holds all per-testenv ini attributes as attributes, see "tox --help-ini" for an overview. """ def __init__(self, envname, config, factors, reader): #: test environment name self.envname = envname #: global tox config object self.config = config #: set of factors self.factors = factors self._reader = reader def get_envbindir(self): """ path to directory where scripts/binaries reside. """ 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 envbindir(self): return self.get_envbindir() @property def envpython(self): """ path to python executable. """ return self.get_envpython() def get_envpython(self): """ path to python/jython executable. """ if "jython" in str(self.basepython): name = "jython" else: name = "python" return self.envbindir.join(name) def get_envsitepackagesdir(self): """ return sitepackagesdir of the virtualenv environment. (only available during execution, not parsing) """ x = self.config.interpreters.get_sitepackagesdir( info=self.python_info, envdir=self.envdir) return x @property def python_info(self): """ return sitepackagesdir of the virtualenv environment. """ 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): factors = set(name.split('-')) reader = SectionReader(section, self._cfg, fallbacksections=["testenv"], factors=factors) vc = TestenvConfig(config=config, envname=name, factors=factors, reader=reader) reader.addsubstitutions(**subs) reader.addsubstitutions(envname=name) reader.addsubstitutions(envbindir=vc.get_envbindir, envsitepackagesdir=vc.get_envsitepackagesdir, envpython=vc.get_envpython) for env_attr in config._testenv_attr: atype = env_attr.type if atype in ("bool", "path", "string", "dict", "dict_setenv", "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(testenv_config=vc, value=res) setattr(vc, env_attr.name, res) if atype == "path": reader.addsubstitutions(**{env_attr.name: res}) 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): if '\n' in env: env = ','.join(env.split('\n')) 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 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 = [] self._setenv = None def get_environ_value(self, name): if self._setenv is None: return os.environ.get(name) return self._setenv.get(name) 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 not None: 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"): value = self.getstring(name, None) return self._getdict(value, default=default, sep=sep) def getdict_setenv(self, name, default=None, sep="\n"): value = self.getstring(name, None, replace=True, crossonly=True) definitions = self._getdict(value, default=default, sep=sep) self._setenv = SetenvDict(definitions, reader=self) return self._setenv def _getdict(self, value, default, sep): if value is None: return default or {} d = {} for line in value.split(sep): if line.strip(): name, rest = line.split('=', 1) d[name.strip()] = rest.strip() return d 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, crossonly=False): 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'): x = self._replace(x, name=name, crossonly=crossonly) # 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(self, value, name=None, section_name=None, crossonly=False): if '{' not in value: return value section_name = section_name if section_name else self.section_name self._subststack.append((section_name, name)) try: return Replacer(self, crossonly=crossonly).do_replace(value) finally: assert self._subststack.pop() == (section_name, name) class Replacer: RE_ITEM_REF = re.compile( r''' (?[^[:{}]+):)? # optional sub_type for special rules (?P[^{}]*) # substitution key [}] ''', re.VERBOSE) def __init__(self, reader, crossonly=False): self.reader = reader self.crossonly = crossonly def do_replace(self, x): return self.RE_ITEM_REF.sub(self._replace_match, x) def _replace_match(self, match): g = match.groupdict() sub_value = g['substitution_value'] if self.crossonly: if sub_value.startswith("["): return self._substitute_from_other_section(sub_value) # in crossonly we return all other hits verbatim start, end = match.span() return match.string[start:end] # special case: opts and packages. Leave {opts} and # {packages} intact, they are replaced manually in # _venv.VirtualEnv.run_install_command. if sub_value in ('opts', 'packages'): return '{%s}' % sub_value try: sub_type = g['sub_type'] except KeyError: raise tox.exception.ConfigError( "Malformed substitution; no substitution type provided") if sub_type == "env": return self._replace_env(match) if sub_type is not None: raise tox.exception.ConfigError( "No support for the %s substitution type" % sub_type) return self._replace_substitution(match) 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 envvalue = self.reader.get_environ_value(envkey) if envvalue is None: if default is None: raise tox.exception.ConfigError( "substitution env:%r: unknown environment variable %r " " or recursive definition." % (envkey, envkey)) return default return envvalue def _substitute_from_other_section(self, key): if key.startswith("[") and "]" in key: i = key.find("]") section, item = key[1:i], key[i + 1:] cfg = self.reader._cfg if section in cfg and item in cfg[section]: if (section, item) in self.reader._subststack: raise ValueError('%s already in %s' % ( (section, item), self.reader._subststack)) x = str(cfg[section][item]) return self.reader._replace(x, name=item, section_name=section, crossonly=self.crossonly) raise tox.exception.ConfigError( "substitution key %r not found" % key) def _replace_substitution(self, match): sub_key = match.group('substitution_value') val = self.reader._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) class _ArgvlistReader: @classmethod def getargvlist(cls, reader, value): """Parse ``commands`` argvlist multiline string. :param str name: Key name in a section. :param str value: 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 value.splitlines(): line = line.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 = '' 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 tox-2.3.1/tox/interpreters.py0000664000175000017500000001311712633511761015641 0ustar hpkhpk00000000000000import sys import py import re import inspect from tox import hookimpl class Interpreters: def __init__(self, hook): self.name2executable = {} self.executable2info = {} self.hook = hook def get_executable(self, envconfig): """ return path object to the executable for the given name (e.g. python2.6, python2.7, python etc.) if name is already an existing path, return name. If an interpreter cannot be found, return None. """ try: return self.name2executable[envconfig.envname] except KeyError: exe = self.hook.tox_get_python_executable(envconfig=envconfig) self.name2executable[envconfig.envname] = exe return exe def get_info(self, envconfig): executable = self.get_executable(envconfig) name = envconfig.basepython if not executable: return NoInterpreterInfo(name=name) try: return self.executable2info[executable] except KeyError: info = run_and_get_interpreter_info(name, executable) self.executable2info[executable] = info return info def get_sitepackagesdir(self, info, envdir): if not info.executable: return "" envdir = str(envdir) try: res = exec_on_interpreter(info.executable, [inspect.getsource(sitepackagesdir), "print (sitepackagesdir(%r))" % envdir]) except ExecFailed: val = sys.exc_info()[1] print ("execution failed: %s -- %s" % (val.out, val.err)) return "" else: return res["dir"] def run_and_get_interpreter_info(name, executable): assert executable try: result = exec_on_interpreter(executable, [inspect.getsource(pyinfo), "print (pyinfo())"]) except ExecFailed: val = sys.exc_info()[1] return NoInterpreterInfo(name, executable=val.executable, out=val.out, err=val.err) else: return InterpreterInfo(name, executable, **result) def exec_on_interpreter(executable, source): if isinstance(source, list): source = "\n".join(source) from subprocess import Popen, PIPE args = [str(executable)] popen = Popen(args, stdin=PIPE, stdout=PIPE, stderr=PIPE) popen.stdin.write(source.encode("utf8")) out, err = popen.communicate() if popen.returncode: raise ExecFailed(executable, source, out, err) try: result = eval(out.strip()) except Exception: raise ExecFailed(executable, source, out, "could not decode %r" % out) return result class ExecFailed(Exception): def __init__(self, executable, source, out, err): self.executable = executable self.source = source self.out = out self.err = err class InterpreterInfo: runnable = True def __init__(self, name, executable, version_info, sysplatform): assert executable and version_info self.name = name self.executable = executable self.version_info = version_info self.sysplatform = sysplatform def __str__(self): return "" % ( self.executable, self.version_info) class NoInterpreterInfo: runnable = False def __init__(self, name, executable=None, out=None, err="not found"): self.name = name self.executable = executable self.version_info = None self.out = out self.err = err def __str__(self): if self.executable: return "" else: return "" % self.name if sys.platform != "win32": @hookimpl def tox_get_python_executable(envconfig): return py.path.local.sysfind(envconfig.basepython) else: @hookimpl def tox_get_python_executable(envconfig): name = envconfig.basepython p = py.path.local.sysfind(name) if p: return p actual = None # Is this a standard PythonX.Y name? m = re.match(r"python(\d)\.(\d)", name) if m: # The standard names are in predictable places. actual = r"c:\python%s%s\python.exe" % m.groups() if not actual: actual = win32map.get(name, None) if actual: actual = py.path.local(actual) if actual.check(): return actual # The standard executables can be found as a last resort via the # Python launcher py.exe if m: return locate_via_py(*m.groups()) # Exceptions to the usual windows mapping win32map = { 'python': sys.executable, 'jython': "c:\jython2.5.1\jython.bat", } def locate_via_py(v_maj, v_min): ver = "-%s.%s" % (v_maj, v_min) script = "import sys; print(sys.executable)" py_exe = py.path.local.sysfind('py') if py_exe: try: exe = py_exe.sysexec(ver, '-c', script).strip() except py.process.cmdexec.Error: exe = None if exe: exe = py.path.local(exe) if exe.check(): return exe def pyinfo(): import sys return dict(version_info=tuple(sys.version_info), sysplatform=sys.platform) def sitepackagesdir(envdir): from distutils.sysconfig import get_python_lib return dict(dir=get_python_lib(prefix=envdir)) tox-2.3.1/tox/__init__.py0000664000175000017500000000154112633511761014650 0ustar hpkhpk00000000000000# __version__ = '2.3.1' from .hookspecs import hookspec, hookimpl # noqa class exception: class Error(Exception): def __str__(self): return "%s: %s" % (self.__class__.__name__, self.args[0]) class ConfigError(Error): """ error in tox configuration. """ 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. """ from tox.session import main as cmdline # noqa tox-2.3.1/tox/venv.py0000664000175000017500000003543712633511761014102 0ustar hpkhpk00000000000000from __future__ import with_statement import os import sys import re import codecs import py import tox from .config import DepConfig, hookimpl class CreationConfig: def __init__(self, md5, python, version, sitepackages, usedevelop, deps): self.md5 = md5 self.python = python self.version = version self.sitepackages = sitepackages self.usedevelop = usedevelop self.deps = deps def writeconfig(self, path): lines = ["%s %s" % (self.md5, self.python)] lines.append("%s %d %d" % (self.version, self.sitepackages, self.usedevelop)) for dep in self.deps: lines.append("%s %s" % dep) path.ensure() path.write("\n".join(lines)) @classmethod def readconfig(cls, path): try: lines = path.readlines(cr=0) value = lines.pop(0).split(None, 1) md5, python = value version, sitepackages, usedevelop = lines.pop(0).split(None, 3) sitepackages = bool(int(sitepackages)) usedevelop = bool(int(usedevelop)) deps = [] for line in lines: md5, depstring = line.split(None, 1) deps.append((md5, depstring)) return CreationConfig(md5, python, version, sitepackages, usedevelop, deps) except Exception: return None def matches(self, other): return (other and self.md5 == other.md5 and self.python == other.python and self.version == other.version and self.sitepackages == other.sitepackages and self.usedevelop == other.usedevelop and self.deps == other.deps) class VirtualEnv(object): def __init__(self, envconfig=None, session=None): self.envconfig = envconfig self.session = session @property def hook(self): return self.envconfig.config.pluginmanager.hook @property def path(self): """ Path to environment base dir. """ return self.envconfig.envdir @property def path_config(self): return self.path.join(".tox-config1") @property def name(self): """ test environment name. """ return self.envconfig.envname def __repr__(self): return "" % (self.path) def getcommandpath(self, name, venv=True, cwd=None): """ return absolute path (str or localpath) for specified command name. If it's a localpath we will rewrite it as as a relative path. If venv is True we will check if the command is coming from the venv or is whitelisted to come from external. """ name = str(name) if os.path.isabs(name): return name if os.path.split(name)[0] == ".": p = cwd.join(name) if p.check(): return str(p) p = None if venv: p = py.path.local.sysfind(name, paths=[self.envconfig.envbindir]) if p is not None: return p p = py.path.local.sysfind(name) if p is None: raise tox.exception.InvocationError( "could not find executable %r" % (name,)) # p is not found in virtualenv script/bin dir if venv: if not self.is_allowed_external(p): self.session.report.warning( "test command found but not installed in testenv\n" " cmd: %s\n" " env: %s\n" "Maybe you forgot to specify a dependency? " "See also the whitelist_externals envconfig setting." % ( p, self.envconfig.envdir)) return str(p) # will not be rewritten for reporting def is_allowed_external(self, p): tryadd = [""] if sys.platform == "win32": tryadd += [ os.path.normcase(x) for x in os.environ['PATHEXT'].split(os.pathsep) ] p = py.path.local(os.path.normcase(str(p))) for x in self.envconfig.whitelist_externals: for add in tryadd: if p.fnmatch(x + add): return True return False def _ispython3(self): return "python3" in str(self.envconfig.basepython) def update(self, action): """ return status string for updating actual venv to match configuration. if status string is empty, all is ok. """ rconfig = CreationConfig.readconfig(self.path_config) if not self.envconfig.recreate and rconfig and \ rconfig.matches(self._getliveconfig()): action.info("reusing", self.envconfig.envdir) return if rconfig is None: action.setactivity("create", self.envconfig.envdir) else: action.setactivity("recreate", self.envconfig.envdir) try: self.hook.tox_testenv_create(action=action, venv=self) self.just_created = True except tox.exception.UnsupportedInterpreter: return sys.exc_info()[1] except tox.exception.InterpreterNotFound: return sys.exc_info()[1] try: self.hook.tox_testenv_install_deps(action=action, venv=self) except tox.exception.InvocationError: v = sys.exc_info()[1] return "could not install deps %s; v = %r" % ( self.envconfig.deps, v) def _getliveconfig(self): python = self.envconfig.python_info.executable md5 = getdigest(python) version = tox.__version__ sitepackages = self.envconfig.sitepackages develop = self.envconfig.usedevelop deps = [] for dep in self._getresolvedeps(): raw_dep = dep.name md5 = getdigest(raw_dep) deps.append((md5, raw_dep)) return CreationConfig(md5, python, version, sitepackages, develop, deps) def _getresolvedeps(self): l = [] for dep in self.envconfig.deps: if dep.indexserver is None: res = self.session._resolve_pkg(dep.name) if res != dep.name: dep = dep.__class__(res) l.append(dep) return l def getsupportedinterpreter(self): return self.envconfig.getsupportedinterpreter() def matching_platform(self): return re.match(self.envconfig.platform, sys.platform) def finish(self): self._getliveconfig().writeconfig(self.path_config) def _needs_reinstall(self, setupdir, action): setup_py = setupdir.join('setup.py') setup_cfg = setupdir.join('setup.cfg') args = [self.envconfig.envpython, str(setup_py), '--name'] output = action.popen(args, cwd=setupdir, redirect=False, returnout=True) name = output.strip() egg_info = setupdir.join('.'.join((name, 'egg-info'))) for conf_file in (setup_py, setup_cfg): if (not egg_info.check() or (conf_file.check() and conf_file.mtime() > egg_info.mtime())): return True return False def developpkg(self, setupdir, action): assert action is not None if getattr(self, 'just_created', False): action.setactivity("develop-inst", setupdir) self.finish() extraopts = [] else: if not self._needs_reinstall(setupdir, action): action.setactivity("develop-inst-noop", setupdir) return action.setactivity("develop-inst-nodeps", setupdir) extraopts = ['--no-deps'] self._install(['-e', setupdir], extraopts=extraopts, action=action) def installpkg(self, sdistpath, action): assert action is not None if getattr(self, 'just_created', False): action.setactivity("inst", sdistpath) self.finish() extraopts = [] else: action.setactivity("inst-nodeps", sdistpath) extraopts = ['-U', '--no-deps'] self._install([sdistpath], extraopts=extraopts, action=action) def _installopts(self, indexserver): l = [] if indexserver: l += ["-i", indexserver] if self.envconfig.downloadcache: self.envconfig.downloadcache.ensure(dir=1) l.append("--download-cache=%s" % self.envconfig.downloadcache) if self.envconfig.pip_pre: l.append("--pre") return l def run_install_command(self, packages, action, options=()): argv = self.envconfig.install_command[:] # use pip-script on win32 to avoid the executable locking i = argv.index('{packages}') argv[i:i + 1] = packages if '{opts}' in argv: i = argv.index('{opts}') argv[i:i + 1] = list(options) for x in ('PIP_RESPECT_VIRTUALENV', 'PIP_REQUIRE_VIRTUALENV', '__PYVENV_LAUNCHER__'): os.environ.pop(x, None) old_stdout = sys.stdout sys.stdout = codecs.getwriter('utf8')(sys.stdout) self._pcall(argv, cwd=self.envconfig.config.toxinidir, action=action) sys.stdout = old_stdout def _install(self, deps, extraopts=None, action=None): if not deps: return d = {} l = [] for dep in deps: if isinstance(dep, (str, py.path.local)): dep = DepConfig(str(dep), None) assert isinstance(dep, DepConfig), dep if dep.indexserver is None: ixserver = self.envconfig.config.indexserver['default'] else: ixserver = dep.indexserver d.setdefault(ixserver, []).append(dep.name) if ixserver not in l: l.append(ixserver) assert ixserver.url is None or isinstance(ixserver.url, str) for ixserver in l: packages = d[ixserver] options = self._installopts(ixserver.url) if extraopts: options.extend(extraopts) self.run_install_command(packages=packages, options=options, action=action) def _getenv(self, testcommand=False): if testcommand: # for executing tests we construct a clean environment env = {} for envname in self.envconfig.passenv: if envname in os.environ: env[envname] = os.environ[envname] else: # for executing non-test commands we use the full # invocation environment env = os.environ.copy() # in any case we honor per-testenv setenv configuration env.update(self.envconfig.setenv) env['VIRTUAL_ENV'] = str(self.path) return env def test(self, redirect=False): action = self.session.newaction(self, "runtests") with action: self.status = 0 self.session.make_emptydir(self.envconfig.envtmpdir) cwd = self.envconfig.changedir env = self._getenv(testcommand=True) # Display PYTHONHASHSEED to assist with reproducibility. action.setactivity("runtests", "PYTHONHASHSEED=%r" % env.get('PYTHONHASHSEED')) for i, argv in enumerate(self.envconfig.commands): # have to make strings as _pcall changes argv[0] to a local() # happens if the same environment is invoked twice message = "commands[%s] | %s" % (i, ' '.join( [str(x) for x in argv])) action.setactivity("runtests", message) # check to see if we need to ignore the return code # if so, we need to alter the command line arguments if argv[0].startswith("-"): ignore_ret = True if argv[0] == "-": del argv[0] else: argv[0] = argv[0].lstrip("-") else: ignore_ret = False try: self._pcall(argv, cwd=cwd, action=action, redirect=redirect, ignore_ret=ignore_ret, testcommand=True) except tox.exception.InvocationError as err: if self.envconfig.ignore_outcome: self.session.report.warning( "command failed but result from testenv is ignored\n" " cmd: %s" % (str(err),)) self.status = "ignored failed command" continue # keep processing commands self.session.report.error(str(err)) self.status = "commands failed" if not self.envconfig.ignore_errors: break # Don't process remaining commands except KeyboardInterrupt: self.status = "keyboardinterrupt" self.session.report.error(self.status) raise def _pcall(self, args, cwd, venv=True, testcommand=False, action=None, redirect=True, ignore_ret=False): for name in ("VIRTUALENV_PYTHON", "PYTHONDONTWRITEBYTECODE"): os.environ.pop(name, None) cwd.ensure(dir=1) args[0] = self.getcommandpath(args[0], venv, cwd) env = self._getenv(testcommand=testcommand) bindir = str(self.envconfig.envbindir) env['PATH'] = p = os.pathsep.join([bindir, os.environ["PATH"]]) self.session.report.verbosity2("setting PATH=%s" % p) return action.popen(args, cwd=cwd, env=env, redirect=redirect, ignore_ret=ignore_ret) def getdigest(path): path = py.path.local(path) if not path.check(file=1): return "0" * 32 return path.computehash() @hookimpl def tox_testenv_create(venv, action): # if self.getcommandpath("activate").dirpath().check(): # return config_interpreter = venv.getsupportedinterpreter() args = [sys.executable, '-m', 'virtualenv'] if venv.envconfig.sitepackages: args.append('--system-site-packages') # add interpreter explicitly, to prevent using # default (virtualenv.ini) args.extend(['--python', str(config_interpreter)]) # if sys.platform == "win32": # f, path, _ = py.std.imp.find_module("virtualenv") # f.close() # args[:1] = [str(config_interpreter), str(path)] # else: venv.session.make_emptydir(venv.path) basepath = venv.path.dirpath() basepath.ensure(dir=1) args.append(venv.path.basename) venv._pcall(args, venv=False, action=action, cwd=basepath) @hookimpl def tox_testenv_install_deps(venv, action): deps = venv._getresolvedeps() if deps: depinfo = ", ".join(map(str, deps)) action.setactivity("installdeps", "%s" % depinfo) venv._install(deps, action=action) tox-2.3.1/tox/__main__.py0000664000175000017500000000010412633511761014623 0ustar hpkhpk00000000000000from tox.session import main if __name__ == "__main__": main() tox-2.3.1/tox/session.py0000664000175000017500000006020612633511761014577 0ustar hpkhpk00000000000000""" 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 prepare(args): config = parseconfig(args) if config.option.help: show_help(config) raise SystemExit(0) elif config.option.helpini: show_help_ini(config) raise SystemExit(0) return config def main(args=None): try: config = prepare(args) 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() tw.line("Environment variables", bold=True) tw.line("TOXENV: comma separated list of environments " "(overridable by '-e')") tw.line("TOX_TESTENV_PASSENV: space-separated list of extra " "environment variables to be passed into test command " "environments") 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.envconfig.envpython)] + 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: """ (unstable API). the session object that ties together configuration, reporting, venv creation, testing. """ 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): if not venv.matching_platform(): venv.status = "platform mismatch" return # we simply omit non-matching platforms 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 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") output = venv._pcall([str(pip), "freeze"], cwd=self.config.toxinidir, action=action) # the output contains a mime-header, skip it output = output.split("\n\n")[-1] 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.skip(msg) elif status and status == "ignored failed command": msg = " %s: %s" % (venv.envconfig.envname, str(status)) self.report.good(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) for attr in self.config._parser._testenv_attr: self.report.line(" %-15s = %s" % (attr.name, getattr(envconfig, attr.name))) 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 tox-2.3.1/tox/_verlib.py0000664000175000017500000002735512633511761014546 0ustar hpkhpk00000000000000""" PEP386-version comparison algorithm. (c) Tarek Ziade and others extracted unmodified from https://bitbucket.org/tarek/distutilsversion licensed under the PSF license (i guess) """ import re class IrrationalVersionError(Exception): """This is an irrational version.""" pass class HugeMajorVersionNumError(IrrationalVersionError): """An irrational version because the major version number is huge (often because a year or date was used). See `error_on_huge_major_num` option in `NormalizedVersion` for details. This guard can be disabled by setting that option False. """ pass # A marker used in the second and third parts of the `parts` tuple, for # versions that don't have those segments, to sort properly. An example # of versions in sort order ('highest' last): # 1.0b1 ((1,0), ('b',1), ('f',)) # 1.0.dev345 ((1,0), ('f',), ('dev', 345)) # 1.0 ((1,0), ('f',), ('f',)) # 1.0.post256.dev345 ((1,0), ('f',), ('f', 'post', 256, 'dev', 345)) # 1.0.post345 ((1,0), ('f',), ('f', 'post', 345, 'f')) # ^ ^ ^ # 'b' < 'f' ---------------------/ | | # | | # 'dev' < 'f' < 'post' -------------------/ | # | # 'dev' < 'f' ----------------------------------------------/ # Other letters would do, but 'f' for 'final' is kind of nice. FINAL_MARKER = ('f',) VERSION_RE = re.compile(r''' ^ (?P\d+\.\d+) # minimum 'N.N' (?P(?:\.\d+)*) # any number of extra '.N' segments (?: (?P[abc]|rc) # 'a'=alpha, 'b'=beta, 'c'=release candidate # 'rc'= alias for release candidate (?P\d+(?:\.\d+)*) )? (?P(\.post(?P\d+))?(\.dev(?P\d+))?)? $''', re.VERBOSE) class NormalizedVersion(object): """A rational version. Good: 1.2 # equivalent to "1.2.0" 1.2.0 1.2a1 1.2.3a2 1.2.3b1 1.2.3c1 1.2.3.4 TODO: fill this out Bad: 1 # mininum two numbers 1.2a # release level must have a release serial 1.2.3b """ def __init__(self, s, error_on_huge_major_num=True): """Create a NormalizedVersion instance from a version string. @param s {str} The version string. @param error_on_huge_major_num {bool} Whether to consider an apparent use of a year or full date as the major version number an error. Default True. One of the observed patterns on PyPI before the introduction of `NormalizedVersion` was version numbers like this: 2009.01.03 20040603 2005.01 This guard is here to strongly encourage the package author to use an alternate version, because a release deployed into PyPI and, e.g. downstream Linux package managers, will forever remove the possibility of using a version number like "1.0" (i.e. where the major number is less than that huge major number). """ self._parse(s, error_on_huge_major_num) @classmethod def from_parts(cls, version, prerelease=FINAL_MARKER, devpost=FINAL_MARKER): return cls(cls.parts_to_str((version, prerelease, devpost))) def _parse(self, s, error_on_huge_major_num=True): """Parses a string version into parts.""" match = VERSION_RE.search(s) if not match: raise IrrationalVersionError(s) groups = match.groupdict() parts = [] # main version block = self._parse_numdots(groups['version'], s, False, 2) extraversion = groups.get('extraversion') if extraversion not in ('', None): block += self._parse_numdots(extraversion[1:], s) parts.append(tuple(block)) # prerelease prerel = groups.get('prerel') if prerel is not None: block = [prerel] block += self._parse_numdots(groups.get('prerelversion'), s, pad_zeros_length=1) parts.append(tuple(block)) else: parts.append(FINAL_MARKER) # postdev if groups.get('postdev'): post = groups.get('post') dev = groups.get('dev') postdev = [] if post is not None: postdev.extend([FINAL_MARKER[0], 'post', int(post)]) if dev is None: postdev.append(FINAL_MARKER[0]) if dev is not None: postdev.extend(['dev', int(dev)]) parts.append(tuple(postdev)) else: parts.append(FINAL_MARKER) self.parts = tuple(parts) if error_on_huge_major_num and self.parts[0][0] > 1980: raise HugeMajorVersionNumError( "huge major version number, %r, " "which might cause future problems: %r" % (self.parts[0][0], s)) def _parse_numdots(self, s, full_ver_str, drop_trailing_zeros=True, pad_zeros_length=0): """Parse 'N.N.N' sequences, return a list of ints. @param s {str} 'N.N.N..." sequence to be parsed @param full_ver_str {str} The full version string from which this comes. Used for error strings. @param drop_trailing_zeros {bool} Whether to drop trailing zeros from the returned list. Default True. @param pad_zeros_length {int} The length to which to pad the returned list with zeros, if necessary. Default 0. """ nums = [] for n in s.split("."): if len(n) > 1 and n[0] == '0': raise IrrationalVersionError( "cannot have leading zero in " "version number segment: '%s' in %r" % (n, full_ver_str)) nums.append(int(n)) if drop_trailing_zeros: while nums and nums[-1] == 0: nums.pop() while len(nums) < pad_zeros_length: nums.append(0) return nums def __str__(self): return self.parts_to_str(self.parts) @classmethod def parts_to_str(cls, parts): """Transforms a version expressed in tuple into its string representation.""" # XXX This doesn't check for invalid tuples main, prerel, postdev = parts s = '.'.join(str(v) for v in main) if prerel is not FINAL_MARKER: s += prerel[0] s += '.'.join(str(v) for v in prerel[1:]) if postdev and postdev is not FINAL_MARKER: if postdev[0] == 'f': postdev = postdev[1:] i = 0 while i < len(postdev): if i % 2 == 0: s += '.' s += str(postdev[i]) i += 1 return s def __repr__(self): return "%s('%s')" % (self.__class__.__name__, self) def _cannot_compare(self, other): raise TypeError("cannot compare %s and %s" % (type(self).__name__, type(other).__name__)) def __eq__(self, other): if not isinstance(other, NormalizedVersion): self._cannot_compare(other) return self.parts == other.parts def __lt__(self, other): if not isinstance(other, NormalizedVersion): self._cannot_compare(other) return self.parts < other.parts def __ne__(self, other): return not self.__eq__(other) def __gt__(self, other): return not (self.__lt__(other) or self.__eq__(other)) def __le__(self, other): return self.__eq__(other) or self.__lt__(other) def __ge__(self, other): return self.__eq__(other) or self.__gt__(other) def suggest_normalized_version(s): """Suggest a normalized version close to the given version string. If you have a version string that isn't rational (i.e. NormalizedVersion doesn't like it) then you might be able to get an equivalent (or close) rational version from this function. This does a number of simple normalizations to the given string, based on observation of versions currently in use on PyPI. Given a dump of those version during PyCon 2009, 4287 of them: - 2312 (53.93%) match NormalizedVersion without change - with the automatic suggestion - 3474 (81.04%) match when using this suggestion method @param s {str} An irrational version string. @returns A rational version string, or None, if couldn't determine one. """ try: NormalizedVersion(s) return s # already rational except IrrationalVersionError: pass rs = s.lower() # part of this could use maketrans for orig, repl in (('-alpha', 'a'), ('-beta', 'b'), ('alpha', 'a'), ('beta', 'b'), ('rc', 'c'), ('-final', ''), ('-pre', 'c'), ('-release', ''), ('.release', ''), ('-stable', ''), ('+', '.'), ('_', '.'), (' ', ''), ('.final', ''), ('final', '')): rs = rs.replace(orig, repl) # if something ends with dev or pre, we add a 0 rs = re.sub(r"pre$", r"pre0", rs) rs = re.sub(r"dev$", r"dev0", rs) # if we have something like "b-2" or "a.2" at the end of the # version, that is pobably beta, alpha, etc # let's remove the dash or dot rs = re.sub(r"([abc|rc])[\-\.](\d+)$", r"\1\2", rs) # 1.0-dev-r371 -> 1.0.dev371 # 0.1-dev-r79 -> 0.1.dev79 rs = re.sub(r"[\-\.](dev)[\-\.]?r?(\d+)$", r".\1\2", rs) # Clean: 2.0.a.3, 2.0.b1, 0.9.0~c1 rs = re.sub(r"[.~]?([abc])\.?", r"\1", rs) # Clean: v0.3, v1.0 if rs.startswith('v'): rs = rs[1:] # Clean leading '0's on numbers. # TODO: unintended side-effect on, e.g., "2003.05.09" # PyPI stats: 77 (~2%) better rs = re.sub(r"\b0+(\d+)(?!\d)", r"\1", rs) # Clean a/b/c with no version. E.g. "1.0a" -> "1.0a0". Setuptools infers # zero. # PyPI stats: 245 (7.56%) better rs = re.sub(r"(\d+[abc])$", r"\g<1>0", rs) # the 'dev-rNNN' tag is a dev tag rs = re.sub(r"\.?(dev-r|dev\.r)\.?(\d+)$", r".dev\2", rs) # clean the - when used as a pre delimiter rs = re.sub(r"-(a|b|c)(\d+)$", r"\1\2", rs) # a terminal "dev" or "devel" can be changed into ".dev0" rs = re.sub(r"[\.\-](dev|devel)$", r".dev0", rs) # a terminal "dev" can be changed into ".dev0" rs = re.sub(r"(?![\.\-])dev$", r".dev0", rs) # a terminal "final" or "stable" can be removed rs = re.sub(r"(final|stable)$", "", rs) # The 'r' and the '-' tags are post release tags # 0.4a1.r10 -> 0.4a1.post10 # 0.9.33-17222 -> 0.9.3.post17222 # 0.9.33-r17222 -> 0.9.3.post17222 rs = re.sub(r"\.?(r|-|-r)\.?(\d+)$", r".post\2", rs) # Clean 'r' instead of 'dev' usage: # 0.9.33+r17222 -> 0.9.3.dev17222 # 1.0dev123 -> 1.0.dev123 # 1.0.git123 -> 1.0.dev123 # 1.0.bzr123 -> 1.0.dev123 # 0.1a0dev.123 -> 0.1a0.dev123 # PyPI stats: ~150 (~4%) better rs = re.sub(r"\.?(dev|git|bzr)\.?(\d+)$", r".dev\2", rs) # Clean '.pre' (normalized from '-pre' above) instead of 'c' usage: # 0.2.pre1 -> 0.2c1 # 0.2-c1 -> 0.2c1 # 1.0preview123 -> 1.0c123 # PyPI stats: ~21 (0.62%) better rs = re.sub(r"\.?(pre|preview|-c)(\d+)$", r"c\g<2>", rs) # Tcl/Tk uses "px" for their post release markers rs = re.sub(r"p(\d+)$", r".post\1", rs) try: NormalizedVersion(rs) return rs # already rational except IrrationalVersionError: pass return None tox-2.3.1/tox.ini0000664000175000017500000000211412633511761013235 0ustar hpkhpk00000000000000[tox] envlist=py27,py26,py34,py33,py35,pypy,flakes,py26-bare [testenv:X] commands=echo {posargs} [testenv] commands= py.test --timeout=180 {posargs:tests} deps=pytest>=2.3.5 pytest-timeout [testenv:py26-bare] deps = commands = tox -h [testenv:docs] basepython=python changedir=doc deps=sphinx {[testenv]deps} commands= py.test -v check_sphinx.py {posargs} [testenv:flakes] platform=linux deps = pytest-flakes>=0.2 pytest-pep8 commands = py.test --flakes -m flakes tox tests py.test --pep8 -m pep8 tox tests [testenv:dev] # required to make looponfail reload on every source code change usedevelop = True deps = pytest-xdist>=1.11 commands = {posargs:py.test -s -x -f -v} [pytest] rsyncdirs = tests tox addopts = -rsxX # pytest-xdist plugin configuration looponfailroots = tox tests norecursedirs = .hg .tox # pytest-pep8 plugin configuration pep8maxlinelength = 99 # W503 - line break before binary operator # E402 - module level import not at top of file # E731 - do not assign a lambda expression, use a def pep8ignore = *.py W503 E402 E731 tox-2.3.1/MANIFEST.in0000664000175000017500000000022512633511761013461 0ustar hpkhpk00000000000000include CHANGELOG include README.rst include CONTRIBUTORS include ISSUES.txt include LICENSE include setup.py include tox.ini graft doc graft tests tox-2.3.1/doc/0000775000175000017500000000000012633511761012471 5ustar hpkhpk00000000000000tox-2.3.1/doc/examples.txt0000664000175000017500000000045412633511761015053 0ustar hpkhpk00000000000000 tox configuration and usage examples ============================================================================== .. toctree:: :maxdepth: 2 example/basic.txt example/pytest.txt example/unittest example/nose.txt example/general.txt example/jenkins.txt example/devenv.txt tox-2.3.1/doc/_templates/0000775000175000017500000000000012633511761014626 5ustar hpkhpk00000000000000tox-2.3.1/doc/_templates/localtoc.html0000664000175000017500000000220612633511761017314 0ustar hpkhpk00000000000000 {%- if pagename != "search" %} {%- endif %}

quicklinks

{% extends "basic/localtoc.html" %} tox-2.3.1/doc/_templates/layout.html0000664000175000017500000000074612633511761017040 0ustar hpkhpk00000000000000{% extends "!layout.html" %} {% block footer %} {{ super() }} {% endblock %} tox-2.3.1/doc/_templates/indexsidebar.html0000664000175000017500000000124312633511761020155 0ustar hpkhpk00000000000000

Download

{% if version.endswith('(hg)') %}

This documentation is for version {{ version }}, which is not released yet.

You can use it from the Mercurial repo or look for released versions in the Python Package Index.

{% else %}

Current: {{ version }} [Changes]

tox on PyPI

pip install tox
{% endif %}

Questions? Suggestions?

Checkout support channels

tox-2.3.1/doc/example/0000775000175000017500000000000012633511761014124 5ustar hpkhpk00000000000000tox-2.3.1/doc/example/unittest.txt0000664000175000017500000000446612633511761016556 0ustar hpkhpk00000000000000 unittest2, discover and tox =============================== Running unittests with 'discover' ------------------------------------------ .. _Pygments: http://pypi.python.org/pypi/Pygments The discover_ project allows to discover and run unittests and we can easily integrate it in a ``tox`` run. As an example, perform a checkout of Pygments_:: hg clone https://bitbucket.org/birkenfeld/pygments-main and add the following ``tox.ini`` to it:: [tox] envlist = py25,py26,py27 [testenv] changedir=tests commands=discover deps=discover If you now invoke ``tox`` you will see the creation of three virtual environments and a unittest-run performed in each of them. Running unittest2 and sphinx tests in one go ----------------------------------------------------- .. _`Michael Foord`: http://www.voidspace.org.uk/ .. _tox.ini: http://code.google.com/p/mock/source/browse/tox.ini `Michael Foord`_ has contributed a ``tox.ini`` file that allows you to run all tests for his mock_ project, including some sphinx-based doctests. If you checkout its repository with: hg clone https://code.google.com/p/mock/ the checkout has a tox.ini_ that looks like this:: [tox] envlist = py24,py25,py26,py27 [testenv] deps=unittest2 commands=unit2 discover [] [testenv:py26] commands= unit2 discover [] sphinx-build -b doctest docs html sphinx-build docs html deps = unittest2 sphinx [testenv:py27] commands= unit2 discover [] sphinx-build -b doctest docs html sphinx-build docs html deps = unittest2 sphinx mock uses unittest2_ to run the tests. Invoking ``tox`` starts test discovery by executing the ``unit2 discover`` commands on Python 2.4, 2.5, 2.6 and 2.7 respectively. Against Python2.6 and Python2.7 it will additionally run sphinx-mediated doctests. If building the docs fails, due to a reST error, or any of the doctests fails, it will be reported by the tox run. The ``[]`` parentheses in the commands provide :ref:`positional substitution` which means you can e.g. type:: tox -- -f -s SOMEPATH which will ultimately invoke:: unit2 discover -f -s SOMEPATH in each of the environments. This allows you to customize test discovery in your ``tox`` runs. .. include:: ../links.txt tox-2.3.1/doc/example/devenv.txt0000664000175000017500000000460012633511761016154 0ustar hpkhpk00000000000000======================= Development environment ======================= Tox can be used for just preparing different virtual environments required by a project. This feature can be used by deployment tools when preparing deployed project environments. It can also be used for setting up normalized project development environments and thus help reduce the risk of different team members using mismatched development environments. Here are some examples illustrating how to set up a project's development environment using tox. For illustration purposes, let us call the development environment ``devenv``. Example 1: Basic scenario ========================= Step 1 - Configure the development environment ---------------------------------------------- First, we prepare the tox configuration for our development environment by defining a ``[testenv:devenv]`` section in the project's ``tox.ini`` configuration file:: [testenv:devenv] envdir = devenv basepython = python2.7 usedevelop = True In it we state: - what directory to locate the environment in, - what Python executable to use in the environment, - that our project should be installed into the environment using ``setup.py develop``, as opposed to building and installing its source distribution using ``setup.py install``. Actually, we can configure a lot more, and these are only the required settings. For example, we can add the following to our configuration, telling tox not to reuse ``commands`` or ``deps`` settings from the base ``[testenv]`` configuration:: commands = deps = Step 2 - Create the development environment ------------------------------------------- Once the ``[testenv:devenv]`` configuration section has been defined, we create the actual development environment by running the following:: tox -e devenv This creates the environment at the path specified by the environment's ``envdir`` configuration value. Example 2: A more complex scenario ================================== Let us say we want our project development environment to: - be located in the ``devenv`` directory, - use Python executable ``python2.7``, - pull packages from ``requirements.txt``, located in the same directory as ``tox.ini``. Here is an example configuration for the described scenario:: [testenv:devenv] envdir = devenv basepython = python2.7 usedevelop = True deps = -rrequirements.txt tox-2.3.1/doc/example/jenkins.txt0000664000175000017500000001375112633511761016335 0ustar hpkhpk00000000000000 Using Tox with the Jenkins Integration Server ================================================= Using Jenkins multi-configuration jobs ------------------------------------------- The Jenkins_ continuous integration server allows to define "jobs" with "build steps" which can be test invocations. If you :doc:`install <../install>` ``tox`` on your default Python installation on each Jenkins slave, you can easily create a Jenkins multi-configuration job that will drive your tox runs from the CI-server side, using these steps: * install the Python plugin for Jenkins under "manage jenkins" * create a "multi-configuration" job, give it a name of your choice * configure your repository so that Jenkins can pull it * (optional) configure multiple nodes so that tox-runs are performed on multiple hosts * configure ``axes`` by using :ref:`TOXENV ` as an axis name and as values provide space-separated test environment names you want Jenkins/tox to execute. * add a **Python-build step** with this content (see also next example):: import tox tox.cmdline() # environment is selected by ``TOXENV`` env variable * check ``Publish JUnit test result report`` and enter ``**/junit-*.xml`` as the pattern so that Jenkins collects test results in the JUnit XML format. The last point requires that your test command creates JunitXML files, for example with ``py.test`` it is done like this: .. code-block:: ini commands = py.test --junitxml=junit-{envname}.xml **zero-installation** for slaves ------------------------------------------------------------- .. note:: This feature is broken currently because "toxbootstrap.py" has been removed. Please file an issue if you'd like to see it back. If you manage many Jenkins slaves and want to use the latest officially released tox (or latest development version) and want to skip manually installing ``tox`` then substitute the above **Python build step** code with this:: import urllib, os url = "https://bitbucket.org/hpk42/tox/raw/default/toxbootstrap.py" #os.environ['USETOXDEV']="1" # use tox dev version d = dict(__file__='toxbootstrap.py') exec urllib.urlopen(url).read() in d d['cmdline'](['--recreate']) The downloaded `toxbootstrap.py` file downloads all neccessary files to install ``tox`` in a virtual sub environment. Notes: * uncomment the line containing ``USETOXDEV`` to use the latest development-release version of tox instead of the latest released version. * adapt the options in the last line as needed (the example code will cause tox to reinstall all virtual environments all the time which is often what one wants in CI server contexts) Integrating "sphinx" documentation checks in a Jenkins job ---------------------------------------------------------------- If you are using a multi-configuration Jenkins job which collects JUnit Test results you will run into problems using the previous method of running the sphinx-build command because it will not generate JUnit results. To accomodate this issue one solution is to have ``py.test`` wrap the sphinx-checks and create a JUnit result file which wraps the result of calling sphinx-build. Here is an example: 1. create a ``docs`` environment in your ``tox.ini`` file like this:: [testenv:docs] basepython=python changedir=doc # or whereever you keep your sphinx-docs deps=sphinx py commands= py.test --tb=line -v --junitxml=junit-{envname}.xml check_sphinx.py 2. create a ``doc/check_sphinx.py`` file like this:: import py import subprocess def test_linkcheck(tmpdir): doctrees = tmpdir.join("doctrees") htmldir = tmpdir.join("html") subprocess.check_call( ["sphinx-build", "-W", "-blinkcheck", "-d", str(doctrees), ".", str(htmldir)]) def test_build_docs(tmpdir): doctrees = tmpdir.join("doctrees") htmldir = tmpdir.join("html") subprocess.check_call([ "sphinx-build", "-W", "-bhtml", "-d", str(doctrees), ".", str(htmldir)]) 3. run ``tox -e docs`` and then you may integrate this environment along with your other environments into Jenkins. Note that ``py.test`` is only installed into the docs environment and does not need to be in use or installed with any other environment. .. _`jenkins artifact example`: Access package artifacts between Jenkins jobs -------------------------------------------------------- .. _`Jenkins Copy Artifact plugin`: http://wiki.jenkins-ci.org/display/HUDSON/Copy+Artifact+Plugin In an extension to :ref:`artifacts` you can also configure Jenkins jobs to access each others artifacts. ``tox`` uses the ``distshare`` directory to access artifacts and in a Jenkins context (detected via existence of the environment variable ``HUDSON_URL``); it defaults to to ``{toxworkdir}/distshare``. This means that each workspace will have its own ``distshare`` directory and we need to configure Jenkins to perform artifact copying. The recommend way to do this is to install the `Jenkins Copy Artifact plugin`_ and for each job which "receives" artifacts you add a **Copy artifacts from another project** build step using roughly this configuration:: Project-name: name of the other (tox-managed) job you want the artifact from Artifacts to copy: .tox/dist/*.zip # where tox jobs create artifacts Target directory: .tox/distshare # where we want it to appear for us Flatten Directories: CHECK # create no subdir-structure You also need to configure the "other" job to archive artifacts; This is done by checking ``Archive the artifacts`` and entering:: Files to archive: .tox/dist/*.zip So our "other" job will create an sdist-package artifact and the "copy-artifacts" plugin will copy it to our ``distshare`` area. Now everything proceeds as :ref:`artifacts` shows it. So if you are using defaults you can re-use and debug exactly the same ``tox.ini`` file and make use of automatic sharing of your artifacts between runs or Jenkins jobs. .. include:: ../links.txt tox-2.3.1/doc/example/result.txt0000664000175000017500000000235512633511761016210 0ustar hpkhpk00000000000000 Writing a json result file -------------------------------------------------------- .. versionadded: 1.6 You can instruct tox to write a json-report file via:: tox --result-json=PATH This will create a json-formatted result file using this schema:: { "testenvs": { "py27": { "python": { "executable": "/home/hpk/p/tox/.tox/py27/bin/python", "version": "2.7.3 (default, Aug 1 2012, 05:14:39) \n[GCC 4.6.3]", "version_info": [ 2, 7, 3, "final", 0 ] }, "test": [ { "output": "...", "command": [ "/home/hpk/p/tox/.tox/py27/bin/py.test", "--instafail", "--junitxml=/home/hpk/p/tox/.tox/py27/log/junit-py27.xml", "tests/test_config.py" ], "retcode": "0" } ], "setup": [] } }, "platform": "linux2", "installpkg": { "basename": "tox-1.6.0.dev1.zip", "sha256": "b6982dde5789a167c4c35af0d34ef72176d0575955f5331ad04aee9f23af4326", "md5": "27ead99fd7fa39ee7614cede6bf175a6" }, "toxversion": "1.6.0.dev1", "reportversion": "1" } tox-2.3.1/doc/example/general.txt0000664000175000017500000001334512633511761016310 0ustar hpkhpk00000000000000.. be in -*- rst -*- mode! General tips and tricks ================================ Interactively passing positional arguments ----------------------------------------------- If you invoke ``tox`` like this:: tox -- -x tests/test_something.py the arguments after the ``--`` will be substituted everywhere where you specify ``{posargs}`` in your test commands, for example using ``py.test``:: # in the testenv or testenv:NAME section of your tox.ini commands = py.test {posargs} or using ``nosetests``:: commands = nosetests {posargs} the above ``tox`` invocation will trigger the test runners to stop after the first failure and to only run a particular test file. You can specify defaults for the positional arguments using this syntax:: commands = nosetests {posargs:--with-coverage} .. _`sphinx checks`: Integrating "sphinx" documentation checks ---------------------------------------------- In a ``testenv`` environment you can specify any command and thus you can easily integrate sphinx_ documentation integrity during a tox test run. Here is an example ``tox.ini`` configuration:: [testenv:docs] basepython=python changedir=doc deps=sphinx commands= sphinx-build -W -b html -d {envtmpdir}/doctrees . {envtmpdir}/html This will create a dedicated ``docs`` virtual environment and install the ``sphinx`` dependency which itself will install the ``sphinx-build`` tool which you can then use as a test command. Note that sphinx output is redirected to the virtualenv environment temporary directory to prevent sphinx from caching results between runs. You can now call:: tox which will make the sphinx tests part of your test run. .. _`TOXENV`: Selecting one or more environments to run tests against -------------------------------------------------------- Using the ``-e ENV[,ENV2,...]`` option you explicitely list the environments where you want to run tests against. For example, given the previous sphinx example you may call:: tox -e docs which will make ``tox`` only manage the ``docs`` environment and call its test commands. You may specify more than one environment like this:: tox -e py25,py26 which would run the commands of the ``py25`` and ``py26`` testenvironments respectively. The special value ``ALL`` selects all environments. You can also specify an environment list in your ``tox.ini``:: [tox] envlist = py25,py26 or override it from the command line or from the environment variable ``TOXENV``:: export TOXENV=py25,py26 # in bash style shells .. _artifacts: Access package artifacts between multiple tox-runs -------------------------------------------------------- If you have multiple projects using tox you can make use of a ``distshare`` directory where ``tox`` will copy in sdist-packages so that another tox run can find the "latest" dependency. This feature allows to test a package against an unreleased development version or even an uncommitted version on your own machine. By default, ``{homedir}/.tox/distshare`` will be used for copying in and copying out artifacts (i.e. Python packages). For project ``two`` to depend on the ``one`` package you use the following entry:: # example two/tox.ini [testenv] deps= {distshare}/one-*.zip # install latest package from "one" project That's all. Tox running on project ``one`` will copy the sdist-package into the ``distshare`` directory after which a ``tox`` run on project ``two`` will grab it because ``deps`` contain an entry with the ``one-*.zip`` pattern. If there is more than one matching package the highest version will be taken. ``tox`` uses verlib_ to compare version strings which must be compliant with :pep:`386`. If you want to use this with Jenkins_, also checkout the :ref:`jenkins artifact example`. .. _verlib: https://bitbucket.org/tarek/distutilsversion/ basepython defaults, overriding ++++++++++++++++++++++++++++++++++++++++++ By default, for any ``pyXY`` test environment name the underlying "pythonX.Y" executable will be searched in your system ``PATH``. It must exist in order to successfully create virtualenv environments. On Windows a ``pythonX.Y`` named executable will be searched in typical default locations using the ``C:\PythonX.Y\python.exe`` pattern. For ``jython`` and ``pypy`` the respective ``jython`` and ``pypy-c`` names will be looked for. You can override any of the default settings by defining the ``basepython`` variable in a specific test environment section, for example:: [testenv:py27] basepython=/my/path/to/python2.7 Avoiding expensive sdist ------------------------ Some projects are large enough that running an sdist, followed by an install every time can be prohibitively costly. To solve this, there are two different options you can add to the ``tox`` section. First, you can simply ask tox to please not make an sdist:: [tox] skipsdist=True If you do this, your local software package will not be installed into the virtualenv. You should probably be okay with that, or take steps to deal with it in your commands section:: [testenv] commands = python setup.py develop py.test Running ``setup.py develop`` is a common enough model that it has its own option:: [testenv] usedevelop=True And a corresponding command line option ``--develop``, which will set ``skipsdist`` to True and then perform the ``setup.py develop`` step at the place where ``tox`` normally perfoms the installation of the sdist. Specifically, it actually runs ``pip install -e .`` behind the scenes, which itself calls ``setup.py develop``. There is an optimization coded in to not bother re-running the command if ``$projectname.egg-info`` is newer than ``setup.py`` or ``setup.cfg``. .. include:: ../links.txt tox-2.3.1/doc/example/nose.txt0000664000175000017500000000220012633511761015623 0ustar hpkhpk00000000000000 nose and tox ================================= It is easy to integrate `nosetests`_ runs with tox. For starters here is a simple ``tox.ini`` config to configure your project for running with nose: Basic nosetests example -------------------------- Assuming the following layout:: tox.ini # see below for content setup.py # a classic distutils/setuptools setup.py file and the following ``tox.ini`` content:: [testenv] deps=nose commands= nosetests \ [] # substitute with tox' positional arguments you can invoke ``tox`` in the directory where your ``tox.ini`` resides. ``tox`` will sdist-package your project create two virtualenv environments with the ``python2.6`` and ``python2.5`` interpreters, respectively, and will then run the specified test command. More examples? ------------------------------------------ You can use and combine other features of ``tox`` with your tox runs, e.g. :ref:`sphinx checks`. If you figure out some particular configurations for nose/tox interactions please submit them. Also you might want to checkout :doc:`general`. .. include:: ../links.txt tox-2.3.1/doc/example/basic.txt0000664000175000017500000002245012633511761015751 0ustar hpkhpk00000000000000Basic usage ============================================= a simple tox.ini / default environments ----------------------------------------------- Put basic information about your project and the test environments you want your project to run in into a ``tox.ini`` file that should reside next to your ``setup.py`` file:: # content of: tox.ini , put in same dir as setup.py [tox] envlist = py26,py27 [testenv] deps=pytest # or 'nose' or ... commands=py.test # or 'nosetests' or ... To sdist-package, install and test your project, you can now type at the command prompt:: tox This will sdist-package your current project, create two virtualenv_ Environments, install the sdist-package into the environments and run the specified command in each of them. With:: tox -e py26 you can run restrict the test run to the python2.6 environment. Available "default" test environments names are:: py py24 py25 py26 py27 py30 py31 py32 py33 py34 jython pypy pypy3 The environment ``py`` uses the version of Python used to invoke tox. However, you can also create your own test environment names, see some of the examples in :doc:`examples <../examples>`. specifying a platform ----------------------------------------------- .. versionadded:: 2.0 If you want to specify which platform(s) your test environment runs on you can set a platform regular expression like this:: platform = linux2|darwin If the expression does not match against ``sys.platform`` the test environment will be skipped. whitelisting non-virtualenv commands ----------------------------------------------- .. versionadded:: 1.5 Sometimes you may want to use tools not contained in your virtualenv such as ``make``, ``bash`` or others. To avoid warnings you can use the ``whitelist_externals`` testenv configuration:: # content of tox.ini [testenv] whitelist_externals = make /bin/bash .. _virtualenv: http://pypi.python.org/pypi/virtualenv .. _multiindex: depending on requirements.txt ----------------------------------------------- .. versionadded:: 1.6.1 (experimental) If you have a ``requirements.txt`` file you can add it to your ``deps`` variable like this:: deps = -rrequirements.txt All installation commands are executed using ``{toxinidir}`` (the directory where ``tox.ini`` resides) as the current working directory. Therefore, the underlying ``pip`` installation will assume ``requirements.txt`` to exist at ``{toxinidir}/requirements.txt``. using a different default PyPI url ----------------------------------------------- .. versionadded:: 0.9 To install dependencies and packages from a different default PyPI server you can type interactively:: tox -i http://pypi.testrun.org This causes tox to install dependencies and the sdist install step to use the specificied url as the index server. You can cause the same effect by this ``tox.ini`` content:: [tox] indexserver = default = http://pypi.testrun.org installing dependencies from multiple PyPI servers --------------------------------------------------- .. versionadded:: 0.9 You can instrument tox to install dependencies from different PyPI servers, example:: [tox] indexserver = DEV = http://mypypiserver.org [testenv] deps = docutils # comes from standard PyPI :DEV:mypackage # will be installed from custom "DEV" pypi url This configuration will install ``docutils`` from the default Python PYPI server and will install the ``mypackage`` from our ``DEV`` indexserver, and the respective ``http://mypypiserver.org`` url. You can override config file settings from the command line like this:: tox -i DEV=http://pypi.python.org/simple # changes :DEV: package URLs tox -i http://pypi.python.org/simple # changes default further customizing installation --------------------------------- .. versionadded:: 1.6 By default tox uses `pip`_ to install packages, both the package-under-test and any dependencies you specify in ``tox.ini``. You can fully customize tox's install-command through the testenv-specific :confval:`install_command=ARGV` setting. For instance, to use ``easy_install`` instead of `pip`_:: [testenv] install_command = easy_install {opts} {packages} Or to use pip's ``--find-links`` and ``--no-index`` options to specify an alternative source for your dependencies:: [testenv] install_command = pip install --pre --find-links http://packages.example.com --no-index {opts} {packages} .. _pip: http://pip-installer.org forcing re-creation of virtual environments ----------------------------------------------- .. versionadded:: 0.9 To force tox to recreate a (particular) virtual environment:: tox --recreate -e py27 would trigger a complete reinstallation of the existing py27 environment (or create it afresh if it doesn't exist). passing down environment variables ------------------------------------------- .. versionadded:: 2.0 By default tox will only pass the ``PATH`` environment variable (and on windows ``SYSTEMROOT`` and ``PATHEXT``) from the tox invocation to the test environments. If you want to pass down additional environment variables you can use the ``passenv`` option:: [testenv] passenv = LANG When your test commands execute they will execute with the same LANG setting as the one with which tox was invoked. setting environment variables ------------------------------------------- .. versionadded:: 1.0 If you need to set an environment variable like ``PYTHONPATH`` you can use the ``setenv`` directive:: [testenv] setenv = PYTHONPATH = {toxinidir}/subdir When your test commands execute they will execute with a PYTHONPATH setting that will lead Python to also import from the ``subdir`` below the directory where your ``tox.ini`` file resides. special handling of PYTHONHASHSEED ------------------------------------------- .. versionadded:: 1.6.2 By default, Tox sets PYTHONHASHSEED_ for test commands to a random integer generated when ``tox`` is invoked. This mimics Python's hash randomization enabled by default starting `in Python 3.3`_. To aid in reproducing test failures, Tox displays the value of ``PYTHONHASHSEED`` in the test output. You can tell Tox to use an explicit hash seed value via the ``--hashseed`` command-line option to ``tox``. You can also override the hash seed value per test environment in ``tox.ini`` as follows:: [testenv] setenv = PYTHONHASHSEED = 100 If you wish to disable this feature, you can pass the command line option ``--hashseed=noset`` when ``tox`` is invoked. You can also disable it from the ``tox.ini`` by setting ``PYTHONHASHSEED = 0`` as described above. .. _`in Python 3.3`: http://docs.python.org/3/whatsnew/3.3.html#builtin-functions-and-types .. _PYTHONHASHSEED: http://docs.python.org/using/cmdline.html#envvar-PYTHONHASHSEED Integration with setuptools/distribute test commands ---------------------------------------------------- Distribute/Setuptools support test requirements and you can extend its test command to trigger a test run when ``python setup.py test`` is issued:: from setuptools.command.test import test as TestCommand import sys class Tox(TestCommand): user_options = [('tox-args=', 'a', "Arguments to pass to tox")] def initialize_options(self): TestCommand.initialize_options(self) self.tox_args = None def finalize_options(self): TestCommand.finalize_options(self) self.test_args = [] self.test_suite = True def run_tests(self): #import here, cause outside the eggs aren't loaded import tox import shlex args = self.tox_args if args: args = shlex.split(self.tox_args) errno = tox.cmdline(args=args) sys.exit(errno) setup( #..., tests_require=['tox'], cmdclass = {'test': Tox}, ) Now if you run:: python setup.py test this will install tox and then run tox. You can pass arguments to ``tox`` using the ``--tox-args`` or ``-a`` command-line options. For example:: python setup.py test -a "-epy27" is equivalent to running ``tox -epy27``. Ignoring a command exit code ---------------------------- In some cases, you may want to ignore a command exit code. For example:: [testenv:py27] commands = coverage erase {envbindir}/python setup.py develop coverage run -p setup.py test coverage combine - coverage html {envbindir}/flake8 loads By using the ``-`` prefix, similar to a ``make`` recipe line, you can ignore the exit code for that command. Compressing dependency matrix ----------------------------- If you have a large matrix of dependencies, python versions and/or environments you can use :ref:`generative-envlist` and :ref:`conditional settings ` to express that in a concise form:: [tox] envlist = py{26,27,33}-django{15,16}-{sqlite,mysql} [testenv] deps = django15: Django>=1.5,<1.6 django16: Django>=1.6,<1.7 py33-mysql: PyMySQL ; use if both py33 and mysql are in an env name py26,py27: urllib3 ; use if any of py26 or py27 are in an env name py{26,27}-sqlite: mock ; mocking sqlite in python 2.x tox-2.3.1/doc/example/pytest.txt0000664000175000017500000000730212633511761016217 0ustar hpkhpk00000000000000 py.test and tox ================================= It is easy to integrate `py.test`_ runs with tox. If you encounter issues, please check if they are `listed as a known issue`_ and/or use the :doc:`support channels <../support>`. Basic example -------------------------- Assuming the following layout:: tox.ini # see below for content setup.py # a classic distutils/setuptools setup.py file and the following ``tox.ini`` content:: [tox] envlist = py26,py31 [testenv] deps=pytest # PYPI package providing py.test commands= py.test \ {posargs} # substitute with tox' positional arguments you can now invoke ``tox`` in the directory where your ``tox.ini`` resides. ``tox`` will sdist-package your project, create two virtualenv environments with the ``python2.6`` and ``python3.1`` interpreters, respectively, and will then run the specified test command in each of them. Extended example: change dir before test and use per-virtualenv tempdir -------------------------------------------------------------------------- Assuming the following layout:: tox.ini # see below for content setup.py # a classic distutils/setuptools setup.py file tests # the directory containing tests and the following ``tox.ini`` content:: [tox] envlist = py26,py31 [testenv] changedir=tests deps=pytest commands= py.test \ --basetemp={envtmpdir} \ # py.test tempdir setting {posargs} # substitute with tox' positional arguments you can invoke ``tox`` in the directory where your ``tox.ini`` resides. Differently than in the previous example the ``py.test`` command will be executed with a current working directory set to ``tests`` and the test run will use the per-virtualenv temporary directory. .. _`passing positional arguments`: Using multiple CPUs for test runs ----------------------------------- ``py.test`` supports distributing tests to multiple processes and hosts through the `pytest-xdist`_ plugin. Here is an example configuration to make ``tox`` use this feature:: [testenv] deps=pytest-xdist changedir=tests commands= py.test \ --basetemp={envtmpdir} \ --confcutdir=.. \ -n 3 \ # use three sub processes {posargs} .. _`listed as a known issue`: Known Issues and limitations ----------------------------- **Too long filenames**. you may encounter "too long filenames" for temporarily created files in your py.test run. Try to not use the "--basetemp" parameter. **installed-versus-checkout version**. ``py.test`` collects test modules on the filesystem and then tries to import them under their `fully qualified name`_. This means that if your test files are importable from somewhere then your ``py.test`` invocation may end up importing the package from the checkout directory rather than the installed package. There are a few ways to prevent this. With installed tests (the tests packages are known to ``setup.py``), a safe and explicit option is to give the explicit path ``{envsitepackagesdir}/mypkg`` to pytest. Alternatively, it is possible to use ``changedir`` so that checked-out files are outside the import path, then pass ``--pyargs mypkg`` to pytest. With tests that won't be installed, the simplest way to run them against your installed package is to avoid ``__init__.py`` files in test directories; pytest will still find and import them by adding their parent directory to ``sys.path`` but they won't be copied to other places or be found by Python's import system outside of pytest. .. _`fully qualified name`: http://pytest.org/latest/goodpractises.html#test-package-name .. include:: ../links.txt tox-2.3.1/doc/index.txt0000664000175000017500000000727612633511761014355 0ustar hpkhpk00000000000000Welcome to the tox automation project =============================================== vision: standardize testing in Python --------------------------------------------- ``tox`` aims to automate and standardize testing in Python. It is part of a larger vision of easing the packaging, testing and release process of Python software. What is Tox? -------------------- Tox is a generic virtualenv_ management and test command line tool you can use for: * checking your package installs correctly with different Python versions and interpreters * running your tests in each of the environments, configuring your test tool of choice * acting as a frontend to Continuous Integration servers, greatly reducing boilerplate and merging CI and shell-based testing. Basic example ----------------- First, install ``tox`` with ``pip install tox`` or ``easy_install tox``. Then put basic information about your project and the test environments you want your project to run in into a ``tox.ini`` file residing right next to your ``setup.py`` file:: # content of: tox.ini , put in same dir as setup.py [tox] envlist = py26,py27 [testenv] deps=pytest # install pytest in the venvs commands=py.test # or 'nosetests' or ... You can also try generating a ``tox.ini`` file automatically, by running ``tox-quickstart`` and then answering a few simple questions. To sdist-package, install and test your project against Python2.6 and Python2.7, just type:: tox and watch things happening (you must have python2.6 and python2.7 installed in your environment otherwise you will see errors). When you run ``tox`` a second time you'll note that it runs much faster because it keeps track of virtualenv details and will not recreate or re-install dependencies. You also might want to checkout :doc:`examples` to get some more ideas. Current features ------------------- * **automation of tedious Python related test activities** * **test your Python package against many interpreter and dependency configs** - automatic customizable (re)creation of virtualenv_ test environments - installs your ``setup.py`` based project into each virtual environment - test-tool agnostic: runs py.test, nose or unittests in a uniform manner * :doc:`(new in 2.0) plugin system ` to modify tox execution with simple hooks. * uses pip_ and setuptools_ by default. Support for configuring the installer command through :confval:`install_command=ARGV`. * **cross-Python compatible**: CPython-2.6, 2.7, 3.2 and higher, Jython and pypy_. * **cross-platform**: Windows and Unix style environments * **integrates with continuous integration servers** like Jenkins_ (formerly known as Hudson) and helps you to avoid boilerplatish and platform-specific build-step hacks. * **full interoperability with devpi**: is integrated with and is used for testing in the devpi_ system, a versatile pypi index server and release managing tool. * **driven by a simple ini-style config file** * **documented** :doc:`examples ` and :doc:`configuration ` * **concise reporting** about tool invocations and configuration errors * **professionally** :doc:`supported ` * supports :ref:`using different / multiple PyPI index servers ` .. _pypy: http://pypy.org .. _`tox.ini`: :doc:configfile .. toctree:: :hidden: install examples config config-v2 support changelog links plugins example/result announce/release-0.5 announce/release-1.0 announce/release-1.1 announce/release-1.2 announce/release-1.3 announce/release-1.4 announce/release-1.4.3 announce/release-1.8 announce/release-1.9 announce/release-2.0 .. include:: links.txt tox-2.3.1/doc/_static/0000775000175000017500000000000012633511761014117 5ustar hpkhpk00000000000000tox-2.3.1/doc/_static/sphinxdoc.css0000664000175000017500000001371212633511761016634 0ustar hpkhpk00000000000000/* * sphinxdoc.css_t * ~~~~~~~~~~~~~~~ * * Sphinx stylesheet -- sphinxdoc theme. Originally created by * Armin Ronacher for Werkzeug. * * :copyright: Copyright 2007-2010 by the Sphinx team, see AUTHORS. * :license: BSD, see LICENSE for details. * */ @import url("basic.css"); /* -- page layout ----------------------------------------------------------- */ body { font-family: 'Lucida Grande', 'Lucida Sans Unicode', 'Geneva', 'Verdana', sans-serif; font-size: 1.1em; letter-spacing: -0.01em; line-height: 150%; text-align: center; background-color: #BFD1D4; color: black; padding: 0; border: 1px solid #aaa; margin: 0px 80px 0px 80px; min-width: 740px; } div.document { background-color: white; text-align: left; background-image: url(contents.png); background-repeat: repeat-x; } div.bodywrapper { margin: 0 240px 0 0; border-right: 1px solid #ccc; } div.body { margin: 0; padding: 0.5em 20px 20px 20px; } div.related { font-size: 0.8em; } div.related ul { background-image: url(navigation.png); height: 2em; border-top: 1px solid #ddd; border-bottom: 1px solid #ddd; } div.related ul li { margin: 0; padding: 0; height: 2em; float: left; } div.related ul li.right { float: right; margin-right: 5px; } div.related ul li a { margin: 0; padding: 0 5px 0 5px; line-height: 1.75em; color: #EE9816; } div.related ul li a:hover { color: #3CA8E7; } div.sphinxsidebarwrapper { padding: 0; } div.sphinxsidebar { margin: 0; padding: 0.5em 15px 15px 0; width: 210px; float: right; font-size: 1em; text-align: left; } div.sphinxsidebar h3, div.sphinxsidebar h4 { margin: 1em 0 0.5em 0; font-size: 1em; padding: 0.1em 0 0.1em 0.5em; color: white; border: 1px solid #86989B; background-color: #AFC1C4; } div.sphinxsidebar h3 a { color: white; } div.sphinxsidebar ul { padding-left: 1.5em; margin-top: 7px; padding: 0; line-height: 130%; } div.sphinxsidebar ul ul { margin-left: 20px; } div.footer { background-color: #E3EFF1; color: #86989B; padding: 3px 8px 3px 0; clear: both; font-size: 0.8em; text-align: right; } div.footer a { color: #86989B; text-decoration: underline; } /* -- body styles ----------------------------------------------------------- */ p { margin: 0.8em 0 0.5em 0; } a { color: #CA7900; text-decoration: none; } a:hover { color: #2491CF; } div.body a { text-decoration: underline; } h1 { margin: 0; padding: 0.7em 0 0.3em 0; font-size: 1.5em; color: #11557C; } h2 { margin: 1.3em 0 0.2em 0; font-size: 1.35em; padding: 0; } h3 { margin: 1em 0 -0.3em 0; font-size: 1.2em; } div.body h1 a, div.body h2 a, div.body h3 a, div.body h4 a, div.body h5 a, div.body h6 a { color: black!important; } h1 a.anchor, h2 a.anchor, h3 a.anchor, h4 a.anchor, h5 a.anchor, h6 a.anchor { display: none; margin: 0 0 0 0.3em; padding: 0 0.2em 0 0.2em; color: #aaa!important; } h1:hover a.anchor, h2:hover a.anchor, h3:hover a.anchor, h4:hover a.anchor, h5:hover a.anchor, h6:hover a.anchor { display: inline; } h1 a.anchor:hover, h2 a.anchor:hover, h3 a.anchor:hover, h4 a.anchor:hover, h5 a.anchor:hover, h6 a.anchor:hover { color: #777; background-color: #eee; } a.headerlink { color: #c60f0f!important; font-size: 1em; margin-left: 6px; padding: 0 4px 0 4px; text-decoration: none!important; } a.headerlink:hover { background-color: #ccc; color: white!important; } cite, code, tt { font-family: 'Consolas', 'Deja Vu Sans Mono', 'Bitstream Vera Sans Mono', monospace; font-size: 0.95em; letter-spacing: 0.01em; } tt { background-color: #f2f2f2; border-bottom: 1px solid #ddd; color: #333; } tt.descname, tt.descclassname, tt.xref { border: 0; } hr { border: 1px solid #abc; margin: 2em; } a tt { border: 0; color: #CA7900; } a tt:hover { color: #2491CF; } pre { font-family: 'Consolas', 'Deja Vu Sans Mono', 'Bitstream Vera Sans Mono', monospace; font-size: 0.95em; letter-spacing: 0.015em; line-height: 120%; padding: 0.5em; border: 1px solid #ccc; background-color: #f8f8f8; } pre a { color: inherit; text-decoration: underline; } td.linenos pre { padding: 0.5em 0; } div.quotebar { background-color: #f8f8f8; max-width: 250px; float: right; padding: 2px 7px; border: 1px solid #ccc; } div.topic { background-color: #f8f8f8; } table { border-collapse: collapse; margin: 0 -0.5em 0 -0.5em; } table td, table th { padding: 0.2em 0.5em 0.2em 0.5em; } div.admonition, div.warning { font-size: 0.9em; margin: 1em 0 1em 0; border: 1px solid #86989B; background-color: #f7f7f7; padding: 0; } div.admonition p, div.warning p { margin: 0.5em 1em 0.5em 1em; padding: 0; } div.admonition pre, div.warning pre { margin: 0.4em 1em 0.4em 1em; } div.admonition p.admonition-title, div.warning p.admonition-title { margin: 0; padding: 0.1em 0 0.1em 0.5em; color: white; border-bottom: 1px solid #86989B; font-weight: bold; background-color: #AFC1C4; } div.warning { border: 1px solid #940000; } div.warning p.admonition-title { background-color: #CF0000; border-bottom-color: #940000; } div.admonition ul, div.admonition ol, div.warning ul, div.warning ol { margin: 0.1em 0.5em 0.5em 3em; padding: 0; } div.versioninfo { margin: 1em 0 0 0; border: 1px solid #ccc; background-color: #DDEAF0; padding: 8px; line-height: 1.3em; font-size: 0.9em; } .viewcode-back { font-family: 'Lucida Grande', 'Lucida Sans Unicode', 'Geneva', 'Verdana', sans-serif; } div.viewcode-block:target { background-color: #f4debf; border-top: 1px solid #ac9; border-bottom: 1px solid #ac9; } tox-2.3.1/doc/changelog.txt0000664000175000017500000000014012633511761015154 0ustar hpkhpk00000000000000 .. _changelog: Changelog history ================================= .. include:: ../CHANGELOG tox-2.3.1/doc/announce/0000775000175000017500000000000012633511761014277 5ustar hpkhpk00000000000000tox-2.3.1/doc/announce/release-2.0.txt0000664000175000017500000001047712633511761016766 0ustar hpkhpk00000000000000tox-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/latest/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 2.0.0 ----------- - (new) introduce environment variable isolation: tox now only passes the PATH and PIP_INDEX_URL variable from the tox invocation environment to the test environment and on Windows also ``SYSTEMROOT``, ``PATHEXT``, ``TEMP`` and ``TMP`` whereas on unix additionally ``TMPDIR`` is passed. If you need to pass through further environment variables you can use the new ``passenv`` setting, a space-separated list of environment variable names. Each name can make use of fnmatch-style glob patterns. All environment variables which exist in the tox-invocation environment will be copied to the test environment. - a new ``--help-ini`` option shows all possible testenv settings and their defaults. - (new) introduce a way to specify on which platform a testenvironment is to execute: the new per-venv "platform" setting allows to specify a regular expression which is matched against sys.platform. If platform is set and doesn't match the platform spec in the test environment the test environment is ignored, no setup or tests are attempted. - (new) add per-venv "ignore_errors" setting, which defaults to False. If ``True``, a non-zero exit code from one command will be ignored and further commands will be executed (which was the default behavior in tox < 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. - fix issue120: allow substitution for the commands section. Thanks Volodymyr Vitvitski. - fix issue235: fix AttributeError with --installpkg. Thanks Volodymyr Vitvitski. - tox has now somewhat pep8 clean code, thanks to Volodymyr Vitvitski. - fix issue240: allow to specify empty argument list without it being rewritten to ".". Thanks Daniel Hahler. - introduce experimental (not much documented yet) plugin system based on pytest's externalized "pluggy" system. See tox/hookspecs.py for the current hooks. - introduce parser.add_testenv_attribute() to register an ini-variable 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. - DEPRECATE distshare in documentation tox-2.3.1/doc/announce/release-0.5.txt0000664000175000017500000000174312633511761016765 0ustar hpkhpk00000000000000tox 0.5: a generic virtualenv and test management tool for Python =========================================================================== I have been talking about with various people in the last year and am happy to now announce the first release of ``tox``. It aims to automate tedious Python related test activities driven from a simple ``tox.ini`` file, including: * creation and management of different virtualenv environments * installing your package into each of them * running your test tool of choice (including e.g. running sphinx checks) * testing packages against each other without needing to upload to PyPI ``tox`` runs well on Python2.4 up until Python3.1 and integrates well with Continuous Integration servers Jenkins. There are many real-life examples and a good chunk of docs. Read it up on http://codespeak.net/tox and please report any issues. This is a fresh project and i'd like to drive further improvements from real world needs. best, holger krekel tox-2.3.1/doc/announce/release-1.4.3.txt0000664000175000017500000000631612633511761017127 0ustar hpkhpk00000000000000tox 1.4.3: the Python virtualenv-based testing automatizer ============================================================================= tox 1.4.3 fixes some bugs and introduces a new script and two new options: - "tox-quickstart" - run this script, answer a few questions, and get a tox.ini created for you (thanks Marc Abramowitz) - "tox -l" lists configured environment names (thanks Lukasz Balcerzak) - (experimental) "--installpkg=localpath" option which will skip the sdist-creation of a package and instead install the given localpath package. - use pip-script.py instead of pip.exe on win32 to avoid windows locking the .exe Note that the sister project "detox" should continue to work - it's a separately released project which drives tox test runs on multiple CPUs in parallel. More documentation: http://tox.testrun.org/ Installation: pip install -U tox repository hosting and issue tracking on bitbucket: https://bitbucket.org/hpk42/tox What is tox? ---------------- tox standardizes and automates tedious python driven 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 CHANGELOG ================ 1.4.3 (compared to 1.4.2) -------------------------------- - introduce -l|--listenv option to list configured environments (thanks Lukasz Balcerzak) - fix downloadcache determination to work according to docs: Only make pip use a download cache if PIP_DOWNLOAD_CACHE or a downloadcache=PATH testenv setting is present. (The ENV setting takes precedence) - fix issue84 - pypy on windows creates a bin not a scripts venv directory (thanks Lukasz Balcerzak) - experimentally introduce --installpkg=PATH option to install a package rather than create/install an sdist package. This will still require and use tox.ini and tests from the current working dir (and not from the remote package). - substitute {envsitepackagesdir} with the package installation directory (closes #72) (thanks g2p) - issue #70 remove PYTHONDONTWRITEBYTECODE workaround now that virtualenv behaves properly (thanks g2p) - merged tox-quickstart command, contributed by Marc Abramowitz, which generates a default tox.ini after asking a few questions - fix #48 - win32 detection of pypy and other interpreters that are on PATH (thanks Gustavo Picon) - fix grouping of index servers, it is now done by name instead of indexserver url, allowing to use it to separate dependencies into groups even if using the same default indexserver. - look for "tox.ini" files in parent dirs of current dir (closes #34) - the "py" environment now by default uses the current interpreter (sys.executable) make tox' own setup.py test execute tests with it (closes #46) - change tests to not rely on os.path.expanduser (closes #60), also make mock session return args[1:] for more precise checking (closes #61) thanks to Barry Warszaw for both. tox-2.3.1/doc/announce/release-1.2.txt0000664000175000017500000000266412633511761016766 0ustar hpkhpk00000000000000tox 1.2: the virtualenv-based test run automatizer =========================================================================== I am happy to announce tox 1.2, now using and depending on the latest virtualenv code and containing some bug fixes. TOX 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 It works well on virtually all Python interpreters that support virtualenv. Docs and examples are at: http://tox.testrun.org/ Installation: pip install -U tox code hosting and issue tracking on bitbucket: https://bitbucket.org/hpk42/tox best, Holger Krekel 1.2 compared to 1.1 --------------------- - remove the virtualenv.py that was distributed with tox and depend on virtualenv-1.6.4 (possible now since the latter fixes a few bugs that the inling tried to work around) - fix issue10: work around UnicodeDecodeError when inokving pip (thanks Marc Abramowitz) - fix a problem with parsing {posargs} in tox commands (spotted by goodwill) - fix the warning check for commands to be installed in testevironment (thanks Michael Foord for reporting) tox-2.3.1/doc/announce/release-1.0.txt0000664000175000017500000000427312633511761016762 0ustar hpkhpk00000000000000tox 1.0: the rapid multi-python test automatizer =========================================================================== I am happy to announce tox 1.0, mostly a stabilization and streamlined release. TOX 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 Docs and examples are at: http://tox.readthedocs.org Installation: pip install -U tox Note that code hosting and issue tracking has moved from Google to Bitbucket: https://bitbucket.org/hpk42/tox The 1.0 release includes contributions and is based on feedback and work from Chris Rose, Ronny Pfannschmidt, Jannis Leidel, Jakob Kaplan-Moss, Sridhar Ratnakumar, Carl Meyer and others. Many thanks! best, Holger Krekel CHANGES --------------------- - fix issue24: introduce a way to set environment variables for for test commands (thanks Chris Rose) - fix issue22: require virtualenv-1.6.1, obsoleting virtualenv5 (thanks Jannis Leidel) and making things work with pypy-1.5 and python3 more seemlessly - toxbootstrap.py (used by jenkins build slaves) now follows the latest release of virtualenv - fix issue20: document format of URLs for specifying dependencies - fix issue19: substitute Hudson for Jenkins everywhere following the renaming of the project. NOTE: if you used the special [tox:hudson] section it will now need to be named [tox:jenkins]. - fix issue 23 / apply some ReST fixes - change the positional argument specifier to use {posargs:} syntax and fix issues #15 and #10 by refining the argument parsing method (Chris Rose) - remove use of inipkg lazy importing logic - the namespace/imports are anyway very small with tox. - fix a fspath related assertion to work with debian installs which uses symlinks - show path of the underlying virtualenv invocation and bootstrap virtualenv.py into a working subdir - added a CONTRIBUTORS file tox-2.3.1/doc/announce/release-1.4.txt0000664000175000017500000000436612633511761016771 0ustar hpkhpk00000000000000tox 1.4: the virtualenv-based test run automatizer ============================================================================= I am happy to announce tox 1.4 which brings: - improvements with configuration file syntax, now allowing re-using selected settings across config file sections. see http://testrun.org/tox/latest/config.html#substitution-for-values-from-other-sections - terminal reporting was simplified and streamlined. Now with verbosity==0 (the default), less information will be shown and you can use one or multiple "-v" options to increase verbosity. - internal re-organisation so that the separately released "detox" tool can reuse tox code to implement a fully distributed tox run. More documentation: 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 1.4 ----------------- - fix issue26 - no warnings on absolute or relative specified paths for commands - fix issue33 - commentchars are ignored in key-value settings allowing for specifying commands like: python -c "import sys ; print sys" which would formerly raise irritating errors because the ";" was considered a comment - tweak and improve reporting - refactor reporting and virtualenv manipulation to be more accessible from 3rd party tools - support value substitution from other sections with the {[section]key} syntax - fix issue29 - correctly point to pytest explanation for importing modules fully qualified - fix issue32 - use --system-site-packages and don't pass --no-site-packages - add python3.3 to the default env list, so early adopters can test - drop python2.4 support (you can still have your tests run on python-2.4, just tox itself requires 2.5 or higher. tox-2.3.1/doc/announce/release-1.3.txt0000664000175000017500000000227312633511761016763 0ustar hpkhpk00000000000000tox 1.3: the virtualenv-based test run automatizer =========================================================================== I am happy to announce tox 1.3, containing a few improvements over 1.2. TOX 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 Docs and examples are at: http://tox.testrun.org/ Installation: pip install -U tox code hosting and issue tracking on bitbucket: https://bitbucket.org/hpk42/tox best, Holger Krekel 1.3 ----------------- - fix: allow to specify wildcard filesystem paths when specifiying dependencies such that tox searches for the highest version - fix issue issue21: clear PIP_REQUIRES_VIRTUALENV which avoids pip installing to the wrong environment, thanks to bb's streeter - make the install step honour a testenv's setenv setting (thanks Ralf Schmitt) tox-2.3.1/doc/announce/release-1.9.txt0000664000175000017500000000422512633511761016770 0ustar hpkhpk00000000000000tox-1.9: refinements, fixes (+detox-0.9.4) ========================================== tox-1.9 was released to pypi, a maintenance release with mostly backward-compatible enhancements and fixes. However, tox now defaults to pip-installing only non-development releases and you have to set "pip_pre = True" in your testenv section to have it install development ("pre") releases. In addition, there is a new detox-0.9.4 out which allow to run tox test environments in parallel and fixes a compat problem with eventlet. Thanks to Alexander Schepanosvki, Florian Schulze and others for the contributed fixes and improvements. 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 1.9.0 ----------- - fix issue193: Remove ``--pre`` from the default ``install_command``; by default tox will now only install final releases from PyPI for unpinned dependencies. Use ``pip_pre = true`` in a testenv or the ``--pre`` command-line option to restore the previous behavior. - fix issue199: fill resultlog structure ahead of virtualenv creation - refine determination if we run from Jenkins, thanks Borge Lanes. - echo output to stdout when ``--report-json`` is used - fix issue11: add a ``skip_install`` per-testenv setting which prevents the installation of a package. Thanks Julian Krause. - fix issue124: ignore command exit codes; when a command has a "-" prefix, tox will ignore the exit code of that command - fix issue198: fix broken envlist settings, e.g. {py26,py27}{-lint,} - fix issue191: lessen factor-use checks tox-2.3.1/doc/announce/release-1.8.txt0000664000175000017500000000332512633511761016767 0ustar hpkhpk00000000000000tox 1.8: Generative/combinatorial environments specs ============================================================================= I am happy to announce tox 1.8 which implements parametrized environments. See https://tox.testrun.org/latest/config.html#generating-environments-conditional-settings for examples and the new backward compatible syntax in your tox.ini file. Many thanks to Alexander Schepanovski for implementing and refining it based on the specifcation draft. More documentation about tox in general: http://tox.testrun.org/ Installation: pip install -U tox code hosting and issue tracking on bitbucket: https://bitbucket.org/hpk42/tox What is tox? ---------------- tox standardizes and automates tedious test activities driven from a simple ``tox.ini`` file, including: * creation and management of different virtualenv environments with different Python interpreters * packaging and installing your package into each of them * running your test tool of choice, be it nose, py.test or unittest2 or other tools such as "sphinx" doc checks * testing dev packages against each other without needing to upload to PyPI best, Holger Krekel, merlinux GmbH Changes 1.8 (compared to 1.7.2) --------------------------------------- - new multi-dimensional configuration support. Many thanks to Alexander Schepanovski for the complete PR with docs. And to Mike Bayer and others for testing and feedback. - fix issue148: remove "__PYVENV_LAUNCHER__" from os.environ when starting subprocesses. Thanks Steven Myint. - fix issue152: set VIRTUAL_ENV when running test commands, thanks Florian Ludwig. - better report if we can't get version_info from an interpreter executable. Thanks Floris Bruynooghe. tox-2.3.1/doc/announce/release-1.1.txt0000664000175000017500000000347012633511761016761 0ustar hpkhpk00000000000000tox 1.1: the rapid multi-python test automatizer =========================================================================== I am happy to announce tox 1.1, a bug fix release easing some commong workflows. TOX 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 It works well on virtually all Python interpreters that support virtualenv. Docs and examples are at: http://tox.readthedocs.org Installation: pip install -U tox Note that code hosting and issue tracking has moved from Google to Bitbucket: https://bitbucket.org/hpk42/tox The 1.0 release includes contributions and is based on feedback and work from Chris Rose, Ronny Pfannschmidt, Jannis Leidel, Jakob Kaplan-Moss, Sridhar Ratnakumar, Carl Meyer and others. Many thanks! best, Holger Krekel CHANGES --------------------- - fix issue5 - don't require argparse for python versions that have it - fix issue6 - recreate virtualenv if installing dependencies failed - fix issue3 - fix example on frontpage - fix issue2 - warn if a test command does not come from the test environment - fixed/enhanced: except for initial install always call "-U --no-deps" for installing the sdist package to ensure that a package gets upgraded even if its version number did not change. (reported on TIP mailing list and IRC) - inline virtualenv.py (1.6.1) script to avoid a number of issues, particularly failing to install python3 environents from a python2 virtualenv installation. tox-2.3.1/doc/config.txt0000664000175000017500000004757012633511761014514 0ustar hpkhpk00000000000000.. be in -*- rst -*- mode! tox configuration specification =============================== .. _ConfigParser: http://docs.python.org/library/configparser.html ``tox.ini`` files uses the standard ConfigParser_ "ini-style" format. Below you find the specification, but you might want to skim some :doc:`examples` first and use this page as a reference. Tox global settings ------------------- List of optional global options:: [tox] minversion=ver # minimally required tox version toxworkdir=path # tox working directory, defaults to {toxinidir}/.tox setupdir=path # defaults to {toxinidir} distdir=path # defaults to {toxworkdir}/dist distshare=path # (DEPRECATED) defaults to {homedir}/.tox/distshare envlist=ENVLIST # defaults to the list of all environments skipsdist=BOOL # defaults to false ``tox`` autodetects if it is running in a Jenkins_ context (by checking for existence of the ``JENKINS_URL`` environment variable) and will first lookup global tox settings in this section:: [tox:jenkins] ... # override [tox] settings for the jenkins context # note: for jenkins distshare defaults to ``{toxworkdir}/distshare`` (DEPRECATED) .. confval:: skip_missing_interpreters=BOOL .. versionadded:: 1.7.2 Setting this to ``True`` is equivalent of passing the ``--skip-missing-interpreters`` command line option, and will force ``tox`` to return success even if some of the specified environments were missing. This is useful for some CI systems or running on a developer box, where you might only have a subset of all your supported interpreters installed but don't want to mark the build as failed because of it. As expected, the command line switch always overrides this setting if passed on the invokation. **Default:** ``False`` .. confval:: envlist=CSV Determining the environment list that ``tox`` is to operate on happens in this order (if any is found, no further lookups are made): * command line option ``-eENVLIST`` * environment variable ``TOXENV`` * ``tox.ini`` file's ``envlist`` Virtualenv test environment settings ------------------------------------ Test environments are defined by a:: [testenv:NAME] ... section. The ``NAME`` will be the name of the virtual environment. Defaults for each setting in this section are looked up in the:: [testenv] ... testenvironment default section. Complete list of settings that you can put into ``testenv*`` sections: .. confval:: basepython=NAME-OR-PATH name or path to a Python interpreter which will be used for creating the virtual environment. **default**: interpreter used for tox invocation. .. confval:: commands=ARGVLIST the commands to be called for testing. Each command is defined by one or more lines; a command can have multiple lines if a line ends with the ``\`` character in which case the subsequent line will be appended (and may contain another ``\`` character ...). For eventually performing a call to ``subprocess.Popen(args, ...)`` ``args`` are determined by splitting the whole command by whitespace. Similar to ``make`` recipe lines, any command with a leading ``-`` will ignore the exit code. .. confval:: install_command=ARGV .. versionadded:: 1.6 the ``install_command`` setting is used for installing packages into the virtual environment; both the package under test and any defined dependencies. Must contain the substitution key ``{packages}`` which will be replaced by the packages to install. You should also accept ``{opts}`` if you are using pip or easy_install -- it will contain index server options if you have configured them via :confval:`indexserver` and the deprecated :confval:`downloadcache` option if you have configured it. **default**:: pip install {opts} {packages} .. confval:: ignore_errors=True|False(default) .. versionadded:: 2.0 If ``True``, a non-zero exit code from one command will be ignored and further commands will be executed (which was the default behavior in tox < 2.0). If ``False`` (the default), then a non-zero exit code from one command will abort execution of commands for that environment. It may be helpful to note that this setting is analogous to the ``-i`` or ``ignore-errors`` option of GNU Make. A similar name was chosen to reflect the similarity in function. Note that in tox 2.0, the default behavior of tox with respect to treating errors from commands changed. Tox < 2.0 would ignore errors by default. Tox >= 2.0 will abort on an error by default, which is safer and more typical of CI and command execution tools, as it doesn't make sense to run tests if installing some prerequisite failed and it doesn't make sense to try to deploy if tests failed. .. confval:: pip_pre=True|False(default) .. versionadded:: 1.9 If ``True``, adds ``--pre`` to the ``opts`` passed to :confval:`install_command`. If :confval:`install_command` uses pip, this will cause it to install the latest available pre-release of any dependencies without a specified version. If ``False`` (the default), pip will only install final releases of unpinned dependencies. Passing the ``--pre`` command-line option to tox will force this to ``True`` for all testenvs. Don't set this option if your :confval:`install_command` does not use pip. .. confval:: whitelist_externals=MULTI-LINE-LIST each line specifies a command name (in glob-style pattern format) which can be used in the ``commands`` section without triggering a "not installed in virtualenv" warning. Example: if you use the unix ``make`` for running tests you can list ``whitelist_externals=make`` or ``whitelist_externals=/usr/bin/make`` if you want more precision. If you don't want tox to issue a warning in any case, just use ``whitelist_externals=*`` which will match all commands (not recommended). .. confval:: changedir=path change to this working directory when executing the test command. **default**: ``{toxinidir}`` .. confval:: deps=MULTI-LINE-LIST test-specific dependencies - to be installed into the environment prior to project package installation. Each line defines a dependency, which will be passed to the installer command for processing. Each line specifies a file, a URL or a package name. You can additionally specify an :confval:`indexserver` to use for installing this dependency but this functionality is deprecated since tox-2.3. All derived dependencies (deps required by the dep) will then be retrieved from the specified indexserver:: deps = :myindexserver:pkg (Experimentally introduced in 1.6.1) all installer commands are executed using the ``{toxinidir}`` as the current working directory. .. confval:: platform=REGEX A testenv can define a new ``platform`` setting as a regular expression. If a non-empty expression is defined and does not match against the ``sys.platform`` string the test environment will be skipped. .. confval:: setenv=MULTI-LINE-LIST .. versionadded:: 0.9 each line contains a NAME=VALUE environment variable setting which will be used for all test command invocations as well as for installing the sdist package into a virtual environment. .. confval:: passenv=SPACE-SEPARATED-GLOBNAMES .. versionadded:: 2.0 A list of wildcard environment variable names which shall be copied from the tox invocation environment to the test environment when executing test commands. If a specified environment variable doesn't exist in the tox invocation environment it is ignored. You can use ``*`` and ``?`` to match multiple environment variables with one name. Note that the ``PATH``, ``LANG`` and ``PIP_INDEX_URL`` variables are unconditionally passed down and on Windows ``SYSTEMROOT``, ``PATHEXT``, ``TEMP`` and ``TMP`` will be passed down as well whereas on unix ``TMPDIR`` will be passed down. You can override these variables with the ``setenv`` option. If defined the ``TOX_TESTENV_PASSENV`` environment variable (in the tox invocation environment) can define additional space-separated variable names that are to be passed down to the test command environment. .. confval:: recreate=True|False(default) Always recreate virtual environment if this option is True. .. confval:: downloadcache=path **DEPRECATED** -- as of August 2013 this option is not very useful because of pypi's CDN and because of caching pypi server solutions like `devpi `_. use this directory for caching downloads. This value is overriden by the environment variable ``PIP_DOWNLOAD_CACHE`` if it exists. If you specify a custom :confval:`install_command` that uses an installer other than pip, your installer must support the `--download-cache` command-line option. **default**: no download cache will be used. .. confval:: sitepackages=True|False Set to ``True`` if you want to create virtual environments that also have access to globally installed packages. **default:** False, meaning that virtualenvs will be created without inheriting the global site packages. .. confval:: args_are_paths=BOOL treat positional arguments passed to ``tox`` as file system paths and - if they exist on the filesystem - rewrite them according to the ``changedir``. **default**: True (due to the exists-on-filesystem check it's usually safe to try rewriting). .. confval:: envtmpdir=path defines a temporary directory for the virtualenv which will be cleared each time before the group of test commands is invoked. **default**: ``{envdir}/tmp`` .. confval:: envlogdir=path defines a directory for logging where tox will put logs of tool invocation. **default**: ``{envdir}/log`` .. confval:: indexserver .. versionadded:: 0.9 (DEPRECATED, will be removed in a future version) Multi-line ``name = URL`` definitions of python package servers. Dependencies can specify using a specified index server through the ``:indexservername:depname`` pattern. The ``default`` indexserver definition determines where unscoped dependencies and the sdist install installs from. Example:: [tox] indexserver = default = http://mypypi.org will make tox install all dependencies from this PYPI index server (including when installing the project sdist package). .. confval:: envdir .. versionadded:: 1.5 User can set specific path for environment. If path would not be absolute it would be treated as relative to ``{toxinidir}``. **default**: ``{toxworkdir}/{envname}`` .. confval:: usedevelop=BOOL .. versionadded:: 1.6 Install the current package in development mode with "setup.py develop" instead of installing from the ``sdist`` package. (This uses pip's `-e` option, so should be avoided if you've specified a custom :confval:`install_command` that does not support ``-e``). **default**: ``False`` .. confval:: skip_install=BOOL .. versionadded:: 1.9 Do not install the current package. This can be used when you need the virtualenv management but do not want to install the current package into that environment. **default**: ``False`` .. confval:: ignore_outcome=BOOL .. versionadded:: 2.2 If set to True a failing result of this testenv will not make tox fail, only a warning will be produced. **default**: ``False`` Substitutions ------------- Any ``key=value`` setting in an ini-file can make use of value substitution through the ``{...}`` string-substitution pattern. You can escape curly braces with the ``\`` character if you need them, for example:: commands = echo "\{posargs\}" = {posargs} Globally available substitutions ++++++++++++++++++++++++++++++++ ``{toxinidir}`` the directory where tox.ini is located ``{toxworkdir}`` the directory where virtual environments are created and sub directories for packaging reside. ``{homedir}`` the user-home directory path. ``{distdir}`` the directory where sdist-packages will be created in ``{distshare}`` (DEPRECATED) the directory where sdist-packages will be copied to so that they may be accessed by other processes or tox runs. substitutions for virtualenv-related sections +++++++++++++++++++++++++++++++++++++++++++++ ``{envname}`` the name of the virtual environment ``{envpython}`` path to the virtual Python interpreter ``{envdir}`` directory of the virtualenv hierarchy ``{envbindir}`` directory where executables are located ``{envsitepackagesdir}`` directory where packages are installed. Note that architecture-specific files may appear in a different directory. ``{envtmpdir}`` the environment temporary directory ``{envlogdir}`` the environment log directory environment variable substitutions ++++++++++++++++++++++++++++++++++ If you specify a substitution string like this:: {env:KEY} then the value will be retrieved as ``os.environ['KEY']`` and raise an Error if the environment variable does not exist. environment variable substitutions with default values ++++++++++++++++++++++++++++++++++++++++++++++++++++++ If you specify a substitution string like this:: {env:KEY:DEFAULTVALUE} then the value will be retrieved as ``os.environ['KEY']`` and replace with DEFAULTVALUE if the environment variable does not exist. If you specify a substitution string like this:: {env:KEY:} then the value will be retrieved as ``os.environ['KEY']`` and replace with and empty string if the environment variable does not exist. .. _`command positional substitution`: .. _`positional substitution`: substitutions for positional arguments in commands ++++++++++++++++++++++++++++++++++++++++++++++++++ .. versionadded:: 1.0 If you specify a substitution string like this:: {posargs:DEFAULTS} then the value will be replaced with positional arguments as provided to the tox command:: tox arg1 arg2 In this instance, the positional argument portion will be replaced with ``arg1 arg2``. If no positional arguments were specified, the value of DEFAULTS will be used instead. If DEFAULTS contains other substitution strings, such as ``{env:*}``, they will be interpreted., Use a double ``--`` if you also want to pass options to an underlying test command, for example:: tox -- --opt1 ARG1 will make the ``--opt1 ARG1`` appear in all test commands where ``[]`` or ``{posargs}`` was specified. By default (see ``args_are_paths`` setting), ``tox`` rewrites each positional argument if it is a relative path and exists on the filesystem to become a path relative to the ``changedir`` setting. Previous versions of tox supported the ``[.*]`` pattern to denote positional arguments with defaults. This format has been deprecated. Use ``{posargs:DEFAULTS}`` to specify those. Substitution for values from other sections +++++++++++++++++++++++++++++++++++++++++++ .. versionadded:: 1.4 Values from other sections can be refered to via:: {[sectionname]valuename} which you can use to avoid repetition of config values. You can put default values in one section and reference them in others to avoid repeating the same values:: [base] deps = pytest mock pytest-xdist [testenv:dulwich] deps = dulwich {[base]deps} [testenv:mercurial] deps = mercurial {[base]deps} Generating environments, conditional settings --------------------------------------------- .. versionadded:: 1.8 Suppose you want to test your package against python2.6, python2.7 and against several versions of a dependency, say Django 1.5 and Django 1.6. You can accomplish that by writing down 2*2 = 4 ``[testenv:*]`` sections and then listing all of them in ``envlist``. However, a better approach looks like this:: [tox] envlist = {py26,py27}-django{15,16} [testenv] basepython = py26: python2.6 py27: python2.7 deps = pytest django15: Django>=1.5,<1.6 django16: Django>=1.6,<1.7 py26: unittest2 commands = py.test This uses two new facilities of tox-1.8: - generative envlist declarations where each envname consists of environment parts or "factors" - "factor" specific settings Let's go through this step by step. .. _generative-envlist: Generative envlist +++++++++++++++++++++++ :: envlist = {py26,py27}-django{15,16} This is bash-style syntax and will create ``2*2=4`` environment names like this:: py26-django15 py26-django16 py27-django15 py27-django16 You can still list environments explicitly along with generated ones:: envlist = {py26,py27}-django{15,16}, docs, flake .. note:: To help with understanding how the variants will produce section values, you can ask tox to show their expansion with a new option:: $ tox -l py26-django15 py26-django16 py27-django15 py27-django16 docs flake .. _factors: Factors and factor-conditional settings ++++++++++++++++++++++++++++++++++++++++ Parts of an environment name delimited by hyphens are called factors and can be used to set values conditionally:: basepython = py26: python2.6 py27: python2.7 This conditional setting will lead to either ``python2.6`` or ``python2.7`` used as base python, e.g. ``python2.6`` is selected if current environment contains ``py26`` factor. In list settings such as ``deps`` or ``commands`` you can freely intermix optional lines with unconditional ones:: deps = pytest django15: Django>=1.5,<1.6 django16: Django>=1.6,<1.7 py26: unittest2 Reading it line by line: - ``pytest`` will be included unconditionally, - ``Django>=1.5,<1.6`` will be included for environments containing ``django15`` factor, - ``Django>=1.6,<1.7`` similarly depends on ``django16`` factor, - ``unittest`` will be loaded for Python 2.6 environments. .. note:: Tox provides good defaults for basepython setting, so the above ini-file can be further reduced by omitting the ``basepython`` setting. Complex factor conditions +++++++++++++++++++++++++ Sometimes you need to specify the same line for several factors or create a special case for a combination of factors. Here is how you do it:: [tox] envlist = py{26,27,33}-django{15,16}-{sqlite,mysql} [testenv] deps = py33-mysql: PyMySQL ; use if both py33 and mysql are in an env name py26,py27: urllib3 ; use if any of py26 or py27 are in an env name py{26,27}-sqlite: mock ; mocking sqlite in python 2.x Take a look at first ``deps`` line. It shows how you can special case something for a combination of factors, you just join combining factors with a hyphen. This particular line states that ``PyMySQL`` will be loaded for python 3.3, mysql environments, e.g. ``py33-django15-mysql`` and ``py33-django16-mysql``. The second line shows how you use same line for several factors - by listing them delimited by commas. It's possible to list not only simple factors, but also their combinations like ``py26-sqlite,py27-sqlite``. Finally, factor expressions are expanded the same way as envlist, so last example could be rewritten as ``py{26,27}-sqlite``. .. note:: Factors don't do substring matching against env name, instead every hyphenated expression is split by ``-`` and if ALL the factors in an expression are also factors of an env then that condition is considered hold. For example, environment ``py26-mysql``: - could be matched with expressions ``py26``, ``py26-mysql``, ``mysql-py26``, - but not with ``py2`` or ``py26-sql``. Other Rules and notes ===================== * ``path`` specifications: if a specified ``path`` is a relative path it will be considered as relative to the ``toxinidir``, the directory where the configuration file resides. .. include:: links.txt tox-2.3.1/doc/links.txt0000664000175000017500000000152212633511761014352 0ustar hpkhpk00000000000000 .. _devpi: http://doc.devpi.net .. _Python: http://www.python.org .. _virtualenv: https://pypi.python.org/pypi/virtualenv .. _virtualenv3: https://pypi.python.org/pypi/virtualenv3 .. _virtualenv5: https://pypi.python.org/pypi/virtualenv5 .. _`py.test`: http://pytest.org .. _nosetests: .. _`nose`: https://pypi.python.org/pypi/nose .. _`Holger Krekel`: https://twitter.com/hpk42 .. _`pytest-xdist`: https://pypi.python.org/pypi/pytest-xdist .. _`easy_install`: http://peak.telecommunity.com/DevCenter/EasyInstall .. _pip: https://pypi.python.org/pypi/pip .. _setuptools: https://pypi.python.org/pypi/setuptools .. _`jenkins`: http://jenkins-ci.org/ .. _sphinx: https://pypi.python.org/pypi/Sphinx .. _discover: https://pypi.python.org/pypi/discover .. _unittest2: https://pypi.python.org/pypi/unittest2 .. _mock: https://pypi.python.org/pypi/mock/ tox-2.3.1/doc/check_sphinx.py0000664000175000017500000000072112633511761015511 0ustar hpkhpk00000000000000import py import subprocess def test_build_docs(tmpdir): doctrees = tmpdir.join("doctrees") htmldir = tmpdir.join("html") subprocess.check_call([ "sphinx-build", "-bhtml", "-d", str(doctrees), ".", str(htmldir)]) def test_linkcheck(tmpdir): doctrees = tmpdir.join("doctrees") htmldir = tmpdir.join("html") subprocess.check_call( ["sphinx-build", "-blinkcheck", "-d", str(doctrees), ".", str(htmldir)]) tox-2.3.1/doc/_getdoctarget.py0000664000175000017500000000072712633511761015664 0ustar hpkhpk00000000000000#!/usr/bin/env python import py def get_version_string(): fn = py.path.local(__file__).join("..", "..", "tox", "__init__.py") for line in fn.readlines(): if "version" in line and not line.strip().startswith('#'): return eval(line.split("=")[-1]) def get_minor_version_string(): return ".".join(get_version_string().split(".")[:2]) if __name__ == "__main__": print (get_minor_version_string()) tox-2.3.1/doc/plugins.txt0000664000175000017500000000415412633511761014717 0ustar hpkhpk00000000000000.. be in -*- rst -*- mode! tox plugins =========== .. versionadded:: 2.0 With tox-2.0 a few aspects of tox running can be experimentally modified by writing hook functions. The list of of available hook function is to grow over time on a per-need basis. writing a setuptools entrypoints plugin --------------------------------------- If you have a ``tox_MYPLUGIN.py`` module you could use the following rough ``setup.py`` to make it into a package which you can upload to the Python packaging index:: # content of setup.py from setuptools import setup if __name__ == "__main__": setup( name='tox-MYPLUGIN', description='tox plugin decsription', license="MIT license", version='0.1', py_modules=['tox_MYPLUGIN'], entry_points={'tox': ['MYPLUGIN = tox_MYPLUGIN']}, install_requires=['tox>=2.0'], ) If installed, the ``entry_points`` part will make tox see and integrate your plugin during startup. You can install the plugin for development ("in-place") via:: pip install -e . and later publish it via something like:: python setup.py sdist register upload Writing hook implementations ---------------------------- A plugin module defines one or more hook implementation functions by decorating them with tox's ``hookimpl`` marker:: from tox import hookimpl @hookimpl def tox_addoption(parser): # add your own command line options @hookimpl def tox_configure(config): # post process tox configuration after cmdline/ini file have # been parsed If you put this into a module and make it pypi-installable with the ``tox`` entry point you'll get your code executed as part of a tox run. tox hook specifications and related API --------------------------------------- .. automodule:: tox.hookspecs :members: .. autoclass:: tox.config.Parser() :members: .. autoclass:: tox.config.Config() :members: .. autoclass:: tox.config.TestenvConfig() :members: .. autoclass:: tox.venv.VirtualEnv() :members: .. autoclass:: tox.session.Session() :members: tox-2.3.1/doc/install.txt0000664000175000017500000000172112633511761014701 0ustar hpkhpk00000000000000tox installation ================================== Install info in a nutshell ---------------------------------- **Pythons**: CPython 2.6-3.3, Jython-2.5.1, pypy-1.9ff **Operating systems**: Linux, Windows, OSX, Unix **Installer Requirements**: setuptools_ **License**: MIT license **hg repository**: https://bitbucket.org/hpk42/tox Installation with pip/easy_install -------------------------------------- Use one of the following commands:: pip install tox easy_install tox It is fine to install ``tox`` itself into a virtualenv_ environment. Install from Checkout ------------------------- Consult the Bitbucket page to get a checkout of the mercurial repository: https://bitbucket.org/hpk42/tox and then install in your environment with something like:: python setup.py install or just activate your checkout in your environment like this:: python setup.py develop so that you can do changes and submit patches. .. include:: links.txt tox-2.3.1/doc/conf.py0000664000175000017500000002125512633511761013775 0ustar hpkhpk00000000000000# -*- coding: utf-8 -*- # # tox documentation build configuration file, created by # sphinx-quickstart on Sat May 29 10:42:26 2010. # # This file is execfile()d with the current directory set to its containing dir. # # Note that not all possible configuration values are present in this # autogenerated file. # # All configuration values have a default; values that are commented out # serve to show the default. import sys, os # The short X.Y version. sys.path.insert(0, os.path.dirname(__file__)) import _getdoctarget version = _getdoctarget.get_minor_version_string() release = _getdoctarget.get_version_string() # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. #sys.path.append(os.path.abspath('.')) # -- General configuration ----------------------------------------------------- # If your documentation needs a minimal Sphinx version, state it here. #needs_sphinx = '1.0' # Add any Sphinx extension module names here, as strings. They can be extensions # coming with Sphinx (named 'sphinx.ext.*') or your custom ones. extensions = ['sphinx.ext.autodoc', 'sphinx.ext.intersphinx', 'sphinx.ext.viewcode'] # Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] # The suffix of source filenames. source_suffix = '.txt' # The encoding of source files. #source_encoding = 'utf-8-sig' # The master toctree document. master_doc = 'index' # General information about the project. project = u'tox' 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 # built documents. # # The full version, including alpha/beta/rc tags. # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. #language = None # There are two options for replacing |today|: either, you set today to some # non-false value, then it is used: #today = '' # Else, today_fmt is used as the format for a strftime call. #today_fmt = '%B %d, %Y' # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. exclude_patterns = ['_build'] # The reST default role (used for this markup: `text`) to use for all documents. #default_role = None # If true, '()' will be appended to :func: etc. cross-reference text. #add_function_parentheses = True # If true, the current module name will be prepended to all description # unit titles (such as .. function::). #add_module_names = True # If true, sectionauthor and moduleauthor directives will be shown in the # output. They are ignored by default. #show_authors = False # The name of the Pygments (syntax highlighting) style to use. pygments_style = 'sphinx' # A list of ignored prefixes for module index sorting. #modindex_common_prefix = [] # -- Options for HTML output --------------------------------------------------- # The theme to use for HTML and HTML Help pages. Major themes that come with # Sphinx are currently 'default' and 'sphinxdoc'. html_theme = 'sphinxdoc' # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the # documentation. #html_theme_options = {'index': 'indexsidebar.html'} # Add any paths that contain custom themes here, relative to this directory. #html_theme_path = [] # The name for this set of Sphinx documents. If None, it defaults to # " v documentation". #html_title = None # A shorter title for the navigation bar. Default is the same as html_title. #html_short_title = None # The name of an image file (relative to this directory) to place at the top # of the sidebar. #html_logo = None # The name of an image file (within the static path) to use as favicon of the # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 # pixels large. #html_favicon = None # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". html_static_path = ['_static'] # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, # using the given strftime format. #html_last_updated_fmt = '%b %d, %Y' # If true, SmartyPants will be used to convert quotes and dashes to # typographically correct entities. #html_use_smartypants = True # Custom sidebar templates, maps document names to template names. #html_sidebars = {'index': 'indexsidebar.html'} # Additional templates that should be rendered to pages, maps page names to # template names. #html_additional_pages = {} # If false, no module index is generated. #html_domain_indices = True # If false, no index is generated. #html_use_index = True # If true, the index is split into individual pages for each letter. #html_split_index = False # If true, links to the reST sources are added to the pages. html_show_sourcelink = False # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. #html_show_sphinx = True # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. #html_show_copyright = True # If true, an OpenSearch description file will be output, and all pages will # contain a tag referring to it. The value of this option must be the # base URL from which the finished HTML is served. #html_use_opensearch = '' # If nonempty, this is the file name suffix for HTML files (e.g. ".xhtml"). #html_file_suffix = '' # Output file base name for HTML help builder. htmlhelp_basename = 'toxdoc' # -- Options for LaTeX output -------------------------------------------------- # The paper size ('letter' or 'a4'). #latex_paper_size = 'a4' # The font size ('10pt', '11pt' or '12pt'). #latex_font_size = '12pt' # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, author, documentclass [howto/manual]). latex_documents = [ ('index', 'tox.tex', u'tox Documentation', u'holger krekel', 'manual'), ] # The name of an image file (relative to this directory) to place at the top of # the title page. #latex_logo = None # For "manual" documents, if this is true, then toplevel headings are parts, # not chapters. #latex_use_parts = False # If true, show page references after internal links. #latex_show_pagerefs = False # If true, show URL addresses after external links. #latex_show_urls = False # Additional stuff for the LaTeX preamble. #latex_preamble = '' # Documents to append as an appendix to all manuals. #latex_appendices = [] # If false, no module index is generated. #latex_domain_indices = True # -- Options for manual page output -------------------------------------------- # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). man_pages = [ ('index', 'tox', u'tox Documentation', [u'holger krekel'], 1) ] # -- Options for Epub output --------------------------------------------------- # Bibliographic Dublin Core info. epub_title = u'tox' epub_author = u'holger krekel' epub_publisher = u'holger krekel' epub_copyright = u'2010, holger krekel' # The language of the text. It defaults to the language option # or en if the language is not set. #epub_language = '' # The scheme of the identifier. Typical schemes are ISBN or URL. #epub_scheme = '' # The unique identifier of the text. This can be a ISBN number # or the project homepage. #epub_identifier = '' # A unique identification for the text. #epub_uid = '' # HTML files that should be inserted before the pages created by sphinx. # The format is a list of tuples containing the path and title. #epub_pre_files = [] # HTML files shat should be inserted after the pages created by sphinx. # The format is a list of tuples containing the path and title. #epub_post_files = [] # A list of files that should not be packed into the epub file. #epub_exclude_files = [] # The depth of the table of contents in toc.ncx. #epub_tocdepth = 3 # Example configuration for intersphinx: refer to the Python standard library. intersphinx_mapping = {'http://docs.python.org/': None} def setup(app): #from sphinx.ext.autodoc import cut_lines #app.connect('autodoc-process-docstring', cut_lines(4, what=['module'])) app.add_description_unit('confval', 'confval', objname='configuration value', indextemplate='pair: %s; configuration value') linkcheck_timeout = 30 linkcheck_ignore = [r'http://holgerkrekel.net'] tox-2.3.1/doc/support.txt0000664000175000017500000000274012633511761014751 0ustar hpkhpk00000000000000 .. _support: support and contact channels ===================================== Getting in contact: * join the `Testing In Python (TIP) mailing list`_ for general and tox/test-tool interaction questions. * file a `report on the issue tracker`_ * hang out on the irc.freenode.net #pylib channel * `clone the mercurial repository`_ and submit patches * the `tetamap blog`_, `holger's twitter presence`_ or for private inquiries holger krekel at gmail. professional support ---------------------------- .. note:: Upcoming: `professional testing with pytest and tox <`http://www.python-academy.com/courses/specialtopics/python_course_testing.html>`_ , 24th-26th June 2013, Leipzig. If you are looking for on-site teaching or consulting support, contact holger at `merlinux.eu`_, an association of experienced well-known Python developers. .. _`Maciej Fijalkowski`: http://www.ohloh.net/accounts/fijal .. _`Benjamin Peterson`: http://www.ohloh.net/accounts/gutworth .. _`Testing In Python (TIP) mailing list`: http://lists.idyll.org/listinfo/testing-in-python .. _`holger's twitter presence`: http://twitter.com/hpk42 .. _`merlinux.eu`: http://merlinux.eu .. _`report on the issue tracker`: https://bitbucket.org/hpk42/tox/issues?status=new&status=open .. _`tetamap blog`: http://holgerkrekel.net .. _`tox-dev`: http://codespeak.net/mailman/listinfo/tox-dev .. _`tox-commit`: http://codespeak.net/mailman/listinfo/tox-commit .. _`clone the mercurial repository`: https://bitbucket.org/hpk42/tox tox-2.3.1/doc/Makefile0000664000175000017500000001140412633511761014131 0ustar hpkhpk00000000000000# Makefile for Sphinx documentation # # You can set these variables from the command line. SPHINXOPTS = SPHINXBUILD = sphinx-build PAPER = BUILDDIR = _build # Internal variables. PAPEROPT_a4 = -D latex_paper_size=a4 PAPEROPT_letter = -D latex_paper_size=letter ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . SITETARGET=$(shell ./_getdoctarget.py) .PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest help: @echo "Please use \`make ' where is one of" @echo " html to make standalone HTML files" @echo " dirhtml to make HTML files named index.html in directories" @echo " singlehtml to make a single large HTML file" @echo " pickle to make pickle files" @echo " json to make JSON files" @echo " htmlhelp to make HTML files and a HTML help project" @echo " qthelp to make HTML files and a qthelp project" @echo " devhelp to make HTML files and a Devhelp project" @echo " epub to make an epub" @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" @echo " latexpdf to make LaTeX files and run them through pdflatex" @echo " text to make text files" @echo " man to make manual pages" @echo " changes to make an overview of all changed/added/deprecated items" @echo " linkcheck to check all external links for integrity" @echo " doctest to run all doctests embedded in the documentation (if enabled)" clean: -rm -rf $(BUILDDIR)/* install: clean html @rsync -avz $(BUILDDIR)/html/ testrun.org:/www/testrun.org/tox/latest @rsync -avz $(BUILDDIR)/html/ testrun.org:/www/testrun.org/tox/$(SITETARGET) #dev #latexpdf #@scp $(BUILDDIR)/latex/*.pdf testrun.org:www-tox/latest html: $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html @echo @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." dirhtml: $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml @echo @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." singlehtml: $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml @echo @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." pickle: $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle @echo @echo "Build finished; now you can process the pickle files." json: $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json @echo @echo "Build finished; now you can process the JSON files." htmlhelp: $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp @echo @echo "Build finished; now you can run HTML Help Workshop with the" \ ".hhp project file in $(BUILDDIR)/htmlhelp." qthelp: $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp @echo @echo "Build finished; now you can run "qcollectiongenerator" with the" \ ".qhcp project file in $(BUILDDIR)/qthelp, like this:" @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/tox.qhcp" @echo "To view the help file:" @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/tox.qhc" devhelp: $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp @echo @echo "Build finished." @echo "To view the help file:" @echo "# mkdir -p $$HOME/.local/share/devhelp/tox" @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/tox" @echo "# devhelp" epub: $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub @echo @echo "Build finished. The epub file is in $(BUILDDIR)/epub." latex: $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex @echo @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." @echo "Run \`make' in that directory to run these through (pdf)latex" \ "(use \`make latexpdf' here to do that automatically)." latexpdf: $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex @echo "Running LaTeX files through pdflatex..." make -C $(BUILDDIR)/latex all-pdf @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." text: $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text @echo @echo "Build finished. The text files are in $(BUILDDIR)/text." man: $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man @echo @echo "Build finished. The manual pages are in $(BUILDDIR)/man." changes: $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes @echo @echo "The overview file is in $(BUILDDIR)/changes." linkcheck: $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck @echo @echo "Link check complete; look for any errors in the above output " \ "or in $(BUILDDIR)/linkcheck/output.txt." doctest: $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest @echo "Testing of doctests in the sources finished, look at the " \ "results in $(BUILDDIR)/doctest/output.txt." tox-2.3.1/doc/config-v2.txt0000664000175000017500000002253012633511761015026 0ustar hpkhpk00000000000000V2: new tox multi-dimensional, platform-specific configuration -------------------------------------------------------------------- .. note:: This is a draft document sketching a to-be-done implementation. It does not fully specify each change yet but should give a good idea of where things are heading. For feedback, mail the testing-in-python mailing list or open a pull request on https://bitbucket.org/hpk42/tox/src/84d8cf3c2a95fefd874f22c8b2d257e94365472f/doc/config-v2.txt?at=default **Abstract**: Adding multi-dimensional configuration, platform-specification and multiple installers to tox.ini. **Target audience**: Developers using or wanting to use tox for testing their python projects. Issues with current tox (1.4) configuration ------------------------------------------------ Tox is used as a tool for creating and managing virtualenv environments and running tests in them. As of tox-1.4 there are some issues frequently coming up with its configuration language: - there is no way to instruct tox to parametrize testenv specifications other than to list all combinations by specifying a ``[testenv:...]`` section for each combination. Examples of real life situations arising from this: * https://github.com/tomchristie/django-rest-framework/blob/b001a146d73348af18cfc4c943d87f2f389349c9/tox.ini * https://bitbucket.org/tabo/django-treebeard/src/93b579395a9c/tox.ini - there is no way to have platform specific settings other than to define specific testenvs and invoke tox with a platform-specific testenv list. - there is no way to specify the platforms against which a project shall successfully run. - tox always uses pip for installing packages currently. This has several issues: - no way to check if installing via easy_install works - no installs of packages with compiled c-extensions (win32 standard) Goals, resolving those issues ------------------------------------ This document discusses a possible solution for each of these issues, namely these goals: - allow to more easily define and run dependency/interpreter variants with testenvs - allow platform-specific settings - allow to specify platforms against which tests should run - allow to run installer-variants (easy_install or pip, xxx) - try to mimick/re-use bash-style syntax to ease learning curve. Example: Generating and selecting variants ---------------------------------------------- Suppose you want to test your package against python2.6, python2.7 and on the windows and linux platforms. Today you would have to write down 2*2 = 4 ``[testenv:*]`` sections and then instruct tox to run a specific list of environments on each platform. With tox-1.X you can directlys specify combinations:: # combination syntax gives 2 * 2 = 4 testenv names # envlist = {py26,py27}-{win,linux} [testenv] deps = pytest platform = win: windows linux: linux basepython = py26: python2.6 py27: python2.7 commands = py.test Let's go through this step by step:: envlist = {py26,py27}-{windows,linux} This is bash-style syntax and will create ``2*2=4`` environment names like this:: py26-windows py26-linux py27-windows py27-linux Our ``[testenv]`` uses a new templating style for the ``platform`` definition:: platform= windows: windows linux: linux These two conditional settings will lead to either ``windows`` or ``linux`` as the platform string. When the test environment is run, its platform string needs to be contained in the string returned from ``platform.platform()``. Otherwise the environment will be skipped. The next configuration item in the ``testenv`` section deals with the python interpreter:: basepython = py26: python2.6 py27: python2.7 This defines a python executable, depending on if ``py26`` or ``py27`` appears in the environment name. The last config item is simply the invocation of the test runner:: commands = py.test Nothing special here :) .. note:: Tox provides good defaults for platform and basepython settings, so the above ini-file can be further reduced:: [tox] envlist = {py26,py27}-{win,linux} [testenv] deps = pytest commands = py.test Voila, this multi-dimensional ``tox.ini`` configuration defines 2*2=4 environments. The new "platform" setting -------------------------------------- A testenv can define a new ``platform`` setting. If its value is not contained in the string obtained from calling ``sys.platform`` the environment will be skipped. Expanding the ``envlist`` setting ---------------------------------------------------------- The new ``envlist`` setting allows to use ``{}`` bash-style expressions. XXX explanation or pointer to bash-docs Templating based on environments names ------------------------------------------------- For a given environment name, all lines in a testenv section which start with "NAME: ..." will be checked for being part in the environment name. If they are part of it, the remainder will be the new line. If they are not part of it, the whole line will be left out. Parts of an environment name are obtained by ``-``-splitting it. Variant specification with [variant:VARNAME] Showing all expanded sections ------------------------------- To help with understanding how the variants will produce section values, you can ask tox to show their expansion with a new option:: $ tox -l [XXX output ommitted for now] Making sure your packages installs with easy_install ------------------------------------------------------ The new "installer" testenv setting allows to specify the tool for installation in a given test environment:: [testenv] installer = easy: easy_install pip: pip If you want to have your package installed with both easy_install and pip, you can list them in your envlist likes this:: [tox] envlist = py[26,27,32]-django[13,14]-[easy,pip] If no installer is specified, ``pip`` will be used. Default settings related to environments names/variants --------------------------------------------------------------- tox comes with predefined settings for certain variants, namely: * ``{easy,pip}`` use easy_install or pip respectively * ``{py24,py25,py26,py27,py31,py32,py33,py34,pypy19]`` use the respective pythonNN or PyPy interpreter * ``{win32,linux,darwin}`` defines the according ``platform``. You can use those in your “envlist” specification without the need to define them yourself. Use more bash-style syntax -------------------------------------- tox leverages bash-style syntax if you specify mintoxversion = 1.4: - $VARNAME or ${...} syntax instead of the older {} substitution. - XXX go through config.txt and see how it would need to be changed Transforming the examples: django-rest ------------------------------------------------ The original `django-rest-framework tox.ini `_ file has 159 lines and a lot of repetition, the new one would +have 20 lines and almost no repetition:: [tox] envlist = {py25,py26,py27}-{django12,django13}{,-example} [testenv] deps= coverage==3.4 unittest-xml-reporting==1.2 Pyyaml==3.10 django12: django==1.2.4 django13: django==1.3.1 # some more deps for running examples example: wsgiref==0.1.2 example: Pygments==1.4 example: httplib2==0.6.0 example: Markdown==2.0.3 commands = !example: python setup.py test example: python examples/runtests.py Note that ``{,-example}`` in the envlist denotes two values, an empty one and a ``example`` one. The empty value means that there are no specific settings and thus no need to define a variant name. Transforming the examples: django-treebeard ------------------------------------------------ Another `tox.ini `_ has 233 lines and runs tests against multiple Postgres and Mysql engines. It also performs backend-specific test commands, passing different command line options to the test script. With the new tox-1.X we not only can do the same with 32 non-repetive configuration lines but we also produce 36 specific testenvs with specific dependencies and test commands:: [tox] envlist = {py24,py25,py26,py27}-{django11,django12,django13}-{nodb,pg,mysql}, docs [testenv:docs] changedir = docs deps = Sphinx Django commands = make clean make html [testenv] deps= coverage pysqlite django11: django==1.1.4 django12: django==1.2.7 django13: django==1.3.1 django14: django==1.4 nodb: pysqlite pg: psycopg2 mysql: MySQL-python commands = nodb: {envpython} runtests.py {posargs} pg: {envpython} runtests.py {posargs} \ --DATABASE_ENGINE=postgresql_psycopg2 \ --DATABASE_USER=postgres {posargs} mysql: {envpython} runtests.py --DATABASE_ENGINE=mysql \ --DATABASE_USER=root {posargs} tox-2.3.1/CONTRIBUTORS0000664000175000017500000000076612633511761013615 0ustar hpkhpk00000000000000 contributions: Krisztian Fekete Marc Abramowitz Aleaxner Schepanovski Sridhar Ratnakumar Barry Warsaw Chris Rose Jannis Leidl Ronny Pfannschmidt Lukasz Balcerzak Philip Thiem Monty Taylor Bruno Oliveira Ionel Maries Cristian Anatoly techntonik Matt Jeffery Chris Jerdonek Ronald Evers Carl Meyer Anthon van der Neuth Matt Good Mattieu Agopian Asmund Grammeltwedt Ionel Maries Cristian Julian Krause Alexandre Conrad Morgan Fainberg Marc Schlaich Clark Boylan Eugene Yunak Mark Hirota Itxaka Serrano tox-2.3.1/CHANGELOG0000664000175000017500000005714312633511761013150 0ustar hpkhpk000000000000002.3.1 ----- - fix issue294: re-allow cross-section substitution for setenv. 2.3.0 ----- - DEPRECATE use of "indexservers" in tox.ini. It complicates the internal code and it is recommended to rather use the devpi system for managing indexes for pip. - fix issue285: make setenv processing fully lazy to fix regressions of tox-2.2.X and so that we can now have testenv attributes like "basepython" depend on environment variables that are set in a setenv section. Thanks Nelfin for some tests and initial work on a PR. - allow "#" in commands. This is slightly incompatible with commands sections that used a comment after a "\" line continuation. Thanks David Stanek for the PR. - fix issue289: fix build_sphinx target, thanks Barry Warsaw. - fix issue252: allow environment names with special characters. Thanks Julien Castets for initial PR and patience. - introduce experimental tox_testenv_create(venv, action) and tox_testenv_install_deps(venv, action) hooks to allow plugins to do additional work on creation or installing deps. These hooks are experimental mainly because of the involved "venv" and session objects whose current public API is not fully guranteed. - internal: push some optional object creation into tests because tox core doesn't need it. 2.2.1 ----- - fix bug where {envdir} substitution could not be used in setenv if that env value is then used in {basepython}. Thanks Florian Bruhin. 2.2.0 ----- - fix issue265 and add LD_LIBRARY_PATH to passenv on linux by default because otherwise the python interpreter might not start up in certain configurations (redhat software collections). Thanks David Riddle. - fix issue246: fix regression in config parsing by reordering such that {envbindir} can be used again in tox.ini. Thanks Olli Walsh. - fix issue99: the {env:...} substitution now properly uses environment settings from the ``setenv`` section. Thanks Itxaka Serrano. - fix issue281: make --force-deps work when urls are present in dependency configs. Thanks Glyph Lefkowitz for reporting. - fix issue174: add new ``ignore_outcome`` testenv attribute which can be set to True in which case it will produce a warning instead of an error on a failed testenv command outcome. Thanks Rebecka Gulliksson for the PR. - fix issue280: properly skip missing interpreter if {envsitepackagesdir} is present in commands. Thanks BB:ceridwenv 2.1.1 ---------- - fix platform skipping for detox - report skipped platforms as skips in the summary 2.1.0 ---------- - fix issue258, fix issue248, fix issue253: for non-test commands (installation, venv creation) we pass in the full invocation environment. - remove experimental --set-home option which was hardly used and hackily implemented (if people want home-directory isolation we should figure out a better way to do it, possibly through a plugin) - fix issue259: passenv is now a line-list which allows to intersperse comments. Thanks stefano-m. - allow envlist to be a multi-line list, to intersperse comments and have long envlist settings split more naturally. Thanks Andre Caron. - introduce a TOX_TESTENV_PASSENV setting which is honored when constructing the set of environment variables for test environments. Thanks Marc Abramowitz for pushing in this direction. 2.0.2 ---------- - fix issue247: tox now passes the LANG variable from the tox invocation environment to the test environment by default. - add SYSTEMDRIVE into default passenv on windows to allow pip6 to work. Thanks Michael Krause. 2.0.1 ----------- - fix wheel packaging to properly require argparse on py26. 2.0.0 ----------- - (new) introduce environment variable isolation: tox now only passes the PATH and PIP_INDEX_URL variable from the tox invocation environment to the test environment and on Windows also ``SYSTEMROOT``, ``PATHEXT``, ``TEMP`` and ``TMP`` whereas on unix additionally ``TMPDIR`` is passed. If you need to pass through further environment variables you can use the new ``passenv`` setting, a space-separated list of environment variable names. Each name can make use of fnmatch-style glob patterns. All environment variables which exist in the tox-invocation environment will be copied to the test environment. - a new ``--help-ini`` option shows all possible testenv settings and their defaults. - (new) introduce a way to specify on which platform a testenvironment is to execute: the new per-venv "platform" setting allows to specify a regular expression which is matched against sys.platform. If platform is set and doesn't match the platform spec in the test environment the test environment is ignored, no setup or tests are attempted. - (new) add per-venv "ignore_errors" setting, which defaults to False. If ``True``, a non-zero exit code from one command will be ignored and further commands will be executed (which was the default behavior in tox < 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. - fix issue120: allow substitution for the commands section. Thanks Volodymyr Vitvitski. - fix issue235: fix AttributeError with --installpkg. Thanks Volodymyr Vitvitski. - tox has now somewhat pep8 clean code, thanks to Volodymyr Vitvitski. - fix issue240: allow to specify empty argument list without it being rewritten to ".". Thanks Daniel Hahler. - introduce experimental (not much documented yet) plugin system based on pytest's externalized "pluggy" system. See tox/hookspecs.py for the current hooks. - introduce parser.add_testenv_attribute() to register an ini-variable 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. - DEPRECATE distshare in documentation 1.9.2 ----------- - backout ability that --force-deps substitutes name/versions in requirement files due to various issues. This fixes issue228, fixes issue230, fixes issue231 which popped up with 1.9.1. 1.9.1 ----------- - use a file instead of a pipe for command output in "--result-json". Fixes some termination issues with python2.6. - allow --force-deps to override dependencies in "-r" requirements files. Thanks Sontek for the PR. - fix issue227: use "-m virtualenv" instead of "-mvirtualenv" to make it work with pyrun. Thanks Marc-Andre Lemburg. 1.9.0 ----------- - fix issue193: Remove ``--pre`` from the default ``install_command``; by default tox will now only install final releases from PyPI for unpinned dependencies. Use ``pip_pre = true`` in a testenv or the ``--pre`` command-line option to restore the previous behavior. - fix issue199: fill resultlog structure ahead of virtualenv creation - refine determination if we run from Jenkins, thanks Borge Lanes. - echo output to stdout when ``--report-json`` is used - fix issue11: add a ``skip_install`` per-testenv setting which prevents the installation of a package. Thanks Julian Krause. - fix issue124: ignore command exit codes; when a command has a "-" prefix, tox will ignore the exit code of that command - fix issue198: fix broken envlist settings, e.g. {py26,py27}{-lint,} - fix issue191: lessen factor-use checks 1.8.1 ----------- - fix issue190: allow setenv to be empty. - allow escaping curly braces with "\". Thanks Marc Abramowitz for the PR. - allow "." names in environment names such that "py27-django1.7" is a valid environment name. Thanks Alex Gaynor and Alex Schepanovski. - report subprocess exit code when execution fails. Thanks Marius Gedminas. 1.8.0 ----------- - new multi-dimensional configuration support. Many thanks to Alexander Schepanovski for the complete PR with docs. And to Mike Bayer and others for testing and feedback. - fix issue148: remove "__PYVENV_LAUNCHER__" from os.environ when starting subprocesses. Thanks Steven Myint. - fix issue152: set VIRTUAL_ENV when running test commands, thanks Florian Ludwig. - better report if we can't get version_info from an interpreter executable. Thanks Floris Bruynooghe. 1.7.2 ----------- - fix issue150: parse {posargs} more like we used to do it pre 1.7.0. The 1.7.0 behaviour broke a lot of OpenStack projects. See PR85 and the issue discussions for (far) more details, hopefully resulting in a more refined behaviour in the 1.8 series. And thanks to Clark Boylan for the PR. - fix issue59: add a config variable ``skip-missing-interpreters`` as well as command line option ``--skip-missing-interpreters`` which won't fail the build if Python interpreters listed in tox.ini are missing. Thanks Alexandre Conrad for PR104. - fix issue164: better traceback info in case of failing test commands. Thanks Marc Abramowitz for PR92. - support optional env variable substitution, thanks Morgan Fainberg for PR86. - limit python hashseed to 1024 on Windows to prevent possible memory errors. Thanks March Schlaich for the PR90. 1.7.1 --------- - fix issue162: don't list python 2.5 as compatibiliy/supported - fix issue158 and fix issue155: windows/virtualenv properly works now: call virtualenv through "python -m virtualenv" with the same interpreter which invoked tox. Thanks Chris Withers, Ionel Maries Cristian. 1.7.0 --------- - don't lookup "pip-script" anymore but rather just "pip" on windows as this is a pip implementation detail and changed with pip-1.5. It might mean that tox-1.7 is not able to install a different pip version into a virtualenv anymore. - drop Python2.5 compatibility because it became too hard due to the setuptools-2.0 dropping support. tox now has no support for creating python2.5 based environments anymore and all internal special-handling has been removed. - merged PR81: new option --force-dep which allows to override tox.ini specified dependencies in setuptools-style. For example "--force-dep 'django<1.6'" will make sure that any environment using "django" as a dependency will get the latest 1.5 release. Thanks Bruno Oliveria for the complete PR. - merged PR125: tox now sets "PYTHONHASHSEED" to a random value and offers a "--hashseed" option to repeat a test run with a specific seed. You can also use --hashsheed=noset to instruct tox to leave the value alone. Thanks Chris Jerdonek for all the work behind this. - fix issue132: removing zip_safe setting (so it defaults to false) to allow installation of tox via easy_install/eggs. Thanks Jenisys. - fix issue126: depend on virtualenv>=1.11.2 so that we can rely (hopefully) on a pip version which supports --pre. (tox by default uses to --pre). also merged in PR84 so that we now call "virtualenv" directly instead of looking up interpreters. Thanks Ionel Maries Cristian. This also fixes issue140. - fix issue130: you can now set install_command=easy_install {opts} {packages} and expect it to work for repeated tox runs (previously it only worked when always recreating). Thanks jenisys for precise reporting. - fix issue129: tox now uses Popen(..., universal_newlines=True) to force creation of unicode stdout/stderr streams. fixes a problem on specific platform configs when creating virtualenvs with Python3.3. Thanks Jorgen Schäfer or investigation and solution sketch. - fix issue128: enable full substitution in install_command, thanks for the PR to Ronald Evers - rework and simplify "commands" parsing and in particular posargs substitutions to avoid various win32/posix related quoting issues. - make sure that the --installpkg option trumps any usedevelop settings in tox.ini or - introduce --no-network to tox's own test suite to skip tests requiring networks - introduce --sitepackages to force sitepackages=True in all environments. - fix issue105 -- don't depend on an existing HOME directory from tox tests. 1.6.1 ----- - fix issue119: {envsitepackagesdir} is now correctly computed and has a better test to prevent regression. - fix issue116: make 1.6 introduced behaviour of changing to a per-env HOME directory during install activities dependent on "--set-home" for now. Should re-establish the old behaviour when no option is given. - fix issue118: correctly have two tests use realpath(). Thanks Barry Warsaw. - fix test runs on environments without a home directory (in this case we use toxinidir as the homedir) - fix issue117: python2.5 fix: don't use ``--insecure`` option because its very existence depends on presence of "ssl". If you want to support python2.5/pip1.3.1 based test environments you need to install ssl and/or use PIP_INSECURE=1 through ``setenv``. section. - fix issue102: change to {toxinidir} when installing dependencies. this allows to use relative path like in "-rrequirements.txt". 1.6.0 ----------------- - fix issue35: add new EXPERIMENTAL "install_command" testenv-option to configure the installation command with options for dep/pkg install. Thanks Carl Meyer for the PR and docs. - fix issue91: python2.5 support by vendoring the virtualenv-1.9.1 script and forcing pip<1.4. Also the default [py25] environment modifies the default installer_command (new config option) to use pip without the "--pre" option which was introduced with pip-1.4 and is now required if you want to install non-stable releases. (tox defaults to install with "--pre" everywhere). - during installation of dependencies HOME is now set to a pseudo location ({envtmpdir}/pseudo-home). If an index url was specified a .pydistutils.cfg file will be written with an index_url setting so that packages defining ``setup_requires`` dependencies will not silently use your HOME-directory settings or https://pypi.python.org. - fix issue1: empty setup files are properly detected, thanks Anthon van der Neuth - remove toxbootstrap.py for now because it is broken. - fix issue109 and fix issue111: multiple "-e" options are now combined (previously the last one would win). Thanks Anthon van der Neut. - add --result-json option to write out detailed per-venv information into a json report file to be used by upstream tools. - add new config options ``usedevelop`` and ``skipsdist`` as well as a command line option ``--develop`` to install the package-under-test in develop mode. thanks Monty Tailor for the PR. - always unset PYTHONDONTWRITEBYTE because newer setuptools doesn't like it - if a HOMEDIR cannot be determined, use the toxinidir. - refactor interpreter information detection to live in new tox/interpreters.py file, tests in tests/test_interpreters.py. 1.5.0 ----------------- - fix issue104: use setuptools by default, instead of distribute, now that setuptools has distribute merged. - make sure test commands are searched first in the virtualenv - re-fix issue2 - add whitelist_externals to be used in ``[testenv*]`` sections, allowing to avoid warnings for commands such as ``make``, used from the commands value. - fix issue97 - allow substitutions to reference from other sections (thanks Krisztian Fekete) - fix issue92 - fix {envsitepackagesdir} to actually work again - show (test) command that is being executed, thanks Lukasz Balcerzak - re-license tox to MIT license - depend on virtualenv-1.9.1 - rename README.txt to README.rst to make bitbucket happier 1.4.3 ----------------- - use pip-script.py instead of pip.exe on win32 to avoid the lock exe file on execution issue (thanks Philip Thiem) - introduce -l|--listenv option to list configured environments (thanks Lukasz Balcerzak) - fix downloadcache determination to work according to docs: Only make pip use a download cache if PIP_DOWNLOAD_CACHE or a downloadcache=PATH testenv setting is present. (The ENV setting takes precedence) - fix issue84 - pypy on windows creates a bin not a scripts venv directory (thanks Lukasz Balcerzak) - experimentally introduce --installpkg=PATH option to install a package rather than create/install an sdist package. This will still require and use tox.ini and tests from the current working dir (and not from the remote package). - substitute {envsitepackagesdir} with the package installation directory (closes #72) (thanks g2p) - issue #70 remove PYTHONDONTWRITEBYTECODE workaround now that virtualenv behaves properly (thanks g2p) - merged tox-quickstart command, contributed by Marc Abramowitz, which generates a default tox.ini after asking a few questions - fix #48 - win32 detection of pypy and other interpreters that are on PATH (thanks Gustavo Picon) - fix grouping of index servers, it is now done by name instead of indexserver url, allowing to use it to separate dependencies into groups even if using the same default indexserver. - look for "tox.ini" files in parent dirs of current dir (closes #34) - the "py" environment now by default uses the current interpreter (sys.executable) make tox' own setup.py test execute tests with it (closes #46) - change tests to not rely on os.path.expanduser (closes #60), also make mock session return args[1:] for more precise checking (closes #61) thanks to Barry Warsaw for both. 1.4.2 ----------------- - fix some tests which fail if /tmp is a symlink to some other place - "python setup.py test" now runs tox tests via tox :) also added an example on how to do it for your project. 1.4.1 ----------------- - fix issue41 better quoting on windows - you can now use "<" and ">" in deps specifications, thanks Chris Withers for reporting 1.4 ----------------- - fix issue26 - no warnings on absolute or relative specified paths for commands - fix issue33 - commentchars are ignored in key-value settings allowing for specifying commands like: python -c "import sys ; print sys" which would formerly raise irritating errors because the ";" was considered a comment - tweak and improve reporting - refactor reporting and virtualenv manipulation to be more accessible from 3rd party tools - support value substitution from other sections with the {[section]key} syntax - fix issue29 - correctly point to pytest explanation for importing modules fully qualified - fix issue32 - use --system-site-packages and don't pass --no-site-packages - add python3.3 to the default env list, so early adopters can test - drop python2.4 support (you can still have your tests run on - fix the links/checkout howtos in the docs python-2.4, just tox itself requires 2.5 or higher. 1.3 ----------------- - fix: allow to specify wildcard filesystem paths when specifying dependencies such that tox searches for the highest version - fix issue issue21: clear PIP_REQUIRES_VIRTUALENV which avoids pip installing to the wrong environment, thanks to bb's streeter - make the install step honour a testenv's setenv setting (thanks Ralf Schmitt) 1.2 ----------------- - remove the virtualenv.py that was distributed with tox and depend on >=virtualenv-1.6.4 (possible now since the latter fixes a few bugs that the inlining tried to work around) - fix issue10: work around UnicodeDecodeError when invoking pip (thanks Marc Abramowitz) - fix a problem with parsing {posargs} in tox commands (spotted by goodwill) - fix the warning check for commands to be installed in testenvironment (thanks Michael Foord for reporting) 1.1 ----------------- - fix issue5 - don't require argparse for python versions that have it - fix issue6 - recreate virtualenv if installing dependencies failed - fix issue3 - fix example on frontpage - fix issue2 - warn if a test command does not come from the test environment - fixed/enhanced: except for initial install always call "-U --no-deps" for installing the sdist package to ensure that a package gets upgraded even if its version number did not change. (reported on TIP mailing list and IRC) - inline virtualenv.py (1.6.1) script to avoid a number of issues, particularly failing to install python3 environments from a python2 virtualenv installation. - rework and enhance docs for display on readthedocs.org 1.0 ----------------- - move repository and toxbootstrap links to http://bitbucket.org/hpk42/tox - fix issue7: introduce a "minversion" directive such that tox bails out if it does not have the correct version. - fix issue24: introduce a way to set environment variables for for test commands (thanks Chris Rose) - fix issue22: require virtualenv-1.6.1, obsoleting virtualenv5 (thanks Jannis Leidel) and making things work with pypy-1.5 and python3 more seamlessly - toxbootstrap.py (used by jenkins build slaves) now follows the latest release of virtualenv - fix issue20: document format of URLs for specifying dependencies - fix issue19: substitute Hudson for Jenkins everywhere following the renaming of the project. NOTE: if you used the special [tox:hudson] section it will now need to be named [tox:jenkins]. - fix issue 23 / apply some ReST fixes - change the positional argument specifier to use {posargs:} syntax and fix issues #15 and #10 by refining the argument parsing method (Chris Rose) - remove use of inipkg lazy importing logic - the namespace/imports are anyway very small with tox. - fix a fspath related assertion to work with debian installs which uses symlinks - show path of the underlying virtualenv invocation and bootstrap virtualenv.py into a working subdir - added a CONTRIBUTORS file 0.9 ----------------- - fix pip-installation mixups by always unsetting PIP_RESPECT_VIRTUALENV (thanks Armin Ronacher) - issue1: Add a toxbootstrap.py script for tox, thanks to Sridhar Ratnakumar - added support for working with different and multiple PyPI indexservers. - new option: -r|--recreate to force recreation of virtualenv - depend on py>=1.4.0 which does not contain or install the py.test anymore which is now a separate distribution "pytest". - show logfile content if there is an error (makes CI output more readable) 0.8 ----------------- - work around a virtualenv limitation which crashes if PYTHONDONTWRITEBYTECODE is set. - run pip/easy installs from the environment log directory, avoids naming clashes between env names and dependencies (thanks ronny) - require a more recent version of py lib - refactor and refine config detection to work from a single file and to detect the case where a python installation overwrote an old one and resulted in a new executable. This invalidates the existing virtualenvironment now. - change all internal source to strip trailing whitespaces 0.7 ----------------- - use virtualenv5 (my own fork of virtualenv3) for now to create python3 environments, fixes a couple of issues and makes tox more likely to work with Python3 (on non-windows environments) - add ``sitepackages`` option for testenv sections so that environments can be created with access to globals (default is not to have access, i.e. create environments with ``--no-site-packages``. - addressing issue4: always prepend venv-path to PATH variable when calling subprocesses - fix issue2: exit with proper non-zero return code if there were errors or test failures. - added unittest2 examples contributed by Michael Foord - only allow 'True' or 'False' for boolean config values (lowercase / uppercase is irrelevant) - recreate virtualenv on changed configurations 0.6 ----------------- - fix OSX related bugs that could cause the caller's environment to get screwed (sorry). tox was using the same file as virtualenv for tracking the Python executable dependency and there also was confusion wrt links. this should be fixed now. - fix long description, thanks Michael Foord 0.5 ----------------- - initial release tox-2.3.1/setup.py0000664000175000017500000000557012633511761013445 0ustar hpkhpk00000000000000import sys import setuptools from setuptools.command.test import test as TestCommand class Tox(TestCommand): def finalize_options(self): TestCommand.finalize_options(self) self.test_args = ["-v", "-epy"] self.test_suite = True def run_tests(self): #import here, cause outside the eggs aren't loaded import tox tox.cmdline(self.test_args) def has_environment_marker_support(): """ Tests that setuptools has support for PEP-426 environment marker support. The first known release to support it is 0.7 (and the earliest on PyPI seems to be 0.7.2 so we're using that), see: http://pythonhosted.org/setuptools/history.html#id142 References: * https://wheel.readthedocs.org/en/latest/index.html#defining-conditional-dependencies * https://www.python.org/dev/peps/pep-0426/#environment-markers """ import pkg_resources try: return pkg_resources.parse_version(setuptools.__version__) >= pkg_resources.parse_version('0.7.2') except Exception as exc: sys.stderr.write("Could not test setuptool's version: %s\n" % exc) return False def main(): version = sys.version_info[:2] install_requires = ['virtualenv>=1.11.2', 'py>=1.4.17', 'pluggy>=0.3.0,<0.4.0'] extras_require = {} if has_environment_marker_support(): extras_require[':python_version=="2.6"'] = ['argparse'] else: if version < (2, 7): install_requires += ['argparse'] setuptools.setup( name='tox', description='virtualenv-based automation of test activities', long_description=open("README.rst").read(), url='http://tox.testrun.org/', version='2.3.1', license='http://opensource.org/licenses/MIT', platforms=['unix', 'linux', 'osx', 'cygwin', 'win32'], author='holger krekel', author_email='holger@merlinux.eu', packages=['tox'], entry_points={'console_scripts': 'tox=tox:cmdline\ntox-quickstart=tox._quickstart:main'}, # we use a public tox version to test, see tox.ini's testenv # "deps" definition for the required dependencies tests_require=['tox'], cmdclass={"test": Tox}, install_requires=install_requires, extras_require=extras_require, classifiers=[ 'Development Status :: 5 - Production/Stable', 'Intended Audience :: Developers', 'License :: OSI Approved :: MIT License', 'Operating System :: POSIX', 'Operating System :: Microsoft :: Windows', 'Operating System :: MacOS :: MacOS X', 'Topic :: Software Development :: Testing', 'Topic :: Software Development :: Libraries', 'Topic :: Utilities', 'Programming Language :: Python', 'Programming Language :: Python :: 3'], ) if __name__ == '__main__': main() tox-2.3.1/tests/0000775000175000017500000000000012633511761013066 5ustar hpkhpk00000000000000tox-2.3.1/tests/test_z_cmdline.py0000664000175000017500000005263412633511761016455 0ustar hpkhpk00000000000000import tox import py import pytest from tox._pytestplugin import ReportExpectMock try: import json except ImportError: import simplejson as json pytest_plugins = "pytester" from tox.session import Session from tox.config import parseconfig def test_report_protocol(newconfig): config = newconfig([], """ [testenv:mypython] deps=xy """) class Popen: def __init__(self, *args, **kwargs): pass def communicate(self): return "", "" def wait(self): pass session = Session(config, popen=Popen, Report=ReportExpectMock) report = session.report report.expect("using") venv = session.getvenv("mypython") action = session.newaction(venv, "update") venv.update(action) report.expect("logpopen") def test__resolve_pkg(tmpdir, mocksession): distshare = tmpdir.join("distshare") spec = distshare.join("pkg123-*") py.test.raises(tox.exception.MissingDirectory, 'mocksession._resolve_pkg(spec)') distshare.ensure(dir=1) py.test.raises(tox.exception.MissingDependency, 'mocksession._resolve_pkg(spec)') distshare.ensure("pkg123-1.3.5.zip") p = distshare.ensure("pkg123-1.4.5.zip") mocksession.report.clear() result = mocksession._resolve_pkg(spec) assert result == p mocksession.report.expect("info", "determin*pkg123*") distshare.ensure("pkg123-1.4.7dev.zip") mocksession._clearmocks() result = mocksession._resolve_pkg(spec) mocksession.report.expect("warning", "*1.4.7*") assert result == p mocksession._clearmocks() distshare.ensure("pkg123-1.4.5a1.tar.gz") result = mocksession._resolve_pkg(spec) assert result == p def test__resolve_pkg_doubledash(tmpdir, mocksession): distshare = tmpdir.join("distshare") p = distshare.ensure("pkg-mine-1.3.0.zip") res = mocksession._resolve_pkg(distshare.join("pkg-mine*")) assert res == p distshare.ensure("pkg-mine-1.3.0a1.zip") res = mocksession._resolve_pkg(distshare.join("pkg-mine*")) assert res == p class TestSession: def test_make_sdist(self, initproj): initproj("example123-0.5", filedefs={ 'tests': {'test_hello.py': "def test_hello(): pass"}, 'tox.ini': ''' ''' }) config = parseconfig([]) session = Session(config) sdist = session.get_installpkg_path() assert sdist.check() assert sdist.ext == ".zip" assert sdist == config.distdir.join(sdist.basename) sdist2 = session.get_installpkg_path() assert sdist2 == sdist sdist.write("hello") assert sdist.stat().size < 10 sdist_new = Session(config).get_installpkg_path() assert sdist_new == sdist assert sdist_new.stat().size > 10 def test_make_sdist_distshare(self, tmpdir, initproj): distshare = tmpdir.join("distshare") initproj("example123-0.6", filedefs={ 'tests': {'test_hello.py': "def test_hello(): pass"}, 'tox.ini': ''' [tox] distshare=%s ''' % distshare }) config = parseconfig([]) session = Session(config) sdist = session.get_installpkg_path() assert sdist.check() assert sdist.ext == ".zip" assert sdist == config.distdir.join(sdist.basename) sdist_share = config.distshare.join(sdist.basename) assert sdist_share.check() assert sdist_share.read("rb") == sdist.read("rb"), (sdist_share, sdist) def test_log_pcall(self, mocksession): mocksession.config.logdir.ensure(dir=1) assert not mocksession.config.logdir.listdir() action = mocksession.newaction(None, "something") action.popen(["echo", ]) match = mocksession.report.getnext("logpopen") assert match[1].outpath.relto(mocksession.config.logdir) assert match[1].shell is False def test_summary_status(self, initproj, capfd): initproj("logexample123-0.5", filedefs={ 'tests': {'test_hello.py': "def test_hello(): pass"}, 'tox.ini': ''' [testenv:hello] [testenv:world] ''' }) config = parseconfig([]) session = Session(config) envs = session.venvlist assert len(envs) == 2 env1, env2 = envs env1.status = "FAIL XYZ" assert env1.status env2.status = 0 assert not env2.status session._summary() out, err = capfd.readouterr() exp = "%s: FAIL XYZ" % env1.envconfig.envname assert exp in out exp = "%s: commands succeeded" % env2.envconfig.envname assert exp in out def test_getvenv(self, initproj, capfd): initproj("logexample123-0.5", filedefs={ 'tests': {'test_hello.py': "def test_hello(): pass"}, 'tox.ini': ''' [testenv:hello] [testenv:world] ''' }) config = parseconfig([]) session = Session(config) venv1 = session.getvenv("hello") venv2 = session.getvenv("hello") assert venv1 is venv2 venv1 = session.getvenv("world") venv2 = session.getvenv("world") assert venv1 is venv2 pytest.raises(LookupError, lambda: session.getvenv("qwe")) # not sure we want this option ATM def XXX_test_package(cmd, initproj): initproj("myproj-0.6", filedefs={ 'tests': {'test_hello.py': "def test_hello(): pass"}, 'MANIFEST.in': """ include doc include myproj """, 'tox.ini': '' }) result = cmd.run("tox", "package") assert not result.ret result.stdout.fnmatch_lines([ "*created sdist package at*", ]) def test_minversion(cmd, initproj): initproj("interp123-0.5", filedefs={ 'tests': {'test_hello.py': "def test_hello(): pass"}, 'tox.ini': ''' [tox] minversion = 6.0 ''' }) result = cmd.run("tox", "-v") result.stdout.fnmatch_lines([ "*ERROR*tox version is * required is at least 6.0*" ]) assert result.ret def test_run_custom_install_command_error(cmd, initproj): initproj("interp123-0.5", filedefs={ 'tox.ini': ''' [testenv] install_command=./tox.ini {opts} {packages} ''' }) result = cmd.run("tox") result.stdout.fnmatch_lines([ "ERROR: invocation failed (errno *), args: ['*/tox.ini*", ]) assert result.ret def test_unknown_interpreter_and_env(cmd, initproj): initproj("interp123-0.5", filedefs={ 'tests': {'test_hello.py': "def test_hello(): pass"}, 'tox.ini': ''' [testenv:python] basepython=xyz_unknown_interpreter [testenv] changedir=tests ''' }) result = cmd.run("tox") assert result.ret result.stdout.fnmatch_lines([ "*ERROR*InterpreterNotFound*xyz_unknown_interpreter*", ]) result = cmd.run("tox", "-exyz") assert result.ret result.stdout.fnmatch_lines([ "*ERROR*unknown*", ]) def test_unknown_interpreter(cmd, initproj): initproj("interp123-0.5", filedefs={ 'tests': {'test_hello.py': "def test_hello(): pass"}, 'tox.ini': ''' [testenv:python] basepython=xyz_unknown_interpreter [testenv] changedir=tests ''' }) result = cmd.run("tox") assert result.ret result.stdout.fnmatch_lines([ "*ERROR*InterpreterNotFound*xyz_unknown_interpreter*", ]) def test_skip_platform_mismatch(cmd, initproj): initproj("interp123-0.5", filedefs={ 'tests': {'test_hello.py': "def test_hello(): pass"}, 'tox.ini': ''' [testenv] changedir=tests platform=x123 ''' }) result = cmd.run("tox") assert not result.ret result.stdout.fnmatch_lines(""" SKIPPED*platform mismatch* """) def test_skip_unknown_interpreter(cmd, initproj): initproj("interp123-0.5", filedefs={ 'tests': {'test_hello.py': "def test_hello(): pass"}, 'tox.ini': ''' [testenv:python] basepython=xyz_unknown_interpreter [testenv] changedir=tests ''' }) result = cmd.run("tox", "--skip-missing-interpreters") assert not result.ret result.stdout.fnmatch_lines([ "*SKIPPED*InterpreterNotFound*xyz_unknown_interpreter*", ]) def test_unknown_dep(cmd, initproj): initproj("dep123-0.7", filedefs={ 'tests': {'test_hello.py': "def test_hello(): pass"}, 'tox.ini': ''' [testenv] deps=qweqwe123 changedir=tests ''' }) result = cmd.run("tox", ) assert result.ret result.stdout.fnmatch_lines([ "*ERROR*could not install*qweqwe123*", ]) def test_venv_special_chars_issue252(cmd, initproj): initproj("pkg123-0.7", filedefs={ 'tests': {'test_hello.py': "def test_hello(): pass"}, 'tox.ini': ''' [tox] envlist = special&&1 [testenv:special&&1] changedir=tests ''' }) result = cmd.run("tox", ) assert result.ret == 0 result.stdout.fnmatch_lines([ "*installed*pkg123*" ]) def test_unknown_environment(cmd, initproj): initproj("env123-0.7", filedefs={ 'tox.ini': '' }) result = cmd.run("tox", "-e", "qpwoei") assert result.ret result.stdout.fnmatch_lines([ "*ERROR*unknown*environment*qpwoei*", ]) def test_skip_sdist(cmd, initproj): initproj("pkg123-0.7", filedefs={ 'tests': {'test_hello.py': "def test_hello(): pass"}, 'setup.py': """ syntax error """, 'tox.ini': ''' [tox] skipsdist=True [testenv] commands=python -c "print('done')" ''' }) result = cmd.run("tox", ) assert result.ret == 0 def test_minimal_setup_py_empty(cmd, initproj): initproj("pkg123-0.7", filedefs={ 'tests': {'test_hello.py': "def test_hello(): pass"}, 'setup.py': """ """, 'tox.ini': '' }) result = cmd.run("tox", ) assert result.ret == 1 result.stdout.fnmatch_lines([ "*ERROR*empty*", ]) def test_minimal_setup_py_comment_only(cmd, initproj): initproj("pkg123-0.7", filedefs={ 'tests': {'test_hello.py': "def test_hello(): pass"}, 'setup.py': """\n# some comment """, 'tox.ini': '' }) result = cmd.run("tox", ) assert result.ret == 1 result.stdout.fnmatch_lines([ "*ERROR*empty*", ]) def test_minimal_setup_py_non_functional(cmd, initproj): initproj("pkg123-0.7", filedefs={ 'tests': {'test_hello.py': "def test_hello(): pass"}, 'setup.py': """ import sys """, 'tox.ini': '' }) result = cmd.run("tox", ) assert result.ret == 1 result.stdout.fnmatch_lines([ "*ERROR*check setup.py*", ]) def test_sdist_fails(cmd, initproj): initproj("pkg123-0.7", filedefs={ 'tests': {'test_hello.py': "def test_hello(): pass"}, 'setup.py': """ syntax error """, 'tox.ini': '', }) result = cmd.run("tox", ) assert result.ret result.stdout.fnmatch_lines([ "*FAIL*could not package project*", ]) def test_package_install_fails(cmd, initproj): initproj("pkg123-0.7", filedefs={ 'tests': {'test_hello.py': "def test_hello(): pass"}, 'setup.py': """ from setuptools import setup setup( name='pkg123', description='pkg123 project', version='0.7', license='MIT', platforms=['unix', 'win32'], packages=['pkg123',], install_requires=['qweqwe123'], ) """, 'tox.ini': '', }) result = cmd.run("tox", ) assert result.ret result.stdout.fnmatch_lines([ "*InvocationError*", ]) class TestToxRun: @pytest.fixture def example123(self, initproj): initproj("example123-0.5", filedefs={ 'tests': { 'test_hello.py': """ def test_hello(pytestconfig): pass """, }, 'tox.ini': ''' [testenv] changedir=tests commands= py.test --basetemp={envtmpdir} \ --junitxml=junit-{envname}.xml deps=pytest ''' }) def test_toxuone_env(self, cmd, example123): result = cmd.run("tox") assert not result.ret result.stdout.fnmatch_lines([ "*junit-python.xml*", "*1 passed*", ]) result = cmd.run("tox", "-epython", ) assert not result.ret result.stdout.fnmatch_lines([ "*1 passed*", "*summary*", "*python: commands succeeded" ]) def test_different_config_cwd(self, cmd, example123, monkeypatch): # see that things work with a different CWD monkeypatch.chdir(cmd.tmpdir) result = cmd.run("tox", "-c", "example123/tox.ini") assert not result.ret result.stdout.fnmatch_lines([ "*1 passed*", "*summary*", "*python: commands succeeded" ]) def test_json(self, cmd, example123): # see that tests can also fail and retcode is correct testfile = py.path.local("tests").join("test_hello.py") assert testfile.check() testfile.write("def test_fail(): assert 0") jsonpath = cmd.tmpdir.join("res.json") result = cmd.run("tox", "--result-json", jsonpath) assert result.ret == 1 data = json.load(jsonpath.open("r")) verify_json_report_format(data) result.stdout.fnmatch_lines([ "*1 failed*", "*summary*", "*python: *failed*", ]) def test_develop(initproj, cmd): initproj("example123", filedefs={'tox.ini': """ """}) result = cmd.run("tox", "-vv", "--develop") assert not result.ret assert "sdist-make" not in result.stdout.str() def test_usedevelop(initproj, cmd): initproj("example123", filedefs={'tox.ini': """ [testenv] usedevelop=True """}) result = cmd.run("tox", "-vv") assert not result.ret assert "sdist-make" not in result.stdout.str() def test_usedevelop_mixed(initproj, cmd): initproj("example123", filedefs={'tox.ini': """ [testenv:devenv] usedevelop=True [testenv:nondev] usedevelop=False """}) # running only 'devenv' should not do sdist result = cmd.run("tox", "-vv", "-e", "devenv") assert not result.ret assert "sdist-make" not in result.stdout.str() # running all envs should do sdist result = cmd.run("tox", "-vv") assert not result.ret assert "sdist-make" in result.stdout.str() def test_test_usedevelop(cmd, initproj): initproj("example123-0.5", filedefs={ 'tests': { 'test_hello.py': """ def test_hello(pytestconfig): pass """, }, 'tox.ini': ''' [testenv] usedevelop=True changedir=tests commands= py.test --basetemp={envtmpdir} --junitxml=junit-{envname}.xml [] deps=pytest ''' }) result = cmd.run("tox", "-v") assert not result.ret result.stdout.fnmatch_lines([ "*junit-python.xml*", "*1 passed*", ]) assert "sdist-make" not in result.stdout.str() result = cmd.run("tox", "-epython", ) assert not result.ret result.stdout.fnmatch_lines([ "*1 passed*", "*summary*", "*python: commands succeeded" ]) # see that things work with a different CWD old = cmd.tmpdir.chdir() result = cmd.run("tox", "-c", "example123/tox.ini") assert not result.ret result.stdout.fnmatch_lines([ "*1 passed*", "*summary*", "*python: commands succeeded" ]) old.chdir() # see that tests can also fail and retcode is correct testfile = py.path.local("tests").join("test_hello.py") assert testfile.check() testfile.write("def test_fail(): assert 0") result = cmd.run("tox", ) assert result.ret result.stdout.fnmatch_lines([ "*1 failed*", "*summary*", "*python: *failed*", ]) def test_test_piphelp(initproj, cmd): initproj("example123", filedefs={'tox.ini': """ # content of: tox.ini [testenv] commands=pip -h [testenv:py26] basepython=python [testenv:py27] basepython=python """}) result = cmd.run("tox") assert not result.ret def test_notest(initproj, cmd): initproj("example123", filedefs={'tox.ini': """ # content of: tox.ini [testenv:py26] basepython=python """}) result = cmd.run("tox", "-v", "--notest") assert not result.ret result.stdout.fnmatch_lines([ "*summary*", "*py26*skipped tests*", ]) result = cmd.run("tox", "-v", "--notest", "-epy26") assert not result.ret result.stdout.fnmatch_lines([ "*py26*reusing*", ]) def test_PYC(initproj, cmd, monkeypatch): initproj("example123", filedefs={'tox.ini': ''}) monkeypatch.setenv("PYTHONDOWNWRITEBYTECODE", 1) result = cmd.run("tox", "-v", "--notest") assert not result.ret result.stdout.fnmatch_lines([ "*create*", ]) def test_env_VIRTUALENV_PYTHON(initproj, cmd, monkeypatch): initproj("example123", filedefs={'tox.ini': ''}) monkeypatch.setenv("VIRTUALENV_PYTHON", '/FOO') result = cmd.run("tox", "-v", "--notest") assert not result.ret, result.stdout.lines result.stdout.fnmatch_lines([ "*create*", ]) def test_sdistonly(initproj, cmd): initproj("example123", filedefs={'tox.ini': """ """}) result = cmd.run("tox", "-v", "--sdistonly") assert not result.ret result.stdout.fnmatch_lines([ "*sdist-make*setup.py*", ]) assert "-mvirtualenv" not in result.stdout.str() def test_separate_sdist_no_sdistfile(cmd, initproj): distshare = cmd.tmpdir.join("distshare") initproj(("pkg123-foo", "0.7"), filedefs={ 'tox.ini': """ [tox] distshare=%s """ % distshare }) result = cmd.run("tox", "--sdistonly") assert not result.ret l = distshare.listdir() assert len(l) == 1 sdistfile = l[0] assert 'pkg123-foo-0.7.zip' in str(sdistfile) def test_separate_sdist(cmd, initproj): distshare = cmd.tmpdir.join("distshare") initproj("pkg123-0.7", filedefs={ 'tox.ini': """ [tox] distshare=%s sdistsrc={distshare}/pkg123-0.7.zip """ % distshare }) result = cmd.run("tox", "--sdistonly") assert not result.ret l = distshare.listdir() assert len(l) == 1 sdistfile = l[0] result = cmd.run("tox", "-v", "--notest") assert not result.ret result.stdout.fnmatch_lines([ "*inst*%s*" % sdistfile, ]) def test_sdist_latest(tmpdir, newconfig): distshare = tmpdir.join("distshare") config = newconfig([], """ [tox] distshare=%s sdistsrc={distshare}/pkg123-* """ % distshare) p = distshare.ensure("pkg123-1.4.5.zip") distshare.ensure("pkg123-1.4.5a1.zip") session = Session(config) sdist_path = session.get_installpkg_path() assert sdist_path == p def test_installpkg(tmpdir, newconfig): p = tmpdir.ensure("pkg123-1.0.zip") config = newconfig(["--installpkg=%s" % p], "") session = Session(config) sdist_path = session.get_installpkg_path() assert sdist_path == p @pytest.mark.xfail("sys.platform == 'win32'", reason="test needs better impl") def test_envsitepackagesdir(cmd, initproj): initproj("pkg512-0.0.5", filedefs={ 'tox.ini': """ [testenv] commands= python -c "print(r'X:{envsitepackagesdir}')" """}) result = cmd.run("tox") assert result.ret == 0 result.stdout.fnmatch_lines(""" X:*tox*site-packages* """) @pytest.mark.xfail("sys.platform == 'win32'", reason="test needs better impl") def test_envsitepackagesdir_skip_missing_issue280(cmd, initproj): initproj("pkg513-0.0.5", filedefs={ 'tox.ini': """ [testenv] basepython=/usr/bin/qwelkjqwle commands= {envsitepackagesdir} """}) result = cmd.run("tox", "--skip-missing-interpreters") assert result.ret == 0 result.stdout.fnmatch_lines(""" SKIPPED:*qwelkj* """) def verify_json_report_format(data, testenvs=True): assert data["reportversion"] == "1" assert data["toxversion"] == tox.__version__ if testenvs: for envname, envdata in data["testenvs"].items(): for commandtype in ("setup", "test"): if commandtype not in envdata: continue for command in envdata[commandtype]: assert command["output"] assert command["retcode"] if envname != "GLOB": assert isinstance(envdata["installed_packages"], list) pyinfo = envdata["python"] assert isinstance(pyinfo["version_info"], list) assert pyinfo["version"] assert pyinfo["executable"] tox-2.3.1/tests/conftest.py0000664000175000017500000000005112633511761015261 0ustar hpkhpk00000000000000 from tox._pytestplugin import * # noqa tox-2.3.1/tests/test_quickstart.py0000664000175000017500000003440012633511761016672 0ustar hpkhpk00000000000000import pytest import tox._quickstart @pytest.fixture(autouse=True) def cleandir(tmpdir): tmpdir.chdir() class TestToxQuickstartMain(object): def mock_term_input_return_values(self, return_values): for return_val in return_values: yield return_val def get_mock_term_input(self, return_values): generator = self.mock_term_input_return_values(return_values) def mock_term_input(prompt): try: return next(generator) except NameError: return generator.next() return mock_term_input def test_quickstart_main_choose_individual_pythons_and_pytest( self, monkeypatch): monkeypatch.setattr( tox._quickstart, 'term_input', self.get_mock_term_input( [ '4', # Python versions: choose one by one 'Y', # py26 'Y', # py27 'Y', # py32 'Y', # py33 'Y', # py34 'Y', # py35 'Y', # pypy 'N', # jython 'py.test', # command to run tests 'pytest' # test dependencies ] ) ) tox._quickstart.main(argv=['tox-quickstart']) expected_tox_ini = """ # Tox (http://tox.testrun.org/) is a tool for running tests # in multiple virtualenvs. This configuration file will run the # test suite on all supported python versions. To use it, "pip install tox" # and then run "tox" from this directory. [tox] envlist = py26, py27, py32, py33, py34, py35, pypy [testenv] commands = py.test deps = pytest """.lstrip() result = open('tox.ini').read() assert(result == expected_tox_ini) def test_quickstart_main_choose_individual_pythons_and_nose_adds_deps( self, monkeypatch): monkeypatch.setattr( tox._quickstart, 'term_input', self.get_mock_term_input( [ '4', # Python versions: choose one by one 'Y', # py26 'Y', # py27 'Y', # py32 'Y', # py33 'Y', # py34 'Y', # py35 'Y', # pypy 'N', # jython 'nosetests', # command to run tests '' # test dependencies ] ) ) tox._quickstart.main(argv=['tox-quickstart']) expected_tox_ini = """ # Tox (http://tox.testrun.org/) is a tool for running tests # in multiple virtualenvs. This configuration file will run the # test suite on all supported python versions. To use it, "pip install tox" # and then run "tox" from this directory. [tox] envlist = py26, py27, py32, py33, py34, py35, pypy [testenv] commands = nosetests deps = nose """.lstrip() result = open('tox.ini').read() assert(result == expected_tox_ini) def test_quickstart_main_choose_individual_pythons_and_trial_adds_deps( self, monkeypatch): monkeypatch.setattr( tox._quickstart, 'term_input', self.get_mock_term_input( [ '4', # Python versions: choose one by one 'Y', # py26 'Y', # py27 'Y', # py32 'Y', # py33 'Y', # py34 'Y', # py35 'Y', # pypy 'N', # jython 'trial', # command to run tests '' # test dependencies ] ) ) tox._quickstart.main(argv=['tox-quickstart']) expected_tox_ini = """ # Tox (http://tox.testrun.org/) is a tool for running tests # in multiple virtualenvs. This configuration file will run the # test suite on all supported python versions. To use it, "pip install tox" # and then run "tox" from this directory. [tox] envlist = py26, py27, py32, py33, py34, py35, pypy [testenv] commands = trial deps = twisted """.lstrip() result = open('tox.ini').read() assert(result == expected_tox_ini) def test_quickstart_main_choose_individual_pythons_and_pytest_adds_deps( self, monkeypatch): monkeypatch.setattr( tox._quickstart, 'term_input', self.get_mock_term_input( [ '4', # Python versions: choose one by one 'Y', # py26 'Y', # py27 'Y', # py32 'Y', # py33 'Y', # py34 'Y', # py35 'Y', # pypy 'N', # jython 'py.test', # command to run tests '' # test dependencies ] ) ) tox._quickstart.main(argv=['tox-quickstart']) expected_tox_ini = """ # Tox (http://tox.testrun.org/) is a tool for running tests # in multiple virtualenvs. This configuration file will run the # test suite on all supported python versions. To use it, "pip install tox" # and then run "tox" from this directory. [tox] envlist = py26, py27, py32, py33, py34, py35, pypy [testenv] commands = py.test deps = pytest """.lstrip() result = open('tox.ini').read() assert(result == expected_tox_ini) def test_quickstart_main_choose_py27_and_pytest_adds_deps( self, monkeypatch): monkeypatch.setattr( tox._quickstart, 'term_input', self.get_mock_term_input( [ '1', # py27 'py.test', # command to run tests '' # test dependencies ] ) ) tox._quickstart.main(argv=['tox-quickstart']) expected_tox_ini = """ # Tox (http://tox.testrun.org/) is a tool for running tests # in multiple virtualenvs. This configuration file will run the # test suite on all supported python versions. To use it, "pip install tox" # and then run "tox" from this directory. [tox] envlist = py27 [testenv] commands = py.test deps = pytest """.lstrip() result = open('tox.ini').read() assert(result == expected_tox_ini) def test_quickstart_main_choose_py27_and_py33_and_pytest_adds_deps( self, monkeypatch): monkeypatch.setattr( tox._quickstart, 'term_input', self.get_mock_term_input( [ '2', # py27 and py33 'py.test', # command to run tests '' # test dependencies ] ) ) tox._quickstart.main(argv=['tox-quickstart']) expected_tox_ini = """ # Tox (http://tox.testrun.org/) is a tool for running tests # in multiple virtualenvs. This configuration file will run the # test suite on all supported python versions. To use it, "pip install tox" # and then run "tox" from this directory. [tox] envlist = py27, py33 [testenv] commands = py.test deps = pytest """.lstrip() result = open('tox.ini').read() assert(result == expected_tox_ini) def test_quickstart_main_choose_all_pythons_and_pytest_adds_deps( self, monkeypatch): monkeypatch.setattr( tox._quickstart, 'term_input', self.get_mock_term_input( [ '3', # all Python versions 'py.test', # command to run tests '' # test dependencies ] ) ) tox._quickstart.main(argv=['tox-quickstart']) expected_tox_ini = """ # Tox (http://tox.testrun.org/) is a tool for running tests # in multiple virtualenvs. This configuration file will run the # test suite on all supported python versions. To use it, "pip install tox" # and then run "tox" from this directory. [tox] envlist = py26, py27, py32, py33, py34, py35, pypy, jython [testenv] commands = py.test deps = pytest """.lstrip() result = open('tox.ini').read() assert(result == expected_tox_ini) def test_quickstart_main_choose_individual_pythons_and_defaults( self, monkeypatch): monkeypatch.setattr( tox._quickstart, 'term_input', self.get_mock_term_input( [ '4', # Python versions: choose one by one '', # py26 '', # py27 '', # py32 '', # py33 '', # py34 '', # py35 '', # pypy '', # jython '', # command to run tests '' # test dependencies ] ) ) tox._quickstart.main(argv=['tox-quickstart']) expected_tox_ini = """ # Tox (http://tox.testrun.org/) is a tool for running tests # in multiple virtualenvs. This configuration file will run the # test suite on all supported python versions. To use it, "pip install tox" # and then run "tox" from this directory. [tox] envlist = py26, py27, py32, py33, py34, py35, pypy, jython [testenv] commands = {envpython} setup.py test deps = """.lstrip() result = open('tox.ini').read() assert(result == expected_tox_ini) def test_quickstart_main_existing_tox_ini(self, monkeypatch): try: f = open('tox.ini', 'w') f.write('foo bar\n') finally: f.close() monkeypatch.setattr( tox._quickstart, 'term_input', self.get_mock_term_input( [ '4', # Python versions: choose one by one '', # py26 '', # py27 '', # py32 '', # py33 '', # py34 '', # py35 '', # pypy '', # jython '', # command to run tests '', # test dependencies '', # tox.ini already exists; overwrite? ] ) ) tox._quickstart.main(argv=['tox-quickstart']) expected_tox_ini = """ # Tox (http://tox.testrun.org/) is a tool for running tests # in multiple virtualenvs. This configuration file will run the # test suite on all supported python versions. To use it, "pip install tox" # and then run "tox" from this directory. [tox] envlist = py26, py27, py32, py33, py34, py35, pypy, jython [testenv] commands = {envpython} setup.py test deps = """.lstrip() result = open('tox-generated.ini').read() assert(result == expected_tox_ini) class TestToxQuickstart(object): def test_pytest(self): d = { 'py26': True, 'py27': True, 'py32': True, 'py33': True, 'py34': True, 'pypy': True, 'commands': 'py.test', 'deps': 'pytest', } expected_tox_ini = """ # Tox (http://tox.testrun.org/) is a tool for running tests # in multiple virtualenvs. This configuration file will run the # test suite on all supported python versions. To use it, "pip install tox" # and then run "tox" from this directory. [tox] envlist = py26, py27, py32, py33, py34, pypy [testenv] commands = py.test deps = pytest """.lstrip() d = tox._quickstart.process_input(d) tox._quickstart.generate(d) result = open('tox.ini').read() # print(result) assert(result == expected_tox_ini) def test_setup_py_test(self): d = { 'py26': True, 'py27': True, 'commands': 'python setup.py test', 'deps': '', } expected_tox_ini = """ # Tox (http://tox.testrun.org/) is a tool for running tests # in multiple virtualenvs. This configuration file will run the # test suite on all supported python versions. To use it, "pip install tox" # and then run "tox" from this directory. [tox] envlist = py26, py27 [testenv] commands = python setup.py test deps = """.lstrip() d = tox._quickstart.process_input(d) tox._quickstart.generate(d) result = open('tox.ini').read() # print(result) assert(result == expected_tox_ini) def test_trial(self): d = { 'py27': True, 'commands': 'trial', 'deps': 'Twisted', } expected_tox_ini = """ # Tox (http://tox.testrun.org/) is a tool for running tests # in multiple virtualenvs. This configuration file will run the # test suite on all supported python versions. To use it, "pip install tox" # and then run "tox" from this directory. [tox] envlist = py27 [testenv] commands = trial deps = Twisted """.lstrip() d = tox._quickstart.process_input(d) tox._quickstart.generate(d) result = open('tox.ini').read() # print(result) assert(result == expected_tox_ini) def test_nosetests(self): d = { 'py27': True, 'py32': True, 'py33': True, 'py34': True, 'py35': True, 'pypy': True, 'commands': 'nosetests -v', 'deps': 'nose', } expected_tox_ini = """ # Tox (http://tox.testrun.org/) is a tool for running tests # in multiple virtualenvs. This configuration file will run the # test suite on all supported python versions. To use it, "pip install tox" # and then run "tox" from this directory. [tox] envlist = py27, py32, py33, py34, py35, pypy [testenv] commands = nosetests -v deps = nose """.lstrip() d = tox._quickstart.process_input(d) tox._quickstart.generate(d) result = open('tox.ini').read() # print(result) assert(result == expected_tox_ini) tox-2.3.1/tests/test_venv.py0000664000175000017500000005367212633511761015472 0ustar hpkhpk00000000000000import py import tox import pytest import os import sys import tox.config from tox.venv import * # noqa from tox.interpreters import NoInterpreterInfo # def test_global_virtualenv(capfd): # v = VirtualEnv() # l = v.list() # assert l # out, err = capfd.readouterr() # assert not out # assert not err # def test_getdigest(tmpdir): assert getdigest(tmpdir) == "0" * 32 def test_getsupportedinterpreter(monkeypatch, newconfig, mocksession): config = newconfig([], """ [testenv:python] basepython=%s """ % sys.executable) venv = VirtualEnv(config.envconfigs['python'], session=mocksession) interp = venv.getsupportedinterpreter() # realpath needed for debian symlinks assert py.path.local(interp).realpath() == py.path.local(sys.executable).realpath() monkeypatch.setattr(sys, 'platform', "win32") monkeypatch.setattr(venv.envconfig, 'basepython', 'jython') py.test.raises(tox.exception.UnsupportedInterpreter, venv.getsupportedinterpreter) monkeypatch.undo() monkeypatch.setattr(venv.envconfig, "envname", "py1") monkeypatch.setattr(venv.envconfig, 'basepython', 'notexistingpython') py.test.raises(tox.exception.InterpreterNotFound, venv.getsupportedinterpreter) monkeypatch.undo() # check that we properly report when no version_info is present info = NoInterpreterInfo(name=venv.name) info.executable = "something" monkeypatch.setattr(config.interpreters, "get_info", lambda *args, **kw: info) pytest.raises(tox.exception.InvocationError, venv.getsupportedinterpreter) def test_create(monkeypatch, mocksession, newconfig): config = newconfig([], """ [testenv:py123] """) envconfig = config.envconfigs['py123'] venv = VirtualEnv(envconfig, session=mocksession) assert venv.path == envconfig.envdir assert not venv.path.check() action = mocksession.newaction(venv, "getenv") tox_testenv_create(action=action, venv=venv) l = mocksession._pcalls assert len(l) >= 1 args = l[0].args assert "virtualenv" == str(args[2]) if sys.platform != "win32": # realpath is needed for stuff like the debian symlinks assert py.path.local(sys.executable).realpath() == py.path.local(args[0]).realpath() # assert Envconfig.toxworkdir in args assert venv.getcommandpath("easy_install", cwd=py.path.local()) interp = venv._getliveconfig().python assert interp == venv.envconfig.python_info.executable assert venv.path_config.check(exists=False) @pytest.mark.skipif("sys.platform == 'win32'") def test_commandpath_venv_precendence(tmpdir, monkeypatch, mocksession, newconfig): config = newconfig([], """ [testenv:py123] """) envconfig = config.envconfigs['py123'] venv = VirtualEnv(envconfig, session=mocksession) tmpdir.ensure("easy_install") monkeypatch.setenv("PATH", str(tmpdir), prepend=os.pathsep) envconfig.envbindir.ensure("easy_install") p = venv.getcommandpath("easy_install") assert py.path.local(p).relto(envconfig.envbindir), p def test_create_sitepackages(monkeypatch, mocksession, newconfig): config = newconfig([], """ [testenv:site] sitepackages=True [testenv:nosite] sitepackages=False """) envconfig = config.envconfigs['site'] venv = VirtualEnv(envconfig, session=mocksession) action = mocksession.newaction(venv, "getenv") tox_testenv_create(action=action, venv=venv) l = mocksession._pcalls assert len(l) >= 1 args = l[0].args assert "--system-site-packages" in map(str, args) mocksession._clearmocks() envconfig = config.envconfigs['nosite'] venv = VirtualEnv(envconfig, session=mocksession) action = mocksession.newaction(venv, "getenv") tox_testenv_create(action=action, venv=venv) l = mocksession._pcalls assert len(l) >= 1 args = l[0].args assert "--system-site-packages" not in map(str, args) assert "--no-site-packages" not in map(str, args) def test_install_deps_wildcard(newmocksession): mocksession = newmocksession([], """ [tox] distshare = {toxworkdir}/distshare [testenv:py123] deps= {distshare}/dep1-* """) venv = mocksession.getenv("py123") action = mocksession.newaction(venv, "getenv") tox_testenv_create(action=action, venv=venv) l = mocksession._pcalls assert len(l) == 1 distshare = venv.session.config.distshare distshare.ensure("dep1-1.0.zip") distshare.ensure("dep1-1.1.zip") tox_testenv_install_deps(action=action, venv=venv) assert len(l) == 2 args = l[-1].args assert l[-1].cwd == venv.envconfig.config.toxinidir assert "pip" in str(args[0]) assert args[1] == "install" # arg = "--download-cache=" + str(venv.envconfig.downloadcache) # assert arg in args[2:] args = [arg for arg in args if str(arg).endswith("dep1-1.1.zip")] assert len(args) == 1 @pytest.mark.parametrize("envdc", [True, False]) def test_install_downloadcache(newmocksession, monkeypatch, tmpdir, envdc): if envdc: monkeypatch.setenv("PIP_DOWNLOAD_CACHE", tmpdir) else: monkeypatch.delenv("PIP_DOWNLOAD_CACHE", raising=False) mocksession = newmocksession([], """ [testenv:py123] deps= dep1 dep2 """) venv = mocksession.getenv("py123") action = mocksession.newaction(venv, "getenv") tox_testenv_create(action=action, venv=venv) l = mocksession._pcalls assert len(l) == 1 tox_testenv_install_deps(action=action, venv=venv) assert len(l) == 2 args = l[-1].args assert l[-1].cwd == venv.envconfig.config.toxinidir assert "pip" in str(args[0]) assert args[1] == "install" assert "dep1" in args assert "dep2" in args deps = list(filter(None, [x[1] for x in venv._getliveconfig().deps])) assert deps == ['dep1', 'dep2'] def test_install_deps_indexserver(newmocksession): mocksession = newmocksession([], """ [tox] indexserver = abc = ABC abc2 = ABC [testenv:py123] deps= dep1 :abc:dep2 :abc2:dep3 """) venv = mocksession.getenv('py123') action = mocksession.newaction(venv, "getenv") tox_testenv_create(action=action, venv=venv) l = mocksession._pcalls assert len(l) == 1 l[:] = [] tox_testenv_install_deps(action=action, venv=venv) # two different index servers, two calls assert len(l) == 3 args = " ".join(l[0].args) assert "-i " not in args assert "dep1" in args args = " ".join(l[1].args) assert "-i ABC" in args assert "dep2" in args args = " ".join(l[2].args) assert "-i ABC" in args assert "dep3" in args def test_install_deps_pre(newmocksession): mocksession = newmocksession([], """ [testenv] pip_pre=true deps= dep1 """) venv = mocksession.getenv('python') action = mocksession.newaction(venv, "getenv") tox_testenv_create(action=action, venv=venv) l = mocksession._pcalls assert len(l) == 1 l[:] = [] tox_testenv_install_deps(action=action, venv=venv) assert len(l) == 1 args = " ".join(l[0].args) assert "--pre " in args assert "dep1" in args def test_installpkg_indexserver(newmocksession, tmpdir): mocksession = newmocksession([], """ [tox] indexserver = default = ABC """) venv = mocksession.getenv('python') l = mocksession._pcalls p = tmpdir.ensure("distfile.tar.gz") mocksession.installpkg(venv, p) # two different index servers, two calls assert len(l) == 1 args = " ".join(l[0].args) assert "-i ABC" in args def test_install_recreate(newmocksession, tmpdir): pkg = tmpdir.ensure("package.tar.gz") mocksession = newmocksession(['--recreate'], """ [testenv] deps=xyz """) venv = mocksession.getenv('python') action = mocksession.newaction(venv, "update") venv.update(action) mocksession.installpkg(venv, pkg) mocksession.report.expect("verbosity0", "*create*") venv.update(action) mocksession.report.expect("verbosity0", "*recreate*") def test_test_hashseed_is_in_output(newmocksession): original_make_hashseed = tox.config.make_hashseed tox.config.make_hashseed = lambda: '123456789' try: mocksession = newmocksession([], ''' [testenv] ''') finally: tox.config.make_hashseed = original_make_hashseed venv = mocksession.getenv('python') action = mocksession.newaction(venv, "update") venv.update(action) venv.test() mocksession.report.expect("verbosity0", "python runtests: PYTHONHASHSEED='123456789'") def test_test_runtests_action_command_is_in_output(newmocksession): mocksession = newmocksession([], ''' [testenv] commands = echo foo bar ''') venv = mocksession.getenv('python') action = mocksession.newaction(venv, "update") venv.update(action) venv.test() mocksession.report.expect("verbosity0", "*runtests*commands?0? | echo foo bar") def test_install_error(newmocksession, monkeypatch): mocksession = newmocksession(['--recreate'], """ [testenv] deps=xyz commands= qwelkqw """) venv = mocksession.getenv('python') venv.test() mocksession.report.expect("error", "*not find*qwelkqw*") assert venv.status == "commands failed" def test_install_command_not_installed(newmocksession, monkeypatch): mocksession = newmocksession(['--recreate'], """ [testenv] commands= py.test """) venv = mocksession.getenv('python') venv.test() mocksession.report.expect("warning", "*test command found but not*") assert venv.status == 0 def test_install_command_whitelisted(newmocksession, monkeypatch): mocksession = newmocksession(['--recreate'], """ [testenv] whitelist_externals = py.test xy* commands= py.test xyz """) venv = mocksession.getenv('python') venv.test() mocksession.report.expect("warning", "*test command found but not*", invert=True) assert venv.status == "commands failed" @pytest.mark.skipif("not sys.platform.startswith('linux')") def test_install_command_not_installed_bash(newmocksession): mocksession = newmocksession(['--recreate'], """ [testenv] commands= bash """) venv = mocksession.getenv('python') venv.test() mocksession.report.expect("warning", "*test command found but not*") def test_install_python3(tmpdir, newmocksession): if not py.path.local.sysfind('python3.3'): pytest.skip("needs python3.3") mocksession = newmocksession([], """ [testenv:py123] basepython=python3.3 deps= dep1 dep2 """) venv = mocksession.getenv('py123') action = mocksession.newaction(venv, "getenv") tox_testenv_create(action=action, venv=venv) l = mocksession._pcalls assert len(l) == 1 args = l[0].args assert str(args[2]) == 'virtualenv' l[:] = [] action = mocksession.newaction(venv, "hello") venv._install(["hello"], action=action) assert len(l) == 1 args = l[0].args assert 'pip' in str(args[0]) for x in args: assert "--download-cache" not in args, args class TestCreationConfig: def test_basic(self, newconfig, mocksession, tmpdir): config = newconfig([], "") envconfig = config.envconfigs['python'] venv = VirtualEnv(envconfig, session=mocksession) cconfig = venv._getliveconfig() assert cconfig.matches(cconfig) path = tmpdir.join("configdump") cconfig.writeconfig(path) newconfig = CreationConfig.readconfig(path) assert newconfig.matches(cconfig) assert cconfig.matches(newconfig) def test_matchingdependencies(self, newconfig, mocksession): config = newconfig([], """ [testenv] deps=abc """) envconfig = config.envconfigs['python'] venv = VirtualEnv(envconfig, session=mocksession) cconfig = venv._getliveconfig() config = newconfig([], """ [testenv] deps=xyz """) envconfig = config.envconfigs['python'] venv = VirtualEnv(envconfig, session=mocksession) otherconfig = venv._getliveconfig() assert not cconfig.matches(otherconfig) def test_matchingdependencies_file(self, newconfig, mocksession): config = newconfig([], """ [tox] distshare={toxworkdir}/distshare [testenv] deps=abc {distshare}/xyz.zip """) xyz = config.distshare.join("xyz.zip") xyz.ensure() envconfig = config.envconfigs['python'] venv = VirtualEnv(envconfig, session=mocksession) cconfig = venv._getliveconfig() assert cconfig.matches(cconfig) xyz.write("hello") newconfig = venv._getliveconfig() assert not cconfig.matches(newconfig) def test_matchingdependencies_latest(self, newconfig, mocksession): config = newconfig([], """ [tox] distshare={toxworkdir}/distshare [testenv] deps={distshare}/xyz-* """) config.distshare.ensure("xyz-1.2.0.zip") xyz2 = config.distshare.ensure("xyz-1.2.1.zip") envconfig = config.envconfigs['python'] venv = VirtualEnv(envconfig, session=mocksession) cconfig = venv._getliveconfig() md5, path = cconfig.deps[0] assert path == xyz2 assert md5 == path.computehash() def test_python_recreation(self, tmpdir, newconfig, mocksession): pkg = tmpdir.ensure("package.tar.gz") config = newconfig([], "") envconfig = config.envconfigs['python'] venv = VirtualEnv(envconfig, session=mocksession) cconfig = venv._getliveconfig() action = mocksession.newaction(venv, "update") venv.update(action) assert not venv.path_config.check() mocksession.installpkg(venv, pkg) assert venv.path_config.check() assert mocksession._pcalls args1 = map(str, mocksession._pcalls[0].args) assert 'virtualenv' in " ".join(args1) mocksession.report.expect("*", "*create*") # modify config and check that recreation happens mocksession._clearmocks() action = mocksession.newaction(venv, "update") venv.update(action) mocksession.report.expect("*", "*reusing*") mocksession._clearmocks() action = mocksession.newaction(venv, "update") cconfig.python = py.path.local("balla") cconfig.writeconfig(venv.path_config) venv.update(action) mocksession.report.expect("verbosity0", "*recreate*") def test_dep_recreation(self, newconfig, mocksession): config = newconfig([], "") envconfig = config.envconfigs['python'] venv = VirtualEnv(envconfig, session=mocksession) action = mocksession.newaction(venv, "update") venv.update(action) cconfig = venv._getliveconfig() cconfig.deps[:] = [("1" * 32, "xyz.zip")] cconfig.writeconfig(venv.path_config) mocksession._clearmocks() action = mocksession.newaction(venv, "update") venv.update(action) mocksession.report.expect("*", "*recreate*") def test_develop_recreation(self, newconfig, mocksession): config = newconfig([], "") envconfig = config.envconfigs['python'] venv = VirtualEnv(envconfig, session=mocksession) action = mocksession.newaction(venv, "update") venv.update(action) cconfig = venv._getliveconfig() cconfig.usedevelop = True cconfig.writeconfig(venv.path_config) mocksession._clearmocks() action = mocksession.newaction(venv, "update") venv.update(action) mocksession.report.expect("verbosity0", "*recreate*") class TestVenvTest: def test_envbinddir_path(self, newmocksession, monkeypatch): monkeypatch.setenv("PIP_RESPECT_VIRTUALENV", "1") mocksession = newmocksession([], """ [testenv:python] commands=abc """) venv = mocksession.getenv("python") action = mocksession.newaction(venv, "getenv") monkeypatch.setenv("PATH", "xyz") l = [] monkeypatch.setattr("py.path.local.sysfind", classmethod( lambda *args, **kwargs: l.append(kwargs) or 0 / 0)) with pytest.raises(ZeroDivisionError): venv._install(list('123'), action=action) assert l.pop()["paths"] == [venv.envconfig.envbindir] with pytest.raises(ZeroDivisionError): venv.test(action) assert l.pop()["paths"] == [venv.envconfig.envbindir] with pytest.raises(ZeroDivisionError): venv.run_install_command(['qwe'], action=action) assert l.pop()["paths"] == [venv.envconfig.envbindir] monkeypatch.setenv("PIP_RESPECT_VIRTUALENV", "1") monkeypatch.setenv("PIP_REQUIRE_VIRTUALENV", "1") monkeypatch.setenv("__PYVENV_LAUNCHER__", "1") py.test.raises(ZeroDivisionError, "venv.run_install_command(['qwe'], action=action)") assert 'PIP_RESPECT_VIRTUALENV' not in os.environ assert 'PIP_REQUIRE_VIRTUALENV' not in os.environ assert '__PYVENV_LAUNCHER__' not in os.environ def test_env_variables_added_to_pcall(tmpdir, mocksession, newconfig, monkeypatch): pkg = tmpdir.ensure("package.tar.gz") monkeypatch.setenv("X123", "123") monkeypatch.setenv("YY", "456") config = newconfig([], """ [testenv:python] commands=python -V passenv = x123 setenv = ENV_VAR = value """) mocksession._clearmocks() venv = VirtualEnv(config.envconfigs['python'], session=mocksession) # import pdb; pdb.set_trace() mocksession.installpkg(venv, pkg) venv.test() l = mocksession._pcalls assert len(l) == 2 for x in l: env = x.env assert env is not None assert 'ENV_VAR' in env assert env['ENV_VAR'] == 'value' assert env['VIRTUAL_ENV'] == str(venv.path) assert env['X123'] == "123" # all env variables are passed for installation assert l[0].env["YY"] == "456" assert "YY" not in l[1].env assert set(["ENV_VAR", "VIRTUAL_ENV", "PYTHONHASHSEED", "X123", "PATH"])\ .issubset(l[1].env) # for e in os.environ: # assert e in env def test_installpkg_no_upgrade(tmpdir, newmocksession): pkg = tmpdir.ensure("package.tar.gz") mocksession = newmocksession([], "") venv = mocksession.getenv('python') venv.just_created = True venv.envconfig.envdir.ensure(dir=1) mocksession.installpkg(venv, pkg) l = mocksession._pcalls assert len(l) == 1 assert '-U' not in l[0].args def test_installpkg_upgrade(newmocksession, tmpdir): pkg = tmpdir.ensure("package.tar.gz") mocksession = newmocksession([], "") venv = mocksession.getenv('python') assert not hasattr(venv, 'just_created') mocksession.installpkg(venv, pkg) l = mocksession._pcalls assert len(l) == 1 index = l[0].args.index(str(pkg)) assert index >= 0 assert '-U' in l[0].args[:index] assert '--no-deps' in l[0].args[:index] def test_run_install_command(newmocksession): mocksession = newmocksession([], "") venv = mocksession.getenv('python') venv.just_created = True venv.envconfig.envdir.ensure(dir=1) action = mocksession.newaction(venv, "hello") venv.run_install_command(packages=["whatever"], action=action) l = mocksession._pcalls assert len(l) == 1 assert 'pip' in l[0].args[0] assert 'install' in l[0].args env = l[0].env assert env is not None def test_run_custom_install_command(newmocksession): mocksession = newmocksession([], """ [testenv] install_command=easy_install {opts} {packages} """) venv = mocksession.getenv('python') venv.just_created = True venv.envconfig.envdir.ensure(dir=1) action = mocksession.newaction(venv, "hello") venv.run_install_command(packages=["whatever"], action=action) l = mocksession._pcalls assert len(l) == 1 assert 'easy_install' in l[0].args[0] assert l[0].args[1:] == ['whatever'] def test_command_relative_issue26(newmocksession, tmpdir, monkeypatch): mocksession = newmocksession([], """ [testenv] """) x = tmpdir.ensure("x") venv = mocksession.getenv("python") x2 = venv.getcommandpath("./x", cwd=tmpdir) assert x == x2 mocksession.report.not_expect("warning", "*test command found but not*") x3 = venv.getcommandpath("/bin/bash", cwd=tmpdir) assert x3 == "/bin/bash" mocksession.report.not_expect("warning", "*test command found but not*") monkeypatch.setenv("PATH", str(tmpdir)) x4 = venv.getcommandpath("x", cwd=tmpdir) assert x4.endswith(os.sep + 'x') mocksession.report.expect("warning", "*test command found but not*") def test_ignore_outcome_failing_cmd(newmocksession): mocksession = newmocksession([], """ [testenv] commands=testenv_fail ignore_outcome=True """) venv = mocksession.getenv('python') venv.test() assert venv.status == "ignored failed command" mocksession.report.expect("warning", "*command failed but result from " "testenv is ignored*") def test_tox_testenv_create(newmocksession): l = [] class Plugin: @hookimpl def tox_testenv_create(self, action, venv): l.append(1) @hookimpl def tox_testenv_install_deps(self, action, venv): l.append(2) mocksession = newmocksession([], """ [testenv] commands=testenv_fail ignore_outcome=True """, plugins=[Plugin()]) venv = mocksession.getenv('python') venv.update(action=mocksession.newaction(venv, "getenv")) assert l == [1, 2] tox-2.3.1/tests/test_interpreters.py0000664000175000017500000000656612633511761017242 0ustar hpkhpk00000000000000import sys import os import pytest from tox.interpreters import * # noqa from tox.config import get_plugin_manager @pytest.fixture def interpreters(): pm = get_plugin_manager() return Interpreters(hook=pm.hook) @pytest.mark.skipif("sys.platform != 'win32'") def test_locate_via_py(monkeypatch): class PseudoPy: def sysexec(self, *args): assert args[0] == '-3.2' assert args[1] == '-c' # Return value needs to actually exist! return sys.executable @staticmethod def ret_pseudopy(name): assert name == 'py' return PseudoPy() # Monkeypatch py.path.local.sysfind to return PseudoPy monkeypatch.setattr(py.path.local, 'sysfind', ret_pseudopy) assert locate_via_py('3', '2') == sys.executable def test_tox_get_python_executable(): class envconfig: basepython = sys.executable envname = "pyxx" p = tox_get_python_executable(envconfig) assert p == py.path.local(sys.executable) for ver in [""] + "2.4 2.5 2.6 2.7 3.0 3.1 3.2 3.3".split(): name = "python%s" % ver if sys.platform == "win32": pydir = "python%s" % ver.replace(".", "") x = py.path.local("c:\%s" % pydir) print (x) if not x.check(): continue else: if not py.path.local.sysfind(name): continue envconfig.basepython = name p = tox_get_python_executable(envconfig) assert p popen = py.std.subprocess.Popen([str(p), '-V'], stderr=py.std.subprocess.PIPE) stdout, stderr = popen.communicate() assert ver in py.builtin._totext(stderr, "ascii") def test_find_executable_extra(monkeypatch): @staticmethod def sysfind(x): return "hello" monkeypatch.setattr(py.path.local, "sysfind", sysfind) class envconfig: basepython = "1lk23j" envname = "pyxx" t = tox_get_python_executable(envconfig) assert t == "hello" def test_run_and_get_interpreter_info(): name = os.path.basename(sys.executable) info = run_and_get_interpreter_info(name, sys.executable) assert info.version_info == tuple(sys.version_info) assert info.name == name assert info.executable == sys.executable class TestInterpreters: def test_get_executable(self, interpreters): class envconfig: basepython = sys.executable envname = "pyxx" x = interpreters.get_executable(envconfig) assert x == sys.executable info = interpreters.get_info(envconfig) assert info.version_info == tuple(sys.version_info) assert info.executable == sys.executable assert info.runnable def test_get_executable_no_exist(self, interpreters): class envconfig: basepython = "1lkj23" envname = "pyxx" assert not interpreters.get_executable(envconfig) info = interpreters.get_info(envconfig) assert not info.version_info assert info.name == "1lkj23" assert not info.executable assert not info.runnable def test_get_sitepackagesdir_error(self, interpreters): class envconfig: basepython = sys.executable envname = "123" info = interpreters.get_info(envconfig) s = interpreters.get_sitepackagesdir(info, "") assert s tox-2.3.1/tests/test_config.py0000664000175000017500000020405412633511761015751 0ustar hpkhpk00000000000000import sys from textwrap import dedent import py import pytest import tox import tox.config from tox.config import * # noqa from tox.venv import VirtualEnv class TestVenvConfig: def test_config_parsing_minimal(self, tmpdir, newconfig): config = newconfig([], """ [testenv:py1] """) assert len(config.envconfigs) == 1 assert config.toxworkdir.realpath() == tmpdir.join(".tox").realpath() assert config.envconfigs['py1'].basepython == sys.executable assert config.envconfigs['py1'].deps == [] assert config.envconfigs['py1'].platform == ".*" def test_config_parsing_multienv(self, tmpdir, newconfig): config = newconfig([], """ [tox] toxworkdir = %s indexserver = xyz = xyz_repo [testenv:py1] deps=hello [testenv:py2] deps= world1 :xyz:http://hello/world """ % (tmpdir, )) assert config.toxworkdir == tmpdir assert len(config.envconfigs) == 2 assert config.envconfigs['py1'].envdir == tmpdir.join("py1") dep = config.envconfigs['py1'].deps[0] assert dep.name == "hello" assert dep.indexserver is None assert config.envconfigs['py2'].envdir == tmpdir.join("py2") dep1, dep2 = config.envconfigs['py2'].deps assert dep1.name == "world1" assert dep2.name == "http://hello/world" assert dep2.indexserver.name == "xyz" assert dep2.indexserver.url == "xyz_repo" def test_envdir_set_manually(self, tmpdir, newconfig): config = newconfig([], """ [testenv:devenv] envdir = devenv """) envconfig = config.envconfigs['devenv'] assert envconfig.envdir == tmpdir.join('devenv') def test_envdir_set_manually_with_substitutions(self, tmpdir, newconfig): config = newconfig([], """ [testenv:devenv] envdir = {toxworkdir}/foobar """) envconfig = config.envconfigs['devenv'] assert envconfig.envdir == config.toxworkdir.join('foobar') def test_force_dep_version(self, initproj): """ Make sure we can override dependencies configured in tox.ini when using the command line option --force-dep. """ initproj("example123-0.5", filedefs={ 'tox.ini': ''' [tox] [testenv] deps= dep1==1.0 dep2>=2.0 dep3 dep4==4.0 ''' }) config = parseconfig( ['--force-dep=dep1==1.5', '--force-dep=dep2==2.1', '--force-dep=dep3==3.0']) assert config.option.force_dep == [ 'dep1==1.5', 'dep2==2.1', 'dep3==3.0'] assert [str(x) for x in config.envconfigs['python'].deps] == [ 'dep1==1.5', 'dep2==2.1', 'dep3==3.0', 'dep4==4.0', ] def test_force_dep_with_url(self, initproj): initproj("example123-0.5", filedefs={ 'tox.ini': ''' [tox] [testenv] deps= dep1==1.0 https://pypi.python.org/xyz/pkg1.tar.gz ''' }) config = parseconfig( ['--force-dep=dep1==1.5']) assert config.option.force_dep == [ 'dep1==1.5' ] assert [str(x) for x in config.envconfigs['python'].deps] == [ 'dep1==1.5', 'https://pypi.python.org/xyz/pkg1.tar.gz' ] def test_is_same_dep(self): """ Ensure correct parseini._is_same_dep is working with a few samples. """ assert DepOption._is_same_dep('pkg_hello-world3==1.0', 'pkg_hello-world3') assert DepOption._is_same_dep('pkg_hello-world3==1.0', 'pkg_hello-world3>=2.0') assert DepOption._is_same_dep('pkg_hello-world3==1.0', 'pkg_hello-world3>2.0') assert DepOption._is_same_dep('pkg_hello-world3==1.0', 'pkg_hello-world3<2.0') assert DepOption._is_same_dep('pkg_hello-world3==1.0', 'pkg_hello-world3<=2.0') assert not DepOption._is_same_dep('pkg_hello-world3==1.0', 'otherpkg>=2.0') class TestConfigPlatform: def test_config_parse_platform(self, newconfig): config = newconfig([], """ [testenv:py1] platform = linux2 """) assert len(config.envconfigs) == 1 assert config.envconfigs['py1'].platform == "linux2" def test_config_parse_platform_rex(self, newconfig, mocksession, monkeypatch): config = newconfig([], """ [testenv:py1] platform = a123|b123 """) assert len(config.envconfigs) == 1 envconfig = config.envconfigs['py1'] venv = VirtualEnv(envconfig, session=mocksession) assert not venv.matching_platform() monkeypatch.setattr(sys, "platform", "a123") assert venv.matching_platform() monkeypatch.setattr(sys, "platform", "b123") assert venv.matching_platform() monkeypatch.undo() assert not venv.matching_platform() @pytest.mark.parametrize("plat", ["win", "lin", ]) def test_config_parse_platform_with_factors(self, newconfig, plat, monkeypatch): monkeypatch.setattr(sys, "platform", "win32") config = newconfig([], """ [tox] envlist = py27-{win,lin,osx} [testenv] platform = win: win32 lin: linux2 """) assert len(config.envconfigs) == 3 platform = config.envconfigs['py27-' + plat].platform expected = {"win": "win32", "lin": "linux2"}.get(plat) assert platform == expected class TestConfigPackage: def test_defaults(self, tmpdir, newconfig): config = newconfig([], "") assert config.setupdir.realpath() == tmpdir.realpath() assert config.toxworkdir.realpath() == tmpdir.join(".tox").realpath() envconfig = config.envconfigs['python'] assert envconfig.args_are_paths assert not envconfig.recreate assert not envconfig.pip_pre def test_defaults_distshare(self, tmpdir, newconfig): config = newconfig([], "") assert config.distshare == config.homedir.join(".tox", "distshare") def test_defaults_changed_dir(self, tmpdir, newconfig): tmpdir.mkdir("abc").chdir() config = newconfig([], "") assert config.setupdir.realpath() == tmpdir.realpath() assert config.toxworkdir.realpath() == tmpdir.join(".tox").realpath() def test_project_paths(self, tmpdir, newconfig): config = newconfig(""" [tox] toxworkdir=%s """ % tmpdir) assert config.toxworkdir == tmpdir class TestParseconfig: def test_search_parents(self, tmpdir): b = tmpdir.mkdir("a").mkdir("b") toxinipath = tmpdir.ensure("tox.ini") old = b.chdir() try: config = parseconfig([]) finally: old.chdir() assert config.toxinipath == toxinipath def test_get_homedir(monkeypatch): monkeypatch.setattr(py.path.local, "_gethomedir", classmethod(lambda x: {}[1])) assert not get_homedir() monkeypatch.setattr(py.path.local, "_gethomedir", classmethod(lambda x: 0 / 0)) assert not get_homedir() monkeypatch.setattr(py.path.local, "_gethomedir", classmethod(lambda x: "123")) assert get_homedir() == "123" class TestGetcontextname: def test_blank(self, monkeypatch): monkeypatch.setattr(os, "environ", {}) assert getcontextname() is None def test_jenkins(self, monkeypatch): monkeypatch.setattr(os, "environ", {"JENKINS_URL": "xyz"}) assert getcontextname() == "jenkins" def test_hudson_legacy(self, monkeypatch): monkeypatch.setattr(os, "environ", {"HUDSON_URL": "xyz"}) assert getcontextname() == "jenkins" class TestIniParserAgainstCommandsKey: """Test parsing commands with substitutions""" def test_command_substitution_from_other_section(self, newconfig): config = newconfig(""" [section] key = whatever [testenv] commands = echo {[section]key} """) reader = SectionReader("testenv", config._cfg) x = reader.getargvlist("commands") assert x == [["echo", "whatever"]] def test_command_substitution_from_other_section_multiline(self, newconfig): """Ensure referenced multiline commands form from other section injected as multiple commands.""" config = newconfig(""" [section] commands = cmd1 param11 param12 # comment is omitted cmd2 param21 \ param22 [base] commands = cmd 1 \ 2 3 4 cmd 2 [testenv] commands = {[section]commands} {[section]commands} # comment is omitted echo {[base]commands} """) reader = SectionReader("testenv", config._cfg) x = reader.getargvlist("commands") assert x == [ "cmd1 param11 param12".split(), "cmd2 param21 param22".split(), "cmd1 param11 param12".split(), "cmd2 param21 param22".split(), ["echo", "cmd", "1", "2", "3", "4", "cmd", "2"], ] def test_command_env_substitution(self, newconfig): """Ensure referenced {env:key:default} values are substituted correctly.""" config = newconfig(""" [testenv:py27] setenv = TEST=testvalue commands = ls {env:TEST} """) envconfig = config.envconfigs["py27"] assert envconfig.commands == [["ls", "testvalue"]] assert envconfig.setenv["TEST"] == "testvalue" class TestIniParser: def test_getstring_single(self, tmpdir, newconfig): config = newconfig(""" [section] key=value """) reader = SectionReader("section", config._cfg) x = reader.getstring("key") assert x == "value" assert not reader.getstring("hello") x = reader.getstring("hello", "world") assert x == "world" def test_missing_substitution(self, tmpdir, newconfig): config = newconfig(""" [mydefault] key2={xyz} """) reader = SectionReader("mydefault", config._cfg, fallbacksections=['mydefault']) assert reader is not None with py.test.raises(tox.exception.ConfigError): reader.getstring("key2") def test_getstring_fallback_sections(self, tmpdir, newconfig): config = newconfig(""" [mydefault] key2=value2 [section] key=value """) reader = SectionReader("section", config._cfg, fallbacksections=['mydefault']) x = reader.getstring("key2") assert x == "value2" x = reader.getstring("key3") assert not x x = reader.getstring("key3", "world") assert x == "world" def test_getstring_substitution(self, tmpdir, newconfig): config = newconfig(""" [mydefault] key2={value2} [section] key={value} """) reader = SectionReader("section", config._cfg, fallbacksections=['mydefault']) reader.addsubstitutions(value="newvalue", value2="newvalue2") x = reader.getstring("key2") assert x == "newvalue2" x = reader.getstring("key3") assert not x x = reader.getstring("key3", "{value2}") assert x == "newvalue2" def test_getlist(self, tmpdir, newconfig): config = newconfig(""" [section] key2= item1 {item2} """) reader = SectionReader("section", config._cfg) reader.addsubstitutions(item1="not", item2="grr") x = reader.getlist("key2") assert x == ['item1', 'grr'] def test_getdict(self, tmpdir, newconfig): config = newconfig(""" [section] key2= key1=item1 key2={item2} """) reader = SectionReader("section", config._cfg) reader.addsubstitutions(item1="not", item2="grr") x = reader.getdict("key2") assert 'key1' in x assert 'key2' in x assert x['key1'] == 'item1' assert x['key2'] == 'grr' x = reader.getdict("key3", {1: 2}) assert x == {1: 2} def test_getstring_environment_substitution(self, monkeypatch, newconfig): monkeypatch.setenv("KEY1", "hello") config = newconfig(""" [section] key1={env:KEY1} key2={env:KEY2} """) reader = SectionReader("section", config._cfg) x = reader.getstring("key1") assert x == "hello" with py.test.raises(tox.exception.ConfigError): reader.getstring("key2") def test_getstring_environment_substitution_with_default(self, monkeypatch, newconfig): monkeypatch.setenv("KEY1", "hello") config = newconfig(""" [section] key1={env:KEY1:DEFAULT_VALUE} key2={env:KEY2:DEFAULT_VALUE} key3={env:KEY3:} """) reader = SectionReader("section", config._cfg) x = reader.getstring("key1") assert x == "hello" x = reader.getstring("key2") assert x == "DEFAULT_VALUE" x = reader.getstring("key3") assert x == "" def test_value_matches_section_substituion(self): assert is_section_substitution("{[setup]commands}") def test_value_doesn_match_section_substitution(self): assert is_section_substitution("{[ ]commands}") is None assert is_section_substitution("{[setup]}") is None assert is_section_substitution("{[setup] commands}") is None def test_getstring_other_section_substitution(self, newconfig): config = newconfig(""" [section] key = rue [testenv] key = t{[section]key} """) reader = SectionReader("testenv", config._cfg) x = reader.getstring("key") assert x == "true" def test_argvlist(self, tmpdir, newconfig): config = newconfig(""" [section] key2= cmd1 {item1} {item2} cmd2 {item2} """) reader = SectionReader("section", config._cfg) reader.addsubstitutions(item1="with space", item2="grr") # py.test.raises(tox.exception.ConfigError, # "reader.getargvlist('key1')") assert reader.getargvlist('key1') == [] x = reader.getargvlist("key2") assert x == [["cmd1", "with", "space", "grr"], ["cmd2", "grr"]] def test_argvlist_windows_escaping(self, tmpdir, newconfig): config = newconfig(""" [section] comm = py.test {posargs} """) reader = SectionReader("section", config._cfg) reader.addsubstitutions([r"hello\this"]) argv = reader.getargv("comm") assert argv == ["py.test", "hello\\this"] def test_argvlist_multiline(self, tmpdir, newconfig): config = newconfig(""" [section] key2= cmd1 {item1} \ {item2} """) reader = SectionReader("section", config._cfg) reader.addsubstitutions(item1="with space", item2="grr") # py.test.raises(tox.exception.ConfigError, # "reader.getargvlist('key1')") assert reader.getargvlist('key1') == [] x = reader.getargvlist("key2") assert x == [["cmd1", "with", "space", "grr"]] def test_argvlist_quoting_in_command(self, tmpdir, newconfig): config = newconfig(""" [section] key1= cmd1 'part one' \ 'part two' """) reader = SectionReader("section", config._cfg) x = reader.getargvlist("key1") assert x == [["cmd1", "part one", "part two"]] def test_argvlist_comment_after_command(self, tmpdir, newconfig): config = newconfig(""" [section] key1= cmd1 --flag # run the flag on the command """) reader = SectionReader("section", config._cfg) x = reader.getargvlist("key1") assert x == [["cmd1", "--flag"]] def test_argvlist_command_contains_hash(self, tmpdir, newconfig): config = newconfig(""" [section] key1= cmd1 --re "use the # symbol for an arg" """) reader = SectionReader("section", config._cfg) x = reader.getargvlist("key1") assert x == [["cmd1", "--re", "use the # symbol for an arg"]] def test_argvlist_positional_substitution(self, tmpdir, newconfig): config = newconfig(""" [section] key2= cmd1 [] cmd2 {posargs:{item2} \ other} """) reader = SectionReader("section", config._cfg) posargs = ['hello', 'world'] reader.addsubstitutions(posargs, item2="value2") # py.test.raises(tox.exception.ConfigError, # "reader.getargvlist('key1')") assert reader.getargvlist('key1') == [] argvlist = reader.getargvlist("key2") assert argvlist[0] == ["cmd1"] + posargs assert argvlist[1] == ["cmd2"] + posargs reader = SectionReader("section", config._cfg) reader.addsubstitutions([], item2="value2") # py.test.raises(tox.exception.ConfigError, # "reader.getargvlist('key1')") assert reader.getargvlist('key1') == [] argvlist = reader.getargvlist("key2") assert argvlist[0] == ["cmd1"] assert argvlist[1] == ["cmd2", "value2", "other"] def test_argvlist_quoted_posargs(self, tmpdir, newconfig): config = newconfig(""" [section] key2= cmd1 --foo-args='{posargs}' cmd2 -f '{posargs}' cmd3 -f {posargs} """) reader = SectionReader("section", config._cfg) reader.addsubstitutions(["foo", "bar"]) assert reader.getargvlist('key1') == [] x = reader.getargvlist("key2") assert x == [["cmd1", "--foo-args=foo bar"], ["cmd2", "-f", "foo bar"], ["cmd3", "-f", "foo", "bar"]] def test_argvlist_posargs_with_quotes(self, tmpdir, newconfig): config = newconfig(""" [section] key2= cmd1 -f {posargs} """) reader = SectionReader("section", config._cfg) reader.addsubstitutions(["foo", "'bar", "baz'"]) assert reader.getargvlist('key1') == [] x = reader.getargvlist("key2") assert x == [["cmd1", "-f", "foo", "bar baz"]] def test_positional_arguments_are_only_replaced_when_standing_alone(self, tmpdir, newconfig): config = newconfig(""" [section] key= cmd0 [] cmd1 -m '[abc]' cmd2 -m '\'something\'' [] cmd3 something[]else """) reader = SectionReader("section", config._cfg) posargs = ['hello', 'world'] reader.addsubstitutions(posargs) argvlist = reader.getargvlist('key') assert argvlist[0] == ['cmd0'] + posargs assert argvlist[1] == ['cmd1', '-m', '[abc]'] assert argvlist[2] == ['cmd2', '-m', "something"] + posargs assert argvlist[3] == ['cmd3', 'something[]else'] def test_substitution_with_multiple_words(self, newconfig): inisource = """ [section] key = py.test -n5 --junitxml={envlogdir}/junit-{envname}.xml [] """ config = newconfig(inisource) reader = SectionReader("section", config._cfg) posargs = ['hello', 'world'] reader.addsubstitutions(posargs, envlogdir='ENV_LOG_DIR', envname='ENV_NAME') expected = [ 'py.test', '-n5', '--junitxml=ENV_LOG_DIR/junit-ENV_NAME.xml', 'hello', 'world' ] assert reader.getargvlist('key')[0] == expected def test_getargv(self, newconfig): config = newconfig(""" [section] key=some command "with quoting" """) reader = SectionReader("section", config._cfg) expected = ['some', 'command', 'with quoting'] assert reader.getargv('key') == expected def test_getpath(self, tmpdir, newconfig): config = newconfig(""" [section] path1={HELLO} """) reader = SectionReader("section", config._cfg) reader.addsubstitutions(toxinidir=tmpdir, HELLO="mypath") x = reader.getpath("path1", tmpdir) assert x == tmpdir.join("mypath") def test_getbool(self, tmpdir, newconfig): config = newconfig(""" [section] key1=True key2=False key1a=true key2a=falsE key5=yes """) reader = SectionReader("section", config._cfg) assert reader.getbool("key1") is True assert reader.getbool("key1a") is True assert reader.getbool("key2") is False assert reader.getbool("key2a") is False py.test.raises(KeyError, 'reader.getbool("key3")') py.test.raises(tox.exception.ConfigError, 'reader.getbool("key5")') class TestConfigTestEnv: def test_commentchars_issue33(self, tmpdir, newconfig): config = newconfig(""" [testenv] # hello deps = http://abc#123 commands= python -c "x ; y" """) envconfig = config.envconfigs["python"] assert envconfig.deps[0].name == "http://abc#123" assert envconfig.commands[0] == ["python", "-c", "x ; y"] def test_defaults(self, tmpdir, newconfig): config = newconfig(""" [testenv] commands= xyz --abc """) assert len(config.envconfigs) == 1 envconfig = config.envconfigs['python'] assert envconfig.commands == [["xyz", "--abc"]] assert envconfig.changedir == config.setupdir assert envconfig.sitepackages is False assert envconfig.usedevelop is False assert envconfig.ignore_errors is False assert envconfig.envlogdir == envconfig.envdir.join("log") assert list(envconfig.setenv.definitions.keys()) == ['PYTHONHASHSEED'] hashseed = envconfig.setenv['PYTHONHASHSEED'] assert isinstance(hashseed, str) # The following line checks that hashseed parses to an integer. int_hashseed = int(hashseed) # hashseed is random by default, so we can't assert a specific value. assert int_hashseed > 0 assert envconfig.ignore_outcome is False def test_sitepackages_switch(self, tmpdir, newconfig): config = newconfig(["--sitepackages"], "") envconfig = config.envconfigs['python'] assert envconfig.sitepackages is True def test_installpkg_tops_develop(self, newconfig): config = newconfig(["--installpkg=abc"], """ [testenv] usedevelop = True """) assert not config.envconfigs["python"].usedevelop def test_specific_command_overrides(self, tmpdir, newconfig): config = newconfig(""" [testenv] commands=xyz [testenv:py] commands=abc """) assert len(config.envconfigs) == 1 envconfig = config.envconfigs['py'] assert envconfig.commands == [["abc"]] def test_whitelist_externals(self, tmpdir, newconfig): config = newconfig(""" [testenv] whitelist_externals = xyz commands=xyz [testenv:x] [testenv:py] whitelist_externals = xyz2 commands=abc """) assert len(config.envconfigs) == 2 envconfig = config.envconfigs['py'] assert envconfig.commands == [["abc"]] assert envconfig.whitelist_externals == ["xyz2"] envconfig = config.envconfigs['x'] assert envconfig.whitelist_externals == ["xyz"] def test_changedir(self, tmpdir, newconfig): config = newconfig(""" [testenv] changedir=xyz """) assert len(config.envconfigs) == 1 envconfig = config.envconfigs['python'] assert envconfig.changedir.basename == "xyz" assert envconfig.changedir == config.toxinidir.join("xyz") def test_ignore_errors(self, tmpdir, newconfig): config = newconfig(""" [testenv] ignore_errors=True """) assert len(config.envconfigs) == 1 envconfig = config.envconfigs['python'] assert envconfig.ignore_errors is True def test_envbindir(self, tmpdir, newconfig): config = newconfig(""" [testenv] basepython=python """) assert len(config.envconfigs) == 1 envconfig = config.envconfigs['python'] assert envconfig.envpython == envconfig.envbindir.join("python") @pytest.mark.parametrize("bp", ["jython", "pypy", "pypy3"]) def test_envbindir_jython(self, tmpdir, newconfig, bp): config = newconfig(""" [testenv] basepython=%s """ % bp) assert len(config.envconfigs) == 1 envconfig = config.envconfigs['python'] # on win32 and linux virtualenv uses "bin" for pypy/jython assert envconfig.envbindir.basename == "bin" if bp == "jython": assert envconfig.envpython == envconfig.envbindir.join(bp) @pytest.mark.parametrize("plat", ["win32", "linux2"]) def test_passenv_as_multiline_list(self, tmpdir, newconfig, monkeypatch, plat): monkeypatch.setattr(sys, "platform", plat) monkeypatch.setenv("A123A", "a") monkeypatch.setenv("A123B", "b") monkeypatch.setenv("BX23", "0") config = newconfig(""" [testenv] passenv = A123* # isolated comment B?23 """) assert len(config.envconfigs) == 1 envconfig = config.envconfigs['python'] if plat == "win32": assert "PATHEXT" in envconfig.passenv assert "SYSTEMDRIVE" in envconfig.passenv assert "SYSTEMROOT" in envconfig.passenv assert "TEMP" in envconfig.passenv assert "TMP" in envconfig.passenv else: assert "TMPDIR" in envconfig.passenv assert "PATH" in envconfig.passenv assert "PIP_INDEX_URL" in envconfig.passenv assert "LANG" in envconfig.passenv assert "LD_LIBRARY_PATH" in envconfig.passenv assert "A123A" in envconfig.passenv assert "A123B" in envconfig.passenv @pytest.mark.parametrize("plat", ["win32", "linux2"]) def test_passenv_as_space_separated_list(self, tmpdir, newconfig, monkeypatch, plat): monkeypatch.setattr(sys, "platform", plat) monkeypatch.setenv("A123A", "a") monkeypatch.setenv("A123B", "b") monkeypatch.setenv("BX23", "0") config = newconfig(""" [testenv] passenv = # comment A123* B?23 """) assert len(config.envconfigs) == 1 envconfig = config.envconfigs['python'] if plat == "win32": assert "PATHEXT" in envconfig.passenv assert "SYSTEMDRIVE" in envconfig.passenv assert "SYSTEMROOT" in envconfig.passenv assert "TEMP" in envconfig.passenv assert "TMP" in envconfig.passenv else: assert "TMPDIR" in envconfig.passenv assert "PATH" in envconfig.passenv assert "PIP_INDEX_URL" in envconfig.passenv assert "LANG" in envconfig.passenv assert "A123A" in envconfig.passenv assert "A123B" in envconfig.passenv def test_passenv_with_factor(self, tmpdir, newconfig, monkeypatch): monkeypatch.setenv("A123A", "a") monkeypatch.setenv("A123B", "b") monkeypatch.setenv("A123C", "c") monkeypatch.setenv("A123D", "d") monkeypatch.setenv("BX23", "0") monkeypatch.setenv("CCA43", "3") monkeypatch.setenv("CB21", "4") config = newconfig(""" [tox] envlist = {x1,x2} [testenv] passenv = x1: A123A CC* x1: CB21 # passed to both environments A123C x2: A123B A123D """) assert len(config.envconfigs) == 2 assert "A123A" in config.envconfigs["x1"].passenv assert "A123C" in config.envconfigs["x1"].passenv assert "CCA43" in config.envconfigs["x1"].passenv assert "CB21" in config.envconfigs["x1"].passenv assert "A123B" not in config.envconfigs["x1"].passenv assert "A123D" not in config.envconfigs["x1"].passenv assert "BX23" not in config.envconfigs["x1"].passenv assert "A123B" in config.envconfigs["x2"].passenv assert "A123D" in config.envconfigs["x2"].passenv assert "A123A" not in config.envconfigs["x2"].passenv assert "A123C" in config.envconfigs["x2"].passenv assert "CCA43" not in config.envconfigs["x2"].passenv assert "CB21" not in config.envconfigs["x2"].passenv assert "BX23" not in config.envconfigs["x2"].passenv def test_passenv_from_global_env(self, tmpdir, newconfig, monkeypatch): monkeypatch.setenv("A1", "a1") monkeypatch.setenv("A2", "a2") monkeypatch.setenv("TOX_TESTENV_PASSENV", "A1") config = newconfig(""" [testenv] passenv = A2 """) env = config.envconfigs["python"] assert "A1" in env.passenv assert "A2" in env.passenv def test_changedir_override(self, tmpdir, newconfig): config = newconfig(""" [testenv] changedir=xyz [testenv:python] changedir=abc basepython=python2.6 """) assert len(config.envconfigs) == 1 envconfig = config.envconfigs['python'] assert envconfig.changedir.basename == "abc" assert envconfig.changedir == config.setupdir.join("abc") def test_install_command_setting(self, newconfig): config = newconfig(""" [testenv] install_command=some_install {packages} """) envconfig = config.envconfigs['python'] assert envconfig.install_command == [ 'some_install', '{packages}'] def test_install_command_must_contain_packages(self, newconfig): py.test.raises(tox.exception.ConfigError, newconfig, """ [testenv] install_command=pip install """) def test_install_command_substitutions(self, newconfig): config = newconfig(""" [testenv] install_command=some_install --arg={toxinidir}/foo \ {envname} {opts} {packages} """) envconfig = config.envconfigs['python'] assert envconfig.install_command == [ 'some_install', '--arg=%s/foo' % config.toxinidir, 'python', '{opts}', '{packages}'] def test_pip_pre(self, newconfig): config = newconfig(""" [testenv] pip_pre=true """) envconfig = config.envconfigs['python'] assert envconfig.pip_pre def test_pip_pre_cmdline_override(self, newconfig): config = newconfig( ['--pre'], """ [testenv] pip_pre=false """) envconfig = config.envconfigs['python'] assert envconfig.pip_pre def test_downloadcache(self, newconfig, monkeypatch): monkeypatch.delenv("PIP_DOWNLOAD_CACHE", raising=False) config = newconfig(""" [testenv] downloadcache=thecache """) envconfig = config.envconfigs['python'] assert envconfig.downloadcache.basename == 'thecache' def test_downloadcache_env_override(self, newconfig, monkeypatch): monkeypatch.setenv("PIP_DOWNLOAD_CACHE", 'fromenv') config = newconfig(""" [testenv] downloadcache=somepath """) envconfig = config.envconfigs['python'] assert envconfig.downloadcache.basename == "fromenv" def test_downloadcache_only_if_in_config(self, newconfig, tmpdir, monkeypatch): monkeypatch.setenv("PIP_DOWNLOAD_CACHE", tmpdir) config = newconfig('') envconfig = config.envconfigs['python'] assert not envconfig.downloadcache def test_simple(tmpdir, newconfig): config = newconfig(""" [testenv:py26] basepython=python2.6 [testenv:py27] basepython=python2.7 """) assert len(config.envconfigs) == 2 assert "py26" in config.envconfigs assert "py27" in config.envconfigs def test_substitution_error(tmpdir, newconfig): py.test.raises(tox.exception.ConfigError, newconfig, """ [testenv:py27] basepython={xyz} """) def test_substitution_defaults(tmpdir, newconfig): config = newconfig(""" [testenv:py27] commands = {toxinidir} {toxworkdir} {envdir} {envbindir} {envtmpdir} {envpython} {homedir} {distshare} {envlogdir} """) conf = config.envconfigs['py27'] argv = conf.commands assert argv[0][0] == config.toxinidir assert argv[1][0] == config.toxworkdir assert argv[2][0] == conf.envdir assert argv[3][0] == conf.envbindir assert argv[4][0] == conf.envtmpdir assert argv[5][0] == conf.envpython assert argv[6][0] == str(config.homedir) assert argv[7][0] == config.homedir.join(".tox", "distshare") assert argv[8][0] == conf.envlogdir def test_substitution_notfound_issue246(tmpdir, newconfig): config = newconfig(""" [testenv:py27] setenv = FOO={envbindir} BAR={envsitepackagesdir} """) conf = config.envconfigs['py27'] env = conf.setenv assert 'FOO' in env assert 'BAR' in env def test_substitution_positional(self, newconfig): inisource = """ [testenv:py27] commands = cmd1 [hello] \ world cmd1 {posargs:hello} \ world """ 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['py27'] argv = conf.commands assert argv[0] == ["cmd1", "[hello]", "world"] assert argv[1] == ["cmd1", "brave", "new", "world"] def test_substitution_noargs_issue240(self, newconfig): inisource = """ [testenv] commands = echo {posargs:foo} """ conf = newconfig([""], inisource).envconfigs['python'] argv = conf.commands assert argv[0] == ["echo"] def test_posargs_backslashed_or_quoted(self, tmpdir, newconfig): inisource = """ [testenv:py27] commands = echo "\{posargs\}" = {posargs} echo "posargs = " "{posargs}" """ conf = newconfig([], inisource).envconfigs['py27'] argv = conf.commands assert argv[0] == ['echo', '\\{posargs\\}', '='] assert argv[1] == ['echo', 'posargs = ', ""] 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:py27] args_are_paths = True changedir = tests commands = cmd1 {posargs:hello} """ conf = newconfig([], inisource).envconfigs['py27'] argv = conf.commands assert argv[0] == ["cmd1", "hello"] 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['py27'] argv = conf.commands assert argv[0] == ["cmd1", "hello"] def test_rewrite_simple_posargs(self, tmpdir, newconfig): inisource = """ [testenv:py27] args_are_paths = True changedir = tests commands = cmd1 {posargs} """ conf = newconfig([], inisource).envconfigs['py27'] argv = conf.commands assert argv[0] == ["cmd1"] 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['py27'] argv = conf.commands assert argv[0] == ["cmd1", "hello"] def test_take_dependencies_from_other_testenv(self, newconfig): inisource = """ [testenv] deps= pytest pytest-cov [testenv:py27] deps= {[testenv]deps} fun """ conf = newconfig([], inisource).envconfigs['py27'] packages = [dep.name for dep in conf.deps] assert packages == ['pytest', 'pytest-cov', 'fun'] def test_take_dependencies_from_other_section(self, newconfig): inisource = """ [testing:pytest] deps= pytest pytest-cov [testing:mock] deps= mock [testenv] deps= {[testing:pytest]deps} {[testing:mock]deps} fun """ conf = newconfig([], inisource) env = conf.envconfigs['python'] packages = [dep.name for dep in env.deps] assert packages == ['pytest', 'pytest-cov', 'mock', 'fun'] def test_multilevel_substitution(self, newconfig): inisource = """ [testing:pytest] deps= pytest pytest-cov [testing:mock] deps= mock [testing] deps= {[testing:pytest]deps} {[testing:mock]deps} [testenv] deps= {[testing]deps} fun """ conf = newconfig([], inisource) env = conf.envconfigs['python'] packages = [dep.name for dep in env.deps] assert packages == ['pytest', 'pytest-cov', 'mock', 'fun'] def test_recursive_substitution_cycle_fails(self, newconfig): inisource = """ [testing:pytest] deps= {[testing:mock]deps} [testing:mock] deps= {[testing:pytest]deps} [testenv] deps= {[testing:pytest]deps} """ py.test.raises(ValueError, newconfig, [], inisource) def test_single_value_from_other_secton(self, newconfig, tmpdir): inisource = """ [common] changedir = testing [testenv] changedir = {[common]changedir} """ conf = newconfig([], inisource).envconfigs['python'] assert conf.changedir.basename == 'testing' assert conf.changedir.dirpath().realpath() == tmpdir.realpath() def test_factors(self, newconfig): inisource = """ [tox] envlist = a-x,b [testenv] deps= dep-all a: dep-a b: dep-b x: dep-x """ conf = newconfig([], inisource) configs = conf.envconfigs assert [dep.name for dep in configs['a-x'].deps] == \ ["dep-all", "dep-a", "dep-x"] assert [dep.name for dep in configs['b'].deps] == ["dep-all", "dep-b"] def test_factor_ops(self, newconfig): inisource = """ [tox] envlist = {a,b}-{x,y} [testenv] deps= a,b: dep-a-or-b a-x: dep-a-and-x {a,b}-y: dep-ab-and-y """ configs = newconfig([], inisource).envconfigs get_deps = lambda env: [dep.name for dep in configs[env].deps] assert get_deps("a-x") == ["dep-a-or-b", "dep-a-and-x"] assert get_deps("a-y") == ["dep-a-or-b", "dep-ab-and-y"] assert get_deps("b-x") == ["dep-a-or-b"] assert get_deps("b-y") == ["dep-a-or-b", "dep-ab-and-y"] def test_default_factors(self, newconfig): inisource = """ [tox] envlist = py{26,27,33,34}-dep [testenv] deps= dep: dep """ conf = newconfig([], inisource) configs = conf.envconfigs for name, config in configs.items(): assert config.basepython == 'python%s.%s' % (name[2], name[3]) @pytest.mark.issue188 def test_factors_in_boolean(self, newconfig): inisource = """ [tox] envlist = py{27,33} [testenv] recreate = py27: True """ configs = newconfig([], inisource).envconfigs assert configs["py27"].recreate assert not configs["py33"].recreate @pytest.mark.issue190 def test_factors_in_setenv(self, newconfig): inisource = """ [tox] envlist = py27,py26 [testenv] setenv = py27: X = 1 """ configs = newconfig([], inisource).envconfigs assert configs["py27"].setenv["X"] == "1" assert "X" not in configs["py26"].setenv @pytest.mark.issue191 def test_factor_use_not_checked(self, newconfig): inisource = """ [tox] envlist = py27-{a,b} [testenv] deps = b: test """ configs = newconfig([], inisource).envconfigs assert set(configs.keys()) == set(['py27-a', 'py27-b']) @pytest.mark.issue198 def test_factors_groups_touch(self, newconfig): inisource = """ [tox] envlist = {a,b}{-x,} [testenv] deps= a,b,x,y: dep """ configs = newconfig([], inisource).envconfigs assert set(configs.keys()) == set(['a', 'a-x', 'b', 'b-x']) def test_period_in_factor(self, newconfig): inisource = """ [tox] envlist = py27-{django1.6,django1.7} [testenv] deps = django1.6: Django==1.6 django1.7: Django==1.7 """ configs = newconfig([], inisource).envconfigs assert sorted(configs) == ["py27-django1.6", "py27-django1.7"] assert [d.name for d in configs["py27-django1.6"].deps] \ == ["Django==1.6"] def test_ignore_outcome(self, newconfig): inisource = """ [testenv] ignore_outcome=True """ config = newconfig([], inisource).envconfigs assert config["python"].ignore_outcome is True class TestGlobalOptions: def test_notest(self, newconfig): config = newconfig([], "") assert not config.option.notest config = newconfig(["--notest"], "") assert config.option.notest def test_verbosity(self, newconfig): config = newconfig([], "") assert config.option.verbosity == 0 config = newconfig(["-v"], "") assert config.option.verbosity == 1 config = newconfig(["-vv"], "") assert config.option.verbosity == 2 def test_substitution_jenkins_default(self, tmpdir, monkeypatch, newconfig): monkeypatch.setenv("HUDSON_URL", "xyz") config = newconfig(""" [testenv:py27] commands = {distshare} """) conf = config.envconfigs['py27'] argv = conf.commands expect_path = config.toxworkdir.join("distshare") assert argv[0][0] == expect_path def test_substitution_jenkins_context(self, tmpdir, monkeypatch, newconfig): monkeypatch.setenv("HUDSON_URL", "xyz") monkeypatch.setenv("WORKSPACE", tmpdir) config = newconfig(""" [tox:jenkins] distshare = {env:WORKSPACE}/hello [testenv:py27] commands = {distshare} """) conf = config.envconfigs['py27'] argv = conf.commands assert argv[0][0] == config.distshare assert config.distshare == tmpdir.join("hello") def test_sdist_specification(self, tmpdir, newconfig): config = newconfig(""" [tox] sdistsrc = {distshare}/xyz.zip """) assert config.sdistsrc == config.distshare.join("xyz.zip") config = newconfig([], "") assert not config.sdistsrc def test_env_selection(self, tmpdir, newconfig, monkeypatch): inisource = """ [tox] envlist = py26 [testenv:py26] basepython=python2.6 [testenv:py31] basepython=python3.1 [testenv:py27] basepython=python2.7 """ # py.test.raises(tox.exception.ConfigError, # "newconfig(['-exyz'], inisource)") config = newconfig([], inisource) assert config.envlist == ["py26"] config = newconfig(["-epy31"], inisource) assert config.envlist == ["py31"] monkeypatch.setenv("TOXENV", "py31,py26") config = newconfig([], inisource) assert config.envlist == ["py31", "py26"] monkeypatch.setenv("TOXENV", "ALL") config = newconfig([], inisource) assert config.envlist == ['py26', 'py27', 'py31'] config = newconfig(["-eALL"], inisource) assert config.envlist == ['py26', 'py27', 'py31'] def test_py_venv(self, tmpdir, newconfig, monkeypatch): config = newconfig(["-epy"], "") env = config.envconfigs['py'] assert str(env.basepython) == sys.executable def test_default_environments(self, tmpdir, newconfig, monkeypatch): envs = "py26,py27,py32,py33,py34,py35,py36,jython,pypy,pypy3" inisource = """ [tox] envlist = %s """ % envs config = newconfig([], inisource) envlist = envs.split(",") assert config.envlist == envlist for name in config.envlist: env = config.envconfigs[name] if name == "jython": assert env.basepython == "jython" elif name.startswith("pypy"): assert env.basepython == name else: assert name.startswith("py") bp = "python%s.%s" % (name[2], name[3]) assert env.basepython == bp def test_envlist_expansion(self, newconfig): inisource = """ [tox] envlist = py{26,27},docs """ config = newconfig([], inisource) assert config.envlist == ["py26", "py27", "docs"] def test_envlist_cross_product(self, newconfig): inisource = """ [tox] envlist = py{26,27}-dep{1,2} """ config = newconfig([], inisource) assert config.envlist == \ ["py26-dep1", "py26-dep2", "py27-dep1", "py27-dep2"] def test_envlist_multiline(self, newconfig): inisource = """ [tox] envlist = py27 py34 """ config = newconfig([], inisource) assert config.envlist == \ ["py27", "py34"] def test_minversion(self, tmpdir, newconfig, monkeypatch): inisource = """ [tox] minversion = 3.0 """ config = newconfig([], inisource) assert config.minversion == "3.0" def test_skip_missing_interpreters_true(self, tmpdir, newconfig, monkeypatch): inisource = """ [tox] skip_missing_interpreters = True """ config = newconfig([], inisource) assert config.option.skip_missing_interpreters def test_skip_missing_interpreters_false(self, tmpdir, newconfig, monkeypatch): inisource = """ [tox] skip_missing_interpreters = False """ config = newconfig([], inisource) assert not config.option.skip_missing_interpreters def test_defaultenv_commandline(self, tmpdir, newconfig, monkeypatch): config = newconfig(["-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 = py27 [testenv:py27] commands= xyz """ config = newconfig([], inisource) env = config.envconfigs['py27'] assert env.basepython == "python2.7" assert env.commands == [['xyz']] class TestHashseedOption: def _get_envconfigs(self, newconfig, args=None, tox_ini=None, make_hashseed=None): if args is None: args = [] if tox_ini is None: tox_ini = """ [testenv] """ if make_hashseed is None: make_hashseed = lambda: '123456789' original_make_hashseed = tox.config.make_hashseed tox.config.make_hashseed = make_hashseed try: config = newconfig(args, tox_ini) finally: tox.config.make_hashseed = original_make_hashseed return config.envconfigs def _get_envconfig(self, newconfig, args=None, tox_ini=None): envconfigs = self._get_envconfigs(newconfig, args=args, tox_ini=tox_ini) return envconfigs["python"] def _check_hashseed(self, envconfig, expected): assert envconfig.setenv['PYTHONHASHSEED'] == expected def _check_testenv(self, newconfig, expected, args=None, tox_ini=None): envconfig = self._get_envconfig(newconfig, args=args, tox_ini=tox_ini) self._check_hashseed(envconfig, expected) def test_default(self, tmpdir, newconfig): self._check_testenv(newconfig, '123456789') def test_passing_integer(self, tmpdir, newconfig): args = ['--hashseed', '1'] self._check_testenv(newconfig, '1', args=args) def test_passing_string(self, tmpdir, newconfig): args = ['--hashseed', 'random'] self._check_testenv(newconfig, 'random', args=args) def test_passing_empty_string(self, tmpdir, newconfig): args = ['--hashseed', ''] self._check_testenv(newconfig, '', args=args) @pytest.mark.xfail(sys.version_info >= (3, 2), reason="at least Debian python 3.2/3.3 have a bug: " "http://bugs.python.org/issue11884") def test_passing_no_argument(self, tmpdir, newconfig): """Test that passing no arguments to --hashseed is not allowed.""" args = ['--hashseed'] try: self._check_testenv(newconfig, '', args=args) except SystemExit: e = sys.exc_info()[1] assert e.code == 2 return assert False # getting here means we failed the test. def test_setenv(self, tmpdir, newconfig): """Check that setenv takes precedence.""" tox_ini = """ [testenv] setenv = PYTHONHASHSEED = 2 """ self._check_testenv(newconfig, '2', tox_ini=tox_ini) args = ['--hashseed', '1'] self._check_testenv(newconfig, '2', args=args, tox_ini=tox_ini) def test_noset(self, tmpdir, newconfig): args = ['--hashseed', 'noset'] envconfig = self._get_envconfig(newconfig, args=args) assert not envconfig.setenv.definitions def test_noset_with_setenv(self, tmpdir, newconfig): tox_ini = """ [testenv] setenv = PYTHONHASHSEED = 2 """ args = ['--hashseed', 'noset'] self._check_testenv(newconfig, '2', args=args, tox_ini=tox_ini) def test_one_random_hashseed(self, tmpdir, newconfig): """Check that different testenvs use the same random seed.""" tox_ini = """ [testenv:hash1] [testenv:hash2] """ next_seed = [1000] # This function is guaranteed to generate a different value each time. def make_hashseed(): next_seed[0] += 1 return str(next_seed[0]) # Check that make_hashseed() works. assert make_hashseed() == '1001' envconfigs = self._get_envconfigs(newconfig, tox_ini=tox_ini, make_hashseed=make_hashseed) self._check_hashseed(envconfigs["hash1"], '1002') # Check that hash2's value is not '1003', for example. self._check_hashseed(envconfigs["hash2"], '1002') def test_setenv_in_one_testenv(self, tmpdir, newconfig): """Check using setenv in one of multiple testenvs.""" tox_ini = """ [testenv:hash1] setenv = PYTHONHASHSEED = 2 [testenv:hash2] """ envconfigs = self._get_envconfigs(newconfig, tox_ini=tox_ini) self._check_hashseed(envconfigs["hash1"], '2') self._check_hashseed(envconfigs["hash2"], '123456789') class TestSetenv: def test_getdict_lazy(self, tmpdir, newconfig, monkeypatch): monkeypatch.setenv("X", "2") config = newconfig(""" [testenv:X] key0 = key1 = {env:X} key2 = {env:Y:1} """) envconfig = config.envconfigs["X"] val = envconfig._reader.getdict_setenv("key0") assert val["key1"] == "2" assert val["key2"] == "1" def test_getdict_lazy_update(self, tmpdir, newconfig, monkeypatch): monkeypatch.setenv("X", "2") config = newconfig(""" [testenv:X] key0 = key1 = {env:X} key2 = {env:Y:1} """) envconfig = config.envconfigs["X"] val = envconfig._reader.getdict_setenv("key0") d = {} d.update(val) assert d == {"key1": "2", "key2": "1"} def test_setenv_uses_os_environ(self, tmpdir, newconfig, monkeypatch): monkeypatch.setenv("X", "1") config = newconfig(""" [testenv:env1] setenv = X = {env:X} """) assert config.envconfigs["env1"].setenv["X"] == "1" def test_setenv_default_os_environ(self, tmpdir, newconfig, monkeypatch): monkeypatch.delenv("X", raising=False) config = newconfig(""" [testenv:env1] setenv = X = {env:X:2} """) assert config.envconfigs["env1"].setenv["X"] == "2" def test_setenv_uses_other_setenv(self, tmpdir, newconfig): config = newconfig(""" [testenv:env1] setenv = Y = 5 X = {env:Y} """) assert config.envconfigs["env1"].setenv["X"] == "5" def test_setenv_recursive_direct(self, tmpdir, newconfig): config = newconfig(""" [testenv:env1] setenv = X = {env:X:3} """) assert config.envconfigs["env1"].setenv["X"] == "3" def test_setenv_overrides(self, tmpdir, newconfig): config = newconfig(""" [testenv] setenv = PYTHONPATH = something ANOTHER_VAL=else """) assert len(config.envconfigs) == 1 envconfig = config.envconfigs['python'] assert 'PYTHONPATH' in envconfig.setenv assert 'ANOTHER_VAL' in envconfig.setenv assert envconfig.setenv['PYTHONPATH'] == 'something' assert envconfig.setenv['ANOTHER_VAL'] == 'else' def test_setenv_with_envdir_and_basepython(self, tmpdir, newconfig): config = newconfig(""" [testenv] setenv = VAL = {envdir} basepython = {env:VAL} """) assert len(config.envconfigs) == 1 envconfig = config.envconfigs['python'] assert 'VAL' in envconfig.setenv assert envconfig.setenv['VAL'] == envconfig.envdir assert envconfig.basepython == envconfig.envdir def test_setenv_ordering_1(self, tmpdir, newconfig): config = newconfig(""" [testenv] setenv= VAL={envdir} commands=echo {env:VAL} """) assert len(config.envconfigs) == 1 envconfig = config.envconfigs['python'] assert 'VAL' in envconfig.setenv assert envconfig.setenv['VAL'] == envconfig.envdir assert str(envconfig.envdir) in envconfig.commands[0] def test_setenv_cross_section_subst_issue294(self, monkeypatch, newconfig): """test that we can do cross-section substitution with setenv""" monkeypatch.delenv('TEST', raising=False) config = newconfig(""" [section] x = NOT_TEST={env:TEST:defaultvalue} [testenv] setenv = {[section]x} """) envconfig = config.envconfigs["python"] assert envconfig.setenv["NOT_TEST"] == "defaultvalue" def test_setenv_cross_section_subst_twice(self, monkeypatch, newconfig): """test that we can do cross-section substitution with setenv""" monkeypatch.delenv('TEST', raising=False) config = newconfig(""" [section] x = NOT_TEST={env:TEST:defaultvalue} [section1] y = {[section]x} [testenv] setenv = {[section1]y} """) envconfig = config.envconfigs["python"] assert envconfig.setenv["NOT_TEST"] == "defaultvalue" def test_setenv_cross_section_mixed(self, monkeypatch, newconfig): """test that we can do cross-section substitution with setenv""" monkeypatch.delenv('TEST', raising=False) config = newconfig(""" [section] x = NOT_TEST={env:TEST:defaultvalue} [testenv] setenv = {[section]x} y = 7 """) envconfig = config.envconfigs["python"] assert envconfig.setenv["NOT_TEST"] == "defaultvalue" assert envconfig.setenv["y"] == "7" class TestIndexServer: def test_indexserver(self, tmpdir, newconfig): config = newconfig(""" [tox] indexserver = name1 = XYZ name2 = ABC """) assert config.indexserver['default'].url is None assert config.indexserver['name1'].url == "XYZ" assert config.indexserver['name2'].url == "ABC" def test_parse_indexserver(self, newconfig): inisource = """ [tox] indexserver = default = http://pypi.testrun.org name1 = whatever """ config = newconfig([], inisource) assert config.indexserver['default'].url == "http://pypi.testrun.org" assert config.indexserver['name1'].url == "whatever" config = newconfig(['-i', 'qwe'], inisource) assert config.indexserver['default'].url == "qwe" assert config.indexserver['name1'].url == "whatever" config = newconfig(['-i', 'name1=abc', '-i', 'qwe2'], inisource) assert config.indexserver['default'].url == "qwe2" assert config.indexserver['name1'].url == "abc" config = newconfig(["-i", "ALL=xzy"], inisource) assert len(config.indexserver) == 2 assert config.indexserver["default"].url == "xzy" assert config.indexserver["name1"].url == "xzy" def test_multiple_homedir_relative_local_indexservers(self, newconfig): inisource = """ [tox] indexserver = default = file://{homedir}/.pip/downloads/simple local1 = file://{homedir}/.pip/downloads/simple local2 = file://{toxinidir}/downloads/simple pypi = http://pypi.python.org/simple """ config = newconfig([], inisource) expected = "file://%s/.pip/downloads/simple" % config.homedir assert config.indexserver['default'].url == expected assert config.indexserver['local1'].url == config.indexserver['default'].url class TestParseEnv: def test_parse_recreate(self, newconfig): inisource = "" config = newconfig([], inisource) assert not config.envconfigs['python'].recreate config = newconfig(['--recreate'], inisource) assert config.envconfigs['python'].recreate config = newconfig(['-r'], inisource) assert config.envconfigs['python'].recreate inisource = """ [testenv:hello] recreate = True """ config = newconfig([], inisource) assert config.envconfigs['hello'].recreate class TestCmdInvocation: def test_help(self, cmd): result = cmd.run("tox", "-h") assert not result.ret result.stdout.fnmatch_lines([ "*help*", ]) def test_version(self, cmd): result = cmd.run("tox", "--version") assert not result.ret stdout = result.stdout.str() assert tox.__version__ in stdout assert "imported from" in stdout def test_listenvs(self, cmd, initproj): initproj('listenvs', filedefs={ 'tox.ini': ''' [tox] envlist=py26,py27,py33,pypy,docs [testenv:notincluded] changedir = whatever [testenv:docs] changedir = docs ''', }) result = cmd.run("tox", "-l") result.stdout.fnmatch_lines(""" *py26* *py27* *py33* *pypy* *docs* """) def test_config_specific_ini(self, tmpdir, cmd): ini = tmpdir.ensure("hello.ini") result = cmd.run("tox", "-c", ini, "--showconfig") assert not result.ret result.stdout.fnmatch_lines([ "*config-file*hello.ini*", ]) def test_no_tox_ini(self, cmd, initproj): initproj("noini-0.5", ) result = cmd.run("tox") assert result.ret result.stderr.fnmatch_lines([ "*ERROR*tox.ini*not*found*", ]) def test_showconfig_with_force_dep_version(self, cmd, initproj): initproj('force_dep_version', filedefs={ 'tox.ini': ''' [tox] [testenv] deps= dep1==2.3 dep2 ''', }) result = cmd.run("tox", "--showconfig") assert result.ret == 0 result.stdout.fnmatch_lines([ r'*deps*dep1==2.3, dep2*', ]) # override dep1 specific version, and force version for dep2 result = cmd.run("tox", "--showconfig", "--force-dep=dep1", "--force-dep=dep2==5.0") assert result.ret == 0 result.stdout.fnmatch_lines([ r'*deps*dep1, dep2==5.0*', ]) @pytest.mark.parametrize("cmdline,envlist", [ ("-e py26", ['py26']), ("-e py26,py33", ['py26', 'py33']), ("-e py26,py26", ['py26', 'py26']), ("-e py26,py33 -e py33,py27", ['py26', 'py33', 'py33', 'py27']) ]) def test_env_spec(cmdline, envlist): args = cmdline.split() config = parseconfig(args) assert config.envlist == envlist class TestCommandParser: def test_command_parser_for_word(self): p = CommandParser('word') # import pytest; pytest.set_trace() assert list(p.words()) == ['word'] def test_command_parser_for_posargs(self): p = CommandParser('[]') assert list(p.words()) == ['[]'] def test_command_parser_for_multiple_words(self): p = CommandParser('w1 w2 w3 ') assert list(p.words()) == ['w1', ' ', 'w2', ' ', 'w3'] def test_command_parser_for_substitution_with_spaces(self): p = CommandParser('{sub:something with spaces}') assert list(p.words()) == ['{sub:something with spaces}'] def test_command_parser_with_complex_word_set(self): complex_case = ( 'word [] [literal] {something} {some:other thing} w{ord} w{or}d w{ord} ' 'w{o:rd} w{o:r}d {w:or}d w[]ord {posargs:{a key}}') p = CommandParser(complex_case) parsed = list(p.words()) expected = [ 'word', ' ', '[]', ' ', '[literal]', ' ', '{something}', ' ', '{some:other thing}', ' ', 'w', '{ord}', ' ', 'w', '{or}', 'd', ' ', 'w', '{ord}', ' ', 'w', '{o:rd}', ' ', 'w', '{o:r}', 'd', ' ', '{w:or}', 'd', ' ', 'w[]ord', ' ', '{posargs:{a key}}', ] assert parsed == expected def test_command_with_runs_of_whitespace(self): cmd = "cmd1 {item1}\n {item2}" p = CommandParser(cmd) parsed = list(p.words()) assert parsed == ['cmd1', ' ', '{item1}', '\n ', '{item2}'] def test_command_with_split_line_in_subst_arguments(self): cmd = dedent(""" cmd2 {posargs:{item2} other}""") p = CommandParser(cmd) parsed = list(p.words()) assert parsed == ['cmd2', ' ', '{posargs:{item2}\n other}'] def test_command_parsing_for_issue_10(self): cmd = "nosetests -v -a !deferred --with-doctest []" p = CommandParser(cmd) parsed = list(p.words()) assert parsed == [ 'nosetests', ' ', '-v', ' ', '-a', ' ', '!deferred', ' ', '--with-doctest', ' ', '[]' ] @pytest.mark.skipif("sys.platform != 'win32'") def test_commands_with_backslash(self, newconfig): config = newconfig([r"hello\world"], """ [testenv:py26] commands = some {posargs} """) envconfig = config.envconfigs["py26"] assert envconfig.commands[0] == ["some", r"hello\world"] tox-2.3.1/tests/test_result.py0000664000175000017500000000427112633511761016021 0ustar hpkhpk00000000000000import sys import py from tox.result import ResultLog import tox import pytest @pytest.fixture def pkg(tmpdir): p = tmpdir.join("hello-1.0.tar.gz") p.write("whatever") return p def test_pre_set_header(pkg): replog = ResultLog() d = replog.dict assert replog.dict == d assert replog.dict["reportversion"] == "1" assert replog.dict["toxversion"] == tox.__version__ assert replog.dict["platform"] == sys.platform assert replog.dict["host"] == py.std.socket.getfqdn() data = replog.dumps_json() replog2 = ResultLog.loads_json(data) assert replog2.dict == replog.dict def test_set_header(pkg): replog = ResultLog() d = replog.dict replog.set_header(installpkg=pkg) assert replog.dict == d assert replog.dict["reportversion"] == "1" assert replog.dict["toxversion"] == tox.__version__ assert replog.dict["platform"] == sys.platform assert replog.dict["host"] == py.std.socket.getfqdn() assert replog.dict["installpkg"] == { "basename": "hello-1.0.tar.gz", "md5": pkg.computehash("md5"), "sha256": pkg.computehash("sha256")} data = replog.dumps_json() replog2 = ResultLog.loads_json(data) assert replog2.dict == replog.dict def test_addenv_setpython(pkg): replog = ResultLog() replog.set_header(installpkg=pkg) envlog = replog.get_envlog("py26") envlog.set_python_info(py.path.local(sys.executable)) assert envlog.dict["python"]["version_info"] == list(sys.version_info) assert envlog.dict["python"]["version"] == sys.version assert envlog.dict["python"]["executable"] == sys.executable def test_get_commandlog(pkg): replog = ResultLog() replog.set_header(installpkg=pkg) envlog = replog.get_envlog("py26") assert "setup" not in envlog.dict setuplog = envlog.get_commandlog("setup") setuplog.add_command(["virtualenv", "..."], "venv created", 0) assert setuplog.list == [{"command": ["virtualenv", "..."], "output": "venv created", "retcode": "0"}] assert envlog.dict["setup"] setuplog2 = replog.get_envlog("py26").get_commandlog("setup") assert setuplog2.list == setuplog.list tox-2.3.1/tox.egg-info/0000775000175000017500000000000012633511761014230 5ustar hpkhpk00000000000000tox-2.3.1/tox.egg-info/PKG-INFO0000664000175000017500000000317012633511761015326 0ustar hpkhpk00000000000000Metadata-Version: 1.1 Name: tox Version: 2.3.1 Summary: virtualenv-based automation of test activities Home-page: http://tox.testrun.org/ Author: holger krekel Author-email: holger@merlinux.eu License: http://opensource.org/licenses/MIT Description: What is Tox? -------------------- Tox is a generic virtualenv management and test command line tool you can use for: * checking your package installs correctly with different Python versions and interpreters * running your tests in each of the environments, configuring your test tool of choice * acting as a frontend to Continuous Integration servers, greatly reducing boilerplate and merging CI and shell-based testing. For more information and the repository please checkout: - homepage: http://tox.testrun.org - repository: https://bitbucket.org/hpk42/tox have fun, holger krekel, 2015 Platform: unix Platform: linux Platform: osx Platform: cygwin Platform: win32 Classifier: Development Status :: 5 - Production/Stable Classifier: Intended Audience :: Developers Classifier: License :: OSI Approved :: MIT License Classifier: Operating System :: POSIX Classifier: Operating System :: Microsoft :: Windows Classifier: Operating System :: MacOS :: MacOS X Classifier: Topic :: Software Development :: Testing Classifier: Topic :: Software Development :: Libraries Classifier: Topic :: Utilities Classifier: Programming Language :: Python Classifier: Programming Language :: Python :: 3 tox-2.3.1/tox.egg-info/entry_points.txt0000664000175000017500000000010712633511761017524 0ustar hpkhpk00000000000000[console_scripts] tox=tox:cmdline tox-quickstart=tox._quickstart:main tox-2.3.1/tox.egg-info/top_level.txt0000664000175000017500000000000412633511761016754 0ustar hpkhpk00000000000000tox tox-2.3.1/tox.egg-info/requires.txt0000664000175000017500000000012612633511761016627 0ustar hpkhpk00000000000000virtualenv>=1.11.2 py>=1.4.17 pluggy>=0.3.0,<0.4.0 [:python_version=="2.6"] argparse tox-2.3.1/tox.egg-info/SOURCES.txt0000664000175000017500000000255712633511761016125 0ustar hpkhpk00000000000000CHANGELOG CONTRIBUTORS ISSUES.txt LICENSE MANIFEST.in README.rst setup.cfg setup.py tox.ini doc/Makefile doc/_getdoctarget.py doc/changelog.txt doc/check_sphinx.py doc/conf.py doc/config-v2.txt doc/config.txt doc/examples.txt doc/index.txt doc/install.txt doc/links.txt doc/plugins.txt doc/support.txt doc/_static/sphinxdoc.css doc/_templates/indexsidebar.html doc/_templates/layout.html doc/_templates/localtoc.html doc/announce/release-0.5.txt doc/announce/release-1.0.txt doc/announce/release-1.1.txt doc/announce/release-1.2.txt doc/announce/release-1.3.txt doc/announce/release-1.4.3.txt doc/announce/release-1.4.txt doc/announce/release-1.8.txt doc/announce/release-1.9.txt doc/announce/release-2.0.txt doc/example/basic.txt doc/example/devenv.txt doc/example/general.txt doc/example/jenkins.txt doc/example/nose.txt doc/example/pytest.txt doc/example/result.txt doc/example/unittest.txt tests/conftest.py tests/test_config.py tests/test_interpreters.py tests/test_quickstart.py tests/test_result.py tests/test_venv.py tests/test_z_cmdline.py tox/__init__.py tox/__main__.py tox/_pytestplugin.py tox/_quickstart.py tox/_verlib.py tox/config.py tox/hookspecs.py tox/interpreters.py tox/result.py tox/session.py tox/venv.py tox.egg-info/PKG-INFO tox.egg-info/SOURCES.txt tox.egg-info/dependency_links.txt tox.egg-info/entry_points.txt tox.egg-info/requires.txt tox.egg-info/top_level.txttox-2.3.1/tox.egg-info/dependency_links.txt0000664000175000017500000000000112633511761020276 0ustar hpkhpk00000000000000 tox-2.3.1/LICENSE0000664000175000017500000000200212633511761012723 0ustar hpkhpk00000000000000 Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.