Repository: tox-dev/tox

Issue:
tox uses wrong virtualenv PATH under MSYS
tox assumes that all Windows installations use "{ENV}\Scripts" as the installation folder. This isn't true for MSYS, which uses "{ENV}\bin".  If I patch [`TestenvConfig.get_envbindir()`](https://github.com/tox-dev/tox/blob/5e9a6977da9e4e39488c7262ccb66dcddfafb3ab/src/tox/config/__init__.py#L1050) to return "bin" instead of "Scripts", tox uses the correct interpreter.

I believe an exception is needed if `MSYSTEM` is set in the environment (or I don't know if there's a more "proper" way to detect MSYS), similar to the exception that exists in that function for PyPy.

```
$ tox -rvv -e py38
using tox.ini: C:/Users/User/source/repos/fuzzfetch/tox.ini (pid 6820)
  removing C:/Users/User/source/repos/fuzzfetch/.tox/log
using tox-3.23.0 from C:/Users/User/Desktop/msys64-JHtqq6ULSEu38CxK8FxfLQ/mingw64/lib/python3.8/site-packages/tox/__init__.py (pid 6820)
skipping sdist step
py38 uses C:/Users/User/Desktop/msys64-JHtqq6ULSEu38CxK8FxfLQ/mingw64/bin/python.exe
py38 start: getenv C:/Users/User/source/repos/fuzzfetch/.tox/py38
py38 cannot reuse: -r flag
py38 recreate: C:/Users/User/source/repos/fuzzfetch/.tox/py38
  removing C:/Users/User/source/repos/fuzzfetch/.tox/py38
setting PATH=C:/Users/User/source/repos/fuzzfetch/.tox/py38/Scripts;C:\Users\User\Desktop\msys64-JHtqq6ULSEu38CxK8FxfLQ\mingw64\bin;C:\Users\User\Desktop\msys64-JHtqq6ULSEu38CxK8FxfLQ\usr\bin;C:\Windows\system32;C:\Windows;C:\Windows\System32\Wbem;C:\Windows\System32\WindowsPowerShell\v1.0;C:\Windows\System32\OpenSSH;C:\Program Files\dotnet;C:\Program Files\Microsoft SQL Server\130\Tools\Binn;C:\Program Files\Microsoft SQL Server\Client SDK\ODBC\170\Tools\Binn;C:\Users\User\AppData\Local\Microsoft\WindowsApps;C:\Users\User\AppData\Local\Programs\Microsoft VS Code\bin;C:\Users\User\.dotnet\tools;C:\Users\User\Desktop\msys64-JHtqq6ULSEu38CxK8FxfLQ\mingw64\bin\
[9056] C:/Users/User/source/repos/fuzzfetch/.tox$ C:/Users/User/Desktop/msys64-JHtqq6ULSEu38CxK8FxfLQ/mingw64/bin/python.exe -m virtualenv --no-download --python C:/Users/User/Desktop/msys64-JHtqq6ULSEu38CxK8FxfLQ/mingw64/bin/python.exe py38
created virtual environment CPython3.8.8.final.0-64 in 532ms
  creator CPython3Windows(dest=C:/Users/User/source/repos/fuzzfetch/.tox/py38, clear=False, no_vcs_ignore=False, global=False)
  seeder FromAppData(download=False, pip=bundle, setuptools=bundle, wheel=bundle, via=copy, app_data_dir=C:/Users/User/AppData/Local/pypa/virtualenv)
    added seed packages: pip==21.0.1, setuptools==54.1.2, wheel==0.36.2
  activators BashActivator,BatchActivator,FishActivator,PowerShellActivator,PythonActivator,XonshActivator
py38 installdeps: freezegun, pytest, pytest-cov, requests-mock
setting PATH=C:/Users/User/source/repos/fuzzfetch/.tox/py38/Scripts;C:\Users\User\Desktop\msys64-JHtqq6ULSEu38CxK8FxfLQ\mingw64\bin;C:\Users\User\Desktop\msys64-JHtqq6ULSEu38CxK8FxfLQ\usr\bin;C:\Windows\system32;C:\Windows;C:\Windows\System32\Wbem;C:\Windows\System32\WindowsPowerShell\v1.0;C:\Windows\System32\OpenSSH;C:\Program Files\dotnet;C:\Program Files\Microsoft SQL Server\130\Tools\Binn;C:\Program Files\Microsoft SQL Server\Client SDK\ODBC\170\Tools\Binn;C:\Users\User\AppData\Local\Microsoft\WindowsApps;C:\Users\User\AppData\Local\Programs\Microsoft VS Code\bin;C:\Users\User\.dotnet\tools;C:\Users\User\Desktop\msys64-JHtqq6ULSEu38CxK8FxfLQ\mingw64\bin\
WARNING: test command found but not installed in testenv
  cmd: C:/Users/User/Desktop/msys64-JHtqq6ULSEu38CxK8FxfLQ/mingw64/bin/python.EXE
  env: C:/Users/User/source/repos/fuzzfetch/.tox/py38
Maybe you forgot to specify a dependency? See also the allowlist_externals envconfig setting.

DEPRECATION WARNING: this will be an error in tox 4 and above!
[5732] C:/Users/User/source/repos/fuzzfetch$ C:/Users/User/Desktop/msys64-JHtqq6ULSEu38CxK8FxfLQ/mingw64/bin/python.EXE -m pip install freezegun pytest pytest-cov requests-mock
Requirement already satisfied: freezegun in c:/users/user/desktop/msys64-jhtqq6ulseu38cxk8fxflq/mingw64/lib/python3.8/site-packages (1.1.0)
Requirement already satisfied: pytest in c:/users/user/desktop/msys64-jhtqq6ulseu38cxk8fxflq/mingw64/lib/python3.8/site-packages (6.2.2)
Requirement already satisfied: pytest-cov in c:/users/user/desktop/msys64-jhtqq6ulseu38cxk8fxflq/mingw64/lib/python3.8/site-packages (2.11.1)
Requirement already satisfied: requests-mock in c:/users/user/desktop/msys64-jhtqq6ulseu38cxk8fxflq/mingw64/lib/python3.8/site-packages (1.8.0)
Requirement already satisfied: python-dateutil>=2.7 in c:/users/user/desktop/msys64-jhtqq6ulseu38cxk8fxflq/mingw64/lib/python3.8/site-packages (from freezegun) (2.8.1)
Requirement already satisfied: six>=1.5 in c:/users/user/desktop/msys64-jhtqq6ulseu38cxk8fxflq/mingw64/lib/python3.8/site-packages (from python-dateutil>=2.7->freezegun) (1.15.0)
Requirement already satisfied: py>=1.8.2 in c:/users/user/desktop/msys64-jhtqq6ulseu38cxk8fxflq/mingw64/lib/python3.8/site-packages (from pytest) (1.10.0)
Requirement already satisfied: toml in c:/users/user/desktop/msys64-jhtqq6ulseu38cxk8fxflq/mingw64/lib/python3.8/site-packages (from pytest) (0.10.2)
Requirement already satisfied: iniconfig in c:/users/user/desktop/msys64-jhtqq6ulseu38cxk8fxflq/mingw64/lib/python3.8/site-packages (from pytest) (1.1.1)
Requirement already satisfied: atomicwrites>=1.0 in c:/users/user/desktop/msys64-jhtqq6ulseu38cxk8fxflq/mingw64/lib/python3.8/site-packages (from pytest) (1.4.0)
Requirement already satisfied: packaging in c:/users/user/desktop/msys64-jhtqq6ulseu38cxk8fxflq/mingw64/lib/python3.8/site-packages (from pytest) (20.9)
Requirement already satisfied: pluggy<1.0.0a1,>=0.12 in c:/users/user/desktop/msys64-jhtqq6ulseu38cxk8fxflq/mingw64/lib/python3.8/site-packages (from pytest) (0.13.1)
Requirement already satisfied: colorama in c:/users/user/desktop/msys64-jhtqq6ulseu38cxk8fxflq/mingw64/lib/python3.8/site-packages (from pytest) (0.4.4)
Requirement already satisfied: attrs>=19.2.0 in c:/users/user/desktop/msys64-jhtqq6ulseu38cxk8fxflq/mingw64/lib/python3.8/site-packages (from pytest) (20.3.0)
Requirement already satisfied: coverage>=5.2.1 in c:/users/user/desktop/msys64-jhtqq6ulseu38cxk8fxflq/mingw64/lib/python3.8/site-packages (from pytest-cov) (5.5)
Requirement already satisfied: requests<3,>=2.3 in c:/users/user/desktop/msys64-jhtqq6ulseu38cxk8fxflq/mingw64/lib/python3.8/site-packages (from requests-mock) (2.25.1)
Requirement already satisfied: certifi>=2017.4.17 in c:/users/user/desktop/msys64-jhtqq6ulseu38cxk8fxflq/mingw64/lib/python3.8/site-packages (from requests<3,>=2.3->requests-mock) (2020.12.5)
Requirement already satisfied: urllib3<1.27,>=1.21.1 in c:/users/user/desktop/msys64-jhtqq6ulseu38cxk8fxflq/mingw64/lib/python3.8/site-packages (from requests<3,>=2.3->requests-mock) (1.26.4)
Requirement already satisfied: chardet<5,>=3.0.2 in c:/users/user/desktop/msys64-jhtqq6ulseu38cxk8fxflq/mingw64/lib/python3.8/site-packages (from requests<3,>=2.3->requests-mock) (4.0.0)
Requirement already satisfied: idna<3,>=2.5 in c:/users/user/desktop/msys64-jhtqq6ulseu38cxk8fxflq/mingw64/lib/python3.8/site-packages (from requests<3,>=2.3->requests-mock) (2.10)
Requirement already satisfied: pyparsing>=2.0.2 in c:/users/user/desktop/msys64-jhtqq6ulseu38cxk8fxflq/mingw64/lib/python3.8/site-packages (from packaging->pytest) (2.4.7)
WARNING: test command found but not installed in testenv
  cmd: C:/Users/User/Desktop/msys64-JHtqq6ULSEu38CxK8FxfLQ/mingw64/bin/python.EXE
  env: C:/Users/User/source/repos/fuzzfetch/.tox/py38
Maybe you forgot to specify a dependency? See also the allowlist_externals envconfig setting.

DEPRECATION WARNING: this will be an error in tox 4 and above!
py38 finish: getenv C:/Users/User/source/repos/fuzzfetch/.tox/py38 after 2.28 seconds
py38 start: developpkg C:/Users/User/source/repos/fuzzfetch
py38 develop-inst: C:/Users/User/source/repos/fuzzfetch
--- snip ---
```

