Repository: pyenv-win/pyenv-win (language: python)

Issue:
bug: `pyenv exec` starts the last `python.exe` with many locals
**Describe the bug**
`pyenv exec` prefers the last version listed in `.python-version` when all other command prefer the first.

**To Reproduce**
Steps to reproduce the behavior:
1. `pyenv local 3.9.6 3.8.10`
2. `python -V`
4. You will see it picked 3.8.10

Alternatively you can run the test from the PR that will be linked from shortly after creating this issue.

**Expected behavior**
It should have picked 3.9.6


Discussion / hints:

Fix patch:
diff --git a/.version b/.version
index c2912de8..b4f4ea3c 100644
--- a/.version
+++ b/.version
@@ -1,1 +1,1 @@
-2.64.10
\ No newline at end of file
+2.64.11
\ No newline at end of file
diff --git a/README.md b/README.md
index 1ead0aec..b07995f2 100644
--- a/README.md
+++ b/README.md
@@ -222,6 +222,9 @@ Now follow the steps to "[finish the installation](#finish-the-installation)".
 
 ## Change Log
 
+### New in 2.64.11
+- Fix [#305](https://github.com/pyenv-win/pyenv-win/issues/305): Fix `exec` preferring the last version listed in `.python-version` instead of the first.
+
 ### New in 2.64.10
 - Check `PATH` in `pyenv version` to report other Python versions.
 
diff --git a/pyenv-win/bin/pyenv.bat b/pyenv-win/bin/pyenv.bat
index aa1ddd90..67093711 100644
--- a/pyenv-win/bin/pyenv.bat
+++ b/pyenv-win/bin/pyenv.bat
@@ -56,12 +56,8 @@ set cmdline=%*
 set cmdline=%cmdline:~5%
 :: update PATH to active version and run command
 :: endlocal needed only if cmdline sets a variable: SET FOO=BAR
-set cmddir=
-if exist %bindir%\%2 set cmddir=%bindir%\
-if exist %bindir%\%2.exe set cmddir=%bindir%\
-if exist %bindir%\%2.bat set cmddir=%bindir%\
 set "path=%extrapaths%%path%"
-%cmddir%%cmdline%
+%cmdline%
 endlocal
 exit /b
 :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
diff --git a/pyenv-win/libexec/libs/pyenv-lib.vbs b/pyenv-win/libexec/libs/pyenv-lib.vbs
index 7a533e4f..4a32bf96 100644
--- a/pyenv-win/libexec/libs/pyenv-lib.vbs
+++ b/pyenv-win/libexec/libs/pyenv-lib.vbs
@@ -253,7 +253,7 @@ Function GetExtensionsNoPeriod(addPy)
 End Function
 
 ' pyenv - bin - windows
-Sub WriteWinScript(baseName, strDirBin)
+Sub WriteWinScript(baseName)
     ' WScript.echo "kkotari: pyenv-lib.vbs write win script..!"
     Dim filespec
     filespec = strDirShims &"\"& baseName &".bat"
@@ -261,21 +261,21 @@ Sub WriteWinScript(baseName, strDirBin)
         With objfs.CreateTextFile(filespec)
             .WriteLine("@echo off")
             .WriteLine("chcp 1250 > NUL")
-            .WriteLine("call pyenv exec "&strDirBin&"%~n0 %*")
+            .WriteLine("call pyenv exec %~n0 %*")
             .Close
         End With
     End If
 End Sub
 
 ' pyenv - bin - linux
-Sub WriteLinuxScript(baseName, strDirBin)
+Sub WriteLinuxScript(baseName)
     ' WScript.echo "kkotari: pyenv-lib.vbs write linux script..!"
     Dim filespec
     filespec = strDirShims &"\"& baseName
     If Not objfs.FileExists(filespec) Then
         With objfs.CreateTextFile(filespec)
             .WriteLine("#!/bin/sh")
-            .WriteLine("pyenv exec "&strDirBin&"$(basename ""$0"") ""$@""")
+            .WriteLine("pyenv exec $(basename ""$0"") ""$@""")
             .Close
         End With
     End If
@@ -305,8 +305,8 @@ Sub Rehash()
             ' WScript.echo "kkotari: pyenv-lib.vbs rehash for winBinDir"
             If exts.Exists(LCase(objfs.GetExtensionName(file))) Then
                 baseName = objfs.GetBaseName(file)
-                WriteWinScript baseName, ""
-                WriteLinuxScript baseName, ""
+                WriteWinScript baseName
+                WriteLinuxScript baseName
             End If
         Next
 
@@ -315,8 +315,8 @@ Sub Rehash()
                 ' WScript.echo "kkotari: pyenv-lib.vbs rehash for winBinDir\Scripts"
                 If exts.Exists(LCase(objfs.GetExtensionName(file))) Then
                     baseName = objfs.GetBaseName(file)
-                    WriteWinScript baseName, "Scripts/"
-                    WriteLinuxScript baseName, "Scripts/"
+                    WriteWinScript baseName
+                    WriteLinuxScript baseName
                 End If
             Next
         End If


Test patch:
diff --git a/tests/test_pyenv_feature_exec.py b/tests/test_pyenv_feature_exec.py
index 8cee2446..adc13858 100644
--- a/tests/test_pyenv_feature_exec.py
+++ b/tests/test_pyenv_feature_exec.py
@@ -84,16 +84,14 @@ def teardown_class(cls):
     )
     def test_exec_arg(self, setup, command, arg):
         with TemporaryEnvironment({'World': 'Earth'}):
-            args = [*command(self.__class__.ctx.pyenv_path), "-c", "import sys; print(sys.argv[1])", arg]
-            result = subprocess.run(args, capture_output=True, encoding="utf8")
-            # \x0c: generated by cls in cmd AutoRun
-            stdout = result.stdout.strip("\r\n\x0c")
-            # " is escaped as \" by python, so we unescape it here
-            stdout = stdout.replace('\\', '"')
-            stderr = result.stderr.strip()
-            if stderr != "":
-                print(f'stderr: {stderr}')
+            stdout, stderr = self.__class__.ctx.exec([
+                *command(self.__class__.ctx.pyenv_path),
+                "-c",
+                "import sys; print(sys.argv[1])",
+                arg
+            ])
             assert stdout == arg.replace('%World%', 'Earth')
