Skip to content
This repository was archived by the owner on Jul 14, 2023. It is now read-only.

Commit

Permalink
Merge pull request #11 from samstav/samstav/pass-if-pyenv-missing
Browse files Browse the repository at this point in the history
Don't kill tox run if pyenv missing
  • Loading branch information
stavxyz authored Aug 29, 2017
2 parents aad7fc1 + 214b88e commit 3f160c9
Show file tree
Hide file tree
Showing 7 changed files with 157 additions and 26 deletions.
49 changes: 48 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ dependencies:
- pyenv local 2.7.9 3.4.3 3.5.0
```
The versions passed to `pyenv local` must be [installed](https://github.com/yyuu/pyenv/blob/master/COMMANDS.md#pyenv-install) for this to work. Check out the list of python versions that are pre-installed in the CircleCI build environment: https://circleci.com/docs/environment#python
The versions passed to `pyenv local` must be [installed](https://github.com/yyuu/pyenv/blob/master/COMMANDS.md#pyenv-install) for this to work. See [CircleCI Preinstalled Python Versions](#circleci-preinstalled-python-versions) for a list.

#### Corresponding [tox.ini](https://tox.readthedocs.org/en/latest/config.html)

Expand All @@ -29,3 +29,50 @@ The result of the setup above means running `tox` will run tests against python
#### notes

If you want tox to _exclusively_ use `pyenv which` to find executables, you will need use the `--tox-pyenv-no-fallback` command line option, or set `tox_pyenv_fallback=False` in your tox.ini. By default, if `tox-pyenv` fails to find a python executable it will fallback to tox's built-in strategy.

#### CircleCI Preinstalled Python Versions

Here is the list of python versions that are *pre-installed* in the CircleCI build environment (as of 09/27/2017):

```
$ pyenv versions
system
2.6.6
2.6.8
2.7
2.7.10
2.7.11
2.7.3
2.7.4
2.7.5
2.7.6
2.7.7
2.7.8
* 2.7.9 (set by /home/ubuntu/.pyenv/version)
3.1.5
3.2
3.2.5
3.3.0
3.3.2
3.3.3
3.4.0
3.4.1
3.4.2
3.4.3
3.5.0
pypy-2.2.1
pypy-2.3.1
pypy-2.4.0
pypy-2.5.0
```

If the version you need isn't in the list, such as Python `3.6-dev` include an `install` step:

```
dependencies:
override:
- pip install tox tox-pyenv
- pyenv install --skip-existing 3.6-dev
- pyenv local 3.6-dev
```

13 changes: 7 additions & 6 deletions circle.yml
Original file line number Diff line number Diff line change
@@ -1,21 +1,22 @@
machine:
python:
version: '2.7.9'
version: '2.7.11'
environment:
TOX_PYPY: 'pypy-2.5.0'
TOX_PY: '2.7.9'
TOX_PY: '2.7.11'
TOX_PY26: '2.6.8'
TOX_PY27: '2.7.10'
TOX_PY32: '3.2.5'
TOX_PY27: '2.7.9'
TOX_PY33: '3.3.3'
TOX_PY34: '3.4.3'
TOX_PY35: '3.5.0'

dependencies:
override:
- pip -V
- pip install -U 'pip<8.0' 'virtualenv<14.0' ipdb tox .
- pyenv local $TOX_PY35 $TOX_PY34 $TOX_PY33 $TOX_PY32 $TOX_PY27 $TOX_PY26 $TOX_PYPY
- pip install -U pip
- pip install -U tox
- pip install -U .
- pyenv local $TOX_PY35 $TOX_PY34 $TOX_PY33 $TOX_PY27 $TOX_PY26 $TOX_PYPY

test:
override:
Expand Down
7 changes: 6 additions & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,11 @@
'tox>=2.0'
]

TESTS_REQUIRE = [
'mock>=2.0.0',
'pycodestyle>=2.3.1',
'pylint>=1.7.2',
]

CLASSIFIERS = [
'Intended Audience :: Developers',
Expand All @@ -52,7 +57,6 @@
'Programming Language :: Python :: 2.6',
'Programming Language :: Python :: 2.7',
'Programming Language :: Python :: 3',
'Programming Language :: Python :: 3.2',
'Programming Language :: Python :: 3.3',
'Programming Language :: Python :: 3.4',
'Programming Language :: Python :: 3.5',
Expand All @@ -72,6 +76,7 @@
'license': about['__license__'],
'long_description': LONG_DESCRIPTION,
'name': about['__title__'],
'tests_require': TESTS_REQUIRE,
'py_modules': ['tox_pyenv'],
'url': about['__url__'],
'version': about['__version__'],
Expand Down
3 changes: 3 additions & 0 deletions test-requirements.txt
Original file line number Diff line number Diff line change
@@ -1 +1,4 @@
mock>=2.0.0
nose==1.3.7
pycodestyle>=2.3.1
pylint>=1.7.2
62 changes: 58 additions & 4 deletions test_tox_pyenv.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,16 @@
from __future__ import print_function

import errno
import os
import platform
import subprocess
import sys
import unittest

import mock

import tox_pyenv

try:
unicode
except NameError:
Expand All @@ -19,6 +24,55 @@ def touni(s, enc='utf8', err='strict'):
return unicode(s or ("" if s is None else s))


class MockTestenvConfig(object):
def __init__(self, basepython):
self.basepython = basepython
self.tox_pyenv_fallback = True


class TestToxPyenvNoPyenv(unittest.TestCase):

def setUp(self):
def _mock_popen_func(cmd, *args, **kw):
if all(x in cmd for x in ['which', '*TEST*']):
raise OSError(errno.ENOENT, 'No such file or directory')
self.fail('Unexpected call to Popen')
# return self.popen_patcher.temp_original(*args, **kw)
self.popen_patcher = mock.patch.object(
tox_pyenv.subprocess, 'Popen', autospec=True,
side_effect=_mock_popen_func,
)
self.popen_patcher.start()
self.warning_patcher = mock.patch.object(
tox_pyenv.LOG, 'warning', autospec=True,
)
self.warning_patcher.start()

def tearDown(self):
self.popen_patcher.stop()
self.warning_patcher.stop()

def test_logs_if_no_pyenv_binary(self):
mock_test_env_config = MockTestenvConfig('*TEST*')
tox_pyenv.tox_get_python_executable(mock_test_env_config)
expected_popen = [
mock.call(
[mock.ANY, 'which', '*TEST*'],
stderr=-1, stdout=-1,
universal_newlines=True
)
]
self.assertEqual(
tox_pyenv.subprocess.Popen.call_args_list,
expected_popen
)
expected_warn = [
mock.call("pyenv doesn't seem to be installed, you "
"probably don't want this plugin installed either.")
]
self.assertEqual(tox_pyenv.LOG.warning.call_args_list, expected_warn)


class TestThings(unittest.TestCase):

def test_the_answer(self):
Expand All @@ -27,12 +81,12 @@ def test_the_answer(self):

def test_is_precisely_correct_version(self):

toxenvname = 'TOX_%s' % os.environ['TOX_ENV_NAME'].upper()
expected_string = os.environ[toxenvname]
toxenvname = 'TOX_%s' % os.environ['TOX_ENV_NAME'].upper().strip()
expected_string = os.environ[toxenvname].strip(' "\'')
print('\n\nTOX ENV NAME: %s' % toxenvname)
if platform.python_implementation() == 'PyPy':
actual_list = [str(_) for _ in sys.pypy_version_info[:3]]
expected_string = expected_string.split('-')[1]
actual_list = [str(_).strip() for _ in sys.pypy_version_info[:3]]
expected_string = expected_string.split('-')[1].strip(' "\'')
print('\nExpected version for this tox env: PyPy %s'
% expected_string)
print('Actual version for this tox env: PyPy %s'
Expand Down
14 changes: 11 additions & 3 deletions tox.ini
Original file line number Diff line number Diff line change
@@ -1,14 +1,22 @@
[tox]
envlist = py,py26,py27,py32,py33,py34,py35,pypy
envlist = style,py,py26,py27,py33,py34,py35,pypy

[testenv]
whitelist_externals = env
install_command = pip install -U {opts} {packages}
setenv= TOX_ENV_NAME={envname}
passenv = TOX_*
deps = {py,py26,py27,py33,py34,py35,pypy}: pip
-r{toxinidir}/requirements.txt
deps = -r{toxinidir}/requirements.txt
-r{toxinidir}/test-requirements.txt
commands = python -V
env
nosetests {posargs} --verbose --nocapture --logging-level=DEBUG


[testenv:style]
deps = -r{toxinidir}/requirements.txt
-r{toxinidir}/test-requirements.txt
basepython = python2.7
commands =
pycodestyle tox_pyenv.py test_tox_pyenv.py
pylint tox_pyenv.py
35 changes: 24 additions & 11 deletions tox_pyenv.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,16 @@ class PyenvWhichFailed(ToxPyenvException):

@tox_hookimpl
def tox_get_python_executable(envconfig):
"""Return a python executable for the given python base name.
The first plugin/hook which returns an executable path will determine it.
``envconfig`` is the testenv configuration which contains
per-testenv configuration, notably the ``.envname`` and ``.basepython``
setting.
"""
try:
# pylint: disable=no-member
pyenv = (getattr(py.path.local.sysfind('pyenv'), 'strpath', 'pyenv')
or 'pyenv')
cmd = [pyenv, 'which', envconfig.basepython]
Expand All @@ -79,19 +88,22 @@ def tox_get_python_executable(envconfig):
)
out, err = pipe.communicate()
except OSError:
raise PyenvMissing(
err = '\'pyenv\': command not found'
LOG.warning(
"pyenv doesn't seem to be installed, you probably "
"don't want this plugin installed either.")
if pipe.poll() == 0:
return out.strip()
"don't want this plugin installed either."
)
else:
if not envconfig.tox_pyenv_fallback:
raise PyenvWhichFailed(err)
LOG.debug("`%s` failed thru tox-pyenv plugin, falling back. "
"STDERR: \"%s\" | To disable this behavior, set "
"tox_pyenv_fallback=False in your tox.ini or use "
" --tox-pyenv-no-fallback on the command line.",
' '.join([str(x) for x in cmd]), err)
if pipe.poll() == 0:
return out.strip()
else:
if not envconfig.tox_pyenv_fallback:
raise PyenvWhichFailed(err)
LOG.debug("`%s` failed thru tox-pyenv plugin, falling back. "
"STDERR: \"%s\" | To disable this behavior, set "
"tox_pyenv_fallback=False in your tox.ini or use "
" --tox-pyenv-no-fallback on the command line.",
' '.join([str(x) for x in cmd]), err)


def _setup_no_fallback(parser):
Expand Down Expand Up @@ -137,4 +149,5 @@ def _pyenv_fallback(testenv_config, value):

@tox_hookimpl
def tox_addoption(parser):
"""Add command line option to the argparse-style parser object."""
_setup_no_fallback(parser)

0 comments on commit 3f160c9

Please sign in to comment.