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

Issue:
Rehash: All installed versions, tests & more shims
- rehash all installed versions
- add unit tests
- install more shims for pythonX, pythonXY, pythonwX & pythonwXY

![image](https://user-images.githubusercontent.com/626824/111915726-020c4800-8a78-11eb-9644-53e25814ec2d.png)

![image](https://user-images.githubusercontent.com/626824/111914794-d6875e80-8a73-11eb-83f1-c9c97569cbd6.png)



Discussion / hints:

Fix patch:
diff --git a/.gitignore b/.gitignore
index 783f6500..c01c7c9e 100644
--- a/.gitignore
+++ b/.gitignore
@@ -112,3 +112,6 @@ exec.bat
 # PyCharm
 .idea/
 
+# git
+*.orig
+
diff --git a/pyenv-win/libexec/libs/pyenv-lib.vbs b/pyenv-win/libexec/libs/pyenv-lib.vbs
index d9ceff39..096af74b 100644
--- a/pyenv-win/libexec/libs/pyenv-lib.vbs
+++ b/pyenv-win/libexec/libs/pyenv-lib.vbs
@@ -118,6 +118,19 @@ Function GetCurrentVersionNoError()
     GetCurrentVersionNoError = str
 End Function
 
+Function GetInstalledVersions()
+    ' WScript.echo "kkotari: pyenv-lib.vbs get installed versions..!"
+    Dim rootBinDir, winBinDir, version, versions()
+    ReDim Preserve versions(0)
+    Set rootBinDir = objfs.GetFolder(strDirVers)
+    For Each winBinDir in rootBinDir.SubFolders
+        version = winBinDir.Name
+        ReDim Preserve versions(UBound(versions) + 1)
+        versions(UBound(versions)) = version
+    Next
+    GetInstalledVersions = versions
+End Function
+
 Function IsVersion(version)
     ' WScript.echo "kkotari: pyenv-lib.vbs is version..!"
     Dim re
@@ -187,23 +200,31 @@ End Function
 ' pyenv - bin - windows
 Sub WriteWinScript(baseName, strDirBin)
     ' WScript.echo "kkotari: pyenv-lib.vbs write win script..!"
-    With objfs.CreateTextFile(strDirShims &"\"& baseName &".bat")
-        .WriteLine("@echo off")
-        .WriteLine("setlocal")
-        .WriteLine("chcp 1250 > NUL")
-        .WriteLine("pyenv exec "&strDirBin&"%~n0 %*")
-        .Close
-    End With
+    Dim filespec
+    filespec = strDirShims &"\"& baseName &".bat"
+    If Not objfs.FileExists(filespec) Then
+        With objfs.CreateTextFile(filespec)
+            .WriteLine("@echo off")
+            .WriteLine("setlocal")
+            .WriteLine("chcp 1250 > NUL")
+            .WriteLine("pyenv exec "&strDirBin&"%~n0 %*")
+            .Close
+        End With
+    End If
 End Sub
 
 ' pyenv - bin - linux
 Sub WriteLinuxScript(baseName, strDirBin)
     ' WScript.echo "kkotari: pyenv-lib.vbs write linux script..!"
-    With objfs.CreateTextFile(strDirShims &"\"& baseName)
-        .WriteLine("#!/bin/sh")
-        .WriteLine("pyenv exec "&strDirBin&"$(basename $0) ""$@""")
-        .Close
-    End With
+    Dim filespec
+    filespec = strDirShims &"\"& baseName
+    If Not objfs.FileExists(filespec) Then
+        With objfs.CreateTextFile(filespec)
+            .WriteLine("#!/bin/sh")
+            .WriteLine("pyenv exec "&strDirBin&"$(basename $0) ""$@""")
+            .Close
+        End With
+    End If
 End Sub
 
 ' pyenv rehash
@@ -220,34 +241,32 @@ Sub Rehash()
     Dim winBinDir, nixBinDir
     Dim exts
     Dim baseName
-    version = GetCurrentVersionNoError()
-    If IsNull(version) Then Exit Sub
 
-    winBinDir = strDirVers &"\"& version(0)
-    If Not objfs.FolderExists(winBinDir) Then Exit Sub
+    For Each version In GetInstalledVersions()
+        winBinDir = strDirVers &"\"& version
+        nixBinDir = "/"& Replace(Replace(winBinDir, ":", ""), "\", "/")
+        Set exts = GetExtensionsNoPeriod(True)
 
-    nixBinDir = "/"& Replace(Replace(winBinDir, ":", ""), "\", "/")
-    Set exts = GetExtensionsNoPeriod(True)
-
-    For Each file In objfs.GetFolder(winBinDir).Files
-        ' 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, ""
-        End If
-    Next
-
-    If objfs.FolderExists(winBinDir & "\Scripts") Then
-        For Each file In objfs.GetFolder(winBinDir & "\Scripts").Files
-            ' WScript.echo "kkotari: pyenv-lib.vbs rehash for winBinDir\Scripts"
+        For Each file In objfs.GetFolder(winBinDir).Files
+            ' WScript.echo "kkotari: pyenv-lib.vbs rehash for winBinDir"
             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
+
+        If objfs.FolderExists(winBinDir & "\Scripts") Then
+            For Each file In objfs.GetFolder(winBinDir & "\Scripts").Files
+                ' 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/"
+                End If
+            Next
+        End If
+    Next
 End Sub
 
 ' SYSTEM:PROCESSOR_ARCHITECTURE = AMD64 on 64-bit computers. (even when using 32-bit cmd.exe)
diff --git a/pyenv-win/libexec/pyenv-install.vbs b/pyenv-win/libexec/pyenv-install.vbs
index d4219abc..304b46dd 100644
--- a/pyenv-win/libexec/pyenv-install.vbs
+++ b/pyenv-win/libexec/pyenv-install.vbs
@@ -123,6 +123,21 @@ Function deepExtract(params)
             Exit Function
         End If
     End If
+
+    ' Add pythonX & pythonXY exe
+    ' Windows try to execute pythonX.Y file (considers Y as en extension)
+    ' It requires explicit .bat extension to work ()pythonX.Y.bat)
+    ' That's why we use the pattern pythonXY
+    Dim version, pythonExe, pythonwExe, major, majorMinor
+    version = params(LV_Code)
+    pythonExe = installPath &"\python.exe"
+    pythonwExe = installPath &"\pythonw.exe"
+    major = Left(version, 1)
+    majorMinor = major & Mid(version, 3, 1)
+    objfs.CopyFile pythonExe, installPath &"\python"& major &".exe"
+    objfs.CopyFile pythonExe, installPath &"\python"& majorMinor &".exe"
+    objfs.CopyFile pythonwExe, installPath &"\pythonw"& major &".exe"
+    objfs.CopyFile pythonwExe, installPath &"\pythonw"& majorMinor &".exe"
 End Function
 
 Sub extract(params)
diff --git a/pyenv-win/libexec/pyenv.vbs b/pyenv-win/libexec/pyenv.vbs
index 0b71bfa1..f9fff762 100644
--- a/pyenv-win/libexec/pyenv.vbs
+++ b/pyenv-win/libexec/pyenv.vbs
@@ -180,7 +180,7 @@ Sub CommandWhich(arg)
     version = getCommandOutput("cscript //Nologo "& WScript.ScriptFullName &" whence "& program)
     If Trim(version) <> "" Then
         WScript.Echo
-        WScript.Echo "The `"& arg(1) &"' command exists in these Python versions:"
+        WScript.Echo "The '"& arg(1) &"' command exists in these Python versions:"
         WScript.Echo "  "& Replace(version, vbCrLf, vbCrLf &"  ")
     End If
 
diff --git a/requirements_dev.txt b/requirements_dev.txt
index 6d6bb689..6cbe087e 100644
--- a/requirements_dev.txt
+++ b/requirements_dev.txt
@@ -5,4 +5,5 @@ coveralls==1.7.0
 # when Travis CI install requirements_dev.txt
 # See https://youtrack.jetbrains.com/issue/PY-40980
 coverage==4.5.4
-pathlib==1.0.1
\ No newline at end of file
+pathlib==1.0.1
+tempenv==0.2.0
\ No newline at end of file


Test patch:
diff --git a/tests/test_pyenv_feature_commands.py b/tests/test_pyenv_feature_commands.py
index 1b5abd79..9ff7bb3b 100644
--- a/tests/test_pyenv_feature_commands.py
+++ b/tests/test_pyenv_feature_commands.py
@@ -1,6 +1,7 @@
 import subprocess
 from test_pyenv import TestPyenvBase
 
+
 class TestPyenvFeatureCommands(TestPyenvBase):
     def test_check_pyenv_commands_list(self, setup):
         # TODO: assert the list of commands
@@ -11,4 +12,4 @@ def test_check_pyenv_commands_list(self, setup):
     def test_check_pyenv_commands_help(self, setup):
         # TODO: assert the help result
         pass
-    
+
diff --git a/tests/test_pyenv_feature_duplicate.py b/tests/test_pyenv_feature_duplicate.py
index 48760954..c7bb99dc 100644
--- a/tests/test_pyenv_feature_duplicate.py
+++ b/tests/test_pyenv_feature_duplicate.py
@@ -1,6 +1,7 @@
 import subprocess
 from test_pyenv import TestPyenvBase
 
+
 class TestPyenvFeatureDuplicate(TestPyenvBase):
     def test_check_pyenv_duplicate(self, setup):
         # TODO: assert the list of commands
@@ -11,4 +12,4 @@ def test_check_pyenv_duplicate(self, setup):
     def test_check_pyenv_commands_help(self, setup):
         # TODO: assert the help result
         pass
-    
+
diff --git a/tests/test_pyenv_feature_exec.py b/tests/test_pyenv_feature_exec.py
index 96a3c8c0..3523a068 100644
--- a/tests/test_pyenv_feature_exec.py
+++ b/tests/test_pyenv_feature_exec.py
@@ -1,5 +1,6 @@
 from test_pyenv import TestPyenvBase
 
+
 class TestPyenvFeatureExec(TestPyenvBase):
     pass
-    
+
diff --git a/tests/test_pyenv_feature_global.py b/tests/test_pyenv_feature_global.py
index 5fe27351..ecaa5ec7 100644
--- a/tests/test_pyenv_feature_global.py
+++ b/tests/test_pyenv_feature_global.py
@@ -1,55 +1,44 @@
-import subprocess
-import tempfile
-from pathlib import Path
 from test_pyenv import TestPyenvBase
-from test_pyenv_helpers import install_pyenv, temp_pyenv, working_directory
+from test_pyenv_helpers import not_installed_output, run_pyenv_test
 
 
 class TestPyenvFeatureGlobal(TestPyenvBase):
     def test_no_global_version(self, setup):
-        with temp_pyenv("global") as output:
-            assert output == "no global version configured"
+        def commands(ctx):
+            assert ctx.pyenv("global") == "no global version configured"
+        run_pyenv_test({}, commands)
 
     def test_global_version_defined(self, setup):
-        with temp_pyenv("global", global_ver="3.8.9") as output:
-            assert output == "3.8.9"
+        def commands(ctx):
+            assert ctx.pyenv("global") == "3.8.9"
+        run_pyenv_test({'global_ver': "3.8.9"}, commands)
 
     def test_global_set_installed_version(self, setup):
-        with tempfile.TemporaryDirectory() as tmp_path:
-            install_pyenv(tmp_path, ["3.7.7", "3.8.9"], "3.8.9")
-            with working_directory(tmp_path):
-                bat = Path(tmp_path, r'bin\pyenv.bat')
-
-                def pyenv_global(option=None):
-                    args = ['cmd', '/d', '/c', f'call {bat}', "global"]
-                    if option is not None:
-                        args.append(option)
-                    result = subprocess.run(args, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
-                    output = str(result.stdout, "utf-8").strip()
-                    return output
-
-                assert pyenv_global("3.7.7") == ""
-                assert pyenv_global() == "3.7.7"
+        def commands(ctx):
+            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"
+        }
+        run_pyenv_test(settings, commands)
 
     def test_global_set_unknown_version(self, setup):
-        with temp_pyenv("global", "3.7.8", ["3.8.9"], "3.8.9") as output:
-            assert output == ("pyenv specific python requisite didn't meet. "
-                              "Project is using different version of python.\r\n"
-                              "Install python '3.7.8' by typing: 'pyenv install 3.7.8'")
+        def commands(ctx):
+            assert ctx.pyenv(["global", "3.7.8"]) == not_installed_output("3.7.8")
+        settings = {
+            'versions': ["3.8.9"],
+            'global_ver': "3.8.9",
+        }
+        run_pyenv_test(settings, commands)
 
     def test_global_unset(self, setup):
-        with tempfile.TemporaryDirectory() as tmp_path:
-            install_pyenv(tmp_path, ["3.8.9"], "3.8.9")
-            with working_directory(tmp_path):
-                bat = Path(tmp_path, r'bin\pyenv.bat')
-
-                def pyenv_global(option=None):
-                    args = ['cmd', '/d', '/c', f'call {bat}', "global"]
-                    if option is not None:
-                        args.append(option)
-                    result = subprocess.run(args, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
-                    output = str(result.stdout, "utf-8").strip()
-                    return output
+        def commands(ctx):
+            assert ctx.pyenv(["global", "--unset"]) == ""
+            assert ctx.pyenv("global") == "no global version configured"
+        settings = {
+            'versions': ["3.8.9"],
+            'global_ver': "3.8.9",
+        }
+        run_pyenv_test(settings, commands)
 
-                assert pyenv_global("--unset") == ""
-                assert pyenv_global() == "no global version configured"
diff --git a/tests/test_pyenv_feature_install.py b/tests/test_pyenv_feature_install.py
index d85079b5..79f20cde 100644
--- a/tests/test_pyenv_feature_install.py
+++ b/tests/test_pyenv_feature_install.py
@@ -1,6 +1,7 @@
 import subprocess
 from test_pyenv import TestPyenvBase
 
+
 class TestPyenvFeatureInstall(TestPyenvBase):
     def test_check_pyenv_install_list(self, setup):
         result = subprocess.run(['pyenv', 'install', '-l'], shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
@@ -32,5 +33,4 @@ def test_check_pyenv_install_list(self, setup):
     def test_check_pyenv_installation(self, setup):
         # TODO: tracking the logs of installation and checking the folder
         pass
-        
-    
+
diff --git a/tests/test_pyenv_feature_local.py b/tests/test_pyenv_feature_local.py
index e3e44397..eba0eaf1 100644
--- a/tests/test_pyenv_feature_local.py
+++ b/tests/test_pyenv_feature_local.py
@@ -1,5 +1,29 @@
 from test_pyenv import TestPyenvBase
+from test_pyenv_helpers import not_installed_output, run_pyenv_test
+
 
 class TestPyenvFeatureLocal(TestPyenvBase):
-    pass
-    
+    def test_no_local_version(self, setup):
+        def commands(ctx):
+            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"
+        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"
+        settings = {
+            'versions': ["3.7.7", "3.8.9"],
+            'local_ver': "3.8.9",
+        }
+        run_pyenv_test(settings, commands)
+
+    def test_local_set_unknown_version(self, setup):
+        def commands(ctx):
+            assert ctx.pyenv(["local", "3.7.8"]) == not_installed_output("3.7.8")
+        run_pyenv_test({'versions': ["3.8.9"]}, commands)
diff --git a/tests/test_pyenv_feature_rehash.py b/tests/test_pyenv_feature_rehash.py
index 818a4e51..c99d8407 100644
--- a/tests/test_pyenv_feature_rehash.py
+++ b/tests/test_pyenv_feature_rehash.py
@@ -1,5 +1,54 @@
+from packaging import version
+from pathlib import Path
+from tempenv import TemporaryEnvironment
 from test_pyenv import TestPyenvBase
+from test_pyenv_helpers import python_exes, script_exes, run_pyenv_test
+
+
+def assert_shims(pyenv_path, ver):
+    shims_path = Path(pyenv_path, 'shims')
+    ver = version.parse(ver)
+    suffixes = [f'{ver.major}', f'{ver.major}{ver.minor}']
+    all_exes = [Path(n).stem for n in list(python_exes(suffixes)) + list(script_exes(ver))]
+    all_shims = [n + s for n in all_exes for s in ['', '.bat']]
+    for s in all_shims:
+        assert shims_path.joinpath(s).is_file()
+
 
 class TestPyenvFeatureRehash(TestPyenvBase):
-    pass
-    
+    def test_rehash_global_version(self, setup):
+        def commands(ctx):
+            assert ctx.pyenv('rehash') == ""
+            assert_shims(ctx.pyenv_path, '3.8.6')
+            assert_shims(ctx.pyenv_path, '3.8.7')
+        settings = {
+            'versions': ['3.8.6', '3.8.7'],
+            'global_ver': '3.8.6',
+        }
+        run_pyenv_test(settings, commands)
+
+    def test_rehash_local_version(self, setup):
+        def commands(ctx):
+            assert ctx.pyenv('rehash') == ""
+            assert_shims(ctx.pyenv_path, '3.8.6')
+            assert_shims(ctx.pyenv_path, '3.9.1')
+        settings = {
+            'versions': ['3.8.6', '3.9.1'],
+            'global_ver': '3.8.6',
+            'local_ver': '3.9.1'
+        }
+        run_pyenv_test(settings, commands)
+
+    def test_rehash_shell_version(self, setup):
+        def commands(ctx):
+            assert ctx.pyenv('rehash') == ""
+        global_ver = '3.7.5'
+        local_ver = '3.8.6'
+        shell_ver = '3.9.1'
+        settings = {
+            'versions': [global_ver, local_ver, shell_ver],
+            'global_ver': global_ver,
+            'local_ver': local_ver
+        }
+        with TemporaryEnvironment({"PYENV_VERSION": shell_ver}):
+            run_pyenv_test(settings, commands)
diff --git a/tests/test_pyenv_feature_shell.py b/tests/test_pyenv_feature_shell.py
index 3979929c..f0252f97 100644
--- a/tests/test_pyenv_feature_shell.py
+++ b/tests/test_pyenv_feature_shell.py
@@ -1,5 +1,39 @@
+import subprocess
+from pathlib import Path
+from tempenv import TemporaryEnvironment
 from test_pyenv import TestPyenvBase
+from test_pyenv_helpers import not_installed_output, run_pyenv_test
+
 
 class TestPyenvFeatureShell(TestPyenvBase):
-    pass
-    
+    def test_no_shell_version(self, setup):
+        def commands(ctx):
+            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"
+        with TemporaryEnvironment({"PYENV_VERSION": "3.9.2"}):
+            run_pyenv_test({}, commands)
+
+    def test_shell_set_installed_version(self, setup):
+        def commands(ctx):
+            pyenv_bat = Path(ctx.pyenv_path, r'bin\pyenv.bat')
+            tmp_bat = str(Path(ctx.local_path, "tmp.bat"))
+            with open(tmp_bat, "w") as f:
+                # must chain commands because env var is lost when cmd ends
+                print(f'@call {pyenv_bat} shell 3.7.7 & call {pyenv_bat} shell', file=f)
+            args = ['cmd', '/d', '/c', 'call', tmp_bat]
+            result = subprocess.run(args, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+            output = str(result.stdout, "utf-8").strip()
+            assert output == "3.7.7"
+
+        with TemporaryEnvironment({"PYENV_VERSION": "3.8.9"}):
+            run_pyenv_test({'versions': ["3.7.7", "3.8.9"]}, commands)
+
+    def test_shell_set_unknown_version(self, setup):
+        def commands(ctx):
+            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_uninstall.py b/tests/test_pyenv_feature_uninstall.py
index 8cf771cc..dd34bcb5 100644
--- a/tests/test_pyenv_feature_uninstall.py
+++ b/tests/test_pyenv_feature_uninstall.py
@@ -1,5 +1,6 @@
 from test_pyenv import TestPyenvBase
 
+
 class TestPyenvFeatureUninstall(TestPyenvBase):
     pass
-    
+
diff --git a/tests/test_pyenv_feature_version.py b/tests/test_pyenv_feature_version.py
index 5a0d2e88..5ec81db3 100644
--- a/tests/test_pyenv_feature_version.py
+++ b/tests/test_pyenv_feature_version.py
@@ -1,5 +1,36 @@
+from tempenv import TemporaryEnvironment
 from test_pyenv import TestPyenvBase
+from test_pyenv_helpers import run_pyenv_test
+
 
 class TestPyenvFeatureVersion(TestPyenvBase):
-    pass
-    
+    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")
+        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)'
+        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)'
+        settings = {
+            'global_ver': "3.7.2",
+            'local_ver': "3.9.1"
+        }
+        run_pyenv_test(settings, commands)
+
+    def test_shell_version(self, setup):
+        def commands(ctx):
+            assert ctx.pyenv("version") == "3.9.2 (set by %PYENV_VERSION%)"
+        settings = {
+            'global_ver': "3.7.5",
+            'local_ver': "3.8.6",
+        }
+        with TemporaryEnvironment({"PYENV_VERSION": "3.9.2"}):
+            run_pyenv_test(settings, commands)
diff --git a/tests/test_pyenv_feature_versions.py b/tests/test_pyenv_feature_versions.py
index 1a95ff3e..d7285700 100644
--- a/tests/test_pyenv_feature_versions.py
+++ b/tests/test_pyenv_feature_versions.py
@@ -1,14 +1,90 @@
+from tempenv import TemporaryEnvironment
 from test_pyenv import TestPyenvBase
-from test_pyenv_helpers import temp_pyenv
+from test_pyenv_helpers import run_pyenv_test
 
 
 class TestPyenvFeatureVersions(TestPyenvBase):
     def test_list_no_version(self, setup):
-        with temp_pyenv("versions") as output:
-            assert output == ""
+        def commands(ctx):
+            assert ctx.pyenv("versions") == ""
+        run_pyenv_test({}, commands)
 
     def test_list_all_versions(self, setup):
-        versions = ['3.7.4', '3.8.5']
-        with temp_pyenv("versions", versions=versions) as output:
+        def commands(ctx):
+            output = ctx.pyenv("versions")
             for v in versions:
                 assert v in output
+        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")
+        settings = {
+            'versions': ["3.6.5", "3.7.7", "3.9.1"],
+            'global_ver': "3.7.7"
+        }
+        run_pyenv_test(settings, commands)
+
+    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")
+        settings = {
+            'versions': ["3.6.5", "3.7.7", "3.9.1"],
+            'global_ver': "3.7.7",
+            'local_ver': "3.6.5"
+        }
+        run_pyenv_test(settings, commands)
+
+    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%)")
+        settings = {
+            'versions': ["3.6.5", "3.7.7", "3.9.1"],
+            'global_ver': "3.7.7",
+            'local_ver': "3.6.5"
+        }
+        with TemporaryEnvironment({"PYENV_VERSION": "3.9.1"}):
+            run_pyenv_test(settings, commands)
+
+    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")
+        settings = {
+            'versions': ["3.6.5", "3.7.7", "3.9.1"],
+            'global_ver': "3.7.5"
+        }
+        run_pyenv_test(settings, commands)
+
+    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")
+        settings = {
+            'versions': ["3.6.5", "3.7.7", "3.9.1"],
+            'global_ver': "3.7.7",
+            'local_ver': "3.6.1"
+        }
+        run_pyenv_test(settings, commands)
+
+    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")
+        settings = {
+            'versions': ["3.6.5", "3.7.7", "3.9.1"],
+            'global_ver': "3.7.7",
+            'local_ver': "3.6.5"
+        }
+        with TemporaryEnvironment({"PYENV_VERSION": "3.9.2"}):
+            run_pyenv_test(settings, commands)
diff --git a/tests/test_pyenv_feature_whence.py b/tests/test_pyenv_feature_whence.py
index 9f221933..7629e3ab 100644
--- a/tests/test_pyenv_feature_whence.py
+++ b/tests/test_pyenv_feature_whence.py
@@ -1,5 +1,22 @@
 from test_pyenv import TestPyenvBase
+from test_pyenv_helpers import run_pyenv_test
+
 
 class TestPyenvFeatureWhence(TestPyenvBase):
-    pass
-    
+    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'
+        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', 'pip3.8']:
+                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 ['python3.8']:
+                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 12fd6fdb..4da8dbeb 100644
--- a/tests/test_pyenv_feature_which.py
+++ b/tests/test_pyenv_feature_which.py
@@ -1,5 +1,120 @@
+from tempenv import TemporaryEnvironment
 from test_pyenv import TestPyenvBase
+from test_pyenv_helpers import run_pyenv_test
+
 
 class TestPyenvFeatureWhich(TestPyenvBase):
-    pass
-    
+    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 ctx.pyenv(["which", name]) == rf'{ctx.pyenv_path}\versions\3.8.5\{sub_dir}{name}.exe' or \
+                    ctx.pyenv(["which", name]) == rf'{ctx.pyenv_path}\versions\3.8.5\{sub_dir}{name}.EXE'
+        settings = {
+            'versions': ['3.8.5'],
+            'global_ver': '3.8.5'
+        }
+        run_pyenv_test(settings, commands)
+
+    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 ctx.pyenv(["which", name]) == rf'{ctx.pyenv_path}\versions\3.8.5\{sub_dir}{name}.exe' or \
+                    ctx.pyenv(["which", name]) == rf'{ctx.pyenv_path}\versions\3.8.5\{sub_dir}{name}.EXE'
+        settings = {
+            'versions': ['3.8.5'],
+            'local_ver': '3.8.5'
+        }
+        run_pyenv_test(settings, commands)
+
+    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 ctx.pyenv(["which", name]) == rf'{ctx.pyenv_path}\versions\3.8.5\{sub_dir}{name}.exe' or \
+                    ctx.pyenv(["which", name]) == rf'{ctx.pyenv_path}\versions\3.8.5\{sub_dir}{name}.EXE'
+        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)"
+        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)"
+        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)"
+        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"  ")
+        settings = {
+            'versions': ['3.8.2', '3.8.6', '3.9.1'],
+            'global_ver': '3.9.1'
+        }
+        run_pyenv_test(settings, commands)
+
+    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"  ")
+        settings = {
+            'versions': ['3.8.2', '3.8.6', '3.9.1'],
+            'local_ver': '3.9.1'
+        }
+        run_pyenv_test(settings, commands)
+
+    def test_which_exists_is_shell_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"  ")
+        settings = {
+            'versions': ['3.8.2', '3.8.6', '3.9.1'],
+        }
+        with TemporaryEnvironment({"PYENV_VERSION": "3.9.1"}):
+            run_pyenv_test(settings, commands)
+
+    def test_which_command_not_found(self, setup):
+        def commands(ctx):
+            for name in ['python3.8']:
+                assert ctx.pyenv(["which", name]) == f"pyenv: {name}: command not found"
+        settings = {
+            'versions': ['3.8.6'],
+            'global_ver': '3.8.6'
+        }
+        run_pyenv_test(settings, commands)
+
+    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")
+        run_pyenv_test({'versions': ['3.8.6']}, commands)
diff --git a/tests/test_pyenv_helpers.py b/tests/test_pyenv_helpers.py
index 8ebd069d..4dd8ae28 100644
--- a/tests/test_pyenv_helpers.py
+++ b/tests/test_pyenv_helpers.py
@@ -3,6 +3,7 @@
 import subprocess
 import tempfile
 from contextlib import contextmanager
