Repository: tox-dev/tox
Pull request #841 (2018-06-08 19:57:08)

Ignore basepython for default factors (#477)
tox provides a number of default factors - py27, py34, py35 etc. - that
are tied to particular interpreter versions. It is possible to override
these through individual sections or the global [testenv] section. For
example, consider the following 'tox.ini' file:

	[tox]
	skipsdist = True
	minversion = 2.0
	distribute = False
	envlist = py35,py27,pep8,py34-test

	[testenv]
	basepython = python3
	install_command = pip install {opts} {packages}
	commands =
	python --version

	[testenv:py27]
	basepython = python2.7

Running any target except for 'py27' will result in the same interpreter
being used. On Fedora 28 with the 'python3-tox' package:

	$ tox -qq -e py27
	Python 2.7.15
	$ tox -qq -e py35
	Python 3.6.5
	$ tox -qq -e py34-test
	Python 3.6.5

This is broken by design. Overriding these makes no sense and is a
source of common misconfigurations, as noted in #477. The only sane
thing to do here is ignore the request and use the correct interpreter
so this is what's done.


codecov[bot]: # [Codecov](https://codecov.io/gh/tox-dev/tox/pull/841?src=pr&el=h1) Report
> Merging [#841](https://codecov.io/gh/tox-dev/tox/pull/841?src=pr&el=desc) into [master](https://codecov.io/gh/tox-dev/tox/commit/e8730fbfde6b3e3b7420a723487a1e96583fa6f5?src=pr&el=desc) will **not change** coverage.
> The diff coverage is `100%`.


```diff
@@          Coverage Diff          @@
##           master   #841   +/-   ##
=====================================
  Coverage      95%    95%           
=====================================
  Files          11     11           
  Lines        2317   2317           
=====================================
  Hits         2209   2209           
  Misses        108    108
```

gaborbernat: @stephenfin thank you very much for the configuration. I would prefer to raise an error for invalid configuration though; instead of ignoring it. @obestwalter agree?
stephenfin: > @stephenfin thank you very much for the configuration. I would prefer to raise an error for invalid configuration though; instead of ignoring it. @obestwalter agree?

I considered that but decided not to because I wanted to allow configurations like the following:

```
[tox]
envlist = py27,py35,py36,docs,style,coverage

[testenv]
basepython = python3
commands = pytest ...

[testenv:docs]
commands = sphinx-build ...

[testenv:coverage]
commands = coverage ...

[testenv:style]
commands = flake8 ...
```

e.g. I would be able to ensure everything *except* the Python-versioned environments would use Python 3, to assist in migrations to Python 3. This has come up repeatedly in OpenStack, as seen [here](https://review.openstack.org/#/c/571207/1/tox.ini) and [here](https://review.openstack.org/#/q/topic:python3-first), and simply overriding things allows us to keep our `tox.ini` smaller. I would have added a DEBUG-level log, but it seems you're not using any logging infrastructure so I can't.

Your call.
gaborbernat: @stephenfin I see your point; I say let's follow the zen of Python: explicit better than implicit. So instead of blindly ignoring things let's have a way to opt-in into such decisions. 

```
basepython_env_name_missmatch = raise | ignore | override
```

Where raise aborts with a config error; ignore has the current strategy, and override can be what you're proposing here. Let's leave ``ignore`` as default for now (we can change it when we do a major release), and we should also expose this option as a command line argument to facilitate with testing/debugging. Does that seem reasonable to you?
stephenfin: > @stephenfin I see your point; I say let's follow the zen of Python: explicit better than implicit. So instead of blindly ignoring things let's have a way to opt-in into such decisions.
> 
>     basepython_env_name_missmatch = raise | ignore | override
> 
> Where raise aborts with a config error; ignore has the current strategy, and override can be what you're proposing here. Let's leave ignore as default for now (we can change it when we do a major release), and we should also expose this option as a command line argument to facilitate with testing/debugging. Does that seem reasonable to you?

That sounds mostly OK but I'd like to propose a slight tweak.

    ignore_basepython_conflict = True | False (default)

Where `False` preserves the current behaviour in this version but raises a warning and `True` gives me the behaviour I described above. Once tox 4.0 is released, we could bump this warning to an exception. What do you think?
gaborbernat: Ok. Make the changes and we can roll with it.
stephenfin: @gaborbernat I have a bad habit of using GitHub like I use mailing lists and Gerrit (including force pushes) :smile: The answer to all your questions above is in the [commit messages](https://github.com/tox-dev/tox/pull/841/commits). Is this what you're looking for?
gaborbernat: Yeah, migrate to the warnings api and we're good.
stephenfin: > Yeah, migrate to the warnings api and we're good.

This is done now.

Patch:
diff --git a/.gitignore b/.gitignore
index e9377be4..056b7e12 100644
--- a/.gitignore
+++ b/.gitignore
@@ -14,6 +14,7 @@ dist
 doc/_build/
 tox.egg-info
 .tox
+.venv
 .cache
 .python-version
 
diff --git a/changelog/477.bugfix.rst b/changelog/477.bugfix.rst
new file mode 100644
index 00000000..39e12987
--- /dev/null
+++ b/changelog/477.bugfix.rst
@@ -0,0 +1,5 @@
+Add ``ignore_basepython_conflict``, which determines whether conflicting
+``basepython`` settings for environments containing default factors, such as
+``py27`` or ``django18-py35``, should be ignored or result in warnings. This
+was a common source of misconfiguration and is rarely, if ever, desirable from
+a user perspective - by @stephenfin
diff --git a/doc/conf.py b/doc/conf.py
index f6a96274..07924c37 100644
--- a/doc/conf.py
+++ b/doc/conf.py
@@ -47,7 +47,7 @@ intersphinx_mapping = {"https://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(
+    app.add_object_type(
         "confval",
         "confval",
         objname="configuration value",
diff --git a/doc/config.rst b/doc/config.rst
index 6e746267..e1986a7d 100644
--- a/doc/config.rst
+++ b/doc/config.rst
@@ -58,6 +58,18 @@ and will first lookup global tox settings in this section:
     * environment variable ``TOXENV``
     * ``tox.ini`` file's ``envlist``
 
+.. confval:: ignore_basepython_conflict=True|False(default)
+
+    .. versionadded:: 3.1.0
+
+    If ``True``, :confval:`basepython` settings that conflict with the Python
+    variant for a environments using default factors, such as ``py27`` or
+    ``py35``, will be ignored. This allows you to configure
+    :confval:`basepython` in the global testenv without affecting these
+    factors. If ``False``, the default, a warning will be emitted if a conflict
+    is identified. In a future version of tox, this warning will become an
+    error.
+
 
 Virtualenv test environment settings
 ------------------------------------
@@ -81,12 +93,19 @@ 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.
+    Name or path to a Python interpreter which will be used for creating
+    the virtual environment; if the environment name contains a :ref:`default
+    factor <factors>`, this value will be ignored. **default**: interpreter
+    used for tox invocation.
+
+    .. versionchanged:: 3.1
+
+       Environments that use a :ref:`default factor <factors>` now ignore this
+       value, defaulting to the interpreter defined for that factor.
 
 .. confval:: commands=ARGVLIST
 
-    the commands to be called for testing. Each command is defined
+    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 ...).
@@ -99,7 +118,7 @@ Complete list of settings that you can put into ``testenv*`` sections:
 
     .. versionadded:: 1.6
 
-    the ``install_command`` setting is used for installing packages into
+    The ``install_command`` setting is used for installing packages into
     the virtual environment; both the package under test
     and its dependencies (defined with :confval:`deps`).
     Must contain the substitution key
@@ -114,38 +133,36 @@ Complete list of settings that you can put into ``testenv*`` sections:
 
         pip install {opts} {packages}
 
-
 .. confval:: list_dependencies_command
 
     .. versionadded:: 2.4
 
-    the ``list_dependencies_command`` setting is used for listing
+    The ``list_dependencies_command`` setting is used for listing
     the packages installed into the virtual environment.
 
     **default**::
 
         pip freeze
 
-
 .. confval:: ignore_errors=True|False(default)
 
     .. versionadded:: 2.0
 
-      If ``True``, a non-zero exit code from one command will be ignored and
-      further commands will be executed (which was the default behavior in tox <
-      2.0).  If ``False`` (the default), then a non-zero exit code from one command
-      will abort execution of commands for that environment.
+    If ``True``, a non-zero exit code from one command will be ignored and
+    further commands will be executed (which was the default behavior in tox <
+    2.0).  If ``False`` (the default), then a non-zero exit code from one
+    command will abort execution of commands for that environment.
 
-      It may be helpful to note that this setting is analogous to the ``-i`` or
-      ``ignore-errors`` option of GNU Make. A similar name was chosen to reflect the
-      similarity in function.
+    It may be helpful to note that this setting is analogous to the ``-i`` or
+    ``ignore-errors`` option of GNU Make. A similar name was chosen to reflect
+    the similarity in function.
 
-      Note that in tox 2.0, the default behavior of tox with respect to
-      treating errors from commands changed. tox < 2.0 would ignore errors by
-      default. tox >= 2.0 will abort on an error by default, which is safer and more
-      typical of CI and command execution tools, as it doesn't make sense to
-      run tests if installing some prerequisite failed and it doesn't make sense to
-      try to deploy if tests failed.
+    Note that in tox 2.0, the default behavior of tox with respect to treating
+    errors from commands changed. tox < 2.0 would ignore errors by default. tox
+    >= 2.0 will abort on an error by default, which is safer and more typical
+    of CI and command execution tools, as it doesn't make sense to run tests if
+    installing some prerequisite failed and it doesn't make sense to try to
+    deploy if tests failed.
 
 .. confval:: pip_pre=True|False(default)
 
@@ -175,11 +192,12 @@ Complete list of settings that you can put into ``testenv*`` sections:
 .. 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
+    Test-specific dependencies - to be installed into the environment prior to project
     package installation.  Each line defines a dependency, which will be
     passed to the installer command for processing (see :confval:`indexserver`).
     Each line specifies a file, a URL or a package name.  You can additionally specify
@@ -205,43 +223,43 @@ Complete list of settings that you can put into ``testenv*`` sections:
 
 .. confval:: setenv=MULTI-LINE-LIST
 
-   .. versionadded:: 0.9
+    .. 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.
+    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:: passenv=SPACE-SEPARATED-GLOBNAMES
 
-   .. versionadded:: 2.0
+    .. versionadded:: 2.0
 
-   A list of wildcard environment variable names which
-   shall be copied from the tox invocation environment to the test
-   environment when executing test commands.  If a specified environment
-   variable doesn't exist in the tox invocation environment it is ignored.
-   You can use ``*`` and ``?`` to match multiple environment variables with
-   one name.
+    A list of wildcard environment variable names which
+    shall be copied from the tox invocation environment to the test
+    environment when executing test commands.  If a specified environment
+    variable doesn't exist in the tox invocation environment it is ignored.
+    You can use ``*`` and ``?`` to match multiple environment variables with
+    one name.
 
-   Some variables are always passed through to ensure the basic functionality
-   of standard library functions or tooling like pip:
+    Some variables are always passed through to ensure the basic functionality
+    of standard library functions or tooling like pip:
 
-   * passed through on all platforms: ``PATH``, ``LANG``, ``LANGUAGE``,
-     ``LD_LIBRARY_PATH``, ``PIP_INDEX_URL``
-   * Windows: ``SYSTEMDRIVE``, ``SYSTEMROOT``, ``PATHEXT``, ``TEMP``, ``TMP``
-      ``NUMBER_OF_PROCESSORS``, ``USERPROFILE``, ``MSYSTEM``
-   * Others (e.g. UNIX, macOS): ``TMPDIR``
+    * passed through on all platforms: ``PATH``, ``LANG``, ``LANGUAGE``,
+      ``LD_LIBRARY_PATH``, ``PIP_INDEX_URL``
+    * Windows: ``SYSTEMDRIVE``, ``SYSTEMROOT``, ``PATHEXT``, ``TEMP``, ``TMP``
+       ``NUMBER_OF_PROCESSORS``, ``USERPROFILE``, ``MSYSTEM``
+    * Others (e.g. UNIX, macOS): ``TMPDIR``
 
-   You can override these variables with the ``setenv`` option.
+    You can override these variables with the ``setenv`` option.
 
-   If defined the ``TOX_TESTENV_PASSENV`` environment variable (in the tox
-   invocation environment) can define additional space-separated variable
-   names that are to be passed down to the test command environment.
+    If defined the ``TOX_TESTENV_PASSENV`` environment variable (in the tox
+    invocation environment) can define additional space-separated variable
+    names that are to be passed down to the test command environment.
 
-   .. versionchanged:: 2.7
+    .. versionchanged:: 2.7
 
-   ``PYTHONPATH`` will be passed down if explicitly defined. If ``PYTHONPATH``
-   exists in the host environment but is **not** declared in ``passenv`` a
-   warning will be emitted.
+        ``PYTHONPATH`` will be passed down if explicitly defined. If
+        ``PYTHONPATH`` exists in the host environment but is **not** declared
+        in ``passenv`` a warning will be emitted.
 
 .. confval:: recreate=True|False(default)
 
@@ -249,7 +267,9 @@ Complete list of settings that you can put into ``testenv*`` sections:
 
 .. confval:: downloadcache=path
 
-    **IGNORED** -- Since pip-8 has caching by default this option is now ignored.  Please remove it from your configs as a future tox version might bark on it.
+    **IGNORED** -- Since pip-8 has caching by default this option is now
+    ignored.  Please remove it from your configs as a future tox version might
+    bark on it.
 
 .. confval:: sitepackages=True|False
 
@@ -261,60 +281,65 @@ Complete list of settings that you can put into ``testenv*`` sections:
 
 .. confval:: alwayscopy=True|False
 
-    Set to ``True`` if you want virtualenv to always copy files rather than symlinking.
+    Set to ``True`` if you want virtualenv to always copy files rather than
+    symlinking.
 
-    This is useful for situations where hardlinks don't work (e.g. running in VMS with Windows guests).
+    This is useful for situations where hardlinks don't work (e.g. running in
+    VMS with Windows guests).
 
     **default:** False, meaning that virtualenvs will make use of symbolic links.
 
 .. confval:: args_are_paths=BOOL
 
-    treat positional arguments passed to ``tox`` as file system paths
+    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
+    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
+    Defines a directory for logging where tox will put logs of tool
     invocation.
+
     **default**: ``{envdir}/log``
 
 .. confval:: indexserver
 
-   .. versionadded:: 0.9
+    .. versionadded:: 0.9
 
-   (DEPRECATED, will be removed in a future version) Multi-line ``name =
-   URL`` definitions of python package servers.  Dependencies 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:
+    (DEPRECATED, will be removed in a future version) Multi-line ``name =
+    URL`` definitions of python package servers.  Dependencies 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:
 
-   .. code-block:: ini
+    .. code-block:: ini
 
         [tox]
         indexserver =
             default = http://mypypi.org
 
-   will make tox install all dependencies from this PYPI index server
-   (including when installing the project sdist package).
-
+    will make tox install all dependencies from this PYPI index server
+    (including when installing the project sdist package).
 
 .. confval:: envdir
 
-   .. versionadded:: 1.5
+    .. 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}``
+    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
 
@@ -356,7 +381,7 @@ Complete list of settings that you can put into ``testenv*`` sections:
 
 .. confval:: description=SINGLE-LINE-TEXT
 
-    a short description of the environment, this will be used to explain
+    A short description of the environment, this will be used to explain
     the environment to the user upon listing environments for the command
     line with any level of verbosity higher than zero. **default**: empty string
 
@@ -540,9 +565,6 @@ However, a better approach looks like this:
     envlist = {py27,py36}-django{15,16}
 
     [testenv]
-    basepython =
-        py27: python2.7
-        py36: python3.6
     deps =
         pytest
         django15: Django>=1.5,<1.6
@@ -606,23 +628,12 @@ Factors and factor-conditional settings
 ++++++++++++++++++++++++++++++++++++++++
 
 Parts of an environment name delimited by hyphens are called factors and can
-be used to set values conditionally:
-
-.. code-block:: ini
-
-    basepython =
-        py27: python2.7
-        py36: python3.6
-
-This conditional setting will lead to either ``python3.6`` or
-``python2.7`` used as base python, e.g. ``python3.6`` is selected if current
-environment contains ``py36`` factor.
-
-In list settings such as ``deps`` or ``commands`` you can freely intermix
-optional lines with unconditional ones:
+be used to set values conditionally. In list settings such as ``deps`` or
+``commands`` you can freely intermix optional lines with unconditional ones:
 
 .. code-block:: ini
 
+    [testenv]
     deps =
         pytest
         django15: Django>=1.5,<1.6
@@ -632,16 +643,23 @@ optional lines with unconditional ones:
 Reading it line by line:
 
 - ``pytest`` will be included unconditionally,
-- ``Django>=1.5,<1.6`` will be included for environments containing ``django15`` factor,
+- ``Django>=1.5,<1.6`` will be included for environments containing
+  ``django15`` factor,
 - ``Django>=1.6,<1.7`` similarly depends on ``django16`` factor,
 - ``unittest`` will be loaded for Python 3.6 environments.
 
-.. note::
+tox provides a number of default factors corresponding to Python interpreter
+versions. The conditional setting above will lead to either ``python3.6`` or
+``python2.7`` used as base python, e.g. ``python3.6`` is selected if current
+environment contains ``py36`` factor.
 
-    tox provides good defaults for basepython setting, so the above
-    ini-file can be further reduced by omitting the ``basepython``
-    setting.
+.. note::
 
+    Configuring :confval:`basepython` for environments using default factors
+    will result in a warning. Configure :confval:`ignore_basepython_conflict`
+    if you wish to explicitly ignore these conflicts, allowing you to define a
+    global :confval:`basepython` for all environments *except* those with
+    default factors.
 
 Complex factor conditions
 +++++++++++++++++++++++++
diff --git a/doc/example/general.rst b/doc/example/general.rst
index 46b8cb77..7e9e7e24 100644
--- a/doc/example/general.rst
+++ b/doc/example/general.rst
@@ -49,7 +49,6 @@ a tox test run.  Here is an example ``tox.ini`` configuration:
 .. code-block:: ini
 
     [testenv:docs]
-    basepython = python
     changedir = doc
     deps = sphinx
     commands = sphinx-build -W -b html -d {envtmpdir}/doctrees . {envtmpdir}/html
@@ -142,26 +141,23 @@ If you want to use this with Jenkins_, also checkout the :ref:`jenkins artifact
 .. _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 any ``pyXY`` test environment name the underlying ``pythonX.Y`` executable
+will be searched in your system ``PATH``. Similarly, for ``jython`` and
+``pypy`` the respective ``jython`` and ``pypy-c`` names will be looked for.
+The executable 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:
+All other targets will use the system ``python`` instead. You can override any
+of the default settings by defining the :confval:`basepython` variable in a
+specific test environment section, for example:
 
 .. code-block:: ini
 
-    [testenv:py27]
-    basepython=/my/path/to/python2.7
+    [testenv:docs]
+    basepython = python2.7
 
 Avoiding expensive sdist
 ------------------------
diff --git a/src/tox/config.py b/src/tox/config.py
index 45b3558b..609a10bc 100755
--- a/src/tox/config.py
+++ b/src/tox/config.py
@@ -8,6 +8,7 @@ import re
 import shlex
 import string
 import sys
+import warnings
 from fnmatch import fnmatchcase
 from subprocess import list2cmdline
 
@@ -505,11 +506,31 @@ def tox_addoption(parser):
     )
 
     def basepython_default(testenv_config, value):
+        """Configure a sane interpreter for the environment.
+
+        If the environment contains a default factor, this will always be the
+        interpreter associated with that factor overriding anything manually
+        set.
+        """
+        for factor in testenv_config.factors:
+            if factor in tox.PYTHON.DEFAULT_FACTORS:
+                default = tox.PYTHON.DEFAULT_FACTORS[factor]
+
+                if value is None or testenv_config.config.ignore_basepython_conflict:
+                    return default
+
+                if str(value) != default:
+                    # TODO(stephenfin): Raise an exception here in tox 4.0
+                    warnings.warn(
+                        "Conflicting basepython for environment '{}'; resolve conflict "
+                        "or configure ignore_basepython_conflict".format(
+                            testenv_config.envname, str(value), default
+                        )
+                    )
+
         if value is None:
-            for factor in testenv_config.factors:
-                if factor in tox.PYTHON.DEFAULT_FACTORS:
-                    return tox.PYTHON.DEFAULT_FACTORS[factor]
             return sys.executable
+
         return str(value)
 
     parser.add_testenv_attribute(
@@ -893,6 +914,8 @@ class parseini:
                 "skip_missing_interpreters", False
             )
 
+        config.ignore_basepython_conflict = reader.getbool("ignore_basepython_conflict", False)
+
         # determine indexserver dictionary
         config.indexserver = {"default": IndexServerConfig("default")}
         prefix = "indexserver"
diff --git a/tox.ini b/tox.ini
index 35807d42..d60dded4 100644
--- a/tox.ini
+++ b/tox.ini
@@ -26,8 +26,7 @@ whitelist_externals = sphinx-build
 basepython = python3.6
 extras = docs
 changedir = {toxinidir}
-commands = sphinx-build -d "{toxworkdir}/docs_doctree" doc "{toxworkdir}/docs_out" --color -W -bhtml
-
+commands = sphinx-build -d "{toxworkdir}/docs_doctree" doc "{toxworkdir}/docs_out" --color -W -bhtml {posargs}
 
 [testenv:fix-lint]
 description = format the code base to adhere to our styles, and complain about what we cannot do automatically


Test patch:
diff --git a/tests/test_config.py b/tests/test_config.py
index 93778848..a66845ab 100644
--- a/tests/test_config.py
+++ b/tests/test_config.py
@@ -1640,6 +1640,37 @@ class TestConfigTestEnv:
         for name, config in configs.items():
             assert config.basepython == "python{}.{}".format(name[2], name[3])
 
+    def test_default_factors_conflict(self, newconfig, capsys):
+        with pytest.warns(UserWarning, match=r"Conflicting basepython .*"):
+            config = newconfig(
+                """
+                [testenv]
+                basepython=python3
+                [testenv:py27]
+                commands = python --version
+            """
+            )
+        assert len(config.envconfigs) == 1
+        envconfig = config.envconfigs["py27"]
+        assert envconfig.basepython == "python3"
+
+    def test_default_factors_conflict_ignore(self, newconfig, capsys):
+        with pytest.warns(None) as record:
+            config = newconfig(
+                """
+                [tox]
+                ignore_basepython_conflict=True
+                [testenv]
+                basepython=python3
+                [testenv:py27]
+                commands = python --version
+            """
+            )
+        assert len(config.envconfigs) == 1
+        envconfig = config.envconfigs["py27"]
+        assert envconfig.basepython == "python2.7"
+        assert len(record) == 0
+
     @pytest.mark.issue188
     def test_factors_in_boolean(self, newconfig):
         inisource = """
diff --git a/tests/test_z_cmdline.py b/tests/test_z_cmdline.py
index 8a96edfe..4cc58a2f 100644
--- a/tests/test_z_cmdline.py
+++ b/tests/test_z_cmdline.py
@@ -796,13 +796,9 @@ def test_test_piphelp(initproj, cmd):
         "example123",
         filedefs={
             "tox.ini": """
-        # content of: tox.ini
-        [testenv]
-        commands=pip -h
-        [testenv:py27]
-        basepython=python
-        [testenv:py36]
-        basepython=python
+            # content of: tox.ini
+            [testenv]
+            commands=pip -h
     """
         },
     )