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

Issue:
pyenv local many versions 🎉
- Depends on #220 & #223 
- Close #204 

![image](https://user-images.githubusercontent.com/626824/112147562-259cd300-8bdd-11eb-86d5-1552583caf4d.png)

![image](https://user-images.githubusercontent.com/626824/112147819-6ac10500-8bdd-11eb-8a23-4921110506c9.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..241cd706 100644
--- a/pyenv-win/libexec/libs/pyenv-lib.vbs
+++ b/pyenv-win/libexec/libs/pyenv-lib.vbs
@@ -65,30 +65,49 @@ Function GetCurrentVersionGlobal()
     End If
 End Function
 
-Function GetCurrentVersionLocal(path)
-    ' WScript.echo "kkotari: pyenv-lib.vbs get current version local..!"
-    GetCurrentVersionLocal = Null
-
+Function GetCurrentVersionsLocal(path)
+    ' WScript.echo "kkotari: pyenv-lib.vbs get current versions local..!"
     Dim fname
     Dim objFile
+    Dim line
+    ReDim versions(-1)
     Do While path <> ""
         fname = path & strVerFile
         If objfs.FileExists(fname) Then
             Set objFile = objfs.OpenTextFile(fname)
-            If objFile.AtEndOfStream <> True Then
-               GetCurrentVersionLocal = Array(objFile.ReadLine, fname)
-            End If
+            Do While objFile.AtEndOfStream <> True
+                line = objFile.ReadLine
+                If line <> "" Then
+                    ReDim Preserve versions (UBound(versions) + 1)
+                    versions(UBound(versions)) = Array(line, fname)
+                End If
+            Loop
             objFile.Close
-            Exit Function
+            Exit Do
         End If
         path = objfs.GetParentFolderName(path)
     Loop
+    if UBound(versions) >= 0 Then
+        GetCurrentVersionsLocal = versions
+    Else
+        GetCurrentVersionsLocal = Null
+    End If
+End Function
+
+Function GetFirstVersionLocal(path)
+    ' WScript.echo "kkotari: pyenv-lib.vbs get first version local..!"
+    Dim versions
+    versions = GetCurrentVersionsLocal(path)
+    if IsNull(versions) Then
+        GetFirstVersionLocal = Null
+    Else
+        GetFirstVersionLocal = versions(0)
+    End If
 End Function
 
 Function GetCurrentVersionShell()
     ' WScript.echo "kkotari: pyenv-lib.vbs get current version shell..!"
     GetCurrentVersionShell = Null
-
     Dim str
     str = objws.Environment("Process")("PYENV_VERSION")
     If str <> "" Then _
@@ -98,9 +117,7 @@ End Function
 Function GetCurrentVersion()
     ' WScript.echo "kkotari: pyenv-lib.vbs get current version..!"
     Dim str
-    str = GetCurrentVersionShell
-    If IsNull(str) Then str = GetCurrentVersionLocal(strCurrent)
-    If IsNull(str) Then str = GetCurrentVersionGlobal
+    str = GetCurrentVersionNoError
     If IsNull(str) Then
 		WScript.echo "No global python version has been set yet. Please set the global version by typing:"
 		WScript.echo "pyenv global 3.7.2"
@@ -113,11 +130,60 @@ Function GetCurrentVersionNoError()
     ' WScript.echo "kkotari: pyenv-lib.vbs get current version no error..!"
     Dim str
     str = GetCurrentVersionShell
-    If IsNull(str) Then str = GetCurrentVersionLocal(strCurrent)
+    If IsNull(str) Then str = GetFirstVersionLocal(strCurrent)
     If IsNull(str) Then str = GetCurrentVersionGlobal
     GetCurrentVersionNoError = str
 End Function
 
+Function GetCurrentVersions()
+    ' WScript.echo "kkotari: pyenv-lib.vbs get current versions..!"
+    Dim versions
+    Set versions = GetCurrentVersionsNoError
+    If versions.Count = 0 Then
+		WScript.echo "No global python version has been set yet. Please set the global version by typing:"
+		WScript.echo "pyenv global 3.7.2"
+		WScript.quit
+	End If
+	Set GetCurrentVersions = versions
+End Function
+
+Function GetCurrentVersionsNoError()
+    ' WScript.echo "kkotari: pyenv-lib.vbs get current version no error..!"
+    Dim versions
+    Set versions = CreateObject("Scripting.Dictionary")
+    Dim str
+    str = GetCurrentVersionShell
+    If Not(IsNull(str)) Then
+        versions.Add str(0), str(1)
+    Else
+        str = GetCurrentVersionsLocal(strCurrent)
+        If Not(IsNull(str)) Then
+            Dim v1
+            For Each v1 in str
+                versions.Add v1(0), v1(1)
+            Next
+        End If
+    End If
+    If IsNull(str) Then
+        str = GetCurrentVersionGlobal
+        If Not(IsNull(str)) Then versions.Add str(0), str(1)
+    End If
+    Set GetCurrentVersionsNoError = versions
+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 +253,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 +294,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
 
-    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
+    For Each version In GetInstalledVersions()
+        winBinDir = strDirVers &"\"& version
+        nixBinDir = "/"& Replace(Replace(winBinDir, ":", ""), "\", "/")
+        Set exts = GetExtensionsNoPeriod(True)
 
-    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..aea83caf 100644
--- a/pyenv-win/libexec/pyenv-install.vbs
+++ b/pyenv-win/libexec/pyenv-install.vbs
@@ -123,6 +123,26 @@ Function deepExtract(params)
             Exit Function
         End If
     End If
+
+    ' Add pythonX, pythonXY & pythonX.Y exe
+    ' pythonX.Y for tox
+    ' 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 also use the pattern pythonXY
+    Dim version, pythonExe, pythonwExe, major, minor, majorMinor, majorDotMinor
+    version = params(LV_Code)
+    pythonExe = installPath &"\python.exe"
+    pythonwExe = installPath &"\pythonw.exe"
+    major = Left(version, 1)
+    minor = Mid(version, 3, 1)
+    majorMinor = major & minor
+    majorDotMinor = major &"."& minor
+    objfs.CopyFile pythonExe, installPath &"\python"& major &".exe"
+    objfs.CopyFile pythonExe, installPath &"\python"& majorMinor &".exe"
+    objfs.CopyFile pythonExe, installPath &"\python"& majorDotMinor &".exe"
+    objfs.CopyFile pythonwExe, installPath &"\pythonw"& major &".exe"
+    objfs.CopyFile pythonwExe, installPath &"\pythonw"& majorMinor &".exe"
+    objfs.CopyFile pythonwExe, installPath &"\pythonw"& majorDotMinor &".exe"
 End Function
 
 Sub extract(params)
@@ -297,6 +317,7 @@ Sub main(arg)
     Else
         If installVersions.Count = 0 Then
             Dim ary
+            ' TODO Should we handle many versions here?
             ary = GetCurrentVersionNoError()
             If Not IsNull(ary) Then
                 installVersions.Item(ary(0)) = Empty
diff --git a/pyenv-win/libexec/pyenv-local.bat b/pyenv-win/libexec/pyenv-local.bat
index 67d92233..1fcbe7ef 100644
--- a/pyenv-win/libexec/pyenv-local.bat
+++ b/pyenv-win/libexec/pyenv-local.bat
@@ -2,7 +2,7 @@
 setlocal
 
 if "%1" == "--help" (
-echo Usage: pyenv local ^<version^>
+echo Usage: pyenv local ^<version^> ^<version2^> ^<..^>
 echo        pyenv local --unset
 echo.
 echo Sets the local application-specific Python version by writing the
@@ -15,6 +15,15 @@ echo specified with `pyenv global'. A version specified with the
 echo `PYENV_VERSION' environment variable takes precedence over local
 echo and global versions.
 echo.
+echo ^<version^> can be specified multiple times and should be a version
+echo tag known to pyenv.  The special version string `system' will use
+echo your default system Python.  Run `pyenv versions' for a list of
+echo available Python versions.
+echo.
+echo Example: To enable the python2.7 and python3.7 shims to find their
+echo          respective executables you could set both versions with:
+echo.
+echo 'pyenv local 3.7.0 2.7.15'
 EXIT /B
 )
 
diff --git a/pyenv-win/libexec/pyenv.vbs b/pyenv-win/libexec/pyenv.vbs
index 0b71bfa1..2b3918d1 100644
--- a/pyenv-win/libexec/pyenv.vbs
+++ b/pyenv-win/libexec/pyenv.vbs
@@ -137,50 +137,53 @@ Sub CommandWhich(arg)
     Dim version
 
     program = arg(1)
-    version = objws.Environment("Process")("PYENV_VERSION")
-
     If program = "" Then PrintHelp "pyenv-which", 1
-    If version = "" Then version = GetCurrentVersion()(0)
     If Right(program, 1) = "." Then program = Left(program, Len(program)-1)
 
     Set exts = GetExtensions(True)
 
-    If Not objfs.FolderExists(strDirVers &"\"& version) Then
-        WScript.Echo "pyenv: version `"& version &"' is not installed (set by "& version &")"
-        WScript.Quit 1
-    End If
-
-    If objfs.FileExists(strDirVers &"\"& version &"\"& program) Then
-        WScript.Echo objfs.GetFile(strDirVers &"\"& version &"\"& program).Path
-        WScript.Quit 0
-    End If
+    Dim versions
+    Set versions = GetCurrentVersions
+    For Each version In versions
 
-    For Each ext In exts.Keys
-        If objfs.FileExists(strDirVers &"\"& version &"\"& program & ext) Then
-            WScript.Echo objfs.GetFile(strDirVers &"\"& version &"\"& program & ext).Path
-            WScript.Quit 0
+        If Not objfs.FolderExists(strDirVers &"\"& version) Then
+            WScript.Echo "pyenv: version `"& version &"' is not installed (set by "& version &")"
+            WScript.Quit 1
         End If
-    Next
 
-    If objfs.FolderExists(strDirVers &"\"& version & "\Scripts") Then
-        If objfs.FileExists(strDirVers &"\"& version &"\Scripts\"& program) Then
-            WScript.Echo objfs.GetFile(strDirVers &"\"& version &"\Scripts\"& program).Path
+        If objfs.FileExists(strDirVers &"\"& version &"\"& program) Then
+            WScript.Echo objfs.GetFile(strDirVers &"\"& version &"\"& program).Path
             WScript.Quit 0
         End If
 
         For Each ext In exts.Keys
-            If objfs.FileExists(strDirVers &"\"& version &"\Scripts\"& program & ext) Then
-                WScript.Echo objfs.GetFile(strDirVers &"\"& version &"\Scripts\"& program & ext).Path
+            If objfs.FileExists(strDirVers &"\"& version &"\"& program & ext) Then
+                WScript.Echo objfs.GetFile(strDirVers &"\"& version &"\"& program & ext).Path
                 WScript.Quit 0
             End If
         Next
-    End If
+
+        If objfs.FolderExists(strDirVers &"\"& version & "\Scripts") Then
+            If objfs.FileExists(strDirVers &"\"& version &"\Scripts\"& program) Then
+                WScript.Echo objfs.GetFile(strDirVers &"\"& version &"\Scripts\"& program).Path
+                WScript.Quit 0
+            End If
+
+            For Each ext In exts.Keys
+                If objfs.FileExists(strDirVers &"\"& version &"\Scripts\"& program & ext) Then
+                    WScript.Echo objfs.GetFile(strDirVers &"\"& version &"\Scripts\"& program & ext).Path
+                    WScript.Quit 0
+                End If
+            Next
+        End If
+    Next
+
     WScript.Echo "pyenv: "& arg(1) &": command not found"
 
     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
 
@@ -402,11 +405,14 @@ Sub CommandLocal(arg)
 
     Dim ver
     If arg.Count < 2 Then
-        ver = GetCurrentVersionLocal(strCurrent)
-        If IsNull(ver) Then
+        Dim currentVersions
+        currentVersions = GetCurrentVersionsLocal(strCurrent)
+        If IsNull(currentVersions) Then
             WScript.Echo "no local version configured for this directory"
         Else
-            WScript.Echo ver(0)
+            For Each ver in currentVersions
+                WScript.Echo ver(0)
+            Next
         End If
     Else
         If arg(1) = "--unset" Then
@@ -414,8 +420,14 @@ Sub CommandLocal(arg)
             objfs.DeleteFile strCurrent & strVerFile, True
             Exit Sub
         Else
-            ver = Check32Bit(arg(1))
-            GetBinDir(ver)
+            Dim versionCount
+            versionCount = arg.Count - 1
+            ReDim localVersions(versionCount - 1)
+            Dim i
+            For i = 0 To versionCount - 1
+                localVersions(i) = Check32Bit(arg(i + 1))
+                GetBinDir(localVersions(i))
+            Next
         End If
 
         Dim ofile
@@ -424,7 +436,9 @@ Sub CommandLocal(arg)
         Else
             Set ofile = objfs.CreateTextFile(strCurrent & strVerFile, True)
         End If
-        ofile.WriteLine(ver)
+        For Each ver in localVersions
+            ofile.WriteLine(ver)
+        Next
         ofile.Close()
     End If
 End Sub
@@ -463,8 +477,11 @@ Sub CommandVersion(arg)
     If Not objfs.FolderExists(strDirVers) Then objfs.CreateFolder(strDirVers)
 
     Dim curVer
-    curVer = GetCurrentVersion
-    WScript.Echo curVer(0) &" (set by "& curVer(1) &")"
+    Dim versions
+    Set versions = GetCurrentVersions
+    For Each curVer In versions
+        WScript.Echo curVer &" (set by "& versions(curVer) &")"
+    Next
 End Sub
 
 Sub CommandVersionName(arg)
@@ -475,18 +492,26 @@ Sub CommandVersionName(arg)
 
     If Not objfs.FolderExists(strDirVers) Then objfs.CreateFolder(strDirVers)
 
-    WScript.Echo GetCurrentVersion()(0)
+    Dim ver, versions
+    Set versions = GetCurrentVersions
+    For Each ver in versions
+        WScript.Echo ver
+    Next
 End Sub
 
 Sub CommandVersionNameShort(arg)
-    ' WScript.echo "kkotari: pyenv.vbs command v-name..!"
+    ' WScript.echo "kkotari: pyenv.vbs command vname..!"
     If arg.Count >= 2 Then
         If arg(1) = "--help" Then PrintHelp "pyenv-vname", 0
     End If
 
     If Not objfs.FolderExists(strDirVers) Then objfs.CreateFolder(strDirVers)
 
-    WScript.Echo GetCurrentVersion()(0)
+    Dim ver, versions
+    Set versions = GetCurrentVersions
+    For Each ver in versions
+        WScript.Echo ver
+    Next
 End Sub
 
 Sub CommandVersions(arg)
@@ -503,11 +528,8 @@ Sub CommandVersions(arg)
 
     If Not objfs.FolderExists(strDirVers) Then objfs.CreateFolder(strDirVers)
 
-    Dim curVer
-    curVer = GetCurrentVersionNoError
-    If IsNull(curVer) Then
-        curVer = Array("", "")
-    End If
+    Dim versions
+    Set versions = GetCurrentVersionsNoError
 
     Dim dir
     Dim ver
@@ -515,8 +537,8 @@ Sub CommandVersions(arg)
         ver = objfs.GetFileName(dir)
         If isBare Then
             WScript.Echo ver
-        ElseIf ver = curVer(0) Then
-            WScript.Echo "* "& ver &" (set by "& curVer(1) &")"
+        ElseIf versions.Exists(ver) Then
+            WScript.Echo "* "& ver &" (set by "& versions(ver) &")"
         Else
             WScript.Echo "  "& ver
         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..9a67055e 100644
--- a/tests/test_pyenv_feature_local.py
+++ b/tests/test_pyenv_feature_local.py
@@ -1,5 +1,45 @@
 from test_pyenv import TestPyenvBase
+from test_pyenv_helpers import local_python_versions, 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)
+
+    def test_local_set_many_versions(self, setup):
+        def commands(ctx):
+            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")
+        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"
+        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 818a4e51..a74fa3b9 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}', 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..53fe092a 100644
--- a/tests/test_pyenv_feature_version.py
+++ b/tests/test_pyenv_feature_version.py
@@ -1,5 +1,46 @@
+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)
+
+    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)')
+        settings = {
+            'global_ver': "3.7.2",
+            'local_ver': "3.8.8\n3.9.1\n"
+        }
+        run_pyenv_test(settings, commands)
diff --git a/tests/test_pyenv_feature_version_name.py b/tests/test_pyenv_feature_version_name.py
new file mode 100644
index 00000000..328a91f6
--- /dev/null
+++ b/tests/test_pyenv_feature_version_name.py
@@ -0,0 +1,53 @@
+import pytest
+from tempenv import TemporaryEnvironment
+from test_pyenv_helpers import run_pyenv_test
+
+
+@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")
+    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"
+    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"
+    settings = {
+        'global_ver': "3.7.2",
+        'local_ver': "3.9.1"
+    }
+    run_pyenv_test(settings, commands)
+
+
+@pytest.mark.parametrize("command", ['version-name', 'vname'])
+def test_shell_version(command):
+    def commands(ctx):
+        assert ctx.pyenv(command) == "3.9.2"
+    settings = {
+        'global_ver': "3.7.5",
+        'local_ver': "3.8.6",
+    }
+    with TemporaryEnvironment({"PYENV_VERSION": "3.9.2"}):
+        run_pyenv_test(settings, commands)
+
+
+@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"
+    settings = {
+        'global_ver': "3.7.2",
+        'local_ver': "3.8.8\n3.9.1\n"
+    }
+    run_pyenv_test(settings, commands)
diff --git a/tests/test_pyenv_feature_versions.py b/tests/test_pyenv_feature_versions.py
index 1a95ff3e..1605ee0b 100644
--- a/tests/test_pyenv_feature_versions.py
+++ b/tests/test_pyenv_feature_versions.py
@@ -1,14 +1,102 @@
+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_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")
+        settings = {
+            'versions': ["3.6.5", "3.7.7", "3.9.1"],
+            'global_ver': "3.9.1",
+            'local_ver': "3.6.5\n3.7.7\n"
+        }
+        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..97f436be 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', 'python3.8', '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 ['unknown3.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..1e937bdf 100644
--- a/tests/test_pyenv_feature_which.py
+++ b/tests/test_pyenv_feature_which.py
@@ -1,5 +1,137 @@
+from tempenv import TemporaryEnvironment
 from test_pyenv import TestPyenvBase
+from test_pyenv_helpers import run_pyenv_test
+
+
+def assert_paths_equal(actual, expected):
+    assert actual.lower() == expected.lower()
+
 
 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_paths_equal(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_paths_equal(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_paths_equal(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', '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"  ")
+        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 ['unknown3.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)
+
+    def test_which_many_local_versions(self, setup):
+        def commands(ctx):
+            cases = [
+                ('python37', r'3.7.7\python37.exe'),
+                ('python38', r'3.8.2\python38.exe'),
+                ('pip3.7', r'3.7.7\Scripts\pip3.7.exe'),
+                ('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}')
+        settings = {
+            'versions': ['3.7.7', '3.8.2', '3.9.1'],
+            'local_ver': '3.7.7\n3.8.2\n'
+        }
+        run_pyenv_test(settings, commands)
diff --git a/tests/test_pyenv_helpers.py b/tests/test_pyenv_helpers.py
index 8ebd069d..c4ab0feb 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}', 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()