+            assert stderr == ""
 
     @pytest.mark.parametrize(
         "args",
@@ -109,11 +107,9 @@ def test_exec_arg(self, setup, command, arg):
         ]
     )
     def test_exec_help(self, setup, args):
-        args = [str(self.__class__.ctx.pyenv_path / "bin" / "pyenv.bat"), *args]
-        result = subprocess.run(args, capture_output=True, encoding="utf8")
-        # \x0c: generated by cls in cmd AutoRun
-        stdout = result.stdout.strip("\r\n\x0c")
+        stdout, stderr = self.__class__.ctx.pyenv(args)
         assert "\r\n".join(stdout.splitlines()[:1]) == pyenv_exec_help()
+        assert stderr == ""
 
     def test_path_not_updated(self, setup):
         python = str(self.__class__.ctx.pyenv_path / "shims" / "python.bat")
@@ -124,34 +120,30 @@ def test_path_not_updated(self, setup):
             print(f'@call {python} -V>nul', file=f)
             print(f'@echo %PATH%', file=f)
         args = ["call", tmp_bat]
-        result = subprocess.run(args, shell=True, capture_output=True, encoding="utf8")
+        stdout, stderr = self.__class__.ctx.exec(args)
         # \x0c: generated by cls in cmd AutoRun
-        stdout = result.stdout.strip("\r\n\x0c").splitlines()
+        stdout = stdout.splitlines()
         assert stdout[0] == stdout[1]
+        assert stderr == ""
 
     def test_many_paths(self, setup):
-        pyenv = str(self.__class__.ctx.pyenv_path / "bin" / "pyenv.bat")
-        args = [pyenv, 'exec', 'python', '-c', "import os; print(os.environ['PATH'])"]
-        result = subprocess.run(args, capture_output=True, encoding="utf8")
-        # \x0c: generated by cls in cmd AutoRun
-        stdout = result.stdout.strip("\r\n\x0c")
-        paths = stdout.split(sep=';')
-        i = 0
-        for v in ['3.7.7', '3.8.9']:
-            for relative in ['', r'\Scripts']:
-                assert paths[i].endswith(f'{v}{relative}')
-                i = i + 1
+        args = ['exec', 'python', '-c', "import os; print(os.environ['PATH'])"]
+        stdout, stderr = self.__class__.ctx.pyenv(args)
+        assert stdout.startswith(
+            (
+                rf"{self.__class__.ctx.pyenv_path}\versions\3.7.7;"
+                rf"{self.__class__.ctx.pyenv_path}\versions\3.7.7\Scripts;"
+                rf"{self.__class__.ctx.pyenv_path}\versions\3.8.9;"
+                rf"{self.__class__.ctx.pyenv_path}\versions\3.8.9\Scripts;"
+            )
+        )
+        assert stderr == ""
+        args = ['exec', 'version']
+        assert self.__class__.ctx.pyenv(args) == ("3.7.7", "")
 
     def test_bat_shim(self, setup):
-        pyenv = str(self.__class__.ctx.pyenv_path / "bin" / "pyenv.bat")
-        args = [pyenv, 'exec', 'Scripts/hello']
-        result = subprocess.run(args, capture_output=True, encoding="utf8")
-        stderr = result.stderr.strip("\r\n")
-        if stderr != '':
-            print(f'stderr: {stderr}')
-        # \x0c: generated by cls in cmd AutoRun
-        stdout = result.stdout.strip("\r\n\x0c")
-        assert stdout == "Hello world!"
+        args = ['exec', 'hello']
+        assert self.__class__.ctx.pyenv(args) == ("Hello world!", "")
 
 
 def pyenv_exec_help():
diff --git a/tests/test_pyenv_feature_global.py b/tests/test_pyenv_feature_global.py
index d4ff0b0b..a4d0905d 100644
--- a/tests/test_pyenv_feature_global.py
+++ b/tests/test_pyenv_feature_global.py
@@ -15,23 +15,25 @@ def commands(ctx):
                 ["help", "global"],
                 ["global", "--help"],
             ]:
-                assert "\r\n".join(ctx.pyenv(args).splitlines()[:2]) == pyenv_global_help()
+                stdout, stderr = ctx.pyenv(args)
+                assert "\r\n".join(stdout.splitlines()[:2]) == pyenv_global_help()
+                assert stderr == ""
         run_pyenv_test({}, commands)
 
     def test_global_no_version(self, setup):
         def commands(ctx):
-            assert ctx.pyenv("global") == "no global version configured"
+            assert ctx.pyenv("global") == ("no global version configured", "")
         run_pyenv_test({}, commands)
 
     def test_global_version_defined(self, setup):
         def commands(ctx):
-            assert ctx.pyenv("global") == "3.8.9"
+            assert ctx.pyenv("global") == ("3.8.9", "")
         run_pyenv_test({'global_ver': "3.8.9"}, commands)
 
     def test_global_set_installed_version(self, setup):
         def commands(ctx):
