From 2de844b01eb642105d35b3f49c43ea27ed9c2c64 Mon Sep 17 00:00:00 2001 From: Joel Sallow <32407840+vexx32@users.noreply.github.com> Date: Sun, 14 Jun 2020 14:30:49 -0400 Subject: [PATCH 01/17] :construction: Prepare CI environment for v5 --- Build/Invoke-ModuleTests.ps1 | 56 +++++++++++------------------- PSKoans/PSKoans.psd1 | 17 +++++---- templates/environment-setup.yml | 2 +- templates/install-built-module.yml | 2 +- 4 files changed, 33 insertions(+), 44 deletions(-) diff --git a/Build/Invoke-ModuleTests.ps1 b/Build/Invoke-ModuleTests.ps1 index 59f352bea..cdb160d70 100644 --- a/Build/Invoke-ModuleTests.ps1 +++ b/Build/Invoke-ModuleTests.ps1 @@ -1,43 +1,29 @@ $Lines = '-' * 70 -$PSVersion = $PSVersionTable.PSVersion - -Write-Host $Lines -Write-Host "STATUS: Testing with PowerShell v$($PSVersionTable.PSVersion)" -Write-Host $Lines - -# Import the module and add temporary entry to PSModulePath for build/test purposes Import-Module 'PSKoans' -$Timestamp = Get-Date -Format "yyyyMMdd-hhmmss" -$TestFile = "PS${PSVersion}_${TimeStamp}_PSKoans.TestResults.xml" -$CodeCoverageFile = "PS${PSVersion}_${TimeStamp}_PSKoans.CodeCoverage.xml" - -$ModuleFolders = @( - Get-Item -Path "$env:PROJECTROOT/PSKoans" - Get-ChildItem -Path "$env:PROJECTROOT/PSKoans" -Directory -Recurse | - Where-Object { 'Koans' -notin $_.Parent.Name, $_.Parent.Parent.Name } -).FullName -join ';' +$PesterVersion = (Get-Module -Name Pester).Version +$PSVersion = $PSVersionTable.PSVersion -# Tell Azure where the test results & code coverage files will be -Write-Host "##vso[task.setvariable variable=TestResults]$TestFile" -Write-Host "##vso[task.setvariable variable=CodeCoverageFile]$CodeCoverageFile" -Write-Host "##vso[task.setvariable variable=SourceFolders]$ModuleFolders" +Write-Host $Lines +Write-Host "TEST: PowerShell Version: $PSVersion" +Write-Host "TEST: Pester Version: $PesterVersion" +Write-Host $Lines -# Gather test results. Store them in a variable and file -$PesterParams = @{ - Path = "$env:PROJECTROOT/Tests" - PassThru = $true - OutputFormat = 'NUnitXml' - OutputFile = "$env:BUILD_ARTIFACTSTAGINGDIRECTORY/$TestFile" - Show = "Header", "Failed", "Summary" - CodeCoverage = (Get-ChildItem -Recurse -Path "$env:PROJECTROOT/PSKoans" -Filter '*.ps*1' -Exclude '*.Koans.ps1').FullName - CodeCoverageOutputFile = "$env:BUILD_ARTIFACTSTAGINGDIRECTORY/$CodeCoverageFile" +try { + # Try/Finally required since -CI will exit with exit code on failure. + Invoke-Pester -Path "$env:PROJECTROOT/Tests" -CI -Output Normal } -$TestResults = Invoke-Pester @PesterParams - -# If tests failed, write errors and exit -if ($TestResults.FailedCount -gt 0) { - Write-Error "Failed $($TestResults.FailedCount) tests; build failed!" - exit $TestResults.FailedCount +finally { + $Timestamp = Get-Date -Format "yyyyMMdd-hhmmss" + $TestFile = "PS${PSVersion}_${TimeStamp}_PSKoans.TestResults.xml" + $CodeCoverageFile = "PS${PSVersion}_${TimeStamp}_PSKoans.CodeCoverage.xml" + + # Tell Azure what the test results & code coverage file names will be + Write-Host "##vso[task.setvariable variable=TestResults]$TestFile" + Write-Host "##vso[task.setvariable variable=CodeCoverageFile]$CodeCoverageFile" + + # Move files generated from Invoke-Pester to expected location + Move-Item -Path './testResults.xml' -Destination "$env:BUILD_ARTIFACTSTAGINGDIRECTORY/$TestFile" + Move-Item -Path './coverage.xml' -Destination "$env:BUILD_ARTIFACTSTAGINGDIRECTORY/$CodeCoverageFile" } diff --git a/PSKoans/PSKoans.psd1 b/PSKoans/PSKoans.psd1 index c786f10db..e76320e4c 100644 --- a/PSKoans/PSKoans.psd1 +++ b/PSKoans/PSKoans.psd1 @@ -54,7 +54,7 @@ RequiredModules = @( @{ ModuleName = 'Pester' - RequiredVersion = '4.10.1' + MinimumVersion = '5.0.2' } ) @@ -68,7 +68,7 @@ # TypesToProcess = @() # Format files (.ps1xml) to be loaded when importing this module - FormatsToProcess = @('PSKoans.format.ps1xml') + FormatsToProcess = @( 'PSKoans.format.ps1xml' ) # Modules to import as nested modules of the module specified in RootModule/ModuleToProcess # NestedModules = @() @@ -86,10 +86,10 @@ 'Move-PSKoanLibrary' - 'Reset-PSKoan' - 'Register-Advice' + 'Reset-PSKoan' + 'Show-Advice' 'Show-Karma' @@ -106,13 +106,17 @@ AliasesToExport = @( 'Invoke-PSKoans' 'Test-Koans' - 'Get-Enlightenment' - 'Measure-Karma' + '__' '____' 'FILL_ME_IN' + 'Clear-Path' + 'Get-Advice' + + 'Get-Enlightenment' + 'Measure-Karma' ) # DSC resources to export from this module @@ -172,5 +176,4 @@ # Default prefix for commands exported from this module. Override the default prefix using Import-Module -Prefix. # DefaultCommandPrefix = '' - } diff --git a/templates/environment-setup.yml b/templates/environment-setup.yml index 8c47ba925..7dc74cbc5 100644 --- a/templates/environment-setup.yml +++ b/templates/environment-setup.yml @@ -17,7 +17,7 @@ steps: $Params.Name = 'Pester' $Params.SkipPublisherCheck = $true - $Params.MaximumVersion = '4.99.99' + $Params.MinimumVersion = '5.0.2' $Params | Out-String | Write-Host Install-Module @Params $Params.Remove('SkipPublisherCheck') diff --git a/templates/install-built-module.yml b/templates/install-built-module.yml index 9977e134f..268f4c651 100644 --- a/templates/install-built-module.yml +++ b/templates/install-built-module.yml @@ -28,7 +28,7 @@ steps: script: | $pesterParams = @{ Name = 'Pester' - MaximumVersion = '4.99.99' + MinimumVersion = '5.0.2' ProviderName = 'NuGet' Path = '${{ parameters.repositoryPath }}' Force = $true From cb053df3b23b4fa16b6d35037b9aa68000e86a39 Mon Sep 17 00:00:00 2001 From: Joel Sallow <32407840+vexx32@users.noreply.github.com> Date: Tue, 16 Jun 2020 00:18:18 -0400 Subject: [PATCH 02/17] :rocket: Fix yaml configuration template --- templates/environment-setup.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/templates/environment-setup.yml b/templates/environment-setup.yml index 7dc74cbc5..634a3f602 100644 --- a/templates/environment-setup.yml +++ b/templates/environment-setup.yml @@ -21,7 +21,7 @@ steps: $Params | Out-String | Write-Host Install-Module @Params $Params.Remove('SkipPublisherCheck') - $Params.Remove('MaximumVersion') + $Params.Remove('MinimumVersion') $Params.Name = 'EZOut' $Params.AllowClobber = $true From 79f079e316d0a770bc7a1617d642f1696c8f2d9e Mon Sep 17 00:00:00 2001 From: Joel Sallow <32407840+vexx32@users.noreply.github.com> Date: Mon, 15 Jun 2020 07:52:42 -0400 Subject: [PATCH 03/17] :arrow_up: Fix pester dependency version in psd1 --- PSKoans/PSKoans.psd1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/PSKoans/PSKoans.psd1 b/PSKoans/PSKoans.psd1 index e76320e4c..df3e9e77a 100644 --- a/PSKoans/PSKoans.psd1 +++ b/PSKoans/PSKoans.psd1 @@ -54,7 +54,7 @@ RequiredModules = @( @{ ModuleName = 'Pester' - MinimumVersion = '5.0.2' + ModuleVersion = '5.0.2' } ) From 075f46912219c85c8fda81101ca0bd78230358f3 Mon Sep 17 00:00:00 2001 From: Joel Sallow <32407840+vexx32@users.noreply.github.com> Date: Mon, 15 Jun 2020 08:59:32 -0400 Subject: [PATCH 04/17] :sparkles: Update module functions for Pester v5 - Utilize built in discovery routines for counting tests - Update Get-Karma/Show-Karma to reference correct result properties. - Update formatters to reference correct properties from result objects :recycle: Adjustments to private functions - Short circuit Get-Karma when no files found - Remove unneeded using statements from Measure-Koan - Qualify type name for SMA types in New-PSKoanErrorRecord --- PSKoans/Private/Assert-UnblockedFile.ps1 | 2 +- PSKoans/Private/Measure-Koan.ps1 | 75 +++++++++-------------- PSKoans/Private/Measure-KoanBlockTest.ps1 | 23 +++++++ PSKoans/Private/New-PSKoanErrorRecord.ps1 | 8 +-- PSKoans/Public/Get-Karma.ps1 | 20 +++--- formatting/PSKoans.Controls.format.ps1 | 39 ++++++------ formatting/PSKoans.Result.format.ps1 | 4 +- 7 files changed, 88 insertions(+), 83 deletions(-) create mode 100644 PSKoans/Private/Measure-KoanBlockTest.ps1 diff --git a/PSKoans/Private/Assert-UnblockedFile.ps1 b/PSKoans/Private/Assert-UnblockedFile.ps1 index 899741c83..00c4913e3 100644 --- a/PSKoans/Private/Assert-UnblockedFile.ps1 +++ b/PSKoans/Private/Assert-UnblockedFile.ps1 @@ -43,7 +43,7 @@ function Assert-UnblockedFile { } } - if (Get-Content -Path $FileInfo.FullName -Stream Zone.Identifier -ErrorAction SilentlyContinue) { + if (Get-Content -Path $FileInfo.FullName -Stream Zone.Identifier -ErrorAction Ignore) { $ErrorDetails = @{ ExceptionType = 'System.IO.FileLoadException' ExceptionMessage = 'Could not read the koan file "{0}". The file is blocked and may have been copied from an Internet location. Use the Unblock-File to remove the block on the file.' -f $FileInfo.FullName diff --git a/PSKoans/Private/Measure-Koan.ps1 b/PSKoans/Private/Measure-Koan.ps1 index 8bbc24606..26d2b4c1e 100644 --- a/PSKoans/Private/Measure-Koan.ps1 +++ b/PSKoans/Private/Measure-Koan.ps1 @@ -1,7 +1,4 @@ -using namespace System.Management.Automation -using namespace System.Management.Automation.Language - -function Measure-Koan { +function Measure-Koan { <# .SYNOPSIS Counts the number of koans in the provided ExternalScriptInfo objects. @@ -10,24 +7,15 @@ function Measure-Koan { files to find all of the 'It' blocks in order to count the total number of Pester tests present in the file. - When provided with a piped list of ExternalScriptInfo objects, sums the entire + When provided with a piped list of KoanInfo objects, sums the entire collection's 'It' blocks and returns a single integer sum. .PARAMETER KoanInfo - Takes an array of ExternalScriptInfo objects (as provided from Get-Command when - passed the path to an external .ps1 script file). + Takes an array of KoanInfo objects (as provided from Get-PSKoan). .EXAMPLE Get-Command .\KoanDirectory\*\*.ps1 | Measure-Koan 422 .NOTES - Measure-Koan is NOT designed to handle dynamic -TestCases values. It will handle - simple counts of directly attached -TestCases hashtables only if they do not - contain variables, pipelines, and other dynamic expressions. - - Handling dynamic scripts with pipelines and variables is beyond scope and will - not be handled; we'd essentially end up reimplementing the PS parser. If it can't - be got with .GetSafeValue() we simply aren't working with it. - Author: Joel Sallow Module: PSKoans .LINK @@ -38,50 +26,45 @@ function Measure-Koan { param( [Parameter(Position = 0, Mandatory, ValueFromPipeline)] [PSTypeName('PSKoans.KoanInfo')] - [object[]] + [psobject[]] $KoanInfo ) begin { $KoanCount = 0 } process { - Write-Verbose "Parsing koan files from [$($KoanInfo.Name -join '], [')]" + Write-Verbose "Discovering koans in [$($KoanInfo.Name -join '], [')]" - # Find all Pester 'It' commands - $ItCommands = @(Get-KoanIt -Path $KoanInfo.Path) + $Result = & (Get-Module Pester) { + [CmdletBinding()] + param( + $Path, + $ExcludePath, + $SessionState + ) - # Find the -TestCases parameters - $TestCasesParameters = $ItCommands.Ast.CommandElements | Where-Object { - $_ -is [CommandParameterAst] -and - $_.ParameterName -eq 'TestCases' - } + $_Pester_State_Backup = $state.PSObject.Copy() + $state.Stack = [System.Collections.Stack]@() + try { + Reset-TestSuiteState - if ($TestCasesParameters) { - # Get the right CommandElements indexes for their arguments - $Indexes = $TestCasesParameters.ForEach{$ItCommands.Ast.CommandElements.IndexOf($_) + 1} - # Get value of the argument for each -TestCases - $ParameterArgument = $ItCommands.Ast.CommandElements[$Indexes] + # to avoid Describe thinking that we run in interactive mode + $invokedViaInvokePester = $true - try { - $TestCaseCount = $ParameterArgument.SafeGetValue().Count + $fileList = Find-File -Path $Path -ExcludePath $ExcludePath -Extension '.Koans.ps1' + $containers = foreach ($file in $fileList) { + New-BlockContainerObject -File (Get-Item $file) + } + + Find-Test -BlockContainer $containers -SessionState $SessionState } - catch { - # We aren't parsing complex or variable expressions, so exit here (violently) - $ParseException = [ParseException]::new( - "Unable to parse unsafe expression in Koan -TestCase syntax.", - $_.Exception - ) - $ErrorRecord = [ErrorRecord]::new( - $ParseException, - "PSKoans.KoanAstParseError", - [ErrorCategory]::ParserError, - $ParameterValues.PipelineElements.Extent.Text - ) - $PSCmdlet.ThrowTerminatingError($ErrorRecord) + finally { + $state = $_Pester_State_Backup + Remove-Variable -Name _Pester_State_Backup } - } + } -Path $KoanInfo.Path -SessionState $PSCmdlet.SessionState - $KoanCount += $TestCaseCount + $ItCommands.Count - $TestCasesParameters.Count + $KoanCount += Measure-KoanTestBlock $Result } end { Write-Verbose "Total Koans: $KoanCount" diff --git a/PSKoans/Private/Measure-KoanBlockTest.ps1 b/PSKoans/Private/Measure-KoanBlockTest.ps1 new file mode 100644 index 000000000..7304cf91d --- /dev/null +++ b/PSKoans/Private/Measure-KoanBlockTest.ps1 @@ -0,0 +1,23 @@ +function Measure-KoanTestBlock { + [CmdletBinding()] + param( + [Parameter(Mandatory)] + [psobject] + $Block + ) + + $Label = if ($Block.Name) { + "Block $($Block.Name)" + } + else { + "File $($Block.BlockContainer.Item)" + } + + $Count = $Block.Tests.Count + foreach ($testBlock in $Block.Blocks) { + $Count += Measure-KoanTestBlock $testBlock + } + + $Count + Write-Information "$Label Tests: $Count" +} diff --git a/PSKoans/Private/New-PSKoanErrorRecord.ps1 b/PSKoans/Private/New-PSKoanErrorRecord.ps1 index 0588f03f8..e2b7fd9a8 100644 --- a/PSKoans/Private/New-PSKoanErrorRecord.ps1 +++ b/PSKoans/Private/New-PSKoanErrorRecord.ps1 @@ -40,7 +40,7 @@ function New-PSKoanErrorRecord { #> [CmdletBinding(DefaultParameterSetName = 'Default')] - [OutputType([ErrorRecord])] + [OutputType([System.Management.Automation.ErrorRecord])] param( [Parameter(ParameterSetName = 'Default')] [Alias('Type')] @@ -73,8 +73,8 @@ function New-PSKoanErrorRecord { [Parameter()] [Alias('Category')] - [ErrorCategory] - $ErrorCategory = [ErrorCategory]::NotSpecified, + [System.Management.Automation.ErrorCategory] + $ErrorCategory = [System.Management.Automation.ErrorCategory]::NotSpecified, [Parameter()] [PSObject] @@ -89,7 +89,7 @@ function New-PSKoanErrorRecord { $ErrorId = "PSKoans.$($ErrorId)" } - [ErrorRecord]::new( + [System.Management.Automation.ErrorRecord]::new( $Exception, $ErrorId, $ErrorCategory, diff --git a/PSKoans/Public/Get-Karma.ps1 b/PSKoans/Public/Get-Karma.ps1 index 9d5b09a6e..cfb05e706 100644 --- a/PSKoans/Public/Get-Karma.ps1 +++ b/PSKoans/Public/Get-Karma.ps1 @@ -59,9 +59,8 @@ Write-Verbose "Koan files retrieved: $($SortedKoanList.Count)" Write-Verbose 'Counting koans...' - [int]$TotalKoans = $SortedKoanList | Measure-Koan - if ($TotalKoans -eq 0) { + if ($SortedKoanList.Count -eq 0) { if ($Topic) { $Message = @( 'Could not find any PSKoans topics matching Topic(s): {0}.' @@ -102,6 +101,7 @@ return # Skip the rest of the function } + [int]$TotalKoans = Measure-Koan $SortedKoanList $KoansPassed = 0 foreach ($KoanFile in $SortedKoanList) { @@ -111,7 +111,7 @@ $PesterTests = Invoke-Koan @{ Script = $KoanFile.Path PassThru = $true - Show = 'None' + Output = 'None' } $KoansPassed += $PesterTests.PassedCount @@ -124,27 +124,25 @@ } $Meditation = if ($PesterTests.FailedCount -gt 0) { - $NextKoanFailed = $PesterTests.TestResult | - Where-Object Result -eq 'Failed' | - Select-Object -First 1 + $NextKoanFailed = $PesterTests.Failed[0] $script:CurrentTopic = @{ Name = $KoanFile.Topic Completed = $PesterTests.PassedCount Total = $PesterTests.TotalCount - CurrentLine = ($NextKoanFailed.StackTrace -split '\r?\n')[1] -replace ':.+' + CurrentLine = ($NextKoanFailed.ErrorRecord.DisplayStackTrace -split '\r?\n')[1] -replace ':.+' } [PSCustomObject]@{ PSTypeName = 'PSKoans.Result' - Describe = $NextKoanFailed.Describe + Describe = $NextKoanFailed.Block.Name It = $NextKoanFailed.Name - Expectation = $NextKoanFailed.ErrorRecord - Meditation = $NextKoanFailed.StackTrace + Expectation = $NextKoanFailed.ErrorRecord.DisplayErrorMessage + Meditation = $NextKoanFailed.ErrorRecord.DisplayStackTrace KoansPassed = $KoansPassed TotalKoans = $TotalKoans CurrentTopic = [PSCustomObject]$script:CurrentTopic - Results = $PesterTests.TestResult + Results = $PesterTests.Tests RequestedTopic = $Topic } } diff --git a/formatting/PSKoans.Controls.format.ps1 b/formatting/PSKoans.Controls.format.ps1 index 557ed2746..1a3c9fd02 100644 --- a/formatting/PSKoans.Controls.format.ps1 +++ b/formatting/PSKoans.Controls.format.ps1 @@ -57,7 +57,7 @@ Write-FormatCustomView -AsControl -Name Prompt.Koan -Action { Write-FormatViewExpression -ScriptBlock { & (Get-Module -Name PSKoans) { $ReplacementPattern = '$1 {0} ' -f [char]0x258c - (Get-Random -InputObject $script:MeditationStrings) -replace '^|(\r?\n)', $ReplacementPattern + ($script:MeditationStrings | Get-Random) -replace '^|(\r?\n)', $ReplacementPattern } } -ForegroundColor 'PSKoans.Meditation.Emphasis' @@ -123,30 +123,31 @@ Write-FormatCustomView -AsControl -Name Prompt.Details -Action { Write-FormatViewExpression -Newline Write-FormatViewExpression -If { - $_.Describe -and - $_.Describe -ne $global:_Koan_Describe + $_.Block.Name -and + $_.Block.Name -ne $global:_Koan_Describe } -ScriptBlock { - $global:_Koan_Describe = $_.Describe + $global:_Koan_Describe = $_.Block.Name $Indent = " " * 4 - '{0}Describing {1}{2}' -f $Indent, $_.Describe, [Environment]::NewLine + '{0}[+] {1}{2}' -f $Indent, $_.Block.Name, [Environment]::NewLine } -ForegroundColor 'PSKoans.Meditation.Text' - Write-FormatViewExpression -If { - $_.Context -and - $_.Context -ne $global:_Koan_Context - } -ScriptBlock { - $global:_Koan_Context = $_.Context - $IndentSpaces = 4 - if ($_.Context) { $IndentSpaces += 2 } - $Indent = " " * $IndentSpaces - - '{0}{1}{2}' -f $Indent, $_.Context, [Environment]::NewLine - } -ForegroundColor 'PSKoans.Meditation.Emphasis' - + <# OMITTED - Pester v5 doesn't distinguish its blocks (Describe/Context aren't distinguishable) + Write-FormatViewExpression -If { + $_.Context -and + $_.Context -ne $global:_Koan_Context + } -ScriptBlock { + $global:_Koan_Context = $_.Context + $IndentSpaces = 4 + if ($_.Context) { $IndentSpaces += 2 } + $Indent = " " * $IndentSpaces + + '{0}{1}{2}' -f $Indent, $_.Context, [Environment]::NewLine + } -ForegroundColor 'PSKoans.Meditation.Emphasis' + #> Write-FormatViewExpression -If { $_.Passed } -ScriptBlock { $IndentSpaces = 4 - if ($_.Context) { $IndentSpaces += 4 } elseif ($_.Describe) { $Indent += 2 } + if ($_.Block.Name) { $IndentSpaces += 2 } # elseif ($_.Context) { $IndentSpaces += 2 } $Indent = " " * $IndentSpaces '{0}[{1}] It {2}' -f $Indent, [char]0x25b8, $_.Name @@ -154,7 +155,7 @@ Write-FormatCustomView -AsControl -Name Prompt.Details -Action { Write-FormatViewExpression -If { -not $_.Passed } -ScriptBlock { $IndentSpaces = 4 - if ($_.Context) { $IndentSpaces += 4 } elseif ($_.Describe) { $Indent += 2 } + if ($_.Block.Name) { $IndentSpaces += 2 } # elseif ($_.Context) { $IndentSpaces += 4 } $Indent = " " * $IndentSpaces '{0}[{1}] It {2}' -f $Indent, [char]0xd7, $_.Name diff --git a/formatting/PSKoans.Result.format.ps1 b/formatting/PSKoans.Result.format.ps1 index 6010a09c4..32f5bf05a 100644 --- a/formatting/PSKoans.Result.format.ps1 +++ b/formatting/PSKoans.Result.format.ps1 @@ -80,8 +80,8 @@ Write-FormatView -TypeName PSKoans.Result -Name Detailed -Action { Write-FormatViewExpression -ScriptBlock { $_.Results } -ControlName Prompt.Details -Enumerate - $ExecutionContext.SessionState.PSVariable.Remove("Global:_Koan_Describe") - $ExecutionContext.SessionState.PSVariable.Remove("Global:_Koan_Context") + $ExecutionContext.SessionState.PSVariable.Remove("global:_Koan_Describe") + # $ExecutionContext.SessionState.PSVariable.Remove("global:_Koan_Context") Write-FormatViewExpression -If { $_.RequestedTopic.Count -ne 1 } -ControlName Prompt.ProgressBar -ScriptBlock { @{ From 7bed7e4bbab3f3276ff85d20b314d768e877cc0b Mon Sep 17 00:00:00 2001 From: Joel Sallow <32407840+vexx32@users.noreply.github.com> Date: Tue, 16 Jun 2020 13:18:10 -0400 Subject: [PATCH 05/17] :art: Adjust -Detailed formatter --- formatting/PSKoans.Controls.format.ps1 | 66 ++++++++++++++++++++++---- formatting/PSKoans.Result.format.ps1 | 2 +- 2 files changed, 58 insertions(+), 10 deletions(-) diff --git a/formatting/PSKoans.Controls.format.ps1 b/formatting/PSKoans.Controls.format.ps1 index 1a3c9fd02..00acd3f10 100644 --- a/formatting/PSKoans.Controls.format.ps1 +++ b/formatting/PSKoans.Controls.format.ps1 @@ -124,15 +124,32 @@ Write-FormatCustomView -AsControl -Name Prompt.Details -Action { Write-FormatViewExpression -If { $_.Block.Name -and - $_.Block.Name -ne $global:_Koan_Describe + $_.Block.Name -ne $global:_Koan_Block_Name } -ScriptBlock { - $global:_Koan_Describe = $_.Block.Name - $Indent = " " * 4 + function Get-Depth { + param($Block) + + if (-not $Block) { + 0 + return + } + + if (-not $Block.Parent) { + 1 + } + else { + 1 + (Get-Depth -Block $Block.Parent) + } + } + + $global:_Koan_Block_Name = $_.Block.Name + $Depth = Get-Depth $_.Block + $Indent = " " * (4 * $Depth) - '{0}[+] {1}{2}' -f $Indent, $_.Block.Name, [Environment]::NewLine + '{0}|{1}| {2}{3}' -f $Indent, [char]0x39e, $_.Block.Name, [Environment]::NewLine } -ForegroundColor 'PSKoans.Meditation.Text' - <# OMITTED - Pester v5 doesn't distinguish its blocks (Describe/Context aren't distinguishable) + <# OMITTED - Pester v5 doesn't distinguish its blocks after runs (Describe/Context aren't distinguishable) Write-FormatViewExpression -If { $_.Context -and $_.Context -ne $global:_Koan_Context @@ -146,16 +163,47 @@ Write-FormatCustomView -AsControl -Name Prompt.Details -Action { } -ForegroundColor 'PSKoans.Meditation.Emphasis' #> Write-FormatViewExpression -If { $_.Passed } -ScriptBlock { - $IndentSpaces = 4 - if ($_.Block.Name) { $IndentSpaces += 2 } # elseif ($_.Context) { $IndentSpaces += 2 } + function Get-Depth { + param($Block) + + if (-not $Block) { + 0 + return + } + + if (-not $Block.Parent) { + 1 + } + else { + 1 + (Get-Depth -Block $Block.Parent) + } + } + + $IndentSpaces = 2 + 4 * (Get-Depth $_.Block) $Indent = " " * $IndentSpaces '{0}[{1}] It {2}' -f $Indent, [char]0x25b8, $_.Name } -ForegroundColor 'PSKoans.Meditation.Passed' Write-FormatViewExpression -If { -not $_.Passed } -ScriptBlock { - $IndentSpaces = 4 - if ($_.Block.Name) { $IndentSpaces += 2 } # elseif ($_.Context) { $IndentSpaces += 4 } + function Get-Depth { + param($Block) + + if (-not $Block) { + 0 + return + } + + if (-not $Block.Parent) { + 1 + } + else { + 1 + (Get-Depth -Block $Block.Parent) + } + } + + $Depth = Get-Depth $_.Block + $IndentSpaces = 2 + 4 * $Depth $Indent = " " * $IndentSpaces '{0}[{1}] It {2}' -f $Indent, [char]0xd7, $_.Name diff --git a/formatting/PSKoans.Result.format.ps1 b/formatting/PSKoans.Result.format.ps1 index 32f5bf05a..066c1f710 100644 --- a/formatting/PSKoans.Result.format.ps1 +++ b/formatting/PSKoans.Result.format.ps1 @@ -80,7 +80,7 @@ Write-FormatView -TypeName PSKoans.Result -Name Detailed -Action { Write-FormatViewExpression -ScriptBlock { $_.Results } -ControlName Prompt.Details -Enumerate - $ExecutionContext.SessionState.PSVariable.Remove("global:_Koan_Describe") + $ExecutionContext.SessionState.PSVariable.Remove("global:_Koan_Block_Name") # $ExecutionContext.SessionState.PSVariable.Remove("global:_Koan_Context") Write-FormatViewExpression -If { $_.RequestedTopic.Count -ne 1 } -ControlName Prompt.ProgressBar -ScriptBlock { From c611e926b886c6f3bef676302e7d0890fe25315b Mon Sep 17 00:00:00 2001 From: Joel Sallow <32407840+vexx32@users.noreply.github.com> Date: Tue, 16 Jun 2020 23:56:01 -0400 Subject: [PATCH 06/17] :rocket: Rebuild format generator file --- PSKoans.ezformat.ps1 | 47 ++++++++++++++++++++++---------------------- 1 file changed, 23 insertions(+), 24 deletions(-) diff --git a/PSKoans.ezformat.ps1 b/PSKoans.ezformat.ps1 index e4168bdce..dff285117 100644 --- a/PSKoans.ezformat.ps1 +++ b/PSKoans.ezformat.ps1 @@ -1,38 +1,37 @@ -#Requires -Modules EZOut -# Install-Module EZOut -# or https://github.com/StartAutomating/EZOut - -$VerbosePreference = 'Continue' - -$ModuleName = $MyInvocation.MyCommand.Name -replace '\.ezformat\.ps1', '' -$ModuleFolder = Get-Item -Path "$PSScriptRoot/PSKoans" | Select-Object -ExpandProperty FullName - -Write-Verbose "Building format file for '$ModuleName' in '$ModuleFolder'" - +#requires -Module EZOut +# Install-Module EZOut or https://github.com/StartAutomating/EZOut +$myFile = $MyInvocation.MyCommand.ScriptBlock.File +$myModuleName = 'PSKoans' +$myRoot = "$PSScriptRoot" +Push-Location $myRoot $formatting = @( + # Add your own Write-FormatView here, + # or put them in a Formatting or Views directory foreach ($potentialDirectory in 'formatting', 'views') { - $path = Join-Path $PSScriptRoot -ChildPath $potentialDirectory - if (Test-Path $path) { - Get-ChildItem -Path $path | ForEach-Object { - Write-Verbose "Processing file '$($_.FullName)'" - Import-FormatView -FilePath $_.FullName - } - } + Join-Path $myRoot -ChildPath $potentialDirectory | + Get-ChildItem -ea ignore | + Import-FormatView -FilePath { $_.Fullname } } ) +$destinationRoot = "$PSScriptRoot/PSKoans" + if ($formatting) { - $formatFilePath = Join-Path $ModuleFolder -ChildPath "$ModuleName.format.ps1xml" - Write-Verbose "Writing format file to '$formatFilePath'" - $formatting | Out-FormatData -Module $ModuleName | Set-Content $formatFilePath -Encoding UTF8 + $myFormatFile = Join-Path $destinationRoot "$myModuleName.format.ps1xml" + $formatting | Out-FormatData -Module $MyModuleName | Set-Content $myFormatFile -Encoding UTF8 } $types = @( # Add your own Write-TypeView statements here + # or declare them in the 'Types' directory + Join-Path $myRoot Types | + Get-Item -ea ignore | + Import-TypeView + ) if ($types) { - $TypesFile = Join-Path $PSScriptRoot "$ModuleName.types.ps1xml" - Write-Verbose "Writing types file to '$TypesFile'" - $types | Out-TypeData | Set-Content $TypesFile -Encoding UTF8 + $myTypesFile = Join-Path $destinationRoot "$myModuleName.types.ps1xml" + $types | Out-TypeData | Set-Content $myTypesFile -Encoding UTF8 } +Pop-Location From 2d7e63f6709ca7e19f4a419a675f7754e5a33cc1 Mon Sep 17 00:00:00 2001 From: Joel Sallow <32407840+vexx32@users.noreply.github.com> Date: Sun, 14 Jun 2020 14:45:06 -0400 Subject: [PATCH 07/17] :white_check_mark: Update tests for Pester v5 - Update sanity tests - Update private function tests - Update public function tests :white_check_mark: :warning: Skip Measure-Koan tests These tests are currently unstable and prone to corrupting Pester's internal state. Without a better option at the moment, we'll need to skip these tests for the time being. The function has been manually verified and seems to be more accurate than the original version. --- .../Private/Assert-UnblockedFile.Tests.ps1 | 95 +-- .../ConvertFrom-WildcardPattern.Tests.ps1 | 39 +- Tests/Functions/Private/Get-KoanAst.Tests.ps1 | 93 +-- .../Private/Get-KoanAttribute.Tests.ps1 | 216 +++---- Tests/Functions/Private/Get-KoanIt.Tests.ps1 | 83 +-- Tests/Functions/Private/Invoke-Koan.Tests.ps1 | 56 +- .../Functions/Private/Measure-Koan.Tests.ps1 | 144 +++-- .../Private/Update-PSKoanFile.Tests.ps1 | 119 ++-- Tests/Functions/Public/Get-Blank.Tests.ps1 | 5 +- Tests/Functions/Public/Get-Karma.Tests.ps1 | 235 ++++---- Tests/Functions/Public/Get-PSKoan.Tests.ps1 | 99 ++-- .../Public/Get-PSKoanLocation.Tests.ps1 | 37 +- .../Public/Get-PSKoanSetting.Tests.ps1 | 43 +- .../Public/Move-PSKoanLibrary.Tests.ps1 | 51 +- .../Public/Register-Advice.Tests.ps1 | 101 ++-- Tests/Functions/Public/Reset-PSKoan.Tests.ps1 | 303 +++++----- .../Public/Set-PSKoanLocation.Tests.ps1 | 5 +- .../Public/Set-PSKoanSetting.Tests.ps1 | 55 +- Tests/Functions/Public/Show-Advice.Tests.ps1 | 127 ++-- Tests/Functions/Public/Show-Karma.Tests.ps1 | 550 +++++++++--------- .../Functions/Public/Update-PSKoan.Tests.ps1 | 166 +++--- Tests/KoanValidation.Tests.ps1 | 70 +-- Tests/ModuleValidation.Tests.ps1 | 77 +-- 23 files changed, 1423 insertions(+), 1346 deletions(-) diff --git a/Tests/Functions/Private/Assert-UnblockedFile.Tests.ps1 b/Tests/Functions/Private/Assert-UnblockedFile.Tests.ps1 index 536c47ee6..2ab1b9ea7 100644 --- a/Tests/Functions/Private/Assert-UnblockedFile.Tests.ps1 +++ b/Tests/Functions/Private/Assert-UnblockedFile.Tests.ps1 @@ -1,59 +1,64 @@ -#region Header -if (-not (Get-Module PSKoans)) { - $moduleBase = Join-Path -Path $psscriptroot.Substring(0, $psscriptroot.IndexOf('\Tests')) -ChildPath 'PSKoans' +#Requires -Modules PSKoans - Import-Module $moduleBase -Force -} -#endregion - -if ($PSVersionTable.PSEdition -eq 'Desktop' -or $PSVersionTable.Platform -eq 'Win32NT') { - InModuleScope PSKoans { - Describe Assert-UnblockedFile { - BeforeAll { - $defaultParams = @{ - FileInfo = [System.IO.FileInfo](Join-Path -Path $TestDrive -ChildPath 'AboutSomething.Koans.ps1') - PassThru = $true - } - } +#region Discovery +$SkipTests = $PSVersionTable.PSEdition -ne 'Desktop' -or $PSVersionTable.Platform -ne 'Win32NT' +#endregion Discovery - BeforeEach { - Set-Content -Path $defaultParams.FileInfo.FullName -Value @' - using module PSKoans +Describe 'Assert-UnblockedFile' -Skip:$SkipTests { - [Koan(Position = 1)] - param ( ) + BeforeAll { + $defaultParams = @{ + FileInfo = [System.IO.FileInfo](Join-Path -Path $TestDrive -ChildPath 'AboutSomething.Koans.ps1') + PassThru = $true + } + } - Describe 'About something' { - It 'Has examples' { - $true | Should -BeTrue - } - } -'@ - } + BeforeEach { + Set-Content -Path $defaultParams.FileInfo.FullName -Value @' + using module PSKoans + [Koan(Position = 1)] + param() + + Describe 'About something' { - AfterEach { - Remove-Item -Path $defaultParams.FileInfo.FullName + It 'Has examples' { + $true | Should -BeTrue + } } +'@ + } - Context 'File is blocked' { - BeforeEach { - Set-Content -Path $defaultParams.FileInfo.FullName -Stream Zone.Identifier -Value @' - [ZoneTransfer] - ZoneId=3 - ReferrerUrl=C:\Downloads\File.zip + AfterEach { + Remove-Item -Path $defaultParams.FileInfo.FullName + } + + Context 'File With External Zone Identifier' { + + BeforeEach { + Set-Content -Path $defaultParams.FileInfo.FullName -Stream Zone.Identifier -Value @' + [ZoneTransfer] + ZoneId=3 + ReferrerUrl=C:\Downloads\File.zip '@ - } + } - It 'should throw a terminating error if the file is blocked' { - { Assert-UnblockedFile @defaultParams } | Should -Throw -ErrorId 'PSKoans.KoanFileIsBlocked' + It 'should throw a terminating error if the file is blocked' { + { + InModuleScope 'PSKoans' -Parameters @{ Params = $defaultParams } { + param($Params) + Assert-UnblockedFile @Params } - } + } | Should -Throw -ErrorId 'PSKoans.KoanFileIsBlocked' + } + } - Context 'File is not blocked' { - It 'returns the original object with -PassThru if the file is not blocked' { - Assert-UnblockedFile @defaultParams | Should -BeOfType [System.IO.FileInfo] - } - } + Context 'File Without Zone Identifier' { + + It 'returns the original object with -PassThru if the file is not blocked' { + InModuleScope 'PSKoans' -Parameters @{ Params = $defaultParams } { + param($Params) + Assert-UnblockedFile @Params + } | Should -BeOfType [System.IO.FileInfo] } } } diff --git a/Tests/Functions/Private/ConvertFrom-WildcardPattern.Tests.ps1 b/Tests/Functions/Private/ConvertFrom-WildcardPattern.Tests.ps1 index 83cf5d1c8..96ac71f97 100644 --- a/Tests/Functions/Private/ConvertFrom-WildcardPattern.Tests.ps1 +++ b/Tests/Functions/Private/ConvertFrom-WildcardPattern.Tests.ps1 @@ -1,29 +1,26 @@ #Requires -Modules PSKoans -InModuleScope 'PSKoans' { - Describe 'ConvertFrom-WildcardPattern' { +Describe 'ConvertFrom-WildcardPattern' { - It 'adds start and end of string anchors to an explicit value' { - ConvertFrom-WildcardPattern 'AboutArrays' | Should -Be '^AboutArrays$' - } - - It 'joins multiple expressions with or' { - ConvertFrom-WildcardPattern 'AboutArrays', 'AboutComparison' | Should -Be '^AboutArrays$|^AboutComparison$' - } + It 'adds start and end of string anchors to an explicit value' { + InModuleScope 'PSKoans' { ConvertFrom-WildcardPattern 'AboutArrays' } | Should -Be '^AboutArrays$' + } + It 'joins multiple expressions with |' { + InModuleScope 'PSKoans' { ConvertFrom-WildcardPattern 'AboutArrays', 'AboutComparison' } | + Should -Be '^AboutArrays$|^AboutComparison$' + } - It 'Replaces wildcard characters with regex equivalent' -TestCases @( - @{ Pattern = 'About*son'; Expected = '^About.*son$' } - @{ Pattern = 'AboutArrays*'; Expected = '^AboutArrays' } - @{ Pattern = '*Arrays'; Expected = 'Arrays$' } - @{ Pattern = '*Array*'; Expected = 'Array' } - ) { - param ( - $Pattern, - $Expected - ) + It 'replaces wildcard characters with regex equivalent' -TestCases @( + @{ Pattern = 'About*son'; Expected = '^About.*son$' } + @{ Pattern = 'AboutArrays*'; Expected = '^AboutArrays' } + @{ Pattern = '*Arrays'; Expected = 'Arrays$' } + @{ Pattern = '*Array*'; Expected = 'Array' } + ) { + InModuleScope 'PSKoans' -Parameters @{ Pattern = $Pattern } { + param($Pattern) - ConvertFrom-WildcardPattern $Pattern | Should -Be $Expected - } + ConvertFrom-WildcardPattern $Pattern + } | Should -Be $Expected } } diff --git a/Tests/Functions/Private/Get-KoanAst.Tests.ps1 b/Tests/Functions/Private/Get-KoanAst.Tests.ps1 index 04551fcb6..cb16f9339 100644 --- a/Tests/Functions/Private/Get-KoanAst.Tests.ps1 +++ b/Tests/Functions/Private/Get-KoanAst.Tests.ps1 @@ -1,57 +1,66 @@ -#region Header -if (-not (Get-Module PSKoans)) { - $moduleBase = Join-Path -Path $psscriptroot.Substring(0, $psscriptroot.IndexOf('\Tests')) -ChildPath 'PSKoans' +#Requires -Modules PSKoans - Import-Module $moduleBase -Force -} -#endregion - -InModuleScope PSKoans { - Describe Get-KoanAst { - BeforeAll { - $path = Join-Path $TestDrive 'AboutSomething.Koans.ps1' - - Set-Content -Path $path -Value @' - using module PSKoans - [Koan(Position = 1)] - param() - <# - About Something - #> - Describe 'Something' { - It 'Has some examples' { - $true | Should -BeTrue - } +Describe 'Get-KoanAst' { + + BeforeAll { + $path = Join-Path $TestDrive 'AboutSomething.Koans.ps1' + + Set-Content -Path $path -Value @' + using module PSKoans + [Koan(Position = 1)] + param() + <# + About Something + #> + Describe 'Something' { + It 'Has some examples' { + $true | Should -BeTrue } + } '@ + } + + It 'excludes the "using module PSKoans" statement from the AST' { + $ast = InModuleScope 'PSKoans' -Parameters @{ Path = $path } { + param($Path) + Get-KoanAst -Path $Path } - It 'Excludes the "using module PSKoans" statement from the AST' { - $ast = Get-KoanAst -Path $path + $ast.UsingStatements | Should -BeNullOrEmpty + } - $ast.UsingStatements | Should -BeNullOrEmpty - } + It 'maintains consistency of position data when reading and modifying the source' { + $tokens = $errors = $null + $originalAst = [System.Management.Automation.Language.Parser]::ParseFile( + $path, + [Ref]$tokens, + [Ref]$errors + ) - It 'When reading and modifying the source, position data is consistent' { - $tokens = $errors = @() - $originalAst = [System.Management.Automation.Language.Parser]::ParseFile( - $path, - [Ref]$tokens, - [Ref]$errors - ) - $originalItBlock = $originalAst.Find( { + $originalItBlock = $originalAst.Find( + { $args[0] -is [System.Management.Automation.Language.CommandAst] -and $args[0].GetCommandName() -eq 'It' - }, $true) + }, + $true + ) - $modifiedAst = Get-KoanAst -Path $path - $modifiedItBlock = $modifiedAst.Find( { + $modifiedAst = InModuleScope 'PSKoans' -Parameters @{ Path = $path } { + param($Path) + Get-KoanAst -Path $Path + } + + $modifiedItBlock = $modifiedAst.Find( + { $args[0] -is [System.Management.Automation.Language.CommandAst] -and $args[0].GetCommandName() -eq 'It' - }, $true) + }, + $true + ) - $modifiedItBlock.Extent.StartOffset | Should -Be $originalItBlock.Extent.StartOffset - $modifiedItBlock.Extent.EndOffset | Should -Be $originalItBlock.Extent.EndOffset - } + $modifiedItBlock.Extent.StartOffset | + Should -Be $originalItBlock.Extent.StartOffset -Because 'the start offsets should match' + $modifiedItBlock.Extent.EndOffset | + Should -Be $originalItBlock.Extent.EndOffset -Because 'the end offsets should match' } } diff --git a/Tests/Functions/Private/Get-KoanAttribute.Tests.ps1 b/Tests/Functions/Private/Get-KoanAttribute.Tests.ps1 index 72fe2d14a..2ba7d11ff 100644 --- a/Tests/Functions/Private/Get-KoanAttribute.Tests.ps1 +++ b/Tests/Functions/Private/Get-KoanAttribute.Tests.ps1 @@ -1,141 +1,159 @@ -#region Header -if (-not (Get-Module PSKoans)) { - $moduleBase = Join-Path -Path $psscriptroot.Substring(0, $psscriptroot.IndexOf('\Tests')) -ChildPath 'PSKoans' +#Requires -Modules PSKoans - Import-Module $moduleBase -Force -} -#endregion +Describe 'Get-KoanAttribute' { -InModuleScope PSKoans { - Describe Get-KoanAttribute { - BeforeAll { - $defaultParams = @{ - Path = Join-Path $TestDrive 'AboutSomething.Koans.ps1' - } + BeforeAll { + $filePath = @{ + Path = Join-Path $TestDrive -ChildPath 'AboutSomething.Koans.ps1' } + } - Context 'Content has no errors' { - BeforeAll { - Mock Get-KoanAst { - { - [Koan(Position = 1)] - param( ) - - Describe 'About something' { - It 'Has examples' { - $true | Should -BeTrue - } - } - }.Ast - } - } - - It 'Gets the position argument from the Koan attribute' { - $attributeInfo = Get-KoanAttribute @defaultParams + Context 'Content has no errors' { - $attributeInfo | Should -Not -BeNullOrEmpty - $attributeInfo.Position | Should -Be 1 - } + BeforeAll { + Mock 'Get-KoanAst' -ModuleName 'PSKoans' { + { + [Koan(Position = 1)] + param() - It 'When Module is not set, uses a default value for Module' { - $attributeInfo = Get-KoanAttribute @defaultParams + Describe 'About something' { - $attributeInfo | Should -Not -BeNullOrEmpty - $attributeInfo.Module | Should -Be ([KoanAttribute]::new().Module) + It 'Has examples' { + $true | Should -BeTrue + } + } + }.Ast } } - Context 'Module declared' { - BeforeAll { - Mock Get-KoanAst { - { - [Koan(Position = 1, Module = 'Name')] - param( ) - - Describe 'About something' { - It 'Has examples' { - $true | Should -BeTrue - } - } - }.Ast - } + It 'gets the position argument from the Koan attribute' { + $attributeInfo = InModuleScope 'PSKoans' -Parameters $filePath { + param($Path) + Get-KoanAttribute $Path } - It 'When Module is set, retrieves the value for the module' { - $attributeInfo = Get-KoanAttribute @defaultParams + $attributeInfo | Should -Not -BeNullOrEmpty + $attributeInfo.Position | Should -Be 1 + } - $attributeInfo | Should -Not -BeNullOrEmpty - $attributeInfo.Position | Should -Be 1 - $attributeInfo.Module | Should -Be 'Name' + It 'uses a default value for Module when it is not set' { + $attributeInfo = InModuleScope 'PSKoans' -Parameters $filePath { + param($Path) + Get-KoanAttribute $Path } + + $attributeInfo | Should -Not -BeNullOrEmpty + $attributeInfo.Module | Should -Be ([KoanAttribute]::new().Module) } + } + + Context 'Module declared' { + + BeforeAll { + Mock 'Get-KoanAst' -ModuleName 'PSKoans' { + { + [Koan(Position = 1, Module = 'Name')] + param() - Context 'Full attribute name used' { - BeforeAll { - Mock Get-KoanAst { - { - [KoanAttribute(Position = 1)] - param( ) - - Describe 'About something' { - It 'Has examples' { - $true | Should -BeTrue - } + Describe 'About something' { + + It 'has examples' { + $true | Should -BeTrue } - }.Ast - } + } + }.Ast } + } - It 'Returns attribute information the full name is used' { - $attributeInfo = Get-KoanAttribute @defaultParams - - $attributeInfo | Should -Not -BeNullOrEmpty - $attributeInfo.Position | Should -Be 1 + It 'retrieves the value for the module when it is set' { + $attributeInfo = InModuleScope 'PSKoans' -Parameters $filePath { + param($Path) + Get-KoanAttribute $Path } + + $attributeInfo | Should -Not -BeNullOrEmpty + $attributeInfo.Position | Should -Be 1 + $attributeInfo.Module | Should -Be 'Name' } + } - Context 'Content has errors' { - BeforeAll { - Set-Content @defaultParams -Value @' - [Koan(Position = 1)] + Context 'Full attribute name used' { + + BeforeAll { + Mock 'Get-KoanAst' -ModuleName 'PSKoans' { + { + [KoanAttribute(Position = 1)] param( ) Describe 'About something' { It 'Has examples' { - -not ____ | Should -BeTrue + $true | Should -BeTrue } } -'@ + }.Ast } + } - It 'Retrieves the Koan attribute if the file has syntax errors' { - $attributeInfo = Get-KoanAttribute @defaultParams - - $attributeInfo | Should -Not -BeNullOrEmpty - $attributeInfo.Position | Should -Be 1 + It 'still returns attribute information when the full name is used' { + $attributeInfo = InModuleScope 'PSKoans' -Parameters $filePath { + param($Path) + Get-KoanAttribute $Path } + + $attributeInfo | Should -Not -BeNullOrEmpty + $attributeInfo.Position | Should -Be 1 } + } - Context 'Attribute is missing' { - BeforeAll { - Mock Get-KoanAst { - { - param( ) + Context 'Content has errors' { - Describe 'About something' { - It 'Has examples' { - $true | Should -BeTrue - } - } - }.Ast + BeforeAll { + Set-Content @filePath -Value @' + [Koan(Position = 1)] + param() + + Describe 'About something' { + It 'has examples' { + -not ____ | Should -BeTrue + } } +'@ + } + + It 'retrieves the Koan attribute even if the file has syntax errors' { + $attributeInfo = InModuleScope 'PSKoans' -Parameters $filePath { + param($Path) + Get-KoanAttribute $Path } - It 'When the Koan attribute is missing, returns nothing' { - $attributeInfo = Get-KoanAttribute @defaultParams + $attributeInfo | Should -Not -BeNullOrEmpty + $attributeInfo.Position | Should -Be 1 + } + } + + Context 'Attribute is missing' { + + BeforeAll { + Mock 'Get-KoanAst' -ModuleName 'PSKoans' { + { + param() + + Describe 'About something' { + It 'Has examples' { + $true | Should -BeTrue + } + } + }.Ast + } + } - $attributeInfo | Should -BeNullOrEmpty + It 'When the Koan attribute is missing, returns nothing' { + $attributeInfo = InModuleScope 'PSKoans' -Parameters $filePath { + param($Path) + Get-KoanAttribute $Path } + + $attributeInfo | Should -BeNullOrEmpty } } } diff --git a/Tests/Functions/Private/Get-KoanIt.Tests.ps1 b/Tests/Functions/Private/Get-KoanIt.Tests.ps1 index a04085571..6f902cb98 100644 --- a/Tests/Functions/Private/Get-KoanIt.Tests.ps1 +++ b/Tests/Functions/Private/Get-KoanIt.Tests.ps1 @@ -1,50 +1,49 @@ -#region Header -if (-not (Get-Module PSKoans)) { - $moduleBase = Join-Path -Path $psscriptroot.Substring(0, $psscriptroot.IndexOf('\Tests')) -ChildPath 'PSKoans' +#Requires -Modules PSKoans - Import-Module $moduleBase -Force -} -#endregion +Describe 'Get-KoanIt' { -InModuleScope PSKoans { - Describe Get-KoanIt { - BeforeAll { - $defaultParams = @{ - Path = Join-Path $TestDrive 'AboutSomething.Koans.ps1' - } + BeforeAll { + $defaultParams = @{ + Path = Join-Path $TestDrive 'AboutSomething.Koans.ps1' } + } - Context 'Content has no errors' { - BeforeAll { - Mock Get-KoanAst { - { - [Koan(Position = 1)] - param( ) + Context 'Content has no errors' { - Describe 'About something' { - It 'first' { - $true | Should -BeTrue - } + BeforeAll { + Mock 'Get-KoanAst' -ModuleName 'PSKoans' { + { + [Koan(Position = 1)] + param( ) - It 'second' { - $true | Should -BeTrue - } + Describe 'About something' { + It 'first' { + $true | Should -BeTrue } - }.Ast - } - } - It 'returns all information about all It blocks' { - $ItCommands = Get-KoanIt @defaultParams + It 'second' { + $true | Should -BeTrue + } + } + }.Ast + } + } - $ItCommands | Should -Not -BeNullOrEmpty - $ItCommands.Count | Should -Be 2 + It 'returns all information about all It blocks' { + $ItCommands = InModuleScope 'PSKoans' -Parameters $defaultParams { + param($Path) + Get-KoanIt $Path } + + $ItCommands | Should -Not -BeNullOrEmpty + $ItCommands.Count | Should -Be 2 } + } - Context 'Content has errors' { - BeforeAll { - Set-Content @defaultParams -Value @' + Context 'Content has errors' { + + BeforeAll { + Set-Content @defaultParams -Value @' [Koan(Position = 1)] param( ) @@ -58,14 +57,16 @@ InModuleScope PSKoans { } } '@ - } - - It 'Retrieves all It blocks when the file has syntax errors' { - $ItCommands = Get-KoanIt @defaultParams + } - $ItCommands | Should -Not -BeNullOrEmpty - $ItCommands.Count | Should -Be 2 + It 'Retrieves all It blocks when the file has syntax errors' { + $ItCommands = InModuleScope 'PSKoans' -Parameters $defaultParams { + param($Path) + Get-KoanIt $Path } + + $ItCommands | Should -Not -BeNullOrEmpty + $ItCommands.Count | Should -Be 2 } } } diff --git a/Tests/Functions/Private/Invoke-Koan.Tests.ps1 b/Tests/Functions/Private/Invoke-Koan.Tests.ps1 index d8cfeefc6..10e58523d 100644 --- a/Tests/Functions/Private/Invoke-Koan.Tests.ps1 +++ b/Tests/Functions/Private/Invoke-Koan.Tests.ps1 @@ -1,34 +1,46 @@ #Requires -Modules PSKoans -${global:Test File} = "$PSScriptRoot/ControlTests/Invoke-Koan.Control_Tests.ps1" +Describe 'Invoke-Koan' { -InModuleScope 'PSKoans' { - Describe 'Invoke-Koan' { - - It 'will not error out' { - { - Invoke-Koan @{ Script = ${global:Test File} } - } | Should -Not -Throw - } + BeforeAll { + $testFile = @{ Script = "$PSScriptRoot/ControlTests/Invoke-Koan.Control_Tests.ps1" } + } - It 'will produce output with -Passthru' { - Invoke-Koan @{ Script = ${global:Test File}; PassThru = $true } | Should -Not -BeNullOrEmpty - } + It 'runs the test successfully' { + { + InModuleScope 'PSKoans' -Parameters $testFile { + param($Script) + Invoke-Koan @{ Script = $Script } + } + } | Should -Not -Throw + } - It 'will correctly report test results' { - $Results = Invoke-Koan @{ Script = ${global:Test File}; PassThru = $true } + It 'produces output with -Passthru' { + InModuleScope 'PSKoans' -Parameters $testFile { + param($Script) + Invoke-Koan @{ Script = $Script; PassThru = $true } + } | Should -Not -BeNullOrEmpty + } - $Results.TotalCount | Should -Be 2 - $Results.PassedCount | Should -Be 0 - $Results.FailedCount | Should -Be 2 + It 'correctly reports test results' { + $Results = InModuleScope 'PSKoans' -Parameters $testFile { + param($Script) + Invoke-Koan @{ Script = $Script; PassThru = $true } } - It 'reports only expected exception types' { - $Results = Invoke-Koan @{ Script = ${global:Test File}; PassThru = $true } + $Results.TotalCount | Should -Be 2 + $Results.PassedCount | Should -Be 0 + $Results.FailedCount | Should -Be 2 + } - $Results.TestResult.ErrorRecord.Exception | - ForEach-Object -MemberName GetType | - Should -Be @([Exception], [NotImplementedException]) + It 'reports only expected exception types' { + $Results = InModuleScope 'PSKoans' -Parameters $testFile { + param($Script) + Invoke-Koan @{ Script = $Script; PassThru = $true } } + + $Results.Tests.ErrorRecord.Exception | + ForEach-Object -MemberName GetType | + Should -Be @([Exception], [NotImplementedException]) } } diff --git a/Tests/Functions/Private/Measure-Koan.Tests.ps1 b/Tests/Functions/Private/Measure-Koan.Tests.ps1 index 7f9a0ffa5..577a82d60 100644 --- a/Tests/Functions/Private/Measure-Koan.Tests.ps1 +++ b/Tests/Functions/Private/Measure-Koan.Tests.ps1 @@ -1,96 +1,90 @@ -#region Header -if (-not (Get-Module PSKoans)) { - $moduleBase = Join-Path -Path $psscriptroot.Substring(0, $psscriptroot.IndexOf('\Tests')) -ChildPath 'PSKoans' +#Requires -Modules PSKoans - Import-Module $moduleBase -Force -} -#endregion +Describe 'Measure-Koan' -Skip { -InModuleScope 'PSKoans' { - Describe 'Measure-Koan' { - BeforeAll { - Set-Content -Path TestDrive:\TestCases.Koans.ps1 -Value @' - Describe 'Test cases param' { - It 'first ' -TestCases @( - @{ TestCase = 1 } - @{ TestCase = 2 } - @{ TestCase = 3 } - ) { - param($TestCase) + BeforeAll { + Set-Content -Path 'TestDrive:\TestCases.Koans.ps1' -Value @' + Describe 'Test cases param' { + It 'first ' -TestCases @( + @{ TestCase = 1 } + @{ TestCase = 2 } + @{ TestCase = 3 } + ) { + param($TestCase) - $TestCase | Should -BeOfType int - } + $TestCase | Should -BeOfType int } + } '@ - Set-Content -Path TestDrive:\JustIt.Koans.ps1 -Value @' - Describe 'Just it' { - It 'first' { - $true | Should -BeTrue - } + Set-Content -Path 'TestDrive:\JustIt.Koans.ps1' -Value @' + Describe 'Just it' { + It 'first' { + $true | Should -BeTrue + } - It 'second' { - $true | Should -BeTrue - } - } + It 'second' { + $true | Should -BeTrue + } + } '@ - Set-Content -Path TestDrive:\Mixed.Koans.ps1 -Value @' - Describe 'Mixed' { - It 'first' { - $true | Should -BeTrue - } + Set-Content -Path TestDrive:\Mixed.Koans.ps1 -Value @' + Describe 'Mixed' { + It 'first' { + $true | Should -BeTrue + } - It 'second ' -TestCases @( - @{ TestCase = 1 } - @{ TestCase = 2 } - ) { - param($TestCase) + It 'second ' -TestCases @( + @{ TestCase = 1 } + @{ TestCase = 2 } + ) { + param($TestCase) - $TestCase | Should -BeOfType int - } - } + $TestCase | Should -BeOfType int + } + } '@ - Set-Content -Path TestDrive:\MutlipleTestCases.Koans.ps1 -Value @' - Describe 'Test cases param' { - It 'first ' -TestCases @( - @{ TestCase = 1 } - @{ TestCase = 2 } - @{ TestCase = 3 } - ) { - param($TestCase) - - $TestCase | Should -BeOfType int - } + Set-Content -Path TestDrive:\MutlipleTestCases.Koans.ps1 -Value @' + Describe 'Test cases param' { + It 'first ' -TestCases @( + @{ TestCase = 1 } + @{ TestCase = 2 } + @{ TestCase = 3 } + ) { + param($TestCase) - It 'second ' -TestCases @( - @{ TestCase = 1 } - @{ TestCase = 2 } - @{ TestCase = 3 } - ) { - param($TestCase) - - $TestCase | Should -BeOfType int - } - } -'@ - } + $TestCase | Should -BeOfType int + } - It 'counts the number of tests in ' -TestCases @( - @{ Path = 'TestDrive:\TestCases.Koans.ps1'; ExpectedValue = 3 } - @{ Path = 'TestDrive:\JustIt.Koans.ps1'; ExpectedValue = 2 } - @{ Path = 'TestDrive:\Mixed.Koans.ps1'; ExpectedValue = 3 } - @{ Path = 'TestDrive:\MutlipleTestCases.Koans.ps1'; ExpectedValue = 6 } - ) { - param($Path, $ExpectedValue) + It 'second ' -TestCases @( + @{ TestCase = 1 } + @{ TestCase = 2 } + @{ TestCase = 3 } + ) { + param($TestCase) - $koanInfo = [PSCustomObject]@{ - Path = $Path - PSTypeName = 'PSKoans.KoanInfo' + $TestCase | Should -BeOfType int + } } +'@ + } - $koanInfo | Measure-Koan | Should -Be $ExpectedValue + It 'correctly counts the number of tests in , including -TestCases' -TestCases @( + @{ Path = 'TestDrive:\TestCases.Koans.ps1'; ExpectedValue = 3 } + @{ Path = 'TestDrive:\JustIt.Koans.ps1'; ExpectedValue = 2 } + @{ Path = 'TestDrive:\Mixed.Koans.ps1'; ExpectedValue = 3 } + @{ Path = 'TestDrive:\MutlipleTestCases.Koans.ps1'; ExpectedValue = 6 } + ) { + $koanInfo = [PSCustomObject]@{ + Path = $Path + PSTypeName = 'PSKoans.KoanInfo' } + + InModuleScope 'PSKoans' -Parameters @{ Koan = $koanInfo } { + param($Koan) + Measure-Koan $Koan + } | Should -Be $ExpectedValue } } diff --git a/Tests/Functions/Private/Update-PSKoanFile.Tests.ps1 b/Tests/Functions/Private/Update-PSKoanFile.Tests.ps1 index a295b4b95..357df55bd 100644 --- a/Tests/Functions/Private/Update-PSKoanFile.Tests.ps1 +++ b/Tests/Functions/Private/Update-PSKoanFile.Tests.ps1 @@ -1,86 +1,81 @@ -#region Header -if (-not (Get-Module PSKoans)) { - $moduleBase = Join-Path -Path $psscriptroot.Substring(0, $psscriptroot.IndexOf('\Tests')) -ChildPath 'PSKoans' +#Requires -Modules PSKoans - Import-Module $moduleBase -Force -} -#endregion +Describe 'Update-PSKoanFile' { -InModuleScope 'PSKoans' { - Describe 'Update-PSKoanFile' { - BeforeAll { - Mock Get-PSKoanLocation { - Join-Path -Path $TestDrive -ChildPath 'Koans' - } - Mock Get-PSKoan { - [PSCustomObject]@{ - Topic = 'AboutSomething' - Path = Join-Path -Path $TestDrive -ChildPath 'Module/Group/AboutSomething.ps1' - RelativePath = 'Group/AboutSomething.ps1' - } + BeforeAll { + Mock 'Get-PSKoanLocation' { + Join-Path -Path $TestDrive -ChildPath 'Koans' + } + + Mock 'Get-PSKoan' { + [PSCustomObject]@{ + Topic = 'AboutSomething' + Path = Join-Path -Path $TestDrive -ChildPath 'Module/Group/AboutSomething.ps1' + RelativePath = 'Group/AboutSomething.ps1' } - New-Item -Path (Join-Path -Path $TestDrive -ChildPath 'Koans/Group') -ItemType Directory - New-Item -Path (Join-Path -Path $TestDrive -ChildPath 'Module/Group') -ItemType Directory + } - Set-Content -Path (Get-PSKoan).Path -Value @' - Describe 'AboutSomething' { - It 'koan 1' { - __ | Should -Be 1 - } + New-Item -Path (Join-Path -Path $TestDrive -ChildPath 'Koans/Group') -ItemType Directory + New-Item -Path (Join-Path -Path $TestDrive -ChildPath 'Module/Group') -ItemType Directory - It 'koan 2' { - __ | Should -Be 2 - } + Set-Content -Path (Get-PSKoan).Path -Value @' + Describe 'AboutSomething' { + It 'koan 1' { + __ | Should -Be 1 + } + + It 'koan 2' { + __ | Should -Be 2 + } - Context 'first' { - It 'koan 3' { - __ | Should -Be 3 - } + Context 'first' { + It 'koan 3' { + __ | Should -Be 3 } + } - Context 'second' { - It 'koan 4' { - __ | Should -Be 4 - } + Context 'second' { + It 'koan 4' { + __ | Should -Be 4 } } + } '@ - $userFilePath = Join-Path -Path (Get-PSKoanLocation) -ChildPath (Get-PSKoan).RelativePath - } + $userFilePath = Join-Path -Path (Get-PSKoanLocation) -ChildPath (Get-PSKoan).RelativePath + } - BeforeEach { - Set-Content -Path $userFilePath -Value @' - Describe 'AboutSomething' { - It 'koan 1' { - 1 | Should -Be 1 - } + BeforeEach { + Set-Content -Path $userFilePath -Value @' + Describe 'AboutSomething' { + It 'koan 1' { + 1 | Should -Be 1 + } - It 'koan 2' { - __ | Should -Be 2 - } + It 'koan 2' { + __ | Should -Be 2 + } - Context 'second' { - It 'koan 4' { - 4 | Should -Be 4 - } + Context 'second' { + It 'koan 4' { + 4 | Should -Be 4 } } + } '@ - } + } - It 'should replay completed koans' { - Update-PSKoanFile -Topic AboutSomething -Confirm:$false + It 'should replay completed koans' { + InModuleScope 'PSKoans' { Update-PSKoanFile -Topic AboutSomething -Confirm:$false } - $userFilePath | Should -FileContentMatch '1 | Should -Be 1' - $userFilePath | Should -FileContentMatch '__ | Should -Be 2' - $userFilePath | Should -FileContentMatch '4 | Should -Be 4' - } + $userFilePath | Should -FileContentMatch '1 | Should -Be 1' + $userFilePath | Should -FileContentMatch '__ | Should -Be 2' + $userFilePath | Should -FileContentMatch '4 | Should -Be 4' + } - It 'should should allow new koans to be inserted' { - Update-PSKoanFile -Topic AboutSomething -Confirm:$false + It 'should should allow new koans to be inserted' { + InModuleScope 'PSKoans' { Update-PSKoanFile -Topic AboutSomething -Confirm:$false } - $userFilePath | Should -FileContentMatch 'koan 3' - } + $userFilePath | Should -FileContentMatch 'koan 3' } } diff --git a/Tests/Functions/Public/Get-Blank.Tests.ps1 b/Tests/Functions/Public/Get-Blank.Tests.ps1 index 34bd04af8..f24e30556 100644 --- a/Tests/Functions/Public/Get-Blank.Tests.ps1 +++ b/Tests/Functions/Public/Get-Blank.Tests.ps1 @@ -1,14 +1,15 @@ #Requires -Modules PSKoans Describe 'Get-Blank' { + It 'should not produce output' { Get-Blank | Should -BeNullOrEmpty } - + It 'should be usable in the middle of a pipeline' { { Get-Variable | Get-Blank | Write-Output } | Should -Not -Throw } - + It 'should quietly ignore parameters' { { Get-Blank -Param1 Value1 -Param2 Value2 } | Should -Not -Throw } diff --git a/Tests/Functions/Public/Get-Karma.Tests.ps1 b/Tests/Functions/Public/Get-Karma.Tests.ps1 index 14b15d20a..7a25cce12 100644 --- a/Tests/Functions/Public/Get-Karma.Tests.ps1 +++ b/Tests/Functions/Public/Get-Karma.Tests.ps1 @@ -1,156 +1,165 @@ -#region Header -if (-not (Get-Module PSKoans)) { - $moduleBase = Join-Path -Path $psscriptroot.Substring(0, $psscriptroot.IndexOf('\Tests')) -ChildPath 'PSKoans' +#Requires -Modules PSKoans - Import-Module $moduleBase -Force -} -#endregion +Describe 'Get-Karma' { -InModuleScope 'PSKoans' { - Describe 'Get-Karma' { - BeforeAll { - $TestLocation = Join-Path -Path $TestDrive -ChildPath 'PSKoans' + BeforeAll { + $originalLocation = Get-PSKoanLocation + Set-PSKoanLocation -Path (Join-Path $TestDrive -ChildPath 'PSKoans') + Update-PSKoan -Confirm:$false + } - Mock Get-PSKoanLocation { - $TestLocation - } + AfterAll { + Set-PSKoanLocation -Path $originalLocation + } - Update-PSKoan -Confirm:$false - } + Context 'Default Behaviour' { - Context 'Default Behaviour' { - BeforeAll { - Mock Invoke-Koan -ModuleName 'PSKoans' { - [PSCustomObject]@{ - PassedCount = 0 - FailedCount = 4 - } + BeforeAll { + Mock 'Measure-Koan' -MockWith { 4 } -ModuleName 'PSKoans' + Mock 'Invoke-Koan' -ModuleName 'PSKoans' { + [PSCustomObject]@{ + PassedCount = 0 + FailedCount = 4 + Failed = @() } } - It 'should produce a hashtable with data' { - $Result = Get-Karma - $Result.PSTypeNames[0] | Should -Be 'PSKoans.Result' - $Result.KoansPassed | Should -Be 0 - } + $Result = Get-Karma + } - It 'should Invoke-Pester on koans until it fails a test' { - Assert-MockCalled Invoke-Koan -Times 1 - } + It 'produces a hashtable with data' { + $Result.PSTypeNames[0] | Should -Be 'PSKoans.Result' + $Result.KoansPassed | Should -Be 0 + } - It 'should populate the $script:CurrentTopic variable' { - $script:CurrentTopic | Should -BeOfType [hashtable] - $script:CurrentTopic.Count | Should -Be 4 - @('Name', 'Completed', 'Total', 'CurrentLine') | Should -BeIn $script:CurrentTopic.Keys - } + It 'calls Measure-Koan on each file to count koans' { + Should -Invoke 'Measure-Koan' -Scope Context -ModuleName 'PSKoans' } - Context 'With Nonexistent Koans Folder / No Koans Found' { - BeforeAll { - Mock Measure-Koan -ModuleName 'PSKoans' { } - Mock Update-PSKoan -ModuleName 'PSKoans' { throw 'Prevent recursion' } - Mock Write-Warning - } + It 'calls Invoke-Koan on each topic file until it fails a test' { + Should -Invoke 'Invoke-Koan' -Times 1 -ModuleName 'PSKoans' -Scope Context + } - It 'should attempt to populate koans and then recurse to reassess' { - { Get-Karma } | Should -Throw -ExpectedMessage 'Prevent recursion' - } + It 'populates the $script:CurrentTopic variable' { + $currentTopic = InModuleScope 'PSKoans' { $script:CurrentTopic } + $currentTopic | Should -BeOfType [hashtable] + $currentTopic.Count | Should -Be 4 + @('Name', 'Completed', 'Total', 'CurrentLine') | Should -BeIn $currentTopic.Keys + } + } - It 'should display a warning before initiating a reset' { - Assert-MockCalled Write-Warning - } + Context 'With Nonexistent Koans Folder / No Koans Found' { - It 'throws an error if a Topic is specified that matches nothing' { - { Get-Karma -Topic 'AboutAbsolutelyNothing' } | Should -Throw -ErrorId 'PSKoans.TopicNotFound' - } + BeforeAll { + Mock 'Measure-Koan' -ModuleName 'PSKoans' + Mock 'Get-PSKoan' -ParameterFilter { $Scope -eq 'User' } + Mock 'Update-PSKoan' { throw 'Prevent recursion' } + Mock 'Write-Warning' } - Context 'With -ListTopics Parameter' { - BeforeAll { - Mock Get-PSKoan { } - } + It 'should attempt to populate koans and then recurse to reassess' { + { Get-Karma } | Should -Throw -ExpectedMessage 'Prevent recursion' + Should -Invoke 'Update-PSKoan' -Scope Context + } - It 'should list all the koan topics' { - Get-Karma -ListTopics + It 'displays a warning before initiating a reset' { + Should -Invoke 'Write-Warning' -Scope Context + } - Assert-MockCalled Get-PSKoan - } + It 'throws an error if a Topic is specified that matches nothing' { + { Get-Karma -Topic 'AboutAbsolutelyNothing' } | Should -Throw -ErrorId 'PSKoans.TopicNotFound,Get-Karma' } + } - Context 'With -Topic Parameter' { - BeforeAll { - Mock Invoke-Koan -ModuleName 'PSKoans' { } - } + Context 'With -ListTopics Parameter' { - It 'should Invoke-Pester on only the topics selected: ' -TestCases @( - @{ Topic = @( 'AboutAssertions' ) } - @{ Topic = @( 'AboutArrays', 'AboutConditionals', 'AboutComparison' ) } - ) { - param([string[]] $Topic) + BeforeAll { + Mock 'Get-PSKoan' + } - Get-Karma -Topic $Topic + It 'lists all the koan topics' { + Get-Karma -ListTopics - Assert-MockCalled Invoke-Koan -Times @($Topic).Count - } + Should -Invoke 'Get-PSKoan' } + } - Context 'Behaviour When All Koans Are Completed' { - BeforeAll { - Mock Get-PSKoanLocation { - Join-Path -Path $TestDrive -ChildPath 'CompletedKoan' - } + Context 'With -Topic Parameter' { + + BeforeAll { + Mock 'Measure-Koan' -ModuleName 'PSKoans' { [int]::MaxValue } + Mock 'Invoke-Koan' -ModuleName 'PSKoans' + } - $TestFile = Join-Path -Path (Get-PSKoanLocation) -ChildPath 'Group\SelectedTopicTest.Koans.Ps1' - New-Item -Path (Split-Path $TestFile -Parent) -ItemType Directory -Force - Set-Content -Path $TestFile -Value @' - using module PSKoans - [Koan(Position = 1)] - param() + It 'calls Invoke-Koan on only the topics selected: ' -TestCases @( + @{ Topic = @( 'AboutAssertions' ) } + @{ Topic = @( 'AboutArrays', 'AboutConditionals', 'AboutComparison' ) } + ) { + Get-Karma -Topic $Topic - Describe 'Koans Test' { + Should -Invoke 'Invoke-Koan' -Times @($Topic).Count -ModuleName 'PSKoans' + } + } - It 'is easy to solve' { - $true | Should -BeTrue - } + Context 'Behaviour When All Koans Are Completed' { - It 'is positively trivial' { - $false | Should -BeFalse - } + BeforeAll { + Mock 'Get-PSKoanLocation' { + Join-Path -Path $TestDrive -ChildPath 'CompletedKoan' + } + + Mock 'Measure-Koan' -ModuleName 'PSKoans' -MockWith { 2 } + + $TestFile = Join-Path -Path (Get-PSKoanLocation) -ChildPath 'Group\SelectedTopicTest.Koans.ps1' + New-Item -Path (Split-Path $TestFile -Parent) -ItemType Directory -Force + Set-Content -Path $TestFile -Value @' + using module PSKoans + [Koan(Position = 1)] + param() + + Describe 'Koans Test' { + + It 'is easy to solve' { + $true | Should -BeTrue } -'@ - try { - $Result = Get-Karma -Topic SelectedTopicTest - } - catch { - # Ignore this. Error tests follow. + It 'is positively trivial' { + $false | Should -BeFalse + } } - } +'@ - It 'should not divide by zero if all Koans are completed' { - { Get-Karma -Topic SelectedTopicTest } | Should -Not -Throw + try { + $Result = Get-Karma -Topic SelectedTopicTest } - - It 'should output the result object' { - $Result | Should -Not -BeNullOrEmpty - $Result.PSTypeNames | Should -Contain 'PSKoans.CompleteResult' + catch { + # Ignore this. Error tests follow. } + } - It 'should indicate completion' { - $Result.Complete | Should -BeTrue - } + It 'does not divide by zero if all Koans are completed' { + { Get-Karma -Topic SelectedTopicTest } | Should -Not -Throw + } - It 'should indicate number of koans passed' { - $Result.KoansPassed | Should -Be 2 - } + It 'outputs the result object' { + $Result | Should -Not -BeNullOrEmpty + $Result.PSTypeNames | Should -Contain 'PSKoans.CompleteResult' + } - It 'should indicate total number of koans' { - $Result.TotalKoans | Should -Be 2 - } + It 'indicates completion' { + $Result.Complete | Should -BeTrue + } - It 'should indicate the requested topic' { - $Result.RequestedTopic | Should -Be 'SelectedTopicTest' - } + It 'indicates number of koans passed' { + $Result.KoansPassed | Should -Be 2 + } + + It 'indicates total number of koans' { + $Result.TotalKoans | Should -Be 2 + } + + It 'indicates the requested topic' { + $Result.RequestedTopic | Should -Be 'SelectedTopicTest' } } } diff --git a/Tests/Functions/Public/Get-PSKoan.Tests.ps1 b/Tests/Functions/Public/Get-PSKoan.Tests.ps1 index 0ac5a1db5..93bca3790 100644 --- a/Tests/Functions/Public/Get-PSKoan.Tests.ps1 +++ b/Tests/Functions/Public/Get-PSKoan.Tests.ps1 @@ -1,76 +1,63 @@ -#region Header -if (-not (Get-Module PSKoans)) { - $moduleBase = Join-Path -Path $psscriptroot.Substring(0, $psscriptroot.IndexOf('\Tests')) -ChildPath 'PSKoans' +#Requires -Modules PSKoans - Import-Module $moduleBase -Force -} -#endregion +Describe 'Get-PSKoan' { -InModuleScope PSKoans { - Describe 'Get-PSKoan' { - BeforeAll { - Mock Get-PSKoanLocation { - Join-Path $TestDrive 'PSKoans' - } + BeforeAll { + Mock 'Get-PSKoanLocation' { + Join-Path $TestDrive 'PSKoans' + } - Update-PSKoan -Confirm:$false + Update-PSKoan -Confirm:$false - # Stage test module - $path = Join-Path -Path (Get-PSKoanLocation) 'Modules\TestModule' - New-Item -Path $path -ItemType Directory -Force - Set-Content -Path (Join-Path -Path $path -ChildPath 'AboutSomething.Koans.ps1') -Value @' - using module PSKoans - [Koan(Position = 1, Module = 'TestModule')] - param( ) + # Stage test module + $path = Join-Path -Path (Get-PSKoanLocation) 'Modules\TestModule' + New-Item -Path $path -ItemType Directory -Force + Set-Content -Path (Join-Path -Path $path -ChildPath 'AboutSomething.Koans.ps1') -Value @' + using module PSKoans + [Koan(Position = 1, Module = 'TestModule')] + param() - Describe 'AboutSomething' { - It 'first' { - $true | Should -BeTrue - } + Describe 'AboutSomething' { + It 'first' { + $true | Should -BeTrue } + } '@ - } - - It 'should retrieve all except module-specific koan files' { - $Files = Get-ChildItem -Path (Get-PSKoanLocation) -Filter *.Koans.ps1 -Recurse -File | - Where-Object FullName -notmatch 'PSKoans[\\/]Modules[\\/]' - - (Get-PSKoan).Topic.Count | Should -Be $Files.Count - } + } - It 'should retrieve specific requested files with -Topic ' -TestCases @( - @{ Topic = 'AboutArrays' } - @{ Topic = 'AboutVariables' } - @{ Topic = 'AboutTypeOperators' } - @{ Topic = 'AboutLists' } - @{ Topic = 'AboutVariables', 'AboutLists' } - ) { - param($Topic) + It 'retrieves all except module-specific koan files' { + $Files = Get-ChildItem -Path (Get-PSKoanLocation) -Filter *.Koans.ps1 -Recurse -File | + Where-Object FullName -NotMatch 'PSKoans[\\/]Modules[\\/]' - (Get-PSKoan -Topic $Topic).Topic | Should -Be $Topic - } + (Get-PSKoan).Topic.Count | Should -Be $Files.Count + } - It 'should retrieve specific requested files with -Module ' -TestCases @( - @{ Topic = 'AboutSomething'; Module = 'TestModule' } - ) { - param($Topic, $Module) + It 'retrieves specific requested files with -Topic ' -TestCases @( + @{ Topic = 'AboutArrays' } + @{ Topic = 'AboutVariables' } + @{ Topic = 'AboutTypeOperators' } + @{ Topic = 'AboutLists' } + @{ Topic = 'AboutVariables', 'AboutLists' } + ) { + (Get-PSKoan -Topic $Topic).Topic | Should -Be $Topic + } - (Get-PSKoan -Module $Module -Scope User).Topic | Should -Be $Topic - } + It 'retrieves specific requested files with -Module ' -TestCases @( + @{ Topic = 'AboutSomething'; Module = 'TestModule' } + ) { + (Get-PSKoan -Module $Module -Scope User).Topic | Should -Be $Topic + } - if ($PSVersionTable.PSEdition -eq 'Desktop' -or $PSVersionTable.Platform -eq 'Win32NT') { - It 'should throw a terminating error if a file is blocked' { - $testFile = Get-ChildItem -Path (Get-PSKoanLocation) -Filter AboutArrays.Koans.ps1 -Recurse -File | - Select-Object -First 1 + It 'should throw a terminating error if a file is blocked' -Skip:($PSVersionTable.PSEdition -ne 'Desktop' -or $PSVersionTable.Platform -ne 'Win32NT') { + $testFile = Get-ChildItem -Path (Get-PSKoanLocation) -Filter AboutArrays.Koans.ps1 -Recurse -File | + Select-Object -First 1 - Set-Content -Path $testFile.FullName -Stream Zone.Identifier -Value @' + Set-Content -Path $testFile.FullName -Stream Zone.Identifier -Value @' [ZoneTransfer] ZoneId=3 ReferrerUrl=C:\Downloads\File.zip '@ - { Get-PSKoan -Topic AboutArrays -Scope User } | Should -Throw -ErrorId PSKoans.KoanFileIsBlocked - } - } + { Get-PSKoan -Topic AboutArrays -Scope User } | Should -Throw -ErrorId PSKoans.KoanFileIsBlocked } } diff --git a/Tests/Functions/Public/Get-PSKoanLocation.Tests.ps1 b/Tests/Functions/Public/Get-PSKoanLocation.Tests.ps1 index b80fe57d2..d62e5be02 100644 --- a/Tests/Functions/Public/Get-PSKoanLocation.Tests.ps1 +++ b/Tests/Functions/Public/Get-PSKoanLocation.Tests.ps1 @@ -1,23 +1,34 @@ #Requires -Modules PSKoans Describe 'Get-PSKoanLocation' { - BeforeAll { - Mock Get-PSKoanSetting -ModuleName PSKoans { - '~/PSKoans' - } -ParameterFilter { $Name -eq 'KoanLocation' } - } - It 'retrieves the koan library location' { - Get-PSKoanLocation | Should -Be '~/PSKoans' - } + Context 'Normal Behaviour' { + + BeforeAll { + Mock 'Get-PSKoanSetting' -ParameterFilter { $Name -eq 'KoanLocation' } -MockWith { + '~/PSKoans' + } - It 'calls Get-PSKoanSetting with -Name "LibraryFolder"' { - Assert-MockCalled Get-PSKoanSetting -ModuleName PSKoans + $Result = Get-PSKoanLocation + } + + It 'retrieves the koan library location' { + $Result | Should -Be '~/PSKoans' + } + + It 'calls Get-PSKoanSetting with -Name "KoanLocation"' { + Should -Invoke 'Get-PSKoanSetting' -Scope Context + } } - It 'throws an error if no value can be retrieved' { - Mock Get-PSKoanSetting -ModuleName PSKoans -ParameterFilter { $Name -eq 'KoanLocation' } + Context 'No Value Available' { + + BeforeAll { + Mock 'Get-PSKoanSetting' -ParameterFilter { $Name -eq 'KoanLocation' } + } - { Get-PSKoanLocation } | Should -Throw -ExpectedMessage 'location has not been defined' + It 'throws an error if no value can be retrieved' { + { Get-PSKoanLocation } | Should -Throw -ExpectedMessage 'PSKoans folder location has not been defined' + } } } diff --git a/Tests/Functions/Public/Get-PSKoanSetting.Tests.ps1 b/Tests/Functions/Public/Get-PSKoanSetting.Tests.ps1 index 220e7047c..e5b5e3efe 100644 --- a/Tests/Functions/Public/Get-PSKoanSetting.Tests.ps1 +++ b/Tests/Functions/Public/Get-PSKoanSetting.Tests.ps1 @@ -1,44 +1,65 @@ #Requires -Modules PSKoans Describe 'Get-PSKoanSetting' { + BeforeAll { - InModuleScope PSKoans { - $script:ConfigPath = 'TestDrive:/config.json' + $configFilePath = 'TestDrive:/config.json' + InModuleScope 'PSKoans' -Parameters @{ Path = $configFilePath } { + param($Path) + $script:OldConfigPath = $script:ConfigPath + $script:ConfigPath = $Path } + ${/} = [IO.Path]::DirectorySeparatorChar } + AfterAll { + InModuleScope 'PSKoans' { + $script:ConfigPath = $script:OldConfigPath + } + } + Context 'Settings file does not exist' { + BeforeAll { - Mock Set-PSKoanSetting { } -ParameterFilter { $Settings -is [hashtable] } -ModuleName PSKoans - $DefaultSettings = InModuleScope PSKoans { $script:DefaultSettings } + Mock 'Set-PSKoanSetting' -ParameterFilter { $Settings -is [hashtable] } + $DefaultSettings = InModuleScope 'PSKoans' { $script:DefaultSettings } } - +` It 'returns the default settings' { - $Result = InModuleScope PSKoans { Get-PSKoanSetting } + $Result = Get-PSKoanSetting $Result | Should -BeOfType [PSCustomObject] $Result.KoanLocation | Should -BeExactly "$HOME${/}PSKoans" $Result.Editor | Should -BeExactly 'code' } It 'calls Set-PSKoanSetting to set the default settings' { - Assert-MockCalled Set-PSKoanSetting -ModuleName PSKoans + Should -Invoke 'Set-PSKoanSetting' -Scope Context } } Context 'Settings file does exist' { + BeforeAll { - '{"KoanLocation": "TestLocation","Editor": "TestEditor"}' | Set-Content -Path 'TestDrive:/config.json' + [PSCustomObject]@{ + KoanLocation = "TestLocation" + Editor = "TestEditor" + } | + ConvertTo-Json | + Set-Content -Path $configFilePath } It 'returns all settings if none are specified' { - $Result = InModuleScope PSKoans { Get-PSKoanSetting } + $Result = Get-PSKoanSetting $Result.KoanLocation | Should -BeExactly 'TestLocation' $Result.Editor | Should -BeExactly 'TestEditor' } - It 'returns only the specified setting with -Name' { - InModuleScope PSKoans { Get-PSKoanSetting -Name KoanLocation } | Should -BeExactly 'TestLocation' + It 'returns only the specified setting with -Name ' -TestCases @( + @{ Name = 'KoanLocation'; Expected = 'TestLocation' } + @{ Name = 'Editor'; Expected = 'TestEditor' } + ) { + Get-PSKoanSetting -Name $Name | Should -BeExactly $Expected } } } diff --git a/Tests/Functions/Public/Move-PSKoanLibrary.Tests.ps1 b/Tests/Functions/Public/Move-PSKoanLibrary.Tests.ps1 index ed806df0d..4763e628e 100644 --- a/Tests/Functions/Public/Move-PSKoanLibrary.Tests.ps1 +++ b/Tests/Functions/Public/Move-PSKoanLibrary.Tests.ps1 @@ -9,9 +9,9 @@ Describe 'Move-PSKoanLibrary' { Select-Object -ExpandProperty FullName $TestPath = New-Item -ItemType Directory -Path 'TestDrive:/TestPath' | Join-Path -ChildPath 'Koans' - Mock Get-PSKoanLocation { $OriginalPath }.GetNewClosure() -ModuleName PSKoans - Mock Set-PSKoanLocation -ParameterFilter { $Path -eq $TestPath } -ModuleName PSKoans - Mock Move-Item -ParameterFilter { $Path -eq $OriginalPath } -MockWith { $Destination } -ModuleName PSKoans + Mock 'Get-PSKoanLocation' { $OriginalPath } + Mock 'Set-PSKoanLocation' -ParameterFilter { $Path -eq $TestPath } + Mock 'Move-Item' -ParameterFilter { $Path -eq $OriginalPath } -MockWith { $Destination } } It 'should output the new location' { @@ -19,15 +19,15 @@ Describe 'Move-PSKoanLibrary' { } It 'should call Get-PSKoanLocation' { - Assert-MockCalled Get-PSKoanLocation -ModuleName PSKoans + Should -Invoke 'Get-PSKoanLocation' -Scope Context } It 'should call Move-Item' { - Assert-MockCalled Move-Item -ModuleName PSKoans + Should -Invoke 'Move-Item' -Scope Context } It 'should call Set-PSKoanLocation' { - Assert-MockCalled Set-PSKoanLocation -ModuleName PSKoans + Should -Invoke 'Set-PSKoanLocation' -Scope Context } } @@ -39,16 +39,6 @@ Describe 'Move-PSKoanLibrary' { Set-PSKoanLocation -Path 'TestDrive:/PSKoans' Update-PSKoan -Confirm:$false - $OriginalFileHashes = Get-PSKoanLocation | - Get-ChildItem -Recurse -File | - Get-FileHash | - ForEach-Object { - @{ - File = ($_.Path -split [regex]::Escape([IO.Path]::DirectorySeparatorChar))[-2, -1] -join ':' - Hash = $_.Hash - } - } - $NewLocation = 'TestDrive:/NewLocation/PSKoans' New-Item -Path ($NewLocation | Split-Path -Parent) -ItemType Directory } @@ -66,16 +56,33 @@ Describe 'Move-PSKoanLibrary' { Get-PSKoanLocation | Should -BeExactly (Get-Item -Path $NewLocation).FullName } - It 'should copy to the new location' -TestCases $OriginalFileHashes { - param($File, $Hash) + It 'should copy all files intact to the new location' { + $OriginalFileHashes = Get-PSKoanLocation | + Get-ChildItem -Recurse -File | + Get-FileHash | + ForEach-Object { + @{ + File = ($_.Path -split [regex]::Escape([IO.Path]::DirectorySeparatorChar))[-2, -1] -join ':' + Hash = $_.Hash + } + } - $FileName = ($File -split ':')[-1] + $finalLocation = 'TestDrive:/FinalLocation/PSKoans' + New-Item -Path ($finalLocation | Split-Path -Parent) -ItemType Directory + Move-PSKoanLibrary -Path $finalLocation - $NewHash = Get-ChildItem -Path $NewLocation -Recurse -File -Filter "*$FileName*" | + $newFileHashes = @{} + Get-ChildItem -Path $finalLocation -Recurse -File -PipelineVariable Item | Get-FileHash | - Select-Object -ExpandProperty Hash + ForEach-Object { + $newFileHashes[$Item.Name] = $_.Hash + } - $NewHash | Should -BeExactly $Hash + foreach ($File in $OriginalFileHashes) { + $FileName = ($File.File -split ':')[-1] + $reason = "the hash of $($File.File) should match the new file hash" + $newFileHashes[$FileName] | Should -BeExactly $File.Hash -Because $reason + } } } diff --git a/Tests/Functions/Public/Register-Advice.Tests.ps1 b/Tests/Functions/Public/Register-Advice.Tests.ps1 index 55e907751..326a1931f 100644 --- a/Tests/Functions/Public/Register-Advice.Tests.ps1 +++ b/Tests/Functions/Public/Register-Advice.Tests.ps1 @@ -1,55 +1,62 @@ #Requires -Modules PSKoans +Describe "Register-Advice" { -InModuleScope 'PSKoans' { - Describe "Register-Advice" { - Context "Checkin the Behaviour if ProfileFolder and ProfilePath Not exists" { - BeforeAll{ - Mock Test-Path { $false } -ParameterFilter { $Path -eq $ProfileFolder } - Mock New-Item { } - Mock Test-Path { $false } -ParameterFilter { $Path -eq $ProfilePath } - Mock Set-Content { } -ParameterFilter { $Value -eq "Show-Advice" } - } - It "First Step Test-Path Fails and Create a Item" { - Register-Advice - Assert-MockCalled -CommandName Test-Path - } - It "Second Step Profile Path fails adding content in the file" { - Register-Advice - Assert-MockCalled -CommandName Set-Content -Times 1 - } + Context "Profile Folder/File Missing" { + + BeforeAll { + Mock New-Item -Verifiable + Mock Test-Path { $false } -Verifiable + Mock Set-Content -ParameterFilter { $Value -eq "Show-Advice" } -Verifiable } - Context "Checkin the Function with other Possible values" { - BeforeAll{ - Mock Test-Path { $true } -ParameterFilter { $Path -eq $ProfilePath } - Mock Select-String { $false } - Mock Add-Content { } - } - It "Third Step If file exists but not having the content (Get|Set)-Advice" { - Register-Advice - Assert-MockCalled -CommandName Add-Content - } + + It 'should create the $profile if it does not exist' { + Register-Advice + Should -InvokeVerifiable } - Context "Checkin the behaviour of function with different parameter values" { - BeforeAll{ - Mock Test-Path { $false } -ParameterFilter { $Path -eq $ProfileFolder } - Mock New-Item { } - Mock Test-Path { $false } -ParameterFilter { $Path -eq $ProfilePath } - Mock Set-Content { } -ParameterFilter { $Value -eq "Show-Advice" } - } - It "Should throw if invalid parameter(Targetprofile) value is passed" { - { Register-Advice -TargetProfile "Invalidvalue" } | Should -Throw - } - $testdata = @( - @{ ProfilePath = 'AllUsersAllHosts' } - @{ ProfilePath = 'AllUsersCurrentHost' } - @{ ProfilePath = 'CurrentUserAllHosts' } - @{ ProfilePath = 'CurrentUserCurrentHost' } - ) - It "Checking with the possible Parameter Values" -TestCases $testdata { - param($ProfilePath) - Register-Advice $ProfilePath | Should -BeNullOrEmpty - + } + + Context "Profile Already Exists" { + + BeforeAll { + Mock 'Test-Path' { $true } -Verifiable + Mock 'Select-String' { $false } -Verifiable + Mock 'Add-Content' -Verifiable + } + + It "adds content to the profile if it already exists (Get|Set)-Advice" { + Register-Advice + Should -InvokeVerifiable + } + } + + Context "Parameter Validation" { + + BeforeAll { + Mock Test-Path { $false } -ParameterFilter { $Path -eq $ProfileFolder } + Mock New-Item + Mock Test-Path { $false } -ParameterFilter { $Path -eq $ProfilePath } + Mock Set-Content -ParameterFilter { $Value -eq "Show-Advice" } + } + + It "throws if an invalid value is supplied for -TargetProfile" { + { Register-Advice -TargetProfile "Invalidvalue" } | Should -Throw + } + + It "works correctly with the profile" -TestCases @( + @{ ProfilePath = 'AllUsersAllHosts' } + @{ ProfilePath = 'AllUsersCurrentHost' } + @{ ProfilePath = 'CurrentUserAllHosts' } + @{ ProfilePath = 'CurrentUserCurrentHost' } + ) { + try { + Register-Advice $ProfilePath | Should -BeNullOrEmpty + } + catch [UnauthorizedAccessException] { + # Current user doesn't have access to the profile path. This can be normal for the 'AllUsers' paths. + if ($ProfilePath -notmatch '^AllUsers') { + throw $_ + } } } } diff --git a/Tests/Functions/Public/Reset-PSKoan.Tests.ps1 b/Tests/Functions/Public/Reset-PSKoan.Tests.ps1 index f0b33776a..885ab87c9 100644 --- a/Tests/Functions/Public/Reset-PSKoan.Tests.ps1 +++ b/Tests/Functions/Public/Reset-PSKoan.Tests.ps1 @@ -1,206 +1,211 @@ -#region Header -if (-not (Get-Module PSKoans)) { - $moduleBase = Join-Path -Path $psscriptroot.Substring(0, $psscriptroot.IndexOf('\Tests')) -ChildPath 'PSKoans' +#Requires -Modules PSKoans - Import-Module $moduleBase -Force -} -#endregion +Describe Reset-PSKoan { -InModuleScope PSKoans { - Describe Reset-PSKoan { - BeforeAll { - $defaultParams = @{ - Confirm = $false - } + BeforeAll { + $defaultParams = @{ + Confirm = $false + } - Mock Get-PSKoanLocation { - Join-Path -Path $TestDrive -ChildPath 'PSKoans' - } - Mock Get-PSKoan -ParameterFilter { $Scope -eq 'Module' } -MockWith { - [PSCustomObject]@{ - Topic = 'AboutSomething' - Path = Join-Path -Path $TestDrive -ChildPath 'Module\Group\AboutSomething.Koans.ps1' - RelativePath = 'Group\AboutSomething.Koans.ps1' - } + Mock 'Get-PSKoanLocation' { + Join-Path -Path $TestDrive -ChildPath 'PSKoans' + } + + Mock 'Get-PSKoan' -ParameterFilter { $Scope -eq 'Module' } -MockWith { + [PSCustomObject]@{ + Topic = 'AboutSomething' + Path = Join-Path -Path $TestDrive -ChildPath 'Module\Group\AboutSomething.Koans.ps1' + RelativePath = 'Group\AboutSomething.Koans.ps1' } - New-Item -Path (Join-Path -Path $TestDrive -ChildPath 'Module\Group') -ItemType Directory - New-Item -Path (Join-Path -Path $TestDrive -ChildPath 'PSKoans\Group') -ItemType Directory + } - $userFilePath = Get-PSKoanLocation | Join-Path -ChildPath 'Group\AboutSomething.Koans.ps1' + New-Item -Path (Join-Path -Path $TestDrive -ChildPath 'Module\Group') -ItemType Directory + New-Item -Path (Join-Path -Path $TestDrive -ChildPath 'PSKoans\Group') -ItemType Directory - Set-Content -Path (Get-PSKoan -Scope Module).Path, $userFilePath -Value @' - using module PSKoans - [Koan(Position = 1)] - param ( ) + $userFilePath = Get-PSKoanLocation | Join-Path -ChildPath 'Group\AboutSomething.Koans.ps1' - Describe 'AboutSomething' { - It 'existing content' { - __ | Should -Be 1 - } + Set-Content -Path (Get-PSKoan -Scope Module).Path, $userFilePath -Value @' + using module PSKoans + [Koan(Position = 1)] + param ( ) - It 'reset content' { - __ | Should -Be 2 - } + Describe 'AboutSomething' { + It 'existing content' { + __ | Should -Be 1 + } - Context 'first' { - It 'nested reset content' { - __ | Should -Be 3 - } + It 'reset content' { + __ | Should -Be 2 + } + + Context 'first' { + It 'nested reset content' { + __ | Should -Be 3 } + } - Context 'second' { - It 'nested reset content' { - __ | Should -Be 4 - } + Context 'second' { + It 'nested reset content' { + __ | Should -Be 4 } } + } '@ - } + } - Context 'User file exists, It block exists' { - BeforeAll { - Mock Set-Content - Mock Copy-Item - } + Context 'User file exists, It block exists' { - It 'When Name is supplied, updates an existing user file' { - Reset-PSKoan -Name 'existing content' @defaultParams + BeforeAll { + Mock 'Set-Content' + Mock 'Copy-Item' + } - Assert-MockCalled Set-Content -Times 1 -Scope It - Assert-MockCalled Copy-Item -Times 0 -Scope It - } + It 'updates an existing user file when -Name is supplied' { + Reset-PSKoan -Name 'existing content' @defaultParams - It 'When Context is supplied, updates an existing user file' { - Reset-PSKoan -Context 'first' @defaultParams + Should -Invoke 'Set-Content' -Times 1 + Should -Invoke 'Copy-Item' -Times 0 + } - Assert-MockCalled Set-Content -Times 1 -Exactly -Scope It - Assert-MockCalled Copy-Item -Times 0 -Scope It - } + It 'updates an existing user file when -Context is supplied' { + Reset-PSKoan -Context 'first' @defaultParams - It 'When Name and Context are supplied, updates an existing user file' { - Reset-PSKoan -Name 'nested reset content' -Context 'first' @defaultParams + Should -Invoke 'Set-Content' -Times 1 -Exactly + Should -Invoke 'Copy-Item' -Times 0 + } - Assert-MockCalled Set-Content -Times 1 -Exactly -Scope It - Assert-MockCalled Copy-Item -Times 0 -Scope It - } + It 'updates an existing user file when -Name and -Context are supplied' { + Reset-PSKoan -Name 'nested reset content' -Context 'first' @defaultParams - It 'When Name and Context are not supplied, copies a koan file from the module' { - Reset-PSKoan @defaultParams + Should -Invoke 'Set-Content' -Times 1 -Exactly + Should -Invoke 'Copy-Item' -Times 0 + } - Assert-MockCalled Set-Content -Times 0 -Scope It - Assert-MockCalled Copy-Item -Times 1 -Exactly -Scope It - } + It 'copies a koan file from the module when -Name and -Context are not supplied' { + Reset-PSKoan @defaultParams + + Should -Invoke 'Set-Content' -Times 0 + Should -Invoke 'Copy-Item' -Times 1 -Exactly } + } - Context 'User file exists, It block does not exist' { - BeforeAll { - Mock Get-KoanIt -ParameterFilter { $Path -match 'PSKoans' } - } + Context 'User file exists, It block does not exist' { - It 'When the user file does not include the specified Koan, writes a non-terminating error' { - { Reset-PSKoan -Topic AboutSomething -Name 'existing content' -ErrorAction Stop @defaultParams } | Should -Throw -ErrorId PSKoans.UserItNotFound - } + It 'writes a non-terminating error when the user file does not include the specified Koan' { + Mock 'Get-KoanIt' -ParameterFilter { $Path -match 'PSKoans' } -Module 'PSKoans' + + { Reset-PSKoan -Topic AboutSomething -Name 'existing content' -ErrorAction Stop @defaultParams } | + Should -Throw -ErrorId 'PSKoans.UserItNotFound,Reset-PSKoan' } + } - Context 'User file does not exist' { - BeforeAll { - New-Item "$TestDrive/DoesNotExist.Koans.ps1" -ItemType File > $null - Mock Get-PSKoan -ParameterFilter { $Scope -eq 'User' } - Mock Get-PSKoan -ParameterFilter { $Scope -eq 'Module' } { - [PSCustomObject]@{ - Topic = $Topic - Module = '_powershell' - Position = 101 - Path = "$TestDrive/DoesNotExist.Koans.ps1" - RelativePath = 'DoesNotExist.Koans.ps1' - PSTypeName = 'PSKoans.KoanInfo' - } + Context 'User file does not exist' { + + BeforeAll { + New-Item "$TestDrive/DoesNotExist.Koans.ps1" -ItemType File > $null + + Mock 'Get-PSKoan' -ParameterFilter { $Scope -eq 'User' } -Verifiable + Mock 'Get-PSKoan' -ParameterFilter { $Scope -eq 'Module' } -Verifiable -MockWith { + [PSCustomObject]@{ + Topic = $Topic + Module = '_powershell' + Position = 101 + Path = "$TestDrive/DoesNotExist.Koans.ps1" + RelativePath = 'DoesNotExist.Koans.ps1' + PSTypeName = 'PSKoans.KoanInfo' } - Mock Update-PSKoan } - It 'When the topic does not exist in the user location, call Update-PSKoan' { - Reset-PSKoan -Topic DoesNotExist -ErrorAction Stop @defaultParams + Mock 'Update-PSKoan' + } - Assert-MockCalled Update-PSKoan -Times 1 - } + It 'calls Update-PSKoan when the topic does not exist in the user location' { + Reset-PSKoan -Topic DoesNotExist -ErrorAction Stop @defaultParams + + Should -InvokeVerifiable + Should -Invoke Update-PSKoan -Times 1 -Exactly } + } - Context 'Module file does not exist' { - BeforeAll { - Mock Get-PSKoan -ParameterFilter { $Scope -eq 'Module' } - } + Context 'Module file does not exist' { - It 'When no topics are found in the module, throw a terminating error' { - { Reset-PSKoan -Topic DoesNotExist @defaultParams } | Should -Throw -ErrorId PSKoans.ModuleTopicNotFound - } + BeforeAll { + Mock 'Get-PSKoan' -ParameterFilter { $Scope -eq 'Module' } } - Context 'Practical tests' { - BeforeEach { - Set-Content -Path $userFilePath -Value @' - using module PSKoans - [Koan(Position = 1)] - param ( ) + It 'throws a terminating error when no topics are found in the module' { + { Reset-PSKoan -Topic DoesNotExist @defaultParams } | + Should -Throw -ErrorId 'PSKoans.ModuleTopicNotFound,Reset-PSKoan' - Describe 'AboutSomething' { - It 'existing content' { - 1 | Should -Be 1 - } + Should -Invoke 'Get-PSKoan' -Times 1 -Exactly + } + } - It 'reset content' { - 1 | Should -Be 2 - } + Context 'Practical tests' { - Context 'first' { - It 'nested reset content' { - 3 | Should -Be 3 - } + BeforeEach { + Set-Content -Path $userFilePath -Value @' + using module PSKoans + [Koan(Position = 1)] + param ( ) + + Describe 'AboutSomething' { + It 'existing content' { + 1 | Should -Be 1 + } + + It 'reset content' { + 1 | Should -Be 2 + } + + Context 'first' { + It 'nested reset content' { + 3 | Should -Be 3 } + } - Context 'second' { - It 'nested reset content' { - 4 | Should -Be 4 - } + Context 'second' { + It 'nested reset content' { + 4 | Should -Be 4 } } + } '@ - } + } - It 'should reset all koans in a file when Name is not specified' { - $userFilePath | Should -FileContentMatch '__ | Should -Be 1' - $userFilePath | Should -FileContentMatch '__ | Should -Be 2' - $userFilePath | Should -FileContentMatch '__ | Should -Be 3' - $userFilePath | Should -FileContentMatch '__ | Should -Be 4' - } + It 'should reset all koans in a file when Name is not specified' { + $userFilePath | Should -FileContentMatch '__ | Should -Be 1' + $userFilePath | Should -FileContentMatch '__ | Should -Be 2' + $userFilePath | Should -FileContentMatch '__ | Should -Be 3' + $userFilePath | Should -FileContentMatch '__ | Should -Be 4' + } - It 'should reset the state of a single koan without affecting others when Name is specified' { - Reset-PSKoan -Topic AboutSomething -Name 'reset content' @defaultParams + It 'should reset the state of a single koan without affecting others when Name is specified' { + Reset-PSKoan -Topic AboutSomething -Name 'reset content' @defaultParams - $userFilePath | Should -FileContentMatch '1 | Should -Be 1' - $userFilePath | Should -FileContentMatch '__ | Should -Be 2' - } + $userFilePath | Should -FileContentMatch '1 | Should -Be 1' + $userFilePath | Should -FileContentMatch '__ | Should -Be 2' + } - It 'supports context based searching' { - Reset-PSKoan -Topic AboutSomething -Name "nested reset content" -Context 'first' @defaultParams + It 'supports context based searching' { + Reset-PSKoan -Topic AboutSomething -Name "nested reset content" -Context 'first' @defaultParams - $userFilePath | Should -FileContentMatch '__ | Should -Be 3' - $userFilePath | Should -FileContentMatch '4 | Should -Be 4' - } + $userFilePath | Should -FileContentMatch '__ | Should -Be 3' + $userFilePath | Should -FileContentMatch '4 | Should -Be 4' + } - It 'allows koans of a given name to be reset across all contexts' { - Reset-PSKoan -Topic AboutSomething -Name "nested reset content" @defaultParams + It 'allows koans of a given name to be reset across all contexts' { + Reset-PSKoan -Topic AboutSomething -Name "nested reset content" @defaultParams - $userFilePath | Should -FileContentMatch '__ | Should -Be 3' - $userFilePath | Should -FileContentMatch '__ | Should -Be 4' - } + $userFilePath | Should -FileContentMatch '__ | Should -Be 3' + $userFilePath | Should -FileContentMatch '__ | Should -Be 4' + } - It 'supports wildcard patterns when matching name' { - Reset-PSKoan -Topic AboutSomething -Name "*content" @defaultParams + It 'supports wildcard patterns when matching name' { + Reset-PSKoan -Topic AboutSomething -Name "*content" @defaultParams - $userFilePath | Should -FileContentMatch '__ | Should -Be 1' - $userFilePath | Should -FileContentMatch '__ | Should -Be 2' - } + $userFilePath | Should -FileContentMatch '__ | Should -Be 1' + $userFilePath | Should -FileContentMatch '__ | Should -Be 2' } } } diff --git a/Tests/Functions/Public/Set-PSKoanLocation.Tests.ps1 b/Tests/Functions/Public/Set-PSKoanLocation.Tests.ps1 index f9a734d14..cac34f907 100644 --- a/Tests/Functions/Public/Set-PSKoanLocation.Tests.ps1 +++ b/Tests/Functions/Public/Set-PSKoanLocation.Tests.ps1 @@ -1,8 +1,9 @@ #Requires -Modules PSKoans Describe 'Set-PSKoanLocation' { + BeforeAll { - Mock Set-PSKoanSetting -ParameterFilter { $Name -eq 'KoanLocation' } -ModuleName PSKoans + Mock 'Set-PSKoanSetting' -ParameterFilter { $Name -eq 'KoanLocation' } } It 'outputs no data by default' { @@ -10,7 +11,7 @@ Describe 'Set-PSKoanLocation' { } It 'sets the KoanLocation setting' { - Assert-MockCalled Set-PSKoanSetting -ModuleName PSKoans + Should -Invoke 'Set-PSKoanSetting' -Scope Describe } It 'returns the input -Path value back to the pipeline with -PassThru' { diff --git a/Tests/Functions/Public/Set-PSKoanSetting.Tests.ps1 b/Tests/Functions/Public/Set-PSKoanSetting.Tests.ps1 index f657d8321..5feec692f 100644 --- a/Tests/Functions/Public/Set-PSKoanSetting.Tests.ps1 +++ b/Tests/Functions/Public/Set-PSKoanSetting.Tests.ps1 @@ -1,6 +1,7 @@ #Requires -Modules PSKoans Describe 'Set-PSKoanSetting' { + BeforeAll { InModuleScope 'PSKoans' { $script:OldConfigPath = $script:ConfigPath @@ -16,6 +17,7 @@ Describe 'Set-PSKoanSetting' { Context 'Settings file Exists' { Describe 'Setting values with -Name and -Value' { + BeforeAll { $NewConfigPath = InModuleScope 'PSKoans' { ($script:ConfigPath = "$TestDrive/config.json") @@ -25,28 +27,23 @@ Describe 'Set-PSKoanSetting' { New-Item -ItemType File -Path "$TestDrive/config.json" } - $Settings = [PSCustomObject]@{ + [PSCustomObject]@{ LibraryFolder = "$TestDrive/PSKoans" Editor = 'code' - } - $Settings | + } | ConvertTo-Json | Set-Content -Path $NewConfigPath - - $TestCases = @( - @{ Name = 'TestSetting1'; Value = 'TestValue1' } - @{ Name = 'LibraryFolder'; Value = "$TestDrive/AltLocation/PSKoans" } - @{ Name = 'Editor'; Value = 'code-insiders' } - ) } AfterAll { Remove-Item -Path $NewConfigPath -Force } - It 'should add a new setting: = ' -TestCases $TestCases { - param($Name, $Value) - + It 'should add a new setting: = ' -TestCases @( + @{ Name = 'TestSetting1'; Value = 'TestValue1' } + @{ Name = 'LibraryFolder'; Value = "TestDrive:/PSKoans" } + @{ Name = 'Editor'; Value = 'code-insiders' } + ) { Set-PSKoanSetting -Name $Name -Value $Value Get-PSKoanSetting -Name $Name | Should -BeExactly $Value @@ -58,6 +55,7 @@ Describe 'Set-PSKoanSetting' { } Context 'Setting values with -Settings Hashtable' { + BeforeAll { $NewConfigPath = InModuleScope 'PSKoans' { ($script:ConfigPath = "$TestDrive/config.json") @@ -67,11 +65,10 @@ Describe 'Set-PSKoanSetting' { New-Item -ItemType File -Path "$TestDrive/config.json" } - $Settings = [PSCustomObject]@{ + [PSCustomObject]@{ LibraryFolder = "$TestDrive/PSKoans" Editor = 'code' - } - $Settings | + } | ConvertTo-Json | Set-Content -Path $NewConfigPath } @@ -85,6 +82,7 @@ Describe 'Set-PSKoanSetting' { TestSetting2 = "TestValue2" LibraryFolder = "$TestDrive/PSKoans" } + Set-PSKoanSetting -Settings $NewSettings $Settings = Get-PSKoanSetting @@ -98,17 +96,13 @@ Describe 'Set-PSKoanSetting' { Context 'Settings file does not exist' { Describe 'Setting values with -Name and -Value' { + BeforeAll { $NewConfigPath = InModuleScope 'PSKoans' { ($script:ConfigPath = "$TestDrive/config.json") } $DefaultSettings = InModuleScope 'PSKoans' { $script:DefaultSettings } - $TestCases = @( - @{ Name = 'TestSetting1' ; Value = 'TestValue1' } - @{ Name = 'Editor'; Value = 'TestEditor' } - @{ Name = 'LibraryFolder'; Value = "$TestDrive/TestFolder" } - ) } BeforeEach { @@ -117,9 +111,11 @@ Describe 'Set-PSKoanSetting' { } } - It 'correctly adds the setting: = ' -TestCases $TestCases { - param($Name, $Value) - + It 'correctly adds the setting: = ' -TestCases @( + @{ Name = 'TestSetting1' ; Value = 'TestValue1' } + @{ Name = 'Editor'; Value = 'TestEditor' } + @{ Name = 'LibraryFolder'; Value = "$env:TEMP/TestFolder" } + ) { $NewConfigPath | Should -Not -Exist Set-PSKoanSetting -Name $Name -Value $Value $NewConfigPath | Should -Exist @@ -135,28 +131,25 @@ Describe 'Set-PSKoanSetting' { } Context 'Setting values with -Settings Hashtable' { + BeforeAll { $NewConfigPath = InModuleScope 'PSKoans' { ($script:ConfigPath = "$TestDrive/config.json") } $DefaultSettings = InModuleScope 'PSKoans' { $script:DefaultSettings } - $TestCases = @( - @{ Settings = @{ TestSetting1 = 'TestValue1'; Editor = 'TestEditor' } } - @{ Settings = @{ LibraryFolder = "$TestDrive/TestFolder"; TestSetting2 = 'TestValue2' } } - ) } BeforeEach { if (Test-Path -Path $NewConfigPath) { Remove-Item -Path $NewConfigPath } - } - It 'adds and replaces values: ' -TestCases $TestCases { - param($Settings) - + It 'adds and replaces values: ' -TestCases @( + @{ Settings = @{ TestSetting1 = 'TestValue1'; Editor = 'TestEditor' } } + @{ Settings = @{ LibraryFolder = "$TestDrive/TestFolder"; TestSetting2 = 'TestValue2' } } + ) { $NewConfigPath | Should -Not -Exist Set-PSKoanSetting -Settings $Settings $NewConfigPath | Should -Exist diff --git a/Tests/Functions/Public/Show-Advice.Tests.ps1 b/Tests/Functions/Public/Show-Advice.Tests.ps1 index fc8345992..e91fa726a 100644 --- a/Tests/Functions/Public/Show-Advice.Tests.ps1 +++ b/Tests/Functions/Public/Show-Advice.Tests.ps1 @@ -1,87 +1,82 @@ #Requires -Modules PSKoans -InModuleScope 'PSKoans' { - Describe "Show-Advice" { +Describe "Show-Advice" { - BeforeAll { - Mock Write-ConsoleLine { } - # Exporting incorrect advices to the folder - $AdviceFolder = $script:ModuleRoot | Join-Path -ChildPath 'Data/Advice' - $AdviceObject = Get-ChildItem -Path $AdviceFolder -Recurse -File -Filter "*.Advice.json" - $RandomAdvicesFilePaths = ($AdviceObject | Get-Random -Count 3).FullName - $Backup = $RandomAdvicesFilePaths | ForEach-Object { Get-Content $_ | ConvertFrom-Json } - } + BeforeAll { + Mock 'Write-ConsoleLine' -ModuleName 'PSKoans' + } + + Context "Behaviour of Parameter-less Calls" { - Context "Behaviour of Parameter-less Calls" { + BeforeAll { $result = Show-Advice + } - It "calls Write-ConsoleLine with Parameter -Title" { - Assert-MockCalled -CommandName Write-ConsoleLine -ParameterFilter { $null -eq $Title } - } + It "calls Write-ConsoleLine with Parameter -Title" { + Should -Invoke 'Write-ConsoleLine' -ModuleName 'PSKoans' -ParameterFilter { $null -eq $Title } -Scope Context + } - It "calls Write-ConsoleLine with only the display string" { - Assert-MockCalled -CommandName Write-ConsoleLine -ParameterFilter { $null -ne $Title } -Times 1 - } + It "calls Write-ConsoleLine with only the display string" { + Should -Invoke 'Write-ConsoleLine' -ModuleName 'PSKoans' -ParameterFilter { $null -ne $Title } -Scope Context + } - It "outputs nothing to the pipeline" { - $result | Should -BeNullOrEmpty - } + It "outputs nothing to the pipeline" { + $result | Should -BeNullOrEmpty } + } - Context "Behaviour with -Name Parameter" { + Context "Behaviour with -Name Parameter" { - It "should call Write-ConsoleLine with normal parameters" { - Show-Advice -Name "Profile" - Assert-MockCalled -CommandName Write-ConsoleLine -ParameterFilter { $null -ne $Title } - } + BeforeAll { + Show-Advice -Name "Profile" + } - It "should call Write-ConsoleLine without parameters" { - Show-Advice -Name "Profile" - Assert-MockCalled -CommandName Write-ConsoleLine -ParameterFilter { $null -eq $Title } - } + It "should call Write-ConsoleLine with normal parameters" { + Should -Invoke 'Write-ConsoleLine' -ParameterFilter { $null -ne $Title } -ModuleName 'PSKoans' -Scope Context + } - It "should throw an error if the requested file cannot be found" { - $message = "Could not find any Advice files matching the specified Name: ThisDoesntExist" - { Show-Advice -Name "ThisDoesntExist" -ErrorAction Stop } | Should -Throw -ExpectedMessage $Message - } + It "should call Write-ConsoleLine without parameters" { + Should -Invoke 'Write-ConsoleLine' -ParameterFilter { $null -eq $Title } -ModuleName 'PSKoans' -Scope Context } - Context 'Behaviour with malformed advice files' { - - BeforeAll { - $MalformedAdviceCases = @( - @{ - Json = @{ - NotTitle = "Fake title" - NotContent = @(1..4 | ForEach-Object { "Fake line $_" }) - } | ConvertTo-Json - } - @{ - Json = @{ - Content = @(1..4 | ForEach-Object { "Fake line $_" }) - } | ConvertTo-Json - } - @{ - Json = @{ - Title = "Fake title" - } | ConvertTo-Json - } - ) - - $GetContentResult = [string]::Empty - - Mock Get-Content -MockWith { $GetContentResult } - Mock Get-ChildItem -MockWith { [PSCustomObject]@{ PSPath = "DummyPath" } } - } + It "should throw an error if the requested file cannot be found" { + $message = "Could not find any Advice files matching the specified Name: ThisDoesntExist." + { Show-Advice -name "ThisDoesntExist" -ErrorAction Stop } | Should -Throw -ExpectedMessage $Message + } + } - It "should throw an error if the requested file's format is not correct" -TestCases $MalformedAdviceCases { - param($Json) + Context 'Behaviour with malformed advice files' { - $GetContentResult = $Json - $AdviceName = "TestAdvice" - $Message = "Could not find Title and/or Content elements for Advice file: {0}" -f $AdviceName - { Show-Advice -Name $AdviceName -ErrorAction Stop } | Should -Throw -ExpectedMessage $Message + BeforeAll { + $GetContentResult = [string]::Empty + + Mock Get-Content -MockWith { $GetContentResult } -Verifiable + Mock Get-ChildItem -MockWith { [PSCustomObject]@{ PSPath = "DummyPath" } } -Verifiable + } + + It "should throw an error if the requested file's format is not correct" -TestCases @( + @{ + Json = @{ + NotTitle = "Fake title" + NotContent = @(1..4 | ForEach-Object { "Fake line $_" }) + } | ConvertTo-Json + } + @{ + Json = @{ + Content = @(1..4 | ForEach-Object { "Fake line $_" }) + } | ConvertTo-Json + } + @{ + Json = @{ + Title = "Fake title" + } | ConvertTo-Json } + ) { + $GetContentResult = $Json + $AdviceName = "TestAdvice" + $Message = "Could not find Title and/or Content elements for Advice file: {0}" -f $AdviceName + { Show-Advice -name $AdviceName -ErrorAction Stop } | Should -Throw -ExpectedMessage $Message + Should -InvokeVerifiable } } } diff --git a/Tests/Functions/Public/Show-Karma.Tests.ps1 b/Tests/Functions/Public/Show-Karma.Tests.ps1 index 15259688a..522adbe64 100644 --- a/Tests/Functions/Public/Show-Karma.Tests.ps1 +++ b/Tests/Functions/Public/Show-Karma.Tests.ps1 @@ -1,15 +1,11 @@ -#region Header -if (-not (Get-Module PSKoans)) { - $moduleBase = Join-Path -Path $psscriptroot.Substring(0, $psscriptroot.IndexOf('\Tests')) -ChildPath 'PSKoans' - - Import-Module $moduleBase -Force -} -#endregion +#Requires -Modules PSKoans Describe 'Show-Karma' { + BeforeAll { - $StartingLocation = Get-PSKoanLocation - Set-PSKoanLocation -Path "$TestDrive/Koans" + Mock 'Get-PSKoanLocation' { + "$TestDrive/Koans" + } $EditorSetting = Get-PSKoanSetting -Name Editor @@ -17,355 +13,367 @@ Describe 'Show-Karma' { } AfterAll { - Set-PSKoanLocation -Path $StartingLocation Set-PSKoanSetting -Name Editor -Value $EditorSetting } - InModuleScope 'PSKoans' { - - Context 'Default Behaviour' { - BeforeAll { - Mock Out-Host { } - Mock Get-Karma -ModuleName 'PSKoans' { - [PSCustomObject]@{ - PSTypeName = 'PSKoans.Result' - Meditation = 'TestMeditation' - KoansPassed = 0 - TotalKoans = 400 - Describe = 'TestDescribe' - Expectation = 'ExpectedTest' - It = 'TestIt' - CurrentTopic = [PSCustomObject]@{ - Name = 'TestTopic"' - Completed = 0 - Total = 4 - CurrentLine = 1 - } + Context 'Default Behaviour' { + + BeforeAll { + Mock 'Out-Host' + Mock 'Get-Karma' { + [PSCustomObject]@{ + PSTypeName = 'PSKoans.Result' + Meditation = 'TestMeditation' + KoansPassed = 0 + TotalKoans = 400 + Describe = 'TestDescribe' + Expectation = 'ExpectedTest' + It = 'TestIt' + CurrentTopic = [PSCustomObject]@{ + Name = 'TestTopic"' + Completed = 0 + Total = 4 + CurrentLine = 1 } } } + } - It 'should not produce output' { - Show-Karma | Should -BeNullOrEmpty - } + It 'should not produce output' { + Show-Karma | Should -BeNullOrEmpty + } - It 'should write the formatted output to host' { - Assert-MockCalled Out-Host - } + It 'should write the formatted output to host' { + Should -Invoke 'Out-Host' -Scope Context + } - It 'should call Get-Karma to examine the koans' { - Assert-MockCalled Get-Karma - } + It 'should call Get-Karma to examine the koans' { + Should -Invoke 'Get-Karma' -Scope Context } + } - Context 'With All Koans Completed' { - BeforeAll { - Mock Out-Host { } - Mock Get-Karma -ModuleName 'PSKoans' { - [PSCustomObject]@{ - PSTypeName = 'PSKoans.CompleteResult' - KoansPassed = 10 - TotalKoans = 10 - RequestedTopci = $null - Complete = $true - } + Context 'With All Koans Completed' { + + BeforeAll { + Mock 'Out-Host' -Verifiable + Mock 'Get-Karma' -Verifiable { + [PSCustomObject]@{ + PSTypeName = 'PSKoans.CompleteResult' + KoansPassed = 10 + TotalKoans = 10 + RequestedTopci = $null + Complete = $true } } + } - It 'should not throw errors' { - { Show-Karma } | Should -Not -Throw - } + It 'should not throw errors' { + { Show-Karma } | Should -Not -Throw + Should -InvokeVerifiable } + } - Context 'With -ClearScreen Switch' { - BeforeAll { - Mock Clear-Host { } - Mock Out-Host { } - Mock Get-Karma -ModuleName 'PSKoans' { - [PSCustomObject]@{ - PSTypeName = 'PSKoans.Result' - Meditation = 'TestMeditation' - KoansPassed = 0 - TotalKoans = 400 - Describe = 'TestDescribe' - Expectation = 'ExpectedTest' - It = 'TestIt' - CurrentTopic = [PSCustomObject]@{ - Name = 'TestTopic"' - Completed = 0 - Total = 4 - CurrentLine = 1 - } + Context 'With -ClearScreen Switch' { + + BeforeAll { + Mock 'Clear-Host' + Mock 'Out-Host' + Mock 'Get-Karma' { + [PSCustomObject]@{ + PSTypeName = 'PSKoans.Result' + Meditation = 'TestMeditation' + KoansPassed = 0 + TotalKoans = 400 + Describe = 'TestDescribe' + Expectation = 'ExpectedTest' + It = 'TestIt' + CurrentTopic = [PSCustomObject]@{ + Name = 'TestTopic"' + Completed = 0 + Total = 4 + CurrentLine = 1 } } } + } - It 'should not produce output' { - Show-Karma -ClearScreen | Should -Be $null - } + It 'should not produce output' { + Show-Karma -ClearScreen | Should -Be $null + } - It 'should clear the screen' { - Assert-MockCalled Clear-Host -Times 1 - } + It 'should clear the screen' { + Should -Invoke 'Clear-Host' -Scope Context -Times 1 -Exactly + } - It 'should display the rendered output' { - Assert-MockCalled Out-Host - } + It 'should display the rendered output' { + Should -Invoke 'Out-Host' -Scope Context + } - It 'should Invoke-Pester on each of the koans' { - Assert-MockCalled Get-Karma - } + It 'should use Get-Karma to retrieve koan results' { + Should -Invoke 'Get-Karma' -Scope Context -Times 1 -Exactly } + } - Context 'With Nonexistent Koans Folder / No Koans Found' { - BeforeAll { - Mock Out-Host { } - Mock Get-PSKoan -ModuleName 'PSKoans' { } - Mock Update-PSKoan -ModuleName 'PSKoans' { throw 'Prevent recursion' } - Mock Write-Warning - Mock Test-Path { $false } - Mock Invoke-Item - Mock New-Item - } + Context 'With Nonexistent Koans Folder / No Koans Found' { - BeforeEach { - $script:CurrentTopic = $null - } + BeforeAll { + Mock 'Write-Host' + Mock 'Get-PSKoan' + Mock 'Update-PSKoan' { throw 'Prevent recursion' } + Mock 'Write-Warning' + Mock 'Test-Path' { $false } + Mock 'Invoke-Item' + Mock 'Measure-Koan' -ModuleName 'PSKoans' + } - It 'should attempt to populate koans and then recurse to reassess' { - { Show-Karma } | Should -Throw -ExpectedMessage 'Prevent recursion' - } + BeforeEach { + InModuleScope 'PSKoans' { $script:CurrentTopic = $null } + } - It 'should display a warning before initiating a reset' { - Assert-MockCalled Write-Warning - } + It 'should attempt to populate koans and then recurse to reassess' { + { Show-Karma } | Should -Throw -ExpectedMessage 'Prevent recursion' + } - It 'throws an error if a Topic is specified that matches nothing' { - { Show-Karma -Topic 'AboutAbsolutelyNothing' } | Should -Throw -ErrorId 'PSKoans.TopicNotFound' - } + It 'should display a warning before initiating a reset' { + Should -Invoke 'Write-Warning' -Scope Context -Times 1 -Exactly + } - It 'should create PSKoans directory with -Library' { - { Show-Karma -Library } | Should -Throw -ExpectedMessage 'Prevent recursion' + It 'throws an error if a Topic is specified that matches nothing' { + { Show-Karma -Topic 'AboutAbsolutelyNothing' } | Should -Throw -ErrorId 'PSKoans.TopicNotFound,Show-Karma' + } - Assert-MockCalled Test-Path -Times 1 - Assert-MockCalled Update-PSKoan -Times 1 - Assert-MockCalled New-Item -Times 1 - } + It 'should create PSKoans directory with -Library' { + { Show-Karma -Library } | Should -Throw -ExpectedMessage 'Prevent recursion' - It 'should create PSKoans directory with -Contemplate' { - { Show-Karma -Contemplate } | Should -Throw -ExpectedMessage 'Prevent recursion' + Should -Invoke 'Test-Path' + Should -Invoke 'Update-PSKoan' -Times 1 -Exactly + } - Assert-MockCalled Test-Path -Times 1 - Assert-MockCalled Update-PSKoan -Times 1 - Assert-MockCalled New-Item -Times 1 - } + It 'should call Get-PSKoan to retrieve the correct file -Contemplate' { + { Show-Karma -Contemplate } | Should -Throw -ExpectedMessage 'Prevent recursion' + + Should -Invoke 'Get-PSKoan' -Times 1 -Exactly + Should -Invoke 'Update-PSKoan' -Times 1 -Exactly } + } - Context 'With -ListTopics Parameter' { - BeforeAll { - Mock Get-PSKoan - } + Context 'With -ListTopics Parameter' { - It 'should list all the koan topics' { - Show-Karma -ListTopics - Assert-MockCalled Get-PSKoan - } + BeforeAll { + Mock 'Get-PSKoan' + } + + It 'should list all the koan topics' { + Show-Karma -ListTopics + Should -Invoke 'Get-PSKoan' -Times 1 -Exactly } + } - Context 'With -Topic Parameter' { - BeforeAll { - Mock Out-Host { } - Mock Get-Karma -MockWith { - [PSCustomObject]@{ - PSTypeName = 'PSKoans.Result' - Meditation = 'TestMeditation' - KoansPassed = 0 - TotalKoans = 400 - Describe = 'TestDescribe' - Expectation = 'ExpectedTest' - It = 'TestIt' - CurrentTopic = [PSCustomObject]@{ - Name = 'TestTopic"' - Completed = 0 - Total = 4 - CurrentLine = 1 - } - RequestedTopic = $Topic + Context 'With -Topic Parameter' { + + BeforeAll { + Mock 'Out-Host' -Verifiable + Mock 'Get-Karma' -ParameterFilter { $Topic -eq 'TestTopic' } -Verifiable -MockWith { + [PSCustomObject]@{ + PSTypeName = 'PSKoans.Result' + Meditation = 'TestMeditation' + KoansPassed = 0 + TotalKoans = 400 + Describe = 'TestDescribe' + Expectation = 'ExpectedTest' + It = 'TestIt' + CurrentTopic = [PSCustomObject]@{ + Name = 'TestTopic"' + Completed = 0 + Total = 4 + CurrentLine = 1 } - } -ParameterFilter { $Topic } + RequestedTopic = $Topic + } } + } - It 'should call Get-Karma on the selected topic' { - Show-Karma -Topic TestTopic - Assert-MockCalled Get-Karma -ParameterFilter { $Topic -eq "TestTopic" } - } + It 'should call Get-Karma on the selected topic' { + Show-Karma -Topic TestTopic + Should -InvokeVerifiable } + } - Context 'With All Koans in a Single Topic Completed' { - BeforeAll { - Mock Format-Custom { $InputObject.Complete } - Mock Out-Host { } - Mock Get-Karma -ModuleName 'PSKoans' { - [PSCustomObject]@{ - PSTypeName = 'PSKoans.CompleteResult' - KoansPassed = 10 - TotalKoans = 10 - RequestedTopic = 'TestTopic' - Complete = $true - } + Context 'With All Koans in a Single Topic Completed' { + + BeforeAll { + Mock 'Format-Custom' -Verifiable { $null } + Mock 'Out-Host' -Verifiable + Mock 'Get-Karma' -Verifiable { + [PSCustomObject]@{ + PSTypeName = 'PSKoans.CompleteResult' + KoansPassed = 10 + TotalKoans = 10 + RequestedTopic = 'TestTopic' + Complete = $true } } + } - It 'should not throw errors' { - { Show-Karma } | Should -Not -Throw - } + It 'should not throw errors' { + { Show-Karma } | Should -Not -Throw + Should -InvokeVerifiable } + } + + Context 'With -Contemplate Switch' { - Context 'With -Contemplate Switch' { - BeforeAll { - $TestFile = New-TemporaryFile + BeforeAll { + $TestFile = New-TemporaryFile - Mock Invoke-Item { $Path } - Mock Get-Command { $true } -ParameterFilter { $Name -ne "missing_editor" } - Mock Get-Command { $false } -ParameterFilter { $Name -eq "missing_editor" } - Mock Start-Process { - @{ Editor = $FilePath; Arguments = $ArgumentList; NoNewWindow = $NoNewWindow } + Mock 'Invoke-Item' { $Path } + Mock 'Get-Command' { $true } -ParameterFilter { $Name -ne "missing_editor" } + Mock 'Get-Command' { $false } -ParameterFilter { $Name -eq "missing_editor" } + Mock 'Start-Process' { + @{ Editor = $FilePath; Arguments = $ArgumentList; NoNewWindow = $NoNewWindow } + } + + Mock 'Get-Karma' { + $currentTopic = @{ + Name = 'TestTopic' + Completed = 0 + Total = 4 + CurrentLine = 1 } - Mock Get-Karma -ModuleName 'PSKoans' { - $script:CurrentTopic = @{ - Name = 'TestTopic' - Completed = 0 - Total = 4 - CurrentLine = 1 - } - [PSCustomObject]@{ - PSTypeName = 'PSKoans.Result' - Meditation = 'TestMeditation' - KoansPassed = 0 - TotalKoans = 400 - Describe = 'TestDescribe' - Expectation = 'ExpectedTest' - It = 'TestIt' - CurrentTopic = [PSCustomObject]$script:CurrentTopic - } + [PSCustomObject]@{ + PSTypeName = 'PSKoans.Result' + Meditation = 'TestMeditation' + KoansPassed = 0 + TotalKoans = 400 + Describe = 'TestDescribe' + Expectation = 'ExpectedTest' + It = 'TestIt' + CurrentTopic = [PSCustomObject]$currentTopic + } + + InModuleScope 'PSKoans' -Parameters @{ Topic = $currentTopic } { + param($Topic) + $script:CurrentTopic = $Topic } - Mock Get-PSKoan -ModuleName 'PSKoans' { - [PSCustomObject]@{ Path = $TestFile.FullName } - } -ParameterFilter { $Topic -eq 'TestTopic' } } - AfterAll { - $TestFile | Remove-Item + Mock 'Get-PSKoan' -ParameterFilter { $Topic -eq 'TestTopic' -and $Scope -eq 'User' } { + [PSCustomObject]@{ Path = $TestFile.FullName } } + } - It 'invokes VS Code with "code" set as Editor with proper arguments' { - Set-PSKoanSetting -Name Editor -Value 'code' - $Result = Show-Karma -Contemplate + AfterAll { + $TestFile | Remove-Item + } - $Result.Editor | Should -BeExactly 'code' - $Result.Arguments[0] | Should -BeExactly '--goto' - $Result.Arguments[1] | Should -MatchExactly '"[^"]+":\d+' - $Result.Arguments[2] | Should -BeExactly '--reuse-window' - $Result.NoNewWindow | Should -BeTrue + It 'invokes VS Code with "code" set as Editor with proper arguments' { + Set-PSKoanSetting -Name Editor -Value 'code' + $Result = Show-Karma -Contemplate - # Resolve-Path doesn't like embedded quotes - $Path = ($Result.Arguments[1] -split '(?<="):')[0] -replace '"' - $Path | Should -BeExactly (Resolve-Path -Path $Path).Path + $Result.Editor | Should -BeExactly 'code' + $Result.Arguments[0] | Should -BeExactly '--goto' + $Result.Arguments[1] | Should -MatchExactly '"[^"]+":\d+' + $Result.Arguments[2] | Should -BeExactly '--reuse-window' + $Result.NoNewWindow | Should -BeTrue - Assert-MockCalled Get-Command -Times 1 - Assert-MockCalled Start-Process -Times 1 + # Resolve-Path doesn't like embedded quotes + $Path = ($Result.Arguments[1] -split '(?<="):')[0] -replace '"' + $Path | Should -BeExactly (Resolve-Path -Path $Path).Path - $script:CurrentTopic | Should -BeNullOrEmpty - } + Should -Invoke 'Get-Command' -Times 1 -Exactly + Should -Invoke 'Start-Process' -Times 1 -Exactly - It 'opens the specified -Topic in the selected editor' { - Set-PSKoanSetting -Name Editor -Value 'code' - $Result = Show-Karma -Contemplate -Topic TestTopic + InModuleScope 'PSKoans' { $script:CurrentTopic } | Should -BeNullOrEmpty + } - $Result.Arguments[1] | Should -MatchExactly ([regex]::Escape($TestFile.FullName)) + It 'opens the specified -Topic in the selected editor' { + Set-PSKoanSetting -Name Editor -Value 'code' - Assert-MockCalled Get-Command -Times 1 - Assert-MockCalled Start-Process -Times 1 + $Result = Show-Karma -Contemplate -Topic TestTopic + $Result.Arguments[1] | Should -MatchExactly ([regex]::Escape($TestFile.FullName)) - $script:CurrentTopic | Should -BeNullOrEmpty - } + Should -Invoke 'Get-Command' -Times 1 -Exactly + Should -Invoke 'Start-Process' -Times 1 -Exactly - It 'invokes the set editor with unknown editor chosen' { - Set-PSKoanSetting -Name Editor -Value 'vim' + InModuleScope 'PSKoans' { $script:CurrentTopic } | Should -BeNullOrEmpty + } - $Result = Show-Karma -Contemplate - $Result.Editor | Should -BeExactly 'vim' - $Result.Arguments | Should -MatchExactly '"[^"]+"' + It 'invokes the set editor with unknown editor chosen' { + Set-PSKoanSetting -Name Editor -Value 'vim' - # Resolve-Path doesn't like embedded quotes - $Path = $Result.Arguments -replace '"' - $Path | Should -BeExactly (Resolve-Path -Path $Path).Path + $Result = Show-Karma -Contemplate + $Result.Editor | Should -BeExactly 'vim' + $Result.Arguments | Should -MatchExactly '"[^"]+"' - Assert-MockCalled Get-Command -Times 1 - Assert-MockCalled Start-Process -Times 1 + # Resolve-Path doesn't like embedded quotes + $Path = $Result.Arguments -replace '"' + $Path | Should -BeExactly (Resolve-Path -Path $Path).Path - $script:CurrentTopic | Should -BeNullOrEmpty - } + Should -Invoke 'Get-Command' -Times 1 -Exactly + Should -Invoke 'Start-Process' -Times 1 -Exactly + + InModuleScope 'PSKoans' { $script:CurrentTopic } | Should -BeNullOrEmpty + } - It 'opens the file directly when selected editor is unavailable' { - Set-PSKoanSetting -Name Editor -Value "missing_editor" + It 'opens the file directly when selected editor is unavailable' { + Set-PSKoanSetting -name Editor -Value "missing_editor" - Show-Karma -Contemplate | Should -BeExactly $TestFile.FullName + Show-Karma -Contemplate | Should -BeExactly $TestFile.FullName - Assert-MockCalled Get-Command -Times 1 -ParameterFilter { $Name -eq "missing_editor" } - Assert-MockCalled Invoke-Item -Times 1 + Should -Invoke 'Get-Command' -Times 1 -Exactly -ParameterFilter { $Name -eq "missing_editor" } + Should -Invoke 'Invoke-Item' -Times 1 -Exactly - $script:CurrentTopic | Should -BeNullOrEmpty - } + InModuleScope 'PSKoans' { $script:CurrentTopic } | Should -BeNullOrEmpty } + } - Context 'With -Library Switch' { - BeforeAll { - Mock Get-Command { $true } -ParameterFilter { $Name -ne "missing_editor" } - Mock Get-Command { $false } -ParameterFilter { $Name -eq "missing_editor" } - Mock Start-Process { - @{ Editor = $FilePath; Arguments = $ArgumentList } - } - Mock Invoke-Item { $Path } + Context 'With -Library Switch' { + + BeforeAll { + Mock 'Get-Command' { $true } -ParameterFilter { $Name -ne "missing_editor" } + Mock 'Get-Command' { $false } -ParameterFilter { $Name -eq "missing_editor" } + Mock 'Start-Process' { + @{ Editor = $FilePath; Arguments = $ArgumentList } } + Mock 'Invoke-Item' { $Path } + } - It 'invokes VS Code with "code" set as Editor with proper arguments' { - Set-PSKoanSetting -Name Editor -Value 'code' - $Result = Show-Karma -Library + It 'invokes VS Code with "code" set as Editor with proper arguments' { + Set-PSKoanSetting -Name Editor -Value 'code' - $Result.Editor | Should -BeExactly 'code' + $Result = Show-Karma -Library + $Result.Editor | Should -BeExactly 'code' - # Resolve-Path doesn't like embedded quotes - $Path = $Result.Arguments -replace '"' - $Path | Should -BeExactly (Resolve-Path -Path $Path).Path + # Resolve-Path doesn't like embedded quotes + $Path = $Result.Arguments -replace '"' + $Path | Should -BeExactly (Resolve-Path -Path $Path).Path - Assert-MockCalled Get-Command -Times 1 - Assert-MockCalled Start-Process -Times 1 - } + Should -Invoke 'Get-Command' -Times 1 -Exactly + Should -Invoke 'Start-Process' -Times 1 -Exactly + } - It 'invokes the set editor with unknown editor chosen' { - Set-PSKoanSetting -Name Editor -Value 'vim' + It 'invokes the set editor with unknown editor chosen' { + Set-PSKoanSetting -Name Editor -Value 'vim' - $Result = Show-Karma -Library - $Result.Editor | Should -BeExactly 'vim' + $Result = Show-Karma -Library + $Result.Editor | Should -BeExactly 'vim' - # Resolve-Path doesn't like embedded quotes - $Path = $Result.Arguments -replace '"' - $Path | Should -BeExactly (Resolve-Path -Path $Path).Path + # Resolve-Path doesn't like embedded quotes + $Path = $Result.Arguments -replace '"' + $Path | Should -BeExactly (Resolve-Path -Path $Path).Path - Assert-MockCalled Get-Command -Times 1 - Assert-MockCalled Start-Process -Times 1 - } + Should -Invoke 'Get-Command' -Times 1 -Exactly + Should -Invoke 'Start-Process' -Times 1 -Exactly + } - It 'opens the file directly when selected editor is unavailable' { - Set-PSKoanSetting -Name Editor -Value "missing_editor" + It 'opens the file directly when selected editor is unavailable' { + Set-PSKoanSetting -name Editor -Value "missing_editor" - Show-Karma -Library | Should -BeExactly (Get-PSKoanLocation) + Show-Karma -Library | Should -BeExactly (Get-PSKoanLocation) - Assert-MockCalled Get-Command -Times 1 -ParameterFilter { $Name -eq "missing_editor" } - Assert-MockCalled Invoke-Item -Times 1 - } + Should -Invoke 'Get-Command' -Times 1 -Exactly -ParameterFilter { $Name -eq "missing_editor" } + Should -Invoke 'Invoke-Item' -Times 1 -Exactly } } } diff --git a/Tests/Functions/Public/Update-PSKoan.Tests.ps1 b/Tests/Functions/Public/Update-PSKoan.Tests.ps1 index 8806c56c8..d86cf7f29 100644 --- a/Tests/Functions/Public/Update-PSKoan.Tests.ps1 +++ b/Tests/Functions/Public/Update-PSKoan.Tests.ps1 @@ -1,115 +1,109 @@ -#region Header -if (-not (Get-Module PSKoans)) { - $moduleBase = Join-Path -Path $psscriptroot.Substring(0, $psscriptroot.IndexOf('\Tests')) -ChildPath 'PSKoans' +#Requires -Modules PSKoans - Import-Module $moduleBase -Force -} -#endregion +Describe 'Update-PSKoan' { + + Context 'Mocked Commands' { -InModuleScope 'PSKoans' { - Describe 'Update-PSKoan' { BeforeAll { - Mock Get-PSKoanLocation { - Join-Path -Path $TestDrive -ChildPath 'PSKoans' + Mock 'Remove-Item' + Mock 'Copy-Item' + Mock 'New-Item' + Mock 'Move-Item' + Mock 'Update-PSKoanFile' -ModuleName 'PSKoans' + + Mock 'Get-PSKoan' -ParameterFilter { $Scope -eq 'Module' } -MockWith { + [PSCustomObject]@{ + Topic = 'Missing' + Path = 'Module\Group\AboutSomethingMissing.Koans.ps1' + } + [PSCustomObject]@{ + Topic = 'IncorrectPath' + Path = 'Module\Group\AboutSomethingIncorrectPath.Koans.ps1' + } + [PSCustomObject]@{ + Topic = 'Existing' + Path = 'Module\Group\AboutSomethingExisting.Koans.ps1' + } } - New-Item -Path (Get-PSKoanLocation) -ItemType Directory - } - Context 'Mocked Commands' { - BeforeAll { - Mock Remove-Item - Mock Copy-Item - Mock New-Item - Mock Move-Item - Mock Update-PSKoanFile - - Mock Get-PSKoan -ParameterFilter { $Scope -eq 'Module' } -MockWith { - [PSCustomObject]@{ - Topic = 'Missing' - Path = 'Module\Group\AboutSomethingMissing.Koans.ps1' - } - [PSCustomObject]@{ - Topic = 'IncorrectPath' - Path = 'Module\Group\AboutSomethingIncorrectPath.Koans.ps1' - } - [PSCustomObject]@{ - Topic = 'Existing' - Path = 'Module\Group\AboutSomethingExisting.Koans.ps1' - } + Mock 'Get-PSKoan' -ParameterFilter { $Scope -eq 'User' } -MockWith { + [PSCustomObject]@{ + Topic = 'IncorrectPath' + Path = 'Module\RetiredGroup\AboutSomethingIncorrectPath.Koans.ps1' } - Mock Get-PSKoan -ParameterFilter { $Scope -eq 'User' } -MockWith { - [PSCustomObject]@{ - Topic = 'IncorrectPath' - Path = 'Module\RetiredGroup\AboutSomethingIncorrectPath.Koans.ps1' - } - [PSCustomObject]@{ - Topic = 'RetiredTopic' - Path = 'Module\RetiredGroup\AboutSomethingRetiredTopic.Koans.ps1' - } - [PSCustomObject]@{ - Topic = 'Existing' - Path = 'Module\Group\AboutSomethingExisting.Koans.ps1' - } + [PSCustomObject]@{ + Topic = 'RetiredTopic' + Path = 'Module\RetiredGroup\AboutSomethingRetiredTopic.Koans.ps1' + } + [PSCustomObject]@{ + Topic = 'Existing' + Path = 'Module\Group\AboutSomethingExisting.Koans.ps1' } } + } - It 'should not produce output' { - Update-PSKoan -Confirm:$false | Should -BeNullOrEmpty - } + It 'should not produce output' { + Update-PSKoan -Confirm:$false | Should -BeNullOrEmpty + } - It 'should copy missing topic files' { - Assert-MockCalled Copy-Item -Times 1 - } + It 'should copy missing topic files' { + Should -Invoke 'Copy-Item' -Times 1 -Scope Context + } - It 'should move incorrectly placed topics' { - Assert-MockCalled Remove-Item -Times 1 - } + It 'should move incorrectly placed topics' { + Should -Invoke 'Remove-Item' -Times 1 -Scope Context + } - It 'should remove discarded topics' { - Assert-MockCalled Remove-Item -Times 1 - } + It 'should remove discarded topics' { + Should -Invoke 'Remove-Item' -Times 1 -Scope Context + } - It 'should update topics which exist in module and koan path' { - Assert-MockCalled Update-PSKoanFile -Times 2 - } + It 'should update topics which exist in module and koan path' { + Should -Invoke 'Update-PSKoanFile' -ModuleName 'PSKoans' -Times 2 -Scope Context } + } - Context 'Practical Tests with TestDrive' { - BeforeAll { - Update-PSKoan -Confirm:$false + Context 'Practical Tests with TestDrive' { - $file = Get-ChildItem -Path (Get-PSKoanLocation) -Filter *.koans.ps1 -File -Recurse | - Select-Object -First 1 + BeforeAll { + Mock 'Get-PSKoanLocation' { + Join-Path -Path $TestDrive -ChildPath 'PSKoans' } - It 'should copy missing topic files' { - $file | Remove-Item - $file.FullName | Should -Not -Exist + New-Item -Path (Get-PSKoanLocation) -ItemType Directory + Update-PSKoan -Confirm:$false - Update-PSKoan -Confirm:$false + $file = Get-ChildItem -Path (Get-PSKoanLocation) -Filter *.koans.ps1 -File -Recurse | + Select-Object -First 1 + } - $file.FullName | Should -Exist - } + It 'should copy missing topic files' { + $file | Remove-Item + $file.FullName | Should -Not -Exist - It 'should move incorrectly placed topics' { - $directory = New-Item -Path (Join-Path -Path $TestDrive -ChildPath 'PSKoans\Wrong') -ItemType Directory - $file | Move-Item -Destination $directory.FullName - $file.FullName | Should -Not -Exist + Update-PSKoan -Confirm:$false - Update-PSKoan -Confirm:$false + $file.FullName | Should -Exist + } - $file.FullName | Should -Exist - } + It 'should move incorrectly placed topics' { + $directory = New-Item -Path (Join-Path -Path $TestDrive -ChildPath 'PSKoans\Wrong') -ItemType Directory + $file | Move-Item -Destination $directory.FullName + $file.FullName | Should -Not -Exist - It 'should remove discarded topics' { - $oldTopicPath = Join-Path $TestDrive 'PSKoans\Foundations\OldTopic.koans.ps1' - $file | Copy-Item -Destination $oldTopicPath - $oldTopicPath | Should -Exist + Update-PSKoan -Confirm:$false - Update-PSKoan -Confirm:$false + $file.FullName | Should -Exist + } - $oldTopicPath | Should -Not -Exist - } + It 'should remove discarded topics' { + $oldTopicPath = Join-Path $TestDrive 'PSKoans\Foundations\OldTopic.koans.ps1' + $file | Copy-Item -Destination $oldTopicPath + $oldTopicPath | Should -Exist + + Update-PSKoan -Confirm:$false + + $oldTopicPath | Should -Not -Exist } } } diff --git a/Tests/KoanValidation.Tests.ps1 b/Tests/KoanValidation.Tests.ps1 index 76708e75b..ac6b8e6c3 100644 --- a/Tests/KoanValidation.Tests.ps1 +++ b/Tests/KoanValidation.Tests.ps1 @@ -3,33 +3,27 @@ using namespace System.Management.Automation.Language using namespace System.Collections.Generic -$ProjectRoot = Resolve-Path "$PSScriptRoot/.." -$KoanFolder = $ProjectRoot | - Join-Path -ChildPath 'PSKoans' -AdditionalChildPath 'Koans' - -Describe 'Koan Topics Static Analysis Checks' { +Describe 'Static Analysis: Koan Topics' { Context 'Individual Topics' { - BeforeAll { - # TestCases are splatted to the script so we need hashtables - $KoanTopics = Get-ChildItem -Path $KoanFolder -Recurse -Filter '*.Koans.ps1' | - ForEach-Object { - $commandInfo = Get-Command -Name $_.FullName -ErrorAction SilentlyContinue - $koanAttribute = $commandInfo.ScriptBlock.Attributes.Where{ $_.TypeID -match 'Koan' } - - @{ - File = $_ - Name = $_.BaseName -replace '\.Koans$' - Position = $koanAttribute.Position - Module = $koanAttribute.Module - } + #region Discovery + $KoanTopics = Resolve-Path "$PSScriptRoot/../PSKoans/Koans" | + Get-ChildItem -Recurse -Filter '*.Koans.ps1' | + ForEach-Object { + $commandInfo = Get-Command -Name $_.FullName -ErrorAction SilentlyContinue + $koanAttribute = $commandInfo.ScriptBlock.Attributes.Where{ $_.TypeID -match 'Koan' } + + @{ + File = $_ + Name = $_.BaseName -replace '\.Koans$' + Position = $koanAttribute.Position + Module = $koanAttribute.Module } - } - - It 'Koan Topic should be valid powershell' -TestCases $KoanTopics { - param($File) + } + #endregion Discovery + It 'has no syntax errors in ' -TestCases $KoanTopics { $File.FullName | Should -Exist $Errors = $Tokens = $null @@ -37,9 +31,7 @@ Describe 'Koan Topics Static Analysis Checks' { $Errors.Count | Should -Be 0 } - It 'Koan Topic should not have nested It blocks' -TestCases $KoanTopics { - param($File) - + It 'does not have nested It blocks in ' -TestCases $KoanTopics { function Test-ItBlock { [CmdletBinding()] param([Ast] $element) @@ -86,14 +78,12 @@ Describe 'Koan Topics Static Analysis Checks' { $ParentItBlocks | Should -BeNullOrEmpty -Because 'It blocks cannot be nested' } - It 'Koan Topic should include one (and only one) line feed at end of file' -TestCases $KoanTopics { - param($File) - + It 'has exactly one line feed at end of the file' -TestCases $KoanTopics { $crlf = [Regex]::Match(($File | Get-Content -Raw), '(\r?(?\n))+\Z') $crlf.Groups['lf'].Captures.Count | Should -Be 1 } - It 'Koan Topic should have a Koan position' -TestCases $KoanTopics { + It 'has a position number defined for ' -TestCases $KoanTopics { param($File, $Position) $Position | Should -Not -BeNullOrEmpty @@ -103,9 +93,23 @@ Describe 'Koan Topics Static Analysis Checks' { Context 'Library Cleanliness' { - It 'should not have topics with duplicate Koan positions' { - $DuplicatePosition = $KoanTopics | - ForEach-Object { [PSCustomObject]$_ } | + BeforeAll { + $KoanFolder = Resolve-Path "$PSScriptRoot/../PSKoans/Koans" + } + + It 'does not have topics with duplicate Koan positions' { + $DuplicatePosition = Get-ChildItem -Path $KoanFolder -Recurse -Filter '*.Koans.ps1' | + ForEach-Object { + $commandInfo = Get-Command -name $_.FullName -ErrorAction SilentlyContinue + $koanAttribute = $commandInfo.ScriptBlock.Attributes.Where{ $_.TypeID -match 'Koan' } + + [PSCustomObject]@{ + File = $_ + Name = $_.BaseName -replace '\.Koans$' + Position = $koanAttribute.Position + Module = $koanAttribute.Module + } + } | Group-Object { '{0}/{1}' -f $_.Module, $_.Position } | Where-Object Count -gt 1 | ForEach-Object { '{0}: {1}' -f $_.Name, ($_.Group.File -join ', ') } @@ -113,7 +117,7 @@ Describe 'Koan Topics Static Analysis Checks' { $DuplicatePosition | Should -BeNullOrEmpty } - It 'should not have non-Koan Topic files in the Koans directory' { + It 'does not have non-Koan Topic files in the Koans directory' { Get-ChildItem -Path $KoanFolder -Recurse -Filter '*.ps1' | Where-Object BaseName -notmatch '\.Koans$' | Should -BeNullOrEmpty diff --git a/Tests/ModuleValidation.Tests.ps1 b/Tests/ModuleValidation.Tests.ps1 index 3ee4a0d22..b13f7c992 100644 --- a/Tests/ModuleValidation.Tests.ps1 +++ b/Tests/ModuleValidation.Tests.ps1 @@ -1,49 +1,52 @@ -$ProjectRoot = Resolve-Path "$PSScriptRoot/.." -$ModuleRoot = Split-Path (Resolve-Path "$ProjectRoot/*/*.psm1") -$ModuleName = Split-Path $ModuleRoot -Leaf - -Describe "General project validation: $ModuleName" { - - BeforeAll { - $FileSearch = @{ - Path = $ProjectRoot - Include = '*.ps1', '*.psm1', '*.psd1' - Recurse = $true - Exclude = '*.Koans.ps1' - } - $Scripts = Get-ChildItem @FileSearch - - # TestCases are splatted to the script so we need hashtables - $TestCases = $Scripts | ForEach-Object { @{File = $_ } } +Describe 'Static Analysis: Module & Repository Files' { + + #region Discovery + $FileSearch = @{ + Path = Resolve-Path "$PSScriptRoot/.." + Include = '*.ps1', '*.psm1', '*.psd1' + Recurse = $true + Exclude = '*.Koans.ps1' } + $Scripts = Get-ChildItem @FileSearch - It ' should be valid powershell' -TestCases $TestCases { - param($File) + $TestCases = $Scripts | ForEach-Object { @{ File = $_ } } + #endregion Discovery - $File.FullName | Should -Exist + Context 'Repository Code' { - $FileContents = Get-Content -Path $File.FullName -ErrorAction Stop - $Errors = $null - [System.Management.Automation.PSParser]::Tokenize($FileContents, [ref]$Errors) > $null - $Errors.Count | Should -Be 0 - } + It 'has no invalid syntax errors in ' -TestCases $TestCases { + $File.FullName | Should -Exist - It ' should include one (and only one) line feed at end of file' -TestCases $TestCases { - param($File) - $crlf = [Regex]::Match(($File | Get-Content -Raw), '(\r?(?\n))+\Z') - $crlf.Groups['lf'].Captures.Count | Should -Be 1 - } + $FileContents = Get-Content -Path $File.FullName -ErrorAction Stop + $Errors = $null + [System.Management.Automation.PSParser]::Tokenize($FileContents, [ref]$Errors) > $null + $Errors.Count | Should -Be 0 + } - It 'can cleanly import the module' { - { Import-Module (Join-Path $ModuleRoot "$ModuleName.psm1") -Force } | Should -Not -Throw + It 'has exactly one line feed at EOF in ' -TestCases $TestCases { + $crlf = [Regex]::Match(($File | Get-Content -Raw), '(\r?(?\n))+\Z') + $crlf.Groups['lf'].Captures.Count | Should -Be 1 + } } - It 'can remove and re-import the module without errors' { - $Script = { - Remove-Module $ModuleName - Import-Module (Join-Path -Path $ModuleRoot -ChildPath "$ModuleName.psm1") + Context 'Module Import' { + + BeforeAll { + $ModuleName = 'PSKoans' + $ModuleRoot = (Get-Module -Name $ModuleName).ModuleBase + } + + It 'cleanly imports the module' { + { Import-Module (Join-Path $ModuleRoot "$ModuleName.psm1") -Force } | Should -Not -Throw } - $Script | Should -Not -Throw + It 'removes and re-imports the module without errors' { + $Script = { + Remove-Module $ModuleName + Import-Module (Join-Path -Path $ModuleRoot -ChildPath "$ModuleName.psm1") + } + + $Script | Should -Not -Throw + } } } From 67186417defefcc95378d5329ee5069e8ae1fbd0 Mon Sep 17 00:00:00 2001 From: Joel Sallow <32407840+vexx32@users.noreply.github.com> Date: Thu, 18 Jun 2020 17:12:18 -0400 Subject: [PATCH 08/17] :recycle: Update Introduction koans --- .../AboutSplatting.Koans.ps1 | 5 +- .../AboutStringBuilder.Koans.ps1 | 12 +- .../AboutAssignmentAndArithmetic.Koans.ps1 | 8 +- .../Koans/Introduction/AboutBinary.Koans.ps1 | 233 +++++++++++------- .../Introduction/AboutBooleans.Koans.ps1 | 28 ++- .../Introduction/AboutGetMember.Koans.ps1 | 188 ++++++++------ .../Koans/Introduction/AboutNumbers.Koans.ps1 | 20 +- 7 files changed, 301 insertions(+), 193 deletions(-) diff --git a/PSKoans/Koans/Constructs and Patterns/AboutSplatting.Koans.ps1 b/PSKoans/Koans/Constructs and Patterns/AboutSplatting.Koans.ps1 index 2c13bdcac..cf4a500b8 100644 --- a/PSKoans/Koans/Constructs and Patterns/AboutSplatting.Koans.ps1 +++ b/PSKoans/Koans/Constructs and Patterns/AboutSplatting.Koans.ps1 @@ -15,6 +15,7 @@ param() 'splatted' into the command by specifying the variable name with an @ symbol: @Variable #> Describe 'Splatting' { + BeforeAll { $PSKoansFolder = Get-PSKoanLocation } @@ -74,8 +75,8 @@ Describe 'Splatting' { } Get-ChildItem @Parameters | - Select-Object -First 1 | - Should -BeOfType 'System.IO.FileInfo' + Select-Object -First 1 | + Should -BeOfType 'System.IO.FileInfo' } It 'can be built from automatic hashtables' { diff --git a/PSKoans/Koans/Constructs and Patterns/AboutStringBuilder.Koans.ps1 b/PSKoans/Koans/Constructs and Patterns/AboutStringBuilder.Koans.ps1 index 0592ee43a..48eb522af 100644 --- a/PSKoans/Koans/Constructs and Patterns/AboutStringBuilder.Koans.ps1 +++ b/PSKoans/Koans/Constructs and Patterns/AboutStringBuilder.Koans.ps1 @@ -96,6 +96,7 @@ Describe 'System.Text.StringBuilder' { } Context 'Constructing the Final String Object' { + BeforeAll { $SB = [System.Text.StringBuilder]::new("Hello!") $SB.Append("BYE!") @@ -115,9 +116,11 @@ Describe 'System.Text.StringBuilder' { } Context 'Other StringBuilder Methods' { + BeforeAll { $StringBuilder = [System.Text.StringBuilder]::new("TEXT") } + It 'can be cleared' { $StringBuilder.Clear() $StringBuilder.Length | Should -Be 0 @@ -143,6 +146,7 @@ Describe 'System.Text.StringBuilder' { } Context 'StringBuilder Properties' { + BeforeAll { $StringBuilder = [System.Text.StringBuilder]::new() $StringBuilder.AppendLine('When you look into the void,') @@ -154,12 +158,12 @@ Describe 'System.Text.StringBuilder' { $PropertyName = '____' $Properties = $StringBuilder | - Get-Member | - Where-Object MemberType -eq 'Property' + Get-Member | + Where-Object MemberType -EQ 'Property' $ExpectedPropertyCount = $Properties | - Measure-Object | - Select-Object -ExpandProperty Count + Measure-Object | + Select-Object -ExpandProperty Count $PropertyCount | Should -Be $ExpectedPropertyCount $PropertyName | Should -BeIn $Properties.Name diff --git a/PSKoans/Koans/Foundations/AboutAssignmentAndArithmetic.Koans.ps1 b/PSKoans/Koans/Foundations/AboutAssignmentAndArithmetic.Koans.ps1 index 09cf6f805..6484d492f 100644 --- a/PSKoans/Koans/Foundations/AboutAssignmentAndArithmetic.Koans.ps1 +++ b/PSKoans/Koans/Foundations/AboutAssignmentAndArithmetic.Koans.ps1 @@ -98,6 +98,7 @@ Describe 'Arithmetic Operators' { __ | Should -Be $Array } } + Context 'Subtraction' { It 'works similarly to addition' { @@ -148,9 +149,10 @@ Describe 'Arithmetic Operators' { } Context 'Modulus' { - - # Modulus is a bit of an odd one, but common enough in programming. It performs a - # division, and then returns the integer value of the remainder. + <# + Modulus is a bit of an odd one, but common enough in programming. It performs a + division, and then returns the integer value of the remainder. + #> It 'is usually used with integers' { $Remainder = 15 % 7 __ | Should -Be $Remainder diff --git a/PSKoans/Koans/Introduction/AboutBinary.Koans.ps1 b/PSKoans/Koans/Introduction/AboutBinary.Koans.ps1 index 3cab25d35..d6232eb87 100644 --- a/PSKoans/Koans/Introduction/AboutBinary.Koans.ps1 +++ b/PSKoans/Koans/Introduction/AboutBinary.Koans.ps1 @@ -43,108 +43,155 @@ param() one, and 1 + 2 is equal to 3. #> -Describe "Binary conversion" { +Describe 'Binary Conversions' { - It "Bit conversion" { - # What would 0 be if converted to be boolean? - # Replace __ with either $true or $false - $____ -as [int] | Should -Be 0 + Context 'Boolean Conversions' { - # What would 1 be if converted to be boolean? - # Replace __ with either $true or $false - $____ -as [int] | Should -Be 1 - } + It 'converts $false to an integer' { + $ExpectedResult = $false -as [int] - It "Binary to integer conversion" { - # Replace __ with the decimal value of 1111 - # E.G. __ becomes 1234 - $Binary = "1111" - $Value = __ - $Value | Should -Be ([Convert]::ToInt32($Binary, 2)) - - # Replace __ with the decimal value of 1000 - $Binary = "1000" - $Value = __ - $Value | Should -Be ([Convert]::ToInt32($Binary, 2)) - - # Replace __ with the decimal value of 0010 - $Binary = "0010" - $Value = __ - $Value | Should -Be ([Convert]::ToInt32($Binary, 2)) - - # Replace __ with the decimal value of 1001 - $Binary = "1001" - $Value = __ - $Value | Should -Be ([Convert]::ToInt32($Binary, 2)) - - # Replace __ with the decimal value of 11111111 - $Binary = "11111111" - $Value = __ - $Value | Should -Be ([Convert]::ToInt32($Binary, 2)) - - # Replace __ with the decimal value of 10101010 - $Binary = "10101010" - $Value = __ - $Value | Should -Be ([Convert]::ToInt32($Binary, 2)) - - # Replace __ with the decimal value of 11001100 - $Binary = "11001100" - $Value = __ - $Value | Should -Be ([Convert]::ToInt32($Binary, 2)) - - # Replace __ with the decimal value of 11110001 - $Binary = "11110001" - $Value = __ - $Value | Should -Be ([Convert]::ToInt32($Binary, 2)) + # What would $false be if converted to a number? + __ | Should -Be $ExpectedResult + } + It 'converts $true to an integer' { + $ExpectedResult = $true -as [int] + # What would $true be if converted to a number? + __ | Should -Be $ExpectedResult + } } - It "Integer to Binary conversion" { + Context "Binary to Integer Conversion" { <# - Convert the following integers into their binary representation + Replace the blanks below with the decimal value of the binary + numbers in each case. For example, the binary sequence "10" is + represented by the number 2 in the standard decimal system. #> + It 'converts 1111 to an integer' { + $ExpectedValue = [Convert]::ToInt32($Binary, 2) + + # Replace the __ with the decimal value of 1111 + $Binary = "1111" + __ | Should -Be $ExpectedValue + } + + It 'converts 1000 to an integer' { + $ExpectedValue = [Convert]::ToInt32($Binary, 2) + + # Replace __ with the decimal value of 1000 + $Binary = "1000" + __ | Should -Be $ExpectedValue + } + + It 'converts 0010 to an integer' { + $ExpectedValue = [Convert]::ToInt32($Binary, 2) + + # Replace __ with the decimal value of 0010 + $Binary = "0010" + __ | Should -Be $ExpectedValue + } + + It 'converts 1001 to an integer' { + $ExpectedValue = [Convert]::ToInt32($Binary, 2) + + # Replace __ with the decimal value of 1001 + $Binary = "1001" + __ | Should -Be $ExpectedValue + } + + It 'converts 11111111 to an integer' { + $ExpectedValue = [Convert]::ToInt32($Binary, 2) + + # Replace __ with the decimal value of 11111111 + $Binary = "11111111" + __ | Should -Be $ExpectedValue + } - # Replace __ with the binary value of 7 - # E.G. "__" becomes "0100" - $Binary = "____" - $Value = 7 - $Value | Should -Be ([Convert]::ToInt32($Binary, 2)) - - # Replace __ with the binary value of 12 - $Binary = "____" - $Value = 12 - $Value | Should -Be ([Convert]::ToInt32($Binary, 2)) - - # Replace __ with the binary value of 2 - $Binary = "____" - $Value = 2 - $Value | Should -Be ([Convert]::ToInt32($Binary, 2)) - - # Replace __ with the binary value of 14 - $Binary = "____" - $Value = 14 - $Value | Should -Be ([Convert]::ToInt32($Binary, 2)) - - # Replace __ with the binary value of 103 - # E.G. "__" becomes "01001110" - $Binary = "____" - $Value = 103 - $Value | Should -Be ([Convert]::ToInt32($Binary, 2)) - - # Replace __ with the binary value of 250 - $Binary = "____" - $Value = 250 - $Value | Should -Be ([Convert]::ToInt32($Binary, 2)) - - # Replace __ with the binary value of 74 - $Binary = "____" - $Value = 74 - $Value | Should -Be ([Convert]::ToInt32($Binary, 2)) - - # Replace __ with the binary value of 32 - $Binary = "____" - $Value = 32 - $Value | Should -Be ([Convert]::ToInt32($Binary, 2)) + It 'converts 10101010 to an integer' { + $ExpectedValue = [Convert]::ToInt32($Binary, 2) + + # Replace __ with the decimal value of 10101010 + $Binary = "10101010" + __ | Should -Be $ExpectedValue + } + + It 'converts 11001100 to an integer' { + $ExpectedValue = [Convert]::ToInt32($Binary, 2) + + # Replace __ with the decimal value of 11001100 + $Binary = "11001100" + __ | Should -Be $ExpectedValue + } + + It 'converts 11110001 to an integer' { + $ExpectedValue = [Convert]::ToInt32($Binary, 2) + + # Replace __ with the decimal value of 11110001 + $Binary = "111g10001" + __ | Should -Be $ExpectedValue + } + } + + Context "Integer to Binary Conversion" { + <# + Convert the following integers into their binary representation. + For example, 2 is represented in binary with the digits "10". + #> + It 'converts the integer 7 to binary' { + # Replace ____ with the binary value of 7 + $Value = 7 + $Binary = [Convert]::ToString($Value, 2) + '____' | Should -Be $Binary + } + + It 'converts the integer 12 to binary' { + # Replace __ with the binary value of 12 + $Value = 12 + $Binary = [Convert]::ToString($Value, 2) + '____' | Should -Be ([Convert]::ToInt32($Binary, 2)) + } + + It 'converts the integer 2 to binary' { + # Replace __ with the binary value of 2 + $Value = 2 + $Binary = [Convert]::ToString($Value, 2) + '____' | Should -Be ([Convert]::ToInt32($Binary, 2)) + } + + It 'converts the integer 14 to binary' { + # Replace __ with the binary value of 14 + $Value = 14 + $Binary = [Convert]::ToString($Value, 2) + '____' | Should -Be ([Convert]::ToInt32($Binary, 2)) + } + + It 'converts the integer 103 to binary' { + # Replace __ with the binary value of 103 + $Value = 103 + $Binary = [Convert]::ToString($Value, 2) + '____' | Should -Be ([Convert]::ToInt32($Binary, 2)) + } + + It 'converts the integer 250 to binary' { + # Replace __ with the binary value of 250 + $Value = 250 + $Binary = [Convert]::ToString($Value, 2) + '____' | Should -Be ([Convert]::ToInt32($Binary, 2)) + } + + It 'converts the integer 74 to binary' { + # Replace __ with the binary value of 74 + $Value = 74 + $Binary = [Convert]::ToString($Value, 2) + '____' | Should -Be ([Convert]::ToInt32($Binary, 2)) + } + + It 'converts the integer 32 to binary' { + # Replace __ with the binary value of 32 + $Value = 32 + $Binary = [Convert]::ToString($Value, 2) + '____' | Should -Be ([Convert]::ToInt32($Binary, 2)) + } } } diff --git a/PSKoans/Koans/Introduction/AboutBooleans.Koans.ps1 b/PSKoans/Koans/Introduction/AboutBooleans.Koans.ps1 index 5b0418a87..a0f0f8f16 100644 --- a/PSKoans/Koans/Introduction/AboutBooleans.Koans.ps1 +++ b/PSKoans/Koans/Introduction/AboutBooleans.Koans.ps1 @@ -8,7 +8,7 @@ param() allow us to represent a logical, binary, state. Effectively, they represent the state of a single bit of data. For example: - | "Can I stop this service?" + "Can I stop this service?" The answer is yes or no, True or False. @@ -27,30 +27,42 @@ param() #> Describe "Booleans" { + <# + Fill in the blanks below with either $true or $false, indicating the + result you expect from the noted expressions. - # Using only booleans, either $true or $false, fill in the blanks below. + We'll cover comparison operators in more detail later on, but here's a + summary of the operators used here: - It '( 1 -gt 2 ) is either true or false' { + -gt => "is greater than" + -eq => "is equal to" + -lt => "is less than" + + As an example, the expression 7 -gt 5 asserts "7 is greater than 5". + This is a true statement, so the final value of the expression is $true. + #> + + It 'evaluates ( 1 -gt 2 ) as a boolean expression' { $____ | Should -Be ( 1 -gt 2 ) -Because '1 is not greater than 2' } - It '( 1 -lt 2 ) is either true or false' { + It 'evaluates ( 1 -lt 2 ) as a boolean expression' { $____ | Should -Be ( 1 -lt 2 ) -Because '1 is less than 2' } - It '( 10 -lt 20 ) is either true or false' { + It 'evaluates ( 10 -lt 20 ) as a boolean expression' { $____ | Should -Be ( 10 -lt 20 ) -Because '10 is less than 20' } - It '( 10 -gt 20 ) is either true or false' { + It 'evaluates ( 10 -gt 20 ) as a boolean expression' { $____ | Should -Be ( 10 -gt 20 ) -Because 'The lesser is not greater' } - It '( 3 -eq 3 ) is either true or false' { + It 'evaluates ( 3 -eq 3 ) as a boolean expression' { $____ | Should -Be ( 3 -eq 3 ) -Because 'A mirror reflects true' } - It '( 100 -lt 1 ) is either true or false' { + It 'evaluates ( 100 -lt 1 ) as a boolean expression' { $____ | Should -Be ( 100 -lt 1 ) -Because '100 is not less than 1' } } diff --git a/PSKoans/Koans/Introduction/AboutGetMember.Koans.ps1 b/PSKoans/Koans/Introduction/AboutGetMember.Koans.ps1 index 15952e115..557e0c3af 100644 --- a/PSKoans/Koans/Introduction/AboutGetMember.Koans.ps1 +++ b/PSKoans/Koans/Introduction/AboutGetMember.Koans.ps1 @@ -38,95 +38,129 @@ param() #> Describe "Get Member" { -<# - Use Get-Member to identify properties or methods of some common objects! - - Make sure you use a different cmdlet each time, we'll be checking :) -#> - - It 'allows us to explore properties on an object' { - - # Get some properties! - <# EXAMPLE + Context 'Exploring Object Properties' { + <# + Let's look at some object properties! - $Cmdlet1 = 'Get-Process' - $PropertyName = 'Threads' - $Reason = $BecauseString -f $PropertyName, $cmdlet1 - & (Get-Command -Name $Cmdlet1) | - Get-Member -MemberType Property -Name $PropertyName | - Should -Not -BeNullOrEmpty -Because $Reason + Using any three cmdlets you like (make sure you use three different + cmdlets!), use Get-Member in your console to peek at the properties + on the object. - #> - $BecauseString = "property '{0}' should be present in output from {1}" - - $Cmdlet1 = '____' - $PropertyName = '____' - - $Reason = $BecauseString -f $PropertyName, $cmdlet1 - & (Get-Command -Name $Cmdlet1) | - Get-Member -MemberType Property -Name $PropertyName | - Should -Not -BeNullOrEmpty -Because $Reason + Let's see an example; by sending the output from Get-Process into + Get-Member we can inspect the objects Get-Process outputs. You can + run the below command in your console: - $Cmdlet2 = '____' - $PropertyName = '____' + Get-Process | Get-Member -MemberType Property - $Reason = $BecauseString -f $PropertyName, $cmdlet2 - & (Get-Command -Name $Cmdlet2) | - Get-Member -MemberType Property -Name $PropertyName | - Should -Not -BeNullOrEmpty -Because $Reason + From the output of the above command, we will see that one of the + properties is named "Threads", so using that cmdlet name and that + property name will satisfy one of the tests below. - $Cmdlet3 = '____' - $PropertyName = '____' + The others are up to you! Get-* cmdlets will be most helpful here; + if you're not sure which to try, you can use the following command + to list Get-* cmdlets you can try out: - $Reason = $BecauseString -f $PropertyName, $cmdlet3 - & (Get-Command -Name $Cmdlet3) | - Get-Member -MemberType Property -Name $PropertyName | - Should -Not -BeNullOrEmpty -Because $Reason - - $cmdlet1, $cmdlet2, $cmdlet3 | - Get-Unique | - Should -HaveCount 3 -Because "three unique cmdlets should be supplied" + Get-Command -Verb Get + #> + BeforeAll { + $Cmdlets = [System.Collections.Generic.HashSet[string]]::new() + $PropertyString = "property '{0}' should be present in output from {1}" + $UniqueString = 'unique cmdlets should be used for each test' + } + + It 'lists one of the properties of the first unique command' { + $CmdletName = '____' + $PropertyName = '____' + + $Reason = $PropertyString -f $PropertyName, $CmdletName + & (Get-Command -Name $CmdletName) | + Get-Member -MemberType Property -Name $PropertyName | + Should -Not -BeNullOrEmpty -Because $Reason + + $Cmdlets.Add($CmdletName) | Should -BeTrue -Because $UniqueString + } + + It 'lists one of the properties of the second unique command' { + $CmdletName = '____' + $PropertyName = '____' + + $Reason = $PropertyString -f $PropertyName, $CmdletName + & (Get-Command -Name $CmdletName) | + Get-Member -MemberType Property -Name $PropertyName | + Should -Not -BeNullOrEmpty -Because $Reason + + $Cmdlets.Add($CmdletName) | Should -BeTrue -Because $UniqueString + } + + It 'lists one of the properties of the third unique command' { + $CmdletName = '____' + $PropertyName = '____' + + $Reason = $PropertyString -f $PropertyName, $CmdletName + & (Get-Command -Name $CmdletName) | + Get-Member -MemberType Property -Name $PropertyName | + Should -Not -BeNullOrEmpty -Because $Reason + + $Cmdlets.Add($CmdletName) | Should -BeTrue -Because $UniqueString + } } - It 'allows us to explore methods on an object' { + Context 'Exploring Object Methods' { + <# + Similar to above, you can inspect the methods available from an + object that a cmdlet outputs, by changing the -MemberType value + you provide to Get-Member: - # Get some methods! + Get-Process | Get-Member -MemberType Method - <# EXAMPLE + If you don't provide a -MemberType option and value, it will simply + list all the members, regardless of the kind of members they are. - $Cmdlet1 = 'Get-Process' - $MethodName = 'Close' - $Reason = $BecauseString -f $MethodName, $cmdlet1 - & (Get-Command -Name $Cmdlet1) | - Get-Member -MemberType Property -Name $MethodName | - Should -Not -BeNullOrEmpty -Because $Reason + You can reuse the same set of cmdlets from above here if you wish, + but you will need to check to see if there are methods available on + the objects they output! #> - $BecauseString = "method '{0}' should be present in output from {1}" - - $Cmdlet1 = '____' - $MethodName = '____' - $Reason = $BecauseString -f $MethodName, $cmdlet1 - & (Get-Command -Name $Cmdlet1) | - Get-Member -MemberType Method -Name $MethodName | - Should -Not -BeNullOrEmpty -Because $Reason - - $Cmdlet2 = '____' - $MethodName = '____' - $Reason = $BecauseString -f $MethodName, $cmdlet2 - & (Get-Command -Name $Cmdlet2) | - Get-Member -MemberType Method -Name $MethodName | - Should -Not -BeNullOrEmpty -Because $Reason - - $Cmdlet3 = '____' - $MethodName = '____' - $Reason = $BecauseString -f $MethodName, $cmdlet3 - & (Get-Command -Name $Cmdlet3) | - Get-Member -MemberType Method -Name $MethodName | - Should -Not -BeNullOrEmpty -Because $Reason - - $cmdlet1, $cmdlet2, $cmdlet3 | - Get-Unique | - Should -HaveCount 3 -Because "three unique cmdlets should be supplied" + BeforeAll { + $Cmdlets = [System.Collections.Generic.HashSet[string]]::new() + $MethodString = "property '{0}' should be present in output from {1}" + $UniqueString = 'unique cmdlets should be used for each test' + } + + It 'lists one of the methods of the first unique command' { + $CmdletName = '____' + $MethodName = '____' + + $Reason = $MethodString -f $MethodName, $CmdletName + & (Get-Command -Name $CmdletName) | + Get-Member -MemberType Property -Name $MethodName | + Should -Not -BeNullOrEmpty -Because $Reason + + $Cmdlets.Add($CmdletName) | Should -BeTrue -Because $UniqueString + } + + It 'lists one of the methods of the second unique command' { + $CmdletName = '____' + $MethodName = '____' + + $Reason = $MethodString -f $MethodName, $CmdletName + & (Get-Command -Name $CmdletName) | + Get-Member -MemberType Property -Name $MethodName | + Should -Not -BeNullOrEmpty -Because $Reason + + $Cmdlets.Add($CmdletName) | Should -BeTrue -Because $UniqueString + } + + It 'lists one of the methods of the third unique command' { + $CmdletName = '____' + $MethodName = '____' + + $Reason = $MethodString -f $MethodName, $CmdletName + & (Get-Command -Name $CmdletName) | + Get-Member -MemberType Property -Name $MethodName | + Should -Not -BeNullOrEmpty -Because $Reason + + $Cmdlets.Add($CmdletName) | Should -BeTrue -Because $UniqueString + } } } diff --git a/PSKoans/Koans/Introduction/AboutNumbers.Koans.ps1 b/PSKoans/Koans/Introduction/AboutNumbers.Koans.ps1 index a4062e5ea..8fa10cb70 100644 --- a/PSKoans/Koans/Introduction/AboutNumbers.Koans.ps1 +++ b/PSKoans/Koans/Introduction/AboutNumbers.Koans.ps1 @@ -26,17 +26,25 @@ Describe 'Basic Number Types' { Context 'Double' { - It 'can result from an operation involving multiple types of numbers' { - <# - When doing arithmetic on different types of numbers, PowerShell - will automatically convert the less precise or more narrow type - to the other kind. - #> + BeforeAll { $Int = 10 $Double = 10.0 + } + It 'has a specific object type for integers' { 'System.____' | Should -Be $Int.GetType().Fullname + } + + It 'has a specific object type for doubles' { 'System.____' | Should -Be $Double.GetType().Fullname + } + + It 'can result from mathematical operations with other number types' { + <# + When doing arithmetic on different types of numbers, PowerShell + will automatically convert the less precise or more narrow type + to the other kind. + #> $Result = $Int * $Double From b2c05ec2007aa1a44f03fce937d79c6168f0c722 Mon Sep 17 00:00:00 2001 From: Joel Sallow <32407840+vexx32@users.noreply.github.com> Date: Fri, 19 Jun 2020 19:16:50 -0400 Subject: [PATCH 09/17] :recycle: Update Cmdlets koans --- .../Koans/Cmdlets 2/AboutCsvCmdlets.Koans.ps1 | 17 ++++++++++++++++- .../Koans/Cmdlets 2/AboutOutCmdlets.Koans.ps1 | 1 - 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/PSKoans/Koans/Cmdlets 2/AboutCsvCmdlets.Koans.ps1 b/PSKoans/Koans/Cmdlets 2/AboutCsvCmdlets.Koans.ps1 index a5ad4f0ba..1a04cbe6f 100644 --- a/PSKoans/Koans/Cmdlets 2/AboutCsvCmdlets.Koans.ps1 +++ b/PSKoans/Koans/Cmdlets 2/AboutCsvCmdlets.Koans.ps1 @@ -19,7 +19,7 @@ param() handled directly by PowerShell's CSV cmdlets without some extra work. #> -Describe '*-Csv Cmdlets' { +Describe 'CSV Cmdlets' { Context 'Export-Csv' { <# @@ -81,7 +81,21 @@ Describe '*-Csv Cmdlets' { } It 'allows you to specify the delimiter' { + $Delimiter = '_' + $Path = 'TestDrive:/DelimitedData.csv' + + $Text = @( + '"Number","Square"' + '"__"?"__"' + '"__"?"__"' + '"__"?"__"' + '"__"?"__"' + '"__"?"__"' + ) + $Objects | Export-Csv -Path $Path -Delimiter $Delimiter + $FileContents = Get-Content -Path $Path + $Text | Should -BeExactly $FileContents } } @@ -207,6 +221,7 @@ Describe '*-Csv Cmdlets' { } Context 'ConvertFrom-Csv' { + BeforeAll { $CsvString = @" "Number","Square" diff --git a/PSKoans/Koans/Cmdlets 2/AboutOutCmdlets.Koans.ps1 b/PSKoans/Koans/Cmdlets 2/AboutOutCmdlets.Koans.ps1 index 6caec8752..e47471d89 100644 --- a/PSKoans/Koans/Cmdlets 2/AboutOutCmdlets.Koans.ps1 +++ b/PSKoans/Koans/Cmdlets 2/AboutOutCmdlets.Koans.ps1 @@ -77,7 +77,6 @@ Context 'Out-* Cmdlets' { partially because in older versions of PowerShell it has a different default encoding than those cmdlets, frequently confusing users. #> - It 'stores data in files' { $Path = 'TestDrive:\File.txt' From 87da15ef73bb6b9c98e4d010a8dc786ab9d3dd8b Mon Sep 17 00:00:00 2001 From: Joel Sallow <32407840+vexx32@users.noreply.github.com> Date: Sat, 20 Jun 2020 12:15:09 -0400 Subject: [PATCH 10/17] :recycle: minor adjustements to katas --- PSKoans/Koans/Katas/ProcessingStrings.Koans.ps1 | 1 + 1 file changed, 1 insertion(+) diff --git a/PSKoans/Koans/Katas/ProcessingStrings.Koans.ps1 b/PSKoans/Koans/Katas/ProcessingStrings.Koans.ps1 index 6a225d4cd..732c2b469 100644 --- a/PSKoans/Koans/Katas/ProcessingStrings.Koans.ps1 +++ b/PSKoans/Koans/Katas/ProcessingStrings.Koans.ps1 @@ -26,6 +26,7 @@ param() -Joel #> Describe "The Stock Challenge" { + BeforeAll { $StockData = @( "Date,Open,High,Low,Close,Volume,Adj Close" From d294eb38c648adbaf73da9b516b3f9cf7798886f Mon Sep 17 00:00:00 2001 From: Joel Sallow <32407840+vexx32@users.noreply.github.com> Date: Sat, 20 Jun 2020 17:57:14 -0400 Subject: [PATCH 11/17] :rocket: unbreak code coverage --- Build/Invoke-ModuleTests.ps1 | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/Build/Invoke-ModuleTests.ps1 b/Build/Invoke-ModuleTests.ps1 index cdb160d70..f506aff90 100644 --- a/Build/Invoke-ModuleTests.ps1 +++ b/Build/Invoke-ModuleTests.ps1 @@ -19,9 +19,16 @@ finally { $TestFile = "PS${PSVersion}_${TimeStamp}_PSKoans.TestResults.xml" $CodeCoverageFile = "PS${PSVersion}_${TimeStamp}_PSKoans.CodeCoverage.xml" + $ModuleFolders = @( + Get-Item -Path "$env:PROJECTROOT/PSKoans" + Get-ChildItem -Path "$env:PROJECTROOT/PSKoans" -Directory -Recurse | + Where-Object FullName -NotMatch '[\\/]Tests[\\/]|[\\/]PSKoans[\\/]Koans[\\/]' + ).FullName -join ';' + # Tell Azure what the test results & code coverage file names will be Write-Host "##vso[task.setvariable variable=TestResults]$TestFile" Write-Host "##vso[task.setvariable variable=CodeCoverageFile]$CodeCoverageFile" + Write-Host "##vso[task.setvariable variable=SourceFolders]$ModuleFolders" # Move files generated from Invoke-Pester to expected location Move-Item -Path './testResults.xml' -Destination "$env:BUILD_ARTIFACTSTAGINGDIRECTORY/$TestFile" From 8e63322c8c9121df432025f40e3dc2f86bd1fb88 Mon Sep 17 00:00:00 2001 From: Joel Sallow <32407840+vexx32@users.noreply.github.com> Date: Thu, 25 Jun 2020 08:48:50 -0400 Subject: [PATCH 12/17] :rocket: Unbreak code coverage attempt 2 --- Build/Invoke-ModuleTests.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Build/Invoke-ModuleTests.ps1 b/Build/Invoke-ModuleTests.ps1 index f506aff90..59924d910 100644 --- a/Build/Invoke-ModuleTests.ps1 +++ b/Build/Invoke-ModuleTests.ps1 @@ -12,7 +12,7 @@ Write-Host $Lines try { # Try/Finally required since -CI will exit with exit code on failure. - Invoke-Pester -Path "$env:PROJECTROOT/Tests" -CI -Output Normal + Invoke-Pester -Path "$env:PROJECTROOT" -CI -Output Normal } finally { $Timestamp = Get-Date -Format "yyyyMMdd-hhmmss" From a2f3bd000f1f6c3653a31b0d7ee7164ff67cfb10 Mon Sep 17 00:00:00 2001 From: Joel Sallow <32407840+vexx32@users.noreply.github.com> Date: Sun, 5 Jul 2020 00:18:38 -0400 Subject: [PATCH 13/17] :sparkles: Use persistent runspace for koan runs :recycle: Avoid corrupting process PSModulePath - env: provider can affect the whole process, so keep changes temporary - move PSModulePath changes to Invoke-Koan instead of New-KoanRunspace --- PSKoans/PSKoans.psm1 | 1 + PSKoans/Private/Invoke-Koan.ps1 | 62 ++++++++++++++++------------ PSKoans/Private/New-KoanRunspace.ps1 | 43 +++++++++++++++++++ 3 files changed, 80 insertions(+), 26 deletions(-) create mode 100644 PSKoans/Private/New-KoanRunspace.ps1 diff --git a/PSKoans/PSKoans.psm1 b/PSKoans/PSKoans.psm1 index b22ae8adb..a6358912b 100644 --- a/PSKoans/PSKoans.psm1 +++ b/PSKoans/PSKoans.psm1 @@ -21,6 +21,7 @@ $script:DefaultSettings = @{ } [hashtable] $script:CurrentTopic = $null +[runspace] $script:KoanRunspace = $null #region SupportingClasses diff --git a/PSKoans/Private/Invoke-Koan.ps1 b/PSKoans/Private/Invoke-Koan.ps1 index 61c1a3f4a..f385ebbb9 100644 --- a/PSKoans/Private/Invoke-Koan.ps1 +++ b/PSKoans/Private/Invoke-Koan.ps1 @@ -25,6 +25,11 @@ [hashtable] $ParameterSplat ) + begin { + if (-not $script:KoanRunspace) { + $script:KoanRunspace = New-KoanRunspace + } + } end { try { $Requirements = [System.Management.Automation.Language.Parser]::ParseFile( @@ -34,42 +39,47 @@ ).Ast.ScriptRequirements $Script = { - 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 + param( $Params, $RequiredModules, $PSModulePath ) + + $oldModulePath = $env:PSModulePath + try { + $env:PSModulePath = @( + $PSKoansPath | Split-Path -Parent + $PSModulePath -split [System.IO.Path]::PathSeparator + $env:PSModulePath -split [System.IO.Path]::PathSeparator + ) -join [System.IO.Path]::PathSeparator + + foreach ($module in $RequiredModules) { + Import-Module $module + } + + . ([scriptblock]::Create('using module PSKoans')) + Invoke-Pester @Params + } + finally { + $env:PSModulePath = $oldModulePath } - - Invoke-Pester @Params } - $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 + $ps = [powershell]::Create($script:KoanRunspace) + $ps.AddScript($Script, <# useLocalScope: #> $true) > $null + + $ps.AddParameter('Params', $ParameterSplat) > $null + $ps.AddParameter('PSModulePath', $env:PSModulePath) > $null if ($Requirements.RequiredModules) { - $Runspace.AddParameter('RequiredModules', $Requirements.RequiredModules) + $ps.AddParameter('RequiredModules', $Requirements.RequiredModules) } - $Status = $Runspace.BeginInvoke() + $Status = $ps.BeginInvoke() - do { Start-Sleep -Milliseconds 1 } until ($Status.IsCompleted) + do { Start-Sleep -Milliseconds 100 } until ($Status.IsCompleted) - $Result = $Runspace.EndInvoke($Status) + $Result = $ps.EndInvoke($Status) - if ($Runspace.HadErrors) { + if ($ps.HadErrors) { # These will be errors outside the test itself; better propagate them upwards. - foreach ($errorItem in $Runspace.Streams.Error) { + foreach ($errorItem in $ps.Streams.Error) { $PSCmdlet.WriteError($errorItem) } } @@ -77,7 +87,7 @@ $Result } finally { - $Runspace.Dispose() + $ps.Dispose() } } } diff --git a/PSKoans/Private/New-KoanRunspace.ps1 b/PSKoans/Private/New-KoanRunspace.ps1 new file mode 100644 index 000000000..b39beda97 --- /dev/null +++ b/PSKoans/Private/New-KoanRunspace.ps1 @@ -0,0 +1,43 @@ +function New-KoanRunspace { + <# + .SYNOPSIS + Creates a new runspace, with PSKoans and Pester already imported, and PSModulePath already set. + + .DESCRIPTION + Creates a new runspace for reuse with Invoke-Koan. The new runspace will be pre-populated with a fresh copy of + Pester and PSKoans imported, and with a copy of the current session state's PSModulePath, which will have the + current PSKoans parent folder in it, such that `using module` statements will find the current PSKoans module + first, avoiding issues with debug environments which may find older PSKoans modules installed instead of using the + current debug version. + + .EXAMPLE + $script:KoanRunspace = New-KoanRunspace + + .NOTES + Run scripts in a new scope to avoid scope bleed wherever possible, with the `$ps.AddScript($script, $true) overload. + #> + [CmdletBinding()] + param() + + $runspace = [runspacefactory]::CreateRunspace() + $runspace.Open() + $runspace.Name = 'PSKoans.KoanRunspace' + $ps = [powershell]::Create($runspace) + + try { + $script = { + param( $PSKoansPath ) + + Get-Module $PSKoansPath -ListAvailable | Import-Module + } + + $ps.AddScript($script) > $null + $ps.AddParameter('PSKoansPath', $MyInvocation.MyCommand.Module.ModuleBase) > $null + $ps.Invoke() > $null + + $runspace + } + finally { + $ps.Dispose() + } +} From 11bf79526782eb262340d6358f88c2adc6d247db Mon Sep 17 00:00:00 2001 From: Joel Sallow <32407840+vexx32@users.noreply.github.com> Date: Sun, 5 Jul 2020 13:59:03 -0400 Subject: [PATCH 14/17] :bug: Adjust PSModulePath for Measure-Koan - If PSKoans is not on the module path, Measure-Koan can fail. We can avoid this by temporarily modifying PSModulePath. --- PSKoans/Private/Measure-Koan.ps1 | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/PSKoans/Private/Measure-Koan.ps1 b/PSKoans/Private/Measure-Koan.ps1 index 26d2b4c1e..3fbc7169b 100644 --- a/PSKoans/Private/Measure-Koan.ps1 +++ b/PSKoans/Private/Measure-Koan.ps1 @@ -31,6 +31,12 @@ ) begin { $KoanCount = 0 + $oldModulePath = $env:PSModulePath + + $env:PSModulePath = @( + $MyInvocation.MyCommand.Module.ModuleBase + $env:PSModulePath -split [System.IO.Path]::PathSeparator + ) -join [System.IO.Path]::PathSeparator } process { Write-Verbose "Discovering koans in [$($KoanInfo.Name -join '], [')]" @@ -69,5 +75,7 @@ end { Write-Verbose "Total Koans: $KoanCount" $KoanCount + + $env:PSModulePath = $oldModulePath } } From e2999254bcdd12b9fd88e65522af57683b4402a9 Mon Sep 17 00:00:00 2001 From: Joel Sallow <32407840+vexx32@users.noreply.github.com> Date: Sun, 5 Jul 2020 13:59:16 -0400 Subject: [PATCH 15/17] :white_check_mark: add tests for New-KoanRunspace --- .../Private/New-KoanRunspace.Tests.ps1 | 34 +++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 Tests/Functions/Private/New-KoanRunspace.Tests.ps1 diff --git a/Tests/Functions/Private/New-KoanRunspace.Tests.ps1 b/Tests/Functions/Private/New-KoanRunspace.Tests.ps1 new file mode 100644 index 000000000..6155de163 --- /dev/null +++ b/Tests/Functions/Private/New-KoanRunspace.Tests.ps1 @@ -0,0 +1,34 @@ +Describe 'New-KoanRunspace' { + + BeforeAll { + [runspace]$runspace = $null + } + + AfterEach { + $runspace.Dispose() + } + + It 'creates a new runspace' { + $runspace = InModuleScope 'PSKoans' { New-KoanRunspace } + $runspace | Should -BeOfType [runspace] + } + + It 'names the new runspace' { + $runspace = InModuleScope 'PSKoans' { New-KoanRunspace } + $runspace.Name | Should -Be 'PSKoans.KoanRunspace' + } + + It 'preloads the runspace with PSKoans' { + $runspace = InModuleScope 'PSKoans' { New-KoanRunspace } + $ps = [powershell]::Create($runspace) + + try { + $ps.AddCommand('Get-Module').AddParameter('Name', 'PSKoans') > $null + $module = $ps.Invoke() + $module.Name | Should -eq 'PSKoans' + } + finally { + $ps.Dispose() + } + } +} From 2c97e45a4aacc6b3b129f91513ae5e4b0b940e8f Mon Sep 17 00:00:00 2001 From: Joel Sallow <32407840+vexx32@users.noreply.github.com> Date: Sun, 5 Jul 2020 04:55:06 -0400 Subject: [PATCH 16/17] :memo: Update documentation for install/lists --- KoanIndex.md | 18 ++++++++++++++---- README.md | 9 ++------- 2 files changed, 16 insertions(+), 11 deletions(-) diff --git a/KoanIndex.md b/KoanIndex.md index 7f5c7972d..bd1d5314d 100644 --- a/KoanIndex.md +++ b/KoanIndex.md @@ -60,13 +60,18 @@ When adding or updating koan topics, please keep this file up to date. ### ActiveDirectory -| Folder | Topic | Position | -| ----------------------- | :------------------------------------------------------------- | :------: | -| Introduction | [AboutFiltering][ActiveDirectory\AboutFiltering] | 101 | +| Folder | Topic | Position | +| ------------ | :----------------------------------------------- | :------: | +| Introduction | [AboutFiltering][ActiveDirectory\AboutFiltering] | 101 | ### Dbatools -> TBD +| Folder | Topic | Position | +| -------- | :-------------------------------------------------------- | :------: | +| dbatools | [AboutDbaDatabase][dbatools\AboutDbaDatabase] | 1001 | +| dbatools | [AboutQueryingDatabases][dbatools\AboutQueryingDatabases] | 1002 | +| dbatools | [AboutNewDatabases][dbatools\AboutNewDatabases] | 1003 | +| dbatools | [AboutBackupDatabases][dbatools\AboutBackupDatabases] | 1004 | @@ -120,3 +125,8 @@ When adding or updating koan topics, please keep this file up to date. [ActiveDirectory\AboutFiltering]: PSKoans/Koans/Modules/ActiveDirectory/Introduction/AboutFiltering.Koans.ps1 + +[dbatools\AboutDbaDatabase]: PSKoans/Koans/Modules/dbatools/AboutDbaDatabase.Koans.ps1 +[dbatools\AboutQueryingDatabases]: PSKoans/Koans/Modules/dbatools/AboutQueryingDatabases.Koans.ps1 +[dbatools\AboutNewDatabases]: PSKoans/Koans/Modules/dbatools/AboutNewDatabases.Koans.ps1 +[dbatools\AboutBackupDatabases]: PSKoans/Koans/Modules/dbatools/AboutBackupDatabases.Koans.ps1 diff --git a/README.md b/README.md index db9b5bdd9..7ef670b1f 100644 --- a/README.md +++ b/README.md @@ -57,17 +57,12 @@ To install the latest version of Pester, use the appropriate command for your ve ```PowerShell # PS 5.1 (upgrade to latest Pester) -Install-Module Pester -Force -SkipPublisherCheck -Scope CurrentUser -MaximumVersion 4.99.99 +Install-Module Pester -Force -SkipPublisherCheck -Scope CurrentUser -MinimumVersion 5.0.2 # PS 6.0+ (Install Pester under current user) -Install-Module Pester -Scope CurrentUser -MaximumVersion 4.99.99 +Install-Module Pester -Scope CurrentUser -MinimumVersion 5.0.2 ``` -> :warning: **WARNING** -> -> PSKoans is not yet compatible with v5 of Pester, and I have (unwisely) neglected to set the -MaximumVersion in the PSKoans module manifest as yet. -> I will make some fixes shortly, but in the meantime you cannot have v5 of Pester installed if you would like to use PSKoans. - ## Getting Started ### Install from Gallery From 39edac748722597f86a25e150769adca5a77de85 Mon Sep 17 00:00:00 2001 From: Joel Sallow <32407840+vexx32@users.noreply.github.com> Date: Sun, 5 Jul 2020 21:55:16 -0400 Subject: [PATCH 17/17] :white_check_mark: Add tests for -Module --- Tests/Functions/Public/Show-Karma.Tests.ps1 | 29 ++++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/Tests/Functions/Public/Show-Karma.Tests.ps1 b/Tests/Functions/Public/Show-Karma.Tests.ps1 index 522adbe64..f0bd6fb8e 100644 --- a/Tests/Functions/Public/Show-Karma.Tests.ps1 +++ b/Tests/Functions/Public/Show-Karma.Tests.ps1 @@ -258,7 +258,7 @@ Describe 'Show-Karma' { } } - Mock 'Get-PSKoan' -ParameterFilter { $Topic -eq 'TestTopic' -and $Scope -eq 'User' } { + Mock 'Get-PSKoan' -ParameterFilter { $Scope -eq 'User' } { [PSCustomObject]@{ Path = $TestFile.FullName } } } @@ -287,6 +287,33 @@ Describe 'Show-Karma' { InModuleScope 'PSKoans' { $script:CurrentTopic } | Should -BeNullOrEmpty } + + $moduleCases = @( + @{ ModuleName = 'ActiveDirectory' } + @{ ModuleName = 'dbatools' } + ) + It 'opens the selected editor targeting koans for the module' -TestCases $moduleCases { + Set-PSKoanSetting -Name Editor -Value 'code' + $Result = Show-Karma -Contemplate -Module $ModuleName + + $Result.Editor | Should -BeExactly 'code' + $Result.Arguments[0] | Should -BeExactly '--goto' + $Result.Arguments[1] | Should -MatchExactly '"[^"]+":\d+' + $Result.Arguments[2] | Should -BeExactly '--reuse-window' + $Result.NoNewWindow | Should -BeTrue + + # Resolve-Path doesn't like embedded quotes + $Path = ($Result.Arguments[1] -split '(?<="):')[0] -replace '"' + $Path | Should -BeExactly (Resolve-Path -Path $Path).Path + + Should -Invoke 'Get-Command' -Times 1 -Exactly + Should -Invoke 'Start-Process' -Times 1 -Exactly + Should -Invoke 'Get-Karma' -ParameterFilter { $Module -eq $ModuleName } + Should -Invoke 'Get-PSKoan' -ParameterFilter { $IncludeModule -eq $ModuleName } + + InModuleScope 'PSKoans' { $script:CurrentTopic } | Should -BeNullOrEmpty + } + It 'opens the specified -Topic in the selected editor' { Set-PSKoanSetting -Name Editor -Value 'code'