Skip to content

Commit 16529d2

Browse files
authored
Merge branch 'master' into envsubs-independence
2 parents 89848ac + 6889c81 commit 16529d2

11 files changed

+258
-50
lines changed

.gitignore

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,15 @@
77
*.orig
88
*.rej
99
*~
10+
__pycache__
1011

1112
build
1213
dist
1314
doc/_build/
1415
tox.egg-info
1516
.tox
1617
.cache
18+
19+
.idea
20+
.eggs/
21+
py27/

CONTRIBUTORS

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,3 +46,6 @@ Paweł Adamczak
4646
Oliver Bestwalter
4747
Selim Belhaouane
4848
Nick Douma
49+
Cyril Roelandt
50+
Bartolome Sanchez Salado
51+
Laszlo Vasko

doc/config.txt

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -214,11 +214,16 @@ Complete list of settings that you can put into ``testenv*`` sections:
214214
You can use ``*`` and ``?`` to match multiple environment variables with
215215
one name.
216216

217-
Note that the ``PATH``, ``LANG``, ``LANGUAGE`` and ``PIP_INDEX_URL``
218-
variables are unconditionally passed down and on Windows ``SYSTEMROOT``,
219-
``PATHEXT``, ``TEMP`` and ``TMP`` will be passed down as well whereas on
220-
unix ``TMPDIR`` will be passed down. You can override these variables
221-
with the ``setenv`` option.
217+
Some variables are always passed through to ensure the basic functionality
218+
of standard library functions or tooling like pip:
219+
220+
* passed through on all platforms: ``PATH``, ``LANG``, ``LANGUAGE``,
221+
``LD_LIBRARY_PATH``, ``PIP_INDEX_URL``
222+
* Windows: ``SYSTEMDRIVE``, ``SYSTEMROOT``, ``PATHEXT``, ``TEMP``, ``TMP``
223+
``NUMBER_OF_PROCESSORS``, ``USERPROFILE``, ``MSYSTEM``
224+
* Others (e.g. UNIX, macOS): ``TMPDIR``
225+
226+
You can override these variables with the ``setenv`` option.
222227

223228
If defined the ``TOX_TESTENV_PASSENV`` environment variable (in the tox
224229
invocation environment) can define additional space-separated variable
@@ -430,6 +435,14 @@ then the value will be retrieved as ``os.environ['KEY']``
430435
and replace with and empty string if the environment variable does not
431436
exist.
432437

438+
Substitutions can also be nested. In that case they are expanded starting
439+
from the innermost expression::
440+
441+
{env:KEY:{env:DEFAULT_OF_KEY}}
442+
443+
the above example is roughly equivalent to
444+
``os.environ.get('KEY', os.environ['DEFAULT_OF_KEY'])``
445+
433446
.. _`command positional substitution`:
434447
.. _`positional substitution`:
435448

tests/test_config.py

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -334,6 +334,16 @@ def test_command_env_substitution(self, newconfig):
334334
assert envconfig.commands == [["ls", "testvalue"]]
335335
assert envconfig.setenv["TEST"] == "testvalue"
336336

337+
def test_command_env_substitution_global(self, newconfig):
338+
"""Ensure referenced {env:key:default} values are substituted correctly."""
339+
config = newconfig("""
340+
[testenv]
341+
setenv = FOO = bar
342+
commands = echo {env:FOO}
343+
""")
344+
envconfig = config.envconfigs['python']
345+
assert envconfig.commands == [["echo", "bar"]]
346+
337347

338348
class TestIniParser:
339349
def test_getstring_single(self, tmpdir, newconfig):
@@ -866,6 +876,7 @@ def test_passenv_as_multiline_list(self, tmpdir, newconfig, monkeypatch, plat):
866876
assert "TMP" in envconfig.passenv
867877
assert "NUMBER_OF_PROCESSORS" in envconfig.passenv
868878
assert "USERPROFILE" in envconfig.passenv
879+
assert "MSYSTEM" in envconfig.passenv
869880
else:
870881
assert "TMPDIR" in envconfig.passenv
871882
assert "PATH" in envconfig.passenv
@@ -1098,7 +1109,7 @@ def test_substitution_notfound_issue515(tmpdir, newconfig):
10981109
]
10991110

