Repository: tox-dev/tox
Pull request #705 (2017-12-09 17:55:42)

code cleanup
Generic code cleanup extracted so it does not pollute other pull requests.

gaborbernat: Looks good 👌
codecov[bot]: # [Codecov](https://codecov.io/gh/tox-dev/tox/pull/705?src=pr&el=h1) Report
> Merging [#705](https://codecov.io/gh/tox-dev/tox/pull/705?src=pr&el=desc) into [master](https://codecov.io/gh/tox-dev/tox/commit/060758d8972a10687f0869ac9ced4c7eabaa904d?src=pr&el=desc) will **not change** coverage.
> The diff coverage is `n/a`.

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

```diff
@@           Coverage Diff           @@
##           master     #705   +/-   ##
=======================================
  Coverage   93.95%   93.95%           
=======================================
  Files          11       11           
  Lines        2416     2416           
=======================================
  Hits         2270     2270           
  Misses        146      146
```



------

[Continue to review full report at Codecov](https://codecov.io/gh/tox-dev/tox/pull/705?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/705?src=pr&el=footer). Last update [060758d...7856281](https://codecov.io/gh/tox-dev/tox/pull/705?src=pr&el=lastupdated). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments).

jurko-gospodnetic: @gaborbernat - change done

Patch:
diff --git a/changelog/708.trivial.rst b/changelog/708.trivial.rst
new file mode 100644
index 00000000..3af7ea41
--- /dev/null
+++ b/changelog/708.trivial.rst
@@ -0,0 +1,1 @@
+touched up ``interpreters.py`` code and added some missing tests for it - by @jurko-gospodnetic
diff --git a/tox.ini b/tox.ini
index 258f5c93..4c9ac636 100644
--- a/tox.ini
+++ b/tox.ini
@@ -32,7 +32,7 @@ extras = lint
 description = run static analysis and style check using flake8
 commands = python -m flake8 --show-source tox setup.py {posargs}
            python -m flake8 --show-source doc tests {posargs}
-           pre-commit run --all-files
+           pre-commit run --all-files --show-diff-on-failure
            python -c 'print("hint: run {envdir}/bin/pre-commit install to add checks as pre-commit hook")'
 
 
diff --git a/tox/interpreters.py b/tox/interpreters.py
index 7eedb15a..49ea431c 100644
--- a/tox/interpreters.py
+++ b/tox/interpreters.py
@@ -48,9 +48,8 @@ class Interpreters:
             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))
+        except ExecFailed as e:
+            print("execution failed: %s -- %s" % (e.out, e.err))
             return ""
         else:
             return res["dir"]
@@ -59,12 +58,11 @@ class Interpreters:
 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)
+        result = exec_on_interpreter(executable, [inspect.getsource(pyinfo),
+                                                  "print(pyinfo())"])
+    except ExecFailed as e:
+        return NoInterpreterInfo(name, executable=e.executable,
+                                 out=e.out, err=e.err)
     else:
         return InterpreterInfo(name, executable, **result)
 
@@ -123,7 +121,7 @@ class NoInterpreterInfo:
 
     def __str__(self):
         if self.executable:
-            return "<executable at %s, not runnable>"
+            return "<executable at %s, not runnable>" % self.executable
         else:
             return "<executable not found for: %s>" % self.name
 
@@ -184,5 +182,5 @@ def pyinfo():
 
 
 def sitepackagesdir(envdir):
-    from distutils.sysconfig import get_python_lib
-    return dict(dir=get_python_lib(prefix=envdir))
+    import distutils.sysconfig
+    return dict(dir=distutils.sysconfig.get_python_lib(prefix=envdir))


Test patch:
diff --git a/tests/test_config.py b/tests/test_config.py
index 9d769d69..7f283924 100644
--- a/tests/test_config.py
+++ b/tests/test_config.py
@@ -95,7 +95,7 @@ class TestVenvConfig:
         assert config.option.force_dep == [
             'dep1==1.5', 'dep2==2.1', 'dep3==3.0']
         assert [str(x) for x in config.envconfigs['python'].deps] == [
-            'dep1==1.5', 'dep2==2.1', 'dep3==3.0', 'dep4==4.0',
+            'dep1==1.5', 'dep2==2.1', 'dep3==3.0', 'dep4==4.0'
         ]
 
     def test_force_dep_with_url(self, initproj):
@@ -313,7 +313,7 @@ class TestIniParserAgainstCommandsKey:
             "cmd2 param21 param22".split(),
             "cmd1 param11 param12".split(),
             "cmd2 param21 param22".split(),
-            ["echo", "cmd", "1", "2", "3", "4", "cmd", "2"],
+            ["echo", "cmd", "1", "2", "3", "4", "cmd", "2"]
         ]
 
     def test_command_substitution_from_other_section_posargs(self, newconfig):
