Repository: tox-dev/tox

Issue:
Allow space after '-r' option in deps configuration file
I was cleaning up a ``tox.ini`` and attempted to change:

    [testenv]
    deps = -rfoo.txt

to

    [testenv]
    deps = -r foo.txt

to make it more readable, however it tries to read `` foo.txt`` (with space at beginning, lost when formatted) which does not exist and this was unexpected. I propose it strips whitespace so it is closer to accepting the pip format, as suggested by the ``deps`` option help: "each line specifies a dependency in pip/setuptools format."

[``pip`` itself uses its own config parser.](https://github.com/pypa/pip/blob/62a695c4b905b503f7ff38c0d47cf0ea203853ea/src/pip/_internal/req/req_file.py#L130)

I would be happy to write a PR myself, this can be implemented by adding:

    if name.startswith('-r'):
        name = '-r' + name[2:].strip()

[after this line.](https://github.com/tox-dev/tox/blob/master/tox/config.py#L125).

Discussion:
RonnyPfannschmidt: i believe this one needs some unit and integration tests, at least a integration test tho
cryvate: @RonnyPfannschmidt I agree, I will have a look later.

I did not run the tests (I was on the train with flaky connection, so running tox/pip was not easy) so I need to fix my use of .format to be py2.6 compatible.

It isn't nice to have to explicitly list all pip options with arguments. [Another road to travel would be to use shlex.split (available in 2.6 as well).](https://docs.python.org/3/library/shlex.html#shlex.split) This means the wheel is not being re-invented, on the other hand, it might do things that breaks backwards compatibility. 

codecov[bot]: # [Codecov](https://codecov.io/gh/tox-dev/tox/pull/669?src=pr&el=h1) Report
> Merging [#669](https://codecov.io/gh/tox-dev/tox/pull/669?src=pr&el=desc) into [master](https://codecov.io/gh/tox-dev/tox/commit/cc34546210701f099f59ec79016c4ec058749559?src=pr&el=desc) will **decrease** coverage by `4.74%`.
> The diff coverage is `50%`.

[![Impacted file tree graph](https://codecov.io/gh/tox-dev/tox/pull/669/graphs/tree.svg?width=650&token=DYodAwDCZ5&height=150&src=pr)](https://codecov.io/gh/tox-dev/tox/pull/669?src=pr&el=tree)

```diff
@@            Coverage Diff             @@
##           master     #669      +/-   ##
==========================================
- Coverage   93.86%   89.12%   -4.75%     
==========================================
  Files          11       11              
  Lines        2364     2372       +8     
  Branches        0      401     +401     
==========================================
- Hits         2219     2114     -105     
- Misses        145      179      +34     
- Partials        0       79      +79
```


| [Impacted Files](https://codecov.io/gh/tox-dev/tox/pull/669?src=pr&el=tree) | Coverage Δ | |
|---|---|---|
| [tox/config.py](https://codecov.io/gh/tox-dev/tox/pull/669?src=pr&el=tree#diff-dG94L2NvbmZpZy5weQ==) | `94.32% <50%> (-3.26%)` | :arrow_down: |
| [tox/interpreters.py](https://codecov.io/gh/tox-dev/tox/pull/669?src=pr&el=tree#diff-dG94L2ludGVycHJldGVycy5weQ==) | `59.37% <0%> (-22.66%)` | :arrow_down: |
| [tox/\_verlib.py](https://codecov.io/gh/tox-dev/tox/pull/669?src=pr&el=tree#diff-dG94L192ZXJsaWIucHk=) | `71.57% <0%> (-9.48%)` | :arrow_down: |
| [tox/\_quickstart.py](https://codecov.io/gh/tox-dev/tox/pull/669?src=pr&el=tree#diff-dG94L19xdWlja3N0YXJ0LnB5) | `77.62% <0%> (-5.6%)` | :arrow_down: |
| [tox/\_pytestplugin.py](https://codecov.io/gh/tox-dev/tox/pull/669?src=pr&el=tree#diff-dG94L19weXRlc3RwbHVnaW4ucHk=) | `89.15% <0%> (-4.82%)` | :arrow_down: |
| [tox/venv.py](https://codecov.io/gh/tox-dev/tox/pull/669?src=pr&el=tree#diff-dG94L3ZlbnYucHk=) | `92.52% <0%> (-3.74%)` | :arrow_down: |
| [tox/session.py](https://codecov.io/gh/tox-dev/tox/pull/669?src=pr&el=tree#diff-dG94L3Nlc3Npb24ucHk=) | `91.24% <0%> (-3.11%)` | :arrow_down: |

------

[Continue to review full report at Codecov](https://codecov.io/gh/tox-dev/tox/pull/669?src=pr&el=continue).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/tox-dev/tox/pull/669?src=pr&el=footer). Last update [cc34546...f47fb5d](https://codecov.io/gh/tox-dev/tox/pull/669?src=pr&el=lastupdated). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments).

asottile: It would be nice to use `shlex.split` but it turns out it's horribly broken in python2.6:

```
Python 2.6.9 (default, Jul 29 2017, 12:47:14) 
[GCC 5.4.0 20160609] on linux4
Type "help", "copyright", "credits" or "license" for more information.
>>> import shlex
>>> shlex.split(u'hello world')
['h\x00\x00\x00e\x00\x00\x00l\x00\x00\x00l\x00\x00\x00o\x00\x00\x00', '\x00\x00\x00w\x00\x00\x00o\x00\x00\x00r\x00\x00\x00l\x00\x00\x00d\x00\x00\x00']
```

Patch:
diff --git a/.travis.yml b/.travis.yml
index 0b7da097..4910ea00 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -23,7 +23,7 @@ matrix:
     language: generic
 
 before_install: if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then source .travis-osx; fi
-install: pip install --pre -U tox
+install: pip install six && pip install --pre -U tox
 script: tox -e py
 after_success:
   - tox -e coverage
diff --git a/CONTRIBUTORS b/CONTRIBUTORS
index c1076cc0..c7592b45 100644
--- a/CONTRIBUTORS
+++ b/CONTRIBUTORS
@@ -18,11 +18,12 @@ Cyril Roelandt
 Eli Collins
 Eugene Yunak
 Fernando L. Pereira
+Henk-Jaap Wagenaar
 Igor Duarte Cardoso
 Ionel Maries Cristian
-Ionel Maries Cristian
 Itxaka Serrano
 Jannis Leidel
+Johannes Christ
 Josh Smeaton
 Julian Krause
 Krisztian Fekete
diff --git a/changelog/331.misc.rst b/changelog/331.misc.rst
new file mode 100644
index 00000000..e77d0e2d
--- /dev/null
+++ b/changelog/331.misc.rst
@@ -0,0 +1,1 @@
+Running ``tox`` without a ``setup.py`` now has a more friendly error message and gives troubleshooting suggestions - by @Volcyy.
diff --git a/changelog/663.misc.rst b/changelog/663.misc.rst
new file mode 100644
index 00000000..6852e188
--- /dev/null
+++ b/changelog/663.misc.rst
@@ -0,0 +1,1 @@
+Fix pycodestyle (formerly pep8) errors E741 (ambiguous variable names, in this case, 'l's) and remove ignore of this error in tox.ini - by @cryvate
diff --git a/changelog/668.feature.rst b/changelog/668.feature.rst
new file mode 100644
index 00000000..5493845c
--- /dev/null
+++ b/changelog/668.feature.rst
@@ -0,0 +1,1 @@
+Allow spaces in command line options to pip in deps. Where previously only ``deps=-rreq.txt`` and ``deps=--requirement=req.txt`` worked, now also ``deps=-r req.txt`` and ``deps=--requirement req.txt`` work - by @cryvate
diff --git a/doc/config.rst b/doc/config.rst
index 1cb1bf6a..11d27cd2 100644
--- a/doc/config.rst
+++ b/doc/config.rst
@@ -567,6 +567,11 @@ You can still list environments explicitly along with generated ones::
 
     envlist = {py26,py27}-django{15,16}, docs, flake
 
+Keep in mind that whitespace characters (except newline) within ``{}``
+are stripped, so the following line defines the same environment names::
+
+    envlist = {py26, py27}-django{ 15, 16 }, docs, flake
+
 .. note::
 
     To help with understanding how the variants will produce section values,
diff --git a/tox.ini b/tox.ini
index 0ab76dfa..b190e388 100644
--- a/tox.ini
+++ b/tox.ini
@@ -47,8 +47,6 @@ commands = python -m flake8 --show-source tox setup.py {posargs}
 [flake8]
 max-complexity = 22
 max-line-length = 99
-# TODO: fix E741
-ignore = E741
 
 [testenv:py26-bare]
 description = invoke the tox help message under Python 2.6
diff --git a/tox/config.py b/tox/config.py
index 7860b871..b056431b 100755
--- a/tox/config.py
+++ b/tox/config.py
@@ -31,6 +31,18 @@ hookimpl = pluggy.HookimplMarker("tox")
 
 _dummy = object()
 
+PIP_INSTALL_SHORT_OPTIONS_ARGUMENT = ['-{0}'.format(option) for option in [
+    'c', 'e', 'r', 'b', 't', 'd',
+]]
+
+PIP_INSTALL_LONG_OPTIONS_ARGUMENT = ['--{0}'.format(option) for option in [
+    'constraint', 'editable', 'requirement', 'build', 'target', 'download',
+    'src', 'upgrade-strategy', 'install-options', 'global-option',
+    'root', 'prefix', 'no-binary', 'only-binary', 'index-url',
+    'extra-index-url', 'find-links', 'proxy', 'retries', 'timeout',
+    'exists-action', 'trusted-host', 'client-cert', 'cache-dir',
+]]
+
 
 def get_plugin_manager(plugins=()):
     # initialize plugin manager
@@ -124,6 +136,24 @@ class DepOption:
             else:
                 name = depline.strip()
                 ixserver = None
+
+                # we need to process options, in case they contain a space,
+                # as the subprocess call to pip install will otherwise fail.
+
+                # in case of a short option, we remove the space
+                for option in PIP_INSTALL_SHORT_OPTIONS_ARGUMENT:
+                    if name.startswith(option):
+                        name = '{0}{1}'.format(
+                            option, name[len(option):].strip()
+                        )
+
+                # in case of a long option, we add an equal sign
+                for option in PIP_INSTALL_LONG_OPTIONS_ARGUMENT:
+                    if name.startswith(option + ' '):
+                        name = '{0}={1}'.format(
+                            option, name[len(option):].strip()
+                        )
+
             name = self._replace_forced_dep(name, config)
             deps.append(DepConfig(name, ixserver))
         return deps
@@ -895,7 +925,7 @@ def _expand_envstr(envstr):
 
     def expand(env):
         tokens = re.split(r'\{([^}]+)\}', env)
-        parts = [token.split(',') for token in tokens]
+        parts = [re.sub('\s+', '', token).split(',') for token in tokens]
         return [''.join(variant) for variant in itertools.product(*parts)]
 
     return mapcat(expand, envlist)
diff --git a/tox/result.py b/tox/result.py
index 06835500..47f68853 100644
--- a/tox/result.py
+++ b/tox/result.py
@@ -63,8 +63,7 @@ class EnvLog:
             version=version)
 
     def get_commandlog(self, name):
-        l = self.dict.setdefault(name, [])
-        return CommandLog(self, l)
+        return CommandLog(self, self.dict.setdefault(name, []))
 
     def set_installed(self, packages):
         self.dict["installed_packages"] = packages
diff --git a/tox/session.py b/tox/session.py
index 35cb4dc3..b2aeec6a 100644
--- a/tox/session.py
+++ b/tox/session.py
@@ -109,12 +109,11 @@ class Action(object):
         else:
             logdir = self.session.config.logdir
         try:
-            l = logdir.listdir("%s-*" % actionid)
+            log_count = len(logdir.listdir("%s-*" % actionid))
         except (py.error.ENOENT, py.error.ENOTDIR):
             logdir.ensure(dir=1)
-            l = []
-        num = len(l)
-        path = logdir.join("%s-%s.log" % (actionid, num))
+            log_count = 0
+        path = logdir.join("%s-%s.log" % (actionid, log_count))
         f = path.open('w')
         f.flush()
         return f
@@ -404,7 +403,17 @@ class Session:
     def _makesdist(self):
         setup = self.config.setupdir.join("setup.py")
         if not setup.check():
-            raise tox.exception.MissingFile(setup)
+            self.report.error(
+                "No setup.py file found. The expected location is:\n"
+                "  %s\n"
+                "You can\n"
+                "  1. Create one:\n"
+                "     https://packaging.python.org/tutorials/distributing-packages/#setup-py\n"
+                "  2. Configure tox to avoid running sdist:\n"
+                "     http://tox.readthedocs.io/en/latest/example/general.html"
+                "#avoiding-expensive-sdist" % setup
+            )
+            raise SystemExit(1)
         action = self.newaction(None, "packaging")
         with action:
             action.setactivity("sdist-make", setup)
diff --git a/tox/venv.py b/tox/venv.py
index 69cc0c49..af2a80b7 100755
--- a/tox/venv.py
+++ b/tox/venv.py
@@ -195,14 +195,14 @@ class VirtualEnv(object):
                               sitepackages, develop, deps, alwayscopy)
 
     def _getresolvedeps(self):
-        l = []
+        deps = []
         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
+            deps.append(dep)
+        return deps
 
     def getsupportedinterpreter(self):
         return self.envconfig.getsupportedinterpreter()
@@ -273,12 +273,12 @@ class VirtualEnv(object):
         self._install([sdistpath], extraopts=extraopts, action=action)
 
     def _installopts(self, indexserver):
-        l = []
+        options = []
         if indexserver:
-            l += ["-i", indexserver]
+            options += ["-i", indexserver]
         if self.envconfig.pip_pre:
-            l.append("--pre")
-        return l
+            options.append("--pre")
+        return options
 
     def run_install_command(self, packages, action, options=()):
         argv = self.envconfig.install_command[:]
@@ -312,7 +312,7 @@ class VirtualEnv(object):
         if not deps:
             return
         d = {}
-        l = []
+        ixservers = []
         for dep in deps:
             if isinstance(dep, (str, py.path.local)):
                 dep = DepConfig(str(dep), None)
@@ -322,11 +322,11 @@ class VirtualEnv(object):
             else:
                 ixserver = dep.indexserver
             d.setdefault(ixserver, []).append(dep.name)
-            if ixserver not in l:
-                l.append(ixserver)
+            if ixserver not in ixservers:
+                ixservers.append(ixserver)
             assert ixserver.url is None or isinstance(ixserver.url, str)
 
-        for ixserver in l:
+        for ixserver in ixservers:
             packages = d[ixserver]
             options = self._installopts(ixserver.url)
             if extraopts:


Test patch:
diff --git a/tests/test_config.py b/tests/test_config.py
index d83114f7..30da4f04 100644
--- a/tests/test_config.py
+++ b/tests/test_config.py
@@ -118,6 +118,23 @@ class TestVenvConfig:
             'dep1==1.5', 'https://pypi.python.org/xyz/pkg1.tar.gz'
         ]
 
+    def test_process_deps(self, newconfig):
+        config = newconfig([], """
+            [testenv]
+            deps =
+                -r requirements.txt
+                --index-url https://pypi.org/simple
+                -fhttps://pypi.org/packages
+                --global-option=foo
+                -v dep1
+                --help dep2
+        """)  # note that those last two are invalid
+        assert [str(x) for x in config.envconfigs['python'].deps] == [
+            '-rrequirements.txt', '--index-url=https://pypi.org/simple',
+            '-fhttps://pypi.org/packages', '--global-option=foo',
+            '-v dep1', '--help dep2'
+        ]
+
     def test_is_same_dep(self):
         """
         Ensure correct parseini._is_same_dep is working with a few samples.
@@ -160,7 +177,7 @@ class TestConfigPlatform:
         monkeypatch.setattr(sys, "platform", "win32")
         config = newconfig([], """
             [tox]
-            envlist = py27-{win,lin,osx}
+            envlist = py27-{win, lin,osx }
             [testenv]
             platform =
                 win: win32
diff --git a/tests/test_venv.py b/tests/test_venv.py
index 527da88d..1eae6e4d 100644
--- a/tests/test_venv.py
+++ b/tests/test_venv.py
@@ -16,8 +16,7 @@ from tox.venv import VirtualEnv
 
 # def test_global_virtualenv(capfd):
 #    v = VirtualEnv()
-#    l = v.list()
-#    assert l
+#    assert v.list()
 #    out, err = capfd.readouterr()
 #    assert not out
 #    assert not err
@@ -62,9 +61,9 @@ def test_create(monkeypatch, mocksession, newconfig):
     assert not venv.path.check()
     action = mocksession.newaction(venv, "getenv")
     tox_testenv_create(action=action, venv=venv)
-    l = mocksession._pcalls
-    assert len(l) >= 1
-    args = l[0].args
+    pcalls = mocksession._pcalls
+    assert len(pcalls) >= 1
+    args = pcalls[0].args
     assert "virtualenv" == str(args[2])
     if sys.platform != "win32":
         # realpath is needed for stuff like the debian symlinks
@@ -103,9 +102,9 @@ def test_create_sitepackages(monkeypatch, mocksession, newconfig):
     venv = VirtualEnv(envconfig, session=mocksession)
     action = mocksession.newaction(venv, "getenv")
     tox_testenv_create(action=action, venv=venv)
-    l = mocksession._pcalls
-    assert len(l) >= 1
-    args = l[0].args
+    pcalls = mocksession._pcalls
+    assert len(pcalls) >= 1
+    args = pcalls[0].args
     assert "--system-site-packages" in map(str, args)
     mocksession._clearmocks()
 
@@ -113,9 +112,9 @@ def test_create_sitepackages(monkeypatch, mocksession, newconfig):
     venv = VirtualEnv(envconfig, session=mocksession)
     action = mocksession.newaction(venv, "getenv")
     tox_testenv_create(action=action, venv=venv)
-    l = mocksession._pcalls
-    assert len(l) >= 1
-    args = l[0].args
+    pcalls = mocksession._pcalls
+    assert len(pcalls) >= 1
+    args = pcalls[0].args
     assert "--system-site-packages" not in map(str, args)
     assert "--no-site-packages" not in map(str, args)
 
@@ -131,16 +130,16 @@ def test_install_deps_wildcard(newmocksession):
     venv = mocksession.getenv("py123")
     action = mocksession.newaction(venv, "getenv")
     tox_testenv_create(action=action, venv=venv)
-    l = mocksession._pcalls
-    assert len(l) == 1
+    pcalls = mocksession._pcalls
+    assert len(pcalls) == 1
     distshare = venv.session.config.distshare
     distshare.ensure("dep1-1.0.zip")
     distshare.ensure("dep1-1.1.zip")
 
     tox_testenv_install_deps(action=action, venv=venv)
-    assert len(l) == 2
-    args = l[-1].args
-    assert l[-1].cwd == venv.envconfig.config.toxinidir
+    assert len(pcalls) == 2
+    args = pcalls[-1].args
+    assert pcalls[-1].cwd == venv.envconfig.config.toxinidir
     assert "pip" in str(args[0])
     assert args[1] == "install"
     args = [arg for arg in args if str(arg).endswith("dep1-1.1.zip")]
@@ -162,21 +161,21 @@ def test_install_deps_indexserver(newmocksession):
     venv = mocksession.getenv('py123')
     action = mocksession.newaction(venv, "getenv")
     tox_testenv_create(action=action, venv=venv)
-    l = mocksession._pcalls
-    assert len(l) == 1
-    l[:] = []
+    pcalls = mocksession._pcalls
+    assert len(pcalls) == 1
+    pcalls[:] = []
 
     tox_testenv_install_deps(action=action, venv=venv)
     # two different index servers, two calls
-    assert len(l) == 3
-    args = " ".join(l[0].args)
+    assert len(pcalls) == 3
+    args = " ".join(pcalls[0].args)
     assert "-i " not in args
     assert "dep1" in args
 
-    args = " ".join(l[1].args)
+    args = " ".join(pcalls[1].args)
     assert "-i ABC" in args
     assert "dep2" in args
-    args = " ".join(l[2].args)
+    args = " ".join(pcalls[2].args)
     assert "-i ABC" in args
     assert "dep3" in args
 
@@ -191,13 +190,13 @@ def test_install_deps_pre(newmocksession):
     venv = mocksession.getenv('python')
     action = mocksession.newaction(venv, "getenv")
     tox_testenv_create(action=action, venv=venv)
-    l = mocksession._pcalls
-    assert len(l) == 1
-    l[:] = []
+    pcalls = mocksession._pcalls
+    assert len(pcalls) == 1
+    pcalls[:] = []
 
     tox_testenv_install_deps(action=action, venv=venv)
-    assert len(l) == 1
-    args = " ".join(l[0].args)
+    assert len(pcalls) == 1
+    args = " ".join(pcalls[0].args)
     assert "--pre " in args
     assert "dep1" in args
 
@@ -209,12 +208,12 @@ def test_installpkg_indexserver(newmocksession, tmpdir):
             default = ABC
     """)
     venv = mocksession.getenv('python')
-    l = mocksession._pcalls
+    pcalls = 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 len(pcalls) == 1
+    args = " ".join(pcalls[0].args)
     assert "-i ABC" in args
 
 
@@ -243,12 +242,12 @@ def test_install_sdist_extras(newmocksession):
     venv = mocksession.getenv('python')
     action = mocksession.newaction(venv, "getenv")
     tox_testenv_create(action=action, venv=venv)
-    l = mocksession._pcalls
-    assert len(l) == 1
-    l[:] = []
+    pcalls = mocksession._pcalls
+    assert len(pcalls) == 1
+    pcalls[:] = []
 
     venv.installpkg('distfile.tar.gz', action=action)
-    assert 'distfile.tar.gz[testing,development]' in l[-1].args
+    assert 'distfile.tar.gz[testing,development]' in pcalls[-1].args
 
 
 def test_develop_extras(newmocksession, tmpdir):
@@ -260,13 +259,13 @@ def test_develop_extras(newmocksession, tmpdir):
     venv = mocksession.getenv('python')
     action = mocksession.newaction(venv, "getenv")
     tox_testenv_create(action=action, venv=venv)
-    l = mocksession._pcalls
-    assert len(l) == 1
-    l[:] = []
+    pcalls = mocksession._pcalls
+    assert len(pcalls) == 1
+    pcalls[:] = []
 
     venv.developpkg(tmpdir, action=action)
     expected = "%s[testing,development]" % tmpdir.strpath
-    assert expected in l[-1].args
+    assert expected in pcalls[-1].args
 
 
 def test_env_variables_added_to_needs_reinstall(tmpdir, mocksession, newconfig, monkeypatch):
@@ -285,9 +284,9 @@ def test_env_variables_added_to_needs_reinstall(tmpdir, mocksession, newconfig,
 
     venv._needs_reinstall(tmpdir, action)
 
-    l = mocksession._pcalls
-    assert len(l) == 2
-    env = l[0].env
+    pcalls = mocksession._pcalls
+    assert len(pcalls) == 2
+    env = pcalls[0].env
 
     # should have access to setenv vars
     assert 'CUSTOM_VAR' in env
@@ -397,15 +396,15 @@ def test_install_python3(tmpdir, newmocksession):
     venv = mocksession.getenv('py123')
     action = mocksession.newaction(venv, "getenv")
     tox_testenv_create(action=action, venv=venv)
-    l = mocksession._pcalls
-    assert len(l) == 1
-    args = l[0].args
+    pcalls = mocksession._pcalls
+    assert len(pcalls) == 1
+    args = pcalls[0].args
     assert str(args[2]) == 'virtualenv'
-    l[:] = []
+    pcalls[:] = []
     action = mocksession.newaction(venv, "hello")
     venv._install(["hello"], action=action)
-    assert len(l) == 1
-    args = l[0].args
+    assert len(pcalls) == 1
+    args = pcalls[0].args
     assert "pip" in args[0]
     for _ in args:
         assert "--download-cache" not in args, args
@@ -543,19 +542,19 @@ class TestVenvTest:
         venv = mocksession.getenv("python")
         action = mocksession.newaction(venv, "getenv")
         monkeypatch.setenv("PATH", "xyz")
-        l = []
+        sysfind_calls = []
         monkeypatch.setattr("py.path.local.sysfind", classmethod(
-                            lambda *args, **kwargs: l.append(kwargs) or 0 / 0))
+                            lambda *args, **kwargs: sysfind_calls.append(kwargs) or 0 / 0))
 
         with pytest.raises(ZeroDivisionError):
             venv._install(list('123'), action=action)
-        assert l.pop()["paths"] == [venv.envconfig.envbindir]
+        assert sysfind_calls.pop()["paths"] == [venv.envconfig.envbindir]
         with pytest.raises(ZeroDivisionError):
             venv.test(action)
-        assert l.pop()["paths"] == [venv.envconfig.envbindir]
+        assert sysfind_calls.pop()["paths"] == [venv.envconfig.envbindir]
         with pytest.raises(ZeroDivisionError):
             venv.run_install_command(['qwe'], action=action)
-        assert l.pop()["paths"] == [venv.envconfig.envbindir]
+        assert sysfind_calls.pop()["paths"] == [venv.envconfig.envbindir]
         monkeypatch.setenv("PIP_RESPECT_VIRTUALENV", "1")
         monkeypatch.setenv("PIP_REQUIRE_VIRTUALENV", "1")
         monkeypatch.setenv("__PYVENV_LAUNCHER__", "1")
@@ -576,9 +575,9 @@ class TestVenvTest:
         assert 'PYTHONPATH' not in os.environ
         mocksession.report.expect("warning", "*Discarding $PYTHONPATH from environment*")
 
-        l = mocksession._pcalls
-        assert len(l) == 1
-        assert 'PYTHONPATH' not in l[0].env
+        pcalls = mocksession._pcalls
+        assert len(pcalls) == 1
+        assert 'PYTHONPATH' not in pcalls[0].env
 
         # passenv = PYTHONPATH allows PYTHONPATH to stay in environment
         monkeypatch.setenv("PYTHONPATH", "/my/awesome/library")
@@ -593,9 +592,9 @@ class TestVenvTest:
         assert 'PYTHONPATH' in os.environ
         mocksession.report.not_expect("warning", "*Discarding $PYTHONPATH from environment*")
 
-        l = mocksession._pcalls
-        assert len(l) == 2
-        assert l[1].env['PYTHONPATH'] == '/my/awesome/library'
+        pcalls = mocksession._pcalls
+        assert len(pcalls) == 2
+        assert pcalls[1].env['PYTHONPATH'] == '/my/awesome/library'
 
 
 # FIXME this test fails when run in isolation - find what this depends on
@@ -618,9 +617,9 @@ def test_env_variables_added_to_pcall(tmpdir, mocksession, newconfig, monkeypatc
     mocksession.installpkg(venv, pkg)
     venv.test()
 
-    l = mocksession._pcalls
-    assert len(l) == 2
-    for x in l:
+    pcalls = mocksession._pcalls
+    assert len(pcalls) == 2
+    for x in pcalls:
         env = x.env
         assert env is not None
         assert 'ENV_VAR' in env
@@ -630,11 +629,11 @@ def test_env_variables_added_to_pcall(tmpdir, mocksession, newconfig, monkeypatc
         assert 'PYTHONPATH' in env
         assert env['PYTHONPATH'] == 'value'
     # all env variables are passed for installation
-    assert l[0].env["YY"] == "456"
-    assert "YY" not in l[1].env
+    assert pcalls[0].env["YY"] == "456"
+    assert "YY" not in pcalls[1].env
 
     assert set(["ENV_VAR", "VIRTUAL_ENV", "PYTHONHASHSEED", "X123", "PATH"])\
-        .issubset(l[1].env)
+        .issubset(pcalls[1].env)
 
     # setenv does not trigger PYTHONPATH warnings
     mocksession.report.not_expect("warning", "*Discarding $PYTHONPATH from environment*")
@@ -650,9 +649,9 @@ def test_installpkg_no_upgrade(tmpdir, newmocksession):
     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
+    pcalls = mocksession._pcalls
+    assert len(pcalls) == 1
+    assert '-U' not in pcalls[0].args
 
 
 def test_installpkg_upgrade(newmocksession, tmpdir):
@@ -661,12 +660,12 @@ def test_installpkg_upgrade(newmocksession, tmpdir):
     venv = mocksession.getenv('python')
     assert not hasattr(venv, 'just_created')
     mocksession.installpkg(venv, pkg)
-    l = mocksession._pcalls
-    assert len(l) == 1
-    index = l[0].args.index(str(pkg))
+    pcalls = mocksession._pcalls
+    assert len(pcalls) == 1
+    index = pcalls[0].args.index(str(pkg))
     assert index >= 0
-    assert '-U' in l[0].args[:index]
-    assert '--no-deps' in l[0].args[:index]
+    assert '-U' in pcalls[0].args[:index]
+    assert '--no-deps' in pcalls[0].args[:index]
 
 
 def test_run_install_command(newmocksession):
@@ -676,11 +675,11 @@ def test_run_install_command(newmocksession):
     venv.envconfig.envdir.ensure(dir=1)
     action = mocksession.newaction(venv, "hello")
     venv.run_install_command(packages=["whatever"], action=action)
-    l = mocksession._pcalls
-    assert len(l) == 1
-    assert 'pip' in l[0].args[0]
-    assert 'install' in l[0].args
-    env = l[0].env
+    pcalls = mocksession._pcalls
+    assert len(pcalls) == 1
+    assert 'pip' in pcalls[0].args[0]
+    assert 'install' in pcalls[0].args
+    env = pcalls[0].env
     assert env is not None
 
 
@@ -694,10 +693,10 @@ def test_run_custom_install_command(newmocksession):
     venv.envconfig.envdir.ensure(dir=1)
     action = mocksession.newaction(venv, "hello")
     venv.run_install_command(packages=["whatever"], action=action)
-    l = mocksession._pcalls
-    assert len(l) == 1
-    assert 'easy_install' in l[0].args[0]
-    assert l[0].args[1:] == ['whatever']
+    pcalls = mocksession._pcalls
+    assert len(pcalls) == 1
+    assert 'easy_install' in pcalls[0].args[0]
+    assert pcalls[0].args[1:] == ['whatever']
 
 
 def test_command_relative_issue26(newmocksession, tmpdir, monkeypatch):
@@ -733,16 +732,16 @@ def test_ignore_outcome_failing_cmd(newmocksession):
 
 
 def test_tox_testenv_create(newmocksession):
-    l = []
+    log = []
 
     class Plugin:
         @hookimpl
         def tox_testenv_create(self, action, venv):
-            l.append(1)
+            log.append(1)
 
         @hookimpl
         def tox_testenv_install_deps(self, action, venv):
-            l.append(2)
+            log.append(2)
 
     mocksession = newmocksession([], """
         [testenv]
@@ -752,20 +751,20 @@ def test_tox_testenv_create(newmocksession):
 
     venv = mocksession.getenv('python')
     venv.update(action=mocksession.newaction(venv, "getenv"))
-    assert l == [1, 2]
+    assert log == [1, 2]
 
 
 def test_tox_testenv_pre_post(newmocksession):
-    l = []
+    log = []
 
     class Plugin:
         @hookimpl
         def tox_runtest_pre(self, venv):
-            l.append('started')
+            log.append('started')
 
         @hookimpl
         def tox_runtest_post(self, venv):
-            l.append('finished')
+            log.append('finished')
 
     mocksession = newmocksession([], """
         [testenv]
@@ -774,6 +773,6 @@ def test_tox_testenv_pre_post(newmocksession):
 
     venv = mocksession.getenv('python')
     venv.status = None
-    assert l == []
+    assert log == []
     mocksession.runtestenv(venv)
-    assert l == ['started', 'finished']
+    assert log == ['started', 'finished']
diff --git a/tests/test_z_cmdline.py b/tests/test_z_cmdline.py
index a856c2f4..91122456 100644
--- a/tests/test_z_cmdline.py
+++ b/tests/test_z_cmdline.py
@@ -1,3 +1,4 @@
+import os
 import platform
 
 import py
@@ -454,6 +455,21 @@ def test_sdist_fails(cmd, initproj):
     ])
 
 
+def test_no_setup_py_exits(cmd, initproj):
+    initproj("pkg123-0.7", filedefs={
+        'tox.ini': """
+            [testenv]
+            commands=python -c "2 + 2"
+        """
+    })
+    os.remove("setup.py")
+    result = cmd.run("tox", )
+    assert result.ret
+    result.stdout.fnmatch_lines([
+        "*ERROR*No setup.py file found*"
+    ])
+
+
 def test_package_install_fails(cmd, initproj):
     initproj("pkg123-0.7", filedefs={
         'tests': {'test_hello.py': "def test_hello(): pass"},
@@ -747,9 +763,9 @@ def test_separate_sdist_no_sdistfile(cmd, initproj):
     })
     result = cmd.run("tox", "--sdistonly")
     assert not result.ret
-    l = distshare.listdir()
-    assert len(l) == 1
-    sdistfile = l[0]
+    distshare_files = distshare.listdir()
+    assert len(distshare_files) == 1
+    sdistfile = distshare_files[0]
     assert 'pkg123-foo-0.7.zip' in str(sdistfile)
 
 
@@ -764,9 +780,9 @@ def test_separate_sdist(cmd, initproj):
     })
     result = cmd.run("tox", "--sdistonly")
     assert not result.ret
-    l = distshare.listdir()
-    assert len(l) == 1
-    sdistfile = l[0]
+    sdistfiles = distshare.listdir()
+    assert len(sdistfiles) == 1
+    sdistfile = sdistfiles[0]
     result = cmd.run("tox", "-v", "--notest")
     assert not result.ret
     result.stdout.fnmatch_lines([