+from packaging import version
 from pathlib import Path
 
 
@@ -16,38 +17,113 @@ def working_directory(path):
         os.chdir(prev_cwd)
 
 
-def install_pyenv(path, versions=None, global_ver=None):
+def python_exes(suffixes=None):
+    if suffixes is None:
+        suffixes = [""]
+    else:
+        suffixes.append("")
+    for suffix in suffixes:
+        yield f'python{suffix}.exe'
+        yield f'pythonw{suffix}.exe'
+
+
+def script_exes(ver):
+    for suffix in ['', f'{ver.major}', f'{ver.major}.{ver.minor}']:
+        yield f'pip{suffix}.exe'
+    for suffix in ['', f'-{ver.major}.{ver.minor}']:
+        yield f'easy_install{suffix}.exe'
+
+
+def pyenv_setup(settings):
+    pyenv_path, local_path, versions, global_ver, local_ver =\
+        settings['pyenv_path'],\
+        settings['local_path'],\
+        settings.get('versions', None),\
+        settings.get("global_ver", None),\
+        settings.get('local_ver', None)
     if versions is None:
         versions = []
     src_path = Path(__file__).resolve().parents[1].joinpath('pyenv-win')
     dirs = [r'bin', r'libexec\libs', r'shims', r'versions']
     for d in dirs:
