From 121ef6ba07c0a8e6302e50aabca16c747f036fff Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Bern=C3=A1t=20G=C3=A1bor?= <bgabor8@bloomberg.net>
Date: Sat, 6 Jun 2020 09:37:02 +0100
Subject: [PATCH] PyPy 7.3.1 now generates Scripts instead of bin on Windows
 (#1597)

---
 .pre-commit-config.yaml                      |  8 ++++----
 docs/changelog/1597.bugfix.rst               |  1 +
 src/tox/config/__init__.py                   | 15 +++++++++++----
 src/tox/helper/get_version.py                |  4 +++-
 src/tox/interpreters/__init__.py             | 15 +++++++++++----
 src/tox/interpreters/via_path.py             |  2 +-
 tests/unit/interpreters/test_interpreters.py |  9 +++++----
 7 files changed, 36 insertions(+), 18 deletions(-)
 create mode 100644 docs/changelog/1597.bugfix.rst

diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index 072b3a3fa..c3beeb26c 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -13,10 +13,6 @@ repos:
   - id: debug-statements
   - id: end-of-file-fixer
   - id: trailing-whitespace
-- repo: https://github.com/asottile/add-trailing-comma
-  rev: v2.0.1
-  hooks:
-  - id: add-trailing-comma
 - repo: https://github.com/asottile/pyupgrade
   rev: v2.4.3
   hooks:
@@ -46,6 +42,10 @@ repos:
     additional_dependencies:
     - black==19.10b0
     language_version: python3.8
+- repo: https://github.com/asottile/add-trailing-comma
+  rev: v2.0.1
+  hooks:
+  - id: add-trailing-comma
 - repo: https://github.com/pre-commit/pygrep-hooks
   rev: v1.5.1
   hooks:
diff --git a/docs/changelog/1597.bugfix.rst b/docs/changelog/1597.bugfix.rst
new file mode 100644
index 000000000..7482e0882
--- /dev/null
+++ b/docs/changelog/1597.bugfix.rst
@@ -0,0 +1 @@
+PyPy 7.3.1 on Windows uses the ``Script`` folder instead of ``bin``. - by :user:`gaborbernat`
diff --git a/src/tox/config/__init__.py b/src/tox/config/__init__.py
index f42a68d57..2ac4cf3aa 100644
--- a/src/tox/config/__init__.py
+++ b/src/tox/config/__init__.py
@@ -955,10 +955,17 @@ def __init__(self, envname, config, factors, reader):
 
     def get_envbindir(self):
         """Path to directory where scripts/binaries reside."""
-        if tox.INFO.IS_WIN and "jython" not in self.basepython and "pypy" not in self.basepython:
-            return self.envdir.join("Scripts")
-        else:
-            return self.envdir.join("bin")
+        is_bin = (
+            isinstance(self.python_info, NoInterpreterInfo)
+            or tox.INFO.IS_WIN is False
+            or self.python_info.implementation == "Jython"
+            or (
+                tox.INFO.IS_WIN
+                and self.python_info.implementation == "PyPy"
+                and self.python_info.extra_version_info < (7, 3, 1)
+            )
+        )
+        return self.envdir.join("bin" if is_bin else "Scripts")
 
     @property
     def envbindir(self):
diff --git a/src/tox/helper/get_version.py b/src/tox/helper/get_version.py
index 3fcc37e43..b1a8455a4 100644
--- a/src/tox/helper/get_version.py
+++ b/src/tox/helper/get_version.py
@@ -1,15 +1,17 @@
 from __future__ import unicode_literals
 
 import json
+import platform
 import sys
 
 info = {
     "executable": sys.executable,
-    "name": "pypy" if hasattr(sys, "pypy_version_info") else "python",
+    "implementation": platform.python_implementation(),
     "version_info": list(sys.version_info),
     "version": sys.version,
     "is_64": sys.maxsize > 2 ** 32,
     "sysplatform": sys.platform,
+    "extra_version_info": getattr(sys, "pypy_version_info", None),
 }
 info_as_dump = json.dumps(info)
 print(info_as_dump)
diff --git a/src/tox/interpreters/__init__.py b/src/tox/interpreters/__init__.py
index 797e5ae57..c03431989 100644
--- a/src/tox/interpreters/__init__.py
+++ b/src/tox/interpreters/__init__.py
@@ -59,13 +59,16 @@ def run_and_get_interpreter_info(name, executable):
     try:
         result = get_python_info(str(executable))
         result["version_info"] = tuple(result["version_info"])  # fix json dump transformation
-        del result["name"]
+        if result["extra_version_info"] is not None:
+            result["extra_version_info"] = tuple(
+                result["extra_version_info"],
+            )  # fix json dump transformation
         del result["version"]
         result["executable"] = str(executable)
     except ExecFailed as e:
         return NoInterpreterInfo(name, executable=e.executable, out=e.out, err=e.err)
     else:
-        return InterpreterInfo(name, **result)
+        return InterpreterInfo(**result)
 
 
 def exec_on_interpreter(*args):
@@ -93,12 +96,16 @@ def __init__(self, executable, source, out, err):
 
 
 class InterpreterInfo:
-    def __init__(self, name, executable, version_info, sysplatform, is_64):
-        self.name = name
+    def __init__(
+        self, implementation, executable, version_info, sysplatform, is_64, extra_version_info,
+    ):
+        self.implementation = implementation
         self.executable = executable
+
         self.version_info = version_info
         self.sysplatform = sysplatform
         self.is_64 = is_64
+        self.extra_version_info = extra_version_info
 
     def __str__(self):
         return "<executable at {}, version_info {}>".format(self.executable, self.version_info)
diff --git a/src/tox/interpreters/via_path.py b/src/tox/interpreters/via_path.py
index 670833208..8634d697d 100644
--- a/src/tox/interpreters/via_path.py
+++ b/src/tox/interpreters/via_path.py
@@ -38,7 +38,7 @@ def exe_spec(python_exe, base):
             info = get_python_info(python_exe)
             if info is not None:
                 found = PythonSpec(
-                    info["name"],
+                    "pypy" if info["implementation"] == "PyPy" else "python",
                     info["version_info"][0],
                     info["version_info"][1],
                     64 if info["is_64"] else 32,
diff --git a/tests/unit/interpreters/test_interpreters.py b/tests/unit/interpreters/test_interpreters.py
index 5d532e0bd..fd364bafb 100644
--- a/tests/unit/interpreters/test_interpreters.py
+++ b/tests/unit/interpreters/test_interpreters.py
@@ -1,6 +1,7 @@
 from __future__ import unicode_literals
 
 import os
+import platform
 import stat
 import subprocess
 import sys
@@ -101,7 +102,7 @@ def test_run_and_get_interpreter_info():
     name = os.path.basename(sys.executable)
     info = run_and_get_interpreter_info(name, sys.executable)
     assert info.version_info == tuple(sys.version_info)
-    assert info.name == name
+    assert info.implementation == platform.python_implementation()
     assert info.executable == sys.executable
 
 
@@ -186,16 +187,16 @@ def test_exec_failed():
 class TestInterpreterInfo:
     @staticmethod
     def info(
-        name="my-name",
+        implementation="CPython",
         executable="my-executable",
         version_info="my-version-info",
         sysplatform="my-sys-platform",
     ):
-        return InterpreterInfo(name, executable, version_info, sysplatform, True)
+        return InterpreterInfo(implementation, executable, version_info, sysplatform, True, None)
 
     def test_data(self):
         x = self.info("larry", "moe", "shemp", "curly")
-        assert x.name == "larry"
+        assert x.implementation == "larry"
         assert x.executable == "moe"
         assert x.version_info == "shemp"
         assert x.sysplatform == "curly"