@@ -1137,7 +1137,7 @@ class TestConfigTestEnv:
         """)
         conf = config.envconfigs['standard-greeting']
         assert conf.commands == [
-            ['python', '-c', 'print("Hello, world!")'],
+            ['python', '-c', 'print("Hello, world!")']
         ]
 
     def test_substitution_nested_env_defaults(tmpdir, newconfig, monkeypatch):
@@ -1268,7 +1268,7 @@ class TestConfigTestEnv:
 
     @pytest.mark.parametrize('envlist, deps', [
         (['py27'], ('pytest', 'pytest-cov')),
-        (['py27', 'py34'], ('pytest', 'py{27,34}: pytest-cov')),
+        (['py27', 'py34'], ('pytest', 'py{27,34}: pytest-cov'))
     ])
     def test_take_dependencies_from_other_testenv(
             self,
@@ -2078,7 +2078,7 @@ class TestCmdInvocation:
         result = cmd.run("tox", "-h")
         assert not result.ret
         result.stdout.fnmatch_lines([
-            "*help*",
+            "*help*"
         ])
 
     def test_version_simple(self, cmd):
@@ -2151,7 +2151,7 @@ class TestCmdInvocation:
 
             [testenv:docs]
             changedir = docs
-            ''',
+            '''
         })
         result = cmd.run("tox", "-l")
         result.stdout.fnmatch_lines("""
@@ -2181,7 +2181,7 @@ class TestCmdInvocation:
             [testenv:docs]
             changedir = docs
             description = let me overwrite that