11001111
@pytest.mark.xfail(raises=AssertionError, reason="issue #301")
1101-
def test_substitution_env_defaults_issue301(tmpdir, newconfig, monkeypatch):
1112+
def test_substitution_nested_env_defaults_issue301(tmpdir, newconfig, monkeypatch):
11021113
monkeypatch.setenv("IGNORE_STATIC_DEFAULT", "env")
11031114
monkeypatch.setenv("IGNORE_DYNAMIC_DEFAULT", "env")
11041115
config = newconfig("""
@@ -2179,6 +2190,20 @@ def test_showconfig_with_force_dep_version(self, cmd, initproj):
21792190
r'*deps*dep1, dep2==5.0*',
21802191
])
21812192

2193+
@pytest.mark.xfail(reason='Upstream bug. See #203')
2194+
def test_colon_symbol_in_directory_name(self, cmd, initproj):
2195+
initproj('colon:_symbol_in_dir_name', filedefs={
2196+
'tox.ini': '''
2197+
[tox]
2198+
envlist = py27
2199+
2200+
[testenv]
2201+
commands = pip --version
2202+
''',
2203+
})
2204+
result = cmd.run("tox")
2205+
assert result.ret == 0
2206+
21822207

21832208
@pytest.mark.parametrize("cmdline,envlist", [
21842209
("-e py26", ['py26']),

tests/test_quickstart.py

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -379,6 +379,106 @@ def test_quickstart_main_existing_tox_ini(self, monkeypatch):
379379
result = read_tox('tox-generated.ini')
380380
assert(result == expected_tox_ini)
381381

382+
def test_quickstart_main_tox_ini_location_can_be_overridden(
383+
self,
384+
tmpdir,
385+
monkeypatch):
386+
monkeypatch.setattr(
387+
tox._quickstart, 'term_input',
388+
self.get_mock_term_input(
389+
[
390+
'1', # py27 and py33
391+
'py.test', # command to run tests
392+
'', # test dependencies
393+
]
394+
)
395+
)
396+
397+
root_dir = tmpdir.mkdir('alt-root')
398+
tox_ini_path = root_dir.join('tox.ini')
399+
400+
tox._quickstart.main(argv=['tox-quickstart', root_dir.basename])
401+
402+
assert tox_ini_path.isfile()
403+
404+
expected_tox_ini = """
405+
# Tox (https://tox.readthedocs.io/) is a tool for running tests
406+
# in multiple virtualenvs. This configuration file will run the
407+
# test suite on all supported python versions. To use it, "pip install tox"
408+
# and then run "tox" from this directory.
409+
410+
[tox]
411+
envlist = py27
412+
413+
[testenv]
414+
commands = py.test
415+
deps =
416+
pytest
417+
""".lstrip()
418+
result = read_tox(fname=tox_ini_path.strpath)
419+
assert(result == expected_tox_ini)
420+
421+
def test_quickstart_main_custom_tox_ini_location_with_existing_tox_ini(
422+
self,
423+
tmpdir,
424+
monkeypatch):
425+
monkeypatch.setattr(
426+
tox._quickstart, 'term_input',
427+
self.get_mock_term_input(
428+
[
429+
'1', # py27 and py33
430+
'py.test', # command to run tests
431+
'', # test dependencies
432+
'', # tox.ini already exists; overwrite?
433+
]
434+
)
435+
)
436+
437+
root_dir = tmpdir.mkdir('alt-root')
438+
tox_ini_path = root_dir.join('tox.ini')
439+
tox_ini_path.write('foo\nbar\n')
440+
441+
tox._quickstart.main(argv=['tox-quickstart', root_dir.basename])
442+
tox_ini_path = root_dir.join('tox-generated.ini')
443+
444+
assert tox_ini_path.isfile()
445+
446+
expected_tox_ini = """
447+
# Tox (https://tox.readthedocs.io/) is a tool for running tests
448+
# in multiple virtualenvs. This configuration file will run the
449+
# test suite on all supported python versions. To use it, "pip install tox"
450+
# and then run "tox" from this directory.
451+
452+
[tox]
453+
envlist = py27
454+
455+
[testenv]
456+
commands = py.test
457+
deps =
458+
pytest
459+
""".lstrip()
460+
result = read_tox(fname=tox_ini_path.strpath)
461+
assert(result == expected_tox_ini)
462+
463+
def test_quickstart_main_custom_nonexistent_tox_ini_location(
464+
self,
465+
tmpdir,
466+
monkeypatch):
467+
monkeypatch.setattr(
468+
tox._quickstart, 'term_input',
469+
self.get_mock_term_input(
470+
[
471+
'1', # py27 and py33
472+
'py.test', # command to run tests
473+
'', # test dependencies
474+
]
475+
)
476+
)
477+
478+
root_dir = tmpdir.join('nonexistent-root')
479+
480+
assert tox._quickstart.main(argv=['tox-quickstart', root_dir.basename]) == 2
481+
382482

383483
class TestToxQuickstart(object):
384484
def test_pytest(self):

tox.ini

Lines changed: 23 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,49 +1,50 @@
11
[tox]
2-
envlist=py27,py26,py34,py33,py35,py36,pypy,flakes,py26-bare
2+
envlist = py27,py26,py34,py33,py35,py36,pypy,flakes,py26-bare
3+
minversion = 2.7.0
34

45
[testenv:X]
5-
commands=echo {posargs}
6+
commands = echo {posargs}
7+
description = print the positional arguments passed in with echo
68

79
[testenv]
8-
commands= pytest --timeout=180 {posargs:tests}
9-
10-
deps=pytest>=3.0.0
11-
pytest-timeout
10+
commands = pytest --timeout=180 {posargs:tests}
11+
description = run the unit tests with pytest under the current Python env
12+
deps = pytest >= 3.0.0
13+
pytest-timeout
1214

1315
[testenv:py26-bare]
1416
deps =
1517
commands = tox -h
1618
install_command = pip install {opts} {packages}
1719
list_dependencies_command = pip freeze
20+
description = invoke the tox help message under Python 2.6
1821

1922
[testenv:py26]
2023
install_command = pip install {opts} {packages}
2124
list_dependencies_command = pip freeze
2225

23-
2426
[testenv:docs]
25-
basepython=python
26-
changedir=doc
27-
deps=sphinx
28-
{[testenv]deps}
29-
commands=
30-
pytest -v check_sphinx.py {posargs}
27+
basepython = python
28+
changedir = doc
29+
deps = sphinx
30+
{[testenv]deps}
31+
commands = pytest -v check_sphinx.py {posargs}
32+
description = invoke sphinx-build and try to build the HTML page (also check link validity)
3133

3234
[testenv:flakes]
33-
deps = pytest-flakes>=0.2
35+
deps = pytest-flakes >= 0.2
3436
pytest-pep8
35-
36-
commands =
37-
pytest --flakes -m flakes tox tests
38-
pytest --pep8 -m pep8 tox tests
37+
description = run static analysis and style check using flakes and pep-8
38+
commands = pytest --flakes -m flakes tox tests
39+
pytest --pep8 -m pep8 tox tests
3940

4041
[testenv:dev]
4142
# required to make looponfail reload on every source code change
4243
usedevelop = True
4344

44-
deps =
45-
pytest-xdist>=1.11
46-
commands = {posargs:pytest -s -x -f -v}
45+
deps = pytest-xdist >= 1.11
46+
commands = {posargs:py.test -s -x -f -v}
47+
description = DEV enviroment, if no posarg is specified: run pytest
4748

4849
[pytest]
4950
rsyncdirs = tests tox
@@ -57,6 +58,5 @@ pep8maxlinelength = 99
5758
# W503 - line break before binary operator
5859
# E402 - module level import not at top of file
5960
# E731 - do not assign a lambda expression, use a def
60-
pep8ignore =
61-
*.py W503 E402 E731
61+
pep8ignore = *.py W503 E402 E731
6262
flakes-ignore = ImportStarUsage

tox/_quickstart.py

Lines changed: 36 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@
4040
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
4141
"""
4242

43+
import argparse
4344
import sys
4445
from os import path
4546
from codecs import open
@@ -224,19 +225,24 @@ def generate(d, overwrite=True, silent=False):
224225

225226
def write_file(fpath, mode, content):
226227
print('Creating file %s.' % fpath)
227-
f = open(fpath, mode, encoding='utf-8')
228228
try:
229-
f.write(content)
230-
finally:
231-
f.close()
229+
with open(fpath, mode, encoding='utf-8') as f:
230+
f.write(content)
231+
except IOError:
232+
print('Error writing file.')
233+
raise
232234

233235
sys.stdout.write('\n')
234236

235-
fpath = 'tox.ini'
237+
fpath = path.join(d.get('path', ''), 'tox.ini')
236238

237239
if path.isfile(fpath) and not overwrite:
238240
print('File %s already exists.' % fpath)
239-
do_prompt(d, 'fpath', 'Alternative path to write tox.ini contents to', 'tox-generated.ini')
241+
do_prompt(
242+
d,
243+
'fpath',
244+
'Alternative path to write tox.ini contents to',
245+
path.join(d.get('path', ''), 'tox-generated.ini'))
240246
fpath = d['fpath']
241247

242248
write_file(fpath, 'w', conf_text)
@@ -251,14 +257,25 @@ def write_file(fpath, mode, content):
251257
''')
252258

253259

260+
def parse_args(argv):
261+
parser = argparse.ArgumentParser(
262+
description='Command-line script to quickly setup tox.ini for a Python project.'
263+
)
264+
parser.add_argument(
265+
'root', type=str, nargs='?', default='.',
266+
help='Custom root directory to write tox.ini to. Defaults to current directory.'
267+
)
268+
parser.add_argument('--version', action='version', version='%(prog)s ' + __version__)
269+
270+
args = argv[1:]
271+
return parser.parse_args(args)
272+
273+
254274
def main(argv=sys.argv):
255-
d = {}
275+
args = parse_args(argv)
256276

257-
if len(argv) > 3:
258-
print('Usage: tox-quickstart [root]')
259-
sys.exit(1)
260-
elif len(argv) == 2:
261-
d['path'] = argv[1]
277+
d = {}
278+
d['path'] = args.root
262279

263280
try:
264281
ask_user(d)
@@ -268,8 +285,13 @@ def main(argv=sys.argv):
268285
return
269286

270287
d = process_input(d)
271-
generate(d, overwrite=False)
288+
try:
289+
generate(d, overwrite=False)
290+
except Exception:
291+
return 2
292+
293+
return 0
272294

273295

274296
if __name__ == '__main__':
275-
main()
297+
sys.exit(main())

0 commit comments

Comments
 (0)