From 1b157e97e0065be38533559fe3ac60c992731ad5 Mon Sep 17 00:00:00 2001 From: Jeremy Skinner Date: Thu, 14 Jun 2018 21:14:42 +0100 Subject: [PATCH] Update installer tests for posh-ssh --- posh-ssh/src/Installer.ps1 | 3 + posh-ssh/src/Keys.ps1 | 69 ++++++++++++++++++++++ posh-ssh/src/Posh-Ssh.psd1 | 3 +- posh-ssh/src/Utils.ps1 | 89 ++++++++++++++++++++++++++++ posh-ssh/test/Installer.Tests.ps1 | 96 ++++++++++++++++--------------- posh-ssh/test/Ssh.Tests.ps1 | 2 +- posh-ssh/test/TestHelper.ps1 | 20 +++++++ posh-ssh/test/test.bat | 2 + 8 files changed, 236 insertions(+), 48 deletions(-) create mode 100644 posh-ssh/test/TestHelper.ps1 create mode 100644 posh-ssh/test/test.bat diff --git a/posh-ssh/src/Installer.ps1 b/posh-ssh/src/Installer.ps1 index e5410d187..121d89a52 100644 --- a/posh-ssh/src/Installer.ps1 +++ b/posh-ssh/src/Installer.ps1 @@ -1,3 +1,6 @@ +# Allows for overriding of module path during test. +$ModuleBasePath = $PSScriptRoot + <# .SYNOPSIS Configures your PowerShell profile (startup) script to import the posh-ssh diff --git a/posh-ssh/src/Keys.ps1 b/posh-ssh/src/Keys.ps1 index e69de29bb..d2e3d2a25 100644 --- a/posh-ssh/src/Keys.ps1 +++ b/posh-ssh/src/Keys.ps1 @@ -0,0 +1,69 @@ +function Get-SshPath($File = 'id_rsa') { + # Avoid paths with path separator char since it is different on Linux/macOS. + # Also avoid ~ as it is invalid if the user is cd'd into say cert:\ or hklm:\. + # Also, apparently using the PowerShell built-in $HOME variable may not cut it for msysGit with has different + # ideas about the path to the user's home dir e.g. /c/Users/Keith + # $homePath = Invoke-NullCoalescing $Env:HOME $Home + $homePath = if ($Env:HOME) {$Env:HOME} else {$Home} + Join-Path $homePath (Join-Path .ssh $File) +} + +<# +.SYNOPSIS + Add a key to the SSH agent +.DESCRIPTION + Adds one or more SSH keys to the SSH agent. +.EXAMPLE + PS C:\> Add-SshKey + Adds ~\.ssh\id_rsa to the SSH agent. +.EXAMPLE + PS C:\> Add-SshKey ~\.ssh\mykey, ~\.ssh\myotherkey + Adds ~\.ssh\mykey and ~\.ssh\myotherkey to the SSH agent. +.INPUTS + None. + You cannot pipe input to this cmdlet. +#> +function Add-SshKey([switch]$Quiet) { + if ($env:GIT_SSH -imatch 'plink') { + $pageant = Get-Command pageant -Erroraction SilentlyContinue | Select-Object -First 1 -ExpandProperty Name + $pageant = if ($pageant) { $pageant } else { Find-Pageant } + if (!$pageant) { + if (!$Quiet) { + Write-Warning 'Could not find Pageant' + } + return + } + + if ($args.Count -eq 0) { + $keyPath = Join-Path $Env:HOME .ssh + $keys = Get-ChildItem $keyPath/*.ppk -ErrorAction SilentlyContinue | Select-Object -ExpandProperty FullName + if ($keys) { + & $pageant $keys + } + } + else { + foreach ($value in $args) { + & $pageant $value + } + } + } + else { + $sshAdd = Get-Command ssh-add -TotalCount 1 -ErrorAction SilentlyContinue + $sshAdd = if ($sshAdd) { $sshAdd } else { Find-Ssh('ssh-add') } + if (!$sshAdd) { + if (!$Quiet) { + Write-Warning 'Could not find ssh-add' + } + return + } + + if ($args.Count -eq 0) { + & $sshAdd + } + else { + foreach ($value in $args) { + & $sshAdd $value + } + } + } +} diff --git a/posh-ssh/src/Posh-Ssh.psd1 b/posh-ssh/src/Posh-Ssh.psd1 index bd1594eef..ccd8a8b45 100644 --- a/posh-ssh/src/Posh-Ssh.psd1 +++ b/posh-ssh/src/Posh-Ssh.psd1 @@ -27,7 +27,8 @@ 'Start-SshAgent', 'Stop-SshAgent', 'Add-SshKey', - 'Get-SshPath' + 'Get-SshPath', + 'Add-PoshSshToProfile' ) # Cmdlets to export from this module diff --git a/posh-ssh/src/Utils.ps1 b/posh-ssh/src/Utils.ps1 index 6b8aa0e5d..deac8ea16 100644 --- a/posh-ssh/src/Utils.ps1 +++ b/posh-ssh/src/Utils.ps1 @@ -1,3 +1,4 @@ +$ModuleBasePath = "$PSScriptRoot\.." function setenv($key, $value) { [void][Environment]::SetEnvironmentVariable($key, $value) @@ -55,4 +56,92 @@ function Test-PoshSshImportedInScript { $match = (@(Get-Content $Path -ErrorAction SilentlyContinue) -match 'posh-ssh').Count -gt 0 if ($match) { Write-Verbose "posh-ssh found in '$Path'" } $match +} + +function Get-PSModulePath { + $modulePaths = $Env:PSModulePath -split ';' + $modulePaths +} + +function Test-InPSModulePath { + param ( + [Parameter(Position=0, Mandatory=$true)] + [ValidateNotNull()] + [string] + $Path + ) + + $modulePaths = Get-PSModulePath + if (!$modulePaths) { return $false } + + $pathStringComparison = Get-PathStringComparison + $Path = $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($Path) + $inModulePath = @($modulePaths | Where-Object { $Path.StartsWith($_.TrimEnd([System.IO.Path]::DirectorySeparatorChar), $pathStringComparison) }).Count -gt 0 + + if ($inModulePath -and ('src' -eq (Split-Path $Path -Leaf))) { + Write-Warning 'posh-git repository structure is incompatible with %PSModulePath%.' + Write-Warning 'Importing with absolute path instead.' + return $false + } + + $inModulePath +} + +<# +.SYNOPSIS + Gets the file encoding of the specified file. +.DESCRIPTION + Gets the file encoding of the specified file. +.PARAMETER Path + Path to the file to check. The file must exist. +.EXAMPLE + PS C:\> Get-FileEncoding $profile + Get's the file encoding of the profile file. +.INPUTS + None. +.OUTPUTS + [System.String] +.NOTES + Adapted from http://www.west-wind.com/Weblog/posts/197245.aspx +#> +function Get-FileEncoding($Path) { + if ($PSVersionTable.PSVersion.Major -ge 6) { + $bytes = [byte[]](Get-Content $Path -AsByteStream -ReadCount 4 -TotalCount 4) + } + else { + $bytes = [byte[]](Get-Content $Path -Encoding byte -ReadCount 4 -TotalCount 4) + } + + if (!$bytes) { return 'utf8' } + + switch -regex ('{0:x2}{1:x2}{2:x2}{3:x2}' -f $bytes[0],$bytes[1],$bytes[2],$bytes[3]) { + '^efbbbf' { return 'utf8' } + '^2b2f76' { return 'utf7' } + '^fffe' { return 'unicode' } + '^feff' { return 'bigendianunicode' } + '^0000feff' { return 'utf32' } + default { return 'ascii' } + } +} + +<# +.SYNOPSIS + Gets a StringComparison enum value appropriate for comparing paths on the OS platform. +.DESCRIPTION + Gets a StringComparison enum value appropriate for comparing paths on the OS platform. +.EXAMPLE + PS C:\> $pathStringComparison = Get-PathStringComparison +.INPUTS + None +.OUTPUTS + [System.StringComparison] +#> +function Get-PathStringComparison { + # File system paths are case-sensitive on Linux and case-insensitive on Windows and macOS + if (($PSVersionTable.PSVersion.Major -ge 6) -and $IsLinux) { + [System.StringComparison]::Ordinal + } + else { + [System.StringComparison]::OrdinalIgnoreCase + } } \ No newline at end of file diff --git a/posh-ssh/test/Installer.Tests.ps1 b/posh-ssh/test/Installer.Tests.ps1 index eed18b71e..a82d54137 100644 --- a/posh-ssh/test/Installer.Tests.ps1 +++ b/posh-ssh/test/Installer.Tests.ps1 @@ -1,10 +1,11 @@ -. $PSScriptRoot\Shared.ps1 -. $modulePath\Utils.ps1 +. $PSScriptRoot\TestHelper.ps1 +# Utils is needed so we can mock Get-PsModulePath +. $PSScriptRoot\..\src\Utils.ps1 $expectedEncoding = if ($PSVersionTable.PSVersion.Major -le 5) { "utf8" } else { "ascii" } Describe 'Utils Function Tests' { - Context 'Add-PoshGitToProfile Tests' { + Context 'Add-PoshSshToProfile Tests' { BeforeAll { [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssigments', '')] $newLine = [System.Environment]::NewLine @@ -23,14 +24,15 @@ Describe 'Utils Function Tests' { Remove-Item -LiteralPath $profilePath Test-Path -LiteralPath $profilePath | Should Be $false - Add-PoshGitToProfile $profilePath + Add-PoshSshToProfile $profilePath Test-Path -LiteralPath $profilePath | Should Be $true Get-FileEncoding $profilePath | Should Be $expectedEncoding $content = Get-Content $profilePath - $content.Count | Should Be 2 - $nativePath = MakeNativePath $modulePath\posh-git.psd1 + $content.Count | Should Be 3 + $nativePath = MakeNativePath $modulePath\posh-ssh.psd1 @($content)[1] | Should BeExactly "Import-Module '$nativePath'" + @($content)[2] | Should BeExactly "Start-SshAgent -Quiet" } It 'Creates profile file if it does not exist that imports from module path' { $parentDir = Split-Path $profilePath -Parent @@ -45,13 +47,14 @@ Describe 'Utils Function Tests' { Remove-Item -LiteralPath $profilePath Test-Path -LiteralPath $profilePath | Should Be $false - Add-PoshGitToProfile $profilePath $parentDir + Add-PoshSshToProfile $profilePath $parentDir Test-Path -LiteralPath $profilePath | Should Be $true Get-FileEncoding $profilePath | Should Be $expectedEncoding $content = Get-Content $profilePath - $content.Count | Should Be 2 - @($content)[1] | Should BeExactly "Import-Module posh-git" + $content.Count | Should Be 3 + $nativePath = MakeNativePath $modulePath\posh-ssh.psd1 + @($content)[1] | Should BeExactly "Import-Module posh-ssh" } It 'Creates profile file if the profile dir does not exist' { # Use $profilePath as missing parent directory (auto-cleanup) @@ -60,21 +63,21 @@ Describe 'Utils Function Tests' { $childProfilePath = Join-Path $profilePath profile.ps1 - Add-PoshGitToProfile $childProfilePath + Add-PoshSshToProfile $childProfilePath Test-Path -LiteralPath $childProfilePath | Should Be $true - $childProfilePath | Should FileContentMatch "^Import-Module .*posh-git" + $childProfilePath | Should FileContentMatch "^Import-Module .*posh-ssh" } - It 'Does not modify profile that already refers to posh-git' { + It 'Does not modify profile that already refers to posh-ssh' { $profileContent = @' Import-Module PSCX -Import-Module posh-git +Import-Module posh-ssh '@ Set-Content $profilePath -Value $profileContent -Encoding Ascii - $output = Add-PoshGitToProfile $profilePath 3>&1 + $output = Add-PoshSshToProfile $profilePath 3>&1 - $output[1] | Should Match 'posh-git appears' + $output[1] | Should Match 'posh-ssh appears' Get-FileEncoding $profilePath | Should Be 'ascii' $content = Get-Content $profilePath $content.Count | Should Be 2 @@ -89,28 +92,29 @@ New-Alias pscore C:\Users\Keith\GitHub\rkeithhill\PowerShell\src\powershell-win- '@ Set-Content $profilePath -Value $profileContent -Encoding Unicode - Add-PoshGitToProfile $profilePath (Split-Path $profilePath -Parent) + Add-PoshSshToProfile $profilePath (Split-Path $profilePath -Parent) Test-Path -LiteralPath $profilePath | Should Be $true Get-FileEncoding $profilePath | Should Be 'unicode' $content = Get-Content $profilePath - $content.Count | Should Be 5 + $content | % { write-host $_ } + $content.Count | Should Be 6 # Last line is the start-sshagent $nativeContent = Convert-NativeLineEnding $profileContent - $nativeContent += "${newLine}${newLine}Import-Module posh-git" + $nativeContent += "${newLine}${newLine}Import-Module posh-ssh" $content -join $newLine | Should BeExactly $nativeContent } - It 'Adds Start-SshAgent if posh-git is not installed' { - Add-PoshGitToProfile $profilePath -StartSshAgent + It 'Adds Start-SshAgent if posh-ssh is not installed' { + Add-PoshSshToProfile $profilePath -StartSshAgent Test-Path -LiteralPath $profilePath | Should Be $true $last = Get-Content $profilePath | Select-Object -Last 1 $last | Should BeExactly "Start-SshAgent -Quiet" } - It 'Does not add Start-SshAgent if posh-git is installed' { - $profileContent = 'Import-Module posh-git' + It 'Does not add Start-SshAgent if posh-ssh is installed' { + $profileContent = 'Import-Module posh-ssh' Set-Content $profilePath -Value $profileContent - Add-PoshGitToProfile $profilePath -StartSshAgent + Add-PoshSshToProfile $profilePath -StartSshAgent Test-Path -LiteralPath $profilePath | Should Be $true $content = Get-Content $profilePath @@ -118,7 +122,7 @@ New-Alias pscore C:\Users\Keith\GitHub\rkeithhill\PowerShell\src\powershell-win- } } - Context 'Test-PoshGitImportedInScript Tests' { + Context 'Test-PoshSshImportedInScript Tests' { BeforeEach { [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssigments', '')] $profilePath = [System.IO.Path]::GetTempFileName() @@ -126,68 +130,68 @@ New-Alias pscore C:\Users\Keith\GitHub\rkeithhill\PowerShell\src\powershell-win- AfterEach { Remove-Item $profilePath -ErrorAction SilentlyContinue } - It 'Detects Import-Module posh-git in profile script' { - $profileContent = "Import-Module posh-git" + It 'Detects Import-Module posh-ssh in profile script' { + $profileContent = "Import-Module posh-ssh" Set-Content $profilePath -Value $profileContent -Encoding Unicode - Test-PoshGitImportedInScript $profilePath | Should Be $true + Test-PoshSshImportedInScript $profilePath | Should Be $true } It 'Detects chocolatey installed line in profile script' { - $profileContent = ". 'C:\tools\poshgit\dahlbyk-posh-git-18d600a\profile.example.ps1" + $profileContent = ". 'C:\tools\poshssh\dahlbyk-posh-ssh-18d600a\profile.example.ps1" Set-Content $profilePath -Value $profileContent -Encoding Unicode - Test-PoshGitImportedInScript $profilePath | Should Be $true + Test-PoshSshImportedInScript $profilePath | Should Be $true } - It 'Returns false when one-line profile script does not import posh-git' { + It 'Returns false when one-line profile script does not import posh-ssh' { $profileContent = "# Test" Set-Content $profilePath -Value $profileContent -Encoding Unicode - Test-PoshGitImportedInScript $profilePath | Should Be $false + Test-PoshSshImportedInScript $profilePath | Should Be $false } - It 'Returns false when profile script does not import posh-git' { + It 'Returns false when profile script does not import posh-ssh' { $profileContent = "Import-Module Pscx`nImport-Module platyPS`nImport-Module Plaster" Set-Content $profilePath -Value $profileContent -Encoding Unicode - Test-PoshGitImportedInScript $profilePath | Should Be $false + Test-PoshSshImportedInScript $profilePath | Should Be $false } } Context 'Test-InPSModulePath Tests' { It 'Returns false for install not under any PSModulePaths' { Mock Get-PSModulePath { } - $path = "C:\Users\Keith\Documents\WindowsPowerShell\Modules\posh-git\0.7.0\" + $path = "C:\Users\Keith\Documents\WindowsPowerShell\Modules\posh-ssh\0.7.0\" Test-InPSModulePath $path | Should Be $false Assert-MockCalled Get-PSModulePath } It 'Returns true for install under single PSModulePath' { Mock Get-PSModulePath { - return MakeNativePath "$HOME\Documents\WindowsPowerShell\Modules\posh-git\" + return MakeNativePath "$HOME\Documents\WindowsPowerShell\Modules\posh-ssh\" } - $path = MakeNativePath "$HOME\Documents\WindowsPowerShell\Modules\posh-git\0.7.0" + $path = MakeNativePath "$HOME\Documents\WindowsPowerShell\Modules\posh-ssh\0.7.0" Test-InPSModulePath $path | Should Be $true Assert-MockCalled Get-PSModulePath } It 'Returns true for install under multiple PSModulePaths' { Mock Get-PSModulePath { - return (MakeNativePath "$HOME\Documents\WindowsPowerShell\Modules\posh-git\"), - (MakeNativePath "$HOME\GitHub\dahlbyk\posh-git\0.6.1.20160330\") + return (MakeNativePath "$HOME\Documents\WindowsPowerShell\Modules\posh-ssh\"), + (MakeNativePath "$HOME\GitHub\dahlbyk\posh-ssh\0.6.1.20160330\") } - $path = MakeNativePath "$HOME\Documents\WindowsPowerShell\Modules\posh-git\0.7.0" + $path = MakeNativePath "$HOME\Documents\WindowsPowerShell\Modules\posh-ssh\0.7.0" Test-InPSModulePath $path | Should Be $true Assert-MockCalled Get-PSModulePath } - It 'Returns false when current posh-git module location is not under PSModulePaths' { + It 'Returns false when current posh-ssh module location is not under PSModulePaths' { Mock Get-PSModulePath { - return (MakeNativePath "$HOME\Documents\WindowsPowerShell\Modules\posh-git\"), - (MakeNativePath "$HOME\GitHub\dahlbyk\posh-git\0.6.1.20160330\") + return (MakeNativePath "$HOME\Documents\WindowsPowerShell\Modules\posh-ssh\"), + (MakeNativePath "$HOME\GitHub\dahlbyk\posh-ssh\0.6.1.20160330\") } - $path = MakeNativePath "\tools\posh-git\dahlbyk-posh-git-18d600a" + $path = MakeNativePath "\tools\posh-ssh\dahlbyk-posh-ssh-18d600a" Test-InPSModulePath $path | Should Be $false Assert-MockCalled Get-PSModulePath } - It 'Returns false when current posh-git module location is under PSModulePath, but in a src directory' { + It 'Returns false when current posh-ssh module location is under PSModulePath, but in a src directory' { Mock Get-PSModulePath { return MakeNativePath '\GitHub' } - $path = MakeNativePath "\GitHub\posh-git\src" + $path = MakeNativePath "\GitHub\posh-ssh\src" Test-InPSModulePath $path | Should Be $false Assert-MockCalled Get-PSModulePath } } -} +} \ No newline at end of file diff --git a/posh-ssh/test/Ssh.Tests.ps1 b/posh-ssh/test/Ssh.Tests.ps1 index 4613b9c5c..f91cb77f4 100644 --- a/posh-ssh/test/Ssh.Tests.ps1 +++ b/posh-ssh/test/Ssh.Tests.ps1 @@ -1,4 +1,4 @@ -. $PSScriptRoot\..\src\SshUtils.ps1 +Import-Module $PSScriptRoot\..\src\posh-ssh.psd1 Describe 'SSH Function Tests' { Context 'Get-SshPath Tests' { diff --git a/posh-ssh/test/TestHelper.ps1 b/posh-ssh/test/TestHelper.ps1 new file mode 100644 index 000000000..9eb1d261f --- /dev/null +++ b/posh-ssh/test/TestHelper.ps1 @@ -0,0 +1,20 @@ + +# This must global in order to be accessible in posh-git module scope +function global:Convert-NativeLineEnding([string]$content, [switch]$SplitLines) { + $tmp = $content -split "`n" | ForEach-Object { $_.TrimEnd("`r")} + if ($SplitLines) { + $tmp + } + else { + $content = $tmp -join [System.Environment]::NewLine + $content + } +} + +function MakeNativePath([string]$Path) { + $Path -replace '\\|/', [System.IO.Path]::DirectorySeparatorChar +} + +$modulePath = Convert-Path $PSScriptRoot\..\src + +Import-Module $PSScriptRoot\..\src\posh-ssh.psd1 \ No newline at end of file diff --git a/posh-ssh/test/test.bat b/posh-ssh/test/test.bat new file mode 100644 index 000000000..2d1113ac5 --- /dev/null +++ b/posh-ssh/test/test.bat @@ -0,0 +1,2 @@ +@echo off +powershell -noprofile -command invoke-pester %* \ No newline at end of file