-            assert ctx.pyenv(["global", "3.7.7"]) == ""
-            assert ctx.pyenv("global") == "3.7.7"
+            assert ctx.pyenv(["global", "3.7.7"]) == ("", "")
+            assert ctx.pyenv("global") == ("3.7.7", "")
         settings = {
             'versions': ["3.7.7", "3.8.9"],
             'global_ver': "3.8.9"
@@ -40,7 +42,7 @@ def commands(ctx):
 
     def test_global_set_unknown_version(self, setup):
         def commands(ctx):
-            assert ctx.pyenv(["global", "3.7.8"]) == not_installed_output("3.7.8")
+            assert ctx.pyenv(["global", "3.7.8"]) == (not_installed_output("3.7.8"), "")
         settings = {
             'versions': ["3.8.9"],
             'global_ver': "3.8.9",
@@ -49,8 +51,8 @@ def commands(ctx):
 
     def test_global_unset(self, setup):
         def commands(ctx):
-            assert ctx.pyenv(["global", "--unset"]) == ""
-            assert ctx.pyenv("global") == "no global version configured"
+            assert ctx.pyenv(["global", "--unset"]) == ("", "")
+            assert ctx.pyenv("global") == ("no global version configured", "")
         settings = {
             'versions': ["3.8.9"],
             'global_ver': "3.8.9",
diff --git a/tests/test_pyenv_feature_help.py b/tests/test_pyenv_feature_help.py
index cb5517be..2c8f9291 100644
--- a/tests/test_pyenv_feature_help.py
+++ b/tests/test_pyenv_feature_help.py
@@ -5,6 +5,8 @@
 class TestPyenvFeatureGlobal(TestPyenvBase):
     def test_help(self, setup):
         def commands(ctx):
-            stdout = "\r\n".join(ctx.pyenv("help").splitlines()[:2])
+            stdout, stderr = ctx.pyenv("help")
+            stdout = "\r\n".join(stdout.splitlines()[:2])
             assert stdout.strip() == "Usage: pyenv <command> [<args>]"
+            assert stderr == ""
         run_pyenv_test({}, commands)
diff --git a/tests/test_pyenv_feature_local.py b/tests/test_pyenv_feature_local.py
index 9a67055e..1f291677 100644
--- a/tests/test_pyenv_feature_local.py
+++ b/tests/test_pyenv_feature_local.py
@@ -5,18 +5,18 @@
 class TestPyenvFeatureLocal(TestPyenvBase):
     def test_no_local_version(self, setup):
         def commands(ctx):
-            assert ctx.pyenv("local") == "no local version configured for this directory"
+            assert ctx.pyenv("local") == ("no local version configured for this directory", "")
         run_pyenv_test({}, commands)
 
     def test_local_version_defined(self, setup):
         def commands(ctx):
-            assert ctx.pyenv("local") == "3.8.9"
+            assert ctx.pyenv("local") == ("3.8.9", "")
         run_pyenv_test({'local_ver': "3.8.9"}, commands)
 
     def test_local_set_installed_version(self, setup):
         def commands(ctx):
-            assert ctx.pyenv(["local", "3.7.7"]) == ""
-            assert ctx.pyenv("local") == "3.7.7"
+            assert ctx.pyenv(["local", "3.7.7"]) == ("", "")
+            assert ctx.pyenv("local") == ("3.7.7", "")
         settings = {
             'versions': ["3.7.7", "3.8.9"],
             'local_ver': "3.8.9",
@@ -25,21 +25,21 @@ def commands(ctx):
 
     def test_local_set_unknown_version(self, setup):
         def commands(ctx):
-            assert ctx.pyenv(["local", "3.7.8"]) == not_installed_output("3.7.8")
+            assert ctx.pyenv(["local", "3.7.8"]) == (not_installed_output("3.7.8"), "")
         run_pyenv_test({'versions': ["3.8.9"]}, commands)
 
     def test_local_set_many_versions(self, setup):
         def commands(ctx):
-            assert ctx.pyenv(["local", "3.7.7", "3.8.9"]) == ""
+            assert ctx.pyenv(["local", "3.7.7", "3.8.9"]) == ("", "")
             assert local_python_versions(ctx.local_path) == "3.7.7\n3.8.9"
         run_pyenv_test({'versions': ["3.7.7", "3.8.9"]}, commands)
 
     def test_local_set_many_versions_one_not_installed(self, setup):
         def commands(ctx):
-            assert ctx.pyenv(["local", "3.7.7", "3.8.9"]) == not_installed_output("3.8.9")
+            assert ctx.pyenv(["local", "3.7.7", "3.8.9"]) == (not_installed_output("3.8.9"), "")
         run_pyenv_test({'versions': ["3.7.7"]}, commands)
 
     def test_local_many_versions_defined(self, setup):
         def commands(ctx):
-            assert ctx.pyenv("local") == "3.7.7\r\n3.8.9"
+            assert ctx.pyenv("local") == ("3.7.7\r\n3.8.9", "")
         run_pyenv_test({'local_ver': "3.7.7\n3.8.9\n"}, commands)
diff --git a/tests/test_pyenv_feature_rehash.py b/tests/test_pyenv_feature_rehash.py
index 6fbaad4b..8d82f4a8 100644
--- a/tests/test_pyenv_feature_rehash.py
+++ b/tests/test_pyenv_feature_rehash.py
@@ -18,12 +18,15 @@ def assert_shims(pyenv_path, ver):
 class TestPyenvFeatureRehash(TestPyenvBase):
     def test_rehash_no_version(self, setup):
         def commands(ctx):
-            assert ctx.pyenv('rehash') == "No version installed. Please install one with 'pyenv install <version>'."
+            assert ctx.pyenv('rehash') == (
+                "No version installed. Please install one with 'pyenv install <version>'.",
+                ""
+            )
         run_pyenv_test({}, commands)
 
     def test_rehash_global_version(self, setup):
         def commands(ctx):
-            assert ctx.pyenv('rehash') == ""
+            assert ctx.pyenv('rehash') == ("", "")
             assert_shims(ctx.pyenv_path, '3.8.6')
             assert_shims(ctx.pyenv_path, '3.8.7')
         settings = {
@@ -34,7 +37,7 @@ def commands(ctx):
 
     def test_rehash_local_version(self, setup):
         def commands(ctx):
-            assert ctx.pyenv('rehash') == ""
+            assert ctx.pyenv('rehash') == ("", "")
             assert_shims(ctx.pyenv_path, '3.8.6')
             assert_shims(ctx.pyenv_path, '3.9.1')
         settings = {
@@ -46,7 +49,7 @@ def commands(ctx):
 
     def test_rehash_shell_version(self, setup):
         def commands(ctx):
-            assert ctx.pyenv('rehash') == ""
+            assert ctx.pyenv('rehash') == ("", "")
         global_ver = '3.7.5'
         local_ver = '3.8.6'
         shell_ver = '3.9.1'
diff --git a/tests/test_pyenv_feature_shell.py b/tests/test_pyenv_feature_shell.py
index 7b07890e..73017a2f 100644
--- a/tests/test_pyenv_feature_shell.py
+++ b/tests/test_pyenv_feature_shell.py
@@ -18,18 +18,20 @@ def commands(ctx):
                 ["help", "shell"],
                 ["shell", "--help"],
             ]:
-                assert "\r\n".join(ctx.pyenv(args).splitlines()[:2]) == pyenv_shell_help()
+                stdout, stderr = ctx.pyenv(args)
+                assert "\r\n".join(stdout.splitlines()[:2]) == pyenv_shell_help()
+                stderr == ""
         run_pyenv_test({}, commands)
 
     def test_no_shell_version(self, setup):
         def commands(ctx):
-            assert ctx.pyenv("shell") == "no shell-specific version configured"
+            assert ctx.pyenv("shell") == ("no shell-specific version configured", "")
         with TemporaryEnvironment({"PYENV_VERSION": ""}):
             run_pyenv_test({}, commands)
 
     def test_shell_version_defined(self, setup):
         def commands(ctx):
-            assert ctx.pyenv("shell") == "3.9.2"
+            assert ctx.pyenv("shell") == ("3.9.2", "")
         with TemporaryEnvironment({"PYENV_VERSION": "3.9.2"}):
             run_pyenv_test({}, commands)
 
@@ -50,5 +52,5 @@ def commands(ctx):
 
     def test_shell_set_unknown_version(self, setup):
         def commands(ctx):
-            assert ctx.pyenv(["shell", "3.7.8"]) == not_installed_output("3.7.8")
+            assert ctx.pyenv(["shell", "3.7.8"]) == (not_installed_output("3.7.8"), "")
         run_pyenv_test({'versions': ["3.8.9"]}, commands)
diff --git a/tests/test_pyenv_feature_version.py b/tests/test_pyenv_feature_version.py
index 657b1884..bc45fa71 100644
--- a/tests/test_pyenv_feature_version.py
+++ b/tests/test_pyenv_feature_version.py
@@ -18,24 +18,31 @@ def commands(ctx):
                 ["help", "version"],
                 ["version", "--help"],
             ]:
-                assert "\r\n".join(ctx.pyenv(args).splitlines()[:2]).strip() == pyenv_version_help()
+                stdout, stderr = ctx.pyenv(args)
+                assert "\r\n".join(stdout.splitlines()[:2]).strip() == pyenv_version_help()
+                assert stderr == ""
         run_pyenv_test({}, commands)
 
     def test_no_version(self, setup):
         def commands(ctx):
-            assert ctx.pyenv("version") == ("No global python version has been set yet. "
-                                            "Please set the global version by typing:\r\n"
-                                            "pyenv global 3.7.2")
+            assert ctx.pyenv("version") == (
+                (
+                    "No global python version has been set yet. "
+                    "Please set the global version by typing:\r\n"
+                    "pyenv global 3.7.2"
+                ),
+                ""
+            )
         run_pyenv_test({}, commands)
 
     def test_global_version(self, setup):
         def commands(ctx):
-            assert ctx.pyenv("version") == rf'3.7.2 (set by {ctx.pyenv_path}\version)'
+            assert ctx.pyenv("version") == (rf'3.7.2 (set by {ctx.pyenv_path}\version)', "")
         run_pyenv_test({'global_ver': "3.7.2"}, commands)
 
     def test_one_local_version(self, setup):
         def commands(ctx):
-            assert ctx.pyenv("version") == rf'3.9.1 (set by {ctx.local_path}\.python-version)'
+            assert ctx.pyenv("version") == (rf'3.9.1 (set by {ctx.local_path}\.python-version)', "")
         settings = {
             'global_ver': "3.7.2",
             'local_ver': "3.9.1"
@@ -44,7 +51,7 @@ def commands(ctx):
 
     def test_shell_version(self, setup):
         def commands(ctx):
-            assert ctx.pyenv("version") == "3.9.2 (set by %PYENV_VERSION%)"
+            assert ctx.pyenv("version") == ("3.9.2 (set by %PYENV_VERSION%)", "")
         settings = {
             'global_ver': "3.7.5",
             'local_ver': "3.8.6",
@@ -54,8 +61,13 @@ def commands(ctx):
 
     def test_many_local_versions(self, setup):
         def commands(ctx):
-            assert ctx.pyenv("version") == (f'3.8.8 (set by {ctx.local_path}\\.python-version)\r\n'
-                                            f'3.9.1 (set by {ctx.local_path}\\.python-version)')
+            assert ctx.pyenv("version") == (
+                (
+                    f'3.8.8 (set by {ctx.local_path}\\.python-version)\r\n'
+                    f'3.9.1 (set by {ctx.local_path}\\.python-version)'
+                ),
+                ""
+            )
         settings = {
             'global_ver': "3.7.2",
             'local_ver': "3.8.8\n3.9.1\n"
@@ -66,7 +78,7 @@ def test_bad_path(self, setup):
         with tempfile.TemporaryDirectory() as tmp_path:
             def commands(ctx):
                 touch(Path(ctx.pyenv_path, r'shims\python.bat'))
-                stdout = ctx.pyenv("version")
+                stdout, stderr = ctx.pyenv("version")
                 expected = (f'\x1b[91mFATAL: Found \x1b[95m{tmp_path}\\python.exe\x1b[91m version '
                             f'before pyenv in PATH.\x1b[0m\r\n'
                             f'\x1b[91mPlease remove \x1b[95m{tmp_path}\\\x1b[91m from '
@@ -76,6 +88,7 @@ def commands(ctx):
                 stdout = stdout.replace('RUNNER~1', 'runneradmin')
                 expected = expected.replace('RUNNER~1', 'runneradmin')
                 assert stdout == expected
+                assert stderr == ""
 
             touch(Path(tmp_path, 'python.exe'))
             with TemporaryEnvironment({"PATH": f"{tmp_path};{os.environ['PATH']}"}):
diff --git a/tests/test_pyenv_feature_version_name.py b/tests/test_pyenv_feature_version_name.py
index 59a20b0c..98b37708 100644
--- a/tests/test_pyenv_feature_version_name.py
+++ b/tests/test_pyenv_feature_version_name.py
@@ -10,8 +10,10 @@ def commands(ctx):
             ["help", "version-name"],
             ["version-name", "--help"],
         ]:
-            stdout = "\r\n".join(ctx.pyenv(args).splitlines()[:2]).strip()
+            stdout, stderr = ctx.pyenv(args)
+            stdout = "\r\n".join(stdout.splitlines()[:2]).strip()
             assert stdout == "Usage: pyenv version-name"
+            assert stderr == ""
     run_pyenv_test({}, commands)
 
 
@@ -22,31 +24,38 @@ def commands(ctx):
             ["help", "vname"],
             ["vname", "--help"],
         ]:
-            stdout = "\r\n".join(ctx.pyenv(args).splitlines()[:2]).strip()
+            stdout, stderr = ctx.pyenv(args)
+            stdout = "\r\n".join(stdout.splitlines()[:2]).strip()
             assert stdout == "Usage: pyenv vname"
+            assert stderr == ""
     run_pyenv_test({}, commands)
 
 
 @pytest.mark.parametrize("command", ['version-name', 'vname'])
 def test_no_version(command):
     def commands(ctx):
-        assert ctx.pyenv(command) == ("No global python version has been set yet. "
-                                      "Please set the global version by typing:\r\n"
-                                      "pyenv global 3.7.2")
+        assert ctx.pyenv(command) == (
+            (
+                "No global python version has been set yet. "
+                "Please set the global version by typing:\r\n"
+                "pyenv global 3.7.2"
+            ),
+            ""
+        )
     run_pyenv_test({}, commands)
 
 
 @pytest.mark.parametrize("command", ['version-name', 'vname'])
 def test_global_version(command):
     def commands(ctx):
-        assert ctx.pyenv(command) == "3.7.2"
+        assert ctx.pyenv(command) == ("3.7.2", "")
     run_pyenv_test({'global_ver': "3.7.2"}, commands)
 
 
 @pytest.mark.parametrize("command", ['version-name', 'vname'])
 def test_one_local_version(command):
     def commands(ctx):
-        assert ctx.pyenv(command) == "3.9.1"
+        assert ctx.pyenv(command) == ("3.9.1", "")
     settings = {
         'global_ver': "3.7.2",
         'local_ver': "3.9.1"
@@ -57,7 +66,7 @@ def commands(ctx):
 @pytest.mark.parametrize("command", ['version-name', 'vname'])
 def test_shell_version(command):
     def commands(ctx):
-        assert ctx.pyenv(command) == "3.9.2"
+        assert ctx.pyenv(command) == ("3.9.2", "")
     settings = {
         'global_ver': "3.7.5",
         'local_ver': "3.8.6",
@@ -69,7 +78,7 @@ def commands(ctx):
 @pytest.mark.parametrize("command", ['version-name', 'vname'])
 def test_many_local_versions(command):
     def commands(ctx):
-        assert ctx.pyenv(command) == "3.8.8\r\n3.9.1"
+        assert ctx.pyenv(command) == ("3.8.8\r\n3.9.1", "")
     settings = {
         'global_ver': "3.7.2",
         'local_ver': "3.8.8\n3.9.1\n"
diff --git a/tests/test_pyenv_feature_versions.py b/tests/test_pyenv_feature_versions.py
index 1605ee0b..80e6f1e0 100644
--- a/tests/test_pyenv_feature_versions.py
+++ b/tests/test_pyenv_feature_versions.py
@@ -6,22 +6,28 @@
 class TestPyenvFeatureVersions(TestPyenvBase):
     def test_list_no_version(self, setup):
         def commands(ctx):
-            assert ctx.pyenv("versions") == ""
+            assert ctx.pyenv("versions") == ("", "")
         run_pyenv_test({}, commands)
 
     def test_list_all_versions(self, setup):
         def commands(ctx):
-            output = ctx.pyenv("versions")
+            stdout, stderr = ctx.pyenv("versions")
             for v in versions:
-                assert v in output
+                assert v in stdout
+            assert stderr == ""
         versions = ['3.7.4', '3.8.5']
         run_pyenv_test({'versions': versions}, commands)
 
     def test_list_current_global_version(self, setup):
         def commands(ctx):
-            assert ctx.pyenv("versions") == (f"  3.6.5\r\n"
-                                             f"* 3.7.7 (set by {ctx.pyenv_path}\\version)\r\n"
-                                             f"  3.9.1")
+            assert ctx.pyenv("versions") == (
+                (
+                    f"  3.6.5\r\n"
+                    f"* 3.7.7 (set by {ctx.pyenv_path}\\version)\r\n"
+                    f"  3.9.1"
+                ),
+                ""
+            )
         settings = {
             'versions': ["3.6.5", "3.7.7", "3.9.1"],
             'global_ver': "3.7.7"
@@ -30,9 +36,14 @@ def commands(ctx):
 
     def test_list_current_local_version(self, setup):
         def commands(ctx):
-            assert ctx.pyenv("versions") == (f"* 3.6.5 (set by {ctx.local_path}\\.python-version)\r\n"
-                                             f"  3.7.7\r\n"
-                                             f"  3.9.1")
+            assert ctx.pyenv("versions") == (
+                (
+                    f"* 3.6.5 (set by {ctx.local_path}\\.python-version)\r\n"
+                    f"  3.7.7\r\n"
+                    f"  3.9.1"
+                ),
+                ""
+            )
         settings = {
             'versions': ["3.6.5", "3.7.7", "3.9.1"],
             'global_ver': "3.7.7",
@@ -42,9 +53,14 @@ def commands(ctx):
 
     def test_list_current_local_many_versions(self, setup):
         def commands(ctx):
-            assert ctx.pyenv("versions") == (f"* 3.6.5 (set by {ctx.local_path}\\.python-version)\r\n"
-                                             f"* 3.7.7 (set by {ctx.local_path}\\.python-version)\r\n"
-                                             f"  3.9.1")
+            assert ctx.pyenv("versions") == (
+                (
+                    f"* 3.6.5 (set by {ctx.local_path}\\.python-version)\r\n"
+                    f"* 3.7.7 (set by {ctx.local_path}\\.python-version)\r\n"
+                    f"  3.9.1"
+                ),
+                ""
+            )
         settings = {
             'versions': ["3.6.5", "3.7.7", "3.9.1"],
             'global_ver': "3.9.1",
@@ -54,9 +70,14 @@ def commands(ctx):
 
     def test_list_current_shell_version(self, setup):
         def commands(ctx):
-            assert ctx.pyenv("versions") == (f"  3.6.5\r\n"
-                                             f"  3.7.7\r\n"
-                                             f"* 3.9.1 (set by %PYENV_VERSION%)")
+            assert ctx.pyenv("versions") == (
+                (
+                    f"  3.6.5\r\n"
+                    f"  3.7.7\r\n"
+                    f"* 3.9.1 (set by %PYENV_VERSION%)"
+                ),
+                ""
+            )
         settings = {
             'versions': ["3.6.5", "3.7.7", "3.9.1"],
             'global_ver': "3.7.7",
@@ -67,9 +88,14 @@ def commands(ctx):
 
     def test_list_uninstalled_current_global_version(self, setup):
         def commands(ctx):
-            assert ctx.pyenv("versions") == (f"  3.6.5\r\n"
-                                             f"  3.7.7\r\n"
-                                             f"  3.9.1")
+            assert ctx.pyenv("versions") == (
+                (
+                    f"  3.6.5\r\n"
+                    f"  3.7.7\r\n"
+                    f"  3.9.1"
+                ),
+                ""
+            )
         settings = {
             'versions': ["3.6.5", "3.7.7", "3.9.1"],
             'global_ver': "3.7.5"
@@ -78,9 +104,14 @@ def commands(ctx):
 
     def test_list_uninstalled_local_version(self, setup):
         def commands(ctx):
-            assert ctx.pyenv("versions") == (f"  3.6.5\r\n"
-                                             f"  3.7.7\r\n"
-                                             f"  3.9.1")
+            assert ctx.pyenv("versions") == (
+                (
+                    f"  3.6.5\r\n"
+                    f"  3.7.7\r\n"
+                    f"  3.9.1"
+                ),
+                ""
+            )
         settings = {
             'versions': ["3.6.5", "3.7.7", "3.9.1"],
             'global_ver': "3.7.7",
@@ -90,9 +121,14 @@ def commands(ctx):
 
     def test_list_uninstalled_shell_version(self, setup):
         def commands(ctx):
-            assert ctx.pyenv("versions") == (f"  3.6.5\r\n"
-                                             f"  3.7.7\r\n"
-                                             f"  3.9.1")
+            assert ctx.pyenv("versions") == (
+                (
+                    f"  3.6.5\r\n"
+                    f"  3.7.7\r\n"
+                    f"  3.9.1"
+                ),
+                ""
+            )
         settings = {
             'versions': ["3.6.5", "3.7.7", "3.9.1"],
             'global_ver': "3.7.7",
diff --git a/tests/test_pyenv_feature_whence.py b/tests/test_pyenv_feature_whence.py
index dd2bcb72..b3f75977 100644
--- a/tests/test_pyenv_feature_whence.py
+++ b/tests/test_pyenv_feature_whence.py
@@ -12,26 +12,26 @@ def pyenv_whence_usage():
 class TestPyenvFeatureWhence(TestPyenvBase):
     def test_whence_no_arg(self, setup):
         def commands(ctx):
-            assert ctx.pyenv("whence") == pyenv_whence_usage()
-            assert ctx.pyenv(["whence", "--help"]) == pyenv_whence_usage()
-            assert ctx.pyenv(["--help", "whence"]) == pyenv_whence_usage()
-            assert ctx.pyenv(["help", "whence"]) == pyenv_whence_usage()
+            assert ctx.pyenv("whence") == (pyenv_whence_usage(), "")
+            assert ctx.pyenv(["whence", "--help"]) == (pyenv_whence_usage(), "")
+            assert ctx.pyenv(["--help", "whence"]) == (pyenv_whence_usage(), "")
+            assert ctx.pyenv(["help", "whence"]) == (pyenv_whence_usage(), "")
         run_pyenv_test({'versions': ['3.7.7']}, commands)
 
     def test_whence_major(self, setup):
         def commands(ctx):
             for name in ['python', 'python3', 'pip3']:
-                assert ctx.pyenv(["whence", name]) == '3.7.7\r\n3.8.2\r\n3.8.7\r\n3.9.1'
+                assert ctx.pyenv(["whence", name]) == ('3.7.7\r\n3.8.2\r\n3.8.7\r\n3.9.1', "")
         run_pyenv_test({'versions': ['3.7.7', '3.8.2', '3.8.7', '3.9.1']}, commands)
 
     def test_whence_major_minor(self, setup):
         def commands(ctx):
             for name in ['python38', 'python3.8', 'pip3.8']:
-                assert ctx.pyenv(["whence", name]) == '3.8.2\r\n3.8.7'
+                assert ctx.pyenv(["whence", name]) == ('3.8.2\r\n3.8.7', "")
         run_pyenv_test({'versions': ['3.7.7', '3.8.2', '3.8.7', '3.9.1']}, commands)
 
     def test_whence_not_found(self, setup):
         def commands(ctx):
             for name in ['unknown3.8']:
-                assert ctx.pyenv(["whence", name]) == ""
+                assert ctx.pyenv(["whence", name]) == ("", "")
         run_pyenv_test({'versions': ['3.7.7', '3.8.2', '3.8.7', '3.9.1']}, commands)
diff --git a/tests/test_pyenv_feature_which.py b/tests/test_pyenv_feature_which.py
index 129eac88..81afbf3a 100644
--- a/tests/test_pyenv_feature_which.py
+++ b/tests/test_pyenv_feature_which.py
@@ -17,17 +17,19 @@ def pyenv_which_usage():
 class TestPyenvFeatureWhich(TestPyenvBase):
     def test_which_no_arg(self, setup):
         def commands(ctx):
-            assert ctx.pyenv("which") == pyenv_which_usage()
-            assert ctx.pyenv(["which", "--help"]) == pyenv_which_usage()
-            assert ctx.pyenv(["--help", "which"]) == pyenv_which_usage()
-            assert ctx.pyenv(["help", "which"]) == pyenv_which_usage()
+            assert ctx.pyenv("which") == (pyenv_which_usage(), "")
+            assert ctx.pyenv(["which", "--help"]) == (pyenv_which_usage(), "")
+            assert ctx.pyenv(["--help", "which"]) == (pyenv_which_usage(), "")
+            assert ctx.pyenv(["help", "which"]) == (pyenv_which_usage(), "")
         run_pyenv_test({'versions': ['3.7.7']}, commands)
 
     def test_which_exists_is_global(self, setup):
         def commands(ctx):
             for name in ['python', 'python3', 'python38', 'pip3', 'pip3.8']:
                 sub_dir = '' if 'python' in name else 'Scripts\\'
-                assert_paths_equal(ctx.pyenv(["which", name]), rf'{ctx.pyenv_path}\versions\3.8.5\{sub_dir}{name}.exe')
+                stdout, stderr = ctx.pyenv(["which", name])
+                assert_paths_equal(stdout, rf'{ctx.pyenv_path}\versions\3.8.5\{sub_dir}{name}.exe')
+                assert stderr == ""
         settings = {
             'versions': ['3.8.5'],
             'global_ver': '3.8.5'
@@ -38,7 +40,9 @@ def test_which_exists_is_local(self, setup):
         def commands(ctx):
             for name in ['python', 'python3', 'python38', 'pip3', 'pip3.8']:
                 sub_dir = '' if 'python' in name else 'Scripts\\'
-                assert_paths_equal(ctx.pyenv(["which", name]), rf'{ctx.pyenv_path}\versions\3.8.5\{sub_dir}{name}.exe')
+                stdout, stderr = ctx.pyenv(["which", name])
+                assert_paths_equal(stdout, rf'{ctx.pyenv_path}\versions\3.8.5\{sub_dir}{name}.exe')
+                assert stderr == ""
         settings = {
             'versions': ['3.8.5'],
             'local_ver': '3.8.5'
@@ -49,38 +53,45 @@ def test_which_exists_is_shell(self, setup):
         def commands(ctx):
             for name in ['python', 'python3', 'python38', 'pip3', 'pip3.8']:
                 sub_dir = '' if 'python' in name else 'Scripts\\'
-                assert_paths_equal(ctx.pyenv(["which", name]), rf'{ctx.pyenv_path}\versions\3.8.5\{sub_dir}{name}.exe')
+                stdout, stderr = ctx.pyenv(["which", name])
+                assert_paths_equal(stdout, rf'{ctx.pyenv_path}\versions\3.8.5\{sub_dir}{name}.exe')
+                assert stderr == ""
         with TemporaryEnvironment({"PYENV_VERSION": "3.8.5"}):
             run_pyenv_test({'versions': ['3.8.5']}, commands)
 
     def test_which_exists_is_global_not_installed(self, setup):
         def commands(ctx):
             for name in ['python', 'python3', 'python38', 'pip3', 'pip3.8']:
-                assert ctx.pyenv(["which", name]) == "pyenv: version `3.8.5' is not installed (set by 3.8.5)"
+                assert ctx.pyenv(["which", name]) == ("pyenv: version `3.8.5' is not installed (set by 3.8.5)", "")
         run_pyenv_test({'global_ver': '3.8.5'}, commands)
 
     def test_which_exists_is_local_not_installed(self, setup):
         def commands(ctx):
             for name in ['python', 'python3', 'python38', 'pip3', 'pip3.8']:
-                assert ctx.pyenv(["which", name]) == "pyenv: version `3.8.5' is not installed (set by 3.8.5)"
+                assert ctx.pyenv(["which", name]) == ("pyenv: version `3.8.5' is not installed (set by 3.8.5)", "")
         run_pyenv_test({'local_ver': '3.8.5'}, commands)
 
     def test_which_exists_is_shell_not_installed(self, setup):
         def commands(ctx):
             for name in ['python', 'python3', 'python38', 'pip3', 'pip3.8']:
-                assert ctx.pyenv(["which", name]) == "pyenv: version `3.8.5' is not installed (set by 3.8.5)"
+                assert ctx.pyenv(["which", name]) == ("pyenv: version `3.8.5' is not installed (set by 3.8.5)", "")
         with TemporaryEnvironment({"PYENV_VERSION": "3.8.5"}):
             run_pyenv_test({}, commands)
 
     def test_which_exists_is_global_other_version(self, setup):
         def commands(ctx):
             for name in ['python38', 'pip3.8']:
-                assert ctx.pyenv(["which", name]) == (f"pyenv: {name}: command not found\r\n"
-                                                      f"\r\n"
-                                                      f"The '{name}' command exists in these Python versions:\r\n"
-                                                      f"  3.8.2\r\n"
-                                                      f"  3.8.6\r\n"
-                                                      f"  ")
+                assert ctx.pyenv(["which", name]) == (
+                    (
+                        f"pyenv: {name}: command not found\r\n"
+                        f"\r\n"
+                        f"The '{name}' command exists in these Python versions:\r\n"
+                        f"  3.8.2\r\n"
+                        f"  3.8.6\r\n"
+                        f"  "
+                    ),
+                    ""
+                )
         settings = {
             'versions': ['3.8.2', '3.8.6', '3.9.1'],
             'global_ver': '3.9.1'
@@ -90,12 +101,17 @@ def commands(ctx):
     def test_which_exists_is_local_other_version(self, setup):
         def commands(ctx):
             for name in ['python38', 'pip3.8']:
-                assert ctx.pyenv(["which", name]) == (f"pyenv: {name}: command not found\r\n"
-                                                      f"\r\n"
-                                                      f"The '{name}' command exists in these Python versions:\r\n"
-                                                      f"  3.8.2\r\n"
-                                                      f"  3.8.6\r\n"
-                                                      f"  ")
+                assert ctx.pyenv(["which", name]) == (
+                    (
+                        f"pyenv: {name}: command not found\r\n"
+                        f"\r\n"
+                        f"The '{name}' command exists in these Python versions:\r\n"
+                        f"  3.8.2\r\n"
+                        f"  3.8.6\r\n"
+                        f"  "
+                    ),
+                    ""
+                )
         settings = {
             'versions': ['3.8.2', '3.8.6', '3.9.1'],
             'local_ver': '3.9.1'
@@ -105,12 +121,17 @@ def commands(ctx):
     def test_which_exists_is_shell_other_version(self, setup):
         def commands(ctx):
             for name in ['python38', 'python3.8', 'pip3.8']:
-                assert ctx.pyenv(["which", name]) == (f"pyenv: {name}: command not found\r\n"
-                                                      f"\r\n"
-                                                      f"The '{name}' command exists in these Python versions:\r\n"
-                                                      f"  3.8.2\r\n"
-                                                      f"  3.8.6\r\n"
-                                                      f"  ")
+                assert ctx.pyenv(["which", name]) == (
+                    (
+                        f"pyenv: {name}: command not found\r\n"
+                        f"\r\n"
+                        f"The '{name}' command exists in these Python versions:\r\n"
+                        f"  3.8.2\r\n"
+                        f"  3.8.6\r\n"
+                        f"  "
+                    ),
+                    ""
+                )
         settings = {
             'versions': ['3.8.2', '3.8.6', '3.9.1'],
         }
@@ -120,7 +141,7 @@ def commands(ctx):
     def test_which_command_not_found(self, setup):
         def commands(ctx):
             for name in ['unknown3.8']:
-                assert ctx.pyenv(["which", name]) == f"pyenv: {name}: command not found"
+                assert ctx.pyenv(["which", name]) == (f"pyenv: {name}: command not found", "")
         settings = {
             'versions': ['3.8.6'],
             'global_ver': '3.8.6'
@@ -130,9 +151,14 @@ def commands(ctx):
     def test_which_no_version_defined(self, setup):
         def commands(ctx):
             for name in ['python']:
-                assert ctx.pyenv(["which", name]) == ("No global python version has been set yet. "
-                                                      "Please set the global version by typing:\r\n"
-                                                      "pyenv global 3.7.2")
+                assert ctx.pyenv(["which", name]) == (
+                    (
+                        "No global python version has been set yet. "
+                        "Please set the global version by typing:\r\n"
+                        "pyenv global 3.7.2"
+                    ),
+                    ""
+                )
         run_pyenv_test({'versions': ['3.8.6']}, commands)
 
     def test_which_many_local_versions(self, setup):
@@ -144,7 +170,9 @@ def commands(ctx):
                 ('pip3.8', r'3.8.2\Scripts\pip3.8.exe'),
             ]
             for (name, path) in cases:
-                assert_paths_equal(ctx.pyenv(["which", name]), rf'{ctx.pyenv_path}\versions\{path}')
+                stdout, stderr = ctx.pyenv(["which", name])
+                assert_paths_equal(stdout, rf'{ctx.pyenv_path}\versions\{path}')
+                assert stderr == ""
         settings = {
             'versions': ['3.7.7', '3.8.2', '3.9.1'],
             'local_ver': '3.7.7\n3.8.2\n'
diff --git a/tests/test_pyenv_helpers.py b/tests/test_pyenv_helpers.py
index e879254c..031abdb0 100644
--- a/tests/test_pyenv_helpers.py
+++ b/tests/test_pyenv_helpers.py
@@ -67,6 +67,8 @@ def create_pythons(path):
         os.mkdir(path)
         for exe in python_exes([f'{ver.major}', f'{ver.major}{ver.minor}', f'{ver.major}.{ver.minor}']):
             touch(path.joinpath(exe))
+        with open(path.joinpath('version.bat'), "w") as batch:
+            print(f"@echo {ver.major}.{ver.minor}.{ver.micro}", file=batch)
         return path
 
     def create_scripts(path):
@@ -75,6 +77,8 @@ def create_scripts(path):
             touch(path.joinpath(exe))
         with open(path.joinpath('hello.bat'), "w") as batch:
             print("@echo Hello world!", file=batch)
+        with open(path.joinpath('version.bat'), "w") as batch:
+            print(f"@echo {ver.major}.{ver.minor}.{ver.micro}", file=batch)
 
     for v in versions:
         ver = version.parse(v)
@@ -89,7 +93,8 @@ def create_scripts(path):
 
 
 class PyenvContext:
-    def __init__(self, pyenv, pyenv_path, local_path):
+    def __init__(self, exec, pyenv, pyenv_path, local_path):
+        self.exec = exec
         self.pyenv = pyenv
         self.pyenv_path = pyenv_path
         self.local_path = local_path
@@ -106,22 +111,24 @@ def __init__(self, settings):
         self.prev_cwd = os.getcwd()
         os.chdir(local_path)
         self.bat = Path(pyenv_path, r'bin\pyenv.bat')
-        self.context = PyenvContext(self.pyenv, pyenv_path, local_path)
+        self.context = PyenvContext(self.exec, self.pyenv, pyenv_path, local_path)
 
     def pyenv(self, pyenv_args=None):
-        args = ['cmd', '/d', '/c', 'call', str(self.bat)]
+        args = [str(self.bat)]
         if pyenv_args is not None:
             if isinstance(pyenv_args, list):
                 args = args + pyenv_args
             else:
                 args.append(pyenv_args)
+        return self.exec(args)
+
+    def exec(self, args):
+        args = ['cmd', '/d', '/c', 'call', *args]
         result = subprocess.run(args, shell=False, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
         stderr = str(result.stderr, "utf-8").strip()
-        if stderr != "":
-            print(f'stderr: {stderr}')
         # \x0c: generated by cls in cmd AutoRun
         stdout = str(result.stdout, "utf-8").strip("\r\n\x0c")
-        return stdout
+        return stdout, stderr
 
     def cleanup(self):
         os.chdir(self.prev_cwd)
@@ -135,32 +142,8 @@ def __exit__(self, exc_type, exc_val, exc_tb):
 
 
 def run_pyenv_test(settings, commands):
-    with tempfile.TemporaryDirectory() as tmp_path:
-        settings['pyenv_path'] = pyenv_path = Path(tmp_path, 'pyenv')
-        settings['local_path'] = local_path = Path(tmp_path, 'local')
-        os.mkdir(pyenv_path)
-        os.mkdir(local_path)
-        pyenv_setup(settings)
-        with working_directory(local_path):
-            bat = Path(pyenv_path, r'bin\pyenv.bat')
-
-            def pyenv(pyenv_args=None):
-                args = ['cmd', '/d', '/c', 'call', str(bat)]
-                if pyenv_args is not None:
-                    if isinstance(pyenv_args, list):
-                        args = args + pyenv_args
-                    else:
-                        args.append(pyenv_args)
-                result = subprocess.run(args, shell=False, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
-                stderr = str(result.stderr, "utf-8").strip()
-                if stderr != "":
-                    print(stderr)
-                # \x0c: generated by cls in cmd AutoRun
-                stdout = str(result.stdout, "utf-8").strip("\r\n\x0c")
-                return stdout
-
-            context = PyenvContext(pyenv, pyenv_path, local_path)
-            commands(context)
+    with TempPyEnv(settings) as context:
+        commands(context)
 
 
 def not_installed_output(ver):