tox-1.6.0/0000775000175000017500000000000012203141322011706 5ustar hpkhpk00000000000000tox-1.6.0/LICENSE0000664000175000017500000000200212203141321012704 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. tox-1.6.0/setup.py0000664000175000017500000000407412203141321013424 0ustar hpkhpk00000000000000import sys from setuptools import setup 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 main(): version = sys.version_info[:2] install_requires = ['virtualenv>=1.9.1', 'py>=1.4.15', ] if version < (2, 7) or (3, 0) <= version <= (3, 1): install_requires += ['argparse'] if version < (2,6): install_requires += ["simplejson"] setup( name='tox', description='virtualenv-based automation of test activities', long_description=open("README.rst").read(), url='http://tox.testrun.org/', version='1.6.0', license='http://opensource.org/licenses/MIT', platforms=['unix', 'linux', 'osx', 'cygwin', 'win32'], author='holger krekel', author_email='holger@merlinux.eu', packages=['tox', 'tox.vendor'], 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, zip_safe=True, 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-1.6.0/CONTRIBUTORS0000664000175000017500000000025112203141321013563 0ustar hpkhpk00000000000000 contributions: Krisztian Fekete Marc Abramowitz Sridhar Ratnakumar Barry Warsaw Chris Rose Jannis Leidl Ronny Pfannschmidt Lukasz Balcerzak Philip Thiem Monty Taylor tox-1.6.0/doc/0000775000175000017500000000000012203141322012453 5ustar hpkhpk00000000000000tox-1.6.0/doc/index.txt0000664000175000017500000000742312203141321014330 0ustar hpkhpk00000000000000Welcome to the tox automation project =============================================== .. note:: second training: `professional testing with Python `_ , 25-27th November 2013, Leipzig. 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 * supports :ref:`using different / multiple PyPI index servers ` * uses pip_ and setuptools_ by default. Experimental support for configuring the installer command through :confval:`install_command=ARGV`. * **cross-Python compatible**: Python-2.5 up to Python-3.3, Jython and pypy_ support. Python-2.5 is supported through a vendored ``virtualenv-1.9.1`` script. * **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 ` .. _pypy: http://pypy.org .. _`tox.ini`: :doc:configfile .. toctree:: :hidden: install examples config config-v2 support changelog links 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 .. include:: links.txt tox-1.6.0/doc/check_sphinx.py0000664000175000017500000000072112203141321015472 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-1.6.0/doc/example/0000775000175000017500000000000012203141322014106 5ustar hpkhpk00000000000000tox-1.6.0/doc/example/nose.txt0000664000175000017500000000220012203141321015604 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-1.6.0/doc/example/unittest.txt0000664000175000017500000000446612203141321016537 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-1.6.0/doc/example/pytest.txt0000664000175000017500000000741512203141321016205 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 \ [] # 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 [] # 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 [] .. _`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. Installed tests are particularly convenient when combined with `Distribute's 2to3 support` (``use_2to3``). With tests that won't be installed, the simplest way is to avoid ``__init__.py`` files in test directories; pytest will still find them but they won't be copied to other places or be found by Python's import system. .. _`fully qualified name`: http://pytest.org/latest/goodpractises.html#package-name .. _`Distribute's 2to3 support`: http://packages.python.org/distribute/python3.html .. include:: ../links.txt tox-1.6.0/doc/example/jenkins.txt0000664000175000017500000001405012203141321016307 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: 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) .. _`toxbootstrap.py`: https://bitbucket.org/hpk42/tox/raw/default/toxbootstrap.py 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 automatical sharing of your artifacts between runs or Jenkins jobs. .. include:: ../links.txt tox-1.6.0/doc/example/general.txt0000664000175000017500000001333712203141321016272 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 and sdist, followed by an install everytime 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 ok 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-1.6.0/doc/example/result.txt0000664000175000017500000000235512203141321016171 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-1.6.0/doc/example/basic.txt0000664000175000017500000001233212203141321015730 0ustar hpkhpk00000000000000 Basic 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] 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:: py24 py25 py26 py27 py30 py31 py32 py33 jython pypy However, you can also create your own test environment names, see some of the examples in :doc:`examples <../examples>`. whitelisting a 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: 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). 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. 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): 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 errno = tox.cmdline(self.test_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. tox-1.6.0/doc/example/devenv.txt0000664000175000017500000000354512203141321016144 0ustar hpkhpk00000000000000 Development environment ======================= Tox can be used to prepare development virtual environment for local projects. This feature can be useful in order to preserve environment across team members working on same project. It can be also used by deployment tools to prepare proper environments. Configuration ------------- Firstly, you need to prepare configuration for your development environment. In order to do that, we must define proper section at ``tox.ini`` file and tell at what directory environment should be created. Moreover, we need to specify python version that should be picked, and that the package should be installed with ``setup.py develop``:: [testenv:devenv] envdir = devenv basepython = python2.7 usedevelop = True commands = deps = Actually, you can configure a lot more, those are the only required settings. In example you can add ``deps`` and ``commands`` settings. Here, we tell tox not to pick ``commands`` or ``deps`` from base ``testenv`` configuration. Creating development environment -------------------------------- Once ``devenv`` section is defined we can instrument tox to create our environment:: tox -e devenv This will create an environment at path specified by ``envdir`` under ``[testenv:devenv]`` section. Full configuration example -------------------------- Let's say we want our development environment sit at ``devenv`` and pull packages from ``requirements.txt`` file which we create at the same directory as ``tox.ini`` file. We also want to speciy Python version to be 2.7, and use ``setup.py develop`` to work in development mode instead of building and installing an ``sdist`` package. Here is example configuration for that:: [testenv:devenv] envdir = devenv basepython = python2.7 usedevelop = True deps = commands = pip install -r requirements.txt tox-1.6.0/doc/config.txt0000664000175000017500000002674512203141321014476 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 # 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:hudson] ... # override [tox] settings for the hudson context # note: for hudson distshare defaults to ``{toxworkdir}/distshare``. envlist setting +++++++++++++++++++++++++ Determining the environment list that ``tox`` is to operate on happens in this order: * 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. .. confval:: install_command=ARGV .. versionadded:: 1.6 **WARNING**: This setting is **EXPERIMENTAL** so use with care and be ready to adapt your tox.ini's with post-1.6 tox releases. the ``install_command`` setting is used for installing packages into the virtual environment; both the package under test and any defined dependencies. Must contain the substitution key ``{packages}`` which will be replaced by the packages to install. You should also accept "{opts}" if you are using pip or easy_install -- it will contain index server options if you have configured them via :confval:`indexserver` and the deprecated :confval:`downloadcache` option if you have configured it. **default**:: pip install --pre {opts} {packages} **default on environments using python2.5**:: pip install --insecure {opts} {packages}`` (this will use pip<1.4 (so no ``--pre`` option) and python2.5 typically has no SSL support, therefore ``--insecure``). .. 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 easy_install/pip for processing. A line specify a file, an URL or a package name. You can additionally specify an :confval:`indexserver` to use for installing this dependency. All derived dependencies (deps required by the dep) will then be retrieved from the specified indexserver:: deps = :myindexserver:pkg .. 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:: 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:: distribute=True|False **DEPRECATED** -- as of August 2013 you should use setuptools which has merged most of distribute_ 's changes. Just use the default, Luke! In future versions of tox this option might be ignored and setuptools always chosen. **default:** False. .. confval:: sitepackages=True|False Set to ``True`` if you want to create virtual environments that also have access to globally installed packages. **default:** False, meaning that virtualenvs will be created 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 Multi-line ``name = URL`` definitions of python package servers. Depedencies 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`` Substitutions --------------------- Any ``key=value`` setting in an ini-file can make use of value substitution through the ``{...}`` string-substitution pattern. 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}`` 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. .. _`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. Substition 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 repeting the same values:: [base] deps = pytest mock pytest-xdist [testenv:dulwich] deps = dulwich {[base]deps} [testenv:mercurial] dep = mercurial {[base]deps} 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-1.6.0/doc/links.txt0000664000175000017500000000161212203141321014333 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 .. _distribute: https://pypi.python.org/pypi/distribute .. _`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-1.6.0/doc/conf.py0000664000175000017500000002104412203141321013752 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 # 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'2013, 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 short X.Y version. release = version = "1.6.0" # 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-1.6.0/doc/changelog.txt0000664000175000017500000000014012203141321015135 0ustar hpkhpk00000000000000 .. _changelog: Changelog history ================================= .. include:: ../CHANGELOG tox-1.6.0/doc/Makefile0000664000175000017500000001120712203141321014113 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) . .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 #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-1.6.0/doc/config-v2.txt0000664000175000017500000002241612203141321015012 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: * http://code.larlet.fr/django-rest-framework/src/eed0f39a7e45/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 ``platform.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,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-1.6.0/doc/install.txt0000664000175000017500000000174012203141321014663 0ustar hpkhpk00000000000000tox installation ================================== Install info in a nutshell ---------------------------------- **Pythons**: CPython 2.4-3.3, Jython-2.5.1, pypy-1.9ff **Operating systems**: Linux, Windows, OSX, Unix **Installer Requirements**: setuptools_ or Distribute_ **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-1.6.0/doc/_templates/0000775000175000017500000000000012203141322014610 5ustar hpkhpk00000000000000tox-1.6.0/doc/_templates/indexsidebar.html0000664000175000017500000000124312203141321020136 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-1.6.0/doc/_templates/layout.html0000664000175000017500000000074612203141321017021 0ustar hpkhpk00000000000000{% extends "!layout.html" %} {% block footer %} {{ super() }} {% endblock %} tox-1.6.0/doc/_templates/localtoc.html0000664000175000017500000000220612203141321017275 0ustar hpkhpk00000000000000 {%- if pagename != "search" %} {%- endif %}

quicklinks

{% extends "basic/localtoc.html" %} tox-1.6.0/doc/announce/0000775000175000017500000000000012203141322014261 5ustar hpkhpk00000000000000tox-1.6.0/doc/announce/release-1.2.txt0000664000175000017500000000266412203141321016747 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-1.6.0/doc/announce/release-0.5.txt0000664000175000017500000000174312203141321016746 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-1.6.0/doc/announce/release-1.1.txt0000664000175000017500000000347012203141321016742 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-1.6.0/doc/announce/release-1.4.3.txt0000664000175000017500000000631612203141321017110 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-1.6.0/doc/announce/release-1.4.txt0000664000175000017500000000436412203141321016750 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#substition-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-1.6.0/doc/announce/release-1.0.txt0000664000175000017500000000427312203141321016743 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-1.6.0/doc/announce/release-1.3.txt0000664000175000017500000000227312203141321016744 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-1.6.0/doc/_static/0000775000175000017500000000000012203141322014101 5ustar hpkhpk00000000000000tox-1.6.0/doc/_static/sphinxdoc.css0000664000175000017500000001371212203141321016615 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-1.6.0/doc/support.txt0000664000175000017500000000304512203141321014731 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`_. .. _`well-known Python developers`: http://merlinux.eu/people.txt .. _`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-1.6.0/doc/examples.txt0000664000175000017500000000045412203141321015034 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-1.6.0/tox.egg-info/0000775000175000017500000000000012203141322014212 5ustar hpkhpk00000000000000tox-1.6.0/tox.egg-info/entry_points.txt0000664000175000017500000000010712203141322017506 0ustar hpkhpk00000000000000[console_scripts] tox=tox:cmdline tox-quickstart=tox._quickstart:main tox-1.6.0/tox.egg-info/top_level.txt0000664000175000017500000000000412203141322016736 0ustar hpkhpk00000000000000tox tox-1.6.0/tox.egg-info/requires.txt0000664000175000017500000000003412203141322016607 0ustar hpkhpk00000000000000virtualenv>=1.9.1 py>=1.4.15tox-1.6.0/tox.egg-info/PKG-INFO0000664000175000017500000000323312203141322015310 0ustar hpkhpk00000000000000Metadata-Version: 1.1 Name: tox Version: 1.6.0 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 as 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, have fun, holger krekel, May 2013 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-1.6.0/tox.egg-info/SOURCES.txt0000664000175000017500000000244312203141322016101 0ustar hpkhpk00000000000000CHANGELOG CONTRIBUTORS ISSUES.txt LICENSE MANIFEST.in README.rst setup.py tox.ini doc/Makefile 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/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/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/_cmdline.py tox/_config.py tox/_exception.py tox/_pytestplugin.py tox/_quickstart.py tox/_venv.py tox/_verlib.py tox/interpreters.py tox/result.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.txt tox.egg-info/zip-safe tox/vendor/__init__.py tox/vendor/virtualenv.pytox-1.6.0/tox.egg-info/dependency_links.txt0000664000175000017500000000000112203141322020260 0ustar hpkhpk00000000000000 tox-1.6.0/tox.egg-info/zip-safe0000664000175000017500000000000112203141321015641 0ustar hpkhpk00000000000000 tox-1.6.0/README.rst0000664000175000017500000000116112203141321013373 0ustar hpkhpk00000000000000 What is Tox? -------------------- Tox as 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, have fun, holger krekel, May 2013 tox-1.6.0/ISSUES.txt0000664000175000017500000001166012203141321013465 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-1.6.0/PKG-INFO0000664000175000017500000000323312203141322013004 0ustar hpkhpk00000000000000Metadata-Version: 1.1 Name: tox Version: 1.6.0 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 as 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, have fun, holger krekel, May 2013 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-1.6.0/CHANGELOG0000664000175000017500000002523012203141321013121 0ustar hpkhpk000000000000001.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-1.6.0/tox/0000775000175000017500000000000012203141322012520 5ustar hpkhpk00000000000000tox-1.6.0/tox/_quickstart.py0000664000175000017500000001754512203141321015436 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 = ['py24', 'py25', 'py26', 'py27', 'py30', 'py31', 'py32', 'py33', '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 dependencies does your project 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-1.6.0/tox/_pytestplugin.py0000664000175000017500000002270112203141321016001 0ustar hpkhpk00000000000000import pytest, py import tox import os import sys from py.builtin import print_ from fnmatch import fnmatch import time from tox._config import parseconfig from tox._venv import VirtualEnv from tox._cmdline import Action from tox.result import ResultLog 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_report_header(): return "tox comes from: %r" % (tox.__file__) def pytest_funcarg__newconfig(request, tmpdir): def newconfig(args, source=None): 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) finally: old.chdir() return newconfig def pytest_funcarg__cmd(request): 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 def pytest_funcarg__mocksession(request): from tox._cmdline 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, stdout=None, stderr=None, env=None): pm = pcallMock(args, cwd, env, stdout, stderr, shell) self._pcalls.append(pm) return pm return MockSession() def pytest_funcarg__newmocksession(request): mocksession = request.getfuncargvalue("mocksession") newconfig = request.getfuncargvalue("newconfig") def newmocksession(args, source): config = newconfig(args, source) mocksession.config = config 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(name, filedefs=None): if filedefs is None: filedefs = {} parts = name.split("-") if len(parts) == 1: parts.append("0.1") name, version = parts 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-1.6.0/tox/interpreters.py0000664000175000017500000001263712203141321015630 0ustar hpkhpk00000000000000import sys import os import py import re import subprocess import inspect class Interpreters: def __init__(self): self.name2executable = {} self.executable2info = {} def get_executable(self, name): """ return path object to the executable for the given name (e.g. python2.5, python2.7, python etc.) if name is already an existing path, return name. If an interpreter cannot be found, return None. """ try: return self.name2executable[name] except KeyError: self.name2executable[name] = e = find_executable(name) return e def get_info(self, name=None, executable=None): if name is None and executable is None: raise ValueError("need to specify name or executable") if name: if executable is not None: raise ValueError("cannot specify both name, executable") executable = self.get_executable(name) if not executable: return NoInterpreterInfo(name=name) try: return self.executable2info[executable] except KeyError: info = run_and_get_interpreter_info(name, executable) self.executable2info[executable] = info return info def get_sitepackagesdir(self, info, envdir): if not info.executable: return "" envdir = str(envdir) try: res = exec_on_interpreter(info.executable, [inspect.getsource(sitepackagesdir), "print (sitepackagesdir(%r))" % envdir]) except ExecFailed: val = sys.exc_info()[1] print ("execution failed: %s -- %s" %(val.out, val.err)) return "" else: return res["dir"] def run_and_get_interpreter_info(name, executable): assert executable try: result = exec_on_interpreter(executable, [inspect.getsource(pyinfo), "print (pyinfo())"]) except ExecFailed: val = sys.exc_info()[1] return NoInterpreterInfo(name, 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): assert executable and version_info self.name = name self.executable = executable self.version_info = version_info 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": def find_executable(name): return py.path.local.sysfind(name) else: # Exceptions to the usual windows mapping win32map = { 'python': sys.executable, 'jython': "c:\jython2.5.1\jython.bat", } def locate_via_py(v_maj, v_min): ver = "-%s.%s" % (v_maj, v_min) script = "import sys; print(sys.executable)" py_exe = py.path.local.sysfind('py') if py_exe: try: exe = py_exe.sysexec(ver, '-c', script).strip() except py.process.cmdexec.Error: exe = None if exe: exe = py.path.local(exe) if exe.check(): return exe def find_executable(name): p = py.path.local.sysfind(name) if p: return p actual = None # Is this a standard PythonX.Y name? m = re.match(r"python(\d)\.(\d)", name) if m: # The standard names are in predictable places. actual = r"c:\python%s%s\python.exe" % m.groups() if not actual: actual = win32map.get(name, None) if actual: actual = py.path.local(actual) if actual.check(): return actual # The standard executables can be found as a last resort via the # Python launcher py.exe if m: locate_via_py(*m.groups()) def pyinfo(): import sys return dict(version_info=tuple(sys.version_info)) def sitepackagesdir(envdir): from distutils.sysconfig import get_python_lib return dict(dir=get_python_lib(envdir)) tox-1.6.0/tox/vendor/0000775000175000017500000000000012203141322014015 5ustar hpkhpk00000000000000tox-1.6.0/tox/vendor/__init__.py0000664000175000017500000000000212203141321016115 0ustar hpkhpk00000000000000# tox-1.6.0/tox/vendor/virtualenv.py0000664000175000017500000034050512203141321016574 0ustar hpkhpk00000000000000#!/usr/bin/env python """Create a "virtual" Python installation """ # If you change the version here, change it in setup.py # and docs/conf.py as well. __version__ = "1.9.1" # following best practices virtualenv_version = __version__ # legacy, again import base64 import sys import os import codecs import optparse import re import shutil import logging import tempfile import zlib import errno import glob import distutils.sysconfig from distutils.util import strtobool import struct import subprocess if sys.version_info < (2, 5): print('ERROR: %s' % sys.exc_info()[1]) print('ERROR: this script requires Python 2.5 or greater.') sys.exit(101) try: set except NameError: from sets import Set as set try: basestring except NameError: basestring = str try: import ConfigParser except ImportError: import configparser as ConfigParser join = os.path.join py_version = 'python%s.%s' % (sys.version_info[0], sys.version_info[1]) is_jython = sys.platform.startswith('java') is_pypy = hasattr(sys, 'pypy_version_info') is_win = (sys.platform == 'win32') is_cygwin = (sys.platform == 'cygwin') is_darwin = (sys.platform == 'darwin') abiflags = getattr(sys, 'abiflags', '') user_dir = os.path.expanduser('~') if is_win: default_storage_dir = os.path.join(user_dir, 'virtualenv') else: default_storage_dir = os.path.join(user_dir, '.virtualenv') default_config_file = os.path.join(default_storage_dir, 'virtualenv.ini') if is_pypy: expected_exe = 'pypy' elif is_jython: expected_exe = 'jython' else: expected_exe = 'python' REQUIRED_MODULES = ['os', 'posix', 'posixpath', 'nt', 'ntpath', 'genericpath', 'fnmatch', 'locale', 'encodings', 'codecs', 'stat', 'UserDict', 'readline', 'copy_reg', 'types', 're', 'sre', 'sre_parse', 'sre_constants', 'sre_compile', 'zlib'] REQUIRED_FILES = ['lib-dynload', 'config'] majver, minver = sys.version_info[:2] if majver == 2: if minver >= 6: REQUIRED_MODULES.extend(['warnings', 'linecache', '_abcoll', 'abc']) if minver >= 7: REQUIRED_MODULES.extend(['_weakrefset']) if minver <= 3: REQUIRED_MODULES.extend(['sets', '__future__']) elif majver == 3: # Some extra modules are needed for Python 3, but different ones # for different versions. REQUIRED_MODULES.extend(['_abcoll', 'warnings', 'linecache', 'abc', 'io', '_weakrefset', 'copyreg', 'tempfile', 'random', '__future__', 'collections', 'keyword', 'tarfile', 'shutil', 'struct', 'copy', 'tokenize', 'token', 'functools', 'heapq', 'bisect', 'weakref', 'reprlib']) if minver >= 2: REQUIRED_FILES[-1] = 'config-%s' % majver if minver == 3: import sysconfig platdir = sysconfig.get_config_var('PLATDIR') REQUIRED_FILES.append(platdir) # The whole list of 3.3 modules is reproduced below - the current # uncommented ones are required for 3.3 as of now, but more may be # added as 3.3 development continues. REQUIRED_MODULES.extend([ #"aifc", #"antigravity", #"argparse", #"ast", #"asynchat", #"asyncore", "base64", #"bdb", #"binhex", #"bisect", #"calendar", #"cgi", #"cgitb", #"chunk", #"cmd", #"codeop", #"code", #"colorsys", #"_compat_pickle", #"compileall", #"concurrent", #"configparser", #"contextlib", #"cProfile", #"crypt", #"csv", #"ctypes", #"curses", #"datetime", #"dbm", #"decimal", #"difflib", #"dis", #"doctest", #"dummy_threading", "_dummy_thread", #"email", #"filecmp", #"fileinput", #"formatter", #"fractions", #"ftplib", #"functools", #"getopt", #"getpass", #"gettext", #"glob", #"gzip", "hashlib", #"heapq", "hmac", #"html", #"http", #"idlelib", #"imaplib", #"imghdr", "imp", "importlib", #"inspect", #"json", #"lib2to3", #"logging", #"macpath", #"macurl2path", #"mailbox", #"mailcap", #"_markupbase", #"mimetypes", #"modulefinder", #"multiprocessing", #"netrc", #"nntplib", #"nturl2path", #"numbers", #"opcode", #"optparse", #"os2emxpath", #"pdb", #"pickle", #"pickletools", #"pipes", #"pkgutil", #"platform", #"plat-linux2", #"plistlib", #"poplib", #"pprint", #"profile", #"pstats", #"pty", #"pyclbr", #"py_compile", #"pydoc_data", #"pydoc", #"_pyio", #"queue", #"quopri", #"reprlib", "rlcompleter", #"runpy", #"sched", #"shelve", #"shlex", #"smtpd", #"smtplib", #"sndhdr", #"socket", #"socketserver", #"sqlite3", #"ssl", #"stringprep", #"string", #"_strptime", #"subprocess", #"sunau", #"symbol", #"symtable", #"sysconfig", #"tabnanny", #"telnetlib", #"test", #"textwrap", #"this", #"_threading_local", #"threading", #"timeit", #"tkinter", #"tokenize", #"token", #"traceback", #"trace", #"tty", #"turtledemo", #"turtle", #"unittest", #"urllib", #"uuid", #"uu", #"wave", #"weakref", #"webbrowser", #"wsgiref", #"xdrlib", #"xml", #"xmlrpc", #"zipfile", ]) if is_pypy: # these are needed to correctly display the exceptions that may happen # during the bootstrap REQUIRED_MODULES.extend(['traceback', 'linecache']) class Logger(object): """ Logging object for use in command-line script. Allows ranges of levels, to avoid some redundancy of displayed information. """ DEBUG = logging.DEBUG INFO = logging.INFO NOTIFY = (logging.INFO+logging.WARN)/2 WARN = WARNING = logging.WARN ERROR = logging.ERROR FATAL = logging.FATAL LEVELS = [DEBUG, INFO, NOTIFY, WARN, ERROR, FATAL] def __init__(self, consumers): self.consumers = consumers self.indent = 0 self.in_progress = None self.in_progress_hanging = False def debug(self, msg, *args, **kw): self.log(self.DEBUG, msg, *args, **kw) def info(self, msg, *args, **kw): self.log(self.INFO, msg, *args, **kw) def notify(self, msg, *args, **kw): self.log(self.NOTIFY, msg, *args, **kw) def warn(self, msg, *args, **kw): self.log(self.WARN, msg, *args, **kw) def error(self, msg, *args, **kw): self.log(self.ERROR, msg, *args, **kw) def fatal(self, msg, *args, **kw): self.log(self.FATAL, msg, *args, **kw) def log(self, level, msg, *args, **kw): if args: if kw: raise TypeError( "You may give positional or keyword arguments, not both") args = args or kw rendered = None for consumer_level, consumer in self.consumers: if self.level_matches(level, consumer_level): if (self.in_progress_hanging and consumer in (sys.stdout, sys.stderr)): self.in_progress_hanging = False sys.stdout.write('\n') sys.stdout.flush() if rendered is None: if args: rendered = msg % args else: rendered = msg rendered = ' '*self.indent + rendered if hasattr(consumer, 'write'): consumer.write(rendered+'\n') else: consumer(rendered) def start_progress(self, msg): assert not self.in_progress, ( "Tried to start_progress(%r) while in_progress %r" % (msg, self.in_progress)) if self.level_matches(self.NOTIFY, self._stdout_level()): sys.stdout.write(msg) sys.stdout.flush() self.in_progress_hanging = True else: self.in_progress_hanging = False self.in_progress = msg def end_progress(self, msg='done.'): assert self.in_progress, ( "Tried to end_progress without start_progress") if self.stdout_level_matches(self.NOTIFY): if not self.in_progress_hanging: # Some message has been printed out since start_progress sys.stdout.write('...' + self.in_progress + msg + '\n') sys.stdout.flush() else: sys.stdout.write(msg + '\n') sys.stdout.flush() self.in_progress = None self.in_progress_hanging = False def show_progress(self): """If we are in a progress scope, and no log messages have been shown, write out another '.'""" if self.in_progress_hanging: sys.stdout.write('.') sys.stdout.flush() def stdout_level_matches(self, level): """Returns true if a message at this level will go to stdout""" return self.level_matches(level, self._stdout_level()) def _stdout_level(self): """Returns the level that stdout runs at""" for level, consumer in self.consumers: if consumer is sys.stdout: return level return self.FATAL def level_matches(self, level, consumer_level): """ >>> l = Logger([]) >>> l.level_matches(3, 4) False >>> l.level_matches(3, 2) True >>> l.level_matches(slice(None, 3), 3) False >>> l.level_matches(slice(None, 3), 2) True >>> l.level_matches(slice(1, 3), 1) True >>> l.level_matches(slice(2, 3), 1) False """ if isinstance(level, slice): start, stop = level.start, level.stop if start is not None and start > consumer_level: return False if stop is not None and stop <= consumer_level: return False return True else: return level >= consumer_level #@classmethod def level_for_integer(cls, level): levels = cls.LEVELS if level < 0: return levels[0] if level >= len(levels): return levels[-1] return levels[level] level_for_integer = classmethod(level_for_integer) # create a silent logger just to prevent this from being undefined # will be overridden with requested verbosity main() is called. logger = Logger([(Logger.LEVELS[-1], sys.stdout)]) def mkdir(path): if not os.path.exists(path): logger.info('Creating %s', path) os.makedirs(path) else: logger.info('Directory %s already exists', path) def copyfileordir(src, dest): if os.path.isdir(src): shutil.copytree(src, dest, True) else: shutil.copy2(src, dest) def copyfile(src, dest, symlink=True): if not os.path.exists(src): # Some bad symlink in the src logger.warn('Cannot find file %s (bad symlink)', src) return if os.path.exists(dest): logger.debug('File %s already exists', dest) return if not os.path.exists(os.path.dirname(dest)): logger.info('Creating parent directories for %s' % os.path.dirname(dest)) os.makedirs(os.path.dirname(dest)) if not os.path.islink(src): srcpath = os.path.abspath(src) else: srcpath = os.readlink(src) if symlink and hasattr(os, 'symlink') and not is_win: logger.info('Symlinking %s', dest) try: os.symlink(srcpath, dest) except (OSError, NotImplementedError): logger.info('Symlinking failed, copying to %s', dest) copyfileordir(src, dest) else: logger.info('Copying to %s', dest) copyfileordir(src, dest) def writefile(dest, content, overwrite=True): if not os.path.exists(dest): logger.info('Writing %s', dest) f = open(dest, 'wb') f.write(content.encode('utf-8')) f.close() return else: f = open(dest, 'rb') c = f.read() f.close() if c != content.encode("utf-8"): if not overwrite: logger.notify('File %s exists with different content; not overwriting', dest) return logger.notify('Overwriting %s with new content', dest) f = open(dest, 'wb') f.write(content.encode('utf-8')) f.close() else: logger.info('Content %s already in place', dest) def rmtree(dir): if os.path.exists(dir): logger.notify('Deleting tree %s', dir) shutil.rmtree(dir) else: logger.info('Do not need to delete %s; already gone', dir) def make_exe(fn): if hasattr(os, 'chmod'): oldmode = os.stat(fn).st_mode & 0xFFF # 0o7777 newmode = (oldmode | 0x16D) & 0xFFF # 0o555, 0o7777 os.chmod(fn, newmode) logger.info('Changed mode of %s to %s', fn, oct(newmode)) def _find_file(filename, dirs): for dir in reversed(dirs): files = glob.glob(os.path.join(dir, filename)) if files and os.path.isfile(files[0]): return True, files[0] return False, filename def _install_req(py_executable, unzip=False, distribute=False, search_dirs=None, never_download=False): if search_dirs is None: search_dirs = file_search_dirs() if not distribute: egg_path = 'setuptools-*-py%s.egg' % sys.version[:3] found, egg_path = _find_file(egg_path, search_dirs) project_name = 'setuptools' bootstrap_script = EZ_SETUP_PY tgz_path = None else: # Look for a distribute egg (these are not distributed by default, # but can be made available by the user) egg_path = 'distribute-*-py%s.egg' % sys.version[:3] found, egg_path = _find_file(egg_path, search_dirs) project_name = 'distribute' if found: tgz_path = None bootstrap_script = DISTRIBUTE_FROM_EGG_PY else: # Fall back to sdist # NB: egg_path is not None iff tgz_path is None # iff bootstrap_script is a generic setup script accepting # the standard arguments. egg_path = None tgz_path = 'distribute-*.tar.gz' found, tgz_path = _find_file(tgz_path, search_dirs) bootstrap_script = DISTRIBUTE_SETUP_PY if is_jython and os._name == 'nt': # Jython's .bat sys.executable can't handle a command line # argument with newlines fd, ez_setup = tempfile.mkstemp('.py') os.write(fd, bootstrap_script) os.close(fd) cmd = [py_executable, ez_setup] else: cmd = [py_executable, '-c', bootstrap_script] if unzip and egg_path: cmd.append('--always-unzip') env = {} remove_from_env = ['__PYVENV_LAUNCHER__'] if logger.stdout_level_matches(logger.DEBUG) and egg_path: cmd.append('-v') old_chdir = os.getcwd() if egg_path is not None and os.path.exists(egg_path): logger.info('Using existing %s egg: %s' % (project_name, egg_path)) cmd.append(egg_path) if os.environ.get('PYTHONPATH'): env['PYTHONPATH'] = egg_path + os.path.pathsep + os.environ['PYTHONPATH'] else: env['PYTHONPATH'] = egg_path elif tgz_path is not None and os.path.exists(tgz_path): # Found a tgz source dist, let's chdir logger.info('Using existing %s egg: %s' % (project_name, tgz_path)) os.chdir(os.path.dirname(tgz_path)) # in this case, we want to be sure that PYTHONPATH is unset (not # just empty, really unset), else CPython tries to import the # site.py that it's in virtualenv_support remove_from_env.append('PYTHONPATH') elif never_download: logger.fatal("Can't find any local distributions of %s to install " "and --never-download is set. Either re-run virtualenv " "without the --never-download option, or place a %s " "distribution (%s) in one of these " "locations: %r" % (project_name, project_name, egg_path or tgz_path, search_dirs)) sys.exit(1) elif egg_path: logger.info('No %s egg found; downloading' % project_name) cmd.extend(['--always-copy', '-U', project_name]) else: logger.info('No %s tgz found; downloading' % project_name) logger.start_progress('Installing %s...' % project_name) logger.indent += 2 cwd = None if project_name == 'distribute': env['DONT_PATCH_SETUPTOOLS'] = 'true' def _filter_ez_setup(line): return filter_ez_setup(line, project_name) if not os.access(os.getcwd(), os.W_OK): cwd = tempfile.mkdtemp() if tgz_path is not None and os.path.exists(tgz_path): # the current working dir is hostile, let's copy the # tarball to a temp dir target = os.path.join(cwd, os.path.split(tgz_path)[-1]) shutil.copy(tgz_path, target) try: call_subprocess(cmd, show_stdout=False, filter_stdout=_filter_ez_setup, extra_env=env, remove_from_env=remove_from_env, cwd=cwd) finally: logger.indent -= 2 logger.end_progress() if cwd is not None: shutil.rmtree(cwd) if os.getcwd() != old_chdir: os.chdir(old_chdir) if is_jython and os._name == 'nt': os.remove(ez_setup) def file_search_dirs(): here = os.path.dirname(os.path.abspath(__file__)) dirs = ['.', here, join(here, 'virtualenv_support')] if os.path.splitext(os.path.dirname(__file__))[0] != 'virtualenv': # Probably some boot script; just in case virtualenv is installed... try: import virtualenv except ImportError: pass else: dirs.append(os.path.join(os.path.dirname(virtualenv.__file__), 'virtualenv_support')) return [d for d in dirs if os.path.isdir(d)] def install_setuptools(py_executable, unzip=False, search_dirs=None, never_download=False): _install_req(py_executable, unzip, search_dirs=search_dirs, never_download=never_download) def install_distribute(py_executable, unzip=False, search_dirs=None, never_download=False): _install_req(py_executable, unzip, distribute=True, search_dirs=search_dirs, never_download=never_download) _pip_re = re.compile(r'^pip-.*(zip|tar.gz|tar.bz2|tgz|tbz)$', re.I) def install_pip(py_executable, search_dirs=None, never_download=False): if search_dirs is None: search_dirs = file_search_dirs() filenames = [] for dir in search_dirs: filenames.extend([join(dir, fn) for fn in os.listdir(dir) if _pip_re.search(fn)]) filenames = [(os.path.basename(filename).lower(), i, filename) for i, filename in enumerate(filenames)] filenames.sort() filenames = [filename for basename, i, filename in filenames] if not filenames: filename = 'pip' else: filename = filenames[-1] easy_install_script = 'easy_install' if is_win: easy_install_script = 'easy_install-script.py' # There's two subtle issues here when invoking easy_install. # 1. On unix-like systems the easy_install script can *only* be executed # directly if its full filesystem path is no longer than 78 characters. # 2. A work around to [1] is to use the `python path/to/easy_install foo` # pattern, but that breaks if the path contains non-ASCII characters, as # you can't put the file encoding declaration before the shebang line. # The solution is to use Python's -x flag to skip the first line of the # script (and any ASCII decoding errors that may have occurred in that line) cmd = [py_executable, '-x', join(os.path.dirname(py_executable), easy_install_script), filename] # jython and pypy don't yet support -x if is_jython or is_pypy: cmd.remove('-x') if filename == 'pip': if never_download: logger.fatal("Can't find any local distributions of pip to install " "and --never-download is set. Either re-run virtualenv " "without the --never-download option, or place a pip " "source distribution (zip/tar.gz/tar.bz2) in one of these " "locations: %r" % search_dirs) sys.exit(1) logger.info('Installing pip from network...') else: logger.info('Installing existing %s distribution: %s' % ( os.path.basename(filename), filename)) logger.start_progress('Installing pip...') logger.indent += 2 def _filter_setup(line): return filter_ez_setup(line, 'pip') try: call_subprocess(cmd, show_stdout=False, filter_stdout=_filter_setup) finally: logger.indent -= 2 logger.end_progress() def filter_ez_setup(line, project_name='setuptools'): if not line.strip(): return Logger.DEBUG if project_name == 'distribute': for prefix in ('Extracting', 'Now working', 'Installing', 'Before', 'Scanning', 'Setuptools', 'Egg', 'Already', 'running', 'writing', 'reading', 'installing', 'creating', 'copying', 'byte-compiling', 'removing', 'Processing'): if line.startswith(prefix): return Logger.DEBUG return Logger.DEBUG for prefix in ['Reading ', 'Best match', 'Processing setuptools', 'Copying setuptools', 'Adding setuptools', 'Installing ', 'Installed ']: if line.startswith(prefix): return Logger.DEBUG return Logger.INFO class UpdatingDefaultsHelpFormatter(optparse.IndentedHelpFormatter): """ Custom help formatter for use in ConfigOptionParser that updates the defaults before expanding them, allowing them to show up correctly in the help listing """ def expand_default(self, option): if self.parser is not None: self.parser.update_defaults(self.parser.defaults) return optparse.IndentedHelpFormatter.expand_default(self, option) class ConfigOptionParser(optparse.OptionParser): """ Custom option parser which updates its defaults by by checking the configuration files and environmental variables """ def __init__(self, *args, **kwargs): self.config = ConfigParser.RawConfigParser() self.files = self.get_config_files() self.config.read(self.files) optparse.OptionParser.__init__(self, *args, **kwargs) def get_config_files(self): config_file = os.environ.get('VIRTUALENV_CONFIG_FILE', False) if config_file and os.path.exists(config_file): return [config_file] return [default_config_file] def update_defaults(self, defaults): """ Updates the given defaults with values from the config files and the environ. Does a little special handling for certain types of options (lists). """ # Then go and look for the other sources of configuration: config = {} # 1. config files config.update(dict(self.get_config_section('virtualenv'))) # 2. environmental variables config.update(dict(self.get_environ_vars())) # Then set the options with those values for key, val in config.items(): key = key.replace('_', '-') if not key.startswith('--'): key = '--%s' % key # only prefer long opts option = self.get_option(key) if option is not None: # ignore empty values if not val: continue # handle multiline configs if option.action == 'append': val = val.split() else: option.nargs = 1 if option.action == 'store_false': val = not strtobool(val) elif option.action in ('store_true', 'count'): val = strtobool(val) try: val = option.convert_value(key, val) except optparse.OptionValueError: e = sys.exc_info()[1] print("An error occured during configuration: %s" % e) sys.exit(3) defaults[option.dest] = val return defaults def get_config_section(self, name): """ Get a section of a configuration """ if self.config.has_section(name): return self.config.items(name) return [] def get_environ_vars(self, prefix='VIRTUALENV_'): """ Returns a generator with all environmental vars with prefix VIRTUALENV """ for key, val in os.environ.items(): if key.startswith(prefix): yield (key.replace(prefix, '').lower(), val) def get_default_values(self): """ Overridding to make updating the defaults after instantiation of the option parser possible, update_defaults() does the dirty work. """ if not self.process_default_values: # Old, pre-Optik 1.5 behaviour. return optparse.Values(self.defaults) defaults = self.update_defaults(self.defaults.copy()) # ours for option in self._get_all_options(): default = defaults.get(option.dest) if isinstance(default, basestring): opt_str = option.get_opt_string() defaults[option.dest] = option.check_value(opt_str, default) return optparse.Values(defaults) def main(): parser = ConfigOptionParser( version=virtualenv_version, usage="%prog [OPTIONS] DEST_DIR", formatter=UpdatingDefaultsHelpFormatter()) parser.add_option( '-v', '--verbose', action='count', dest='verbose', default=0, help="Increase verbosity") parser.add_option( '-q', '--quiet', action='count', dest='quiet', default=0, help='Decrease verbosity') parser.add_option( '-p', '--python', dest='python', metavar='PYTHON_EXE', help='The Python interpreter to use, e.g., --python=python2.5 will use the python2.5 ' 'interpreter to create the new environment. The default is the interpreter that ' 'virtualenv was installed with (%s)' % sys.executable) parser.add_option( '--clear', dest='clear', action='store_true', help="Clear out the non-root install and start from scratch") parser.set_defaults(system_site_packages=False) parser.add_option( '--no-site-packages', dest='system_site_packages', action='store_false', help="Don't give access to the global site-packages dir to the " "virtual environment (default)") parser.add_option( '--system-site-packages', dest='system_site_packages', action='store_true', help="Give access to the global site-packages dir to the " "virtual environment") parser.add_option( '--unzip-setuptools', dest='unzip_setuptools', action='store_true', help="Unzip Setuptools or Distribute when installing it") parser.add_option( '--relocatable', dest='relocatable', action='store_true', help='Make an EXISTING virtualenv environment relocatable. ' 'This fixes up scripts and makes all .pth files relative') parser.add_option( '--distribute', '--use-distribute', # the second option is for legacy reasons here. Hi Kenneth! dest='use_distribute', action='store_true', help='Use Distribute instead of Setuptools. Set environ variable ' 'VIRTUALENV_DISTRIBUTE to make it the default ') parser.add_option( '--no-setuptools', dest='no_setuptools', action='store_true', help='Do not install distribute/setuptools (or pip) ' 'in the new virtualenv.') parser.add_option( '--no-pip', dest='no_pip', action='store_true', help='Do not install pip in the new virtualenv.') parser.add_option( '--setuptools', dest='use_distribute', action='store_false', help='Use Setuptools instead of Distribute. Set environ variable ' 'VIRTUALENV_SETUPTOOLS to make it the default ') # Set this to True to use distribute by default, even in Python 2. parser.set_defaults(use_distribute=False) default_search_dirs = file_search_dirs() parser.add_option( '--extra-search-dir', dest="search_dirs", action="append", default=default_search_dirs, help="Directory to look for setuptools/distribute/pip distributions in. " "You can add any number of additional --extra-search-dir paths.") parser.add_option( '--never-download', dest="never_download", action="store_true", help="Never download anything from the network. Instead, virtualenv will fail " "if local distributions of setuptools/distribute/pip are not present.") parser.add_option( '--prompt', dest='prompt', help='Provides an alternative prompt prefix for this environment') if 'extend_parser' in globals(): extend_parser(parser) options, args = parser.parse_args() global logger if 'adjust_options' in globals(): adjust_options(options, args) verbosity = options.verbose - options.quiet logger = Logger([(Logger.level_for_integer(2 - verbosity), sys.stdout)]) if options.python and not os.environ.get('VIRTUALENV_INTERPRETER_RUNNING'): env = os.environ.copy() interpreter = resolve_interpreter(options.python) if interpreter == sys.executable: logger.warn('Already using interpreter %s' % interpreter) else: logger.notify('Running virtualenv with interpreter %s' % interpreter) env['VIRTUALENV_INTERPRETER_RUNNING'] = 'true' file = __file__ if file.endswith('.pyc'): file = file[:-1] popen = subprocess.Popen([interpreter, file] + sys.argv[1:], env=env) raise SystemExit(popen.wait()) # Force --distribute on Python 3, since setuptools is not available. if majver > 2: options.use_distribute = True if os.environ.get('PYTHONDONTWRITEBYTECODE') and not options.use_distribute: print( "The PYTHONDONTWRITEBYTECODE environment variable is " "not compatible with setuptools. Either use --distribute " "or unset PYTHONDONTWRITEBYTECODE.") sys.exit(2) if not args: print('You must provide a DEST_DIR') parser.print_help() sys.exit(2) if len(args) > 1: print('There must be only one argument: DEST_DIR (you gave %s)' % ( ' '.join(args))) parser.print_help() sys.exit(2) home_dir = args[0] if os.environ.get('WORKING_ENV'): logger.fatal('ERROR: you cannot run virtualenv while in a workingenv') logger.fatal('Please deactivate your workingenv, then re-run this script') sys.exit(3) if 'PYTHONHOME' in os.environ: logger.warn('PYTHONHOME is set. You *must* activate the virtualenv before using it') del os.environ['PYTHONHOME'] if options.relocatable: make_environment_relocatable(home_dir) return create_environment(home_dir, site_packages=options.system_site_packages, clear=options.clear, unzip_setuptools=options.unzip_setuptools, use_distribute=options.use_distribute, prompt=options.prompt, search_dirs=options.search_dirs, never_download=options.never_download, no_setuptools=options.no_setuptools, no_pip=options.no_pip) if 'after_install' in globals(): after_install(options, home_dir) def call_subprocess(cmd, show_stdout=True, filter_stdout=None, cwd=None, raise_on_returncode=True, extra_env=None, remove_from_env=None): cmd_parts = [] for part in cmd: if len(part) > 45: part = part[:20]+"..."+part[-20:] if ' ' in part or '\n' in part or '"' in part or "'" in part: part = '"%s"' % part.replace('"', '\\"') if hasattr(part, 'decode'): try: part = part.decode(sys.getdefaultencoding()) except UnicodeDecodeError: part = part.decode(sys.getfilesystemencoding()) cmd_parts.append(part) cmd_desc = ' '.join(cmd_parts) if show_stdout: stdout = None else: stdout = subprocess.PIPE logger.debug("Running command %s" % cmd_desc) if extra_env or remove_from_env: env = os.environ.copy() if extra_env: env.update(extra_env) if remove_from_env: for varname in remove_from_env: env.pop(varname, None) else: env = None try: proc = subprocess.Popen( cmd, stderr=subprocess.STDOUT, stdin=None, stdout=stdout, cwd=cwd, env=env) except Exception: e = sys.exc_info()[1] logger.fatal( "Error %s while executing command %s" % (e, cmd_desc)) raise all_output = [] if stdout is not None: stdout = proc.stdout encoding = sys.getdefaultencoding() fs_encoding = sys.getfilesystemencoding() while 1: line = stdout.readline() try: line = line.decode(encoding) except UnicodeDecodeError: line = line.decode(fs_encoding) if not line: break line = line.rstrip() all_output.append(line) if filter_stdout: level = filter_stdout(line) if isinstance(level, tuple): level, line = level logger.log(level, line) if not logger.stdout_level_matches(level): logger.show_progress() else: logger.info(line) else: proc.communicate() proc.wait() if proc.returncode: if raise_on_returncode: if all_output: logger.notify('Complete output from command %s:' % cmd_desc) logger.notify('\n'.join(all_output) + '\n----------------------------------------') raise OSError( "Command %s failed with error code %s" % (cmd_desc, proc.returncode)) else: logger.warn( "Command %s had error code %s" % (cmd_desc, proc.returncode)) def create_environment(home_dir, site_packages=False, clear=False, unzip_setuptools=False, use_distribute=False, prompt=None, search_dirs=None, never_download=False, no_setuptools=False, no_pip=False): """ Creates a new environment in ``home_dir``. If ``site_packages`` is true, then the global ``site-packages/`` directory will be on the path. If ``clear`` is true (default False) then the environment will first be cleared. """ home_dir, lib_dir, inc_dir, bin_dir = path_locations(home_dir) py_executable = os.path.abspath(install_python( home_dir, lib_dir, inc_dir, bin_dir, site_packages=site_packages, clear=clear)) install_distutils(home_dir) if not no_setuptools: if use_distribute: install_distribute(py_executable, unzip=unzip_setuptools, search_dirs=search_dirs, never_download=never_download) else: install_setuptools(py_executable, unzip=unzip_setuptools, search_dirs=search_dirs, never_download=never_download) if not no_pip: install_pip(py_executable, search_dirs=search_dirs, never_download=never_download) install_activate(home_dir, bin_dir, prompt) def is_executable_file(fpath): return os.path.isfile(fpath) and os.access(fpath, os.X_OK) def path_locations(home_dir): """Return the path locations for the environment (where libraries are, where scripts go, etc)""" # XXX: We'd use distutils.sysconfig.get_python_inc/lib but its # prefix arg is broken: http://bugs.python.org/issue3386 if is_win: # Windows has lots of problems with executables with spaces in # the name; this function will remove them (using the ~1 # format): mkdir(home_dir) if ' ' in home_dir: import ctypes GetShortPathName = ctypes.windll.kernel32.GetShortPathNameW size = max(len(home_dir)+1, 256) buf = ctypes.create_unicode_buffer(size) try: u = unicode except NameError: u = str ret = GetShortPathName(u(home_dir), buf, size) if not ret: print('Error: the path "%s" has a space in it' % home_dir) print('We could not determine the short pathname for it.') print('Exiting.') sys.exit(3) home_dir = str(buf.value) lib_dir = join(home_dir, 'Lib') inc_dir = join(home_dir, 'Include') bin_dir = join(home_dir, 'Scripts') if is_jython: lib_dir = join(home_dir, 'Lib') inc_dir = join(home_dir, 'Include') bin_dir = join(home_dir, 'bin') elif is_pypy: lib_dir = home_dir inc_dir = join(home_dir, 'include') bin_dir = join(home_dir, 'bin') elif not is_win: lib_dir = join(home_dir, 'lib', py_version) multiarch_exec = '/usr/bin/multiarch-platform' if is_executable_file(multiarch_exec): # In Mageia (2) and Mandriva distros the include dir must be like: # virtualenv/include/multiarch-x86_64-linux/python2.7 # instead of being virtualenv/include/python2.7 p = subprocess.Popen(multiarch_exec, stdout=subprocess.PIPE, stderr=subprocess.PIPE) stdout, stderr = p.communicate() # stdout.strip is needed to remove newline character inc_dir = join(home_dir, 'include', stdout.strip(), py_version + abiflags) else: inc_dir = join(home_dir, 'include', py_version + abiflags) bin_dir = join(home_dir, 'bin') return home_dir, lib_dir, inc_dir, bin_dir def change_prefix(filename, dst_prefix): prefixes = [sys.prefix] if is_darwin: prefixes.extend(( os.path.join("/Library/Python", sys.version[:3], "site-packages"), os.path.join(sys.prefix, "Extras", "lib", "python"), os.path.join("~", "Library", "Python", sys.version[:3], "site-packages"), # Python 2.6 no-frameworks os.path.join("~", ".local", "lib","python", sys.version[:3], "site-packages"), # System Python 2.7 on OSX Mountain Lion os.path.join("~", "Library", "Python", sys.version[:3], "lib", "python", "site-packages"))) if hasattr(sys, 'real_prefix'): prefixes.append(sys.real_prefix) if hasattr(sys, 'base_prefix'): prefixes.append(sys.base_prefix) prefixes = list(map(os.path.expanduser, prefixes)) prefixes = list(map(os.path.abspath, prefixes)) # Check longer prefixes first so we don't split in the middle of a filename prefixes = sorted(prefixes, key=len, reverse=True) filename = os.path.abspath(filename) for src_prefix in prefixes: if filename.startswith(src_prefix): _, relpath = filename.split(src_prefix, 1) if src_prefix != os.sep: # sys.prefix == "/" assert relpath[0] == os.sep relpath = relpath[1:] return join(dst_prefix, relpath) assert False, "Filename %s does not start with any of these prefixes: %s" % \ (filename, prefixes) def copy_required_modules(dst_prefix): import imp # If we are running under -p, we need to remove the current # directory from sys.path temporarily here, so that we # definitely get the modules from the site directory of # the interpreter we are running under, not the one # virtualenv.py is installed under (which might lead to py2/py3 # incompatibility issues) _prev_sys_path = sys.path if os.environ.get('VIRTUALENV_INTERPRETER_RUNNING'): sys.path = sys.path[1:] try: for modname in REQUIRED_MODULES: if modname in sys.builtin_module_names: logger.info("Ignoring built-in bootstrap module: %s" % modname) continue try: f, filename, _ = imp.find_module(modname) except ImportError: logger.info("Cannot import bootstrap module: %s" % modname) else: if f is not None: f.close() # special-case custom readline.so on OS X, but not for pypy: if modname == 'readline' and sys.platform == 'darwin' and not ( is_pypy or filename.endswith(join('lib-dynload', 'readline.so'))): dst_filename = join(dst_prefix, 'lib', 'python%s' % sys.version[:3], 'readline.so') else: dst_filename = change_prefix(filename, dst_prefix) copyfile(filename, dst_filename) if filename.endswith('.pyc'): pyfile = filename[:-1] if os.path.exists(pyfile): copyfile(pyfile, dst_filename[:-1]) finally: sys.path = _prev_sys_path def subst_path(prefix_path, prefix, home_dir): prefix_path = os.path.normpath(prefix_path) prefix = os.path.normpath(prefix) home_dir = os.path.normpath(home_dir) if not prefix_path.startswith(prefix): logger.warn('Path not in prefix %r %r', prefix_path, prefix) return return prefix_path.replace(prefix, home_dir, 1) def install_python(home_dir, lib_dir, inc_dir, bin_dir, site_packages, clear): """Install just the base environment, no distutils patches etc""" if sys.executable.startswith(bin_dir): print('Please use the *system* python to run this script') return if clear: rmtree(lib_dir) ## FIXME: why not delete it? ## Maybe it should delete everything with #!/path/to/venv/python in it logger.notify('Not deleting %s', bin_dir) if hasattr(sys, 'real_prefix'): logger.notify('Using real prefix %r' % sys.real_prefix) prefix = sys.real_prefix elif hasattr(sys, 'base_prefix'): logger.notify('Using base prefix %r' % sys.base_prefix) prefix = sys.base_prefix else: prefix = sys.prefix mkdir(lib_dir) fix_lib64(lib_dir) stdlib_dirs = [os.path.dirname(os.__file__)] if is_win: stdlib_dirs.append(join(os.path.dirname(stdlib_dirs[0]), 'DLLs')) elif is_darwin: stdlib_dirs.append(join(stdlib_dirs[0], 'site-packages')) if hasattr(os, 'symlink'): logger.info('Symlinking Python bootstrap modules') else: logger.info('Copying Python bootstrap modules') logger.indent += 2 try: # copy required files... for stdlib_dir in stdlib_dirs: if not os.path.isdir(stdlib_dir): continue for fn in os.listdir(stdlib_dir): bn = os.path.splitext(fn)[0] if fn != 'site-packages' and bn in REQUIRED_FILES: copyfile(join(stdlib_dir, fn), join(lib_dir, fn)) # ...and modules copy_required_modules(home_dir) finally: logger.indent -= 2 mkdir(join(lib_dir, 'site-packages')) import site site_filename = site.__file__ if site_filename.endswith('.pyc'): site_filename = site_filename[:-1] elif site_filename.endswith('$py.class'): site_filename = site_filename.replace('$py.class', '.py') site_filename_dst = change_prefix(site_filename, home_dir) site_dir = os.path.dirname(site_filename_dst) writefile(site_filename_dst, SITE_PY) writefile(join(site_dir, 'orig-prefix.txt'), prefix) site_packages_filename = join(site_dir, 'no-global-site-packages.txt') if not site_packages: writefile(site_packages_filename, '') if is_pypy or is_win: stdinc_dir = join(prefix, 'include') else: stdinc_dir = join(prefix, 'include', py_version + abiflags) if os.path.exists(stdinc_dir): copyfile(stdinc_dir, inc_dir) else: logger.debug('No include dir %s' % stdinc_dir) platinc_dir = distutils.sysconfig.get_python_inc(plat_specific=1) if platinc_dir != stdinc_dir: platinc_dest = distutils.sysconfig.get_python_inc( plat_specific=1, prefix=home_dir) if platinc_dir == platinc_dest: # Do platinc_dest manually due to a CPython bug; # not http://bugs.python.org/issue3386 but a close cousin platinc_dest = subst_path(platinc_dir, prefix, home_dir) if platinc_dest: # PyPy's stdinc_dir and prefix are relative to the original binary # (traversing virtualenvs), whereas the platinc_dir is relative to # the inner virtualenv and ignores the prefix argument. # This seems more evolved than designed. copyfile(platinc_dir, platinc_dest) # pypy never uses exec_prefix, just ignore it if sys.exec_prefix != prefix and not is_pypy: if is_win: exec_dir = join(sys.exec_prefix, 'lib') elif is_jython: exec_dir = join(sys.exec_prefix, 'Lib') else: exec_dir = join(sys.exec_prefix, 'lib', py_version) for fn in os.listdir(exec_dir): copyfile(join(exec_dir, fn), join(lib_dir, fn)) if is_jython: # Jython has either jython-dev.jar and javalib/ dir, or just # jython.jar for name in 'jython-dev.jar', 'javalib', 'jython.jar': src = join(prefix, name) if os.path.exists(src): copyfile(src, join(home_dir, name)) # XXX: registry should always exist after Jython 2.5rc1 src = join(prefix, 'registry') if os.path.exists(src): copyfile(src, join(home_dir, 'registry'), symlink=False) copyfile(join(prefix, 'cachedir'), join(home_dir, 'cachedir'), symlink=False) mkdir(bin_dir) py_executable = join(bin_dir, os.path.basename(sys.executable)) if 'Python.framework' in prefix: # OS X framework builds cause validation to break # https://github.com/pypa/virtualenv/issues/322 if os.environ.get('__PYVENV_LAUNCHER__'): os.unsetenv('__PYVENV_LAUNCHER__') if re.search(r'/Python(?:-32|-64)*$', py_executable): # The name of the python executable is not quite what # we want, rename it. py_executable = os.path.join( os.path.dirname(py_executable), 'python') logger.notify('New %s executable in %s', expected_exe, py_executable) pcbuild_dir = os.path.dirname(sys.executable) pyd_pth = os.path.join(lib_dir, 'site-packages', 'virtualenv_builddir_pyd.pth') if is_win and os.path.exists(os.path.join(pcbuild_dir, 'build.bat')): logger.notify('Detected python running from build directory %s', pcbuild_dir) logger.notify('Writing .pth file linking to build directory for *.pyd files') writefile(pyd_pth, pcbuild_dir) else: pcbuild_dir = None if os.path.exists(pyd_pth): logger.info('Deleting %s (not Windows env or not build directory python)' % pyd_pth) os.unlink(pyd_pth) if sys.executable != py_executable: ## FIXME: could I just hard link? executable = sys.executable shutil.copyfile(executable, py_executable) make_exe(py_executable) if is_win or is_cygwin: pythonw = os.path.join(os.path.dirname(sys.executable), 'pythonw.exe') if os.path.exists(pythonw): logger.info('Also created pythonw.exe') shutil.copyfile(pythonw, os.path.join(os.path.dirname(py_executable), 'pythonw.exe')) python_d = os.path.join(os.path.dirname(sys.executable), 'python_d.exe') python_d_dest = os.path.join(os.path.dirname(py_executable), 'python_d.exe') if os.path.exists(python_d): logger.info('Also created python_d.exe') shutil.copyfile(python_d, python_d_dest) elif os.path.exists(python_d_dest): logger.info('Removed python_d.exe as it is no longer at the source') os.unlink(python_d_dest) # we need to copy the DLL to enforce that windows will load the correct one. # may not exist if we are cygwin. py_executable_dll = 'python%s%s.dll' % ( sys.version_info[0], sys.version_info[1]) py_executable_dll_d = 'python%s%s_d.dll' % ( sys.version_info[0], sys.version_info[1]) pythondll = os.path.join(os.path.dirname(sys.executable), py_executable_dll) pythondll_d = os.path.join(os.path.dirname(sys.executable), py_executable_dll_d) pythondll_d_dest = os.path.join(os.path.dirname(py_executable), py_executable_dll_d) if os.path.exists(pythondll): logger.info('Also created %s' % py_executable_dll) shutil.copyfile(pythondll, os.path.join(os.path.dirname(py_executable), py_executable_dll)) if os.path.exists(pythondll_d): logger.info('Also created %s' % py_executable_dll_d) shutil.copyfile(pythondll_d, pythondll_d_dest) elif os.path.exists(pythondll_d_dest): logger.info('Removed %s as the source does not exist' % pythondll_d_dest) os.unlink(pythondll_d_dest) if is_pypy: # make a symlink python --> pypy-c python_executable = os.path.join(os.path.dirname(py_executable), 'python') if sys.platform in ('win32', 'cygwin'): python_executable += '.exe' logger.info('Also created executable %s' % python_executable) copyfile(py_executable, python_executable) if is_win: for name in 'libexpat.dll', 'libpypy.dll', 'libpypy-c.dll', 'libeay32.dll', 'ssleay32.dll', 'sqlite.dll': src = join(prefix, name) if os.path.exists(src): copyfile(src, join(bin_dir, name)) if os.path.splitext(os.path.basename(py_executable))[0] != expected_exe: secondary_exe = os.path.join(os.path.dirname(py_executable), expected_exe) py_executable_ext = os.path.splitext(py_executable)[1] if py_executable_ext == '.exe': # python2.4 gives an extension of '.4' :P secondary_exe += py_executable_ext if os.path.exists(secondary_exe): logger.warn('Not overwriting existing %s script %s (you must use %s)' % (expected_exe, secondary_exe, py_executable)) else: logger.notify('Also creating executable in %s' % secondary_exe) shutil.copyfile(sys.executable, secondary_exe) make_exe(secondary_exe) if '.framework' in prefix: if 'Python.framework' in prefix: logger.debug('MacOSX Python framework detected') # Make sure we use the the embedded interpreter inside # the framework, even if sys.executable points to # the stub executable in ${sys.prefix}/bin # See http://groups.google.com/group/python-virtualenv/ # browse_thread/thread/17cab2f85da75951 original_python = os.path.join( prefix, 'Resources/Python.app/Contents/MacOS/Python') if 'EPD' in prefix: logger.debug('EPD framework detected') original_python = os.path.join(prefix, 'bin/python') shutil.copy(original_python, py_executable) # Copy the framework's dylib into the virtual # environment virtual_lib = os.path.join(home_dir, '.Python') if os.path.exists(virtual_lib): os.unlink(virtual_lib) copyfile( os.path.join(prefix, 'Python'), virtual_lib) # And then change the install_name of the copied python executable try: mach_o_change(py_executable, os.path.join(prefix, 'Python'), '@executable_path/../.Python') except: e = sys.exc_info()[1] logger.warn("Could not call mach_o_change: %s. " "Trying to call install_name_tool instead." % e) try: call_subprocess( ["install_name_tool", "-change", os.path.join(prefix, 'Python'), '@executable_path/../.Python', py_executable]) except: logger.fatal("Could not call install_name_tool -- you must " "have Apple's development tools installed") raise if not is_win: # Ensure that 'python', 'pythonX' and 'pythonX.Y' all exist py_exe_version_major = 'python%s' % sys.version_info[0] py_exe_version_major_minor = 'python%s.%s' % ( sys.version_info[0], sys.version_info[1]) py_exe_no_version = 'python' required_symlinks = [ py_exe_no_version, py_exe_version_major, py_exe_version_major_minor ] py_executable_base = os.path.basename(py_executable) if py_executable_base in required_symlinks: # Don't try to symlink to yourself. required_symlinks.remove(py_executable_base) for pth in required_symlinks: full_pth = join(bin_dir, pth) if os.path.exists(full_pth): os.unlink(full_pth) os.symlink(py_executable_base, full_pth) if is_win and ' ' in py_executable: # There's a bug with subprocess on Windows when using a first # argument that has a space in it. Instead we have to quote # the value: py_executable = '"%s"' % py_executable # NOTE: keep this check as one line, cmd.exe doesn't cope with line breaks cmd = [py_executable, '-c', 'import sys;out=sys.stdout;' 'getattr(out, "buffer", out).write(sys.prefix.encode("utf-8"))'] logger.info('Testing executable with %s %s "%s"' % tuple(cmd)) try: proc = subprocess.Popen(cmd, stdout=subprocess.PIPE) proc_stdout, proc_stderr = proc.communicate() except OSError: e = sys.exc_info()[1] if e.errno == errno.EACCES: logger.fatal('ERROR: The executable %s could not be run: %s' % (py_executable, e)) sys.exit(100) else: raise e proc_stdout = proc_stdout.strip().decode("utf-8") proc_stdout = os.path.normcase(os.path.abspath(proc_stdout)) norm_home_dir = os.path.normcase(os.path.abspath(home_dir)) if hasattr(norm_home_dir, 'decode'): norm_home_dir = norm_home_dir.decode(sys.getfilesystemencoding()) if proc_stdout != norm_home_dir: logger.fatal( 'ERROR: The executable %s is not functioning' % py_executable) logger.fatal( 'ERROR: It thinks sys.prefix is %r (should be %r)' % (proc_stdout, norm_home_dir)) logger.fatal( 'ERROR: virtualenv is not compatible with this system or executable') if is_win: logger.fatal( 'Note: some Windows users have reported this error when they ' 'installed Python for "Only this user" or have multiple ' 'versions of Python installed. Copying the appropriate ' 'PythonXX.dll to the virtualenv Scripts/ directory may fix ' 'this problem.') sys.exit(100) else: logger.info('Got sys.prefix result: %r' % proc_stdout) pydistutils = os.path.expanduser('~/.pydistutils.cfg') if os.path.exists(pydistutils): logger.notify('Please make sure you remove any previous custom paths from ' 'your %s file.' % pydistutils) ## FIXME: really this should be calculated earlier fix_local_scheme(home_dir) if site_packages: if os.path.exists(site_packages_filename): logger.info('Deleting %s' % site_packages_filename) os.unlink(site_packages_filename) return py_executable def install_activate(home_dir, bin_dir, prompt=None): home_dir = os.path.abspath(home_dir) if is_win or is_jython and os._name == 'nt': files = { 'activate.bat': ACTIVATE_BAT, 'deactivate.bat': DEACTIVATE_BAT, 'activate.ps1': ACTIVATE_PS, } # MSYS needs paths of the form /c/path/to/file drive, tail = os.path.splitdrive(home_dir.replace(os.sep, '/')) home_dir_msys = (drive and "/%s%s" or "%s%s") % (drive[:1], tail) # Run-time conditional enables (basic) Cygwin compatibility home_dir_sh = ("""$(if [ "$OSTYPE" "==" "cygwin" ]; then cygpath -u '%s'; else echo '%s'; fi;)""" % (home_dir, home_dir_msys)) files['activate'] = ACTIVATE_SH.replace('__VIRTUAL_ENV__', home_dir_sh) else: files = {'activate': ACTIVATE_SH} # suppling activate.fish in addition to, not instead of, the # bash script support. files['activate.fish'] = ACTIVATE_FISH # same for csh/tcsh support... files['activate.csh'] = ACTIVATE_CSH files['activate_this.py'] = ACTIVATE_THIS if hasattr(home_dir, 'decode'): home_dir = home_dir.decode(sys.getfilesystemencoding()) vname = os.path.basename(home_dir) for name, content in files.items(): content = content.replace('__VIRTUAL_PROMPT__', prompt or '') content = content.replace('__VIRTUAL_WINPROMPT__', prompt or '(%s)' % vname) content = content.replace('__VIRTUAL_ENV__', home_dir) content = content.replace('__VIRTUAL_NAME__', vname) content = content.replace('__BIN_NAME__', os.path.basename(bin_dir)) writefile(os.path.join(bin_dir, name), content) def install_distutils(home_dir): distutils_path = change_prefix(distutils.__path__[0], home_dir) mkdir(distutils_path) ## FIXME: maybe this prefix setting should only be put in place if ## there's a local distutils.cfg with a prefix setting? home_dir = os.path.abspath(home_dir) ## FIXME: this is breaking things, removing for now: #distutils_cfg = DISTUTILS_CFG + "\n[install]\nprefix=%s\n" % home_dir writefile(os.path.join(distutils_path, '__init__.py'), DISTUTILS_INIT) writefile(os.path.join(distutils_path, 'distutils.cfg'), DISTUTILS_CFG, overwrite=False) def fix_local_scheme(home_dir): """ Platforms that use the "posix_local" install scheme (like Ubuntu with Python 2.7) need to be given an additional "local" location, sigh. """ try: import sysconfig except ImportError: pass else: if sysconfig._get_default_scheme() == 'posix_local': local_path = os.path.join(home_dir, 'local') if not os.path.exists(local_path): os.mkdir(local_path) for subdir_name in os.listdir(home_dir): if subdir_name == 'local': continue os.symlink(os.path.abspath(os.path.join(home_dir, subdir_name)), \ os.path.join(local_path, subdir_name)) def fix_lib64(lib_dir): """ Some platforms (particularly Gentoo on x64) put things in lib64/pythonX.Y instead of lib/pythonX.Y. If this is such a platform we'll just create a symlink so lib64 points to lib """ if [p for p in distutils.sysconfig.get_config_vars().values() if isinstance(p, basestring) and 'lib64' in p]: logger.debug('This system uses lib64; symlinking lib64 to lib') assert os.path.basename(lib_dir) == 'python%s' % sys.version[:3], ( "Unexpected python lib dir: %r" % lib_dir) lib_parent = os.path.dirname(lib_dir) top_level = os.path.dirname(lib_parent) lib_dir = os.path.join(top_level, 'lib') lib64_link = os.path.join(top_level, 'lib64') assert os.path.basename(lib_parent) == 'lib', ( "Unexpected parent dir: %r" % lib_parent) if os.path.lexists(lib64_link): return os.symlink('lib', lib64_link) def resolve_interpreter(exe): """ If the executable given isn't an absolute path, search $PATH for the interpreter """ if os.path.abspath(exe) != exe: paths = os.environ.get('PATH', '').split(os.pathsep) for path in paths: if os.path.exists(os.path.join(path, exe)): exe = os.path.join(path, exe) break if not os.path.exists(exe): logger.fatal('The executable %s (from --python=%s) does not exist' % (exe, exe)) raise SystemExit(3) if not is_executable(exe): logger.fatal('The executable %s (from --python=%s) is not executable' % (exe, exe)) raise SystemExit(3) return exe def is_executable(exe): """Checks a file is executable""" return os.access(exe, os.X_OK) ############################################################ ## Relocating the environment: def make_environment_relocatable(home_dir): """ Makes the already-existing environment use relative paths, and takes out the #!-based environment selection in scripts. """ home_dir, lib_dir, inc_dir, bin_dir = path_locations(home_dir) activate_this = os.path.join(bin_dir, 'activate_this.py') if not os.path.exists(activate_this): logger.fatal( 'The environment doesn\'t have a file %s -- please re-run virtualenv ' 'on this environment to update it' % activate_this) fixup_scripts(home_dir) fixup_pth_and_egg_link(home_dir) ## FIXME: need to fix up distutils.cfg OK_ABS_SCRIPTS = ['python', 'python%s' % sys.version[:3], 'activate', 'activate.bat', 'activate_this.py'] def fixup_scripts(home_dir): # This is what we expect at the top of scripts: shebang = '#!%s/bin/python' % os.path.normcase(os.path.abspath(home_dir)) # This is what we'll put: new_shebang = '#!/usr/bin/env python%s' % sys.version[:3] if is_win: bin_suffix = 'Scripts' else: bin_suffix = 'bin' bin_dir = os.path.join(home_dir, bin_suffix) home_dir, lib_dir, inc_dir, bin_dir = path_locations(home_dir) for filename in os.listdir(bin_dir): filename = os.path.join(bin_dir, filename) if not os.path.isfile(filename): # ignore subdirs, e.g. .svn ones. continue f = open(filename, 'rb') try: try: lines = f.read().decode('utf-8').splitlines() except UnicodeDecodeError: # This is probably a binary program instead # of a script, so just ignore it. continue finally: f.close() if not lines: logger.warn('Script %s is an empty file' % filename) continue if not lines[0].strip().startswith(shebang): if os.path.basename(filename) in OK_ABS_SCRIPTS: logger.debug('Cannot make script %s relative' % filename) elif lines[0].strip() == new_shebang: logger.info('Script %s has already been made relative' % filename) else: logger.warn('Script %s cannot be made relative (it\'s not a normal script that starts with %s)' % (filename, shebang)) continue logger.notify('Making script %s relative' % filename) script = relative_script([new_shebang] + lines[1:]) f = open(filename, 'wb') f.write('\n'.join(script).encode('utf-8')) f.close() def relative_script(lines): "Return a script that'll work in a relocatable environment." activate = "import os; activate_this=os.path.join(os.path.dirname(os.path.realpath(__file__)), 'activate_this.py'); execfile(activate_this, dict(__file__=activate_this)); del os, activate_this" # Find the last future statement in the script. If we insert the activation # line before a future statement, Python will raise a SyntaxError. activate_at = None for idx, line in reversed(list(enumerate(lines))): if line.split()[:3] == ['from', '__future__', 'import']: activate_at = idx + 1 break if activate_at is None: # Activate after the shebang. activate_at = 1 return lines[:activate_at] + ['', activate, ''] + lines[activate_at:] def fixup_pth_and_egg_link(home_dir, sys_path=None): """Makes .pth and .egg-link files use relative paths""" home_dir = os.path.normcase(os.path.abspath(home_dir)) if sys_path is None: sys_path = sys.path for path in sys_path: if not path: path = '.' if not os.path.isdir(path): continue path = os.path.normcase(os.path.abspath(path)) if not path.startswith(home_dir): logger.debug('Skipping system (non-environment) directory %s' % path) continue for filename in os.listdir(path): filename = os.path.join(path, filename) if filename.endswith('.pth'): if not os.access(filename, os.W_OK): logger.warn('Cannot write .pth file %s, skipping' % filename) else: fixup_pth_file(filename) if filename.endswith('.egg-link'): if not os.access(filename, os.W_OK): logger.warn('Cannot write .egg-link file %s, skipping' % filename) else: fixup_egg_link(filename) def fixup_pth_file(filename): lines = [] prev_lines = [] f = open(filename) prev_lines = f.readlines() f.close() for line in prev_lines: line = line.strip() if (not line or line.startswith('#') or line.startswith('import ') or os.path.abspath(line) != line): lines.append(line) else: new_value = make_relative_path(filename, line) if line != new_value: logger.debug('Rewriting path %s as %s (in %s)' % (line, new_value, filename)) lines.append(new_value) if lines == prev_lines: logger.info('No changes to .pth file %s' % filename) return logger.notify('Making paths in .pth file %s relative' % filename) f = open(filename, 'w') f.write('\n'.join(lines) + '\n') f.close() def fixup_egg_link(filename): f = open(filename) link = f.readline().strip() f.close() if os.path.abspath(link) != link: logger.debug('Link in %s already relative' % filename) return new_link = make_relative_path(filename, link) logger.notify('Rewriting link %s in %s as %s' % (link, filename, new_link)) f = open(filename, 'w') f.write(new_link) f.close() def make_relative_path(source, dest, dest_is_directory=True): """ Make a filename relative, where the filename is dest, and it is being referred to from the filename source. >>> make_relative_path('/usr/share/something/a-file.pth', ... '/usr/share/another-place/src/Directory') '../another-place/src/Directory' >>> make_relative_path('/usr/share/something/a-file.pth', ... '/home/user/src/Directory') '../../../home/user/src/Directory' >>> make_relative_path('/usr/share/a-file.pth', '/usr/share/') './' """ source = os.path.dirname(source) if not dest_is_directory: dest_filename = os.path.basename(dest) dest = os.path.dirname(dest) dest = os.path.normpath(os.path.abspath(dest)) source = os.path.normpath(os.path.abspath(source)) dest_parts = dest.strip(os.path.sep).split(os.path.sep) source_parts = source.strip(os.path.sep).split(os.path.sep) while dest_parts and source_parts and dest_parts[0] == source_parts[0]: dest_parts.pop(0) source_parts.pop(0) full_parts = ['..']*len(source_parts) + dest_parts if not dest_is_directory: full_parts.append(dest_filename) if not full_parts: # Special case for the current directory (otherwise it'd be '') return './' return os.path.sep.join(full_parts) ############################################################ ## Bootstrap script creation: def create_bootstrap_script(extra_text, python_version=''): """ Creates a bootstrap script, which is like this script but with extend_parser, adjust_options, and after_install hooks. This returns a string that (written to disk of course) can be used as a bootstrap script with your own customizations. The script will be the standard virtualenv.py script, with your extra text added (your extra text should be Python code). If you include these functions, they will be called: ``extend_parser(optparse_parser)``: You can add or remove options from the parser here. ``adjust_options(options, args)``: You can change options here, or change the args (if you accept different kinds of arguments, be sure you modify ``args`` so it is only ``[DEST_DIR]``). ``after_install(options, home_dir)``: After everything is installed, this function is called. This is probably the function you are most likely to use. An example would be:: def after_install(options, home_dir): subprocess.call([join(home_dir, 'bin', 'easy_install'), 'MyPackage']) subprocess.call([join(home_dir, 'bin', 'my-package-script'), 'setup', home_dir]) This example immediately installs a package, and runs a setup script from that package. If you provide something like ``python_version='2.5'`` then the script will start with ``#!/usr/bin/env python2.5`` instead of ``#!/usr/bin/env python``. You can use this when the script must be run with a particular Python version. """ filename = __file__ if filename.endswith('.pyc'): filename = filename[:-1] f = codecs.open(filename, 'r', encoding='utf-8') content = f.read() f.close() py_exe = 'python%s' % python_version content = (('#!/usr/bin/env %s\n' % py_exe) + '## WARNING: This file is generated\n' + content) return content.replace('##EXT' 'END##', extra_text) ##EXTEND## def convert(s): b = base64.b64decode(s.encode('ascii')) return zlib.decompress(b).decode('utf-8') ##file site.py SITE_PY = convert(""" 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 UM3ckC/Q8aP8VLy+mjYY5MxOtAdgjULwf2RtvCc= """) ##file ez_setup.py EZ_SETUP_PY = convert(""" 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 qL7+C1AkCcw= """) ##file distribute_from_egg.py DISTRIBUTE_FROM_EGG_PY = convert(""" eJw9j8tqAzEMRfcG/4MgmxQyptkGusonZBmGoGTUGYFfWPKE6dfXTkM3gqt7rh47OKP3NMF3SQFW LlrRU1zhybpAxoKBlIqcrNnBdRjQP3GTocYfzmNrrCPQPN9iwzpxSQfQhWBi0cL3qtRtYIG/4Mv0 KApY5hooqrOGQ05FQTaxptF9Fnx16Rq0XofjaE1XGXVxHIWK7j8P8EY/rHndLqQ1a0pe3COFgHFy hLLdWkDbi/DeEpCjNb3u/zccT2Ob8gtnwVyI """) ##file distribute_setup.py DISTRIBUTE_SETUP_PY = convert(""" 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 """) ##file activate.sh ACTIVATE_SH = convert(""" eJytVVFvokAQfudXTLEPtTlLeo9tvMSmJpq02hSvl7u2wRUG2QR2DSxSe7n/frOACEVNLlceRHa+ nfl25pvZDswCnoDPQ4QoTRQsENIEPci4CsBMZBq7CAsuLOYqvmYKTTj3YxnBgiXBudGBjUzBZUJI BXEqgCvweIyuCjeG4eF2F5x14bcB9KQiQQWrjSddI1/oQIx6SYYeoFjzWIoIhYI1izlbhJjkKO7D M/QEmKfO9O7WeRo/zr4P7pyHwWxkwitcgwpQ5Ej96OX+PmiFwLeVjFUOrNYKaq1Nud3nR2n8nI2m k9H0friPTGVsUdptaxGrTEfpNVFEskxpXtUkkCkl1UNF9cgLBkx48J4EXyALuBtAwNYIjF5kcmUU abMKmMq1ULoiRbgsDEkTSsKSGFCJ6Z8vY/2xYiSacmtyAfCDdCNTVZoVF8vSTQOoEwSnOrngBkws MYGMBMg8/bMBLSYKS7pYEXP0PqT+ZmBT0Xuy+Pplj5yn4aM9nk72JD8/Wi+Gr98sD9eWSMOwkapD BbUv91XSvmyVkICt2tmXR4tWmrcUCsjWOpw87YidEC8i0gdTSOFhouJUNxR+4NYBG0MftoCTD9F7 2rTtxG3oPwY1b2HncYwhrlmj6Wq924xtGDWqfdNxap+OYxplEurnMVo9RWks+rH8qKEtx7kZT5zJ 4H7oOFclrN6uFe+d+nW2aIUsSgs/42EIPuOhXq+jEo3S6tX6w2ilNkDnIpHCWdEQhFgwj9pkk7FN l/y5eQvRSIQ5+TrL05lewxWpt/Lbhes5cJF3mLET1MGhcKCF+40tNWnUulxrpojwDo2sObdje3Bz N3QeHqf3D7OjEXMVV8LN3ZlvuzoWHqiUcNKHtwNd0IbvPGKYYM31nPKCgkUILw3KL+Y8l7aO1ArS Ad37nIU0fCj5NE5gQCuC5sOSu+UdI2NeXg/lFkQIlFpdWVaWZRfvqGiirC9o6liJ9FXGYrSY9mI1 D/Ncozgn13vJvsznr7DnkJWXsyMH7e42ljdJ+aqNDF1bFnKWFLdj31xtaJYK6EXFgqmV/ymD/ROG +n8O9H8f5vsGOWXsL1+1k3g= """) ##file activate.fish ACTIVATE_FISH = convert(""" eJyVVWFv2jAQ/c6vuBoqQVWC9nVSNVGVCaS2VC2rNLWVZZILWAs2s52wVvvxsyEJDrjbmgpK7PP5 3bt3d22YLbmGlGcIq1wbmCPkGhPYcLMEEsGciwGLDS+YwSjlekngLFVyBe73GXSXxqw/DwbuTS8x yyKpFr1WG15lDjETQhpQuQBuIOEKY5O9tlppLqxHKSDByjVAPwEy+mXtCq5MzjIUBTCRgEKTKwFG gpBqxTLYXgN2myspVigMaYF92tZSowGZJf4mFExxNs9Qb614CgZtmH0BpEOn11f0cXI/+za8pnfD 2ZjA1sg9zlV/8QvcMhxbNu0QwgYokn/d+n02nt6Opzcjcnx1vXcIoN74O4ymWQXmHURfJw9jenc/ vbmb0enj6P5+cuVhqlKm3S0u2XRtRbA2QQAhV7VhBF0rsgUX9Ur1rBUXJgVSy8O751k8mzY5OrKH RW3eaQhYGTr8hrXO59ALhxQ83mCsDLAid3T72CCSdJhaFE+fXgicXAARUiR2WeVO37gH3oYHzFKo 9k7CaPZ1UeNwH1tWuXA4uFKYYcEa8vaKqXl7q1UpygMPhFLvlVKyNzsSM3S2km7UBOl4xweUXk5u 6e3wZmQ9leY1XE/Ili670tr9g/5POBBpGIJXCCF79L1siarl/dbESa8mD8PL61GpzqpzuMS7tqeB 1YkALrRBloBMbR9yLcVx7frQAgUqR7NZIuzkEu110gbNit1enNs82Rx5utq7Z3prU78HFRgulqNC OTwbqJa9vkJFclQgZSjbKeBgSsUtCtt9D8OwAbIVJuewQdfvQRaoFE9wd1TmCuRG7OgJ1bVXGHc7 z5WDL/WW36v2oi37CyVBak61+yPBA9C1qqGxzKQqZ0oPuocU9hpud0PIp8sDHkXR1HKkNlzjuUWA a0enFUyzOWZA4yXGP+ZMI3Tdt2OuqU/SO4q64526cPE0A7ZyW2PMbWZiZ5HamIZ2RcCKLXhcDl2b vXL+eccQoRzem80mekPDEiyiWK4GWqZmwxQOmPM0eIfgp1P9cqrBsewR2p/DPMtt+pfcYM+Ls2uh hALufTAdmGl8B1H3VPd2af8fQAc4PgqjlIBL9cGQqNpXaAwe3LrtVn8AkZTUxg== """) ##file activate.csh ACTIVATE_CSH = convert(""" eJx9VG1P2zAQ/u5fcYQKNgTNPtN1WxlIQ4KCUEGaxuQ6yYVYSuzKdhqVX7+zk3bpy5YPUXL3PPfc ne98DLNCWshliVDV1kGCUFvMoJGugMjq2qQIiVSxSJ1cCofD1BYRnOVGV0CfZ0N2DD91DalQSjsw tQLpIJMGU1euvPe7QeJlkKzgWixlhnAt4aoUVsLnLBiy5NtbJWQ5THX1ZciYKKWwkOFaE04dUm6D r/zh7pq/3D7Nnid3/HEy+wFHY/gEJydg0aFaQrBFgz1c5DG1IhTs+UZgsBC2GMFBlaeH+8dZXwcW VPvCjXdlAvCfQsE7al0+07XjZvrSCUevR5dnkVeKlFYZmUztG4BdzL2u9KyLVabTU0bdfg7a0hgs cSmUg6UwUiQl2iHrcbcVGNvPCiLOe7+cRwG13z9qRGgx2z6DHjfm/Op2yqeT+xvOLzs0PTKHDz2V tkckFHoQfQRXoGJAj9el0FyJCmEMhzgMS4sB7KPOE2ExoLcSieYwDvR+cP8cg11gKkVJc2wRcm1g QhYFlXiTaTfO2ki0fQoiFM4tLuO4aZrhOzqR4dIPcWx17hphMBY+Srwh7RTyN83XOWkcSPh1Pg/k TXX/jbJTbMtUmcxZ+/bbqOsy82suFQg/BhdSOTRhMNBHlUarCpU7JzBhmkKmRejKOQzayQe6MWoa n1wqWmuh6LZAaHxcdeqIlVLhIBJdO9/kbl0It2oEXQj+eGjJOuvOIR/YGRqvFhttUB2XTvLXYN2H 37CBdbW2W7j2r2+VsCn0doVWcFG1/4y1VwBjfwAyoZhD """) ##file activate.bat ACTIVATE_BAT = convert(""" eJx9UdEKgjAUfW6wfxjiIH+hEDKUFHSKLCMI7kNOEkIf9P9pTJ3OLJ/03HPPPed4Es9XS9qqwqgT PbGKKOdXL4aAFS7A4gvAwgijuiKlqOpGlATS2NeMLE+TjJM9RkQ+SmqAXLrBo1LLIeLdiWlD6jZt r7VNubWkndkXaxg5GO3UaOOKS6drO3luDDiO5my3iA0YAKGzPRV1ack8cOdhysI0CYzIPzjSiH5X 0QcvC8Lfaj0emsVKYF2rhL5L3fCkVjV76kShi59NHwDniAHzkgDgqBcwOgTMx+gDQQqXCw== """) ##file deactivate.bat DEACTIVATE_BAT = convert(""" eJxzSE3OyFfIT0vj4ipOLVEI8wwKCXX0iXf1C7Pl4spMU0hJTcvMS01RiPf3cYmHyQYE+fsGhCho cCkAAUibEkTEVhWLMlUlLk6QGixStlyaeCyJDPHw9/Pw93VFsQguim4ZXAJoIUw5DhX47XUM8UCx EchHtwsohN1bILUgw61c/Vy4AJYPYm4= """) ##file activate.ps1 ACTIVATE_PS = convert(""" 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 QastYw== """) ##file distutils-init.py DISTUTILS_INIT = convert(""" eJytV1uL4zYUfvevOE0ottuMW9q3gVDa3aUMXXbLMlDKMBiNrSTqOJKRlMxkf33PkXyRbGe7Dw2E UXTu37lpxLFV2oIyifAncxmOL0xLIfcG+gv80x9VW6maw7o/CANSWWBwFtqeWMPlGY6qPjV8A0bB C4eKSTgZ5LRgFeyErMEeOBhbN+Ipgeizhjtnhkn7DdyjuNLPoCS0l/ayQTG0djwZC08cLXozeMss aG5EzQ0IScpnWtHSTXuxByV/QCmxE7y+eS0uxWeoheaVVfqSJHiU7Mhhi6gULbOHorshkrEnKxpT 0n3A8Y8SMpuwZx6aoix3ouFlmW8gHRSkeSJ2g7hU+kiHLDaQw3bmRDaTGfTnty7gPm0FHbIBg9U9 oh1kZzAFLaue2R6htPCtAda2nGlDSUJ4PZBgCJBGVcwKTAMz/vJiLD+Oin5Z5QlvDPdulC6EsiyE NFzb7McNTKJzbJqzphx92VKRFY1idenzmq3K0emRcbWBD0ryqc4NZGmKOOOX9Pz5x+/l27tP797c f/z0d+4NruGNai8uAM0bfsYaw8itFk8ny41jsfpyO+BWlpqfhcG4yxLdi/0tQqoT4a8Vby382mt8 p7XSo7aWGdPBc+b6utaBmCQ7rQKQoWtAuthQCiold2KfJIPTT8xwg9blPumc+YDZC/wYGdAyHpJk vUbHbHWAp5No6pK/WhhLEWrFjUwtPEv1Agf8YmnsuXUQYkeZoHm8ogP16gt2uHoxcEMdf2C6pmbw hUMsWGhanboh4IzzmsIpWs134jVPqD/c74bZHdY69UKKSn/+KfVhxLgUlToemayLMYQOqfEC61bh cbhwaqoGUzIyZRFHPmau5juaWqwRn3mpWmoEA5nhzS5gog/5jbcFQqOZvmBasZtwYlG93k5GEiyw buHhMWLjDarEGpMGB2LFs5nIJkhp/nUmZneFaRth++lieJtHepIvKgx6PJqIlD9X2j6pG1i9x3pZ 5bHuCPFiirGHeO7McvoXkz786GaKVzC9DSpnOxJdc4xm6NSVq7lNEnKdVlnpu9BNYoKX2Iq3wvgh gGEUM66kK6j4NiyoneuPLSwaCWDxczgaolEWpiMyDVDb7dNuLAbriL8ig8mmeju31oNvQdpnvEPC 1vAXbWacGRVrGt/uXN/gU0CDDwgooKRrHfTBb1/s9lYZ8ZqOBU0yLvpuP6+K9hLFsvIjeNhBi0KL MlOuWRn3FRwx5oHXjl0YImUx0+gLzjGchrgzca026ETmYJzPD+IpuKzNi8AFn048Thd63OdD86M6 84zE8yQm0VqXdbbgvub2pKVnS76icBGdeTHHXTKspUmr4NYo/furFLKiMdQzFjHJNcdAnMhltBJK 0/IKX3DVFqvPJ2dLE7bDBkH0l/PJ29074+F0CsGYOxsb7U3myTUncYfXqnLLfa6sJybX4g+hmcjO kMRBfA1JellfRRKJcyRpxdS4rIl6FdmQCWjo/o9Qz7yKffoP4JHjOvABcRn4CZIT2RH4jnxmfpVG qgLaAvQBNfuO6X0/Ux02nb4FKx3vgP+XnkX0QW9pLy/NsXgdN24dD3LxO2Nwil7Zlc1dqtP3d7/h kzp1/+7hGBuY4pk0XD/0Ao/oTe/XGrfyM773aB7iUhgkpy+dwAMalxMP0DrBcsVw/6p25+/hobP9 GBknrWExDhLJ1bwt1NcCNblaFbMKCyvmX0PeRaQ= """) ##file distutils.cfg DISTUTILS_CFG = convert(""" eJxNj00KwkAMhfc9xYNuxe4Ft57AjYiUtDO1wXSmNJnK3N5pdSEEAu8nH6lxHVlRhtDHMPATA4uH xJ4EFmGbvfJiicSHFRzUSISMY6hq3GLCRLnIvSTnEefN0FIjw5tF0Hkk9Q5dRunBsVoyFi24aaLg 9FDOlL0FPGluf4QjcInLlxd6f6rqkgPu/5nHLg0cXCscXoozRrP51DRT3j9QNl99AP53T2Q= """) ##file activate_this.py ACTIVATE_THIS = convert(""" eJyNU01v2zAMvetXEB4K21jmDOstQA4dMGCHbeihlyEIDMWmG62yJEiKE//7kXKdpN2KzYBt8euR fKSyLPs8wiEo8wh4wqZTGou4V6Hm0wJa1cSiTkJdr8+GsoTRHuCotBayiWqQEYGtMCgfD1KjGYBe 5a3p0cRKiAe2NtLADikftnDco0ko/SFEVgEZ8aRC5GLux7i3BpSJ6J1H+i7A2CjiHq9z7JRZuuQq siwTIvpxJYCeuWaBpwZdhB+yxy/eWz+ZvVSU8C4E9FFZkyxFsvCT/ZzL8gcz9aXVE14Yyp2M+2W0 y7n5mp0qN+avKXvbsyyzUqjeWR8hjGE+2iCE1W1tQ82hsCZN9UzlJr+/e/iab8WfqsmPI6pWeUPd FrMsd4H/55poeO9n54COhUs+sZNEzNtg/wanpjpuqHJaxs76HtZryI/K3H7KJ/KDIhqcbJ7kI4ar XL+sMgXnX0D+Te2Iy5xdP8yueSlQB/x/ED2BTAtyE3K4SYUN6AMNfbO63f4lBW3bUJPbTL+mjSxS PyRfJkZRgj+VbFv+EzHFi5pKwUEepa4JslMnwkowSRCXI+m5XvEOvtuBrxHdhLalG0JofYBok6qj YdN2dEngUlbC4PG60M1WEN0piu7Nq7on0mgyyUw3iV1etLo6r/81biWdQ9MWHFaePWZYaq+nmp+t s3az+sj7eA0jfgPfeoN1 """) MH_MAGIC = 0xfeedface MH_CIGAM = 0xcefaedfe MH_MAGIC_64 = 0xfeedfacf MH_CIGAM_64 = 0xcffaedfe FAT_MAGIC = 0xcafebabe BIG_ENDIAN = '>' LITTLE_ENDIAN = '<' LC_LOAD_DYLIB = 0xc maxint = majver == 3 and getattr(sys, 'maxsize') or getattr(sys, 'maxint') class fileview(object): """ A proxy for file-like objects that exposes a given view of a file. Modified from macholib. """ def __init__(self, fileobj, start=0, size=maxint): if isinstance(fileobj, fileview): self._fileobj = fileobj._fileobj else: self._fileobj = fileobj self._start = start self._end = start + size self._pos = 0 def __repr__(self): return '' % ( self._start, self._end, self._fileobj) def tell(self): return self._pos def _checkwindow(self, seekto, op): if not (self._start <= seekto <= self._end): raise IOError("%s to offset %d is outside window [%d, %d]" % ( op, seekto, self._start, self._end)) def seek(self, offset, whence=0): seekto = offset if whence == os.SEEK_SET: seekto += self._start elif whence == os.SEEK_CUR: seekto += self._start + self._pos elif whence == os.SEEK_END: seekto += self._end else: raise IOError("Invalid whence argument to seek: %r" % (whence,)) self._checkwindow(seekto, 'seek') self._fileobj.seek(seekto) self._pos = seekto - self._start def write(self, bytes): here = self._start + self._pos self._checkwindow(here, 'write') self._checkwindow(here + len(bytes), 'write') self._fileobj.seek(here, os.SEEK_SET) self._fileobj.write(bytes) self._pos += len(bytes) def read(self, size=maxint): assert size >= 0 here = self._start + self._pos self._checkwindow(here, 'read') size = min(size, self._end - here) self._fileobj.seek(here, os.SEEK_SET) bytes = self._fileobj.read(size) self._pos += len(bytes) return bytes def read_data(file, endian, num=1): """ Read a given number of 32-bits unsigned integers from the given file with the given endianness. """ res = struct.unpack(endian + 'L' * num, file.read(num * 4)) if len(res) == 1: return res[0] return res def mach_o_change(path, what, value): """ Replace a given name (what) in any LC_LOAD_DYLIB command found in the given binary with a new name (value), provided it's shorter. """ def do_macho(file, bits, endian): # Read Mach-O header (the magic number is assumed read by the caller) cputype, cpusubtype, filetype, ncmds, sizeofcmds, flags = read_data(file, endian, 6) # 64-bits header has one more field. if bits == 64: read_data(file, endian) # The header is followed by ncmds commands for n in range(ncmds): where = file.tell() # Read command header cmd, cmdsize = read_data(file, endian, 2) if cmd == LC_LOAD_DYLIB: # The first data field in LC_LOAD_DYLIB commands is the # offset of the name, starting from the beginning of the # command. name_offset = read_data(file, endian) file.seek(where + name_offset, os.SEEK_SET) # Read the NUL terminated string load = file.read(cmdsize - name_offset).decode() load = load[:load.index('\0')] # If the string is what is being replaced, overwrite it. if load == what: file.seek(where + name_offset, os.SEEK_SET) file.write(value.encode() + '\0'.encode()) # Seek to the next command file.seek(where + cmdsize, os.SEEK_SET) def do_file(file, offset=0, size=maxint): file = fileview(file, offset, size) # Read magic number magic = read_data(file, BIG_ENDIAN) if magic == FAT_MAGIC: # Fat binaries contain nfat_arch Mach-O binaries nfat_arch = read_data(file, BIG_ENDIAN) for n in range(nfat_arch): # Read arch header cputype, cpusubtype, offset, size, align = read_data(file, BIG_ENDIAN, 5) do_file(file, offset, size) elif magic == MH_MAGIC: do_macho(file, 32, BIG_ENDIAN) elif magic == MH_CIGAM: do_macho(file, 32, LITTLE_ENDIAN) elif magic == MH_MAGIC_64: do_macho(file, 64, BIG_ENDIAN) elif magic == MH_CIGAM_64: do_macho(file, 64, LITTLE_ENDIAN) assert(len(what) >= len(value)) do_file(open(path, 'r+b')) if __name__ == '__main__': main() ## TODO: ## Copy python.exe.manifest ## Monkeypatch distutils.sysconfig tox-1.6.0/tox/__init__.py0000664000175000017500000000144412203141321014633 0ustar hpkhpk00000000000000# __version__ = '1.6.0' 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._cmdline import main as cmdline tox-1.6.0/tox/_verlib.py0000664000175000017500000002730712203141321014524 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 sys 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-1.6.0/tox/_venv.py0000664000175000017500000003667312203141321014225 0ustar hpkhpk00000000000000from __future__ import with_statement import sys, os, re import subprocess import py import tox from tox._config import DepConfig class CreationConfig: def __init__(self, md5, python, version, distribute, sitepackages, develop, deps): self.md5 = md5 self.python = python self.version = version self.distribute = distribute self.sitepackages = sitepackages self.develop = develop self.deps = deps def writeconfig(self, path): lines = ["%s %s" % (self.md5, self.python)] lines.append("%s %d %d %d" % (self.version, self.distribute, self.sitepackages, self.develop)) 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, distribute, sitepackages, develop = lines.pop(0).split( None, 3) distribute = bool(int(distribute)) sitepackages = bool(int(sitepackages)) develop = bool(int(develop)) deps = [] for line in lines: md5, depstring = line.split(None, 1) deps.append((md5, depstring)) return CreationConfig(md5, python, version, distribute, sitepackages, develop, deps) except KeyboardInterrupt: raise except: 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.distribute == other.distribute and self.sitepackages == other.sitepackages and self.develop == other.develop and self.deps == other.deps) class VirtualEnv(object): def __init__(self, envconfig=None, session=None): self.envconfig = envconfig self.session = session self.path = envconfig.envdir self.path_config = self.path.join(".tox-config1") @property def name(self): return self.envconfig.envname def __repr__(self): return "" %(self.path) def getcommandpath(self, name=None, venv=True, cwd=None): if name is None: return self.envconfig.envpython 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 forgot to specify a dependency?" % (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=None): """ return status string for updating actual venv to match configuration. if status string is empty, all is ok. """ if action is None: action = self.session.newaction(self, "update") report = self.session.report name = self.envconfig.envname rconfig = CreationConfig.readconfig(self.path_config) if not self.envconfig.recreate and rconfig and \ rconfig.matches(self._getliveconfig()): 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.create(action) except tox.exception.UnsupportedInterpreter: return sys.exc_info()[1] except tox.exception.InterpreterNotFound: return sys.exc_info()[1] try: self.install_deps(action) except tox.exception.InvocationError: v = sys.exc_info()[1] return "could not install deps %s" %(self.envconfig.deps,) def _getliveconfig(self): python = self.envconfig._basepython_info.executable md5 = getdigest(python) version = tox.__version__ distribute = self.envconfig.distribute sitepackages = self.envconfig.sitepackages develop = self.envconfig.develop deps = [] for dep in self._getresolvedeps(): raw_dep = dep.name md5 = getdigest(raw_dep) deps.append((md5, raw_dep)) return CreationConfig(md5, python, version, distribute, 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 create(self, action=None): #if self.getcommandpath("activate").dirpath().check(): # return if action is None: action = self.session.newaction(self, "create") interpreters = self.envconfig.config.interpreters config_interpreter = self.getsupportedinterpreter() info = interpreters.get_info(executable=config_interpreter) use_venv191 = use_pip13 = info.version_info < (2,6) if not use_venv191: f, path, _ = py.std.imp.find_module("virtualenv") f.close() venvscript = path.rstrip("co") else: venvscript = py.path.local(tox.__file__).dirpath( "vendor", "virtualenv.py") args = [config_interpreter, str(venvscript)] if self.envconfig.distribute: args.append("--distribute") else: args.append("--setuptools") if self.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: self.session.make_emptydir(self.path) basepath = self.path.dirpath() basepath.ensure(dir=1) args.append(self.path.basename) self._pcall(args, venv=False, action=action, cwd=basepath) self.just_created = True if use_pip13: indexserver = self.envconfig.config.indexserver['default'].url action = self.session.newaction(self, "pip_downgrade") action.setactivity('pip-downgrade', 'pip<1.4') argv = ["easy_install"] + \ self._installopts(indexserver) + ['pip<1.4'] self._pcall(argv, cwd=self.envconfig.envlogdir, action=action) 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().decode('utf-8') 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 install_deps(self, action=None): if action is None: action = self.session.newaction(self, "install_deps") deps = self._getresolvedeps() if deps: depinfo = ", ".join(map(str, deps)) action.setactivity("installdeps", "%s" % depinfo) self._install(deps, 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) return l def run_install_command(self, args, indexserver=None, action=None, extraenv=None): argv = self.envconfig.install_command[:] # use pip-script on win32 to avoid the executable locking if argv[0] == "pip" and sys.platform == "win32": argv[0] = "pip-script.py" i = argv.index('{packages}') argv[i:i+1] = args if '{opts}' in argv: i = argv.index('{opts}') argv[i:i+1] = self._installopts(indexserver) for x in ('PIP_RESPECT_VIRTUALENV', 'PIP_REQUIRE_VIRTUALENV'): try: del os.environ[x] except KeyError: pass env = dict(PYTHONIOENCODING='utf_8') if extraenv is not None: env.update(extraenv) self._pcall(argv, cwd=self.envconfig.envlogdir, extraenv=env, action=action) 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) extraopts = extraopts or [] for ixserver in l: extraenv = hack_home_env( homedir=self.envconfig.envtmpdir.join("pseudo-home"), index_url = ixserver.url) args = d[ixserver] + extraopts self.run_install_command(args, ixserver.url, action, extraenv=extraenv) def _getenv(self): env = self.envconfig.setenv if env: env_arg = os.environ.copy() env_arg.update(env) else: env_arg = None return env_arg def 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 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) try: self._pcall(argv, cwd=cwd, action=action, redirect=redirect) except tox.exception.InvocationError: val = sys.exc_info()[1] self.session.report.error(str(val)) self.status = "commands failed" except KeyboardInterrupt: self.status = "keyboardinterrupt" self.session.report.error(self.status) raise def _pcall(self, args, venv=True, cwd=None, extraenv={}, action=None, redirect=True): for name in ("VIRTUALENV_PYTHON", "PYTHONDONTWRITEBYTECODE"): try: del os.environ[name] except KeyError: pass assert cwd cwd.ensure(dir=1) old = self.patchPATH() try: args[0] = self.getcommandpath(args[0], venv, cwd) env = self._getenv() or os.environ.copy() env.update(extraenv) return action.popen(args, cwd=cwd, env=env, redirect=redirect) finally: os.environ['PATH'] = old def patchPATH(self): oldPATH = os.environ['PATH'] bindir = str(self.envconfig.envbindir) os.environ['PATH'] = os.pathsep.join([bindir, oldPATH]) self.session.report.verbosity2("setting PATH=%s" % os.environ["PATH"]) return oldPATH def getdigest(path): path = py.path.local(path) if not path.check(file=1): return "0" * 32 return path.computehash() def hack_home_env(homedir, index_url=None): # XXX HACK (this could also live with tox itself, consider) # if tox uses pip on a package that requires setup_requires # the index url set with pip is usually not recognized # because it is setuptools executing very early. # We therefore run the tox command in an artifical home # directory and set .pydistutils.cfg and pip.conf files # accordingly. if not homedir.check(): homedir.ensure(dir=1) d = dict(HOME=str(homedir)) if not index_url: index_url = os.environ.get("TOX_INDEX_URL") if index_url: homedir.join(".pydistutils.cfg").write( "[easy_install]\n" "index_url = %s\n" % index_url) d["PIP_INDEX_URL"] = index_url d["TOX_INDEX_URL"] = index_url return d tox-1.6.0/tox/_cmdline.py0000664000175000017500000005002612203141321014646 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 main(args=None): try: config = parseconfig(args, 'tox') retcode = Session(config).runcommand() raise SystemExit(retcode) except KeyboardInterrupt: raise SystemExit(2) 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" cat = {"runtests": "test", "getenv": "setup"}.get(msg) if cat: 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): logged_command = "%s$ %s" %(cwd, " ".join(map(str, args))) f = outpath = None resultjson = self.session.config.option.resultjson if resultjson or redirect: f = self._initlogpath(self.id) f.write("actionid=%s\nmsg=%s\ncmdargs=%r\nenv=%s\n" %( self.id, self.msg, args, env)) f.flush() self.popen_outpath = outpath = py.path.local(f.name) elif returnout: f = subprocess.PIPE if cwd is None: # XXX cwd = self.session.config.cwd cwd = py.path.local() popen = self._popen(args, cwd, env=env, stdout=f, stderr=STDOUT) popen.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: out, err = popen.communicate() except KeyboardInterrupt: self.report.keyboard_interrupt() popen.wait() raise KeyboardInterrupt() ret = popen.wait() finally: self._popenlist.remove(popen) if ret: invoked = " ".join(map(str, popen.args)) if outpath: self.report.error("invocation failed, logfile: %s" % 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, )) if not out and outpath: out = outpath.read() if hasattr(self, "commandlog"): self.commandlog.add_command(popen.args, out, ret) return out def _rewriteargs(self, cwd, args): newargs = [] for arg in args: if sys.platform != "win32" and isinstance(arg, py.path.local): arg = cwd.bestrelpath(arg) newargs.append(str(arg)) #subprocess does not always take kindly to .py scripts #so adding the interpreter here. if sys.platform == "win32": ext = os.path.splitext(str(newargs[0]))[1].lower() if ext == '.py' and self.venv: newargs = [str(self.venv.getcommandpath())] + newargs return newargs def _popen(self, args, cwd, stdout, stderr, env=None): args = self._rewriteargs(cwd, args) if env is None: env = os.environ.copy() return self.session.popen(args, shell=False, cwd=str(cwd), 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 logline(self, msg, **opts): self._reportedlines.append(msg) self.tw.line("%s" % msg, **opts) def verbosity0(self, msg, **opts): if self.session.config.option.verbosity >= 0: self.logline("%s" % msg, **opts) def verbosity1(self, msg, **opts): if self.session.config.option.verbosity >= 1: self.logline("%s" % msg, **opts) def verbosity2(self, msg, **opts): if self.session.config.option.verbosity >= 2: self.logline("%s" % msg, **opts) #def log(self, msg): # py.builtin.print_(msg, file=sys.stderr) class Session: def __init__(self, config, popen=subprocess.Popen, Report=Reporter): self.config = config self.popen = popen self.resultlog = ResultLog() self.report = Report(self) self.make_emptydir(config.logdir) config.logdir.ensure(dir=1) #self.report.using("logdir %s" %(self.config.logdir,)) self.report.using("tox.ini: %s" %(self.config.toxinipath,)) self._spec2pkg = {} self._name2venv = {} try: self.venvlist = [self.getvenv(x) for x in self.config.envlist] except LookupError: raise SystemExit(1) self._actions = [] def _makevenv(self, name): envconfig = self.config.envconfigs.get(name, None) if envconfig is None: self.report.error("unknown environment %r" % name) raise LookupError(name) venv = VirtualEnv(envconfig=envconfig, session=self) self._name2venv[name] = venv return venv def getvenv(self, name): """ return a VirtualEnv controler object for the 'name' env. """ try: return self._name2venv[name] except KeyError: return self._makevenv(name) def newaction(self, venv, msg, *args): action = Action(self, venv, msg, args) self._actions.append(action) return action def runcommand(self): self.report.using("tox-%s from %s" %(tox.__version__, tox.__file__)) if self.config.minversion: minversion = NormalizedVersion(self.config.minversion) toxversion = NormalizedVersion(tox.__version__) if toxversion < minversion: self.report.error( "tox version is %s, required is at least %s" %( toxversion, minversion)) raise SystemExit(1) if self.config.option.showconfig: self.showconfig() elif self.config.option.listenvs: self.showenvs() else: return self.subcommand_test() def _copyfiles(self, srcdir, pathlist, destdir): for relpath in pathlist: src = srcdir.join(relpath) if not src.check(): self.report.error("missing source file: %s" %(src,)) raise SystemExit(1) target = destdir.join(relpath) target.dirpath().ensure(dir=1) src.copy(target) def _makesdist(self): setup = self.config.setupdir.join("setup.py") if not setup.check(): raise tox.exception.MissingFile(setup) action = self.newaction(None, "packaging") with action: action.setactivity("sdist-make", setup) self.make_emptydir(self.config.distdir) action.popen([sys.executable, setup, "sdist", "--formats=zip", "--dist-dir", self.config.distdir, ], cwd=self.config.setupdir) try: return self.config.distdir.listdir()[0] except py.error.ENOENT: # check if empty or comment only data = [] with open(str(setup)) as fp: for line in fp: if line and line[0] == '#': continue data.append(line) if not ''.join(data).strip(): self.report.error( 'setup.py is empty' ) raise SystemExit(1) self.report.error( 'No dist directory found. Please check setup.py, e.g with:\n'\ ' python setup.py sdist' ) raise SystemExit(1) def make_emptydir(self, path): if path.check(): self.report.info(" removing %s" % path) py.std.shutil.rmtree(str(path), ignore_errors=True) path.ensure(dir=1) def setupenv(self, venv): action = self.newaction(venv, "getenv", venv.envconfig.envdir) with action: venv.status = 0 envlog = self.resultlog.get_envlog(venv.name) try: status = venv.update(action=action) except tox.exception.InvocationError: status = sys.exc_info()[1] if status: commandlog = envlog.get_commandlog("setup") commandlog.add_command(["setup virtualenv"], str(status), 1) venv.status = status self.report.error(str(status)) return False commandpath = venv.getcommandpath("python") envlog.set_python_info(commandpath) return True def finishvenv(self, venv): action = self.newaction(venv, "finishvenv") with action: venv.finish() return True def developpkg(self, venv, setupdir): action = self.newaction(venv, "developpkg", setupdir) with action: try: venv.developpkg(setupdir, action) return True except tox.exception.InvocationError: venv.status = sys.exc_info()[1] return False def installpkg(self, venv, sdist_path): self.resultlog.set_header(installpkg=sdist_path) action = self.newaction(venv, "installpkg", sdist_path) with action: try: venv.installpkg(sdist_path, action) return True except tox.exception.InvocationError: venv.status = sys.exc_info()[1] return False def sdist(self): if not self.config.option.sdistonly and (self.config.sdistsrc or self.config.option.installpkg): sdist_path = self.config.option.installpkg if not sdist_path: sdist_path = self.config.sdistsrc sdist_path = self._resolve_pkg(sdist_path) self.report.info("using package %r, skipping 'sdist' activity " % str(sdist_path)) else: try: sdist_path = self._makesdist() except tox.exception.InvocationError: v = sys.exc_info()[1] self.report.error("FAIL could not package project") return sdistfile = self.config.distshare.join(sdist_path.basename) if sdistfile != sdist_path: self.report.info("copying new sdistfile to %r" % str(sdistfile)) sdistfile.dirpath().ensure(dir=1) sdist_path.copy(sdistfile) return sdist_path def subcommand_test(self): if self.config.skipsdist: self.report.info("skipping sdist step") sdist_path = None else: sdist_path = self.sdist() if not sdist_path: return 2 if self.config.option.sdistonly: return for venv in self.venvlist: if self.setupenv(venv): if venv.envconfig.develop: self.developpkg(venv, self.config.setupdir) elif self.config.skipsdist: self.finishvenv(venv) else: self.installpkg(venv, sdist_path) 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 status and status != "skipped tests": msg = " %s: %s" %(venv.envconfig.envname, str(status)) self.report.error(msg) retcode = 1 else: if not status: status = "commands succeeded" self.report.good(" %s: %s" %(venv.envconfig.envname, status)) if not retcode: self.report.good(" congratulations :)") path = self.config.option.resultjson if path: path = py.path.local(path) path.write(self.resultlog.dumps_json()) self.report.line("wrote json report at: %s" % path) return retcode def showconfig(self): self.info_versions() self.report.keyvalue("config-file:", self.config.option.configfile) self.report.keyvalue("toxinipath: ", self.config.toxinipath) self.report.keyvalue("toxinidir: ", self.config.toxinidir) self.report.keyvalue("toxworkdir: ", self.config.toxworkdir) self.report.keyvalue("setupdir: ", self.config.setupdir) self.report.keyvalue("distshare: ", self.config.distshare) self.report.keyvalue("skipsdist: ", self.config.skipsdist) self.report.tw.line() for envconfig in self.config.envconfigs.values(): self.report.line("[testenv:%s]" % envconfig.envname, bold=True) self.report.line(" basepython=%s" % envconfig.basepython) self.report.line(" _basepython_info=%s" % envconfig._basepython_info) self.report.line(" envpython=%s" % envconfig.envpython) self.report.line(" envtmpdir=%s" % envconfig.envtmpdir) self.report.line(" envbindir=%s" % envconfig.envbindir) self.report.line(" envlogdir=%s" % envconfig.envlogdir) self.report.line(" changedir=%s" % envconfig.changedir) self.report.line(" args_are_path=%s" % envconfig.args_are_paths) self.report.line(" install_command=%s" % envconfig.install_command) self.report.line(" commands=") for command in envconfig.commands: self.report.line(" %s" % command) self.report.line(" deps=%s" % envconfig.deps) self.report.line(" envdir= %s" % envconfig.envdir) self.report.line(" downloadcache=%s" % envconfig.downloadcache) self.report.line(" usedevelop=%s" % envconfig.develop) 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-1.6.0/tox/_config.py0000664000175000017500000006167612203141321014515 0ustar hpkhpk00000000000000import argparse import distutils.sysconfig import os import sys import re import shlex import string import subprocess import textwrap from tox.interpreters import Interpreters import py import tox defaultenvs = {'jython': 'jython', 'pypy': 'pypy'} for _name in "py,py24,py25,py26,py27,py30,py31,py32,py33,py34".split(","): if _name == "py": basepython = sys.executable else: basepython = "python" + ".".join(_name[2:4]) defaultenvs[_name] = basepython def parseconfig(args=None, pkg=None): if args is None: args = sys.argv[1:] parser = prepare_parse(pkg) opts = parser.parse_args(args) config = Config() config.option = opts 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)) 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): name = argparser.pkgname mod = __import__(name) version = mod.__version__ py.builtin.print_("%s imported from %s" %(version, mod.__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) def prepare_parse(pkgname): parser = argparse.ArgumentParser(description=__doc__,) #formatter_class=argparse.ArgumentDefaultsHelpFormatter) parser.pkgname = pkgname parser.add_argument("--version", nargs=0, action=VersionAction, dest="version", help="report version information to stdout.") 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("-r", "--recreate", action="store_true", dest="recreate", help="force recreation of virtual environments") parser.add_argument("--result-json", action="store", dest="resultjson", metavar="PATH", help="write a json file with detailed information about " "all commands and results involved. This will turn off " "pass-through output from running test commands which is " "instead captured into the json result file.") parser.add_argument("args", nargs="*", help="additional arguments available to command positional substition") return parser class Config: def __init__(self): self.envconfigs = {} self.invocationcwd = py.path.local() self.interpreters = Interpreters() class VenvConfig: def __init__(self, **kw): self.__dict__.update(kw) @property def envbindir(self): if (sys.platform == "win32" and "jython" not in self.basepython and "pypy" not in self.basepython): return self.envdir.join("Scripts") else: return self.envdir.join("bin") @property def envpython(self): if "jython" in str(self.basepython): name = "jython" else: name = "python" return self.envbindir.join(name) # no @property to avoid early calling (see callable(subst[key]) checks) def envsitepackagesdir(self): self.getsupportedinterpreter() # for throwing exceptions x = self.config.interpreters.get_sitepackagesdir( info=self._basepython_info, envdir=self.envdir) return x 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(self.basepython) if not info.executable: raise tox.exception.InterpreterNotFound(self.basepython) return info.executable testenvprefix = "testenv:" class parseini: def __init__(self, config, inipath): config.toxinipath = inipath config.toxinidir = toxinidir = config.toxinipath.dirpath() self._cfg = py.iniconfig.IniConfig(config.toxinipath) config._cfg = self._cfg self.config = config ctxname = getcontextname() if ctxname == "jenkins": reader = IniReader(self._cfg, fallbacksections=['tox']) toxsection = "tox:%s" % ctxname distshare_default = "{toxworkdir}/distshare" elif not ctxname: reader = IniReader(self._cfg) toxsection = "tox" distshare_default = "{homedir}/.tox/distshare" else: raise ValueError("invalid context") config.homedir = py.path.local._gethomedir() if config.homedir is None: config.homedir = config.toxinidir # XXX good idea? reader.addsubstitions(toxinidir=config.toxinidir, homedir=config.homedir) config.toxworkdir = reader.getpath(toxsection, "toxworkdir", "{toxinidir}/.tox") config.minversion = reader.getdefault(toxsection, "minversion", None) # determine indexserver dictionary config.indexserver = {'default': IndexServerConfig('default')} prefix = "indexserver" for line in reader.getlist(toxsection, "indexserver"): 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.addsubstitions(toxworkdir=config.toxworkdir) config.distdir = reader.getpath(toxsection, "distdir", "{toxworkdir}/dist") reader.addsubstitions(distdir=config.distdir) config.distshare = reader.getpath(toxsection, "distshare", distshare_default) reader.addsubstitions(distshare=config.distshare) config.sdistsrc = reader.getpath(toxsection, "sdistsrc", None) config.setupdir = reader.getpath(toxsection, "setupdir", "{toxinidir}") config.logdir = config.toxworkdir.join("log") for sectionwrapper in self._cfg: section = sectionwrapper.name if section.startswith(testenvprefix): name = section[len(testenvprefix):] envconfig = self._makeenvconfig(name, section, reader._subs, config) config.envconfigs[name] = envconfig if not config.envconfigs: config.envconfigs['python'] = \ self._makeenvconfig("python", "_xz_9", reader._subs, config) config.envlist = self._getenvlist(reader, toxsection) for name in config.envlist: if name not in config.envconfigs: if name in defaultenvs: config.envconfigs[name] = \ self._makeenvconfig(name, "_xz_9", reader._subs, config) all_develop = all(name in config.envconfigs and config.envconfigs[name].develop for name in config.envlist) config.skipsdist = reader.getbool(toxsection, "skipsdist", all_develop) def _makeenvconfig(self, name, section, subs, config): vc = VenvConfig(envname=name) vc.config = config reader = IniReader(self._cfg, fallbacksections=["testenv"]) reader.addsubstitions(**subs) vc.develop = reader.getbool(section, "usedevelop", config.option.develop) vc.envdir = reader.getpath(section, "envdir", "{toxworkdir}/%s" % name) vc.args_are_paths = reader.getbool(section, "args_are_paths", True) if reader.getdefault(section, "python", None): raise tox.exception.ConfigError( "'python=' key was renamed to 'basepython='") if name in defaultenvs: bp = defaultenvs[name] else: bp = sys.executable vc.basepython = reader.getdefault(section, "basepython", bp) vc._basepython_info = config.interpreters.get_info(vc.basepython) reader.addsubstitions(envdir=vc.envdir, envname=vc.envname, envbindir=vc.envbindir, envpython=vc.envpython, envsitepackagesdir=vc.envsitepackagesdir) vc.envtmpdir = reader.getpath(section, "tmpdir", "{envdir}/tmp") vc.envlogdir = reader.getpath(section, "envlogdir", "{envdir}/log") reader.addsubstitions(envlogdir=vc.envlogdir, envtmpdir=vc.envtmpdir) vc.changedir = reader.getpath(section, "changedir", "{toxinidir}") if config.option.recreate: vc.recreate = True else: vc.recreate = reader.getbool(section, "recreate", False) args = config.option.args if args: if vc.args_are_paths: args = [] for arg in config.option.args: origpath = config.invocationcwd.join(arg, abs=True) if origpath.check(): arg = vc.changedir.bestrelpath(origpath) args.append(arg) reader.addsubstitions(args) vc.setenv = reader.getdict(section, 'setenv') if not vc.setenv: vc.setenv = None vc.commands = reader.getargvlist(section, "commands") vc.whitelist_externals = reader.getlist(section, "whitelist_externals") vc.deps = [] for depline in reader.getlist(section, "deps"): m = re.match(r":(\w+):\s*(\S+)", depline) if m: iname, name = m.groups() ixserver = config.indexserver[iname] else: name = depline.strip() ixserver = None vc.deps.append(DepConfig(name, ixserver)) vc.distribute = reader.getbool(section, "distribute", False) vc.sitepackages = reader.getbool(section, "sitepackages", False) vc.downloadcache = None downloadcache = reader.getdefault(section, "downloadcache") if downloadcache: # env var, if present, takes precedence downloadcache = os.environ.get("PIP_DOWNLOAD_CACHE", downloadcache) vc.downloadcache = py.path.local(downloadcache) # on python 2.5 we can't use "--pre" and we typically # need to use --insecure for pip commands because python2.5 # doesn't support SSL pip_default_opts = ["{opts}", "{packages}"] info = vc._basepython_info if info.runnable and info.version_info < (2,6): pip_default_opts.insert(0, "--insecure") else: pip_default_opts.insert(0, "--pre") vc.install_command = reader.getargv( section, "install_command", "pip install " + " ".join(pip_default_opts), replace=False, ) if '{packages}' not in vc.install_command: raise tox.exception.ConfigError( "'install_command' must contain '{packages}' substitution") return vc def _getenvlist(self, reader, toxsection): env = self.config.option.env if not env: env = os.environ.get("TOXENV", None) if not env: envlist = reader.getlist(toxsection, "envlist", sep=",") if not envlist: envlist = self.config.envconfigs.keys() return envlist envlist = _split_env(env) if "ALL" in envlist: envlist = list(self.config.envconfigs) envlist.sort() return envlist def _split_env(env): """if handed a list, action="append" was used for -e """ envlist = [] if not isinstance(env, list): env = [env] for to_split in env: for single_env in to_split.split(","): # "remove True or", if not allowing multiple same runs, update tests if True or single_env not in envlist: envlist.append(single_env) return envlist 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 RE_ITEM_REF = re.compile( ''' [{] (?:(?P[^[:{}]+):)? # optional sub_type for special rules (?P[^{}]*) # substitution key [}] ''', re.VERBOSE) class IniReader: def __init__(self, cfgparser, fallbacksections=None): self._cfg = cfgparser self.fallbacksections = fallbacksections or [] self._subs = {} self._subststack = [] def addsubstitions(self, _posargs=None, **kw): self._subs.update(kw) if _posargs: self._subs['_posargs'] = _posargs def getpath(self, section, name, defaultpath): toxinidir = self._subs['toxinidir'] path = self.getdefault(section, name, defaultpath) if path is None: return path return toxinidir.join(path, abs=True) def getlist(self, section, name, sep="\n"): s = self.getdefault(section, name, None) if s is None: return [] return [x.strip() for x in s.split(sep) if x.strip()] def getdict(self, section, name, sep="\n"): s = self.getdefault(section, name, None) if s is None: return {} value = {} for line in s.split(sep): name, rest = line.split('=', 1) value[name.strip()] = rest.strip() return value def getargvlist(self, section, name): s = self.getdefault(section, name, '', replace=False) #if s is None: # raise tox.exception.ConfigError( # "no command list %r defined in section [%s]" %(name, section)) commandlist = [] current_command = "" for line in s.split("\n"): line = line.rstrip() i = line.find("#") if i != -1: line = line[:i].rstrip() if not line: continue if line.endswith("\\"): current_command += " " + line[:-1] continue current_command += line commandlist.append(self._processcommand(current_command)) current_command = "" else: if current_command: raise tox.exception.ConfigError( "line-continuation for [%s] %s ends nowhere" % (section, name)) return commandlist def _processcommand(self, command): posargs = self._subs.get('_posargs', None) words = list(CommandParser(command).words()) new_command = '' for word in words: if word == '[]': if posargs: new_command += ' '.join(posargs) continue new_word = self._replace(word, quote=True) # two passes; we might have substitutions in the result new_word = self._replace(new_word, quote=True) new_command += new_word return shlex.split(new_command.strip()) def getargv(self, section, name, default=None, replace=True): command = self.getdefault( section, name, default=default, replace=replace) return shlex.split(command.strip()) def getbool(self, section, name, default=None): s = self.getdefault(section, name, default) if s is None: raise KeyError("no config value [%s] %s found" % ( section, name)) if not isinstance(s, bool): if s.lower() == "true": s = True elif s.lower() == "false": s = False else: raise tox.exception.ConfigError( "boolean value %r needs to be 'True' or 'False'") return s def getdefault(self, section, name, default=None, replace=True): try: x = self._cfg[section][name] except KeyError: for fallbacksection in self.fallbacksections: try: x = self._cfg[fallbacksection][name] except KeyError: pass else: break else: x = default if replace and x and hasattr(x, 'replace'): self._subststack.append((section, name)) try: x = self._replace(x) finally: assert self._subststack.pop() == (section, name) #print "getdefault", section, name, "returned", repr(x) return x def _replace_posargs(self, match, quote): return self._do_replace_posargs(lambda: match.group('substitution_value')) def _do_replace_posargs(self, value_func): posargs = self._subs.get('_posargs', None) if posargs: return " ".join(posargs) value = value_func() if value: return value return '' def _replace_env(self, match, quote): envkey = match.group('substitution_value') if not envkey: raise tox.exception.ConfigError( 'env: requires an environment variable name') if not envkey in os.environ: raise tox.exception.ConfigError( "substitution env:%r: unkown environment variable %r" % (envkey, envkey)) return os.environ[envkey] def _substitute_from_other_section(self, key, quote): if key.startswith("[") and "]" in key: i = key.find("]") section, item = key[1:i], key[i+1:] if section in self._cfg and item in self._cfg[section]: if (section, item) in self._subststack: raise ValueError('%s already in %s' %( (section, item), self._subststack)) x = str(self._cfg[section][item]) self._subststack.append((section, item)) try: return self._replace(x, quote=quote) finally: self._subststack.pop() raise tox.exception.ConfigError( "substitution key %r not found" % key) def _replace_substitution(self, match, quote): sub_key = match.group('substitution_value') val = self._subs.get(sub_key, None) if val is None: val = self._substitute_from_other_section(sub_key, quote) if py.builtin.callable(val): val = val() if quote: return '"%s"' % str(val).replace('"', r'\"') else: return str(val) def _is_bare_posargs(self, groupdict): return groupdict.get('substitution_value', None) == 'posargs' \ and not groupdict.get('sub_type') def _replace_match(self, match, quote): g = match.groupdict() # special case: posargs. If there is a 'posargs' substitution value # and no type, handle it as empty posargs if self._is_bare_posargs(g): return self._do_replace_posargs(lambda: '') handlers = { 'posargs' : self._replace_posargs, 'env' : self._replace_env, None : self._replace_substitution, } try: sub_type = g['sub_type'] except KeyError: raise tox.exception.ConfigError("Malformed substitution; no substitution type provided") try: handler = handlers[sub_type] except KeyError: raise tox.exception.ConfigError("No support for the %s substitution type" % sub_type) # quoting is done in handlers, as at least posargs handling is special: # all of its arguments are inserted as separate parameters return handler(match, quote) def _replace_match_quote(self, match): return self._replace_match(match, quote=True) def _replace_match_no_quote(self, match): return self._replace_match(match, quote=False) def _replace(self, x, rexpattern=RE_ITEM_REF, quote=False): # XXX is rexpattern used by callers? can it be removed? if '{' in x: if quote: replace_func = self._replace_match_quote else: replace_func = self._replace_match_no_quote return rexpattern.sub(replace_func, x) return x def _parse_command(self, command): pass 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) 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 'HUDSON_URL' in os.environ: return 'jenkins' return None tox-1.6.0/tox/_exception.py0000664000175000017500000000111312203141321015222 0ustar hpkhpk00000000000000 class Error(Exception): def __str__(self): return "%s: %s" %(self.__class__.__name__, self.args[0]) class UnsupportedInterpreter(Error): "signals an unsupported Interpreter" class InterpreterNotFound(Error): "signals that an interpreter could not be found" class InvocationError(Error): """ an error while invoking a script. """ class MissingFile(Error): """ an error while invoking a script. """ class MissingDirectory(Error): """ a directory did not exist. """ class MissingDependency(Error): """ a dependency could not be found or determined. """ tox-1.6.0/tox/result.py0000664000175000017500000000417012203141321014411 0ustar hpkhpk00000000000000import sys import py try: import json except ImportError: import simplejson as json class ResultLog: def __init__(self, dict=None): if dict is None: dict = {} self.dict = dict def set_header(self, installpkg): from tox import __version__ as toxver self.dict.update({"reportversion": "1", "toxversion": toxver}) self.dict["platform"] = sys.platform self.dict["host"] = py.std.socket.getfqdn() 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) 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-1.6.0/tests/0000775000175000017500000000000012203141322013050 5ustar hpkhpk00000000000000tox-1.6.0/tests/test_interpreters.py0000664000175000017500000000613012203141321017206 0ustar hpkhpk00000000000000import sys import os import pytest from tox.interpreters import * @pytest.fixture def interpreters(): return Interpreters() @pytest.mark.skipif("sys.platform != 'win32'") def test_locate_via_py(monkeypatch): class PseudoPy: def sysexec(self, *args): assert args[0] == '-3.2' assert args[1] == '-c' # Return value needs to actually exist! return sys.executable @staticmethod def ret_pseudopy(name): assert name == 'py' return PseudoPy() # Monkeypatch py.path.local.sysfind to return PseudoPy monkeypatch.setattr(py.path.local, 'sysfind', ret_pseudopy) assert locate_via_py('3', '2') == sys.executable def test_find_executable(): p = find_executable(sys.executable) assert p == py.path.local(sys.executable) for ver in [""] + "2.4 2.5 2.6 2.7 3.0 3.1 3.2 3.3".split(): name = "python%s" % ver if sys.platform == "win32": pydir = "python%s" % ver.replace(".", "") x = py.path.local("c:\%s" % pydir) print (x) if not x.check(): continue else: if not py.path.local.sysfind(name): continue p = find_executable(name) assert p popen = py.std.subprocess.Popen([str(p), '-V'], stderr=py.std.subprocess.PIPE) stdout, stderr = popen.communicate() assert ver in py.builtin._totext(stderr, "ascii") def test_find_executable_extra(monkeypatch): @staticmethod def sysfind(x): return "hello" monkeypatch.setattr(py.path.local, "sysfind", sysfind) t = find_executable("qweqwe") assert t == "hello" def test_run_and_get_interpreter_info(): name = os.path.basename(sys.executable) info = run_and_get_interpreter_info(name, sys.executable) assert info.version_info == tuple(sys.version_info) assert info.name == name assert info.executable == sys.executable class TestInterpreters: def test_get_info_self_exceptions(self, interpreters): pytest.raises(ValueError, lambda: interpreters.get_info()) pytest.raises(ValueError, lambda: interpreters.get_info(name="12", executable="123")) def test_get_executable(self, interpreters): x = interpreters.get_executable(sys.executable) assert x == sys.executable assert not interpreters.get_executable("12l3k1j23") def test_get_info__name(self, interpreters): info = interpreters.get_info(executable=sys.executable) assert info.version_info == tuple(sys.version_info) assert info.executable == sys.executable assert info.runnable def test_get_info__name_not_exists(self, interpreters): info = interpreters.get_info("qlwkejqwe") assert not info.version_info assert info.name == "qlwkejqwe" assert not info.executable assert not info.runnable def test_get_sitepackagesdir_error(self, interpreters): info = interpreters.get_info(sys.executable) s = interpreters.get_sitepackagesdir(info, "") assert s tox-1.6.0/tests/test_config.py0000664000175000017500000011041312203141321015725 0ustar hpkhpk00000000000000import tox import pytest import os, sys import subprocess from textwrap import dedent import py from tox._config import * from tox._config import _split_env 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 == [] 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') 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 def test_defaults_distshare(self, tmpdir, newconfig): config = newconfig([], "") envconfig = config.envconfigs['python'] homedir = py.path.local._gethomedir() assert config.distshare == 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 class TestIniParser: def test_getdefault_single(self, tmpdir, newconfig): config = newconfig(""" [section] key=value """) reader = IniReader(config._cfg) x = reader.getdefault("section", "key") assert x == "value" assert not reader.getdefault("section", "hello") x = reader.getdefault("section", "hello", "world") assert x == "world" def test_missing_substitution(self, tmpdir, newconfig): config = newconfig(""" [mydefault] key2={xyz} """) reader = IniReader(config._cfg, fallbacksections=['mydefault']) py.test.raises(tox.exception.ConfigError, 'reader.getdefault("mydefault", "key2")') def test_getdefault_fallback_sections(self, tmpdir, newconfig): config = newconfig(""" [mydefault] key2=value2 [section] key=value """) reader = IniReader(config._cfg, fallbacksections=['mydefault']) x = reader.getdefault("section", "key2") assert x == "value2" x = reader.getdefault("section", "key3") assert not x x = reader.getdefault("section", "key3", "world") assert x == "world" def test_getdefault_substitution(self, tmpdir, newconfig): config = newconfig(""" [mydefault] key2={value2} [section] key={value} """) reader = IniReader(config._cfg, fallbacksections=['mydefault']) reader.addsubstitions(value="newvalue", value2="newvalue2") x = reader.getdefault("section", "key2") assert x == "newvalue2" x = reader.getdefault("section", "key3") assert not x x = reader.getdefault("section", "key3", "{value2}") assert x == "newvalue2" def test_getlist(self, tmpdir, newconfig): config = newconfig(""" [section] key2= item1 {item2} """) reader = IniReader(config._cfg) reader.addsubstitions(item1="not", item2="grr") x = reader.getlist("section", "key2") assert x == ['item1', 'grr'] def test_getdict(self, tmpdir, newconfig): config = newconfig(""" [section] key2= key1=item1 key2={item2} """) reader = IniReader(config._cfg) reader.addsubstitions(item1="not", item2="grr") x = reader.getdict("section", "key2") assert 'key1' in x assert 'key2' in x assert x['key1'] == 'item1' assert x['key2'] == 'grr' def test_getdefault_environment_substitution(self, monkeypatch, newconfig): monkeypatch.setenv("KEY1", "hello") config = newconfig(""" [section] key1={env:KEY1} key2={env:KEY2} """) reader = IniReader(config._cfg) x = reader.getdefault("section", "key1") assert x == "hello" py.test.raises(tox.exception.ConfigError, 'reader.getdefault("section", "key2")') def test_getdefault_other_section_substitution(self, newconfig): config = newconfig(""" [section] key = rue [testenv] key = t{[section]key} """) reader = IniReader(config._cfg) x = reader.getdefault("testenv", "key") assert x == "true" def test_command_substitution_from_other_section(self, newconfig): config = newconfig(""" [section] key = whatever [testenv] commands = echo {[section]key} """) reader = IniReader(config._cfg) x = reader.getargvlist("testenv", "commands") assert x == [["echo", "whatever"]] def test_argvlist(self, tmpdir, newconfig): config = newconfig(""" [section] key2= cmd1 {item1} {item2} cmd2 {item2} """) reader = IniReader(config._cfg) reader.addsubstitions(item1="with space", item2="grr") #py.test.raises(tox.exception.ConfigError, # "reader.getargvlist('section', 'key1')") assert reader.getargvlist('section', 'key1') == [] x = reader.getargvlist("section", "key2") assert x == [["cmd1", "with space", "grr"], ["cmd2", "grr"]] def test_argvlist_multiline(self, tmpdir, newconfig): config = newconfig(""" [section] key2= cmd1 {item1} \ # a comment {item2} """) reader = IniReader(config._cfg) reader.addsubstitions(item1="with space", item2="grr") #py.test.raises(tox.exception.ConfigError, # "reader.getargvlist('section', 'key1')") assert reader.getargvlist('section', 'key1') == [] x = reader.getargvlist("section", "key2") assert x == [["cmd1", "with space", "grr"]] def test_argvlist_quoting_in_command(self, tmpdir, newconfig): config = newconfig(""" [section] key1= cmd1 'with space' \ # a comment 'after the comment' """) reader = IniReader(config._cfg) x = reader.getargvlist("section", "key1") assert x == [["cmd1", "with space", "after the comment"]] def test_argvlist_positional_substitution(self, tmpdir, newconfig): config = newconfig(""" [section] key2= cmd1 [] cmd2 {posargs:{item2} \ other} """) reader = IniReader(config._cfg) posargs = ['hello', 'world'] reader.addsubstitions(posargs, item2="value2") #py.test.raises(tox.exception.ConfigError, # "reader.getargvlist('section', 'key1')") assert reader.getargvlist('section', 'key1') == [] argvlist = reader.getargvlist("section", "key2") assert argvlist[0] == ["cmd1"] + posargs assert argvlist[1] == ["cmd2"] + posargs reader = IniReader(config._cfg) reader.addsubstitions([], item2="value2") #py.test.raises(tox.exception.ConfigError, # "reader.getargvlist('section', 'key1')") assert reader.getargvlist('section', 'key1') == [] argvlist = reader.getargvlist("section", "key2") assert argvlist[0] == ["cmd1"] assert argvlist[1] == ["cmd2", "value2", "other"] 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 = IniReader(config._cfg) posargs = ['hello', 'world'] reader.addsubstitions(posargs) argvlist = reader.getargvlist('section', '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_substition_with_multiple_words(self, newconfig): inisource = """ [section] key = py.test -n5 --junitxml={envlogdir}/junit-{envname}.xml [] """ config = newconfig(inisource) reader = IniReader(config._cfg) posargs = ['hello', 'world'] reader.addsubstitions(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('section', 'key')[0] == expected def test_getargv(self, newconfig): config = newconfig(""" [section] key=some command "with quoting" """) reader = IniReader(config._cfg) expected = ['some', 'command', 'with quoting'] assert reader.getargv('section', 'key') == expected def test_getpath(self, tmpdir, newconfig): config = newconfig(""" [section] path1={HELLO} """) reader = IniReader(config._cfg) reader.addsubstitions(toxinidir=tmpdir, HELLO="mypath") x = reader.getpath("section", "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 = IniReader(config._cfg) assert reader.getbool("section", "key1") == True assert reader.getbool("section", "key1a") == True assert reader.getbool("section", "key2") == False assert reader.getbool("section", "key2a") == False py.test.raises(KeyError, 'reader.getbool("section", "key3")') py.test.raises(tox.exception.ConfigError, 'reader.getbool("section", "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.distribute == False assert envconfig.sitepackages == False assert envconfig.develop == False assert envconfig.envlogdir == envconfig.envdir.join("log") assert envconfig.setenv is None 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_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"]) 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) 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_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_defaults_py25(self, newconfig, monkeypatch): from tox.interpreters import Interpreters def get_info(self, name): if "x25" in name: class I: runnable = True executable = "python2.5" version_info = (2,5) else: class I: runnable = False executable = "python" return I monkeypatch.setattr(Interpreters, "get_info", get_info) config = newconfig(""" [testenv:x25] basepython = x25 [testenv:py25-x] basepython = x25 [testenv:py26] basepython = "python" """) for name in ("x25", "py25-x"): env = config.envconfigs[name] assert env.install_command == \ "pip install --insecure {opts} {packages}".split() env = config.envconfigs["py26"] assert env.install_command == \ "pip install --pre {opts} {packages}".split() def test_install_command_setting(self, newconfig): config = newconfig(""" [testenv] install_command=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_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:py24] basepython=python2.4 [testenv:py25] basepython=python2.5 """) assert len(config.envconfigs) == 2 assert "py24" in config.envconfigs assert "py25" in config.envconfigs def test_substitution_error(tmpdir, newconfig): py.test.raises(tox.exception.ConfigError, newconfig, """ [testenv:py24] basepython={xyz} """) def test_substitution_defaults(tmpdir, newconfig): config = newconfig(""" [testenv:py24] commands = {toxinidir} {toxworkdir} {envdir} {envbindir} {envtmpdir} {envpython} {homedir} {distshare} {envlogdir} """) conf = config.envconfigs['py24'] 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(py.path.local._gethomedir()) assert argv[7][0] == config.homedir.join(".tox", "distshare") assert argv[8][0] == conf.envlogdir def test_substitution_positional(self, newconfig): inisource = """ [testenv:py24] commands = cmd1 [hello] \ world cmd1 {posargs:hello} \ world """ conf = newconfig([], inisource).envconfigs['py24'] argv = conf.commands assert argv[0] == ["cmd1", "[hello]", "world"] assert argv[1] == ["cmd1", "hello", "world"] conf = newconfig(['brave', 'new'], inisource).envconfigs['py24'] argv = conf.commands assert argv[0] == ["cmd1", "[hello]", "world"] assert argv[1] == ["cmd1", "brave", "new", "world"] def test_rewrite_posargs(self, tmpdir, newconfig): inisource = """ [testenv:py24] args_are_paths = True changedir = tests commands = cmd1 {posargs:hello} """ conf = newconfig([], inisource).envconfigs['py24'] argv = conf.commands assert argv[0] == ["cmd1", "hello"] conf = newconfig(["tests/hello"], inisource).envconfigs['py24'] argv = conf.commands assert argv[0] == ["cmd1", "tests/hello"] tmpdir.ensure("tests", "hello") conf = newconfig(["tests/hello"], inisource).envconfigs['py24'] argv = conf.commands assert argv[0] == ["cmd1", "hello"] def test_rewrite_simple_posargs(self, tmpdir, newconfig): inisource = """ [testenv:py24] args_are_paths = True changedir = tests commands = cmd1 {posargs} """ conf = newconfig([], inisource).envconfigs['py24'] argv = conf.commands assert argv[0] == ["cmd1"] conf = newconfig(["tests/hello"], inisource).envconfigs['py24'] argv = conf.commands assert argv[0] == ["cmd1", "tests/hello"] tmpdir.ensure("tests", "hello") conf = newconfig(["tests/hello"], inisource).envconfigs['py24'] argv = conf.commands assert argv[0] == ["cmd1", "hello"] def test_take_dependencies_from_other_testenv(self, newconfig): inisource=""" [testenv] deps= pytest pytest-cov [testenv:py24] deps= {[testenv]deps} fun """ conf = newconfig([], inisource).envconfigs['py24'] 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() 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:py24] commands = {distshare} """) conf = config.envconfigs['py24'] 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:py24] commands = {distshare} """) conf = config.envconfigs['py24'] 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 = "py24,py25,py26,py27,py31,py32,jython,pypy" 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 == "pypy": assert env.basepython == "pypy" else: assert name.startswith("py") bp = "python%s.%s" %(name[2], name[3]) assert env.basepython == bp def test_minversion(self, tmpdir, newconfig, monkeypatch): inisource = """ [tox] minversion = 3.0 """ config = newconfig([], inisource) assert config.minversion == "3.0" def test_defaultenv_commandline(self, tmpdir, newconfig, monkeypatch): config = newconfig(["-epy24"], "") env = config.envconfigs['py24'] assert env.basepython == "python2.4" assert not env.commands def test_defaultenv_partial_override(self, tmpdir, newconfig, monkeypatch): inisource = """ [tox] envlist = py24 [testenv:py24] commands= xyz """ config = newconfig([], inisource) env = config.envconfigs['py24'] assert env.basepython == "python2.4" assert env.commands == [['xyz']] class TestIndexServer: def test_indexserver(self, tmpdir, newconfig): config = newconfig(""" [tox] indexserver = name1 = XYZ name2 = ABC """) assert config.indexserver['default'].url == 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) homedir = str(py.path.local._gethomedir()) expected = "file://%s/.pip/downloads/simple" % 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*", ]) class TestArgumentParser: def test_dash_e_single_1(self): parser = prepare_parse('testpkg') args = parser.parse_args('-e py26'.split()) envlist = _split_env(args.env) assert envlist == ['py26'] def test_dash_e_single_2(self): parser = prepare_parse('testpkg') args = parser.parse_args('-e py26,py33'.split()) envlist = _split_env(args.env) assert envlist == ['py26', 'py33'] def test_dash_e_same(self): parser = prepare_parse('testpkg') args = parser.parse_args('-e py26,py26'.split()) envlist = _split_env(args.env) assert envlist == ['py26', 'py26'] def test_dash_e_combine(self): parser = prepare_parse('testpkg') args = parser.parse_args('-e py26,py25,py33 -e py33,py27'.split()) envlist = _split_env(args.env) assert envlist == ['py26', 'py25', 'py33', 'py33', 'py27'] 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', ' ', '[]'] tox-1.6.0/tests/conftest.py0000664000175000017500000000004112203141321015241 0ustar hpkhpk00000000000000 from tox._pytestplugin import * tox-1.6.0/tests/test_result.py0000664000175000017500000000350112203141321015775 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_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-1.6.0/tests/test_z_cmdline.py0000664000175000017500000004573612203141321016443 0ustar hpkhpk00000000000000import tox import py import pytest import sys from tox._pytestplugin import ReportExpectMock try: import json except ImportError: import simplejson as json pytest_plugins = "pytester" from tox._cmdline 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") venv.update() 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.sdist() assert sdist.check() assert sdist.ext == ".zip" assert sdist == config.distdir.join(sdist.basename) sdist2 = session.sdist() assert sdist2 == sdist sdist.write("hello") assert sdist.stat().size < 10 sdist_new = Session(config).sdist() 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.sdist() 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 == 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) envlist = ['hello', 'world'] 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_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_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_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 "virtualenv" not in result.stdout.str() def test_separate_sdist_no_sdistfile(cmd, initproj): distshare = cmd.tmpdir.join("distshare") initproj("pkg123-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] 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) p0 = distshare.ensure("pkg123-1.3.5.zip") p = distshare.ensure("pkg123-1.4.5.zip") distshare.ensure("pkg123-1.4.5a1.zip") session = Session(config) sdist_path = session.sdist() 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.sdist() 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('X:{envsitepackagesdir}')" """}) result = cmd.run("tox") assert result.ret == 0 result.stdout.fnmatch_lines(""" X:*site-packages* """) 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"] pyinfo = envdata["python"] assert isinstance(pyinfo["version_info"], list) assert pyinfo["version"] assert pyinfo["executable"] tox-1.6.0/tests/test_venv.py0000664000175000017500000004732212203141321015446 0ustar hpkhpk00000000000000import py import tox import pytest import os, sys from tox._venv import * py25calls = int(sys.version_info[:2] == (2,5)) #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 interp == 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, 'basepython', 'notexistingpython') py.test.raises(tox.exception.InterpreterNotFound, 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() venv.create() l = mocksession._pcalls assert len(l) >= 1 args = l[0].args assert "virtualenv" in str(args[1]) if sys.platform != "win32": # realpath is needed for stuff like the debian symlinks assert py.path.local(sys.executable).realpath() == args[0] #assert Envconfig.toxworkdir in args assert venv.getcommandpath("easy_install", cwd=py.path.local()) interp = venv._getliveconfig().python assert interp == venv.envconfig._basepython_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_distribute(monkeypatch, mocksession, newconfig): config = newconfig([], """ [testenv:py123] distribute=False """) envconfig = config.envconfigs['py123'] venv = VirtualEnv(envconfig, session=mocksession) assert venv.path == envconfig.envdir assert not venv.path.check() venv.create() l = mocksession._pcalls assert len(l) >= 1 args = l[0].args assert "--distribute" not in map(str, args) assert "--setuptools" in map(str, args) 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) venv.create() 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) venv.create() 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([], """ [testenv:py123] deps= {distshare}/dep1-* """) venv = mocksession.getenv("py123") venv.create() l = mocksession._pcalls assert len(l) == 1 + py25calls distshare = venv.session.config.distshare distshare.ensure("dep1-1.0.zip") distshare.ensure("dep1-1.1.zip") venv.install_deps() assert len(l) == 2 + py25calls args = l[-1].args assert l[-1].cwd == venv.envconfig.envlogdir 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] distribute=True deps= dep1 dep2 """) venv = mocksession.getenv("py123") venv.create() l = mocksession._pcalls assert len(l) == 1 + py25calls venv.install_deps() assert len(l) == 2 + py25calls args = l[-1].args assert l[-1].cwd == venv.envconfig.envlogdir 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') venv.create() l = mocksession._pcalls assert len(l) == 1 + py25calls l[:] = [] venv.install_deps() # 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_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') venv.update() mocksession.installpkg(venv, pkg) mocksession.report.expect("verbosity0", "*create*") venv.update() mocksession.report.expect("verbosity0", "*recreate*") def test_test_runtests_action_command_is_in_output(newmocksession): mocksession = newmocksession([], ''' [testenv] commands = echo foo bar ''') venv = mocksession.getenv('python') venv.update() 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 == "commands failed" 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(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.1'): py.test.skip("needs python3.1") mocksession = newmocksession([], """ [testenv:py123] basepython=python3.1 deps= dep1 dep2 """) venv = mocksession.getenv('py123') venv.create() l = mocksession._pcalls assert len(l) == 1 args = l[0].args assert str(args[1]).endswith('virtualenv.py') 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-* """) 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() venv.update() 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() venv.update() mocksession.report.expect("*", "*reusing*") mocksession._clearmocks() cconfig.python = py.path.local("balla") cconfig.writeconfig(venv.path_config) venv.update() mocksession.report.expect("verbosity0", "*recreate*") def test_dep_recreation(self, newconfig, mocksession): config = newconfig([], "") envconfig = config.envconfigs['python'] venv = VirtualEnv(envconfig, session=mocksession) venv.update() cconfig = venv._getliveconfig() cconfig.deps[:] = [("1"*32, "xyz.zip")] cconfig.writeconfig(venv.path_config) mocksession._clearmocks() venv.update() mocksession.report.expect("*", "*recreate*") def test_distribute_recreation(self, newconfig, mocksession): config = newconfig([], "") envconfig = config.envconfigs['python'] venv = VirtualEnv(envconfig, session=mocksession) venv.update() cconfig = venv._getliveconfig() cconfig.distribute = True cconfig.writeconfig(venv.path_config) mocksession._clearmocks() venv.update() mocksession.report.expect("verbosity0", "*recreate*") def test_develop_recreation(self, newconfig, mocksession): config = newconfig([], "") envconfig = config.envconfigs['python'] venv = VirtualEnv(envconfig, session=mocksession) venv.update() cconfig = venv._getliveconfig() cconfig.develop = True cconfig.writeconfig(venv.path_config) mocksession._clearmocks() venv.update() mocksession.report.expect("verbosity0", "*recreate*") class TestVenvTest: def test_patchPATH(self, newmocksession, monkeypatch): monkeypatch.setenv("PIP_RESPECT_VIRTUALENV", "1") mocksession = newmocksession([], """ [testenv:python] commands=abc """) venv = mocksession.getenv("python") envconfig = venv.envconfig monkeypatch.setenv("PATH", "xyz") oldpath = venv.patchPATH() assert oldpath == "xyz" res = os.environ['PATH'] assert res == "%s%sxyz" % (envconfig.envbindir, os.pathsep) p = "xyz"+os.pathsep+str(envconfig.envbindir) monkeypatch.setenv("PATH", p) venv.patchPATH() res = os.environ['PATH'] assert res == "%s%s%s" %(envconfig.envbindir, os.pathsep, p) assert envconfig.commands monkeypatch.setattr(venv, '_pcall', lambda *args, **kwargs: 0/0) py.test.raises(ZeroDivisionError, "venv._install(list('123'))") py.test.raises(ZeroDivisionError, "venv.test()") py.test.raises(ZeroDivisionError, "venv.run_install_command(['qwe'])") py.test.raises(ZeroDivisionError, "venv._pcall([1,2,3])") monkeypatch.setenv("PIP_RESPECT_VIRTUALENV", "1") monkeypatch.setenv("PIP_REQUIRE_VIRTUALENV", "1") py.test.raises(ZeroDivisionError, "venv.run_install_command(['qwe'])") assert 'PIP_RESPECT_VIRTUALENV' not in os.environ assert 'PIP_REQUIRE_VIRTUALENV' not in os.environ def test_setenv_added_to_pcall(tmpdir, mocksession, newconfig): pkg = tmpdir.ensure("package.tar.gz") config = newconfig([], """ [testenv:python] commands=python -V 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: args = x.args env = x.env assert env is not None assert 'ENV_VAR' in env assert env['ENV_VAR'] == 'value' 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 assert '-U' in l[0].args assert '--no-deps' in l[0].args 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(args=["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 assert 'PYTHONIOENCODING' in env assert env['PYTHONIOENCODING'] == 'utf_8' 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(args=["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) mocksession.report.expect("warning", "*test command found but not*") def test_hack_home_env(tmpdir): from tox._venv import hack_home_env env = hack_home_env(tmpdir, "http://index") assert env["HOME"] == str(tmpdir) assert env["PIP_INDEX_URL"] == "http://index" assert "index_url = http://index" in \ tmpdir.join(".pydistutils.cfg").read() tmpdir.remove() env = hack_home_env(tmpdir, None) assert env["HOME"] == str(tmpdir) assert not tmpdir.join(".pydistutils.cfg").check() assert "PIP_INDEX_URL" not in env def test_hack_home_env_passthrough(tmpdir, monkeypatch): from tox._venv import hack_home_env env = hack_home_env(tmpdir, "http://index") monkeypatch.setattr(os, "environ", env) tmpdir = tmpdir.mkdir("tmpdir2") env2 = hack_home_env(tmpdir) assert env2["HOME"] == str(tmpdir) assert env2["PIP_INDEX_URL"] == "http://index" assert "index_url = http://index" in \ tmpdir.join(".pydistutils.cfg").read() tox-1.6.0/tests/test_quickstart.py0000664000175000017500000002522612203141321016661 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', 'Y', 'Y', 'Y', 'Y', 'N', 'N', 'Y', 'Y', 'Y', 'N', 'py.test', 'pytest'])) 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 = py24, py25, py26, py27, py32, py33, 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', 'Y', 'Y', 'Y', 'Y', 'N', 'N', 'Y', 'Y', 'Y', 'N', 'nosetests', ''])) 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 = py24, py25, py26, py27, py32, py33, 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', 'Y', 'Y', 'Y', 'Y', 'N', 'N', 'Y', 'Y', 'Y', 'N', 'trial', ''])) 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 = py24, py25, py26, py27, py32, py33, 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', 'Y', 'Y', 'Y', 'Y', 'N', 'N', 'Y', 'Y', 'Y', 'N', 'py.test', ''])) 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 = py24, py25, py26, py27, py32, py33, 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', 'py.test', ''])) 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', 'py.test', ''])) 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', 'py.test', ''])) 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 = py24, py25, py26, py27, py30, py31, py32, py33, 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', '', '', '', '', '', '', '', '', '', '', '', ''])) 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 = py24, py25, py26, py27, py30, py31, py32, py33, 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', '', '', '', '', '', '', '', '', '', '', '', '', ''])) 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 = py24, py25, py26, py27, py30, py31, py32, py33, 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 = { 'py24': True, 'py25': True, 'py26': True, 'py27': True, 'py32': True, 'py33': 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 = py24, py25, py26, py27, py32, py33, 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, '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, 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-1.6.0/tox.ini0000664000175000017500000000063012203141321013217 0ustar hpkhpk00000000000000[tox] envlist=py25,py27,py26,py32,py33,pypy [testenv:X] commands=echo {posargs} [testenv] commands=py.test --junitxml={envlogdir}/junit-{envname}.xml {posargs} deps=pytest>=2.3.5 [testenv:docs] basepython=python changedir=doc deps=sphinx {[testenv]deps} commands= py.test -v \ --junitxml={envlogdir}/junit-{envname}.xml \ check_sphinx.py {posargs} [pytest] rsyncdirs=tests tox tox-1.6.0/setup.cfg0000664000175000017500000000007312203141322013527 0ustar hpkhpk00000000000000[egg_info] tag_build = tag_date = 0 tag_svn_revision = 0 tox-1.6.0/MANIFEST.in0000664000175000017500000000022512203141321013442 0ustar hpkhpk00000000000000include CHANGELOG include README.rst include CONTRIBUTORS include ISSUES.txt include LICENSE include setup.py include tox.ini graft doc graft tests