diff --git a/.vscode/launch.json b/.vscode/launch.json index ee6013c73..ae10584f8 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -5,37 +5,19 @@ "version": "0.2.0", "configurations": [ { - "name": "PowerShell Launch Current File", + "name": "PowerShell Launch Script", "type": "PowerShell", "request": "launch", - "script": "${file}", - "args": [], - "cwd": "${file}" - }, - { - "name": "PowerShell Launch Current File in Temporary Console", - "type": "PowerShell", - "request": "launch", - "script": "${file}", - "args": [], - "cwd": "${file}", - "createTemporaryIntegratedConsole": true + "script": "./Start-DebugSession.ps1", + "cwd": "${workspaceFolder}" }, { - "name": "PowerShell Launch Current File w/Args Prompt", + "name": "PowerShell: Launch Current File", "type": "PowerShell", "request": "launch", "script": "${file}", - "args": [ - "${command:SpecifyScriptArgs}" - ], "cwd": "${file}" }, - { - "name": "PowerShell Attach to Host Process", - "type": "PowerShell", - "request": "attach" - }, { "name": "PowerShell Interactive Session", "type": "PowerShell", @@ -47,6 +29,12 @@ "type": "PowerShell", "request": "attach", "processId": "current" + }, + { + "name": "PowerShell Attach to Host Process", + "type": "PowerShell", + "request": "attach", + "runspaceId": 1 } ] } diff --git a/PSKoans/Private/Invoke-Koan.ps1 b/PSKoans/Private/Invoke-Koan.ps1 index 5befa832c..61c1a3f4a 100644 --- a/PSKoans/Private/Invoke-Koan.ps1 +++ b/PSKoans/Private/Invoke-Koan.ps1 @@ -27,25 +27,57 @@ ) end { try { + $Requirements = [System.Management.Automation.Language.Parser]::ParseFile( + $ParameterSplat.Script, + [ref]$null, + [ref]$null + ).Ast.ScriptRequirements + $Script = { - param( $Params ) + param( $Params, $RequiredModules, $PSKoansPath, $PSModulePath ) + + [System.Collections.Generic.HashSet[string]] $ModulePaths = @( + $PSModulePath -split [System.IO.Path]::PathSeparator + $env:PSModulePath -split [System.IO.Path]::PathSeparator + ) + + $env:PSModulePath = $ModulePaths -join [System.IO.Path]::PathSeparator + + Get-Module $PSKoansPath -ListAvailable | Import-Module + foreach ($module in $RequiredModules) { + Import-Module $module + } - . ([scriptblock]::Create('using module PSKoans')) Invoke-Pester @Params } - $Thread = [powershell]::Create() - $Thread.AddScript($Script) > $null - $Thread.AddArgument($ParameterSplat) > $null + $Runspace = [powershell]::Create() + $Runspace.AddScript($Script) > $null + $Runspace.AddParameter('Params', $ParameterSplat) > $null + $Runspace.AddParameter('PSKoansPath', $MyInvocation.MyCommand.Module.ModuleBase) > $null + $Runspace.AddParameter('PSModulePath', $env:PSModulePath) > $null - $Status = $Thread.BeginInvoke() + if ($Requirements.RequiredModules) { + $Runspace.AddParameter('RequiredModules', $Requirements.RequiredModules) + } + + $Status = $Runspace.BeginInvoke() do { Start-Sleep -Milliseconds 1 } until ($Status.IsCompleted) - $Thread.EndInvoke($Status) + $Result = $Runspace.EndInvoke($Status) + + if ($Runspace.HadErrors) { + # These will be errors outside the test itself; better propagate them upwards. + foreach ($errorItem in $Runspace.Streams.Error) { + $PSCmdlet.WriteError($errorItem) + } + } + + $Result } finally { - $Thread.Dispose() + $Runspace.Dispose() } } } diff --git a/PSKoans/Public/Get-Karma.ps1 b/PSKoans/Public/Get-Karma.ps1 index db9d72d95..9d5b09a6e 100644 --- a/PSKoans/Public/Get-Karma.ps1 +++ b/PSKoans/Public/Get-Karma.ps1 @@ -37,7 +37,7 @@ 'ListKoans' { Get-PSKoan @GetParams } - "Default" { + default { Write-Verbose 'Sorting koans...' try { $SortedKoanList = Get-PSKoan @GetParams @@ -46,6 +46,17 @@ $PSCmdlet.ThrowTerminatingError($_) } + foreach ($item in $IncludeModule) { + if ($SortedKoanList.Where{ $_.Module -notlike $item }.Count -eq 0) { + $warningString = @( + "Koans for a module name matching '$item' were not found in your user directory." + "Please run Update-PSKoan -Module $item to ensure those modules are present." + ) -join ' ' + + Write-Warning $warningString + } + } + Write-Verbose "Koan files retrieved: $($SortedKoanList.Count)" Write-Verbose 'Counting koans...' [int]$TotalKoans = $SortedKoanList | Measure-Koan @@ -68,9 +79,24 @@ $PSCmdlet.ThrowTerminatingError( (New-PSKoanErrorRecord @ErrorDetails) ) } + if ($Module) { + # No koans were found because modules aren't copied by default. + Update-PSKoan -Module $Module -Confirm:$false + Get-Karma @PSBoundParameters + + return + } + # Something's wrong; possibly a koan folder from older versions, or a folder exists but has no files + $Modules = if ($IncludeModule) { + @{ IncludeModule = $IncludeModule } + } + else { + @{ } + } + Write-Warning 'No koans found in your koan directory. Initiating full reset...' - Update-PSKoan -Confirm:$false + Update-PSKoan -Confirm:$false @Modules Get-Karma @PSBoundParameters # Re-call ourselves with the same parameters return # Skip the rest of the function diff --git a/PSKoans/Public/Get-PSKoan.ps1 b/PSKoans/Public/Get-PSKoan.ps1 index 6158b695f..49645d3d3 100644 --- a/PSKoans/Public/Get-PSKoan.ps1 +++ b/PSKoans/Public/Get-PSKoan.ps1 @@ -45,15 +45,19 @@ function Get-PSKoan { 'Module' { Join-Path -Path $script:ModuleRoot -ChildPath 'Koans' } } - if ($pscmdlet.ParameterSetName -eq 'ListModules') { - $moduleList = Join-Path -Path $ParentPath -ChildPath 'Modules' | - Get-ChildItem -Directory | - Select-Object -ExpandProperty Name + if ($PSCmdlet.ParameterSetName -eq 'ListModules') { + $modulesPath = Join-Path -Path $ParentPath -ChildPath 'Modules' - return $moduleList + if (Test-Path $modulesPath) { + $modulesPath | + Get-ChildItem -Directory | + Select-Object -ExpandProperty Name + } + + return } - $KoanDirectories = switch ($pscmdlet.ParameterSetName) { + $KoanDirectories = switch ($PSCmdlet.ParameterSetName) { 'IncludeModule' { $Module = $IncludeModule Get-ChildItem $ParentPath -Exclude Modules -Directory @@ -61,9 +65,11 @@ function Get-PSKoan { { $Module } { $ModuleRegex = ConvertFrom-WildcardPattern -Pattern $Module - Join-Path -Path $ParentPath -ChildPath 'Modules' | - Get-ChildItem -Directory | - Where-Object { $_.Name -match $ModuleRegex } + $modulesPath = Join-Path -Path $ParentPath -ChildPath 'Modules' + if (Test-Path $modulesPath) { + Get-ChildItem $modulesPath -Directory | + Where-Object { $_.Name -match $ModuleRegex } + } } } diff --git a/PSKoans/Public/Show-Karma.ps1 b/PSKoans/Public/Show-Karma.ps1 index 65dbc5ba5..d0007f589 100644 --- a/PSKoans/Public/Show-Karma.ps1 +++ b/PSKoans/Public/Show-Karma.ps1 @@ -5,9 +5,13 @@ function Show-Karma { [Alias('Invoke-PSKoans', 'Test-Koans', 'Get-Enlightenment', 'Meditate', 'Clear-Path', 'Measure-Karma')] param( [Parameter(ParameterSetName = 'ListKoans')] + [Parameter(ParameterSetName = 'ListKoans-ModuleOnly')] + [Parameter(ParameterSetName = 'ListKoans-IncludeModule')] [Parameter(ParameterSetName = 'ModuleOnly')] [Parameter(ParameterSetName = 'IncludeModule')] [Parameter(ParameterSetName = 'OpenFile')] + [Parameter(ParameterSetName = 'OpenFile-ModuleOnly')] + [Parameter(ParameterSetName = 'OpenFile-IncludeModule')] [Parameter(ParameterSetName = 'Default')] [Alias('Koan', 'File')] [SupportsWildcards()] @@ -15,22 +19,29 @@ function Show-Karma { $Topic, [Parameter(Mandatory, ParameterSetName = 'ModuleOnly')] - [Parameter(ParameterSetName = 'ListKoans')] + [Parameter(Mandatory, ParameterSetName = 'ListKoans-ModuleOnly')] + [Parameter(Mandatory, ParameterSetName = 'OpenFile-ModuleOnly')] [SupportsWildcards()] [string[]] $Module, [Parameter(Mandatory, ParameterSetName = 'IncludeModule')] + [Parameter(Mandatory, ParameterSetName = 'ListKoans-IncludeModule')] + [Parameter(Mandatory, ParameterSetName = 'OpenFile-IncludeModule')] [SupportsWildcards()] [string[]] $IncludeModule, [Parameter(Mandatory, ParameterSetName = 'ListKoans')] + [Parameter(Mandatory, ParameterSetName = 'ListKoans-ModuleOnly')] + [Parameter(Mandatory, ParameterSetName = 'ListKoans-IncludeModule')] [Alias('ListKoans', 'ListTopics')] [switch] $List, [Parameter(Mandatory, ParameterSetName = 'OpenFile')] + [Parameter(Mandatory, ParameterSetName = 'OpenFile-ModuleOnly')] + [Parameter(Mandatory, ParameterSetName = 'OpenFile-IncludeModule')] [Alias('Meditate')] [switch] $Contemplate, @@ -55,13 +66,13 @@ function Show-Karma { $GetParams = @{ } switch ($PSCmdlet.ParameterSetName) { - 'IncludeModule' { $GetParams['IncludeModule'] = $IncludeModule } - 'ModuleOnly' { $GetParams['Module'] = $Module } + { $_ -match 'IncludeModule$' } { $GetParams['IncludeModule'] = $IncludeModule } + { $_ -match 'ModuleOnly$' } { $GetParams['Module'] = $Module } { $PSBoundParameters.ContainsKey('Topic') } { $GetParams['Topic'] = $Topic } } switch ($PSCmdlet.ParameterSetName) { - 'ListKoans' { + { $_ -match '^ListKoans' } { Get-PSKoan @GetParams } 'OpenFolder' { @@ -86,7 +97,7 @@ function Show-Karma { $KoanLocation | Invoke-Item } } - 'OpenFile' { + { $_ -match '^OpenFile' } { # If there is no cached data, we need to call Get-Karma to populate it if (-not $script:CurrentTopic -or ($Topic -and $script:CurrentTopic.Name -notlike $Topic)) { try { @@ -99,7 +110,12 @@ function Show-Karma { } $Editor = Get-PSKoanSetting -Name Editor - $FilePath = (Get-PSKoan -Topic $script:CurrentTopic.Name -Scope User).Path + $KoanParams = @{ + Topic = $script:CurrentTopic.Name + IncludeModule = @( $Module; $IncludeModule ) + Scope = 'User' + } + $FilePath = (Get-PSKoan @KoanParams).Path $LineNumber = $script:CurrentTopic.CurrentLine $Arguments = switch ($Editor) { @@ -140,8 +156,7 @@ function Show-Karma { try { Get-Karma @GetParams | Format-Custom @FormatParams | - Out-String | - Write-Host + Out-Host } catch { $PSCmdlet.ThrowTerminatingError($_) diff --git a/Start-DebugSession.ps1 b/Start-DebugSession.ps1 new file mode 100644 index 000000000..f28582f67 --- /dev/null +++ b/Start-DebugSession.ps1 @@ -0,0 +1,11 @@ +$env:PSModulePath = $( + @( + $env:PSModulePath -split [System.IO.Path]::PathSeparator + $PSScriptRoot + ) | Select-Object -Unique +) -join [System.IO.Path]::PathSeparator + +& $PSScriptRoot/PSKoans.ezformat.ps1 +Import-Module $PSScriptRoot/PSKoans + +### Enter code to test below diff --git a/Tests/Functions/Public/Show-Karma.Tests.ps1 b/Tests/Functions/Public/Show-Karma.Tests.ps1 index a758309d6..15259688a 100644 --- a/Tests/Functions/Public/Show-Karma.Tests.ps1 +++ b/Tests/Functions/Public/Show-Karma.Tests.ps1 @@ -25,7 +25,7 @@ Describe 'Show-Karma' { Context 'Default Behaviour' { BeforeAll { - Mock Write-Host { } + Mock Out-Host { } Mock Get-Karma -ModuleName 'PSKoans' { [PSCustomObject]@{ PSTypeName = 'PSKoans.Result' @@ -50,7 +50,7 @@ Describe 'Show-Karma' { } It 'should write the formatted output to host' { - Assert-MockCalled Write-Host + Assert-MockCalled Out-Host } It 'should call Get-Karma to examine the koans' { @@ -60,7 +60,7 @@ Describe 'Show-Karma' { Context 'With All Koans Completed' { BeforeAll { - Mock Write-Host { } + Mock Out-Host { } Mock Get-Karma -ModuleName 'PSKoans' { [PSCustomObject]@{ PSTypeName = 'PSKoans.CompleteResult' @@ -80,7 +80,7 @@ Describe 'Show-Karma' { Context 'With -ClearScreen Switch' { BeforeAll { Mock Clear-Host { } - Mock Write-Host { } + Mock Out-Host { } Mock Get-Karma -ModuleName 'PSKoans' { [PSCustomObject]@{ PSTypeName = 'PSKoans.Result' @@ -109,7 +109,7 @@ Describe 'Show-Karma' { } It 'should display the rendered output' { - Assert-MockCalled Write-Host + Assert-MockCalled Out-Host } It 'should Invoke-Pester on each of the koans' { @@ -119,7 +119,7 @@ Describe 'Show-Karma' { Context 'With Nonexistent Koans Folder / No Koans Found' { BeforeAll { - Mock Write-Host { } + Mock Out-Host { } Mock Get-PSKoan -ModuleName 'PSKoans' { } Mock Update-PSKoan -ModuleName 'PSKoans' { throw 'Prevent recursion' } Mock Write-Warning @@ -174,7 +174,7 @@ Describe 'Show-Karma' { Context 'With -Topic Parameter' { BeforeAll { - Mock Write-Host { } + Mock Out-Host { } Mock Get-Karma -MockWith { [PSCustomObject]@{ PSTypeName = 'PSKoans.Result' @@ -204,7 +204,7 @@ Describe 'Show-Karma' { Context 'With All Koans in a Single Topic Completed' { BeforeAll { Mock Format-Custom { $InputObject.Complete } - Mock Write-Host { } + Mock Out-Host { } Mock Get-Karma -ModuleName 'PSKoans' { [PSCustomObject]@{ PSTypeName = 'PSKoans.CompleteResult'