-        os.makedirs(Path(path, d))
+        os.makedirs(Path(pyenv_path, d))
     files = [r'bin\pyenv.bat',
              r'libexec\pyenv.vbs',
              r'libexec\libs\pyenv-install-lib.vbs',
              r'libexec\libs\pyenv-lib.vbs']
     for f in files:
-        shutil.copy(src_path.joinpath(f), Path(path, f))
-    versions_dir = Path(path, r'versions')
+        shutil.copy(src_path.joinpath(f), Path(pyenv_path, f))
+    versions_dir = Path(pyenv_path, r'versions')
+
+    def touch(exe):
+        with open(exe, 'a'):
+            os.utime(exe, None)
+
+    def create_pythons(path):
+        os.mkdir(path)
+        for exe in python_exes([f'{ver.major}', f'{ver.major}{ver.minor}']):
+            touch(path.joinpath(exe))
+        return path
+
+    def create_scripts(path):
+        os.mkdir(path)
+        for exe in script_exes(ver):
+            touch(path.joinpath(exe))
+
     for v in versions:
-        os.mkdir(versions_dir.joinpath(v))
+        ver = version.parse(v)
+        version_path = create_pythons(versions_dir.joinpath(v))
+        create_scripts(version_path.joinpath('Scripts'))
     if global_ver is not None:
-        with open(Path(path, "version"), "w") as f:
+        with open(Path(pyenv_path, "version"), "w") as f:
             print(global_ver, file=f)
+    if local_ver is not None:
+        with open(Path(local_path, ".python-version"), "w") as f:
+            print(local_ver, file=f)
 
 
-@contextmanager
-def temp_pyenv(command, option=None, versions=None, global_ver=None):
-    if versions is None:
-        versions = []
+class PyenvContext:
+    def __init__(self, pyenv, pyenv_path, local_path):
+        self.pyenv = pyenv
+        self.pyenv_path = pyenv_path
+        self.local_path = local_path
+
+
+def run_pyenv_test(settings, commands):
     with tempfile.TemporaryDirectory() as tmp_path:
-        install_pyenv(tmp_path, versions, global_ver)
-        with working_directory(tmp_path):
-            bat = Path(tmp_path, r'bin\pyenv.bat')
-            args = ['cmd', '/d', '/c', f'call {bat}', command]
-            if option is not None:
-                args.append(option)
-            result = subprocess.run(args, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
-            result = str(result.stdout, "utf-8").strip()
-            yield result
+        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', f'call {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=True, 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)
+
+
+def not_installed_output(ver):
+    return (f"pyenv specific python requisite didn't meet. "
+            f"Project is using different version of python.\r\n"
+            f"Install python '{ver}' by typing: 'pyenv install {ver}'")
+
+
+def local_python_versions(path):
+    with open(Path(path, '.python-version'), mode='r') as f:
+        return f.read().strip()