This was already mentioned in https://github.com/msys2/MINGW-packages/issues/7014 and #1648, but those dealt with other upstream packaging issues, and I see no tox issue regarding this specific problem.

### Steps to reproduce

Install [msys2](https://www.msys2.org/) and in its `bash` shell, run ` pacman -S mingw-w64-x86_64-python  mingw-w64-x86_64-python-pip  mingw-w64-x86_64-python-wheel`, then install latest tox using pip.

The project I've experienced this on is [fuzzfetch](https://github.com/MozillaSecurity/fuzzfetch). Just clone and run `tox -e py38`.

Discussion:
jschwartzentruber: Added a test. I also renamed the `TestenvConfig` attribute from `pathsep` to `ossep`, because `os.pathsep` is something else.

Patch:
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index e4a8c8e2..b1f3d28d 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -46,7 +46,7 @@ repos:
         args:
           - --min-py3-version
           - "3.4"
-  - repo: https://gitlab.com/pycqa/flake8
+  - repo: https://github.com/PyCQA/flake8
     rev: 3.9.0
     hooks:
       - id: flake8
@@ -64,3 +64,9 @@ repos:
         exclude: >-
           ^docs/changelog/(\d+\.(bugfix|feature|deprecation|breaking|doc|misc).rst|README.rst|template.jinja2)
         files: ^docs/changelog/
+      - id: changelogs-user-role
+        name: changelog files have a non-broken :user:`name` role
+        language: pygrep
+        entry: :user:([^`]+`?|`[^`]+[\s,])
+        pass_filenames: true
+        types: [file, rst]
diff --git a/CONTRIBUTORS b/CONTRIBUTORS
index 55dd6d1b..5be3eaa0 100644
--- a/CONTRIBUTORS
+++ b/CONTRIBUTORS
@@ -48,6 +48,7 @@ Ionel Maries Cristian
 Itxaka Serrano
 Jake Windle
 Jannis Leidel
+Jesse Schwartzentruber
 Joachim Brandon LeBlanc
 Johannes Christ
 John Mark Vandenberg
diff --git a/docs/changelog/1982.bugfix.rst b/docs/changelog/1982.bugfix.rst
new file mode 100644
index 00000000..6cd703a7
--- /dev/null
+++ b/docs/changelog/1982.bugfix.rst
@@ -0,0 +1,3 @@
+Distinguish between normal Windows Python and MSYS2 Python when looking for
+virtualenv executable path.  Adds os.sep to :class:`InterpreterInfo`
+- by :user:`jschwartzentruber`
diff --git a/src/tox/config/__init__.py b/src/tox/config/__init__.py
index 278d4b2b..6d45c987 100644
--- a/src/tox/config/__init__.py
+++ b/src/tox/config/__init__.py
@@ -1053,6 +1053,11 @@ class TestenvConfig:
             isinstance(self.python_info, NoInterpreterInfo)
             or tox.INFO.IS_WIN is False
             or self.python_info.implementation == "Jython"
+            or (
+                # this combination is MSYS2
+                tox.INFO.IS_WIN
+                and self.python_info.os_sep == "/"
+            )
             or (
                 tox.INFO.IS_WIN
                 and self.python_info.implementation == "PyPy"
diff --git a/src/tox/helper/get_version.py b/src/tox/helper/get_version.py
index b1a8455a..8f370e5a 100644
--- a/src/tox/helper/get_version.py
+++ b/src/tox/helper/get_version.py
@@ -1,6 +1,7 @@
 from __future__ import unicode_literals
 
 import json
+import os
 import platform
 import sys
 
@@ -11,6 +12,7 @@ info = {
     "version": sys.version,
     "is_64": sys.maxsize > 2 ** 32,
     "sysplatform": sys.platform,
+    "os_sep": os.sep,
     "extra_version_info": getattr(sys, "pypy_version_info", None),
 }
 info_as_dump = json.dumps(info)
diff --git a/src/tox/interpreters/__init__.py b/src/tox/interpreters/__init__.py
index a3131400..7bc2fbd1 100644
--- a/src/tox/interpreters/__init__.py
+++ b/src/tox/interpreters/__init__.py
@@ -103,6 +103,7 @@ class InterpreterInfo:
         version_info,
         sysplatform,
         is_64,
+        os_sep,
         extra_version_info,
     ):
         self.implementation = implementation
@@ -111,6 +112,7 @@ class InterpreterInfo:
         self.version_info = version_info
         self.sysplatform = sysplatform
         self.is_64 = is_64
+        self.os_sep = os_sep
         self.extra_version_info = extra_version_info
 
     def __str__(self):


Test patch:
diff --git a/tests/unit/config/test_config.py b/tests/unit/config/test_config.py
index b4bd24fb..33adc1da 100644
--- a/tests/unit/config/test_config.py
+++ b/tests/unit/config/test_config.py
@@ -1469,6 +1469,23 @@ class TestConfigTestEnv:
         if bp == "jython":
             assert envconfig.envpython == envconfig.envbindir.join(bp)
 
+    @pytest.mark.skipif(tox.INFO.IS_PYPY, reason="only applies to CPython")
+    @pytest.mark.parametrize("sep, bindir", [("\\", "Scripts"), ("/", "bin")])
+    def test_envbindir_win(self, newconfig, monkeypatch, sep, bindir):
+        monkeypatch.setattr(tox.INFO, "IS_WIN", True)
+        config = newconfig(
+            """
+            [testenv]
+            basepython=python
+        """,
+        )
+        assert len(config.envconfigs) == 1
+        envconfig = config.envconfigs["python"]
+        envconfig.python_info.os_sep = sep  # force os.sep result
+        # on win32 with msys2, virtualenv uses "bin" for python
+        assert envconfig.envbindir.basename == bindir
+        assert envconfig.envpython == envconfig.envbindir.join("python")
+
     @pytest.mark.parametrize("plat", ["win32", "linux2"])
     def test_passenv_as_multiline_list(self, newconfig, monkeypatch, plat):
         monkeypatch.setattr(tox.INFO, "IS_WIN", plat == "win32")
diff --git a/tests/unit/interpreters/test_interpreters.py b/tests/unit/interpreters/test_interpreters.py
index fd364baf..298a4aae 100644
--- a/tests/unit/interpreters/test_interpreters.py
+++ b/tests/unit/interpreters/test_interpreters.py
@@ -192,7 +192,9 @@ class TestInterpreterInfo:
         version_info="my-version-info",
         sysplatform="my-sys-platform",
     ):
-        return InterpreterInfo(implementation, executable, version_info, sysplatform, True, None)
+        return InterpreterInfo(
+            implementation, executable, version_info, sysplatform, True, "/", None
+        )
 
     def test_data(self):
         x = self.info("larry", "moe", "shemp", "curly")