-            ''',
+            '''
         })
         result = cmd.run("tox", "-lv")
         result.stdout.fnmatch_lines("""
@@ -2204,7 +2204,7 @@ class TestCmdInvocation:
 
             [testenv:docs]
             changedir = docs
-            ''',
+            '''
         })
         result = cmd.run("tox", "-a")
         result.stdout.fnmatch_lines("""
@@ -2231,7 +2231,7 @@ class TestCmdInvocation:
 
             [testenv:docs]
             changedir = docs
-            ''',
+            '''
         })
         result = cmd.run("tox", "-av")
         result.stdout.fnmatch_lines("""
@@ -2250,7 +2250,7 @@ class TestCmdInvocation:
             'tox.ini': '''
             [tox]
             envlist=py27,py36
-            ''',
+            '''
         })
         result = cmd.run("tox", "-av")
         result.stdout.fnmatch_lines("""
@@ -2265,7 +2265,7 @@ class TestCmdInvocation:
         result = cmd.run("tox", "-c", ini, "--showconfig")
         assert not result.ret
         result.stdout.fnmatch_lines([
-            "*config-file*hello.ini*",
+            "*config-file*hello.ini*"
         ])
 
     def test_no_tox_ini(self, cmd, initproj):
@@ -2273,7 +2273,7 @@ class TestCmdInvocation:
         result = cmd.run("tox")
         assert result.ret
         result.stderr.fnmatch_lines([
-            "*ERROR*tox.ini*not*found*",
+            "*ERROR*tox.ini*not*found*"
         ])
 
     def test_override_workdir(self, tmpdir, cmd, initproj):
@@ -2283,7 +2283,7 @@ class TestCmdInvocation:
             'tox.ini': '''
             [tox]
             toxworkdir=%s
-            ''' % baddir,
+            ''' % baddir
         })
         result = cmd.run("tox", "--workdir", gooddir, "--showconfig")
         assert not result.ret
@@ -2302,19 +2302,19 @@ class TestCmdInvocation:
             deps=
                 dep1==2.3
                 dep2
-            ''',
+            '''
         })
         result = cmd.run("tox", "--showconfig")
         assert result.ret == 0
         result.stdout.fnmatch_lines([
-            r'*deps*dep1==2.3, dep2*',
+            r'*deps*dep1==2.3, dep2*'
         ])
         # override dep1 specific version, and force version for dep2
         result = cmd.run("tox", "--showconfig", "--force-dep=dep1",
                          "--force-dep=dep2==5.0")
         assert result.ret == 0
         result.stdout.fnmatch_lines([
-            r'*deps*dep1, dep2==5.0*',
+            r'*deps*dep1, dep2==5.0*'
         ])
 
     @pytest.mark.xfail(
@@ -2328,7 +2328,7 @@ class TestCmdInvocation:
 
             [testenv]
             commands = pip --version
-            ''',
+            '''
         })
         result = cmd.run("tox")
         assert result.ret == 0
@@ -2374,7 +2374,7 @@ class TestCommandParser:
             '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}}',
+            ' ', 'w[]ord', ' ', '{posargs:{a key}}'
         ]
 
         assert parsed == expected
diff --git a/tests/test_interpreters.py b/tests/test_interpreters.py
index 4ad75d67..fcc5a7a1 100644
--- a/tests/test_interpreters.py
+++ b/tests/test_interpreters.py
@@ -7,8 +7,13 @@ import py
 import pytest
 
 from tox.config import get_plugin_manager
+from tox.interpreters import ExecFailed
+from tox.interpreters import InterpreterInfo
 from tox.interpreters import Interpreters
+from tox.interpreters import NoInterpreterInfo
+from tox.interpreters import pyinfo
 from tox.interpreters import run_and_get_interpreter_info
+from tox.interpreters import sitepackagesdir
 from tox.interpreters import tox_get_python_executable
 
 
@@ -70,7 +75,8 @@ def test_tox_get_python_executable():
         envconfig.basepython = name
         p = tox_get_python_executable(envconfig)
         assert p
-        popen = subprocess.Popen([str(p), '-V'], stderr=subprocess.PIPE, stdout=subprocess.PIPE)
+        popen = subprocess.Popen([str(p), '-V'], stderr=subprocess.PIPE,
+                                 stdout=subprocess.PIPE)
         stdout, stderr = popen.communicate()
         assert not stdout or not stderr
         assert ver in stderr.decode('ascii') or ver in stdout.decode('ascii')
@@ -130,3 +136,92 @@ class TestInterpreters:
         info = interpreters.get_info(envconfig)
         s = interpreters.get_sitepackagesdir(info, "")
         assert s
+
+
+def test_pyinfo(monkeypatch):
+    monkeypatch.setattr(sys, "version_info", [42, 4242])
+    monkeypatch.setattr(sys, "platform", "an unladen swallow")
+    result = pyinfo()
+    assert len(result) == 2
+    assert result["version_info"] == (42, 4242)
+    assert result["sysplatform"] == "an unladen swallow"
+
+
+def test_sitepackagesdir(monkeypatch):
+    import distutils.sysconfig as sysconfig
+    test_envdir = "holy grail"
+    test_dir = "Now go away or I will taunt you a second time."
+
+    def dummy_get_python_lib(prefix):
+        assert prefix is test_envdir
+        return test_dir
+
+    monkeypatch.setattr(sysconfig, "get_python_lib", dummy_get_python_lib)
+    assert sitepackagesdir(test_envdir) == {"dir": test_dir}
+
+
+def test_exec_failed():
+    x = ExecFailed("my-executable", "my-source", "my-out", "my-err")
+    assert isinstance(x, Exception)
+    assert x.executable == "my-executable"
+    assert x.source == "my-source"
+    assert x.out == "my-out"
+    assert x.err == "my-err"
+
+
+class TestInterpreterInfo:
+
+    def info(self, name="my-name", executable="my-executable",
+             version_info="my-version-info", sysplatform="my-sys-platform"):
+        return InterpreterInfo(name, executable, version_info, sysplatform)
+
+    def test_runnable(self):
+        assert self.info().runnable
+
+    @pytest.mark.parametrize("missing_arg", ("executable", "version_info"))
+    def test_assert_on_missing_args(self, missing_arg):
+        with pytest.raises(AssertionError):
+            self.info(**{missing_arg: None})
+
+    def test_data(self):
+        x = self.info("larry", "moe", "shemp", "curly")
+        assert x.name == "larry"
+        assert x.executable == "moe"
+        assert x.version_info == "shemp"
+        assert x.sysplatform == "curly"
+
+    def test_str(self):
+        x = self.info(executable="foo", version_info="bar")
+        assert str(x) == "<executable at foo, version_info bar>"
+
+
+class TestNoInterpreterInfo:
+
+    def test_runnable(self):
+        assert not NoInterpreterInfo("foo").runnable
+        assert not NoInterpreterInfo("foo", executable=sys.executable).runnable
+
+    def test_default_data(self):
+        x = NoInterpreterInfo("foo")
+        assert x.name == "foo"
+        assert x.executable is None
+        assert x.version_info is None
+        assert x.out is None
+        assert x.err == "not found"
+
+    def test_set_data(self):
+        x = NoInterpreterInfo("migraine", executable="my-executable",
+                              out="my-out", err="my-err")
+        assert x.name == "migraine"
+        assert x.executable == "my-executable"
+        assert x.version_info is None
+        assert x.out == "my-out"
+        assert x.err == "my-err"
+
+    def test_str_without_executable(self):
+        x = NoInterpreterInfo("coconut")
+        assert str(x) == "<executable not found for: coconut>"
+
+    def test_str_with_executable(self):
+        x = NoInterpreterInfo("coconut", executable="bang/em/together")
+        assert str(x) == "<executable at bang/em/together, not runnable>"
diff --git a/tox/_pytestplugin.py b/tox/_pytestplugin.py
index 7c7020fd..e63acc75 100644
--- a/tox/_pytestplugin.py
+++ b/tox/_pytestplugin.py
@@ -291,7 +291,30 @@ class LineMatcher:
 
 @pytest.fixture
 def initproj(request, tmpdir):
-    """ create a factory function for creating example projects. """
+    """Create a factory function for creating example projects
+
+    Constructed folder/file hierarchy examples:
+
+    with `src_root` other than `.`:
+
+      tmpdir/
+          name/                  # base
+            src_root/            # src_root
+                name/            # package_dir
+                    __init__.py
+                name.egg-info/   # created later on package build
+            setup.py
+
+    with `src_root` given as `.`:
+
+      tmpdir/
+          name/                  # base, src_root
+            name/                # package_dir
+                __init__.py
+            name.egg-info/       # created later on package build
+            setup.py
+
+    """
     def initproj(nameversion, filedefs=None, src_root="."):
         if filedefs is None:
             filedefs = {}